Source code for zisk/precompiles/sha256f/src/sha256f.rs
1use core::panic;
2use std::sync::Arc;
3
4use fields::PrimeField64;
5use rayon::prelude::*;
6
7use pil_std_lib::Std;
8use proofman_common::{AirInstance, FromTrace, ProofmanResult};
9use proofman_util::{timer_start_trace, timer_stop_and_log_trace};
10#[cfg(not(feature = "packed"))]
11use zisk_pil::{Sha256fTrace, Sha256fTraceRow};
12#[cfg(feature = "packed")]
13use zisk_pil::{Sha256fTracePacked, Sha256fTraceRowPacked};
14
15#[cfg(feature = "packed")]
16type Sha256fTraceRowType<F> = Sha256fTraceRowPacked<F>;
17#[cfg(feature = "packed")]
18type Sha256fTraceType<F> = Sha256fTracePacked<F>;
19
20#[cfg(not(feature = "packed"))]
21type Sha256fTraceRowType<F> = Sha256fTraceRow<F>;
22#[cfg(not(feature = "packed"))]
23type Sha256fTraceType<F> = Sha256fTrace<F>;
24
25use super::{sha256f_constants::*, Sha256fInput};
26
27/// The `Sha256fSM` struct encapsulates the logic of the Sha256f State Machine.
28pub struct Sha256fSM<F: PrimeField64> {
29 /// Reference to the PIL2 standard library.
30 pub std: Arc<Std<F>>,
31
32 /// Number of available sha256fs in the trace.
33 pub num_available_sha256fs: usize,
34
35 num_non_usable_rows: usize,
36
37 /// Range checks ID's
38 a_range_id: usize,
39 e_range_id: usize,
40}
41
42impl<F: PrimeField64> Sha256fSM<F> {
43 /// Creates a new Sha256f State Machine instance.
44 ///
45 /// # Returns
46 /// A new `Sha256fSM` instance.
47 pub fn new(std: Arc<Std<F>>) -> Arc<Self> {
48 // Compute some useful values
49 let num_available_sha256fs = Sha256fTraceType::<F>::NUM_ROWS / CLOCKS - 1;
50 let num_non_usable_rows = Sha256fTraceType::<F>::NUM_ROWS % CLOCKS;
51
52 let a_range_id = std.get_range_id(0, (1 << 3) - 1, None).expect("Failed to get range ID");
53 let e_range_id = std.get_range_id(0, (1 << 3) - 1, None).expect("Failed to get range ID");
54
55 Arc::new(Self { std, num_available_sha256fs, num_non_usable_rows, a_range_id, e_range_id })
56 }
57
58 /// Processes a slice of operation data, updating the trace and multiplicities.
59 ///
60 /// # Arguments
61 /// * `trace` - A mutable reference to the Sha256f trace.
62 /// * `num_circuits` - The number of circuits to process.
63 /// * `input` - The operation data to process.
64 /// * `multiplicity` - A mutable slice to update with multiplicities for the operation.
65 #[inline(always)]
66 pub fn process_input(
67 &self,
68 input: &Sha256fInput,
69 trace: &mut [Sha256fTraceRowType<F>],
70 ) -> ([u32; 8], [u32; 8]) {
71 let mut a_range_checks = [0u32; 8];
72 let mut e_range_checks = [0u32; 8];
73
74 let step_main = input.step_main;
75 let addr_main = input.addr_main;
76 let state_addr = input.state_addr;
77 let input_addr = input.input_addr;
78 let state = &input.state;
79 let input = &input.input;
80
81 // Fill the step_addr
82 trace[0].set_step_addr(step_main); // STEP_MAIN
83 trace[1].set_step_addr(addr_main as u64); // ADDR_OP
84 trace[2].set_step_addr(state_addr as u64); // ADDR_STATE
85 trace[3].set_step_addr(input_addr as u64); // ADDR_INPUT
86 trace[4].set_step_addr(state_addr as u64); // ADDR_IND_0
87 trace[5].set_step_addr(input_addr as u64); // ADDR_IND_1
88
89 // Activate the clk_0 selector
90 trace[0].set_in_use_clk_0(true);
91
92 // Activate the in_use selector
93 for r in trace.iter_mut().take(18) {
94 r.set_in_use(true);
95 }
96
97 // Compute the load state stage
98 let mut offset = 0;
99 let mut prev_state = [0u32; 8];
100 for i in 0..CLOCKS_LOAD_STATE {
101 let word = state[i];
102 let word_high = (word >> 32) as u32;
103 let word_low = (word & 0xFFFF_FFFF) as u32;
104
105 // Store the state as u32 for further processing
106 prev_state[2 * i] = word_high;
107 prev_state[2 * i + 1] = word_low;
108
109 let mut row = if i == 1 || i == 3 { offset + 1 } else { offset + 3 };
110
111 // Locate the state bits in the trace
112 let is_a = i < 2;
113 for j in 0..32 {
114 let bit = ((word_high >> j) & 1) != 0;
115 if is_a {
116 trace[row].set_a(j, bit);
117 } else {
118 trace[row].set_e(j, bit);
119 }
120 }
121 row -= 1;
122 for j in 0..32 {
123 let bit = ((word_low >> j) & 1) != 0;
124 if is_a {
125 trace[row].set_a(j, bit);
126 } else {
127 trace[row].set_e(j, bit);
128 }
129 }
130 }
131 offset += CLOCKS_LOAD_STATE;
132
133 // Compute the load input stage
134 let mut w = [0u32; 16];
135 for i in 0..CLOCKS_LOAD_INPUT {
136 let word = input[i / 2];
137
138 // Store the input as u32 for further processing
139 w[i] = if i % 2 == 0 { (word >> 32) as u32 } else { (word & 0xFFFF_FFFF) as u32 };
140
141 // Compute the a and e values for the current input
142 let [old_a, old_b, old_c, old_d, old_e, old_f, old_g, old_h] = prev_state;
143 let (a, e) =
144 compute_ae(old_a, old_b, old_c, old_d, old_e, old_f, old_g, old_h, w[i], RC[i]);
145
146 let (a_carry, a) = ((a >> 32) as u8, (a & 0xFFFF_FFFF) as u32);
147 let (e_carry, e) = ((e >> 32) as u8, (e & 0xFFFF_FFFF) as u32);
148
149 let row = offset + i;
150
151 // Locate the carry
152 trace[row].set_new_a_carry_bits(a_carry);
153 trace[row].set_new_e_carry_bits(e_carry);
154 a_range_checks[a_carry as usize] += 1;
155 e_range_checks[e_carry as usize] += 1;
156
157 // Locate the input bits in the trace
158 for j in 0..32 {
159 let bit_a = ((a >> j) & 1) != 0;
160 let bit_e = ((e >> j) & 1) != 0;
161 let bit_w = ((w[i] >> j) & 1) != 0;
162 trace[row].set_a(j, bit_a);
163 trace[row].set_e(j, bit_e);
164 trace[row].set_w(j, bit_w);
165 }
166
167 // Update prev_state for the next iteration
168 prev_state[7] = old_g;
169 prev_state[6] = old_f;
170 prev_state[5] = old_e;
171 prev_state[4] = e;
172 prev_state[3] = old_c;
173 prev_state[2] = old_b;
174 prev_state[1] = old_a;
175 prev_state[0] = a;
176 }
177 offset += CLOCKS_LOAD_INPUT;
178
179 // Compute the mixing stage
180 for i in 0..CLOCKS_MIXING {
181 let [old_w2, old_w7, old_w15, old_w16] = [
182 w[CLOCKS_LOAD_INPUT - 2],
183 w[CLOCKS_LOAD_INPUT - 7],
184 w[CLOCKS_LOAD_INPUT - 15],
185 w[CLOCKS_LOAD_INPUT - 16],
186 ];
187 let new_w = compute_w(old_w2, old_w7, old_w15, old_w16);
188 let (new_w_carry, new_w) = ((new_w >> 32) as u8, (new_w & 0xFFFF_FFFF) as u32);
189
190 let [old_a, old_b, old_c, old_d, old_e, old_f, old_g, old_h] = prev_state;
191 #[rustfmt::skip]
192 let (a, e) = compute_ae(old_a, old_b, old_c, old_d, old_e, old_f, old_g, old_h, new_w, RC[CLOCKS_LOAD_INPUT + i]);
193
194 let (a_carry, a) = ((a >> 32) as u8, (a & 0xFFFF_FFFF) as u32);
195 let (e_carry, e) = ((e >> 32) as u8, (e & 0xFFFF_FFFF) as u32);
196
197 let row = offset + i;
198
199 // Locate the carry
200 trace[row].set_new_a_carry_bits(a_carry);
201 trace[row].set_new_e_carry_bits(e_carry);
202 trace[row].set_new_w_carry_bits(new_w_carry);
203 a_range_checks[a_carry as usize] += 1;
204 e_range_checks[e_carry as usize] += 1;
205
206 for j in 0..32 {
207 let bit_a = ((a >> j) & 1) != 0;
208 let bit_e = ((e >> j) & 1) != 0;
209 let bit_w = ((new_w >> j) & 1) != 0;
210 trace[row].set_a(j, bit_a);
211 trace[row].set_e(j, bit_e);
212 trace[row].set_w(j, bit_w);
213 }
214
215 // Update prev_state for the next iteration
216 prev_state[7] = old_g;
217 prev_state[6] = old_f;
218 prev_state[5] = old_e;
219 prev_state[4] = e;
220 prev_state[3] = old_c;
221 prev_state[2] = old_b;
222 prev_state[1] = old_a;
223 prev_state[0] = a;
224
225 // Update the w array for the next iteration
226 for j in 0..15 {
227 w[j] = w[j + 1];
228 }
229 w[15] = new_w;
230 }
231 offset += CLOCKS_MIXING;
232
233 for i in 0..CLOCKS_WRITE_STATE {
234 let prev = state[i];
235 let prev_high = prev >> 32;
236 let prev_low = prev & 0xFFFF_FFFF;
237
238 let curr_high = (prev_state[2 * i]) as u64;
239 let curr_low = (prev_state[2 * i + 1]) as u64;
240
241 let new_high = curr_high + prev_high;
242 let new_low = curr_low + prev_low;
243 let (new_high_carry, new_high) =
244 ((new_high >> 32) as u8, (new_high & 0xFFFF_FFFF) as u32);
245 let (new_low_carry, new_low) = ((new_low >> 32) as u8, (new_low & 0xFFFF_FFFF) as u32);
246
247 let mut row = if i == 1 || i == 3 { offset + 1 } else { offset + 3 };
248
249 // Locate the state bits in the trace
250 let is_a = i < 2;
251 if is_a {
252 trace[row].set_new_a_carry_bits(new_high_carry);
253 a_range_checks[new_high_carry as usize] += 1;
254 } else {
255 trace[row].set_new_e_carry_bits(new_high_carry);
256 e_range_checks[new_high_carry as usize] += 1;
257 }
258
259 for j in 0..32 {
260 let bit = ((new_high >> j) & 1) != 0;
261 if is_a {
262 trace[row].set_a(j, bit);
263 } else {
264 trace[row].set_e(j, bit);
265 }
266 }
267 row -= 1;
268
269 if is_a {
270 trace[row].set_new_a_carry_bits(new_low_carry);
271 a_range_checks[new_low_carry as usize] += 1;
272 } else {
273 trace[row].set_new_e_carry_bits(new_low_carry);
274 e_range_checks[new_low_carry as usize] += 1;
275 }
276
277 for j in 0..32 {
278 let bit = ((new_low >> j) & 1) != 0;
279 if is_a {
280 trace[row].set_a(j, bit);
281 } else {
282 trace[row].set_e(j, bit);
283 }
284 }
285 }
286
287 // Perform the zero range checks
288 a_range_checks[0] += CLOCKS_LOAD_STATE as u32;
289 e_range_checks[0] += CLOCKS_LOAD_STATE as u32;
290
291 return (a_range_checks, e_range_checks);
292
293 #[rustfmt::skip]
294 #[allow(clippy::too_many_arguments)]
295 fn compute_ae(old_a: u32, old_b: u32, old_c: u32, old_d: u32, old_e: u32, old_f: u32, old_g: u32, old_h: u32, w: u32, k: u32) -> (u64, u64) {
296 let s0 = rotate_right(old_a, 2) ^ rotate_right(old_a, 13) ^ rotate_right(old_a, 22);
297 let s1 = rotate_right(old_e, 6) ^ rotate_right(old_e, 11) ^ rotate_right(old_e, 25);
298 let t1 = (old_h as u64) + (s1 as u64) + (ch(old_e, old_f, old_g) as u64) + (k as u64) + (w as u64);
299 let t2 = (s0 as u64) + (maj(old_a, old_b, old_c) as u64);
300 let a = (t1 as u64) + (t2 as u64);
301 let e = (old_d as u64) + (t1 as u64);
302 (a, e)
303 // (s0 as u64, s1 as u64)
304 }
305
306 fn compute_w(old_w2: u32, old_w7: u32, old_w15: u32, old_w16: u32) -> u64 {
307 let s0 = rotate_right(old_w15, 7) ^ rotate_right(old_w15, 18) ^ shift_right(old_w15, 3);
308 let s1 = rotate_right(old_w2, 17) ^ rotate_right(old_w2, 19) ^ shift_right(old_w2, 10);
309 (s1 as u64) + (old_w7 as u64) + (s0 as u64) + (old_w16 as u64)
310 }
311
312 fn rotate_right(x: u32, n: u32) -> u32 {
313 x.rotate_right(n)
314 }
315
316 fn shift_right(x: u32, n: u32) -> u32 {
317 x >> n
318 }
319
320 fn maj(x: u32, y: u32, z: u32) -> u32 {
321 (x & y) ^ (x & z) ^ (y & z)
322 }
323
324 fn ch(x: u32, y: u32, z: u32) -> u32 {
325 (x & y) ^ (!x & z)
326 }
327 }
328
329 /// Computes the witness for a series of inputs and produces an `AirInstance`.
330 ///
331 /// # Arguments
332 /// * `sctx` - The setup context containing the setup data.
333 /// * `inputs` - A slice of operations to process.
334 ///
335 /// # Returns
336 /// An `AirInstance` containing the computed witness data.
337 pub fn compute_witness(
338 &self,
339 inputs: &[Vec<Sha256fInput>],
340 trace_buffer: Vec<F>,
341 ) -> ProofmanResult<AirInstance<F>> {
342 let mut sha256f_trace = Sha256fTraceType::new_from_vec_zeroes(trace_buffer)?;
343 let num_rows = sha256f_trace.num_rows();
344 let num_available_sha256fs = self.num_available_sha256fs;
345
346 let mut a_range_checks = vec![0; 1 << 3];
347 let mut e_range_checks = vec![0; 1 << 3];
348
349 // Check that we can fit all the sha256fs in the trace
350 let num_inputs = inputs.iter().map(|v| v.len()).sum::<usize>();
351 let num_rows_filled = num_inputs * CLOCKS;
352 let num_rows_needed = if num_inputs < num_available_sha256fs {
353 num_inputs * CLOCKS
354 } else if num_inputs == num_available_sha256fs {
355 num_rows
356 } else {
357 panic!(
358 "Exceeded available Sha256fs inputs: requested {}, but only {} are available.",
359 num_inputs, self.num_available_sha256fs
360 );
361 };
362
363 tracing::debug!(
364 "··· Creating Sha256f instance [{} / {} rows filled {:.2}%]",
365 num_rows_needed,
366 num_rows,
367 num_rows_needed as f64 / num_rows as f64 * 100.0
368 );
369
370 timer_start_trace!(SHA256F_TRACE);
371 let mut trace_rows = sha256f_trace.buffer.as_mut_slice();
372 let mut par_traces = Vec::new();
373 let mut inputs_indexes = Vec::new();
374 for (i, inputs) in inputs.iter().enumerate() {
375 for (j, _) in inputs.iter().enumerate() {
376 let (head, tail) = trace_rows.split_at_mut(CLOCKS);
377 par_traces.push(head);
378 inputs_indexes.push((i, j));
379 trace_rows = tail;
380 }
381 }
382
383 // Fill the trace
384 let input_range_checks: Vec<([u32; 8], [u32; 8])> = par_traces
385 .into_par_iter()
386 .enumerate()
387 .map(|(index, trace)| {
388 let input_index = inputs_indexes[index];
389 let input = &inputs[input_index.0][input_index.1];
390 self.process_input(input, trace)
391 })
392 .collect();
393
394 for (a_inp_range_checks, e_inp_range_checks) in input_range_checks {
395 for i in 0..8 {
396 a_range_checks[i] += a_inp_range_checks[i];
397 e_range_checks[i] += e_inp_range_checks[i];
398 }
399 }
400
401 timer_stop_and_log_trace!(SHA256F_TRACE);
402
403 timer_start_trace!(SHA256F_PADDING);
404 // Set a = e = w = 0 for the state and input rows
405 let zero_row = Sha256fTraceRowType::<F>::default();
406
407 // precompute compute_ae() with initial a = e = 0 (PC_A and PC_E)
408 // compute_w() with w = 0 is equal to 0, nothing to do
409 let mut mid_rows = [Sha256fTraceRowType::<F>::default(); 64];
410 for i in 0..64 {
411 let a = PC_A[i];
412 let e = PC_E[i];
413 let (a_carry, a) = ((a >> 32) as u8, (a & 0xFFFF_FFFF) as u32);
414 let (e_carry, e) = ((e >> 32) as u8, (e & 0xFFFF_FFFF) as u32);
415 mid_rows[i].set_new_a_carry_bits(a_carry);
416 mid_rows[i].set_new_e_carry_bits(e_carry);
417 for j in 0..32 {
418 let bit_a = ((a >> j) & 1) != 0;
419 let bit_e = ((e >> j) & 1) != 0;
420 mid_rows[i].set_a(j, bit_a);
421 mid_rows[i].set_e(j, bit_e);
422 }
423
424 a_range_checks[a_carry as usize] += (num_available_sha256fs - num_inputs) as u32;
425 e_range_checks[e_carry as usize] += (num_available_sha256fs - num_inputs) as u32;
426 }
427
428 // At the end, we should have that a === 4'and e === 4'e
429 let mut final_rows = [Sha256fTraceRowType::<F>::default(); 4];
430 for i in 0..4 {
431 let a = (PC_A[60 + i] & 0xFFFF_FFFF) as u32;
432 let e = (PC_E[60 + i] & 0xFFFF_FFFF) as u32;
433 for j in 0..32 {
434 let bit_a = ((a >> j) & 1) != 0;
435 let bit_e = ((e >> j) & 1) != 0;
436 final_rows[i].set_a(j, bit_a);
437 final_rows[i].set_e(j, bit_e);
438 }
439 }
440
441 const CLOCKS_OP: usize = CLOCKS_LOAD_STATE + CLOCKS_LOAD_INPUT + CLOCKS_MIXING;
442 // The last (CLOCKS + NUM_NON_USABLE_ROWS) have CLK_0 desactivated, so
443 // a trace full of zeroes passes the constraints
444 sha256f_trace.buffer[num_rows_filled..(num_rows - self.num_non_usable_rows - CLOCKS)]
445 .par_iter_mut()
446 .enumerate()
447 .for_each(|(elem, row)| {
448 let row_r = elem % CLOCKS;
449 if row_r < CLOCKS_LOAD_STATE {
450 *row = zero_row;
451 } else if row_r < CLOCKS_OP {
452 *row = mid_rows[row_r - CLOCKS_LOAD_STATE];
453 } else {
454 *row = final_rows[row_r - CLOCKS_OP];
455 }
456 });
457
458 // Perform the zero range checks
459 let count_zeros = (num_available_sha256fs - num_inputs)
460 * (CLOCKS_LOAD_STATE + CLOCKS_WRITE_STATE)
461 + CLOCKS
462 + self.num_non_usable_rows;
463 a_range_checks[0] += count_zeros as u32;
464 e_range_checks[0] += count_zeros as u32;
465
466 self.std.range_checks(self.a_range_id, a_range_checks);
467 self.std.range_checks(self.e_range_id, e_range_checks);
468
469 timer_stop_and_log_trace!(SHA256F_PADDING);
470
471 Ok(AirInstance::new_from_trace(FromTrace::new(&mut sha256f_trace)))
472 }
473}