Skip to content

WIP: End-to-end testing of encryption/decryption with concurrent CGKA ops #75

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

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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 keyhive_core/benches/bench_cgka.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use keyhive_core::{
use divan::Bencher;
use keyhive_core::{
cgka::{
error::CgkaError,
test_utils::{
31 changes: 31 additions & 0 deletions keyhive_core/src/cgka.rs
Original file line number Diff line number Diff line change
@@ -132,6 +132,7 @@ impl Cgka {
let current_pcs_key = if !self.has_pcs_key() {
let new_share_secret_key = ShareSecretKey::generate(csprng);
let new_share_key = new_share_secret_key.share_key();
println!("!@ new_app_secret update op for {}", self.owner_id);
let (pcs_key, update_op) = self.update(new_share_key, new_share_secret_key, csprng)?;
self.insert_pcs_key(&pcs_key, Digest::hash(&update_op));
op = Some(update_op);
@@ -163,17 +164,21 @@ impl Cgka {
&mut self,
encrypted: &EncryptedContent<T, Cr>,
) -> Result<SymmetricKey, CgkaError> {
println!("!@ decryption_key_for 0");
let pcs_key =
self.pcs_key_from_hashes(&encrypted.pcs_key_hash, &encrypted.pcs_update_op_hash)?;
println!("!@ decryption_key_for 1");
if !self.pcs_keys.contains_key(&encrypted.pcs_key_hash) {
self.insert_pcs_key(&pcs_key, encrypted.pcs_update_op_hash);
}
println!("!@ decryption_key_for 2");
let app_secret = pcs_key.derive_application_secret(
&encrypted.nonce,
&encrypted.content_ref,
&encrypted.pred_refs,
&encrypted.pcs_update_op_hash,
);
println!("!@ decryption_key_for 3");
Ok(app_secret.key())
}

@@ -192,6 +197,7 @@ impl Cgka {
if self.tree.contains_id(&id) {
return Ok(None);
}
println!("!@ Calling Cgka::add()");
if self.should_replay() {
self.replay_ops_graph()?;
}
@@ -227,6 +233,7 @@ impl Cgka {
if !self.tree.contains_id(&id) {
return Ok(None);
}
println!("!@ Calling Cgka::remove()");
if self.should_replay() {
self.replay_ops_graph()?;
}
@@ -254,6 +261,7 @@ impl Cgka {
new_sk: ShareSecretKey,
csprng: &mut R,
) -> Result<(PcsKey, CgkaOperation), CgkaError> {
println!("!@ Calling Cgka::update()");
if self.should_replay() {
self.replay_ops_graph()?;
}
@@ -269,6 +277,7 @@ impl Cgka {
predecessors,
doc_id: self.doc_id,
};
println!("!@ -- Op Digest: {}", Digest::hash(&op));
self.ops_graph.add_local_op(&op);
self.insert_pcs_key(&pcs_key, Digest::hash(&op));
Ok((pcs_key, op))
@@ -289,6 +298,11 @@ impl Cgka {
/// membership changes and we receive a concurrent update, we can apply it
/// immediately.
pub fn merge_concurrent_operation(&mut self, op: Rc<CgkaOperation>) -> Result<(), CgkaError> {
println!(
"\nMerging operation at {}: {:?}\n",
self.owner_id,
op.name()
);
if self.ops_graph.contains_op_hash(&Digest::hash(op.borrow())) {
return Ok(());
}
@@ -315,6 +329,10 @@ impl Cgka {
Ok(())
}

pub fn ops(&self) -> Result<NonEmpty<CgkaEpoch>, CgkaError> {
self.ops_graph.topsort_graph()
}

/// Apply a [`CgkaOperation`].
fn apply_operation(&mut self, op: Rc<CgkaOperation>) -> Result<(), CgkaError> {
if self.ops_graph.contains_op_hash(&Digest::hash(op.borrow())) {
@@ -397,15 +415,20 @@ impl Cgka {
pcs_key_hash: &Digest<PcsKey>,
update_op_hash: &Digest<CgkaOperation>,
) -> Result<PcsKey, CgkaError> {
println!("pcs_key_from_hashes 0");
if let Some(pcs_key) = self.pcs_keys.get(pcs_key_hash) {
println!("pcs_key_from_hashes DONE");
Ok(*pcs_key.clone())
} else {
if self.has_pcs_key() {
println!("pcs_key_from_hashes tree root");
let pcs_key = self.pcs_key_from_tree_root()?;
println!("pcs_key_from_hashes tree root FOUND");
if &Digest::hash(&pcs_key) == pcs_key_hash {
return Ok(pcs_key);
}
}
println!("pcs_key_from_hashes derive pcs key");
self.derive_pcs_key_for_op(update_op_hash)
}
}
@@ -415,12 +438,15 @@ impl Cgka {
&mut self,
op_hash: &Digest<CgkaOperation>,
) -> Result<PcsKey, CgkaError> {
println!("!@ derive_pcs_key_for_op 0");
if !self.ops_graph.contains_op_hash(op_hash) {
return Err(CgkaError::UnknownPcsKey);
}
println!("!@ derive_pcs_key_for_op 1");
let mut heads = HashSet::new();
heads.insert(*op_hash);
let ops = self.ops_graph.topsort_for_heads(&heads)?;
println!("!@ derive_pcs_key_for_op 2");
self.rebuild_pcs_key(ops)
}

@@ -459,12 +485,17 @@ impl Cgka {
epochs.last()[0].borrow(),
&CgkaOperation::Update { .. }
));
println!("rebuild_pcs_key 0");
let mut rebuilt_cgka =
Cgka::new(self.doc_id, self.original_member.0, self.original_member.1)?
.with_new_owner(self.owner_id, self.owner_sks.clone())?;
println!("rebuild_pcs_key 1");
rebuilt_cgka.apply_epochs(&epochs)?;
println!("rebuild_pcs_key 2");
let pcs_key = rebuilt_cgka.pcs_key_from_tree_root()?;
println!("rebuild_pcs_key 3");
self.insert_pcs_key(&pcs_key, Digest::hash(&epochs.last()[0]));
println!("rebuild_pcs_key 4");
Ok(pcs_key)
}

3 changes: 3 additions & 0 deletions keyhive_core/src/cgka/beekem.rs
Original file line number Diff line number Diff line change
@@ -279,6 +279,7 @@ impl BeeKem {
sks: &mut ShareKeyMap,
csprng: &mut R,
) -> Result<Option<(PcsKey, PathChange)>, CgkaError> {
println!("!@ encrypt_path by {}: pk {}", id, pk);
let leaf_idx = *self.leaf_index_for_id(id)?;
debug_assert!(self.id_for_leaf(leaf_idx).unwrap() == id);
let mut new_path = PathChange {
@@ -330,6 +331,8 @@ impl BeeKem {
/// Applies a [`PathChange`] representing new public and encrypted secret keys for each
/// node on a path.
pub(crate) fn apply_path(&mut self, new_path: &PathChange) {
println!("!@ apply_path received from {}", new_path.leaf_id);

// If this id has been concurrently removed, it might no longer be present
// when we try to apply the concurrent update at that id.
if !self.id_to_leaf_idx.contains_key(&new_path.leaf_id) {
17 changes: 16 additions & 1 deletion keyhive_core/src/cgka/operation.rs
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@ use topological_sort::TopologicalSort;

/// An ordered [`NonEmpty`] of concurrent [`CgkaOperation`]s.
#[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) struct CgkaEpoch(NonEmpty<Rc<CgkaOperation>>);
pub struct CgkaEpoch(NonEmpty<Rc<CgkaOperation>>);

impl From<NonEmpty<Rc<CgkaOperation>>> for CgkaEpoch {
fn from(item: NonEmpty<Rc<CgkaOperation>>) -> Self {
@@ -61,6 +61,15 @@ pub enum CgkaOperation {
}

impl CgkaOperation {
// FIXME: Remove
pub(crate) fn name(&self) -> String {
match self {
CgkaOperation::Add { .. } => String::from("Add"),
CgkaOperation::Remove { .. } => String::from("Remove"),
CgkaOperation::Update { .. } => String::from("Update"),
}
}

/// The zero or more immediate causal predecessors of this operation.
pub(crate) fn predecessors(&self) -> HashSet<Digest<CgkaOperation>> {
match self {
@@ -135,11 +144,13 @@ impl CgkaOperationGraph {

/// Add an operation that was created locally to the graph.
pub(crate) fn add_local_op(&mut self, op: &CgkaOperation) {
println!("!@ OpsGraph: adding local {}", Digest::hash(op));
self.add_op_and_update_heads(op, None);
}

/// Add an operation to the graph.
pub(crate) fn add_op(&mut self, op: &CgkaOperation, heads: &HashSet<Digest<CgkaOperation>>) {
println!("!@ OpsGraph: adding remote {}", Digest::hash(op));
self.add_op_and_update_heads(op, Some(heads));
}

@@ -211,6 +222,7 @@ impl CgkaOperationGraph {
&self,
heads: &HashSet<Digest<CgkaOperation>>,
) -> Result<NonEmpty<CgkaEpoch>, CgkaError> {
println!("!@ topsort_for_heads 0");
debug_assert!(heads.iter().all(|head| self.cgka_ops.contains_key(head)));
let mut op_hashes = Vec::new();
let mut dependencies = TopologicalSort::<Digest<CgkaOperation>>::new();
@@ -225,9 +237,11 @@ impl CgkaOperationGraph {
}
// Populate dependencies and successors with all ancestors of the initial heads.
while let Some(op_hash) = frontier.pop_front() {
println!("!@ topsort_for_heads loopy op_hash: {}", op_hash);
let preds = self
.predecessors_for(&op_hash)
.ok_or(CgkaError::OperationNotFound)?;
println!("!@ -- preds: {:?}", preds);
for update_pred in preds {
dependencies.add_dependency(*update_pred, op_hash);
successors.entry(*update_pred).or_default().insert(op_hash);
@@ -304,6 +318,7 @@ impl CgkaOperationGraph {
}
}
for hash in next_set {
println!("!@ topsort_for_heads pushy");
next_epoch.push(
self.cgka_ops
.get(&hash)
Loading