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:
parent
2b3b2344e8
commit
bceafae2e6
|
@ -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
|
||||
/// ∏^{t−1}_{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,
|
||||
|
|
|
@ -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(t−1)})) 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(t−1)}) ← Z_q
|
||||
//
|
||||
// Round 1, Step 3
|
||||
//
|
||||
// > Every participant P_i computes a public commitment
|
||||
// > C⃗_i = 〈φ_{i0}, ..., φ_{i(t−1)}〉, 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}^{t−1} φ_{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)} ≟ ∏^{t−1}_{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 group’s 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}^{t−1} φ_{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))
|
||||
}
|
|
@ -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()`]).
|
||||
///
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue