commit
9389f2fb40
33 changed files with 4697 additions and 141 deletions
2
.git-blame-ignore-revs
Normal file
2
.git-blame-ignore-revs
Normal file
|
@ -0,0 +1,2 @@
|
|||
# initial run of pre-commit
|
||||
956e4c6fad779ef15eaa27702b26f05f65d31494
|
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
@ -6,4 +6,4 @@
|
|||
- Commit messages and codestyle follow [conventions](./CONTRIBUTING.md).
|
||||
- Relevant issues are linked in the PR description.
|
||||
- Tests added for new functionality.
|
||||
- Documentation/comments updated according to changes.
|
||||
- Documentation/comments updated according to changes.
|
||||
|
|
43
.pre-commit-config.yaml
Normal file
43
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,43 @@
|
|||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.2.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-json
|
||||
- id: check-toml
|
||||
- id: pretty-format-json
|
||||
- id: check-added-large-files
|
||||
- id: check-case-conflict
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-merge-conflict
|
||||
- id: detect-private-key
|
||||
- repo: https://github.com/hackaugusto/pre-commit-cargo
|
||||
rev: v1.0.0
|
||||
hooks:
|
||||
# Allows cargo fmt to modify the source code prior to the commit
|
||||
- id: cargo
|
||||
name: Cargo fmt
|
||||
args: ["+stable", "fmt", "--all"]
|
||||
stages: [commit]
|
||||
# Requires code to be properly formatted prior to pushing upstream
|
||||
- id: cargo
|
||||
name: Cargo fmt --check
|
||||
args: ["+stable", "fmt", "--all", "--check"]
|
||||
stages: [push, manual]
|
||||
- id: cargo
|
||||
name: Cargo check --all-targets
|
||||
args: ["+stable", "check", "--all-targets"]
|
||||
- id: cargo
|
||||
name: Cargo check --all-targets --no-default-features
|
||||
args: ["+stable", "check", "--all-targets", "--no-default-features"]
|
||||
- id: cargo
|
||||
name: Cargo check --all-targets --all-features
|
||||
args: ["+stable", "check", "--all-targets", "--all-features"]
|
||||
# Unlike fmt, clippy will not be automatically applied
|
||||
- id: cargo
|
||||
name: Cargo clippy
|
||||
args: ["+nightly", "clippy", "--workspace", "--", "--deny", "clippy::all", "--deny", "warnings"]
|
|
@ -1,3 +1,12 @@
|
|||
## 0.2.0 (2023-03-24)
|
||||
|
||||
- Implemented `Mmr` and related structs (#67).
|
||||
- Implemented `MerkleStore` (#93, #94, #95, #107 #112).
|
||||
- Added benchmarks for `MerkleStore` vs. other structs (#97).
|
||||
- Added Merkle path containers (#99).
|
||||
- Fixed depth handling in `MerklePathSet` (#110).
|
||||
- Updated Winterfell dependency to v0.6.
|
||||
|
||||
## 0.1.4 (2023-02-22)
|
||||
|
||||
- Re-export winter-crypto Hasher, Digest & ElementHasher (#72)
|
||||
|
|
|
@ -17,7 +17,7 @@ We are using [Github Flow](https://docs.github.com/en/get-started/quickstart/git
|
|||
### Branching
|
||||
- The current active branch is `next`. Every branch with a fix/feature must be forked from `next`.
|
||||
|
||||
- The branch name should contain a short issue/feature description separated with hyphens [(kebab-case)](https://en.wikipedia.org/wiki/Letter_case#Kebab_case).
|
||||
- The branch name should contain a short issue/feature description separated with hyphens [(kebab-case)](https://en.wikipedia.org/wiki/Letter_case#Kebab_case).
|
||||
|
||||
For example, if the issue title is `Fix functionality X in component Y` then the branch name will be something like: `fix-x-in-y`.
|
||||
|
||||
|
|
16
Cargo.toml
16
Cargo.toml
|
@ -1,12 +1,12 @@
|
|||
[package]
|
||||
name = "miden-crypto"
|
||||
version = "0.1.4"
|
||||
version = "0.2.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.1.4"
|
||||
documentation = "https://docs.rs/miden-crypto/0.2.0"
|
||||
categories = ["cryptography", "no-std"]
|
||||
keywords = ["miden", "crypto", "hash", "merkle"]
|
||||
edition = "2021"
|
||||
|
@ -20,17 +20,21 @@ harness = false
|
|||
name = "smt"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "store"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
default = ["blake3/default", "std", "winter_crypto/default", "winter_math/default", "winter_utils/default"]
|
||||
std = ["blake3/std", "winter_crypto/std", "winter_math/std", "winter_utils/std"]
|
||||
|
||||
[dependencies]
|
||||
blake3 = { version = "1.3", default-features = false }
|
||||
winter_crypto = { version = "0.5", package = "winter-crypto", default-features = false }
|
||||
winter_math = { version = "0.5", package = "winter-math", default-features = false }
|
||||
winter_utils = { version = "0.5", package = "winter-utils", default-features = false }
|
||||
winter_crypto = { version = "0.6", package = "winter-crypto", default-features = false }
|
||||
winter_math = { version = "0.6", package = "winter-math", default-features = false }
|
||||
winter_utils = { version = "0.6", package = "winter-utils", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.4", features = ["html_reports"] }
|
||||
proptest = "1.1.0"
|
||||
rand_utils = { version = "0.5", package = "winter-rand-utils" }
|
||||
rand_utils = { version = "0.6", package = "winter-rand-utils" }
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Polygon Miden
|
||||
Copyright (c) 2023 Polygon 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
|
||||
|
|
|
@ -15,6 +15,8 @@ For performance benchmarks of these hash functions and their comparison to other
|
|||
* `MerkleTree`: a regular fully-balanced binary Merkle tree. The depth of this tree can be at most 64.
|
||||
* `SimpleSmt`: a Sparse Merkle Tree, mapping 63-bit keys to 4-element leaf values.
|
||||
* `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.
|
||||
|
||||
The module also contains additional supporting components such as `NodeIndex`, `MerklePath`, and `MerkleError` to assist with tree indexation, opening proofs, and reporting inconsistent arguments/state.
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Miden VM Hash Functions
|
||||
# Miden VM Hash Functions
|
||||
In the Miden VM, we make use of different hash functions. Some of these are "traditional" hash functions, like `BLAKE3`, which are optimized for out-of-STARK performance, while others are algebraic hash functions, like `Rescue Prime`, and are more optimized for a better performance inside the STARK. In what follows, we benchmark several such hash functions and compare against other constructions that are used by other proving systems. More precisely, we benchmark:
|
||||
|
||||
* **BLAKE3** as specified [here](https://github.com/BLAKE3-team/BLAKE3-specs/blob/master/blake3.pdf) and implemented [here](https://github.com/BLAKE3-team/BLAKE3) (with a wrapper exposed via this crate).
|
||||
|
@ -13,7 +13,7 @@ In the Miden VM, we make use of different hash functions. Some of these are "tra
|
|||
We benchmark the above hash functions using two scenarios. The first is a 2-to-1 $(a,b)\mapsto h(a,b)$ hashing where both $a$, $b$ and $h(a,b)$ are the digests corresponding to each of the hash functions.
|
||||
The second scenario is that of sequential hashing where we take a sequence of length $100$ field elements and hash these to produce a single digest. The digests are $4$ field elements in a prime field with modulus $2^{64} - 2^{32} + 1$ (i.e., 32 bytes) for Poseidon, Rescue Prime and RPO, and an array `[u8; 32]` for SHA3 and BLAKE3.
|
||||
|
||||
#### Scenario 1: 2-to-1 hashing `h(a,b)`
|
||||
#### Scenario 1: 2-to-1 hashing `h(a,b)`
|
||||
|
||||
| Function | BLAKE3 | SHA3 | Poseidon | Rp64_256 | RPO_256 |
|
||||
| ------------------- | ------ | --------| --------- | --------- | ------- |
|
||||
|
@ -46,4 +46,4 @@ To run the benchmarks for Rescue Prime, Poseidon and SHA3, clone the following [
|
|||
|
||||
```
|
||||
cargo bench hash
|
||||
```
|
||||
```
|
||||
|
|
|
@ -18,7 +18,7 @@ fn smt_rpo(c: &mut Criterion) {
|
|||
(i, word)
|
||||
})
|
||||
.collect();
|
||||
let tree = SimpleSmt::new(entries, depth).unwrap();
|
||||
let tree = SimpleSmt::new(depth).unwrap().with_leaves(entries).unwrap();
|
||||
trees.push(tree);
|
||||
}
|
||||
}
|
||||
|
|
506
benches/store.rs
Normal file
506
benches/store.rs
Normal file
|
@ -0,0 +1,506 @@
|
|||
use criterion::{black_box, criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
|
||||
use miden_crypto::merkle::{MerkleStore, MerkleTree, NodeIndex, SimpleSmt};
|
||||
use miden_crypto::Word;
|
||||
use miden_crypto::{hash::rpo::RpoDigest, Felt};
|
||||
use rand_utils::{rand_array, rand_value};
|
||||
|
||||
/// Since MerkleTree can only be created when a power-of-two number of elements is used, the sample
|
||||
/// sizes are limited to that.
|
||||
static BATCH_SIZES: [usize; 3] = [2usize.pow(4), 2usize.pow(7), 2usize.pow(10)];
|
||||
|
||||
/// Generates a random `RpoDigest`.
|
||||
fn random_rpo_digest() -> RpoDigest {
|
||||
rand_array::<Felt, 4>().into()
|
||||
}
|
||||
|
||||
/// Generates a random `Word`.
|
||||
fn random_word() -> Word {
|
||||
rand_array::<Felt, 4>().into()
|
||||
}
|
||||
|
||||
/// Generates a u64 in `0..range`.
|
||||
fn random_index(range: u64) -> u64 {
|
||||
rand_value::<u64>() % range
|
||||
}
|
||||
|
||||
/// Benchmarks getting an empty leaf from the SMT and MerkleStore backends.
|
||||
fn get_empty_leaf_simplesmt(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("get_empty_leaf_simplesmt");
|
||||
|
||||
let depth = 63u8;
|
||||
let size = 2u64.pow(depth as u32);
|
||||
|
||||
// both SMT and the store are pre-populated with empty hashes, accessing these values is what is
|
||||
// being benchmarked here, so no values are inserted into the backends
|
||||
let smt = SimpleSmt::new(depth).unwrap();
|
||||
let store = MerkleStore::new();
|
||||
let root = smt.root();
|
||||
|
||||
group.bench_function(BenchmarkId::new("SimpleSmt", depth), |b| {
|
||||
b.iter_batched(
|
||||
|| random_index(size),
|
||||
|value| black_box(smt.get_node(&NodeIndex::new(depth, value))),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
group.bench_function(BenchmarkId::new("MerkleStore", depth), |b| {
|
||||
b.iter_batched(
|
||||
|| random_index(size),
|
||||
|value| black_box(store.get_node(root, NodeIndex::new(depth, value))),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
/// Benchmarks getting a leaf on Merkle trees and Merkle stores of varying power-of-two sizes.
|
||||
fn get_leaf_merkletree(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("get_leaf_merkletree");
|
||||
|
||||
let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
|
||||
let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
|
||||
|
||||
for size in BATCH_SIZES {
|
||||
let leaves = &random_data[..size];
|
||||
|
||||
let mtree_leaves: Vec<Word> = leaves.iter().map(|v| v.into()).collect();
|
||||
let mtree = MerkleTree::new(mtree_leaves.clone()).unwrap();
|
||||
let store = MerkleStore::new().with_merkle_tree(mtree_leaves).unwrap();
|
||||
let depth = mtree.depth();
|
||||
let root = mtree.root();
|
||||
let size_u64 = size as u64;
|
||||
|
||||
group.bench_function(BenchmarkId::new("MerkleTree", size), |b| {
|
||||
b.iter_batched(
|
||||
|| random_index(size_u64),
|
||||
|value| black_box(mtree.get_node(NodeIndex::new(depth, value))),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
|
||||
b.iter_batched(
|
||||
|| random_index(size_u64),
|
||||
|value| black_box(store.get_node(root, NodeIndex::new(depth, value))),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Benchmarks getting a leaf on SMT and Merkle stores of varying power-of-two sizes.
|
||||
fn get_leaf_simplesmt(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("get_leaf_simplesmt");
|
||||
|
||||
let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
|
||||
let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
|
||||
|
||||
for size in BATCH_SIZES {
|
||||
let leaves = &random_data[..size];
|
||||
|
||||
let smt_leaves = leaves
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(c, v)| (c.try_into().unwrap(), v.into()))
|
||||
.collect::<Vec<(u64, Word)>>();
|
||||
let smt = SimpleSmt::new(63)
|
||||
.unwrap()
|
||||
.with_leaves(smt_leaves.clone())
|
||||
.unwrap();
|
||||
let store = MerkleStore::new()
|
||||
.with_sparse_merkle_tree(smt_leaves)
|
||||
.unwrap();
|
||||
let depth = smt.depth();
|
||||
let root = smt.root();
|
||||
let size_u64 = size as u64;
|
||||
|
||||
group.bench_function(BenchmarkId::new("SimpleSmt", size), |b| {
|
||||
b.iter_batched(
|
||||
|| random_index(size_u64),
|
||||
|value| black_box(smt.get_node(&NodeIndex::new(depth, value))),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
|
||||
b.iter_batched(
|
||||
|| random_index(size_u64),
|
||||
|value| black_box(store.get_node(root, NodeIndex::new(depth, value))),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Benchmarks getting a node at half of the depth of an empty SMT and an empty Merkle store.
|
||||
fn get_node_of_empty_simplesmt(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("get_node_of_empty_simplesmt");
|
||||
|
||||
let depth = 63u8;
|
||||
let size = 2u64.pow(depth as u32);
|
||||
|
||||
// both SMT and the store are pre-populated with the empty hashes, accessing the internal nodes
|
||||
// of these values is what is being benchmarked here, so no values are inserted into the
|
||||
// backends.
|
||||
let smt = SimpleSmt::new(depth).unwrap();
|
||||
let store = MerkleStore::new();
|
||||
let root = smt.root();
|
||||
let half_depth = depth / 2;
|
||||
|
||||
group.bench_function(BenchmarkId::new("SimpleSmt", depth), |b| {
|
||||
b.iter_batched(
|
||||
|| random_index(size),
|
||||
|value| black_box(smt.get_node(&NodeIndex::new(half_depth, value))),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
group.bench_function(BenchmarkId::new("MerkleStore", depth), |b| {
|
||||
b.iter_batched(
|
||||
|| random_index(size),
|
||||
|value| black_box(store.get_node(root, NodeIndex::new(half_depth, value))),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
/// Benchmarks getting a node at half of the depth of a Merkle tree and Merkle store of varying
|
||||
/// power-of-two sizes.
|
||||
fn get_node_merkletree(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("get_node_merkletree");
|
||||
|
||||
let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
|
||||
let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
|
||||
|
||||
for size in BATCH_SIZES {
|
||||
let leaves = &random_data[..size];
|
||||
|
||||
let mtree_leaves: Vec<Word> = leaves.iter().map(|v| v.into()).collect();
|
||||
let mtree = MerkleTree::new(mtree_leaves.clone()).unwrap();
|
||||
let store = MerkleStore::new().with_merkle_tree(mtree_leaves).unwrap();
|
||||
let half_depth = mtree.depth() / 2;
|
||||
let root = mtree.root();
|
||||
let size_u64 = size as u64;
|
||||
|
||||
group.bench_function(BenchmarkId::new("MerkleTree", size), |b| {
|
||||
b.iter_batched(
|
||||
|| random_index(size_u64),
|
||||
|value| black_box(mtree.get_node(NodeIndex::new(half_depth, value))),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
|
||||
b.iter_batched(
|
||||
|| random_index(size_u64),
|
||||
|value| black_box(store.get_node(root, NodeIndex::new(half_depth, value))),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Benchmarks getting a node at half the depth on SMT and Merkle stores of varying power-of-two
|
||||
/// sizes.
|
||||
fn get_node_simplesmt(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("get_node_simplesmt");
|
||||
|
||||
let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
|
||||
let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
|
||||
|
||||
for size in BATCH_SIZES {
|
||||
let leaves = &random_data[..size];
|
||||
|
||||
let smt_leaves = leaves
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(c, v)| (c.try_into().unwrap(), v.into()))
|
||||
.collect::<Vec<(u64, Word)>>();
|
||||
let smt = SimpleSmt::new(63)
|
||||
.unwrap()
|
||||
.with_leaves(smt_leaves.clone())
|
||||
.unwrap();
|
||||
let store = MerkleStore::new()
|
||||
.with_sparse_merkle_tree(smt_leaves)
|
||||
.unwrap();
|
||||
let root = smt.root();
|
||||
let size_u64 = size as u64;
|
||||
let half_depth = smt.depth() / 2;
|
||||
|
||||
group.bench_function(BenchmarkId::new("SimpleSmt", size), |b| {
|
||||
b.iter_batched(
|
||||
|| random_index(size_u64),
|
||||
|value| black_box(smt.get_node(&NodeIndex::new(half_depth, value))),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
|
||||
b.iter_batched(
|
||||
|| random_index(size_u64),
|
||||
|value| black_box(store.get_node(root, NodeIndex::new(half_depth, value))),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Benchmarks getting a path of a leaf on the Merkle tree and Merkle store backends.
|
||||
fn get_leaf_path_merkletree(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("get_leaf_path_merkletree");
|
||||
|
||||
let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
|
||||
let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
|
||||
|
||||
for size in BATCH_SIZES {
|
||||
let leaves = &random_data[..size];
|
||||
|
||||
let mtree_leaves: Vec<Word> = leaves.iter().map(|v| v.into()).collect();
|
||||
let mtree = MerkleTree::new(mtree_leaves.clone()).unwrap();
|
||||
let store = MerkleStore::new().with_merkle_tree(mtree_leaves).unwrap();
|
||||
let depth = mtree.depth();
|
||||
let root = mtree.root();
|
||||
let size_u64 = size as u64;
|
||||
|
||||
group.bench_function(BenchmarkId::new("MerkleTree", size), |b| {
|
||||
b.iter_batched(
|
||||
|| random_index(size_u64),
|
||||
|value| black_box(mtree.get_path(NodeIndex::new(depth, value))),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
|
||||
b.iter_batched(
|
||||
|| random_index(size_u64),
|
||||
|value| black_box(store.get_path(root, NodeIndex::new(depth, value))),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Benchmarks getting a path of a leaf on the SMT and Merkle store backends.
|
||||
fn get_leaf_path_simplesmt(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("get_leaf_path_simplesmt");
|
||||
|
||||
let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
|
||||
let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
|
||||
|
||||
for size in BATCH_SIZES {
|
||||
let leaves = &random_data[..size];
|
||||
|
||||
let smt_leaves = leaves
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(c, v)| (c.try_into().unwrap(), v.into()))
|
||||
.collect::<Vec<(u64, Word)>>();
|
||||
let smt = SimpleSmt::new(63)
|
||||
.unwrap()
|
||||
.with_leaves(smt_leaves.clone())
|
||||
.unwrap();
|
||||
let store = MerkleStore::new()
|
||||
.with_sparse_merkle_tree(smt_leaves)
|
||||
.unwrap();
|
||||
let depth = smt.depth();
|
||||
let root = smt.root();
|
||||
let size_u64 = size as u64;
|
||||
|
||||
group.bench_function(BenchmarkId::new("SimpleSmt", size), |b| {
|
||||
b.iter_batched(
|
||||
|| random_index(size_u64),
|
||||
|value| black_box(smt.get_path(NodeIndex::new(depth, value))),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
|
||||
b.iter_batched(
|
||||
|| random_index(size_u64),
|
||||
|value| black_box(store.get_path(root, NodeIndex::new(depth, value))),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Benchmarks creation of the different storage backends
|
||||
fn new(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("new");
|
||||
|
||||
let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
|
||||
let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
|
||||
|
||||
for size in BATCH_SIZES {
|
||||
let leaves = &random_data[..size];
|
||||
|
||||
// MerkleTree constructor is optimized to work with vectors. Create a new copy of the data
|
||||
// and pass it to the benchmark function
|
||||
group.bench_function(BenchmarkId::new("MerkleTree::new", size), |b| {
|
||||
b.iter_batched(
|
||||
|| leaves.iter().map(|v| v.into()).collect::<Vec<Word>>(),
|
||||
|l| black_box(MerkleTree::new(l)),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
// This could be done with `bench_with_input`, however to remove variables while comparing
|
||||
// with MerkleTree it is using `iter_batched`
|
||||
group.bench_function(
|
||||
BenchmarkId::new("MerkleStore::with_merkle_tree", size),
|
||||
|b| {
|
||||
b.iter_batched(
|
||||
|| leaves.iter().map(|v| v.into()).collect::<Vec<Word>>(),
|
||||
|l| black_box(MerkleStore::new().with_merkle_tree(l)),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
group.bench_function(BenchmarkId::new("SimpleSmt::new", size), |b| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
leaves
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(c, v)| (c.try_into().unwrap(), v.into()))
|
||||
.collect::<Vec<(u64, Word)>>()
|
||||
},
|
||||
|l| black_box(SimpleSmt::new(63).unwrap().with_leaves(l)),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
group.bench_function(
|
||||
BenchmarkId::new("MerkleStore::with_sparse_merkle_tree", size),
|
||||
|b| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
leaves
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(c, v)| (c.try_into().unwrap(), v.into()))
|
||||
.collect::<Vec<(u64, Word)>>()
|
||||
},
|
||||
|l| black_box(MerkleStore::new().with_sparse_merkle_tree(l)),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Benchmarks updating a leaf on MerkleTree and MerkleStore backends.
|
||||
fn update_leaf_merkletree(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("update_leaf_merkletree");
|
||||
|
||||
let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
|
||||
let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
|
||||
|
||||
for size in BATCH_SIZES {
|
||||
let leaves = &random_data[..size];
|
||||
|
||||
let mtree_leaves: Vec<Word> = leaves.iter().map(|v| v.into()).collect();
|
||||
let mut mtree = MerkleTree::new(mtree_leaves.clone()).unwrap();
|
||||
let mut store = MerkleStore::new().with_merkle_tree(mtree_leaves).unwrap();
|
||||
let depth = mtree.depth();
|
||||
let root = mtree.root();
|
||||
let size_u64 = size as u64;
|
||||
|
||||
group.bench_function(BenchmarkId::new("MerkleTree", size), |b| {
|
||||
b.iter_batched(
|
||||
|| (random_index(size_u64), random_word()),
|
||||
|(index, value)| black_box(mtree.update_leaf(index, value)),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
let mut store_root = root;
|
||||
group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
|
||||
b.iter_batched(
|
||||
|| (random_index(size_u64), random_word()),
|
||||
|(index, value)| {
|
||||
// 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, NodeIndex::new(depth, index), value)
|
||||
.unwrap()
|
||||
.root;
|
||||
black_box(store_root)
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Benchmarks updating a leaf on SMT and MerkleStore backends.
|
||||
fn update_leaf_simplesmt(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("update_leaf_simplesmt");
|
||||
|
||||
let random_data_size = BATCH_SIZES.into_iter().max().unwrap();
|
||||
let random_data: Vec<RpoDigest> = (0..random_data_size).map(|_| random_rpo_digest()).collect();
|
||||
|
||||
for size in BATCH_SIZES {
|
||||
let leaves = &random_data[..size];
|
||||
|
||||
let smt_leaves = leaves
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(c, v)| (c.try_into().unwrap(), v.into()))
|
||||
.collect::<Vec<(u64, Word)>>();
|
||||
let mut smt = SimpleSmt::new(63)
|
||||
.unwrap()
|
||||
.with_leaves(smt_leaves.clone())
|
||||
.unwrap();
|
||||
let mut store = MerkleStore::new()
|
||||
.with_sparse_merkle_tree(smt_leaves)
|
||||
.unwrap();
|
||||
let depth = smt.depth();
|
||||
let root = smt.root();
|
||||
let size_u64 = size as u64;
|
||||
|
||||
group.bench_function(BenchmarkId::new("SimpleSMT", size), |b| {
|
||||
b.iter_batched(
|
||||
|| (random_index(size_u64), random_word()),
|
||||
|(index, value)| black_box(smt.update_leaf(index, value)),
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
|
||||
let mut store_root = root;
|
||||
group.bench_function(BenchmarkId::new("MerkleStore", size), |b| {
|
||||
b.iter_batched(
|
||||
|| (random_index(size_u64), random_word()),
|
||||
|(index, value)| {
|
||||
// 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, NodeIndex::new(depth, index), value)
|
||||
.unwrap()
|
||||
.root;
|
||||
black_box(store_root)
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
store_group,
|
||||
get_empty_leaf_simplesmt,
|
||||
get_leaf_merkletree,
|
||||
get_leaf_path_merkletree,
|
||||
get_leaf_path_simplesmt,
|
||||
get_leaf_simplesmt,
|
||||
get_node_merkletree,
|
||||
get_node_of_empty_simplesmt,
|
||||
get_node_simplesmt,
|
||||
new,
|
||||
update_leaf_merkletree,
|
||||
update_leaf_simplesmt,
|
||||
);
|
||||
criterion_main!(store_group);
|
169
src/bit.rs
Normal file
169
src/bit.rs
Normal file
|
@ -0,0 +1,169 @@
|
|||
/// Yields the bits of a `u64`.
|
||||
pub struct BitIterator {
|
||||
/// The value that is being iterated bit-wise
|
||||
value: u64,
|
||||
/// True bits in the `mask` are the bits that have been visited.
|
||||
mask: u64,
|
||||
}
|
||||
|
||||
impl BitIterator {
|
||||
pub fn new(value: u64) -> BitIterator {
|
||||
BitIterator { value, mask: 0 }
|
||||
}
|
||||
|
||||
/// An efficient skip implementation.
|
||||
///
|
||||
/// Note: The compiler is smart enough to translate a `skip(n)` into a single shift instruction
|
||||
/// if the code is inlined, however inlining does not always happen.
|
||||
pub fn skip_front(mut self, n: u32) -> Self {
|
||||
let mask = bitmask(n);
|
||||
let ones = self.mask.trailing_ones();
|
||||
let mask_position = ones;
|
||||
self.mask ^= mask.checked_shl(mask_position).unwrap_or(0);
|
||||
self
|
||||
}
|
||||
|
||||
/// An efficient skip from the back.
|
||||
///
|
||||
/// Note: The compiler is smart enough to translate a `skip(n)` into a single shift instruction
|
||||
/// if the code is inlined, however inlining does not always happen.
|
||||
pub fn skip_back(mut self, n: u32) -> Self {
|
||||
let mask = bitmask(n);
|
||||
let ones = self.mask.leading_ones();
|
||||
let mask_position = u64::BITS - ones - n;
|
||||
self.mask ^= mask.checked_shl(mask_position).unwrap_or(0);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for BitIterator {
|
||||
type Item = bool;
|
||||
|
||||
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
||||
// trailing_ones is implemented with trailing_zeros, and the zeros are computed with the
|
||||
// intrinsic cttz. [Rust 1.67.0] x86 uses the `bsf` instruction. AArch64 uses the `rbit
|
||||
// clz` instructions.
|
||||
let ones = self.mask.trailing_ones();
|
||||
|
||||
if ones == u64::BITS {
|
||||
None
|
||||
} else {
|
||||
let bit_position = ones;
|
||||
let mask = 1 << bit_position;
|
||||
self.mask ^= mask;
|
||||
let bit = self.value & mask;
|
||||
Some(bit != 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DoubleEndedIterator for BitIterator {
|
||||
fn next_back(&mut self) -> Option<<Self as Iterator>::Item> {
|
||||
// leading_ones is implemented with leading_zeros, and the zeros are computed with the
|
||||
// intrinsic ctlz. [Rust 1.67.0] x86 uses the `bsr` instruction. AArch64 uses the `clz`
|
||||
// instruction.
|
||||
let ones = self.mask.leading_ones();
|
||||
|
||||
if ones == u64::BITS {
|
||||
None
|
||||
} else {
|
||||
let bit_position = u64::BITS - ones - 1;
|
||||
let mask = 1 << bit_position;
|
||||
self.mask ^= mask;
|
||||
let bit = self.value & mask;
|
||||
Some(bit != 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::BitIterator;
|
||||
|
||||
#[test]
|
||||
fn test_bit_iterator() {
|
||||
let v = 0b1;
|
||||
let mut it = BitIterator::new(v);
|
||||
assert!(it.next().unwrap(), "first bit is true");
|
||||
assert!(it.all(|v| v == false), "every other value is false");
|
||||
|
||||
let v = 0b10;
|
||||
let mut it = BitIterator::new(v);
|
||||
assert!(!it.next().unwrap(), "first bit is false");
|
||||
assert!(it.next().unwrap(), "first bit is true");
|
||||
assert!(it.all(|v| v == false), "every other value is false");
|
||||
|
||||
let v = 0b10;
|
||||
let mut it = BitIterator::new(v);
|
||||
assert!(!it.next_back().unwrap(), "last bit is false");
|
||||
assert!(!it.next().unwrap(), "first bit is false");
|
||||
assert!(it.next().unwrap(), "first bit is true");
|
||||
assert!(it.all(|v| v == false), "every other value is false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bit_iterator_skip() {
|
||||
let v = 0b1;
|
||||
let mut it = BitIterator::new(v).skip_front(1);
|
||||
assert!(it.all(|v| v == false), "every other value is false");
|
||||
|
||||
let v = 0b10;
|
||||
let mut it = BitIterator::new(v).skip_front(1);
|
||||
assert!(it.next().unwrap(), "first bit is true");
|
||||
assert!(it.all(|v| v == false), "every other value is false");
|
||||
|
||||
let high_bit = 0b1 << (u64::BITS - 1);
|
||||
let mut it = BitIterator::new(high_bit).skip_back(1);
|
||||
assert!(it.all(|v| v == false), "every other value is false");
|
||||
|
||||
let v = 0b10;
|
||||
let mut it = BitIterator::new(v).skip_back(1);
|
||||
assert!(!it.next_back().unwrap(), "last bit is false");
|
||||
assert!(!it.next().unwrap(), "first bit is false");
|
||||
assert!(it.next().unwrap(), "first bit is true");
|
||||
assert!(it.all(|v| v == false), "every other value is false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_skip_all() {
|
||||
let v = 0b1;
|
||||
let mut it = BitIterator::new(v).skip_front(u64::BITS);
|
||||
assert!(it.next().is_none(), "iterator must be exhausted");
|
||||
|
||||
let v = 0b1;
|
||||
let mut it = BitIterator::new(v).skip_back(u64::BITS);
|
||||
assert!(it.next().is_none(), "iterator must be exhausted");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bit_iterator_count_bits_after_skip() {
|
||||
let any_value = 0b1;
|
||||
for s in 0..u64::BITS {
|
||||
let it = BitIterator::new(any_value).skip_front(s);
|
||||
assert_eq!(it.count() as u32, u64::BITS - s)
|
||||
}
|
||||
|
||||
let any_value = 0b1;
|
||||
for s in 1..u64::BITS {
|
||||
let it = BitIterator::new(any_value).skip_back(s);
|
||||
assert_eq!(it.count() as u32, u64::BITS - s)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bit_iterator_rev() {
|
||||
let v = 0b1;
|
||||
let mut it = BitIterator::new(v).rev();
|
||||
assert!(it.nth(63).unwrap(), "the last value is true");
|
||||
}
|
||||
}
|
||||
|
||||
// UTILITIES
|
||||
// ===============================================================================================
|
||||
|
||||
fn bitmask(s: u32) -> u64 {
|
||||
match 1u64.checked_shl(s) {
|
||||
Some(r) => r - 1,
|
||||
None => u64::MAX,
|
||||
}
|
||||
}
|
|
@ -290,7 +290,7 @@ where
|
|||
let digest = if Felt::IS_CANONICAL {
|
||||
blake3::hash(E::elements_as_bytes(elements))
|
||||
} else {
|
||||
let base_elements = E::as_base_elements(elements);
|
||||
let base_elements = E::slice_as_base_elements(elements);
|
||||
let blen = base_elements.len() << 3;
|
||||
|
||||
let mut bytes = unsafe { uninit_vector(blen) };
|
||||
|
|
|
@ -11,7 +11,7 @@ use core::{cmp::Ordering, ops::Deref};
|
|||
pub struct RpoDigest([Felt; DIGEST_SIZE]);
|
||||
|
||||
impl RpoDigest {
|
||||
pub fn new(value: [Felt; DIGEST_SIZE]) -> Self {
|
||||
pub const fn new(value: [Felt; DIGEST_SIZE]) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
|
||||
|
@ -73,12 +73,24 @@ impl From<[Felt; DIGEST_SIZE]> for RpoDigest {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&RpoDigest> for [Felt; DIGEST_SIZE] {
|
||||
fn from(value: &RpoDigest) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RpoDigest> for [Felt; DIGEST_SIZE] {
|
||||
fn from(value: RpoDigest) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&RpoDigest> for [u8; 32] {
|
||||
fn from(value: &RpoDigest) -> Self {
|
||||
value.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RpoDigest> for [u8; 32] {
|
||||
fn from(value: RpoDigest) -> Self {
|
||||
value.as_bytes()
|
||||
|
|
|
@ -212,7 +212,7 @@ impl ElementHasher for Rpo256 {
|
|||
|
||||
fn hash_elements<E: FieldElement<BaseField = Self::BaseField>>(elements: &[E]) -> Self::Digest {
|
||||
// convert the elements into a list of base field elements
|
||||
let elements = E::as_base_elements(elements);
|
||||
let elements = E::slice_as_base_elements(elements);
|
||||
|
||||
// initialize state to all zeros, except for the first element of the capacity part, which
|
||||
// is set to 1 if the number of elements is not a multiple of RATE_WIDTH.
|
||||
|
|
|
@ -4,20 +4,16 @@
|
|||
#[cfg_attr(test, macro_use)]
|
||||
extern crate alloc;
|
||||
|
||||
mod bit;
|
||||
pub mod hash;
|
||||
pub mod merkle;
|
||||
pub mod utils;
|
||||
|
||||
// RE-EXPORTS
|
||||
// ================================================================================================
|
||||
|
||||
pub use winter_crypto::{RandomCoin, RandomCoinError};
|
||||
pub use winter_math::{fields::f64::BaseElement as Felt, FieldElement, StarkField};
|
||||
pub mod utils {
|
||||
pub use winter_utils::{
|
||||
collections, string, uninit_vector, ByteReader, ByteWriter, Deserializable,
|
||||
DeserializationError, Serializable, SliceReader,
|
||||
};
|
||||
}
|
||||
|
||||
// TYPE ALIASES
|
||||
// ================================================================================================
|
||||
|
|
1585
src/merkle/empty_roots.rs
Normal file
1585
src/merkle/empty_roots.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,9 +1,13 @@
|
|||
use super::{Felt, MerkleError, RpoDigest, StarkField};
|
||||
use crate::bit::BitIterator;
|
||||
|
||||
// NODE INDEX
|
||||
// ================================================================================================
|
||||
|
||||
/// A Merkle tree address to an arbitrary node.
|
||||
///
|
||||
/// The position is relative to a tree in level order, where for a given depth `d` elements are
|
||||
/// numbered from $0..2^d$.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||
pub struct NodeIndex {
|
||||
depth: u8,
|
||||
|
@ -74,7 +78,7 @@ impl NodeIndex {
|
|||
self.depth
|
||||
}
|
||||
|
||||
/// Returns the value of the current depth.
|
||||
/// Returns the value of this index.
|
||||
pub const fn value(&self) -> u64 {
|
||||
self.value
|
||||
}
|
||||
|
@ -94,6 +98,19 @@ impl NodeIndex {
|
|||
self.depth == 0
|
||||
}
|
||||
|
||||
/// Returns a bit iterator for the `value`.
|
||||
///
|
||||
/// Bits read from left-to-right represent which internal node's child should be visited to
|
||||
/// arrive at the leaf. From the right-to-left the bit represent the position the hash of the
|
||||
/// current element should go.
|
||||
///
|
||||
/// Additionally, the value that is not visited are the sibling values necessary for a Merkle
|
||||
/// opening.
|
||||
pub fn bit_iterator(&self) -> BitIterator {
|
||||
let depth: u32 = self.depth.into();
|
||||
BitIterator::new(self.value).skip_back(u64::BITS - depth)
|
||||
}
|
||||
|
||||
// STATE MUTATORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use super::{Felt, MerkleError, MerklePath, NodeIndex, Rpo256, RpoDigest, Vec, Word};
|
||||
use crate::{utils::uninit_vector, FieldElement};
|
||||
use core::slice;
|
||||
use crate::{
|
||||
utils::{string::String, uninit_vector, word_to_hex},
|
||||
FieldElement,
|
||||
};
|
||||
use core::{fmt, slice};
|
||||
use winter_math::log2;
|
||||
|
||||
// MERKLE TREE
|
||||
|
@ -9,7 +12,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>,
|
||||
pub(crate) nodes: Vec<Word>,
|
||||
}
|
||||
|
||||
impl MerkleTree {
|
||||
|
@ -108,6 +111,11 @@ impl MerkleTree {
|
|||
index.move_up();
|
||||
}
|
||||
|
||||
debug_assert!(
|
||||
index.is_root(),
|
||||
"the path walk must go all the way to the root"
|
||||
);
|
||||
|
||||
Ok(path.into())
|
||||
}
|
||||
|
||||
|
@ -152,6 +160,52 @@ impl MerkleTree {
|
|||
}
|
||||
}
|
||||
|
||||
/// Utility to vizualize a [MerkleTree] in text.
|
||||
pub fn tree_to_text(tree: &MerkleTree) -> Result<String, fmt::Error> {
|
||||
let indent = " ";
|
||||
let mut s = String::new();
|
||||
s.push_str(&word_to_hex(&tree.root())?);
|
||||
s.push('\n');
|
||||
for d in 1..=tree.depth() {
|
||||
let entries = 2u64.pow(d.into());
|
||||
for i in 0..entries {
|
||||
let index = NodeIndex::new(d, i);
|
||||
|
||||
let node = tree
|
||||
.get_node(index)
|
||||
.expect("The index must always be valid");
|
||||
|
||||
for _ in 0..d {
|
||||
s.push_str(indent);
|
||||
}
|
||||
s.push_str(&word_to_hex(&node)?);
|
||||
s.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
/// Utility to vizualize a [MerklePath] in text.
|
||||
pub fn path_to_text(path: &MerklePath) -> Result<String, fmt::Error> {
|
||||
let mut s = String::new();
|
||||
s.push('[');
|
||||
|
||||
for el in path.iter() {
|
||||
s.push_str(&word_to_hex(el)?);
|
||||
s.push_str(", ");
|
||||
}
|
||||
|
||||
// remove the last ", "
|
||||
if path.len() != 0 {
|
||||
s.pop();
|
||||
s.pop();
|
||||
}
|
||||
s.push(']');
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
// TESTS
|
||||
// ================================================================================================
|
||||
|
||||
|
|
44
src/merkle/mmr/accumulator.rs
Normal file
44
src/merkle/mmr/accumulator.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use super::{super::Vec, MmrProof, Rpo256, Word};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct MmrPeaks {
|
||||
/// The number of leaves is used to differentiate accumulators that have the same number of
|
||||
/// peaks. This happens because the number of peaks goes up-and-down as the structure is used
|
||||
/// causing existing trees to be merged and new ones to be created. As an example, every time
|
||||
/// the MMR has a power-of-two number of leaves there is a single peak.
|
||||
///
|
||||
/// Every tree in the MMR forest has a distinct power-of-two size, this means only the right
|
||||
/// most tree can have an odd number of elements (1). Additionally this means that the bits in
|
||||
/// `num_leaves` conveniently encode the size of each individual tree.
|
||||
///
|
||||
/// Examples:
|
||||
///
|
||||
/// Example 1: With 5 leaves, the binary 0b101. The number of set bits is equal the number
|
||||
/// of peaks, in this case there are 2 peaks. The 0-indexed least-significant position of
|
||||
/// the bit determines the number of elements of a tree, so the rightmost tree has 2**0
|
||||
/// elements and the left most has 2**2.
|
||||
///
|
||||
/// Example 2: With 12 leaves, the binary is 0b1100, this case also has 2 peaks, the
|
||||
/// leftmost tree has 2**3=8 elements, and the right most has 2**2=4 elements.
|
||||
pub num_leaves: usize,
|
||||
|
||||
/// All the peaks of every tree in the MMR forest. The peaks are always ordered by number of
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
impl MmrPeaks {
|
||||
/// Hashes the peaks sequentially, compacting it to a single digest
|
||||
pub fn hash_peaks(&self) -> Word {
|
||||
Rpo256::hash_elements(&self.peaks.as_slice().concat()).into()
|
||||
}
|
||||
|
||||
pub fn verify(&self, value: Word, opening: MmrProof) -> bool {
|
||||
let root = &self.peaks[opening.peak_index()];
|
||||
opening
|
||||
.merkle_path
|
||||
.verify(opening.relative_pos() as u64, value, root)
|
||||
}
|
||||
}
|
46
src/merkle/mmr/bit.rs
Normal file
46
src/merkle/mmr/bit.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
/// Iterate over the bits of a `usize` and yields the bit positions for the true bits.
|
||||
pub struct TrueBitPositionIterator {
|
||||
value: usize,
|
||||
}
|
||||
|
||||
impl TrueBitPositionIterator {
|
||||
pub fn new(value: usize) -> TrueBitPositionIterator {
|
||||
TrueBitPositionIterator { value }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for TrueBitPositionIterator {
|
||||
type Item = u32;
|
||||
|
||||
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
||||
// trailing_zeros is computed with the intrinsic cttz. [Rust 1.67.0] x86 uses the `bsf`
|
||||
// instruction. AArch64 uses the `rbit clz` instructions.
|
||||
let zeros = self.value.trailing_zeros();
|
||||
|
||||
if zeros == usize::BITS {
|
||||
None
|
||||
} else {
|
||||
let bit_position = zeros;
|
||||
let mask = 1 << bit_position;
|
||||
self.value ^= mask;
|
||||
Some(bit_position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DoubleEndedIterator for TrueBitPositionIterator {
|
||||
fn next_back(&mut self) -> Option<<Self as Iterator>::Item> {
|
||||
// trailing_zeros is computed with the intrinsic ctlz. [Rust 1.67.0] x86 uses the `bsr`
|
||||
// instruction. AArch64 uses the `clz` instruction.
|
||||
let zeros = self.value.leading_zeros();
|
||||
|
||||
if zeros == usize::BITS {
|
||||
None
|
||||
} else {
|
||||
let bit_position = usize::BITS - zeros - 1;
|
||||
let mask = 1 << bit_position;
|
||||
self.value ^= mask;
|
||||
Some(bit_position)
|
||||
}
|
||||
}
|
||||
}
|
299
src/merkle/mmr/full.rs
Normal file
299
src/merkle/mmr/full.rs
Normal file
|
@ -0,0 +1,299 @@
|
|||
//! A fully materialized Merkle mountain range (MMR).
|
||||
//!
|
||||
//! A MMR is a forest structure, i.e. it is an ordered set of disjoint rooted trees. The trees are
|
||||
//! ordered by size, from the most to least number of leaves. Every tree is a perfect binary tree,
|
||||
//! meaning a tree has all its leaves at the same depth, and every inner node has a branch-factor
|
||||
//! of 2 with both children set.
|
||||
//!
|
||||
//! Additionally the structure only supports adding leaves to the right-most tree, the one with the
|
||||
//! least number of leaves. The structure preserves the invariant that each tree has different
|
||||
//! depths, i.e. as part of adding adding a new element to the forest the trees with same depth are
|
||||
//! merged, creating a new tree with depth d+1, this process is continued until the property is
|
||||
//! restabilished.
|
||||
use super::bit::TrueBitPositionIterator;
|
||||
use super::{super::Vec, MmrPeaks, MmrProof, Rpo256, Word};
|
||||
use crate::merkle::MerklePath;
|
||||
use core::fmt::{Display, Formatter};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use std::error::Error;
|
||||
|
||||
// MMR
|
||||
// ===============================================================================================
|
||||
|
||||
/// A fully materialized Merkle Mountain Range, with every tree in the forest and all their
|
||||
/// elements.
|
||||
///
|
||||
/// Since this is a full representation of the MMR, elements are never removed and the MMR will
|
||||
/// grow roughly `O(2n)` in number of leaf elements.
|
||||
pub struct Mmr {
|
||||
/// Refer to the `forest` method documentation for details of the semantics of this value.
|
||||
pub(super) forest: usize,
|
||||
|
||||
/// Contains every element of the forest.
|
||||
///
|
||||
/// The trees are in postorder sequential representation. This representation allows for all
|
||||
/// the elements of every tree in the forest to be stored in the same sequential buffer. It
|
||||
/// also means new elements can be added to the forest, and merging of trees is very cheap with
|
||||
/// no need to copy elements.
|
||||
pub(super) nodes: Vec<Word>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum MmrError {
|
||||
InvalidPosition(usize),
|
||||
}
|
||||
|
||||
impl Display for MmrError {
|
||||
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
|
||||
match self {
|
||||
MmrError::InvalidPosition(pos) => write!(fmt, "Mmr does not contain position {pos}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl Error for MmrError {}
|
||||
|
||||
impl Default for Mmr {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mmr {
|
||||
// CONSTRUCTORS
|
||||
// ============================================================================================
|
||||
|
||||
/// Constructor for an empty `Mmr`.
|
||||
pub fn new() -> Mmr {
|
||||
Mmr {
|
||||
forest: 0,
|
||||
nodes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// ACCESSORS
|
||||
// ============================================================================================
|
||||
|
||||
/// Returns the MMR forest representation.
|
||||
///
|
||||
/// The forest value has the following interpretations:
|
||||
/// - its value is the number of elements in the forest
|
||||
/// - bit count corresponds to the number of trees in the forest
|
||||
/// - each true bit position determines the depth of a tree in the forest
|
||||
pub const fn forest(&self) -> usize {
|
||||
self.forest
|
||||
}
|
||||
|
||||
// FUNCTIONALITY
|
||||
// ============================================================================================
|
||||
|
||||
/// Given a leaf position, returns the Merkle path to its corresponding peak. If the position
|
||||
/// is greater-or-equal than the tree size an error is returned.
|
||||
///
|
||||
/// Note: The leaf position is the 0-indexed number corresponding to the order the leaves were
|
||||
/// added, this corresponds to the MMR size _prior_ to adding the element. So the 1st element
|
||||
/// has position 0, the second position 1, and so on.
|
||||
pub fn open(&self, pos: usize) -> Result<MmrProof, MmrError> {
|
||||
// find the target tree responsible for the MMR position
|
||||
let tree_bit =
|
||||
leaf_to_corresponding_tree(pos, self.forest).ok_or(MmrError::InvalidPosition(pos))?;
|
||||
let forest_target = 1usize << tree_bit;
|
||||
|
||||
// isolate the trees before the target
|
||||
let forest_before = self.forest & high_bitmask(tree_bit + 1);
|
||||
let index_offset = nodes_in_forest(forest_before);
|
||||
|
||||
// find the root
|
||||
let index = nodes_in_forest(forest_target) - 1;
|
||||
|
||||
// update the value position from global to the target tree
|
||||
let relative_pos = pos - forest_before;
|
||||
|
||||
// collect the path and the final index of the target value
|
||||
let (_, path) =
|
||||
self.collect_merkle_path_and_value(tree_bit, relative_pos, index_offset, index);
|
||||
|
||||
Ok(MmrProof {
|
||||
forest: self.forest,
|
||||
position: pos,
|
||||
merkle_path: MerklePath::new(path),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the leaf value at position `pos`.
|
||||
///
|
||||
/// Note: The leaf position is the 0-indexed number corresponding to the order the leaves were
|
||||
/// added, this corresponds to the MMR size _prior_ to adding the element. So the 1st element
|
||||
/// has position 0, the second position 1, and so on.
|
||||
pub fn get(&self, pos: usize) -> Result<Word, MmrError> {
|
||||
// find the target tree responsible for the MMR position
|
||||
let tree_bit =
|
||||
leaf_to_corresponding_tree(pos, self.forest).ok_or(MmrError::InvalidPosition(pos))?;
|
||||
let forest_target = 1usize << tree_bit;
|
||||
|
||||
// isolate the trees before the target
|
||||
let forest_before = self.forest & high_bitmask(tree_bit + 1);
|
||||
let index_offset = nodes_in_forest(forest_before);
|
||||
|
||||
// find the root
|
||||
let index = nodes_in_forest(forest_target) - 1;
|
||||
|
||||
// update the value position from global to the target tree
|
||||
let relative_pos = pos - forest_before;
|
||||
|
||||
// collect the path and the final index of the target value
|
||||
let (value, _) =
|
||||
self.collect_merkle_path_and_value(tree_bit, relative_pos, index_offset, index);
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Adds a new element to the MMR.
|
||||
pub fn add(&mut self, el: Word) {
|
||||
// Note: every node is also a tree of size 1, adding an element to the forest creates a new
|
||||
// rooted-tree of size 1. This may temporarily break the invariant that every tree in the
|
||||
// forest has different sizes, the loop below will eagerly merge trees of same size and
|
||||
// restore the invariant.
|
||||
self.nodes.push(el);
|
||||
|
||||
let mut left_offset = self.nodes.len().saturating_sub(2);
|
||||
let mut right = el;
|
||||
let mut left_tree = 1;
|
||||
while self.forest & left_tree != 0 {
|
||||
right = *Rpo256::merge(&[self.nodes[left_offset].into(), right.into()]);
|
||||
self.nodes.push(right);
|
||||
|
||||
left_offset = left_offset.saturating_sub(nodes_in_forest(left_tree));
|
||||
left_tree <<= 1;
|
||||
}
|
||||
|
||||
self.forest += 1;
|
||||
}
|
||||
|
||||
/// Returns an accumulator representing the current state of the MMMR.
|
||||
pub fn accumulator(&self) -> MmrPeaks {
|
||||
let peaks: Vec<Word> = TrueBitPositionIterator::new(self.forest)
|
||||
.rev()
|
||||
.map(|bit| nodes_in_forest(1 << bit))
|
||||
.scan(0, |offset, el| {
|
||||
*offset += el;
|
||||
Some(*offset)
|
||||
})
|
||||
.map(|offset| self.nodes[offset - 1])
|
||||
.collect();
|
||||
|
||||
MmrPeaks {
|
||||
num_leaves: self.forest,
|
||||
peaks,
|
||||
}
|
||||
}
|
||||
|
||||
// UTILITIES
|
||||
// ============================================================================================
|
||||
|
||||
/// Internal function used to collect the Merkle path of a value.
|
||||
fn collect_merkle_path_and_value(
|
||||
&self,
|
||||
tree_bit: u32,
|
||||
relative_pos: usize,
|
||||
index_offset: usize,
|
||||
mut index: usize,
|
||||
) -> (Word, Vec<Word>) {
|
||||
// collect the Merkle path
|
||||
let mut tree_depth = tree_bit as usize;
|
||||
let mut path = Vec::with_capacity(tree_depth + 1);
|
||||
while tree_depth > 0 {
|
||||
let bit = relative_pos & tree_depth;
|
||||
let right_offset = index - 1;
|
||||
let left_offset = right_offset - nodes_in_forest(tree_depth);
|
||||
|
||||
// Elements to the right have a higher position because they were
|
||||
// added later. Therefore when the bit is true the node's path is
|
||||
// to the right, and its sibling to the left.
|
||||
let sibling = if bit != 0 {
|
||||
index = right_offset;
|
||||
self.nodes[index_offset + left_offset]
|
||||
} else {
|
||||
index = left_offset;
|
||||
self.nodes[index_offset + right_offset]
|
||||
};
|
||||
|
||||
tree_depth >>= 1;
|
||||
path.push(sibling);
|
||||
}
|
||||
|
||||
// the rest of the codebase has the elements going from leaf to root, adjust it here for
|
||||
// easy of use/consistency sake
|
||||
path.reverse();
|
||||
|
||||
let value = self.nodes[index_offset + index];
|
||||
(value, path)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Mmr
|
||||
where
|
||||
T: IntoIterator<Item = Word>,
|
||||
{
|
||||
fn from(values: T) -> Self {
|
||||
let mut mmr = Mmr::new();
|
||||
for v in values {
|
||||
mmr.add(v)
|
||||
}
|
||||
mmr
|
||||
}
|
||||
}
|
||||
|
||||
// UTILITIES
|
||||
// ===============================================================================================
|
||||
|
||||
/// Given a 0-indexed leaf position and the current forest, return the tree number responsible for
|
||||
/// the position.
|
||||
///
|
||||
/// Note:
|
||||
/// The result is a tree position `p`, it has the following interpretations. $p+1$ is the depth of
|
||||
/// the tree, which corresponds to the size of a Merkle proof for that tree. $2^p$ is equal to the
|
||||
/// number of leaves in this particular tree. and $2^(p+1)-1$ corresponds to size of the tree.
|
||||
pub(crate) const fn leaf_to_corresponding_tree(pos: usize, forest: usize) -> Option<u32> {
|
||||
if pos >= forest {
|
||||
None
|
||||
} else {
|
||||
// - each bit in the forest is a unique tree and the bit position its power-of-two size
|
||||
// - each tree owns a consecutive range of positions equal to its size from left-to-right
|
||||
// - this means the first tree owns from `0` up to the `2^k_0` first positions, where `k_0`
|
||||
// is the highest true bit position, the second tree from `2^k_0 + 1` up to `2^k_1` where
|
||||
// `k_1` is the second higest bit, so on.
|
||||
// - this means the highest bits work as a category marker, and the position is owned by
|
||||
// the first tree which doesn't share a high bit with the position
|
||||
let before = forest & pos;
|
||||
let after = forest ^ before;
|
||||
let tree = after.ilog2();
|
||||
|
||||
Some(tree)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a bitmask for the bits including and above the given position.
|
||||
pub(crate) const fn high_bitmask(bit: u32) -> usize {
|
||||
if bit > usize::BITS - 1 {
|
||||
0
|
||||
} else {
|
||||
usize::MAX << bit
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the total number of nodes of a given forest
|
||||
///
|
||||
/// Panics:
|
||||
///
|
||||
/// This will panic if the forest has size greater than `usize::MAX / 2`
|
||||
pub(crate) const fn nodes_in_forest(forest: usize) -> usize {
|
||||
// - the size of a perfect binary tree is $2^{k+1}-1$ or $2*2^k-1$
|
||||
// - the forest represents the sum of $2^k$ so a single multiplication is necessary
|
||||
// - the number of `-1` is the same as the number of trees, which is the same as the number
|
||||
// bits set
|
||||
let tree_count = forest.count_ones() as usize;
|
||||
forest * 2 - tree_count
|
||||
}
|
15
src/merkle/mmr/mod.rs
Normal file
15
src/merkle/mmr/mod.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
mod accumulator;
|
||||
mod bit;
|
||||
mod full;
|
||||
mod proof;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use super::{Rpo256, Word};
|
||||
|
||||
// REEXPORTS
|
||||
// ================================================================================================
|
||||
pub use accumulator::MmrPeaks;
|
||||
pub use full::Mmr;
|
||||
pub use proof::MmrProof;
|
33
src/merkle/mmr/proof.rs
Normal file
33
src/merkle/mmr/proof.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
/// The representation of a single Merkle path.
|
||||
use super::super::MerklePath;
|
||||
use super::full::{high_bitmask, leaf_to_corresponding_tree};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct MmrProof {
|
||||
/// The state of the MMR when the MmrProof was created.
|
||||
pub forest: usize,
|
||||
|
||||
/// The position of the leaf value on this MmrProof.
|
||||
pub position: usize,
|
||||
|
||||
/// The Merkle opening, starting from the value's sibling up to and excluding the root of the
|
||||
/// responsible tree.
|
||||
pub merkle_path: MerklePath,
|
||||
}
|
||||
|
||||
impl MmrProof {
|
||||
/// Converts the leaf global position into a local position that can be used to verify the
|
||||
/// merkle_path.
|
||||
pub fn relative_pos(&self) -> usize {
|
||||
let tree_bit = leaf_to_corresponding_tree(self.position, self.forest)
|
||||
.expect("position must be part of the forest");
|
||||
let forest_before = self.forest & high_bitmask(tree_bit + 1);
|
||||
self.position - forest_before
|
||||
}
|
||||
|
||||
pub fn peak_index(&self) -> usize {
|
||||
let root = leaf_to_corresponding_tree(self.position, self.forest)
|
||||
.expect("position must be part of the forest");
|
||||
(self.forest.count_ones() - root - 1) as usize
|
||||
}
|
||||
}
|
440
src/merkle/mmr/tests.rs
Normal file
440
src/merkle/mmr/tests.rs
Normal file
|
@ -0,0 +1,440 @@
|
|||
use super::bit::TrueBitPositionIterator;
|
||||
use super::full::{high_bitmask, leaf_to_corresponding_tree, nodes_in_forest};
|
||||
use super::{super::Vec, Mmr, Rpo256, Word};
|
||||
use crate::merkle::{int_to_node, MerklePath};
|
||||
|
||||
#[test]
|
||||
fn test_position_equal_or_higher_than_leafs_is_never_contained() {
|
||||
let empty_forest = 0;
|
||||
for pos in 1..1024 {
|
||||
// pos is index, 0 based
|
||||
// tree is a length counter, 1 based
|
||||
// so a valid pos is always smaller, not equal, to tree
|
||||
assert_eq!(leaf_to_corresponding_tree(pos, pos), None);
|
||||
assert_eq!(leaf_to_corresponding_tree(pos, pos - 1), None);
|
||||
// and empty forest has no trees, so no position is valid
|
||||
assert_eq!(leaf_to_corresponding_tree(pos, empty_forest), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_position_zero_is_always_contained_by_the_highest_tree() {
|
||||
for leaves in 1..1024usize {
|
||||
let tree = leaves.ilog2();
|
||||
assert_eq!(leaf_to_corresponding_tree(0, leaves), Some(tree));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_leaf_to_corresponding_tree() {
|
||||
assert_eq!(leaf_to_corresponding_tree(0, 0b0001), Some(0));
|
||||
assert_eq!(leaf_to_corresponding_tree(0, 0b0010), Some(1));
|
||||
assert_eq!(leaf_to_corresponding_tree(0, 0b0011), Some(1));
|
||||
assert_eq!(leaf_to_corresponding_tree(0, 0b1011), Some(3));
|
||||
|
||||
// position one is always owned by the left-most tree
|
||||
assert_eq!(leaf_to_corresponding_tree(1, 0b0010), Some(1));
|
||||
assert_eq!(leaf_to_corresponding_tree(1, 0b0011), Some(1));
|
||||
assert_eq!(leaf_to_corresponding_tree(1, 0b1011), Some(3));
|
||||
|
||||
// position two starts as its own root, and then it is merged with the left-most tree
|
||||
assert_eq!(leaf_to_corresponding_tree(2, 0b0011), Some(0));
|
||||
assert_eq!(leaf_to_corresponding_tree(2, 0b0100), Some(2));
|
||||
assert_eq!(leaf_to_corresponding_tree(2, 0b1011), Some(3));
|
||||
|
||||
// position tree is merged on the left-most tree
|
||||
assert_eq!(leaf_to_corresponding_tree(3, 0b0011), None);
|
||||
assert_eq!(leaf_to_corresponding_tree(3, 0b0100), Some(2));
|
||||
assert_eq!(leaf_to_corresponding_tree(3, 0b1011), Some(3));
|
||||
|
||||
assert_eq!(leaf_to_corresponding_tree(4, 0b0101), Some(0));
|
||||
assert_eq!(leaf_to_corresponding_tree(4, 0b0110), Some(1));
|
||||
assert_eq!(leaf_to_corresponding_tree(4, 0b0111), Some(1));
|
||||
assert_eq!(leaf_to_corresponding_tree(4, 0b1000), Some(3));
|
||||
|
||||
assert_eq!(leaf_to_corresponding_tree(12, 0b01101), Some(0));
|
||||
assert_eq!(leaf_to_corresponding_tree(12, 0b01110), Some(1));
|
||||
assert_eq!(leaf_to_corresponding_tree(12, 0b01111), Some(1));
|
||||
assert_eq!(leaf_to_corresponding_tree(12, 0b10000), Some(4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_high_bitmask() {
|
||||
assert_eq!(high_bitmask(0), usize::MAX);
|
||||
assert_eq!(high_bitmask(1), usize::MAX << 1);
|
||||
assert_eq!(high_bitmask(usize::BITS - 2), 0b11usize.rotate_right(2));
|
||||
assert_eq!(high_bitmask(usize::BITS - 1), 0b1usize.rotate_right(1));
|
||||
assert_eq!(high_bitmask(usize::BITS), 0, "overflow should be handled");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nodes_in_forest() {
|
||||
assert_eq!(nodes_in_forest(0b0000), 0);
|
||||
assert_eq!(nodes_in_forest(0b0001), 1);
|
||||
assert_eq!(nodes_in_forest(0b0010), 3);
|
||||
assert_eq!(nodes_in_forest(0b0011), 4);
|
||||
assert_eq!(nodes_in_forest(0b0100), 7);
|
||||
assert_eq!(nodes_in_forest(0b0101), 8);
|
||||
assert_eq!(nodes_in_forest(0b0110), 10);
|
||||
assert_eq!(nodes_in_forest(0b0111), 11);
|
||||
assert_eq!(nodes_in_forest(0b1000), 15);
|
||||
assert_eq!(nodes_in_forest(0b1001), 16);
|
||||
assert_eq!(nodes_in_forest(0b1010), 18);
|
||||
assert_eq!(nodes_in_forest(0b1011), 19);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nodes_in_forest_single_bit() {
|
||||
assert_eq!(nodes_in_forest(2usize.pow(0)), 2usize.pow(1) - 1);
|
||||
assert_eq!(nodes_in_forest(2usize.pow(1)), 2usize.pow(2) - 1);
|
||||
assert_eq!(nodes_in_forest(2usize.pow(2)), 2usize.pow(3) - 1);
|
||||
assert_eq!(nodes_in_forest(2usize.pow(3)), 2usize.pow(4) - 1);
|
||||
|
||||
for bit in 0..(usize::BITS - 1) {
|
||||
let size = 2usize.pow(bit + 1) - 1;
|
||||
assert_eq!(nodes_in_forest(1usize << bit), size);
|
||||
}
|
||||
}
|
||||
|
||||
const LEAVES: [Word; 7] = [
|
||||
int_to_node(0),
|
||||
int_to_node(1),
|
||||
int_to_node(2),
|
||||
int_to_node(3),
|
||||
int_to_node(4),
|
||||
int_to_node(5),
|
||||
int_to_node(6),
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_mmr_simple() {
|
||||
let mut postorder = Vec::new();
|
||||
postorder.push(LEAVES[0]);
|
||||
postorder.push(LEAVES[1]);
|
||||
postorder.push(*Rpo256::hash_elements(&[LEAVES[0], LEAVES[1]].concat()));
|
||||
postorder.push(LEAVES[2]);
|
||||
postorder.push(LEAVES[3]);
|
||||
postorder.push(*Rpo256::hash_elements(&[LEAVES[2], LEAVES[3]].concat()));
|
||||
postorder.push(*Rpo256::hash_elements(
|
||||
&[postorder[2], postorder[5]].concat(),
|
||||
));
|
||||
postorder.push(LEAVES[4]);
|
||||
postorder.push(LEAVES[5]);
|
||||
postorder.push(*Rpo256::hash_elements(&[LEAVES[4], LEAVES[5]].concat()));
|
||||
postorder.push(LEAVES[6]);
|
||||
|
||||
let mut mmr = Mmr::new();
|
||||
assert_eq!(mmr.forest(), 0);
|
||||
assert_eq!(mmr.nodes.len(), 0);
|
||||
|
||||
mmr.add(LEAVES[0]);
|
||||
assert_eq!(mmr.forest(), 1);
|
||||
assert_eq!(mmr.nodes.len(), 1);
|
||||
assert_eq!(mmr.nodes.as_slice(), &postorder[0..mmr.nodes.len()]);
|
||||
|
||||
let acc = mmr.accumulator();
|
||||
assert_eq!(acc.num_leaves, 1);
|
||||
assert_eq!(acc.peaks, &[postorder[0]]);
|
||||
|
||||
mmr.add(LEAVES[1]);
|
||||
assert_eq!(mmr.forest(), 2);
|
||||
assert_eq!(mmr.nodes.len(), 3);
|
||||
assert_eq!(mmr.nodes.as_slice(), &postorder[0..mmr.nodes.len()]);
|
||||
|
||||
let acc = mmr.accumulator();
|
||||
assert_eq!(acc.num_leaves, 2);
|
||||
assert_eq!(acc.peaks, &[postorder[2]]);
|
||||
|
||||
mmr.add(LEAVES[2]);
|
||||
assert_eq!(mmr.forest(), 3);
|
||||
assert_eq!(mmr.nodes.len(), 4);
|
||||
assert_eq!(mmr.nodes.as_slice(), &postorder[0..mmr.nodes.len()]);
|
||||
|
||||
let acc = mmr.accumulator();
|
||||
assert_eq!(acc.num_leaves, 3);
|
||||
assert_eq!(acc.peaks, &[postorder[2], postorder[3]]);
|
||||
|
||||
mmr.add(LEAVES[3]);
|
||||
assert_eq!(mmr.forest(), 4);
|
||||
assert_eq!(mmr.nodes.len(), 7);
|
||||
assert_eq!(mmr.nodes.as_slice(), &postorder[0..mmr.nodes.len()]);
|
||||
|
||||
let acc = mmr.accumulator();
|
||||
assert_eq!(acc.num_leaves, 4);
|
||||
assert_eq!(acc.peaks, &[postorder[6]]);
|
||||
|
||||
mmr.add(LEAVES[4]);
|
||||
assert_eq!(mmr.forest(), 5);
|
||||
assert_eq!(mmr.nodes.len(), 8);
|
||||
assert_eq!(mmr.nodes.as_slice(), &postorder[0..mmr.nodes.len()]);
|
||||
|
||||
let acc = mmr.accumulator();
|
||||
assert_eq!(acc.num_leaves, 5);
|
||||
assert_eq!(acc.peaks, &[postorder[6], postorder[7]]);
|
||||
|
||||
mmr.add(LEAVES[5]);
|
||||
assert_eq!(mmr.forest(), 6);
|
||||
assert_eq!(mmr.nodes.len(), 10);
|
||||
assert_eq!(mmr.nodes.as_slice(), &postorder[0..mmr.nodes.len()]);
|
||||
|
||||
let acc = mmr.accumulator();
|
||||
assert_eq!(acc.num_leaves, 6);
|
||||
assert_eq!(acc.peaks, &[postorder[6], postorder[9]]);
|
||||
|
||||
mmr.add(LEAVES[6]);
|
||||
assert_eq!(mmr.forest(), 7);
|
||||
assert_eq!(mmr.nodes.len(), 11);
|
||||
assert_eq!(mmr.nodes.as_slice(), &postorder[0..mmr.nodes.len()]);
|
||||
|
||||
let acc = mmr.accumulator();
|
||||
assert_eq!(acc.num_leaves, 7);
|
||||
assert_eq!(acc.peaks, &[postorder[6], postorder[9], postorder[10]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mmr_open() {
|
||||
let mmr: Mmr = LEAVES.into();
|
||||
let h01: Word = Rpo256::hash_elements(&LEAVES[0..2].concat()).into();
|
||||
let h23: Word = Rpo256::hash_elements(&LEAVES[2..4].concat()).into();
|
||||
|
||||
// node at pos 7 is the root
|
||||
assert!(
|
||||
mmr.open(7).is_err(),
|
||||
"Element 7 is not in the tree, result should be None"
|
||||
);
|
||||
|
||||
// node at pos 6 is the root
|
||||
let empty: MerklePath = MerklePath::new(vec![]);
|
||||
let opening = mmr
|
||||
.open(6)
|
||||
.expect("Element 6 is contained in the tree, expected an opening result.");
|
||||
assert_eq!(opening.merkle_path, empty);
|
||||
assert_eq!(opening.forest, mmr.forest);
|
||||
assert_eq!(opening.position, 6);
|
||||
assert!(
|
||||
mmr.accumulator().verify(LEAVES[6], opening),
|
||||
"MmrProof should be valid for the current accumulator."
|
||||
);
|
||||
|
||||
// nodes 4,5 are detph 1
|
||||
let root_to_path = MerklePath::new(vec![LEAVES[4]]);
|
||||
let opening = mmr
|
||||
.open(5)
|
||||
.expect("Element 5 is contained in the tree, expected an opening result.");
|
||||
assert_eq!(opening.merkle_path, root_to_path);
|
||||
assert_eq!(opening.forest, mmr.forest);
|
||||
assert_eq!(opening.position, 5);
|
||||
assert!(
|
||||
mmr.accumulator().verify(LEAVES[5], opening),
|
||||
"MmrProof should be valid for the current accumulator."
|
||||
);
|
||||
|
||||
let root_to_path = MerklePath::new(vec![LEAVES[5]]);
|
||||
let opening = mmr
|
||||
.open(4)
|
||||
.expect("Element 4 is contained in the tree, expected an opening result.");
|
||||
assert_eq!(opening.merkle_path, root_to_path);
|
||||
assert_eq!(opening.forest, mmr.forest);
|
||||
assert_eq!(opening.position, 4);
|
||||
assert!(
|
||||
mmr.accumulator().verify(LEAVES[4], opening),
|
||||
"MmrProof should be valid for the current accumulator."
|
||||
);
|
||||
|
||||
// nodes 0,1,2,3 are detph 2
|
||||
let root_to_path = MerklePath::new(vec![LEAVES[2], h01]);
|
||||
let opening = mmr
|
||||
.open(3)
|
||||
.expect("Element 3 is contained in the tree, expected an opening result.");
|
||||
assert_eq!(opening.merkle_path, root_to_path);
|
||||
assert_eq!(opening.forest, mmr.forest);
|
||||
assert_eq!(opening.position, 3);
|
||||
assert!(
|
||||
mmr.accumulator().verify(LEAVES[3], opening),
|
||||
"MmrProof should be valid for the current accumulator."
|
||||
);
|
||||
|
||||
let root_to_path = MerklePath::new(vec![LEAVES[3], h01]);
|
||||
let opening = mmr
|
||||
.open(2)
|
||||
.expect("Element 2 is contained in the tree, expected an opening result.");
|
||||
assert_eq!(opening.merkle_path, root_to_path);
|
||||
assert_eq!(opening.forest, mmr.forest);
|
||||
assert_eq!(opening.position, 2);
|
||||
assert!(
|
||||
mmr.accumulator().verify(LEAVES[2], opening),
|
||||
"MmrProof should be valid for the current accumulator."
|
||||
);
|
||||
|
||||
let root_to_path = MerklePath::new(vec![LEAVES[0], h23]);
|
||||
let opening = mmr
|
||||
.open(1)
|
||||
.expect("Element 1 is contained in the tree, expected an opening result.");
|
||||
assert_eq!(opening.merkle_path, root_to_path);
|
||||
assert_eq!(opening.forest, mmr.forest);
|
||||
assert_eq!(opening.position, 1);
|
||||
assert!(
|
||||
mmr.accumulator().verify(LEAVES[1], opening),
|
||||
"MmrProof should be valid for the current accumulator."
|
||||
);
|
||||
|
||||
let root_to_path = MerklePath::new(vec![LEAVES[1], h23]);
|
||||
let opening = mmr
|
||||
.open(0)
|
||||
.expect("Element 0 is contained in the tree, expected an opening result.");
|
||||
assert_eq!(opening.merkle_path, root_to_path);
|
||||
assert_eq!(opening.forest, mmr.forest);
|
||||
assert_eq!(opening.position, 0);
|
||||
assert!(
|
||||
mmr.accumulator().verify(LEAVES[0], opening),
|
||||
"MmrProof should be valid for the current accumulator."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mmr_get() {
|
||||
let mmr: Mmr = LEAVES.into();
|
||||
assert_eq!(
|
||||
mmr.get(0).unwrap(),
|
||||
LEAVES[0],
|
||||
"value at pos 0 must correspond"
|
||||
);
|
||||
assert_eq!(
|
||||
mmr.get(1).unwrap(),
|
||||
LEAVES[1],
|
||||
"value at pos 1 must correspond"
|
||||
);
|
||||
assert_eq!(
|
||||
mmr.get(2).unwrap(),
|
||||
LEAVES[2],
|
||||
"value at pos 2 must correspond"
|
||||
);
|
||||
assert_eq!(
|
||||
mmr.get(3).unwrap(),
|
||||
LEAVES[3],
|
||||
"value at pos 3 must correspond"
|
||||
);
|
||||
assert_eq!(
|
||||
mmr.get(4).unwrap(),
|
||||
LEAVES[4],
|
||||
"value at pos 4 must correspond"
|
||||
);
|
||||
assert_eq!(
|
||||
mmr.get(5).unwrap(),
|
||||
LEAVES[5],
|
||||
"value at pos 5 must correspond"
|
||||
);
|
||||
assert_eq!(
|
||||
mmr.get(6).unwrap(),
|
||||
LEAVES[6],
|
||||
"value at pos 6 must correspond"
|
||||
);
|
||||
assert!(mmr.get(7).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mmr_invariants() {
|
||||
let mut mmr = Mmr::new();
|
||||
for v in 1..=1028 {
|
||||
mmr.add(int_to_node(v));
|
||||
let accumulator = mmr.accumulator();
|
||||
assert_eq!(
|
||||
v as usize,
|
||||
mmr.forest(),
|
||||
"MMR leaf count must increase by one on every add"
|
||||
);
|
||||
assert_eq!(
|
||||
v as usize, accumulator.num_leaves,
|
||||
"MMR and its accumulator must match leaves count"
|
||||
);
|
||||
assert_eq!(
|
||||
accumulator.num_leaves.count_ones() as usize,
|
||||
accumulator.peaks.len(),
|
||||
"bits on leaves must match the number of peaks"
|
||||
);
|
||||
|
||||
let expected_nodes: usize = TrueBitPositionIterator::new(mmr.forest())
|
||||
.map(|bit_pos| nodes_in_forest(1 << bit_pos))
|
||||
.sum();
|
||||
|
||||
assert_eq!(
|
||||
expected_nodes,
|
||||
mmr.nodes.len(),
|
||||
"the sum of every tree size must be equal to the number of nodes in the MMR (forest: {:b})",
|
||||
mmr.forest(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bit_position_iterator() {
|
||||
assert_eq!(TrueBitPositionIterator::new(0).count(), 0);
|
||||
assert_eq!(TrueBitPositionIterator::new(0).rev().count(), 0);
|
||||
|
||||
assert_eq!(
|
||||
TrueBitPositionIterator::new(1).collect::<Vec<u32>>(),
|
||||
vec![0]
|
||||
);
|
||||
assert_eq!(
|
||||
TrueBitPositionIterator::new(1).rev().collect::<Vec<u32>>(),
|
||||
vec![0],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
TrueBitPositionIterator::new(2).collect::<Vec<u32>>(),
|
||||
vec![1]
|
||||
);
|
||||
assert_eq!(
|
||||
TrueBitPositionIterator::new(2).rev().collect::<Vec<u32>>(),
|
||||
vec![1],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
TrueBitPositionIterator::new(3).collect::<Vec<u32>>(),
|
||||
vec![0, 1],
|
||||
);
|
||||
assert_eq!(
|
||||
TrueBitPositionIterator::new(3).rev().collect::<Vec<u32>>(),
|
||||
vec![1, 0],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
TrueBitPositionIterator::new(0b11010101).collect::<Vec<u32>>(),
|
||||
vec![0, 2, 4, 6, 7],
|
||||
);
|
||||
assert_eq!(
|
||||
TrueBitPositionIterator::new(0b11010101)
|
||||
.rev()
|
||||
.collect::<Vec<u32>>(),
|
||||
vec![7, 6, 4, 2, 0],
|
||||
);
|
||||
}
|
||||
|
||||
mod property_tests {
|
||||
use super::leaf_to_corresponding_tree;
|
||||
use proptest::prelude::*;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_last_position_is_always_contained_in_the_last_tree(leaves in any::<usize>().prop_filter("cant have an empty tree", |v| *v != 0)) {
|
||||
let last_pos = leaves - 1;
|
||||
let lowest_bit = leaves.trailing_zeros();
|
||||
|
||||
assert_eq!(
|
||||
leaf_to_corresponding_tree(last_pos, leaves),
|
||||
Some(lowest_bit),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_contained_tree_is_always_power_of_two((leaves, pos) in any::<usize>().prop_flat_map(|v| (Just(v), 0..v))) {
|
||||
let tree = leaf_to_corresponding_tree(pos, leaves).expect("pos is smaller than leaves, there should always be a corresponding tree");
|
||||
let mask = 1usize << tree;
|
||||
|
||||
assert!(tree < usize::BITS, "the result must be a bit in usize");
|
||||
assert!(mask & leaves != 0, "the result should be a tree in leaves");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +1,24 @@
|
|||
use super::{
|
||||
hash::rpo::{Rpo256, RpoDigest},
|
||||
utils::collections::{vec, BTreeMap, Vec},
|
||||
Felt, StarkField, Word, ZERO,
|
||||
utils::collections::{vec, BTreeMap, BTreeSet, Vec},
|
||||
Felt, StarkField, Word, WORD_SIZE, ZERO,
|
||||
};
|
||||
use core::fmt;
|
||||
|
||||
// REEXPORTS
|
||||
// ================================================================================================
|
||||
|
||||
mod empty_roots;
|
||||
pub use empty_roots::EmptySubtreeRoots;
|
||||
|
||||
mod index;
|
||||
pub use index::NodeIndex;
|
||||
|
||||
mod merkle_tree;
|
||||
pub use merkle_tree::MerkleTree;
|
||||
pub use merkle_tree::{path_to_text, tree_to_text, MerkleTree};
|
||||
|
||||
mod path;
|
||||
pub use path::MerklePath;
|
||||
pub use path::{MerklePath, RootPath, ValuePath};
|
||||
|
||||
mod path_set;
|
||||
pub use path_set::MerklePathSet;
|
||||
|
@ -20,25 +26,35 @@ pub use path_set::MerklePathSet;
|
|||
mod simple_smt;
|
||||
pub use simple_smt::SimpleSmt;
|
||||
|
||||
mod mmr;
|
||||
pub use mmr::{Mmr, MmrPeaks};
|
||||
|
||||
mod store;
|
||||
pub use store::MerkleStore;
|
||||
|
||||
// ERRORS
|
||||
// ================================================================================================
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MerkleError {
|
||||
ConflictingRoots(Vec<Word>),
|
||||
DepthTooSmall(u8),
|
||||
DepthTooBig(u64),
|
||||
NodeNotInStore(Word, NodeIndex),
|
||||
NumLeavesNotPowerOfTwo(usize),
|
||||
InvalidIndex(NodeIndex),
|
||||
InvalidDepth { expected: u8, provided: u8 },
|
||||
InvalidPath(MerklePath),
|
||||
InvalidEntriesCount(usize, usize),
|
||||
NodeNotInSet(u64),
|
||||
RootNotInStore(Word),
|
||||
}
|
||||
|
||||
impl fmt::Display for MerkleError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use MerkleError::*;
|
||||
match self {
|
||||
ConflictingRoots(roots) => write!(f, "the merkle paths roots do not match {roots:?}"),
|
||||
DepthTooSmall(depth) => write!(f, "the provided depth {depth} is too small"),
|
||||
DepthTooBig(depth) => write!(f, "the provided depth {depth} is too big"),
|
||||
NumLeavesNotPowerOfTwo(leaves) => {
|
||||
|
@ -55,6 +71,8 @@ impl fmt::Display for MerkleError {
|
|||
InvalidPath(_path) => write!(f, "the provided path is not valid"),
|
||||
InvalidEntriesCount(max, provided) => write!(f, "the provided number of entries is {provided}, but the maximum for the given depth is {max}"),
|
||||
NodeNotInSet(index) => write!(f, "the node indexed by {index} is not in the set"),
|
||||
NodeNotInStore(hash, index) => write!(f, "the node {:?} indexed by {} and depth {} is not in the store", hash, index.value(), index.depth(),),
|
||||
RootNotInStore(root) => write!(f, "the root {:?} is not in the store", root),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,3 +82,27 @@ impl IntoIterator for MerklePath {
|
|||
self.nodes.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
// MERKLE PATH CONTAINERS
|
||||
// ================================================================================================
|
||||
|
||||
/// A container for a [Word] value and its [MerklePath] opening.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct ValuePath {
|
||||
/// The node value opening for `path`.
|
||||
pub value: Word,
|
||||
/// The path from `value` to `root` (exclusive).
|
||||
pub path: MerklePath,
|
||||
}
|
||||
|
||||
/// A container for a [MerklePath] and its [Word] root.
|
||||
///
|
||||
/// This structure does not provide any guarantees regarding the correctness of the path to the
|
||||
/// root. For more information, check [MerklePath::verify].
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct RootPath {
|
||||
/// The node value opening for `path`.
|
||||
pub root: Word,
|
||||
/// The path from `value` to `root` (exclusive).
|
||||
pub path: MerklePath,
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::{BTreeMap, MerkleError, MerklePath, NodeIndex, Rpo256, Vec, Word, ZERO};
|
||||
use super::{BTreeMap, MerkleError, MerklePath, NodeIndex, Rpo256, ValuePath, Vec, Word, ZERO};
|
||||
|
||||
// MERKLE PATH SET
|
||||
// ================================================================================================
|
||||
|
@ -76,15 +76,23 @@ impl MerklePathSet {
|
|||
});
|
||||
}
|
||||
|
||||
let index_value = index.to_scalar_index();
|
||||
let parity = index_value & 1;
|
||||
let index_value = index_value / 2;
|
||||
let parity = index.value() & 1;
|
||||
let path_key = index.value() - parity;
|
||||
self.paths
|
||||
.get(&index_value)
|
||||
.ok_or(MerkleError::NodeNotInSet(index_value))
|
||||
.get(&path_key)
|
||||
.ok_or(MerkleError::NodeNotInSet(path_key))
|
||||
.map(|path| path[parity as usize])
|
||||
}
|
||||
|
||||
/// Returns a leaf at the specified index.
|
||||
///
|
||||
/// # Errors
|
||||
/// * The specified index is not valid for the depth of the structure.
|
||||
/// * Leaf with the requested path does not exist in the set.
|
||||
pub fn get_leaf(&self, index: u64) -> Result<Word, MerkleError> {
|
||||
self.get_node(NodeIndex::new(self.depth(), index))
|
||||
}
|
||||
|
||||
/// Returns a Merkle path to the node at the specified index. The node itself is
|
||||
/// not included in the path.
|
||||
///
|
||||
|
@ -103,18 +111,42 @@ impl MerklePathSet {
|
|||
});
|
||||
}
|
||||
|
||||
let index_value = index.to_scalar_index();
|
||||
let index = index_value / 2;
|
||||
let parity = index_value & 1;
|
||||
let parity = index.value() & 1;
|
||||
let path_key = index.value() - parity;
|
||||
let mut path = self
|
||||
.paths
|
||||
.get(&index)
|
||||
.get(&path_key)
|
||||
.cloned()
|
||||
.ok_or(MerkleError::NodeNotInSet(index))?;
|
||||
.ok_or(MerkleError::NodeNotInSet(index.value()))?;
|
||||
path.remove(parity as usize);
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Returns all paths in this path set together with their indexes.
|
||||
pub fn to_paths(&self) -> Vec<(u64, ValuePath)> {
|
||||
let mut result = Vec::with_capacity(self.paths.len() * 2);
|
||||
|
||||
for (&index, path) in self.paths.iter() {
|
||||
// push path for the even index into the result
|
||||
let path1 = ValuePath {
|
||||
value: path[0],
|
||||
path: MerklePath::new(path[1..].to_vec()),
|
||||
};
|
||||
result.push((index, path1));
|
||||
|
||||
// push path for the odd index into the result
|
||||
let mut path2 = path.clone();
|
||||
let leaf2 = path2.remove(1);
|
||||
let path2 = ValuePath {
|
||||
value: leaf2,
|
||||
path: path2,
|
||||
};
|
||||
result.push((index + 1, path2));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
// STATE MUTATORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
|
@ -133,7 +165,7 @@ impl MerklePathSet {
|
|||
value: Word,
|
||||
mut path: MerklePath,
|
||||
) -> Result<(), MerkleError> {
|
||||
let depth = (path.len() + 1) as u8;
|
||||
let depth = path.len() as u8;
|
||||
let mut index = NodeIndex::new(depth, index_value);
|
||||
if index.depth() != self.total_depth {
|
||||
return Err(MerkleError::InvalidDepth {
|
||||
|
@ -143,8 +175,6 @@ impl MerklePathSet {
|
|||
}
|
||||
|
||||
// update the current path
|
||||
let index_value = index.to_scalar_index();
|
||||
let upper_index_value = index_value / 2;
|
||||
let parity = index_value & 1;
|
||||
path.insert(parity as usize, value);
|
||||
|
||||
|
@ -164,7 +194,8 @@ impl MerklePathSet {
|
|||
}
|
||||
|
||||
// finish updating the path
|
||||
self.paths.insert(upper_index_value, path);
|
||||
let path_key = index_value - parity;
|
||||
self.paths.insert(path_key, path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -180,10 +211,9 @@ impl MerklePathSet {
|
|||
return Err(MerkleError::InvalidIndex(index));
|
||||
}
|
||||
|
||||
let path = match self
|
||||
.paths
|
||||
.get_mut(&index.clone().move_up().to_scalar_index())
|
||||
{
|
||||
let parity = index.value() & 1;
|
||||
let path_key = index.value() - parity;
|
||||
let path = match self.paths.get_mut(&path_key) {
|
||||
Some(path) => path,
|
||||
None => return Err(MerkleError::NodeNotInSet(base_index_value)),
|
||||
};
|
||||
|
@ -247,7 +277,7 @@ mod tests {
|
|||
|
||||
let root_exp = calculate_parent_hash(parent0, 0, parent1);
|
||||
|
||||
let set = super::MerklePathSet::new(3)
|
||||
let set = super::MerklePathSet::new(2)
|
||||
.with_paths([(0, leaf0, vec![leaf1, parent1].into())])
|
||||
.unwrap();
|
||||
|
||||
|
@ -259,7 +289,7 @@ mod tests {
|
|||
let path_6 = vec![int_to_node(7), int_to_node(45), int_to_node(123)];
|
||||
let hash_6 = int_to_node(6);
|
||||
let index = 6_u64;
|
||||
let depth = 4_u8;
|
||||
let depth = 3_u8;
|
||||
let set = super::MerklePathSet::new(depth)
|
||||
.with_paths([(index, hash_6, path_6.clone().into())])
|
||||
.unwrap();
|
||||
|
@ -274,7 +304,7 @@ mod tests {
|
|||
let path_6 = vec![int_to_node(7), int_to_node(45), int_to_node(123)];
|
||||
let hash_6 = int_to_node(6);
|
||||
let index = 6_u64;
|
||||
let depth = 4_u8;
|
||||
let depth = 3_u8;
|
||||
let set = MerklePathSet::new(depth)
|
||||
.with_paths([(index, hash_6, path_6.into())])
|
||||
.unwrap();
|
||||
|
@ -304,7 +334,7 @@ mod tests {
|
|||
let index_6 = 6_u64;
|
||||
let index_5 = 5_u64;
|
||||
let index_4 = 4_u64;
|
||||
let depth = 4_u8;
|
||||
let depth = 3_u8;
|
||||
let mut set = MerklePathSet::new(depth)
|
||||
.with_paths([
|
||||
(index_6, hash_6, path_6.into()),
|
||||
|
@ -329,6 +359,58 @@ mod tests {
|
|||
assert_eq!(new_hash_5, new_path_4[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn depth_3_is_correct() {
|
||||
let a = int_to_node(1);
|
||||
let b = int_to_node(2);
|
||||
let c = int_to_node(3);
|
||||
let d = int_to_node(4);
|
||||
let e = int_to_node(5);
|
||||
let f = int_to_node(6);
|
||||
let g = int_to_node(7);
|
||||
let h = int_to_node(8);
|
||||
|
||||
let i = Rpo256::merge(&[a.into(), b.into()]);
|
||||
let j = Rpo256::merge(&[c.into(), d.into()]);
|
||||
let k = Rpo256::merge(&[e.into(), f.into()]);
|
||||
let l = Rpo256::merge(&[g.into(), h.into()]);
|
||||
|
||||
let m = Rpo256::merge(&[i.into(), j.into()]);
|
||||
let n = Rpo256::merge(&[k.into(), l.into()]);
|
||||
|
||||
let root = Rpo256::merge(&[m.into(), n.into()]);
|
||||
|
||||
let mut set = MerklePathSet::new(3);
|
||||
|
||||
let value = b;
|
||||
let index = 1;
|
||||
let path = MerklePath::new([a.into(), j.into(), n.into()].to_vec());
|
||||
set.add_path(index, value, path.clone()).unwrap();
|
||||
assert_eq!(value, set.get_leaf(index).unwrap());
|
||||
assert_eq!(Word::from(root), set.root());
|
||||
|
||||
let value = e;
|
||||
let index = 4;
|
||||
let path = MerklePath::new([f.into(), l.into(), m.into()].to_vec());
|
||||
set.add_path(index, value, path.clone()).unwrap();
|
||||
assert_eq!(value, set.get_leaf(index).unwrap());
|
||||
assert_eq!(Word::from(root), set.root());
|
||||
|
||||
let value = a;
|
||||
let index = 0;
|
||||
let path = MerklePath::new([b.into(), j.into(), n.into()].to_vec());
|
||||
set.add_path(index, value, path.clone()).unwrap();
|
||||
assert_eq!(value, set.get_leaf(index).unwrap());
|
||||
assert_eq!(Word::from(root), set.root());
|
||||
|
||||
let value = h;
|
||||
let index = 7;
|
||||
let path = MerklePath::new([g.into(), k.into(), m.into()].to_vec());
|
||||
set.add_path(index, value, path.clone()).unwrap();
|
||||
assert_eq!(value, set.get_leaf(index).unwrap());
|
||||
assert_eq!(Word::from(root), set.root());
|
||||
}
|
||||
|
||||
// HELPER FUNCTIONS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use super::{BTreeMap, MerkleError, MerklePath, NodeIndex, Rpo256, RpoDigest, Vec, Word};
|
||||
use super::{
|
||||
BTreeMap, EmptySubtreeRoots, MerkleError, MerklePath, NodeIndex, Rpo256, RpoDigest, Vec, Word,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
@ -13,7 +15,7 @@ mod tests;
|
|||
pub struct SimpleSmt {
|
||||
root: Word,
|
||||
depth: u8,
|
||||
store: Store,
|
||||
pub(crate) store: Store,
|
||||
}
|
||||
|
||||
impl SimpleSmt {
|
||||
|
@ -29,38 +31,55 @@ impl SimpleSmt {
|
|||
// CONSTRUCTORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Creates a new simple SMT.
|
||||
///
|
||||
/// The provided entries will be tuples of the leaves and their corresponding keys.
|
||||
/// Creates a new simple SMT with the provided depth.
|
||||
pub fn new(depth: u8) -> Result<Self, MerkleError> {
|
||||
// validate the range of the depth.
|
||||
if depth < Self::MIN_DEPTH {
|
||||
return Err(MerkleError::DepthTooSmall(depth));
|
||||
} else if Self::MAX_DEPTH < depth {
|
||||
return Err(MerkleError::DepthTooBig(depth as u64));
|
||||
}
|
||||
|
||||
let (store, root) = Store::new(depth);
|
||||
Ok(Self { root, depth, store })
|
||||
}
|
||||
|
||||
/// Appends the provided entries as leaves of the tree.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The function will fail if the provided entries count exceed the maximum tree capacity, that
|
||||
/// is `2^{depth}`.
|
||||
pub fn new<R, I>(entries: R, depth: u8) -> Result<Self, MerkleError>
|
||||
pub fn with_leaves<R, I>(mut self, entries: R) -> Result<Self, MerkleError>
|
||||
where
|
||||
R: IntoIterator<IntoIter = I>,
|
||||
I: Iterator<Item = (u64, Word)> + ExactSizeIterator,
|
||||
{
|
||||
// check if the leaves count will fit the depth setup
|
||||
let mut entries = entries.into_iter();
|
||||
|
||||
// validate the range of the depth.
|
||||
let max = 1 << depth;
|
||||
if depth < Self::MIN_DEPTH {
|
||||
return Err(MerkleError::DepthTooSmall(depth));
|
||||
} else if Self::MAX_DEPTH < depth {
|
||||
return Err(MerkleError::DepthTooBig(depth as u64));
|
||||
} else if entries.len() > max {
|
||||
let max = 1 << self.depth;
|
||||
if entries.len() > max {
|
||||
return Err(MerkleError::InvalidEntriesCount(max, entries.len()));
|
||||
}
|
||||
|
||||
let (store, root) = Store::new(depth);
|
||||
let mut tree = Self { root, depth, store };
|
||||
entries.try_for_each(|(key, leaf)| tree.insert_leaf(key, leaf))?;
|
||||
|
||||
Ok(tree)
|
||||
// append leaves and return
|
||||
entries.try_for_each(|(key, leaf)| self.insert_leaf(key, leaf))?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Replaces the internal empty digests used when a given depth doesn't contain a node.
|
||||
pub fn with_empty_subtrees<I>(mut self, hashes: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = RpoDigest>,
|
||||
{
|
||||
self.store
|
||||
.replace_empty_subtrees(hashes.into_iter().collect());
|
||||
self
|
||||
}
|
||||
|
||||
// PUBLIC ACCESSORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Returns the root of this Merkle tree.
|
||||
pub const fn root(&self) -> Word {
|
||||
self.root
|
||||
|
@ -71,6 +90,9 @@ impl SimpleSmt {
|
|||
self.depth
|
||||
}
|
||||
|
||||
// PROVIDERS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Returns the set count of the keys of the leaves.
|
||||
pub fn leaves_count(&self) -> usize {
|
||||
self.store.leaves_count()
|
||||
|
@ -81,16 +103,24 @@ impl SimpleSmt {
|
|||
/// # Errors
|
||||
/// Returns an error if:
|
||||
/// * The specified depth is greater than the depth of the tree.
|
||||
/// * The specified key does not exist
|
||||
pub fn get_node(&self, index: &NodeIndex) -> Result<Word, MerkleError> {
|
||||
if index.is_root() {
|
||||
Err(MerkleError::DepthTooSmall(index.depth()))
|
||||
} else if index.depth() > self.depth() {
|
||||
Err(MerkleError::DepthTooBig(index.depth() as u64))
|
||||
} else if index.depth() == self.depth() {
|
||||
self.store.get_leaf_node(index.value())
|
||||
self.store
|
||||
.get_leaf_node(index.value())
|
||||
.or_else(|| {
|
||||
self.store
|
||||
.empty_hashes
|
||||
.get(index.depth() as usize)
|
||||
.copied()
|
||||
.map(Word::from)
|
||||
})
|
||||
.ok_or(MerkleError::InvalidIndex(*index))
|
||||
} else {
|
||||
let branch_node = self.store.get_branch_node(index)?;
|
||||
let branch_node = self.store.get_branch_node(index);
|
||||
Ok(Rpo256::merge(&[branch_node.left, branch_node.right]).into())
|
||||
}
|
||||
}
|
||||
|
@ -100,23 +130,19 @@ impl SimpleSmt {
|
|||
///
|
||||
/// # Errors
|
||||
/// Returns an error if:
|
||||
/// * The specified key does not exist as a branch or leaf node
|
||||
/// * The specified depth is greater than the depth of the tree.
|
||||
pub fn get_path(&self, mut index: NodeIndex) -> Result<MerklePath, MerkleError> {
|
||||
if index.is_root() {
|
||||
return Err(MerkleError::DepthTooSmall(index.depth()));
|
||||
} else if index.depth() > self.depth() {
|
||||
return Err(MerkleError::DepthTooBig(index.depth() as u64));
|
||||
} else if index.depth() == self.depth() && !self.store.check_leaf_node_exists(index.value())
|
||||
{
|
||||
return Err(MerkleError::InvalidIndex(index.with_depth(self.depth())));
|
||||
}
|
||||
|
||||
let mut path = Vec::with_capacity(index.depth() as usize);
|
||||
for _ in 0..index.depth() {
|
||||
let is_right = index.is_value_odd();
|
||||
index.move_up();
|
||||
let BranchNode { left, right } = self.store.get_branch_node(&index)?;
|
||||
let BranchNode { left, right } = self.store.get_branch_node(&index);
|
||||
let value = if is_right { left } else { right };
|
||||
path.push(*value);
|
||||
}
|
||||
|
@ -133,6 +159,9 @@ impl SimpleSmt {
|
|||
self.get_path(NodeIndex::new(self.depth(), key))
|
||||
}
|
||||
|
||||
// STATE MUTATORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Replaces the leaf located at the specified key, and recomputes hashes by walking up the tree
|
||||
///
|
||||
/// # Errors
|
||||
|
@ -156,10 +185,7 @@ impl SimpleSmt {
|
|||
for _ in 0..index.depth() {
|
||||
let is_right = index.is_value_odd();
|
||||
index.move_up();
|
||||
let BranchNode { left, right } = self
|
||||
.store
|
||||
.get_branch_node(&index)
|
||||
.unwrap_or_else(|_| self.store.get_empty_node(index.depth() as usize + 1));
|
||||
let BranchNode { left, right } = self.store.get_branch_node(&index);
|
||||
let (left, right) = if is_right {
|
||||
(left, value)
|
||||
} else {
|
||||
|
@ -181,17 +207,17 @@ impl SimpleSmt {
|
|||
/// 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(Debug, Clone, PartialEq, Eq)]
|
||||
struct Store {
|
||||
branches: BTreeMap<NodeIndex, BranchNode>,
|
||||
pub(crate) struct Store {
|
||||
pub(crate) branches: BTreeMap<NodeIndex, BranchNode>,
|
||||
leaves: BTreeMap<u64, Word>,
|
||||
empty_hashes: Vec<RpoDigest>,
|
||||
pub(crate) empty_hashes: Vec<RpoDigest>,
|
||||
depth: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
struct BranchNode {
|
||||
left: RpoDigest,
|
||||
right: RpoDigest,
|
||||
pub(crate) struct BranchNode {
|
||||
pub(crate) left: RpoDigest,
|
||||
pub(crate) right: RpoDigest,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
|
@ -200,16 +226,7 @@ impl Store {
|
|||
let leaves = BTreeMap::new();
|
||||
|
||||
// Construct empty node digests for each layer of the tree
|
||||
let empty_hashes: Vec<RpoDigest> = (0..depth + 1)
|
||||
.scan(Word::default().into(), |state, _| {
|
||||
let value = *state;
|
||||
*state = Rpo256::merge(&[value, value]);
|
||||
Some(value)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.rev()
|
||||
.collect();
|
||||
let empty_hashes = EmptySubtreeRoots::empty_hashes(depth).to_vec();
|
||||
|
||||
let root = empty_hashes[0].into();
|
||||
let store = Self {
|
||||
|
@ -222,34 +239,30 @@ impl Store {
|
|||
(store, root)
|
||||
}
|
||||
|
||||
fn get_empty_node(&self, depth: usize) -> BranchNode {
|
||||
let digest = self.empty_hashes[depth];
|
||||
BranchNode {
|
||||
left: digest,
|
||||
right: digest,
|
||||
}
|
||||
fn replace_empty_subtrees(&mut self, hashes: Vec<RpoDigest>) {
|
||||
self.empty_hashes = hashes;
|
||||
}
|
||||
|
||||
fn check_leaf_node_exists(&self, key: u64) -> bool {
|
||||
self.leaves.contains_key(&key)
|
||||
}
|
||||
|
||||
fn get_leaf_node(&self, key: u64) -> Result<Word, MerkleError> {
|
||||
self.leaves
|
||||
.get(&key)
|
||||
.cloned()
|
||||
.ok_or(MerkleError::InvalidIndex(NodeIndex::new(self.depth, key)))
|
||||
fn get_leaf_node(&self, key: u64) -> Option<Word> {
|
||||
self.leaves.get(&key).copied()
|
||||
}
|
||||
|
||||
fn insert_leaf_node(&mut self, key: u64, node: Word) {
|
||||
self.leaves.insert(key, node);
|
||||
}
|
||||
|
||||
fn get_branch_node(&self, index: &NodeIndex) -> Result<BranchNode, MerkleError> {
|
||||
self.branches
|
||||
.get(index)
|
||||
.cloned()
|
||||
.ok_or(MerkleError::InvalidIndex(*index))
|
||||
fn get_branch_node(&self, index: &NodeIndex) -> BranchNode {
|
||||
self.branches.get(index).cloned().unwrap_or_else(|| {
|
||||
let node = self.empty_hashes[index.depth() as usize + 1];
|
||||
BranchNode {
|
||||
left: node,
|
||||
right: node,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn insert_branch_node(&mut self, index: NodeIndex, left: RpoDigest, right: RpoDigest) {
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
use super::{
|
||||
super::{MerkleTree, RpoDigest, SimpleSmt},
|
||||
super::{int_to_node, MerkleTree, RpoDigest, SimpleSmt},
|
||||
NodeIndex, Rpo256, Vec, Word,
|
||||
};
|
||||
use crate::{Felt, FieldElement};
|
||||
use core::iter;
|
||||
use proptest::prelude::*;
|
||||
use rand_utils::prng_array;
|
||||
|
||||
|
@ -32,7 +30,7 @@ const ZERO_VALUES8: [Word; 8] = [int_to_node(0); 8];
|
|||
|
||||
#[test]
|
||||
fn build_empty_tree() {
|
||||
let smt = SimpleSmt::new(iter::empty(), 3).unwrap();
|
||||
let smt = SimpleSmt::new(3).unwrap();
|
||||
let mt = MerkleTree::new(ZERO_VALUES8.to_vec()).unwrap();
|
||||
assert_eq!(mt.root(), smt.root());
|
||||
}
|
||||
|
@ -40,7 +38,7 @@ fn build_empty_tree() {
|
|||
#[test]
|
||||
fn empty_digests_are_consistent() {
|
||||
let depth = 5;
|
||||
let root = SimpleSmt::new(iter::empty(), depth).unwrap().root();
|
||||
let root = SimpleSmt::new(depth).unwrap().root();
|
||||
let computed: [RpoDigest; 2] = (0..depth).fold([Default::default(); 2], |state, _| {
|
||||
let digest = Rpo256::merge(&state);
|
||||
[digest; 2]
|
||||
|
@ -51,7 +49,7 @@ fn empty_digests_are_consistent() {
|
|||
|
||||
#[test]
|
||||
fn build_sparse_tree() {
|
||||
let mut smt = SimpleSmt::new(iter::empty(), 3).unwrap();
|
||||
let mut smt = SimpleSmt::new(3).unwrap();
|
||||
let mut values = ZERO_VALUES8.to_vec();
|
||||
|
||||
// insert single value
|
||||
|
@ -83,7 +81,10 @@ fn build_sparse_tree() {
|
|||
|
||||
#[test]
|
||||
fn build_full_tree() {
|
||||
let tree = SimpleSmt::new(KEYS4.into_iter().zip(VALUES4.into_iter()), 2).unwrap();
|
||||
let tree = SimpleSmt::new(2)
|
||||
.unwrap()
|
||||
.with_leaves(KEYS4.into_iter().zip(VALUES4.into_iter()))
|
||||
.unwrap();
|
||||
|
||||
let (root, node2, node3) = compute_internal_nodes();
|
||||
assert_eq!(root, tree.root());
|
||||
|
@ -93,7 +94,10 @@ fn build_full_tree() {
|
|||
|
||||
#[test]
|
||||
fn get_values() {
|
||||
let tree = SimpleSmt::new(KEYS4.into_iter().zip(VALUES4.into_iter()), 2).unwrap();
|
||||
let tree = SimpleSmt::new(2)
|
||||
.unwrap()
|
||||
.with_leaves(KEYS4.into_iter().zip(VALUES4.into_iter()))
|
||||
.unwrap();
|
||||
|
||||
// check depth 2
|
||||
assert_eq!(VALUES4[0], tree.get_node(&NodeIndex::new(2, 0)).unwrap());
|
||||
|
@ -104,7 +108,10 @@ fn get_values() {
|
|||
|
||||
#[test]
|
||||
fn get_path() {
|
||||
let tree = SimpleSmt::new(KEYS4.into_iter().zip(VALUES4.into_iter()), 2).unwrap();
|
||||
let tree = SimpleSmt::new(2)
|
||||
.unwrap()
|
||||
.with_leaves(KEYS4.into_iter().zip(VALUES4.into_iter()))
|
||||
.unwrap();
|
||||
|
||||
let (_, node2, node3) = compute_internal_nodes();
|
||||
|
||||
|
@ -133,18 +140,20 @@ fn get_path() {
|
|||
|
||||
#[test]
|
||||
fn update_leaf() {
|
||||
let mut tree = SimpleSmt::new(KEYS8.into_iter().zip(VALUES8.into_iter()), 3).unwrap();
|
||||
let mut tree = SimpleSmt::new(3)
|
||||
.unwrap()
|
||||
.with_leaves(KEYS8.into_iter().zip(VALUES8.into_iter()))
|
||||
.unwrap();
|
||||
|
||||
// update one value
|
||||
let key = 3;
|
||||
let new_node = int_to_node(9);
|
||||
let mut expected_values = VALUES8.to_vec();
|
||||
expected_values[key] = new_node;
|
||||
let expected_tree = SimpleSmt::new(
|
||||
KEYS8.into_iter().zip(expected_values.clone().into_iter()),
|
||||
3,
|
||||
)
|
||||
.unwrap();
|
||||
let expected_tree = SimpleSmt::new(3)
|
||||
.unwrap()
|
||||
.with_leaves(KEYS8.into_iter().zip(expected_values.clone().into_iter()))
|
||||
.unwrap();
|
||||
|
||||
tree.update_leaf(key as u64, new_node).unwrap();
|
||||
assert_eq!(expected_tree.root, tree.root);
|
||||
|
@ -153,8 +162,10 @@ fn update_leaf() {
|
|||
let key = 6;
|
||||
let new_node = int_to_node(10);
|
||||
expected_values[key] = new_node;
|
||||
let expected_tree =
|
||||
SimpleSmt::new(KEYS8.into_iter().zip(expected_values.into_iter()), 3).unwrap();
|
||||
let expected_tree = SimpleSmt::new(3)
|
||||
.unwrap()
|
||||
.with_leaves(KEYS8.into_iter().zip(expected_values.into_iter()))
|
||||
.unwrap();
|
||||
|
||||
tree.update_leaf(key as u64, new_node).unwrap();
|
||||
assert_eq!(expected_tree.root, tree.root);
|
||||
|
@ -189,7 +200,7 @@ fn small_tree_opening_is_consistent() {
|
|||
|
||||
let depth = 3;
|
||||
let entries = vec![(0, a), (1, b), (4, c), (7, d)];
|
||||
let tree = SimpleSmt::new(entries, depth).unwrap();
|
||||
let tree = SimpleSmt::new(depth).unwrap().with_leaves(entries).unwrap();
|
||||
|
||||
assert_eq!(tree.root(), Word::from(k));
|
||||
|
||||
|
@ -220,7 +231,7 @@ proptest! {
|
|||
key in prop::num::u64::ANY,
|
||||
leaf in prop::num::u64::ANY,
|
||||
) {
|
||||
let mut tree = SimpleSmt::new(iter::empty(), depth).unwrap();
|
||||
let mut tree = SimpleSmt::new(depth).unwrap();
|
||||
|
||||
let key = key % (1 << depth as u64);
|
||||
let leaf = int_to_node(leaf);
|
||||
|
@ -241,7 +252,7 @@ proptest! {
|
|||
count in 2u8..10u8,
|
||||
ref seed in any::<[u8; 32]>()
|
||||
) {
|
||||
let mut tree = SimpleSmt::new(iter::empty(), depth).unwrap();
|
||||
let mut tree = SimpleSmt::new(depth).unwrap();
|
||||
let mut seed = *seed;
|
||||
let leaves = (1 << depth) - 1;
|
||||
|
||||
|
@ -275,7 +286,3 @@ fn compute_internal_nodes() -> (Word, Word, Word) {
|
|||
|
||||
(root.into(), node2.into(), node3.into())
|
||||
}
|
||||
|
||||
const fn int_to_node(value: u64) -> Word {
|
||||
[Felt::new(value), Felt::ZERO, Felt::ZERO, Felt::ZERO]
|
||||
}
|
||||
|
|
478
src/merkle/store/mod.rs
Normal file
478
src/merkle/store/mod.rs
Normal file
|
@ -0,0 +1,478 @@
|
|||
use super::{
|
||||
BTreeMap, BTreeSet, EmptySubtreeRoots, MerkleError, MerklePath, MerklePathSet, MerkleTree,
|
||||
NodeIndex, RootPath, Rpo256, RpoDigest, SimpleSmt, ValuePath, Vec, Word,
|
||||
};
|
||||
use crate::utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct Node {
|
||||
left: RpoDigest,
|
||||
right: RpoDigest,
|
||||
}
|
||||
|
||||
/// An in-memory data store for Merkle-lized data.
|
||||
///
|
||||
/// This is a in memory data store for Merkle trees, this store allows all the nodes of multiple
|
||||
/// trees to live as long as necessary and without duplication, this allows the implementation of
|
||||
/// space efficient persistent data structures.
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use miden_crypto::{ZERO, Felt, Word};
|
||||
/// # use miden_crypto::merkle::{NodeIndex, MerkleStore, MerkleTree};
|
||||
/// # use miden_crypto::hash::rpo::Rpo256;
|
||||
/// # const fn int_to_node(value: u64) -> Word {
|
||||
/// # [Felt::new(value), ZERO, ZERO, ZERO]
|
||||
/// # }
|
||||
/// # let A = int_to_node(1);
|
||||
/// # let B = int_to_node(2);
|
||||
/// # let C = int_to_node(3);
|
||||
/// # let D = int_to_node(4);
|
||||
/// # let E = int_to_node(5);
|
||||
/// # let F = int_to_node(6);
|
||||
/// # let G = int_to_node(7);
|
||||
/// # let H0 = int_to_node(8);
|
||||
/// # let H1 = int_to_node(9);
|
||||
/// # let T0 = MerkleTree::new([A, B, C, D, E, F, G, H0].to_vec()).expect("even number of leaves provided");
|
||||
/// # let T1 = MerkleTree::new([A, B, C, D, E, F, G, H1].to_vec()).expect("even number of leaves provided");
|
||||
/// # let ROOT0 = T0.root();
|
||||
/// # let ROOT1 = T1.root();
|
||||
/// let mut store = MerkleStore::new();
|
||||
///
|
||||
/// // the store is initialized with the SMT empty nodes
|
||||
/// assert_eq!(store.num_internal_nodes(), 255);
|
||||
///
|
||||
/// // populates the store with two merkle trees, common nodes are shared
|
||||
/// store.add_merkle_tree([A, B, C, D, E, F, G, H0]);
|
||||
/// store.add_merkle_tree([A, B, C, D, E, F, G, H1]);
|
||||
///
|
||||
/// // every leaf except the last are the same
|
||||
/// for i in 0..7 {
|
||||
/// let d0 = store.get_node(ROOT0, NodeIndex::new(3, i)).unwrap();
|
||||
/// let d1 = store.get_node(ROOT1, NodeIndex::new(3, i)).unwrap();
|
||||
/// assert_eq!(d0, d1, "Both trees have the same leaf at pos {i}");
|
||||
/// }
|
||||
///
|
||||
/// // The leafs A-B-C-D are the same for both trees, so are their 2 immediate parents
|
||||
/// for i in 0..4 {
|
||||
/// let d0 = store.get_path(ROOT0, NodeIndex::new(3, i)).unwrap();
|
||||
/// let d1 = store.get_path(ROOT1, NodeIndex::new(3, i)).unwrap();
|
||||
/// assert_eq!(d0.path[0..2], d1.path[0..2], "Both sub-trees are equal up to two levels");
|
||||
/// }
|
||||
///
|
||||
/// // Common internal nodes are shared, the two added trees have a total of 30, but the store has
|
||||
/// // only 10 new entries, corresponding to the 10 unique internal nodes of these trees.
|
||||
/// assert_eq!(store.num_internal_nodes() - 255, 10);
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct MerkleStore {
|
||||
nodes: BTreeMap<RpoDigest, Node>,
|
||||
}
|
||||
|
||||
impl Default for MerkleStore {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MerkleStore {
|
||||
// CONSTRUCTORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Creates an empty `MerkleStore` instance.
|
||||
pub fn new() -> MerkleStore {
|
||||
// pre-populate the store with the empty hashes
|
||||
let subtrees = EmptySubtreeRoots::empty_hashes(255);
|
||||
let nodes = subtrees
|
||||
.iter()
|
||||
.rev()
|
||||
.copied()
|
||||
.zip(subtrees.iter().rev().skip(1).copied())
|
||||
.map(|(child, parent)| {
|
||||
(
|
||||
parent,
|
||||
Node {
|
||||
left: child,
|
||||
right: child,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
MerkleStore { nodes }
|
||||
}
|
||||
|
||||
/// Appends the provided merkle tree represented by its `leaves` to the set.
|
||||
pub fn with_merkle_tree<I>(mut self, leaves: I) -> Result<Self, MerkleError>
|
||||
where
|
||||
I: IntoIterator<Item = Word>,
|
||||
{
|
||||
self.add_merkle_tree(leaves)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Appends the provided sparse merkle tree represented by its `entries` to the set.
|
||||
pub fn with_sparse_merkle_tree<R, I>(mut self, entries: R) -> Result<Self, MerkleError>
|
||||
where
|
||||
R: IntoIterator<IntoIter = I>,
|
||||
I: Iterator<Item = (u64, Word)> + ExactSizeIterator,
|
||||
{
|
||||
self.add_sparse_merkle_tree(entries)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Appends the provided merkle path set.
|
||||
pub fn with_merkle_path(
|
||||
mut self,
|
||||
index_value: u64,
|
||||
node: Word,
|
||||
path: MerklePath,
|
||||
) -> Result<Self, MerkleError> {
|
||||
self.add_merkle_path(index_value, node, path)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Appends the provided merkle path set.
|
||||
pub fn with_merkle_paths<I>(mut self, paths: I) -> Result<Self, MerkleError>
|
||||
where
|
||||
I: IntoIterator<Item = (u64, Word, MerklePath)>,
|
||||
{
|
||||
self.add_merkle_paths(paths)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
// PUBLIC ACCESSORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Return a count of the non-leaf nodes in the store.
|
||||
pub fn num_internal_nodes(&self) -> usize {
|
||||
self.nodes.len()
|
||||
}
|
||||
|
||||
/// Returns the node at `index` rooted on the tree `root`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This method can return the following errors:
|
||||
/// - `RootNotInStore` if the `root` is not present in the store.
|
||||
/// - `NodeNotInStore` if a node needed to traverse from `root` to `index` is not present in the store.
|
||||
pub fn get_node(&self, root: Word, index: NodeIndex) -> Result<Word, MerkleError> {
|
||||
let mut hash: RpoDigest = root.into();
|
||||
|
||||
// corner case: check the root is in the store when called with index `NodeIndex::root()`
|
||||
self.nodes
|
||||
.get(&hash)
|
||||
.ok_or(MerkleError::RootNotInStore(hash.into()))?;
|
||||
|
||||
for bit in index.bit_iterator().rev() {
|
||||
let node = self
|
||||
.nodes
|
||||
.get(&hash)
|
||||
.ok_or(MerkleError::NodeNotInStore(hash.into(), index))?;
|
||||
hash = if bit { node.right } else { node.left }
|
||||
}
|
||||
|
||||
Ok(hash.into())
|
||||
}
|
||||
|
||||
/// Returns the node at the specified `index` and its opening to the `root`.
|
||||
///
|
||||
/// The path starts at the sibling of the target leaf.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This method can return the following errors:
|
||||
/// - `RootNotInStore` if the `root` is not present in the store.
|
||||
/// - `NodeNotInStore` if a node needed to traverse from `root` to `index` is not present in the store.
|
||||
pub fn get_path(&self, root: Word, index: NodeIndex) -> Result<ValuePath, MerkleError> {
|
||||
let mut hash: RpoDigest = root.into();
|
||||
let mut path = Vec::with_capacity(index.depth().into());
|
||||
|
||||
// corner case: check the root is in the store when called with index `NodeIndex::root()`
|
||||
self.nodes
|
||||
.get(&hash)
|
||||
.ok_or(MerkleError::RootNotInStore(hash.into()))?;
|
||||
|
||||
for bit in index.bit_iterator().rev() {
|
||||
let node = self
|
||||
.nodes
|
||||
.get(&hash)
|
||||
.ok_or(MerkleError::NodeNotInStore(hash.into(), index))?;
|
||||
|
||||
hash = if bit {
|
||||
path.push(node.left.into());
|
||||
node.right
|
||||
} else {
|
||||
path.push(node.right.into());
|
||||
node.left
|
||||
}
|
||||
}
|
||||
|
||||
// the path is computed from root to leaf, so it must be reversed
|
||||
path.reverse();
|
||||
|
||||
Ok(ValuePath {
|
||||
value: hash.into(),
|
||||
path: MerklePath::new(path),
|
||||
})
|
||||
}
|
||||
|
||||
// STATE MUTATORS
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
/// Adds all the nodes of a Merkle tree represented by `leaves`.
|
||||
///
|
||||
/// This will instantiate a Merkle tree using `leaves` and include all the nodes into the
|
||||
/// store.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This method may return the following errors:
|
||||
/// - `DepthTooSmall` if leaves is empty or contains only 1 element
|
||||
/// - `NumLeavesNotPowerOfTwo` if the number of leaves is not a power-of-two
|
||||
pub fn add_merkle_tree<I>(&mut self, leaves: I) -> Result<Word, MerkleError>
|
||||
where
|
||||
I: IntoIterator<Item = Word>,
|
||||
{
|
||||
let leaves: Vec<_> = leaves.into_iter().collect();
|
||||
if leaves.len() < 2 {
|
||||
return Err(MerkleError::DepthTooSmall(leaves.len() as u8));
|
||||
}
|
||||
|
||||
let layers = leaves.len().ilog2();
|
||||
let tree = MerkleTree::new(leaves)?;
|
||||
|
||||
let mut depth = 0;
|
||||
let mut parent_offset = 1;
|
||||
let mut child_offset = 2;
|
||||
while depth < layers {
|
||||
let layer_size = 1usize << depth;
|
||||
for _ in 0..layer_size {
|
||||
// merkle tree is using level form representation, so left and right siblings are
|
||||
// next to each other
|
||||
let left = tree.nodes[child_offset];
|
||||
let right = tree.nodes[child_offset + 1];
|
||||
self.nodes.insert(
|
||||
tree.nodes[parent_offset].into(),
|
||||
Node {
|
||||
left: left.into(),
|
||||
right: right.into(),
|
||||
},
|
||||
);
|
||||
parent_offset += 1;
|
||||
child_offset += 2;
|
||||
}
|
||||
depth += 1;
|
||||
}
|
||||
|
||||
Ok(tree.nodes[1])
|
||||
}
|
||||
|
||||
/// Adds all the nodes of a Sparse Merkle tree represented by `entries`.
|
||||
///
|
||||
/// This will instantiate a Sparse Merkle tree using `entries` and include all the nodes into
|
||||
/// the store.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This will return `InvalidEntriesCount` if the length of `entries` is not `63`.
|
||||
pub fn add_sparse_merkle_tree<R, I>(&mut self, entries: R) -> Result<Word, MerkleError>
|
||||
where
|
||||
R: IntoIterator<IntoIter = I>,
|
||||
I: Iterator<Item = (u64, Word)> + ExactSizeIterator,
|
||||
{
|
||||
let smt = SimpleSmt::new(SimpleSmt::MAX_DEPTH)?.with_leaves(entries)?;
|
||||
for branch in smt.store.branches.values() {
|
||||
let parent = Rpo256::merge(&[branch.left, branch.right]);
|
||||
self.nodes.insert(
|
||||
parent,
|
||||
Node {
|
||||
left: branch.left,
|
||||
right: branch.right,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Ok(smt.root())
|
||||
}
|
||||
|
||||
/// Adds all the nodes of a Merkle path represented by `path`, opening to `node`. Returns the
|
||||
/// new root.
|
||||
///
|
||||
/// This will compute the sibling elements determined by the Merkle `path` and `node`, and
|
||||
/// include all the nodes into the store.
|
||||
pub fn add_merkle_path(
|
||||
&mut self,
|
||||
index_value: u64,
|
||||
mut node: Word,
|
||||
path: MerklePath,
|
||||
) -> Result<Word, MerkleError> {
|
||||
let mut index = NodeIndex::new(self.nodes.len() as u8, index_value);
|
||||
|
||||
for sibling in path {
|
||||
let (left, right) = match index.is_value_odd() {
|
||||
true => (sibling, node),
|
||||
false => (node, sibling),
|
||||
};
|
||||
let parent = Rpo256::merge(&[left.into(), right.into()]);
|
||||
self.nodes.insert(
|
||||
parent,
|
||||
Node {
|
||||
left: left.into(),
|
||||
right: right.into(),
|
||||
},
|
||||
);
|
||||
|
||||
index.move_up();
|
||||
node = parent.into();
|
||||
}
|
||||
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
/// Adds all the nodes of multiple Merkle paths into the store.
|
||||
///
|
||||
/// This will compute the sibling elements for each Merkle `path` and include all the nodes
|
||||
/// into the store.
|
||||
///
|
||||
/// For further reference, check [MerkleStore::add_merkle_path].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Every path must resolve to the same root, otherwise this will return an `ConflictingRoots`
|
||||
/// error.
|
||||
pub fn add_merkle_paths<I>(&mut self, paths: I) -> Result<Word, MerkleError>
|
||||
where
|
||||
I: IntoIterator<Item = (u64, Word, MerklePath)>,
|
||||
{
|
||||
let paths: Vec<(u64, Word, MerklePath)> = paths.into_iter().collect();
|
||||
|
||||
let roots: BTreeSet<RpoDigest> = paths
|
||||
.iter()
|
||||
.map(|(index, node, path)| path.compute_root(*index, *node).into())
|
||||
.collect();
|
||||
|
||||
if roots.len() != 1 {
|
||||
return Err(MerkleError::ConflictingRoots(
|
||||
roots.iter().map(|v| Word::from(*v)).collect(),
|
||||
));
|
||||
}
|
||||
|
||||
for (index_value, node, path) in paths {
|
||||
self.add_merkle_path(index_value, node, path)?;
|
||||
}
|
||||
|
||||
Ok(roots.iter().next().unwrap().into())
|
||||
}
|
||||
|
||||
/// Appends the provided [MerklePathSet] into the store.
|
||||
///
|
||||
/// For further reference, check [MerkleStore::add_merkle_path].
|
||||
pub fn add_merkle_path_set(&mut self, path_set: &MerklePathSet) -> Result<Word, MerkleError> {
|
||||
let root = path_set.root();
|
||||
for (index, path) in path_set.to_paths() {
|
||||
self.add_merkle_path(index, path.value, path.path)?;
|
||||
}
|
||||
Ok(root)
|
||||
}
|
||||
|
||||
/// Sets a node to `value`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This method can return the following errors:
|
||||
/// - `RootNotInStore` if the `root` is not present in the store.
|
||||
/// - `NodeNotInStore` if a node needed to traverse from `root` to `index` is not present in the store.
|
||||
pub fn set_node(
|
||||
&mut self,
|
||||
mut root: Word,
|
||||
index: NodeIndex,
|
||||
value: Word,
|
||||
) -> Result<RootPath, MerkleError> {
|
||||
let node = value;
|
||||
let ValuePath { value, path } = self.get_path(root, index)?;
|
||||
|
||||
// performs the update only if the node value differs from the opening
|
||||
if node != value {
|
||||
root = self.add_merkle_path(index.value(), node, path.clone())?;
|
||||
}
|
||||
|
||||
Ok(RootPath { root, path })
|
||||
}
|
||||
|
||||
pub fn merge_roots(&mut self, root1: Word, root2: Word) -> Result<Word, MerkleError> {
|
||||
let root1: RpoDigest = root1.into();
|
||||
let root2: RpoDigest = root2.into();
|
||||
|
||||
if !self.nodes.contains_key(&root1) {
|
||||
Err(MerkleError::NodeNotInStore(
|
||||
root1.into(),
|
||||
NodeIndex::new(0, 0),
|
||||
))
|
||||
} else if !self.nodes.contains_key(&root1) {
|
||||
Err(MerkleError::NodeNotInStore(
|
||||
root2.into(),
|
||||
NodeIndex::new(0, 0),
|
||||
))
|
||||
} else {
|
||||
let parent: Word = Rpo256::merge(&[root1, root2]).into();
|
||||
self.nodes.insert(
|
||||
parent.into(),
|
||||
Node {
|
||||
left: root1,
|
||||
right: root2,
|
||||
},
|
||||
);
|
||||
|
||||
Ok(parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SERIALIZATION
|
||||
// ================================================================================================
|
||||
|
||||
impl Serializable for Node {
|
||||
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||
self.left.write_into(target);
|
||||
self.right.write_into(target);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializable for Node {
|
||||
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||
let left = RpoDigest::read_from(source)?;
|
||||
let right = RpoDigest::read_from(source)?;
|
||||
Ok(Node { left, right })
|
||||
}
|
||||
}
|
||||
|
||||
impl Serializable for MerkleStore {
|
||||
fn write_into<W: ByteWriter>(&self, target: &mut W) {
|
||||
target.write_u64(self.nodes.len() as u64);
|
||||
|
||||
for (k, v) in self.nodes.iter() {
|
||||
k.write_into(target);
|
||||
v.write_into(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deserializable for MerkleStore {
|
||||
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
|
||||
let len = source.read_u64()?;
|
||||
let mut nodes: BTreeMap<RpoDigest, Node> = BTreeMap::new();
|
||||
|
||||
for _ in 0..len {
|
||||
let key = RpoDigest::read_from(source)?;
|
||||
let value = Node::read_from(source)?;
|
||||
nodes.insert(key, value);
|
||||
}
|
||||
|
||||
Ok(MerkleStore { nodes })
|
||||
}
|
||||
}
|
637
src/merkle/store/tests.rs
Normal file
637
src/merkle/store/tests.rs
Normal file
|
@ -0,0 +1,637 @@
|
|||
use super::*;
|
||||
use crate::{
|
||||
hash::rpo::Rpo256,
|
||||
merkle::{int_to_node, MerklePathSet},
|
||||
Felt, Word, WORD_SIZE, ZERO,
|
||||
};
|
||||
|
||||
#[cfg(std)]
|
||||
use std::error::Error;
|
||||
|
||||
const KEYS4: [u64; 4] = [0, 1, 2, 3];
|
||||
const LEAVES4: [Word; 4] = [
|
||||
int_to_node(1),
|
||||
int_to_node(2),
|
||||
int_to_node(3),
|
||||
int_to_node(4),
|
||||
];
|
||||
const EMPTY: Word = [ZERO; WORD_SIZE];
|
||||
|
||||
#[test]
|
||||
fn test_root_not_in_store() -> Result<(), MerkleError> {
|
||||
let mtree = MerkleTree::new(LEAVES4.to_vec())?;
|
||||
let store = MerkleStore::default().with_merkle_tree(LEAVES4)?;
|
||||
assert_eq!(
|
||||
store.get_node(LEAVES4[0], NodeIndex::new(mtree.depth(), 0)),
|
||||
Err(MerkleError::RootNotInStore(LEAVES4[0])),
|
||||
"Leaf 0 is not a root"
|
||||
);
|
||||
assert_eq!(
|
||||
store.get_path(LEAVES4[0], NodeIndex::new(mtree.depth(), 0)),
|
||||
Err(MerkleError::RootNotInStore(LEAVES4[0])),
|
||||
"Leaf 0 is not a root"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merkle_tree() -> Result<(), MerkleError> {
|
||||
let mut store = MerkleStore::default();
|
||||
|
||||
let mtree = MerkleTree::new(LEAVES4.to_vec())?;
|
||||
store.add_merkle_tree(LEAVES4.to_vec())?;
|
||||
|
||||
// STORE LEAVES ARE CORRECT ==============================================================
|
||||
// checks the leaves in the store corresponds to the expected values
|
||||
assert_eq!(
|
||||
store.get_node(mtree.root(), NodeIndex::new(mtree.depth(), 0)),
|
||||
Ok(LEAVES4[0]),
|
||||
"node 0 must be in the tree"
|
||||
);
|
||||
assert_eq!(
|
||||
store.get_node(mtree.root(), NodeIndex::new(mtree.depth(), 1)),
|
||||
Ok(LEAVES4[1]),
|
||||
"node 1 must be in the tree"
|
||||
);
|
||||
assert_eq!(
|
||||
store.get_node(mtree.root(), NodeIndex::new(mtree.depth(), 2)),
|
||||
Ok(LEAVES4[2]),
|
||||
"node 2 must be in the tree"
|
||||
);
|
||||
assert_eq!(
|
||||
store.get_node(mtree.root(), NodeIndex::new(mtree.depth(), 3)),
|
||||
Ok(LEAVES4[3]),
|
||||
"node 3 must be in the tree"
|
||||
);
|
||||
|
||||
// STORE LEAVES MATCH TREE ===============================================================
|
||||
// sanity check the values returned by the store and the tree
|
||||
assert_eq!(
|
||||
mtree.get_node(NodeIndex::new(mtree.depth(), 0)),
|
||||
store.get_node(mtree.root(), NodeIndex::new(mtree.depth(), 0)),
|
||||
"node 0 must be the same for both MerkleTree and MerkleStore"
|
||||
);
|
||||
assert_eq!(
|
||||
mtree.get_node(NodeIndex::new(mtree.depth(), 1)),
|
||||
store.get_node(mtree.root(), NodeIndex::new(mtree.depth(), 1)),
|
||||
"node 1 must be the same for both MerkleTree and MerkleStore"
|
||||
);
|
||||
assert_eq!(
|
||||
mtree.get_node(NodeIndex::new(mtree.depth(), 2)),
|
||||
store.get_node(mtree.root(), NodeIndex::new(mtree.depth(), 2)),
|
||||
"node 2 must be the same for both MerkleTree and MerkleStore"
|
||||
);
|
||||
assert_eq!(
|
||||
mtree.get_node(NodeIndex::new(mtree.depth(), 3)),
|
||||
store.get_node(mtree.root(), NodeIndex::new(mtree.depth(), 3)),
|
||||
"node 3 must be the same for both MerkleTree and MerkleStore"
|
||||
);
|
||||
|
||||
// STORE MERKLE PATH MATCHS ==============================================================
|
||||
// assert the merkle path returned by the store is the same as the one in the tree
|
||||
let result = store
|
||||
.get_path(mtree.root(), NodeIndex::new(mtree.depth(), 0))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
LEAVES4[0], result.value,
|
||||
"Value for merkle path at index 0 must match leaf value"
|
||||
);
|
||||
assert_eq!(
|
||||
mtree.get_path(NodeIndex::new(mtree.depth(), 0)),
|
||||
Ok(result.path),
|
||||
"merkle path for index 0 must be the same for the MerkleTree and MerkleStore"
|
||||
);
|
||||
|
||||
let result = store
|
||||
.get_path(mtree.root(), NodeIndex::new(mtree.depth(), 1))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
LEAVES4[1], result.value,
|
||||
"Value for merkle path at index 0 must match leaf value"
|
||||
);
|
||||
assert_eq!(
|
||||
mtree.get_path(NodeIndex::new(mtree.depth(), 1)),
|
||||
Ok(result.path),
|
||||
"merkle path for index 1 must be the same for the MerkleTree and MerkleStore"
|
||||
);
|
||||
|
||||
let result = store
|
||||
.get_path(mtree.root(), NodeIndex::new(mtree.depth(), 2))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
LEAVES4[2], result.value,
|
||||
"Value for merkle path at index 0 must match leaf value"
|
||||
);
|
||||
assert_eq!(
|
||||
mtree.get_path(NodeIndex::new(mtree.depth(), 2)),
|
||||
Ok(result.path),
|
||||
"merkle path for index 0 must be the same for the MerkleTree and MerkleStore"
|
||||
);
|
||||
|
||||
let result = store
|
||||
.get_path(mtree.root(), NodeIndex::new(mtree.depth(), 3))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
LEAVES4[3], result.value,
|
||||
"Value for merkle path at index 0 must match leaf value"
|
||||
);
|
||||
assert_eq!(
|
||||
mtree.get_path(NodeIndex::new(mtree.depth(), 3)),
|
||||
Ok(result.path),
|
||||
"merkle path for index 0 must be the same for the MerkleTree and MerkleStore"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_roots() {
|
||||
let store = MerkleStore::default();
|
||||
let mut root = RpoDigest::new(EMPTY);
|
||||
|
||||
for depth in 0..255 {
|
||||
root = Rpo256::merge(&[root; 2]);
|
||||
assert!(
|
||||
store.get_node(root.into(), NodeIndex::new(0, 0)).is_ok(),
|
||||
"The root of the empty tree of depth {depth} must be registered"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_leaf_paths_for_empty_trees() -> Result<(), MerkleError> {
|
||||
let store = MerkleStore::default();
|
||||
|
||||
// Starts at 1 because leafs are not included in the store.
|
||||
// Ends at 64 because it is not possible to represent an index of a depth greater than 64,
|
||||
// because a u64 is used to index the leaf.
|
||||
for depth in 1..64 {
|
||||
let smt = SimpleSmt::new(depth)?;
|
||||
|
||||
let index = NodeIndex::new(depth, 0);
|
||||
let store_path = store.get_path(smt.root(), index)?;
|
||||
let smt_path = smt.get_path(index)?;
|
||||
assert_eq!(
|
||||
store_path.value, EMPTY,
|
||||
"the leaf of an empty tree is always ZERO"
|
||||
);
|
||||
assert_eq!(
|
||||
store_path.path, smt_path,
|
||||
"the returned merkle path does not match the computed values"
|
||||
);
|
||||
assert_eq!(
|
||||
store_path.path.compute_root(depth.into(), EMPTY),
|
||||
smt.root(),
|
||||
"computed root from the path must match the empty tree root"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_invalid_node() {
|
||||
let mut store = MerkleStore::default();
|
||||
let mtree = MerkleTree::new(LEAVES4.to_vec()).expect("creating a merkle tree must work");
|
||||
store
|
||||
.add_merkle_tree(LEAVES4.to_vec())
|
||||
.expect("adding a merkle tree to the store must work");
|
||||
let _ = store.get_node(mtree.root(), NodeIndex::new(mtree.depth(), 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_sparse_merkle_tree_one_level() -> Result<(), MerkleError> {
|
||||
let mut store = MerkleStore::default();
|
||||
let keys2: [u64; 2] = [0, 1];
|
||||
let leaves2: [Word; 2] = [int_to_node(1), int_to_node(2)];
|
||||
store.add_sparse_merkle_tree(keys2.into_iter().zip(leaves2.into_iter()))?;
|
||||
let smt = SimpleSmt::new(1)
|
||||
.unwrap()
|
||||
.with_leaves(keys2.into_iter().zip(leaves2.into_iter()))
|
||||
.unwrap();
|
||||
|
||||
let idx = NodeIndex::new(1, 0);
|
||||
assert_eq!(smt.get_node(&idx).unwrap(), leaves2[0]);
|
||||
assert_eq!(
|
||||
store.get_node(smt.root(), idx).unwrap(),
|
||||
smt.get_node(&idx).unwrap()
|
||||
);
|
||||
|
||||
let idx = NodeIndex::new(1, 1);
|
||||
assert_eq!(smt.get_node(&idx).unwrap(), leaves2[1]);
|
||||
assert_eq!(
|
||||
store.get_node(smt.root(), idx).unwrap(),
|
||||
smt.get_node(&idx).unwrap()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sparse_merkle_tree() -> Result<(), MerkleError> {
|
||||
let mut store = MerkleStore::default();
|
||||
store.add_sparse_merkle_tree(KEYS4.into_iter().zip(LEAVES4.into_iter()))?;
|
||||
|
||||
let smt = SimpleSmt::new(SimpleSmt::MAX_DEPTH)
|
||||
.unwrap()
|
||||
.with_leaves(KEYS4.into_iter().zip(LEAVES4.into_iter()))
|
||||
.unwrap();
|
||||
|
||||
// STORE LEAVES ARE CORRECT ==============================================================
|
||||
// checks the leaves in the store corresponds to the expected values
|
||||
assert_eq!(
|
||||
store.get_node(smt.root(), NodeIndex::new(smt.depth(), 0)),
|
||||
Ok(LEAVES4[0]),
|
||||
"node 0 must be in the tree"
|
||||
);
|
||||
assert_eq!(
|
||||
store.get_node(smt.root(), NodeIndex::new(smt.depth(), 1)),
|
||||
Ok(LEAVES4[1]),
|
||||
"node 1 must be in the tree"
|
||||
);
|
||||
assert_eq!(
|
||||
store.get_node(smt.root(), NodeIndex::new(smt.depth(), 2)),
|
||||
Ok(LEAVES4[2]),
|
||||
"node 2 must be in the tree"
|
||||
);
|
||||
assert_eq!(
|
||||
store.get_node(smt.root(), NodeIndex::new(smt.depth(), 3)),
|
||||
Ok(LEAVES4[3]),
|
||||
"node 3 must be in the tree"
|
||||
);
|
||||
assert_eq!(
|
||||
store.get_node(smt.root(), NodeIndex::new(smt.depth(), 4)),
|
||||
Ok(EMPTY),
|
||||
"unmodified node 4 must be ZERO"
|
||||
);
|
||||
|
||||
// STORE LEAVES MATCH TREE ===============================================================
|
||||
// sanity check the values returned by the store and the tree
|
||||
assert_eq!(
|
||||
smt.get_node(&NodeIndex::new(smt.depth(), 0)),
|
||||
store.get_node(smt.root(), NodeIndex::new(smt.depth(), 0)),
|
||||
"node 0 must be the same for both SparseMerkleTree and MerkleStore"
|
||||
);
|
||||
assert_eq!(
|
||||
smt.get_node(&NodeIndex::new(smt.depth(), 1)),
|
||||
store.get_node(smt.root(), NodeIndex::new(smt.depth(), 1)),
|
||||
"node 1 must be the same for both SparseMerkleTree and MerkleStore"
|
||||
);
|
||||
assert_eq!(
|
||||
smt.get_node(&NodeIndex::new(smt.depth(), 2)),
|
||||
store.get_node(smt.root(), NodeIndex::new(smt.depth(), 2)),
|
||||
"node 2 must be the same for both SparseMerkleTree and MerkleStore"
|
||||
);
|
||||
assert_eq!(
|
||||
smt.get_node(&NodeIndex::new(smt.depth(), 3)),
|
||||
store.get_node(smt.root(), NodeIndex::new(smt.depth(), 3)),
|
||||
"node 3 must be the same for both SparseMerkleTree and MerkleStore"
|
||||
);
|
||||
assert_eq!(
|
||||
smt.get_node(&NodeIndex::new(smt.depth(), 4)),
|
||||
store.get_node(smt.root(), NodeIndex::new(smt.depth(), 4)),
|
||||
"node 4 must be the same for both SparseMerkleTree and MerkleStore"
|
||||
);
|
||||
|
||||
// STORE MERKLE PATH MATCHS ==============================================================
|
||||
// assert the merkle path returned by the store is the same as the one in the tree
|
||||
let result = store
|
||||
.get_path(smt.root(), NodeIndex::new(smt.depth(), 0))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
LEAVES4[0], result.value,
|
||||
"Value for merkle path at index 0 must match leaf value"
|
||||
);
|
||||
assert_eq!(
|
||||
smt.get_path(NodeIndex::new(smt.depth(), 0)),
|
||||
Ok(result.path),
|
||||
"merkle path for index 0 must be the same for the MerkleTree and MerkleStore"
|
||||
);
|
||||
|
||||
let result = store
|
||||
.get_path(smt.root(), NodeIndex::new(smt.depth(), 1))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
LEAVES4[1], result.value,
|
||||
"Value for merkle path at index 1 must match leaf value"
|
||||
);
|
||||
assert_eq!(
|
||||
smt.get_path(NodeIndex::new(smt.depth(), 1)),
|
||||
Ok(result.path),
|
||||
"merkle path for index 1 must be the same for the MerkleTree and MerkleStore"
|
||||
);
|
||||
|
||||
let result = store
|
||||
.get_path(smt.root(), NodeIndex::new(smt.depth(), 2))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
LEAVES4[2], result.value,
|
||||
"Value for merkle path at index 2 must match leaf value"
|
||||
);
|
||||
assert_eq!(
|
||||
smt.get_path(NodeIndex::new(smt.depth(), 2)),
|
||||
Ok(result.path),
|
||||
"merkle path for index 2 must be the same for the MerkleTree and MerkleStore"
|
||||
);
|
||||
|
||||
let result = store
|
||||
.get_path(smt.root(), NodeIndex::new(smt.depth(), 3))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
LEAVES4[3], result.value,
|
||||
"Value for merkle path at index 3 must match leaf value"
|
||||
);
|
||||
assert_eq!(
|
||||
smt.get_path(NodeIndex::new(smt.depth(), 3)),
|
||||
Ok(result.path),
|
||||
"merkle path for index 3 must be the same for the MerkleTree and MerkleStore"
|
||||
);
|
||||
|
||||
let result = store
|
||||
.get_path(smt.root(), NodeIndex::new(smt.depth(), 4))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
EMPTY, result.value,
|
||||
"Value for merkle path at index 4 must match leaf value"
|
||||
);
|
||||
assert_eq!(
|
||||
smt.get_path(NodeIndex::new(smt.depth(), 4)),
|
||||
Ok(result.path),
|
||||
"merkle path for index 4 must be the same for the MerkleTree and MerkleStore"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_merkle_paths() -> Result<(), MerkleError> {
|
||||
let mtree = MerkleTree::new(LEAVES4.to_vec())?;
|
||||
|
||||
let i0 = 0;
|
||||
let p0 = mtree.get_path(NodeIndex::new(2, i0)).unwrap();
|
||||
|
||||
let i1 = 1;
|
||||
let p1 = mtree.get_path(NodeIndex::new(2, i1)).unwrap();
|
||||
|
||||
let i2 = 2;
|
||||
let p2 = mtree.get_path(NodeIndex::new(2, i2)).unwrap();
|
||||
|
||||
let i3 = 3;
|
||||
let p3 = mtree.get_path(NodeIndex::new(2, i3)).unwrap();
|
||||
|
||||
let paths = [
|
||||
(i0, LEAVES4[i0 as usize], p0),
|
||||
(i1, LEAVES4[i1 as usize], p1),
|
||||
(i2, LEAVES4[i2 as usize], p2),
|
||||
(i3, LEAVES4[i3 as usize], p3),
|
||||
];
|
||||
|
||||
let mut store = MerkleStore::default();
|
||||
store
|
||||
.add_merkle_paths(paths.clone())
|
||||
.expect("the valid paths must work");
|
||||
|
||||
let depth = 2;
|
||||
let set = MerklePathSet::new(depth).with_paths(paths).unwrap();
|
||||
|
||||
// STORE LEAVES ARE CORRECT ==============================================================
|
||||
// checks the leaves in the store corresponds to the expected values
|
||||
assert_eq!(
|
||||
store.get_node(set.root(), NodeIndex::new(set.depth(), 0)),
|
||||
Ok(LEAVES4[0]),
|
||||
"node 0 must be in the set"
|
||||
);
|
||||
assert_eq!(
|
||||
store.get_node(set.root(), NodeIndex::new(set.depth(), 1)),
|
||||
Ok(LEAVES4[1]),
|
||||
"node 1 must be in the set"
|
||||
);
|
||||
assert_eq!(
|
||||
store.get_node(set.root(), NodeIndex::new(set.depth(), 2)),
|
||||
Ok(LEAVES4[2]),
|
||||
"node 2 must be in the set"
|
||||
);
|
||||
assert_eq!(
|
||||
store.get_node(set.root(), NodeIndex::new(set.depth(), 3)),
|
||||
Ok(LEAVES4[3]),
|
||||
"node 3 must be in the set"
|
||||
);
|
||||
|
||||
// STORE LEAVES MATCH SET ================================================================
|
||||
// sanity check the values returned by the store and the set
|
||||
assert_eq!(
|
||||
set.get_node(NodeIndex::new(set.depth(), 0)),
|
||||
store.get_node(set.root(), NodeIndex::new(set.depth(), 0)),
|
||||
"node 0 must be the same for both SparseMerkleTree and MerkleStore"
|
||||
);
|
||||
assert_eq!(
|
||||
set.get_node(NodeIndex::new(set.depth(), 1)),
|
||||
store.get_node(set.root(), NodeIndex::new(set.depth(), 1)),
|
||||
"node 1 must be the same for both SparseMerkleTree and MerkleStore"
|
||||
);
|
||||
assert_eq!(
|
||||
set.get_node(NodeIndex::new(set.depth(), 2)),
|
||||
store.get_node(set.root(), NodeIndex::new(set.depth(), 2)),
|
||||
"node 2 must be the same for both SparseMerkleTree and MerkleStore"
|
||||
);
|
||||
assert_eq!(
|
||||
set.get_node(NodeIndex::new(set.depth(), 3)),
|
||||
store.get_node(set.root(), NodeIndex::new(set.depth(), 3)),
|
||||
"node 3 must be the same for both SparseMerkleTree and MerkleStore"
|
||||
);
|
||||
|
||||
// STORE MERKLE PATH MATCHS ==============================================================
|
||||
// assert the merkle path returned by the store is the same as the one in the set
|
||||
let result = store
|
||||
.get_path(set.root(), NodeIndex::new(set.depth(), 0))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
LEAVES4[0], result.value,
|
||||
"Value for merkle path at index 0 must match leaf value"
|
||||
);
|
||||
assert_eq!(
|
||||
set.get_path(NodeIndex::new(set.depth(), 0)),
|
||||
Ok(result.path),
|
||||
"merkle path for index 0 must be the same for the MerkleTree and MerkleStore"
|
||||
);
|
||||
|
||||
let result = store
|
||||
.get_path(set.root(), NodeIndex::new(set.depth(), 1))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
LEAVES4[1], result.value,
|
||||
"Value for merkle path at index 0 must match leaf value"
|
||||
);
|
||||
assert_eq!(
|
||||
set.get_path(NodeIndex::new(set.depth(), 1)),
|
||||
Ok(result.path),
|
||||
"merkle path for index 1 must be the same for the MerkleTree and MerkleStore"
|
||||
);
|
||||
|
||||
let result = store
|
||||
.get_path(set.root(), NodeIndex::new(set.depth(), 2))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
LEAVES4[2], result.value,
|
||||
"Value for merkle path at index 0 must match leaf value"
|
||||
);
|
||||
assert_eq!(
|
||||
set.get_path(NodeIndex::new(set.depth(), 2)),
|
||||
Ok(result.path),
|
||||
"merkle path for index 0 must be the same for the MerkleTree and MerkleStore"
|
||||
);
|
||||
|
||||
let result = store
|
||||
.get_path(set.root(), NodeIndex::new(set.depth(), 3))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
LEAVES4[3], result.value,
|
||||
"Value for merkle path at index 0 must match leaf value"
|
||||
);
|
||||
assert_eq!(
|
||||
set.get_path(NodeIndex::new(set.depth(), 3)),
|
||||
Ok(result.path),
|
||||
"merkle path for index 0 must be the same for the MerkleTree and MerkleStore"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wont_open_to_different_depth_root() {
|
||||
let empty = EmptySubtreeRoots::empty_hashes(64);
|
||||
let a = [Felt::new(1); 4];
|
||||
let b = [Felt::new(2); 4];
|
||||
|
||||
// Compute the root for a different depth. We cherry-pick this specific depth to prevent a
|
||||
// regression to a bug in the past that allowed the user to fetch a node at a depth lower than
|
||||
// the inserted path of a Merkle tree.
|
||||
let mut root = Rpo256::merge(&[a.into(), b.into()]);
|
||||
for depth in (1..=63).rev() {
|
||||
root = Rpo256::merge(&[root, empty[depth]]);
|
||||
}
|
||||
let root = Word::from(root);
|
||||
|
||||
// For this example, the depth of the Merkle tree is 1, as we have only two leaves. Here we
|
||||
// attempt to fetch a node on the maximum depth, and it should fail because the root shouldn't
|
||||
// exist for the set.
|
||||
let store = MerkleStore::default().with_merkle_tree([a, b]).unwrap();
|
||||
let index = NodeIndex::root();
|
||||
let err = store.get_node(root, index).err().unwrap();
|
||||
assert_eq!(err, MerkleError::RootNotInStore(root));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn store_path_opens_from_leaf() {
|
||||
let a = [Felt::new(1); 4];
|
||||
let b = [Felt::new(2); 4];
|
||||
let c = [Felt::new(3); 4];
|
||||
let d = [Felt::new(4); 4];
|
||||
let e = [Felt::new(5); 4];
|
||||
let f = [Felt::new(6); 4];
|
||||
let g = [Felt::new(7); 4];
|
||||
let h = [Felt::new(8); 4];
|
||||
|
||||
let i = Rpo256::merge(&[a.into(), b.into()]);
|
||||
let j = Rpo256::merge(&[c.into(), d.into()]);
|
||||
let k = Rpo256::merge(&[e.into(), f.into()]);
|
||||
let l = Rpo256::merge(&[g.into(), h.into()]);
|
||||
|
||||
let m = Rpo256::merge(&[i.into(), j.into()]);
|
||||
let n = Rpo256::merge(&[k.into(), l.into()]);
|
||||
|
||||
let root = Rpo256::merge(&[m.into(), n.into()]);
|
||||
|
||||
let store = MerkleStore::default()
|
||||
.with_merkle_tree([a, b, c, d, e, f, g, h])
|
||||
.unwrap();
|
||||
let path = store
|
||||
.get_path(root.into(), NodeIndex::new(3, 1))
|
||||
.unwrap()
|
||||
.path;
|
||||
|
||||
let expected = MerklePath::new([a.into(), j.into(), n.into()].to_vec());
|
||||
assert_eq!(path, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_node() -> Result<(), MerkleError> {
|
||||
let mtree = MerkleTree::new(LEAVES4.to_vec())?;
|
||||
let mut store = MerkleStore::default().with_merkle_tree(LEAVES4)?;
|
||||
let value = int_to_node(42);
|
||||
let index = NodeIndex::new(mtree.depth(), 0);
|
||||
let new_root = store.set_node(mtree.root(), index, value)?.root;
|
||||
assert_eq!(
|
||||
store.get_node(new_root, index),
|
||||
Ok(value),
|
||||
"Value must have changed"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_constructors() -> Result<(), MerkleError> {
|
||||
let store = MerkleStore::new().with_merkle_tree(LEAVES4)?;
|
||||
let mtree = MerkleTree::new(LEAVES4.to_vec())?;
|
||||
|
||||
let depth = mtree.depth();
|
||||
let leaves = 2u64.pow(depth.into());
|
||||
for index in 0..leaves {
|
||||
let index = NodeIndex::new(depth, index);
|
||||
let value_path = store.get_path(mtree.root(), index)?;
|
||||
assert_eq!(mtree.get_path(index)?, value_path.path);
|
||||
}
|
||||
|
||||
let store = MerkleStore::default()
|
||||
.with_sparse_merkle_tree(KEYS4.into_iter().zip(LEAVES4.into_iter()))?;
|
||||
let smt = SimpleSmt::new(SimpleSmt::MAX_DEPTH)
|
||||
.unwrap()
|
||||
.with_leaves(KEYS4.into_iter().zip(LEAVES4.into_iter()))
|
||||
.unwrap();
|
||||
let depth = smt.depth();
|
||||
|
||||
for key in KEYS4 {
|
||||
let index = NodeIndex::new(depth, key);
|
||||
let value_path = store.get_path(smt.root(), index)?;
|
||||
assert_eq!(smt.get_path(index)?, value_path.path);
|
||||
}
|
||||
|
||||
let d = 2;
|
||||
let paths = [
|
||||
(0, LEAVES4[0], mtree.get_path(NodeIndex::new(d, 0)).unwrap()),
|
||||
(1, LEAVES4[1], mtree.get_path(NodeIndex::new(d, 1)).unwrap()),
|
||||
(2, LEAVES4[2], mtree.get_path(NodeIndex::new(d, 2)).unwrap()),
|
||||
(3, LEAVES4[3], mtree.get_path(NodeIndex::new(d, 3)).unwrap()),
|
||||
];
|
||||
|
||||
let store1 = MerkleStore::default().with_merkle_paths(paths.clone())?;
|
||||
let store2 = MerkleStore::default()
|
||||
.with_merkle_path(0, LEAVES4[0], mtree.get_path(NodeIndex::new(d, 0))?)?
|
||||
.with_merkle_path(1, LEAVES4[1], mtree.get_path(NodeIndex::new(d, 1))?)?
|
||||
.with_merkle_path(2, LEAVES4[2], mtree.get_path(NodeIndex::new(d, 2))?)?
|
||||
.with_merkle_path(3, LEAVES4[3], mtree.get_path(NodeIndex::new(d, 3))?)?;
|
||||
let set = MerklePathSet::new(d).with_paths(paths).unwrap();
|
||||
|
||||
for key in [0, 1, 2, 3] {
|
||||
let index = NodeIndex::new(d, key);
|
||||
let value_path1 = store1.get_path(set.root(), index)?;
|
||||
let value_path2 = store2.get_path(set.root(), index)?;
|
||||
assert_eq!(value_path1, value_path2);
|
||||
|
||||
let index = NodeIndex::new(d, key);
|
||||
assert_eq!(set.get_path(index)?, value_path1.path);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(std)]
|
||||
#[test]
|
||||
fn test_serialization() -> Result<(), Box<dyn Error>> {
|
||||
let original = MerkleStore::new().with_merkle_tree(LEAVES4)?;
|
||||
let decoded = MerkleStore::read_from_bytes(&original.to_bytes())?;
|
||||
assert_eq!(original, decoded);
|
||||
Ok(())
|
||||
}
|
21
src/utils.rs
Normal file
21
src/utils.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
use super::Word;
|
||||
use crate::utils::string::String;
|
||||
use core::fmt::{self, Write};
|
||||
|
||||
// RE-EXPORTS
|
||||
// ================================================================================================
|
||||
pub use winter_utils::{
|
||||
collections, string, uninit_vector, ByteReader, ByteWriter, Deserializable,
|
||||
DeserializationError, Serializable, SliceReader,
|
||||
};
|
||||
|
||||
/// Converts a [Word] into hex.
|
||||
pub fn word_to_hex(w: &Word) -> Result<String, fmt::Error> {
|
||||
let mut s = String::new();
|
||||
|
||||
for byte in w.iter().flat_map(|e| e.to_bytes()) {
|
||||
write!(s, "{byte:02x}")?;
|
||||
}
|
||||
|
||||
Ok(s)
|
||||
}
|
Loading…
Add table
Reference in a new issue