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}