Compare commits

...

10 Commits

12 changed files with 270 additions and 253 deletions

View File

@ -3,24 +3,28 @@ var bscript = bitcoin.script
var crypto = bitcoin.crypto
var networks = bitcoin.networks
var TransactionBuilder = bitcoin.TransactionBuilder
var TxSigner = bitcoin.TxSigner
var network = networks.testnet
var entropy = new Buffer('14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac')
var root = bitcoin.HDNode.fromSeedBuffer(entropy, network)
var address = root.keyPair.getAddress()
var wif = root.keyPair.toWIF()
console.log(address)
console.log(wif)
var txid = '06fc7b675a31bfe3f05dab40d0cd8c044a9b2e890c696a53449d970a4adc6d52'
var vout = 0
var p2shScript = bscript.witnessPubKeyHash.output.encode(crypto.hash160(root.keyPair.getPublicKeyBuffer()))
var scriptPubKey = bscript.scriptHash.output.encode(crypto.hash160(p2shScript))
var amount = 22000
var receivePK = bscript.pubKeyHash.output.encode(crypto.hash160(root.keyPair.getPublicKeyBuffer()))
var receiveAmount = 22000
var fundP2shScript = bscript.witnessPubKeyHash.output.encode(crypto.hash160(root.keyPair.getPublicKeyBuffer()))
var fundScriptPubKey = bscript.scriptHash.output.encode(crypto.hash160(fundP2shScript))
var txb = new TransactionBuilder(network)
txb.addInput(txid, vout, 0xffffffff)
txb.addOutput(scriptPubKey, amount - 5000)
txb.sign(0, root.keyPair)
var tx = txb.build()
txb.addOutput(fundScriptPubKey, receiveAmount - 5000)
var unsigned = txb.buildIncomplete()
var signer = new TxSigner(unsigned)
signer.sign(0, root.keyPair, {
scriptPubKey: receivePK
})
var tx = signer.done()
console.log(tx.toBuffer().toString('hex'))

View File

@ -3,6 +3,7 @@ var bscript = bitcoin.script
var crypto = bitcoin.crypto
var networks = bitcoin.networks
var TransactionBuilder = bitcoin.TransactionBuilder
var TxSigner = bitcoin.TxSigner
var network = networks.testnet
var entropy = new Buffer('14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac')
@ -12,18 +13,20 @@ var pubkeyhash = crypto.hash160(root.keyPair.getPublicKeyBuffer())
var txid = 'beb647db98bda750f8202e6bc3441781ea5cfc6e3630c9d0ae47b0bfb111c249'
var vout = 1
var txOut = {
script: bscript.pubKeyHash.output.encode(pubkeyhash),
value: 100000
}
var receiveAmount = 10000
var receivePK = bscript.pubKeyHash.output.encode(pubkeyhash)
var witnessScript = txOut.script
var witnessScript = bscript.pubKeyHash.output.encode(pubkeyhash)
var p2shScript = bscript.witnessScriptHash.output.encode(crypto.sha256(witnessScript))
var scriptPubKey = bscript.scriptHash.output.encode(crypto.hash160(p2shScript))
var txb = new TransactionBuilder(network)
txb.addInput(txid, vout, 0xffffffff, txOut.script)
txb.addOutput(scriptPubKey, txOut.value - 10000)
txb.sign(0, root.keyPair)
var tx = txb.build()
txb.addInput(txid, vout, 0xffffffff)
txb.addOutput(scriptPubKey, receiveAmount - 10000)
var txs = new TxSigner(txb.buildIncomplete())
txs.sign(0, root.keyPair, {
scriptPubKey: receivePK
})
var tx = txs.done()
console.log(tx.toBuffer().toString('hex'))

View File

