Phase 2: add coordinator (#157)
* phase 2: add coordinator * completed, but untested * fix bug * make it work for both ciphersuites * allow generating fresh randomizer * remove test for now
This commit is contained in:
parent
07ea1a1f66
commit
0ecf022bd9
|
@ -408,7 +408,9 @@ dependencies = [
|
|||
"message-io",
|
||||
"rand",
|
||||
"reddsa",
|
||||
"reqwest",
|
||||
"serde_json",
|
||||
"server",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
@ -1891,10 +1893,13 @@ dependencies = [
|
|||
"axum",
|
||||
"axum-test",
|
||||
"clap",
|
||||
"coordinator",
|
||||
"derivative",
|
||||
"eyre",
|
||||
"frost-ed25519",
|
||||
"rand",
|
||||
"reddsa",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
|
@ -17,9 +17,11 @@ serde_json = "1.0"
|
|||
itertools = "0.12.1"
|
||||
exitcode = "1.1.2"
|
||||
clap = { version = "4.5.2", features = ["derive"] }
|
||||
reqwest = { version = "0.11.23", features = ["json"] }
|
||||
server = { path = "../server" }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
message-io = "0.18"
|
||||
|
||||
[features]
|
||||
redpallas = []
|
||||
redpallas = ["server/redpallas"]
|
||||
default = []
|
||||
|
|
|
@ -9,6 +9,11 @@ pub struct Args {
|
|||
#[arg(long, default_value_t = false)]
|
||||
pub cli: bool,
|
||||
|
||||
/// HTTP mode. If enabled, it will use HTTP communication with a
|
||||
/// FROST server.
|
||||
#[arg(long, default_value_t = false)]
|
||||
pub http: bool,
|
||||
|
||||
/// The number of participants. If 0, will prompt for a value.
|
||||
#[arg(short = 'n', long, default_value_t = 0)]
|
||||
pub num_signers: u16,
|
||||
|
@ -24,8 +29,9 @@ pub struct Args {
|
|||
#[arg(short = 'm', long, default_value = "")]
|
||||
pub message: String,
|
||||
|
||||
/// The randomizer to use. Can be a file with the raw randomizer, or "-". If "-"
|
||||
/// is specified, then it will be read from standard input as a hex string.
|
||||
/// The randomizer to use. Can be a file with the raw randomizer, empty, or
|
||||
/// "-". If empty, a random one will be generated. If "-" is specified, then
|
||||
/// it will be read from standard input as a hex string.
|
||||
#[cfg(feature = "redpallas")]
|
||||
#[arg(short = 'r', long, default_value = "")]
|
||||
pub randomizer: String,
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::io::{BufRead, Write};
|
|||
|
||||
use crate::args::Args;
|
||||
use crate::comms::cli::CLIComms;
|
||||
use crate::comms::http::HTTPComms;
|
||||
use crate::comms::socket::SocketComms;
|
||||
use crate::comms::Comms;
|
||||
use crate::step_1::step_1;
|
||||
|
@ -20,6 +21,8 @@ pub async fn cli(
|
|||
|
||||
let mut comms: Box<dyn Comms> = if args.cli {
|
||||
Box::new(CLIComms {})
|
||||
} else if args.http {
|
||||
Box::new(HTTPComms::new(args))
|
||||
} else {
|
||||
Box::new(SocketComms::new(args))
|
||||
};
|
||||
|
@ -39,7 +42,7 @@ pub async fn cli(
|
|||
)?;
|
||||
|
||||
#[cfg(feature = "redpallas")]
|
||||
let randomizer = request_randomizer(args, reader, logger)?;
|
||||
let randomizer = request_randomizer(args, reader, logger, &signing_package)?;
|
||||
|
||||
writeln!(logger, "=== STEP 3: BUILD GROUP SIGNATURE ===\n")?;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
pub mod cli;
|
||||
pub mod http;
|
||||
pub mod socket;
|
||||
|
||||
#[cfg(not(feature = "redpallas"))]
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
//! HTTP implementation of the Comms trait.
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
#[cfg(not(feature = "redpallas"))]
|
||||
use frost_ed25519 as frost;
|
||||
#[cfg(feature = "redpallas")]
|
||||
use reddsa::frost::redpallas as frost;
|
||||
|
||||
use eyre::eyre;
|
||||
use server::Uuid;
|
||||
|
||||
use frost::{
|
||||
keys::PublicKeyPackage, round1::SigningCommitments, round2::SignatureShare, Identifier,
|
||||
SigningPackage,
|
||||
};
|
||||
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
error::Error,
|
||||
io::{BufRead, Write},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use super::Comms;
|
||||
use crate::args::Args;
|
||||
|
||||
pub struct HTTPComms {
|
||||
client: reqwest::Client,
|
||||
host_port: String,
|
||||
session_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
impl HTTPComms {
|
||||
pub fn new(args: &Args) -> Self {
|
||||
let client = reqwest::Client::new();
|
||||
Self {
|
||||
client,
|
||||
host_port: format!("http://{}:{}", args.ip, args.port),
|
||||
session_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl Comms for HTTPComms {
|
||||
async fn get_signing_commitments(
|
||||
&mut self,
|
||||
_input: &mut dyn BufRead,
|
||||
_output: &mut dyn Write,
|
||||
_pub_key_package: &PublicKeyPackage,
|
||||
num_signers: u16,
|
||||
) -> Result<BTreeMap<Identifier, SigningCommitments>, Box<dyn Error>> {
|
||||
let r = self
|
||||
.client
|
||||
.post(format!("{}/create_new_session", self.host_port))
|
||||
.json(&server::CreateNewSessionArgs {
|
||||
num_signers,
|
||||
message_count: 1,
|
||||
})
|
||||
.send()
|
||||
.await?
|
||||
.json::<server::CreateNewSessionOutput>()
|
||||
.await?;
|
||||
|
||||
eprintln!(
|
||||
"Send the following session ID to participants: {}",
|
||||
r.session_id
|
||||
);
|
||||
self.session_id = Some(r.session_id);
|
||||
eprint!("Waiting for participants to send their commitments...");
|
||||
|
||||
let r = loop {
|
||||
let r = self
|
||||
.client
|
||||
.post(format!("{}/get_commitments", self.host_port))
|
||||
.json(&server::GetCommitmentsArgs {
|
||||
session_id: r.session_id,
|
||||
})
|
||||
.send()
|
||||
.await?;
|
||||
if r.status() != 200 {
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
eprint!(".");
|
||||
} else {
|
||||
break r.json::<server::GetCommitmentsOutput>().await?;
|
||||
}
|
||||
};
|
||||
eprintln!();
|
||||
|
||||
Ok(r.commitments
|
||||
.first()
|
||||
.ok_or(eyre!("empty commitments"))
|
||||
.cloned()?)
|
||||
}
|
||||
|
||||
async fn get_signature_shares(
|
||||
&mut self,
|
||||
_input: &mut dyn BufRead,
|
||||
_output: &mut dyn Write,
|
||||
signing_package: &SigningPackage,
|
||||
#[cfg(feature = "redpallas")] randomizer: frost::round2::Randomizer,
|
||||
) -> Result<BTreeMap<Identifier, SignatureShare>, Box<dyn Error>> {
|
||||
// Send SigningPackage to all participants
|
||||
eprintln!("Sending SigningPackage to participants...");
|
||||
|
||||
let _r = self
|
||||
.client
|
||||
.post(format!("{}/send_signing_package", self.host_port))
|
||||
.json(&server::SendSigningPackageArgs {
|
||||
aux_msg: Default::default(),
|
||||
session_id: self.session_id.unwrap(),
|
||||
signing_package: vec![signing_package.clone()],
|
||||
#[cfg(feature = "redpallas")]
|
||||
randomizer: vec![randomizer],
|
||||
})
|
||||
.send()
|
||||
.await?
|
||||
.bytes()
|
||||
.await?;
|
||||
|
||||
eprintln!("Waiting for participants to send their SignatureShares...");
|
||||
|
||||
let r = loop {
|
||||
let r = self
|
||||
.client
|
||||
.post(format!("{}/get_signature_shares", self.host_port))
|
||||
.json(&server::GetSignatureSharesArgs {
|
||||
session_id: self.session_id.unwrap(),
|
||||
})
|
||||
.send()
|
||||
.await?;
|
||||
if r.status() != 200 {
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
eprint!(".");
|
||||
} else {
|
||||
break r.json::<server::GetSignatureSharesOutput>().await?;
|
||||
}
|
||||
};
|
||||
eprintln!();
|
||||
|
||||
Ok(r.signature_shares
|
||||
.first()
|
||||
.ok_or(eyre!("empty signature shares"))?
|
||||
.clone())
|
||||
}
|
||||
}
|
|
@ -17,7 +17,12 @@ pub fn request_randomizer(
|
|||
args: &Args,
|
||||
input: &mut impl BufRead,
|
||||
logger: &mut dyn Write,
|
||||
signing_package: &SigningPackage,
|
||||
) -> Result<frost::round2::Randomizer, Box<dyn std::error::Error>> {
|
||||
if args.randomizer.is_empty() {
|
||||
let rng = rand::thread_rng();
|
||||
return Ok(frost::round2::Randomizer::new(rng, signing_package)?);
|
||||
};
|
||||
let randomizer = if args.randomizer == "-" {
|
||||
writeln!(logger, "Enter the randomizer (hex string):")?;
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ axum = "0.7.3"
|
|||
clap = { version = "4.5.2", features = ["derive"] }
|
||||
derivative = "2.2.0"
|
||||
eyre = "0.6.11"
|
||||
frost-ed25519 = { version = "1.0.0-rc.0", features = ["serde"] }
|
||||
rand = "0.8"
|
||||
reddsa = { git = "https://github.com/ZcashFoundation/reddsa.git", rev = "81c649c412e5b6ba56d491d2857f91fbd28adbc7", features = [
|
||||
"frost",
|
||||
|
@ -24,5 +25,11 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
|||
uuid = { version = "1.6.1", features = ["v4", "fast-rng", "serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
axum-test = "14.4.0"
|
||||
reqwest = { version = "0.11.26", features = ["json"] }
|
||||
axum-test = "14.2.2"
|
||||
coordinator = { path = "../coordinator" }
|
||||
reqwest = { version = "0.11.26", features = ["json"] }
|
||||
regex = "1.10.3"
|
||||
|
||||
[features]
|
||||
redpallas = ["coordinator/redpallas"]
|
||||
default = []
|
||||
|
|
|
@ -158,9 +158,14 @@ pub(crate) async fn send_signing_package(
|
|||
|
||||
match &mut session.state {
|
||||
SessionState::CommitmentsReady { commitments } => {
|
||||
if args.signing_package.len() != session.message_count as usize
|
||||
|| args.randomizer.len() != session.message_count as usize
|
||||
{
|
||||
if args.signing_package.len() != session.message_count as usize {
|
||||
return Err(AppError(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
eyre!("wrong number of inputs"),
|
||||
));
|
||||
}
|
||||
#[cfg(feature = "redpallas")]
|
||||
if args.randomizer.len() != session.message_count as usize {
|
||||
return Err(AppError(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
eyre!("wrong number of inputs"),
|
||||
|
@ -170,6 +175,7 @@ pub(crate) async fn send_signing_package(
|
|||
identifiers: commitments.keys().cloned().collect(),
|
||||
signing_package: args.signing_package,
|
||||
signature_shares: Default::default(),
|
||||
#[cfg(feature = "redpallas")]
|
||||
randomizer: args.randomizer,
|
||||
aux_msg: args.aux_msg,
|
||||
};
|
||||
|
@ -202,10 +208,12 @@ pub(crate) async fn get_signing_package(
|
|||
identifiers: _,
|
||||
signing_package,
|
||||
signature_shares: _,
|
||||
#[cfg(feature = "redpallas")]
|
||||
randomizer,
|
||||
aux_msg,
|
||||
} => Ok(Json(GetSigningPackageOutput {
|
||||
signing_package: signing_package.clone(),
|
||||
#[cfg(feature = "redpallas")]
|
||||
randomizer: randomizer.clone(),
|
||||
aux_msg: aux_msg.clone(),
|
||||
})),
|
||||
|
@ -238,7 +246,8 @@ pub(crate) async fn send_signature_share(
|
|||
identifiers,
|
||||
signing_package: _,
|
||||
signature_shares,
|
||||
randomizer: _,
|
||||
#[cfg(feature = "redpallas")]
|
||||
randomizer: _,
|
||||
aux_msg: _,
|
||||
} => {
|
||||
if !identifiers.contains(&args.identifier) {
|
||||
|
|
|
@ -3,10 +3,13 @@ use std::{
|
|||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(not(feature = "redpallas"))]
|
||||
use frost_ed25519 as frost;
|
||||
#[cfg(feature = "redpallas")]
|
||||
use reddsa::frost::redpallas as frost;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
/// The current state of the server, and the required data for the state.
|
||||
#[derive(derivative::Derivative)]
|
||||
#[derivative(Debug)]
|
||||
|
@ -35,7 +38,8 @@ pub enum SessionState {
|
|||
/// Randomizer sent by coordinator to be sent to participants, for each
|
||||
/// message being signed.
|
||||
/// (Rerandomized FROST only. TODO: make it optional?)
|
||||
#[derivative(Debug = "ignore")]
|
||||
#[cfg(feature = "redpallas")]
|
||||
#[cfg_attr(feature = "redpallas", derivative(Debug = "ignore"))]
|
||||
randomizer: Vec<frost::round2::Randomizer>,
|
||||
/// Auxiliary (optional) message. A context-specific data that is
|
||||
/// supposed to be interpreted by the participants.
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
pub use uuid::Uuid;
|
||||
|
||||
#[cfg(not(feature = "redpallas"))]
|
||||
use frost_ed25519 as frost;
|
||||
#[cfg(feature = "redpallas")]
|
||||
use reddsa::frost::redpallas as frost;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
@ -50,7 +53,8 @@ pub struct SendSigningPackageArgs {
|
|||
pub session_id: Uuid,
|
||||
pub signing_package: Vec<frost::SigningPackage>,
|
||||
pub aux_msg: Vec<u8>,
|
||||
#[derivative(Debug = "ignore")]
|
||||
#[cfg(feature = "redpallas")]
|
||||
#[cfg_attr(feature = "redpallas", derivative(Debug = "ignore"))]
|
||||
pub randomizer: Vec<frost::round2::Randomizer>,
|
||||
}
|
||||
|
||||
|
@ -63,7 +67,8 @@ pub struct GetSigningPackageArgs {
|
|||
#[derivative(Debug)]
|
||||
pub struct GetSigningPackageOutput {
|
||||
pub signing_package: Vec<frost::SigningPackage>,
|
||||
#[derivative(Debug = "ignore")]
|
||||
#[cfg(feature = "redpallas")]
|
||||
#[cfg_attr(feature = "redpallas", derivative(Debug = "ignore"))]
|
||||
pub randomizer: Vec<frost::round2::Randomizer>,
|
||||
pub aux_msg: Vec<u8>,
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ use axum_test::TestServer;
|
|||
use rand::thread_rng;
|
||||
use server::{args::Args, router};
|
||||
|
||||
#[cfg(not(feature = "redpallas"))]
|
||||
use frost_ed25519 as frost;
|
||||
#[cfg(feature = "redpallas")]
|
||||
use reddsa::frost::redpallas as frost;
|
||||
|
||||
/// Test the entire FROST signing flow using axum_test.
|
||||
|
@ -110,6 +113,7 @@ async fn test_main_router() -> Result<(), Box<dyn std::error::Error>> {
|
|||
.enumerate()
|
||||
.map(|(i, msg)| frost::SigningPackage::new(commitments[i].clone(), msg))
|
||||
.collect::<Vec<_>>();
|
||||
#[cfg(feature = "redpallas")]
|
||||
let randomized_params = signing_packages
|
||||
.iter()
|
||||
.map(|p| frost::RandomizedParams::new(pubkeys.verifying_key(), p, &mut rng))
|
||||
|
@ -121,6 +125,7 @@ async fn test_main_router() -> Result<(), Box<dyn std::error::Error>> {
|
|||
.json(&server::SendSigningPackageArgs {
|
||||
session_id,
|
||||
signing_package: signing_packages.clone(),
|
||||
#[cfg(feature = "redpallas")]
|
||||
randomizer: randomized_params.iter().map(|p| *p.randomizer()).collect(),
|
||||
aux_msg: aux_msg.to_owned(),
|
||||
})
|
||||
|
@ -141,6 +146,7 @@ async fn test_main_router() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let r: server::GetSigningPackageOutput = res.json();
|
||||
|
||||
// Generate SignatureShares for each SigningPackage
|
||||
#[cfg(feature = "redpallas")]
|
||||
let signature_share = r
|
||||
.signing_package
|
||||
.iter()
|
||||
|
@ -156,6 +162,16 @@ async fn test_main_router() -> Result<(), Box<dyn std::error::Error>> {
|
|||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
#[cfg(not(feature = "redpallas"))]
|
||||
let signature_share = r
|
||||
.signing_package
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, signing_package)| {
|
||||
frost::round2::sign(signing_package, &nonces_map[identifier][i], key_package)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// Send SignatureShares to the server
|
||||
let res = server
|
||||
.post("/send_signature_share")
|
||||
|
@ -177,11 +193,18 @@ async fn test_main_router() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let r: server::GetSignatureSharesOutput = res.json();
|
||||
|
||||
// Generate the final Signature for each message
|
||||
#[cfg(feature = "redpallas")]
|
||||
let signatures = signing_packages
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, p)| frost::aggregate(p, &r.signature_shares[i], &pubkeys, &randomized_params[i]))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
#[cfg(not(feature = "redpallas"))]
|
||||
let signatures = signing_packages
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, p)| frost::aggregate(p, &r.signature_shares[i], &pubkeys))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// Close the session
|
||||
let res = server
|
||||
|
@ -191,10 +214,15 @@ async fn test_main_router() -> Result<(), Box<dyn std::error::Error>> {
|
|||
res.assert_status_ok();
|
||||
|
||||
// Verify signatures to test if they were generated correctly
|
||||
#[cfg(feature = "redpallas")]
|
||||
for (i, p) in randomized_params.iter().enumerate() {
|
||||
p.randomized_verifying_key()
|
||||
.verify(messages[i], &signatures[i])?;
|
||||
}
|
||||
#[cfg(not(feature = "redpallas"))]
|
||||
for (i, m) in messages.iter().enumerate() {
|
||||
pubkeys.verifying_key().verify(m, &signatures[i])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue