Skip to content

Commit

Permalink
Tristan/war 589 sylow code coverage (#28)
Browse files Browse the repository at this point in the history
* equality + select tests on fields

* first pass on coverage

* second pass on coverage

* added fuzzing and invariant testing

* added doctests

* clippy + fmt

* typos

* added const time check for signature generation

* added const time check for pairing

* fmt
  • Loading branch information
trbritt authored Aug 27, 2024
1 parent 96d5013 commit f8c2222
Show file tree
Hide file tree
Showing 13 changed files with 691 additions and 75 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ criterion = { version = "0.5", features = ["html_reports"] }
tracing-subscriber = "0.3.18"
tracing = "0.1.40"
confy = "0.6.1"
proptest = "1.5.0"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
dudect-bencher = "0.6.0"
rand = "0.8.5"

[[bench]]
name = "mod"
Expand Down
47 changes: 47 additions & 0 deletions examples/const_time_pairing_check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use dudect_bencher::{ctbench_main, BenchRng, Class, CtRunner};
use rand::Rng;
use sylow::{sign, verify, KeyPair};

const MIN_MSG_LEN: usize = 1;
const MAX_MSG_LEN: usize = 1024;

fn generate_random_message(rng: &mut BenchRng) -> Vec<u8> {
let len = rng.gen_range(MIN_MSG_LEN..=MAX_MSG_LEN);
(0..len).map(|_| rng.gen::<u8>()).collect()
}

fn bench_pairing_generation(runner: &mut CtRunner, rng: &mut BenchRng) {
let mut inputs = Vec::new();
let mut classes = Vec::new();

// Make 100,000 inputs on each run
for _ in 0..10_000 {
inputs.push(generate_random_message(rng));
// Randomly pick which distribution this example belongs to
if rng.gen::<bool>() {
classes.push(Class::Left);
} else {
classes.push(Class::Right);
}
}

for (msg, class) in inputs.into_iter().zip(classes.into_iter()) {
runner.run_one(class, || {
let key_pair = KeyPair::generate();
match sign(&key_pair.secret_key, &msg) {
Ok(signature) => {
// Verify the signature
match verify(&key_pair.public_key, &msg, &signature) {
Ok(is_valid) => {
assert!(is_valid, "Signature verification failed");
}
Err(e) => println!("Verification error: {:?}", e),
}
}
Err(e) => println!("Signing error: {:?}", e),
}
});
}
}

ctbench_main!(bench_pairing_generation);
43 changes: 43 additions & 0 deletions examples/const_time_signature_check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use dudect_bencher::{ctbench_main, BenchRng, Class, CtRunner};
use rand::Rng;
use sha3::Keccak256;
use sylow::{FieldExtensionTrait, Fp, Fr, G1Projective, GroupTrait, XMDExpander};

const DST: &[u8; 30] = b"WARLOCK-CHAOS-V01-CS01-SHA-256";
const K: u64 = 128;
const MIN_MSG_LEN: usize = 1;
const MAX_MSG_LEN: usize = 1024;

fn generate_random_message(rng: &mut BenchRng) -> Vec<u8> {
let len = rng.gen_range(MIN_MSG_LEN..=MAX_MSG_LEN);
(0..len).map(|_| rng.gen::<u8>()).collect()
}

fn bench_signature_generation(runner: &mut CtRunner, rng: &mut BenchRng) {
let mut inputs = Vec::new();
let mut classes = Vec::new();

let expander = XMDExpander::<Keccak256>::new(DST, K);
let private_key = Fp::new(Fr::rand(rng).value());

// Make 100,000 inputs on each run
for _ in 0..100_000 {
inputs.push(generate_random_message(rng));
// Randomly pick which distribution this example belongs to
if rng.gen::<bool>() {
classes.push(Class::Left);
} else {
classes.push(Class::Right);
}
}

for (msg, class) in inputs.into_iter().zip(classes.into_iter()) {
runner.run_one(class, || {
if let Ok(hashed_message) = G1Projective::hash_to_curve(&expander, &msg) {
let _signature = hashed_message * private_key;
}
});
}
}

ctbench_main!(bench_signature_generation);
207 changes: 207 additions & 0 deletions src/fields/fp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1101,4 +1101,211 @@ mod tests {
// As per Feldman VSS, I believe we need to select appropriate p and q.
}
}

