diff --git a/src/psbt.js b/src/psbt.js index f352a8b..c7e8b55 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -273,7 +273,7 @@ class Psbt extends bip174_1.Psbt { } return results.every(res => res === true); } - sign(keyPair) { + sign(keyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input'); // TODO: Add a pubkey/pubkeyhash cache to each input @@ -282,7 +282,7 @@ class Psbt extends bip174_1.Psbt { const results = []; for (const i of range(this.inputs.length)) { try { - this.signInput(i, keyPair); + this.signInput(i, keyPair, sighashTypes); results.push(true); } catch (err) { results.push(false); @@ -293,7 +293,7 @@ class Psbt extends bip174_1.Psbt { } return this; } - signAsync(keyPair) { + signAsync(keyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { return new Promise((resolve, reject) => { if (!keyPair || !keyPair.publicKey) return reject(new Error('Need Signer to sign input')); @@ -304,7 +304,7 @@ class Psbt extends bip174_1.Psbt { const promises = []; for (const [i] of this.inputs.entries()) { promises.push( - this.signInputAsync(i, keyPair).then( + this.signInputAsync(i, keyPair, sighashTypes).then( () => { results.push(true); }, @@ -322,7 +322,11 @@ class Psbt extends bip174_1.Psbt { }); }); } - signInput(inputIndex, keyPair) { + signInput( + inputIndex, + keyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input'); const { hash, sighashType } = getHashAndSighashType( @@ -330,6 +334,7 @@ class Psbt extends bip174_1.Psbt { inputIndex, keyPair.publicKey, this.__CACHE, + sighashTypes, ); const partialSig = { pubkey: keyPair.publicKey, @@ -337,7 +342,11 @@ class Psbt extends bip174_1.Psbt { }; return this.addPartialSigToInput(inputIndex, partialSig); } - signInputAsync(inputIndex, keyPair) { + signInputAsync( + inputIndex, + keyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { return new Promise((resolve, reject) => { if (!keyPair || !keyPair.publicKey) return reject(new Error('Need Signer to sign input')); @@ -346,6 +355,7 @@ class Psbt extends bip174_1.Psbt { inputIndex, keyPair.publicKey, this.__CACHE, + sighashTypes, ); Promise.resolve(keyPair.sign(hash)).then(signature => { const partialSig = { @@ -548,19 +558,37 @@ function getFinalScripts( finalScriptWitness, }; } -function getHashAndSighashType(inputs, inputIndex, pubkey, cache) { +function getHashAndSighashType( + inputs, + inputIndex, + pubkey, + cache, + sighashTypes, +) { const input = utils_1.checkForInput(inputs, inputIndex); - const { hash, sighashType, script } = getHashForSig(inputIndex, input, cache); + const { hash, sighashType, script } = getHashForSig( + inputIndex, + input, + cache, + sighashTypes, + ); checkScriptForPubkey(pubkey, script, 'sign'); return { hash, sighashType, }; } -function getHashForSig(inputIndex, input, cache) { +function getHashForSig(inputIndex, input, cache, sighashTypes) { const unsignedTx = cache.__TX; const sighashType = input.sighashType || transaction_1.Transaction.SIGHASH_ALL; + if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) { + const str = sighashTypeToString(sighashType); + throw new Error( + `Sighash type is not allowed. Retry the sign method passing the ` + + `sighashTypes array of whitelisted types. Sighash type: ${str}`, + ); + } let hash; let script; if (input.nonWitnessUtxo) { @@ -800,6 +828,25 @@ function scriptWitnessToWitnessStack(buffer) { } return readVector(); } +function sighashTypeToString(sighashType) { + let text = + sighashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY + ? 'SIGHASH_ANYONECANPAY | ' + : ''; + const sigMod = sighashType & 0x1f; + switch (sigMod) { + case transaction_1.Transaction.SIGHASH_ALL: + text += 'SIGHASH_ALL'; + break; + case transaction_1.Transaction.SIGHASH_SINGLE: + text += 'SIGHASH_SINGLE'; + break; + case transaction_1.Transaction.SIGHASH_NONE: + text += 'SIGHASH_NONE'; + break; + } + return text; +} function witnessStackToScriptWitness(witness) { let buffer = Buffer.allocUnsafe(0); function writeSlice(slice) { diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index acb35f6..8fb1e97 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -332,7 +332,10 @@ export class Psbt extends PsbtBase { return results.every(res => res === true); } - sign(keyPair: Signer): this { + sign( + keyPair: Signer, + sighashTypes: number[] = [Transaction.SIGHASH_ALL], + ): this { if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input'); @@ -342,7 +345,7 @@ export class Psbt extends PsbtBase { const results: boolean[] = []; for (const i of range(this.inputs.length)) { try { - this.signInput(i, keyPair); + this.signInput(i, keyPair, sighashTypes); results.push(true); } catch (err) { results.push(false); @@ -354,7 +357,10 @@ export class Psbt extends PsbtBase { return this; } - signAsync(keyPair: SignerAsync): Promise { + signAsync( + keyPair: SignerAsync, + sighashTypes: number[] = [Transaction.SIGHASH_ALL], + ): Promise { return new Promise( (resolve, reject): any => { if (!keyPair || !keyPair.publicKey) @@ -367,7 +373,7 @@ export class Psbt extends PsbtBase { const promises: Array> = []; for (const [i] of this.inputs.entries()) { promises.push( - this.signInputAsync(i, keyPair).then( + this.signInputAsync(i, keyPair, sighashTypes).then( () => { results.push(true); }, @@ -387,7 +393,11 @@ export class Psbt extends PsbtBase { ); } - signInput(inputIndex: number, keyPair: Signer): this { + signInput( + inputIndex: number, + keyPair: Signer, + sighashTypes: number[] = [Transaction.SIGHASH_ALL], + ): this { if (!keyPair || !keyPair.publicKey) throw new Error('Need Signer to sign input'); const { hash, sighashType } = getHashAndSighashType( @@ -395,6 +405,7 @@ export class Psbt extends PsbtBase { inputIndex, keyPair.publicKey, this.__CACHE, + sighashTypes, ); const partialSig = { @@ -405,7 +416,11 @@ export class Psbt extends PsbtBase { return this.addPartialSigToInput(inputIndex, partialSig); } - signInputAsync(inputIndex: number, keyPair: SignerAsync): Promise { + signInputAsync( + inputIndex: number, + keyPair: SignerAsync, + sighashTypes: number[] = [Transaction.SIGHASH_ALL], + ): Promise { return new Promise( (resolve, reject): void => { if (!keyPair || !keyPair.publicKey) @@ -415,6 +430,7 @@ export class Psbt extends PsbtBase { inputIndex, keyPair.publicKey, this.__CACHE, + sighashTypes, ); Promise.resolve(keyPair.sign(hash)).then(signature => { @@ -683,12 +699,18 @@ function getHashAndSighashType( inputIndex: number, pubkey: Buffer, cache: PsbtCache, + sighashTypes: number[], ): { hash: Buffer; sighashType: number; } { const input = checkForInput(inputs, inputIndex); - const { hash, sighashType, script } = getHashForSig(inputIndex, input, cache); + const { hash, sighashType, script } = getHashForSig( + inputIndex, + input, + cache, + sighashTypes, + ); checkScriptForPubkey(pubkey, script, 'sign'); return { hash, @@ -700,6 +722,7 @@ function getHashForSig( inputIndex: number, input: PsbtInput, cache: PsbtCache, + sighashTypes?: number[], ): { script: Buffer; hash: Buffer; @@ -707,6 +730,13 @@ function getHashForSig( } { const unsignedTx = cache.__TX; const sighashType = input.sighashType || Transaction.SIGHASH_ALL; + if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) { + const str = sighashTypeToString(sighashType); + throw new Error( + `Sighash type is not allowed. Retry the sign method passing the ` + + `sighashTypes array of whitelisted types. Sighash type: ${str}`, + ); + } let hash: Buffer; let script: Buffer; @@ -981,6 +1011,26 @@ function scriptWitnessToWitnessStack(buffer: Buffer): Buffer[] { return readVector(); } +function sighashTypeToString(sighashType: number): string { + let text = + sighashType & Transaction.SIGHASH_ANYONECANPAY + ? 'SIGHASH_ANYONECANPAY | ' + : ''; + const sigMod = sighashType & 0x1f; + switch (sigMod) { + case Transaction.SIGHASH_ALL: + text += 'SIGHASH_ALL'; + break; + case Transaction.SIGHASH_SINGLE: + text += 'SIGHASH_SINGLE'; + break; + case Transaction.SIGHASH_NONE: + text += 'SIGHASH_NONE'; + break; + } + return text; +} + function witnessStackToScriptWitness(witness: Buffer[]): Buffer { let buffer = Buffer.allocUnsafe(0); diff --git a/types/psbt.d.ts b/types/psbt.d.ts index 10617c2..95f6624 100644 --- a/types/psbt.d.ts +++ b/types/psbt.d.ts @@ -27,10 +27,10 @@ export declare class Psbt extends PsbtBase { finalizeInput(inputIndex: number): this; validateAllSignatures(): boolean; validateSignatures(inputIndex: number, pubkey?: Buffer): boolean; - sign(keyPair: Signer): this; - signAsync(keyPair: SignerAsync): Promise; - signInput(inputIndex: number, keyPair: Signer): this; - signInputAsync(inputIndex: number, keyPair: SignerAsync): Promise; + sign(keyPair: Signer, sighashTypes?: number[]): this; + signAsync(keyPair: SignerAsync, sighashTypes?: number[]): Promise; + signInput(inputIndex: number, keyPair: Signer, sighashTypes?: number[]): this; + signInputAsync(inputIndex: number, keyPair: SignerAsync, sighashTypes?: number[]): Promise; } interface PsbtOptsOptional { network?: Network;