Add documentation generator; add frost-p256 documentation using it (#130)

* add gendoc.py

* add frost_p256 docs with gendoc.py

* convert Python script to Rust

* A word

* replace 'symbol' to the more appropriate 'item'

* Apply suggestions from code review

* Remove rustdoc ref to removed SharePackage

Co-authored-by: Deirdre Connolly <durumcrustulum@gmail.com>
This commit is contained in:
Conrado Gouvea 2022-10-06 16:16:42 -03:00 committed by GitHub
parent 28be955e38
commit b82ea8a8d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 195 additions and 14 deletions

View File

@ -22,7 +22,7 @@ mod tests;
pub use frost_core::Error;
#[derive(Clone, Copy)]
/// An implementation of the FROST P-256 SHA-256 ciphersuite scalar field.
/// An implementation of the FROST(P-256, SHA-256) ciphersuite scalar field.
pub struct P256ScalarField;
impl Field for P256ScalarField {
@ -77,7 +77,7 @@ impl Field for P256ScalarField {
}
#[derive(Clone, Copy, PartialEq, Eq)]
/// An implementation of the FROST P-256 ciphersuite group.
/// An implementation of the FROST(P-256, SHA-256) ciphersuite group.
pub struct P256Group;
impl Group for P256Group {
@ -150,7 +150,7 @@ impl Group for P256Group {
const CONTEXT_STRING: &str = "FROST-P256-SHA256-v10";
#[derive(Clone, Copy, PartialEq, Eq)]
/// An implementation of the FROST ciphersuite FROST(P-256, SHA-256).
/// An implementation of the FROST(P-256, SHA-256) ciphersuite.
pub struct P256Sha256;
impl Ciphersuite for P256Sha256 {
@ -225,14 +225,15 @@ impl Ciphersuite for P256Sha256 {
// Shorthand alias for the ciphersuite
type P = P256Sha256;
///
/// A FROST(P-256, SHA-256) participant identifier.
pub type Identifier = frost::Identifier<P>;
///
/// FROST(P-256, SHA-256) keys, key generation, key shares.
pub mod keys {
use super::*;
///
/// Allows all participants' keys to be generated using a central, trusted
/// dealer.
pub fn keygen_with_dealer<RNG: RngCore + CryptoRng>(
num_signers: u8,
threshold: u8,
@ -241,28 +242,52 @@ pub mod keys {
frost::keys::keygen_with_dealer(num_signers, threshold, &mut rng)
}
/// Secret and public key material generated by a dealer performing
/// [`keygen_with_dealer`].
///
/// # Security
///
/// To derive a FROST(P-256, SHA-256) keypair, the receiver of the [`SecretShare`] *must* call
/// .try_into(), which under the hood also performs validation.
pub type SecretShare = frost::keys::SecretShare<P>;
/// A FROST(P-256, SHA-256) keypair, which can be generated either by a trusted dealer or using
/// a DKG.
///
/// 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<P>;
/// 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<P>;
}
///
/// FROST(P-256, SHA-256) Round 1 functionality and types.
pub mod round1 {
use frost_core::frost::keys::SigningShare;
use super::*;
/// Comprised of FROST(P-256, SHA-256) hiding and binding nonces.
///
/// 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<P>;
/// 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<P>;
/// 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<P>,
secret: &SigningShare<P>,
@ -275,20 +300,30 @@ pub mod round1 {
}
}
///
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party.
pub type SigningPackage = frost::SigningPackage<P>;
///
/// FROST(P-256, SHA-256) Round 2 functionality and types, for signature share generation.
pub mod round2 {
use super::*;
///
/// A FROST(P-256, 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<P>;
///
/// Generated by the coordinator of the signing operation and distributed to
/// each signing party
pub type SigningPackage = frost::SigningPackage<P>;
/// Performed once by each participant selected for the signing operation.
///
/// Receives the message to be signed and a set of signing commitments and a set
/// of randomizing commitments to be used in that signing operation, including
/// that for this participant.
///
/// Assumes the participant has already determined which nonce corresponds with
/// the commitment that was assigned by the coordinator in the SigningPackage.
pub fn sign(
signing_package: &SigningPackage,
signer_nonces: &round1::SigningNonces,
@ -298,10 +333,24 @@ pub mod round2 {
}
}
///
/// A Schnorr signature on FROST(P-256, SHA-256).
pub type Signature = frost_core::Signature<P>;
/// Verifies each FROST(P-256, SHA-256) participant's signature share, and if all are valid,
/// aggregates the shares into a signature to publish.
///
/// Resulting signature is compatible with verification of a plain Schnorr
/// signature.
///
/// This operation is performed by a coordinator that can communicate with all
/// the signing participants before publishing the final signature. The
/// coordinator can be one of the participants or a semi-trusted third party
/// (who is trusted to not perform denial of service attacks, but does not learn
/// any secret information). Note that because the coordinator is trusted to
/// report misbehaving parties in order to avoid publishing an invalid
/// signature, if the coordinator themselves is a signer and misbehaves, they
/// can avoid that step. However, at worst, this results in a denial of
/// service attack due to publishing an invalid signature.
pub fn aggregate(
signing_package: &round2::SigningPackage,
signature_shares: &[round2::SignatureShare],
@ -310,8 +359,8 @@ pub fn aggregate(
frost::aggregate(signing_package, signature_shares, pubkeys)
}
///
/// A signing key for a Schnorr signature on FROST(P-256, SHA-256).
pub type SigningKey = frost_core::SigningKey<P>;
///
/// A valid verifying key for Schnorr signatures on FROST(P-256, SHA-256).
pub type VerifyingKey = frost_core::VerifyingKey<P>;

9
gendoc/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "gendoc"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
regex = "1.6.0"

123
gendoc/src/main.rs Normal file
View File

@ -0,0 +1,123 @@
//! Generate documentation for a ciphersuite based on another ciphersuite implementation.
//!
//! The documentation for each ciphersuite is very similar, with the only difference being
//! the ciphersuite name.
//!
//! To make it easier to update all ciphersuite documentation when a change is needed,
//! this binary allows updating all of them based on a single one. This binary
//! uses frost-ristretto255 as the "canonical" one, so:
//!
//! - Change any documentation of a public function or struct in `frost-ristretto255/src/lib.rs`
//! - Run `cargo run --manifest-path gendoc/Cargo.toml` to update the documentation
//! of the other ciphersuites.
use std::{fs, iter::zip};
use regex::Regex;
/// Read the public documentation of public items (functions, types, etc.) in the given file.
///
/// This identifiers snippets in the given file with the format:
///
/// ```
/// /// Some documentation
/// pub [rest of the line...]
/// ```
///
/// It will return details for each match:
/// - the item "name" ("[rest of the line...]" above, but after replacing
/// any string in `suite_names_code` with "SuiteName")
/// - the entire documentation string
/// - the start and end position of the documentation string in the code, which allows
/// replacing it later
///
/// # Parameters
///
/// filename: the name of the file to read.
/// suite_names_code: strings that reference the specific suite in code
/// inside `fn` and should be ignore when using for replacements.
///
/// # Returns
///
/// A list with data for each item, see above.
fn read_docs(filename: &str, suite_names_code: &[&str]) -> Vec<(String, String, usize, usize)> {
let mut docs = Vec::new();
let code = fs::read_to_string(filename).unwrap();
let re = Regex::new(r"(?m)((^[ ]*///.*\n)+)\s*pub (.*)").unwrap();
for m in re.captures_iter(code.as_str()) {
// Captures: 0 - the whole match; 1: documentation;
// 2: internal capture group; 3: the item "name" as described above
let (name, doc) = (m.get(3).unwrap().as_str(), m.get(1).unwrap().as_str());
let mut name = name.to_string();
// Replacing ciphersuite-specific names with a fixed string allows
// comparing item "names" to check later if we're working on the
// same item.
for n in suite_names_code.iter() {
name = name.replace(n, "SuiteName");
}
docs.push((
name,
doc.to_string(),
m.get(1).unwrap().start(),
m.get(1).unwrap().end(),
))
}
docs
}
/// Write the documentation for the given file, using a previously-read documentation
/// from another file as a base, replacing ciphersuite-specific references as needed.
///
/// # Parameters
///
/// docs: the documentation from another file which will be used as a base.
/// filename: the name of the file to write documentation for.
/// suite_names_code: ciphersuite-specific references for code in `fn`, see read_docs
/// old_suite_names_doc: ciphersuite-specific references in the documentation of
/// the base file
/// new_suite_names_doc: replacements to use in the documentation of the given file
/// for each reference in `old_suite_names_doc`.
fn write_docs(
docs: Vec<(String, String, usize, usize)>,
filename: &str,
suite_names_code: &[&str],
old_suite_names_doc: &[&str],
new_suite_names_doc: &[&str],
) {
let old_docs = read_docs(filename, suite_names_code);
let mut code = fs::read_to_string(filename).unwrap();
// To be able to replace the documentation properly, start from the end, which
// will keep the string positions consistent
for ((old_name, _, old_start, old_end), (new_name, new_doc, _, _)) in
zip(old_docs.iter().rev(), docs.iter().rev())
{
assert_eq!(old_name, new_name, "source code does not match");
// Replaces ciphersuite-references in documentation
let mut new_doc = new_doc.to_string();
for (old_n, new_n) in zip(old_suite_names_doc.iter(), new_suite_names_doc.iter()) {
new_doc = new_doc.replace(old_n, new_n)
}
code.replace_range(old_start..old_end, &new_doc);
}
fs::write(filename, code).unwrap();
}
fn main() {
let docs = read_docs(
"frost-ristretto255/src/lib.rs",
&["Ristretto255Sha512", "Ristretto", "<R>"],
);
let old_suite_names_doc = &["FROST(ristretto255, SHA-512)"];
// To add a new ciphersuite, just copy this call and replace the required strings.
write_docs(
docs,
"frost-p256/src/lib.rs",
&["P256Sha256", "P256", "<P>"],
old_suite_names_doc,
&["FROST(P-256, SHA-256)"],
)
}