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 crate::bmt::{Hasher, constants::*, error::BmtError};
9use crate::error::Result;
10
11/// Construct a Keccak256 seeded with the prefix when one is present.
12///
13/// Mirrors the hasher's per-node prefixing so that every node in a proof path is
14/// `keccak(prefix || data)`, byte-identical to bee's prefix BMT.
15#[inline(always)]
16fn new_node_hasher(prefix: Option<&[u8]>) -> Keccak256 {
17    let mut hasher = Keccak256::new();
18    if let Some(p) = prefix {
19        hasher.update(p);
20    }
21    hasher
22}
23
24/// Compute the prefix-aware zero subtree hash for `level`.
25///
26/// `level` 0 is `keccak(prefix || 64 zero bytes)`; each higher level doubles the
27/// previous. With no prefix this equals the plain zero tree used by the hasher.
28fn prefixed_zero_hash(prefix: Option<&[u8]>, level: usize) -> B256 {
29    let mut hash = {
30        let mut hasher = new_node_hasher(prefix);
31        hasher.update([0u8; 2 * SEGMENT_SIZE]);
32        B256::from_slice(hasher.finalize().as_slice())
33    };
34    for _ in 0..level {
35        let mut hasher = new_node_hasher(prefix);
36        hasher.update(hash.as_slice());
37        hasher.update(hash.as_slice());
38        hash = B256::from_slice(hasher.finalize().as_slice());
39    }
40    hash
41}
42
43/// Represents a proof for a specific segment in a Binary Merkle Tree
44#[derive(Clone, Debug)]
45pub struct Proof {
46    /// The segment index this proof is for
47    pub segment_index: usize,
48    /// The segment data being proven
49    pub segment: B256,
50    /// The proof segments (sibling hashes in the path to the root)
51    pub proof_segments: Vec<B256>,
52    /// The span of the data
53    pub span: u64,
54    /// Optional prefix (used during verification)
55    pub prefix: Option<Vec<u8>>,
56}
57
58impl Proof {
59    /// Create a new BMT proof
60    pub const fn new(
61        segment_index: usize,
62        segment: B256,
63        proof_segments: Vec<B256>,
64        span: u64,
65        prefix: Option<Vec<u8>>,
66    ) -> Self {
67        Self {
68            segment_index,
69            segment,
70            proof_segments,
71            span,
72            prefix,
73        }
74    }
75
76    /// Verify this proof against a root hash
77    pub fn verify(&self, root_hash: &[u8]) -> Result<bool> {
78        if self.proof_segments.len() != PROOF_LENGTH {
79            return Err(
80                BmtError::invalid_proof_length(PROOF_LENGTH, self.proof_segments.len()).into(),
81            );
82        }
83
84        // Start with the segment being proven
85        let mut current_hash = self.segment;
86        let mut current_index = self.segment_index;
87
88        let prefix = self.prefix.as_deref();
89
90        // Apply each proof segment to compute the root
91        for proof_segment in &self.proof_segments {
92            // Every intermediate node is keccak(prefix || left || right) to match
93            // bee's per-node prefix hasher; verifying without the prefix at each
94            // level would reject a valid anchor-keyed proof on-chain.
95            let mut hasher = new_node_hasher(prefix);
96
97            // Order matters - left then right
98            if current_index.is_multiple_of(2) {
99                hasher.update(current_hash.as_slice());
100                hasher.update(proof_segment.as_slice());
101            } else {
102                hasher.update(proof_segment.as_slice());
103                hasher.update(current_hash.as_slice());
104            }
105
106            // Get hash for next level
107            current_hash = B256::from_slice(hasher.finalize().as_slice());
108            current_index /= 2;
109        }
110
111        // Final step: add prefix (if any) and span to compute the root hash
112        let mut hasher = Keccak256::new();
113
114        // Add prefix if present
115        if let Some(prefix) = &self.prefix {
116            hasher.update(prefix);
117        }
118
119        // Add span as little-endian bytes
120        hasher.update(self.span.to_le_bytes());
121
122        // Add the intermediate hash
123        hasher.update(current_hash.as_slice());
124
125        let computed_root = B256::from_slice(hasher.finalize().as_slice());
126
127        // Compare with provided root hash
128        Ok(computed_root.as_slice() == root_hash)
129    }
130}
131
132/// Extension trait to add proof-related functionality to BMTHasher
133pub trait Prover {
134    /// Generate a proof for a specific segment
135    fn generate_proof(&self, data: &[u8], segment_index: usize) -> Result<Proof>;
136
137    /// Verify a proof against a root hash
138    fn verify_proof(proof: &Proof, root_hash: &[u8]) -> Result<bool>;
139}
140
141impl Prover for Hasher {
142    fn generate_proof(&self, data: &[u8], segment_index: usize) -> Result<Proof> {
143        if segment_index >= BRANCHES {
144            return Err(self::BmtError::invalid_input_size(format!(
145                "Segment index {segment_index} out of bounds for BRANCHES"
146            ))
147            .into());
148        }
149
150        // Create segments from data, padding with zeros if needed
151        let data_len = data.len();
152
153        // Use platform-specific optimizations for segment generation
154        #[cfg(not(target_arch = "wasm32"))]
155        let segments = {
156            use rayon::prelude::*;
157            (0..BRANCHES)
158                .into_par_iter()
159                .map(|i| {
160                    let start = i * SEGMENT_SIZE;
161                    let mut segment = [0u8; SEGMENT_SIZE];
162
163                    if start < data_len {
164                        let end = (start + SEGMENT_SIZE).min(data_len);
165                        let copy_len = end - start;
166                        segment[..copy_len].copy_from_slice(&data[start..end]);
167                    }
168
169                    B256::from_slice(&segment)
170                })
171                .collect::<Vec<_>>()
172        };
173
174        #[cfg(target_arch = "wasm32")]
175        let segments = {
176            let mut segs = Vec::with_capacity(BRANCHES);
177            for i in 0..BRANCHES {
178                let start = i * SEGMENT_SIZE;
179                let mut segment = [0u8; SEGMENT_SIZE];
180
181                if start < data_len {
182                    let end = (start + SEGMENT_SIZE).min(data_len);
183                    let copy_len = end - start;
184                    segment[..copy_len].copy_from_slice(&data[start..end]);
185                }
186
187                segs.push(B256::from_slice(&segment));
188            }
189
190            segs
191        };
192
193        // Get the segment being proven
194        let segment = segments[segment_index];
195
196        // Include the prefix in the proof if there is one
197        let prefix = if self.prefix().is_empty() {
198            None
199        } else {
200            Some(self.prefix().to_vec())
201        };
202        let prefix_ref = prefix.as_deref();
203
204        // Generate proof segments
205        let mut proof_segments = Vec::with_capacity(PROOF_LENGTH);
206
207        // Build the Merkle tree level by level
208        let mut current_level = segments;
209        let mut current_index = segment_index;
210        // Tree level of the nodes in `current_level`: 0 = raw leaf segments,
211        // 1 = leaf-pair hashes, etc. Used to pick the right prefix-aware zero
212        // hash when padding an odd/short level.
213        let mut level: usize = 0;
214
215        // Continue until we reach the root (or until we have BMT_PROOF_LENGTH segments)
216        while proof_segments.len() < PROOF_LENGTH {
217            // Get sibling's index
218            let sibling_index = if current_index.is_multiple_of(2) {
219                current_index + 1
220            } else {
221                current_index - 1
222            };
223
224            // Add sibling to proof. A missing sibling is an all-zero subtree at
225            // this level, which under a prefix hashes differently from B256::ZERO.
226            if sibling_index < current_level.len() {
227                proof_segments.push(current_level[sibling_index]);
228            } else if level == 0 {
229                // Missing raw leaf segment is 32 zero bytes (not a hash).
230                proof_segments.push(B256::ZERO);
231            } else {
232                proof_segments.push(prefixed_zero_hash(prefix_ref, level - 1));
233            }
234
235            // Compute the next level up in the tree
236            let mut next_level = Vec::with_capacity(current_level.len().div_ceil(2));
237
238            for i in (0..current_level.len()).step_by(2) {
239                let left = &current_level[i];
240                let right = if i + 1 < current_level.len() {
241                    current_level[i + 1]
242                } else if level == 0 {
243                    B256::ZERO
244                } else {
245                    prefixed_zero_hash(prefix_ref, level - 1)
246                };
247
248                // Hash the pair to create the parent node (prefix-aware)
249                let mut hasher = new_node_hasher(prefix_ref);
250                hasher.update(left.as_slice());
251                hasher.update(right.as_slice());
252
253                let parent = B256::from_slice(hasher.finalize().as_slice());
254                next_level.push(parent);
255            }
256
257            // Move up to the next level
258            current_level = next_level;
259            current_index /= 2;
260            level += 1;
261
262            // If we've reached the root or have only one node, break
263            if current_level.len() <= 1 {
264                break;
265            }
266        }
267
268        // Ensure we have exactly BMT_PROOF_LENGTH segments in our proof
269        while proof_segments.len() < PROOF_LENGTH {
270            proof_segments.push(B256::ZERO);
271        }
272
273        Ok(Proof::new(
274            segment_index,
275            segment,
276            proof_segments,
277            self.span(),
278            prefix,
279        ))
280    }
281
282    fn verify_proof(proof: &Proof, root_hash: &[u8]) -> Result<bool> {
283        proof.verify(root_hash)
284    }
285}