#[test]
fn test_conditional_selection() {
let a = create_field([1, 2, 3, 4]);
let b = create_field([5, 6, 7, 8]);
assert_eq!(Fp::conditional_select(&a, &b, Choice::from(0u8)), a);
assert_eq!(Fp::conditional_select(&a, &b, Choice::from(1u8)), b);
}
#[test]
fn test_conversion() {
let b = Fr::new(U256::from_words([1, 2, 3, 4]));
let c = Fp::from(&b);
assert_eq!(c.value().to_words(), [1, 2, 3, 4]);
}

#[test]
fn test_equality() {
fn is_equal(a: &Fp, b: &Fp) -> bool {
let eq = a == b;
let ct_eq = a.ct_eq(b);

assert_eq!(eq, bool::from(ct_eq));
eq
}
assert!(is_equal(
&create_field([1, 2, 3, 4]),
&create_field([1, 2, 3, 4])
));
assert!(!is_equal(
&create_field([9, 2, 3, 4]),
&create_field([1, 2, 3, 4])
));
assert!(!is_equal(
&create_field([1, 9, 3, 4]),
&create_field([1, 2, 3, 4])
));
assert!(!is_equal(
&create_field([1, 2, 9, 4]),
&create_field([1, 2, 3, 4])
));
assert!(!is_equal(
&create_field([1, 2, 3, 9]),
&create_field([1, 2, 3, 4])
));
}

#[test]
fn test_characteristic() {
let char = Fp::characteristic() - U256::from(1u64);
assert_eq!(char, (BN254_FP_MODULUS - Fp::ONE).value());
}

#[test]
fn test_from_u64() {
for i in 0..255 {
let res = Fp::from(i);
assert_eq!(res.value().to_words(), [i, 0, 0, 0]);
}
}

#[test]
fn test_debug() {
let res = Fp::new(U256::from_words([
0x0,
0x97816A916871CA8D,
0x0,
0x30644E02E131A029,
]));
assert_eq!(
format!("{:?}", res),
"Fp { U256: Uint(0x30644E02E131A029000000000000000097816A916871CA8D0000000000000000) }"
);
}

mod euclid_tests {
use super::*;
#[test]
fn test_div_euclid() {
let test_cases = [
(10, 3, 3), // Normal case
(10, 2, 5), // Exact division
(0, 5, 0), // Zero dividend
(10, 1, 10), // Divisor is 1
(10, 11, 0), // Divisor larger than dividend
];
for (a, b, expected) in test_cases.iter() {
let a = Fp::from(*a as u64);
let b = Fp::from(*b as u64);
let expected = Fp::from(*expected as u64);
assert_eq!(
a.div_euclid(&b),
expected,
"Failed for {} div_euclid {}",
a.value(),
b.value()
);
}
}
#[test]
fn test_rem_euclid() {
let test_cases = [
(10, 3, 1), // Normal case
(10, 2, 0), // No remainder
(0, 5, 0), // Zero dividend
(10, 1, 0), // Divisor is 1
(10, 11, 10), // Divisor larger than dividend
];
for (a, b, expected) in test_cases.iter() {
let a = Fp::from(*a as u64);
let b = Fp::from(*b as u64);
let expected = Fp::from(*expected as u64);
assert_eq!(
a.rem_euclid(&b),
expected,
"Failed for {} rem_euclid {}",
a.value(),
b.value()
);
}
}
}
#[test]
fn assignment_tests() {
let mut a = Fp::from(10);
let b = Fp::from(5);

// addition
let c = a + b;
a += b;

assert_eq!(c, a, "Addition assignment failed");

// subtraction
let mut a = Fp::from(10);
let c = a - b;
a -= b;
assert_eq!(c, a, "Subtraction assignment failed");

// multiplication
let mut a = Fp::from(10);
let c = a * b;
a *= b;
assert_eq!(c, a, "Multiplication assignment failed");

// division
let mut a = Fp::from(10);
let c = a / b;
a /= b;
assert_eq!(c, a, "Division assignment failed");
}

