messy commits, clean later, just one test left

This commit is contained in:
Thomas Kerin 2017-01-03 17:41:34 +01:00 committed by Daniel Cousens
parent a213435135
commit f8a94f3496
2 changed files with 80 additions and 112 deletions

View file

@ -8,13 +8,12 @@ var types = require('./types')
var scriptTypes = bscript.types var scriptTypes = bscript.types
var SIGNABLE = [bscript.types.P2PKH, bscript.types.P2PK, bscript.types.MULTISIG] var SIGNABLE = [bscript.types.P2PKH, bscript.types.P2PK, bscript.types.MULTISIG]
var P2SH = SIGNABLE.concat([bscript.types.P2WPKH, bscript.types.P2WSH]) var P2SH = SIGNABLE.concat([bscript.types.P2WPKH, bscript.types.P2WSH])
var EMPTY_SCRIPT = new Buffer(0)
var ECPair = require('./ecpair') var ECPair = require('./ecpair')
var ECSignature = require('./ecsignature') var ECSignature = require('./ecsignature')
var Transaction = require('./transaction') var Transaction = require('./transaction')
function extractChunks (type, chunks) { function extractChunks (type, chunks, script) {
var pubKeys = [] var pubKeys = []
var signatures = [] var signatures = []
switch (type) { switch (type) {
@ -25,110 +24,136 @@ function extractChunks (type, chunks) {
break break
case scriptTypes.P2PK: case scriptTypes.P2PK:
pubKeys[0] = null pubKeys[0] = script ? bscript.pubKey.output.decode(script) : undefined
signatures = chunks.slice(0, 1) signatures = chunks.slice(0, 1)
break break
case scriptTypes.MULTISIG: case scriptTypes.MULTISIG:
if (script) {
var multisig = bscript.multisig.output.decode(script)
pubKeys = multisig.pubKeys
}
signatures = chunks.slice(1).map(function (chunk) { signatures = chunks.slice(1).map(function (chunk) {
return chunk === ops.OP_0 ? undefined : chunk return chunk.length === 0 ? undefined : chunk
}) })
break break
default:
throw new Error('Only bare scripts can be handled here: not ', type)
} }
return { return {
pubKeys: pubKeys, pubKeys: pubKeys,
signatures: signatures signatures: signatures
} }
} }
function expandInput (scriptSig, witnessStack) {
function expandInput (scriptSig, redeemScript, witnessStack) {
var prevOutScript var prevOutScript
var prevOutType var prevOutType
var scriptType
var script
var redeemScript
var witnessScript var witnessScript
var witnessScriptType var witnessScriptType
var redeemScriptType
var witness = false var witness = false
var p2wsh = false var p2wsh = false
var p2sh = false var p2sh = false
var witnessProgram var witnessProgram
var chunks
var classifyWitness = bscript.classifyWitness(witnessStack); var scriptSigChunks = bscript.decompile(scriptSig)
var sigType = bscript.classifyInput(scriptSigChunks, true)
if (sigType === scriptTypes.P2SH) {
p2sh = true
redeemScript = scriptSigChunks[scriptSigChunks.length - 1]
redeemScriptType = bscript.classifyOutput(redeemScript)
prevOutScript = bscript.scriptHash.output.encode(bcrypto.hash160(redeemScript))
prevOutType = scriptTypes.P2SH
script = redeemScript
}
var classifyWitness = bscript.classifyWitness(witnessStack)
if (classifyWitness === scriptTypes.P2WSH) { if (classifyWitness === scriptTypes.P2WSH) {
witnessScript = witnessStack[witnessStack.length - 1] witnessScript = witnessStack[witnessStack.length - 1]
witnessScriptType = bscript.classifyOutput(witnessScript) witnessScriptType = bscript.classifyOutput(witnessScript)
p2wsh = true p2wsh = true
if (scriptSig.length === 0) { if (scriptSig.length === 0) {
prevOutScript = bscript.witnessScriptHash.output.encode(bcrypto.sha256(witnessScript)) prevOutScript = bscript.witnessScriptHash.output.encode(bcrypto.sha256(witnessScript))
prevOutType = scriptTypes.P2WSH
if (typeof redeemScript !== 'undefined') {
throw new Error('Redeem script given when unnecessary')
}
// bare witness // bare witness
} else { } else {
if (!redeemScript) { if (!redeemScript) {
throw new Error('No redeemScript provided for P2WSH, but scriptSig wasn\'t empty') throw new Error('No redeemScript provided for P2WSH, but scriptSig non-empty')
} }
witnessProgram = bscript.witnessScriptHash.output.encode(bcrypto.sha256(witnessScript)) witnessProgram = bscript.witnessScriptHash.output.encode(bcrypto.sha256(witnessScript))
if (!redeemScript.equals(witnessProgram)) { if (!redeemScript.equals(witnessProgram)) {
throw new Error('Redeem script didn\'t match witnessScript') throw new Error('Redeem script didn\'t match witnessScript')
} }
prevOutScript = bscript.scriptHash.output.encode(bscript.hash160(witnessProgram)) prevOutScript = bscript.scriptHash.output.encode(bcrypto.hash160(witnessProgram))
prevOutType = scriptTypes.P2SH
} }
console.log(bscript.classifyOutput(witnessScript))
console.log(SIGNABLE.indexOf(bscript.classifyOutput(witnessScript)))
if (SIGNABLE.indexOf(bscript.classifyOutput(witnessScript)) === -1) { if (SIGNABLE.indexOf(bscript.classifyOutput(witnessScript)) === -1) {
throw new Error('unsupported witness script') throw new Error('unsupported witness script')
} }
script = witnessScript
scriptType = witnessScriptType
chunks = witnessStack.slice(0, -1)
} else if (classifyWitness === scriptTypes.P2WPKH) { } else if (classifyWitness === scriptTypes.P2WPKH) {
var keyHash = witnessStack[witnessStack.length - 1] var key = witnessStack[witnessStack.length - 1]
var keyHash = bcrypto.hash160(key)
if (scriptSig.length === 0) { if (scriptSig.length === 0) {
prevOutScript = bscript.witnessPubKeyHash.output.encode(keyHash) prevOutScript = bscript.witnessPubKeyHash.output.encode(keyHash)
// bare witness prevOutType = scriptTypes.P2WPKH
if (typeof redeemScript !== 'undefined') {
throw new Error('Redeem script given when unnecessary')
}
} else { } else {
if (!redeemScript) { if (!redeemScript) {
throw new Error('No redeemScript provided for P2WPKH, but scriptSig wasn\'t empty'); throw new Error('No redeemScript provided for P2WPKH, but scriptSig wasn\'t empty')
} }
witnessProgram = bscript.witnessPubKeyHash.output.encode(keyHash) witnessProgram = bscript.witnessPubKeyHash.output.encode(keyHash)
if (!redeemScript.equals(witnessProgram)) { if (!redeemScript.equals(witnessProgram)) {
throw new Error('Redeem script did not have the right witness program') throw new Error('Redeem script did not have the right witness program')
} }
prevOutScript = bscript.scriptHash.output.encode(bcrypto.hash160(witnessProgram)); prevOutScript = bscript.scriptHash.output.encode(bcrypto.hash160(witnessProgram))
} prevOutType = scriptTypes.P2SH
} }
if (typeof prevOutScript === 'undefined' && redeemScript) { scriptType = scriptTypes.P2PKH
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 chunks = witnessStack
} else if (redeemScript) {
redeemScriptType = bscript.classifyOutput(redeemScript)
if (P2SH.indexOf(redeemScriptType) === -1) {
throw new Error('Bad redeemscript!')
} }
var expanded = extractChunks(scriptType, chunks) script = redeemScript
scriptType = redeemScriptType
prevOutType = scriptTypes.P2SH
prevOutScript = bscript.scriptHash.output.encode(bcrypto.hash160(redeemScript))
chunks = scriptSigChunks.slice(0, -1)
} else {
var bareType = bscript.classifyInput(scriptSig)
prevOutType = scriptType = bareType
chunks = scriptSigChunks
}
if (SIGNABLE.indexOf(scriptType) === -1) {
throw new Error(scriptType + ' not supported')
}
var expanded = extractChunks(scriptType, chunks, script)
var result = { var result = {
pubKeys: expanded.pubKeys, pubKeys: expanded.pubKeys,
signatures: expanded.signatures, signatures: expanded.signatures,
prevOutScript: prevOutScript, prevOutScript: prevOutScript,
prevOutType: prevOutType prevOutType: prevOutType,
witness: Boolean(witness)
} }
if (p2sh) { if (p2sh) {
@ -144,60 +169,6 @@ function expandInput (scriptSig, redeemScript, witnessStack) {
return result return result
} }
// inspects a scriptSig w/ optional redeemScript and
// derives any input information required
function expandInput2 (scriptSig, redeemScript, witnessStack) {
var witnessType
if (witnessStack) {
witnessType = bscript.classifyWitness(witnessStack)
}
var prevOutType, scriptSigChunks
if (scriptSig.length === 0 && witnessStack) {
prevOutType = witnessType
} else {
scriptSigChunks = bscript.decompile(scriptSig)
prevOutType = bscript.classifyInput(scriptSigChunks, true)
}
var pubKeys, signatures, prevOutScript
switch (prevOutType) {
case scriptTypes.P2SH:
// FIXME: maybe depth limit instead, how possible is this anyway?
if (redeemScript) throw new Error('Recursive P2SH script')
var redeemScriptSig = scriptSigChunks.slice(0, -1)
redeemScript = scriptSigChunks[scriptSigChunks.length - 1]
var result = expandInput(redeemScriptSig, redeemScript)
result.redeemScript = redeemScript
result.redeemScriptType = result.prevOutType
result.prevOutScript = bscript.scriptHash.output.encode(bcrypto.hash160(redeemScript))
result.prevOutType = scriptTypes.P2SH
result.witness = false
return result
case scriptTypes.P2WPKH:
pubKeys = witnessStack.slice(1)
signatures = witnessStack.slice(0, 1)
break
case scriptTypes.NONSTANDARD:
return { prevOutType: prevOutType, prevOutScript: EMPTY_SCRIPT }
default: return {}
}
return {
pubKeys: pubKeys,
signatures: signatures,
prevOutScript: prevOutScript,
prevOutType: prevOutType,
witness: Boolean(witnessStack)
}
}
// 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 !== scriptTypes.MULTISIG || !input.redeemScript) return
@ -233,6 +204,7 @@ function fixMultisigOrder (input, transaction, vin) {
} }
function expandOutput (script, scriptType, ourPubKey) { function expandOutput (script, scriptType, ourPubKey) {
console.log('expandOutput: ', script.toString('hex'), scriptType, ourPubKey)
typeforce(types.Buffer, script) typeforce(types.Buffer, script)
var scriptChunks = bscript.decompile(script) var scriptChunks = bscript.decompile(script)
@ -242,9 +214,11 @@ function expandOutput (script, scriptType, ourPubKey) {
var pubKeys = [] var pubKeys = []
console.log(scriptType)
switch (scriptType) { switch (scriptType) {
// does our hash160(pubKey) match the output scripts? // does our hash160(pubKey) match the output scripts?
case scriptTypes.P2PKH: case scriptTypes.P2PKH:
console.log(scriptChunks)
if (!ourPubKey) break if (!ourPubKey) break
var pkh1 = scriptChunks[2] var pkh1 = scriptChunks[2]
@ -362,7 +336,8 @@ function prepareInput (input, kpPubKey, redeemScript, witnessValue, witnessScrip
witness = (input.prevOutScript === scriptTypes.P2WPKH) witness = (input.prevOutScript === scriptTypes.P2WPKH)
} else { } else {
prevOutScript = bscript.witnessPubKeyHash.output.encode(bcrypto.hash160(kpPubKey)) prevOutScript = bscript.pubKeyHash.output.encode(bcrypto.hash160(kpPubKey))
console.log(prevOutScript)
expanded = expandOutput(prevOutScript, scriptTypes.P2PKH, kpPubKey) expanded = expandOutput(prevOutScript, scriptTypes.P2PKH, kpPubKey)
prevOutType = scriptTypes.P2PKH prevOutType = scriptTypes.P2PKH
witness = false witness = false
@ -387,10 +362,10 @@ function prepareInput (input, kpPubKey, redeemScript, witnessValue, witnessScrip
function buildStack (type, signatures, pubKeys, allowIncomplete) { function buildStack (type, signatures, pubKeys, allowIncomplete) {
if (type === scriptTypes.P2PKH) { if (type === scriptTypes.P2PKH) {
if (signatures.length < 1 || !signatures[0]) throw new Error('Not enough signatures provided') if (!allowIncomplete && (signatures.length < 1 || !signatures[0])) throw new Error('Not enough signatures provided')
return bscript.pubKeyHash.input.encodeStack(signatures[0], pubKeys[0]) return bscript.pubKeyHash.input.encodeStack(signatures[0], pubKeys[0])
} else if (type === scriptTypes.P2PK) { } else if (type === scriptTypes.P2PK) {
if (signatures.length < 1 || !signatures[0]) throw new Error('Not enough signatures provided') if (!allowIncomplete && (signatures.length < 1 || !signatures[0])) throw new Error('Not enough signatures provided')
return bscript.pubKey.input.encodeStack(signatures[0]) return bscript.pubKey.input.encodeStack(signatures[0])
} else { } else {
signatures = signatures.map(function (signature) { signatures = signatures.map(function (signature) {
@ -417,7 +392,7 @@ function buildInput (input, allowIncomplete) {
var p2sh = false var p2sh = false
if (scriptType === bscript.types.P2SH) { if (scriptType === bscript.types.P2SH) {
// We can remove this error later when we have a guarantee prepareInput // We can remove this error later when we have a guarantee prepareInput
// rejects unsignabale scripts - it MUST be signable at this point. // rejects unsignable scripts - it MUST be signable at this point.
if (P2SH.indexOf(input.redeemScriptType) === -1) { if (P2SH.indexOf(input.redeemScriptType) === -1) {
throw new Error('Impossible to sign this type') throw new Error('Impossible to sign this type')
} }
@ -505,13 +480,11 @@ 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
@ -562,8 +535,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, options.witness)
input = expandInput(options.script, null, options.witness)
} }
// if an input value was given, retain it // if an input value was given, retain it
@ -591,7 +563,6 @@ 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
@ -627,9 +598,7 @@ 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) {
console.log(input)
var scriptType = input.witnessScriptType || input.redeemScriptType || input.prevOutType 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)
@ -690,7 +659,6 @@ TransactionBuilder.prototype.sign = function (vin, keyPair, redeemScript, hashTy
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)
} }

View file

@ -92,7 +92,6 @@ describe('TransactionBuilder', function () {
var tx = Transaction.fromHex(f.txHex) var tx = Transaction.fromHex(f.txHex)
var txb = TransactionBuilder.fromTransaction(tx, network) var txb = TransactionBuilder.fromTransaction(tx, network)
assert.strictEqual(txb.build().toHex(), f.txHex) assert.strictEqual(txb.build().toHex(), f.txHex)
assert.strictEqual(txb.network, network) assert.strictEqual(txb.network, network)
}) })
@ -347,6 +346,7 @@ describe('TransactionBuilder', function () {
it('does not throw if buildIncomplete', function () { it('does not throw if buildIncomplete', function () {
var txb var txb
if (f.txHex) { if (f.txHex) {
console.log(f.txHex)
txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex)) txb = TransactionBuilder.fromTransaction(Transaction.fromHex(f.txHex))
} else { } else {
txb = construct(f) txb = construct(f)