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 minimum participant constraints #453

Merged
merged 6 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
9 changes: 9 additions & 0 deletions frost-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ pub enum Error<C: Ciphersuite> {
/// Commitment equals the identity
#[error("Commitment equals the identity.")]
IdentityCommitment,
/// The participant's commitment is missing from the Signing Package
#[error("The Signing Package must contain the participant's Commitment.")]
MissingCommitment,

/// The participant's commitment is incorrect
#[error("The participant's commitment is incorrect.")]
IncorrectCommitment,
/// Signature share verification failed.
#[error("Invalid signature share.")]
InvalidSignatureShare {
Expand Down Expand Up @@ -125,6 +132,8 @@ where
| Error::DuplicatedShares
| Error::IncorrectNumberOfShares
| Error::IdentityCommitment
| Error::MissingCommitment
| Error::IncorrectCommitment
| Error::PackageNotFound
| Error::IncorrectNumberOfPackages
| Error::IncorrectPackage
Expand Down
33 changes: 22 additions & 11 deletions frost-core/src/frost/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,8 @@ pub fn split<C: Ciphersuite, R: RngCore + CryptoRng>(
identifiers: IdentifierList<C>,
rng: &mut R,
) -> Result<(HashMap<Identifier<C>, SecretShare<C>>, PublicKeyPackage<C>), Error<C>> {
validate_num_of_signers(min_signers, max_signers)?;

let group_public = VerifyingKey::from(key);

let coefficients = generate_coefficients::<C, R>(min_signers as usize - 1, rng);
Expand Down Expand Up @@ -641,6 +643,25 @@ where
}
}

fn validate_num_of_signers<C: Ciphersuite>(
min_signers: u16,
max_signers: u16,
) -> Result<(), Error<C>> {
if min_signers < 2 {
return Err(Error::InvalidMinSigners);
}

if max_signers < 2 {
return Err(Error::InvalidMaxSigners);
}

if min_signers > max_signers {
return Err(Error::InvalidMinSigners);
}

Ok(())
}

/// Generate a secret polynomial to use in secret sharing, for the given
/// secret value. Also validates the given parameters.
///
Expand All @@ -655,17 +676,7 @@ pub(crate) fn generate_secret_polynomial<C: Ciphersuite>(
min_signers: u16,
mut coefficients: Vec<Scalar<C>>,
) -> Result<(Vec<Scalar<C>>, VerifiableSecretSharingCommitment<C>), Error<C>> {
if min_signers < 2 {
return Err(Error::InvalidMinSigners);
}

if max_signers < 2 {
return Err(Error::InvalidMaxSigners);
}

if min_signers > max_signers {
return Err(Error::InvalidMinSigners);
}
validate_num_of_signers(min_signers, max_signers)?;

if coefficients.len() != min_signers as usize - 1 {
return Err(Error::InvalidCoefficients);
Expand Down
15 changes: 15 additions & 0 deletions frost-core/src/frost/round2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use crate::{
#[cfg(feature = "serde")]
use crate::ScalarSerialization;

use super::round1::SigningCommitments;

// Used to help encoding a SignatureShare. Since it has a Scalar<C> it can't
// be directly encoded with serde, so we use this struct to wrap the scalar.
#[cfg(feature = "serde")]
Expand Down Expand Up @@ -187,6 +189,19 @@ pub fn sign<C: Ciphersuite>(
signer_nonces: &round1::SigningNonces<C>,
key_package: &frost::keys::KeyPackage<C>,
) -> Result<SignatureShare<C>, Error<C>> {
// Validate the signer's commitment is present in the signing package
let commitment = signing_package
.signing_commitments
.get(&key_package.identifier)
.ok_or(Error::MissingCommitment)?;

let signing_commitments = SigningCommitments::from(signer_nonces);

// Validate the signer's commitment exists
if &signing_commitments != commitment {
return Err(Error::IncorrectCommitment);
}

// Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the
// binding factor.
let binding_factor_list: BindingFactorList<C> =
Expand Down
190 changes: 190 additions & 0 deletions frost-core/src/tests/ciphersuite_generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,30 @@ pub fn check_share_generation<C: Ciphersuite, R: RngCore + CryptoRng>(mut rng: R
);
}

/// Test share generation with a Ciphersuite
pub fn check_share_generation_fails_with_invalid_signers<C: Ciphersuite, R: RngCore + CryptoRng>(
min_signers: u16,
max_signers: u16,
error: Error<C>,
mut rng: R,
) {
let secret = crate::SigningKey::<C>::new(&mut rng);

// Use arbitrary number of coefficients so tests don't fail for overflow reasons
let coefficients = frost::keys::generate_coefficients::<C, _>(3, &mut rng);

let secret_shares = frost::keys::generate_secret_shares(
&secret,
max_signers,
min_signers,
coefficients,
&frost::keys::default_identifiers(max_signers),
);

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

/// Test FROST signing with trusted dealer with a Ciphersuite.
pub fn check_sign_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>(
mut rng: R,
Expand Down Expand Up @@ -89,6 +113,24 @@ pub fn check_sign_with_dealer<C: Ciphersuite, R: RngCore + CryptoRng>(
check_sign(min_signers, key_packages, rng, pubkeys)
}

/// Test FROST signing with trusted dealer fails with invalid numbers of signers.
pub fn check_sign_with_dealer_fails_with_invalid_signers<C: Ciphersuite, R: RngCore + CryptoRng>(
min_signers: u16,
max_signers: u16,
error: Error<C>,
mut rng: R,
) {
let out = frost::keys::generate_with_dealer(
max_signers,
min_signers,
frost::keys::IdentifierList::Default::<C>,
&mut rng,
);

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

fn check_sign<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(
min_signers: u16,
key_packages: HashMap<frost::Identifier<C>, frost::keys::KeyPackage<C>>,
Expand Down Expand Up @@ -448,3 +490,151 @@ pub fn check_identifier_derivation<C: Ciphersuite>() {
assert!(id1a == id1b);
assert!(id1a != id2);
}

/// Checks the signer's identifier is included in the package
pub fn check_sign_with_missing_identifier<C: Ciphersuite, R: RngCore + CryptoRng>(mut rng: R) {
////////////////////////////////////////////////////////////////////////////
// Key generation
////////////////////////////////////////////////////////////////////////////

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

// Verifies the secret shares from the dealer
let mut key_packages: HashMap<frost::Identifier<C>, frost::keys::KeyPackage<C>> =
HashMap::new();

for (k, v) in shares {
let key_package = frost::keys::KeyPackage::try_from(v).unwrap();
key_packages.insert(k, key_package);
}

let mut nonces_map: HashMap<frost::Identifier<C>, frost::round1::SigningNonces<C>> =
HashMap::new();
let mut commitments_map: BTreeMap<frost::Identifier<C>, frost::round1::SigningCommitments<C>> =
BTreeMap::new();

////////////////////////////////////////////////////////////////////////////
// Round 1: generating nonces and signing commitments for each participant
////////////////////////////////////////////////////////////////////////////

let id_1 = Identifier::<C>::try_from(1).unwrap();
let id_2 = Identifier::<C>::try_from(2).unwrap();
let id_3 = Identifier::<C>::try_from(3).unwrap();
let key_packages_inc = vec![id_1, id_2, id_3];

for participant_identifier in key_packages_inc {
// The nonces and commitments for each participant are generated.
let (nonces, commitments) = frost::round1::commit(
key_packages
.get(&participant_identifier)
.unwrap()
.secret_share(),
&mut rng,
);
nonces_map.insert(participant_identifier, nonces);

// Participant with id_1 is excluded from the commitments_map so it is missing from the signing package
if participant_identifier == id_1 {
continue;
}
commitments_map.insert(participant_identifier, commitments);
}

// This is what the signature aggregator / coordinator needs to do:
// - decide what message to sign
// - take one (unused) commitment per signing participant
let message = "message to sign".as_bytes();
let signing_package = frost::SigningPackage::new(commitments_map, message);

////////////////////////////////////////////////////////////////////////////
// Round 2: Participant with id_1 signs
////////////////////////////////////////////////////////////////////////////

let key_package_1 = key_packages.get(&id_1).unwrap();

let nonces_to_use = &nonces_map.get(&id_1).unwrap();

// Each participant generates their signature share.
let signature_share = frost::round2::sign(&signing_package, nonces_to_use, key_package_1);

assert!(signature_share.is_err());
assert!(signature_share == Err(Error::MissingCommitment))
}

/// Checks the signer's commitment is valid
pub fn check_sign_with_incorrect_commitments<C: Ciphersuite, R: RngCore + CryptoRng>(mut rng: R) {
////////////////////////////////////////////////////////////////////////////
// Key generation
////////////////////////////////////////////////////////////////////////////

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

// Verifies the secret shares from the dealer
let mut key_packages: HashMap<frost::Identifier<C>, frost::keys::KeyPackage<C>> =
HashMap::new();

for (k, v) in shares {
let key_package = frost::keys::KeyPackage::try_from(v).unwrap();
key_packages.insert(k, key_package);
}

let mut commitments_map: BTreeMap<frost::Identifier<C>, frost::round1::SigningCommitments<C>> =
BTreeMap::new();

////////////////////////////////////////////////////////////////////////////
// Round 1: generating nonces and signing commitments for each participant
////////////////////////////////////////////////////////////////////////////

let id_1 = Identifier::<C>::try_from(1).unwrap();
let id_2 = Identifier::<C>::try_from(2).unwrap();
let id_3 = Identifier::<C>::try_from(3).unwrap();
// let key_packages_inc = vec![id_1, id_2, id_3];

let (_nonces_1, commitments_1) =
frost::round1::commit(key_packages[&id_1].secret_share(), &mut rng);

let (_nonces_2, commitments_2) =
frost::round1::commit(key_packages[&id_2].secret_share(), &mut rng);

let (nonces_3, _commitments_3) =
frost::round1::commit(key_packages[&id_3].secret_share(), &mut rng);

commitments_map.insert(id_1, commitments_1);
commitments_map.insert(id_2, commitments_2);
// Invalid commitment for id_3
commitments_map.insert(id_3, commitments_1);

// This is what the signature aggregator / coordinator needs to do:
// - decide what message to sign
// - take one (unused) commitment per signing participant
let message = "message to sign".as_bytes();
let signing_package = frost::SigningPackage::new(commitments_map, message);

////////////////////////////////////////////////////////////////////////////
// Round 2: Participant with id_3 signs
////////////////////////////////////////////////////////////////////////////

let key_package_3 = key_packages.get(&id_3).unwrap();

// Each participant generates their signature share.
let signature_share = frost::round2::sign(&signing_package, &nonces_3, key_package_3);

assert!(signature_share.is_err());
assert!(signature_share == Err(Error::IncorrectCommitment))
}
Loading
Loading