3P stores of a site

pull/19/head
Niko PLP 7 months ago
parent d01a59a5a6
commit 0ebf17d594
  1. 1
      Cargo.lock
  2. 4
      nextgraph/Cargo.toml
  3. 2
      nextgraph/examples/README.md
  4. 62
      nextgraph/examples/in_memory.rs
  5. 17
      nextgraph/examples/open.md
  6. 81
      nextgraph/examples/open.rs
  7. 124
      nextgraph/examples/persistent.rs
  8. 856
      nextgraph/src/local_broker.rs
  9. 55
      ng-app/src-tauri/src/lib.rs
  10. 1
      ng-broker/src/server_ws.rs
  11. 5
      ng-net/src/types.rs
  12. 7
      ng-repo/src/commit.rs
  13. 4
      ng-repo/src/errors.rs
  14. 76
      ng-repo/src/event.rs
  15. 2
      ng-repo/src/lib.rs
  16. 12
      ng-repo/src/object.rs
  17. 55
      ng-repo/src/repo.rs
  18. 68
      ng-repo/src/store.rs
  19. 42
      ng-repo/src/types.rs
  20. 68
      ng-sdk-js/src/lib.rs
  21. 5
      ng-storage-rocksdb/src/kcv_storage.rs
  22. 3
      ng-verifier/Cargo.toml
  23. 2
      ng-verifier/src/lib.rs
  24. 109
      ng-verifier/src/site.rs
  25. 31
      ng-verifier/src/types.rs
  26. 370
      ng-verifier/src/verifier.rs
  27. 6
      ng-wallet/Cargo.toml
  28. 129
      ng-wallet/src/lib.rs
  29. 154
      ng-wallet/src/types.rs
  30. 1
      ngd/src/main.rs

1
Cargo.lock generated

@ -3449,6 +3449,7 @@ dependencies = [
"lazy_static",
"ng-net",
"ng-repo",
"ng-verifier",
"rand 0.7.3",
"safe-transmute",
"serde",

@ -43,3 +43,7 @@ required-features = []
[[example]]
name = "persistent"
required-features = []
[[example]]
name = "open"
required-features = []

@ -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)

@ -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,

@ -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.

@ -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
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// 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(())
}

@ -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(())
}

