mirror of
				https://github.com/Xahau/Validation-Ledger-Tx-Store-to-xPOP.git
				synced 2025-11-04 12:25:48 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			186 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			186 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						|
 * Richard Holland, XRPL Labs, 2021
 | 
						|
 * Originally part of https://github.com/RichardAH/xpop-generator/tree/master
 | 
						|
 */
 | 
						|
 | 
						|
import elliptic from 'elliptic'
 | 
						|
const ed25519 = elliptic.eddsa('ed25519')
 | 
						|
const secp256k1 = elliptic.ec('secp256k1')
 | 
						|
import { createHash } from 'crypto';
 | 
						|
import fetch from 'node-fetch'
 | 
						|
import address from 'ripple-address-codec'
 | 
						|
 | 
						|
const fetchUnl = (url, master_public_key) => {
 | 
						|
  return new Promise(async (resolve, reject) => {
 | 
						|
    try {
 | 
						|
      const codec = { address, }
 | 
						|
      const assert = (c, m) => {
 | 
						|
        if (!c) reject("Invalid manifest: " + (m ? m : ""));
 | 
						|
      }
 | 
						|
 | 
						|
      const parse_manifest = buf => {
 | 
						|
        let man = {}
 | 
						|
        let upto = 0
 | 
						|
 | 
						|
        let verify_fields = [Buffer.from('MAN\x00', 'utf-8')];
 | 
						|
        let last_signing = 0;
 | 
						|
 | 
						|
        // sequence number
 | 
						|
        assert(buf[upto++] == 0x24, "Missing Sequence Number")
 | 
						|
        man['Sequence'] = (buf[upto] << 24) + (buf[upto+1] << 16) + (buf[upto+2] << 8) + buf[upto+3]
 | 
						|
        upto += 4
 | 
						|
 | 
						|
        // public key
 | 
						|
        assert(buf[upto++] == 0x71, "Missing Public Key")       // type 7 = VL, 1 = PublicKey
 | 
						|
        assert(buf[upto++] == 33, "Missing Public Key size")    // one byte size
 | 
						|
        man['PublicKey'] = buf.slice(upto, upto + 33).toString('hex')
 | 
						|
        upto += 33
 | 
						|
 | 
						|
        // signing public key
 | 
						|
        assert(buf[upto++] == 0x73, "Missing Signing Public Key")       // type 7 = VL, 3 = SigningPubKey
 | 
						|
        assert(buf[upto++] == 33, "Missing Signing Public Key size")    // one byte size
 | 
						|
        man['SigningPubKey'] = buf.slice(upto, upto + 33).toString('hex')
 | 
						|
        upto += 33
 | 
						|
 | 
						|
        // signature
 | 
						|
        verify_fields.push(buf.slice(last_signing, upto))
 | 
						|
        assert(buf[upto++] == 0x76, "Missing Signature")    // type 7 = VL, 6 = Signature
 | 
						|
        let signature_size = buf[upto++];
 | 
						|
        man['Signature'] = buf.slice(upto, upto + signature_size).toString('hex')
 | 
						|
        upto += signature_size
 | 
						|
        last_signing = upto
 | 
						|
 | 
						|
        // domain field | optional
 | 
						|
        if (buf[upto] == 0x77) {
 | 
						|
          upto++
 | 
						|
          let domain_size = buf[upto++]
 | 
						|
          man['Domain'] = buf.slice(upto, upto + domain_size).toString('utf-8')
 | 
						|
          upto += domain_size
 | 
						|
        }
 | 
						|
 | 
						|
        // master signature
 | 
						|
        verify_fields.push(buf.slice(last_signing, upto))
 | 
						|
        assert(buf[upto++] == 0x70, "Missing Master Signature lead byte")   // type 7 = VL, 0 = uncommon field
 | 
						|
        assert(buf[upto++] == 0x12, "Missing Master Signature follow byte") // un field = 0x12 master signature
 | 
						|
        let master_size = buf[upto++];
 | 
						|
        man['MasterSignature'] = buf.slice(upto, upto + master_size).toString('hex')
 | 
						|
        upto += master_size
 | 
						|
        last_signing = upto // here in case more fields ever added below
 | 
						|
 | 
						|
        assert(upto == buf.length, "Extra bytes after end of manifest")
 | 
						|
 | 
						|
        // for signature verification
 | 
						|
        man.without_signing_fields = Buffer.concat(verify_fields)
 | 
						|
        return man;
 | 
						|
      }
 | 
						|
 | 
						|
      const unlData = await fetch(url)
 | 
						|
      const json = await unlData.json()
 | 
						|
 | 
						|
      // initial json validation
 | 
						|
      assert(json.public_key !== undefined, "public key missing from vl")
 | 
						|
      assert(json.signature !== undefined, "signature missing from vl")
 | 
						|
      assert(json.version !== undefined, "version missing from vl")
 | 
						|
      assert(json.manifest !== undefined, "manifest missing from vl")
 | 
						|
      assert(json.blob !== undefined, "blob missing from vl")
 | 
						|
      assert(json.version == 1, "vl version != 1")
 | 
						|
 | 
						|
      // check key is recognised
 | 
						|
      if (master_public_key)
 | 
						|
          assert(json.public_key.toUpperCase() == master_public_key.toUpperCase(),
 | 
						|
              "Provided VL key does not match")
 | 
						|
      else
 | 
						|
          master_public_key = json.public_key.toUpperCase()
 | 
						|
 | 
						|
      // parse blob
 | 
						|
      let blob = Buffer.from(json.blob, 'base64')
 | 
						|
 | 
						|
      // parse manifest
 | 
						|
      const manifest = parse_manifest(Buffer.from(json.manifest, 'base64'))
 | 
						|
 | 
						|
      // verify manifest signature and payload signature
 | 
						|
      // distinguish between ed25519 and secp256k1
 | 
						|
      let master_key = null;
 | 
						|
      if(master_public_key.startsWith('ED')) {
 | 
						|
        master_key = ed25519.keyFromPublic(master_public_key.slice(2), 'hex');
 | 
						|
        assert(master_key.verify(manifest.without_signing_fields, manifest.MasterSignature),
 | 
						|
          "Master signature in master manifest does not match ed25519 vl key")
 | 
						|
      } else {
 | 
						|
        master_key = secp256k1.keyFromPublic(master_public_key, 'hex');
 | 
						|
        assert(master_key.verify(manifest.without_signing_fields, manifest.MasterSignature),
 | 
						|
          "Master signature in master manifest does not match secp256k1 vl key");
 | 
						|
      }
 | 
						|
      
 | 
						|
      let signing_key = null;
 | 
						|
 | 
						|
      if(manifest.SigningPubKey.toUpperCase().startsWith('ED')) {
 | 
						|
        signing_key = ed25519.keyFromPublic(manifest.SigningPubKey.slice(2), 'hex')
 | 
						|
        assert(signing_key.verify(blob.toString('hex'), json.signature),
 | 
						|
          "Payload signature in mantifest failed verification with ed25519")
 | 
						|
      } else {
 | 
						|
        signing_key = secp256k1.keyFromPublic(manifest.SigningPubKey.toString('hex'), 'hex');
 | 
						|
        
 | 
						|
        //sha512 half the blob!
 | 
						|
        //https://xrpl.org/cryptographic-keys.html#key-derivation
 | 
						|
        let sha512Blob = createHash("sha512").update(blob);
 | 
						|
        let sha512HalfBuffer = sha512Blob.digest().slice(0,32);
 | 
						|
 | 
						|
        assert(signing_key.verify(sha512HalfBuffer, json.signature),
 | 
						|
          "Payload signature in mantifest failed verification with secp256k1")
 | 
						|
      }
 | 
						|
 | 
						|
      blob = JSON.parse(blob)
 | 
						|
 | 
						|
      assert(blob.validators !== undefined, "validators missing from blob")
 | 
						|
 | 
						|
      // parse manifests inside blob (actual validator list)
 | 
						|
      let unl = {}
 | 
						|
      for (let idx in blob.validators) {
 | 
						|
        assert(blob.validators[idx].manifest !== undefined,
 | 
						|
          "validators list in blob contains invalid entry (missing manifest)")
 | 
						|
        assert(blob.validators[idx].validation_public_key !== undefined,
 | 
						|
          "validators list in blob contains invalid entry (missing validation public key)")
 | 
						|
      
 | 
						|
        let manifest = parse_manifest(Buffer.from(blob.validators[idx].manifest, 'base64'))
 | 
						|
 | 
						|
        // verify signature
 | 
						|
 | 
						|
        let publicKey = blob.validators[idx].validation_public_key;
 | 
						|
 | 
						|
        if (publicKey.slice(0, 1) === 'n') {
 | 
						|
          const publicKeyBuffer = codec.address.decodeNodePublic(publicKey);
 | 
						|
          publicKey = publicKeyBuffer.toString("hex").toUpperCase();
 | 
						|
        }
 | 
						|
 | 
						|
        if(publicKey.toUpperCase().startsWith('ED')) {
 | 
						|
          signing_key = ed25519.keyFromPublic(publicKey.slice(2), 'hex')
 | 
						|
 | 
						|
          assert(signing_key.verify(manifest.without_signing_fields, manifest.MasterSignature),
 | 
						|
            "Validation manifest " + idx + " signature verification failed with ed25519");
 | 
						|
        } else {
 | 
						|
          signing_key = secp256k1.keyFromPublic(publicKey, 'hex');
 | 
						|
          const computedHash = createHash("sha512").update(manifest.without_signing_fields).digest().toString("hex").slice(0, 32);
 | 
						|
          assert(signing_key.verify(computedHash, manifest.MasterSignature),
 | 
						|
            "Validation manifest " + idx + " signature verification failed with secp256k1");
 | 
						|
        }
 | 
						|
 | 
						|
        blob.validators[idx].validation_public_key = Buffer.from(blob.validators[idx].validation_public_key, 'hex')
 | 
						|
        blob.validators[idx].manifest = manifest
 | 
						|
        
 | 
						|
        let nodepub = codec.address.encodeNodePublic(Buffer.from(manifest.SigningPubKey, 'hex'))
 | 
						|
        unl[nodepub] = manifest.SigningPubKey
 | 
						|
      }
 | 
						|
 | 
						|
      resolve({unl: {...unl}, vl: json})
 | 
						|
    } catch (e) {
 | 
						|
      console.log('Error fetching UNL', e)
 | 
						|
    }
 | 
						|
 | 
						|
    return resolve({})
 | 
						|
  })
 | 
						|
}
 | 
						|
 | 
						|
export {
 | 
						|
  fetchUnl,
 | 
						|
}
 |