bitcoinjs-lib/src/transaction_builder.ts

786 lines
24 KiB
TypeScript
Raw Normal View History

2018-12-27 10:26:08 +01:00
import { Transaction, Output } from './transaction'
2018-06-25 08:25:12 +02:00
const Buffer = require('safe-buffer').Buffer
const baddress = require('./address')
const bcrypto = require('./crypto')
const bscript = require('./script')
const networks = require('./networks')
const ops = require('bitcoin-ops')
2018-06-27 09:29:18 +02:00
const payments = require('./payments')
2018-06-25 08:25:12 +02:00
const typeforce = require('typeforce')
const types = require('./types')
const classify = require('./classify')
const SCRIPT_TYPES = classify.types
2014-06-16 08:05:31 +02:00
2018-06-25 08:25:12 +02:00
const ECPair = require('./ecpair')
2014-06-16 08:05:31 +02:00
2018-06-27 09:29:18 +02:00
function expandInput (scriptSig, witnessStack, type, scriptPubKey) {
if (scriptSig.length === 0 && witnessStack.length === 0) return {}
if (!type) {
let ssType = classify.input(scriptSig, true)
let wsType = classify.witness(witnessStack, true)
2018-06-27 09:29:18 +02:00
if (ssType === SCRIPT_TYPES.NONSTANDARD) ssType = undefined
if (wsType === SCRIPT_TYPES.NONSTANDARD) wsType = undefined
type = ssType || wsType
}
2017-01-02 17:45:23 +01:00
switch (type) {
2018-06-27 09:29:18 +02:00
case SCRIPT_TYPES.P2WPKH: {
const { output, pubkey, signature } = payments.p2wpkh({ witness: witnessStack })
return {
prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2WPKH,
pubkeys: [pubkey],
signatures: [signature]
}
2018-06-27 09:29:18 +02:00
}
2018-06-27 09:29:18 +02:00
case SCRIPT_TYPES.P2PKH: {
const { output, pubkey, signature } = payments.p2pkh({ input: scriptSig })
2018-06-27 09:29:18 +02:00
return {
prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2PKH,
pubkeys: [pubkey],
signatures: [signature]
}
}
2018-06-27 09:29:18 +02:00
case SCRIPT_TYPES.P2PK: {
const { signature } = payments.p2pk({ input: scriptSig })
2018-06-27 09:29:18 +02:00
return {
prevOutType: SCRIPT_TYPES.P2PK,
pubkeys: [undefined],
signatures: [signature]
2017-01-02 17:45:23 +01:00
}
}
2018-09-03 06:06:30 +02:00
case SCRIPT_TYPES.P2MS: {
const { m, pubkeys, signatures } = payments.p2ms({
2018-06-27 09:29:18 +02:00
input: scriptSig,
output: scriptPubKey
}, { allowIncomplete: true })
2018-06-27 09:29:18 +02:00
return {
2018-09-03 06:06:30 +02:00
prevOutType: SCRIPT_TYPES.P2MS,
2018-06-27 09:29:18 +02:00
pubkeys: pubkeys,
signatures: signatures,
maxSignatures: m
2017-01-02 17:45:23 +01:00
}
}
2018-06-27 09:29:18 +02:00
}
2017-01-02 17:45:23 +01:00
2018-06-27 09:29:18 +02:00
if (type === SCRIPT_TYPES.P2SH) {
const { output, redeem } = payments.p2sh({
input: scriptSig,
witness: witnessStack
})
2017-01-02 17:45:23 +01:00
const outputType = classify.output(redeem.output)
2018-06-27 09:29:18 +02:00
const expanded = expandInput(redeem.input, redeem.witness, outputType, redeem.output)
if (!expanded.prevOutType) return {}
2017-01-02 17:45:23 +01:00
2018-06-27 09:29:18 +02:00
return {
prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2SH,
redeemScript: redeem.output,
redeemScriptType: expanded.prevOutType,
witnessScript: expanded.witnessScript,
witnessScriptType: expanded.witnessScriptType,
2017-01-02 17:45:23 +01:00
2018-06-27 09:29:18 +02:00
pubkeys: expanded.pubkeys,
signatures: expanded.signatures
}
2017-01-02 17:45:23 +01:00
}
2018-06-27 09:29:18 +02:00
if (type === SCRIPT_TYPES.P2WSH) {
const { output, redeem } = payments.p2wsh({
input: scriptSig,
witness: witnessStack
})
const outputType = classify.output(redeem.output)
2018-06-27 09:29:18 +02:00
let expanded
if (outputType === SCRIPT_TYPES.P2WPKH) {
expanded = expandInput(redeem.input, redeem.witness, outputType, undefined)
2018-06-27 09:29:18 +02:00
} else {
expanded = expandInput(bscript.compile(redeem.witness), [], outputType, redeem.output)
}
if (!expanded.prevOutType) return {}
return {
prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2WSH,
witnessScript: redeem.output,
witnessScriptType: expanded.prevOutType,
2017-01-02 17:45:23 +01:00
2018-06-27 09:29:18 +02:00
pubkeys: expanded.pubkeys,
signatures: expanded.signatures
}
2017-01-02 17:45:23 +01:00
}
2018-06-27 09:29:18 +02:00
return {
prevOutType: SCRIPT_TYPES.NONSTANDARD,
prevOutScript: scriptSig
}
2017-01-02 17:45:23 +01:00
}
// could be done in expandInput, but requires the original Transaction for hashForSignature
function fixMultisigOrder (input, transaction, vin) {
2018-09-03 06:06:30 +02:00
if (input.redeemScriptType !== SCRIPT_TYPES.P2MS || !input.redeemScript) return
2018-06-27 09:29:18 +02:00
if (input.pubkeys.length === input.signatures.length) return
const unmatched = input.signatures.concat()
2018-06-27 09:29:18 +02:00
input.signatures = input.pubkeys.map(function (pubKey) {
const keyPair = ECPair.fromPublicKey(pubKey)
let match
// check for a signature
unmatched.some(function (signature, i) {
// skip if undefined || OP_0
if (!signature) return false
// TODO: avoid O(n) hashForSignature
const parsed = bscript.signature.decode(signature)
const 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
})
}
2018-06-27 09:29:18 +02:00
function expandOutput (script, ourPubKey) {
typeforce(types.Buffer, script)
const type = classify.output(script)
2018-06-27 09:29:18 +02:00
switch (type) {
case SCRIPT_TYPES.P2PKH: {
if (!ourPubKey) return { type }
2018-06-27 09:29:18 +02:00
// does our hash160(pubKey) match the output scripts?
const pkh1 = payments.p2pkh({ output: script }).hash
const pkh2 = bcrypto.hash160(ourPubKey)
2018-06-27 09:29:18 +02:00
if (!pkh1.equals(pkh2)) return { type }
return {
type,
pubkeys: [ourPubKey],
signatures: [undefined]
}
}
2018-06-27 09:29:18 +02:00
case SCRIPT_TYPES.P2WPKH: {
if (!ourPubKey) return { type }
2016-12-14 05:41:24 +01:00
2018-06-27 09:29:18 +02:00
// does our hash160(pubKey) match the output scripts?
const wpkh1 = payments.p2wpkh({ output: script }).hash
const wpkh2 = bcrypto.hash160(ourPubKey)
2018-06-27 09:29:18 +02:00
if (!wpkh1.equals(wpkh2)) return { type }
2016-12-14 05:41:24 +01:00
2018-06-27 09:29:18 +02:00
return {
type,
pubkeys: [ourPubKey],
signatures: [undefined]
}
}
2018-06-27 09:29:18 +02:00
case SCRIPT_TYPES.P2PK: {
const p2pk = payments.p2pk({ output: script })
return {
type,
pubkeys: [p2pk.pubkey],
signatures: [undefined]
}
}
2016-09-27 13:08:48 +02:00
2018-09-03 06:06:30 +02:00
case SCRIPT_TYPES.P2MS: {
2018-06-27 09:29:18 +02:00
const p2ms = payments.p2ms({ output: script })
return {
type,
pubkeys: p2ms.pubkeys,
signatures: p2ms.pubkeys.map(() => undefined),
maxSignatures: p2ms.m
2018-06-27 09:29:18 +02:00
}
}
2016-09-27 13:08:48 +02:00
}
2018-06-27 09:29:18 +02:00
return { type }
}
2018-06-27 09:29:18 +02:00
function prepareInput (input, ourPubKey, redeemScript, witnessValue, witnessScript) {
if (redeemScript && witnessScript) {
const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } })
const p2wshAlt = payments.p2wsh({ output: redeemScript })
const p2sh = payments.p2sh({ redeem: { output: redeemScript } })
const p2shAlt = payments.p2sh({ redeem: p2wsh })
// enforces P2SH(P2WSH(...))
if (!p2wsh.hash.equals(p2wshAlt.hash)) throw new Error('Witness script inconsistent with prevOutScript')
if (!p2sh.hash.equals(p2shAlt.hash)) throw new Error('Redeem script inconsistent with prevOutScript')
const expanded = expandOutput(p2wsh.redeem.output, ourPubKey)
if (!expanded.pubkeys) throw new Error(expanded.type + ' not supported as witnessScript (' + bscript.toASM(witnessScript) + ')')
if (input.signatures && input.signatures.some(x => x)) {
expanded.signatures = input.signatures
}
let signScript = witnessScript
2018-07-23 02:41:01 +02:00
if (expanded.type === SCRIPT_TYPES.P2WPKH) throw new Error('P2SH(P2WSH(P2WPKH)) is a consensus failure')
2018-06-27 09:29:18 +02:00
return {
redeemScript,
2018-06-27 09:29:18 +02:00
redeemScriptType: SCRIPT_TYPES.P2WSH,
witnessScript,
2018-06-27 09:29:18 +02:00
witnessScriptType: expanded.type,
2018-06-27 09:29:18 +02:00
prevOutType: SCRIPT_TYPES.P2SH,
prevOutScript: p2sh.output,
2018-06-27 09:29:18 +02:00
hasWitness: true,
signScript,
2018-06-27 09:29:18 +02:00
signType: expanded.type,
2018-06-27 09:29:18 +02:00
pubkeys: expanded.pubkeys,
signatures: expanded.signatures,
maxSignatures: expanded.maxSignatures
2018-06-27 09:29:18 +02:00
}
}
2018-06-27 09:29:18 +02:00
if (redeemScript) {
const p2sh = payments.p2sh({ redeem: { output: redeemScript } })
2018-06-27 09:29:18 +02:00
if (input.prevOutScript) {
let p2shAlt
try {
p2shAlt = payments.p2sh({ output: input.prevOutScript })
} catch (e) { throw new Error('PrevOutScript must be P2SH') }
if (!p2sh.hash.equals(p2shAlt.hash)) throw new Error('Redeem script inconsistent with prevOutScript')
}
2018-06-27 09:29:18 +02:00
const expanded = expandOutput(p2sh.redeem.output, ourPubKey)
if (!expanded.pubkeys) throw new Error(expanded.type + ' not supported as redeemScript (' + bscript.toASM(redeemScript) + ')')
if (input.signatures && input.signatures.some(x => x)) {
expanded.signatures = input.signatures
}
2018-06-27 09:29:18 +02:00
let signScript = redeemScript
if (expanded.type === SCRIPT_TYPES.P2WPKH) {
signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output
}
2018-06-27 09:29:18 +02:00
return {
redeemScript,
2018-06-27 09:29:18 +02:00
redeemScriptType: expanded.type,
2018-06-27 09:29:18 +02:00
prevOutType: SCRIPT_TYPES.P2SH,
prevOutScript: p2sh.output,
hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH,
signScript,
2018-06-27 09:29:18 +02:00
signType: expanded.type,
2018-06-27 09:29:18 +02:00
pubkeys: expanded.pubkeys,
signatures: expanded.signatures,
maxSignatures: expanded.maxSignatures
2018-06-27 09:29:18 +02:00
}
}
2018-06-27 09:29:18 +02:00
if (witnessScript) {
const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } })
2018-06-27 09:29:18 +02:00
if (input.prevOutScript) {
const p2wshAlt = payments.p2wsh({ output: input.prevOutScript })
if (!p2wsh.hash.equals(p2wshAlt.hash)) throw new Error('Witness script inconsistent with prevOutScript')
}
2018-06-27 09:29:18 +02:00
const expanded = expandOutput(p2wsh.redeem.output, ourPubKey)
if (!expanded.pubkeys) throw new Error(expanded.type + ' not supported as witnessScript (' + bscript.toASM(witnessScript) + ')')
if (input.signatures && input.signatures.some(x => x)) {
expanded.signatures = input.signatures
2016-12-22 13:18:45 +01:00
}
let signScript = witnessScript
2018-07-23 02:41:01 +02:00
if (expanded.type === SCRIPT_TYPES.P2WPKH) throw new Error('P2WSH(P2WPKH) is a consensus failure')
2018-06-27 09:29:18 +02:00
return {
witnessScript,
2018-06-27 09:29:18 +02:00
witnessScriptType: expanded.type,
2018-06-27 09:29:18 +02:00
prevOutType: SCRIPT_TYPES.P2WSH,
prevOutScript: p2wsh.output,
2018-06-27 09:29:18 +02:00
hasWitness: true,
signScript,
2018-06-27 09:29:18 +02:00
signType: expanded.type,
2016-12-14 05:41:24 +01:00
2018-06-27 09:29:18 +02:00
pubkeys: expanded.pubkeys,
signatures: expanded.signatures,
maxSignatures: expanded.maxSignatures
2016-12-22 13:18:45 +01:00
}
2018-06-27 09:29:18 +02:00
}
2018-06-27 09:29:18 +02:00
if (input.prevOutType && input.prevOutScript) {
// embedded scripts are not possible without extra information
if (input.prevOutType === SCRIPT_TYPES.P2SH) throw new Error('PrevOutScript is ' + input.prevOutType + ', requires redeemScript')
if (input.prevOutType === SCRIPT_TYPES.P2WSH) throw new Error('PrevOutScript is ' + input.prevOutType + ', requires witnessScript')
if (!input.prevOutScript) throw new Error('PrevOutScript is missing')
const expanded = expandOutput(input.prevOutScript, ourPubKey)
if (!expanded.pubkeys) throw new Error(expanded.type + ' not supported (' + bscript.toASM(input.prevOutScript) + ')')
if (input.signatures && input.signatures.some(x => x)) {
expanded.signatures = input.signatures
2016-12-22 13:18:45 +01:00
}
let signScript = input.prevOutScript
if (expanded.type === SCRIPT_TYPES.P2WPKH) {
signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output
}
2018-06-27 09:29:18 +02:00
return {
prevOutType: expanded.type,
prevOutScript: input.prevOutScript,
hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH,
signScript,
2018-06-27 09:29:18 +02:00
signType: expanded.type,
pubkeys: expanded.pubkeys,
signatures: expanded.signatures,
maxSignatures: expanded.maxSignatures
}
2016-12-14 05:41:24 +01:00
}
2018-06-27 09:29:18 +02:00
const prevOutScript = payments.p2pkh({ pubkey: ourPubKey }).output
return {
prevOutType: SCRIPT_TYPES.P2PKH,
prevOutScript: prevOutScript,
2018-06-27 09:29:18 +02:00
hasWitness: false,
signScript: prevOutScript,
signType: SCRIPT_TYPES.P2PKH,
2018-06-27 09:29:18 +02:00
pubkeys: [ourPubKey],
signatures: [undefined]
2016-12-14 05:41:24 +01:00
}
2018-06-27 09:29:18 +02:00
}
2016-12-14 05:41:24 +01:00
2018-06-27 09:29:18 +02:00
function build (type, input, allowIncomplete) {
const pubkeys = input.pubkeys || []
let signatures = input.signatures || []
2018-06-27 09:29:18 +02:00
switch (type) {
case SCRIPT_TYPES.P2PKH: {
if (pubkeys.length === 0) break
if (signatures.length === 0) break
return payments.p2pkh({ pubkey: pubkeys[0], signature: signatures[0] })
}
case SCRIPT_TYPES.P2WPKH: {
if (pubkeys.length === 0) break
if (signatures.length === 0) break
return payments.p2wpkh({ pubkey: pubkeys[0], signature: signatures[0] })
}
case SCRIPT_TYPES.P2PK: {
if (pubkeys.length === 0) break
if (signatures.length === 0) break
return payments.p2pk({ signature: signatures[0] })
}
2018-09-03 06:06:30 +02:00
case SCRIPT_TYPES.P2MS: {
const m = input.maxSignatures
2018-06-27 09:29:18 +02:00
if (allowIncomplete) {
signatures = signatures.map(x => x || ops.OP_0)
} else {
signatures = signatures.filter(x => x)
}
2018-09-26 06:57:03 +02:00
// 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)
const validate = !allowIncomplete || (m === signatures.length)
return payments.p2ms({ m, pubkeys, signatures }, { allowIncomplete, validate })
2018-06-27 09:29:18 +02:00
}
case SCRIPT_TYPES.P2SH: {
const redeem = build(input.redeemScriptType, input, allowIncomplete)
if (!redeem) return
return payments.p2sh({
redeem: {
output: redeem.output || input.redeemScript,
input: redeem.input,
witness: redeem.witness
}
})
}
case SCRIPT_TYPES.P2WSH: {
const redeem = build(input.witnessScriptType, input, allowIncomplete)
if (!redeem) return
return payments.p2wsh({
redeem: {
output: input.witnessScript,
input: redeem.input,
witness: redeem.witness
}
})
}
2016-12-14 05:41:24 +01:00
}
}
function TransactionBuilder (network, maximumFeeRate) {
2018-04-13 17:27:57 +02:00
this.__prevTxSet = {}
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 || 2500
2018-04-13 17:27:57 +02:00
this.__inputs = []
this.__tx = new Transaction()
this.__tx.version = 2
}
2015-11-26 02:40:06 +01:00
TransactionBuilder.prototype.setLockTime = function (locktime) {
typeforce(types.UInt32, locktime)
// if any signatures exist, throw
2018-04-13 17:27:57 +02:00
if (this.__inputs.some(function (input) {
2015-11-26 02:40:06 +01:00
if (!input.signatures) return false
return input.signatures.some(function (s) { return s })
})) {
throw new Error('No, this would invalidate signatures')
}
2018-04-13 17:27:57 +02:00
this.__tx.locktime = locktime
2015-11-26 02:40:06 +01:00
}
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
2018-04-13 17:27:57 +02:00
this.__tx.version = version
2016-06-22 06:57:11 +02:00
}
TransactionBuilder.fromTransaction = function (transaction, network) {
const txb = new TransactionBuilder(network, undefined)
// 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
2018-04-13 17:27:57 +02:00
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')
}
let value
2016-11-09 03:01:29 +01:00
// 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) {
const txOut = txHash.outs[vout]
2016-11-09 03:01:29 +01:00
prevOutScript = txOut.script
2018-12-27 10:26:08 +01:00
value = (<Output>txOut).value
2016-11-09 03:01:29 +01:00
2018-12-27 10:26:08 +01:00
txHash = txHash.getHash(false)
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')
}
const prevTxOut = txHash.toString('hex') + ':' + vout
2018-04-13 17:27:57 +02:00
if (this.__prevTxSet[prevTxOut] !== undefined) throw new Error('Duplicate TxOut: ' + prevTxOut)
let input = {
value: undefined,
prevOutScript: undefined,
pubkeys: undefined,
signatures: undefined,
prevOutType: undefined,
}
// 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 || [], undefined, undefined)
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) {
let prevOutType
2018-06-27 09:29:18 +02:00
if (!input.pubkeys && !input.signatures) {
const expanded = expandOutput(options.prevOutScript, undefined)
2018-06-27 09:29:18 +02:00
if (expanded.pubkeys) {
input.pubkeys = expanded.pubkeys
input.signatures = expanded.signatures
}
2018-06-27 09:29:18 +02:00
prevOutType = expanded.type
}
2014-06-16 08:05:31 +02:00
2016-11-09 03:01:29 +01:00
input.prevOutScript = options.prevOutScript
input.prevOutType = prevOutType || classify.output(options.prevOutScript)
2014-06-16 08:05:31 +02:00
}
const vin = this.__tx.addInput(txHash, vout, options.sequence, options.scriptSig)
2018-04-13 17:27:57 +02:00
this.__inputs[vin] = input
this.__prevTxSet[prevTxOut] = true
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
2018-01-10 15:54:38 +01:00
// Attempt to get a script if it's a base58 or bech32 address string
if (typeof scriptPubKey === 'string') {
scriptPubKey = baddress.toOutputScript(scriptPubKey, this.network)
}
2018-04-13 17:27:57 +02:00
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) {
2018-04-13 17:27:57 +02:00
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
}
const tx = this.__tx.clone()
2018-06-27 09:29:18 +02:00
// create script signatures from inputs
2018-04-13 17:27:57 +02:00
this.__inputs.forEach(function (input, i) {
2018-06-27 09:29:18 +02:00
if (!input.prevOutType && !allowIncomplete) throw new Error('Transaction is not complete')
const result = build(input.prevOutType, input, allowIncomplete)
if (!result) {
if (!allowIncomplete && input.prevOutType === SCRIPT_TYPES.NONSTANDARD) throw new Error('Unknown input type')
if (!allowIncomplete) throw new Error('Not enough information')
return
2016-12-22 13:18:45 +01:00
}
2014-06-16 08:05:31 +02:00
2018-06-27 09:29:18 +02:00
tx.setInputScript(i, result.input)
2016-12-22 13:18:45 +01:00
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) {
2018-06-27 09:29:18 +02:00
return input.signScript !== undefined &&
input.signType !== undefined &&
input.pubkeys !== undefined &&
input.signatures !== undefined &&
2018-06-27 09:29:18 +02:00
input.signatures.length === input.pubkeys.length &&
input.pubkeys.length > 0 &&
(
2018-06-27 09:29:18 +02:00
input.hasWitness === false ||
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')
2018-04-13 17:27:57 +02:00
if (!this.__inputs[vin]) throw new Error('No input at index: ' + vin)
hashType = hashType || Transaction.SIGHASH_ALL
2018-09-26 06:47:14 +02:00
if (this.__needsOutputs(hashType)) throw new Error('Transaction needs outputs')
const 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
2018-06-27 09:29:18 +02:00
const ourPubKey = keyPair.publicKey || keyPair.getPublicKey()
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
}
2018-06-27 09:29:18 +02:00
if (!canSign(input)) {
const prepared = prepareInput(input, ourPubKey, redeemScript, witnessValue, witnessScript)
// updates inline
Object.assign(input, prepared)
}
if (!canSign(input)) throw Error(input.prevOutType + ' not supported')
}
// ready to sign
let signatureHash
2018-06-27 09:29:18 +02:00
if (input.hasWitness) {
2018-04-13 17:27:57 +02:00
signatureHash = this.__tx.hashForWitnessV0(vin, input.signScript, input.value, hashType)
2016-12-14 05:41:24 +01:00
} else {
2018-04-13 17:27:57 +02:00
signatureHash = this.__tx.hashForSignature(vin, input.signScript, hashType)
2016-12-14 05:41:24 +01:00
}
// enforce in order signing of public keys
2018-06-27 09:29:18 +02:00
const signed = input.pubkeys.some(function (pubKey, i) {
if (!ourPubKey.equals(pubKey)) return false
if (input.signatures[i]) throw new Error('Signature already exists')
2018-06-27 09:29:18 +02:00
// TODO: add tests
if (ourPubKey.length !== 33 && input.hasWitness) {
throw new Error('BIP143 rejects uncompressed public keys in P2WPKH or P2WSH')
}
2015-03-02 06:48:36 +01:00
const signature = keyPair.sign(signatureHash)
input.signatures[i] = bscript.signature.encode(signature, 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) {
if (!input.signatures) return true
return (input.signatures || []).every(function (signature) {
if (!signature) return true
const hashType = signatureHashType(signature)
// if SIGHASH_ANYONECANPAY is set, signatures would not
// be invalidated by more inputs
return hashType & Transaction.SIGHASH_ANYONECANPAY
})
})
}
2018-09-26 06:47:14 +02:00
TransactionBuilder.prototype.__needsOutputs = function (signingHashType) {
if (signingHashType === Transaction.SIGHASH_ALL) {
return this.__tx.outs.length === 0
}
// if inputs are being signed with SIGHASH_NONE, we don't strictly need outputs
// .build() will fail, but .buildIncomplete() is OK
return (this.__tx.outs.length === 0) && this.__inputs.some((input) => {
if (!input.signatures) return false
return input.signatures.some((signature) => {
if (!signature) return false // no signature, no issue
const hashType = signatureHashType(signature)
if (hashType & Transaction.SIGHASH_NONE) return false // SIGHASH_NONE doesn't care about outputs
return true // SIGHASH_* does care
})
})
}
TransactionBuilder.prototype.__canModifyOutputs = function () {
const nInputs = this.__tx.ins.length
const nOutputs = this.__tx.outs.length
2018-04-13 17:27:57 +02:00
return this.__inputs.every(function (input) {
if (input.signatures === undefined) return true
return input.signatures.every(function (signature) {
if (!signature) return true
const hashType = signatureHashType(signature)
const 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
const incoming = this.__inputs.reduce(function (a, x) { return a + (x.value >>> 0) }, 0)
2016-11-09 03:01:29 +01:00
// but all outputs do, and if we have any input value
// we can immediately determine if the outputs are too small
const outgoing = this.__tx.outs.reduce(function (a, x) { return a + x.value }, 0)
const fee = incoming - outgoing
const 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
export {}