Skip to main content

tasm_lib/verifier/master_table/
verify_table_rows.rs

1use strum::Display;
2use strum::EnumIter;
3use triton_vm::prelude::*;
4use triton_vm::table::NUM_QUOTIENT_SEGMENTS;
5use triton_vm::table::master_table::MasterAuxTable;
6use triton_vm::table::master_table::MasterMainTable;
7use twenty_first::math::x_field_element::EXTENSION_DEGREE;
8
9use crate::hashing::algebraic_hasher::hash_static_size::HashStaticSize;
10use crate::prelude::*;
11
12#[derive(Debug, Copy, Clone, Display, EnumIter)]
13pub enum ColumnType {
14    Main,
15    Aux,
16    Quotient,
17}
18
19/// Crashes the VM is the table rows do not authenticate against the provided Merkle root
20/// First hashes the rows, then verifies that the digests belong in the Merkle tree.
21#[derive(Debug, Copy, Clone)]
22pub struct VerifyTableRows {
23    pub column_type: ColumnType,
24}
25
26impl VerifyTableRows {
27    pub fn new(column_type: ColumnType) -> Self {
28        Self { column_type }
29    }
30}
31
32impl VerifyTableRows {
33    pub fn row_size(&self) -> usize {
34        match self.column_type {
35            ColumnType::Main => MasterMainTable::NUM_COLUMNS,
36            ColumnType::Aux => MasterAuxTable::NUM_COLUMNS * EXTENSION_DEGREE,
37            ColumnType::Quotient => NUM_QUOTIENT_SEGMENTS * EXTENSION_DEGREE,
38        }
39    }
40}
41
42impl BasicSnippet for VerifyTableRows {
43    fn parameters(&self) -> Vec<(DataType, String)> {
44        vec![
45            (DataType::U32, "num_combination_codeword_checks".to_owned()),
46            (DataType::U32, "merkle_tree_height".to_owned()),
47            (DataType::VoidPointer, "*merkle_tree_root".to_owned()),
48            (DataType::VoidPointer, "*fri_revealed".to_owned()),
49            // type of {main|aux|quot} table rows i
50            // `Vec<[{BaseFieldElement, XFieldElement, XFieldElement}: COLUMN_COUNT]>` but encoded
51            // in memory as a flat structure. So I'm not sure what type to use here. Anyway, it's
52            // certainly a list.
53            (DataType::VoidPointer, "*table_rows".to_owned()),
54        ]
55    }
56
57    fn return_values(&self) -> Vec<(DataType, String)> {
58        vec![]
59    }
60
61    fn entrypoint(&self) -> String {
62        format!(
63            "tasmlib_verifier_master_table_verify_{}_table_rows",
64            self.column_type
65        )
66    }
67
68    fn code(&self, library: &mut Library) -> Vec<LabelledInstruction> {
69        let entrypoint = self.entrypoint();
70
71        let hash_row = library.import(Box::new(HashStaticSize {
72            size: self.row_size(),
73        }));
74
75        let loop_over_auth_path_digests_label =
76            format!("{entrypoint}_loop_over_auth_path_elements");
77        let loop_over_auth_paths_code = triton_asm!(
78            {loop_over_auth_path_digests_label}:
79                merkle_step                 // move up one level in the Merkle tree
80                recurse_or_return           // break loop if node_index is 1
81        );
82
83        let loop_over_rows_label = format!("{entrypoint}_loop_over_rows");
84        let loop_over_rows_code = triton_asm!(
85            // Invariant: _ remaining_iterations num_leaves *merkle_tree_root *fri_revealed_leaf_index *row_elem [mt_root]
86            {loop_over_rows_label}:
87                // Check end-loop condition
88                dup 9
89                push 0
90                eq
91                skiz
92                    return
93
94                // _ remaining_iterations num_leaves *merkle_tree_root *fri_revealed_leaf_index *row_elem [mt_root]
95
96                push 1
97                // _ remaining_iterations num_leaves *merkle_tree_root *fri_revealed_leaf_index *row_elem [mt_root] 1
98
99                /* 2. */
100                dup 7
101                read_mem 1
102                push {EXTENSION_DEGREE + 2}
103                add
104                // _ remaining_iterations num_leaves *merkle_tree_root *fri_revealed_leaf_index *row_elem [mt_root] 1 fri_revealed_leaf_index (*fri_revealed_leaf_index + 4)
105                // _ remaining_iterations num_leaves *merkle_tree_root *fri_revealed_leaf_index *row_elem [mt_root] 1 fri_revealed_leaf_index *fri_revealed_leaf_index_next
106
107                swap 9
108                pop 1
109                // _ remaining_iterations num_leaves *merkle_tree_root *fri_revealed_leaf_index_next *row_elem [mt_root] 1 fri_revealed_leaf_index
110
111                dup 10
112                add
113                // _ remaining_iterations num_leaves *merkle_tree_root *fri_revealed_leaf_index_next *row_elem [mt_root] 1 node_index
114
115                /* 3. */
116                dup 7
117                call {hash_row}
118                // _ remaining_iterations num_leaves *merkle_tree_root *fri_revealed_leaf_index_next *row_elem [mt_root] 1 node_index [row_digest] *row_elem_next
119
120                swap 13
121                pop 1
122                // _ remaining_iterations num_leaves *merkle_tree_root *fri_revealed_leaf_index_next *row_elem_next [mt_root] 1 node_index [row_digest]
123
124                call {loop_over_auth_path_digests_label}
125                // _ remaining_iterations num_leaves *merkle_tree_root *fri_revealed_leaf_index_next *row_elem_next [mt_root] 1 1 [calculated_root]
126
127                swap 2
128                swap 4
129                swap 6
130                pop 1
131                swap 2
132                swap 4
133                pop 1
134                // _ remaining_iterations num_leaves *merkle_tree_root *fri_revealed_leaf_index_next *row_elem_next [mt_root] [calculated_root]
135
136                assert_vector error_id 40
137                // _ remaining_iterations num_leaves *merkle_tree_root *fri_revealed_leaf_index_next *row_elem_next [mt_root]
138
139                swap 9
140                push -1
141                add
142                swap 9
143                // _ (remaining_iterations - 1) num_leaves *merkle_tree_root *fri_revealed_leaf_index_next *row_elem_next [mt_root]
144
145                recurse
146
147                {&loop_over_auth_paths_code}
148        );
149
150        triton_asm!(
151            // _ num_combination_codeword_checks merkle_tree_height *merkle_tree_root *fri_revealed *table_rows
152            {entrypoint}:
153
154                swap 1
155                push {1 + EXTENSION_DEGREE}
156                add
157                swap 1
158                // _ num_combination_codeword_checks merkle_tree_height *merkle_tree_root (*fri_revealed_first_elem.0) *table_rows
159
160                // Verify length of `rows`
161                read_mem 1
162                push 2
163                add
164                swap 1
165                // _ num_combination_codeword_checks merkle_tree_height *merkle_tree_root (*fri_revealed_first_elem.0) *table_rows[0] length
166
167                dup 5
168                eq
169                assert error_id 41
170                // _ num_combination_codeword_checks merkle_tree_height *merkle_tree_root (*fri_revealed_first_elem.0) *table_rows[0]
171
172                swap 3
173                push 2
174                pow
175                // _ num_combination_codeword_checks *table_rows[0] *merkle_tree_root (*fri_revealed_first_elem.0) (2^merkle_tree_height)
176                // _ num_combination_codeword_checks *table_rows[0] *merkle_tree_root (*fri_revealed_first_elem.0) num_leaves
177
178                swap 3
179                // _ num_combination_codeword_checks num_leaves *merkle_tree_root (*fri_revealed_first_elem.0) *table_rows[0]
180
181                dup 2
182                push {Digest::LEN - 1}
183                add
184                read_mem {Digest::LEN}
185                pop 1
186
187                // _ num_combination_codeword_checks num_leaves *merkle_tree_root (*fri_revealed_first_elem.0) *table_rows[0] [mt_root]
188
189                call {loop_over_rows_label}
190                // _ 0 num_leaves *merkle_tree_root *fri_revealed_leaf_index_next *row_elem_next [mt_root]
191
192                pop 5
193                pop 5
194                // _
195
196                return
197
198            {&loop_over_rows_code}
199        )
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use twenty_first::math::other::random_elements;
206    use twenty_first::prelude::*;
207    use twenty_first::tip5::RATE;
208
209    use super::*;
210    use crate::memory::encode_to_memory;
211    use crate::rust_shadowing_helper_functions::list::list_insert;
212    use crate::test_prelude::*;
213
214    #[macro_rules_attr::apply(test)]
215    fn verify_table_pbt_main() {
216        ShadowedProcedure::new(VerifyTableRows {
217            column_type: ColumnType::Main,
218        })
219        .test()
220    }
221
222    #[macro_rules_attr::apply(test)]
223    fn verify_table_pbt_aux() {
224        ShadowedProcedure::new(VerifyTableRows {
225            column_type: ColumnType::Aux,
226        })
227        .test()
228    }
229
230    #[macro_rules_attr::apply(test)]
231    fn verify_table_pbt_quot() {
232        ShadowedProcedure::new(VerifyTableRows {
233            column_type: ColumnType::Quotient,
234        })
235        .test()
236    }
237
238    mod negative_tests {
239        use strum::IntoEnumIterator;
240        use tasm_lib::test_helpers::test_assertion_failure;
241
242        use super::*;
243
244        #[macro_rules_attr::apply(proptest(cases = 50))]
245        fn verify_bad_auth_path_crashes_vm(seed: [u8; 32]) {
246            let mut rng = StdRng::from_seed(seed);
247            let snippets = ColumnType::iter().map(|column_type| VerifyTableRows { column_type });
248
249            for snippet in snippets {
250                let mut init_state = snippet.pseudorandom_initial_state(seed, None);
251                let num_digests = init_state.nondeterminism.digests.len();
252                init_state.nondeterminism.digests[rng.random_range(0..num_digests)] = rng.random();
253
254                test_assertion_failure(&ShadowedProcedure::new(snippet), init_state.into(), &[40]);
255            }
256        }
257
258        #[macro_rules_attr::apply(proptest(cases = 50))]
259        fn verify_bad_row_list_length(seed: [u8; 32]) {
260            let snippets = ColumnType::iter().map(|column_type| VerifyTableRows { column_type });
261            for snippet in snippets {
262                let mut init_state = snippet.pseudorandom_initial_state(seed, None);
263
264                // Mutate `num_combination_codeword_checks` to make it invalid
265                let init_stack_length = init_state.stack.len();
266                init_state.stack[init_stack_length - 5].increment();
267
268                test_assertion_failure(&ShadowedProcedure::new(snippet), init_state.into(), &[41]);
269            }
270        }
271    }
272
273    // TODO: Add negative tests, to verify that VM crashes if fed a leaf index that's not a valid
274    // u32.
275
276    impl Procedure for VerifyTableRows {
277        fn rust_shadow(
278            &self,
279            stack: &mut Vec<BFieldElement>,
280            memory: &mut HashMap<BFieldElement, BFieldElement>,
281            nondeterminism: &NonDeterminism,
282            _public_input: &[BFieldElement],
283            sponge_arg: &mut Option<Tip5>,
284        ) -> Result<Vec<BFieldElement>, RustShadowError> {
285            fn verify_one_row(
286                leaf_index: u32,
287                merkle_root: Digest,
288                merkle_tree_height: u32,
289                authentication_path: Vec<Digest>,
290                row: &[BFieldElement],
291                sponge: &mut Tip5,
292            ) -> Result<(), RustShadowError> {
293                // We define a local hash_varlen to be able to simulate what happens to the sponge,
294                // as this is required by the test framework.
295                fn local_hash_varlen(input: &[BFieldElement], sponge: &mut Tip5) -> Digest {
296                    *sponge = Tip5::init();
297                    sponge.pad_and_absorb_all(input);
298                    let produce: [BFieldElement; RATE] = sponge.squeeze();
299
300                    Digest::new((&produce[..Digest::LEN]).try_into().unwrap())
301                }
302
303                let leaf_digest = local_hash_varlen(row, sponge);
304                let merkle_tree_inclusion_proof = MerkleTreeInclusionProof {
305                    tree_height: merkle_tree_height,
306                    indexed_leafs: vec![(leaf_index as usize, leaf_digest)],
307                    authentication_structure: authentication_path,
308                };
309
310                merkle_tree_inclusion_proof
311                    .verify(merkle_root)
312                    .then_some(())
313                    .ok_or(RustShadowError::InvalidProof)
314            }
315
316            let table_rows_pointer = stack.pop().ok_or(RustShadowError::StackUnderflow)?;
317            let fri_revealed_pointer = stack.pop().ok_or(RustShadowError::StackUnderflow)?;
318            let merkle_tree_root_pointer = stack.pop().ok_or(RustShadowError::StackUnderflow)?;
319            let merkle_tree_height: u32 = stack
320                .pop()
321                .ok_or(RustShadowError::StackUnderflow)?
322                .try_into()
323                .map_err(|_| RustShadowError::U64ToU32Error)?;
324            let num_combination_codeword_checks: u32 = stack
325                .pop()
326                .ok_or(RustShadowError::StackUnderflow)?
327                .try_into()
328                .map_err(|_| RustShadowError::U64ToU32Error)?;
329
330            let merkle_root_innards = (0..Digest::LEN)
331                .map(|i| {
332                    memory
333                        .get(&(merkle_tree_root_pointer + bfe!(i as u32)))
334                        .ok_or(RustShadowError::DecodingError)
335                        .copied()
336                })
337                .try_collect::<_, Vec<_>, _>()?;
338            let merkle_root = Digest::new(merkle_root_innards.try_into().unwrap());
339
340            // Verify all rows
341            let mut sponge = Tip5::init();
342            let mut j = 0;
343            for i in 0..num_combination_codeword_checks {
344                // Read a row from memory
345                let row: Vec<_> = (0..self.row_size())
346                    .map(|l| {
347                        let word_pointer = table_rows_pointer
348                            + bfe!(l as u64 + 1 + (self.row_size() as u64) * i as u64);
349                        memory
350                            .get(&word_pointer)
351                            .copied()
352                            .ok_or(RustShadowError::DecodingError)
353                    })
354                    .try_collect()?;
355
356                // Read leaf index as provided by the FRI verifier
357                let leaf_index: u32 = nondeterminism
358                    .ram
359                    .get(&(fri_revealed_pointer + bfe!(4) + BFieldElement::new(i as u64 * 4)))
360                    .copied()
361                    .ok_or(RustShadowError::DecodingError)?
362                    .try_into()
363                    .map_err(|_| RustShadowError::U64ToU32Error)?;
364                let mut authentication_path = vec![];
365                for _ in 0..merkle_tree_height {
366                    authentication_path.push(nondeterminism.digests[j]);
367                    j += 1;
368                }
369
370                verify_one_row(
371                    leaf_index,
372                    merkle_root,
373                    merkle_tree_height,
374                    authentication_path,
375                    &row,
376                    &mut sponge,
377                )?;
378            }
379            *sponge_arg = Some(sponge);
380
381            Ok(Vec::new())
382        }
383
384        fn pseudorandom_initial_state(
385            &self,
386            seed: [u8; 32],
387            bench_case: Option<BenchmarkCase>,
388        ) -> ProcedureInitialState {
389            let mut rng = StdRng::from_seed(seed);
390            let merkle_tree_height = match bench_case {
391                Some(BenchmarkCase::CommonCase) => 17,
392                Some(BenchmarkCase::WorstCase) => 22,
393                None => rng.random_range(2..7),
394            };
395            let num_leafs = 1 << merkle_tree_height;
396            let num_combination_codeword_checks = 3;
397            let mut memory = HashMap::default();
398
399            let rows: Vec<Vec<BFieldElement>> =
400                vec![vec![rng.random(); self.row_size()]; num_combination_codeword_checks];
401            let leaf_indices: Vec<usize> = (0..num_combination_codeword_checks)
402                .map(|_| rng.random_range(0..num_leafs))
403                .collect_vec();
404
405            // Construct Merkle tree with specified rows as preimages to the leafs at the specified
406            // indices.
407            let mut leafs: Vec<Digest> = random_elements(1 << merkle_tree_height);
408            for (leaf_index, leaf_preimage) in leaf_indices.iter().zip_eq(rows.iter()) {
409                leafs[*leaf_index] = Tip5::hash_varlen(leaf_preimage);
410            }
411
412            let merkle_tree = MerkleTree::par_new(&leafs).unwrap();
413            let merkle_root = merkle_tree.root();
414            let merkle_root_pointer: BFieldElement = rng.random();
415            encode_to_memory(&mut memory, merkle_root_pointer, &merkle_root);
416
417            // Insert all rows into memory, as a list
418            let row_pointer: BFieldElement = rng.random();
419            memory.insert(row_pointer, bfe!(num_combination_codeword_checks as u64));
420            let mut j: BFieldElement = bfe!(1);
421            for row in rows {
422                for word in row {
423                    memory.insert(row_pointer + j, word);
424                    j.increment();
425                }
426            }
427
428            let mocked_fri_return_value: Vec<(u32, XFieldElement)> = leaf_indices
429                .iter()
430                .map(|x| *x as u32)
431                .zip((0..num_combination_codeword_checks).map(|_| rng.random()))
432                .collect_vec();
433            let fri_return_value_pointer = rng.random();
434            list_insert(
435                fri_return_value_pointer,
436                mocked_fri_return_value,
437                &mut memory,
438            );
439
440            let mut authentication_paths: Vec<Digest> = vec![];
441            for leaf_index in leaf_indices {
442                authentication_paths
443                    .extend(merkle_tree.authentication_structure(&[leaf_index]).unwrap());
444            }
445
446            let stack = [
447                self.init_stack_for_isolated_run(),
448                vec![
449                    bfe!(num_combination_codeword_checks as u64),
450                    bfe!(merkle_tree_height),
451                    merkle_root_pointer,
452                    fri_return_value_pointer,
453                    row_pointer,
454                ],
455            ]
456            .concat();
457            ProcedureInitialState {
458                stack,
459                nondeterminism: NonDeterminism::default()
460                    .with_ram(memory)
461                    .with_digests(authentication_paths),
462                public_input: vec![],
463                sponge: None,
464            }
465        }
466    }
467}
468
469#[cfg(test)]
470mod benches {
471    use super::*;
472    use crate::test_prelude::*;
473
474    #[macro_rules_attr::apply(test)]
475    fn verify_table_bench_main() {
476        ShadowedProcedure::new(VerifyTableRows {
477            column_type: ColumnType::Main,
478        })
479        .bench()
480    }
481
482    #[macro_rules_attr::apply(test)]
483    fn verify_table_bench_aux() {
484        ShadowedProcedure::new(VerifyTableRows {
485            column_type: ColumnType::Aux,
486        })
487        .bench()
488    }
489
490    #[macro_rules_attr::apply(test)]
491    fn verify_table_bench_quot() {
492        ShadowedProcedure::new(VerifyTableRows {
493            column_type: ColumnType::Quotient,
494        })
495        .bench()
496    }
497}