Add DKG support (#129)

* add DKG support

* refactor: call SecretShare::verify() in keygen_part3

* refactor: add generate_secret_polynomial and evaluate_polynomial

* refactor: factor out `evaluate_vss`

* refactor: factor out `compute_verifying_keys`

* fix full vector of coefficients not being returned

* simplify evaluate_polynomial to not receive constant term separately

* delete accidentally comitted .orig file

* Apply suggestions from code review

Co-authored-by: Marek <mail@marek.onl>

* Update frost-core/src/frost/keys.rs

Co-authored-by: Marek <mail@marek.onl>

* fix doc warnings

Co-authored-by: Marek <mail@marek.onl>
This commit is contained in:
Conrado Gouvea 2022-10-18 19:11:05 -03:00 committed by GitHub
parent 2b3b2344e8
commit bceafae2e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 687 additions and 62 deletions

View File

@ -14,12 +14,14 @@ use zeroize::{DefaultIsZeroes, Zeroize};
use crate::{frost::Identifier, Ciphersuite, Error, Field, Group, Scalar, VerifyingKey};
pub mod dkg;
/// Return a vector of randomly generated polynomial coefficients ([`Scalar`]s).
pub(crate) fn generate_coefficients<C: Ciphersuite, R: RngCore + CryptoRng>(
size: usize,
mut rng: R,
rng: &mut R,
) -> Vec<<<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar> {
iter::repeat_with(|| <<C::Group as Group>::Field as Field>::random(&mut rng))
iter::repeat_with(|| <<C::Group as Group>::Field as Field>::random(rng))
.take(size)
.collect()
}
@ -279,16 +281,7 @@ where
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-10.html#appendix-C.2-4
pub fn verify(&self) -> Result<(VerifyingShare<C>, VerifyingKey<C>), &'static str> {
let f_result = <C::Group as Group>::generator() * self.value.0;
let x = self.identifier.to_scalar()?;
let (_, result) = self.commitment.0.iter().fold(
(
<<C::Group as Group>::Field as Field>::one(),
<C::Group as Group>::identity(),
),
|(x_to_the_i, sum_so_far), comm_i| (x * x_to_the_i, sum_so_far + comm_i.0 * x_to_the_i),
);
let result = evaluate_vss(&self.commitment, self.identifier)?;
if !(f_result == result) {
return Err("SecretShare is invalid.");
@ -325,7 +318,7 @@ pub fn keygen_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>(
let secret = SharedSecret::random(&mut rng);
let group_public = VerifyingKey::from(&secret);
let coefficients = generate_coefficients::<C, R>(threshold as usize - 1, rng);
let coefficients = generate_coefficients::<C, R>(threshold as usize - 1, &mut rng);
let secret_shares = generate_secret_shares(&secret, num_signers, threshold, coefficients)?;
let mut signer_pubkeys: HashMap<Identifier<C>, VerifyingShare<C>> =
@ -345,6 +338,45 @@ pub fn keygen_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>(
))
}
/// Evaluate the polynomial with the given coefficients (constant term first)
/// at the point x=identifier using Horner's method.
///
/// Implements [`polynomial_evaluate`] from the spec.
///
/// [`polynomial_evaluate`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-10.html#name-evaluation-of-a-polynomial
fn evaluate_polynomial<C: Ciphersuite>(
identifier: Identifier<C>,
coefficients: &[Scalar<C>],
) -> Result<<<<C as Ciphersuite>::Group as Group>::Field as Field>::Scalar, &'static str> {
let mut value = <<C::Group as Group>::Field as Field>::zero();
let ell_scalar = identifier.to_scalar()?;
for coeff in coefficients.iter().skip(1).rev() {
value = value + *coeff;
value = ell_scalar * value;
}
value = value + coefficients[0];
Ok(value)
}
/// Evaluates the right-hand side of the VSS verification equation, namely
/// ∏^{t1}_{k=0} φ^{i^k mod q}_{k} using `identifier` as `i` and the
/// `commitment` as the commitment vector φ_
fn evaluate_vss<C: Ciphersuite>(
commitment: &VerifiableSecretSharingCommitment<C>,
identifier: Identifier<C>,
) -> Result<<<C as Ciphersuite>::Group as Group>::Element, &'static str> {
let i = identifier.to_scalar()?;
let (_, result) = commitment.0.iter().fold(
(
<<C::Group as Group>::Field as Field>::one(),
<C::Group as Group>::identity(),
),
|(i_to_the_k, sum_so_far), comm_k| (i * i_to_the_k, sum_so_far + comm_k.0 * i_to_the_k),
);
Ok(result)
}
/// A FROST keypair, which can be generated either by a trusted dealer or using
/// a DKG.
///
@ -428,6 +460,50 @@ pub struct PublicKeyPackage<C: Ciphersuite> {
pub group_public: VerifyingKey<C>,
}
/// Generate a secret polynomial to use in secret sharing, for the given
/// secret value. Also validates the given parameters.
///
/// Returns the full vector of coefficients in little-endian order (including the
/// given secret, which is the first element) and a [`VerifiableSecretSharingCommitment`]
/// which contains commitments to those coefficients.
///
/// Returns an error if the parameters (num_signers, threshold) are inconsistent.
pub(crate) fn generate_secret_polynomial<C: Ciphersuite>(
secret: &SharedSecret<C>,
num_signers: u8,
threshold: u8,
mut coefficients: Vec<Scalar<C>>,
) -> Result<(Vec<Scalar<C>>, VerifiableSecretSharingCommitment<C>), &'static str> {
if threshold < 2 {
return Err("Threshold cannot be less than 2");
}
if num_signers < 2 {
return Err("Number of signers cannot be less than the minimum threshold 2");
}
if threshold > num_signers {
return Err("Threshold cannot exceed num_signers");
}
if coefficients.len() != threshold as usize - 1 {
return Err("Must pass threshold-1 coefficients");
}
// Prepend the secret, which is the 0th coefficient
coefficients.insert(0, secret.0);
// Create the vector of commitments
let commitment: Vec<_> = coefficients
.iter()
.map(|c| CoefficientCommitment(<C::Group as Group>::generator() * *c))
.collect();
let commitment: VerifiableSecretSharingCommitment<C> =
VerifiableSecretSharingCommitment(commitment);
Ok((coefficients, commitment))
}
/// Creates secret shares for a given secret using the given coefficients.
///
/// This function accepts a secret from which shares are generated,
@ -453,58 +529,13 @@ pub(crate) fn generate_secret_shares<C: Ciphersuite>(
threshold: u8,
coefficients: Vec<Scalar<C>>,
) -> Result<Vec<SecretShare<C>>, &'static str> {
if threshold < 2 {
return Err("Threshold cannot be less than 2");
}
if numshares < 2 {
return Err("Number of shares cannot be less than the minimum threshold 2");
}
if threshold > numshares {
return Err("Threshold cannot exceed numshares");
}
let numcoeffs = threshold - 1;
if coefficients.len() != numcoeffs as usize {
return Err("Must pass threshold-1 coefficients");
}
let mut secret_shares: Vec<SecretShare<C>> = Vec::with_capacity(numshares as usize);
let mut commitment: VerifiableSecretSharingCommitment<C> =
VerifiableSecretSharingCommitment(Vec::with_capacity(threshold as usize));
let (coefficients, commitment) =
generate_secret_polynomial(secret, numshares, threshold, coefficients)?;
// Verifiable secret sharing, to make sure that participants can ensure their
// secret is consistent with every other participant's.
commitment.0.push(CoefficientCommitment(
<C::Group as Group>::generator() * secret.0,
));
for c in &coefficients {
commitment
.0
.push(CoefficientCommitment(<C::Group as Group>::generator() * *c));
}
// Evaluate the polynomial with `secret` as the constant term
// and `coeffs` as the other coefficients at the point x=share_identifier,
// using Horner's method.
for id in (1..=numshares as u16).map_while(|i| Identifier::<C>::try_from(i).ok()) {
let mut value = <<C::Group as Group>::Field as Field>::zero();
let id_scalar = id.to_scalar()?;
// Polynomial evaluation, for this identifier
//
// We rely only on `Add` and `Mul` here so as to not require `AddAssign` and `MulAssign`
//
// Note that this is from the 'last' coefficient to the 'first'.
for i in (0..numcoeffs).rev() {
value = value + coefficients[i as usize];
value = id_scalar * value;
}
value = value + secret.0;
let value = evaluate_polynomial(id, &coefficients)?;
secret_shares.push(SecretShare {
identifier: id,

View File

@ -0,0 +1,364 @@
//! Distributed Key Generation functions and structures.
use std::{collections::HashMap, iter};
use rand_core::{CryptoRng, RngCore};
use crate::{
frost::Identifier, Challenge, Ciphersuite, Field, Group, Scalar, Signature, VerifyingKey,
};
use super::{
evaluate_polynomial, evaluate_vss, generate_coefficients, generate_secret_polynomial,
KeyPackage, PublicKeyPackage, SecretShare, SharedSecret, SigningShare,
VerifiableSecretSharingCommitment, VerifyingShare,
};
/// 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)]
pub struct Round1Package<C: Ciphersuite> {
/// The identifier of the participant who is sending the package (i).
pub sender_identifier: Identifier<C>,
/// The public commitment from the participant (C_i)
pub commitment: VerifiableSecretSharingCommitment<C>,
/// The proof of knowledge of the temporary secret (σ_i = (R_i, μ_i))
pub proof_of_knowledge: Signature<C>,
}
/// 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)]
pub struct Round1SecretPackage<C: Ciphersuite> {
/// The identifier of the participant holding the secret.
pub 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 coefficients: Vec<Scalar<C>>,
/// The public commitment for the participant (C_i)
pub commitment: VerifiableSecretSharingCommitment<C>,
/// The total number of signers.
pub num_signers: u8,
}
/// 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)]
pub struct Round2Package<C: Ciphersuite> {
/// The identifier of the participant that generated the package (i).
pub sender_identifier: Identifier<C>,
/// The identifier of the participant what will receive the package ().
pub receiver_identifier: Identifier<C>,
/// The secret share being sent.
pub secret_share: SigningShare<C>,
}
/// 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 struct Round2SecretPackage<C: Ciphersuite> {
/// The identifier of the participant holding the secret.
pub identifier: Identifier<C>,
/// The public commitment from the participant (C_i)
pub commitment: VerifiableSecretSharingCommitment<C>,
/// The participant's own secret share (f_i(i)).
pub secret_share: Scalar<C>,
/// The total number of signers.
pub num_signers: u8,
}
/// 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<C: Ciphersuite, R: RngCore + CryptoRng>(
identifier: Identifier<C>,
num_signers: u8,
threshold: u8,
mut rng: R,
) -> Result<(Round1SecretPackage<C>, Round1Package<C>), &'static str> {
let secret: SharedSecret<C> = SharedSecret::random(&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>(threshold as usize - 1, &mut rng);
let (coefficients, commitment) =
generate_secret_polynomial(&secret, num_signers, threshold, coefficients)?;
// 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 as Field>::random(&mut rng);
let R_i = <C::Group as Group>::generator() * k;
let c_i = challenge::<C>(identifier, &R_i, &commitment.0[0].0)
.ok_or("DKG not supported by ciphersuite")?;
let mu_i = k + coefficients[0] * c_i.0;
let secret_package = Round1SecretPackage {
identifier,
coefficients,
commitment: commitment.clone(),
num_signers,
};
let package = Round1Package {
sender_identifier: identifier,
commitment,
proof_of_knowledge: Signature { R: R_i, z: mu_i },
};
Ok((secret_package, package))
}
/// Generates the challenge for the proof of knowledge to a secret for the DKG.
fn challenge<C>(
identifier: Identifier<C>,
R: &<C::Group as Group>::Element,
verifying_key: &<C::Group as Group>::Element,
) -> Option<Challenge<C>>
where
C: Ciphersuite,
{
let mut preimage = vec![];
let i_scalar = identifier
.to_scalar()
.expect("this will never fail after identifier is defined as scalar");
preimage
.extend_from_slice(<<C::Group as Group>::Field as Field>::serialize(&i_scalar).as_ref());
preimage.extend_from_slice(<C::Group as Group>::serialize(R).as_ref());
preimage.extend_from_slice(<C::Group as Group>::serialize(verifying_key).as_ref());
Some(Challenge(C::HDKG(&preimage[..])?))
}
/// 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<C: Ciphersuite>(
secret_package: Round1SecretPackage<C>,
round1_packages: &[Round1Package<C>],
) -> Result<(Round2SecretPackage<C>, Vec<Round2Package<C>>), &'static str> {
if round1_packages.len() != (secret_package.num_signers - 1) as usize {
return Err("incorrect number of packages");
}
let mut round2_packages = Vec::new();
for round1_package in round1_packages {
let ell = round1_package.sender_identifier;
// 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 R_ell = round1_package.proof_of_knowledge.R;
let mu_ell = round1_package.proof_of_knowledge.z;
let phi_ell0 = round1_package.commitment.0[0].0;
let c_ell =
challenge::<C>(ell, &R_ell, &phi_ell0).ok_or("DKG not supported by ciphersuite")?;
if R_ell != <C::Group as Group>::generator() * mu_ell - phi_ell0 * c_ell.0 {
return Err("Invalid 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 value = evaluate_polynomial(ell, &secret_package.coefficients)?;
round2_packages.push(Round2Package {
sender_identifier: secret_package.identifier,
receiver_identifier: ell,
secret_share: SigningShare(value),
});
}
let fii = evaluate_polynomial(secret_package.identifier, &secret_package.coefficients)?;
Ok((
Round2SecretPackage {
identifier: secret_package.identifier,
commitment: secret_package.commitment,
secret_share: fii,
num_signers: secret_package.num_signers,
},
round2_packages,
))
}
/// Computes the verifying keys of the other participants for the third step
/// of the DKG protocol.
fn compute_verifying_keys<C: Ciphersuite>(
round2_packages: &[Round2Package<C>],
round1_packages_map: HashMap<Identifier<C>, &Round1Package<C>>,
round2_secret_package: &Round2SecretPackage<C>,
) -> Result<HashMap<Identifier<C>, VerifyingShare<C>>, &'static str> {
// Round 2, Step 4
//
// > Any participant can compute the public verification share of any other participant
// > by calculating Y_i = ∏_{j=1}^n ∏_{k=0}^{t1} φ_{jk}^{i^k mod q}.
let mut others_verifying_keys = HashMap::new();
// Note that in this loop, "i" refers to the other participant whose public verification share
// we are computing, and not the current participant.
for i in round2_packages.iter().map(|p| p.sender_identifier) {
let mut y_i = <C::Group as Group>::identity();
// We need to iterate through all commitment vectors, including our own,
// so chain it manually
for commitments in round2_packages
.iter()
.map(|p| {
// Get the commitment vector for this participant
Ok::<&VerifiableSecretSharingCommitment<C>, &'static str>(
&round1_packages_map
.get(&p.sender_identifier)
.ok_or("Round 1 package not found for Round 2 participant")?
.commitment,
)
})
// Chain our own commitment vector
.chain(iter::once(Ok(&round2_secret_package.commitment)))
{
let result = evaluate_vss(commitments?, i)?;
y_i = y_i + result;
}
let y_i = VerifyingShare(y_i);
others_verifying_keys.insert(i, y_i);
}
Ok(others_verifying_keys)
}
/// 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 other participants; both of which are required to compute FROST
/// signatures.
pub fn keygen_part3<C: Ciphersuite>(
round2_secret_package: &Round2SecretPackage<C>,
round1_packages: &[Round1Package<C>],
round2_packages: &[Round2Package<C>],
) -> Result<(KeyPackage<C>, PublicKeyPackage<C>), &'static str> {
if round1_packages.len() != (round2_secret_package.num_signers - 1) as usize {
return Err("incorrect number of packages");
}
if round1_packages.len() != round2_packages.len() {
return Err("inconsistent number of packages");
}
let mut signing_share: Scalar<C> = <<C::Group as Group>::Field as Field>::zero();
let mut group_public: <C::Group as Group>::Element = <C::Group as Group>::identity();
let round1_packages_map: HashMap<Identifier<C>, &Round1Package<C>> = round1_packages
.iter()
.map(|package| (package.sender_identifier, package))
.collect();
for round2_package in round2_packages {
// Sanity check; was the package really meant to us?
if round2_package.receiver_identifier != round2_secret_package.identifier {
return Err("Round 2 package receiver is not the current participant");
}
// 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 = round2_package.sender_identifier;
let f_ell_i = round2_package.secret_share;
let commitment = &round1_packages_map
.get(&ell)
.ok_or("commitment package missing")?
.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 {
identifier: round2_secret_package.identifier,
value: 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;
// Round 2, Step 4
//
// > Each P_i calculates [...] the groups public key Y = ∏^n_{j=1} φ_{j0}.
group_public = group_public + commitment.0[0].0;
}
signing_share = signing_share + round2_secret_package.secret_share;
group_public = group_public + round2_secret_package.commitment.0[0].0;
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_key = signing_share.into();
let group_public = VerifyingKey {
element: group_public,
};
// Round 2, Step 4
//
// > Any participant can compute the public verification share of any other participant
// > by calculating Y_i = ∏_{j=1}^n ∏_{k=0}^{t1} φ_{jk}^{i^k mod q}.
let others_verifying_keys =
compute_verifying_keys(round2_packages, round1_packages_map, round2_secret_package)?;
let key_package = KeyPackage {
identifier: round2_secret_package.identifier,
secret_share: signing_share,
public: verifying_key,
group_public,
};
let public_key_package = PublicKeyPackage {
signer_pubkeys: others_verifying_keys,
group_public,
};
Ok((key_package, public_key_package))
}

