frost/frost-core/src/keys/dkg.rs

546 lines
20 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Distributed Key Generation functions and structures.
//!
//! The DKG module supports generating FROST key shares in a distributed manner,
//! without a trusted dealer, via two rounds of communication between all
//! participants.
//!
//! This implements FROST KeyGen from the original [FROST paper], specifically
//! Figure 1. This protocol is a variant of [Pedersen's DKG] that additionally
//! requires each participant to demonstrate knowledge of their secret by providing
//! other participants with proof in zero knowledge, instantiated as a Schnorr signature,
//! to protect against rogue-key attacks in the setting where `t ≥ n/2`.
//!
//! In Pedersen's DKG, each of the `n` participants executes [Feldman's
//! Verifiable Secret Sharing (VSS)][Feldman's VSS] as the dealer in parallel,
//! and derives their secret share as the sum of the shares received from each
//! of the `n` VSS executions.
//!
//! As required for any multi-party protocol using Feldman's VSS, the key
//! generation stage in FROST requires participants to maintain a consistent
//! view of the pubic commitments to the secret polynomial coefficients. This
//! DKG protocol requires participants to broadcast the commitment values
//! honestly (e.g., participants do not provide different commitment values to a
//! subset of participants) over a _[secure broadcast channel]_.
//!
//! For more details and an example, see the ciphersuite-specific crates, e.g.
//! [`frost_ristretto255::keys::dkg`](../../../../frost_ristretto255/keys/dkg).
//!
//! [FROST paper]: https://eprint.iacr.org/2020/852.pdf
//! [Pedersen's DKG]: https://link.springer.com/chapter/10.1007/3-540-46416-6_47
//! [Feldman's VSS]: https://www.cs.umd.edu/~gasarch/TOPICS/secretsharing/feldmanVSS.pdf
//! [secure broadcast channel]: https://frost.zfnd.org/terminology.html#broadcast-channel
use std::{collections::BTreeMap, iter};
use rand_core::{CryptoRng, RngCore};
use crate::{
Challenge, Ciphersuite, Element, Error, Field, Group, Header, Identifier, Scalar, Signature,
SigningKey, VerifyingKey,
};
use super::{
evaluate_polynomial, generate_coefficients, generate_secret_polynomial,
validate_num_of_signers, KeyPackage, PublicKeyPackage, SecretShare, SigningShare,
VerifiableSecretSharingCommitment,
};
/// DKG Round 1 structures.
pub mod round1 {
use derive_getters::Getters;
use zeroize::Zeroize;
use crate::serialization::{Deserialize, Serialize};
use super::*;
/// 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).
#[derive(Clone, Debug, PartialEq, Eq, Getters)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
pub struct Package<C: Ciphersuite> {
/// Serialization header
#[getter(skip)]
pub(crate) header: Header<C>,
/// The public commitment from the participant (C_i)
pub(crate) commitment: VerifiableSecretSharingCommitment<C>,
/// The proof of knowledge of the temporary secret (σ_i = (R_i, μ_i))
pub(crate) proof_of_knowledge: Signature<C>,
}
impl<C> Package<C>
where
C: Ciphersuite,
{
/// Create a new [`Package`] instance.
pub fn new(
commitment: VerifiableSecretSharingCommitment<C>,
proof_of_knowledge: Signature<C>,
) -> Self {
Self {
header: Header::default(),
commitment,
proof_of_knowledge,
}
}
}
#[cfg(feature = "serialization")]
impl<C> Package<C>
where
C: Ciphersuite,
{
/// Serialize the struct into a Vec.
pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
Serialize::serialize(&self)
}
/// Deserialize the struct from a slice of bytes.
pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
Deserialize::deserialize(bytes)
}
}
/// 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!
#[derive(Clone, PartialEq, Eq)]
pub struct SecretPackage<C: Ciphersuite> {
/// The identifier of the participant holding the secret.
pub(crate) identifier: Identifier<C>,
/// Coefficients of the temporary secret polynomial for the participant.
/// These are (a_{i0}, ..., a_{i(t1)})) which define the polynomial f_i(x)
pub(crate) coefficients: Vec<Scalar<C>>,
/// The public commitment for the participant (C_i)
pub(crate) commitment: VerifiableSecretSharingCommitment<C>,
/// The minimum number of signers.
pub(crate) min_signers: u16,
/// The total number of signers.
pub(crate) max_signers: u16,
}
impl<C> SecretPackage<C>
where
C: Ciphersuite,
{
/// Returns the secret coefficients.
#[cfg(feature = "internals")]
pub fn coefficients(&self) -> &[Scalar<C>] {
&self.coefficients
}
}
impl<C> std::fmt::Debug for SecretPackage<C>
where
C: Ciphersuite,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SecretPackage")
.field("identifier", &self.identifier)
.field("coefficients", &"<redacted>")
.field("commitment", &self.commitment)
.field("min_signers", &self.min_signers)
.field("max_signers", &self.max_signers)
.finish()
}
}
impl<C> Zeroize for SecretPackage<C>
where
C: Ciphersuite,
{
fn zeroize(&mut self) {
for c in self.coefficients.iter_mut() {
*c = <<C::Group as Group>::Field>::zero();
}
}
}
}
/// DKG Round 2 structures.
pub mod round2 {
use derive_getters::Getters;
use zeroize::Zeroize;
use crate::serialization::{Deserialize, Serialize};
use super::*;
/// 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.
#[derive(Clone, Debug, PartialEq, Eq, Getters)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(bound = "C: Ciphersuite"))]
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
pub struct Package<C: Ciphersuite> {
/// Serialization header
#[getter(skip)]
pub(crate) header: Header<C>,
/// The secret share being sent.
pub(crate) signing_share: SigningShare<C>,
}
impl<C> Package<C>
where
C: Ciphersuite,
{
/// Create a new [`Package`] instance.
pub fn new(signing_share: SigningShare<C>) -> Self {
Self {
header: Header::default(),
signing_share,
}
}
}
#[cfg(feature = "serialization")]
impl<C> Package<C>
where
C: Ciphersuite,
{
/// Serialize the struct into a Vec.
pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
Serialize::serialize(&self)
}
/// Deserialize the struct from a slice of bytes.
pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
Deserialize::deserialize(bytes)
}
}
/// 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!
#[derive(Clone, PartialEq, Eq)]
pub struct SecretPackage<C: Ciphersuite> {
/// The identifier of the participant holding the secret.
pub(crate) identifier: Identifier<C>,
/// The public commitment from the participant (C_i)
pub(crate) commitment: VerifiableSecretSharingCommitment<C>,
/// The participant's own secret share (f_i(i)).
pub(crate) secret_share: Scalar<C>,
/// The minimum number of signers.
pub(crate) min_signers: u16,
/// The total number of signers.
pub(crate) max_signers: u16,
}
impl<C> std::fmt::Debug for SecretPackage<C>
where
C: Ciphersuite,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SecretPackage")
.field("identifier", &self.identifier)
.field("commitment", &self.commitment)
.field("secret_share", &"<redacted>")
.field("min_signers", &self.min_signers)
.field("max_signers", &self.max_signers)
.finish()
}
}
impl<C> Zeroize for SecretPackage<C>
where
C: Ciphersuite,
{
fn zeroize(&mut self) {
self.secret_share = <<C::Group as Group>::Field>::zero();
}
}
}
/// Performs the first part of the distributed key generation protocol
/// for the given participant.
///
/// It returns the [`round1::SecretPackage`] that must be kept in memory
/// by the participant for the other steps, and the [`round1::Package`] that
/// must be sent to other participants.
pub fn part1<C: Ciphersuite, R: RngCore + CryptoRng>(
identifier: Identifier<C>,
max_signers: u16,
min_signers: u16,
mut rng: R,
) -> Result<(round1::SecretPackage<C>, round1::Package<C>), Error<C>> {
validate_num_of_signers::<C>(min_signers, max_signers)?;
let secret: SigningKey<C> = SigningKey::new(&mut rng);
// Round 1, Step 1
//
// > Every participant P_i samples t random values (a_{i0}, ..., a_{i(t1)}) ← Z_q
//
// Round 1, Step 3
//
// > Every participant P_i computes a public commitment
// > C⃗_i = 〈φ_{i0}, ..., φ_{i(t1)}〉, where φ_{ij} = g^{a_{ij}}, 0 ≤ j ≤ t 1
let coefficients = generate_coefficients::<C, R>(min_signers as usize - 1, &mut rng);
let (coefficients, commitment) =
generate_secret_polynomial(&secret, max_signers, min_signers, coefficients)?;
let proof_of_knowledge =
compute_proof_of_knowledge(identifier, &coefficients, &commitment, &mut rng)?;
let secret_package = round1::SecretPackage {
identifier,
coefficients,
commitment: commitment.clone(),
min_signers,
max_signers,
};
let package = round1::Package {
header: Header::default(),
commitment,
proof_of_knowledge,
};
Ok((secret_package, package))
}
/// Generates the challenge for the proof of knowledge to a secret for the DKG.
fn challenge<C>(
identifier: Identifier<C>,
verifying_key: &VerifyingKey<C>,
R: &Element<C>,
) -> Option<Challenge<C>>
where
C: Ciphersuite,
{
let mut preimage = vec![];
preimage.extend_from_slice(identifier.serialize().as_ref());
preimage.extend_from_slice(<C::Group>::serialize(&verifying_key.element).as_ref());
preimage.extend_from_slice(<C::Group>::serialize(R).as_ref());
Some(Challenge(C::HDKG(&preimage[..])?))
}
/// Compute the proof of knowledge of the secret coefficients used to generate
/// the public secret sharing commitment.
#[cfg_attr(feature = "internals", visibility::make(pub))]
pub(crate) fn compute_proof_of_knowledge<C: Ciphersuite, R: RngCore + CryptoRng>(
identifier: Identifier<C>,
coefficients: &[Scalar<C>],
commitment: &VerifiableSecretSharingCommitment<C>,
mut rng: R,
) -> Result<Signature<C>, Error<C>> {
// Round 1, Step 2
//
// > Every P_i computes a proof of knowledge to the corresponding secret
// > a_{i0} by calculating σ_i = (R_i, μ_i), such that k ← Z_q, R_i = g^k,
// > c_i = H(i, Φ, g^{a_{i0}} , R_i), μ_i = k + a_{i0} · c_i, with Φ being
// > a context string to prevent replay attacks.
let k = <<C::Group as Group>::Field>::random(&mut rng);
let R_i = <C::Group>::generator() * k;
let c_i = challenge::<C>(identifier, &commitment.verifying_key()?, &R_i)
.ok_or(Error::DKGNotSupported)?;
let a_i0 = *coefficients
.get(0)
.expect("coefficients must have at least one element");
let mu_i = k + a_i0 * c_i.0;
Ok(Signature { R: R_i, z: mu_i })
}
/// Verifies the proof of knowledge of the secret coefficients used to generate the
/// public secret sharing commitment.
#[cfg_attr(feature = "internals", visibility::make(pub))]
pub(crate) fn verify_proof_of_knowledge<C: Ciphersuite>(
identifier: Identifier<C>,
commitment: &VerifiableSecretSharingCommitment<C>,
proof_of_knowledge: Signature<C>,
) -> Result<(), Error<C>> {
// Round 1, Step 5
//
// > Upon receiving C⃗_, σ_ from participants 1 ≤ ≤ n, ≠ i, participant
// > P_i verifies σ_ = (R_, μ_), aborting on failure, by checking
// > R_ ? ≟ g^{μ_} · φ^{-c_}_{0}, where c_ = H(, Φ, φ_{0}, R_).
let ell = identifier;
let R_ell = proof_of_knowledge.R;
let mu_ell = proof_of_knowledge.z;
let phi_ell0 = commitment.verifying_key()?;
let c_ell = challenge::<C>(ell, &phi_ell0, &R_ell).ok_or(Error::DKGNotSupported)?;
if R_ell != <C::Group>::generator() * mu_ell - phi_ell0.element * c_ell.0 {
return Err(Error::InvalidProofOfKnowledge { culprit: ell });
}
Ok(())
}
/// Performs the second part of the distributed key generation protocol
/// for the participant holding the given [`round1::SecretPackage`],
/// given the received [`round1::Package`]s received from the other participants.
///
/// `round1_packages` maps the identifier of each participant to the
/// [`round1::Package`] they sent. These identifiers must come from whatever mapping
/// the coordinator has between communication channels and participants, i.e.
/// they must have assurance that the [`round1::Package`] came from
/// the participant with that identifier.
///
/// It returns the [`round2::SecretPackage`] that must be kept in memory
/// by the participant for the final step, and the a map of [`round2::Package`]s that
/// must be sent to each participant who has the given identifier in the map key.
pub fn part2<C: Ciphersuite>(
secret_package: round1::SecretPackage<C>,
round1_packages: &BTreeMap<Identifier<C>, round1::Package<C>>,
) -> Result<
(
round2::SecretPackage<C>,
BTreeMap<Identifier<C>, round2::Package<C>>,
),
Error<C>,
> {
if round1_packages.len() != (secret_package.max_signers - 1) as usize {
return Err(Error::IncorrectNumberOfPackages);
}
let mut round2_packages = BTreeMap::new();
for (sender_identifier, round1_package) in round1_packages {
let ell = *sender_identifier;
// Round 1, Step 5
verify_proof_of_knowledge(
ell,
&round1_package.commitment,
round1_package.proof_of_knowledge,
)?;
// Round 2, Step 1
//
// > Each P_i securely sends to each other participant P_ a secret share (, f_i()),
// > deleting f_i and each share afterward except for (i, f_i(i)),
// > which they keep for themselves.
let signing_share = SigningShare::from_coefficients(&secret_package.coefficients, ell);
round2_packages.insert(
ell,
round2::Package {
header: Header::default(),
signing_share,
},
);
}
let fii = evaluate_polynomial(secret_package.identifier, &secret_package.coefficients);
Ok((
round2::SecretPackage {
identifier: secret_package.identifier,
commitment: secret_package.commitment,
secret_share: fii,
min_signers: secret_package.min_signers,
max_signers: secret_package.max_signers,
},
round2_packages,
))
}
/// Performs the third and final part of the distributed key generation protocol
/// for the participant holding the given [`round2::SecretPackage`],
/// given the received [`round1::Package`]s and [`round2::Package`]s received from
/// the other participants.
///
/// `round1_packages` must be the same used in [`part2()`].
///
/// `round2_packages` maps the identifier of each participant to the
/// [`round2::Package`] they sent. These identifiers must come from whatever mapping
/// the coordinator has between communication channels and participants, i.e.
/// they must have assurance that the [`round2::Package`] came from
/// the participant with that identifier.
///
/// 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 part3<C: Ciphersuite>(
round2_secret_package: &round2::SecretPackage<C>,
round1_packages: &BTreeMap<Identifier<C>, round1::Package<C>>,
round2_packages: &BTreeMap<Identifier<C>, round2::Package<C>>,
) -> Result<(KeyPackage<C>, PublicKeyPackage<C>), Error<C>> {
if round1_packages.len() != (round2_secret_package.max_signers - 1) as usize {
return Err(Error::IncorrectNumberOfPackages);
}
if round1_packages.len() != round2_packages.len() {
return Err(Error::IncorrectNumberOfPackages);
}
if round1_packages
.keys()
.any(|id| !round2_packages.contains_key(id))
{
return Err(Error::IncorrectPackage);
}
let mut signing_share = <<C::Group as Group>::Field>::zero();
for (sender_identifier, round2_package) in round2_packages {
// Round 2, Step 2
//
// > Each P_i verifies their shares by calculating:
// > g^{f_(i)} ≟ ∏^{t1}_{k=0} φ^{i^k mod q}_{k}, aborting if the
// > check fails.
let ell = *sender_identifier;
let f_ell_i = round2_package.signing_share;
let commitment = &round1_packages
.get(&ell)
.ok_or(Error::PackageNotFound)?
.commitment;
// The verification is exactly the same as the regular SecretShare verification;
// however the required components are in different places.
// Build a temporary SecretShare so what we can call verify().
let secret_share = SecretShare {
header: Header::default(),
identifier: round2_secret_package.identifier,
signing_share: f_ell_i,
commitment: commitment.clone(),
};
// Verify the share. We don't need the result.
let _ = secret_share.verify()?;
// Round 2, Step 3
//
// > Each P_i calculates their long-lived private signing share by computing
// > s_i = ∑^n_{=1} f_(i), stores s_i securely, and deletes each f_(i).
signing_share = signing_share + f_ell_i.0;
}
signing_share = signing_share + round2_secret_package.secret_share;
let signing_share = SigningShare(signing_share);
// Round 2, Step 4
//
// > Each P_i calculates their public verification share Y_i = g^{s_i}.
let verifying_share = signing_share.into();
let commitments: BTreeMap<_, _> = round1_packages
.iter()
.map(|(id, package)| (*id, &package.commitment))
.chain(iter::once((
round2_secret_package.identifier,
&round2_secret_package.commitment,
)))
.collect();
let public_key_package = PublicKeyPackage::from_dkg_commitments(&commitments)?;
let key_package = KeyPackage {
header: Header::default(),
identifier: round2_secret_package.identifier,
signing_share,
verifying_share,
verifying_key: public_key_package.verifying_key,
min_signers: round2_secret_package.min_signers,
};
Ok((key_package, public_key_package))
}