fullcodec_plonk/constraint_system/logic.rs
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4//
5// Copyright (c) DUSK NETWORK. All rights reserved.
6
7use crate::bit_iterator::*;
8use crate::constraint_system::TurboComposer;
9use crate::constraint_system::{WireData, Witness};
10use dusk_bls12_381::BlsScalar;
11use dusk_bytes::Serializable;
12use sp_std::vec::Vec;
13
14impl TurboComposer {
15 /// Performs a logical AND or XOR op between the inputs provided for the
16 /// specified number of bits.
17 ///
18 /// Each logic gate adds `(num_bits / 2) + 1` gates to the circuit to
19 /// perform the whole operation.
20 ///
21 /// ## Constraint
22 /// - is_component_xor = 1 -> Performs XOR between the first `num_bits` for
23 /// `a` and `b`.
24 /// - is_component_xor = 0 -> Performs AND between the first `num_bits` for
25 /// `a` and `b`.
26 ///
27 /// # Panics
28 /// This function will panic if the num_bits specified is not even, ie.
29 /// `num_bits % 2 != 0`.
30 fn logic_gate(
31 &mut self,
32 a: Witness,
33 b: Witness,
34 num_bits: usize,
35 is_component_xor: bool,
36 ) -> Witness {
37 // Since we work on base4, we need to guarantee that we have an even
38 // number of bits representing the greatest input.
39 assert_eq!(num_bits & 1, 0);
40
41 // We will have exactly `num_bits / 2` quads (quaternary digits)
42 // representing both numbers.
43 let num_quads = num_bits >> 1;
44
45 // Allocate accumulators for gate construction.
46 let mut left_accumulator = BlsScalar::zero();
47 let mut right_accumulator = BlsScalar::zero();
48 let mut out_accumulator = BlsScalar::zero();
49 let mut left_quad: u8;
50 let mut right_quad: u8;
51
52 // Get vars as bits and reverse them to get the Little Endian repr.
53 let a_bit_iter = BitIterator8::new(self.witnesses[&a].to_bytes());
54 let a_bits: Vec<_> = a_bit_iter.skip(256 - num_bits).collect();
55
56 let b_bit_iter = BitIterator8::new(self.witnesses[&b].to_bytes());
57 let b_bits: Vec<_> = b_bit_iter.skip(256 - num_bits).collect();
58
59 assert!(a_bits.len() >= num_bits);
60 assert!(b_bits.len() >= num_bits);
61
62 // If we take a look to the program memory structure of the ref. impl.
63 // * +-----+-----+-----+-----+
64 // * | A | B | C | D |
65 // * +-----+-----+-----+-----+
66 // * | 0 | 0 | w1 | 0 |
67 // * | a1 | b1 | w2 | c1 |
68 // * | a2 | b2 | w3 | c2 |
69 // * | : | : | : | : |
70 // * | an | bn | --- | cn |
71 // * +-----+-----+-----+-----+
72 // We need to have w_4, w_l and w_r pointing to one gate ahead of w_o.
73 // We increase the gate idx and assign w_4, w_l and w_r to `zero`.
74 // Now we can add the first row as: `| 0 | 0 | -- | 0 |`.
75 // Note that `w_1` will be set on the first loop iteration.
76 self.perm.add_variable_to_map(
77 Self::constant_zero(),
78 WireData::Left(self.n as usize),
79 );
80 self.perm.add_variable_to_map(
81 Self::constant_zero(),
82 WireData::Right(self.n as usize),
83 );
84 self.perm.add_variable_to_map(
85 Self::constant_zero(),
86 WireData::Fourth(self.n as usize),
87 );
88 self.w_l.push(Self::constant_zero());
89 self.w_r.push(Self::constant_zero());
90 self.w_4.push(Self::constant_zero());
91 // Increase the gate index so we can add the following rows in the
92 // correct order.
93 self.n += 1;
94
95 // Start generating accumulator rows and adding them to the circuit.
96 // Note that we will do this process exactly `num_bits / 2` counting
97 // that the first step above was done correctly to obtain the
98 // right format the the first row. This means that we will need
99 // to pad the end of the memory program once we've built it.
100 // As we can see in the last row structure: `| an | bn | --- | cn |`.
101 for i in 0..num_quads {
102 // On each round, we will commit every accumulator step. To do so,
103 // we first need to get the ith quads of `a` and `b` and then
104 // compute `out_quad`(logical OP result) and
105 // `prod_quad`(intermediate prod result).
106
107 // Here we compute each quad by taking the most significant bit
108 // multiplying it by two and adding to it the less significant
109 // bit to form the quad with a ternary value encapsulated in an `u8`
110 // in Big Endian form.
111 left_quad = {
112 let idx = i << 1;
113 ((a_bits[idx] as u8) << 1) + (a_bits[idx + 1] as u8)
114 };
115 right_quad = {
116 let idx = i << 1;
117 ((b_bits[idx] as u8) << 1) + (b_bits[idx + 1] as u8)
118 };
119 let left_quad_fr = BlsScalar::from(left_quad as u64);
120 let right_quad_fr = BlsScalar::from(right_quad as u64);
121 // The `out_quad` is the result of the bitwise ops `&` or `^`
122 // between the left and right quads. The op is decided
123 // with a boolean flag set as input of the function.
124 let out_quad_fr = match is_component_xor {
125 true => BlsScalar::from((left_quad ^ right_quad) as u64),
126 false => BlsScalar::from((left_quad & right_quad) as u64),
127 };
128 // We also need to allocate a helper item which is the result
129 // of the product between the left and right quads.
130 // This param is identified as `w` in the program memory and
131 // is needed to prevent the degree of our quotient polynomial from
132 // blowing up
133 let prod_quad_fr = BlsScalar::from((left_quad * right_quad) as u64);
134
135 // Now that we've computed this round results, we need to apply the
136 // logic transition constraint that will check the following:
137 // a - 4 . a ϵ [0, 1, 2, 3]
138 // i + 1 i
139 //
140 //
141 //
142 //
143 // b - 4 . b ϵ [0, 1, 2, 3]
144 // i + 1 i
145 //
146 //
147 //
148 //
149 // / \ /
150 // \ c - 4 . c = | a - 4 . a | (& OR ^) | b
151 // - 4 . b | i + 1 i \ i + 1 i /
152 // \ i + 1 i /
153 //
154 let prev_left_accum = left_accumulator;
155 let prev_right_accum = right_accumulator;
156 let prev_out_accum = out_accumulator;
157 // We also need to add the computed quad fr_s to the circuit
158 // representing a logic gate. To do so, we just mul by 4
159 // the previous accomulated result and we add to it
160 // the new computed quad.
161 // With this technique we're basically accumulating the quads and
162 // adding them to get back to the starting value, at the
163 // i-th iteration. i
164 // ===
165 // \ j
166 // x = / q . 4
167 // i === (bits/2 - j)
168 // j = 0
169 //
170 left_accumulator *= BlsScalar::from(4u64);
171 left_accumulator += left_quad_fr;
172 right_accumulator *= BlsScalar::from(4u64);
173 right_accumulator += right_quad_fr;
174 out_accumulator *= BlsScalar::from(4u64);
175 out_accumulator += out_quad_fr;
176 // Apply logic transition gates.
177 assert!(
178 left_accumulator - (prev_left_accum * BlsScalar::from(4u64))
179 < BlsScalar::from(4u64)
180 );
181 assert!(
182 right_accumulator - (prev_right_accum * BlsScalar::from(4u64))
183 < BlsScalar::from(4u64)
184 );
185 assert!(
186 out_accumulator - (prev_out_accum * BlsScalar::from(4u64))
187 < BlsScalar::from(4u64)
188 );
189
190 // Get variables pointing to the previous accumulated values.
191 let var_a = self.append_witness(left_accumulator);
192 let var_b = self.append_witness(right_accumulator);
193 let var_c = self.append_witness(prod_quad_fr);
194 let var_4 = self.append_witness(out_accumulator);
195 // Add the variables to the variable map linking them to it's
196 // corresponding gate index.
197 //
198 // Note that by doing this, we are basically setting the wire_coeffs
199 // of the wire polynomials, but we still need to link the
200 // selector_poly coefficients in order to be able to
201 // have complete gates.
202 //
203 // Also note that here we're setting left, right and fourth
204 // variables to the actual gate, meanwhile we set out to
205 // the previous gate.
206 self.perm
207 .add_variable_to_map(var_a, WireData::Left(self.n as usize));
208 self.perm
209 .add_variable_to_map(var_b, WireData::Right(self.n as usize));
210 self.perm
211 .add_variable_to_map(var_4, WireData::Fourth(self.n as usize));
212 self.perm.add_variable_to_map(
213 var_c,
214 WireData::Output(self.n as usize - 1),
215 );
216 // Push the variables to it's actual wire vector storage
217 self.w_l.push(var_a);
218 self.w_r.push(var_b);
219 self.w_o.push(var_c);
220 self.w_4.push(var_4);
221 // Update the gate index
222 self.n += 1;
223 }
224
225 // We have one missing value for the last row of the program memory
226 // which is `w_o` since the rest of wires are pointing one gate
227 // ahead. To fix this, we simply pad with a 0 so the last row of
228 // the program memory will look like this:
229 // | an | bn | --- | cn |
230 self.perm.add_variable_to_map(
231 Self::constant_zero(),
232 WireData::Output(self.n as usize - 1),
233 );
234 self.w_o.push(Self::constant_zero());
235
236 // Now the wire values are set for each gate, indexed and mapped in the
237 // `variable_map` inside of the `Permutation` struct.
238 // Now we just need to extend the selector polynomials with the
239 // appropriate coefficients to form complete logic gates.
240 for _ in 0..num_quads {
241 self.q_m.push(BlsScalar::zero());
242 self.q_l.push(BlsScalar::zero());
243 self.q_r.push(BlsScalar::zero());
244 self.q_arith.push(BlsScalar::zero());
245 self.q_o.push(BlsScalar::zero());
246 self.q_4.push(BlsScalar::zero());
247 self.q_range.push(BlsScalar::zero());
248 self.q_fixed_group_add.push(BlsScalar::zero());
249 self.q_variable_group_add.push(BlsScalar::zero());
250 self.q_lookup.push(BlsScalar::zero());
251 match is_component_xor {
252 true => {
253 self.q_c.push(-BlsScalar::one());
254 self.q_logic.push(-BlsScalar::one());
255 }
256 false => {
257 self.q_c.push(BlsScalar::one());
258 self.q_logic.push(BlsScalar::one());
259 }
260 };
261 }
262 // For the last gate, `q_c` and `q_logic` we use no-op values (Zero).
263 self.q_m.push(BlsScalar::zero());
264 self.q_l.push(BlsScalar::zero());
265 self.q_r.push(BlsScalar::zero());
266 self.q_arith.push(BlsScalar::zero());
267 self.q_o.push(BlsScalar::zero());
268 self.q_4.push(BlsScalar::zero());
269 self.q_range.push(BlsScalar::zero());
270 self.q_fixed_group_add.push(BlsScalar::zero());
271 self.q_variable_group_add.push(BlsScalar::zero());
272 self.q_lookup.push(BlsScalar::zero());
273
274 self.q_c.push(BlsScalar::zero());
275 self.q_logic.push(BlsScalar::zero());
276
277 // Now we need to assert that the sum of accumulated values
278 // matches the original values provided to the fn.
279 // Note that we're only considering the quads that are included
280 // in the range 0..num_bits. So, when actually executed, we're checking
281 // that x & ((1 << num_bits +1) -1) == [0..num_quads]
282 // accumulated sums of x.
283 //
284 // We could also check that the last gates wire coefficients match the
285 // original values introduced in the function taking into account the
286 // bitnum specified on the fn call parameters.
287 // This can be done with an `assert_equal` constraint gate or simply
288 // by taking the values behind the n'th variables of `w_l` & `w_r` and
289 // checking that they're equal to the original ones behind the variables
290 // sent through the function parameters.
291 assert_eq!(
292 self.witnesses[&a]
293 & (BlsScalar::from(2u64).pow(&[(num_bits) as u64, 0, 0, 0])
294 - BlsScalar::one()),
295 self.witnesses[&self.w_l[(self.n - 1) as usize]]
296 );
297 assert_eq!(
298 self.witnesses[&b]
299 & (BlsScalar::from(2u64).pow(&[(num_bits) as u64, 0, 0, 0])
300 - BlsScalar::one()),
301 self.witnesses[&self.w_r[(self.n - 1) as usize]]
302 );
303
304 // Once the inputs are checked against the accumulated additions,
305 // we can safely return the resulting variable of the gate computation
306 // which is stored on the last program memory row and in the column that
307 // `w_4` is holding.
308 self.w_4[self.w_4.len() - 1]
309 }
310
311 /// Adds a logical XOR gate that performs the XOR between two values for the
312 /// specified first `num_bits` returning a [`Witness`] holding the
313 /// result.
314 ///
315 /// # Panics
316 ///
317 /// If the `num_bits` specified in the fn params is odd.
318 pub fn component_xor(
319 &mut self,
320 a: Witness,
321 b: Witness,
322 num_bits: usize,
323 ) -> Witness {
324 self.logic_gate(a, b, num_bits, true)
325 }
326
327 /// Adds a logical AND gate that performs the bitwise AND between two values
328 /// for the specified first `num_bits` returning a [`Witness`]
329 /// holding the result.
330 ///
331 /// # Panics
332 ///
333 /// If the `num_bits` specified in the fn params is odd.
334 pub fn component_and(
335 &mut self,
336 a: Witness,
337 b: Witness,
338 num_bits: usize,
339 ) -> Witness {
340 self.logic_gate(a, b, num_bits, false)
341 }
342}
343
344#[cfg(feature = "std")]
345#[cfg(test)]
346mod tests {
347 use super::super::helper::*;
348 use dusk_bls12_381::BlsScalar;
349
350 #[test]
351 fn test_logic_xor_and_constraint() {
352 // Should pass since the XOR result is correct and the bit-num is even.
353 let res = gadget_tester(
354 |composer| {
355 let witness_a =
356 composer.append_witness(BlsScalar::from(500u64));
357 let witness_b =
358 composer.append_witness(BlsScalar::from(357u64));
359 let xor_res = composer.component_xor(witness_a, witness_b, 10);
360 // Check that the XOR result is indeed what we are expecting.
361 composer.assert_equal_constant(
362 xor_res,
363 BlsScalar::from(500u64 ^ 357u64),
364 None,
365 );
366 },
367 200,
368 );
369 assert!(res.is_ok());
370
371 // Should pass since the AND result is correct even the bit-num is even.
372 let res = gadget_tester(
373 |composer| {
374 let witness_a =
375 composer.append_witness(BlsScalar::from(469u64));
376 let witness_b =
377 composer.append_witness(BlsScalar::from(321u64));
378 let xor_res = composer.component_and(witness_a, witness_b, 10);
379 // Check that the AND result is indeed what we are expecting.
380 composer.assert_equal_constant(
381 xor_res,
382 BlsScalar::from(469u64 & 321u64),
383 None,
384 );
385 },
386 200,
387 );
388 assert!(res.is_ok());
389
390 // Should not pass since the XOR result is not correct even the bit-num
391 // is even.
392 let res = gadget_tester(
393 |composer| {
394 let witness_a =
395 composer.append_witness(BlsScalar::from(139u64));
396 let witness_b = composer.append_witness(BlsScalar::from(33u64));
397 let xor_res = composer.component_xor(witness_a, witness_b, 10);
398 // Check that the XOR result is indeed what we are expecting.
399 composer.assert_equal_constant(
400 xor_res,
401 BlsScalar::from(139u64 & 33u64),
402 None,
403 );
404 },
405 200,
406 );
407 assert!(res.is_err());
408
409 // Should pass even the bitnum is less than the number bit-size
410 let res = gadget_tester(
411 |composer| {
412 let witness_a =
413 composer.append_witness(BlsScalar::from(256u64));
414 let witness_b =
415 composer.append_witness(BlsScalar::from(235u64));
416 let xor_res = composer.component_xor(witness_a, witness_b, 2);
417 // Check that the XOR result is indeed what we are expecting.
418 composer.assert_equal_constant(
419 xor_res,
420 BlsScalar::from(256 ^ 235),
421 None,
422 );
423 },
424 200,
425 );
426 assert!(res.is_err());
427 }
428
429 #[test]
430 #[should_panic]
431 fn test_logical_gate_odd_bit_num() {
432 // Should fail since the bit-num is odd.
433 let _ = gadget_tester(
434 |composer| {
435 let witness_a =
436 composer.append_witness(BlsScalar::from(500u64));
437 let witness_b =
438 composer.append_witness(BlsScalar::from(499u64));
439 let xor_res = composer.component_xor(witness_a, witness_b, 9);
440 // Check that the XOR result is indeed what we are expecting.
441 composer.assert_equal_constant(
442 xor_res,
443 BlsScalar::from(7u64),
444 None,
445 );
446 },
447 200,
448 );
449 }
450}