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}