Skip to content
Draft
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
24 changes: 24 additions & 0 deletions crypto/crypto/src/merkle_tree/backends/field_element_vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,30 @@ where
}
}

impl<F, D, const NUM_BYTES: usize> FieldElementVectorBackend<F, D, NUM_BYTES>
where
F: IsField,
FieldElement<F>: AsBytes,
D: Digest,
[u8; NUM_BYTES]: From<Output<D>>,
{
/// Hash a sequence of borrowed field elements into a leaf node, identical
/// to `hash_data` over the same sequence — but without materializing a Vec.
pub fn hash_elements<'a, I>(elements: I) -> [u8; NUM_BYTES]
where
I: IntoIterator<Item = &'a FieldElement<F>>,
F: 'a,
{
let mut hasher = D::new();
for element in elements {
hasher.update(element.as_bytes());
}
let mut result_hash = [0u8; NUM_BYTES];
result_hash.copy_from_slice(&hasher.finalize());
result_hash
}
}

impl<F, D: Digest, const NUM_BYTES: usize> IsMerkleTreeBackend
for FieldElementVectorBackend<F, D, NUM_BYTES>
where
Expand Down
22 changes: 16 additions & 6 deletions crypto/crypto/src/merkle_tree/proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,35 @@ pub struct Proof<T: PartialEq + Eq> {
}

impl<T: PartialEq + Eq> Proof<T> {
/// Verifies a Merkle inclusion proof for the value contained at leaf index.
pub fn verify<B>(&self, root_hash: &B::Node, mut index: usize, value: &B::Data) -> bool
/// Verify inclusion when the caller already computed the leaf hash
/// (lets callers hash borrowed leaf data without materializing `B::Data`).
pub fn verify_hashed<B>(
&self,
root_hash: &B::Node,
mut index: usize,
mut hashed_value: B::Node,
) -> bool
where
B: IsMerkleTreeBackend<Node = T>,
{
let mut hashed_value = B::hash_data(value);

for sibling_node in self.merkle_path.iter() {
if index.is_multiple_of(2) {
hashed_value = B::hash_new_parent(&hashed_value, sibling_node);
} else {
hashed_value = B::hash_new_parent(sibling_node, &hashed_value);
}

index >>= 1;
}

root_hash == &hashed_value
}

/// Verifies a Merkle inclusion proof for the value contained at leaf index.
pub fn verify<B>(&self, root_hash: &B::Node, index: usize, value: &B::Data) -> bool
where
B: IsMerkleTreeBackend<Node = T>,
{
self.verify_hashed::<B>(root_hash, index, B::hash_data(value))
}
}

#[cfg(feature = "alloc")]
Expand Down
33 changes: 33 additions & 0 deletions crypto/crypto/src/tests/field_element_vector_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use sha3::{Keccak256, Keccak512, Sha3_256, Sha3_512};

use crate::merkle_tree::{
backends::field_element_vector::FieldElementVectorBackend, merkle::MerkleTree,
traits::IsMerkleTreeBackend,
};

type F = GoldilocksField;
Expand Down Expand Up @@ -120,3 +121,35 @@ fn hash_data_field_element_backend_works_with_sha2_512() {
&values[0]
));
}

#[test]
fn hash_elements_matches_hash_data_byte_for_byte() {
type Backend = FieldElementVectorBackend<F, Keccak256, 32>;

// Pseudo-random Vec generated from a simple LCG so the test is deterministic
// yet exercises a non-trivial sequence of field elements.
let mut state: u64 = 0x9E3779B97F4A7C15;
let v: Vec<FE> = (0..37)
.map(|_| {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
FE::from(state)
})
.collect();

let via_hash_data = Backend::hash_data(&v);
let via_hash_elements = Backend::hash_elements(v.iter());

assert_eq!(
via_hash_data, via_hash_elements,
"hash_elements must be byte-identical to hash_data over the same sequence"
);

// Empty sequence must also agree.
let empty: Vec<FE> = Vec::new();
assert_eq!(
Backend::hash_data(&empty),
Backend::hash_elements(empty.iter())
);
}
Loading
Loading