View File

@ -204,6 +204,17 @@ pub trait Ciphersuite: Copy + Clone + PartialEq {
/// [H5]: https://github.com/cfrg/draft-irtf-cfrg-frost/blob/master/draft-irtf-cfrg-frost.md#cryptographic-hash
fn H5(m: &[u8]) -> Self::HashOutput;
/// Hash function for a FROST ciphersuite, used for the DKG.
///
/// The DKG it not part of the specification, thus this is optional.
/// It can return None if DKG is not supported by the Ciphersuite. This is
/// the default implementation.
///
/// Maps arbitrary inputs to non-zero `Self::Scalar` elements of the prime-order group scalar field.
fn HDKG(_m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
None
}
/// Verify a signature for this ciphersuite. The default implementation uses the "cofactored"
/// equation (it multiplies by the cofactor returned by [`Group::cofactor()`]).
///

View File

@ -17,7 +17,7 @@ pub fn check_share_generation<C: Ciphersuite, R: RngCore + CryptoRng>(mut rng: R
let numshares = 5;
let threshold = 3;
let coefficients = generate_coefficients::<C, R>(threshold as usize - 1, rng);
let coefficients = generate_coefficients::<C, R>(threshold as usize - 1, &mut rng);
let secret_shares =
frost::keys::generate_secret_shares(&secret, numshares, threshold, coefficients).unwrap();
@ -54,6 +54,15 @@ pub fn check_sign_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>(mut rng: R
})
.collect();
check_sign(threshold, key_packages, rng, pubkeys);
}
fn check_sign<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(
threshold: u8,
key_packages: HashMap<Identifier<C>, frost::keys::KeyPackage<C>>,
mut rng: R,
pubkeys: frost::keys::PublicKeyPackage<C>,
) {
let mut nonces: HashMap<Identifier<C>, frost::round1::SigningNonces<C>> = HashMap::new();
let mut commitments: HashMap<Identifier<C>, frost::round1::SigningCommitments<C>> =
HashMap::new();
@ -131,3 +140,178 @@ pub fn check_sign_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>(mut rng: R
.is_ok());
}
}
/// Test FROST signing with trusted dealer with a Ciphersuite.
pub fn check_sign_with_dkg<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(mut rng: R)
where
<C as Ciphersuite>::Group: std::cmp::PartialEq,
{
////////////////////////////////////////////////////////////////////////////
// Key generation, Round 1
////////////////////////////////////////////////////////////////////////////
let numsigners = 5;
let threshold = 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<
frost::Identifier<C>,
frost::keys::dkg::Round1SecretPackage<C>,
> = 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<
frost::Identifier<C>,
Vec<frost::keys::dkg::Round1Package<C>>,
> = 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..=numsigners {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (secret_package, round1_package) = frost::keys::dkg::keygen_part1(
participant_identifier,
numsigners as u8,
threshold,
&mut rng,
)
.unwrap();
// 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..=numsigners {
if receiver_participant_index == participant_index {
continue;
}
let receiver_participant_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..=numsigners {
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
.get(&participant_identifier)
.unwrap(),
)
.expect("should work");
// 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();
// Map of the verifying key of each participant.
// Used by the signing test that follows.
let mut verifying_keys = HashMap::new();
// The group public key, used by the signing test that follows.
let mut group_public = None;
// For each participant, store the set of verifying keys of the other
// participants. This is used to check if the set is correct for all
// participants.
// 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.
// The verifying keys are used to verify the signature shares produced
// for each signature before being aggregated.
let mut others_verifying_keys_by_participant = 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..=numsigners {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let (key_package, others_verifying_keys) = frost::keys::dkg::keygen_part3(
&round2_secret_packages[&participant_identifier],
&received_round1_packages[&participant_identifier],
&received_round2_packages[&participant_identifier],
)
.unwrap();
verifying_keys.insert(participant_identifier, key_package.public);
// Test if all group_public are equal
if let Some(previous_group_public) = group_public {
assert_eq!(previous_group_public, key_package.group_public)
}
group_public = Some(key_package.group_public);
key_packages.insert(participant_identifier, key_package);
others_verifying_keys_by_participant.insert(participant_identifier, others_verifying_keys);
}
// Test if the set of verifying keys is correct for all participants.
for participant_index in 1..=numsigners {
let participant_identifier = participant_index.try_into().expect("should be nonzero");
let public_key_package = others_verifying_keys_by_participant
.get(&participant_identifier)
.unwrap();
for (identifier, verifying_key) in &public_key_package.signer_pubkeys {
assert_eq!(
verifying_keys.get(identifier).unwrap(),
verifying_key,
"the verifying key that participant {:?} computed for participant {:?} is not correct",
participant_identifier,
identifier
);
}
}
let pubkeys = frost::keys::PublicKeyPackage {
signer_pubkeys: verifying_keys,
group_public: group_public.unwrap(),
};
// Proceed with the signing test.
check_sign(threshold, key_packages, rng, pubkeys);
}

View File

@ -220,6 +220,15 @@ impl Ciphersuite for P256Sha256 {
output.copy_from_slice(h.finalize().as_slice());
output
}
/// HDKG for FROST(P-256, SHA-256)
fn HDKG(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
let mut u = [P256ScalarField::zero()];
let dst = CONTEXT_STRING.to_owned() + "dkg";
hash_to_field::<ExpandMsgXmd<Sha256>, Scalar>(&[m], dst.as_bytes(), &mut u)
.expect("should never return error according to error cases described in ExpandMsgXmd");
Some(u[0])
}
}
// Shorthand alias for the ciphersuite

