Skip to content

Commit 9f5db64

Browse files
spike
1 parent 325c42c commit 9f5db64

3 files changed

Lines changed: 120 additions & 56 deletions

File tree

storage/src/qmdb/any/ordered/mod.rs

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ pub use crate::qmdb::any::operation::{update::Ordered as Update, Ordered as Oper
2626
/// Type alias for a location and its associated key data.
2727
type LocatedKey<F, K, V> = Option<(Location<F>, Update<K, V>)>;
2828

29+
/// Whether the ordered-key span defined by `span_start` and `span_end` contains `key`.
30+
pub fn span_contains<K: Key>(span_start: &K, span_end: &K, key: &K) -> bool {
31+
if span_start >= span_end {
32+
key >= span_start || key < span_end
33+
} else {
34+
key >= span_start && key < span_end
35+
}
36+
}
37+
2938
impl<
3039
F: Family,
3140
E: Context,
@@ -50,23 +59,6 @@ where
5059
}
5160
}
5261

53-
/// Whether the span defined by `span_start` and `span_end` contains `key`.
54-
pub fn span_contains(span_start: &K, span_end: &K, key: &K) -> bool {
55-
if span_start >= span_end {
56-
// cyclic span case
57-
if key >= span_start || key < span_end {
58-
return true;
59-
}
60-
} else {
61-
// normal span case
62-
if key >= span_start && key < span_end {
63-
return true;
64-
}
65-
}
66-
67-
false
68-
}
69-
7062
/// Find the span produced by the provided locations that contains `key`, if any.
7163
async fn find_span(
7264
&self,
@@ -77,7 +69,7 @@ where
7769
for loc in locs {
7870
// Iterate over conflicts in the snapshot entry to find the span.
7971
let data = Self::get_update_op(&reader, loc).await?;
80-
if Self::span_contains(&data.key, &data.next_key, key) {
72+
if span_contains(&data.key, &data.next_key, key) {
8173
return Ok(Some((loc, data)));
8274
}
8375
}

storage/src/qmdb/current/ordered/db.rs

Lines changed: 51 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,53 @@ impl<F: merkle::Graftable, K: Key, D: Digest, const N: usize> Read for KeyValueP
6161
}
6262
}
6363

64+
impl<F: merkle::Graftable, K: Key, D: Digest, const N: usize> KeyValueProof<F, K, D, N> {
65+
/// Return true if this proof authenticates `update` against `root`.
66+
///
67+
/// This also checks that the proof's embedded `next_key` matches the provided update, which is
68+
/// required when the operation is decoded separately from the proof.
69+
pub fn verify_update<H, V>(
70+
&self,
71+
hasher: &StandardHasher<H>,
72+
update: &Update<K, V>,
73+
root: &D,
74+
) -> bool
75+
where
76+
H: Hasher<Digest = D>,
77+
V: ValueEncoding,
78+
Operation<F, K, V>: Codec,
79+
{
80+
if self.next_key != update.next_key {
81+
return false;
82+
}
83+
84+
self.proof
85+
.verify(hasher, Operation::Update(update.clone()), root)
86+
}
87+
88+
/// Return true if this proof authenticates `operation` as the active key-value update.
89+
pub fn verify_operation<H, V>(
90+
&self,
91+
hasher: &StandardHasher<H>,
92+
operation: &Operation<F, K, V>,
93+
root: &D,
94+
) -> bool
95+
where
96+
H: Hasher<Digest = D>,
97+
V: ValueEncoding,
98+
Operation<F, K, V>: Codec + Clone,
99+
{
100+
let Operation::Update(update) = operation else {
101+
return false;
102+
};
103+
if self.next_key != update.next_key {
104+
return false;
105+
}
106+
107+
self.proof.verify(hasher, operation.clone(), root)
108+
}
109+
}
110+
64111
#[cfg(feature = "arbitrary")]
65112
impl<F: merkle::Graftable, K: Key, D: Digest, const N: usize> arbitrary::Arbitrary<'_>
66113
for KeyValueProof<F, K, D, N>
@@ -113,13 +160,13 @@ where
113160
proof: &KeyValueProof<F, K, H::Digest, N>,
114161
root: &H::Digest,
115162
) -> bool {
116-
let op = Operation::Update(Update {
163+
let update = Update {
117164
key,
118165
value,
119166
next_key: proof.next_key.clone(),
120-
});
167+
};
121168

122-
proof.proof.verify(hasher, op, root)
169+
proof.verify_update(hasher, &update, root)
123170
}
124171

125172
/// Get the operation that currently defines the span whose range contains `key`, or None if the
@@ -148,37 +195,7 @@ where
148195
proof: &super::ExclusionProof<F, K, V, H::Digest, N>,
149196
root: &H::Digest,
150197
) -> bool {
151-
let (op_proof, op) = match proof {
152-
super::ExclusionProof::KeyValue(op_proof, data) => {
153-
if data.key == *key {
154-
// The provided `key` is in the DB if it matches the start of the span.
155-
return false;
156-
}
157-
if !crate::qmdb::any::db::Db::<F, E, C, I, H, Update<K, V>, N, S>::span_contains(
158-
&data.key,
159-
&data.next_key,
160-
key,
161-
) {
162-
// If the key is not within the span, then this proof cannot prove its
163-
// exclusion.
164-
return false;
165-
}
166-
167-
(op_proof, Operation::Update(data.clone()))
168-
}
169-
super::ExclusionProof::Commit(op_proof, metadata) => {
170-
// Handle the case where the proof shows the db is empty, hence any key is proven
171-
// excluded. For the db to be empty, the floor must equal the commit operation's
172-
// location.
173-
let floor_loc = op_proof.loc;
174-
(
175-
op_proof,
176-
Operation::CommitFloor(metadata.clone(), floor_loc),
177-
)
178-
}
179-
};
180-
181-
op_proof.verify(hasher, op, root)
198+
proof.verify_key(hasher, key, root)
182199
}
183200
}
184201

