1use std::marker::PhantomData;
2
3use alloc::vec::Vec;
4use thiserror::Error;
5use warg_crypto::{
6 hash::{Hash, SupportedDigest},
7 VisitBytes,
8};
9
10use super::{hash_branch, hash_leaf, node::Node, LogData};
11
12#[derive(Debug, Clone, PartialEq)]
14pub struct InclusionProof<D: SupportedDigest, V: VisitBytes> {
15 leaf: Node,
17 log_length: usize,
19 _digest: PhantomData<D>,
21 _value: PhantomData<V>,
23}
24
25#[derive(Error, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
27pub enum InclusionProofError {
28 #[error("leaf newer than when it should be included")]
31 LeafTooNew,
32 #[error("required hash for proof is not available")]
35 HashNotKnown,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct InclusionProofWalk {
53 pub(crate) nodes: Vec<Node>,
54 initial_walk_len: usize,
55 lower_broots: usize,
56 upper_broots: usize,
57}
58
59impl InclusionProofWalk {
60 fn initial_walk_end(&self) -> usize {
61 self.initial_walk_len
62 }
63
64 fn lower_broot_walk_end(&self) -> usize {
65 self.initial_walk_end() + self.lower_broots
66 }
67
68 fn upper_broot_walk_end(&self) -> usize {
69 self.lower_broot_walk_end() + self.upper_broots
70 }
71
72 fn initial_walk(&self) -> &[Node] {
73 &self.nodes[..self.initial_walk_end()]
74 }
75
76 fn lower_broot_walk(&self) -> &[Node] {
77 &self.nodes[self.initial_walk_end()..self.lower_broot_walk_end()]
78 }
79
80 fn upper_broot_walk(&self) -> &[Node] {
81 &self.nodes[self.lower_broot_walk_end()..self.upper_broot_walk_end()]
82 }
83}
84
85impl<D, V> InclusionProof<D, V>
86where
87 D: SupportedDigest,
88 V: VisitBytes,
89{
90 pub(crate) fn new(leaf: Node, log_length: usize) -> Self {
91 Self {
92 leaf,
93 log_length,
94 _digest: PhantomData,
95 _value: PhantomData,
96 }
97 }
98
99 pub fn leaf(&self) -> Node {
101 self.leaf
102 }
103
104 pub fn log_length(&self) -> usize {
106 self.log_length
107 }
108
109 pub fn walk(&self) -> Result<InclusionProofWalk, InclusionProofError> {
112 let length = self.log_length;
113 let broots = Node::broots_for_len(length);
114 let mut current_node = self.leaf;
115
116 if !current_node.exists_at_length(length) {
117 return Err(InclusionProofError::LeafTooNew);
118 }
119
120 let mut nodes = Vec::new();
121
122 while !broots.contains(¤t_node) {
124 let sibling = current_node.sibling();
125 nodes.push(sibling);
126 current_node = current_node.parent();
127 }
128
129 let initial_walk_len = nodes.len();
130
131 let index = broots
132 .iter()
133 .position(|broot| *broot == current_node)
134 .unwrap();
135
136 let lower_broots = broots.len() - index - 1;
137 for broot in broots[index + 1..].iter().rev() {
138 nodes.push(*broot);
139 }
140
141 let upper_broots = index;
142 for broot in broots[..index].iter().rev() {
143 nodes.push(*broot);
144 }
145
146 Ok(InclusionProofWalk {
147 nodes,
148 initial_walk_len,
149 lower_broots,
150 upper_broots,
151 })
152 }
153
154 pub fn evaluate_value(
159 &self,
160 hashes: &impl LogData<D, V>,
161 value: &V,
162 ) -> Result<Hash<D>, InclusionProofError> {
163 self.evaluate_hash(hashes, hash_leaf(value))
164 }
165
166 pub fn evaluate_hash(
171 &self,
172 hashes: &impl LogData<D, V>,
173 hash: Hash<D>,
174 ) -> Result<Hash<D>, InclusionProofError> {
175 let leaf = (self.leaf, hash);
176 let walk = self.walk()?;
177
178 if walk.nodes.iter().any(|node| !hashes.has_hash(*node)) {
180 return Err(InclusionProofError::HashNotKnown);
181 }
182
183 let current = walk
185 .initial_walk()
186 .iter()
187 .map(|node| (*node, hashes.hash_for(*node).unwrap()))
188 .fold(leaf, combine);
189
190 let lower_broot = walk
192 .lower_broot_walk()
193 .iter()
194 .map(|node| (*node, hashes.hash_for(*node).unwrap()))
195 .reduce(combine);
196
197 let current = match lower_broot {
199 Some(lower_broot) => combine(current, lower_broot),
200 None => current,
201 };
202
203 let current = walk
205 .upper_broot_walk()
206 .iter()
207 .map(|node| (*node, hashes.hash_for(*node).unwrap()))
208 .fold(current, combine);
209
210 Ok(current.1)
211 }
212}
213
214fn combine<D: SupportedDigest>(first: (Node, Hash<D>), second: (Node, Hash<D>)) -> (Node, Hash<D>) {
215 let (lhs, rhs) = if first.0.index() < second.0.index() {
216 (first.1, second.1)
217 } else {
218 (second.1, first.1)
219 };
220
221 (second.0, hash_branch::<D>(lhs, rhs))
222}
223
224#[derive(Debug, Clone, Copy, PartialEq)]
227pub struct ConsistencyProof<D, V>
228where
229 D: SupportedDigest,
230 V: VisitBytes,
231{
232 pub old_length: usize,
234 pub new_length: usize,
236 _digest: PhantomData<D>,
238 _value: PhantomData<V>,
240}
241
242#[derive(Error, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
244pub enum ConsistencyProofError {
245 #[error("tries to prove later value comes before earlier")]
247 PointsOutOfOrder,
248 #[error("a hash needed for evaluation was not available")]
250 HashNotKnown,
251 #[error("constituent inclusion proof failed")]
253 InclusionError(#[from] InclusionProofError),
254 #[error("constituent inclusion proofs diverge produce different roots")]
256 DivergingRoots,
257}
258
259impl<D, V> ConsistencyProof<D, V>
260where
261 D: SupportedDigest,
262 V: VisitBytes,
263{
264 pub(crate) fn new(old_length: usize, new_length: usize) -> Self {
265 Self {
266 old_length,
267 new_length,
268 _digest: PhantomData,
269 _value: PhantomData,
270 }
271 }
272
273 pub fn evaluate(
278 &self,
279 hashes: &impl LogData<D, V>,
280 ) -> Result<(Hash<D>, Hash<D>), ConsistencyProofError> {
281 let mut old_broots = Vec::new();
282 let mut new_root = None;
283
284 for inc_proof in self.inclusions().unwrap() {
285 let leaf_hash = hashes
286 .hash_for(inc_proof.leaf())
287 .ok_or(ConsistencyProofError::HashNotKnown)?;
288 old_broots.push(leaf_hash.clone());
289 let found_root = inc_proof.evaluate_hash(hashes, leaf_hash)?;
290 if let Some(previous_root) = &new_root {
291 if previous_root != &found_root {
292 return Err(ConsistencyProofError::DivergingRoots);
293 }
294 } else {
295 new_root = Some(found_root);
296 }
297 }
298
299 let old_root = old_broots
300 .into_iter()
301 .rev()
302 .reduce(|new, old| hash_branch(old, new));
303 let old_root = old_root.unwrap();
305 let new_root = new_root.unwrap();
306 Ok((old_root, new_root))
307 }
308
309 pub fn inclusions(&self) -> Result<Vec<InclusionProof<D, V>>, ConsistencyProofError> {
313 if self.old_length > self.new_length {
314 return Err(ConsistencyProofError::PointsOutOfOrder);
315 }
316
317 let inclusions = Node::broots_for_len(self.old_length)
318 .into_iter()
319 .map(|broot| InclusionProof::new(broot, self.new_length))
320 .collect();
321
322 Ok(inclusions)
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use crate::log::{LogBuilder, VecLog};
329
330 use super::*;
331
332 use warg_crypto::hash::Sha256;
333
334 #[test]
335 fn test_inc_even_2() {
336 let mut log: VecLog<Sha256, u8> = VecLog::default();
337
338 log.push(&100);
339 log.push(&102);
340
341 let inc_proof = InclusionProof::new(Node(0), 2);
342 let expected = InclusionProofWalk {
343 nodes: vec![Node(2)],
344 initial_walk_len: 1,
345 lower_broots: 0,
346 upper_broots: 0,
347 };
348 assert_eq!(inc_proof.walk().unwrap(), expected);
349
350 assert_eq!(
351 inc_proof.evaluate_value(&log, &100).unwrap(),
352 log.as_ref()[1].clone()
353 );
354 }
355
356 #[test]
357 fn test_inc_odd_3() {
358 let mut log: VecLog<Sha256, u8> = VecLog::default();
359
360 log.push(&100);
361 log.push(&102);
362 log.push(&104);
363
364 let root: Hash<Sha256> = hash_branch(log.as_ref()[1].clone(), log.as_ref()[4].clone());
365
366 let inc_proof = InclusionProof::new(Node(0), 3);
368 let expected = InclusionProofWalk {
369 nodes: vec![Node(2), Node(4)],
370 initial_walk_len: 1,
371 lower_broots: 1,
372 upper_broots: 0,
373 };
374 assert_eq!(inc_proof.walk().unwrap(), expected);
375 assert_eq!(inc_proof.evaluate_value(&log, &100).unwrap(), root);
376
377 let inc_proof = InclusionProof::new(Node(2), 3);
379 let expected = InclusionProofWalk {
380 nodes: vec![Node(0), Node(4)],
381 initial_walk_len: 1,
382 lower_broots: 1,
383 upper_broots: 0,
384 };
385 assert_eq!(inc_proof.walk().unwrap(), expected);
386 assert_eq!(inc_proof.evaluate_value(&log, &102u8).unwrap(), root);
387
388 let inc_proof = InclusionProof::new(Node(4), 3);
390 let expected = InclusionProofWalk {
391 nodes: vec![Node(1)],
392 initial_walk_len: 0,
393 lower_broots: 0,
394 upper_broots: 1,
395 };
396 assert_eq!(inc_proof.walk().unwrap(), expected);
397 assert_eq!(inc_proof.evaluate_value(&log, &104u8).unwrap(), root);
398 }
399
400 #[test]
401 fn test_inc_odd_7() {
402 let mut log: VecLog<Sha256, u8> = VecLog::default();
403
404 log.push(&100);
405 log.push(&102);
406 log.push(&104);
407 log.push(&106);
408 log.push(&108);
409 log.push(&110);
410 log.push(&112);
411
412 let artificial_branch: Hash<Sha256> =
413 hash_branch(log.as_ref()[9].clone(), log.as_ref()[12].clone());
414 let root: Hash<Sha256> = hash_branch(log.as_ref()[3].clone(), artificial_branch);
415
416 let inc_proof = InclusionProof::new(Node(6), 7);
418 let expected = InclusionProofWalk {
419 nodes: vec![Node(4), Node(1), Node(12), Node(9)],
420 initial_walk_len: 2,
421 lower_broots: 2,
422 upper_broots: 0,
423 };
424 assert_eq!(inc_proof.walk().unwrap(), expected);
425 assert_eq!(inc_proof.evaluate_value(&log, &106).unwrap(), root);
426 }
427}