add gendoc --check, use in CI (#184)

* add gendoc --check, use in CI

* ran gendoc
This commit is contained in:
Conrado Gouvea 2022-11-24 21:36:34 -03:00 committed by GitHub
parent 3e1fe25dbd
commit d8700fed06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 298 additions and 49 deletions

View File

@ -85,6 +85,11 @@ jobs:
command: fmt
args: --all -- --check
- uses: actions-rs/cargo@v1.0.3
with:
command: run
args: --bin gendoc -- --check
docs:
name: Check Rust doc
runs-on: ubuntu-latest

View File

@ -58,8 +58,7 @@ for participant_index in 1..=max_signers {
max_signers,
min_signers,
&mut rng,
)
.unwrap();
)?;
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
@ -104,11 +103,8 @@ for participant_index in 1..=max_signers {
round1_secret_packages
.remove(&participant_identifier)
.unwrap(),
received_round1_packages
.get(&participant_identifier)
.unwrap(),
)
.expect("should work");
&received_round1_packages[&participant_identifier],
)?;
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
@ -150,12 +146,12 @@ for participant_index in 1..=max_signers {
&round2_secret_packages[&participant_identifier],
&received_round1_packages[&participant_identifier],
&received_round2_packages[&participant_identifier],
)
.unwrap();
)?;
key_packages.insert(participant_identifier, key_package);
pubkey_packages.insert(participant_identifier, pubkey_package_for_participant);
}
// With its own key package and the pubkey package, each participant can now proceed
// to sign with FROST.
# Ok::<(), frost::Error>(())
```

View File

@ -58,8 +58,7 @@ for participant_index in 1..=max_signers {
max_signers,
min_signers,
&mut rng,
)
.unwrap();
)?;
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
@ -104,11 +103,8 @@ for participant_index in 1..=max_signers {
round1_secret_packages
.remove(&participant_identifier)
.unwrap(),
received_round1_packages
.get(&participant_identifier)
.unwrap(),
)
.expect("should work");
&received_round1_packages[&participant_identifier],
)?;
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
@ -150,12 +146,12 @@ for participant_index in 1..=max_signers {
&round2_secret_packages[&participant_identifier],
&received_round1_packages[&participant_identifier],
&received_round2_packages[&participant_identifier],
)
.unwrap();
)?;
key_packages.insert(participant_identifier, key_package);
pubkey_packages.insert(participant_identifier, pubkey_package_for_participant);
}
// With its own key package and the pubkey package, each participant can now proceed
// to sign with FROST.
# Ok::<(), frost::Error>(())
```

157
frost-secp256k1/dkg.md Normal file
View File

