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

View File

@@ -2,6 +2,7 @@ use zeroize::Zeroize;
use sha2::{Digest, Sha512};
use group::Group;
use dalek_ff_group::Scalar;
use crate::{curve::Curve, algorithm::Hram};
@@ -12,13 +13,11 @@ macro_rules! dalek_curve {
$Hram: ident,
$Point: ident,
$POINT: ident,
$ID: literal,
$CONTEXT: literal,
$chal: literal,
) => {
use dalek_ff_group::{$Point, $POINT};
use dalek_ff_group::$Point;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct $Curve;
@@ -35,7 +34,7 @@ macro_rules! dalek_curve {
const ID: &'static [u8] = $ID;
fn generator() -> Self::G {
$POINT
$Point::generator()
}
fn hash_to_vec(dst: &[u8], data: &[u8]) -> Vec<u8> {
@@ -69,7 +68,6 @@ dalek_curve!(
Ristretto,
IetfRistrettoHram,
RistrettoPoint,
RISTRETTO_BASEPOINT_POINT,
b"ristretto",
b"FROST-RISTRETTO255-SHA512-v8",
b"chal",
@@ -80,7 +78,6 @@ dalek_curve!(
Ed25519,
IetfEd25519Hram,
EdwardsPoint,
ED25519_BASEPOINT_POINT,
b"edwards25519",
b"FROST-ED25519-SHA512-v8",
b"",

View File

@@ -0,0 +1,62 @@
use zeroize::Zeroize;
use sha3::{digest::ExtendableOutput, Shake256};
use group::{Group, GroupEncoding};
use minimal_ed448::{scalar::Scalar, point::Point};
use crate::{curve::Curve, algorithm::Hram};
const CONTEXT: &[u8] = b"FROST-ED448-SHAKE256-v8";
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
pub struct Ed448;
impl Ed448 {
fn hash(prefix: &[u8], context: &[u8], dst: &[u8], data: &[u8]) -> [u8; 114] {
let mut res = [0; 114];
Shake256::digest_xof(&[prefix, context, dst, data].concat(), &mut res);
res
}
}
impl Curve for Ed448 {
type F = Scalar;
type G = Point;
const ID: &'static [u8] = b"ed448";
fn generator() -> Self::G {
Point::generator()
}
fn hash_to_vec(dst: &[u8], data: &[u8]) -> Vec<u8> {
Self::hash(b"", CONTEXT, dst, data).as_ref().to_vec()
}
fn hash_to_F(dst: &[u8], data: &[u8]) -> Self::F {
Scalar::wide_reduce(Self::hash(b"", CONTEXT, dst, data))
}
}
#[derive(Copy, Clone)]
pub struct Ietf8032Ed448Hram;
impl Ietf8032Ed448Hram {
#[allow(non_snake_case)]
pub fn hram(context: &[u8], R: &Point, A: &Point, m: &[u8]) -> Scalar {
Scalar::wide_reduce(Ed448::hash(
&[b"SigEd448".as_ref(), &[0, u8::try_from(context.len()).unwrap()]].concat(),
context,
b"",
&[R.to_bytes().as_ref(), A.to_bytes().as_ref(), m].concat(),
))
}
}
#[derive(Copy, Clone)]
pub struct NonIetfEd448Hram;
impl Hram<Ed448> for NonIetfEd448Hram {
#[allow(non_snake_case)]
fn hram(R: &Point, A: &Point, m: &[u8]) -> Scalar {
Ietf8032Ed448Hram::hram(&[CONTEXT, b"chal"].concat(), R, A, m)
}
}

View File

@@ -24,6 +24,11 @@ pub use kp256::{Secp256k1, IetfSecp256k1Hram};
#[cfg(feature = "p256")]
pub use kp256::{P256, IetfP256Hram};
#[cfg(feature = "ed448")]
mod ed448;
#[cfg(feature = "ed448")]
pub use ed448::{Ed448, Ietf8032Ed448Hram, NonIetfEd448Hram};
/// Set of errors for curve-related operations, namely encoding and decoding
#[derive(Clone, Error, Debug)]
pub enum CurveError {

View File

@@ -19,24 +19,26 @@ fn keys_serialization<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
}
}
// Test successful multiexp, with enough pairs to trigger its variety of algorithms
// Multiexp has its own tests, yet only against k256 and Ed25519 (which should be sufficient
// as-is to prove multiexp), and this doesn't hurt
pub fn test_multiexp<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
let mut pairs = Vec::with_capacity(1000);
let mut sum = C::G::identity();
for _ in 0 .. 10 {
for _ in 0 .. 100 {
pairs.push((C::F::random(&mut *rng), C::generator() * C::F::random(&mut *rng)));
sum += pairs[pairs.len() - 1].1 * pairs[pairs.len() - 1].0;
}
assert_eq!(multiexp::multiexp(&pairs), sum);
assert_eq!(multiexp::multiexp_vartime(&pairs), sum);
}
}
pub fn test_curve<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
// TODO: Test the Curve functions themselves
// Test successful multiexp, with enough pairs to trigger its variety of algorithms
// Multiexp has its own tests, yet only against k256 and Ed25519 (which should be sufficient
// as-is to prove multiexp), and this doesn't hurt
{
let mut pairs = Vec::with_capacity(1000);
let mut sum = C::G::identity();
for _ in 0 .. 10 {
for _ in 0 .. 100 {
pairs.push((C::F::random(&mut *rng), C::generator() * C::F::random(&mut *rng)));
sum += pairs[pairs.len() - 1].1 * pairs[pairs.len() - 1].0;
}
assert_eq!(multiexp::multiexp(&pairs), sum);
assert_eq!(multiexp::multiexp_vartime(&pairs), sum);
}
}
test_multiexp::<_, C>(rng);
// Test FROST key generation and serialization of FrostCore works as expected
key_generation::<_, C>(rng);

View File

@@ -0,0 +1,132 @@
use std::io::Cursor;
use rand_core::OsRng;
use crate::{
curve::{Curve, Ed448, Ietf8032Ed448Hram, NonIetfEd448Hram},
schnorr::{SchnorrSignature, verify},
tests::vectors::{Vectors, test_with_vectors},
};
#[test]
fn ed448_8032_vector() {
let context = hex::decode("666f6f").unwrap();
#[allow(non_snake_case)]
let A = Ed448::read_G(&mut Cursor::new(
hex::decode(
"43ba28f430cdff456ae531545f7ecd0ac834a55d9358c0372bfa0c6c".to_owned() +
"6798c0866aea01eb00742802b8438ea4cb82169c235160627b4c3a94" +
"80",
)
.unwrap(),
))
.unwrap();
let msg = hex::decode("03").unwrap();
let mut sig = Cursor::new(
hex::decode(
"d4f8f6131770dd46f40867d6fd5d5055de43541f8c5e35abbcd001b3".to_owned() +
"2a89f7d2151f7647f11d8ca2ae279fb842d607217fce6e042f6815ea" +
"00" +
"0c85741de5c8da1144a6a1aba7f96de42505d7a7298524fda538fccb" +
"bb754f578c1cad10d54d0d5428407e85dcbc98a49155c13764e66c3c" +
"00",
)
.unwrap(),
);
#[allow(non_snake_case)]
let R = Ed448::read_G(&mut sig).unwrap();
let s = Ed448::read_F(&mut sig).unwrap();
assert!(verify(
A,
Ietf8032Ed448Hram::hram(&context, &R, &A, &msg),
&SchnorrSignature::<Ed448> { R, s }
));
}
#[test]
fn ed448_non_ietf() {
test_with_vectors::<_, Ed448, NonIetfEd448Hram>(
&mut OsRng,
Vectors {
threshold: 2,
shares: &[
concat!(
"4a2b2f5858a932ad3d3b18bd16e76ced3070d72fd79ae4402df201f5",
"25e754716a1bc1b87a502297f2a99d89ea054e0018eb55d39562fd01",
"00"
),
concat!(
"2503d56c4f516444a45b080182b8a2ebbe4d9b2ab509f25308c88c0e",
"a7ccdc44e2ef4fc4f63403a11b116372438a1e287265cadeff1fcb07",
"00"
),
concat!(
"00db7a8146f995db0a7cf844ed89d8e94c2b5f259378ff66e39d1728",
"28b264185ac4decf7219e4aa4478285b9c0eef4fccdf3eea69dd980d",
"00"
),
],
group_secret: concat!(
"6298e1eef3c379392caaed061ed8a31033c9e9e3420726f23b404158",
"a401cd9df24632adfe6b418dc942d8a091817dd8bd70e1c72ba52f3c",
"00"
),
group_key: concat!(
"3832f82fda00ff5365b0376df705675b63d2a93c24c6e81d40801ba2",
"65632be10f443f95968fadb70d10786827f30dc001c8d0f9b7c1d1b0",
"00"
),
msg: "74657374",
included: &[1, 3],
nonces: &[
[
concat!(
"afa99ad5138f89d064c828ecb17accde77e4dc52e017c20b34d1db11",
"bdd0b17d2f4ec6ea7d5414df33977267c49b8d4b3b35c7f4a089db2f",
"00"
),
concat!(
"c9c2f6119d5a7f60fc1a3517f08f3aced6f84f53cbcfa4709080858d",
"b8c8b49d4cb9921c4118f1961d4fb653ad5e320d175de3ee5258e904",
"00"
),
],
[
concat!(
"a575cf9ae013b63204a56cc0bb0c21184eed6e42f448344e59153cf4",
"3798ad3b8c300a2c0ffa04ee7228a5c4ff84fcad4cf9616d1cd7fe0a",
"00"
),
concat!(
"12419016a6c0d38a1d9d1eeb1455525d73a464113a9323fcfc75e5fb",
"7c1f17ad71ca2f2852b71f33950adedd7f8489551ad356ecf39a4d29",
"00"
),
],
],
sig_shares: &[
concat!(
"e88d1e9743ac059553de940131508205eff504816935f8c9d22a29df",
"4c541e4bb55d4c4a5c58dd65e6d2c421e35f2ddc7ea11095cffb3b16",
"00"
),
concat!(
"d6ae2965ee86f925d38eedf0690ee54395243d244b59a5fece45cece",
"721867a00a6c7af9635c621ea09edad8fc26db5de4ce3aa4e7e7ea3f",
"00"
),
],
sig: "c07db58a26bd0c33930455f1923df2ffa50c3a1679e06a1940f84e0e".to_owned() +
"067bcec3e46008c3b4018b7b2563ba0f26740b7b5932883355e569f5" +
"00" +
"cbf7ef509f708697d1ddbc64289cfa27f4e36bf66ab34e04b84c2d31" +
"c06c85ebbfc9c643c0b43f8486719ffadf86083a63704b39b7e32616" +
"00",
},
);
}

View File

@@ -1,6 +1,5 @@
use rand_core::OsRng;
#[cfg(any(feature = "secp256k1", feature = "p256"))]
use crate::tests::vectors::{Vectors, test_with_vectors};
#[cfg(feature = "secp256k1")]
@@ -11,7 +10,7 @@ use crate::curve::{P256, IetfP256Hram};
#[cfg(feature = "secp256k1")]
#[test]
fn secp256k1_ietf() {
fn secp256k1_vectors() {
test_with_vectors::<_, Secp256k1, IetfSecp256k1Hram>(
&mut OsRng,
Vectors {

View File

@@ -2,3 +2,5 @@
mod dalek;
#[cfg(feature = "kp256")]
mod kp256;
#[cfg(feature = "ed448")]
mod ed448;