v0.2 tracking PRal-gkr-basic-workflow v0.2.0
@ -0,0 +1,2 @@ |
|||
# initial run of pre-commit |
|||
956e4c6fad779ef15eaa27702b26f05f65d31494 |
@ -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"] |
@ -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);
|
@ -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,
|
|||
}
|
|||
}
|
@ -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)
|
|||
}
|
|||
}
|
@ -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)
|
|||
}
|
|||
}
|
|||
}
|
@ -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
|
|||
}
|
@ -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;
|
@ -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
|
|||
}
|
|||
}
|
@ -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");
|
|||
}
|
|||
}
|
|||
}
|
@ -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 })
|
|||
}
|
|||
}
|
@ -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(())
|
|||
}
|
@ -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)
|
|||
}
|