@ -3,6 +3,7 @@ var bscript = bitcoin.script
var crypto = bitcoin.crypto
var networks = bitcoin.networks
var TransactionBuilder = bitcoin.TransactionBuilder
var TxSigner = bitcoin.TxSigner
var network = networks.testnet
var entropy = new Buffer('14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac')
@ -13,20 +14,20 @@ var pubkeyhash = crypto.hash160(root.keyPair.getPublicKeyBuffer())
var txid = 'aed14f8e918c6e7cc9347391b790f765030b07e6985fbb146bf3f6b25ddc0043'
var vout = 0
var txOut = {
script: bscript.pubKeyHash.output.encode(pubkeyhash),
value: 22000
}
var receivePK = bscript.pubKeyHash.output.encode(pubkeyhash)
var receiveAmount = 22000
var multisig = bscript.multisig.output.encode(2, [root.getPublicKeyBuffer(), root2.getPublicKeyBuffer()])
var p2shScript = bscript.witnessScriptHash.output.encode(crypto.sha256(multisig))
var scriptPubKey = bscript.scriptHash.output.encode(crypto.hash160(p2shScript))
var txb = new TransactionBuilder(network)
txb.addInput(txid, vout, 0xffffffff, txOut.script)
txb.addOutput(scriptPubKey, txOut.value - 5000)
txb.sign(0, root.keyPair)
var tx = txb.build()
console.log(tx.toBuffer().toString('hex'))
txb.addInput(txid, vout, 0xffffffff)
txb.addOutput(scriptPubKey, receiveAmount - 5000)
// b5e7c1d911b078c754770fc372bc92096ce2605ac6b785f91ec7df0a5034ec69
var txs = new TxSigner(txb.buildIncomplete())
txs.sign(0, root.keyPair, {
scriptPubKey: receivePK
})
var tx = txs.done()
console.log(tx.toBuffer().toString('hex'))

View File

@ -3,27 +3,29 @@ var bscript = bitcoin.script
var crypto = bitcoin.crypto
var networks = bitcoin.networks
var TransactionBuilder = bitcoin.TransactionBuilder
var TxSigner = bitcoin.TxSigner
var network = networks.testnet
var entropy = new Buffer('14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac')
var root = bitcoin.HDNode.fromSeedBuffer(entropy, network)
var address = root.keyPair.getAddress()
var wif = root.keyPair.toWIF()
console.log(address)
console.log(wif)
var pubkeyhash = crypto.hash160(root.keyPair.getPublicKeyBuffer())
var txid = '9aa8d1a1c5df0afccf76e84df1029062b65a98dad68e13cc765aef88ab378dd0'
var vout = 0
var scriptPubKey = bscript.pubKeyHash.output.encode(pubkeyhash)
var amount = 22000
var receivePK = bscript.pubKeyHash.output.encode(pubkeyhash)
var receiveAmount = 22000
var toSegwitPubkey = bscript.witnessPubKeyHash.output.encode(pubkeyhash)
var txb = new TransactionBuilder(network)
txb.addInput(txid, vout, 0xffffffff, scriptPubKey)
txb.addOutput(toSegwitPubkey, amount - 5000)
txb.sign(0, root.keyPair)
var tx = txb.build()
txb.addInput(txid, vout, 0xffffffff)
txb.addOutput(toSegwitPubkey, receiveAmount - 5000)
var txs = new TxSigner(txb.buildIncomplete())
txs.sign(0, root.keyPair, {
scriptPubKey: receivePK
})
var tx = txs.done()
console.log(tx.toBuffer().toString('hex'))

View File

@ -3,24 +3,28 @@ var bscript = bitcoin.script
var crypto = bitcoin.crypto
var networks = bitcoin.networks
var TransactionBuilder = bitcoin.TransactionBuilder
var TxSigner = bitcoin.TxSigner
var network = networks.testnet
var entropy = new Buffer('14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac')
var root = bitcoin.HDNode.fromSeedBuffer(entropy, network)
console.log(root.getAddress())
var pubkeyhash = crypto.hash160(root.keyPair.getPublicKeyBuffer())
var txid = '79f560d078eacf4cf9381544b15c400773fddd6bbfb1064956e0c345d39be260'
var vout = 0
var scriptPubKey = bscript.pubKeyHash.output.encode(pubkeyhash)
var amount = 70000
var receivePK = bscript.pubKeyHash.output.encode(pubkeyhash)
var receiveAmount = 70000
var witnessScriptHash = crypto.sha256(scriptPubKey)
var toP2WSH = bscript.witnessScriptHash.output.encode(witnessScriptHash)
var witnessScript = bscript.pubKeyHash.output.encode(pubkeyhash)
var toP2WSH = bscript.witnessScriptHash.output.encode(crypto.sha256(witnessScript))
var txb = new TransactionBuilder(network)
txb.addInput(txid, vout, 0xffffffff, scriptPubKey)
txb.addOutput(toP2WSH, amount - 5000)
txb.sign(0, root.keyPair)
var tx = txb.build()
txb.addInput(txid, vout, 0xffffffff)
txb.addOutput(toP2WSH, receiveAmount - 5000)
var txs = new TxSigner(txb.buildIncomplete())
txs.sign(0, root.keyPair, {
scriptPubKey: receivePK
})
var tx = txs.done()
console.log(tx.toBuffer().toString('hex'))