View File

@ -9,6 +9,13 @@ fn check_sign_with_dealer() {
frost_core::tests::check_sign_with_dealer::<P256Sha256, _>(rng);
}
#[test]
fn check_sign_with_dkg() {
let rng = thread_rng();
frost_core::tests::check_sign_with_dkg::<P256Sha256, _>(rng);
}
// TODO: re-enable after batch is changed to work with big-endian scalars
// #[test]
#[allow(unused)]

View File

@ -198,6 +198,18 @@ impl Ciphersuite for Ristretto255Sha512 {
output.copy_from_slice(h.finalize().as_slice());
output
}
/// HDKG for FROST(ristretto255, SHA-512)
fn HDKG(m: &[u8]) -> Option<<<Self::Group as Group>::Field as Field>::Scalar> {
let h = Sha512::new()
.chain(CONTEXT_STRING.as_bytes())
.chain("dkg")
.chain(m);
let mut output = [0u8; 64];
output.copy_from_slice(h.finalize().as_slice());
Some(<<Self::Group as Group>::Field as Field>::Scalar::from_bytes_mod_order_wide(&output))
}
}
type R = Ristretto255Sha512;

View File

@ -10,6 +10,13 @@ fn check_sign_with_dealer() {
frost_core::tests::check_sign_with_dealer::<Ristretto255Sha512, _>(rng);
}
#[test]
fn check_sign_with_dkg() {
let rng = thread_rng();
frost_core::tests::check_sign_with_dkg::<Ristretto255Sha512, _>(rng);
}
#[test]
fn check_batch_verify() {
let rng = thread_rng();