Skip to content

Commit 67ce638

Browse files
committed
feat: Add new snippet for read-only algorithm
And convert `merkle_verify` snippet to this trait. This is done to show that this snippet does not modify memory.
1 parent f15a171 commit 67ce638

File tree

3 files changed

+201
-16
lines changed

3 files changed

+201
-16
lines changed

tasm-lib/src/hashing/merkle_verify.rs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -114,24 +114,25 @@ impl BasicSnippet for MerkleVerify {
114114
#[cfg(test)]
115115
mod tests {
116116
use std::collections::HashMap;
117+
use std::collections::VecDeque;
117118

118119
use itertools::Itertools;
119120
use rand::prelude::*;
120121
use triton_vm::twenty_first::util_types::algebraic_hasher::AlgebraicHasher;
121122

122123
use crate::snippet_bencher::BenchmarkCase;
123124
use crate::test_helpers::negative_test;
124-
use crate::traits::algorithm::Algorithm;
125-
use crate::traits::algorithm::AlgorithmInitialState;
126-
use crate::traits::algorithm::ShadowedAlgorithm;
125+
use crate::traits::read_only_algorithm::ReadOnlyAlgorithm;
126+
use crate::traits::read_only_algorithm::ReadOnlyAlgorithmInitialState;
127+
use crate::traits::read_only_algorithm::ShadowedReadOnlyAlgorithm;
127128
use crate::traits::rust_shadow::RustShadow;
128129
use crate::VmHasher;
129130

130131
use super::*;
131132

132133
#[test]
133134
fn merkle_verify_test() {
134-
ShadowedAlgorithm::new(MerkleVerify).test()
135+
ShadowedReadOnlyAlgorithm::new(MerkleVerify).test()
135136
}
136137

137138
#[test]
@@ -185,19 +186,20 @@ mod tests {
185186
};
186187

187188
negative_test(
188-
&ShadowedAlgorithm::new(MerkleVerify),
189+
&ShadowedReadOnlyAlgorithm::new(MerkleVerify),
189190
init_state.into(),
190191
&allowed_error_codes,
191192
);
192193
}
193194
}
194195

