Skip to main content

midnight_circuits/hash/sha512/
sha512_chip.rs

1//! This file implements a chip providing support for in-circuit evaluation of
2//! the SHA512 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 u64 is the u128 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: u64 by ~X: u128.
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^64), Z = X ⊕ Y can be enforced as
25//! ~Z + 2 * ~W = ~X + ~Y and Z, W in [0, 2^64); 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 64-bit values are represented in limbs of at most 13 bits. This allows
34//! us to have a small table with (only) values in [1, 2, 3, 4, 5, 6, 10, 11,
35//! 12, 13].
36//!
37//! We have 2 parallel lookups, which allow us to call such plain-spreaded table
38//! twice per row; on columns named (T0, A0, A1) and (T1, A2, A3).
39
40use midnight_proofs::{
41    circuit::{Chip, Layouter, Region, Value},
42    plonk::{
43        Advice, Column, ConstraintSystem, Constraints, Error, Expression, Fixed, Selector,
44        TableColumn,
45    },
46    poly::Rotation,
47};
48use num_integer::Integer;
49
50use crate::{
51    field::{decomposition::chip::P2RDecompositionChip, NativeChip, NativeGadget},
52    hash::sha512::{
53        types::{
54            AssignedMessageWord, AssignedPlain, AssignedPlainSpreaded, AssignedSpreaded,
55            CompressionState, LimbsOfA, LimbsOfE,
56        },
57        utils::{
58            expr_pow2_ip, expr_pow4_ip, gen_spread_table, get_even_and_odd_bits, negate_spreaded,
59            spread, spreaded_Sigma_0, spreaded_Sigma_1, spreaded_maj, spreaded_sigma_0,
60            spreaded_sigma_1, u64_in_be_limbs, MASK_EVN_128,
61        },
62    },
63    instructions::{assignments::AssignmentInstructions, DecompositionInstructions},
64    types::{AssignedByte, AssignedNative},
65    utils::{
66        util::{fe_to_u128, fe_to_u64, u128_to_fe, u64_to_fe},
67        ComposableChip,
68    },
69    CircuitField,
70};
71
72/// Number of advice columns used by the identities of the SHA512 chip.
73pub const NB_SHA512_ADVICE_COLS: usize = 8;
74
75/// Number of fixed columns used by the identities of the SHA512 chip.
76pub const NB_SHA512_FIXED_COLS: usize = 2;
77
78#[rustfmt::skip]
79const ROUND_CONSTANTS: [u64; 80] = [
80    0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc,
81    0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
82    0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
83    0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694,
84    0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
85    0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
86    0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4,
87    0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70,
88    0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
89    0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
90    0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30,
91    0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
92    0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8,
93    0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
94    0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
95    0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b,
96    0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
97    0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
98    0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
99    0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817,
100];
101
102#[rustfmt::skip]
103const IV: [u64; 8] = [
104    0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1,
105    0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179,
106];
107
108/// Tag for the even and odd 13x4-12 decompositions.
109enum Parity {
110    Evn,
111    Odd,
112}
113
114/// Plain-Spreaded lookup table.
115#[derive(Clone, Debug)]
116struct SpreadTable {
117    nbits_col: TableColumn,
118    plain_col: TableColumn,
119    sprdd_col: TableColumn,
120}
121
122/// Configuration of Sha512Chip.
123#[derive(Clone, Debug)]
124pub struct Sha512Config {
125    advice_cols: [Column<Advice>; NB_SHA512_ADVICE_COLS],
126    fixed_cols: [Column<Fixed>; NB_SHA512_FIXED_COLS],
127
128    q_lookup: Selector,
129    table: SpreadTable,
130
131    q_maj: Selector,
132    q_half_ch: Selector,
133    q_Sigma_0: Selector,
134    q_Sigma_1: Selector,
135    q_sigma_0: Selector,
136    q_sigma_1: Selector,
137
138    q_13x4_12: Selector,
139    q_13_12_5_6_13_13_2: Selector,
140    q_13_10_13_10_4_13_1: Selector,
141    q_3_13x3_3_11_1_1_5_1: Selector,
142    q_add_mod_2_64: Selector,
143}
144
145/// Chip for SHA512.
146#[derive(Clone, Debug)]
147pub struct Sha512Chip<F: CircuitField> {
148    config: Sha512Config,
149    pub(super) native_gadget: NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>,
150}
151
152impl<F: CircuitField> Chip<F> for Sha512Chip<F> {
153    type Config = Sha512Config;
154    type Loaded = ();
155
156    fn config(&self) -> &Self::Config {
157        &self.config
158    }
159
160    fn loaded(&self) -> &Self::Loaded {
161        &()
162    }
163}
164
165impl<F: CircuitField> ComposableChip<F> for Sha512Chip<F> {
166    type SharedResources = (
167        [Column<Advice>; NB_SHA512_ADVICE_COLS],
168        [Column<Fixed>; NB_SHA512_FIXED_COLS],
169    );
170
171    type InstructionDeps = NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>;
172
173    fn new(config: &Sha512Config, native_gadget: &Self::InstructionDeps) -> Self {
174        Self {
175            config: config.clone(),
176            native_gadget: native_gadget.clone(),
177        }
178    }
179
180    fn configure(
181        meta: &mut ConstraintSystem<F>,
182        shared_res: &Self::SharedResources,
183    ) -> Sha512Config {
184        let fixed_cols = shared_res.1;
185
186        // Columns A0 and A2 do not need to be copy-enabled.
187        // We have the convention that chips enable copy in a prefix of their shared
188        // advice columns. Thus we let A0 and A2 be the last two columns of the given
189        // shared resources.
190        let advice_cols = [6, 0, 7, 1, 2, 3, 4, 5].map(|i| shared_res.0[i]);
191        for (i, column) in advice_cols.iter().enumerate() {
192            if i != 0 && i != 2 {
193                meta.enable_equality(*column);
194            }
195        }
196
197        let q_lookup = meta.complex_selector();
198        let table = SpreadTable {
199            nbits_col: meta.lookup_table_column(),
200            plain_col: meta.lookup_table_column(),
201            sprdd_col: meta.lookup_table_column(),
202        };
203
204        let q_maj = meta.selector();
205        let q_half_ch = meta.selector();
206        let q_Sigma_0 = meta.selector();
207        let q_Sigma_1 = meta.selector();
208        let q_sigma_0 = meta.selector();
209        let q_sigma_1 = meta.selector();
210
211        let q_13x4_12 = meta.selector();
212        let q_13_12_5_6_13_13_2 = meta.selector();
213        let q_13_10_13_10_4_13_1 = meta.selector();
214        let q_3_13x3_3_11_1_1_5_1 = meta.selector();
215        let q_add_mod_2_64 = meta.selector();
216
217        (0..2).for_each(|idx| {
218            meta.lookup("plain-spreaded lookup", |meta| {
219                let q_lookup = meta.query_selector(q_lookup);
220
221                let nbits = meta.query_fixed(fixed_cols[idx], Rotation(0));
222                let plain = meta.query_advice(advice_cols[2 * idx], Rotation(0));
223                let sprdd = meta.query_advice(advice_cols[2 * idx + 1], Rotation(0));
224
225                vec![
226                    (q_lookup.clone() * nbits, table.nbits_col),
227                    (q_lookup.clone() * plain, table.plain_col),
228                    (q_lookup * sprdd, table.sprdd_col),
229                ]
230            });
231        });
232
233        meta.create_gate("Maj(A, B, C)", |meta| {
234            // See function `maj` for a description of the following layout.
235            let sA = meta.query_advice(advice_cols[5], Rotation(-1));
236            let sB = meta.query_advice(advice_cols[6], Rotation(-1));
237            let sC = meta.query_advice(advice_cols[5], Rotation(0));
238            let s_odd_13a = meta.query_advice(advice_cols[1], Rotation(-1));
239            let s_odd_13b = meta.query_advice(advice_cols[1], Rotation(0));
240            let s_odd_13c = meta.query_advice(advice_cols[1], Rotation(1));
241            let s_odd_13d = meta.query_advice(advice_cols[1], Rotation(2));
242            let s_odd_12 = meta.query_advice(advice_cols[1], Rotation(3));
243            let s_evn_13a = meta.query_advice(advice_cols[3], Rotation(-1));
244            let s_evn_13b = meta.query_advice(advice_cols[3], Rotation(0));
245            let s_evn_13c = meta.query_advice(advice_cols[3], Rotation(1));
246            let s_evn_13d = meta.query_advice(advice_cols[3], Rotation(2));
247            let s_evn_12 = meta.query_advice(advice_cols[3], Rotation(3));
248
249            let s_evn = expr_pow4_ip(
250                [51, 38, 25, 12, 0],
251                [&s_evn_13a, &s_evn_13b, &s_evn_13c, &s_evn_13d, &s_evn_12],
252            );
253            let s_odd = expr_pow4_ip(
254                [51, 38, 25, 12, 0],
255                [&s_odd_13a, &s_odd_13b, &s_odd_13c, &s_odd_13d, &s_odd_12],
256            );
257
258            let id = (sA + sB + sC) - (s_evn + Expression::from(2) * s_odd);
259
260            Constraints::with_selector(q_maj, vec![("Maj", id)])
261        });
262
263        meta.create_gate("half Ch(E, F, G)", |meta| {
264            // See function `ch` for a description of the following layout.
265            let sX = meta.query_advice(advice_cols[5], Rotation(-1));
266            let sY = meta.query_advice(advice_cols[6], Rotation(-1));
267            let s_odd_13a = meta.query_advice(advice_cols[1], Rotation(-1));
268            let s_odd_13b = meta.query_advice(advice_cols[1], Rotation(0));
269            let s_odd_13c = meta.query_advice(advice_cols[1], Rotation(1));
270            let s_odd_13d = meta.query_advice(advice_cols[1], Rotation(2));
271            let s_odd_12 = meta.query_advice(advice_cols[1], Rotation(3));
272            let s_evn_13a = meta.query_advice(advice_cols[3], Rotation(-1));
273            let s_evn_13b = meta.query_advice(advice_cols[3], Rotation(0));
274            let s_evn_13c = meta.query_advice(advice_cols[3], Rotation(1));
275            let s_evn_13d = meta.query_advice(advice_cols[3], Rotation(2));
276            let s_evn_12 = meta.query_advice(advice_cols[3], Rotation(3));
277            let summand_1 = meta.query_advice(advice_cols[4], Rotation(0));
278            let summand_2 = meta.query_advice(advice_cols[5], Rotation(0));
279            let sum = meta.query_advice(advice_cols[6], Rotation(0));
280
281            let s_evn = expr_pow4_ip(
282                [51, 38, 25, 12, 0],
283                [&s_evn_13a, &s_evn_13b, &s_evn_13c, &s_evn_13d, &s_evn_12],
284            );
285            let s_odd = expr_pow4_ip(
286                [51, 38, 25, 12, 0],
287                [&s_odd_13a, &s_odd_13b, &s_odd_13c, &s_odd_13d, &s_odd_12],
288            );
289
290            let sprdd_id = (sX + sY) - (s_evn + Expression::from(2) * s_odd);
291            let sum_id = (summand_1 + summand_2) - sum;
292
293            Constraints::with_selector(
294                q_half_ch,
295                vec![
296                    ("Half-Ch spreadded", sprdd_id),
297                    ("Half Ch sum (2 terms)", sum_id),
298                ],
299            )
300        });
301
302        meta.create_gate("Σ₀(A)", |meta| {
303            // See function `Sigma_0` for a description of the following layout.
304            let s13a = meta.query_advice(advice_cols[5], Rotation(-1));
305            let s12 = meta.query_advice(advice_cols[6], Rotation(-1));
306            let s05 = meta.query_advice(advice_cols[5], Rotation(0));
307            let s06 = meta.query_advice(advice_cols[6], Rotation(0));
308            let s13b = meta.query_advice(advice_cols[5], Rotation(1));
309            let s13c = meta.query_advice(advice_cols[6], Rotation(1));
310            let s02 = meta.query_advice(advice_cols[5], Rotation(2));
311            let s_evn_13a = meta.query_advice(advice_cols[1], Rotation(-1));
312            let s_evn_13b = meta.query_advice(advice_cols[1], Rotation(0));
313            let s_evn_13c = meta.query_advice(advice_cols[1], Rotation(1));
314            let s_evn_13d = meta.query_advice(advice_cols[1], Rotation(2));
315            let s_evn_12 = meta.query_advice(advice_cols[1], Rotation(3));
316            let s_odd_13a = meta.query_advice(advice_cols[3], Rotation(-1));
317            let s_odd_13b = meta.query_advice(advice_cols[3], Rotation(0));
318            let s_odd_13c = meta.query_advice(advice_cols[3], Rotation(1));
319            let s_odd_13d = meta.query_advice(advice_cols[3], Rotation(2));
320            let s_odd_12 = meta.query_advice(advice_cols[3], Rotation(3));
321
322            let s_1st_rot = expr_pow4_ip(
323                [51, 38, 36, 23, 11, 6, 0],
324                [&s13b, &s13c, &s02, &s13a, &s12, &s05, &s06],
325            );
326            let s_2nd_rot = expr_pow4_ip(
327                [58, 45, 32, 30, 17, 5, 0],
328                [&s06, &s13b, &s13c, &s02, &s13a, &s12, &s05],
329            );
330            let s_3rd_rot = expr_pow4_ip(
331                [59, 53, 40, 27, 25, 12, 0],
332                [&s05, &s06, &s13b, &s13c, &s02, &s13a, &s12],
333            );
334
335            let s_evn = expr_pow4_ip(
336                [51, 38, 25, 12, 0],
337                [&s_evn_13a, &s_evn_13b, &s_evn_13c, &s_evn_13d, &s_evn_12],
338            );
339            let s_odd = expr_pow4_ip(
340                [51, 38, 25, 12, 0],
341                [&s_odd_13a, &s_odd_13b, &s_odd_13c, &s_odd_13d, &s_odd_12],
342            );
343
344            let id = (s_1st_rot + s_2nd_rot + s_3rd_rot) - (s_evn + Expression::from(2) * s_odd);
345
346            Constraints::with_selector(q_Sigma_0, vec![("Sigma_0", id)])
347        });
348
349        meta.create_gate("Σ₁(E)", |meta| {
350            // See function `Sigma_1` for a description of the following layout.
351            let s13a = meta.query_advice(advice_cols[5], Rotation(-1));
352            let s10a = meta.query_advice(advice_cols[6], Rotation(-1));
353            let s13b = meta.query_advice(advice_cols[5], Rotation(0));
354            let s10b = meta.query_advice(advice_cols[6], Rotation(0));
355            let s04 = meta.query_advice(advice_cols[5], Rotation(1));
356            let s13c = meta.query_advice(advice_cols[6], Rotation(1));
357            let s01 = meta.query_advice(advice_cols[5], Rotation(2));
358            let s_evn_13a = meta.query_advice(advice_cols[1], Rotation(-1));
359            let s_evn_13b = meta.query_advice(advice_cols[1], Rotation(0));
360            let s_evn_13c = meta.query_advice(advice_cols[1], Rotation(1));
361            let s_evn_13d = meta.query_advice(advice_cols[1], Rotation(2));
362            let s_evn_12 = meta.query_advice(advice_cols[1], Rotation(3));
363            let s_odd_13a = meta.query_advice(advice_cols[3], Rotation(-1));
364            let s_odd_13b = meta.query_advice(advice_cols[3], Rotation(0));
365            let s_odd_13c = meta.query_advice(advice_cols[3], Rotation(1));
366            let s_odd_13d = meta.query_advice(advice_cols[3], Rotation(2));
367            let s_odd_12 = meta.query_advice(advice_cols[3], Rotation(3));
368
369            let s_1st_rot = expr_pow4_ip(
370                [51, 50, 37, 27, 14, 4, 0],
371                [&s13c, &s01, &s13a, &s10a, &s13b, &s10b, &s04],
372            );
373            let s_2nd_rot = expr_pow4_ip(
374                [60, 47, 46, 33, 23, 10, 0],
375                [&s04, &s13c, &s01, &s13a, &s10a, &s13b, &s10b],
376            );
377            let s_3rd_rot = expr_pow4_ip(
378                [51, 41, 37, 24, 23, 10, 0],
379                [&s13b, &s10b, &s04, &s13c, &s01, &s13a, &s10a],
380            );
381
382            let s_evn = expr_pow4_ip(
383                [51, 38, 25, 12, 0],
384                [&s_evn_13a, &s_evn_13b, &s_evn_13c, &s_evn_13d, &s_evn_12],
385            );
386            let s_odd = expr_pow4_ip(
387                [51, 38, 25, 12, 0],
388                [&s_odd_13a, &s_odd_13b, &s_odd_13c, &s_odd_13d, &s_odd_12],
389            );
390
391            let id = (s_1st_rot + s_2nd_rot + s_3rd_rot) - (s_evn + Expression::from(2) * s_odd);
392
393            Constraints::with_selector(q_Sigma_1, vec![("Sigma_1", id)])
394        });
395
396        meta.create_gate("σ₀(W)", |meta| {
397            // See function `sigma_0` for a description of the following layout.
398            let s03a = meta.query_advice(advice_cols[5], Rotation(-1));
399            let s13a = meta.query_advice(advice_cols[6], Rotation(-1));
400            let s13b = meta.query_advice(advice_cols[5], Rotation(0));
401            let s13c = meta.query_advice(advice_cols[6], Rotation(0));
402            let s03b = meta.query_advice(advice_cols[5], Rotation(1));
403            let s11 = meta.query_advice(advice_cols[6], Rotation(1));
404            let s01a = meta.query_advice(advice_cols[5], Rotation(2));
405            let s01b = meta.query_advice(advice_cols[6], Rotation(2));
406            let s05 = meta.query_advice(advice_cols[5], Rotation(3));
407            let s01c = meta.query_advice(advice_cols[6], Rotation(3));
408            let s_evn_13a = meta.query_advice(advice_cols[1], Rotation(-1));
409            let s_evn_13b = meta.query_advice(advice_cols[1], Rotation(0));
410            let s_evn_13c = meta.query_advice(advice_cols[1], Rotation(1));
411            let s_evn_13d = meta.query_advice(advice_cols[1], Rotation(2));
412            let s_evn_12 = meta.query_advice(advice_cols[1], Rotation(3));
413            let s_odd_13a = meta.query_advice(advice_cols[3], Rotation(-1));
414            let s_odd_13b = meta.query_advice(advice_cols[3], Rotation(0));
415            let s_odd_13c = meta.query_advice(advice_cols[3], Rotation(1));
416            let s_odd_13d = meta.query_advice(advice_cols[3], Rotation(2));
417            let s_odd_12 = meta.query_advice(advice_cols[3], Rotation(3));
418
419            let s_1st_shift = expr_pow4_ip(
420                [54, 41, 28, 15, 12, 1, 0],
421                [&s03a, &s13a, &s13b, &s13c, &s03b, &s11, &s01a],
422            );
423            let s_2nd_rot = expr_pow4_ip(
424                [63, 60, 47, 34, 21, 18, 7, 6, 5, 0],
425                [
426                    &s01c, &s03a, &s13a, &s13b, &s13c, &s03b, &s11, &s01a, &s01b, &s05,
427                ],
428            );
429            let s_3rd_rot = expr_pow4_ip(
430                [63, 62, 57, 56, 53, 40, 27, 14, 11, 0],
431                [
432                    &s01a, &s01b, &s05, &s01c, &s03a, &s13a, &s13b, &s13c, &s03b, &s11,
433                ],
434            );
435
436            let s_evn = expr_pow4_ip(
437                [51, 38, 25, 12, 0],
438                [&s_evn_13a, &s_evn_13b, &s_evn_13c, &s_evn_13d, &s_evn_12],
439            );
440            let s_odd = expr_pow4_ip(
441                [51, 38, 25, 12, 0],
442                [&s_odd_13a, &s_odd_13b, &s_odd_13c, &s_odd_13d, &s_odd_12],
443            );
444
445            let id = (s_1st_shift + s_2nd_rot + s_3rd_rot) - (s_evn + Expression::from(2) * s_odd);
446
447            Constraints::with_selector(q_sigma_0, vec![("sigma_0", id)])
448        });
449
450        meta.create_gate("σ₁(W)", |meta| {
451            // See function `sigma_1` for a description of the following layout.
452            let s03a = meta.query_advice(advice_cols[5], Rotation(-1));
453            let s13a = meta.query_advice(advice_cols[6], Rotation(-1));
454            let s13b = meta.query_advice(advice_cols[5], Rotation(0));
455            let s13c = meta.query_advice(advice_cols[6], Rotation(0));
456            let s03b = meta.query_advice(advice_cols[5], Rotation(1));
457            let s11 = meta.query_advice(advice_cols[6], Rotation(1));
458            let s01a = meta.query_advice(advice_cols[5], Rotation(2));
459            let s01b = meta.query_advice(advice_cols[6], Rotation(2));
460            let s05 = meta.query_advice(advice_cols[5], Rotation(3));
461            let s01c = meta.query_advice(advice_cols[6], Rotation(3));
462            let s_evn_13a = meta.query_advice(advice_cols[1], Rotation(-1));
463            let s_evn_13b = meta.query_advice(advice_cols[1], Rotation(0));
464            let s_evn_13c = meta.query_advice(advice_cols[1], Rotation(1));
465            let s_evn_13d = meta.query_advice(advice_cols[1], Rotation(2));
466            let s_evn_12 = meta.query_advice(advice_cols[1], Rotation(3));
467            let s_odd_13a = meta.query_advice(advice_cols[3], Rotation(-1));
468            let s_odd_13b = meta.query_advice(advice_cols[3], Rotation(0));
469            let s_odd_13c = meta.query_advice(advice_cols[3], Rotation(1));
470            let s_odd_13d = meta.query_advice(advice_cols[3], Rotation(2));
471            let s_odd_12 = meta.query_advice(advice_cols[3], Rotation(3));
472
473            let s_1st_shift = expr_pow4_ip(
474                [55, 42, 29, 16, 13, 2, 1, 0],
475                [&s03a, &s13a, &s13b, &s13c, &s03b, &s11, &s01a, &s01b],
476            );
477            let s_2nd_rot = expr_pow4_ip(
478                [53, 52, 51, 46, 45, 42, 29, 16, 3, 0],
479                [
480                    &s11, &s01a, &s01b, &s05, &s01c, &s03a, &s13a, &s13b, &s13c, &s03b,
481                ],
482            );
483            let s_3rd_rot = expr_pow4_ip(
484                [51, 38, 25, 22, 11, 10, 9, 4, 3, 0],
485                [
486                    &s13a, &s13b, &s13c, &s03b, &s11, &s01a, &s01b, &s05, &s01c, &s03a,
487                ],
488            );
489
490            let s_evn = expr_pow4_ip(
491                [51, 38, 25, 12, 0],
492                [&s_evn_13a, &s_evn_13b, &s_evn_13c, &s_evn_13d, &s_evn_12],
493            );
494            let s_odd = expr_pow4_ip(
495                [51, 38, 25, 12, 0],
496                [&s_odd_13a, &s_odd_13b, &s_odd_13c, &s_odd_13d, &s_odd_12],
497            );
498
499            let id = (s_1st_shift + s_2nd_rot + s_3rd_rot) - (s_evn + Expression::from(2) * s_odd);
500
501            Constraints::with_selector(q_sigma_1, vec![("sigma_1", id)])
502        });
503
504        meta.create_gate("13x4-12 decomposition", |meta| {
505            // See function `assign_sprdd_13x4_12` for a description of the following
506            // layout.
507            let p13a = meta.query_advice(advice_cols[0], Rotation(-1));
508            let p13b = meta.query_advice(advice_cols[0], Rotation(0));
509            let p13c = meta.query_advice(advice_cols[0], Rotation(1));
510            let p13d = meta.query_advice(advice_cols[0], Rotation(2));
511            let p12 = meta.query_advice(advice_cols[0], Rotation(3));
512            let output = meta.query_advice(advice_cols[4], Rotation(-1));
513
514            let id = expr_pow2_ip([51, 38, 25, 12, 0], [&p13a, &p13b, &p13c, &p13d, &p12]) - output;
515
516            Constraints::with_selector(q_13x4_12, vec![("13x4-12 decomposition", id)])
517        });
518
519        meta.create_gate("13-12-5-6-13-13-2 decomposition", |meta| {
520            // See function `prepare_A` for a description of the following layout.
521            let p13a = meta.query_advice(advice_cols[0], Rotation(-1));
522            let p12 = meta.query_advice(advice_cols[2], Rotation(-1));
523            let p05 = meta.query_advice(advice_cols[0], Rotation(0));
524            let p06 = meta.query_advice(advice_cols[2], Rotation(0));
525            let p13b = meta.query_advice(advice_cols[0], Rotation(1));
526            let p13c = meta.query_advice(advice_cols[2], Rotation(1));
527            let p02 = meta.query_advice(advice_cols[0], Rotation(2));
528            let s13a = meta.query_advice(advice_cols[1], Rotation(-1));
529            let s12 = meta.query_advice(advice_cols[3], Rotation(-1));
530            let s05 = meta.query_advice(advice_cols[1], Rotation(0));
531            let s06 = meta.query_advice(advice_cols[3], Rotation(0));
532            let s13b = meta.query_advice(advice_cols[1], Rotation(1));
533            let s13c = meta.query_advice(advice_cols[3], Rotation(1));
534            let s02 = meta.query_advice(advice_cols[1], Rotation(2));
535            let plain = meta.query_advice(advice_cols[4], Rotation(-1));
536            let sprdd = meta.query_advice(advice_cols[4], Rotation(0));
537
538            let plain_id = expr_pow2_ip(
539                [51, 39, 34, 28, 15, 2, 0],
540                [&p13a, &p12, &p05, &p06, &p13b, &p13c, &p02],
541            ) - plain;
542            let sprdd_id = expr_pow4_ip(
543                [51, 39, 34, 28, 15, 2, 0],
544                [&s13a, &s12, &s05, &s06, &s13b, &s13c, &s02],
545            ) - sprdd;
546
547            Constraints::with_selector(
548                q_13_12_5_6_13_13_2,
549                vec![
550                    ("13_12_5_6_13_13_2 decomposition plain", plain_id),
551                    ("13_12_5_6_13_13_2 decomposition sprdd", sprdd_id),
552                ],
553            )
554        });
555
556        meta.create_gate("13-10-13-10-4-13-1 decomposition", |meta| {
557            // See function `prepare_E` for a description of the following layout.
558            let p13a = meta.query_advice(advice_cols[0], Rotation(-1));
559            let p10a = meta.query_advice(advice_cols[2], Rotation(-1));
560            let p13b = meta.query_advice(advice_cols[0], Rotation(0));
561            let p10b = meta.query_advice(advice_cols[2], Rotation(0));
562            let p04 = meta.query_advice(advice_cols[0], Rotation(1));
563            let p13c = meta.query_advice(advice_cols[2], Rotation(1));
564            let p01 = meta.query_advice(advice_cols[0], Rotation(2));
565            let s13a = meta.query_advice(advice_cols[1], Rotation(-1));
566            let s10a = meta.query_advice(advice_cols[3], Rotation(-1));
567            let s13b = meta.query_advice(advice_cols[1], Rotation(0));
568            let s10b = meta.query_advice(advice_cols[3], Rotation(0));
569            let s04 = meta.query_advice(advice_cols[1], Rotation(1));
570            let s13c = meta.query_advice(advice_cols[3], Rotation(1));
571            let s01 = meta.query_advice(advice_cols[1], Rotation(2));
572            let plain = meta.query_advice(advice_cols[4], Rotation(-1));
573            let sprdd = meta.query_advice(advice_cols[4], Rotation(0));
574
575            let plain_id = expr_pow2_ip(
576                [51, 41, 28, 18, 14, 1, 0],
577                [&p13a, &p10a, &p13b, &p10b, &p04, &p13c, &p01],
578            ) - plain;
579            let sprdd_id = expr_pow4_ip(
580                [51, 41, 28, 18, 14, 1, 0],
581                [&s13a, &s10a, &s13b, &s10b, &s04, &s13c, &s01],
582            ) - sprdd;
583
584            Constraints::with_selector(
585                q_13_10_13_10_4_13_1,
586                vec![
587                    ("13_10_13_10_4_13_1 decomposition plain", plain_id),
588                    ("13_10_13_10_4_13_1 decomposition sprdd", sprdd_id),
589                ],
590            )
591        });
592
593        meta.create_gate("3-13x3-3-11-1-1-5-1 decomposition", |meta| {
594            // See function `prepare_message_word` for a description of the following
595            // layout.
596            let w03a = meta.query_advice(advice_cols[0], Rotation(-1));
597            let w13a = meta.query_advice(advice_cols[2], Rotation(-1));
598            let w13b = meta.query_advice(advice_cols[0], Rotation(0));
599            let w13c = meta.query_advice(advice_cols[2], Rotation(0));
600            let w03b = meta.query_advice(advice_cols[0], Rotation(1));
601            let w11 = meta.query_advice(advice_cols[2], Rotation(1));
602            let w01a = meta.query_advice(advice_cols[7], Rotation(-1));
603            let w01b = meta.query_advice(advice_cols[7], Rotation(0));
604            let w05 = meta.query_advice(advice_cols[0], Rotation(2));
605            let w01c = meta.query_advice(advice_cols[7], Rotation(1));
606            let plain = meta.query_advice(advice_cols[4], Rotation(-1));
607
608            let plain_id = expr_pow2_ip(
609                [61, 48, 35, 22, 19, 8, 7, 6, 1, 0],
610                [
611                    &w03a, &w13a, &w13b, &w13c, &w03b, &w11, &w01a, &w01b, &w05, &w01c,
612                ],
613            ) - plain;
614
615            // 1-bit check for W.01a, W.01b and W.01c
616            let w_01a_check = w01a.clone() * (w01a - Expression::from(1));
617            let w_01b_check = w01b.clone() * (w01b - Expression::from(1));
618            let w_01c_check = w01c.clone() * (w01c - Expression::from(1));
619
620            Constraints::with_selector(
621                q_3_13x3_3_11_1_1_5_1,
622                vec![
623                    ("q_3_13x3_3_11_1_1_5_1 decomposition ", plain_id),
624                    ("W.1a 1-bit check", w_01a_check),
625                    ("W.1b 1-bit check", w_01b_check),
626                    ("W.1c 1-bit check", w_01c_check),
627                ],
628            )
629        });
630
631        meta.create_gate("add mod 2^64", |meta| {
632            // See function `assign_add_mod_2_64` for a description of the following layout.
633            let s0 = meta.query_advice(advice_cols[5], Rotation(-1));
634            let s1 = meta.query_advice(advice_cols[6], Rotation(-1));
635            let s2 = meta.query_advice(advice_cols[5], Rotation(0));
636            let s3 = meta.query_advice(advice_cols[6], Rotation(0));
637            let s4 = meta.query_advice(advice_cols[4], Rotation(1));
638            let s5 = meta.query_advice(advice_cols[5], Rotation(1));
639            let s6 = meta.query_advice(advice_cols[6], Rotation(1));
640
641            let carry = meta.query_advice(advice_cols[2], Rotation(2));
642            let result = meta.query_advice(advice_cols[4], Rotation(-1));
643
644            let summands = [s0, s1, s2, s3, s4, s5, s6];
645            let lhs = summands.into_iter().reduce(|acc, x| acc + x).unwrap();
646            let rhs = result + carry * Expression::Constant(u128_to_fe(1u128 << 64));
647
648            Constraints::with_selector(q_add_mod_2_64, vec![("add_mod_2_64", lhs - rhs)])
649        });
650
651        Sha512Config {
652            advice_cols,
653            fixed_cols,
654
655            q_lookup,
656            table,
657
658            q_maj,
659            q_half_ch,
660            q_Sigma_0,
661            q_Sigma_1,
662            q_sigma_0,
663            q_sigma_1,
664            q_13x4_12,
665
666            q_13_12_5_6_13_13_2,
667            q_13_10_13_10_4_13_1,
668            q_3_13x3_3_11_1_1_5_1,
669            q_add_mod_2_64,
670        }
671    }
672
673    fn load(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
674        let SpreadTable {
675            nbits_col,
676            plain_col,
677            sprdd_col,
678        } = self.config().table;
679
680        layouter.assign_table(
681            || "spread table",
682            |mut table| {
683                for (index, triple) in gen_spread_table::<F>().enumerate() {
684                    table.assign_cell(|| "nbits", nbits_col, index, || Value::known(triple.0))?;
685                    table.assign_cell(|| "plain", plain_col, index, || Value::known(triple.1))?;
686                    table.assign_cell(|| "sprdd", sprdd_col, index, || Value::known(triple.2))?;
687                }
688                Ok(())
689            },
690        )
691    }
692}
693
694impl<F: CircuitField> Sha512Chip<F> {
695    /// In-circuit SHA512 computation, the protagonist of this chip.
696    pub(super) fn sha512(
697        &self,
698        layouter: &mut impl Layouter<F>,
699        input_bytes: &[AssignedByte<F>],
700    ) -> Result<[AssignedPlain<F, 64>; 8], Error> {
701        let mut state = CompressionState::<F>::fixed(layouter, &self.native_gadget, IV)?;
702
703        for block_bytes in self.pad(layouter, input_bytes)?.chunks(128) {
704            let block = self.block_from_bytes(layouter, block_bytes.try_into().unwrap())?;
705            let message_blocks = self.message_schedule(layouter, &block)?;
706            let mut compression_state = state.clone();
707            for i in 0..80 {
708                compression_state = self.compression_round(
709                    layouter,
710                    &compression_state,
711                    ROUND_CONSTANTS[i],
712                    &message_blocks[i],
713                )?;
714            }
715            state = state.add(self, layouter, &compression_state)?;
716        }
717
718        Ok(state.plain())
719    }
720
721    /// Pads the input byte array to be a multiple of 128 bytes (1024 bits).
722    fn pad(
723        &self,
724        layouter: &mut impl Layouter<F>,
725        bytes: &[AssignedByte<F>],
726    ) -> Result<Vec<AssignedByte<F>>, Error> {
727        let l = 8 * bytes.len();
728        let k = 1024 - (l + 129) % 1024;
729
730        let mut padded = bytes.to_vec();
731        padded.push(self.native_gadget.assign_fixed(layouter, 128u8)?); // k is always 7 mod 8
732        padded.extend(vec![self.native_gadget.assign_fixed(layouter, 0u8)?; k / 8]);
733        for byte in u128::to_be_bytes(l as u128) {
734            padded.push(self.native_gadget.assign_fixed(layouter, byte)?);
735        }
736
737        Ok(padded)
738    }
739
740    /// Given a byte array of exactly 128 bytes, this function converts it into
741    /// a block of 16 `AssignedPlain` values, each (64 bits) value representing
742    /// 8 bytes in big-endian.
743    pub(super) fn block_from_bytes(
744        &self,
745        layouter: &mut impl Layouter<F>,
746        bytes: &[AssignedByte<F>; 128],
747    ) -> Result<[AssignedPlain<F, 64>; 16], Error> {
748        Ok(bytes
749            .chunks(8)
750            .map(|word_bytes| {
751                self.native_gadget
752                    .assigned_from_be_bytes(layouter, word_bytes)
753                    .map(AssignedPlain)
754            })
755            .collect::<Result<Vec<_>, Error>>()?
756            .try_into()
757            .unwrap())
758    }
759
760    /// Takes a 1024-bits block, represented with 16 `AssignedPlain<64>` words.
761    /// Outputs the 80 `AssignedPlain<64>` words Wi from SHA512's message
762    /// schedule.
763    pub(super) fn message_schedule(
764        &self,
765        layouter: &mut impl Layouter<F>,
766        block: &[AssignedPlain<F, 64>; 16],
767    ) -> Result<[AssignedPlain<F, 64>; 80], Error> {
768        let message_word = self.prepare_message_word(layouter, &[block[0].clone()])?;
769        let mut message_words: [AssignedMessageWord<F>; 80] =
770            core::array::from_fn(|_| message_word.clone());
771
772        // The first 16 message words are got by decomposing the block words
773        // into 3_13x3_3_11_1_1_5_1 limbs directly.
774        for word_idx in 1..16 {
775            message_words[word_idx] =
776                self.prepare_message_word(layouter, &[block[word_idx].clone()])?;
777        }
778        // The remaining 64 message words are computed using the recurrence relation
779        // W.i = W.(i-16) + W.(i-7) + σ₀(W.(i-15)) + σ₁(W.(i-2))
780        // and decomposing into 3_13x3_3_11_1_1_5_1 limbs.
781        for word_idx in 16..80 {
782            let sigma0_w_i_minus_15 = &self.sigma_0(layouter, &message_words[word_idx - 15])?;
783            let sigma1_w_i_minus_2 = &self.sigma_1(layouter, &message_words[word_idx - 2])?;
784            message_words[word_idx] = self.prepare_message_word(
785                layouter,
786                &[
787                    message_words[word_idx - 16].combined_plain.clone(),
788                    message_words[word_idx - 7].combined_plain.clone(),
789                    sigma0_w_i_minus_15.clone(),
790                    sigma1_w_i_minus_2.clone(),
791                ],
792            )?;
793        }
794
795        Ok(message_words.map(|w| w.combined_plain))
796    }
797
798    /// A compression round. This is called 80 times per block.
799    pub(super) fn compression_round(
800        &self,
801        layouter: &mut impl Layouter<F>,
802        state: &CompressionState<F>,
803        round_k: u64,
804        round_w: &AssignedPlain<F, 64>,
805    ) -> Result<CompressionState<F>, Error> {
806        let round_k = AssignedPlain::<F, 64>::fixed(layouter, &self.native_gadget, round_k)?;
807
808        let Sigma_0_of_a = self.Sigma_0(layouter, &state.a)?;
809        let Maj_of_a_b_c = self.maj(
810            layouter,
811            &state.a.combined.spreaded,
812            &state.b.spreaded,
813            &state.c.spreaded,
814        )?;
815        let Sigma_1_of_e = self.Sigma_1(layouter, &state.e)?;
816        let Ch_of_e_f_g = self.ch(
817            layouter,
818            &state.e.combined.spreaded,
819            &state.f.spreaded,
820            &state.g.spreaded,
821        )?;
822
823        let next_a_summands = [
824            state.h.clone(),
825            Sigma_1_of_e.clone(),
826            Ch_of_e_f_g.clone(),
827            round_k.clone(),
828            round_w.clone(),
829            Sigma_0_of_a,
830            Maj_of_a_b_c,
831        ];
832
833        let next_e_summands = [
834            state.d.clone(),
835            state.h.clone(),
836            Sigma_1_of_e,
837            Ch_of_e_f_g,
838            round_k.clone(),
839            round_w.clone(),
840        ];
841
842        Ok(CompressionState {
843            a: self.prepare_A(layouter, &next_a_summands)?,
844            b: state.a.combined.clone(),
845            c: state.b.clone(),
846            d: state.c.plain.clone(),
847            e: self.prepare_E(layouter, &next_e_summands)?,
848            f: state.e.combined.clone(),
849            g: state.f.clone(),
850            h: state.g.plain.clone(),
851        })
852    }
853
854    /// Computes Maj(A, B, C).
855    fn maj(
856        &self,
857        layouter: &mut impl Layouter<F>,
858        sprdd_a: &AssignedSpreaded<F, 64>,
859        sprdd_b: &AssignedSpreaded<F, 64>,
860        sprdd_c: &AssignedSpreaded<F, 64>,
861    ) -> Result<AssignedPlain<F, 64>, Error> {
862        /*
863        We need to compute:
864            Maj(A, B, C) = (A ∧ B) ⊕ (A ∧ C) ⊕ (B ∧ C)
865
866        Note that the "majority" function (bit-wise most commont value) between A, B, C
867        is encoded in the odd bits of (~A + ~B + ~C). This is because, for every bit
868        position i, iff at least two out of three are 1, the sum A_i + B_i + C_i will
869        overflow, leaving a carry bit of 1 (the result of majority for that bit).
870
871        Maj can be encoded by
872
873        1) applying the plain-spreaded lookup on 13x4-12 limbs of Evn and Odd:
874             Evn: (Evn.13a, Evn.13b, Evn.13c, Evn.13d, Evn.12)
875             Odd: (Odd.13a, Odd.13b, Odd.13c, Odd.13d, Odd.12)
876
877        2) asserting the 13x4-12 decomposition identity for Odd:
878              2^51 * Odd.13a + 2^38 * Odd.13b + 2^25 * Odd.13c + 2^12 * Odd.13d + Odd.12
879            = Odd
880
881        3) asserting the major identity regarding the spreaded values:
882              (4^51 * ~Evn.13a + 4^38 * ~Evn.13b + 4^25 * ~Evn.13c + 4^12 * ~Evn.13d + ~Evn.12)
883          2 * (4^51 * ~Odd.13a + 4^38 * ~Odd.13b + 4^25 * ~Odd.13c + 4^12 * ~Odd.13d + ~Odd.12)
884             = ~A + ~B + ~C
885
886        The output is Odd.
887
888        We distribute these values in the PLONK table as follows.
889
890        | T0 |    A0    |     A1    | T1 |    A2    |     A3    |  A4  |   A5  |  A6   |
891        |----|----------|-----------|----|----------|-----------|------|-------|-------|
892        | 13 |  Odd.13a | ~Odd.13a  | 13 |  Evn.13a | ~Evn.13a  | Odd  |  ~A   |  ~B   |
893        | 13 |  Odd.13b | ~Odd.13b  | 13 |  Evn.13b | ~Evn.13b  |      |  ~C   |       | <- q_maj
894        | 13 |  Odd.13c | ~Odd.13c  | 13 |  Evn.13c | ~Evn.13c  |      |       |       |
895        | 13 |  Odd.13d | ~Odd.13d  | 13 |  Evn.13d | ~Evn.13d  |      |       |       |
896        | 12 |  Odd.12  | ~Odd.12   | 12 |  Evn.12  | ~Evn.12   |      |       |       |
897        */
898
899        let adv_cols = self.config().advice_cols;
900
901        layouter.assign_region(
902            || "Maj(A, B, C)",
903            |mut region| {
904                self.config().q_maj.enable(&mut region, 1)?;
905
906                sprdd_a.0.copy_advice(|| "~A", &mut region, adv_cols[5], 0)?;
907                sprdd_b.0.copy_advice(|| "~B", &mut region, adv_cols[6], 0)?;
908                sprdd_c.0.copy_advice(|| "~C", &mut region, adv_cols[5], 1)?;
909
910                let val_of_sprdd_forms: Value<[u128; 3]> = Value::from_iter([
911                    sprdd_a.0.value().copied().map(fe_to_u128),
912                    sprdd_b.0.value().copied().map(fe_to_u128),
913                    sprdd_c.0.value().copied().map(fe_to_u128),
914                ])
915                .map(|sprdd_forms: Vec<u128>| sprdd_forms.try_into().unwrap());
916
917                self.assign_sprdd_13x4_12(
918                    &mut region,
919                    val_of_sprdd_forms.map(spreaded_maj),
920                    Parity::Odd,
921                    0,
922                )
923            },
924        )
925    }
926
927    /// Computes Ch(E, F, G)
928    fn ch(
929        &self,
930        layouter: &mut impl Layouter<F>,
931        sprdd_E: &AssignedSpreaded<F, 64>,
932        sprdd_F: &AssignedSpreaded<F, 64>,
933        sprdd_G: &AssignedSpreaded<F, 64>,
934    ) -> Result<AssignedPlain<F, 64>, Error> {
935        /*
936        We need to compute:
937            Ch(E, F, G) = (E ∧ F) ⊕ (¬E ∧ G)
938
939        which can be achieved by
940
941        1) applying the plain-spreaded lookup on 13x4-12 limbs of Evn and Odd,
942           for both (~E + ~F) and (~(¬E) + ~G):
943             Evn_EF: (Evn_EF.13a, Evn_EF.13b, Evn_EF.13c, Evn_EF.13d, Evn_EF.12)
944             Odd_EF: (Odd_EF.13a, Odd_EF.13b, Odd_EF.13c, Odd_EF.13d, Odd_EF.12)
945
946             Evn_nEG: (Evn_nEG.13a, Evn_nEG.13b, Evn_nEG.13c, Evn_nEG.13d, Evn_nEG.12)
947             Odd_nEG: (Odd_nEG.13a, Odd_nEG.13b, Odd_nEG.13c, Odd_nEG.13d, Odd_nEG.12)
948
949        2) asserting the 13x4-12 decomposition identity for Odd_EF and Odd_nEG:
950              2^51 * Odd_EF.13a + 2^38 * Odd_EF.13b + 2^25 * Odd_EF.13c + 2^12 * Odd_EF.13d + Odd_EF.12
951            = Odd_EF
952
953              2^51 * Odd_nEG.13a + 2^38 * Odd_nEG.13b + 2^25 * Odd_nEG.13c + 2^12 * Odd_nEG.13d + Odd_nEG.12
954            = Odd_nEG
955
956        3) asserting the spreaded addition identity for (~E + ~F) and (~(¬E) + ~G):
957              (4^51 * ~Evn_EF.13a + 4^38 * ~Evn_EF.13b + 4^25 * ~Evn_EF.13c + 4^12 * ~Evn_EF.13d + ~Evn_EF.12)
958          2 * (4^51 * ~Odd_EF.13a + 4^38 * ~Odd_EF.13b + 4^25 * ~Odd_EF.13c + 4^12 * ~Odd_EF.13d + ~Odd_EF.12)
959             = ~E + ~F
960
961              (4^51 * ~Evn_nEG.13a + 4^38 * ~Evn_nEG.13b + 4^25 * ~Evn_nEG.13c + 4^12 * ~Evn_nEG.13d + ~Evn_nEG.12)
962          2 * (4^51 * ~Odd_nEG.13a + 4^38 * ~Odd_nEG.13b + 4^25 * ~Odd_nEG.13c + 4^12 * ~Odd_nEG.13d + ~Odd_nEG.12)
963             = ~(¬E) + ~G
964
965        4) asserting the following two addition identities:
966                     Ret = Odd_EF + Odd_nEG
967            MASK_EVN_128 = ~E + ~(¬E)
968
969        The output is Ret.
970
971        We distribute these values in the PLONK table as follows.
972
973        | T0 |      A0      |       A1      | T1 |       A2     |       A3      |     A4  |    A5   |      A6     |
974        |----|--------------|---------------|----|--------------|---------------|---------|---------|-------------|
975        | 13 |  Odd_EF.13a  |  ~Odd_EF.13a  | 13 |  Evn_EF.13a  |  ~Evn_EF.13a  | Odd_EF  |    ~E   |     ~F      |
976        | 13 |  Odd_EF.13b  |  ~Odd_EF.13b  | 13 |  Evn_EF.13b  |  ~Evn_EF.13b  | Odd_EF  | Odd_nEG |     Ret     | <- q_ch
977        | 13 |  Odd_EF.13c  |  ~Odd_EF.13c  | 13 |  Evn_EF.13c  |  ~Evn_EF.13c  |         |         |             |
978        | 13 |  Odd_EF.13d  |  ~Odd_EF.13d  | 13 |  Evn_EF.13d  |  ~Evn_EF.13d  |         |         |             |
979        | 12 |  Odd_EF.12   |  ~Odd_EF.12   | 12 |  Evn_EF.12   |  ~Evn_EF.12   |         |         |             |
980        | 13 |  Odd_nEF.13a |  ~Odd_nEF.13a | 13 |  Evn_nEF.13a |  ~Evn_nEF.13a | Odd_nEG |  ~(¬E)  |     ~G      |
981        | 13 |  Odd_nEF.13b |  ~Odd_nEF.13b | 13 |  Evn_nEF.13b |  ~Evn_nEF.13b |   ~E    |  ~(¬E)  |MASK_EVN_128 | <- q_ch
982        | 13 |  Odd_nEF.13c |  ~Odd_nEF.13c | 13 |  Evn_nEF.13c |  ~Evn_nEF.13c |         |         |             |
983        | 13 |  Odd_nEF.13d |  ~Odd_nEF.13d | 13 |  Evn_nEF.13d |  ~Evn_nEF.13d |         |         |             |
984        | 12 |  Odd_nEF.12  |  ~Odd_nEF.12  | 12 |  Evn_nEF.12  |  ~Evn_nEF.12  |         |         |             |
985        */
986
987        let adv_cols = self.config().advice_cols;
988
989        let sprdd_E_val = sprdd_E.0.value().copied().map(fe_to_u128);
990        let sprdd_F_val = sprdd_F.0.value().copied().map(fe_to_u128);
991        let sprdd_G_val = sprdd_G.0.value().copied().map(fe_to_u128);
992        let sprdd_nE_val = sprdd_E_val.map(negate_spreaded);
993
994        let EpF_val = sprdd_E_val + sprdd_F_val;
995        let nEpG_val = sprdd_nE_val + sprdd_G_val;
996        let sprdd_nE_val: Value<F> = sprdd_nE_val.map(u128_to_fe);
997
998        let mask_evn_128: AssignedNative<F> =
999            (self.native_gadget).assign_fixed(layouter, u128_to_fe(MASK_EVN_128))?;
1000
1001        layouter.assign_region(
1002            || "Ch(E, F, G)",
1003            |mut region| {
1004                self.config().q_half_ch.enable(&mut region, 1)?;
1005                self.config().q_half_ch.enable(&mut region, 6)?;
1006
1007                sprdd_E.0.copy_advice(|| "~E", &mut region, adv_cols[5], 0)?;
1008                sprdd_E.0.copy_advice(|| "~E", &mut region, adv_cols[4], 6)?;
1009
1010                sprdd_F.0.copy_advice(|| "~F", &mut region, adv_cols[6], 0)?;
1011                sprdd_G.0.copy_advice(|| "~G", &mut region, adv_cols[6], 5)?;
1012
1013                let sprdd_nE = region.assign_advice(|| "~(¬E)", adv_cols[5], 5, || sprdd_nE_val)?;
1014                sprdd_nE.copy_advice(|| "~(¬E)", &mut region, adv_cols[5], 6)?;
1015
1016                mask_evn_128.copy_advice(|| "MASK_EVN_128", &mut region, adv_cols[6], 6)?;
1017
1018                let odd_EF = self.assign_sprdd_13x4_12(&mut region, EpF_val, Parity::Odd, 0)?;
1019                odd_EF.0.copy_advice(|| "Odd_EF", &mut region, adv_cols[4], 1)?;
1020
1021                let odd_nEG = self.assign_sprdd_13x4_12(&mut region, nEpG_val, Parity::Odd, 5)?;
1022                odd_nEG.0.copy_advice(|| "Odd_nEG", &mut region, adv_cols[5], 1)?;
1023
1024                let ret_val = odd_EF.0.value().copied() + odd_nEG.0.value().copied();
1025                region
1026                    .assign_advice(|| "Ret", adv_cols[6], 1, || ret_val)
1027                    .map(AssignedPlain::<F, 64>)
1028            },
1029        )
1030    }
1031
1032    /// Computes Σ₀(A).
1033    fn Sigma_0(
1034        &self,
1035        layouter: &mut impl Layouter<F>,
1036        a: &LimbsOfA<F>,
1037    ) -> Result<AssignedPlain<F, 64>, Error> {
1038        /*
1039        Given
1040                    A:  ( A.13a || A.12 || A.05 || A.06 || A.13b || A.13c || A.02 )
1041
1042        We need to compute:
1043            A >>> 28 :  ( A.13b || A.13c || A.02  || A.13a || A.12  || A.05  || A.06 )
1044          ⊕ A >>> 34 :  ( A.06  || A.13b || A.13c || A.02  || A.13a || A.12  || A.05 )
1045          ⊕ A >>> 39 :  ( A.05  || A.06  || A.13b || A.13c || A.02  || A.13a || A.12 )
1046
1047        which can be achieved by
1048
1049        1) applying the plain-spreaded lookup on 13x4-12 limbs of Evn and Odd:
1050             Evn: (Evn.13a, Evn.13b, Evn.13c, Evn.13d, Evn.12)
1051             Odd: (Odd.13a, Odd.13b, Odd.13c, Odd.13d, Odd.12)
1052
1053        2) asserting the 13x4-12 decomposition identity for Evn:
1054              2^51 * Evn.13a + 2^38 * Evn.13b + 2^25 * Evn.13c + 2^12 * Evn.13d + Evn.12
1055            = Evn
1056
1057        3) asserting the Sigma_0 identity regarding the spreaded values:
1058              (4^51 * ~Evn.13a + 4^38 * ~Evn.13b + 4^25 * ~Evn.13c + 4^12 * ~Evn.13d + ~Evn.12) +
1059          2 * (4^51 * ~Odd.13a + 4^38 * ~Odd.13b + 4^25 * ~Odd.13c + 4^12 * ~Odd.13d + ~Odd.12)
1060             = 4^51 * ~A.13b + 4^38 * ~A.13c + 4^36 * ~A.02  + 4^23 * ~A.13a + 4^11 * ~A.12  + 4^6  * ~A.05  + ~A.06
1061             + 4^58 * ~A.06  + 4^45 * ~A.13b + 4^32 * ~A.13c + 4^30 * ~A.02  + 4^17 * ~A.13a + 4^5  * ~A.12  + ~A.05
1062             + 4^59 * ~A.05  + 4^53 * ~A.06  + 4^40 * ~A.13b + 4^27 * ~A.13c + 4^25 * ~A.02  + 4^12 * ~A.13a + ~A.12
1063
1064        The output is Evn.
1065
1066        We distribute these values in the PLONK table as follows.
1067
1068        | T0 |    A0    |     A1    | T1 |    A2    |     A3    |  A4  |    A5    |   A6   |
1069        |----|----------|-----------|----|----------|-----------|------|----------|--------|
1070        | 13 |  Evn.13a | ~Evn.13a  | 13 |  Odd.13a | ~Odd.13a  | Evn  |  ~A.13a  | ~A.12  |
1071        | 13 |  Evn.13b | ~Evn.13b  | 13 |  Odd.13b | ~Odd.13b  |      |  ~A.05   | ~A.06  | <- q_Sigma_0
1072        | 13 |  Evn.13c | ~Evn.13c  | 13 |  Odd.13c | ~Odd.13c  |      |  ~A.13b  | ~A.13c |
1073        | 13 |  Evn.13d | ~Evn.13d  | 13 |  Odd.13d | ~Odd.13d  |      |  ~A.02   |        |
1074        | 12 |  Evn.12  | ~Evn.12   | 12 |  Odd.12  | ~Odd.12   |      |          |        |
1075        */
1076
1077        let adv_cols = self.config().advice_cols;
1078
1079        layouter.assign_region(
1080            || "Σ₀(A)",
1081            |mut region| {
1082                self.config().q_Sigma_0.enable(&mut region, 1)?;
1083
1084                // Copy and assign the input.
1085                a.spreaded_limb_13a.0.copy_advice(|| "~A.13a", &mut region, adv_cols[5], 0)?;
1086                a.spreaded_limb_12.0.copy_advice(|| "~A.12", &mut region, adv_cols[6], 0)?;
1087                a.spreaded_limb_05.0.copy_advice(|| "~A.05", &mut region, adv_cols[5], 1)?;
1088                a.spreaded_limb_06.0.copy_advice(|| "~A.06", &mut region, adv_cols[6], 1)?;
1089                a.spreaded_limb_13b.0.copy_advice(|| "~A.13b", &mut region, adv_cols[5], 2)?;
1090                a.spreaded_limb_13c.0.copy_advice(|| "~A.13c", &mut region, adv_cols[6], 2)?;
1091                a.spreaded_limb_02.0.copy_advice(|| "~A.02", &mut region, adv_cols[5], 3)?;
1092
1093                // Compute the spreaded Σ₀(A) off-circuit, assign the 13x4-12 limbs
1094                // of its even and odd bits into the circuit, enable the q_13x4_12
1095                // selector for the even part and q_lookup selector for the
1096                // related rows, return the assigned 64 even bits.
1097                let val_of_sprdd_limbs: Value<[u128; 7]> = Value::from_iter([
1098                    a.spreaded_limb_13a.0.value().copied().map(fe_to_u128),
1099                    a.spreaded_limb_12.0.value().copied().map(fe_to_u128),
1100                    a.spreaded_limb_05.0.value().copied().map(fe_to_u128),
1101                    a.spreaded_limb_06.0.value().copied().map(fe_to_u128),
1102                    a.spreaded_limb_13b.0.value().copied().map(fe_to_u128),
1103                    a.spreaded_limb_13c.0.value().copied().map(fe_to_u128),
1104                    a.spreaded_limb_02.0.value().copied().map(fe_to_u128),
1105                ])
1106                .map(|limbs: Vec<u128>| limbs.try_into().unwrap());
1107
1108                self.assign_sprdd_13x4_12(
1109                    &mut region,
1110                    val_of_sprdd_limbs.map(spreaded_Sigma_0),
1111                    Parity::Evn,
1112                    0,
1113                )
1114            },
1115        )
1116    }
1117
1118    /// Computes Σ₁(E).
1119    fn Sigma_1(
1120        &self,
1121        layouter: &mut impl Layouter<F>,
1122        e: &LimbsOfE<F>,
1123    ) -> Result<AssignedPlain<F, 64>, Error> {
1124        /*
1125        Given
1126                    E:  ( E.13a || E.10a || E.13b || E.10b || E.04 || E.13c || E.01 )
1127
1128        We need to compute:
1129            E >>> 14 :  ( E.13c || E.01  || E.13a || E.10a || E.13b || E.10b || E.04  )
1130          ⊕ E >>> 18 :  ( E.04  || E.13c || E.01  || E.13a || E.10a || E.13b || E.10b )
1131          ⊕ E >>> 41 :  ( E.13b || E.10b || E.04  || E.13c || E.01  || E.13a || E.10a )
1132
1133        which can be achieved by
1134
1135        1) applying the plain-spreaded lookup on 13x4-12 limbs of Evn and Odd:
1136             Evn: (Evn.13a, Evn.13b, Evn.13c, Evn.13d, Evn.12)
1137             Odd: (Odd.13a, Odd.13b, Odd.13c, Odd.13d, Odd.12)
1138
1139        2) asserting the 13x4-12 decomposition identity for Evn:
1140              2^51 * Evn.13a + 2^38 * Evn.13b + 2^25 * Evn.13c + 2^12 * Evn.13d + Evn.12
1141            = Evn
1142
1143        3) asserting the Sigma_0 identity regarding the spreaded values:
1144              (4^51 * ~Evn.13a + 4^38 * ~Evn.13b + 4^25 * ~Evn.13c + 4^12 * ~Evn.13d + ~Evn.12) +
1145          2 * (4^51 * ~Odd.13a + 4^38 * ~Odd.13b + 4^25 * ~Odd.13c + 4^12 * ~Odd.13d + ~Odd.12)
1146             = 4^51 * ~E.13c + 4^50 * ~E.01  + 4^37 * ~E.13a + 4^27 * ~E.10a + 4^14 * ~E.13b + 4^4  * ~E.10b  + ~E.04
1147             + 4^60 * ~E.04  + 4^47 * ~E.13c + 4^46 * ~E.01  + 4^33 * ~E.13a + 4^23 * ~E.10a + 4^10 * ~E.13b  + ~E.10b
1148             + 4^51 * ~E.13b + 4^41 * ~E.10b + 4^37 * ~E.04  + 4^24 * ~E.13c + 4^23 * ~E.01  + 4^10 * ~E.13a  + ~E.10a
1149
1150        The output is Evn.
1151
1152        We distribute these values in the PLONK table as follows.
1153
1154        | T0 |    A0    |     A1    | T1 |    A2    |     A3    |  A4  |    A5    |   A6   |
1155        |----|----------|-----------|----|----------|-----------|------|----------|--------|
1156        | 13 |  Evn.13a | ~Evn.13a  | 13 |  Odd.13a | ~Odd.13a  | Evn  |  ~E.13a  | ~E.10a |
1157        | 13 |  Evn.13b | ~Evn.13b  | 13 |  Odd.13b | ~Odd.13b  |      |  ~E.13b  | ~E.10b | <- q_Sigma_1
1158        | 13 |  Evn.13c | ~Evn.13c  | 13 |  Odd.13c | ~Odd.13c  |      |  ~E.04   | ~E.13c |
1159        | 13 |  Evn.13d | ~Evn.13d  | 13 |  Odd.13d | ~Odd.13d  |      |  ~E.01   |        |
1160        | 12 |  Evn.12  | ~Evn.12   | 12 |  Odd.12  | ~Odd.12   |      |          |        |
1161        */
1162
1163        let adv_cols = self.config().advice_cols;
1164
1165        layouter.assign_region(
1166            || "Σ₁(E)",
1167            |mut region| {
1168                self.config().q_Sigma_1.enable(&mut region, 1)?;
1169
1170                // Copy and assign the input.
1171                e.spreaded_limb_13a.0.copy_advice(|| "~E.13a", &mut region, adv_cols[5], 0)?;
1172                e.spreaded_limb_10a.0.copy_advice(|| "~E.10a", &mut region, adv_cols[6], 0)?;
1173                e.spreaded_limb_13b.0.copy_advice(|| "~E.13b", &mut region, adv_cols[5], 1)?;
1174                e.spreaded_limb_10b.0.copy_advice(|| "~E.10b", &mut region, adv_cols[6], 1)?;
1175                e.spreaded_limb_04.0.copy_advice(|| "~E.04", &mut region, adv_cols[5], 2)?;
1176                e.spreaded_limb_13c.0.copy_advice(|| "~E.13c", &mut region, adv_cols[6], 2)?;
1177                e.spreaded_limb_01.0.copy_advice(|| "~E.01", &mut region, adv_cols[5], 3)?;
1178
1179                // Compute the spreaded Σ₁(E) off-circuit, assign the 13x4-12 limbs
1180                // of its even and odd bits into the circuit, enable the q_13x4_12
1181                // selector for the even part and q_lookup selector for the
1182                // related rows, return the assigned 64 even bits.
1183                let val_of_sprdd_limbs: Value<[u128; 7]> = Value::from_iter([
1184                    e.spreaded_limb_13a.0.value().copied().map(fe_to_u128),
1185                    e.spreaded_limb_10a.0.value().copied().map(fe_to_u128),
1186                    e.spreaded_limb_13b.0.value().copied().map(fe_to_u128),
1187                    e.spreaded_limb_10b.0.value().copied().map(fe_to_u128),
1188                    e.spreaded_limb_04.0.value().copied().map(fe_to_u128),
1189                    e.spreaded_limb_13c.0.value().copied().map(fe_to_u128),
1190                    e.spreaded_limb_01.0.value().copied().map(fe_to_u128),
1191                ])
1192                .map(|limbs: Vec<u128>| limbs.try_into().unwrap());
1193
1194                self.assign_sprdd_13x4_12(
1195                    &mut region,
1196                    val_of_sprdd_limbs.map(spreaded_Sigma_1),
1197                    Parity::Evn,
1198                    0,
1199                )
1200            },
1201        )
1202    }
1203
1204    /// Computes σ₀(W).
1205    fn sigma_0(
1206        &self,
1207        layouter: &mut impl Layouter<F>,
1208        w: &AssignedMessageWord<F>,
1209    ) -> Result<AssignedPlain<F, 64>, Error> {
1210        /*
1211        Given
1212                    W:  ( W.03a || W.13a || W.13b || W.13c || W.03b || W.11 || W.01a || W.01b || W.05 || W.01c )
1213
1214        We need to compute:
1215            W  >>  7 :  ( W.03a || W.13a || W.13b || W.13c || W.03b || W.11  || W.01a )
1216          ⊕ W >>>  1 :  ( W.01c || W.03a || W.13a || W.13b || W.13c || W.03b || W.11  || W.01a || W.01b || W.05 )
1217          ⊕ W >>>  8 :  ( W.01a || W.01b || W.05  || W.01c || W.03a || W.13a || W.13b || W.13c || W.03b || W.11 )
1218
1219        which can be achieved by
1220
1221        1) applying the plain-spreaded lookup on 13x4-12 limbs of Evn and Odd:
1222             Evn: (Evn.13a, Evn.13b, Evn.13c, Evn.13d, Evn.12)
1223             Odd: (Odd.13a, Odd.13b, Odd.13c, Odd.13d, Odd.12)
1224
1225        2) asserting the 13x4-12 decomposition identity for Evn:
1226              2^51 * Evn.13a + 2^38 * Evn.13b + 2^25 * Evn.13c + 2^12 * Evn.13d + Evn.12
1227            = Evn
1228
1229        3) asserting the Sigma_0 identity regarding the spreaded values:
1230              (4^51 * ~Evn.13a + 4^38 * ~Evn.13b + 4^25 * ~Evn.13c + 4^12 * ~Evn.13d + ~Evn.12) +
1231          2 * (4^51 * ~Odd.13a + 4^38 * ~Odd.13b + 4^25 * ~Odd.13c + 4^12 * ~Odd.13d + ~Odd.12)
1232             = 4^54 * ~W.03a + 4^41 * ~W.13a + 4^28 * ~W.13b + 4^15 * ~W.13c + 4^12 * ~W.03b + 4^1
1233             * ~W.11  + ~W.01a
1234             + 4^63 * ~W.01c + 4^60 * ~W.03a + 4^47 * ~W.13a + 4^34 * ~W.13b + 4^21 * ~W.13c + 4^18
1235             * ~W.03b + 4^7  * ~W.11 + 4^6  * ~W.01a + 4^5  * ~W.01b + ~W.05
1236             + 4^63 * ~W.05  + 4^62 * ~W.06  + 4^57 * ~W.13b + 4^56 * ~W.13c + 4^53 * ~W.02  + 4^40
1237             * ~W.13a + 4^27 * ~W.12 + 4^14 * ~W.12  + 4^11 * ~W.03b + ~W.11
1238
1239        The output is Evn.
1240
1241        We distribute these values in the PLONK table as follows.
1242
1243        | T0 |    A0    |     A1    | T1 |    A2    |     A3    |  A4  |    A5    |   A6   |
1244        |----|----------|-----------|----|----------|-----------|------|----------|--------|
1245        | 13 |  Evn.13a | ~Evn.13a  | 13 |  Odd.13a | ~Odd.13a  | Evn  |  ~W.03a  | ~W.13a |
1246        | 13 |  Evn.13b | ~Evn.13b  | 13 |  Odd.13b | ~Odd.13b  |      |  ~W.13b  | ~W.13c | <- q_sigma_0
1247        | 13 |  Evn.13c | ~Evn.13c  | 13 |  Odd.13c | ~Odd.13c  |      |  ~W.03b  | ~W.11  |
1248        | 13 |  Evn.13d | ~Evn.13d  | 13 |  Odd.13d | ~Odd.13d  |      |  ~W.01a  | ~W.01b |
1249        | 12 |  Evn.12  | ~Evn.12   | 12 |  Odd.12  | ~Odd.12   |      |  ~W.05   | ~W.01c |
1250        */
1251
1252        let adv_cols = self.config().advice_cols;
1253
1254        layouter.assign_region(
1255            || "σ₀(W)",
1256            |mut region| {
1257                self.config().q_sigma_0.enable(&mut region, 1)?;
1258
1259                // Copy and assign the input.
1260                w.spreaded_w_03a.0.copy_advice(|| "~W.03a", &mut region, adv_cols[5], 0)?;
1261                w.spreaded_w_13a.0.copy_advice(|| "~W.13a", &mut region, adv_cols[6], 0)?;
1262                w.spreaded_w_13b.0.copy_advice(|| "~W.13b", &mut region, adv_cols[5], 1)?;
1263                w.spreaded_w_13c.0.copy_advice(|| "~W.13c", &mut region, adv_cols[6], 1)?;
1264                w.spreaded_w_03b.0.copy_advice(|| "~W.03b", &mut region, adv_cols[5], 2)?;
1265                w.spreaded_w_11.0.copy_advice(|| "~W.11", &mut region, adv_cols[6], 2)?;
1266                w.spreaded_w_01a.0.copy_advice(|| "~W.01a", &mut region, adv_cols[5], 3)?;
1267                w.spreaded_w_01b.0.copy_advice(|| "~W.01b", &mut region, adv_cols[6], 3)?;
1268                w.spreaded_w_05.0.copy_advice(|| "~W.05", &mut region, adv_cols[5], 4)?;
1269                w.spreaded_w_01c.0.copy_advice(|| "~W.01c", &mut region, adv_cols[6], 4)?;
1270
1271                // Compute the spreaded σ₀(W) off-circuit, assign the 13x4-12 limbs
1272                // of its even and odd bits into the circuit, enable the q_13x4_12
1273                // selector for the even part and q_lookup selector for the
1274                // related rows, return the assigned 64 even bits.
1275                let val_of_sprdd_limbs: Value<[u128; 10]> = Value::from_iter([
1276                    w.spreaded_w_03a.0.value().copied().map(fe_to_u128),
1277                    w.spreaded_w_13a.0.value().copied().map(fe_to_u128),
1278                    w.spreaded_w_13b.0.value().copied().map(fe_to_u128),
1279                    w.spreaded_w_13c.0.value().copied().map(fe_to_u128),
1280                    w.spreaded_w_03b.0.value().copied().map(fe_to_u128),
1281                    w.spreaded_w_11.0.value().copied().map(fe_to_u128),
1282                    w.spreaded_w_01a.0.value().copied().map(fe_to_u128),
1283                    w.spreaded_w_01b.0.value().copied().map(fe_to_u128),
1284                    w.spreaded_w_05.0.value().copied().map(fe_to_u128),
1285                    w.spreaded_w_01c.0.value().copied().map(fe_to_u128),
1286                ])
1287                .map(|limbs: Vec<u128>| limbs.try_into().unwrap());
1288
1289                self.assign_sprdd_13x4_12(
1290                    &mut region,
1291                    val_of_sprdd_limbs.map(spreaded_sigma_0),
1292                    Parity::Evn,
1293                    0,
1294                )
1295            },
1296        )
1297    }
1298
1299    /// Computes σ₁(W).
1300    fn sigma_1(
1301        &self,
1302        layouter: &mut impl Layouter<F>,
1303        w: &AssignedMessageWord<F>,
1304    ) -> Result<AssignedPlain<F, 64>, Error> {
1305        /*
1306        Given
1307                    W:  ( W.03a || W.13a || W.13b || W.13c || W.03b || W.11 || W.01a || W.01b || W.05 || W.01c )
1308
1309        We need to compute:
1310            W  >>  6 :  ( W.03a || W.13a || W.13b || W.13c || W.03b || W.11  || W.01a || W.01b )
1311          ⊕ W >>> 19 :  ( W.11  || W.01a || W.01b || W.05  || W.01c || W.03a || W.13a || W.13b || W.13c || W.03b )
1312          ⊕ W >>> 61 :  ( W.13a || W.13b || W.13c || W.03b || W.11  || W.01a || W.01b || W.05  || W.01c || W.03a )
1313
1314        which can be achieved by
1315
1316        1) applying the plain-spreaded lookup on 13x4-12 limbs of Evn and Odd:
1317             Evn: (Evn.13a, Evn.13b, Evn.13c, Evn.13d, Evn.12)
1318             Odd: (Odd.13a, Odd.13b, Odd.13c, Odd.13d, Odd.12)
1319
1320        2) asserting the 13x4-12 decomposition identity for Evn:
1321              2^51 * Evn.13a + 2^38 * Evn.13b + 2^25 * Evn.13c + 2^12 * Evn.13d + Evn.12
1322            = Evn
1323
1324        3) asserting the Sigma_0 identity regarding the spreaded values:
1325              (4^51 * ~Evn.13a + 4^38 * ~Evn.13b + 4^25 * ~Evn.13c + 4^12 * ~Evn.13d + ~Evn.12) +
1326          2 * (4^51 * ~Odd.13a + 4^38 * ~Odd.13b + 4^25 * ~Odd.13c + 4^12 * ~Odd.13d + ~Odd.12)
1327             = 4^55 * ~W.03a + 4^42 * ~W.13a + 4^29 * ~W.13b + 4^16 * ~W.13c + 4^13 * ~W.03b + 4^2
1328             * ~W.11  + 4^1  * ~W.01a + ~W.01b
1329             + 4^53 * ~W.11  + 4^52 * ~W.01a + 4^51 * ~W.01b + 4^46 * ~W.05  + 4^45 * ~W.01c + 4^42
1330             * ~W.03a + 4^29 * ~W.13a + 4^16 * ~W.13b + 4^3 * ~W.13c + ~W.03b
1331             + 4^51 * ~W.13a + 4^38 * ~W.13b + 4^25 * ~W.13c + 4^22 * ~W.03b + 4^11 * ~W.11  + 4^10
1332             * ~W.01a + 4^9 * ~W.01b  + 4^4  * ~W.05  + 4^3 * ~W.01c + ~W.03a
1333
1334        The output is Evn.
1335
1336        We distribute these values in the PLONK table as follows.
1337
1338        | T0 |    A0    |     A1    | T1 |    A2    |     A3    |  A4  |    A5    |   A6   |
1339        |----|----------|-----------|----|----------|-----------|------|----------|--------|
1340        | 13 |  Evn.13a | ~Evn.13a  | 13 |  Odd.13a | ~Odd.13a  | Evn  |  ~W.03a  | ~W.13a |
1341        | 13 |  Evn.13b | ~Evn.13b  | 13 |  Odd.13b | ~Odd.13b  |      |  ~W.13b  | ~W.13c | <- q_sigma_1
1342        | 13 |  Evn.13c | ~Evn.13c  | 13 |  Odd.13c | ~Odd.13c  |      |  ~W.03b  | ~W.11  |
1343        | 13 |  Evn.13d | ~Evn.13d  | 13 |  Odd.13d | ~Odd.13d  |      |  ~W.01a  | ~W.01b |
1344        | 12 |  Evn.12  | ~Evn.12   | 12 |  Odd.12  | ~Odd.12   |      |  ~W.05   | ~W.01c |
1345        */
1346
1347        let adv_cols = self.config().advice_cols;
1348
1349        layouter.assign_region(
1350            || "σ₁(W)",
1351            |mut region| {
1352                self.config().q_sigma_1.enable(&mut region, 1)?;
1353
1354                // Copy and assign the input.
1355                w.spreaded_w_03a.0.copy_advice(|| "~W.03a", &mut region, adv_cols[5], 0)?;
1356                w.spreaded_w_13a.0.copy_advice(|| "~W.13a", &mut region, adv_cols[6], 0)?;
1357                w.spreaded_w_13b.0.copy_advice(|| "~W.13b", &mut region, adv_cols[5], 1)?;
1358                w.spreaded_w_13c.0.copy_advice(|| "~W.13c", &mut region, adv_cols[6], 1)?;
1359                w.spreaded_w_03b.0.copy_advice(|| "~W.03b", &mut region, adv_cols[5], 2)?;
1360                w.spreaded_w_11.0.copy_advice(|| "~W.11", &mut region, adv_cols[6], 2)?;
1361                w.spreaded_w_01a.0.copy_advice(|| "~W.01a", &mut region, adv_cols[5], 3)?;
1362                w.spreaded_w_01b.0.copy_advice(|| "~W.01b", &mut region, adv_cols[6], 3)?;
1363                w.spreaded_w_05.0.copy_advice(|| "~W.05", &mut region, adv_cols[5], 4)?;
1364                w.spreaded_w_01c.0.copy_advice(|| "~W.01c", &mut region, adv_cols[6], 4)?;
1365
1366                // Compute the spreaded σ₁(W) off-circuit, assign the 13x4-12 limbs
1367                // of its even and odd bits into the circuit, enable the q_13x4_12
1368                // selector for the even part and q_lookup selector for the
1369                // related rows, return the assigned 64 even bits.
1370                let val_of_sprdd_limbs: Value<[u128; 10]> = Value::from_iter([
1371                    w.spreaded_w_03a.0.value().copied().map(fe_to_u128),
1372                    w.spreaded_w_13a.0.value().copied().map(fe_to_u128),
1373                    w.spreaded_w_13b.0.value().copied().map(fe_to_u128),
1374                    w.spreaded_w_13c.0.value().copied().map(fe_to_u128),
1375                    w.spreaded_w_03b.0.value().copied().map(fe_to_u128),
1376                    w.spreaded_w_11.0.value().copied().map(fe_to_u128),
1377                    w.spreaded_w_01a.0.value().copied().map(fe_to_u128),
1378                    w.spreaded_w_01b.0.value().copied().map(fe_to_u128),
1379                    w.spreaded_w_05.0.value().copied().map(fe_to_u128),
1380                    w.spreaded_w_01c.0.value().copied().map(fe_to_u128),
1381                ])
1382                .map(|limbs: Vec<u128>| limbs.try_into().unwrap());
1383
1384                self.assign_sprdd_13x4_12(
1385                    &mut region,
1386                    val_of_sprdd_limbs.map(spreaded_sigma_1),
1387                    Parity::Evn,
1388                    0,
1389                )
1390            },
1391        )
1392    }
1393
1394    /// Given a u128, representing a spreaded value, this function fills a
1395    /// lookup table with the limbs of its even and odd parts (or vice versa)
1396    /// and returns the former or the latter, depending on the desired value
1397    /// `even_or_odd`.
1398    ///
1399    /// If `even_or_odd` = `Parity::Evn`:
1400    ///
1401    ///  | T0 |    A0   |    A1    | T1 |    A2   |    A3    |  A4 |
1402    ///  |----|---------|----------|----|---------|----------|-----|
1403    ///  | 13 | Evn.13a | ~Evn.13a | 13 | Odd.13a | ~Odd.13a | Evn |
1404    ///  | 13 | Evn.13b | ~Evn.13b | 13 | Odd.13b | ~Odd.13b |     | <- q_13x4_12
1405    ///  | 13 | Evn.13c | ~Evn.13c | 13 | Odd.13c | ~Odd.13c |     |
1406    ///  | 13 | Evn.13d | ~Evn.13d | 13 | Odd.13d | ~Odd.13d |     |
1407    ///  | 12 | Evn.12  | ~Evn.12  | 12 | Odd.12  | ~Odd.12  |     |
1408    ///
1409    /// and returns `Evn`.
1410    ///
1411    /// If `even_or_odd` = `Parity::Odd`:
1412    ///
1413    ///  | T0 |    A0   |    A1    | T1 |    A2   |    A3    |  A4 |
1414    ///  |----|---------|----------|----|---------|----------|-----|
1415    ///  | 13 | Odd.13a | ~Odd.13a | 13 | Evn.13a | ~Evn.13a | Odd |
1416    ///  | 13 | Odd.13b | ~Odd.13b | 13 | Evn.13b | ~Evn.13b |     | <- q_13x4_12
1417    ///  | 13 | Odd.13c | ~Odd.13c | 13 | Evn.13c | ~Evn.13c |     |
1418    ///  | 13 | Odd.13d | ~Odd.13d | 13 | Evn.13d | ~Evn.13d |     |
1419    ///  | 12 | Odd.12  | ~Odd.12  | 12 | Evn.12  | ~Evn.12  |     |
1420    ///
1421    /// and returns `Odd`.
1422    ///
1423    /// This function guarantees that the returned value is consistent with
1424    /// the values in the filled lookup table.
1425    fn assign_sprdd_13x4_12(
1426        &self,
1427        region: &mut Region<'_, F>,
1428        value: Value<u128>,
1429        even_or_odd: Parity,
1430        offset: usize,
1431    ) -> Result<AssignedPlain<F, 64>, Error> {
1432        self.config().q_13x4_12.enable(region, offset + 1)?;
1433
1434        let (evn_val, odd_val) = value.map(get_even_and_odd_bits).unzip();
1435
1436        let [evn0_13, evn1_13, evn2_13, evn3_13, evn4_12] =
1437            evn_val.map(|v| u64_in_be_limbs(v, [13, 13, 13, 13, 12])).transpose_array();
1438
1439        let [odd0_13, odd1_13, odd2_13, odd3_13, odd4_12] =
1440            odd_val.map(|v| u64_in_be_limbs(v, [13, 13, 13, 13, 12])).transpose_array();
1441
1442        let idx = match even_or_odd {
1443            Parity::Evn => 0,
1444            Parity::Odd => 1,
1445        };
1446
1447        self.assign_plain_and_spreaded::<13>(region, evn0_13, offset, idx)?;
1448        self.assign_plain_and_spreaded::<13>(region, evn1_13, offset + 1, idx)?;
1449        self.assign_plain_and_spreaded::<13>(region, evn2_13, offset + 2, idx)?;
1450        self.assign_plain_and_spreaded::<13>(region, evn3_13, offset + 3, idx)?;
1451        self.assign_plain_and_spreaded::<12>(region, evn4_12, offset + 4, idx)?;
1452
1453        self.assign_plain_and_spreaded::<13>(region, odd0_13, offset, 1 - idx)?;
1454        self.assign_plain_and_spreaded::<13>(region, odd1_13, offset + 1, 1 - idx)?;
1455        self.assign_plain_and_spreaded::<13>(region, odd2_13, offset + 2, 1 - idx)?;
1456        self.assign_plain_and_spreaded::<13>(region, odd3_13, offset + 3, 1 - idx)?;
1457        self.assign_plain_and_spreaded::<12>(region, odd4_12, offset + 4, 1 - idx)?;
1458
1459        let out_col = self.config().advice_cols[4];
1460        match even_or_odd {
1461            Parity::Evn => {
1462                region.assign_advice(|| "Evn", out_col, offset, || evn_val.map(u64_to_fe))
1463            }
1464            Parity::Odd => {
1465                region.assign_advice(|| "Odd", out_col, offset, || odd_val.map(u64_to_fe))
1466            }
1467        }
1468        .map(AssignedPlain)
1469    }
1470
1471    /// Given a slice of at most 7 `AssignedPlain` values, it adds them
1472    /// modulo 2^64 and decomposes the result (named A) into (big-endian)
1473    /// limbs of bit sizes 13, 12, 5, 6, 13, 13 and 2.
1474    ///
1475    /// This function returns the plain and spreaded forms, as well as
1476    /// the spreaded limbs of A.
1477    fn prepare_A(
1478        &self,
1479        layouter: &mut impl Layouter<F>,
1480        summands: &[AssignedPlain<F, 64>],
1481    ) -> Result<LimbsOfA<F>, Error> {
1482        /*
1483        Given assigned plain inputs S0, ..., S6 (if fewer inputs are given
1484        they will be completed up to length 7, padding with fixed zeros),
1485        let A be their sum modulo 2^64.
1486
1487        We use the following table distribution.
1488
1489        | T0 |    A0    |     A1    | T1 |    A2    |     A3    |   A4   |  A5  |  A6  |
1490        |----|----------|-----------|----|----------|-----------|--------|------|------|
1491        | 13 |   A.13a  |  ~A.13a   | 12 |   A.12   |   ~A.12   |   A    |  S0  |  S1  |
1492        | 05 |   A.05   |  ~A.05    | 06 |   A.06   |   ~A.06   |   ~A   |  S2  |  S3  | <- q_13_12_5_6_13_13_2
1493        | 13 |   A.13b  |  ~A.13b   | 13 |   A.13c  |   ~A.13c  |   S4   |  S5  |  S6  |
1494        | 02 |   A.02   |  ~A.02    | 03 |   carry  |   ~carry  |        |      |      |
1495
1496        Apart from the lookups, the following identities are checked via a
1497        custom gate with selector q_13_12_5_6_13_13_2:
1498
1499            A = 2^51 *  A.13a + 2^39 *  A.12  + 2^34 *  A.05 + 2^28 * A.06
1500              + 2^15 *  A.13b + 2^2  *  A.13c + A.02
1501           ~A = 4^51 * ~A.13a + 4^39 * ~A.12  + 4^34 * ~A.05 + 4^28 * ~A.06
1502              + 4^15 * ~A.13b + 4^2  * ~A.13c + ~A.02
1503
1504        and the following is checked with a custom gate with selector
1505        q_add_mod_2_32:
1506
1507            S0 + S1 + S2 + S3 + S4 + S5 + S6 = A + carry * 2^64
1508
1509        Note that A is implicitly being range-checked in [0, 2^64) via
1510        the lookup, and the carry is range-checked in [0, 8). This makes
1511        the gate complete and sound (the range on the carry does not need
1512        to be tight as long as it prevents overflows in the native field).
1513        */
1514
1515        let zero = AssignedPlain::<F, 64>::fixed(layouter, &self.native_gadget, 0)?;
1516
1517        layouter.assign_region(
1518            || "decompose A in 13-12-5-6-13-13-2 limbs",
1519            |mut region| {
1520                self.config().q_13_12_5_6_13_13_2.enable(&mut region, 1)?;
1521
1522                let a_plain = self.assign_add_mod_2_64(&mut region, summands, &zero)?;
1523                let a_sprdd_val =
1524                    a_plain.0.value().copied().map(fe_to_u64).map(spread).map(u128_to_fe);
1525                let a_sprdd = region
1526                    .assign_advice(|| "~A", self.config().advice_cols[4], 1, || a_sprdd_val)
1527                    .map(AssignedSpreaded)?;
1528
1529                let [val_13a, val_12, val_05, val_06, val_13b, val_13c, val_02] = a_plain
1530                    .0
1531                    .value()
1532                    .copied()
1533                    .map(|a| u64_in_be_limbs(fe_to_u64(a), [13, 12, 5, 6, 13, 13, 2]))
1534                    .transpose_array();
1535
1536                let limb_13a = self.assign_plain_and_spreaded(&mut region, val_13a, 0, 0)?;
1537                let limb_12 = self.assign_plain_and_spreaded(&mut region, val_12, 0, 1)?;
1538                let limb_05 = self.assign_plain_and_spreaded(&mut region, val_05, 1, 0)?;
1539                let limb_06 = self.assign_plain_and_spreaded(&mut region, val_06, 1, 1)?;
1540                let limb_13b = self.assign_plain_and_spreaded(&mut region, val_13b, 2, 0)?;
1541                let limb_13c = self.assign_plain_and_spreaded(&mut region, val_13c, 2, 1)?;
1542                let limb_02 = self.assign_plain_and_spreaded(&mut region, val_02, 3, 0)?;
1543
1544                Ok(LimbsOfA {
1545                    combined: AssignedPlainSpreaded {
1546                        plain: a_plain,
1547                        spreaded: a_sprdd,
1548                    },
1549                    spreaded_limb_13a: limb_13a.spreaded,
1550                    spreaded_limb_12: limb_12.spreaded,
1551                    spreaded_limb_05: limb_05.spreaded,
1552                    spreaded_limb_06: limb_06.spreaded,
1553                    spreaded_limb_13b: limb_13b.spreaded,
1554                    spreaded_limb_13c: limb_13c.spreaded,
1555                    spreaded_limb_02: limb_02.spreaded,
1556                })
1557            },
1558        )
1559    }
1560
1561    /// Given a slice of at most 7 `AssignedPlain` values, it adds them
1562    /// modulo 2^64 and decomposes the result (named E) into (big-endian)
1563    /// limbs of bit sizes 13, 10, 13, 10, 4, 13 and 1.
1564    ///
1565    /// This function returns the plain and spreaded forms, as well as
1566    /// the spreaded limbs of E.
1567    fn prepare_E(
1568        &self,
1569        layouter: &mut impl Layouter<F>,
1570        summands: &[AssignedPlain<F, 64>],
1571    ) -> Result<LimbsOfE<F>, Error> {
1572        /*
1573        Given assigned plain inputs S0, ..., S6 (if fewer inputs are given
1574        they will be completed up to length 7, padding with fixed zeros),
1575        let E be their sum modulo 2^64.
1576
1577        We use the following table distribution.
1578
1579        | T0 |    A0    |     A1    | T1 |    A2    |     A3    |   A4   |  A5  |  A6  |
1580        |----|----------|-----------|----|----------|-----------|--------|------|------|
1581        | 13 |   E.13a  |  ~E.13a   | 10 |   E.10a  |   ~E.10a  |   E    |  S0  |  S1  |
1582        | 13 |   E.13b  |  ~E.13b   | 10 |   E.10b  |   ~E.10b  |   ~E   |  S2  |  S3  | <- q_13_10_13_10_4_13_1
1583        | 04 |   E.04   |  ~E.04    | 13 |   E.13c  |   ~E.13c  |   S4   |  S5  |  S6  |
1584        | 01 |   E.01   |  ~E.01    | 03 |   carry  |   ~carry  |        |      |      |
1585
1586        Apart from the lookups, the following identities are checked via a
1587        custom gate with selector q_13_10_13_10_4_13_1:
1588
1589            E = 2^51 *  E.13a + 2^41 *  E.10a + 2^28  *  E.13b + 2^18 * E.10b
1590              + 2^14 *  E.04  + 2^1  *  E.13c + E.01
1591           ~E = 4^51 * ~E.13a + 4^41 * ~E.10a + 4^28  * ~E.13b + 4^18 * ~E.10b
1592              + 4^14 * ~E.04  + 4^1  * ~E.13c + ~E.01
1593
1594        and the following is checked with a custom gate with selector
1595        q_add_mod_2_64:
1596
1597            S0 + S1 + S2 + S3 + S4 + S5 + S6 = E + carry * 2^64
1598
1599        Note that E is implicitly being range-checked in [0, 2^64) via
1600        the lookup, and the carry is range-checked in [0, 8). This makes
1601        the gate complete and sound (the range on the carry does not need
1602        to be tight as long as it prevents overflows in the native field).
1603        */
1604
1605        let zero = AssignedPlain::<F, 64>::fixed(layouter, &self.native_gadget, 0)?;
1606
1607        layouter.assign_region(
1608            || "decompose E in 13-10-13-10-4-13-1 limbs",
1609            |mut region| {
1610                self.config().q_13_10_13_10_4_13_1.enable(&mut region, 1)?;
1611
1612                let e_plain = self.assign_add_mod_2_64(&mut region, summands, &zero)?;
1613                let e_sprdd_val =
1614                    e_plain.0.value().copied().map(fe_to_u64).map(spread).map(u128_to_fe);
1615                let e_sprdd = region
1616                    .assign_advice(|| "~E", self.config().advice_cols[4], 1, || e_sprdd_val)
1617                    .map(AssignedSpreaded)?;
1618
1619                let [val_13a, val_10a, val_13b, val_10b, val_04, val_13c, val_01] = e_plain
1620                    .0
1621                    .value()
1622                    .copied()
1623                    .map(|e| u64_in_be_limbs(fe_to_u64(e), [13, 10, 13, 10, 4, 13, 1]))
1624                    .transpose_array();
1625
1626                let limb_13a = self.assign_plain_and_spreaded(&mut region, val_13a, 0, 0)?;
1627                let limb_10a = self.assign_plain_and_spreaded(&mut region, val_10a, 0, 1)?;
1628                let limb_13b = self.assign_plain_and_spreaded(&mut region, val_13b, 1, 0)?;
1629                let limb_10b = self.assign_plain_and_spreaded(&mut region, val_10b, 1, 1)?;
1630                let limb_04 = self.assign_plain_and_spreaded(&mut region, val_04, 2, 0)?;
1631                let limb_13c = self.assign_plain_and_spreaded(&mut region, val_13c, 2, 1)?;
1632                let limb_01 = self.assign_plain_and_spreaded(&mut region, val_01, 3, 0)?;
1633
1634                Ok(LimbsOfE {
1635                    combined: AssignedPlainSpreaded {
1636                        plain: e_plain,
1637                        spreaded: e_sprdd,
1638                    },
1639                    spreaded_limb_13a: limb_13a.spreaded,
1640                    spreaded_limb_10a: limb_10a.spreaded,
1641                    spreaded_limb_13b: limb_13b.spreaded,
1642                    spreaded_limb_10b: limb_10b.spreaded,
1643                    spreaded_limb_04: limb_04.spreaded,
1644                    spreaded_limb_13c: limb_13c.spreaded,
1645                    spreaded_limb_01: limb_01.spreaded,
1646                })
1647            },
1648        )
1649    }
1650
1651    /// Given a slice of at most 7 `AssignedPlain` values, this function adds
1652    /// them modulo 2^64 and decomposes the result (named W_i) into (big-endian)
1653    /// limbs of bit sizes 3, 13, 13, 13, 3, 11, 1, 1, 5 and 1.
1654    fn prepare_message_word(
1655        &self,
1656        layouter: &mut impl Layouter<F>,
1657        summands: &[AssignedPlain<F, 64>],
1658    ) -> Result<AssignedMessageWord<F>, Error> {
1659        /*
1660        Given assigned plain inputs S0, ..., S6 (if fewer inputs are given
1661        they will be completed up to length 7, padding with fixed zeros),
1662        and computes W.i as their sum modulo 2^64.
1663
1664        We use the following table distribution.
1665
1666        | T0 |    A0    |     A1    | T1 |    A2    |     A3    |    A4   |  A5  |  A6  |  A.7  |
1667        |----|----------|-----------|----|----------|-----------|---------|------|------|-------|
1668        | 03 |   W.03a  |  ~W.03a   | 13 |   W.13a  |  ~W.13a   |  W.i    |  S0  |  S1  | W.01a |
1669        | 13 |   W.13b  |  ~W.13b   | 13 |   W.13c  |  ~W.13c   |         |  S2  |  S3  | W.01b | <- q_3_13x3_3_11_1_1_5_1
1670        | 03 |   W.03b  |  ~W.03b   | 11 |   W.11   |  ~W.11    |   S4    |  S5  |  S6  | W.01c |
1671        | 05 |   W.05   |  ~W.05    | 03 |   carry  |  ~carry   |         |      |      |       |
1672
1673        Apart from the lookups, the following identities are checked via a
1674        custom gate with selector q_3_13x3_3_11_1_1_5_1:
1675
1676          W.i =   2^61 * W.03a + 2^48 * W.13a + 2^35 * W.13b + 2^22 * W.13c
1677                + 2^19 * W.03b + 2^8  * W.11  + 2^7  * W.01a + 2^6  * W.01b
1678                + 2^1 * W.05 + W.01c
1679
1680          W.01a * (W.01a - 1) = 0
1681          W.01b * (W.01b - 1) = 0
1682          W.01c * (W.01c - 1) = 0
1683
1684        and the following is checked with a custom gate with selector
1685        q_add_mod_2_32:
1686
1687          S0 + S1 + S2 + S3 + S4 + S5 + S6 = W.i + carry * 2^64
1688
1689        Note that W.i is implicitly being range-checked in [0, 2^64) via
1690        the lookup, and the carry is range-checked in [0, 8). This makes
1691        the gate complete and sound (the range on the carry does not need
1692        to be tight as long as it prevents overflows in the native field).
1693        */
1694
1695        let zero = AssignedPlain::<F, 64>::fixed(layouter, &self.native_gadget, 0)?;
1696
1697        layouter.assign_region(
1698            || "prepare message word",
1699            |mut region| {
1700                self.config().q_3_13x3_3_11_1_1_5_1.enable(&mut region, 1)?;
1701
1702                let w_i_plain = self.assign_add_mod_2_64(&mut region, summands, &zero)?;
1703
1704                let [val_03a, val_13a, val_13b, val_13c, val_03b, val_11, val_01a, val_01b, val_05, val_01c] =
1705                    w_i_plain.0.value().copied()
1706                        .map(|w| u64_in_be_limbs(fe_to_u64(w), [3, 13, 13, 13, 3, 11, 1, 1, 5, 1]))
1707                        .transpose_array();
1708                let limb_03a = self.assign_plain_and_spreaded(&mut region, val_03a, 0, 0)?;
1709                let limb_13a = self.assign_plain_and_spreaded(&mut region, val_13a, 0, 1)?;
1710                let limb_13b = self.assign_plain_and_spreaded(&mut region, val_13b, 1, 0)?;
1711                let limb_13c = self.assign_plain_and_spreaded(&mut region, val_13c, 1, 1)?;
1712                let limb_03b = self.assign_plain_and_spreaded(&mut region, val_03b, 2, 0)?;
1713                let limb_11 = self.assign_plain_and_spreaded(&mut region, val_11, 2, 1)?;
1714                let limb_05 = self.assign_plain_and_spreaded(&mut region, val_05, 3, 0)?;
1715
1716                // The spreaded forms of 1-bit values W.01a, W.01b and W.01c equal themselves.
1717                let col = self.config().advice_cols[7];
1718                let limb_01a = region.assign_advice(|| "W.01a", col, 0, || val_01a.map(u64_to_fe))?;
1719                let limb_01b = region.assign_advice(|| "W.01b", col, 1, || val_01b.map(u64_to_fe))?;
1720                let limb_01c = region.assign_advice(|| "W.01c", col, 2, || val_01c.map(u64_to_fe))?;
1721
1722                Ok(AssignedMessageWord {
1723                    combined_plain: w_i_plain,
1724                    spreaded_w_03a: limb_03a.spreaded,
1725                    spreaded_w_13a: limb_13a.spreaded,
1726                    spreaded_w_13b: limb_13b.spreaded,
1727                    spreaded_w_13c: limb_13c.spreaded,
1728                    spreaded_w_03b: limb_03b.spreaded,
1729                    spreaded_w_11: limb_11.spreaded,
1730                    spreaded_w_01a: AssignedSpreaded(limb_01a),
1731                    spreaded_w_01b: AssignedSpreaded(limb_01b),
1732                    spreaded_w_05: limb_05.spreaded,
1733                    spreaded_w_01c: AssignedSpreaded(limb_01c),
1734                })
1735            },
1736        )
1737    }
1738
1739    /// Given a plain u64 value, supposedly in the range [0, 2^L), assigns it
1740    /// in plain and spreaded form, returning an `AssignedPlainSpreaded<F, L>`.
1741    ///
1742    /// The assigned values are guaranteed to be well-formed and consistent
1743    /// via a lookup check at the specified offset.
1744    ///
1745    /// Note that we have two parallel lookup arguments. The caller must
1746    /// choose which of the two is used via the `lookup_idx`.
1747    /// If `lookup_idx = 0`, the lookup on columns (T0, A0, A1) will be used.
1748    /// If `lookup_idx = 1`, the lookup on columns (T1, A2, A3) will be used.
1749    ///
1750    /// # Unsatisfiable
1751    ///
1752    /// If the given value is not in the range [0, 2^L), the circuit will become
1753    /// unsatisfiable.
1754    fn assign_plain_and_spreaded<const L: usize>(
1755        &self,
1756        region: &mut Region<'_, F>,
1757        plain_val: Value<u64>,
1758        offset: usize,
1759        lookup_idx: usize,
1760    ) -> Result<AssignedPlainSpreaded<F, L>, Error> {
1761        self.config().q_lookup.enable(region, offset)?;
1762
1763        let nbits_col = self.config().fixed_cols[lookup_idx]; // 0 or 1
1764        let plain_col = self.config().advice_cols[2 * lookup_idx]; // 0 or 2
1765        let sprdd_col = self.config().advice_cols[2 * lookup_idx + 1]; // 1 or 3
1766
1767        let nbits_val = Value::known(F::from(L as u64));
1768        let sprdd_val = plain_val.map(spread).map(u128_to_fe);
1769        let plain_val = plain_val.map(u64_to_fe);
1770
1771        region.assign_fixed(|| "nbits", nbits_col, offset, || nbits_val)?;
1772        let plain = region.assign_advice(|| "plain", plain_col, offset, || plain_val)?;
1773        let spreaded = region.assign_advice(|| "sprdd", sprdd_col, offset, || sprdd_val)?;
1774
1775        Ok(AssignedPlainSpreaded {
1776            plain: AssignedPlain(plain),
1777            spreaded: AssignedSpreaded(spreaded),
1778        })
1779    }
1780
1781    /// Given a slice of at most 7 `AssignedPlain` values, this function adds
1782    /// them modulo 2^64.
1783    ///
1784    /// The `zero` argument is supposed to contain a fixed assigned plain
1785    /// containing value 0, this is not enforced in this function, it is the
1786    /// responsibility of the caller to do so.
1787    ///
1788    /// # Panics
1789    ///
1790    /// If the more than 7 summands are provided.
1791    fn assign_add_mod_2_64(
1792        &self,
1793        region: &mut Region<'_, F>,
1794        summands: &[AssignedPlain<F, 64>],
1795        zero: &AssignedPlain<F, 64>,
1796    ) -> Result<AssignedPlain<F, 64>, Error> {
1797        /*
1798        We distribute values in the PLONK table as follows.
1799
1800        | T1 |   A2  |   A3   |     A4    | A5 | A6 |
1801        |----|-------|--------|-----------|----|----|
1802        |    |       |        | sum_plain | S0 | S1 |
1803        |    |       |        |           | S2 | S3 | <- q_add_mod_2_64
1804        |    |       |        |     S4    | S5 | S6 |
1805        |  3 | carry | ~carry |           |    |    |
1806
1807        We enforce S0 + S1 + S2 + S3 + S4 + S5 + S6 = sum_plain + carry * 2^64.
1808        */
1809
1810        assert!(summands.len() <= 7);
1811
1812        self.config().q_add_mod_2_64.enable(region, 1)?;
1813        let adv_cols = self.config().advice_cols;
1814
1815        let mut summands = summands.to_vec();
1816        summands.resize(7, zero.clone());
1817
1818        let (carry_val, sum_val): (Value<u64>, Value<F>) =
1819            Value::<Vec<F>>::from_iter(summands.iter().map(|s| s.0.value().copied()))
1820                .map(|v| v.into_iter().map(fe_to_u128).sum())
1821                .map(|s: u128| s.div_rem(&(1 << 64)))
1822                .map(|(carry, r)| (carry as u64, u128_to_fe(r)))
1823                .unzip();
1824
1825        summands[0].0.copy_advice(|| "S0", region, adv_cols[5], 0)?;
1826        summands[1].0.copy_advice(|| "S1", region, adv_cols[6], 0)?;
1827        summands[2].0.copy_advice(|| "S2", region, adv_cols[5], 1)?;
1828        summands[3].0.copy_advice(|| "S3", region, adv_cols[6], 1)?;
1829        summands[4].0.copy_advice(|| "S4", region, adv_cols[4], 2)?;
1830        summands[5].0.copy_advice(|| "S5", region, adv_cols[5], 2)?;
1831        summands[6].0.copy_advice(|| "S6", region, adv_cols[6], 2)?;
1832        let _carry: AssignedPlainSpreaded<F, 3> =
1833            self.assign_plain_and_spreaded(region, carry_val, 3, 1)?;
1834        region.assign_advice(|| "sum", adv_cols[4], 0, || sum_val).map(AssignedPlain)
1835    }
1836}
1837
1838impl<F: CircuitField> CompressionState<F> {
1839    /// Adds pair-wise (modulo 2^64) the fields of two compression states.
1840    pub fn add(
1841        &self,
1842        sha512_chip: &Sha512Chip<F>,
1843        layouter: &mut impl Layouter<F>,
1844        other: &Self,
1845    ) -> Result<Self, Error> {
1846        let a = sha512_chip.prepare_A(layouter, &[self.a.plain(), other.a.plain()])?;
1847        let b = sha512_chip.prepare_A(layouter, &[self.b.plain.clone(), other.b.plain.clone()])?;
1848        let c = sha512_chip.prepare_A(layouter, &[self.c.plain.clone(), other.c.plain.clone()])?;
1849        let d = sha512_chip.prepare_A(layouter, &[self.d.clone(), other.d.clone()])?;
1850        // NB: d can be optimized and do it in a single row without `prepare_A`.
1851
1852        let e = sha512_chip.prepare_E(layouter, &[self.e.plain(), other.e.plain()])?;
1853        let f = sha512_chip.prepare_E(layouter, &[self.f.plain.clone(), other.f.plain.clone()])?;
1854        let g = sha512_chip.prepare_E(layouter, &[self.g.plain.clone(), other.g.plain.clone()])?;
1855        let h = sha512_chip.prepare_E(layouter, &[self.h.clone(), other.h.clone()])?;
1856        // NB: h can be optimized and do it in a single row without `prepare_E`.
1857
1858        Ok(Self {
1859            a,
1860            b: b.combined,
1861            c: c.combined,
1862            d: d.combined.plain,
1863            e,
1864            f: f.combined,
1865            g: g.combined,
1866            h: h.combined.plain,
1867        })
1868    }
1869}
1870
1871#[cfg(any(test, feature = "testing"))]
1872use midnight_proofs::plonk::Instance;
1873
1874#[cfg(any(test, feature = "testing"))]
1875use crate::{field::decomposition::chip::P2RDecompositionConfig, testing_utils::FromScratch};
1876
1877#[cfg(any(test, feature = "testing"))]
1878impl<F: CircuitField> FromScratch<F> for Sha512Chip<F> {
1879    type Config = (Sha512Config, P2RDecompositionConfig);
1880
1881    fn new_from_scratch(config: &Self::Config) -> Self {
1882        Self {
1883            config: config.0.clone(),
1884            native_gadget: NativeGadget::new_from_scratch(&config.1),
1885        }
1886    }
1887
1888    fn configure_from_scratch(
1889        meta: &mut ConstraintSystem<F>,
1890        advice_columns: &mut Vec<Column<Advice>>,
1891        fixed_columns: &mut Vec<Column<Fixed>>,
1892        instance_columns: &[Column<Instance>; 2],
1893    ) -> Self::Config {
1894        use std::cmp::max;
1895
1896        use crate::field::{
1897            decomposition::pow2range::Pow2RangeChip,
1898            native::{NB_ARITH_COLS, NB_ARITH_FIXED_COLS},
1899        };
1900
1901        let nb_advice_needed = max(NB_ARITH_COLS, NB_SHA512_ADVICE_COLS);
1902        let nb_fixed_needed = max(NB_ARITH_FIXED_COLS, NB_SHA512_FIXED_COLS);
1903
1904        while advice_columns.len() < nb_advice_needed {
1905            advice_columns.push(meta.advice_column());
1906        }
1907        while fixed_columns.len() < nb_fixed_needed {
1908            fixed_columns.push(meta.fixed_column());
1909        }
1910
1911        let native_config = NativeChip::configure(
1912            meta,
1913            &(
1914                advice_columns[..NB_ARITH_COLS].try_into().unwrap(),
1915                fixed_columns[..NB_ARITH_FIXED_COLS].try_into().unwrap(),
1916                *instance_columns,
1917            ),
1918        );
1919
1920        let pow2range_config = Pow2RangeChip::configure(meta, &advice_columns[1..=4]);
1921        let core_decomposition_config =
1922            P2RDecompositionChip::configure(meta, &(native_config, pow2range_config));
1923
1924        let sha512_config = Sha512Chip::configure(
1925            meta,
1926            &(
1927                advice_columns[..NB_SHA512_ADVICE_COLS].try_into().unwrap(),
1928                fixed_columns[..NB_SHA512_FIXED_COLS].try_into().unwrap(),
1929            ),
1930        );
1931
1932        (sha512_config, core_decomposition_config)
1933    }
1934
1935    fn load_from_scratch(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
1936        self.native_gadget.load_from_scratch(layouter)?;
1937        self.load(layouter)
1938    }
1939}