storage/src/qmdb/current/ordered/mod.rs

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,19 @@
99
//! - [variable]: Variant for values of variable size.
1010
1111
use crate::{
12-
merkle::Graftable,
12+
merkle::{hasher::Standard as StandardHasher, Graftable},
1313
qmdb::{
14-
any::{ordered::Update, ValueEncoding},
14+
any::{
15+
ordered::{self, Operation, Update},
16+
ValueEncoding,
17+
},
1518
current::proof::OperationProof,
1619
operation::Key,
1720
},
1821
};
1922
use bytes::{Buf, BufMut};
20-
use commonware_codec::{EncodeSize, Read, ReadExt as _, Write};
21-
use commonware_cryptography::Digest;
23+
use commonware_codec::{Codec, EncodeSize, Read, ReadExt as _, Write};
24+
use commonware_cryptography::{Digest, Hasher};
2225

2326
pub mod db;
2427
pub mod fixed;
@@ -46,6 +49,43 @@ pub enum ExclusionProof<F: Graftable, K: Key, V: ValueEncoding, D: Digest, const
4649
Commit(OperationProof<F, D, N>, Option<V::Value>),
4750
}
4851

52+
impl<F: Graftable, K: Key, V: ValueEncoding, D: Digest, const N: usize>
53+
ExclusionProof<F, K, V, D, N>
54+
where
55+
Operation<F, K, V>: Codec,
56+
{
57+
/// Return true if this proof authenticates that `key` is not active at `root`.
58+
pub fn verify_key<H>(&self, hasher: &StandardHasher<H>, key: &K, root: &D) -> bool
59+
where
60+
H: Hasher<Digest = D>,
61+
{
62+
let (op_proof, operation) = match self {
63+
Self::KeyValue(op_proof, update) => {
64+
if update.key == *key {
65+
// The provided `key` is in the DB if it matches the start of the span.
66+
return false;
67+
}
68+
if !ordered::span_contains(&update.key, &update.next_key, key) {
69+
// If the key is not within the span, then this proof cannot prove its
70+
// exclusion.
71+
return false;
72+
}
73+
74+
(op_proof, Operation::Update(update.clone()))
75+
}
76+
Self::Commit(op_proof, metadata) => {
77+
// Handle the case where the proof shows the db is empty, hence any key is proven
78+
// excluded. For the db to be empty, the floor must equal the commit operation's
79+
// location.
80+
let floor = op_proof.loc;
81+
(op_proof, Operation::CommitFloor(metadata.clone(), floor))
82+
}
83+
};
84+
85+
op_proof.verify(hasher, operation, root)
86+
}
87+
}
88+
4989
const KEY_VALUE_CONTEXT: u8 = 0;
5090
const COMMIT_CONTEXT: u8 = 1;
5191

@@ -587,6 +627,14 @@ pub mod tests {
587627
assert!(TestDb::<F, C, V>::verify_key_value_proof(
588628
&hasher, key, value, &proof, &root
589629
));
630+
let update = Update {
631+
key,
632+
value,
633+
next_key: proof.next_key,
634+
};
635+
assert!(proof.verify_update(&hasher, &update, &root));
636+
assert!(proof.verify_operation(&hasher, &Operation::Update(update.clone()), &root));
637+
assert!(!proof.verify_operation(&hasher, &Operation::Delete(key), &root));
590638
// Proof should fail against the wrong value. Use hash instead of fill to ensure
591639
// the value differs from any key/value created by TestKey::from_seed (which uses
592640
// fill patterns).
@@ -614,6 +662,9 @@ pub mod tests {
614662
assert!(!TestDb::<F, C, V>::verify_key_value_proof(
615663
&hasher, key, value, &bad_proof, &root,
616664
));
665+
let mut bad_update = update;
666+
bad_update.next_key = wrong_key;
667+
assert!(!proof.verify_update(&hasher, &bad_update, &root));
617668
}
618669

619670
db.destroy().await.unwrap();
@@ -705,6 +756,7 @@ pub mod tests {
705756
&empty_proof,
706757
&empty_root,
707758
));
759+
assert!(empty_proof.verify_key(&hasher, &key_exists_1, &empty_root));
708760

709761
// Add `key_exists_1` and test exclusion proving over the single-key database case.
710762
let v1 = Sha256::fill(0xA1);
@@ -737,6 +789,7 @@ pub mod tests {
737789
&proof,
738790
&root,
739791
));
792+
assert!(proof.verify_key(&hasher, &greater_key, &root));
740793
assert!(TestDb::<F, C, V>::verify_exclusion_proof(
741794
&hasher,
742795
&lesser_key,
@@ -750,6 +803,7 @@ pub mod tests {
750803
&proof,
751804
&root,
752805
));
806+
assert!(!proof.verify_key(&hasher, &key_exists_1, &root));
753807

754808
// Add a second key and test exclusion proving over the two-key database case.
755809
let key_exists_2 = Sha256::fill(0x30);
@@ -811,6 +865,7 @@ pub mod tests {
811865
&proof,
812866
&root,
813867
));
868+
assert!(proof.verify_key(&hasher, &middle_key, &root));
814869
assert!(!TestDb::<F, C, V>::verify_exclusion_proof(
815870
&hasher,
816871
&key_exists_2,

0 commit comments

Comments
 (0)