Commit work to date
This commit is contained in:
parent
b24dc44770
commit
a213435135
3 changed files with 179 additions and 35 deletions
|
@ -14,9 +14,139 @@ var ECPair = require('./ecpair')
|
||||||
var ECSignature = require('./ecsignature')
|
var ECSignature = require('./ecsignature')
|
||||||
var Transaction = require('./transaction')
|
var Transaction = require('./transaction')
|
||||||
|
|
||||||
|
function extractChunks (type, chunks) {
|
||||||
|
var pubKeys = []
|
||||||
|
var signatures = []
|
||||||
|
switch (type) {
|
||||||
|
case scriptTypes.P2PKH:
|
||||||
|
// if (redeemScript) throw new Error('Nonstandard... P2SH(P2PKH)')
|
||||||
|
pubKeys = chunks.slice(1)
|
||||||
|
signatures = chunks.slice(0, 1)
|
||||||
|
break
|
||||||
|
|
||||||
|
case scriptTypes.P2PK:
|
||||||
|
pubKeys[0] = null
|
||||||
|
signatures = chunks.slice(0, 1)
|
||||||
|
break
|
||||||
|
|
||||||
|
case scriptTypes.MULTISIG:
|
||||||
|
signatures = chunks.slice(1).map(function (chunk) {
|
||||||
|
return chunk === ops.OP_0 ? undefined : chunk
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
pubKeys: pubKeys,
|
||||||
|
signatures: signatures
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function expandInput (scriptSig, redeemScript, witnessStack) {
|
||||||
|
var prevOutScript
|
||||||
|
var prevOutType
|
||||||
|
var witnessScript
|
||||||
|
var witnessScriptType
|
||||||
|
var witness = false
|
||||||
|
var p2wsh = false
|
||||||
|
var p2sh = false
|
||||||
|
var witnessProgram
|
||||||
|
|
||||||
|
var classifyWitness = bscript.classifyWitness(witnessStack);
|
||||||
|
if (classifyWitness === scriptTypes.P2WSH) {
|
||||||
|
witnessScript = witnessStack[witnessStack.length - 1]
|
||||||
|
witnessScriptType = bscript.classifyOutput(witnessScript)
|
||||||
|
p2wsh = true
|
||||||
|
if (scriptSig.length === 0) {
|
||||||
|
prevOutScript = bscript.witnessScriptHash.output.encode(bcrypto.sha256(witnessScript))
|
||||||
|
// bare witness
|
||||||
|
} else {
|
||||||
|
if (!redeemScript) {
|
||||||
|
throw new Error('No redeemScript provided for P2WSH, but scriptSig wasn\'t empty')
|
||||||
|
}
|
||||||
|
witnessProgram = bscript.witnessScriptHash.output.encode(bcrypto.sha256(witnessScript))
|
||||||
|
if (!redeemScript.equals(witnessProgram)) {
|
||||||
|
throw new Error('Redeem script didn\'t match witnessScript')
|
||||||
|
}
|
||||||
|
prevOutScript = bscript.scriptHash.output.encode(bscript.hash160(witnessProgram))
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(bscript.classifyOutput(witnessScript))
|
||||||
|
console.log(SIGNABLE.indexOf(bscript.classifyOutput(witnessScript)))
|
||||||
|
if (SIGNABLE.indexOf(bscript.classifyOutput(witnessScript)) === -1) {
|
||||||
|
throw new Error('unsupported witness script')
|
||||||
|
}
|
||||||
|
} else if (classifyWitness === scriptTypes.P2WPKH) {
|
||||||
|
var keyHash = witnessStack[witnessStack.length - 1]
|
||||||
|
if (scriptSig.length === 0) {
|
||||||
|
prevOutScript = bscript.witnessPubKeyHash.output.encode(keyHash)
|
||||||
|
// bare witness
|
||||||
|
} else {
|
||||||
|
if (!redeemScript) {
|
||||||
|
throw new Error('No redeemScript provided for P2WPKH, but scriptSig wasn\'t empty');
|
||||||
|
}
|
||||||
|
witnessProgram = bscript.witnessPubKeyHash.output.encode(keyHash)
|
||||||
|
if (!redeemScript.equals(witnessProgram)) {
|
||||||
|
throw new Error('Redeem script did not have the right witness program')
|
||||||
|
}
|
||||||
|
prevOutScript = bscript.scriptHash.output.encode(bcrypto.hash160(witnessProgram));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof prevOutScript === 'undefined' && redeemScript) {
|
||||||
|
prevOutScript = bscript.scriptHash.output.encode(bcrypto.hash160(redeemScript))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof prevOutScript === 'undefined' && scriptSig) {
|
||||||
|
prevOutType = bscript.classifyInput(scriptSig)
|
||||||
|
if (!(scriptTypes.P2SH === prevOutType || P2SH.indexOf(prevOutType) !== -1)) {
|
||||||
|
throw new Error('Unsupported scriptSig')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var scriptType = bscript.classifyOutput(prevOutScript)
|
||||||
|
var redeemScriptType
|
||||||
|
var chunks = bscript.toStack(scriptSig)
|
||||||
|
if (scriptType === scriptTypes.P2SH) {
|
||||||
|
p2sh = true
|
||||||
|
scriptType = redeemScriptType = bscript.classifyOutput(redeemScript)
|
||||||
|
if (P2SH.indexOf(scriptType) === -1) {
|
||||||
|
throw new Error('P2SH script not supported ' + scriptType)
|
||||||
|
}
|
||||||
|
chunks = chunks.slice(0, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scriptType === scriptTypes.P2WSH) {
|
||||||
|
chunks = witnessStack.slice(0, -1)
|
||||||
|
scriptType = bscript.classifyOutput(witnessScript)
|
||||||
|
} else if (scriptType === scriptTypes.P2WPKH) {
|
||||||
|
chunks = witnessStack
|
||||||
|
}
|
||||||
|
|
||||||
|
var expanded = extractChunks(scriptType, chunks)
|
||||||
|
|
||||||
|
var result = {
|
||||||
|
pubKeys: expanded.pubKeys,
|
||||||
|
signatures: expanded.signatures,
|
||||||
|
prevOutScript: prevOutScript,
|
||||||
|
prevOutType: prevOutType
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p2sh) {
|
||||||
|
result.redeemScript = redeemScript
|
||||||
|
result.redeemScriptType = redeemScriptType
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p2wsh) {
|
||||||
|
result.witnessScript = witnessScript
|
||||||
|
result.witnessScriptType = witnessScriptType
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// inspects a scriptSig w/ optional redeemScript and
|
// inspects a scriptSig w/ optional redeemScript and
|
||||||
// derives any input information required
|
// derives any input information required
|
||||||
function expandInput (scriptSig, redeemScript, witnessStack) {
|
function expandInput2 (scriptSig, redeemScript, witnessStack) {
|
||||||
var witnessType
|
var witnessType
|
||||||
if (witnessStack) {
|
if (witnessStack) {
|
||||||
witnessType = bscript.classifyWitness(witnessStack)
|
witnessType = bscript.classifyWitness(witnessStack)
|
||||||
|
@ -53,33 +183,6 @@ function expandInput (scriptSig, redeemScript, witnessStack) {
|
||||||
signatures = witnessStack.slice(0, 1)
|
signatures = witnessStack.slice(0, 1)
|
||||||
break
|
break
|
||||||
|
|
||||||
case scriptTypes.P2PKH:
|
|
||||||
// if (redeemScript) throw new Error('Nonstandard... P2SH(P2PKH)')
|
|
||||||
pubKeys = scriptSigChunks.slice(1)
|
|
||||||
signatures = scriptSigChunks.slice(0, 1)
|
|
||||||
|
|
||||||
if (redeemScript) break
|
|
||||||
prevOutScript = bscript.pubKeyHash.output.encode(bcrypto.hash160(pubKeys[0]))
|
|
||||||
break
|
|
||||||
|
|
||||||
case scriptTypes.P2PK:
|
|
||||||
if (redeemScript) {
|
|
||||||
pubKeys = bscript.decompile(redeemScript).slice(0, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
signatures = scriptSigChunks.slice(0, 1)
|
|
||||||
break
|
|
||||||
|
|
||||||
case scriptTypes.MULTISIG:
|
|
||||||
if (redeemScript) {
|
|
||||||
pubKeys = bscript.decompile(redeemScript).slice(1, -2)
|
|
||||||
}
|
|
||||||
|
|
||||||
signatures = scriptSigChunks.slice(1).map(function (chunk) {
|
|
||||||
return chunk === ops.OP_0 ? undefined : chunk
|
|
||||||
})
|
|
||||||
break
|
|
||||||
|
|
||||||
case scriptTypes.NONSTANDARD:
|
case scriptTypes.NONSTANDARD:
|
||||||
return { prevOutType: prevOutType, prevOutScript: EMPTY_SCRIPT }
|
return { prevOutType: prevOutType, prevOutScript: EMPTY_SCRIPT }
|
||||||
|
|
||||||
|
@ -190,7 +293,7 @@ function checkP2WSHInput (input, witnessScriptHash) {
|
||||||
if (input.prevOutType !== scriptTypes.P2WSH) throw new Error('PrevOutScript must be P2WSH')
|
if (input.prevOutType !== scriptTypes.P2WSH) throw new Error('PrevOutScript must be P2WSH')
|
||||||
|
|
||||||
var scriptHash = bscript.decompile(input.prevOutScript)[1]
|
var scriptHash = bscript.decompile(input.prevOutScript)[1]
|
||||||
if (!scriptHash.equals(witnessScriptHash)) throw new Error('Inconsistent hash160(WitnessScript)')
|
if (!scriptHash.equals(witnessScriptHash)) throw new Error('Inconsistent sha25(WitnessScript)')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +338,7 @@ function prepareInput (input, kpPubKey, redeemScript, witnessValue, witnessScrip
|
||||||
p2sh = true
|
p2sh = true
|
||||||
p2shType = expanded.scriptType
|
p2shType = expanded.scriptType
|
||||||
} else if (witnessScript) {
|
} else if (witnessScript) {
|
||||||
witnessScriptHash = bcrypto.hash256(witnessScript)
|
witnessScriptHash = bcrypto.sha256(witnessScript)
|
||||||
checkP2WSHInput(input, witnessScriptHash)
|
checkP2WSHInput(input, witnessScriptHash)
|
||||||
|
|
||||||
expanded = expandOutput(witnessScript, undefined, kpPubKey)
|
expanded = expandOutput(witnessScript, undefined, kpPubKey)
|
||||||
|
@ -259,7 +362,7 @@ function prepareInput (input, kpPubKey, redeemScript, witnessValue, witnessScrip
|
||||||
|
|
||||||
witness = (input.prevOutScript === scriptTypes.P2WPKH)
|
witness = (input.prevOutScript === scriptTypes.P2WPKH)
|
||||||
} else {
|
} else {
|
||||||
prevOutScript = bscript.pubKeyHash.output.encode(bcrypto.hash160(kpPubKey))
|
prevOutScript = bscript.witnessPubKeyHash.output.encode(bcrypto.hash160(kpPubKey))
|
||||||
expanded = expandOutput(prevOutScript, scriptTypes.P2PKH, kpPubKey)
|
expanded = expandOutput(prevOutScript, scriptTypes.P2PKH, kpPubKey)
|
||||||
prevOutType = scriptTypes.P2PKH
|
prevOutType = scriptTypes.P2PKH
|
||||||
witness = false
|
witness = false
|
||||||
|
@ -402,11 +505,13 @@ TransactionBuilder.fromTransaction = function (transaction, network) {
|
||||||
|
|
||||||
// Copy inputs
|
// Copy inputs
|
||||||
transaction.ins.forEach(function (txIn) {
|
transaction.ins.forEach(function (txIn) {
|
||||||
|
console.log('add input')
|
||||||
txb.__addInputUnsafe(txIn.hash, txIn.index, {
|
txb.__addInputUnsafe(txIn.hash, txIn.index, {
|
||||||
sequence: txIn.sequence,
|
sequence: txIn.sequence,
|
||||||
script: txIn.script,
|
script: txIn.script,
|
||||||
witness: txIn.witness
|
witness: txIn.witness
|
||||||
})
|
})
|
||||||
|
console.log('done input')
|
||||||
})
|
})
|
||||||
|
|
||||||
// fix some things not possible through the public API
|
// fix some things not possible through the public API
|
||||||
|
@ -457,6 +562,7 @@ TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, options)
|
||||||
|
|
||||||
// derive what we can from the scriptSig
|
// derive what we can from the scriptSig
|
||||||
if (options.script !== undefined) {
|
if (options.script !== undefined) {
|
||||||
|
console.log('options.script provided, so peek')
|
||||||
input = expandInput(options.script, null, options.witness)
|
input = expandInput(options.script, null, options.witness)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -485,6 +591,7 @@ TransactionBuilder.prototype.__addInputUnsafe = function (txHash, vout, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
var vin = this.tx.addInput(txHash, vout, options.sequence, options.scriptSig)
|
var vin = this.tx.addInput(txHash, vout, options.sequence, options.scriptSig)
|
||||||
|
console.log(this.tx)
|
||||||
this.inputs[vin] = input
|
this.inputs[vin] = input
|
||||||
this.prevTxMap[prevTxOut] = vin
|
this.prevTxMap[prevTxOut] = vin
|
||||||
|
|
||||||
|
@ -520,7 +627,9 @@ TransactionBuilder.prototype.__build = function (allowIncomplete) {
|
||||||
var tx = this.tx.clone()
|
var tx = this.tx.clone()
|
||||||
// Create script signatures from inputs
|
// Create script signatures from inputs
|
||||||
this.inputs.forEach(function (input, i) {
|
this.inputs.forEach(function (input, i) {
|
||||||
var scriptType = input.redeemScriptType || input.prevOutType
|
console.log(input)
|
||||||
|
var scriptType = input.witnessScriptType || input.redeemScriptType || input.prevOutType
|
||||||
|
console.log(scriptType)
|
||||||
if (!scriptType && !allowIncomplete) throw new Error('Transaction is not complete')
|
if (!scriptType && !allowIncomplete) throw new Error('Transaction is not complete')
|
||||||
var result = buildInput(input, allowIncomplete)
|
var result = buildInput(input, allowIncomplete)
|
||||||
|
|
||||||
|
@ -576,11 +685,12 @@ TransactionBuilder.prototype.sign = function (vin, keyPair, redeemScript, hashTy
|
||||||
}
|
}
|
||||||
|
|
||||||
// ready to sign
|
// ready to sign
|
||||||
var hashScript = input.redeemScript || input.prevOutScript
|
var hashScript = input.witnessScript || input.redeemScript || input.prevOutScript
|
||||||
|
|
||||||
var signatureHash
|
var signatureHash
|
||||||
if (input.witness) {
|
if (input.witness) {
|
||||||
signatureHash = this.tx.hashForWitnessV0(vin, hashScript, witnessValue, hashType)
|
signatureHash = this.tx.hashForWitnessV0(vin, hashScript, witnessValue, hashType)
|
||||||
|
console.log(hashScript);
|
||||||
} else {
|
} else {
|
||||||
signatureHash = this.tx.hashForSignature(vin, hashScript, hashType)
|
signatureHash = this.tx.hashForSignature(vin, hashScript, hashType)
|
||||||
}
|
}
|
||||||
|
|
26
test/fixtures/transaction_builder.json
vendored
26
test/fixtures/transaction_builder.json
vendored
|
@ -400,7 +400,33 @@
|
||||||
"value": 10000
|
"value": 10000
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"description": "Transaction w/ P2WSH P2PK -> P2PKH",
|
||||||
|
"txHex": "010000000001014533a3bc1e039bd787656068e135aaee10aee95a64776bfc047ee6a7c1ebdd2f0000000000ffffffff0160ea0000000000001976a914851a33a5ef0d4279bd5854949174e2c65b1d450088ac02473044022039725bb7291a14dd182dafdeaf3ea0d5c05c34f4617ccbaa46522ca913995c4e02203b170d072ed2e489e7424ad96d8fa888deb530be2d4c5d9aaddf111a7efdb2d3012321038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2bac00000000",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"txId": "2fddebc1a7e67e04fc6b77645ae9ae10eeaa35e168606587d79b031ebca33345",
|
||||||
|
"vout": 0,
|
||||||
|
"prevTxScript": "OP_0 0f9ea7bae7166c980169059e39443ed13324495b0d6678ce716262e879591210",
|
||||||
|
"signs": [
|
||||||
|
{
|
||||||
|
"keyPair": "L2FroWqrUgsPpTMhpXcAFnVDLPTToDbveh3bhDaU4jhe7Cw6YujN",
|
||||||
|
"witnessScript": "038de63cf582d058a399a176825c045672d5ff8ea25b64d28d4375dcdb14c02b2b OP_CHECKSIG",
|
||||||
|
"value": 80000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"script": "OP_DUP OP_HASH160 851a33a5ef0d4279bd5854949174e2c65b1d4500 OP_EQUALVERIFY OP_CHECKSIG",
|
||||||
|
"value": 60000
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
],
|
],
|
||||||
"fromTransaction": [
|
"fromTransaction": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -55,12 +55,19 @@ function construct (f, dontSign) {
|
||||||
input.signs.forEach(function (sign) {
|
input.signs.forEach(function (sign) {
|
||||||
var keyPair = ECPair.fromWIF(sign.keyPair, network)
|
var keyPair = ECPair.fromWIF(sign.keyPair, network)
|
||||||
var redeemScript
|
var redeemScript
|
||||||
|
var witnessScript
|
||||||
|
var value
|
||||||
if (sign.redeemScript) {
|
if (sign.redeemScript) {
|
||||||
redeemScript = bscript.fromASM(sign.redeemScript)
|
redeemScript = bscript.fromASM(sign.redeemScript)
|
||||||
}
|
}
|
||||||
|
if (sign.value) {
|
||||||
|
value = sign.value
|
||||||
|
}
|
||||||
|
if (sign.witnessScript) {
|
||||||
|
witnessScript = bscript.fromASM(sign.witnessScript)
|
||||||
|
}
|
||||||
|
|
||||||
txb.sign(index, keyPair, redeemScript, sign.hashType)
|
txb.sign(index, keyPair, redeemScript, sign.hashType, value, witnessScript)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -82,6 +89,7 @@ describe('TransactionBuilder', function () {
|
||||||
fixtures.valid.build.forEach(function (f) {
|
fixtures.valid.build.forEach(function (f) {
|
||||||
it('returns TransactionBuilder, with ' + f.description, function () {
|
it('returns TransactionBuilder, with ' + f.description, function () {
|
||||||
var network = NETWORKS[f.network || 'bitcoin']
|
var network = NETWORKS[f.network || 'bitcoin']
|
||||||
|
|
||||||
var tx = Transaction.fromHex(f.txHex)
|
var tx = Transaction.fromHex(f.txHex)
|
||||||
var txb = TransactionBuilder.fromTransaction(tx, network)
|
var txb = TransactionBuilder.fromTransaction(tx, network)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue