diff --git a/CHANGELOG.md b/CHANGELOG.md index 3814cea8..6fb4df34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] ### Added +- [\#154](https://github.com/Manta-Network/manta-signer/pull/154) Add storage abstractions and add server storage hook ### Changed diff --git a/Cargo.toml b/Cargo.toml index c9e79660..305338a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,11 +36,11 @@ derivative = { version = "2.2.0", default-features = false, features = ["use_cor dirs-next = { version = "2.0.0", default-features = false } futures = { version = "0.3.17", default-features = false, features = ["alloc"] } http-types = { version = "2.12.0", default-features = false } -manta-accounting = { git = "https://github.com/manta-network/manta-rs", tag = "v0.5.3", default-features = false, features = ["cocoon-fs"] } -manta-crypto = { git = "https://github.com/manta-network/manta-rs", tag = "v0.5.3", default-features = false, features = ["getrandom"] } -manta-parameters = { git = "https://github.com/manta-network/manta-rs", tag = "v0.5.3", default-features = false, features = ["download"] } -manta-pay = { git = "https://github.com/manta-network/manta-rs", tag = "v0.5.3", default-features = false, features = ["bs58", "groth16", "serde", "wallet"] } -manta-util = { git = "https://github.com/manta-network/manta-rs", tag = "v0.5.3", default-features = false } +manta-accounting = { git = "https://github.com/manta-network/manta-rs", tag = "v0.5.4", default-features = false, features = ["cocoon-fs"] } +manta-crypto = { git = "https://github.com/manta-network/manta-rs", tag = "v0.5.4", default-features = false, features = ["getrandom"] } +manta-parameters = { git = "https://github.com/manta-network/manta-rs", tag = "v0.5.4", default-features = false, features = ["download"] } +manta-pay = { git = "https://github.com/manta-network/manta-rs", tag = "v0.5.4", default-features = false, features = ["bs58", "groth16", "serde", "wallet"] } +manta-util = { git = "https://github.com/manta-network/manta-rs", tag = "v0.5.4", default-features = false } parking_lot = { version = "0.12.1", default-features = false } password-hash = { version = "0.4.2", default-features = false, features = ["alloc"] } secrecy = { version = "0.8.0", default-features = false, features = ["alloc"] } diff --git a/README.md b/README.md index 604a7458..6fc01088 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ `manta-signer` is manta's native client that **turbo charges** zero-knowledge-proof generation. - +

+ +

