kimchi/circuits/polynomials/
xor.rs

1//! This module includes the definition of the XOR gadget for 64, 32, and 16 bits,
2//! the definition of the constraints of the `Xor16` circuit gate,
3//! and the code for witness generation for the XOR gadget.
4use crate::{
5    circuits::{
6        argument::{Argument, ArgumentEnv, ArgumentType},
7        berkeley_columns::BerkeleyChallengeTerm,
8        expr::{constraints::ExprOps, Cache},
9        gate::{CircuitGate, Connect, GateType},
10        lookup::{
11            self,
12            tables::{GateLookupTable, LookupTable},
13        },
14        polynomial::COLUMNS,
15        wires::Wire,
16        witness::{self, ConstantCell, CopyBitsCell, VariableBitsCell, Variables, WitnessCell},
17    },
18    variable_map,
19};
20use ark_ff::PrimeField;
21use core::{array, marker::PhantomData};
22use num_bigint::BigUint;
23use o1_utils::{BigUintFieldHelpers, BigUintHelpers, BitwiseOps, FieldHelpers};
24
25use super::generic::GenericGateSpec;
26
27impl<F: PrimeField> CircuitGate<F> {
28    /// Extends a XOR gadget for `bits` length to a circuit
29    ///
30    /// Includes:
31    /// - num_xors Xor16 gates
32    /// - 1 Generic gate to constrain the final row to be zero with itself
33    ///
34    /// Input:
35    /// - gates     : vector of circuit gates
36    /// - bits      : length of the XOR gadget
37    ///
38    /// Output:
39    /// - new row index
40    pub fn extend_xor_gadget(gates: &mut Vec<Self>, bits: usize) -> usize {
41        let new_row = gates.len();
42        let (_, mut xor_gates) = Self::create_xor_gadget(new_row, bits);
43        // extend the whole circuit with the xor gadget
44        gates.append(&mut xor_gates);
45
46        // check fin_in1, fin_in2, fin_out are zero
47        let zero_row = gates.len() - 1;
48        gates.connect_cell_pair((zero_row, 0), (zero_row, 1));
49        gates.connect_cell_pair((zero_row, 0), (zero_row, 2));
50
51        gates.len()
52    }
53
54    /// Creates a XOR gadget for `bits` length
55    ///
56    /// Includes:
57    /// - num_xors Xor16 gates
58    /// - 1 Generic gate to constrain the final row to be zero with itself
59    ///
60    /// Input:
61    /// - new_row   : row to start the XOR gadget
62    /// - bits      : number of bits in the XOR
63    ///   Outputs tuple (next_row, circuit_gates) where
64    /// - next_row  : next row after this gate
65    /// - gates     : vector of circuit gates comprising this gate
66    ///
67    /// Warning:
68    /// - don't forget to check that the final row is all zeros as in
69    ///   `extend_xor_gadget`
70    pub fn create_xor_gadget(new_row: usize, bits: usize) -> (usize, Vec<Self>) {
71        let num_xors = num_xors(bits);
72        let mut xor_gates = (0..num_xors)
73            .map(|i| CircuitGate {
74                typ: GateType::Xor16,
75                wires: Wire::for_row(new_row + i),
76                coeffs: vec![],
77            })
78            .collect::<Vec<_>>();
79        let zero_row = new_row + num_xors;
80        xor_gates.push(CircuitGate::create_generic_gadget(
81            Wire::for_row(zero_row),
82            GenericGateSpec::Const(F::zero()),
83            None,
84        ));
85
86        (new_row + xor_gates.len(), xor_gates)
87    }
88}
89
90/// Get the xor lookup table
91pub fn lookup_table<F: PrimeField>() -> LookupTable<F> {
92    lookup::tables::get_table::<F>(GateLookupTable::Xor)
93}
94
95//~ `Xor16` - Chainable XOR constraints for words of multiples of 16 bits.
96//~
97//~ * This circuit gate is used to constrain that `in1` xored with `in2` equals `out`
98//~ * The length of `in1`, `in2` and `out` must be the same and a multiple of 16bits.
99//~ * This gate operates on the `Curr` and `Next` rows.
100//~
101//~ It uses three different types of constraints:
102//~
103//~ * copy          - copy to another cell (32-bits)
104//~ * plookup       - xor-table plookup (4-bits)
105//~ * decomposition - the constraints inside the gate
106//~
107//~ The 4-bit nybbles are assumed to be laid out with `0` column being the least significant nybble.
108//~ Given values `in1`, `in2` and `out`, the layout looks like this:
109//~
110//~ | Column |          `Curr`  |          `Next`  |
111//~ | ------ | ---------------- | ---------------- |
112//~ |      0 | copy     `in1`   | copy     `in1'`  |
113//~ |      1 | copy     `in2`   | copy     `in2'`  |
114//~ |      2 | copy     `out`   | copy     `out'`  |
115//~ |      3 | plookup0 `in1_0` |                  |
116//~ |      4 | plookup1 `in1_1` |                  |
117//~ |      5 | plookup2 `in1_2` |                  |
118//~ |      6 | plookup3 `in1_3` |                  |
119//~ |      7 | plookup0 `in2_0` |                  |
120//~ |      8 | plookup1 `in2_1` |                  |
121//~ |      9 | plookup2 `in2_2` |                  |
122//~ |     10 | plookup3 `in2_3` |                  |
123//~ |     11 | plookup0 `out_0` |                  |
124//~ |     12 | plookup1 `out_1` |                  |
125//~ |     13 | plookup2 `out_2` |                  |
126//~ |     14 | plookup3 `out_3` |                  |
127//~
128//~ One single gate with next values of `in1'`, `in2'` and `out'` being zero can be used to check
129//~ that the original `in1`, `in2` and `out` had 16-bits. We can chain this gate 4 times as follows
130//~ to obtain a gadget for 64-bit words XOR:
131//~
132//~ | Row | `CircuitGate` | Purpose                                    |
133//~ | --- | ------------- | ------------------------------------------ |
134//~ |   0 | `Xor16`       | Xor 2 least significant bytes of the words |
135//~ |   1 | `Xor16`       | Xor next 2 bytes of the words              |
136//~ |   2 | `Xor16`       | Xor next 2 bytes of the words              |
137//~ |   3 | `Xor16`       | Xor 2 most significant bytes of the words  |
138//~ |   4 | `Generic`     | Zero values, can be reused as generic gate |
139//~
140//~ ```admonish info
141//~ We could halve the number of rows of the 64-bit XOR gadget by having lookups
142//~ for 8 bits at a time, but for now we will use the 4-bit XOR table that we have.
143//~ Rough computations show that if we run 8 or more Keccaks in one circuit we should
144//~ use the 8-bit XOR table.
145//~ ```
146
147#[derive(Default)]
148pub struct Xor16<F>(PhantomData<F>);
149
150impl<F> Argument<F> for Xor16<F>
151where
152    F: PrimeField,
153{
154    const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::Xor16);
155    const CONSTRAINTS: u32 = 3;
156
157    // Constraints for Xor16
158    //   * Operates on Curr and Next rows
159    //   * Constrain the decomposition of `in1`, `in2` and `out` of multiples of 16 bits
160    //   * The actual XOR is performed thanks to the plookups of 4-bit XORs.
161    fn constraint_checks<T: ExprOps<F, BerkeleyChallengeTerm>>(
162        env: &ArgumentEnv<F, T>,
163        _cache: &mut Cache,
164    ) -> Vec<T> {
165        let two = T::from(2u64);
166        // in1 = in1_0 + in1_1 * 2^4 + in1_2 * 2^8 + in1_3 * 2^12 + next_in1 * 2^16
167        // in2 = in2_0 + in2_1 * 2^4 + in2_2 * 2^8 + in2_3 * 2^12 + next_in2 * 2^16
168        // out = out_0 + out_1 * 2^4 + out_2 * 2^8 + out_3 * 2^12 + next_out * 2^16
169        (0..3)
170            .map(|i| {
171                env.witness_curr(3 + 4 * i)
172                    + env.witness_curr(4 + 4 * i) * two.clone().pow(4)
173                    + env.witness_curr(5 + 4 * i) * two.clone().pow(8)
174                    + env.witness_curr(6 + 4 * i) * two.clone().pow(12)
175                    + two.clone().pow(16) * env.witness_next(i)
176                    - env.witness_curr(i)
177            })
178            .collect::<Vec<T>>()
179    }
180}
181
182// Witness layout
183fn layout<F: PrimeField>(curr_row: usize, bits: usize) -> Vec<Vec<Box<dyn WitnessCell<F>>>> {
184    let num_xor = num_xors(bits);
185    let mut layout = (0..num_xor)
186        .map(|i| xor_row(i, curr_row + i))
187        .collect::<Vec<_>>();
188    layout.push(zero_row());
189    layout
190}
191
192fn xor_row<F: PrimeField>(nybble: usize, curr_row: usize) -> Vec<Box<dyn WitnessCell<F>>> {
193    let start = nybble * 16;
194    vec![
195        VariableBitsCell::create("in1", start, None),
196        VariableBitsCell::create("in2", start, None),
197        VariableBitsCell::create("out", start, None),
198        CopyBitsCell::create(curr_row, 0, 0, 4), // First 4-bit nybble of in1
199        CopyBitsCell::create(curr_row, 0, 4, 8), // Second 4-bit nybble of in1
200        CopyBitsCell::create(curr_row, 0, 8, 12), // Third 4-bit nybble of in1
201        CopyBitsCell::create(curr_row, 0, 12, 16), // Fourth 4-bit nybble of in1
202        CopyBitsCell::create(curr_row, 1, 0, 4), // First 4-bit nybble of in2
203        CopyBitsCell::create(curr_row, 1, 4, 8), // Second 4-bit nybble of in2
204        CopyBitsCell::create(curr_row, 1, 8, 12), // Third 4-bit nybble of in2
205        CopyBitsCell::create(curr_row, 1, 12, 16), // Fourth 4-bit nybble of in2
206        CopyBitsCell::create(curr_row, 2, 0, 4), // First 4-bit nybble of out
207        CopyBitsCell::create(curr_row, 2, 4, 8), // Second 4-bit nybble of out
208        CopyBitsCell::create(curr_row, 2, 8, 12), // Third 4-bit nybble of out
209        CopyBitsCell::create(curr_row, 2, 12, 16), // Fourth 4-bit nybble of out
210    ]
211}
212
213fn zero_row<F: PrimeField>() -> Vec<Box<dyn WitnessCell<F>>> {
214    vec![
215        ConstantCell::create(F::zero()),
216        ConstantCell::create(F::zero()),
217        ConstantCell::create(F::zero()),
218        ConstantCell::create(F::zero()),
219        ConstantCell::create(F::zero()),
220        ConstantCell::create(F::zero()),
221        ConstantCell::create(F::zero()),
222        ConstantCell::create(F::zero()),
223        ConstantCell::create(F::zero()),
224        ConstantCell::create(F::zero()),
225        ConstantCell::create(F::zero()),
226        ConstantCell::create(F::zero()),
227        ConstantCell::create(F::zero()),
228        ConstantCell::create(F::zero()),
229        ConstantCell::create(F::zero()),
230    ]
231}
232
233pub(crate) fn init_xor<F: PrimeField>(
234    witness: &mut [Vec<F>; COLUMNS],
235    curr_row: usize,
236    bits: usize,
237    words: (F, F, F),
238) {
239    let xor_rows = layout(curr_row, bits);
240
241    witness::init(
242        witness,
243        curr_row,
244        &xor_rows,
245        &variable_map!["in1" => words.0, "in2" => words.1, "out" => words.2],
246    )
247}
248
249/// Extends the Xor rows to the full witness
250/// Panics if the words are larger than the desired bits
251pub fn extend_xor_witness<F: PrimeField>(
252    witness: &mut [Vec<F>; COLUMNS],
253    input1: F,
254    input2: F,
255    bits: usize,
256) {
257    let xor_witness = create_xor_witness(input1, input2, bits);
258    for col in 0..COLUMNS {
259        witness[col].extend(xor_witness[col].iter());
260    }
261}
262
263/// Create a Xor for up to the native length starting at row 0
264/// Input: first input and second input, bits length, current row
265/// Panics if the desired bits is smaller than the inputs length
266pub fn create_xor_witness<F: PrimeField>(input1: F, input2: F, bits: usize) -> [Vec<F>; COLUMNS] {
267    let input1_big = input1.to_biguint();
268    let input2_big = input2.to_biguint();
269    if bits < input1_big.bitlen() || bits < input2_big.bitlen() {
270        panic!("Bits must be greater or equal than the inputs length");
271    }
272    let output = BigUint::bitwise_xor(&input1_big, &input2_big);
273
274    let mut xor_witness: [Vec<F>; COLUMNS] =
275        array::from_fn(|_| vec![F::zero(); 1 + num_xors(bits)]);
276
277    init_xor(
278        &mut xor_witness,
279        0,
280        bits,
281        (input1, input2, output.to_field().unwrap()),
282    );
283
284    xor_witness
285}
286
287/// Returns the number of XOR rows needed for inputs of usize bits
288pub fn num_xors(bits: usize) -> usize {
289    (bits as f64 / 16.0).ceil() as usize
290}