From 445bb3786e1f9aec6dbb03089419fdce1b208c73 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 24 Dec 2022 15:09:09 -0500 Subject: [PATCH] Add a dedicated crate for testing ff/group implementors Provides extensive testing for dalek-ff-group and ed448. Also includes a fix for an observed bug in ed448. --- Cargo.lock | 11 + Cargo.toml | 1 + crypto/dalek-ff-group/Cargo.toml | 3 + crypto/dalek-ff-group/src/field.rs | 182 +------------- crypto/dalek-ff-group/src/lib.rs | 89 +------ crypto/ed448/Cargo.toml | 2 + crypto/ed448/src/backend.rs | 29 +-- crypto/ed448/src/field.rs | 29 +-- crypto/ed448/src/lib.rs | 1 + crypto/ed448/src/point.rs | 48 ++-- crypto/ed448/src/scalar.rs | 5 +- crypto/ff-group-tests/Cargo.toml | 20 ++ crypto/ff-group-tests/LICENSE | 21 ++ crypto/ff-group-tests/README.md | 5 + crypto/ff-group-tests/src/field.rs | 122 ++++++++++ crypto/ff-group-tests/src/group.rs | 169 +++++++++++++ crypto/ff-group-tests/src/lib.rs | 9 + crypto/ff-group-tests/src/prime_field.rs | 291 +++++++++++++++++++++++ 18 files changed, 701 insertions(+), 336 deletions(-) create mode 100644 crypto/ff-group-tests/Cargo.toml create mode 100644 crypto/ff-group-tests/LICENSE create mode 100644 crypto/ff-group-tests/README.md create mode 100644 crypto/ff-group-tests/src/field.rs create mode 100644 crypto/ff-group-tests/src/group.rs create mode 100644 crypto/ff-group-tests/src/lib.rs create mode 100644 crypto/ff-group-tests/src/prime_field.rs diff --git a/Cargo.lock b/Cargo.lock index e1a05fe6..39e8deab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1512,6 +1512,7 @@ dependencies = [ "curve25519-dalek 3.2.0", "digest 0.10.6", "ff", + "ff-group-tests", "group", "rand_core 0.6.4", "subtle", @@ -2331,6 +2332,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "ff-group-tests" +version = "0.12.0" +dependencies = [ + "group", + "k256", + "p256", +] + [[package]] name = "fiat-crypto" version = "0.1.17" @@ -4570,6 +4580,7 @@ dependencies = [ "dalek-ff-group", "digest 0.10.6", "ff", + "ff-group-tests", "generic-array 0.14.6", "group", "hex", diff --git a/Cargo.toml b/Cargo.toml index 4fce7c27..df9adc65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "crypto/transcript", + "crypto/ff-group-tests", "crypto/dalek-ff-group", "crypto/ed448", "crypto/ciphersuite", diff --git a/crypto/dalek-ff-group/Cargo.toml b/crypto/dalek-ff-group/Cargo.toml index df22f767..aea5f2a9 100644 --- a/crypto/dalek-ff-group/Cargo.toml +++ b/crypto/dalek-ff-group/Cargo.toml @@ -24,3 +24,6 @@ group = "0.12" crypto-bigint = "0.4" curve25519-dalek = "3.2" + +[dev-dependencies] +ff-group-tests = { path = "../ff-group-tests" } diff --git a/crypto/dalek-ff-group/src/field.rs b/crypto/dalek-ff-group/src/field.rs index e0648ecb..78d6633d 100644 --- a/crypto/dalek-ff-group/src/field.rs +++ b/crypto/dalek-ff-group/src/field.rs @@ -99,30 +99,6 @@ impl Field for FieldElement { let candidate = Self::conditional_select(&tv2, &tv1, tv1.square().ct_eq(self)); CtOption::new(candidate, candidate.square().ct_eq(self)) } - - fn is_zero(&self) -> Choice { - self.0.ct_eq(&U256::ZERO) - } - - fn cube(&self) -> Self { - self.square() * self - } - - fn pow_vartime>(&self, exp: S) -> Self { - let mut sum = Self::one(); - let mut accum = *self; - for (_, num) in exp.as_ref().iter().enumerate() { - let mut num = *num; - for _ in 0 .. 64 { - if (num & 1) == 1 { - sum *= accum; - } - num >>= 1; - accum *= accum; - } - } - sum - } } impl PrimeField for FieldElement { @@ -218,160 +194,6 @@ impl FieldElement { } #[test] -fn test_s() { - // "This is the number of leading zero bits in the little-endian bit representation of - // `modulus - 1`." - let mut s = 0; - for b in (FieldElement::zero() - FieldElement::one()).to_le_bits() { - if b { - break; - } - s += 1; - } - assert_eq!(s, FieldElement::S); -} - -#[test] -fn test_root_of_unity() { - // "It can be calculated by exponentiating `Self::multiplicative_generator` by `t`, where - // `t = (modulus - 1) >> Self::S`." - let t = FieldElement::zero() - FieldElement::one(); - let mut bytes = t.to_repr(); - for _ in 0 .. FieldElement::S { - bytes[0] >>= 1; - for b in 1 .. 32 { - // Shift the dropped but down a byte - bytes[b - 1] |= (bytes[b] & 1) << 7; - // Shift the byte - bytes[b] >>= 1; - } - } - let t = FieldElement::from_repr(bytes).unwrap(); - - assert_eq!(FieldElement::multiplicative_generator().pow(t), FieldElement::root_of_unity()); - assert_eq!( - FieldElement::root_of_unity() - .pow(FieldElement::from(2u64).pow(FieldElement::from(FieldElement::S))), - FieldElement::one() - ); -} - -#[test] -fn test_conditional_negate() { - let one = FieldElement::one(); - let true_choice = 1.into(); - let false_choice = 0.into(); - - let mut var = one; - - var.conditional_negate(false_choice); - assert_eq!(var, FieldElement::one()); - - var.conditional_negate(true_choice); - assert_eq!(var, -FieldElement::one()); - - var.conditional_negate(false_choice); - assert_eq!(var, -FieldElement::one()); -} - -#[test] -fn test_edwards_d() { - // TODO: Generate the constant with this when const fn mul_mod is available, removing the need - // for this test - let a = -FieldElement::from(121665u32); - let b = FieldElement::from(121666u32); - assert_eq!(EDWARDS_D, a * b.invert().unwrap()); -} - -#[test] -fn test_sqrt_m1() { - // TODO: Ideally, tlike EDWARDS_D, this would be calculated undder const. A const pow is just - // even more unlikely than a const mul... - let sqrt_m1 = MODULUS.saturating_sub(&U256::from_u8(1)).wrapping_div(&U256::from_u8(4)); - let sqrt_m1 = - FieldElement::one().double().pow(FieldElement::from_repr(sqrt_m1.to_le_bytes()).unwrap()); - assert_eq!(SQRT_M1, sqrt_m1); -} - -#[test] -fn test_is_odd() { - assert_eq!(0, FieldElement::zero().is_odd().unwrap_u8()); - assert_eq!(1, FieldElement::one().is_odd().unwrap_u8()); - assert_eq!(0, FieldElement::one().double().is_odd().unwrap_u8()); - - // 0 is even, yet the modulus is odd - // -1 moves to the even value before the modulus - assert_eq!(0, (-FieldElement::one()).is_odd().unwrap_u8()); - assert_eq!(1, (-FieldElement::one().double()).is_odd().unwrap_u8()); -} - -#[test] -fn test_mul() { - assert_eq!(FieldElement(MODULUS) * FieldElement::one(), FieldElement::zero()); - assert_eq!(FieldElement(MODULUS) * FieldElement::one().double(), FieldElement::zero()); - assert_eq!(SQRT_M1.square(), -FieldElement::one()); -} - -#[test] -fn test_sqrt() { - assert_eq!(FieldElement::zero().sqrt().unwrap(), FieldElement::zero()); - assert_eq!(FieldElement::one().sqrt().unwrap(), FieldElement::one()); - for _ in 0 .. 10 { - let mut elem; - while { - elem = FieldElement::random(&mut rand_core::OsRng); - elem.sqrt().is_none().into() - } {} - assert_eq!(elem.sqrt().unwrap().square(), elem); - } -} - -#[test] -fn test_pow() { - let base = FieldElement::from(0b11100101u64); - assert_eq!(base.pow(FieldElement::zero()), FieldElement::one()); - assert_eq!(base.pow_vartime(&[]), FieldElement::one()); - assert_eq!(base.pow_vartime(&[0]), FieldElement::one()); - assert_eq!(base.pow_vartime(&[0, 0]), FieldElement::one()); - - assert_eq!(base.pow(FieldElement::one()), base); - assert_eq!(base.pow_vartime(&[1]), base); - assert_eq!(base.pow_vartime(&[1, 0]), base); - - let one_65 = FieldElement::from(u64::MAX) + FieldElement::one(); - assert_eq!(base.pow_vartime(&[0, 1]), base.pow(one_65)); - assert_eq!(base.pow_vartime(&[1, 1]), base.pow(one_65 + FieldElement::one())); -} - -#[test] -fn test_sqrt_ratio_i() { - let zero = FieldElement::zero(); - let one = FieldElement::one(); - let two = one + one; - let three = two + one; - - let (choice, sqrt) = FieldElement::sqrt_ratio_i(zero, zero); - assert_eq!(sqrt, zero); - assert_eq!(sqrt.is_odd().unwrap_u8(), 0); - assert_eq!(choice.unwrap_u8(), 1); - - let (choice, sqrt) = FieldElement::sqrt_ratio_i(one, zero); - assert_eq!(sqrt, zero); - assert_eq!(sqrt.is_odd().unwrap_u8(), 0); - assert_eq!(choice.unwrap_u8(), 0); - - let (choice, sqrt) = FieldElement::sqrt_ratio_i(two, one); - assert_eq!(sqrt.square(), two * SQRT_M1); - assert_eq!(sqrt.is_odd().unwrap_u8(), 0); - assert_eq!(choice.unwrap_u8(), 0); - - let (choice, sqrt) = FieldElement::sqrt_ratio_i(three, one); - assert_eq!(sqrt.square(), three); - assert_eq!(sqrt.is_odd().unwrap_u8(), 0); - assert_eq!(choice.unwrap_u8(), 1); - - let (choice, sqrt) = FieldElement::sqrt_ratio_i(one, three); - assert_eq!(sqrt.square() * three, one); - assert_eq!(sqrt.is_odd().unwrap_u8(), 0); - assert_eq!(choice.unwrap_u8(), 1); +fn test_field() { + ff_group_tests::prime_field::test_prime_field_bits::(); } diff --git a/crypto/dalek-ff-group/src/lib.rs b/crypto/dalek-ff-group/src/lib.rs index 8497a7ca..fb66b790 100644 --- a/crypto/dalek-ff-group/src/lib.rs +++ b/crypto/dalek-ff-group/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![no_std] use core::{ @@ -256,27 +257,6 @@ impl Field for Scalar { let candidate = Self::conditional_select(&tv2, &tv1, tv1.square().ct_eq(self)); CtOption::new(candidate, candidate.square().ct_eq(self)) } - fn is_zero(&self) -> Choice { - self.0.ct_eq(&DScalar::zero()) - } - fn cube(&self) -> Self { - *self * self * self - } - fn pow_vartime>(&self, exp: S) -> Self { - let mut sum = Self::one(); - let mut accum = *self; - for (_, num) in exp.as_ref().iter().enumerate() { - let mut num = *num; - for _ in 0 .. 64 { - if (num & 1) == 1 { - sum *= accum; - } - num >>= 1; - accum *= accum; - } - } - sum - } } impl PrimeField for Scalar { @@ -454,70 +434,11 @@ dalek_group!( ); #[test] -fn test_s() { - // "This is the number of leading zero bits in the little-endian bit representation of - // `modulus - 1`." - let mut s = 0; - for b in (Scalar::zero() - Scalar::one()).to_le_bits() { - if b { - break; - } - s += 1; - } - assert_eq!(s, Scalar::S); +fn test_ed25519_group() { + ff_group_tests::group::test_prime_group_bits::(); } #[test] -fn test_root_of_unity() { - // "It can be calculated by exponentiating `Self::multiplicative_generator` by `t`, where - // `t = (modulus - 1) >> Self::S`." - let t = Scalar::zero() - Scalar::one(); - let mut bytes = t.to_repr(); - for _ in 0 .. Scalar::S { - bytes[0] >>= 1; - for b in 1 .. 32 { - // Shift the dropped but down a byte - bytes[b - 1] |= (bytes[b] & 1) << 7; - // Shift the byte - bytes[b] >>= 1; - } - } - let t = Scalar::from_repr(bytes).unwrap(); - - assert_eq!(Scalar::multiplicative_generator().pow(t), Scalar::root_of_unity()); - assert_eq!( - Scalar::root_of_unity().pow(Scalar::from(2u64).pow(Scalar::from(Scalar::S))), - Scalar::one() - ); -} - -#[test] -fn test_sqrt() { - assert_eq!(Scalar::zero().sqrt().unwrap(), Scalar::zero()); - assert_eq!(Scalar::one().sqrt().unwrap(), Scalar::one()); - for _ in 0 .. 10 { - let mut elem; - while { - elem = Scalar::random(&mut rand_core::OsRng); - elem.sqrt().is_none().into() - } {} - assert_eq!(elem.sqrt().unwrap().square(), elem); - } -} - -#[test] -fn test_pow() { - let base = Scalar::from(0b11100101u64); - assert_eq!(base.pow(Scalar::zero()), Scalar::one()); - assert_eq!(base.pow_vartime(&[]), Scalar::one()); - assert_eq!(base.pow_vartime(&[0]), Scalar::one()); - assert_eq!(base.pow_vartime(&[0, 0]), Scalar::one()); - - assert_eq!(base.pow(Scalar::one()), base); - assert_eq!(base.pow_vartime(&[1]), base); - assert_eq!(base.pow_vartime(&[1, 0]), base); - - let one_65 = Scalar::from(u64::MAX) + Scalar::one(); - assert_eq!(base.pow_vartime(&[0, 1]), base.pow(one_65)); - assert_eq!(base.pow_vartime(&[1, 1]), base.pow(one_65 + Scalar::one())); +fn test_ristretto_group() { + ff_group_tests::group::test_prime_group_bits::(); } diff --git a/crypto/ed448/Cargo.toml b/crypto/ed448/Cargo.toml index 43a01fbe..207d4bde 100644 --- a/crypto/ed448/Cargo.toml +++ b/crypto/ed448/Cargo.toml @@ -32,3 +32,5 @@ dalek-ff-group = { path = "../dalek-ff-group", version = "^0.1.2" } [dev-dependencies] hex-literal = "0.3" hex = "0.4" + +ff-group-tests = { path = "../ff-group-tests" } diff --git a/crypto/ed448/src/backend.rs b/crypto/ed448/src/backend.rs index b63902f7..484df359 100644 --- a/crypto/ed448/src/backend.rs +++ b/crypto/ed448/src/backend.rs @@ -38,7 +38,7 @@ macro_rules! field { impl Neg for $FieldName { type Output = $FieldName; fn neg(self) -> $FieldName { - $MODULUS - self + Self(self.0.neg_mod(&$MODULUS.0)) } } @@ -104,29 +104,10 @@ macro_rules! field { } fn sqrt(&self) -> CtOption { - unimplemented!() - } - - fn is_zero(&self) -> Choice { - self.0.ct_eq(&U512::ZERO) - } - fn cube(&self) -> Self { - self.square() * self - } - fn pow_vartime>(&self, exp: S) -> Self { - let mut sum = Self::one(); - let mut accum = *self; - for (_, num) in exp.as_ref().iter().enumerate() { - let mut num = *num; - for _ in 0 .. 64 { - if (num & 1) == 1 { - sum *= accum; - } - num >>= 1; - accum *= accum; - } - } - sum + const MOD_1_4: $FieldName = + Self($MODULUS.0.saturating_add(&U512::from_u8(1)).wrapping_div(&U512::from_u8(4))); + let res = self.pow(MOD_1_4); + CtOption::new(res, res.square().ct_eq(self)) } } diff --git a/crypto/ed448/src/field.rs b/crypto/ed448/src/field.rs index 1b5ca571..69e4ecc9 100644 --- a/crypto/ed448/src/field.rs +++ b/crypto/ed448/src/field.rs @@ -30,30 +30,7 @@ pub(crate) const Q_4: FieldElement = field!(FieldElement, MODULUS, WIDE_MODULUS, 448); #[test] -fn repr() { - assert_eq!(FieldElement::from_repr(FieldElement::one().to_repr()).unwrap(), FieldElement::one()); -} - -#[test] -fn one_two() { - assert_eq!(FieldElement::one() * FieldElement::one().double(), FieldElement::from(2u8)); - assert_eq!( - FieldElement::from_repr(FieldElement::from(2u8).to_repr()).unwrap(), - FieldElement::from(2u8) - ); -} - -#[test] -fn pow() { - assert_eq!(FieldElement::one().pow(FieldElement::one()), FieldElement::one()); - let two = FieldElement::one().double(); - assert_eq!(two.pow(two), two.double()); - - let three = two + FieldElement::one(); - assert_eq!(three.pow(three), three * three * three); -} - -#[test] -fn invert() { - assert_eq!(FieldElement::one().invert().unwrap(), FieldElement::one()); +fn test_field() { + // TODO: Move to test_prime_field_bits once the impl is finished + ff_group_tests::prime_field::test_prime_field::(); } diff --git a/crypto/ed448/src/lib.rs b/crypto/ed448/src/lib.rs index 4afeebde..81823aac 100644 --- a/crypto/ed448/src/lib.rs +++ b/crypto/ed448/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![no_std] mod backend; diff --git a/crypto/ed448/src/point.rs b/crypto/ed448/src/point.rs index 3949b97a..505abbdc 100644 --- a/crypto/ed448/src/point.rs +++ b/crypto/ed448/src/point.rs @@ -309,31 +309,38 @@ impl GroupEncoding for Point { impl PrimeGroup for Point {} #[test] -fn identity() { - assert_eq!(Point::from_bytes(&Point::identity().to_bytes()).unwrap(), Point::identity()); - assert_eq!(Point::identity() + Point::identity(), Point::identity()); +fn test_group() { + // TODO: Move to test_prime_group_bits once the impl is finished + use ff_group_tests::group::*; + + test_eq::(); + test_identity::(); + test_generator::(); + test_double::(); + test_add::(); + test_sum::(); + test_neg::(); + test_sub::(); + test_mul::(); + test_order::(); + + test_encoding::(); } -#[test] -fn addition_multiplication_serialization() { - let mut accum = Point::identity(); - for x in 1 .. 10 { - accum += Point::generator(); - let mul = Point::generator() * Scalar::from(u8::try_from(x).unwrap()); - assert_eq!(accum, mul); - assert_eq!(Point::from_bytes(&mul.to_bytes()).unwrap(), mul); - } -} - -#[rustfmt::skip] #[test] fn torsion() { + use generic_array::GenericArray; + // Uses the originally suggested generator which had torsion - let old_y = FieldElement::from_repr( - hex_literal::hex!( - "12796c1532041525945f322e414d434467cfd5c57c9a9af2473b27758c921c4828b277ca5f2891fc4f3d79afdf29a64c72fb28b59c16fa5100" - ).into(), - ) + let old_y = FieldElement::from_repr(*GenericArray::from_slice( + &hex::decode( + "\ +12796c1532041525945f322e414d434467cfd5c57c9a9af2473b2775\ +8c921c4828b277ca5f2891fc4f3d79afdf29a64c72fb28b59c16fa51\ +00", + ) + .unwrap(), + )) .unwrap(); let old = Point { x: -recover_x(old_y).unwrap(), y: old_y, z: FieldElement::one() }; assert!(bool::from(!old.is_torsion_free())); @@ -382,6 +389,7 @@ a401cd9df24632adfe6b418dc942d8a091817dd8bd70e1c72ba52f3c\ ); } +// Checks random won't infinitely loop #[test] fn random() { Point::random(&mut rand_core::OsRng); diff --git a/crypto/ed448/src/scalar.rs b/crypto/ed448/src/scalar.rs index 72b7b2fa..406ab8df 100644 --- a/crypto/ed448/src/scalar.rs +++ b/crypto/ed448/src/scalar.rs @@ -33,6 +33,7 @@ impl Scalar { } #[test] -fn invert() { - assert_eq!(Scalar::one().invert().unwrap(), Scalar::one()); +fn test_scalar_field() { + // TODO: Move to test_prime_field_bits once the impl is finished + ff_group_tests::prime_field::test_prime_field::(); } diff --git a/crypto/ff-group-tests/Cargo.toml b/crypto/ff-group-tests/Cargo.toml new file mode 100644 index 00000000..2c2897da --- /dev/null +++ b/crypto/ff-group-tests/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "ff-group-tests" +version = "0.12.0" +description = "A collection of sanity tests for implementors of ff/group APIs" +license = "MIT" +repository = "https://github.com/serai-dex/serai/tree/develop/crypto/ff-group-tests" +authors = ["Luke Parker "] +keywords = ["ff", "group", "ecc"] +edition = "2021" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +group = "0.12" + +[dev-dependencies] +k256 = { version = "0.11", features = ["bits"] } +p256 = { version = "0.11", features = ["bits"] } diff --git a/crypto/ff-group-tests/LICENSE b/crypto/ff-group-tests/LICENSE new file mode 100644 index 00000000..f05b748b --- /dev/null +++ b/crypto/ff-group-tests/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Luke Parker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crypto/ff-group-tests/README.md b/crypto/ff-group-tests/README.md new file mode 100644 index 00000000..797ee3af --- /dev/null +++ b/crypto/ff-group-tests/README.md @@ -0,0 +1,5 @@ +# FF/Group Tests + +A series of sanity checks for implementors of the ff/group APIs. Implementors +are assumed to be of a non-trivial size. These tests do not attempt to check if +constant time implementations are used. diff --git a/crypto/ff-group-tests/src/field.rs b/crypto/ff-group-tests/src/field.rs new file mode 100644 index 00000000..71544785 --- /dev/null +++ b/crypto/ff-group-tests/src/field.rs @@ -0,0 +1,122 @@ +use group::ff::Field; + +/// Perform basic tests on equality. +pub fn test_eq() { + let zero = F::zero(); + let one = F::one(); + + assert!(zero != one, "0 == 1"); + assert!(!bool::from(zero.ct_eq(&one)), "0 ct_eq 1"); + + assert_eq!(zero, F::zero(), "0 != 0"); + assert!(bool::from(zero.ct_eq(&F::zero())), "0 !ct_eq 0"); + + assert_eq!(one, F::one(), "1 != 1"); + assert!(bool::from(one.ct_eq(&F::one())), "1 !ct_eq 1"); +} + +/// Verify conditional selection works. Doesn't verify it's actually constant time. +pub fn test_conditional_select() { + let zero = F::zero(); + let one = F::one(); + assert_eq!(F::conditional_select(&zero, &one, 0.into()), zero, "couldn't select when false"); + assert_eq!(F::conditional_select(&zero, &one, 1.into()), one, "couldn't select when true"); +} + +/// Perform basic tests on addition. +pub fn test_add() { + assert_eq!(F::zero() + F::zero(), F::zero(), "0 + 0 != 0"); + assert_eq!(F::zero() + F::one(), F::one(), "0 + 1 != 1"); + assert_eq!(F::one() + F::zero(), F::one(), "1 + 0 != 1"); + // Only PrimeField offers From + // Accordingly, we assume either double or addition is correct + // They either have to be matchingly correct or matchingly incorrect, yet we can't + // reliably determine that here + assert_eq!(F::one() + F::one(), F::one().double(), "1 + 1 != 2"); +} + +/// Perform basic tests on subtraction. +pub fn test_sub() { + assert_eq!(F::zero() - F::zero(), F::zero(), "0 - 0 != 0"); + assert_eq!(F::one() - F::zero(), F::one(), "1 - 0 != 1"); + assert_eq!(F::one() - F::one(), F::zero(), "1 - 1 != 0"); +} + +/// Perform basic tests on negation. +pub fn test_neg() { + assert_eq!(-F::zero(), F::zero(), "-0 != 0"); + assert_eq!(-(-F::one()), F::one(), "-(-1) != 1"); + assert_eq!(F::one() + (-F::one()), F::zero(), "1 + -1 != 0"); + assert_eq!(F::one() - (-F::one()), F::one().double(), "1 - -1 != 2"); +} + +/// Perform basic tests on multiplication. +pub fn test_mul() { + assert_eq!(F::zero() * F::zero(), F::zero(), "0 * 0 != 0"); + assert_eq!(F::one() * F::zero(), F::zero(), "1 * 0 != 0"); + assert_eq!(F::one() * F::one(), F::one(), "1 * 1 != 1"); + let two = F::one().double(); + assert_eq!(two * (two + F::one()), two + two + two, "2 * 3 != 6"); +} + +/// Perform basic tests on the square function. +pub fn test_square() { + assert_eq!(F::zero().square(), F::zero(), "0^2 != 0"); + assert_eq!(F::one().square(), F::one(), "1^2 != 1"); + let two = F::one().double(); + assert_eq!(two.square(), two + two, "2^2 != 4"); + let three = two + F::one(); + assert_eq!(three.square(), three * three, "3^2 != 9"); +} + +/// Perform basic tests on the invert function. +pub fn test_invert() { + assert!(bool::from(F::zero().invert().is_none()), "0.invert() is some"); + assert_eq!(F::one().invert().unwrap(), F::one(), "1.invert() != 1"); + + let two = F::one().double(); + let three = two + F::one(); + assert_eq!(two * three.invert().unwrap() * three, two, "2 * 3.invert() * 3 != 2"); +} + +/// Perform basic tests on the sqrt function. +pub fn test_sqrt() { + assert_eq!(F::zero().sqrt().unwrap(), F::zero(), "sqrt(0) != 0"); + assert_eq!(F::one().sqrt().unwrap(), F::one(), "sqrt(1) != 1"); + + let mut has_root = F::one().double(); + while bool::from(has_root.sqrt().is_none()) { + has_root += F::one(); + } + let root = has_root.sqrt().unwrap(); + assert_eq!(root * root, has_root, "sqrt(x)^2 != x"); +} + +/// Perform basic tests on the is_zero functions. +pub fn test_is_zero() { + assert!(bool::from(F::zero().is_zero()), "0 is not 0"); + assert!(F::zero().is_zero_vartime(), "0 is not 0"); +} + +/// Perform basic tests on the cube function. +pub fn test_cube() { + assert_eq!(F::zero().cube(), F::zero(), "0^3 != 0"); + assert_eq!(F::one().cube(), F::one(), "1^3 != 1"); + let two = F::one().double(); + assert_eq!(two.cube(), two * two * two, "2^3 != 8"); +} + +/// Run all tests on fields implementing Field. +pub fn test_field() { + test_eq::(); + test_conditional_select::(); + test_add::(); + test_sub::(); + test_neg::(); + test_mul::(); + test_square::(); + test_invert::(); + test_sqrt::(); + test_is_zero::(); + test_cube::(); +} diff --git a/crypto/ff-group-tests/src/group.rs b/crypto/ff-group-tests/src/group.rs new file mode 100644 index 00000000..c5cc996d --- /dev/null +++ b/crypto/ff-group-tests/src/group.rs @@ -0,0 +1,169 @@ +use group::{ + ff::{Field, PrimeFieldBits}, + Group, + prime::PrimeGroup, +}; + +use crate::prime_field::{test_prime_field, test_prime_field_bits}; + +/// Test equality. +pub fn test_eq() { + assert_eq!(G::identity(), G::identity(), "identity != identity"); + assert_eq!(G::generator(), G::generator(), "generator != generator"); + assert!(G::identity() != G::generator(), "identity != generator"); +} + +/// Test identity. +pub fn test_identity() { + assert!(bool::from(G::identity().is_identity()), "identity wasn't identity"); + assert!( + bool::from((G::identity() + G::identity()).is_identity()), + "identity + identity wasn't identity" + ); + assert!( + bool::from((G::generator() - G::generator()).is_identity()), + "generator - generator wasn't identity" + ); + assert!(!bool::from(G::generator().is_identity()), "is_identity claimed generator was identity"); +} + +/// Sanity check the generator. +pub fn test_generator() { + assert!(G::generator() != G::identity(), "generator was identity"); + assert!( + (G::generator() + G::generator()) != G::generator(), + "generator added to itself identity" + ); +} + +/// Test doubling of group elements. +pub fn test_double() { + assert!(bool::from(G::identity().double().is_identity()), "identity.double() wasn't identity"); + assert_eq!( + G::generator() + G::generator(), + G::generator().double(), + "generator + generator != generator.double()" + ); +} + +/// Test addition. +pub fn test_add() { + assert_eq!(G::identity() + G::identity(), G::identity(), "identity + identity != identity"); + assert_eq!(G::identity() + G::generator(), G::generator(), "identity + generator != generator"); + assert_eq!(G::generator() + G::identity(), G::generator(), "generator + identity != generator"); + + let two = G::generator().double(); + assert_eq!(G::generator() + G::generator(), two, "generator + generator != two"); + let four = two.double(); + assert_eq!( + G::generator() + G::generator() + G::generator() + G::generator(), + four, + "generator + generator + generator + generator != four" + ); +} + +/// Test summation. +pub fn test_sum() { + assert_eq!( + [G::generator(), G::generator()].iter().sum::(), + G::generator().double(), + "[generator, generator].sum() != two" + ); +} + +/// Test negation. +pub fn test_neg() { + assert_eq!(G::identity(), G::identity().neg(), "identity != -identity"); + assert_eq!( + G::generator() + G::generator().neg(), + G::identity(), + "generator + -generator != identity" + ); +} + +/// Test subtraction. +pub fn test_sub() { + assert_eq!(G::generator() - G::generator(), G::identity(), "generator - generator != identity"); + let two = G::generator() + G::generator(); + assert_eq!(two - G::generator(), G::generator(), "two - one != one"); +} + +/// Test scalar multiplication +pub fn test_mul() { + assert_eq!(G::generator() * G::Scalar::from(0), G::identity(), "generator * 0 != identity"); + assert_eq!(G::generator() * G::Scalar::from(1), G::generator(), "generator * 1 != generator"); + assert_eq!( + G::generator() * G::Scalar::from(2), + G::generator() + G::generator(), + "generator * 2 != generator + generator" + ); + assert_eq!(G::identity() * G::Scalar::from(2), G::identity(), "identity * 2 != identity"); +} + +/// Test `((order - 1) * G) + G == identity`. +pub fn test_order() { + let minus_one = G::generator() * (G::Scalar::zero() - G::Scalar::one()); + assert!(minus_one != G::identity(), "(modulus - 1) * G was identity"); + assert_eq!(minus_one + G::generator(), G::identity(), "((modulus - 1) * G) + G wasn't identity"); +} + +/// Run all tests on groups implementing Group. +pub fn test_group() { + test_prime_field::(); + + test_eq::(); + test_identity::(); + test_generator::(); + test_double::(); + test_add::(); + test_sum::(); + test_neg::(); + test_sub::(); + test_mul::(); + test_order::(); +} + +/// Test encoding and decoding of group elements. +pub fn test_encoding() { + let test = |point: G, msg| { + let bytes = point.to_bytes(); + let mut repr = G::Repr::default(); + repr.as_mut().copy_from_slice(bytes.as_ref()); + assert_eq!(point, G::from_bytes(&repr).unwrap(), "{} couldn't be encoded and decoded", msg); + assert_eq!( + point, + G::from_bytes_unchecked(&repr).unwrap(), + "{} couldn't be encoded and decoded", + msg + ); + }; + test(G::identity(), "identity"); + test(G::generator(), "generator"); + test(G::generator() + G::generator(), "(generator * 2)"); +} + +/// Run all tests on groups implementing PrimeGroup (Group + GroupEncoding). +pub fn test_prime_group() { + test_group::(); + + test_encoding::(); +} + +/// Run all tests offered by this crate on the group. +pub fn test_prime_group_bits() +where + G::Scalar: PrimeFieldBits, +{ + test_prime_field_bits::(); + test_prime_group::(); +} + +#[test] +fn test_k256_group_encoding() { + test_prime_group_bits::(); +} + +#[test] +fn test_p256_group_encoding() { + test_prime_group_bits::(); +} diff --git a/crypto/ff-group-tests/src/lib.rs b/crypto/ff-group-tests/src/lib.rs new file mode 100644 index 00000000..1b05b98a --- /dev/null +++ b/crypto/ff-group-tests/src/lib.rs @@ -0,0 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_auto_cfg))] + +/// Tests for the Field trait. +pub mod field; +/// Tests for the PrimeField and PrimeFieldBits traits. +pub mod prime_field; + +/// Tests for the Group and GroupEncoding traits. +pub mod group; diff --git a/crypto/ff-group-tests/src/prime_field.rs b/crypto/ff-group-tests/src/prime_field.rs new file mode 100644 index 00000000..f0ec5bab --- /dev/null +++ b/crypto/ff-group-tests/src/prime_field.rs @@ -0,0 +1,291 @@ +use group::ff::{PrimeField, PrimeFieldBits}; + +use crate::field::test_field; + +// Ideally, this and test_one would be under Field, yet these tests require access to From +/// Test zero returns F::from(0). +pub fn test_zero() { + assert_eq!(F::zero(), F::from(0u64), "0 != 0"); +} + +/// Test one returns F::from(1). +pub fn test_one() { + assert_eq!(F::one(), F::from(1u64), "1 != 1"); +} + +/// Test From for F works. +pub fn test_from_u64() { + assert_eq!(F::one().double(), F::from(2u64), "2 != 2"); +} + +/// Test is_odd/is_even works. +/// This test assumes an odd modulus with oddness being determined by the least-significant bit. +/// Accordingly, this test doesn't support fields alternatively defined. +/// TODO: Improve in the future. +pub fn test_is_odd() { + assert_eq!(F::zero().is_odd().unwrap_u8(), 0, "0 was odd"); + assert_eq!(F::zero().is_even().unwrap_u8(), 1, "0 wasn't even"); + + assert_eq!(F::one().is_odd().unwrap_u8(), 1, "1 was even"); + assert_eq!(F::one().is_even().unwrap_u8(), 0, "1 wasn't odd"); + + let neg_one = -F::one(); + assert_eq!(neg_one.is_odd().unwrap_u8(), 0, "-1 was odd"); + assert_eq!(neg_one.is_even().unwrap_u8(), 1, "-1 wasn't even"); + + assert_eq!(neg_one.double().is_odd().unwrap_u8(), 1, "(-1).double() was even"); + assert_eq!(neg_one.double().is_even().unwrap_u8(), 0, "(-1).double() wasn't odd"); +} + +/// Test encoding and decoding of field elements. +pub fn test_encoding() { + let test = |scalar: F, msg| { + let bytes = scalar.to_repr(); + let mut repr = F::Repr::default(); + repr.as_mut().copy_from_slice(bytes.as_ref()); + assert_eq!(scalar, F::from_repr(repr).unwrap(), "{} couldn't be encoded and decoded", msg); + assert_eq!( + scalar, + F::from_repr_vartime(repr).unwrap(), + "{} couldn't be encoded and decoded", + msg + ); + }; + test(F::zero(), "0"); + test(F::one(), "1"); + test(F::one() + F::one(), "2"); + test(-F::one(), "-1"); +} + +/// Run all tests on fields implementing PrimeField. +pub fn test_prime_field() { + test_field::(); + + test_zero::(); + test_one::(); + test_from_u64::(); + test_is_odd::(); + + // Do a sanity check on the CAPACITY. A full test can't be done at this time + assert!(F::CAPACITY <= F::NUM_BITS, "capacity exceeded number of bits"); + + test_encoding::(); +} + +/// Test to_le_bits returns the little-endian bits of a value. +// This test assumes that the modulus is at least 4. +pub fn test_to_le_bits() { + { + let bits = F::zero().to_le_bits(); + assert_eq!(bits.iter().filter(|bit| **bit).count(), 0, "0 had bits set"); + } + + { + let bits = F::one().to_le_bits(); + assert!(bits[0], "1 didn't have its least significant bit set"); + assert_eq!(bits.iter().filter(|bit| **bit).count(), 1, "1 had multiple bits set"); + } + + { + let bits = F::from(2).to_le_bits(); + assert!(bits[1], "2 didn't have its second bit set"); + assert_eq!(bits.iter().filter(|bit| **bit).count(), 1, "2 had multiple bits set"); + } + + { + let bits = F::from(3).to_le_bits(); + assert!(bits[0], "3 didn't have its first bit set"); + assert!(bits[1], "3 didn't have its second bit set"); + assert_eq!(bits.iter().filter(|bit| **bit).count(), 2, "2 didn't have two bits set"); + } +} + +/// Test char_le_bits returns the bits of the modulus. +pub fn test_char_le_bits() { + // A field with a modulus of 0 may be technically valid? Yet these tests assume some basic + // functioning. + assert!(F::char_le_bits().iter().any(|bit| *bit), "char_le_bits contained 0"); + + // Test this is the bit pattern of the modulus by reconstructing the modulus from it + let mut bit = F::one(); + let mut modulus = F::zero(); + for set in F::char_le_bits() { + if set { + modulus += bit; + } + bit = bit.double(); + } + assert_eq!(modulus, F::zero(), "char_le_bits did not contain the field's modulus"); +} + +/// Test NUM_BITS is accurate. +pub fn test_num_bits() { + let mut val = F::one(); + let mut bit = 0; + while ((bit + 1) < val.to_le_bits().len()) && val.double().to_le_bits()[bit + 1] { + val = val.double(); + bit += 1; + } + assert_eq!( + F::NUM_BITS, + u32::try_from(bit + 1).unwrap(), + "NUM_BITS was incorrect. it should be {}", + bit + 1 + ); +} + +/// Test CAPACITY is accurate. +pub fn test_capacity() { + assert!(F::CAPACITY <= F::NUM_BITS, "capacity exceeded number of bits"); + + let mut val = F::one(); + assert!(val.to_le_bits()[0], "1 didn't have its least significant bit set"); + for b in 1 .. F::CAPACITY { + val = val.double(); + val += F::one(); + for i in 0 ..= b { + assert!( + val.to_le_bits()[usize::try_from(i).unwrap()], + "couldn't set a bit within the capacity", + ); + } + } + + // If the field has a modulus which is a power of 2, NUM_BITS should equal CAPACITY + // Adding one would also be sufficient to trigger an overflow + if F::char_le_bits().iter().filter(|bit| **bit).count() == 1 { + assert_eq!( + F::NUM_BITS, + F::CAPACITY, + "field has a power of two modulus yet CAPACITY doesn't equal NUM_BITS", + ); + assert_eq!(val + F::one(), F::zero()); + return; + } + + assert_eq!(F::NUM_BITS - 1, F::CAPACITY, "capacity wasn't NUM_BITS - 1"); +} + +fn pow(base: F, exp: F) -> F { + let mut res = F::one(); + for bit in exp.to_le_bits().iter().rev() { + res *= res; + if *bit { + res *= base; + } + } + res +} + +// Ideally, this would be under field.rs, yet the above pow function requires PrimeFieldBits +/// Perform basic tests on the pow functions, even when passed non-canonical inputs. +pub fn test_pow() { + // Sanity check the local pow algorithm. Does not have assert messages as these shouldn't fail + assert_eq!(pow(F::one(), F::zero()), F::one()); + assert_eq!(pow(F::one().double(), F::zero()), F::one()); + assert_eq!(pow(F::one(), F::one()), F::one()); + + let two = F::one().double(); + assert_eq!(pow(two, F::one()), two); + assert_eq!(pow(two, two), two.double()); + let three = two + F::one(); + assert_eq!(pow(three, F::one()), three); + assert_eq!(pow(three, two), three * three); + assert_eq!(pow(three, three), three * three * three); + + // TODO: Test against Field::pow once updated to ff 0.13 + + // Choose a small base without a notably uniform bit pattern + let bit_0 = F::one(); + let base = { + let bit_1 = bit_0.double(); + let bit_2 = bit_1.double(); + let bit_3 = bit_2.double(); + let bit_4 = bit_3.double(); + let bit_5 = bit_4.double(); + let bit_6 = bit_5.double(); + let bit_7 = bit_6.double(); + bit_7 + bit_6 + bit_5 + bit_2 + bit_0 + }; + + // Ensure pow_vartime returns 1 when the base is raised to 0, handling malleated inputs + assert_eq!(base.pow_vartime([]), F::one(), "pow_vartime x^0 ([]) != 1"); + assert_eq!(base.pow_vartime([0]), F::one(), "pow_vartime x^0 ([0]) != 1"); + assert_eq!(base.pow_vartime([0, 0]), F::one(), "pow_vartime x^0 ([0, 0]) != 1"); + + // Ensure pow_vartime returns the base when raised to 1, handling malleated inputs + assert_eq!(base.pow_vartime([1]), base, "pow_vartime x^1 ([1]) != x"); + assert_eq!(base.pow_vartime([1, 0]), base, "pow_vartime x^1 ([1, 0]) != x"); + + // Ensure pow_vartime can handle multiple u64s properly + // Create a scalar which exceeds u64 + let mut bit_64 = bit_0; + for _ in 0 .. 64 { + bit_64 = bit_64.double(); + } + // Run the tests + assert_eq!(base.pow_vartime([0, 1]), pow(base, bit_64), "pow_vartime x^(2^64) != x^(2^64)"); + assert_eq!( + base.pow_vartime([1, 1]), + pow(base, bit_64 + F::one()), + "pow_vartime x^(2^64 + 1) != x^(2^64 + 1)" + ); +} + +/// Test S is correct. +pub fn test_s() { + // "This is the number of leading zero bits in the little-endian bit representation of + // `modulus - 1`." + let mut s = 0; + for b in (F::zero() - F::one()).to_le_bits() { + if b { + break; + } + s += 1; + } + assert_eq!(s, F::S, "incorrect S"); +} + +// Test the root of unity is correct for the given multiplicative generator. +pub fn test_root_of_unity() { + // "It can be calculated by exponentiating `Self::multiplicative_generator` by `t`, where + // `t = (modulus - 1) >> Self::S`." + + // Get the bytes to shift + let mut bits = (F::zero() - F::one()).to_le_bits().iter().map(|bit| *bit).collect::>(); + for _ in 0 .. F::S { + bits.remove(0); + } + + // Construct t + let mut bit = F::one(); + let mut t = F::zero(); + for set in bits { + if set { + t += bit; + } + bit = bit.double(); + } + + assert_eq!(pow(F::multiplicative_generator(), t), F::root_of_unity(), "incorrect root of unity"); + assert_eq!( + pow(F::root_of_unity(), pow(F::from(2u64), F::from(F::S.into()))), + F::one(), + "root of unity raised to 2^S wasn't 1" + ); +} + +/// Run all tests on fields implementing PrimeFieldBits. +pub fn test_prime_field_bits() { + test_prime_field::(); + + test_to_le_bits::(); + test_char_le_bits::(); + + test_pow::(); + test_s::(); + test_root_of_unity::(); + + test_num_bits::(); + test_capacity::(); +}