**Disclaimer: `manta-signer` is experimental software, use it at your own risk.** diff --git a/examples/test_server.rs b/examples/test_server.rs index 8042ca0f..02b3933f 100644 --- a/examples/test_server.rs +++ b/examples/test_server.rs @@ -20,7 +20,7 @@ use manta_crypto::rand::{CryptoRng, OsRng, RngCore, Sample}; use manta_signer::{ config::Config, secret::{Authorizer, Password, PasswordFuture, SecretString}, - service::{self, Error}, + service::{Error, Server}, }; /// Mock User @@ -58,5 +58,8 @@ async fn main() -> Result<(), Error> { if let Some(url) = std::env::args().nth(1) { config.service_url = url; } - service::start(config, MockUser::new(&mut OsRng)).await + Server::build(config, MockUser::new(&mut OsRng)) + .await? + .start() + .await } diff --git a/mac-installation.png b/mac-installation.png new file mode 100644 index 00000000..55798ca2 Binary files /dev/null and b/mac-installation.png differ diff --git a/src/http.rs b/src/http.rs new file mode 100644 index 00000000..159e8e64 --- /dev/null +++ b/src/http.rs @@ -0,0 +1,63 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-signer. +// +// manta-signer is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-signer is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-signer. If not, see . + +//! Manta Signer HTTP Utilities + +use crate::serde::{de::DeserializeOwned, Serialize}; +use core::future::Future; + +pub use tide::{Body, Error, Request, Response, Server, StatusCode}; + +/// Generates the JSON body for the output of `f`, returning an HTTP reponse. +#[inline] +pub async fn into_body(f: F) -> Result +where + R: Serialize, + E: Into, + F: FnOnce() -> Fut, + Fut: Future>, +{ + Ok(Body::from_json(&f().await.map_err(Into::into)?)?.into()) +} + +/// Executes `f` on the incoming `request`. +#[inline] +pub async fn execute(mut request: Request, f: F) -> Result +where + S: Clone, + T: DeserializeOwned, + R: Serialize, + E: Into, + F: FnOnce(S, T) -> Fut, + Fut: Future>, +{ + let args = request.body_json::().await?; + into_body(move || async move { f(request.state().clone(), args).await }).await +} + +/// Registers a `POST` command with the given `path` and execution `f`. +#[inline] +pub fn register_post(api: &mut Server, path: &'static str, f: F) +where + S: Clone + Send + Sync + 'static, + T: DeserializeOwned + Send + 'static, + R: Serialize + 'static, + E: Into + 'static, + F: Clone + Send + Sync + 'static + Fn(S, T) -> Fut, + Fut: Future> + Send + 'static, +{ + api.at(path).post(move |r| execute(r, f.clone())); +} diff --git a/src/lib.rs b/src/lib.rs index 07a53a2b..714e2eb3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,11 +20,15 @@ #![forbid(rustdoc::broken_intra_doc_links)] #![forbid(missing_docs)] +extern crate alloc; + pub mod config; +pub mod http; pub mod log; pub mod parameters; pub mod secret; pub mod service; +pub mod storage; #[doc(inline)] pub use manta_util::serde; diff --git a/src/secret.rs b/src/secret.rs index 58b2bf6d..c10ed621 100644 --- a/src/secret.rs +++ b/src/secret.rs @@ -22,6 +22,7 @@ use crate::config::Setup; use futures::future::BoxFuture; use manta_util::serde::Serialize; use password_hash::{PasswordHashString, SaltString}; +use tokio::sync::mpsc::{channel, Receiver, Sender}; pub use password_hash::{Error as PasswordHashError, PasswordHasher, PasswordVerifier}; pub use secrecy::{ExposeSecret, Secret, SecretString}; @@ -95,9 +96,9 @@ pub trait Authorizer: 'static + Send { /// # Implementation Note /// /// For custom service implementations, this method should be called before any service is run. - /// The [`service::start`] function already calls this method internally. + /// The [`Server::start`] function already calls this method internally. /// - /// [`service::start`]: crate::service::start + /// [`Server::start`]: crate::service::Server::start #[inline] fn setup<'s>(&'s mut self, setup: &'s Setup) -> UnitFuture<'s> { let _ = setup; @@ -198,3 +199,88 @@ where .to_owned() } } + +/// Password Sender +pub struct PasswordSender { + /// Password Sender + pub password: Sender, + + /// Retry Receiver + pub retry: Receiver, +} + +impl PasswordSender { + /// Builds a new [`PasswordSender`] from `password` and `retry`. + #[inline] + pub fn new(password: Sender, retry: Receiver) -> Self { + Self { password, retry } + } + + /// Loads the password with `password` waiting for a retry message. + #[inline] + pub async fn load(&mut self, password: SecretString) -> bool { + self.load_exact(password).await; + self.retry + .recv() + .await + .expect("Failed to receive retry message.") + } + + /// Loads the password with `password` without requesting a retry message. + #[inline] + pub async fn load_exact(&mut self, password: SecretString) { + let _ = self.password.send(Password::from_known(password)).await; + } + + /// Clears the currently stored password. + #[inline] + pub async fn clear(&self) { + let _ = self.password.send(Password::from_unknown()).await; + } +} + +/// Password Receiver +pub struct PasswordReceiver { + /// Password Receiver + pub password: Receiver, + + /// Retry Sender + pub retry: Sender, +} + +impl PasswordReceiver { + /// Builds a new [`PasswordReceiver`] from `password` and `retry`. + #[inline] + pub fn new(password: Receiver, retry: Sender) -> Self { + Self { password, retry } + } + + /// Sends the message `retry` across the retry channel. + #[inline] + pub async fn should_retry(&mut self, retry: bool) { + self.retry + .send(retry) + .await + .expect("Failed to send retry message."); + } + + /// Loads the password from the password channel. + #[inline] + pub async fn password(&mut self) -> Password { + self.password + .recv() + .await + .expect("Failed to receive password message.") + } +} + +/// Generates a new password-sending channel. +#[inline] +pub fn password_channel() -> (PasswordSender, PasswordReceiver) { + let (password_sender, password_receiver) = channel(1); + let (retry_sender, retry_receiver) = channel(1); + ( + PasswordSender::new(password_sender, retry_receiver), + PasswordReceiver::new(password_receiver, retry_sender), + ) +} diff --git a/src/service.rs b/src/service.rs index ffe8c088..96b721fb 100644 --- a/src/service.rs +++ b/src/service.rs @@ -18,10 +18,15 @@ use crate::{ config::{Config, Setup}, + http, log::{info, trace, warn}, secret::{Argon2, Authorizer, ExposeSecret, PasswordHash, SecretString}, }; -use core::{future::Future, time::Duration}; +use alloc::sync::Arc; +use core::{ + fmt::{self, Display}, + time::Duration, +}; use http_types::headers::HeaderValue; use manta_accounting::{ fs::{cocoon::File, File as _, SaveError}, @@ -29,31 +34,21 @@ use manta_accounting::{ transfer::canonical::TransferShape, }; use manta_pay::{ - config::{receiving_key_to_base58, ReceivingKey}, key::{Mnemonic, TestnetKeySecret}, - signer::{ - base::{ - HierarchicalKeyDerivationFunction, Signer, SignerParameters, SignerState, - UtxoAccumulator, - }, - ReceivingKeyRequest, SignError, SignRequest, SignResponse, SyncError, SyncRequest, - SyncResponse, + signer::base::{ + HierarchicalKeyDerivationFunction, Signer, SignerParameters, SignerState, UtxoAccumulator, }, }; -use manta_util::{ - from_variant_impl, - serde::{de::DeserializeOwned, Serialize}, -}; +use manta_util::{from_variant, serde::Serialize}; use parking_lot::Mutex; use std::{ io, net::{AddrParseError, SocketAddr}, path::Path, - sync::Arc, }; use tide::{ security::{CorsMiddleware, Origin}, - Body, Request, Response, StatusCode, + StatusCode, }; use tokio::{ fs, @@ -61,6 +56,14 @@ use tokio::{ task::{self, JoinError}, }; +pub use manta_pay::{ + config::{receiving_key_to_base58, ReceivingKey}, + signer::{ + ReceivingKeyRequest, SignError, SignRequest, SignResponse, SyncError, SyncRequest, + SyncResponse, + }, +}; + /// Password Retry Interval pub const PASSWORD_RETRY_INTERVAL: Duration = Duration::from_millis(1000); @@ -92,10 +95,10 @@ pub enum Error { AuthorizationError, } -from_variant_impl!(Error, AddrParseError, AddrParseError); -from_variant_impl!(Error, JoinError, JoinError); -from_variant_impl!(Error, SaveError, SaveError); -from_variant_impl!(Error, Io, io::Error); +from_variant!(Error, AddrParseError, AddrParseError); +from_variant!(Error, JoinError, JoinError); +from_variant!(Error, SaveError, SaveError); +from_variant!(Error, Io, io::Error); impl From for tide::Error { #[inline] @@ -112,6 +115,20 @@ impl From for tide::Error { } } +impl Display for Error { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::AddrParseError(err) => write!(f, "Address Parse Error: {}", err), + Self::JoinError(err) => write!(f, "Join Error: {}", err), + Self::ParameterLoadingError => write!(f, "Parameter Loading Error"), + Self::SaveError(err) => write!(f, "Save Error: {}", err), + Self::Io(err) => write!(f, "I/O Error: {}", err), + Self::AuthorizationError => write!(f, "Authorization Error"), + } + } +} + /// Result Type pub type Result = core::result::Result; @@ -168,7 +185,7 @@ struct State { /// Signer Server #[derive(derivative::Derivative)] #[derivative(Clone(bound = ""))] -struct Server +pub struct Server where A: Authorizer, { @@ -185,7 +202,7 @@ where { /// Builds a new [`Server`] from `config` and `authorizer`. #[inline] - async fn build(config: Config, mut authorizer: A) -> Result { + pub async fn build(config: Config, mut authorizer: A) -> Result { info!("building signer server")?; info!("loading latest parameters from Manta Parameters")?; let data_path = config.data_directory().to_owned(); @@ -232,6 +249,31 @@ where }) } + /// Starts the signer server with `config` and `authorizer`. + #[inline] + pub async fn start(self) -> Result<()> { + let config = self.state.lock().config.clone(); + info!("performing service setup with {:#?}", config)?; + let socket_address = config.service_url.parse::()?; + let cors = CorsMiddleware::new() + .allow_methods("GET, POST".parse::().unwrap()) + .allow_origin(match &config.origin_url { + Some(origin_url) => Origin::from(origin_url.as_str()), + _ => Origin::from("*"), + }) + .allow_credentials(false); + let mut api = tide::Server::with_state(self); + api.with(cors); + api.at("/version") + .get(|_| http::into_body(Server::::version)); + http::register_post(&mut api, "/sync", Server::sync); + http::register_post(&mut api, "/sign", Server::sign); + http::register_post(&mut api, "/receivingKeys", Server::receiving_keys); + info!("serving signer API at {}", socket_address)?; + api.listen(socket_address).await?; + Ok(()) + } + /// Loads the password from the `authorizer` and compute the password hash. #[inline] async fn load_password(authorizer: &mut A) -> Option<(SecretString, PasswordHash)> { @@ -287,22 +329,6 @@ where } } - /// Executes `f` on the incoming `request`. - #[inline] - async fn execute( - mut request: Request, - f: F, - ) -> Result - where - T: DeserializeOwned, - R: Serialize, - F: FnOnce(Self, T) -> Fut, - Fut: Future>, - { - let args = request.body_json::().await?; - into_body(move || async move { f(request.state().clone(), args).await }).await - } - /// Saves the signer state to disk. #[inline] async fn save(self) -> Result<()> { @@ -323,14 +349,14 @@ where /// Returns the [`crate::VERSION`] string to the client. #[inline] - async fn version() -> Result<&'static str> { + pub async fn version() -> Result<&'static str> { trace!("[PING] current signer version: {}", crate::VERSION)?; Ok(crate::VERSION) } /// Runs the synchronization protocol on the signer. #[inline] - async fn sync(self, request: SyncRequest) -> Result> { + pub async fn sync(self, request: SyncRequest) -> Result> { info!("[REQUEST] processing `sync`: {:?}.", request)?; let response = self.state.lock().signer.sync(request); task::spawn(async { @@ -344,7 +370,7 @@ where /// Runs the transaction signing protocol on the signer. #[inline] - async fn sign(self, request: SignRequest) -> Result> { + pub async fn sign(self, request: SignRequest) -> Result> { info!("[REQUEST] processing `sign`: {:?}.", request)?; let SignRequest { transaction, @@ -371,7 +397,7 @@ where /// Runs the receiving key sampling protocol on the signer. #[inline] - async fn receiving_keys(self, request: ReceivingKeyRequest) -> Result> { + pub async fn receiving_keys(self, request: ReceivingKeyRequest) -> Result> { info!("[REQUEST] processing `receivingKeys`: {:?}", request)?; let response = self.state.lock().signer.receiving_keys(request); info!( @@ -381,41 +407,3 @@ where Ok(response) } } - -/// Generates the JSON body for the output of `f`, returning an HTTP reponse. -#[inline] -async fn into_body(f: F) -> Result -where - R: Serialize, - F: FnOnce() -> Fut, - Fut: Future>, -{ - Ok(Body::from_json(&f().await?)?.into()) -} - -/// Starts the signer server with `config` and `authorizer`. -#[inline] -pub async fn start(config: Config, authorizer: A) -> Result<()> -where - A: Authorizer, -{ - info!("performing service setup with {:#?}", config)?; - let socket_address = config.service_url.parse::()?; - let cors = CorsMiddleware::new() - .allow_methods("GET, POST".parse::().unwrap()) - .allow_origin(match &config.origin_url { - Some(origin_url) => Origin::from(origin_url.as_str()), - _ => Origin::from("*"), - }) - .allow_credentials(false); - let mut api = tide::Server::with_state(Server::build(config, authorizer).await?); - api.with(cors); - api.at("/version").get(|_| into_body(Server::::version)); - api.at("/sync").post(|r| Server::execute(r, Server::sync)); - api.at("/sign").post(|r| Server::execute(r, Server::sign)); - api.at("/receivingKeys") - .post(|r| Server::execute(r, Server::receiving_keys)); - info!("serving signer API at {}", socket_address)?; - api.listen(socket_address).await?; - Ok(()) -} diff --git a/src/storage.rs b/src/storage.rs new file mode 100644 index 00000000..103eb898 --- /dev/null +++ b/src/storage.rs @@ -0,0 +1,88 @@ +// Copyright 2019-2022 Manta Network. +// This file is part of manta-signer. +// +// manta-signer is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// manta-signer is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with manta-signer. If not, see . + +//! Manta Signer State Handling Utilities + +use alloc::sync::Arc; +use tokio::sync::{Mutex, MutexGuard}; + +/// Underlying Storage Container +type Storage = Arc>>; + +/// Storage Entry +#[derive(derivative::Derivative)] +#[derivative(Clone(bound = ""), Debug, Default(bound = ""))] +pub struct Store(Storage); + +impl Store { + /// Returns the mutex guard for the optional storage item. + #[inline] + pub async fn lock(&self) -> MutexGuard> { + self.0.lock().await + } + + /// Writes `state` to the state stored in `self`. + #[inline] + pub async fn set(&self, state: T) { + self.write(move |t| *t = Some(state)).await + } + + /// Writes the contents of `self` using `f`. + #[inline] + pub async fn write(&self, f: F) -> U + where + F: FnOnce(&mut Option) -> U, + { + f(&mut *self.lock().await) + } + + /// Writes to the internal state of `self` with `f`, first unwrapping the optional state. + /// + /// # Panics + /// + /// This method panics if the internal state is the `None` variant. + #[inline] + pub async fn unwrapping_write(&self, f: F) -> U + where + F: FnOnce(&mut T) -> U, + { + self.write(move |t| f(t.as_mut().expect("Internal state was missing."))) + .await + } + + /// Reads the contents of `self` and sends them to `f`. + #[inline] + pub async fn update(&self, f: F) -> U + where + F: FnOnce(Option<&mut T>) -> U, + { + f(self.lock().await.as_mut()) + } + + /// Reads the contents of `self`, unwrapping the optional state and then sends it to `f`. + /// + /// # Panics + /// + /// This method panics if the internal state is the `None` variant. + #[inline] + pub async fn unwrapping_update(&self, f: F) -> U + where + F: FnOnce(&mut T) -> U, + { + self.update(move |t| f(t.expect("Internal state was missing."))) + .await + } +} diff --git a/ui/src-tauri/Cargo.lock b/ui/src-tauri/Cargo.lock index 448dbf84..165ee0c0 100644 --- a/ui/src-tauri/Cargo.lock +++ b/ui/src-tauri/Cargo.lock @@ -616,18 +616,18 @@ checksum = "3bdca834647821e0b13d9539a8634eb62d3501b6b6c2cec1722786ee6671b851" [[package]] name = "bip32" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "873faa4363bfc54c36a48321da034c92a0645a363eed34d948683ffc1706e37f" +checksum = "b30ed1d6f8437a487a266c8293aeb95b61a23261273e3e02912cdb8b68bf798b" dependencies = [ "bs58", - "hmac 0.11.0", + "hmac 0.12.1", "k256", "once_cell", - "pbkdf2", + "pbkdf2 0.11.0", "rand_core 0.6.3", - "ripemd160", - "sha2 0.9.9", + "ripemd", + "sha2 0.10.2", "subtle", "zeroize", ] @@ -684,7 +684,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "block-padding", "generic-array", ] @@ -697,12 +696,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-padding" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" - [[package]] name = "blocking" version = "1.2.0" @@ -808,9 +801,6 @@ name = "cc" version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" -dependencies = [ - "jobserver", -] [[package]] name = "cesu8" @@ -947,7 +937,7 @@ dependencies = [ "aes-gcm 0.9.4", "chacha20poly1305", "hmac 0.11.0", - "pbkdf2", + "pbkdf2 0.9.0", "rand 0.8.5", "sha2 0.9.9", "zeroize", @@ -971,18 +961,18 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "1.2.2" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" dependencies = [ "cache-padded", ] [[package]] name = "const-oid" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" +checksum = "722e23542a15cea1f65d4a1419c4cfd7a26706c70871a13a04238ca3f40f1661" [[package]] name = "const_fn" @@ -1106,9 +1096,9 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.3.2" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +checksum = "9f2b443d17d49dad5ef0ede301c3179cc923b8822f3393b4d2c28c269dd4a122" dependencies = [ "generic-array", "rand_core 0.6.3", @@ -1185,9 +1175,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" +checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb" dependencies = [ "quote", "syn", @@ -1273,9 +1263,9 @@ dependencies = [ [[package]] name = "der" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +checksum = "13dd2ae565c0a381dde7fade45fce95984c568bdcb4700a4fdbe3175e0380b2f" dependencies = [ "const-oid", ] @@ -1374,9 +1364,9 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.13.4" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +checksum = "3bd46e0c364655e5baf2f5e99b603e7a09905da9966d7928d7470af393b28670" dependencies = [ "der", "elliptic-curve", @@ -1386,13 +1376,14 @@ dependencies = [ [[package]] name = "elliptic-curve" -version = "0.11.12" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +checksum = "c47abd0a791d2ac0c7aa1118715f85b83689e4522c4e3a244e159d4fc9848a8d" dependencies = [ "base16ct", "crypto-bigint", "der", + "digest 0.10.3", "ff", "generic-array", "group", @@ -1432,9 +1423,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastrand" @@ -1447,9 +1438,9 @@ dependencies = [ [[package]] name = "ff" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +checksum = "df689201f395c6b90dfe87127685f8dbfc083a5e779e613575d8bd7314300c3e" dependencies = [ "rand_core 0.6.3", "subtle", @@ -1754,10 +1745,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -1899,9 +1888,9 @@ dependencies = [ [[package]] name = "group" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +checksum = "7391856def869c1c81063a03457c676fbcd419709c3dfb33d8d319de484b154d" dependencies = [ "ff", "rand_core 0.6.3", @@ -2057,6 +2046,15 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.3", +] + [[package]] name = "html5ever" version = "0.25.2" @@ -2358,15 +2356,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" -[[package]] -name = "jobserver" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.59" @@ -2389,15 +2378,14 @@ dependencies = [ [[package]] name = "k256" -version = "0.10.4" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +checksum = "2c8a5a96d92d849c4499d99461da81c9cdc1467418a8ed2aaeb407e8d85940ed" dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "sec1", - "sha2 0.9.9", + "sha2 0.10.2", "sha3", ] @@ -2474,12 +2462,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "libm" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" - [[package]] name = "line-wrap" version = "0.1.1" @@ -2541,8 +2523,8 @@ dependencies = [ [[package]] name = "manta-accounting" -version = "0.5.3" -source = "git+https://github.com/manta-network/manta-rs?tag=v0.5.3#c90a4cd6105510b73646ed3029a24f9aa5b6594b" +version = "0.5.4" +source = "git+https://github.com/manta-network/manta-rs?tag=v0.5.4#aef233ce1b57c265b70b7fca6d951556eb97c715" dependencies = [ "bitflags", "cocoon", @@ -2556,8 +2538,8 @@ dependencies = [ [[package]] name = "manta-crypto" -version = "0.5.3" -source = "git+https://github.com/manta-network/manta-rs?tag=v0.5.3#c90a4cd6105510b73646ed3029a24f9aa5b6594b" +version = "0.5.4" +source = "git+https://github.com/manta-network/manta-rs?tag=v0.5.4#aef233ce1b57c265b70b7fca6d951556eb97c715" dependencies = [ "derivative", "manta-util", @@ -2567,8 +2549,8 @@ dependencies = [ [[package]] name = "manta-parameters" -version = "0.5.3" -source = "git+https://github.com/manta-network/manta-rs?tag=v0.5.3#c90a4cd6105510b73646ed3029a24f9aa5b6594b" +version = "0.5.4" +source = "git+https://github.com/manta-network/manta-rs?tag=v0.5.4#aef233ce1b57c265b70b7fca6d951556eb97c715" dependencies = [ "anyhow", "attohttpc", @@ -2580,8 +2562,8 @@ dependencies = [ [[package]] name = "manta-pay" -version = "0.5.3" -source = "git+https://github.com/manta-network/manta-rs?tag=v0.5.3#c90a4cd6105510b73646ed3029a24f9aa5b6594b" +version = "0.5.4" +source = "git+https://github.com/manta-network/manta-rs?tag=v0.5.4#aef233ce1b57c265b70b7fca6d951556eb97c715" dependencies = [ "aes-gcm 0.9.4", "ark-bls12-381", @@ -2641,8 +2623,8 @@ dependencies = [ [[package]] name = "manta-util" -version = "0.5.3" -source = "git+https://github.com/manta-network/manta-rs?tag=v0.5.3#c90a4cd6105510b73646ed3029a24f9aa5b6594b" +version = "0.5.4" +source = "git+https://github.com/manta-network/manta-rs?tag=v0.5.4#aef233ce1b57c265b70b7fca6d951556eb97c715" dependencies = [ "serde", "serde_with", @@ -2834,7 +2816,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -3045,6 +3026,15 @@ dependencies = [ "sha2 0.9.9", ] +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.3", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -3449,9 +3439,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534cfe58d6a18cc17120fbf4635d53d14691c1fe4d951064df9bd326178d7d5a" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -3542,12 +3532,12 @@ dependencies = [ [[package]] name = "rfc6979" -version = "0.1.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +checksum = "88c86280f057430a52f4861551b092a01b419b8eacefc7c995eacb9dc132fe32" dependencies = [ "crypto-bigint", - "hmac 0.11.0", + "hmac 0.12.1", "zeroize", ] @@ -3577,14 +3567,12 @@ dependencies = [ ] [[package]] -name = "ripemd160" -version = "0.9.1" +name = "ripemd" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" +checksum = "1facec54cb5e0dc08553501fa740091086d0259ad0067e0d4103448e4cb22ed3" dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "opaque-debug", + "digest 0.10.3", ] [[package]] @@ -3671,10 +3659,11 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "sec1" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ + "base16ct", "der", "generic-array", "subtle", @@ -3935,14 +3924,12 @@ dependencies = [ [[package]] name = "sha3" -version = "0.9.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", + "digest 0.10.3", "keccak", - "opaque-debug", ] [[package]] @@ -3975,11 +3962,11 @@ dependencies = [ [[package]] name = "signature" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" dependencies = [ - "digest 0.9.0", + "digest 0.10.3", "rand_core 0.6.3", ] @@ -5306,45 +5293,7 @@ dependencies = [ [[package]] name = "workspace-hack" version = "0.1.0" -source = "git+https://github.com/manta-network/manta-rs?tag=v0.5.3#c90a4cd6105510b73646ed3029a24f9aa5b6594b" -dependencies = [ - "aes-gcm 0.9.4", - "anyhow", - "bitflags", - "blake3", - "block-buffer 0.9.0", - "cc", - "crypto-common", - "digest 0.10.3", - "digest 0.9.0", - "futures", - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", - "generic-array", - "getrandom 0.2.7", - "indexmap", - "log", - "memchr", - "num-traits", - "pbkdf2", - "ppv-lite86", - "rand 0.8.5", - "rand_chacha 0.3.1", - "rand_core 0.6.3", - "serde", - "serde_json", - "sha2 0.9.9", - "standback", - "subtle", - "syn", - "url", - "web-sys", - "zeroize", -] +source = "git+https://github.com/manta-network/manta-rs?tag=v0.5.4#aef233ce1b57c265b70b7fca6d951556eb97c715" [[package]] name = "wry" diff --git a/ui/src-tauri/src/main.rs b/ui/src-tauri/src/main.rs index c936d902..881d5c9f 100644 --- a/ui/src-tauri/src/main.rs +++ b/ui/src-tauri/src/main.rs @@ -26,19 +26,21 @@ extern crate alloc; -use alloc::sync::Arc; use core::time::Duration; use manta_signer::{ config::{Config, Setup}, - secret::{Authorizer, Password, PasswordFuture, Secret, SecretString, UnitFuture}, + secret::{ + password_channel, Authorizer, Password, PasswordFuture, PasswordReceiver, PasswordSender, + Secret, UnitFuture, + }, serde::Serialize, - service, + service::Server, + storage::Store, tokio::time::sleep, }; use tauri::{ - async_runtime::{channel, spawn, Mutex, Receiver, Sender}, - CustomMenuItem, Manager, RunEvent, State, SystemTray, SystemTrayEvent, SystemTrayMenu, Window, - WindowEvent, + async_runtime::spawn, CustomMenuItem, Manager, RunEvent, Runtime, State, SystemTray, + SystemTrayEvent, SystemTrayMenu, Window, WindowEvent, }; /// User @@ -47,23 +49,19 @@ pub struct User { window: Window, /// Password Receiver - password: Receiver, - - /// Password Retry Sender - retry: Sender, + password_receiver: PasswordReceiver, /// Waiting Flag waiting: bool, } impl User { - /// Builds a new [`User`] from `window`, `password`, and `retry`. + /// Builds a new [`User`] from `window` and `password_receiver`. #[inline] - pub fn new(window: Window, password: Receiver, retry: Sender) -> Self { + pub fn new(window: Window, password_receiver: PasswordReceiver) -> Self { Self { window, - password, - retry, + password_receiver, waiting: false, } } @@ -74,16 +72,9 @@ impl User { where T: Serialize, { - self.window.emit(kind, message).unwrap() - } - - /// Sends a the `retry` message to have the user retry the password. - #[inline] - async fn should_retry(&mut self, retry: bool) { - self.retry - .send(retry) - .await - .expect("Failed to send retry message."); + self.window + .emit(kind, message) + .expect("Unable to emit message to the window.") } /// Requests password from user, sending a retry message if the previous password did not match @@ -91,13 +82,9 @@ impl User { #[inline] async fn request_password(&mut self) -> Password { if self.waiting { - self.should_retry(true).await; + self.password_receiver.should_retry(true).await; } - let password = self - .password - .recv() - .await - .expect("Failed to receive retry message."); + let password = self.password_receiver.password().await; self.waiting = password.is_known(); password } @@ -106,7 +93,7 @@ impl User { #[inline] async fn validate_password(&mut self) { self.waiting = false; - self.should_retry(false).await; + self.password_receiver.should_retry(false).await; } } @@ -143,70 +130,11 @@ impl Authorizer for User { } } -/// Password Storage Channel -struct PasswordStoreChannel { - /// Password Sender - password: Sender, - - /// Retry Receiver - retry: Receiver, -} - -/// Password Storage Type -type PasswordStoreType = Arc>>; - -/// Password Storage Handle -pub struct PasswordStoreHandle(PasswordStoreType); - -impl PasswordStoreHandle { - /// Constructs the opposite end of `self` for the password storage handle. - #[inline] - pub async fn into_channel(self) -> (Receiver, Sender) { - let (password, receiver) = channel(1); - let (sender, retry) = channel(1); - *self.0.lock().await = Some(PasswordStoreChannel { password, retry }); - (receiver, sender) - } -} - -/// Password Storage -#[derive(Default)] -pub struct PasswordStore(PasswordStoreType); - -impl PasswordStore { - /// Returns a handle for setting up a [`PasswordStore`]. - #[inline] - pub fn handle(&self) -> PasswordStoreHandle { - PasswordStoreHandle(self.0.clone()) - } - - /// Loads the password store with `password`, returning `true` if the password was correct. - #[inline] - pub async fn load(&self, password: SecretString) -> bool { - if let Some(store) = &mut *self.0.lock().await { - let _ = store.password.send(Password::from_known(password)).await; - store.retry.recv().await.unwrap() - } else { - false - } - } +/// Password Store +pub type PasswordStore = Store; - /// Loads the password with `password`, not requesting a retry. - #[inline] - pub async fn load_exact(&self, password: SecretString) { - if let Some(store) = &mut *self.0.lock().await { - let _ = store.password.send(Password::from_known(password)).await; - } - } - - /// Clears the password from the store. - #[inline] - pub async fn clear(&self) { - if let Some(store) = &mut *self.0.lock().await { - let _ = store.password.send(Password::from_unknown()).await; - } - } -} +/// Server Store +pub type ServerStore = Store>; /// Sends the current `password` into storage from the UI. #[tauri::command] @@ -214,16 +142,39 @@ async fn send_password( password_store: State<'_, PasswordStore>, password: String, ) -> Result { - Ok(password_store.load(Secret::new(password)).await) + if let Some(store) = &mut *password_store.lock().await { + Ok(store.load(Secret::new(password)).await) + } else { + Ok(false) + } } /// Stops the server from prompting for the password. #[tauri::command] async fn stop_password_prompt(password_store: State<'_, PasswordStore>) -> Result<(), ()> { - password_store.clear().await; + if let Some(store) = &mut *password_store.lock().await { + store.clear().await; + } Ok(()) } +/// Returns the window with the given `label` from `app`. +/// +/// # Panics +/// +/// This function panics if the window with the given `label` was unreachable. +#[inline] +pub fn window(app: &M, label: &str) -> Window +where + R: Runtime, + M: Manager, +{ + match app.get_window(label) { + Some(window) => window, + _ => panic!("Unable to get {:?} window handler.", label), + } +} + /// Runs the main Tauri application. fn main() { let config = @@ -240,21 +191,27 @@ fn main() { .on_system_tray_event(move |app, event| { if let SystemTrayEvent::MenuItemClick { id, .. } = event { match id.as_str() { - "about" => app.get_window("about").unwrap().show().unwrap(), + "about" => window(app, "about").show().expect("Unable to show window."), "exit" => app.exit(0), _ => {} } } }) .manage(PasswordStore::default()) - .manage(config) + .manage(ServerStore::default()) .setup(|app| { - let window = app.get_window("main").unwrap(); - let config = app.state::().inner().clone(); - let password_store = app.state::().handle(); + let window = window(app, "main"); + let password_store = app.state::().inner().clone(); + let server_store = app.state::().inner().clone(); spawn(async move { - let (password, retry) = password_store.into_channel().await; - service::start(config, User::new(window, password, retry)) + let (password_sender, password_receiver) = password_channel(); + password_store.set(password_sender).await; + let server = Server::build(config, User::new(window, password_receiver)) + .await + .expect("Unable to build manta-signer server."); + server_store.set(server.clone()).await; + server + .start() .await .expect("Unable to build manta-signer service."); }); @@ -271,7 +228,7 @@ fn main() { app.set_activation_policy(tauri::ActivationPolicy::Accessory); app.run(|app, event| match event { - RunEvent::Ready => app.get_window("about").unwrap().hide().unwrap(), + RunEvent::Ready => window(app, "about").hide().expect("Unable to hind window."), RunEvent::WindowEvent { label, event: WindowEvent::CloseRequested { api, .. }, @@ -279,7 +236,7 @@ fn main() { } => { api.prevent_close(); match label.as_str() { - "about" => app.get_window(&label).unwrap().hide().unwrap(), + "about" => window(app, "about").hide().expect("Unable to hide window."), "main" => app.exit(0), _ => unreachable!("There are no other windows."), }