diff --git a/Cargo.lock b/Cargo.lock index 1ab0110..e49435b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -520,6 +520,12 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64-url" version = "2.0.0" @@ -1421,6 +1427,12 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + [[package]] name = "debug_print" version = "1.0.0" @@ -1791,6 +1803,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + [[package]] name = "flume" version = "0.10.14" @@ -2620,6 +2638,12 @@ dependencies = [ "tiff", ] +[[package]] +name = "imagesize" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" + [[package]] name = "indexmap" version = "1.9.3" @@ -2830,6 +2854,16 @@ dependencies = [ "selectors", ] +[[package]] +name = "kurbo" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c" +dependencies = [ + "arrayvec", + "smallvec", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -3271,10 +3305,12 @@ dependencies = [ "ng-verifier", "ng-wallet", "once_cell", + "pdf-writer", "qrcode", "serde_bare", "serde_bytes", "serde_json", + "svg2pdf", "web-time", "whoami", "zeroize", @@ -4046,6 +4082,18 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +[[package]] +name = "pdf-writer" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af6a7882fda7808481d43c51cadfc3ec934c6af72612a1fe6985ce329a2f0469" +dependencies = [ + "bitflags 2.5.0", + "itoa 1.0.6", + "memchr", + "ryu", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -4203,6 +4251,12 @@ dependencies = [ "siphasher 0.3.10", ] +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.0" @@ -4711,6 +4765,12 @@ dependencies = [ "winreg 0.10.1", ] +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + [[package]] name = "rust-embed" version = "6.7.0" @@ -5160,6 +5220,15 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + [[package]] name = "siphasher" version = "0.3.10" @@ -5292,6 +5361,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + [[package]] name = "string_cache" version = "0.8.7" @@ -5330,6 +5408,29 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "svg2pdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31565956eb1dc398c0d9776ee1d1bac4e34759af63dcbe0520df32313a5b53b" +dependencies = [ + "log", + "miniz_oxide", + "once_cell", + "pdf-writer", + "usvg", +] + +[[package]] +name = "svgtypes" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fae3064df9b89391c9a76a0425a69d124aee9c5c28455204709e72c39868a43c" +dependencies = [ + "kurbo", + "siphasher 1.0.1", +] + [[package]] name = "swift-rs" version = "1.0.6" @@ -5854,6 +5955,17 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -6194,6 +6306,28 @@ dependencies = [ "serde", ] +[[package]] +name = "usvg" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84ea542ae85c715f07b082438a4231c3760539d902e11d093847a0b22963032" +dependencies = [ + "base64 0.22.1", + "data-url", + "flate2", + "imagesize", + "kurbo", + "log", + "pico-args", + "roxmltree", + "simplecss", + "siphasher 1.0.1", + "strict-num", + "svgtypes", + "tiny-skia-path", + "xmlwriter", +] + [[package]] name = "utf-8" version = "0.7.6" @@ -6568,9 +6702,9 @@ dependencies = [ [[package]] name = "weezl" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "whoami" @@ -7042,6 +7176,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + [[package]] name = "xsalsa20poly1305" version = "0.9.1" diff --git a/nextgraph/Cargo.toml b/nextgraph/Cargo.toml index ae4d0d8..3fe4aec 100644 --- a/nextgraph/Cargo.toml +++ b/nextgraph/Cargo.toml @@ -30,6 +30,8 @@ lazy_static = "1.4.0" web-time = "0.2.0" whoami = "1.5.1" qrcode = { version = "0.14.1", default-features = false, features = ["svg"] } +svg2pdf = { version = "0.11.0", default-features = false } +pdf-writer = "0.10.0" ng-repo = { path = "../ng-repo", version = "0.1.0-preview.1" } ng-net = { path = "../ng-net", version = "0.1.0-preview.1" } ng-wallet = { path = "../ng-wallet", version = "0.1.0-preview.5" } diff --git a/nextgraph/src/local_broker.rs b/nextgraph/src/local_broker.rs index 81ce09e..c5754fb 100644 --- a/nextgraph/src/local_broker.rs +++ b/nextgraph/src/local_broker.rs @@ -16,12 +16,14 @@ use async_once_cell::OnceCell; use async_std::sync::{Arc, Mutex, RwLock}; use futures::channel::mpsc; use futures::SinkExt; +use lazy_static::lazy_static; use once_cell::sync::Lazy; +use pdf_writer::{Content, Finish, Name, Pdf, Rect, Ref, Str}; +use qrcode::{render::svg, QrCode}; use serde_bare::to_vec; use serde_json::json; +use svg2pdf::{ConversionOptions, PageOptions}; use zeroize::Zeroize; -use qrcode::{render::svg, QrCode}; -use lazy_static::lazy_static; use ng_repo::block_storage::BlockStorage; use ng_repo::block_storage::HashMapBlockStorage; @@ -29,31 +31,31 @@ use ng_repo::errors::{NgError, ProtocolError}; use ng_repo::log::*; use ng_repo::os_info::get_os_info; use ng_repo::types::*; -use ng_repo::utils::{derive_key, generate_keypair, encrypt_in_place}; +use ng_repo::utils::{derive_key, encrypt_in_place, generate_keypair}; -use ng_net::{actor::*,actors::admin::*}; +use ng_net::app_protocol::*; use ng_net::broker::*; -use ng_net::connection::{ClientConfig, IConnect, NoiseFSM, StartConfig, AppConfig}; +use ng_net::connection::{AppConfig, ClientConfig, IConnect, NoiseFSM, StartConfig}; use ng_net::types::*; -use ng_net::app_protocol::*; use ng_net::utils::{Receiver, Sender}; +use ng_net::{actor::*, actors::admin::*}; use ng_verifier::types::*; use ng_verifier::verifier::Verifier; -use ng_wallet::emojis::encode_pazzle; use ng_wallet::bip39::encode_mnemonic; +use ng_wallet::emojis::encode_pazzle; use ng_wallet::{create_wallet_first_step_v0, create_wallet_second_step_v0, types::*}; #[cfg(not(target_family = "wasm"))] use ng_client_ws::remote_ws::ConnectionWebSocket; #[cfg(target_family = "wasm")] use ng_client_ws::remote_ws_wasm::ConnectionWebSocket; -#[cfg(not(any(target_family = "wasm",docsrs)))] +#[cfg(not(any(target_family = "wasm", docsrs)))] use ng_storage_rocksdb::block_storage::RocksDbBlockStorage; #[doc(hidden)] -#[derive(Debug,Clone)] +#[derive(Debug, Clone)] pub struct HeadlessConfig { // parse_ip_and_port_for(string, "verifier_server") pub server_addr: BindAddress, @@ -272,24 +274,45 @@ struct RemoteSession { impl RemoteSession { pub(crate) async fn send_request(&self, req: AppRequest) -> Result { - match BROKER.read().await.request::(&Some(self.user_id), &Some(self.remote_peer_id), req).await { + match BROKER + .read() + .await + .request::( + &Some(self.user_id), + &Some(self.remote_peer_id), + req, + ) + .await + { Err(e) => Err(e), Ok(SoS::Stream(_)) => Err(NgError::InvalidResponse), - Ok(SoS::Single(res)) => Ok(res), + Ok(SoS::Single(res)) => Ok(res), } } - - pub(crate) async fn send_request_stream(&self, req: AppRequest) -> Result<(Receiver, CancelFn), NgError> { - match BROKER.read().await.request::(&Some(self.user_id), &Some(self.remote_peer_id), req).await { + + pub(crate) async fn send_request_stream( + &self, + req: AppRequest, + ) -> Result<(Receiver, CancelFn), NgError> { + match BROKER + .read() + .await + .request::( + &Some(self.user_id), + &Some(self.remote_peer_id), + req, + ) + .await + { Err(e) => Err(e), Ok(SoS::Single(_)) => Err(NgError::InvalidResponse), Ok(SoS::Stream(stream)) => { let fnonce = Box::new(move || { // stream.close(); - //TODO: implement CancelStream in AppRequest + //TODO: implement CancelStream in AppRequest }); Ok((stream, fnonce)) - }, + } } } } @@ -299,8 +322,7 @@ struct HeadlessSession { user_id: UserId, } -impl HeadlessSession { -} +impl HeadlessSession {} #[derive(Debug)] struct Session { @@ -340,7 +362,7 @@ impl SessionConfig { Self::HeadlessV0(_) => true, } } - pub fn set_verifier_type(&mut self, vt:VerifierType) { + pub fn set_verifier_type(&mut self, vt: VerifierType) { match self { Self::V0(v0) => v0.verifier_type = vt, Self::WithCredentialsV0(creds) => creds.verifier_type = vt, @@ -401,9 +423,7 @@ impl SessionConfig { } #[doc(hidden)] - pub fn new_headless( - user_id: UserId, - ) -> Self { + pub fn new_headless(user_id: UserId) -> Self { SessionConfig::HeadlessV0(user_id) } @@ -451,8 +471,8 @@ impl SessionConfig { if match self { Self::HeadlessV0(_) => { panic!("don't call session_start on a Headless LocalBroker") - }, - _ => match local_broker_config { + } + _ => match local_broker_config { LocalBrokerConfig::InMemory => { self.set_verifier_type(VerifierType::Memory); true @@ -471,7 +491,6 @@ impl SessionConfig { panic!("don't call session_start on a Headless LocalBroker") } }, - } { Ok(()) } else { @@ -606,54 +625,91 @@ impl LocalBroker { } async fn connect_remote_broker(&mut self) -> Result<(), NgError> { - self.err_if_not_headless()?; - if self.headless_connected_to_remote_broker {return Ok(())} + if self.headless_connected_to_remote_broker { + return Ok(()); + } let info = get_client_info(ClientType::NodeService); let config = self.config.headless_config(); - BROKER.write().await.connect( - Arc::new(Box::new(ConnectionWebSocket {})), - config.client_peer_key.as_ref().unwrap().clone(), - config.client_peer_key.as_ref().unwrap().to_pub(), - config.server_peer_id, - StartConfig::App(AppConfig{user_priv:None, info, addr:config.server_addr}) - ).await?; - + BROKER + .write() + .await + .connect( + Arc::new(Box::new(ConnectionWebSocket {})), + config.client_peer_key.as_ref().unwrap().clone(), + config.client_peer_key.as_ref().unwrap().to_pub(), + config.server_peer_id, + StartConfig::App(AppConfig { + user_priv: None, + info, + addr: config.server_addr, + }), + ) + .await?; + self.headless_connected_to_remote_broker = true; Ok(()) } - pub(crate) async fn send_request_headless + std::fmt::Debug + Sync + Send + 'static, - B: TryFrom + std::fmt::Debug + Sync + Send + 'static,>(&self, req: A) -> Result { + pub(crate) async fn send_request_headless< + A: Into + std::fmt::Debug + Sync + Send + 'static, + B: TryFrom + std::fmt::Debug + Sync + Send + 'static, + >( + &self, + req: A, + ) -> Result { self.err_if_not_headless()?; - match BROKER.read().await.request::(&None, &Some(self.config.headless_config().server_peer_id), req).await { + match BROKER + .read() + .await + .request::( + &None, + &Some(self.config.headless_config().server_peer_id), + req, + ) + .await + { Err(e) => Err(e), Ok(SoS::Stream(_)) => Err(NgError::InvalidResponse), - Ok(SoS::Single(res)) => Ok(res), + Ok(SoS::Single(res)) => Ok(res), } } - + #[allow(dead_code)] - pub(crate) async fn send_request_stream_headless + std::fmt::Debug + Sync + Send + 'static, - B: TryFrom + std::fmt::Debug + Sync + Send + 'static,>(&self, req: A) -> Result<(Receiver, CancelFn), NgError> { + pub(crate) async fn send_request_stream_headless< + A: Into + std::fmt::Debug + Sync + Send + 'static, + B: TryFrom + std::fmt::Debug + Sync + Send + 'static, + >( + &self, + req: A, + ) -> Result<(Receiver, CancelFn), NgError> { self.err_if_not_headless()?; - match BROKER.read().await.request::(&None, &Some(self.config.headless_config().server_peer_id), req).await { + match BROKER + .read() + .await + .request::( + &None, + &Some(self.config.headless_config().server_peer_id), + req, + ) + .await + { Err(e) => Err(e), Ok(SoS::Single(_)) => Err(NgError::InvalidResponse), Ok(SoS::Stream(stream)) => { let fnonce = Box::new(move || { // stream.close(); - //TODO: implement CancelStream in AppRequest + //TODO: implement CancelStream in AppRequest }); Ok((stream, fnonce)) - }, + } } } @@ -666,8 +722,8 @@ impl LocalBroker { fn err_if_not_headless(&self) -> Result<(), NgError> { match self.config { - LocalBrokerConfig::Headless(_) => Ok(()), - _ => Err(NgError::LocalBrokerIsHeadless), + LocalBrokerConfig::Headless(_) => Ok(()), + _ => Err(NgError::LocalBrokerIsHeadless), } } @@ -700,12 +756,13 @@ impl LocalBroker { #[allow(dead_code)] fn to_external_session_id(session_id: u64, is_remote: bool) -> u64 { let mut ext = (session_id) << 1; - if !is_remote {ext += 1;} + if !is_remote { + ext += 1; + } ext } - fn user_to_local_session_id_for_mut(&self, user_id: &UserId) -> Result { - + fn user_to_local_session_id_for_mut(&self, user_id: &UserId) -> Result { let session_id = self .opened_sessions .get(user_id) @@ -713,17 +770,18 @@ impl LocalBroker { self.get_local_session_id_for_mut(*session_id) } - fn get_local_session_id_for_mut(&self, session_id: u64) -> Result { - let _ = Self::is_local_session(session_id).then_some(true).ok_or(NgError::SessionNotFound)?; - let session_id = Self::to_real_session_id(session_id) as usize ; + fn get_local_session_id_for_mut(&self, session_id: u64) -> Result { + let _ = Self::is_local_session(session_id) + .then_some(true) + .ok_or(NgError::SessionNotFound)?; + let session_id = Self::to_real_session_id(session_id) as usize; if session_id >= self.opened_sessions_list.len() { return Err(NgError::InvalidArgument); } - Ok(session_id ) + Ok(session_id) } - fn get_real_session_id_for_mut(&self, session_id: u64) -> Result<(usize,bool),NgError> { - + fn get_real_session_id_for_mut(&self, session_id: u64) -> Result<(usize, bool), NgError> { let is_remote = Self::is_remote_session(session_id); let session_id = Self::to_real_session_id(session_id) as usize; if is_remote { @@ -739,7 +797,9 @@ impl LocalBroker { } fn get_session(&self, session_id: u64) -> Result<&Session, NgError> { - let _ = Self::is_local_session(session_id).then_some(true).ok_or(NgError::SessionNotFound)?; + let _ = Self::is_local_session(session_id) + .then_some(true) + .ok_or(NgError::SessionNotFound)?; let session_id = Self::to_real_session_id(session_id); if session_id as usize >= self.opened_sessions_list.len() { return Err(NgError::InvalidArgument); @@ -751,32 +811,35 @@ impl LocalBroker { #[allow(dead_code)] fn get_headless_session(&self, session_id: u64) -> Result<&HeadlessSession, NgError> { - self.err_if_not_headless()?; - self - .headless_sessions + self.headless_sessions .get(&session_id) .ok_or(NgError::SessionNotFound) - } #[allow(dead_code)] fn get_headless_session_by_user(&self, user_id: &UserId) -> Result<&HeadlessSession, NgError> { - self.err_if_not_headless()?; - let session_id = self.opened_sessions.get(user_id).ok_or(NgError::SessionNotFound)?; + let session_id = self + .opened_sessions + .get(user_id) + .ok_or(NgError::SessionNotFound)?; self.get_headless_session(*session_id) - } - fn remove_headless_session(&mut self, user_id: &UserId) -> Result<(u64,HeadlessSession), NgError> { - + fn remove_headless_session( + &mut self, + user_id: &UserId, + ) -> Result<(u64, HeadlessSession), NgError> { self.err_if_not_headless()?; - let session_id = self.opened_sessions.remove(user_id).ok_or(NgError::SessionNotFound)?; + let session_id = self + .opened_sessions + .remove(user_id) + .ok_or(NgError::SessionNotFound)?; let session = self .headless_sessions @@ -787,7 +850,9 @@ impl LocalBroker { #[allow(dead_code)] fn get_remote_session(&self, session_id: u64) -> Result<&RemoteSession, NgError> { - let _ = Self::is_remote_session(session_id).then_some(true).ok_or(NgError::SessionNotFound)?; + let _ = Self::is_remote_session(session_id) + .then_some(true) + .ok_or(NgError::SessionNotFound)?; let session_id = Self::to_real_session_id(session_id); if session_id as usize >= self.remote_sessions_list.len() { return Err(NgError::InvalidArgument); @@ -914,7 +979,7 @@ impl LocalBroker { Arc::new(std::sync::RwLock::new(HashMapBlockStorage::new())) as Arc> } else { - #[cfg(all(not(target_family = "wasm"),not(docsrs)))] + #[cfg(all(not(target_family = "wasm"), not(docsrs)))] { let key_material = wallet .client() @@ -935,7 +1000,7 @@ impl LocalBroker { )?)) as Arc> } - #[cfg(any(target_family = "wasm",docsrs))] + #[cfg(any(target_family = "wasm", docsrs))] { Arc::new(std::sync::RwLock::new(HashMapBlockStorage::new())) as Arc> @@ -972,7 +1037,6 @@ impl LocalBroker { } fn add_headless_session(&mut self, session: HeadlessSession) -> Result { - let user_id = session.user_id; let mut first_available: u64 = 0; @@ -1494,14 +1558,121 @@ pub async fn wallet_add(lws: LocalWalletStorageV0) -> Result<(), NgError> { } Ok(()) } + +pub fn wallet_to_wallet_recovery( + wallet: &Wallet, + pazzle: Vec, + mnemonic: [u16; 12], + pin: [u8; 4], +) -> NgQRCodeWalletRecoveryV0 { + match wallet { + Wallet::V0(v0) => { + let mut content = v0.content.clone(); + content.security_img = vec![]; + NgQRCodeWalletRecoveryV0 { + wallet: content, + pazzle, + mnemonic, + pin, + } + } + _ => unimplemented!(), + } +} + +/// Generates the Recovery PDF containing the Wallet, PIN, Pazzle and Mnemonic. +pub async fn wallet_recovery_pdf( + content: NgQRCodeWalletRecoveryV0, + size: u32, +) -> Result, NgError> { + let ser = serde_bare::to_vec(&content)?; + if ser.len() > 2_953 { + return Err(NgError::InvalidPayload); + } + let recovery_str = base64_url::encode(&ser); + let wallet_svg = match QrCode::with_error_correction_level(&ser, qrcode::EcLevel::M) { + Ok(qr) => { + let svg = qr + .render() + .max_dimensions(size, size) + .dark_color(svg::Color("#000000")) + .light_color(svg::Color("#ffffff")) + .build(); + svg + } + Err(_e) => return Err(NgError::BrokerError), + }; + + let options = svg2pdf::usvg::Options::default(); + let tree = svg2pdf::usvg::Tree::from_str(&wallet_svg, &options) + .map_err(|e| NgError::WalletError(e.to_string()))?; + + // PDF uses A4 format (21cm x 29.7cm) + // TODO: instead of to_pdf in the next line, do to_chunk, and then add the text below the SVG. + // the SVG should take all the width of the A4 (so that only 29.7-21 = 8cm remains below the SVG, for all the following) + // the text is : + // - one line with : "PIN = 1234 pazzle = cat_slug:emoji_slug cat_slug:emoji_slug ...[x9]" + // - one line with the 9 emoji SVGs (with size so they fit in one line, width of the A4) + // - one line with : "mnemonic = [12 words of mnemonic]" + // - one line with recovery_str (it is quite long. choose a font size that make it fit here so the whole document is only one page) + + // you can use the methods of pdf_writer library. + + let (mut chunk, reference) = svg2pdf::to_chunk(&tree, ConversionOptions::default()); + // probably then: add the text with chunk.stream() or chunk.indirect() + + //let pdf_buf = svg2pdf::to_pdf(&tree, ConversionOptions::default(), PageOptions::default()); + + // Define some indirect reference ids we'll use. + let catalog_id = Ref::new(1000); + let page_tree_id = Ref::new(1001); + let page_id = Ref::new(1002); + let font_id = Ref::new(1003); + let content_id = Ref::new(1004); + let font_name = Name(b"F1"); + + let mut content = Content::new(); + content.begin_text(); + content.set_font(font_name, 14.0); + content.next_line(108.0, 734.0); + content.show(Str(b"Hello World from Rust!")); + content.end_text(); + + // Write a document catalog and a page tree with one A4 page that uses no resources. + let mut pdf = Pdf::new(); + pdf.stream(content_id, &content.finish()); + pdf.catalog(catalog_id).pages(page_tree_id); + pdf.pages(page_tree_id).kids([page_id]).count(1); + { + let mut page = pdf.page(page_id); + let mut page_resources = page + .parent(page_tree_id) + .media_box(Rect::new(0.0, 0.0, 595.0, 842.0)) + .resources(); + page_resources.fonts().pair(font_name, font_id); + page_resources.finish(); + + page.contents(content_id); + page.finish(); + } + pdf.type1_font(font_id).base_font(Name(b"Courier")); + pdf.extend(&chunk); + let pdf_buf = pdf.finish(); + + Ok(pdf_buf) +} + #[cfg(debug_assertions)] lazy_static! { - static ref NEXTGRAPH_EU: BrokerServerV0 = BrokerServerV0 { server_type: BrokerServerTypeV0::Localhost(14400), can_verify: false, can_forward: false, - peer_id: ng_repo::utils::decode_key({use crate::local_broker_dev_env::PEER_ID; PEER_ID}).unwrap(), + peer_id: ng_repo::utils::decode_key({ + use crate::local_broker_dev_env::PEER_ID; + PEER_ID + }) + .unwrap(), }; } @@ -1511,9 +1682,9 @@ lazy_static! { server_type: BrokerServerTypeV0::Domain("nextgraph.eu".to_string()), can_verify: false, can_forward: false, - peer_id: ng_repo::utils::decode_key("FtdzuDYGewfXWdoPuXIPb0wnd0SAg1WoA2B14S7jW3MA").unwrap(), + peer_id: ng_repo::utils::decode_key("FtdzuDYGewfXWdoPuXIPb0wnd0SAg1WoA2B14S7jW3MA") + .unwrap(), }; - } /// Obtains a Wallet object from a QRCode or a TextCode. @@ -1522,19 +1693,25 @@ lazy_static! { /// with the help of the function [wallet_open_with_pazzle_words] /// followed by [wallet_import] pub async fn wallet_import_from_code(code: String) -> Result { - let qr = NgQRCode::from_code(code)?; match qr { - NgQRCode::V0(NgQRCodeV0{broker, rendezvous, secret_key, is_rendezvous}) => { + NgQRCode::WalletTransferV0(NgQRCodeWalletTransferV0 { + broker, + rendezvous, + secret_key, + is_rendezvous, + }) => { let wallet: ExportedWallet = do_ext_call( &broker, ExtWalletGetExportV0 { - id:rendezvous, - is_rendezvous - }).await?; + id: rendezvous, + is_rendezvous, + }, + ) + .await?; let mut buf = wallet.0.into_vec(); - encrypt_in_place(&mut buf,*secret_key.slice(), [0;12]); + encrypt_in_place(&mut buf, *secret_key.slice(), [0; 12]); let wallet: Wallet = serde_bare::from_slice(&buf)?; let broker = match LOCAL_BROKER.get() { @@ -1548,8 +1725,8 @@ pub async fn wallet_import_from_code(code: String) -> Result { } else { Err(NgError::WalletAlreadyAdded) } - } + _ => Err(NgError::IncompatibleQrCode), } } @@ -1557,56 +1734,54 @@ pub async fn wallet_import_from_code(code: String) -> Result { /// /// A rendezvous is used when the device that is importing, doesn't have a camera. /// The QRCode is displayed on that device, and another device (with camera, and with the wallet) will scan it. -/// +/// /// Returns the QRcode in SVG format, and the code (a string) to be used with [wallet_import_from_code] -pub async fn wallet_import_rendezvous(size: u32) -> Result<(String,String), NgError> { - let code = NgQRCode::V0(NgQRCodeV0 { +pub async fn wallet_import_rendezvous(size: u32) -> Result<(String, String), NgError> { + let code = NgQRCode::WalletTransferV0(NgQRCodeWalletTransferV0 { broker: NEXTGRAPH_EU.clone(), rendezvous: SymKey::random(), secret_key: SymKey::random(), - is_rendezvous: true + is_rendezvous: true, }); let code_string = code.to_code(); let code_svg = match QrCode::with_error_correction_level(&code_string, qrcode::EcLevel::M) { Ok(qr) => { let svg = qr - .render() - .max_dimensions(size, size) - .dark_color(svg::Color("#000000")) - .light_color(svg::Color("#ffffff")) - .build(); + .render() + .max_dimensions(size, size) + .dark_color(svg::Color("#000000")) + .light_color(svg::Color("#ffffff")) + .build(); svg - }, - Err(_e) => {return Err(NgError::BrokerError)} + } + Err(_e) => return Err(NgError::BrokerError), }; - Ok((code_svg,code_string)) + Ok((code_svg, code_string)) } /// Gets the TextCode to display in order to export the wallet of the current session ID /// /// The ExportedWallet is valid for 5 min. -/// +/// /// Returns the TextCode pub async fn wallet_export_get_textcode(session_id: u64) -> Result { - let broker = match LOCAL_BROKER.get() { None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), Some(Ok(broker)) => broker.read().await, }; match &broker.config { - LocalBrokerConfig::Headless(_) => { - return Err(NgError::LocalBrokerIsHeadless) - }, + LocalBrokerConfig::Headless(_) => return Err(NgError::LocalBrokerIsHeadless), _ => { let (real_session_id, is_remote) = broker.get_real_session_id_for_mut(session_id)?; if is_remote { return Err(NgError::NotImplemented); } else { - let session = broker.opened_sessions_list[real_session_id].as_ref() + let session = broker.opened_sessions_list[real_session_id] + .as_ref() .ok_or(NgError::SessionNotFound)?; let wallet_name = session.config.wallet_name(); @@ -1616,25 +1791,36 @@ pub async fn wallet_export_get_textcode(session_id: u64) -> Result(WalletPutExport::V0(WalletPutExportV0{wallet:exported_wallet, rendezvous_id:rendezvous, is_rendezvous:false})).await { + encrypt_in_place(&mut wallet_ser, *secret_key.slice(), [0; 12]); + let exported_wallet = + ExportedWallet(serde_bytes::ByteBuf::from(wallet_ser)); + match session + .verifier + .client_request::(WalletPutExport::V0( + WalletPutExportV0 { + wallet: exported_wallet, + rendezvous_id: rendezvous, + is_rendezvous: false, + }, + )) + .await + { Err(e) => Err(e), Ok(SoS::Stream(_)) => Err(NgError::InvalidResponse), - Ok(SoS::Single(_)) => Ok(code_string) + Ok(SoS::Single(_)) => Ok(code_string), } } } - } + } } } } @@ -1642,38 +1828,39 @@ pub async fn wallet_export_get_textcode(session_id: u64) -> Result Result { - let code_string = wallet_export_get_textcode(session_id).await?; let code_svg = match QrCode::with_error_correction_level(&code_string, qrcode::EcLevel::M) { Ok(qr) => { let svg = qr - .render() - .max_dimensions(size, size) - .dark_color(svg::Color("#000000")) - .light_color(svg::Color("#ffffff")) - .build(); + .render() + .max_dimensions(size, size) + .dark_color(svg::Color("#000000")) + .light_color(svg::Color("#ffffff")) + .build(); svg - }, - Err(_e) => {return Err(NgError::BrokerError)} + } + Err(_e) => return Err(NgError::BrokerError), }; - - Ok(code_svg) + Ok(code_svg) } /// Puts the Wallet to the rendezvous ID that was scanned /// /// The rendezvous ID is valid for 5 min. pub async fn wallet_export_rendezvous(session_id: u64, code: String) -> Result<(), NgError> { - let qr = NgQRCode::from_code(code)?; match qr { - NgQRCode::V0(NgQRCodeV0{broker: _, rendezvous, secret_key, is_rendezvous}) => { - + NgQRCode::WalletTransferV0(NgQRCodeWalletTransferV0 { + broker: _, + rendezvous, + secret_key, + is_rendezvous, + }) => { if !is_rendezvous { return Err(NgError::NotARendezVous); } @@ -1682,45 +1869,57 @@ pub async fn wallet_export_rendezvous(session_id: u64, code: String) -> Result<( None | Some(Err(_)) => return Err(NgError::LocalBrokerNotInitialized), Some(Ok(broker)) => broker.read().await, }; - + match &broker.config { - LocalBrokerConfig::Headless(_) => { - return Err(NgError::LocalBrokerIsHeadless) - }, + LocalBrokerConfig::Headless(_) => return Err(NgError::LocalBrokerIsHeadless), _ => { - let (real_session_id, is_remote) = broker.get_real_session_id_for_mut(session_id)?; - + let (real_session_id, is_remote) = + broker.get_real_session_id_for_mut(session_id)?; + if is_remote { return Err(NgError::NotImplemented); } else { - let session = broker.opened_sessions_list[real_session_id].as_ref() + let session = broker.opened_sessions_list[real_session_id] + .as_ref() .ok_or(NgError::SessionNotFound)?; let wallet_name = session.config.wallet_name(); - + match broker.wallets.get(&wallet_name) { None => Err(NgError::WalletNotFound), Some(lws) => { //let broker = lws.bootstrap.servers().first().unwrap(); let wallet = &lws.wallet; - + let mut wallet_ser = serde_bare::to_vec(wallet)?; - encrypt_in_place(&mut wallet_ser,*secret_key.slice(), [0;12]); - let exported_wallet = ExportedWallet(serde_bytes::ByteBuf::from(wallet_ser)); - + encrypt_in_place(&mut wallet_ser, *secret_key.slice(), [0; 12]); + let exported_wallet = + ExportedWallet(serde_bytes::ByteBuf::from(wallet_ser)); + // TODO: send the WalletPutExport client request to the broker received from QRcode. for now it is cheer luck that all clients are connected to nextgraph.eu. // if the user doesn't have an account with nextgraph.eu, their broker should relay the request (core protocol ?) - match session.verifier.client_request::(WalletPutExport::V0(WalletPutExportV0{wallet:exported_wallet, rendezvous_id:rendezvous, is_rendezvous:true})).await { + match session + .verifier + .client_request::(WalletPutExport::V0( + WalletPutExportV0 { + wallet: exported_wallet, + rendezvous_id: rendezvous, + is_rendezvous: true, + }, + )) + .await + { Err(e) => Err(e), Ok(SoS::Stream(_)) => Err(NgError::InvalidResponse), - Ok(SoS::Single(_)) => Ok(()) + Ok(SoS::Single(_)) => Ok(()), } } } - } + } } } } + _ => Err(NgError::IncompatibleQrCode), } } @@ -1787,27 +1986,6 @@ pub fn wallet_open_with_pazzle( Ok(opened_wallet) } -#[doc(hidden)] -/// This is a bit hard to use as the mnemonic words are encoded in u16. -/// prefer the function wallet_open_with_mnemonic_words -pub fn wallet_open_with_mnemonic( - wallet: &Wallet, - mnemonic: Vec, - pin: [u8; 4], -) -> Result { - if mnemonic.len() != 12 { - return Err(NgError::InvalidMnemonic); - } - // Convert from vec to array. - let mut mnemonic_arr = [0u16; 12]; - for (place, element) in mnemonic_arr.iter_mut().zip(mnemonic.iter()) { - *place = *element; - } - let opened_wallet = ng_wallet::open_wallet_with_mnemonic(wallet, mnemonic_arr, pin)?; - - Ok(opened_wallet) -} - /// Opens a wallet by providing an ordered list of words, and the pin. /// /// If you are opening a wallet that is already known to the LocalBroker, you must then call [wallet_was_opened]. @@ -1822,7 +2000,6 @@ pub fn wallet_open_with_pazzle_words( wallet_open_with_pazzle(wallet, encode_pazzle(pazzle_words)?, pin) } - /// Opens a wallet by providing an ordered list of mnemonic words, and the pin. /// /// If you are opening a wallet that is already known to the LocalBroker, you must then call [wallet_was_opened]. @@ -1832,9 +2009,11 @@ pub fn wallet_open_with_mnemonic_words( mnemonic: &Vec, pin: [u8; 4], ) -> Result { - let encoded: Vec = encode_mnemonic(mnemonic)?; - - wallet_open_with_mnemonic(wallet, encoded, pin) + Ok(ng_wallet::open_wallet_with_mnemonic( + wallet, + encode_mnemonic(mnemonic)?, + pin, + )?) } /// Imports a wallet into the LocalBroker so the user can then access its content. @@ -1901,7 +2080,7 @@ pub async fn session_start(config: SessionConfig) -> Result Result it needs to establish a connection to remote here, then send the AppSessionStart in it. // also, it is using broker.remote_sessions.get _ => { - if config.is_remote() || config.is_with_credentials() { unimplemented!(); } @@ -2185,10 +2363,9 @@ pub async fn session_stop(user_id: &UserId) -> Result<(), NgError> { match broker.config { LocalBrokerConfig::Headless(_) => { - - let (session_id,_) = broker.remove_headless_session(user_id)?; + let (session_id, _) = broker.remove_headless_session(user_id)?; - let request = AppSessionStop::V0(AppSessionStopV0{ + let request = AppSessionStop::V0(AppSessionStopV0 { session_id, force_close: false, }); @@ -2223,15 +2400,17 @@ pub async fn session_headless_stop(session_id: u64, force_close: bool) -> Result match broker.config { LocalBrokerConfig::Headless(_) => { - let session = broker .headless_sessions .remove(&session_id) .ok_or(NgError::SessionNotFound)?; - let _ = broker.opened_sessions.remove(&session.user_id).ok_or(NgError::SessionNotFound)?; + let _ = broker + .opened_sessions + .remove(&session.user_id) + .ok_or(NgError::SessionNotFound)?; - let request = AppSessionStop::V0(AppSessionStopV0{ + let request = AppSessionStop::V0(AppSessionStopV0 { session_id, force_close, }); @@ -2271,7 +2450,7 @@ pub async fn wallet_close(wallet_name: &String) -> Result<(), NgError> { for user in opened_wallet.wallet.site_names() { let key: PubKey = (user.as_str()).try_into().unwrap(); match broker.opened_sessions.remove(&key) { - Some(id) => { + Some(id) => { let session = broker.get_local_session_id_for_mut(id)?; broker.opened_sessions_list[session].take(); } @@ -2342,23 +2521,22 @@ pub async fn app_request(request: AppRequest) -> Result { Some(Ok(broker)) => broker.write().await, }; match &broker.config { - LocalBrokerConfig::Headless(_) => { - broker.send_request_headless(request).await - }, + LocalBrokerConfig::Headless(_) => broker.send_request_headless(request).await, _ => { - let (real_session_id, is_remote) = broker.get_real_session_id_for_mut(request.session_id())?; + let (real_session_id, is_remote) = + broker.get_real_session_id_for_mut(request.session_id())?; if is_remote { let session = broker.remote_sessions_list[real_session_id] - .as_ref() - .ok_or(NgError::SessionNotFound)?; + .as_ref() + .ok_or(NgError::SessionNotFound)?; session.send_request(request).await } else { let session = broker.opened_sessions_list[real_session_id] .as_mut() .ok_or(NgError::SessionNotFound)?; session.verifier.app_request(request).await - } + } } } } @@ -2372,22 +2550,21 @@ pub async fn app_request_stream( Some(Ok(broker)) => broker.write().await, }; match &broker.config { - LocalBrokerConfig::Headless(_) => { - broker.send_request_stream_headless(request).await - }, + LocalBrokerConfig::Headless(_) => broker.send_request_stream_headless(request).await, _ => { - let (real_session_id, is_remote) = broker.get_real_session_id_for_mut(request.session_id())?; + let (real_session_id, is_remote) = + broker.get_real_session_id_for_mut(request.session_id())?; if is_remote { let session = broker.remote_sessions_list[real_session_id] - .as_ref() - .ok_or(NgError::SessionNotFound)?; + .as_ref() + .ok_or(NgError::SessionNotFound)?; session.send_request_stream(request).await } else { let session = broker.opened_sessions_list[real_session_id] .as_mut() .ok_or(NgError::SessionNotFound)?; - session.verifier.app_request_stream(request).await + session.verifier.app_request_stream(request).await } } } @@ -2447,45 +2624,41 @@ async fn do_admin_call< async fn do_ext_call< A: Into + Into + std::fmt::Debug + Sync + Send + 'static, - B: TryFrom - + std::fmt::Debug - + Sync - + Send - + 'static, + B: TryFrom + std::fmt::Debug + Sync + Send + 'static, >( broker_server: &BrokerServerV0, cmd: A, ) -> Result { let (peer_privk, peer_pubk) = generate_keypair(); Broker::ext( - Box::new(ConnectionWebSocket {}), - peer_privk, - peer_pubk, - broker_server.peer_id, - broker_server.get_ws_url(&None).await.unwrap().0, // for now we are only connecting to NextGraph SaaS cloud (nextgraph.eu) so it is safe. - cmd, - ) - .await + Box::new(ConnectionWebSocket {}), + peer_privk, + peer_pubk, + broker_server.peer_id, + broker_server.get_ws_url(&None).await.unwrap().0, // for now we are only connecting to NextGraph SaaS cloud (nextgraph.eu) so it is safe. + cmd, + ) + .await } #[doc(hidden)] -pub async fn admin_create_user(server_peer_id: DirectPeerId, admin_user_key: PrivKey, server_addr: BindAddress) -> Result { - +pub async fn admin_create_user( + server_peer_id: DirectPeerId, + admin_user_key: PrivKey, + server_addr: BindAddress, +) -> Result { let res = do_admin_call( server_peer_id, admin_user_key, server_addr, - CreateUser::V0(CreateUserV0 { - - }), + CreateUser::V0(CreateUserV0 {}), ) .await?; match res { AdminResponseContentV0::UserId(id) => Ok(id), - _ => Err(ProtocolError::InvalidValue) + _ => Err(ProtocolError::InvalidValue), } - } #[allow(unused_imports)] @@ -2717,4 +2890,35 @@ mod test { // closes the wallet wallet_close(&wallet_name).await.expect("wallet_close"); } + + #[async_std::test] + async fn recovery_pdf() { + let wallet_file = read("tests/wallet.ngw").expect("read wallet file"); + + init_local_broker(Box::new(|| LocalBrokerConfig::InMemory)).await; + + let wallet = wallet_read_file(wallet_file) + .await + .expect("wallet_read_file"); + + let pazzle_string = read_to_string("tests/wallet.pazzle").expect("read pazzle file"); + let pazzle_words = pazzle_string.split(' ').map(|s| s.to_string()).collect(); + + let mnemonic_string = read_to_string("tests/wallet.mnemonic").expect("read mnemonic file"); + let mnemonic_words = mnemonic_string.split(' ').map(|s| s.to_string()).collect(); + + let pin: [u8; 4] = [1, 2, 1, 2]; + + let pazzle = encode_pazzle(&pazzle_words).expect("encode_pazzle"); + let mnemonic = encode_mnemonic(&mnemonic_words).expect("encode_mnemonic"); + + let wallet_recovery = wallet_to_wallet_recovery(&wallet, pazzle, mnemonic, pin); + + let pdf_buffer = wallet_recovery_pdf(wallet_recovery, 600) + .await + .expect("wallet_recovery_pdf"); + let mut file = + File::create("tests/recovery.pdf").expect("open for write recovery.pdf file"); + file.write_all(&pdf_buffer).expect("write of recovery.pdf"); + } } diff --git a/ng-app/src-tauri/build.rs b/ng-app/src-tauri/build.rs index 795b9b7..d860e1e 100644 --- a/ng-app/src-tauri/build.rs +++ b/ng-app/src-tauri/build.rs @@ -1,3 +1,3 @@ fn main() { - tauri_build::build() + tauri_build::build() } diff --git a/ng-app/src-tauri/src/lib.rs b/ng-app/src-tauri/src/lib.rs index 536f9c5..18ff0f4 100644 --- a/ng-app/src-tauri/src/lib.rs +++ b/ng-app/src-tauri/src/lib.rs @@ -111,12 +111,12 @@ async fn wallet_open_with_pazzle( #[tauri::command(rename_all = "snake_case")] async fn wallet_open_with_mnemonic( wallet: Wallet, - mnemonic: Vec, + mnemonic: [u16; 12], pin: [u8; 4], _app: tauri::AppHandle, ) -> Result { - let wallet = nextgraph::local_broker::wallet_open_with_mnemonic(&wallet, mnemonic, pin) - .map_err(|e| e.to_string())?; + let wallet = + ng_wallet::open_wallet_with_mnemonic(&wallet, mnemonic, pin).map_err(|e| e.to_string())?; Ok(wallet) } diff --git a/ng-app/src/api.ts b/ng-app/src/api.ts index 7c0f423..ffd3c18 100644 --- a/ng-app/src/api.ts +++ b/ng-app/src/api.ts @@ -17,6 +17,7 @@ const mapping = { "wallet_gen_shuffle_for_pin": [], "wallet_open_with_pazzle": ["wallet","pazzle","pin"], "wallet_open_with_mnemonic_words": ["wallet","mnemonic_words","pin"], + "wallet_open_with_mnemonic": ["wallet","mnemonic","pin"], "wallet_was_opened": ["opened_wallet"], "wallet_create": ["params"], "wallet_read_file": ["file"], @@ -189,7 +190,7 @@ const handler = { return false; } else if (path[0] === "get_local_url") { return false; - } else if (path[0] === "wallet_open_with_pazzle" || path[0] === "wallet_open_with_mnemonic_words") { + } else if (path[0] === "wallet_open_with_pazzle" || path[0] === "wallet_open_with_mnemonic_words" || path[0] === "wallet_open_with_mnemonic") { let arg:any = {}; args.map((el,ix) => arg[mapping[path[0]][ix]]=el) let img = Array.from(new Uint8Array(arg.wallet.V0.content.security_img)); diff --git a/ng-app/src/locales/en.json b/ng-app/src/locales/en.json index 66d1e95..fcf671c 100644 --- a/ng-app/src/locales/en.json +++ b/ng-app/src/locales/en.json @@ -348,7 +348,8 @@ "InvalidNuri": "Invalid NextGraph URI.", "InvalidTarget": "Cannot resolve target.", "ExportWalletTimeOut": "Export of wallet has expired.", - "ConnectionError": "Could not connect to the server." + "ConnectionError": "Could not connect to the server.", + "IncompatibleQrCode": "You scanned a NextGraph QR-Code that is of the wrong type" }, "connectivity": { "stopped": "Stopped", diff --git a/ng-net/src/broker.rs b/ng-net/src/broker.rs index e430567..25bf52a 100644 --- a/ng-net/src/broker.rs +++ b/ng-net/src/broker.rs @@ -639,7 +639,7 @@ impl Broker { let res = join.next().await; match res { Some(Either::Right(remote_peer_id)) => { - let res = join.next().await; + let _res = join.next().await; // if res.is_some() // && res.as_ref().unwrap().as_ref().unwrap_left() == &NetError::Closing diff --git a/ng-oxigraph/build.rs b/ng-oxigraph/build.rs index 02716ec..bd0dfc1 100644 --- a/ng-oxigraph/build.rs +++ b/ng-oxigraph/build.rs @@ -1,7 +1,5 @@ fn main() { - if std::env::var("DOCS_RS").is_ok() { println!("cargo:rustc-cfg=docsrs"); } - -} \ No newline at end of file +} diff --git a/ng-oxigraph/src/oxigraph/storage/backend/mod.rs b/ng-oxigraph/src/oxigraph/storage/backend/mod.rs index 1a29081..f42e21a 100644 --- a/ng-oxigraph/src/oxigraph/storage/backend/mod.rs +++ b/ng-oxigraph/src/oxigraph/storage/backend/mod.rs @@ -1,12 +1,12 @@ //! A storage backend //! RocksDB is available, if not in memory -#[cfg(any(target_family = "wasm",docsrs))] +#[cfg(any(target_family = "wasm", docsrs))] pub use fallback::{ColumnFamily, ColumnFamilyDefinition, Db, Iter, Reader, Transaction}; -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] pub use oxi_rocksdb::{ColumnFamily, ColumnFamilyDefinition, Db, Iter, Reader, Transaction}; -#[cfg(any(target_family = "wasm",docsrs))] +#[cfg(any(target_family = "wasm", docsrs))] mod fallback; -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] mod oxi_rocksdb; diff --git a/ng-oxigraph/tests/store.rs b/ng-oxigraph/tests/store.rs index 0f36aba..6e8b1cb 100644 --- a/ng-oxigraph/tests/store.rs +++ b/ng-oxigraph/tests/store.rs @@ -5,20 +5,20 @@ use ng_oxigraph::oxigraph::io::RdfFormat; use ng_oxigraph::oxigraph::model::vocab::{rdf, xsd}; use ng_oxigraph::oxigraph::model::*; use ng_oxigraph::oxigraph::store::Store; -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] use rand::random; -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] use std::env::temp_dir; use std::error::Error; -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] use std::fs::{create_dir_all, remove_dir_all, File}; -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] use std::io::Write; -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] use std::iter::empty; #[cfg(all(target_os = "linux"))] use std::iter::once; -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] use std::path::{Path, PathBuf}; #[cfg(all(target_os = "linux"))] use std::process::Command; @@ -121,7 +121,7 @@ fn test_load_graph() -> Result<(), Box> { } #[test] -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] fn test_bulk_load_graph() -> Result<(), Box> { let store = Store::new()?; store @@ -135,7 +135,7 @@ fn test_bulk_load_graph() -> Result<(), Box> { } #[test] -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] fn test_bulk_load_graph_lenient() -> Result<(), Box> { let store = Store::new()?; store.bulk_loader().on_parse_error(|_| Ok(())).load_from_read( @@ -154,7 +154,7 @@ fn test_bulk_load_graph_lenient() -> Result<(), Box> { } #[test] -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] fn test_bulk_load_empty() -> Result<(), Box> { let store = Store::new()?; store.bulk_loader().load_quads(empty::())?; @@ -177,7 +177,7 @@ fn test_load_dataset() -> Result<(), Box> { } #[test] -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] fn test_bulk_load_dataset() -> Result<(), Box> { let store = Store::new()?; store @@ -258,7 +258,7 @@ fn test_snapshot_isolation_iterator() -> Result<(), Box> { } #[test] -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] fn test_bulk_load_on_existing_delete_overrides_the_delete() -> Result<(), Box> { let quad = QuadRef::new( NamedNodeRef::new_unchecked("http://example.com/s"), @@ -274,7 +274,7 @@ fn test_bulk_load_on_existing_delete_overrides_the_delete() -> Result<(), Box Result<(), Box> { let dir = TempDir::default(); create_dir_all(&dir.0)?; @@ -344,7 +344,7 @@ fn test_bad_stt_open() -> Result<(), Box> { // } #[test] -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] fn test_bad_backup() -> Result<(), Box> { let store_dir = TempDir::default(); let backup_dir = TempDir::default(); @@ -355,7 +355,7 @@ fn test_bad_backup() -> Result<(), Box> { } #[test] -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] fn test_backup_on_in_memory() -> Result<(), Box> { let backup_dir = TempDir::default(); Store::new()?.backup(&backup_dir).unwrap_err(); @@ -442,7 +442,7 @@ fn test_backward_compatibility() -> Result<(), Box> { // } #[test] -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] fn test_read_only() -> Result<(), Box> { let s = NamedNodeRef::new_unchecked("http://example.com/s"); let p = NamedNodeRef::new_unchecked("http://example.com/p"); @@ -491,7 +491,7 @@ fn test_read_only() -> Result<(), Box> { } #[test] -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] fn test_open_read_only_bad_dir() -> Result<(), Box> { let dir = TempDir::default(); create_dir_all(&dir.0)?; @@ -515,24 +515,24 @@ fn reset_dir(dir: &str) -> Result<(), Box> { Ok(()) } -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] struct TempDir(PathBuf); -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] impl Default for TempDir { fn default() -> Self { Self(temp_dir().join(format!("oxigraph-test-{}", random::()))) } } -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] impl AsRef for TempDir { fn as_ref(&self) -> &Path { &self.0 } } -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] impl Drop for TempDir { fn drop(&mut self) { if self.0.is_dir() { diff --git a/ng-repo/src/errors.rs b/ng-repo/src/errors.rs index 2406644..183c35e 100644 --- a/ng-repo/src/errors.rs +++ b/ng-repo/src/errors.rs @@ -87,6 +87,7 @@ pub enum NgError { InvalidQrCode, NotImplemented, NotARendezVous, + IncompatibleQrCode, } impl Error for NgError {} diff --git a/ng-sdk-js/src/lib.rs b/ng-sdk-js/src/lib.rs index 509eb19..7e606d5 100644 --- a/ng-sdk-js/src/lib.rs +++ b/ng-sdk-js/src/lib.rs @@ -148,14 +148,16 @@ pub fn wallet_open_with_pazzle( #[wasm_bindgen] pub fn wallet_open_with_mnemonic( wallet: JsValue, - mnemonic: Vec, + mnemonic: JsValue, pin: JsValue, ) -> Result { let encrypted_wallet = serde_wasm_bindgen::from_value::(wallet) .map_err(|_| "Deserialization error of wallet")?; let pin = serde_wasm_bindgen::from_value::<[u8; 4]>(pin) .map_err(|_| "Deserialization error of pin")?; - let res = nextgraph::local_broker::wallet_open_with_mnemonic(&encrypted_wallet, mnemonic, pin); + let mnemonic = serde_wasm_bindgen::from_value::<[u16; 12]>(mnemonic) + .map_err(|_| "Deserialization error of mnemonic")?; + let res = ng_wallet::open_wallet_with_mnemonic(&encrypted_wallet, mnemonic, pin); match res { Ok(r) => Ok(r .serialize(&serde_wasm_bindgen::Serializer::new().serialize_maps_as_objects(true)) diff --git a/ng-storage-rocksdb/build.rs b/ng-storage-rocksdb/build.rs index 02716ec..bd0dfc1 100644 --- a/ng-storage-rocksdb/build.rs +++ b/ng-storage-rocksdb/build.rs @@ -1,7 +1,5 @@ fn main() { - if std::env::var("DOCS_RS").is_ok() { println!("cargo:rustc-cfg=docsrs"); } - -} \ No newline at end of file +} diff --git a/ng-storage-rocksdb/src/lib.rs b/ng-storage-rocksdb/src/lib.rs index eace706..87d6bc8 100644 --- a/ng-storage-rocksdb/src/lib.rs +++ b/ng-storage-rocksdb/src/lib.rs @@ -1,5 +1,5 @@ -#[cfg(all(not(target_arch = "wasm32"),not(docsrs)))] +#[cfg(all(not(target_arch = "wasm32"), not(docsrs)))] pub mod block_storage; -#[cfg(all(not(target_arch = "wasm32"),not(docsrs)))] +#[cfg(all(not(target_arch = "wasm32"), not(docsrs)))] pub mod kcv_storage; diff --git a/ng-verifier/build.rs b/ng-verifier/build.rs index 02716ec..bd0dfc1 100644 --- a/ng-verifier/build.rs +++ b/ng-verifier/build.rs @@ -1,7 +1,5 @@ fn main() { - if std::env::var("DOCS_RS").is_ok() { println!("cargo:rustc-cfg=docsrs"); } - -} \ No newline at end of file +} diff --git a/ng-verifier/src/lib.rs b/ng-verifier/src/lib.rs index 8c2c26e..2ab89fd 100644 --- a/ng-verifier/src/lib.rs +++ b/ng-verifier/src/lib.rs @@ -1,4 +1,3 @@ - pub mod types; pub mod site; @@ -12,5 +11,5 @@ mod commits; mod request_processor; -#[cfg(all(not(target_family = "wasm"),not(docsrs)))] +#[cfg(all(not(target_family = "wasm"), not(docsrs)))] mod rocksdb_user_storage; diff --git a/ng-wallet/src/bip39.rs b/ng-wallet/src/bip39.rs index dc02674..e254073 100644 --- a/ng-wallet/src/bip39.rs +++ b/ng-wallet/src/bip39.rs @@ -227,14 +227,15 @@ lazy_static! { } /// Taking a list of bip39 words, returns a list of u16 codes -pub fn encode_mnemonic(words: &Vec) -> Result, NgError> { - let mut res = vec![]; - for word in words { - res.push( - *BIP39_WORD_MAP - .get(word.as_str()) - .ok_or(NgError::InvalidMnemonic)?, - ); +pub fn encode_mnemonic(words: &Vec) -> Result<[u16; 12], NgError> { + if words.len() != 12 { + return Err(NgError::InvalidMnemonic); + } + let mut res = [0u16; 12]; + for (idx, word) in words.iter().enumerate() { + res[idx] = *BIP39_WORD_MAP + .get(word.as_str()) + .ok_or(NgError::InvalidMnemonic)?; } Ok(res) } diff --git a/ng-wallet/src/types.rs b/ng-wallet/src/types.rs index 36aca89..31a2732 100644 --- a/ng-wallet/src/types.rs +++ b/ng-wallet/src/types.rs @@ -1428,16 +1428,25 @@ pub struct ShuffledPazzle { } #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct NgQRCodeV0 { +pub struct NgQRCodeWalletTransferV0 { pub broker: BrokerServerV0, pub rendezvous: SymKey, // Rendez-vous ID pub secret_key: SymKey, pub is_rendezvous: bool, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct NgQRCodeWalletRecoveryV0 { + pub wallet: WalletContentV0, //of which security_img is emptied + pub pazzle: Vec, + pub mnemonic: [u16; 12], + pub pin: [u8; 4], +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub enum NgQRCode { - V0(NgQRCodeV0), + WalletTransferV0(NgQRCodeWalletTransferV0), + WalletRecoveryV0(NgQRCodeWalletRecoveryV0), } impl NgQRCode { diff --git a/ngaccount/src/main.rs b/ngaccount/src/main.rs index 61ebdc0..45ace0e 100644 --- a/ngaccount/src/main.rs +++ b/ngaccount/src/main.rs @@ -12,17 +12,17 @@ extern crate anyhow; mod types; use std::convert::Infallible; +use std::env; use std::net::IpAddr; use std::str::FromStr; use std::sync::Arc; -use std::{env}; use duration_str::parse; +use rust_embed::RustEmbed; use serde::{Deserialize, Serialize}; use warp::http::header::{HeaderMap, HeaderValue}; use warp::reply::Response; use warp::{Filter, Reply}; -use rust_embed::RustEmbed; use ng_repo::log::*; use ng_repo::types::*; @@ -32,12 +32,11 @@ use ng_net::actors::admin::add_invitation::*; use ng_net::broker::BROKER; use ng_net::types::{ AdminResponseContentV0, BindAddress, CreateAccountBSP, Invitation, InvitationCode, - APP_ACCOUNT_REGISTERED_SUFFIX, + APP_ACCOUNT_REGISTERED_SUFFIX, }; use ng_client_ws::remote_ws::ConnectionWebSocket; - #[derive(RustEmbed)] #[folder = "web/dist"] struct Static; @@ -261,7 +260,6 @@ async fn main() -> anyhow::Result<()> { "default-src 'self' data:; connect-src ipc: https://ipc.localhost 'self' http://localhost:3031", #[cfg(not(debug_assertions))] "default-src 'self' data:; connect-src ipc: https://ipc.localhost 'self'", - ), );