1use crate::mmr::{
2 proof, verification, verification::ProofStore, Error, Location, Position, Proof,
3 StandardHasher as Standard,
4};
5use commonware_codec::Encode;
6use commonware_cryptography::{Digest, Hasher};
7use core::ops::Range;
8
9pub fn verify_proof<Op, H, D>(
11 hasher: &mut Standard<H>,
12 proof: &Proof<D>,
13 start_loc: Location,
14 operations: &[Op],
15 target_root: &D,
16) -> bool
17where
18 Op: Encode,
19 H: Hasher<Digest = D>,
20 D: Digest,
21{
22 let elements = operations.iter().map(|op| op.encode()).collect::<Vec<_>>();
23 proof.verify_range_inclusion(hasher, &elements, start_loc, target_root)
24}
25
26pub fn extract_pinned_nodes<D: Digest>(
32 proof: &Proof<D>,
33 start_loc: Location,
34 operations_len: u64,
35) -> Result<Vec<D>, Error> {
36 let Some(end_loc) = start_loc.checked_add(operations_len) else {
37 return Err(Error::LocationOverflow(start_loc));
38 };
39 proof.extract_pinned_nodes(start_loc..end_loc)
40}
41
42pub fn verify_proof_and_extract_digests<Op, H, D>(
45 hasher: &mut Standard<H>,
46 proof: &Proof<D>,
47 start_loc: Location,
48 operations: &[Op],
49 target_root: &D,
50) -> Result<Vec<(Position, D)>, Error>
51where
52 Op: Encode,
53 H: Hasher<Digest = D>,
54 D: Digest,
55{
56 let elements = operations.iter().map(|op| op.encode()).collect::<Vec<_>>();
57 proof.verify_range_inclusion_and_extract_digests(hasher, &elements, start_loc, target_root)
58}
59
60pub fn digests_required_for_proof<D: Digest>(
70 op_count: Location,
71 range: Range<Location>,
72) -> Result<Vec<Position>, crate::mmr::Error> {
73 let mmr_size = Position::try_from(op_count)?;
74 proof::nodes_required_for_range_proof(mmr_size, range)
75}
76
77pub fn create_proof<D: Digest>(
85 op_count: Location,
86 digests: Vec<D>,
87) -> Result<Proof<D>, crate::mmr::Error> {
88 Ok(Proof::<D> {
89 size: Position::try_from(op_count)?,
90 digests,
91 })
92}
93
94pub fn create_proof_store<Op, H, D>(
96 hasher: &mut Standard<H>,
97 proof: &Proof<D>,
98 start_loc: Location,
99 operations: &[Op],
100 root: &D,
101) -> Result<ProofStore<D>, Error>
102where
103 Op: Encode,
104 H: Hasher<Digest = D>,
105 D: Digest,
106{
107 let elements = operations.iter().map(|op| op.encode()).collect::<Vec<_>>();
109
110 ProofStore::new(hasher, proof, &elements, start_loc, root)
112}
113
114pub fn create_proof_store_from_digests<D: Digest>(
118 proof: &Proof<D>,
119 digests: Vec<(Position, D)>,
120) -> ProofStore<D> {
121 ProofStore::new_from_digests(proof.size, digests)
122}
123
124pub async fn create_multi_proof<D: Digest>(
126 proof_store: &ProofStore<D>,
127 locations: &[Location],
128) -> Result<Proof<D>, Error> {
129 verification::multi_proof(proof_store, locations).await
131}
132
133pub fn verify_multi_proof<Op, H, D>(
135 hasher: &mut Standard<H>,
136 proof: &Proof<D>,
137 operations: &[(Location, Op)],
138 target_root: &D,
139) -> bool
140where
141 Op: Encode,
142 H: Hasher<Digest = D>,
143 D: Digest,
144{
145 let elements = operations
147 .iter()
148 .map(|(loc, op)| (op.encode(), *loc))
149 .collect::<Vec<_>>();
150
151 proof.verify_multi_inclusion(hasher, &elements, target_root)
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use crate::mmr::{iterator::nodes_to_pin, location::LocationRangeExt as _, mem::CleanMmr};
159 use commonware_cryptography::{sha256::Digest, Sha256};
160 use commonware_macros::test_traced;
161 use commonware_runtime::{deterministic, Runner};
162
163 fn test_digest(v: u8) -> Digest {
164 Sha256::hash(&[v])
165 }
166
167 fn test_hasher() -> Standard<Sha256> {
168 Standard::new()
169 }
170
171 #[test_traced]
172 fn test_verify_proof() {
173 let executor = deterministic::Runner::default();
174 executor.start(|_| async move {
175 let mut hasher = test_hasher();
176 let mut mmr = CleanMmr::new(&mut hasher);
177
178 let operations = vec![1, 2, 3];
180 let mut positions = Vec::new();
181 for op in &operations {
182 let encoded = op.encode();
183 let pos = mmr.add(&mut hasher, &encoded);
184 positions.push(pos);
185 }
186 let root = mmr.root();
187
188 let proof = mmr
190 .range_proof(Location::new_unchecked(0)..Location::new_unchecked(3))
191 .unwrap();
192
193 assert!(verify_proof(
195 &mut hasher,
196 &proof,
197 Location::new_unchecked(0), &operations,
199 root,
200 ));
201
202 let wrong_root = test_digest(99);
204 assert!(!verify_proof(
205 &mut hasher,
206 &proof,
207 Location::new_unchecked(0),
208 &operations,
209 &wrong_root,
210 ));
211
212 let wrong_operations = vec![9, 10, 11];
214 assert!(!verify_proof(
215 &mut hasher,
216 &proof,
217 Location::new_unchecked(0),
218 &wrong_operations,
219 root,
220 ));
221 });
222 }
223
224 #[test_traced]
225 fn test_verify_proof_with_offset() {
226 let executor = deterministic::Runner::default();
227 executor.start(|_| async move {
228 let mut hasher = test_hasher();
229 let mut mmr = CleanMmr::new(&mut hasher);
230
231 for i in 0u64..5 {
233 mmr.add(&mut hasher, &i.encode());
234 }
235
236 let operations = vec![10, 11, 12];
238 let start_loc = Location::new_unchecked(5u64);
239 for op in &operations {
240 let encoded = op.encode();
241 mmr.add(&mut hasher, &encoded);
242 }
243 let root = mmr.root();
244 let proof = mmr
245 .range_proof(Location::new_unchecked(5)..Location::new_unchecked(8))
246 .unwrap();
247
248 assert!(verify_proof(
250 &mut hasher,
251 &proof,
252 start_loc,
253 &operations,
254 root,
255 ));
256
257 assert!(!verify_proof(
259 &mut hasher,
260 &proof,
261 Location::new_unchecked(0), &operations,
263 root,
264 ));
265 });
266 }
267
268 #[test_traced]
269 fn test_extract_pinned_nodes() {
270 let executor = deterministic::Runner::default();
271 executor.start(|_| async move {
272 let mut hasher = test_hasher();
273 let mut mmr = CleanMmr::new(&mut hasher);
274
275 let mut positions = Vec::new();
277 for i in 0u64..10 {
278 positions.push(mmr.add(&mut hasher, &i.encode()));
279 }
280
281 let start_loc = Location::new_unchecked(2);
283 let operations_len = 4u64;
284 let end_loc = start_loc + operations_len;
285 let range = start_loc..end_loc;
286 let proof = mmr.range_proof(range).unwrap();
287
288 let pinned_nodes = extract_pinned_nodes(&proof, start_loc, operations_len);
290 assert!(pinned_nodes.is_ok());
291 let nodes = pinned_nodes.unwrap();
292 assert!(!nodes.is_empty());
293
294 let start_pos = Position::try_from(start_loc).unwrap();
296 let expected_pinned: Vec<Position> = nodes_to_pin(start_pos).collect();
297 assert_eq!(nodes.len(), expected_pinned.len());
298 });
299 }
300
301 #[test_traced]
302 fn test_verify_proof_and_extract_digests() {
303 let executor = deterministic::Runner::default();
304 executor.start(|_| async move {
305 let mut hasher = test_hasher();
306 let mut mmr = CleanMmr::new(&mut hasher);
307
308 let operations = vec![1, 2, 3, 4];
310 let mut positions = Vec::new();
311 for op in &operations {
312 let encoded = op.encode();
313 let pos = mmr.add(&mut hasher, &encoded);
314 positions.push(pos);
315 }
316 let root = mmr.root();
317 let range = Location::new_unchecked(1)..Location::new_unchecked(4);
318 let proof = mmr.range_proof(range.clone()).unwrap();
319
320 let result = verify_proof_and_extract_digests(
322 &mut hasher,
323 &proof,
324 Location::new_unchecked(1), &operations[range.to_usize_range()],
326 root,
327 );
328 assert!(result.is_ok());
329 let digests = result.unwrap();
330 assert!(!digests.is_empty());
331
332 let wrong_root = test_digest(99);
334 assert!(verify_proof_and_extract_digests(
335 &mut hasher,
336 &proof,
337 Location::new_unchecked(1),
338 &operations[range.to_usize_range()],
339 &wrong_root,
340 )
341 .is_err());
342 });
343 }
344
345 #[test_traced]
346 fn test_create_proof() {
347 const OP_COUNT: Location = Location::new_unchecked(15);
348
349 let executor = deterministic::Runner::default();
350 executor.start(|_| async move {
351 let mut hasher = test_hasher();
352 let mut mmr = CleanMmr::new(&mut hasher);
353
354 let operations: Vec<u64> = (0..*OP_COUNT).collect();
357 let mut positions = Vec::new();
358 for op in &operations {
359 let encoded = op.encode();
360 positions.push(mmr.add(&mut hasher, &encoded));
361 }
362 let root = mmr.root();
363
364 let start_loc = Location::new_unchecked(3u64);
366 let end_loc = Location::new_unchecked(8u64);
367
368 let end_plus_one = end_loc.checked_add(1).expect("test location in bounds");
370 let required_positions =
371 digests_required_for_proof::<Digest>(OP_COUNT, start_loc..end_plus_one).unwrap();
372
373 let mut digests = Vec::new();
375 for pos in required_positions {
376 if let Some(digest) = mmr.get_node(pos) {
377 digests.push(digest);
378 }
379 }
380
381 let proof = create_proof(OP_COUNT, digests.clone()).expect("test locations valid");
383 assert_eq!(proof.size, Position::try_from(OP_COUNT).unwrap());
384 assert_eq!(proof.digests.len(), digests.len());
385
386 assert!(verify_proof(
387 &mut hasher,
388 &proof,
389 start_loc,
390 &operations[*start_loc as usize..=*end_loc as usize],
391 root,
392 ));
393 });
394 }
395
396 #[test_traced]
397 fn test_create_proof_store() {
398 let executor = deterministic::Runner::default();
399 executor.start(|_| async move {
400 let mut hasher = test_hasher();
401 let mut mmr = CleanMmr::new(&mut hasher);
402
403 let op_count = 15;
405 let operations: Vec<u64> = (0..op_count).collect();
406 let mut positions = Vec::new();
407 for op in &operations {
408 let encoded = op.encode();
409 let pos = mmr.add(&mut hasher, &encoded);
410 positions.push(pos);
411 }
412 let root = mmr.root();
413 let range = Location::new_unchecked(0)..Location::new_unchecked(3);
414 let proof = mmr.range_proof(range.clone()).unwrap();
415
416 let result = create_proof_store(
418 &mut hasher,
419 &proof,
420 range.start, &operations[range.to_usize_range()], root,
423 );
424 assert!(result.is_ok());
425 let proof_store = result.unwrap();
426
427 let range = Location::new_unchecked(0)..Location::new_unchecked(2);
429 let sub_proof = verification::range_proof(&proof_store, range.clone())
430 .await
431 .unwrap();
432
433 assert!(verify_proof(
435 &mut hasher,
436 &sub_proof,
437 range.start,
438 &operations[range.to_usize_range()],
439 root,
440 ));
441 });
442 }
443
444 #[test_traced]
445 fn test_create_proof_store_invalid_proof() {
446 let executor = deterministic::Runner::default();
447 executor.start(|_| async move {
448 let mut hasher = test_hasher();
449 let mut mmr = CleanMmr::new(&mut hasher);
450
451 let operations = vec![1, 2, 3];
453 let mut positions = Vec::new();
454 for op in &operations {
455 let encoded = op.encode();
456 let pos = mmr.add(&mut hasher, &encoded);
457 positions.push(pos);
458 }
459 let range = Location::new_unchecked(0)..Location::new_unchecked(2);
460 let proof = mmr.range_proof(range).unwrap();
461
462 let wrong_root = test_digest(99);
464 assert!(create_proof_store(
465 &mut hasher,
466 &proof,
467 Location::new_unchecked(0),
468 &operations,
469 &wrong_root
470 )
471 .is_err());
472 });
473 }
474
475 #[test_traced]
476 fn test_create_proof_store_from_digests() {
477 let executor = deterministic::Runner::default();
478 executor.start(|_| async move {
479 let mut hasher = test_hasher();
480 let mut mmr = CleanMmr::new(&mut hasher);
481
482 let operations = vec![1, 2, 3];
484 let mut positions = Vec::new();
485 for op in &operations {
486 let encoded = op.encode();
487 let pos = mmr.add(&mut hasher, &encoded);
488 positions.push(pos);
489 }
490 let root = mmr.root();
491 let proof = mmr
492 .range_proof(Location::new_unchecked(0)..Location::new_unchecked(3))
493 .unwrap();
494
495 let digests = verify_proof_and_extract_digests(
497 &mut hasher,
498 &proof,
499 Location::new_unchecked(0),
500 &operations,
501 root,
502 )
503 .unwrap();
504
505 let proof_store = create_proof_store_from_digests(&proof, digests);
507
508 let sub_proof = verification::range_proof(
510 &proof_store,
511 Location::new_unchecked(0)..Location::new_unchecked(2),
512 )
513 .await
514 .unwrap();
515
516 assert!(verify_proof(
518 &mut hasher,
519 &sub_proof,
520 Location::new_unchecked(0),
521 &operations[0..2],
522 root,
523 ));
524 });
525 }
526
527 #[test_traced]
528 fn test_create_multi_proof() {
529 let executor = deterministic::Runner::default();
530 executor.start(|_| async move {
531 let mut hasher = test_hasher();
532 let mut mmr = CleanMmr::new(&mut hasher);
533
534 let operations: Vec<u64> = (0..20).collect();
536 for op in &operations {
537 let encoded = op.encode();
538 mmr.add(&mut hasher, &encoded);
539 }
540 let root = mmr.root();
541
542 let proof = mmr
544 .range_proof(Location::new_unchecked(0)..Location::new_unchecked(20))
545 .unwrap();
546
547 let proof_store = create_proof_store(
549 &mut hasher,
550 &proof,
551 Location::new_unchecked(0),
552 &operations,
553 root,
554 )
555 .unwrap();
556
557 let target_locations = vec![
559 Location::new_unchecked(2),
560 Location::new_unchecked(5),
561 Location::new_unchecked(10),
562 Location::new_unchecked(15),
563 Location::new_unchecked(18),
564 ];
565 let multi_proof = create_multi_proof(&proof_store, &target_locations)
566 .await
567 .unwrap();
568
569 let selected_ops: Vec<(Location, u64)> = target_locations
571 .iter()
572 .map(|&loc| (loc, operations[*loc as usize]))
573 .collect();
574
575 assert!(verify_multi_proof(
577 &mut hasher,
578 &multi_proof,
579 &selected_ops,
580 root,
581 ));
582 });
583 }
584
585 #[test_traced]
586 fn test_verify_multi_proof() {
587 let executor = deterministic::Runner::default();
588 executor.start(|_| async move {
589 let mut hasher = test_hasher();
590 let mut mmr = CleanMmr::new(&mut hasher);
591
592 let operations: Vec<u64> = (0..10).collect();
594 let mut positions = Vec::new();
595 for op in &operations {
596 let encoded = op.encode();
597 let pos = mmr.add(&mut hasher, &encoded);
598 positions.push(pos);
599 }
600 let root = mmr.root();
601
602 let target_locations = vec![
604 Location::new_unchecked(1),
605 Location::new_unchecked(4),
606 Location::new_unchecked(7),
607 ];
608 let multi_proof = verification::multi_proof(&mmr, &target_locations)
609 .await
610 .unwrap();
611
612 let selected_ops = vec![
614 (Location::new_unchecked(1), operations[1]),
615 (Location::new_unchecked(4), operations[4]),
616 (Location::new_unchecked(7), operations[7]),
617 ];
618 assert!(verify_multi_proof(
619 &mut hasher,
620 &multi_proof,
621 &selected_ops,
622 root,
623 ));
624
625 let wrong_ops = vec![
627 (Location::new_unchecked(1), 99),
628 (Location::new_unchecked(4), operations[4]),
629 (Location::new_unchecked(7), operations[7]),
630 ];
631 assert!(!verify_multi_proof(
632 &mut hasher,
633 &multi_proof,
634 &wrong_ops,
635 root,
636 ));
637
638 let wrong_locations = vec![
640 (Location::new_unchecked(0), operations[1]),
641 (Location::new_unchecked(4), operations[4]),
642 (Location::new_unchecked(7), operations[7]),
643 ];
644 assert!(!verify_multi_proof(
645 &mut hasher,
646 &multi_proof,
647 &wrong_locations,
648 root,
649 ));
650 });
651 }
652
653 #[test_traced]
654 fn test_multi_proof_empty() {
655 let executor = deterministic::Runner::default();
656 executor.start(|_| async move {
657 let mut hasher = test_hasher();
658 let empty_mmr = CleanMmr::new(&mut hasher);
659 let empty_root = empty_mmr.root();
660
661 let empty_proof = Proof::default();
663 assert!(verify_multi_proof(
664 &mut hasher,
665 &empty_proof,
666 &[] as &[(Location, u64)],
667 empty_root,
668 ));
669
670 assert!(matches!(
672 verification::multi_proof(&empty_mmr, &[]).await,
673 Err(Error::Empty)
674 ));
675 });
676 }
677
678 #[test_traced]
679 fn test_multi_proof_single_element() {
680 let executor = deterministic::Runner::default();
681 executor.start(|_| async move {
682 let mut hasher = test_hasher();
683 let mut mmr = CleanMmr::new(&mut hasher);
684
685 let operations = vec![1, 2, 3];
687 let mut positions = Vec::new();
688 for op in &operations {
689 let encoded = op.encode();
690 let pos = mmr.add(&mut hasher, &encoded);
691 positions.push(pos);
692 }
693 let root = mmr.root();
694
695 let proof = mmr
697 .range_proof(Location::new_unchecked(0)..Location::new_unchecked(3))
698 .unwrap();
699 let proof_store = create_proof_store(
700 &mut hasher,
701 &proof,
702 Location::new_unchecked(0),
703 &operations,
704 root,
705 )
706 .unwrap();
707
708 let multi_proof = create_multi_proof(&proof_store, &[Location::new_unchecked(1)])
710 .await
711 .unwrap();
712
713 assert!(verify_multi_proof(
715 &mut hasher,
716 &multi_proof,
717 &[(Location::new_unchecked(1), operations[1])],
718 root,
719 ));
720 });
721 }
722}