diff --git a/keyhive_core/benches/bench_cgka.rs b/keyhive_core/benches/bench_cgka.rs index dd35c020..1d2e3513 100644 --- a/keyhive_core/benches/bench_cgka.rs +++ b/keyhive_core/benches/bench_cgka.rs @@ -1,5 +1,5 @@ -use keyhive_core::{ use divan::Bencher; +use keyhive_core::{ cgka::{ error::CgkaError, test_utils::{ diff --git a/keyhive_core/src/cgka.rs b/keyhive_core/src/cgka.rs index 714e398c..f6659ca4 100644 --- a/keyhive_core/src/cgka.rs +++ b/keyhive_core/src/cgka.rs @@ -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, ) -> Result { + 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) -> 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, CgkaError> { + self.ops_graph.topsort_graph() + } + /// Apply a [`CgkaOperation`]. fn apply_operation(&mut self, op: Rc) -> Result<(), CgkaError> { if self.ops_graph.contains_op_hash(&Digest::hash(op.borrow())) { @@ -397,15 +415,20 @@ impl Cgka { pcs_key_hash: &Digest, update_op_hash: &Digest, ) -> Result { + 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, ) -> Result { + 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) } diff --git a/keyhive_core/src/cgka/beekem.rs b/keyhive_core/src/cgka/beekem.rs index ac0f8730..b5bb7a6e 100644 --- a/keyhive_core/src/cgka/beekem.rs +++ b/keyhive_core/src/cgka/beekem.rs @@ -279,6 +279,7 @@ impl BeeKem { sks: &mut ShareKeyMap, csprng: &mut R, ) -> Result, 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) { diff --git a/keyhive_core/src/cgka/operation.rs b/keyhive_core/src/cgka/operation.rs index ed6d06d6..6ebc8fba 100644 --- a/keyhive_core/src/cgka/operation.rs +++ b/keyhive_core/src/cgka/operation.rs @@ -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>); +pub struct CgkaEpoch(NonEmpty>); impl From>> for CgkaEpoch { fn from(item: NonEmpty>) -> 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> { 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>) { + 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>, ) -> Result, 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::>::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) diff --git a/keyhive_core/src/keyhive.rs b/keyhive_core/src/keyhive.rs index 5ea339d4..ebba7649 100644 --- a/keyhive_core/src/keyhive.rs +++ b/keyhive_core/src/keyhive.rs @@ -322,32 +322,43 @@ impl< ) } + // FIXME: I've temporarily changed this to return EncryptedContentWithUpdate + // to make progress on writing end-to-end decrypt/encrypt tests. pub fn try_encrypt_content( &mut self, doc: Rc>>, content_ref: &T, pred_refs: &Vec, content: &[u8], - ) -> Result, T>, EncryptContentError> { - let EncryptedContentWithUpdate { - encrypted_content, - update_op, - } = doc.borrow_mut().try_encrypt_content( + ) -> Result, EncryptContentError> { + // ) -> Result, T>, EncryptContentError> { + // let EncryptedContentWithUpdate { + // encrypted_content, + // update_op, + // } = doc.borrow_mut().try_encrypt_content( + // content_ref, + // content, + // pred_refs, + // &mut self.csprng, + // )?; + + // if let Some(found_update_op) = update_op { + // let signed_op = Rc::new( + // self.try_sign(found_update_op) + // .map_err(EncryptContentError::SignCgkaOpError)?, + // ); + // self.event_listener.on_cgka_op(&signed_op); + // } + + // Ok(encrypted_content) + // } + let res = doc.borrow_mut().try_encrypt_content( content_ref, content, pred_refs, &mut self.csprng, )?; - - if let Some(found_update_op) = update_op { - let signed_op = Rc::new( - self.try_sign(found_update_op) - .map_err(EncryptContentError::SignCgkaOpError)?, - ); - self.event_listener.on_cgka_op(&signed_op); - } - - Ok(encrypted_content) + Ok(res) } pub fn try_decrypt_content( @@ -803,6 +814,7 @@ impl< .get(&subject_id.into()) .and_then(|content_heads| NonEmpty::collect(content_heads.iter().cloned())) { + println!("!@ Inserting doc into self.docs!"); let doc = Document::from_group(group, &self.active.borrow(), content_heads)?; self.docs.insert(doc.doc_id(), Rc::new(RefCell::new(doc))); } else { @@ -1327,6 +1339,7 @@ mod tests { use crate::{access::Access, principal::public::Public}; use nonempty::nonempty; use pretty_assertions::assert_eq; + use rand::RngCore; #[test] fn test_archival_round_trip() { @@ -1571,4 +1584,164 @@ mod tests { assert_eq!(dlg.delegation.subject_id(), doc.borrow().doc_id().into()); } + + // TODO: + // * Replace Public with real individuals + // * Break out helper functions + // * Concurrent combinations + // * Add group to doc + // * Add group to multiple docs, then add member to that group + + fn make_indie() -> Rc> { + let mut csprng = rand::thread_rng(); + let indie_sk = ed25519_dalek::SigningKey::generate(&mut csprng); + Rc::new(RefCell::new( + Individual::generate(&indie_sk.into(), &mut csprng).unwrap(), + )) + } + + fn initialize_keyhives_and_doc(count: usize) -> (Vec, DocumentId) { + let mut csprng = rand::thread_rng(); + let mut keyhives = Vec::new(); + let mut first = make_keyhive(); + keyhives.push(first); + let indie = make_indie(); + let doc = keyhives[0] + .generate_doc(vec![indie.dupe().into()], nonempty![[0u8; 32]]) + .unwrap(); + let cgka_epochs = doc.borrow().cgka_ops().unwrap(); + for _ in 0..count - 1 { + let mut next = make_keyhive(); + let ops = keyhives[0].membership_ops_for_agent(&indie.dupe().into()); + for (_h, op) in &ops { + next.receive_op(&op.clone().into()).unwrap(); + } + for epoch in &cgka_epochs { + for op in epoch.iter() { + next.receive_cgka_op((**op).clone()).unwrap(); + } + } + keyhives.push(next); + } + let doc_id = doc.borrow().doc_id(); + for keyhive in &mut keyhives { + keyhive.get_document(doc_id).unwrap(); + } + (keyhives, doc_id) + } + + fn share_delegation( + delegation: Rc>>, + keyhives: &mut Vec, + sharer_idx: usize, + ) { + let static_dlg = Signed { + issuer: delegation.issuer, + signature: delegation.signature, + payload: delegation.payload.clone().into(), + }; + for idx in 0..keyhives.len() { + if idx == sharer_idx { + continue; + } + keyhives[idx].receive_delegation(&static_dlg).unwrap(); + } + } + + fn share_ops_for_agent(agent: Agent, keyhives: &mut Vec, sharer_idx: usize) -> usize { + let mut share_count = 0; + for idx in 0..keyhives.len() { + if idx == sharer_idx { + continue; + } + let op_store = keyhives[sharer_idx].membership_ops_for_agent(&agent); + + for (_h, op) in op_store.iter() { + share_count += 1; + keyhives[idx].receive_op(&op.clone().into()).unwrap(); + } + } + + share_count + } + + fn share_cgka_op(op: &CgkaOperation, keyhives: &mut Vec, sharer_idx: usize) { + for idx in 0..keyhives.len() { + if idx == sharer_idx { + continue; + } + keyhives[idx].receive_cgka_op(op.clone()).unwrap(); + } + } + + #[test] + fn test_sharing_ops_to_debug() { + let (mut keyhives, _doc_id) = initialize_keyhives_and_doc(2); + let a_idx = 0; + let b_idx = 1; + + let a_peer: Peer = Rc::new(RefCell::new( + keyhives[a_idx].active.borrow().individual.clone(), + )) + .into(); + + let second_group = keyhives[b_idx].generate_group(vec![a_peer]).unwrap(); + let share_count = + share_ops_for_agent(keyhives[a_idx].active.dupe().into(), &mut keyhives, 1); + assert!(share_count > 0); + + let indie = make_indie(); + let add = keyhives[b_idx] + .add_member( + indie.dupe().into(), + &mut second_group.dupe().into(), + Access::Read, + &[], + ) + .unwrap(); + share_delegation(add.delegation.dupe(), &mut keyhives, 1); + let indie_op_count = share_ops_for_agent(indie.dupe().into(), &mut keyhives, 1); + assert!(indie_op_count > 0); + } + + // #[test] + // fn test_concurrent_cgka_operations() { + // let (mut keyhives, doc_id) = initialize_keyhives_and_doc(2); + // let a_idx = 0; + // let b_idx = 1; + + // let a_doc = keyhives[a_idx].get_document(doc_id).unwrap().dupe(); + // let cgka_op = keyhives[a_idx].force_pcs_update(a_doc.dupe()).unwrap(); + // share_cgka_op(&cgka_op, &mut keyhives, a_idx); + + // let content = "Let's start!"; + // let content_ref = generate_content_ref(); + // let pred_refs = Vec::new(); + // let encrypted_with_update = keyhives[a_idx].try_encrypt_content(a_doc.dupe(), &content_ref, &pred_refs, content.as_bytes()).unwrap(); + // assert_eq!(content, &String::from_utf8(keyhives[a_idx].try_decrypt_content(a_doc, &encrypted_with_update.encrypted_content).unwrap()).unwrap()); + + // if let Some(op) = encrypted_with_update.update_op { + // keyhives[b_idx].receive_cgka_op(op.clone()).unwrap(); + // } + // let encrypted = encrypted_with_update.encrypted_content; + // let b_doc = keyhives[b_idx].get_document(doc_id).unwrap().dupe(); + // let b_content: String = String::from_utf8(keyhives[b_idx].try_decrypt_content(b_doc.dupe(), &encrypted).unwrap()).unwrap(); + // assert_eq!(content, &b_content); + + // let second_group = keyhives[b_idx].generate_group(vec![]).unwrap(); + // share_ops_for_agent(&second_group.dupe().into(), &mut keyhives, 1); + // let indie = make_indie(); + // let add = keyhives[b_idx] + // .add_member(indie.dupe().into(), &mut second_group.dupe().into(), Access::Read, &[]) + // .unwrap(); + // // share_delegation(add.delegation.dupe(), &mut keyhives, b_idx); + // share_ops_for_agent(&indie.dupe().into(), &mut keyhives, b_idx); + // } + + fn generate_content_ref() -> [u8; 32] { + let mut csprng = rand::thread_rng(); + let mut content_ref = [0u8; 32]; + csprng.fill_bytes(&mut content_ref); + content_ref + } } diff --git a/keyhive_core/src/listener.rs b/keyhive_core/src/listener.rs index cf6716af..86b7ab8a 100644 --- a/keyhive_core/src/listener.rs +++ b/keyhive_core/src/listener.rs @@ -1,5 +1,5 @@ -pub mod deque; pub mod cgka; +pub mod deque; pub mod membership; pub mod no_listener; pub mod prekey; diff --git a/keyhive_core/src/principal/document.rs b/keyhive_core/src/principal/document.rs index a732726f..31042c0e 100644 --- a/keyhive_core/src/principal/document.rs +++ b/keyhive_core/src/principal/document.rs @@ -3,7 +3,12 @@ pub mod id; use super::{group::AddGroupMemberError, individual::id::IndividualId}; use crate::{ access::Access, - cgka::{error::CgkaError, keys::ShareKeyMap, operation::CgkaOperation, Cgka}, + cgka::{ + error::CgkaError, + keys::ShareKeyMap, + operation::{CgkaEpoch, CgkaOperation}, + Cgka, + }, content::reference::ContentRef, crypto::{ digest::Digest, @@ -155,10 +160,6 @@ impl> Document { .cloned(), ); } - let (_pcs_key, update_op) = cgka.update(owner_share_key, owner_share_secret_key, csprng)?; - // FIXME: We don't currently do anything with these ops, but need to share them - // across the network. - ops.push(update_op); Ok(Document { group, reader_keys: HashMap::new(), // FIXME @@ -260,6 +261,10 @@ impl> Document { self.cgka.merge_concurrent_operation(Rc::new(op)) } + pub fn cgka_ops(&self) -> Result, CgkaError> { + self.cgka.ops() + } + pub fn pcs_update( &mut self, csprng: &mut R, @@ -430,7 +435,8 @@ pub enum GenerateDocError { #[derive(Debug, Clone, PartialEq, Eq)] pub struct EncryptedContentWithUpdate { - pub(crate) encrypted_content: EncryptedContent, T>, + // FIXME: Changed visibility to get tests running + pub encrypted_content: EncryptedContent, T>, pub(crate) update_op: Option, } diff --git a/keyhive_core/src/store/delegation.rs b/keyhive_core/src/store/delegation.rs index 814b50cc..64634015 100644 --- a/keyhive_core/src/store/delegation.rs +++ b/keyhive_core/src/store/delegation.rs @@ -30,6 +30,10 @@ impl> DelegationStore { borrowed.get(key).cloned() } + pub fn len(&self) -> usize { + self.0.borrow().len() + } + pub fn contains_key(&self, key: &Digest>>) -> bool { let rc = self.0.dupe(); let borrowed = RefCell::borrow(&rc); diff --git a/keyhive_wasm/src/js/keyhive.rs b/keyhive_wasm/src/js/keyhive.rs index 55f252b8..e3c6ddc0 100644 --- a/keyhive_wasm/src/js/keyhive.rs +++ b/keyhive_wasm/src/js/keyhive.rs @@ -118,6 +118,9 @@ impl JsKeyhive { Ok(self .0 .try_encrypt_content(doc.0, &content_ref, &pred_refs, content)? + // FIXME: This is based on temporary change to return type to get tests + // running. + .encrypted_content .into()) } @@ -133,6 +136,9 @@ impl JsKeyhive { Ok(self .0 .try_encrypt_content(doc.0, &content_ref, &pred_refs, content)? + // FIXME: This is based on temporary change to return type to get tests + // running. + .encrypted_content .into()) }