Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add frost-client trusted-dealer #309

Merged
merged 3 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.lock

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

6 changes: 6 additions & 0 deletions frost-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ serde = { version = "1.0", features = ["derive"] }
snow = "0.9.6"
toml = "0.8.19"
server = { path = "../server" }
trusted-dealer = { path = "../trusted-dealer" }
eyre = "0.6.12"
rpassword = "7.3.1"
directories = "5.0.1"
Expand All @@ -20,3 +21,8 @@ serdect = "0.2.0"
bech32 = "0.11.0"
postcard = "1.0.10"
tempfile = "3.12.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"] }
rand = "0.8"
32 changes: 32 additions & 0 deletions frost-client/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,36 @@ pub(crate) enum Command {
#[arg(short, long)]
config: Option<String>,
},
TrustedDealer {
/// The path to the config file to manage.
///
/// You can specify `num_signers` different paths. In that case, the
/// group information will be added to each config file. This is
/// particularly useful for tests, where all participants are run in the
/// same machine with a config file for each.
///
/// If a single path is specified (or none, which will use
/// $HOME/.local/frost/credentials.toml), then it will run the trusted
/// dealer process via the FROST server (TODO: this is not supported yet)
#[arg(short, long)]
config: Vec<String>,
/// The name of each participant.
#[arg(short = 'N', long, value_delimiter = ',')]
names: Vec<String>,
#[arg(short = 'C', long, default_value = "ed25519")]
ciphersuite: String,
/// The threshold (minimum number of signers).
#[arg(short = 't', long, default_value_t = 2)]
threshold: u16,
/// The total number of participants (maximum number of signers).
#[arg(short = 'n', long, default_value_t = 3)]
num_signers: u16,
},
/// Lists the groups the user is in.
Groups {
/// The path to the config file to manage. If not specified, it uses
/// $HOME/.local/frost/credentials.toml
#[arg(short, long)]
config: Option<String>,
},
}
72 changes: 72 additions & 0 deletions frost-client/src/ciphersuite_helper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use std::{error::Error, marker::PhantomData};

use eyre::eyre;
use frost_core::{
keys::{KeyPackage, PublicKeyPackage},
Ciphersuite,
};
use frost_ed25519::Ed25519Sha512;

/// Additional information about a group, derived from the key packages.
#[derive(Debug, Clone)]
pub struct GroupInfo {
pub hex_verifying_key: String,
pub threshold: usize,
pub num_participants: usize,
}

/// A trait that helps obtaining ciphersuite-dependent information.
pub trait CiphersuiteHelper {
fn group_info(
&self,
encoded_key_package: &[u8],
encoded_public_key_package: &[u8],
) -> Result<GroupInfo, Box<dyn Error>>;
}

/// An implementation of CiphersuiteHelper that works for any Ciphersuite.
struct CiphersuiteHelperImpl<C: Ciphersuite> {
_phantom: PhantomData<C>,
}

impl<C> Default for CiphersuiteHelperImpl<C>
where
C: Ciphersuite,
{
fn default() -> Self {
Self {
_phantom: Default::default(),
}
}
}

/// Get a CiphersuiteHelper for the given ciphersuite.
pub(crate) fn ciphersuite_helper(
ciphersuite_id: &str,
) -> Result<Box<dyn CiphersuiteHelper>, Box<dyn Error>> {
if ciphersuite_id == Ed25519Sha512::ID {
return Ok(Box::new(CiphersuiteHelperImpl::<Ed25519Sha512>::default()));
}
Err(eyre!("invalid ciphersuite ID").into())
}

impl<C> CiphersuiteHelper for CiphersuiteHelperImpl<C>
where
C: Ciphersuite + 'static,
{
fn group_info(
&self,
encoded_key_package: &[u8],
encoded_public_key_package: &[u8],
) -> Result<GroupInfo, Box<dyn Error>> {
let key_package: KeyPackage<C> = postcard::from_bytes(encoded_key_package)?;
let public_key_package: PublicKeyPackage<C> =
postcard::from_bytes(encoded_public_key_package)?;
let hex_verifying_key = hex::encode(public_key_package.verifying_key().serialize()?);
Ok(GroupInfo {
hex_verifying_key,
threshold: *key_package.min_signers() as usize,
num_participants: public_key_package.verifying_shares().len(),
})
}
}
67 changes: 57 additions & 10 deletions frost-client/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@ use core::str;
use std::{
collections::BTreeMap,
error::Error,
fs::File,
os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
str::FromStr,
};

