Skip to content

Commit

Permalink
Add refresh shares with dealer functionality (#665)
Browse files Browse the repository at this point in the history
* Add refresh shares with dealer functionality (#245)

* Change refresh share API (#245)

Split refresh_shares_with_dealer into calculate_zero_key and refresh_share

* Fix serialisation error with refresh share (#245)

Add serialisation test

* Fix serialisation errors after updates (#245)

Fixed some typos

* Update refresh_share to accept and return a KeyPackage instead of SecretShare (#245)

* Tidy up refresh share functionality (#245)

* Add refresh share functionality to Book (#245)

Diagram is still to be added

* Update book for rereshing shares with trusted dealer (#245)

* Add new verifying shares calculation for refresh shares (#245)

Add tests for invalid identifiers when refreshing shares

* Rename calculate_zero_key to compute_refreshing_shares (#245)

* Import Vec from the alloc crate (#245)

This is to be compatible with the no_std attribute

* Use alloc crate instead of std for refresh shares (#245)

* Fix fmt error (#245)

* Refactoring refresh shares functionality (#245)

* cleanups during review

* Update book/src/tutorial/refreshing-shares.md

* update docs

* always return error in detect_cheater

* add changelog entry

---------

Co-authored-by: Conrado Gouvea <[email protected]>
Co-authored-by: Conrado Gouvea <[email protected]>
  • Loading branch information
3 people authored Jul 18, 2024
1 parent 60cc5b9 commit 835a3f0
Show file tree
Hide file tree
Showing 26 changed files with 1,293 additions and 55 deletions.
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 crate::serialization::{Deserialize, Serialize};
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
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

0 comments on commit 835a3f0

Please sign in to comment.