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

Refresh Shares with DKG #766

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
269 changes: 265 additions & 4 deletions frost-core/src/keys/refresh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ use alloc::collections::BTreeMap;
use alloc::vec::Vec;

use crate::{
keys::dkg::{compute_proof_of_knowledge, round1, round2},
keys::{
generate_coefficients, generate_secret_shares, validate_num_of_signers,
CoefficientCommitment, PublicKeyPackage, SigningKey, SigningShare, VerifyingShare,
evaluate_polynomial, generate_coefficients, generate_secret_polynomial,
generate_secret_shares, validate_num_of_signers, CoefficientCommitment, PublicKeyPackage,
SigningKey, SigningShare, VerifyingShare,
},
Ciphersuite, CryptoRng, Error, Field, Group, Identifier, RngCore,
Ciphersuite, CryptoRng, Error, Field, Group, Header, Identifier, RngCore,
};

use super::{KeyPackage, SecretShare, VerifiableSecretSharingCommitment};
use core::iter;

use super::{dkg::round1::Package, KeyPackage, SecretShare, VerifiableSecretSharingCommitment};

/// Generates new zero key shares and a public key package using a trusted
/// dealer Building a new public key package is done by taking the verifying
Expand Down Expand Up @@ -114,3 +118,260 @@ pub fn refresh_share<C: Ciphersuite>(

Ok(new_key_package)
}

/// Part 1 of refresh share with DKG. A refreshing_key is generated and a new package and secret_package are generated.
/// The identity commitment is removed from the packages.
Copy link
Contributor

@conradoplg conradoplg Nov 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: This line about the identity commitment is an implementation detail, I don't think it needs to be part of the public documentation

pub fn refresh_dkg_part_1<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)?;

// Build refreshing shares
let refreshing_key = SigningKey {
scalar: <<C::Group as Group>::Field>::zero(),
};

// Round 1, Step 1
let coefficients = generate_coefficients::<C, R>(min_signers as usize - 1, &mut rng);

let (coefficients, commitment) =
generate_secret_polynomial(&refreshing_key, max_signers, min_signers, coefficients)?;

// Remove identity element from coefficients
let mut coeff_comms = commitment.0;
coeff_comms.remove(0);
let commitment = VerifiableSecretSharingCommitment::new(coeff_comms.clone());

let proof_of_knowledge =
compute_proof_of_knowledge(identifier, &coefficients, &commitment, &mut rng)?;
Comment on lines +148 to +149
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This builds a kind of "nonsensical" proof of knowledge since it uses the first coefficient (zero) and the commitment to the second coefficient (now first in the list). Which is not an issue, since we simply don't verify it. But it may be confusing to whoever is reading the source.

My suggestion is to directly build a dummy Signature using Signature::new() and passing a generator element (generator()) and a 1 scalar (one()), and adding a comment saying that we are building a dummy proof of knowledge because the secret is always zero and the proof isn't needed (and couldn't be encoded since it would have an identity element)


let secret_package = round1::SecretPackage {
identifier,
coefficients: coefficients.clone(),
commitment: commitment.clone(),
min_signers,
max_signers,
};
let package = round1::Package {
header: Header::default(),
commitment,
proof_of_knowledge,
};

Ok((secret_package, package))
}

/// Part 2 of refresh share with DKG. The identity commitment needs to be added back into the secret package.
pub fn refresh_dkg_part2<C: Ciphersuite>(
mut 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);
}

// The identity commitment needs to be added to the VSS commitment for secret package
let identity_commitment: Vec<CoefficientCommitment<C>> =
vec![CoefficientCommitment::new(C::Group::identity())];

let refreshing_secret_share_commitments: Vec<CoefficientCommitment<C>> = identity_commitment
.into_iter()
.chain(secret_package.commitment.0.clone())
.collect();

secret_package.commitment =
VerifiableSecretSharingCommitment::<C>::new(refreshing_secret_share_commitments);

let mut round2_packages = BTreeMap::new();

