UNPKG

liquidjs-lib

Version:

Client-side Liquid JavaScript library

793 lines (792 loc) 26.3 kB
'use strict'; var __createBinding = (this && this.__createBinding) || (Object.create ? function (o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if ( !desc || ('get' in desc ? !m.__esModule : desc.writable || desc.configurable) ) { desc = { enumerable: true, get: function () { return m[k]; }, }; } Object.defineProperty(o, k2, desc); } : function (o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; }); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? function (o, v) { Object.defineProperty(o, 'default', { enumerable: true, value: v }); } : function (o, v) { o['default'] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== 'default' && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, '__esModule', { value: true }); exports.Updater = void 0; const address_1 = require('../address'); const asset_1 = require('../asset'); const issuance_1 = require('../issuance'); const creator_1 = require('./creator'); const bscript = __importStar(require('../script')); function processOutputDestination(dest) { if (typeof dest === 'string') { const script = (0, address_1.toOutputScript)(dest); if ((0, address_1.isConfidential)(dest)) return { script, blindingPublicKey: (0, address_1.fromConfidential)(dest).blindingKey, }; return { script }; } return dest; } class Updater { constructor(pset) { pset.sanityCheck(); this.pset = pset; } addInputs(ins) { const pset = this.pset.copy(); ins.forEach((input) => { const creatorInput = new creator_1.CreatorInput( input.txid, input.txIndex, input.sequence, input.heightLocktime, input.timeLocktime, ); creatorInput.validate(); pset.addInput(creatorInput.toPartialInput()); // we know at this point, index can't be negative const inputIndex = pset.inputs.length - 1; if (input.witnessUtxo) this.addInWitnessUtxo(inputIndex, input.witnessUtxo); if (input.witnessUtxo && input.witnessUtxo.rangeProof) { this.addInUtxoRangeProof(inputIndex, input.witnessUtxo.rangeProof); } if (input.witnessScript) this.addInWitnessScript(inputIndex, input.witnessScript); if (input.nonWitnessUtxo) this.addInNonWitnessUtxo(inputIndex, input.nonWitnessUtxo); if (input.nonWitnessUtxo && input.nonWitnessUtxo.outs[input.txIndex]) { const previousOutput = input.nonWitnessUtxo.outs[input.txIndex]; if (previousOutput.rangeProof) this.addInUtxoRangeProof(inputIndex, previousOutput.rangeProof); } if (input.sighashType !== undefined) this.addInSighashType(inputIndex, input.sighashType); if (input.tapInternalKey) this.addInTapInternalKey(inputIndex, input.tapInternalKey); if (input.tapLeafScript) this.addInTapLeafScript(inputIndex, input.tapLeafScript); if (input.tapMerkleRoot) this.addInTapMerkleRoot(inputIndex, input.tapMerkleRoot); if (input.issaunceOpts) this.addInIssuance(inputIndex, input.issaunceOpts); if (input.reissuanceOpts) this.addInReissuance(inputIndex, input.reissuanceOpts); if (input.explicitAsset) { this.addInExplicitAsset( inputIndex, input.explicitAsset, input.explicitAssetProof ?? Buffer.alloc(0), ); } if (input.explicitValue) { this.addInExplicitValue( inputIndex, input.explicitValue, input.explicitValueProof ?? Buffer.alloc(0), ); } }); pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addOutputs(outs) { const pset = this.pset.copy(); outs.forEach((output) => { const creatorOutput = new creator_1.CreatorOutput( output.asset, output.amount, output.script, output.blindingPublicKey, output.blinderIndex, ); creatorOutput.validate(); pset.addOutput(creatorOutput.toPartialOutput()); }); pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInNonWitnessUtxo(inIndex, nonWitnessUtxo) { this.validateInputIndex(inIndex); const pset = this.pset.copy(); const txid = nonWitnessUtxo.getHash(false); if (!txid.equals(pset.inputs[inIndex].previousTxid)) { throw new Error('Non-witness utxo hash does not match prevout txid'); } pset.inputs[inIndex].nonWitnessUtxo = nonWitnessUtxo; pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInWitnessUtxo(inIndex, witnessUtxo) { this.validateInputIndex(inIndex); const pset = this.pset.copy(); pset.inputs[inIndex].witnessUtxo = witnessUtxo; pset.inputs[inIndex].utxoRangeProof = witnessUtxo.rangeProof; pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInRedeemScript(inIndex, redeemScript) { this.validateInputIndex(inIndex); const pset = this.pset.copy(); pset.inputs[inIndex].redeemScript = redeemScript; pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInWitnessScript(inIndex, witnessScript) { this.validateInputIndex(inIndex); const pset = this.pset.copy(); pset.inputs[inIndex].witnessScript = witnessScript; pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInBIP32Derivation(inIndex, d) { this.validateInputIndex(inIndex); if (d.pubkey.length !== 33) { throw new Error('Invalid pubkey length'); } const pset = this.pset.copy(); if (!pset.inputs[inIndex].bip32Derivation) { pset.inputs[inIndex].bip32Derivation = []; } if ( pset.inputs[inIndex].bip32Derivation.some(({ pubkey }) => pubkey.equals(d.pubkey), ) ) { throw new Error('Duplicated bip32 derivation pubkey'); } pset.inputs[inIndex].bip32Derivation.push(d); pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInSighashType(inIndex, sighashType) { this.validateInputIndex(inIndex); if (sighashType < 0) { throw new Error('Invalid sighash type'); } const pset = this.pset.copy(); pset.inputs[inIndex].sighashType = sighashType; pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInUtxoRangeProof(inIndex, proof) { this.validateInputIndex(inIndex); const pset = this.pset.copy(); pset.inputs[inIndex].utxoRangeProof = proof; pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInIssuance(inIndex, args) { this.validateIssuanceInput(inIndex); validateAddInIssuanceArgs(args); const pset = this.pset.copy(); const assetAmount = args.assetAmount || 0; const tokenAmount = args.tokenAmount || 0; const issuance = (0, issuance_1.newIssuance)( assetAmount, tokenAmount, args.contract, ); pset.inputs[inIndex].issuanceValue = assetAmount; pset.inputs[inIndex].issuanceInflationKeys = tokenAmount; pset.inputs[inIndex].issuanceAssetEntropy = issuance.assetEntropy; pset.inputs[inIndex].issuanceBlindingNonce = issuance.assetBlindingNonce; pset.inputs[inIndex].blindedIssuance = args.blindedIssuance !== undefined ? args.blindedIssuance : true; const entropy = (0, issuance_1.generateEntropy)( { txHash: pset.inputs[inIndex].previousTxid, vout: pset.inputs[inIndex].previousTxIndex, }, issuance.assetEntropy, ); if (assetAmount > 0) { const issuedAsset = asset_1.AssetHash.fromBytes( (0, issuance_1.calculateAsset)(entropy), ).hex; const { blindingPublicKey, script } = processOutputDestination( args.assetAddress, ); const output = new creator_1.CreatorOutput( issuedAsset, assetAmount, script, blindingPublicKey, blindingPublicKey ? inIndex : undefined, ); pset.addOutput(output.toPartialOutput()); } if (tokenAmount > 0) { const reissuanceToken = asset_1.AssetHash.fromBytes( (0, issuance_1.calculateReissuanceToken)( entropy, args.blindedIssuance ?? true, ), ).hex; const { blindingPublicKey, script } = processOutputDestination( args.tokenAddress, ); const output = new creator_1.CreatorOutput( reissuanceToken, tokenAmount, script, blindingPublicKey, blindingPublicKey ? inIndex : undefined, ); pset.addOutput(output.toPartialOutput()); } pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInReissuance(inIndex, args) { this.validateReissuanceInput(inIndex); validateAddInReissuanceArgs(args); const pset = this.pset.copy(); const entropy = typeof args.entropy === 'string' ? Buffer.from(args.entropy, 'hex').reverse() : args.entropy; const blindingNonce = typeof args.tokenAssetBlinder === 'string' ? Buffer.from(args.tokenAssetBlinder, 'hex').reverse() : args.tokenAssetBlinder; const asset = asset_1.AssetHash.fromBytes( (0, issuance_1.calculateAsset)(entropy), ).hex; const reissuanceToken = asset_1.AssetHash.fromBytes( (0, issuance_1.calculateReissuanceToken)( entropy, args.initialIssuanceBlinded ?? true, ), ).hex; pset.inputs[inIndex].issuanceAssetEntropy = entropy; pset.inputs[inIndex].issuanceBlindingNonce = blindingNonce; pset.inputs[inIndex].issuanceValue = args.assetAmount; pset.inputs[inIndex].issuanceInflationKeys = 0; if (args.blindedIssuance !== undefined) { pset.inputs[inIndex].blindedIssuance = args.blindedIssuance; } if (args.assetAddress) { const { blindingPublicKey, script } = processOutputDestination( args.assetAddress, ); const assetOutput = new creator_1.CreatorOutput( asset, args.assetAmount, script, blindingPublicKey, blindingPublicKey ? inIndex : undefined, ); pset.addOutput(assetOutput.toPartialOutput()); } if (args.tokenAddress) { const { blindingPublicKey, script } = processOutputDestination( args.tokenAddress, ); const tokenOutput = new creator_1.CreatorOutput( reissuanceToken, args.tokenAmount, script, blindingPublicKey, blindingPublicKey ? inIndex : undefined, ); pset.addOutput(tokenOutput.toPartialOutput()); } pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInPartialSignature(inIndex, ps, validator) { this.validateInputIndex(inIndex); // ensure the pubkey and signature are valid validatePartialSignature(ps); // check for duplicates if ( (this.pset.inputs[inIndex].partialSigs || []).some(({ pubkey }) => pubkey.equals(ps.pubkey), ) ) { throw new Error('Duplicated signature pubkey'); } const pset = this.pset.copy(); // validate signature against input's preimage and pubkey const sighashType = ps.signature.slice(-1)[0]; const preimage = pset.getInputPreimage(inIndex, sighashType); const { signature } = bscript.signature.decode(ps.signature); if (!validator(ps.pubkey, preimage, signature)) { throw new Error('Invalid signature'); } if (!pset.inputs[inIndex].partialSigs) { pset.inputs[inIndex].partialSigs = []; } pset.inputs[inIndex].partialSigs.push(ps); pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInTimeLocktime(inIndex, locktime) { this.validateInputIndex(inIndex); if (locktime < 0) { throw new Error('Invalid required time locktime'); } const pset = this.pset.copy(); pset.inputs[inIndex].requiredTimeLocktime = locktime; pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInHeightLocktime(inIndex, locktime) { this.validateInputIndex(inIndex); if (locktime < 0) { throw new Error('Invalid required height locktime'); } const pset = this.pset.copy(); pset.inputs[inIndex].requiredHeightLocktime = locktime; pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInTapKeySig(inIndex, sig, genesisBlockHash, validator) { this.validateInputIndex(inIndex); if (sig.length !== 64 && sig.length !== 65) { throw new Error('Invalid taproot key signature length'); } if (genesisBlockHash.length !== 32) { throw new Error('Invalid genesis block hash length'); } const pset = this.pset.copy(); const input = pset.inputs[inIndex]; if (!input.getUtxo()) { throw new Error('Missing input witness utxo'); } if (input.sighashType === undefined) { throw new Error('Missing input sighash type'); } const tweakedKey = input.getUtxo().script.slice(2); const sighash = pset.getInputPreimage( inIndex, input.sighashType, genesisBlockHash, ); if (!validator(tweakedKey, sighash, sig)) { throw new Error(`Invalid taproot key signature for input ${inIndex}`); } pset.inputs[inIndex].tapKeySig = sig; pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInTapScriptSig(inIndex, sig, genesisBlockHash, validator) { this.validateInputIndex(inIndex); if (sig.pubkey.length !== 32) { throw new Error('Invalid xonly pubkey length'); } if (sig.leafHash.length !== 32) { throw new Error('Invalid leaf hash length'); } if (sig.signature.length !== 64 && sig.signature.length !== 65) { throw new Error('Invalid signature length'); } if (genesisBlockHash.length !== 32) { throw new Error('Invalid genesis block hash length'); } const pset = this.pset.copy(); const input = pset.inputs[inIndex]; if (input.sighashType === undefined) { throw new Error('Missing input sighash type'); } const sighash = pset.getInputPreimage( inIndex, input.sighashType, genesisBlockHash, sig.leafHash, ); if (!validator(sig.pubkey, sighash, sig.signature)) { throw new Error(`Invalid taproot script signature for input ${inIndex}`); } if (!pset.inputs[inIndex].tapScriptSig) { pset.inputs[inIndex].tapScriptSig = []; } pset.inputs[inIndex].tapScriptSig.push(sig); pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInTapLeafScript(inIndex, tapLeafScript) { this.validateInputIndex(inIndex); const pset = this.pset.copy(); if (!pset.inputs[inIndex].tapLeafScript) { pset.inputs[inIndex].tapLeafScript = []; } pset.inputs[inIndex].tapLeafScript.push(tapLeafScript); pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInTapBIP32Derivation(inIndex, d) { this.validateInputIndex(inIndex); if (d.pubkey.length !== 33) { throw new Error('Invalid input taproot pubkey length'); } const pset = this.pset.copy(); if (!pset.inputs[inIndex].tapBip32Derivation) { pset.inputs[inIndex].tapBip32Derivation = []; } if ( pset.inputs[inIndex].bip32Derivation.some(({ pubkey }) => pubkey.equals(d.pubkey), ) ) { throw new Error('Duplicated taproot bip32 derivation pubkey'); } pset.inputs[inIndex].tapBip32Derivation.push(d); pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInTapInternalKey(inIndex, tapInternalKey) { this.validateInputIndex(inIndex); if (tapInternalKey.length !== 32) { throw new Error('Invalid taproot internal key length'); } if ( this.pset.inputs[inIndex].tapInternalKey && this.pset.inputs[inIndex].tapInternalKey.length > 0 ) { throw new Error('Duplicated taproot internal key'); } const pset = this.pset.copy(); pset.inputs[inIndex].tapInternalKey = tapInternalKey; pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInTapMerkleRoot(inIndex, tapMerkleRoot) { this.validateInputIndex(inIndex); if (tapMerkleRoot.length !== 32) { throw new Error('Invalid taproot merkle root length'); } if ( this.pset.inputs[inIndex].tapMerkleRoot && this.pset.inputs[inIndex].tapMerkleRoot.length > 0 ) { throw new Error('Duplicated taproot merkle root'); } const pset = this.pset.copy(); pset.inputs[inIndex].tapMerkleRoot = tapMerkleRoot; pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInExplicitValue(inIndex, explicitValue, explicitValueProof) { this.validateInputIndex(inIndex); const pset = this.pset.copy(); pset.inputs[inIndex].explicitValue = explicitValue; pset.inputs[inIndex].explicitValueProof = explicitValueProof; pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addInExplicitAsset(inIndex, explicitAsset, explicitAssetProof) { this.validateInputIndex(inIndex); const pset = this.pset.copy(); pset.inputs[inIndex].explicitAsset = explicitAsset; pset.inputs[inIndex].explicitAssetProof = explicitAssetProof; pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addOutBIP32Derivation(outIndex, d) { this.validateOutputIndex(outIndex); if (d.pubkey.length !== 33) { throw new Error('Invalid pubkey length'); } const pset = this.pset.copy(); if (!pset.outputs[outIndex].bip32Derivation) { pset.outputs[outIndex].bip32Derivation = []; } if ( pset.outputs[outIndex].bip32Derivation.some(({ pubkey }) => pubkey.equals(d.pubkey), ) ) { throw new Error('Duplicated bip32 derivation pubkey'); } pset.outputs[outIndex].bip32Derivation.push(d); pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addOutRedeemScript(outIndex, redeemScript) { this.validateOutputIndex(outIndex); const pset = this.pset.copy(); pset.outputs[outIndex].redeemScript = redeemScript; pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addOutWitnessScript(outIndex, witnessScript) { this.validateOutputIndex(outIndex); const pset = this.pset.copy(); pset.outputs[outIndex].witnessScript = witnessScript; pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addOutTapInternalKey(outIndex, tapInternalKey) { this.validateOutputIndex(outIndex); if (tapInternalKey.length !== 32) { throw new Error('Invalid taproot internal key length'); } if ( this.pset.outputs[outIndex].tapInternalKey && this.pset.outputs[outIndex].tapInternalKey.length > 0 ) { throw new Error('Duplicated taproot internal key'); } const pset = this.pset.copy(); pset.outputs[outIndex].tapInternalKey = tapInternalKey; pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addOutTapTree(outIndex, tapTree) { this.validateOutputIndex(outIndex); if (this.pset.outputs[outIndex].tapTree) { throw new Error('Duplicated taproot tree'); } const pset = this.pset.copy(); pset.outputs[outIndex].tapTree = tapTree; pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } addOutTapBIP32Derivation(outIndex, d) { this.validateOutputIndex(outIndex); if (d.pubkey.length !== 33) { throw new Error('Invalid output taproot pubkey length'); } const pset = this.pset.copy(); if (!pset.outputs[outIndex].tapBip32Derivation) { pset.outputs[outIndex].tapBip32Derivation = []; } if ( pset.outputs[outIndex].bip32Derivation.some(({ pubkey }) => pubkey.equals(d.pubkey), ) ) { throw new Error('Duplicated taproot bip32 derivation pubkey'); } pset.outputs[outIndex].tapBip32Derivation.push(d); pset.sanityCheck(); this.pset.globals = pset.globals; this.pset.inputs = pset.inputs; this.pset.outputs = pset.outputs; return this; } validateIssuanceInput(inIndex) { this.validateInputIndex(inIndex); const input = this.pset.inputs[inIndex]; if (input.issuanceAssetEntropy && input.issuanceAssetEntropy.length > 0) { throw new Error('Input ' + inIndex + ' already has an issuance'); } } validateReissuanceInput(inIndex) { this.validateInputIndex(inIndex); const input = this.pset.inputs[inIndex]; if (input.issuanceAssetEntropy && input.issuanceAssetEntropy.length > 0) { throw new Error(`Input ${inIndex} already has an issuance`); } const prevout = input.getUtxo(); if (!prevout) { throw new Error('Input is missing prevout (non-)witness utxo'); } if (prevout.nonce.length <= 1) { throw new Error('Input prevout (non-)witness utxo must be confidential'); } } validateOutputIndex(outIndex) { if (outIndex < 0 || outIndex >= this.pset.globals.outputCount) { throw new Error('Output index out of range'); } } validateInputIndex(inIndex) { if (inIndex < 0 || inIndex >= this.pset.globals.inputCount) { throw new Error('Input index out of range'); } } } exports.Updater = Updater; function validateAddInIssuanceArgs(args) { const assetAmount = args.assetAmount || 0; const tokenAmount = args.tokenAmount || 0; if (assetAmount <= 0 && tokenAmount <= 0) { throw new Error('Either asset or token amounts must be a positive number'); } if (assetAmount > 0) { if (!args.assetAddress) { throw new Error( 'Asset address must be defined if asset amount is non-zero', ); } } if (tokenAmount > 0) { if (!args.tokenAddress) { throw new Error( 'Token address must be defined if token amount is non-zero', ); } } if (!matchAddressesNetworkType(args.assetAddress, args.tokenAddress)) { throw new Error('Asset and token addresses must be of same network'); } } function validateAddInReissuanceArgs(args) { const entropy = typeof args.entropy === 'string' ? Buffer.from(args.entropy, 'hex').reverse() : args.entropy; if (entropy.length !== 32) { throw new Error('Invalid entropy length'); } const blinder = typeof args.tokenAssetBlinder === 'string' ? Buffer.from(args.tokenAssetBlinder, 'hex').reverse() : args.tokenAssetBlinder; if (blinder.length !== 32) { throw new Error('Invalid token asset blinder length'); } if (args.assetAmount <= 0) { throw new Error('Asset amount must be a positive number'); } if (args.tokenAmount <= 0) { throw new Error('Token amount must be a positive number'); } if (!args.assetAddress) { throw new Error('Missing asset address'); } if (!args.assetAddress) { throw new Error('Missing token address'); } if (!matchAddressesNetworkType(args.assetAddress, args.tokenAddress)) { throw new Error( 'Asset and token addresses must be both of same network and both confidential', ); } } function matchAddressesNetworkType(addrA, addrB) { if (!addrA || !addrB) { return true; } if (typeof addrA === 'string' && typeof addrB === 'string') { const netA = (0, address_1.getNetwork)(addrA); const netB = (0, address_1.getNetwork)(addrB); return netA.name === netB.name; } return true; } function validatePartialSignature(psig) { if (psig.pubkey.length !== 33) { throw new Error('Invalid pubkey length'); } bscript.signature.decode(psig.signature); }