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

Support optional cheater detection #564

Merged
merged 8 commits into from
Oct 25, 2023
2 changes: 2 additions & 0 deletions frost-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ serde = ["dep:serde", "dep:serdect"]
serialization = ["serde", "dep:postcard"]
# Exposes ciphersuite-generic tests for other crates to use
test-impl = ["proptest", "serde_json", "criterion"]
# Enables cheater detection
cheater-detection = []
conradoplg marked this conversation as resolved.
Show resolved Hide resolved

[lib]
bench = false
77 changes: 77 additions & 0 deletions frost-core/src/frost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,8 @@ where
/// signature, if the coordinator themselves is a signer and misbehaves, they
/// can avoid that step. However, at worst, this results in a denial of
/// service attack due to publishing an invalid signature.

#[cfg(feature = "cheater-detection")]
pub fn aggregate<C>(
signing_package: &SigningPackage<C>,
signature_shares: &BTreeMap<Identifier<C>, round2::SignatureShare<C>>,
Expand Down Expand Up @@ -506,3 +508,78 @@ where

Ok(signature)
}

/// Aggregates the signature shares to produce a final signature that
/// can be verified with the group public key.
///
/// `signature_shares` maps the identifier of each participant to the
/// [`round2::SignatureShare`] they sent. These identifiers must come from whatever mapping
/// the coordinator has between communication channels and participants, i.e.
/// they must have assurance that the [`round2::SignatureShare`] came from
/// the participant with that identifier.
///
/// This operation is performed by a coordinator that can communicate with all
/// the signing participants before publishing the final signature. The
/// coordinator can be one of the participants or a semi-trusted third party
/// (who is trusted to not perform denial of service attacks, but does not learn
/// any secret information). Note that because the coordinator is trusted to
/// report misbehaving parties in order to avoid publishing an invalid
/// signature, if the coordinator themselves is a signer and misbehaves, they
/// can avoid that step. However, at worst, this results in a denial of
/// service attack due to publishing an invalid signature.

