Skip to main content

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