From 4f65d01df4c357553e7e3bc80196ca09824d796f Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare Date: Tue, 27 Dec 2022 19:40:58 -0800 Subject: [PATCH 1/7] feat: add PartialEq and Eq traits to Merkle structs --- src/merkle/merkle_path_set.rs | 2 +- src/merkle/merkle_tree.rs | 2 +- src/merkle/simple_smt/mod.rs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/merkle/merkle_path_set.rs b/src/merkle/merkle_path_set.rs index da61bed6..8210285b 100644 --- a/src/merkle/merkle_path_set.rs +++ b/src/merkle/merkle_path_set.rs @@ -4,7 +4,7 @@ use super::{BTreeMap, MerkleError, Rpo256, Vec, Word, ZERO}; // ================================================================================================ /// A set of Merkle paths. -#[derive(Clone, Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct MerklePathSet { root: Word, total_depth: u32, diff --git a/src/merkle/merkle_tree.rs b/src/merkle/merkle_tree.rs index c0c381cd..5eff1c35 100644 --- a/src/merkle/merkle_tree.rs +++ b/src/merkle/merkle_tree.rs @@ -7,7 +7,7 @@ use winter_math::log2; // ================================================================================================ /// A fully-balanced binary Merkle tree (i.e., a tree where the number of leaves is a power of two). -#[derive(Clone, Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct MerkleTree { nodes: Vec, } diff --git a/src/merkle/simple_smt/mod.rs b/src/merkle/simple_smt/mod.rs index 8de3103f..821b1a6f 100644 --- a/src/merkle/simple_smt/mod.rs +++ b/src/merkle/simple_smt/mod.rs @@ -9,7 +9,7 @@ mod tests; /// A sparse Merkle tree with 63-bit keys and 4-element leaf values, without compaction. /// Manipulation and retrieval of leaves and internal nodes is provided by its internal `Store`. /// The root of the tree is recomputed on each new leaf update. -#[derive(Clone, Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct SimpleSmt { root: Word, depth: u32, @@ -186,7 +186,7 @@ impl SimpleSmt { /// Leaves and branch nodes are stored separately in B-tree maps, indexed by key and (key, depth) /// respectively. Hashes for blank subtrees at each layer are stored in `empty_hashes`, beginning /// with the root hash of an empty tree, and ending with the zero value of a leaf node. -#[derive(Clone, Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] struct Store { branches: BTreeMap<(u64, u32), BranchNode>, leaves: BTreeMap, @@ -194,7 +194,7 @@ struct Store { depth: u32, } -#[derive(Clone, Debug, Default)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] struct BranchNode { left: RpoDigest, right: RpoDigest, From bc6191b3fadd563b5a91c0b3b3f27c275be2fc21 Mon Sep 17 00:00:00 2001 From: 0xKanekiKen <100861945+0xKanekiKen@users.noreply.github.com> Date: Mon, 23 Jan 2023 16:26:53 +0000 Subject: [PATCH 2/7] feat: new merge method for 2 digest with a domain separator Signed-off-by: 0xKanekiKen <100861945+0xKanekiKen@users.noreply.github.com> --- src/hash/rpo/mod.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/hash/rpo/mod.rs b/src/hash/rpo/mod.rs index 735461b2..c85c13c8 100644 --- a/src/hash/rpo/mod.rs +++ b/src/hash/rpo/mod.rs @@ -294,6 +294,28 @@ impl Rpo256 { ::hash_elements(elements) } + // DOMAIN IDENTIFIER + // -------------------------------------------------------------------------------------------- + + /// Returns a hash of two digests and a domain separator. + pub fn merge_in_domain(values: &[RpoDigest; 2], domain: Felt) -> RpoDigest { + // initialize the state by copying the digest elements into the rate portion of the state + // (8 total elements), and set the capacity elements to 0. + let mut state = [ZERO; STATE_WIDTH]; + let it = RpoDigest::digests_as_elements(values.iter()); + for (i, v) in it.enumerate() { + state[RATE_RANGE.start + i] = *v; + } + + // set the second capacity element to the domain value. The first capacity element is used + // for padding purposes. + state[CAPACITY_RANGE.start + 1] = domain; + + // apply the RPO permutation and return the first four elements of the state + Self::apply_permutation(&mut state); + RpoDigest::new(state[DIGEST_RANGE].try_into().unwrap()) + } + // RESCUE PERMUTATION // -------------------------------------------------------------------------------------------- From ce2cbe704b39d71e77adf8ce252a941542242b2b Mon Sep 17 00:00:00 2001 From: 0xKanekiKen <100861945+0xKanekiKen@users.noreply.github.com> Date: Mon, 23 Jan 2023 16:50:30 +0000 Subject: [PATCH 3/7] refactor: reexport publiccoin & publiccoinerror from winterfell Signed-off-by: 0xKanekiKen <100861945+0xKanekiKen@users.noreply.github.com> --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index ce650c6b..8cf4e3c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,8 @@ pub mod merkle; // RE-EXPORTS // ================================================================================================ +pub use winter_crypto::{RandomCoin, RandomCoinError}; + pub use winter_math::{fields::f64::BaseElement as Felt, FieldElement, StarkField}; pub mod utils { From 5757b896fe6c6cfbcd000008d9676239776fb3c7 Mon Sep 17 00:00:00 2001 From: 0xKanekiKen <100861945+0xKanekiKen@users.noreply.github.com> Date: Mon, 23 Jan 2023 20:17:34 +0000 Subject: [PATCH 4/7] test: unit tests for merge in domain method Signed-off-by: 0xKanekiKen <100861945+0xKanekiKen@users.noreply.github.com> --- src/hash/rpo/mod.rs | 2 +- src/hash/rpo/tests.rs | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/hash/rpo/mod.rs b/src/hash/rpo/mod.rs index c85c13c8..0d2fde3d 100644 --- a/src/hash/rpo/mod.rs +++ b/src/hash/rpo/mod.rs @@ -297,7 +297,7 @@ impl Rpo256 { // DOMAIN IDENTIFIER // -------------------------------------------------------------------------------------------- - /// Returns a hash of two digests and a domain separator. + /// Returns a hash of two digests and a domain identifier. pub fn merge_in_domain(values: &[RpoDigest; 2], domain: Felt) -> RpoDigest { // initialize the state by copying the digest elements into the rate portion of the state // (8 total elements), and set the capacity elements to 0. diff --git a/src/hash/rpo/tests.rs b/src/hash/rpo/tests.rs index 03f69f94..80227ab1 100644 --- a/src/hash/rpo/tests.rs +++ b/src/hash/rpo/tests.rs @@ -1,5 +1,6 @@ use super::{ - Felt, FieldElement, Hasher, Rpo256, RpoDigest, StarkField, ALPHA, INV_ALPHA, STATE_WIDTH, ZERO, + Felt, FieldElement, Hasher, Rpo256, RpoDigest, StarkField, ALPHA, CAPACITY_RANGE, DIGEST_RANGE, + INV_ALPHA, RATE_RANGE, STATE_WIDTH, ZERO, }; use core::convert::TryInto; use rand_utils::rand_value; @@ -51,6 +52,40 @@ fn hash_elements_vs_merge() { assert_eq!(m_result, h_result); } +#[test] +fn hash_elements_vs_merge_in_domain() { + let elements = [Felt::new(rand_value()); 8]; + + let digests: [RpoDigest; 2] = [ + RpoDigest::new(elements[..4].try_into().unwrap()), + RpoDigest::new(elements[4..].try_into().unwrap()), + ]; + + // pick a random domain value. + let domain = Felt::new(rand_value()); + + // convert the elements into a list of base field elements + let elements = Felt::as_base_elements(&elements); + + // initialize state to all zeros. + let mut state = [ZERO; STATE_WIDTH]; + + // set the second capacity element to the domain. + state[CAPACITY_RANGE.start + 1] = domain; + + // absorb elements into the state. + state[RATE_RANGE.start..RATE_RANGE.end].copy_from_slice(elements); + + // apply permutation to the state. + Rpo256::apply_permutation(&mut state); + + // return the first 4 elements of the state as hash result + let h_result = RpoDigest::new(state[DIGEST_RANGE].try_into().unwrap()); + + let m_result = Rpo256::merge_in_domain(&digests, domain); + assert_eq!(m_result, h_result); +} + #[test] fn hash_elements_vs_merge_with_int() { let tmp = [Felt::new(rand_value()); 4]; From 37c6f003c45e352c4a73b2a36787dce6da1a29e9 Mon Sep 17 00:00:00 2001 From: 0xKanekiKen <100861945+0xKanekiKen@users.noreply.github.com> Date: Wed, 1 Feb 2023 13:14:03 +0000 Subject: [PATCH 5/7] tests: refactor merge_in_domain tests Signed-off-by: 0xKanekiKen <100861945+0xKanekiKen@users.noreply.github.com> --- src/hash/rpo/tests.rs | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/hash/rpo/tests.rs b/src/hash/rpo/tests.rs index 80227ab1..b10e02b0 100644 --- a/src/hash/rpo/tests.rs +++ b/src/hash/rpo/tests.rs @@ -1,6 +1,6 @@ use super::{ - Felt, FieldElement, Hasher, Rpo256, RpoDigest, StarkField, ALPHA, CAPACITY_RANGE, DIGEST_RANGE, - INV_ALPHA, RATE_RANGE, STATE_WIDTH, ZERO, + Felt, FieldElement, Hasher, Rpo256, RpoDigest, StarkField, ALPHA, INV_ALPHA, ONE, STATE_WIDTH, + ZERO, }; use core::convert::TryInto; use rand_utils::rand_value; @@ -53,37 +53,30 @@ fn hash_elements_vs_merge() { } #[test] -fn hash_elements_vs_merge_in_domain() { +fn merge_vs_merge_in_domain() { let elements = [Felt::new(rand_value()); 8]; let digests: [RpoDigest; 2] = [ RpoDigest::new(elements[..4].try_into().unwrap()), RpoDigest::new(elements[4..].try_into().unwrap()), ]; + let merge_result = Rpo256::merge(&digests); - // pick a random domain value. - let domain = Felt::new(rand_value()); + // ------------- merge with domain = 0 ---------------------------------------------------------- - // convert the elements into a list of base field elements - let elements = Felt::as_base_elements(&elements); + // set domain to ZERO. This should not change the result. + let domain = ZERO; - // initialize state to all zeros. - let mut state = [ZERO; STATE_WIDTH]; + let merge_in_domain_result = Rpo256::merge_in_domain(&digests, domain); + assert_eq!(merge_result, merge_in_domain_result); - // set the second capacity element to the domain. - state[CAPACITY_RANGE.start + 1] = domain; + // ------------- merge with domain = 1 ---------------------------------------------------------- - // absorb elements into the state. - state[RATE_RANGE.start..RATE_RANGE.end].copy_from_slice(elements); + // set domain to ONE. This should change the result. + let domain = ONE; - // apply permutation to the state. - Rpo256::apply_permutation(&mut state); - - // return the first 4 elements of the state as hash result - let h_result = RpoDigest::new(state[DIGEST_RANGE].try_into().unwrap()); - - let m_result = Rpo256::merge_in_domain(&digests, domain); - assert_eq!(m_result, h_result); + let merge_in_domain_result = Rpo256::merge_in_domain(&digests, domain); + assert_ne!(merge_result, merge_in_domain_result); } #[test] From e0e4610f2a11c5639a089bc87846b4727b9a84d0 Mon Sep 17 00:00:00 2001 From: grjte Date: Mon, 6 Feb 2023 14:51:41 +0000 Subject: [PATCH 6/7] docs: add changelog --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..0d452475 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +## 0.1.1 (2023-02-06) + +- Introduced `merge_in_domain` for the RPO hash function, to allow using a specified domain value in the second capacity register when hashing two digests together. +- Added a simple sparse Merkle tree implementation. +- Added re-exports of Winterfell RandomCoin and RandomCoinError. + +## 0.1.0 (2022-12-02) + +- Initial release on crates.io containing the cryptographic primitives used in Miden VM and the Miden Rollup. +- Hash module with the BLAKE3 and Rescue Prime Optimized hash functions. + - BLAKE3 is implemented with 256-bit, 192-bit, or 160-bit output. + - RPO is implemented with 256-bit output. +- Merkle module, with a set of data structures related to Merkle trees, implemented using the RPO hash function. From d25a90b5a42151696c84e9067e4871e365f0ff9a Mon Sep 17 00:00:00 2001 From: grjte Date: Mon, 6 Feb 2023 15:00:58 +0000 Subject: [PATCH 7/7] chore: update version to 0.1.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 455dddc2..815a42be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miden-crypto" -version = "0.1.0" +version = "0.1.1" description="Miden Cryptographic primitives" authors = ["miden contributors"] readme="README.md"