arnaucube
arnaucube/miden-cryptoPublic Self-hosted repository
miden-crypto/src/main.rs
src/main.rsRaw
  1use std::time::Instant;
  2
  3use clap::Parser;
  4use miden_crypto::{
  5    hash::rpo::{Rpo256, RpoDigest},
  6    merkle::{MerkleError, Smt},
  7    Felt, Word, EMPTY_WORD, ONE,
  8};
  9use rand::{prelude::IteratorRandom, thread_rng, Rng};
 10use rand_utils::rand_value;
 11
 12#[derive(Parser, Debug)]
 13#[clap(name = "Benchmark", about = "SMT benchmark", version, rename_all = "kebab-case")]
 14pub struct BenchmarkCmd {
 15    /// Size of the tree
 16    #[clap(short = 's', long = "size")]
 17    size: usize,
 18}
 19
 20fn main() {
 21    benchmark_smt();
 22}
 23
 24/// Run a benchmark for [`Smt`].
 25pub fn benchmark_smt() {
 26    let args = BenchmarkCmd::parse();
 27    let tree_size = args.size;
 28
 29    // prepare the `leaves` vector for tree creation
 30    let mut entries = Vec::new();
 31    for i in 0..tree_size {
 32        let key = rand_value::<RpoDigest>();
 33        let value = [ONE, ONE, ONE, Felt::new(i as u64)];
 34        entries.push((key, value));
 35    }
 36
 37    let mut tree = construction(entries.clone(), tree_size).unwrap();
 38    insertion(&mut tree).unwrap();
 39    batched_insertion(&mut tree).unwrap();
 40    batched_update(&mut tree, entries).unwrap();
 41    proof_generation(&mut tree).unwrap();
 42}
 43
 44/// Runs the construction benchmark for [`Smt`], returning the constructed tree.
 45pub fn construction(entries: Vec<(RpoDigest, Word)>, size: usize) -> Result<Smt, MerkleError> {
 46    println!("Running a construction benchmark:");
 47    let now = Instant::now();
 48    let tree = Smt::with_entries(entries)?;
 49    let elapsed = now.elapsed().as_secs_f32();
 50
 51    println!("Constructed a SMT with {size} key-value pairs in {elapsed:.1} seconds");
 52    println!("Number of leaf nodes: {}\n", tree.leaves().count());
 53
 54    Ok(tree)
 55}
 56
 57/// Runs the insertion benchmark for the [`Smt`].
 58pub fn insertion(tree: &mut Smt) -> Result<(), MerkleError> {
 59    const NUM_INSERTIONS: usize = 1_000;
 60
 61    println!("Running an insertion benchmark:");
 62
 63    let size = tree.num_leaves();
 64
 65    let mut insertion_times = Vec::new();
 66
 67    for i in 0..NUM_INSERTIONS {
 68        let test_key = Rpo256::hash(&rand_value::<u64>().to_be_bytes());
 69        let test_value = [ONE, ONE, ONE, Felt::new((size + i) as u64)];
 70
 71        let now = Instant::now();
 72        tree.insert(test_key, test_value);
 73        let elapsed = now.elapsed();
 74        insertion_times.push(elapsed.as_micros());
 75    }
 76
 77    println!(
 78        "An average insertion time measured by {NUM_INSERTIONS} inserts into an SMT with {size} leaves is {:.0} μs\n",
 79        // calculate the average
 80        insertion_times.iter().sum::<u128>() as f64 / (NUM_INSERTIONS as f64),
 81    );
 82
 83    Ok(())
 84}
 85
 86pub fn batched_insertion(tree: &mut Smt) -> Result<(), MerkleError> {
 87    const NUM_INSERTIONS: usize = 1_000;
 88
 89    println!("Running a batched insertion benchmark:");
 90
 91    let size = tree.num_leaves();
 92
 93    let new_pairs: Vec<(RpoDigest, Word)> = (0..NUM_INSERTIONS)
 94        .map(|i| {
 95            let key = Rpo256::hash(&rand_value::<u64>().to_be_bytes());
 96            let value = [ONE, ONE, ONE, Felt::new((size + i) as u64)];
 97            (key, value)
 98        })
 99        .collect();
100
101    let now = Instant::now();
102    let mutations = tree.compute_mutations(new_pairs);
103    let compute_elapsed = now.elapsed().as_secs_f64() * 1000_f64; // time in ms
104
105    let now = Instant::now();
106    tree.apply_mutations(mutations)?;
107    let apply_elapsed = now.elapsed().as_secs_f64() * 1000_f64; // time in ms
108
109    println!(
110        "An average insert-batch computation time measured by a {NUM_INSERTIONS}-batch into an SMT with {size} leaves over {:.1} ms is {:.0} μs",
111        compute_elapsed,
112        compute_elapsed * 1000_f64 / NUM_INSERTIONS as f64, // time in μs
113    );
114
115    println!(
116        "An average insert-batch application time measured by a {NUM_INSERTIONS}-batch into an SMT with {size} leaves over {:.1} ms is {:.0} μs",
117        apply_elapsed,
118        apply_elapsed * 1000_f64 / NUM_INSERTIONS as f64, // time in μs
119    );
120
121    println!(
122        "An average batch insertion time measured by a 1k-batch into an SMT with {size} leaves totals to {:.1} ms",
123        (compute_elapsed + apply_elapsed),
124    );
125
126    println!();
127
128    Ok(())
129}
130
131pub fn batched_update(tree: &mut Smt, entries: Vec<(RpoDigest, Word)>) -> Result<(), MerkleError> {
132    const NUM_UPDATES: usize = 1_000;
133    const REMOVAL_PROBABILITY: f64 = 0.2;
134
135    println!("Running a batched update benchmark:");
136
137    let size = tree.num_leaves();
138    let mut rng = thread_rng();
139
140    let new_pairs =
141        entries
142            .into_iter()
143            .choose_multiple(&mut rng, NUM_UPDATES)
144            .into_iter()
145            .map(|(key, _)| {
146                let value = if rng.gen_bool(REMOVAL_PROBABILITY) {
147                    EMPTY_WORD
148                } else {
149                    [ONE, ONE, ONE, Felt::new(rng.gen())]
150                };
151
152                (key, value)
153            });
154
155    assert_eq!(new_pairs.len(), NUM_UPDATES);
156
157    let now = Instant::now();
158    let mutations = tree.compute_mutations(new_pairs);
159    let compute_elapsed = now.elapsed().as_secs_f64() * 1000_f64; // time in ms
160
161    let now = Instant::now();
162    tree.apply_mutations(mutations)?;
163    let apply_elapsed = now.elapsed().as_secs_f64() * 1000_f64; // time in ms
164
165    println!(
166        "An average update-batch computation time measured by a {NUM_UPDATES}-batch into an SMT with {size} leaves over {:.1} ms is {:.0} μs",
167        compute_elapsed,
168        compute_elapsed * 1000_f64 / NUM_UPDATES as f64, // time in μs
169    );
170
171    println!(
172        "An average update-batch application time measured by a {NUM_UPDATES}-batch into an SMT with {size} leaves over {:.1} ms is {:.0} μs",
173        apply_elapsed,
174        apply_elapsed * 1000_f64 / NUM_UPDATES as f64, // time in μs
175    );
176
177    println!(
178        "An average batch update time measured by a 1k-batch into an SMT with {size} leaves totals to {:.1} ms",
179        (compute_elapsed + apply_elapsed),
180    );
181
182    println!();
183
184    Ok(())
185}
186
187/// Runs the proof generation benchmark for the [`Smt`].
188pub fn proof_generation(tree: &mut Smt) -> Result<(), MerkleError> {
189    const NUM_PROOFS: usize = 100;
190
191    println!("Running a proof generation benchmark:");
192
193    let mut insertion_times = Vec::new();
194
195    let size = tree.num_leaves();
196
197    for i in 0..NUM_PROOFS {
198        let test_key = Rpo256::hash(&rand_value::<u64>().to_be_bytes());
199        let test_value = [ONE, ONE, ONE, Felt::new((size + i) as u64)];
200        tree.insert(test_key, test_value);
201
202        let now = Instant::now();
203        let _proof = tree.open(&test_key);
204        insertion_times.push(now.elapsed().as_micros());
205    }
206
207    println!(
208        "An average proving time measured by {NUM_PROOFS} value proofs in an SMT with {size} leaves in {:.0} μs",
209        // calculate the average
210        insertion_times.iter().sum::<u128>() as f64 / (NUM_PROOFS as f64),
211    );
212
213    Ok(())
214}