use payments in TxBuilder
This commit is contained in:
parent
1fba0c62a5
commit
400be7114b
6 changed files with 389 additions and 444 deletions
|
@ -19,6 +19,7 @@ function stacksEqual (a, b) {
|
||||||
// output: m [pubKeys ...] n OP_CHECKMULTISIG
|
// output: m [pubKeys ...] n OP_CHECKMULTISIG
|
||||||
function p2ms (a, opts) {
|
function p2ms (a, opts) {
|
||||||
if (
|
if (
|
||||||
|
!a.input &&
|
||||||
!a.output &&
|
!a.output &&
|
||||||
!(a.pubkeys && a.m !== undefined) &&
|
!(a.pubkeys && a.m !== undefined) &&
|
||||||
!a.signatures
|
!a.signatures
|
||||||
|
|
|
@ -10,6 +10,7 @@ let BITCOIN_NETWORK = require('../networks').bitcoin
|
||||||
// output: {pubKey} OP_CHECKSIG
|
// output: {pubKey} OP_CHECKSIG
|
||||||
function p2pk (a, opts) {
|
function p2pk (a, opts) {
|
||||||
if (
|
if (
|
||||||
|
!a.input &&
|
||||||
!a.output &&
|
!a.output &&
|
||||||
!a.pubkey &&
|
!a.pubkey &&
|
||||||
!a.input &&
|
!a.input &&
|
||||||
|
|
|
@ -5,188 +5,133 @@ const bscript = require('./script')
|
||||||
const btemplates = require('./templates')
|
const btemplates = require('./templates')
|
||||||
const networks = require('./networks')
|
const networks = require('./networks')
|
||||||
const ops = require('bitcoin-ops')
|
const ops = require('bitcoin-ops')
|
||||||
|
const payments = require('./payments')
|
||||||
|
const SCRIPT_TYPES = btemplates.types
|
||||||
const typeforce = require('typeforce')
|
const typeforce = require('typeforce')
|
||||||
const types = require('./types')
|
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 ECPair = require('./ecpair')
|
||||||
const Transaction = require('./transaction')
|
const Transaction = require('./transaction')
|
||||||
|
|
||||||
function supportedType (type) {
|
function expandInput (scriptSig, witnessStack, type, scriptPubKey) {
|
||||||
return [
|
if (scriptSig.length === 0 && witnessStack.length === 0) return {}
|
||||||
btemplates.types.P2PKH,
|
if (!type) {
|
||||||
btemplates.types.P2PK,
|
let ssType = btemplates.classifyInput(scriptSig, true)
|
||||||
btemplates.types.MULTISIG
|
let wsType = btemplates.classifyWitness(witnessStack, true)
|
||||||
].indexOf(type) !== -1
|
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) {
|
switch (type) {
|
||||||
case scriptTypes.P2PKH:
|
case SCRIPT_TYPES.P2WPKH: {
|
||||||
// if (redeemScript) throw new Error('Nonstandard... P2SH(P2PKH)')
|
const { output, pubkey, signature } = payments.p2wpkh({ witness: witnessStack })
|
||||||
pubKeys = chunks.slice(1)
|
|
||||||
signatures = chunks.slice(0, 1)
|
|
||||||
break
|
|
||||||
|
|
||||||
case scriptTypes.P2PK:
|
return {
|
||||||
pubKeys[0] = script ? btemplates.pubKey.output.decode(script) : undefined
|
prevOutScript: output,
|
||||||
signatures = chunks.slice(0, 1)
|
prevOutType: SCRIPT_TYPES.P2WPKH,
|
||||||
break
|
pubkeys: [pubkey],
|
||||||
|
signatures: [signature]
|
||||||
case scriptTypes.MULTISIG:
|
|
||||||
if (script) {
|
|
||||||
const multisig = btemplates.multisig.output.decode(script)
|
|
||||||
pubKeys = multisig.pubKeys
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
signatures = chunks.slice(1).map(function (chunk) {
|
case SCRIPT_TYPES.P2PKH: {
|
||||||
return chunk.length === 0 ? undefined : chunk
|
const { output, pubkey, signature } = payments.p2pkh({ input: scriptSig })
|
||||||
})
|
|
||||||
break
|
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 {
|
return {
|
||||||
pubKeys: pubKeys,
|
prevOutType: SCRIPT_TYPES.NONSTANDARD,
|
||||||
signatures: signatures
|
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
|
// could be done in expandInput, but requires the original Transaction for hashForSignature
|
||||||
function fixMultisigOrder (input, transaction, vin) {
|
function fixMultisigOrder (input, transaction, vin) {
|
||||||
if (input.redeemScriptType !== scriptTypes.MULTISIG || !input.redeemScript) return
|
if (input.redeemScriptType !== SCRIPT_TYPES.MULTISIG || !input.redeemScript) return
|
||||||
if (input.pubKeys.length === input.signatures.length) return
|
if (input.pubkeys.length === input.signatures.length) return
|
||||||
|
|
||||||
const unmatched = input.signatures.concat()
|
const unmatched = input.signatures.concat()
|
||||||
|
|
||||||
input.signatures = input.pubKeys.map(function (pubKey) {
|
input.signatures = input.pubkeys.map(function (pubKey) {
|
||||||
const keyPair = ECPair.fromPublicKey(pubKey)
|
const keyPair = ECPair.fromPublicKey(pubKey)
|
||||||
let match
|
let match
|
||||||
|
|
||||||
|
@ -213,265 +158,262 @@ function fixMultisigOrder (input, transaction, vin) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandOutput (script, scriptType, ourPubKey) {
|
function expandOutput (script, ourPubKey) {
|
||||||
typeforce(types.Buffer, script)
|
typeforce(types.Buffer, script)
|
||||||
|
const type = btemplates.classifyOutput(script)
|
||||||
|
|
||||||
const scriptChunks = bscript.decompile(script) || []
|
switch (type) {
|
||||||
if (!scriptType) {
|
case SCRIPT_TYPES.P2PKH: {
|
||||||
scriptType = btemplates.classifyOutput(script)
|
if (!ourPubKey) return { type }
|
||||||
}
|
|
||||||
|
|
||||||
let pubKeys = []
|
// does our hash160(pubKey) match the output scripts?
|
||||||
|
const pkh1 = payments.p2pkh({ output: script }).hash
|
||||||
switch (scriptType) {
|
|
||||||
// does our hash160(pubKey) match the output scripts?
|
|
||||||
case scriptTypes.P2PKH:
|
|
||||||
if (!ourPubKey) break
|
|
||||||
|
|
||||||
const pkh1 = scriptChunks[2]
|
|
||||||
const pkh2 = bcrypto.hash160(ourPubKey)
|
const pkh2 = bcrypto.hash160(ourPubKey)
|
||||||
if (pkh1.equals(pkh2)) pubKeys = [ourPubKey]
|
if (!pkh1.equals(pkh2)) return { type }
|
||||||
break
|
|
||||||
|
|
||||||
// does our hash160(pubKey) match the output scripts?
|
return {
|
||||||
case scriptTypes.P2WPKH:
|
type,
|
||||||
if (!ourPubKey) break
|
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)
|
const wpkh2 = bcrypto.hash160(ourPubKey)
|
||||||
if (wpkh1.equals(wpkh2)) pubKeys = [ourPubKey]
|
if (!wpkh1.equals(wpkh2)) return { type }
|
||||||
break
|
|
||||||
|
|
||||||
case scriptTypes.P2PK:
|
return {
|
||||||
pubKeys = scriptChunks.slice(0, 1)
|
type,
|
||||||
break
|
pubkeys: [ourPubKey],
|
||||||
|
signatures: [undefined]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case scriptTypes.MULTISIG:
|
case SCRIPT_TYPES.P2PK: {
|
||||||
pubKeys = scriptChunks.slice(1, -2)
|
const p2pk = payments.p2pk({ output: script })
|
||||||
break
|
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 {
|
return { type }
|
||||||
pubKeys: pubKeys,
|
|
||||||
scriptType: scriptType,
|
|
||||||
signatures: pubKeys.map(function () { return undefined })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkP2SHInput (input, redeemScriptHash) {
|
function prepareInput (input, ourPubKey, redeemScript, witnessValue, witnessScript) {
|
||||||
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
|
|
||||||
|
|
||||||
if (redeemScript && witnessScript) {
|
if (redeemScript && witnessScript) {
|
||||||
redeemScriptHash = bcrypto.hash160(redeemScript)
|
const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } })
|
||||||
witnessScriptHash = bcrypto.sha256(witnessScript)
|
const p2wshAlt = payments.p2wsh({ output: redeemScript })
|
||||||
checkP2SHInput(input, redeemScriptHash)
|
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)
|
const expanded = expandOutput(p2wsh.redeem.output, ourPubKey)
|
||||||
if (!expanded.pubKeys) throw new Error(expanded.scriptType + ' not supported as witnessScript (' + bscript.toASM(witnessScript) + ')')
|
if (!expanded.pubkeys) throw new Error(expanded.type + ' not supported as witnessScript (' + bscript.toASM(witnessScript) + ')')
|
||||||
|
if (input.signatures && input.signatures.some(x => x)) {
|
||||||
prevOutType = btemplates.types.P2SH
|
expanded.signatures = input.signatures
|
||||||
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')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.prevOutType === scriptTypes.P2WSH) {
|
return {
|
||||||
throw new Error('PrevOutScript is ' + input.prevOutType + ', requires witnessScript')
|
redeemScript: redeemScript,
|
||||||
}
|
redeemScriptType: SCRIPT_TYPES.P2WSH,
|
||||||
|
|
||||||
prevOutType = input.prevOutType
|
witnessScript: witnessScript,
|
||||||
prevOutScript = input.prevOutScript
|
witnessScriptType: expanded.type,
|
||||||
expanded = expandOutput(input.prevOutScript, input.prevOutType, kpPubKey)
|
|
||||||
if (!expanded.pubKeys) return
|
|
||||||
|
|
||||||
witness = (input.prevOutType === scriptTypes.P2WPKH)
|
prevOutType: SCRIPT_TYPES.P2SH,
|
||||||
signType = prevOutType
|
prevOutScript: p2sh.output,
|
||||||
signScript = prevOutScript
|
|
||||||
} else {
|
|
||||||
prevOutScript = btemplates.pubKeyHash.output.encode(bcrypto.hash160(kpPubKey))
|
|
||||||
expanded = expandOutput(prevOutScript, scriptTypes.P2PKH, kpPubKey)
|
|
||||||
|
|
||||||
prevOutType = scriptTypes.P2PKH
|
hasWitness: true,
|
||||||
witness = false
|
signScript: witnessScript,
|
||||||
signType = prevOutType
|
signType: expanded.type,
|
||||||
signScript = prevOutScript
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signType === scriptTypes.P2WPKH) {
|
pubkeys: expanded.pubkeys,
|
||||||
signScript = btemplates.pubKeyHash.output.encode(btemplates.witnessPubKeyHash.output.decode(signScript))
|
signatures: expanded.signatures
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (scriptType) {
|
if (redeemScript) {
|
||||||
// P2WPKH is a special case of P2PKH
|
const p2sh = payments.p2sh({ redeem: { output: redeemScript } })
|
||||||
case btemplates.types.P2WPKH:
|
|
||||||
witness = buildStack(btemplates.types.P2PKH, input.signatures, input.pubKeys, allowIncomplete)
|
|
||||||
break
|
|
||||||
|
|
||||||
case btemplates.types.P2WSH:
|
if (input.prevOutScript) {
|
||||||
// We can remove this check later
|
let p2shAlt
|
||||||
if (!allowIncomplete && !supportedType(input.witnessScriptType)) {
|
try {
|
||||||
throw new Error('Impossible to sign this type')
|
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)) {
|
const expanded = expandOutput(p2sh.redeem.output, ourPubKey)
|
||||||
witness = buildStack(input.witnessScriptType, input.signatures, input.pubKeys, allowIncomplete)
|
if (!expanded.pubkeys) throw new Error(expanded.type + ' not supported as redeemScript (' + bscript.toASM(redeemScript) + ')')
|
||||||
witness.push(input.witnessScript)
|
if (input.signatures && input.signatures.some(x => x)) {
|
||||||
scriptType = input.witnessScriptType
|
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 (witnessScript) {
|
||||||
if (p2sh) {
|
const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } })
|
||||||
sig.push(input.redeemScript)
|
|
||||||
|
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 {
|
return {
|
||||||
type: scriptType,
|
prevOutType: SCRIPT_TYPES.P2PKH,
|
||||||
script: bscript.compile(sig),
|
prevOutScript: prevOutScript,
|
||||||
witness: witness
|
|
||||||
|
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) {
|
if (!input.prevOutScript && options.prevOutScript) {
|
||||||
let prevOutType
|
let prevOutType
|
||||||
|
|
||||||
if (!input.pubKeys && !input.signatures) {
|
if (!input.pubkeys && !input.signatures) {
|
||||||
const expanded = expandOutput(options.prevOutScript)
|
const expanded = expandOutput(options.prevOutScript)
|
||||||
|
if (expanded.pubkeys) {
|
||||||
if (expanded.pubKeys) {
|
input.pubkeys = expanded.pubkeys
|
||||||
input.pubKeys = expanded.pubKeys
|
|
||||||
input.signatures = expanded.signatures
|
input.signatures = expanded.signatures
|
||||||
}
|
}
|
||||||
|
|
||||||
prevOutType = expanded.scriptType
|
prevOutType = expanded.type
|
||||||
}
|
}
|
||||||
|
|
||||||
input.prevOutScript = options.prevOutScript
|
input.prevOutScript = options.prevOutScript
|
||||||
|
@ -638,20 +579,19 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const tx = this.__tx.clone()
|
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
|
// create script signatures from inputs
|
||||||
if (!allowIncomplete) {
|
this.__inputs.forEach(function (input, i) {
|
||||||
if (!supportedType(result.type) && result.type !== btemplates.types.P2WPKH) {
|
if (!input.prevOutType && !allowIncomplete) throw new Error('Transaction is not complete')
|
||||||
throw new Error(result.type + ' not supported')
|
|
||||||
}
|
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)
|
tx.setWitness(i, result.witness)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -666,15 +606,15 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function canSign (input) {
|
function canSign (input) {
|
||||||
return input.prevOutScript !== undefined &&
|
return input.signScript !== undefined &&
|
||||||
input.signScript !== undefined &&
|
input.signType !== undefined &&
|
||||||
input.pubKeys !== undefined &&
|
input.pubkeys !== undefined &&
|
||||||
input.signatures !== undefined &&
|
input.signatures !== undefined &&
|
||||||
input.signatures.length === input.pubKeys.length &&
|
input.signatures.length === input.pubkeys.length &&
|
||||||
input.pubKeys.length > 0 &&
|
input.pubkeys.length > 0 &&
|
||||||
(
|
(
|
||||||
input.witness === false ||
|
input.hasWitness === false ||
|
||||||
(input.witness === true && input.value !== undefined)
|
input.value !== undefined
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -693,7 +633,7 @@ TransactionBuilder.prototype.sign = function (vin, keyPair, redeemScript, hashTy
|
||||||
throw new Error('Inconsistent redeemScript')
|
throw new Error('Inconsistent redeemScript')
|
||||||
}
|
}
|
||||||
|
|
||||||
const kpPubKey = keyPair.publicKey || keyPair.getPublicKey()
|
const ourPubKey = keyPair.publicKey || keyPair.getPublicKey()
|
||||||
if (!canSign(input)) {
|
if (!canSign(input)) {
|
||||||
if (witnessValue !== undefined) {
|
if (witnessValue !== undefined) {
|
||||||
if (input.value !== undefined && input.value !== witnessValue) throw new Error('Input didn\'t match witnessValue')
|
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
|
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')
|
if (!canSign(input)) throw Error(input.prevOutType + ' not supported')
|
||||||
}
|
}
|
||||||
|
|
||||||
// ready to sign
|
// ready to sign
|
||||||
let signatureHash
|
let signatureHash
|
||||||
if (input.witness) {
|
if (input.hasWitness) {
|
||||||
signatureHash = this.__tx.hashForWitnessV0(vin, input.signScript, input.value, hashType)
|
signatureHash = this.__tx.hashForWitnessV0(vin, input.signScript, input.value, hashType)
|
||||||
} else {
|
} else {
|
||||||
signatureHash = this.__tx.hashForSignature(vin, input.signScript, hashType)
|
signatureHash = this.__tx.hashForSignature(vin, input.signScript, hashType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// enforce in order signing of public keys
|
// enforce in order signing of public keys
|
||||||
const signed = input.pubKeys.some(function (pubKey, i) {
|
const signed = input.pubkeys.some(function (pubKey, i) {
|
||||||
if (!kpPubKey.equals(pubKey)) return false
|
if (!ourPubKey.equals(pubKey)) return false
|
||||||
if (input.signatures[i]) throw new Error('Signature already exists')
|
if (input.signatures[i]) throw new Error('Signature already exists')
|
||||||
|
|
||||||
if (kpPubKey.length !== 33 && (
|
// TODO: add tests
|
||||||
input.signType === scriptTypes.P2WPKH ||
|
if (ourPubKey.length !== 33 && input.hasWitness) {
|
||||||
input.redeemScriptType === scriptTypes.P2WSH ||
|
throw new Error('BIP143 rejects uncompressed public keys in P2WPKH or P2WSH')
|
||||||
input.prevOutType === scriptTypes.P2WSH
|
}
|
||||||
)) throw new Error('BIP143 rejects uncompressed public keys in P2WPKH or P2WSH')
|
|
||||||
|
|
||||||
const signature = keyPair.sign(signatureHash)
|
const signature = keyPair.sign(signatureHash)
|
||||||
input.signatures[i] = bscript.signature.encode(signature, hashType)
|
input.signatures[i] = bscript.signature.encode(signature, hashType)
|
||||||
|
|
22
test/fixtures/transaction_builder.json
vendored
22
test/fixtures/transaction_builder.json
vendored
|
@ -395,7 +395,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Transaction w/ P2WPKH -> P2WPKH",
|
"description": "Transaction w/ P2WPKH -> P2WPKH",
|
||||||
"txHex": "01000000000101ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff011027000000000000160014aa4d7985c57e011a8b3dd8e0e5a73aaef41629c502483045022100a8fc5e4c6d7073474eff2af5d756966e75be0cdfbba299518526080ce8b584be02200f26d41082764df89e3c815b8eaf51034a3b68a25f1be51208f54222c1bb6c1601210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179800000000",
|
"txHex": "01000000000101ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff011027000000000000160014aa4d7985c57e011a8b3dd8e0e5a73aaef41629c502483045022100b4a9d46ea4d38d6b3ea098911c9f72c0ae6ebc72408e6be7880a6b22a4b3e4da02207996107d0e6437f80363f96f502a38f275156f7501ea51f67899ba78a0c129c101210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179800000000",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
|
@ -833,8 +833,8 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Sighash V1: ALL",
|
"description": "SIGHASH V0+V1, (P2PKH, P2WPKH) -> 2x P2PKH",
|
||||||
"txHex": "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000484730440220691a19d365c8d75f921346c70271506bde136f13a4b566dd796902c262e2ec6d02202b00c4aa030eedf294552bdfc163936d2f4e91c59e7798c4471250cf07cb859501eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff0230f45e13000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac00e9a435000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac0002483045022100fddd014889f18d489b5400bfa8cb0a32301a768d934b1a0e2b55398119f26cab02207676c64c16ffa7ffaaf8e16b3b74e916687eebdfdb36b9b7997e838384d464640121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000",
|
"txHex": "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000484730440220691a19d365c8d75f921346c70271506bde136f13a4b566dd796902c262e2ec6d02202b00c4aa030eedf294552bdfc163936d2f4e91c59e7798c4471250cf07cb859501eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff0230f45e13000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac00e9a435000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac000247304402200a7b08cccedf608e279410091acbd7e990e19a8edf401c3698763d2920de5871022060462ed172a02ecef73ebc19811d8fc72ed68f4419742df70241ad0a5a6a36410121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
|
@ -877,7 +877,7 @@
|
||||||
"locktime": 17
|
"locktime": 17
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Sighash V1: ALL 2",
|
"description": "SIGHASH V0+V1, P2SH(P2WPKH) -> P2PKH",
|
||||||
"txHex": "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009bdf0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac02473044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb012103ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000",
|
"txHex": "01000000000101db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477010000001716001479091972186c449eb1ded22b78e40d009bdf0089feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac02473044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb012103ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a2687392040000",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
@ -1313,8 +1313,8 @@
|
||||||
"locktime": 0
|
"locktime": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "P2WPKH",
|
"description": "P2WPKH -> P2PKH",
|
||||||
"txHex": "0100000000010133defbe3e28860007ff3e21222774c220cb35d554fa3e3796d25bf8ee983e1080000000000ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac0248304502210097c3006f0b390982eb47f762b2853773c6cedf83668a22d710f4c13c4fd6b15502205e26ef16a81fc818a37f3a34fc6d0700e61100ea6c6773907c9c046042c440340121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b00000000",
|
"txHex": "0100000000010133defbe3e28860007ff3e21222774c220cb35d554fa3e3796d25bf8ee983e1080000000000ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac02483045022100834f56825e880ab7926164458e10582d9fd8df005396b7e51a1efb8db277204e02206a3610b7101c3242643ac9c9d3487c2d28ffdad19ec26a7f81fc100bdac625f10121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b00000000",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
|
@ -1340,7 +1340,7 @@
|
||||||
"locktime": 0
|
"locktime": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "P2SH(P2WPKH)",
|
"description": "P2SH(P2WPKH) -> P2PKH",
|
||||||
"txHex": "010000000001015df9a0b9ade2d835881704e0f53b51a4b19ecfc794ea1f3555783dd7f68659ce0000000017160014851a33a5ef0d4279bd5854949174e2c65b1d4500ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac02483045022100cb3929c128fec5108071b662e5af58e39ac8708882753a421455ca80462956f6022030c0f4738dd1a13fc7a34393002d25c6e8a6399f29c7db4b98f53a9475d94ca20121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b00000000",
|
"txHex": "010000000001015df9a0b9ade2d835881704e0f53b51a4b19ecfc794ea1f3555783dd7f68659ce0000000017160014851a33a5ef0d4279bd5854949174e2c65b1d4500ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac02483045022100cb3929c128fec5108071b662e5af58e39ac8708882753a421455ca80462956f6022030c0f4738dd1a13fc7a34393002d25c6e8a6399f29c7db4b98f53a9475d94ca20121038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b00000000",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"inputs": [
|
"inputs": [
|
||||||
|
@ -1872,7 +1872,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Incomplete transaction, known prevTxScript, thereby throws for missing signatures",
|
"description": "Incomplete transaction, known prevTxScript, thereby throws for missing signatures",
|
||||||
"exception": "Not enough signatures provided",
|
"exception": "Not enough information",
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
@ -1925,7 +1925,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Complete transaction w/ non-standard inputs",
|
"description": "Complete transaction w/ non-standard inputs",
|
||||||
"exception": "nonstandard not supported",
|
"exception": "Unknown input type",
|
||||||
"txHex": "010000000100000000171a0000e028f2000000000050178500000000000d0000000e00000000000000201ff691b2263260e71f363d1db51ff3100d285956a40cc0e4f8c8c2c4a80559b1ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000"
|
"txHex": "010000000100000000171a0000e028f2000000000050178500000000000d0000000e00000000000000201ff691b2263260e71f363d1db51ff3100d285956a40cc0e4f8c8c2c4a80559b1ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -2149,7 +2149,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Inconsistent RedeemScript hash",
|
"description": "Inconsistent RedeemScript hash",
|
||||||
"exception": "Inconsistent hash160",
|
"exception": "Redeem script inconsistent with prevOutScript",
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
@ -2174,7 +2174,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Inconsistent WitnessScript hash",
|
"description": "Inconsistent WitnessScript hash",
|
||||||
"exception": "Inconsistent sha256",
|
"exception": "Witness script inconsistent with prevOutScript",
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
"txId": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
|
|
@ -103,7 +103,7 @@ describe('bitcoinjs-lib (transactions)', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can create (and broadcast via 3PBP) a Transaction with a 2-of-4 P2SH(multisig) input', function (done) {
|
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2MS(2 of 4)) (multisig) input', function (done) {
|
||||||
this.timeout(30000)
|
this.timeout(30000)
|
||||||
|
|
||||||
const keyPairs = [
|
const keyPairs = [
|
||||||
|
@ -112,21 +112,19 @@ describe('bitcoinjs-lib (transactions)', function () {
|
||||||
bitcoin.ECPair.makeRandom({ network: regtest }),
|
bitcoin.ECPair.makeRandom({ network: regtest }),
|
||||||
bitcoin.ECPair.makeRandom({ network: regtest })
|
bitcoin.ECPair.makeRandom({ network: regtest })
|
||||||
]
|
]
|
||||||
const pubKeys = keyPairs.map(function (x) { return x.publicKey })
|
const pubkeys = keyPairs.map(x => x.publicKey)
|
||||||
|
const p2ms = bitcoin.payments.p2ms({ m: 2, pubkeys: pubkeys, network: regtest })
|
||||||
|
const p2sh = bitcoin.payments.p2sh({ redeem: p2ms, network: regtest })
|
||||||
|
|
||||||
const redeemScript = bitcoin.script.multisig.output.encode(2, pubKeys)
|
regtestUtils.faucet(p2sh.address, 2e4, function (err, unspent) {
|
||||||
const scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
|
|
||||||
const address = bitcoin.address.fromOutputScript(scriptPubKey, regtest)
|
|
||||||
|
|
||||||
regtestUtils.faucet(address, 2e4, function (err, unspent) {
|
|
||||||
if (err) return done(err)
|
if (err) return done(err)
|
||||||
|
|
||||||
const txb = new bitcoin.TransactionBuilder(regtest)
|
const txb = new bitcoin.TransactionBuilder(regtest)
|
||||||
txb.addInput(unspent.txId, unspent.vout)
|
txb.addInput(unspent.txId, unspent.vout)
|
||||||
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4)
|
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4)
|
||||||
|
|
||||||
txb.sign(0, keyPairs[0], redeemScript)
|
txb.sign(0, keyPairs[0], p2sh.redeem.output)
|
||||||
txb.sign(0, keyPairs[2], redeemScript)
|
txb.sign(0, keyPairs[2], p2sh.redeem.output)
|
||||||
const tx = txb.build()
|
const tx = txb.build()
|
||||||
|
|
||||||
// build and broadcast to the Bitcoin RegTest network
|
// build and broadcast to the Bitcoin RegTest network
|
||||||
|
@ -143,7 +141,7 @@ describe('bitcoinjs-lib (transactions)', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can create (and broadcast via 3PBP) a Transaction with a SegWit P2SH(P2WPKH) input', function (done) {
|
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input', function (done) {
|
||||||
this.timeout(30000)
|
this.timeout(30000)
|
||||||
|
|
||||||
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
|
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest })
|
||||||
|
@ -174,7 +172,7 @@ describe('bitcoinjs-lib (transactions)', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can create (and broadcast via 3PBP) a Transaction with a SegWit 3-of-4 P2SH(P2WSH(multisig)) input', function (done) {
|
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', function (done) {
|
||||||
this.timeout(50000)
|
this.timeout(50000)
|
||||||
|
|
||||||
const keyPairs = [
|
const keyPairs = [
|
||||||
|
|
|
@ -149,7 +149,6 @@ describe('TransactionBuilder', function () {
|
||||||
txb.__inputs.forEach(function (i) {
|
txb.__inputs.forEach(function (i) {
|
||||||
assert.strictEqual(i.prevOutType, 'scripthash')
|
assert.strictEqual(i.prevOutType, 'scripthash')
|
||||||
assert.strictEqual(i.redeemScriptType, 'multisig')
|
assert.strictEqual(i.redeemScriptType, 'multisig')
|
||||||
assert.strictEqual(i.signType, 'multisig')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -537,14 +536,15 @@ describe('TransactionBuilder', function () {
|
||||||
txb.addInput('a4696c4b0cd27ec2e173ab1fa7d1cc639a98ee237cec95a77ca7ff4145791529', 1, 0xffffffff, scriptPubKey)
|
txb.addInput('a4696c4b0cd27ec2e173ab1fa7d1cc639a98ee237cec95a77ca7ff4145791529', 1, 0xffffffff, scriptPubKey)
|
||||||
txb.addOutput(scriptPubKey, 99000)
|
txb.addOutput(scriptPubKey, 99000)
|
||||||
txb.sign(0, keyPair, redeemScript, null, 100000, witnessScript)
|
txb.sign(0, keyPair, redeemScript, null, 100000, witnessScript)
|
||||||
|
|
||||||
// 2-of-2 signed only once
|
// 2-of-2 signed only once
|
||||||
const tx = txb.buildIncomplete()
|
const tx = txb.buildIncomplete()
|
||||||
|
|
||||||
// Only input is segwit, so txid should be accurate with the final tx
|
// Only input is segwit, so txid should be accurate with the final tx
|
||||||
assert.equal(tx.getId(), 'f15d0a65b21b4471405b21a099f8b18e1ae4d46d55efbd0f4766cf11ad6cb821')
|
assert.equal(tx.getId(), 'f15d0a65b21b4471405b21a099f8b18e1ae4d46d55efbd0f4766cf11ad6cb821')
|
||||||
|
|
||||||
const txHex = tx.toHex()
|
const txHex = tx.toHex()
|
||||||
const newTxb = TransactionBuilder.fromTransaction(Transaction.fromHex(txHex))
|
TransactionBuilder.fromTransaction(Transaction.fromHex(txHex))
|
||||||
// input should have the key 'witness' set to true
|
|
||||||
assert.equal(newTxb.__inputs[0].witness, true)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle badly pre-filled OP_0s', function () {
|
it('should handle badly pre-filled OP_0s', function () {
|
||||||
|
|
Loading…
Reference in a new issue