UNPKG

liquidjs-lib

Version:

Client-side Liquid JavaScript library

500 lines (499 loc) 20 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.PsetOutput = exports.OutputDuplicateFieldError = void 0; const bufferutils_1 = require('../bufferutils'); const bip32_1 = require('./bip32'); const fields_1 = require('./fields'); const key_pair_1 = require('./key_pair'); const proprietary_data_1 = require('./proprietary_data'); const pset_1 = require('./pset'); const utils_1 = require('./utils'); class OutputDuplicateFieldError extends Error { constructor(message) { if (message) { message = 'Duplicated output ' + message; } super(message); } } exports.OutputDuplicateFieldError = OutputDuplicateFieldError; class PsetOutput { constructor(value, asset, script) { this.value = value || 0; this.asset = asset || Buffer.from([]); this.script = script; } static fromBuffer(r) { let kp; const output = new PsetOutput(); while (true) { try { kp = key_pair_1.KeyPair.fromBuffer(r); } catch (e) { if (e instanceof Error && e === key_pair_1.ErrEmptyKey) { output.sanityCheck(); return output; } throw e; } switch (kp.key.keyType) { case fields_1.OutputTypes.REDEEM_SCRIPT: if (output.redeemScript && output.redeemScript.length > 0) { throw new OutputDuplicateFieldError('redeem script'); } output.redeemScript = kp.value; break; case fields_1.OutputTypes.WITNESS_SCRIPT: if (output.witnessScript && output.witnessScript.length > 0) { throw new OutputDuplicateFieldError('witness script'); } output.witnessScript = kp.value; break; case fields_1.OutputTypes.BIP32_DERIVATION: const pubkey = kp.key.keyData; if (pubkey.length !== 33) { throw new Error('Invalid output bip32 pubkey length'); } if (!output.bip32Derivation) { output.bip32Derivation = []; } if (output.bip32Derivation.find((d) => d.pubkey.equals(pubkey))) { throw new OutputDuplicateFieldError('bip32 derivation'); } const { masterFingerprint, path } = (0, bip32_1.decodeBip32Derivation)(kp.value); output.bip32Derivation.push({ pubkey, masterFingerprint, path }); break; case fields_1.OutputTypes.AMOUNT: if (output.value > 0) { throw new OutputDuplicateFieldError('value'); } if (kp.value.length !== 8) { throw new Error('Invalid output amount length'); } output.value = (0, bufferutils_1.readUInt64LE)(kp.value, 0); break; case fields_1.OutputTypes.SCRIPT: if (output.script && output.script.length > 0) { throw new OutputDuplicateFieldError('script'); } output.script = kp.value; break; case fields_1.OutputTypes.TAP_BIP32_DERIVATION: const tapKey = kp.key.keyData; if (tapKey.length !== 33) { throw new Error('Invalid output bip32 derivation pubkey length'); } if (!output.tapBip32Derivation) { output.tapBip32Derivation = []; } const tapBip32Pubkey = kp.key.keyData; if ( output.tapBip32Derivation.find((d) => d.pubkey.equals(tapBip32Pubkey), ) ) { throw new OutputDuplicateFieldError('taproot bip32 derivation'); } const nHashes = bufferutils_1.varuint.decode(kp.value); const nHashesLen = bufferutils_1.varuint.encodingLength(nHashes); const bip32Deriv = (0, bip32_1.decodeBip32Derivation)( kp.value.slice(nHashesLen + nHashes * 32), ); const leafHashes = new Array(nHashes); for (let i = 0, _ofs = nHashesLen; i < nHashes; i++, _ofs += 32) { leafHashes[i] = kp.value.slice(_ofs, _ofs + 32); } output.tapBip32Derivation.push({ pubkey: tapBip32Pubkey, masterFingerprint: bip32Deriv.masterFingerprint, path: bip32Deriv.path, leafHashes, }); break; case fields_1.OutputTypes.TAP_TREE: if (output.tapTree) { throw new OutputDuplicateFieldError('taproot tree'); } let _offset = 0; const leaves = []; while (_offset < kp.value.length) { const depth = kp.value[_offset++]; const leafVersion = kp.value[_offset++]; const scriptLen = bufferutils_1.varuint.decode(kp.value, _offset); _offset += bufferutils_1.varuint.encodingLength(scriptLen); leaves.push({ depth, leafVersion, script: kp.value.slice(_offset, _offset + scriptLen), }); _offset += scriptLen; } output.tapTree = { leaves }; break; case fields_1.OutputTypes.TAP_INTERNAL_KEY: if (output.tapInternalKey && output.tapInternalKey.length > 0) { throw new OutputDuplicateFieldError('taproot internal key'); } if (kp.value.length !== 32) { throw new Error('Invalid output taproot internal key length'); } output.tapInternalKey = kp.value; break; case fields_1.OutputTypes.PROPRIETARY: const data = proprietary_data_1.ProprietaryData.fromKeyPair(kp); if (Buffer.compare(data.identifier, pset_1.magicPrefix) === 0) { switch (data.subType) { case fields_1.OutputProprietaryTypes.VALUE_COMMITMENT: if ( output.valueCommitment && output.valueCommitment.length > 0 ) { throw new OutputDuplicateFieldError('value commitment'); } if (kp.value.length !== 33) { throw new Error('Invalid output value commitment length'); } output.valueCommitment = kp.value; break; case fields_1.OutputProprietaryTypes.ASSET: if (output.asset && output.asset.length > 0) { throw new OutputDuplicateFieldError('asset'); } if (kp.value.length !== 32) { throw new Error('Invalid output asset length'); } output.asset = kp.value; break; case fields_1.OutputProprietaryTypes.ASSET_COMMITMENT: if ( output.assetCommitment && output.assetCommitment.length > 0 ) { throw new OutputDuplicateFieldError('asset commitment'); } if (kp.value.length !== 33) { throw new Error('Invalid output asset commitment length'); } output.assetCommitment = kp.value; break; case fields_1.OutputProprietaryTypes.VALUE_RANGEPROOF: if ( output.valueRangeproof && output.valueRangeproof.length > 0 ) { throw new OutputDuplicateFieldError('value range proof'); } output.valueRangeproof = kp.value; break; case fields_1.OutputProprietaryTypes.ASSET_SURJECTION_PROOF: if ( output.assetSurjectionProof && output.assetSurjectionProof.length > 0 ) { throw new OutputDuplicateFieldError('asset surjection proof'); } output.assetSurjectionProof = kp.value; break; case fields_1.OutputProprietaryTypes.BLINDING_PUBKEY: if (output.blindingPubkey && output.blindingPubkey.length > 0) { throw new OutputDuplicateFieldError('blinding pubkey'); } if (kp.value.length !== 33) { throw new Error('Invalid output blinding pubkey length'); } output.blindingPubkey = kp.value; break; case fields_1.OutputProprietaryTypes.ECDH_PUBKEY: if (output.ecdhPubkey && output.ecdhPubkey.length > 0) { throw new OutputDuplicateFieldError('ecdh pubkey'); } if (kp.value.length !== 33) { throw new Error('Invalid output ecdh pubkey length'); } output.ecdhPubkey = kp.value; break; case fields_1.OutputProprietaryTypes.BLINDER_INDEX: if (output.blinderIndex !== undefined) { throw new OutputDuplicateFieldError('blinder index'); } if (kp.value.length !== 4) { throw new Error('Invalid output blinder index length'); } output.blinderIndex = kp.value.readUInt32LE(); break; case fields_1.OutputProprietaryTypes.BLIND_VALUE_PROOF: if ( output.blindValueProof && output.blindValueProof.length > 0 ) { throw new OutputDuplicateFieldError('blind value proof'); } output.blindValueProof = kp.value; break; case fields_1.OutputProprietaryTypes.BLIND_ASSET_PROOF: if ( output.blindAssetProof && output.blindAssetProof.length > 0 ) { throw new OutputDuplicateFieldError('blind asset proof'); } output.blindAssetProof = kp.value; break; default: if (!output.proprietaryData) { output.proprietaryData = []; } output.proprietaryData.push(data); } } break; default: if (!output.unknowns) { output.unknowns = []; } output.unknowns.push(kp); break; } } } sanityCheck() { const valueCommitSet = this.valueCommitment && this.valueCommitment.length > 0; const blindValueProofSet = this.blindValueProof && this.blindValueProof.length > 0; if (this.value > 0 && valueCommitSet !== blindValueProofSet) { throw new Error('Missing output value commitment or blind proof'); } const assetCommitSet = this.assetCommitment && this.assetCommitment.length > 0; const blindAssetProofSet = this.blindAssetProof && this.blindAssetProof.length > 0; if (!assetCommitSet && (!this.asset || this.asset.length === 0)) { throw new Error('Missing output asset'); } if ( this.asset && this.asset.length > 0 && assetCommitSet !== blindAssetProofSet ) { throw new Error('Missing output asset commitment or blind proof'); } if (this.isPartiallyBlinded() && !this.isFullyBlinded()) { throw new Error( 'Output is partially blinded while it must be either unblinded or fully blinded', ); } if (this.isFullyBlinded() && this.blinderIndex > 0) { throw new Error('Blinder index must be unset for fully blinded output'); } return this; } needsBlinding() { return this.blindingPubkey && this.blindingPubkey.length > 0; } isPartiallyBlinded() { return ( (this.valueCommitment && this.valueCommitment.length > 0) || (this.assetCommitment && this.assetCommitment.length > 0) || (this.valueRangeproof && this.valueRangeproof.length > 0) || (this.assetSurjectionProof && this.assetSurjectionProof.length > 0) || (this.ecdhPubkey && this.ecdhPubkey.length > 0) ); } isFullyBlinded() { return ( this.valueCommitment && this.valueCommitment.length > 0 && this.assetCommitment && this.assetCommitment.length > 0 && this.valueRangeproof && this.valueRangeproof.length > 0 && (this.assetSurjectionProof && this.assetSurjectionProof.length) > 0 && this.ecdhPubkey && this.ecdhPubkey.length > 0 ); } isTaproot() { return !!( this.tapInternalKey || this.tapTree || (this.tapBip32Derivation && this.tapBip32Derivation.length) || (this.script && (0, utils_1.isP2TR)(this.script)) ); } toBuffer() { const keyPairs = this.getKeyPairs(); const kpBuf = keyPairs.map((kp) => kp.toBuffer()); let size = 0; kpBuf.forEach((buf) => { size += buf.length; }); const w = bufferutils_1.BufferWriter.withCapacity(size); kpBuf.forEach((buf) => w.writeSlice(buf)); return w.buffer; } getKeyPairs() { const keyPairs = []; if (this.redeemScript && this.redeemScript.length > 0) { const key = new key_pair_1.Key(fields_1.OutputTypes.REDEEM_SCRIPT); keyPairs.push(new key_pair_1.KeyPair(key, this.redeemScript)); } if (this.witnessScript && this.witnessScript.length > 0) { const key = new key_pair_1.Key(fields_1.OutputTypes.WITNESS_SCRIPT); keyPairs.push(new key_pair_1.KeyPair(key, this.witnessScript)); } if (this.bip32Derivation && this.bip32Derivation.length > 0) { this.bip32Derivation.forEach(({ pubkey, masterFingerprint, path }) => { const key = new key_pair_1.Key( fields_1.OutputTypes.BIP32_DERIVATION, pubkey, ); const value = (0, bip32_1.encodeBIP32Derivation)( masterFingerprint, path, ); keyPairs.push(new key_pair_1.KeyPair(key, value)); }); } if (this.tapBip32Derivation && this.tapBip32Derivation.length > 0) { this.tapBip32Derivation.forEach( ({ pubkey, masterFingerprint, path, leafHashes }) => { const key = new key_pair_1.Key( fields_1.OutputTypes.TAP_BIP32_DERIVATION, pubkey, ); const nHashesLen = bufferutils_1.varuint.encodingLength( leafHashes.length, ); const nHashesBuf = Buffer.allocUnsafe(nHashesLen); bufferutils_1.varuint.encode(leafHashes.length, nHashesBuf); const value = Buffer.concat([ nHashesBuf, ...leafHashes, (0, bip32_1.encodeBIP32Derivation)(masterFingerprint, path), ]); keyPairs.push(new key_pair_1.KeyPair(key, value)); }, ); } if (this.tapTree) { const key = new key_pair_1.Key(fields_1.OutputTypes.TAP_TREE); const bufs = [].concat( ...this.tapTree.leaves.map((tapLeaf) => [ Buffer.of(tapLeaf.depth, tapLeaf.leafVersion), bufferutils_1.varuint.encode(tapLeaf.script.length), tapLeaf.script, ]), ); const value = Buffer.concat(bufs); keyPairs.push(new key_pair_1.KeyPair(key, value)); } if (this.tapInternalKey && this.tapInternalKey.length > 0) { const key = new key_pair_1.Key(fields_1.OutputTypes.TAP_INTERNAL_KEY); keyPairs.push(new key_pair_1.KeyPair(key, this.tapInternalKey)); } const amountKey = new key_pair_1.Key(fields_1.OutputTypes.AMOUNT); const amount = Buffer.allocUnsafe(8); (0, bufferutils_1.writeUInt64LE)(amount, this.value, 0); keyPairs.push(new key_pair_1.KeyPair(amountKey, amount)); const scriptKey = new key_pair_1.Key(fields_1.OutputTypes.SCRIPT); keyPairs.push( new key_pair_1.KeyPair(scriptKey, this.script || Buffer.alloc(0)), ); if (this.valueCommitment && this.valueCommitment.length > 0) { const keyData = proprietary_data_1.ProprietaryData.proprietaryKey( fields_1.OutputProprietaryTypes.VALUE_COMMITMENT, ); const key = new key_pair_1.Key(fields_1.OutputTypes.PROPRIETARY, keyData); keyPairs.push(new key_pair_1.KeyPair(key, this.valueCommitment)); } if (this.assetCommitment && this.assetCommitment.length > 0) { const keyData = proprietary_data_1.ProprietaryData.proprietaryKey( fields_1.OutputProprietaryTypes.ASSET_COMMITMENT, ); const key = new key_pair_1.Key(fields_1.OutputTypes.PROPRIETARY, keyData); keyPairs.push(new key_pair_1.KeyPair(key, this.assetCommitment)); } if (this.asset && this.asset.length > 0) { const keyData = proprietary_data_1.ProprietaryData.proprietaryKey( fields_1.OutputProprietaryTypes.ASSET, ); const key = new key_pair_1.Key(fields_1.OutputTypes.PROPRIETARY, keyData); keyPairs.push(new key_pair_1.KeyPair(key, this.asset)); } if (this.valueRangeproof && this.valueRangeproof.length > 0) { const keyData = proprietary_data_1.ProprietaryData.proprietaryKey( fields_1.OutputProprietaryTypes.VALUE_RANGEPROOF, ); const key = new key_pair_1.Key(fields_1.OutputTypes.PROPRIETARY, keyData); keyPairs.push(new key_pair_1.KeyPair(key, this.valueRangeproof)); } if (this.assetSurjectionProof && this.assetSurjectionProof.length > 0) { const keyData = proprietary_data_1.ProprietaryData.proprietaryKey( fields_1.OutputProprietaryTypes.ASSET_SURJECTION_PROOF, ); const key = new key_pair_1.Key(fields_1.OutputTypes.PROPRIETARY, keyData); keyPairs.push(new key_pair_1.KeyPair(key, this.assetSurjectionProof)); } if (this.blindingPubkey && this.blindingPubkey.length > 0) { const keyData = proprietary_data_1.ProprietaryData.proprietaryKey( fields_1.OutputProprietaryTypes.BLINDING_PUBKEY, ); const key = new key_pair_1.Key(fields_1.OutputTypes.PROPRIETARY, keyData); keyPairs.push(new key_pair_1.KeyPair(key, this.blindingPubkey)); } if (this.ecdhPubkey && this.ecdhPubkey.length > 0) { const keyData = proprietary_data_1.ProprietaryData.proprietaryKey( fields_1.OutputProprietaryTypes.ECDH_PUBKEY, ); const key = new key_pair_1.Key(fields_1.OutputTypes.PROPRIETARY, keyData); keyPairs.push(new key_pair_1.KeyPair(key, this.ecdhPubkey)); } const proprietaryKeyData = proprietary_data_1.ProprietaryData.proprietaryKey( fields_1.OutputProprietaryTypes.BLINDER_INDEX, ); const blinderIndexKey = new key_pair_1.Key( fields_1.OutputTypes.PROPRIETARY, proprietaryKeyData, ); const blinderIndex = Buffer.allocUnsafe(4); let bi = 0; if (this.blinderIndex > 0) { bi = this.blinderIndex; } blinderIndex.writeUInt32LE(bi); keyPairs.push(new key_pair_1.KeyPair(blinderIndexKey, blinderIndex)); if (this.blindValueProof && this.blindValueProof.length > 0) { const keyData = proprietary_data_1.ProprietaryData.proprietaryKey( fields_1.OutputProprietaryTypes.BLIND_VALUE_PROOF, ); const key = new key_pair_1.Key(fields_1.OutputTypes.PROPRIETARY, keyData); keyPairs.push(new key_pair_1.KeyPair(key, this.blindValueProof)); } if (this.blindAssetProof && this.blindAssetProof.length > 0) { const keyData = proprietary_data_1.ProprietaryData.proprietaryKey( fields_1.OutputProprietaryTypes.BLIND_ASSET_PROOF, ); const key = new key_pair_1.Key(fields_1.OutputTypes.PROPRIETARY, keyData); keyPairs.push(new key_pair_1.KeyPair(key, this.blindAssetProof)); } if (this.proprietaryData && this.proprietaryData.length > 0) { this.proprietaryData.forEach((data) => { const keyData = proprietary_data_1.ProprietaryData.proprietaryKey( data.subType, data.keyData, ); const key = new key_pair_1.Key( fields_1.OutputTypes.PROPRIETARY, keyData, ); keyPairs.push(new key_pair_1.KeyPair(key, data.value)); }); } keyPairs.concat(this.unknowns || []); return keyPairs; } } exports.PsetOutput = PsetOutput;