From 7b83737137ab94c40d76c76b180193de1e176d54 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Tue, 1 Nov 2022 12:54:04 -0300 Subject: [PATCH] Improve examples (#160) * add trusted dealer example * add example for each ciphersuite-specific crate * simplify example * improve example; use ? instead of unwrap --- README.md | 93 ------------------------------ frost-core/README.md | 5 +- frost-core/src/tests.rs | 2 +- frost-ed25519/README.md | 106 ++++++++++++++++++++++++++++------- frost-p256/README.md | 104 +++++++++++++++++++++++++++------- frost-ristretto255/README.md | 104 +++++++++++++++++++++++++++------- gendoc/src/main.rs | 32 +++++++++++ 7 files changed, 293 insertions(+), 153 deletions(-) diff --git a/README.md b/README.md index 1f30520..0d57d52 100644 --- a/README.md +++ b/README.md @@ -30,98 +30,5 @@ scratch. End-users should not use `frost-core` if they want to sign and verify s should use the crate specific to their ciphersuite/curve parameters that uses `frost-core` as a dependency. -## Example - -```rust -use std::{collections::HashMap, convert::TryFrom}; - -use rand::thread_rng; - -use frost_ristretto255::frost; - -let mut rng = thread_rng(); -let numsigners = 5; -let threshold = 3; -let (shares, pubkeys) = - frost::keys::keygen_with_dealer(numsigners, threshold, &mut rng).unwrap(); - -// Verifies the secret shares from the dealer -let key_packages: Vec = shares - .into_iter() - .map(|share| frost::keys::KeyPackage::try_from(share).unwrap()) - .collect(); - -let mut nonces: HashMap> = HashMap::new(); -let mut commitments: HashMap> = HashMap::new(); - -//////////////////////////////////////////////////////////////////////////// -// Round 1: generating nonces and signing commitments for each participant -//////////////////////////////////////////////////////////////////////////// - -for participant_index in 1..(threshold + 1) { - // Generate one (1) nonce and one SigningCommitments instance for each - // participant, up to _threshold_. - let (nonce, commitment) = frost::round1::commit(participant_index as u16, &mut rng); - nonces.insert(participant_index as u16, nonce); - commitments.insert(participant_index as u16, commitment); -} - -// This is what the signature aggregator / coordinator needs to do: -// - decide what message to sign -// - take one (unused) commitment per signing participant -let mut signature_shares: Vec = Vec::new(); -let message = "message to sign".as_bytes(); -let comms = commitments.clone().into_values().flatten().collect(); -let signing_package = frost::SigningPackage::new(comms, message.to_vec()); - -//////////////////////////////////////////////////////////////////////////// -// Round 2: each participant generates their signature share -//////////////////////////////////////////////////////////////////////////// - -for participant_index in nonces.keys() { - let key_package = key_packages - .iter() - .find(|key_package| *participant_index == key_package.index) - .unwrap(); - - let nonces_to_use = nonces.get(participant_index).unwrap()[0]; - - // Each participant generates their signature share. - let signature_share = - frost::round2::sign(&signing_package, &nonces_to_use, key_package).unwrap(); - signature_shares.push(signature_share); -} - -//////////////////////////////////////////////////////////////////////////// -// Aggregation: collects the signing shares from all participants, -// generates the final signature. -//////////////////////////////////////////////////////////////////////////// - -// Aggregate (also verifies the signature shares) -let group_signature_res = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys); - -assert!(group_signature_res.is_ok()); - -let group_signature = group_signature_res.unwrap(); - -// Check that the threshold signature can be verified by the group public -// key (the verification key). -assert!(pubkeys - .group_public - .verify(message, &group_signature) - .is_ok()); - -// Check that the threshold signature can be verified by the group public -// key (the verification key) from SharePackage.group_public -for (participant_index, _) in nonces.clone() { - let key_package = key_packages.get(participant_index as usize).unwrap(); - - assert!(key_package - .group_public - .verify(message, &group_signature) - .is_ok()); -} - -``` diff --git a/frost-core/README.md b/frost-core/README.md index 3e452fa..a298498 100644 --- a/frost-core/README.md +++ b/frost-core/README.md @@ -15,5 +15,8 @@ released. The APIs and types in `frost-core` are subject to change. implementations for different ciphersuites / curves without having to implement all of FROST from scratch. End-users should not use `frost-core` if they want to sign and verify signatures, they should use the crate specific to their ciphersuite/curve parameters that uses `frost-core` as a -dependency, such as `frost-ristretto255`. +dependency, such as [`frost_ristretto255`](../frost_ristretto255). +## Example + +See ciphersuite-specific crates, e.g. [`frost_ristretto255`](../frost_ristretto255). diff --git a/frost-core/src/tests.rs b/frost-core/src/tests.rs index a23cc1a..a036a62 100644 --- a/frost-core/src/tests.rs +++ b/frost-core/src/tests.rs @@ -92,7 +92,7 @@ fn check_sign( // This is what the signature aggregator / coordinator needs to do: // - decide what message to sign // - take one (unused) commitment per signing participant - let mut signature_shares: Vec> = Vec::new(); + let mut signature_shares = Vec::new(); let message = "message to sign".as_bytes(); let comms = commitments.clone().into_values().collect(); let signing_package = frost::SigningPackage::new(comms, message.to_vec()); diff --git a/frost-ed25519/README.md b/frost-ed25519/README.md index c1ccf5c..68238a7 100644 --- a/frost-ed25519/README.md +++ b/frost-ed25519/README.md @@ -1,32 +1,98 @@ -An implementation of Schnorr signatures on the Ristretto group for both single and threshold numbers +An implementation of Schnorr signatures on the Ed25519 curve for both single and threshold numbers of signers (FROST). -## Examples +## Example: key generation with trusted dealer and FROST signing + +Creating a key with a trusted dealer and splitting into shares; then signing a message +and aggregating the signature. Note that the example just simulates a distributed +scenario in a single thread and it abstracts away any communication between peers. -Creating a `Signature` with a single signer, serializing and deserializing it, and verifying the -signature: ```rust +use frost_ed25519 as frost; use rand::thread_rng; -use frost_ed25519::*; +use std::collections::HashMap; -let msg = b"Hello!"; +let mut rng = thread_rng(); +let max_signers = 5; +let min_signers = 3; +let (shares, pubkeys) = frost::keys::keygen_with_dealer(max_signers, min_signers, &mut rng)?; -// Generate a secret key and sign the message -let sk = SigningKey::new(thread_rng()); -let sig = sk.sign(thread_rng(), msg); +// Verifies the secret shares from the dealer and store them in a HashMap. +// In practice, the KeyPackages must be sent to its respective participants +// through a confidential and authenticated channel. +let key_packages: HashMap<_, _> = shares + .into_iter() + .map(|share| Ok((share.identifier, frost::keys::KeyPackage::try_from(share)?))) + .collect::>()?; -// Types can be converted to raw byte arrays using `from_bytes`/`to_bytes` -let sig_bytes = sig.to_bytes(); -let pk_bytes = VerifyingKey::from(&sk).to_bytes(); +let mut nonces = HashMap::new(); +let mut commitments = HashMap::new(); -// Deserialize and verify the signature. -let sig = Signature::from_bytes(sig_bytes)?; +//////////////////////////////////////////////////////////////////////////// +// Round 1: generating nonces and signing commitments for each participant +//////////////////////////////////////////////////////////////////////////// -assert!( - VerifyingKey::from_bytes(pk_bytes) - .and_then(|pk| pk.verify(msg, &sig)) - .is_ok() -); -# Ok::<(), Error>(()) +// In practice, each iteration of this loop will be executed by its respective participant. +for participant_index in 1..(min_signers as u16 + 1) { + let participant_identifier = participant_index.try_into().expect("should be nonzero"); + // Generate one (1) nonce and one SigningCommitments instance for each + // participant, up to _threshold_. + let (nonce, commitment) = frost::round1::commit( + participant_identifier, + key_packages[&participant_identifier].secret_share(), + &mut rng, + ); + // In practice, the nonces and commitment must be sent to the coordinator + // (or to every other participant if there is no coordinator) using + // an authenticated channel. + nonces.insert(participant_identifier, nonce); + commitments.insert(participant_identifier, commitment); +} + +// This is what the signature aggregator / coordinator needs to do: +// - decide what message to sign +// - take one (unused) commitment per signing participant +let mut signature_shares = Vec::new(); +let message = "message to sign".as_bytes(); +let comms = commitments.clone().into_values().collect(); +// In practice, the SigningPackage must be sent to all participants +// involved in the current signing (at least min_signers participants), +// using an authenticate channel (and confidential if the message is secret). +let signing_package = frost::SigningPackage::new(comms, message.to_vec()); + +//////////////////////////////////////////////////////////////////////////// +// Round 2: each participant generates their signature share +//////////////////////////////////////////////////////////////////////////// + +// In practice, each iteration of this loop will be executed by its respective participant. +for participant_identifier in nonces.keys() { + let key_package = &key_packages[participant_identifier]; + + let nonces_to_use = &nonces[participant_identifier]; + + // Each participant generates their signature share. + let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package)?; + + // In practice, the signature share must be sent to the Coordinator + // using an authenticated channel. + signature_shares.push(signature_share); +} + +//////////////////////////////////////////////////////////////////////////// +// Aggregation: collects the signing shares from all participants, +// generates the final signature. +//////////////////////////////////////////////////////////////////////////// + +// Aggregate (also verifies the signature shares) +let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys)?; + +// Check that the threshold signature can be verified by the group public +// key (the verification key). +assert!(pubkeys + .group_public + .verify(message, &group_signature) + .is_ok()); + +# Ok::<(), frost::Error>(()) ``` diff --git a/frost-p256/README.md b/frost-p256/README.md index 1b3c858..de2379a 100644 --- a/frost-p256/README.md +++ b/frost-p256/README.md @@ -1,32 +1,98 @@ An implementation of Schnorr signatures on the P-256 curve for both single and threshold numbers of signers (FROST). -## Examples +## Example: key generation with trusted dealer and FROST signing + +Creating a key with a trusted dealer and splitting into shares; then signing a message +and aggregating the signature. Note that the example just simulates a distributed +scenario in a single thread and it abstracts away any communication between peers. -Creating a `Signature` with a single signer, serializing and deserializing it, and verifying the -signature: ```rust +use frost_p256 as frost; use rand::thread_rng; -use frost_p256::*; +use std::collections::HashMap; -let msg = b"Hello!"; +let mut rng = thread_rng(); +let max_signers = 5; +let min_signers = 3; +let (shares, pubkeys) = frost::keys::keygen_with_dealer(max_signers, min_signers, &mut rng)?; -// Generate a secret key and sign the message -let sk = SigningKey::new(thread_rng()); -let sig = sk.sign(thread_rng(), msg); +// Verifies the secret shares from the dealer and store them in a HashMap. +// In practice, the KeyPackages must be sent to its respective participants +// through a confidential and authenticated channel. +let key_packages: HashMap<_, _> = shares + .into_iter() + .map(|share| Ok((share.identifier, frost::keys::KeyPackage::try_from(share)?))) + .collect::>()?; -// Types can be converted to raw byte arrays using `from_bytes`/`to_bytes` -let sig_bytes = sig.to_bytes(); -let pk_bytes = VerifyingKey::from(&sk).to_bytes(); +let mut nonces = HashMap::new(); +let mut commitments = HashMap::new(); -// Deserialize and verify the signature. -let sig = Signature::from_bytes(sig_bytes)?; +//////////////////////////////////////////////////////////////////////////// +// Round 1: generating nonces and signing commitments for each participant +//////////////////////////////////////////////////////////////////////////// -assert!( - VerifyingKey::from_bytes(pk_bytes) - .and_then(|pk| pk.verify(msg, &sig)) - .is_ok() -); -# Ok::<(), Error>(()) +// In practice, each iteration of this loop will be executed by its respective participant. +for participant_index in 1..(min_signers as u16 + 1) { + let participant_identifier = participant_index.try_into().expect("should be nonzero"); + // Generate one (1) nonce and one SigningCommitments instance for each + // participant, up to _threshold_. + let (nonce, commitment) = frost::round1::commit( + participant_identifier, + key_packages[&participant_identifier].secret_share(), + &mut rng, + ); + // In practice, the nonces and commitment must be sent to the coordinator + // (or to every other participant if there is no coordinator) using + // an authenticated channel. + nonces.insert(participant_identifier, nonce); + commitments.insert(participant_identifier, commitment); +} + +// This is what the signature aggregator / coordinator needs to do: +// - decide what message to sign +// - take one (unused) commitment per signing participant +let mut signature_shares = Vec::new(); +let message = "message to sign".as_bytes(); +let comms = commitments.clone().into_values().collect(); +// In practice, the SigningPackage must be sent to all participants +// involved in the current signing (at least min_signers participants), +// using an authenticate channel (and confidential if the message is secret). +let signing_package = frost::SigningPackage::new(comms, message.to_vec()); + +//////////////////////////////////////////////////////////////////////////// +// Round 2: each participant generates their signature share +//////////////////////////////////////////////////////////////////////////// + +// In practice, each iteration of this loop will be executed by its respective participant. +for participant_identifier in nonces.keys() { + let key_package = &key_packages[participant_identifier]; + + let nonces_to_use = &nonces[participant_identifier]; + + // Each participant generates their signature share. + let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package)?; + + // In practice, the signature share must be sent to the Coordinator + // using an authenticated channel. + signature_shares.push(signature_share); +} + +//////////////////////////////////////////////////////////////////////////// +// Aggregation: collects the signing shares from all participants, +// generates the final signature. +//////////////////////////////////////////////////////////////////////////// + +// Aggregate (also verifies the signature shares) +let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys)?; + +// Check that the threshold signature can be verified by the group public +// key (the verification key). +assert!(pubkeys + .group_public + .verify(message, &group_signature) + .is_ok()); + +# Ok::<(), frost::Error>(()) ``` diff --git a/frost-ristretto255/README.md b/frost-ristretto255/README.md index b1b53a0..496d677 100644 --- a/frost-ristretto255/README.md +++ b/frost-ristretto255/README.md @@ -1,32 +1,98 @@ An implementation of Schnorr signatures on the Ristretto group for both single and threshold numbers of signers (FROST). -## Examples +## Example: key generation with trusted dealer and FROST signing + +Creating a key with a trusted dealer and splitting into shares; then signing a message +and aggregating the signature. Note that the example just simulates a distributed +scenario in a single thread and it abstracts away any communication between peers. -Creating a `Signature` with a single signer, serializing and deserializing it, and verifying the -signature: ```rust +use frost_ristretto255 as frost; use rand::thread_rng; -use frost_ristretto255::*; +use std::collections::HashMap; -let msg = b"Hello!"; +let mut rng = thread_rng(); +let max_signers = 5; +let min_signers = 3; +let (shares, pubkeys) = frost::keys::keygen_with_dealer(max_signers, min_signers, &mut rng)?; -// Generate a secret key and sign the message -let sk = SigningKey::new(thread_rng()); -let sig = sk.sign(thread_rng(), msg); +// Verifies the secret shares from the dealer and store them in a HashMap. +// In practice, the KeyPackages must be sent to its respective participants +// through a confidential and authenticated channel. +let key_packages: HashMap<_, _> = shares + .into_iter() + .map(|share| Ok((share.identifier, frost::keys::KeyPackage::try_from(share)?))) + .collect::>()?; -// Types can be converted to raw byte arrays using `from_bytes`/`to_bytes` -let sig_bytes = sig.to_bytes(); -let pk_bytes = VerifyingKey::from(&sk).to_bytes(); +let mut nonces = HashMap::new(); +let mut commitments = HashMap::new(); -// Deserialize and verify the signature. -let sig = Signature::from_bytes(sig_bytes)?; +//////////////////////////////////////////////////////////////////////////// +// Round 1: generating nonces and signing commitments for each participant +//////////////////////////////////////////////////////////////////////////// -assert!( - VerifyingKey::from_bytes(pk_bytes) - .and_then(|pk| pk.verify(msg, &sig)) - .is_ok() -); -# Ok::<(), Error>(()) +// In practice, each iteration of this loop will be executed by its respective participant. +for participant_index in 1..(min_signers as u16 + 1) { + let participant_identifier = participant_index.try_into().expect("should be nonzero"); + // Generate one (1) nonce and one SigningCommitments instance for each + // participant, up to _threshold_. + let (nonce, commitment) = frost::round1::commit( + participant_identifier, + key_packages[&participant_identifier].secret_share(), + &mut rng, + ); + // In practice, the nonces and commitment must be sent to the coordinator + // (or to every other participant if there is no coordinator) using + // an authenticated channel. + nonces.insert(participant_identifier, nonce); + commitments.insert(participant_identifier, commitment); +} + +// This is what the signature aggregator / coordinator needs to do: +// - decide what message to sign +// - take one (unused) commitment per signing participant +let mut signature_shares = Vec::new(); +let message = "message to sign".as_bytes(); +let comms = commitments.clone().into_values().collect(); +// In practice, the SigningPackage must be sent to all participants +// involved in the current signing (at least min_signers participants), +// using an authenticate channel (and confidential if the message is secret). +let signing_package = frost::SigningPackage::new(comms, message.to_vec()); + +//////////////////////////////////////////////////////////////////////////// +// Round 2: each participant generates their signature share +//////////////////////////////////////////////////////////////////////////// + +// In practice, each iteration of this loop will be executed by its respective participant. +for participant_identifier in nonces.keys() { + let key_package = &key_packages[participant_identifier]; + + let nonces_to_use = &nonces[participant_identifier]; + + // Each participant generates their signature share. + let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package)?; + + // In practice, the signature share must be sent to the Coordinator + // using an authenticated channel. + signature_shares.push(signature_share); +} + +//////////////////////////////////////////////////////////////////////////// +// Aggregation: collects the signing shares from all participants, +// generates the final signature. +//////////////////////////////////////////////////////////////////////////// + +// Aggregate (also verifies the signature shares) +let group_signature = frost::aggregate(&signing_package, &signature_shares[..], &pubkeys)?; + +// Check that the threshold signature can be verified by the group public +// key (the verification key). +assert!(pubkeys + .group_public + .verify(message, &group_signature) + .is_ok()); + +# Ok::<(), frost::Error>(()) ``` diff --git a/gendoc/src/main.rs b/gendoc/src/main.rs index 029568a..ece4fc4 100644 --- a/gendoc/src/main.rs +++ b/gendoc/src/main.rs @@ -108,6 +108,23 @@ fn write_docs( fs::write(filename, code).unwrap(); } +/// Copy a file into a new one, replacing the strings in `original_strings` +/// by the respective one in `replacement_strings` in the process. +fn copy_and_replace( + origin_filename: &str, + destination_filename: &str, + original_strings: &[&str], + replacement_strings: &[&str], +) { + let mut text = fs::read_to_string(origin_filename).unwrap(); + + for (from, to) in std::iter::zip(original_strings, replacement_strings) { + text = text.replace(from, to) + } + + fs::write(destination_filename, text).unwrap(); +} + fn main() { let docs = read_docs( "frost-ristretto255/src/lib.rs", @@ -115,6 +132,9 @@ fn main() { ); let old_suite_names_doc = &["FROST(ristretto255, SHA-512)"]; + let readme_filename = "frost-ristretto255/README.md"; + let original_strings = &["frost_ristretto255", "Ristretto group"]; + // To add a new ciphersuite, just copy this call and replace the required strings. write_docs( @@ -124,6 +144,12 @@ fn main() { old_suite_names_doc, &["FROST(P-256, SHA-256)"], ); + copy_and_replace( + readme_filename, + "frost-p256/README.md", + original_strings, + &["frost_p256", "P-256 curve"], + ); write_docs( &docs, @@ -132,4 +158,10 @@ fn main() { old_suite_names_doc, &["FROST(Ed25519, SHA-512)"], ); + copy_and_replace( + readme_filename, + "frost-ed25519/README.md", + original_strings, + &["frost_ed25519", "Ed25519 curve"], + ); }