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.
This commit is contained in:
Luke Parker 2022-12-24 15:09:09 -05:00
parent 6e518f5c22
commit 445bb3786e
No known key found for this signature in database
18 changed files with 701 additions and 336 deletions

11
Cargo.lock generated
View file

@ -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",

View file

@ -4,6 +4,7 @@ members = [
"crypto/transcript",
"crypto/ff-group-tests",
"crypto/dalek-ff-group",
"crypto/ed448",
"crypto/ciphersuite",

View file

@ -24,3 +24,6 @@ group = "0.12"
crypto-bigint = "0.4"
curve25519-dalek = "3.2"
[dev-dependencies]
ff-group-tests = { path = "../ff-group-tests" }

View file

@ -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<S: AsRef<[u64]>>(&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::<FieldElement>();
}

View file

@ -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<S: AsRef<[u64]>>(&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::<EdwardsPoint>();
}
#[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::<RistrettoPoint>();
}

View file

@ -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" }

View file

@ -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<Self> {
unimplemented!()
}
fn is_zero(&self) -> Choice {
self.0.ct_eq(&U512::ZERO)
}
fn cube(&self) -> Self {
self.square() * self
}
fn pow_vartime<S: AsRef<[u64]>>(&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))
}
}

View file

@ -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::<FieldElement>();
}

View file

@ -1,3 +1,4 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![no_std]
mod backend;

View file

@ -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::<Point>();
test_identity::<Point>();
test_generator::<Point>();
test_double::<Point>();
test_add::<Point>();
test_sum::<Point>();
test_neg::<Point>();
test_sub::<Point>();
test_mul::<Point>();
test_order::<Point>();
test_encoding::<Point>();
}
#[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);

View file

@ -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::<Scalar>();
}

View file

@ -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 <lukeparker5132@gmail.com>"]
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"] }

View file

@ -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.

View file

@ -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.

View file

@ -0,0 +1,122 @@
use group::ff::Field;
/// Perform basic tests on equality.
pub fn test_eq<F: Field>() {
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<F: Field>() {
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<F: Field>() {
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<u64>
// 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<F: Field>() {
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<F: Field>() {
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<F: Field>() {
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<F: Field>() {
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<F: Field>() {
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<F: Field>() {
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<F: Field>() {
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<F: Field>() {
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<F: Field>() {
test_eq::<F>();
test_conditional_select::<F>();
test_add::<F>();
test_sub::<F>();
test_neg::<F>();
test_mul::<F>();
test_square::<F>();
test_invert::<F>();
test_sqrt::<F>();
test_is_zero::<F>();
test_cube::<F>();
}

View file

@ -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<G: Group>() {
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<G: Group>() {
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<G: Group>() {
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<G: Group>() {
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<G: Group>() {
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<G: Group>() {
assert_eq!(
[G::generator(), G::generator()].iter().sum::<G>(),
G::generator().double(),
"[generator, generator].sum() != two"
);
}
/// Test negation.
pub fn test_neg<G: Group>() {
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<G: Group>() {
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<G: Group>() {
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<G: Group>() {
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<G: Group>() {
test_prime_field::<G::Scalar>();
test_eq::<G>();
test_identity::<G>();
test_generator::<G>();
test_double::<G>();
test_add::<G>();
test_sum::<G>();
test_neg::<G>();
test_sub::<G>();
test_mul::<G>();
test_order::<G>();
}
/// Test encoding and decoding of group elements.
pub fn test_encoding<G: PrimeGroup>() {
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<G: PrimeGroup>() {
test_group::<G>();
test_encoding::<G>();
}
/// Run all tests offered by this crate on the group.
pub fn test_prime_group_bits<G: PrimeGroup>()
where
G::Scalar: PrimeFieldBits,
{
test_prime_field_bits::<G::Scalar>();
test_prime_group::<G>();
}
#[test]
fn test_k256_group_encoding() {
test_prime_group_bits::<k256::ProjectivePoint>();
}
#[test]
fn test_p256_group_encoding() {
test_prime_group_bits::<p256::ProjectivePoint>();
}

View file

@ -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;

View file

@ -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<u64>
/// Test zero returns F::from(0).
pub fn test_zero<F: PrimeField>() {
assert_eq!(F::zero(), F::from(0u64), "0 != 0");
}
/// Test one returns F::from(1).
pub fn test_one<F: PrimeField>() {
assert_eq!(F::one(), F::from(1u64), "1 != 1");
}
/// Test From<u64> for F works.
pub fn test_from_u64<F: PrimeField>() {
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<F: PrimeField>() {
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<F: PrimeField>() {
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<F: PrimeField>() {
test_field::<F>();
test_zero::<F>();
test_one::<F>();
test_from_u64::<F>();
test_is_odd::<F>();
// 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::<F>();
}
/// 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<F: PrimeField + PrimeFieldBits>() {
{
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<F: PrimeField + PrimeFieldBits>() {
// 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<F: PrimeField + PrimeFieldBits>() {
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<F: PrimeField + PrimeFieldBits>() {
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<F: PrimeFieldBits>(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<F: PrimeFieldBits>() {
// 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<F: PrimeFieldBits>() {
// "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<F: PrimeFieldBits>() {
// "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::<Vec<_>>();
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<F: PrimeFieldBits>() {
test_prime_field::<F>();
test_to_le_bits::<F>();
test_char_le_bits::<F>();
test_pow::<F>();
test_s::<F>();
test_root_of_unity::<F>();
test_num_bits::<F>();
test_capacity::<F>();
}