diff --git a/Cargo.lock b/Cargo.lock index 6a5b6ce..9a568ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3449,6 +3449,7 @@ dependencies = [ "lazy_static", "ng-net", "ng-repo", + "ng-verifier", "rand 0.7.3", "safe-transmute", "serde", diff --git a/nextgraph/Cargo.toml b/nextgraph/Cargo.toml index 79d105c..8a025ea 100644 --- a/nextgraph/Cargo.toml +++ b/nextgraph/Cargo.toml @@ -42,4 +42,8 @@ required-features = [] [[example]] name = "persistent" +required-features = [] + +[[example]] +name = "open" required-features = [] \ No newline at end of file diff --git a/nextgraph/examples/README.md b/nextgraph/examples/README.md index 4f94f27..fd2a88c 100644 --- a/nextgraph/examples/README.md +++ b/nextgraph/examples/README.md @@ -7,9 +7,11 @@ run them with: ``` cargo run -p nextgraph --example in_memory cargo run -p nextgraph --example persistent +cargo run -p nextgraph --example open ``` See the code: - [in_memory](in_memory.md) - [persistent](persistent.md) +- [open](open.md) diff --git a/nextgraph/examples/in_memory.rs b/nextgraph/examples/in_memory.rs index bb61d32..336edea 100644 --- a/nextgraph/examples/in_memory.rs +++ b/nextgraph/examples/in_memory.rs @@ -8,9 +8,10 @@ // according to those terms. use nextgraph::local_broker::{ - init_local_broker, session_start, session_stop, user_connect, user_disconnect, wallet_close, - wallet_create_v0, wallet_get_file, wallet_import, wallet_open_with_pazzle_words, - wallet_read_file, wallet_was_opened, LocalBrokerConfig, SessionConfig, + doc_fetch, init_local_broker, session_start, session_stop, user_connect, user_disconnect, + wallet_close, wallet_create_v0, wallet_get, wallet_get_file, wallet_import, + wallet_open_with_pazzle_words, wallet_read_file, wallet_was_opened, LocalBrokerConfig, + SessionConfig, }; use nextgraph::net::types::BootstrapContentV0; use nextgraph::repo::errors::NgError; @@ -57,6 +58,8 @@ async fn main() -> std::io::Result<()> { }) .await?; + println!("Your wallet name is : {}", wallet_result.wallet_name); + let pazzle = display_pazzle(&wallet_result.pazzle); let mut pazzle_words = vec![]; println!("Your pazzle is: {:?}", wallet_result.pazzle); @@ -70,16 +73,21 @@ async fn main() -> std::io::Result<()> { .for_each(|word| print!("{} ", word.as_str())); println!(""); - // at this point, the wallet is kept in the internal memory of the LocalBroker, but it hasn't been saved to disk - // and it hasn't been opened yet, so it is not usable right away. - // now let's open the wallet, by providing the pazzle and PIN code - let opened_wallet = - wallet_open_with_pazzle_words(&wallet_result.wallet, &pazzle_words, [1, 2, 1, 2])?; + // A session has been opened for you and you can directly use it without the need to call [wallet_was_opened] nor [session_start]. + let user_id = wallet_result.personal_identity(); - let user_id = opened_wallet.personal_identity(); + // if the user has internet access, they can now decide to connect to its Server Broker, in order to sync data + let status = user_connect(&user_id).await?; - // once the wallet is opened, we notify the LocalBroker that we have opened it. - let _client = wallet_was_opened(opened_wallet).await?; + // The connection cannot succeed because we miss-configured the core_bootstrap of the wallet. its Peer ID is invalid. + let error_reason = status[0].3.as_ref().unwrap(); + assert!(error_reason == "NoiseHandshakeFailed" || error_reason == "ConnectionError"); + + // a session ID has been assigned to you in `wallet_result.session_id` you can use it to fetch a document + //let _ = doc_fetch(wallet_result.session_id, "ng:example".to_string(), None).await?; + + // Then we should disconnect + user_disconnect(&user_id).await?; // if you need the Wallet File again (if you didn't select `result_with_wallet_file` by example), you can retrieve it with: let wallet_file = wallet_get_file(&wallet_result.wallet_name).await?; @@ -87,17 +95,39 @@ async fn main() -> std::io::Result<()> { // if you did ask for `result_with_wallet_file`, as we did above, then the 2 vectors should be identical assert_eq!(wallet_file, wallet_result.wallet_file); + // stop the session + session_stop(&user_id).await?; + + // closes the wallet + wallet_close(&wallet_result.wallet_name).await?; + + // if you have saved the wallet locally (which we haven't done in the example above, see `local_save: false`), next time you want to connect, + // you can retrieve the wallet, display the security phrase and image to the user, ask for the pazzle or mnemonic, and then open the wallet + // if you haven't saved the wallet, the next line will not work once you restart the LocalBroker. + let wallet = wallet_get(&wallet_result.wallet_name).await?; + + // at this point, the wallet is kept in the internal memory of the LocalBroker + // and it hasn't been opened yet, so it is not usable right away. + // now let's open the wallet, by providing the pazzle and PIN code + let opened_wallet = + wallet_open_with_pazzle_words(&wallet_result.wallet, &pazzle_words, [1, 2, 1, 2])?; + + // once the wallet is opened, we notify the LocalBroker that we have opened it. + let _client = wallet_was_opened(opened_wallet).await?; + + // if instead of saving the wallet locally, you want to provide the Wallet File for every login, + // then you have to import the wallet. here is an example: { - // this part should happen on another device. just given here as an example + // this part should happen on another device or on the same machine if you haven't saved the wallet locally - // on another device, you could use the Wallet File and import it there so it could be used for login. + // you could use the Wallet File and import it there so it could be used for login. // first you would read and decode the Wallet File // this fails here because we already added this wallet in the LocalBroker (when we created it). - // But on another device, it would work. + // But on another device or after a restart of LocalBroker, it would work. let wallet = wallet_read_file(wallet_file).await; assert_eq!(wallet.unwrap_err(), NgError::WalletAlreadyAdded); - // on another device, we would then open the wallet + // we would then open the wallet // (here we take the Wallet as we received it from wallet_create_v0, but in real case you would use `wallet`) let opened_wallet2 = wallet_open_with_pazzle_words(&wallet_result.wallet, &pazzle_words, [1, 2, 1, 2])?; @@ -109,7 +139,7 @@ async fn main() -> std::io::Result<()> { assert_eq!(client_fail.unwrap_err(), NgError::WalletAlreadyAdded); } - // anyway, now that the wallet is opened, let's start a session. + // now that the wallet is opened or imported, let's start a session. // we pass the user_id and the wallet_name let _session = session_start(SessionConfig::new_in_memory( &user_id, diff --git a/nextgraph/examples/open.md b/nextgraph/examples/open.md new file mode 100644 index 0000000..6b9f826 --- /dev/null +++ b/nextgraph/examples/open.md @@ -0,0 +1,17 @@ +# open LocalBroker + +Example of LocalBroker configured with persistence to disk, and opening of a previsouly saved wallet + +You need to replace `wallet_name` on line 40 with the name that was given to you when you ran the example [persistent], in `Your wallet name is : ` + +You need to replace the argument `pazzle` in the function call `wallet_open_with_pazzle` with the array that you received in `Your pazzle is:` + +then, run with: + +``` +cargo run -p nextgraph -r --example open +``` + +we assume that you run this command from the root of the git repo (nextgraph-rs). + +the `-r` for release version is important, without it, the creation and opening of the wallet will take ages. diff --git a/nextgraph/examples/open.rs b/nextgraph/examples/open.rs new file mode 100644 index 0000000..a733d03 --- /dev/null +++ b/nextgraph/examples/open.rs @@ -0,0 +1,81 @@ +// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers +// All rights reserved. +// Licensed under the Apache License, Version 2.0 +// +// or the MIT license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use nextgraph::local_broker::{ + doc_fetch, init_local_broker, session_start, session_stop, user_connect, user_disconnect, + wallet_close, wallet_create_v0, wallet_get, wallet_get_file, wallet_import, + wallet_open_with_pazzle, wallet_open_with_pazzle_words, wallet_read_file, wallet_was_opened, + LocalBrokerConfig, SessionConfig, +}; +use nextgraph::net::types::BootstrapContentV0; +use nextgraph::repo::errors::NgError; +use nextgraph::repo::types::PubKey; +use nextgraph::wallet::types::CreateWalletV0; +use nextgraph::wallet::{display_mnemonic, emojis::display_pazzle}; + +use std::env::current_dir; +use std::fs::create_dir_all; +use std::fs::read; + +#[async_std::main] +async fn main() -> std::io::Result<()> { + // get the current working directory + let mut current_path = current_dir()?; + current_path.push(".ng"); + current_path.push("example"); + create_dir_all(current_path.clone())?; + + // initialize the local_broker with config to save to disk in a folder called `.ng/example` in the current directory + init_local_broker(Box::new(move || { + LocalBrokerConfig::BasePath(current_path.clone()) + })) + .await; + + let wallet_name = "EJdLRVx93o3iUXoB0wSTqxh1-zYac-84vHb3oBbZ_HY".to_string(); + + // as we have previously saved the wallet, + // we can retrieve it, display the security phrase and image to the user, ask for the pazzle or mnemonic, and then open the wallet + let wallet = wallet_get(&wallet_name).await?; + + // at this point, the wallet is kept in the internal memory of the LocalBroker + // and it hasn't been opened yet, so it is not usable right away. + // now let's open the wallet, by providing the pazzle and PIN code + let opened_wallet = wallet_open_with_pazzle( + &wallet, + vec![117, 134, 59, 92, 98, 35, 70, 22, 9], + [1, 2, 1, 2], + )?; + + let user_id = opened_wallet.personal_identity(); + + // once the wallet is opened, we notify the LocalBroker that we have opened it. + let _client = wallet_was_opened(opened_wallet).await?; + + // now that the wallet is opened, let's start a session. + // we pass the user_id and the wallet_name + let _session = session_start(SessionConfig::new_rocksdb(&user_id, &wallet_name)).await?; + + // if the user has internet access, they can now decide to connect to its Server Broker, in order to sync data + let status = user_connect(&user_id).await?; + + // The connection cannot succeed because we miss-configured the core_bootstrap of the wallet. its Peer ID is invalid. + let error_reason = status[0].3.as_ref().unwrap(); + assert!(error_reason == "NoiseHandshakeFailed" || error_reason == "ConnectionError"); + + // Then we should disconnect + user_disconnect(&user_id).await?; + + // stop the session + session_stop(&user_id).await?; + + // closes the wallet + wallet_close(&wallet_name).await?; + + Ok(()) +} diff --git a/nextgraph/examples/persistent.rs b/nextgraph/examples/persistent.rs index 1115bc6..753427f 100644 --- a/nextgraph/examples/persistent.rs +++ b/nextgraph/examples/persistent.rs @@ -7,9 +7,21 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use nextgraph::local_broker::{init_local_broker, LocalBrokerConfig}; +use nextgraph::local_broker::{ + doc_fetch, init_local_broker, session_start, session_stop, user_connect, user_disconnect, + wallet_close, wallet_create_v0, wallet_get, wallet_get_file, wallet_import, + wallet_open_with_pazzle_words, wallet_read_file, wallet_was_opened, LocalBrokerConfig, + SessionConfig, +}; +use nextgraph::net::types::BootstrapContentV0; +use nextgraph::repo::errors::NgError; +use nextgraph::repo::types::PubKey; +use nextgraph::wallet::types::CreateWalletV0; +use nextgraph::wallet::{display_mnemonic, emojis::display_pazzle}; + use std::env::current_dir; use std::fs::create_dir_all; +use std::fs::read; #[async_std::main] async fn main() -> std::io::Result<()> { @@ -25,5 +37,115 @@ async fn main() -> std::io::Result<()> { })) .await; + // load some image that will be used as security_img + // we assume here for the sake of this example, + // that the current directory contains this demo image file + let security_img = read("nextgraph/examples/wallet-security-image-demo.png")?; + + // the peer_id should come from somewhere else. + // this is just given for the sake of an example + #[allow(deprecated)] + let peer_id_of_server_broker = PubKey::nil(); + + // Create your wallet + // this will take some time ! + println!("Creating the wallet. this will take some time..."); + + let wallet_result = wallet_create_v0(CreateWalletV0 { + security_img, + security_txt: "know yourself".to_string(), + pin: [1, 2, 1, 2], + pazzle_length: 9, + send_bootstrap: false, + send_wallet: false, + result_with_wallet_file: true, + local_save: true, + // we default to localhost:14400. this is just for the sake of an example + core_bootstrap: BootstrapContentV0::new_localhost(peer_id_of_server_broker), + core_registration: None, + additional_bootstrap: None, + }) + .await?; + + println!("Your wallet name is : {}", wallet_result.wallet_name); + + let pazzle = display_pazzle(&wallet_result.pazzle); + let mut pazzle_words = vec![]; + println!("Your pazzle is: {:?}", wallet_result.pazzle); + for emoji in pazzle { + println!(" {}:\t{}", emoji.0, emoji.1); + pazzle_words.push(emoji.1.to_string()); + } + println!("Your mnemonic is:"); + display_mnemonic(&wallet_result.mnemonic) + .iter() + .for_each(|word| print!("{} ", word.as_str())); + println!(""); + + // A session has been opened for you and you can directly use it without the need to call [wallet_was_opened] nor [session_start]. + let user_id = wallet_result.personal_identity(); + + // if the user has internet access, they can now decide to connect to its Server Broker, in order to sync data + let status = user_connect(&user_id).await?; + + // The connection cannot succeed because we miss-configured the core_bootstrap of the wallet. its Peer ID is invalid. + let error_reason = status[0].3.as_ref().unwrap(); + assert!(error_reason == "NoiseHandshakeFailed" || error_reason == "ConnectionError"); + + // a session ID has been assigned to you in `wallet_result.session_id` you can use it to fetch a document + //let _ = doc_fetch(wallet_result.session_id, "ng:example".to_string(), None).await?; + + // Then we should disconnect + user_disconnect(&user_id).await?; + + // if you need the Wallet File again (if you didn't select `result_with_wallet_file` by example), you can retrieve it with: + let wallet_file = wallet_get_file(&wallet_result.wallet_name).await?; + + // if you did ask for `result_with_wallet_file`, as we did above, then the 2 vectors should be identical + assert_eq!(wallet_file, wallet_result.wallet_file); + + // stop the session + session_stop(&user_id).await?; + + // closes the wallet + wallet_close(&wallet_result.wallet_name).await?; + + // as we have saved the wallet, the next time we want to connect, + // we can retrieve the wallet, display the security phrase and image to the user, ask for the pazzle or mnemonic, and then open the wallet + let wallet = wallet_get(&wallet_result.wallet_name).await?; + + // at this point, the wallet is kept in the internal memory of the LocalBroker + // and it hasn't been opened yet, so it is not usable right away. + // now let's open the wallet, by providing the pazzle and PIN code + let opened_wallet = + wallet_open_with_pazzle_words(&wallet_result.wallet, &pazzle_words, [1, 2, 1, 2])?; + + // once the wallet is opened, we notify the LocalBroker that we have opened it. + let _client = wallet_was_opened(opened_wallet).await?; + + // now that the wallet is opened, let's start a session. + // we pass the user_id and the wallet_name + let _session = session_start(SessionConfig::new_rocksdb( + &user_id, + &wallet_result.wallet_name, + )) + .await?; + + // if the user has internet access, they can now decide to connect to its Server Broker, in order to sync data + let status = user_connect(&user_id).await?; + + // The connection cannot succeed because we miss-configured the core_bootstrap of the wallet. its Peer ID is invalid. + let error_reason = status[0].3.as_ref().unwrap(); + assert!(error_reason == "NoiseHandshakeFailed" || error_reason == "ConnectionError"); + + // Then we should disconnect + user_disconnect(&user_id).await?; + + // stop the session + session_stop(&user_id).await?; + + // closes the wallet + wallet_close(&wallet_result.wallet_name).await?; + Ok(()) } diff --git a/nextgraph/src/local_broker.rs b/nextgraph/src/local_broker.rs index beccc28..39fcd6e 100644 --- a/nextgraph/src/local_broker.rs +++ b/nextgraph/src/local_broker.rs @@ -34,7 +34,7 @@ use ng_repo::errors::NgError; use ng_repo::log::*; use ng_repo::types::*; use ng_repo::utils::derive_key; -use ng_wallet::{create_wallet_v0, types::*}; +use ng_wallet::{create_wallet_first_step_v0, create_wallet_second_step_v0, types::*}; #[cfg(not(target_arch = "wasm32"))] use ng_client_ws::remote_ws::ConnectionWebSocket; @@ -80,14 +80,16 @@ impl JsStorageConfig { } Err(_) => 0, }; - let new_val = val + qty as u64; - let spls = SessionPeerLastSeq::V0(new_val); - let ser = serde_bare::to_vec(&spls)?; - //saving the new val - let encoded = base64_url::encode(&ser); - let r = (session_write2)(format!("ng_peer_last_seq@{}", peer_id), encoded); - if r.is_ok() { - return Err(NgError::SerializationError); + if qty > 0 { + let new_val = val + qty as u64; + let spls = SessionPeerLastSeq::V0(new_val); + let ser = serde_bare::to_vec(&spls)?; + //saving the new val + let encoded = base64_url::encode(&ser); + let r = (session_write2)(format!("ng_peer_last_seq@{}", peer_id), encoded); + if r.is_ok() { + return Err(NgError::SerializationError); + } } Ok(val) }), @@ -266,6 +268,31 @@ impl SessionConfig { }) } + pub fn new_for_local_broker_config( + user_id: &UserId, + wallet_name: &String, + local_broker_config: &LocalBrokerConfig, + in_memory: bool, + ) -> Result { + Ok(SessionConfig::V0(SessionConfigV0 { + user_id: user_id.clone(), + wallet_name: wallet_name.clone(), + verifier_type: match local_broker_config { + LocalBrokerConfig::InMemory => { + if !in_memory { + return Err(NgError::CannotSaveWhenInMemoryConfig); + } + VerifierType::Memory + } + LocalBrokerConfig::JsStorage(js_config) => VerifierType::Memory, + LocalBrokerConfig::BasePath(_) => match in_memory { + true => VerifierType::Memory, + false => VerifierType::RocksDb, + }, + }, + })) + } + fn valid_verifier_config_for_local_broker_config( &mut self, local_broker_config: &LocalBrokerConfig, @@ -283,7 +310,7 @@ impl SessionConfig { }, LocalBrokerConfig::BasePath(_) => match v0.verifier_type { VerifierType::RocksDb | VerifierType::Remote(_) => true, - //VerifierType::Memory => true, + VerifierType::Memory => true, _ => false, }, }, @@ -366,16 +393,16 @@ impl EActor for LocalBroker { } impl LocalBroker { - fn storage_path_for_user(&self, user_id: &UserId) -> Option { - match &self.config { - LocalBrokerConfig::InMemory | LocalBrokerConfig::JsStorage(_) => None, - LocalBrokerConfig::BasePath(base) => { - let mut path = base.clone(); - path.push(user_id.to_hash_string()); - Some(path) - } - } - } + // fn storage_path_for_user(&self, user_id: &UserId) -> Option { + // match &self.config { + // LocalBrokerConfig::InMemory | LocalBrokerConfig::JsStorage(_) => None, + // LocalBrokerConfig::BasePath(base) => { + // let mut path = base.clone(); + // path.push(format!("user{}", user_id.to_hash_string())); + // Some(path) + // } + // } + // } fn get_mut_session_for_user(&mut self, user: &UserId) -> Option<&mut Session> { match self.opened_sessions.get(user) { @@ -390,9 +417,10 @@ impl LocalBroker { ) -> VerifierConfigType { match (config.verifier_type(), &self.config) { (VerifierType::Memory, LocalBrokerConfig::InMemory) => VerifierConfigType::Memory, + (VerifierType::Memory, LocalBrokerConfig::BasePath(_)) => VerifierConfigType::Memory, (VerifierType::RocksDb, LocalBrokerConfig::BasePath(base)) => { let mut path = base.clone(); - path.push(config.user_id().to_hash_string()); + path.push(format!("user{}", config.user_id().to_hash_string())); VerifierConfigType::RocksDb(path) } (VerifierType::Remote(to), _) => VerifierConfigType::Remote(*to), @@ -439,6 +467,295 @@ impl LocalBroker { } Ok(()) } + + async fn wallet_was_opened( + &mut self, + mut wallet: SensitiveWallet, + ) -> Result { + let broker = self; + + match broker.opened_wallets.get(&wallet.id()) { + Some(opened_wallet) => { + return Ok(opened_wallet.wallet.client().to_owned().unwrap()); + } + None => {} //Err(NgError::WalletAlreadyOpened); + } + let wallet_id = wallet.id(); + let lws = match broker.wallets.get(&wallet_id) { + Some(lws) => { + if wallet.client().is_none() { + // this case happens when the wallet is opened and not when it is imported (as the client is already there) + wallet.set_client(lws.to_client_v0(wallet.privkey())?); + } + lws + } + None => { + return Err(NgError::WalletNotFound); + } + }; + let block_storage = if lws.in_memory { + Arc::new(std::sync::RwLock::new(HashMapBlockStorage::new())) + as Arc> + } else { + #[cfg(not(target_family = "wasm"))] + { + let mut key_material = wallet + .client() + .as_ref() + .unwrap() + .sensitive_client_storage + .priv_key + .slice(); + let path = broker.config.compute_path(&format!( + "block{}", + wallet.client().as_ref().unwrap().id.to_hash_string() + ))?; + let mut key: [u8; 32] = + derive_key("NextGraph Client BlockStorage BLAKE3 key", key_material); + Arc::new(std::sync::RwLock::new(RocksDbBlockStorage::open( + &path, key, + )?)) + as Arc> + } + #[cfg(target_family = "wasm")] + { + panic!("no RocksDB in WASM"); + } + }; + let client = wallet.client().as_ref().unwrap().clone(); + let opened_wallet = OpenedWallet { + wallet, + block_storage, + }; + + broker.opened_wallets.insert(wallet_id, opened_wallet); + Ok(client) + } + + async fn session_start( + &mut self, + mut config: SessionConfig, + user_priv_key: Option, + ) -> Result { + let broker = self; + + config.valid_verifier_config_for_local_broker_config(&broker.config)?; + + let wallet_name: String = config.wallet_name(); + let wallet_id: PubKey = (*wallet_name).try_into()?; + let user_id = config.user_id(); + + match broker.opened_sessions.get(&user_id) { + Some(idx) => { + let ses = &(broker.opened_sessions_list)[*idx as usize]; + match ses.as_ref() { + Some(sess) => { + if sess.config.verifier_type() != config.verifier_type() { + return Err(NgError::SessionAlreadyStarted); + } else { + return Ok(SessionInfo { + session_id: *idx, + user: user_id, + }); + } + } + None => {} + } + } + None => {} + }; + + // log_info!("wallet_name {} {:?}", wallet_name, broker.opened_wallets); + match broker.opened_wallets.get(&wallet_name) { + None => return Err(NgError::WalletNotFound), + Some(opened_wallet) => { + let block_storage = Arc::clone(&opened_wallet.block_storage); + let credentials = match opened_wallet.wallet.individual_site(&user_id) { + Some(creds) => creds, + None => match user_priv_key { + Some(user_pk) => (user_pk, None, None), + None => return Err(NgError::NotFound), + }, + }; + + let client_storage_master_key = serde_bare::to_vec( + &opened_wallet + .wallet + .client() + .as_ref() + .unwrap() + .sensitive_client_storage + .storage_master_key, + ) + .unwrap(); + + let session = match broker.sessions.get(&user_id) { + Some(session) => session, + None => { + // creating the session now + let closed_wallet = broker.wallets.get(&wallet_name).unwrap(); + if closed_wallet.in_memory { + let session = SessionPeerStorageV0::new(user_id); + broker.sessions.insert(user_id, session); + broker.sessions.get(&user_id).unwrap() + } else { + // first check if there is a saved SessionWalletStorage + let mut sws = match &broker.config { + LocalBrokerConfig::InMemory => panic!("cannot open saved session"), + LocalBrokerConfig::JsStorage(js_config) => { + // read session wallet storage from JsStorage + let res = (js_config.session_read)(format!( + "ng_wallet@{}", + wallet_name + )); + match res { + Ok(string) => { + let decoded = base64_url::decode(&string) + .map_err(|_| NgError::SerializationError)?; + Some(SessionWalletStorageV0::dec_session( + opened_wallet.wallet.privkey(), + &decoded, + )?) + } + Err(_) => None, + } + } + LocalBrokerConfig::BasePath(base_path) => { + // read session wallet storage from disk + let mut path = base_path.clone(); + path.push("sessions"); + path.push(format!("session{}", wallet_name.clone())); + let res = read(path); + if res.is_ok() { + Some(SessionWalletStorageV0::dec_session( + opened_wallet.wallet.privkey(), + &res.unwrap(), + )?) + } else { + None + } + } + }; + let (session, new_sws) = match &mut sws { + None => { + let (s, sws_ser) = SessionWalletStorageV0::create_new_session( + &wallet_id, user_id, + )?; + broker.sessions.insert(user_id, s); + (broker.sessions.get(&user_id).unwrap(), sws_ser) + } + Some(sws) => { + match sws.users.get(&user_id.to_string()) { + Some(sps) => { + broker.sessions.insert(user_id, sps.clone()); + (broker.sessions.get(&user_id).unwrap(), vec![]) + } + None => { + // the user was not found in the SWS. we need to create a SPS, add it, encrypt and serialize the new SWS, + // add the SPS to broker.sessions, and return the newly created SPS and the new SWS encrypted serialization + let sps = SessionPeerStorageV0::new(user_id); + sws.users.insert(user_id.to_string(), sps.clone()); + let encrypted = sws.enc_session(&wallet_id)?; + broker.sessions.insert(user_id, sps); + (broker.sessions.get(&user_id).unwrap(), encrypted) + } + } + } + }; + // save the new sws + if new_sws.len() > 0 { + match &broker.config { + LocalBrokerConfig::InMemory => { + panic!("cannot save session when InMemory mode") + } + LocalBrokerConfig::JsStorage(js_config) => { + // save session wallet storage to JsStorage + let encoded = base64_url::encode(&new_sws); + (js_config.session_write)( + format!("ng_wallet@{}", wallet_name), + encoded, + )?; + } + LocalBrokerConfig::BasePath(base_path) => { + // save session wallet storage to disk + let mut path = base_path.clone(); + path.push("sessions"); + std::fs::create_dir_all(path.clone()).unwrap(); + path.push(format!("session{}", wallet_name)); + //log_debug!("{}", path.clone().display()); + write(path.clone(), &new_sws) + .map_err(|_| NgError::IoError)?; + } + } + } + session + } + } + }; + let session = session.clone(); + + // derive user_master_key from client's storage_master_key + let user_id_ser = serde_bare::to_vec(&user_id).unwrap(); + let mut key_material = [user_id_ser, client_storage_master_key].concat(); // + let mut key: [u8; 32] = derive_key( + "NextGraph user_master_key BLAKE3 key", + key_material.as_slice(), + ); + key_material.zeroize(); + let verifier = Verifier::new( + VerifierConfig { + config_type: broker.verifier_config_type_from_session_config(&config), + user_master_key: key, + peer_priv_key: session.peer_key.clone(), + user_priv_key: credentials.0, + private_store_read_cap: credentials.1, + private_store_id: credentials.2, + }, + block_storage, + )?; + key.zeroize(); + broker.opened_sessions_list.push(Some(Session { + config, + peer_key: session.peer_key.clone(), + last_wallet_nonce: session.last_wallet_nonce, + verifier, + })); + let idx = broker.opened_sessions_list.len() - 1; + broker.opened_sessions.insert(user_id, idx as u8); + Ok(SessionInfo { + session_id: idx as u8, + user: user_id, + }) + } + } + } + + pub(crate) fn wallet_save(broker: &mut Self) -> Result<(), NgError> { + match &broker.config { + LocalBrokerConfig::JsStorage(js_config) => { + // JS save + let lws_ser = LocalWalletStorage::v0_to_vec(&broker.wallets); + let encoded = base64_url::encode(&lws_ser); + (js_config.local_write)("ng_wallets".to_string(), encoded)?; + } + LocalBrokerConfig::BasePath(base_path) => { + // save on disk + // TODO: use https://lib.rs/crates/keyring instead of AppLocalData on Tauri apps + let mut path = base_path.clone(); + std::fs::create_dir_all(path.clone()).unwrap(); + path.push("wallets"); + + let lws_ser = LocalWalletStorage::v0_to_vec(&broker.wallets); + let r = write(path.clone(), &lws_ser); + if r.is_err() { + log_debug!("write {:?} {}", path, r.unwrap_err()); + return Err(NgError::IoError); + } + } + _ => panic!("wrong LocalBrokerConfig"), + } + Ok(()) + } } static LOCAL_BROKER: OnceCell>, NgError>> = OnceCell::new(); @@ -541,20 +858,52 @@ pub async fn wallets_get_all() -> Result, /// /// Wallets are transferable to to other devices (see [wallet_get_file] and [wallet_import]) pub async fn wallet_create_v0(params: CreateWalletV0) -> Result { - { - // entering sub-block to release the lock asap - let broker = match LOCAL_BROKER.get() { - None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), - Some(Ok(broker)) => broker.read().await, - }; - if params.local_save && broker.config.is_in_memory() { - return Err(NgError::CannotSaveWhenInMemoryConfig); - } + // TODO: entering sub-block to release the lock asap + let mut broker = match LOCAL_BROKER.get() { + None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), + Some(Ok(broker)) => broker.write().await, + }; + if params.local_save && broker.config.is_in_memory() { + return Err(NgError::CannotSaveWhenInMemoryConfig); } - let res = create_wallet_v0(params)?; - let lws: LocalWalletStorageV0 = (&res).into(); - wallet_add(lws).await?; + let intermediate = create_wallet_first_step_v0(params)?; + let lws: LocalWalletStorageV0 = (&intermediate).into(); + + let wallet_name = intermediate.wallet_name.clone(); + broker.wallets.insert(wallet_name, lws); + + let sensitive_wallet: SensitiveWallet = (&intermediate).into(); + + let _client = broker.wallet_was_opened(sensitive_wallet).await?; + let session_config = SessionConfig::new_for_local_broker_config( + &intermediate.user_privkey.to_pub(), + &intermediate.wallet_name, + &broker.config, + intermediate.in_memory, + )?; + + let session_info = broker + .session_start(session_config, Some(intermediate.user_privkey.clone())) + .await?; + + let session = broker.opened_sessions_list[session_info.session_id as usize] + .as_mut() + .unwrap(); + + let (mut res, site, brokers) = + create_wallet_second_step_v0(intermediate, &mut session.verifier)?; + + broker.wallets.get_mut(&res.wallet_name).unwrap().wallet = res.wallet.clone(); + LocalBroker::wallet_save(&mut broker)?; + //TODO: change read_cap in verifier + broker + .opened_wallets + .get_mut(&res.wallet_name) + .unwrap() + .wallet + .complete_with_site_and_brokers(site, brokers); + res.session_id = session_info.session_id; Ok(res) } @@ -602,29 +951,7 @@ pub async fn wallet_add(lws: LocalWalletStorageV0) -> Result<(), NgError> { // (broker.config.js_config().unwrap().wallets_in_mem_changed)(); // } } else { - match &broker.config { - LocalBrokerConfig::JsStorage(js_config) => { - // JS save - let lws_ser = LocalWalletStorage::v0_to_vec(&broker.wallets); - let encoded = base64_url::encode(&lws_ser); - (js_config.local_write)("ng_wallets".to_string(), encoded)?; - } - LocalBrokerConfig::BasePath(base_path) => { - // save on disk - // TODO: use https://lib.rs/crates/keyring instead of AppLocalData on Tauri apps - let mut path = base_path.clone(); - std::fs::create_dir_all(path.clone()).unwrap(); - path.push("wallets"); - - let lws_ser = LocalWalletStorage::v0_to_vec(&broker.wallets); - let r = write(path.clone(), &lws_ser); - if r.is_err() { - log_debug!("write {:?} {}", path, r.unwrap_err()); - return Err(NgError::IoError); - } - } - _ => panic!("wrong LocalBrokerConfig"), - } + LocalBroker::wallet_save(&mut broker)?; } Ok(()) } @@ -653,6 +980,19 @@ pub async fn wallet_read_file(file: Vec) -> Result { } } +/// Retrieves the the Wallet by its name, to be used for opening it +pub async fn wallet_get(wallet_name: &String) -> Result { + let broker = match LOCAL_BROKER.get() { + None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), + Some(Ok(broker)) => broker.read().await, + }; + // check that the wallet exists + match broker.wallets.get(wallet_name) { + None => Err(NgError::WalletNotFound), + Some(lws) => Ok(lws.wallet.clone()), + } +} + /// Retrieves the binary content of a Wallet File for the Wallet identified by its name pub async fn wallet_get_file(wallet_name: &String) -> Result, NgError> { let broker = match LOCAL_BROKER.get() { @@ -734,63 +1074,13 @@ pub async fn wallet_was_opened(mut wallet: SensitiveWallet) -> Result broker.write().await, }; - if broker.opened_wallets.get(&wallet.id()).is_some() { - return Err(NgError::WalletAlreadyOpened); - } - let wallet_id = wallet.id(); - - let lws = match broker.wallets.get(&wallet_id) { - Some(lws) => { - if wallet.client().is_none() { - // this case happens when the wallet is opened and not when it is imported (as the client is already there) - wallet.set_client(lws.to_client_v0(wallet.privkey())?); - } - lws - } - None => { - return Err(NgError::WalletNotFound); - } - }; - let block_storage = if lws.in_memory { - Arc::new(std::sync::RwLock::new(HashMapBlockStorage::new())) - as Arc> - } else { - #[cfg(not(target_family = "wasm"))] - { - let mut key_material = wallet - .client() - .as_ref() - .unwrap() - .sensitive_client_storage - .priv_key - .slice(); - let path = broker - .config - .compute_path(&wallet.client().as_ref().unwrap().id.to_hash_string())?; - let mut key: [u8; 32] = - derive_key("NextGraph Client BlockStorage BLAKE3 key", key_material); - Arc::new(std::sync::RwLock::new(RocksDbBlockStorage::open( - &path, key, - )?)) as Arc> - } - #[cfg(target_family = "wasm")] - { - panic!("no RocksDB in WASM"); - } - }; - let client = wallet.client().as_ref().unwrap().clone(); - let opened_wallet = OpenedWallet { - wallet, - block_storage, - }; - - broker.opened_wallets.insert(wallet_id, opened_wallet); - Ok(client) + broker.wallet_was_opened(wallet).await } /// Starts a session with the LocalBroker. The type of verifier is selected at this moment. /// /// The session is valid even if there is no internet. The local data will be used in this case. +/// wallet_creation_events should be the list of events that was returned by wallet_create_v0 /// Return value is the index of the session, will be used in all the doc_* API calls. pub async fn session_start(mut config: SessionConfig) -> Result { let mut broker = match LOCAL_BROKER.get() { @@ -798,167 +1088,7 @@ pub async fn session_start(mut config: SessionConfig) -> Result broker.write().await, }; - config.valid_verifier_config_for_local_broker_config(&broker.config)?; - - let wallet_name: String = config.wallet_name(); - let wallet_id: PubKey = (*wallet_name).try_into()?; - let user_id = config.user_id(); - - match broker.opened_wallets.get(&wallet_name) { - None => return Err(NgError::WalletNotFound), - Some(opened_wallet) => { - let block_storage = Arc::clone(&opened_wallet.block_storage); - let credentials = match opened_wallet.wallet.individual_site(&user_id) { - Some(creds) => creds.clone(), - None => return Err(NgError::NotFound), - }; - - let client_storage_master_key = serde_bare::to_vec( - &opened_wallet - .wallet - .client() - .as_ref() - .unwrap() - .sensitive_client_storage - .storage_master_key, - ) - .unwrap(); - - let session = match broker.sessions.get(&user_id) { - Some(session) => session, - None => { - // creating the session now - let closed_wallet = broker.wallets.get(&wallet_name).unwrap(); - if closed_wallet.in_memory { - let session = SessionPeerStorageV0::new(user_id); - broker.sessions.insert(user_id, session); - broker.sessions.get(&user_id).unwrap() - } else { - // first check if there is a saved SessionWalletStorage - let mut sws = match &broker.config { - LocalBrokerConfig::InMemory => panic!("cannot open saved session"), - LocalBrokerConfig::JsStorage(js_config) => { - // read session wallet storage from JsStorage - let res = - (js_config.session_read)(format!("ng_wallet@{}", wallet_name)); - match res { - Ok(string) => { - let decoded = base64_url::decode(&string) - .map_err(|_| NgError::SerializationError)?; - Some(SessionWalletStorageV0::dec_session( - opened_wallet.wallet.privkey(), - &decoded, - )?) - } - Err(_) => None, - } - } - LocalBrokerConfig::BasePath(base_path) => { - // read session wallet storage from disk - let mut path = base_path.clone(); - path.push("sessions"); - path.push(wallet_name.clone()); - let res = read(path); - if res.is_ok() { - Some(SessionWalletStorageV0::dec_session( - opened_wallet.wallet.privkey(), - &res.unwrap(), - )?) - } else { - None - } - } - }; - let (session, new_sws) = match &mut sws { - None => { - let (s, sws_ser) = SessionWalletStorageV0::create_new_session( - &wallet_id, user_id, - )?; - broker.sessions.insert(user_id, s); - (broker.sessions.get(&user_id).unwrap(), sws_ser) - } - Some(sws) => { - match sws.users.get(&user_id.to_string()) { - Some(sps) => { - broker.sessions.insert(user_id, sps.clone()); - (broker.sessions.get(&user_id).unwrap(), vec![]) - } - None => { - // the user was not found in the SWS. we need to create a SPS, add it, encrypt and serialize the new SWS, - // add the SPS to broker.sessions, and return the newly created SPS and the new SWS encrypted serialization - let sps = SessionPeerStorageV0::new(user_id); - sws.users.insert(user_id.to_string(), sps.clone()); - let encrypted = sws.enc_session(&wallet_id)?; - broker.sessions.insert(user_id, sps); - (broker.sessions.get(&user_id).unwrap(), encrypted) - } - } - } - }; - // save the new sws - if new_sws.len() > 0 { - match &broker.config { - LocalBrokerConfig::InMemory => { - panic!("cannot save session when InMemory mode") - } - LocalBrokerConfig::JsStorage(js_config) => { - // save session wallet storage to JsStorage - let encoded = base64_url::encode(&new_sws); - (js_config.session_write)( - format!("ng_wallet@{}", wallet_name), - encoded, - )?; - } - LocalBrokerConfig::BasePath(base_path) => { - // save session wallet storage to disk - let mut path = base_path.clone(); - path.push("sessions"); - std::fs::create_dir_all(path.clone()).unwrap(); - path.push(wallet_name); - //log_debug!("{}", path.clone().display()); - write(path.clone(), &new_sws).map_err(|_| NgError::IoError)?; - } - } - } - session - } - } - }; - let session = session.clone(); - - // derive user_master_key from client's storage_master_key - let user_id_ser = serde_bare::to_vec(&user_id).unwrap(); - let mut key_material = [user_id_ser, client_storage_master_key].concat(); // - let mut key: [u8; 32] = derive_key( - "NextGraph user_master_key BLAKE3 key", - key_material.as_slice(), - ); - key_material.zeroize(); - let verifier = Verifier::new( - VerifierConfig { - config_type: broker.verifier_config_type_from_session_config(&config), - user_master_key: key, - peer_priv_key: session.peer_key.clone(), - user_priv_key: credentials.0, - private_store_read_cap: credentials.1, - }, - block_storage, - )?; - key.zeroize(); - broker.opened_sessions_list.push(Some(Session { - config, - peer_key: session.peer_key.clone(), - last_wallet_nonce: session.last_wallet_nonce, - verifier, - })); - let idx = broker.opened_sessions_list.len() - 1; - broker.opened_sessions.insert(user_id, idx as u8); - Ok(SessionInfo { - session_id: idx as u8, - user: user_id, - }) - } - } + broker.session_start(config, None).await } use web_time::SystemTime; diff --git a/ng-app/src-tauri/src/lib.rs b/ng-app/src-tauri/src/lib.rs index 4677289..048721b 100644 --- a/ng-app/src-tauri/src/lib.rs +++ b/ng-app/src-tauri/src/lib.rs @@ -21,7 +21,6 @@ use serde_json::Value; use std::collections::HashMap; use std::fs::{read, write, File, OpenOptions}; use std::io::Write; -use std::path::PathBuf; use tauri::scope::ipc::RemoteDomainAccessScope; use tauri::utils::config::WindowConfig; use tauri::{path::BaseDirectory, App, Manager, Window}; @@ -42,17 +41,6 @@ async fn test(app: tauri::AppHandle) -> Result<(), ()> { .unwrap(); init_local_broker(Box::new(move || LocalBrokerConfig::BasePath(path.clone()))).await; - // init_local_broker(&Lazy::new(|| { - // Box::new(move || LocalBrokerConfig::BasePath(path.clone())) - // })) - // .await; - //pub type LastSeqFn = dyn Fn(PubKey, u16) -> Result + 'static + Sync + Send; - - // let test: Box = Box::new(move |peer_id: PubKey, qty: u16| { - // take_some_peer_last_seq_numbers(peer_id, qty, path.clone()) - // }); - //pub type ConfigInitFn = dyn Fn() -> LocalBrokerConfig + 'static + Sync + Send; - //log_debug!("test is {}", BROKER.read().await.test()); let path = app .path() @@ -136,49 +124,6 @@ async fn wallet_create( Ok(cwr) } -fn take_some_peer_last_seq_numbers( - peer_id: PubKey, - qty: u16, - mut path: PathBuf, -) -> Result { - std::fs::create_dir_all(path.clone()).unwrap(); - path.push(peer_id.to_string()); - log_debug!("{}", path.display()); - - let file = read(path.clone()); - let (mut file_save, val) = match file { - Ok(ser) => { - let old_val = match SessionPeerLastSeq::deser(&ser)? { - SessionPeerLastSeq::V0(v) => v, - _ => unimplemented!(), - }; - ( - OpenOptions::new() - .write(true) - .open(path) - .map_err(|_| NgError::SerializationError)?, - old_val, - ) - } - Err(_) => ( - File::create(path).map_err(|_| NgError::SerializationError)?, - 0, - ), - }; - let new_val = val + qty as u64; - let spls = SessionPeerLastSeq::V0(new_val); - let ser = spls.ser()?; - file_save - .write_all(&ser) - .map_err(|_| NgError::SerializationError)?; - - file_save - .sync_data() - .map_err(|_| NgError::SerializationError)?; - - Ok(val) -} - #[tauri::command(rename_all = "snake_case")] async fn wallet_read_file(file: Vec, app: tauri::AppHandle) -> Result { nextgraph::local_broker::wallet_read_file(file) diff --git a/ng-broker/src/server_ws.rs b/ng-broker/src/server_ws.rs index b097910..aa96c12 100644 --- a/ng-broker/src/server_ws.rs +++ b/ng-broker/src/server_ws.rs @@ -734,6 +734,7 @@ pub async fn run_server_v0( servers.push(BrokerServerV0 { peer_id: common_peer_id.unwrap_or(peer_id), can_verify: false, + can_forward: !run_core, server_type, }) } diff --git a/ng-net/src/types.rs b/ng-net/src/types.rs index ae0209b..5baf8fc 100644 --- a/ng-net/src/types.rs +++ b/ng-net/src/types.rs @@ -150,6 +150,9 @@ pub struct BrokerServerV0 { /// is this server capable of running a verifier pub can_verify: bool, + /// is this server capable of forwarding client connections to another broker + pub can_forward: bool, + /// peerId of the server pub peer_id: PubKey, } @@ -164,6 +167,7 @@ impl BrokerServerV0 { BrokerServerV0 { server_type: BrokerServerTypeV0::Localhost(WS_PORT_ALTERNATE[0]), can_verify: false, + can_forward: true, peer_id, } } @@ -3772,6 +3776,7 @@ mod test { servers: vec![BrokerServerV0 { server_type: BrokerServerTypeV0::Localhost(14400), can_verify: false, + can_forward: false, peer_id: PubKey::Ed25519PubKey([ 95, 73, 225, 250, 3, 147, 24, 164, 177, 211, 34, 244, 45, 130, 111, 136, 229, 145, 53, 167, 50, 168, 140, 227, 65, 111, 203, 41, 210, 186, 162, 149, diff --git a/ng-repo/src/commit.rs b/ng-repo/src/commit.rs index 0a72a5d..0bf2af5 100644 --- a/ng-repo/src/commit.rs +++ b/ng-repo/src/commit.rs @@ -402,6 +402,11 @@ impl Commit { } } + /// Get branch ID this commit is about + pub fn branch(&self) -> &BranchId { + self.content().branch() + } + /// Get commit signature pub fn header(&self) -> &Option { match self { @@ -701,7 +706,7 @@ impl Commit { } self.verify_sig(repo)?; self.verify_perm(repo)?; - //self.verify_full_object_refs_of_branch_at_commit(repo.store.unwrap())?; + self.verify_full_object_refs_of_branch_at_commit(&repo.store)?; Ok(()) } } diff --git a/ng-repo/src/errors.rs b/ng-repo/src/errors.rs index 959dd77..7b172c1 100644 --- a/ng-repo/src/errors.rs +++ b/ng-repo/src/errors.rs @@ -44,6 +44,10 @@ pub enum NgError { WalletError(String), BrokerError, SessionNotFound, + SessionAlreadyStarted, + RepoNotFound, + BranchNotFound, + StoreNotFound, } impl Error for NgError {} diff --git a/ng-repo/src/event.rs b/ng-repo/src/event.rs index 65c292a..5dc55a3 100644 --- a/ng-repo/src/event.rs +++ b/ng-repo/src/event.rs @@ -8,13 +8,20 @@ //! Event, a message sent in the PUB/SUB +use zeroize::Zeroize; + use crate::block_storage::*; use crate::errors::*; use crate::object::*; +use crate::repo::Repo; use crate::store::Store; use crate::types::*; use crate::utils::*; use core::fmt; +use std::sync::Arc; + +use chacha20::cipher::{KeyIvInit, StreamCipher}; +use chacha20::ChaCha20; impl fmt::Display for Event { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -54,38 +61,75 @@ impl fmt::Display for EventContentV0 { } impl Event { - pub fn new<'a>( + pub fn new( publisher: &PrivKey, seq: u64, commit: &Commit, additional_blocks: &Vec, - topic_id: TopicId, - topic_priv_key: &BranchWriteCapSecret, - store: &'a Store, + repo: &Repo, ) -> Result { Ok(Event::V0(EventV0::new( publisher, seq, commit, additional_blocks, - topic_id, - topic_priv_key, - store, + repo, )?)) } + + pub fn seq_num(&self) -> u64 { + match self { + Event::V0(v0) => v0.content.seq, + } + } } impl EventV0 { - pub fn new<'a>( + pub fn derive_key( + repo_id: &RepoId, + branch_id: &BranchId, + branch_secret: &ReadCapSecret, + publisher: &PubKey, + ) -> [u8; blake3::OUT_LEN] { + let mut key_material = match (*repo_id, *branch_id, branch_secret.clone(), *publisher) { + ( + PubKey::Ed25519PubKey(repo), + PubKey::Ed25519PubKey(branch), + SymKey::ChaCha20Key(branch_sec), + PubKey::Ed25519PubKey(publ), + ) => [repo, branch, branch_sec, publ].concat(), + (_, _, _, _) => panic!("cannot derive key with Montgomery key"), + }; + let res = blake3::derive_key( + "NextGraph Event Commit ObjectKey ChaCha20 key", + key_material.as_slice(), + ); + key_material.zeroize(); + res + } + + pub fn new( publisher: &PrivKey, seq: u64, commit: &Commit, additional_blocks: &Vec, - topic_id: TopicId, - topic_priv_key: &BranchWriteCapSecret, - store: &'a Store, + repo: &Repo, ) -> Result { - let branch_read_cap_secret = &store.get_store_readcap().key; + let branch_id = commit.branch(); + let repo_id = repo.id; + let store = Arc::clone(&repo.store); + let branch = repo.branch(branch_id)?; + let topic_id = &branch.topic; + let topic_priv_key = &branch.topic_priv_key; + let publisher_pubkey = publisher.to_pub(); + let key = Self::derive_key(&repo_id, branch_id, &branch.read_cap.key, &publisher_pubkey); + let commit_key = commit.key().unwrap(); + let mut encrypted_commit_key = Vec::from(commit_key.slice()); + let mut nonce = seq.to_le_bytes().to_vec(); + nonce.append(&mut vec![0; 4]); + let mut cipher = ChaCha20::new((&key).into(), (nonce.as_slice()).into()); + cipher.apply_keystream(encrypted_commit_key.as_mut_slice()); + let mut blocks = vec![]; for bid in commit.blocks().iter() { blocks.push(store.get(bid)?); @@ -93,10 +137,8 @@ impl EventV0 { for bid in additional_blocks.iter() { blocks.push(store.get(bid)?); } - // (*seq) += 1; - let publisher_pubkey = publisher.to_pub(); let event_content = EventContentV0 { - topic: topic_id, + topic: *topic_id, publisher: PeerId::Forwarded(publisher_pubkey), seq, blocks, @@ -104,10 +146,10 @@ impl EventV0 { .header() .as_ref() .map_or_else(|| vec![], |h| h.files().to_vec()), - key: vec![], // TODO + key: encrypted_commit_key, }; let event_content_ser = serde_bare::to_vec(&event_content).unwrap(); - let topic_sig = sign(topic_priv_key, &topic_id, &event_content_ser)?; + let topic_sig = sign(topic_priv_key, topic_id, &event_content_ser)?; let peer_sig = sign(publisher, &publisher_pubkey, &event_content_ser)?; Ok(EventV0 { content: event_content, diff --git a/ng-repo/src/lib.rs b/ng-repo/src/lib.rs index 41ef77e..4c472bc 100644 --- a/ng-repo/src/lib.rs +++ b/ng-repo/src/lib.rs @@ -22,8 +22,6 @@ pub mod branch; pub mod repo; -pub mod site; - pub mod store; pub mod event; diff --git a/ng-repo/src/object.rs b/ng-repo/src/object.rs index 4671965..7029d5d 100644 --- a/ng-repo/src/object.rs +++ b/ng-repo/src/object.rs @@ -60,19 +60,17 @@ pub struct Object { } impl Object { - pub(crate) fn convergence_key( - /*store_pubkey: &StoreRepo, - store_readcap_secret: &ReadCapSecret,*/ - store: &Store, - ) -> [u8; blake3::OUT_LEN] { + // if it is a Store root repo, the key_material is derived from RepoId + RepoWriteCapSecret + // for a private store root repo, the repowritecapsecret is omitted (zeros) + pub(crate) fn convergence_key(store: &Store) -> [u8; blake3::OUT_LEN] { let mut key_material = match ( *store.get_store_repo().repo_id(), - store.get_store_readcap_secret().clone(), + store.get_store_overlay_branch_readcap_secret().clone(), ) { (PubKey::Ed25519PubKey(pubkey), SymKey::ChaCha20Key(secret)) => { [pubkey, secret].concat() } - (_, _) => panic!("cannot sign with Montgomery key"), + (_, _) => panic!("cannot derive key with Montgomery key"), }; let res = blake3::derive_key("NextGraph Data BLAKE3 key", key_material.as_slice()); key_material.zeroize(); diff --git a/ng-repo/src/repo.rs b/ng-repo/src/repo.rs index b3bb8cf..1185ad4 100644 --- a/ng-repo/src/repo.rs +++ b/ng-repo/src/repo.rs @@ -74,15 +74,36 @@ impl UserInfo { } } +#[derive(Debug)] +pub struct BranchInfo { + pub id: BranchId, + + pub branch_type: BranchType, + + pub topic: TopicId, + + pub topic_priv_key: BranchWriteCapSecret, + + pub read_cap: ReadCap, +} + /// In memory Repository representation. With helper functions that access the underlying UserStore and keeps proxy of the values +#[derive(Debug)] pub struct Repo { pub id: RepoId, /// Repo definition pub repo_def: Repository, + pub read_cap: Option, + + pub write_cap: Option, + pub signer: Option, pub members: HashMap, + + pub branches: HashMap, + pub store: Arc, } @@ -139,16 +160,19 @@ impl Repo { members, store, signer: None, + read_cap: None, + write_cap: None, + branches: HashMap::new(), } } pub fn verify_permission(&self, commit: &Commit) -> Result<(), NgError> { let content_author = commit.content_v0().author; - // let body = commit.load_body(self.store.unwrap())?; - // match self.members.get(&content_author) { - // Some(info) => return info.has_any_perm(&body.required_permission()), - // None => {} - // } + let body = commit.load_body(&self.store)?; + match self.members.get(&content_author) { + Some(info) => return info.has_any_perm(&body.required_permission()), + None => {} + } Err(NgError::PermissionDenied) } @@ -159,6 +183,27 @@ impl Repo { } } + pub fn branch(&self, id: &BranchId) -> Result<&BranchInfo, NgError> { + //TODO: load the BranchInfo from storage + self.branches.get(id).ok_or(NgError::BranchNotFound) + } + + pub fn overlay_branch(&self) -> Option<&BranchInfo> { + for (_, branch) in self.branches.iter() { + if branch.branch_type == BranchType::Overlay { + return Some(branch); + } + } + None + } + + pub fn overlay_branch_read_cap(&self) -> Option<&ReadCap> { + match self.overlay_branch() { + Some(bi) => Some(&bi.read_cap), + None => self.read_cap.as_ref(), // this is for private stores that don't have an overlay branch + } + } + // pub(crate) fn get_store(&self) -> &Store { // self.store.unwrap() // } diff --git a/ng-repo/src/store.rs b/ng-repo/src/store.rs index 1a9f75a..50a64ce 100644 --- a/ng-repo/src/store.rs +++ b/ng-repo/src/store.rs @@ -11,30 +11,51 @@ //! Store of a Site, or of a Group or Dialog +use core::fmt; use std::collections::HashMap; use std::sync::{Arc, RwLock}; use crate::block_storage::BlockStorage; use crate::errors::{NgError, StorageError}; use crate::object::Object; -use crate::repo::Repo; +use crate::repo::{BranchInfo, Repo}; use crate::types::*; use crate::utils::{generate_keypair, sign, verify}; use crate::log::*; use rand::prelude::*; - use threshold_crypto::{SecretKeySet, SecretKeyShare}; pub struct Store { store_repo: StoreRepo, store_readcap: ReadCap, + store_overlay_branch_readcap: ReadCap, overlay_id: OverlayId, storage: Arc>, - //repos: HashMap, } + +impl fmt::Debug for Store { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "Store.\nstore_repo {:?}", self.store_repo)?; + writeln!(f, "store_readcap {:?}", self.store_readcap)?; + writeln!( + f, + "store_overlay_branch_readcap {:?}", + self.store_overlay_branch_readcap + )?; + writeln!(f, "overlay_id {:?}", self.overlay_id) + } +} + impl Store { + pub fn set_read_caps(&mut self, read_cap: ReadCap, overlay_read_cap: Option) { + self.store_readcap = read_cap; + if let Some(overlay_read_cap) = overlay_read_cap { + self.store_overlay_branch_readcap = overlay_read_cap; + } + } + pub fn get_store_repo(&self) -> &StoreRepo { &self.store_repo } @@ -43,6 +64,10 @@ impl Store { &self.store_readcap } + pub fn get_store_overlay_branch_readcap_secret(&self) -> &ReadCapSecret { + &self.store_overlay_branch_readcap.key + } + pub fn get_store_readcap_secret(&self) -> &ReadCapSecret { &self.store_readcap.key } @@ -83,6 +108,7 @@ impl Store { self: Arc, creator: &UserId, creator_priv_key: &PrivKey, + repo_write_cap_secret: SymKey, ) -> Result<(Repo, Vec<(Commit, Vec)>), NgError> { let mut events = Vec::with_capacity(6); @@ -118,8 +144,6 @@ impl Store { // creating the RootBranch commit, acks to Repository commit - let repo_write_cap_secret = SymKey::random(); - let root_branch_commit_body = CommitBody::V0(CommitBodyV0::RootBranch(RootBranch::V0(RootBranchV0 { id: repo_pub_key, @@ -316,7 +340,7 @@ impl Store { repo_pub_key, QuorumType::IamTheSignature, vec![add_branch_commit.reference().unwrap()], - vec![root_branch_readcap], + vec![root_branch_readcap.clone()], sync_sig_commit_body.clone(), &self, )?; @@ -340,7 +364,7 @@ impl Store { main_branch_pub_key, QuorumType::IamTheSignature, vec![branch_read_cap.clone()], - vec![branch_read_cap], + vec![branch_read_cap.clone()], sync_sig_commit_body, &self, )?; @@ -358,12 +382,34 @@ impl Store { // preparing the Repo + let root_branch = BranchInfo { + id: repo_pub_key.clone(), + branch_type: BranchType::Root, + topic: topic_pub_key, + topic_priv_key: topic_priv_key, + read_cap: root_branch_readcap.clone(), + }; + + let main_branch = BranchInfo { + id: main_branch_pub_key.clone(), + branch_type: BranchType::Main, + topic: main_branch_topic_pub_key, + topic_priv_key: main_branch_topic_priv_key, + read_cap: branch_read_cap, + }; + let repo = Repo { id: repo_pub_key, repo_def: repository, signer: Some(signer_cap), members: HashMap::new(), - store: self, + store: Arc::clone(&self), + read_cap: Some(root_branch_readcap), + write_cap: Some(repo_write_cap_secret), + branches: HashMap::from([ + (repo_pub_key, root_branch), + (main_branch_pub_key, main_branch), + ]), }; Ok((repo, events)) @@ -372,6 +418,7 @@ impl Store { pub fn new( store_repo: StoreRepo, store_readcap: ReadCap, + store_overlay_branch_readcap: ReadCap, storage: Arc>, ) -> Self { Self { @@ -379,6 +426,7 @@ impl Store { store_readcap, overlay_id: store_repo.overlay_id_for_storage_purpose(), storage, + store_overlay_branch_readcap, } } @@ -388,9 +436,11 @@ impl Store { use crate::block_storage::HashMapBlockStorage; let store_repo = StoreRepo::dummy_public_v0(); let store_readcap = ReadCap::dummy(); + let store_overlay_branch_readcap = ReadCap::dummy(); Arc::new(Self::new( store_repo, store_readcap, + store_overlay_branch_readcap, Arc::new(RwLock::new(HashMapBlockStorage::new())) as Arc>, )) @@ -401,9 +451,11 @@ impl Store { use crate::block_storage::HashMapBlockStorage; let store_repo = StoreRepo::dummy_with_key(repo_pubkey); let store_readcap = ReadCap::dummy(); + let store_overlay_branch_readcap = ReadCap::dummy(); Arc::new(Self::new( store_repo, store_readcap, + store_overlay_branch_readcap, Arc::new(RwLock::new(HashMapBlockStorage::new())) as Arc>, )) diff --git a/ng-repo/src/types.rs b/ng-repo/src/types.rs index 7b602dd..feb0d4d 100644 --- a/ng-repo/src/types.rs +++ b/ng-repo/src/types.rs @@ -91,7 +91,6 @@ impl SymKey { pub fn from_array(array: [u8; 32]) -> Self { SymKey::ChaCha20Key(array) } - #[deprecated(note = "**Don't use nil method**")] pub fn nil() -> Self { SymKey::ChaCha20Key([0; 32]) } @@ -399,7 +398,6 @@ impl BlockId { Digest::Blake3Digest32([0u8; 32]) } - #[deprecated(note = "**Don't use nil method**")] pub fn nil() -> Self { Digest::Blake3Digest32([0u8; 32]) } @@ -414,7 +412,6 @@ impl BlockRef { } } - #[deprecated(note = "**Don't use nil method**")] pub fn nil() -> Self { BlockRef { id: Digest::Blake3Digest32([0u8; 32]), @@ -742,29 +739,6 @@ pub enum SiteName { Name(String), } -/// Site V0 -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct SiteV0 { - pub site_type: SiteType, - - pub id: PubKey, - - pub name: SiteName, - - // Identity::OrgPublicStore or Identity::IndividualPublicStore - pub public: SiteStore, - - // Identity::OrgProtectedStore or Identity::IndividualProtectedStore - pub protected: SiteStore, - - // Identity::OrgPrivateStore or Identity::IndividualPrivateStore - pub private: SiteStore, - - /// Only for IndividualSite: TODO reorganize those 2 fields - pub cores: Vec<(PubKey, Option<[u8; 32]>)>, - pub bootstraps: Vec, -} - /// Reduced Site (for QRcode) #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct ReducedSiteV0 { @@ -971,9 +945,8 @@ pub enum Block { /// /// First commit published in root branch, signed by repository key /// For the Root repo of a store(overlay), the convergence_key should be derived from : -/// "NextGraph Store Root Repo BLAKE3 convergence key", -/// RepoId + RepoWriteCapSecret) -/// for a private store root repo, the repowritecapsecret can be omitted +/// "NextGraph Data BLAKE3 key", RepoId + RepoWriteCapSecret) +/// for a private store root repo, the RepoWriteCapSecret can be omitted #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct RepositoryV0 { /// Repo public key ID @@ -1077,7 +1050,7 @@ pub struct RootBranchV0 { /// Mutable App-specific metadata /// when the list of owners is changed, a crypto_box containing the RepoWriteCapSecret should be included here for each owner. - /// this should also be done at creation time, with the UserId of the first owner, except for individual store repo, because it doesnt have a RepoWriteCapSecret + /// this should also be done at creation time, with the UserId of the first owner, except for individual private store repo, because it doesnt have a RepoWriteCapSecret #[serde(with = "serde_bytes")] pub metadata: Vec, } @@ -1247,7 +1220,8 @@ pub enum BranchType { Store, Overlay, User, - Transactional, // this could have been called OtherTransaction, but for the sake of simplicity, we use Transaction for any branch that is not the Main one. + Transactional, // this could have been called OtherTransaction, but for the sake of simplicity, we use Transactional for any branch that is not the Main one. + Root, // only used for BranchInfo } impl fmt::Display for BranchType { @@ -1262,6 +1236,7 @@ impl fmt::Display for BranchType { Self::Overlay => "Overlay", Self::User => "User", Self::Transactional => "Transactional", + Self::Root => "Root", } ) } @@ -2161,6 +2136,11 @@ impl CommitContent { CommitContent::V0(v0) => &v0.author, } } + pub fn branch(&self) -> &BranchId { + match self { + CommitContent::V0(v0) => &v0.branch, + } + } pub fn author_digest(author: &UserId, overlay: OverlayId) -> Digest { let author_id = serde_bare::to_vec(author).unwrap(); diff --git a/ng-sdk-js/src/lib.rs b/ng-sdk-js/src/lib.rs index e9afeaf..cfdae4f 100644 --- a/ng-sdk-js/src/lib.rs +++ b/ng-sdk-js/src/lib.rs @@ -162,74 +162,6 @@ pub async fn get_wallets() -> Result { Ok(JsValue::UNDEFINED) } -fn take_some_peer_last_seq_numbers(peer_id: PubKey, qty: u16) -> Result { - let res = session_get(format!("ng_peer_last_seq@{}", peer_id)); - let val = match res { - Some(old_str) => { - let decoded = base64_url::decode(&old_str).map_err(|_| NgError::SerializationError)?; - match serde_bare::from_slice(&decoded)? { - SessionPeerLastSeq::V0(old_val) => old_val, - _ => unimplemented!(), - } - } - None => 0, - }; - let new_val = val + qty as u64; - let spls = SessionPeerLastSeq::V0(new_val); - let ser = serde_bare::to_vec(&spls)?; - //saving the new val - let encoded = base64_url::encode(&ser); - let r = session_save(format!("ng_peer_last_seq@{}", peer_id), encoded); - if r.is_some() { - return Err(NgError::SerializationError); - } - Ok(val) -} - -// #[cfg(target_arch = "wasm32")] -// #[wasm_bindgen] -// async fn init_local_broker_persistent() -> Result<(), String> { -// init_local_broker_(false, None).await -// } - -// #[cfg(target_arch = "wasm32")] -// #[wasm_bindgen] -// async fn init_local_broker_with_path(path: String) -> Result<(), String> { -// init_local_broker_(false, Some(path)).await -// } - -// #[cfg(target_arch = "wasm32")] -// #[wasm_bindgen] -// async fn init_local_broker_in_memory() -> Result<(), String> { -// init_local_broker_(true, None).await -// } - -async fn init_local_broker_(in_memory: bool, path: Option) -> Result<(), String> { - // INIT_BROKER - // .get_or_init(async { - // BROKER - // .write() - // .await - // .register_last_seq_function(Box::new(take_some_peer_last_seq_numbers)); - // true - // }) - // .await; - Ok(()) -} - -async fn init_local_broker_internal() -> Result<(), String> { - // INIT_BROKER - // .get_or_init(async { - // BROKER - // .write() - // .await - // .register_last_seq_function(Box::new(take_some_peer_last_seq_numbers)); - // true - // }) - // .await; - Ok(()) -} - #[cfg(target_arch = "wasm32")] #[wasm_bindgen] pub async fn session_start(wallet_name: String, user_js: JsValue) -> Result { diff --git a/ng-storage-rocksdb/src/kcv_storage.rs b/ng-storage-rocksdb/src/kcv_storage.rs index 48c39cc..0e41b26 100644 --- a/ng-storage-rocksdb/src/kcv_storage.rs +++ b/ng-storage-rocksdb/src/kcv_storage.rs @@ -585,7 +585,10 @@ impl RocksdbKCVStore { let db: TransactionDB = TransactionDB::open_cf(&opts, &tx_options, &path, vec!["cf0", "cf1"]).unwrap(); - log_info!("created db with Rocksdb Version: {}", Env::version()); + log_info!( + "created kcv storage with Rocksdb Version: {}", + Env::version() + ); Ok(RocksdbKCVStore { db: db, diff --git a/ng-verifier/Cargo.toml b/ng-verifier/Cargo.toml index e6d22b0..a3dcb6f 100644 --- a/ng-verifier/Cargo.toml +++ b/ng-verifier/Cargo.toml @@ -15,6 +15,9 @@ rust-version.workspace = true [badges] maintenance = { status = "actively-developed" } +[features] +testing = [] + [dependencies] ng-repo = { path = "../ng-repo", version = "0.1.0" } ng-net = { path = "../ng-net", version = "0.1.0" } diff --git a/ng-verifier/src/lib.rs b/ng-verifier/src/lib.rs index 5f13f5b..afd6068 100644 --- a/ng-verifier/src/lib.rs +++ b/ng-verifier/src/lib.rs @@ -4,5 +4,7 @@ pub mod user_storage; pub mod verifier; +pub mod site; + #[cfg(not(target_family = "wasm"))] pub mod rocksdb_user_storage; diff --git a/ng-repo/src/site.rs b/ng-verifier/src/site.rs similarity index 54% rename from ng-repo/src/site.rs rename to ng-verifier/src/site.rs index a284055..3880726 100644 --- a/ng-repo/src/site.rs +++ b/ng-verifier/src/site.rs @@ -11,9 +11,35 @@ //! Site (Public, Protected, Private) of Individual and Org -use crate::errors::NgError; use crate::types::*; -use crate::utils::{generate_keypair, sign, verify}; +use crate::verifier::Verifier; +use ng_repo::errors::NgError; +use ng_repo::types::*; +use ng_repo::utils::{generate_keypair, sign, verify}; +use serde::{Deserialize, Serialize}; + +/// Site V0 +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct SiteV0 { + pub site_type: SiteType, + + pub id: PubKey, + + pub name: SiteName, + + // Identity::OrgPublicStore or Identity::IndividualPublicStore + pub public: SiteStore, + + // Identity::OrgProtectedStore or Identity::IndividualProtectedStore + pub protected: SiteStore, + + // Identity::OrgPrivateStore or Identity::IndividualPrivateStore + pub private: SiteStore, + + /// Only for IndividualSite: TODO reorganize those 2 fields + pub cores: Vec<(PubKey, Option<[u8; 32]>)>, + pub bootstraps: Vec, +} impl SiteV0 { pub fn get_individual_user_priv_key(&self) -> Option { @@ -23,9 +49,18 @@ impl SiteV0 { } } - pub fn create_personal( + fn site_store_to_store_repo(site_store: &SiteStore) -> StoreRepo { + StoreRepo::V0(match site_store.store_type { + SiteStoreType::Public => StoreRepoV0::PublicStore(site_store.id), + SiteStoreType::Protected => StoreRepoV0::ProtectedStore(site_store.id), + SiteStoreType::Private => StoreRepoV0::PrivateStore(site_store.id), + }) + } + + fn create_individual_( user_priv_key: PrivKey, - private_store_read_cap: ReadCap, + verifier: &mut Verifier, + site_name: SiteName, ) -> Result { let site_pubkey = user_priv_key.to_pub(); @@ -50,10 +85,31 @@ impl SiteV0 { store_type: SiteStoreType::Private, }; + let public_store = Self::site_store_to_store_repo(&public); + let protected_store = Self::site_store_to_store_repo(&protected); + let private_store = Self::site_store_to_store_repo(&private); + + verifier.reserve_more(18)?; + + let public_repo = + verifier.new_store_default(&site_pubkey, &user_priv_key, &public_store, false)?; + + let protected_repo = + verifier.new_store_default(&site_pubkey, &user_priv_key, &protected_store, false)?; + + let private_repo = + verifier.new_store_default(&site_pubkey, &user_priv_key, &private_store, true)?; + + // TODO: create user branch + // TODO: add the 2 commits in user branch about StoreUpdate of public and protected stores. + Ok(Self { - site_type: SiteType::Individual((user_priv_key, private_store_read_cap)), + site_type: SiteType::Individual(( + user_priv_key, + private_repo.read_cap.to_owned().unwrap(), + )), id: site_pubkey, - name: SiteName::Personal, + name: site_name, public, protected, private, @@ -65,41 +121,16 @@ impl SiteV0 { pub fn create_individual( name: String, user_priv_key: PrivKey, - private_store_read_cap: ReadCap, + verifier: &mut Verifier, ) -> Result { - let site_pubkey = user_priv_key.to_pub(); - - let (public_store_privkey, public_store_pubkey) = generate_keypair(); - - let (protected_store_privkey, protected_store_pubkey) = generate_keypair(); - - let (private_store_privkey, private_store_pubkey) = generate_keypair(); - - let public = SiteStore { - id: public_store_pubkey, - store_type: SiteStoreType::Public, - }; - - let protected = SiteStore { - id: protected_store_pubkey, - store_type: SiteStoreType::Protected, - }; - - let private = SiteStore { - id: private_store_pubkey, - store_type: SiteStoreType::Private, - }; + Self::create_individual_(user_priv_key, verifier, SiteName::Name(name)) + } - Ok(Self { - site_type: SiteType::Individual((user_priv_key, private_store_read_cap)), - id: site_pubkey, - name: SiteName::Name(name), - public, - protected, - private, - cores: vec![], - bootstraps: vec![], - }) + pub fn create_personal( + user_priv_key: PrivKey, + verifier: &mut Verifier, + ) -> Result { + Self::create_individual_(user_priv_key, verifier, SiteName::Personal) } pub fn create_org(name: String) -> Result { diff --git a/ng-verifier/src/types.rs b/ng-verifier/src/types.rs index cf1d7c7..6684d12 100644 --- a/ng-verifier/src/types.rs +++ b/ng-verifier/src/types.rs @@ -36,7 +36,22 @@ use serde::{Deserialize, Serialize}; use web_time::SystemTime; //use yrs::{StateVector, Update}; -#[derive(Debug, Clone)] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum SessionPeerLastSeq { + V0(u64), + V1((u64, Sig)), +} + +impl SessionPeerLastSeq { + pub fn ser(&self) -> Result, NgError> { + Ok(serde_bare::to_vec(self)?) + } + pub fn deser(ser: &[u8]) -> Result { + Ok(serde_bare::from_slice(ser).map_err(|_| NgError::SerializationError)?) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] pub enum VerifierType { /// nothing will be saved on disk during the session Memory, @@ -59,7 +74,7 @@ impl VerifierType { } } -//type LastSeqFn = fn(PubKey, u16) -> Result; +//type LastSeqFn = fn(peer_id: PubKey, qty: u16) -> Result; pub type LastSeqFn = dyn Fn(PubKey, u16) -> Result + 'static + Sync + Send; // peer_id: PubKey, seq_num:u64, event_ser: vec, @@ -96,6 +111,15 @@ pub enum VerifierConfigType { WebRocksDb, } +impl VerifierConfigType { + pub(crate) fn should_load_last_seq_num(&self) -> bool { + match self { + Self::JsSaveSession(_) | Self::RocksDb(_) => true, + _ => false, + } + } +} + #[derive(Debug)] pub struct VerifierConfig { pub config_type: VerifierConfigType, @@ -104,7 +128,8 @@ pub struct VerifierConfig { /// not used for Memory pub peer_priv_key: PrivKey, pub user_priv_key: PrivKey, - pub private_store_read_cap: ObjectRef, + pub private_store_read_cap: Option, + pub private_store_id: Option, } pub type CancelFn = Box; diff --git a/ng-verifier/src/verifier.rs b/ng-verifier/src/verifier.rs index 0e7a47b..d04c513 100644 --- a/ng-verifier/src/verifier.rs +++ b/ng-verifier/src/verifier.rs @@ -21,6 +21,9 @@ use ng_repo::{ types::*, utils::{generate_keypair, sign}, }; +use std::cmp::max; +use std::fs::{create_dir_all, read, write, File, OpenOptions}; +use std::io::Write; use core::fmt; //use oxigraph::io::{RdfFormat, RdfParser, RdfSerializer}; @@ -55,6 +58,8 @@ pub struct Verifier { last_reservation: SystemTime, stores: HashMap>, repos: HashMap, + /// only used for InMemory type, to store the outbox + in_memory_outbox: Vec, } impl fmt::Debug for Verifier { @@ -65,7 +70,8 @@ impl fmt::Debug for Verifier { } impl Verifier { - #[cfg(test)] + #[allow(deprecated)] + #[cfg(any(test, feature = "testing"))] pub fn new_dummy() -> Self { use ng_repo::block_storage::HashMapBlockStorage; let (peer_priv_key, peer_id) = generate_keypair(); @@ -77,7 +83,8 @@ impl Verifier { user_master_key: [0; 32], peer_priv_key, user_priv_key: PrivKey::random_ed(), - private_store_read_cap: ObjectRef::nil(), + private_store_read_cap: None, + private_store_id: None, }, connected_server_id: None, graph_dataset: None, @@ -86,35 +93,105 @@ impl Verifier { last_seq_num: 0, peer_id, max_reserved_seq_num: 1, - last_reservation: SystemTime::now(), + last_reservation: SystemTime::UNIX_EPOCH, stores: HashMap::new(), repos: HashMap::new(), + in_memory_outbox: vec![], } } - pub fn get_store(&mut self, store_repo: &StoreRepo) -> Arc { + pub fn get_store_mut(&mut self, store_repo: &StoreRepo) -> Arc { let overlay_id = store_repo.overlay_id_for_storage_purpose(); let store = self.stores.entry(overlay_id).or_insert_with(|| { // FIXME: get store_readcap from user storage let store_readcap = ReadCap::nil(); + let store_overlay_branch_readcap = ReadCap::nil(); let store = Store::new( *store_repo, store_readcap, + store_overlay_branch_readcap, Arc::clone( &self .block_storage .as_ref() .ok_or(core::fmt::Error) - .expect("get_store cannot be called on Remote Verifier"), + .expect("get_store_mut cannot be called on Remote Verifier"), ), ); - //self.stores.insert(overlay_id, store); - //let store = self.stores.entry(overlay_id).or_insert(store); Arc::new(store) }); Arc::clone(store) } + pub fn complete_site_store( + &mut self, + store_repo: &StoreRepo, + mut repo: Repo, + //read_cap: &ReadCap, + //overlay_read_cap: Option<&ReadCap>, + ) -> Result { + let read_cap = repo.read_cap.to_owned().unwrap(); + let overlay_read_cap = repo.overlay_branch_read_cap().cloned(); + + let overlay_id = store_repo.overlay_id_for_storage_purpose(); + let store = self + .stores + .remove(&overlay_id) + .ok_or(NgError::StoreNotFound)?; + // let mut repo = self + // .repos + // .remove(store_repo.repo_id()) + // .ok_or(NgError::RepoNotFound)?; + // log_info!( + // "{}", + // Arc::::strong_count(&repo.store) + // ); + drop(repo.store); + //log_info!("{}", Arc::::strong_count(&store)); + let mut mut_store = Arc::::into_inner(store).unwrap(); + mut_store.set_read_caps(read_cap, overlay_read_cap); + let new_store = Arc::new(mut_store); + let _ = self.stores.insert(overlay_id, Arc::clone(&new_store)); + repo.store = new_store; + //let _ = self.repos.insert(*store_repo.repo_id(), repo); + Ok(repo) + } + + pub fn get_store(&self, store_repo: &StoreRepo) -> Result, NgError> { + let overlay_id = store_repo.overlay_id_for_storage_purpose(); + let store = self.stores.get(&overlay_id).ok_or(NgError::StoreNotFound)?; + Ok(Arc::clone(store)) + } + + pub fn get_repo_mut( + &mut self, + id: RepoId, + store_repo: &StoreRepo, + ) -> Result<&mut Repo, NgError> { + let store = self.get_store(store_repo); + let repo_ref = self.repos.get_mut(&id).ok_or(NgError::RepoNotFound); + // .or_insert_with(|| { + // // load from storage + // Repo { + // id, + // repo_def: Repository::new(&PubKey::nil(), &vec![]), + // read_cap: None, + // write_cap: None, + // signer: None, + // members: HashMap::new(), + // branches: HashMap::new(), + // store, + // } + // }); + repo_ref + } + + pub fn get_repo(&self, id: RepoId, store_repo: &StoreRepo) -> Result<&Repo, NgError> { + //let store = self.get_store(store_repo); + let repo_ref = self.repos.get(&id).ok_or(NgError::RepoNotFound); + repo_ref + } + pub fn add_store(&mut self, store: Arc) { let overlay_id = store.get_store_repo().overlay_id_for_storage_purpose(); if self.stores.contains_key(&overlay_id) { @@ -131,12 +208,13 @@ impl Verifier { additional_blocks: &Vec, //topic_id: TopicId, //topic_priv_key: &BranchWriteCapSecret, - store: &Store, // store could be omitted and a store repo ID would be given instead. - ) -> Result { + repo_id: RepoId, + store_repo: &StoreRepo, + ) -> Result<(), NgError> { if self.last_seq_num + 1 >= self.max_reserved_seq_num { self.reserve_more(1)?; } - self.new_event_(commit, additional_blocks, store) + self.new_event_(commit, additional_blocks, repo_id, store_repo) } fn new_event_( @@ -147,67 +225,187 @@ impl Verifier { additional_blocks: &Vec, //topic_id: TopicId, //topic_priv_key: &BranchWriteCapSecret, - store: &Store, // store could be omitted and a store repo ID would be given instead. - ) -> Result { - let topic_id = TopicId::nil(); // should be fetched from user storage, based on the Commit.branch - let topic_priv_key = BranchWriteCapSecret::nil(); // should be fetched from user storage, based on repoId found in user storage (search by branchId) + // store: &Store, // store could be omitted and a store repo ID would be given instead. + repo_id: RepoId, + store_repo: &StoreRepo, + ) -> Result<(), NgError> { + //let topic_id = TopicId::nil(); // should be fetched from user storage, based on the Commit.branch + //let topic_priv_key = BranchWriteCapSecret::nil(); // should be fetched from user storage, based on repoId found in user storage (search by branchId) + //self.get_store(store_repo) + let publisher = self.config.peer_priv_key.clone(); + self.last_seq_num += 1; + let seq_num = self.last_seq_num; + let repo = self.get_repo(repo_id, store_repo)?; + + let event = Event::new(&publisher, seq_num, commit, additional_blocks, repo)?; + self.send_or_save_event_to_outbox(event)?; + Ok(()) + } + + fn new_event_with_repo_( + &mut self, + //publisher: &PrivKey, + //seq: &mut u64, + commit: &Commit, + additional_blocks: &Vec, + //topic_id: TopicId, + //topic_priv_key: &BranchWriteCapSecret, + // store: &Store, // store could be omitted and a store repo ID would be given instead. + repo: &Repo, + ) -> Result<(), NgError> { + //let topic_id = TopicId::nil(); // should be fetched from user storage, based on the Commit.branch + //let topic_priv_key = BranchWriteCapSecret::nil(); // should be fetched from user storage, based on repoId found in user storage (search by branchId) + //self.get_store(store_repo) + let publisher = self.config.peer_priv_key.clone(); self.last_seq_num += 1; - Event::new( - &self.config.peer_priv_key, - self.last_seq_num, - commit, - additional_blocks, - topic_id, - &topic_priv_key, - store, - ) + let seq_num = self.last_seq_num; + + let event = Event::new(&publisher, seq_num, commit, additional_blocks, repo)?; + self.send_or_save_event_to_outbox(event)?; + Ok(()) } pub(crate) fn last_seq_number(&mut self) -> Result { - if self.last_seq_num + 1 >= self.max_reserved_seq_num { + if self.available_seq_nums() <= 1 { self.reserve_more(1)?; } self.last_seq_num += 1; Ok(self.last_seq_num) } + pub(crate) fn new_events_with_repo( + &mut self, + events: Vec<(Commit, Vec)>, + repo: &Repo, + ) -> Result<(), NgError> { + let missing_count = events.len() as i64 - self.available_seq_nums() as i64; + // this is reducing the capacity of reserver_seq_num by half (cast from u64 to i64) + // but we will never reach situation where so many seq_nums are reserved, neither such a big list of events to process + if missing_count >= 0 { + self.reserve_more(missing_count as u64 + 1)?; + } + for event in events { + self.new_event_with_repo_(&event.0, &event.1, repo)?; + } + Ok(()) + } + pub(crate) fn new_events( &mut self, events: Vec<(Commit, Vec)>, - store: &Store, - ) -> Result, NgError> { + repo_id: RepoId, + store_repo: &StoreRepo, + ) -> Result<(), NgError> { let missing_count = events.len() as i64 - self.available_seq_nums() as i64; // this is reducing the capacity of reserver_seq_num by half (cast from u64 to i64) // but we will never reach situation where so many seq_nums are reserved, neither such a big list of events to process if missing_count >= 0 { self.reserve_more(missing_count as u64 + 1)?; } - let mut res = vec![]; for event in events { - let event = self.new_event_(&event.0, &event.1, store)?; - res.push(event); + self.new_event_(&event.0, &event.1, repo_id.clone(), store_repo)?; } - Ok(res) + Ok(()) } fn available_seq_nums(&self) -> u64 { self.max_reserved_seq_num - self.last_seq_num } - fn reserve_more(&mut self, at_least: u64) -> Result<(), NgError> { - // the qty should be calculated based on the last_reservation. the closer to now, the higher the qty. + pub(crate) fn reserve_more(&mut self, at_least: u64) -> Result<(), NgError> { + // the qty is calculated based on the last_reservation. the closer to now, the higher the qty. // below 1 sec, => 100 // below 5 sec, => 10 - // below 10 sec => 1 - self.take_some_peer_last_seq_numbers(10) + // above 5 sec => 1 + let qty = match self.last_reservation.elapsed().unwrap().as_secs() { + 0..=1 => 100u16, + 2..=5 => 10u16, + 6.. => 1u16, + }; + self.take_some_peer_last_seq_numbers(max(at_least as u16, qty)) } - fn take_some_peer_last_seq_numbers(&mut self, qty: u16) -> Result<(), NgError> { - // TODO the magic + fn send_or_save_event_to_outbox<'a>(&'a mut self, event: Event) -> Result<(), NgError> { + log_debug!("========== EVENT {:03}: {}", event.seq_num(), event); + + if self.connected_server_id.is_some() { + // send the events to the server already + } else { + match &self.config.config_type { + VerifierConfigType::JsSaveSession(js) => { + (js.outbox_write_function)( + self.peer_id, + event.seq_num(), + serde_bare::to_vec(&event)?, + )?; + } + VerifierConfigType::RocksDb(path) => {} + VerifierConfigType::Memory => { + self.in_memory_outbox.push(event); + } + _ => unimplemented!(), + } + } + Ok(()) + } - self.max_reserved_seq_num += qty as u64; + fn take_some_peer_last_seq_numbers(&mut self, qty: u16) -> Result<(), NgError> { + match &self.config.config_type { + VerifierConfigType::JsSaveSession(js) => { + let res = (js.last_seq_function)(self.peer_id, qty)?; + self.max_reserved_seq_num = res + qty as u64; + } + VerifierConfigType::RocksDb(path) => { + let mut path = path.clone(); + std::fs::create_dir_all(path.clone()).unwrap(); + path.push(format!("lastseq{}", self.peer_id.to_string())); + log_debug!("last_seq path {}", path.display()); + + let file = read(path.clone()); + let (mut file_save, val) = match file { + Ok(ser) => { + let old_val = if ser.len() > 0 { + match SessionPeerLastSeq::deser(&ser)? { + SessionPeerLastSeq::V0(v) => v, + _ => unimplemented!(), + } + } else { + 0 + }; + ( + OpenOptions::new() + .write(true) + .open(path) + .map_err(|_| NgError::SerializationError)?, + old_val, + ) + } + Err(_) => ( + File::create(path).map_err(|_| NgError::SerializationError)?, + 0, + ), + }; + if qty > 0 { + let new_val = val + qty as u64; + let spls = SessionPeerLastSeq::V0(new_val); + let ser = spls.ser()?; + file_save + .write_all(&ser) + .map_err(|_| NgError::SerializationError)?; + + file_save + .sync_data() + .map_err(|_| NgError::SerializationError)?; + } + self.max_reserved_seq_num = val + qty as u64; + } + _ => { + self.max_reserved_seq_num += qty as u64; + } + } + self.last_reservation = SystemTime::now(); log_debug!( - "reserving more seq_nums {qty}. now at {}", + "reserving more {qty} seq_nums. now at {}", self.max_reserved_seq_num ); Ok(()) @@ -224,21 +422,33 @@ impl Verifier { Some(block_storage), ), #[cfg(not(target_family = "wasm"))] - VerifierConfigType::RocksDb(path) => ( - // FIXME BIG TIME: we are reusing the same encryption key here. - // this is very temporary, until we remove the code in oxi_rocksdb of oxigraph, - // and have oxigraph use directly the UserStorage - Some(oxigraph::store::Store::open_with_key(path, config.user_master_key).unwrap()), - Some( - Box::new(RocksDbUserStorage::open(path, config.user_master_key)?) - as Box, - ), - Some(block_storage), - ), + VerifierConfigType::RocksDb(path) => { + let mut path_oxi = path.clone(); + path_oxi.push("graph"); + create_dir_all(path_oxi.clone()).unwrap(); + let mut path_user = path.clone(); + path_user.push("user"); + create_dir_all(path_user.clone()).unwrap(); + ( + // FIXME BIG TIME: we are reusing the same encryption key here. + // this is very temporary, until we remove the code in oxi_rocksdb of oxigraph, + // and have oxigraph use directly the UserStorage + Some( + oxigraph::store::Store::open_with_key(path_oxi, config.user_master_key) + .unwrap(), + ), + Some(Box::new(RocksDbUserStorage::open( + &path_user, + config.user_master_key, + )?) as Box), + Some(block_storage), + ) + } VerifierConfigType::Remote(_) => (None, None, None), _ => unimplemented!(), // can be WebRocksDb or RocksDb on wasm platforms }; let peer_id = config.peer_priv_key.to_pub(); + let should_load_last_seq_num = config.config_type.should_load_last_seq_num(); let mut verif = Verifier { config, connected_server_id: None, @@ -246,13 +456,18 @@ impl Verifier { user_storage: user, block_storage: block, peer_id, - last_reservation: SystemTime::now(), + last_reservation: SystemTime::UNIX_EPOCH, // this is to avoid reserving 100 seq_nums at every start of a new session max_reserved_seq_num: 0, last_seq_num: 0, stores: HashMap::new(), repos: HashMap::new(), + in_memory_outbox: vec![], }; - verif.take_some_peer_last_seq_numbers(1)?; + // this is important as it will load the last seq from storage + if should_load_last_seq_num { + verif.take_some_peer_last_seq_numbers(0)?; + verif.last_seq_num = verif.max_reserved_seq_num; + } Ok(verif) } @@ -272,24 +487,66 @@ impl Verifier { unimplemented!(); } + pub fn new_store_default<'a>( + &'a mut self, + creator: &UserId, + creator_priv_key: &PrivKey, + store_repo: &StoreRepo, + private: bool, + ) -> Result<&'a Repo, NgError> { + let repo_write_cap_secret = match private { + false => SymKey::random(), + true => SymKey::nil(), + }; + let overlay_id = store_repo.overlay_id_for_storage_purpose(); + let store = self.stores.entry(overlay_id).or_insert_with(|| { + let store_readcap = ReadCap::nil(); + // temporarily set the store_overlay_branch_readcap to an objectRef that has an empty id, and a key = to the repo_write_cap_secret + let store_overlay_branch_readcap = + ReadCap::from_id_key(ObjectId::nil(), repo_write_cap_secret.clone()); + let store = Store::new( + *store_repo, + store_readcap, + store_overlay_branch_readcap, + Arc::clone( + &self + .block_storage + .as_ref() + .ok_or(core::fmt::Error) + .expect("get_store_mut cannot be called on Remote Verifier"), + ), + ); + Arc::new(store) + }); + let (repo, proto_events) = Arc::clone(store).create_repo_default( + creator, + creator_priv_key, + repo_write_cap_secret, + )?; + self.new_events_with_repo(proto_events, &repo)?; + let repo = self.complete_site_store(store_repo, repo)?; + let repo_ref = self.repos.entry(repo.id).or_insert(repo); + Ok(repo_ref) + } + /// returns the Repo and the last seq_num of the peer pub fn new_repo_default<'a>( &'a mut self, creator: &UserId, creator_priv_key: &PrivKey, store_repo: &StoreRepo, - ) -> Result<(&'a Repo, Vec), NgError> { - let store = self.get_store(store_repo); - let (repo, proto_events) = store.create_repo_default(creator, creator_priv_key)?; - - let events = self.new_events(proto_events, &repo.store)?; + ) -> Result<&'a Repo, NgError> { + let store = self.get_store_mut(store_repo); + let repo_write_cap_secret = SymKey::random(); + let (repo, proto_events) = + store.create_repo_default(creator, creator_priv_key, repo_write_cap_secret)?; + self.new_events_with_repo(proto_events, &repo)?; // let mut events = vec![]; // for event in proto_events { // events.push(self.new_event(&event.0, &event.1, &repo.store)?); // } - let repo_ref = self.repos.entry(repo.id).or_insert(repo); - Ok((repo_ref, events)) + Ok(repo_ref) } } #[cfg(test)] @@ -312,19 +569,12 @@ mod test { let mut verifier = Verifier::new_dummy(); verifier.add_store(store); - let (repo, events) = verifier + let repo = verifier .new_repo_default(&creator_pub_key, &creator_priv_key, &store_repo) .expect("new_default"); log_debug!("REPO OBJECT {}", repo); - log_debug!("events: {}\n", events.len()); - let mut i = 0; - for e in events { - log_debug!("========== EVENT {:03}: {}", i, e); - i += 1; - } - - assert_eq!(verifier.last_seq_number(), Ok(6)); + assert_eq!(verifier.last_seq_num, 5); } } diff --git a/ng-wallet/Cargo.toml b/ng-wallet/Cargo.toml index 0a6461b..afc99d5 100644 --- a/ng-wallet/Cargo.toml +++ b/ng-wallet/Cargo.toml @@ -20,6 +20,7 @@ serde_bytes = "0.11.7" serde-big-array = "0.5.1" ng-repo = { path = "../ng-repo", version = "0.1.0" } ng-net = { path = "../ng-net", version = "0.1.0" } +ng-verifier = { path = "../ng-verifier", version = "0.1.0" } image = "0.24.6" getrandom = { version = "0.1.1", features = ["wasm-bindgen"] } rand = { version = "0.7", features = ["getrandom"] } @@ -35,4 +36,9 @@ web-time = "0.2.0" lazy_static = "1.4.0" zeroize = { version = "1.6.0", features = ["zeroize_derive"] } crypto_box = { version = "0.8.2", features = ["seal"] } -blake3 = "1.3.1" \ No newline at end of file +blake3 = "1.3.1" + + +[dev-dependencies] +ng-repo = { path = "../ng-repo", version = "0.1.0", features = ["testing"] } +ng-verifier = { path = "../ng-verifier", version = "0.1.0", features = ["testing"] } \ No newline at end of file diff --git a/ng-wallet/src/lib.rs b/ng-wallet/src/lib.rs index 3e7fd5c..97d2376 100644 --- a/ng-wallet/src/lib.rs +++ b/ng-wallet/src/lib.rs @@ -19,6 +19,7 @@ pub mod bip39; pub mod emojis; +use ng_verifier::{site::SiteV0, verifier::Verifier}; use rand::distributions::{Distribution, Uniform}; use std::{collections::HashMap, io::Cursor, sync::Arc}; @@ -46,26 +47,31 @@ impl Wallet { pub fn id(&self) -> WalletId { match self { Wallet::V0(v0) => v0.id, + _ => unimplemented!(), } } pub fn content_as_bytes(&self) -> Vec { match self { Wallet::V0(v0) => serde_bare::to_vec(&v0.content).unwrap(), + _ => unimplemented!(), } } pub fn sig(&self) -> Sig { match self { Wallet::V0(v0) => v0.sig, + _ => unimplemented!(), } } pub fn pazzle_length(&self) -> u8 { match self { Wallet::V0(v0) => v0.content.pazzle_length, + _ => unimplemented!(), } } pub fn name(&self) -> String { match self { Wallet::V0(v0) => base64_url::encode(&v0.id.slice()), + _ => unimplemented!(), } } @@ -90,6 +96,7 @@ impl Wallet { let mut wallet_content = match self { Wallet::V0(v0) => v0.content.clone(), + _ => unimplemented!(), }; wallet_content.timestamp = timestamp; @@ -347,6 +354,7 @@ pub fn open_wallet_with_pazzle( v0.id, )?)) } + _ => unimplemented!(), } } @@ -385,6 +393,7 @@ pub fn open_wallet_with_mnemonic( v0.id, )?)) } + _ => unimplemented!(), } } @@ -447,9 +456,9 @@ pub fn gen_shuffle_for_pin() -> Vec { /// creates a Wallet from a pin, a security text and image (with option to send the bootstrap and wallet to nextgraph.one) /// and returns the Wallet, the pazzle and the mnemonic -pub fn create_wallet_v0(mut params: CreateWalletV0) -> Result { - let creating_pazzle = Instant::now(); - +pub fn create_wallet_first_step_v0( + params: CreateWalletV0, +) -> Result { // pazzle_length can only be 9, 12, or 15 if params.pazzle_length != 9 //&& params.pazzle_length != 12 @@ -533,11 +542,53 @@ pub fn create_wallet_v0(mut params: CreateWalletV0) -> Result Result< + ( + CreateWalletResultV0, + SiteV0, + HashMap>, + ), + NgWalletError, +> { + let creating_pazzle = Instant::now(); + + let mut site = SiteV0::create_personal(params.user_privkey.clone(), verifier).map_err(|e| { + log_err!("{e}"); + NgWalletError::InternalError + })?; - // TODO: private_store_read_cap - let site = SiteV0::create_personal(user_priv_key.clone(), BlockRef::nil()) - .map_err(|e| NgWalletError::InternalError)?; + let user = params.user_privkey.to_pub(); + + let wallet_id = params.wallet_privkey.to_pub(); let mut ran = thread_rng(); @@ -565,17 +616,12 @@ pub fn create_wallet_v0(mut params: CreateWalletV0) -> Result Result, + let mut brokers: HashMap> = HashMap::new(); + + let core_pubkey = params + .core_bootstrap + .get_first_peer_id() + .ok_or(NgWalletError::InvalidBootstrap)?; wallet_log.add(WalletOperation::AddSiteCoreV0(( user, - params - .core_bootstrap - .get_first_peer_id() - .ok_or(NgWalletError::InvalidBootstrap)?, + core_pubkey, params.core_registration, ))); + site.cores.push((core_pubkey, params.core_registration)); + if let Some(additional) = ¶ms.additional_bootstrap { params.core_bootstrap.merge(additional); } @@ -615,6 +666,17 @@ pub fn create_wallet_v0(mut params: CreateWalletV0) -> Result Result Result Result vec![], true => to_vec(&NgFile::V0(NgFileV0::Wallet(wallet.clone()))).unwrap(), }; - Ok(CreateWalletResultV0 { - wallet: wallet, - wallet_privkey, - wallet_file, - pazzle, - mnemonic: mnemonic.clone(), - wallet_name: base64_url::encode(&wallet_id.slice()), - client, - user, - in_memory: !params.local_save, - }) + Ok(( + CreateWalletResultV0 { + wallet: wallet, + wallet_file, + pazzle, + mnemonic: mnemonic.clone(), + wallet_name: params.wallet_name.clone(), + client: params.client.clone(), + user, + in_memory: params.in_memory, + session_id: 0, + }, + site, + brokers, + )) } #[cfg(test)] @@ -766,7 +833,7 @@ mod test { let creation = Instant::now(); - let res = create_wallet_v0(CreateWalletV0::new( + let res = create_wallet_first_step_v0(CreateWalletV0::new( img_buffer, " know yourself ".to_string(), pin, @@ -777,7 +844,11 @@ mod test { None, None, )) - .expect("create_wallet_v0"); + .expect("create_wallet_first_step_v0"); + + let mut verifier = Verifier::new_dummy(); + let (res, _, _) = + create_wallet_second_step_v0(res, &mut verifier).expect("create_wallet_second_step_v0"); log_debug!( "creation of wallet took: {} ms", diff --git a/ng-wallet/src/types.rs b/ng-wallet/src/types.rs index 5c65bc4..5d1db05 100644 --- a/ng-wallet/src/types.rs +++ b/ng-wallet/src/types.rs @@ -20,6 +20,7 @@ use ng_net::types::*; use ng_repo::errors::NgError; use ng_repo::types::*; use ng_repo::utils::{encrypt_in_place, generate_keypair, now_timestamp, sign}; +use ng_verifier::site::SiteV0; /// WalletId is a PubKey pub type WalletId = PubKey; @@ -64,21 +65,6 @@ impl Bootstrap { } } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum SessionPeerLastSeq { - V0(u64), - V1((u64, Sig)), -} - -impl SessionPeerLastSeq { - pub fn ser(&self) -> Result, NgError> { - Ok(serde_bare::to_vec(self)?) - } - pub fn deser(ser: &[u8]) -> Result { - Ok(serde_bare::from_slice(ser).map_err(|_| NgError::SerializationError)?) - } -} - #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SessionWalletStorageV0 { // string is base64_url encoding of userId(pubkey) @@ -231,11 +217,11 @@ pub struct LocalWalletStorageV0 { pub encrypted_client_storage: Vec, } -impl From<&CreateWalletResultV0> for LocalWalletStorageV0 { - fn from(res: &CreateWalletResultV0) -> Self { +impl From<&CreateWalletIntermediaryV0> for LocalWalletStorageV0 { + fn from(res: &CreateWalletIntermediaryV0) -> Self { LocalWalletStorageV0 { bootstrap: BootstrapContent::V0(BootstrapContentV0::new_empty()), - wallet: res.wallet.clone(), + wallet: Wallet::TemporarilyEmpty, in_memory: res.in_memory, client_id: res.client.id, client_auto_open: res.client.auto_open.clone(), @@ -249,6 +235,38 @@ impl From<&CreateWalletResultV0> for LocalWalletStorageV0 { } } +impl From<&CreateWalletIntermediaryV0> for SensitiveWalletV0 { + fn from(res: &CreateWalletIntermediaryV0) -> Self { + SensitiveWalletV0 { + wallet_privkey: res.wallet_privkey.clone(), + wallet_id: res.wallet_name.clone(), + save_to_ng_one: if res.send_wallet { + SaveToNGOne::Wallet + } else if res.send_bootstrap { + SaveToNGOne::Bootstrap + } else { + SaveToNGOne::No + }, + // for now, personal_site is null. will be replaced later + personal_site: PubKey::nil(), + personal_site_id: "".to_string(), + sites: HashMap::new(), + brokers: HashMap::new(), + overlay_core_overrides: HashMap::new(), + third_parties: HashMap::new(), + log: None, + master_key: None, + client: None, + } + } +} + +impl From<&CreateWalletIntermediaryV0> for SensitiveWallet { + fn from(res: &CreateWalletIntermediaryV0) -> SensitiveWallet { + SensitiveWallet::V0(res.into()) + } +} + impl LocalWalletStorageV0 { #[doc(hidden)] pub fn new( @@ -468,11 +486,16 @@ impl SensitiveWallet { Self::V0(v0) => v0.client = Some(client), } } - pub fn individual_site(&self, user_id: &UserId) -> Option<&(PrivKey, ReadCap)> { + pub fn individual_site( + &self, + user_id: &UserId, + ) -> Option<(PrivKey, Option, Option)> { match self { Self::V0(v0) => match v0.sites.get(&user_id.to_string()) { Some(site) => match &site.site_type { - SiteType::Individual(creds) => Some(creds), + SiteType::Individual((user, readcap)) => { + Some((user.clone(), Some(readcap.clone()), Some(site.private.id))) + } _ => None, }, None => None, @@ -498,6 +521,16 @@ impl SensitiveWallet { Self::V0(v0) => v0.import(encrypted_wallet, in_memory), } } + + pub fn complete_with_site_and_brokers( + &mut self, + site: SiteV0, + brokers: HashMap>, + ) { + match self { + Self::V0(v0) => v0.complete_with_site_and_brokers(site, brokers), + } + } } impl SensitiveWalletV0 { @@ -546,6 +579,19 @@ impl SensitiveWalletV0 { .overlay_core_overrides .insert(overlay.to_string(), cores.to_vec()); } + + pub fn complete_with_site_and_brokers( + &mut self, + site: SiteV0, + brokers: HashMap>, + ) { + let personal_site = site.id; + let personal_site_id = personal_site.to_string(); + self.personal_site = personal_site; + self.personal_site_id = personal_site_id.clone(); + self.sites.insert(personal_site_id, site); + self.brokers = brokers; + } } /// Wallet content Version 0 @@ -1067,6 +1113,7 @@ pub struct WalletV0 { #[derive(Clone, Debug, Serialize, Deserialize)] pub enum Wallet { V0(WalletV0), + TemporarilyEmpty, } /// Add Wallet Version 0 @@ -1186,16 +1233,28 @@ impl CreateWalletV0 { } } +// #[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize)] +// pub struct WalletCreationSiteEventsV0 { +// store_id: RepoId, +// store_read_cap: ReadCap, +// topic_id: TopicId, +// topic_priv_key: BranchWriteCapSecret, +// events: Vec<(Commit, Vec)>, +// } + +// #[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize)] +// pub struct WalletCreationEventsV0 {} + #[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize)] pub struct CreateWalletResultV0 { #[zeroize(skip)] /// The encrypted form of the Wallet object that was created. /// basically the same as what the file contains. pub wallet: Wallet, - #[serde(skip)] - /// The private key of the Wallet. Used for signing the wallet and other internal purposes. - /// it is contained in the opened wallet. No need to save it anywhere. - pub wallet_privkey: PrivKey, + // #[serde(skip)] + // /// The private key of the Wallet. Used for signing the wallet and other internal purposes. + // /// it is contained in the opened wallet. No need to save it anywhere. + // pub wallet_privkey: PrivKey, #[serde(with = "serde_bytes")] #[zeroize(skip)] /// The binary file that can be saved to disk and given to the user @@ -1216,6 +1275,53 @@ pub struct CreateWalletResultV0 { #[zeroize(skip)] /// is this an in_memory wallet that should not be saved to disk by the LocalBroker? pub in_memory: bool, + + pub session_id: u8, +} + +impl CreateWalletResultV0 { + pub fn personal_identity(&self) -> UserId { + self.user + } +} + +#[derive(Clone, Zeroize, ZeroizeOnDrop, Debug)] +pub struct CreateWalletIntermediaryV0 { + /// The private key of the Wallet. Used for signing the wallet and other internal purposes. + /// it is contained in the opened wallet. No need to save it anywhere. + pub wallet_privkey: PrivKey, + #[zeroize(skip)] + /// a string identifying uniquely the wallet + pub wallet_name: String, + /// newly created Client that uniquely identifies the device where the wallet has been created. + pub client: ClientV0, + + /// User priv key of the "personal identity" of the user + pub user_privkey: PrivKey, + #[zeroize(skip)] + /// is this an in_memory wallet that should not be saved to disk by the LocalBroker? + pub in_memory: bool, + + #[zeroize(skip)] + pub security_img: Vec, + + pub security_txt: String, + + pub pazzle_length: u8, + + pub pin: [u8; 4], + + #[zeroize(skip)] + pub send_bootstrap: bool, + #[zeroize(skip)] + pub send_wallet: bool, + #[zeroize(skip)] + pub result_with_wallet_file: bool, + #[zeroize(skip)] + pub core_bootstrap: BootstrapContentV0, + pub core_registration: Option<[u8; 32]>, + #[zeroize(skip)] + pub additional_bootstrap: Option, } #[derive(Debug, Eq, PartialEq, Clone)] diff --git a/ngd/src/main.rs b/ngd/src/main.rs index b199a52..f48943c 100644 --- a/ngd/src/main.rs +++ b/ngd/src/main.rs @@ -950,6 +950,7 @@ async fn main_inner() -> Result<(), ()> { overlays_config.forward = vec![BrokerServerV0 { server_type, can_verify: false, + can_forward: false, peer_id, }]; }