openvm_keccak256_circuit/xorin/
air.rs

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    /// Bus to send 8-bit XOR requests to.
34    pub bitwise_lookup_bus: BitwiseOperationLookupBus,
35    /// Maximum number of bits allowed for an address pointer
36    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    // Increases timestamp by 105
50    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    // Increases timestamp by 3
81    #[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        // returns start_read_timestamp
89        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        // check that is_padding_bytes is of the form 0...0111...1
113        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        // Increases timestamp by 3
141        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        // SAFETY: this approach only works when self.ptr_max_bits >= 24
159        // because we are only range checking the last limb
160        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    // Increases timestamp by <= 2 * 34 = 68
204    #[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        // Constrain read of buffer bytes
217        // Timestamp increases by <= (136/4) = 34
218        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        // Constrain read of input_bytes
246        // Timestamp increases by at most (136/4) = 34
247        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        // Constrain write of buffer bytes
316        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}