bitcoinjs-lib
Advanced tools
Comparing version 5.1.10 to 5.2.0
@@ -0,1 +1,8 @@ | ||
# 5.2.0 | ||
__changed__ | ||
- Updated PSBT to allow for witnessUtxo and nonWitnessUtxo simultaneously (Re: segwit psbt bug) (#1563) | ||
__added__ | ||
- PSBT methods `getInputType`, `inputHasPubkey`, `inputHasHDKey`, `outputHasPubkey`, `outputHasHDKey` (#1563) | ||
# 5.1.10 | ||
@@ -2,0 +9,0 @@ __fixed__ |
{ | ||
"name": "bitcoinjs-lib", | ||
"version": "5.1.10", | ||
"version": "5.2.0", | ||
"description": "Client-side Bitcoin JavaScript library", | ||
@@ -53,3 +53,3 @@ "main": "./src/index.js", | ||
"bech32": "^1.1.2", | ||
"bip174": "^1.0.1", | ||
"bip174": "^2.0.1", | ||
"bip32": "^2.0.4", | ||
@@ -56,0 +56,0 @@ "bip66": "^1.1.0", |
@@ -45,5 +45,5 @@ 'use strict'; | ||
function cloneBuffer(buffer) { | ||
const clone = Buffer.alloc(buffer.length); | ||
const clone = Buffer.allocUnsafe(buffer.length); | ||
buffer.copy(clone); | ||
return buffer; | ||
return clone; | ||
} | ||
@@ -50,0 +50,0 @@ exports.cloneBuffer = cloneBuffer; |
328
src/psbt.js
@@ -72,2 +72,10 @@ 'use strict'; | ||
__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, | ||
}; | ||
@@ -191,2 +199,3 @@ if (this.data.inputs.length === 0) this.setVersion(2); | ||
checkInputsForPartialSig(this.data.inputs, 'addInput'); | ||
if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript); | ||
const c = this.__CACHE; | ||
@@ -288,2 +297,39 @@ this.data.addInput(inputData); | ||
} | ||
getInputType(inputIndex) { | ||
const input = utils_1.checkForInput(this.data.inputs, inputIndex); | ||
const script = getScriptFromUtxo(inputIndex, input, this.__CACHE); | ||
const result = getMeaningfulScript( | ||
script, | ||
inputIndex, | ||
'input', | ||
input.redeemScript || redeemFromFinalScriptSig(input.finalScriptSig), | ||
input.witnessScript || | ||
redeemFromFinalWitnessScript(input.finalScriptWitness), | ||
); | ||
const type = result.type === 'raw' ? '' : result.type + '-'; | ||
const mainType = classifyScript(result.meaningfulScript); | ||
return type + mainType; | ||
} | ||
inputHasPubkey(inputIndex, pubkey) { | ||
const input = utils_1.checkForInput(this.data.inputs, inputIndex); | ||
return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE); | ||
} | ||
inputHasHDKey(inputIndex, root) { | ||
const input = utils_1.checkForInput(this.data.inputs, inputIndex); | ||
const derivationIsMine = bip32DerivationIsMine(root); | ||
return ( | ||
!!input.bip32Derivation && input.bip32Derivation.some(derivationIsMine) | ||
); | ||
} | ||
outputHasPubkey(outputIndex, pubkey) { | ||
const output = utils_1.checkForOutput(this.data.outputs, outputIndex); | ||
return pubkeyInOutput(pubkey, output, outputIndex, this.__CACHE); | ||
} | ||
outputHasHDKey(outputIndex, root) { | ||
const output = utils_1.checkForOutput(this.data.outputs, outputIndex); | ||
const derivationIsMine = bip32DerivationIsMine(root); | ||
return ( | ||
!!output.bip32Derivation && output.bip32Derivation.some(derivationIsMine) | ||
); | ||
} | ||
validateSignaturesOfAllInputs() { | ||
@@ -317,2 +363,3 @@ utils_1.checkForInput(this.data.inputs, 0); // making sure we have at least one | ||
this.__CACHE, | ||
true, | ||
) | ||
@@ -517,8 +564,11 @@ : { hash: hashCache, script: scriptCache }; | ||
toBuffer() { | ||
checkCache(this.__CACHE); | ||
return this.data.toBuffer(); | ||
} | ||
toHex() { | ||
checkCache(this.__CACHE); | ||
return this.data.toHex(); | ||
} | ||
toBase64() { | ||
checkCache(this.__CACHE); | ||
return this.data.toBase64(); | ||
@@ -531,2 +581,3 @@ } | ||
updateInput(inputIndex, updateData) { | ||
if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript); | ||
this.data.updateInput(inputIndex, updateData); | ||
@@ -632,2 +683,7 @@ if (updateData.nonWitnessUtxo) { | ||
} | ||
function checkCache(cache) { | ||
if (cache.__UNSAFE_SIGN_NONSEGWIT !== false) { | ||
throw new Error('Not BIP174 compliant, can not export'); | ||
} | ||
} | ||
function hasSigs(neededSigs, partialSig, pubkeys) { | ||
@@ -668,2 +724,10 @@ if (!partialSig) return false; | ||
const isP2WSHScript = isPaymentFactory(payments.p2wsh); | ||
const isP2SHScript = isPaymentFactory(payments.p2sh); | ||
function bip32DerivationIsMine(root) { | ||
return d => { | ||
if (!d.masterFingerprint.equals(root.fingerprint)) return false; | ||
if (!root.derivePath(d.path).publicKey.equals(d.pubkey)) return false; | ||
return true; | ||
}; | ||
} | ||
function check32Bit(num) { | ||
@@ -739,10 +803,3 @@ if ( | ||
function checkScriptForPubkey(pubkey, script, action) { | ||
const pubkeyHash = crypto_1.hash160(pubkey); | ||
const decompiled = bscript.decompile(script); | ||
if (decompiled === null) throw new Error('Unknown script error'); | ||
const hasKey = decompiled.some(element => { | ||
if (typeof element === 'number') return false; | ||
return element.equals(pubkey) || element.equals(pubkeyHash); | ||
}); | ||
if (!hasKey) { | ||
if (!pubkeyInScript(pubkey, script)) { | ||
throw new Error( | ||
@@ -779,3 +836,3 @@ `Can not ${action} for this input with the key ${pubkey.toString('hex')}`, | ||
function scriptCheckerFactory(payment, paymentScriptName) { | ||
return (inputIndex, scriptPubKey, redeemScript) => { | ||
return (inputIndex, scriptPubKey, redeemScript, ioType) => { | ||
const redeemScriptOutput = payment({ | ||
@@ -786,3 +843,3 @@ redeem: { output: redeemScript }, | ||
throw new Error( | ||
`${paymentScriptName} for input #${inputIndex} doesn't match the scriptPubKey in the prevout`, | ||
`${paymentScriptName} for ${ioType} #${inputIndex} doesn't match the scriptPubKey in the prevout`, | ||
); | ||
@@ -874,2 +931,3 @@ } | ||
cache, | ||
false, | ||
sighashTypes, | ||
@@ -883,3 +941,3 @@ ); | ||
} | ||
function getHashForSig(inputIndex, input, cache, sighashTypes) { | ||
function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) { | ||
const unsignedTx = cache.__TX; | ||
@@ -896,3 +954,3 @@ const sighashType = | ||
let hash; | ||
let script; | ||
let prevout; | ||
if (input.nonWitnessUtxo) { | ||
@@ -913,79 +971,60 @@ const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( | ||
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); | ||
script = input.redeemScript; | ||
} else { | ||
script = prevout.script; | ||
} | ||
if (isP2WSHScript(script)) { | ||
if (!input.witnessScript) | ||
throw new Error('Segwit input needs witnessScript if not P2WPKH'); | ||
checkWitnessScript(inputIndex, script, input.witnessScript); | ||
hash = unsignedTx.hashForWitnessV0( | ||
inputIndex, | ||
input.witnessScript, | ||
prevout.value, | ||
sighashType, | ||
); | ||
script = input.witnessScript; | ||
} else 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, | ||
prevout.value, | ||
sighashType, | ||
); | ||
} else { | ||
hash = unsignedTx.hashForSignature(inputIndex, script, sighashType); | ||
} | ||
prevout = nonWitnessUtxoTx.outs[prevoutIndex]; | ||
} else if (input.witnessUtxo) { | ||
let _script; // so we don't shadow the `let script` above | ||
if (input.redeemScript) { | ||
// If a redeemScript is provided, the scriptPubKey must be for that redeemScript | ||
checkRedeemScript( | ||
inputIndex, | ||
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, | ||
); | ||
script = _script; | ||
} else if (isP2WSHScript(_script)) { | ||
if (!input.witnessScript) | ||
throw new Error('Segwit input needs witnessScript if not P2WPKH'); | ||
checkWitnessScript(inputIndex, _script, input.witnessScript); | ||
hash = unsignedTx.hashForWitnessV0( | ||
inputIndex, | ||
input.witnessScript, | ||
input.witnessUtxo.value, | ||
sighashType, | ||
); | ||
// want to make sure the script we return is the actual meaningful script | ||
script = input.witnessScript; | ||
} else { | ||
prevout = input.witnessUtxo; | ||
} else { | ||
throw new Error('Need a Utxo input item for signing'); | ||
} | ||
const { meaningfulScript, type } = getMeaningfulScript( | ||
prevout.script, | ||
inputIndex, | ||
'input', | ||
input.redeemScript, | ||
input.witnessScript, | ||
); | ||
if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) { | ||
hash = unsignedTx.hashForWitnessV0( | ||
inputIndex, | ||
meaningfulScript, | ||
prevout.value, | ||
sighashType, | ||
); | ||
} else if (isP2WPKH(meaningfulScript)) { | ||
// P2WPKH uses the P2PKH template for prevoutScript when signing | ||
const signingScript = payments.p2pkh({ hash: meaningfulScript.slice(2) }) | ||
.output; | ||
hash = unsignedTx.hashForWitnessV0( | ||
inputIndex, | ||
signingScript, | ||
prevout.value, | ||
sighashType, | ||
); | ||
} else { | ||
// non-segwit | ||
if ( | ||
input.nonWitnessUtxo === undefined && | ||
cache.__UNSAFE_SIGN_NONSEGWIT === false | ||
) | ||
throw new Error( | ||
`Input #${inputIndex} has witnessUtxo but non-segwit script: ` + | ||
`${_script.toString('hex')}`, | ||
`${meaningfulScript.toString('hex')}`, | ||
); | ||
} | ||
} else { | ||
throw new Error('Need a Utxo input item for signing'); | ||
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, | ||
sighashType, | ||
); | ||
} | ||
return { | ||
script, | ||
script: meaningfulScript, | ||
sighashType, | ||
@@ -1242,2 +1281,125 @@ hash, | ||
} | ||
function getScriptFromUtxo(inputIndex, input, cache) { | ||
if (input.witnessUtxo !== undefined) { | ||
return input.witnessUtxo.script; | ||
} else if (input.nonWitnessUtxo !== undefined) { | ||
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( | ||
cache, | ||
input, | ||
inputIndex, | ||
); | ||
return nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script; | ||
} else { | ||
throw new Error("Can't find pubkey in input without Utxo data"); | ||
} | ||
} | ||
function pubkeyInInput(pubkey, input, inputIndex, cache) { | ||
const script = getScriptFromUtxo(inputIndex, input, cache); | ||
const { meaningfulScript } = getMeaningfulScript( | ||
script, | ||
inputIndex, | ||
'input', | ||
input.redeemScript, | ||
input.witnessScript, | ||
); | ||
return pubkeyInScript(pubkey, meaningfulScript); | ||
} | ||
function pubkeyInOutput(pubkey, output, outputIndex, cache) { | ||
const script = cache.__TX.outs[outputIndex].script; | ||
const { meaningfulScript } = getMeaningfulScript( | ||
script, | ||
outputIndex, | ||
'output', | ||
output.redeemScript, | ||
output.witnessScript, | ||
); | ||
return pubkeyInScript(pubkey, meaningfulScript); | ||
} | ||
function redeemFromFinalScriptSig(finalScript) { | ||
if (!finalScript) return; | ||
const decomp = bscript.decompile(finalScript); | ||
if (!decomp) return; | ||
const lastItem = decomp[decomp.length - 1]; | ||
if ( | ||
!Buffer.isBuffer(lastItem) || | ||
isPubkeyLike(lastItem) || | ||
isSigLike(lastItem) | ||
) | ||
return; | ||
const sDecomp = bscript.decompile(lastItem); | ||
if (!sDecomp) return; | ||
return lastItem; | ||
} | ||
function redeemFromFinalWitnessScript(finalScript) { | ||
if (!finalScript) return; | ||
const decomp = scriptWitnessToWitnessStack(finalScript); | ||
const lastItem = decomp[decomp.length - 1]; | ||
if (isPubkeyLike(lastItem)) return; | ||
const sDecomp = bscript.decompile(lastItem); | ||
if (!sDecomp) return; | ||
return lastItem; | ||
} | ||
function isPubkeyLike(buf) { | ||
return buf.length === 33 && bscript.isCanonicalPubKey(buf); | ||
} | ||
function isSigLike(buf) { | ||
return bscript.isCanonicalScriptSignature(buf); | ||
} | ||
function getMeaningfulScript( | ||
script, | ||
index, | ||
ioType, | ||
redeemScript, | ||
witnessScript, | ||
) { | ||
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 meaningfulScript; | ||
if (isP2SHP2WSH) { | ||
meaningfulScript = witnessScript; | ||
checkRedeemScript(index, script, redeemScript, ioType); | ||
checkWitnessScript(index, redeemScript, witnessScript, ioType); | ||
checkInvalidP2WSH(meaningfulScript); | ||
} else if (isP2WSH) { | ||
meaningfulScript = witnessScript; | ||
checkWitnessScript(index, script, witnessScript, ioType); | ||
checkInvalidP2WSH(meaningfulScript); | ||
} else if (isP2SH) { | ||
meaningfulScript = redeemScript; | ||
checkRedeemScript(index, script, redeemScript, ioType); | ||
} else { | ||
meaningfulScript = script; | ||
} | ||
return { | ||
meaningfulScript, | ||
type: isP2SHP2WSH | ||
? 'p2sh-p2wsh' | ||
: isP2SH | ||
? 'p2sh' | ||
: isP2WSH | ||
? 'p2wsh' | ||
: 'raw', | ||
}; | ||
} | ||
function checkInvalidP2WSH(script) { | ||
if (isP2WPKH(script) || isP2SHScript(script)) { | ||
throw new Error('P2WPKH or P2SH can not be contained within P2WSH'); | ||
} | ||
} | ||
function pubkeyInScript(pubkey, script) { | ||
const pubkeyHash = crypto_1.hash160(pubkey); | ||
const decompiled = bscript.decompile(script); | ||
if (decompiled === null) throw new Error('Unknown script error'); | ||
return decompiled.some(element => { | ||
if (typeof element === 'number') return false; | ||
return element.equals(pubkey) || element.equals(pubkeyHash); | ||
}); | ||
} | ||
function classifyScript(script) { | ||
@@ -1244,0 +1406,0 @@ if (isP2WPKH(script)) return 'witnesspubkeyhash'; |
@@ -10,3 +10,3 @@ import * as bip32 from 'bip32'; | ||
export { Block } from './block'; | ||
export { Psbt } from './psbt'; | ||
export { Psbt, PsbtTxInput, PsbtTxOutput } from './psbt'; | ||
export { OPS as opcodes } from './script'; | ||
@@ -13,0 +13,0 @@ export { Transaction } from './transaction'; |
@@ -6,2 +6,8 @@ import { Psbt as PsbtBase } from 'bip174'; | ||
import { Transaction } from './transaction'; | ||
export interface PsbtTxInput extends TransactionInput { | ||
hash: Buffer; | ||
} | ||
export interface PsbtTxOutput extends TransactionOutput { | ||
address: string | undefined; | ||
} | ||
/** | ||
@@ -50,4 +56,4 @@ * Psbt class can parse and generate a PSBT binary based off of the BIP174. | ||
locktime: number; | ||
readonly txInputs: TransactionInput[]; | ||
readonly txOutputs: TransactionOutput[]; | ||
readonly txInputs: PsbtTxInput[]; | ||
readonly txOutputs: PsbtTxOutput[]; | ||
combine(...those: Psbt[]): this; | ||
@@ -68,2 +74,7 @@ clone(): Psbt; | ||
finalizeInput(inputIndex: number, finalScriptsFunc?: FinalScriptsFunc): this; | ||
getInputType(inputIndex: number): AllScriptType; | ||
inputHasPubkey(inputIndex: number, pubkey: Buffer): boolean; | ||
inputHasHDKey(inputIndex: number, root: HDSigner): boolean; | ||
outputHasPubkey(outputIndex: number, pubkey: Buffer): boolean; | ||
outputHasHDKey(outputIndex: number, root: HDSigner): boolean; | ||
validateSignaturesOfAllInputs(): boolean; | ||
@@ -149,2 +160,3 @@ validateSignaturesOfInput(inputIndex: number, pubkey?: Buffer): boolean; | ||
}; | ||
declare type AllScriptType = 'witnesspubkeyhash' | 'pubkeyhash' | 'multisig' | 'pubkey' | 'nonstandard' | 'p2sh-witnesspubkeyhash' | 'p2sh-pubkeyhash' | 'p2sh-multisig' | 'p2sh-pubkey' | 'p2sh-nonstandard' | 'p2wsh-pubkeyhash' | 'p2wsh-multisig' | 'p2wsh-pubkey' | 'p2wsh-nonstandard' | 'p2sh-p2wsh-pubkeyhash' | 'p2sh-p2wsh-multisig' | 'p2sh-p2wsh-pubkey' | 'p2sh-p2wsh-nonstandard'; | ||
export {}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
227048
5876
+ Addedbip174@2.1.1(transitive)
- Removedbip174@1.0.1(transitive)
Updatedbip174@^2.0.1