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 inputs(&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 outputs(&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    #[test]
215    fn verify_table_pbt_main() {
216        ShadowedProcedure::new(VerifyTableRows {
217            column_type: ColumnType::Main,
218        })
219        .test()
220    }
221
222    #[test]
223    fn verify_table_pbt_aux() {
224        ShadowedProcedure::new(VerifyTableRows {
225            column_type: ColumnType::Aux,
226        })
227        .test()
228    }
229
230    #[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        use test_strategy::proptest;
242
243        use super::*;
244
245        #[proptest(cases = 50)]
246        fn verify_bad_auth_path_crashes_vm(seed: [u8; 32]) {
247            let mut rng = StdRng::from_seed(seed);
248            let snippets = ColumnType::iter().map(|column_type| VerifyTableRows { column_type });
249
250            for snippet in snippets {
251                let mut init_state = snippet.pseudorandom_initial_state(seed, None);
252                let num_digests = init_state.nondeterminism.digests.len();
253                init_state.nondeterminism.digests[rng.random_range(0..num_digests)] = rng.random();
254
255                test_assertion_failure(&ShadowedProcedure::new(snippet), init_state.into(), &[40]);
256            }
257        }
258
259        #[proptest(cases = 50)]
260        fn verify_bad_row_list_length(seed: [u8; 32]) {
261            let snippets = ColumnType::iter().map(|column_type| VerifyTableRows { column_type });
262            for snippet in snippets {
263                let mut init_state = snippet.pseudorandom_initial_state(seed, None);
264
265                // Mutate `num_combination_codeword_checks` to make it invalid
266                let init_stack_length = init_state.stack.len();
267                init_state.stack[init_stack_length - 5].increment();
268
269                test_assertion_failure(&ShadowedProcedure::new(snippet), init_state.into(), &[41]);
270            }
271        }
272    }
273
274    // TODO: Add negative tests, to verify that VM crashes if fed a leaf index that's not a valid
275    // u32.
276
277    impl Procedure for VerifyTableRows {
278        fn rust_shadow(
279            &self,
280            stack: &mut Vec<BFieldElement>,
281            memory: &mut HashMap<BFieldElement, BFieldElement>,
282            nondeterminism: &NonDeterminism,
283            _public_input: &[BFieldElement],
284            sponge: &mut Option<Tip5>,
285        ) -> Vec<BFieldElement> {
286            fn verify_one_row(
287                leaf_index: u32,
288                merkle_root: Digest,
289                merkle_tree_height: u32,
290                authentication_path: Vec<Digest>,
291                row: &[BFieldElement],
292                sponge: &mut Tip5,
293            ) {
294                // We define a local hash_varlen to be able to simulate what happens to the sponge,
295                // as this is required by the test framework.
296                fn local_hash_varlen(input: &[BFieldElement], sponge: &mut Tip5) -> Digest {
297                    *sponge = Tip5::init();
298                    sponge.pad_and_absorb_all(input);
299                    let produce: [BFieldElement; RATE] = sponge.squeeze();
300
301                    Digest::new((&produce[..Digest::LEN]).try_into().unwrap())
302                }
303
304                let leaf_digest = local_hash_varlen(row, sponge);
305                let merkle_tree_inclusion_proof = MerkleTreeInclusionProof {
306                    tree_height: merkle_tree_height,
307                    indexed_leafs: vec![(leaf_index as usize, leaf_digest)],
308                    authentication_structure: authentication_path,
309                };
310
311                assert!(merkle_tree_inclusion_proof.verify(merkle_root));
312            }
313
314            *sponge = Some(Tip5::init());
315            let table_rows_pointer = stack.pop().unwrap();
316            let fri_revealed_pointer = stack.pop().unwrap();
317            let merkle_tree_root_pointer = stack.pop().unwrap();
318            let merkle_tree_height: u32 = stack.pop().unwrap().try_into().unwrap();
319            let num_combination_codeword_checks: u32 = stack.pop().unwrap().try_into().unwrap();
320
321            let merkle_root = Digest::new(
322                (0..Digest::LEN)
323                    .map(|i| memory[&(merkle_tree_root_pointer + bfe!(i as u32))])
324                    .collect_vec()
325                    .try_into()
326                    .unwrap(),
327            );
328
329            // Verify all rows
330            let mut j = 0;
331            for i in 0..num_combination_codeword_checks {
332                // Read a row from memory
333                let row = (0..self.row_size())
334                    .map(|l| {
335                        memory[&(table_rows_pointer
336                            + bfe!(l as u64 + 1 + (self.row_size() as u64) * i as u64))]
337                    })
338                    .collect_vec();
339
340                // Read leaf index as provided by the FRI verifier
341                let leaf_index: u32 = nondeterminism.ram
342                    [&(fri_revealed_pointer + bfe!(4) + BFieldElement::new(i as u64 * 4))]
343                    .try_into()
344                    .unwrap();
345                let mut authentication_path = vec![];
346                for _ in 0..merkle_tree_height {
347                    authentication_path.push(nondeterminism.digests[j]);
348                    j += 1;
349                }
350
351                verify_one_row(
352                    leaf_index,
353                    merkle_root,
354                    merkle_tree_height,
355                    authentication_path,
356                    &row,
357                    sponge.as_mut().unwrap(),
358                )
359            }
360
361            vec![]
362        }
363
364        fn pseudorandom_initial_state(
365            &self,
366            seed: [u8; 32],
367            bench_case: Option<BenchmarkCase>,
368        ) -> ProcedureInitialState {
369            let mut rng = StdRng::from_seed(seed);
370            let merkle_tree_height = match bench_case {
371                Some(BenchmarkCase::CommonCase) => 17,
372                Some(BenchmarkCase::WorstCase) => 22,
373                None => rng.random_range(2..7),
374            };
375            let num_leafs = 1 << merkle_tree_height;
376            let num_combination_codeword_checks = 3;
377            let mut memory = HashMap::default();
378
379            let rows: Vec<Vec<BFieldElement>> =
380                vec![vec![rng.random(); self.row_size()]; num_combination_codeword_checks];
381            let leaf_indices: Vec<usize> = (0..num_combination_codeword_checks)
382                .map(|_| rng.random_range(0..num_leafs))
383                .collect_vec();
384
385            // Construct Merkle tree with specified rows as preimages to the leafs at the specified
386            // indices.
387            let mut leafs: Vec<Digest> = random_elements(1 << merkle_tree_height);
388            for (leaf_index, leaf_preimage) in leaf_indices.iter().zip_eq(rows.iter()) {
389                leafs[*leaf_index] = Tip5::hash_varlen(leaf_preimage);
390            }
391
392            let merkle_tree = MerkleTree::par_new(&leafs).unwrap();
393            let merkle_root = merkle_tree.root();
394            let merkle_root_pointer: BFieldElement = rng.random();
395            encode_to_memory(&mut memory, merkle_root_pointer, &merkle_root);
396
397            // Insert all rows into memory, as a list
398            let row_pointer: BFieldElement = rng.random();
399            memory.insert(row_pointer, bfe!(num_combination_codeword_checks as u64));
400            let mut j: BFieldElement = bfe!(1);
401            for row in rows {
402                for word in row {
403                    memory.insert(row_pointer + j, word);
404                    j.increment();
405                }
406            }
407
408            let mocked_fri_return_value: Vec<(u32, XFieldElement)> = leaf_indices
409                .iter()
410                .map(|x| *x as u32)
411                .zip((0..num_combination_codeword_checks).map(|_| rng.random()))
412                .collect_vec();
413            let fri_return_value_pointer = rng.random();
414            list_insert(
415                fri_return_value_pointer,
416                mocked_fri_return_value,
417                &mut memory,
418            );
419
420            let mut authentication_paths: Vec<Digest> = vec![];
421            for leaf_index in leaf_indices {
422                authentication_paths
423                    .extend(merkle_tree.authentication_structure(&[leaf_index]).unwrap());
424            }
425
426            let stack = [
427                self.init_stack_for_isolated_run(),
428                vec![
429                    bfe!(num_combination_codeword_checks as u64),
430                    bfe!(merkle_tree_height),
431                    merkle_root_pointer,
432                    fri_return_value_pointer,
433                    row_pointer,
434                ],
435            ]
436            .concat();
437            ProcedureInitialState {
438                stack,
439                nondeterminism: NonDeterminism::default()
440                    .with_ram(memory)
441                    .with_digests(authentication_paths),
442                public_input: vec![],
443                sponge: None,
444            }
445        }
446    }
447}
448
449#[cfg(test)]
450mod benches {
451    use super::*;
452    use crate::test_prelude::*;
453
454    #[test]
455    fn verify_table_bench_main() {
456        ShadowedProcedure::new(VerifyTableRows {
457            column_type: ColumnType::Main,
458        })
459        .bench()
460    }
461
462    #[test]
463    fn verify_table_bench_aux() {
464        ShadowedProcedure::new(VerifyTableRows {
465            column_type: ColumnType::Aux,
466        })
467        .bench()
468    }
469
470    #[test]
471    fn verify_table_bench_quot() {
472        ShadowedProcedure::new(VerifyTableRows {
473            column_type: ColumnType::Quotient,
474        })
475        .bench()
476    }
477}