@ -0,0 +1,157 @@
# Distributed Key Generation (DKG)
The DKG module supports generating FROST key shares in a distributed manner,
without a trusted dealer.
Before starting, each participant needs an unique identifier, which can be built from
a `u16`. The process in which these identifiers are allocated is up to the application.
The distributed key generation process has 3 parts, with 2 communication rounds
between them, in which each participant needs to send a "package" to every other
participant. In the first round, each participant sends the same package
(a [`Round1Package`]) to every other. In the second round, each receiver gets
their own package (a [`Round2Package`]).
Between part 1 and 2, each participant needs to hold onto a [`Round1SecretPackage`]
that MUST be kept secret. Between part 2 and 3, each participant needs to hold
onto a [`Round2SecretPackage`].
After the third part, each participant will get a [`KeyPackage`] with their
long-term secret share that must be kept secret, and a [`PublicKeyPackage`]
that is public (and will be the same between all participants). With those
they can proceed to sign messages with FROST.
## Example
```rust
use rand::thread_rng;
use std::collections::HashMap;
use frost_secp256k1 as frost;
let mut rng = thread_rng();
////////////////////////////////////////////////////////////////////////////
// Key generation, Round 1
////////////////////////////////////////////////////////////////////////////
let max_signers = 5;
let min_signers = 3;
// Keep track of each participant's round 1 secret package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
let mut round1_secret_packages = HashMap::new();
// Keep track of all round 1 packages sent to the given participant.
// This is used to simulate the broadcast; in practice the packages
// will be sent through some communication channel.
let mut received_round1_packages = HashMap::new();
// For each participant, perform the first part of the DKG protocol.
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (secret_package, round1_package) = frost::keys::dkg::keygen_part1(
participant_identifier,
max_signers,
min_signers,
&mut rng,
)?;
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
round1_secret_packages.insert(participant_identifier, secret_package);
// "Send" the round 1 package to all other participants. In this
// test this is simulated using a HashMap; in practice this will be
// sent through some communication channel.
for receiver_participant_index in 1..=max_signers {
if receiver_participant_index == participant_index {
continue;
}
let receiver_participant_identifier: frost::Identifier = receiver_participant_index
.try_into()
.expect("should be nonzero");
received_round1_packages
.entry(receiver_participant_identifier)
.or_insert_with(Vec::new)
.push(round1_package.clone());
}
}
////////////////////////////////////////////////////////////////////////////
// Key generation, Round 2
////////////////////////////////////////////////////////////////////////////
// Keep track of each participant's round 2 secret package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
let mut round2_secret_packages = HashMap::new();
// Keep track of all round 2 packages sent to the given participant.
// This is used to simulate the broadcast; in practice the packages
// will be sent through some communication channel.
let mut received_round2_packages = HashMap::new();
// For each participant, perform the second part of the DKG protocol.
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (round2_secret_package, round2_packages) = frost::keys::dkg::keygen_part2(
round1_secret_packages
.remove(&participant_identifier)
.unwrap(),
&received_round1_packages[&participant_identifier],
)?;
// Store the participant's secret package for later use.
// In practice each participant will store it in their own environment.
round2_secret_packages.insert(participant_identifier, round2_secret_package);
// "Send" the round 2 package to all other participants. In this
// test this is simulated using a HashMap; in practice this will be
// sent through some communication channel.
// Note that, in contrast to the previous part, here each other participant
// gets its own specific package.
for round2_package in round2_packages {
received_round2_packages
.entry(round2_package.receiver_identifier)
.or_insert_with(Vec::new)
.push(round2_package);
}
}
////////////////////////////////////////////////////////////////////////////
// Key generation, final computation
////////////////////////////////////////////////////////////////////////////
// Keep track of each participant's long-lived key package.
// In practice each participant will keep its copy; no one
// will have all the participant's packages.
let mut key_packages = HashMap::new();
// Keep track of each participant's public key package.
// In practice, if there is a Coordinator, only they need to store the set.
// If there is not, then all candidates must store their own sets.
// All participants will have the same exact public key package.
let mut pubkey_packages = HashMap::new();
// For each participant, perform the third part of the DKG protocol.
// In practice, each participant will perform this on their own environments.
for participant_index in 1..=max_signers {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (key_package, pubkey_package_for_participant) = frost::keys::dkg::keygen_part3(
&round2_secret_packages[&participant_identifier],
&received_round1_packages[&participant_identifier],
&received_round2_packages[&participant_identifier],
)?;
key_packages.insert(participant_identifier, key_package);
pubkey_packages.insert(participant_identifier, pubkey_package_for_participant);
}
// With its own key package and the pubkey package, each participant can now proceed
// to sign with FROST.
# Ok::<(), frost::Error>(())
```

View File

