diff --git a/latest/fund_p2sh_p2wpkh.js b/latest/fund_p2sh_p2wpkh.js new file mode 100644 index 0000000..d252aa6 --- /dev/null +++ b/latest/fund_p2sh_p2wpkh.js @@ -0,0 +1,26 @@ +var bitcoin = require('../src/index.js') +var bscript = bitcoin.script +var crypto = bitcoin.crypto +var networks = bitcoin.networks +var TransactionBuilder = bitcoin.TransactionBuilder + +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 txb = new TransactionBuilder(network) +txb.addInput(txid, vout, 0xffffffff) +txb.addOutput(scriptPubKey, amount - 5000) +txb.sign(0, root.keyPair) +var tx = txb.build() +console.log(tx.toBuffer().toString('hex')) diff --git a/latest/fund_p2sh_p2wsh.js b/latest/fund_p2sh_p2wsh.js new file mode 100644 index 0000000..39a2822 --- /dev/null +++ b/latest/fund_p2sh_p2wsh.js @@ -0,0 +1,29 @@ +var bitcoin = require('../src/index.js') +var bscript = bitcoin.script +var crypto = bitcoin.crypto +var networks = bitcoin.networks +var TransactionBuilder = bitcoin.TransactionBuilder + +var network = networks.testnet +var entropy = new Buffer('14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac') +var root = bitcoin.HDNode.fromSeedBuffer(entropy, network) + +var pubkeyhash = crypto.hash160(root.keyPair.getPublicKeyBuffer()) + +var txid = 'beb647db98bda750f8202e6bc3441781ea5cfc6e3630c9d0ae47b0bfb111c249' +var vout = 1 +var txOut = { + script: bscript.pubKeyHash.output.encode(pubkeyhash), + value: 100000 +} + +var witnessScript = txOut.script +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() +console.log(tx.toBuffer().toString('hex')) diff --git a/latest/fund_p2sh_p2wsh_multisig.js b/latest/fund_p2sh_p2wsh_multisig.js new file mode 100644 index 0000000..4a232a6 --- /dev/null +++ b/latest/fund_p2sh_p2wsh_multisig.js @@ -0,0 +1,32 @@ +var bitcoin = require('../src/index.js') +var bscript = bitcoin.script +var crypto = bitcoin.crypto +var networks = bitcoin.networks +var TransactionBuilder = bitcoin.TransactionBuilder + +var network = networks.testnet +var entropy = new Buffer('14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac14bdfeac') +var root = bitcoin.HDNode.fromSeedBuffer(entropy, network) +var root2 = root.derive(1) + +var pubkeyhash = crypto.hash160(root.keyPair.getPublicKeyBuffer()) + +var txid = 'aed14f8e918c6e7cc9347391b790f765030b07e6985fbb146bf3f6b25ddc0043' +var vout = 0 +var txOut = { + script: bscript.pubKeyHash.output.encode(pubkeyhash), + value: 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')) + +// b5e7c1d911b078c754770fc372bc92096ce2605ac6b785f91ec7df0a5034ec69 diff --git a/latest/fund_p2wpkh.js b/latest/fund_p2wpkh.js new file mode 100644 index 0000000..d26f39b --- /dev/null +++ b/latest/fund_p2wpkh.js @@ -0,0 +1,29 @@ +var bitcoin = require('../src/index.js') +var bscript = bitcoin.script +var crypto = bitcoin.crypto +var networks = bitcoin.networks +var TransactionBuilder = bitcoin.TransactionBuilder + +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 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() +console.log(tx.toBuffer().toString('hex')) diff --git a/latest/fund_p2wsh.js b/latest/fund_p2wsh.js new file mode 100644 index 0000000..d5820fa --- /dev/null +++ b/latest/fund_p2wsh.js @@ -0,0 +1,26 @@ +var bitcoin = require('../src/index.js') +var bscript = bitcoin.script +var crypto = bitcoin.crypto +var networks = bitcoin.networks +var TransactionBuilder = bitcoin.TransactionBuilder + +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 witnessScriptHash = crypto.sha256(scriptPubKey) +var toP2WSH = bscript.witnessScriptHash.output.encode(witnessScriptHash) + +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() +console.log(tx.toBuffer().toString('hex')) diff --git a/latest/spend_p2sh_p2wpkh.js b/latest/spend_p2sh_p2wpkh.js new file mode 100644 index 0000000..13e9799 --- /dev/null +++ b/latest/spend_p2sh_p2wpkh.js @@ -0,0 +1,46 @@ +var bitcoin = require('../src/index.js') + +var bscript = bitcoin.script +var crypto = bitcoin.crypto +var networks = bitcoin.networks +// var baddress = bitcoin.address +var TransactionBuilder = bitcoin.TransactionBuilder +var TxSigner = bitcoin.TxSigner +var network = networks.testnet + +var entropy = new Buffer('14bdfeac14bdfeac14bdfeac1100feac14bdfeac14bdfeac14bdfeac14bdfeac') // my entropy +var root = bitcoin.HDNode.fromSeedBuffer(entropy, network) + +var pubkeyhash = crypto.hash160(root.keyPair.getPublicKeyBuffer()) +// redeem script +var toSegwitPubkey = bscript.witnessPubKeyHash.output.encode(pubkeyhash) +// aixo es l-envoltori del p2sh crec +var p2sh = bscript.scriptHash.output.encode(crypto.hash160(toSegwitPubkey)) +// on hem rebut la pasta inicial +// var receiveAddress = baddress.toBase58Check(crypto.hash160(toSegwitPubkey), network.scriptHash) + +// txhash de la tx que estem gastant +var txhashUnspent = 'b085099291d44edecfb3a98384f4266282964fe7b0a12d6db9169698cb7e6487' +var vout = 0 +// on gastarem la pasta despres + +var myaddress = '2N6stcWuMpLgt4nkiaEFXP6p9J9VKRHCwDJ' + +var txOut = { + script: p2sh, + value: 30000 +} + +var txb = new TransactionBuilder(network) +txb.addInput(txhashUnspent, vout, 0xffffffff, p2sh) +txb.addOutput(myaddress, txOut.value - 5000) + +var unsigned = txb.buildIncomplete() +var signer = new TxSigner(unsigned) +signer.sign(0, root.keyPair, { + scriptPubKey: txOut.script, + redeemScript: toSegwitPubkey, + value: txOut.value +}) +var txd = signer.done() +console.log(txd.toBufferWithWitness().toString('hex')) diff --git a/latest/spend_p2sh_p2wsh.js b/latest/spend_p2sh_p2wsh.js new file mode 100644 index 0000000..069d008 --- /dev/null +++ b/latest/spend_p2sh_p2wsh.js @@ -0,0 +1,39 @@ +var bitcoin = require('../src/index.js') + +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 pubkeyhash = crypto.hash160(root.keyPair.getPublicKeyBuffer()) +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 txid = '2f789c63bb88c0ca844cf9ab5c59e1d6e935fa9ae6d6b5bc2c5251fca549f09d' +var vout = 0 +var txOut = { + script: scriptPubKey, + value: 90000 +} + +var builder = new TransactionBuilder(network) +builder.addInput(txid, vout, 0xffffffff, txOut.script) +builder.addOutput('2N6stcWuMpLgt4nkiaEFXP6p9J9VKRHCwDJ', 10000) + +var unsigned = builder.buildIncomplete() +var signer = new TxSigner(unsigned) +signer.sign(0, root.keyPair, { + scriptPubKey: txOut.scriptPubKey, + redeemScript: p2shScript, + witnessScript: witnessScript, + value: txOut.value +}) + +var txd = signer.done() +console.log(txd.toBuffer().toString('hex')) diff --git a/latest/spend_p2sh_p2wsh_multisig.js b/latest/spend_p2sh_p2wsh_multisig.js new file mode 100644 index 0000000..1e6de0e --- /dev/null +++ b/latest/spend_p2sh_p2wsh_multisig.js @@ -0,0 +1,47 @@ +var bitcoin = require('../src/index.js') +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 root2 = root.derive(1) + +// var address = root.keyPair.getAddress() +// var wif = root.keyPair.toWIF() +// console.log(address) +// console.log(wif) + +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 txid = '5d614b47c75ca29a16086e7866de7522e59a09491bbd7e914923f5aabc62616a' +var vout = 0 +var txOut = { + script: scriptPubKey, + value: 15000 +} + +var builder = new TransactionBuilder(network) +builder.addInput(txid, vout, 0xffffffff, txOut.script) +builder.addOutput('2N6stcWuMpLgt4nkiaEFXP6p9J9VKRHCwDJ', 10000) + +var unsigned = builder.buildIncomplete() +var signer = new TxSigner(unsigned) + +var data = { + scriptPubKey: txOut.script, + value: txOut.value, + redeemScript: p2shScript, + witnessScript: multisig +} + +signer.sign(0, root.keyPair, data) +signer.sign(0, root2.keyPair, data) + +var txd = signer.done() +console.log(txd.toBuffer().toString('hex')) diff --git a/latest/spend_p2wpkh.js b/latest/spend_p2wpkh.js new file mode 100644 index 0000000..16d4f58 --- /dev/null +++ b/latest/spend_p2wpkh.js @@ -0,0 +1,38 @@ +var bitcoin = require('../src/index.js') +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 = 'cca0f194d0ca770737121532497f82b3b4eb861df462bbb26978e53acfec7de8' +var vout = 0 + +var txOut = { + script: bscript.witnessPubKeyHash.output.encode(pubkeyhash), + value: 17000 +} + +var builder = new TransactionBuilder(network) +builder.addInput(txid, vout, 0xffffffff, txOut.script) +builder.addOutput('2N6stcWuMpLgt4nkiaEFXP6p9J9VKRHCwDJ', txOut.value - 5000) + +var unsigned = builder.buildIncomplete() +var signer = new TxSigner(unsigned) +signer.sign(0, root.keyPair, { + scriptPubKey: txOut.script, + value: txOut.value +}) + +var txd = signer.done() + +console.log(txd.toBuffer().toString('hex')) diff --git a/latest/spend_p2wsh.js b/latest/spend_p2wsh.js new file mode 100644 index 0000000..d9c2afd --- /dev/null +++ b/latest/spend_p2wsh.js @@ -0,0 +1,42 @@ +var bitcoin = require('../src/index.js') +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 witnessScript = bscript.pubKeyHash.output.encode(pubkeyhash) +var scriptHash = crypto.sha256(witnessScript) +var scriptPubKey = bscript.witnessScriptHash.output.encode(scriptHash) +var txid = '6d1c2682f553889e3887762fcf4669ab3844c6803d6c5c366bc2909bbe33cbf9' +var vout = 0 + +var txOut = { + script: scriptPubKey, + value: 65000 +} + +var builder = new TransactionBuilder(network) +builder.addInput(txid, vout, 0xffffffff, txOut.script) +builder.addOutput('2N6stcWuMpLgt4nkiaEFXP6p9J9VKRHCwDJ', 10000) + +var unsigned = builder.buildIncomplete() +var signer = new TxSigner(unsigned) +signer.sign(0, root.keyPair, { + scriptPubKey: scriptPubKey, + witnessScript: witnessScript, + value: txOut.value +}) + +var txd = signer.done() +console.log(txd.toBuffer().toString('hex')) + diff --git a/src/index.js b/src/index.js index 25b3da5..a03687b 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,7 @@ module.exports = { HDNode: require('./hdnode'), Transaction: require('./transaction'), TransactionBuilder: require('./transaction_builder'), - + TxSigner: require('./transaction_signer'), address: require('./address'), bufferutils: require('./bufferutils'), crypto: require('./crypto'), diff --git a/src/transaction.js b/src/transaction.js index 2e9f028..33424c6 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -34,7 +34,8 @@ Transaction.SIGHASH_SINGLE = 0x03 Transaction.SIGHASH_ANYONECANPAY = 0x80 Transaction.ADVANCED_TRANSACTION_MARKER = 0x00 Transaction.ADVANCED_TRANSACTION_FLAG = 0x01 - +Transaction.SIG_V0 = 0 +Transaction.SIG_V1 = 1 var EMPTY_SCRIPT = new Buffer(0) var EMPTY_WITNESS = [] var ZERO = new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex') diff --git a/src/transaction_signer.js b/src/transaction_signer.js new file mode 100644 index 0000000..122d0b4 --- /dev/null +++ b/src/transaction_signer.js @@ -0,0 +1,516 @@ +var bscript = require('./script') +var OPS = require('./opcodes.json') +var bscriptNumber = require('./script_number') +var bcrypto = require('./crypto') +var bufferEquals = require('buffer-equals') +var typeforce = require('typeforce') +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, + bscript.types.P2PK +] + +var ALLOWED_P2SH_SCRIPTS = [ + bscript.types.MULTISIG, + bscript.types.P2PKH, + bscript.types.P2PK, + bscript.types.P2WSH, + bscript.types.P2WPKH +] + +function calculateSigHash (tx, nIn, scriptCode, sigHashType, sigVersion, txOutAmount) { + return sigVersion === 1 + ? tx.hashForWitnessV0(nIn, scriptCode, txOutAmount, sigHashType) + : tx.hashForSignature(nIn, scriptCode, sigHashType) +} + +function sortMultisigs (tx, nIn, txOutValue, ecsigs, publicKeys, scriptCode, sigVersion) { + var results = [] + var hash + var ikey = 0 + var isig = 0 + var sig, key + var success = true + var sigsCount = ecsigs.length + var keysCount = publicKeys.length + while (success && ecsigs.length > 0) { + 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] + } + ikey++ + if (sigsCount > keysCount) { + success = false + } + } + + return results +} + +function calculateSignature (tx, nIn, key, scriptCode, sigHashType, sigVersion, txOutValue) { + var hash = calculateSigHash(tx, nIn, scriptCode, sigHashType, sigVersion, txOutValue) + return key.sign(hash).toScriptSignature(sigHashType) +} + +function pushOnlyRead (op) { + if (op === OPS.OP_0) { + return new Buffer() + } else if (op instanceof Buffer) { + return op + } else if (op === OPS.OP_1NEGATE || op >= OPS.OP_1 && op <= OPS.OP_16) { + return bscriptNumber.encode(op - 0x50) + } else { + throw new Error('Should only be run on a push-only script') + } +} + +function pushOnlyWrite (buffer) { + if (!(buffer instanceof Buffer)) { + throw new Error('Non-buffer passed to pushOnlyWrite') + } + + if (buffer.length === 0) { + return OPS.OP_0 + } else if (buffer.length === 1 && (buffer[0] === 0x81 || buffer[0] >= 1 && buffer[0] <= 16)) { + return bscriptNumber.decode(buffer) + } else { + return buffer + } +} + +function evalPushOnly (script) { + return bscript.decompile(script).map(pushOnlyRead) +} + +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 + * + * - tolerate arbitrary sigHashType's on signatures + * - given tx, nIn, txOut, we can reliably check a redeemScript and eventual witnessScript at signing + * - reliably extract signatures from a signed input + * - create, and re-serialize given minimal state + * - clear separation of 'standard scripts' and the various script-hash scripts + * + * @param tx - the transaction we want to sign + * @param nIn - the input we will sign here + * @param opts + */ +function InSigner (tx, nIn, opts) { + if ((tx instanceof Transaction) === false) { + throw new Error('A transaction is required for InSigner') + } + if (opts.scriptPubKey === undefined) { + throw new Error('A value for scriptPubKey is required') + } + + this.tx = tx + this.nIn = nIn + this.publicKeys = [] + this.signatures = [] + this.requiredSigs = null + this.solve(opts) + this.extractSig() +} + +InSigner.prototype.isFullySigned = function () { + return this.requiredSigs !== null && this.requiredSigs === this.signatures.length +} + +InSigner.prototype.extractStandard = function (solution, chunks, sigVersion) { + var signatures = [] + var publicKeys = [] + var decoded + + // only SIGNABLE_SCRIPTS can be extracted here + if (solution.type === bscript.types.P2PK) { + if (bscript.pubKey.input.check(chunks)) { + decoded = bscript.pubKey.input.decode(chunks) + 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) + 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) + + // 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) + for (var i = 0, l = publicKeys.length; i < l; i++) { + var str = publicKeys[ i ].getPublicKeyBuffer().toString('binary') + if (sigs[ str ] !== undefined && bscript.isCanonicalSignature(sigs[str])) { + signatures[ i ] = sigs[ str ] + } + } + } + } else { + throw new Error('Never call extractStandardFromChunks on a non-SIGNABLE script') + } + + 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) + } + + if (solution.type === bscript.types.P2SH) { + if (bscript.scriptHash.input.check(input.script)) { + 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 (solution.type === bscript.types.P2WPKH) { + if (input.witness.length === 2) { + var witnessKeyHash = solution.solvedBy + 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) + } + } else if (solution.type === bscript.types.P2WSH) { + if (input.witness.length > 0) { + if (!this.witnessScript.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) + } + } + } +} + +function signStandard (tx, nIn, txOutValue, signatures, publicKeys, key, solution, sigHashType, sigVersion) { + // 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 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 if (solution.type === bscript.types.MULTISIG) { + 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 + signatures[i] = calculateSignature(tx, nIn, key, solution.script, sigHashType, sigVersion, txOutValue) + } + } + } else { + throw new Error('signStandard can only sign SIGNABLE_SCRIPTS') + } + + if (!didSign) { + throw new Error('Signing input with wrong private key') + } + + return [signatures, publicKeys] +} + +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 (bscript.P2SH_SCRIPTS.indexOf(solution.type)) { + 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) + } + } + + this.requiredSigs = this.publicKeys.length + + return solution +} + +function serializeStandard (outputType, signatures, publicKeys) { + // When adding a new script type, edit here + var chunks = [] + switch (outputType) { + case bscript.types.P2PK: + if (signatures.length === 1) { + chunks = [signatures[ 0 ]] + } + break + case bscript.types.P2PKH: + if (signatures.length === 1) { + chunks = [signatures[ 0 ], publicKeys[ 0 ]] + } + break + case bscript.types.MULTISIG: + chunks.push(new Buffer(0)) + chunks = chunks.concat(signatures.map(function (signature) { + if (signature instanceof Buffer === false) { + throw new Error('debugging probably required') + } + return signature + })) + break + default: + throw new Error('serializeStandardChunks only works with a SIGNABLE_SCRIPT') + } + + return chunks +} + +function serializeSigData (signatures, publicKeys, scriptPubKey, redeemScript, witnessScript) { + var sigData = { + scriptSig: EMPTY_SCRIPT, + witness: [] + } + var type = scriptPubKey.type + if (scriptPubKey.canSign) { + sigData.scriptSig = pushAll(serializeStandard(type, signatures, 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) { + sigData.scriptSig = pushAll(serializeStandard(type, signatures, publicKeys)) + } + } + + if (type === bscript.types.P2WPKH) { + sigData.witness = serializeStandard(bscript.types.P2PKH, signatures, publicKeys) + } else if (type === bscript.types.P2WSH) { + if (witnessScript === undefined) { + throw new Error('Witness script not provided') + } + type = witnessScript.type + if (witnessScript.canSign) { + sigData.scriptSig = EMPTY_SCRIPT + sigData.witness = serializeStandard(type, signatures, publicKeys) + sigData.witness.push(witnessScript.script) + } + } + + if (p2sh) { + sigData.scriptSig = bscript.scriptHash.input.encode(sigData.scriptSig, redeemScript.script) + } + + return sigData +} + +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 + * @constructor + */ +function TxSigner (tx) { + if (tx === undefined || (tx instanceof Transaction) === false) { + throw new Error('A transaction is required for TxSigner') + } + + this.tx = tx.clone() + this.states = [] +} + +/** + * Sign a transaction. + * + * @param nIn - the input to sign + * @param key - the private key to sign with + * @param sigHashType - SIGHASH type to sign with + * @param opts - optional data required to solve UTXO + */ +TxSigner.prototype.sign = function (nIn, key, opts, sigHashType) { + typeforce(types.Number, nIn) + typeforce(types.maybe(Number), sigHashType) + if (sigHashType === undefined) { + sigHashType = Transaction.SIGHASH_ALL + } + // 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)) { + throw new Error('Unsignable input: ', nIn) + } + + return true +} + +/** + * Produce a Transaction with our changes. + */ +TxSigner.prototype.done = function () { + var tx = this.tx.clone() + + var states = this.states + for (var i = 0, l = tx.ins.length; i < l; i++) { + if (this.states[i] !== undefined) { + var sigData = states[i].serializeSigData() + tx.ins[i].script = sigData.scriptSig + tx.ins[i].witness = sigData.witness + } + } + return tx +} + +module.exports = TxSigner