mod hash_tests {
use super::*;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

fn calculate_hash<T: Hash>(t: &T) -> u64 {
let mut s = DefaultHasher::new();
t.hash(&mut s);
s.finish()
}
#[test]
fn test_equality() {
let v1 = Fp::from(123456789u64);
let v2 = Fp::from(123456789u64);

assert_eq!(
calculate_hash(&v1),
calculate_hash(&v2),
"Hash not consistent for equal values"
);
}
#[test]
fn test_hash_set_insertion() {
use std::collections::HashSet;
let mut set = HashSet::new();
let v1 = Fp::from(123456789u64);
let v2 = Fp::from(123456789u64);

set.insert(v1);
assert!(set.contains(&v2), "HashSet insertion failed");
assert!(
!set.insert(v1),
"Shouldn't be able to add the same element twice"
);
}
}

#[test]
fn test_curve_constant() {
let curve_constant = <Fp as FieldExtensionTrait<1, 1>>::curve_constant();
let also_curve_constant = <Fp as FieldExtensionTrait<2, 2>>::curve_constant();
assert!(
bool::from(curve_constant.ct_eq(&Fp::THREE) & also_curve_constant.ct_eq(&Fp::THREE)),
"Curve constant is not 3"
);
}

#[test]
fn test_frobenius() {
let a = Fp::from(10);
assert_eq!(
Fp::ONE,
a.frobenius(1).frobenius(1),
"Frobenius squared should be equal to one"
);
}
}
58 changes: 58 additions & 0 deletions src/fields/fp12.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ impl Fp12 {
mod tests {
use super::*;
use crypto_bigint::{rand_core::OsRng, U256};
use subtle::ConstantTimeEq;

fn create_field(value: [u64; 4]) -> Fp {
Fp::new(U256::from_words(value))
Expand Down Expand Up @@ -583,4 +584,61 @@ mod tests {
let _ = a / b;
}
}
#[test]
fn test_conditional_select() {
let a = Fp12::rand(&mut OsRng);
let b = Fp12::rand(&mut OsRng);

assert_eq!(
a,
Fp12::conditional_select(&a, &b, Choice::from(0u8)),
"Conditional select failed when choice is 0"
);
assert_eq!(
b,
Fp12::conditional_select(&a, &b, Choice::from(1u8)),
"Conditional select failed when choice is 1"
);
let one = Fp12::one();
assert!(one.is_one(), "One is not one!");
}
#[test]
fn assignment_tests() {
let mut a = Fp12::from(10);
let b = Fp12::from(5);

// addition
let c = a + b;
a += b;

assert_eq!(c, a, "Addition assignment failed");

// subtraction
let mut a = Fp12::from(10);
let c = a - b;
a -= b;
assert_eq!(c, a, "Subtraction assignment failed");

// multiplication
let mut a = Fp12::from(10);
let c = a * b;
a *= b;
assert_eq!(c, a, "Multiplication assignment failed");

// division
let mut a = Fp12::from(10);
let c = a / b;
a /= b;
assert_eq!(c, a, "Division assignment failed");
}
#[test]
fn test_curve_constant() {
let curve_constant = Fp12::curve_constant();

let tmp = Fp12::from(3);
assert!(
bool::from(curve_constant.ct_eq(&tmp)),
"Curve constant is not 3"
);
}
}
Loading

0 comments on commit f8c2222

Please sign in to comment.