Improve examples (#160)

* add trusted dealer example

* add example for each ciphersuite-specific crate

* simplify example

* improve example; use ? instead of unwrap
This commit is contained in:
Conrado Gouvea 2022-11-01 12:54:04 -03:00 committed by GitHub
parent 6df6e32221
commit 7b83737137
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 293 additions and 153 deletions

View File

@ -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<frost::keys::KeyPackage> = shares
.into_iter()
.map(|share| frost::keys::KeyPackage::try_from(share).unwrap())
.collect();
let mut nonces: HashMap<u16, Vec<frost::round1::SigningNonces>> = HashMap::new();
let mut commitments: HashMap<u16, Vec<frost::round1::SigningCommitments>> = 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<frost::round2::SignatureShare> = 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());
}
```

View File

@ -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).

View File

@ -92,7 +92,7 @@ fn check_sign<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(
// 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<frost::round2::SignatureShare<C>> = 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());

View File

@ -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::<Result<_, _>>()?;
// 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>(())
```

View File

@ -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::<Result<_, _>>()?;
// 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>(())
```

View File

@ -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::<Result<_, _>>()?;
// 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>(())
```

View File

@ -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"],
);
}