|
@ -9,7 +9,39 @@ var ECPair = require('./ecpair') |
|
|
var ECSignature = require('./ecsignature') |
|
|
var ECSignature = require('./ecsignature') |
|
|
var Transaction = require('./transaction') |
|
|
var Transaction = require('./transaction') |
|
|
|
|
|
|
|
|
function extractInput (txIn) { |
|
|
// re-orders signatures to match pubKeys, fills undefined otherwise
|
|
|
|
|
|
function fixMSSignatures (transaction, vin, pubKeys, signatures, prevOutScript, hashType, skipPubKey) { |
|
|
|
|
|
// maintain a local copy of unmatched signatures
|
|
|
|
|
|
var unmatched = signatures.slice() |
|
|
|
|
|
var cache = {} |
|
|
|
|
|
|
|
|
|
|
|
return pubKeys.map(function (pubKey) { |
|
|
|
|
|
// skip optionally provided pubKey
|
|
|
|
|
|
if (skipPubKey && bufferutils.equal(skipPubKey, pubKey)) return undefined |
|
|
|
|
|
|
|
|
|
|
|
var matched |
|
|
|
|
|
var keyPair2 = ECPair.fromPublicKeyBuffer(pubKey) |
|
|
|
|
|
|
|
|
|
|
|
// check for a matching signature
|
|
|
|
|
|
unmatched.some(function (signature, i) { |
|
|
|
|
|
// skip if undefined || OP_0
|
|
|
|
|
|
if (!signature) return false |
|
|
|
|
|
|
|
|
|
|
|
var signatureHash = cache[hashType] = cache[hashType] || transaction.hashForSignature(vin, prevOutScript, hashType) |
|
|
|
|
|
if (!keyPair2.verify(signatureHash, signature)) return false |
|
|
|
|
|
|
|
|
|
|
|
// remove matched signature from unmatched
|
|
|
|
|
|
unmatched[i] = undefined |
|
|
|
|
|
matched = signature |
|
|
|
|
|
|
|
|
|
|
|
return true |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
return matched || undefined |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function extractInput (transaction, txIn, vin) { |
|
|
var redeemScript |
|
|
var redeemScript |
|
|
var scriptSig = txIn.script |
|
|
var scriptSig = txIn.script |
|
|
var scriptSigChunks = bscript.decompile(scriptSig) |
|
|
var scriptSigChunks = bscript.decompile(scriptSig) |
|
@ -63,7 +95,7 @@ function extractInput (txIn) { |
|
|
|
|
|
|
|
|
case 'multisig': |
|
|
case 'multisig': |
|
|
signatures = scriptSigChunks.slice(1).map(function (chunk) { |
|
|
signatures = scriptSigChunks.slice(1).map(function (chunk) { |
|
|
if (chunk === ops.OP_0) return chunk |
|
|
if (chunk === ops.OP_0) return undefined |
|
|
|
|
|
|
|
|
var parsed = ECSignature.parseScriptSignature(chunk) |
|
|
var parsed = ECSignature.parseScriptSignature(chunk) |
|
|
hashType = parsed.hashType |
|
|
hashType = parsed.hashType |
|
@ -73,6 +105,10 @@ function extractInput (txIn) { |
|
|
|
|
|
|
|
|
if (redeemScript) { |
|
|
if (redeemScript) { |
|
|
pubKeys = redeemScriptChunks.slice(1, -2) |
|
|
pubKeys = redeemScriptChunks.slice(1, -2) |
|
|
|
|
|
|
|
|
|
|
|
if (pubKeys.length !== signatures.length) { |
|
|
|
|
|
signatures = fixMSSignatures(transaction, vin, pubKeys, signatures, redeemScript, hashType, redeemScript) |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
break |
|
|
break |
|
@ -117,7 +153,7 @@ TransactionBuilder.fromTransaction = function (transaction, network) { |
|
|
}) |
|
|
}) |
|
|
|
|
|
|
|
|
// Extract/add signatures
|
|
|
// Extract/add signatures
|
|
|
txb.inputs = transaction.ins.map(function (txIn) { |
|
|
txb.inputs = transaction.ins.map(function (txIn, vin) { |
|
|
// TODO: verify whether extractInput is sane with coinbase scripts
|
|
|
// TODO: verify whether extractInput is sane with coinbase scripts
|
|
|
if (Transaction.isCoinbaseHash(txIn.hash)) { |
|
|
if (Transaction.isCoinbaseHash(txIn.hash)) { |
|
|
throw new Error('coinbase inputs not supported') |
|
|
throw new Error('coinbase inputs not supported') |
|
@ -126,7 +162,7 @@ TransactionBuilder.fromTransaction = function (transaction, network) { |
|
|
// Ignore empty scripts
|
|
|
// Ignore empty scripts
|
|
|
if (txIn.script.length === 0) return {} |
|
|
if (txIn.script.length === 0) return {} |
|
|
|
|
|
|
|
|
return extractInput(txIn) |
|
|
return extractInput(transaction, txIn, vin) |
|
|
}) |
|
|
}) |
|
|
|
|
|
|
|
|
return txb |
|
|
return txb |
|
@ -154,10 +190,14 @@ TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOu |
|
|
switch (prevOutType) { |
|
|
switch (prevOutType) { |
|
|
case 'multisig': |
|
|
case 'multisig': |
|
|
input.pubKeys = prevOutScriptChunks.slice(1, -2) |
|
|
input.pubKeys = prevOutScriptChunks.slice(1, -2) |
|
|
|
|
|
input.signatures = input.pubKeys.map(function () { return undefined }) |
|
|
|
|
|
|
|
|
break |
|
|
break |
|
|
|
|
|
|
|
|
case 'pubkey': |
|
|
case 'pubkey': |
|
|
input.pubKeys = prevOutScriptChunks.slice(0, 1) |
|
|
input.pubKeys = prevOutScriptChunks.slice(0, 1) |
|
|
|
|
|
input.signatures = [undefined] |
|
|
|
|
|
|
|
|
break |
|
|
break |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -211,10 +251,10 @@ TransactionBuilder.prototype.buildIncomplete = function () { |
|
|
return this.__build(true) |
|
|
return this.__build(true) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var canSignTypes = { |
|
|
var canBuildTypes = { |
|
|
'pubkeyhash': true, |
|
|
|
|
|
'multisig': true, |
|
|
'multisig': true, |
|
|
'pubkey': true |
|
|
'pubkey': true, |
|
|
|
|
|
'pubkeyhash': true |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
TransactionBuilder.prototype.__build = function (allowIncomplete) { |
|
|
TransactionBuilder.prototype.__build = function (allowIncomplete) { |
|
@ -225,14 +265,16 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) { |
|
|
|
|
|
|
|
|
var tx = this.tx.clone() |
|
|
var tx = this.tx.clone() |
|
|
|
|
|
|
|
|
// Create script signatures from signature meta-data
|
|
|
// Create script signatures from inputs
|
|
|
this.inputs.forEach(function (input, index) { |
|
|
this.inputs.forEach(function (input, index) { |
|
|
var scriptType = input.scriptType |
|
|
var scriptType = input.scriptType |
|
|
var scriptSig |
|
|
var scriptSig |
|
|
|
|
|
|
|
|
if (!allowIncomplete) { |
|
|
if (!allowIncomplete) { |
|
|
if (!scriptType) throw new Error('Transaction is not complete') |
|
|
if (!scriptType) throw new Error('Transaction is not complete') |
|
|
if (!canSignTypes[scriptType]) throw new Error(scriptType + ' not supported') |
|
|
if (!canBuildTypes[scriptType]) throw new Error(scriptType + ' not supported') |
|
|
|
|
|
|
|
|
|
|
|
// XXX: only relevant to types that need signatures
|
|
|
if (!input.signatures) throw new Error('Transaction is missing signatures') |
|
|
if (!input.signatures) throw new Error('Transaction is missing signatures') |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -244,7 +286,6 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) { |
|
|
break |
|
|
break |
|
|
|
|
|
|
|
|
case 'multisig': |
|
|
case 'multisig': |
|
|
// Array.prototype.map is sparse-compatible
|
|
|
|
|
|
var msSignatures = input.signatures.map(function (signature) { |
|
|
var msSignatures = input.signatures.map(function (signature) { |
|
|
return signature && signature.toScriptSignature(input.hashType) |
|
|
return signature && signature.toScriptSignature(input.hashType) |
|
|
}) |
|
|
}) |
|
@ -252,12 +293,11 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) { |
|
|
// fill in blanks with OP_0
|
|
|
// fill in blanks with OP_0
|
|
|
if (allowIncomplete) { |
|
|
if (allowIncomplete) { |
|
|
for (var i = 0; i < msSignatures.length; ++i) { |
|
|
for (var i = 0; i < msSignatures.length; ++i) { |
|
|
if (msSignatures[i]) continue |
|
|
msSignatures[i] = msSignatures[i] || ops.OP_0 |
|
|
|
|
|
|
|
|
msSignatures[i] = ops.OP_0 |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// remove blank signatures
|
|
|
} else { |
|
|
} else { |
|
|
// Array.prototype.filter returns non-sparse array
|
|
|
|
|
|
msSignatures = msSignatures.filter(function (x) { return x }) |
|
|
msSignatures = msSignatures.filter(function (x) { return x }) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -297,11 +337,12 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash |
|
|
input.prevOutType && |
|
|
input.prevOutType && |
|
|
input.pubKeys && |
|
|
input.pubKeys && |
|
|
input.scriptType && |
|
|
input.scriptType && |
|
|
input.signatures |
|
|
input.signatures && |
|
|
|
|
|
input.signatures.length === input.pubKeys.length |
|
|
|
|
|
|
|
|
var kpPubKey = keyPair.getPublicKeyBuffer() |
|
|
var kpPubKey = keyPair.getPublicKeyBuffer() |
|
|
|
|
|
|
|
|
// are we almost ready to sign?
|
|
|
// are we ready to sign?
|
|
|
if (canSign) { |
|
|
if (canSign) { |
|
|
// if redeemScript was provided, enforce consistency
|
|
|
// if redeemScript was provided, enforce consistency
|
|
|
if (redeemScript) { |
|
|
if (redeemScript) { |
|
@ -323,13 +364,13 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var scriptType = bscript.classifyOutput(redeemScript) |
|
|
var scriptType = bscript.classifyOutput(redeemScript) |
|
|
if (!canSignTypes[scriptType]) throw new Error('RedeemScript not supported (' + scriptType + ')') |
|
|
|
|
|
|
|
|
|
|
|
var redeemScriptChunks = bscript.decompile(redeemScript) |
|
|
var redeemScriptChunks = bscript.decompile(redeemScript) |
|
|
var pubKeys = [] |
|
|
var pubKeys |
|
|
|
|
|
|
|
|
switch (scriptType) { |
|
|
switch (scriptType) { |
|
|
case 'multisig': |
|
|
case 'multisig': |
|
|
pubKeys = redeemScriptChunks.slice(1, -2) |
|
|
pubKeys = redeemScriptChunks.slice(1, -2) |
|
|
|
|
|
|
|
|
break |
|
|
break |
|
|
|
|
|
|
|
|
case 'pubkeyhash': |
|
|
case 'pubkeyhash': |
|
@ -338,11 +379,16 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash |
|
|
|
|
|
|
|
|
if (!bufferutils.equal(pkh1, pkh2)) throw new Error('privateKey cannot sign for this input') |
|
|
if (!bufferutils.equal(pkh1, pkh2)) throw new Error('privateKey cannot sign for this input') |
|
|
pubKeys = [kpPubKey] |
|
|
pubKeys = [kpPubKey] |
|
|
|
|
|
|
|
|
break |
|
|
break |
|
|
|
|
|
|
|
|
case 'pubkey': |
|
|
case 'pubkey': |
|
|
pubKeys = redeemScriptChunks.slice(0, 1) |
|
|
pubKeys = redeemScriptChunks.slice(0, 1) |
|
|
|
|
|
|
|
|
break |
|
|
break |
|
|
|
|
|
|
|
|
|
|
|
default: |
|
|
|
|
|
throw new Error('RedeemScript not supported (' + scriptType + ')') |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// if we don't have a prevOutScript, generate a P2SH script
|
|
|
// if we don't have a prevOutScript, generate a P2SH script
|
|
@ -354,55 +400,31 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash |
|
|
input.pubKeys = pubKeys |
|
|
input.pubKeys = pubKeys |
|
|
input.redeemScript = redeemScript |
|
|
input.redeemScript = redeemScript |
|
|
input.scriptType = scriptType |
|
|
input.scriptType = scriptType |
|
|
|
|
|
input.signatures = pubKeys.map(function () { return undefined }) |
|
|
// cannot be pay-to-scriptHash
|
|
|
|
|
|
} else { |
|
|
} else { |
|
|
|
|
|
// pay-to-scriptHash is not possible without a redeemScript
|
|
|
if (input.prevOutType === 'scripthash') throw new Error('PrevOutScript is P2SH, missing redeemScript') |
|
|
if (input.prevOutType === 'scripthash') throw new Error('PrevOutScript is P2SH, missing redeemScript') |
|
|
|
|
|
|
|
|
// can we otherwise sign this?
|
|
|
// if we don't have a scriptType, assume pubKeyHash otherwise
|
|
|
if (input.scriptType) { |
|
|
if (!input.scriptType) { |
|
|
if (!input.pubKeys) throw new Error(input.scriptType + ' not supported') |
|
|
|
|
|
|
|
|
|
|
|
// we know nothin' Jon Snow, assume pubKeyHash
|
|
|
|
|
|
} else { |
|
|
|
|
|
input.prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(keyPair.getPublicKeyBuffer())) |
|
|
input.prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(keyPair.getPublicKeyBuffer())) |
|
|
input.prevOutType = 'pubkeyhash' |
|
|
input.prevOutType = 'pubkeyhash' |
|
|
input.pubKeys = [kpPubKey] |
|
|
input.pubKeys = [kpPubKey] |
|
|
input.scriptType = input.prevOutType |
|
|
input.scriptType = input.prevOutType |
|
|
|
|
|
input.signatures = [undefined] |
|
|
|
|
|
} else { |
|
|
|
|
|
// throw if we can't sign with it
|
|
|
|
|
|
if (!input.pubKeys || !input.signatures) throw new Error(input.scriptType + ' not supported') |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
input.hashType = hashType |
|
|
input.hashType = hashType |
|
|
input.signatures = input.signatures || [] |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ready to sign?
|
|
|
var signatureScript = input.redeemScript || input.prevOutScript |
|
|
var signatureScript = input.redeemScript || input.prevOutScript |
|
|
var signatureHash = this.tx.hashForSignature(index, signatureScript, hashType) |
|
|
var signatureHash = this.tx.hashForSignature(index, signatureScript, hashType) |
|
|
|
|
|
|
|
|
// enforce signature order matches public keys
|
|
|
|
|
|
if (input.scriptType === 'multisig' && input.redeemScript && input.signatures.length !== input.pubKeys.length) { |
|
|
|
|
|
// maintain a local copy of unmatched signatures
|
|
|
|
|
|
var unmatched = input.signatures.slice() |
|
|
|
|
|
|
|
|
|
|
|
input.signatures = input.pubKeys.map(function (pubKey) { |
|
|
|
|
|
var match |
|
|
|
|
|
var keyPair2 = ECPair.fromPublicKeyBuffer(pubKey) |
|
|
|
|
|
|
|
|
|
|
|
// check for any matching signatures
|
|
|
|
|
|
unmatched.some(function (signature, i) { |
|
|
|
|
|
if (!signature || !keyPair2.verify(signatureHash, signature)) return false |
|
|
|
|
|
match = signature |
|
|
|
|
|
|
|
|
|
|
|
// remove matched signature from unmatched
|
|
|
|
|
|
unmatched.splice(i, 1) |
|
|
|
|
|
|
|
|
|
|
|
return true |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
return match || undefined |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// enforce in order signing of public keys
|
|
|
// enforce in order signing of public keys
|
|
|
var valid = input.pubKeys.some(function (pubKey, i) { |
|
|
var valid = input.pubKeys.some(function (pubKey, i) { |
|
|
if (!bufferutils.equal(kpPubKey, pubKey)) return false |
|
|
if (!bufferutils.equal(kpPubKey, pubKey)) return false |
|
|