|
|
@ -12,163 +12,251 @@ var ECPair = require('./ecpair') |
|
|
|
var ECSignature = require('./ecsignature') |
|
|
|
var Transaction = require('./transaction') |
|
|
|
|
|
|
|
// 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 signatureHash |
|
|
|
|
|
|
|
return pubKeys.map(function (pubKey) { |
|
|
|
// skip optionally provided pubKey
|
|
|
|
if (skipPubKey && bufferEquals(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 |
|
|
|
// inspects a scriptSig w/ optional provided redeemScript and derives
|
|
|
|
// all necessary input information as required by TransactionBuilder
|
|
|
|
function expandInput (scriptSig, redeemScript) { |
|
|
|
var scriptSigChunks = bscript.decompile(scriptSig) |
|
|
|
var scriptSigType = bscript.classifyInput(scriptSigChunks, true) |
|
|
|
|
|
|
|
if (!signatureHash) { |
|
|
|
signatureHash = transaction.hashForSignature(vin, prevOutScript, hashType) |
|
|
|
} |
|
|
|
if (!keyPair2.verify(signatureHash, signature)) return false |
|
|
|
var hashType, pubKeys, signatures, prevOutScript |
|
|
|
|
|
|
|
// remove matched signature from unmatched
|
|
|
|
unmatched[i] = undefined |
|
|
|
matched = signature |
|
|
|
switch (scriptSigType) { |
|
|
|
case 'scripthash': |
|
|
|
// FIXME: maybe depth limit instead, how possible is this anyway?
|
|
|
|
if (redeemScript) throw new Error('Recursive P2SH script') |
|
|
|
|
|
|
|
return true |
|
|
|
}) |
|
|
|
|
|
|
|
return matched || undefined |
|
|
|
}) |
|
|
|
} |
|
|
|
var redeemScriptSig = scriptSigChunks.slice(0, -1) |
|
|
|
redeemScript = scriptSigChunks[scriptSigChunks.length - 1] |
|
|
|
|
|
|
|
function extractInput (transaction, txIn, vin) { |
|
|
|
if (txIn.script.length === 0) return {} |
|
|
|
var result = expandInput(redeemScriptSig, redeemScript) |
|
|
|
result.redeemScript = redeemScript |
|
|
|
result.redeemScriptType = result.prevOutType |
|
|
|
result.prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript)) |
|
|
|
result.prevOutType = 'scripthash' |
|
|
|
return result |
|
|
|
|
|
|
|
var scriptSigChunks = bscript.decompile(txIn.script) |
|
|
|
var prevOutType = bscript.classifyInput(scriptSigChunks, true) |
|
|
|
case 'pubkeyhash': |
|
|
|
// if (redeemScript) throw new Error('Nonstandard... P2SH(P2PKH)')
|
|
|
|
var s = ECSignature.parseScriptSignature(scriptSigChunks[0]) |
|
|
|
hashType = s.hashType |
|
|
|
pubKeys = scriptSigChunks.slice(1) |
|
|
|
signatures = [s.signature] |
|
|
|
|
|
|
|
function processScript (scriptType, scriptSigChunks, redeemScriptChunks) { |
|
|
|
// ensure chunks are decompiled
|
|
|
|
scriptSigChunks = bscript.decompile(scriptSigChunks) |
|
|
|
redeemScriptChunks = redeemScriptChunks ? bscript.decompile(redeemScriptChunks) : undefined |
|
|
|
if (redeemScript) break |
|
|
|
|
|
|
|
var hashType, pubKeys, signatures, prevOutScript, redeemScript, redeemScriptType, result, parsed |
|
|
|
prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(pubKeys[0])) |
|
|
|
break |
|
|
|
|
|
|
|
switch (scriptType) { |
|
|
|
case 'scripthash': |
|
|
|
redeemScript = scriptSigChunks.slice(-1)[0] |
|
|
|
scriptSigChunks = bscript.compile(scriptSigChunks.slice(0, -1)) |
|
|
|
case 'pubkey': |
|
|
|
if (redeemScript) { |
|
|
|
pubKeys = bscript.decompile(redeemScript).slice(0, 1) |
|
|
|
} |
|
|
|
|
|
|
|
redeemScriptType = bscript.classifyInput(scriptSigChunks, true) |
|
|
|
prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript)) |
|
|
|
var ss = ECSignature.parseScriptSignature(scriptSigChunks[0]) |
|
|
|
hashType = ss.hashType |
|
|
|
signatures = [ss.signature] |
|
|
|
break |
|
|
|
|
|
|
|
result = processScript(redeemScriptType, scriptSigChunks, bscript.decompile(redeemScript)) |
|
|
|
case 'multisig': |
|
|
|
if (redeemScript) { |
|
|
|
pubKeys = bscript.decompile(redeemScript).slice(1, -2) |
|
|
|
} |
|
|
|
|
|
|
|
result.prevOutScript = prevOutScript |
|
|
|
result.redeemScript = redeemScript |
|
|
|
result.redeemScriptType = redeemScriptType |
|
|
|
signatures = scriptSigChunks.slice(1).map(function (chunk) { |
|
|
|
if (chunk === ops.OP_0) return undefined |
|
|
|
|
|
|
|
return result |
|
|
|
var sss = ECSignature.parseScriptSignature(chunk) |
|
|
|
|
|
|
|
case 'pubkeyhash': |
|
|
|
parsed = ECSignature.parseScriptSignature(scriptSigChunks[0]) |
|
|
|
hashType = parsed.hashType |
|
|
|
pubKeys = scriptSigChunks.slice(1) |
|
|
|
signatures = [parsed.signature] |
|
|
|
prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(pubKeys[0])) |
|
|
|
if (hashType !== undefined) { |
|
|
|
if (sss.hashType !== hashType) throw new Error('Inconsistent hashType') |
|
|
|
} else { |
|
|
|
hashType = sss.hashType |
|
|
|
} |
|
|
|
|
|
|
|
break |
|
|
|
return sss.signature |
|
|
|
}) |
|
|
|
|
|
|
|
case 'pubkey': |
|
|
|
parsed = ECSignature.parseScriptSignature(scriptSigChunks[0]) |
|
|
|
hashType = parsed.hashType |
|
|
|
signatures = [parsed.signature] |
|
|
|
break |
|
|
|
} |
|
|
|
|
|
|
|
if (redeemScriptChunks) { |
|
|
|
pubKeys = redeemScriptChunks.slice(0, 1) |
|
|
|
} |
|
|
|
return { |
|
|
|
hashType: hashType, |
|
|
|
pubKeys: pubKeys, |
|
|
|
signatures: signatures, |
|
|
|
prevOutScript: prevOutScript, |
|
|
|
prevOutType: scriptSigType |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
break |
|
|
|
function expandOutput (script, ourPubKey) { |
|
|
|
typeforce(types.Buffer, script) |
|
|
|
|
|
|
|
case 'multisig': |
|
|
|
signatures = scriptSigChunks.slice(1).map(function (chunk) { |
|
|
|
if (chunk === ops.OP_0) return undefined |
|
|
|
var scriptChunks = bscript.decompile(script) |
|
|
|
var scriptType = bscript.classifyOutput(script) |
|
|
|
|
|
|
|
parsed = ECSignature.parseScriptSignature(chunk) |
|
|
|
hashType = parsed.hashType |
|
|
|
var pubKeys = [] |
|
|
|
|
|
|
|
return parsed.signature |
|
|
|
}) |
|
|
|
switch (scriptType) { |
|
|
|
// does our hash160(pubKey) match the output scripts?
|
|
|
|
case 'pubkeyhash': |
|
|
|
if (!ourPubKey) break |
|
|
|
|
|
|
|
if (redeemScriptChunks) { |
|
|
|
pubKeys = redeemScriptChunks.slice(1, -2) |
|
|
|
var pkh1 = scriptChunks[2] |
|
|
|
var pkh2 = bcrypto.hash160(ourPubKey) |
|
|
|
if (bufferEquals(pkh1, pkh2)) pubKeys = [ourPubKey] |
|
|
|
break |
|
|
|
|
|
|
|
if (pubKeys.length !== signatures.length) { |
|
|
|
signatures = fixMSSignatures(transaction, vin, pubKeys, signatures, bscript.compile(redeemScriptChunks), hashType, redeemScript) |
|
|
|
} |
|
|
|
} |
|
|
|
case 'pubkey': |
|
|
|
pubKeys = scriptChunks.slice(0, 1) |
|
|
|
break |
|
|
|
|
|
|
|
break |
|
|
|
} |
|
|
|
case 'multisig': |
|
|
|
pubKeys = scriptChunks.slice(1, -2) |
|
|
|
break |
|
|
|
|
|
|
|
return { |
|
|
|
hashType: hashType, |
|
|
|
pubKeys: pubKeys, |
|
|
|
signatures: signatures, |
|
|
|
prevOutScript: prevOutScript, |
|
|
|
redeemScript: redeemScript, |
|
|
|
redeemScriptType: redeemScriptType |
|
|
|
} |
|
|
|
default: return |
|
|
|
} |
|
|
|
|
|
|
|
// Extract hashType, pubKeys, signatures and prevOutScript
|
|
|
|
var result = processScript(prevOutType, scriptSigChunks) |
|
|
|
|
|
|
|
return { |
|
|
|
hashType: result.hashType, |
|
|
|
prevOutScript: result.prevOutScript, |
|
|
|
prevOutType: prevOutType, |
|
|
|
pubKeys: result.pubKeys, |
|
|
|
redeemScript: result.redeemScript, |
|
|
|
redeemScriptType: result.redeemScriptType, |
|
|
|
signatures: result.signatures |
|
|
|
pubKeys: pubKeys, |
|
|
|
scriptType: scriptType, |
|
|
|
signatures: pubKeys.map(function () { return undefined }) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
function extractFromOutputScript (outputScript, kpPubKey) { |
|
|
|
var scriptType = bscript.classifyOutput(outputScript) |
|
|
|
var outputScriptChunks = bscript.decompile(outputScript) |
|
|
|
function buildInput (input, scriptType, allowIncomplete) { |
|
|
|
var signatures = input.signatures |
|
|
|
var scriptSig |
|
|
|
|
|
|
|
var pubKeys |
|
|
|
switch (scriptType) { |
|
|
|
case 'pubkeyhash': |
|
|
|
var pkh1 = outputScriptChunks[2] |
|
|
|
var pkh2 = bcrypto.hash160(kpPubKey) |
|
|
|
// remove blank signatures
|
|
|
|
signatures = signatures.filter(function (x) { return x }) |
|
|
|
|
|
|
|
if (!bufferEquals(pkh1, pkh2)) throw new Error('privateKey cannot sign for this input') |
|
|
|
pubKeys = [kpPubKey] |
|
|
|
if (signatures.length < 1) throw new Error('Not enough signatures provided') |
|
|
|
if (signatures.length > 1) throw new Error('Too many signatures provided') |
|
|
|
|
|
|
|
var pkhSignature = signatures[0].toScriptSignature(input.hashType) |
|
|
|
scriptSig = bscript.pubKeyHashInput(pkhSignature, input.pubKeys[0]) |
|
|
|
break |
|
|
|
|
|
|
|
case 'pubkey': |
|
|
|
pubKeys = outputScriptChunks.slice(0, 1) |
|
|
|
// remove blank signatures
|
|
|
|
signatures = signatures.filter(function (x) { return x }) |
|
|
|
|
|
|
|
if (signatures.length < 1) throw new Error('Not enough signatures provided') |
|
|
|
if (signatures.length > 1) throw new Error('Too many signatures provided') |
|
|
|
|
|
|
|
var pkSignature = signatures[0].toScriptSignature(input.hashType) |
|
|
|
scriptSig = bscript.pubKeyInput(pkSignature) |
|
|
|
break |
|
|
|
|
|
|
|
// ref https://github.com/bitcoin/bitcoin/blob/d612837814020ae832499d18e6ee5eb919a87907/src/script/sign.cpp#L232
|
|
|
|
case 'multisig': |
|
|
|
pubKeys = outputScriptChunks.slice(1, -2) |
|
|
|
signatures = signatures.map(function (signature) { |
|
|
|
return signature && signature.toScriptSignature(input.hashType) |
|
|
|
}) |
|
|
|
|
|
|
|
if (allowIncomplete) { |
|
|
|
// fill in blanks with OP_0
|
|
|
|
for (var i = 0; i < signatures.length; ++i) { |
|
|
|
signatures[i] = signatures[i] || ops.OP_0 |
|
|
|
} |
|
|
|
} else { |
|
|
|
// remove blank signatures
|
|
|
|
signatures = signatures.filter(function (x) { return x }) |
|
|
|
} |
|
|
|
|
|
|
|
scriptSig = bscript.multisigInput(signatures, allowIncomplete ? undefined : input.redeemScript) |
|
|
|
break |
|
|
|
} |
|
|
|
|
|
|
|
default: return |
|
|
|
// wrap as scriptHash if necessary
|
|
|
|
if (input.prevOutType === 'scripthash') { |
|
|
|
scriptSig = bscript.scriptHashInput(scriptSig, input.redeemScript) |
|
|
|
} |
|
|
|
|
|
|
|
return { |
|
|
|
pubKeys: pubKeys, |
|
|
|
scriptType: scriptType |
|
|
|
return scriptSig |
|
|
|
} |
|
|
|
|
|
|
|
function prepareInput (input, kpPubKey, redeemScript, hashType) { |
|
|
|
if (redeemScript) { |
|
|
|
var redeemScriptHash = bcrypto.hash160(redeemScript) |
|
|
|
|
|
|
|
// if redeemScript exists, it is pay-to-scriptHash
|
|
|
|
// if we have a prevOutScript, enforce hash160(redeemScriptequality) to the redeemScript
|
|
|
|
if (input.prevOutType) { |
|
|
|
if (input.prevOutType !== 'scripthash') throw new Error('PrevOutScript must be P2SH') |
|
|
|
|
|
|
|
var prevOutScriptScriptHash = bscript.decompile(input.prevOutScript)[1] |
|
|
|
if (!bufferEquals(prevOutScriptScriptHash, redeemScriptHash)) throw new Error('Inconsistent hash160(RedeemScript)') |
|
|
|
|
|
|
|
// or, we don't have a prevOutScript, so generate a P2SH script
|
|
|
|
} else { |
|
|
|
input.prevOutScript = bscript.scriptHashOutput(redeemScriptHash) |
|
|
|
input.prevOutType = 'scripthash' |
|
|
|
} |
|
|
|
|
|
|
|
var expanded = expandOutput(redeemScript, kpPubKey) |
|
|
|
if (!expanded) throw new Error('RedeemScript not supported "' + bscript.toASM(redeemScript) + '"') |
|
|
|
|
|
|
|
input.pubKeys = expanded.pubKeys |
|
|
|
input.redeemScript = redeemScript |
|
|
|
input.redeemScriptType = expanded.scriptType |
|
|
|
input.signatures = expanded.signatures |
|
|
|
|
|
|
|
// maybe we have some prior knowledge
|
|
|
|
} else if (input.prevOutType) { |
|
|
|
// pay-to-scriptHash is not possible without a redeemScript
|
|
|
|
if (input.prevOutType === 'scripthash') throw new Error('PrevOutScript is P2SH, missing redeemScript') |
|
|
|
|
|
|
|
// throw if we can't sign with it
|
|
|
|
if (!input.pubKeys || !input.signatures) throw new Error(input.prevOutType + ' not supported') |
|
|
|
|
|
|
|
// no prior knowledge, assume pubKeyHash
|
|
|
|
} else { |
|
|
|
input.prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(kpPubKey)) |
|
|
|
input.prevOutType = 'pubkeyhash' |
|
|
|
|
|
|
|
input.pubKeys = [kpPubKey] |
|
|
|
input.signatures = [undefined] |
|
|
|
} |
|
|
|
|
|
|
|
input.hashType = hashType |
|
|
|
} |
|
|
|
|
|
|
|
function fixMultisigOrder (input, transaction, vin) { |
|
|
|
var hashScriptType = input.redeemScriptType || input.prevOutType |
|
|
|
if (hashScriptType !== 'multisig') throw new TypeError('Expected multisig input') |
|
|
|
|
|
|
|
var hashType = input.hashType || Transaction.SIGHASH_ALL |
|
|
|
var hashScript = input.redeemScript || input.prevOutScript |
|
|
|
|
|
|
|
// maintain a local copy of unmatched signatures
|
|
|
|
var unmatched = input.signatures.concat() |
|
|
|
var signatureHash = transaction.hashForSignature(vin, hashScript, hashType) |
|
|
|
|
|
|
|
input.signatures = input.pubKeys.map(function (pubKey, y) { |
|
|
|
var keyPair = ECPair.fromPublicKeyBuffer(pubKey) |
|
|
|
var match |
|
|
|
|
|
|
|
// check for a signature
|
|
|
|
unmatched.some(function (signature, i) { |
|
|
|
// skip if undefined || OP_0
|
|
|
|
if (!signature) return false |
|
|
|
|
|
|
|
// skip if signature does not match pubKey
|
|
|
|
if (!keyPair.verify(signatureHash, signature)) return false |
|
|
|
|
|
|
|
// remove matched signature from unmatched
|
|
|
|
unmatched[i] = undefined |
|
|
|
match = signature |
|
|
|
|
|
|
|
return true |
|
|
|
}) |
|
|
|
|
|
|
|
return match || undefined |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
function TransactionBuilder (network) { |
|
|
@ -204,34 +292,40 @@ TransactionBuilder.prototype.setVersion = function (version) { |
|
|
|
TransactionBuilder.fromTransaction = function (transaction, network) { |
|
|
|
var txb = new TransactionBuilder(network) |
|
|
|
|
|
|
|
// Copy other transaction fields
|
|
|
|
txb.tx.version = transaction.version |
|
|
|
txb.tx.locktime = transaction.locktime |
|
|
|
|
|
|
|
// Extract/add inputs
|
|
|
|
transaction.ins.forEach(function (txIn) { |
|
|
|
txb.addInput(txIn.hash, txIn.index, txIn.sequence) |
|
|
|
}) |
|
|
|
// Copy transaction fields
|
|
|
|
txb.setVersion(transaction.version) |
|
|
|
txb.setLockTime(transaction.locktime) |
|
|
|
|
|
|
|
// Extract/add outputs
|
|
|
|
// Copy outputs (done first to avoid signature invalidation)
|
|
|
|
transaction.outs.forEach(function (txOut) { |
|
|
|
txb.addOutput(txOut.script, txOut.value) |
|
|
|
}) |
|
|
|
|
|
|
|
// Extract/add signatures
|
|
|
|
txb.inputs = transaction.ins.map(function (txIn, vin) { |
|
|
|
// TODO: verify whether extractInput is sane with coinbase scripts
|
|
|
|
if (Transaction.isCoinbaseHash(txIn.hash)) { |
|
|
|
throw new Error('coinbase inputs not supported') |
|
|
|
} |
|
|
|
// Copy inputs
|
|
|
|
transaction.ins.forEach(function (txIn) { |
|
|
|
txb.__addInputUnsafe(txIn.hash, txIn.index, txIn.sequence, txIn.script) |
|
|
|
}) |
|
|
|
|
|
|
|
return extractInput(transaction, txIn, vin) |
|
|
|
// fix some things not possible through the public API
|
|
|
|
txb.inputs.forEach(function (input, i) { |
|
|
|
// attempt to fix any multisig inputs if they exist
|
|
|
|
if ((input.redeemScriptType || input.prevOutType) === 'multisig') { |
|
|
|
// pubKeys will only exist for 'multisig' if a redeemScript was found
|
|
|
|
if (!input.pubKeys || !input.signatures) return |
|
|
|
if (input.pubKeys.length === input.signatures.length) return |
|
|
|
|
|
|
|
fixMultisigOrder(input, transaction, i) |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
return txb |
|
|
|
} |
|
|
|
|
|
|
|
TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOutScript) { |
|
|
|
if (!this.__canModifyInputs()) { |
|
|
|
throw new Error('No, this would invalidate signatures') |
|
|
|
} |
|
|
|
|
|
|
|
// is it a hex string?
|
|
|
|
if (typeof txHash === 'string') { |
|
|
|
// transaction hashs's are displayed in reverse order, un-reverse it
|
|
|
@ -243,73 +337,50 @@ TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOu |
|
|
|
txHash = txHash.getHash() |
|
|
|
} |
|
|
|
|
|
|
|
var input = {} |
|
|
|
if (prevOutScript) { |
|
|
|
var prevOutScriptChunks = bscript.decompile(prevOutScript) |
|
|
|
var prevOutType = bscript.classifyOutput(prevOutScriptChunks) |
|
|
|
return this.__addInputUnsafe(txHash, vout, sequence, null, prevOutScript) |
|
|
|
} |
|
|
|
|
|
|
|
// if we can, extract pubKey information
|
|
|
|
switch (prevOutType) { |
|
|
|
case 'multisig': |
|
|
|
input.pubKeys = prevOutScriptChunks.slice(1, -2) |
|
|
|
input.signatures = input.pubKeys.map(function () { return undefined }) |
|
|
|
TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, sequence, scriptSig, prevOutScript) { |
|
|
|
if (Transaction.isCoinbaseHash(txHash)) { |
|
|
|
throw new Error('coinbase inputs not supported') |
|
|
|
} |
|
|
|
|
|
|
|
break |
|
|
|
var prevTxOut = txHash.toString('hex') + ':' + vout |
|
|
|
if (this.prevTxMap[prevTxOut]) throw new Error('Duplicate TxOut: ' + prevTxOut) |
|
|
|
|
|
|
|
case 'pubkey': |
|
|
|
input.pubKeys = prevOutScriptChunks.slice(0, 1) |
|
|
|
input.signatures = [undefined] |
|
|
|
var input = {} |
|
|
|
|
|
|
|
break |
|
|
|
} |
|
|
|
// derive what we can from the scriptSig
|
|
|
|
if (scriptSig) { |
|
|
|
input = expandInput(scriptSig) |
|
|
|
} |
|
|
|
|
|
|
|
if (prevOutType !== 'scripthash') { |
|
|
|
input.scriptType = prevOutType |
|
|
|
// derive what we can from the previous transactions output script
|
|
|
|
if (!input.prevOutScript && prevOutScript) { |
|
|
|
var prevOutScriptChunks = bscript.decompile(prevOutScript) |
|
|
|
var prevOutType = bscript.classifyOutput(prevOutScriptChunks) |
|
|
|
|
|
|
|
if (!input.pubKeys && !input.signatures) { |
|
|
|
var expanded = expandOutput(prevOutScript) |
|
|
|
if (expanded) { |
|
|
|
input.pubKeys = expanded.pubKeys |
|
|
|
input.signatures = expanded.signatures |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
input.prevOutScript = prevOutScript |
|
|
|
input.prevOutType = prevOutType |
|
|
|
} |
|
|
|
|
|
|
|
// if signatures exist, adding inputs is only acceptable if SIGHASH_ANYONECANPAY is used
|
|
|
|
// throw if any signatures *didn't* use SIGHASH_ANYONECANPAY
|
|
|
|
if (!this.inputs.every(function (otherInput) { |
|
|
|
// no signature
|
|
|
|
if (otherInput.hashType === undefined) return true |
|
|
|
|
|
|
|
return otherInput.hashType & Transaction.SIGHASH_ANYONECANPAY |
|
|
|
})) { |
|
|
|
throw new Error('No, this would invalidate signatures') |
|
|
|
} |
|
|
|
|
|
|
|
var prevTxOut = txHash.toString('hex') + ':' + vout |
|
|
|
if (this.prevTxMap[prevTxOut]) throw new Error('Duplicate TxOut: ' + prevTxOut) |
|
|
|
|
|
|
|
var vin = this.tx.addInput(txHash, vout, sequence) |
|
|
|
var vin = this.tx.addInput(txHash, vout, sequence, scriptSig) |
|
|
|
this.inputs[vin] = input |
|
|
|
this.prevTxMap[prevTxOut] = vin |
|
|
|
this.prevTxMap[prevTxOut] = true |
|
|
|
|
|
|
|
return vin |
|
|
|
} |
|
|
|
|
|
|
|
TransactionBuilder.prototype.addOutput = function (scriptPubKey, value) { |
|
|
|
var nOutputs = this.tx.outs.length |
|
|
|
|
|
|
|
// if signatures exist, adding outputs is only acceptable if SIGHASH_NONE or SIGHASH_SINGLE is used
|
|
|
|
// throws if any signatures didn't use SIGHASH_NONE|SIGHASH_SINGLE
|
|
|
|
if (!this.inputs.every(function (input, index) { |
|
|
|
// no signature
|
|
|
|
if (input.hashType === undefined) return true |
|
|
|
|
|
|
|
var hashTypeMod = input.hashType & 0x1f |
|
|
|
if (hashTypeMod === Transaction.SIGHASH_NONE) return true |
|
|
|
if (hashTypeMod === Transaction.SIGHASH_SINGLE) { |
|
|
|
// account for SIGHASH_SINGLE signing of a non-existing output, aka the "SIGHASH_SINGLE" bug
|
|
|
|
return index < nOutputs |
|
|
|
} |
|
|
|
|
|
|
|
return false |
|
|
|
})) { |
|
|
|
if (!this.__canModifyOutputs()) { |
|
|
|
throw new Error('No, this would invalidate signatures') |
|
|
|
} |
|
|
|
|
|
|
@ -334,48 +405,6 @@ var canBuildTypes = { |
|
|
|
'pubkeyhash': true |
|
|
|
} |
|
|
|
|
|
|
|
function buildFromInputData (input, scriptType, parentType, redeemScript, allowIncomplete) { |
|
|
|
var scriptSig |
|
|
|
|
|
|
|
switch (scriptType) { |
|
|
|
case 'pubkeyhash': |
|
|
|
var pkhSignature = input.signatures[0].toScriptSignature(input.hashType) |
|
|
|
scriptSig = bscript.pubKeyHashInput(pkhSignature, input.pubKeys[0]) |
|
|
|
break |
|
|
|
|
|
|
|
case 'pubkey': |
|
|
|
var pkSignature = input.signatures[0].toScriptSignature(input.hashType) |
|
|
|
scriptSig = bscript.pubKeyInput(pkSignature) |
|
|
|
break |
|
|
|
|
|
|
|
case 'multisig': |
|
|
|
var msSignatures = input.signatures.map(function (signature) { |
|
|
|
return signature && signature.toScriptSignature(input.hashType) |
|
|
|
}) |
|
|
|
|
|
|
|
// fill in blanks with OP_0
|
|
|
|
if (allowIncomplete) { |
|
|
|
for (var i = 0; i < msSignatures.length; ++i) { |
|
|
|
msSignatures[i] = msSignatures[i] || ops.OP_0 |
|
|
|
} |
|
|
|
|
|
|
|
// remove blank signatures
|
|
|
|
} else { |
|
|
|
msSignatures = msSignatures.filter(function (x) { return x }) |
|
|
|
} |
|
|
|
|
|
|
|
scriptSig = bscript.multisigInput(msSignatures, allowIncomplete ? undefined : redeemScript) |
|
|
|
break |
|
|
|
} |
|
|
|
|
|
|
|
// wrap as scriptHash if necessary
|
|
|
|
if (parentType === 'scripthash') { |
|
|
|
scriptSig = bscript.scriptHashInput(scriptSig, redeemScript) |
|
|
|
} |
|
|
|
|
|
|
|
return scriptSig |
|
|
|
} |
|
|
|
|
|
|
|
TransactionBuilder.prototype.__build = function (allowIncomplete) { |
|
|
|
if (!allowIncomplete) { |
|
|
|
if (!this.tx.ins.length) throw new Error('Transaction has no inputs') |
|
|
@ -385,48 +414,43 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) { |
|
|
|
var tx = this.tx.clone() |
|
|
|
|
|
|
|
// Create script signatures from inputs
|
|
|
|
this.inputs.forEach(function (input, index) { |
|
|
|
this.inputs.forEach(function (input, i) { |
|
|
|
var scriptType = input.redeemScriptType || input.prevOutType |
|
|
|
var scriptSig |
|
|
|
|
|
|
|
if (!allowIncomplete) { |
|
|
|
if (!scriptType) throw new Error('Transaction is not complete') |
|
|
|
if (!canBuildTypes[scriptType]) throw new Error(scriptType + ' not supported') |
|
|
|
|
|
|
|
// XXX: only relevant to types that need signatures
|
|
|
|
// FIXME: only relevant to types that need signatures
|
|
|
|
if (!input.signatures) throw new Error('Transaction is missing signatures') |
|
|
|
} |
|
|
|
|
|
|
|
if (input.signatures) { |
|
|
|
scriptSig = buildFromInputData(input, scriptType, input.prevOutType, input.redeemScript, allowIncomplete) |
|
|
|
} |
|
|
|
// FIXME: only relevant to types that need signatures
|
|
|
|
// skip if no scriptSig exists
|
|
|
|
if (!input.signatures) return |
|
|
|
|
|
|
|
// did we build a scriptSig? Buffer('') is allowed
|
|
|
|
if (scriptSig) { |
|
|
|
tx.setInputScript(index, scriptSig) |
|
|
|
} |
|
|
|
// build a scriptSig
|
|
|
|
var scriptSig = buildInput(input, scriptType, allowIncomplete) |
|
|
|
tx.setInputScript(i, scriptSig) |
|
|
|
}) |
|
|
|
|
|
|
|
return tx |
|
|
|
} |
|
|
|
|
|
|
|
TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hashType) { |
|
|
|
TransactionBuilder.prototype.sign = function (vin, keyPair, redeemScript, hashType) { |
|
|
|
if (keyPair.network !== this.network) throw new Error('Inconsistent network') |
|
|
|
if (!this.inputs[index]) throw new Error('No input at index: ' + index) |
|
|
|
if (!this.inputs[vin]) throw new Error('No input at index: ' + vin) |
|
|
|
hashType = hashType || Transaction.SIGHASH_ALL |
|
|
|
|
|
|
|
var input = this.inputs[index] |
|
|
|
var canSign = input.hashType && |
|
|
|
input.prevOutScript && |
|
|
|
input.prevOutType && |
|
|
|
input.pubKeys && |
|
|
|
input.redeemScriptType && |
|
|
|
input.signatures && |
|
|
|
var input = this.inputs[vin] |
|
|
|
var canSign = input.hashType !== undefined && |
|
|
|
input.prevOutScript !== undefined && |
|
|
|
input.pubKeys !== undefined && |
|
|
|
input.signatures !== undefined && |
|
|
|
input.signatures.length === input.pubKeys.length |
|
|
|
|
|
|
|
var kpPubKey = keyPair.getPublicKeyBuffer() |
|
|
|
|
|
|
|
// are we ready to sign?
|
|
|
|
if (canSign) { |
|
|
|
// if redeemScript was provided, enforce consistency
|
|
|
|
if (redeemScript) { |
|
|
@ -434,56 +458,13 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash |
|
|
|
} |
|
|
|
|
|
|
|
if (input.hashType !== hashType) throw new Error('Inconsistent hashType') |
|
|
|
|
|
|
|
// no? prepare
|
|
|
|
} else { |
|
|
|
// must be pay-to-scriptHash?
|
|
|
|
if (redeemScript) { |
|
|
|
// if we have a prevOutScript, enforce scriptHash equality to the redeemScript
|
|
|
|
if (input.prevOutScript) { |
|
|
|
if (input.prevOutType !== 'scripthash') throw new Error('PrevOutScript must be P2SH') |
|
|
|
|
|
|
|
var scriptHash = bscript.decompile(input.prevOutScript)[1] |
|
|
|
if (!bufferEquals(scriptHash, bcrypto.hash160(redeemScript))) throw new Error('RedeemScript does not match ' + scriptHash.toString('hex')) |
|
|
|
} |
|
|
|
|
|
|
|
var extracted = extractFromOutputScript(redeemScript, kpPubKey) |
|
|
|
if (!extracted) throw new Error('RedeemScript not supported "' + bscript.toASM(redeemScript) + '"') |
|
|
|
|
|
|
|
// if we don't have a prevOutScript, generate a P2SH script
|
|
|
|
if (!input.prevOutScript) { |
|
|
|
input.prevOutScript = bscript.scriptHashOutput(bcrypto.hash160(redeemScript)) |
|
|
|
input.prevOutType = 'scripthash' |
|
|
|
} |
|
|
|
|
|
|
|
input.pubKeys = extracted.pubKeys |
|
|
|
input.redeemScript = redeemScript |
|
|
|
input.redeemScriptType = extracted.scriptType |
|
|
|
input.signatures = extracted.pubKeys.map(function () { return undefined }) |
|
|
|
} else { |
|
|
|
// pay-to-scriptHash is not possible without a redeemScript
|
|
|
|
if (input.prevOutType === 'scripthash') throw new Error('PrevOutScript is P2SH, missing redeemScript') |
|
|
|
|
|
|
|
// if we don't have a scriptType, assume pubKeyHash otherwise
|
|
|
|
if (!input.scriptType) { |
|
|
|
input.prevOutScript = bscript.pubKeyHashOutput(bcrypto.hash160(kpPubKey)) |
|
|
|
input.prevOutType = 'pubkeyhash' |
|
|
|
|
|
|
|
input.pubKeys = [kpPubKey] |
|
|
|
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 |
|
|
|
prepareInput(input, kpPubKey, redeemScript, hashType) |
|
|
|
} |
|
|
|
|
|
|
|
// ready to sign?
|
|
|
|
var signatureScript = input.redeemScript || input.prevOutScript |
|
|
|
var signatureHash = this.tx.hashForSignature(index, signatureScript, hashType) |
|
|
|
// ready to sign
|
|
|
|
var hashScript = input.redeemScript || input.prevOutScript |
|
|
|
var signatureHash = this.tx.hashForSignature(vin, hashScript, hashType) |
|
|
|
|
|
|
|
// enforce in order signing of public keys
|
|
|
|
var valid = input.pubKeys.some(function (pubKey, i) { |
|
|
@ -491,11 +472,42 @@ TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hash |
|
|
|
if (input.signatures[i]) throw new Error('Signature already exists') |
|
|
|
|
|
|
|
input.signatures[i] = keyPair.sign(signatureHash) |
|
|
|
|
|
|
|
return true |
|
|
|
}) |
|
|
|
|
|
|
|
if (!valid) throw new Error('Key pair cannot sign for this input') |
|
|
|
} |
|
|
|
|
|
|
|
TransactionBuilder.prototype.__canModifyInputs = function () { |
|
|
|
return this.inputs.every(function (otherInput) { |
|
|
|
// no signature
|
|
|
|
if (otherInput.hashType === undefined) return true |
|
|
|
|
|
|
|
// if SIGHASH_ANYONECANPAY is set, signatures would not
|
|
|
|
// be invalidated by more inputs
|
|
|
|
return otherInput.hashType & Transaction.SIGHASH_ANYONECANPAY |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
TransactionBuilder.prototype.__canModifyOutputs = function () { |
|
|
|
var nInputs = this.tx.ins.length |
|
|
|
var nOutputs = this.tx.outs.length |
|
|
|
|
|
|
|
return this.inputs.every(function (input, i) { |
|
|
|
// any signatures?
|
|
|
|
if (input.hashType === undefined) return true |
|
|
|
|
|
|
|
var hashTypeMod = input.hashType & 0x1f |
|
|
|
if (hashTypeMod === Transaction.SIGHASH_NONE) return true |
|
|
|
if (hashTypeMod === Transaction.SIGHASH_SINGLE) { |
|
|
|
// if SIGHASH_SINGLE is set, and nInputs > nOutputs
|
|
|
|
// some signatures would be invalidated by the addition
|
|
|
|
// of more outputs
|
|
|
|
return nInputs <= nOutputs |
|
|
|
} |
|
|
|
|
|
|
|
return false |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
module.exports = TransactionBuilder |
|
|
|