openvm_sha2_circuit/sha2_chips/block_hasher_chip/
trace.rs

1use std::slice;
2
3use openvm_circuit::arch::get_record_from_slice;
4use openvm_circuit_primitives::{utils::next_power_of_two_or_zero, Chip};
5use openvm_cpu_backend::CpuBackend;
6use openvm_sha2_air::{
7    be_limbs_into_word, le_limbs_into_word, Sha2BlockHasherFillerHelper, Sha2RoundColsRef,
8    Sha2RoundColsRefMut,
9};
10use openvm_stark_backend::{
11    p3_field::{PrimeCharacteristicRing, PrimeField32},
12    p3_matrix::dense::RowMajorMatrix,
13    p3_maybe_rayon::prelude::*,
14    prover::AirProvingContext,
15    StarkProtocolConfig, Val,
16};
17
18use crate::{
19    Sha2BlockHasherChip, Sha2BlockHasherRoundColsRefMut, Sha2BlockHasherVmConfig, Sha2Config,
20    Sha2Metadata, Sha2RecordLayout, Sha2RecordMut, Sha2SharedRecords, INNER_OFFSET,
21};
22
23// We don't use the record arena associated with this chip. Instead, we will use the record arena
24// provided by the main chip, which will be passed to this chip after the main chip's tracegen is
25// done.
26impl<R, SC, C: Sha2Config> Chip<R, CpuBackend<SC>> for Sha2BlockHasherChip<Val<SC>, C>
27where
28    Val<SC>: PrimeField32,
29    SC: StarkProtocolConfig,
30{
31    fn generate_proving_ctx(&self, _: R) -> AirProvingContext<CpuBackend<SC>> {
32        // SAFETY: the tracegen for Sha2MainChip must be done before this chip's tracegen
33        let mut records = self.records.lock().unwrap();
34        let mut records = records.take().unwrap();
35        let rows_used = records.num_records * C::ROWS_PER_BLOCK;
36
37        let height = next_power_of_two_or_zero(rows_used);
38        let trace = Val::<SC>::zero_vec(height * C::BLOCK_HASHER_WIDTH);
39        let mut trace_matrix = RowMajorMatrix::new(trace, C::BLOCK_HASHER_WIDTH);
40
41        self.fill_trace(&mut trace_matrix, &mut records, rows_used);
42
43        AirProvingContext::simple_no_pis(trace_matrix)
44    }
45}
46
47impl<F, C> Sha2BlockHasherChip<F, C>
48where
49    F: PrimeField32,
50    C: Sha2BlockHasherVmConfig,
51{
52    fn fill_trace(
53        &self,
54        trace_matrix: &mut RowMajorMatrix<F>,
55        records: &mut Sha2SharedRecords<F>,
56        rows_used: usize,
57    ) {
58        if rows_used == 0 {
59            return;
60        }
61
62        let trace = &mut trace_matrix.values[..];
63
64        // grab all the records
65        // we need to do this first, so we can pass (this_block.prev_hash, next_block.prev_hash) to
66        // each block (in the call to fill_block_trace)
67        let (records, prev_hashes): (Vec<_>, Vec<_>) = records
68            .matrix
69            .par_rows_mut()
70            .take(records.num_records)
71            .map(|mut record| {
72                // SAFETY:
73                // - caller ensures `records` contains a valid record representation that was
74                //   previously written by the executor
75                // - records contains a valid Sha2RecordMut with the exact layout specified
76                // - get_record_from_slice will correctly split the buffer into header, input, and
77                //   aux components based on this layout
78                let record: Sha2RecordMut = unsafe {
79                    get_record_from_slice(
80                        &mut record,
81                        Sha2RecordLayout {
82                            metadata: Sha2Metadata {
83                                variant: C::VARIANT,
84                            },
85                        },
86                    )
87                };
88
89                let prev_hash = (0..C::HASH_WORDS)
90                    .map(|i| {
91                        le_limbs_into_word::<C>(
92                            &record.prev_state[i * C::WORD_U8S..(i + 1) * C::WORD_U8S]
93                                .iter()
94                                .map(|x| *x as u32)
95                                .collect::<Vec<_>>(),
96                        )
97                    })
98                    .collect::<Vec<_>>();
99
100                (record, prev_hash)
101            })
102            .unzip();
103
104        // zip the prev_hashes with the next block's prev_hash
105        let prev_hashes_and_next_block_prev_hashes = prev_hashes.par_iter().zip(
106            prev_hashes[1..]
107                .par_iter()
108                .chain(prev_hashes[..1].par_iter()),
109        );
110
111        // fill in used rows
112        trace[..rows_used * C::BLOCK_HASHER_WIDTH]
113            .par_chunks_exact_mut(C::BLOCK_HASHER_WIDTH * C::ROWS_PER_BLOCK)
114            .zip(
115                records
116                    .par_iter()
117                    .zip(prev_hashes_and_next_block_prev_hashes),
118            )
119            .enumerate()
120            .for_each(
121                |(block_idx, (block_slice, (record, (prev_hash, next_block_prev_hash))))| {
122                    self.fill_block_trace(
123                        block_slice,
124                        record.message_bytes,
125                        block_idx + 1, // 1-indexed
126                        prev_hash,
127                        next_block_prev_hash,
128                        block_idx,
129                    );
130                },
131            );
132
133        // fill in the first dummy row.
134        // we need to do this first, so we can compute the carries that make the
135        // constraint_word_addition constraints hold on dummy rows (or more precisely, on rows such
136        // that the next row is a dummy row).
137        let num_blocks = rows_used / C::ROWS_PER_BLOCK;
138        let first_dummy_row_cols_const = self.fill_first_dummy_row(
139            &mut trace[rows_used * C::BLOCK_HASHER_WIDTH..(rows_used + 1) * C::BLOCK_HASHER_WIDTH],
140            &prev_hashes[0],
141            num_blocks,
142        );
143
144        // fill in the rest of the dummy rows
145        let padding_global_block_idx = (num_blocks + 1) as u32;
146        trace[(rows_used + 1) * C::BLOCK_HASHER_WIDTH..]
147            .par_chunks_exact_mut(C::BLOCK_HASHER_WIDTH)
148            .for_each(|row| {
149                // copy the carries from the first dummy row into the current dummy row
150                self.inner.generate_default_row(
151                    &mut Sha2RoundColsRefMut::from::<C>(
152                        &mut row[INNER_OFFSET..INNER_OFFSET + C::SUBAIR_ROUND_WIDTH],
153                    ),
154                    &prev_hashes[0],
155                    Some(
156                        first_dummy_row_cols_const
157                            .work_vars
158                            .carry_a
159                            .as_slice()
160                            .unwrap(),
161                    ),
162                    Some(
163                        first_dummy_row_cols_const
164                            .work_vars
165                            .carry_e
166                            .as_slice()
167                            .unwrap(),
168                    ),
169                    padding_global_block_idx,
170                );
171            });
172
173        // Do a second pass over the trace to fill in the missing values
174        // Note, we need to skip the very first row
175        trace[C::BLOCK_HASHER_WIDTH..]
176            .par_chunks_mut(C::BLOCK_HASHER_WIDTH * C::ROWS_PER_BLOCK)
177            .take(rows_used / C::ROWS_PER_BLOCK)
178            .for_each(|chunk| {
179                self.inner
180                    .generate_missing_cells(chunk, C::BLOCK_HASHER_WIDTH, INNER_OFFSET);
181            });
182
183        self.fill_wraparound(trace);
184    }
185
186    /// Fill in dummy values for the wrap-around (last row → first row) so that
187    /// unconditional constraints hold:
188    /// - `intermed_4` on row 0 for the message schedule sigma constraint
189    /// - `intermed_12` on the last row for the message schedule addition constraint
190    fn fill_wraparound(&self, trace: &mut [F]) {
191        let height = trace.len() / C::BLOCK_HASHER_WIDTH;
192        let last_row_start = (height - 1) * C::BLOCK_HASHER_WIDTH;
193
194        // Fill intermed_4 on the first row (needs first_row mut, last_row immut)
195        {
196            let (first_row, rest) = trace.split_at_mut(C::BLOCK_HASHER_WIDTH);
197            let last_row = &rest[(height - 2) * C::BLOCK_HASHER_WIDTH..];
198            let local = Sha2RoundColsRef::from::<C>(
199                &last_row[INNER_OFFSET..INNER_OFFSET + C::SUBAIR_ROUND_WIDTH],
200            );
201            let mut next = Sha2RoundColsRefMut::from::<C>(
202                &mut first_row[INNER_OFFSET..INNER_OFFSET + C::SUBAIR_ROUND_WIDTH],
203            );
204            Sha2BlockHasherFillerHelper::<C>::generate_intermed_4(local, &mut next);
205        }
206
207        // Fill intermed_12 on the last row (needs last_row mut, first_row immut)
208        {
209            let (first_row, rest) = trace.split_at_mut(C::BLOCK_HASHER_WIDTH);
210            let next = Sha2RoundColsRef::from::<C>(
211                &first_row[INNER_OFFSET..INNER_OFFSET + C::SUBAIR_ROUND_WIDTH],
212            );
213            let last_row = &mut rest[last_row_start - C::BLOCK_HASHER_WIDTH..last_row_start];
214            let mut local = Sha2RoundColsRefMut::from::<C>(
215                &mut last_row[INNER_OFFSET..INNER_OFFSET + C::SUBAIR_ROUND_WIDTH],
216            );
217            Sha2BlockHasherFillerHelper::<C>::generate_intermed_12(&mut local, next);
218        }
219    }
220
221    fn fill_first_dummy_row(
222        &self,
223        first_dummy_row_mut: &mut [F],
224        first_block_prev_hash: &[C::Word],
225        num_blocks: usize,
226    ) -> Sha2RoundColsRef<'_, F> {
227        let first_dummy_row_const =
228            unsafe { slice::from_raw_parts(first_dummy_row_mut.as_ptr(), C::BLOCK_HASHER_WIDTH) };
229        let first_dummy_row_cols_const = Sha2RoundColsRef::from::<C>(
230            &first_dummy_row_const[INNER_OFFSET..INNER_OFFSET + C::SUBAIR_ROUND_WIDTH],
231        );
232
233        let first_dummy_row_mut = unsafe {
234            slice::from_raw_parts_mut(first_dummy_row_mut.as_mut_ptr(), C::BLOCK_HASHER_WIDTH)
235        };
236        let mut first_dummy_row_cols_mut: Sha2RoundColsRefMut<F> = Sha2RoundColsRefMut::from::<C>(
237            &mut first_dummy_row_mut[INNER_OFFSET..INNER_OFFSET + C::SUBAIR_ROUND_WIDTH],
238        );
239
240        // first, fill in everything but the carries into the first dummy row (i.e. fill in the
241        // work vars, row_idx, and global_block_idx)
242        self.inner.generate_default_row(
243            &mut first_dummy_row_cols_mut,
244            first_block_prev_hash,
245            None,
246            None,
247            (num_blocks + 1) as u32,
248        );
249
250        // Now, this will fill in the first dummy row with the correct carries.
251        // This works because we already filled in the work vars into the first dummy row, and
252        // generate_carry_ae only looks at the work vars.
253        // Note that these carries will work for any pair of dummy rows, since all dummy rows
254        // have the same work vars (the first block's prev_hash).
255        Sha2BlockHasherFillerHelper::<C>::generate_carry_ae(
256            first_dummy_row_cols_const.clone(),
257            &mut first_dummy_row_cols_mut,
258        );
259
260        first_dummy_row_cols_const
261    }
262}
263
264impl<F, C: Sha2BlockHasherVmConfig> Sha2BlockHasherChip<F, C> {
265    #[allow(clippy::too_many_arguments)]
266    fn fill_block_trace(
267        &self,
268        block_slice: &mut [F],
269        input: &[u8],
270        global_block_idx: usize, // 1-indexed
271        prev_hash: &[C::Word],
272        next_block_prev_hash: &[C::Word],
273        request_id: usize,
274    ) where
275        F: PrimeField32,
276    {
277        debug_assert_eq!(input.len(), C::BLOCK_U8S);
278        debug_assert_eq!(prev_hash.len(), C::HASH_WORDS);
279
280        // Set request_id
281        block_slice
282            .par_chunks_exact_mut(C::BLOCK_HASHER_WIDTH)
283            .for_each(|row_slice| {
284                // Set request_id
285                let cols = Sha2BlockHasherRoundColsRefMut::<F>::from::<C>(
286                    &mut row_slice[..C::BLOCK_HASHER_WIDTH],
287                );
288                *cols.request_id = F::from_usize(request_id);
289            });
290
291        let input_words = (0..C::BLOCK_WORDS)
292            .map(|i| {
293                be_limbs_into_word::<C>(
294                    &input[i * C::WORD_U8S..(i + 1) * C::WORD_U8S]
295                        .iter()
296                        .map(|x| *x as u32)
297                        .collect::<Vec<_>>(),
298                )
299            })
300            .collect::<Vec<_>>();
301
302        // Fill in the inner trace
303        self.inner.generate_block_trace(
304            block_slice,
305            C::BLOCK_HASHER_WIDTH,
306            INNER_OFFSET,
307            &input_words,
308            self.bitwise_lookup_chip.clone(),
309            prev_hash,
310            next_block_prev_hash,
311            global_block_idx as u32,
312        );
313    }
314}