Skip to main content

midnight_circuits/hash/sha256/
sha256_chip.rs

1//! This file implements a chip providing support for in-circuit evaluation of
2//! the SHA256 hash function.
3//!
4//! Throughout the file, we use the notation from NIST FIPS PUB 180-4:
5//! <https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf> (Section 6.2).
6//!
7//! This implementation uses the amazing trick of a plain-spreaded table,
8//! devised by the Zcash team (to the best of our knowledge):
9//! See <https://zcash.github.io/halo2/design/gadgets/sha256/table16.html>.
10//!
11//! In a nutshell, the "spreaded" form of a u32 is the u64 resulting from
12//! inserting a zero between all its bits. For example, the spreaded version
13//! of 13 = 0b1101 is 0b01010001 = 81.
14//!                     ^ ^ ^ ^
15//! We denote the spreaded form of a value X: u32 by ~X: u64.
16//!
17//! The spreaded form can be used to enforce bit-wise operations very
18//! efficiently, essentially with a single native field addition (which can be
19//! seen as an integer addition since values are guaranteed to not wrap-around
20//! the native modulus).
21//!
22//! For example, the bit-wise XOR of two values X and Y is encoded in the
23//! even bits of ~X + ~Y (and the odd bits encode their bit-wise AND).
24//! Thus, for X, Y in [0, 2^32), Z = X ⊕ Y can be enforced as
25//! ~Z + 2 * ~W = ~X + ~Y and Z, W in [0, 2^32); where W is an auxiliary
26//! variable. The consistency between X, Y, Z, W and ~X, ~Y, ~Z, ~W (and, by the
27//! way, their range condition) be enforced with a lookup table.
28//!
29//! In this chip we use a lookup table with 3 columns of the form (n, X, ~X)
30//! which guarantees that ~X is the spreaded form of X and that X has n-bits,
31//! i.e. X in [0, 2^n).
32//!
33//! Our 32-bit values are represented in limbs of at most 12 bits. This allows
34//! us to have a small table with (only) values in the range n = 2..=12
35//! (n = 8 is an exception, intentionally not included, to give room for the ZK
36//! unused rows, this way the table fits in a K = 13 domain).
37//!
38//! We have 2 parallel lookups, which allow us to call such plain-spreaded table
39//! twice per row; on columns named (T0, A0, A1) and (T1, A2, A3).
40
41use midnight_proofs::{
42    circuit::{Chip, Layouter, Region, Value},
43    plonk::{
44        Advice, Column, ConstraintSystem, Constraints, Error, Expression, Fixed, Selector,
45        TableColumn,
46    },
47    poly::Rotation,
48};
49use num_integer::Integer;
50
51use crate::{
52    field::{decomposition::chip::P2RDecompositionChip, NativeChip, NativeGadget},
53    hash::sha256::{
54        types::{
55            AssignedMessageWord, AssignedPlain, AssignedPlainSpreaded, AssignedSpreaded,
56            CompressionState, LimbsOfA, LimbsOfE,
57        },
58        utils::{
59            expr_pow2_ip, expr_pow4_ip, gen_spread_table, get_even_and_odd_bits, negate_spreaded,
60            spread, spreaded_Sigma_0, spreaded_Sigma_1, spreaded_maj, spreaded_sigma_0,
61            spreaded_sigma_1, u32_in_be_limbs, MASK_EVN_64,
62        },
63    },
64    instructions::{assignments::AssignmentInstructions, DecompositionInstructions},
65    types::{AssignedByte, AssignedNative},
66    utils::{
67        util::{fe_to_u32, fe_to_u64, u32_to_fe, u64_to_fe},
68        ComposableChip,
69    },
70    CircuitField,
71};
72
73/// Number of advice columns used by the identities of the SHA256 chip.
74pub const NB_SHA256_ADVICE_COLS: usize = 8;
75
76/// Number of fixed columns used by the identities of the SHA256 chip.
77pub const NB_SHA256_FIXED_COLS: usize = 2;
78
79pub(super) const ROUND_CONSTANTS: [u32; 64] = [
80    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
81    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
82    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
83    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
84    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
85    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
86    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
87    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
88];
89
90pub(super) const IV: [u32; 8] = [
91    0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
92];
93
94/// Tag for the even and odd 11-11-10 decompositions.
95enum Parity {
96    Evn,
97    Odd,
98}
99
100/// Plain-Spreaded lookup table.
101#[derive(Clone, Debug)]
102struct SpreadTable {
103    nbits_col: TableColumn,
104    plain_col: TableColumn,
105    sprdd_col: TableColumn,
106}
107
108/// Configuration of Sha256Chip.
109#[derive(Clone, Debug)]
110pub struct Sha256Config {
111    advice_cols: [Column<Advice>; NB_SHA256_ADVICE_COLS],
112    fixed_cols: [Column<Fixed>; NB_SHA256_FIXED_COLS],
113
114    q_lookup: Selector,
115    table: SpreadTable,
116
117    q_maj: Selector,
118    q_half_ch: Selector,
119    q_Sigma_0: Selector,
120    q_Sigma_1: Selector,
121    q_sigma_0: Selector,
122    q_sigma_1: Selector,
123
124    q_11_11_10: Selector,
125    q_10_9_11_2: Selector,
126    q_7_12_2_5_6: Selector,
127    q_12_1x3_7_3_4_3: Selector,
128    q_add_mod_2_32: Selector,
129}
130
131/// Chip for SHA256.
132#[derive(Clone, Debug)]
133pub struct Sha256Chip<F: CircuitField> {
134    config: Sha256Config,
135    pub(super) native_gadget: NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>,
136}
137
138impl<F: CircuitField> Chip<F> for Sha256Chip<F> {
139    type Config = Sha256Config;
140    type Loaded = ();
141
142    fn config(&self) -> &Self::Config {
143        &self.config
144    }
145
146    fn loaded(&self) -> &Self::Loaded {
147        &()
148    }
149}
150
151impl<F: CircuitField> ComposableChip<F> for Sha256Chip<F> {
152    type SharedResources = (
153        [Column<Advice>; NB_SHA256_ADVICE_COLS],
154        [Column<Fixed>; NB_SHA256_FIXED_COLS],
155    );
156
157    type InstructionDeps = NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>;
158
159    fn new(config: &Sha256Config, native_gadget: &Self::InstructionDeps) -> Self {
160        Self {
161            config: config.clone(),
162            native_gadget: native_gadget.clone(),
163        }
164    }
165
166    fn configure(
167        meta: &mut ConstraintSystem<F>,
168        shared_res: &Self::SharedResources,
169    ) -> Sha256Config {
170        let fixed_cols = shared_res.1;
171
172        // Columns A0 and A2 do not need to be copy-enabled.
173        // We have the convention that chips enable copy in a prefix of their shared
174        // advice columns. Thus we let A0 and A2 be the last two columns of the given
175        // shared resources.
176        let advice_cols = [6, 0, 7, 1, 2, 3, 4, 5].map(|i| shared_res.0[i]);
177        for (i, column) in advice_cols.iter().enumerate() {
178            if i != 0 && i != 2 {
179                meta.enable_equality(*column);
180            }
181        }
182
183        let q_lookup = meta.complex_selector();
184        let table = SpreadTable {
185            nbits_col: meta.lookup_table_column(),
186            plain_col: meta.lookup_table_column(),
187            sprdd_col: meta.lookup_table_column(),
188        };
189
190        let q_maj = meta.selector();
191        let q_half_ch = meta.selector();
192        let q_Sigma_0 = meta.selector();
193        let q_Sigma_1 = meta.selector();
194        let q_sigma_0 = meta.selector();
195        let q_sigma_1 = meta.selector();
196
197        let q_11_11_10 = meta.selector();
198        let q_10_9_11_2 = meta.selector();
199        let q_7_12_2_5_6 = meta.selector();
200        let q_12_1x3_7_3_4_3 = meta.selector();
201        let q_add_mod_2_32 = meta.selector();
202
203        (0..2).for_each(|idx| {
204            meta.lookup("plain-spreaded lookup", |meta| {
205                let q_lookup = meta.query_selector(q_lookup);
206
207                let nbits = meta.query_fixed(fixed_cols[idx], Rotation(0));
208                let plain = meta.query_advice(advice_cols[2 * idx], Rotation(0));
209                let sprdd = meta.query_advice(advice_cols[2 * idx + 1], Rotation(0));
210
211                vec![
212                    (q_lookup.clone() * nbits, table.nbits_col),
213                    (q_lookup.clone() * plain, table.plain_col),
214                    (q_lookup * sprdd, table.sprdd_col),
215                ]
216            });
217        });
218
219        meta.create_gate("Maj(A, B, C)", |meta| {
220            // See function `maj` for a description of the following layout.
221            let sA = meta.query_advice(advice_cols[5], Rotation(-1));
222            let sB = meta.query_advice(advice_cols[6], Rotation(-1));
223            let sC = meta.query_advice(advice_cols[5], Rotation(0));
224            let s_odd_11a = meta.query_advice(advice_cols[1], Rotation(-1));
225            let s_odd_11b = meta.query_advice(advice_cols[1], Rotation(0));
226            let s_odd_010 = meta.query_advice(advice_cols[1], Rotation(1));
227            let s_evn_11a = meta.query_advice(advice_cols[3], Rotation(-1));
228            let s_evn_11b = meta.query_advice(advice_cols[3], Rotation(0));
229            let s_evn_010 = meta.query_advice(advice_cols[3], Rotation(1));
230
231            let s_evn = expr_pow4_ip([21, 10, 0], [&s_evn_11a, &s_evn_11b, &s_evn_010]);
232            let s_odd = expr_pow4_ip([21, 10, 0], [&s_odd_11a, &s_odd_11b, &s_odd_010]);
233
234            let id = (sA + sB + sC) - (s_evn + Expression::from(2) * s_odd);
235
236            Constraints::with_selector(q_maj, vec![("Maj", id)])
237        });
238
239        meta.create_gate("half Ch(E, F, G)", |meta| {
240            // See function `ch` for a description of the following layout.
241            let sX = meta.query_advice(advice_cols[5], Rotation(-1));
242            let sY = meta.query_advice(advice_cols[6], Rotation(-1));
243            let s_odd_11a = meta.query_advice(advice_cols[1], Rotation(-1));
244            let s_odd_11b = meta.query_advice(advice_cols[1], Rotation(0));
245            let s_odd_010 = meta.query_advice(advice_cols[1], Rotation(1));
246            let s_evn_11a = meta.query_advice(advice_cols[3], Rotation(-1));
247            let s_evn_11b = meta.query_advice(advice_cols[3], Rotation(0));
248            let s_evn_010 = meta.query_advice(advice_cols[3], Rotation(1));
249            let summand_1 = meta.query_advice(advice_cols[4], Rotation(0));
250            let summand_2 = meta.query_advice(advice_cols[5], Rotation(0));
251            let sum = meta.query_advice(advice_cols[6], Rotation(0));
252
253            let s_evn = expr_pow4_ip([21, 10, 0], [&s_evn_11a, &s_evn_11b, &s_evn_010]);
254            let s_odd = expr_pow4_ip([21, 10, 0], [&s_odd_11a, &s_odd_11b, &s_odd_010]);
255
256            let sprdd_id = (sX + sY) - (s_evn + Expression::from(2) * s_odd);
257            let sum_id = (summand_1 + summand_2) - sum;
258
259            Constraints::with_selector(
260                q_half_ch,
261                vec![
262                    ("Half-Ch spreadded", sprdd_id),
263                    ("Half Ch sum (2 terms)", sum_id),
264                ],
265            )
266        });
267
268        meta.create_gate("Σ₀(A)", |meta| {
269            // See function `Sigma_0` for a description of the following layout.
270            let s10 = meta.query_advice(advice_cols[5], Rotation(-1));
271            let s09 = meta.query_advice(advice_cols[6], Rotation(-1));
272            let s11 = meta.query_advice(advice_cols[5], Rotation(0));
273            let s02 = meta.query_advice(advice_cols[6], Rotation(0));
274            let s_evn_11a = meta.query_advice(advice_cols[1], Rotation(-1));
275            let s_evn_11b = meta.query_advice(advice_cols[1], Rotation(0));
276            let s_evn_010 = meta.query_advice(advice_cols[1], Rotation(1));
277            let s_odd_11a = meta.query_advice(advice_cols[3], Rotation(-1));
278            let s_odd_11b = meta.query_advice(advice_cols[3], Rotation(0));
279            let s_odd_010 = meta.query_advice(advice_cols[3], Rotation(1));
280
281            let s_1st_rot = expr_pow4_ip([30, 20, 11, 0], [&s02, &s10, &s09, &s11]);
282            let s_2nd_rot = expr_pow4_ip([21, 19, 9, 0], [&s11, &s02, &s10, &s09]);
283            let s_3rd_rot = expr_pow4_ip([23, 12, 10, 0], [&s09, &s11, &s02, &s10]);
284
285            let s_evn = expr_pow4_ip([21, 10, 0], [&s_evn_11a, &s_evn_11b, &s_evn_010]);
286            let s_odd = expr_pow4_ip([21, 10, 0], [&s_odd_11a, &s_odd_11b, &s_odd_010]);
287
288            let id = (s_1st_rot + s_2nd_rot + s_3rd_rot) - (s_evn + Expression::from(2) * s_odd);
289
290            Constraints::with_selector(q_Sigma_0, vec![("Sigma_0", id)])
291        });
292
293        meta.create_gate("Σ₁(E)", |meta| {
294            // See function `Sigma_1` for a description of the following layout.
295            let s07 = meta.query_advice(advice_cols[5], Rotation(-1));
296            let s12 = meta.query_advice(advice_cols[6], Rotation(-1));
297            let s02 = meta.query_advice(advice_cols[5], Rotation(0));
298            let s05 = meta.query_advice(advice_cols[6], Rotation(0));
299            let s06 = meta.query_advice(advice_cols[5], Rotation(1));
300            let s_evn_11a = meta.query_advice(advice_cols[1], Rotation(-1));
301            let s_evn_11b = meta.query_advice(advice_cols[1], Rotation(0));
302            let s_evn_10 = meta.query_advice(advice_cols[1], Rotation(1));
303            let s_odd_11a = meta.query_advice(advice_cols[3], Rotation(-1));
304            let s_odd_11b = meta.query_advice(advice_cols[3], Rotation(0));
305            let s_odd_10 = meta.query_advice(advice_cols[3], Rotation(1));
306
307            let s_1st_rot = expr_pow4_ip([26, 19, 7, 5, 0], [&s06, &s07, &s12, &s02, &s05]);
308            let s_2nd_rot = expr_pow4_ip([27, 21, 14, 2, 0], [&s05, &s06, &s07, &s12, &s02]);
309            let s_3rd_rot = expr_pow4_ip([20, 18, 13, 7, 0], [&s12, &s02, &s05, &s06, &s07]);
310
311            let s_evn = expr_pow4_ip([21, 10, 0], [&s_evn_11a, &s_evn_11b, &s_evn_10]);
312            let s_odd = expr_pow4_ip([21, 10, 0], [&s_odd_11a, &s_odd_11b, &s_odd_10]);
313
314            let id = (s_1st_rot + s_2nd_rot + s_3rd_rot) - (s_evn + Expression::from(2) * s_odd);
315
316            Constraints::with_selector(q_Sigma_1, vec![("Sigma_1", id)])
317        });
318
319        meta.create_gate("σ₀(W)", |meta| {
320            // See function `sigma_0` for a description of the following layout.
321            let s12 = meta.query_advice(advice_cols[5], Rotation(-1));
322            let s1a = meta.query_advice(advice_cols[6], Rotation(-1));
323            let s1b = meta.query_advice(advice_cols[4], Rotation(0));
324            let s1c = meta.query_advice(advice_cols[5], Rotation(0));
325            let s07 = meta.query_advice(advice_cols[6], Rotation(0));
326            let s3a = meta.query_advice(advice_cols[4], Rotation(1));
327            let s04 = meta.query_advice(advice_cols[5], Rotation(1));
328            let s3b = meta.query_advice(advice_cols[6], Rotation(1));
329            let s_evn_11a = meta.query_advice(advice_cols[1], Rotation(-1));
330            let s_evn_11b = meta.query_advice(advice_cols[1], Rotation(0));
331            let s_evn_10 = meta.query_advice(advice_cols[1], Rotation(1));
332            let s_odd_11a = meta.query_advice(advice_cols[3], Rotation(-1));
333            let s_odd_11b = meta.query_advice(advice_cols[3], Rotation(0));
334            let s_odd_10 = meta.query_advice(advice_cols[3], Rotation(1));
335
336            let sprdd_1st_shift = expr_pow4_ip(
337                [17, 16, 15, 14, 7, 4, 0],
338                [&s12, &s1a, &s1b, &s1c, &s07, &s3a, &s04],
339            );
340            let sprdd_2nd_rot = expr_pow4_ip(
341                [28, 25, 13, 12, 11, 10, 3, 0],
342                [&s04, &s3b, &s12, &s1a, &s1b, &s1c, &s07, &s3a],
343            );
344            let sprdd_3rd_rot = expr_pow4_ip(
345                [31, 24, 21, 17, 14, 2, 1, 0],
346                [&s1c, &s07, &s3a, &s04, &s3b, &s12, &s1a, &s1b],
347            );
348
349            let sprdd_evn = expr_pow4_ip([21, 10, 0], [&s_evn_11a, &s_evn_11b, &s_evn_10]);
350            let sprdd_odd = expr_pow4_ip([21, 10, 0], [&s_odd_11a, &s_odd_11b, &s_odd_10]);
351
352            let id = (sprdd_1st_shift + sprdd_2nd_rot + sprdd_3rd_rot)
353                - (sprdd_evn + Expression::from(2) * sprdd_odd);
354
355            Constraints::with_selector(q_sigma_0, vec![("sigma_0", id)])
356        });
357
358        meta.create_gate("σ₁(W)", |meta| {
359            // See function `sigma_1` for a description of the following layout.
360            let s12 = meta.query_advice(advice_cols[5], Rotation(-1));
361            let s1a = meta.query_advice(advice_cols[6], Rotation(-1));
362            let s1b = meta.query_advice(advice_cols[4], Rotation(0));
363            let s1c = meta.query_advice(advice_cols[5], Rotation(0));
364            let s07 = meta.query_advice(advice_cols[6], Rotation(0));
365            let s3a = meta.query_advice(advice_cols[4], Rotation(1));
366            let s04 = meta.query_advice(advice_cols[5], Rotation(1));
367            let s3b = meta.query_advice(advice_cols[6], Rotation(1));
368            let s_evn_11a = meta.query_advice(advice_cols[1], Rotation(-1));
369            let s_evn_11b = meta.query_advice(advice_cols[1], Rotation(0));
370            let s_evn_10 = meta.query_advice(advice_cols[1], Rotation(1));
371            let s_odd_11a = meta.query_advice(advice_cols[3], Rotation(-1));
372            let s_odd_11b = meta.query_advice(advice_cols[3], Rotation(0));
373            let s_odd_10 = meta.query_advice(advice_cols[3], Rotation(1));
374
375            let sprdd_1st_shift = expr_pow4_ip([10, 9, 8, 7, 0], [&s12, &s1a, &s1b, &s1c, &s07]);
376            let sprdd_2nd_rot = expr_pow4_ip(
377                [25, 22, 18, 15, 3, 2, 1, 0],
378                [&s07, &s3a, &s04, &s3b, &s12, &s1a, &s1b, &s1c],
379            );
380            let sprdd_3rd_rot = expr_pow4_ip(
381                [31, 30, 23, 20, 16, 13, 1, 0],
382                [&s1b, &s1c, &s07, &s3a, &s04, &s3b, &s12, &s1a],
383            );
384
385            let sprdd_evn = expr_pow4_ip([21, 10, 0], [&s_evn_11a, &s_evn_11b, &s_evn_10]);
386            let sprdd_odd = expr_pow4_ip([21, 10, 0], [&s_odd_11a, &s_odd_11b, &s_odd_10]);
387
388            let id = (sprdd_1st_shift + sprdd_2nd_rot + sprdd_3rd_rot)
389                - (sprdd_evn + Expression::from(2) * sprdd_odd);
390
391            Constraints::with_selector(q_sigma_1, vec![("sigma_1", id)])
392        });
393
394        meta.create_gate("11-11-10 decomposition", |meta| {
395            // See function `assign_sprdd_11_11_10` for a description of the following
396            // layout.
397            let p11a = meta.query_advice(advice_cols[0], Rotation(-1));
398            let p11b = meta.query_advice(advice_cols[0], Rotation(0));
399            let p_10 = meta.query_advice(advice_cols[0], Rotation(1));
400            let output = meta.query_advice(advice_cols[4], Rotation(-1));
401
402            let id = expr_pow2_ip([21, 10, 0], [&p11a, &p11b, &p_10]) - output;
403
404            Constraints::with_selector(q_11_11_10, vec![("11-11-10 decomposition", id)])
405        });
406
407        meta.create_gate("10-9-11-2 decomposition", |meta| {
408            // See function `prepare_A` for a description of the following layout.
409            let p10 = meta.query_advice(advice_cols[0], Rotation(-1));
410            let p09 = meta.query_advice(advice_cols[2], Rotation(-1));
411            let p11 = meta.query_advice(advice_cols[0], Rotation(0));
412            let p02 = meta.query_advice(advice_cols[2], Rotation(0));
413            let s10 = meta.query_advice(advice_cols[1], Rotation(-1));
414            let s09 = meta.query_advice(advice_cols[3], Rotation(-1));
415            let s11 = meta.query_advice(advice_cols[1], Rotation(0));
416            let s02 = meta.query_advice(advice_cols[3], Rotation(0));
417            let plain = meta.query_advice(advice_cols[4], Rotation(-1));
418            let sprdd = meta.query_advice(advice_cols[4], Rotation(0));
419
420            let plain_id = expr_pow2_ip([22, 13, 2, 0], [&p10, &p09, &p11, &p02]) - plain;
421            let sprdd_id = expr_pow4_ip([22, 13, 2, 0], [&s10, &s09, &s11, &s02]) - sprdd;
422
423            Constraints::with_selector(
424                q_10_9_11_2,
425                vec![
426                    ("10_9_11_2 decomposition plain", plain_id),
427                    ("10_9_11_2 decomposition sprdd", sprdd_id),
428                ],
429            )
430        });
431
432        meta.create_gate("7-12-2-5-6 decomposition", |meta| {
433            // See function `prepare_E` for a description of the following layout.
434            let p07 = meta.query_advice(advice_cols[0], Rotation(-1));
435            let p12 = meta.query_advice(advice_cols[2], Rotation(-1));
436            let p02 = meta.query_advice(advice_cols[0], Rotation(0));
437            let p05 = meta.query_advice(advice_cols[2], Rotation(0));
438            let p06 = meta.query_advice(advice_cols[0], Rotation(1));
439            let s07 = meta.query_advice(advice_cols[1], Rotation(-1));
440            let s12 = meta.query_advice(advice_cols[3], Rotation(-1));
441            let s02 = meta.query_advice(advice_cols[1], Rotation(0));
442            let s05 = meta.query_advice(advice_cols[3], Rotation(0));
443            let s06 = meta.query_advice(advice_cols[1], Rotation(1));
444            let plain = meta.query_advice(advice_cols[4], Rotation(-1));
445            let sprdd = meta.query_advice(advice_cols[4], Rotation(0));
446
447            let plain_id = expr_pow2_ip([25, 13, 11, 6, 0], [&p07, &p12, &p02, &p05, &p06]) - plain;
448            let sprdd_id = expr_pow4_ip([25, 13, 11, 6, 0], [&s07, &s12, &s02, &s05, &s06]) - sprdd;
449
450            Constraints::with_selector(
451                q_7_12_2_5_6,
452                vec![
453                    ("7_12_2_5_6 decomposition plain", plain_id),
454                    ("7_12_2_5_6 decomposition sprdd", sprdd_id),
455                ],
456            )
457        });
458
459        meta.create_gate("12-1x3-7-3-4-3 decomposition", |meta| {
460            // See function `prepare_message_word` for a description of the following
461            // layout.
462            let w12 = meta.query_advice(advice_cols[0], Rotation(-1));
463            let w07 = meta.query_advice(advice_cols[2], Rotation(-1));
464            let w3a = meta.query_advice(advice_cols[0], Rotation(0));
465            let w04 = meta.query_advice(advice_cols[2], Rotation(0));
466            let w3b = meta.query_advice(advice_cols[0], Rotation(1));
467            let w1a = meta.query_advice(advice_cols[7], Rotation(-1));
468            let w1b = meta.query_advice(advice_cols[7], Rotation(0));
469            let w1c = meta.query_advice(advice_cols[7], Rotation(1));
470            let plain = meta.query_advice(advice_cols[4], Rotation(-1));
471
472            let plain_id = expr_pow2_ip(
473                [20, 19, 18, 17, 10, 7, 3, 0],
474                [&w12, &w1a, &w1b, &w1c, &w07, &w3a, &w04, &w3b],
475            ) - plain;
476
477            // 1-bit check for W.1a, W.1b and W.1c
478            let w_1a_check = w1a.clone() * (w1a - Expression::from(1));
479            let w_1b_check = w1b.clone() * (w1b - Expression::from(1));
480            let w_1c_check = w1c.clone() * (w1c - Expression::from(1));
481
482            Constraints::with_selector(
483                q_12_1x3_7_3_4_3,
484                vec![
485                    ("12_1x3_7_3_4_3 decomposition ", plain_id),
486                    ("W.1a 1-bit check", w_1a_check),
487                    ("W.1b 1-bit check", w_1b_check),
488                    ("W.1c 1-bit check", w_1c_check),
489                ],
490            )
491        });
492
493        meta.create_gate("add mod 2^32", |meta| {
494            // See function `assign_add_mod_2_32` for a description of the following layout.
495            let s0 = meta.query_advice(advice_cols[5], Rotation(-1));
496            let s1 = meta.query_advice(advice_cols[6], Rotation(-1));
497            let s2 = meta.query_advice(advice_cols[5], Rotation(0));
498            let s3 = meta.query_advice(advice_cols[6], Rotation(0));
499            let s4 = meta.query_advice(advice_cols[4], Rotation(1));
500            let s5 = meta.query_advice(advice_cols[5], Rotation(1));
501            let s6 = meta.query_advice(advice_cols[6], Rotation(1));
502
503            let carry = meta.query_advice(advice_cols[2], Rotation(1));
504            let result = meta.query_advice(advice_cols[4], Rotation(-1));
505
506            let summands = [s0, s1, s2, s3, s4, s5, s6];
507            let lhs = summands.into_iter().reduce(|acc, x| acc + x).unwrap();
508            let rhs = result + carry * Expression::Constant(F::from(1u64 << 32));
509
510            Constraints::with_selector(q_add_mod_2_32, vec![("add_mod_2_32", lhs - rhs)])
511        });
512
513        Sha256Config {
514            advice_cols,
515            fixed_cols,
516
517            q_lookup,
518            table,
519
520            q_maj,
521            q_half_ch,
522            q_Sigma_0,
523            q_Sigma_1,
524            q_sigma_0,
525            q_sigma_1,
526
527            q_11_11_10,
528            q_10_9_11_2,
529            q_7_12_2_5_6,
530            q_12_1x3_7_3_4_3,
531            q_add_mod_2_32,
532        }
533    }
534
535    fn load(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
536        let SpreadTable {
537            nbits_col,
538            plain_col,
539            sprdd_col,
540        } = self.config().table;
541
542        layouter.assign_table(
543            || "spread table",
544            |mut table| {
545                for (index, triple) in gen_spread_table::<F>().enumerate() {
546                    table.assign_cell(|| "nbits", nbits_col, index, || Value::known(triple.0))?;
547                    table.assign_cell(|| "plain", plain_col, index, || Value::known(triple.1))?;
548                    table.assign_cell(|| "sprdd", sprdd_col, index, || Value::known(triple.2))?;
549                }
550                Ok(())
551            },
552        )
553    }
554}
555
556impl<F: CircuitField> Sha256Chip<F> {
557    /// In-circuit SHA256 computation, the protagonist of this chip.
558    pub(super) fn sha256(
559        &self,
560        layouter: &mut impl Layouter<F>,
561        input_bytes: &[AssignedByte<F>],
562    ) -> Result<[AssignedPlain<F, 32>; 8], Error> {
563        let mut state = CompressionState::<F>::fixed(layouter, &self.native_gadget, IV)?;
564
565        for block_bytes in self.pad(layouter, input_bytes)?.chunks(64) {
566            let block = self.block_from_bytes(layouter, block_bytes.try_into().unwrap())?;
567            let message_blocks = self.message_schedule(layouter, &block)?;
568            let mut compression_state = state.clone();
569            for i in 0..64 {
570                compression_state = self.compression_round(
571                    layouter,
572                    &compression_state,
573                    ROUND_CONSTANTS[i],
574                    &message_blocks[i],
575                )?;
576            }
577            state = state.add(self, layouter, &compression_state)?;
578        }
579
580        Ok(state.plain())
581    }
582
583    /// Pads the input byte array to be a multiple of 64 bytes (512 bits).
584    fn pad(
585        &self,
586        layouter: &mut impl Layouter<F>,
587        bytes: &[AssignedByte<F>],
588    ) -> Result<Vec<AssignedByte<F>>, Error> {
589        let l = 8 * bytes.len();
590        let k = 512 - (l + 65) % 512;
591
592        let mut padded = bytes.to_vec();
593        padded.push(self.native_gadget.assign_fixed(layouter, 128u8)?); // k is always 7 mod 8
594        padded.extend(vec![self.native_gadget.assign_fixed(layouter, 0u8)?; k / 8]);
595        for byte in u64::to_be_bytes(l as u64) {
596            padded.push(self.native_gadget.assign_fixed(layouter, byte)?);
597        }
598
599        Ok(padded)
600    }
601
602    /// Given a byte array of exactly 64 bytes, this function converts it into a
603    /// block of 16 `AssignedPlain` values, each (32 bits) value representing 4
604    /// bytes in big-endian.
605    pub(super) fn block_from_bytes(
606        &self,
607        layouter: &mut impl Layouter<F>,
608        bytes: &[AssignedByte<F>; 64],
609    ) -> Result<[AssignedPlain<F, 32>; 16], Error> {
610        Ok(bytes
611            .chunks(4)
612            .map(|word_bytes| {
613                self.native_gadget
614                    .assigned_from_be_bytes(layouter, word_bytes)
615                    .map(AssignedPlain)
616            })
617            .collect::<Result<Vec<_>, Error>>()?
618            .try_into()
619            .unwrap())
620    }
621
622    /// Takes a 512-bits block, represented with 16 `AssignedPlain<32>` words.
623    /// Outputs the 64 `AssignedPlain<32>` words Wi from SHA256's message
624    /// schedule.
625    pub(super) fn message_schedule(
626        &self,
627        layouter: &mut impl Layouter<F>,
628        block: &[AssignedPlain<F, 32>; 16],
629    ) -> Result<[AssignedPlain<F, 32>; 64], Error> {
630        let message_word = self.prepare_message_word(layouter, &[block[0].clone()])?;
631        let mut message_words: [AssignedMessageWord<F>; 64] =
632            core::array::from_fn(|_| message_word.clone());
633
634        // The first 16 message words are got by decomposing the block words
635        // into 12-1x3-7-3-4-3 limbs directly.
636        for word_idx in 1..16 {
637            message_words[word_idx] =
638                self.prepare_message_word(layouter, &[block[word_idx].clone()])?;
639        }
640        // The remaining 48 message words are computed using the recurrence relation
641        // W.i = W.(i-16) + W.(i-7) + σ₀(W.(i-15)) + σ₁(W.(i-2))
642        // and decomposing into 12-1x3-7-3-4-3 limbs.
643        for word_idx in 16..64 {
644            let sigma0_w_i_minus_15 = &self.sigma_0(layouter, &message_words[word_idx - 15])?;
645            let sigma1_w_i_minus_2 = &self.sigma_1(layouter, &message_words[word_idx - 2])?;
646            message_words[word_idx] = self.prepare_message_word(
647                layouter,
648                &[
649                    message_words[word_idx - 16].combined_plain.clone(),
650                    message_words[word_idx - 7].combined_plain.clone(),
651                    sigma0_w_i_minus_15.clone(),
652                    sigma1_w_i_minus_2.clone(),
653                ],
654            )?;
655        }
656
657        Ok(message_words.map(|w| w.combined_plain))
658    }
659
660    /// A compression round. This is called 64 times per block.
661    pub(super) fn compression_round(
662        &self,
663        layouter: &mut impl Layouter<F>,
664        state: &CompressionState<F>,
665        round_k: u32,
666        round_w: &AssignedPlain<F, 32>,
667    ) -> Result<CompressionState<F>, Error> {
668        let round_k = AssignedPlain::<F, 32>::fixed(layouter, &self.native_gadget, round_k)?;
669
670        let Sigma_0_of_a = self.Sigma_0(layouter, &state.a)?;
671        let Maj_of_a_b_c = self.maj(
672            layouter,
673            &state.a.combined.spreaded,
674            &state.b.spreaded,
675            &state.c.spreaded,
676        )?;
677        let Sigma_1_of_e = self.Sigma_1(layouter, &state.e)?;
678        let Ch_of_e_f_g = self.ch(
679            layouter,
680            &state.e.combined.spreaded,
681            &state.f.spreaded,
682            &state.g.spreaded,
683        )?;
684
685        let next_a_summands = [
686            state.h.clone(),
687            Sigma_1_of_e.clone(),
688            Ch_of_e_f_g.clone(),
689            round_k.clone(),
690            round_w.clone(),
691            Sigma_0_of_a,
692            Maj_of_a_b_c,
693        ];
694
695        let next_e_summands = [
696            state.d.clone(),
697            state.h.clone(),
698            Sigma_1_of_e,
699            Ch_of_e_f_g,
700            round_k.clone(),
701            round_w.clone(),
702        ];
703
704        Ok(CompressionState {
705            a: self.prepare_A(layouter, &next_a_summands)?,
706            b: state.a.combined.clone(),
707            c: state.b.clone(),
708            d: state.c.plain.clone(),
709            e: self.prepare_E(layouter, &next_e_summands)?,
710            f: state.e.combined.clone(),
711            g: state.f.clone(),
712            h: state.g.plain.clone(),
713        })
714    }
715
716    /// Computes Maj(A, B, C).
717    fn maj(
718        &self,
719        layouter: &mut impl Layouter<F>,
720        sprdd_a: &AssignedSpreaded<F, 32>,
721        sprdd_b: &AssignedSpreaded<F, 32>,
722        sprdd_c: &AssignedSpreaded<F, 32>,
723    ) -> Result<AssignedPlain<F, 32>, Error> {
724        /*
725        We need to compute:
726            Maj(A, B, C) = (A ∧ B) ⊕ (A ∧ C) ⊕ (B ∧ C)
727
728        Note that the "majority" function (bit-wise most commont value) between A, B, C
729        is encoded in the odd bits of (~A + ~B + ~C). This is because, for every bit
730        position i, iff at least two out of three are 1, the sum A_i + B_i + C_i will
731        overflow, leaving a carry bit of 1 (the result of majority for that bit).
732
733        Maj can be encoded by
734
735        1) applying the plain-spreaded lookup on 11-11-10 limbs of Evn and Odd:
736             Evn: (Evn.11a, Evn.11b, Evn.10)
737             Odd: (Odd.11a, Odd.11b, Odd.10)
738
739        2) asserting the 11-11-10 decomposition identity for Odd:
740              2^21 * Odd.11a + 2^10 * Odd.11b + Odd.10
741            = Odd
742
743        3) asserting the major identity regarding the spreaded values:
744              (4^21 * ~Evn.11a + 4^10 * ~Evn.11b + ~Evn.10) +
745          2 * (4^21 * ~Odd.11a + 4^10 * ~Odd.11b + ~Odd.10)
746             = ~A + ~B + ~C
747
748        The output is Odd.
749
750        We distribute these values in the PLONK table as follows.
751
752        | T0 |    A0   |     A1   | T1 |    A2   |    A3    |  A4 | A5 | A6 |
753        |----|---------|----------|----|---------|----------|-----|----|----|
754        | 11 | Odd.11a | ~Odd.11a | 11 | Evn.11a | ~Evn.11a | Odd | ~A | ~B |
755        | 11 | Odd.11b | ~Odd.11b | 11 | Evn.11b | ~Evn.11b |     | ~C |    | <- q_maj
756        | 10 | Odd.10  | ~Odd.10  | 10 | Evn.10  | ~Evn.10  |     |    |    |
757        */
758
759        let adv_cols = self.config().advice_cols;
760
761        layouter.assign_region(
762            || "Maj(A, B, C)",
763            |mut region| {
764                self.config().q_maj.enable(&mut region, 1)?;
765
766                sprdd_a.0.copy_advice(|| "~A", &mut region, adv_cols[5], 0)?;
767                sprdd_b.0.copy_advice(|| "~B", &mut region, adv_cols[6], 0)?;
768                sprdd_c.0.copy_advice(|| "~C", &mut region, adv_cols[5], 1)?;
769
770                let val_of_sprdd_forms: Value<[u64; 3]> = Value::from_iter([
771                    sprdd_a.0.value().copied().map(fe_to_u64),
772                    sprdd_b.0.value().copied().map(fe_to_u64),
773                    sprdd_c.0.value().copied().map(fe_to_u64),
774                ])
775                .map(|sprdd_forms: Vec<u64>| sprdd_forms.try_into().unwrap());
776
777                self.assign_sprdd_11_11_10(
778                    &mut region,
779                    val_of_sprdd_forms.map(spreaded_maj),
780                    Parity::Odd,
781                    0,
782                )
783            },
784        )
785    }
786
787    /// Computes Ch(E, F, G)
788    fn ch(
789        &self,
790        layouter: &mut impl Layouter<F>,
791        sprdd_E: &AssignedSpreaded<F, 32>,
792        sprdd_F: &AssignedSpreaded<F, 32>,
793        sprdd_G: &AssignedSpreaded<F, 32>,
794    ) -> Result<AssignedPlain<F, 32>, Error> {
795        /*
796        We need to compute:
797            Ch(E, F, G) = (E ∧ F) ⊕ (¬E ∧ G)
798
799        which can be achieved by
800
801        1) applying the plain-spreaded lookup on 11-11-10 limbs of Evn and Odd,
802           for both (~E + ~F) and (~(¬E) + ~G):
803             Evn_EF: (Evn_EF.11a, Evn_EF.11b, Evn_EF.10)
804             Odd_EF: (Odd_EF.11a, Odd_EF.11b, Odd_EF.10)
805
806             Evn_nEG: (Evn_nEG.11a, Evn_nEG.11b, Evn_nEG.10)
807             Odd_nEG: (Odd_nEG.11a, Odd_nEG.11b, Odd_nEG.10)
808
809        2) asserting the 11-11-10 decomposition identity for Odd_EF and Odd_nEG:
810              2^21 * Odd_EF.11a + 2^10 * Odd_EF.11b + Odd_EF.10
811            = Odd_EF
812
813              2^21 * Odd_nEG.11a + 2^10 * Odd_nEG.11b + Odd_nEG.10
814            = Odd_nEG
815
816        3) asserting the spreaded addition identity for (~E + ~F) and (~(¬E) + ~G):
817              (4^21 * ~Evn_EF.11a + 4^10 * ~Evn_EF.11b + ~Evn_EF.10) +
818          2 * (4^21 * ~Odd_EF.11a + 4^10 * ~Odd_EF.11b + ~Odd_EF.10)
819             = ~E + ~F
820
821              (4^21 * ~Evn_nEG.11a + 4^10 * ~Evn_nEG.11b + ~Evn_nEG.10) +
822          2 * (4^21 * ~Odd_nEG.11a + 4^10 * ~Odd_nEG.11b + ~Odd_nEG.10)
823             = ~(¬E) + ~G
824
825        4) asserting the following two addition identities:
826                    Ret = Odd_EF + Odd_nEG
827            MASK_EVN_64 = ~E + ~(¬E)
828
829        The output is Ret.
830
831        We distribute these values in the PLONK table as follows.
832
833        | T0 |      A0     |      A1      | T1 |      A2     |      A3      |    A4   |    A5   |      A6     |
834        |----|-------------|--------------|----|-------------|--------------|---------|---------|-------------|
835        | 11 |  Odd_EF.11a |  ~Odd_EF.11a | 11 |  Evn_EF.11a |  ~Evn_EF.11a | Odd_EF  |   ~E    |      ~F     |
836        | 11 |  Odd_EF.11b |  ~Odd_EF.11b | 11 |  Evn_EF.11b |  ~Evn_EF.11b | Odd_EF  | Odd_nEG |     Ret     | <- q_ch
837        | 10 |  Odd_EF.10  |   ~Odd_EF.10 | 10 |  Evn_EF.10  |  ~Evn_EF.10  |         |         |             |
838        | 11 | Odd_nEG.11a | ~Odd_nEG.11a | 11 | Evn_nEG.11a | ~Evn_nEG.11a | Odd_nEG |  ~(¬E)  |      ~G     |
839        | 11 | Odd_nEG.11b | ~Odd_nEG.11b | 11 | Evn_nEG.11b | ~Evn_nEG.11b |   ~E    |  ~(¬E)  | MASK_EVN_64 | <- q_ch
840        | 10 | Odd_nEG.10  |  ~Odd_nEG.10 | 10 | Evn_nEG.10  | ~Evn_nEG.10  |         |         |             |
841        */
842
843        let adv_cols = self.config().advice_cols;
844
845        let sprdd_E_val = sprdd_E.0.value().copied().map(fe_to_u64);
846        let sprdd_F_val = sprdd_F.0.value().copied().map(fe_to_u64);
847        let sprdd_G_val = sprdd_G.0.value().copied().map(fe_to_u64);
848        let sprdd_nE_val = sprdd_E_val.map(negate_spreaded);
849
850        let EpF_val = sprdd_E_val + sprdd_F_val;
851        let nEpG_val = sprdd_nE_val + sprdd_G_val;
852        let sprdd_nE_val: Value<F> = sprdd_nE_val.map(u64_to_fe);
853
854        let mask_evn_64: AssignedNative<F> =
855            self.native_gadget.assign_fixed(layouter, F::from(MASK_EVN_64))?;
856
857        layouter.assign_region(
858            || "Ch(E, F, G)",
859            |mut region| {
860                self.config().q_half_ch.enable(&mut region, 1)?;
861                self.config().q_half_ch.enable(&mut region, 4)?;
862
863                sprdd_E.0.copy_advice(|| "~E", &mut region, adv_cols[5], 0)?;
864                sprdd_E.0.copy_advice(|| "~E", &mut region, adv_cols[4], 4)?;
865
866                sprdd_F.0.copy_advice(|| "~F", &mut region, adv_cols[6], 0)?;
867                sprdd_G.0.copy_advice(|| "~G", &mut region, adv_cols[6], 3)?;
868
869                let sprdd_nE = region.assign_advice(|| "~(¬E)", adv_cols[5], 3, || sprdd_nE_val)?;
870                sprdd_nE.copy_advice(|| "~(¬E)", &mut region, adv_cols[5], 4)?;
871
872                mask_evn_64.copy_advice(|| "MASK_EVN_64", &mut region, adv_cols[6], 4)?;
873
874                let odd_EF = self.assign_sprdd_11_11_10(&mut region, EpF_val, Parity::Odd, 0)?;
875                odd_EF.0.copy_advice(|| "Odd_EF", &mut region, adv_cols[4], 1)?;
876
877                let odd_nEG = self.assign_sprdd_11_11_10(&mut region, nEpG_val, Parity::Odd, 3)?;
878                odd_nEG.0.copy_advice(|| "Odd_nEG", &mut region, adv_cols[5], 1)?;
879
880                let ret_val = odd_EF.0.value().copied() + odd_nEG.0.value().copied();
881                region
882                    .assign_advice(|| "Ret", adv_cols[6], 1, || ret_val)
883                    .map(AssignedPlain::<F, 32>)
884            },
885        )
886    }
887
888    /// Computes Σ₀(A).
889    fn Sigma_0(
890        &self,
891        layouter: &mut impl Layouter<F>,
892        a: &LimbsOfA<F>,
893    ) -> Result<AssignedPlain<F, 32>, Error> {
894        /*
895        Given
896                    A:  ( A.10 || A.09 || A.11 || A.02 )
897
898        We need to compute:
899            A >>>  2 :  ( A.02 || A.10 || A.09 || A.11 )
900          ⊕ A >>> 13 :  ( A.11 || A.02 || A.10 || A.09 )
901          ⊕ A >>> 22 :  ( A.09 || A.11 || A.02 || A.10 )
902
903        which can be achieved by
904
905        1) applying the plain-spreaded lookup on 11-11-10 limbs of Evn and Odd:
906             Evn: (Evn.11a, Evn.11b, Evn.10)
907             Odd: (Odd.11a, Odd.11b, Odd.10)
908
909        2) asserting the 11-11-10 decomposition identity for Evn:
910              2^21 * Evn.11a + 2^10 * Evn.11b + Evn.10
911            = Evn
912
913        3) asserting the Sigma_0 identity regarding the spreaded values:
914              (4^21 * ~Evn.11a + 4^10 * ~Evn.11b + ~Evn.10) +
915          2 * (4^21 * ~Odd.11a + 4^10 * ~Odd.11b + ~Odd.10)
916             = 4^30 * ~A.02 + 4^20 * ~A.10 + 4^11 * ~A.09 + ~A.11
917             + 4^21 * ~A.11 + 4^19 * ~A.02 + 4^9  * ~A.10 + ~A.09
918             + 4^23 * ~A.09 + 4^12 * ~A.11 + 4^10 * ~A.02 + ~A.10
919
920        The output is Evn.
921
922        We distribute these values in the PLONK table as follows.
923
924        | T0 |    A0   |    A1    | T1 |    A2   |    A3    |  A4 |   A5  |   A6  |
925        |----|---------|----------|----|---------|----------|-----|-------|-------|
926        | 11 | Evn.11a | ~Evn.11a | 11 | Odd.11a | ~Odd.11a | Evn | ~A.10 | ~A.09 |
927        | 11 | Evn.11b | ~Evn.11b | 11 | Odd.11b | ~Odd.11b |     | ~A.11 | ~A.02 | <- q_Sigma_0
928        | 10 | Evn.10  | ~Evn.10  | 10 | Odd.10  | ~Odd.10  |     |       |       |
929        */
930
931        let adv_cols = self.config().advice_cols;
932
933        layouter.assign_region(
934            || "Σ₀(A)",
935            |mut region| {
936                self.config().q_Sigma_0.enable(&mut region, 1)?;
937
938                // Copy and assign the input.
939                a.spreaded_limb_10.0.copy_advice(|| "~A.10", &mut region, adv_cols[5], 0)?;
940                a.spreaded_limb_09.0.copy_advice(|| "~A.09", &mut region, adv_cols[6], 0)?;
941                a.spreaded_limb_11.0.copy_advice(|| "~A.11", &mut region, adv_cols[5], 1)?;
942                a.spreaded_limb_02.0.copy_advice(|| "~A.02", &mut region, adv_cols[6], 1)?;
943
944                // Compute the spreaded Σ₀(A) off-circuit, assign the 11-11-10 limbs
945                // of its even and odd bits into the circuit, enable the q_11_11_10 selector
946                // for the even part and q_lookup selector for the related rows, return the
947                // assigned 32 even bits.
948                let val_of_sprdd_limbs: Value<[u64; 4]> = Value::from_iter([
949                    a.spreaded_limb_10.0.value().copied().map(fe_to_u64),
950                    a.spreaded_limb_09.0.value().copied().map(fe_to_u64),
951                    a.spreaded_limb_11.0.value().copied().map(fe_to_u64),
952                    a.spreaded_limb_02.0.value().copied().map(fe_to_u64),
953                ])
954                .map(|limbs: Vec<u64>| limbs.try_into().unwrap());
955
956                self.assign_sprdd_11_11_10(
957                    &mut region,
958                    val_of_sprdd_limbs.map(spreaded_Sigma_0),
959                    Parity::Evn,
960                    0,
961                )
962            },
963        )
964    }
965
966    /// Computes Σ₁(E).
967    fn Sigma_1(
968        &self,
969        layouter: &mut impl Layouter<F>,
970        e: &LimbsOfE<F>,
971    ) -> Result<AssignedPlain<F, 32>, Error> {
972        /*
973        Given
974                    E:  ( E.07 || E.12 || E.02 || E.05 || E.06 )
975
976        We need to compute:
977            E >>>  6 :  ( E.06 || E.07 || E.12 || E.02 || E.05 )
978          ⊕ E >>> 11 :  ( E.05 || E.06 || E.07 || E.12 || E.02 )
979          ⊕ E >>> 25 :  ( E.12 || E.02 || E.05 || E.06 || E.07 )
980
981        which can be achieved by
982
983        1) applying the plain-spreaded lookup on 11-11-10 limbs of Evn and Odd:
984             Evn: (Evn.11a, Evn.11b, Evn.10)
985             Odd: (Odd.11a, Odd.11b, Odd.10)
986
987        2) asserting the 11-11-10 decomposition identity for Evn:
988              2^21 * Evn.11a + 2^10 * Evn.11b + Evn.10
989            = Evn
990
991         3) asserting the Sigma_1 identity regarding the spreaded values:
992              (4^21 * ~Evn.11a + 4^10 * ~Evn.11b + ~Evn.10) +
993          2 * (4^21 * ~Odd.11a + 4^10 * ~Odd.11b + ~Odd.10)
994             = 4^26 * ~E.06 + 4^19 * ~E.07 + 4^7  * ~E.12 + 4^5 * ~E.02 + ~E.05
995             + 4^27 * ~E.05 + 4^21 * ~E.06 + 4^14 * ~E.07 + 4^2 * ~E.12 + ~E.02
996             + 4^20 * ~E.12 + 4^18 * ~E.02 + 4^13 * ~E.05 + 4^7 * ~E.06 + ~E.07
997
998        The output is Evn.
999
1000        We distribute these values in the PLONK table as follows.
1001
1002        | T0 |    A0   |    A1    | T1 |    A2   |    A3    |  A4 |  A5  |   A6  |
1003        |----|---------|----------|----|---------|----------|-----|------|-------|
1004        | 11 | Evn.11a | ~Evn.11a | 11 | Odd.11a | ~Odd.11a | Evn | ~E.7 | ~E.12 |
1005        | 11 | Evn.11b | ~Evn.11b | 11 | Odd.11b | ~Odd.11b |     | ~E.2 | ~E.5  | <- q_Sigma_1
1006        | 10 | Evn.10  | ~Evn.10  | 10 | Odd.10  | ~Odd.10  |     | ~E.6 |       |
1007        */
1008
1009        let adv_cols = self.config().advice_cols;
1010
1011        layouter.assign_region(
1012            || "Σ₁(E)",
1013            |mut region| {
1014                self.config().q_Sigma_1.enable(&mut region, 1)?;
1015
1016                // Copy and assign the input.
1017                e.spreaded_limb_07.0.copy_advice(|| "~E.07", &mut region, adv_cols[5], 0)?;
1018                e.spreaded_limb_12.0.copy_advice(|| "~E.12", &mut region, adv_cols[6], 0)?;
1019                e.spreaded_limb_02.0.copy_advice(|| "~E.02", &mut region, adv_cols[5], 1)?;
1020                e.spreaded_limb_05.0.copy_advice(|| "~E.05", &mut region, adv_cols[6], 1)?;
1021                e.spreaded_limb_06.0.copy_advice(|| "~E.06", &mut region, adv_cols[5], 2)?;
1022
1023                // Compute the spreaded Σ₁(E) off-circuit, assign the 11-11-10 limbs
1024                // of its even and odd bits into the circuit, enable the q_11_11_10 selector
1025                // for the even part and q_lookup selector for the related rows, return the
1026                // assigned 32 even bits.
1027                let val_of_sprdd_limbs: Value<[u64; 5]> = Value::from_iter([
1028                    e.spreaded_limb_07.0.value().copied().map(fe_to_u64),
1029                    e.spreaded_limb_12.0.value().copied().map(fe_to_u64),
1030                    e.spreaded_limb_02.0.value().copied().map(fe_to_u64),
1031                    e.spreaded_limb_05.0.value().copied().map(fe_to_u64),
1032                    e.spreaded_limb_06.0.value().copied().map(fe_to_u64),
1033                ])
1034                .map(|limbs: Vec<u64>| limbs.try_into().unwrap());
1035
1036                self.assign_sprdd_11_11_10(
1037                    &mut region,
1038                    val_of_sprdd_limbs.map(spreaded_Sigma_1),
1039                    Parity::Evn,
1040                    0,
1041                )
1042            },
1043        )
1044    }
1045
1046    /// Computes σ₀(W).
1047    fn sigma_0(
1048        &self,
1049        layouter: &mut impl Layouter<F>,
1050        w: &AssignedMessageWord<F>,
1051    ) -> Result<AssignedPlain<F, 32>, Error> {
1052        /*
1053        Given
1054                    W:  ( W.12 || W.1a || W.1b || W.1c || W.07 || W.3a || W.04 || W.3b )
1055
1056         We need to compute:
1057            W  >>  3 :          ( W.12 || W.1a || W.1b || W.1c || W.07 || W.3a || W.04 )
1058          ⊕ W >>>  7 :  ( W.04 || W.3b || W.12 || W.1a || W.1b || W.1c || W.07 || W.3a )
1059          ⊕ W >>> 18 :  ( W.1c || W.07 || W.3a || W.04 || W.3b || W.12 || W.1a || W.1b )
1060
1061        which can be achieved by
1062
1063         1) applying the plain-spreaded lookup on 11-11-10 limbs of Evn and Odd:
1064             Evn: (Evn.11a, Evn.11b, Evn.10)
1065             Odd: (Odd.11a, Odd.11b, Odd.10)
1066
1067        2) asserting the 11-11-10 decomposition identity for Evn:
1068              2^21 * Evn.11a + 2^10 * Evn.11b + Evn.10
1069            = Evn
1070
1071        3) asserting the sigma_0 identity regarding the spreaded values:
1072              (4^21 * ~Evn.11a + 4^10 * ~Evn.11b + ~Evn.10) +
1073          2 * (4^21 * ~Odd.11a + 4^10 * ~Odd.11b + ~Odd.10)
1074             =                4^17 * ~W.12 + 4^16 * ~W.1a + 4^15 * ~W.1b + 4^14 * ~W.1c +  4^7 * ~W.07 + 4^4 * ~W.3a + ~W.04
1075             + 4^28 * ~W.04 + 4^25 * ~W.3b + 4^13 * ~W.12 + 4^12 * ~W.1a + 4^11 * ~W.1b + 4^10 * ~W.1c + 4^3 * ~W.07 + ~W.3a
1076             + 4^31 * ~W.1c + 4^24 * ~W.07 + 4^21 * ~W.3a + 4^17 * ~W.04 + 4^14 * ~W.3b +  4^2 * ~W.12 + 4^1 * ~W.1a + ~W.1b
1077
1078        The output is Evn.
1079
1080        We distribute these values in the PLONK table as follows.
1081
1082        | T0 |    A0    |     A1    | T1 |   A_2   |    A3    |   A4  |   A5  |   A6  |
1083        |----|----------|-----------|----|---------|----------|-------|-------|-------|
1084        | 11 | Even.11a | ~Even.11a | 11 | Odd.11a | ~Odd.11a |  Evn  | ~W.12 | ~W.1a |
1085        | 11 | Even.11b | ~Even.11b | 11 | Odd.11b | ~Odd.11b | ~W.1b | ~W.1c | ~W.7  | <- q_sigma_0
1086        | 10 | Even.10  | ~Even.10  | 10 | Odd.10  | ~Odd.10  | ~W.3a | ~W.4  | ~W.3b |
1087        */
1088
1089        let adv_cols = self.config().advice_cols;
1090
1091        layouter.assign_region(
1092            || "σ₀(W)",
1093            |mut region| {
1094                self.config().q_sigma_0.enable(&mut region, 1)?;
1095
1096                w.spreaded_w_12.0.copy_advice(|| "~W.12", &mut region, adv_cols[5], 0)?;
1097                w.spreaded_w_1a.0.copy_advice(|| "~W.1a", &mut region, adv_cols[6], 0)?;
1098                w.spreaded_w_1b.0.copy_advice(|| "~W.1b", &mut region, adv_cols[4], 1)?;
1099                w.spreaded_w_1c.0.copy_advice(|| "~W.1c", &mut region, adv_cols[5], 1)?;
1100                w.spreaded_w_07.0.copy_advice(|| "~W.07", &mut region, adv_cols[6], 1)?;
1101                w.spreaded_w_3a.0.copy_advice(|| "~W.3a", &mut region, adv_cols[4], 2)?;
1102                w.spreaded_w_04.0.copy_advice(|| "~W.04", &mut region, adv_cols[5], 2)?;
1103                w.spreaded_w_3b.0.copy_advice(|| "~W.3b", &mut region, adv_cols[6], 2)?;
1104
1105                let val_of_sprdd_limbs: Value<[u64; 8]> = Value::from_iter([
1106                    w.spreaded_w_12.0.value().copied().map(fe_to_u64),
1107                    w.spreaded_w_1a.0.value().copied().map(fe_to_u64),
1108                    w.spreaded_w_1b.0.value().copied().map(fe_to_u64),
1109                    w.spreaded_w_1c.0.value().copied().map(fe_to_u64),
1110                    w.spreaded_w_07.0.value().copied().map(fe_to_u64),
1111                    w.spreaded_w_3a.0.value().copied().map(fe_to_u64),
1112                    w.spreaded_w_04.0.value().copied().map(fe_to_u64),
1113                    w.spreaded_w_3b.0.value().copied().map(fe_to_u64),
1114                ])
1115                .map(|limbs: Vec<u64>| limbs.try_into().unwrap());
1116
1117                self.assign_sprdd_11_11_10(
1118                    &mut region,
1119                    val_of_sprdd_limbs.map(spreaded_sigma_0),
1120                    Parity::Evn,
1121                    0,
1122                )
1123            },
1124        )
1125    }
1126
1127    /// Computes σ₁(W).
1128    fn sigma_1(
1129        &self,
1130        layouter: &mut impl Layouter<F>,
1131        w: &AssignedMessageWord<F>,
1132    ) -> Result<AssignedPlain<F, 32>, Error> {
1133        /*
1134        Given
1135                    W:  ( W.12 || W.1a || W.1b || W.1c || W.07 || W.3a || W.04 || W.3b )
1136
1137         We need to compute:
1138            W  >> 10 :                          ( W.12 || W.1a || W.1b || W.1c || W.07 )
1139          ⊕ W >>> 17 :  ( W.07 || W.3a || W.04 || W.3b || W.12 || W.1a || W.1b || W.1c )
1140          ⊕ W >>> 19 :  ( W.1b || W.1c || W.07 || W.3a || W.04 || W.3b || W.12 || W.1a )
1141
1142        which can be achieved by
1143
1144         1) applying the plain-spreaded lookup on 11-11-10 limbs of Evn and Odd:
1145             Evn: (Evn.11a, Evn.11b, Evn.10)
1146             Odd: (Odd.11a, Odd.11b, Odd.10)
1147
1148        2) asserting the 11-11-10 decomposition identity for Evn:
1149              2^21 * Evn.11a + 2^10 * Evn.11b + Evn.10
1150            = Evn
1151
1152        3) asserting the sigma_0 identity regarding the spreaded values:
1153              (4^21 * ~Evn.11a + 4^10 * ~Evn.11b + ~Evn.10) +
1154          2 * (4^21 * ~Odd.11a + 4^10 * ~Odd.11b + ~Odd.10)
1155             =                                              4^10 * ~W.12 +  4^9 * ~W.1a +  4^8 * ~W.1b + 4^7 * ~W.1c + ~W.07
1156             + 4^25 * ~W.07 + 4^22 * ~W.3a + 4^18 * ~W.04 + 4^15 * ~W.3b +  4^3 * ~W.12 +  4^2 * ~W.1a + 4^1 * ~W.1b + ~W.1c
1157             + 4^31 * ~W.1b + 4^30 * ~W.1c + 4^23 * ~W.07 + 4^20 * ~W.3a + 4^16 * ~W.04 + 4^13 * ~W.3b + 4^1 * ~W.12 + ~W.1a
1158
1159        The output is Evn.
1160
1161        We distribute these values in the PLONK table as follows.
1162
1163        | T0 |    A0    |     A1    | T1 |    A2   |    A3    |   A4  |   A5  |   A6  |
1164        |----|----------|-----------|----|---------|----------|-------|-------|-------|
1165        | 11 | Even.11a | ~Even.11a | 11 | Odd.11a | ~Odd.11a |  Evn  | ~W.12 | ~W.1a |
1166        | 11 | Even.11b | ~Even.11b | 11 | Odd.11b | ~Odd.11b | ~W.1b | ~W.1c | ~W.7  | <- q_sigma_1
1167        | 10 | Even.10  | ~Even.10  | 10 | Odd.10  | ~Odd.10  | ~W.3a | ~W.4  | ~W.3b |
1168        */
1169
1170        let adv_cols = self.config().advice_cols;
1171
1172        layouter.assign_region(
1173            || "σ₁(W)",
1174            |mut region| {
1175                self.config().q_sigma_1.enable(&mut region, 1)?;
1176
1177                w.spreaded_w_12.0.copy_advice(|| "~W.12", &mut region, adv_cols[5], 0)?;
1178                w.spreaded_w_1a.0.copy_advice(|| "~W.1a", &mut region, adv_cols[6], 0)?;
1179                w.spreaded_w_1b.0.copy_advice(|| "~W.1b", &mut region, adv_cols[4], 1)?;
1180                w.spreaded_w_1c.0.copy_advice(|| "~W.1c", &mut region, adv_cols[5], 1)?;
1181                w.spreaded_w_07.0.copy_advice(|| "~W.07", &mut region, adv_cols[6], 1)?;
1182                w.spreaded_w_3a.0.copy_advice(|| "~W.3a", &mut region, adv_cols[4], 2)?;
1183                w.spreaded_w_04.0.copy_advice(|| "~W.04", &mut region, adv_cols[5], 2)?;
1184                w.spreaded_w_3b.0.copy_advice(|| "~W.3b", &mut region, adv_cols[6], 2)?;
1185
1186                let val_of_sprdd_limbs: Value<[u64; 8]> = Value::from_iter([
1187                    w.spreaded_w_12.0.value().copied().map(fe_to_u64),
1188                    w.spreaded_w_1a.0.value().copied().map(fe_to_u64),
1189                    w.spreaded_w_1b.0.value().copied().map(fe_to_u64),
1190                    w.spreaded_w_1c.0.value().copied().map(fe_to_u64),
1191                    w.spreaded_w_07.0.value().copied().map(fe_to_u64),
1192                    w.spreaded_w_3a.0.value().copied().map(fe_to_u64),
1193                    w.spreaded_w_04.0.value().copied().map(fe_to_u64),
1194                    w.spreaded_w_3b.0.value().copied().map(fe_to_u64),
1195                ])
1196                .map(|limbs: Vec<u64>| limbs.try_into().unwrap());
1197
1198                self.assign_sprdd_11_11_10(
1199                    &mut region,
1200                    val_of_sprdd_limbs.map(spreaded_sigma_1),
1201                    Parity::Evn,
1202                    0,
1203                )
1204            },
1205        )
1206    }
1207
1208    /// Given a u64, representing a spreaded value, this function fills the
1209    /// plonk table with the limbs of its even and odd parts (or vice versa)
1210    /// and returns the former or the latter, depending on the desired value
1211    /// `even_or_odd`.
1212    ///
1213    /// If `even_or_odd` = `Parity::Evn`:
1214    ///
1215    ///  | T0 |    A0   |    A1    | T1 |    A2   |    A3    |  A4 |
1216    ///  |----|---------|----------|----|---------|----------|-----|
1217    ///  | 11 | Evn.11a | ~Evn.11a | 11 | Odd.11a | ~Odd.11a | Evn |
1218    ///  | 11 | Evn.11b | ~Evn.11b | 11 | Odd.11b | ~Odd.11b |     | <- q_11_11_10
1219    ///  | 10 | Evn.10  | ~Evn.10  | 10 | Odd.10  | ~Odd.10  |     |
1220    ///
1221    /// and returns `Evn`.
1222    ///
1223    /// If `even_or_odd` = `Parity::Odd`:
1224    ///
1225    ///  | T0 |    A0   |    A1    | T1 |    A2   |    A3    |  A4 |
1226    ///  |----|---------|----------|----|---------|----------|-----|
1227    ///  | 11 | Odd.11a | ~Odd.11a | 11 | Evn.11a | ~Evn.11a | Odd |
1228    ///  | 11 | Odd.11b | ~Odd.11b | 11 | Evn.11b | ~Evn.11b |     | <- q_11_11_10
1229    ///  | 10 | Odd.10  | ~Odd.10  | 10 | Evn.10  | ~Evn.10  |     |
1230    ///
1231    /// and returns `Odd`.
1232    ///
1233    /// This function guarantees that the returned value is consistent with
1234    /// the values filled in the table.
1235    ///
1236    /// Namely, that (e.g. in the case of `even_or_odd` = `Parity::Evn`):
1237    ///
1238    ///   2^21 * Evn.11a + 2^10 * Evn.11b + Evn.10 = Evn
1239    ///
1240    /// NB: This function DOES activate the plain-spreaded lookup table, which
1241    /// guarantees that all 6 plain and spreaded values are consistent.
1242    fn assign_sprdd_11_11_10(
1243        &self,
1244        region: &mut Region<'_, F>,
1245        value: Value<u64>,
1246        even_or_odd: Parity,
1247        offset: usize,
1248    ) -> Result<AssignedPlain<F, 32>, Error> {
1249        self.config().q_11_11_10.enable(region, offset + 1)?;
1250
1251        let (evn_val, odd_val) = value.map(get_even_and_odd_bits).unzip();
1252
1253        let [evn_11a, evn_11b, evn_10] =
1254            evn_val.map(|v| u32_in_be_limbs(v, [11, 11, 10])).transpose_array();
1255
1256        let [odd_11a, odd_11b, odd_10] =
1257            odd_val.map(|v| u32_in_be_limbs(v, [11, 11, 10])).transpose_array();
1258
1259        let idx = match even_or_odd {
1260            Parity::Evn => 0,
1261            Parity::Odd => 1,
1262        };
1263
1264        self.assign_plain_and_spreaded::<11>(region, evn_11a, offset, idx)?;
1265        self.assign_plain_and_spreaded::<11>(region, evn_11b, offset + 1, idx)?;
1266        self.assign_plain_and_spreaded::<10>(region, evn_10, offset + 2, idx)?;
1267
1268        self.assign_plain_and_spreaded::<11>(region, odd_11a, offset, 1 - idx)?;
1269        self.assign_plain_and_spreaded::<11>(region, odd_11b, offset + 1, 1 - idx)?;
1270        self.assign_plain_and_spreaded::<10>(region, odd_10, offset + 2, 1 - idx)?;
1271
1272        let out_col = self.config().advice_cols[4];
1273        match even_or_odd {
1274            Parity::Evn => {
1275                region.assign_advice(|| "Evn", out_col, offset, || evn_val.map(u32_to_fe))
1276            }
1277            Parity::Odd => {
1278                region.assign_advice(|| "Odd", out_col, offset, || odd_val.map(u32_to_fe))
1279            }
1280        }
1281        .map(AssignedPlain)
1282    }
1283
1284    /// Given a slice of at most 7 `AssignedPlain` values, it adds them
1285    /// modulo 2^32 and decomposes the result (named A) into (big-endian)
1286    /// limbs of bit sizes 10, 9, 11 and 2.
1287    ///
1288    /// This function returns the plain and spreaded forms, as well as
1289    /// the spreaded limbs of A.
1290    fn prepare_A(
1291        &self,
1292        layouter: &mut impl Layouter<F>,
1293        summands: &[AssignedPlain<F, 32>],
1294    ) -> Result<LimbsOfA<F>, Error> {
1295        /*
1296        Given assigned plain inputs S0, ..., S6 (if fewer inputs are given
1297        they will be completed up to length 7, padding with fixed zeros),
1298        let A be their sum modulo 2^32.
1299
1300        We use the following table distribution.
1301
1302        | T0 |  A0  |  A1   | T1 |   A2  |   A3   | A4 | A5 | A6 |
1303        |----|------|-------|----|-------|--------|----|----|----|
1304        | 10 | A.10 | ~A.10 |  9 |  A.9  |  ~A.9  |  A | S0 | S1 |
1305        | 11 | A.11 | ~A.11 |  2 |  A.2  |  ~A.2  | ~A | S2 | S3 | <- q_10_9_11_2
1306        |  0 |   0  |   0   |  3 | carry | ~carry | S4 | S5 | S6 |
1307
1308        Apart from the lookups, the following identities are checked via a
1309        custom gate with selector q_10_9_11_2:
1310
1311            A = 2^22 *  A.10 + 2^13 *  A.9 + 2^2 *  A.11 +  A.2
1312           ~A = 4^22 * ~A.10 + 4^13 * ~A.9 + 4^2 * ~A.11 + ~A.2
1313
1314        and the following is checked with a custom gate with selector
1315        q_add_mod_2_32:
1316
1317            S0 + S1 + S2 + S3 + S4 + S5 + S6 = A + carry * 2^32
1318
1319        Note that A is implicitly being range-checked in [0, 2^32) via
1320        the lookup, and the carry is range-checked in [0, 8). This makes
1321        the gate complete and sound (the range on the carry does not need
1322        to be tight as long as it prevents overflows in the native field).
1323        */
1324
1325        let zero = AssignedPlain::<F, 32>::fixed(layouter, &self.native_gadget, 0)?;
1326
1327        layouter.assign_region(
1328            || "decompose A in 10-9-11-2",
1329            |mut region| {
1330                self.config().q_10_9_11_2.enable(&mut region, 1)?;
1331
1332                let a_plain = self.assign_add_mod_2_32(&mut region, summands, &zero)?;
1333                let a_sprdd_val =
1334                    a_plain.value().copied().map(fe_to_u32).map(spread).map(u64_to_fe);
1335                let a_sprdd = region
1336                    .assign_advice(|| "~A", self.config().advice_cols[4], 1, || a_sprdd_val)
1337                    .map(AssignedSpreaded)?;
1338
1339                let [val_10, val_09, val_11, val_02] = (a_plain.value().copied())
1340                    .map(|a| u32_in_be_limbs(fe_to_u32(a), [10, 9, 11, 2]))
1341                    .transpose_array();
1342
1343                let limb_10 = self.assign_plain_and_spreaded(&mut region, val_10, 0, 0)?;
1344                let limb_09 = self.assign_plain_and_spreaded(&mut region, val_09, 0, 1)?;
1345                let limb_11 = self.assign_plain_and_spreaded(&mut region, val_11, 1, 0)?;
1346                let limb_02 = self.assign_plain_and_spreaded(&mut region, val_02, 1, 1)?;
1347                let _zeros =
1348                    self.assign_plain_and_spreaded::<0>(&mut region, Value::known(0), 2, 0)?;
1349
1350                Ok(LimbsOfA {
1351                    combined: AssignedPlainSpreaded {
1352                        plain: AssignedPlain(a_plain),
1353                        spreaded: a_sprdd,
1354                    },
1355                    spreaded_limb_10: limb_10.spreaded,
1356                    spreaded_limb_09: limb_09.spreaded,
1357                    spreaded_limb_11: limb_11.spreaded,
1358                    spreaded_limb_02: limb_02.spreaded,
1359                })
1360            },
1361        )
1362    }
1363
1364    /// Given a slice of at most 7 `AssignedPlain` values, it adds them
1365    /// modulo 2^32 and decomposes the result (named E) into (big-endian)
1366    /// limbs of bit sizes 7, 12, 2, 5 and 6.
1367    ///
1368    /// This function returns the plain and spreaded forms, as well as
1369    /// the spreaded limbs of E.
1370    fn prepare_E(
1371        &self,
1372        layouter: &mut impl Layouter<F>,
1373        summands: &[AssignedPlain<F, 32>],
1374    ) -> Result<LimbsOfE<F>, Error> {
1375        /*
1376        Given assigned plain inputs S0, ..., S6 (if fewer inputs are given
1377        they will be completed up to length 7, padding with fixed zeros),
1378        let E be their sum modulo 2^32.
1379
1380        | T0 |  A0  |   A1  | T1 |   A2  |   A3   | A4 | A5 | A6 |
1381        |----|------|-------|----|-------|--------|----|----|----|
1382        |  7 | E.07 | ~E.07 | 12 |  E.12 |  ~E.12 |  E | S0 | S1 |
1383        |  2 | E.02 | ~E.02 |  5 |  E.5  |  ~E.5  | ~E | S2 | S3 | <- q_7_12_2_5_6
1384        |  6 | E.06 | ~E.06 |  3 | carry | ~carry | S4 | S5 | S6 |
1385
1386        Apart from the lookups, the following identities are checked via a
1387        custom gate with selector q_7_12_2_5_6:
1388
1389            E = 2^25 *  E.07 + 2^13 *  E.12 + 2^11 *  E.02 + 2^6 *  E.05 +  E.06
1390           ~E = 4^25 * ~E.07 + 4^13 * ~E.12 + 4^11 * ~E.02 + 4^6 * ~E.05 + ~E.06
1391
1392        and the following is checked with a custom gate with selector
1393        q_add_mod_2_32:
1394
1395            S0 + S1 + S2 + S3 + S4 + S5 + S6 = E + carry * 2^32
1396
1397        Note that E is implicitly being range-checked in [0, 2^32) via
1398        the lookup, and the carry is range-checked in [0, 8). This makes
1399        the gate complete and sound (the range on the carry does not need
1400        to be tight as long as it prevents overflows in the native field).
1401        */
1402
1403        let zero = AssignedPlain::<F, 32>::fixed(layouter, &self.native_gadget, 0)?;
1404
1405        layouter.assign_region(
1406            || "decompose E in 7-12-2-5-6",
1407            |mut region| {
1408                self.config().q_7_12_2_5_6.enable(&mut region, 1)?;
1409
1410                let e_plain = self.assign_add_mod_2_32(&mut region, summands, &zero)?;
1411                let e_sprdd_val =
1412                    (e_plain.value().copied()).map(fe_to_u32).map(spread).map(u64_to_fe);
1413                let e_sprdd = region
1414                    .assign_advice(|| "~E", self.config().advice_cols[4], 1, || e_sprdd_val)
1415                    .map(AssignedSpreaded)?;
1416
1417                let [val_07, val_12, val_02, val_05, val_06] = (e_plain.value().copied())
1418                    .map(|e| u32_in_be_limbs(fe_to_u32(e), [7, 12, 2, 5, 6]))
1419                    .transpose_array();
1420
1421                let limb_07 = self.assign_plain_and_spreaded(&mut region, val_07, 0, 0)?;
1422                let limb_12 = self.assign_plain_and_spreaded(&mut region, val_12, 0, 1)?;
1423                let limb_02 = self.assign_plain_and_spreaded(&mut region, val_02, 1, 0)?;
1424                let limb_05 = self.assign_plain_and_spreaded(&mut region, val_05, 1, 1)?;
1425                let limb_06 = self.assign_plain_and_spreaded(&mut region, val_06, 2, 0)?;
1426
1427                Ok(LimbsOfE {
1428                    combined: AssignedPlainSpreaded {
1429                        plain: AssignedPlain(e_plain),
1430                        spreaded: e_sprdd,
1431                    },
1432                    spreaded_limb_07: limb_07.spreaded,
1433                    spreaded_limb_12: limb_12.spreaded,
1434                    spreaded_limb_02: limb_02.spreaded,
1435                    spreaded_limb_05: limb_05.spreaded,
1436                    spreaded_limb_06: limb_06.spreaded,
1437                })
1438            },
1439        )
1440    }
1441
1442    /// Given a slice of at most 7 `AssignedPlain` values, this function adds
1443    /// them modulo 2^32 and decomposes the result (named W_i) into (big-endian)
1444    /// limbs of bit sizes 12, 1, 1, 1, 7, 3, 4 and 3.
1445    fn prepare_message_word(
1446        &self,
1447        layouter: &mut impl Layouter<F>,
1448        summands: &[AssignedPlain<F, 32>],
1449    ) -> Result<AssignedMessageWord<F>, Error> {
1450        /*
1451        Given assigned plain inputs S0, ..., S6 (if fewer inputs are given
1452        they will be completed up to length 7, padding with fixed zeros),
1453        and computes W.i as their sum modulo 2^32.
1454
1455        We use the following table distribution.
1456
1457        | T0 |  A0  |   A1  | T1 |   A2  |   A3   |  A4 | A5 | A6 |  A7  |
1458        |----|------|-------|----|-------|--------|-----|----|----|------|
1459        | 12 | W.12 | ~W.12 |  7 |  W.07 | ~W.07  | W.i | S0 | S1 | W.1a |
1460        |  3 | W.3a | ~W.3a |  4 |  W.04 | ~W.04  |     | S2 | S3 | W.1b | <- q_12_1x3_7_3_4_3
1461        |  3 | W.3b | ~W.3b |  3 | carry | ~carry |  S4 | S5 | S6 | W.1c |
1462
1463        Apart from the lookups, the following identities are checked via a
1464        custom gate with selector q_12_1x3_7_3_4_3:
1465
1466          W.i =   2^20 * W.12 + 2^19 * W.1a + 2^18 * W.1b + 2^17 * W.1c
1467                + 2^10 * W.07 + 2^7 * W.3a + 2^3 * W.04 + W.3b
1468
1469          W.1a * (W.1a - 1) = 0
1470          W.1b * (W.1b - 1) = 0
1471          W.1c * (W.1c - 1) = 0
1472
1473        and the following is checked with a custom gate with selector
1474        q_add_mod_2_32:
1475
1476          S0 + S1 + S2 + S3 + S4 + S5 + S6 = W.i + carry * 2^32
1477
1478        Note that W.i is implicitly being range-checked in [0, 2^32) via
1479        the lookup, and the carry is range-checked in [0, 8). This makes
1480        the gate complete and sound (the range on the carry does not need
1481        to be tight as long as it prevents overflows in the native field).
1482        */
1483
1484        let zero = AssignedPlain::<F, 32>::fixed(layouter, &self.native_gadget, 0)?;
1485
1486        layouter.assign_region(
1487            || "prepare message word",
1488            |mut region| {
1489                self.config().q_12_1x3_7_3_4_3.enable(&mut region, 1)?;
1490
1491                let w_i_plain = self.assign_add_mod_2_32(&mut region, summands, &zero)?;
1492
1493                let [val_12, val_1a, val_1b, val_1c, val_07, val_3a, val_04, val_3b] =
1494                    (w_i_plain.value().copied())
1495                        .map(|w| u32_in_be_limbs(fe_to_u32(w), [12, 1, 1, 1, 7, 3, 4, 3]))
1496                        .transpose_array();
1497                let limb_12 = self.assign_plain_and_spreaded(&mut region, val_12, 0, 0)?;
1498                let limb_07 = self.assign_plain_and_spreaded(&mut region, val_07, 0, 1)?;
1499                let limb_3a = self.assign_plain_and_spreaded(&mut region, val_3a, 1, 0)?;
1500                let limb_04 = self.assign_plain_and_spreaded(&mut region, val_04, 1, 1)?;
1501                let limb_3b = self.assign_plain_and_spreaded(&mut region, val_3b, 2, 0)?;
1502
1503                // The spreaded forms of 1-bit values W.1a, W.1b and W.1c equal themselves.
1504                let col = self.config().advice_cols[7];
1505                let limb_1a = region.assign_advice(|| "W.1a", col, 0, || val_1a.map(u32_to_fe))?;
1506                let limb_1b = region.assign_advice(|| "W.1b", col, 1, || val_1b.map(u32_to_fe))?;
1507                let limb_1c = region.assign_advice(|| "W.1c", col, 2, || val_1c.map(u32_to_fe))?;
1508
1509                Ok(AssignedMessageWord {
1510                    combined_plain: AssignedPlain(w_i_plain),
1511                    spreaded_w_12: limb_12.spreaded,
1512                    spreaded_w_1a: AssignedSpreaded(limb_1a),
1513                    spreaded_w_1b: AssignedSpreaded(limb_1b),
1514                    spreaded_w_1c: AssignedSpreaded(limb_1c),
1515                    spreaded_w_07: limb_07.spreaded,
1516                    spreaded_w_3a: limb_3a.spreaded,
1517                    spreaded_w_04: limb_04.spreaded,
1518                    spreaded_w_3b: limb_3b.spreaded,
1519                })
1520            },
1521        )
1522    }
1523
1524    /// Given a plain u32 value, supposedly in the range [0, 2^L), assigns it
1525    /// in plain and spreaded form, returning an `AssignedPlainSpreaded<F, L>`.
1526    ///
1527    /// The assigned values are guaranteed to be well-formed and consistent
1528    /// via a lookup check at the specified offset.
1529    ///
1530    /// Note that we have two parallel lookup arguments. The caller must
1531    /// choose which of the two is used via the `lookup_idx`.
1532    /// If `lookup_idx = 0`, the lookup on columns (T0, A0, A1) will be used.
1533    /// If `lookup_idx = 1`, the lookup on columns (T1, A2, A3) will be used.
1534    ///
1535    /// # Unsatisfiable Circuit
1536    ///
1537    /// If the given value is not in the range [0, 2^L).
1538    fn assign_plain_and_spreaded<const L: usize>(
1539        &self,
1540        region: &mut Region<'_, F>,
1541        plain_val: Value<u32>,
1542        offset: usize,
1543        lookup_idx: usize,
1544    ) -> Result<AssignedPlainSpreaded<F, L>, Error> {
1545        self.config().q_lookup.enable(region, offset)?;
1546
1547        let nbits_col = self.config().fixed_cols[lookup_idx]; // 0 or 1
1548        let plain_col = self.config().advice_cols[2 * lookup_idx]; // 0 or 2
1549        let sprdd_col = self.config().advice_cols[2 * lookup_idx + 1]; // 1 or 3
1550
1551        let nbits_val = Value::known(F::from(L as u64));
1552        let sprdd_val = plain_val.map(spread).map(u64_to_fe);
1553        let plain_val = plain_val.map(u32_to_fe);
1554
1555        region.assign_fixed(|| "nbits", nbits_col, offset, || nbits_val)?;
1556        let plain = region.assign_advice(|| "plain", plain_col, offset, || plain_val)?;
1557        let spreaded = region.assign_advice(|| "sprdd", sprdd_col, offset, || sprdd_val)?;
1558
1559        Ok(AssignedPlainSpreaded {
1560            plain: AssignedPlain(plain),
1561            spreaded: AssignedSpreaded(spreaded),
1562        })
1563    }
1564
1565    /// Given a slice of at most 7 `AssignedPlain` values, this function adds
1566    /// them modulo 2^32.
1567    ///
1568    /// The `zero` argument is supposed to contain a fixed assigned plain
1569    /// containing value 0, this is not enforced in this function, it is the
1570    /// responsibility of the caller to do so.
1571    ///
1572    /// # Panics
1573    ///
1574    /// If the more than 7 summands are provided.
1575    ///
1576    /// # WARNING
1577    ///
1578    /// This function does not constrain the result to be in the range
1579    /// [0, 2^32), it is the caller's responsibility to do so.
1580    /// Without such check, this function would be unsound.
1581    fn assign_add_mod_2_32(
1582        &self,
1583        region: &mut Region<'_, F>,
1584        summands: &[AssignedPlain<F, 32>],
1585        zero: &AssignedPlain<F, 32>,
1586    ) -> Result<AssignedNative<F>, Error> {
1587        /*
1588        We distribute values in the PLONK table as follows.
1589
1590        | T1 |   A2  |   A3   |     A4    | A5 | A6 |
1591        |----|-------|--------|-----------|----|----|
1592        |    |       |        | sum_plain | S0 | S1 |
1593        |    |       |        |           | S2 | S3 | <- q_add_mod_2_32
1594        |  3 | carry | ~carry |     S4    | S5 | S6 |
1595
1596        We enforce S0 + S1 + S2 + S3 + S4 + S5 + S6 = sum_plain + carry * 2^32.
1597        */
1598
1599        assert!(summands.len() <= 7);
1600
1601        self.config().q_add_mod_2_32.enable(region, 1)?;
1602        let adv_cols = self.config().advice_cols;
1603
1604        let mut summands = summands.to_vec();
1605        summands.resize(7, zero.clone());
1606
1607        let (carry_val, sum_val): (Value<u32>, Value<F>) =
1608            Value::<Vec<F>>::from_iter(summands.iter().map(|s| s.0.value().copied()))
1609                .map(|v| v.into_iter().map(fe_to_u64).sum())
1610                .map(|s: u64| s.div_rem(&(1 << 32)))
1611                .map(|(carry, r)| (carry as u32, u64_to_fe(r)))
1612                .unzip();
1613
1614        summands[0].0.copy_advice(|| "S0", region, adv_cols[5], 0)?;
1615        summands[1].0.copy_advice(|| "S1", region, adv_cols[6], 0)?;
1616        summands[2].0.copy_advice(|| "S2", region, adv_cols[5], 1)?;
1617        summands[3].0.copy_advice(|| "S3", region, adv_cols[6], 1)?;
1618        summands[4].0.copy_advice(|| "S4", region, adv_cols[4], 2)?;
1619        summands[5].0.copy_advice(|| "S5", region, adv_cols[5], 2)?;
1620        summands[6].0.copy_advice(|| "S6", region, adv_cols[6], 2)?;
1621        let _carry: AssignedPlainSpreaded<F, 3> =
1622            self.assign_plain_and_spreaded(region, carry_val, 2, 1)?;
1623        region.assign_advice(|| "sum", adv_cols[4], 0, || sum_val)
1624    }
1625}
1626
1627impl<F: CircuitField> CompressionState<F> {
1628    /// Adds pair-wise (modulo 2^32) the fields of two compression states.
1629    pub fn add(
1630        &self,
1631        sha256_chip: &Sha256Chip<F>,
1632        layouter: &mut impl Layouter<F>,
1633        other: &Self,
1634    ) -> Result<Self, Error> {
1635        let a = sha256_chip.prepare_A(layouter, &[self.a.plain(), other.a.plain()])?;
1636        let b = sha256_chip.prepare_A(layouter, &[self.b.plain.clone(), other.b.plain.clone()])?;
1637        let c = sha256_chip.prepare_A(layouter, &[self.c.plain.clone(), other.c.plain.clone()])?;
1638        let d = sha256_chip.prepare_A(layouter, &[self.d.clone(), other.d.clone()])?;
1639        // NB: d can be optimized and do it in a single row without `prepare_A`.
1640
1641        let e = sha256_chip.prepare_E(layouter, &[self.e.plain(), other.e.plain()])?;
1642        let f = sha256_chip.prepare_E(layouter, &[self.f.plain.clone(), other.f.plain.clone()])?;
1643        let g = sha256_chip.prepare_E(layouter, &[self.g.plain.clone(), other.g.plain.clone()])?;
1644        let h = sha256_chip.prepare_E(layouter, &[self.h.clone(), other.h.clone()])?;
1645        // NB: h can be optimized and do it in a single row without `prepare_E`.
1646
1647        Ok(Self {
1648            a,
1649            b: b.combined,
1650            c: c.combined,
1651            d: d.combined.plain,
1652            e,
1653            f: f.combined,
1654            g: g.combined,
1655            h: h.combined.plain,
1656        })
1657    }
1658}
1659
1660#[cfg(any(test, feature = "testing"))]
1661use midnight_proofs::plonk::Instance;
1662
1663#[cfg(any(test, feature = "testing"))]
1664use crate::{field::decomposition::chip::P2RDecompositionConfig, testing_utils::FromScratch};
1665
1666#[cfg(any(test, feature = "testing"))]
1667impl<F: CircuitField> FromScratch<F> for Sha256Chip<F> {
1668    type Config = (Sha256Config, P2RDecompositionConfig);
1669
1670    fn new_from_scratch(config: &Self::Config) -> Self {
1671        Self {
1672            config: config.0.clone(),
1673            native_gadget: NativeGadget::new_from_scratch(&config.1),
1674        }
1675    }
1676
1677    fn configure_from_scratch(
1678        meta: &mut ConstraintSystem<F>,
1679        advice_columns: &mut Vec<Column<Advice>>,
1680        fixed_columns: &mut Vec<Column<Fixed>>,
1681        instance_columns: &[Column<Instance>; 2],
1682    ) -> Self::Config {
1683        use std::cmp::max;
1684
1685        use crate::field::{
1686            decomposition::pow2range::Pow2RangeChip,
1687            native::{NB_ARITH_COLS, NB_ARITH_FIXED_COLS},
1688        };
1689
1690        let nb_advice_needed = max(NB_ARITH_COLS, NB_SHA256_ADVICE_COLS);
1691        let nb_fixed_needed = max(NB_ARITH_FIXED_COLS, NB_SHA256_FIXED_COLS);
1692
1693        while advice_columns.len() < nb_advice_needed {
1694            advice_columns.push(meta.advice_column());
1695        }
1696        while fixed_columns.len() < nb_fixed_needed {
1697            fixed_columns.push(meta.fixed_column());
1698        }
1699
1700        let native_config = NativeChip::configure(
1701            meta,
1702            &(
1703                advice_columns[..NB_ARITH_COLS].try_into().unwrap(),
1704                fixed_columns[..NB_ARITH_FIXED_COLS].try_into().unwrap(),
1705                *instance_columns,
1706            ),
1707        );
1708
1709        let pow2range_config = Pow2RangeChip::configure(meta, &advice_columns[1..=4]);
1710        let core_decomposition_config =
1711            P2RDecompositionChip::configure(meta, &(native_config, pow2range_config));
1712
1713        let sha256_config = Sha256Chip::configure(
1714            meta,
1715            &(
1716                advice_columns[..NB_SHA256_ADVICE_COLS].try_into().unwrap(),
1717                fixed_columns[..NB_SHA256_FIXED_COLS].try_into().unwrap(),
1718            ),
1719        );
1720
1721        (sha256_config, core_decomposition_config)
1722    }
1723
1724    fn load_from_scratch(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
1725        self.native_gadget.load_from_scratch(layouter)?;
1726        self.load(layouter)
1727    }
1728}