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}