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}