|
|
@ -5,181 +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 SIGNABLE.indexOf(type) !== -1 |
|
|
|
} |
|
|
|
|
|
|
|
function supportedP2SHType (type) { |
|
|
|
return P2SH.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 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 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 |
|
|
|
case SCRIPT_TYPES.P2WPKH: { |
|
|
|
const { output, pubkey, signature } = payments.p2wpkh({ witness: witnessStack }) |
|
|
|
|
|
|
|
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 { |
|
|
|
pubKeys: pubKeys, |
|
|
|
signatures: signatures |
|
|
|
} |
|
|
|
} |
|
|
|
function expandInput (scriptSig, witnessStack) { |
|
|
|
if (scriptSig.length === 0 && witnessStack.length === 0) return {} |
|
|
|
return { |
|
|
|
prevOutScript: output, |
|
|
|
prevOutType: SCRIPT_TYPES.P2PKH, |
|
|
|
pubkeys: [pubkey], |
|
|
|
signatures: [signature] |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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 |
|
|
|
} |
|
|
|
case SCRIPT_TYPES.P2PK: { |
|
|
|
const { signature } = payments.p2pk({ input: scriptSig }) |
|
|
|
|
|
|
|
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') |
|
|
|
return { |
|
|
|
prevOutType: SCRIPT_TYPES.P2PK, |
|
|
|
pubkeys: [undefined], |
|
|
|
signatures: [signature] |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (!supportedType(btemplates.classifyOutput(witnessScript))) { |
|
|
|
throw new Error('unsupported witness script') |
|
|
|
} |
|
|
|
case SCRIPT_TYPES.MULTISIG: { |
|
|
|
const { pubkeys, signatures } = payments.p2ms({ |
|
|
|
input: scriptSig, |
|
|
|
output: scriptPubKey |
|
|
|
}, { allowIncomplete: true }) |
|
|
|
|
|
|
|
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') |
|
|
|
return { |
|
|
|
prevOutType: SCRIPT_TYPES.MULTISIG, |
|
|
|
pubkeys: pubkeys, |
|
|
|
signatures: signatures |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
scriptType = scriptTypes.P2PKH |
|
|
|
chunks = witnessStack |
|
|
|
} else if (redeemScript) { |
|
|
|
if (!supportedP2SHType(redeemScriptType)) { |
|
|
|
throw new Error('Bad redeemscript!') |
|
|
|
} |
|
|
|
if (type === SCRIPT_TYPES.P2SH) { |
|
|
|
const { output, redeem } = payments.p2sh({ |
|
|
|
input: scriptSig, |
|
|
|
witness: witnessStack |
|
|
|
}) |
|
|
|
|
|
|
|
script = redeemScript |
|
|
|
scriptType = redeemScriptType |
|
|
|
chunks = scriptSigChunks.slice(0, -1) |
|
|
|
} else { |
|
|
|
prevOutType = scriptType = btemplates.classifyInput(scriptSig) |
|
|
|
chunks = scriptSigChunks |
|
|
|
} |
|
|
|
const outputType = btemplates.classifyOutput(redeem.output) |
|
|
|
const expanded = expandInput(redeem.input, redeem.witness, outputType, redeem.output) |
|
|
|
if (!expanded.prevOutType) return {} |
|
|
|
|
|
|
|
const expanded = extractChunks(scriptType, chunks, script) |
|
|
|
return { |
|
|
|
prevOutScript: output, |
|
|
|
prevOutType: SCRIPT_TYPES.P2SH, |
|
|
|
redeemScript: redeem.output, |
|
|
|
redeemScriptType: expanded.prevOutType, |
|
|
|
witnessScript: expanded.witnessScript, |
|
|
|
witnessScriptType: expanded.witnessScriptType, |
|
|
|
|
|
|
|
const result = { |
|
|
|
pubKeys: expanded.pubKeys, |
|
|
|
signatures: expanded.signatures, |
|
|
|
prevOutScript: prevOutScript, |
|
|
|
prevOutType: prevOutType, |
|
|
|
signType: scriptType, |
|
|
|
signScript: script, |
|
|
|
witness: Boolean(witness) |
|
|
|
pubkeys: expanded.pubkeys, |
|
|
|
signatures: expanded.signatures |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (p2sh) { |
|
|
|
result.redeemScript = redeemScript |
|
|
|
result.redeemScriptType = redeemScriptType |
|
|
|
} |
|
|
|
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, |
|
|
|
|
|
|
|
if (p2wsh) { |
|
|
|
result.witnessScript = witnessScript |
|
|
|
result.witnessScriptType = witnessScriptType |
|
|
|
pubkeys: expanded.pubkeys, |
|
|
|
signatures: expanded.signatures |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return result |
|
|
|
return { |
|
|
|
prevOutType: SCRIPT_TYPES.NONSTANDARD, |
|
|
|
prevOutScript: scriptSig |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 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 |
|
|
|
|
|
|
@ -206,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) |
|
|
|
} |
|
|
|
|
|
|
|
let pubKeys = [] |
|
|
|
|
|
|
|
switch (scriptType) { |
|
|
|
// does our hash160(pubKey) match the output scripts?
|
|
|
|
case scriptTypes.P2PKH: |
|
|
|
if (!ourPubKey) break |
|
|
|
switch (type) { |
|
|
|
case SCRIPT_TYPES.P2PKH: { |
|
|
|
if (!ourPubKey) return { type } |
|
|
|
|
|
|
|
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 } |
|
|
|
|
|
|
|
return { |
|
|
|
type, |
|
|
|
pubkeys: [ourPubKey], |
|
|
|
signatures: [undefined] |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// does our hash160(pubKey) match the output scripts?
|
|
|
|
case scriptTypes.P2WPKH: |
|
|
|
if (!ourPubKey) break |
|
|
|
case SCRIPT_TYPES.P2WPKH: { |
|
|
|
if (!ourPubKey) return { type } |
|
|
|
|
|
|
|
const wpkh1 = scriptChunks[1] |
|
|
|
// 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') |
|
|
|
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 |
|
|
|
} |
|
|
|
|
|
|
|
const chunks = bscript.decompile(input.prevOutScript) |
|
|
|
if (!chunks) throw new Error('Invalid prevOutScript') |
|
|
|
if (!chunks[1].equals(redeemScriptHash)) throw new Error('Inconsistent hash160(redeemScript)') |
|
|
|
} |
|
|
|
} |
|
|
|
return { |
|
|
|
redeemScript: redeemScript, |
|
|
|
redeemScriptType: SCRIPT_TYPES.P2WSH, |
|
|
|
|
|
|
|
function checkP2WSHInput (input, witnessScriptHash) { |
|
|
|
if (input.prevOutType) { |
|
|
|
if (input.prevOutType !== scriptTypes.P2WSH) throw new Error('PrevOutScript must be P2WSH') |
|
|
|
witnessScript: witnessScript, |
|
|
|
witnessScriptType: expanded.type, |
|
|
|
|
|
|
|
const chunks = bscript.decompile(input.prevOutScript) |
|
|
|
if (!chunks) throw new Error('Invalid witnessScript') |
|
|
|
if (!chunks[1].equals(witnessScriptHash)) throw new Error('Inconsistent sha256(witnessScript)') |
|
|
|
} |
|
|
|
} |
|
|
|
prevOutType: SCRIPT_TYPES.P2SH, |
|
|
|
prevOutScript: p2sh.output, |
|
|
|
|
|
|
|
function prepareInput (input, kpPubKey, redeemScript, witnessValue, witnessScript) { |
|
|
|
let expanded |
|
|
|
let prevOutType |
|
|
|
let prevOutScript |
|
|
|
hasWitness: true, |
|
|
|
signScript: witnessScript, |
|
|
|
signType: expanded.type, |
|
|
|
|
|
|
|
let p2sh = false |
|
|
|
let p2shType |
|
|
|
let redeemScriptHash |
|
|
|
pubkeys: expanded.pubkeys, |
|
|
|
signatures: expanded.signatures |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
let witness = false |
|
|
|
let p2wsh = false |
|
|
|
let witnessType |
|
|
|
let witnessScriptHash |
|
|
|
if (redeemScript) { |
|
|
|
const p2sh = payments.p2sh({ redeem: { output: redeemScript } }) |
|
|
|
|
|
|
|
let signType |
|
|
|
let signScript |
|
|
|
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 (redeemScript && witnessScript) { |
|
|
|
redeemScriptHash = bcrypto.hash160(redeemScript) |
|
|
|
witnessScriptHash = bcrypto.sha256(witnessScript) |
|
|
|
checkP2SHInput(input, redeemScriptHash) |
|
|
|
|
|
|
|
if (!redeemScript.equals(btemplates.witnessScriptHash.output.encode(witnessScriptHash))) throw new Error('Witness script inconsistent with redeem script') |
|
|
|
|
|
|
|
expanded = expandOutput(witnessScript, undefined, kpPubKey) |
|
|
|
if (!expanded.pubKeys) throw new Error(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') |
|
|
|
} |
|
|
|
|
|
|
|
if (input.prevOutType === scriptTypes.P2WSH) { |
|
|
|
throw new Error('PrevOutScript is ' + input.prevOutType + ', requires witnessScript') |
|
|
|
} |
|
|
|
|
|
|
|
prevOutType = input.prevOutType |
|
|
|
prevOutScript = input.prevOutScript |
|
|
|
expanded = expandOutput(input.prevOutScript, input.prevOutType, kpPubKey) |
|
|
|
if (!expanded.pubKeys) return |
|
|
|
|
|
|
|
witness = (input.prevOutType === scriptTypes.P2WPKH) |
|
|
|
signType = prevOutType |
|
|
|
signScript = prevOutScript |
|
|
|
} else { |
|
|
|
prevOutScript = btemplates.pubKeyHash.output.encode(bcrypto.hash160(kpPubKey)) |
|
|
|
expanded = expandOutput(prevOutScript, scriptTypes.P2PKH, kpPubKey) |
|
|
|
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 |
|
|
|
} |
|
|
|
|
|
|
|
prevOutType = scriptTypes.P2PKH |
|
|
|
witness = false |
|
|
|
signType = prevOutType |
|
|
|
signScript = prevOutScript |
|
|
|
} |
|
|
|
let signScript = redeemScript |
|
|
|
if (expanded.type === SCRIPT_TYPES.P2WPKH) { |
|
|
|
signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output |
|
|
|
} |
|
|
|
|
|
|
|
if (signType === scriptTypes.P2WPKH) { |
|
|
|
signScript = btemplates.pubKeyHash.output.encode(btemplates.witnessPubKeyHash.output.decode(signScript)) |
|
|
|
} |
|
|
|
return { |
|
|
|
redeemScript: redeemScript, |
|
|
|
redeemScriptType: expanded.type, |
|
|
|
|
|
|
|
if (p2sh) { |
|
|
|
input.redeemScript = redeemScript |
|
|
|
input.redeemScriptType = p2shType |
|
|
|
} |
|
|
|
prevOutType: SCRIPT_TYPES.P2SH, |
|
|
|
prevOutScript: p2sh.output, |
|
|
|
|
|
|
|
hasWitness: expanded.type === SCRIPT_TYPES.P2WPKH, |
|
|
|
signScript: signScript, |
|
|
|
signType: expanded.type, |
|
|
|
|
|
|
|
if (p2wsh) { |
|
|
|
input.witnessScript = witnessScript |
|
|
|
input.witnessScriptType = witnessType |
|
|
|
pubkeys: expanded.pubkeys, |
|
|
|
signatures: expanded.signatures |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
input.pubKeys = expanded.pubKeys |
|
|
|
input.signatures = expanded.signatures |
|
|
|
input.signScript = signScript |
|
|
|
input.signType = signType |
|
|
|
input.prevOutScript = prevOutScript |
|
|
|
input.prevOutType = prevOutType |
|
|
|
input.witness = witness |
|
|
|
} |
|
|
|
if (witnessScript) { |
|
|
|
const p2wsh = payments.p2wsh({ redeem: { output: witnessScript } }) |
|
|
|
|
|
|
|
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 }) |
|
|
|
} |
|
|
|
if (input.prevOutScript) { |
|
|
|
const p2wshAlt = payments.p2wsh({ output: input.prevOutScript }) |
|
|
|
if (!p2wsh.hash.equals(p2wshAlt.hash)) throw new Error('Witness script inconsistent with prevOutScript') |
|
|
|
} |
|
|
|
|
|
|
|
return btemplates.multisig.input.encodeStack(signatures) |
|
|
|
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 |
|
|
|
} |
|
|
|
} else { |
|
|
|
throw new Error('Not yet supported') |
|
|
|
} |
|
|
|
|
|
|
|
if (!allowIncomplete) throw new Error('Not enough signatures provided') |
|
|
|
return [] |
|
|
|
} |
|
|
|
return { |
|
|
|
witnessScript: witnessScript, |
|
|
|
witnessScriptType: expanded.type, |
|
|
|
|
|
|
|
function buildInput (input, allowIncomplete) { |
|
|
|
let scriptType = input.prevOutType |
|
|
|
let sig = [] |
|
|
|
let witness = [] |
|
|
|
prevOutType: SCRIPT_TYPES.P2WSH, |
|
|
|
prevOutScript: p2wsh.output, |
|
|
|
|
|
|
|
if (supportedType(scriptType)) { |
|
|
|
sig = buildStack(scriptType, input.signatures, input.pubKeys, allowIncomplete) |
|
|
|
} |
|
|
|
hasWitness: true, |
|
|
|
signScript: witnessScript, |
|
|
|
signType: expanded.type, |
|
|
|
|
|
|
|
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') |
|
|
|
pubkeys: expanded.pubkeys, |
|
|
|
signatures: expanded.signatures |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (supportedType(input.redeemScriptType)) { |
|
|
|
sig = buildStack(input.redeemScriptType, input.signatures, input.pubKeys, allowIncomplete) |
|
|
|
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 |
|
|
|
} |
|
|
|
|
|
|
|
// If it wasn't SIGNABLE, it's witness, defer to that
|
|
|
|
if (input.redeemScriptType) { |
|
|
|
p2sh = true |
|
|
|
scriptType = input.redeemScriptType |
|
|
|
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 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
switch (scriptType) { |
|
|
|
// P2WPKH is a special case of P2PKH
|
|
|
|
case btemplates.types.P2WPKH: |
|
|
|
witness = buildStack(btemplates.types.P2PKH, input.signatures, input.pubKeys, allowIncomplete) |
|
|
|
break |
|
|
|
|
|
|
|
case btemplates.types.P2WSH: |
|
|
|
// We can remove this check later
|
|
|
|
if (!allowIncomplete && !supportedType(input.witnessScriptType)) { |
|
|
|
throw new Error('Impossible to sign this type') |
|
|
|
} |
|
|
|
const prevOutScript = payments.p2pkh({ pubkey: ourPubKey }).output |
|
|
|
return { |
|
|
|
prevOutType: SCRIPT_TYPES.P2PKH, |
|
|
|
prevOutScript: prevOutScript, |
|
|
|
|
|
|
|
if (supportedType(input.witnessScriptType)) { |
|
|
|
witness = buildStack(input.witnessScriptType, input.signatures, input.pubKeys, allowIncomplete) |
|
|
|
witness.push(input.witnessScript) |
|
|
|
scriptType = input.witnessScriptType |
|
|
|
} |
|
|
|
hasWitness: false, |
|
|
|
signScript: prevOutScript, |
|
|
|
signType: SCRIPT_TYPES.P2PKH, |
|
|
|
|
|
|
|
break |
|
|
|
pubkeys: [ourPubKey], |
|
|
|
signatures: [undefined] |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// append redeemScript if necessary
|
|
|
|
if (p2sh) { |
|
|
|
sig.push(input.redeemScript) |
|
|
|
} |
|
|
|
function build (type, input, allowIncomplete) { |
|
|
|
const pubkeys = input.pubkeys || [] |
|
|
|
let signatures = input.signatures || [] |
|
|
|
|
|
|
|
return { |
|
|
|
type: scriptType, |
|
|
|
script: bscript.compile(sig), |
|
|
|
witness: witness |
|
|
|
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 |
|
|
|
} |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@ -583,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 |
|
|
@ -631,20 +579,19 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) { |
|
|
|
} |
|
|
|
|
|
|
|
const tx = this.__tx.clone() |
|
|
|
// Create script signatures from inputs
|
|
|
|
|
|
|
|
// 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') |
|
|
|
} |
|
|
|
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) |
|
|
|
}) |
|
|
|
|
|
|
@ -659,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 |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
@ -686,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') |
|
|
@ -694,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) |
|
|
|