Skip to content

Commit

Permalink
frost-client, dkg: add support for DKG with server (#423)
Browse files Browse the repository at this point in the history
* frost-client, dkg: add support for DKG with server

* comment out test for now
  • Loading branch information
conradoplg authored Jan 15, 2025
1 parent 54537ba commit ef37c44
Show file tree
Hide file tree
Showing 18 changed files with 1,379 additions and 322 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

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

7 changes: 7 additions & 0 deletions dkg/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
async-trait = "0.1"
eyre = "0.6.12"
frost-core = { version = "2.0.0", features = ["serde"] }
frost-ed25519 = { version = "2.0.0", features = ["serde"] }
Expand All @@ -18,6 +19,12 @@ serde_json = "1.0"
itertools = "0.13.0"
exitcode = "1.1.2"
pipe = "0.4.0"
frostd = { path = "../frostd" }
participant = { path = "../participant" }
xeddsa = "1.0.2"
reqwest = { version = "0.12.9", features = ["json"] }
tokio = { version = "1", features = ["full"] }
snow = "0.9.6"

[features]
default = []
68 changes: 68 additions & 0 deletions dkg/src/args.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,76 @@
use std::rc::Rc;

use clap::Parser;
use frost_core::{Ciphersuite, Identifier};

#[derive(Parser, Debug, Default)]
#[command(author, version, about, long_about = None)]
pub struct Args {
#[arg(short = 'C', long, default_value = "ed25519")]
pub ciphersuite: String,
}

#[derive(Clone)]
pub struct ProcessedArgs<C: Ciphersuite> {
/// CLI mode. If enabled, it will prompt for inputs from stdin
/// and print values to stdout, ignoring other flags.
pub cli: bool,

/// HTTP mode. If enabled, it will use HTTP communication with a
/// FROST server.
pub http: bool,

/// IP to connect to, if using HTTP mode.
pub ip: String,

/// Port to connect to, if using HTTP mode.
pub port: u16,

/// The participant's communication private key for HTTP mode.
pub comm_privkey: Option<Vec<u8>>,

/// The participant's communication public key for HTTP mode.
pub comm_pubkey: Option<Vec<u8>>,

/// A function that confirms that a public key from the server is trusted by
/// the user; returns the same public key. For HTTP mode.
// It is a `Rc<dyn Fn>` to make it easier to use;
// using `fn()` would preclude using closures and using generics would
// require a lot of code change for something simple.
#[allow(clippy::type_complexity)]
pub comm_participant_pubkey_getter: Option<Rc<dyn Fn(&Vec<u8>) -> Option<Vec<u8>>>>,

/// The threshold to use for the shares
pub min_signers: u16,

/// The total number of signers. Only needed for CLI mode.
pub max_signers: Option<u16>,

/// The list of pubkeys for the other participants. This is only required
/// for the first participant who creates the DKG session.
pub participants: Vec<Vec<u8>>,

/// Identifier to use for the participant. Only needed for CLI mode.
pub identifier: Option<Identifier<C>>,
}

impl<C> ProcessedArgs<C>
where
C: Ciphersuite,
{
pub(crate) fn new(config: &crate::inputs::Config<C>) -> Self {
Self {
cli: true,
http: false,
ip: String::new(),
port: 0,
comm_privkey: None,
comm_pubkey: None,
comm_participant_pubkey_getter: None,
min_signers: config.min_signers,
max_signers: Some(config.max_signers),
participants: Vec::new(),
identifier: Some(config.identifier),
}
}
}
122 changes: 56 additions & 66 deletions dkg/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
use eyre::eyre;
use frost_core::keys::{KeyPackage, PublicKeyPackage};
use frost_core::{self as frost, Ciphersuite};
use frost_core::{self as frost, Ciphersuite, Identifier};

use rand::thread_rng;
use reddsa::frost::redpallas::keys::EvenY;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::error::Error;
use std::io::{BufRead, Write};

use crate::inputs::{read_round1_package, read_round2_package, request_inputs};
use crate::args::ProcessedArgs;
use crate::comms::cli::CLIComms;
use crate::comms::http::HTTPComms;
use crate::comms::Comms;
use crate::inputs::request_inputs;

// The redpallas ciphersuite, when used for generating Orchard spending key
// signatures, requires ensuring public key have an even Y coordinate. Since the
Expand Down Expand Up @@ -39,91 +45,75 @@ impl MaybeIntoEvenY for reddsa::frost::redpallas::PallasBlake2b512 {
}
}

