From c1920e3a1a1b88c27a4fe215f4a3c746350a12b2 Mon Sep 17 00:00:00 2001 From: Himess <95512809+Himess@users.noreply.github.com> Date: Fri, 9 May 2025 08:01:46 +0300 Subject: [PATCH 1/6] docs: add module and function documentation (#408) --- CHANGELOG.md | 1 + .../src/dsa/rpo_falcon512/keys/public_key.rs | 12 ++++++++++++ .../src/dsa/rpo_falcon512/math/polynomial.rs | 15 +++++++++++++++ miden-crypto/src/dsa/rpo_falcon512/signature.rs | 7 ++++++- miden-crypto/src/hash/mod.rs | 4 ++++ miden-crypto/src/hash/rescue/rpo/digest.rs | 9 +++++++++ 6 files changed, 47 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a7928b..30b2376 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 0.15.0 (TBD) - Added default constructors to `MmrPeaks` and `PartialMmr` (#409). +- Add module and function documentation. (#408). ## 0.14.0 (2025-03-15) diff --git a/miden-crypto/src/dsa/rpo_falcon512/keys/public_key.rs b/miden-crypto/src/dsa/rpo_falcon512/keys/public_key.rs index 36eb849..7ad3f2f 100644 --- a/miden-crypto/src/dsa/rpo_falcon512/keys/public_key.rs +++ b/miden-crypto/src/dsa/rpo_falcon512/keys/public_key.rs @@ -1,3 +1,13 @@ +//! Public key types for the RPO Falcon 512 digital signature scheme used in Miden VM. +//! +//! This module defines two main types: +//! - `PublicKey`: A commitment to a polynomial, represented as a hash of the polynomial’s +//! coefficients. +//! - `PubKeyPoly`: A public key represented directly as a polynomial over FalconFelt coefficients. +//! +//! The `PublicKey` is used for signature verification. +//! The `PubKeyPoly` provides the raw polynomial form of a public key. + use alloc::string::ToString; use core::ops::Deref; @@ -50,6 +60,8 @@ impl From for Word { // PUBLIC KEY POLYNOMIAL // ================================================================================================ +/// Public key represented as a polynomial with coefficients over the Falcon prime field. +/// Used in the RPO Falcon 512 signature scheme. #[derive(Debug, Clone, PartialEq, Eq)] pub struct PubKeyPoly(pub Polynomial); diff --git a/miden-crypto/src/dsa/rpo_falcon512/math/polynomial.rs b/miden-crypto/src/dsa/rpo_falcon512/math/polynomial.rs index 5989ba1..7588458 100644 --- a/miden-crypto/src/dsa/rpo_falcon512/math/polynomial.rs +++ b/miden-crypto/src/dsa/rpo_falcon512/math/polynomial.rs @@ -1,3 +1,5 @@ +//! Generic polynomial type and operations used in Falcon. + use alloc::vec::Vec; use core::{ default::Default, @@ -13,8 +15,10 @@ use crate::{ dsa::rpo_falcon512::{MODULUS, N}, }; +/// Represents a polynomial with coefficients of type F. #[derive(Debug, Clone, Default)] pub struct Polynomial { + /// Coefficients of the polynomial, ordered from lowest to highest degree. pub coefficients: Vec, } @@ -22,6 +26,7 @@ impl Polynomial where F: Clone, { + /// Creates a new polynomial from the provided coefficients. pub fn new(coefficients: Vec) -> Self { Self { coefficients } } @@ -30,6 +35,7 @@ where impl + Sub + AddAssign + Zero + Div + Clone + Inverse> Polynomial { + /// Multiplies two polynomials coefficient-wise (Hadamard multiplication). pub fn hadamard_mul(&self, other: &Self) -> Self { Polynomial::new( self.coefficients @@ -39,6 +45,7 @@ impl + Sub + AddAssign + Zero + Div + .collect(), ) } + /// Divides two polynomials coefficient-wise (Hadamard division). pub fn hadamard_div(&self, other: &Self) -> Self { let other_coefficients_inverse = F::batch_inverse_or_zero(&other.coefficients); Polynomial::new( @@ -50,6 +57,7 @@ impl + Sub + AddAssign + Zero + Div + ) } + /// Computes the coefficient-wise inverse (Hadamard inverse). pub fn hadamard_inv(&self) -> Self { let coefficients_inverse = F::batch_inverse_or_zero(&self.coefficients); Polynomial::new(coefficients_inverse) @@ -57,6 +65,7 @@ impl + Sub + AddAssign + Zero + Div + } impl Polynomial { + /// Returns the degree of the polynomial. pub fn degree(&self) -> Option { if self.coefficients.is_empty() { return None; @@ -72,6 +81,7 @@ impl Polynomial { Some(max_index) } + /// Returns the leading coefficient of the polynomial. pub fn lc(&self) -> F { match self.degree() { Some(non_negative_degree) => self.coefficients[non_negative_degree].clone(), @@ -392,20 +402,24 @@ where } impl Polynomial { + /// Shifts the polynomial by the specified amount (adds leading zeros). pub fn shift(&self, shamt: usize) -> Self { Self { coefficients: [vec![F::zero(); shamt], self.coefficients.clone()].concat(), } } + /// Creates a constant polynomial with a single coefficient. pub fn constant(f: F) -> Self { Self { coefficients: vec![f] } } + /// Applies a function to each coefficient and returns a new polynomial. pub fn map G>(&self, closure: C) -> Polynomial { Polynomial::::new(self.coefficients.iter().map(closure).collect()) } + /// Folds the coefficients using the provided function and initial value. pub fn fold G + Clone>(&self, mut initial_value: G, closure: C) -> G { for c in self.coefficients.iter() { initial_value = (closure.clone())(initial_value, c); @@ -544,6 +558,7 @@ impl From<&Vec> for Polynomial { } impl Polynomial { + /// Computes the squared L2 norm of the polynomial. pub fn norm_squared(&self) -> u64 { self.coefficients .iter() diff --git a/miden-crypto/src/dsa/rpo_falcon512/signature.rs b/miden-crypto/src/dsa/rpo_falcon512/signature.rs index db35051..6011e98 100644 --- a/miden-crypto/src/dsa/rpo_falcon512/signature.rs +++ b/miden-crypto/src/dsa/rpo_falcon512/signature.rs @@ -56,6 +56,9 @@ pub struct Signature { impl Signature { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- + + /// Creates a new signature from the given nonce, public key polynomial, and signature + /// polynomial. pub fn new(nonce: Nonce, h: PubKeyPoly, s2: SignaturePoly) -> Signature { Self { header: SignatureHeader::default(), @@ -73,7 +76,7 @@ impl Signature { &self.h } - // Returns the polynomial representation of the signature in Z_p[x]/(phi). + /// Returns the polynomial representation of the signature in Z_p\[x\]/(phi). pub fn sig_poly(&self) -> &Polynomial { &self.s2 } @@ -124,6 +127,7 @@ impl Deserializable for Signature { // SIGNATURE HEADER // ================================================================================================ +/// The header byte used to encode the signature metadata. #[derive(Debug, Clone, PartialEq, Eq)] pub struct SignatureHeader(u8); @@ -174,6 +178,7 @@ impl Deserializable for SignatureHeader { // SIGNATURE POLYNOMIAL // ================================================================================================ +/// A polynomial used as the `s2` component of the signature. #[derive(Debug, Clone, PartialEq, Eq)] pub struct SignaturePoly(pub Polynomial); diff --git a/miden-crypto/src/hash/mod.rs b/miden-crypto/src/hash/mod.rs index e7fd9c7..3fe9777 100644 --- a/miden-crypto/src/hash/mod.rs +++ b/miden-crypto/src/hash/mod.rs @@ -2,13 +2,17 @@ use super::{CubeExtension, Felt, FieldElement, StarkField, ZERO}; +/// Blake2s hash function. pub mod blake; mod rescue; + +/// Rescue Prime Optimized (RPO) hash function. pub mod rpo { pub use super::rescue::{Rpo256, RpoDigest, RpoDigestError}; } +/// Rescue Prime Extended (RPX) hash function. pub mod rpx { pub use super::rescue::{Rpx256, RpxDigest, RpxDigestError}; } diff --git a/miden-crypto/src/hash/rescue/rpo/digest.rs b/miden-crypto/src/hash/rescue/rpo/digest.rs index 47d09c6..961a104 100644 --- a/miden-crypto/src/hash/rescue/rpo/digest.rs +++ b/miden-crypto/src/hash/rescue/rpo/digest.rs @@ -21,6 +21,7 @@ use crate::{ // DIGEST TRAIT IMPLEMENTATIONS // ================================================================================================ +/// An RPO digest consisting of 4 field elements. #[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(into = "String", try_from = "&str"))] @@ -30,18 +31,22 @@ impl RpoDigest { /// The serialized size of the digest in bytes. pub const SERIALIZED_SIZE: usize = DIGEST_BYTES; + /// Creates a new RpoDigest from the given field elements. pub const fn new(value: [Felt; DIGEST_SIZE]) -> Self { Self(value) } + /// Returns the digest as a slice of field elements. pub fn as_elements(&self) -> &[Felt] { self.as_ref() } + /// Returns the digest as a byte array. pub fn as_bytes(&self) -> [u8; DIGEST_BYTES] { ::as_bytes(self) } + /// Returns an iterator over the elements of multiple digests. pub fn digests_as_elements_iter<'a, I>(digests: I) -> impl Iterator where I: Iterator, @@ -49,6 +54,7 @@ impl RpoDigest { digests.flat_map(|d| d.0.iter()) } + /// Returns all elements of multiple digests as a slice. pub fn digests_as_elements(digests: &[Self]) -> &[Felt] { let p = digests.as_ptr(); let len = digests.len() * DIGEST_SIZE; @@ -141,10 +147,13 @@ impl Randomizable for RpoDigest { // CONVERSIONS: FROM RPO DIGEST // ================================================================================================ +/// Errors that can occur when working with RpoDigest. #[derive(Debug, Error)] pub enum RpoDigestError { + /// Failed to convert digest field element to the specified type. #[error("failed to convert digest field element to {0}")] TypeConversion(&'static str), + /// Field element conversion failed due to invalid value. #[error("failed to convert to field element: {0}")] InvalidFieldElement(String), } From 8649dd1e0444b1f7ae71adf18d7ac8a743dfa544 Mon Sep 17 00:00:00 2001 From: Himess <95512809+Himess@users.noreply.github.com> Date: Fri, 9 May 2025 10:28:55 +0300 Subject: [PATCH 2/6] docs: update old GitHub and GitHub Pages URLs to new 0xMiden naming (#414) --- CONTRIBUTING.md | 8 ++++---- Cargo.toml | 2 +- LICENSE | 2 +- README.md | 8 ++++---- miden-crypto/src/lib.rs | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e1452de..3cdc447 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,10 +4,10 @@ We want to make contributing to this project as easy and transparent as possible, whether it's: -- Reporting a [bug](https://github.com/0xPolygonMiden/crypto/issues/new) -- Taking part in [discussions](https://github.com/0xPolygonMiden/crypto/discussions) -- Submitting a [fix](https://github.com/0xPolygonMiden/crypto/pulls) -- Proposing new [features](https://github.com/0xPolygonMiden/crypto/issues/new) +- Reporting a [bug](https://github.com/0xMiden/crypto/issues/new) +- Taking part in [discussions](https://github.com/0xMiden/crypto/discussions) +- Submitting a [fix](https://github.com/0xMiden/crypto/pulls) +- Proposing new [features](https://github.com/0xMiden/crypto/issues/new)   diff --git a/Cargo.toml b/Cargo.toml index cf00b3a..bc198ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ resolver = "3" # Use the edition 2024 dependency resolver [workspace.package] authors = ["miden contributors"] license = "MIT" -repository = "https://github.com/0xPolygonMiden/crypto" +repository = "https://github.com/0xMiden/crypto" categories = ["cryptography", "no-std"] keywords = ["miden", "crypto", "hash", "merkle"] edition = "2024" diff --git a/LICENSE b/LICENSE index 58fc0d5..bab6bbd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Polygon Miden +Copyright (c) 2025 Miden Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 4bc9625..3bf5eef 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Miden Crypto -[![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/0xPolygonMiden/crypto/blob/main/LICENSE) -[![test](https://github.com/0xPolygonMiden/crypto/actions/workflows/test.yml/badge.svg)](https://github.com/0xPolygonMiden/crypto/actions/workflows/test.yml) -[![build](https://github.com/0xPolygonMiden/crypto/actions/workflows/build.yml/badge.svg)](https://github.com/0xPolygonMiden/crypto/actions/workflows/build.yml) +[![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/0xMiden/crypto/blob/main/LICENSE) +[![test](https://github.com/0xMiden/crypto/actions/workflows/test.yml/badge.svg)](https://github.com/0xMiden/crypto/actions/workflows/test.yml) +[![build](https://github.com/0xMiden/crypto/actions/workflows/build.yml/badge.svg)](https://github.com/0xMiden/crypto/actions/workflows/build.yml) [![RUST_VERSION](https://img.shields.io/badge/rustc-1.85+-lightgray.svg)](https://www.rust-lang.org/tools/install) [![CRATE](https://img.shields.io/crates/v/miden-crypto)](https://crates.io/crates/miden-crypto) -This crate contains cryptographic primitives used in Polygon Miden. +This crate contains cryptographic primitives used in Miden. ## Hash diff --git a/miden-crypto/src/lib.rs b/miden-crypto/src/lib.rs index 7e0a6d1..cc73c84 100644 --- a/miden-crypto/src/lib.rs +++ b/miden-crypto/src/lib.rs @@ -53,7 +53,7 @@ fn debug_assert_is_checked() { // downstream. // // for reference, check - // https://github.com/0xPolygonMiden/miden-vm/issues/433 + // https://github.com/0xMiden/miden-vm/issues/433 debug_assert!(false); } From 442123602c9911dd474a48931e5f3fe05be862cf Mon Sep 17 00:00:00 2001 From: Himess <95512809+Himess@users.noreply.github.com> Date: Fri, 9 May 2025 10:32:54 +0300 Subject: [PATCH 3/6] fix: replace deprecated #[clap(...)] with #[command(...)] and #[arg(...)] (#413) --- CHANGELOG.md | 1 + miden-crypto/benches/merkle.rs | 4 ++-- miden-crypto/benches/smt-subtree.rs | 6 +++--- miden-crypto/benches/smt-with-entries.rs | 6 +++--- miden-crypto/benches/smt.rs | 4 +--- miden-crypto/src/hash/rescue/rpo/digest.rs | 2 +- miden-crypto/src/hash/rescue/rpx/digest.rs | 2 +- miden-crypto/src/main.rs | 8 ++++---- miden-crypto/src/merkle/mmr/partial.rs | 8 ++------ miden-crypto/src/merkle/smt/full/concurrent/tests.rs | 9 +++------ miden-crypto/src/merkle/sparse_path.rs | 6 ++---- 11 files changed, 23 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30b2376..9ef7130 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 0.15.0 (TBD) +- Replace deprecated #[clap(...)] with #[command(...)] and #[arg(...)] (#413). - Added default constructors to `MmrPeaks` and `PartialMmr` (#409). - Add module and function documentation. (#408). diff --git a/miden-crypto/benches/merkle.rs b/miden-crypto/benches/merkle.rs index b815394..270ca8c 100644 --- a/miden-crypto/benches/merkle.rs +++ b/miden-crypto/benches/merkle.rs @@ -5,7 +5,7 @@ //! tree will perform better than the sparse version. At the time of this writing (2024/11/24), this //! benchmark is about four times more efficient than the equivalent benchmark in //! `benches/smt-subtree.rs`. -use std::{hint, mem, time::Duration}; +use std::{hint, time::Duration}; use criterion::{BatchSize, Criterion, criterion_group, criterion_main}; use miden_crypto::{Felt, ONE, Word, merkle::MerkleTree}; @@ -60,7 +60,7 @@ criterion_main!(smt_subtree_group); // -------------------------------------------------------------------------------------------- fn generate_word(seed: &mut [u8; 32]) -> Word { - mem::swap(seed, &mut prng_array(*seed)); + *seed = prng_array(*seed); let nums: [u64; 4] = prng_array(*seed); [Felt::new(nums[0]), Felt::new(nums[1]), Felt::new(nums[2]), Felt::new(nums[3])] } diff --git a/miden-crypto/benches/smt-subtree.rs b/miden-crypto/benches/smt-subtree.rs index 315ba3b..b2bc677 100644 --- a/miden-crypto/benches/smt-subtree.rs +++ b/miden-crypto/benches/smt-subtree.rs @@ -1,4 +1,4 @@ -use std::{fmt::Debug, hint, mem, time::Duration}; +use std::{fmt::Debug, hint, time::Duration}; use criterion::{BatchSize, BenchmarkId, Criterion, criterion_group, criterion_main}; use miden_crypto::{ @@ -131,13 +131,13 @@ criterion_main!(smt_subtree_group); // -------------------------------------------------------------------------------------------- fn generate_value(seed: &mut [u8; 32]) -> T { - mem::swap(seed, &mut prng_array(*seed)); + *seed = prng_array(*seed); let value: [T; 1] = rand_utils::prng_array(*seed); value[0] } fn generate_word(seed: &mut [u8; 32]) -> Word { - mem::swap(seed, &mut prng_array(*seed)); + *seed = prng_array(*seed); let nums: [u64; 4] = prng_array(*seed); [Felt::new(nums[0]), Felt::new(nums[1]), Felt::new(nums[2]), Felt::new(nums[3])] } diff --git a/miden-crypto/benches/smt-with-entries.rs b/miden-crypto/benches/smt-with-entries.rs index a9ca097..cb7cd85 100644 --- a/miden-crypto/benches/smt-with-entries.rs +++ b/miden-crypto/benches/smt-with-entries.rs @@ -1,4 +1,4 @@ -use std::{fmt::Debug, hint, mem, time::Duration}; +use std::{fmt::Debug, hint, time::Duration}; use criterion::{BatchSize, BenchmarkId, Criterion, criterion_group, criterion_main}; use miden_crypto::{Felt, ONE, Word, hash::rpo::RpoDigest, merkle::Smt}; @@ -59,13 +59,13 @@ fn prepare_entries(pair_count: u64, seed: &mut [u8; 32]) -> Vec<(RpoDigest, [Fel } fn generate_value(seed: &mut [u8; 32]) -> T { - mem::swap(seed, &mut prng_array(*seed)); + *seed = prng_array(*seed); let value: [T; 1] = rand_utils::prng_array(*seed); value[0] } fn generate_word(seed: &mut [u8; 32]) -> Word { - mem::swap(seed, &mut prng_array(*seed)); + *seed = prng_array(*seed); let nums: [u64; 4] = prng_array(*seed); [Felt::new(nums[0]), Felt::new(nums[1]), Felt::new(nums[2]), Felt::new(nums[3])] } diff --git a/miden-crypto/benches/smt.rs b/miden-crypto/benches/smt.rs index f8a81c9..6a5b700 100644 --- a/miden-crypto/benches/smt.rs +++ b/miden-crypto/benches/smt.rs @@ -1,5 +1,3 @@ -use core::mem::swap; - use criterion::{Criterion, black_box, criterion_group, criterion_main}; use miden_crypto::{ Felt, Word, @@ -71,7 +69,7 @@ criterion_main!(smt_group); // -------------------------------------------------------------------------------------------- fn generate_word(seed: &mut [u8; 32]) -> Word { - swap(seed, &mut prng_array(*seed)); + *seed = prng_array(*seed); let nums: [u64; 4] = prng_array(*seed); [Felt::new(nums[0]), Felt::new(nums[1]), Felt::new(nums[2]), Felt::new(nums[3])] } diff --git a/miden-crypto/src/hash/rescue/rpo/digest.rs b/miden-crypto/src/hash/rescue/rpo/digest.rs index 961a104..00bef42 100644 --- a/miden-crypto/src/hash/rescue/rpo/digest.rs +++ b/miden-crypto/src/hash/rescue/rpo/digest.rs @@ -126,7 +126,7 @@ impl PartialOrd for RpoDigest { impl Display for RpoDigest { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let encoded: String = self.into(); - write!(f, "{}", encoded)?; + write!(f, "{encoded}")?; Ok(()) } } diff --git a/miden-crypto/src/hash/rescue/rpx/digest.rs b/miden-crypto/src/hash/rescue/rpx/digest.rs index 9b83c72..f847f40 100644 --- a/miden-crypto/src/hash/rescue/rpx/digest.rs +++ b/miden-crypto/src/hash/rescue/rpx/digest.rs @@ -108,7 +108,7 @@ impl PartialOrd for RpxDigest { impl Display for RpxDigest { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let encoded: String = self.into(); - write!(f, "{}", encoded)?; + write!(f, "{encoded}")?; Ok(()) } } diff --git a/miden-crypto/src/main.rs b/miden-crypto/src/main.rs index 8559e9f..f999b78 100644 --- a/miden-crypto/src/main.rs +++ b/miden-crypto/src/main.rs @@ -10,16 +10,16 @@ use rand::{Rng, prelude::IteratorRandom, rng}; use rand_utils::rand_value; #[derive(Parser, Debug)] -#[clap(name = "Benchmark", about = "SMT benchmark", version, rename_all = "kebab-case")] +#[command(name = "Benchmark", about = "SMT benchmark", version, rename_all = "kebab-case")] pub struct BenchmarkCmd { /// Size of the tree - #[clap(short = 's', long = "size", default_value = "1000000")] + #[arg(short = 's', long = "size", default_value = "1000000")] size: usize, /// Number of insertions - #[clap(short = 'i', long = "insertions", default_value = "1000")] + #[arg(short = 'i', long = "insertions", default_value = "1000")] insertions: usize, /// Number of updates - #[clap(short = 'u', long = "updates", default_value = "1000")] + #[arg(short = 'u', long = "updates", default_value = "1000")] updates: usize, } diff --git a/miden-crypto/src/merkle/mmr/partial.rs b/miden-crypto/src/merkle/mmr/partial.rs index 310f55e..5edd2e5 100644 --- a/miden-crypto/src/merkle/mmr/partial.rs +++ b/miden-crypto/src/merkle/mmr/partial.rs @@ -246,9 +246,7 @@ impl PartialMmr { debug_assert!( old.is_none(), - "Idx {:?} already contained an element {:?}", - left_idx, - old + "Idx {left_idx:?} already contained an element {old:?}", ); }; if track_left { @@ -257,9 +255,7 @@ impl PartialMmr { debug_assert!( old.is_none(), - "Idx {:?} already contained an element {:?}", - right_idx, - old + "Idx {right_idx:?} already contained an element {old:?}", ); }; diff --git a/miden-crypto/src/merkle/smt/full/concurrent/tests.rs b/miden-crypto/src/merkle/smt/full/concurrent/tests.rs index f6e5c9b..fe266e3 100644 --- a/miden-crypto/src/merkle/smt/full/concurrent/tests.rs +++ b/miden-crypto/src/merkle/smt/full/concurrent/tests.rs @@ -247,8 +247,7 @@ fn test_singlethreaded_subtrees() { let control_node = control.get_inner_node(index); assert_eq!( test_node, &control_node, - "depth {} subtree {}: test node does not match control at index {:?}", - current_depth, i, index, + "depth {current_depth} subtree {i}: test node does not match control at index {index:?}", ); } (nodes, subtree_root) @@ -329,8 +328,7 @@ fn test_multithreaded_subtrees() { let control_node = control.get_inner_node(index); assert_eq!( test_node, &control_node, - "depth {} subtree {}: test node does not match control at index {:?}", - current_depth, i, index, + "depth {current_depth} subtree {i}: test node does not match control at index {index:?}", ); } (nodes, subtree_root) @@ -428,8 +426,7 @@ fn test_singlethreaded_subtree_mutations() { let control_mutation = control.node_mutations().get(&index).unwrap(); assert_eq!( control_mutation, mutation, - "depth {} subtree {}: mutation does not match control at index {:?}", - current_depth, i, index, + "depth {current_depth} subtree {i}: mutation does not match control at index {index:?}", ); } (mutations_per_subtree, subtree_root) diff --git a/miden-crypto/src/merkle/sparse_path.rs b/miden-crypto/src/merkle/sparse_path.rs index 5bdaa80..3862c0d 100644 --- a/miden-crypto/src/merkle/sparse_path.rs +++ b/miden-crypto/src/merkle/sparse_path.rs @@ -154,16 +154,14 @@ impl Deserializable for SparseMerklePath { let depth = source.read_u8()?; if depth > SMT_MAX_DEPTH { return Err(DeserializationError::InvalidValue(format!( - "SparseMerklePath max depth exceeded ({} > {})", - depth, SMT_MAX_DEPTH + "SparseMerklePath max depth exceeded ({depth} > {SMT_MAX_DEPTH})", ))); } let empty_nodes_mask = source.read_u64()?; let empty_nodes_count = empty_nodes_mask.count_ones(); if empty_nodes_count > depth as u32 { return Err(DeserializationError::InvalidValue(format!( - "SparseMerklePath has more empty nodes ({}) than its full length ({})", - empty_nodes_count, depth + "SparseMerklePath has more empty nodes ({empty_nodes_count}) than its full length ({depth})", ))); } let count = depth as u32 - empty_nodes_count; From f4f773563c695194236f16078761f308b7a4b3a5 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Fri, 9 May 2025 15:10:23 +0200 Subject: [PATCH 4/6] rename MerklePath::inner_nodes to authenticated_nodes and update docs This name better reflects its actual functionality, but is open to bikeshedding. --- miden-crypto/src/merkle/path.rs | 25 ++++++++++++++++++------- miden-crypto/src/merkle/store/mod.rs | 2 +- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/miden-crypto/src/merkle/path.rs b/miden-crypto/src/merkle/path.rs index 28e78bf..dce31ac 100644 --- a/miden-crypto/src/merkle/path.rs +++ b/miden-crypto/src/merkle/path.rs @@ -88,21 +88,31 @@ impl MerklePath { Ok(()) } - /// Returns an iterator over every inner node of this [MerklePath]. + /// Given the node this path opens to, return an iterator of all the nodes that are known via + /// this path. /// - /// The iteration order is unspecified. + /// Each item in the iterator is an [InnerNodeInfo], containing the hash of a node as `.value`, + /// and its two children as `.left` and `.right`. The very first item in that iterator will be + /// the parent of `node_to_prove`, either `left` or `right` will be `node_to_prove` itself, and + /// the other child will be `node_to_prove` as stored in this [MerklePath]. + /// + /// From there, the iterator will continue to yield every further parent and both of its + /// children, up to and including the root node. + /// + /// If `node_to_prove` is not the node this path is an opening to, or `index` is not the + /// correct index for that node, the returned nodes will be meaningless. /// /// # Errors /// Returns an error if the specified index is not valid for this path. - pub fn inner_nodes( + pub fn authenticated_nodes( &self, index: u64, - node: RpoDigest, + node_to_prove: RpoDigest, ) -> Result { Ok(InnerNodeIterator { nodes: &self.nodes, index: NodeIndex::new(self.depth(), index)?, - value: node, + value: node_to_prove, }) } } @@ -162,7 +172,7 @@ impl IntoIterator for MerklePath { } } -/// An iterator over internal nodes of a [MerklePath]. +/// An iterator over internal nodes of a [MerklePath]. See [`MerklePath::authenticated_nodes()`] pub struct InnerNodeIterator<'a> { nodes: &'a Vec, index: NodeIndex, @@ -293,7 +303,8 @@ mod tests { let node = int_to_node(5); let root = merkle_path.compute_root(index, node).unwrap(); - let inner_root = merkle_path.inner_nodes(index, node).unwrap().last().unwrap().value; + let inner_root = + merkle_path.authenticated_nodes(index, node).unwrap().last().unwrap().value; assert_eq!(root, inner_root); } diff --git a/miden-crypto/src/merkle/store/mod.rs b/miden-crypto/src/merkle/store/mod.rs index ac8e52f..db1db9f 100644 --- a/miden-crypto/src/merkle/store/mod.rs +++ b/miden-crypto/src/merkle/store/mod.rs @@ -393,7 +393,7 @@ impl> MerkleStore { node: RpoDigest, path: MerklePath, ) -> Result { - let root = path.inner_nodes(index, node)?.fold(RpoDigest::default(), |_, node| { + let root = path.authenticated_nodes(index, node)?.fold(RpoDigest::default(), |_, node| { let value: RpoDigest = node.value; let left: RpoDigest = node.left; let right: RpoDigest = node.right; From e52e6d2f53a41f7ba78fb08e83cdff8a2aaac76c Mon Sep 17 00:00:00 2001 From: Qyriad Date: Fri, 9 May 2025 16:35:47 +0200 Subject: [PATCH 5/6] SparseMerklePath: add compute_root and friends for parity with MerklePath --- miden-crypto/src/merkle/sparse_path.rs | 127 ++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/miden-crypto/src/merkle/sparse_path.rs b/miden-crypto/src/merkle/sparse_path.rs index 3862c0d..788336f 100644 --- a/miden-crypto/src/merkle/sparse_path.rs +++ b/miden-crypto/src/merkle/sparse_path.rs @@ -6,8 +6,11 @@ use core::{ use winter_utils::{Deserializable, DeserializationError, Serializable}; +use crate::hash::rpo::Rpo256; + use super::{ - EmptySubtreeRoots, MerkleError, MerklePath, RpoDigest, SMT_MAX_DEPTH, ValuePath, Word, + EmptySubtreeRoots, InnerNodeInfo, MerkleError, MerklePath, NodeIndex, RpoDigest, SMT_MAX_DEPTH, + ValuePath, Word, }; /// A different representation of [`MerklePath`] designed for memory efficiency for Merkle paths @@ -107,6 +110,72 @@ impl SparseMerklePath { self.into_iter() } + /// Computes the Merkle root for this opening. + pub fn compute_root( + &self, + index: u64, + node_to_prove: RpoDigest, + ) -> Result { + let mut index = NodeIndex::new(self.depth(), index)?; + let root = self.iter().fold(node_to_prove, |node, sibling| { + // Compute the node and move to the next iteration. + let input = index.build_node(node, sibling); + index.move_up(); + Rpo256::merge(&input) + }); + + assert!(index.is_root()); // XXX + + Ok(root) + } + + /// Verifies the Merkle opening proof towards the provided root. + /// + /// # Errors + /// Returns an error if: + /// - provided node index is invalid. + /// - root calculated during the verification differs from the provided one. + pub fn verify( + &self, + index: u64, + node: RpoDigest, + &expected_root: &RpoDigest, + ) -> Result<(), MerkleError> { + let computed_root = self.compute_root(index, node)?; + if computed_root != expected_root { + return Err(MerkleError::ConflictingRoots { + expected_root, + actual_root: computed_root, + }); + } + + Ok(()) + } + + /// Given the node this path opens to, return an iterator of all the nodes that are known via + /// this path. + /// + /// Each item in the iterator is an [InnerNodeInfo], containing the hash of a node as `.value`, + /// and its two children as `.left` and `.right`. The very first item in that iterator will be + /// the parent of `node_to_prove` as stored in this [SparseMerklePath]. + /// + /// From there, the iterator will continue to yield every further parent and both of its + /// children, up to and including the root node. + /// + /// If `node_to_prove` is not the node this path is an opening to, or `index` is not the + /// correct index for that node, the returned nodes will be meaningless. + /// + /// # Errors + /// Returns an error if the specified index is not valid for this path. + pub fn authenticated_nodes( + &self, + index: u64, + node_to_prove: RpoDigest, + ) -> Result { + let index = NodeIndex::new(self.depth(), index)?; + Ok(InnerNodeIterator { path: self, index, value: node_to_prove }) + } + // PRIVATE HELPERS // ============================================================================================ @@ -271,6 +340,39 @@ impl<'p> IntoIterator for &'p SparseMerklePath { } } +/// An iterator over nodes known by a [SparseMerklePath]. See +/// [`SparseMerklePath::authenticated_nodes()`]. +pub struct InnerNodeIterator<'p> { + path: &'p SparseMerklePath, + index: NodeIndex, + value: RpoDigest, +} + +impl Iterator for InnerNodeIterator<'_> { + type Item = InnerNodeInfo; + + fn next(&mut self) -> Option { + if self.index.is_root() { + return None; + } + + let index_depth = NonZero::new(self.index.depth()).expect("non-root depth cannot be 0"); + let path_node = self.path.at_depth(index_depth).unwrap(); + + let is_right = self.index.is_value_odd(); + let (left, right) = if is_right { + (path_node, self.value) + } else { + (self.value, path_node) + }; + + self.value = Rpo256::merge(&[left, right]); + self.index.move_up(); + + Some(InnerNodeInfo { value: self.value, left, right }) + } +} + // COMPARISONS // ================================================================================================ impl PartialEq for SparseMerklePath { @@ -613,4 +715,27 @@ mod tests { assert_eq!(sparse_path.iter().next(), None); assert_eq!(sparse_path.into_iter().next(), None); } + + #[test] + fn test_root() { + let tree = make_smt(8192); + + for (key, _value) in tree.entries() { + let leaf = tree.get_leaf(key); + let leaf_node = leaf.hash(); + let index: NodeIndex = Smt::key_to_leaf_index(key).into(); + let control_path = tree.get_path(key); + let sparse_path = SparseMerklePath::try_from(control_path.clone()).unwrap(); + + let authed_nodes: Vec<_> = + sparse_path.authenticated_nodes(index.value(), leaf_node).unwrap().collect(); + let authed_root = authed_nodes.last().unwrap().value; + + let control_root = control_path.compute_root(index.value(), leaf_node).unwrap(); + let sparse_root = sparse_path.compute_root(index.value(), leaf_node).unwrap(); + assert_eq!(control_root, sparse_root); + assert_eq!(authed_root, control_root); + assert_eq!(authed_root, tree.root()); + } + } } From 50ca16fab427961135d65942d7615200126444b9 Mon Sep 17 00:00:00 2001 From: Qyriad Date: Fri, 9 May 2025 16:50:31 +0200 Subject: [PATCH 6/6] add CHANGELOG entry for authenticated_nodes --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ef7130..51ff406 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ - Replace deprecated #[clap(...)] with #[command(...)] and #[arg(...)] (#413). - Added default constructors to `MmrPeaks` and `PartialMmr` (#409). - Add module and function documentation. (#408). +- Renamed `MerklePath::inner_nodes()` to `authenticated_nodes()` to better reflect its functionality (#415). +- Added `compute_root()`, `verify()`, and `authenticated_nodes()` to `SparseMerklePath` for parity with `MerklePath` (#415). ## 0.14.0 (2025-03-15)