UNPKG

17.7 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.verifyRangeProof = void 0;
4const nibbles_1 = require("./util/nibbles");
5const baseTrie_1 = require("./baseTrie");
6const trieNode_1 = require("./trieNode");
7// reference: https://github.com/ethereum/go-ethereum/blob/20356e57b119b4e70ce47665a71964434e15200d/trie/proof.go
8/**
9 * unset will remove all nodes to the left or right of the target key(decided by `removeLeft`).
10 * @param trie - trie object.
11 * @param parent - parent node, it can be `null`.
12 * @param child - child node.
13 * @param key - target nibbles.
14 * @param pos - key position.
15 * @param removeLeft - remove all nodes to the left or right of the target key.
16 * @param stack - a stack of modified nodes.
17 * @returns The end position of key.
18 */
19async function unset(trie, parent, child, key, pos, removeLeft, stack) {
20 if (child instanceof trieNode_1.BranchNode) {
21 /**
22 * This node is a branch node,
23 * remove all branches on the left or right
24 */
25 if (removeLeft) {
26 for (let i = 0; i < key[pos]; i++) {
27 child.setBranch(i, null);
28 }
29 }
30 else {
31 for (let i = key[pos] + 1; i < 16; i++) {
32 child.setBranch(i, null);
33 }
34 }
35 // record this node on the stack
36 stack.push(child);
37 // continue to the next node
38 const next = child.getBranch(key[pos]);
39 const _child = next && (await trie.lookupNode(next));
40 return await unset(trie, child, _child, key, pos + 1, removeLeft, stack);
41 }
42 else if (child instanceof trieNode_1.ExtensionNode || child instanceof trieNode_1.LeafNode) {
43 /**
44 * This node is an extension node or lead node,
45 * if node._nibbles is less or greater than the target key,
46 * remove self from parent
47 */
48 if (key.length - pos < child.keyLength ||
49 (0, nibbles_1.nibblesCompare)(child._nibbles, key.slice(pos, pos + child.keyLength)) !== 0) {
50 if (removeLeft) {
51 if ((0, nibbles_1.nibblesCompare)(child._nibbles, key.slice(pos)) < 0) {
52 ;
53 parent.setBranch(key[pos - 1], null);
54 }
55 }
56 else {
57 if ((0, nibbles_1.nibblesCompare)(child._nibbles, key.slice(pos)) > 0) {
58 ;
59 parent.setBranch(key[pos - 1], null);
60 }
61 }
62 return pos - 1;
63 }
64 if (child instanceof trieNode_1.LeafNode) {
65 // This node is a leaf node, directly remove it from parent
66 ;
67 parent.setBranch(key[pos - 1], null);
68 return pos - 1;
69 }
70 else {
71 const _child = await trie.lookupNode(child.value);
72 if (_child && _child instanceof trieNode_1.LeafNode) {
73 // The child of this node is leaf node, remove it from parent too
74 ;
75 parent.setBranch(key[pos - 1], null);
76 return pos - 1;
77 }
78 // record this node on the stack
79 stack.push(child);
80 // continue to the next node
81 return await unset(trie, child, _child, key, pos + child.keyLength, removeLeft, stack);
82 }
83 }
84 else if (child === null) {
85 return pos - 1;
86 }
87 else {
88 throw new Error('invalid node');
89 }
90}
91/**
92 * unsetInternal will remove all nodes between `left` and `right` (including `left` and `right`)
93 * @param trie - trie object.
94 * @param left - left nibbles.
95 * @param right - right nibbles.
96 * @returns Is it an empty trie.
97 */
98async function unsetInternal(trie, left, right) {
99 // Key position
100 let pos = 0;
101 // Parent node
102 let parent = null;
103 // Current node
104 let node = await trie.lookupNode(trie.root);
105 let shortForkLeft;
106 let shortForkRight;
107 // A stack of modified nodes.
108 const stack = [];
109 // 1. Find the fork point of `left` and `right`
110 // eslint-disable-next-line no-constant-condition
111 while (true) {
112 if (node instanceof trieNode_1.ExtensionNode || node instanceof trieNode_1.LeafNode) {
113 // record this node on the stack
114 stack.push(node);
115 if (left.length - pos < node.keyLength) {
116 shortForkLeft = (0, nibbles_1.nibblesCompare)(left.slice(pos), node._nibbles);
117 }
118 else {
119 shortForkLeft = (0, nibbles_1.nibblesCompare)(left.slice(pos, pos + node.keyLength), node._nibbles);
120 }
121 if (right.length - pos < node.keyLength) {
122 shortForkRight = (0, nibbles_1.nibblesCompare)(right.slice(pos), node._nibbles);
123 }
124 else {
125 shortForkRight = (0, nibbles_1.nibblesCompare)(right.slice(pos, pos + node.keyLength), node._nibbles);
126 }
127 // If one of `left` and `right` is not equal to node._nibbles, it means we found the fork point
128 if (shortForkLeft !== 0 || shortForkRight !== 0) {
129 break;
130 }
131 if (node instanceof trieNode_1.LeafNode) {
132 // it shouldn't happen
133 throw new Error('invalid node');
134 }
135 // continue to the next node
136 parent = node;
137 pos += node.keyLength;
138 node = await trie.lookupNode(node.value);
139 }
140 else if (node instanceof trieNode_1.BranchNode) {
141 // record this node on the stack
142 stack.push(node);
143 const leftNode = node.getBranch(left[pos]);
144 const rightNode = node.getBranch(right[pos]);
145 // One of `left` and `right` is `null`, stop searching
146 if (leftNode === null || rightNode === null) {
147 break;
148 }
149 // Stop searching if `left` and `right` are not equal
150 if (!(leftNode instanceof Buffer)) {
151 if (rightNode instanceof Buffer) {
152 break;
153 }
154 if (leftNode.length !== rightNode.length) {
155 break;
156 }
157 let abort = false;
158 for (let i = 0; i < leftNode.length; i++) {
159 if (leftNode[i].compare(rightNode[i]) !== 0) {
160 abort = true;
161 break;
162 }
163 }
164 if (abort) {
165 break;
166 }
167 }
168 else {
169 if (!(rightNode instanceof Buffer)) {
170 break;
171 }
172 if (leftNode.compare(rightNode) !== 0) {
173 break;
174 }
175 }
176 // continue to the next node
177 parent = node;
178 node = await trie.lookupNode(leftNode);
179 pos += 1;
180 }
181 else {
182 throw new Error('invalid node');
183 }
184 }
185 // 2. Starting from the fork point, delete all nodes between `left` and `right`
186 const saveStack = (key, stack) => {
187 return trie._saveStack(key, stack, []);
188 };
189 if (node instanceof trieNode_1.ExtensionNode || node instanceof trieNode_1.LeafNode) {
190 /**
191 * There can have these five scenarios:
192 * - both proofs are less than the trie path => no valid range
193 * - both proofs are greater than the trie path => no valid range
194 * - left proof is less and right proof is greater => valid range, unset the entire trie
195 * - left proof points to the trie node, but right proof is greater => valid range, unset left node
196 * - right proof points to the trie node, but left proof is less => valid range, unset right node
197 */
198 const removeSelfFromParentAndSaveStack = async (key) => {
199 if (parent === null) {
200 return true;
201 }
202 stack.pop();
203 parent.setBranch(key[pos - 1], null);
204 await saveStack(key.slice(0, pos - 1), stack);
205 return false;
206 };
207 if (shortForkLeft === -1 && shortForkRight === -1) {
208 throw new Error('invalid range');
209 }
210 if (shortForkLeft === 1 && shortForkRight === 1) {
211 throw new Error('invalid range');
212 }
213 if (shortForkLeft !== 0 && shortForkRight !== 0) {
214 // Unset the entire trie
215 return await removeSelfFromParentAndSaveStack(left);
216 }
217 // Unset left node
218 if (shortForkRight !== 0) {
219 if (node instanceof trieNode_1.LeafNode) {
220 return await removeSelfFromParentAndSaveStack(left);
221 }
222 const child = await trie.lookupNode(node._value);
223 if (child && child instanceof trieNode_1.LeafNode) {
224 return await removeSelfFromParentAndSaveStack(left);
225 }
226 const endPos = await unset(trie, node, child, left.slice(pos), node.keyLength, false, stack);
227 await saveStack(left.slice(0, pos + endPos), stack);
228 return false;
229 }
230 // Unset right node
231 if (shortForkLeft !== 0) {
232 if (node instanceof trieNode_1.LeafNode) {
233 return await removeSelfFromParentAndSaveStack(right);
234 }
235 const child = await trie.lookupNode(node._value);
236 if (child && child instanceof trieNode_1.LeafNode) {
237 return await removeSelfFromParentAndSaveStack(right);
238 }
239 const endPos = await unset(trie, node, child, right.slice(pos), node.keyLength, true, stack);
240 await saveStack(right.slice(0, pos + endPos), stack);
241 return false;
242 }
243 return false;
244 }
245 else if (node instanceof trieNode_1.BranchNode) {
246 // Unset all internal nodes in the forkpoint
247 for (let i = left[pos] + 1; i < right[pos]; i++) {
248 node.setBranch(i, null);
249 }
250 {
251 /**
252 * `stack` records the path from root to fork point.
253 * Since we need to unset both left and right nodes once,
254 * we need to make a copy here.
255 */
256 const _stack = [...stack];
257 const next = node.getBranch(left[pos]);
258 const child = next && (await trie.lookupNode(next));
259 const endPos = await unset(trie, node, child, left.slice(pos), 1, false, _stack);
260 await saveStack(left.slice(0, pos + endPos), _stack);
261 }
262 {
263 const _stack = [...stack];
264 const next = node.getBranch(right[pos]);
265 const child = next && (await trie.lookupNode(next));
266 const endPos = await unset(trie, node, child, right.slice(pos), 1, true, _stack);
267 await saveStack(right.slice(0, pos + endPos), _stack);
268 }
269 return false;
270 }
271 else {
272 throw new Error('invalid node');
273 }
274}
275/**
276 * Verifies a proof and return the verified trie.
277 * @param rootHash - root hash.
278 * @param key - target key.
279 * @param proof - proof node list.
280 * @throws If proof is found to be invalid.
281 * @returns The value from the key, or null if valid proof of non-existence.
282 */
283async function verifyProof(rootHash, key, proof) {
284 let proofTrie = new baseTrie_1.Trie(null, rootHash);
285 try {
286 proofTrie = await baseTrie_1.Trie.fromProof(proof, proofTrie);
287 }
288 catch (e) {
289 throw new Error('Invalid proof nodes given');
290 }
291 try {
292 const value = await proofTrie.get(key, true);
293 return {
294 trie: proofTrie,
295 value,
296 };
297 }
298 catch (err) {
299 if (err.message == 'Missing node in DB') {
300 throw new Error('Invalid proof provided');
301 }
302 else {
303 throw err;
304 }
305 }
306}
307/**
308 * hasRightElement returns the indicator whether there exists more elements
309 * on the right side of the given path
310 * @param trie - trie object.
311 * @param key - given path.
312 */
313async function hasRightElement(trie, key) {
314 let pos = 0;
315 let node = await trie.lookupNode(trie.root);
316 while (node !== null) {
317 if (node instanceof trieNode_1.BranchNode) {
318 for (let i = key[pos] + 1; i < 16; i++) {
319 if (node.getBranch(i) !== null) {
320 return true;
321 }
322 }
323 const next = node.getBranch(key[pos]);
324 node = next && (await trie.lookupNode(next));
325 pos += 1;
326 }
327 else if (node instanceof trieNode_1.ExtensionNode) {
328 if (key.length - pos < node.keyLength ||
329 (0, nibbles_1.nibblesCompare)(node._nibbles, key.slice(pos, pos + node.keyLength)) !== 0) {
330 return (0, nibbles_1.nibblesCompare)(node._nibbles, key.slice(pos)) > 0;
331 }
332 pos += node.keyLength;
333 node = await trie.lookupNode(node._value);
334 }
335 else if (node instanceof trieNode_1.LeafNode) {
336 return false;
337 }
338 else {
339 throw new Error('invalid node');
340 }
341 }
342 return false;
343}
344/**
345 * verifyRangeProof checks whether the given leaf nodes and edge proof
346 * can prove the given trie leaves range is matched with the specific root.
347 *
348 * There are four situations:
349 *
350 * - All elements proof. In this case the proof can be null, but the range should
351 * be all the leaves in the trie.
352 *
353 * - One element proof. In this case no matter the edge proof is a non-existent
354 * proof or not, we can always verify the correctness of the proof.
355 *
356 * - Zero element proof. In this case a single non-existent proof is enough to prove.
357 * Besides, if there are still some other leaves available on the right side, then
358 * an error will be returned.
359 *
360 * - Two edge elements proof. In this case two existent or non-existent proof(fisrt and last) should be provided.
361 *
362 * NOTE: Currently only supports verification when the length of firstKey and lastKey are the same.
363 *
364 * @param rootHash - root hash.
365 * @param firstKey - first key.
366 * @param lastKey - last key.
367 * @param keys - key list.
368 * @param values - value list, one-to-one correspondence with keys.
369 * @param proof - proof node list, if proof is null, both `firstKey` and `lastKey` must be null
370 * @returns a flag to indicate whether there exists more trie node in the trie
371 */
372async function verifyRangeProof(rootHash, firstKey, lastKey, keys, values, proof) {
373 if (keys.length !== values.length) {
374 throw new Error('invalid keys length or values length');
375 }
376 // Make sure the keys are in order
377 for (let i = 0; i < keys.length - 1; i++) {
378 if ((0, nibbles_1.nibblesCompare)(keys[i], keys[i + 1]) >= 0) {
379 throw new Error('invalid keys order');
380 }
381 }
382 // Make sure all values are present
383 for (const value of values) {
384 if (value.length === 0) {
385 throw new Error('invalid values');
386 }
387 }
388 // All elements proof
389 if (proof === null && firstKey === null && lastKey === null) {
390 const trie = new baseTrie_1.Trie();
391 for (let i = 0; i < keys.length; i++) {
392 await trie.put((0, nibbles_1.nibblesToBuffer)(keys[i]), values[i]);
393 }
394 if (rootHash.compare(trie.root) !== 0) {
395 throw new Error('invalid all elements proof: root mismatch');
396 }
397 return false;
398 }
399 if (proof === null || firstKey === null || lastKey === null) {
400 throw new Error('invalid all elements proof: proof, firstKey, lastKey must be null at the same time');
401 }
402 // Zero element proof
403 if (keys.length === 0) {
404 const { trie, value } = await verifyProof(rootHash, (0, nibbles_1.nibblesToBuffer)(firstKey), proof);
405 if (value !== null || (await hasRightElement(trie, firstKey))) {
406 throw new Error('invalid zero element proof: value mismatch');
407 }
408 return false;
409 }
410 // One element proof
411 if (keys.length === 1 && (0, nibbles_1.nibblesCompare)(firstKey, lastKey) === 0) {
412 const { trie, value } = await verifyProof(rootHash, (0, nibbles_1.nibblesToBuffer)(firstKey), proof);
413 if ((0, nibbles_1.nibblesCompare)(firstKey, keys[0]) !== 0) {
414 throw new Error('invalid one element proof: firstKey should be equal to keys[0]');
415 }
416 if (value === null || value.compare(values[0]) !== 0) {
417 throw new Error('invalid one element proof: value mismatch');
418 }
419 return hasRightElement(trie, firstKey);
420 }
421 // Two edge elements proof
422 if ((0, nibbles_1.nibblesCompare)(firstKey, lastKey) >= 0) {
423 throw new Error('invalid two edge elements proof: firstKey should be less than lastKey');
424 }
425 if (firstKey.length !== lastKey.length) {
426 throw new Error('invalid two edge elements proof: the length of firstKey should be equal to the length of lastKey');
427 }
428 let trie = new baseTrie_1.Trie(null, rootHash);
429 trie = await baseTrie_1.Trie.fromProof(proof, trie);
430 // Remove all nodes between two edge proofs
431 const empty = await unsetInternal(trie, firstKey, lastKey);
432 if (empty) {
433 trie.root = trie.EMPTY_TRIE_ROOT;
434 }
435 // Put all elements to the trie
436 for (let i = 0; i < keys.length; i++) {
437 await trie.put((0, nibbles_1.nibblesToBuffer)(keys[i]), values[i]);
438 }
439 // Compare rootHash
440 if (trie.root.compare(rootHash) !== 0) {
441 throw new Error('invalid two edge elements proof: root mismatch');
442 }
443 return hasRightElement(trie, keys[keys.length - 1]);
444}
445exports.verifyRangeProof = verifyRangeProof;
446//# sourceMappingURL=verifyRangeProof.js.map
\No newline at end of file