Skip to main content

nectar_primitives/bmt/
proof.rs

1//! Proof-related traits and structures for the Binary Merkle Tree
2//!
3//! This module provides functionality for generating and verifying inclusion proofs
4//! for specific segments within a binary merkle tree.
5
6use alloy_primitives::{B256, Keccak256};
7
8use super::hasher::hash_pairs;
9use crate::bmt::{Hasher, constants::*, error::BmtError};
10use crate::error::Result;
11
12/// Construct a Keccak256 seeded with the prefix when one is present.
13///
14/// Mirrors the hasher's per-node prefixing so that every node in a proof path is
15/// `keccak(prefix || data)`, byte-identical to bee's prefix BMT.
16#[inline(always)]
17fn new_node_hasher(prefix: Option<&[u8]>) -> Keccak256 {
18    let mut hasher = Keccak256::new();
19    if let Some(p) = prefix {
20        hasher.update(p);
21    }
22    hasher
23}
24
25/// Represents a proof for a specific segment in a Binary Merkle Tree
26#[derive(Clone, Debug)]
27pub struct Proof {
28    /// The segment index this proof is for
29    pub segment_index: usize,
30    /// The segment data being proven
31    pub segment: B256,
32    /// The proof segments (sibling hashes in the path to the root)
33    pub proof_segments: Vec<B256>,
34    /// The span of the data
35    pub span: u64,
36    /// Optional prefix (used during verification)
37    pub prefix: Option<Vec<u8>>,
38}
39
40impl Proof {
41    /// Create a new BMT proof
42    pub const fn new(
43        segment_index: usize,
44        segment: B256,
45        proof_segments: Vec<B256>,
46        span: u64,
47        prefix: Option<Vec<u8>>,
48    ) -> Self {
49        Self {
50            segment_index,
51            segment,
52            proof_segments,
53            span,
54            prefix,
55        }
56    }
57
58    /// Verify this proof against a root hash
59    pub fn verify(&self, root_hash: &[u8]) -> Result<bool> {
60        if self.proof_segments.len() != PROOF_LENGTH {
61            return Err(
62                BmtError::invalid_proof_length(PROOF_LENGTH, self.proof_segments.len()).into(),
63            );
64        }
65
66        // Start with the segment being proven
67        let mut current_hash = self.segment;
68        let mut current_index = self.segment_index;
69
70        let prefix = self.prefix.as_deref();
71
72        // Apply each proof segment to compute the root
73        for proof_segment in &self.proof_segments {
74            // Every intermediate node is keccak(prefix || left || right) to match
75            // bee's per-node prefix hasher; verifying without the prefix at each
76            // level would reject a valid anchor-keyed proof on-chain.
77            let mut hasher = new_node_hasher(prefix);
78
79            // Order matters - left then right
80            if current_index.is_multiple_of(2) {
81                hasher.update(current_hash.as_slice());
82                hasher.update(proof_segment.as_slice());
83            } else {
84                hasher.update(proof_segment.as_slice());
85                hasher.update(current_hash.as_slice());
86            }
87
88            // Get hash for next level
89            current_hash = B256::from_slice(hasher.finalize().as_slice());
90            current_index /= 2;
91        }
92
93        // Final step: add prefix (if any) and span to compute the root hash
94        let mut hasher = Keccak256::new();
95
96        // Add prefix if present
97        if let Some(prefix) = &self.prefix {
98            hasher.update(prefix);
99        }
100
101        // Add span as little-endian bytes
102        hasher.update(self.span.to_le_bytes());
103
104        // Add the intermediate hash
105        hasher.update(current_hash.as_slice());
106
107        let computed_root = B256::from_slice(hasher.finalize().as_slice());
108
109        // Compare with provided root hash
110        Ok(computed_root.as_slice() == root_hash)
111    }
112}
113
114/// Extension trait to add proof-related functionality to BMTHasher
115pub trait Prover {
116    /// Generate a proof for a specific segment
117    fn generate_proof(&self, data: &[u8], segment_index: usize) -> Result<Proof>;
118
119    /// Verify a proof against a root hash
120    fn verify_proof(proof: &Proof, root_hash: &[u8]) -> Result<bool>;
121}
122
123impl Prover for Hasher {
124    fn generate_proof(&self, data: &[u8], segment_index: usize) -> Result<Proof> {
125        if segment_index >= BRANCHES {
126            return Err(self::BmtError::invalid_input_size(format!(
127                "Segment index {segment_index} out of bounds for BRANCHES"
128            ))
129            .into());
130        }
131
132        // Materialise the BRANCHES zero-padded 32-byte leaf segments.
133        let mut leaves = [[0u8; SEGMENT_SIZE]; BRANCHES];
134        let n = data.len().min(BRANCHES * SEGMENT_SIZE);
135        for (leaf, chunk) in leaves.iter_mut().zip(data[..n].chunks(SEGMENT_SIZE)) {
136            leaf[..chunk.len()].copy_from_slice(chunk);
137        }
138
139        // Get the segment being proven
140        let segment = B256::from(leaves[segment_index]);
141
142        // Include the prefix in the proof if there is one
143        let prefix = if self.prefix().is_empty() {
144            None
145        } else {
146            Some(self.prefix().to_vec())
147        };
148        let prefix_ref = prefix.as_deref();
149
150        // Build every tree level below the root, batching each level's sibling
151        // pairs across SIMD lanes. Zero padding is hashed literally, so under a
152        // prefix every zero subtree comes out as keccak(prefix || ...).
153        let mut levels: Vec<Vec<[u8; 32]>> = Vec::with_capacity(PROOF_LENGTH);
154        let mut current = leaves.to_vec();
155        while current.len() > 1 {
156            let mut next = vec![[0u8; 32]; current.len() / 2];
157            hash_pairs(prefix_ref, current.as_flattened(), &mut next);
158            levels.push(current);
159            current = next;
160        }
161
162        // The proof is the sibling of the proven node at every level.
163        let mut proof_segments = Vec::with_capacity(PROOF_LENGTH);
164        let mut index = segment_index;
165        for level in &levels {
166            proof_segments.push(B256::from(level[index ^ 1]));
167            index /= 2;
168        }
169
170        Ok(Proof::new(
171            segment_index,
172            segment,
173            proof_segments,
174            self.span(),
175            prefix,
176        ))
177    }
178
179    fn verify_proof(proof: &Proof, root_hash: &[u8]) -> Result<bool> {
180        proof.verify(root_hash)
181    }
182}