use eyre::eyre;
use eyre::{eyre, OptionExt};
use serde::{Deserialize, Serialize};

use crate::{contact::Contact, write_atomic};
use crate::{ciphersuite_helper::ciphersuite_helper, contact::Contact, write_atomic};

/// The config file, which is serialized with serde.
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
Expand All @@ -29,9 +27,20 @@ pub struct Config {
/// The address book of the user, keyed by each contact's name.
#[serde(default)]
pub contact: BTreeMap<String, Contact>,
/// The FROST groups the user belongs to, keyed by (TODO)
/// The FROST groups the user belongs to, keyed by hex-encoded verifying key
#[serde(default)]
pub groups: BTreeMap<String, Group>,
pub group: BTreeMap<String, Group>,
}

impl Config {
pub fn contact_by_pubkey(&self, pubkey: &[u8]) -> Result<Contact, Box<dyn Error>> {
Ok(self
.contact
.values()
.find(|c| c.pubkey == pubkey)
.cloned()
.ok_or_eyre("contact not found")?)
}
}

/// A registry entry. Note that the server URL is not in the struct;
Expand Down Expand Up @@ -65,23 +74,61 @@ pub struct CommunicationKey {
// TODO: add a textual name for the group?
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Group {
pub ciphersuite: String,
/// The encoded public key package for the group.
#[serde(
serialize_with = "serdect::slice::serialize_hex_lower_or_bin",
deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec"
)]
pub public_key_package: Vec<u8>,
/// The user's encodede key package for the group.
#[serde(
serialize_with = "serdect::slice::serialize_hex_lower_or_bin",
deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec"
)]
pub key_package: Vec<u8>,
/// The group participants, keyed by (TODO)
/// The server the participants are registered in, if any.
pub server_url: Option<String>,
/// The group participants, keyed by hex-encoded identifier
pub participant: BTreeMap<String, Participant>,
}

/// A FROST grou participant.
impl Group {
/// Returns a human-readable summary of the contact; used when it is
/// printed to the terminal.
pub fn as_human_readable_summary(&self, config: &Config) -> Result<String, Box<dyn Error>> {
let helper = ciphersuite_helper(&self.ciphersuite)?;
let info = helper.group_info(&self.key_package, &self.public_key_package)?;
let mut s = format!(
"Group with public key {}\nServer URL: {}\nThreshold: {}\nParticipants: {}\n",
info.hex_verifying_key,
self.server_url.clone().unwrap_or_default(),
info.threshold,
info.num_participants
);
for participant in self.participant.values() {
let contact = config.contact_by_pubkey(&participant.pubkey)?;
s += &format!("\t{}\n", contact.name);
}
Ok(s)
}
}

/// A FROST group participant.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Participant {
/// The identifier of the participant in the group.
#[serde(
serialize_with = "serdect::slice::serialize_hex_lower_or_bin",
deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec"
)]
pub identifier: Vec<u8>,
/// The communication public key for the participant.
#[serde(
serialize_with = "serdect::slice::serialize_hex_lower_or_bin",
deserialize_with = "serdect::slice::deserialize_hex_or_bin_vec"
)]
pub pubkey: Vec<u8>,
/// The server the participant is registered in, if any.
pub server_url: Option<String>,
/// The username of the participant in the server, if any.
pub username: Option<String>,
}
Expand Down
18 changes: 18 additions & 0 deletions frost-client/src/group.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use std::error::Error;

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

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

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

for group in config.group.values() {
eprint!("{}", group.as_human_readable_summary(&config)?);
eprintln!();
}

Ok(())
}
5 changes: 5 additions & 0 deletions frost-client/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
pub mod args;
pub mod ciphersuite_helper;
pub mod config;
pub mod contact;
pub mod group;
pub mod init;
pub mod login;
pub mod trusted_dealer;
pub mod write_atomic;

use std::error::Error;
Expand All @@ -20,6 +23,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
Command::Export { .. } => contact::export(&args.command),
Command::Import { .. } => contact::import(&args.command),
Command::Contacts { .. } => contact::list(&args.command),
Command::Groups { .. } => group::list(&args.command),
Command::TrustedDealer { .. } => trusted_dealer::trusted_dealer(&args.command),
}?;

Ok(())
Expand Down
Loading