Add socket comms for Participant demo (#131)

* Add CLI comms to participant demo (#92)

* Update Coordinator unit tests after comms changes (#92)

* Update integration tests with participant cli comms (#92)

* Add socket comms to Participant demo (#92)

Remove group signature verification from participant demo

* Update participant cli test (#92)

* fix redpallas compile issue

* Update participant/src/args.rs

---------

Co-authored-by: Conrado Gouvea <conradoplg@gmail.com>
Co-authored-by: Conrado Gouvea <conrado@zfnd.org>
This commit is contained in:
natalie 2024-01-30 19:07:32 +00:00 committed by GitHub
parent e9269f204e
commit 4925183519
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 579 additions and 178 deletions

3
Cargo.lock generated
View File

@ -843,13 +843,16 @@ dependencies = [
name = "participant"
version = "0.1.0"
dependencies = [
"clap",
"exitcode",
"eyre",
"frost-ed25519",
"hex",
"message-io",
"rand",
"reddsa",
"serde_json",
"tokio",
]
[[package]]

View File

@ -21,4 +21,5 @@ message-io = "0.18"
[features]
redpallas = []
sockets = []
default = []

View File

@ -1,7 +1,10 @@
use std::io::{BufRead, Write};
use crate::args::Args;
#[cfg(not(feature = "sockets"))]
use crate::comms::cli::CLIComms;
#[cfg(feature = "sockets")]
use crate::comms::socket::SocketComms;
use crate::step_1::step_1;
use crate::step_2::step_2;
use crate::step_3::step_3;
@ -16,8 +19,12 @@ pub async fn cli(
) -> Result<(), Box<dyn std::error::Error>> {
writeln!(logger, "\n=== STEP 1: CHOOSE PARTICIPANTS ===\n")?;
#[cfg(not(feature = "sockets"))]
let mut comms = CLIComms {};
#[cfg(feature = "sockets")]
let mut comms = SocketComms::new(&args);
let participants_config = step_1(args, &mut comms, reader, logger).await?;
writeln!(
@ -25,7 +32,7 @@ pub async fn cli(
"=== STEP 2: CHOOSE MESSAGE AND GENERATE COMMITMENT PACKAGE ===\n"
)?;
let signing_package = step_2(reader, logger, participants_config.commitments.clone())?;
let signing_package = step_2(reader, logger, participants_config.commitments.clone()).await?;
#[cfg(feature = "redpallas")]
let randomizer = request_randomizer(reader, logger)?;

View File

@ -36,12 +36,12 @@ impl SocketComms {
pub fn new(args: &Args) -> Self {
let (handler, listener) = node::split::<()>();
let addr = format!("{}:{}", args.ip, args.port);
let (tx, rx) = mpsc::channel(100);
let (tx, rx) = mpsc::channel(2000);
handler
let _ = handler
.network()
.listen(Transport::FramedTcp, addr)
.unwrap();
.map_err(|e| println!("{}", e));
let socket_comm = Self {
input_rx: rx,
@ -62,8 +62,9 @@ impl SocketComms {
NetEvent::Accepted(_endpoint, _listener) => println!("Client connected"), // Tcp or Ws
NetEvent::Message(endpoint, data) => {
println!("Received: {}", String::from_utf8_lossy(data));
// TODO: handle error
let _ = input_tx.try_send((endpoint, data.to_vec()));
let _ = input_tx
.try_send((endpoint, data.to_vec()))
.map_err(|e| println!("{}", e));
}
NetEvent::Disconnected(_endpoint) => println!("Client disconnected"), //Tcp or Ws
});

View File

@ -6,7 +6,7 @@ use std::{
};
/// Read the contents of a file or from a stdin.
/// If `object_name` is "-" or a file that does not exist, then it reads from
/// If `file_path` is "-" or a file that does not exist, then it reads from
/// stdin.
/// `object_name` is used for printing prompts and it should describe what
/// is being read.

View File

@ -1,3 +1,6 @@
#[cfg(all(test, not(feature = "redpallas")))]
mod tests;
use std::io;
use clap::Parser;

View File

@ -16,7 +16,7 @@ pub struct CommitmentsConfig {
pub signer_commitments: BTreeMap<Identifier, SigningCommitments>,
}
pub fn step_2(
pub async fn step_2(
input: &mut impl BufRead,
logger: &mut dyn Write,
commitments: BTreeMap<Identifier, SigningCommitments>,

1
coordinator/src/tests.rs Normal file
View File

@ -0,0 +1 @@
// mod steps;

View File

@ -1,3 +1,20 @@
#[cfg(test)]
use coordinator::{
args::Args,
comms::cli::CLIComms,
step_1::{step_1, ParticipantsConfig},
step_2::step_2,
step_3::step_3,
};
use frost::{
keys::{PublicKeyPackage, VerifyingShare},
round1::{NonceCommitment, SigningCommitments},
Identifier, SigningPackage, VerifyingKey,
};
use frost_ed25519 as frost;
use hex::FromHex;
use std::{collections::BTreeMap, io::BufWriter};
// Test values from https://github.com/ZcashFoundation/frost/blob/main/frost-ed25519/tests/helpers/vectors.json
const PUBLIC_KEY_1: &str = "fc2c9b8e335c132d9ebe0403c9317aac480bbbf8cbdb1bc3730bb68eb60dadf9";
@ -10,7 +27,7 @@ fn build_pub_key_package() -> PublicKeyPackage {
let id_2 = Identifier::try_from(2).unwrap();
let id_3 = Identifier::try_from(3).unwrap();
let mut signer_pubkeys = HashMap::new();
let mut signer_pubkeys = BTreeMap::new();
signer_pubkeys.insert(
id_1,
VerifyingShare::deserialize(<[u8; 32]>::from_hex(PUBLIC_KEY_1).unwrap()).unwrap(),
@ -24,12 +41,13 @@ fn build_pub_key_package() -> PublicKeyPackage {
VerifyingShare::deserialize(<[u8; 32]>::from_hex(PUBLIC_KEY_3).unwrap()).unwrap(),
);
let group_public = VerifyingKey::from_hex(GROUP_PUBLIC_KEY).unwrap();
let group_public =
VerifyingKey::deserialize(<[u8; 32]>::from_hex(GROUP_PUBLIC_KEY).unwrap()).unwrap();
PublicKeyPackage::new(signer_pubkeys, group_public)
}
fn build_signing_package() -> SigningPackage {
fn build_signing_commitments() -> BTreeMap<Identifier, SigningCommitments> {
let id_1 = Identifier::try_from(1).unwrap();
let id_3 = Identifier::try_from(3).unwrap();
@ -55,15 +73,19 @@ fn build_signing_package() -> SigningPackage {
signing_commitments.insert(id_1, signer_commitments_1);
signing_commitments.insert(id_3, signer_commitments_3);
SigningPackage::new(signing_commitments, b"test")
signing_commitments
// SigningPackage::new(signing_commitments, b"test")
}
// Input required:
// 1. public key package
// 2. number of signers
// 3. identifiers for all signers
#[test]
fn check_step_1() {
#[tokio::test]
async fn check_step_1() {
let mut comms = CLIComms {};
let args = Args::default();
let mut buf = BufWriter::new(Vec::new());
let id_1 = Identifier::try_from(1).unwrap();
@ -87,16 +109,6 @@ fn check_step_1() {
// --
let expected_participants_config = ParticipantsConfig {
participants: vec![id_1, id_3],
pub_key_package: build_pub_key_package(),
};
let participants_config = step_1(&mut valid_input, &mut buf);
assert!(participants_config.is_ok());
assert!(participants_config.unwrap() == expected_participants_config);
let expected = "Paste the JSON public key package: \nThe number of participants: \nIdentifier for participant 1 (hex encoded): \nIdentifier for participant 2 (hex encoded): \nSelected participants: \n\"0100000000000000000000000000000000000000000000000000000000000000\"\n\"0300000000000000000000000000000000000000000000000000000000000000\"\n";
let (_, res) = &buf.into_parts();
@ -109,16 +121,18 @@ fn check_step_1() {
// 1. message
// 2. number of signers
// 3. commitments for all signers
#[test]
fn check_step_2() {
#[tokio::test]
async fn check_step_2() {
let mut comms = CLIComms {};
let args = Args::default();
let mut buf = BufWriter::new(Vec::new());
// -- INPUTS --
let message = "74657374";
let commitments_input_1 = "{\"hiding\":\"5078f5c6d679654bb88a8887242d49cc21a553ed26caed4d52570c6656fb9b92\", \"binding\":\"936b660d3008d8298b0a7220a327a0813ffedd9d07604bdc73d7cffef63c0da0\", \"ciphersuite\":\"FROST(Ed25519, SHA-512)\" }";
let commitments_input_3 = "{\"hiding\":\"91c2469b501fe5af8493f9ae77c8f57999460af317f2d9f2d4378ae0e665860e\", \"binding\":\"c225618accff2266a45d87dc3219b04c774ca26c8629c4fa483e7e87da820007\",\"ciphersuite\":\"FROST(Ed25519, SHA-512)\"}";
let commitments_input_1 = "{\"header\":{\"version\":0,\"ciphersuite\":\"FROST-ED25519-SHA512-v1\"},\"identifier\":\"0100000000000000000000000000000000000000000000000000000000000000\",\"signing_share\":\"4ca8a14c31582e92770b23d8b4e5f253d94cbbdc34332cbbb9972f7d0a16a106\",\"commitment\":[\"c0b1eb84bc74624e9196a4ae01d7b784133dd714943001524e33f62ac09fe6df\",\"a4ed252f52e34077e990f70a743a261ff74cbda88173269cc1feeb0616af734b\",\"0fe7ced03a6d5cc4286d050f20fea6dbc14f412a430fc21f92ee2861011fb93c\"]}";
let commitments_input_3 = "{\"header\":{\"version\":0,\"ciphersuite\":\"FROST-ED25519-SHA512-v1\"},\"identifier\":\"0300000000000000000000000000000000000000000000000000000000000000\",\"signing_share\":\"4ca8a14c31582e92770b23d8b4e5f253d94cbbdc34332cbbb9972f7d0a16a106\",\"commitment\":[\"c0b1eb84bc74624e9196a4ae01d7b784133dd714943001524e33f62ac09fe6df\",\"a4ed252f52e34077e990f70a743a261ff74cbda88173269cc1feeb0616af734b\",\"0fe7ced03a6d5cc4286d050f20fea6dbc14f412a430fc21f92ee2861011fb93c\"]}";
let input = format!(
"{}\n{}\n{}\n",
@ -131,15 +145,25 @@ fn check_step_2() {
let id_1 = Identifier::try_from(1).unwrap();
let id_3 = Identifier::try_from(3).unwrap();
let participants = vec![id_1, id_3];
let expected_signing_package = build_signing_package();
let signing_commitments = build_signing_commitments();
let signing_package = step_2(&mut valid_input, &mut buf, participants);
let expected_signing_package = SigningPackage::new(signing_commitments.clone(), b"test");
let signing_package = step_2(&mut valid_input, &mut buf, signing_commitments.clone());
assert!(signing_package.is_ok());
assert!(signing_package.unwrap() == expected_signing_package);
let expected_participants_config = ParticipantsConfig {
commitments: signing_commitments.clone(),
pub_key_package: build_pub_key_package(),
};
let participants_config = step_1(&args, &mut comms, &mut valid_input, &mut buf);
assert!(participants_config.await.unwrap() == expected_participants_config);
let expected = "The message to be signed (hex encoded)\nPlease enter JSON encoded commitments for participant 0100000000000000000000000000000000000000000000000000000000000000:\nPlease enter JSON encoded commitments for participant 0300000000000000000000000000000000000000000000000000000000000000:\nSigning Package:\n{\"signing_commitments\":{\"0100000000000000000000000000000000000000000000000000000000000000\":{\"hiding\":\"5078f5c6d679654bb88a8887242d49cc21a553ed26caed4d52570c6656fb9b92\",\"binding\":\"936b660d3008d8298b0a7220a327a0813ffedd9d07604bdc73d7cffef63c0da0\",\"ciphersuite\":\"FROST(Ed25519, SHA-512)\"},\"0300000000000000000000000000000000000000000000000000000000000000\":{\"hiding\":\"91c2469b501fe5af8493f9ae77c8f57999460af317f2d9f2d4378ae0e665860e\",\"binding\":\"c225618accff2266a45d87dc3219b04c774ca26c8629c4fa483e7e87da820007\",\"ciphersuite\":\"FROST(Ed25519, SHA-512)\"}},\"message\":\"74657374\",\"ciphersuite\":\"FROST(Ed25519, SHA-512)\"}\n";
let (_, res) = &buf.into_parts();
@ -148,28 +172,13 @@ fn check_step_2() {
assert_eq!(expected, actual)
}
use crate::{
step_1::{step_1, ParticipantsConfig},
step_2::step_2,
step_3::step_3,
};
use frost::{
keys::{PublicKeyPackage, VerifyingShare},
round1::{NonceCommitment, SigningCommitments},
Identifier, SigningPackage, VerifyingKey,
};
use frost_ed25519 as frost;
use hex::FromHex;
use std::{
collections::{BTreeMap, HashMap},
io::BufWriter,
};
// // Input required:
// // 1. number of signers (TODO: maybe pass this in?)
// // 2. signatures for all signers
#[test]
fn check_step_3() {
#[tokio::test]
async fn check_step_3() {
let mut comms = CLIComms {};
let args = Args::default();
let mut buf = BufWriter::new(Vec::new());
let id_1 = Identifier::try_from(1).unwrap();
@ -180,7 +189,7 @@ fn check_step_3() {
const GROUP_PUBLIC_KEY: &str =
"15d21ccd7ee42959562fc8aa63224c8851fb3ec85a3faf66040d380fb9738673";
let mut signer_pubkeys = HashMap::new();
let mut signer_pubkeys = BTreeMap::new();
signer_pubkeys.insert(
id_1,
VerifyingShare::deserialize(<[u8; 32]>::from_hex(PUBLIC_KEY_1).unwrap()).unwrap(),
@ -190,7 +199,8 @@ fn check_step_3() {
VerifyingShare::deserialize(<[u8; 32]>::from_hex(PUBLIC_KEY_3).unwrap()).unwrap(),
);
let group_public = VerifyingKey::from_hex(GROUP_PUBLIC_KEY).unwrap();
let group_public =
VerifyingKey::deserialize(<[u8; 32]>::from_hex(GROUP_PUBLIC_KEY).unwrap()).unwrap();
let signature_1 = "{\"share\":\"b97409beff18861f0959530db091a64b812e3fefaa87e1e3d2c039f11d96cc09\",\"ciphersuite\":\"FROST(Ed25519, SHA-512)\"}";
let signature_3 = "{\"share\":\"9816a14e7cdecfcb240976f564cf98c5640e596b6ddf270379efbef4e9f7db0b\",\"ciphersuite\":\"FROST(Ed25519, SHA-512)\"}";
@ -199,17 +209,20 @@ fn check_step_3() {
let mut valid_input = input.as_bytes();
let commitments = build_signing_commitments();
let participants_config = ParticipantsConfig {
participants: vec![id_1, id_3],
commitments: commitments.clone(),
pub_key_package: PublicKeyPackage::new(signer_pubkeys, group_public),
};
let signing_package = build_signing_package();
let signing_package = SigningPackage::new(commitments, b"test");
step_3(
&mut comms,
&mut valid_input,
&mut buf,
participants_config,
signing_package,
&signing_package,
);
let expected = "Please enter JSON encoded signature shares for participant 0100000000000000000000000000000000000000000000000000000000000000:\nPlease enter JSON encoded signature shares for participant 0300000000000000000000000000000000000000000000000000000000000000:\nGroup signature: \"72c948a63797c693e8e978fdb703a1f5a7590472a539da13b71dd6c2b8c1b2a664b7b4af6194439357c5d15f366760fce53c985a186709e74bb0f8e5078ea805\"\n";

View File

@ -13,7 +13,11 @@ rand = "0.8"
eyre = "0.6.11"
exitcode = "1.1.2"
serde_json = "1.0"
clap = { version = "4.4.7", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
message-io = "0.18"
[features]
redpallas = []
sockets = []
default = []

19
participant/src/args.rs Normal file
View File

@ -0,0 +1,19 @@
use clap::Parser;
#[derive(Parser, Debug, Default)]
#[command(author, version, about, long_about = None)]
pub struct Args {
/// Public key package to use. Can be a file with a JSON-encoded
/// package, or "-". If the file does not exist or if "-" is specified,
/// then it will be read from standard input.
#[arg(short = 'k', long, default_value = "key-package-1.json")]
pub key_package: String,
/// IP to bind to, if using online comms
#[arg(short, long, default_value = "0.0.0.0")]
pub ip: String,
/// Port to bind to, if using online comms
#[arg(short, long, default_value_t = 2744)]
pub port: u16,
}

View File

@ -1,63 +1,54 @@
use frost::{Error, Signature};
#[cfg(not(feature = "redpallas"))]
use frost_ed25519 as frost;
#[cfg(feature = "redpallas")]
use reddsa::frost::redpallas as frost;
use crate::args::Args;
use crate::comms::cli::CLIComms;
use crate::round1::{print_values, request_inputs};
#[cfg(feature = "sockets")]
use crate::comms::socket::SocketComms;
use crate::comms::Comms;
use crate::round1::{generate_nonces_and_commitments, print_values, request_inputs};
use crate::round2::{generate_signature, print_values_round_2, round_2_request_inputs};
use rand::thread_rng;
use std::io::{BufRead, Write};
pub fn cli(
pub async fn cli(
args: &Args,
input: &mut impl BufRead,
logger: &mut impl Write,
) -> Result<(), Box<dyn std::error::Error>> {
let round_1_config = request_inputs(input, logger)?;
#[cfg(not(feature = "sockets"))]
let mut comms = CLIComms {};
#[cfg(feature = "sockets")]
let mut comms = SocketComms::new(&args);
// Round 1
let round_1_config = request_inputs(args, input, logger).await?;
let key_package = round_1_config.key_package;
writeln!(logger, "Key Package succesfully created.")?;
let mut rng = thread_rng();
let (nonces, commitments) = frost::round1::commit(key_package.signing_share(), &mut rng);
let (nonces, commitments) = generate_nonces_and_commitments(&key_package, &mut rng);
print_values(commitments, logger)?;
let round_2_config = round_2_request_inputs(input, logger)?;
let config_message = round_2_config.clone();
// Sign
// Round 2 - Sign
let round_2_config = round_2_request_inputs(
&mut comms,
input,
logger,
commitments,
*key_package.identifier(),
)
.await?;
let signature = generate_signature(round_2_config, &key_package, &nonces)?;
comms.send_signature_share(signature).await?;
print_values_round_2(signature, logger)?;
let group_signature = request_signature(input, logger)?;
key_package
.verifying_key()
.verify(config_message.signing_package.message(), &group_signature)?;
writeln!(logger, "Group Signature verified.")?;
Ok(())
}
fn request_signature(
input: &mut impl BufRead,
logger: &mut impl Write,
) -> Result<Signature, Box<dyn std::error::Error>> {
writeln!(logger, "The group signature (as a \"quoted_string\"):")?;
let mut signature_input = String::new();
input.read_line(&mut signature_input)?;
let group_signature =
serde_json::from_str(signature_input.trim()).map_err(|_| Error::InvalidSignature)?;
// TODO: add redpallas feature
Ok(group_signature)
}

47
participant/src/comms.rs Normal file
View File

@ -0,0 +1,47 @@
pub mod cli;
pub mod socket;
#[cfg(not(feature = "redpallas"))]
use frost_ed25519 as frost;
#[cfg(feature = "redpallas")]
use reddsa::frost::redpallas as frost;
use std::{
error::Error,
io::{BufRead, Write},
};
use frost::{
round1::SigningCommitments,
round2::SignatureShare,
serde::{self, Deserialize, Serialize},
Identifier, SigningPackage,
};
#[derive(Serialize, Deserialize)]
#[serde(crate = "self::serde")]
#[allow(clippy::large_enum_variant)]
pub enum Message {
IdentifiedCommitments {
identifier: Identifier,
commitments: SigningCommitments,
},
SigningPackage(SigningPackage),
SignatureShare(SignatureShare),
}
#[allow(async_fn_in_trait)]
pub trait Comms {
async fn get_signing_package(
&mut self,
input: &mut dyn BufRead,
output: &mut dyn Write,
commitments: SigningCommitments,
identifier: Identifier,
) -> Result<SigningPackage, Box<dyn Error>>;
async fn send_signature_share(
&mut self,
signature_share: SignatureShare,
) -> Result<(), Box<dyn Error>>;
}

View File

@ -0,0 +1,74 @@
//! Command line interface implementation of the Comms trait.
#[cfg(not(feature = "redpallas"))]
use frost_ed25519 as frost;
#[cfg(feature = "redpallas")]
use reddsa::frost::redpallas as frost;
use eyre::eyre;
use frost::{
keys::PublicKeyPackage, round1::SigningCommitments, round2::SignatureShare, Identifier,
SigningPackage,
};
use std::{
error::Error,
io::{BufRead, Write},
};
use crate::comms::Comms;
// use super::Comms;
pub struct CLIComms {}
impl Comms for CLIComms {
async fn get_signing_package(
&mut self,
input: &mut dyn BufRead,
output: &mut dyn Write,
_commitments: SigningCommitments,
_identifier: Identifier,
) -> Result<SigningPackage, Box<dyn Error>> {
writeln!(output, "Enter the JSON-encoded SigningPackage:")?;
let mut signing_package_json = String::new();
input.read_line(&mut signing_package_json)?;
// TODO: change to return a generic Error and use a better error
let signing_package: SigningPackage = serde_json::from_str(signing_package_json.trim())?;
Ok(signing_package)
}
async fn send_signature_share(
&mut self,
_signature_share: SignatureShare,
) -> Result<(), Box<dyn Error>> {
Ok(())
}
}
pub fn read_identifier(input: &mut dyn BufRead) -> Result<Identifier, Box<dyn Error>> {
let mut identifier_input = String::new();
input.read_line(&mut identifier_input)?;
let bytes = hex::decode(identifier_input.trim())?;
let serialization = bytes.try_into().map_err(|_| eyre!("Invalid Identifier"))?;
let identifier = Identifier::deserialize(&serialization)?;
Ok(identifier)
}
pub fn validate(
id: Identifier,
key_package: &PublicKeyPackage,
id_list: &[Identifier],
) -> Result<(), frost::Error> {
if !key_package.verifying_shares().contains_key(&id) {
return Err(frost::Error::MalformedIdentifier);
}; // TODO: Error is actually that the identifier does not exist
if id_list.contains(&id) {
return Err(frost::Error::DuplicatedIdentifier);
};
Ok(())
}

View File

@ -0,0 +1,117 @@
//! Socket implementation of the Comms trait, using message-io.
#[cfg(not(feature = "redpallas"))]
use frost_ed25519 as frost;
#[cfg(feature = "redpallas")]
use reddsa::frost::redpallas as frost;
use eyre::eyre;
use message_io::{
network::{Endpoint, NetEvent, Transport},
node::{self, NodeHandler, NodeListener},
};
use tokio::sync::mpsc::{self, Receiver, Sender};
use frost::{round1::SigningCommitments, round2::SignatureShare, Identifier, SigningPackage};
use std::{
error::Error,
io::{BufRead, Write},
};
use super::{Comms, Message};
use crate::args::Args;
pub struct SocketComms {
input_rx: Receiver<(Endpoint, Vec<u8>)>,
endpoint: Endpoint,
handler: NodeHandler<()>,
}
impl SocketComms {
pub fn new(args: &Args) -> Self {
let (handler, listener) = node::split::<()>();
let addr = format!("{}:{}", args.ip, args.port);
let (tx, rx) = mpsc::channel(2000); // Don't need to receive the endpoint. Change this
let (endpoint, _addr) = handler
.network()
.connect(Transport::FramedTcp, addr)
.unwrap();
let socket_comm = Self {
input_rx: rx,
endpoint,
handler,
};
// TODO: save handle
let _handle = tokio::spawn(async move { Self::run(listener, tx) });
socket_comm
}
fn run(listener: NodeListener<()>, input_tx: Sender<(Endpoint, Vec<u8>)>) {
// Read incoming network events.
listener.for_each(|event| match event.network() {
NetEvent::Connected(endpoint, false) => {
println!("Error connecting to server at {}", endpoint)
} // Used for explicit connections.
NetEvent::Connected(endpoint, true) => println!("Connected to server at {}", endpoint), // Used for explicit connections.
NetEvent::Accepted(endpoint, _listener) => {
println!("Server accepted connection at {}", endpoint)
} // Tcp or Ws
NetEvent::Message(endpoint, data) => {
println!("Received: {}", String::from_utf8_lossy(data));
let _ = input_tx
.try_send((endpoint, data.to_vec()))
.map_err(|e| println!("{}", e));
}
NetEvent::Disconnected(endpoint) => {
println!("Disconnected from server at {}", endpoint)
} //Tcp or Ws
});
}
}
impl Comms for SocketComms {
async fn get_signing_package(
&mut self,
_input: &mut dyn BufRead,
_output: &mut dyn Write,
commitments: SigningCommitments,
identifier: Identifier,
) -> Result<SigningPackage, Box<dyn Error>> {
// Send Commitments to Coordinator
let data = serde_json::to_vec(&Message::IdentifiedCommitments {
identifier,
commitments,
})?;
self.handler.network().send(self.endpoint, &data);
// Receive SigningPackage from Coordinator
let (_endpoint, data) = self
.input_rx
.recv()
.await
.ok_or(eyre!("Did not receive signing package!"))?;
let message: Message = serde_json::from_slice(&data)?;
if let Message::SigningPackage(signing_package) = message {
Ok(signing_package)
} else {
Err(eyre!("Expected SigningPackage message"))?
}
}
async fn send_signature_share(
&mut self,
signature_share: SignatureShare,
) -> Result<(), Box<dyn Error>> {
// Send signature shares to Coordinator
let data = serde_json::to_vec(&Message::SignatureShare(signature_share))?;
self.handler.network().send(self.endpoint, &data);
Ok(())
}
}

48
participant/src/input.rs Normal file
View File

@ -0,0 +1,48 @@
use std::{
error::Error,
fs,
io::{BufRead, Write},
path::Path,
};
/// Read the contents of a file or from a stdin.
/// If `object_name` is "-" or a file that does not exist, then it reads from
/// stdin.
/// `object_name` is used for printing prompts and it should describe what
/// is being read.
pub fn read_from_file_or_stdin(
input: &mut dyn BufRead,
output: &mut dyn Write,
object_name: &str,
file_path: &str,
) -> Result<String, Box<dyn Error>> {
let file_path = {
if file_path == "-" {
None
} else {
let p = Path::new(&file_path);
if p.exists() {
writeln!(output, "Reading {} from {}", object_name, file_path)?;
Some(p)
} else {
writeln!(
output,
"File not found: {}\nWill read from stdin",
file_path
)?;
None
}
}
};
match file_path {
Some(file_path) => Ok(fs::read_to_string(file_path)?),
None => {
writeln!(output, "Paste the {}: ", object_name)?;
let mut key_package = String::new();
input.read_line(&mut key_package)?;
Ok(key_package)
}
}
// TODO: write to file
}

View File

@ -1,3 +1,7 @@
pub mod args;
pub mod cli;
pub mod comms;
pub mod input;
pub mod round1;
pub mod round2;

View File

@ -1,19 +1,19 @@
mod cli;
mod round1;
mod round2;
#[cfg(all(test, not(feature = "redpallas")))]
mod tests;
use cli::cli;
use clap::Parser;
use participant::args::Args;
use participant::cli::cli;
use std::io;
// TODO: Update to use exit codes
fn main() -> Result<(), Box<dyn std::error::Error>> {
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let mut reader = Box::new(io::stdin().lock());
let mut logger = io::stdout();
cli(&mut reader, &mut logger)?;
cli(&args, &mut reader, &mut logger).await?;
Ok(())
}

View File

@ -3,11 +3,15 @@ use frost_ed25519 as frost;
#[cfg(feature = "redpallas")]
use reddsa::frost::redpallas as frost;
use crate::args::Args;
use crate::input::read_from_file_or_stdin;
use frost::{
keys::{KeyPackage, SecretShare},
round1::SigningCommitments,
round1::SigningNonces,
Error,
};
use rand::rngs::ThreadRng;
use std::io::{BufRead, Write};
// TODO: Rethink the types here. They're inconsistent with each other
@ -17,26 +21,36 @@ pub struct Round1Config {
}
// TODO: refactor to generate config
pub fn request_inputs(
pub async fn request_inputs(
args: &Args,
input: &mut impl BufRead,
logger: &mut impl Write,
) -> Result<Round1Config, Box<dyn std::error::Error>> {
writeln!(logger, "Your JSON-encoded secret share or key package:")?;
let mut json = String::new();
let secret_share = read_from_file_or_stdin(input, logger, "key package", &args.key_package)?;
input.read_line(&mut json)?;
let key_package = if let Ok(secret_share) = serde_json::from_str::<SecretShare>(&json) {
let key_package = if let Ok(secret_share) = serde_json::from_str::<SecretShare>(&secret_share) {
KeyPackage::try_from(secret_share)?
} else {
// TODO: Improve error
serde_json::from_str::<KeyPackage>(&json).map_err(|_| Error::InvalidSecretShare)?
serde_json::from_str::<KeyPackage>(&secret_share).map_err(|_| Error::InvalidSecretShare)?
};
Ok(Round1Config { key_package })
}
pub fn generate_nonces_and_commitments(
key_package: &KeyPackage,
rng: &mut ThreadRng,
) -> (SigningNonces, SigningCommitments) {
let (nonces, commitments) = frost::round1::commit(key_package.signing_share(), rng);
// TODO: Store nonces
(nonces, commitments)
}
pub fn print_values(
commitments: SigningCommitments,
logger: &mut dyn Write,

View File

@ -5,11 +5,12 @@ use frost_ed25519 as frost;
#[cfg(feature = "redpallas")]
use reddsa::frost::redpallas as frost;
use crate::comms::Comms;
use frost::{
keys::KeyPackage,
round1::SigningNonces,
round1::{SigningCommitments, SigningNonces},
round2::{self, SignatureShare},
Error, SigningPackage,
Error, Identifier, SigningPackage,
};
use std::io::{BufRead, Write};
@ -22,21 +23,18 @@ pub struct Round2Config {
// TODO: refactor to generate config
// TODO: handle errors
pub fn round_2_request_inputs(
pub async fn round_2_request_inputs(
comms: &mut impl Comms,
input: &mut impl BufRead,
logger: &mut dyn Write,
commitments: SigningCommitments,
identifier: Identifier,
) -> Result<Round2Config, Box<dyn std::error::Error>> {
writeln!(logger, "=== Round 2 ===")?;
writeln!(logger, "Enter the JSON-encoded SigningPackage:")?;
let mut signing_package_json = String::new();
input.read_line(&mut signing_package_json)?;
// TODO: change to return a generic Error and use a better error
let signing_package: SigningPackage = serde_json::from_str(signing_package_json.trim())
.map_err(|_| Error::MalformedSigningKey)?;
let signing_package = comms
.get_signing_package(input, logger, commitments, identifier)
.await?;
#[cfg(feature = "redpallas")]
{
@ -70,6 +68,7 @@ pub fn generate_signature(
let signing_package = config.signing_package;
#[cfg(not(feature = "redpallas"))]
let signature = round2::sign(&signing_package, signing_nonces, key_package)?;
#[cfg(feature = "redpallas")]
let signature = round2::sign(
&signing_package,

View File

@ -1,12 +1,14 @@
use std::io::BufWriter;
use crate::cli::cli;
use participant::args::Args;
use participant::cli::cli;
// TODO: to restore this test, we need to intercept that generated commitments
// to put them inside the SigningPackage
// #[test]
#[allow(unused)]
fn check_cli() {
async fn check_cli() {
let args = Args::default();
let key_package = r#"{"header":{"version":0,"ciphersuite":"FROST-ED25519-SHA512-v1"},"identifier":"0100000000000000000000000000000000000000000000000000000000000000","signing_share":"ee4a66fec3ced53cac04b0abc309bb57f03f8d7dede033e4ae7b6ef57630120f","commitment":["21446705fa7da298998a567a3c2fdd7274903a886dcde9a77f615d915feb6764","56ce223ffbde8ce5971be587cbb0b8b31aa2bc220a6803b9ce73c63f9f432514","6dcc10da9443ef2c9bbd5fc6a9c3bcd4c5ede8048cc0b1342b091fd1ff6dc53c"]}"#;
let signing_package = r#"{"header":{"version":0,"ciphersuite":"FROST-ED25519-SHA512-v1"},"signing_commitments":{"0100000000000000000000000000000000000000000000000000000000000000":{"header":{"version":0,"ciphersuite":"FROST-ED25519-SHA512-v1"},"hiding":"710a280fcedbcbe626fff055f682e4a525c31f157dd6071ef2c04ea0ecbe8de9","binding":"6dc707cdf26a589b3e2de4f6bae09b94d5d3bb939937b52bc6b16bdecd0b041f"},"0200000000000000000000000000000000000000000000000000000000000000":{"header":{"version":0,"ciphersuite":"FROST-ED25519-SHA512-v1"},"hiding":"777f011bf695e27ce62474747a9c110cc3b827268047913a21030c3eba0e1eed","binding":"67f051035284cd619f0e7fc583eb3cb0c88d993aad621c856edc0f995f4588b2"},"0300000000000000000000000000000000000000000000000000000000000000":{"header":{"version":0,"ciphersuite":"FROST-ED25519-SHA512-v1"},"hiding":"c052599bb7a52911b6b58e7c20747f12d45d23aab4aec98aaecdc7909dc6aff3","binding":"b3fbefc67070b1b56203ef875a2c7caf24802dbc943bdc62decac33287b63b23"}},"message":"74657374"}"#;
@ -19,7 +21,7 @@ fn check_cli() {
key_package, signing_package, group_signature
);
let signature = cli(&mut input.as_bytes(), &mut buf);
let signature = cli(&args, &mut input.as_bytes(), &mut buf).await;
assert!(
signature.is_ok(),
"invalid signature: {}",

View File

@ -8,7 +8,10 @@ use frost::{
};
use frost_ed25519 as frost;
use hex::FromHex;
use participant::round1::{print_values, request_inputs, Round1Config};
use participant::{
args::Args,
round1::{print_values, request_inputs, Round1Config},
};
use rand::thread_rng;
@ -17,7 +20,7 @@ const GROUP_PUBLIC_KEY: &str = "087e22f970daf6ac5b07b55bd7fc0af6dea199ab847dc34f
const SIGNING_SHARE: &str = "ceed7dd148a1a1ec2e65b50ecab6a7c453ccbd38c397c3506a540b7cf0dd9104";
const SECRET_SHARE_JSON: &str = r#"{"header":{"version":0,"ciphersuite":"FROST-ED25519-SHA512-v1"},"identifier":"0100000000000000000000000000000000000000000000000000000000000000","signing_share":"ceed7dd148a1a1ec2e65b50ecab6a7c453ccbd38c397c3506a540b7cf0dd9104","commitment":["087e22f970daf6ac5b07b55bd7fc0af6dea199ab847dc34fc92a6f8641a1bb8e","926d5910e146dccb9148ca39dc7607f4f7123ff1c0ffaf109add1d165c568bf2", "291bb78d7e4ef124f5aa6a36cbcf8c276e70fbb4e208212e916d762fc42c1bbc"]}"#;
fn build_key_package() -> KeyPackage {
async fn build_key_package() -> KeyPackage {
KeyPackage::new(
Identifier::try_from(1).unwrap(),
SigningShare::deserialize(<[u8; 32]>::from_hex(SIGNING_SHARE).unwrap()).unwrap(),
@ -27,32 +30,40 @@ fn build_key_package() -> KeyPackage {
)
}
#[test]
fn check_valid_round_1_inputs() {
#[tokio::test]
async fn check_valid_round_1_inputs() {
let config = Round1Config {
key_package: build_key_package(),
key_package: build_key_package().await,
};
let mut buf = BufWriter::new(Vec::new());
let args = Args {
key_package: "-".to_string(),
ip: "0.0.0.0".to_string(),
port: 80,
};
let input = SECRET_SHARE_JSON;
let mut valid_input = input.as_bytes();
let expected = request_inputs(&mut valid_input, &mut buf).unwrap();
let expected = request_inputs(&args, &mut valid_input, &mut buf)
.await
.unwrap();
assert_eq!(expected, config);
}
#[test]
fn check_0_input_for_identifier() {
#[tokio::test]
async fn check_0_input_for_identifier() {
let mut buf = BufWriter::new(Vec::new());
let args = Args::default();
let input = r#"{"identifier":"0000000000000000000000000000000000000000000000000000000000000000","value":"ceed7dd148a1a1ec2e65b50ecab6a7c453ccbd38c397c3506a540b7cf0dd9104","commitment":["087e22f970daf6ac5b07b55bd7fc0af6dea199ab847dc34fc92a6f8641a1bb8e","291bb78d7e4ef124f5aa6a36cbcf8c276e70fbb4e208212e916d762fc42c1bbc"],"ciphersuite":"FROST(Ed25519, SHA-512)"}"#;
let mut invalid_input = input.as_bytes();
// let expected = request_inputs(&mut invalid_input, &mut buf).unwrap_err();
let expected = request_inputs(&mut invalid_input, &mut buf).unwrap_err();
let expected = request_inputs(&args, &mut invalid_input, &mut buf)
.await
.unwrap_err();
assert_eq!(
*expected.downcast::<Error>().unwrap(),
@ -60,15 +71,18 @@ fn check_0_input_for_identifier() {
);
}
#[test]
fn check_invalid_length_signing_share() {
#[tokio::test]
async fn check_invalid_length_signing_share() {
let mut buf = BufWriter::new(Vec::new());
let args = Args::default();
let input = r#"{"identifier":"0100000000000000000000000000000000000000000000000000000000000000","value":"ed7dd148a1a1ec2e65b50ecab6a7c453ccbd38c397c3506a540b7cf0dd9104","commitment":["087e22f970daf6ac5b07b55bd7fc0af6dea199ab847dc34fc92a6f8641a1bb8e","291bb78d7e4ef124f5aa6a36cbcf8c276e70fbb4e208212e916d762fc42c1bbc"],"ciphersuite":"FROST(Ed25519, SHA-512)"}"#;
let mut invalid_input = input.as_bytes();
let expected = request_inputs(&mut invalid_input, &mut buf).unwrap_err();
let expected = request_inputs(&args, &mut invalid_input, &mut buf)
.await
.unwrap_err();
assert_eq!(
*expected.downcast::<Error>().unwrap(),
@ -76,15 +90,18 @@ fn check_invalid_length_signing_share() {
);
}
#[test]
fn check_invalid_round_1_inputs() {
#[tokio::test]
async fn check_invalid_round_1_inputs() {
let input = r#"{"header":{"version":0,"ciphersuite":"FROST-ED25519-SHA512-v1"},"signing_share":"ceed7dd148a1a1ec2e65b50ecab6a7c453ccbd38c397c3506a540b7cf0dd9104","commitment":["087e22f970daf6ac5b07b55bd7fc0af6dea199ab847dc34fc92a6f8641a1bb8e","926d5910e146dccb9148ca39dc7607f4f7123ff1c0ffaf109add1d165c568bf2", "291bb78d7e4ef124f5aa6a36cbcf8c276e70fbb4e208212e916d762fc42c1bbc"]}"#;
let mut buf = BufWriter::new(Vec::new());
let args = Args::default();
let mut valid_input = input.as_bytes();
let expected = request_inputs(&mut valid_input, &mut buf).unwrap_err();
let expected = request_inputs(&args, &mut valid_input, &mut buf)
.await
.unwrap_err();
assert_eq!(
*expected.downcast::<Error>().unwrap(),
Error::InvalidSecretShare
@ -92,21 +109,23 @@ fn check_invalid_round_1_inputs() {
}
// TODO: Handle this error differently
#[test]
fn check_invalid_length_vss_commitment() {
#[tokio::test]
async fn check_invalid_length_vss_commitment() {
let mut buf = BufWriter::new(Vec::new());
let args = Args::default();
let input = r#"{"identifier":"0100000000000000000000000000000000000000000000000000000000000000","value":"ceed7dd148a1a1ec2e65b50ecab6a7c453ccbd38c397c3506a540b7cf0dd9104","commitment":["7e22f970daf6ac5b07b55bd7fc0af6dea199ab847dc34fc92a6f8641a1bb8e","291bb78d7e4ef124f5aa6a36cbcf8c276e70fbb4e208212e916d762fc42c1bbc"],"ciphersuite":"FROST(Ed25519, SHA-512)"}"#;
let mut invalid_input = input.as_bytes();
let expected = request_inputs(&mut invalid_input, &mut buf);
assert!(expected.is_err())
let expected = request_inputs(&args, &mut invalid_input, &mut buf);
assert!(expected.await.is_err())
}
#[test]
fn check_print_values() {
#[tokio::test]
async fn check_print_values() {
let mut buf = BufWriter::new(Vec::new());
let signing_share =
SigningShare::deserialize(<[u8; 32]>::from_hex(SIGNING_SHARE).unwrap()).unwrap();
let mut rng = thread_rng();

View File

@ -10,6 +10,7 @@ use frost::{
};
use frost_ed25519 as frost;
use hex::FromHex;
use participant::comms::cli::CLIComms;
use participant::round2::print_values_round_2;
use participant::round2::{generate_signature, round_2_request_inputs, Round2Config};
use rand::thread_rng;
@ -31,12 +32,13 @@ pub fn nonce_commitment(input: &str) -> NonceCommitment {
NonceCommitment::deserialize(<[u8; 32]>::from_hex(input).unwrap()).unwrap()
}
#[test]
fn check_valid_round_2_inputs() {
#[tokio::test]
async fn check_valid_round_2_inputs() {
// TODO: refactor
// Generate commitments
let mut comms = CLIComms {};
let my_signer_commitments = SigningCommitments::new(
nonce_commitment(MY_HIDING_COMMITMENT),
nonce_commitment(MY_BINDING_COMMITMENT),
@ -64,7 +66,14 @@ fn check_valid_round_2_inputs() {
let input = format!("{}\n", signing_package);
let mut valid_input = input.as_bytes();
let round_2_config = round_2_request_inputs(&mut valid_input, &mut buf);
let round_2_config = round_2_request_inputs(
&mut comms,
&mut valid_input,
&mut buf,
my_signer_commitments,
Identifier::try_from(1).unwrap(),
)
.await;
assert!(round_2_config.is_ok());
assert_eq!(
@ -75,8 +84,8 @@ fn check_valid_round_2_inputs() {
// TODO: test for invalid inputs
#[test]
fn check_sign() {
#[tokio::test]
async fn check_sign() {
let key_package = KeyPackage::new(
Identifier::try_from(1).unwrap(),
SigningShare::deserialize(<[u8; 32]>::from_hex(SIGNING_SHARE).unwrap()).unwrap(),
@ -85,10 +94,6 @@ fn check_sign() {
2,
);
// let config = Round1Config {
// key_package
// };
let mut rng = thread_rng();
// TODO: Nonce doesn't seem to be exported. Look into this to improve these tests
@ -119,8 +124,8 @@ fn check_sign() {
assert!(signature.is_ok()) // TODO: Should be able to test this more specifically when I remove randomness from the test
}
#[test]
fn check_print_values_round_2() {
#[tokio::test]
async fn check_print_values_round_2() {
let mut buf = BufWriter::new(Vec::new());
const SIGNATURE_SHARE: &str =

View File

@ -1,7 +1,11 @@
#![cfg(not(feature = "redpallas"))]
use coordinator::args::Args;
use coordinator::comms::cli::CLIComms;
use coordinator::args::Args as CoordinatorArgs;
use coordinator::comms::cli::CLIComms as CoordinatorCLIComms;
use participant::args::Args as ParticipantArgs;
use participant::comms::cli::CLIComms as ParticipantCLIComms;
use frost_ed25519 as frost;
use frost::keys::IdentifierList;
@ -26,8 +30,12 @@ async fn trusted_dealer_journey() {
let mut buf = BufWriter::new(Vec::new());
let mut rng = thread_rng();
let args = Args::default();
let mut comms = CLIComms {};
let coordinator_args = CoordinatorArgs::default();
let mut coordinator_comms = CoordinatorCLIComms {};
// For a CLI test we can use the same CLIComms instance
let mut participant_comms = ParticipantCLIComms {};
let participant_args = ParticipantArgs::default();
// Trusted dealer
@ -57,10 +65,12 @@ async fn trusted_dealer_journey() {
key_packages.insert(identifier, key_package);
}
let mut nonces_map = HashMap::new();
// Round 1
let mut nonces_map = BTreeMap::new();
let mut commitments_map = BTreeMap::new();
for participant_index in 1..=3 {
for participant_index in 1..=3u16 {
let participant_identifier = Identifier::try_from(participant_index).unwrap();
let share = key_packages[&participant_identifier].signing_share();
@ -70,7 +80,9 @@ async fn trusted_dealer_journey() {
&serde_json::to_string(&key_packages[&participant_identifier]).unwrap()
);
let round_1_config =
participant_input_round_1(&mut round_1_input.as_bytes(), &mut buf).unwrap();
participant_input_round_1(&participant_args, &mut round_1_input.as_bytes(), &mut buf)
.await
.unwrap();
assert_eq!(
round_1_config.key_package,
@ -95,10 +107,14 @@ async fn trusted_dealer_journey() {
serde_json::to_string(&commitments_map[&participant_id_3]).unwrap(),
);
let participants_config =
coordinator::step_1::step_1(&args, &mut comms, &mut step_1_input.as_bytes(), &mut buf)
.await
.unwrap();
let participants_config = coordinator::step_1::step_1(
&coordinator_args,
&mut coordinator_comms,
&mut step_1_input.as_bytes(),
&mut buf,
)
.await
.unwrap();
// Coordinator step 2
@ -107,23 +123,36 @@ async fn trusted_dealer_journey() {
let message = "74657374";
let step_2_input = format!("{}\n", message);
let signing_package =
coordinator::step_2::step_2(&mut step_2_input.as_bytes(), &mut buf, commitments_map)
.unwrap();
let signing_package = coordinator::step_2::step_2(
&mut step_2_input.as_bytes(),
&mut buf,
commitments_map.clone(),
)
.await
.unwrap();
// Participants round 2
// Round 2
for participant_identifier in nonces_map.keys() {
for participant_index in 1..=3 {
let participant_identifier = Identifier::try_from(participant_index).unwrap();
let signing_commitments = commitments_map[&participant_identifier];
let round_2_input = format!("{}\n", serde_json::to_string(&signing_package).unwrap());
let round_2_config =
participant_input_round_2(&mut round_2_input.as_bytes(), &mut buf).unwrap();
let round_2_config = participant_input_round_2(
&mut participant_comms,
&mut round_2_input.as_bytes(),
&mut buf,
signing_commitments,
participant_identifier,
)
.await
.unwrap();
let signature = generate_signature(
round_2_config,
&key_packages[participant_identifier],
&nonces_map[participant_identifier],
&key_packages[&participant_identifier],
&nonces_map[&participant_identifier],
)
.unwrap();
signature_shares.insert(*participant_identifier, signature);
signature_shares.insert(participant_identifier, signature);
}
// coordinator step 3
@ -135,7 +164,7 @@ async fn trusted_dealer_journey() {
serde_json::to_string(&signature_shares[&participant_id_3]).unwrap()
);
let group_signature = coordinator::step_3::step_3(
&mut comms,
&mut coordinator_comms,
&mut step_3_input.as_bytes(),
&mut buf,
participants_config,