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    let mut insertion_times = Vec::new();
 65
 66    for i in 0..NUM_INSERTIONS {
 67        let test_key = Rpo256::hash(&rand_value::<u64>().to_be_bytes());
 68        let test_value = [ONE, ONE, ONE, Felt::new((size + i) as u64)];
 69
 70        let now = Instant::now();
 71        tree.insert(test_key, test_value);
 72        let elapsed = now.elapsed();
 73        insertion_times.push(elapsed.as_micros());
 74    }
 75
 76    println!(
 77        "An average insertion time measured by {NUM_INSERTIONS} inserts into an SMT with {size} leaves is {:.0} μs\n",
 78        // calculate the average
 79        insertion_times.iter().sum::<u128>() as f64 / (NUM_INSERTIONS as f64),
 80    );
 81
 82    Ok(())
 83}
 84
 85pub fn batched_insertion(tree: &mut Smt) -> Result<(), MerkleError> {
 86    const NUM_INSERTIONS: usize = 1_000;
 87
 88    println!("Running a batched insertion benchmark:");
 89
 90    let size = tree.num_leaves();
 91
 92    let new_pairs: Vec<(RpoDigest, Word)> = (0..NUM_INSERTIONS)
 93        .map(|i| {
 94            let key = Rpo256::hash(&rand_value::<u64>().to_be_bytes());
 95            let value = [ONE, ONE, ONE, Felt::new((size + i) as u64)];
 96            (key, value)
 97        })
 98        .collect();
 99
100    let now = Instant::now();
101    let mutations = tree.compute_mutations(new_pairs);
102    let compute_elapsed = now.elapsed().as_secs_f64() * 1000_f64; // time in ms
103
104    let now = Instant::now();
105    tree.apply_mutations(mutations)?;
106    let apply_elapsed = now.elapsed().as_secs_f64() * 1000_f64; // time in ms
107
108    println!(
109        "An average insert-batch computation time measured by a {NUM_INSERTIONS}-batch into an SMT with {size} leaves over {:.1} ms is {:.0} μs",
110        compute_elapsed,
111        compute_elapsed * 1000_f64 / NUM_INSERTIONS as f64, // time in μs
112    );
113
114    println!(
115        "An average insert-batch application time measured by a {NUM_INSERTIONS}-batch into an SMT with {size} leaves over {:.1} ms is {:.0} μs",
116        apply_elapsed,
117        apply_elapsed * 1000_f64 / NUM_INSERTIONS as f64, // time in μs
118    );
119
120    println!(
121        "An average batch insertion time measured by a 1k-batch into an SMT with {size} leaves totals to {:.1} ms",
122        (compute_elapsed + apply_elapsed),
123    );
124
125    println!();
126
127    Ok(())
128}
129
130pub fn batched_update(tree: &mut Smt, entries: Vec<(RpoDigest, Word)>) -> Result<(), MerkleError> {
131    const NUM_UPDATES: usize = 1_000;
132    const REMOVAL_PROBABILITY: f64 = 0.2;
133
134    println!("Running a batched update benchmark:");
135
136    let size = tree.num_leaves();
137    let mut rng = thread_rng();
138
139    let new_pairs =
140        entries
141            .into_iter()
142            .choose_multiple(&mut rng, NUM_UPDATES)
143            .into_iter()
144            .map(|(key, _)| {
145                let value = if rng.gen_bool(REMOVAL_PROBABILITY) {
146                    EMPTY_WORD
147                } else {
148                    [ONE, ONE, ONE, Felt::new(rng.gen())]
149                };
150
151                (key, value)
152            });
153
154    assert_eq!(new_pairs.len(), NUM_UPDATES);
155
156    let now = Instant::now();
157    let mutations = tree.compute_mutations(new_pairs);
158    let compute_elapsed = now.elapsed().as_secs_f64() * 1000_f64; // time in ms
159
160    let now = Instant::now();
161    tree.apply_mutations(mutations)?;
162    let apply_elapsed = now.elapsed().as_secs_f64() * 1000_f64; // time in ms
163
164    println!(
165        "An average update-batch computation time measured by a {NUM_UPDATES}-batch into an SMT with {size} leaves over {:.1} ms is {:.0} μs",
166        compute_elapsed,
167        compute_elapsed * 1000_f64 / NUM_UPDATES as f64, // time in μs
168    );
169
170    println!(
171        "An average update-batch application time measured by a {NUM_UPDATES}-batch into an SMT with {size} leaves over {:.1} ms is {:.0} μs",
172        apply_elapsed,
173        apply_elapsed * 1000_f64 / NUM_UPDATES as f64, // time in μs
174    );
175
176    println!(
177        "An average batch update time measured by a 1k-batch into an SMT with {size} leaves totals to {:.1} ms",
178        (compute_elapsed + apply_elapsed),
179    );
180
181    println!();
182
183    Ok(())
184}
185
186/// Runs the proof generation benchmark for the [`Smt`].
187pub fn proof_generation(tree: &mut Smt) -> Result<(), MerkleError> {
188    const NUM_PROOFS: usize = 100;
189
190    println!("Running a proof generation benchmark:");
191
192    let mut insertion_times = Vec::new();
193    let size = tree.num_leaves();
194
195    for i in 0..NUM_PROOFS {
196        let test_key = Rpo256::hash(&rand_value::<u64>().to_be_bytes());
197        let test_value = [ONE, ONE, ONE, Felt::new((size + i) as u64)];
198        tree.insert(test_key, test_value);
199
200        let now = Instant::now();
201        let _proof = tree.open(&test_key);
202        insertion_times.push(now.elapsed().as_micros());
203    }
204
205    println!(
206        "An average proving time measured by {NUM_PROOFS} value proofs in an SMT with {size} leaves in {:.0} μs",
207        // calculate the average
208        insertion_times.iter().sum::<u128>() as f64 / (NUM_PROOFS as f64),
209    );
210
211    Ok(())
212}