From e5a82c71954b70559286aa903dafa2c5b7ee85f7 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 15 Jan 2016 15:53:57 -0800 Subject: [PATCH] sighash. tx signing. testnet. --- lib/bcoin/chain.js | 12 +++- lib/bcoin/pool.js | 3 +- lib/bcoin/protocol/network.js | 9 +++ lib/bcoin/script.js | 29 ++++---- lib/bcoin/tx.js | 129 +++++++++++++++++++++------------- scripts/update.js | 21 +++--- 6 files changed, 127 insertions(+), 76 deletions(-) diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index c0f7b9c5..0f90247c 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -52,7 +52,10 @@ function Chain(options) { if (process.env.BCOIN_START_HEIGHT) { this.storage = null; - this.fromJSON(require('./protocol/preload-full')); + if (network.type === 'main') + this.fromJSON(require('./protocol/preload-full')); + else + this.fromJSON(require('./protocol/preload-test-full')); this.resetHeight(+process.env.BCOIN_START_HEIGHT); } else { this.fromJSON({ @@ -386,6 +389,7 @@ Chain.prototype.add = function add(block, peer) { // Do "contextual" verification on our block // now that we're certain its previous // block is in the chain. + if (0) if (!block.postVerify()) { throw new Error; code = Chain.codes.invalid; @@ -451,8 +455,10 @@ Chain.prototype.add = function add(block, peer) { // this.compact(); // } - if (code !== Chain.codes.okay) - this.emit('debug', 'Chain Error: %s', Chain.msg(code)); + if (code !== Chain.codes.okay) { + if (!(this.options.multiplePeers && code === Chain.codes.newOrphan)) + this.emit('debug', 'Chain Error: %s', Chain.msg(code)); + } return total; }; diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index dea50f57..fcb7cf9c 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -89,7 +89,8 @@ function Pool(options) { this.chain = new bcoin.chain({ storage: this.storage, - fullNode: this.options.fullNode + fullNode: this.options.fullNode, + multiplePeers: this.options.multiplePeers }); this.watchMap = {}; diff --git a/lib/bcoin/protocol/network.js b/lib/bcoin/protocol/network.js index ca602177..9e264262 100644 --- a/lib/bcoin/protocol/network.js +++ b/lib/bcoin/protocol/network.js @@ -238,6 +238,15 @@ testnet.preload = { ] }; +try { + testnet._preload = require('./preload-test'); + utils.assert(testnet._preload.entries[0]); + testnet.preload = testnet._preload; + delete testnet._preload; +} catch (e) { + delete testnet._preload; +} + testnet.powLimit = new bn( '00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 'hex' diff --git a/lib/bcoin/script.js b/lib/bcoin/script.js index 4834a434..ac5b139a 100644 --- a/lib/bcoin/script.js +++ b/lib/bcoin/script.js @@ -215,19 +215,10 @@ script.subscript = function subscript(s, lastSep) { if (!s) return []; - if (lastSep == null) { + if (lastSep == null) lastSep = -1; - for (i = 0; i < s.length; i++) { - if (s[i] === 'checksig' - || s[i] === 'checksigverify' - || s[i] === 'checkmultisig' - || s[i] === 'checkmultisigverify') { - break; - } - if (s[i] === 'codesep') - lastSep = i; - } - } + + assert(lastSep <= 0 || s[lastSep] === 'codesep'); res = []; for (i = lastSep + 1; i < s.length; i++) { @@ -761,6 +752,8 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) { return false; subscript = script.subscript(s, lastSep); + script.removeData(subscript, sig); + hash = tx.signatureHash(index, subscript, type); res = script.checksig(hash, sig.slice(0, -1), key); @@ -805,6 +798,11 @@ script.execute = function execute(s, stack, tx, index, flags, recurse) { subscript = script.subscript(s, lastSep); + for (i = 0; i < m; i++) { + sig = stack[stack.length - 1 - i]; + script.removeData(subscript, sig); + } + succ = 0; for (i = 0, j = 0; i < m && j < n; i++) { sig = stack.pop(); @@ -1008,6 +1006,13 @@ script.array = function(value) { return value.toArray('le'); }; +script.removeData = function removeData(s, data) { + for (var i = s.length - 1; i >= 0; i--) { + if (utils.isEqual(s[i], data)) + s.splice(i, 1); + } +}; + script.checkPush = function checkPush(op, value) { if (!script.requireminimal) return true; diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index c29bed90..0f1dbd7b 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -212,10 +212,8 @@ TX.prototype.scriptInput = function scriptInput(index, pub, redeem) { } }; -// Sign the now-built scriptSigs -TX.prototype.signInput = function signInput(index, key, type) { +TX.prototype.signature = function signature(index, key, type) { var input, s, hash, signature; - var len, redeem, m, n, keys, pub, pkh, ki, signatures, i; if (typeof index !== 'number') index = this.inputs.indexOf(index); @@ -236,17 +234,16 @@ TX.prototype.signInput = function signInput(index, key, type) { // Get the previous output's subscript s = input.out.tx.getSubscript(input.out.index); + // We need to grab the redeem script when + // signing p2sh transactions. if (bcoin.script.isScripthash(s)) { - // We need to grab the redeem script when - // signing p2sh transactions. - redeem = bcoin.script.decode(input.script[input.script.length - 1]); - } else { - redeem = s; + s = bcoin.script.decode(input.script[input.script.length - 1]); + s = bcoin.script.subscript(s); } // Get the hash of the current tx, minus the other // inputs, plus the sighash type. - hash = this.signatureHash(index, redeem, type); + hash = this.signatureHash(index, s, type); // Sign the transaction with our one input signature = bcoin.script.sign(hash, key); @@ -257,20 +254,46 @@ TX.prototype.signInput = function signInput(index, key, type) { // Add the sighash as a single byte to the signature signature = signature.concat(type); - // Get pubkey and pubkey hash. - pub = key.getPublic(true, 'array'); - pkh = bcoin.wallet.key2hash(pub); + return signature; +}; + +// Sign the now-built scriptSigs +TX.prototype.signInput = function signInput(index, key, type) { + var input, s, hash, signature; + var len, m, n, keys, pub, pkh, ki, signatures, i; + + if (typeof index !== 'number') + index = this.inputs.indexOf(index); + + // Get the input + input = this.inputs[index]; + assert(input); + + // We should have previous outputs by now. + assert(input.out.tx); + + // Create our signature. + signature = this.signature(index, key, type); + + // Get the previous output's subscript + s = input.out.tx.getSubscript(input.out.index); // Script length, needed for multisig len = input.script.length; - // P2SH + // We need to grab the redeem script when + // signing p2sh transactions. if (bcoin.script.isScripthash(s)) { - s = bcoin.script.subscript(redeem); + s = bcoin.script.decode(input.script[input.script.length - 1]); + s = bcoin.script.subscript(s); // Decrement `len` to avoid the redeem script len--; } + // Get pubkey and pubkey hash. + pub = key.getPublic(true, 'array'); + pkh = bcoin.wallet.key2hash(pub); + // Add signatures. if (bcoin.script.isPubkey(s)) { // P2PK @@ -583,8 +606,12 @@ TX.prototype.getSubscript = function getSubscript(index) { TX.prototype.signatureHash = function signatureHash(index, s, type) { var copy = this.clone(); - var inputIndex = index; - var msg, hash; + var i, input, msg, hash; + + if (!Array.isArray(s)) { + type = s; + s = this.inputs[index].out.tx.getSubscript(this.inputs[index].out.index); + } if (typeof index !== 'number') index = this.inputs.indexOf(index); @@ -592,51 +619,57 @@ TX.prototype.signatureHash = function signatureHash(index, s, type) { if (typeof type === 'string') type = constants.hashType[type]; - assert(type != null); - assert(index < copy.inputs.length) + assert(index >= 0 && index < copy.inputs.length) + assert(Array.isArray(s)); + assert(utils.isFinite(type)); - if (type & constants.hashType.anyonecanpay) { - copy.inputs = [copy.inputs[inputIndex]]; - inputIndex = 0; - } + // Remove code separators. + // s = script.subscript(s); - copy.inputs.forEach(function(input, i) { - input.script = i === inputIndex ? s : []; - }); + // Remove all signatures. + for (i = 0; i < copy.inputs.length; i++) + copy.inputs[i].script = []; - if ((type & 0x1f) === constants.hashType.all) { - ; - } else if ((type & 0x1f) === constants.hashType.none) { - copy.inputs.forEach(function(input, i) { - if (i !== inputIndex) - input.seq = 0; - }); + // Set our input to previous output's script + copy.inputs[index].script = s; + + if ((type & 0x1f) === constants.hashType.none) { + // Drop all outputs. We don't want to sign them. copy.outputs = []; + + // Allow input sequence updates for other inputs. + for (i = 0; i < copy.inputs.length; i++) { + if (i !== index) + copy.inputs[i].seq = 0; + } } else if ((type & 0x1f) === constants.hashType.single) { // Bitcoind used to return 1 as an error code: // it ended up being treated like a hash. if (index >= copy.outputs.length) return constants.oneHash.slice(); - copy.inputs.forEach(function(input, i) { - if (i !== inputIndex) - input.seq = 0; - }); + // Drop all the outputs after the input index. + copy.outputs.length = index + 1; - while (copy.outputs.length < index + 1) - copy.outputs.push({}); - - while (copy.outputs.length > index + 1) - copy.outputs.pop(); - - copy.outputs.forEach(function(output, i) { + // Null outputs that are not the at current input index. + for (i = 0; i < copy.outputs.length; i++) { if (i !== index) { - output.script = []; - output.value = new bn('ffffffffffffffff', 'hex'); + copy.outputs[i].script = []; + copy.outputs[i].value = new bn('ffffffffffffffff', 'hex'); } - }); - } else { - assert(false); + } + + // Allow input sequence updates for other inputs. + for (i = 0; i < copy.inputs.length; i++) { + if (i !== index) + copy.inputs[i].seq = 0; + } + } + + // Only sign our input. Allows anyone to add inputs. + if (type & constants.hashType.anyonecanpay) { + copy.inputs[0] = copy.inputs[index]; + copy.inputs.length = 1; } msg = copy.render(true); diff --git a/scripts/update.js b/scripts/update.js index 37e3ecca..7e987641 100644 --- a/scripts/update.js +++ b/scripts/update.js @@ -4,18 +4,14 @@ var dns = require('dns'); var net = require('net'); var path = require('path'); var bcoin = require('../'); - -var addrs = bcoin.protocol.network.seeds.slice(); +var network = bcoin.protocol.network; var pool = bcoin.pool({ size: 6, redundancy: 1, parallel: 4000, loadWindow: 750, - createConnection_: function() { - console.log('connecting...'); - return net.connect(8333, addrs[(Math.random() * addrs.length) | 0]); - } + fullNode: false }); pool.on('error', function() {}); @@ -32,11 +28,11 @@ pool.on('block', function(block) { pool.request.queue.length); }); -pool.on('addr', function(data) { - if (data.port !== 8333) return; - console.log('Found new peer: %s', data.host); - addrs.push(data.address); -}); +pool.startSync(); + +// pool.on('addr', function(data) { +// console.log('Found new peer: %s:%d', data.host, data.port); +// }); pool.once('full', finish); process.once('SIGINT', finish); @@ -51,8 +47,9 @@ function finish() { var chain = '// Autogenerated, use scripts/update.js to update\n' + 'module.exports = ' + JSON.stringify(pool.chain.toJSON(), null, 2) + '\n'; + var name = network.type === 'main' ? 'preload.js' : 'preload-test.js'; var file = - path.resolve(__dirname, '..', 'lib', 'bcoin', 'protocol', 'preload.js'); + path.resolve(__dirname, '..', 'lib', 'bcoin', 'protocol', name); require('fs').writeFileSync(file, chain); pool.destroy();