Skip to main content

commonware_storage/qmdb/current/ordered/
mod.rs

1//! _Ordered_ variants of a [crate::qmdb::current] authenticated database.
2//!
3//! These variants maintain the lexicographic-next active key for each active key, enabling
4//! exclusion proofs via [ExclusionProof]. This adds overhead compared to [super::unordered]
5//! variants.
6//!
7//! Variants:
8//! - [fixed]: Variant optimized for values of fixed size.
9//! - [variable]: Variant for values of variable size.
10
11use 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/// Proof that a key has no assigned value in the database.
30///
31/// When the database has active keys, exclusion is proven by showing the key falls within a span
32/// between two adjacent active keys. Otherwise exclusion is proven by showing the database contains
33/// no active keys through the most recent commit operation.
34///
35/// Verify using [Db::verify_exclusion_proof](fixed::Db::verify_exclusion_proof).
36#[derive(Clone, Eq, PartialEq, Debug)]
37pub enum ExclusionProof<F: Graftable, K: Key, V: ValueEncoding, D: Digest, const N: usize> {
38    /// Proves that two keys are active in the database and adjacent to each other in the key
39    /// ordering. Any key falling between them (non-inclusively) can be proven excluded.
40    KeyValue(OperationProof<F, D, N>, Update<K, V>),
41
42    /// Proves that the database has no active keys, allowing any key to be proven excluded.
43    /// Specifically, the proof establishes the most recent Commit operation has an activity floor
44    /// equal to its own location, which is a necessary and sufficient condition for an empty
45    /// database.
46    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    /// `(max_digests, update_cfg, value_cfg)`: Merkle digest cap forwarded to the embedded
101    /// operation proof, the read configuration for [Update], and the read configuration for the
102    /// value type.
103    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    //! Shared test utilities for ordered Current QMDB variants.
150
151    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    /// Concrete db type used in the shared proof tests, generic over journal (`C`) and value
188    /// encoding (`V`).
189    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    /// Run `test_current_db_build_small_close_reopen` against an ordered database factory.
202    ///
203    /// This test builds a small database, performs basic operations (create, delete, commit),
204    /// and verifies state is preserved across close/reopen cycles.
205    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        // Add one key.
225        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        // Create of same key should fail (key already exists).
246        assert!(db.get(&k1).await.unwrap().is_some());
247
248        // Delete that one key.
249        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        // Repeated delete of same key should fail (key already deleted).
268        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        // Confirm all activity bits except the last are false.
276        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        // Test that we can get a non-durable root.
283        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    /// Build a tiny database and verify that proofs over uncommitted bitmap chunks are correct.
296    ///
297    /// Tests that the verifier rejects proofs for old values after updates, including attempts
298    /// to forge proofs by swapping locations or flipping activity bits.
299    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            // Add one key.
316            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            // Proof should be verifiable against current root.
330            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            // Proof should not verify against a different value.
337            assert!(!TestDb::<F, C, V>::verify_key_value_proof(
338                k, v2, &proof, &root,
339            ));
340            // Proof should not verify against a mangled next_key.
341            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            // Update the key to a new value (v2), which inactivates the previous operation.
351            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            // New value should not be verifiable against the old proof.
361            assert!(!TestDb::<F, C, V>::verify_key_value_proof(
362                k, v2, &proof, &root,
363            ));
364
365            // But the new value should verify against a new proof.
366            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            // Old value will not verify against new proof.
372            assert!(!TestDb::<F, C, V>::verify_key_value_proof(
373                k, v1, &proof, &root,
374            ));
375
376            // Create a proof of the now-inactive update operation assigning v1 to k against the
377            // current root.
378            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            // This proof should verify using verify_range_proof which does not check activity
388            // status.
389            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            // But this proof should *not* verify as a key value proof, since verification will see
403            // that the operation is inactive.
404            assert!(!TestDb::<F, C, V>::verify_key_value_proof(
405                k,
406                v1,
407                &proof_inactive,
408                &root,
409            ));
410
411            // Attempt #1 to "fool" the verifier:  change the location to that of an active
412            // operation. This should not fool the verifier if we're properly validating the
413            // inclusion of the operation itself, and not just the chunk.
414            let (_, active_loc) = db.any.get_with_loc(&k).await.unwrap().unwrap();
415            // The new location should differ but still be in the same chunk.
416            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            // Attempt #2 to "fool" the verifier: Modify the chunk in the proof info to make it
431            // look like the operation is active by flipping its corresponding bit to 1. This
432            // should not fool the verifier if we are correctly incorporating the partial chunk
433            // information into the root computation.
434            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    /// Verify that range proofs are correct across a database populated with random operations.
454    ///
455    /// Tests that every location from the inactivity floor to the tip produces a valid range
456    /// proof, and that adding extra chunks causes verification to fail.
457    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            // Empty range proof should not crash or verify, since even an empty db has a single
474            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            // Make sure size-constrained batches of operations are provable from the oldest
496            // retained op to tip.
497            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                // Proof should not verify if we include extra chunks.
509                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    /// Verify key-value proofs for every active operation in a randomly-populated database.
525    ///
526    /// Checks that proofs validate against the correct key/value/root and fail against
527    /// wrong keys, wrong values, wrong roots, and wrong next-keys.
528    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            // Confirm bad keys produce the expected error.
550            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                // Found an active operation! Create a proof for its active current key/value if
560                // it's a key-updating operation.
561                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                // Proof should validate against the current value and correct root.
570                assert!(TestDb::<F, C, V>::verify_key_value_proof(
571                    key, value, &proof, &root
572                ));
573                // Proof should fail against the wrong value. Use hash instead of fill to ensure
574                // the value differs from any key/value created by TestKey::from_seed (which uses
575                // fill patterns).
576                let wrong_val = Sha256::hash(&[0xFF]);
577                assert!(!TestDb::<F, C, V>::verify_key_value_proof(
578                    key, wrong_val, &proof, &root
579                ));
580                // Proof should fail against the wrong key.
581                let wrong_key = Sha256::hash(&[0xEE]);
582                assert!(!TestDb::<F, C, V>::verify_key_value_proof(
583                    wrong_key, value, &proof, &root
584                ));
585                // Proof should fail against the wrong root.
586                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                // Proof should fail with the wrong next-key.
594                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    /// Repeatedly update the same key and ensure the proof tracks the latest value.
606    ///
607    /// After each update, verifies that the new value's proof succeeds and the previous
608    /// value's proof fails.
609    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            // Add one key.
625            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                // Create a proof for the current value of k.
640                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                // Ensure the proof does NOT verify if we use the previous value.
646                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    /// Build a tiny database and confirm exclusion proofs work as expected.
658    ///
659    /// Tests empty-db exclusion, single-key exclusion, two-key exclusion with cycle-around
660    /// and inner spans, and re-emptied-db exclusion. Also verifies that wrong proofs and
661    /// wrong roots are rejected.
662    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            // We should be able to prove exclusion for any key against an empty db.
680            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            // Add `key_exists_1` and test exclusion proving over the single-key database case.
689            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            // We shouldn't be able to generate an exclusion proof for a key already in the db.
700            let result = db.exclusion_proof(&key_exists_1).await;
701            assert!(matches!(result, Err(Error::KeyExists)));
702
703            // Generate some valid exclusion proofs for keys on either side.
704            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            // Since there's only one span in the DB, the two exclusion proofs should be identical,
710            // and the proof should verify any key but the one that exists in the db.
711            assert_eq!(proof, proof2);
712            // Any key except the one that exists should verify against this proof.
713            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            // Exclusion should fail if we test it on a key that exists.
724            assert!(!TestDb::<F, C, V>::verify_exclusion_proof(
725                &key_exists_1,
726                &proof,
727                &root,
728            ));
729
730            // Add a second key and test exclusion proving over the two-key database case.
731            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            // Use a lesser/greater key that has a translated-key conflict based
744            // on our use of OneCap translator.
745            let lesser_key = Sha256::fill(0x0F); // < k1=0x10
746            let greater_key = Sha256::fill(0x31); // > k2=0x30
747            let middle_key = Sha256::fill(0x20); // between k1=0x10 and k2=0x30
748            let proof = db.exclusion_proof(&greater_key).await.unwrap();
749            // Test the "cycle around" span. This should prove exclusion of greater_key & lesser
750            // key, but fail on middle_key.
751            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            // Due to the cycle, lesser & greater keys should produce the same proof.
768            let new_proof = db.exclusion_proof(&lesser_key).await.unwrap();
769            assert_eq!(proof, new_proof);
770
771            // Test the inner span [k, k2).
772            let proof = db.exclusion_proof(&middle_key).await.unwrap();
773            // `k` should fail since it's in the db.
774            assert!(!TestDb::<F, C, V>::verify_exclusion_proof(
775                &key_exists_1,
776                &proof,
777                &root,
778            ));
779            // `middle_key` should succeed since it's in range.
780            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); // between k1=0x10 and k2=0x30
792            assert!(TestDb::<F, C, V>::verify_exclusion_proof(
793                &conflicting_middle_key,
794                &proof,
795                &root,
796            ));
797
798            // Using lesser/greater keys for the middle-proof should fail.
799            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            // Make the DB empty again by deleting the keys and check the empty case
811            // again.
812            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            // This root should be different than the empty root from earlier since the DB now has a
823            // non-zero number of operations.
824            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            // Try fooling the verifier with improper values.
841            assert!(!TestDb::<F, C, V>::verify_exclusion_proof(
842                &key_exists_1,
843                &empty_proof, // wrong proof
844                &root,
845            ));
846            assert!(!TestDb::<F, C, V>::verify_exclusion_proof(
847                &key_exists_1,
848                &proof,
849                &empty_root, // wrong root
850            ));
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]; // unknown tag
967        bytes.extend_from_slice(&[0u8; 32]); // garbage
968        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}