1use crate::{
12 merkle::Graftable,
13 qmdb::{
14 any::{ordered::Update, ValueEncoding},
15 current::proof::OperationProof,
16 operation::Key,
17 },
18};
19use bytes::{Buf, BufMut};
20use commonware_codec::{EncodeSize, Read, ReadExt as _, Write};
21use commonware_cryptography::Digest;
22
23pub mod db;
24pub mod fixed;
25#[cfg(any(test, feature = "test-traits"))]
26mod test_trait_impls;
27pub mod variable;
28
29#[derive(Clone, Eq, PartialEq, Debug)]
37pub enum ExclusionProof<F: Graftable, K: Key, V: ValueEncoding, D: Digest, const N: usize> {
38 KeyValue(OperationProof<F, D, N>, Update<K, V>),
41
42 Commit(OperationProof<F, D, N>, Option<V::Value>),
47}
48
49const KEY_VALUE_CONTEXT: u8 = 0;
50const COMMIT_CONTEXT: u8 = 1;
51
52impl<F, K, V, D, const N: usize> Write for ExclusionProof<F, K, V, D, N>
53where
54 F: Graftable,
55 K: Key,
56 V: ValueEncoding,
57 D: Digest,
58 Update<K, V>: Write,
59{
60 fn write(&self, buf: &mut impl BufMut) {
61 match self {
62 Self::KeyValue(op_proof, update) => {
63 KEY_VALUE_CONTEXT.write(buf);
64 op_proof.write(buf);
65 update.write(buf);
66 }
67 Self::Commit(op_proof, value) => {
68 COMMIT_CONTEXT.write(buf);
69 op_proof.write(buf);
70 value.write(buf);
71 }
72 }
73 }
74}
75
76impl<F, K, V, D, const N: usize> EncodeSize for ExclusionProof<F, K, V, D, N>
77where
78 F: Graftable,
79 K: Key,
80 V: ValueEncoding,
81 D: Digest,
82 Update<K, V>: EncodeSize,
83{
84 fn encode_size(&self) -> usize {
85 1 + match self {
86 Self::KeyValue(op_proof, update) => op_proof.encode_size() + update.encode_size(),
87 Self::Commit(op_proof, value) => op_proof.encode_size() + value.encode_size(),
88 }
89 }
90}
91
92impl<F, K, V, D, const N: usize> Read for ExclusionProof<F, K, V, D, N>
93where
94 F: Graftable,
95 K: Key,
96 V: ValueEncoding,
97 D: Digest,
98 Update<K, V>: Read,
99{
100 type Cfg = (usize, <Update<K, V> as Read>::Cfg, <V::Value as Read>::Cfg);
104
105 fn read_cfg(
106 buf: &mut impl Buf,
107 (max_digests, update_cfg, value_cfg): &Self::Cfg,
108 ) -> Result<Self, commonware_codec::Error> {
109 match u8::read(buf)? {
110 KEY_VALUE_CONTEXT => {
111 let op_proof = OperationProof::<F, D, N>::read_cfg(buf, max_digests)?;
112 let update = Update::<K, V>::read_cfg(buf, update_cfg)?;
113 Ok(Self::KeyValue(op_proof, update))
114 }
115 COMMIT_CONTEXT => {
116 let op_proof = OperationProof::<F, D, N>::read_cfg(buf, max_digests)?;
117 let value = Option::<V::Value>::read_cfg(buf, value_cfg)?;
118 Ok(Self::Commit(op_proof, value))
119 }
120 tag => Err(commonware_codec::Error::InvalidEnum(tag)),
121 }
122 }
123}
124
125#[cfg(feature = "arbitrary")]
126impl<F, K, V, D, const N: usize> arbitrary::Arbitrary<'_> for ExclusionProof<F, K, V, D, N>
127where
128 F: Graftable,
129 K: Key,
130 V: ValueEncoding,
131 D: Digest,
132 K: for<'a> arbitrary::Arbitrary<'a>,
133 V::Value: for<'a> arbitrary::Arbitrary<'a>,
134 D: for<'a> arbitrary::Arbitrary<'a>,
135 F::PendingChunk<D>: for<'a> arbitrary::Arbitrary<'a>,
136{
137 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
138 let op_proof = u.arbitrary()?;
139 if u.arbitrary()? {
140 Ok(Self::KeyValue(op_proof, u.arbitrary()?))
141 } else {
142 Ok(Self::Commit(op_proof, u.arbitrary()?))
143 }
144 }
145}
146
147#[cfg(test)]
148pub mod tests {
149 use super::{db, ExclusionProof};
152 use crate::{
153 index::ordered::Index,
154 journal::contiguous::{Contiguous as _, Mutable},
155 merkle::{Graftable, Location, Proof},
156 mmb,
157 qmdb::{
158 any::{
159 ordered::{Operation, Update},
160 traits::{DbAny, UnmerkleizedBatch as _},
161 value::FixedEncoding,
162 ValueEncoding,
163 },
164 current::{
165 proof::{OperationProof, RangeProof},
166 tests::apply_random_ops,
167 BitmapPrunedBits,
168 },
169 store::tests::{TestKey, TestValue},
170 Error,
171 },
172 translator::OneCap,
173 };
174 use commonware_codec::{Codec, Decode as _, Encode as _, EncodeSize as _};
175 use commonware_cryptography::{sha256::Digest, Digest as _, Hasher as _, Sha256};
176 use commonware_runtime::{
177 deterministic::{self, Context},
178 Runner as _, Supervisor as _,
179 };
180 use commonware_utils::{
181 bitmap::{Prunable as BitMap, Readable as _},
182 NZU64,
183 };
184 use core::future::Future;
185 use rand::Rng;
186
187 type TestDb<F, C, V> = db::Db<
190 F,
191 deterministic::Context,
192 C,
193 Digest,
194 V,
195 Index<OneCap, Location<F>>,
196 Sha256,
197 32,
198 commonware_parallel::Sequential,
199 >;
200
201 pub async fn test_build_small_close_reopen<F, C, Fn, Fut>(context: Context, mut open_db: Fn)
206 where
207 F: Graftable,
208 C: DbAny<F> + BitmapPrunedBits,
209 C::Key: TestKey,
210 <C as DbAny<F>>::Value: TestValue,
211 Fn: FnMut(Context, String) -> Fut,
212 Fut: Future<Output = C>,
213 {
214 let partition = "build-small".to_string();
215 let db: C = open_db(context.child("first"), partition.clone()).await;
216 assert_eq!(db.inactivity_floor_loc(), Location::<F>::new(0));
217 assert_eq!(db.oldest_retained(), 0);
218 let root0 = db.root();
219 drop(db);
220 let mut db: C = open_db(context.child("second"), partition.clone()).await;
221 assert!(db.get_metadata().await.unwrap().is_none());
222 assert_eq!(db.root(), root0);
223
224 let k1: C::Key = TestKey::from_seed(0);
226 let v1: <C as DbAny<F>>::Value = TestValue::from_seed(10);
227 assert!(db.get(&k1).await.unwrap().is_none());
228 let merkleized = db
229 .new_batch()
230 .write(k1, Some(v1.clone()))
231 .merkleize(&db, None)
232 .await
233 .unwrap();
234 db.apply_batch(merkleized).await.unwrap();
235 db.commit().await.unwrap();
236 assert_eq!(db.get(&k1).await.unwrap().unwrap(), v1);
237 assert!(db.get_metadata().await.unwrap().is_none());
238 let root1 = db.root();
239 assert_ne!(root1, root0);
240
241 drop(db);
242 let mut db: C = open_db(context.child("third"), partition.clone()).await;
243 assert_eq!(db.root(), root1);
244
245 assert!(db.get(&k1).await.unwrap().is_some());
247
248 assert!(db.get(&k1).await.unwrap().is_some());
250 let metadata: <C as DbAny<F>>::Value = TestValue::from_seed(1);
251 let merkleized = db
252 .new_batch()
253 .write(k1, None)
254 .merkleize(&db, Some(metadata.clone()))
255 .await
256 .unwrap();
257 db.apply_batch(merkleized).await.unwrap();
258 db.commit().await.unwrap();
259 assert_eq!(db.get_metadata().await.unwrap().unwrap(), metadata);
260 let root2 = db.root();
261
262 drop(db);
263 let mut db: C = open_db(context.child("fourth"), partition.clone()).await;
264 assert_eq!(db.get_metadata().await.unwrap().unwrap(), metadata);
265 assert_eq!(db.root(), root2);
266
267 assert!(db.get(&k1).await.unwrap().is_none());
269 let merkleized = db.new_batch().merkleize(&db, None).await.unwrap();
270 db.apply_batch(merkleized).await.unwrap();
271 db.commit().await.unwrap();
272 let root3 = db.root();
273 assert_ne!(root3, root2);
274
275 let bounds = db.bounds();
277 for i in 0..*bounds.end - 1 {
278 assert!(!db.get_bit(i));
279 }
280 assert!(db.get_bit(*bounds.end - 1));
281
282 let merkleized = db
284 .new_batch()
285 .write(k1, Some(v1))
286 .merkleize(&db, None)
287 .await
288 .unwrap();
289 db.apply_batch(merkleized).await.unwrap();
290 assert_ne!(db.root(), root3);
291
292 db.destroy().await.unwrap();
293 }
294
295 pub(super) fn test_verify_proof_over_bits_in_uncommitted_chunk<F, C, V, Fn, Fut>(
300 mut open_db: Fn,
301 ) where
302 F: Graftable,
303 C: Mutable<Item = Operation<F, Digest, V>> + 'static,
304 V: ValueEncoding<Value = Digest> + 'static,
305 Operation<F, Digest, V>: Codec,
306 TestDb<F, C, V>: DbAny<F, Key = Digest, Value = Digest, Digest = Digest> + 'static,
307 Fn: FnMut(Context, String) -> Fut + 'static,
308 Fut: Future<Output = TestDb<F, C, V>>,
309 {
310 let executor = deterministic::Runner::default();
311 executor.start(|context| async move {
312 let partition = "build-small".to_string();
313 let mut db = open_db(context.child("db"), partition.clone()).await;
314
315 let k = Sha256::fill(0x01);
317 let v1 = Sha256::fill(0xA1);
318 let merkleized = db
319 .new_batch()
320 .write(k, Some(v1))
321 .merkleize(&db, None)
322 .await
323 .unwrap();
324 db.apply_batch(merkleized).await.unwrap();
325
326 let (_, op_loc) = db.any.get_with_loc(&k).await.unwrap().unwrap();
327 let proof = db.key_value_proof(k).await.unwrap();
328
329 let root = db.root();
331 assert!(TestDb::<F, C, V>::verify_key_value_proof(
332 k, v1, &proof, &root,
333 ));
334
335 let v2 = Sha256::fill(0xA2);
336 assert!(!TestDb::<F, C, V>::verify_key_value_proof(
338 k, v2, &proof, &root,
339 ));
340 let mut mangled_proof = proof.clone();
342 mangled_proof.next_key = Sha256::fill(0xFF);
343 assert!(!TestDb::<F, C, V>::verify_key_value_proof(
344 k,
345 v1,
346 &mangled_proof,
347 &root,
348 ));
349
350 let merkleized = db
352 .new_batch()
353 .write(k, Some(v2))
354 .merkleize(&db, None)
355 .await
356 .unwrap();
357 db.apply_batch(merkleized).await.unwrap();
358 let root = db.root();
359
360 assert!(!TestDb::<F, C, V>::verify_key_value_proof(
362 k, v2, &proof, &root,
363 ));
364
365 let proof = db.key_value_proof(k).await.unwrap();
367 assert!(TestDb::<F, C, V>::verify_key_value_proof(
368 k, v2, &proof, &root,
369 ));
370
371 assert!(!TestDb::<F, C, V>::verify_key_value_proof(
373 k, v1, &proof, &root,
374 ));
375
376 let (p, _, chunks) = db.range_proof(op_loc, NZU64!(1)).await.unwrap();
379 let proof_inactive = db::KeyValueProof {
380 proof: crate::qmdb::current::proof::OperationProof {
381 loc: op_loc,
382 chunk: chunks[0],
383 range_proof: p,
384 },
385 next_key: k,
386 };
387 let op = Operation::Update(Update {
390 key: k,
391 value: v1,
392 next_key: k,
393 });
394 assert!(TestDb::<F, C, V>::verify_range_proof(
395 &proof_inactive.proof.range_proof,
396 proof_inactive.proof.loc,
397 &[op],
398 &[proof_inactive.proof.chunk],
399 &root,
400 ));
401
402 assert!(!TestDb::<F, C, V>::verify_key_value_proof(
405 k,
406 v1,
407 &proof_inactive,
408 &root,
409 ));
410
411 let (_, active_loc) = db.any.get_with_loc(&k).await.unwrap().unwrap();
415 assert_ne!(active_loc, proof_inactive.proof.loc);
417 assert_eq!(
418 BitMap::<32>::to_chunk_index(*active_loc),
419 BitMap::<32>::to_chunk_index(*proof_inactive.proof.loc)
420 );
421 let mut fake_proof = proof_inactive.clone();
422 fake_proof.proof.loc = active_loc;
423 assert!(!TestDb::<F, C, V>::verify_key_value_proof(
424 k,
425 v1,
426 &fake_proof,
427 &root,
428 ));
429
430 let mut modified_chunk = proof_inactive.proof.chunk;
435 let bit_pos = *proof_inactive.proof.loc;
436 let byte_idx = bit_pos / 8;
437 let bit_idx = bit_pos % 8;
438 modified_chunk[byte_idx as usize] |= 1 << bit_idx;
439
440 let mut fake_proof = proof_inactive.clone();
441 fake_proof.proof.chunk = modified_chunk;
442 assert!(!TestDb::<F, C, V>::verify_key_value_proof(
443 k,
444 v1,
445 &fake_proof,
446 &root,
447 ));
448
449 db.destroy().await.unwrap();
450 });
451 }
452
453 pub(super) fn test_range_proofs<F, C, V, Fn, Fut>(mut open_db: Fn)
458 where
459 F: Graftable,
460 C: Mutable<Item = Operation<F, Digest, V>> + 'static,
461 V: ValueEncoding<Value = Digest> + 'static,
462 Operation<F, Digest, V>: Codec,
463 TestDb<F, C, V>: DbAny<F, Key = Digest, Value = Digest, Digest = Digest> + 'static,
464 Fn: FnMut(Context, String) -> Fut + 'static,
465 Fut: Future<Output = TestDb<F, C, V>>,
466 {
467 let executor = deterministic::Runner::default();
468 executor.start(|mut context| async move {
469 let partition = "range-proofs".to_string();
470 let db = open_db(context.child("db"), partition.clone()).await;
471 let root = db.root();
472
473 let proof = RangeProof {
475 proof: Proof::default(),
476 pending_chunk_digest: None.try_into().unwrap(),
477 partial_chunk_digest: None,
478 ops_root: Digest::EMPTY,
479 };
480 assert!(!TestDb::<F, C, V>::verify_range_proof(
481 &proof,
482 Location::<F>::new(0),
483 &[],
484 &[],
485 &root,
486 ));
487
488 let mut db = apply_random_ops::<F, TestDb<F, C, V>>(200, true, context.next_u64(), db)
489 .await
490 .unwrap();
491 let merkleized = db.new_batch().merkleize(&db, None).await.unwrap();
492 db.apply_batch(merkleized).await.unwrap();
493 let root = db.root();
494
495 let max_ops = 4;
498 let end_loc = db.bounds().end;
499 let start_loc = db.any.inactivity_floor_loc();
500
501 for loc in *start_loc..*end_loc {
502 let loc = Location::<F>::new(loc);
503 let (proof, ops, chunks) = db.range_proof(loc, NZU64!(max_ops)).await.unwrap();
504 assert!(
505 TestDb::<F, C, V>::verify_range_proof(&proof, loc, &ops, &chunks, &root),
506 "failed to verify range at start_loc {start_loc}",
507 );
508 let mut chunks_with_extra = chunks.clone();
510 chunks_with_extra.push(chunks[chunks.len() - 1]);
511 assert!(!TestDb::<F, C, V>::verify_range_proof(
512 &proof,
513 loc,
514 &ops,
515 &chunks_with_extra,
516 &root,
517 ));
518 }
519
520 db.destroy().await.unwrap();
521 });
522 }
523
524 pub(super) fn test_key_value_proof<F, C, V, Fn, Fut>(mut open_db: Fn)
529 where
530 F: Graftable,
531 C: Mutable<Item = Operation<F, Digest, V>> + 'static,
532 V: ValueEncoding<Value = Digest> + 'static,
533 Operation<F, Digest, V>: Codec,
534 TestDb<F, C, V>: DbAny<F, Key = Digest, Value = Digest, Digest = Digest> + 'static,
535 Fn: FnMut(Context, String) -> Fut + 'static,
536 Fut: Future<Output = TestDb<F, C, V>>,
537 {
538 let executor = deterministic::Runner::default();
539 executor.start(|mut context| async move {
540 let partition = "range-proofs".to_string();
541 let db = open_db(context.child("db"), partition.clone()).await;
542 let mut db = apply_random_ops::<F, TestDb<F, C, V>>(500, true, context.next_u64(), db)
543 .await
544 .unwrap();
545 let merkleized = db.new_batch().merkleize(&db, None).await.unwrap();
546 db.apply_batch(merkleized).await.unwrap();
547 let root = db.root();
548
549 let bad_key = Sha256::fill(0xAA);
551 let res = db.key_value_proof(bad_key).await;
552 assert!(matches!(res, Err(Error::KeyNotFound)));
553
554 let start = *db.inactivity_floor_loc();
555 for i in start..db.any.bitmap.len() {
556 if !db.any.bitmap.get_bit(i) {
557 continue;
558 }
559 let op = db.any.log.read(*Location::<F>::new(i)).await.unwrap();
562 let (key, value) = match op {
563 Operation::Update(key_data) => (key_data.key, key_data.value),
564 Operation::CommitFloor(_, _) => continue,
565 _ => unreachable!("expected update or commit floor operation"),
566 };
567 let proof = db.key_value_proof(key).await.unwrap();
568
569 assert!(TestDb::<F, C, V>::verify_key_value_proof(
571 key, value, &proof, &root
572 ));
573 let wrong_val = Sha256::hash(&[0xFF]);
577 assert!(!TestDb::<F, C, V>::verify_key_value_proof(
578 key, wrong_val, &proof, &root
579 ));
580 let wrong_key = Sha256::hash(&[0xEE]);
582 assert!(!TestDb::<F, C, V>::verify_key_value_proof(
583 wrong_key, value, &proof, &root
584 ));
585 let wrong_root = Sha256::hash(&[0xDD]);
587 assert!(!TestDb::<F, C, V>::verify_key_value_proof(
588 key,
589 value,
590 &proof,
591 &wrong_root,
592 ));
593 let mut bad_proof = proof.clone();
595 bad_proof.next_key = wrong_key;
596 assert!(!TestDb::<F, C, V>::verify_key_value_proof(
597 key, value, &bad_proof, &root,
598 ));
599 }
600
601 db.destroy().await.unwrap();
602 });
603 }
604
605 pub(super) fn test_proving_repeated_updates<F, C, V, Fn, Fut>(mut open_db: Fn)
610 where
611 F: Graftable,
612 C: Mutable<Item = Operation<F, Digest, V>> + 'static,
613 V: ValueEncoding<Value = Digest> + 'static,
614 Operation<F, Digest, V>: Codec,
615 TestDb<F, C, V>: DbAny<F, Key = Digest, Value = Digest, Digest = Digest> + 'static,
616 Fn: FnMut(Context, String) -> Fut + 'static,
617 Fut: Future<Output = TestDb<F, C, V>>,
618 {
619 let executor = deterministic::Runner::default();
620 executor.start(|context| async move {
621 let partition = "build-small".to_string();
622 let mut db = open_db(context.child("db"), partition.clone()).await;
623
624 let k = Sha256::fill(0x00);
626 let mut old_val = Sha256::fill(0x00);
627 for i in 1u8..=255 {
628 let v = Sha256::fill(i);
629 let merkleized = db
630 .new_batch()
631 .write(k, Some(v))
632 .merkleize(&db, None)
633 .await
634 .unwrap();
635 db.apply_batch(merkleized).await.unwrap();
636 assert_eq!(db.get(&k).await.unwrap().unwrap(), v);
637 let root = db.root();
638
639 let proof = db.key_value_proof(k).await.unwrap();
641 assert!(
642 TestDb::<F, C, V>::verify_key_value_proof(k, v, &proof, &root),
643 "proof of update {i} failed to verify"
644 );
645 assert!(
647 !TestDb::<F, C, V>::verify_key_value_proof(k, old_val, &proof, &root,),
648 "proof of update {i} verified when it should not have"
649 );
650 old_val = v;
651 }
652
653 db.destroy().await.unwrap();
654 });
655 }
656
657 pub(super) fn test_exclusion_proofs<F, C, V, Fn, Fut>(mut open_db: Fn)
663 where
664 F: Graftable + PartialEq,
665 C: Mutable<Item = Operation<F, Digest, V>> + 'static,
666 V: ValueEncoding<Value = Digest> + PartialEq + core::fmt::Debug + 'static,
667 Operation<F, Digest, V>: Codec,
668 TestDb<F, C, V>: DbAny<F, Key = Digest, Value = Digest, Digest = Digest> + 'static,
669 Fn: FnMut(Context, String) -> Fut + 'static,
670 Fut: Future<Output = TestDb<F, C, V>>,
671 {
672 let executor = deterministic::Runner::default();
673 executor.start(|context| async move {
674 let partition = "exclusion-proofs".to_string();
675 let mut db = open_db(context.child("db"), partition.clone()).await;
676
677 let key_exists_1 = Sha256::fill(0x10);
678
679 let empty_root = db.root();
681 let empty_proof = db.exclusion_proof(&key_exists_1).await.unwrap();
682 assert!(TestDb::<F, C, V>::verify_exclusion_proof(
683 &key_exists_1,
684 &empty_proof,
685 &empty_root,
686 ));
687
688 let v1 = Sha256::fill(0xA1);
690 let merkleized = db
691 .new_batch()
692 .write(key_exists_1, Some(v1))
693 .merkleize(&db, None)
694 .await
695 .unwrap();
696 db.apply_batch(merkleized).await.unwrap();
697 let root = db.root();
698
699 let result = db.exclusion_proof(&key_exists_1).await;
701 assert!(matches!(result, Err(Error::KeyExists)));
702
703 let greater_key = Sha256::fill(0xFF);
705 let lesser_key = Sha256::fill(0x00);
706 let proof = db.exclusion_proof(&greater_key).await.unwrap();
707 let proof2 = db.exclusion_proof(&lesser_key).await.unwrap();
708
709 assert_eq!(proof, proof2);
712 assert!(TestDb::<F, C, V>::verify_exclusion_proof(
714 &greater_key,
715 &proof,
716 &root,
717 ));
718 assert!(TestDb::<F, C, V>::verify_exclusion_proof(
719 &lesser_key,
720 &proof,
721 &root,
722 ));
723 assert!(!TestDb::<F, C, V>::verify_exclusion_proof(
725 &key_exists_1,
726 &proof,
727 &root,
728 ));
729
730 let key_exists_2 = Sha256::fill(0x30);
732 let v2 = Sha256::fill(0xB2);
733
734 let merkleized = db
735 .new_batch()
736 .write(key_exists_2, Some(v2))
737 .merkleize(&db, None)
738 .await
739 .unwrap();
740 db.apply_batch(merkleized).await.unwrap();
741 let root = db.root();
742
743 let lesser_key = Sha256::fill(0x0F); let greater_key = Sha256::fill(0x31); let middle_key = Sha256::fill(0x20); let proof = db.exclusion_proof(&greater_key).await.unwrap();
749 assert!(TestDb::<F, C, V>::verify_exclusion_proof(
752 &greater_key,
753 &proof,
754 &root,
755 ));
756 assert!(TestDb::<F, C, V>::verify_exclusion_proof(
757 &lesser_key,
758 &proof,
759 &root,
760 ));
761 assert!(!TestDb::<F, C, V>::verify_exclusion_proof(
762 &middle_key,
763 &proof,
764 &root,
765 ));
766
767 let new_proof = db.exclusion_proof(&lesser_key).await.unwrap();
769 assert_eq!(proof, new_proof);
770
771 let proof = db.exclusion_proof(&middle_key).await.unwrap();
773 assert!(!TestDb::<F, C, V>::verify_exclusion_proof(
775 &key_exists_1,
776 &proof,
777 &root,
778 ));
779 assert!(TestDb::<F, C, V>::verify_exclusion_proof(
781 &middle_key,
782 &proof,
783 &root,
784 ));
785 assert!(!TestDb::<F, C, V>::verify_exclusion_proof(
786 &key_exists_2,
787 &proof,
788 &root,
789 ));
790
791 let conflicting_middle_key = Sha256::fill(0x11); assert!(TestDb::<F, C, V>::verify_exclusion_proof(
793 &conflicting_middle_key,
794 &proof,
795 &root,
796 ));
797
798 assert!(!TestDb::<F, C, V>::verify_exclusion_proof(
800 &greater_key,
801 &proof,
802 &root,
803 ));
804 assert!(!TestDb::<F, C, V>::verify_exclusion_proof(
805 &lesser_key,
806 &proof,
807 &root,
808 ));
809
810 let merkleized = db
813 .new_batch()
814 .write(key_exists_1, None)
815 .write(key_exists_2, None)
816 .merkleize(&db, None)
817 .await
818 .unwrap();
819 db.apply_batch(merkleized).await.unwrap();
820 db.sync().await.unwrap();
821 let root = db.root();
822 assert!(db.is_empty());
825 assert_ne!(db.bounds().end, 0);
826 assert_ne!(root, empty_root);
827
828 let proof = db.exclusion_proof(&key_exists_1).await.unwrap();
829 assert!(TestDb::<F, C, V>::verify_exclusion_proof(
830 &key_exists_1,
831 &proof,
832 &root,
833 ));
834 assert!(TestDb::<F, C, V>::verify_exclusion_proof(
835 &key_exists_2,
836 &proof,
837 &root,
838 ));
839
840 assert!(!TestDb::<F, C, V>::verify_exclusion_proof(
842 &key_exists_1,
843 &empty_proof, &root,
845 ));
846 assert!(!TestDb::<F, C, V>::verify_exclusion_proof(
847 &key_exists_1,
848 &proof,
849 &empty_root, ));
851 });
852 }
853
854 fn sample_op_proof() -> OperationProof<mmb::Family, Digest, 32> {
855 let range_proof = RangeProof {
856 proof: Proof::<mmb::Family, Digest> {
857 leaves: mmb::Location::new(7),
858 inactive_peaks: 0,
859 digests: vec![Sha256::hash(b"sib")],
860 },
861 pending_chunk_digest: None,
862 partial_chunk_digest: None,
863 ops_root: Sha256::hash(b"ops"),
864 };
865 let chunk: [u8; 32] = core::array::from_fn(|i| i as u8);
866 OperationProof {
867 loc: mmb::Location::new(5),
868 chunk,
869 range_proof,
870 }
871 }
872
873 fn op_proof_digest_count(proof: &OperationProof<mmb::Family, Digest, 32>) -> usize {
874 proof.range_proof.proof.digests.len()
875 }
876
877 type CodecExclusionProof =
878 ExclusionProof<mmb::Family, Digest, FixedEncoding<Digest>, Digest, 32>;
879 type CodecKeyValueProof = db::KeyValueProof<mmb::Family, Digest, Digest, 32>;
880 const MAX_DIGESTS: usize = 64;
881
882 #[test]
883 fn test_key_value_proof_codec_roundtrip() {
884 let proof = CodecKeyValueProof {
885 proof: sample_op_proof(),
886 next_key: Sha256::hash(b"next-key"),
887 };
888
889 let encoded = proof.encode();
890 assert_eq!(encoded.len(), proof.encode_size());
891 let decoded = CodecKeyValueProof::decode_cfg(encoded, &(MAX_DIGESTS, ())).unwrap();
892 assert_eq!(decoded, proof);
893 }
894
895 #[test]
896 fn test_key_value_proof_codec_enforces_merkle_digest_budget() {
897 let proof = CodecKeyValueProof {
898 proof: sample_op_proof(),
899 next_key: Sha256::hash(b"next-key"),
900 };
901 let total_digests = op_proof_digest_count(&proof.proof);
902
903 let encoded = proof.encode();
904 let decoded =
905 CodecKeyValueProof::decode_cfg(encoded.clone(), &(total_digests, ())).unwrap();
906 assert_eq!(decoded, proof);
907 assert!(CodecKeyValueProof::decode_cfg(encoded, &(total_digests - 1, ())).is_err());
908 }
909
910 #[test]
911 fn test_exclusion_proof_codec_roundtrip() {
912 let cases = [
913 CodecExclusionProof::KeyValue(
914 sample_op_proof(),
915 Update {
916 key: Sha256::hash(b"key"),
917 value: Sha256::hash(b"value"),
918 next_key: Sha256::hash(b"next-key"),
919 },
920 ),
921 CodecExclusionProof::Commit(sample_op_proof(), Some(Sha256::hash(b"metadata"))),
922 CodecExclusionProof::Commit(sample_op_proof(), None),
923 ];
924
925 for proof in cases {
926 let encoded = proof.encode();
927 assert_eq!(encoded.len(), proof.encode_size());
928 let decoded = CodecExclusionProof::decode_cfg(encoded, &(MAX_DIGESTS, (), ())).unwrap();
929 assert_eq!(decoded, proof);
930 }
931 }
932
933 #[test]
934 fn test_exclusion_proof_codec_enforces_merkle_digest_budget() {
935 let cases = [
936 CodecExclusionProof::KeyValue(
937 sample_op_proof(),
938 Update {
939 key: Sha256::hash(b"key"),
940 value: Sha256::hash(b"value"),
941 next_key: Sha256::hash(b"next-key"),
942 },
943 ),
944 CodecExclusionProof::Commit(sample_op_proof(), Some(Sha256::hash(b"metadata"))),
945 CodecExclusionProof::Commit(sample_op_proof(), None),
946 ];
947
948 for proof in cases {
949 let total_digests = match &proof {
950 CodecExclusionProof::KeyValue(op_proof, _) => op_proof_digest_count(op_proof),
951 CodecExclusionProof::Commit(op_proof, _) => op_proof_digest_count(op_proof),
952 };
953
954 let encoded = proof.encode();
955 let decoded =
956 CodecExclusionProof::decode_cfg(encoded.clone(), &(total_digests, (), ())).unwrap();
957 assert_eq!(decoded, proof);
958 assert!(
959 CodecExclusionProof::decode_cfg(encoded, &(total_digests - 1, (), ())).is_err()
960 );
961 }
962 }
963
964 #[test]
965 fn test_exclusion_proof_rejects_unknown_tag() {
966 let mut bytes = vec![42u8]; bytes.extend_from_slice(&[0u8; 32]); let result = CodecExclusionProof::decode_cfg(bytes.as_slice(), &(MAX_DIGESTS, (), ()));
969 assert!(result.is_err());
970 }
971
972 #[cfg(feature = "arbitrary")]
973 mod conformance {
974 use crate::{
975 merkle::{mmb, mmr},
976 qmdb::{
977 any::value::{FixedEncoding, VariableEncoding},
978 current::ordered::{db::KeyValueProof, ExclusionProof},
979 },
980 };
981 use commonware_codec::conformance::CodecConformance;
982 use commonware_cryptography::sha256::Digest as Sha256Digest;
983 use commonware_utils::sequence::U64;
984
985 commonware_conformance::conformance_tests! {
986 CodecConformance<KeyValueProof<mmr::Family, U64, Sha256Digest, 32>>,
987 CodecConformance<KeyValueProof<mmb::Family, U64, Sha256Digest, 32>>,
988 CodecConformance<ExclusionProof<mmr::Family, U64, FixedEncoding<U64>, Sha256Digest, 32>>,
989 CodecConformance<ExclusionProof<mmr::Family, U64, VariableEncoding<Vec<u8>>, Sha256Digest, 32>>,
990 CodecConformance<ExclusionProof<mmb::Family, U64, FixedEncoding<U64>, Sha256Digest, 32>>,
991 CodecConformance<ExclusionProof<mmb::Family, U64, VariableEncoding<Vec<u8>>, Sha256Digest, 32>>,
992 }
993 }
994}