1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
use ark_ff::{FftField, One, Zero};
use poly_commitment::PolyComm;
use serde::{Deserialize, Serialize};

pub mod range_check;
pub mod xor;

// If you add new tables, update ../../../../../book/src/kimchi/lookup.md
// accordingly

//~ spec:startcode
/// The table ID associated with the XOR lookup table.
pub const XOR_TABLE_ID: i32 = 0;

/// The range check table ID.
pub const RANGE_CHECK_TABLE_ID: i32 = 1;
//~ spec:endcode

/// Enumerates the different 'fixed' lookup tables used by individual gates
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum GateLookupTable {
    Xor,
    RangeCheck,
}

/// Enumerates the different 'fixed' lookup tables used by individual gates
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct GateLookupTables {
    pub xor: bool,
    pub range_check: bool,
}

impl std::ops::Index<GateLookupTable> for GateLookupTables {
    type Output = bool;

    fn index(&self, index: GateLookupTable) -> &Self::Output {
        match index {
            GateLookupTable::Xor => &self.xor,
            GateLookupTable::RangeCheck => &self.range_check,
        }
    }
}

impl std::ops::IndexMut<GateLookupTable> for GateLookupTables {
    fn index_mut(&mut self, index: GateLookupTable) -> &mut Self::Output {
        match index {
            GateLookupTable::Xor => &mut self.xor,
            GateLookupTable::RangeCheck => &mut self.range_check,
        }
    }
}

impl IntoIterator for GateLookupTables {
    type Item = GateLookupTable;
    type IntoIter = std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        // Destructor pattern to make sure we add new lookup patterns.
        let GateLookupTables { xor, range_check } = self;

        let mut patterns = Vec::with_capacity(2);

        if xor {
            patterns.push(GateLookupTable::Xor)
        }
        if range_check {
            patterns.push(GateLookupTable::RangeCheck)
        }
        patterns.into_iter()
    }
}

/// A table of values that can be used for a lookup, along with the ID for the table.
#[derive(Debug, Clone)]
pub struct LookupTable<F> {
    pub id: i32,
    pub data: Vec<Vec<F>>,
}

impl<F> LookupTable<F>
where
    F: FftField,
{
    /// Return true if the table has an entry (row) containing all zeros.
    pub fn has_zero_entry(&self) -> bool {
        // reminder: a table is written as a list of columns,
        // not as a list of row entries.
        for row in 0..self.len() {
            if self.data.iter().all(|col| col[row].is_zero()) {
                return true;
            }
        }
        false
    }

    /// Returns the number of columns, i.e. the width of the table.
    /// It is less error prone to introduce this method than using the public
    /// field data.
    pub fn width(&self) -> usize {
        self.data.len()
    }

    /// Returns the length of the table.
    pub fn len(&self) -> usize {
        self.data[0].len()
    }

    /// Returns `true` if the lookup table is empty, `false` otherwise.
    pub fn is_empty(&self) -> bool {
        self.data.is_empty()
    }
}

/// Returns the lookup table associated to a [`GateLookupTable`].
pub fn get_table<F: FftField>(table_name: GateLookupTable) -> LookupTable<F> {
    match table_name {
        GateLookupTable::Xor => xor::xor_table(),
        GateLookupTable::RangeCheck => range_check::range_check_table(),
    }
}

impl GateLookupTable {
    /// Returns the lookup table associated to a [`GateLookupTable`].
    pub fn table_size(&self) -> usize {
        match self {
            GateLookupTable::Xor => xor::TABLE_SIZE,
            GateLookupTable::RangeCheck => range_check::TABLE_SIZE,
        }
    }
}

/// Let's say we want to do a lookup in a "vector-valued" table `T: Vec<[F; n]>` (here I
/// am using `[F; n]` to model a vector of length `n`).
///
/// For `i < n`, define `T_i := T.map(|t| t[i]).collect()`. In other words, the table
/// obtained by taking the `ith` entry of each element of `T`.
///
/// In the lookup argument, we perform lookups in `T` by sampling a random challenge
/// `joint_combiner`, and computing a "combined" lookup table `sum_{i < n} joint_combiner^i T_i`.
///
/// To check a vector's membership in this lookup table, we combine the values in that vector
/// analogously using `joint_combiner`.
///
/// This function computes that combined value.
pub fn combine_table_entry<'a, F, I>(
    joint_combiner: &F,
    table_id_combiner: &F,
    v: I,
    // TODO: this should be an option?
    table_id: &F,
) -> F
where
    F: 'a, // Any references in `F` must have a lifetime longer than `'a`.
    F: Zero + One + Clone,
    I: DoubleEndedIterator<Item = &'a F>,
{
    v.rev()
        .fold(F::zero(), |acc, x| joint_combiner.clone() * acc + x.clone())
        + table_id_combiner.clone() * table_id.clone()
}

/// Same as [`combine_table_entry`], but for an entire table.
/// The function will panic if given an empty table (0 columns).
///
/// # Panics
///
/// Will panic if `columns` is empty.
pub fn combine_table<G>(
    columns: &[&PolyComm<G>],
    column_combiner: G::ScalarField,
    table_id_combiner: G::ScalarField,
    table_id_vector: Option<&PolyComm<G>>,
    runtime_vector: Option<&PolyComm<G>>,
) -> PolyComm<G>
where
    G: poly_commitment::commitment::CommitmentCurve,
{
    assert!(!columns.is_empty());

    // combine the columns
    let mut j = G::ScalarField::one();
    let mut scalars = vec![j];
    let mut commitments = vec![columns[0]];
    for comm in columns.iter().skip(1) {
        j *= column_combiner;
        scalars.push(j);
        commitments.push(comm);
    }

    // combine the table id
    if let Some(table_id) = table_id_vector {
        scalars.push(table_id_combiner);
        commitments.push(table_id);
    }

    // combine the runtime vector
    if let Some(runtime) = runtime_vector {
        scalars.push(column_combiner); // 2nd column idx is j^1
        commitments.push(runtime);
    }

    PolyComm::multi_scalar_mul(&commitments, &scalars)
}

#[cfg(feature = "ocaml_types")]
pub mod caml {
    use ark_ff::PrimeField;
    use ocaml;
    use ocaml_gen;

    use super::LookupTable;

    //
    // CamlLookupTable<CamlF>
    //

    #[derive(ocaml::IntoValue, ocaml::FromValue, ocaml_gen::Struct)]
    pub struct CamlLookupTable<CamlF> {
        pub id: i32,
        pub data: Vec<Vec<CamlF>>,
    }

    impl<F, CamlF> From<CamlLookupTable<CamlF>> for LookupTable<F>
    where
        F: PrimeField,
        CamlF: Into<F>,
    {
        fn from(caml_lt: CamlLookupTable<CamlF>) -> Self {
            Self {
                id: caml_lt.id,
                data: caml_lt
                    .data
                    .into_iter()
                    .map(|t| t.into_iter().map(Into::into).collect())
                    .collect(),
            }
        }
    }

    impl<F, CamlF> From<LookupTable<F>> for CamlLookupTable<CamlF>
    where
        F: PrimeField,
        CamlF: From<F>,
    {
        fn from(lt: LookupTable<F>) -> Self {
            Self {
                id: lt.id,
                data: lt
                    .data
                    .into_iter()
                    .map(|t| t.into_iter().map(Into::into).collect())
                    .collect(),
            }
        }
    }
}