Skip to content

Commit

Permalink
Create Coordinator CLI (#59)
Browse files Browse the repository at this point in the history
* 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
natalieesk authored Jul 20, 2023
1 parent 2c26bf9 commit 58abe8d
Show file tree
Hide file tree
Showing 15 changed files with 715 additions and 1 deletion.
14 changes: 14 additions & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
members = [
"participant",
"trusted-dealer",
"dkg"
"dkg",
"coordinator"
]
16 changes: 16 additions & 0 deletions coordinator/Cargo.toml
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"
55 changes: 55 additions & 0 deletions coordinator/README.md
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`)
29 changes: 29 additions & 0 deletions coordinator/src/cli.rs
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(())
}
6 changes: 6 additions & 0 deletions coordinator/src/lib.rs
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;
19 changes: 19 additions & 0 deletions coordinator/src/main.rs
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
169 changes: 169 additions & 0 deletions coordinator/src/step_1.rs
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))
}
}
69 changes: 69 additions & 0 deletions coordinator/src/step_2.rs
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();
}
Loading

0 comments on commit 58abe8d

Please sign in to comment.