Skip to content

Commit dcc18fb

Browse files
authored
Support verifying proofs stored in a trie nodes table (#3568)
* Implement aristo verifyProof function which supports passing in a table of nodes. * Add more tests.
1 parent ecf67fe commit dcc18fb

File tree

2 files changed

+305
-117
lines changed

2 files changed

+305
-117
lines changed

execution_chain/db/aristo/aristo_proof.nim

Lines changed: 134 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -83,58 +83,6 @@ proc chainRlpNodes(
8383
# Recursion!
8484
db.chainRlpNodes((rvid.root,vtx.bVid(nibble)), rest, chain, nodesCache)
8585

86-
87-
proc trackRlpNodes(
88-
chain: openArray[seq[byte]];
89-
topKey: HashKey;
90-
path: NibblesBuf;
91-
start = false;
92-
): Result[seq[byte], AristoError]
93-
{.gcsafe, raises: [RlpError]} =
94-
## Verify rlp-encoded node chain created by `chainRlpNodes()`.
95-
if path.len == 0:
96-
return err(PartTrkEmptyPath)
97-
if chain.len() == 0:
98-
return err(PartTrkEmptyProof)
99-
100-
# Verify key against rlp-node
101-
let digest = chain[0].digestTo(HashKey)
102-
if start:
103-
if topKey.to(Hash32) != digest.to(Hash32):
104-
return err(PartTrkFollowUpKeyMismatch)
105-
else:
106-
if topKey != digest:
107-
return err(PartTrkFollowUpKeyMismatch)
108-
109-
var
110-
node = rlpFromBytes chain[0]
111-
nChewOff = 0
112-
link: seq[byte]
113-
114-
# Decode rlp-node and prepare for recursion
115-
case node.listLen
116-
of 2:
117-
let (isLeaf, segm) = NibblesBuf.fromHexPrefix node.listElem(0).toBytes
118-
nChewOff = sharedPrefixLen(path, segm)
119-
link = node.listElem(1).toBytes # link or payload
120-
if isLeaf:
121-
if nChewOff == path.len:
122-
return ok(link)
123-
return err(PartTrkLeafPfxMismatch)
124-
of 17:
125-
nChewOff = 1
126-
link = node.listElem(path[0].int).toBytes
127-
else:
128-
return err(PartTrkGarbledNode)
129-
130-
let nextKey = HashKey.fromBytes(link).valueOr:
131-
return err(PartTrkLinkExpected)
132-
133-
if chain.len() > 1:
134-
chain.toOpenArray(1, chain.len() - 1).trackRlpNodes(nextKey, path.slice nChewOff)
135-
else:
136-
err(PartTrkLinkExpected)
137-
13886
proc makeProof(
13987
db: AristoTxRef;
14088
root: VertexID;
@@ -251,16 +199,148 @@ proc makeMultiProof*(
251199

252200
ok()
253201

202+
proc trackRlpNodes(
203+
chain: openArray[seq[byte]];
204+
nextIndex: int;
205+
topKey: HashKey;
206+
path: NibblesBuf;
207+
start = false;
208+
): Result[seq[byte], AristoError]
209+
{.gcsafe, raises: [RlpError]} =
210+
## Verify rlp-encoded node chain created by `chainRlpNodes()`.
211+
212+
if nextIndex > chain.high:
213+
return err(PartTrkLinkExpected)
214+
if path.len == 0:
215+
return err(PartTrkEmptyPath)
216+
217+
# Verify key against rlp-node
218+
let digest = chain[nextIndex].digestTo(HashKey)
219+
if start:
220+
if topKey.to(Hash32) != digest.to(Hash32):
221+
return err(PartTrkFollowUpKeyMismatch)
222+
else:
223+
if topKey != digest:
224+
return err(PartTrkFollowUpKeyMismatch)
225+
226+
var
227+
rlpNode = rlpFromBytes chain[nextIndex]
228+
nChewOff = 0
229+
link: seq[byte]
230+
231+
# Decode rlp-node and prepare for recursion
232+
case rlpNode.listLen
233+
of 2:
234+
let (isLeaf, segm) = NibblesBuf.fromHexPrefix rlpNode.listElem(0).toBytes
235+
nChewOff = sharedPrefixLen(path, segm)
236+
link = rlpNode.listElem(1).toBytes # link or payload
237+
if isLeaf:
238+
if nChewOff == path.len:
239+
return ok(link)
240+
return err(PartTrkLeafPfxMismatch)
241+
of 17:
242+
nChewOff = 1
243+
link = rlpNode.listElem(path[0].int).toBytes
244+
else:
245+
return err(PartTrkGarbledNode)
246+
247+
let nextKey = HashKey.fromBytes(link).valueOr:
248+
return err(PartTrkLinkExpected)
249+
250+
trackRlpNodes(chain, nextIndex + 1, nextKey, path.slice nChewOff)
251+
252+
proc trackRlpNodes(
253+
nodes: Table[Hash32, seq[byte]];
254+
visitedNodes: var HashSet[Hash32];
255+
topKey: HashKey;
256+
path: NibblesBuf;
257+
start = false;
258+
): Result[seq[byte], AristoError]
259+
{.gcsafe, raises: [RlpError]} =
260+
## Verify rlp-encoded node chain created by `chainRlpNodes()`.
261+
262+
let nodeHash = topKey.to(Hash32)
263+
if visitedNodes.contains(nodeHash):
264+
return err(PartTrkFollowUpKeyMismatch)
265+
if nodeHash notin nodes:
266+
if start:
267+
return err(PartTrkFollowUpKeyMismatch)
268+
else:
269+
return err(PartTrkLinkExpected)
270+
if path.len == 0:
271+
return err(PartTrkEmptyPath)
272+
273+
let node = nodes.getOrDefault(nodeHash)
274+
visitedNodes.incl(nodeHash)
275+
276+
# Verify key against rlp-node
277+
let digest = node.digestTo(HashKey)
278+
if start:
279+
if topKey.to(Hash32) != digest.to(Hash32):
280+
return err(PartTrkFollowUpKeyMismatch)
281+
else:
282+
if topKey != digest:
283+
return err(PartTrkFollowUpKeyMismatch)
284+
285+
var
286+
rlpNode = rlpFromBytes node
287+
nChewOff = 0
288+
link: seq[byte]
289+
290+
# Decode rlp-node and prepare for recursion
291+
case rlpNode.listLen
292+
of 2:
293+
let (isLeaf, segm) = NibblesBuf.fromHexPrefix rlpNode.listElem(0).toBytes
294+
nChewOff = sharedPrefixLen(path, segm)
295+
link = rlpNode.listElem(1).toBytes # link or payload
296+
if isLeaf:
297+
if nChewOff == path.len:
298+
return ok(link)
299+
return err(PartTrkLeafPfxMismatch)
300+
of 17:
301+
nChewOff = 1
302+
link = rlpNode.listElem(path[0].int).toBytes
303+
else:
304+
return err(PartTrkGarbledNode)
305+
306+
let nextKey = HashKey.fromBytes(link).valueOr:
307+
return err(PartTrkLinkExpected)
308+
309+
trackRlpNodes(nodes, visitedNodes, nextKey, path.slice nChewOff)
310+
254311
proc verifyProof*(
255312
chain: openArray[seq[byte]];
256313
root: Hash32;
257314
path: Hash32;
258315
): Result[Opt[seq[byte]], AristoError] =
259-
## Variant of `partUntwigGeneric()`.
316+
if chain.len() == 0:
317+
return err(PartTrkEmptyProof)
318+
319+
try:
320+
let
321+
nibbles = NibblesBuf.fromBytes path.data
322+
rc = trackRlpNodes(chain, 0, root.to(HashKey), nibbles, start=true)
323+
if rc.isOk:
324+
return ok(Opt.some rc.value)
325+
if rc.error in TrackRlpNodesNoEntry:
326+
return ok(Opt.none seq[byte])
327+
return err(rc.error)
328+
except RlpError:
329+
return err(PartTrkRlpError)
330+
331+
proc verifyProof*(
332+
nodes: Table[Hash32, seq[byte]];
333+
root: Hash32;
334+
path: Hash32;
335+
): Result[Opt[seq[byte]], AristoError] =
336+
if nodes.len() == 0:
337+
return err(PartTrkEmptyProof)
338+
260339
try:
340+
var visitedNodes: HashSet[Hash32]
261341
let
262342
nibbles = NibblesBuf.fromBytes path.data
263-
rc = chain.trackRlpNodes(root.to(HashKey), nibbles, start=true)
343+
rc = trackRlpNodes(nodes, visitedNodes, root.to(HashKey), nibbles, start=true)
264344
if rc.isOk:
265345
return ok(Opt.some rc.value)
266346
if rc.error in TrackRlpNodesNoEntry:

0 commit comments

Comments
 (0)