diff --git a/src/psbt.js b/src/psbt.js index 48fb798..91e0438 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -1235,7 +1235,7 @@ function pubkeyInInput(pubkey, input, inputIndex, cache) { } else { throw new Error("Can't find pubkey in input without Utxo data"); } - const meaningfulScript = checkScripts( + const meaningfulScript = getMeaningfulScript( script, input.redeemScript, input.witnessScript, @@ -1244,55 +1244,49 @@ function pubkeyInInput(pubkey, input, inputIndex, cache) { } function pubkeyInOutput(pubkey, output, outputIndex, cache) { const script = cache.__TX.outs[outputIndex].script; - const meaningfulScript = checkScripts( + const meaningfulScript = getMeaningfulScript( script, output.redeemScript, output.witnessScript, ); return pubkeyInScript(pubkey, meaningfulScript); } -function checkScripts(script, redeemScript, witnessScript) { - let fail = false; - if (isP2SHScript(script)) { - if (redeemScript === undefined) { - fail = true; - } else if (isP2WSHScript(redeemScript)) { - if (witnessScript === undefined) { - fail = true; - } else { - fail = !payments - .p2sh({ - redeem: payments.p2wsh({ - redeem: { output: witnessScript }, - }), - }) - .output.equals(script); - if (!fail) return witnessScript; - } - } else { - fail = !payments - .p2sh({ - redeem: { output: redeemScript }, - }) - .output.equals(script); - if (!fail) return redeemScript; - } - } else if (isP2WSHScript(script)) { - if (witnessScript === undefined) { - fail = true; - } else { - fail = !payments - .p2wsh({ - redeem: { output: witnessScript }, - }) - .output.equals(script); - if (!fail) return witnessScript; - } +function getMeaningfulScript(script, redeemScript, witnessScript) { + const { p2sh, p2wsh } = payments; + const isP2SH = isP2SHScript(script); + const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript); + const isP2WSH = isP2WSHScript(script); + if (isP2SH && redeemScript === undefined) + throw new Error('scriptPubkey is P2SH but redeemScript missing'); + if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined) + throw new Error( + 'scriptPubkey or redeemScript is P2WSH but witnessScript missing', + ); + let payment; + let meaningfulScript; + if (isP2SHP2WSH) { + meaningfulScript = witnessScript; + payment = p2sh({ redeem: p2wsh({ redeem: { output: meaningfulScript } }) }); + if (!payment.redeem.output.equals(redeemScript)) + throw new Error('P2SHP2WSH witnessScript and redeemScript do not match'); + if (!payment.output.equals(script)) + throw new Error( + 'P2SHP2WSH witnessScript+redeemScript and scriptPubkey do not match', + ); + } else if (isP2WSH) { + meaningfulScript = witnessScript; + payment = p2wsh({ redeem: { output: meaningfulScript } }); + if (!payment.output.equals(script)) + throw new Error('P2WSH witnessScript and scriptPubkey do not match'); + } else if (isP2SH) { + meaningfulScript = redeemScript; + payment = p2sh({ redeem: { output: meaningfulScript } }); + if (!payment.output.equals(script)) + throw new Error('P2SH redeemScript and scriptPubkey do not match'); + } else { + meaningfulScript = script; } - if (fail) { - throw new Error('Incomplete script information'); - } - return script; + return meaningfulScript; } function pubkeyInScript(pubkey, script) { const pubkeyHash = crypto_1.hash160(pubkey); diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index de83fe3..ff2131b 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -566,7 +566,7 @@ describe(`Psbt`, () => { assert.throws(() => { psbt.inputHasPubkey(0, Buffer.from([])); - }, new RegExp('Incomplete script information')); + }, new RegExp('scriptPubkey is P2SH but redeemScript missing')); delete psbt.data.inputs[0].witnessUtxo; @@ -581,7 +581,7 @@ describe(`Psbt`, () => { assert.throws(() => { psbt.inputHasPubkey(0, Buffer.from([])); - }, new RegExp('Incomplete script information')); + }, new RegExp('scriptPubkey or redeemScript is P2WSH but witnessScript missing')); delete psbt.data.inputs[0].witnessUtxo; @@ -601,7 +601,7 @@ describe(`Psbt`, () => { assert.throws(() => { psbt.inputHasPubkey(0, Buffer.from([])); - }, new RegExp('Incomplete script information')); + }, new RegExp('scriptPubkey or redeemScript is P2WSH but witnessScript missing')); psbt.updateInput(0, { witnessScript: Buffer.from([0x51]), @@ -631,7 +631,7 @@ describe(`Psbt`, () => { assert.throws(() => { psbt.outputHasPubkey(0, Buffer.from([])); - }, new RegExp('Incomplete script information')); + }, new RegExp('scriptPubkey is P2SH but redeemScript missing')); (psbt as any).__CACHE.__TX.outs[0].script = payments.p2wsh({ redeem: { output: Buffer.from([0x51]) }, @@ -639,7 +639,7 @@ describe(`Psbt`, () => { assert.throws(() => { psbt.outputHasPubkey(0, Buffer.from([])); - }, new RegExp('Incomplete script information')); + }, new RegExp('scriptPubkey or redeemScript is P2WSH but witnessScript missing')); (psbt as any).__CACHE.__TX.outs[0].script = payments.p2sh({ redeem: payments.p2wsh({ @@ -655,7 +655,7 @@ describe(`Psbt`, () => { assert.throws(() => { psbt.outputHasPubkey(0, Buffer.from([])); - }, new RegExp('Incomplete script information')); + }, new RegExp('scriptPubkey or redeemScript is P2WSH but witnessScript missing')); delete psbt.data.outputs[0].redeemScript; @@ -665,7 +665,7 @@ describe(`Psbt`, () => { assert.throws(() => { psbt.outputHasPubkey(0, Buffer.from([])); - }, new RegExp('Incomplete script information')); + }, new RegExp('scriptPubkey is P2SH but redeemScript missing')); psbt.updateOutput(0, { redeemScript: payments.p2wsh({ diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 31d15c7..7749827 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -1587,7 +1587,7 @@ function pubkeyInInput( } else { throw new Error("Can't find pubkey in input without Utxo data"); } - const meaningfulScript = checkScripts( + const meaningfulScript = getMeaningfulScript( script, input.redeemScript, input.witnessScript, @@ -1602,7 +1602,7 @@ function pubkeyInOutput( cache: PsbtCache, ): boolean { const script = cache.__TX.outs[outputIndex].script; - const meaningfulScript = checkScripts( + const meaningfulScript = getMeaningfulScript( script, output.redeemScript, output.witnessScript, @@ -1610,52 +1610,49 @@ function pubkeyInOutput( return pubkeyInScript(pubkey, meaningfulScript); } -function checkScripts( +function getMeaningfulScript( script: Buffer, redeemScript?: Buffer, witnessScript?: Buffer, ): Buffer { - let fail = false; - if (isP2SHScript(script)) { - if (redeemScript === undefined) { - fail = true; - } else if (isP2WSHScript(redeemScript)) { - if (witnessScript === undefined) { - fail = true; - } else { - fail = !payments - .p2sh({ - redeem: payments.p2wsh({ - redeem: { output: witnessScript }, - }), - }) - .output!.equals(script); - if (!fail) return witnessScript; - } - } else { - fail = !payments - .p2sh({ - redeem: { output: redeemScript }, - }) - .output!.equals(script); - if (!fail) return redeemScript; - } - } else if (isP2WSHScript(script)) { - if (witnessScript === undefined) { - fail = true; - } else { - fail = !payments - .p2wsh({ - redeem: { output: witnessScript }, - }) - .output!.equals(script); - if (!fail) return witnessScript; - } + const { p2sh, p2wsh } = payments; + const isP2SH = isP2SHScript(script); + const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript); + const isP2WSH = isP2WSHScript(script); + + if (isP2SH && redeemScript === undefined) + throw new Error('scriptPubkey is P2SH but redeemScript missing'); + if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined) + throw new Error( + 'scriptPubkey or redeemScript is P2WSH but witnessScript missing', + ); + + let payment: payments.Payment; + let meaningfulScript: Buffer; + + if (isP2SHP2WSH) { + meaningfulScript = witnessScript!; + payment = p2sh({ redeem: p2wsh({ redeem: { output: meaningfulScript } }) }); + if (!payment.redeem!.output!.equals(redeemScript!)) + throw new Error('P2SHP2WSH witnessScript and redeemScript do not match'); + if (!payment.output!.equals(script!)) + throw new Error( + 'P2SHP2WSH witnessScript+redeemScript and scriptPubkey do not match', + ); + } else if (isP2WSH) { + meaningfulScript = witnessScript!; + payment = p2wsh({ redeem: { output: meaningfulScript } }); + if (!payment.output!.equals(script!)) + throw new Error('P2WSH witnessScript and scriptPubkey do not match'); + } else if (isP2SH) { + meaningfulScript = redeemScript!; + payment = p2sh({ redeem: { output: meaningfulScript } }); + if (!payment.output!.equals(script!)) + throw new Error('P2SH redeemScript and scriptPubkey do not match'); + } else { + meaningfulScript = script; } - if (fail) { - throw new Error('Incomplete script information'); - } - return script; + return meaningfulScript; } function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean {