|
@ -1,16 +1,16 @@ |
|
|
"use strict"; |
|
|
'use strict'; |
|
|
Object.defineProperty(exports, "__esModule", { value: true }); |
|
|
Object.defineProperty(exports, '__esModule', { value: true }); |
|
|
const baddress = require("./address"); |
|
|
const baddress = require('./address'); |
|
|
const bufferutils_1 = require("./bufferutils"); |
|
|
const bufferutils_1 = require('./bufferutils'); |
|
|
const classify = require("./classify"); |
|
|
const classify = require('./classify'); |
|
|
const bcrypto = require("./crypto"); |
|
|
const bcrypto = require('./crypto'); |
|
|
const ECPair = require("./ecpair"); |
|
|
const ECPair = require('./ecpair'); |
|
|
const networks = require("./networks"); |
|
|
const networks = require('./networks'); |
|
|
const payments = require("./payments"); |
|
|
const payments = require('./payments'); |
|
|
const bscript = require("./script"); |
|
|
const bscript = require('./script'); |
|
|
const script_1 = require("./script"); |
|
|
const script_1 = require('./script'); |
|
|
const transaction_1 = require("./transaction"); |
|
|
const transaction_1 = require('./transaction'); |
|
|
const types = require("./types"); |
|
|
const types = require('./types'); |
|
|
const typeforce = require('typeforce'); |
|
|
const typeforce = require('typeforce'); |
|
|
const SCRIPT_TYPES = classify.types; |
|
|
const SCRIPT_TYPES = classify.types; |
|
|
function txIsString(tx) { |
|
|
function txIsString(tx) { |
|
@ -56,11 +56,12 @@ class TransactionBuilder { |
|
|
setLockTime(locktime) { |
|
|
setLockTime(locktime) { |
|
|
typeforce(types.UInt32, locktime); |
|
|
typeforce(types.UInt32, locktime); |
|
|
// if any signatures exist, throw
|
|
|
// if any signatures exist, throw
|
|
|
if (this.__INPUTS.some(input => { |
|
|
if ( |
|
|
if (!input.signatures) |
|
|
this.__INPUTS.some(input => { |
|
|
return false; |
|
|
if (!input.signatures) return false; |
|
|
return input.signatures.some(s => s !== undefined); |
|
|
return input.signatures.some(s => s !== undefined); |
|
|
})) { |
|
|
}) |
|
|
|
|
|
) { |
|
|
throw new Error('No, this would invalidate signatures'); |
|
|
throw new Error('No, this would invalidate signatures'); |
|
|
} |
|
|
} |
|
|
this.__TX.locktime = locktime; |
|
|
this.__TX.locktime = locktime; |
|
@ -80,8 +81,7 @@ class TransactionBuilder { |
|
|
// transaction hashs's are displayed in reverse order, un-reverse it
|
|
|
// transaction hashs's are displayed in reverse order, un-reverse it
|
|
|
txHash = bufferutils_1.reverseBuffer(Buffer.from(txHash, 'hex')); |
|
|
txHash = bufferutils_1.reverseBuffer(Buffer.from(txHash, 'hex')); |
|
|
// is it a Transaction object?
|
|
|
// is it a Transaction object?
|
|
|
} |
|
|
} else if (txIsTransaction(txHash)) { |
|
|
else if (txIsTransaction(txHash)) { |
|
|
|
|
|
const txOut = txHash.outs[vout]; |
|
|
const txOut = txHash.outs[vout]; |
|
|
prevOutScript = txOut.script; |
|
|
prevOutScript = txOut.script; |
|
|
value = txOut.value; |
|
|
value = txOut.value; |
|
@ -113,16 +113,17 @@ class TransactionBuilder { |
|
|
// TODO: remove keyPair.network matching in 4.0.0
|
|
|
// TODO: remove keyPair.network matching in 4.0.0
|
|
|
if (keyPair.network && keyPair.network !== this.network) |
|
|
if (keyPair.network && keyPair.network !== this.network) |
|
|
throw new TypeError('Inconsistent network'); |
|
|
throw new TypeError('Inconsistent network'); |
|
|
if (!this.__INPUTS[vin]) |
|
|
if (!this.__INPUTS[vin]) throw new Error('No input at index: ' + vin); |
|
|
throw new Error('No input at index: ' + vin); |
|
|
|
|
|
hashType = hashType || transaction_1.Transaction.SIGHASH_ALL; |
|
|
hashType = hashType || transaction_1.Transaction.SIGHASH_ALL; |
|
|
if (this.__needsOutputs(hashType)) |
|
|
if (this.__needsOutputs(hashType)) |
|
|
throw new Error('Transaction needs outputs'); |
|
|
throw new Error('Transaction needs outputs'); |
|
|
const input = this.__INPUTS[vin]; |
|
|
const input = this.__INPUTS[vin]; |
|
|
// if redeemScript was previously provided, enforce consistency
|
|
|
// if redeemScript was previously provided, enforce consistency
|
|
|
if (input.redeemScript !== undefined && |
|
|
if ( |
|
|
|
|
|
input.redeemScript !== undefined && |
|
|
redeemScript && |
|
|
redeemScript && |
|
|
!input.redeemScript.equals(redeemScript)) { |
|
|
!input.redeemScript.equals(redeemScript) |
|
|
|
|
|
) { |
|
|
throw new Error('Inconsistent redeemScript'); |
|
|
throw new Error('Inconsistent redeemScript'); |
|
|
} |
|
|
} |
|
|
const ourPubKey = keyPair.publicKey || keyPair.getPublicKey(); |
|
|
const ourPubKey = keyPair.publicKey || keyPair.getPublicKey(); |
|
@ -134,37 +135,48 @@ class TransactionBuilder { |
|
|
input.value = witnessValue; |
|
|
input.value = witnessValue; |
|
|
} |
|
|
} |
|
|
if (!canSign(input)) { |
|
|
if (!canSign(input)) { |
|
|
const prepared = prepareInput(input, ourPubKey, redeemScript, witnessScript); |
|
|
const prepared = prepareInput( |
|
|
|
|
|
input, |
|
|
|
|
|
ourPubKey, |
|
|
|
|
|
redeemScript, |
|
|
|
|
|
witnessScript, |
|
|
|
|
|
); |
|
|
// updates inline
|
|
|
// updates inline
|
|
|
Object.assign(input, prepared); |
|
|
Object.assign(input, prepared); |
|
|
} |
|
|
} |
|
|
if (!canSign(input)) |
|
|
if (!canSign(input)) throw Error(input.prevOutType + ' not supported'); |
|
|
throw Error(input.prevOutType + ' not supported'); |
|
|
|
|
|
} |
|
|
} |
|
|
// ready to sign
|
|
|
// ready to sign
|
|
|
let signatureHash; |
|
|
let signatureHash; |
|
|
if (input.hasWitness) { |
|
|
if (input.hasWitness) { |
|
|
signatureHash = this.__TX.hashForWitnessV0(vin, input.signScript, input.value, hashType); |
|
|
signatureHash = this.__TX.hashForWitnessV0( |
|
|
} |
|
|
vin, |
|
|
else { |
|
|
input.signScript, |
|
|
signatureHash = this.__TX.hashForSignature(vin, input.signScript, hashType); |
|
|
input.value, |
|
|
|
|
|
hashType, |
|
|
|
|
|
); |
|
|
|
|
|
} else { |
|
|
|
|
|
signatureHash = this.__TX.hashForSignature( |
|
|
|
|
|
vin, |
|
|
|
|
|
input.signScript, |
|
|
|
|
|
hashType, |
|
|
|
|
|
); |
|
|
} |
|
|
} |
|
|
// enforce in order signing of public keys
|
|
|
// enforce in order signing of public keys
|
|
|
const signed = input.pubkeys.some((pubKey, i) => { |
|
|
const signed = input.pubkeys.some((pubKey, i) => { |
|
|
if (!ourPubKey.equals(pubKey)) |
|
|
if (!ourPubKey.equals(pubKey)) return false; |
|
|
return false; |
|
|
if (input.signatures[i]) throw new Error('Signature already exists'); |
|
|
if (input.signatures[i]) |
|
|
|
|
|
throw new Error('Signature already exists'); |
|
|
|
|
|
// TODO: add tests
|
|
|
// TODO: add tests
|
|
|
if (ourPubKey.length !== 33 && input.hasWitness) { |
|
|
if (ourPubKey.length !== 33 && input.hasWitness) { |
|
|
throw new Error('BIP143 rejects uncompressed public keys in P2WPKH or P2WSH'); |
|
|
throw new Error( |
|
|
|
|
|
'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH', |
|
|
|
|
|
); |
|
|
} |
|
|
} |
|
|
const signature = keyPair.sign(signatureHash); |
|
|
const signature = keyPair.sign(signatureHash); |
|
|
input.signatures[i] = bscript.signature.encode(signature, hashType); |
|
|
input.signatures[i] = bscript.signature.encode(signature, hashType); |
|
|
return true; |
|
|
return true; |
|
|
}); |
|
|
}); |
|
|
if (!signed) |
|
|
if (!signed) throw new Error('Key pair cannot sign for this input'); |
|
|
throw new Error('Key pair cannot sign for this input'); |
|
|
|
|
|
} |
|
|
} |
|
|
__addInputUnsafe(txHash, vout, options) { |
|
|
__addInputUnsafe(txHash, vout, options) { |
|
|
if (transaction_1.Transaction.isCoinbaseHash(txHash)) { |
|
|
if (transaction_1.Transaction.isCoinbaseHash(txHash)) { |
|
@ -196,17 +208,20 @@ class TransactionBuilder { |
|
|
input.prevOutScript = options.prevOutScript; |
|
|
input.prevOutScript = options.prevOutScript; |
|
|
input.prevOutType = prevOutType || classify.output(options.prevOutScript); |
|
|
input.prevOutType = prevOutType || classify.output(options.prevOutScript); |
|
|
} |
|
|
} |
|
|
const vin = this.__TX.addInput(txHash, vout, options.sequence, options.scriptSig); |
|
|
const vin = this.__TX.addInput( |
|
|
|
|
|
txHash, |
|
|
|
|
|
vout, |
|
|
|
|
|
options.sequence, |
|
|
|
|
|
options.scriptSig, |
|
|
|
|
|
); |
|
|
this.__INPUTS[vin] = input; |
|
|
this.__INPUTS[vin] = input; |
|
|
this.__PREV_TX_SET[prevTxOut] = true; |
|
|
this.__PREV_TX_SET[prevTxOut] = true; |
|
|
return vin; |
|
|
return vin; |
|
|
} |
|
|
} |
|
|
__build(allowIncomplete) { |
|
|
__build(allowIncomplete) { |
|
|
if (!allowIncomplete) { |
|
|
if (!allowIncomplete) { |
|
|
if (!this.__TX.ins.length) |
|
|
if (!this.__TX.ins.length) throw new Error('Transaction has no inputs'); |
|
|
throw new Error('Transaction has no inputs'); |
|
|
if (!this.__TX.outs.length) throw new Error('Transaction has no outputs'); |
|
|
if (!this.__TX.outs.length) |
|
|
|
|
|
throw new Error('Transaction has no outputs'); |
|
|
|
|
|
} |
|
|
} |
|
|
const tx = this.__TX.clone(); |
|
|
const tx = this.__TX.clone(); |
|
|
// create script signatures from inputs
|
|
|
// create script signatures from inputs
|
|
@ -217,8 +232,7 @@ class TransactionBuilder { |
|
|
if (!result) { |
|
|
if (!result) { |
|
|
if (!allowIncomplete && input.prevOutType === SCRIPT_TYPES.NONSTANDARD) |
|
|
if (!allowIncomplete && input.prevOutType === SCRIPT_TYPES.NONSTANDARD) |
|
|
throw new Error('Unknown input type'); |
|
|
throw new Error('Unknown input type'); |
|
|
if (!allowIncomplete) |
|
|
if (!allowIncomplete) throw new Error('Not enough information'); |
|
|
throw new Error('Not enough information'); |
|
|
|
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
tx.setInputScript(i, result.input); |
|
|
tx.setInputScript(i, result.input); |
|
@ -234,15 +248,15 @@ class TransactionBuilder { |
|
|
} |
|
|
} |
|
|
__canModifyInputs() { |
|
|
__canModifyInputs() { |
|
|
return this.__INPUTS.every(input => { |
|
|
return this.__INPUTS.every(input => { |
|
|
if (!input.signatures) |
|
|
if (!input.signatures) return true; |
|
|
return true; |
|
|
|
|
|
return input.signatures.every(signature => { |
|
|
return input.signatures.every(signature => { |
|
|
if (!signature) |
|
|
if (!signature) return true; |
|
|
return true; |
|
|
|
|
|
const hashType = signatureHashType(signature); |
|
|
const hashType = signatureHashType(signature); |
|
|
// if SIGHASH_ANYONECANPAY is set, signatures would not
|
|
|
// if SIGHASH_ANYONECANPAY is set, signatures would not
|
|
|
// be invalidated by more inputs
|
|
|
// be invalidated by more inputs
|
|
|
return (hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY) !== 0; |
|
|
return ( |
|
|
|
|
|
(hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY) !== 0 |
|
|
|
|
|
); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
@ -252,33 +266,29 @@ class TransactionBuilder { |
|
|
} |
|
|
} |
|
|
// if inputs are being signed with SIGHASH_NONE, we don't strictly need outputs
|
|
|
// if inputs are being signed with SIGHASH_NONE, we don't strictly need outputs
|
|
|
// .build() will fail, but .buildIncomplete() is OK
|
|
|
// .build() will fail, but .buildIncomplete() is OK
|
|
|
return (this.__TX.outs.length === 0 && |
|
|
return ( |
|
|
|
|
|
this.__TX.outs.length === 0 && |
|
|
this.__INPUTS.some(input => { |
|
|
this.__INPUTS.some(input => { |
|
|
if (!input.signatures) |
|
|
if (!input.signatures) return false; |
|
|
return false; |
|
|
|
|
|
return input.signatures.some(signature => { |
|
|
return input.signatures.some(signature => { |
|
|
if (!signature) |
|
|
if (!signature) return false; // no signature, no issue
|
|
|
return false; // no signature, no issue
|
|
|
|
|
|
const hashType = signatureHashType(signature); |
|
|
const hashType = signatureHashType(signature); |
|
|
if (hashType & transaction_1.Transaction.SIGHASH_NONE) |
|
|
if (hashType & transaction_1.Transaction.SIGHASH_NONE) return false; // SIGHASH_NONE doesn't care about outputs
|
|
|
return false; // SIGHASH_NONE doesn't care about outputs
|
|
|
|
|
|
return true; // SIGHASH_* does care
|
|
|
return true; // SIGHASH_* does care
|
|
|
}); |
|
|
}); |
|
|
})); |
|
|
}) |
|
|
|
|
|
); |
|
|
} |
|
|
} |
|
|
__canModifyOutputs() { |
|
|
__canModifyOutputs() { |
|
|
const nInputs = this.__TX.ins.length; |
|
|
const nInputs = this.__TX.ins.length; |
|
|
const nOutputs = this.__TX.outs.length; |
|
|
const nOutputs = this.__TX.outs.length; |
|
|
return this.__INPUTS.every(input => { |
|
|
return this.__INPUTS.every(input => { |
|
|
if (input.signatures === undefined) |
|
|
if (input.signatures === undefined) return true; |
|
|
return true; |
|
|
|
|
|
return input.signatures.every(signature => { |
|
|
return input.signatures.every(signature => { |
|
|
if (!signature) |
|
|
if (!signature) return true; |
|
|
return true; |
|
|
|
|
|
const hashType = signatureHashType(signature); |
|
|
const hashType = signatureHashType(signature); |
|
|
const hashTypeMod = hashType & 0x1f; |
|
|
const hashTypeMod = hashType & 0x1f; |
|
|
if (hashTypeMod === transaction_1.Transaction.SIGHASH_NONE) |
|
|
if (hashTypeMod === transaction_1.Transaction.SIGHASH_NONE) return true; |
|
|
return true; |
|
|
|
|
|
if (hashTypeMod === transaction_1.Transaction.SIGHASH_SINGLE) { |
|
|
if (hashTypeMod === transaction_1.Transaction.SIGHASH_SINGLE) { |
|
|
// if SIGHASH_SINGLE is set, and nInputs > nOutputs
|
|
|
// if SIGHASH_SINGLE is set, and nInputs > nOutputs
|
|
|
// some signatures would be invalidated by the addition
|
|
|
// some signatures would be invalidated by the addition
|
|
@ -302,15 +312,12 @@ class TransactionBuilder { |
|
|
} |
|
|
} |
|
|
exports.TransactionBuilder = TransactionBuilder; |
|
|
exports.TransactionBuilder = TransactionBuilder; |
|
|
function expandInput(scriptSig, witnessStack, type, scriptPubKey) { |
|
|
function expandInput(scriptSig, witnessStack, type, scriptPubKey) { |
|
|
if (scriptSig.length === 0 && witnessStack.length === 0) |
|
|
if (scriptSig.length === 0 && witnessStack.length === 0) return {}; |
|
|
return {}; |
|
|
|
|
|
if (!type) { |
|
|
if (!type) { |
|
|
let ssType = classify.input(scriptSig, true); |
|
|
let ssType = classify.input(scriptSig, true); |
|
|
let wsType = classify.witness(witnessStack, true); |
|
|
let wsType = classify.witness(witnessStack, true); |
|
|
if (ssType === SCRIPT_TYPES.NONSTANDARD) |
|
|
if (ssType === SCRIPT_TYPES.NONSTANDARD) ssType = undefined; |
|
|
ssType = undefined; |
|
|
if (wsType === SCRIPT_TYPES.NONSTANDARD) wsType = undefined; |
|
|
if (wsType === SCRIPT_TYPES.NONSTANDARD) |
|
|
|
|
|
wsType = undefined; |
|
|
|
|
|
type = ssType || wsType; |
|
|
type = ssType || wsType; |
|
|
} |
|
|
} |
|
|
switch (type) { |
|
|
switch (type) { |
|
@ -345,10 +352,13 @@ function expandInput(scriptSig, witnessStack, type, scriptPubKey) { |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
case SCRIPT_TYPES.P2MS: { |
|
|
case SCRIPT_TYPES.P2MS: { |
|
|
const { m, pubkeys, signatures } = payments.p2ms({ |
|
|
const { m, pubkeys, signatures } = payments.p2ms( |
|
|
|
|
|
{ |
|
|
input: scriptSig, |
|
|
input: scriptSig, |
|
|
output: scriptPubKey, |
|
|
output: scriptPubKey, |
|
|
}, { allowIncomplete: true }); |
|
|
}, |
|
|
|
|
|
{ allowIncomplete: true }, |
|
|
|
|
|
); |
|
|
return { |
|
|
return { |
|
|
prevOutType: SCRIPT_TYPES.P2MS, |
|
|
prevOutType: SCRIPT_TYPES.P2MS, |
|
|
pubkeys, |
|
|
pubkeys, |
|
@ -363,9 +373,13 @@ function expandInput(scriptSig, witnessStack, type, scriptPubKey) { |
|
|
witness: witnessStack, |
|
|
witness: witnessStack, |
|
|
}); |
|
|
}); |
|
|
const outputType = classify.output(redeem.output); |
|
|
const outputType = classify.output(redeem.output); |
|
|
const expanded = expandInput(redeem.input, redeem.witness, outputType, redeem.output); |
|
|
const expanded = expandInput( |
|
|
if (!expanded.prevOutType) |
|
|
redeem.input, |
|
|
return {}; |
|
|
redeem.witness, |
|
|
|
|
|
outputType, |
|
|
|
|
|
redeem.output, |
|
|
|
|
|
); |
|
|
|
|
|
if (!expanded.prevOutType) return {}; |
|
|
return { |
|
|
return { |
|
|
prevOutScript: output, |
|
|
prevOutScript: output, |
|
|
prevOutType: SCRIPT_TYPES.P2SH, |
|
|
prevOutType: SCRIPT_TYPES.P2SH, |
|
@ -386,12 +400,15 @@ function expandInput(scriptSig, witnessStack, type, scriptPubKey) { |
|
|
let expanded; |
|
|
let expanded; |
|
|
if (outputType === SCRIPT_TYPES.P2WPKH) { |
|
|
if (outputType === SCRIPT_TYPES.P2WPKH) { |
|
|
expanded = expandInput(redeem.input, redeem.witness, outputType); |
|
|
expanded = expandInput(redeem.input, redeem.witness, outputType); |
|
|
} |
|
|
} else { |
|
|
else { |
|
|
expanded = expandInput( |
|
|
expanded = expandInput(bscript.compile(redeem.witness), [], outputType, redeem.output); |
|
|
bscript.compile(redeem.witness), |
|
|
} |
|
|
[], |
|
|
if (!expanded.prevOutType) |
|
|
outputType, |
|
|
return {}; |
|
|
redeem.output, |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
if (!expanded.prevOutType) return {}; |
|
|
return { |
|
|
return { |
|
|
prevOutScript: output, |
|
|
prevOutScript: output, |
|
|
prevOutType: SCRIPT_TYPES.P2WSH, |
|
|
prevOutType: SCRIPT_TYPES.P2WSH, |
|
@ -410,8 +427,7 @@ function expandInput(scriptSig, witnessStack, type, scriptPubKey) { |
|
|
function fixMultisigOrder(input, transaction, vin) { |
|
|
function fixMultisigOrder(input, transaction, vin) { |
|
|
if (input.redeemScriptType !== SCRIPT_TYPES.P2MS || !input.redeemScript) |
|
|
if (input.redeemScriptType !== SCRIPT_TYPES.P2MS || !input.redeemScript) |
|
|
return; |
|
|
return; |
|
|
if (input.pubkeys.length === input.signatures.length) |
|
|
if (input.pubkeys.length === input.signatures.length) return; |
|
|
return; |
|
|
|
|
|
const unmatched = input.signatures.concat(); |
|
|
const unmatched = input.signatures.concat(); |
|
|
input.signatures = input.pubkeys.map(pubKey => { |
|
|
input.signatures = input.pubkeys.map(pubKey => { |
|
|
const keyPair = ECPair.fromPublicKey(pubKey); |
|
|
const keyPair = ECPair.fromPublicKey(pubKey); |
|
@ -419,14 +435,16 @@ function fixMultisigOrder(input, transaction, vin) { |
|
|
// check for a signature
|
|
|
// check for a signature
|
|
|
unmatched.some((signature, i) => { |
|
|
unmatched.some((signature, i) => { |
|
|
// skip if undefined || OP_0
|
|
|
// skip if undefined || OP_0
|
|
|
if (!signature) |
|
|
if (!signature) return false; |
|
|
return false; |
|
|
|
|
|
// TODO: avoid O(n) hashForSignature
|
|
|
// TODO: avoid O(n) hashForSignature
|
|
|
const parsed = bscript.signature.decode(signature); |
|
|
const parsed = bscript.signature.decode(signature); |
|
|
const hash = transaction.hashForSignature(vin, input.redeemScript, parsed.hashType); |
|
|
const hash = transaction.hashForSignature( |
|
|
|
|
|
vin, |
|
|
|
|
|
input.redeemScript, |
|
|
|
|
|
parsed.hashType, |
|
|
|
|
|
); |
|
|
// skip if signature does not match pubKey
|
|
|
// skip if signature does not match pubKey
|
|
|
if (!keyPair.verify(hash, parsed.signature)) |
|
|
if (!keyPair.verify(hash, parsed.signature)) return false; |
|
|
return false; |
|
|
|
|
|
// remove matched signature from unmatched
|
|
|
// remove matched signature from unmatched
|
|
|
unmatched[i] = undefined; |
|
|
unmatched[i] = undefined; |
|
|
match = signature; |
|
|
match = signature; |
|
@ -440,13 +458,11 @@ function expandOutput(script, ourPubKey) { |
|
|
const type = classify.output(script); |
|
|
const type = classify.output(script); |
|
|
switch (type) { |
|
|
switch (type) { |
|
|
case SCRIPT_TYPES.P2PKH: { |
|
|
case SCRIPT_TYPES.P2PKH: { |
|
|
if (!ourPubKey) |
|
|
if (!ourPubKey) return { type }; |
|
|
return { type }; |
|
|
|
|
|
// does our hash160(pubKey) match the output scripts?
|
|
|
// does our hash160(pubKey) match the output scripts?
|
|
|
const pkh1 = payments.p2pkh({ output: script }).hash; |
|
|
const pkh1 = payments.p2pkh({ output: script }).hash; |
|
|
const pkh2 = bcrypto.hash160(ourPubKey); |
|
|
const pkh2 = bcrypto.hash160(ourPubKey); |
|
|
if (!pkh1.equals(pkh2)) |
|
|
if (!pkh1.equals(pkh2)) return { type }; |
|
|
return { type }; |
|
|
|
|
|
return { |
|
|
return { |
|
|
type, |
|
|
type, |
|
|
pubkeys: [ourPubKey], |
|
|
pubkeys: [ourPubKey], |
|
@ -454,13 +470,11 @@ function expandOutput(script, ourPubKey) { |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
case SCRIPT_TYPES.P2WPKH: { |
|
|
case SCRIPT_TYPES.P2WPKH: { |
|
|
if (!ourPubKey) |
|
|
if (!ourPubKey) return { type }; |
|
|
return { type }; |
|
|
|
|
|
// does our hash160(pubKey) match the output scripts?
|
|
|
// does our hash160(pubKey) match the output scripts?
|
|
|
const wpkh1 = payments.p2wpkh({ output: script }).hash; |
|
|
const wpkh1 = payments.p2wpkh({ output: script }).hash; |
|
|
const wpkh2 = bcrypto.hash160(ourPubKey); |
|
|
const wpkh2 = bcrypto.hash160(ourPubKey); |
|
|
if (!wpkh1.equals(wpkh2)) |
|
|
if (!wpkh1.equals(wpkh2)) return { type }; |
|
|
return { type }; |
|
|
|
|
|
return { |
|
|
return { |
|
|
type, |
|
|
type, |
|
|
pubkeys: [ourPubKey], |
|
|
pubkeys: [ourPubKey], |
|
@ -502,10 +516,12 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) { |
|
|
throw new Error('Redeem script inconsistent with prevOutScript'); |
|
|
throw new Error('Redeem script inconsistent with prevOutScript'); |
|
|
const expanded = expandOutput(p2wsh.redeem.output, ourPubKey); |
|
|
const expanded = expandOutput(p2wsh.redeem.output, ourPubKey); |
|
|
if (!expanded.pubkeys) |
|
|
if (!expanded.pubkeys) |
|
|
throw new Error(expanded.type + |
|
|
throw new Error( |
|
|
|
|
|
expanded.type + |
|
|
' not supported as witnessScript (' + |
|
|
' not supported as witnessScript (' + |
|
|
bscript.toASM(witnessScript) + |
|
|
bscript.toASM(witnessScript) + |
|
|
')'); |
|
|
')', |
|
|
|
|
|
); |
|
|
if (input.signatures && input.signatures.some(x => x !== undefined)) { |
|
|
if (input.signatures && input.signatures.some(x => x !== undefined)) { |
|
|
expanded.signatures = input.signatures; |
|
|
expanded.signatures = input.signatures; |
|
|
} |
|
|
} |
|
@ -533,8 +549,7 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) { |
|
|
let p2shAlt; |
|
|
let p2shAlt; |
|
|
try { |
|
|
try { |
|
|
p2shAlt = payments.p2sh({ output: input.prevOutScript }); |
|
|
p2shAlt = payments.p2sh({ output: input.prevOutScript }); |
|
|
} |
|
|
} catch (e) { |
|
|
catch (e) { |
|
|
|
|
|
throw new Error('PrevOutScript must be P2SH'); |
|
|
throw new Error('PrevOutScript must be P2SH'); |
|
|
} |
|
|
} |
|
|
if (!p2sh.hash.equals(p2shAlt.hash)) |
|
|
if (!p2sh.hash.equals(p2shAlt.hash)) |
|
@ -542,10 +557,12 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) { |
|
|
} |
|
|
} |
|
|
const expanded = expandOutput(p2sh.redeem.output, ourPubKey); |
|
|
const expanded = expandOutput(p2sh.redeem.output, ourPubKey); |
|
|
if (!expanded.pubkeys) |
|
|
if (!expanded.pubkeys) |
|
|
throw new Error(expanded.type + |
|
|
throw new Error( |
|
|
|
|
|
expanded.type + |
|
|
' not supported as redeemScript (' + |
|
|
' not supported as redeemScript (' + |
|
|
bscript.toASM(redeemScript) + |
|
|
bscript.toASM(redeemScript) + |
|
|
')'); |
|
|
')', |
|
|
|
|
|
); |
|
|
if (input.signatures && input.signatures.some(x => x !== undefined)) { |
|
|
if (input.signatures && input.signatures.some(x => x !== undefined)) { |
|
|
expanded.signatures = input.signatures; |
|
|
expanded.signatures = input.signatures; |
|
|
} |
|
|
} |
|
@ -575,10 +592,12 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) { |
|
|
} |
|
|
} |
|
|
const expanded = expandOutput(p2wsh.redeem.output, ourPubKey); |
|
|
const expanded = expandOutput(p2wsh.redeem.output, ourPubKey); |
|
|
if (!expanded.pubkeys) |
|
|
if (!expanded.pubkeys) |
|
|
throw new Error(expanded.type + |
|
|
throw new Error( |
|
|
|
|
|
expanded.type + |
|
|
' not supported as witnessScript (' + |
|
|
' not supported as witnessScript (' + |
|
|
bscript.toASM(witnessScript) + |
|
|
bscript.toASM(witnessScript) + |
|
|
')'); |
|
|
')', |
|
|
|
|
|
); |
|
|
if (input.signatures && input.signatures.some(x => x !== undefined)) { |
|
|
if (input.signatures && input.signatures.some(x => x !== undefined)) { |
|
|
expanded.signatures = input.signatures; |
|
|
expanded.signatures = input.signatures; |
|
|
} |
|
|
} |
|
@ -601,24 +620,28 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) { |
|
|
if (input.prevOutType && input.prevOutScript) { |
|
|
if (input.prevOutType && input.prevOutScript) { |
|
|
// embedded scripts are not possible without extra information
|
|
|
// embedded scripts are not possible without extra information
|
|
|
if (input.prevOutType === SCRIPT_TYPES.P2SH) |
|
|
if (input.prevOutType === SCRIPT_TYPES.P2SH) |
|
|
throw new Error('PrevOutScript is ' + input.prevOutType + ', requires redeemScript'); |
|
|
throw new Error( |
|
|
|
|
|
'PrevOutScript is ' + input.prevOutType + ', requires redeemScript', |
|
|
|
|
|
); |
|
|
if (input.prevOutType === SCRIPT_TYPES.P2WSH) |
|
|
if (input.prevOutType === SCRIPT_TYPES.P2WSH) |
|
|
throw new Error('PrevOutScript is ' + input.prevOutType + ', requires witnessScript'); |
|
|
throw new Error( |
|
|
if (!input.prevOutScript) |
|
|
'PrevOutScript is ' + input.prevOutType + ', requires witnessScript', |
|
|
throw new Error('PrevOutScript is missing'); |
|
|
); |
|
|
|
|
|
if (!input.prevOutScript) throw new Error('PrevOutScript is missing'); |
|
|
const expanded = expandOutput(input.prevOutScript, ourPubKey); |
|
|
const expanded = expandOutput(input.prevOutScript, ourPubKey); |
|
|
if (!expanded.pubkeys) |
|
|
if (!expanded.pubkeys) |
|
|
throw new Error(expanded.type + |
|
|
throw new Error( |
|
|
|
|
|
expanded.type + |
|
|
' not supported (' + |
|
|
' not supported (' + |
|
|
bscript.toASM(input.prevOutScript) + |
|
|
bscript.toASM(input.prevOutScript) + |
|
|
')'); |
|
|
')', |
|
|
|
|
|
); |
|
|
if (input.signatures && input.signatures.some(x => x !== undefined)) { |
|
|
if (input.signatures && input.signatures.some(x => x !== undefined)) { |
|
|
expanded.signatures = input.signatures; |
|
|
expanded.signatures = input.signatures; |
|
|
} |
|
|
} |
|
|
let signScript = input.prevOutScript; |
|
|
let signScript = input.prevOutScript; |
|
|
if (expanded.type === SCRIPT_TYPES.P2WPKH) { |
|
|
if (expanded.type === SCRIPT_TYPES.P2WPKH) { |
|
|
signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }) |
|
|
signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output; |
|
|
.output; |
|
|
|
|
|
} |
|
|
} |
|
|
return { |
|
|
return { |
|
|
prevOutType: expanded.type, |
|
|
prevOutType: expanded.type, |
|
@ -643,47 +666,42 @@ function prepareInput(input, ourPubKey, redeemScript, witnessScript) { |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
function build(type, input, allowIncomplete) { |
|
|
function build(type, input, allowIncomplete) { |
|
|
const pubkeys = (input.pubkeys || []); |
|
|
const pubkeys = input.pubkeys || []; |
|
|
let signatures = (input.signatures || []); |
|
|
let signatures = input.signatures || []; |
|
|
switch (type) { |
|
|
switch (type) { |
|
|
case SCRIPT_TYPES.P2PKH: { |
|
|
case SCRIPT_TYPES.P2PKH: { |
|
|
if (pubkeys.length === 0) |
|
|
if (pubkeys.length === 0) break; |
|
|
break; |
|
|
if (signatures.length === 0) break; |
|
|
if (signatures.length === 0) |
|
|
|
|
|
break; |
|
|
|
|
|
return payments.p2pkh({ pubkey: pubkeys[0], signature: signatures[0] }); |
|
|
return payments.p2pkh({ pubkey: pubkeys[0], signature: signatures[0] }); |
|
|
} |
|
|
} |
|
|
case SCRIPT_TYPES.P2WPKH: { |
|
|
case SCRIPT_TYPES.P2WPKH: { |
|
|
if (pubkeys.length === 0) |
|
|
if (pubkeys.length === 0) break; |
|
|
break; |
|
|
if (signatures.length === 0) break; |
|
|
if (signatures.length === 0) |
|
|
|
|
|
break; |
|
|
|
|
|
return payments.p2wpkh({ pubkey: pubkeys[0], signature: signatures[0] }); |
|
|
return payments.p2wpkh({ pubkey: pubkeys[0], signature: signatures[0] }); |
|
|
} |
|
|
} |
|
|
case SCRIPT_TYPES.P2PK: { |
|
|
case SCRIPT_TYPES.P2PK: { |
|
|
if (pubkeys.length === 0) |
|
|
if (pubkeys.length === 0) break; |
|
|
break; |
|
|
if (signatures.length === 0) break; |
|
|
if (signatures.length === 0) |
|
|
|
|
|
break; |
|
|
|
|
|
return payments.p2pk({ signature: signatures[0] }); |
|
|
return payments.p2pk({ signature: signatures[0] }); |
|
|
} |
|
|
} |
|
|
case SCRIPT_TYPES.P2MS: { |
|
|
case SCRIPT_TYPES.P2MS: { |
|
|
const m = input.maxSignatures; |
|
|
const m = input.maxSignatures; |
|
|
if (allowIncomplete) { |
|
|
if (allowIncomplete) { |
|
|
signatures = signatures.map(x => x || script_1.OPS.OP_0); |
|
|
signatures = signatures.map(x => x || script_1.OPS.OP_0); |
|
|
} |
|
|
} else { |
|
|
else { |
|
|
|
|
|
signatures = signatures.filter(x => x); |
|
|
signatures = signatures.filter(x => x); |
|
|
} |
|
|
} |
|
|
// if the transaction is not not complete (complete), or if signatures.length === m, validate
|
|
|
// if the transaction is not not complete (complete), or if signatures.length === m, validate
|
|
|
// otherwise, the number of OP_0's may be >= m, so don't validate (boo)
|
|
|
// otherwise, the number of OP_0's may be >= m, so don't validate (boo)
|
|
|
const validate = !allowIncomplete || m === signatures.length; |
|
|
const validate = !allowIncomplete || m === signatures.length; |
|
|
return payments.p2ms({ m, pubkeys, signatures }, { allowIncomplete, validate }); |
|
|
return payments.p2ms( |
|
|
|
|
|
{ m, pubkeys, signatures }, |
|
|
|
|
|
{ allowIncomplete, validate }, |
|
|
|
|
|
); |
|
|
} |
|
|
} |
|
|
case SCRIPT_TYPES.P2SH: { |
|
|
case SCRIPT_TYPES.P2SH: { |
|
|
const redeem = build(input.redeemScriptType, input, allowIncomplete); |
|
|
const redeem = build(input.redeemScriptType, input, allowIncomplete); |
|
|
if (!redeem) |
|
|
if (!redeem) return; |
|
|
return; |
|
|
|
|
|
return payments.p2sh({ |
|
|
return payments.p2sh({ |
|
|
redeem: { |
|
|
redeem: { |
|
|
output: redeem.output || input.redeemScript, |
|
|
output: redeem.output || input.redeemScript, |
|
@ -694,8 +712,7 @@ function build(type, input, allowIncomplete) { |
|
|
} |
|
|
} |
|
|
case SCRIPT_TYPES.P2WSH: { |
|
|
case SCRIPT_TYPES.P2WSH: { |
|
|
const redeem = build(input.witnessScriptType, input, allowIncomplete); |
|
|
const redeem = build(input.witnessScriptType, input, allowIncomplete); |
|
|
if (!redeem) |
|
|
if (!redeem) return; |
|
|
return; |
|
|
|
|
|
return payments.p2wsh({ |
|
|
return payments.p2wsh({ |
|
|
redeem: { |
|
|
redeem: { |
|
|
output: input.witnessScript, |
|
|
output: input.witnessScript, |
|
@ -707,13 +724,15 @@ function build(type, input, allowIncomplete) { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
function canSign(input) { |
|
|
function canSign(input) { |
|
|
return (input.signScript !== undefined && |
|
|
return ( |
|
|
|
|
|
input.signScript !== undefined && |
|
|
input.signType !== undefined && |
|
|
input.signType !== undefined && |
|
|
input.pubkeys !== undefined && |
|
|
input.pubkeys !== undefined && |
|
|
input.signatures !== undefined && |
|
|
input.signatures !== undefined && |
|
|
input.signatures.length === input.pubkeys.length && |
|
|
input.signatures.length === input.pubkeys.length && |
|
|
input.pubkeys.length > 0 && |
|
|
input.pubkeys.length > 0 && |
|
|
(input.hasWitness === false || input.value !== undefined)); |
|
|
(input.hasWitness === false || input.value !== undefined) |
|
|
|
|
|
); |
|
|
} |
|
|
} |
|
|
function signatureHashType(buffer) { |
|
|
function signatureHashType(buffer) { |
|
|
return buffer.readUInt8(buffer.length - 1); |
|
|
return buffer.readUInt8(buffer.length - 1); |
|
|