kimchi/circuits/polynomials/
not.rs

1//! This module includes the definition of the NOT gadget and the witness code generation,
2//! for both the implementation running with `Xor16` gates and the one with `Generic` gates.
3//! Note that this module does not include a `Not` gate type.
4use super::{
5    generic::GenericGateSpec,
6    xor::{init_xor, num_xors},
7};
8use crate::circuits::{
9    gate::{CircuitGate, Connect, GateType},
10    polynomial::COLUMNS,
11    wires::Wire,
12};
13use ark_ff::PrimeField;
14use core::{array, cmp::max};
15use num_bigint::BigUint;
16use o1_utils::{BigUintHelpers, BitwiseOps, FieldHelpers};
17
18//~ We implement NOT, i.e. bitwise negation, as a gadget in two different ways, needing no new gate type for it. Instead, it reuses the XOR gadget and the Generic gate.
19//~
20//~ The first version of the NOT gadget reuses `Xor16` by making the following observation: *the bitwise NOT operation is equivalent to the
21//~ bitwise XOR operation with the all one words of a certain length*. In other words,
22//~ $$\neg x = x \oplus 1^*$$
23//~ where $1^*$ denotes a bitstring of all ones of length $|x|$. Let $x_i$ be the $i$-th bit of $x$, the intuition is that if $x_i = 0$ then
24//~ XOR with $1$ outputs $1$, thus negating $x_i$. Similarly, if $x_i = 1$ then XOR with 1 outputs 0, again negating $x_i$. Thus, bitwise XOR
25//~ with $1^*$ is equivalent to bitwise negation (i.e. NOT).
26//~
27//~ Then, if we take the XOR gadget with a second input to be the all one word of the same length, that gives us the NOT gadget.
28//~ The correct length can be imposed by having a public input containing the `2^bits - 1` value and wiring it to the second input of the XOR gate.
29//~ This approach needs as many rows as an XOR would need, for a single negation, but it comes with the advantage of making sure the input is of a certain length.
30//~
31//~ The other approach can be more efficient if we already know the length of the inputs. For example, the input may be the input of a range check gate,
32//~ or the output of a previous XOR gadget (which will be the case in our Keccak usecase).
33//~ In this case, we simply perform the negation as a subtraction of the input word from the all one word (which again can be copied from a public input).
34//~ This comes with the advantage of holding up to 2 word negations per row (an eight-times improvement over the XOR approach), but it requires the user to know the length of the input.
35//~
36//~ ##### NOT Layout using XOR
37//~
38//~ Here we show the layout of the NOT gadget using the XOR approach. The gadget needs a row with a public input containing the all-one word of the given length. Then, a number of XORs
39//~ follow, and a final `Zero` row is needed. In this case, the NOT gadget needs $\ceil(\frac{|x|}{16})$ `Xor16` gates, that means one XOR row for every 16 bits of the input word.
40//~
41//~ | Row       | `CircuitGate` | Purpose                                                               |
42//~ | --------- | ------------- | --------------------------------------------------------------------- |
43//~ | pub       | `Generic`     | Leading row with the public $1^*$ value                               |
44//~ | i...i+n-1 | `Xor16`       | Negate every 4 nybbles of the word, from least to most significant    |
45//~ | i+n       | `Generic`     | Constrain that the final row is all zeros for correctness of Xor gate |
46//~
47//~ ##### NOT Layout using Generic gates
48//~
49//~ Here we show the layout of the NOT gadget using the Generic approach. The gadget needs a row with a public input containing the all-one word of the given length, exactly as above.
50//~ Then, one Generic gate reusing the all-one word as left inputs can be used to negate up to two words per row. This approach requires that the input word is known (or constrained)
51//~ to have a given length.
52//~
53//~ | Row | `CircuitGate` | Purpose                                                                       |
54//~ | --- | ------------- | ----------------------------------------------------------------------------- |
55//~ | pub | `Generic`     | Leading row with the public $1^*$ value                                       |
56//~ | i   | `Generic`     | Negate one or two words of the length given by the length of the all-one word |
57//~
58impl<F: PrimeField> CircuitGate<F> {
59    /// Extends a bitwise negation gadget with `n` NOT components of some length
60    /// previously constrained using a generic gate (checking that a cell stores
61    /// `2^bits-1` value).
62    /// Assumes that the inputs are known to have at most `bits` length.
63    /// Starts the gates in the `new_row` position.
64    ///
65    /// Includes:
66    /// - ceil(n/2) Double Generic gates to perform the `( 2^(bits) - 1 ) -
67    ///   input` operation for every two inputs in each row
68    ///
69    /// Input:
70    /// - gates     : full circuit
71    /// - n         : number of negations to perform
72    /// - pub_row   : row containing the public input with the all-one word of the given length
73    ///
74    /// Important:
75    /// - If the bit length of the input is not fixed, then it must be
76    ///   constrained somewhere else.
77    /// - Otherwise, use the `extend_neg_checked_length` instead (but this one
78    ///   requires about 8 times more rows).
79    ///
80    /// Warning:
81    /// - don't forget to include a public input in `pub_row` to constrain the
82    ///   left of each generic gate for negation to be `2^bits - 1`
83    pub fn extend_not_gadget_unchecked_length(
84        gates: &mut Vec<Self>,
85        n: usize,
86        pub_row: usize,
87    ) -> usize {
88        // taking advantage of double generic gates to negate two words in each row
89        let mut new_row = gates.len();
90        for _ in 0..(n / 2) {
91            new_row = Self::extend_not_gnrc(gates, pub_row, new_row, true);
92        }
93        // odd number of NOTs require one more row to negate the last word only
94        if n % 2 == 1 {
95            new_row = Self::extend_not_gnrc(gates, pub_row, new_row, false);
96        }
97        new_row
98    }
99
100    // Extends a double generic gate for negation for one or two words
101    // Input:
102    // - gates          : full circuit to which the not will be added
103    // - new_row        : row to start the double NOT generic gate
104    // - pub_row        : row where the public inputs is stored (the 2^bits - 1) value (in the column 0 of that row)
105    // - double_generic : whether to perform two NOTs or only one inside the generic gate
106    // Output:
107    // - new_row : next row after the double NOT generic gate, corresponds to `new_row+1`
108    fn extend_not_gnrc(
109        gates: &mut Vec<Self>,
110        pub_row: usize,
111        new_row: usize,
112        double_generic: bool,
113    ) -> usize {
114        let g1 = GenericGateSpec::Add {
115            left_coeff: None,
116            right_coeff: Some(-F::one()),
117            output_coeff: None,
118        };
119        let g2 = {
120            if double_generic {
121                Some(GenericGateSpec::Add {
122                    left_coeff: None,
123                    right_coeff: Some(-F::one()),
124                    output_coeff: None,
125                })
126            } else {
127                None
128            }
129        };
130        let mut not_gate = vec![CircuitGate::create_generic_gadget(
131            Wire::for_row(new_row),
132            g1,
133            g2,
134        )];
135        gates.append(&mut not_gate);
136        // check left inputs of the double generic gate correspond to the 2^bits - 1 value
137        gates.connect_cell_pair((pub_row, 0), (new_row, 0));
138        if double_generic {
139            gates.connect_cell_pair((pub_row, 0), (new_row, 3));
140        }
141        new_row + 1
142    }
143
144    /// Extends a NOT gadget for `bits` length using Xor gates.
145    /// It implicitly constrains the length of the input to be at most 16 * num_xors bits.
146    /// Includes:
147    /// - num_xors Xor16 gates
148    /// - 1 Generic gate to constrain the final row to be zero with itself
149    ///   Input:
150    /// - gates : full circuit
151    /// - pub_row : row containing the public input with the all-one word of the given length
152    /// - bits    : number of bits of the input
153    ///   Precndition:
154    /// - 1 initial public input generic gate in `all_ones_row` to constrain the input to be `2^bits-1`.
155    ///   Warning:
156    /// - don't forget to connect the left input to a public input row containing the `2^bits - 1` value
157    pub fn extend_not_gadget_checked_length(
158        gates: &mut Vec<Self>,
159        all_ones_row: usize,
160        bits: usize,
161    ) -> usize {
162        let n = num_xors(bits);
163        let new_row = gates.len();
164        let mut not_gates = (0..n)
165            .map(|i| CircuitGate {
166                typ: GateType::Xor16,
167                wires: Wire::for_row(new_row + i),
168                coeffs: vec![],
169            })
170            .collect::<Vec<_>>();
171        let zero_row = new_row + n;
172        not_gates.push(CircuitGate::create_generic_gadget(
173            Wire::for_row(zero_row),
174            GenericGateSpec::Const(F::zero()),
175            None,
176        ));
177        gates.extend(not_gates);
178        // check fin_in1, fin_in2, fin_out are zero
179        gates.connect_cell_pair((zero_row, 0), (zero_row, 1));
180        gates.connect_cell_pair((zero_row, 0), (zero_row, 2));
181        // Integration
182        gates.connect_cell_pair((all_ones_row, 0), (new_row, 1)); // input2 of xor is all ones
183
184        gates.len()
185    }
186}
187
188/// Extend a NOT witness for less than 255 bits (native field)
189/// Input: full witness, first input and optional bit length
190/// If `bits` is not provided, the negation is performed using the length of the `input` in bits.
191/// If `bits` is provided, the negation takes the maximum length between `bits` and that of `input`.
192/// Warning:
193/// - don't forget to set a row of the witness with public input `2^bits -1` and wire it to the second input of the first `Xor16` gate
194pub fn extend_not_witness_checked_length<F: PrimeField>(
195    witness: &mut [Vec<F>; COLUMNS],
196    input: F,
197    bits: Option<usize>,
198) {
199    let input = input.to_biguint();
200    let output = BigUint::bitwise_not(&input, bits);
201    let bits = max(input.bitlen(), bits.unwrap_or(0));
202    let mut not_witness: [Vec<F>; COLUMNS] =
203        array::from_fn(|_| vec![F::zero(); num_xors(bits) + 1]);
204    init_xor(
205        &mut not_witness,
206        0,
207        bits,
208        (
209            F::from_biguint(&input).unwrap(),
210            F::from(2u8).pow([bits as u64]) - F::one(),
211            F::from_biguint(&output).unwrap(),
212        ),
213    );
214
215    for col in 0..COLUMNS {
216        witness[col].extend(not_witness[col].iter());
217    }
218}
219
220/// Extends negation witnesses from generic gate, assuming the input witness already contains
221/// public input rows holding the 2^bits-1 value.
222/// Input: a vector of words to be negated, and the number of bits (all the same)
223/// Returns error if the bits length is too small for the inputs
224/// Warning: Set public input of bits in public generic gate
225/// Note: `witness[0][pub] = 2^bits - 1`
226pub fn extend_not_witness_unchecked_length<F: PrimeField>(
227    witness: &mut [Vec<F>; COLUMNS],
228    inputs: &[F],
229    bits: usize,
230) -> Result<(), String> {
231    // Check inputs fit in bits and in native field
232    let inputs = inputs
233        .iter()
234        .map(|input| input.to_biguint())
235        .collect::<Vec<_>>();
236    for input in inputs.clone() {
237        if bits < input.bitlen() {
238            Err(format!(
239                "Bits must be greater or equal than the inputs length: {} < {}",
240                bits,
241                input.bitlen()
242            ))?;
243        }
244    }
245    let all_ones = F::from(2u8).pow([bits as u64]) - F::one();
246    let rows = (inputs.len() as f64 / 2.0).ceil() as usize;
247    let mut not_witness: [Vec<F>; COLUMNS] = array::from_fn(|_| vec![F::zero(); rows]);
248    for (i, input) in inputs.iter().enumerate().step_by(2) {
249        let row = i / 2;
250        // fill in first NOT
251        let negated_input1 = all_ones - F::from_biguint(input).unwrap();
252        not_witness[0][row] = all_ones;
253        not_witness[1][row] = F::from_biguint(input).unwrap();
254        not_witness[2][row] = negated_input1;
255        // Next element exists
256        if i < inputs.len() - 1 {
257            let next = &inputs[i + 1];
258            // fill in second NOT
259            let negated_input2 = all_ones - F::from_biguint(next).unwrap();
260            not_witness[3][row] = all_ones;
261            not_witness[4][row] = F::from_biguint(next).unwrap();
262            not_witness[5][row] = negated_input2;
263        }
264    }
265    for col in 0..COLUMNS {
266        witness[col].extend(not_witness[col].iter());
267    }
268    Ok(())
269}