@@ -81,6 +81,16 @@ pub struct ExecutionWitness {
8181 /// storage trie. The guest rebuilds the tries straight from the stream.
8282 #[ rkyv( with = crate :: rkyv_utils:: VecVecWrapper ) ]
8383 pub state_nodes : Vec < Vec < u8 > > ,
84+ /// JUMPDEST bitmap for each entry of `codes` (parallel array, same order):
85+ /// one bit per bytecode byte, set when that offset holds a `JUMPDEST` that
86+ /// is not part of a `PUSH` immediate (exactly `Code::jumpdests`). Carrying
87+ /// it in the witness lets the guest build each `Code` with
88+ /// `Code::from_parts_unchecked` instead of re-scanning the whole bytecode
89+ /// corpus in-circuit. Trust model: like the shipped node hashes, a forged
90+ /// bitmap makes execution diverge (a JUMP wrongly succeeds or fails), so
91+ /// the final state root / receipts anchors reject the block.
92+ #[ rkyv( with = crate :: rkyv_utils:: VecVecWrapper ) ]
93+ pub codes_jumpdests : Vec < Vec < u8 > > ,
8494}
8595
8696/// RPC-friendly representation of an execution witness.
@@ -218,12 +228,18 @@ impl RpcExecutionWitness {
218228 let initial_state_root = find_parent_state_root ( decoded_headers, first_block_number) ?;
219229 let nodes = node_map_from_rlp ( self . state . iter ( ) ) ;
220230 let state_nodes = witness_records_from_node_map ( & nodes, initial_state_root, crypto) ?;
231+ let codes_jumpdests = self
232+ . codes
233+ . iter ( )
234+ . map ( |b| Code :: compute_jumpdests ( b) . to_vec ( ) )
235+ . collect ( ) ;
221236 Ok ( ExecutionWitness {
222237 codes : self . codes . into_iter ( ) . map ( |b| b. to_vec ( ) ) . collect ( ) ,
223238 chain_config,
224239 first_block_number,
225240 block_headers_bytes : self . headers . into_iter ( ) . map ( |b| b. to_vec ( ) ) . collect ( ) ,
226241 state_nodes,
242+ codes_jumpdests,
227243 } )
228244 }
229245}
@@ -337,13 +353,19 @@ impl ExecutionWitness {
337353 let initial_state_root = find_parent_state_root ( & headers, first_block_number) ?;
338354 let nodes = node_map_from_rlp ( input. witness . state_as_vecs ( ) . iter ( ) ) ;
339355 let state_nodes = witness_records_from_node_map ( & nodes, initial_state_root, crypto) ?;
356+ let codes = input. witness . codes_as_vecs ( ) ;
357+ let codes_jumpdests = codes
358+ . iter ( )
359+ . map ( |c| Code :: compute_jumpdests ( c) . to_vec ( ) )
360+ . collect ( ) ;
340361
341362 Ok ( Self {
342- codes : input . witness . codes_as_vecs ( ) ,
363+ codes,
343364 block_headers_bytes,
344365 first_block_number,
345366 chain_config : amsterdam_chain_config ( input. chain_id ) ,
346367 state_nodes,
368+ codes_jumpdests,
347369 } )
348370 }
349371}
@@ -509,13 +531,25 @@ impl GuestProgramState {
509531 let ( state_trie, storage_tries) =
510532 build_tries_from_records ( & value. state_nodes , parent_header. state_root ) ?;
511533
512- // hash codes
513- // TODO: codes here probably needs to be Vec<Code>, rather than recomputing here. This requires rkyv implementation.
534+ // hash codes — the witness carries each code's JUMPDEST bitmap, so
535+ // building `Code` needs no in-circuit scan of the bytecode corpus.
536+ if value. codes . len ( ) != value. codes_jumpdests . len ( ) {
537+ return Err ( GuestProgramStateError :: Custom ( format ! (
538+ "witness carries {} codes but {} jumpdest bitmaps" ,
539+ value. codes. len( ) ,
540+ value. codes_jumpdests. len( )
541+ ) ) ) ;
542+ }
514543 let codes_hashed = value
515544 . codes
516545 . into_iter ( )
517- . map ( |code| {
518- let code = Code :: from_bytecode ( code. into ( ) , crypto) ;
546+ . zip ( value. codes_jumpdests )
547+ . map ( |( code, jumpdests) | {
548+ let code = Code :: from_parts_unchecked (
549+ H256 ( crypto. keccak256 ( & code) ) ,
550+ & code,
551+ std:: sync:: Arc :: from ( jumpdests. as_slice ( ) ) ,
552+ ) ;
519553 ( code. hash , code)
520554 } )
521555 . collect ( ) ;
0 commit comments