diff --git a/.gitignore b/.gitignore index 5f9a88d..141f7e8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,14 +2,16 @@ package-lock.json node_modules dist/ -baseTrie.js -checkpoint-interface.js -index.js -prioritizedTaskExecutor.js -proof.js -readStream.js -secure-interface.js -secure.js -scratchReadStream.js -trieNode.js -util.js +/baseTrie.js +/checkpointTrie.js +/db.js +/index.js +/karma-conf.js +/prioritizedTaskExecutor.js +/proof.js +/readStream.js +/scratch.js +/scratchReadStream.js +/secure.js +/trieNode.js +util/ diff --git a/src/baseTrie.js b/src/baseTrie.js index b7a3796..3c90e8a 100644 --- a/src/baseTrie.js +++ b/src/baseTrie.js @@ -31,7 +31,7 @@ module.exports = class Trie { this.db = db || new DB() Object.defineProperty(this, 'root', { - set(value) { + set (value) { if (value) { value = ethUtil.toBuffer(value) assert(value.length === 32, 'Invalid root length. Roots are 32 bytes') @@ -41,7 +41,7 @@ module.exports = class Trie { this._root = value }, - get() { + get () { return this._root } }) @@ -49,6 +49,38 @@ module.exports = class Trie { this.root = root } + static fromProof(proofNodes, cb, proofTrie){ + let opStack = proofNodes.map((nodeValue) => { + return {type: 'put', key: ethUtil.keccak(nodeValue), value: ethUtil.toBuffer(nodeValue)} + }) + + if(!proofTrie){ + proofTrie = new Trie() + if(opStack[0]){ proofTrie.root = opStack[0].key} + } + + proofTrie.db.batch(opStack, (e) => { + cb(e, proofTrie) + }) + } + + static prove (trie, key, cb) { + trie.findPath(key, function (err, node, remaining, stack) { + if (err) return cb(err) + let p = stack.map((stackElem)=>{ return stackElem.serialize() }) + cb(null, p) + }) + } + + static verifyProof (rootHash, key, proofNodes, cb) { + Trie.fromProof(proofNodes, (error,proofTrie)=>{ + if(error)cb('Invalid proof nodes given', null) + proofTrie.get(key, (e,r)=>{ + return cb(e,r) + }) + }) + } + /** * Gets a value given a `key` * @method get @@ -132,18 +164,17 @@ module.exports = class Trie { // retrieves a node from dbs by hash _lookupNode (node, cb) { if (TrieNode.isRawNode(node)) { - cb(new TrieNode(node)) + cb(null, new TrieNode(node)) } else { this.db.get(node, (err, value) => { - if (err) { - throw err - } if (value) { value = new TrieNode(rlp.decode(value)) + } else { + err = 'Missing node in DB' } - cb(value) + cb(err, value) }) } } @@ -375,7 +406,8 @@ module.exports = class Trie { return onDone() } - this._lookupNode(root, node => { + this._lookupNode(root, (e, node) => { + if (e) { return onDone(e, node) } processNode(root, node, null, err => { if (err) { return onDone(err) @@ -421,7 +453,8 @@ module.exports = class Trie { const childKey = key.concat(keyExtension) const priority = childKey.length taskExecutor.execute(priority, taskCallback => { - self._lookupNode(childRef, childNode => { + self._lookupNode(childRef, (e, childNode) => { + if (e) { return cb(e, node) } taskCallback() processNode(childRef, childNode, childKey, cb) }) @@ -434,7 +467,8 @@ module.exports = class Trie { childKey.push(childIndex) const priority = childKey.length taskExecutor.execute(priority, taskCallback => { - self._lookupNode(childRef, childNode => { + self._lookupNode(childRef, (e, childNode) => { + if (e) { return cb(e, node) } taskCallback() processNode(childRef, childNode, childKey, cb) }) @@ -585,7 +619,8 @@ module.exports = class Trie { const branchNodeKey = branchNodes[0][0] // look up node - this._lookupNode(branchNode, foundNode => { + this._lookupNode(branchNode, (e, foundNode) => { + if (e) { return cb(e, foundNode) } key = processBranchNode(key, branchNodeKey, foundNode, parentNode, stack, opStack) this._saveStack(key, stack, opStack, cb) }) @@ -694,7 +729,7 @@ module.exports = class Trie { */ checkRoot (root, cb) { root = ethUtil.toBuffer(root) - this._lookupNode(root, value => { + this._lookupNode(root, (e, value) => { cb(null, !!value) }) } diff --git a/src/checkpointTrie.js b/src/checkpointTrie.js index f9998b1..b5e8258 100644 --- a/src/checkpointTrie.js +++ b/src/checkpointTrie.js @@ -1,11 +1,11 @@ const async = require('async') const WriteStream = require('level-ws') const BaseTrie = require('./baseTrie') -const proof = require('./proof.js') const ScratchReadStream = require('./scratchReadStream') const ScratchDB = require('./scratch') const { callTogether } = require('./util/async') + module.exports = class CheckpointTrie extends BaseTrie { constructor (...args) { super(...args) @@ -17,14 +17,6 @@ module.exports = class CheckpointTrie extends BaseTrie { this._checkpoints = [] } - static prove (...args) { - return proof.prove(...args) - } - - static verifyProof (...args) { - return proof.verifyProof(...args) - } - /** * Is the trie during a checkpoint phase? */ diff --git a/src/proof.js b/src/proof.js deleted file mode 100644 index 7c13317..0000000 --- a/src/proof.js +++ /dev/null @@ -1,100 +0,0 @@ -const TrieNode = require('./trieNode') -const ethUtil = require('ethereumjs-util') -const { stringToNibbles, matchingNibbleLength } = require('./util/nibbles') - -/** - * Returns a merkle proof for a given key - * @method prove - * @param {Trie} trie - * @param {String} key - * @param {Function} cb A callback `Function` (arguments {Error} `err`, {Array.} `proof`) - */ -exports.prove = function (trie, key, cb) { - var nodes - - trie.findPath(key, function (err, node, remaining, stack) { - if (err) return cb(err) - if (remaining.length > 0) return cb(new Error('Node does not contain the key')) - nodes = stack - var p = [] - for (var i = 0; i < nodes.length; i++) { - var rlpNode = nodes[i].serialize() - - if ((rlpNode.length >= 32) || (i === 0)) { - p.push(rlpNode) - } - } - cb(null, p) - }) -} - -/** - * Verifies a merkle proof for a given key - * @method verifyProof - * @param {Buffer} rootHash - * @param {String} key - * @param {Array.} proof - * @param {Function} cb A callback `Function` (arguments {Error} `err`, {String} `val`) - */ -exports.verifyProof = function (rootHash, key, proof, cb) { - key = stringToNibbles(key) - var wantHash = ethUtil.toBuffer(rootHash) - for (var i = 0; i < proof.length; i++) { - var p = ethUtil.toBuffer(proof[i]) - var hash = ethUtil.sha3(proof[i]) - if (Buffer.compare(hash, wantHash)) { - return cb(new Error('Bad proof node ' + i + ': hash mismatch')) - } - var node = new TrieNode(ethUtil.rlp.decode(p)) - var cld - if (node.type === 'branch') { - if (key.length === 0) { - if (i !== proof.length - 1) { - return cb(new Error('Additional nodes at end of proof (branch)')) - } - return cb(null, node.value) - } - cld = node.raw[key[0]] - key = key.slice(1) - if (cld.length === 2) { - var embeddedNode = new TrieNode(cld) - if (i !== proof.length - 1) { - return cb(new Error('Additional nodes at end of proof (embeddedNode)')) - } - - if (matchingNibbleLength(embeddedNode.key, key) !== embeddedNode.key.length) { - return cb(new Error('Key length does not match with the proof one (embeddedNode)')) - } - key = key.slice(embeddedNode.key.length) - if (key.length !== 0) { - return cb(new Error('Key does not match with the proof one (embeddedNode)')) - } - return cb(null, embeddedNode.value) - } else { - wantHash = cld - } - } else if ((node.type === 'extention') || (node.type === 'leaf')) { - if (matchingNibbleLength(node.key, key) !== node.key.length) { - return cb(new Error('Key does not match with the proof one (extention|leaf)')) - } - cld = node.value - key = key.slice(node.key.length) - if (key.length === 0 || (cld.length === 17 && key.length === 1)) { - // The value is in an embedded branch. Extract it. - if (cld.length === 17) { - cld = cld[key[0]][1] - key = key.slice(1) - } - if (i !== proof.length - 1) { - return cb(new Error('Additional nodes at end of proof (extention|leaf)')) - } - return cb(null, cld) - } else { - wantHash = cld - } - } else { - return cb(new Error('Invalid node type')) - } - } - cb(new Error('Unexpected end of proof')) -} diff --git a/test/index.js b/test/index.js index c5256f3..e56e780 100644 --- a/test/index.js +++ b/test/index.js @@ -6,16 +6,17 @@ const ethUtil = require('ethereumjs-util') tape('simple save and retrive', function (tester) { var it = tester.test + it('should not crash if given a non-existant root', function (t) { var root = new Buffer('3f4399b08efe68945c1cf90ffe85bbe3ce978959da753f9e649f034015b8817d', 'hex') var trie = new Trie(null, root) trie.get('test', function (err, value) { t.equal(value, null) - t.end(err) + t.notEqual(err, null) + t.end() }) }) - var trie = new Trie() it('save a value', function (t) { diff --git a/test/proof.js b/test/proof.js index 39a4c16..ba63a43 100644 --- a/test/proof.js +++ b/test/proof.js @@ -37,34 +37,57 @@ tape('simple merkle proofs generation and verification', function (tester) { }) }) }, - function (cb) { + function (cb) { //should create a valid proof of null Trie.prove(trie, 'key2bb', function (err, prove) { + t.equal(err, null, 'Path to key2 should create valid proof of absence') if (err) return cb(err) - Trie.verifyProof(trie.root, 'randomkey', prove, function (err, val) { - t.notEqual(err, null, 'Expected error: ' + err.message) + Trie.verifyProof(trie.root, 'key2', prove, function (err, val) { + t.equal(val, null, 'Expected value at a random key to be null') + t.equal(err, null, 'Path to key2 should show a null value') cb() }) }) }, function (cb) { - Trie.prove(trie, 'key2bb', function (err, prove) { + let myKey = "anyrandomkey" + Trie.prove(trie, myKey, function (err, prove) { if (err) return cb(err) - Trie.verifyProof(trie.root, 'key2b', prove, function (err, val) { - t.notEqual(err, null, 'Expected error: ' + err.message) + Trie.verifyProof(trie.root, myKey, prove, function (err, val) { + t.equal(val, null, 'Expected value to be null') + t.equal(err, null, err) cb() }) }) }, function (cb) { - Trie.prove(trie, 'key2bb', function (err, prove) { + let myKey = "anothergarbagekey" //should generate a valid proof of null + Trie.prove(trie, myKey, function (err, prove) { if (err) return cb(err) - prove.push(Buffer.from('123456')) - Trie.verifyProof(trie.root, 'key2b', prove, function (err, val) { - t.notEqual(err, null, 'Expected error: ' + err.message) + prove.push(Buffer.from('123456'))//extra nodes are just ignored + Trie.verifyProof(trie.root, myKey, prove, function (err, val) { + t.equal(val, null, 'Expected value to be null') + t.equal(err, null, err) cb() }) }) - } + }, + function (cb) { + trie.put('another', '3498h4riuhgwe', cb) + }, + function (cb) { + // to throw an error we need to request proof for one key + Trie.prove(trie, 'another', function (err, prove) { + if (err) return cb(err) + // and try to use that proof on another key + Trie.verifyProof(trie.root, 'key1aa', prove, function (err, val) { + t.equal(val, null, 'Expected value: to be null ') + // this proof would be insignificant to prove `key1aa` + t.notEqual(err, null, 'Expected error: Missing node in DB') + t.notEqual(err, undefined, 'Expected error: Missing node in DB') + cb() + }) + }) + }, ], function (err) { t.end(err) }) diff --git a/test/secure.js b/test/secure.js index 4d06570..1c2d5dd 100644 --- a/test/secure.js +++ b/test/secure.js @@ -2,7 +2,6 @@ const Trie = require('../src/secure.js') const async = require('async') const tape = require('tape') - tape('SecureTrie', function (t) { const trie = new Trie() const k = Buffer.from('foo')