Skip to content

Commit

Permalink
add frost-client trusted-dealer
Browse files Browse the repository at this point in the history
  • Loading branch information
conradoplg committed Sep 20, 2024
1 parent d2adff8 commit 8a0467e
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 64 deletions.
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"
22 changes: 22 additions & 0 deletions frost-client/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,26 @@ 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>,
#[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,
},
}
18 changes: 17 additions & 1 deletion frost-client/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub struct Config {
pub contact: BTreeMap<String, Contact>,
/// The FROST groups the user belongs to, keyed by (TODO)
#[serde(default)]
pub groups: BTreeMap<String, Group>,
pub group: BTreeMap<String, Group>,
}

/// A registry entry. Note that the server URL is not in the struct;
Expand Down Expand Up @@ -66,8 +66,16 @@ pub struct CommunicationKey {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Group {
/// 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)
pub participant: BTreeMap<String, Participant>,
Expand All @@ -77,8 +85,16 @@ pub struct Group {
#[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>,
Expand Down
1 change: 1 addition & 0 deletions frost-client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod contact;
pub mod init;
pub mod login;
pub mod write_atomic;
pub mod trusted_dealer;

use std::error::Error;

Expand Down
90 changes: 90 additions & 0 deletions frost-client/src/trusted_dealer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use eyre::{eyre, OptionExt};
use std::{collections::BTreeMap, error::Error};

use frost_core::{keys::KeyPackage, Ciphersuite};
use frost_ed25519::Ed25519Sha512;
use rand::thread_rng;
use trusted_dealer::MaybeIntoEvenY;

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

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

if ciphersuite == "ed25519" {
trusted_dealer_for_ciphersuite::<Ed25519Sha512>(args)
} else {
Err(eyre!("unsupported ciphersuite").into())
}
}

pub(crate) fn trusted_dealer_for_ciphersuite<C: Ciphersuite + MaybeIntoEvenY + 'static>(
args: &Command,
) -> Result<(), Box<dyn Error>> {
let Command::TrustedDealer {
config,
ciphersuite: _,
threshold,
num_signers,
} = (*args).clone()
else {
panic!("invalid Command");
};

if config.len() != num_signers as usize {
return Err(
eyre!("The `config` option must specify `num_signers` different config files").into(),
);
}

let trusted_dealer_config = trusted_dealer::Config {
max_signers: num_signers,
min_signers: threshold,
secret: vec![],
};
let mut rng = thread_rng();

// Generate key shares
let (shares, public_key_package) =
trusted_dealer::trusted_dealer::<C, _>(&trusted_dealer_config, &mut rng)?;

// First pass over configs; create participants map
let mut participants = BTreeMap::new();
for (identifier, path) in shares.keys().zip(config.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,
server_url: None,
username: None,
};
participants.insert(hex::encode(identifier.serialize()), participant);
}

// 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 {
key_package: postcard::to_allocvec(&key_package)?,
public_key_package: postcard::to_allocvec(&public_key_package)?,
participant: participants.clone(),
};
config.group.insert(
hex::encode(public_key_package.verifying_key().serialize()?),
group,
);
config.write()?;
}

Ok(())
}
62 changes: 4 additions & 58 deletions trusted-dealer/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,59 +1,11 @@
use rand::thread_rng;
use std::collections::BTreeMap;
use std::io::{BufRead, Write};

use frost_core::keys::{IdentifierList, PublicKeyPackage, SecretShare};
use frost_core::{Ciphersuite, Identifier};
use reddsa::frost::redpallas::keys::EvenY;
use frost_core::Ciphersuite;

use crate::args::Args;
use crate::inputs::{print_values, request_inputs};
use crate::trusted_dealer_keygen::{split_secret, trusted_dealer_keygen};

// The redpallas ciphersuite, when used for generating Orchard spending key
// signatures, requires ensuring public key have an even Y coordinate. Since the
// code uses generics, this trait is used to convert if needed depending on the
// ciphersuite.
//
// If you are adding a new ciphersuite to this tool which does note require
// this, just implement it and the default implementation (which does nothing)
// will suffice. See below.
pub trait MaybeIntoEvenY: Ciphersuite {
fn into_even_y(
secret_shares_and_public_key_package: (
BTreeMap<Identifier<Self>, SecretShare<Self>>,
PublicKeyPackage<Self>,
),
) -> (
BTreeMap<Identifier<Self>, SecretShare<Self>>,
PublicKeyPackage<Self>,
) {
secret_shares_and_public_key_package
}
}

// A ciphersuite that does not need the conversion.
impl MaybeIntoEvenY for frost_ed25519::Ed25519Sha512 {}

