Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor computing the public key package and expose it. #502

Closed
wants to merge 12 commits into from
50 changes: 50 additions & 0 deletions frost-core/src/frost/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@ use super::compute_lagrange_coefficient;
pub mod dkg;
pub mod repairable;

/// Sum the commitments from all peers into a group commitment.
#[cfg_attr(feature = "internals", visibility::make(pub))]
pub(crate) fn compute_group_commitment<C: Ciphersuite>(
commitments: &[VerifiableSecretSharingCommitment<C>],
) -> VerifiableSecretSharingCommitment<C> {
let mut group_commitment =
vec![CoefficientCommitment(<C::Group>::identity()); commitments[0].0.len()];
for commitment in commitments {
for i in 0..group_commitment.len() {
group_commitment[i] =
CoefficientCommitment(group_commitment[i].value() + commitment.0[i].value());
}
}
VerifiableSecretSharingCommitment(group_commitment)
}

/// Return a vector of randomly generated polynomial coefficients ([`Scalar`]s).
pub(crate) fn generate_coefficients<C: Ciphersuite, R: RngCore + CryptoRng>(
size: usize,
Expand All @@ -39,6 +55,7 @@ pub(crate) fn generate_coefficients<C: Ciphersuite, R: RngCore + CryptoRng>(
}

/// Return a list of default identifiers (1 to max_signers, inclusive).
#[cfg_attr(feature = "internals", visibility::make(pub))]
pub(crate) fn default_identifiers<C: Ciphersuite>(max_signers: u16) -> Vec<Identifier<C>> {
(1..=max_signers)
.map(|i| Identifier::<C>::try_from(i).expect("nonzero"))
Expand Down Expand Up @@ -81,6 +98,12 @@ where
pub fn serialize(&self) -> <<C::Group as Group>::Field as Field>::Serialization {
<<C::Group as Group>::Field>::serialize(&self.0)
}

/// Computes the signing share from a list of coefficients.
#[cfg_attr(feature = "internals", visibility::make(pub))]
pub(crate) fn from_coefficients(coefficients: &[Scalar<C>], peer: Identifier<C>) -> Self {
Self(evaluate_polynomial(peer, coefficients))
}
}

impl<C> Debug for SigningShare<C>
Expand Down Expand Up @@ -178,6 +201,15 @@ where
pub fn serialize(&self) -> <C::Group as Group>::Serialization {
<C::Group as Group>::serialize(&self.0)
}

/// Computes a verifying share for a peer given the group commitment.
#[cfg_attr(feature = "internals", visibility::make(pub))]
pub(crate) fn from_commitment(
commitment: &VerifiableSecretSharingCommitment<C>,
peer: Identifier<C>,
) -> VerifyingShare<C> {
VerifyingShare(evaluate_vss(commitment, peer))
}
}

impl<C> Debug for VerifyingShare<C>
Expand Down Expand Up @@ -334,6 +366,11 @@ where
pub(crate) fn first(&self) -> Result<CoefficientCommitment<C>, Error<C>> {
self.0.get(0).ok_or(Error::MissingCommitment).copied()
}

/// Returns the coefficient commitments.
pub fn coefficients(&self) -> &[CoefficientCommitment<C>] {
&self.0
}
}

/// A secret share generated by performing a (t-out-of-n) secret sharing scheme,
Expand Down Expand Up @@ -671,6 +708,19 @@ where
ciphersuite: (),
}
}

/// Computes the public key package given a list of commitments.
#[cfg_attr(feature = "internals", visibility::make(pub))]
pub(crate) fn from_commitment(
members: &BTreeSet<Identifier<C>>,
commitment: &VerifiableSecretSharingCommitment<C>,
) -> PublicKeyPackage<C> {
let mut verifying_keys = HashMap::new();
for peer in members {
verifying_keys.insert(*peer, VerifyingShare::from_commitment(commitment, *peer));
}
PublicKeyPackage::new(verifying_keys, VerifyingKey::from_commitment(commitment))
}
}

