From 0c52803ba1fe5ae8dd3b84ad50bd84b0878d17de Mon Sep 17 00:00:00 2001 From: junderw Date: Tue, 28 Apr 2020 18:52:43 +0900 Subject: [PATCH] Add discouraged unsafe nonsegwit signing --- src/psbt.js | 35 +++++++++++++++++++++++++++++++++-- ts_src/psbt.ts | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/src/psbt.js b/src/psbt.js index d240350..5958b1e 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -69,6 +69,14 @@ class Psbt { __NON_WITNESS_UTXO_BUF_CACHE: [], __TX_IN_CACHE: {}, __TX: this.data.globalMap.unsignedTx.tx, + // Old TransactionBuilder behavior was to not confirm input values + // before signing. Even though we highly encourage people to get + // the full parent transaction to verify values, the ability to + // sign non-segwit inputs without the full transaction was often + // requested. So the only way to activate is to use @ts-ignore. + // We will disable exporting the Psbt when unsafe sign is active. + // because it is not BIP174 compliant. + __UNSAFE_SIGN_NONSEGWIT: false, }; if (this.data.inputs.length === 0) this.setVersion(2); // Make data hidden when enumerating @@ -313,6 +321,7 @@ class Psbt { inputIndex, Object.assign({}, input, { sighashType: sig.hashType }), this.__CACHE, + true, ) : { hash: hashCache, script: scriptCache }; sighashCache = sig.hashType; @@ -513,12 +522,15 @@ class Psbt { }); } toBuffer() { + checkCache(this.__CACHE); return this.data.toBuffer(); } toHex() { + checkCache(this.__CACHE); return this.data.toHex(); } toBase64() { + checkCache(this.__CACHE); return this.data.toBase64(); } updateGlobal(updateData) { @@ -626,6 +638,11 @@ function canFinalize(input, script, scriptType) { return false; } } +function checkCache(cache) { + if (cache.__UNSAFE_SIGN_NONSEGWIT !== false) { + throw new Error('Not BIP174 compliant, can not export'); + } +} function hasSigs(neededSigs, partialSig, pubkeys) { if (!partialSig) return false; let sigs; @@ -857,6 +874,7 @@ function getHashAndSighashType( inputIndex, input, cache, + false, sighashTypes, ); checkScriptForPubkey(pubkey, script, 'sign'); @@ -865,7 +883,7 @@ function getHashAndSighashType( sighashType, }; } -function getHashForSig(inputIndex, input, cache, sighashTypes) { +function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) { const unsignedTx = cache.__TX; const sighashType = input.sighashType || transaction_1.Transaction.SIGHASH_ALL; @@ -925,11 +943,24 @@ function getHashForSig(inputIndex, input, cache, sighashTypes) { ); } else { // non-segwit - if (input.nonWitnessUtxo === undefined) + if ( + input.nonWitnessUtxo === undefined && + cache.__UNSAFE_SIGN_NONSEGWIT === false + ) throw new Error( `Input #${inputIndex} has witnessUtxo but non-segwit script: ` + `${meaningfulScript.toString('hex')}`, ); + if (!forValidate && cache.__UNSAFE_SIGN_NONSEGWIT !== false) + console.warn( + 'Warning: Signing non-segwit inputs without the full parent transaction ' + + 'means there is a chance that a miner could feed you incorrect information ' + + 'to trick you into paying large fees. This behavior is the same as the old ' + + 'TransactionBuilder class when signing non-segwit scripts. You are not ' + + 'able to export this Psbt with toBuffer|toBase64|toHex since it is not ' + + 'BIP174 compliant.\n*********************\nPROCEED WITH CAUTION!\n' + + '*********************', + ); hash = unsignedTx.hashForSignature( inputIndex, meaningfulScript, diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 13e1286..23bdc1d 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -115,6 +115,14 @@ export class Psbt { __NON_WITNESS_UTXO_BUF_CACHE: [], __TX_IN_CACHE: {}, __TX: (this.data.globalMap.unsignedTx as PsbtTransaction).tx, + // Old TransactionBuilder behavior was to not confirm input values + // before signing. Even though we highly encourage people to get + // the full parent transaction to verify values, the ability to + // sign non-segwit inputs without the full transaction was often + // requested. So the only way to activate is to use @ts-ignore. + // We will disable exporting the Psbt when unsafe sign is active. + // because it is not BIP174 compliant. + __UNSAFE_SIGN_NONSEGWIT: false, }; if (this.data.inputs.length === 0) this.setVersion(2); @@ -386,6 +394,7 @@ export class Psbt { inputIndex, Object.assign({}, input, { sighashType: sig.hashType }), this.__CACHE, + true, ) : { hash: hashCache!, script: scriptCache! }; sighashCache = sig.hashType; @@ -619,14 +628,17 @@ export class Psbt { } toBuffer(): Buffer { + checkCache(this.__CACHE); return this.data.toBuffer(); } toHex(): string { + checkCache(this.__CACHE); return this.data.toHex(); } toBase64(): string { + checkCache(this.__CACHE); return this.data.toBase64(); } @@ -681,6 +693,7 @@ interface PsbtCache { __FEE_RATE?: number; __FEE?: number; __EXTRACTED_TX?: Transaction; + __UNSAFE_SIGN_NONSEGWIT: boolean; } interface PsbtOptsOptional { @@ -825,6 +838,12 @@ function canFinalize( } } +function checkCache(cache: PsbtCache): void { + if (cache.__UNSAFE_SIGN_NONSEGWIT !== false) { + throw new Error('Not BIP174 compliant, can not export'); + } +} + function hasSigs( neededSigs: number, partialSig?: any[], @@ -1130,6 +1149,7 @@ function getHashAndSighashType( inputIndex, input, cache, + false, sighashTypes, ); checkScriptForPubkey(pubkey, script, 'sign'); @@ -1143,6 +1163,7 @@ function getHashForSig( inputIndex: number, input: PsbtInput, cache: PsbtCache, + forValidate: boolean, sighashTypes?: number[], ): { script: Buffer; @@ -1213,11 +1234,24 @@ function getHashForSig( ); } else { // non-segwit - if (input.nonWitnessUtxo === undefined) + if ( + input.nonWitnessUtxo === undefined && + cache.__UNSAFE_SIGN_NONSEGWIT === false + ) throw new Error( `Input #${inputIndex} has witnessUtxo but non-segwit script: ` + `${meaningfulScript.toString('hex')}`, ); + if (!forValidate && cache.__UNSAFE_SIGN_NONSEGWIT !== false) + console.warn( + 'Warning: Signing non-segwit inputs without the full parent transaction ' + + 'means there is a chance that a miner could feed you incorrect information ' + + 'to trick you into paying large fees. This behavior is the same as the old ' + + 'TransactionBuilder class when signing non-segwit scripts. You are not ' + + 'able to export this Psbt with toBuffer|toBase64|toHex since it is not ' + + 'BIP174 compliant.\n*********************\nPROCEED WITH CAUTION!\n' + + '*********************', + ); hash = unsignedTx.hashForSignature( inputIndex, meaningfulScript,