Add discouraged unsafe nonsegwit signing

This commit is contained in:
junderw 2020-04-28 18:52:43 +09:00
parent 7d09fe5dcb
commit 0c52803ba1
No known key found for this signature in database
GPG key ID: B256185D3A971908
2 changed files with 68 additions and 3 deletions

View file

@ -69,6 +69,14 @@ class Psbt {
__NON_WITNESS_UTXO_BUF_CACHE: [], __NON_WITNESS_UTXO_BUF_CACHE: [],
__TX_IN_CACHE: {}, __TX_IN_CACHE: {},
__TX: this.data.globalMap.unsignedTx.tx, __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); if (this.data.inputs.length === 0) this.setVersion(2);
// Make data hidden when enumerating // Make data hidden when enumerating
@ -313,6 +321,7 @@ class Psbt {
inputIndex, inputIndex,
Object.assign({}, input, { sighashType: sig.hashType }), Object.assign({}, input, { sighashType: sig.hashType }),
this.__CACHE, this.__CACHE,
true,
) )
: { hash: hashCache, script: scriptCache }; : { hash: hashCache, script: scriptCache };
sighashCache = sig.hashType; sighashCache = sig.hashType;
@ -513,12 +522,15 @@ class Psbt {
}); });
} }
toBuffer() { toBuffer() {
checkCache(this.__CACHE);
return this.data.toBuffer(); return this.data.toBuffer();
} }
toHex() { toHex() {
checkCache(this.__CACHE);
return this.data.toHex(); return this.data.toHex();
} }
toBase64() { toBase64() {
checkCache(this.__CACHE);
return this.data.toBase64(); return this.data.toBase64();
} }
updateGlobal(updateData) { updateGlobal(updateData) {
@ -626,6 +638,11 @@ function canFinalize(input, script, scriptType) {
return false; 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) { function hasSigs(neededSigs, partialSig, pubkeys) {
if (!partialSig) return false; if (!partialSig) return false;
let sigs; let sigs;
@ -857,6 +874,7 @@ function getHashAndSighashType(
inputIndex, inputIndex,
input, input,
cache, cache,
false,
sighashTypes, sighashTypes,
); );
checkScriptForPubkey(pubkey, script, 'sign'); checkScriptForPubkey(pubkey, script, 'sign');
@ -865,7 +883,7 @@ function getHashAndSighashType(
sighashType, sighashType,
}; };
} }
function getHashForSig(inputIndex, input, cache, sighashTypes) { function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) {
const unsignedTx = cache.__TX; const unsignedTx = cache.__TX;
const sighashType = const sighashType =
input.sighashType || transaction_1.Transaction.SIGHASH_ALL; input.sighashType || transaction_1.Transaction.SIGHASH_ALL;
@ -925,11 +943,24 @@ function getHashForSig(inputIndex, input, cache, sighashTypes) {
); );
} else { } else {
// non-segwit // non-segwit
if (input.nonWitnessUtxo === undefined) if (
input.nonWitnessUtxo === undefined &&
cache.__UNSAFE_SIGN_NONSEGWIT === false
)
throw new Error( throw new Error(
`Input #${inputIndex} has witnessUtxo but non-segwit script: ` + `Input #${inputIndex} has witnessUtxo but non-segwit script: ` +
`${meaningfulScript.toString('hex')}`, `${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( hash = unsignedTx.hashForSignature(
inputIndex, inputIndex,
meaningfulScript, meaningfulScript,

View file

@ -115,6 +115,14 @@ export class Psbt {
__NON_WITNESS_UTXO_BUF_CACHE: [], __NON_WITNESS_UTXO_BUF_CACHE: [],
__TX_IN_CACHE: {}, __TX_IN_CACHE: {},
__TX: (this.data.globalMap.unsignedTx as PsbtTransaction).tx, __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); if (this.data.inputs.length === 0) this.setVersion(2);
@ -386,6 +394,7 @@ export class Psbt {
inputIndex, inputIndex,
Object.assign({}, input, { sighashType: sig.hashType }), Object.assign({}, input, { sighashType: sig.hashType }),
this.__CACHE, this.__CACHE,
true,
) )
: { hash: hashCache!, script: scriptCache! }; : { hash: hashCache!, script: scriptCache! };
sighashCache = sig.hashType; sighashCache = sig.hashType;
@ -619,14 +628,17 @@ export class Psbt {
} }
toBuffer(): Buffer { toBuffer(): Buffer {
checkCache(this.__CACHE);
return this.data.toBuffer(); return this.data.toBuffer();
} }
toHex(): string { toHex(): string {
checkCache(this.__CACHE);
return this.data.toHex(); return this.data.toHex();
} }
toBase64(): string { toBase64(): string {
checkCache(this.__CACHE);
return this.data.toBase64(); return this.data.toBase64();
} }
@ -681,6 +693,7 @@ interface PsbtCache {
__FEE_RATE?: number; __FEE_RATE?: number;
__FEE?: number; __FEE?: number;
__EXTRACTED_TX?: Transaction; __EXTRACTED_TX?: Transaction;
__UNSAFE_SIGN_NONSEGWIT: boolean;
} }
interface PsbtOptsOptional { 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( function hasSigs(
neededSigs: number, neededSigs: number,
partialSig?: any[], partialSig?: any[],
@ -1130,6 +1149,7 @@ function getHashAndSighashType(
inputIndex, inputIndex,
input, input,
cache, cache,
false,
sighashTypes, sighashTypes,
); );
checkScriptForPubkey(pubkey, script, 'sign'); checkScriptForPubkey(pubkey, script, 'sign');
@ -1143,6 +1163,7 @@ function getHashForSig(
inputIndex: number, inputIndex: number,
input: PsbtInput, input: PsbtInput,
cache: PsbtCache, cache: PsbtCache,
forValidate: boolean,
sighashTypes?: number[], sighashTypes?: number[],
): { ): {
script: Buffer; script: Buffer;
@ -1213,11 +1234,24 @@ function getHashForSig(
); );
} else { } else {
// non-segwit // non-segwit
if (input.nonWitnessUtxo === undefined) if (
input.nonWitnessUtxo === undefined &&
cache.__UNSAFE_SIGN_NONSEGWIT === false
)
throw new Error( throw new Error(
`Input #${inputIndex} has witnessUtxo but non-segwit script: ` + `Input #${inputIndex} has witnessUtxo but non-segwit script: ` +
`${meaningfulScript.toString('hex')}`, `${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( hash = unsignedTx.hashForSignature(
inputIndex, inputIndex,
meaningfulScript, meaningfulScript,