|
|
@ -15,6 +15,90 @@ function TransactionBuilder() { |
|
|
|
this.tx = new Transaction() |
|
|
|
} |
|
|
|
|
|
|
|
// Static constructors
|
|
|
|
TransactionBuilder.fromTransaction = function(transaction) { |
|
|
|
var txb = new TransactionBuilder() |
|
|
|
|
|
|
|
// Extract/add inputs
|
|
|
|
transaction.ins.forEach(function(txin) { |
|
|
|
txb.addInput(txin.hash, txin.index, txin.sequence) |
|
|
|
}) |
|
|
|
|
|
|
|
// Extract/add outputs
|
|
|
|
transaction.outs.forEach(function(txout) { |
|
|
|
txb.addOutput(txout.script, txout.value) |
|
|
|
}) |
|
|
|
|
|
|
|
// Extract/add signatures
|
|
|
|
transaction.ins.forEach(function(txin) { |
|
|
|
// Ignore empty scripts
|
|
|
|
if (txin.script.buffer.length === 0) return |
|
|
|
|
|
|
|
var redeemScript |
|
|
|
var scriptSig = txin.script |
|
|
|
var scriptType = scripts.classifyInput(scriptSig) |
|
|
|
|
|
|
|
// Re-classify if P2SH
|
|
|
|
if (scriptType === 'scripthash') { |
|
|
|
redeemScript = Script.fromBuffer(scriptSig.chunks.slice(-1)[0]) |
|
|
|
scriptSig = Script.fromChunks(scriptSig.chunks.slice(0, -1)) |
|
|
|
|
|
|
|
scriptType = scripts.classifyInput(scriptSig) |
|
|
|
assert.equal(scripts.classifyOutput(redeemScript), scriptType, 'Non-matching scriptSig and scriptPubKey in input') |
|
|
|
} |
|
|
|
|
|
|
|
// Extract hashType, pubKeys and signatures
|
|
|
|
var hashType, pubKeys, signatures |
|
|
|
|
|
|
|
switch (scriptType) { |
|
|
|
case 'pubkeyhash': |
|
|
|
var parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0]) |
|
|
|
var pubKey = ECPubKey.fromBuffer(scriptSig.chunks[1]) |
|
|
|
|
|
|
|
hashType = parsed.hashType |
|
|
|
pubKeys = [pubKey] |
|
|
|
signatures = [parsed.signature] |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
case 'multisig': |
|
|
|
var scriptSigs = scriptSig.chunks.slice(1) // ignore OP_0
|
|
|
|
var parsed = scriptSigs.map(function(scriptSig) { |
|
|
|
return ECSignature.parseScriptSignature(scriptSig) |
|
|
|
}) |
|
|
|
|
|
|
|
hashType = parsed[0].hashType |
|
|
|
pubKeys = [] |
|
|
|
signatures = parsed.map(function(p) { return p.signature }) |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
case 'pubkey': |
|
|
|
var parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0]) |
|
|
|
|
|
|
|
hashType = parsed.hashType |
|
|
|
pubKeys = [] |
|
|
|
signatures = [parsed.signature] |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
default: |
|
|
|
assert(false, scriptType + ' not supported') |
|
|
|
} |
|
|
|
|
|
|
|
txb.signatures[txin.index] = { |
|
|
|
hashType: hashType, |
|
|
|
pubKeys: pubKeys, |
|
|
|
redeemScript: redeemScript, |
|
|
|
scriptType: scriptType, |
|
|
|
signatures: signatures |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
return txb |
|
|
|
} |
|
|
|
|
|
|
|
// Operations
|
|
|
|
TransactionBuilder.prototype.addInput = function(prevTx, index, sequence, prevOutScript) { |
|
|
|
var prevOutHash |
|
|
|
|
|
|
@ -63,55 +147,6 @@ TransactionBuilder.prototype.addOutput = function(scriptPubKey, value) { |
|
|
|
return this.tx.addOutput(scriptPubKey, value) |
|
|
|
} |
|
|
|
|
|
|
|
TransactionBuilder.prototype.sign = function(index, privKey, redeemScript, hashType) { |
|
|
|
assert(this.tx.ins.length >= index, 'No input at index: ' + index) |
|
|
|
hashType = hashType || Transaction.SIGHASH_ALL |
|
|
|
|
|
|
|
var prevOutScript = this.prevOutScripts[index] |
|
|
|
var prevOutType = this.prevOutTypes[index] |
|
|
|
|
|
|
|
var scriptType, hash |
|
|
|
if (redeemScript) { |
|
|
|
prevOutScript = prevOutScript || scripts.scriptHashOutput(redeemScript.getHash()) |
|
|
|
prevOutType = prevOutType || 'scripthash' |
|
|
|
|
|
|
|
assert.equal(prevOutType, 'scripthash', 'PrevOutScript must be P2SH') |
|
|
|
|
|
|
|
scriptType = scripts.classifyOutput(redeemScript) |
|
|
|
|
|
|
|
assert.notEqual(scriptType, 'scripthash', 'RedeemScript can\'t be P2SH') |
|
|
|
assert.notEqual(scriptType, 'nonstandard', 'RedeemScript not supported (nonstandard)') |
|
|
|
|
|
|
|
hash = this.tx.hashForSignature(index, redeemScript, hashType) |
|
|
|
|
|
|
|
} else { |
|
|
|
prevOutScript = prevOutScript || privKey.pub.getAddress().toOutputScript() |
|
|
|
scriptType = prevOutType || 'pubkeyhash' |
|
|
|
|
|
|
|
assert.notEqual(scriptType, 'scripthash', 'PrevOutScript requires redeemScript') |
|
|
|
|
|
|
|
hash = this.tx.hashForSignature(index, prevOutScript, hashType) |
|
|
|
} |
|
|
|
|
|
|
|
if (!(index in this.signatures)) { |
|
|
|
this.signatures[index] = { |
|
|
|
hashType: hashType, |
|
|
|
pubKeys: [], |
|
|
|
redeemScript: redeemScript, |
|
|
|
scriptType: scriptType, |
|
|
|
signatures: [] |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var input = this.signatures[index] |
|
|
|
assert.equal(input.hashType, hashType, 'Inconsistent hashType') |
|
|
|
assert.deepEqual(input.redeemScript, redeemScript, 'Inconsistent redeemScript') |
|
|
|
|
|
|
|
var signature = privKey.sign(hash) |
|
|
|
input.pubKeys.push(privKey.pub) |
|
|
|
input.signatures.push(signature) |
|
|
|
} |
|
|
|
|
|
|
|
TransactionBuilder.prototype.build = function() { |
|
|
|
return this.__build(false) |
|
|
|
} |
|
|
@ -173,86 +208,53 @@ TransactionBuilder.prototype.__build = function(allowIncomplete) { |
|
|
|
return tx |
|
|
|
} |
|
|
|
|
|
|
|
TransactionBuilder.fromTransaction = function(transaction) { |
|
|
|
var txb = new TransactionBuilder() |
|
|
|
|
|
|
|
// Extract/add inputs
|
|
|
|
transaction.ins.forEach(function(txin) { |
|
|
|
txb.addInput(txin.hash, txin.index, txin.sequence) |
|
|
|
}) |
|
|
|
|
|
|
|
// Extract/add outputs
|
|
|
|
transaction.outs.forEach(function(txout) { |
|
|
|
txb.addOutput(txout.script, txout.value) |
|
|
|
}) |
|
|
|
|
|
|
|
// Extract/add signatures
|
|
|
|
transaction.ins.forEach(function(txin) { |
|
|
|
// Ignore empty scripts
|
|
|
|
if (txin.script.buffer.length === 0) return |
|
|
|
|
|
|
|
var redeemScript |
|
|
|
var scriptSig = txin.script |
|
|
|
var scriptType = scripts.classifyInput(scriptSig) |
|
|
|
|
|
|
|
// Re-classify if P2SH
|
|
|
|
if (scriptType === 'scripthash') { |
|
|
|
redeemScript = Script.fromBuffer(scriptSig.chunks.slice(-1)[0]) |
|
|
|
scriptSig = Script.fromChunks(scriptSig.chunks.slice(0, -1)) |
|
|
|
|
|
|
|
scriptType = scripts.classifyInput(scriptSig) |
|
|
|
assert.equal(scripts.classifyOutput(redeemScript), scriptType, 'Non-matching scriptSig and scriptPubKey in input') |
|
|
|
} |
|
|
|
|
|
|
|
// Extract hashType, pubKeys and signatures
|
|
|
|
var hashType, pubKeys, signatures |
|
|
|
|
|
|
|
switch (scriptType) { |
|
|
|
case 'pubkeyhash': |
|
|
|
var parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0]) |
|
|
|
var pubKey = ECPubKey.fromBuffer(scriptSig.chunks[1]) |
|
|
|
TransactionBuilder.prototype.sign = function(index, privKey, redeemScript, hashType) { |
|
|
|
assert(this.tx.ins.length >= index, 'No input at index: ' + index) |
|
|
|
hashType = hashType || Transaction.SIGHASH_ALL |
|
|
|
|
|
|
|
hashType = parsed.hashType |
|
|
|
pubKeys = [pubKey] |
|
|
|
signatures = [parsed.signature] |
|
|
|
var prevOutScript = this.prevOutScripts[index] |
|
|
|
var prevOutType = this.prevOutTypes[index] |
|
|
|
|
|
|
|
break |
|
|
|
var scriptType, hash |
|
|
|
if (redeemScript) { |
|
|
|
prevOutScript = prevOutScript || scripts.scriptHashOutput(redeemScript.getHash()) |
|
|
|
prevOutType = prevOutType || 'scripthash' |
|
|
|
|
|
|
|
case 'multisig': |
|
|
|
var scriptSigs = scriptSig.chunks.slice(1) // ignore OP_0
|
|
|
|
var parsed = scriptSigs.map(function(scriptSig) { |
|
|
|
return ECSignature.parseScriptSignature(scriptSig) |
|
|
|
}) |
|
|
|
assert.equal(prevOutType, 'scripthash', 'PrevOutScript must be P2SH') |
|
|
|
|
|
|
|
hashType = parsed[0].hashType |
|
|
|
pubKeys = [] |
|
|
|
signatures = parsed.map(function(p) { return p.signature }) |
|
|
|
scriptType = scripts.classifyOutput(redeemScript) |
|
|
|
|
|
|
|
break |
|
|
|
assert.notEqual(scriptType, 'scripthash', 'RedeemScript can\'t be P2SH') |
|
|
|
assert.notEqual(scriptType, 'nonstandard', 'RedeemScript not supported (nonstandard)') |
|
|
|
|
|
|
|
case 'pubkey': |
|
|
|
var parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0]) |
|
|
|
hash = this.tx.hashForSignature(index, redeemScript, hashType) |
|
|
|
|
|
|
|
hashType = parsed.hashType |
|
|
|
pubKeys = [] |
|
|
|
signatures = [parsed.signature] |
|
|
|
} else { |
|
|
|
prevOutScript = prevOutScript || privKey.pub.getAddress().toOutputScript() |
|
|
|
scriptType = prevOutType || 'pubkeyhash' |
|
|
|
|
|
|
|
break |
|
|
|
assert.notEqual(scriptType, 'scripthash', 'PrevOutScript requires redeemScript') |
|
|
|
|
|
|
|
default: |
|
|
|
assert(false, scriptType + ' not supported') |
|
|
|
} |
|
|
|
hash = this.tx.hashForSignature(index, prevOutScript, hashType) |
|
|
|
} |
|
|
|
|
|
|
|
txb.signatures[txin.index] = { |
|
|
|
if (!(index in this.signatures)) { |
|
|
|
this.signatures[index] = { |
|
|
|
hashType: hashType, |
|
|
|
pubKeys: pubKeys, |
|
|
|
pubKeys: [], |
|
|
|
redeemScript: redeemScript, |
|
|
|
scriptType: scriptType, |
|
|
|
signatures: signatures |
|
|
|
signatures: [] |
|
|
|
} |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
return txb |
|
|
|
var input = this.signatures[index] |
|
|
|
assert.equal(input.hashType, hashType, 'Inconsistent hashType') |
|
|
|
assert.deepEqual(input.redeemScript, redeemScript, 'Inconsistent redeemScript') |
|
|
|
|
|
|
|
var signature = privKey.sign(hash) |
|
|
|
input.pubKeys.push(privKey.pub) |
|
|
|
input.signatures.push(signature) |
|
|
|
} |
|
|
|
|
|
|
|
module.exports = TransactionBuilder |
|
|
|