@feelyourprotocol/mpt 8141.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/README.md +448 -0
  2. package/dist/cjs/constructors.d.ts +12 -0
  3. package/dist/cjs/constructors.d.ts.map +1 -0
  4. package/dist/cjs/constructors.js +57 -0
  5. package/dist/cjs/constructors.js.map +1 -0
  6. package/dist/cjs/db/checkpointDB.d.ts +87 -0
  7. package/dist/cjs/db/checkpointDB.d.ts.map +1 -0
  8. package/dist/cjs/db/checkpointDB.js +258 -0
  9. package/dist/cjs/db/checkpointDB.js.map +1 -0
  10. package/dist/cjs/db/index.d.ts +2 -0
  11. package/dist/cjs/db/index.d.ts.map +1 -0
  12. package/dist/cjs/db/index.js +18 -0
  13. package/dist/cjs/db/index.js.map +1 -0
  14. package/dist/cjs/index.d.ts +8 -0
  15. package/dist/cjs/index.d.ts.map +1 -0
  16. package/dist/cjs/index.js +24 -0
  17. package/dist/cjs/index.js.map +1 -0
  18. package/dist/cjs/mpt.d.ts +261 -0
  19. package/dist/cjs/mpt.d.ts.map +1 -0
  20. package/dist/cjs/mpt.js +900 -0
  21. package/dist/cjs/mpt.js.map +1 -0
  22. package/dist/cjs/node/branch.d.ts +14 -0
  23. package/dist/cjs/node/branch.d.ts.map +1 -0
  24. package/dist/cjs/node/branch.js +52 -0
  25. package/dist/cjs/node/branch.js.map +1 -0
  26. package/dist/cjs/node/extension.d.ts +7 -0
  27. package/dist/cjs/node/extension.d.ts.map +1 -0
  28. package/dist/cjs/node/extension.js +14 -0
  29. package/dist/cjs/node/extension.js.map +1 -0
  30. package/dist/cjs/node/extensionOrLeafNodeBase.d.ts +15 -0
  31. package/dist/cjs/node/extensionOrLeafNodeBase.d.ts.map +1 -0
  32. package/dist/cjs/node/extensionOrLeafNodeBase.js +42 -0
  33. package/dist/cjs/node/extensionOrLeafNodeBase.js.map +1 -0
  34. package/dist/cjs/node/index.d.ts +5 -0
  35. package/dist/cjs/node/index.d.ts.map +1 -0
  36. package/dist/cjs/node/index.js +21 -0
  37. package/dist/cjs/node/index.js.map +1 -0
  38. package/dist/cjs/node/leaf.d.ts +7 -0
  39. package/dist/cjs/node/leaf.d.ts.map +1 -0
  40. package/dist/cjs/node/leaf.js +14 -0
  41. package/dist/cjs/node/leaf.js.map +1 -0
  42. package/dist/cjs/node/util.d.ts +8 -0
  43. package/dist/cjs/node/util.d.ts.map +1 -0
  44. package/dist/cjs/node/util.js +38 -0
  45. package/dist/cjs/node/util.js.map +1 -0
  46. package/dist/cjs/package.json +3 -0
  47. package/dist/cjs/proof/index.d.ts +3 -0
  48. package/dist/cjs/proof/index.d.ts.map +1 -0
  49. package/dist/cjs/proof/index.js +19 -0
  50. package/dist/cjs/proof/index.js.map +1 -0
  51. package/dist/cjs/proof/proof.d.ts +41 -0
  52. package/dist/cjs/proof/proof.d.ts.map +1 -0
  53. package/dist/cjs/proof/proof.js +119 -0
  54. package/dist/cjs/proof/proof.js.map +1 -0
  55. package/dist/cjs/proof/range.d.ts +35 -0
  56. package/dist/cjs/proof/range.d.ts.map +1 -0
  57. package/dist/cjs/proof/range.js +456 -0
  58. package/dist/cjs/proof/range.js.map +1 -0
  59. package/dist/cjs/types.d.ts +110 -0
  60. package/dist/cjs/types.d.ts.map +1 -0
  61. package/dist/cjs/types.js +6 -0
  62. package/dist/cjs/types.js.map +1 -0
  63. package/dist/cjs/util/asyncWalk.d.ts +20 -0
  64. package/dist/cjs/util/asyncWalk.d.ts.map +1 -0
  65. package/dist/cjs/util/asyncWalk.js +50 -0
  66. package/dist/cjs/util/asyncWalk.js.map +1 -0
  67. package/dist/cjs/util/encoding.d.ts +31 -0
  68. package/dist/cjs/util/encoding.d.ts.map +1 -0
  69. package/dist/cjs/util/encoding.js +200 -0
  70. package/dist/cjs/util/encoding.js.map +1 -0
  71. package/dist/cjs/util/genesisState.d.ts +6 -0
  72. package/dist/cjs/util/genesisState.d.ts.map +1 -0
  73. package/dist/cjs/util/genesisState.js +45 -0
  74. package/dist/cjs/util/genesisState.js.map +1 -0
  75. package/dist/cjs/util/hex.d.ts +20 -0
  76. package/dist/cjs/util/hex.d.ts.map +1 -0
  77. package/dist/cjs/util/hex.js +48 -0
  78. package/dist/cjs/util/hex.js.map +1 -0
  79. package/dist/cjs/util/index.d.ts +4 -0
  80. package/dist/cjs/util/index.d.ts.map +1 -0
  81. package/dist/cjs/util/index.js +20 -0
  82. package/dist/cjs/util/index.js.map +1 -0
  83. package/dist/cjs/util/nibbles.d.ts +30 -0
  84. package/dist/cjs/util/nibbles.d.ts.map +1 -0
  85. package/dist/cjs/util/nibbles.js +79 -0
  86. package/dist/cjs/util/nibbles.js.map +1 -0
  87. package/dist/cjs/util/walkController.d.ts +72 -0
  88. package/dist/cjs/util/walkController.d.ts.map +1 -0
  89. package/dist/cjs/util/walkController.js +138 -0
  90. package/dist/cjs/util/walkController.js.map +1 -0
  91. package/dist/esm/constructors.d.ts +12 -0
  92. package/dist/esm/constructors.d.ts.map +1 -0
  93. package/dist/esm/constructors.js +53 -0
  94. package/dist/esm/constructors.js.map +1 -0
  95. package/dist/esm/db/checkpointDB.d.ts +87 -0
  96. package/dist/esm/db/checkpointDB.d.ts.map +1 -0
  97. package/dist/esm/db/checkpointDB.js +254 -0
  98. package/dist/esm/db/checkpointDB.js.map +1 -0
  99. package/dist/esm/db/index.d.ts +2 -0
  100. package/dist/esm/db/index.d.ts.map +1 -0
  101. package/dist/esm/db/index.js +2 -0
  102. package/dist/esm/db/index.js.map +1 -0
  103. package/dist/esm/index.d.ts +8 -0
  104. package/dist/esm/index.d.ts.map +1 -0
  105. package/dist/esm/index.js +8 -0
  106. package/dist/esm/index.js.map +1 -0
  107. package/dist/esm/mpt.d.ts +261 -0
  108. package/dist/esm/mpt.d.ts.map +1 -0
  109. package/dist/esm/mpt.js +897 -0
  110. package/dist/esm/mpt.js.map +1 -0
  111. package/dist/esm/node/branch.d.ts +14 -0
  112. package/dist/esm/node/branch.d.ts.map +1 -0
  113. package/dist/esm/node/branch.js +48 -0
  114. package/dist/esm/node/branch.js.map +1 -0
  115. package/dist/esm/node/extension.d.ts +7 -0
  116. package/dist/esm/node/extension.d.ts.map +1 -0
  117. package/dist/esm/node/extension.js +10 -0
  118. package/dist/esm/node/extension.js.map +1 -0
  119. package/dist/esm/node/extensionOrLeafNodeBase.d.ts +15 -0
  120. package/dist/esm/node/extensionOrLeafNodeBase.d.ts.map +1 -0
  121. package/dist/esm/node/extensionOrLeafNodeBase.js +38 -0
  122. package/dist/esm/node/extensionOrLeafNodeBase.js.map +1 -0
  123. package/dist/esm/node/index.d.ts +5 -0
  124. package/dist/esm/node/index.d.ts.map +1 -0
  125. package/dist/esm/node/index.js +5 -0
  126. package/dist/esm/node/index.js.map +1 -0
  127. package/dist/esm/node/leaf.d.ts +7 -0
  128. package/dist/esm/node/leaf.d.ts.map +1 -0
  129. package/dist/esm/node/leaf.js +10 -0
  130. package/dist/esm/node/leaf.js.map +1 -0
  131. package/dist/esm/node/util.d.ts +8 -0
  132. package/dist/esm/node/util.d.ts.map +1 -0
  133. package/dist/esm/node/util.js +33 -0
  134. package/dist/esm/node/util.js.map +1 -0
  135. package/dist/esm/package.json +3 -0
  136. package/dist/esm/proof/index.d.ts +3 -0
  137. package/dist/esm/proof/index.d.ts.map +1 -0
  138. package/dist/esm/proof/index.js +3 -0
  139. package/dist/esm/proof/index.js.map +1 -0
  140. package/dist/esm/proof/proof.d.ts +41 -0
  141. package/dist/esm/proof/proof.d.ts.map +1 -0
  142. package/dist/esm/proof/proof.js +113 -0
  143. package/dist/esm/proof/proof.js.map +1 -0
  144. package/dist/esm/proof/range.d.ts +35 -0
  145. package/dist/esm/proof/range.d.ts.map +1 -0
  146. package/dist/esm/proof/range.js +453 -0
  147. package/dist/esm/proof/range.js.map +1 -0
  148. package/dist/esm/types.d.ts +110 -0
  149. package/dist/esm/types.d.ts.map +1 -0
  150. package/dist/esm/types.js +3 -0
  151. package/dist/esm/types.js.map +1 -0
  152. package/dist/esm/util/asyncWalk.d.ts +20 -0
  153. package/dist/esm/util/asyncWalk.d.ts.map +1 -0
  154. package/dist/esm/util/asyncWalk.js +47 -0
  155. package/dist/esm/util/asyncWalk.js.map +1 -0
  156. package/dist/esm/util/encoding.d.ts +31 -0
  157. package/dist/esm/util/encoding.d.ts.map +1 -0
  158. package/dist/esm/util/encoding.js +188 -0
  159. package/dist/esm/util/encoding.js.map +1 -0
  160. package/dist/esm/util/genesisState.d.ts +6 -0
  161. package/dist/esm/util/genesisState.d.ts.map +1 -0
  162. package/dist/esm/util/genesisState.js +42 -0
  163. package/dist/esm/util/genesisState.js.map +1 -0
  164. package/dist/esm/util/hex.d.ts +20 -0
  165. package/dist/esm/util/hex.d.ts.map +1 -0
  166. package/dist/esm/util/hex.js +43 -0
  167. package/dist/esm/util/hex.js.map +1 -0
  168. package/dist/esm/util/index.d.ts +4 -0
  169. package/dist/esm/util/index.d.ts.map +1 -0
  170. package/dist/esm/util/index.js +4 -0
  171. package/dist/esm/util/index.js.map +1 -0
  172. package/dist/esm/util/nibbles.d.ts +30 -0
  173. package/dist/esm/util/nibbles.d.ts.map +1 -0
  174. package/dist/esm/util/nibbles.js +73 -0
  175. package/dist/esm/util/nibbles.js.map +1 -0
  176. package/dist/esm/util/walkController.d.ts +72 -0
  177. package/dist/esm/util/walkController.d.ts.map +1 -0
  178. package/dist/esm/util/walkController.js +134 -0
  179. package/dist/esm/util/walkController.js.map +1 -0
  180. package/dist/tsconfig.prod.cjs.tsbuildinfo +1 -0
  181. package/dist/tsconfig.prod.esm.tsbuildinfo +1 -0
  182. package/package.json +85 -0
  183. package/src/constructors.ts +71 -0
  184. package/src/db/checkpointDB.ts +298 -0
  185. package/src/db/index.ts +1 -0
  186. package/src/index.ts +7 -0
  187. package/src/mpt.ts +1090 -0
  188. package/src/node/branch.ts +60 -0
  189. package/src/node/extension.ts +13 -0
  190. package/src/node/extensionOrLeafNodeBase.ts +54 -0
  191. package/src/node/index.ts +4 -0
  192. package/src/node/leaf.ts +13 -0
  193. package/src/node/util.ts +35 -0
  194. package/src/proof/index.ts +2 -0
  195. package/src/proof/proof.ts +135 -0
  196. package/src/proof/range.ts +542 -0
  197. package/src/types.ts +151 -0
  198. package/src/util/asyncWalk.ts +60 -0
  199. package/src/util/encoding.ts +209 -0
  200. package/src/util/genesisState.ts +52 -0
  201. package/src/util/hex.ts +47 -0
  202. package/src/util/index.ts +3 -0
  203. package/src/util/nibbles.ts +80 -0
  204. package/src/util/walkController.ts +172 -0
