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

Make tests generic #105

Merged
merged 3 commits into from
Sep 5, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions frost-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,19 @@ features = ["nightly"]

[dependencies]
byteorder = "1.4"
debugless-unwrap = "0.0.4"
digest = "0.10"
hex = { version = "0.4.3", features = ["serde"] }
rand_core = "0.6"
serde = { version = "1", optional = true, features = ["derive"] }
thiserror = "1.0"
zeroize = { version = "1.5.4", default-features = false, features = ["derive"] }

# Test dependencies used with the test-impl feature
proptest = { version = "1.0", optional = true }
proptest-derive = { version = "0.3", optional = true }
serde_json = { version = "1.0", optional = true }

[dev-dependencies]
curve25519-dalek = { version = "4.0.0-pre.1", features = ["serde"] }
lazy_static = "1.4"
Expand All @@ -38,3 +44,5 @@ sha2 = "0.10.2"
[features]
nightly = []
default = ["serde"]
# Exposes ciphersuite-generic tests for other crates to use
test-impl = ["proptest", "proptest-derive", "serde_json"]
2 changes: 2 additions & 0 deletions frost-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub mod frost;
mod scalar_mul;
mod signature;
mod signing_key;
#[cfg(any(test, feature = "test-impl"))]
pub mod tests;
mod verifying_key;

pub use error::Error;
Expand Down
17 changes: 4 additions & 13 deletions frost-core/src/signature.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
//! Schnorr signatures over prime order groups (or subgroups)

use std::fmt::Debug;

// use hex::FromHex;
use debugless_unwrap::DebuglessUnwrap;

use crate::{Ciphersuite, Error, Field, Group};

