Skip to content
Closed
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
122 changes: 122 additions & 0 deletions cometbft/src/merkle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,128 @@ where
}
}

/// Get the split point for merkle tree construction.
/// Returns the largest power of 2 less than n, matching CometBFT's getSplitPoint.
pub fn get_split_point(n: u64) -> u64 {
if n < 1 {
panic!("n must be >= 1");
}
let bit_len = 64 - n.leading_zeros();
let k = 1u64 << (bit_len - 1);

// If k equals n, then n is a power of 2, and we need k/2 to get a value LESS than n
if k == n {
k >> 1
} else {
k
}
}

/// Compute the merkle root hash from a leaf hash and its aunt hashes.
/// This implements CometBFT's computeHashFromAunts algorithm.
///
/// * `aunts` - The sibling hashes from leaf to root (bottom to top)
pub fn compute_hash_from_aunts<H: MerkleHash + Default>(
index: u64,
total: u64,
leaf_hash: Hash,
aunts: &[Hash],
) -> Option<Hash> {
if total == 0 {
return None;
}

if total == 1 {
if aunts.is_empty() {
return Some(leaf_hash);
}
return None;
}

if aunts.is_empty() {
return None;
}

let split = get_split_point(total);
let mut hasher = H::default();

if index < split {
// Leaf is in left subtree
let left_hash = compute_hash_from_aunts::<H>(
index,
split,
leaf_hash,
&aunts[..aunts.len() - 1],
)?;
let right_hash = aunts.last()?;
Some(hasher.inner_hash(left_hash, *right_hash))
} else {
// Leaf is in right subtree
let left_hash = aunts.last()?;
let right_hash = compute_hash_from_aunts::<H>(
index - split,
total - split,
leaf_hash,
&aunts[..aunts.len() - 1],
)?;
Some(hasher.inner_hash(*left_hash, right_hash))
}
}

/// Compute the merkle root from a slice of leaf hashes.
pub fn compute_root_from_leaf_hashes<H: MerkleHash + Default>(leaf_hashes: &[Hash]) -> Hash {
let mut hasher = H::default();
compute_root_recursive(&mut hasher, leaf_hashes)
}

fn compute_root_recursive<H: MerkleHash>(hasher: &mut H, hashes: &[Hash]) -> Hash {
match hashes.len() {
0 => hasher.empty_hash(),
1 => hashes[0],
n => {
let split = get_split_point(n as u64) as usize;
let left = compute_root_recursive(hasher, &hashes[..split]);
let right = compute_root_recursive(hasher, &hashes[split..]);
hasher.inner_hash(left, right)
},
}
}

/// Generate a merkle proof (aunt hashes) for a leaf at the given index.
/// Returns the sibling hashes from leaf to root (bottom to top).
pub fn generate_proof<H: MerkleHash + Default>(leaf_hashes: &[Hash], index: usize) -> Vec<Hash> {
let mut hasher = H::default();
generate_proof_recursive(&mut hasher, leaf_hashes, index)
}

fn generate_proof_recursive<H: MerkleHash>(
hasher: &mut H,
hashes: &[Hash],
index: usize,
) -> Vec<Hash> {
if hashes.len() <= 1 {
return Vec::new();
}

let split = get_split_point(hashes.len() as u64) as usize;

if index < split {
// Leaf is in left subtree/branch
let mut proof = generate_proof_recursive(hasher, &hashes[..split], index);
// Add right subtree root as aunt/sibling
let right_root = compute_root_recursive(hasher, &hashes[split..]);
proof.push(right_root);
proof
} else {
// Leaf is in right subtree/branch
let mut proof = generate_proof_recursive(hasher, &hashes[split..], index - split);
// Add left subtree root as aunt/sibling
let left_root = compute_root_recursive(hasher, &hashes[..split]);
proof.push(left_root);
proof
}
}

/// A wrapper for platform-provided host functions which can't do incremental
/// hashing. One unfortunate example of such platform is Polkadot.
pub struct NonIncremental<H>(PhantomData<H>);
Expand Down
39 changes: 39 additions & 0 deletions cometbft/src/merkle/proof.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Merkle proofs

use core::convert::TryInto;

use cometbft_proto::crypto::v1::Proof as RawProof;
use serde::{Deserialize, Serialize};

Expand All @@ -18,6 +20,43 @@ pub struct Proof {
pub aunts: Vec<Hash>,
}

impl Proof {
/// Create a new Proof
pub fn new(total: u64, index: u64, leaf_hash: Hash, aunts: Vec<Hash>) -> Self {
Self { total, index, leaf_hash, aunts }
}

/// Verify that this proof correctly proves inclusion in a merkle tree with the given root.
/// Uses the MerkleHash implementation for hashing.
pub fn verify<H: super::MerkleHash + Default>(&self, root_hash: Hash) -> bool {
match self.compute_root_hash::<H>() {
Some(computed) => computed == root_hash,
None => false,
}
}

/// Compute the root hash from this proof.
pub fn compute_root_hash<H: super::MerkleHash + Default>(&self) -> Option<Hash> {
let leaf_hash_bytes: [u8; 32] = self.leaf_hash.as_bytes().try_into().ok()?;
let aunts_bytes: Vec<[u8; 32]> = self.aunts.iter()
.filter_map(|h| h.as_bytes().try_into().ok())
.collect();

if aunts_bytes.len() != self.aunts.len() {
return None;
}

let computed = super::compute_hash_from_aunts::<H>(
self.index,
self.total,
leaf_hash_bytes,
&aunts_bytes,
)?;

Some(Hash::Sha256(computed))
}
}

/// Merkle proof defined by the list of ProofOps
/// <https://github.com/cometbft/cometbft/blob/c8483531d8e756f7fbb812db1dd16d841cdf298a/crypto/merkle/merkle.proto#L26>
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
Expand Down