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:
parent
28be955e38
commit
b82ea8a8d2
|
@ -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>;
|
||||
|
|
|
@ -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"
|
|
@ -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)"],
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue