From e10324f850bcaf371d5363ae3bbb3e9370dee53a Mon Sep 17 00:00:00 2001 From: junderw Date: Mon, 2 Dec 2019 15:58:04 +0900 Subject: [PATCH] PSBT Bugfix for multiple of same pubkey in p2ms --- src/psbt.js | 20 ++++++++++++++---- test/integration/transactions.spec.ts | 30 +++++++++++++++++++++++++++ ts_src/psbt.ts | 24 +++++++++++++++++---- 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/src/psbt.js b/src/psbt.js index 57a159c..163d6d2 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -568,15 +568,27 @@ function canFinalize(input, script, scriptType) { return hasSigs(1, input.partialSig); case 'multisig': const p2ms = payments.p2ms({ output: script }); - return hasSigs(p2ms.m, input.partialSig); + return hasSigs(p2ms.m, input.partialSig, p2ms.pubkeys); default: return false; } } -function hasSigs(neededSigs, partialSig) { +function hasSigs(neededSigs, partialSig, pubkeys) { if (!partialSig) return false; - if (partialSig.length > neededSigs) throw new Error('Too many signatures'); - return partialSig.length === neededSigs; + let sigs; + if (pubkeys) { + sigs = pubkeys + .map(pkey => { + const pubkey = ecpair_1.fromPublicKey(pkey, { compressed: true }) + .publicKey; + return partialSig.filter(pSig => pSig.pubkey.equals(pubkey))[0]; + }) + .filter(v => !!v); + } else { + sigs = partialSig; + } + if (sigs.length > neededSigs) throw new Error('Too many signatures'); + return sigs.length === neededSigs; } function isFinalized(input) { return !!input.finalScriptSig || !!input.finalScriptWitness; diff --git a/test/integration/transactions.spec.ts b/test/integration/transactions.spec.ts index 2bcee3b..34feec5 100644 --- a/test/integration/transactions.spec.ts +++ b/test/integration/transactions.spec.ts @@ -530,6 +530,36 @@ describe('bitcoinjs-lib (transactions with psbt)', () => { }, ); + it( + 'can create (and broadcast via 3PBP) a Transaction, w/ a ' + + 'P2SH(P2MS(2 of 2)) input with nonWitnessUtxo', + async () => { + const myKey = bitcoin.ECPair.makeRandom({ network: regtest }); + const myKeys = [ + myKey, + bitcoin.ECPair.fromPrivateKey(myKey.privateKey!, { network: regtest }), + ]; + const p2sh = createPayment('p2sh-p2ms(2 of 2)', myKeys); + const inputData = await getInputData(5e4, p2sh.payment, false, 'p2sh'); + const psbt = new bitcoin.Psbt({ network: regtest }) + .addInput(inputData) + .addOutput({ + address: regtestUtils.RANDOM_ADDRESS, + value: 2e4, + }) + .signInput(0, p2sh.keys[0]); + psbt.finalizeAllInputs(); + const tx = psbt.extractTransaction(); + await regtestUtils.broadcast(tx.toHex()); + await regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 2e4, + }); + }, + ); + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input using HD', async () => { const hdRoot = bip32.fromSeed(rng(64)); const masterFingerprint = hdRoot.fingerprint; diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index d35fd4c..d0f2e6b 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -747,16 +747,32 @@ function canFinalize( return hasSigs(1, input.partialSig); case 'multisig': const p2ms = payments.p2ms({ output: script }); - return hasSigs(p2ms.m!, input.partialSig); + return hasSigs(p2ms.m!, input.partialSig, p2ms.pubkeys); default: return false; } } -function hasSigs(neededSigs: number, partialSig?: any[]): boolean { +function hasSigs( + neededSigs: number, + partialSig?: any[], + pubkeys?: Buffer[], +): boolean { if (!partialSig) return false; - if (partialSig.length > neededSigs) throw new Error('Too many signatures'); - return partialSig.length === neededSigs; + let sigs: any; + if (pubkeys) { + sigs = pubkeys + .map(pkey => { + const pubkey = ecPairFromPublicKey(pkey, { compressed: true }) + .publicKey; + return partialSig.filter(pSig => pSig.pubkey.equals(pubkey))[0]; + }) + .filter(v => !!v); + } else { + sigs = partialSig; + } + if (sigs.length > neededSigs) throw new Error('Too many signatures'); + return sigs.length === neededSigs; } function isFinalized(input: PsbtInput): boolean {