UNPKG

merkle-patricia-tree

Version:

This is an implementation of the modified merkle patricia tree as specified in Ethereum's yellow paper.

654 lines 31.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.verifyRangeProof = void 0; var nibbles_1 = require("./util/nibbles"); var baseTrie_1 = require("./baseTrie"); var trieNode_1 = require("./trieNode"); // reference: https://github.com/ethereum/go-ethereum/blob/20356e57b119b4e70ce47665a71964434e15200d/trie/proof.go /** * unset will remove all nodes to the left or right of the target key(decided by `removeLeft`). * @param trie - trie object. * @param parent - parent node, it can be `null`. * @param child - child node. * @param key - target nibbles. * @param pos - key position. * @param removeLeft - remove all nodes to the left or right of the target key. * @param stack - a stack of modified nodes. * @returns The end position of key. */ function unset(trie, parent, child, key, pos, removeLeft, stack) { return __awaiter(this, void 0, void 0, function () { var i, i, next, _child, _a, _child; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!(child instanceof trieNode_1.BranchNode)) return [3 /*break*/, 4]; /** * This node is a branch node, * remove all branches on the left or right */ if (removeLeft) { for (i = 0; i < key[pos]; i++) { child.setBranch(i, null); } } else { for (i = key[pos] + 1; i < 16; i++) { child.setBranch(i, null); } } // record this node on the stack stack.push(child); next = child.getBranch(key[pos]); _a = next; if (!_a) return [3 /*break*/, 2]; return [4 /*yield*/, trie.lookupNode(next)]; case 1: _a = (_b.sent()); _b.label = 2; case 2: _child = _a; return [4 /*yield*/, unset(trie, child, _child, key, pos + 1, removeLeft, stack)]; case 3: return [2 /*return*/, _b.sent()]; case 4: if (!(child instanceof trieNode_1.ExtensionNode || child instanceof trieNode_1.LeafNode)) return [3 /*break*/, 9]; /** * This node is an extension node or lead node, * if node._nibbles is less or greater than the target key, * remove self from parent */ if (key.length - pos < child.keyLength || (0, nibbles_1.nibblesCompare)(child._nibbles, key.slice(pos, pos + child.keyLength)) !== 0) { if (removeLeft) { if ((0, nibbles_1.nibblesCompare)(child._nibbles, key.slice(pos)) < 0) { ; parent.setBranch(key[pos - 1], null); } } else { if ((0, nibbles_1.nibblesCompare)(child._nibbles, key.slice(pos)) > 0) { ; parent.setBranch(key[pos - 1], null); } } return [2 /*return*/, pos - 1]; } if (!(child instanceof trieNode_1.LeafNode)) return [3 /*break*/, 5]; // This node is a leaf node, directly remove it from parent ; parent.setBranch(key[pos - 1], null); return [2 /*return*/, pos - 1]; case 5: return [4 /*yield*/, trie.lookupNode(child.value)]; case 6: _child = _b.sent(); if (_child && _child instanceof trieNode_1.LeafNode) { // The child of this node is leaf node, remove it from parent too ; parent.setBranch(key[pos - 1], null); return [2 /*return*/, pos - 1]; } // record this node on the stack stack.push(child); return [4 /*yield*/, unset(trie, child, _child, key, pos + child.keyLength, removeLeft, stack)]; case 7: // continue to the next node return [2 /*return*/, _b.sent()]; case 8: return [3 /*break*/, 10]; case 9: if (child === null) { return [2 /*return*/, pos - 1]; } else { throw new Error('invalid node'); } _b.label = 10; case 10: return [2 /*return*/]; } }); }); } /** * unsetInternal will remove all nodes between `left` and `right` (including `left` and `right`) * @param trie - trie object. * @param left - left nibbles. * @param right - right nibbles. * @returns Is it an empty trie. */ function unsetInternal(trie, left, right) { return __awaiter(this, void 0, void 0, function () { var pos, parent, node, shortForkLeft, shortForkRight, stack, leftNode, rightNode, abort, i, saveStack, removeSelfFromParentAndSaveStack, child, endPos, child, endPos, i, _stack, next, child, _a, endPos, _stack, next, child, _b, endPos; var _this = this; return __generator(this, function (_c) { switch (_c.label) { case 0: pos = 0; parent = null; return [4 /*yield*/, trie.lookupNode(trie.root)]; case 1: node = _c.sent(); stack = []; _c.label = 2; case 2: if (!true) return [3 /*break*/, 8]; if (!(node instanceof trieNode_1.ExtensionNode || node instanceof trieNode_1.LeafNode)) return [3 /*break*/, 4]; // record this node on the stack stack.push(node); if (left.length - pos < node.keyLength) { shortForkLeft = (0, nibbles_1.nibblesCompare)(left.slice(pos), node._nibbles); } else { shortForkLeft = (0, nibbles_1.nibblesCompare)(left.slice(pos, pos + node.keyLength), node._nibbles); } if (right.length - pos < node.keyLength) { shortForkRight = (0, nibbles_1.nibblesCompare)(right.slice(pos), node._nibbles); } else { shortForkRight = (0, nibbles_1.nibblesCompare)(right.slice(pos, pos + node.keyLength), node._nibbles); } // If one of `left` and `right` is not equal to node._nibbles, it means we found the fork point if (shortForkLeft !== 0 || shortForkRight !== 0) { return [3 /*break*/, 8]; } if (node instanceof trieNode_1.LeafNode) { // it shouldn't happen throw new Error('invalid node'); } // continue to the next node parent = node; pos += node.keyLength; return [4 /*yield*/, trie.lookupNode(node.value)]; case 3: node = _c.sent(); return [3 /*break*/, 7]; case 4: if (!(node instanceof trieNode_1.BranchNode)) return [3 /*break*/, 6]; // record this node on the stack stack.push(node); leftNode = node.getBranch(left[pos]); rightNode = node.getBranch(right[pos]); // One of `left` and `right` is `null`, stop searching if (leftNode === null || rightNode === null) { return [3 /*break*/, 8]; } // Stop searching if `left` and `right` are not equal if (!(leftNode instanceof Buffer)) { if (rightNode instanceof Buffer) { return [3 /*break*/, 8]; } if (leftNode.length !== rightNode.length) { return [3 /*break*/, 8]; } abort = false; for (i = 0; i < leftNode.length; i++) { if (leftNode[i].compare(rightNode[i]) !== 0) { abort = true; break; } } if (abort) { return [3 /*break*/, 8]; } } else { if (!(rightNode instanceof Buffer)) { return [3 /*break*/, 8]; } if (leftNode.compare(rightNode) !== 0) { return [3 /*break*/, 8]; } } // continue to the next node parent = node; return [4 /*yield*/, trie.lookupNode(leftNode)]; case 5: node = _c.sent(); pos += 1; return [3 /*break*/, 7]; case 6: throw new Error('invalid node'); case 7: return [3 /*break*/, 2]; case 8: saveStack = function (key, stack) { return trie._saveStack(key, stack, []); }; if (!(node instanceof trieNode_1.ExtensionNode || node instanceof trieNode_1.LeafNode)) return [3 /*break*/, 27]; removeSelfFromParentAndSaveStack = function (key) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: if (parent === null) { return [2 /*return*/, true]; } stack.pop(); parent.setBranch(key[pos - 1], null); return [4 /*yield*/, saveStack(key.slice(0, pos - 1), stack)]; case 1: _a.sent(); return [2 /*return*/, false]; } }); }); }; if (shortForkLeft === -1 && shortForkRight === -1) { throw new Error('invalid range'); } if (shortForkLeft === 1 && shortForkRight === 1) { throw new Error('invalid range'); } if (!(shortForkLeft !== 0 && shortForkRight !== 0)) return [3 /*break*/, 10]; return [4 /*yield*/, removeSelfFromParentAndSaveStack(left)]; case 9: // Unset the entire trie return [2 /*return*/, _c.sent()]; case 10: if (!(shortForkRight !== 0)) return [3 /*break*/, 18]; if (!(node instanceof trieNode_1.LeafNode)) return [3 /*break*/, 12]; return [4 /*yield*/, removeSelfFromParentAndSaveStack(left)]; case 11: return [2 /*return*/, _c.sent()]; case 12: return [4 /*yield*/, trie.lookupNode(node._value)]; case 13: child = _c.sent(); if (!(child && child instanceof trieNode_1.LeafNode)) return [3 /*break*/, 15]; return [4 /*yield*/, removeSelfFromParentAndSaveStack(left)]; case 14: return [2 /*return*/, _c.sent()]; case 15: return [4 /*yield*/, unset(trie, node, child, left.slice(pos), node.keyLength, false, stack)]; case 16: endPos = _c.sent(); return [4 /*yield*/, saveStack(left.slice(0, pos + endPos), stack)]; case 17: _c.sent(); return [2 /*return*/, false]; case 18: if (!(shortForkLeft !== 0)) return [3 /*break*/, 26]; if (!(node instanceof trieNode_1.LeafNode)) return [3 /*break*/, 20]; return [4 /*yield*/, removeSelfFromParentAndSaveStack(right)]; case 19: return [2 /*return*/, _c.sent()]; case 20: return [4 /*yield*/, trie.lookupNode(node._value)]; case 21: child = _c.sent(); if (!(child && child instanceof trieNode_1.LeafNode)) return [3 /*break*/, 23]; return [4 /*yield*/, removeSelfFromParentAndSaveStack(right)]; case 22: return [2 /*return*/, _c.sent()]; case 23: return [4 /*yield*/, unset(trie, node, child, right.slice(pos), node.keyLength, true, stack)]; case 24: endPos = _c.sent(); return [4 /*yield*/, saveStack(right.slice(0, pos + endPos), stack)]; case 25: _c.sent(); return [2 /*return*/, false]; case 26: return [2 /*return*/, false]; case 27: if (!(node instanceof trieNode_1.BranchNode)) return [3 /*break*/, 36]; // Unset all internal nodes in the forkpoint for (i = left[pos] + 1; i < right[pos]; i++) { node.setBranch(i, null); } _stack = __spreadArray([], __read(stack), false); next = node.getBranch(left[pos]); _a = next; if (!_a) return [3 /*break*/, 29]; return [4 /*yield*/, trie.lookupNode(next)]; case 28: _a = (_c.sent()); _c.label = 29; case 29: child = _a; return [4 /*yield*/, unset(trie, node, child, left.slice(pos), 1, false, _stack)]; case 30: endPos = _c.sent(); return [4 /*yield*/, saveStack(left.slice(0, pos + endPos), _stack)]; case 31: _c.sent(); _stack = __spreadArray([], __read(stack), false); next = node.getBranch(right[pos]); _b = next; if (!_b) return [3 /*break*/, 33]; return [4 /*yield*/, trie.lookupNode(next)]; case 32: _b = (_c.sent()); _c.label = 33; case 33: child = _b; return [4 /*yield*/, unset(trie, node, child, right.slice(pos), 1, true, _stack)]; case 34: endPos = _c.sent(); return [4 /*yield*/, saveStack(right.slice(0, pos + endPos), _stack)]; case 35: _c.sent(); return [2 /*return*/, false]; case 36: throw new Error('invalid node'); } }); }); } /** * Verifies a proof and return the verified trie. * @param rootHash - root hash. * @param key - target key. * @param proof - proof node list. * @throws If proof is found to be invalid. * @returns The value from the key, or null if valid proof of non-existence. */ function verifyProof(rootHash, key, proof) { return __awaiter(this, void 0, void 0, function () { var proofTrie, e_1, value, err_1; return __generator(this, function (_a) { switch (_a.label) { case 0: proofTrie = new baseTrie_1.Trie(null, rootHash); _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, baseTrie_1.Trie.fromProof(proof, proofTrie)]; case 2: proofTrie = _a.sent(); return [3 /*break*/, 4]; case 3: e_1 = _a.sent(); throw new Error('Invalid proof nodes given'); case 4: _a.trys.push([4, 6, , 7]); return [4 /*yield*/, proofTrie.get(key, true)]; case 5: value = _a.sent(); return [2 /*return*/, { trie: proofTrie, value: value, }]; case 6: err_1 = _a.sent(); if (err_1.message == 'Missing node in DB') { throw new Error('Invalid proof provided'); } else { throw err_1; } return [3 /*break*/, 7]; case 7: return [2 /*return*/]; } }); }); } /** * hasRightElement returns the indicator whether there exists more elements * on the right side of the given path * @param trie - trie object. * @param key - given path. */ function hasRightElement(trie, key) { return __awaiter(this, void 0, void 0, function () { var pos, node, i, next, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: pos = 0; return [4 /*yield*/, trie.lookupNode(trie.root)]; case 1: node = _b.sent(); _b.label = 2; case 2: if (!(node !== null)) return [3 /*break*/, 9]; if (!(node instanceof trieNode_1.BranchNode)) return [3 /*break*/, 5]; for (i = key[pos] + 1; i < 16; i++) { if (node.getBranch(i) !== null) { return [2 /*return*/, true]; } } next = node.getBranch(key[pos]); _a = next; if (!_a) return [3 /*break*/, 4]; return [4 /*yield*/, trie.lookupNode(next)]; case 3: _a = (_b.sent()); _b.label = 4; case 4: node = _a; pos += 1; return [3 /*break*/, 8]; case 5: if (!(node instanceof trieNode_1.ExtensionNode)) return [3 /*break*/, 7]; if (key.length - pos < node.keyLength || (0, nibbles_1.nibblesCompare)(node._nibbles, key.slice(pos, pos + node.keyLength)) !== 0) { return [2 /*return*/, (0, nibbles_1.nibblesCompare)(node._nibbles, key.slice(pos)) > 0]; } pos += node.keyLength; return [4 /*yield*/, trie.lookupNode(node._value)]; case 6: node = _b.sent(); return [3 /*break*/, 8]; case 7: if (node instanceof trieNode_1.LeafNode) { return [2 /*return*/, false]; } else { throw new Error('invalid node'); } _b.label = 8; case 8: return [3 /*break*/, 2]; case 9: return [2 /*return*/, false]; } }); }); } /** * verifyRangeProof checks whether the given leaf nodes and edge proof * can prove the given trie leaves range is matched with the specific root. * * There are four situations: * * - All elements proof. In this case the proof can be null, but the range should * be all the leaves in the trie. * * - One element proof. In this case no matter the edge proof is a non-existent * proof or not, we can always verify the correctness of the proof. * * - Zero element proof. In this case a single non-existent proof is enough to prove. * Besides, if there are still some other leaves available on the right side, then * an error will be returned. * * - Two edge elements proof. In this case two existent or non-existent proof(fisrt and last) should be provided. * * NOTE: Currently only supports verification when the length of firstKey and lastKey are the same. * * @param rootHash - root hash. * @param firstKey - first key. * @param lastKey - last key. * @param keys - key list. * @param values - value list, one-to-one correspondence with keys. * @param proof - proof node list, if proof is null, both `firstKey` and `lastKey` must be null * @returns a flag to indicate whether there exists more trie node in the trie */ function verifyRangeProof(rootHash, firstKey, lastKey, keys, values, proof) { return __awaiter(this, void 0, void 0, function () { var i, values_1, values_1_1, value, trie_1, i, _a, trie_2, value, _b, _c, trie_3, value, trie, empty, i; var e_2, _d; return __generator(this, function (_e) { switch (_e.label) { case 0: if (keys.length !== values.length) { throw new Error('invalid keys length or values length'); } // Make sure the keys are in order for (i = 0; i < keys.length - 1; i++) { if ((0, nibbles_1.nibblesCompare)(keys[i], keys[i + 1]) >= 0) { throw new Error('invalid keys order'); } } try { // Make sure all values are present for (values_1 = __values(values), values_1_1 = values_1.next(); !values_1_1.done; values_1_1 = values_1.next()) { value = values_1_1.value; if (value.length === 0) { throw new Error('invalid values'); } } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (values_1_1 && !values_1_1.done && (_d = values_1.return)) _d.call(values_1); } finally { if (e_2) throw e_2.error; } } if (!(proof === null && firstKey === null && lastKey === null)) return [3 /*break*/, 5]; trie_1 = new baseTrie_1.Trie(); i = 0; _e.label = 1; case 1: if (!(i < keys.length)) return [3 /*break*/, 4]; return [4 /*yield*/, trie_1.put((0, nibbles_1.nibblesToBuffer)(keys[i]), values[i])]; case 2: _e.sent(); _e.label = 3; case 3: i++; return [3 /*break*/, 1]; case 4: if (rootHash.compare(trie_1.root) !== 0) { throw new Error('invalid all elements proof: root mismatch'); } return [2 /*return*/, false]; case 5: if (proof === null || firstKey === null || lastKey === null) { throw new Error('invalid all elements proof: proof, firstKey, lastKey must be null at the same time'); } if (!(keys.length === 0)) return [3 /*break*/, 9]; return [4 /*yield*/, verifyProof(rootHash, (0, nibbles_1.nibblesToBuffer)(firstKey), proof)]; case 6: _a = _e.sent(), trie_2 = _a.trie, value = _a.value; _b = value !== null; if (_b) return [3 /*break*/, 8]; return [4 /*yield*/, hasRightElement(trie_2, firstKey)]; case 7: _b = (_e.sent()); _e.label = 8; case 8: if (_b) { throw new Error('invalid zero element proof: value mismatch'); } return [2 /*return*/, false]; case 9: if (!(keys.length === 1 && (0, nibbles_1.nibblesCompare)(firstKey, lastKey) === 0)) return [3 /*break*/, 11]; return [4 /*yield*/, verifyProof(rootHash, (0, nibbles_1.nibblesToBuffer)(firstKey), proof)]; case 10: _c = _e.sent(), trie_3 = _c.trie, value = _c.value; if ((0, nibbles_1.nibblesCompare)(firstKey, keys[0]) !== 0) { throw new Error('invalid one element proof: firstKey should be equal to keys[0]'); } if (value === null || value.compare(values[0]) !== 0) { throw new Error('invalid one element proof: value mismatch'); } return [2 /*return*/, hasRightElement(trie_3, firstKey)]; case 11: // Two edge elements proof if ((0, nibbles_1.nibblesCompare)(firstKey, lastKey) >= 0) { throw new Error('invalid two edge elements proof: firstKey should be less than lastKey'); } if (firstKey.length !== lastKey.length) { throw new Error('invalid two edge elements proof: the length of firstKey should be equal to the length of lastKey'); } trie = new baseTrie_1.Trie(null, rootHash); return [4 /*yield*/, baseTrie_1.Trie.fromProof(proof, trie) // Remove all nodes between two edge proofs ]; case 12: trie = _e.sent(); return [4 /*yield*/, unsetInternal(trie, firstKey, lastKey)]; case 13: empty = _e.sent(); if (empty) { trie.root = trie.EMPTY_TRIE_ROOT; } i = 0; _e.label = 14; case 14: if (!(i < keys.length)) return [3 /*break*/, 17]; return [4 /*yield*/, trie.put((0, nibbles_1.nibblesToBuffer)(keys[i]), values[i])]; case 15: _e.sent(); _e.label = 16; case 16: i++; return [3 /*break*/, 14]; case 17: // Compare rootHash if (trie.root.compare(rootHash) !== 0) { throw new Error('invalid two edge elements proof: root mismatch'); } return [2 /*return*/, hasRightElement(trie, keys[keys.length - 1])]; } }); }); } exports.verifyRangeProof = verifyRangeProof; //# sourceMappingURL=verifyRangeProof.js.map