mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-23 03:59:22 +00:00
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:
parent
6e518f5c22
commit
445bb3786e
18 changed files with 701 additions and 336 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -1512,6 +1512,7 @@ dependencies = [
|
||||||
"curve25519-dalek 3.2.0",
|
"curve25519-dalek 3.2.0",
|
||||||
"digest 0.10.6",
|
"digest 0.10.6",
|
||||||
"ff",
|
"ff",
|
||||||
|
"ff-group-tests",
|
||||||
"group",
|
"group",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
"subtle",
|
"subtle",
|
||||||
|
@ -2331,6 +2332,15 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ff-group-tests"
|
||||||
|
version = "0.12.0"
|
||||||
|
dependencies = [
|
||||||
|
"group",
|
||||||
|
"k256",
|
||||||
|
"p256",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fiat-crypto"
|
name = "fiat-crypto"
|
||||||
version = "0.1.17"
|
version = "0.1.17"
|
||||||
|
@ -4570,6 +4580,7 @@ dependencies = [
|
||||||
"dalek-ff-group",
|
"dalek-ff-group",
|
||||||
"digest 0.10.6",
|
"digest 0.10.6",
|
||||||
"ff",
|
"ff",
|
||||||
|
"ff-group-tests",
|
||||||
"generic-array 0.14.6",
|
"generic-array 0.14.6",
|
||||||
"group",
|
"group",
|
||||||
"hex",
|
"hex",
|
||||||
|
|
|
@ -4,6 +4,7 @@ members = [
|
||||||
|
|
||||||
"crypto/transcript",
|
"crypto/transcript",
|
||||||
|
|
||||||
|
"crypto/ff-group-tests",
|
||||||
"crypto/dalek-ff-group",
|
"crypto/dalek-ff-group",
|
||||||
"crypto/ed448",
|
"crypto/ed448",
|
||||||
"crypto/ciphersuite",
|
"crypto/ciphersuite",
|
||||||
|
|
|
@ -24,3 +24,6 @@ group = "0.12"
|
||||||
|
|
||||||
crypto-bigint = "0.4"
|
crypto-bigint = "0.4"
|
||||||
curve25519-dalek = "3.2"
|
curve25519-dalek = "3.2"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
ff-group-tests = { path = "../ff-group-tests" }
|
||||||
|
|
|
@ -99,30 +99,6 @@ impl Field for FieldElement {
|
||||||
let candidate = Self::conditional_select(&tv2, &tv1, tv1.square().ct_eq(self));
|
let candidate = Self::conditional_select(&tv2, &tv1, tv1.square().ct_eq(self));
|
||||||
CtOption::new(candidate, candidate.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 {
|
impl PrimeField for FieldElement {
|
||||||
|
@ -218,160 +194,6 @@ impl FieldElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_s() {
|
fn test_field() {
|
||||||
// "This is the number of leading zero bits in the little-endian bit representation of
|
ff_group_tests::prime_field::test_prime_field_bits::<FieldElement>();
|
||||||
// `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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use core::{
|
use core::{
|
||||||
|
@ -256,27 +257,6 @@ impl Field for Scalar {
|
||||||
let candidate = Self::conditional_select(&tv2, &tv1, tv1.square().ct_eq(self));
|
let candidate = Self::conditional_select(&tv2, &tv1, tv1.square().ct_eq(self));
|
||||||
CtOption::new(candidate, candidate.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 {
|
impl PrimeField for Scalar {
|
||||||
|
@ -454,70 +434,11 @@ dalek_group!(
|
||||||
);
|
);
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_s() {
|
fn test_ed25519_group() {
|
||||||
// "This is the number of leading zero bits in the little-endian bit representation of
|
ff_group_tests::group::test_prime_group_bits::<EdwardsPoint>();
|
||||||
// `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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_root_of_unity() {
|
fn test_ristretto_group() {
|
||||||
// "It can be calculated by exponentiating `Self::multiplicative_generator` by `t`, where
|
ff_group_tests::group::test_prime_group_bits::<RistrettoPoint>();
|
||||||
// `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()));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,3 +32,5 @@ dalek-ff-group = { path = "../dalek-ff-group", version = "^0.1.2" }
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex-literal = "0.3"
|
hex-literal = "0.3"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
|
||||||
|
ff-group-tests = { path = "../ff-group-tests" }
|
||||||
|
|
|
@ -38,7 +38,7 @@ macro_rules! field {
|
||||||
impl Neg for $FieldName {
|
impl Neg for $FieldName {
|
||||||
type Output = $FieldName;
|
type Output = $FieldName;
|
||||||
fn neg(self) -> $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> {
|
fn sqrt(&self) -> CtOption<Self> {
|
||||||
unimplemented!()
|
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);
|
||||||
fn is_zero(&self) -> Choice {
|
CtOption::new(res, res.square().ct_eq(self))
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,30 +30,7 @@ pub(crate) const Q_4: FieldElement =
|
||||||
field!(FieldElement, MODULUS, WIDE_MODULUS, 448);
|
field!(FieldElement, MODULUS, WIDE_MODULUS, 448);
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn repr() {
|
fn test_field() {
|
||||||
assert_eq!(FieldElement::from_repr(FieldElement::one().to_repr()).unwrap(), FieldElement::one());
|
// TODO: Move to test_prime_field_bits once the impl is finished
|
||||||
}
|
ff_group_tests::prime_field::test_prime_field::<FieldElement>();
|
||||||
|
|
||||||
#[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());
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
mod backend;
|
mod backend;
|
||||||
|
|
|
@ -309,31 +309,38 @@ impl GroupEncoding for Point {
|
||||||
impl PrimeGroup for Point {}
|
impl PrimeGroup for Point {}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn identity() {
|
fn test_group() {
|
||||||
assert_eq!(Point::from_bytes(&Point::identity().to_bytes()).unwrap(), Point::identity());
|
// TODO: Move to test_prime_group_bits once the impl is finished
|
||||||
assert_eq!(Point::identity() + Point::identity(), Point::identity());
|
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]
|
#[test]
|
||||||
fn torsion() {
|
fn torsion() {
|
||||||
|
use generic_array::GenericArray;
|
||||||
|
|
||||||
// Uses the originally suggested generator which had torsion
|
// Uses the originally suggested generator which had torsion
|
||||||
let old_y = FieldElement::from_repr(
|
let old_y = FieldElement::from_repr(*GenericArray::from_slice(
|
||||||
hex_literal::hex!(
|
&hex::decode(
|
||||||
"12796c1532041525945f322e414d434467cfd5c57c9a9af2473b27758c921c4828b277ca5f2891fc4f3d79afdf29a64c72fb28b59c16fa5100"
|
"\
|
||||||
).into(),
|
12796c1532041525945f322e414d434467cfd5c57c9a9af2473b2775\
|
||||||
|
8c921c4828b277ca5f2891fc4f3d79afdf29a64c72fb28b59c16fa51\
|
||||||
|
00",
|
||||||
)
|
)
|
||||||
|
.unwrap(),
|
||||||
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let old = Point { x: -recover_x(old_y).unwrap(), y: old_y, z: FieldElement::one() };
|
let old = Point { x: -recover_x(old_y).unwrap(), y: old_y, z: FieldElement::one() };
|
||||||
assert!(bool::from(!old.is_torsion_free()));
|
assert!(bool::from(!old.is_torsion_free()));
|
||||||
|
@ -382,6 +389,7 @@ a401cd9df24632adfe6b418dc942d8a091817dd8bd70e1c72ba52f3c\
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks random won't infinitely loop
|
||||||
#[test]
|
#[test]
|
||||||
fn random() {
|
fn random() {
|
||||||
Point::random(&mut rand_core::OsRng);
|
Point::random(&mut rand_core::OsRng);
|
||||||
|
|
|
@ -33,6 +33,7 @@ impl Scalar {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invert() {
|
fn test_scalar_field() {
|
||||||
assert_eq!(Scalar::one().invert().unwrap(), Scalar::one());
|
// TODO: Move to test_prime_field_bits once the impl is finished
|
||||||
|
ff_group_tests::prime_field::test_prime_field::<Scalar>();
|
||||||
}
|
}
|
||||||
|
|
20
crypto/ff-group-tests/Cargo.toml
Normal file
20
crypto/ff-group-tests/Cargo.toml
Normal 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"] }
|
21
crypto/ff-group-tests/LICENSE
Normal file
21
crypto/ff-group-tests/LICENSE
Normal 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.
|
5
crypto/ff-group-tests/README.md
Normal file
5
crypto/ff-group-tests/README.md
Normal 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.
|
122
crypto/ff-group-tests/src/field.rs
Normal file
122
crypto/ff-group-tests/src/field.rs
Normal 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>();
|
||||||
|
}
|
169
crypto/ff-group-tests/src/group.rs
Normal file
169
crypto/ff-group-tests/src/group.rs
Normal 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>();
|
||||||
|
}
|
9
crypto/ff-group-tests/src/lib.rs
Normal file
9
crypto/ff-group-tests/src/lib.rs
Normal 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;
|
291
crypto/ff-group-tests/src/prime_field.rs
Normal file
291
crypto/ff-group-tests/src/prime_field.rs
Normal 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>();
|
||||||
|
}
|
Loading…
Reference in a new issue