@ -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,6 +80,7 @@ impl JsStorageConfig {
}
Err(_) => 0,
};
if qty > 0 {
let new_val = val + qty as u64;
let spls = SessionPeerLastSeq::V0(new_val);
let ser = serde_bare::to_vec(&spls)?;
@ -89,6 +90,7 @@ impl JsStorageConfig {
if r.is_ok() {
return Err(NgError::SerializationError);
}
}
Ok(val)
}),
outbox_write_function: Box::new(
@ -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<SessionConfig, NgError> {
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<PathBuf> {
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<PathBuf> {
// 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,169 +467,270 @@ impl LocalBroker {
}
Ok(())
}
}
static LOCAL_BROKER: OnceCell<Result<Arc<RwLock<LocalBroker>>, NgError>> = OnceCell::new();
pub type ConfigInitFn = dyn Fn() -> LocalBrokerConfig + 'static + Sync + Send;
async fn wallet_was_opened(
&mut self,
mut wallet: SensitiveWallet,
) -> Result<ClientV0, NgError> {
let broker = self;
async fn init_(config: LocalBrokerConfig) -> Result<Arc<RwLock<LocalBroker>>, NgError> {
let wallets = match &config {
LocalBrokerConfig::InMemory => HashMap::new(),
LocalBrokerConfig::BasePath(base_path) => {
// load the wallets and sessions from disk
let mut path = base_path.clone();
path.push("wallets");
let map_ser = read(path);
if map_ser.is_ok() {
let wallets = LocalWalletStorage::v0_from_vec(&map_ser.unwrap())?;
let LocalWalletStorage::V0(wallets) = wallets;
wallets
} else {
HashMap::new()
match broker.opened_wallets.get(&wallet.id()) {
Some(opened_wallet) => {
return Ok(opened_wallet.wallet.client().to_owned().unwrap());
}
None => {} //Err(NgError::WalletAlreadyOpened);
}
LocalBrokerConfig::JsStorage(js_storage_config) => {
// load the wallets from JsStorage
match (js_storage_config.local_read)("ng_wallets".to_string()) {
Err(_) => HashMap::new(),
Ok(wallets_string) => {
let map_ser = base64_url::decode(&wallets_string)
.map_err(|_| NgError::SerializationError)?;
let wallets: LocalWalletStorage = serde_bare::from_slice(&map_ser)?;
let LocalWalletStorage::V0(v0) = wallets;
v0
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 local_broker = LocalBroker {
config,
wallets,
opened_wallets: HashMap::new(),
sessions: HashMap::new(),
opened_sessions: HashMap::new(),
opened_sessions_list: vec![],
let block_storage = if lws.in_memory {
Arc::new(std::sync::RwLock::new(HashMapBlockStorage::new()))
as Arc<std::sync::RwLock<dyn BlockStorage + Send + Sync + 'static>>
} 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<std::sync::RwLock<dyn BlockStorage + Send + Sync + 'static>>
}
#[cfg(target_family = "wasm")]
{
panic!("no RocksDB in WASM");
}
};
let client = wallet.client().as_ref().unwrap().clone();
let opened_wallet = OpenedWallet {
wallet,
block_storage,
};
//log_debug!("{:?}", &local_broker);
let broker = Arc::new(RwLock::new(local_broker));
BROKER.write().await.set_local_broker(Arc::clone(
&(Arc::clone(&broker) as Arc<RwLock<dyn ILocalBroker>>),
));
broker.opened_wallets.insert(wallet_id, opened_wallet);
Ok(client)
}
Ok(broker)
}
async fn session_start(
&mut self,
mut config: SessionConfig,
user_priv_key: Option<PrivKey>,
) -> Result<SessionInfo, NgError> {
let broker = self;
#[doc(hidden)]
pub async fn init_local_broker_with_lazy(config_fn: &Lazy<Box<ConfigInitFn>>) {
LOCAL_BROKER
.get_or_init(async {
let config = (&*config_fn)();
init_(config).await
})
.await;
}
config.valid_verifier_config_for_local_broker_config(&broker.config)?;
/// Initialize the configuration of your local broker
///
/// , by passing in a function (or closure) that returns a `LocalBrokerConfig`.
/// You must call `init_local_broker` at least once before you can start to use the broker.
/// After the first call, all subsequent calls will have no effect.
pub async fn init_local_broker(config_fn: Box<ConfigInitFn>) {
LOCAL_BROKER
.get_or_init(async {
let config = (config_fn)();
init_(config).await
})
.await;
}
let wallet_name: String = config.wallet_name();
let wallet_id: PubKey = (*wallet_name).try_into()?;
let user_id = config.user_id();
/// Retrieves a HashMap of wallets known to the LocalBroker. The name of the Wallet is used as key
pub async fn wallets_get_all() -> Result<HashMap<String, LocalWalletStorageV0>, NgError> {
let broker = match LOCAL_BROKER.get() {
Some(Err(e)) => {
log_err!("LocalBrokerNotInitialized: {}", e);
return Err(NgError::LocalBrokerNotInitialized);
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 => {
log_err!("Not initialized");
return Err(NgError::LocalBrokerNotInitialized);
}
Some(Ok(broker)) => broker.read().await,
None => {}
}
}
None => {}
};
Ok(broker.wallets.clone())
}
/// Creates a new Wallet for the user. Each user should create only one Wallet.
///
/// See [CreateWalletV0] for a list of parameters.
///
/// Wallets are transferable to to other devices (see [wallet_get_file] and [wallet_import])
pub async fn wallet_create_v0(params: CreateWalletV0) -> Result<CreateWalletResultV0, NgError> {
{
// 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,
// 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),
},
};
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?;
Ok(res)
}
let client_storage_master_key = serde_bare::to_vec(
&opened_wallet
.wallet
.client()
.as_ref()
.unwrap()
.sensitive_client_storage
.storage_master_key,
)
.unwrap();
#[doc(hidden)]
/// Only used by JS SDK when the localStorage changes and brings out of sync for the Rust side copy of the wallets
pub async fn wallets_reload() -> Result<(), NgError> {
let mut broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.write().await,
};
match &broker.config {
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) => {
// load the wallets from JsStorage
let wallets_string = (js_config.local_read)("ng_wallets".to_string())?;
let map_ser =
base64_url::decode(&wallets_string).map_err(|_| NgError::SerializationError)?;
let wallets: LocalWalletStorage = serde_bare::from_slice(&map_ser)?;
let LocalWalletStorage::V0(v0) = wallets;
broker.wallets.extend(v0);
// 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,
}
Ok(())
}
#[doc(hidden)]
/// This should not be used by programmers. Only here because the JS SDK needs it.
///
/// It will throw and error if you use it.
pub async fn wallet_add(lws: LocalWalletStorageV0) -> Result<(), NgError> {
let mut broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.write().await,
};
if !lws.in_memory && broker.config.is_in_memory() {
return Err(NgError::CannotSaveWhenInMemoryConfig);
}
if broker.wallets.get(&lws.wallet.name()).is_some() {
return Err(NgError::WalletAlreadyAdded);
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 in_memory = lws.in_memory;
broker.wallets.insert(lws.wallet.name(), lws);
if in_memory {
// if broker.config.is_js() {
// (broker.config.js_config().unwrap().wallets_in_mem_changed)();
// }
} else {
}
};
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
@ -625,6 +754,204 @@ pub async fn wallet_add(lws: LocalWalletStorageV0) -> Result<(), NgError> {
}
_ => panic!("wrong LocalBrokerConfig"),
}
Ok(())
}
}
static LOCAL_BROKER: OnceCell<Result<Arc<RwLock<LocalBroker>>, NgError>> = OnceCell::new();
pub type ConfigInitFn = dyn Fn() -> LocalBrokerConfig + 'static + Sync + Send;
async fn init_(config: LocalBrokerConfig) -> Result<Arc<RwLock<LocalBroker>>, NgError> {
let wallets = match &config {
LocalBrokerConfig::InMemory => HashMap::new(),
LocalBrokerConfig::BasePath(base_path) => {
// load the wallets and sessions from disk
let mut path = base_path.clone();
path.push("wallets");
let map_ser = read(path);
if map_ser.is_ok() {
let wallets = LocalWalletStorage::v0_from_vec(&map_ser.unwrap())?;
let LocalWalletStorage::V0(wallets) = wallets;
wallets
} else {
HashMap::new()
}
}
LocalBrokerConfig::JsStorage(js_storage_config) => {
// load the wallets from JsStorage
match (js_storage_config.local_read)("ng_wallets".to_string()) {
Err(_) => HashMap::new(),
Ok(wallets_string) => {
let map_ser = base64_url::decode(&wallets_string)
.map_err(|_| NgError::SerializationError)?;
let wallets: LocalWalletStorage = serde_bare::from_slice(&map_ser)?;
let LocalWalletStorage::V0(v0) = wallets;
v0
}
}
}
};
let local_broker = LocalBroker {
config,
wallets,
opened_wallets: HashMap::new(),
sessions: HashMap::new(),
opened_sessions: HashMap::new(),
opened_sessions_list: vec![],
};
//log_debug!("{:?}", &local_broker);
let broker = Arc::new(RwLock::new(local_broker));
BROKER.write().await.set_local_broker(Arc::clone(
&(Arc::clone(&broker) as Arc<RwLock<dyn ILocalBroker>>),
));
Ok(broker)
}
#[doc(hidden)]
pub async fn init_local_broker_with_lazy(config_fn: &Lazy<Box<ConfigInitFn>>) {
LOCAL_BROKER
.get_or_init(async {
let config = (&*config_fn)();
init_(config).await
})
.await;
}
/// Initialize the configuration of your local broker
///
/// , by passing in a function (or closure) that returns a `LocalBrokerConfig`.
/// You must call `init_local_broker` at least once before you can start to use the broker.
/// After the first call, all subsequent calls will have no effect.
pub async fn init_local_broker(config_fn: Box<ConfigInitFn>) {
LOCAL_BROKER
.get_or_init(async {
let config = (config_fn)();
init_(config).await
})
.await;
}
/// Retrieves a HashMap of wallets known to the LocalBroker. The name of the Wallet is used as key
pub async fn wallets_get_all() -> Result<HashMap<String, LocalWalletStorageV0>, NgError> {
let broker = match LOCAL_BROKER.get() {
Some(Err(e)) => {
log_err!("LocalBrokerNotInitialized: {}", e);
return Err(NgError::LocalBrokerNotInitialized);
}
None => {
log_err!("Not initialized");
return Err(NgError::LocalBrokerNotInitialized);
}
Some(Ok(broker)) => broker.read().await,
};
Ok(broker.wallets.clone())
}
/// Creates a new Wallet for the user. Each user should create only one Wallet.
///
/// See [CreateWalletV0] for a list of parameters.
///
/// Wallets are transferable to to other devices (see [wallet_get_file] and [wallet_import])
pub async fn wallet_create_v0(params: CreateWalletV0) -> Result<CreateWalletResultV0, NgError> {
// 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 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)
}
#[doc(hidden)]
/// Only used by JS SDK when the localStorage changes and brings out of sync for the Rust side copy of the wallets
pub async fn wallets_reload() -> Result<(), NgError> {
let mut broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.write().await,
};
match &broker.config {
LocalBrokerConfig::JsStorage(js_config) => {
// load the wallets from JsStorage
let wallets_string = (js_config.local_read)("ng_wallets".to_string())?;
let map_ser =
base64_url::decode(&wallets_string).map_err(|_| NgError::SerializationError)?;
let wallets: LocalWalletStorage = serde_bare::from_slice(&map_ser)?;
let LocalWalletStorage::V0(v0) = wallets;
broker.wallets.extend(v0);
}
_ => {}
}
Ok(())
}
#[doc(hidden)]
/// This should not be used by programmers. Only here because the JS SDK needs it.
///
/// It will throw and error if you use it.
pub async fn wallet_add(lws: LocalWalletStorageV0) -> Result<(), NgError> {
let mut broker = match LOCAL_BROKER.get() {
None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized),
Some(Ok(broker)) => broker.write().await,
};
if !lws.in_memory && broker.config.is_in_memory() {
return Err(NgError::CannotSaveWhenInMemoryConfig);
}
if broker.wallets.get(&lws.wallet.name()).is_some() {
return Err(NgError::WalletAlreadyAdded);
}
let in_memory = lws.in_memory;
broker.wallets.insert(lws.wallet.name(), lws);
if in_memory {
// if broker.config.is_js() {
// (broker.config.js_config().unwrap().wallets_in_mem_changed)();
// }
} else {
LocalBroker::wallet_save(&mut broker)?;
}
Ok(())
}
@ -653,6 +980,19 @@ pub async fn wallet_read_file(file: Vec<u8>) -> Result<Wallet, NgError> {
}
}
/// Retrieves the the Wallet by its name, to be used for opening it
pub async fn wallet_get(wallet_name: &String) -> Result<Wallet, NgError> {
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<Vec<u8>, NgError> {
let broker = match LOCAL_BROKER.get() {
@ -734,63 +1074,13 @@ pub async fn wallet_was_opened(mut wallet: SensitiveWallet) -> Result<ClientV0,
Some(Ok(broker)) => 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<std::sync::RwLock<dyn BlockStorage + Send + Sync + 'static>>
} 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<std::sync::RwLock<dyn BlockStorage + Send + Sync + 'static>>
}
#[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<SessionInfo, NgError> {
let mut broker = match LOCAL_BROKER.get() {
@ -798,167 +1088,7 @@ pub async fn session_start(mut config: SessionConfig) -> Result<SessionInfo, NgE
Some(Ok(broker)) => 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;

@ -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<u64, NgError> + 'static + Sync + Send;
// let test: Box<LastSeqFn> = 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<u64, NgError> {
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<u8>, app: tauri::AppHandle) -> Result<Wallet, String> {
nextgraph::local_broker::wallet_read_file(file)

@ -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,
})
}

