Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ ethereum_serde_utils = "0.8.0"
ethereum_ssz = "0.9.0"
serde = "1.0.0"
serde_derive = "1.0.0"
typenum = "1.12.0"
typenum = { version = "1.12.0", features = ["const-generics"] }
smallvec = "1.8.0"
arbitrary = { version = "1.0", features = ["derive"], optional = true }
itertools = "0.13.0"

[dev-dependencies]
serde_json = "1.0.0"
tree_hash_derive = "0.10.0"

[patch.crates-io]
tree_hash = { path = "../tree_hash/tree_hash" }
tree_hash_derive = { path = "../tree_hash/tree_hash_derive" }
78 changes: 78 additions & 0 deletions src/fixed_vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,14 @@ where
}
}

impl<T, N: Unsigned> tree_hash::prototype::MerkleProof for FixedVector<T, N>
where
T: tree_hash::TreeHash
Comment on lines +214 to +215
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
where
T: tree_hash::TreeHash
where
T: tree_hash::prototype::MerkleProof

Ideally we want to be able to combine proofs in a nested way. E.g. List<List<u64, U10>, U20> should work.

{
fn compute_proof_for_gindex(&self, gindex: usize) -> Result<Vec<Hash256>, tree_hash::prototype::Error>{
crate::tree_hash::generate_proof_for_vec::<T, N>(&self.vec, gindex)
}
}
impl<T, N: Unsigned> ssz::Encode for FixedVector<T, N>
where
T: ssz::Encode,
Expand Down Expand Up @@ -569,4 +577,74 @@ mod test {
let result: Result<FixedVector<u64, U4>, _> = serde_json::from_value(json);
assert!(result.is_ok());
}

#[test]
fn merkle_proof_basic() {
use tree_hash::prototype::MerkleProof;
use typenum::U4;

let vec: FixedVector<u64, U4> = FixedVector::new(vec![1, 2, 3, 4]).unwrap();

let proof = vec.compute_proof_for_gindex(1);
assert!(proof.is_ok());
if let Ok(proof) = proof {
assert_eq!(proof.len(), 0);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the proof should probably be length 1 here, and be exactly equal to the tree hash root?

Copy link
Member

@hopinheimer hopinheimer Jul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was assuming that no one would ever request for a proof for the root. it's just a small change I can add that. do you think we should add it?

}

let proof = vec.compute_proof_for_gindex(2);
assert!(proof.is_ok());
if let Ok(proof) = proof {
assert!(!proof.is_empty());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could assert something more detailed here:

  • First element of the proof should be the tree_hash_root of the full tree
  • Second element should be the length

Alternatively we should write a function to check a merkle proof given a gindex, a leaf and a root

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could probably use the merkle_proof library from Lighthouse for this actually

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(would have to put these tests in Lighthouse, until LEAP happens and all these libraries are co-located in one repo)

}

let proof = vec.compute_proof_for_gindex(0);
assert!(proof.is_err());
}

#[test]
fn merkle_proof_complex_types() {
use tree_hash::prototype::MerkleProof;
use typenum::U2;

let a1 = A { a: 1, b: 2 };
let a2 = A { a: 3, b: 4 };
let vec: FixedVector<A, U2> = FixedVector::new(vec![a1, a2]).unwrap();

let proof = vec.compute_proof_for_gindex(2);
assert!(proof.is_ok());
if let Ok(proof) = proof {
assert!(!proof.is_empty());

for hash in proof {
assert_eq!(hash.len(), 32);
}
}
}

#[test]
fn merkle_proof_tree_depth() {
use tree_hash::prototype::MerkleProof;
use typenum::U8;

let vec: FixedVector<u64, U8> = FixedVector::new(vec![1, 2, 3, 4, 5, 6, 7, 8]).unwrap();

let gindices = vec![1, 2, 3, 4, 5, 6, 7, 8, 15, 16];

for gindex in gindices {
let proof = vec.compute_proof_for_gindex(gindex);
Comment on lines +633 to +634
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could iterate through VecIndex<I, N> here instead, to check we can do proofs for all leaves


if gindex == 0 {
assert!(proof.is_err());
} else if gindex == 1 {
if let Ok(proof) = proof {
assert_eq!(proof.len(), 0);
}
} else {
if let Ok(proof) = proof {
let expected_depth = 64 - gindex.leading_zeros() as usize - 1;
assert_eq!(proof.len(), expected_depth);
}
}
}
}
}
Loading
Loading