diff --git a/src/psbt.js b/src/psbt.js index 7bfa439..9a9ad7e 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -2,18 +2,35 @@ Object.defineProperty(exports, '__esModule', { value: true }); const bip174_1 = require('bip174'); const payments = require('./payments'); -const script = require('./script'); +const bscript = require('./script'); const transaction_1 = require('./transaction'); -const checkRedeemScript = (inputIndex, scriptPubKey, redeemScript) => { - const redeemScriptOutput = payments.p2sh({ +const scriptCheckerFactory = (payment, paymentScriptName) => ( + inputIndex, + scriptPubKey, + redeemScript, +) => { + const redeemScriptOutput = payment({ redeem: { output: redeemScript }, }).output; if (!scriptPubKey.equals(redeemScriptOutput)) { throw new Error( - `Redeem script for input #${inputIndex} doesn't match the scriptPubKey in the prevout`, + `${paymentScriptName} for input #${inputIndex} doesn't match the scriptPubKey in the prevout`, ); } }; +const checkRedeemScript = scriptCheckerFactory(payments.p2sh, 'Redeem script'); +const checkWitnessScript = scriptCheckerFactory( + payments.p2wsh, + 'Witness script', +); +const isP2WPKH = script => { + try { + payments.p2wpkh({ output: script }); + return true; + } catch (err) { + return false; + } +}; class Psbt extends bip174_1.Psbt { constructor() { super(); @@ -44,10 +61,12 @@ class Psbt extends bip174_1.Psbt { // assert False const input = this.inputs[inputIndex]; if (input === undefined) throw new Error(`No input #${inputIndex}`); + const unsignedTx = transaction_1.Transaction.fromBuffer( + this.globalMap.unsignedTx, + ); + const sighashType = input.sighashType || 0x01; + let hash; if (input.nonWitnessUtxo) { - const unsignedTx = transaction_1.Transaction.fromBuffer( - this.globalMap.unsignedTx, - ); const nonWitnessUtxoTx = transaction_1.Transaction.fromBuffer( input.nonWitnessUtxo, ); @@ -59,13 +78,25 @@ class Psbt extends bip174_1.Psbt { `Non-witness UTXO hash for input #${inputIndex} doesn't match the hash specified in the prevout`, ); } + const prevoutIndex = unsignedTx.ins[inputIndex].index; + const prevout = nonWitnessUtxoTx.outs[prevoutIndex]; if (input.redeemScript) { - const prevoutIndex = unsignedTx.ins[inputIndex].index; - const prevout = nonWitnessUtxoTx.outs[prevoutIndex]; // If a redeemScript is provided, the scriptPubKey must be for that redeemScript checkRedeemScript(inputIndex, prevout.script, input.redeemScript); + hash = unsignedTx.hashForSignature( + inputIndex, + input.redeemScript, + sighashType, + ); + } else { + hash = unsignedTx.hashForSignature( + inputIndex, + prevout.script, + sighashType, + ); } } else if (input.witnessUtxo) { + let script; if (input.redeemScript) { // If a redeemScript is provided, the scriptPubKey must be for that redeemScript checkRedeemScript( @@ -73,16 +104,36 @@ class Psbt extends bip174_1.Psbt { input.witnessUtxo.script, input.redeemScript, ); + script = input.redeemScript; + } else { + script = input.witnessUtxo.script; + } + if (isP2WPKH(script)) { + // P2WPKH uses the P2PKH template for prevoutScript when signing + const signingScript = payments.p2pkh({ hash: script.slice(2) }).output; + hash = unsignedTx.hashForWitnessV0( + inputIndex, + signingScript, + input.witnessUtxo.value, + sighashType, + ); + } else { + if (!input.witnessScript) + throw new Error('Segwit input needs witnessScript if not P2WPKH'); + checkWitnessScript(inputIndex, script, input.witnessScript); + hash = unsignedTx.hashForWitnessV0( + inputIndex, + script, + input.witnessUtxo.value, + sighashType, + ); } + } else { + throw new Error('Need a Utxo input item for signing'); } - // TODO: Get hash to sign - const hash = Buffer.alloc(32); const partialSig = { pubkey: keyPair.publicKey, - signature: script.signature.encode( - keyPair.sign(hash), - input.sighashType || 0x01, - ), + signature: bscript.signature.encode(keyPair.sign(hash), sighashType), }; return this.addPartialSigToInput(inputIndex, partialSig); } diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 63bc057..5ec9ef1 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -1,25 +1,45 @@ import { Psbt as PsbtBase } from 'bip174'; import { Signer } from './ecpair'; import * as payments from './payments'; -import * as script from './script'; +import * as bscript from './script'; import { Transaction } from './transaction'; -const checkRedeemScript = ( +type ScriptCheckerFunction = (idx: number, spk: Buffer, rs: Buffer) => void; + +const scriptCheckerFactory = ( + payment: any, + paymentScriptName: string, +): ScriptCheckerFunction => ( inputIndex: number, scriptPubKey: Buffer, redeemScript: Buffer, ): void => { - const redeemScriptOutput = payments.p2sh({ + const redeemScriptOutput = payment({ redeem: { output: redeemScript }, }).output as Buffer; if (!scriptPubKey.equals(redeemScriptOutput)) { throw new Error( - `Redeem script for input #${inputIndex} doesn't match the scriptPubKey in the prevout`, + `${paymentScriptName} for input #${inputIndex} doesn't match the scriptPubKey in the prevout`, ); } }; +const checkRedeemScript = scriptCheckerFactory(payments.p2sh, 'Redeem script'); +const checkWitnessScript = scriptCheckerFactory( + payments.p2wsh, + 'Witness script', +); + +const isP2WPKH = (script: Buffer): boolean => { + try { + payments.p2wpkh({ output: script }); + return true; + } catch (err) { + return false; + } +}; + export class Psbt extends PsbtBase { constructor() { super(); @@ -53,8 +73,11 @@ export class Psbt extends PsbtBase { const input = this.inputs[inputIndex]; if (input === undefined) throw new Error(`No input #${inputIndex}`); + const unsignedTx = Transaction.fromBuffer(this.globalMap.unsignedTx!); + const sighashType = input.sighashType || 0x01; + let hash: Buffer; + if (input.nonWitnessUtxo) { - const unsignedTx = Transaction.fromBuffer(this.globalMap.unsignedTx!); const nonWitnessUtxoTx = Transaction.fromBuffer(input.nonWitnessUtxo); const prevoutHash = unsignedTx.ins[inputIndex].hash; @@ -67,14 +90,26 @@ export class Psbt extends PsbtBase { ); } - if (input.redeemScript) { - const prevoutIndex = unsignedTx.ins[inputIndex].index; - const prevout = nonWitnessUtxoTx.outs[prevoutIndex]; + const prevoutIndex = unsignedTx.ins[inputIndex].index; + const prevout = nonWitnessUtxoTx.outs[prevoutIndex]; + if (input.redeemScript) { // If a redeemScript is provided, the scriptPubKey must be for that redeemScript checkRedeemScript(inputIndex, prevout.script, input.redeemScript); + hash = unsignedTx.hashForSignature( + inputIndex, + input.redeemScript, + sighashType, + ); + } else { + hash = unsignedTx.hashForSignature( + inputIndex, + prevout.script, + sighashType, + ); } } else if (input.witnessUtxo) { + let script: Buffer; if (input.redeemScript) { // If a redeemScript is provided, the scriptPubKey must be for that redeemScript checkRedeemScript( @@ -82,18 +117,37 @@ export class Psbt extends PsbtBase { input.witnessUtxo.script, input.redeemScript, ); + script = input.redeemScript; + } else { + script = input.witnessUtxo.script; } + if (isP2WPKH(script)) { + // P2WPKH uses the P2PKH template for prevoutScript when signing + const signingScript = payments.p2pkh({ hash: script.slice(2) }).output!; + hash = unsignedTx.hashForWitnessV0( + inputIndex, + signingScript, + input.witnessUtxo.value, + sighashType, + ); + } else { + if (!input.witnessScript) + throw new Error('Segwit input needs witnessScript if not P2WPKH'); + checkWitnessScript(inputIndex, script, input.witnessScript); + hash = unsignedTx.hashForWitnessV0( + inputIndex, + script, + input.witnessUtxo.value, + sighashType, + ); + } + } else { + throw new Error('Need a Utxo input item for signing'); } - // TODO: Get hash to sign - const hash = Buffer.alloc(32); - const partialSig = { pubkey: keyPair.publicKey, - signature: script.signature.encode( - keyPair.sign(hash), - input.sighashType || 0x01, - ), + signature: bscript.signature.encode(keyPair.sign(hash), sighashType), }; return this.addPartialSigToInput(inputIndex, partialSig);