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

Add refresh shares with dealer functionality #665

Merged
merged 19 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
19 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
10 changes: 10 additions & 0 deletions book/src/frost.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@ 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.

## Refreshing

Refreshing shares starts with a Trusted Dealer who calculated a zero key to
send to each participant who can then refresh their shares and create a new
key package.

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

## Ciphersuites

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions book/src/tutorial/refreshing_shares.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 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 `calculate_zero_key` where the new SecretShares are generated and then verified.
This is done with
[`KeyPackage::try_from()`](https://docs.rs/frost-core/latest/frost_core/frost/keys/struct.KeyPackage.html#method.try_from):
`caluclate_zero_key` returns a new SecretShare and PublicKeyPackage
Each new `SecretShare` and `PublicKeyPackage` must then be sent via an [**authenticated** and
**confidential** channel
](https://frost.zfnd.org/terminology.html#peer-to-peer-channel) for each
participant, who must verify the package to obtain a `KeyPackage` which contains
their signing share, verifying share and group verifying key.

Each Participant then runs `refresh_share` to generate a new `KeyPackage`.

```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.
```
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
111 changes: 111 additions & 0 deletions frost-core/src/keys/refresh.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//! 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 std::collections::BTreeMap;

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};

/// Refreshes shares using a trusted dealer
pub fn calculate_zero_key<C: Ciphersuite, R: RngCore + CryptoRng>(
old_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 zero key shares

let zero_key = SigningKey {
scalar: <<C::Group as Group>::Field>::zero(),
};

let coefficients = generate_coefficients::<C, R>(min_signers as usize - 1, rng);

let zero_shares = generate_secret_shares(
&zero_key,
max_signers,
min_signers,
coefficients,
identifiers,
)?;

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

for share in zero_shares.clone() {
let signer_public = SigningShare::into(share.signing_share);
verifying_shares.insert(share.identifier, signer_public);
let mut coefficients = share.commitment.0;
coefficients.remove(0);
zero_shares_minus_identity.push(SecretShare {
header: share.header,
identifier: share.identifier,
signing_share: share.signing_share,
commitment: VerifiableSecretSharingCommitment::new(coefficients),
});
}

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

Ok((zero_shares_minus_identity, pub_key_package))
}

/// Each participant refreshes their shares
/// This is done by taking the `zero_share` received from the trusted dealer and adding it to the original share
pub fn refresh_share<C: Ciphersuite>(
zero_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 zero_commitments_without_id = zero_share.commitment.0;

let zero_commitment: Vec<CoefficientCommitment<C>> = identity_commitment
.into_iter()
.chain(zero_commitments_without_id.clone())
.collect();

let zero_share = SecretShare {
header: zero_share.header,
identifier: zero_share.identifier,
signing_share: zero_share.signing_share,
commitment: VerifiableSecretSharingCommitment::<C>::new(zero_commitment),
};

// verify zero_share secret share
let zero_key_package = KeyPackage::<C>::try_from(zero_share)?;

let signing_share: SigningShare<C> = SigningShare::new(
zero_key_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)
}
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
185 changes: 185 additions & 0 deletions frost-core/src/tests/refresh.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
//! Test for Refreshing shares

use std::collections::BTreeMap;

use rand_core::{CryptoRng, RngCore};

use crate::keys::generate_with_dealer;
use crate::keys::refresh::{calculate_zero_key, refresh_share};
use crate::{self as frost};
use crate::{
keys::{KeyPackage, PublicKeyPackage, SecretShare},
Ciphersuite, Error, Identifier,
};

use super::ciphersuite_generic::check_sign;

/// We want to test that recover share matches the original share
pub fn check_refresh_shares_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>(mut rng: R) {
// Compute shares

////////////////////////////////////////////////////////////////////////////
// Old Key generation
////////////////////////////////////////////////////////////////////////////

let max_signers = 5;
let min_signers = 3;
let (old_shares, pub_key_package) = generate_with_dealer(
max_signers,
min_signers,
frost::keys::IdentifierList::Default,
&mut rng,
)
.unwrap();

let mut old_key_packages: BTreeMap<frost::Identifier<C>, KeyPackage<C>> = BTreeMap::new();

for (k, v) in old_shares {
let key_package = KeyPackage::try_from(v).unwrap();
old_key_packages.insert(k, key_package);
}

////////////////////////////////////////////////////////////////////////////
// New Key generation
////////////////////////////////////////////////////////////////////////////

// Signer 2 will be removed and Signers 1, 3, 4 & 5 will remain

let remaining_ids = vec![
Identifier::try_from(1).unwrap(),
Identifier::try_from(3).unwrap(),
Identifier::try_from(4).unwrap(),
Identifier::try_from(5).unwrap(),
];

const NEW_MAX_SIGNERS: u16 = 4;

// Trusted Dealer generates zero keys

let (zero_shares, new_pub_key_package) = calculate_zero_key(
pub_key_package,
NEW_MAX_SIGNERS,
min_signers,
&remaining_ids,
&mut rng,
)
.unwrap();

conradoplg marked this conversation as resolved.
Show resolved Hide resolved
// Each participant refreshes their share

let mut new_shares = BTreeMap::new();

for i in 0..remaining_ids.len() {
let identifier = remaining_ids[i];
let current_share = &old_key_packages[&identifier];
new_shares.insert(
identifier,
refresh_share(zero_shares[i].clone(), current_share),
);
}

let mut key_packages: BTreeMap<frost::Identifier<C>, KeyPackage<C>> = BTreeMap::new();

for (k, v) in new_shares {
key_packages.insert(k, v.unwrap());
}
check_sign(min_signers, key_packages, rng, new_pub_key_package).unwrap();
}

/// Check refesh shares with dealer errors
pub fn check_refresh_shares_with_dealer_fails_with_invalid_signers<
C: Ciphersuite,
R: RngCore + CryptoRng,
>(
new_max_signers: u16,
min_signers: u16,
identifiers: &[Identifier<C>],
error: Error<C>,
mut rng: R,
) {
let (_old_shares, pub_key_package) =
generate_with_dealer::<C, R>(5, 2, frost::keys::IdentifierList::Default, &mut rng).unwrap();
let out = calculate_zero_key(
pub_key_package,
new_max_signers,
min_signers,
identifiers,
&mut rng,
);

assert!(out.is_err());
assert!(out == Err(error))
}

/// Check serialisation
pub fn check_refresh_shares_with_dealer_serialisation<C: Ciphersuite, R: RngCore + CryptoRng>(
mut rng: R,
) {
// Compute shares

////////////////////////////////////////////////////////////////////////////
// Old Key generation
////////////////////////////////////////////////////////////////////////////

let max_signers = 5;
let min_signers = 3;
let (_old_shares, pub_key_package) = generate_with_dealer(
max_signers,
min_signers,
frost::keys::IdentifierList::Default,
&mut rng,
)
.unwrap();

////////////////////////////////////////////////////////////////////////////
// New Key generation
//
// Zero key is calculated by trusted dealer
// Participant 2 will be removed and Participants 1, 3, 4 & 5 will remain
////////////////////////////////////////////////////////////////////////////

let remaining_ids = vec![
Identifier::try_from(1).unwrap(),
Identifier::try_from(3).unwrap(),
Identifier::try_from(4).unwrap(),
Identifier::try_from(5).unwrap(),
];

const NEW_MAX_SIGNERS: u16 = 4;

let (zero_shares, new_pub_key_package) = calculate_zero_key(
pub_key_package,
NEW_MAX_SIGNERS,
min_signers,
&remaining_ids,
&mut rng,
)
.unwrap();

// Trusted dealer serialises zero shares and key package

let zero_shares_serialised = SecretShare::<C>::serialize(&zero_shares[0]);

assert!(zero_shares_serialised.is_ok());

let new_pub_key_package_serialised = PublicKeyPackage::<C>::serialize(&new_pub_key_package);

assert!(new_pub_key_package_serialised.is_ok());

// Participant 1 deserialises zero share and key package

let zero_share = SecretShare::<C>::deserialize(&zero_shares_serialised.unwrap());

assert!(zero_share.is_ok());

let new_pub_key_package =
PublicKeyPackage::<C>::deserialize(&new_pub_key_package_serialised.unwrap());

assert!(new_pub_key_package.is_ok());

// Participant 1 checks Key Package can be created from Secret Share

let key_package = KeyPackage::<C>::try_from(zero_share.unwrap());

assert!(key_package.is_ok());
}
Loading
Loading