@ -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,

@ -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<CommitHeader> {
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(())
}
}

@ -44,6 +44,10 @@ pub enum NgError {
WalletError(String),
BrokerError,
SessionNotFound,
SessionAlreadyStarted,
RepoNotFound,
BranchNotFound,
StoreNotFound,
}
impl Error for NgError {}

@ -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<BlockId>,
topic_id: TopicId,
topic_priv_key: &BranchWriteCapSecret,
store: &'a Store,
repo: &Repo,
) -> Result<Event, NgError> {
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<BlockId>,
topic_id: TopicId,
topic_priv_key: &BranchWriteCapSecret,
store: &'a Store,
repo: &Repo,
) -> Result<EventV0, NgError> {
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,

@ -22,8 +22,6 @@ pub mod branch;
pub mod repo;
pub mod site;
pub mod store;
pub mod event;

@ -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();

@ -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<ReadCap>,
pub write_cap: Option<RepoWriteCapSecret>,
pub signer: Option<SignerCap>,
pub members: HashMap<Digest, UserInfo>,
pub branches: HashMap<BranchId, BranchInfo>,
pub store: Arc<Store>,
}
@ -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()
// }

@ -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<RwLock<dyn BlockStorage + Send + Sync>>,
//repos: HashMap<RepoId, Repo>,
}
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<ReadCap>) {
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<Self>,
creator: &UserId,
creator_priv_key: &PrivKey,
repo_write_cap_secret: SymKey,
) -> Result<(Repo, Vec<(Commit, Vec<Digest>)>), 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<RwLock<dyn BlockStorage + Send + Sync>>,
) -> 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<RwLock<dyn BlockStorage + Send + Sync>>,
))
@ -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<RwLock<dyn BlockStorage + Send + Sync>>,
))

