diff --git a/etc/sample.conf b/etc/sample.conf index 9c9f7836..6d73b57e 100644 --- a/etc/sample.conf +++ b/etc/sample.conf @@ -22,7 +22,7 @@ log-file: true # witness: true # prune: false use-checkpoints: true -coin-cache: true +coin-cache: 40000000 index-tx: false index-address: false diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index 8bd5c406..1cef54bc 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -280,7 +280,7 @@ Chain.prototype.isGenesis = function isGenesis(block) { Chain.prototype.verify = co(function* verify(block, prev) { var ret = new VerifyResult(); var i, err, height, ts, tx, medianTime; - var commitmentHash, ancestors, state; + var commit, ancestors, state; // Skip the genesis block. if (this.isGenesis(block)) @@ -358,15 +358,15 @@ Chain.prototype.verify = co(function* verify(block, prev) { // Check the commitment hash for segwit. if (state.hasWitness()) { - commitmentHash = block.commitmentHash; - if (commitmentHash) { + commit = block.getCommitmentHash(); + if (commit) { // These are totally malleable. Someone // may have even accidentally sent us // the non-witness version of the block. // We don't want to consider this block // "invalid" if either of these checks // fail. - if (!block.witnessNonce) { + if (!block.getWitnessNonce()) { err = new VerifyError(block, 'invalid', 'bad-witness-merkle-size', @@ -375,7 +375,7 @@ Chain.prototype.verify = co(function* verify(block, prev) { throw err; } - if (commitmentHash !== block.getCommitmentHash('hex')) { + if (!util.equal(commit, block.createCommitmentHash())) { err = new VerifyError(block, 'invalid', 'bad-witness-merkle-match', @@ -388,7 +388,7 @@ Chain.prototype.verify = co(function* verify(block, prev) { // Blocks that do not commit to // witness data cannot contain it. - if (!commitmentHash) { + if (!commit) { if (block.hasWitness()) { err = new VerifyError(block, 'invalid', diff --git a/lib/crypto/crypto.js b/lib/crypto/crypto.js index 6d7a2917..e90ae601 100644 --- a/lib/crypto/crypto.js +++ b/lib/crypto/crypto.js @@ -217,59 +217,64 @@ crypto.hkdfExpand = function hkdfExpand(prk, info, len, alg) { /** * Build a merkle tree from leaves. + * Note that this will mutate the `leaves` array! * @param {Buffer[]} leaves - * @returns {Buffer[]} Tree (in rare cases this may return null). + * @returns {MerkleTree} */ -crypto.buildMerkleTree = function buildMerkleTree(leaves) { - var tree = leaves.slice(); +crypto.createMerkleTree = function createMerkleTree(leaves) { + var nodes = leaves; var size = leaves.length; - var i, j, i2, hash, left, right, buf; + var malleated = false; + var i, j, k, hash, left, right, lr; - if (size > 1) - buf = new Buffer(64); + if (size === 0) { + hash = new Buffer(32); + hash.fill(0); + nodes.push(hash); + return new MerkleTree(nodes, malleated); + } + + lr = new Buffer(64); for (j = 0; size > 1; size = ((size + 1) / 2) | 0) { for (i = 0; i < size; i += 2) { - i2 = Math.min(i + 1, size - 1); - left = tree[j + i]; - right = tree[j + i2]; + k = Math.min(i + 1, size - 1); + left = nodes[j + i]; + right = nodes[j + k]; - if (i2 === i + 1 && i2 + 1 === size + if (k === i + 1 && k + 1 === size && left.compare(right) === 0) { - return; + malleated = true; } - left.copy(buf, 0); - right.copy(buf, 32); - hash = crypto.hash256(buf); + left.copy(lr, 0); + right.copy(lr, 32); - tree.push(hash); + hash = crypto.hash256(lr); + + nodes.push(hash); } j += size; } - if (tree.length === 0) - return; - - return tree; + return new MerkleTree(nodes, malleated); }; if (native) - crypto.buildMerkleTree = native.buildMerkleTree; + crypto.createMerkleTree = native.createMerkleTree; /** * Calculate merkle root from leaves. * @param {Buffer[]} leaves - * @returns {Buffer?} Merkle root. + * @returns {MerkleRoot} */ -crypto.getMerkleRoot = function getMerkleRoot(leaves) { - var tree = crypto.buildMerkleTree(leaves); - if (!tree) - return; - - return tree[tree.length - 1]; +crypto.createMerkleRoot = function createMerkleRoot(leaves) { + var tree = crypto.createMerkleTree(leaves); + var hash = tree.nodes[tree.nodes.length - 1]; + var malleated = tree.malleated; + return new MerkleRoot(hash, malleated); }; /** @@ -279,16 +284,16 @@ crypto.getMerkleRoot = function getMerkleRoot(leaves) { * @returns {Buffer[]} branch */ -crypto.getMerkleBranch = function getMerkleBranch(index, leaves) { - var tree = crypto.buildMerkleTree(leaves); +crypto.createMerkleBranch = function createMerkleBranch(index, leaves) { var size = leaves.length; + var tree = crypto.createMerkleTree(leaves); var branch = []; var j = 0; var i; for (; size > 1; size = (size + 1) / 2 | 0) { i = Math.min(index ^ 1, size - 1); - branch.push(tree[j + i]); + branch.push(tree.nodes[j + i]); index >>>= 1; j += size; } @@ -304,26 +309,26 @@ crypto.getMerkleBranch = function getMerkleBranch(index, leaves) { * @returns {Buffer} Hash. */ -crypto.checkMerkleBranch = function checkMerkleBranch(hash, branch, index) { - var i, otherside, buf; +crypto.verifyMerkleBranch = function verifyMerkleBranch(hash, branch, index) { + var i, otherside, lr; if (branch.length === 0) return hash; - buf = new Buffer(64); + lr = new Buffer(64); for (i = 0; i < branch.length; i++) { otherside = branch[i]; if (index & 1) { - otherside.copy(buf, 0); - hash.copy(buf, 32); + otherside.copy(lr, 0); + hash.copy(lr, 32); } else { - hash.copy(buf, 0); - otherside.copy(buf, 32); + hash.copy(lr, 0); + otherside.copy(lr, 32); } - hash = crypto.hash256(buf); + hash = crypto.hash256(lr); index >>>= 1; } @@ -331,7 +336,7 @@ crypto.checkMerkleBranch = function checkMerkleBranch(hash, branch, index) { }; if (native) - crypto.checkMerkleBranch = native.checkMerkleBranch; + crypto.verifyMerkleBranch = native.verifyMerkleBranch; /** * Encrypt with aes-256-cbc. @@ -465,3 +470,17 @@ crypto.randomRange = function randomRange(min, max) { var num = crypto.randomInt(); return Math.floor((num / 0x100000000) * (max - min) + min); }; + +/* + * Helpers + */ + +function MerkleTree(nodes, malleated) { + this.nodes = nodes; + this.malleated = malleated; +} + +function MerkleRoot(hash, malleated) { + this.hash = hash; + this.malleated = malleated; +} diff --git a/lib/crypto/index.js b/lib/crypto/index.js index 952f4dae..f97542be 100644 --- a/lib/crypto/index.js +++ b/lib/crypto/index.js @@ -19,10 +19,10 @@ exports.scrypt = crypto.scrypt; exports.scryptAsync = crypto.scryptAsync; exports.hkdfExtract = crypto.hkdfExtract; exports.hkdfExpand = crypto.hkdfExpand; -exports.buildMerkleTree = crypto.buildMerkleTree; -exports.getMerkleRoot = crypto.getMerkleRoot; -exports.getMerkleBranch = crypto.getMerkleBranch; -exports.checkMerkleBranch = crypto.checkMerkleBranch; +exports.createMerkleTree = crypto.createMerkleTree; +exports.createMerkleRoot = crypto.createMerkleRoot; +exports.createMerkleBranch = crypto.createMerkleBranch; +exports.verifyMerkleBranch = crypto.verifyMerkleBranch; exports.encipher = crypto.encipher; exports.decipher = crypto.decipher; exports.ccmp = crypto.ccmp; diff --git a/lib/mining/minerblock.js b/lib/mining/minerblock.js index cfe2d7e4..76128162 100644 --- a/lib/mining/minerblock.js +++ b/lib/mining/minerblock.js @@ -107,7 +107,16 @@ MinerBlock.prototype._init = function _init() { var scale = constants.WITNESS_SCALE_FACTOR; var block = this.block; var cb = this.coinbase; - var input, output, hash, witnessNonce; + var input, output, nonce; + + // Setup our block. + block.version = this.version; + block.prevBlock = this.tip.hash; + block.merkleRoot = constants.NULL_HASH; + block.ts = Math.max(time.now(), this.tip.ts + 1); + block.bits = this.bits; + block.nonce = 0; + block.height = this.height; // Coinbase input. input = new Input(); @@ -145,26 +154,16 @@ MinerBlock.prototype._init = function _init() { if (this.witness) { // Our witness nonce is the hash256 // of the previous block hash. - hash = new Buffer(this.tip.hash, 'hex'); - witnessNonce = crypto.hash256(hash); + nonce = block.createWitnessNonce(); // Set up the witness nonce. - input.witness.set(0, witnessNonce); + input.witness.set(0, nonce); input.witness.compile(); // Commitment output. cb.outputs.push(new Output()); } - // Setup our block. - block.version = this.version; - block.prevBlock = this.tip.hash; - block.merkleRoot = constants.NULL_HASH; - block.ts = Math.max(time.now(), this.tip.ts + 1); - block.bits = this.bits; - block.nonce = 0; - block.height = this.height; - block.txs.push(cb); // Update coinbase since our coinbase was added. @@ -196,7 +195,7 @@ MinerBlock.prototype.updateCommitment = function updateCommitment() { var hash; // Recalculate witness merkle root. - hash = this.block.getCommitmentHash(); + hash = this.block.createCommitmentHash(); // Update commitment. output.script.clear(); @@ -253,7 +252,7 @@ MinerBlock.prototype.updateMerkle = function updateMerkle() { this.block.ts = Math.max(time.now(), this.tip.ts + 1); // Recalculate merkle root. - this.block.merkleRoot = this.block.getMerkleRoot('hex'); + this.block.merkleRoot = this.block.createMerkleRoot('hex'); }; /** diff --git a/lib/primitives/block.js b/lib/primitives/block.js index 73525765..c80e3776 100644 --- a/lib/primitives/block.js +++ b/lib/primitives/block.js @@ -146,35 +146,35 @@ Block.prototype.getRaw = function getRaw() { /** * Calculate real size and size of the witness bytes. - * @returns {Object} Contains `size` and `witnessSize`. + * @returns {Object} Contains `total` and `witness`. */ Block.prototype.getSizes = function getSizes() { + var sizes = new BlockSizes(); var writer; if (this._size !== -1) { - return { - size: this._size, - witnessSize: this._witnessSize - }; + sizes.total = this._size; + sizes.witness = this._witnessSize; + return sizes; } if (!this.mutable) { assert(!this._raw); this.getRaw(); - return { - size: this._size, - witnessSize: this._witnessSize - }; + sizes.total = this._size; + sizes.witness = this._witnessSize; + return sizes; } writer = new BufferWriter(); + this.toRaw(writer); - return { - size: writer.written, - witnessSize: this._lastWitnessSize - }; + sizes.total = writer.written; + sizes.witness = this._lastWitnessSize; + + return sizes; }; /** @@ -194,8 +194,8 @@ Block.prototype.getVirtualSize = function getVirtualSize() { Block.prototype.getWeight = function getWeight() { var sizes = this.getSizes(); - var base = sizes.size - sizes.witnessSize; - return base * (constants.WITNESS_SCALE_FACTOR - 1) + sizes.size; + var base = sizes.total - sizes.witness; + return base * (constants.WITNESS_SCALE_FACTOR - 1) + sizes.total; }; /** @@ -204,7 +204,7 @@ Block.prototype.getWeight = function getWeight() { */ Block.prototype.getSize = function getSize() { - return this.getSizes().size; + return this.getSizes().total; }; /** @@ -214,7 +214,7 @@ Block.prototype.getSize = function getSize() { Block.prototype.getBaseSize = function getBaseSize() { var sizes = this.getSizes(); - return sizes.size - sizes.witnessSize; + return sizes.total - sizes.witness; }; /** @@ -280,106 +280,148 @@ Block.prototype.indexOf = function indexOf(hash) { }; /** - * Calculate merkle root. + * Calculate merkle root. Returns null + * if merkle tree has been malleated. * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {Buffer|Hash} hash + * @returns {Hash|null} */ -Block.prototype.getMerkleRoot = function getMerkleRoot(enc) { +Block.prototype.createMerkleRoot = function createMerkleRoot(enc) { var leaves = []; var i, root; for (i = 0; i < this.txs.length; i++) leaves.push(this.txs[i].hash()); - root = crypto.getMerkleRoot(leaves); + root = crypto.createMerkleRoot(leaves); - if (!root) - return; + if (root.malleated) + return null; return enc === 'hex' - ? root.toString('hex') - : root; + ? root.hash.toString('hex') + : root.hash; +}; + +/** + * Create a witness nonce (for mining). + * @returns {Buffer} + */ + +Block.prototype.createWitnessNonce = function createWitnessNonce() { + return crypto.hash256(new Buffer(this.prevBlock, 'hex')); }; /** * Calculate commitment hash (the root of the * witness merkle tree hashed with the witnessNonce). * @param {String?} enc - Encoding, can be `'hex'` or null. - * @returns {Buffer|Hash} hash + * @returns {Hash} + */ + +Block.prototype.createCommitmentHash = function createCommitmentHash(enc) { + var nonce = this.getWitnessNonce(); + var leaves = []; + var i, root, data, hash; + + assert(nonce, 'No witness nonce present.'); + + leaves.push(constants.ZERO_HASH); + + for (i = 1; i < this.txs.length; i++) + leaves.push(this.txs[i].witnessHash()); + + root = crypto.createMerkleRoot(leaves); + + // Note: malleation check ignored here. + // assert(!root.malleated); + + data = util.concat(root.hash, nonce); + + hash = crypto.hash256(data); + + return enc === 'hex' + ? hash.toString('hex') + : hash; +}; + +/** + * Retrieve the merkle root from the block header. + * @param {String?} enc + * @returns {Hash} + */ + +Block.prototype.getMerkleRoot = function getMerkleRoot(enc) { + if (enc === 'hex') + return this.merkleRoot; + return new Buffer(this.merkleRoot, 'hex'); +}; + +/** + * Retrieve the witness nonce from the + * coinbase's witness vector (if present). + * @returns {Buffer|null} + */ + +Block.prototype.getWitnessNonce = function getWitnessNonce() { + var coinbase = this.txs[0]; + var input; + + if (!coinbase) + return null; + + if (coinbase.inputs.length !== 1) + return null; + + input = coinbase.inputs[0]; + + if (input.witness.items.length !== 1) + return null; + + if (input.witness.items[0].length !== 32) + return null; + + return input.witness.items[0]; +}; + +/** + * Retrieve the commitment hash + * from the coinbase's outputs. + * @param {String?} enc + * @returns {Hash|null} */ Block.prototype.getCommitmentHash = function getCommitmentHash(enc) { - var leaves = []; - var witnessNonce = this.witnessNonce; - var i, buf, witnessRoot, commitmentHash; + var hash = this._commitmentHash; + var i, coinbase, output; - if (!witnessNonce) - return; + if (!hash) { + coinbase = this.txs[0]; - for (i = 0; i < this.txs.length; i++) - leaves.push(this.txs[i].witnessHash()); + if (!coinbase) + return null; - witnessRoot = crypto.getMerkleRoot(leaves); + for (i = coinbase.outputs.length - 1; i >= 0; i--) { + output = coinbase.outputs[i]; - if (!witnessRoot) - return; + if (output.script.isCommitment()) { + hash = output.script.getCommitmentHash(); - buf = new Buffer(64); - witnessRoot.copy(buf, 0); - witnessNonce.copy(buf, 32); + if (!this.mutable) + this._commitmentHash = hash; - commitmentHash = crypto.hash256(buf); - - return enc === 'hex' - ? commitmentHash.toString('hex') - : commitmentHash; -}; - -Block.prototype.__defineGetter__('witnessNonce', function() { - var coinbase = this.txs[0]; - - if (!coinbase) - return; - - if (coinbase.inputs.length !== 1) - return; - - if (coinbase.inputs[0].witness.items.length !== 1) - return; - - if (coinbase.inputs[0].witness.items[0].length !== 32) - return; - - return coinbase.inputs[0].witness.items[0]; -}); - -Block.prototype.__defineGetter__('commitmentHash', function() { - var i, coinbase, script, commitmentHash; - - if (this._commitmentHash) - return this._commitmentHash; - - coinbase = this.txs[0]; - - if (!coinbase) - return; - - for (i = coinbase.outputs.length - 1; i >= 0; i--) { - script = coinbase.outputs[i].script; - if (script.isCommitment()) { - commitmentHash = script.getCommitmentHash(); - commitmentHash = commitmentHash.toString('hex'); - - if (!this.mutable) - this._commitmentHash = commitmentHash; - - break; + break; + } } + + if (!hash) + return null; } - return commitmentHash; -}); + return enc === 'hex' + ? hash.toString('hex') + : hash; +}; /** * Do non-contextual verification on the block. Including checking the block @@ -441,7 +483,7 @@ Block.prototype._verify = function _verify(ret) { } // Check merkle root - merkle = this.getMerkleRoot('hex'); + merkle = this.createMerkleRoot('hex'); // If the merkle is mutated, // we have duplicate txs. @@ -563,6 +605,7 @@ Block.prototype.getPrevout = function getPrevout() { */ Block.prototype.inspect = function inspect() { + var commitmentHash = this.getCommitmentHash('hex'); return { hash: this.rhash, height: this.height, @@ -572,8 +615,8 @@ Block.prototype.inspect = function inspect() { version: util.hex32(this.version), prevBlock: util.revHex(this.prevBlock), merkleRoot: util.revHex(this.merkleRoot), - commitmentHash: this.commitmentHash - ? util.revHex(this.commitmentHash) + commitmentHash: commitmentHash + ? util.revHex(commitmentHash) : null, ts: this.ts, bits: this.bits, @@ -783,6 +826,15 @@ Block.isBlock = function isBlock(obj) { && typeof obj.getClaimed === 'function'; }; +/* + * Helpers + */ + +function BlockSizes() { + this.total = 0; + this.witness = 0; +} + /* * Expose */ diff --git a/lib/primitives/tx.js b/lib/primitives/tx.js index 38578711..23b11f95 100644 --- a/lib/primitives/tx.js +++ b/lib/primitives/tx.js @@ -356,24 +356,24 @@ TX.prototype.getRaw = function getRaw() { */ TX.prototype.getSizes = function getSizes() { + var sizes = new TXSizes(); var writer; if (this.mutable) { assert(!this._raw); writer = new BufferWriter(); this.toRaw(writer); - return { - size: writer.written, - witnessSize: this._lastWitnessSize - }; + sizes.total = writer.written; + sizes.witness = this._lastWitnessSize; + return sizes; } this.getRaw(); - return { - size: this._size, - witnessSize: this._witnessSize - }; + sizes.total = this._size; + sizes.witness = this._witnessSize; + + return sizes; }; /** @@ -395,8 +395,8 @@ TX.prototype.getVirtualSize = function getVirtualSize() { TX.prototype.getWeight = function getWeight() { var sizes = this.getSizes(); - var base = sizes.size - sizes.witnessSize; - return base * (constants.WITNESS_SCALE_FACTOR - 1) + sizes.size; + var base = sizes.total - sizes.witness; + return base * (constants.WITNESS_SCALE_FACTOR - 1) + sizes.total; }; /** @@ -406,7 +406,7 @@ TX.prototype.getWeight = function getWeight() { */ TX.prototype.getSize = function getSize() { - return this.getSizes().size; + return this.getSizes().total; }; /** @@ -418,7 +418,7 @@ TX.prototype.getSize = function getSize() { TX.prototype.getBaseSize = function getBaseSize() { var sizes = this.getSizes(); - return sizes.size - sizes.witnessSize; + return sizes.total - sizes.witness; }; /** @@ -2541,6 +2541,15 @@ TX.isTX = function isTX(obj) { && typeof obj.witnessHash === 'function'; }; +/* + * Helpers + */ + +function TXSizes() { + this.total = 0; + this.witness = 0; +} + /* * Expose */ diff --git a/lib/script/script.js b/lib/script/script.js index 266261d8..1d37ac39 100644 --- a/lib/script/script.js +++ b/lib/script/script.js @@ -3357,7 +3357,7 @@ Script.verifyMast = function verifyMast(program, stack, output, flags, tx, i) { } scriptRoot = crypto.hash256(scriptRoot.render()); - scriptRoot = crypto.checkMerkleBranch(scriptRoot, path, pos); + scriptRoot = crypto.verifyMerkleBranch(scriptRoot, path, pos); mastRoot.writeBytes(scriptRoot); mastRoot = crypto.hash256(mastRoot.render()); diff --git a/lib/utils/util.js b/lib/utils/util.js index 91f9d1fb..c257f5b2 100644 --- a/lib/utils/util.js +++ b/lib/utils/util.js @@ -186,12 +186,15 @@ util.isHex = function isHex(obj) { util.equal = function equal(a, b) { var i; - if (!Buffer.isBuffer(a)) + if (a == null) return false; - if (!Buffer.isBuffer(b)) + if (b == null) return false; + assert(Buffer.isBuffer(a)); + assert(Buffer.isBuffer(b)); + if (a.compare) return a.compare(b) === 0; diff --git a/package.json b/package.json index 69d07883..34ab0789 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "elliptic": "6.3.2" }, "optionalDependencies": { - "bcoin-native": "0.0.11", + "bcoin-native": "0.0.12", "leveldown": "1.5.0", "secp256k1": "3.2.0", "socket.io": "1.4.8", diff --git a/test/block-test.js b/test/block-test.js index 17f32374..25a57254 100644 --- a/test/block-test.js +++ b/test/block-test.js @@ -126,7 +126,7 @@ describe('Block', function() { '8cc72c02a958de5a8b35a23bb7e3bced8bf840cc0a4e1c820000000000000000'); assert.equal(block.rhash, '0000000000000000821c4e0acc40f88bedbce3b73ba2358b5ade58a9022cc78c'); - assert.equal(block.merkleRoot, block.getMerkleRoot('hex')); + assert.equal(block.merkleRoot, block.createMerkleRoot('hex')); }); it('should create a merkle block', function() {