Skip to main content

kimchi/circuits/polynomials/
rot.rs

1use alloc::{boxed::Box, vec, vec::Vec};
2//~ Rotation of a 64-bit word by a known offset
3
4use super::range_check::witness::range_check_0_row;
5use crate::{
6    circuits::{
7        argument::{Argument, ArgumentEnv, ArgumentType},
8        berkeley_columns::BerkeleyChallengeTerm,
9        expr::{
10            constraints::{crumb, ExprOps},
11            Cache,
12        },
13        gate::{CircuitGate, Connect, GateType},
14        lookup::{
15            self,
16            tables::{GateLookupTable, LookupTable},
17        },
18        polynomial::COLUMNS,
19        wires::Wire,
20        witness::{self, VariableBitsCell, VariableCell, Variables, WitnessCell},
21    },
22    variable_map,
23};
24use ark_ff::PrimeField;
25use core::{array, marker::PhantomData};
26
27#[derive(Clone, Copy, PartialEq, Eq, Debug)]
28pub enum RotMode {
29    Left,
30    Right,
31}
32
33impl<F: PrimeField> CircuitGate<F> {
34    /// Creates a Rot64 gadget to rotate a word
35    /// It will need:
36    /// - 1 Generic gate to constrain to zero the top 2 limbs of the shifted and
37    ///   excess witness of the rotation
38    ///
39    /// It has:
40    /// - 1 Rot64 gate to rotate the word
41    /// - 1 RangeCheck0 to constrain the size of the shifted witness of the
42    ///   rotation
43    /// - 1 RangeCheck0 to constrain the size of the excess witness of the
44    ///   rotation
45    ///
46    /// Assumes:
47    /// - the witness word is 64-bits, otherwise, will need to append a new RangeCheck0 for the word
48    pub fn create_rot64(new_row: usize, rot: u32) -> Vec<Self> {
49        vec![
50            CircuitGate {
51                typ: GateType::Rot64,
52                wires: Wire::for_row(new_row),
53                coeffs: vec![F::two_pow(rot as u64)],
54            },
55            CircuitGate {
56                typ: GateType::RangeCheck0,
57                wires: Wire::for_row(new_row + 1),
58                coeffs: vec![F::zero()],
59            },
60            CircuitGate {
61                typ: GateType::RangeCheck0,
62                wires: Wire::for_row(new_row + 2),
63                coeffs: vec![F::zero()],
64            },
65        ]
66    }
67
68    /// Extend one rotation
69    /// Right now it only creates a Generic gate followed by the Rot64 gates
70    /// It allows to configure left or right rotation.
71    ///
72    /// Input:
73    /// - gates : the full circuit
74    /// - rot : the rotation offset
75    /// - side : the rotation side
76    /// - zero_row : the row of the Generic gate to constrain the 64-bit check of shifted word
77    ///
78    /// Warning:
79    /// - witness word should come from the copy of another cell so it is intrinsic that it is 64-bits length,
80    /// - same with rotated word
81    pub fn extend_rot(gates: &mut Vec<Self>, rot: u32, side: RotMode, zero_row: usize) -> usize {
82        let (new_row, mut rot_gates) = Self::create_rot(gates.len(), rot, side);
83        gates.append(&mut rot_gates);
84        // Check that 2 most significant limbs of shifted and excess are zero
85        gates.connect_64bit(zero_row, new_row - 2);
86        gates.connect_64bit(zero_row, new_row - 1);
87        // Connect excess with the Rot64 gate
88        gates.connect_cell_pair((new_row - 3, 2), (new_row - 1, 0));
89
90        gates.len()
91    }
92
93    /// Create one rotation
94    /// Right now it only creates a Generic gate followed by the Rot64 gates
95    /// It allows to configure left or right rotation.
96    ///
97    /// Input:
98    /// - rot : the rotation offset
99    /// - side : the rotation side
100    ///
101    /// Warning:
102    /// - Word should come from the copy of another cell so it is intrinsic that it is 64-bits length,
103    /// - same with rotated word
104    /// - need to check that the 2 most significant limbs of shifted are zero
105    pub fn create_rot(new_row: usize, rot: u32, side: RotMode) -> (usize, Vec<Self>) {
106        // Initial Generic gate to constrain the output to be zero
107        let rot_gates = if side == RotMode::Left {
108            Self::create_rot64(new_row, rot)
109        } else {
110            Self::create_rot64(new_row, 64 - rot)
111        };
112
113        (new_row + rot_gates.len(), rot_gates)
114    }
115}
116
117/// Get the rot lookup table
118pub fn lookup_table<F: PrimeField>() -> LookupTable<F> {
119    lookup::tables::get_table::<F>(GateLookupTable::RangeCheck)
120}
121
122//~ `Rot64` onstrains known-length rotation of 64-bit words:
123//~
124//~ * This circuit gate is used to constrain that a 64-bit word is rotated by $r < 64$ bits to the "left".
125//~ * The rotation is performed towards the most significant side (thus, the new LSB is fed with the old MSB).
126//~ * This gate operates on the `Curr` and `Next` rows.
127//~
128//~ The idea is to split the rotation operation into two parts:
129//~
130//~ * Shift to the left
131//~ * Add the excess bits to the right
132//~
133//~ We represent shifting with multiplication modulo $2^{64}$. That is, for each word to be rotated, we provide in
134//~ the witness a quotient and a remainder, similarly to `ForeignFieldMul` such that the following operation holds:
135//~
136//~ $$word \cdot 2^{rot} = quotient \cdot 2^{64} + remainder$$
137//~
138//~ Then, the remainder corresponds to the shifted word, and the quotient corresponds to the excess bits.
139//~
140//~ $$word \cdot 2^{rot} = excess \cdot 2^{64} + shifted$$
141//~
142//~ Thus, in order to obtain the rotated word, we need to add the quotient and the remainder as follows:
143//~
144//~ $$rotated = shifted + excess$$
145//~
146//~ The input word is known to be of length 64 bits. All we need for soundness is check that the shifted and
147//~ excess parts of the word have the correct size as well. That means, we need to range check that:
148//~
149//~ $$
150//~ \begin{aligned}
151//~ excess &< 2^{rot}\\
152//~ shifted &< 2^{64}
153//~ \end{aligned}
154//~ $$
155//~
156//~ The latter can be obtained with a `RangeCheck0` gate setting the two most significant limbs to zero.
157//~ The former is equivalent to the following check:
158//~
159//~ $$bound = excess - 2^{rot} + 2^{64} < 2^{64}$$
160//~
161//~ which is doable with the constraints in a `RangeCheck0` gate. Since our current row within the `Rot64` gate
162//~ is almost empty, we can use it to perform the range check within the same gate. Then, using the following layout
163//~ and assuming that the gate has a coefficient storing the value $2^{rot}$, which is publicly known
164//~
165//~ | Gate   | `Rot64`             | `RangeCheck0` gadgets (designer's duty)                   |
166//~ | ------ | ------------------- | --------------------------------------------------------- |
167//~ | Column | `Curr`              | `Next`           | `Next` + 1      | `Next`+ 2, if needed |
168//~ | ------ | ------------------- | ---------------- | --------------- | -------------------- |
169//~ |      0 | copy `word`         |`shifted`         |   copy `excess` |    copy      `word`  |
170//~ |      1 | copy `rotated`      | 0                |              0  |                  0   |
171//~ |      2 |      `excess`       | 0                |              0  |                  0   |
172//~ |      3 |      `bound_limb0`  | `shifted_limb0`  |  `excess_limb0` |        `word_limb0`  |
173//~ |      4 |      `bound_limb1`  | `shifted_limb1`  |  `excess_limb1` |        `word_limb1`  |
174//~ |      5 |      `bound_limb2`  | `shifted_limb2`  |  `excess_limb2` |        `word_limb2`  |
175//~ |      6 |      `bound_limb3`  | `shifted_limb3`  |  `excess_limb3` |        `word_limb3`  |
176//~ |      7 |      `bound_crumb0` | `shifted_crumb0` | `excess_crumb0` |       `word_crumb0`  |
177//~ |      8 |      `bound_crumb1` | `shifted_crumb1` | `excess_crumb1` |       `word_crumb1`  |
178//~ |      9 |      `bound_crumb2` | `shifted_crumb2` | `excess_crumb2` |       `word_crumb2`  |
179//~ |     10 |      `bound_crumb3` | `shifted_crumb3` | `excess_crumb3` |       `word_crumb3`  |
180//~ |     11 |      `bound_crumb4` | `shifted_crumb4` | `excess_crumb4` |       `word_crumb4`  |
181//~ |     12 |      `bound_crumb5` | `shifted_crumb5` | `excess_crumb5` |       `word_crumb5`  |
182//~ |     13 |      `bound_crumb6` | `shifted_crumb6` | `excess_crumb6` |       `word_crumb6`  |
183//~ |     14 |      `bound_crumb7` | `shifted_crumb7` | `excess_crumb7` |       `word_crumb7`  |
184//~
185//~ In Keccak, rotations are performed over a 5x5 matrix state of w-bit words each cell. The values used
186//~ to perform the rotation are fixed, public, and known in advance, according to the following table,
187//~ depending on the coordinate of each cell within the 5x5 matrix state:
188//~
189//~ | y \ x |   0 |   1 |   2 |   3 |   4 |
190//~ | ----- | --- | --- | --- | --- | --- |
191//~ | 0     |   0 |  36 |   3 | 105 | 210 |
192//~ | 1     |   1 | 300 |  10 |  45 |  66 |
193//~ | 2     | 190 |   6 | 171 |  15 | 253 |
194//~ | 3     |  28 |  55 | 153 |  21 | 120 |
195//~ | 4     |  91 | 276 | 231 | 136 |  78 |
196//~
197//~ But since we will always be using 64-bit words in our Keccak usecase ($w = 64$), we can have an equivalent
198//~ table with these values modulo 64 to avoid needing multiple passes of the rotation gate (a single step would
199//~ cause overflows otherwise):
200//~
201//~ | y \ x |   0 |   1 |   2 |   3 |   4 |
202//~ | ----- | --- | --- | --- | --- | --- |
203//~ | 0     |   0 |  36 |   3 |  41 |  18 |
204//~ | 1     |   1 |  44 |  10 |  45 |   2 |
205//~ | 2     |  62 |   6 |  43 |  15 |  61 |
206//~ | 3     |  28 |  55 |  25 |  21 |  56 |
207//~ | 4     |  27 |  20 |  39 |   8 |  14 |
208//~
209//~ Since there is one value of the coordinates (x, y) where the rotation is 0 bits, we can skip that step in the
210//~ gadget. This will save us one gate, and thus the whole 25-1=24 rotations will be performed in just 48 rows.
211//~
212#[derive(Default)]
213pub struct Rot64<F>(PhantomData<F>);
214
215impl<F> Argument<F> for Rot64<F>
216where
217    F: PrimeField,
218{
219    const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::Rot64);
220    const CONSTRAINTS: u32 = 11;
221
222    // Constraints for rotation of three 64-bit words by any three number of bits modulo 64
223    // (stored in coefficient as a power-of-two form)
224    //   * Operates on Curr row
225    //   * Shifts the words by `rot` bits and then adds the excess to obtain the rotated word.
226    fn constraint_checks<T: ExprOps<F, BerkeleyChallengeTerm>>(
227        env: &ArgumentEnv<F, T>,
228        _cache: &mut Cache,
229    ) -> Vec<T> {
230        // Check that the last 8 columns are 2-bit crumbs
231        // C1..C8: x * (x - 1) * (x - 2) * (x - 3) = 0
232        let mut constraints = (7..COLUMNS)
233            .map(|i| crumb(&env.witness_curr(i)))
234            .collect::<Vec<T>>();
235
236        // NOTE:
237        // If we ever want to make this gate more generic, the power of two for the length
238        // could be a coefficient of the gate instead of a fixed value in the constraints.
239        let two_to_64 = T::two_pow(64);
240
241        let word = env.witness_curr(0);
242        let rotated = env.witness_curr(1);
243        let excess = env.witness_curr(2);
244        let shifted = env.witness_next(0);
245        let two_to_rot = env.coeff(0);
246
247        // Obtains the following checks:
248        // C9: word * 2^{rot} = (excess * 2^64 + shifted)
249        // C10: rotated = shifted + excess
250        constraints.push(
251            word * two_to_rot.clone() - (excess.clone() * two_to_64.clone() + shifted.clone()),
252        );
253        constraints.push(rotated - (shifted + excess.clone()));
254
255        // Compute the bound from the crumbs and limbs
256        let mut power_of_2 = T::one();
257        let mut bound = T::zero();
258
259        // Sum 2-bit limbs
260        for i in (7..COLUMNS).rev() {
261            bound += power_of_2.clone() * env.witness_curr(i);
262            power_of_2 *= T::two_pow(2); // 2 bits
263        }
264
265        // Sum 12-bit limbs
266        for i in (3..=6).rev() {
267            bound += power_of_2.clone() * env.witness_curr(i);
268            power_of_2 *= T::two_pow(12); // 12 bits
269        }
270
271        // Check that excess < 2^rot by checking that bound < 2^64
272        // Check RFC of Keccak for more details on the proof of this
273        // C11:bound = excess - 2^rot + 2^64
274        constraints.push(bound - (excess - two_to_rot + two_to_64));
275
276        constraints
277    }
278}
279
280// ROTATION WITNESS COMPUTATION
281
282fn layout_rot64<F: PrimeField>(curr_row: usize) -> [Vec<Box<dyn WitnessCell<F>>>; 3] {
283    [
284        rot_row(),
285        range_check_0_row("shifted", curr_row + 1),
286        range_check_0_row("excess", curr_row + 2),
287    ]
288}
289
290fn rot_row<F: PrimeField>() -> Vec<Box<dyn WitnessCell<F>>> {
291    vec![
292        VariableCell::create("word"),
293        VariableCell::create("rotated"),
294        VariableCell::create("excess"),
295        /* 12-bit plookups */
296        VariableBitsCell::create("bound", 52, Some(64)),
297        VariableBitsCell::create("bound", 40, Some(52)),
298        VariableBitsCell::create("bound", 28, Some(40)),
299        VariableBitsCell::create("bound", 16, Some(28)),
300        /* 2-bit crumbs */
301        VariableBitsCell::create("bound", 14, Some(16)),
302        VariableBitsCell::create("bound", 12, Some(14)),
303        VariableBitsCell::create("bound", 10, Some(12)),
304        VariableBitsCell::create("bound", 8, Some(10)),
305        VariableBitsCell::create("bound", 6, Some(8)),
306        VariableBitsCell::create("bound", 4, Some(6)),
307        VariableBitsCell::create("bound", 2, Some(4)),
308        VariableBitsCell::create("bound", 0, Some(2)),
309    ]
310}
311
312fn init_rot64<F: PrimeField>(
313    witness: &mut [Vec<F>; COLUMNS],
314    curr_row: usize,
315    word: F,
316    rotated: F,
317    excess: F,
318    shifted: F,
319    bound: F,
320) {
321    let rot_rows = layout_rot64(curr_row);
322    witness::init(
323        witness,
324        curr_row,
325        &rot_rows,
326        &variable_map!["word" => word, "rotated" => rotated, "excess" => excess, "shifted" => shifted, "bound" => excess+bound],
327    );
328}
329
330/// Extends the rot rows to the full witness
331/// Input
332/// - witness: full witness of the circuit
333/// - word: 64-bit word to be rotated
334/// - rot:  rotation offset
335/// - side: side of the rotation, either left or right
336///
337/// Warning:
338/// - don't forget to include a public input row with zero value
339pub fn extend_rot<F: PrimeField>(
340    witness: &mut [Vec<F>; COLUMNS],
341    word: u64,
342    rot: u32,
343    side: RotMode,
344) {
345    assert!(rot <= 64, "Rotation value must be less or equal than 64");
346
347    let rot = if side == RotMode::Right {
348        64 - rot
349    } else {
350        rot
351    };
352    // Split word into shifted and excess parts to compute the witnesses for rotation as follows
353    //          <   64     >  bits
354    // word   = [---|------]
355    //          <rot>         bits
356    // excess = [---]
357    // shifted      [------] * 2^rot
358    // rot    = [------|000]
359    //        +        [---] excess
360    let shifted = (word as u128) * 2u128.pow(rot) % 2u128.pow(64);
361    let excess = (word as u128) / 2u128.pow(64 - rot);
362    let rotated = shifted + excess;
363    // Value for the added value for the bound
364    // Right input of the "FFAdd" for the bound equation
365    let bound = 2u128.pow(64) - 2u128.pow(rot);
366
367    let rot_row = witness[0].len();
368    let rot_witness: [Vec<F>; COLUMNS] = array::from_fn(|_| vec![F::zero(); 3]);
369    for col in 0..COLUMNS {
370        witness[col].extend(rot_witness[col].iter());
371    }
372    init_rot64(
373        witness,
374        rot_row,
375        word.into(),
376        rotated.into(),
377        excess.into(),
378        shifted.into(),
379        bound.into(),
380    );
381}