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

merge queue: embarking main (60cc5b9) and #665 together #706

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Besides FROST itself, this repository also provides:
- Distributed key generation as specified in the original paper [FROST20](https://eprint.iacr.org/2020/852.pdf);
- Repairable Threshold Scheme (RTS) from ['A Survey and Refinement of Repairable Threshold Schemes'](https://eprint.iacr.org/2017/1155) which allows a participant to recover a lost share with the help of a threshold of other participants;
- Rerandomized FROST (paper under review).
- Refresh Share functionality using a Trusted Dealer. This can be used to refresh the shares of the participants or to remove a participant.

## Getting Started

Expand Down
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- [Trusted Dealer Key Generation](tutorial/trusted-dealer.md)
- [Signing](tutorial/signing.md)
- [Distributed Key Generation](tutorial/dkg.md)
- [Refreshing Shares](tutorial/refreshing-shares.md)
- [User Documentation](user.md)
- [Serialization Format](user/serialization.md)
- [FROST with Zcash](zcash.md)
Expand Down
33 changes: 33 additions & 0 deletions book/src/frost.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,39 @@ is still free to start the process with only 2 participants if they wish.
Signature verification is carried out as normal with single-party signatures,
along with the signed message and the group verifying key as inputs.

## Repairing

Repairing shares allow participants to help another participant recover their
share if they have lost it, or also issue a new share to a new participant
(while keeping the same threshold).

The repair share functionality requires a threshold of participants to work.
For example, in a 2-of-3 scenario, two participants can help the third recover
their share, or they could issue a new share to move to a 2-of-4 group.

The functionality works in such a way that each participant running the repair
share function is not able to obtain the share that is being recovered or
issued.

## Refreshing

Refreshing shares allow participants (or a subset of them) to update their
shares in a way that maintains the same group public key. Some applications are:

- Make it harder for attackers to compromise the shares. For example, in a
2-of-3 threshold scenario, if an attacker steals one participant's device and
all participants refresh their shares, the attacker will need to start over
and steal two shares instead of just one more.
- Remove a participant from the group. For example, in a 2-of-3 threshold
scenario, if two participants decide to remove the third they can both refresh
their shares and the third participant would no longer be able to participate
in signing sessions with the others. (They can also then use the repair share
functionality to issue a new share and move from 2-of-2 back to 2-of-3.)

```admonish note
This is also possible via Distributed Key Generation but this has not yet been
implemented.
```

## Ciphersuites

Expand Down
34 changes: 34 additions & 0 deletions book/src/tutorial/refreshing-shares.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Refreshing Shares using a Trusted Dealer

The diagram below shows the refresh share process. Dashed lines
represent data being sent through an [authenticated and confidential communication
channel](https://frost.zfnd.org/terminology.html#peer-to-peer-channel).

<!-- ![Diagram of Refreshing shares, illustrating what is explained in the text](refreshing.png) -->

The Trusted Dealer needs to first run `compute_refreshing_shares()` which
returns SecretShares (the "refreshing shares") and a PublicKeyPackage. Each
`SecretShare` must then be sent along with the `PublicKeyPackage` via an
[**authenticated** and **confidential** channel
](https://frost.zfnd.org/terminology.html#peer-to-peer-channel) for each
participant.

Each Participant then runs `refresh_share()` to generate a new `KeyPackage`
which will replace their old `KeyPackage`; they must also replace their old
`PublicKeyPackage` with the one sent by the Trusted Dealer.

```admonish danger
The refreshed `KeyPackage` contents must be stored securely and the original
`KeyPackage` should be deleted. For example:

- Make sure other users in the system can't read it;
- If possible, use the OS secure storage such that the package
contents can only be opened with the user's password or biometrics.
```

```admonish danger
Applications should first ensure that all participants who refreshed their
`KeyPackages` were actually able to do so successfully, before deleting their old
`KeyPackages`. How this is done is up to the application; it might require
sucessfully generating a signature with all of those participants.
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions frost-core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Entries are listed in reverse chronological order.

## Unreleased

* Added refresh share functionality for trusted dealer:
`frost_core::keys::refresh::{compute_refreshing_shares, refresh_share}`

## 2.0.0-rc.0

* Changed the `deserialize()` function of Elements and structs containing
Expand Down
1 change: 1 addition & 0 deletions frost-core/src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use super::compute_lagrange_coefficient;

pub mod dkg;
pub mod refresh;
pub mod repairable;

/// Sum the commitments from all participants in a distributed key generation
Expand Down Expand Up @@ -327,7 +328,7 @@
}

/// Returns VerifiableSecretSharingCommitment from a iterator of serialized
/// CoefficientCommitments (e.g. a Vec<Vec<u8>>).

Check warning on line 331 in frost-core/src/keys.rs

View workflow job for this annotation

GitHub Actions / Check Rust doc

unclosed HTML tag `u8`
pub fn deserialize<I, V>(serialized_coefficient_commitments: I) -> Result<Self, Error<C>>
where
I: IntoIterator<Item = V>,
Expand Down
116 changes: 116 additions & 0 deletions frost-core/src/keys/refresh.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//! Refresh Shares
//!
//! Implements the functionality to refresh a share. This requires the participation
//! of all the remaining signers. This can be done using a Trusted Dealer or
//! DKG (not yet implemented)

use alloc::collections::BTreeMap;
use alloc::vec::Vec;

use crate::{
keys::{
generate_coefficients, generate_secret_shares, validate_num_of_signers,
CoefficientCommitment, PublicKeyPackage, SigningKey, SigningShare, VerifyingShare,
},
Ciphersuite, CryptoRng, Error, Field, Group, Identifier, RngCore,
};

use super::{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
/// shares from the new public key package and adding them to the original
/// verifying shares
pub fn compute_refreshing_shares<C: Ciphersuite, R: RngCore + CryptoRng>(
pub_key_package: PublicKeyPackage<C>,
max_signers: u16,
min_signers: u16,
identifiers: &[Identifier<C>],
rng: &mut R,
) -> Result<(Vec<SecretShare<C>>, PublicKeyPackage<C>), Error<C>> {
// Validate inputs
if identifiers.len() != max_signers as usize {
return Err(Error::IncorrectNumberOfIdentifiers);
}
validate_num_of_signers(min_signers, max_signers)?;

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

let coefficients = generate_coefficients::<C, R>(min_signers as usize - 1, rng);
let refreshing_shares = generate_secret_shares(
&refreshing_key,
max_signers,
min_signers,
coefficients,
identifiers,
)?;

let mut refreshed_verifying_shares: BTreeMap<Identifier<C>, VerifyingShare<C>> =
BTreeMap::new();
let mut refreshing_shares_minus_identity: Vec<SecretShare<C>> = Vec::new();

for mut share in refreshing_shares {
let refreshing_verifying_share: VerifyingShare<C> = SigningShare::into(share.signing_share);

let verifying_share = pub_key_package.verifying_shares.get(&share.identifier);

match verifying_share {
Some(verifying_share) => {
let refreshed_verifying_share =
refreshing_verifying_share.to_element() + verifying_share.to_element();
refreshed_verifying_shares.insert(
share.identifier,
VerifyingShare::new(refreshed_verifying_share),
);
}
None => return Err(Error::UnknownIdentifier),
};

share.commitment.0.remove(0);
refreshing_shares_minus_identity.push(share);
}

let refreshed_pub_key_package = PublicKeyPackage::<C> {
header: pub_key_package.header,
verifying_shares: refreshed_verifying_shares,
verifying_key: pub_key_package.verifying_key,
};

Ok((refreshing_shares_minus_identity, refreshed_pub_key_package))
}

/// Each participant refreshes their shares This is done by taking the
/// `refreshing_share` received from the trusted dealer and adding it to the
/// original share
pub fn refresh_share<C: Ciphersuite>(
mut refreshing_share: SecretShare<C>,
current_key_package: &KeyPackage<C>,
) -> Result<KeyPackage<C>, Error<C>> {
// The identity commitment needs to be added to the VSS commitment
let identity_commitment: Vec<CoefficientCommitment<C>> =
vec![CoefficientCommitment::new(C::Group::identity())];

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

refreshing_share.commitment =
VerifiableSecretSharingCommitment::<C>::new(refreshing_share_commitments);

// Verify refreshing_share secret share
let refreshed_share_package = KeyPackage::<C>::try_from(refreshing_share)?;

let signing_share: SigningShare<C> = SigningShare::new(
refreshed_share_package.signing_share.to_scalar()
+ current_key_package.signing_share.to_scalar(),
);

let mut new_key_package = current_key_package.clone();
new_key_package.signing_share = signing_share;

Ok(new_key_package)
}
102 changes: 60 additions & 42 deletions frost-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ where
if signing_package.signing_commitments().len() != signature_shares.len() {
return Err(Error::UnknownIdentifier);
}

if !signing_package.signing_commitments().keys().all(|id| {
#[cfg(feature = "cheater-detection")]
return signature_shares.contains_key(id) && pubkeys.verifying_shares().contains_key(id);
Expand All @@ -586,7 +587,6 @@ where
// binding factor.
let binding_factor_list: BindingFactorList<C> =
compute_binding_factor_list(signing_package, &pubkeys.verifying_key, &[])?;

// Compute the group commitment from signing commitments produced in round one.
let group_commitment = compute_group_commitment(signing_package, &binding_factor_list)?;

Expand Down Expand Up @@ -616,52 +616,70 @@ where
// This approach is more efficient since we don't need to verify all shares
// if the aggregate signature is valid (which should be the common case).
#[cfg(feature = "cheater-detection")]
if let Err(err) = verification_result {
// Compute the per-message challenge.
let challenge = crate::challenge::<C>(
&group_commitment.0,
&pubkeys.verifying_key,
signing_package.message().as_slice(),
if verification_result.is_err() {
detect_cheater(
group_commitment,
pubkeys,
signing_package,
signature_shares,
&binding_factor_list,
)?;

// Verify the signature shares.
for (signature_share_identifier, signature_share) in signature_shares {
// Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_,
// and where s[i] is a secret share of the constant term of _f_, the secret polynomial.
let signer_pubkey = pubkeys
.verifying_shares
.get(signature_share_identifier)
.ok_or(Error::UnknownIdentifier)?;

// Compute Lagrange coefficient.
let lambda_i = derive_interpolating_value(signature_share_identifier, signing_package)?;

let binding_factor = binding_factor_list
.get(signature_share_identifier)
.ok_or(Error::UnknownIdentifier)?;

// Compute the commitment share.
let R_share = signing_package
.signing_commitment(signature_share_identifier)
.ok_or(Error::UnknownIdentifier)?
.to_group_commitment_share(binding_factor);

// Compute relation values to verify this signature share.
signature_share.verify(
*signature_share_identifier,
&R_share,
signer_pubkey,
lambda_i,
&challenge,
)?;
}

// We should never reach here; but we return the verification error to be safe.
return Err(err);
}

#[cfg(not(feature = "cheater-detection"))]
verification_result?;

Ok(signature)
}

/// Optional cheater detection feature
/// Each share is verified to find the cheater
fn detect_cheater<C: Ciphersuite>(
group_commitment: GroupCommitment<C>,
pubkeys: &keys::PublicKeyPackage<C>,
signing_package: &SigningPackage<C>,
signature_shares: &BTreeMap<Identifier<C>, round2::SignatureShare<C>>,
binding_factor_list: &BindingFactorList<C>,
) -> Result<(), Error<C>> {
// Compute the per-message challenge.
let challenge = crate::challenge::<C>(
&group_commitment.0,
&pubkeys.verifying_key,
signing_package.message().as_slice(),
)?;

// Verify the signature shares.
for (signature_share_identifier, signature_share) in signature_shares {
// Look up the public key for this signer, where `signer_pubkey` = _G.ScalarBaseMult(s[i])_,
// and where s[i] is a secret share of the constant term of _f_, the secret polynomial.
let signer_pubkey = pubkeys
.verifying_shares
.get(signature_share_identifier)
.ok_or(Error::UnknownIdentifier)?;

// Compute Lagrange coefficient.
let lambda_i = derive_interpolating_value(signature_share_identifier, signing_package)?;

let binding_factor = binding_factor_list
.get(signature_share_identifier)
.ok_or(Error::UnknownIdentifier)?;

// Compute the commitment share.
let R_share = signing_package
.signing_commitment(signature_share_identifier)
.ok_or(Error::UnknownIdentifier)?
.to_group_commitment_share(binding_factor);

// Compute relation values to verify this signature share.
signature_share.verify(
*signature_share_identifier,
&R_share,
signer_pubkey,
lambda_i,
&challenge,
)?;
}

// We should never reach here; but we return an error to be safe.
Err(Error::InvalidSignature)
}
4 changes: 2 additions & 2 deletions frost-core/src/serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ impl<C> SerializableElement<C>
where
C: Ciphersuite,
{
/// Serialize an Element. Returns and error if it's the identity.
/// Serialize an Element. Returns an error if it's the identity.
pub fn serialize(&self) -> Result<Vec<u8>, Error<C>> {
Ok(<C::Group as Group>::serialize(&self.0)?.as_ref().to_vec())
}

/// Deserialized an Element. Returns an error if it's malformed or is the
/// Deserialize an Element. Returns an error if it's malformed or is the
/// identity.
pub fn deserialize(bytes: &[u8]) -> Result<Self, Error<C>> {
let serialized: <C::Group as Group>::Serialization = bytes
Expand Down
1 change: 1 addition & 0 deletions frost-core/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod ciphersuite_generic;
pub mod coefficient_commitment;
pub mod helpers;
pub mod proptests;
pub mod refresh;
pub mod repairable;
pub mod vectors;
pub mod vectors_dkg;
Expand Down
Loading
Loading