View File

@ -37,10 +37,17 @@ txb.addOutput(myaddress, txOut.value - 5000)
var unsigned = txb.buildIncomplete()
var signer = new TxSigner(unsigned)
signer.sign(0, root.keyPair, {
var opts = {
scriptPubKey: txOut.script,
redeemScript: toSegwitPubkey,
value: txOut.value
})
}
signer.sign(0, root.keyPair, opts)
var txd = signer.done()
console.log(txd.toBuffer().toString('hex'))
var testSigner = new TxSigner(txd)
console.log(testSigner.signer(0, opts).isFullySigned());
console.log(testSigner.done().toBuffer().equals(txd.toBuffer()))

View File

@ -28,12 +28,17 @@ builder.addOutput('2N6stcWuMpLgt4nkiaEFXP6p9J9VKRHCwDJ', 10000)
var unsigned = builder.buildIncomplete()
var signer = new TxSigner(unsigned)
signer.sign(0, root.keyPair, {
var opts = {
scriptPubKey: txOut.script,
redeemScript: p2shScript,
witnessScript: witnessScript,
value: txOut.value
})
}
signer.sign(0, root.keyPair, opts)
var txd = signer.done()
console.log(txd.toBuffer().toString('hex'))
var testSigner = new TxSigner(txd)
console.log(testSigner.signer(0, opts).isFullySigned());
console.log(testSigner.done().toBuffer().equals(txd.toBuffer()))

View File

