From 868a63a6b2e03f2648050d03071f32b9dde1f3fe Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 25 May 2022 00:28:57 -0400 Subject: [PATCH] Start modularizing FROST tests as per https://github.com/serai-dex/serai/issues/9 --- crypto/frost/src/lib.rs | 2 + crypto/frost/src/tests/curve.rs | 32 +++++ crypto/frost/src/tests/literal/mod.rs | 2 + crypto/frost/src/tests/literal/schnorr.rs | 42 +++++++ .../tests/literal/secp256k1.rs} | 9 +- .../key_gen_and_sign.rs => src/tests/mod.rs} | 115 ++++++++++-------- crypto/frost/src/tests/schnorr.rs | 32 +++++ 7 files changed, 182 insertions(+), 52 deletions(-) create mode 100644 crypto/frost/src/tests/curve.rs create mode 100644 crypto/frost/src/tests/literal/mod.rs create mode 100644 crypto/frost/src/tests/literal/schnorr.rs rename crypto/frost/{tests/common.rs => src/tests/literal/secp256k1.rs} (92%) rename crypto/frost/{tests/key_gen_and_sign.rs => src/tests/mod.rs} (50%) create mode 100644 crypto/frost/src/tests/schnorr.rs diff --git a/crypto/frost/src/lib.rs b/crypto/frost/src/lib.rs index 64a6a1d0..b9229a09 100644 --- a/crypto/frost/src/lib.rs +++ b/crypto/frost/src/lib.rs @@ -14,6 +14,8 @@ pub mod key_gen; pub mod algorithm; pub mod sign; +pub mod tests; + /// Set of errors for curve-related operations, namely encoding and decoding #[derive(Error, Debug)] pub enum CurveError { diff --git a/crypto/frost/src/tests/curve.rs b/crypto/frost/src/tests/curve.rs new file mode 100644 index 00000000..9560ac21 --- /dev/null +++ b/crypto/frost/src/tests/curve.rs @@ -0,0 +1,32 @@ +use rand_core::{RngCore, CryptoRng}; + +use crate::{ + Curve, MultisigKeys, + tests::{schnorr::{sign, verify, batch_verify}, key_gen} +}; + +// Test generation of FROST keys +fn key_generation(rng: &mut R) { + // This alone verifies the verification shares and group key are agreed upon as expected + key_gen::<_, C>(rng); +} + +// Test serialization of generated keys +fn keys_serialization(rng: &mut R) { + for (_, keys) in key_gen::<_, C>(rng) { + assert_eq!(&MultisigKeys::::deserialize(&keys.serialize()).unwrap(), &*keys); + } +} + +pub fn test_curve(rng: &mut R) { + // TODO: Test the Curve functions themselves + + // Test Schnorr signatures work as expected + sign::<_, C>(rng); + verify::<_, C>(rng); + batch_verify::<_, C>(rng); + + // Test FROST key generation and serialization of MultisigKeys works as expected + key_generation::<_, C>(rng); + keys_serialization::<_, C>(rng); +} diff --git a/crypto/frost/src/tests/literal/mod.rs b/crypto/frost/src/tests/literal/mod.rs new file mode 100644 index 00000000..d766f844 --- /dev/null +++ b/crypto/frost/src/tests/literal/mod.rs @@ -0,0 +1,2 @@ +mod secp256k1; +mod schnorr; diff --git a/crypto/frost/src/tests/literal/schnorr.rs b/crypto/frost/src/tests/literal/schnorr.rs new file mode 100644 index 00000000..d9d1eacc --- /dev/null +++ b/crypto/frost/src/tests/literal/schnorr.rs @@ -0,0 +1,42 @@ +use std::rc::Rc; + +use rand::rngs::OsRng; + +use crate::{ + Curve, schnorr, algorithm::{Hram, Schnorr}, + tests::{key_gen, algorithm_machines, sign as sign_test, actual::secp256k1::{Secp256k1, TestHram}} +}; + +const MESSAGE: &[u8] = b"Hello World"; + +#[test] +fn sign() { + sign_test( + &mut OsRng, + algorithm_machines( + &mut OsRng, + Schnorr::::new(), + &key_gen::<_, Secp256k1>(&mut OsRng) + ), + MESSAGE + ); +} + +#[test] +fn sign_with_offset() { + let mut keys = key_gen::<_, Secp256k1>(&mut OsRng); + let group_key = keys[&1].group_key(); + + let offset = Secp256k1::hash_to_F(b"offset"); + for i in 1 ..= u16::try_from(keys.len()).unwrap() { + keys.insert(i, Rc::new(keys[&i].offset(offset))); + } + let offset_key = group_key + (Secp256k1::generator_table() * offset); + + let sig = sign_test( + &mut OsRng, + algorithm_machines(&mut OsRng, Schnorr::::new(), &keys), + MESSAGE + ); + assert!(schnorr::verify(offset_key, TestHram::hram(&sig.R, &offset_key, MESSAGE), &sig)); +} diff --git a/crypto/frost/tests/common.rs b/crypto/frost/src/tests/literal/secp256k1.rs similarity index 92% rename from crypto/frost/tests/common.rs rename to crypto/frost/src/tests/literal/secp256k1.rs index 8983badf..c21de90b 100644 --- a/crypto/frost/tests/common.rs +++ b/crypto/frost/src/tests/literal/secp256k1.rs @@ -1,5 +1,7 @@ use core::convert::TryInto; +use rand::rngs::OsRng; + use ff::PrimeField; use group::GroupEncoding; @@ -11,7 +13,7 @@ use k256::{ ProjectivePoint }; -use frost::{CurveError, Curve, multiexp_vartime, algorithm::Hram}; +use crate::{CurveError, Curve, multiexp_vartime, algorithm::Hram, tests::curve::test_curve}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Secp256k1; @@ -105,3 +107,8 @@ impl Hram for TestHram { ) } } + +#[test] +fn secp256k1_curve() { + test_curve::<_, Secp256k1>(&mut OsRng); +} diff --git a/crypto/frost/tests/key_gen_and_sign.rs b/crypto/frost/src/tests/mod.rs similarity index 50% rename from crypto/frost/tests/key_gen_and_sign.rs rename to crypto/frost/src/tests/mod.rs index 201bc005..eb747d31 100644 --- a/crypto/frost/tests/key_gen_and_sign.rs +++ b/crypto/frost/src/tests/mod.rs @@ -1,21 +1,29 @@ use std::{rc::Rc, collections::HashMap}; -use rand::rngs::OsRng; +use rand_core::{RngCore, CryptoRng}; -use frost::{ +use crate::{ Curve, MultisigParams, MultisigKeys, key_gen, - algorithm::{Algorithm, Schnorr, SchnorrSignature}, + algorithm::Algorithm, sign::{StateMachine, AlgorithmMachine} }; -mod common; -use common::{Secp256k1, TestHram}; +// Internal tests +mod schnorr; -const PARTICIPANTS: u16 = 8; +// Test suites for public usage +pub mod curve; -fn clone_without( +// Literal test definitions to run during `cargo test` +#[cfg(test)] +mod literal; + +pub const PARTICIPANTS: u16 = 5; +pub const THRESHOLD: u16 = ((PARTICIPANTS / 3) * 2) + 1; + +pub fn clone_without( map: &HashMap, without: &K ) -> HashMap { @@ -24,7 +32,9 @@ fn clone_without( res } -fn key_gen() -> HashMap>> { +pub fn key_gen( + rng: &mut R +) -> HashMap>> { let mut params = HashMap::new(); let mut machines = HashMap::new(); @@ -33,7 +43,7 @@ fn key_gen() -> HashMap>> { params.insert( i, MultisigParams::new( - ((PARTICIPANTS / 3) * 2) + 1, + THRESHOLD, PARTICIPANTS, i ).unwrap() @@ -47,7 +57,7 @@ fn key_gen() -> HashMap>> { ); commitments.insert( i, - machines.get_mut(&i).unwrap().generate_coefficients(&mut OsRng).unwrap() + machines.get_mut(&i).unwrap().generate_coefficients(rng).unwrap() ); } @@ -55,7 +65,7 @@ fn key_gen() -> HashMap>> { for (l, machine) in machines.iter_mut() { secret_shares.insert( *l, - machine.generate_secret_shares(&mut OsRng, clone_without(&commitments, l)).unwrap() + machine.generate_secret_shares(rng, clone_without(&commitments, l)).unwrap() ); } @@ -72,55 +82,69 @@ fn key_gen() -> HashMap>> { } let these_keys = machine.complete(our_secret_shares).unwrap(); - // Test serialization - assert_eq!( - MultisigKeys::::deserialize(&these_keys.serialize()).unwrap(), - these_keys - ); - + // Verify the verification_shares are agreed upon if verification_shares.is_none() { verification_shares = Some(these_keys.verification_shares()); } assert_eq!(verification_shares.as_ref().unwrap(), &these_keys.verification_shares()); + // Verify the group keys are agreed upon if group_key.is_none() { group_key = Some(these_keys.group_key()); } assert_eq!(group_key.unwrap(), these_keys.group_key()); - keys.insert(*i, Rc::new(these_keys.clone())); + keys.insert(*i, Rc::new(these_keys)); } keys } -fn sign>>( +pub fn algorithm_machines>( + rng: &mut R, algorithm: A, - keys: &HashMap>> -) { - let t = keys[&1].params().t(); - let mut machines = HashMap::new(); + keys: &HashMap>>, +) -> HashMap> { + let mut included = vec![]; + while included.len() < usize::from(keys[&1].params().t()) { + let n = u16::try_from((rng.next_u64() % u64::try_from(keys.len()).unwrap()) + 1).unwrap(); + if included.contains(&n) { + continue; + } + included.push(n); + } + + keys.iter().filter_map( + |(i, keys)| if included.contains(&i) { + Some(( + *i, + AlgorithmMachine::new( + algorithm.clone(), + keys.clone(), + &included.clone() + ).unwrap() + )) + } else { + None + } + ).collect() +} + +pub fn sign( + rng: &mut R, + mut machines: HashMap, + msg: &[u8] +) -> M::Signature { let mut commitments = HashMap::new(); - for i in 1 ..= t { - machines.insert( - i, - AlgorithmMachine::new( - algorithm.clone(), - keys[&i].clone(), - &(1 ..= t).collect::>() - ).unwrap() - ); - commitments.insert( - i, - machines.get_mut(&i).unwrap().preprocess(&mut OsRng).unwrap() - ); + for (i, machine) in machines.iter_mut() { + commitments.insert(*i, machine.preprocess(rng).unwrap()); } let mut shares = HashMap::new(); for (i, machine) in machines.iter_mut() { shares.insert( *i, - machine.sign(clone_without(&commitments, i), b"Hello World").unwrap() + machine.sign(clone_without(&commitments, i), msg).unwrap() ); } @@ -128,20 +152,9 @@ fn sign>>( for (i, machine) in machines.iter_mut() { let sig = machine.complete(clone_without(&shares, i)).unwrap(); if signature.is_none() { - signature = Some(sig); + signature = Some(sig.clone()); } - assert_eq!(sig, signature.unwrap()); + assert_eq!(&sig, signature.as_ref().unwrap()); } -} - -#[test] -fn key_gen_and_sign() { - let mut keys = key_gen::(); - - sign(Schnorr::::new(), &keys); - - for i in 1 ..= u16::try_from(PARTICIPANTS).unwrap() { - keys.insert(i, Rc::new(keys[&i].offset(Secp256k1::hash_to_F(b"offset")))); - } - sign(Schnorr::::new(), &keys); + signature.unwrap() } diff --git a/crypto/frost/src/tests/schnorr.rs b/crypto/frost/src/tests/schnorr.rs new file mode 100644 index 00000000..15bdaa48 --- /dev/null +++ b/crypto/frost/src/tests/schnorr.rs @@ -0,0 +1,32 @@ +use rand_core::{RngCore, CryptoRng}; + +use ff::Field; + +use crate::{Curve, schnorr, algorithm::SchnorrSignature}; + +pub(crate) fn sign(rng: &mut R) { + let private_key = C::F::random(&mut *rng); + let nonce = C::F::random(&mut *rng); + let challenge = C::F::random(rng); // Doesn't bother to craft an HRAM + assert!( + schnorr::verify::( + C::generator_table() * private_key, + challenge, + &schnorr::sign(private_key, nonce, challenge) + ) + ); +} + +// The above sign function verifies signing works +// This verifies invalid signatures don't pass, using zero signatures, which should effectively be +// random +pub(crate) fn verify(rng: &mut R) { + assert!( + !schnorr::verify::( + C::generator_table() * C::F::random(&mut *rng), + C::F::random(rng), + &SchnorrSignature { R: C::generator_table() * C::F::zero(), s: C::F::zero() } + ) + ); +} +