bitcoinjs-lib/src/transaction_builder.js

778 lines
24 KiB
JavaScript
Raw Normal View History

2017-05-23 07:14:08 +02:00
var Buffer = require('safe-buffer').Buffer
var baddress = require('./address')
var bcrypto = require('./crypto')
var bscript = require('./script')
var btemplates = require('./templates')
var networks = require('./networks')
var ops = require('bitcoin-ops')
2015-11-26 02:40:06 +01:00
var typeforce = require('typeforce')
var types = require('./types')
var scriptTypes = btemplates.types
var SIGNABLE = [btemplates.types.P2PKH, btemplates.types.P2PK, btemplates.types.MULTISIG]
var P2SH = SIGNABLE.concat([btemplates.types.P2WPKH, btemplates.types.P2WSH])
2014-06-16 08:05:31 +02:00
2015-03-02 06:48:36 +01:00
var ECPair = require('./ecpair')
var ECSignature = require('./ecsignature')
var Transaction = require('./transaction')
2014-06-16 08:05:31 +02:00
function supportedType (type) {
return SIGNABLE.indexOf(type) !== -1
}
function supportedP2SHType (type) {
return P2SH.indexOf(type) !== -1
}
function extractChunks (type, chunks, script) {
2017-01-02 17:45:23 +01:00
var pubKeys = []
var signatures = []
switch (type) {
case scriptTypes.P2PKH:
// if (redeemScript) throw new Error('Nonstandard... P2SH(P2PKH)')
pubKeys = chunks.slice(1)
signatures = chunks.slice(0, 1)
break
case scriptTypes.P2PK:
pubKeys[0] = script ? btemplates.pubKey.output.decode(script) : undefined
2017-01-02 17:45:23 +01:00
signatures = chunks.slice(0, 1)
break
case scriptTypes.MULTISIG:
if (script) {
var multisig = btemplates.multisig.output.decode(script)
pubKeys = multisig.pubKeys
}
2017-01-02 17:45:23 +01:00
signatures = chunks.slice(1).map(function (chunk) {
return chunk.length === 0 ? undefined : chunk
2017-01-02 17:45:23 +01:00
})
break
}
2017-01-02 17:45:23 +01:00
return {
pubKeys: pubKeys,
signatures: signatures
}
}
function expandInput (scriptSig, witnessStack) {
if (scriptSig.length === 0 && witnessStack.length === 0) return {}
2017-01-02 17:45:23 +01:00
var prevOutScript
var prevOutType
var scriptType
var script
var redeemScript
2017-01-02 17:45:23 +01:00
var witnessScript
var witnessScriptType
var redeemScriptType
2017-01-02 17:45:23 +01:00
var witness = false
var p2wsh = false
var p2sh = false
var witnessProgram
var chunks
2017-01-02 17:45:23 +01:00
var scriptSigChunks = bscript.decompile(scriptSig)
var sigType = btemplates.classifyInput(scriptSigChunks, true)
if (sigType === scriptTypes.P2SH) {
p2sh = true
redeemScript = scriptSigChunks[scriptSigChunks.length - 1]
redeemScriptType = btemplates.classifyOutput(redeemScript)
prevOutScript = btemplates.scriptHash.output.encode(bcrypto.hash160(redeemScript))
prevOutType = scriptTypes.P2SH
script = redeemScript
}
var classifyWitness = btemplates.classifyWitness(witnessStack, true)
2017-01-02 17:45:23 +01:00
if (classifyWitness === scriptTypes.P2WSH) {
witnessScript = witnessStack[witnessStack.length - 1]
witnessScriptType = btemplates.classifyOutput(witnessScript)
2017-01-02 17:45:23 +01:00
p2wsh = true
2017-08-28 11:19:44 +02:00
witness = true
2017-01-02 17:45:23 +01:00
if (scriptSig.length === 0) {
prevOutScript = btemplates.witnessScriptHash.output.encode(bcrypto.sha256(witnessScript))
prevOutType = scriptTypes.P2WSH
if (redeemScript !== undefined) {
throw new Error('Redeem script given when unnecessary')
}
2017-01-02 17:45:23 +01:00
// bare witness
} else {
if (!redeemScript) {
throw new Error('No redeemScript provided for P2WSH, but scriptSig non-empty')
2017-01-02 17:45:23 +01:00
}
witnessProgram = btemplates.witnessScriptHash.output.encode(bcrypto.sha256(witnessScript))
2017-01-02 17:45:23 +01:00
if (!redeemScript.equals(witnessProgram)) {
throw new Error('Redeem script didn\'t match witnessScript')
}
}
if (!supportedType(btemplates.classifyOutput(witnessScript))) {
2017-01-02 17:45:23 +01:00
throw new Error('unsupported witness script')
}
script = witnessScript
scriptType = witnessScriptType
chunks = witnessStack.slice(0, -1)
2017-01-02 17:45:23 +01:00
} else if (classifyWitness === scriptTypes.P2WPKH) {
2017-08-28 11:19:44 +02:00
witness = true
var key = witnessStack[witnessStack.length - 1]
var keyHash = bcrypto.hash160(key)
2017-01-02 17:45:23 +01:00
if (scriptSig.length === 0) {
prevOutScript = btemplates.witnessPubKeyHash.output.encode(keyHash)
prevOutType = scriptTypes.P2WPKH
if (typeof redeemScript !== 'undefined') {
throw new Error('Redeem script given when unnecessary')
}
2017-01-02 17:45:23 +01:00
} else {
if (!redeemScript) {
throw new Error('No redeemScript provided for P2WPKH, but scriptSig wasn\'t empty')
2017-01-02 17:45:23 +01:00
}
witnessProgram = btemplates.witnessPubKeyHash.output.encode(keyHash)
2017-01-02 17:45:23 +01:00
if (!redeemScript.equals(witnessProgram)) {
throw new Error('Redeem script did not have the right witness program')
}
}
scriptType = scriptTypes.P2PKH
chunks = witnessStack
} else if (redeemScript) {
if (!supportedP2SHType(redeemScriptType)) {
throw new Error('Bad redeemscript!')
2017-01-02 17:45:23 +01:00
}
script = redeemScript
scriptType = redeemScriptType
chunks = scriptSigChunks.slice(0, -1)
} else {
prevOutType = scriptType = btemplates.classifyInput(scriptSig)
chunks = scriptSigChunks
2017-01-02 17:45:23 +01:00
}
var expanded = extractChunks(scriptType, chunks, script)
2017-01-02 17:45:23 +01:00
var result = {
pubKeys: expanded.pubKeys,
signatures: expanded.signatures,
prevOutScript: prevOutScript,
prevOutType: prevOutType,
signType: scriptType,
signScript: script,
witness: Boolean(witness)
2017-01-02 17:45:23 +01:00
}
if (p2sh) {
result.redeemScript = redeemScript
result.redeemScriptType = redeemScriptType
}
if (p2wsh) {
result.witnessScript = witnessScript
result.witnessScriptType = witnessScriptType
}
return result
}
// could be done in expandInput, but requires the original Transaction for hashForSignature
function fixMultisigOrder (input, transaction, vin) {
if (input.redeemScriptType !== scriptTypes.MULTISIG || !input.redeemScript) return
if (input.pubKeys.length === input.signatures.length) return
var unmatched = input.signatures.concat()
2017-01-06 03:44:00 +01:00
input.signatures = input.pubKeys.map(function (pubKey) {
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
// TODO: avoid O(n) hashForSignature
var parsed = ECSignature.parseScriptSignature(signature)
var hash = transaction.hashForSignature(vin, input.redeemScript, parsed.hashType)
// skip if signature does not match pubKey
if (!keyPair.verify(hash, parsed.signature)) return false
// remove matched signature from unmatched
unmatched[i] = undefined
match = signature
return true
})
return match
})
}
function expandOutput (script, scriptType, ourPubKey) {
typeforce(types.Buffer, script)
var scriptChunks = bscript.decompile(script)
if (!scriptType) {
scriptType = btemplates.classifyOutput(script)
}
var pubKeys = []
switch (scriptType) {
// does our hash160(pubKey) match the output scripts?
case scriptTypes.P2PKH:
if (!ourPubKey) break
var pkh1 = scriptChunks[2]
var pkh2 = bcrypto.hash160(ourPubKey)
if (pkh1.equals(pkh2)) pubKeys = [ourPubKey]
2016-09-27 13:08:48 +02:00
break
2016-12-14 05:41:24 +01:00
// does our hash160(pubKey) match the output scripts?
case scriptTypes.P2WPKH:
if (!ourPubKey) break
var wpkh1 = scriptChunks[1]
var wpkh2 = bcrypto.hash160(ourPubKey)
if (wpkh1.equals(wpkh2)) pubKeys = [ourPubKey]
break
case scriptTypes.P2PK:
pubKeys = scriptChunks.slice(0, 1)
2016-09-27 13:08:48 +02:00
break
case scriptTypes.MULTISIG:
pubKeys = scriptChunks.slice(1, -2)
2016-09-27 13:08:48 +02:00
break
default: return { scriptType: scriptType }
2016-09-27 13:08:48 +02:00
}
return {
pubKeys: pubKeys,
scriptType: scriptType,
signatures: pubKeys.map(function () { return undefined })
}
}
2017-10-19 01:52:21 +02:00
function checkP2SHInput (input, redeemScriptHash) {
if (input.prevOutType) {
if (input.prevOutType !== scriptTypes.P2SH) throw new Error('PrevOutScript must be P2SH')
var prevOutScriptScriptHash = bscript.decompile(input.prevOutScript)[1]
if (!prevOutScriptScriptHash.equals(redeemScriptHash)) throw new Error('Inconsistent hash160(RedeemScript)')
}
}
function checkP2WSHInput (input, witnessScriptHash) {
if (input.prevOutType) {
if (input.prevOutType !== scriptTypes.P2WSH) throw new Error('PrevOutScript must be P2WSH')
var scriptHash = bscript.decompile(input.prevOutScript)[1]
2017-01-02 17:45:23 +01:00
if (!scriptHash.equals(witnessScriptHash)) throw new Error('Inconsistent sha25(WitnessScript)')
}
}
2016-12-29 18:23:15 +01:00
function prepareInput (input, kpPubKey, redeemScript, witnessValue, witnessScript) {
var expanded
var prevOutType
var prevOutScript
var p2sh = false
var p2shType
var redeemScriptHash
var witness = false
2016-12-29 18:23:15 +01:00
var p2wsh = false
var witnessType
var witnessScriptHash
var signType
var signScript
if (redeemScript && witnessScript) {
redeemScriptHash = bcrypto.hash160(redeemScript)
2016-12-29 18:23:15 +01:00
witnessScriptHash = bcrypto.sha256(witnessScript)
2017-10-19 01:52:21 +02:00
checkP2SHInput(input, redeemScriptHash)
2016-12-29 18:23:15 +01:00
if (!redeemScript.equals(btemplates.witnessScriptHash.output.encode(witnessScriptHash))) throw new Error('Witness script inconsistent with redeem script')
expanded = expandOutput(witnessScript, undefined, kpPubKey)
if (!expanded.pubKeys) throw new Error('WitnessScript not supported "' + bscript.toASM(redeemScript) + '"')
prevOutType = btemplates.types.P2SH
prevOutScript = btemplates.scriptHash.output.encode(redeemScriptHash)
2016-12-29 18:23:15 +01:00
p2sh = witness = p2wsh = true
p2shType = btemplates.types.P2WSH
signType = witnessType = expanded.scriptType
signScript = witnessScript
} else if (redeemScript) {
redeemScriptHash = bcrypto.hash160(redeemScript)
2017-10-19 01:52:21 +02:00
checkP2SHInput(input, redeemScriptHash)
expanded = expandOutput(redeemScript, undefined, kpPubKey)
if (!expanded.pubKeys) throw new Error('RedeemScript not supported "' + bscript.toASM(redeemScript) + '"')
prevOutType = btemplates.types.P2SH
prevOutScript = btemplates.scriptHash.output.encode(redeemScriptHash)
p2sh = true
signType = p2shType = expanded.scriptType
signScript = redeemScript
witness = signType === btemplates.types.P2WPKH
} else if (witnessScript) {
2017-01-02 17:45:23 +01:00
witnessScriptHash = bcrypto.sha256(witnessScript)
checkP2WSHInput(input, witnessScriptHash)
expanded = expandOutput(witnessScript, undefined, kpPubKey)
if (!expanded.pubKeys) throw new Error('WitnessScript not supported "' + bscript.toASM(redeemScript) + '"')
prevOutType = btemplates.types.P2WSH
prevOutScript = btemplates.witnessScriptHash.output.encode(witnessScriptHash)
2016-12-29 18:23:15 +01:00
witness = p2wsh = true
signType = witnessType = expanded.scriptType
signScript = witnessScript
} else if (input.prevOutType) {
2016-12-14 05:41:24 +01:00
// embedded scripts are not possible without a redeemScript
if (input.prevOutType === scriptTypes.P2SH ||
input.prevOutType === scriptTypes.P2WSH) {
2016-12-14 05:41:24 +01:00
throw new Error('PrevOutScript is ' + input.prevOutType + ', requires redeemScript')
}
prevOutType = input.prevOutType
prevOutScript = input.prevOutScript
expanded = expandOutput(input.prevOutScript, input.prevOutType, kpPubKey)
if (!expanded.pubKeys) return
witness = (input.prevOutType === scriptTypes.P2WPKH)
signType = prevOutType
signScript = prevOutScript
} else {
prevOutScript = btemplates.pubKeyHash.output.encode(bcrypto.hash160(kpPubKey))
expanded = expandOutput(prevOutScript, scriptTypes.P2PKH, kpPubKey)
prevOutType = scriptTypes.P2PKH
witness = false
signType = prevOutType
signScript = prevOutScript
}
if (signType === scriptTypes.P2WPKH) {
signScript = btemplates.pubKeyHash.output.encode(btemplates.witnessPubKeyHash.output.decode(signScript))
}
if (p2sh) {
input.redeemScript = redeemScript
input.redeemScriptType = p2shType
}
2016-12-29 18:23:15 +01:00
if (p2wsh) {
input.witnessScript = witnessScript
input.witnessScriptType = witnessType
}
input.pubKeys = expanded.pubKeys
input.signatures = expanded.signatures
input.signScript = signScript
input.signType = signType
input.prevOutScript = prevOutScript
input.prevOutType = prevOutType
input.witness = witness
}
2016-12-22 13:18:45 +01:00
function buildStack (type, signatures, pubKeys, allowIncomplete) {
if (type === scriptTypes.P2PKH) {
if (signatures.length === 1 && Buffer.isBuffer(signatures[0]) && pubKeys.length === 1) return btemplates.pubKeyHash.input.encodeStack(signatures[0], pubKeys[0])
2016-12-22 13:18:45 +01:00
} else if (type === scriptTypes.P2PK) {
if (signatures.length === 1 && Buffer.isBuffer(signatures[0])) return btemplates.pubKey.input.encodeStack(signatures[0])
} else if (type === scriptTypes.MULTISIG) {
if (signatures.length > 0) {
signatures = signatures.map(function (signature) {
return signature || ops.OP_0
})
if (!allowIncomplete) {
// remove blank signatures
signatures = signatures.filter(function (x) { return x !== ops.OP_0 })
}
return btemplates.multisig.input.encodeStack(signatures)
2016-12-22 13:18:45 +01:00
}
} else {
throw new Error('Not yet supported')
}
if (!allowIncomplete) throw new Error('Not enough signatures provided')
return []
2016-12-22 13:18:45 +01:00
}
2016-12-22 13:18:45 +01:00
function buildInput (input, allowIncomplete) {
var scriptType = input.prevOutType
var sig = []
var witness = []
if (supportedType(scriptType)) {
sig = buildStack(scriptType, input.signatures, input.pubKeys, allowIncomplete)
2016-12-14 05:41:24 +01:00
}
2016-12-22 13:18:45 +01:00
var p2sh = false
if (scriptType === btemplates.types.P2SH) {
2016-12-22 13:18:45 +01:00
// We can remove this error later when we have a guarantee prepareInput
// rejects unsignable scripts - it MUST be signable at this point.
if (!allowIncomplete && !supportedP2SHType(input.redeemScriptType)) {
2016-12-22 13:18:45 +01:00
throw new Error('Impossible to sign this type')
}
if (supportedType(input.redeemScriptType)) {
2016-12-22 13:18:45 +01:00
sig = buildStack(input.redeemScriptType, input.signatures, input.pubKeys, allowIncomplete)
}
2016-12-22 13:18:45 +01:00
// If it wasn't SIGNABLE, it's witness, defer to that
if (input.redeemScriptType) {
p2sh = true
scriptType = input.redeemScriptType
}
2016-12-14 05:41:24 +01:00
}
switch (scriptType) {
2016-12-22 13:18:45 +01:00
// P2WPKH is a special case of P2PKH
case btemplates.types.P2WPKH:
witness = buildStack(btemplates.types.P2PKH, input.signatures, input.pubKeys, allowIncomplete)
break
case btemplates.types.P2WSH:
// We can remove this check later
if (!allowIncomplete && !supportedType(input.witnessScriptType)) {
throw new Error('Impossible to sign this type')
}
if (supportedType(input.witnessScriptType)) {
witness = buildStack(input.witnessScriptType, input.signatures, input.pubKeys, allowIncomplete)
witness.push(input.witnessScript)
scriptType = input.witnessScriptType
}
break
2016-12-14 05:41:24 +01:00
}
2016-12-22 13:18:45 +01:00
// append redeemScript if necessary
if (p2sh) {
sig.push(input.redeemScript)
}
2016-12-14 05:41:24 +01:00
return {
2016-12-22 13:18:45 +01:00
type: scriptType,
script: bscript.compile(sig),
2017-09-06 14:14:34 +02:00
witness: witness
2016-12-14 05:41:24 +01:00
}
}
function TransactionBuilder (network, maximumFeeRate) {
this.prevTxMap = {}
this.network = network || networks.bitcoin
// WARNING: This is __NOT__ to be relied on, its just another potential safety mechanism (safety in-depth)
this.maximumFeeRate = maximumFeeRate || 1000
this.inputs = []
this.tx = new Transaction()
}
2015-11-26 02:40:06 +01:00
TransactionBuilder.prototype.setLockTime = function (locktime) {
typeforce(types.UInt32, locktime)
// if any signatures exist, throw
if (this.inputs.some(function (input) {
if (!input.signatures) return false
return input.signatures.some(function (s) { return s })
})) {
throw new Error('No, this would invalidate signatures')
}
this.tx.locktime = locktime
}
2016-06-22 06:57:11 +02:00
TransactionBuilder.prototype.setVersion = function (version) {
typeforce(types.UInt32, version)
// XXX: this might eventually become more complex depending on what the versions represent
this.tx.version = version
}
TransactionBuilder.fromTransaction = function (transaction, network) {
var txb = new TransactionBuilder(network)
// Copy transaction fields
txb.setVersion(transaction.version)
txb.setLockTime(transaction.locktime)
// Copy outputs (done first to avoid signature invalidation)
2015-02-23 00:36:57 +01:00
transaction.outs.forEach(function (txOut) {
2014-12-02 04:20:04 +01:00
txb.addOutput(txOut.script, txOut.value)
})
// Copy inputs
transaction.ins.forEach(function (txIn) {
2016-11-09 03:01:29 +01:00
txb.__addInputUnsafe(txIn.hash, txIn.index, {
sequence: txIn.sequence,
2016-12-14 05:41:24 +01:00
script: txIn.script,
witness: txIn.witness
2016-11-09 03:01:29 +01:00
})
})
// fix some things not possible through the public API
txb.inputs.forEach(function (input, i) {
fixMultisigOrder(input, transaction, i)
})
return txb
}
2015-03-02 08:06:49 +01:00
TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOutScript) {
if (!this.__canModifyInputs()) {
throw new Error('No, this would invalidate signatures')
}
2016-11-09 03:01:29 +01:00
var value
// is it a hex string?
2015-03-02 08:06:49 +01:00
if (typeof txHash === 'string') {
// transaction hashs's are displayed in reverse order, un-reverse it
2017-04-19 09:24:58 +02:00
txHash = Buffer.from(txHash, 'hex').reverse()
2015-03-02 08:06:49 +01:00
// is it a Transaction object?
2015-03-02 08:06:49 +01:00
} else if (txHash instanceof Transaction) {
2016-11-09 03:01:29 +01:00
var txOut = txHash.outs[vout]
prevOutScript = txOut.script
value = txOut.value
2015-03-02 08:06:49 +01:00
txHash = txHash.getHash()
2014-06-16 08:05:31 +02:00
}
2016-11-09 03:01:29 +01:00
return this.__addInputUnsafe(txHash, vout, {
sequence: sequence,
prevOutScript: prevOutScript,
value: value
})
}
2016-11-09 03:01:29 +01:00
TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, options) {
if (Transaction.isCoinbaseHash(txHash)) {
throw new Error('coinbase inputs not supported')
}
var prevTxOut = txHash.toString('hex') + ':' + vout
if (this.prevTxMap[prevTxOut] !== undefined) throw new Error('Duplicate TxOut: ' + prevTxOut)
var input = {}
// derive what we can from the scriptSig
2016-11-09 03:01:29 +01:00
if (options.script !== undefined) {
input = expandInput(options.script, options.witness || [])
2016-11-09 03:01:29 +01:00
}
// if an input value was given, retain it
if (options.value !== undefined) {
input.value = options.value
}
// derive what we can from the previous transactions output script
2016-11-09 03:01:29 +01:00
if (!input.prevOutScript && options.prevOutScript) {
var prevOutType
if (!input.pubKeys && !input.signatures) {
2016-11-09 03:01:29 +01:00
var expanded = expandOutput(options.prevOutScript)
if (expanded.pubKeys) {
input.pubKeys = expanded.pubKeys
input.signatures = expanded.signatures
}
prevOutType = expanded.scriptType
}
2014-06-16 08:05:31 +02:00
2016-11-09 03:01:29 +01:00
input.prevOutScript = options.prevOutScript
input.prevOutType = prevOutType || btemplates.classifyOutput(options.prevOutScript)
2014-06-16 08:05:31 +02:00
}
2016-11-09 03:01:29 +01:00
var vin = this.tx.addInput(txHash, vout, options.sequence, options.scriptSig)
this.inputs[vin] = input
this.prevTxMap[prevTxOut] = vin
return vin
2014-06-16 08:05:31 +02:00
}
2015-02-23 00:36:57 +01:00
TransactionBuilder.prototype.addOutput = function (scriptPubKey, value) {
if (!this.__canModifyOutputs()) {
throw new Error('No, this would invalidate signatures')
}
2014-06-16 08:05:31 +02:00
// Attempt to get a script if it's a base58 address string
if (typeof scriptPubKey === 'string') {
scriptPubKey = baddress.toOutputScript(scriptPubKey, this.network)
}
return this.tx.addOutput(scriptPubKey, value)
2014-06-16 08:05:31 +02:00
}
2015-02-23 00:36:57 +01:00
TransactionBuilder.prototype.build = function () {
return this.__build(false)
}
TransactionBuilder.prototype.buildIncomplete = function () {
return this.__build(true)
}
2015-02-23 00:36:57 +01:00
TransactionBuilder.prototype.__build = function (allowIncomplete) {
2014-06-16 08:05:31 +02:00
if (!allowIncomplete) {
if (!this.tx.ins.length) throw new Error('Transaction has no inputs')
if (!this.tx.outs.length) throw new Error('Transaction has no outputs')
2014-06-16 08:05:31 +02:00
}
var tx = this.tx.clone()
// Create script signatures from inputs
this.inputs.forEach(function (input, i) {
2017-01-02 17:45:23 +01:00
var scriptType = input.witnessScriptType || input.redeemScriptType || input.prevOutType
if (!scriptType && !allowIncomplete) throw new Error('Transaction is not complete')
2016-12-14 05:41:24 +01:00
var result = buildInput(input, allowIncomplete)
2014-06-16 08:05:31 +02:00
2016-12-14 05:41:24 +01:00
// skip if no result
2016-12-22 13:18:45 +01:00
if (!allowIncomplete) {
if (!supportedType(result.type) && result.type !== btemplates.types.P2WPKH) {
2016-12-22 13:18:45 +01:00
throw new Error(result.type + ' not supported')
}
}
2014-06-16 08:05:31 +02:00
2016-12-22 13:18:45 +01:00
tx.setInputScript(i, result.script)
tx.setWitness(i, result.witness)
2014-06-16 08:05:31 +02:00
})
if (!allowIncomplete) {
// do not rely on this, its merely a last resort
2017-08-31 06:00:22 +02:00
if (this.__overMaximumFees(tx.virtualSize())) {
throw new Error('Transaction has absurd fees')
}
}
2014-06-16 08:05:31 +02:00
return tx
}
function canSign (input) {
return input.prevOutScript !== undefined &&
input.signScript !== undefined &&
input.pubKeys !== undefined &&
input.signatures !== undefined &&
input.signatures.length === input.pubKeys.length &&
2016-12-14 05:41:24 +01:00
input.pubKeys.length > 0 &&
(
input.witness === false ||
(input.witness === true && input.value !== undefined)
)
}
2016-12-29 18:23:15 +01:00
TransactionBuilder.prototype.sign = function (vin, keyPair, redeemScript, hashType, witnessValue, witnessScript) {
// TODO: remove keyPair.network matching in 4.0.0
if (keyPair.network && keyPair.network !== this.network) throw new TypeError('Inconsistent network')
if (!this.inputs[vin]) throw new Error('No input at index: ' + vin)
hashType = hashType || Transaction.SIGHASH_ALL
var input = this.inputs[vin]
2015-03-02 06:48:36 +01:00
// if redeemScript was previously provided, enforce consistency
if (input.redeemScript !== undefined &&
redeemScript &&
!input.redeemScript.equals(redeemScript)) {
throw new Error('Inconsistent redeemScript')
}
2014-07-28 07:40:07 +02:00
var kpPubKey = keyPair.publicKey || keyPair.getPublicKeyBuffer()
if (!canSign(input)) {
if (witnessValue !== undefined) {
if (input.value !== undefined && input.value !== witnessValue) throw new Error('Input didn\'t match witnessValue')
typeforce(types.Satoshi, witnessValue)
input.value = witnessValue
}
if (!canSign(input)) prepareInput(input, kpPubKey, redeemScript, witnessValue, witnessScript)
if (!canSign(input)) throw Error(input.prevOutType + ' not supported')
}
// ready to sign
2016-12-14 05:41:24 +01:00
var signatureHash
if (input.witness) {
signatureHash = this.tx.hashForWitnessV0(vin, input.signScript, input.value, hashType)
2016-12-14 05:41:24 +01:00
} else {
signatureHash = this.tx.hashForSignature(vin, input.signScript, hashType)
2016-12-14 05:41:24 +01:00
}
// enforce in order signing of public keys
var signed = input.pubKeys.some(function (pubKey, i) {
if (!kpPubKey.equals(pubKey)) return false
if (input.signatures[i]) throw new Error('Signature already exists')
if (kpPubKey.length !== 33 &&
input.signType === scriptTypes.P2WPKH) throw new Error('BIP143 rejects uncompressed public keys in P2WPKH or P2WSH')
2015-03-02 06:48:36 +01:00
var signature = keyPair.sign(signatureHash)
if (Buffer.isBuffer(signature)) signature = ECSignature.fromRSBuffer(signature)
input.signatures[i] = signature.toScriptSignature(hashType)
return true
})
if (!signed) throw new Error('Key pair cannot sign for this input')
}
function signatureHashType (buffer) {
return buffer.readUInt8(buffer.length - 1)
}
TransactionBuilder.prototype.__canModifyInputs = function () {
return this.inputs.every(function (input) {
// any signatures?
if (input.signatures === undefined) return true
return input.signatures.every(function (signature) {
if (!signature) return true
var hashType = signatureHashType(signature)
// if SIGHASH_ANYONECANPAY is set, signatures would not
// be invalidated by more inputs
return 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) {
if (input.signatures === undefined) return true
return input.signatures.every(function (signature) {
if (!signature) return true
var hashType = signatureHashType(signature)
var hashTypeMod = 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
}
})
})
}
TransactionBuilder.prototype.__overMaximumFees = function (bytes) {
2016-11-09 03:01:29 +01:00
// not all inputs will have .value defined
var incoming = this.inputs.reduce(function (a, x) { return a + (x.value >>> 0) }, 0)
// but all outputs do, and if we have any input value
// we can immediately determine if the outputs are too small
var outgoing = this.tx.outs.reduce(function (a, x) { return a + x.value }, 0)
var fee = incoming - outgoing
var feeRate = fee / bytes
2016-11-09 03:01:29 +01:00
return feeRate > this.maximumFeeRate
2016-11-09 03:01:29 +01:00
}
2014-06-16 08:05:31 +02:00
module.exports = TransactionBuilder