#[cfg(not(feature = "cheater-detection"))]
pub fn aggregate<C>(
conradoplg marked this conversation as resolved.
Show resolved Hide resolved
signing_package: &SigningPackage<C>,
signature_shares: &BTreeMap<Identifier<C>, round2::SignatureShare<C>>,
pubkeys: &keys::PublicKeyPackage<C>,
) -> Result<Signature<C>, Error<C>>
where
C: Ciphersuite,
{
// Check if signing_package.signing_commitments and signature_shares have
// the same set of identifiers

if signing_package.signing_commitments().len() != signature_shares.len() {
return Err(Error::UnknownIdentifier);
}
if !signing_package
.signing_commitments()
.keys()
.all(|id| signature_shares.contains_key(id))
{
return Err(Error::UnknownIdentifier);
}

// Encodes the signing commitment list produced in round one as part of generating [`BindingFactor`], the
// 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)?;

// The aggregation of the signature shares by summing them up, resulting in
// a plain Schnorr signature.
//
// Implements [`aggregate`] from the spec.
//
// [`aggregate`]: https://www.ietf.org/archive/id/draft-irtf-cfrg-frost-14.html#section-5.3
let mut z = <<C::Group as Group>::Field>::zero();

for signature_share in signature_shares.values() {
z = z + signature_share.share;
}

let signature = Signature {
R: group_commitment.0,
z,
};

// Verify the aggregate signature
pubkeys
.verifying_key
.verify(signing_package.message(), &signature)?;

Ok(signature)
}
4 changes: 2 additions & 2 deletions frost-core/src/frost/round2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,12 @@ where
&self,
identifier: Identifier<C>,
group_commitment_share: &round1::GroupCommitmentShare<C>,
public_key: &frost::keys::VerifyingShare<C>,
verifying_share: &frost::keys::VerifyingShare<C>,
lambda_i: Scalar<C>,
challenge: &Challenge<C>,
) -> Result<(), Error<C>> {
if (<C::Group>::generator() * self.share)
!= (group_commitment_share.0 + (public_key.0 * challenge.0 * lambda_i))
!= (group_commitment_share.0 + (verifying_share.0 * challenge.0 * lambda_i))
{
return Err(Error::InvalidSignatureShare {
culprit: identifier,
Expand Down
14 changes: 12 additions & 2 deletions frost-core/src/tests/ciphersuite_generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use std::{collections::BTreeMap, convert::TryFrom};

use crate::{
frost::{self, Identifier},
frost::{self, keys::PublicKeyPackage, Identifier},
Error, Field, Group, Signature, SigningKey, VerifyingKey,
};
use rand_core::{CryptoRng, RngCore};
Expand Down Expand Up @@ -191,7 +191,7 @@
min_signers: u16,
key_packages: BTreeMap<frost::Identifier<C>, frost::keys::KeyPackage<C>>,
mut rng: R,
pubkey_package: frost::keys::PublicKeyPackage<C>,
pubkey_package: PublicKeyPackage<C>,
) -> Result<(Vec<u8>, Signature<C>, VerifyingKey<C>), Error<C>> {
let mut nonces_map: BTreeMap<frost::Identifier<C>, frost::round1::SigningNonces<C>> =
BTreeMap::new();
Expand Down Expand Up @@ -248,6 +248,13 @@
// generates the final signature.
////////////////////////////////////////////////////////////////////////////

#[cfg(not(feature = "cheater-detection"))]
let pubkey_package = PublicKeyPackage {
header: pubkey_package.header,
verifying_shares: BTreeMap::new(),
verifying_key: pubkey_package.verifying_key,
};

check_aggregate_errors(
signing_package.clone(),
signature_shares.clone(),
Expand Down Expand Up @@ -305,18 +312,21 @@
signature_shares: BTreeMap<frost::Identifier<C>, frost::round2::SignatureShare<C>>,
pubkey_package: frost::keys::PublicKeyPackage<C>,
) {
#[cfg(feature = "cheater-detection")]
check_aggregate_corrupted_share(
signing_package.clone(),
signature_shares.clone(),
pubkey_package.clone(),
);

check_aggregate_invalid_share_identifier_for_verifying_shares(
signing_package,
signature_shares,
pubkey_package,
);
}

#[cfg(feature = "cheater-detection")]
fn check_aggregate_corrupted_share<C: Ciphersuite + PartialEq>(
signing_package: frost::SigningPackage<C>,
mut signature_shares: BTreeMap<frost::Identifier<C>, frost::round2::SignatureShare<C>>,
Expand Down Expand Up @@ -405,7 +415,7 @@
.expect("should be nonzero");
received_round1_packages
.entry(receiver_participant_identifier)
.or_insert_with(BTreeMap::new)

Check failure on line 418 in frost-core/src/tests/ciphersuite_generic.rs

View workflow job for this annotation

GitHub Actions / Clippy (stable) Results

use of `or_insert_with` to construct default value

error: use of `or_insert_with` to construct default value --> frost-core/src/tests/ciphersuite_generic.rs:418:18 | 418 | .or_insert_with(BTreeMap::new) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `or_default()` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unwrap_or_default = note: `-D clippy::unwrap-or-default` implied by `-D warnings`
.insert(participant_identifier, round1_package.clone());
}
}
Expand Down
2 changes: 2 additions & 0 deletions frost-ed25519/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ serialization = ["serde", "frost-core/serialization"]
## can use `serde` to serialize structs with any encoder that supports
## `serde` (e.g. JSON with `serde_json`).
serde = ["frost-core/serde"]
## Enable cheater detection
cheater-detection = ["frost-core/cheater-detection"]
conradoplg marked this conversation as resolved.
Show resolved Hide resolved

[lib]
# Disables non-criterion benchmark which is not used; prevents errors
Expand Down
30 changes: 30 additions & 0 deletions frost-ed25519/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ fn check_zero_key_fails() {
frost_core::tests::ciphersuite_generic::check_zero_key_fails::<Ed25519Sha512>();
}

#[cfg(feature = "cheater-detection")]
#[test]
fn check_sign_with_dkg() {
let rng = thread_rng();

frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<Ed25519Sha512, _>(rng);
}

#[cfg(not(feature = "cheater-detection"))]
conradoplg marked this conversation as resolved.
Show resolved Hide resolved
#[test]
fn check_sign_with_dkg() {
let rng = thread_rng();
Expand Down Expand Up @@ -64,6 +73,15 @@ fn check_rts() {
frost_core::tests::repairable::check_rts::<Ed25519Sha512, _>(rng);
}

#[cfg(feature = "cheater-detection")]
#[test]
fn check_sign_with_dealer() {
let rng = thread_rng();

frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<Ed25519Sha512, _>(rng);
}

#[cfg(not(feature = "cheater-detection"))]
conradoplg marked this conversation as resolved.
Show resolved Hide resolved
#[test]
fn check_sign_with_dealer() {
let rng = thread_rng();
Expand Down Expand Up @@ -205,6 +223,18 @@ fn check_identifier_generation() -> Result<(), Error> {
Ok(())
}

#[cfg(feature = "cheater-detection")]
#[test]
fn check_sign_with_dealer_and_identifiers() {
let rng = thread_rng();

frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::<
Ed25519Sha512,
_,
>(rng);
}

#[cfg(not(feature = "cheater-detection"))]
conradoplg marked this conversation as resolved.
Show resolved Hide resolved
#[test]
fn check_sign_with_dealer_and_identifiers() {
let rng = thread_rng();
Expand Down
2 changes: 2 additions & 0 deletions frost-ed448/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ serialization = ["serde", "frost-core/serialization"]
## can use `serde` to serialize structs with any encoder that supports
## `serde` (e.g. JSON with `serde_json`).
serde = ["frost-core/serde"]
## Enable cheater detection
cheater-detection = ["frost-core/cheater-detection"]

[lib]
# Disables non-criterion benchmark which is not used; prevents errors
Expand Down
30 changes: 30 additions & 0 deletions frost-ed448/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ fn check_zero_key_fails() {
frost_core::tests::ciphersuite_generic::check_zero_key_fails::<Ed448Shake256>();
}

#[cfg(feature = "cheater-detection")]
#[test]
fn check_sign_with_dkg() {
let rng = thread_rng();

frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<Ed448Shake256, _>(rng);
}

#[cfg(not(feature = "cheater-detection"))]
#[test]
fn check_sign_with_dkg() {
let rng = thread_rng();
Expand Down Expand Up @@ -64,6 +73,15 @@ fn check_rts() {
frost_core::tests::repairable::check_rts::<Ed448Shake256, _>(rng);
}

#[cfg(feature = "cheater-detection")]
#[test]
fn check_sign_with_dealer() {
let rng = thread_rng();

frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<Ed448Shake256, _>(rng);
}

#[cfg(not(feature = "cheater-detection"))]
#[test]
fn check_sign_with_dealer() {
let rng = thread_rng();
Expand Down Expand Up @@ -205,6 +223,18 @@ fn check_identifier_generation() -> Result<(), Error> {
Ok(())
}

#[cfg(feature = "cheater-detection")]
#[test]
fn check_sign_with_dealer_and_identifiers() {
let rng = thread_rng();

frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::<
Ed448Shake256,
_,
>(rng);
}

#[cfg(not(feature = "cheater-detection"))]
#[test]
fn check_sign_with_dealer_and_identifiers() {
let rng = thread_rng();
Expand Down
2 changes: 2 additions & 0 deletions frost-p256/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ serialization = ["serde", "frost-core/serialization"]
## can use `serde` to serialize structs with any encoder that supports
## `serde` (e.g. JSON with `serde_json`).
serde = ["frost-core/serde"]
## Enable cheater detection
cheater-detection = ["frost-core/cheater-detection"]

[lib]
# Disables non-criterion benchmark which is not used; prevents errors
Expand Down
29 changes: 29 additions & 0 deletions frost-p256/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ fn check_zero_key_fails() {
frost_core::tests::ciphersuite_generic::check_zero_key_fails::<P256Sha256>();
}

#[cfg(feature = "cheater-detection")]
#[test]
fn check_sign_with_dkg() {
let rng = thread_rng();

frost_core::tests::ciphersuite_generic::check_sign_with_dkg::<P256Sha256, _>(rng);
}

#[cfg(not(feature = "cheater-detection"))]
#[test]
fn check_sign_with_dkg() {
let rng = thread_rng();
Expand Down Expand Up @@ -64,6 +73,15 @@ fn check_rts() {
frost_core::tests::repairable::check_rts::<P256Sha256, _>(rng);
}

#[cfg(feature = "cheater-detection")]
#[test]
fn check_sign_with_dealer() {
let rng = thread_rng();

frost_core::tests::ciphersuite_generic::check_sign_with_dealer::<P256Sha256, _>(rng);
}

#[cfg(not(feature = "cheater-detection"))]
#[test]
fn check_sign_with_dealer() {
let rng = thread_rng();
Expand Down Expand Up @@ -203,6 +221,17 @@ fn check_identifier_generation() -> Result<(), Error> {
Ok(())
}

#[cfg(feature = "cheater-detection")]
#[test]
fn check_sign_with_dealer_and_identifiers() {
let rng = thread_rng();

frost_core::tests::ciphersuite_generic::check_sign_with_dealer_and_identifiers::<P256Sha256, _>(
rng,
);
}

#[cfg(not(feature = "cheater-detection"))]
#[test]
fn check_sign_with_dealer_and_identifiers() {
let rng = thread_rng();
Expand Down
1 change: 1 addition & 0 deletions frost-ristretto255/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ serialization = ["serde", "frost-core/serialization"]
## can use `serde` to serialize structs with any encoder that supports
## `serde` (e.g. JSON with `serde_json`).
serde = ["frost-core/serde"]
cheater-detection = ["frost-core/cheater-detection"]

[lib]
# Disables non-criterion benchmark which is not used; prevents errors
Expand Down
Loading
Loading