kimchi/circuits/polynomials/
rot.rs

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