Skip to content

Commit

Permalink
frost-client: support multiple sessions (#399)
Browse files Browse the repository at this point in the history
  • Loading branch information
conradoplg authored Dec 26, 2024
1 parent 11c4583 commit 3385036
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 4 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion frost-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ 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"
itertools = "0.13.0"
itertools = "0.13.0"
xeddsa = "1.0.2"
19 changes: 19 additions & 0 deletions frost-client/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,21 @@ pub(crate) enum Command {
#[arg(short, long)]
group: String,
},
/// Lists the active FROST signing sessions the user is in.
Sessions {
/// The path to the config file to manage. If not specified, it uses
/// $HOME/.local/frost/credentials.toml
#[arg(short, long)]
config: Option<String>,
/// The server URL to use. If `group` is specified and `server_url`
/// is not, it will use the server URL associated with `group` if any.
#[arg(short, long)]
server_url: Option<String>,
/// Optional group whose associated server URL will be used, identified
/// by the group public key (use `groups` to list).
#[arg(short, long)]
group: Option<String>,
},
Coordinator {
/// The path to the config file to manage. If not specified, it uses
/// $HOME/.local/frost/credentials.toml
Expand Down Expand Up @@ -155,5 +170,9 @@ pub(crate) enum Command {
/// to list)
#[arg(short, long)]
group: String,
/// The session ID to use (use `sessions` to list). Can be omitted in
/// case there is a single active session.
#[arg(short = 'S', long)]
session: Option<String>,
},
}
2 changes: 1 addition & 1 deletion frost-client/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ impl Group {
);
for participant in self.participant.values() {
let contact = config.contact_by_pubkey(&participant.pubkey)?;
s += &format!("\t{} ({})\n", contact.name, hex::encode(contact.pubkey));
s += &format!("\t{}\t({})\n", contact.name, hex::encode(contact.pubkey));
}
Ok(s)
}
Expand Down
2 changes: 2 additions & 0 deletions frost-client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod coordinator;
pub mod group;
pub mod init;
pub mod participant;
pub mod session;
pub mod trusted_dealer;
pub mod write_atomic;

Expand All @@ -27,6 +28,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
Command::RemoveContact { .. } => contact::remove(&args.command),
Command::Groups { .. } => group::list(&args.command),
Command::RemoveGroup { .. } => group::remove(&args.command),
Command::Sessions { .. } => session::list(&args.command).await,
Command::TrustedDealer { .. } => trusted_dealer::trusted_dealer(&args.command),
Command::Coordinator { .. } => crate::coordinator::run(&args.command).await,
Command::Participant { .. } => crate::participant::run(&args.command).await,
Expand Down
3 changes: 2 additions & 1 deletion frost-client/src/participant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub(crate) async fn run_for_ciphersuite<C: RandomizedCiphersuite + 'static>(
config,
server_url,
group,
session,
} = (*args).clone()
else {
panic!("invalid Command");
Expand Down Expand Up @@ -72,7 +73,7 @@ pub(crate) async fn run_for_ciphersuite<C: RandomizedCiphersuite + 'static>(
.ok_or_eyre("host missing in URL")?
.to_owned(),
port: server_url_parsed.port().unwrap_or(2744),
session_id: String::new(),
session_id: session.unwrap_or_default(),
comm_privkey: Some(
config
.communication_key
Expand Down
131 changes: 131 additions & 0 deletions frost-client/src/session.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
use std::error::Error;

use eyre::{eyre, OptionExt as _};
use rand::thread_rng;
use xeddsa::{xed25519, Sign as _};

use crate::{args::Command, config::Config};

pub(crate) async fn list(args: &Command) -> Result<(), Box<dyn Error>> {
let Command::Sessions {
config,
group,
server_url,
} = (*args).clone()
else {
panic!("invalid Command");
};

let config = Config::read(config)?;

let server_url = if let Some(server_url) = server_url {
server_url
} else if let Some(group) = group {
let group = config.group.get(&group).ok_or_eyre("Group not found")?;
group
.server_url
.clone()
.ok_or_eyre("the group specified does not have an associated server URL")?
} else {
return Err(eyre!("must specify either server_url or group").into());
};

let comm_privkey = config
.communication_key
.clone()
.ok_or_eyre("user not initialized")?
.privkey
.clone();
let comm_pubkey = config
.communication_key
.clone()
.ok_or_eyre("user not initialized")?
.pubkey
.clone();

let client = reqwest::Client::new();
let host_port = format!("http://{}", server_url);

let mut rng = thread_rng();

let challenge = client
.post(format!("{}/challenge", host_port))
.json(&server::ChallengeArgs {})
.send()
.await?
.json::<server::ChallengeOutput>()
.await?
.challenge;

let privkey = xed25519::PrivateKey::from(
&TryInto::<[u8; 32]>::try_into(comm_privkey.clone())
.map_err(|_| eyre!("invalid comm_privkey"))?,
);
let signature: [u8; 64] = privkey.sign(challenge.as_bytes(), &mut rng);

let access_token = client
.post(format!("{}/login", host_port))
.json(&server::KeyLoginArgs {
uuid: challenge,
pubkey: comm_pubkey.clone(),
signature: signature.to_vec(),
})
.send()
.await?
.json::<server::LoginOutput>()
.await?
.access_token
.to_string();

// Get session ID from server
let r = client
.post(format!("{}/list_sessions", host_port))
.bearer_auth(&access_token)
.send()
.await?
.json::<server::ListSessionsOutput>()
.await?;

if r.session_ids.is_empty() {
eprintln!("No active sessions.");
} else {
for session_id in r.session_ids {
let r = client
.post(format!("{}/get_session_info", host_port))
.bearer_auth(&access_token)
.json(&server::GetSessionInfoArgs { session_id })
.send()
.await?
.json::<server::GetSessionInfoOutput>()
.await?;
let coordinator = config.contact_by_pubkey(&r.coordinator_pubkey);
let participants: Vec<_> = r
.pubkeys
.iter()
.map(|pubkey| config.contact_by_pubkey(pubkey))
.collect();
eprintln!("Session with ID {}", session_id);
eprintln!(
"Coordinator: {}",
coordinator
.map(|c| c.name)
.unwrap_or("(Unknown contact)".to_string())
);
eprintln!("Signers: {}", participants.len());
for participant in participants {
if let Ok(participant) = participant {
eprintln!(
"\t{}\t({})",
participant.name,
hex::encode(participant.pubkey)
);
} else {
eprintln!("\t(Unknown contact)");
}
}
eprintln!();
}
}

Ok(())
}
2 changes: 1 addition & 1 deletion participant/src/comms/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ where
.json::<server::ListSessionsOutput>()
.await?;
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());
return Err(eyre!("user has more than one FROST session active; use `frost-client sessions` to list them and specify the session ID with `-S`").into());
} else if r.session_ids.is_empty() {
return Err(eyre!("User has no current sessions active. The Coordinator should either specify your username, or manually share the session ID which you can specify with --session_id").into());
}
Expand Down

0 comments on commit 3385036

Please sign in to comment.