@ -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<PubKey>,
}
/// 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<u8>,
}
@ -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();

@ -162,74 +162,6 @@ pub async fn get_wallets() -> Result<JsValue, JsValue> {
Ok(JsValue::UNDEFINED)
}
fn take_some_peer_last_seq_numbers(peer_id: PubKey, qty: u16) -> Result<u64, NgError> {
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<String>) -> 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<JsValue, String> {

@ -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,

@ -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" }

@ -4,5 +4,7 @@ pub mod user_storage;
pub mod verifier;
pub mod site;
#[cfg(not(target_family = "wasm"))]
pub mod rocksdb_user_storage;

@ -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<PubKey>,
}
impl SiteV0 {
pub fn get_individual_user_priv_key(&self) -> Option<PrivKey> {
@ -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<Self, NgError> {
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<Self, NgError> {
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, NgError> {
Self::create_individual_(user_priv_key, verifier, SiteName::Personal)
}
pub fn create_org(name: String) -> Result<Self, NgError> {

@ -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<Vec<u8>, NgError> {
Ok(serde_bare::to_vec(self)?)
}
pub fn deser(ser: &[u8]) -> Result<Self, NgError> {
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<u64, NgError>;
//type LastSeqFn = fn(peer_id: PubKey, qty: u16) -> Result<u64, NgError>;
pub type LastSeqFn = dyn Fn(PubKey, u16) -> Result<u64, NgError> + 'static + Sync + Send;
// peer_id: PubKey, seq_num:u64, event_ser: vec<u8>,
@ -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<ObjectRef>,
pub private_store_id: Option<RepoId>,
}
pub type CancelFn = Box<dyn FnOnce()>;

@ -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<OverlayId, Arc<Store>>,
repos: HashMap<RepoId, Repo>,
/// only used for InMemory type, to store the outbox
in_memory_outbox: Vec<Event>,
}
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<Store> {
pub fn get_store_mut(&mut self, store_repo: &StoreRepo) -> Arc<Store> {
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<Repo, NgError> {
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::<ng_repo::store::Store>::strong_count(&repo.store)
// );
drop(repo.store);
//log_info!("{}", Arc::<ng_repo::store::Store>::strong_count(&store));
let mut mut_store = Arc::<ng_repo::store::Store>::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<Arc<Store>, 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<Store>) {
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<BlockId>,
//topic_id: TopicId,
//topic_priv_key: &BranchWriteCapSecret,
store: &Store, // store could be omitted and a store repo ID would be given instead.
) -> Result<Event, NgError> {
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<BlockId>,
//topic_id: TopicId,
//topic_priv_key: &BranchWriteCapSecret,
store: &Store, // store could be omitted and a store repo ID would be given instead.
) -> Result<Event, 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)
// 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;
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 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<BlockId>,
//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;
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<u64, NgError> {
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<Digest>)>,
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<Digest>)>,
store: &Store,
) -> Result<Vec<Event>, 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(())
}
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) => (
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, config.user_master_key).unwrap()),
Some(
Box::new(RocksDbUserStorage::open(path, config.user_master_key)?)
as Box<dyn UserStorage>,
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<dyn UserStorage>),
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<Event>), 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);
}
}

