Expand description
An implementation of Schnorr signatures on the Ed25519 curve for both single and threshold numbers of signers (FROST).
§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.
use frost_ed25519 as frost;
use rand::thread_rng;
use std::collections::BTreeMap;
let mut rng = thread_rng();
let max_signers = 5;
let min_signers = 3;
let (shares, pubkey_package) = frost::keys::generate_with_dealer(
max_signers,
min_signers,
frost::keys::IdentifierList::Default,
&mut rng,
)?;
// Verifies the secret shares from the dealer and store them in a BTreeMap.
// In practice, the KeyPackages must be sent to its respective participants
// through a confidential and authenticated channel.
let mut key_packages: BTreeMap<_, _> = BTreeMap::new();
for (identifier, secret_share) in shares {
let key_package = frost::keys::KeyPackage::try_from(secret_share)?;
key_packages.insert(identifier, key_package);
}
let mut nonces_map = BTreeMap::new();
let mut commitments_map = BTreeMap::new();
////////////////////////////////////////////////////////////////////////////
// Round 1: generating nonces and signing commitments for each participant
////////////////////////////////////////////////////////////////////////////
// In practice, each iteration of this loop will be executed by its respective participant.
for participant_index in 1..=min_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let key_package = &key_packages[&participant_identifier];
// Generate one (1) nonce and one SigningCommitments instance for each
// participant, up to _threshold_.
let (nonces, commitments) = frost::round1::commit(
key_package.signing_share(),
&mut rng,
);
// In practice, the nonces must be kept by the participant to use in the
// next round, while the commitment must be sent to the coordinator
// (or to every other participant if there is no coordinator) using
// an authenticated channel.
nonces_map.insert(participant_identifier, nonces);
commitments_map.insert(participant_identifier, commitments);
}
// 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 = BTreeMap::new();
let message = "message to sign".as_bytes();
let signing_package = frost::SigningPackage::new(commitments_map, message);
////////////////////////////////////////////////////////////////////////////
// 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_map.keys() {
let key_package = &key_packages[participant_identifier];
let nonces = &nonces_map[participant_identifier];
// Each participant generates their signature share.
let signature_share = frost::round2::sign(&signing_package, nonces, key_package)?;
// In practice, the signature share must be sent to the Coordinator
// using an authenticated channel.
signature_shares.insert(*participant_identifier, 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, &pubkey_package)?;
// Check that the threshold signature can be verified by the group public
// key (the verification key).
let is_signature_valid = pubkey_package
.verifying_key()
.verify(message, &group_signature)
.is_ok();
assert!(is_signature_valid);
§Features
std
(enabled by default) — Enable standard library support.serde
— Enableserde
support for types that need to be communicated. You can useserde
to serialize structs with any encoder that supportsserde
(e.g. JSON withserde_json
).serialization
(enabled by default) — Enable a default serialization format. Enablesserde
.cheater-detection
(enabled by default) — Enable cheater detection
Re-exports§
Modules§
- FROST(Ed25519, SHA-512) keys, key generation, key shares.
- FROST(Ed25519, SHA-512) Round 1 functionality and types.
- FROST(Ed25519, SHA-512) Round 2 functionality and types, for signature share generation.
Structs§
- An implementation of the FROST(Ed25519, SHA-512) ciphersuite group.
- An implementation of the FROST(Ed25519, SHA-512) ciphersuite scalar field.
- An implementation of the FROST(Ed25519, SHA-512) ciphersuite.
Enums§
- An error related to a scalar Field.
- An error related to a Group (usually an elliptic curve or constructed from one) or one of its Elements.
Traits§
- A FROST ciphersuite specifies the underlying prime-order group details and cryptographic hash function.
- A prime order finite field GF(q) over which all scalar values for our prime order group can be multiplied are defined.
- A prime-order group (or subgroup) that provides everything we need to create and verify Schnorr signatures.
Functions§
- Verifies each FROST(Ed25519, SHA-512) participant’s signature share, and if all are valid, aggregates the shares into a signature to publish.
Type Aliases§
- An error.
- A FROST(Ed25519, SHA-512) participant identifier.
- A Schnorr signature on FROST(Ed25519, SHA-512).
- A signing key for a Schnorr signature on FROST(Ed25519, SHA-512).
- Generated by the coordinator of the signing operation and distributed to each signing party.
- A valid verifying key for Schnorr signatures on FROST(Ed25519, SHA-512).