Skip to content

Commit 9df9f51

Browse files
committed
adding the plan
1 parent 1590829 commit 9df9f51

1 file changed

Lines changed: 356 additions & 0 deletions

File tree

PLAN_Cairo0_Eth_Call.md

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
---
2+
name: Constrained Bytecode Implementation
3+
overview: Implement constrained bytecode retrieval in Cairo0 with bytecode-to-code-hash verification. Bytecode will be stored in EVM memorizer keyed by code hash, replacing the current unconstrained approach. The implementation spans Cairo0, Cairo1, and Rust layers.
4+
todos:
5+
- id: "1"
6+
content: Create Cairo0 bytecode hashing utility (src/utils/bytecode_hash.cairo) with verify_bytecode_hash and bytecode_le_words_to_u8_array function
7+
status: done
8+
- id: "2"
9+
content: Add bytecode key hashing to EVM memorizer (src/memorizers/evm/memorizer.cairo) - EvmPackParams.bytecode and EvmHashParams.bytecode
10+
status: pending
11+
- id: "3"
12+
content: Add BYTECODE to EvmStateAccessType and register in state_access.cairo
13+
status: pending
14+
- id: "4"
15+
content: Add bytecode_data input structure and bytecode_load_loop to src/hdp.cairo - verify BytecodeLeWords format, extract u8 array, and store in EVM memorizer
16+
status: pending
17+
- id: "5"
18+
content: Update Rust types (crates/types/src/lib.rs) to include bytecode in HDPInput
19+
status: pending
20+
- id: "6"
21+
content: Update dry run handler to fetch bytecode and record code hash key (crates/dry_hint_processor)
22+
status: pending
23+
- id: "7"
24+
content: Update sound run handler to read bytecode from EVM memorizer by code hash (crates/sound_hint_processor)
25+
status: pending
26+
- id: "8"
27+
content: Add bytecode syscall routing in execute_syscalls.cairo
28+
status: pending
29+
- id: "9"
30+
content: Add account_get_bytecode function to hdp_cairo/src/evm/account.cairo
31+
status: pending
32+
- id: "10"
33+
content: Update fetcher to collect bytecode data keyed by code hash (crates/fetcher/src/lib.rs)
34+
status: pending
35+
- id: "11"
36+
content: Add bytecode serialization hint for Cairo0 input loading (crates/hints)
37+
status: pending
38+
- id: "12"
39+
content: Create tests for bytecode functionality (tests/src/evm_modules/bytecode.cairo and .rs)
40+
status: pending
41+
isProject: false
42+
---
43+
44+
# Constrained Bytecode Implementation Plan
45+
46+
## Overview
47+
48+
This plan implements constrained bytecode retrieval where bytecode is verified against code hash in Cairo0 and stored in the EVM memorizer, accessible by code hash only. This replaces the current unconstrained bytecode approach.
49+
50+
## Architecture Flow
51+
52+
```
53+
Dry Run → Fetch Bytecode from RPC → Add to HDPInput → Cairo0 Verification → Store in EVM Memorizer → Access by Code Hash
54+
```
55+
56+
## Hint Language Usage
57+
58+
- **Rust Hints**: Used for Cairo1 code (in `hdp_cairo/`)
59+
- Dry run handlers: `crates/dry_hint_processor/src/syscall_handler/`
60+
- Sound run handlers: `crates/sound_hint_processor/src/syscall_handler/`
61+
- **Python Hints**: Used for Cairo0 code (in `src/`)
62+
- Input loading: `crates/hints/src/contract_bootloader/params.rs`
63+
- Other Cairo0 hints: `crates/hints/src/`
64+
65+
## Existing Code to Reuse
66+
67+
1. **BytecodeLeWords Conversion**: `crates/types/src/cairo/unconstrained/bytecode.rs`
68+
69+
- `BytecodeLeWords::from(Bytes)` - converts raw bytes to BytecodeLeWords format
70+
- `BytecodeLeWords::to_memory()` - serializes to Cairo memory
71+
- Already used in unconstrained bytecode handling
72+
73+
1. **RPC Fetching**: `crates/fetcher/src/proof_keys/unconstrained.rs`
74+
75+
- `UnconstrainedProofKeys::fetch_bytecode(key)` - fetches bytecode from RPC
76+
- Already handles RPC URL resolution and provider setup
77+
78+
1. **Dry Run Pattern**: `crates/dry_hint_processor/src/syscall_handler/unconstrained/mod.rs`
79+
80+
- Lines 66-70 show pattern for fetching bytecode and converting to BytecodeLeWords
81+
- Can be adapted for EVM bytecode handler
82+
83+
## Implementation Steps
84+
85+
### Step 1: Create Cairo0 Bytecode Hashing Utility
86+
87+
**File**: `src/utils/bytecode_hash.cairo` (new file)
88+
89+
- Create a standalone function `verify_bytecode_hash` that:
90+
- Takes bytecode (as `ByteCodeLeWords` format) and expected code hash (as `Uint256`)
91+
- Computes keccak hash of bytecode using `cairo_keccak`
92+
- Reverses endianness to match EVM format
93+
- Compares with expected code hash
94+
- Returns success/failure or panics on mismatch
95+
- Make it easily testable with minimal dependencies
96+
- Reference implementation: `hdp_cairo/src/unconstrained/state.cairo:30-38`
97+
98+
**Key functions:**
99+
100+
- `verify_bytecode_hash{keccak_ptr: felt*, range_check_ptr, bitwise_ptr: BitwiseBuiltin*}(bytecode_words: felt*, words_len: felt, lastInputWord: felt, lastInputNumBytes: felt, expected_hash: Uint256) -> ()`
101+
- `bytecode_le_words_to_u8_array(bytecode_le_words_ptr: felt*) -> (u8_array: felt*, len: felt)` - Extracts u8 array from BytecodeLeWords format (used after verification for storage)
102+
103+
**Note**: We do NOT need `u8_array_to_bytecode_le_words` because:
104+
105+
- Rust side handles conversion from raw bytes to `BytecodeLeWords` format before passing to Cairo0
106+
- After verification, we convert `BytecodeLeWords` to u8 array for storage
107+
- When retrieving, memorizer returns u8 array directly (no conversion back needed)
108+
109+
### Step 2: Add Bytecode Get Function to EVM Memorizer (Cairo0)
110+
111+
**File**: `src/memorizers/evm/memorizer.cairo`
112+
113+
- Add new namespace `EvmPackParams.bytecode` that packs code hash (2 felts: high, low)
114+
- Add `EvmHashParams.bytecode{poseidon_ptr: PoseidonBuiltin*}(code_hash: Uint256) -> felt` to hash code hash for memorizer key
115+
- Add `EvmHashParams2.bytecode{poseidon_ptr: PoseidonBuiltin*}(params: felt*) -> felt` variant for syscall routing
116+
- The key will be just the code hash (Poseidon hash of the 2-felt Uint256)
117+
- **The value stored/retrieved is u8 array format**: array of felts `[byte0, byte1, byte2, ...]` where each felt = one byte/opcode
118+
- `EvmMemorizer.get` for bytecode returns this u8 array directly (opcode-by-opcode form)
119+
120+
**File**: `src/memorizers/evm/state_access.cairo`
121+
122+
- Add `BYTECODE` to `EvmStateAccessType` enum
123+
- Register bytecode hasher in `EvmStateAccess.init()`
124+
- Add bytecode accessor function if needed (or reuse existing pattern)
125+
126+
### Step 3: Update HDPInput Structure and Processing
127+
128+
**File**: `src/hdp.cairo`
129+
130+
- Add new input section for bytecode data: `bytecode_data` (similar to `unconstrained`)
131+
- Structure: array of `(code_hash_high, code_hash_low, bytecode_le_words_ptr)` tuples
132+
- `bytecode_le_words_ptr` points to `BytecodeLeWords` format in memory (for easy keccak hashing)
133+
- Create `bytecode_load_loop` function that:
134+
- Iterates over bytecode entries
135+
- For each entry:
136+
- Extracts code hash (Uint256 from 2 felts)
137+
- Reads `BytecodeLeWords` from pointer (format: `words64bit_len, words64bit[], lastInputWord, lastInputNumBytes`)
138+
- Calls `verify_bytecode_hash` to verify bytecode matches code hash (using BytecodeLeWords format)
139+
- If match:
140+
- Extracts actual bytecode bytes from `BytecodeLeWords` using helper function `bytecode_le_words_to_u8_array`
141+
- Stores u8 array in EVM memorizer where each byte is stored as a felt252
142+
- Key: code hash (Poseidon hash of Uint256)
143+
- Value: array of felts `[byte0, byte1, byte2, ...]`, each representing one byte (opcode)
144+
- If mismatch: panics with error message
145+
- Call this loop after unconstrained loading, before chain state verification
146+
147+
**Input structure in Python hint:**
148+
149+
```python
150+
bytecode_data = [
151+
(code_hash_high, code_hash_low, bytecode_le_words_ptr),
152+
...
153+
]
154+
# bytecode_le_words_ptr points to BytecodeLeWords serialized format
155+
```
156+
157+
**Storage format in memorizer:**
158+
159+
- Key: Poseidon hash of code hash (2 felts: high, low)
160+
- Value: Array of felts `[byte0, byte1, byte2, ...]` where each felt is one opcode/byte
161+
162+
### Step 4: Update Rust Types
163+
164+
**File**: `crates/types/src/lib.rs`
165+
166+
- Add `bytecode: HashMap<Uint256, Bytes>` to `HDPInput` struct (or create `BytecodeState` similar to `UnconstrainedState`)
167+
- Update `HDPInput` deserialization to include bytecode data
168+
- The key is `Uint256` (code hash), value is `Bytes` (raw bytecode)
169+
- Note: The raw `Bytes` will be converted to `BytecodeLeWords` format when serializing to Cairo0 memory using existing `BytecodeLeWords::from(Bytes)` from `crates/types/src/cairo/unconstrained/bytecode.rs`
170+
171+
**File**: `crates/types/src/cairo/unconstrained/bytecode.rs` (reference existing)
172+
173+
- **REUSE**: `BytecodeLeWords::from(Bytes)` already exists and converts raw `Bytes` to `BytecodeLeWords` format
174+
- This conversion is already used in unconstrained bytecode handling
175+
- No new conversion function needed
176+
177+
### Step 5: Update Dry Run Handler (Rust - Rust Hints for Cairo1)
178+
179+
**File**: `crates/dry_hint_processor/src/syscall_handler/evm/mod.rs` or new file `bytecode.rs`
180+
181+
- Add new `CallHandlerId::Bytecode` variant
182+
- When bytecode is requested in dry run:
183+
- Extract `AccountKey` from calldata (chain_id, block_number, address)
184+
- **REUSE**: Fetch bytecode from RPC using existing pattern from `crates/dry_hint_processor/src/syscall_handler/unconstrained/mod.rs:66-69`
185+
- **REUSE**: Convert to `BytecodeLeWords` using `BytecodeLeWords::from(bytes)` (already exists, see line 70 in unconstrained handler)
186+
- Fetch code hash from account (via existing account handler)
187+
- Record `DryRunKey::Bytecode(code_hash, account_key)` - key includes code hash
188+
- Store both the bytecode and the code hash for later use
189+
- **Note**: This uses Rust hints (for Cairo1 code in `hdp_cairo/`)
190+
191+
**File**: `crates/dry_hint_processor/src/syscall_handler/evm/mod.rs`
192+
193+
- Update `DryRunKey` enum to include `Bytecode(Uint256, keys::evm::account::Key)` where first param is code hash
194+
- Update key recording logic
195+
196+
### Step 6: Update Sound Run Handler (Rust - Rust Hints for Cairo1)
197+
198+
**File**: `crates/sound_hint_processor/src/syscall_handler/evm/mod.rs` or new `bytecode.rs`
199+
200+
- Add handler for bytecode syscall
201+
- When bytecode is requested:
202+
- Extract code hash from calldata (2 felts: high, low)
203+
- Read bytecode from EVM memorizer using code hash as key
204+
- **The EVM memorizer get_bytecode function returns u8 array format directly** (array of felts, each felt = one byte/opcode)
205+
- This is the opcode-by-opcode form stored in the memorizer
206+
- Return this u8 array format directly (no conversion needed)
207+
- This replaces the unconstrained bytecode handler for EVM bytecode
208+
- **Note**: This uses Rust hints (for Cairo1 code in `hdp_cairo/`)
209+
210+
### Step 7: Update Syscall Routing (Cairo0)
211+
212+
**File**: `src/contract_bootloader/execute_syscalls.cairo`
213+
214+
- Add new contract address or extend existing EVM contract address handling
215+
- When bytecode selector is called:
216+
- Extract code hash from calldata (memorizer pointer + code_hash high/low)
217+
- Compute memorizer key using `EvmHashParams.bytecode`
218+
- Read from EVM memorizer using `EvmMemorizer.get`
219+
- **The EVM memorizer get function returns u8 array format directly** (array of felts, each felt = one byte/opcode)
220+
- This is the opcode-by-opcode form - what is stored and what is retrieved
221+
- Return the u8 array directly (no conversion needed)
222+
- This should be accessible via contract address `1` (ACCOUNT) with a new selector
223+
224+
### Step 8: Update Cairo1 API
225+
226+
**File**: `hdp_cairo/src/evm/account.cairo`
227+
228+
- Add new function `account_get_bytecode(self: @EvmMemorizer, code_hash: u256) -> ByteCode`
229+
- This function:
230+
- Calls `call_contract_syscall` with ACCOUNT contract, new BYTECODE selector
231+
- Passes code hash (high, low) in calldata
232+
- Receives u8 array format directly (array of felts, each felt = one byte/opcode)
233+
- Converts u8 array to `ByteCode` format (Span) for return
234+
- Remove or deprecate the unconstrained bytecode access for EVM accounts
235+
236+
**File**: `hdp_cairo/src/lib.cairo`
237+
238+
- Ensure `ByteCode` and `ByteCodeLeWords` types are exported
239+
- Update documentation
240+
241+
### Step 9: Update Fetcher
242+
243+
**File**: `crates/fetcher/src/lib.rs`
244+
245+
- Create `collect_bytecode_data` function (similar to `collect_unconstrained_data`)
246+
- For each bytecode key from dry run:
247+
- **REUSE**: Fetch bytecode from RPC using `UnconstrainedProofKeys::fetch_bytecode(key)` from `crates/fetcher/src/proof_keys/unconstrained.rs:19-27`
248+
- Extract code hash from the key (first element of `DryRunKey::Bytecode(code_hash, account_key)`)
249+
- Store in `HDPInput.bytecode` map: `code_hash -> bytecode_bytes` (raw `Bytes`)
250+
- The fetcher stores raw `Bytes` - conversion to `BytecodeLeWords` happens in hints when serializing to Cairo0 memory
251+
252+
**File**: `crates/fetcher/src/proof_keys/unconstrained.rs` (reference existing)
253+
254+
- **REUSE**: `UnconstrainedProofKeys::fetch_bytecode(key)` already exists and fetches bytecode from RPC
255+
- No new RPC fetching code needed - reuse this existing function
256+
257+
### Step 10: Update Input Loading Hints (Python Hints for Cairo0)
258+
259+
**File**: `crates/hints/src/contract_bootloader/params.rs`
260+
261+
- Add Python hint function `hint_bytecode_data` (similar to `hint_unconstrained_data` at lines 156-179)
262+
- **REUSE**: Use existing `BytecodeLeWords::from(Bytes)` conversion from `crates/types/src/cairo/unconstrained/bytecode.rs`
263+
- **REUSE**: Use existing `BytecodeLeWords::to_memory()` method to serialize to Cairo memory
264+
- Structure: `[(code_hash_high, code_hash_low, bytecode_le_words_ptr), ...]`
265+
- The `bytecode_le_words_ptr` should point to serialized `BytecodeLeWords` in memory
266+
- Format: `words64bit_len, words64bit[], lastInputWord, lastInputNumBytes`
267+
- This format is used for keccak hashing verification in Cairo0
268+
- **Note**: This uses Python hints (for Cairo0 code in `src/`)
269+
- Reference the unconstrained loading pattern in `crates/hints/src/contract_bootloader/params.rs:156-179`
270+
- Note: The actual bytecode bytes (u8 array) will be extracted from `BytecodeLeWords` in Cairo0 and stored in memorizer
271+
272+
### Step 11: Testing
273+
274+
**File**: `tests/src/evm_modules/bytecode.cairo` (new or update)
275+
276+
- Create test that:
277+
- Requests bytecode via new EVM memorizer API
278+
- Verifies bytecode matches expected code hash
279+
- Tests error case (mismatched code hash)
280+
- Test the Cairo0 `verify_bytecode_hash` function independently
281+
282+
**File**: `tests/src/evm_modules/bytecode.rs` (new or update)
283+
284+
- Rust test driver for bytecode tests
285+
- Test end-to-end: dry run → fetch → sound run → verify
286+
287+
### Step 12: Cleanup (Optional)
288+
289+
- Consider deprecating unconstrained bytecode for EVM accounts (keep for other use cases)
290+
- Update documentation in `hdp_cairo/README.md`
291+
- Update `REPO.md` with new bytecode flow
292+
293+
## Key Design Decisions
294+
295+
1. **Key Format**: Use code hash (Uint256) directly as the memorizer key (after Poseidon hashing for consistency)
296+
2. **Input Format**: Rust converts raw bytecode to `BytecodeLeWords` format using existing `BytecodeLeWords::from(Bytes)` and passes to Cairo0 for easy keccak hashing
297+
3. **Verification**: All bytecode is verified in Cairo0 during `hdp.cairo` execution using `BytecodeLeWords` format with keccak hashing, ensuring integrity
298+
4. **Storage Format**: After verification, bytecode is converted from `BytecodeLeWords` to u8 array using `bytecode_le_words_to_u8_array` and stored in memorizer (each byte = one felt252)
299+
5. **Retrieval Format**: **The EVM memorizer `get_bytecode` function returns the u8 array directly** (opcode-by-opcode form, each felt = one byte). This is what is stored and what is retrieved. No conversion back to `BytecodeLeWords` needed.
300+
6. **Conversion Flow**: Rust → BytecodeLeWords → Cairo0 (verify) → u8 array (store) → u8 array (retrieve) → Cairo1 (convert to ByteCode if needed)
301+
7. **Hint Languages**:
302+
303+
- **Rust hints** for Cairo1 code (in `hdp_cairo/`) - used in dry_run and sound_run handlers
304+
- **Python hints** for Cairo0 code (in `src/`) - used in input loading hints
305+
306+
1. **Code Reuse**:
307+
308+
- Reuse `BytecodeLeWords::from(Bytes)` from `crates/types/src/cairo/unconstrained/bytecode.rs`
309+
- Reuse `UnconstrainedProofKeys::fetch_bytecode()` from `crates/fetcher/src/proof_keys/unconstrained.rs`
310+
- Reuse RPC fetching pattern from unconstrained bytecode handler
311+
312+
1. **Backward Compatibility**: Unconstrained bytecode remains for non-EVM use cases
313+
2. **Error Handling**: Mismatched code hash causes panic in Cairo0, preventing invalid data
314+
315+
## Files to Create
316+
317+
- `src/utils/bytecode_hash.cairo` - Bytecode hashing and verification
318+
- `tests/src/evm_modules/bytecode.cairo` - Cairo1 tests
319+
- `tests/src/evm_modules/bytecode.rs` - Rust test driver
320+
- `crates/dry_hint_processor/src/syscall_handler/evm/bytecode.rs` - Dry run handler (optional, can be in mod.rs)
321+
- `crates/sound_hint_processor/src/syscall_handler/evm/bytecode.rs` - Sound run handler (optional)
322+
323+
## Files to Modify
324+
325+
- `src/memorizers/evm/memorizer.cairo` - Add bytecode key hashing
326+
- `src/memorizers/evm/state_access.cairo` - Add bytecode access type
327+
- `src/hdp.cairo` - Add bytecode loading and verification loop
328+
- `src/contract_bootloader/execute_syscalls.cairo` - Add bytecode syscall routing
329+
- `hdp_cairo/src/evm/account.cairo` - Add bytecode getter function
330+
- `crates/types/src/lib.rs` - Add bytecode to HDPInput
331+
- `crates/dry_hint_processor/src/syscall_handler/evm/mod.rs` - Add bytecode key recording
332+
- `crates/sound_hint_processor/src/syscall_handler/evm/mod.rs` - Add bytecode handler
333+
- `crates/fetcher/src/lib.rs` - Add bytecode collection
334+
- `crates/hints/src/contract_bootloader/params.rs` - Add bytecode serialization hint
335+
336+
## Testing Strategy
337+
338+
1. **Unit Test**: Test `verify_bytecode_hash` with known bytecode/code hash pairs
339+
2. **Integration Test**: Test full flow from dry run to sound run
340+
3. **Error Test**: Test with mismatched code hash to ensure proper error handling
341+
4. **Edge Cases**: Empty bytecode, large bytecode, various code hashes
342+
343+
# Plan preimage
344+
345+
## Simple Overview
346+
347+
- in cairo0 inputs we add codeHash -> bytecode mapping
348+
- in hdp.cairo we constrain it - when loading inputs we check that every bytecode hashes to it's code hash, if not throw
349+
350+
## More in depth
351+
352+
- [Doneish] we add get bytecode function to evm memoizer
353+
- when someone gets bytecode in dry run we get it from rpc and later add it to cairo0 inputs
354+
- what we add to cairo0 inputs in src/hdp.cairo should be easy to iterate over all codeHashes and their bytecodes -> when iterating we need to hash every bytecode in cairo0 and check if it matches the codeHash, if it does we save it in the evm memoizer, available to get bytecode by codeHash, if it doesnt we throw error
355+
- after src/hdp.cairo processing the inputs as explained above the bytecode should be easily gettable using the evm memoizer get bytecode function - by code hash - so the key for getting the bytecode should be code hash only.
356+
- a component we definitely need for this to work is equivalent of hdp_cairo/src/unconstrained/state.cairo:30-38 - calculating the code hash in cairo0, this will need to be tested easily cause we will probably need to iterate many times to get to the correct hash, so a separate function taking the bytecode and codehash and trying to keccak the bytecode to get to the correct codehash and super easily runable and testable is needed

0 commit comments

Comments
 (0)