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}