@ -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"] }
@ -36,3 +37,8 @@ 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"
[dev-dependencies]
ng-repo = { path = "../ng-repo", version = "0.1.0", features = ["testing"] }
ng-verifier = { path = "../ng-verifier", version = "0.1.0", features = ["testing"] }

@ -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<u8> {
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<u8> {
/// 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<CreateWalletResultV0, NgWalletError> {
let creating_pazzle = Instant::now();
pub fn create_wallet_first_step_v0(
params: CreateWalletV0,
) -> Result<CreateWalletIntermediaryV0, NgWalletError> {
// 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<CreateWalletResult
let (wallet_privkey, wallet_id) = generate_keypair();
// TODO: should be derived from OwnershipProof
let user_priv_key = PrivKey::random_ed();
let user_privkey = PrivKey::random_ed();
let user = user_privkey.to_pub();
// TODO: private_store_read_cap
let site = SiteV0::create_personal(user_priv_key.clone(), BlockRef::nil())
.map_err(|e| NgWalletError::InternalError)?;
let client = ClientV0::new_with_auto_open(user);
let intermediary = CreateWalletIntermediaryV0 {
wallet_privkey,
wallet_name: base64_url::encode(&wallet_id.slice()),
client,
user_privkey,
in_memory: !params.local_save,
security_img: cursor.into_inner(),
security_txt: new_string,
pazzle_length: params.pazzle_length,
pin: params.pin,
send_bootstrap: params.send_bootstrap,
send_wallet: params.send_wallet,
result_with_wallet_file: params.result_with_wallet_file,
core_bootstrap: params.core_bootstrap.clone(),
core_registration: params.core_registration,
additional_bootstrap: params.additional_bootstrap.clone(),
};
Ok(intermediary)
}
pub fn create_wallet_second_step_v0(
mut params: CreateWalletIntermediaryV0,
verifier: &mut Verifier,
) -> Result<
(
CreateWalletResultV0,
SiteV0,
HashMap<String, Vec<BrokerInfoV0>>,
),
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
})?;
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<CreateWalletResult
//.ok_or(NgWalletError::InternalError)?
//.clone(),
let user = user_priv_key.to_pub();
// Creating a new client
let client = ClientV0::new_with_auto_open(user);
let create_op = WalletOpCreateV0 {
wallet_privkey: wallet_privkey.clone(),
wallet_privkey: params.wallet_privkey.clone(),
// pazzle: pazzle.clone(),
// mnemonic,
// pin: params.pin,
personal_site: site,
personal_site: site.clone(),
save_to_ng_one: if params.send_wallet {
SaveToNGOne::Wallet
} else if params.send_bootstrap {
@ -599,15 +645,20 @@ pub fn create_wallet_v0(mut params: CreateWalletV0) -> Result<CreateWalletResult
// #[zeroize(skip)]
// pub additional_bootstrap: Option<BootstrapContentV0>,
wallet_log.add(WalletOperation::AddSiteCoreV0((
user,
params
let mut brokers: HashMap<String, Vec<BrokerInfoV0>> = HashMap::new();
let core_pubkey = params
.core_bootstrap
.get_first_peer_id()
.ok_or(NgWalletError::InvalidBootstrap)?,
.ok_or(NgWalletError::InvalidBootstrap)?;
wallet_log.add(WalletOperation::AddSiteCoreV0((
user,
core_pubkey,
params.core_registration,
)));
site.cores.push((core_pubkey, params.core_registration));
if let Some(additional) = &params.additional_bootstrap {
params.core_bootstrap.merge(additional);
}
@ -615,6 +666,17 @@ pub fn create_wallet_v0(mut params: CreateWalletV0) -> Result<CreateWalletResult
for server in &params.core_bootstrap.servers {
wallet_log.add(WalletOperation::AddBrokerServerV0(server.clone()));
wallet_log.add(WalletOperation::AddSiteBootstrapV0((user, server.peer_id)));
site.bootstraps.push(server.peer_id);
let broker = BrokerInfoV0::ServerV0(server.clone());
let key = broker.get_id().to_string();
let mut list = brokers.get_mut(&key);
if list.is_none() {
let new_list = vec![];
brokers.insert(key.clone(), new_list);
list = brokers.get_mut(&key);
}
list.unwrap().push(broker);
}
let mut master_key = [0u8; 32];
@ -664,8 +726,8 @@ pub fn create_wallet_v0(mut params: CreateWalletV0) -> Result<CreateWalletResult
master_key.zeroize();
let wallet_content = WalletContentV0 {
security_img: cursor.into_inner(),
security_txt: new_string,
security_img: params.security_img.clone(),
security_txt: params.security_txt.clone(),
pazzle_length: params.pazzle_length,
salt_pazzle,
salt_mnemonic,
@ -680,7 +742,7 @@ pub fn create_wallet_v0(mut params: CreateWalletV0) -> Result<CreateWalletResult
let ser_wallet = serde_bare::to_vec(&wallet_content).unwrap();
let sig = sign(&wallet_privkey, &wallet_id, &ser_wallet).unwrap();
let sig = sign(&params.wallet_privkey, &wallet_id, &ser_wallet).unwrap();
let wallet_v0 = WalletV0 {
/// ID
@ -705,22 +767,27 @@ pub fn create_wallet_v0(mut params: CreateWalletV0) -> Result<CreateWalletResult
"creating of wallet took: {} ms",
creating_pazzle.elapsed().as_millis()
);
let wallet = Wallet::V0(wallet_v0);
let wallet_file = match params.result_with_wallet_file {
false => vec![],
true => to_vec(&NgFile::V0(NgFileV0::Wallet(wallet.clone()))).unwrap(),
};
Ok(CreateWalletResultV0 {
Ok((
CreateWalletResultV0 {
wallet: wallet,
wallet_privkey,
wallet_file,
pazzle,
mnemonic: mnemonic.clone(),
wallet_name: base64_url::encode(&wallet_id.slice()),
client,
wallet_name: params.wallet_name.clone(),
client: params.client.clone(),
user,
in_memory: !params.local_save,
})
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",

@ -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<Vec<u8>, NgError> {
Ok(serde_bare::to_vec(self)?)
}
pub fn deser(ser: &[u8]) -> Result<Self, NgError> {
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<u8>,
}
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<ReadCap>, Option<RepoId>)> {
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<String, Vec<BrokerInfoV0>>,
) {
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<String, Vec<BrokerInfoV0>>,
) {
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<Digest>)>,
// }
// #[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<u8>,
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<BootstrapContentV0>,
}
#[derive(Debug, Eq, PartialEq, Clone)]

@ -950,6 +950,7 @@ async fn main_inner() -> Result<(), ()> {
overlays_config.forward = vec![BrokerServerV0 {
server_type,
can_verify: false,
can_forward: false,
peer_id,
}];
}

Loading…
Cancel
Save