openvm_keccak256_circuit/
execution.rs1use std::{
2 borrow::{Borrow, BorrowMut},
3 mem::size_of,
4};
5
6use openvm_circuit::{arch::*, system::memory::online::GuestMemory};
7use openvm_circuit_primitives_derive::AlignedBytesBorrow;
8use openvm_instructions::{
9 instruction::Instruction,
10 program::DEFAULT_PC_STEP,
11 riscv::{RV32_MEMORY_AS, RV32_REGISTER_AS},
12 LocalOpcode,
13};
14use openvm_keccak256_transpiler::Rv32KeccakOpcode;
15use openvm_stark_backend::p3_field::PrimeField32;
16use p3_keccak_air::NUM_ROUNDS;
17
18use super::{KeccakVmExecutor, KECCAK_WORD_SIZE};
19use crate::utils::{keccak256, num_keccak_f};
20
21#[derive(AlignedBytesBorrow, Clone)]
22#[repr(C)]
23struct KeccakPreCompute {
24 a: u8,
25 b: u8,
26 c: u8,
27}
28
29impl KeccakVmExecutor {
30 fn pre_compute_impl<F: PrimeField32>(
31 &self,
32 pc: u32,
33 inst: &Instruction<F>,
34 data: &mut KeccakPreCompute,
35 ) -> Result<(), StaticProgramError> {
36 let Instruction {
37 opcode,
38 a,
39 b,
40 c,
41 d,
42 e,
43 ..
44 } = inst;
45 let e_u32 = e.as_canonical_u32();
46 if d.as_canonical_u32() != RV32_REGISTER_AS || e_u32 != RV32_MEMORY_AS {
47 return Err(StaticProgramError::InvalidInstruction(pc));
48 }
49 *data = KeccakPreCompute {
50 a: a.as_canonical_u32() as u8,
51 b: b.as_canonical_u32() as u8,
52 c: c.as_canonical_u32() as u8,
53 };
54 assert_eq!(&Rv32KeccakOpcode::KECCAK256.global_opcode(), opcode);
55 Ok(())
56 }
57}
58
59impl<F: PrimeField32> Executor<F> for KeccakVmExecutor {
60 fn pre_compute_size(&self) -> usize {
61 size_of::<KeccakPreCompute>()
62 }
63
64 #[cfg(not(feature = "tco"))]
65 fn pre_compute<Ctx>(
66 &self,
67 pc: u32,
68 inst: &Instruction<F>,
69 data: &mut [u8],
70 ) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError>
71 where
72 Ctx: ExecutionCtxTrait,
73 {
74 let data: &mut KeccakPreCompute = data.borrow_mut();
75 self.pre_compute_impl(pc, inst, data)?;
76 Ok(execute_e1_impl::<_, _>)
77 }
78
79 #[cfg(feature = "tco")]
80 fn handler<Ctx>(
81 &self,
82 pc: u32,
83 inst: &Instruction<F>,
84 data: &mut [u8],
85 ) -> Result<Handler<F, Ctx>, StaticProgramError>
86 where
87 Ctx: ExecutionCtxTrait,
88 {
89 let data: &mut KeccakPreCompute = data.borrow_mut();
90 self.pre_compute_impl(pc, inst, data)?;
91 Ok(execute_e1_handler)
92 }
93}
94
95impl<F: PrimeField32> MeteredExecutor<F> for KeccakVmExecutor {
96 fn metered_pre_compute_size(&self) -> usize {
97 size_of::<E2PreCompute<KeccakPreCompute>>()
98 }
99
100 #[cfg(not(feature = "tco"))]
101 fn metered_pre_compute<Ctx>(
102 &self,
103 chip_idx: usize,
104 pc: u32,
105 inst: &Instruction<F>,
106 data: &mut [u8],
107 ) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError>
108 where
109 Ctx: MeteredExecutionCtxTrait,
110 {
111 let data: &mut E2PreCompute<KeccakPreCompute> = data.borrow_mut();
112 data.chip_idx = chip_idx as u32;
113 self.pre_compute_impl(pc, inst, &mut data.data)?;
114 Ok(execute_e2_impl::<_, _>)
115 }
116
117 #[cfg(feature = "tco")]
118 fn metered_handler<Ctx>(
119 &self,
120 chip_idx: usize,
121 pc: u32,
122 inst: &Instruction<F>,
123 data: &mut [u8],
124 ) -> Result<Handler<F, Ctx>, StaticProgramError>
125 where
126 Ctx: MeteredExecutionCtxTrait,
127 {
128 let data: &mut E2PreCompute<KeccakPreCompute> = data.borrow_mut();
129 data.chip_idx = chip_idx as u32;
130 self.pre_compute_impl(pc, inst, &mut data.data)?;
131 Ok(execute_e2_handler::<_, _>)
132 }
133}
134
135#[inline(always)]
136unsafe fn execute_e12_impl<F: PrimeField32, CTX: ExecutionCtxTrait, const IS_E1: bool>(
137 pre_compute: &KeccakPreCompute,
138 instret: &mut u64,
139 pc: &mut u32,
140 exec_state: &mut VmExecState<F, GuestMemory, CTX>,
141) -> u32 {
142 let dst = exec_state.vm_read(RV32_REGISTER_AS, pre_compute.a as u32);
143 let src = exec_state.vm_read(RV32_REGISTER_AS, pre_compute.b as u32);
144 let len = exec_state.vm_read(RV32_REGISTER_AS, pre_compute.c as u32);
145 let dst_u32 = u32::from_le_bytes(dst);
146 let src_u32 = u32::from_le_bytes(src);
147 let len_u32 = u32::from_le_bytes(len);
148
149 let (output, height) = if IS_E1 {
150 let message = exec_state.vm_read_slice(RV32_MEMORY_AS, src_u32, len_u32 as usize);
152 let output = keccak256(message);
153 (output, 0)
154 } else {
155 let num_reads = (len_u32 as usize).div_ceil(KECCAK_WORD_SIZE);
156 let message: Vec<_> = (0..num_reads)
157 .flat_map(|i| {
158 exec_state.vm_read::<u8, KECCAK_WORD_SIZE>(
159 RV32_MEMORY_AS,
160 src_u32 + (i * KECCAK_WORD_SIZE) as u32,
161 )
162 })
163 .collect();
164 let output = keccak256(&message[..len_u32 as usize]);
165 let height = (num_keccak_f(len_u32 as usize) * NUM_ROUNDS) as u32;
166 (output, height)
167 };
168 exec_state.vm_write(RV32_MEMORY_AS, dst_u32, &output);
169
170 *pc = pc.wrapping_add(DEFAULT_PC_STEP);
171 *instret += 1;
172
173 height
174}
175
176#[create_handler]
177#[inline(always)]
178unsafe fn execute_e1_impl<F: PrimeField32, CTX: ExecutionCtxTrait>(
179 pre_compute: &[u8],
180 instret: &mut u64,
181 pc: &mut u32,
182 _instret_end: u64,
183 exec_state: &mut VmExecState<F, GuestMemory, CTX>,
184) {
185 let pre_compute: &KeccakPreCompute = pre_compute.borrow();
186 execute_e12_impl::<F, CTX, true>(pre_compute, instret, pc, exec_state);
187}
188
189#[create_handler]
190#[inline(always)]
191unsafe fn execute_e2_impl<F: PrimeField32, CTX: MeteredExecutionCtxTrait>(
192 pre_compute: &[u8],
193 instret: &mut u64,
194 pc: &mut u32,
195 _arg: u64,
196 exec_state: &mut VmExecState<F, GuestMemory, CTX>,
197) {
198 let pre_compute: &E2PreCompute<KeccakPreCompute> = pre_compute.borrow();
199 let height = execute_e12_impl::<F, CTX, false>(&pre_compute.data, instret, pc, exec_state);
200 exec_state
201 .ctx
202 .on_height_change(pre_compute.chip_idx as usize, height);
203}