use std::collections::HashMap;
use crate::{errors::DirectoryError, test_config};
use akd_core::{configuration::Configuration, hash::DIGEST_BYTES};
use rand::{rngs::StdRng, SeedableRng};
use crate::{
auditor::{audit_verify, verify_consecutive_append_only},
client::{key_history_verify, lookup_verify},
directory::{Directory, PublishCorruption, ReadOnlyDirectory},
ecvrf::{HardCodedAkdVRF, VRFKeyStorage},
errors::{AkdError, StorageError},
storage::{
manager::StorageManager,
memory::AsyncInMemoryDatabase,
types::{DbRecord, KeyData, ValueState, ValueStateRetrievalFlag},
Database, DbSetState, Storable,
},
tree_node::TreeNodeWithPreviousValue,
AkdLabel, AkdValue, AppendOnlyProof, Azks, EpochHash, HistoryParams, HistoryVerificationParams,
VerifyResult,
};
#[derive(Clone)]
pub struct LocalDatabase;
unsafe impl Send for LocalDatabase {}
unsafe impl Sync for LocalDatabase {}
mockall::mock! {
pub LocalDatabase {
}
impl Clone for LocalDatabase {
fn clone(&self) -> Self;
}
#[async_trait::async_trait]
impl Database for LocalDatabase {
async fn set(&self, record: DbRecord) -> Result<(), StorageError>;
async fn batch_set(
&self,
records: Vec<DbRecord>,
state: DbSetState,
) -> Result<(), StorageError>;
async fn get<St: Storable>(&self, id: &St::StorageKey) -> Result<DbRecord, StorageError>;
async fn batch_get<St: Storable>(
&self,
ids: &[St::StorageKey],
) -> Result<Vec<DbRecord>, StorageError>;
async fn get_user_data(&self, username: &AkdLabel) -> Result<KeyData, StorageError>;
async fn get_user_state(
&self,
username: &AkdLabel,
flag: ValueStateRetrievalFlag,
) -> Result<ValueState, StorageError>;
async fn get_user_state_versions(
&self,
usernames: &[AkdLabel],
flag: ValueStateRetrievalFlag,
) -> Result<HashMap<AkdLabel, (u64, AkdValue)>, StorageError>;
}
}
fn setup_mocked_db(db: &mut MockLocalDatabase, test_db: &AsyncInMemoryDatabase) {
let tmp_db = test_db.clone();
db.expect_set()
.returning(move |record| futures::executor::block_on(tmp_db.set(record)));
let tmp_db = test_db.clone();
db.expect_batch_set().returning(move |record, other| {
futures::executor::block_on(tmp_db.batch_set(record, other))
});
let tmp_db = test_db.clone();
db.expect_get::<Azks>()
.returning(move |key| futures::executor::block_on(tmp_db.get::<Azks>(key)));
let tmp_db = test_db.clone();
db.expect_get::<TreeNodeWithPreviousValue>()
.returning(move |key| {
futures::executor::block_on(tmp_db.get::<TreeNodeWithPreviousValue>(key))
});
let tmp_db = test_db.clone();
db.expect_get::<Azks>()
.returning(move |key| futures::executor::block_on(tmp_db.get::<Azks>(key)));
let tmp_db = test_db.clone();
db.expect_batch_get::<Azks>()
.returning(move |key| futures::executor::block_on(tmp_db.batch_get::<Azks>(key)));
let tmp_db = test_db.clone();
db.expect_batch_get::<TreeNodeWithPreviousValue>()
.returning(move |key| {
futures::executor::block_on(tmp_db.batch_get::<TreeNodeWithPreviousValue>(key))
});
let tmp_db = test_db.clone();
db.expect_batch_get::<Azks>()
.returning(move |key| futures::executor::block_on(tmp_db.batch_get::<Azks>(key)));
let tmp_db = test_db.clone();
db.expect_get_user_data()
.returning(move |arg| futures::executor::block_on(tmp_db.get_user_data(arg)));
let tmp_db = test_db.clone();
db.expect_get_user_state()
.returning(move |arg, flag| futures::executor::block_on(tmp_db.get_user_state(arg, flag)));
let tmp_db = test_db.clone();
db.expect_get_user_state_versions()
.returning(move |arg, flag| {
futures::executor::block_on(tmp_db.get_user_state_versions(arg, flag))
});
}
test_config!(test_empty_tree_root_hash);
async fn test_empty_tree_root_hash<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd: Directory<_, AsyncInMemoryDatabase, HardCodedAkdVRF> =
Directory::<TC, _, _>::new(storage, vrf).await?;
let hash = akd.get_epoch_hash().await?.1;
assert_eq!(
TC::compute_root_hash_from_val(&TC::empty_root_value()),
hash
);
Ok(())
}
test_config!(test_simple_publish);
async fn test_simple_publish<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<TC, _, _>::new(storage, vrf).await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world"))])
.await?;
Ok(())
}
test_config!(test_complex_publish);
async fn test_complex_publish<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<TC, _, _>::new(storage, vrf).await?;
let num_entries = 10000;
let mut entries = vec![];
let mut rng = StdRng::seed_from_u64(42);
for _ in 0..num_entries {
let label = AkdLabel::random(&mut rng);
let value = AkdValue::random(&mut rng);
entries.push((label, value));
}
akd.publish(entries).await?;
Ok(())
}
test_config!(test_simple_lookup);
async fn test_simple_lookup<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<TC, _, _>::new(storage, vrf).await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world")),
(AkdLabel::from("hello2"), AkdValue::from("world2")),
])
.await?;
let (lookup_proof, root_hash) = akd.lookup(AkdLabel::from("hello")).await?;
let vrf_pk = akd.get_public_key().await?;
lookup_verify::<TC>(
vrf_pk.as_bytes(),
root_hash.hash(),
root_hash.epoch(),
AkdLabel::from("hello"),
lookup_proof,
)?;
Ok(())
}
test_config!(test_small_key_history);
async fn test_small_key_history<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<TC, _, _>::new(storage, vrf).await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world"))])
.await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world2"))])
.await?;
let (key_history_proof, root_hash) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::default())
.await?;
let vrf_pk = akd.get_public_key().await?;
let result = key_history_verify::<TC>(
vrf_pk.as_bytes(),
root_hash.hash(),
root_hash.epoch(),
AkdLabel::from("hello"),
key_history_proof,
HistoryVerificationParams::default(),
)?;
assert_eq!(
result,
vec![
VerifyResult {
epoch: 2,
version: 2,
value: AkdValue::from("world2"),
},
VerifyResult {
epoch: 1,
version: 1,
value: AkdValue::from("world"),
},
]
);
Ok(())
}
test_config!(test_simple_key_history);
async fn test_simple_key_history<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<TC, _, _>::new(storage, vrf).await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world")),
(AkdLabel::from("hello2"), AkdValue::from("world2")),
])
.await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world_2")),
(AkdLabel::from("hello2"), AkdValue::from("world2_2")),
])
.await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world3")),
(AkdLabel::from("hello2"), AkdValue::from("world4")),
])
.await?;
akd.publish(vec![
(AkdLabel::from("hello3"), AkdValue::from("world")),
(AkdLabel::from("hello4"), AkdValue::from("world2")),
])
.await?;
akd.publish(vec![(
AkdLabel::from("hello"),
AkdValue::from("world_updated"),
)])
.await?;
akd.publish(vec![
(AkdLabel::from("hello3"), AkdValue::from("world6")),
(AkdLabel::from("hello4"), AkdValue::from("world12")),
])
.await?;
let (key_history_proof, _) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::default())
.await?;
if key_history_proof.update_proofs.len() != 4 {
return Err(AkdError::TestErr(format!(
"Key history proof should have 4 update_proofs but has {:?}",
key_history_proof.update_proofs.len()
)));
}
let EpochHash(current_epoch, root_hash) = akd.get_epoch_hash().await?;
let vrf_pk = akd.get_public_key().await?;
key_history_verify::<TC>(
vrf_pk.as_bytes(),
root_hash,
current_epoch,
AkdLabel::from("hello"),
key_history_proof,
HistoryVerificationParams::default(),
)?;
let (key_history_proof, _) = akd
.key_history(&AkdLabel::from("hello2"), HistoryParams::default())
.await?;
if key_history_proof.update_proofs.len() != 3 {
return Err(AkdError::TestErr(format!(
"Key history proof should have 3 update_proofs but has {:?}",
key_history_proof.update_proofs.len()
)));
}
key_history_verify::<TC>(
vrf_pk.as_bytes(),
root_hash,
current_epoch,
AkdLabel::from("hello2"),
key_history_proof,
HistoryVerificationParams::default(),
)?;
let (key_history_proof, _) = akd
.key_history(&AkdLabel::from("hello3"), HistoryParams::default())
.await?;
if key_history_proof.update_proofs.len() != 2 {
return Err(AkdError::TestErr(format!(
"Key history proof should have 2 update_proofs but has {:?}",
key_history_proof.update_proofs.len()
)));
}
key_history_verify::<TC>(
vrf_pk.as_bytes(),
root_hash,
current_epoch,
AkdLabel::from("hello3"),
key_history_proof,
HistoryVerificationParams::default(),
)?;
let (key_history_proof, _) = akd
.key_history(&AkdLabel::from("hello4"), HistoryParams::default())
.await?;
if key_history_proof.update_proofs.len() != 2 {
return Err(AkdError::TestErr(format!(
"Key history proof should have 2 update_proofs but has {:?}",
key_history_proof.update_proofs.len()
)));
}
key_history_verify::<TC>(
vrf_pk.as_bytes(),
root_hash,
current_epoch,
AkdLabel::from("hello4"),
key_history_proof.clone(),
HistoryVerificationParams::default(),
)?;
let mut borked_proof = key_history_proof;
borked_proof.update_proofs = borked_proof.update_proofs.into_iter().rev().collect();
let result = key_history_verify::<TC>(
vrf_pk.as_bytes(),
root_hash,
current_epoch,
AkdLabel::from("hello4"),
borked_proof,
HistoryVerificationParams::default(),
);
assert!(matches!(result, Err(_)), "{}", "{result:?}");
Ok(())
}
test_config!(test_complex_verification_many_versions);
async fn test_complex_verification_many_versions<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage_manager = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<TC, _, _>::new(storage_manager, vrf).await?;
let vrf_pk = akd.get_public_key().await?;
let num_labels = 4;
let num_iterations = 20;
let mut previous_hash = [0u8; DIGEST_BYTES];
for epoch in 1..num_iterations {
let mut to_insert = vec![];
for i in 0..num_labels {
let index = 1 << i;
let label = AkdLabel::from(format!("{index}").as_str());
let value = AkdValue::from(format!("{index},{epoch}").as_str());
if epoch % index == 0 {
to_insert.push((label, value));
}
}
let epoch_hash = akd.publish(to_insert).await?;
if epoch > 1 {
let audit_proof = akd
.audit(epoch_hash.epoch() - 1, epoch_hash.epoch())
.await?;
crate::auditor::audit_verify::<TC>(vec![previous_hash, epoch_hash.hash()], audit_proof)
.await?;
}
previous_hash = epoch_hash.hash();
for i in 0..num_labels {
let index = 1 << i;
if epoch < index {
continue;
}
let latest_added_epoch = epoch_hash.epoch() - (epoch_hash.epoch() % index);
let label = AkdLabel::from(format!("{index}").as_str());
let lookup_value = AkdValue::from(format!("{index},{latest_added_epoch}").as_str());
let (lookup_proof, epoch_hash_from_lookup) = akd.lookup(label.clone()).await?;
assert_eq!(epoch_hash, epoch_hash_from_lookup);
let lookup_verify_result = lookup_verify::<TC>(
vrf_pk.as_bytes(),
epoch_hash.hash(),
epoch_hash.epoch(),
label.clone(),
lookup_proof,
)?;
assert_eq!(lookup_verify_result.epoch, latest_added_epoch);
assert_eq!(lookup_verify_result.value, lookup_value);
assert_eq!(lookup_verify_result.version, epoch / index);
let (history_proof, epoch_hash_from_history) =
akd.key_history(&label, HistoryParams::Complete).await?;
assert_eq!(epoch_hash, epoch_hash_from_history);
let history_results = key_history_verify::<TC>(
vrf_pk.as_bytes(),
epoch_hash.hash(),
epoch_hash.epoch(),
label,
history_proof,
HistoryVerificationParams::default(),
)?;
for (j, res) in history_results.iter().enumerate() {
let added_in_epoch =
epoch_hash.epoch() - (epoch_hash.epoch() % index) - (j as u64) * index;
let history_value = AkdValue::from(format!("{index},{added_in_epoch}").as_str());
assert_eq!(res.epoch, added_in_epoch);
assert_eq!(res.value, history_value);
assert_eq!(res.version, epoch / index - j as u64);
}
}
}
Ok(())
}
test_config!(test_limited_key_history);
async fn test_limited_key_history<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage_manager = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<TC, _, _>::new(storage_manager, vrf).await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world")),
(AkdLabel::from("hello2"), AkdValue::from("world2")),
])
.await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world_2")),
(AkdLabel::from("hello2"), AkdValue::from("world2_2")),
])
.await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world3")),
(AkdLabel::from("hello2"), AkdValue::from("world4")),
])
.await?;
akd.publish(vec![
(AkdLabel::from("hello3"), AkdValue::from("world")),
(AkdLabel::from("hello4"), AkdValue::from("world2")),
])
.await?;
akd.publish(vec![(
AkdLabel::from("hello"),
AkdValue::from("world_updated"),
)])
.await?;
akd.publish(vec![
(AkdLabel::from("hello3"), AkdValue::from("world6")),
(AkdLabel::from("hello4"), AkdValue::from("world12")),
])
.await?;
akd.publish(vec![
(AkdLabel::from("hello3"), AkdValue::from("world7")),
(AkdLabel::from("hello4"), AkdValue::from("world13")),
])
.await?;
let vrf_pk = akd.get_public_key().await?;
let current_azks = akd.retrieve_azks().await?;
let current_epoch = current_azks.get_latest_epoch();
let (history_proof, root_hash) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::MostRecent(1))
.await?;
assert_eq!(1, history_proof.update_proofs.len());
assert_eq!(5, history_proof.update_proofs[0].epoch);
key_history_verify::<TC>(
vrf_pk.as_bytes(),
root_hash.hash(),
current_epoch,
AkdLabel::from("hello"),
history_proof,
HistoryVerificationParams::default(),
)?;
let (history_proof, root_hash) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::MostRecent(3))
.await?;
assert_eq!(3, history_proof.update_proofs.len());
assert_eq!(5, history_proof.update_proofs[0].epoch);
assert_eq!(3, history_proof.update_proofs[1].epoch);
assert_eq!(2, history_proof.update_proofs[2].epoch);
key_history_verify::<TC>(
vrf_pk.as_bytes(),
root_hash.hash(),
current_epoch,
AkdLabel::from("hello"),
history_proof,
HistoryVerificationParams::default(),
)?;
let (history_proof, root_hash) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::SinceEpoch(3))
.await?;
assert_eq!(2, history_proof.update_proofs.len());
assert_eq!(5, history_proof.update_proofs[0].epoch);
assert_eq!(3, history_proof.update_proofs[1].epoch);
key_history_verify::<TC>(
vrf_pk.as_bytes(),
root_hash.hash(),
current_epoch,
AkdLabel::from("hello"),
history_proof,
HistoryVerificationParams::default(),
)?;
Ok(())
}
test_config!(test_malicious_key_history);
async fn test_malicious_key_history<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<TC, _, _>::new(storage, vrf).await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world"))])
.await?;
let corruption_2 = PublishCorruption::UnmarkedStaleVersion(AkdLabel::from("hello"));
akd.publish_malicious_update(
vec![(AkdLabel::from("hello"), AkdValue::from("world2"))],
corruption_2,
)
.await?;
let (key_history_proof, root_hash) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::default())
.await?;
let vrf_pk = akd.get_public_key().await?;
key_history_verify::<TC>(
vrf_pk.as_bytes(),
root_hash.hash(),
root_hash.epoch(),
AkdLabel::from("hello"),
key_history_proof,
HistoryVerificationParams::default(),
).expect_err("The key history proof should fail here since the previous value was not marked stale at all");
let corruption_3 = PublishCorruption::MarkVersionStale(AkdLabel::from("hello"), 1);
akd.publish_malicious_update(
vec![(AkdLabel::from("hello2"), AkdValue::from("world"))],
corruption_3,
)
.await?;
let (key_history_proof, root_hash) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::default())
.await?;
let vrf_pk = akd.get_public_key().await?;
key_history_verify::<TC>(
vrf_pk.as_bytes(),
root_hash.hash(),
root_hash.epoch(),
AkdLabel::from("hello"),
key_history_proof,
HistoryVerificationParams::default(),
).expect_err("The key history proof should fail here since the previous value was marked stale one epoch too late.");
Ok(())
}
test_config!(test_simple_audit);
async fn test_simple_audit<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<TC, _, _>::new(storage, vrf).await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world")),
(AkdLabel::from("hello2"), AkdValue::from("world2")),
])
.await?;
let root_hash_1 = akd.get_epoch_hash().await?.1;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world_2")),
(AkdLabel::from("hello2"), AkdValue::from("world2_2")),
])
.await?;
let root_hash_2 = akd.get_epoch_hash().await?.1;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world3")),
(AkdLabel::from("hello2"), AkdValue::from("world4")),
])
.await?;
let root_hash_3 = akd.get_epoch_hash().await?.1;
akd.publish(vec![
(AkdLabel::from("hello3"), AkdValue::from("world")),
(AkdLabel::from("hello4"), AkdValue::from("world2")),
])
.await?;
let root_hash_4 = akd.get_epoch_hash().await?.1;
akd.publish(vec![(
AkdLabel::from("hello"),
AkdValue::from("world_updated"),
)])
.await?;
let root_hash_5 = akd.get_epoch_hash().await?.1;
akd.publish(vec![
(AkdLabel::from("hello3"), AkdValue::from("world6")),
(AkdLabel::from("hello4"), AkdValue::from("world12")),
])
.await?;
let root_hash_6 = akd.get_epoch_hash().await?.1;
let audit_proof_1 = akd.audit(1, 2).await?;
audit_verify::<TC>(vec![root_hash_1, root_hash_2], audit_proof_1).await?;
let audit_proof_2 = akd.audit(1, 3).await?;
audit_verify::<TC>(vec![root_hash_1, root_hash_2, root_hash_3], audit_proof_2).await?;
let audit_proof_3 = akd.audit(1, 4).await?;
audit_verify::<TC>(
vec![root_hash_1, root_hash_2, root_hash_3, root_hash_4],
audit_proof_3,
)
.await?;
let audit_proof_4 = akd.audit(1, 5).await?;
audit_verify::<TC>(
vec![
root_hash_1,
root_hash_2,
root_hash_3,
root_hash_4,
root_hash_5,
],
audit_proof_4,
)
.await?;
let audit_proof_5 = akd.audit(2, 3).await?;
audit_verify::<TC>(vec![root_hash_2, root_hash_3], audit_proof_5).await?;
let audit_proof_6 = akd.audit(2, 4).await?;
audit_verify::<TC>(vec![root_hash_2, root_hash_3, root_hash_4], audit_proof_6).await?;
let audit_proof_7 = akd.audit(4, 6).await?;
audit_verify::<TC>(vec![root_hash_4, root_hash_5, root_hash_6], audit_proof_7).await?;
let audit_proof_8 = akd.audit(4, 6).await?;
let invalid_audit_verification = audit_verify::<TC>(
vec![
root_hash_1,
root_hash_2,
root_hash_3,
root_hash_4,
root_hash_5,
],
audit_proof_8,
)
.await;
assert!(matches!(
invalid_audit_verification,
Err(AkdError::AuditErr(_))
));
let audit_proof_9 = akd.audit(1, 5).await?;
let audit_proof_10 = akd.audit(4, 6).await?;
let invalid_audit_proof = AppendOnlyProof {
proofs: audit_proof_10.proofs,
epochs: audit_proof_9.epochs,
};
let invalid_audit_verification = audit_verify::<TC>(
vec![
root_hash_1,
root_hash_2,
root_hash_3,
root_hash_4,
root_hash_5,
],
invalid_audit_proof,
)
.await;
assert!(matches!(
invalid_audit_verification,
Err(AkdError::AuditErr(_))
));
let audit_proof_11 = akd.audit(1, 2).await?;
let verification = verify_consecutive_append_only::<TC>(
&audit_proof_11.proofs[0],
root_hash_1,
root_hash_3, audit_proof_11.epochs[0] + 1,
)
.await;
assert!(matches!(verification, Err(AkdError::AzksErr(_))));
let invalid_audit = akd.audit(3, 3).await;
assert!(matches!(invalid_audit, Err(_)));
let invalid_audit = akd.audit(3, 2).await;
assert!(matches!(invalid_audit, Err(_)));
let invalid_audit = akd.audit(6, 7).await;
assert!(matches!(invalid_audit, Err(_)));
Ok(())
}
test_config!(test_read_during_publish);
async fn test_read_during_publish<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db.clone());
let vrf = HardCodedAkdVRF {};
let akd = Directory::<TC, _, _>::new(storage, vrf).await?;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world")),
(AkdLabel::from("hello2"), AkdValue::from("world2")),
])
.await
.unwrap();
let root_hash_1 = akd.get_epoch_hash().await?.1;
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world_2")),
(AkdLabel::from("hello2"), AkdValue::from("world2_2")),
])
.await
.unwrap();
let root_hash_2 = akd.get_epoch_hash().await?.1;
let checkpoint_azks = akd.retrieve_azks().await.unwrap();
akd.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world_3")),
(AkdLabel::from("hello2"), AkdValue::from("world2_3")),
])
.await
.unwrap();
db.set(DbRecord::Azks(checkpoint_azks))
.await
.expect("Error resetting directory to previous epoch");
let storage = StorageManager::new_no_cache(db.clone());
let vrf = HardCodedAkdVRF {};
let akd = ReadOnlyDirectory::<TC, _, _>::new(storage, vrf)
.await
.unwrap();
let vrf_pk = akd.get_public_key().await.unwrap();
let (lookup_proof, root_hash) = akd.lookup(AkdLabel::from("hello")).await.unwrap();
assert_eq!(AkdValue::from("world_2"), lookup_proof.value);
lookup_verify::<TC>(
vrf_pk.as_bytes(),
root_hash.hash(),
root_hash.epoch(),
AkdLabel::from("hello"),
lookup_proof,
)
.unwrap();
let (history_proof, root_hash) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::default())
.await
.unwrap();
key_history_verify::<TC>(
vrf_pk.as_bytes(),
root_hash.hash(),
root_hash.epoch(),
AkdLabel::from("hello"),
history_proof,
HistoryVerificationParams::default(),
)
.unwrap();
let audit_proof = akd.audit(1, 2).await.unwrap();
audit_verify::<TC>(vec![root_hash_1, root_hash_2], audit_proof)
.await
.unwrap();
let invalid_audit = akd.audit(2, 3).await;
assert!(matches!(invalid_audit, Err(_)));
Ok(())
}
test_config!(test_directory_read_only_mode);
async fn test_directory_read_only_mode<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = ReadOnlyDirectory::<TC, _, _>::new(storage, vrf).await;
assert!(matches!(akd, Err(_)));
Ok(())
}
test_config!(test_directory_polling_azks_change);
async fn test_directory_polling_azks_change<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new(db, None, None, None);
let vrf = HardCodedAkdVRF {};
let writer = Directory::<TC, _, _>::new(storage.clone(), vrf.clone()).await?;
writer
.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world")),
(AkdLabel::from("hello2"), AkdValue::from("world2")),
])
.await?;
let reader = ReadOnlyDirectory::<TC, _, _>::new(storage, vrf).await?;
let (tx, mut rx) = tokio::sync::mpsc::channel(10);
let reader_clone = reader.clone();
let _join_handle = tokio::task::spawn(async move {
reader_clone
.poll_for_azks_changes(tokio::time::Duration::from_millis(100), Some(tx))
.await
});
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
async_poll_helper_proof(&reader, AkdValue::from("world")).await?;
writer
.publish(vec![
(AkdLabel::from("hello"), AkdValue::from("world_2")),
(AkdLabel::from("hello2"), AkdValue::from("world2_2")),
])
.await?;
let notification = tokio::time::timeout(tokio::time::Duration::from_secs(10), rx.recv()).await;
assert!(matches!(notification, Ok(Some(()))));
async_poll_helper_proof(&reader, AkdValue::from("world_2")).await?;
Ok(())
}
test_config!(test_tombstoned_key_history);
async fn test_tombstoned_key_history<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<TC, _, _>::new(storage.clone(), vrf).await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world"))])
.await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world2"))])
.await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world3"))])
.await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world4"))])
.await?;
akd.publish(vec![(AkdLabel::from("hello"), AkdValue::from("world5"))])
.await?;
let vrf_pk = akd.get_public_key().await?;
let tombstones = [
crate::storage::types::ValueStateKey("hello".as_bytes().to_vec(), 1u64),
crate::storage::types::ValueStateKey("hello".as_bytes().to_vec(), 2u64),
];
storage.tombstone_value_states(&tombstones).await?;
let (history_proof, root_hash) = akd
.key_history(&AkdLabel::from("hello"), HistoryParams::default())
.await?;
assert_eq!(5, history_proof.update_proofs.len());
let tombstones = key_history_verify::<TC>(
vrf_pk.as_bytes(),
root_hash.hash(),
root_hash.epoch(),
AkdLabel::from("hello"),
history_proof.clone(),
HistoryVerificationParams::default(),
);
assert!(matches!(tombstones, Err(_)));
let results = key_history_verify::<TC>(
vrf_pk.as_bytes(),
root_hash.hash(),
root_hash.epoch(),
AkdLabel::from("hello"),
history_proof,
HistoryVerificationParams::AllowMissingValues,
)?;
assert_ne!(crate::TOMBSTONE, results[0].value.0);
assert_ne!(crate::TOMBSTONE, results[1].value.0);
assert_ne!(crate::TOMBSTONE, results[2].value.0);
assert_eq!(crate::TOMBSTONE, results[3].value.0);
assert_eq!(crate::TOMBSTONE, results[4].value.0);
Ok(())
}
test_config!(test_publish_op_makes_no_get_requests);
async fn test_publish_op_makes_no_get_requests<TC: Configuration>() -> Result<(), AkdError> {
let test_db = AsyncInMemoryDatabase::new();
let mut db = MockLocalDatabase {
..Default::default()
};
setup_mocked_db(&mut db, &test_db);
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<TC, _, _>::new(storage, vrf)
.await
.expect("Failed to create directory");
let mut updates = vec![];
for i in 0..2 {
updates.push((
AkdLabel(format!("hello1{i}").as_bytes().to_vec()),
AkdValue(format!("hello1{i}").as_bytes().to_vec()),
));
}
akd.publish(updates)
.await
.expect("Failed to do initial publish");
let mut db2 = MockLocalDatabase {
..Default::default()
};
setup_mocked_db(&mut db2, &test_db);
db2.expect_get::<TreeNodeWithPreviousValue>()
.returning(|_| Err(StorageError::Other("Boom!".to_string())));
let storage = StorageManager::new_no_cache(db2);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<TC, _, _>::new(storage, vrf)
.await
.expect("Failed to create directory");
let mut updates = vec![];
for i in 0..2 {
updates.push((
AkdLabel(format!("hello1{i}").as_bytes().to_vec()),
AkdValue(format!("hello1{}", i + 1).as_bytes().to_vec()),
));
}
akd.publish(updates)
.await
.expect("Failed to do subsequent publish");
Ok(())
}
test_config!(test_simple_lookup_for_small_tree);
async fn test_simple_lookup_for_small_tree<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<TC, _, _>::new(storage, vrf.clone()).await?;
let mut updates = vec![];
for i in 0..2 {
updates.push((
AkdLabel(format!("hello1{i}").as_bytes().to_vec()),
AkdValue(format!("hello1{i}").as_bytes().to_vec()),
));
}
akd.publish(updates).await?;
let target_label = AkdLabel(format!("hello1{}", 0).as_bytes().to_vec());
let (lookup_proof, root_hash) = akd.lookup(target_label.clone()).await?;
let vrf_pk = vrf.get_vrf_public_key().await?;
let akd_result = crate::client::lookup_verify::<TC>(
vrf_pk.as_bytes(),
root_hash.hash(),
root_hash.epoch(),
target_label.clone(),
lookup_proof,
)?;
assert_eq!(
akd_result,
VerifyResult {
epoch: 1,
version: 1,
value: AkdValue::from("hello10"),
},
);
Ok(())
}
test_config!(test_lookup_verify_invalid_version_number);
async fn test_lookup_verify_invalid_version_number<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<TC, _, _>::new(storage, vrf.clone()).await?;
let mut updates = vec![];
for i in 0..2 {
updates.push((
AkdLabel(format!("hello1{i}").as_bytes().to_vec()),
AkdValue(format!("hello1{i}").as_bytes().to_vec()),
));
}
for _ in 0..10 {
akd.publish(updates.clone()).await?;
}
let target_label = AkdLabel(format!("hello1{}", 0).as_bytes().to_vec());
let (lookup_proof, root_hash) = akd.lookup(target_label.clone()).await?;
let vrf_pk = vrf.get_vrf_public_key().await?;
let akd_result = crate::client::lookup_verify::<TC>(
vrf_pk.as_bytes(),
root_hash.hash(),
root_hash.epoch() - 1, target_label.clone(),
lookup_proof,
);
match akd_result {
Err(akd_core::verify::VerificationError::LookupProof(_)) => (),
_ => panic!("Expected an invalid epoch error"),
}
Ok(())
}
test_config!(test_publish_duplicate_entries);
async fn test_publish_duplicate_entries<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<TC, _, _>::new(storage, vrf.clone()).await?;
let mut updates = vec![];
for i in 0..10 {
updates.push((
AkdLabel(format!("hello1{i}").as_bytes().to_vec()),
AkdValue(format!("hello1{i}").as_bytes().to_vec()),
));
}
updates.push(updates[0].clone());
let Err(AkdError::Directory(DirectoryError::Publish(_))) = akd.publish(updates).await else {
panic!("Expected a directory publish error");
};
Ok(())
}
test_config!(test_key_history_verify_malformed);
async fn test_key_history_verify_malformed<TC: Configuration>() -> Result<(), AkdError> {
let db = AsyncInMemoryDatabase::new();
let storage = StorageManager::new_no_cache(db);
let vrf = HardCodedAkdVRF {};
let akd = Directory::<TC, _, _>::new(storage, vrf.clone()).await?;
let mut rng = rand::rngs::OsRng;
for _ in 0..100 {
let mut updates = vec![];
updates.push((
AkdLabel(format!("label").as_bytes().to_vec()),
AkdValue::random(&mut rng),
));
akd.publish(updates.clone()).await?;
}
for _ in 0..100 {
let mut updates = vec![];
updates.push((
AkdLabel(format!("another label").as_bytes().to_vec()),
AkdValue::random(&mut rng),
));
akd.publish(updates.clone()).await?;
}
let EpochHash(current_epoch, root_hash) = akd.get_epoch_hash().await?;
let vrf_pk = akd.get_public_key().await?;
let target_label = AkdLabel(format!("label").as_bytes().to_vec());
let (key_history_proof, _) = akd
.key_history(&target_label, HistoryParams::default())
.await?;
key_history_verify::<TC>(
vrf_pk.as_bytes(),
root_hash,
current_epoch,
target_label.clone(),
key_history_proof.clone(),
HistoryVerificationParams::default(),
)?;
let mut malformed_proof_1 = key_history_proof.clone();
malformed_proof_1.until_marker_vrf_proofs = key_history_proof.until_marker_vrf_proofs
[..key_history_proof.until_marker_vrf_proofs.len() - 1]
.to_vec();
let mut malformed_proof_2 = key_history_proof.clone();
malformed_proof_2.non_existence_until_marker_proofs = key_history_proof
.non_existence_until_marker_proofs
[..key_history_proof.non_existence_until_marker_proofs.len() - 1]
.to_vec();
let mut malformed_proof_3 = key_history_proof.clone();
malformed_proof_3.future_marker_vrf_proofs = key_history_proof.future_marker_vrf_proofs
[..key_history_proof.future_marker_vrf_proofs.len() - 1]
.to_vec();
let mut malformed_proof_4 = key_history_proof.clone();
malformed_proof_4.non_existence_of_future_marker_proofs = key_history_proof
.non_existence_of_future_marker_proofs[..key_history_proof
.non_existence_of_future_marker_proofs
.len()
- 1]
.to_vec();
for malformed_proof in [
malformed_proof_1,
malformed_proof_2,
malformed_proof_3,
malformed_proof_4,
] {
assert!(key_history_verify::<TC>(
vrf_pk.as_bytes(),
root_hash,
current_epoch,
target_label.clone(),
malformed_proof,
HistoryVerificationParams::default(),
)
.is_err());
}
Ok(())
}
async fn async_poll_helper_proof<TC: Configuration, T: Database + 'static, V: VRFKeyStorage>(
reader: &ReadOnlyDirectory<TC, T, V>,
value: AkdValue,
) -> Result<(), AkdError> {
let (lookup_proof, root_hash) = reader.lookup(AkdLabel::from("hello")).await?;
assert_eq!(value, lookup_proof.value);
let pk = reader.get_public_key().await?;
lookup_verify::<TC>(
pk.as_bytes(),
root_hash.hash(),
root_hash.epoch(),
AkdLabel::from("hello"),
lookup_proof,
)?;
Ok(())
}