for (sender_identifier, round1_package) in round1_packages {
// The identity commitment needs to be added to the VSS commitment for every round 1 package
let identity_commitment: Vec<CoefficientCommitment<C>> =
vec![CoefficientCommitment::new(C::Group::identity())];

let refreshing_share_commitments: Vec<CoefficientCommitment<C>> = identity_commitment
.into_iter()
.chain(round1_package.commitment.0.clone())
.collect();

if refreshing_share_commitments.clone().len() != secret_package.min_signers as usize {
return Err(Error::IncorrectNumberOfCommitments);
}

let ell = *sender_identifier;

// Round 1, Step 5
// We don't need to verify the 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,
))
}

/// This is the step that actually refreshes the shares. New public key packages
/// and key packages are created.
pub fn refresh_dkg_shares<C: Ciphersuite>(
round2_secret_package: &round2::SecretPackage<C>,
round1_packages: &BTreeMap<Identifier<C>, round1::Package<C>>,
round2_packages: &BTreeMap<Identifier<C>, round2::Package<C>>,
old_pub_key_package: PublicKeyPackage<C>,
old_key_package: KeyPackage<C>,
) -> Result<(KeyPackage<C>, PublicKeyPackage<C>), Error<C>> {
// Add identity commitment back into round1_packages
let mut new_round_1_packages = BTreeMap::new();
for (sender_identifier, round1_package) in round1_packages {
// The identity commitment needs to be added to the VSS commitment for every round 1 package
let identity_commitment: Vec<CoefficientCommitment<C>> =
vec![CoefficientCommitment::new(C::Group::identity())];

let refreshing_share_commitments: Vec<CoefficientCommitment<C>> = identity_commitment
.into_iter()
.chain(round1_package.commitment.0.clone())
.collect();

let new_commitments =
VerifiableSecretSharingCommitment::<C>::new(refreshing_share_commitments);

let new_round_1_package = Package {
header: round1_package.header,
commitment: new_commitments,
proof_of_knowledge: round1_package.proof_of_knowledge,
};

new_round_1_packages.insert(*sender_identifier, new_round_1_package);
}

if new_round_1_packages.len() != (round2_secret_package.max_signers - 1) as usize {
return Err(Error::IncorrectNumberOfPackages);
}
if new_round_1_packages.len() != round2_packages.len() {
return Err(Error::IncorrectNumberOfPackages);
}
if new_round_1_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)} ≟ ∏^{t−1}_{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 = &new_round_1_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.to_scalar();
}

signing_share = signing_share + round2_secret_package.secret_share;

// Build new signing share
let old_signing_share = old_key_package.signing_share.to_scalar();
signing_share = signing_share + old_signing_share;
let signing_share = SigningShare::new(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<_, _> = new_round_1_packages
.iter()
.map(|(id, package)| (*id, &package.commitment))
.chain(iter::once((
round2_secret_package.identifier,
&round2_secret_package.commitment,
)))
.collect();

let zero_shares_public_key_package = PublicKeyPackage::from_dkg_commitments(&commitments)?;

let mut new_verifying_shares = BTreeMap::new();

for (identifier, verifying_share) in zero_shares_public_key_package.verifying_shares {
let new_verifying_share = verifying_share.to_element()
+ old_pub_key_package
.verifying_shares
.get(&identifier)
.ok_or(Error::UnknownIdentifier)?
.to_element();
new_verifying_shares.insert(identifier, VerifyingShare::new(new_verifying_share));
}

let public_key_package = PublicKeyPackage {
header: old_pub_key_package.header,
verifying_shares: new_verifying_shares,
verifying_key: old_pub_key_package.verifying_key,
};

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))
}
2 changes: 1 addition & 1 deletion frost-core/src/tests/ciphersuite_generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ fn check_part3_errors<C: Ciphersuite>(
}

/// Check that calling dkg::part3() with distinct sets of participants fail.
fn check_part3_different_participants<C: Ciphersuite>(
pub fn check_part3_different_participants<C: Ciphersuite>(
max_signers: u16,
round2_secret_packages: BTreeMap<Identifier<C>, frost::keys::dkg::round2::SecretPackage<C>>,
received_round1_packages: BTreeMap<
Expand Down
Loading
Loading