impl MaybeIntoEvenY for reddsa::frost::redpallas::PallasBlake2b512 {
fn into_even_y(
(secret_shares, public_key_package): (
BTreeMap<Identifier<Self>, SecretShare<Self>>,
PublicKeyPackage<Self>,
),
) -> (
BTreeMap<Identifier<Self>, SecretShare<Self>>,
PublicKeyPackage<Self>,
) {
let is_even = public_key_package.has_even_y();
let public_key_package = public_key_package.into_even_y(Some(is_even));
let secret_shares = secret_shares
.iter()
.map(|(i, s)| (*i, s.clone().into_even_y(Some(is_even))))
.collect();
(secret_shares, public_key_package)
}
}
use crate::{trusted_dealer, MaybeIntoEvenY};

pub fn cli<C: Ciphersuite + 'static + MaybeIntoEvenY>(
args: &Args,
Expand All @@ -64,15 +16,9 @@ pub fn cli<C: Ciphersuite + 'static + MaybeIntoEvenY>(

let mut rng = thread_rng();

let shares_and_package = if config.secret.is_empty() {
trusted_dealer_keygen(&config, IdentifierList::<C>::Default, &mut rng)?
} else {
split_secret(&config, IdentifierList::<C>::Default, &mut rng)?
};

let (shares, pubkeys) = MaybeIntoEvenY::into_even_y(shares_and_package);
let (shares, pubkeys) = trusted_dealer(&config, &mut rng)?;

print_values(args, &shares, &pubkeys, logger)?;
print_values::<C>(args, &shares, &pubkeys, logger)?;

Ok(())
}
75 changes: 75 additions & 0 deletions trusted-dealer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,78 @@ pub mod args;
pub mod cli;
pub mod inputs;
pub mod trusted_dealer_keygen;

pub use inputs::Config;

use rand::{CryptoRng, RngCore};
use std::collections::BTreeMap;

use frost_core::keys::{IdentifierList, PublicKeyPackage, SecretShare};
use frost_core::{Ciphersuite, Identifier};
use reddsa::frost::redpallas::keys::EvenY;

use crate::trusted_dealer_keygen::{split_secret, trusted_dealer_keygen};

// The redpallas ciphersuite, when used for generating Orchard spending key
// signatures, requires ensuring public key have an even Y coordinate. Since the
// code uses generics, this trait is used to convert if needed depending on the
// ciphersuite.
//
// If you are adding a new ciphersuite to this tool which does note require
// this, just implement it and the default implementation (which does nothing)
// will suffice. See below.
pub trait MaybeIntoEvenY: Ciphersuite {
fn into_even_y(
secret_shares_and_public_key_package: (
BTreeMap<Identifier<Self>, SecretShare<Self>>,
PublicKeyPackage<Self>,
),
) -> (
BTreeMap<Identifier<Self>, SecretShare<Self>>,
PublicKeyPackage<Self>,
) {
secret_shares_and_public_key_package
}
}

// A ciphersuite that does not need the conversion.
impl MaybeIntoEvenY for frost_ed25519::Ed25519Sha512 {}

impl MaybeIntoEvenY for reddsa::frost::redpallas::PallasBlake2b512 {
fn into_even_y(
(secret_shares, public_key_package): (
BTreeMap<Identifier<Self>, SecretShare<Self>>,
PublicKeyPackage<Self>,
),
) -> (
BTreeMap<Identifier<Self>, SecretShare<Self>>,
PublicKeyPackage<Self>,
) {
let is_even = public_key_package.has_even_y();
let public_key_package = public_key_package.into_even_y(Some(is_even));
let secret_shares = secret_shares
.iter()
.map(|(i, s)| (*i, s.clone().into_even_y(Some(is_even))))
.collect();
(secret_shares, public_key_package)
}
}

#[allow(clippy::type_complexity)]
pub fn trusted_dealer<C: Ciphersuite + 'static + MaybeIntoEvenY, R: RngCore + CryptoRng>(
config: &Config,
rng: &mut R,
) -> Result<
(BTreeMap<Identifier<C>, SecretShare<C>>, PublicKeyPackage<C>),
Box<dyn std::error::Error>,
> {
let shares_and_package = if config.secret.is_empty() {
trusted_dealer_keygen(config, IdentifierList::<C>::Default, rng)?
} else {
split_secret(config, IdentifierList::<C>::Default, rng)?
};

let (shares, pubkeys) = MaybeIntoEvenY::into_even_y(shares_and_package);

Ok((shares, pubkeys))
}
Loading

0 comments on commit 8a0467e

Please sign in to comment.