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}