Skip to content

Commit

Permalink
Merge pull request #154 from 0xPolygonMiden/next
Browse files Browse the repository at this point in the history
Tracking PR for v0.6.0 release
  • Loading branch information
bobbinth authored Jun 25, 2023
2 parents 2ed880d + 858f95d commit 18302d6
Show file tree
Hide file tree
Showing 25 changed files with 1,648 additions and 470 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.6.0 (2023-06-25)

* [BREAKING] Added support for recording capabilities for `MerkleStore` (#162).
* [BREAKING] Refactored Merkle struct APIs to use `RpoDigest` instead of `Word` (#157).
* Added initial implementation of `PartialMerkleTree` (#156).

## 0.5.0 (2023-05-26)

* Implemented `TieredSmt` (#152, #153).
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[package]
name = "miden-crypto"
version = "0.5.0"
version = "0.6.0"
description = "Miden Cryptographic primitives"
authors = ["miden contributors"]
readme = "README.md"
license = "MIT"
repository = "https://github.com/0xPolygonMiden/crypto"
documentation = "https://docs.rs/miden-crypto/0.5.0"
documentation = "https://docs.rs/miden-crypto/0.6.0"
categories = ["cryptography", "no-std"]
keywords = ["miden", "crypto", "hash", "merkle"]
edition = "2021"
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ For performance benchmarks of these hash functions and their comparison to other
## Merkle
[Merkle module](./src/merkle/) provides a set of data structures related to Merkle trees. All these data structures are implemented using the RPO hash function described above. The data structures are:

* `Mmr`: a Merkle mountain range structure designed to function as an append-only log.
* `MerkleTree`: a regular fully-balanced binary Merkle tree. The depth of this tree can be at most 64.
* `MerklePathSet`: a collection of Merkle authentication paths all resolving to the same root. The length of the paths can be at most 64.
* `MerkleStore`: a collection of Merkle trees of different heights designed to efficiently store trees with common subtrees.
* `Mmr`: a Merkle mountain range structure designed to function as an append-only log.
* `MerkleStore`: a collection of Merkle trees of different heights designed to efficiently store trees with common subtrees. When instantiated with `RecordingMap`, a Merkle store records all accesses to the original data.
* `PartialMerkleTree`: a partial view of a Merkle tree where some sub-trees may not be known. This is similar to a collection of Merkle paths all resolving to the same root. The length of the paths can be at most 64.
* `SimpleSmt`: a Sparse Merkle Tree (with no compaction), mapping 64-bit keys to 4-element values.
* `TieredSmt`: a Sparse Merkle tree (with compaction), mapping 4-element keys to 4-element values.

Expand Down
6 changes: 3 additions & 3 deletions benches/store.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use criterion::{black_box, criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
use miden_crypto::merkle::{MerkleStore, MerkleTree, NodeIndex, SimpleSmt};
use miden_crypto::merkle::{DefaultMerkleStore as MerkleStore, MerkleTree, NodeIndex, SimpleSmt};
use miden_crypto::Word;
use miden_crypto::{hash::rpo::RpoDigest, Felt};
use rand_utils::{rand_array, rand_value};
Expand Down Expand Up @@ -409,7 +409,7 @@ fn update_leaf_merkletree(c: &mut Criterion) {
// The MerkleTree automatically updates its internal root, the Store maintains
// the old root and adds the new one. Here we update the root to have a fair
// comparison
store_root = store.set_node(root, index, value).unwrap().root;
store_root = store.set_node(root, index, value.into()).unwrap().root;
black_box(store_root)
},
BatchSize::SmallInput,
Expand Down Expand Up @@ -455,7 +455,7 @@ fn update_leaf_simplesmt(c: &mut Criterion) {
// The MerkleTree automatically updates its internal root, the Store maintains
// the old root and adds the new one. Here we update the root to have a fair
// comparison
store_root = store.set_node(root, index, value).unwrap().root;
store_root = store.set_node(root, index, value.into()).unwrap().root;
black_box(store_root)
},
BatchSize::SmallInput,
Expand Down
13 changes: 8 additions & 5 deletions src/hash/rpo/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use super::{
Felt, FieldElement, Hasher, Rpo256, RpoDigest, StarkField, ALPHA, INV_ALPHA, ONE, STATE_WIDTH,
ZERO,
};
use crate::utils::collections::{BTreeSet, Vec};
use crate::{
utils::collections::{BTreeSet, Vec},
Word,
};
use core::convert::TryInto;
use proptest::prelude::*;
use rand_utils::rand_value;
Expand Down Expand Up @@ -203,7 +206,7 @@ fn sponge_bytes_with_remainder_length_wont_panic() {
// size.
//
// this is a preliminary test to the fuzzy-stress of proptest.
Rpo256::hash(&vec![0; 113]);
Rpo256::hash(&[0; 113]);
}

#[test]
Expand All @@ -227,12 +230,12 @@ fn sponge_zeroes_collision() {

proptest! {
#[test]
fn rpo256_wont_panic_with_arbitrary_input(ref vec in any::<Vec<u8>>()) {
Rpo256::hash(&vec);
fn rpo256_wont_panic_with_arbitrary_input(ref bytes in any::<Vec<u8>>()) {
Rpo256::hash(bytes);
}
}

const EXPECTED: [[Felt; 4]; 19] = [
const EXPECTED: [Word; 19] = [
[
Felt::new(1502364727743950833),
Felt::new(5880949717274681448),
Expand Down
2 changes: 1 addition & 1 deletion src/merkle/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ mod tests {
if value > (1 << depth) { // round up
depth += 1;
}
NodeIndex::new(depth, value.into()).unwrap()
NodeIndex::new(depth, value).unwrap()
}
}

Expand Down
77 changes: 42 additions & 35 deletions src/merkle/merkle_tree.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
use super::{
Felt, InnerNodeInfo, MerkleError, MerklePath, NodeIndex, Rpo256, RpoDigest, Vec, Word,
};
use crate::{
utils::{string::String, uninit_vector, word_to_hex},
FieldElement,
};
use core::{fmt, slice};
use super::{InnerNodeInfo, MerkleError, MerklePath, NodeIndex, Rpo256, RpoDigest, Vec, Word};
use crate::utils::{string::String, uninit_vector, word_to_hex};
use core::{fmt, ops::Deref, slice};
use winter_math::log2;

// MERKLE TREE
Expand All @@ -14,7 +9,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(Debug, Clone, PartialEq, Eq)]
pub struct MerkleTree {
nodes: Vec<Word>,
nodes: Vec<RpoDigest>,
}

impl MerkleTree {
Expand All @@ -34,10 +29,12 @@ impl MerkleTree {

// create un-initialized vector to hold all tree nodes
let mut nodes = unsafe { uninit_vector(2 * n) };
nodes[0] = [Felt::ZERO; 4];
nodes[0] = RpoDigest::default();

// copy leaves into the second part of the nodes vector
nodes[n..].copy_from_slice(&leaves);
nodes[n..].iter_mut().zip(leaves).for_each(|(node, leaf)| {
*node = RpoDigest::from(leaf);
});

// re-interpret nodes as an array of two nodes fused together
// Safety: `nodes` will never move here as it is not bound to an external lifetime (i.e.
Expand All @@ -47,7 +44,7 @@ impl MerkleTree {

// calculate all internal tree nodes
for i in (1..n).rev() {
nodes[i] = Rpo256::merge(&pairs[i]).into();
nodes[i] = Rpo256::merge(&pairs[i]);
}

Ok(Self { nodes })
Expand All @@ -57,7 +54,7 @@ impl MerkleTree {
// --------------------------------------------------------------------------------------------

/// Returns the root of this Merkle tree.
pub fn root(&self) -> Word {
pub fn root(&self) -> RpoDigest {
self.nodes[1]
}

Expand All @@ -74,7 +71,7 @@ impl MerkleTree {
/// Returns an error if:
/// * The specified depth is greater than the depth of the tree.
/// * The specified index is not valid for the specified depth.
pub fn get_node(&self, index: NodeIndex) -> Result<Word, MerkleError> {
pub fn get_node(&self, index: NodeIndex) -> Result<RpoDigest, MerkleError> {
if index.is_root() {
return Err(MerkleError::DepthTooSmall(index.depth()));
} else if index.depth() > self.depth() {
Expand Down Expand Up @@ -120,7 +117,11 @@ impl MerkleTree {
/// Returns an iterator over the leaves of this [MerkleTree].
pub fn leaves(&self) -> impl Iterator<Item = (u64, &Word)> {
let leaves_start = self.nodes.len() / 2;
self.nodes.iter().skip(leaves_start).enumerate().map(|(i, v)| (i as u64, v))
self.nodes
.iter()
.skip(leaves_start)
.enumerate()
.map(|(i, v)| (i as u64, v.deref()))
}

/// Returns n iterator over every inner node of this [MerkleTree].
Expand Down Expand Up @@ -159,13 +160,13 @@ impl MerkleTree {

// update the current node
let pos = index.to_scalar_index() as usize;
self.nodes[pos] = value;
self.nodes[pos] = value.into();

// traverse to the root, updating each node with the merged values of its parents
for _ in 0..index.depth() {
index.move_up();
let pos = index.to_scalar_index() as usize;
let value = Rpo256::merge(&pairs[pos]).into();
let value = Rpo256::merge(&pairs[pos]);
self.nodes[pos] = value;
}

Expand All @@ -180,7 +181,7 @@ impl MerkleTree {
///
/// Use this to extract the data of the tree, there is no guarantee on the order of the elements.
pub struct InnerNodeIterator<'a> {
nodes: &'a Vec<Word>,
nodes: &'a Vec<RpoDigest>,
index: usize,
}

Expand Down Expand Up @@ -258,13 +259,17 @@ pub fn path_to_text(path: &MerklePath) -> Result<String, fmt::Error> {
#[cfg(test)]
mod tests {
use super::*;
use crate::merkle::{int_to_node, InnerNodeInfo};
use crate::{
merkle::{digests_to_words, int_to_leaf, int_to_node, InnerNodeInfo},
Felt, Word, WORD_SIZE,
};
use core::mem::size_of;
use proptest::prelude::*;

const LEAVES4: [Word; 4] = [int_to_node(1), int_to_node(2), int_to_node(3), int_to_node(4)];
const LEAVES4: [RpoDigest; WORD_SIZE] =
[int_to_node(1), int_to_node(2), int_to_node(3), int_to_node(4)];

const LEAVES8: [Word; 8] = [
const LEAVES8: [RpoDigest; 8] = [
int_to_node(1),
int_to_node(2),
int_to_node(3),
Expand All @@ -277,7 +282,7 @@ mod tests {

#[test]
fn build_merkle_tree() {
let tree = super::MerkleTree::new(LEAVES4.to_vec()).unwrap();
let tree = super::MerkleTree::new(digests_to_words(&LEAVES4)).unwrap();
assert_eq!(8, tree.nodes.len());

// leaves were copied correctly
Expand All @@ -296,7 +301,7 @@ mod tests {

#[test]
fn get_leaf() {
let tree = super::MerkleTree::new(LEAVES4.to_vec()).unwrap();
let tree = super::MerkleTree::new(digests_to_words(&LEAVES4)).unwrap();

// check depth 2
assert_eq!(LEAVES4[0], tree.get_node(NodeIndex::make(2, 0)).unwrap());
Expand All @@ -313,7 +318,7 @@ mod tests {

#[test]
fn get_path() {
let tree = super::MerkleTree::new(LEAVES4.to_vec()).unwrap();
let tree = super::MerkleTree::new(digests_to_words(&LEAVES4)).unwrap();

let (_, node2, node3) = compute_internal_nodes();

Expand All @@ -330,12 +335,12 @@ mod tests {

#[test]
fn update_leaf() {
let mut tree = super::MerkleTree::new(LEAVES8.to_vec()).unwrap();
let mut tree = super::MerkleTree::new(digests_to_words(&LEAVES8)).unwrap();

// update one leaf
let value = 3;
let new_node = int_to_node(9);
let mut expected_leaves = LEAVES8.to_vec();
let new_node = int_to_leaf(9);
let mut expected_leaves = digests_to_words(&LEAVES8);
expected_leaves[value as usize] = new_node;
let expected_tree = super::MerkleTree::new(expected_leaves.clone()).unwrap();

Expand All @@ -344,7 +349,7 @@ mod tests {

// update another leaf
let value = 6;
let new_node = int_to_node(10);
let new_node = int_to_leaf(10);
expected_leaves[value as usize] = new_node;
let expected_tree = super::MerkleTree::new(expected_leaves.clone()).unwrap();

Expand All @@ -354,7 +359,7 @@ mod tests {

#[test]
fn nodes() -> Result<(), MerkleError> {
let tree = super::MerkleTree::new(LEAVES4.to_vec()).unwrap();
let tree = super::MerkleTree::new(digests_to_words(&LEAVES4)).unwrap();
let root = tree.root();
let l1n0 = tree.get_node(NodeIndex::make(1, 0))?;
let l1n1 = tree.get_node(NodeIndex::make(1, 1))?;
Expand Down Expand Up @@ -403,8 +408,8 @@ mod tests {
let digest = RpoDigest::from(word);

// assert the addresses are different
let word_ptr = (&word).as_ptr() as *const u8;
let digest_ptr = (&digest).as_ptr() as *const u8;
let word_ptr = word.as_ptr() as *const u8;
let digest_ptr = digest.as_ptr() as *const u8;
assert_ne!(word_ptr, digest_ptr);

// compare the bytes representation
Expand All @@ -417,11 +422,13 @@ mod tests {
// HELPER FUNCTIONS
// --------------------------------------------------------------------------------------------

fn compute_internal_nodes() -> (Word, Word, Word) {
let node2 = Rpo256::hash_elements(&[LEAVES4[0], LEAVES4[1]].concat());
let node3 = Rpo256::hash_elements(&[LEAVES4[2], LEAVES4[3]].concat());
fn compute_internal_nodes() -> (RpoDigest, RpoDigest, RpoDigest) {
let node2 =
Rpo256::hash_elements(&[Word::from(LEAVES4[0]), Word::from(LEAVES4[1])].concat());
let node3 =
Rpo256::hash_elements(&[Word::from(LEAVES4[2]), Word::from(LEAVES4[3])].concat());
let root = Rpo256::merge(&[node2, node3]);

(root.into(), node2.into(), node3.into())
(root, node2, node3)
}
}
19 changes: 15 additions & 4 deletions src/merkle/mmr/accumulator.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use super::{super::Vec, super::ZERO, Felt, MmrProof, Rpo256, Word};
use super::{
super::{RpoDigest, Vec, ZERO},
Felt, MmrProof, Rpo256, Word,
};

#[derive(Debug, Clone, PartialEq)]
pub struct MmrPeaks {
Expand All @@ -25,7 +28,7 @@ pub struct MmrPeaks {
/// leaves, starting from the peak with most children, to the one with least.
///
/// Invariant: The length of `peaks` must be equal to the number of true bits in `num_leaves`.
pub peaks: Vec<Word>,
pub peaks: Vec<RpoDigest>,
}

impl MmrPeaks {
Expand All @@ -38,7 +41,7 @@ impl MmrPeaks {
Rpo256::hash_elements(&self.flatten_and_pad_peaks()).into()
}

pub fn verify(&self, value: Word, opening: MmrProof) -> bool {
pub fn verify(&self, value: RpoDigest, opening: MmrProof) -> bool {
let root = &self.peaks[opening.peak_index()];
opening.merkle_path.verify(opening.relative_pos() as u64, value, root)
}
Expand Down Expand Up @@ -72,7 +75,15 @@ impl MmrPeaks {
};

let mut elements = Vec::with_capacity(len);
elements.extend_from_slice(&self.peaks.as_slice().concat());
elements.extend_from_slice(
&self
.peaks
.as_slice()
.iter()
.map(|digest| digest.into())
.collect::<Vec<Word>>()
.concat(),
);
elements.resize(len, ZERO);
elements
}
Expand Down
Loading

0 comments on commit 18302d6

Please sign in to comment.