Skip to content

Commit

Permalink
also write contacts to configs; move URL from Participant to Group; a…
Browse files Browse the repository at this point in the history
…dd 'groups'
  • Loading branch information
conradoplg authored and natalieesk committed Oct 2, 2024
1 parent f449bbe commit 5b3c14c
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 10 deletions.
10 changes: 10 additions & 0 deletions frost-client/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ pub(crate) enum Command {
/// 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).
Expand All @@ -92,4 +95,11 @@ pub(crate) enum Command {
#[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 == "ed25519" {
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(),
})
}
}
47 changes: 40 additions & 7 deletions frost-client/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ use std::{
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,11 +29,22 @@ 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 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;
/// it is the key in the `registry` map in Config.
#[derive(Clone, Debug, Serialize, Deserialize)]
Expand Down Expand Up @@ -65,6 +76,7 @@ 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",
Expand All @@ -77,11 +89,34 @@ pub struct Group {
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!("\tName: {}", contact.name);
}
Ok(s)
}
}

/// A FROST group participant.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Participant {
/// The identifier of the participant in the group.
Expand All @@ -96,8 +131,6 @@ pub struct Participant {
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: 4 additions & 1 deletion frost-client/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
pub mod args;
pub mod ciphersuite_helper;
pub mod config;
pub mod contact;
pub mod group;
pub mod init;
pub mod login;
pub mod write_atomic;
pub mod trusted_dealer;
pub mod write_atomic;

use std::error::Error;

Expand All @@ -21,6 +23,7 @@ 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),
}?;

Ok(())
Expand Down
22 changes: 20 additions & 2 deletions frost-client/src/trusted_dealer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use trusted_dealer::MaybeIntoEvenY;
use crate::{
args::Command,
config::{Config, Group, Participant},
contact::Contact,
};

pub(crate) fn trusted_dealer(args: &Command) -> Result<(), Box<dyn Error>> {
Expand All @@ -31,6 +32,7 @@ pub(crate) fn trusted_dealer_for_ciphersuite<C: Ciphersuite + MaybeIntoEvenY + '
ciphersuite: _,
threshold,
num_signers,
names,
} = (*args).clone()
else {
panic!("invalid Command");
Expand All @@ -41,6 +43,9 @@ pub(crate) fn trusted_dealer_for_ciphersuite<C: Ciphersuite + MaybeIntoEvenY + '
eyre!("The `config` option must specify `num_signers` different config files").into(),
);
}
if names.len() != num_signers as usize {
return Err(eyre!("The `names` option must specify `num_signers` names").into());
}

let trusted_dealer_config = trusted_dealer::Config {
max_signers: num_signers,
Expand All @@ -55,34 +60,47 @@ pub(crate) fn trusted_dealer_for_ciphersuite<C: Ciphersuite + MaybeIntoEvenY + '

// First pass over configs; create participants map
let mut participants = BTreeMap::new();
for (identifier, path) in shares.keys().zip(config.iter()) {
let mut contacts = Vec::new();
for ((identifier, path), name) in shares.keys().zip(config.iter()).zip(names.iter()) {
let config = Config::read(Some(path.to_string()))?;
let pubkey = config
.communication_key
.ok_or_eyre("config not initialized")?
.pubkey;
let participant = Participant {
identifier: identifier.serialize(),
pubkey: pubkey.clone(),
username: None,
};
participants.insert(hex::encode(identifier.serialize()), participant);
let contact = Contact {
version: None,
name: name.clone(),
pubkey,
server_url: None,
username: None,
};
participants.insert(hex::encode(identifier.serialize()), participant);
contacts.push(contact);
}

// Second pass over configs; write group information
for (share, path) in shares.values().zip(config.iter()) {
let mut config = Config::read(Some(path.to_string()))?;
let key_package: KeyPackage<C> = share.clone().try_into()?;
let group = Group {
ciphersuite: C::ID.to_string(),
key_package: postcard::to_allocvec(&key_package)?,
public_key_package: postcard::to_allocvec(&public_key_package)?,
participant: participants.clone(),
server_url: None,
};
config.group.insert(
hex::encode(public_key_package.verifying_key().serialize()?),
group,
);
for c in &contacts {
config.contact.insert(c.name.clone(), c.clone());
}
config.write()?;
}

Expand Down

0 comments on commit 5b3c14c

Please sign in to comment.