195-
impl Algorithm for MerkleVerify {
196+
impl ReadOnlyAlgorithm for MerkleVerify {
196197
fn rust_shadow(
197198
&self,
198199
stack: &mut Vec<BFieldElement>,
199-
_memory: &mut HashMap<BFieldElement, BFieldElement>,
200-
nondeterminism: &NonDeterminism,
200+
_memory: &HashMap<BFieldElement, BFieldElement>,
201+
_nd_tokens: VecDeque<BFieldElement>,
202+
nd_digests: VecDeque<Digest>,
201203
) {
202204
// BEFORE: _ [root; 5] tree_height leaf_index [leaf; 5]
203205
// AFTER: _
@@ -223,11 +225,12 @@ mod tests {
223225
let mut sibling_height = 0;
224226
let mut node_index = leaf_index + num_leaves;
225227
while node_index != 1 {
226-
let sibling = nondeterminism.digests[sibling_height];
228+
let sibling = nd_digests[sibling_height];
227229
let node_is_left_sibling = node_index % 2 == 0;
228-
node_digest = match node_is_left_sibling {
229-
true => VmHasher::hash_pair(node_digest, sibling),
230-
false => VmHasher::hash_pair(sibling, node_digest),
230+
node_digest = if node_is_left_sibling {
231+
Tip5::hash_pair(node_digest, sibling)
232+
} else {
233+
Tip5::hash_pair(sibling, node_digest)
231234
};
232235
sibling_height += 1;
233236
node_index /= 2;
@@ -239,7 +242,7 @@ mod tests {
239242
&self,
240243
seed: [u8; 32],
241244
maybe_bench_case: Option<BenchmarkCase>,
242-
) -> AlgorithmInitialState {
245+
) -> ReadOnlyAlgorithmInitialState {
243246
{
244247
// BEFORE: _ [root; 5] tree_height leaf_index [leaf; 5]
245248
let mut rng: StdRng = SeedableRng::from_seed(seed);
@@ -280,7 +283,7 @@ mod tests {
280283
}
281284

282285
let nondeterminism = NonDeterminism::default().with_digests(path);
283-
AlgorithmInitialState {
286+
ReadOnlyAlgorithmInitialState {
284287
stack,
285288
nondeterminism,
286289
}
@@ -291,13 +294,13 @@ mod tests {
291294

292295
#[cfg(test)]
293296
mod bench {
294-
use crate::traits::algorithm::ShadowedAlgorithm;
297+
use crate::traits::read_only_algorithm::ShadowedReadOnlyAlgorithm;
295298
use crate::traits::rust_shadow::RustShadow;
296299

297300
use super::MerkleVerify;
298301

299302
#[test]
300303
fn merkle_verify_bench() {
301-
ShadowedAlgorithm::new(MerkleVerify).bench()
304+
ShadowedReadOnlyAlgorithm::new(MerkleVerify).bench()
302305
}
303306
}

tasm-lib/src/traits.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ pub mod deprecated_snippet;
77
pub mod function;
88
pub mod mem_preserver;
99
pub mod procedure;
10+
pub mod read_only_algorithm;
1011
pub mod rust_shadow;
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
use std::cell::RefCell;
2+
use std::collections::HashMap;
3+
use std::collections::VecDeque;
4+
use std::rc::Rc;
5+
6+
use rand::prelude::*;
7+
use triton_vm::prelude::*;
8+
9+
use crate::linker::execute_bench;
10+
use crate::linker::link_for_isolated_run;
11+
use crate::snippet_bencher::write_benchmarks;
12+
use crate::snippet_bencher::BenchmarkCase;
13+
use crate::snippet_bencher::NamedBenchmarkResult;
14+
use crate::test_helpers::test_rust_equivalence_given_complete_state;
15+
use crate::InitVmState;
16+
use crate::VmHasher;
17+
18+
use super::basic_snippet::BasicSnippet;
19+
use super::rust_shadow::RustShadow;
20+
21+
/// An ReadOnlyAlgorithm can read memory and take ND_input.
22+
///
23+
/// An ReadOnlyAlgorithm is a piece of tasm code that can read memory and read
24+
/// from non-determinism.
25+
pub trait ReadOnlyAlgorithm: BasicSnippet {
26+
fn rust_shadow(
27+
&self,
28+
stack: &mut Vec<BFieldElement>,
29+
memory: &HashMap<BFieldElement, BFieldElement>,
30+
nd_tokens: VecDeque<BFieldElement>,
31+
nd_digests: VecDeque<Digest>,
32+
);
33+
34+
fn pseudorandom_initial_state(
35+
&self,
36+
seed: [u8; 32],
37+
bench_case: Option<BenchmarkCase>,
38+
) -> ReadOnlyAlgorithmInitialState;
39+
40+
fn corner_case_initial_states(&self) -> Vec<ReadOnlyAlgorithmInitialState> {
41+
vec![]
42+
}
43+
}
44+
45+
#[derive(Debug, Clone, Default)]
46+
pub struct ReadOnlyAlgorithmInitialState {
47+
pub stack: Vec<BFieldElement>,
48+
pub nondeterminism: NonDeterminism,
49+
}
50+
51+
impl From<ReadOnlyAlgorithmInitialState> for InitVmState {
52+
fn from(value: ReadOnlyAlgorithmInitialState) -> Self {
53+
Self {
54+
stack: value.stack,
55+
nondeterminism: value.nondeterminism,
56+
..Default::default()
57+
}
58+
}
59+
}
60+
61+
pub struct ShadowedReadOnlyAlgorithm<T: ReadOnlyAlgorithm + 'static> {
62+
read_only_algorithm: Rc<RefCell<T>>,
63+
}
64+
65+
impl<T: ReadOnlyAlgorithm + 'static> ShadowedReadOnlyAlgorithm<T> {
66+
pub fn new(algorithm: T) -> Self {
67+
Self {
68+
read_only_algorithm: Rc::new(RefCell::new(algorithm)),
69+
}
70+
}
71+
}
72+
73+
impl<T> RustShadow for ShadowedReadOnlyAlgorithm<T>
74+
where
75+
T: ReadOnlyAlgorithm + 'static,
76+
{
77+
fn inner(&self) -> Rc<RefCell<dyn BasicSnippet>> {
78+
self.read_only_algorithm.clone()
79+
}
80+
81+
fn rust_shadow_wrapper(
82+
&self,
83+
_stdin: &[BFieldElement],
84+
nondeterminism: &NonDeterminism,
85+
stack: &mut Vec<BFieldElement>,
86+
memory: &mut HashMap<BFieldElement, BFieldElement>,
87+
_sponge: &mut Option<VmHasher>,
88+
) -> Vec<BFieldElement> {
89+
self.read_only_algorithm.borrow().rust_shadow(
90+
stack,
91+
memory,
92+
nondeterminism.individual_tokens.to_owned().into(),
93+
nondeterminism.digests.to_owned().into(),
94+
);
95+
vec![]
96+
}
97+
98+
fn test(&self) {
99+
for (i, corner_case) in self
100+
.read_only_algorithm
101+
.borrow()
102+
.corner_case_initial_states()
103+
.into_iter()
104+
.enumerate()
105+
{
106+
println!(
107+
"testing {} corner case number {i}",
108+
self.read_only_algorithm.borrow().entrypoint(),
109+
);
110+
111+
let stdin = vec![];
112+
test_rust_equivalence_given_complete_state(
113+
self,
114+
&corner_case.stack,
115+
&stdin,
116+
&corner_case.nondeterminism,
117+
&None,
118+
None,
119+
);
120+
}
121+
122+
let num_states = 10;
123+
let seed: [u8; 32] = thread_rng().gen();
124+
let mut rng: StdRng = SeedableRng::from_seed(seed);
125+
for _ in 0..num_states {
126+
let seed: [u8; 32] = rng.gen();
127+
println!(
128+
"testing {} common case with seed: {:#4x?}",
129+
self.read_only_algorithm.borrow().entrypoint(),
130+
seed
131+
);
132+
let ReadOnlyAlgorithmInitialState {
133+
stack,
134+
nondeterminism,
135+
} = self
136+
.read_only_algorithm
137+
.borrow()
138+
.pseudorandom_initial_state(seed, None);
139+
140+
let stdin = vec![];
141+
test_rust_equivalence_given_complete_state(
142+
self,
143+
&stack,
144+
&stdin,
145+
&nondeterminism,
146+
&None,
147+
None,
148+
);
149+
}
150+
}
151+
152+
fn bench(&self) {
153+
let mut rng: StdRng = SeedableRng::from_seed(
154+
hex::decode("73a24b6b8b32e4d7d563a4d9a85f476573a24b6b8b32e4d7d563a4d9a85f4765")
155+
.unwrap()
156+
.try_into()
157+
.unwrap(),
158+
);
159+
let mut benchmarks = Vec::with_capacity(2);
160+
161+
for bench_case in [BenchmarkCase::CommonCase, BenchmarkCase::WorstCase] {
162+
let ReadOnlyAlgorithmInitialState {
163+
stack,
164+
nondeterminism,
165+
} = self
166+
.read_only_algorithm
167+
.borrow()
168+
.pseudorandom_initial_state(rng.gen(), Some(bench_case));
169+
let program = link_for_isolated_run(self.read_only_algorithm.clone());
170+
let benchmark = execute_bench(&program, &stack, vec![], nondeterminism, None);
171+
let benchmark = NamedBenchmarkResult {
172+
name: self.read_only_algorithm.borrow().entrypoint(),
173+
benchmark_result: benchmark,
174+
case: bench_case,
175+
};
176+
benchmarks.push(benchmark);
177+
}
178+
179+
write_benchmarks(benchmarks);
180+
}
181+
}

0 commit comments

Comments
 (0)