fn validate_num_of_signers<C: Ciphersuite>(
Expand Down
193 changes: 86 additions & 107 deletions frost-core/src/frost/keys/dkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ use rand_core::{CryptoRng, RngCore};

use crate::{
frost::Identifier, Challenge, Ciphersuite, Element, Error, Field, Group, Scalar, Signature,
SigningKey, VerifyingKey,
SigningKey,
};

use super::{
evaluate_polynomial, evaluate_vss, generate_coefficients, generate_secret_polynomial,
validate_num_of_signers, KeyPackage, PublicKeyPackage, SecretShare, SigningShare,
VerifiableSecretSharingCommitment, VerifyingShare,
compute_group_commitment, evaluate_polynomial, generate_coefficients,
generate_secret_polynomial, validate_num_of_signers, KeyPackage, PublicKeyPackage, SecretShare,
SigningShare, VerifiableSecretSharingCommitment,
};

/// DKG Round 1 structures.
Expand Down Expand Up @@ -111,6 +111,17 @@ pub mod round1 {
pub(crate) max_signers: u16,
}

impl<C> SecretPackage<C>
where
C: Ciphersuite,
{
#[cfg(feature = "internals")]
/// Returns the secret coefficients.
pub fn coefficients(&self) -> &[Scalar<C>] {
&self.coefficients
}
}

impl<C> std::fmt::Debug for SecretPackage<C>
where
C: Ciphersuite,
Expand Down Expand Up @@ -252,22 +263,8 @@ pub fn part1<C: Ciphersuite, R: RngCore + CryptoRng>(
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)?;

// 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, &R_i, &commitment.first()?.0).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;
let proof_of_knowledge =
construct_proof_of_knowledge(identifier, &coefficients, &commitment, &mut rng)?;

let secret_package = round1::SecretPackage {
identifier,
Expand All @@ -277,7 +274,7 @@ pub fn part1<C: Ciphersuite, R: RngCore + CryptoRng>(
};
let package = round1::Package {
commitment,
proof_of_knowledge: Signature { R: R_i, z: mu_i },
proof_of_knowledge,
ciphersuite: (),
};

Expand All @@ -302,6 +299,56 @@ where
Some(Challenge(C::HDKG(&preimage[..])?))
}

/// Constructs 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 construct_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, &R_i, &commitment.first()?.0).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.first()?.0;
let c_ell = challenge::<C>(ell, &R_ell, &phi_ell0).ok_or(Error::DKGNotSupported)?;
if R_ell != <C::Group>::generator() * mu_ell - phi_ell0 * 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.
Expand Down Expand Up @@ -333,19 +380,11 @@ pub fn part2<C: Ciphersuite>(

for (sender_identifier, round1_package) in round1_packages {
let ell = *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.first()?.0;
let c_ell = challenge::<C>(ell, &R_ell, &phi_ell0).ok_or(Error::DKGNotSupported)?;

if R_ell != <C::Group>::generator() * mu_ell - phi_ell0 * c_ell.0 {
return Err(Error::InvalidProofOfKnowledge { culprit: ell });
}
verify_proof_of_knowledge(
ell,
&round1_package.commitment,
round1_package.proof_of_knowledge,
)?;

// Round 2, Step 1
//
Expand Down Expand Up @@ -374,47 +413,6 @@ pub fn part2<C: Ciphersuite>(
))
}

/// Computes the verifying keys of the other participants for the third step
/// of the DKG protocol.
fn compute_verifying_keys<C: Ciphersuite>(
round1_packages: &HashMap<Identifier<C>, round1::Package<C>>,
round2_secret_package: &round2::SecretPackage<C>,
) -> Result<HashMap<Identifier<C>, VerifyingShare<C>>, Error<C>> {
// 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 round1_packages.keys().cloned() {
let mut y_i = <C::Group>::identity();

// We need to iterate through all commitment vectors, including our own,
// so chain it manually
for commitment in round1_packages
.keys()
.map(|k| {
// Get the commitment vector for this participant
Ok::<&VerifiableSecretSharingCommitment<C>, Error<C>>(
&round1_packages
.get(k)
.ok_or(Error::PackageNotFound)?
.commitment,
)
})
// Chain our own commitment vector
.chain(iter::once(Ok(&round2_secret_package.commitment)))
{
y_i = y_i + evaluate_vss(commitment?, i);
}
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 [`round2::SecretPackage`],
/// given the received [`round1::Package`]s and [`round2::Package`]s received from
Expand Down Expand Up @@ -451,7 +449,6 @@ pub fn part3<C: Ciphersuite>(
}

let mut signing_share = <<C::Group as Group>::Field>::zero();
let mut group_public = <C::Group>::identity();

for (sender_identifier, round2_package) in round2_packages {
// Round 2, Step 2
Expand Down Expand Up @@ -485,44 +482,26 @@ pub fn part3<C: Ciphersuite>(
// > 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.first()?.0;
}

signing_share = signing_share + round2_secret_package.secret_share;
group_public = group_public + round2_secret_package.commitment.first()?.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 mut all_verifying_keys = compute_verifying_keys(round1_packages, round2_secret_package)?;

// Add the participant's own public verification share for consistency
all_verifying_keys.insert(round2_secret_package.identifier, verifying_key);

let members = round1_packages.keys().copied().collect();
let commitments: Vec<_> = round1_packages
.values()
.map(|package| package.commitment.clone())
.chain(iter::once(round2_secret_package.commitment.clone()))
.collect();
let group_commitment = compute_group_commitment(&commitments);
let public_key_package = PublicKeyPackage::from_commitment(&members, &group_commitment);
let key_package = KeyPackage {
identifier: round2_secret_package.identifier,
secret_share: signing_share,
public: verifying_key,
group_public,
ciphersuite: (),
};
let public_key_package = PublicKeyPackage {
signer_pubkeys: all_verifying_keys,
group_public,
public: *public_key_package
.signer_pubkeys
.get(&round2_secret_package.identifier)
.expect("round2_secret_package.commitment is in the hashmap"),
group_public: public_key_package.group_public,
ciphersuite: (),
};

Expand Down
2 changes: 1 addition & 1 deletion frost-core/src/tests/repairable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
self, compute_lagrange_coefficient,
keys::{
repairable::{repair_share_step_1, repair_share_step_2, repair_share_step_3},
PublicKeyPackage, SecretShare, SigningShare,
KeyPackage, PublicKeyPackage, SecretShare, SigningShare,
},
Identifier,
},
Expand Down
Loading