fullcodec_plonk/plonkup/table/
lookup_table.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
7//! Structs and functions for LookupTables
8//! Denoted as 't' in Plonkup paper.
9
10use super::hash_tables::constants::{BLS_SCALAR_REAL, DECOMPOSITION_S_I, SBOX};
11use crate::error::Error;
12use crate::plonkup::MultiSet;
13use crate::prelude::BlsScalar;
14use sp_std::vec;
15use sp_std::vec::Vec;
16
17/// This struct is a table, contaning a vector,
18/// of arity 4 where each of the values is a
19/// BlsScalar. The elements of the table are
20/// determined by the function g for
21/// g(x,y), used to compute tuples.
22///
23/// This struct will be used to determine
24/// the outputs of gates within arithmetic
25/// circuits.
26#[derive(Clone, Eq, PartialEq, Debug)]
27pub struct LookupTable(pub Vec<[BlsScalar; 4]>);
28
29impl Default for LookupTable {
30    fn default() -> Self {
31        LookupTable::new()
32    }
33}
34
35impl LookupTable {
36    /// Create a new, empty Plonkup table, with arity 4.
37    pub fn new() -> Self {
38        LookupTable(vec![])
39    }
40
41    /// Insert a new row for an addition operation.
42    /// This function needs to know the upper bound of the amount of addition
43    /// operations that will be done in the plonkup table.
44    pub fn insert_add_row(&mut self, a: u64, b: u64, upper_bound: u64) {
45        let c = (a + b) % upper_bound;
46        self.0.push([
47            BlsScalar::from(a),
48            BlsScalar::from(b),
49            BlsScalar::from(c),
50            BlsScalar::zero(),
51        ]);
52    }
53
54    /// Insert a new row for an addition operation.
55    /// This function needs to know the upper bound of the amount of addition
56    /// operations that will be done in the plonkup table.
57    pub fn insert_special_row(
58        &mut self,
59        a: BlsScalar,
60        b: BlsScalar,
61        c: BlsScalar,
62        d: BlsScalar,
63    ) {
64        self.0.push([a, b, c, d]);
65    }
66
67    /// Insert a new row for an multiplication operation.
68    /// This function needs to know the upper bound of the amount of
69    /// multiplication operations that will be done in the plonkup table.
70    pub fn insert_mul_row(&mut self, a: u64, b: u64, upper_bound: u64) {
71        let c = (a * b) % upper_bound;
72        self.0.push([
73            BlsScalar::from(a),
74            BlsScalar::from(b),
75            BlsScalar::from(c),
76            BlsScalar::one(),
77        ]);
78    }
79
80    /// Insert a new row for an XOR operation.
81    /// This function needs to know the upper bound of the amount of XOR
82    /// operations that will be done in the plonkup table.
83    pub fn insert_xor_row(&mut self, a: u64, b: u64, upper_bound: u64) {
84        let c = (a ^ b) % upper_bound;
85        self.0.push([
86            BlsScalar::from(a),
87            BlsScalar::from(b),
88            BlsScalar::from(c),
89            -BlsScalar::one(),
90        ]);
91    }
92
93    /// Insert a new row for an AND operation.
94    /// This function needs to know the upper bound of the amount of XOR
95    /// operations that will be done in the plonkup table.
96    pub fn insert_and_row(&mut self, a: u64, b: u64, upper_bound: u64) {
97        let c = (a & b) % upper_bound;
98        self.0.push([
99            BlsScalar::from(a),
100            BlsScalar::from(b),
101            BlsScalar::from(c),
102            BlsScalar::from(2u64),
103        ]);
104    }
105
106    /// Function builds a table from more than one operation. This is denoted
107    /// as 'Multiple Tables' in the paper. If, for example, we are using lookup
108    /// tables for both XOR and mul operataions, we can create a table where the
109    /// rows 0..n/2 use a mul function on all 2^n indices and have the 4th wire
110    /// storing index 0. For all indices n/2..n, an XOR gate can be added, where
111    /// the index of the 4th wire is 0.
112    /// These numbers require exponentiation outside, for the lower bound,
113    /// otherwise the range cannot start from zero, as 2^0 = 1.
114    pub fn insert_multi_add(&mut self, lower_bound: u64, n: u8) {
115        let upper_bound = 2u64.pow(n.into());
116
117        let range = lower_bound..upper_bound;
118
119        for a in range.clone() {
120            range
121                .clone()
122                .for_each(|b| self.insert_add_row(a, b, upper_bound));
123        }
124    }
125
126    /// Function builds a table from mutiple operations. If, for example,
127    /// we are using lookup tables for both XOR and mul operataions, we can
128    /// create a table where the rows 0..n/2 use a mul function on all 2^n
129    /// indices and have the 4th wire storing index 0. For all indices n/2..n,
130    /// an XOR gate can be added, wheren the index of the 4th wire is 0.
131    /// These numbers require exponentiation outside, for the lower bound,
132    /// otherwise the range cannot start from zero, as 2^0 = 1.
133    /// Particular multiplication row(s) can be added with this function.
134    pub fn insert_multi_mul(&mut self, lower_bound: u64, n: u8) {
135        let upper_bound = 2u64.pow(n.into());
136
137        let range = lower_bound..upper_bound;
138
139        for a in range.clone() {
140            range
141                .clone()
142                .for_each(|b| self.insert_mul_row(a, b, upper_bound));
143        }
144    }
145
146    /// Function builds a table from mutiple operations. If, for example,
147    /// we are using lookup tables for both XOR and mul operataions, we can
148    /// create a table where the rows 0..n/2 use a mul function on all 2^n
149    /// indices and have the 4th wire storing index 0. For all indices n/2..n,
150    /// an XOR gate can be added, wheren the index of the 4th wire is 0.
151    /// These numbers require exponentiation outside, for the lower bound,
152    /// otherwise the range cannot start from zero, as 2^0 = 1.
153    /// Particular XOR row(s) can be added with this function.
154    pub fn insert_multi_xor(&mut self, lower_bound: u64, n: u8) {
155        let upper_bound = 2u64.pow(n.into());
156
157        let range = lower_bound..upper_bound;
158
159        for a in range.clone() {
160            range
161                .clone()
162                .for_each(|b| self.insert_xor_row(a, b, upper_bound));
163        }
164    }
165
166    /// Function builds a table from mutiple operations. If, for example,
167    /// we are using lookup tables for both XOR and mul operataions, we can
168    /// create a table where the rows 0..n/2 use a mul function on all 2^n
169    /// indices and have the 4th wire storing index 0. For all indices n/2..n,
170    /// an XOR gate can be added, wheren the index of the 4th wire is 0.
171    /// These numbers require exponentiation outside, for the lower bound,
172    /// otherwise the range cannot start from zero, as 2^0 = 1.
173    /// Particular AND row(s) can be added with this function.
174    pub fn insert_multi_and(&mut self, lower_bound: u64, n: u8) {
175        let upper_bound = 2u64.pow(n.into());
176
177        let range = lower_bound..upper_bound;
178
179        for a in range.clone() {
180            range
181                .clone()
182                .for_each(|b| self.insert_and_row(a, b, upper_bound));
183        }
184    }
185
186    /// Takes in a table, which is a list of vectors containing
187    /// 4 elements, and turns them into 4 distinct multisets for
188    /// a, b, c and d.
189    pub fn vec_to_multiset(&self) -> (MultiSet, MultiSet, MultiSet, MultiSet) {
190        let mut multiset_a = MultiSet::new();
191        let mut multiset_b = MultiSet::new();
192        let mut multiset_c = MultiSet::new();
193        let mut multiset_d = MultiSet::new();
194
195        self.0.iter().for_each(|row| {
196            multiset_a.push(row[0]);
197            multiset_b.push(row[1]);
198            multiset_c.push(row[2]);
199            multiset_d.push(row[3]);
200        });
201
202        (multiset_a, multiset_b, multiset_c, multiset_d)
203    }
204
205    /// Attempts to find an output value, given two input values, by querying
206    /// the lookup table. The final wire holds the index of the table. The
207    /// element must be predetermined to be between -1 and 2 depending on
208    /// the type of table used. If the element does not exist, it will
209    /// return an error.
210    pub fn lookup(
211        &self,
212        a: BlsScalar,
213        b: BlsScalar,
214        d: BlsScalar,
215    ) -> Result<BlsScalar, Error> {
216        let pos = self
217            .0
218            .iter()
219            .position(|row| row[0] == a && row[1] == b && row[3] == d)
220            .ok_or(Error::ElementNotIndexed)?;
221
222        Ok(self.0[pos][2])
223    }
224
225    /// Function that creates the table needed for reinforced concrete.
226    /// Creates one table that is the concatenation T_2 || T_3 || T_1
227    /// from the paper
228    pub fn create_hash_table() -> Self {
229        let mut table = Vec::new();
230        let two = BlsScalar::from(2);
231
232        // Build the T_2 part
233        // (0..2).for_each(|i| {
234        //     (0..2).for_each(|j| {
235        //         (0..2).for_each(|k| {
236        //             (0..2).for_each(|m| {
237        //                 table.push([
238        //                     BlsScalar::from(i),
239        //                     BlsScalar::from(j),
240        //                     BlsScalar::from(k),
241        //                     BlsScalar::from(m),
242        //                 ])
243        //             })
244        //         })
245        //     })
246        // });
247
248        // Add the parts of T_2 that aren't in T_3
249        (0..2).for_each(|i| {
250            (0..2).for_each(|j| {
251                (0..2).for_each(|k| {
252                    if (i, j, k) != (1, 1, 1) {
253                        table.push([
254                            BlsScalar::one(),
255                            BlsScalar::from(i),
256                            BlsScalar::from(j),
257                            BlsScalar::from(k),
258                        ])
259                    }
260                })
261            })
262        });
263        (0..2).for_each(|i| {
264            (0..2).for_each(|j| {
265                if (i, j) != (1, 1) {
266                    table.push([
267                        BlsScalar::zero(),
268                        BlsScalar::one(),
269                        BlsScalar::from(i),
270                        BlsScalar::from(j),
271                    ])
272                }
273            })
274        });
275        table.push([
276            BlsScalar::zero(),
277            BlsScalar::zero(),
278            BlsScalar::one(),
279            BlsScalar::zero(),
280        ]);
281
282        // Build the T_3 part
283        (0..2).for_each(|i| {
284            table.push([
285                BlsScalar::zero(),
286                BlsScalar::zero(),
287                BlsScalar::zero(),
288                BlsScalar::from(i),
289            ])
290        });
291        (0..2).for_each(|i| {
292            (1..3).for_each(|j| {
293                table.push([
294                    BlsScalar::zero(),
295                    BlsScalar::from(i),
296                    BlsScalar::one(),
297                    BlsScalar::from(j),
298                ])
299            })
300        });
301        (1..3).for_each(|i| {
302            table.push([
303                BlsScalar::zero(),
304                BlsScalar::one(),
305                two,
306                BlsScalar::from(i),
307            ])
308        });
309        (1..3).for_each(|i| {
310            (1..3).for_each(|j| {
311                (1..3).for_each(|k| {
312                    (1..3).for_each(|m| {
313                        table.push([
314                            BlsScalar::from(i),
315                            BlsScalar::from(j),
316                            BlsScalar::from(k),
317                            BlsScalar::from(m),
318                        ])
319                    })
320                })
321            })
322        });
323
324        // Construct the T_1 part
325        // Build the permutation part of the table (the top section)
326        for k in 0..659 {
327            let first = BlsScalar::from(k);
328            let third = BlsScalar::from_raw([SBOX[k as usize] as u64, 0, 0, 0]);
329            table.push([first, BlsScalar::zero(), third, BlsScalar::one()]);
330        }
331        // Build the remaining 27 sections that range from p' to s_i (except
332        // when i=1)
333        for k in (0..27).rev() {
334            // The rev denotes that it is inverted, so s_rev_26 will actually be
335            // s_1 (i.e. i = 27-k)
336            let s_rev_k = DECOMPOSITION_S_I[k].0[0];
337            let v_rev_k = BLS_SCALAR_REAL[k] as u64;
338            // If i=1, then we go to v_1 and not s_1
339            if k == 26 {
340                // v_1 = 678
341                for j in 659..(v_rev_k + 1) {
342                    let first = BlsScalar::from(j as u64);
343
344                    // Fourth column is 1, unless j=v_i, in which case it is 0
345                    let fourth = if j == v_rev_k {
346                        BlsScalar::zero()
347                    } else {
348                        BlsScalar::one()
349                    };
350
351                    table.push([first, BlsScalar::one(), first, fourth]);
352                }
353            } else {
354                // When j is between p' and v_i the fourth column is always 1
355                let second = BlsScalar::from((27 - k) as u64);
356                for j in 659..v_rev_k {
357                    let first = BlsScalar::from(j);
358                    table.push([first, second, first, BlsScalar::one()]);
359                }
360
361                // When we get to j=v_i the fourth column can be either 0 or 2
362                let first = BlsScalar::from(v_rev_k);
363                table.push([first, second, first, BlsScalar::zero()]);
364                table.push([first, second, first, two]);
365
366                // For j between v_i and s_i the fourth column is always 2
367                for j in (v_rev_k + 1)..s_rev_k {
368                    let first = BlsScalar::from(j);
369                    table.push([first, second, first, two]);
370                }
371            }
372        }
373
374        LookupTable(table)
375    }
376}
377
378#[cfg(test)]
379mod test {
380    use super::*;
381
382    #[test]
383    fn test_add_table() {
384        let n = 4;
385
386        let table = {
387            let mut table = LookupTable::default();
388            table.insert_multi_add(0, n);
389            table
390        };
391
392        // Create an identical matrix, but with std numbers.
393        // This way, we can also do the modulo operation, and properly
394        // check all results.
395        let mut i = 0;
396        let p = 2u64.pow(n as u32);
397        (0..p).for_each(|a| {
398            (0..p).for_each(|b| {
399                let c = (a + b) % p;
400                assert_eq!(BlsScalar::from(c), table.0[i][2]);
401                i += 1;
402            })
403        });
404
405        assert_eq!(
406            table.0.len() as u64,
407            2u64.pow(n as u32) * 2u64.pow(n as u32)
408        );
409    }
410
411    #[test]
412    fn test_xor_table() {
413        let n = 4;
414
415        let table = {
416            let mut table = LookupTable::default();
417            table.insert_multi_xor(0, n);
418            table
419        };
420
421        // println!("{:?}", table);
422        let mut i = 0;
423        let p = 2u64.pow(n as u32);
424        (0..p).for_each(|a| {
425            (0..p).for_each(|b| {
426                let c = (a ^ b) % p;
427                assert_eq!(BlsScalar::from(c), table.0[i][2]);
428                i += 1;
429            })
430        });
431
432        assert_eq!(
433            table.0.len() as u64,
434            2u64.pow(n as u32) * 2u64.pow(n as u32)
435        );
436    }
437
438    #[test]
439    fn test_mul_table() {
440        let n = 4;
441
442        let table = {
443            let mut table = LookupTable::default();
444            table.insert_multi_mul(0, n);
445            table
446        };
447
448        // println!("{:?}", table);
449        let mut i = 0;
450        let p = 2u64.pow(n as u32);
451        (0..p).for_each(|a| {
452            (0..p).for_each(|b| {
453                let c = (a * b) % p;
454                assert_eq!(BlsScalar::from(c), table.0[i][2]);
455                i += 1;
456            })
457        });
458
459        assert_eq!(
460            table.0.len() as u64,
461            2u64.pow(n as u32) * 2u64.pow(n as u32)
462        );
463    }
464
465    #[test]
466    fn test_lookup() {
467        let add_table = {
468            let mut table = LookupTable::default();
469            table.insert_multi_add(0, 3);
470            table
471        };
472
473        assert!(add_table
474            .lookup(BlsScalar::from(2), BlsScalar::from(3), BlsScalar::zero())
475            .is_ok());
476
477        let output = add_table.0[1][0] + add_table.0[1][1] + add_table.0[1][2]; // TODO are we sure this is right
478
479        assert_eq!(output, BlsScalar::from(2));
480
481        let second_output =
482            add_table.0[12][0] + add_table.0[12][1] + add_table.0[12][2]; // TODO are we sure this is right
483
484        assert_eq!(second_output, BlsScalar::from(10));
485    }
486
487    #[test]
488    fn test_missing_lookup_value() {
489        let xor_table = {
490            let mut table = LookupTable::default();
491            table.insert_multi_xor(0, 5);
492            table
493        };
494
495        assert!(xor_table
496            .lookup(
497                BlsScalar::from(17),
498                BlsScalar::from(367),
499                BlsScalar::from(1)
500            )
501            .is_err());
502    }
503
504    #[test]
505    fn test_concatenated_table() {
506        let mut table = LookupTable::new();
507
508        table.insert_multi_xor(0, 5);
509        table.insert_multi_add(4, 7);
510
511        assert_eq!(table.0.last().unwrap()[2], BlsScalar::from(126u64));
512        let xor = table.0[36][0] ^ table.0[36][1] ^ table.0[36][2];
513        assert_eq!(xor, BlsScalar::zero());
514    }
515}