//! Ordered current proofs with fixed-size or runtime-sized bitmap chunks. use super::{COMMIT_CONTEXT, KEY_VALUE_CONTEXT}; use crate::{ merkle::Graftable, qmdb::{ any::{ ValueEncoding, ordered::{Operation, Update}, }, current::proof::operation::Proof as OperationProof, operation::Key, }, }; use bytes::BufMut; use commonware_codec::{Buf, Codec, EncodeSize, Read, ReadExt as _, Write}; use commonware_cryptography::{Digest, Hasher}; use commonware_utils::range::contains_cyclic; /// Proofs with fixed-size bitmap chunks. pub mod constant { /// Proof information for verifying a key has a particular value in the database. pub type KeyValueProof = super::KeyValueProof; /// Proof that a key has no assigned value in the database. pub type ExclusionProof = super::ExclusionProof; } /// Proofs with runtime-sized bitmap chunks. pub mod dynamic { /// Proof information for verifying a key has a particular value in the database. pub type KeyValueProof = super::KeyValueProof; /// Proof that a key has no assigned value in the database. pub type ExclusionProof = super::ExclusionProof; } /// Proof information for verifying a key has a particular value in the database. /// /// `C` stores the embedded operation proof's bitmap chunk. #[derive(Clone, Eq, PartialEq, Debug)] pub struct KeyValueProof { /// The proof authenticating the active update operation. pub proof: OperationProof, /// The next active key in lexicographic order, wrapping at the end. pub next_key: K, } impl> KeyValueProof { /// Return true if the proof authenticates that `key` currently has `value` in the database /// with the provided `root`. /// /// `V` selects the fixed or variable value encoding used by the database. pub fn verify(&self, key: K, value: V::Value, root: &D) -> bool where H: Hasher, V: ValueEncoding, Operation: Codec, { let op = Operation::::Update(Update { key, value, next_key: self.next_key.clone(), }); self.proof.verify::(op, root) } } impl> Write for KeyValueProof { fn write(&self, buf: &mut impl BufMut) { self.proof.write(buf); self.next_key.write(buf); } } impl> EncodeSize for KeyValueProof { fn encode_size(&self) -> usize { self.proof.encode_size() + self.next_key.encode_size() } } impl Read for KeyValueProof where OperationProof: Read, { /// `(proof_cfg, key_cfg)`: the read configurations for the embedded operation proof and key. type Cfg = ( as Read>::Cfg, ::Cfg); fn read_cfg( buf: &mut impl Buf, (proof_cfg, key_cfg): &Self::Cfg, ) -> Result { let proof = OperationProof::::read_cfg(buf, proof_cfg)?; let next_key = K::read_cfg(buf, key_cfg)?; Ok(Self { proof, next_key }) } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for KeyValueProof where K: for<'a> arbitrary::Arbitrary<'a>, OperationProof: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { Ok(Self { proof: u.arbitrary()?, next_key: u.arbitrary()?, }) } } /// Proof that a key has no assigned value in the database. /// /// When the database has active keys, exclusion is proven by showing the key falls within a span /// between two adjacent active keys. Otherwise exclusion is proven by showing the database contains /// no active keys through the most recent commit operation. /// /// `C` stores the embedded operation proof's bitmap chunk. Verify using [Self::verify]. #[derive(Clone, Eq, PartialEq, Debug)] pub enum ExclusionProof { /// Proves that two keys are active in the database and adjacent to each other in the key /// ordering. Any key falling between them (non-inclusively) can be proven excluded. KeyValue(OperationProof, Update), /// Proves that the database has no active keys, allowing any key to be proven excluded. /// The commit operation's activity floor must equal its own location. Commit(OperationProof, Option), } impl ExclusionProof where F: Graftable, K: Key, V: ValueEncoding, D: Digest, C: AsRef<[u8]>, Operation: Codec, { /// Return true if the proof authenticates that `key` does not exist in the database with /// the provided `root`. pub fn verify>(&self, key: &K, root: &D) -> bool { let (op_proof, op) = match self { Self::KeyValue(op_proof, data) => { if data.key == *key || !contains_cyclic(&data.key..&data.next_key, key) { return false; } (op_proof, Operation::Update(data.clone())) } Self::Commit(op_proof, metadata) => { // An empty database's commit floor equals the commit operation's location ( op_proof, Operation::CommitFloor(metadata.clone(), op_proof.loc), ) } }; op_proof.verify::(op, root) } } impl Write for ExclusionProof where F: Graftable, K: Key, V: ValueEncoding, D: Digest, C: AsRef<[u8]>, Update: Write, { fn write(&self, buf: &mut impl BufMut) { match self { Self::KeyValue(op_proof, update) => { KEY_VALUE_CONTEXT.write(buf); op_proof.write(buf); update.write(buf); } Self::Commit(op_proof, value) => { COMMIT_CONTEXT.write(buf); op_proof.write(buf); value.write(buf); } } } } impl EncodeSize for ExclusionProof where F: Graftable, K: Key, V: ValueEncoding, D: Digest, C: AsRef<[u8]>, Update: EncodeSize, { fn encode_size(&self) -> usize { 1 + match self { Self::KeyValue(op_proof, update) => op_proof.encode_size() + update.encode_size(), Self::Commit(op_proof, value) => op_proof.encode_size() + value.encode_size(), } } } impl Read for ExclusionProof where F: Graftable, K: Key, V: ValueEncoding, D: Digest, OperationProof: Read, Update: Read, { /// `(proof_cfg, update_cfg, value_cfg)`: the read configurations for the embedded operation /// proof, [Update], and commit metadata value. type Cfg = ( as Read>::Cfg, as Read>::Cfg, ::Cfg, ); fn read_cfg( buf: &mut impl Buf, (proof_cfg, update_cfg, value_cfg): &Self::Cfg, ) -> Result { match u8::read(buf)? { KEY_VALUE_CONTEXT => { let op_proof = OperationProof::::read_cfg(buf, proof_cfg)?; let update = Update::::read_cfg(buf, update_cfg)?; Ok(Self::KeyValue(op_proof, update)) } COMMIT_CONTEXT => { let op_proof = OperationProof::::read_cfg(buf, proof_cfg)?; let value = Option::::read_cfg(buf, value_cfg)?; Ok(Self::Commit(op_proof, value)) } tag => Err(commonware_codec::Error::InvalidEnum(tag)), } } } #[cfg(feature = "arbitrary")] impl arbitrary::Arbitrary<'_> for ExclusionProof where F: Graftable, K: Key + for<'a> arbitrary::Arbitrary<'a>, V: ValueEncoding, D: Digest, V::Value: for<'a> arbitrary::Arbitrary<'a>, OperationProof: for<'a> arbitrary::Arbitrary<'a>, { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let op_proof = u.arbitrary()?; if u.arbitrary()? { Ok(Self::KeyValue(op_proof, u.arbitrary()?)) } else { Ok(Self::Commit(op_proof, u.arbitrary()?)) } } }