Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding support for HistoryProofV2 #433

Merged
merged 1 commit into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion akd/benches/directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ fn history_generation<TC: NamedConfiguration>(c: &mut Criterion) {

// generate for the most recent 10 updates
let label = AkdLabel::from("User 1");
let params = akd::HistoryParams::MostRecentInsecure(5);
let params = akd::HistoryParams::MostRecent(5);
runtime
.block_on(directory.key_history(&label, params))
.unwrap();
Expand Down
6 changes: 3 additions & 3 deletions akd/src/append_only_zks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ impl Azks {
/// before it is written to storage. The is_new flag indicates whether the
/// returned node is new or not.
#[async_recursion]
#[allow(clippy::multiple_bound_locations)]
pub(crate) async fn recursive_batch_insert_nodes<TC: Configuration, S: Database + 'static>(
storage: &StorageManager<S>,
node_label: Option<NodeLabel>,
Expand Down Expand Up @@ -880,6 +881,7 @@ impl Azks {

#[async_recursion]
#[allow(clippy::type_complexity)]
#[allow(clippy::multiple_bound_locations)]
async fn get_append_only_proof_helper<TC: Configuration, S: Database + 'static>(
latest_epoch: u64,
storage: &StorageManager<S>,
Expand Down Expand Up @@ -1669,9 +1671,7 @@ mod tests {
// Recursively traverse the tree and check that the sibling of each node is correct
let root_node = TreeNode::get_from_storage(&db, &NodeKey(NodeLabel::root()), 1).await?;
let mut nodes: Vec<TreeNode> = vec![root_node];
while !nodes.is_empty() {
let current_node = nodes.pop().unwrap();

while let Some(current_node) = nodes.pop() {
let left_child = current_node.get_child_node(&db, Direction::Left, 1).await?;
let right_child = current_node
.get_child_node(&db, Direction::Right, 1)
Expand Down
112 changes: 43 additions & 69 deletions akd/src/directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ use crate::storage::types::{DbRecord, ValueState, ValueStateRetrievalFlag};
use crate::storage::Database;
use crate::{
AkdLabel, AkdValue, AppendOnlyProof, AzksElement, Digest, EpochHash, HistoryProof, LookupProof,
NonMembershipProof, UpdateProof,
UpdateProof,
};

use crate::VersionFreshness;
use akd_core::configuration::Configuration;
use akd_core::utils::get_marker_versions;
use akd_core::verify::history::HistoryParams;
use log::{error, info};
use std::collections::{HashMap, HashSet};
use std::marker::PhantomData;
Expand Down Expand Up @@ -465,18 +467,7 @@ where
// apply filters specified by HistoryParams struct
user_data = match params {
HistoryParams::Complete => user_data,
HistoryParams::MostRecentInsecure(n) => {
user_data.into_iter().take(n).collect::<Vec<_>>()
}
HistoryParams::SinceEpochInsecure(epoch) => {
user_data = user_data
.into_iter()
.filter(|val| val.epoch >= epoch)
.collect::<Vec<_>>();
// Ordering should be maintained after filtering, but let's re-sort just in case
user_data.sort_by(|a, b| b.epoch.cmp(&a.epoch));
user_data
}
HistoryParams::MostRecent(n) => user_data.into_iter().take(n).collect::<Vec<_>>(),
};

if user_data.is_empty() {
Expand All @@ -502,61 +493,65 @@ where
}

let mut update_proofs = Vec::<UpdateProof>::new();
let mut last_version = 0;
let mut start_version = user_data[0].version;
let mut end_version = 0;
for user_state in user_data {
// Ignore states in storage that are ahead of current directory epoch
if user_state.epoch <= current_epoch {
let proof = self
.create_single_update_proof(akd_label, &user_state)
.await?;
update_proofs.push(proof);
last_version = if user_state.version > last_version {
user_state.version
} else {
last_version
};
start_version = std::cmp::min(user_state.version, start_version);
end_version = std::cmp::max(user_state.version, end_version);
}
}
let next_marker = get_marker_version(last_version) + 1;
let final_marker = get_marker_version(current_epoch);

let mut until_marker_vrf_proofs = Vec::<Vec<u8>>::new();
let mut non_existence_until_marker_proofs = Vec::<NonMembershipProof>::new();
if start_version == 0 {
return Err(AkdError::Directory(DirectoryError::InvalidVersion(
"Computed start version for the key history should be non-zero".to_string(),
)));
}

let (past_marker_versions, future_marker_versions) =
get_marker_versions(start_version, end_version, current_epoch);

let mut past_marker_vrf_proofs = vec![];
let mut existence_of_past_marker_proofs = vec![];

for ver in last_version + 1..(1 << next_marker) {
let label_for_ver = self
for version in past_marker_versions {
let node_label = self
.vrf
.get_node_label::<TC>(akd_label, VersionFreshness::Fresh, ver)
.get_node_label::<TC>(akd_label, VersionFreshness::Fresh, version)
.await?;
let non_existence_of_ver = current_azks
.get_non_membership_proof::<TC, _>(&self.storage, label_for_ver)
let existence_vrf = self
.vrf
.get_label_proof::<TC>(akd_label, VersionFreshness::Fresh, version)
.await?;
non_existence_until_marker_proofs.push(non_existence_of_ver);
until_marker_vrf_proofs.push(
self.vrf
.get_label_proof::<TC>(akd_label, VersionFreshness::Fresh, ver)
.await?
.to_bytes()
.to_vec(),
past_marker_vrf_proofs.push(existence_vrf.to_bytes().to_vec());
existence_of_past_marker_proofs.push(
current_azks
.get_membership_proof::<TC, _>(&self.storage, node_label)
.await?,
);
}

let mut future_marker_vrf_proofs = Vec::<Vec<u8>>::new();
let mut non_existence_of_future_marker_proofs = Vec::<NonMembershipProof>::new();
let mut future_marker_vrf_proofs = vec![];
let mut non_existence_of_future_marker_proofs = vec![];

for marker_power in next_marker..final_marker + 1 {
let ver = 1 << marker_power;
let label_for_ver = self
for version in future_marker_versions {
let node_label = self
.vrf
.get_node_label::<TC>(akd_label, VersionFreshness::Fresh, ver)
.await?;
let non_existence_of_ver = current_azks
.get_non_membership_proof::<TC, _>(&self.storage, label_for_ver)
.get_node_label::<TC>(akd_label, VersionFreshness::Fresh, version)
.await?;
non_existence_of_future_marker_proofs.push(non_existence_of_ver);
non_existence_of_future_marker_proofs.push(
current_azks
.get_non_membership_proof::<TC, _>(&self.storage, node_label)
.await?,
);
future_marker_vrf_proofs.push(
self.vrf
.get_label_proof::<TC>(akd_label, VersionFreshness::Fresh, ver)
.get_label_proof::<TC>(akd_label, VersionFreshness::Fresh, version)
.await?
.to_bytes()
.to_vec(),
Expand All @@ -571,8 +566,8 @@ where
Ok((
HistoryProof {
update_proofs,
until_marker_vrf_proofs,
non_existence_until_marker_proofs,
past_marker_vrf_proofs,
existence_of_past_marker_proofs,
future_marker_vrf_proofs,
non_existence_of_future_marker_proofs,
},
Expand Down Expand Up @@ -861,27 +856,6 @@ where
}
}

/// The parameters that dictate how much of the history proof to return to the consumer
/// (either a complete history, or some limited form).
#[derive(Copy, Clone)]
pub enum HistoryParams {
/// Returns a complete history for a label
Complete,
/// Returns up to the most recent N updates for a label. This is not secure, and
/// should not be used in a production environment.
MostRecentInsecure(usize),
/// Returns all updates since a specified epoch (inclusive). This is not secure, and
/// should not be used in a production environment.
SinceEpochInsecure(u64),
}

impl Default for HistoryParams {
/// By default, we return a complete history
fn default() -> Self {
Self::Complete
}
}

/// Helpers

pub(crate) fn get_marker_version(version: u64) -> u64 {
Expand Down
5 changes: 5 additions & 0 deletions akd/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ pub enum DirectoryError {
ReadOnlyDirectory(String),
/// Publish
Publish(String),
/// Detected an invalid version
InvalidVersion(String),
}

impl std::error::Error for DirectoryError {}
Expand All @@ -249,6 +251,9 @@ impl fmt::Display for DirectoryError {
Self::Publish(inner_message) => {
write!(f, "Directory publish error: {inner_message}")
}
Self::InvalidVersion(inner_message) => {
write!(f, "Invalid version error: {inner_message}")
}
}
}
}
Expand Down
20 changes: 16 additions & 4 deletions akd/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,18 @@
//! with respect to the latest root hash and public key, as follows. This function
//! returns a list of values that have been associated with the specified entry, in
//! reverse chronological order.
//!
//!
//! We also use [`HistoryVerificationParams`] as an argument to the verification function, which encodes two values.
//! First, it encodes a [`HistoryParams`]. Note that the same argument for [`HistoryParams`] that was used to generate
//! the key history proof must also be used to verify the proof. Otherwise, verification may fail.
//!
//! [`HistoryVerificationParams`] also allows the consumer to specify whether or not a "tombstoned" value should be
//! accepted in place of a valid value for the corresponding entry. This is useful
//! in scenarios where the consumer wishes to verify that a particular entry exists,
//! but does not care about the value associated with it. The default behavior is to
//! not accept tombstoned values, but [`HistoryVerificationParams::AllowMissingValues`] can
//! be specified to enable this behavior.
//! ```
//! # use akd::storage::StorageManager;
//! # use akd::storage::memory::AsyncInMemoryDatabase;
Expand Down Expand Up @@ -430,8 +442,7 @@
//! The [HistoryParams] enum can be used to limit the number of updates for a given entry that the server provides
//! to the client. The enum has the following options:
//! - [HistoryParams::Complete]: Includes a complete history of all updates to an entry. This is the default option.
//! - [HistoryParams::MostRecentInsecure]: Includes (at most) the most recent input number of updates for an entry.
//! - [HistoryParams::SinceEpochInsecure]: Includes all updates to an entry since a given epoch.
//! - [HistoryParams::MostRecent]: Includes (at most) the most recent input number of updates for an entry.
//!
//! Note that the "insecure" options are not recommended for use in production, as they do not provide a
//! complete history of updates, and lack inclusion proofs for earlier entries. These options should only be
Expand Down Expand Up @@ -492,7 +503,8 @@ pub mod tree_node;
pub mod local_auditing;

pub use akd_core::{
configuration, configuration::*, ecvrf, hash, hash::Digest, proto, types::*, verify, ARITY,
configuration, configuration::*, ecvrf, hash, hash::Digest, proto, types::*, verify,
verify::history::HistoryParams, ARITY,
};

#[macro_use]
Expand All @@ -501,7 +513,7 @@ mod utils;
// ========== Type re-exports which are commonly used ========== //
pub use append_only_zks::Azks;
pub use client::HistoryVerificationParams;
pub use directory::{Directory, HistoryParams};
pub use directory::Directory;
pub use helper_structs::EpochHash;

// ========== Constants and type aliases ========== //
Expand Down
7 changes: 4 additions & 3 deletions akd/src/local_auditing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,12 @@ pub struct AuditBlobName {
pub current_hash: Digest,
}

impl std::string::ToString for AuditBlobName {
fn to_string(&self) -> String {
impl std::fmt::Display for AuditBlobName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let previous_hash = hex::encode(self.previous_hash);
let current_hash = hex::encode(self.current_hash);
format!(
write!(
f,
"{}{}{}{}{}",
self.epoch, NAME_SEPARATOR, previous_hash, NAME_SEPARATOR, current_hash
)
Expand Down
Loading
Loading