Expand All @@ -23,11 +21,7 @@ where
<C::Group as Group>::Field: Field,
{
/// Converts bytes as [`C::SignatureSerialization`] into a `Signature<C>`.
pub fn from_bytes(bytes: C::SignatureSerialization) -> Result<Self, Error>
where
<<C::Group as Group>::Serialization as TryFrom<Vec<u8>>>::Error: Debug,
<<<C::Group as Group>::Field as Field>::Serialization as TryFrom<Vec<u8>>>::Error: Debug,
{
pub fn from_bytes(bytes: C::SignatureSerialization) -> Result<Self, Error> {
// To compute the expected length of the encoded point, encode the generator
// and get its length. Note that we can't use the identity because it can be encoded
// shorter in some cases (e.g. P-256, which uses SEC1 encoding).
Expand Down Expand Up @@ -57,16 +51,13 @@ where
}

/// Converts this signature to its [`C::SignatureSerialization`] in bytes.
pub fn to_bytes(&self) -> C::SignatureSerialization
where
<<C as Ciphersuite>::SignatureSerialization as TryFrom<Vec<u8>>>::Error: Debug,
{
pub fn to_bytes(&self) -> C::SignatureSerialization {
let mut bytes = vec![];

bytes.extend(<C::Group as Group>::serialize(&self.R).as_ref());
bytes.extend(<<C::Group as Group>::Field as Field>::serialize(&self.z).as_ref());

bytes.try_into().unwrap()
bytes.try_into().debugless_unwrap()
}
}

Expand Down
41 changes: 30 additions & 11 deletions frost-core/tests/frost.rs → frost-core/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
//! Ciphersuite-generic test functions.
use std::{collections::HashMap, convert::TryFrom};

use frost_core::frost::{self, Identifier};
use rand::thread_rng;
use crate::frost::{self, Identifier};
use rand_core::{CryptoRng, RngCore};

mod common;
use crate::Ciphersuite;

use common::ciphersuite::Ristretto255Sha512 as R;
pub mod proptests;
pub mod vectors;

#[test]
fn check_sign_with_dealer() {
let mut rng = thread_rng();
/// Test share generation with a Ciphersuite
pub fn check_share_generation<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(mut rng: R) {
let secret = frost::keys::Secret::<C>::random(&mut rng);

let secret_shares = frost::keys::generate_secret_shares(&secret, 5, 3, rng).unwrap();

for secret_share in secret_shares.iter() {
assert_eq!(secret_share.verify(), Ok(()));
}

assert_eq!(
frost::keys::reconstruct_secret::<C>(secret_shares).unwrap(),
secret
)
}

/// Test FROST signing with trusted dealer with a Ciphersuite.
pub fn check_sign_with_dealer<C: Ciphersuite + PartialEq, R: RngCore + CryptoRng>(mut rng: R)
where
<C as Ciphersuite>::Group: std::cmp::PartialEq,
{
////////////////////////////////////////////////////////////////////////////
// Key generation
////////////////////////////////////////////////////////////////////////////
Expand All @@ -21,7 +40,7 @@ fn check_sign_with_dealer() {
frost::keys::keygen_with_dealer(numsigners, threshold, &mut rng).unwrap();

// Verifies the secret shares from the dealer
let key_packages: HashMap<frost::Identifier<R>, frost::keys::KeyPackage<R>> = shares
let key_packages: HashMap<frost::Identifier<C>, frost::keys::KeyPackage<C>> = shares
.into_iter()
.map(|share| {
(
Expand All @@ -31,8 +50,8 @@ fn check_sign_with_dealer() {
})
.collect();

let mut nonces: HashMap<Identifier<R>, frost::round1::SigningNonces<R>> = HashMap::new();
let mut commitments: HashMap<Identifier<R>, frost::round1::SigningCommitments<R>> =
let mut nonces: HashMap<Identifier<C>, frost::round1::SigningNonces<C>> = HashMap::new();
let mut commitments: HashMap<Identifier<C>, frost::round1::SigningCommitments<C>> =
HashMap::new();

////////////////////////////////////////////////////////////////////////////
Expand All @@ -58,7 +77,7 @@ fn check_sign_with_dealer() {
// This is what the signature aggregator / coordinator needs to do:
// - decide what message to sign
// - take one (unused) commitment per signing participant
let mut signature_shares: Vec<frost::round2::SignatureShare<R>> = Vec::new();
let mut signature_shares: Vec<frost::round2::SignatureShare<C>> = Vec::new();
let message = "message to sign".as_bytes();
let comms = commitments.clone().into_values().collect();
let signing_package = frost::SigningPackage::new(comms, message.to_vec());
Expand Down
74 changes: 22 additions & 52 deletions frost-core/tests/proptests.rs → frost-core/src/tests/proptests.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use frost_core::*;
//! Ciphersuite-generic functions for proptests

use crate::*;
use proptest::prelude::*;
use rand_core::{CryptoRng, RngCore};

mod common;

use common::ciphersuite::Ristretto255Sha512 as R;

/// A signature test-case, containing signature data and expected validity.
#[derive(Clone, Debug)]
struct SignatureCase<C: Ciphersuite> {
pub struct SignatureCase<C: Ciphersuite> {
msg: Vec<u8>,
sig: Signature<C>,
vk: VerifyingKey<C>,
Expand All @@ -18,7 +16,7 @@ struct SignatureCase<C: Ciphersuite> {

/// A modification to a test-case.
#[derive(Copy, Clone, Debug)]
enum Tweak {
pub enum Tweak {
/// No-op, used to check that unchanged cases verify.
None,
/// Change the message the signature is defined for, invalidating the signature.
Expand All @@ -41,7 +39,8 @@ impl<C> SignatureCase<C>
where
C: Ciphersuite,
{
fn new<R: RngCore + CryptoRng>(mut rng: R, msg: Vec<u8>) -> Self {
/// Create a new SignatureCase.
pub fn new<R: RngCore + CryptoRng>(mut rng: R, msg: Vec<u8>) -> Self {
let sk = SigningKey::<C>::new(&mut rng);
let sig = sk.sign(&mut rng, &msg);
let vk = VerifyingKey::<C>::from(&sk);
Expand All @@ -55,24 +54,25 @@ where
}
}

// Check that signature verification succeeds or fails, as expected.
fn check(&self) -> bool {
// // The signature data is stored in (refined) byte types, but do a round trip
// // conversion to raw bytes to exercise those code paths.
// let sig = {
// let bytes: [u8; 64] = self.sig.into();
// Signature::<C>::from_bytes(bytes)
// };
/// Check that signature verification succeeds or fails, as expected.
pub fn check(&self) -> bool {
// The signature data is stored in (refined) byte types, but do a round trip
// conversion to raw bytes to exercise those code paths.
let _sig = {
let bytes = self.sig.to_bytes();
Signature::<C>::from_bytes(bytes)
};

// // Check that the verification key is a valid key.
// let pub_key = VerifyingKey::<C>::from_bytes(pk_bytes)
// .expect("The test verification key to be well-formed.");
// Check that the verification key is a valid key.
let _pub_key = VerifyingKey::<C>::from_bytes(self.vk.to_bytes())
.expect("The test verification key to be well-formed.");

// Check that signature validation has the expected result.
self.is_valid == self.vk.verify(&self.msg, &self.sig).is_ok()
}

fn apply_tweak(&mut self, tweak: &Tweak) {
/// Apply the given tweak to the signature test case.
pub fn apply_tweak(&mut self, tweak: &Tweak) {
match tweak {
Tweak::None => {}
Tweak::ChangeMessage => {
Expand All @@ -89,41 +89,11 @@ where
}
}

fn tweak_strategy() -> impl Strategy<Value = Tweak> {
/// Tweak the proptest strategy
pub fn tweak_strategy() -> impl Strategy<Value = Tweak> {
prop_oneof![
10 => Just(Tweak::None),
1 => Just(Tweak::ChangeMessage),
1 => Just(Tweak::ChangePubkey),
]
}

use rand_chacha::ChaChaRng;
use rand_core::SeedableRng;

proptest! {

#[test]
fn tweak_signature(
tweaks in prop::collection::vec(tweak_strategy(), (0,5)),
rng_seed in prop::array::uniform32(any::<u8>()),
) {
// Use a deterministic RNG so that test failures can be reproduced.
// Seeding with 64 bits of entropy is INSECURE and this code should
// not be copied outside of this test!
let mut rng = ChaChaRng::from_seed(rng_seed);

// Create a test case for each signature type.
let msg = b"test message for proptests";
let mut sig = SignatureCase::<R>::new(&mut rng, msg.to_vec());


// Apply tweaks to each case.
for t in &tweaks {
sig.apply_tweak(t);
}

assert!(sig.check());
}


}
Loading