pub fn cli<C: Ciphersuite + 'static + MaybeIntoEvenY>(
pub async fn cli<C: Ciphersuite + 'static + MaybeIntoEvenY>(
reader: &mut impl BufRead,
logger: &mut impl Write,
) -> Result<(), Box<dyn std::error::Error>> {
let config = request_inputs::<C>(reader, logger)?;
let pargs = ProcessedArgs::<C>::new(&config);

let rng = thread_rng();

let (secret_package, package) = frost::keys::dkg::part1(
config.identifier,
config.max_signers,
config.min_signers,
rng,
)?;

writeln!(logger, "\n=== ROUND 1: SEND PACKAGES ===\n")?;
let (key_package, public_key_package, _) =
cli_for_processed_args(pargs, reader, logger).await?;

writeln!(
logger,
"Round 1 Package to send to all other participants (your identifier: {}):\n\n{}\n",
serde_json::to_string(&config.identifier)?,
serde_json::to_string(&package)?
"Participant key package:\n\n{}\n",
serde_json::to_string(&key_package)?,
)?;

writeln!(logger, "=== ROUND 1: RECEIVE PACKAGES ===\n")?;

writeln!(
logger,
"Input Round 1 Packages from the other {} participants.\n",
config.max_signers - 1,
"Participant public key package:\n\n{}\n",
serde_json::to_string(&public_key_package)?,
)?;
let mut received_round1_packages = BTreeMap::new();
for _ in 0..config.max_signers - 1 {
let (identifier, round1_package) = read_round1_package(reader, logger)?;
received_round1_packages.insert(identifier, round1_package);
writeln!(logger)?;
}

let (round2_secret_package, round2_packages) =
frost::keys::dkg::part2(secret_package, &received_round1_packages)?;

writeln!(logger, "=== ROUND 2: SEND PACKAGES ===\n")?;

for (identifier, package) in round2_packages {
writeln!(
logger,
"Round 2 Package to send to participant {} (your identifier: {}):\n\n{}\n",
serde_json::to_string(&identifier)?,
serde_json::to_string(&config.identifier)?,
serde_json::to_string(&package)?
)?;
}
Ok(())
}

writeln!(logger, "=== ROUND 2: RECEIVE PACKAGES ===\n")?;
pub async fn cli_for_processed_args<C: Ciphersuite + 'static + MaybeIntoEvenY>(
pargs: ProcessedArgs<C>,
input: &mut impl BufRead,
logger: &mut impl Write,
) -> Result<
(
KeyPackage<C>,
PublicKeyPackage<C>,
HashMap<Vec<u8>, Identifier<C>>,
),
Box<dyn Error>,
> {
let mut comms: Box<dyn Comms<C>> = if pargs.cli {
Box::new(CLIComms::new(&pargs))
} else if pargs.http {
Box::new(HTTPComms::new(&pargs)?)
} else {
return Err(eyre!("either --cli or --http must be specified").into());
};

writeln!(
logger,
"Input Round 2 Packages from the other {} participants.\n",
config.max_signers - 1,
)?;
let mut received_round2_packages = BTreeMap::new();
for _ in 0..config.max_signers - 1 {
let (identifier, round2_package) = read_round2_package(reader, logger)?;
received_round2_packages.insert(identifier, round2_package);
writeln!(logger)?;
}
let rng = thread_rng();

let (identifier, max_signers) = comms.get_identifier(input, logger).await?;

writeln!(logger, "=== DKG FINISHED ===")?;
let (round1_secret_package, round1_package) =
frost::keys::dkg::part1(identifier, max_signers, pargs.min_signers, rng)?;

let received_round1_packages = comms
.get_round1_packages(input, logger, round1_package)
.await?;

let (round2_secret_package, round2_packages) =
frost::keys::dkg::part2(round1_secret_package, &received_round1_packages)?;

let received_round2_packages = comms
.get_round2_packages(input, logger, round2_packages)
.await?;

let (key_package, public_key_package) = MaybeIntoEvenY::into_even_y(frost::keys::dkg::part3(
&round2_secret_package,
&received_round1_packages,
&received_round2_packages,
)?);

writeln!(
logger,
"Participant key package:\n\n{}\n",
serde_json::to_string(&key_package)?,
)?;
writeln!(
logger,
"Participant public key package:\n\n{}\n",
serde_json::to_string(&public_key_package)?,
)?;
let pubkey_map = comms.get_pubkey_identifier_map()?;

Ok(())
Ok((key_package, public_key_package, pubkey_map))
}
43 changes: 43 additions & 0 deletions dkg/src/comms.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
pub mod cli;
pub mod http;

use frost_core::{
self as frost,
keys::dkg::{round1, round2},
Ciphersuite,
};

use std::{
collections::{BTreeMap, HashMap},
error::Error,
io::{BufRead, Write},
};

use async_trait::async_trait;

use frost::Identifier;

#[async_trait(?Send)]
pub trait Comms<C: Ciphersuite> {
async fn get_identifier(
&mut self,
input: &mut dyn BufRead,
output: &mut dyn Write,
) -> Result<(Identifier<C>, u16), Box<dyn Error>>;

async fn get_round1_packages(
&mut self,
input: &mut dyn BufRead,
output: &mut dyn Write,
round1_package: round1::Package<C>,
) -> Result<BTreeMap<Identifier<C>, round1::Package<C>>, Box<dyn Error>>;

async fn get_round2_packages(
&mut self,
input: &mut dyn BufRead,
output: &mut dyn Write,
round2_packages: BTreeMap<Identifier<C>, round2::Package<C>>,
) -> Result<BTreeMap<Identifier<C>, round2::Package<C>>, Box<dyn Error>>;

fn get_pubkey_identifier_map(&self) -> Result<HashMap<Vec<u8>, Identifier<C>>, Box<dyn Error>>;
}
Loading

0 comments on commit ef37c44

Please sign in to comment.