use payments in TxBuilder

This commit is contained in:
Daniel Cousens 2018-06-27 17:29:18 +10:00
parent 1fba0c62a5
commit 400be7114b
6 changed files with 389 additions and 444 deletions

View file

@ -5,188 +5,133 @@ const bscript = require('./script')
const btemplates = require('./templates')
const networks = require('./networks')
const ops = require('bitcoin-ops')
const payments = require('./payments')
const SCRIPT_TYPES = btemplates.types
const typeforce = require('typeforce')
const types = require('./types')
const scriptTypes = btemplates.types
const SIGNABLE = [btemplates.types.P2PKH, btemplates.types.P2PK, btemplates.types.MULTISIG]
const P2SH = SIGNABLE.concat([btemplates.types.P2WPKH, btemplates.types.P2WSH])
const ECPair = require('./ecpair')
const Transaction = require('./transaction')
function supportedType (type) {
return [
btemplates.types.P2PKH,
btemplates.types.P2PK,
btemplates.types.MULTISIG
].indexOf(type) !== -1
}
function expandInput (scriptSig, witnessStack, type, scriptPubKey) {
if (scriptSig.length === 0 && witnessStack.length === 0) return {}
if (!type) {
let ssType = btemplates.classifyInput(scriptSig, true)
let wsType = btemplates.classifyWitness(witnessStack, true)
if (ssType === SCRIPT_TYPES.NONSTANDARD) ssType = undefined
if (wsType === SCRIPT_TYPES.NONSTANDARD) wsType = undefined
type = ssType || wsType
}
function supportedP2SHType (type) {
return supportedType(type) || [
btemplates.types.P2WPKH,
btemplates.types.P2WSH
].indexOf(type) !== -1
}
function extractChunks (type, chunks, script) {
let pubKeys = []
let 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 SCRIPT_TYPES.P2WPKH: {
const { output, pubkey, signature } = payments.p2wpkh({ witness: witnessStack })
case scriptTypes.P2PK:
pubKeys[0] = script ? btemplates.pubKey.output.decode(script) : undefined
signatures = chunks.slice(0, 1)
break
case scriptTypes.MULTISIG:
if (script) {
const multisig = btemplates.multisig.output.decode(script)
pubKeys = multisig.pubKeys
return {
prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2WPKH,
pubkeys: [pubkey],
signatures: [signature]
}
}
signatures = chunks.slice(1).map(function (chunk) {
return chunk.length === 0 ? undefined : chunk
})
break
case SCRIPT_TYPES.P2PKH: {
const { output, pubkey, signature } = payments.p2pkh({ input: scriptSig })
return {
prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2PKH,
pubkeys: [pubkey],
signatures: [signature]
}
}
case SCRIPT_TYPES.P2PK: {
const { signature } = payments.p2pk({ input: scriptSig })
return {
prevOutType: SCRIPT_TYPES.P2PK,
pubkeys: [undefined],
signatures: [signature]
}
}
case SCRIPT_TYPES.MULTISIG: {
const { pubkeys, signatures } = payments.p2ms({
input: scriptSig,
output: scriptPubKey
}, { allowIncomplete: true })
return {
prevOutType: SCRIPT_TYPES.MULTISIG,
pubkeys: pubkeys,
signatures: signatures
}
}
}
if (type === SCRIPT_TYPES.P2SH) {
const { output, redeem } = payments.p2sh({
input: scriptSig,
witness: witnessStack
})
const outputType = btemplates.classifyOutput(redeem.output)
const expanded = expandInput(redeem.input, redeem.witness, outputType, redeem.output)
if (!expanded.prevOutType) return {}
return {
prevOutScript: output,
prevOutType: SCRIPT_TYPES.P2SH,
redeemScript: redeem.output,
redeemScriptType: expanded.prevOutType,
witnessScript: expanded.witnessScript,
witnessScriptType: expanded.witnessScriptType,
pubkeys: expanded.pubkeys,
signatures: expanded.signatures
}
}
if (type === SCRIPT_TYPES.P2WSH) {
const { output, redeem } = payments.p2wsh({
input: scriptSig,
witness: witnessStack
})
const outputType = btemplates.classifyOutput(redeem.output)
let expanded
if (outputType === SCRIPT_TYPES.P2WPKH) {
expanded = expandInput(redeem.input, redeem.witness, outputType)
} 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,
pubkeys: expanded.pubkeys,
signatures: expanded.signatures
}
}
return {
pubKeys: pubKeys,
signatures: signatures
prevOutType: SCRIPT_TYPES.NONSTANDARD,
prevOutScript: scriptSig
}
}
function expandInput (scriptSig, witnessStack) {
if (scriptSig.length === 0 && witnessStack.length === 0) return {}
let prevOutScript
let prevOutType
let scriptType
let script
let redeemScript
let witnessScript
let witnessScriptType
let redeemScriptType
let witness = false
let p2wsh = false
let p2sh = false
let witnessProgram
let chunks
const scriptSigChunks = bscript.decompile(scriptSig) || []
const 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
}
const classifyWitness = btemplates.classifyWitness(witnessStack, true)
if (classifyWitness === scriptTypes.P2WSH) {
witnessScript = witnessStack[witnessStack.length - 1]
witnessScriptType = btemplates.classifyOutput(witnessScript)
p2wsh = true
witness = true
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')
}
// bare witness
} else {
if (!redeemScript) {
throw new Error('No redeemScript provided for P2WSH, but scriptSig non-empty')
}
witnessProgram = btemplates.witnessScriptHash.output.encode(bcrypto.sha256(witnessScript))
if (!redeemScript.equals(witnessProgram)) {
throw new Error('Redeem script didn\'t match witnessScript')
}
}
if (!supportedType(btemplates.classifyOutput(witnessScript))) {
throw new Error('unsupported witness script')
}
script = witnessScript
scriptType = witnessScriptType
chunks = witnessStack.slice(0, -1)
} else if (classifyWitness === scriptTypes.P2WPKH) {
witness = true
const key = witnessStack[witnessStack.length - 1]
const keyHash = bcrypto.hash160(key)
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')
}
} else {
if (!redeemScript) {
throw new Error('No redeemScript provided for P2WPKH, but scriptSig wasn\'t empty')
}
witnessProgram = btemplates.witnessPubKeyHash.output.encode(keyHash)
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!')
}
script = redeemScript
scriptType = redeemScriptType
chunks = scriptSigChunks.slice(0, -1)
} else {
prevOutType = scriptType = btemplates.classifyInput(scriptSig)
chunks = scriptSigChunks
}
const expanded = extractChunks(scriptType, chunks, script)
const result = {
pubKeys: expanded.pubKeys,
signatures: expanded.signatures,
prevOutScript: prevOutScript,
prevOutType: prevOutType,
signType: scriptType,
signScript: script,
witness: Boolean(witness)
}
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
if (input.redeemScriptType !== SCRIPT_TYPES.MULTISIG || !input.redeemScript) return
if (input.pubkeys.length === input.signatures.length) return
const unmatched = input.signatures.concat()
input.signatures = input.pubKeys.map(function (pubKey) {
input.signatures = input.pubkeys.map(function (pubKey) {
const keyPair = ECPair.fromPublicKey(pubKey)
let match
@ -213,265 +158,262 @@ function fixMultisigOrder (input, transaction, vin) {
})
}
function expandOutput (script, scriptType, ourPubKey) {
function expandOutput (script, ourPubKey) {
typeforce(types.Buffer, script)
const type = btemplates.classifyOutput(script)
const scriptChunks = bscript.decompile(script) || []
if (!scriptType) {
scriptType = btemplates.classifyOutput(script)
}
switch (type) {
case SCRIPT_TYPES.P2PKH: {
if (!ourPubKey) return { type }
let pubKeys = []
switch (scriptType) {
// does our hash160(pubKey) match the output scripts?
case scriptTypes.P2PKH:
if (!ourPubKey) break
const pkh1 = scriptChunks[2]
// does our hash160(pubKey) match the output scripts?
const pkh1 = payments.p2pkh({ output: script }).hash
const pkh2 = bcrypto.hash160(ourPubKey)
if (pkh1.equals(pkh2)) pubKeys = [ourPubKey]
break
if (!pkh1.equals(pkh2)) return { type }
// does our hash160(pubKey) match the output scripts?
case scriptTypes.P2WPKH:
if (!ourPubKey) break
return {
type,
pubkeys: [ourPubKey],
signatures: [undefined]
}
}
const wpkh1 = scriptChunks[1]
case SCRIPT_TYPES.P2WPKH: {
if (!ourPubKey) return { type }
// does our hash160(pubKey) match the output scripts?
const wpkh1 = payments.p2wpkh({ output: script }).hash
const wpkh2 = bcrypto.hash160(ourPubKey)
if (wpkh1.equals(wpkh2)) pubKeys = [ourPubKey]
break
if (!wpkh1.equals(wpkh2)) return { type }
case scriptTypes.P2PK:
pubKeys = scriptChunks.slice(0, 1)
break
return {
type,
pubkeys: [ourPubKey],
signatures: [undefined]
}
}
case scriptTypes.MULTISIG:
pubKeys = scriptChunks.slice(1, -2)
break
case SCRIPT_TYPES.P2PK: {
const p2pk = payments.p2pk({ output: script })
return {
type,
pubkeys: [p2pk.pubkey],
signatures: [undefined]
}
}
default: return { scriptType: scriptType }
case SCRIPT_TYPES.MULTISIG: {
const p2ms = payments.p2ms({ output: script })
return {
type,
pubkeys: p2ms.pubkeys,
signatures: p2ms.pubkeys.map(() => undefined)
}
}
}
return {
pubKeys: pubKeys,
scriptType: scriptType,
signatures: pubKeys.map(function () { return undefined })
}
return { type }
}
function checkP2SHInput (input, redeemScriptHash) {
if (input.prevOutType) {
if (input.prevOutType !== scriptTypes.P2SH) throw new Error('PrevOutScript must be P2SH')
const chunks = bscript.decompile(input.prevOutScript)
if (!chunks) throw new Error('Invalid prevOutScript')
if (!chunks[1].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')
const chunks = bscript.decompile(input.prevOutScript)
if (!chunks) throw new Error('Invalid witnessScript')
if (!chunks[1].equals(witnessScriptHash)) throw new Error('Inconsistent sha256(witnessScript)')
}
}
function prepareInput (input, kpPubKey, redeemScript, witnessValue, witnessScript) {
let expanded
let prevOutType
let prevOutScript
let p2sh = false
let p2shType
let redeemScriptHash
let witness = false
let p2wsh = false
let witnessType
let witnessScriptHash
let signType
let signScript
function prepareInput (input, ourPubKey, redeemScript, witnessValue, witnessScript) {
if (redeemScript && witnessScript) {
redeemScriptHash = bcrypto.hash160(redeemScript)
witnessScriptHash = bcrypto.sha256(witnessScript)
checkP2SHInput(input, redeemScriptHash)
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 })
if (!redeemScript.equals(btemplates.witnessScriptHash.output.encode(witnessScriptHash))) throw new Error('Witness script inconsistent with redeem script')
// 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')
expanded = expandOutput(witnessScript, undefined, kpPubKey)
if (!expanded.pubKeys) throw new Error(expanded.scriptType + ' not supported as witnessScript (' + bscript.toASM(witnessScript) + ')')
prevOutType = btemplates.types.P2SH
prevOutScript = btemplates.scriptHash.output.encode(redeemScriptHash)
p2sh = witness = p2wsh = true
p2shType = btemplates.types.P2WSH
signType = witnessType = expanded.scriptType
signScript = witnessScript
} else if (redeemScript) {
redeemScriptHash = bcrypto.hash160(redeemScript)
checkP2SHInput(input, redeemScriptHash)
expanded = expandOutput(redeemScript, undefined, kpPubKey)
if (!expanded.pubKeys) throw new Error(expanded.scriptType + ' not supported as redeemScript (' + 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) {
witnessScriptHash = bcrypto.sha256(witnessScript)
checkP2WSHInput(input, witnessScriptHash)
expanded = expandOutput(witnessScript, undefined, kpPubKey)
if (!expanded.pubKeys) throw new Error(expanded.scriptType + ' not supported as witnessScript (' + bscript.toASM(witnessScript) + ')')
prevOutType = btemplates.types.P2WSH
prevOutScript = btemplates.witnessScriptHash.output.encode(witnessScriptHash)
witness = p2wsh = true
signType = witnessType = expanded.scriptType
signScript = witnessScript
} else if (input.prevOutType) {
// embedded scripts are not possible without a redeemScript
if (input.prevOutType === scriptTypes.P2SH) {
throw new Error('PrevOutScript is ' + input.prevOutType + ', requires redeemScript')
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
}
if (input.prevOutType === scriptTypes.P2WSH) {
throw new Error('PrevOutScript is ' + input.prevOutType + ', requires witnessScript')
}
return {
redeemScript: redeemScript,
redeemScriptType: SCRIPT_TYPES.P2WSH,
prevOutType = input.prevOutType
prevOutScript = input.prevOutScript
expanded = expandOutput(input.prevOutScript, input.prevOutType, kpPubKey)
if (!expanded.pubKeys) return
witnessScript: witnessScript,
witnessScriptType: expanded.type,
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: SCRIPT_TYPES.P2SH,
prevOutScript: p2sh.output,
prevOutType = scriptTypes.P2PKH
witness = false
signType = prevOutType
signScript = prevOutScript
}
hasWitness: true,
signScript: witnessScript,
signType: expanded.type,
if (signType === scriptTypes.P2WPKH) {
signScript = btemplates.pubKeyHash.output.encode(btemplates.witnessPubKeyHash.output.decode(signScript))
}
if (p2sh) {
input.redeemScript = redeemScript
input.redeemScriptType = p2shType
}
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
}
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])
} 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)
}
} else {
throw new Error('Not yet supported')
}
if (!allowIncomplete) throw new Error('Not enough signatures provided')
return []
}
function buildInput (input, allowIncomplete) {
let scriptType = input.prevOutType
let sig = []
let witness = []
if (supportedType(scriptType)) {
sig = buildStack(scriptType, input.signatures, input.pubKeys, allowIncomplete)
}
let p2sh = false
if (scriptType === btemplates.types.P2SH) {
// 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)) {
throw new Error('Impossible to sign this type')
}
if (supportedType(input.redeemScriptType)) {
sig = buildStack(input.redeemScriptType, input.signatures, input.pubKeys, allowIncomplete)
}
// If it wasn't SIGNABLE, it's witness, defer to that
if (input.redeemScriptType) {
p2sh = true
scriptType = input.redeemScriptType
pubkeys: expanded.pubkeys,
signatures: expanded.signatures
}
}
switch (scriptType) {
// P2WPKH is a special case of P2PKH
case btemplates.types.P2WPKH:
witness = buildStack(btemplates.types.P2PKH, input.signatures, input.pubKeys, allowIncomplete)
break
if (redeemScript) {
const p2sh = payments.p2sh({ redeem: { output: redeemScript } })
case btemplates.types.P2WSH:
// We can remove this check later
if (!allowIncomplete && !supportedType(input.witnessScriptType)) {
throw new Error('Impossible to sign this type')
}
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')
}
if (supportedType(input.witnessScriptType)) {
witness = buildStack(input.witnessScriptType, input.signatures, input.pubKeys, allowIncomplete)
witness.push(input.witnessScript)
scriptType = input.witnessScriptType
}
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
}
break
let signScript = redeemScript
if (expanded.type === SCRIPT_TYPES.P2WPKH) {
signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output
}
return {
redeemScript: redeemScript,
redeemScriptType: expanded.type,
prevOutType: SCRIPT_TYPES.P2SH,
prevOutScript: p2sh.output,
hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH,
signScript: signScript,
signType: expanded.type,
pubkeys: expanded.pubkeys,
signatures: expanded.signatures
}
}
// append redeemScript if necessary
if (p2sh) {
sig.push(input.redeemScript)
if (witnessScript) {
const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } })
if (input.prevOutScript) {
const p2wshAlt = payments.p2wsh({ output: input.prevOutScript })
if (!p2wsh.hash.equals(p2wshAlt.hash)) throw new Error('Witness 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
}
return {
witnessScript: witnessScript,
witnessScriptType: expanded.type,
prevOutType: SCRIPT_TYPES.P2WSH,
prevOutScript: p2wsh.output,
hasWitness: true,
signScript: witnessScript,
signType: expanded.type,
pubkeys: expanded.pubkeys,
signatures: expanded.signatures
}
}
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
}
return {
prevOutType: expanded.type,
prevOutScript: input.prevOutScript,
hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH,
signScript: input.prevOutScript,
signType: expanded.type,
pubkeys: expanded.pubkeys,
signatures: expanded.signatures
}
}
const prevOutScript = payments.p2pkh({ pubkey: ourPubKey }).output
return {
type: scriptType,
script: bscript.compile(sig),
witness: witness
prevOutType: SCRIPT_TYPES.P2PKH,
prevOutScript: prevOutScript,
hasWitness: false,
signScript: prevOutScript,
signType: SCRIPT_TYPES.P2PKH,
pubkeys: [ourPubKey],
signatures: [undefined]
}
}
function build (type, input, allowIncomplete) {
const pubkeys = input.pubkeys || []
let signatures = input.signatures || []
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] })
}
case SCRIPT_TYPES.MULTISIG: {
if (allowIncomplete) {
signatures = signatures.map(x => x || ops.OP_0)
} else {
signatures = signatures.filter(x => x)
}
return payments.p2ms({ signatures }, { allowIncomplete })
}
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
}
})
}
}
}
@ -590,15 +532,14 @@ TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, options)
if (!input.prevOutScript && options.prevOutScript) {
let prevOutType
if (!input.pubKeys && !input.signatures) {
if (!input.pubkeys && !input.signatures) {
const expanded = expandOutput(options.prevOutScript)
if (expanded.pubKeys) {
input.pubKeys = expanded.pubKeys
if (expanded.pubkeys) {
input.pubkeys = expanded.pubkeys
input.signatures = expanded.signatures
}
prevOutType = expanded.scriptType
prevOutType = expanded.type
}
input.prevOutScript = options.prevOutScript
@ -638,20 +579,19 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) {
}
const tx = this.__tx.clone()
// Create script signatures from inputs
this.__inputs.forEach(function (input, i) {
const scriptType = input.witnessScriptType || input.redeemScriptType || input.prevOutType
if (!scriptType && !allowIncomplete) throw new Error('Transaction is not complete')
const result = buildInput(input, allowIncomplete)
// skip if no result
if (!allowIncomplete) {
if (!supportedType(result.type) && result.type !== btemplates.types.P2WPKH) {
throw new Error(result.type + ' not supported')
}
// create script signatures from inputs
this.__inputs.forEach(function (input, i) {
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
}
tx.setInputScript(i, result.script)
tx.setInputScript(i, result.input)
tx.setWitness(i, result.witness)
})
@ -666,15 +606,15 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) {
}
function canSign (input) {
return input.prevOutScript !== undefined &&
input.signScript !== undefined &&
input.pubKeys !== undefined &&
return input.signScript !== undefined &&
input.signType !== undefined &&
input.pubkeys !== undefined &&
input.signatures !== undefined &&
input.signatures.length === input.pubKeys.length &&
input.pubKeys.length > 0 &&
input.signatures.length === input.pubkeys.length &&
input.pubkeys.length > 0 &&
(
input.witness === false ||
(input.witness === true && input.value !== undefined)
input.hasWitness === false ||
input.value !== undefined
)
}
@ -693,7 +633,7 @@ TransactionBuilder.prototype.sign = function (vin, keyPair, redeemScript, hashTy
throw new Error('Inconsistent redeemScript')
}
const kpPubKey = keyPair.publicKey || keyPair.getPublicKey()
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')
@ -701,28 +641,33 @@ TransactionBuilder.prototype.sign = function (vin, keyPair, redeemScript, hashTy
input.value = witnessValue
}
if (!canSign(input)) prepareInput(input, kpPubKey, redeemScript, witnessValue, witnessScript)
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
if (input.witness) {
if (input.hasWitness) {
signatureHash = this.__tx.hashForWitnessV0(vin, input.signScript, input.value, hashType)
} else {
signatureHash = this.__tx.hashForSignature(vin, input.signScript, hashType)
}
// enforce in order signing of public keys
const signed = input.pubKeys.some(function (pubKey, i) {
if (!kpPubKey.equals(pubKey)) return false
const signed = input.pubkeys.some(function (pubKey, i) {
if (!ourPubKey.equals(pubKey)) return false
if (input.signatures[i]) throw new Error('Signature already exists')
if (kpPubKey.length !== 33 && (
input.signType === scriptTypes.P2WPKH ||
input.redeemScriptType === scriptTypes.P2WSH ||
input.prevOutType === scriptTypes.P2WSH
)) throw new Error('BIP143 rejects uncompressed public keys in P2WPKH or P2WSH')
// TODO: add tests
if (ourPubKey.length !== 33 && input.hasWitness) {
throw new Error('BIP143 rejects uncompressed public keys in P2WPKH or P2WSH')
}
const signature = keyPair.sign(signatureHash)
input.signatures[i] = bscript.signature.encode(signature, hashType)