diff --git a/src/baseTrie.js b/src/baseTrie.js index e355a3d..1b29d29 100644 --- a/src/baseTrie.js +++ b/src/baseTrie.js @@ -72,6 +72,24 @@ module.exports = class Trie { }) } + /** + * Generate a proof for multiple keys. Result is a de-duplicated set of + * trie nodes (in no particular order) which can be used to construct + * a partial trie. + */ + static proveMultiple (trie, keys, cb) { + const proofNodes = {} + async.map(keys, (key, cb) => Trie.prove(trie, key, cb), (err, results) => { + if (err) return cb(err) + for (let proof of results) { + for (let node of proof) { + proofNodes[ethUtil.keccak256(node)] = node + } + } + return cb(null, Object.values(proofNodes)) + }) + } + static verifyProof (rootHash, key, proofNodes, cb) { let proofTrie = new Trie(null, rootHash) Trie.fromProof(proofNodes, (error, proofTrie) => { @@ -82,6 +100,14 @@ module.exports = class Trie { }, proofTrie) } + static verifyMultiproof (rootHash, keys, proofNodes, cb) { + let proofTrie = new Trie(null, rootHash) + Trie.fromProof(proofNodes, (err, proofTrie) => { + if (err) return cb(new Error('Invalid proof nodes given'), null) + async.map(keys, (k, cb) => proofTrie.get(k, cb), cb) + }, proofTrie) + } + /** * Gets a value given a `key` * @method get diff --git a/test/proof.js b/test/proof.js index 9362e6b..652f63a 100644 --- a/test/proof.js +++ b/test/proof.js @@ -238,3 +238,58 @@ tape('simple merkle proofs generation and verification', function (tester) { }) }) }) + +tape('multiproof generation and verification', function (tester) { + var it = tester.test + it('create a multiproof and verify it', function (t) { + var trie = new Trie() + + async.series([ + function (cb) { + trie.put('key1aa', '0123456789012345678901234567890123456789xx', cb) + }, + function (cb) { + trie.put('key2bb', 'aval2', cb) + }, + function (cb) { + trie.put('key3cc', 'aval3', cb) + }, + function (cb) { + Trie.proveMultiple(trie, ['key2bb', 'key3cc'], function (err, proofNodes) { + if (err) return cb(err) + Trie.verifyMultiproof(trie.root, ['key2bb', 'key3cc'], proofNodes, function (err, values) { + if (err) return cb(err) + t.equal(values.length, 2) + t.equal(values[0].toString('utf8'), 'aval2') + t.equal(values[1].toString('utf8'), 'aval3') + cb() + }) + }) + }, + function (cb) { + Trie.proveMultiple(trie, ['nono', 'key3cc'], function (err, proofNodes) { + if (err) return cb(err) + Trie.verifyMultiproof(trie.root, ['nono', 'key3cc'], proofNodes, function (err, values) { + if (err) return cb(err) + t.equal(values.length, 2) + t.equal(values[0], null) + t.equal(values[1].toString('utf8'), 'aval3') + cb() + }) + }) + }, + function (cb) { + // proof shouldn't work for different keys + Trie.proveMultiple(trie, ['key2bb', 'key3cc'], function (err, proofNodes) { + if (err) return cb(err) + Trie.verifyMultiproof(trie.root, ['key3cc', 'key1aa'], proofNodes, function (err, values) { + t.ok(err) + cb() + }) + }) + } + ], function (err) { + t.end(err) + }) + }) +})