@ -33,15 +33,19 @@ builder.addOutput('2N6stcWuMpLgt4nkiaEFXP6p9J9VKRHCwDJ', 10000)
var unsigned = builder.buildIncomplete()
var signer = new TxSigner(unsigned)
var data = {
var opts = {
scriptPubKey: txOut.script,
value: txOut.value,
redeemScript: p2shScript,
witnessScript: multisig
}
signer.sign(0, root.keyPair, data)
signer.sign(0, root2.keyPair, data)
signer.sign(0, root.keyPair, opts)
signer.sign(0, root2.keyPair, opts)
var txd = signer.done()
console.log(txd.toBuffer().toString('hex'))
var testSigner = new TxSigner(txd)
console.log(testSigner.signer(0, opts).isFullySigned())
console.log(testSigner.done().toBuffer().equals(txd.toBuffer()))

View File

@ -28,11 +28,16 @@ builder.addOutput('2N6stcWuMpLgt4nkiaEFXP6p9J9VKRHCwDJ', txOut.value - 5000)
var unsigned = builder.buildIncomplete()
var signer = new TxSigner(unsigned)
signer.sign(0, root.keyPair, {
var opts = {
scriptPubKey: txOut.script,
value: txOut.value
})
}
signer.sign(0, root.keyPair, opts)
var txd = signer.done()
console.log(txd.toBuffer().toString('hex'))
var testSigner = new TxSigner(txd)
console.log(testSigner.signer(0, opts).isFullySigned());
console.log(testSigner.done().toBuffer().equals(txd.toBuffer()))

View File

@ -31,12 +31,17 @@ builder.addOutput('2N6stcWuMpLgt4nkiaEFXP6p9J9VKRHCwDJ', 10000)
var unsigned = builder.buildIncomplete()
var signer = new TxSigner(unsigned)
signer.sign(0, root.keyPair, {
var opts = {
scriptPubKey: scriptPubKey,
witnessScript: witnessScript,
value: txOut.value
})
}
signer.sign(0, root.keyPair, opts)
var txd = signer.done()
console.log(txd.toBuffer().toString('hex'))
var testSigner = new TxSigner(txd)
console.log(testSigner.signer(0, opts).isFullySigned())
console.log(testSigner.done().toBuffer().equals(txd.toBuffer()))

View File

@ -5,7 +5,7 @@ var typeforce = require('typeforce')
function check (script, allowIncomplete) {
var chunks = bscript.decompile(script)
if (chunks.length < 2) return false
if (chunks.length < 1) return false
var lastChunk = chunks[chunks.length - 1]
if (!Buffer.isBuffer(lastChunk)) return false
@ -13,11 +13,12 @@ function check (script, allowIncomplete) {
var scriptSigChunks = chunks.slice(0, -1)
var redeemScriptChunks = bscript.decompile(lastChunk)
// is redeemScript a valid script?
if (redeemScriptChunks.length === 0) return false
var outputType = bscript.classifyOutput(redeemScriptChunks)
if (outputType === bscript.types.P2WSH || outputType === bscript.types.P2WPKH) {
return true
}
var inputType = bscript.classifyInput(scriptSigChunks, allowIncomplete)
var outputType = bscript.classifyOutput(redeemScriptChunks)
return inputType === outputType
}
check.toJSON = function () { return 'scriptHash input' }

View File

@ -8,7 +8,6 @@ var types = require('./types')
var ECPair = require('./ecpair')
var ECSignature = require('./ecsignature')
var Transaction = require('./transaction')
var EMPTY_SCRIPT = new Buffer(0)
var SIGNABLE_SCRIPTS = [
bscript.types.MULTISIG,
bscript.types.P2PKH,
@ -38,13 +37,14 @@ function sortMultisigs (tx, nIn, txOutValue, ecsigs, publicKeys, scriptCode, sig
var success = true
var sigsCount = ecsigs.length
var keysCount = publicKeys.length
while (success && ecsigs.length > 0) {
while (success && ecsigs.length > isig) {
sig = ECSignature.parseScriptSignature(ecsigs[isig])
key = ECPair.fromPublicKeyBuffer(publicKeys[ikey])
hash = calculateSigHash(tx, nIn, scriptCode, sig.hashType, sigVersion, txOutValue)
if (key.verify(hash, sig.signature)) {
isig++
results[key.getPublicKeyBuffer().toString('binary')] = ecsigs[isig]
results[key.getPublicKeyBuffer().toString('binary')] = sig
}
ikey++
if (sigsCount > keysCount) {
@ -94,54 +94,6 @@ function pushAll (chunks) {
return bscript.compile(chunks.map(pushOnlyWrite))
}
function solveScript (scriptCode) {
if (!(scriptCode instanceof Buffer)) {
throw new Error('Argument 0 for solveScript must be a Buffer')
}
var outputType = bscript.classifyOutput(scriptCode)
var canSign = SIGNABLE_SCRIPTS.indexOf(outputType) !== -1
var solvedBy = null
var requiredSigs = null
switch (outputType) {
// We can only determine the relevant hash from these, not if it's signable
case bscript.types.P2SH:
solvedBy = bscript.scriptHash.output.decode(scriptCode)
break
case bscript.types.P2WSH:
solvedBy = bscript.witnessScriptHash.output.decode(scriptCode)
break
// We can immediately solve signatures for these
// When adding a new script type, edit here
case bscript.types.P2WPKH:
requiredSigs = 1
solvedBy = bscript.witnessPubKeyHash.output.decode(scriptCode)
break
case bscript.types.P2PK:
requiredSigs = 1
solvedBy = bscript.pubKey.output.decode(scriptCode)
break
case bscript.types.P2PKH:
requiredSigs = 1
solvedBy = bscript.pubKeyHash.output.decode(scriptCode)
break
case bscript.types.MULTISIG:
solvedBy = bscript.multisig.output.decode(scriptCode)
requiredSigs = solvedBy.m
break
}
return {
type: outputType,
script: scriptCode,
canSign: canSign,
solvedBy: solvedBy,
requiredSigs: requiredSigs
}
}
/**
* Design goals
*
@ -159,6 +111,9 @@ function InSigner (tx, nIn, opts) {
if ((tx instanceof Transaction) === false) {
throw new Error('A transaction is required for InSigner')
}
if (tx.ins[nIn] === undefined) {
throw new Error('No transaction input at this index')
}
if (opts.scriptPubKey === undefined) {
throw new Error('A value for scriptPubKey is required')
}
@ -167,16 +122,93 @@ function InSigner (tx, nIn, opts) {
this.nIn = nIn
this.publicKeys = []
this.signatures = []
this.requiredSigs = null
this.requiredSigs = 0
this.solve(opts)
this.extractSig()
}
InSigner.prototype.isFullySigned = function () {
return this.requiredSigs !== null && this.requiredSigs === this.signatures.length
InSigner.prototype.solve = function (opts) {
// Link scriptPubKey, and check that redeemScript/witnessScript/amount are provided
// Sets sigVersion, signScript, scriptPubKey (solution), redeemScript (solution), and witnessScript (solution)
// Determine scriptPubKey is solvable
var scriptPubKey = bscript.solveOutput(opts.scriptPubKey)
if (scriptPubKey.type !== bscript.types.P2SH && ALLOWED_P2SH_SCRIPTS.indexOf(scriptPubKey.type) === -1) {
throw new Error('txOut script is non-standard')
}
var solution = scriptPubKey
var redeemScript
var witnessScript
var sigVersion = Transaction.SIG_V0
var value = 0
if (solution.type === bscript.types.P2SH) {
var scriptHash = solution.solvedBy
// Redeem script must be provided by opts
if (!(opts.redeemScript instanceof Buffer)) {
throw new Error('Redeem script required to solve utxo')
}
// Check redeemScript against script-hash
if (!scriptHash.equals(bcrypto.hash160(opts.redeemScript))) {
throw new Error('Redeem script does not match txOut script hash')
}
// Prevent non-standard P2SH scripts
solution = bscript.solveOutput(opts.redeemScript)
if (!ALLOWED_P2SH_SCRIPTS.indexOf(solution.type)) {
throw new Error('Unsupported P2SH script')
}
redeemScript = solution
}
if (solution.type === bscript.types.P2WPKH) {
// txOutValue is required here
if (!types.Satoshi(opts.value)) {
throw new Error('Value is required for witness-key-hash')
}
// We set solution to this to avoid work later, since P2WPKH is a special case of P2PKH
solution = bscript.solveOutput(bscript.pubKeyHash.output.encode(solution.solvedBy))
sigVersion = Transaction.SIG_V1
value = opts.value
} else if (solution.type === bscript.types.P2WSH) {
var witnessScriptHash = solution.solvedBy
// Witness script must be provided by opts
if (!(opts.witnessScript instanceof Buffer)) {
throw new Error('P2WSH script required to solve utxo')
}
// txOutValue is required here
if (!types.Satoshi(opts.value)) {
throw new Error('Value is required for witness-script-hash')
}
// Check witnessScript against script hash
if (!bcrypto.sha256(opts.witnessScript).equals(witnessScriptHash)) {
throw new Error('Witness script does not match txOut script hash')
}
// Prevent non-standard P2WSH scripts
solution = bscript.solveOutput(opts.witnessScript)
if (SIGNABLE_SCRIPTS.indexOf(solution.type) === -1) {
throw new Error('Witness script is not supported')
}
witnessScript = solution
sigVersion = Transaction.SIG_V1
value = opts.value
}
this.requiredSigs = solution.requiredSigs
this.scriptPubKey = scriptPubKey
this.redeemScript = redeemScript
this.witnessScript = witnessScript
this.value = value
this.sigVersion = sigVersion
this.signScript = solution
}
InSigner.prototype.extractStandard = function (solution, chunks, sigVersion) {
InSigner.prototype.isFullySigned = function () {
return this.requiredSigs !== null && this.signatures.length === this.requiredSigs
}
InSigner.prototype.extractSignableChunks = function (solution, chunks, sigVersion) {
// Only SIGNABLE_SCRIPTS can be extracted here
var signatures = []
var publicKeys = []
var decoded
@ -185,24 +217,31 @@ InSigner.prototype.extractStandard = function (solution, chunks, sigVersion) {
if (solution.type === bscript.types.P2PK) {
if (bscript.pubKey.input.check(chunks)) {
decoded = bscript.pubKey.input.decode(chunks)
if (!decoded.pubKey.equals(solution.solvedBy)) {
throw new Error('Key in scriptSig does not match output script key')
}
signatures[0] = decoded.signature
publicKeys[0] = solution.solvedBy
}
} else if (solution.type === bscript.types.P2PKH) {
if (bscript.pubKeyHash.input.check(chunks)) {
decoded = bscript.pubKeyHash.input.decode(chunks)
if (!bcrypto.hash160(decoded.pubKey).equals(solution.solvedBy)) {
throw new Error('Key in scriptSig does not match output script key-hash')
}
signatures[0] = decoded.signature
publicKeys[0] = decoded.pubKey
}
} else if (solution.type === bscript.types.MULTISIG) {
if (bscript.multisig.input.check(chunks)) {
publicKeys = solution.solvedBy.publicKeys
signatures = bscript.multisig.input.decode(chunks, true)
if (bscript.multisig.input.check(pushAll(chunks))) {
publicKeys = solution.solvedBy.pubKeys
signatures = bscript.multisig.input.decode(pushAll(chunks), true)
// We need to map signature to the pubkey index in order to re-serialize
var sigs = sortMultisigs(this.tx, this.nIn, this.txOut.value, signatures, publicKeys, solution.script, sigVersion)
var sigs = sortMultisigs(this.tx, this.nIn, this.value, signatures, publicKeys, solution.script, sigVersion)
for (var i = 0, l = publicKeys.length; i < l; i++) {
var str = publicKeys[ i ].getPublicKeyBuffer().toString('binary')
var str = publicKeys[ i ].toString('binary')
if (sigs[ str ] !== undefined && bscript.isCanonicalSignature(sigs[str])) {
signatures[ i ] = sigs[ str ]
}
@ -215,73 +254,28 @@ InSigner.prototype.extractStandard = function (solution, chunks, sigVersion) {
return [signatures, publicKeys]
}
InSigner.prototype.solve = function (opts) {
var solution = solveScript(opts.scriptPubKey)
if (solution.type === bscript.types.NONSTANDARD) {
throw new Error('txOut script is non-standard')
}
this.scriptPubKey = solution
if (solution.type === bscript.types.P2SH) {
var scriptHash = solution.solvedBy
if (!(opts.redeemScript instanceof Buffer)) {
throw new Error('Redeem script required to solve utxo')
}
if (!scriptHash.equals(bcrypto.hash160(opts.redeemScript))) {
throw new Error('Redeem script does not match txOut script hash')
}
solution = solveScript(opts.redeemScript)
if (ALLOWED_P2SH_SCRIPTS.indexOf(solution.type)) {
this.redeemScript = solution
} else {
throw new Error('Unsupported P2SH script')
}
}
if (solution.type === bscript.types.P2WPKH) {
if (!types.Satoshi(opts.value)) {
throw new Error('Value is required for witness-key-hash')
}
this.value = opts.value
} else if (solution.type === bscript.types.P2WSH) {
var witnessScriptHash = solution.solvedBy
if (!(opts.witnessScript instanceof Buffer)) {
throw new Error('P2WSH script required to solve utxo')
}
if (!types.Satoshi(opts.value)) {
throw new Error('Value is required for witness-script-hash')
}
if (!bufferEquals(bcrypto.sha256(opts.witnessScript), witnessScriptHash)) {
throw new Error('Witness script does not match txOut script hash')
}
this.witnessScript = solveScript(opts.witnessScript)
this.value = opts.value
if (SIGNABLE_SCRIPTS.indexOf(this.witnessScript.type) === -1) {
throw new Error('witness script is not supported')
}
}
}
InSigner.prototype.extractSig = function () {
// Attempt decoding of the input scriptSig and witness
var input = this.tx.ins[this.nIn]
var solution = this.scriptPubKey
if (solution.canSign) {
[this.signatures, this.publicKeys] = this.extractStandard(solution, evalPushOnly(input.script), Transaction.SIG_V0)
var extractChunks = []
if (SIGNABLE_SCRIPTS.indexOf(solution.type) !== -1) {
extractChunks = evalPushOnly(input.script)
}
if (solution.type === bscript.types.P2SH) {
if (bscript.scriptHash.input.check(input.script)) {
extractChunks = bscript.decompile(input.script)
if (extractChunks.length > 0) {
// If we go to extract a P2SH scriptSig, verify the provided redeemScript
var p2sh = bscript.scriptHash.input.decode(input.script)
if (!p2sh.redeemScript.equals(this.redeemScript.script)) {
throw new Error('Redeem script from scriptSig does not match')
}
if (this.redeemScript.canSign) {
[this.signatures, this.publicKeys] = this.extractStandard(solution, evalPushOnly(p2sh.redeemScriptSig), Transaction.SIG_V0)
}
solution = this.redeemScript
if (SIGNABLE_SCRIPTS.indexOf(solution.type) !== -1) {
// only do this if we can sign, otherwise we waste CPU
extractChunks = evalPushOnly(p2sh.redeemScriptSig)
}
}
}
@ -291,94 +285,83 @@ InSigner.prototype.extractSig = function () {
if (!witnessKeyHash.equals(bcrypto.hash160(input.witness[1]))) {
throw new Error('Public key does not match key-hash')
}
[this.signatures, this.publicKeys] = this.extractStandard(bscript.types.P2PKH, input.witness)
extractChunks = input.witness
}
} else if (solution.type === bscript.types.P2WSH) {
if (input.witness.length > 0) {
if (!this.witnessScript.equals(input.witness[ input.witness.length - 1 ])) {
if (!this.witnessScript.script.equals(input.witness[ input.witness.length - 1 ])) {
throw new Error('Witness script does not match')
}
if (this.witnessScript.canSign) {
[ this.signatures, this.publicKeys ] = this.extractStandard(solution, input.witness.slice(0, -1), Transaction.SIG_V1)
}
extractChunks = input.witness.slice(0, -1)
}
}
if (extractChunks.length > 0) {
[this.signatures, this.publicKeys] = this.extractSignableChunks(this.signScript, extractChunks, this.sigVersion)
}
}
function signStandard (tx, nIn, txOutValue, signatures, publicKeys, key, solution, sigHashType, sigVersion) {
function signSignable (tx, nIn, txOutValue, signatures, publicKeys, key, solution, sigHashType, sigVersion) {
// Todo, skip if already signed
// Only SIGNABLE_SCRIPTS can be signed here
var didSign = false
var keyBuffer = key.getPublicKeyBuffer()
if (solution.type === bscript.types.P2PK) {
if (bufferEquals(keyBuffer, solution.solvedBy)) {
signatures = [calculateSignature(tx, nIn, key, solution.script, sigHashType, sigVersion, txOutValue)]
publicKeys = [keyBuffer]
didSign = true
} else {
throw new Error('Signing input with wrong private key')
}
} else if (solution.type === bscript.types.P2PKH) {
if (bufferEquals(bcrypto.hash160(keyBuffer), solution.solvedBy)) {
signatures = [calculateSignature(tx, nIn, key, solution.script, sigHashType, sigVersion, txOutValue)]
publicKeys = [keyBuffer]
didSign = true
} else {
throw new Error('Signing input with wrong private key')
}
} else if (solution.type === bscript.types.MULTISIG) {
var match = false
for (var i = 0, keyLen = solution.solvedBy.pubKeys.length; i < keyLen; i++) {
publicKeys[i] = solution.solvedBy.pubKeys[i]
if (bufferEquals(keyBuffer, publicKeys[i])) {
didSign = true
match = true
signatures[i] = calculateSignature(tx, nIn, key, solution.script, sigHashType, sigVersion, txOutValue)
}
}
if (!match) {
throw new Error('Signing input with wrong private key')
}
} else {
throw new Error('signStandard can only sign SIGNABLE_SCRIPTS')
}
if (!didSign) {
throw new Error('Signing input with wrong private key')
}
return [signatures, publicKeys]
}
/**
* Signs the input given a key/sigHashType
* @param key
* @param sigHashType
* @returns {boolean}
*/
InSigner.prototype.sign = function (key, sigHashType) {
sigHashType = sigHashType || Transaction.SIGHASH_ALL
// Attempt to solve the txOut script
var solution = this.scriptPubKey
if (solution.canSign) {
[this.signatures, this.publicKeys] = signStandard(this.tx, this.nIn, undefined, this.signatures, this.publicKeys, key, solution, sigHashType, Transaction.SIG_V0)
}
// If the spkPubKeyHash was solvable, and the type is P2SH, we try again with the redeemScript
if (solution.type === bscript.types.P2SH) {
// solution updated, type is the type of the redeemScript
solution = this.redeemScript
if (ALLOWED_P2SH_SCRIPTS.indexOf(solution.type) !== -1) {
if (solution.canSign) {
[this.signatures, this.publicKeys] = signStandard(this.tx, this.nIn, undefined, this.signatures, this.publicKeys, key, solution, sigHashType, Transaction.SIG_V0)
}
}
}
if (solution.type === bscript.types.P2WPKH) {
var p2wpkh = solveScript(bscript.pubKeyHash.output.encode(solution.solvedBy));
[this.signatures, this.publicKeys] = signStandard(this.tx, this.nIn, this.value, this.signatures, this.publicKeys, key, p2wpkh, sigHashType, Transaction.SIG_V1)
} else if (solution.type === bscript.types.P2WSH) {
solution = this.witnessScript
if (solution.canSign) {
[this.signatures, this.publicKeys] = signStandard(this.tx, this.nIn, this.value, this.signatures, this.publicKeys, key, solution, sigHashType, Transaction.SIG_V1)
}
}
sigHashType = sigHashType || Transaction.SIGHASH_ALL;
[this.signatures, this.publicKeys] = signSignable(this.tx, this.nIn, this.value, this.signatures, this.publicKeys, key, this.signScript, sigHashType, this.sigVersion)
this.requiredSigs = this.publicKeys.length
return solution
return true
}
function serializeStandard (outputType, signatures, publicKeys) {
// When adding a new script type, edit here
/**
* Creates an array of chunks for the scriptSig or witness
* Future signable types go here (CSV, HTLC)
*
* @param outputType
* @param signatures
* @param publicKeys
* @returns {Array}
*/
function serializeSignableChunks (outputType, signatures, publicKeys) {
// Only SIGNABLE_SCRIPTS can be serialized here
var chunks = []
switch (outputType) {
case bscript.types.P2PK:
@ -407,42 +390,36 @@ function serializeStandard (outputType, signatures, publicKeys) {
return chunks
}
function serializeSigData (signatures, publicKeys, scriptPubKey, redeemScript, witnessScript) {
InSigner.prototype.serializeSigData = function () {
var scriptChunks = []
var witnessChunks = []
var type = scriptPubKey.type
if (scriptPubKey.canSign) {
scriptChunks = serializeStandard(type, signatures, publicKeys)
var type = this.scriptPubKey.type
if (SIGNABLE_SCRIPTS.indexOf(this.scriptPubKey.type) !== -1) {
scriptChunks = serializeSignableChunks(type, this.signatures, this.publicKeys)
}
var p2sh = false
if (type === bscript.types.P2SH) {
if (redeemScript === undefined) {
throw new Error('Redeem script not provided')
}
p2sh = true
type = redeemScript.type
if (redeemScript.canSign) {
scriptChunks = serializeStandard(type, signatures, publicKeys)
type = this.redeemScript.type
if (SIGNABLE_SCRIPTS.indexOf(this.redeemScript.type) !== -1) {
scriptChunks = serializeSignableChunks(type, this.signatures, this.publicKeys)
}
}
if (type === bscript.types.P2WPKH) {
witnessChunks = serializeStandard(bscript.types.P2PKH, signatures, publicKeys)
witnessChunks = serializeSignableChunks(bscript.types.P2PKH, this.signatures, this.publicKeys)
} else if (type === bscript.types.P2WSH) {
if (witnessScript === undefined) {
throw new Error('Witness script not provided')
}
type = witnessScript.type
if (witnessScript.canSign) {
type = this.witnessScript.type
if (SIGNABLE_SCRIPTS.indexOf(this.witnessScript.type) !== -1) {
scriptChunks = []
witnessChunks = serializeStandard(type, signatures, publicKeys)
witnessChunks.push(witnessScript.script);
witnessChunks = serializeSignableChunks(type, this.signatures, this.publicKeys)
witnessChunks.push(this.witnessScript.script)
}
}
if (p2sh) {
scriptChunks.push(redeemScript.script)
scriptChunks.push(this.redeemScript.script)
}
return {
@ -451,10 +428,6 @@ function serializeSigData (signatures, publicKeys, scriptPubKey, redeemScript, w
}
}
InSigner.prototype.serializeSigData = function () {
return serializeSigData(this.signatures, this.publicKeys, this.scriptPubKey, this.redeemScript, this.witnessScript)
}
/**
* Create a TxSigner for this transaction instance
* @param tx
@ -469,6 +442,12 @@ function TxSigner (tx) {
this.states = []
}
TxSigner.prototype.signer = function (nIn, opts) {
if (this.states[nIn] === undefined) {
this.states[nIn] = new InSigner(this.tx, nIn, opts)
}
return this.states[nIn]
}
/**
* Sign a transaction.
*
@ -485,11 +464,8 @@ TxSigner.prototype.sign = function (nIn, key, opts, sigHashType) {
}
// You can probably make this work with the current library, if you can work out the witnessScript above!
// generate opts for the internal signer based off older way of positional arguments to TxSigner.sign
if (this.states[nIn] === undefined) {
this.states[nIn] = new InSigner(this.tx, nIn, opts)
}
if (!this.states[nIn].sign(key, sigHashType)) {
if (!this.signer(nIn, opts).sign(key, sigHashType)) {
throw new Error('Unsignable input: ', nIn)
}