@@ -0,0 +1,542 @@
1
+ import { EthereumJSErrorWithoutCode, equalsBytes } from '@feelyourprotocol/util'
2
+ import { keccak_256 } from '@noble/hashes/sha3.js'
3
+
4
+ import { createMPTFromProof } from '../index.ts'
5
+ import { MerklePatriciaTrie } from '../mpt.ts'
6
+ import { BranchMPTNode, ExtensionMPTNode, LeafMPTNode } from '../node/index.ts'
7
+ import { bytesToNibbles, nibblesCompare, nibblesTypeToPackedBytes } from '../util/nibbles.ts'
8
+
9
+ import type { HashKeysFunction, MPTNode, Nibbles } from '../types.ts'
10
+
11
+ // reference: https://github.com/ethereum/go-ethereum/blob/20356e57b119b4e70ce47665a71964434e15200d/trie/proof.go
12
+
13
+ /**
14
+ * unset will remove all nodes to the left or right of the target key(decided by `removeLeft`).
15
+ * @param trie - trie object.
16
+ * @param parent - parent node, it can be `null`.
17
+ * @param child - child node.
18
+ * @param key - target nibbles.
19
+ * @param pos - key position.
20
+ * @param removeLeft - remove all nodes to the left or right of the target key.
21
+ * @param stack - a stack of modified nodes.
22
+ * @returns The end position of key.
23
+ */
24
+ async function unset(
25
+ trie: MerklePatriciaTrie,
26
+ parent: MPTNode,
27
+ child: MPTNode | null,
28
+ key: Nibbles,
29
+ pos: number,
30
+ removeLeft: boolean,
31
+ stack: MPTNode[],
32
+ ): Promise<number> {
33
+ if (child instanceof BranchMPTNode) {
34
+ /**
35
+ * This node is a branch node,
36
+ * remove all branches on the left or right
37
+ */
38
+ if (removeLeft) {
39
+ for (let i = 0; i < key[pos]; i++) {
40
+ child.setBranch(i, null)
41
+ }
42
+ } else {
43
+ for (let i = key[pos] + 1; i < 16; i++) {
44
+ child.setBranch(i, null)
45
+ }
46
+ }
47
+
48
+ // record this node on the stack
49
+ stack.push(child)
50
+
51
+ // continue to the next node
52
+ const next = child.getBranch(key[pos])
53
+ const _child = next && (await trie.lookupNode(next))
54
+ return unset(trie, child, _child, key, pos + 1, removeLeft, stack)
55
+ } else if (child instanceof ExtensionMPTNode || child instanceof LeafMPTNode) {
56
+ /**
57
+ * This node is an extension node or lead node,
58
+ * if node._nibbles is less or greater than the target key,
59
+ * remove self from parent
60
+ */
61
+ if (
62
+ key.length - pos < child.keyLength() ||
63
+ nibblesCompare(child._nibbles, key.slice(pos, pos + child.keyLength())) !== 0
64
+ ) {
65
+ if (removeLeft) {
66
+ if (nibblesCompare(child._nibbles, key.slice(pos)) < 0) {
67
+ ;(parent as BranchMPTNode).setBranch(key[pos - 1], null)
68
+ }
69
+ } else {
70
+ if (nibblesCompare(child._nibbles, key.slice(pos)) > 0) {
71
+ ;(parent as BranchMPTNode).setBranch(key[pos - 1], null)
72
+ }
73
+ }
74
+ return pos - 1
75
+ }
76
+
77
+ if (child instanceof LeafMPTNode) {
78
+ // This node is a leaf node, directly remove it from parent
79
+ ;(parent as BranchMPTNode).setBranch(key[pos - 1], null)
80
+ return pos - 1
81
+ } else {
82
+ const _child = await trie.lookupNode(child.value())
83
+ if (_child instanceof LeafMPTNode) {
84
+ // The child of this node is leaf node, remove it from parent too
85
+ ;(parent as BranchMPTNode).setBranch(key[pos - 1], null)
86
+ return pos - 1
87
+ }
88
+
89
+ // record this node on the stack
90
+ stack.push(child)
91
+
92
+ // continue to the next node
93
+ return unset(trie, child, _child, key, pos + child.keyLength(), removeLeft, stack)
94
+ }
95
+ } else if (child === null) {
96
+ return pos - 1
97
+ } else {
98
+ throw EthereumJSErrorWithoutCode('invalid node')
99
+ }
100
+ }
101
+
102
+ /**
103
+ * unsetInternal will remove all nodes between `left` and `right` (including `left` and `right`)
104
+ * @param trie - trie object.
105
+ * @param left - left nibbles.
106
+ * @param right - right nibbles.
107
+ * @returns Is it an empty trie.
108
+ */
109
+ async function unsetInternal(
110
+ trie: MerklePatriciaTrie,
111
+ left: Nibbles,
112
+ right: Nibbles,
113
+ ): Promise<boolean> {
114
+ // Key position
115
+ let pos = 0
116
+ // Parent node
117
+ let parent: MPTNode | null = null
118
+ // Current node
119
+ let node: MPTNode | null = await trie.lookupNode(trie.root())
120
+ let shortForkLeft!: number
121
+ let shortForkRight!: number
122
+ // A stack of modified nodes.
123
+ const stack: MPTNode[] = []
124
+
125
+ // 1. Find the fork point of `left` and `right`
126
+
127
+ while (true) {
128
+ if (node instanceof ExtensionMPTNode || node instanceof LeafMPTNode) {
129
+ // record this node on the stack
130
+ stack.push(node)
131
+
132
+ if (left.length - pos < node.keyLength()) {
133
+ shortForkLeft = nibblesCompare(left.slice(pos), node._nibbles)
134
+ } else {
135
+ shortForkLeft = nibblesCompare(left.slice(pos, pos + node.keyLength()), node._nibbles)
136
+ }
137
+
138
+ if (right.length - pos < node.keyLength()) {
139
+ shortForkRight = nibblesCompare(right.slice(pos), node._nibbles)
140
+ } else {
141
+ shortForkRight = nibblesCompare(right.slice(pos, pos + node.keyLength()), node._nibbles)
142
+ }
143
+
144
+ // If one of `left` and `right` is not equal to node._nibbles, it means we found the fork point
145
+ if (shortForkLeft !== 0 || shortForkRight !== 0) {
146
+ break
147
+ }
148
+
149
+ if (node instanceof LeafMPTNode) {
150
+ // it shouldn't happen
151
+ throw EthereumJSErrorWithoutCode('invalid node')
152
+ }
153
+
154
+ // continue to the next node
155
+ parent = node
156
+ pos += node.keyLength()
157
+ node = await trie.lookupNode(node.value())
158
+ } else if (node instanceof BranchMPTNode) {
159
+ // record this node on the stack
160
+ stack.push(node)
161
+
162
+ const leftNode = node.getBranch(left[pos])
163
+ const rightNode = node.getBranch(right[pos])
164
+
165
+ // One of `left` and `right` is `null`, stop searching
166
+ if (leftNode === null || rightNode === null) {
167
+ break
168
+ }
169
+
170
+ // Stop searching if `left` and `right` are not equal
171
+ if (!(leftNode instanceof Uint8Array)) {
172
+ if (rightNode instanceof Uint8Array) {
173
+ break
174
+ }
175
+
176
+ if (leftNode.length !== rightNode.length) {
177
+ break
178
+ }
179
+
180
+ let abort = false
181
+ for (let i = 0; i < leftNode.length; i++) {
182
+ if (!equalsBytes(leftNode[i], rightNode[i])) {
183
+ abort = true
184
+ break
185
+ }
186
+ }
187
+ if (abort) {
188
+ break
189
+ }
190
+ } else {
191
+ if (!(rightNode instanceof Uint8Array)) {
192
+ break
193
+ }
194
+
195
+ if (!equalsBytes(leftNode, rightNode)) {
196
+ break
197
+ }
198
+ }
199
+
200
+ // continue to the next node
201
+ parent = node
202
+ node = await trie.lookupNode(leftNode)
203
+ pos += 1
204
+ } else {
205
+ throw EthereumJSErrorWithoutCode('invalid node')
206
+ }
207
+ }
208
+
209
+ // 2. Starting from the fork point, delete all nodes between `left` and `right`
210
+
211
+ const saveStack = (key: Nibbles, stack: MPTNode[]) => {
212
+ return trie.saveStack(key, stack, [])
213
+ }
214
+
215
+ if (node instanceof ExtensionMPTNode || node instanceof LeafMPTNode) {
216
+ /**
217
+ * There can have these five scenarios:
218
+ * - both proofs are less than the trie path => no valid range
219
+ * - both proofs are greater than the trie path => no valid range
220
+ * - left proof is less and right proof is greater => valid range, unset the entire trie
221
+ * - left proof points to the trie node, but right proof is greater => valid range, unset left node
222
+ * - right proof points to the trie node, but left proof is less => valid range, unset right node
223
+ */
224
+ const removeSelfFromParentAndSaveStack = async (key: Nibbles) => {
225
+ if (parent === null) {
226
+ return true
227
+ }
228
+
229
+ stack.pop()
230
+ ;(parent as BranchMPTNode).setBranch(key[pos - 1], null)
231
+ await saveStack(key.slice(0, pos - 1), stack)
232
+ return false
233
+ }
234
+
235
+ if (shortForkLeft === -1 && shortForkRight === -1) {
236
+ throw EthereumJSErrorWithoutCode('invalid range')
237
+ }
238
+
239
+ if (shortForkLeft === 1 && shortForkRight === 1) {
240
+ throw EthereumJSErrorWithoutCode('invalid range')
241
+ }
242
+
243
+ if (shortForkLeft !== 0 && shortForkRight !== 0) {
244
+ // Unset the entire trie
245
+ return removeSelfFromParentAndSaveStack(left)
246
+ }
247
+
248
+ // Unset left node
249
+ if (shortForkRight !== 0) {
250
+ if (node instanceof LeafMPTNode) {
251
+ return removeSelfFromParentAndSaveStack(left)
252
+ }
253
+
254
+ const child = await trie.lookupNode(node._value)
255
+ if (child instanceof LeafMPTNode) {
256
+ return removeSelfFromParentAndSaveStack(left)
257
+ }
258
+
259
+ const endPos = await unset(trie, node, child, left.slice(pos), node.keyLength(), false, stack)
260
+ await saveStack(left.slice(0, pos + endPos), stack)
261
+
262
+ return false
263
+ }
264
+
265
+ // Unset right node
266
+ if (shortForkLeft !== 0) {
267
+ if (node instanceof LeafMPTNode) {
268
+ return removeSelfFromParentAndSaveStack(right)
269
+ }
270
+
271
+ const child = await trie.lookupNode(node._value)
272
+ if (child instanceof LeafMPTNode) {
273
+ return removeSelfFromParentAndSaveStack(right)
274
+ }
275
+
276
+ const endPos = await unset(trie, node, child, right.slice(pos), node.keyLength(), true, stack)
277
+ await saveStack(right.slice(0, pos + endPos), stack)
278
+
279
+ return false
280
+ }
281
+
282
+ return false
283
+ } else if (node instanceof BranchMPTNode) {
284
+ // Unset all internal nodes in the forkPoint
285
+ for (let i = left[pos] + 1; i < right[pos]; i++) {
286
+ node.setBranch(i, null)
287
+ }
288
+
289
+ {
290
+ /**
291
+ * `stack` records the path from root to fork point.
292
+ * Since we need to unset both left and right nodes once,
293
+ * we need to make a copy here.
294
+ */
295
+ const _stack = [...stack]
296
+ const next = node.getBranch(left[pos])
297
+ const child = next && (await trie.lookupNode(next))
298
+ const endPos = await unset(trie, node, child, left.slice(pos), 1, false, _stack)
299
+ await saveStack(left.slice(0, pos + endPos), _stack)
300
+ }
301
+
302
+ {
303
+ const _stack = [...stack]
304
+ const next = node.getBranch(right[pos])
305
+ const child = next && (await trie.lookupNode(next))
306
+ const endPos = await unset(trie, node, child, right.slice(pos), 1, true, _stack)
307
+ await saveStack(right.slice(0, pos + endPos), _stack)
308
+ }
309
+
310
+ return false
311
+ } else {
312
+ throw EthereumJSErrorWithoutCode('invalid node')
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Verifies a proof and return the verified trie.
318
+ * @param rootHash - root hash.
319
+ * @param key - target key.
320
+ * @param proof - proof node list.
321
+ * @throws If proof is found to be invalid.
322
+ * @returns The value from the key, or null if valid proof of non-existence.
323
+ */
324
+ async function verifyMPTWithMerkleProof(
325
+ rootHash: Uint8Array,
326
+ key: Uint8Array,
327
+ proof: Uint8Array[],
328
+ useKeyHashingFunction: HashKeysFunction,
329
+ ): Promise<{ value: Uint8Array | null; trie: MerklePatriciaTrie }> {
330
+ const proofTrie = await createMPTFromProof(proof, {
331
+ root: rootHash,
332
+ useKeyHashingFunction,
333
+ })
334
+ try {
335
+ const value = await proofTrie.get(key, true)
336
+ return {
337
+ trie: proofTrie,
338
+ value,
339
+ }
340
+ } catch (err: any) {
341
+ if (err.message === 'Missing node in DB') {
342
+ throw EthereumJSErrorWithoutCode('Invalid proof provided')
343
+ } else {
344
+ throw err
345
+ }
346
+ }
347
+ }
348
+
349
+ /**
350
+ * hasRightElement returns the indicator whether there exists more elements
351
+ * on the right side of the given path
352
+ * @param trie - trie object.
353
+ * @param key - given path.
354
+ */
355
+ async function hasRightElement(trie: MerklePatriciaTrie, key: Nibbles): Promise<boolean> {
356
+ let pos = 0
357
+ let node: MPTNode | null = await trie.lookupNode(trie.root())
358
+ while (node !== null) {
359
+ if (node instanceof BranchMPTNode) {
360
+ for (let i = key[pos] + 1; i < 16; i++) {
361
+ if (node.getBranch(i) !== null) {
362
+ return true
363
+ }
364
+ }
365
+
366
+ const next = node.getBranch(key[pos])
367
+ node = next && (await trie.lookupNode(next))
368
+ pos += 1
369
+ } else if (node instanceof ExtensionMPTNode) {
370
+ if (
371
+ key.length - pos < node.keyLength() ||
372
+ nibblesCompare(node._nibbles, key.slice(pos, pos + node.keyLength())) !== 0
373
+ ) {
374
+ return nibblesCompare(node._nibbles, key.slice(pos)) > 0
375
+ }
376
+
377
+ pos += node.keyLength()
378
+ node = await trie.lookupNode(node._value)
379
+ } else if (node instanceof LeafMPTNode) {
380
+ return false
381
+ } else {
382
+ throw EthereumJSErrorWithoutCode('invalid node')
383
+ }
384
+ }
385
+ return false
386
+ }
387
+
388
+ /**
389
+ * Checks whether the given leaf nodes and edge proof can prove the given trie leaves range is matched with the specific root.
390
+ *
391
+ * A range proof is a proof that includes the encoded trie nodes from the root node to leaf node for one or more branches of a trie,
392
+ * allowing an entire range of leaf nodes to be validated. This is useful in applications such as snap sync where contiguous ranges
393
+ * of state trie data is received and validated for constructing world state, locally.
394
+ *
395
+ * There are four situations:
396
+ *
397
+ * - All elements proof. In this case the proof can be null, but the range should
398
+ * be all the leaves in the trie.
399
+ *
400
+ * - One element proof. In this case no matter the edge proof is a non-existent
401
+ * proof or not, we can always verify the correctness of the proof.
402
+ *
403
+ * - Zero element proof. In this case a single non-existent proof is enough to prove.
404
+ * Besides, if there are still some other leaves available on the right side, then
405
+ * an error will be returned.
406
+ *
407
+ * - Two edge elements proof. In this case two existent or non-existent proof(first and last) should be provided.
408
+ *
409
+ * NOTE: Currently only supports verification when the length of firstKey and lastKey are the same.
410
+ *
411
+ * @param rootHash - root hash of state trie this proof is being verified against.
412
+ * @param firstKey - first key of range being proven.
413
+ * @param lastKey - last key of range being proven.
414
+ * @param keys - key list of leaf data being proven.
415
+ * @param values - value list of leaf data being proven, one-to-one correspondence with keys.
416
+ * @param proof - proof node list, if all-elements-proof where no proof is needed, proof should be null, and both `firstKey` and `lastKey` must be null as well
417
+ * @param opts - optional, the opts may include a custom hashing function to use with the trie for proof verification
418
+ * @returns a flag to indicate whether there exists more trie node in the trie
419
+ */
420
+ export async function verifyMerkleRangeProof(
421
+ rootHash: Uint8Array,
422
+ firstKeyRaw: Uint8Array | null,
423
+ lastKeyRaw: Uint8Array | null,
424
+ keysRaw: Uint8Array[],
425
+ values: Uint8Array[],
426
+ proof: Uint8Array[] | null,
427
+ useKeyHashingFunction: HashKeysFunction = keccak_256,
428
+ ): Promise<boolean> {
429
+ // Convert Uint8Array keys to nibbles
430
+ const firstKey = firstKeyRaw !== null ? bytesToNibbles(firstKeyRaw) : null
431
+ const lastKey = lastKeyRaw !== null ? bytesToNibbles(lastKeyRaw) : null
432
+ const keys = keysRaw.map(bytesToNibbles)
433
+
434
+ if (keys.length !== values.length) {
435
+ throw EthereumJSErrorWithoutCode('invalid keys length or values length')
436
+ }
437
+
438
+ // Make sure the keys are in order
439
+ for (let i = 0; i < keys.length - 1; i++) {
440
+ if (nibblesCompare(keys[i], keys[i + 1]) >= 0) {
441
+ throw EthereumJSErrorWithoutCode('invalid keys order')
442
+ }
443
+ }
444
+ // Make sure all values are present
445
+ for (const value of values) {
446
+ if (value.length === 0) {
447
+ throw EthereumJSErrorWithoutCode('invalid values')
448
+ }
449
+ }
450
+
451
+ // All elements proof
452
+ if (proof === null && firstKey === null && lastKey === null) {
453
+ const trie = new MerklePatriciaTrie({ useKeyHashingFunction })
454
+ for (let i = 0; i < keys.length; i++) {
455
+ await trie.put(nibblesTypeToPackedBytes(keys[i]), values[i])
456
+ }
457
+ if (!equalsBytes(rootHash, trie.root())) {
458
+ throw EthereumJSErrorWithoutCode('invalid all elements proof: root mismatch')
459
+ }
460
+ return false
461
+ }
462
+
463
+ if (proof !== null && firstKey !== null && lastKey === null) {
464
+ // Zero element proof
465
+ if (keys.length === 0) {
466
+ const { trie, value } = await verifyMPTWithMerkleProof(
467
+ rootHash,
468
+ nibblesTypeToPackedBytes(firstKey),
469
+ proof,
470
+ useKeyHashingFunction,
471
+ )
472
+
473
+ if (value !== null || (await hasRightElement(trie, firstKey))) {
474
+ throw EthereumJSErrorWithoutCode('invalid zero element proof: value mismatch')
475
+ }
476
+
477
+ return false
478
+ }
479
+ }
480
+
481
+ if (proof === null || firstKey === null || lastKey === null) {
482
+ throw EthereumJSErrorWithoutCode(
483
+ 'invalid all elements proof: proof, firstKey, lastKey must be null at the same time',
484
+ )
485
+ }
486
+
487
+ // One element proof
488
+ if (keys.length === 1 && nibblesCompare(firstKey, lastKey) === 0) {
489
+ const { trie, value } = await verifyMPTWithMerkleProof(
490
+ rootHash,
491
+ nibblesTypeToPackedBytes(firstKey),
492
+ proof,
493
+ useKeyHashingFunction,
494
+ )
495
+
496
+ if (nibblesCompare(firstKey, keys[0]) !== 0) {
497
+ throw EthereumJSErrorWithoutCode(
498
+ 'invalid one element proof: firstKey should be equal to keys[0]',
499
+ )
500
+ }
501
+ if (value === null || !equalsBytes(value, values[0])) {
502
+ throw EthereumJSErrorWithoutCode('invalid one element proof: value mismatch')
503
+ }
504
+
505
+ return hasRightElement(trie, firstKey)
506
+ }
507
+
508
+ // Two edge elements proof
509
+ if (nibblesCompare(firstKey, lastKey) >= 0) {
510
+ throw EthereumJSErrorWithoutCode(
511
+ 'invalid two edge elements proof: firstKey should be less than lastKey',
512
+ )
513
+ }
514
+ if (firstKey.length !== lastKey.length) {
515
+ throw EthereumJSErrorWithoutCode(
516
+ 'invalid two edge elements proof: the length of firstKey should be equal to the length of lastKey',
517
+ )
518
+ }
519
+
520
+ const trie = await createMPTFromProof(proof, {
521
+ useKeyHashingFunction,
522
+ root: rootHash,
523
+ })
524
+
525
+ // Remove all nodes between two edge proofs
526
+ const empty = await unsetInternal(trie, firstKey, lastKey)
527
+ if (empty) {
528
+ trie.root(trie.EMPTY_TRIE_ROOT)
529
+ }
530
+
531
+ // Put all elements to the trie
532
+ for (let i = 0; i < keys.length; i++) {
533
+ await trie.put(nibblesTypeToPackedBytes(keys[i]), values[i])
534
+ }
535
+
536
+ // Compare rootHash
537
+ if (!equalsBytes(trie.root(), rootHash)) {
538
+ throw EthereumJSErrorWithoutCode('invalid two edge elements proof: root mismatch')
539
+ }
540
+
541
+ return hasRightElement(trie, keys[keys.length - 1])
542
+ }
package/src/types.ts ADDED
@@ -0,0 +1,151 @@
1
+ import { utf8ToBytes } from '@feelyourprotocol/util'
2
+
3
+ import type { DB, ValueEncoding } from '@feelyourprotocol/util'
4
+ import type { BranchMPTNode, ExtensionMPTNode, LeafMPTNode } from './node/index.ts'
5
+ import type { WalkController } from './util/walkController.ts'
6
+
7
+ export type MPTNode = BranchMPTNode | ExtensionMPTNode | LeafMPTNode
8
+
9
+ export type Nibbles = number[]
10
+
11
+ // A raw node refers to the non-serialized, array form of the node
12
+ // A raw extension node is a 2-item node, where the first item is the encoded path to the next node, and the second item is the reference to the next node
13
+ // A raw leaf node is a 2-item node, where the first item is the remaining path to the leaf node, and the second item is the value
14
+ // To learn more: https://ethereum.org/en/developers/docs/data-structures-and-encoding/patricia-merkle-trie/#optimization
15
+ export type RawExtensionMPTNode = [Uint8Array, Uint8Array]
16
+ export type RawLeafMPTNode = [Uint8Array, Uint8Array]
17
+
18
+ // Branch and extension nodes might store
19
+ // hash to next node, or a raw node if its length < 32
20
+ export type NodeReferenceOrRawMPTNode = Uint8Array | RawExtensionMPTNode | RawLeafMPTNode
21
+
22
+ export type BranchMPTNodeBranchValue = NodeReferenceOrRawMPTNode | null
23
+
24
+ export type Proof = Uint8Array[]
25
+
26
+ export interface CommonInterface {
27
+ customCrypto: {
28
+ keccak256?: (msg: Uint8Array) => Uint8Array
29
+ }
30
+ }
31
+
32
+ export interface Path {
33
+ node: MPTNode | null
34
+ remaining: Nibbles
35
+ stack: MPTNode[]
36
+ }
37
+
38
+ export type FoundNodeFunction = (
39
+ nodeRef: NodeReferenceOrRawMPTNode,
40
+ node: MPTNode | null,
41
+ key: Nibbles,
42
+ walkController: WalkController,
43
+ ) => void
44
+
45
+ export type HashKeysFunction = (msg: Uint8Array) => Uint8Array
46
+
47
+ export interface MPTOpts {
48
+ /**
49
+ * A database instance.
50
+ */
51
+ db?: DB<string, string | Uint8Array>
52
+
53
+ /**
54
+ * A `Uint8Array` for the root of a previously stored trie
55
+ */
56
+ root?: Uint8Array
57
+
58
+ /**
59
+ * Create as a secure MerklePatriciaTrie where the keys are automatically hashed using the
60
+ * **keccak_256** hash function or alternatively the custom hash function provided.
61
+ * Default: `false`
62
+ *
63
+ * This is the flavor of the MerklePatriciaTrie which is used in production Ethereum networks
64
+ * like Ethereum Mainnet.
65
+ *
66
+ * Note: This functionality has been refactored along the v5 release and was before
67
+ * provided as a separate inherited class `SecureTrie`. Just replace with `Trie`
68
+ * instantiation with `useKeyHashing` set to `true`.
69
+ */
70
+ useKeyHashing?: boolean
71
+
72
+ /**
73
+ * Hash function used for hashing trie node and securing key.
74
+ */
75
+ useKeyHashingFunction?: HashKeysFunction
76
+
77
+ /**
78
+ * Add a prefix to the trie node keys
79
+ *
80
+ * (potential performance benefits if multiple tries are stored within the same DB,
81
+ * e.g. all storage tries being stored in the outer account state DB)
82
+ */
83
+ keyPrefix?: Uint8Array
84
+
85
+ /**
86
+ * ValueEncoding of the database (the values which are `put`/`get` in the db are of this type). Defaults to `string`
87
+ */
88
+ valueEncoding?: ValueEncoding
89
+
90
+ /**
91
+ * Store the root inside the database after every `write` operation
92
+ */
93
+ useRootPersistence?: boolean
94
+
95
+ /**
96
+ * Flag to prune the trie. When set to `true`, each time a value is overridden,
97
+ * unreachable nodes will be pruned (deleted) from the trie
98
+ */
99
+ useNodePruning?: boolean
100
+
101
+ /**
102
+ * LRU cache for trie nodes to allow for faster node retrieval.
103
+ *
104
+ * Default: 0 (deactivated)
105
+ */
106
+ cacheSize?: number
107
+
108
+ /**
109
+ * @feelyourprotocol/common `Common` instance (an alternative to passing in a `customHashingFunction`)
110
+ */
111
+ common?: CommonInterface
112
+ }
113
+
114
+ export type MPTOptsWithDefaults = MPTOpts & {
115
+ useKeyHashing: boolean
116
+ useKeyHashingFunction: HashKeysFunction
117
+ useRootPersistence: boolean
118
+ useNodePruning: boolean
119
+ cacheSize: number
120
+ }
121
+
122
+ export interface TrieShallowCopyOpts {
123
+ keyPrefix?: Uint8Array
124
+ cacheSize?: number
125
+ }
126
+
127
+ export interface CheckpointDBOpts {
128
+ /**
129
+ * A database instance.
130
+ */
131
+ db: DB<string, string | Uint8Array>
132
+
133
+ /**
134
+ * ValueEncoding of the database (the values which are `put`/`get` in the db are of this type). Defaults to `string`
135
+ */
136
+ valueEncoding?: ValueEncoding
137
+
138
+ /**
139
+ * Cache size (default: 0)
140
+ */
141
+ cacheSize?: number
142
+ }
143
+
144
+ export type Checkpoint = {
145
+ // We cannot use a Uint8Array => Uint8Array map directly. If you create two Uint8Arrays with the same internal value,
146
+ // then when setting a value on the Map, it actually creates two indices.
147
+ keyValueMap: Map<string, Uint8Array | undefined>
148
+ root: Uint8Array
149
+ }
150
+
151
+ export const ROOT_DB_KEY = utf8ToBytes('__root__')