@ -242,10 +242,10 @@ impl Ciphersuite for Secp256K1Sha256 {
}
}
type R = Secp256K1Sha256;
type S = Secp256K1Sha256;
/// A FROST(secp256k1, SHA-256) participant identifier.
pub type Identifier = frost::Identifier<R>;
pub type Identifier = frost::Identifier<S>;
/// FROST(secp256k1, SHA-256) keys, key generation, key shares.
pub mod keys {
@ -268,7 +268,7 @@ pub mod keys {
///
/// To derive a FROST(secp256k1, SHA-256) keypair, the receiver of the [`SecretShare`] *must* call
/// .into(), which under the hood also performs validation.
pub type SecretShare = frost::keys::SecretShare<R>;
pub type SecretShare = frost::keys::SecretShare<S>;
/// A FROST(secp256k1, SHA-256) keypair, which can be generated either by a trusted dealer or using
/// a DKG.
@ -276,13 +276,93 @@ pub mod keys {
/// When using a central dealer, [`SecretShare`]s are distributed to
/// participants, who then perform verification, before deriving
/// [`KeyPackage`]s, which they store to later use during signing.
pub type KeyPackage = frost::keys::KeyPackage<R>;
pub type KeyPackage = frost::keys::KeyPackage<S>;
/// Public data that contains all the signers' public keys as well as the
/// group public key.
///
/// Used for verification purposes before publishing a signature.
pub type PublicKeyPackage = frost::keys::PublicKeyPackage<R>;
pub type PublicKeyPackage = frost::keys::PublicKeyPackage<S>;
pub mod dkg {
#![doc = include_str!("../dkg.md")]
use super::*;
/// The secret package that must be kept in memory by the participant
/// between the first and second parts of the DKG protocol (round 1).
///
/// # Security
///
/// This package MUST NOT be sent to other participants!
pub type Round1SecretPackage = frost::keys::dkg::Round1SecretPackage<S>;
/// The package that must be broadcast by each participant to all other participants
/// between the first and second parts of the DKG protocol (round 1).
pub type Round1Package = frost::keys::dkg::Round1Package<S>;
/// The secret package that must be kept in memory by the participant
/// between the second and third parts of the DKG protocol (round 2).
///
/// # Security
///
/// This package MUST NOT be sent to other participants!
pub type Round2SecretPackage = frost::keys::dkg::Round2SecretPackage<S>;
/// A package that must be sent by each participant to some other participants
/// in Round 2 of the DKG protocol. Note that there is one specific package
/// for each specific recipient, in contrast to Round 1.
///
/// # Security
///
/// The package must be sent on an *confidential* and *authenticated* channel.
pub type Round2Package = frost::keys::dkg::Round2Package<S>;
/// Performs the first part of the distributed key generation protocol
/// for the given participant.
///
/// It returns the [`Round1SecretPackage`] that must be kept in memory
/// by the participant for the other steps, and the [`Round1Package`] that
/// must be sent to other participants.
pub fn keygen_part1<R: RngCore + CryptoRng>(
identifier: Identifier,
max_signers: u16,
min_signers: u16,
mut rng: R,
) -> Result<(Round1SecretPackage, Round1Package), Error> {
frost::keys::dkg::keygen_part1(identifier, max_signers, min_signers, &mut rng)
}
/// Performs the second part of the distributed key generation protocol
/// for the participant holding the given [`Round1SecretPackage`],
/// given the received [`Round1Package`]s received from the other participants.
///
/// It returns the [`Round2SecretPackage`] that must be kept in memory
/// by the participant for the final step, and the [`Round2Package`]s that
/// must be sent to other participants.
pub fn keygen_part2(
secret_package: Round1SecretPackage,
round1_packages: &[Round1Package],
) -> Result<(Round2SecretPackage, Vec<Round2Package>), Error> {
frost::keys::dkg::keygen_part2(secret_package, round1_packages)
}
/// Performs the third and final part of the distributed key generation protocol
/// for the participant holding the given [`Round2SecretPackage`],
/// given the received [`Round1Package`]s and [`Round2Package`]s received from
/// the other participants.
///
/// It returns the [`KeyPackage`] that has the long-lived key share for the
/// participant, and the [`PublicKeyPackage`]s that has public information
/// about all participants; both of which are required to compute FROST
/// signatures.
pub fn keygen_part3(
round2_secret_package: &Round2SecretPackage,
round1_packages: &[Round1Package],
round2_packages: &[Round2Package],
) -> Result<(KeyPackage, PublicKeyPackage), Error> {
frost::keys::dkg::keygen_part3(round2_secret_package, round1_packages, round2_packages)
}
}
}
/// FROST(secp256k1, SHA-256) Round 1 functionality and types.
@ -296,33 +376,33 @@ pub mod round1 {
/// Note that [`SigningNonces`] must be used *only once* for a signing
/// operation; re-using nonces will result in leakage of a signer's long-lived
/// signing key.
pub type SigningNonces = frost::round1::SigningNonces<R>;
pub type SigningNonces = frost::round1::SigningNonces<S>;
/// Published by each participant in the first round of the signing protocol.
///
/// This step can be batched if desired by the implementation. Each
/// SigningCommitment can be used for exactly *one* signature.
pub type SigningCommitments = frost::round1::SigningCommitments<R>;
pub type SigningCommitments = frost::round1::SigningCommitments<S>;
/// Performed once by each participant selected for the signing operation.
///
/// Generates the signing nonces and commitments to be used in the signing
/// operation.
pub fn commit<RNG>(
participant_identifier: frost::Identifier<R>,
secret: &SigningShare<R>,
participant_identifier: frost::Identifier<S>,
secret: &SigningShare<S>,
rng: &mut RNG,
) -> (SigningNonces, SigningCommitments)
where
RNG: CryptoRng + RngCore,
{
frost::round1::commit::<R, RNG>(participant_identifier, secret, rng)
frost::round1::commit::<S, RNG>(participant_identifier, secret, rng)
}
}
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party.
pub type SigningPackage = frost::SigningPackage<R>;
pub type SigningPackage = frost::SigningPackage<S>;
/// FROST(secp256k1, SHA-256) Round 2 functionality and types, for signature share generation.
pub mod round2 {
@ -330,11 +410,11 @@ pub mod round2 {
/// A FROST(secp256k1, SHA-256) participant's signature share, which the Coordinator will aggregate with all other signer's
/// shares into the joint signature.
pub type SignatureShare = frost::round2::SignatureShare<R>;
pub type SignatureShare = frost::round2::SignatureShare<S>;
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party
pub type SigningPackage = frost::SigningPackage<R>;
pub type SigningPackage = frost::SigningPackage<S>;
/// Performed once by each participant selected for the signing operation.
///
@ -354,7 +434,7 @@ pub mod round2 {
}
/// A Schnorr signature on FROST(secp256k1, SHA-256).
pub type Signature = frost_core::Signature<R>;
pub type Signature = frost_core::Signature<S>;
/// Verifies each FROST(secp256k1, SHA-256) participant's signature share, and if all are valid,
/// aggregates the shares into a signature to publish.
@ -380,7 +460,7 @@ pub fn aggregate(
}
/// A signing key for a Schnorr signature on FROST(secp256k1, SHA-256).
pub type SigningKey = frost_core::SigningKey<R>;
pub type SigningKey = frost_core::SigningKey<S>;
/// A valid verifying key for Schnorr signatures on FROST(secp256k1, SHA-256).
pub type VerifyingKey = frost_core::VerifyingKey<R>;
pub type VerifyingKey = frost_core::VerifyingKey<S>;

View File

@ -11,7 +11,7 @@
//! - Run `cargo run --manifest-path gendoc/Cargo.toml` to update the documentation
//! of the other ciphersuites.
use std::{fs, iter::zip};
use std::{env, fs, iter::zip, process::ExitCode};
use regex::Regex;
@ -68,6 +68,7 @@ fn read_docs(filename: &str, suite_names_code: &[&str]) -> Vec<(String, String,
/// Write the documentation for the given file, using a previously-read documentation
/// from another file as a base, replacing ciphersuite-specific references as needed.
/// Returns 1 if the file was modified or 0 otherwise.
///
/// # Parameters
///
@ -84,9 +85,10 @@ fn write_docs(
suite_names_code: &[&str],
old_suite_names_doc: &[&str],
new_suite_names_doc: &[&str],
) {
) -> u8 {
let old_docs = read_docs(filename, suite_names_code);
let mut code = fs::read_to_string(filename).unwrap();
let original_code = code.clone();
// To be able to replace the documentation properly, start from the end, which
// will keep the string positions consistent
@ -105,7 +107,8 @@ fn write_docs(
}
code.replace_range(old_start..old_end, &new_doc);
}
fs::write(filename, code).unwrap();
fs::write(filename, &code).unwrap();
u8::from(original_code != code)
}
/// Copy a file into a new one, replacing the strings in `original_strings`
@ -115,17 +118,23 @@ fn copy_and_replace(
destination_filename: &str,
original_strings: &[&str],
replacement_strings: &[&str],
) {
) -> u8 {
let mut text = fs::read_to_string(origin_filename).unwrap();
let original_text = fs::read_to_string(destination_filename).unwrap_or_else(|_| "".to_string());
for (from, to) in std::iter::zip(original_strings, replacement_strings) {
text = text.replace(from, to)
}
fs::write(destination_filename, text).unwrap();
fs::write(destination_filename, &text).unwrap();
u8::from(original_text != text)
}
fn main() {
fn main() -> ExitCode {
let args: Vec<String> = env::args().collect();
let mut replaced = 0;
let check = args.len() == 2 && args[1] == "--check";
let docs = read_docs(
"frost-ristretto255/src/lib.rs",
&["Ristretto255Sha512", "Ristretto", "<R>"],
@ -137,7 +146,7 @@ fn main() {
// To add a new ciphersuite, just copy this call and replace the required strings.
write_docs(
replaced |= write_docs(
&docs,
"frost-p256/src/lib.rs",
&["P256Sha256", "P256", "<P>"],
@ -145,7 +154,7 @@ fn main() {
&["FROST(P-256, SHA-256)"],
);
for filename in ["README.md", "dkg.md"] {
copy_and_replace(
replaced |= copy_and_replace(
format!("{}/{}", original_basename, filename).as_str(),
format!("frost-p256/{}", filename).as_str(),
original_strings,
@ -153,7 +162,7 @@ fn main() {
);
}
write_docs(
replaced |= write_docs(
&docs,
"frost-ed25519/src/lib.rs",
&["Ed25519Sha512", "Ed25519", "<E>"],
@ -161,7 +170,7 @@ fn main() {
&["FROST(Ed25519, SHA-512)"],
);
for filename in ["README.md", "dkg.md"] {
copy_and_replace(
replaced |= copy_and_replace(
format!("{}/{}", original_basename, filename).as_str(),
format!("frost-ed25519/{}", filename).as_str(),
original_strings,
@ -169,19 +178,25 @@ fn main() {
);
}
write_docs(
replaced |= write_docs(
&docs,
"frost-secp256k1/src/lib.rs",
&["Secp256K1Sha556", "Secp256K1", "<E>"],
&["Secp256K1Sha556", "Secp256K1", "<S>"],
old_suite_names_doc,
&["FROST(secp256k1, SHA-256)"],
);
for filename in ["README.md", "dkg.md"] {
copy_and_replace(
filename,
"frost-secp256k1/README.md",
replaced |= copy_and_replace(
format!("{}/{}", original_basename, filename).as_str(),
format!("frost-secp256k1/{}", filename).as_str(),
original_strings,
&["frost_secp256k1", "secp256k1 curve"],
);
}
if check {
ExitCode::from(replaced)
} else {
ExitCode::SUCCESS
}
}