1use std::borrow::Borrow;
2
3use itertools::izip;
4use openvm_circuit::{
5 arch::{ExecutionBridge, ExecutionState},
6 system::memory::{
7 offline_checker::{MemoryBridge, MemoryReadAuxCols, MemoryWriteAuxCols},
8 MemoryAddress,
9 },
10};
11use openvm_circuit_primitives::{
12 bitwise_op_lookup::BitwiseOperationLookupBus, utils::not, ColumnsAir,
13};
14use openvm_instructions::riscv::{
15 RV32_CELL_BITS, RV32_MEMORY_AS, RV32_REGISTER_AS, RV32_REGISTER_NUM_LIMBS,
16};
17use openvm_keccak256_transpiler::XorinOpcode;
18use openvm_stark_backend::{
19 interaction::InteractionBuilder,
20 p3_air::{Air, AirBuilder, BaseAir},
21 p3_field::PrimeCharacteristicRing,
22 p3_matrix::Matrix,
23 BaseAirWithPublicValues, PartitionedBaseAir,
24};
25
26use crate::xorin::columns::{XorinVmCols, NUM_XORIN_VM_COLS};
27
28#[derive(Clone, Copy, Debug, derive_new::new, ColumnsAir)]
29#[columns_via(XorinVmCols<u8>)]
30pub struct XorinVmAir {
31 pub execution_bridge: ExecutionBridge,
32 pub memory_bridge: MemoryBridge,
33 pub bitwise_lookup_bus: BitwiseOperationLookupBus,
35 pub ptr_max_bits: usize,
37 pub(super) offset: usize,
38}
39
40impl<F> BaseAirWithPublicValues<F> for XorinVmAir {}
41impl<F> PartitionedBaseAir<F> for XorinVmAir {}
42impl<F> BaseAir<F> for XorinVmAir {
43 fn width(&self) -> usize {
44 NUM_XORIN_VM_COLS
45 }
46}
47
48impl<AB: InteractionBuilder> Air<AB> for XorinVmAir {
49 fn eval(&self, builder: &mut AB) {
51 let main = builder.main();
52
53 let local = main.row_slice(0).unwrap();
54 let local: &XorinVmCols<_> = (*local).borrow();
55
56 let mem = &local.mem_oc;
57
58 let start_read_timestamp = self.eval_instruction(builder, local, &mem.register_aux_cols);
59
60 let start_write_timestamp = self.constrain_input_read(
61 builder,
62 local,
63 start_read_timestamp,
64 &mem.input_bytes_read_aux_cols,
65 &mem.buffer_bytes_read_aux_cols,
66 );
67
68 self.constrain_xor(builder, local);
69
70 self.constrain_output_write(
71 builder,
72 local,
73 start_write_timestamp,
74 &mem.buffer_bytes_write_aux_cols,
75 );
76 }
77}
78
79impl XorinVmAir {
80 #[inline]
82 pub fn eval_instruction<AB: InteractionBuilder>(
83 &self,
84 builder: &mut AB,
85 local: &XorinVmCols<AB::Var>,
86 register_aux: &[MemoryReadAuxCols<AB::Var>; 3],
87 ) -> AB::Expr {
88 let instruction = local.instruction;
90 let is_enabled = local.instruction.is_enabled;
91 builder.assert_bool(is_enabled);
92
93 let [buffer_reg_ptr, input_reg_ptr, len_reg_ptr] = [
94 instruction.buffer_reg_ptr,
95 instruction.input_reg_ptr,
96 instruction.len_reg_ptr,
97 ];
98
99 let mut timestamp_change = AB::Expr::from_u32(3);
100 let mut not_padding_sum = AB::Expr::ZERO;
101
102 for is_padding in local.sponge.is_padding_bytes {
103 timestamp_change += AB::Expr::from_u32(3) * not(is_padding);
104 not_padding_sum += not(is_padding);
105 builder.assert_bool(is_padding);
106 }
107
108 not_padding_sum *= AB::Expr::from_u32(4);
109 builder
110 .when(is_enabled)
111 .assert_eq(not_padding_sum, instruction.len);
112 for i in 0..33 {
114 builder.when(is_enabled).assert_bool(
115 local.sponge.is_padding_bytes[i + 1] - local.sponge.is_padding_bytes[i],
116 );
117 }
118
119 self.execution_bridge
120 .execute_and_increment_pc(
121 AB::Expr::from_usize(XorinOpcode::XORIN as usize + self.offset),
122 [
123 buffer_reg_ptr.into(),
124 input_reg_ptr.into(),
125 len_reg_ptr.into(),
126 AB::Expr::from_u32(RV32_REGISTER_AS),
127 AB::Expr::from_u32(RV32_MEMORY_AS),
128 ],
129 ExecutionState::new(instruction.pc, instruction.start_timestamp),
130 timestamp_change,
131 )
132 .eval(builder, is_enabled);
133
134 let mut timestamp: AB::Expr = instruction.start_timestamp.into();
135
136 let buffer_ptr_limbs = instruction.buffer_ptr_limbs.map(Into::into);
137 let input_ptr_limbs = instruction.input_ptr_limbs.map(Into::into);
138 let len_limbs = instruction.len_limbs.map(Into::into);
139
140 for (ptr, value, aux) in izip!(
142 [buffer_reg_ptr, input_reg_ptr, len_reg_ptr],
143 [buffer_ptr_limbs, input_ptr_limbs, len_limbs],
144 register_aux
145 ) {
146 self.memory_bridge
147 .read(
148 MemoryAddress::new(AB::Expr::from_u32(RV32_REGISTER_AS), ptr),
149 value,
150 timestamp.clone(),
151 aux,
152 )
153 .eval(builder, is_enabled);
154
155 timestamp += AB::Expr::ONE;
156 }
157
158 assert!(self.ptr_max_bits >= RV32_CELL_BITS * (RV32_REGISTER_NUM_LIMBS - 1));
161 let need_range_check = [
162 *instruction.buffer_ptr_limbs.last().unwrap(),
163 *instruction.input_ptr_limbs.last().unwrap(),
164 *instruction.len_limbs.last().unwrap(),
165 *instruction.len_limbs.last().unwrap(),
166 ];
167
168 let limb_shift =
169 AB::F::from_usize(1 << (RV32_CELL_BITS * RV32_REGISTER_NUM_LIMBS - self.ptr_max_bits));
170 for pair in need_range_check.chunks_exact(2) {
171 self.bitwise_lookup_bus
172 .send_range(pair[0] * limb_shift, pair[1] * limb_shift)
173 .eval(builder, is_enabled);
174 }
175
176 builder.assert_eq(
177 instruction.buffer_ptr,
178 instruction.buffer_ptr_limbs[0]
179 + instruction.buffer_ptr_limbs[1] * AB::F::from_u32(1 << 8)
180 + instruction.buffer_ptr_limbs[2] * AB::F::from_u32(1 << 16)
181 + instruction.buffer_ptr_limbs[3] * AB::F::from_u32(1 << 24),
182 );
183
184 builder.assert_eq(
185 instruction.input_ptr,
186 instruction.input_ptr_limbs[0]
187 + instruction.input_ptr_limbs[1] * AB::F::from_u32(1 << 8)
188 + instruction.input_ptr_limbs[2] * AB::F::from_u32(1 << 16)
189 + instruction.input_ptr_limbs[3] * AB::F::from_u32(1 << 24),
190 );
191
192 builder.assert_eq(
193 instruction.len,
194 instruction.len_limbs[0]
195 + instruction.len_limbs[1] * AB::F::from_u32(1 << 8)
196 + instruction.len_limbs[2] * AB::F::from_u32(1 << 16)
197 + instruction.len_limbs[3] * AB::F::from_u32(1 << 24),
198 );
199
200 timestamp
201 }
202
203 #[inline]
205 pub fn constrain_input_read<AB: InteractionBuilder>(
206 &self,
207 builder: &mut AB,
208 local: &XorinVmCols<AB::Var>,
209 start_read_timestamp: AB::Expr,
210 input_bytes_read_aux_cols: &[MemoryReadAuxCols<AB::Var>; 34],
211 buffer_bytes_read_aux_cols: &[MemoryReadAuxCols<AB::Var>; 34],
212 ) -> AB::Expr {
213 let is_enabled = local.instruction.is_enabled;
214 let mut timestamp = start_read_timestamp;
215
216 for (i, (input, is_padding, mem_aux)) in izip!(
219 local.sponge.preimage_buffer_bytes.chunks_exact(4),
220 local.sponge.is_padding_bytes,
221 buffer_bytes_read_aux_cols
222 )
223 .enumerate()
224 {
225 let ptr = local.instruction.buffer_ptr + AB::F::from_usize(i * 4);
226 let should_read = is_enabled * not(is_padding);
227
228 self.memory_bridge
229 .read(
230 MemoryAddress::new(AB::Expr::from_u32(RV32_MEMORY_AS), ptr),
231 [
232 input[0].into(),
233 input[1].into(),
234 input[2].into(),
235 input[3].into(),
236 ],
237 timestamp.clone(),
238 mem_aux,
239 )
240 .eval(builder, should_read);
241
242 timestamp += not(is_padding);
243 }
244
245 for (i, (input, is_padding, mem_aux)) in izip!(
248 local.sponge.input_bytes.chunks_exact(4),
249 local.sponge.is_padding_bytes,
250 input_bytes_read_aux_cols
251 )
252 .enumerate()
253 {
254 let ptr = local.instruction.input_ptr + AB::F::from_usize(i * 4);
255 let should_read = is_enabled * not(is_padding);
256
257 self.memory_bridge
258 .read(
259 MemoryAddress::new(AB::Expr::from_u32(RV32_MEMORY_AS), ptr),
260 [
261 input[0].into(),
262 input[1].into(),
263 input[2].into(),
264 input[3].into(),
265 ],
266 timestamp.clone(),
267 mem_aux,
268 )
269 .eval(builder, should_read);
270
271 timestamp += not(is_padding);
272 }
273
274 timestamp
275 }
276
277 #[inline]
278 pub fn constrain_xor<AB: InteractionBuilder>(
279 &self,
280 builder: &mut AB,
281 local: &XorinVmCols<AB::Var>,
282 ) {
283 let buffer_bytes = local.sponge.preimage_buffer_bytes;
284 let input_bytes = local.sponge.input_bytes;
285 let result_bytes = local.sponge.postimage_buffer_bytes;
286 let padding_bytes = local.sponge.is_padding_bytes;
287 let is_enabled = local.instruction.is_enabled;
288
289 for (x_chunks, y_chunks, x_xor_y_chunks, is_padding) in izip!(
290 buffer_bytes.chunks_exact(4),
291 input_bytes.chunks_exact(4),
292 result_bytes.chunks_exact(4),
293 padding_bytes
294 ) {
295 let should_send = is_enabled * not(is_padding);
296 for (x, y, x_xor_y) in izip!(x_chunks, y_chunks, x_xor_y_chunks) {
297 self.bitwise_lookup_bus
298 .send_xor(*x, *y, *x_xor_y)
299 .eval(builder, should_send.clone());
300 }
301 }
302 }
303
304 #[inline]
305 pub fn constrain_output_write<AB: InteractionBuilder>(
306 &self,
307 builder: &mut AB,
308 local: &XorinVmCols<AB::Var>,
309 start_write_timestamp: AB::Expr,
310 mem_aux: &[MemoryWriteAuxCols<AB::Var, 4>; 34],
311 ) {
312 let mut timestamp = start_write_timestamp;
313 let is_enabled = local.instruction.is_enabled;
314
315 for (i, (output, is_padding, mem_aux)) in izip!(
317 local.sponge.postimage_buffer_bytes.chunks_exact(4),
318 local.sponge.is_padding_bytes,
319 mem_aux
320 )
321 .enumerate()
322 {
323 let should_write = is_enabled * not(is_padding);
324 let ptr = local.instruction.buffer_ptr + AB::F::from_usize(i * 4);
325
326 self.memory_bridge
327 .write(
328 MemoryAddress::new(AB::Expr::from_u32(RV32_MEMORY_AS), ptr),
329 [
330 output[0].into(),
331 output[1].into(),
332 output[2].into(),
333 output[3].into(),
334 ],
335 timestamp.clone(),
336 mem_aux,
337 )
338 .eval(builder, should_write);
339
340 timestamp += not(is_padding);
341 }
342 }
343}