From 0a84519326d24f9e2b8e68fe53f75c0647f36972 Mon Sep 17 00:00:00 2001 From: Niko PLP Date: Fri, 30 Jun 2023 18:44:38 +0300 Subject: [PATCH] wallet_log and CRDT of wallet operations --- ng-sdk-js/src/lib.rs | 1 + ng-wallet/src/lib.rs | 36 ++-- ng-wallet/src/types.rs | 412 +++++++++++++++++++++++++++++++++++++++-- p2p-net/src/types.rs | 14 +- 4 files changed, 424 insertions(+), 39 deletions(-) diff --git a/ng-sdk-js/src/lib.rs b/ng-sdk-js/src/lib.rs index 1baca07..2755606 100644 --- a/ng-sdk-js/src/lib.rs +++ b/ng-sdk-js/src/lib.rs @@ -95,6 +95,7 @@ pub fn test_create_wallet() -> JsValue { 243, 49, 90, 7, 0, 157, 58, 14, 187, 14, 3, 116, 86, ]), 0, + ClientV0::dummy(), ); serde_wasm_bindgen::to_value(&r).unwrap() } diff --git a/ng-wallet/src/lib.rs b/ng-wallet/src/lib.rs index e77a040..54d35b4 100644 --- a/ng-wallet/src/lib.rs +++ b/ng-wallet/src/lib.rs @@ -98,22 +98,22 @@ fn gen_associated_data(timestamp: Timestamp, wallet_id: WalletId) -> Vec { [ser_wallet, timestamp.to_be_bytes().to_vec()].concat() } -pub fn enc_encrypted_block( - block: &EncryptedWalletV0, +pub fn enc_wallet_log( + log: &WalletLog, master_key: &[u8; 32], peer_id: PubKey, nonce: u64, timestamp: Timestamp, wallet_id: WalletId, ) -> Result, NgWalletError> { - let ser_encrypted_block = to_vec(block).map_err(|e| NgWalletError::InternalError)?; + let ser_log = to_vec(log).map_err(|e| NgWalletError::InternalError)?; let nonce_buffer: [u8; 24] = gen_nonce(peer_id, nonce); let cipher = XChaCha20Poly1305::new(master_key.into()); - let mut buffer: Vec = Vec::with_capacity(ser_encrypted_block.len() + 16); // Note: buffer needs 16-bytes overhead for auth tag - buffer.extend_from_slice(&ser_encrypted_block); + let mut buffer: Vec = Vec::with_capacity(ser_log.len() + 16); // Note: buffer needs 16-bytes overhead for auth tag + buffer.extend_from_slice(&ser_log); // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext cipher @@ -154,11 +154,14 @@ pub fn dec_encrypted_block( // `ciphertext` now contains the decrypted block //log_debug!("decrypted_block {:?}", ciphertext); - let decrypted_block = - from_slice::(&ciphertext).map_err(|e| NgWalletError::DecryptionError)?; + let decrypted_log = + from_slice::(&ciphertext).map_err(|e| NgWalletError::DecryptionError)?; master_key.zeroize(); - Ok(decrypted_block) + + match decrypted_log { + WalletLog::V0(v0) => v0.reduce(), + } } pub fn derive_key_from_pass(mut pass: Vec, salt: [u8; 16], wallet_id: WalletId) -> [u8; 32] { @@ -459,18 +462,18 @@ pub async fn create_wallet_v0( //.ok_or(NgWalletError::InternalError)? //.clone(), - let encrypted_block = EncryptedWalletV0 { + let create_op = WalletOpCreateV0 { wallet_privkey: wallet_privkey.clone(), pazzle: pazzle.clone(), mnemonic, pin: params.pin, - sites: vec![site], + personal_site: site, brokers: vec![], //TODO add the broker here - clients: vec![], // TODO add a client here - overlay_core_overrides: HashMap::new(), - third_parties: HashMap::new(), + client: params.client.clone(), }; + let wallet_log = WalletLog::new_v0(create_op); + let mut master_key = [0u8; 32]; getrandom::getrandom(&mut master_key).map_err(|e| NgWalletError::InternalError)?; @@ -506,8 +509,8 @@ pub async fn create_wallet_v0( let timestamp = now_timestamp(); - let encrypted = enc_encrypted_block( - &encrypted_block, + let encrypted = enc_wallet_log( + &wallet_log, &master_key, params.peer_id, params.nonce, @@ -630,6 +633,7 @@ mod test { 98, 243, 49, 90, 7, 0, 157, 58, 14, 187, 14, 3, 116, 86, ]), 0, + ClientV0::dummy(), )) .await .expect("create_wallet_v0"); @@ -690,7 +694,7 @@ mod test { opening_pazzle.elapsed().as_millis() ); } - //log_debug!("encrypted part {:?}", w); + log_debug!("encrypted part {:?}", w); } } } diff --git a/ng-wallet/src/types.rs b/ng-wallet/src/types.rs index eda5eae..49a689f 100644 --- a/ng-wallet/src/types.rs +++ b/ng-wallet/src/types.rs @@ -7,6 +7,7 @@ // notice may not be copied, modified, or distributed except // according to those terms. +use std::hash::{Hash, Hasher}; use std::{collections::HashMap, fmt}; use web_time::SystemTime; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -71,6 +72,17 @@ pub struct ClientV0 { pub auto_open: Vec, } +impl ClientV0 { + #[deprecated(note = "**Don't use dummy method**")] + pub fn dummy() -> Self { + ClientV0 { + priv_key: PrivKey::dummy(), + storage_master_key: SymKey::random(), + auto_open: vec![], + } + } +} + /// EncryptedWallet block Version 0 #[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize)] pub struct EncryptedWalletV0 { @@ -83,17 +95,19 @@ pub struct EncryptedWalletV0 { pub pin: [u8; 4], - // first in the list is the main Site (Personal) #[zeroize(skip)] - pub sites: Vec, + pub personal_site: PubKey, - // list of brokers and their connection details #[zeroize(skip)] - pub brokers: Vec, + pub sites: HashMap, + + // map of brokers and their connection details + #[zeroize(skip)] + pub brokers: HashMap>, - // list of all devices of the user + // map of all devices of the user #[zeroize(skip)] - pub clients: Vec, + pub clients: HashMap, #[zeroize(skip)] pub overlay_core_overrides: HashMap>, @@ -104,6 +118,32 @@ pub struct EncryptedWalletV0 { pub third_parties: HashMap>, } +impl EncryptedWalletV0 { + pub fn add_site(&mut self, site: SiteV0) { + let site_id = site.site_key.to_pub(); + let _ = self.sites.insert(site_id, site); + } + pub fn add_brokers(&mut self, brokers: Vec) { + for broker in brokers { + let key = broker.get_id(); + let mut list = self.brokers.get_mut(&key); + if list.is_none() { + let new_list = vec![]; + self.brokers.insert(key, new_list); + list = self.brokers.get_mut(&key); + } + list.unwrap().push(broker); + } + } + pub fn add_client(&mut self, client: ClientV0) { + let client_id = client.priv_key.to_pub(); + let _ = self.clients.insert(client_id, client); + } + pub fn add_overlay_core_overrides(&mut self, overlay: &OverlayId, cores: &Vec) { + let _ = self.overlay_core_overrides.insert(*overlay, cores.to_vec()); + } +} + /// EncryptedWallet block #[derive(Clone, Debug, Serialize, Deserialize)] pub enum EncryptedWallet { @@ -143,18 +183,36 @@ pub struct WalletContentV0 { pub peer_id: PubKey, pub nonce: u64, - // WalletLog0 content encrypted with XChaCha20Poly1305, AD = timestamp and walletID + // WalletLog content encrypted with XChaCha20Poly1305, AD = timestamp and walletID #[serde(with = "serde_bytes")] pub encrypted: Vec, } -/// Wallet Log +/// Wallet Log V0 #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct WalletLog0 { +pub struct WalletLogV0 { pub log: Vec<(u128, WalletOperationV0)>, } -impl WalletLog0 { +/// Wallet Log +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum WalletLog { + V0(WalletLogV0), +} + +impl WalletLog { + pub fn new_v0(create_op: WalletOpCreateV0) -> Self { + WalletLog::V0(WalletLogV0::new(create_op)) + } +} + +impl WalletLogV0 { + pub fn new(create_op: WalletOpCreateV0) -> Self { + let mut wallet = WalletLogV0 { log: vec![] }; + wallet.add(WalletOperationV0::CreateWalletV0(create_op)); + wallet + } + pub fn add(&mut self, op: WalletOperationV0) { let duration = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) @@ -162,6 +220,204 @@ impl WalletLog0 { .as_nanos(); self.log.push((duration, op)); } + + /// applies all the operation and produces an encrypted wallet object. + pub fn reduce(&self) -> Result { + if self.log.len() < 1 { + Err(NgWalletError::NoCreateWalletPresent) + } else if let (_, WalletOperationV0::CreateWalletV0(create_op)) = &self.log[0] { + let mut wallet: EncryptedWalletV0 = create_op.into(); + + for op in &self.log { + match &op.1 { + WalletOperationV0::CreateWalletV0(_) => { /* intentionally left blank. this op is already reduced */ + } + WalletOperationV0::AddSiteV0(o) => { + if self.is_first_and_not_deleted_afterwards(op, "RemoveSiteV0") { + wallet.add_site(o.clone()); + } + } + WalletOperationV0::RemoveSiteV0(_) => {} + WalletOperationV0::AddBrokerServerV0(o) => { + if self.is_last_and_not_deleted_afterwards(op, "RemoveBrokerServerV0") { + wallet.add_brokers(vec![BrokerInfoV0::ServerV0(o.clone())]); + } + } + WalletOperationV0::RemoveBrokerServerV0(_) => {} + WalletOperationV0::SetBrokerCoreV0(o) => { + if self.is_last_occurrence(op.0, &op.1) != 0 { + wallet.add_brokers(vec![BrokerInfoV0::CoreV0(o.clone())]); + } + } + WalletOperationV0::SetClientV0(o) => { + if self.is_last_occurrence(op.0, &op.1) != 0 { + wallet.add_client(o.clone()); + } + } + WalletOperationV0::AddOverlayCoreOverrideV0((overlay, cores)) => { + if self + .is_last_and_not_deleted_afterwards(op, "RemoveOverlayCoreOverrideV0") + { + wallet.add_overlay_core_overrides(overlay, cores); + } + } + WalletOperationV0::RemoveOverlayCoreOverrideV0(_) => {} + WalletOperationV0::AddSiteCoreV0((site, core)) => { + if self.is_first_and_not_deleted_afterwards(op, "RemoveSiteCoreV0") { + let _ = wallet.sites.get_mut(&site).and_then(|site| { + site.cores.push(*core); + None:: + }); + } + } + WalletOperationV0::RemoveSiteCoreV0(_) => {} + WalletOperationV0::AddSiteBootstrapV0((site, server)) => { + if self.is_first_and_not_deleted_afterwards(op, "RemoveSiteBootstrapV0") { + let _ = wallet.sites.get_mut(&site).and_then(|site| { + site.bootstraps.push(*server); + None:: + }); + } + } + WalletOperationV0::RemoveSiteBootstrapV0(_) => {} + WalletOperationV0::AddThirdPartyDataV0((key, value)) => { + if self.is_last_and_not_deleted_afterwards(op, "RemoveThirdPartyDataV0") { + let _ = wallet.third_parties.insert(key.to_string(), value.to_vec()); + } + } + WalletOperationV0::RemoveThirdPartyDataV0(_) => {} + WalletOperationV0::SetSiteRBDRefV0((site, store_type, rbdr)) => { + if self.is_last_occurrence(op.0, &op.1) != 0 { + let _ = wallet.sites.get_mut(&site).and_then(|site| { + match store_type { + SiteStoreType::Public => { + site.public.root_branch_def_ref = rbdr.clone() + } + SiteStoreType::Protected => { + site.protected.root_branch_def_ref = rbdr.clone() + } + SiteStoreType::Private => { + site.private.root_branch_def_ref = rbdr.clone() + } + }; + None:: + }); + } + } + WalletOperationV0::SetSiteRepoSecretV0((site, store_type, secret)) => { + if self.is_last_occurrence(op.0, &op.1) != 0 { + let _ = wallet.sites.get_mut(&site).and_then(|site| { + match store_type { + SiteStoreType::Public => { + site.public.repo_secret = secret.clone() + } + SiteStoreType::Protected => { + site.protected.repo_secret = secret.clone() + } + SiteStoreType::Private => { + site.private.repo_secret = secret.clone() + } + }; + None:: + }); + } + } + } + } + + Ok(wallet) + } else { + Err(NgWalletError::NoCreateWalletPresent) + } + } + + pub fn is_first_and_not_deleted_afterwards( + &self, + item: &(u128, WalletOperationV0), + delete_type: &str, + ) -> bool { + let hash = self.is_first_occurrence(item.0, &item.1); + if hash != 0 { + // check that it hasn't been deleted since the first occurrence + let deleted = self.find_first_occurrence_of_type_and_hash_after_timestamp( + delete_type, + hash, + item.0, + ); + return deleted.is_none(); + } + false + } + + pub fn is_last_and_not_deleted_afterwards( + &self, + item: &(u128, WalletOperationV0), + delete_type: &str, + ) -> bool { + let hash = self.is_last_occurrence(item.0, &item.1); + if hash != 0 { + // check that it hasn't been deleted since the last occurrence + let deleted = self.find_first_occurrence_of_type_and_hash_after_timestamp( + delete_type, + hash, + item.0, + ); + return deleted.is_none(); + } + false + } + + pub fn is_first_occurrence(&self, timestamp: u128, searched_op: &WalletOperationV0) -> u64 { + let searched_hash = searched_op.hash(); + //let mut timestamp = u128::MAX; + //let mut found = searched_op; + for op in &self.log { + let hash = op.1.hash(); + if hash.0 == searched_hash.0 && op.0 < timestamp && hash.1 == searched_hash.1 { + //timestamp = op.0; + //found = &op.1; + return 0; + } + } + searched_hash.0 + } + + pub fn is_last_occurrence(&self, timestamp: u128, searched_op: &WalletOperationV0) -> u64 { + let searched_hash = searched_op.hash(); + //let mut timestamp = 0u128; + //let mut found = searched_op; + for op in &self.log { + let hash = op.1.hash(); + if hash.0 == searched_hash.0 && op.0 > timestamp && hash.1 == searched_hash.1 { + //timestamp = op.0; + //found = &op.1; + return 0; + } + } + searched_hash.0 + } + + pub fn find_first_occurrence_of_type_and_hash_after_timestamp( + &self, + searched_type: &str, + searched_hash: u64, + after: u128, + ) -> Option<(u128, &WalletOperationV0)> { + let mut timestamp = u128::MAX; + let mut found = None; + for op in &self.log { + let hash = op.1.hash(); + if hash.0 == searched_hash + && op.0 > after + && op.0 < timestamp + && hash.1 == searched_type + { + timestamp = op.0; + found = Some(&op.1); + } + } + found.map(|f| (timestamp, f)) + } } /// WalletOperation @@ -169,20 +425,101 @@ impl WalletLog0 { pub enum WalletOperationV0 { CreateWalletV0(WalletOpCreateV0), AddSiteV0(SiteV0), - RemoveSiteV0(Identity), - AddBrokerV0(BrokerInfoV0), - RemoveBrokerV0(BrokerInfoV0), - AddClientV0(ClientV0), + RemoveSiteV0(PrivKey), + AddBrokerServerV0(BrokerServerV0), + RemoveBrokerServerV0(BrokerServerV0), + SetBrokerCoreV0(BrokerCoreV0), + SetClientV0(ClientV0), AddOverlayCoreOverrideV0((OverlayId, Vec)), RemoveOverlayCoreOverrideV0(OverlayId), - AddSiteCoreV0((Identity, PubKey)), - RemoveSiteCoreV0((Identity, PubKey)), - AddSiteBootstrapV0((Identity, PubKey)), - RemoveSiteBootstrapV0((Identity, PubKey)), + AddSiteCoreV0((PubKey, PubKey)), + RemoveSiteCoreV0((PubKey, PubKey)), + AddSiteBootstrapV0((PubKey, PubKey)), + RemoveSiteBootstrapV0((PubKey, PubKey)), AddThirdPartyDataV0((String, Vec)), RemoveThirdPartyDataV0(String), - SetSiteRBDRefV0((Identity, ObjectRef)), - SetSiteRepoSecretV0((Identity, SymKey)), + SetSiteRBDRefV0((PubKey, SiteStoreType, ObjectRef)), + SetSiteRepoSecretV0((PubKey, SiteStoreType, SymKey)), +} +use std::collections::hash_map::DefaultHasher; + +impl WalletOperationV0 { + pub fn hash(&self) -> (u64, &str) { + let mut s = DefaultHasher::new(); + match self { + Self::CreateWalletV0(t) => (0, "CreateWalletV0"), + Self::AddSiteV0(t) => { + t.site_key.hash(&mut s); + (s.finish(), "AddSiteV0") + } + Self::RemoveSiteV0(t) => { + t.hash(&mut s); + (s.finish(), "RemoveSiteV0") + } + Self::AddBrokerServerV0(t) => { + t.hash(&mut s); + (s.finish(), "AddBrokerServerV0") + } + Self::RemoveBrokerServerV0(t) => { + t.hash(&mut s); + (s.finish(), "RemoveBrokerServerV0") + } + Self::SetBrokerCoreV0(t) => { + t.peer_id.hash(&mut s); + (s.finish(), "SetBrokerCoreV0") + } + Self::SetClientV0(t) => { + t.priv_key.hash(&mut s); + (s.finish(), "SetClientV0") + } + Self::AddOverlayCoreOverrideV0(t) => { + t.0.hash(&mut s); + (s.finish(), "AddOverlayCoreOverrideV0") + } + Self::RemoveOverlayCoreOverrideV0(t) => { + t.hash(&mut s); + (s.finish(), "RemoveOverlayCoreOverrideV0") + } + Self::AddSiteCoreV0(t) => { + t.0.hash(&mut s); + t.1.hash(&mut s); + (s.finish(), "AddSiteCoreV0") + } + Self::RemoveSiteCoreV0(t) => { + t.0.hash(&mut s); + t.1.hash(&mut s); + (s.finish(), "RemoveSiteCoreV0") + } + Self::AddSiteBootstrapV0(t) => { + t.0.hash(&mut s); + t.1.hash(&mut s); + (s.finish(), "AddSiteBootstrapV0") + } + Self::RemoveSiteBootstrapV0(t) => { + t.0.hash(&mut s); + t.1.hash(&mut s); + (s.finish(), "RemoveSiteBootstrapV0") + } + Self::AddThirdPartyDataV0(t) => { + t.0.hash(&mut s); + (s.finish(), "AddThirdPartyDataV0") + } + Self::RemoveThirdPartyDataV0(t) => { + t.hash(&mut s); + (s.finish(), "RemoveThirdPartyDataV0") + } + Self::SetSiteRBDRefV0(t) => { + t.0.hash(&mut s); + t.1.hash(&mut s); + (s.finish(), "SetSiteRBDRefV0") + } + Self::SetSiteRepoSecretV0(t) => { + t.0.hash(&mut s); + t.1.hash(&mut s); + (s.finish(), "SetSiteRepoSecretV0") + } + } + } } /// WalletOp Create V0 @@ -210,6 +547,27 @@ pub struct WalletOpCreateV0 { pub client: ClientV0, } +impl From<&WalletOpCreateV0> for EncryptedWalletV0 { + fn from(op: &WalletOpCreateV0) -> Self { + let mut wallet = EncryptedWalletV0 { + wallet_privkey: op.wallet_privkey.clone(), + pazzle: op.pazzle.clone(), + mnemonic: op.mnemonic.clone(), + pin: op.pin.clone(), + personal_site: op.personal_site.site_key.to_pub(), + sites: HashMap::new(), + brokers: HashMap::new(), + clients: HashMap::new(), + overlay_core_overrides: HashMap::new(), + third_parties: HashMap::new(), + }; + wallet.add_site(op.personal_site.clone()); + wallet.add_brokers(op.brokers.clone()); + wallet.add_client(op.client.clone()); + wallet + } +} + /// Reduced Wallet content Version 0, for Login QRcode #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ReducedWalletContentV0 { @@ -250,6 +608,15 @@ pub enum BrokerInfoV0 { CoreV0(BrokerCoreV0), } +impl BrokerInfoV0 { + pub fn get_id(&self) -> PubKey { + match self { + Self::CoreV0(c) => c.peer_id, + Self::ServerV0(s) => s.peer_id, + } + } +} + /// ReducedEncryptedWallet block Version 0 #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ReducedEncryptedWalletV0 { @@ -365,6 +732,8 @@ pub struct CreateWalletV0 { pub peer_id: PubKey, #[zeroize(skip)] pub nonce: u64, + #[zeroize(skip)] + pub client: ClientV0, } impl CreateWalletV0 { @@ -377,6 +746,7 @@ impl CreateWalletV0 { send_wallet: bool, peer_id: PubKey, nonce: u64, + client: ClientV0, ) -> Self { CreateWalletV0 { result_with_wallet_file: false, @@ -389,6 +759,7 @@ impl CreateWalletV0 { send_wallet, peer_id, nonce, + client, } } } @@ -418,6 +789,7 @@ pub enum NgWalletError { EncryptionError, DecryptionError, InvalidSignature, + NoCreateWalletPresent, } impl fmt::Display for NgWalletError { diff --git a/p2p-net/src/types.rs b/p2p-net/src/types.rs index fa45c32..95995ee 100644 --- a/p2p-net/src/types.rs +++ b/p2p-net/src/types.rs @@ -118,6 +118,14 @@ pub struct SiteStore { pub repo_secret: SymKey, } +/// Site Store type +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum SiteStoreType { + Public, + Protected, + Private, +} + /// Site V0 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct SiteV0 { @@ -151,7 +159,7 @@ pub struct ReducedSiteV0 { pub private_site_repo_secret: SymKey, - pub cores: Vec, + pub core: PubKey, pub bootstraps: Vec, } @@ -200,7 +208,7 @@ pub enum BrokerCore { } /// BrokerServerTypeV0 type -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum BrokerServerTypeV0 { Localhost(u16), // optional port number BoxPrivate(Vec), @@ -211,7 +219,7 @@ pub enum BrokerServerTypeV0 { } /// BrokerServer details Version 0 -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct BrokerServerV0 { /// Network addresses pub server_type: BrokerServerTypeV0,