From fe493e21bcbe44e4e947dca53c928833603aad4f Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Tue, 16 Jul 2024 20:41:34 -0300 Subject: [PATCH] cleanup --- Cargo.lock | 1 + coordinator/src/args.rs | 29 ++-- coordinator/src/comms/http.rs | 247 ++++++++++++-------------- participant/src/comms/http.rs | 78 ++------- server/Cargo.toml | 1 + server/src/functions.rs | 276 +----------------------------- server/src/lib.rs | 15 -- server/src/state.rs | 62 +------ server/src/types.rs | 210 ++--------------------- server/tests/integration_tests.rs | 200 +++++++++++++--------- 10 files changed, 278 insertions(+), 841 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 028985e9..2af281e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2387,6 +2387,7 @@ dependencies = [ "axum-macros", "axum-test", "clap", + "coordinator", "derivative", "eyre", "frost-core", diff --git a/coordinator/src/args.rs b/coordinator/src/args.rs index efa9d372..135b4c32 100644 --- a/coordinator/src/args.rs +++ b/coordinator/src/args.rs @@ -45,12 +45,13 @@ pub struct Args { #[arg(short = 'S', long, value_delimiter = ',')] pub signers: Vec, - /// The number of participants. If 0, will prompt for a value. + /// The number of participants. If `signers` is specified, it will use the + /// length of `signers`. Otherwise, if 0, it will prompt for a value. #[arg(short = 'n', long, default_value_t = 0)] pub num_signers: u16, /// Public key package to use. Can be a file with a JSON-encoded - /// package, or "". If the file does not exist or if "" is specified, + /// package, or "-". If the file does not exist or if "-" is specified, /// then it will be read from standard input. #[arg(short = 'P', long, default_value = "public-key-package.json")] pub public_key_package: String, @@ -58,14 +59,15 @@ pub struct Args { /// 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. + /// from standard input as a hex string. #[arg(short = 'm', long)] pub message: Vec, /// The randomizers to use. Each instance can be a file with the raw - /// randomizer, "" or "-". If empty or "-" it will be read from standard - /// input as a hex string. If none are passed, random ones will be - /// generated. + /// 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)] pub randomizer: Vec, @@ -131,10 +133,13 @@ pub struct ProcessedArgs { } impl ProcessedArgs { + /// Create a ProcessedArgs from a Args. + /// + /// Validates inputs and reads/parses arguments. pub fn new( args: &Args, input: &mut dyn BufRead, - logger: &mut dyn Write, + 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"))? @@ -145,7 +150,7 @@ impl ProcessedArgs { let num_signers = if !args.signers.is_empty() { args.signers.len() as u16 } else if args.num_signers == 0 { - writeln!(logger, "The number of participants: ")?; + writeln!(output, "The number of participants: ")?; let mut participants = String::new(); input.read_line(&mut participants)?; @@ -156,7 +161,7 @@ impl ProcessedArgs { let out = read_from_file_or_stdin( input, - logger, + output, "public key package", &args.public_key_package, )?; @@ -164,7 +169,7 @@ impl ProcessedArgs { let public_key_package: PublicKeyPackage = serde_json::from_str(&out)?; let messages = if args.message.is_empty() { - writeln!(logger, "The message to be signed (hex encoded)")?; + writeln!(output, "The message to be signed (hex encoded)")?; let mut msg = String::new(); input.read_line(&mut msg)?; vec![hex::decode(msg.trim())?] @@ -173,7 +178,7 @@ impl ProcessedArgs { .iter() .map(|filename| { let msg = if filename == "-" || filename.is_empty() { - writeln!(logger, "The message to be signed (hex encoded)")?; + writeln!(output, "The message to be signed (hex encoded)")?; let mut msg = String::new(); input.read_line(&mut msg)?; hex::decode(msg.trim())? @@ -195,7 +200,7 @@ impl ProcessedArgs { .iter() .map(|filename| { let randomizer = if filename == "-" || filename.is_empty() { - writeln!(logger, "Enter the randomizer (hex string):")?; + writeln!(output, "Enter the randomizer (hex string):")?; let mut randomizer = String::new(); input.read_line(&mut randomizer)?; let bytes = hex::decode(randomizer.trim())?; diff --git a/coordinator/src/comms/http.rs b/coordinator/src/comms/http.rs index 0cdc8669..8b41a4eb 100644 --- a/coordinator/src/comms/http.rs +++ b/coordinator/src/comms/http.rs @@ -1,21 +1,5 @@ //! HTTP implementation of the Comms trait. -use async_trait::async_trait; - -use frost_core::{self as frost, serde, serde::Deserialize, serde::Serialize}; - -use frost_core::Ciphersuite; - -use eyre::eyre; -use frost_rerandomized::Randomizer; -use itertools::Itertools; -use server::{Msg, Uuid}; - -use frost::{ - keys::PublicKeyPackage, round1::SigningCommitments, round2::SignatureShare, Identifier, - SigningPackage, -}; - use std::{ collections::{BTreeMap, HashMap, HashSet}, error::Error, @@ -25,33 +9,51 @@ use std::{ vec, }; +use async_trait::async_trait; +use eyre::eyre; +use frost_core::{ + keys::PublicKeyPackage, round1::SigningCommitments, round2::SignatureShare, Ciphersuite, + Identifier, SigningPackage, +}; +use itertools::Itertools; + +use server::{Msg, SendCommitmentsArgs, SendSignatureSharesArgs, SendSigningPackageArgs, Uuid}; + use super::Comms; use crate::args::ProcessedArgs; -/// The current state of the server, and the required data for the state. +#[derive(Clone, Debug)] +pub struct SessionStateArgs { + pub num_messages: usize, + pub num_signers: usize, +} + +/// The current state of a session. +/// +/// This can be used by a Coordinator to help maitain state and handle +/// messages from the Participants. #[derive(derivative::Derivative)] #[derivative(Debug)] pub enum SessionState { /// Waiting for participants to send their commitments. WaitingForCommitments { + /// Session arguments + args: SessionStateArgs, /// Commitments sent by participants so far, for each message being /// signed. commitments: HashMap, Vec>>, usernames: HashMap>, }, - /// Commitments have been sent by all participants; ready to be fetched by - /// the coordinator. Waiting for coordinator to send the SigningPackage. - CommitmentsReady { + /// Commitments have been sent by all participants. Coordinator can create + /// SigningPackage and send to participants. Waiting for participants to + /// send their signature shares. + WaitingForSignatureShares { + /// Session arguments + args: SessionStateArgs, /// All commitments sent by participants, for each message being signed. commitments: HashMap, Vec>>, + /// Username -> Identifier mapping. usernames: HashMap>, - }, - /// SigningPackage ready to be fetched by participants. Waiting for - /// participants to send their signature shares. - WaitingForSignatureShares { - /// Identifiers of the participants that sent commitments in the - /// previous state. - identifiers: HashSet>, /// Signature shares sent by participants so far, for each message being /// signed. signature_shares: HashMap, Vec>>, @@ -59,84 +61,63 @@ pub enum SessionState { /// SignatureShares have been sent by all participants; ready to be fetched /// by the coordinator. SignatureSharesReady { + /// Session arguments + args: SessionStateArgs, /// Signature shares sent by participants, for each message being signed. signature_shares: HashMap, Vec>>, }, } -impl Default for SessionState -where - C: Ciphersuite, -{ - fn default() -> Self { - SessionState::WaitingForCommitments { +impl SessionState { + /// Create a new SessionState for the given number of messages and signers. + pub fn new(num_messages: usize, num_signers: usize) -> Self { + let args = SessionStateArgs { + num_messages, + num_signers, + }; + Self::WaitingForCommitments { + args, commitments: Default::default(), usernames: Default::default(), } } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(crate = "self::serde")] -#[serde(bound = "C: Ciphersuite")] -pub struct SendCommitmentsArgs { - pub identifier: Identifier, - pub commitments: Vec>, -} - -#[derive(Serialize, Deserialize, derivative::Derivative)] -#[derivative(Debug)] -#[serde(crate = "self::serde")] -#[serde(bound = "C: Ciphersuite")] -pub struct SendSigningPackageArgs { - pub signing_package: Vec>, - #[serde( - serialize_with = "serdect::slice::serialize_hex_lower_or_bin", - deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec" - )] - pub aux_msg: Vec, - #[derivative(Debug = "ignore")] - pub randomizer: Vec>, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(crate = "self::serde")] -#[serde(bound = "C: Ciphersuite")] -pub struct SendSignatureSharesArgs { - pub identifier: Identifier, - pub signature_share: Vec>, -} -impl SessionState { - fn recv(&mut self, args: &ProcessedArgs, msg: Msg) -> Result<(), Box> { + /// Handle a Msg received from a participant. + /// + /// This should be called for new Msgs until [`are_commitments_ready()`] + /// returns true, and after the SigningPackage is sent to the participants, + /// it should be called for new Msgs until [`are_signature_shares_ready()`] + /// returns true. + pub fn recv(&mut self, msg: Msg) -> Result<(), Box> { match self { SessionState::WaitingForCommitments { .. } => { let send_commitments_args: SendCommitmentsArgs = serde_json::from_slice(&msg.msg)?; - self.send_commitments(args, msg.sender, send_commitments_args)?; + self.handle_commitments(msg.sender, send_commitments_args)?; } SessionState::WaitingForSignatureShares { .. } => { let send_signature_shares_args: SendSignatureSharesArgs = serde_json::from_slice(&msg.msg)?; - self.send_signature_shares_args(args, msg.sender, send_signature_shares_args)?; + self.handle_signature_share(msg.sender, send_signature_shares_args)?; } _ => return Err(eyre!("received message during wrong state").into()), } Ok(()) } - fn send_commitments( + /// Handle commitments sent by a participant. + fn handle_commitments( &mut self, - args: &ProcessedArgs, username: String, send_commitments_args: SendCommitmentsArgs, ) -> Result<(), Box> { if let SessionState::WaitingForCommitments { + args, commitments, usernames, } = self { - if send_commitments_args.commitments.len() != args.messages.len() { + if send_commitments_args.commitments.len() != args.num_messages { return Err(eyre!("wrong number of commitments").into()); } @@ -148,18 +129,15 @@ impl SessionState { send_commitments_args.identifier, send_commitments_args.commitments, ); - eprintln!( - "added commitments, currently {}/{}", - commitments.len(), - args.num_signers - ); usernames.insert(username, send_commitments_args.identifier); // If complete, advance to next state - if commitments.len() == args.num_signers as usize { - *self = SessionState::CommitmentsReady { + if commitments.len() == args.num_signers { + *self = SessionState::WaitingForSignatureShares { + args: args.clone(), commitments: commitments.clone(), usernames: usernames.clone(), + signature_shares: Default::default(), } } Ok(()) @@ -168,14 +146,19 @@ impl SessionState { } } - fn are_commitments_ready(&self) -> bool { - matches!(self, SessionState::CommitmentsReady { .. }) + /// Returns if all participants sent their commitments. + /// When this returns `true`, [`commitments()`] can be called. + pub fn has_commitments(&self) -> bool { + matches!(self, SessionState::WaitingForSignatureShares { .. }) } + /// Returns: + /// - A vector (one item per message) of maps linking a participant identifier + /// and the SigningCommitments they have sent. + /// - A map linking usernames to participant identifiers. #[allow(clippy::type_complexity)] - fn get_commitments( + pub fn commitments( &mut self, - args: &ProcessedArgs, ) -> Result< ( Vec, SigningCommitments>>, @@ -183,68 +166,49 @@ impl SessionState { ), Box, > { - if let SessionState::CommitmentsReady { + if let SessionState::WaitingForSignatureShares { + args, commitments, usernames, + .. } = self { // Convert the BTreeMap> map // into a Vec> map to make // it easier for the coordinator to build the SigningPackages. - let commitments: Vec, SigningCommitments>> = - (0..args.messages.len()) - .map(|i| commitments.iter().map(|(id, c)| (*id, c[i])).collect()) - .collect(); + let commitments: Vec, SigningCommitments>> = (0..args + .num_messages) + .map(|i| commitments.iter().map(|(id, c)| (*id, c[i])).collect()) + .collect(); Ok((commitments, usernames.clone())) } else { panic!("wrong state"); } } - fn send_signing_package( - &mut self, - signing_package: Vec>, - randomizer: Vec>, - aux_msg: Vec, - ) -> Result, Box> { - if let SessionState::CommitmentsReady { - commitments, - usernames: _, - } = self - { - *self = SessionState::WaitingForSignatureShares { - identifiers: commitments.keys().cloned().collect(), - signature_shares: Default::default(), - }; - Ok(SendSigningPackageArgs { - signing_package, - randomizer, - aux_msg, - }) - } else { - panic!("wrong state"); - } - } - - fn are_signature_shares_ready(&self) -> bool { + /// Returns if all participants sent their SignatureShares. + /// When this returns `true`, [`signature_shares()`] can be called. + pub fn has_signature_shares(&self) -> bool { matches!(self, SessionState::SignatureSharesReady { .. }) } - fn send_signature_shares_args( + /// Handle signature share sent by a participant. + fn handle_signature_share( &mut self, - args: &ProcessedArgs, _username: String, send_signature_shares_args: SendSignatureSharesArgs, ) -> Result<(), Box> { if let SessionState::WaitingForSignatureShares { - identifiers, + args, + commitments, signature_shares, + .. } = self { - if send_signature_shares_args.signature_share.len() != args.messages.len() { + if send_signature_shares_args.signature_share.len() != args.num_messages { return Err(eyre!("wrong number of signature shares").into()); } - if !identifiers.contains(&send_signature_shares_args.identifier) { + if !commitments.contains_key(&send_signature_shares_args.identifier) { return Err(eyre!("invalid identifier").into()); } @@ -256,8 +220,11 @@ impl SessionState { send_signature_shares_args.signature_share, ); // If complete, advance to next state - if signature_shares.keys().cloned().collect::>() == *identifiers { + if signature_shares.keys().cloned().collect::>() + == commitments.keys().cloned().collect::>() + { *self = SessionState::SignatureSharesReady { + args: args.clone(), signature_shares: signature_shares.clone(), } } @@ -267,16 +234,21 @@ impl SessionState { } } + /// Returns a vector (one item per message) of maps linking a participant + /// identifier and the SignatureShare they have sent. #[allow(clippy::type_complexity)] - fn get_signature_shares( + pub fn signature_shares( &mut self, - args: &ProcessedArgs, ) -> Result, SignatureShare>>, Box> { - if let SessionState::SignatureSharesReady { signature_shares } = self { + if let SessionState::SignatureSharesReady { + args, + signature_shares, + } = self + { // Convert the BTreeMap> map // into a Vec> map to make // it easier for the coordinator to build the SigningPackages. - let signature_shares = (0..args.messages.len()) + let signature_shares = (0..args.num_messages) .map(|i| signature_shares.iter().map(|(id, s)| (*id, s[i])).collect()) .collect(); Ok(signature_shares) @@ -308,7 +280,7 @@ impl HTTPComms { access_token: String::new(), num_signers: 0, args: args.clone(), - state: Default::default(), + state: SessionState::new(args.messages.len(), args.num_signers as usize), usernames: Default::default(), _phantom: Default::default(), }) @@ -376,17 +348,17 @@ impl Comms for HTTPComms { .json::() .await?; for msg in r.msgs { - self.state.recv(&self.args, msg)?; + self.state.recv(msg)?; } tokio::time::sleep(Duration::from_secs(2)).await; eprint!("."); - if self.state.are_commitments_ready() { + if self.state.has_commitments() { break; } } eprintln!(); - let (commitments, usernames) = self.state.get_commitments(&self.args)?; + let (commitments, usernames) = self.state.commitments()?; self.usernames = usernames; // TODO: support more than 1 @@ -400,15 +372,12 @@ impl Comms for HTTPComms { signing_package: &SigningPackage, randomizer: Option>, ) -> Result, SignatureShare>, Box> { - // Send SigningPackage to all participants eprintln!("Sending SigningPackage to participants..."); - - let send_signing_package_args = self.state.send_signing_package( - vec![signing_package.clone()], - randomizer.map(|r| vec![r]).unwrap_or_default(), - Default::default(), - )?; - + let send_signing_package_args = SendSigningPackageArgs { + signing_package: vec![signing_package.clone()], + aux_msg: Default::default(), + randomizer: randomizer.map(|r| vec![r]).unwrap_or_default(), + }; let _r = self .client .post(format!("{}/send", self.host_port)) @@ -439,11 +408,11 @@ impl Comms for HTTPComms { .json::() .await?; for msg in r.msgs { - self.state.recv(&self.args, msg)?; + self.state.recv(msg)?; } tokio::time::sleep(Duration::from_secs(2)).await; eprint!("."); - if self.state.are_signature_shares_ready() { + if self.state.has_signature_shares() { break; } } @@ -466,7 +435,7 @@ impl Comms for HTTPComms { .send() .await?; - let signature_shares = self.state.get_signature_shares(&self.args)?; + let signature_shares = self.state.signature_shares()?; // TODO: support more than 1 Ok(signature_shares[0].clone()) diff --git a/participant/src/comms/http.rs b/participant/src/comms/http.rs index ddb4529d..36c5a457 100644 --- a/participant/src/comms/http.rs +++ b/participant/src/comms/http.rs @@ -1,25 +1,20 @@ //! HTTP implementation of the Comms trait. -use async_trait::async_trait; - -use frost_core::SigningPackage; -use frost_core::{self as frost, serde, serde::Deserialize, serde::Serialize, Ciphersuite}; +use std::{ + env, + error::Error, + io::{BufRead, Write}, + marker::PhantomData, + time::Duration, +}; +use async_trait::async_trait; use eyre::eyre; - -use frost::{round1::SigningCommitments, round2::SignatureShare, Identifier}; -use frost_rerandomized::Randomizer; +use frost_core::{ + self as frost, round1::SigningCommitments, round2::SignatureShare, Ciphersuite, Identifier, +}; use super::Comms; - -use std::env; -use std::io::{BufRead, Write}; - -use std::error::Error; - -use std::marker::PhantomData; -use std::time::Duration; - use crate::args::Args; pub struct HTTPComms { @@ -29,45 +24,10 @@ pub struct HTTPComms { username: String, password: String, access_token: String, - coordinator: String, _phantom: PhantomData, } -use server::Uuid; - -// TODO: deduplicate with coordinator -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(crate = "self::serde")] -#[serde(bound = "C: Ciphersuite")] -pub struct SendCommitmentsArgs { - pub identifier: Identifier, - pub commitments: Vec>, -} - -// TODO: deduplicate with coordinator -#[derive(Serialize, Deserialize, derivative::Derivative)] -#[derivative(Debug)] -#[serde(crate = "self::serde")] -#[serde(bound = "C: Ciphersuite")] -pub struct SendSigningPackageArgs { - pub signing_package: Vec>, - #[serde( - serialize_with = "serdect::slice::serialize_hex_lower_or_bin", - deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec" - )] - pub aux_msg: Vec, - #[derivative(Debug = "ignore")] - pub randomizer: Vec>, -} - -// TODO: deduplicate with coordinator -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(crate = "self::serde")] -#[serde(bound = "C: Ciphersuite")] -pub struct SendSignatureSharesArgs { - pub identifier: Identifier, - pub signature_share: Vec>, -} +use server::{SendCommitmentsArgs, SendSignatureSharesArgs, SendSigningPackageArgs, Uuid}; // TODO: Improve error handling for invalid session id impl HTTPComms @@ -84,7 +44,6 @@ where username: args.username.clone(), password, access_token: String::new(), - coordinator: String::new(), _phantom: Default::default(), }) } @@ -138,24 +97,13 @@ where if r.session_ids.len() > 1 { return Err(eyre!("user has more than one FROST session active, which is still not supported by this tool").into()); } else if r.session_ids.is_empty() { - return Err(eyre!("User has no current session actives. The Coordinator should either specify your username, or manually share the session ID which you can specify with --session_id").into()); + return Err(eyre!("User has no current session active. The Coordinator should either specify your username, or manually share the session ID which you can specify with --session_id").into()); } r.session_ids[0] } }; self.session_id = Some(session_id); - let r = self - .client - .post(format!("{}/get_session_info", self.host_port)) - .bearer_auth(&self.access_token) - .json(&server::GetSessionInfoArgs { session_id }) - .send() - .await? - .json::() - .await?; - self.coordinator = r.coordinator; - // Send Commitments to Server let send_commitments_args = SendCommitmentsArgs { identifier, diff --git a/server/Cargo.toml b/server/Cargo.toml index 9d86f8d9..af6e7dd5 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -35,6 +35,7 @@ reddsa = { git = "https://github.com/ZcashFoundation/reddsa.git", rev = "4d8c4bb ] } reqwest = { version = "0.12.5", features = ["json"] } regex = "1.10.6" +coordinator = { path = "../coordinator" } [features] default = [] diff --git a/server/src/functions.rs b/server/src/functions.rs index 87d98330..4779eaaf 100644 --- a/server/src/functions.rs +++ b/server/src/functions.rs @@ -1,11 +1,9 @@ -use std::collections::HashSet; - use axum::{extract::State, http::StatusCode, Json}; use eyre::eyre; use uuid::Uuid; use crate::{ - state::{Session, SessionState, SharedState}, + state::{Session, SharedState}, types::*, user::{ add_access_token, authenticate_user, create_user, delete_user, get_user, @@ -172,9 +170,6 @@ pub(crate) async fn create_new_session( coordinator: user.username, num_signers: args.num_signers, message_count: args.message_count, - state: SessionState::WaitingForCommitments { - commitments: Default::default(), - }, queue: Default::default(), }; // Save session into global state. @@ -296,275 +291,6 @@ pub(crate) async fn receive( Ok(Json(ReceiveOutput { msgs })) } -/// Implement the send_commitments API -// TODO: get identifier from channel rather from arguments -#[tracing::instrument(ret, err(Debug), skip(state,user), fields(user.username = %user.username))] -pub(crate) async fn send_commitments( - State(state): State, - user: User, - Json(args): Json, -) -> Result<(), AppError> { - // Get the mutex lock to read and write from the state - let mut state_lock = state.write().unwrap(); - - let session = state_lock - .sessions - .get_mut(&args.session_id) - .ok_or(AppError( - StatusCode::NOT_FOUND, - eyre!("session ID not found").into(), - ))?; - - match &mut session.state { - SessionState::WaitingForCommitments { commitments } => { - if args.commitments.len() != session.message_count as usize { - return Err(AppError( - StatusCode::INTERNAL_SERVER_ERROR, - eyre!("wrong number of commitments").into(), - )); - } - // Add commitment to map. - // Currently ignores the possibility of overwriting previous values - // (it seems better to ignore overwrites, which could be caused by - // poor networking connectivity leading to retries) - commitments.insert(args.identifier, args.commitments); - tracing::debug!( - "added commitments, currently {}/{}", - commitments.len(), - session.num_signers - ); - // If complete, advance to next state - if commitments.len() == session.num_signers as usize { - session.state = SessionState::CommitmentsReady { - commitments: commitments.clone(), - } - } - } - _ => { - return Err(AppError( - StatusCode::INTERNAL_SERVER_ERROR, - eyre!("incompatible session state").into(), - )); - } - } - Ok(()) -} - -/// Implement the get_commitments API -#[tracing::instrument(ret, err(Debug), skip(state,user), fields(user.username = %user.username))] -pub(crate) async fn get_commitments( - State(state): State, - user: User, - Json(args): Json, -) -> Result, AppError> { - let state_lock = state.read().unwrap(); - - let session = state_lock.sessions.get(&args.session_id).ok_or(AppError( - StatusCode::NOT_FOUND, - eyre!("session ID not found").into(), - ))?; - - match &session.state { - SessionState::CommitmentsReady { commitments } => Ok(Json(GetCommitmentsOutput { - // Convert the BTreeMap> map - // into a Vec> map to make - // it easier for the coordinator to build the SigningPackages. - commitments: (0..session.message_count) - .map(|i| { - commitments - .iter() - .map(|(id, c)| (id.clone(), c[i as usize].clone())) - .collect() - }) - .collect(), - })), - _ => Err(AppError( - StatusCode::INTERNAL_SERVER_ERROR, - eyre!("incompatible session state").into(), - )), - } -} - -/// Implement the send_signing_package API -#[tracing::instrument(ret, err(Debug), skip(state,user), fields(user.username = %user.username))] -pub(crate) async fn send_signing_package( - State(state): State, - user: User, - Json(args): Json, -) -> Result<(), AppError> { - let mut state_lock = state.write().unwrap(); - - let session = state_lock - .sessions - .get_mut(&args.session_id) - .ok_or(AppError( - StatusCode::NOT_FOUND, - eyre!("session ID not found").into(), - ))?; - - match &mut session.state { - SessionState::CommitmentsReady { commitments } => { - if args.signing_package.len() != session.message_count as usize { - return Err(AppError( - StatusCode::INTERNAL_SERVER_ERROR, - eyre!("wrong number of inputs").into(), - )); - } - if args.randomizer.len() != session.message_count as usize - && !args.randomizer.is_empty() - { - return Err(AppError( - StatusCode::INTERNAL_SERVER_ERROR, - eyre!("wrong number of inputs").into(), - )); - } - session.state = SessionState::WaitingForSignatureShares { - identifiers: commitments.keys().cloned().collect(), - signing_package: args.signing_package, - signature_shares: Default::default(), - randomizer: args.randomizer, - aux_msg: args.aux_msg, - }; - } - _ => { - return Err(AppError( - StatusCode::INTERNAL_SERVER_ERROR, - eyre!("incompatible session state").into(), - )); - } - } - Ok(()) -} - -/// Implement the get_signing_package API -#[tracing::instrument(ret, err(Debug), skip(state,user), fields(user.username = %user.username))] -pub(crate) async fn get_signing_package( - State(state): State, - user: User, - Json(args): Json, -) -> Result, AppError> { - let state_lock = state.read().unwrap(); - - let session = state_lock.sessions.get(&args.session_id).ok_or(AppError( - StatusCode::NOT_FOUND, - eyre!("session ID not found").into(), - ))?; - - match &session.state { - SessionState::WaitingForSignatureShares { - identifiers: _, - signing_package, - signature_shares: _, - randomizer, - aux_msg, - } => Ok(Json(GetSigningPackageOutput { - signing_package: signing_package.clone(), - randomizer: randomizer.clone(), - aux_msg: aux_msg.clone(), - })), - _ => Err(AppError( - StatusCode::INTERNAL_SERVER_ERROR, - eyre!("incompatible session state").into(), - )), - } -} - -/// Implement the send_signature_share API -// TODO: get identifier from channel rather from arguments -#[tracing::instrument(ret, err(Debug), skip(state,user), fields(user.username = %user.username))] -pub(crate) async fn send_signature_share( - State(state): State, - user: User, - Json(args): Json, -) -> Result<(), AppError> { - let mut state_lock = state.write().unwrap(); - - let session = state_lock - .sessions - .get_mut(&args.session_id) - .ok_or(AppError( - StatusCode::NOT_FOUND, - eyre!("session ID not found").into(), - ))?; - - match &mut session.state { - SessionState::WaitingForSignatureShares { - identifiers, - signing_package: _, - signature_shares, - randomizer: _, - aux_msg: _, - } => { - if !identifiers.contains(&args.identifier) { - return Err(AppError( - StatusCode::NOT_FOUND, - eyre!("invalid identifier").into(), - )); - } - if args.signature_share.len() != session.message_count as usize { - return Err(AppError( - StatusCode::INTERNAL_SERVER_ERROR, - eyre!("wrong number of signature shares").into(), - )); - } - // Currently ignoring the possibility of overwriting previous values - // (it seems better to ignore overwrites, which could be caused by - // poor networking connectivity leading to retries) - signature_shares.insert(args.identifier, args.signature_share); - // If complete, advance to next state - if signature_shares.keys().cloned().collect::>() == *identifiers { - session.state = SessionState::SignatureSharesReady { - signature_shares: signature_shares.clone(), - }; - } - } - _ => { - return Err(AppError( - StatusCode::INTERNAL_SERVER_ERROR, - eyre!("incompatible session state").into(), - )); - } - } - Ok(()) -} - -/// Implement the get_signature_shares API -#[tracing::instrument(ret, err(Debug), skip(state,user), fields(user.username = %user.username))] -pub(crate) async fn get_signature_shares( - State(state): State, - user: User, - Json(args): Json, -) -> Result, AppError> { - let state_lock = state.read().unwrap(); - - let session = state_lock.sessions.get(&args.session_id).ok_or(AppError( - StatusCode::NOT_FOUND, - eyre!("session ID not found").into(), - ))?; - - match &session.state { - SessionState::SignatureSharesReady { signature_shares } => { - Ok(Json(GetSignatureSharesOutput { - // Convert the BTreeMap> map - // into a Vec> map to make - // it easier for the coordinator to build the SigningPackages. - signature_shares: (0..session.message_count) - .map(|i| { - signature_shares - .iter() - .map(|(id, s)| (id.clone(), s[i as usize].clone())) - .collect() - }) - .collect(), - })) - } - _ => Err(AppError( - StatusCode::INTERNAL_SERVER_ERROR, - eyre!("incompatible session state").into(), - )), - } -} - /// Implement the close_session API. #[tracing::instrument(ret, err(Debug), skip(state,user), fields(user.username = %user.username))] pub(crate) async fn close_session( diff --git a/server/src/lib.rs b/server/src/lib.rs index 185a6c59..01f24bbb 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -31,21 +31,6 @@ pub fn router(shared_state: SharedState) -> Router { .route("/get_session_info", post(functions::get_session_info)) .route("/send", post(functions::send)) .route("/receive", post(functions::receive)) - .route("/send_commitments", post(functions::send_commitments)) - .route("/get_commitments", post(functions::get_commitments)) - .route( - "/send_signing_package", - post(functions::send_signing_package), - ) - .route("/get_signing_package", post(functions::get_signing_package)) - .route( - "/send_signature_share", - post(functions::send_signature_share), - ) - .route( - "/get_signature_shares", - post(functions::get_signature_shares), - ) .route("/close_session", post(functions::close_session)) .layer(TraceLayer::new_for_http()) .with_state(shared_state) diff --git a/server/src/state.rs b/server/src/state.rs index b0bb00aa..7c2cb94a 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -10,64 +10,7 @@ use sqlx::{ }; use uuid::Uuid; -use crate::{ - Msg, SerializedIdentifier, SerializedSignatureShare, SerializedSigningCommitments, - SerializedSigningPackage, -}; - -use crate::SerializedRandomizer; - -/// The current state of the server, and the required data for the state. -#[derive(derivative::Derivative)] -#[derivative(Debug)] -pub enum SessionState { - /// Waiting for participants to send their commitments. - WaitingForCommitments { - /// Commitments sent by participants so far, for each message being - /// signed. - commitments: HashMap>, - }, - /// Commitments have been sent by all participants; ready to be fetched by - /// the coordinator. Waiting for coordinator to send the SigningPackage. - CommitmentsReady { - /// All commitments sent by participants, for each message being signed. - commitments: HashMap>, - }, - /// SigningPackage ready to be fetched by participants. Waiting for - /// participants to send their signature shares. - WaitingForSignatureShares { - /// Identifiers of the participants that sent commitments in the - /// previous state. - identifiers: HashSet, - /// SigningPackage sent by the coordinator to be sent to participants, - /// for each message being signed. - signing_package: Vec, - /// Randomizer sent by coordinator to be sent to participants, for each - /// message being signed. Can be empty if not being used. - #[derivative(Debug = "ignore")] - randomizer: Vec, - /// Auxiliary (optional) message. A context-specific data that is - /// supposed to be interpreted by the participants. - aux_msg: Vec, - /// Signature shares sent by participants so far, for each message being - /// signed. - signature_shares: HashMap>, - }, - /// SignatureShares have been sent by all participants; ready to be fetched - /// by the coordinator. - SignatureSharesReady { - /// Signature shares sent by participants, for each message being signed. - signature_shares: HashMap>, - }, -} - -impl Default for SessionState { - fn default() -> Self { - SessionState::WaitingForCommitments { - commitments: Default::default(), - } - } -} +use crate::Msg; /// A particular signing session. #[derive(Debug)] @@ -82,8 +25,7 @@ pub struct Session { // pub(crate) identifiers: BTreeSet, /// The number of messages being simultaneously signed. pub(crate) message_count: u8, - /// The session state. - pub(crate) state: SessionState, + /// The message queue. pub(crate) queue: HashMap>, } diff --git a/server/src/types.rs b/server/src/types.rs index a65b69a0..fcc5041f 100644 --- a/server/src/types.rs +++ b/server/src/types.rs @@ -1,152 +1,10 @@ -use std::collections::HashMap; - +use frost_core::{ + round1::SigningCommitments, round2::SignatureShare, Ciphersuite, Identifier, SigningPackage, +}; +use frost_rerandomized::Randomizer; use serde::{Deserialize, Serialize}; pub use uuid::Uuid; -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] -#[serde(transparent)] -pub struct SerializedIdentifier( - #[serde( - serialize_with = "serdect::slice::serialize_hex_lower_or_bin", - deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec" - )] - pub Vec, -); - -impl From> for SerializedIdentifier { - fn from(identifier: frost_core::Identifier) -> Self { - Self(identifier.serialize().to_vec()) - } -} - -impl TryFrom<&SerializedIdentifier> for frost_core::Identifier { - type Error = frost_core::Error; - - fn try_from(serialized_identifier: &SerializedIdentifier) -> Result { - frost_core::Identifier::::deserialize(&serialized_identifier.0) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(transparent)] -pub struct SerializedSigningCommitments( - #[serde( - serialize_with = "serdect::slice::serialize_hex_lower_or_bin", - deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec" - )] - pub Vec, -); - -impl TryFrom<&frost_core::round1::SigningCommitments> - for SerializedSigningCommitments -{ - type Error = frost_core::Error; - - fn try_from( - signing_commitments: &frost_core::round1::SigningCommitments, - ) -> Result { - Ok(Self(signing_commitments.serialize()?)) - } -} - -impl TryFrom<&SerializedSigningCommitments> - for frost_core::round1::SigningCommitments -{ - type Error = frost_core::Error; - - fn try_from( - serialized_signing_commitments: &SerializedSigningCommitments, - ) -> Result { - frost_core::round1::SigningCommitments::::deserialize(&serialized_signing_commitments.0) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(transparent)] -pub struct SerializedSigningPackage( - #[serde( - serialize_with = "serdect::slice::serialize_hex_lower_or_bin", - deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec" - )] - pub Vec, -); - -impl TryFrom<&frost_core::SigningPackage> - for SerializedSigningPackage -{ - type Error = frost_core::Error; - - fn try_from(signing_package: &frost_core::SigningPackage) -> Result { - Ok(Self(signing_package.serialize()?)) - } -} - -impl TryFrom<&SerializedSigningPackage> - for frost_core::SigningPackage -{ - type Error = frost_core::Error; - - fn try_from( - serialized_signing_package: &SerializedSigningPackage, - ) -> Result { - frost_core::SigningPackage::::deserialize(&serialized_signing_package.0) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(transparent)] -pub struct SerializedRandomizer( - #[serde( - serialize_with = "serdect::slice::serialize_hex_lower_or_bin", - deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec" - )] - pub Vec, -); - -impl From> for SerializedRandomizer { - fn from(randomizer: frost_rerandomized::Randomizer) -> Self { - Self(randomizer.serialize().to_vec()) - } -} - -impl TryFrom<&SerializedRandomizer> - for frost_rerandomized::Randomizer -{ - type Error = frost_core::Error; - - fn try_from(serialized_randomizer: &SerializedRandomizer) -> Result { - frost_rerandomized::Randomizer::::deserialize(&serialized_randomizer.0) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(transparent)] -pub struct SerializedSignatureShare( - #[serde( - serialize_with = "serdect::slice::serialize_hex_lower_or_bin", - deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec" - )] - pub Vec, -); - -impl From> - for SerializedSignatureShare -{ - fn from(randomizer: frost_core::round2::SignatureShare) -> Self { - Self(randomizer.serialize().to_vec()) - } -} - -impl TryFrom<&SerializedSignatureShare> - for frost_core::round2::SignatureShare -{ - type Error = frost_core::Error; - - fn try_from(serialized_randomizer: &SerializedSignatureShare) -> Result { - frost_core::round2::SignatureShare::::deserialize(&serialized_randomizer.0) - } -} - #[derive(Debug, Serialize, Deserialize)] pub struct RegisterArgs { pub username: String, @@ -232,68 +90,34 @@ pub struct ReceiveOutput { } #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SendCommitmentsArgs { - pub session_id: Uuid, - pub identifier: SerializedIdentifier, - pub commitments: Vec, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct GetCommitmentsArgs { +pub struct CloseSessionArgs { pub session_id: Uuid, } #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct GetCommitmentsOutput { - pub commitments: Vec>, +#[serde(bound = "C: Ciphersuite")] +pub struct SendCommitmentsArgs { + pub identifier: Identifier, + pub commitments: Vec>, } #[derive(Serialize, Deserialize, derivative::Derivative)] #[derivative(Debug)] -pub struct SendSigningPackageArgs { - pub session_id: Uuid, - pub signing_package: Vec, +#[serde(bound = "C: Ciphersuite")] +pub struct SendSigningPackageArgs { + pub signing_package: Vec>, #[serde( serialize_with = "serdect::slice::serialize_hex_lower_or_bin", deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec" )] pub aux_msg: Vec, #[derivative(Debug = "ignore")] - pub randomizer: Vec, + pub randomizer: Vec>, } #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct GetSigningPackageArgs { - pub session_id: Uuid, -} - -#[derive(Serialize, Deserialize, derivative::Derivative)] -#[derivative(Debug)] -pub struct GetSigningPackageOutput { - pub signing_package: Vec, - #[derivative(Debug = "ignore")] - pub randomizer: Vec, - pub aux_msg: Vec, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SendSignatureShareArgs { - pub session_id: Uuid, - pub identifier: SerializedIdentifier, - pub signature_share: Vec, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct GetSignatureSharesArgs { - pub session_id: Uuid, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct GetSignatureSharesOutput { - pub signature_shares: Vec>, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct CloseSessionArgs { - pub session_id: Uuid, +#[serde(bound = "C: Ciphersuite")] +pub struct SendSignatureSharesArgs { + pub identifier: Identifier, + pub signature_share: Vec>, } diff --git a/server/tests/integration_tests.rs b/server/tests/integration_tests.rs index be8a59e1..5e8976a9 100644 --- a/server/tests/integration_tests.rs +++ b/server/tests/integration_tests.rs @@ -1,8 +1,12 @@ use std::{collections::BTreeMap, time::Duration}; use axum_test::TestServer; +use coordinator::comms::http::SessionState; use rand::thread_rng; -use server::{args::Args, router, AppState, SerializedSignatureShare, SerializedSigningPackage}; +use server::{ + args::Args, router, AppState, SendCommitmentsArgs, SendSignatureSharesArgs, + SendSigningPackageArgs, +}; use frost_core as frost; @@ -84,13 +88,25 @@ async fn test_main_router< .await; res.assert_status_ok(); let r: server::LoginOutput = res.json(); - let token = r.access_token; + let alice_token = r.access_token; + + let res = server + .post("/login") + .json(&server::LoginArgs { + username: "bob".to_string(), + password: "passw0rd".to_string(), + }) + .await; + res.assert_status_ok(); + let r: server::LoginOutput = res.json(); + let bob_token = r.access_token; + let tokens = [alice_token, bob_token]; // As the coordinator, create a new signing session with all participants, // for 2 messages let res = server .post("/create_new_session") - .authorization_bearer(token) + .authorization_bearer(alice_token) .json(&server::CreateNewSessionArgs { usernames: vec!["alice".to_string(), "bob".to_string()], num_signers: 2, @@ -106,7 +122,7 @@ async fn test_main_router< // Map to store the SigningNonces (for each message, for each participant) let mut nonces_map = BTreeMap::<_, _>::new(); - for (identifier, key_package) in key_packages.iter().take(2) { + for ((identifier, key_package), token) in key_packages.iter().take(2).zip(tokens.iter()) { // As participant `identifier` // Get the number of messages (the participants wouldn't know without @@ -126,20 +142,25 @@ async fn test_main_router< let (nonces, commitments) = frost::round1::commit(key_package.signing_share(), &mut rng); nonces_vec.push(nonces); - commitments_vec.push((&commitments).try_into()?); + commitments_vec.push(commitments); } // Store nonces for later use nonces_map.insert(*identifier, nonces_vec); // Send commitments to server + let send_commitments_args = SendCommitmentsArgs { + identifier: *identifier, + commitments: commitments_vec, + }; let res = server - .post("/send_commitments") + .post("/send") .authorization_bearer(token) - .json(&server::SendCommitmentsArgs { - identifier: (*identifier).into(), + .json(&server::SendArgs { session_id, - commitments: commitments_vec, + // Empty recipients: Coordinator + recipients: vec![], + msg: serde_json::to_vec(&send_commitments_args)?, }) .await; if res.status_code() != 200 { @@ -148,23 +169,27 @@ async fn test_main_router< } // As the coordinator, get the commitments - let res = server - .post("/get_commitments") - .authorization_bearer(token) - .json(&server::GetCommitmentsArgs { session_id }) - .await; - res.assert_status_ok(); - let r: server::GetCommitmentsOutput = res.json(); - // Deserialize commitments in the response - let commitments = r - .commitments - .iter() - .map(|m| { - m.iter() - .map(|(i, c)| Ok((i.try_into()?, c.try_into()?))) - .collect::, Box>>() - }) - .collect::, _>>()?; + let mut coordinator_state = SessionState::::new(2, 2); + loop { + let res = server + .post("/receive") + .authorization_bearer(alice_token) + .json(&server::ReceiveArgs { + session_id, + as_coordinator: true, + }) + .await; + res.assert_status_ok(); + let r: server::ReceiveOutput = res.json(); + for msg in r.msgs { + coordinator_state.recv(msg)?; + } + tokio::time::sleep(Duration::from_secs(2)).await; + if coordinator_state.has_commitments() { + break; + } + } + let (commitments, usernames) = coordinator_state.commitments()?; // As the coordinator, choose messages and create one SigningPackage // and one RandomizedParams for each. @@ -185,54 +210,64 @@ async fn test_main_router< .collect::, _>>()?; // As the coordinator, send the SigningPackages to the server + let send_signing_package_args = SendSigningPackageArgs { + signing_package: signing_packages.clone(), + aux_msg: aux_msg.to_vec(), + randomizer: if rerandomized { + randomized_params + .iter() + .map(|p| (*p.randomizer())) + .collect() + } else { + Vec::new() + }, + }; let res = server - .post("/send_signing_package") - .authorization_bearer(token) - .json(&server::SendSigningPackageArgs { + .post("/send") + .authorization_bearer(alice_token) + .json(&server::SendArgs { session_id, - signing_package: signing_packages - .iter() - .map(std::convert::TryInto::::try_into) - .collect::>()?, - randomizer: if rerandomized { - randomized_params - .iter() - .map(|p| (*p.randomizer()).into()) - .collect() - } else { - Vec::new() - }, - aux_msg: aux_msg.to_owned(), + recipients: usernames.keys().cloned().collect(), + msg: serde_json::to_vec(&send_signing_package_args)?, }) .await; res.assert_status_ok(); // As each participant, get SigningPackages and generate the SignatureShares // for each. - for (identifier, key_package) in key_packages.iter().take(2) { + for ((identifier, key_package), token) in key_packages.iter().take(2).zip(tokens.iter()) { // As participant `identifier` // Get SigningPackages - let res = server - .post("get_signing_package") - .authorization_bearer(token) - .json(&server::GetSigningPackageArgs { session_id }) - .await; - res.assert_status_ok(); - let r: server::GetSigningPackageOutput = res.json(); + let r: SendSigningPackageArgs = loop { + let r = server + .post("/receive") + .authorization_bearer(token) + .json(&server::ReceiveArgs { + session_id, + as_coordinator: false, + }) + .await + .json::(); + if r.msgs.is_empty() { + tokio::time::sleep(Duration::from_secs(2)).await; + } else { + break serde_json::from_slice(&r.msgs[0].msg)?; + } + }; // Generate SignatureShares for each SigningPackage - let signature_share = if rerandomized { + let signature_shares = if rerandomized { r.signing_package .iter() .zip(r.randomizer.iter()) .enumerate() .map(|(i, (signing_package, randomizer))| { frost_rerandomized::sign( - &signing_package.try_into()?, + signing_package, &nonces_map[identifier][i], key_package, - randomizer.try_into()?, + *randomizer, ) }) .collect::, _>>()? @@ -241,49 +276,50 @@ async fn test_main_router< .iter() .enumerate() .map(|(i, signing_package)| { - frost::round2::sign( - &signing_package.try_into()?, - &nonces_map[identifier][i], - key_package, - ) + frost::round2::sign(signing_package, &nonces_map[identifier][i], key_package) }) .collect::, _>>()? }; // Send SignatureShares to the server + let send_signature_shares_args = SendSignatureSharesArgs { + identifier: *identifier, + signature_share: signature_shares, + }; let res = server - .post("/send_signature_share") + .post("/send") .authorization_bearer(token) - .json(&server::SendSignatureShareArgs { - identifier: (*identifier).into(), + .json(&server::SendArgs { session_id, - signature_share: signature_share - .iter() - .map(|s| std::convert::Into::::into(*s)) - .collect(), + // Empty recipients: Coordinator + recipients: vec![], + msg: serde_json::to_vec(&send_signature_shares_args)?, }) .await; res.assert_status_ok(); } // As the coordinator, get SignatureShares - let res = server - .post("/get_signature_shares") - .authorization_bearer(token) - .json(&server::GetSignatureSharesArgs { session_id }) - .await; - res.assert_status_ok(); - let r: server::GetSignatureSharesOutput = res.json(); + loop { + let r = server + .post("/receive") + .authorization_bearer(alice_token) + .json(&server::ReceiveArgs { + session_id, + as_coordinator: true, + }) + .await + .json::(); + for msg in r.msgs { + coordinator_state.recv(msg)?; + } + tokio::time::sleep(Duration::from_secs(2)).await; + if coordinator_state.has_signature_shares() { + break; + } + } - let signature_shares = r - .signature_shares - .iter() - .map(|m| { - m.iter() - .map(|(i, s)| Ok((i.try_into()?, s.try_into()?))) - .collect::, Box>>() - }) - .collect::, _>>()?; + let signature_shares = coordinator_state.signature_shares()?; // Generate the final Signature for each message let signatures = if rerandomized { @@ -310,7 +346,7 @@ async fn test_main_router< // Close the session let res = server .post("/close_session") - .authorization_bearer(token) + .authorization_bearer(alice_token) .json(&server::CloseSessionArgs { session_id }) .await; res.assert_status_ok();