From 3338161fc10c13a025c8e45e84903a6f3acd16da Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Thu, 24 Oct 2024 10:36:20 -0300 Subject: [PATCH] add frost-client {coordinator,participant} (#328) --- Cargo.lock | 67 ++++++------ coordinator/Cargo.toml | 3 +- coordinator/src/args.rs | 141 +++++++++++++++---------- coordinator/src/cli.rs | 13 ++- coordinator/src/comms/http.rs | 46 ++++---- coordinator/src/comms/socket.rs | 4 +- coordinator/src/step_3.rs | 3 +- dkg/Cargo.toml | 2 +- frost-client/Cargo.toml | 6 +- frost-client/src/args.rs | 50 +++++++++ frost-client/src/ciphersuite_helper.rs | 5 + frost-client/src/config.rs | 9 ++ frost-client/src/coordinator.rs | 95 +++++++++++++++++ frost-client/src/main.rs | 5 + frost-client/src/participant.rs | 87 +++++++++++++++ frost-client/src/trusted_dealer.rs | 7 +- participant/Cargo.toml | 3 +- participant/src/args.rs | 98 +++++++++++++++++ participant/src/cli.rs | 31 ++++-- participant/src/comms/http.rs | 54 +++++----- participant/src/comms/socket.rs | 4 +- server/Cargo.toml | 2 +- tests/Cargo.toml | 2 +- trusted-dealer/Cargo.toml | 2 +- 24 files changed, 578 insertions(+), 161 deletions(-) create mode 100644 frost-client/src/coordinator.rs create mode 100644 frost-client/src/participant.rs diff --git a/Cargo.lock b/Cargo.lock index f744af31..94a18386 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -324,9 +324,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -604,8 +604,9 @@ dependencies = [ "itertools", "message-io", "rand", - "reddsa 0.5.1 (git+https://github.com/ZcashFoundation/reddsa.git?rev=4d8c4bb337231e6e89117334d7c61dada589a953)", + "reddsa", "reqwest", + "rpassword", "serde_json", "serdect", "server", @@ -845,7 +846,7 @@ dependencies = [ "itertools", "pipe", "rand", - "reddsa 0.5.1 (git+https://github.com/ZcashFoundation/reddsa.git?rev=4d8c4bb337231e6e89117334d7c61dada589a953)", + "reddsa", "serde_json", "thiserror", ] @@ -1018,15 +1019,18 @@ version = "0.1.0" dependencies = [ "bech32", "clap", + "coordinator", "directories", "dirs", "eyre", "frost-core", "frost-ed25519", + "frost-rerandomized", "hex", + "participant", "postcard", "rand", - "reddsa 0.5.1 (git+https://github.com/ZcashFoundation/reddsa.git?rev=4d8c4bb337231e6e89117334d7c61dada589a953)", + "reddsa", "reqwest", "rpassword", "serde", @@ -1034,6 +1038,7 @@ dependencies = [ "serdect", "server", "snow", + "stable-eyre", "tempfile", "tokio", "toml", @@ -1203,9 +1208,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "group" @@ -1869,9 +1874,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.2" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -1988,8 +1993,9 @@ dependencies = [ "hex", "message-io", "rand", - "reddsa 0.5.1 (git+https://github.com/ZcashFoundation/reddsa.git?rev=4d8c4bb337231e6e89117334d7c61dada589a953)", + "reddsa", "reqwest", + "rpassword", "serde_json", "serdect", "server", @@ -2239,25 +2245,7 @@ dependencies = [ [[package]] name = "reddsa" version = "0.5.1" -source = "git+https://github.com/ZcashFoundation/reddsa.git?rev=4d8c4bb337231e6e89117334d7c61dada589a953#4d8c4bb337231e6e89117334d7c61dada589a953" -dependencies = [ - "blake2b_simd", - "byteorder", - "frost-rerandomized", - "group", - "hex", - "jubjub", - "pasta_curves", - "rand_core", - "serde", - "thiserror", - "zeroize", -] - -[[package]] -name = "reddsa" -version = "0.5.1" -source = "git+https://github.com/ZcashFoundation/reddsa.git?rev=56a31af7099b95737031ef6cf251939ed99627c0#56a31af7099b95737031ef6cf251939ed99627c0" +source = "git+https://github.com/ZcashFoundation/reddsa.git?rev=ed49e9ca0699a6450f6d4a9fe62ff168f5ea1ead#ed49e9ca0699a6450f6d4a9fe62ff168f5ea1ead" dependencies = [ "blake2b_simd", "byteorder", @@ -2684,7 +2672,7 @@ dependencies = [ "frost-rerandomized", "password-auth", "rand", - "reddsa 0.5.1 (git+https://github.com/ZcashFoundation/reddsa.git?rev=4d8c4bb337231e6e89117334d7c61dada589a953)", + "reddsa", "regex", "reqwest", "serde", @@ -3024,6 +3012,17 @@ dependencies = [ "uuid", ] +[[package]] +name = "stable-eyre" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556fec8c2da34c70b75f16d88df8a8cd7e652e567ff097b7e9df0022c8695cc4" +dependencies = [ + "backtrace", + "eyre", + "indenter", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -3169,7 +3168,7 @@ dependencies = [ "hex", "participant", "rand", - "reddsa 0.5.1 (git+https://github.com/ZcashFoundation/reddsa.git?rev=4d8c4bb337231e6e89117334d7c61dada589a953)", + "reddsa", "serde_json", "server", "tokio", @@ -3499,7 +3498,7 @@ dependencies = [ "hex", "itertools", "rand", - "reddsa 0.5.1 (git+https://github.com/ZcashFoundation/reddsa.git?rev=56a31af7099b95737031ef6cf251939ed99627c0)", + "reddsa", "serde_json", "thiserror", ] diff --git a/coordinator/Cargo.toml b/coordinator/Cargo.toml index d74725fc..9b6ac06a 100644 --- a/coordinator/Cargo.toml +++ b/coordinator/Cargo.toml @@ -12,7 +12,7 @@ eyre = "0.6.12" frost-core = { version = "2.0.0-rc.0", features = ["serde"] } frost-rerandomized = { version = "2.0.0-rc.0", features = ["serde"] } frost-ed25519 = { version = "2.0.0-rc.0", features = ["serde"] } -reddsa = { git = "https://github.com/ZcashFoundation/reddsa.git", rev = "4d8c4bb337231e6e89117334d7c61dada589a953", features = ["frost", "serde"] } +reddsa = { git = "https://github.com/ZcashFoundation/reddsa.git", rev = "ed49e9ca0699a6450f6d4a9fe62ff168f5ea1ead", features = ["frost", "serde"] } hex = { version = "0.4", features = ["serde"] } thiserror = "1.0" rand = "0.8" @@ -25,6 +25,7 @@ reqwest = { version = "0.12.8", features = ["json"] } server = { path = "../server" } tokio = { version = "1", features = ["full"] } message-io = "0.18" +rpassword = "7.3.1" [features] default = [] diff --git a/coordinator/src/args.rs b/coordinator/src/args.rs index 135b4c32..a69b73ba 100644 --- a/coordinator/src/args.rs +++ b/coordinator/src/args.rs @@ -76,19 +76,19 @@ pub struct Args { #[arg(short = 's', long, default_value = "")] pub signature: String, - /// IP to bind to, if using online comms + /// IP to bind to, if using socket comms. + /// IP to connect to, if using HTTP mode. #[arg(short, long, default_value = "0.0.0.0")] pub ip: String, - /// Port to bind to, if using online comms + /// Port to bind to, if using socket comms. + /// Port to connect to, if using HTTP mode. #[arg(short, long, default_value_t = 2744)] pub port: u16, } #[derive(Clone, Debug)] pub struct ProcessedArgs { - pub ciphersuite: String, - /// CLI mode. If enabled, it will prompt for inputs from stdin /// and print values to stdout, ignoring other flags. /// If false, socket communication is enabled. @@ -104,6 +104,10 @@ pub struct ProcessedArgs { /// The (actual) password to use in HTTP mode. pub password: String, + /// The authentication token to use in HTTP mode; if not specified + /// it will login with `password` + pub authentication_token: Option, + /// The comma-separated usernames of the signers to use in HTTP mode. /// If HTTP mode is enabled and this is empty, then the session ID /// will be printed and will have to be shared manually. @@ -125,10 +129,12 @@ pub struct ProcessedArgs { /// human-readable hex-string is printed to stdout. pub signature: String, - /// IP to bind to, if using online comms + /// IP to bind to, if using socket comms. + /// IP to connect to, if using HTTP mode. pub ip: String, - /// Port to bind to, if using online comms + /// Port to bind to, if using socket comms. + /// Port to connect to, if using HTTP mode. pub port: u16, } @@ -142,7 +148,7 @@ impl ProcessedArgs { output: &mut dyn Write, ) -> Result> { let password = if args.http { - env::var(&args.password).map_err(|_| eyre!("The password argument must specify the name of a environment variable containing the password"))? + read_password(&args.password)? } else { String::new() }; @@ -168,58 +174,12 @@ impl ProcessedArgs { let public_key_package: PublicKeyPackage = serde_json::from_str(&out)?; - let messages = if args.message.is_empty() { - writeln!(output, "The message to be signed (hex encoded)")?; - let mut msg = String::new(); - input.read_line(&mut msg)?; - vec![hex::decode(msg.trim())?] - } else { - args.message - .iter() - .map(|filename| { - let msg = if filename == "-" || filename.is_empty() { - writeln!(output, "The message to be signed (hex encoded)")?; - let mut msg = String::new(); - input.read_line(&mut msg)?; - hex::decode(msg.trim())? - } else { - eprintln!("Reading message from {}...", &filename); - fs::read(filename)? - }; - Ok(msg) - }) - .collect::>>()? - }; + let messages = read_messages(&args.message, output, input)?; println!("Processing randomizer {:?}", args.randomizer); - let randomizers = if args.ciphersuite == "redpallas" { - if args.randomizer.is_empty() { - Vec::new() - } else { - args.randomizer - .iter() - .map(|filename| { - let randomizer = if filename == "-" || filename.is_empty() { - writeln!(output, "Enter the randomizer (hex string):")?; - let mut randomizer = String::new(); - input.read_line(&mut randomizer)?; - let bytes = hex::decode(randomizer.trim())?; - frost_rerandomized::Randomizer::deserialize(&bytes)? - } else { - eprintln!("Reading randomizer from {}...", &filename); - let bytes = fs::read(filename)?; - frost_rerandomized::Randomizer::deserialize(&bytes)? - }; - Ok(randomizer) - }) - .collect::>>()? - } - } else { - Vec::new() - }; + let randomizers = read_randomizers(&args.randomizer, output, input)?; Ok(ProcessedArgs { - ciphersuite: args.ciphersuite.clone(), cli: args.cli, http: args.http, username: args.username.clone(), @@ -232,6 +192,77 @@ impl ProcessedArgs { signature: args.signature.clone(), ip: args.ip.clone(), port: args.port, + authentication_token: None, }) } } + +pub fn read_password(password_env_name: &str) -> Result> { + if password_env_name.is_empty() { + Ok( + rpassword::prompt_password("Password: ") + .map_err(|_| eyre!("Error reading password"))?, + ) + } else { + Ok(env::var(password_env_name).map_err(|_| eyre!("The password argument must specify the name of a environment variable containing the password"))?) + } +} + +pub fn read_messages( + message_paths: &[String], + output: &mut dyn Write, + input: &mut dyn BufRead, +) -> Result>, Box> { + let messages = if message_paths.is_empty() { + writeln!(output, "The message to be signed (hex encoded)")?; + let mut msg = String::new(); + input.read_line(&mut msg)?; + vec![hex::decode(msg.trim())?] + } else { + message_paths + .iter() + .map(|filename| { + let msg = if *filename == "-" || filename.is_empty() { + writeln!(output, "The message to be signed (hex encoded)")?; + let mut msg = String::new(); + input.read_line(&mut msg)?; + hex::decode(msg.trim())? + } else { + eprintln!("Reading message from {}...", &filename); + fs::read(filename)? + }; + Ok(msg) + }) + .collect::>>()? + }; + Ok(messages) +} + +pub fn read_randomizers( + randomizer_paths: &[String], + output: &mut dyn Write, + input: &mut dyn BufRead, +) -> Result>, Box> { + let randomizers = if randomizer_paths.is_empty() { + Vec::new() + } else { + randomizer_paths + .iter() + .map(|filename| { + let randomizer = if filename == "-" || filename.is_empty() { + writeln!(output, "Enter the randomizer (hex string):")?; + let mut randomizer = String::new(); + input.read_line(&mut randomizer)?; + let bytes = hex::decode(randomizer.trim())?; + frost_rerandomized::Randomizer::deserialize(&bytes)? + } else { + eprintln!("Reading randomizer from {}...", &filename); + let bytes = fs::read(filename)?; + frost_rerandomized::Randomizer::deserialize(&bytes)? + }; + Ok(randomizer) + }) + .collect::>>()? + }; + Ok(randomizers) +} diff --git a/coordinator/src/cli.rs b/coordinator/src/cli.rs index 7469640c..671044b6 100644 --- a/coordinator/src/cli.rs +++ b/coordinator/src/cli.rs @@ -18,15 +18,22 @@ pub async fn cli( logger: &mut impl Write, ) -> Result<(), Box> { let pargs = ProcessedArgs::::new(args, reader, logger)?; + cli_for_processed_args(pargs, reader, logger).await +} +pub async fn cli_for_processed_args( + pargs: ProcessedArgs, + reader: &mut impl BufRead, + logger: &mut impl Write, +) -> Result<(), Box> { writeln!(logger, "\n=== STEP 1: CHOOSE PARTICIPANTS ===\n")?; - let mut comms: Box> = if args.cli { + let mut comms: Box> = if pargs.cli { Box::new(CLIComms::new()) - } else if args.http { + } else if pargs.http { Box::new(HTTPComms::new(&pargs)?) } else { - Box::new(SocketComms::new(args)) + Box::new(SocketComms::new(&pargs)) }; let participants_config = step_1(&pargs, &mut *comms, reader, logger).await?; diff --git a/coordinator/src/comms/http.rs b/coordinator/src/comms/http.rs index 8b41a4eb..6426a325 100644 --- a/coordinator/src/comms/http.rs +++ b/coordinator/src/comms/http.rs @@ -267,6 +267,7 @@ pub struct HTTPComms { args: ProcessedArgs, state: SessionState, usernames: HashMap>, + should_logout: bool, _phantom: PhantomData, } @@ -277,11 +278,12 @@ impl HTTPComms { client, host_port: format!("http://{}:{}", args.ip, args.port), session_id: None, - access_token: String::new(), + access_token: args.authentication_token.clone().unwrap_or_default(), num_signers: 0, args: args.clone(), state: SessionState::new(args.messages.len(), args.num_signers as usize), usernames: Default::default(), + should_logout: args.authentication_token.is_none(), _phantom: Default::default(), }) } @@ -296,19 +298,21 @@ impl Comms for HTTPComms { _pub_key_package: &PublicKeyPackage, num_signers: u16, ) -> Result, SigningCommitments>, Box> { - self.access_token = self - .client - .post(format!("{}/login", self.host_port)) - .json(&server::LoginArgs { - username: self.args.username.clone(), - password: self.args.password.clone(), - }) - .send() - .await? - .json::() - .await? - .access_token - .to_string(); + if self.access_token.is_empty() { + self.access_token = self + .client + .post(format!("{}/login", self.host_port)) + .json(&server::LoginArgs { + username: self.args.username.clone(), + password: self.args.password.clone(), + }) + .send() + .await? + .json::() + .await? + .access_token + .to_string(); + } let r = self .client @@ -428,12 +432,14 @@ impl Comms for HTTPComms { .send() .await?; - let _r = self - .client - .post(format!("{}/logout", self.host_port)) - .bearer_auth(&self.access_token) - .send() - .await?; + if self.should_logout { + let _r = self + .client + .post(format!("{}/logout", self.host_port)) + .bearer_auth(&self.access_token) + .send() + .await?; + } let signature_shares = self.state.signature_shares()?; diff --git a/coordinator/src/comms/socket.rs b/coordinator/src/comms/socket.rs index c8ad24b6..a33e5fd0 100644 --- a/coordinator/src/comms/socket.rs +++ b/coordinator/src/comms/socket.rs @@ -26,7 +26,7 @@ use std::{ }; use super::{Comms, Message}; -use crate::args::Args; +use crate::args::ProcessedArgs; pub struct SocketComms { input_rx: Receiver<(Endpoint, Vec)>, @@ -36,7 +36,7 @@ pub struct SocketComms { } impl SocketComms { - pub fn new(args: &Args) -> Self { + pub fn new(args: &ProcessedArgs) -> Self { let (handler, listener) = node::split::<()>(); let addr = format!("{}:{}", args.ip, args.port); let (tx, rx) = mpsc::channel(2000); diff --git a/coordinator/src/step_3.rs b/coordinator/src/step_3.rs index 7acd3f61..9b1db8dc 100644 --- a/coordinator/src/step_3.rs +++ b/coordinator/src/step_3.rs @@ -3,6 +3,7 @@ use frost_core::{self as frost, Ciphersuite}; use frost::{Signature, SigningPackage}; use frost_rerandomized::{RandomizedCiphersuite, Randomizer}; use rand::thread_rng; +use reddsa::frost::redpallas::PallasBlake2b512; use std::{ fs, @@ -38,7 +39,7 @@ async fn request_inputs_signature_shares( signing_package: &SigningPackage, ) -> Result, Box> { // TODO: support multiple - let randomizer = if args.randomizers.is_empty() && args.ciphersuite == "redpallas" { + let randomizer = if args.randomizers.is_empty() && C::ID == PallasBlake2b512::ID { let rng = thread_rng(); Some(Randomizer::new(rng, signing_package)?) } else if args.randomizers.is_empty() { diff --git a/dkg/Cargo.toml b/dkg/Cargo.toml index 2c703681..f7138fb8 100644 --- a/dkg/Cargo.toml +++ b/dkg/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" eyre = "0.6.12" frost-core = { version = "2.0.0-rc.0", features = ["serde"] } frost-ed25519 = { version = "2.0.0-rc.0", features = ["serde"] } -reddsa = { git = "https://github.com/ZcashFoundation/reddsa.git", rev = "4d8c4bb337231e6e89117334d7c61dada589a953", features = ["frost"] } +reddsa = { git = "https://github.com/ZcashFoundation/reddsa.git", rev = "ed49e9ca0699a6450f6d4a9fe62ff168f5ea1ead", features = ["frost"] } clap = { version = "4.5.20", features = ["derive"] } hex = { version = "0.4", features = ["serde"] } thiserror = "1.0" diff --git a/frost-client/Cargo.toml b/frost-client/Cargo.toml index 33209b7c..d114b07c 100644 --- a/frost-client/Cargo.toml +++ b/frost-client/Cargo.toml @@ -11,6 +11,8 @@ snow = "0.9.6" toml = "0.8.19" server = { path = "../server" } trusted-dealer = { path = "../trusted-dealer" } +coordinator = { path = "../coordinator" } +participant = { path = "../participant" } eyre = "0.6.12" rpassword = "7.3.1" directories = "5.0.1" @@ -24,5 +26,7 @@ tempfile = "3.13.0" serde_json = "1.0" frost-core = { version = "2.0.0-rc.0", features = ["serde"] } frost-ed25519 = { version = "2.0.0-rc.0", features = ["serde"] } -reddsa = { git = "https://github.com/ZcashFoundation/reddsa.git", rev = "4d8c4bb337231e6e89117334d7c61dada589a953", features = ["frost"] } +frost-rerandomized = { version = "2.0.0-rc.0", features = ["serde"] } +reddsa = { git = "https://github.com/ZcashFoundation/reddsa.git", rev = "ed49e9ca0699a6450f6d4a9fe62ff168f5ea1ead", features = ["frost"] } rand = "0.8" +stable-eyre = "0.2" diff --git a/frost-client/src/args.rs b/frost-client/src/args.rs index b962122a..57776208 100644 --- a/frost-client/src/args.rs +++ b/frost-client/src/args.rs @@ -102,4 +102,54 @@ pub(crate) enum Command { #[arg(short, long)] config: Option, }, + Coordinator { + /// The path to the config file to manage. If not specified, it uses + /// $HOME/.local/frost/credentials.toml + #[arg(short, long)] + config: Option, + /// The server URL to use. You can use a substring of the URL. It will + /// use the username previously logged in via the `login` subcommand for + /// the given server. + #[arg(short, long)] + server_url: String, + /// The group to use, identified by the group public key (use `groups` + /// to list) + #[arg(short, long)] + group: String, + /// The comma-separated usernames of the signers to use. + #[arg(short = 'S', long, value_delimiter = ',')] + signers: Vec, + /// The messages to sign. Each instance can be a file with the raw message, + /// "" or "-". If "" or "-" is specified, then it will be read from standard + /// input as a hex string. If none are passed, a single one will be read + /// from standard input as a hex string. + #[arg(short = 'm', long)] + message: Vec, + /// The randomizers to use. Each instance can be a file with the raw + /// randomizer, "" or "-". If "" or "-" is specified, then it will be read + /// from standard input as a hex string. If none are passed, random ones + /// will be generated. If one or more are passed, the number should match + /// the `message` parameter. + #[arg(short = 'r', long)] + randomizer: Vec, + /// Where to write the generated raw bytes signature. If "-", the + /// human-readable hex-string is printed to stdout. + #[arg(short = 'o', long, default_value = "")] + signature: String, + }, + Participant { + /// The path to the config file to manage. If not specified, it uses + /// $HOME/.local/frost/credentials.toml + #[arg(short, long)] + config: Option, + /// The server URL to use. You can use a substring of the URL. It will + /// use the username previously logged in via the `login` subcommand for + /// the given server. + #[arg(short, long)] + server_url: String, + /// The group to use, identified by the group public key (use `groups` + /// to list) + #[arg(short, long)] + group: String, + }, } diff --git a/frost-client/src/ciphersuite_helper.rs b/frost-client/src/ciphersuite_helper.rs index de665066..f9b93389 100644 --- a/frost-client/src/ciphersuite_helper.rs +++ b/frost-client/src/ciphersuite_helper.rs @@ -6,6 +6,7 @@ use frost_core::{ Ciphersuite, }; use frost_ed25519::Ed25519Sha512; +use reddsa::frost::redpallas::PallasBlake2b512; /// Additional information about a group, derived from the key packages. #[derive(Debug, Clone)] @@ -46,6 +47,10 @@ pub(crate) fn ciphersuite_helper( ) -> Result, Box> { if ciphersuite_id == Ed25519Sha512::ID { return Ok(Box::new(CiphersuiteHelperImpl::::default())); + } else if ciphersuite_id == PallasBlake2b512::ID { + return Ok(Box::new( + CiphersuiteHelperImpl::::default(), + )); } Err(eyre!("invalid ciphersuite ID").into()) } diff --git a/frost-client/src/config.rs b/frost-client/src/config.rs index 49c726dd..b7f80f17 100644 --- a/frost-client/src/config.rs +++ b/frost-client/src/config.rs @@ -41,6 +41,15 @@ impl Config { .cloned() .ok_or_eyre("contact not found")?) } + + pub fn username_by_server_url(&self, server_url: &str) -> Result> { + Ok(self + .registry + .get(server_url) + .ok_or_eyre("Not logged in in the giver server")? + .username + .clone()) + } } /// A registry entry. Note that the server URL is not in the struct; diff --git a/frost-client/src/coordinator.rs b/frost-client/src/coordinator.rs new file mode 100644 index 00000000..9fb5befc --- /dev/null +++ b/frost-client/src/coordinator.rs @@ -0,0 +1,95 @@ +use std::error::Error; + +use coordinator::cli::cli_for_processed_args; +use eyre::eyre; +use eyre::Context; +use eyre::OptionExt; + +use frost_core::keys::PublicKeyPackage; +use frost_core::Ciphersuite; +use frost_ed25519::Ed25519Sha512; +use frost_rerandomized::RandomizedCiphersuite; +use reddsa::frost::redpallas::PallasBlake2b512; +use reqwest::Url; + +use crate::{args::Command, config::Config}; + +pub(crate) async fn run(args: &Command) -> Result<(), Box> { + let Command::Coordinator { config, group, .. } = (*args).clone() else { + panic!("invalid Command"); + }; + + let config = Config::read(config)?; + + let group = config.group.get(&group).ok_or_eyre("Group not found")?; + + if group.ciphersuite == Ed25519Sha512::ID { + run_for_ciphersuite::(args).await + } else if group.ciphersuite == PallasBlake2b512::ID { + run_for_ciphersuite::(args).await + } else { + Err(eyre!("unsupported ciphersuite").into()) + } +} + +pub(crate) async fn run_for_ciphersuite( + args: &Command, +) -> Result<(), Box> { + let Command::Coordinator { + config, + server_url, + group, + signers, + message, + randomizer, + signature, + } = (*args).clone() + else { + panic!("invalid Command"); + }; + + let config = Config::read(config)?; + + let group = config.group.get(&group).ok_or_eyre("Group not found")?; + + let public_key_package: PublicKeyPackage = postcard::from_bytes(&group.public_key_package)?; + + let mut input = Box::new(std::io::stdin().lock()); + let mut output = std::io::stdout(); + + let server_url_parsed = + Url::parse(&format!("http://{}", server_url)).wrap_err("error parsing server-url")?; + + let registry = config + .registry + .get(&server_url) + .ok_or_eyre("Not registered in the given server")?; + + let pargs = coordinator::args::ProcessedArgs { + cli: false, + http: true, + username: registry.username.clone(), + password: String::new(), + signers: signers.clone(), + num_signers: signers.len() as u16, + public_key_package, + messages: coordinator::args::read_messages(&message, &mut output, &mut input)?, + randomizers: coordinator::args::read_randomizers(&randomizer, &mut output, &mut input)?, + signature, + ip: server_url_parsed + .host_str() + .ok_or_eyre("host missing in URL")? + .to_owned(), + port: server_url_parsed.port().unwrap_or(2744), + authentication_token: Some( + registry + .token + .clone() + .ok_or_eyre("Not logged in in the given server")?, + ), + }; + + cli_for_processed_args(pargs, &mut input, &mut output).await?; + + Ok(()) +} diff --git a/frost-client/src/main.rs b/frost-client/src/main.rs index 5998a4c9..7fd3a2c5 100644 --- a/frost-client/src/main.rs +++ b/frost-client/src/main.rs @@ -2,9 +2,11 @@ pub mod args; pub mod ciphersuite_helper; pub mod config; pub mod contact; +pub mod coordinator; pub mod group; pub mod init; pub mod login; +pub mod participant; pub mod trusted_dealer; pub mod write_atomic; @@ -15,6 +17,7 @@ use clap::Parser; #[tokio::main] async fn main() -> Result<(), Box> { + stable_eyre::install()?; let args = Args::parse(); match args.command { @@ -25,6 +28,8 @@ async fn main() -> Result<(), Box> { Command::Contacts { .. } => contact::list(&args.command), Command::Groups { .. } => group::list(&args.command), Command::TrustedDealer { .. } => trusted_dealer::trusted_dealer(&args.command), + Command::Coordinator { .. } => crate::coordinator::run(&args.command).await, + Command::Participant { .. } => crate::participant::run(&args.command).await, }?; Ok(()) diff --git a/frost-client/src/participant.rs b/frost-client/src/participant.rs new file mode 100644 index 00000000..ac1e0cbf --- /dev/null +++ b/frost-client/src/participant.rs @@ -0,0 +1,87 @@ +use std::error::Error; + +use eyre::eyre; +use eyre::Context; +use eyre::OptionExt; +use reddsa::frost::redpallas::PallasBlake2b512; +use reqwest::Url; + +use frost_core::keys::KeyPackage; +use frost_core::Ciphersuite; +use frost_ed25519::Ed25519Sha512; +use frost_rerandomized::RandomizedCiphersuite; + +use crate::{args::Command, config::Config}; +use participant::cli::cli_for_processed_args; + +pub(crate) async fn run(args: &Command) -> Result<(), Box> { + let Command::Participant { config, group, .. } = (*args).clone() else { + panic!("invalid Command"); + }; + + let config = Config::read(config)?; + + let group = config.group.get(&group).ok_or_eyre("Group not found")?; + + if group.ciphersuite == Ed25519Sha512::ID { + run_for_ciphersuite::(args).await + } else if group.ciphersuite == PallasBlake2b512::ID { + run_for_ciphersuite::(args).await + } else { + Err(eyre!("unsupported ciphersuite").into()) + } +} + +pub(crate) async fn run_for_ciphersuite( + args: &Command, +) -> Result<(), Box> { + let Command::Participant { + config, + server_url, + group, + } = (*args).clone() + else { + panic!("invalid Command"); + }; + + let config = Config::read(config)?; + + let group = config.group.get(&group).ok_or_eyre("Group not found")?; + + let key_package: KeyPackage = postcard::from_bytes(&group.key_package)?; + + let mut input = Box::new(std::io::stdin().lock()); + let mut output = std::io::stdout(); + + let server_url_parsed = + Url::parse(&format!("http://{}", server_url)).wrap_err("error parsing server-url")?; + + let registry = config + .registry + .get(&server_url) + .ok_or_eyre("Not registered in the given server")?; + + let pargs = participant::args::ProcessedArgs { + cli: false, + http: true, + username: registry.username.clone(), + password: String::new(), + key_package, + ip: server_url_parsed + .host_str() + .ok_or_eyre("host missing in URL")? + .to_owned(), + port: server_url_parsed.port().unwrap_or(2744), + authentication_token: Some( + registry + .token + .clone() + .ok_or_eyre("Not logged in in the given server")?, + ), + session_id: String::new(), + }; + + cli_for_processed_args(pargs, &mut input, &mut output).await?; + + Ok(()) +} diff --git a/frost-client/src/trusted_dealer.rs b/frost-client/src/trusted_dealer.rs index dd7dcd5e..6bf92e0f 100644 --- a/frost-client/src/trusted_dealer.rs +++ b/frost-client/src/trusted_dealer.rs @@ -1,9 +1,10 @@ -use eyre::{eyre, OptionExt}; use std::{collections::BTreeMap, error::Error}; +use eyre::{eyre, OptionExt}; +use rand::thread_rng; + use frost_core::{keys::KeyPackage, Ciphersuite}; use frost_ed25519::Ed25519Sha512; -use rand::thread_rng; use trusted_dealer::MaybeIntoEvenY; use crate::{ @@ -19,6 +20,8 @@ pub(crate) fn trusted_dealer(args: &Command) -> Result<(), Box> { if ciphersuite == "ed25519" { trusted_dealer_for_ciphersuite::(args) + } else if ciphersuite == "redpallas" { + trusted_dealer_for_ciphersuite::(args) } else { Err(eyre!("unsupported ciphersuite").into()) } diff --git a/participant/Cargo.toml b/participant/Cargo.toml index 38ac067f..f6e016c8 100644 --- a/participant/Cargo.toml +++ b/participant/Cargo.toml @@ -11,7 +11,7 @@ derivative = "2.2.0" frost-core = { version = "2.0.0-rc.0", features = ["serde"] } frost-rerandomized = { version = "2.0.0-rc.0", features = ["serde"] } frost-ed25519 = { version = "2.0.0-rc.0", features = ["serde"] } -reddsa = { git = "https://github.com/ZcashFoundation/reddsa.git", rev = "4d8c4bb337231e6e89117334d7c61dada589a953", features = ["frost"] } +reddsa = { git = "https://github.com/ZcashFoundation/reddsa.git", rev = "ed49e9ca0699a6450f6d4a9fe62ff168f5ea1ead", features = ["frost"] } hex = "0.4" rand = "0.8" eyre = "0.6.12" @@ -23,6 +23,7 @@ tokio = { version = "1", features = ["full"] } message-io = "0.18" reqwest = { version = "0.12.8", features = ["json"] } server = { path = "../server" } +rpassword = "7.3.1" [features] default = [] diff --git a/participant/src/args.rs b/participant/src/args.rs index 8bfc9561..961f149a 100644 --- a/participant/src/args.rs +++ b/participant/src/args.rs @@ -1,4 +1,17 @@ +use std::{ + env, + error::Error, + io::{BufRead, Write}, +}; + use clap::Parser; +use eyre::eyre; +use frost_core::{ + keys::{KeyPackage, SecretShare}, + Ciphersuite, +}; + +use crate::input::read_from_file_or_stdin; #[derive(Parser, Debug, Default)] #[command(author, version, about, long_about = None)] @@ -44,3 +57,88 @@ pub struct Args { #[arg(short, long, default_value = "")] pub session_id: String, } + +#[derive(Clone, Debug)] +pub struct ProcessedArgs { + /// CLI mode. If enabled, it will prompt for inputs from stdin + /// and print values to stdout, ignoring other flags. + /// If false, socket communication is enabled. + pub cli: bool, + + /// HTTP mode. If enabled, it will use HTTP communication with a + /// FROST server. + pub http: bool, + + /// The username to use in HTTP mode. + pub username: String, + + /// The (actual) password to use in HTTP mode. + pub password: String, + + /// The authentication token to use in HTTP mode; if not specified + /// it will login with `password` + pub authentication_token: Option, + + /// Key package to use. + pub key_package: KeyPackage, + + /// IP to bind to, if using socket comms. + /// IP to connect to, if using HTTP mode. + pub ip: String, + + /// Port to bind to, if using socket comms. + /// Port to connect to, if using HTTP mode. + pub port: u16, + + /// Optional Session ID + pub session_id: String, +} + +impl ProcessedArgs { + /// Create a ProcessedArgs from a Args. + /// + /// Validates inputs and reads/parses arguments. + pub fn new( + args: &Args, + input: &mut dyn BufRead, + output: &mut dyn Write, + ) -> Result> { + let password = if args.http { + read_password(&args.password)? + } else { + String::new() + }; + + let bytes = read_from_file_or_stdin(input, output, "key package", &args.key_package)?; + + let key_package = if let Ok(secret_share) = serde_json::from_str::>(&bytes) { + KeyPackage::try_from(secret_share)? + } else { + // TODO: Improve error + serde_json::from_str::>(&bytes)? + }; + + Ok(ProcessedArgs { + cli: args.cli, + http: args.http, + username: args.username.clone(), + password, + key_package, + ip: args.ip.clone(), + port: args.port, + authentication_token: None, + session_id: args.session_id.clone(), + }) + } +} + +pub fn read_password(password_env_name: &str) -> Result> { + if password_env_name.is_empty() { + Ok( + rpassword::prompt_password("Password: ") + .map_err(|_| eyre!("Error reading password"))?, + ) + } else { + Ok(env::var(password_env_name).map_err(|_| eyre!("The password argument must specify the name of a environment variable containing the password"))?) + } +} diff --git a/participant/src/cli.rs b/participant/src/cli.rs index c6a9cf19..ae768749 100644 --- a/participant/src/cli.rs +++ b/participant/src/cli.rs @@ -1,4 +1,4 @@ -use crate::args::Args; +use crate::args::{Args, ProcessedArgs}; use crate::comms::cli::CLIComms; use crate::comms::http::HTTPComms; @@ -6,29 +6,40 @@ use crate::comms::socket::SocketComms; use crate::comms::Comms; -use crate::round1::{generate_nonces_and_commitments, print_values, request_inputs}; +use crate::round1::{generate_nonces_and_commitments, print_values}; use crate::round2::{generate_signature, print_values_round_2, round_2_request_inputs}; +use frost_core::Ciphersuite; +use frost_ed25519::Ed25519Sha512; use frost_rerandomized::RandomizedCiphersuite; use rand::thread_rng; +use reddsa::frost::redpallas::PallasBlake2b512; use std::io::{BufRead, Write}; pub async fn cli( args: &Args, + reader: &mut impl BufRead, + logger: &mut impl Write, +) -> Result<(), Box> { + let pargs = ProcessedArgs::::new(args, reader, logger)?; + cli_for_processed_args(pargs, reader, logger).await +} + +pub async fn cli_for_processed_args( + pargs: ProcessedArgs, input: &mut impl BufRead, logger: &mut impl Write, ) -> Result<(), Box> { - let mut comms: Box> = if args.cli { + let mut comms: Box> = if pargs.cli { Box::new(CLIComms::new()) - } else if args.http { - Box::new(HTTPComms::new(args)?) + } else if pargs.http { + Box::new(HTTPComms::new(&pargs)?) } else { - Box::new(SocketComms::new(args)) + Box::new(SocketComms::new(&pargs)) }; // Round 1 - let round_1_config = request_inputs(args, input, logger).await?; - let key_package = round_1_config.key_package; + let key_package = pargs.key_package; writeln!(logger, "Key Package succesfully created.")?; @@ -39,9 +50,9 @@ pub async fn cli( // Round 2 - Sign - let rerandomized = if args.ciphersuite == "ed25519" { + let rerandomized = if C::ID == Ed25519Sha512::ID { false - } else if args.ciphersuite == "redpallas" { + } else if C::ID == PallasBlake2b512::ID { true } else { panic!("invalid ciphersuite"); diff --git a/participant/src/comms/http.rs b/participant/src/comms/http.rs index b7b27695..1ac35e6e 100644 --- a/participant/src/comms/http.rs +++ b/participant/src/comms/http.rs @@ -1,7 +1,6 @@ //! HTTP implementation of the Comms trait. use std::{ - env, error::Error, io::{BufRead, Write}, marker::PhantomData, @@ -15,7 +14,7 @@ use frost_core::{ }; use super::Comms; -use crate::args::Args; +use crate::args::ProcessedArgs; pub struct HTTPComms { client: reqwest::Client, @@ -24,6 +23,7 @@ pub struct HTTPComms { username: String, password: String, access_token: String, + should_logout: bool, _phantom: PhantomData, } @@ -34,16 +34,16 @@ impl HTTPComms where C: Ciphersuite, { - pub fn new(args: &Args) -> Result> { + pub fn new(args: &ProcessedArgs) -> Result> { let client = reqwest::Client::new(); - let password = env::var(&args.password).map_err(|_| eyre!("The password argument must specify the name of a environment variable containing the password"))?; Ok(Self { client, host_port: format!("http://{}:{}", args.ip, args.port), session_id: Uuid::parse_str(&args.session_id).ok(), username: args.username.clone(), - password, - access_token: String::new(), + password: args.password.clone(), + access_token: args.authentication_token.clone().unwrap_or_default(), + should_logout: args.authentication_token.is_none(), _phantom: Default::default(), }) } @@ -68,19 +68,21 @@ where ), Box, > { - self.access_token = self - .client - .post(format!("{}/login", self.host_port)) - .json(&server::LoginArgs { - username: self.username.clone(), - password: self.password.clone(), - }) - .send() - .await? - .json::() - .await? - .access_token - .to_string(); + if self.access_token.is_empty() { + self.access_token = self + .client + .post(format!("{}/login", self.host_port)) + .json(&server::LoginArgs { + username: self.username.clone(), + password: self.password.clone(), + }) + .send() + .await? + .json::() + .await? + .access_token + .to_string(); + } let session_id = match self.session_id { Some(s) => s, @@ -191,12 +193,14 @@ where .send() .await?; - let _r = self - .client - .post(format!("{}/logout", self.host_port)) - .bearer_auth(&self.access_token) - .send() - .await?; + if self.should_logout { + let _r = self + .client + .post(format!("{}/logout", self.host_port)) + .bearer_auth(&self.access_token) + .send() + .await?; + } Ok(()) } diff --git a/participant/src/comms/socket.rs b/participant/src/comms/socket.rs index 0eb1080f..6d01531e 100644 --- a/participant/src/comms/socket.rs +++ b/participant/src/comms/socket.rs @@ -20,7 +20,7 @@ use std::{ }; use super::{Comms, Message}; -use crate::args::Args; +use crate::args::ProcessedArgs; pub struct SocketComms { input_rx: Receiver<(Endpoint, Vec)>, @@ -33,7 +33,7 @@ impl SocketComms where C: Ciphersuite, { - pub fn new(args: &Args) -> Self { + pub fn new(args: &ProcessedArgs) -> Self { let (handler, listener) = node::split::<()>(); let addr = format!("{}:{}", args.ip, args.port); let (tx, rx) = mpsc::channel(2000); // Don't need to receive the endpoint. Change this diff --git a/server/Cargo.toml b/server/Cargo.toml index 19a00e74..50d778fe 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -30,7 +30,7 @@ uuid = { version = "1.10.0", features = ["v4", "fast-rng", "serde"] } [dev-dependencies] axum-test = "15.2.0" frost-ed25519 = { version = "2.0.0-rc.0", features = ["serde"] } -reddsa = { git = "https://github.com/ZcashFoundation/reddsa.git", rev = "4d8c4bb337231e6e89117334d7c61dada589a953", features = [ +reddsa = { git = "https://github.com/ZcashFoundation/reddsa.git", rev = "ed49e9ca0699a6450f6d4a9fe62ff168f5ea1ead", features = [ "frost", "serde", ] } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index d6aefdc8..2a1b05ae 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] frost-ed25519 = { version = "2.0.0-rc.0", features = ["serde"] } -reddsa = { git = "https://github.com/ZcashFoundation/reddsa.git", rev = "4d8c4bb337231e6e89117334d7c61dada589a953", features = ["frost"] } +reddsa = { git = "https://github.com/ZcashFoundation/reddsa.git", rev = "ed49e9ca0699a6450f6d4a9fe62ff168f5ea1ead", features = ["frost"] } hex = "0.4" rand = "0.8" exitcode = "1.1.2" diff --git a/trusted-dealer/Cargo.toml b/trusted-dealer/Cargo.toml index 4fdb1b51..199b5a5f 100644 --- a/trusted-dealer/Cargo.toml +++ b/trusted-dealer/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" frost-core = { version = "2.0.0-rc.0", features = ["serde"] } frost-rerandomized = { version = "2.0.0-rc.0", features = ["serde"] } frost-ed25519 = { version = "2.0.0-rc.0", features = ["serde"] } -reddsa = { git = "https://github.com/ZcashFoundation/reddsa.git", rev = "56a31af7099b95737031ef6cf251939ed99627c0", features = ["frost"] } +reddsa = { git = "https://github.com/ZcashFoundation/reddsa.git", rev = "ed49e9ca0699a6450f6d4a9fe62ff168f5ea1ead", features = ["frost"] } clap = { version = "4.5.20", features = ["derive"] } thiserror = "1.0" rand = "0.8"