-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add coordinator demo (#48) * Add test for step_3 in coordinator (#48) * Add validation for participant selection in coordinator demo (#48) * Add tests for validation in step_1 for coordinator (#48) * Improve error handling in Coordinator (#48) * Fix clippy error (#48) * Improve usability for coordinator demo (#48) Fix test values Improve identifier input so it doesn't need to be in quotes Remove unecessary text
- Loading branch information
1 parent
2c26bf9
commit 58abe8d
Showing
15 changed files
with
715 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,5 +2,6 @@ | |
members = [ | ||
"participant", | ||
"trusted-dealer", | ||
"dkg" | ||
"dkg", | ||
"coordinator" | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
[package] | ||
name = "coordinator" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
eyre = "0.6.8" | ||
frost-ed25519 = { version = "0.6.0", features = ["serde"] } | ||
hex = { version = "0.4", features = ["serde"] } | ||
thiserror = "1.0" | ||
rand = "0.8" | ||
serde_json = "1.0" | ||
itertools = "0.11.0" | ||
exitcode = "1.1.2" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# FROST Coordinator Demo | ||
|
||
TODO | ||
|
||
This will be part of a set of demos and a proof of concept application that uses the FROST libraries and reference implementation. The purpose of these demos is to: | ||
|
||
1. identify gaps in our documentation | ||
2. provide usage examples for developer facing documentation | ||
3. provide reference implementations for developers wanting to use FROST in a “real world” scenario. | ||
|
||
This demo uses the (Ed25519, SHA-512) ciphersuite. The crate can be found [here](https://crates.io/crates/frost-ed25519). | ||
|
||
## About FROST (Flexible Round-Optimised Schnorr Threshold signatures) | ||
|
||
Unlike signatures in a single-party setting, threshold signatures require cooperation among a threshold number of signers, each holding a share of a common private key. The security of threshold | ||
schemes in general assume that an adversary can corrupt strictly fewer than a threshold number of participants. | ||
|
||
[Two-Round Threshold Schnorr Signatures with FROST](https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost/) presents a variant of a Flexible Round-Optimized Schnorr Threshold (FROST) signature scheme originally defined in [FROST20](https://eprint.iacr.org/2020/852.pdf). FROST reduces network overhead during threshold | ||
signing operations while employing a novel technique to protect against forgery attacks applicable to prior Schnorr-based threshold signature constructions. This variant of FROST requires two rounds to compute a signature, and implements signing efficiency improvements described by [Schnorr21](https://eprint.iacr.org/2021/1375.pdf). Single-round signing with FROST is not implemented here. | ||
|
||
## Status ⚠ | ||
|
||
The Coordinator Demo is a WIP | ||
|
||
## Usage | ||
|
||
NOTE: This is for demo purposes only and should not be used in production. | ||
|
||
You will need to have [Rust and Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) installed. | ||
|
||
To run: | ||
1. Clone the repo. Run `git clone https://github.com/ZcashFoundation/frost-zcash-demo.git` | ||
2. Run `cargo install` | ||
3. Run `cargo run` | ||
|
||
TODO | ||
|
||
## Using the output | ||
|
||
TODO | ||
|
||
## Developer information | ||
|
||
TODO | ||
|
||
### Pre-commit checks | ||
|
||
1. Run `cargo make all` | ||
|
||
### Coverage | ||
|
||
Test coverage checks are performed in the pipeline. This is configured here: `.github/workflows/coverage.yaml` | ||
To run these locally: | ||
1. Install coverage tool by running `cargo install cargo-llvm-cov` | ||
2. Run `cargo make cov` (you may be asked if you want to install `llvm-tools-preview`, if so type `Y`) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
use std::io::{BufRead, Write}; | ||
|
||
use crate::step_1::step_1; | ||
use crate::step_2::step_2; | ||
use crate::step_3::step_3; | ||
|
||
pub fn cli( | ||
reader: &mut impl BufRead, | ||
logger: &mut impl Write, | ||
) -> Result<(), Box<dyn std::error::Error>> { | ||
writeln!(logger, "\n=== STEP 1: CHOOSE PARTICIPANTS ===\n")?; | ||
|
||
let participants_config = step_1(reader, logger)?; | ||
|
||
writeln!( | ||
logger, | ||
"=== STEP 2: CHOOSE MESSAGE AND GENERATE COMMITMENT PACKAGE ===\n" | ||
)?; | ||
|
||
let signing_package = step_2(reader, logger, participants_config.participants.clone())?; | ||
|
||
writeln!(logger, "=== STEP 3: BUILD GROUP SIGNATURE ===\n")?; | ||
|
||
step_3(reader, logger, participants_config, signing_package); | ||
|
||
writeln!(logger, "=== END ===")?; | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
pub mod cli; | ||
pub mod step_1; | ||
mod step_2; | ||
mod step_3; | ||
#[cfg(test)] | ||
mod tests; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
use std::io; | ||
|
||
use coordinator::cli::cli; | ||
|
||
fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
let mut reader = Box::new(io::stdin().lock()); | ||
let mut logger = io::stdout(); | ||
cli(&mut reader, &mut logger)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
// Choose participants -> send message to those participants - gen message to send | ||
|
||
// Choose message - receive commitments - build commitment list - send to participants | ||
|
||
// Receive signature shares - aggregate - send to participants. signautre shares must be validated first | ||
|
||
// Verify group signature |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
use frost_ed25519 as frost; | ||
|
||
use frost::{keys::PublicKeyPackage, Error, Identifier}; | ||
|
||
use eyre::eyre; | ||
use std::io::{BufRead, Write}; | ||
|
||
pub struct ParticipantsConfig { | ||
pub participants: Vec<Identifier>, | ||
pub pub_key_package: PublicKeyPackage, | ||
} | ||
|
||
// TODO: needs to include the coordinator's keys! | ||
pub fn step_1( | ||
reader: &mut impl BufRead, | ||
logger: &mut dyn Write, | ||
) -> Result<ParticipantsConfig, Error> { | ||
let participants = choose_participants(reader, logger)?; | ||
print_participants(logger, &participants.participants); | ||
Ok(participants) | ||
} | ||
|
||
fn validate( | ||
id: Identifier, | ||
key_package: PublicKeyPackage, | ||
id_list: &[Identifier], | ||
) -> Result<(), Error> { | ||
if !key_package.signer_pubkeys().contains_key(&id) { | ||
return Err(Error::MalformedIdentifier); | ||
}; // TODO: Error is actually that the identifier does not exist | ||
if id_list.contains(&id) { | ||
return Err(Error::DuplicatedIdentifier); | ||
}; | ||
Ok(()) | ||
} | ||
|
||
// TODO: validate min num of participants | ||
|
||
pub fn read_identifier(input: &mut impl BufRead) -> Result<Identifier, Box<dyn std::error::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) | ||
} | ||
|
||
// Input required: | ||
// 1. public key package | ||
// 2. number of participants | ||
// 3. identifiers for all participants | ||
fn choose_participants( | ||
input: &mut impl BufRead, | ||
logger: &mut dyn Write, | ||
) -> Result<ParticipantsConfig, Error> { | ||
writeln!(logger, "Paste the JSON public key package: ").unwrap(); | ||
let mut key_package = String::new(); | ||
input.read_line(&mut key_package).unwrap(); | ||
let pub_key_package: PublicKeyPackage = serde_json::from_str(&key_package).unwrap(); | ||
|
||
writeln!(logger, "The number of participants: ").unwrap(); | ||
|
||
let mut participants = String::new(); | ||
input.read_line(&mut participants).unwrap(); | ||
let num_of_participants = participants.trim().parse::<u16>().unwrap(); | ||
|
||
let mut participants = Vec::new(); | ||
|
||
for i in 1..=num_of_participants { | ||
let package = pub_key_package.clone(); | ||
writeln!(logger, "Identifier for participant {:?} (hex encoded):", i).unwrap(); | ||
|
||
let id_value = read_identifier(input).unwrap(); | ||
|
||
validate(id_value, package, &participants)?; | ||
|
||
participants.push(id_value) | ||
} | ||
Ok(ParticipantsConfig { | ||
participants, | ||
pub_key_package, | ||
}) | ||
} | ||
|
||
pub fn print_participants(logger: &mut dyn Write, participants: &Vec<Identifier>) { | ||
writeln!(logger, "Selected participants:",).unwrap(); | ||
|
||
for p in participants { | ||
writeln!(logger, "{}", serde_json::to_string(p).unwrap()).unwrap(); | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use std::collections::HashMap; | ||
|
||
use frost::{ | ||
keys::{PublicKeyPackage, VerifyingShare}, | ||
Error, Identifier, VerifyingKey, | ||
}; | ||
use frost_ed25519 as frost; | ||
use hex::FromHex; | ||
|
||
use crate::step_1::validate; | ||
|
||
const PUBLIC_KEY_1: &str = "fc2c9b8e335c132d9ebe0403c9317aac480bbbf8cbdb1bc3730bb68eb60dadf9"; | ||
const PUBLIC_KEY_2: &str = "2cff4148a2f965801fb1f25f1d2a4e5df2f75b3a57cd06f30471c2c774419a41"; | ||
const GROUP_PUBLIC_KEY: &str = | ||
"15d21ccd7ee42959562fc8aa63224c8851fb3ec85a3faf66040d380fb9738673"; | ||
|
||
fn build_pub_key_package() -> PublicKeyPackage { | ||
let id_1 = Identifier::try_from(1).unwrap(); | ||
let id_2 = Identifier::try_from(2).unwrap(); | ||
|
||
let mut signer_pubkeys = HashMap::new(); | ||
signer_pubkeys.insert( | ||
id_1, | ||
VerifyingShare::deserialize(<[u8; 32]>::from_hex(PUBLIC_KEY_1).unwrap()).unwrap(), | ||
); | ||
signer_pubkeys.insert( | ||
id_2, | ||
VerifyingShare::deserialize(<[u8; 32]>::from_hex(PUBLIC_KEY_2).unwrap()).unwrap(), | ||
); | ||
|
||
let group_public = VerifyingKey::from_hex(GROUP_PUBLIC_KEY).unwrap(); | ||
|
||
PublicKeyPackage::new(signer_pubkeys, group_public) | ||
} | ||
|
||
#[test] | ||
fn check_validate() { | ||
let id_1 = Identifier::try_from(1).unwrap(); | ||
let id_2 = Identifier::try_from(2).unwrap(); | ||
|
||
let id_list = [id_1]; | ||
let key_package = build_pub_key_package(); | ||
|
||
let validated = validate(id_2, key_package, &id_list); | ||
|
||
assert!(validated.is_ok()) | ||
} | ||
|
||
#[test] | ||
fn check_validation_errors_for_missing_identifiers() { | ||
let id_1 = Identifier::try_from(1).unwrap(); | ||
let id_2 = Identifier::try_from(2).unwrap(); | ||
let id_3 = Identifier::try_from(3).unwrap(); | ||
|
||
let id_list = [id_1, id_2]; | ||
let key_package = build_pub_key_package(); | ||
|
||
let validated = validate(id_3, key_package, &id_list); | ||
assert!(validated.is_err()); | ||
assert!(validated == Err(Error::MalformedIdentifier)) | ||
} | ||
|
||
#[test] | ||
fn check_validation_errors_for_duplicate_identifiers() { | ||
let id_1 = Identifier::try_from(1).unwrap(); | ||
let id_2 = Identifier::try_from(2).unwrap(); | ||
|
||
let id_list = [id_1, id_2]; | ||
let key_package = build_pub_key_package(); | ||
|
||
let validated = validate(id_1, key_package, &id_list); | ||
assert!(validated.is_err()); | ||
assert!(validated == Err(Error::DuplicatedIdentifier)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
use frost::{round1::SigningCommitments, Identifier, SigningPackage}; | ||
|
||
use frost_ed25519 as frost; | ||
|
||
use std::{ | ||
collections::BTreeMap, | ||
io::{BufRead, Write}, | ||
}; | ||
|
||
#[derive(Debug, PartialEq, Clone)] | ||
pub struct CommitmentsConfig { | ||
pub message: Vec<u8>, | ||
pub signer_commitments: BTreeMap<Identifier, SigningCommitments>, | ||
} | ||
|
||
pub fn step_2( | ||
input: &mut impl BufRead, | ||
logger: &mut dyn Write, | ||
participants: Vec<Identifier>, | ||
) -> Result<SigningPackage, Box<dyn std::error::Error>> { | ||
let signing_package = request_inputs_commitments(input, logger, participants)?; | ||
print_commitments(logger, &signing_package); | ||
Ok(signing_package) | ||
} | ||
|
||
// Input required: | ||
// 1. message | ||
// 2. number of signers | ||
// 3. commitments for all signers | ||
fn request_inputs_commitments( | ||
input: &mut impl BufRead, | ||
logger: &mut dyn Write, | ||
participants: Vec<Identifier>, | ||
) -> Result<SigningPackage, Box<dyn std::error::Error>> { | ||
writeln!(logger, "The message to be signed (hex encoded)")?; | ||
|
||
let mut msg = String::new(); | ||
input.read_line(&mut msg)?; | ||
|
||
let message = hex::decode(msg.trim())?; | ||
|
||
let mut commitments_list: BTreeMap<Identifier, SigningCommitments> = BTreeMap::new(); | ||
|
||
for p in participants { | ||
writeln!( | ||
logger, | ||
"Please enter JSON encoded commitments for participant {:#?}:", | ||
p | ||
)?; // TODO: improve printing | ||
|
||
let mut commitments_input = String::new(); | ||
input.read_line(&mut commitments_input)?; | ||
let commitments = serde_json::from_str(&commitments_input)?; | ||
commitments_list.insert(p, commitments); | ||
} | ||
|
||
let signing_package = SigningPackage::new(commitments_list, &message); | ||
|
||
Ok(signing_package) | ||
} | ||
|
||
fn print_commitments(logger: &mut dyn Write, signing_package: &SigningPackage) { | ||
writeln!( | ||
logger, | ||
"Signing Package: {}", | ||
serde_json::to_string(&signing_package).unwrap() | ||
) | ||
.unwrap(); | ||
} |
Oops, something went wrong.