Skip to content

Commit

Permalink
add computing PublicKeyPackage from commitments (#551)
Browse files Browse the repository at this point in the history
* Refactor computing the public key package and expose it.

* Fix clippy issue.

* Add test.

* Fix test.

* Improve test.

* Split it up.

* Fix build.

* Improve api.

* Expose more stuff.

* Expose more stuff.

* Extract proof of knowledge verification.

* Add construct_proof_of_knowledge.

* fix dkg test

* overall cleanup

* change IncorrectCommitment to IncorrectNumberOfCommitments

* clippy fixes

* fix order of inputs for DKG commitment hashing

* also change regular challenge() verifying key argument to VerifyingKey

---------

Co-authored-by: David Craven <[email protected]>
  • Loading branch information
conradoplg and dvc94ch authored Oct 27, 2023
1 parent e0db615 commit 71c0925
Show file tree
Hide file tree
Showing 15 changed files with 267 additions and 122 deletions.
3 changes: 3 additions & 0 deletions frost-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ Entries are listed in reverse chronological order.
string was also changed for all ciphersuites: it is now equal to the
`contextString` of each ciphersuite per the FROST spec.
* An option to disable cheater detection during aggregation of signatures has been added.
* Added `PublicKeyPackage::from_commitment()` and
`PublicKeyPackage::from_dkg_commitments` to create a `PublicKeyPackage` from
the commitments generated in trusted dealer or distributed key generation.

## Released

Expand Down
2 changes: 1 addition & 1 deletion frost-core/src/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ where
{
fn from((vk, sig, msg): (VerifyingKey<C>, Signature<C>, &'msg M)) -> Self {
// Compute c now to avoid dependency on the msg lifetime.
let c = crate::challenge(&sig.R, &vk.element, msg.as_ref());
let c = crate::challenge(&sig.R, &vk, msg.as_ref());

Self { vk, sig, c }
}
Expand Down
2 changes: 1 addition & 1 deletion frost-core/src/frost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ where
// Compute the per-message challenge.
let challenge = crate::challenge::<C>(
&group_commitment.0,
&pubkeys.verifying_key.element,
&pubkeys.verifying_key,
signing_package.message().as_slice(),
);

Expand Down
120 changes: 107 additions & 13 deletions frost-core/src/frost/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,35 @@ use super::compute_lagrange_coefficient;
pub mod dkg;
pub mod repairable;

/// Sum the commitments from all participants in a distributed key generation
/// run into a single group commitment.
#[cfg_attr(feature = "internals", visibility::make(pub))]
pub(crate) fn sum_commitments<C: Ciphersuite>(
commitments: &[&VerifiableSecretSharingCommitment<C>],
) -> Result<VerifiableSecretSharingCommitment<C>, Error<C>> {
let mut group_commitment = vec![
CoefficientCommitment(<C::Group>::identity());
commitments
.get(0)
.ok_or(Error::IncorrectNumberOfCommitments)?
.0
.len()
];
for commitment in commitments {
for (i, c) in group_commitment.iter_mut().enumerate() {
*c = CoefficientCommitment(
c.value()
+ commitment
.0
.get(i)
.ok_or(Error::IncorrectNumberOfCommitments)?
.value(),
);
}
}
Ok(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 @@ -40,6 +69,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 @@ -83,6 +113,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 @@ -181,6 +217,26 @@ 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(
identifier: Identifier<C>,
commitment: &VerifiableSecretSharingCommitment<C>,
) -> VerifyingShare<C> {
// DKG 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}.
//
// Rewriting the equation by moving the product over j to further inside
// the equation:
// Y_i = ∏_{k=0}^{t−1} (∏_{j=1}^n φ_{jk})^{i^k mod q}
// i.e. we can operate on the sum of all φ_j commitments, which is
// what is passed to the functions.
VerifyingShare(evaluate_vss(identifier, commitment))
}
}

impl<C> Debug for VerifyingShare<C>
Expand Down Expand Up @@ -334,10 +390,18 @@ where
Ok(Self(coefficient_commitments))
}

/// Get the first commitment (which is equivalent to the VerifyingKey),
/// or an error if the vector is empty.
pub(crate) fn first(&self) -> Result<CoefficientCommitment<C>, Error<C>> {
self.0.get(0).ok_or(Error::MissingCommitment).copied()
/// Get the VerifyingKey matching this commitment vector (which is the first
/// element in the vector), or an error if the vector is empty.
pub(crate) fn verifying_key(&self) -> Result<VerifyingKey<C>, Error<C>> {
Ok(VerifyingKey::new(
self.0.get(0).ok_or(Error::MissingCommitment)?.0,
))
}

/// Returns the coefficient commitments.
#[cfg_attr(feature = "internals", visibility::make(pub))]
pub(crate) fn coefficients(&self) -> &[CoefficientCommitment<C>] {
&self.0
}
}

Expand Down Expand Up @@ -404,17 +468,13 @@ where
/// [spec]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#appendix-C.2-4
pub fn verify(&self) -> Result<(VerifyingShare<C>, VerifyingKey<C>), Error<C>> {
let f_result = <C::Group>::generator() * self.signing_share.0;
let result = evaluate_vss(&self.commitment, self.identifier);
let result = evaluate_vss(self.identifier, &self.commitment);

if !(f_result == result) {
return Err(Error::InvalidSecretShare);
}

let verifying_key = VerifyingKey {
element: self.commitment.first()?.0,
};

Ok((VerifyingShare(result), verifying_key))
Ok((VerifyingShare(result), self.commitment.verifying_key()?))
}
}

Expand Down Expand Up @@ -547,11 +607,13 @@ fn evaluate_polynomial<C: Ciphersuite>(
}

/// 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 φ_ℓ
/// ∏^{t−1}_{k=0} φ^{i^k mod q}_{ℓk} (multiplicative notation) using
/// `identifier` as `i` and the `commitment` as the commitment vector φ_ℓ.
///
/// This is also used in Round 2, Step 4 of the DKG.
fn evaluate_vss<C: Ciphersuite>(
commitment: &VerifiableSecretSharingCommitment<C>,
identifier: Identifier<C>,
commitment: &VerifiableSecretSharingCommitment<C>,
) -> Element<C> {
let i = identifier;

Expand Down Expand Up @@ -691,6 +753,38 @@ where
verifying_key,
}
}

/// Computes the public key package given a list of participant identifiers
/// and a [`VerifiableSecretSharingCommitment`]. This is useful in scenarios
/// where the commitments are published somewhere and it's desirable to
/// recreate the public key package from them.
pub fn from_commitment(
identifiers: &BTreeSet<Identifier<C>>,
commitment: &VerifiableSecretSharingCommitment<C>,
) -> Result<PublicKeyPackage<C>, Error<C>> {
let verifying_keys: BTreeMap<_, _> = identifiers
.iter()
.map(|id| (*id, VerifyingShare::from_commitment(*id, commitment)))
.collect();
Ok(PublicKeyPackage::new(
verifying_keys,
VerifyingKey::from_commitment(commitment)?,
))
}

/// Computes the public key package given a map of participant identifiers
/// and their [`VerifiableSecretSharingCommitment`] from a distributed key
/// generation process. This is useful in scenarios where the commitments
/// are published somewhere and it's desirable to recreate the public key
/// package from them.
pub fn from_dkg_commitments(
commitments: &BTreeMap<Identifier<C>, &VerifiableSecretSharingCommitment<C>>,
) -> Result<PublicKeyPackage<C>, Error<C>> {
let identifiers: BTreeSet<_> = commitments.keys().copied().collect();
let commitments: Vec<_> = commitments.values().copied().collect();
let group_commitment = sum_commitments(&commitments)?;
Self::from_commitment(&identifiers, &group_commitment)
}
}

#[cfg(feature = "serialization")]
Expand Down
Loading

0 comments on commit 71c0925

Please sign in to comment.