Skip to content

Commit f94ba61

Browse files
committed
feat: generic tree
1 parent 9b5f404 commit f94ba61

File tree

6 files changed

+749
-5
lines changed

6 files changed

+749
-5
lines changed

Diff for: Cargo.lock

+53-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: crates/lean-imt/Cargo.toml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "zk-kit-lean-imt"
3+
version = "0.1.0"
4+
description = "Lean Incremental Merkle Tree"
5+
edition.workspace = true
6+
license.workspace = true
7+
publish.workspace = true
8+
9+
[dependencies]
10+
serde = { version = "1.0.219", features = ["derive"] }
11+
thiserror = "2.0.12"

Diff for: crates/lean-imt/README.md

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<p align="center">
2+
<h1 align="center">
3+
LeanIMT
4+
</h1>
5+
<p align="center">Lean Incremental Merkle Tree implementation in Rust.</p>
6+
</p>
7+
8+
<p align="center">
9+
<a href="https://github.com/privacy-scaling-explorations/zk-kit">
10+
<img src="https://img.shields.io/badge/project-zk--kit-blue.svg?style=flat-square">
11+
</a>
12+
<a href="https://github.com/privacy-scaling-explorations/zk-kit.rust/tree/main/LICENSE">
13+
<img alt="License" src="https://img.shields.io/badge/license-MIT-blue.svg">
14+
</a>
15+
</p>
16+
17+
<div align="center">
18+
<h4>
19+
<a href="https://appliedzkp.org/discord">
20+
🗣️ Chat &amp; Support
21+
</a>
22+
</h4>
23+
</div>
24+
25+
The LeanIMT is an optimized binary version of the [IMT](https://github.com/privacy-scaling-explorations/zk-kit/tree/main/packages/imt) into binary-focused model, eliminating the need for zero values and allowing dynamic depth adjustment. Unlike the IMT, which uses a zero hash for incomplete nodes, the LeanIMT directly adopts the left child's value when a node lacks a right counterpart. The tree's depth dynamically adjusts to the count of leaves, enhancing efficiency by reducing the number of required hash calculations. To understand more about the LeanIMT, take a look at this [visual explanation](https://hackmd.io/@vplasencia/S1whLBN16). For detailed insights into the implementation specifics, please refer to the [LeanIMT paper](https://github.com/privacy-scaling-explorations/zk-kit/tree/main/papers/leanimt).
26+
27+
---
28+
29+
## 🛠 Install
30+
31+
Add the `zk-kit-lean-imt` crate to your `cargo.toml`:
32+
33+
```toml
34+
zk-kit-lean-imt = { git = "https://github.com/privacy-scaling-explorations/zk-kit.rust", package = "zk-kit-lean-imt" }
35+
```
36+
37+
## 📜 Usage
38+
39+
```rust
40+
use zk_kit_lean_imt::hashed_tree::{LeanIMTHasher, HashedLeanIMT};
41+
42+
// Setup hasher
43+
struct SampleHasher;
44+
45+
impl LeanIMTHasher for SampleHasher {
46+
fn hash(input: &[u8]) -> Vec<u8> {
47+
let mut hasher = DefaultHasher::new();
48+
for byte in input {
49+
byte.hash(&mut hasher);
50+
}
51+
let hash = hasher.finish();
52+
53+
let mut result = vec![0u8; 32];
54+
result[..8].copy_from_slice(&hash.to_le_bytes());
55+
result
56+
}
57+
}
58+
59+
fn main() {
60+
// Create an empty tree
61+
let mut tree = HashedLeanIMT::new(&[], SampleHasher).unwrap();
62+
63+
// Insert a leaf
64+
tree.insert(&[1; 32]);
65+
66+
// Insert another leaf
67+
tree.insert(&[2; 32]);
68+
69+
// Get the root
70+
let root = tree.root().unwrap();
71+
println!("Tree root: {:?}", root);
72+
73+
// Get the tree depth
74+
let depth = tree.depth();
75+
println!("Tree depth: {}", depth);
76+
77+
// Get the leaves
78+
let leaves = tree.leaves();
79+
println!("Tree leaves: {:?}", leaves);
80+
81+
// Generate a proof for the leaf at index 1
82+
let proof = tree.generate_proof(1).unwrap();
83+
84+
// Verify the proof
85+
assert!(HashedLeanIMT::<SampleHasher>::verify_proof(&proof));
86+
}
87+
```

Diff for: crates/lean-imt/src/hashed_tree.rs

+183
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
//! # Hashed LeanIMT
2+
//!
3+
//! Lean Incremental Merkle Tree hashing function wrapper.
4+
5+
use crate::lean_imt::{LeanIMT, LeanIMTError, MerkleProof};
6+
use serde::{Deserialize, Serialize};
7+
8+
/// LeanIMT hasher trait.
9+
pub trait LeanIMTHasher {
10+
fn hash(input: &[u8]) -> Vec<u8>;
11+
}
12+
13+
/// Hashed LeanIMT struct.
14+
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
15+
pub struct HashedLeanIMT<H> {
16+
/// LeanIMT
17+
tree: LeanIMT,
18+
/// Hasher
19+
hasher: H,
20+
}
21+
22+
impl<H> HashedLeanIMT<H>
23+
where
24+
H: LeanIMTHasher,
25+
{
26+
/// Creates a new tree with optional initial leaves.
27+
pub fn new(leaves: &[Vec<u8>], hasher: H) -> Result<Self, LeanIMTError> {
28+
let imt = LeanIMT::new(leaves, H::hash)?;
29+
30+
Ok(Self { tree: imt, hasher })
31+
}
32+
33+
/// Returns the root, if it exists.
34+
pub fn root(&self) -> Option<Vec<u8>> {
35+
self.tree.root()
36+
}
37+
38+
/// Returns the tree depth.
39+
pub fn depth(&self) -> usize {
40+
self.tree.depth()
41+
}
42+
43+
/// Returns the leaves.
44+
pub fn leaves(&self) -> &[Vec<u8>] {
45+
self.tree.leaves()
46+
}
47+
48+
/// Returns the number of leaves.
49+
pub fn size(&self) -> usize {
50+
self.tree.size()
51+
}
52+
53+
/// Returns the index of a leaf, if it exists.
54+
pub fn index_of(&self, leaf: &[u8]) -> Option<usize> {
55+
self.tree.index_of(leaf)
56+
}
57+
58+
/// Checks if a leaf exists.
59+
pub fn contains(&self, leaf: &[u8]) -> bool {
60+
self.tree.contains(leaf)
61+
}
62+
63+
/// Returns the leaf at the given index.
64+
pub fn get_leaf(&self, index: usize) -> Option<Vec<u8>> {
65+
self.tree.get_leaf(index)
66+
}
67+
68+
/// Inserts a single leaf.
69+
pub fn insert(&mut self, leaf: &[u8]) {
70+
self.tree.insert(leaf, H::hash)
71+
}
72+
73+
/// Inserts multiple leaves.
74+
pub fn insert_many(&mut self, leaves: &[Vec<u8>]) -> Result<(), LeanIMTError> {
75+
self.tree.insert_many(leaves, H::hash)
76+
}
77+
78+
/// Updates a leaf at the given index.
79+
pub fn update(&mut self, index: usize, new_leaf: &[u8]) -> Result<(), LeanIMTError> {
80+
self.tree.update(index, new_leaf, H::hash)
81+
}
82+
83+
/// Generates a Merkle proof for a leaf at the given index.
84+
pub fn generate_proof(&self, index: usize) -> Result<MerkleProof, LeanIMTError> {
85+
self.tree.generate_proof(index)
86+
}
87+
88+
/// Verifies a Merkle proof.
89+
pub fn verify_proof(proof: &MerkleProof) -> bool {
90+
LeanIMT::verify_proof(proof, H::hash)
91+
}
92+
}
93+
94+
#[cfg(test)]
95+
mod tests {
96+
use super::{HashedLeanIMT, LeanIMTHasher};
97+
use std::collections::hash_map::DefaultHasher;
98+
use std::hash::{Hash, Hasher};
99+
100+
struct SampleHasher;
101+
102+
impl LeanIMTHasher for SampleHasher {
103+
fn hash(input: &[u8]) -> Vec<u8> {
104+
let mut hasher = DefaultHasher::new();
105+
for byte in input {
106+
byte.hash(&mut hasher);
107+
}
108+
let hash = hasher.finish();
109+
110+
let mut result = vec![0u8; 32];
111+
result[..8].copy_from_slice(&hash.to_le_bytes());
112+
result
113+
}
114+
}
115+
116+
#[test]
117+
fn test_new_empty_tree() {
118+
let tree = HashedLeanIMT::new(&[], SampleHasher).unwrap();
119+
assert_eq!(tree.size(), 0);
120+
assert_eq!(tree.root(), None);
121+
}
122+
123+
#[test]
124+
fn test_insert_leaves() {
125+
let mut tree = HashedLeanIMT::new(&[], SampleHasher).unwrap();
126+
127+
// Insert a single leaf
128+
tree.insert(&[1; 32]);
129+
assert_eq!(tree.size(), 1);
130+
assert!(tree.contains(&[1; 32]));
131+
132+
// Insert multiple leaves
133+
tree.insert_many(&[b"leaf2".to_vec(), b"leaf3".to_vec()])
134+
.unwrap();
135+
assert_eq!(tree.size(), 3);
136+
assert!(tree.contains(b"leaf2"));
137+
assert!(tree.contains(b"leaf3"));
138+
}
139+
140+
#[test]
141+
fn test_update_leaf() {
142+
let mut tree =
143+
HashedLeanIMT::new(&[b"leaf1".to_vec(), b"leaf2".to_vec()], SampleHasher).unwrap();
144+
145+
tree.update(0, b"updated_leaf").unwrap();
146+
assert!(!tree.contains(b"leaf1"));
147+
assert!(tree.contains(b"updated_leaf"));
148+
149+
assert!(tree.contains(b"leaf2"));
150+
}
151+
152+
#[test]
153+
fn test_merkle_proof() {
154+
let leaves = vec![
155+
b"leaf1".to_vec(),
156+
b"leaf2".to_vec(),
157+
b"leaf3".to_vec(),
158+
b"leaf4".to_vec(),
159+
];
160+
161+
let tree = HashedLeanIMT::new(&leaves, SampleHasher).unwrap();
162+
163+
let proof = tree.generate_proof(1).unwrap();
164+
165+
assert!(HashedLeanIMT::<SampleHasher>::verify_proof(&proof));
166+
167+
assert_eq!(proof.index, 1);
168+
assert_eq!(&proof.leaf, b"leaf2");
169+
}
170+
171+
#[test]
172+
fn test_index_of_and_get_leaf() {
173+
let leaves = vec![b"leaf1".to_vec(), b"leaf2".to_vec(), b"leaf3".to_vec()];
174+
175+
let tree = HashedLeanIMT::new(&leaves, SampleHasher).unwrap();
176+
177+
assert_eq!(tree.index_of(b"leaf2"), Some(1));
178+
assert_eq!(tree.index_of(b"nonexistent"), None);
179+
180+
assert_eq!(tree.get_leaf(1), Some(b"leaf2".to_vec()));
181+
assert_eq!(tree.get_leaf(5), None);
182+
}
183+
}

0 commit comments

Comments
 (0)