FROST Ed448 (#107)

* Theoretical ed448 impl

* Fixes

* Basic tests

* More efficient scalarmul

Precomputes a table to minimize additions required.

* Add a torsion test

* Split into a constant and variable time backend

The variable time one is still far too slow, at 53s for the tests (~5s a 
scalarmul). It should be usable as a PoC though.

* Rename unsafe Ed448

It's not only unworthy of the Serai branding and deserves more clarity
in the name.

* Add wide reduction to ed448

* Add Zeroize to Ed448

* Rename Ed448 group.rs to point.rs

* Minor lint to FROST

* Ed448 ciphersuite with 8032 test vector

* Macro out the backend fields

* Slight efficiency improvement to point decompression

* Disable the multiexp test in FROST for Ed448

* fmt + clippy ed448

* Fix an infinite loop in the constant time ed448 backend

* Add b"chal" to the 8032 context string for Ed448

Successfully tests against proposed vectors for the FROST IETF draft.

* Fix fmt and clippy

* Use a tabled pow algorithm in ed448's const backend

* Slight tweaks to variable time backend

Stop from_repr(MODULUS) from passing.

* Use extended points

Almost two orders of magnitude faster.

* Efficient ed448 doubling

* Remove the variable time backend

With the recent performance improvements, the constant time backend is 
now 4x faster than the variable time backend was. While the variable 
time backend remains much faster, and the constant time backend is still 
slow compared to other libraries, it's sufficiently performant now.

The FROST test, which runs a series of multiexps over the curve, does 
take 218.26s while Ristretto takes 1 and secp256k1 takes 4.57s.

While 50x slower than secp256k1 is horrible, it's ~1.5 orders of 
magntiude, which is close enough to the desire stated in 
https://github.com/serai-dex/serai/issues/108 to meet it.

Largely makes this library safe to use.

* Correct constants in ed448

* Rename unsafe-ed448 to minimal-ed448

Enables all FROST tests against it.

* No longer require the hazmat feature to use ed448

* Remove extraneous as_refs
This commit is contained in:
Luke Parker
2022-08-29 02:32:59 -05:00
committed by GitHub
parent f71f19e26c
commit 081b9a1975
20 changed files with 975 additions and 32 deletions

165
crypto/ed448/src/backend.rs Normal file
View File

@@ -0,0 +1,165 @@
#[doc(hidden)]
#[macro_export]
macro_rules! field {
($FieldName: ident, $MODULUS: ident, $WIDE_MODULUS: ident, $NUM_BITS: literal) => {
use core::ops::{Add, AddAssign, Neg, Sub, SubAssign, Mul, MulAssign};
use rand_core::RngCore;
use subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable};
use generic_array::{typenum::U57, GenericArray};
use crypto_bigint::Encoding;
use ff::{Field, PrimeField, FieldBits, PrimeFieldBits};
use dalek_ff_group::{constant_time, from_uint, math};
fn reduce(x: U1024) -> U512 {
U512::from_le_slice(&x.reduce(&$WIDE_MODULUS).unwrap().to_le_bytes()[.. 64])
}
constant_time!($FieldName, U512);
math!(
$FieldName,
$FieldName,
|x, y| U512::add_mod(&x, &y, &$MODULUS.0),
|x, y| U512::sub_mod(&x, &y, &$MODULUS.0),
|x, y| {
let wide = U512::mul_wide(&x, &y);
reduce(U1024::from((wide.1, wide.0)))
}
);
from_uint!($FieldName, U512);
impl Neg for $FieldName {
type Output = $FieldName;
fn neg(self) -> $FieldName {
*$MODULUS - self
}
}
impl<'a> Neg for &'a $FieldName {
type Output = $FieldName;
fn neg(self) -> Self::Output {
(*self).neg()
}
}
lazy_static! {
pub(crate) static ref ZERO: $FieldName = $FieldName(U512::ZERO);
pub(crate) static ref ONE: $FieldName = $FieldName(U512::ONE);
pub(crate) static ref TWO: $FieldName = $FieldName(U512::ONE.saturating_add(&U512::ONE));
}
impl $FieldName {
pub fn pow(&self, other: $FieldName) -> $FieldName {
let mut table = [*ONE; 16];
table[1] = *self;
for i in 2 .. 16 {
table[i] = table[i - 1] * self;
}
let mut res = *ONE;
let mut bits = 0;
for (i, bit) in other.to_le_bits().iter().rev().enumerate() {
bits <<= 1;
let bit = *bit as u8;
assert_eq!(bit | 1, 1);
bits |= bit;
if ((i + 1) % 4) == 0 {
if i != 3 {
for _ in 0 .. 4 {
res *= res;
}
}
res *= table[usize::from(bits)];
bits = 0;
}
}
res
}
}
impl Field for $FieldName {
fn random(mut rng: impl RngCore) -> Self {
let mut bytes = [0; 128];
rng.fill_bytes(&mut bytes);
$FieldName(reduce(U1024::from_le_slice(bytes.as_ref())))
}
fn zero() -> Self {
*ZERO
}
fn one() -> Self {
*ONE
}
fn square(&self) -> Self {
*self * self
}
fn double(&self) -> Self {
*self + self
}
fn invert(&self) -> CtOption<Self> {
CtOption::new(self.pow(-*TWO), !self.is_zero())
}
fn sqrt(&self) -> CtOption<Self> {
unimplemented!()
}
fn is_zero(&self) -> Choice {
self.ct_eq(&ZERO)
}
fn cube(&self) -> Self {
*self * self * self
}
fn pow_vartime<S: AsRef<[u64]>>(&self, _exp: S) -> Self {
unimplemented!()
}
}
impl PrimeField for $FieldName {
type Repr = GenericArray<u8, U57>;
const NUM_BITS: u32 = $NUM_BITS;
const CAPACITY: u32 = $NUM_BITS - 1;
fn from_repr(bytes: Self::Repr) -> CtOption<Self> {
let res = $FieldName(U512::from_le_slice(&[bytes.as_ref(), [0; 7].as_ref()].concat()));
CtOption::new(res, res.0.add_mod(&U512::ZERO, &$MODULUS.0).ct_eq(&res.0))
}
fn to_repr(&self) -> Self::Repr {
let mut repr = GenericArray::<u8, U57>::default();
repr.copy_from_slice(&self.0.to_le_bytes()[.. 57]);
repr
}
// True for both the Ed448 Scalar field and FieldElement field
const S: u32 = 1;
fn is_odd(&self) -> Choice {
(self.to_repr()[0] & 1).into()
}
fn multiplicative_generator() -> Self {
unimplemented!()
}
fn root_of_unity() -> Self {
unimplemented!()
}
}
impl PrimeFieldBits for $FieldName {
type ReprBits = [u8; 56];
fn to_le_bits(&self) -> FieldBits<Self::ReprBits> {
let mut repr = [0; 56];
repr.copy_from_slice(&self.to_repr()[.. 56]);
repr.into()
}
fn char_le_bits() -> FieldBits<Self::ReprBits> {
MODULUS.to_le_bits()
}
}
};
}