mirror of
https://github.com/serai-dex/serai.git
synced 2024-11-16 17:07:35 +00:00
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:
parent
f71f19e26c
commit
081b9a1975
20 changed files with 975 additions and 32 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
@ -4492,6 +4492,24 @@ version = "0.3.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-ed448"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-bigint",
|
||||||
|
"dalek-ff-group",
|
||||||
|
"digest 0.10.3",
|
||||||
|
"ff",
|
||||||
|
"generic-array 0.14.6",
|
||||||
|
"group",
|
||||||
|
"hex",
|
||||||
|
"hex-literal",
|
||||||
|
"lazy_static",
|
||||||
|
"rand_core 0.6.3",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimal-lexical"
|
name = "minimal-lexical"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -4531,10 +4549,12 @@ dependencies = [
|
||||||
"group",
|
"group",
|
||||||
"hex",
|
"hex",
|
||||||
"k256",
|
"k256",
|
||||||
|
"minimal-ed448",
|
||||||
"multiexp",
|
"multiexp",
|
||||||
"p256",
|
"p256",
|
||||||
"rand_core 0.6.3",
|
"rand_core 0.6.3",
|
||||||
"sha2 0.10.2",
|
"sha2 0.10.2",
|
||||||
|
"sha3 0.10.2",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
|
@ -3,6 +3,8 @@ members = [
|
||||||
"crypto/transcript",
|
"crypto/transcript",
|
||||||
|
|
||||||
"crypto/dalek-ff-group",
|
"crypto/dalek-ff-group",
|
||||||
|
"crypto/ed448",
|
||||||
|
|
||||||
"crypto/multiexp",
|
"crypto/multiexp",
|
||||||
|
|
||||||
"crypto/dleq",
|
"crypto/dleq",
|
||||||
|
@ -33,6 +35,7 @@ group = { opt-level = 3 }
|
||||||
|
|
||||||
crypto-bigint = { opt-level = 3 }
|
crypto-bigint = { opt-level = 3 }
|
||||||
dalek-ff-group = { opt-level = 3 }
|
dalek-ff-group = { opt-level = 3 }
|
||||||
|
minimal-ed448 = { opt-level = 3 }
|
||||||
|
|
||||||
multiexp = { opt-level = 3 }
|
multiexp = { opt-level = 3 }
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crypto_bigint::{Encoding, U256, U512};
|
||||||
|
|
||||||
use ff::{Field, PrimeField, FieldBits, PrimeFieldBits};
|
use ff::{Field, PrimeField, FieldBits, PrimeFieldBits};
|
||||||
|
|
||||||
use crate::{choice, constant_time, math_op, math, from_wrapper, from_uint};
|
use crate::{choice, constant_time, math, from_uint};
|
||||||
|
|
||||||
const FIELD_MODULUS: U256 =
|
const FIELD_MODULUS: U256 =
|
||||||
U256::from_be_hex("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed");
|
U256::from_be_hex("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed");
|
||||||
|
|
|
@ -122,7 +122,7 @@ macro_rules! math_op {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[macro_export]
|
#[macro_export(local_inner_macros)]
|
||||||
macro_rules! math {
|
macro_rules! math {
|
||||||
($Value: ident, $Factor: ident, $add: expr, $sub: expr, $mul: expr) => {
|
($Value: ident, $Factor: ident, $add: expr, $sub: expr, $mul: expr) => {
|
||||||
math_op!($Value, $Value, Add, add, AddAssign, add_assign, $add);
|
math_op!($Value, $Value, Add, add, AddAssign, add_assign, $add);
|
||||||
|
@ -131,6 +131,8 @@ macro_rules! math {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[macro_export(local_inner_macros)]
|
||||||
macro_rules! math_neg {
|
macro_rules! math_neg {
|
||||||
($Value: ident, $Factor: ident, $add: expr, $sub: expr, $mul: expr) => {
|
($Value: ident, $Factor: ident, $add: expr, $sub: expr, $mul: expr) => {
|
||||||
math!($Value, $Factor, $add, $sub, $mul);
|
math!($Value, $Factor, $add, $sub, $mul);
|
||||||
|
@ -157,7 +159,7 @@ macro_rules! from_wrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[macro_export]
|
#[macro_export(local_inner_macros)]
|
||||||
macro_rules! from_uint {
|
macro_rules! from_uint {
|
||||||
($wrapper: ident, $inner: ident) => {
|
($wrapper: ident, $inner: ident) => {
|
||||||
from_wrapper!($wrapper, $inner, u8);
|
from_wrapper!($wrapper, $inner, u8);
|
||||||
|
|
30
crypto/ed448/Cargo.toml
Normal file
30
crypto/ed448/Cargo.toml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
[package]
|
||||||
|
name = "minimal-ed448"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Unaudited, inefficient implementation of Ed448 in Rust"
|
||||||
|
license = "MIT"
|
||||||
|
repository = "https://github.com/serai-dex/serai"
|
||||||
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
|
keywords = ["ed448", "ff", "group"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
hex-literal = "0.3"
|
||||||
|
lazy_static = "1"
|
||||||
|
|
||||||
|
rand_core = "0.6"
|
||||||
|
digest = "0.10"
|
||||||
|
|
||||||
|
zeroize = { version = "1.3", features = ["zeroize_derive"] }
|
||||||
|
subtle = "2.4"
|
||||||
|
|
||||||
|
ff = "0.12"
|
||||||
|
group = "0.12"
|
||||||
|
|
||||||
|
generic-array = "0.14"
|
||||||
|
crypto-bigint = {version = "0.4", features = ["zeroize"] }
|
||||||
|
|
||||||
|
dalek-ff-group = { path = "../dalek-ff-group", version = "^0.1.2" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
hex = "0.4"
|
21
crypto/ed448/LICENSE
Normal file
21
crypto/ed448/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.
|
9
crypto/ed448/README.md
Normal file
9
crypto/ed448/README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# Minimal Ed448
|
||||||
|
|
||||||
|
Inefficient, barebones implementation of Ed448 bound to the ff/group API,
|
||||||
|
rejecting torsion to achieve a PrimeGroup definition. This likely should not be
|
||||||
|
used and was only done so another library under Serai could confirm its
|
||||||
|
completion. It is minimally tested, yet should be correct for what it has.
|
||||||
|
Multiple functions remain unimplemented.
|
||||||
|
|
||||||
|
constant time and no_std.
|
165
crypto/ed448/src/backend.rs
Normal file
165
crypto/ed448/src/backend.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
64
crypto/ed448/src/field.rs
Normal file
64
crypto/ed448/src/field.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
use core::ops::Div;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
use crypto_bigint::{NonZero, U512, U1024};
|
||||||
|
|
||||||
|
use crate::field;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Zeroize)]
|
||||||
|
pub struct FieldElement(pub(crate) U512);
|
||||||
|
|
||||||
|
// 2**448 - 2**224 - 1
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref MODULUS: FieldElement = FieldElement(U512::from_be_hex(concat!(
|
||||||
|
"00000000000000",
|
||||||
|
"00",
|
||||||
|
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffe",
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
)));
|
||||||
|
static ref WIDE_MODULUS: U1024 = {
|
||||||
|
let res = U1024::from((U512::ZERO, MODULUS.0));
|
||||||
|
debug_assert_eq!(MODULUS.0.to_le_bytes()[..], res.to_le_bytes()[.. 64]);
|
||||||
|
res
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
field!(FieldElement, MODULUS, WIDE_MODULUS, 448);
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub(crate) static ref Q_4: FieldElement = FieldElement(
|
||||||
|
MODULUS.0.saturating_add(&U512::ONE).div(NonZero::new(TWO.0.saturating_add(&TWO.0)).unwrap())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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());
|
||||||
|
}
|
6
crypto/ed448/src/lib.rs
Normal file
6
crypto/ed448/src/lib.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
mod backend;
|
||||||
|
pub mod scalar;
|
||||||
|
pub mod field;
|
||||||
|
pub mod point;
|
380
crypto/ed448/src/point.rs
Normal file
380
crypto/ed448/src/point.rs
Normal file
|
@ -0,0 +1,380 @@
|
||||||
|
use core::{
|
||||||
|
ops::{Add, AddAssign, Neg, Sub, SubAssign, Mul, MulAssign},
|
||||||
|
iter::Sum,
|
||||||
|
};
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
use rand_core::RngCore;
|
||||||
|
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
use subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable, ConditionallyNegatable};
|
||||||
|
|
||||||
|
use ff::{Field, PrimeField, PrimeFieldBits};
|
||||||
|
use group::{Group, GroupEncoding, prime::PrimeGroup};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
scalar::{Scalar, MODULUS as SCALAR_MODULUS},
|
||||||
|
field::{FieldElement, Q_4},
|
||||||
|
};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref D: FieldElement = -FieldElement::from(39081u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recover_x(y: FieldElement) -> CtOption<FieldElement> {
|
||||||
|
let ysq = y.square();
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let D_ysq = *D * ysq;
|
||||||
|
(D_ysq - FieldElement::one()).invert().and_then(|inverted| {
|
||||||
|
let temp = (ysq - FieldElement::one()) * inverted;
|
||||||
|
let mut x = temp.pow(*Q_4);
|
||||||
|
x.conditional_negate(x.is_odd());
|
||||||
|
|
||||||
|
let xsq = x.square();
|
||||||
|
CtOption::new(x, (xsq + ysq).ct_eq(&(FieldElement::one() + (xsq * D_ysq))))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Zeroize)]
|
||||||
|
pub struct Point {
|
||||||
|
x: FieldElement,
|
||||||
|
y: FieldElement,
|
||||||
|
z: FieldElement,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
lazy_static! {
|
||||||
|
static ref G_Y: FieldElement = FieldElement::from_repr(
|
||||||
|
hex_literal::hex!(
|
||||||
|
"14fa30f25b790898adc8d74e2c13bdfdc4397ce61cffd33ad7c2a0051e9c78874098a36c7373ea4b62c7c9563720768824bcb66e71463f6900"
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
static ref G: Point = Point { x: recover_x(*G_Y).unwrap(), y: *G_Y, z: FieldElement::one() };
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConstantTimeEq for Point {
|
||||||
|
fn ct_eq(&self, other: &Self) -> Choice {
|
||||||
|
let x1 = self.x * other.z;
|
||||||
|
let x2 = other.x * self.z;
|
||||||
|
|
||||||
|
let y1 = self.y * other.z;
|
||||||
|
let y2 = other.y * self.z;
|
||||||
|
|
||||||
|
x1.ct_eq(&x2) & y1.ct_eq(&y2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Point {
|
||||||
|
fn eq(&self, other: &Point) -> bool {
|
||||||
|
self.ct_eq(other).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Point {}
|
||||||
|
|
||||||
|
impl ConditionallySelectable for Point {
|
||||||
|
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
|
||||||
|
Point {
|
||||||
|
x: FieldElement::conditional_select(&a.x, &b.x, choice),
|
||||||
|
y: FieldElement::conditional_select(&a.y, &b.y, choice),
|
||||||
|
z: FieldElement::conditional_select(&a.z, &b.z, choice),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for Point {
|
||||||
|
type Output = Point;
|
||||||
|
fn add(self, other: Self) -> Self {
|
||||||
|
// 12 muls, 7 additions, 4 negations
|
||||||
|
let xcp = self.x * other.x;
|
||||||
|
let ycp = self.y * other.y;
|
||||||
|
let zcp = self.z * other.z;
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let B = zcp.square();
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let E = *D * xcp * ycp;
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let F = B - E;
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let G_ = B + E;
|
||||||
|
|
||||||
|
Point {
|
||||||
|
x: zcp * F * ((self.x + self.y) * (other.x + other.y) - xcp - ycp),
|
||||||
|
y: zcp * G_ * (ycp - xcp),
|
||||||
|
z: F * G_,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign for Point {
|
||||||
|
fn add_assign(&mut self, other: Point) {
|
||||||
|
*self = *self + other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<&Point> for Point {
|
||||||
|
type Output = Point;
|
||||||
|
fn add(self, other: &Point) -> Point {
|
||||||
|
self + *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign<&Point> for Point {
|
||||||
|
fn add_assign(&mut self, other: &Point) {
|
||||||
|
*self += *other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Neg for Point {
|
||||||
|
type Output = Point;
|
||||||
|
fn neg(self) -> Self {
|
||||||
|
Point { x: -self.x, y: self.y, z: self.z }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub for Point {
|
||||||
|
type Output = Point;
|
||||||
|
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||||
|
fn sub(self, other: Self) -> Self {
|
||||||
|
self + other.neg()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubAssign for Point {
|
||||||
|
fn sub_assign(&mut self, other: Point) {
|
||||||
|
*self = *self - other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub<&Point> for Point {
|
||||||
|
type Output = Point;
|
||||||
|
fn sub(self, other: &Point) -> Point {
|
||||||
|
self - *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubAssign<&Point> for Point {
|
||||||
|
fn sub_assign(&mut self, other: &Point) {
|
||||||
|
*self -= *other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Group for Point {
|
||||||
|
type Scalar = Scalar;
|
||||||
|
// Ideally, this would be cryptographically secure, yet that's not a bound on the trait
|
||||||
|
// k256 also does this
|
||||||
|
fn random(rng: impl RngCore) -> Self {
|
||||||
|
Self::generator() * Scalar::random(rng)
|
||||||
|
}
|
||||||
|
fn identity() -> Self {
|
||||||
|
Point { x: FieldElement::zero(), y: FieldElement::one(), z: FieldElement::one() }
|
||||||
|
}
|
||||||
|
fn generator() -> Self {
|
||||||
|
*G
|
||||||
|
}
|
||||||
|
fn is_identity(&self) -> Choice {
|
||||||
|
self.ct_eq(&Self::identity())
|
||||||
|
}
|
||||||
|
fn double(&self) -> Self {
|
||||||
|
// 7 muls, 7 additions, 4 negations
|
||||||
|
let xsq = self.x.square();
|
||||||
|
let ysq = self.y.square();
|
||||||
|
let zsq = self.z.square();
|
||||||
|
let xy = self.x + self.y;
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let F = xsq + ysq;
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
let J = F - zsq.double();
|
||||||
|
Point { x: J * (xy.square() - xsq - ysq), y: F * (xsq - ysq), z: F * J }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sum<Point> for Point {
|
||||||
|
fn sum<I: Iterator<Item = Point>>(iter: I) -> Point {
|
||||||
|
let mut res = Self::identity();
|
||||||
|
for i in iter {
|
||||||
|
res += i;
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Sum<&'a Point> for Point {
|
||||||
|
fn sum<I: Iterator<Item = &'a Point>>(iter: I) -> Point {
|
||||||
|
Point::sum(iter.cloned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<Scalar> for Point {
|
||||||
|
type Output = Point;
|
||||||
|
fn mul(self, other: Scalar) -> Point {
|
||||||
|
// Precompute the optimal amount that's a multiple of 2
|
||||||
|
let mut table = [Point::identity(); 16];
|
||||||
|
table[1] = self;
|
||||||
|
for i in 2 .. 16 {
|
||||||
|
table[i] = table[i - 1] + self;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut res = Self::identity();
|
||||||
|
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.double();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res += table[usize::from(bits)];
|
||||||
|
bits = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MulAssign<Scalar> for Point {
|
||||||
|
fn mul_assign(&mut self, other: Scalar) {
|
||||||
|
*self = *self * other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<&Scalar> for Point {
|
||||||
|
type Output = Point;
|
||||||
|
fn mul(self, other: &Scalar) -> Point {
|
||||||
|
self * *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MulAssign<&Scalar> for Point {
|
||||||
|
fn mul_assign(&mut self, other: &Scalar) {
|
||||||
|
*self *= *other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Point {
|
||||||
|
pub fn is_torsion_free(&self) -> Choice {
|
||||||
|
(*self * *SCALAR_MODULUS).is_identity()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GroupEncoding for Point {
|
||||||
|
type Repr = <FieldElement as PrimeField>::Repr;
|
||||||
|
|
||||||
|
fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
|
||||||
|
// Extract and clear the sign bit
|
||||||
|
let sign = Choice::from(bytes[56] >> 7);
|
||||||
|
let mut bytes = *bytes;
|
||||||
|
let mut_ref: &mut [u8] = bytes.as_mut();
|
||||||
|
mut_ref[56] &= !(1 << 7);
|
||||||
|
|
||||||
|
// Parse y, recover x
|
||||||
|
FieldElement::from_repr(bytes).and_then(|y| {
|
||||||
|
recover_x(y).and_then(|mut x| {
|
||||||
|
x.conditional_negate(x.is_odd().ct_eq(&!sign));
|
||||||
|
let not_negative_zero = !(x.is_zero() & sign);
|
||||||
|
let point = Point { x, y, z: FieldElement::one() };
|
||||||
|
CtOption::new(point, not_negative_zero & point.is_torsion_free())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
|
||||||
|
Point::from_bytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_bytes(&self) -> Self::Repr {
|
||||||
|
let z = self.z.invert().unwrap();
|
||||||
|
let x = self.x * z;
|
||||||
|
let y = self.y * z;
|
||||||
|
|
||||||
|
let mut bytes = y.to_repr();
|
||||||
|
let mut_ref: &mut [u8] = bytes.as_mut();
|
||||||
|
mut_ref[56] |= x.is_odd().unwrap_u8() << 7;
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn addition_multiplication_serialization() {
|
||||||
|
let mut accum = Point::identity();
|
||||||
|
for x in 1 .. 10 {
|
||||||
|
accum += Point::generator();
|
||||||
|
let mul = Point::generator() * Scalar::from(x as u8);
|
||||||
|
assert_eq!(accum, mul);
|
||||||
|
assert_eq!(Point::from_bytes(&mul.to_bytes()).unwrap(), mul);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[test]
|
||||||
|
fn torsion() {
|
||||||
|
// Uses the originally suggested generator which had torsion
|
||||||
|
let old_y = FieldElement::from_repr(
|
||||||
|
hex_literal::hex!(
|
||||||
|
"12796c1532041525945f322e414d434467cfd5c57c9a9af2473b27758c921c4828b277ca5f2891fc4f3d79afdf29a64c72fb28b59c16fa5100"
|
||||||
|
).into(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let old = Point { x: -recover_x(old_y).unwrap(), y: old_y, z: FieldElement::one() };
|
||||||
|
assert!(bool::from(!old.is_torsion_free()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vector() {
|
||||||
|
use generic_array::GenericArray;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Point::generator().double(),
|
||||||
|
Point::from_bytes(GenericArray::from_slice(
|
||||||
|
&hex::decode(
|
||||||
|
"\
|
||||||
|
ed8693eacdfbeada6ba0cdd1beb2bcbb98302a3a8365650db8c4d88a\
|
||||||
|
726de3b7d74d8835a0d76e03b0c2865020d659b38d04d74a63e905ae\
|
||||||
|
80"
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
))
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Point::generator() *
|
||||||
|
Scalar::from_repr(*GenericArray::from_slice(
|
||||||
|
&hex::decode(
|
||||||
|
"\
|
||||||
|
6298e1eef3c379392caaed061ed8a31033c9e9e3420726f23b404158\
|
||||||
|
a401cd9df24632adfe6b418dc942d8a091817dd8bd70e1c72ba52f3c\
|
||||||
|
00"
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
))
|
||||||
|
.unwrap(),
|
||||||
|
Point::from_bytes(GenericArray::from_slice(
|
||||||
|
&hex::decode(
|
||||||
|
"\
|
||||||
|
3832f82fda00ff5365b0376df705675b63d2a93c24c6e81d40801ba2\
|
||||||
|
65632be10f443f95968fadb70d10786827f30dc001c8d0f9b7c1d1b0\
|
||||||
|
00"
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
))
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
38
crypto/ed448/src/scalar.rs
Normal file
38
crypto/ed448/src/scalar.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
use crypto_bigint::{U512, U1024};
|
||||||
|
|
||||||
|
pub use crate::field;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Zeroize)]
|
||||||
|
pub struct Scalar(pub(crate) U512);
|
||||||
|
|
||||||
|
// 2**446 - 13818066809895115352007386748515426880336692474882178609894547503885
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref MODULUS: Scalar = Scalar(U512::from_be_hex(concat!(
|
||||||
|
"00000000000000",
|
||||||
|
"00",
|
||||||
|
"3fffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3",
|
||||||
|
)));
|
||||||
|
static ref WIDE_MODULUS: U1024 = {
|
||||||
|
let res = U1024::from((U512::ZERO, MODULUS.0));
|
||||||
|
debug_assert_eq!(MODULUS.0.to_le_bytes()[..], res.to_le_bytes()[.. 64]);
|
||||||
|
res
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
field!(Scalar, MODULUS, WIDE_MODULUS, 446);
|
||||||
|
|
||||||
|
impl Scalar {
|
||||||
|
pub fn wide_reduce(bytes: [u8; 114]) -> Scalar {
|
||||||
|
Scalar(reduce(U1024::from_le_slice(&[bytes.as_ref(), &[0; 14]].concat())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invert() {
|
||||||
|
assert_eq!(Scalar::one().invert().unwrap(), Scalar::one());
|
||||||
|
}
|
|
@ -18,14 +18,18 @@ zeroize = { version = "1.3", features = ["zeroize_derive"] }
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
|
||||||
sha2 = { version = "0.10", optional = true }
|
sha2 = { version = "0.10", optional = true }
|
||||||
|
sha3 = { version = "0.10", optional = true }
|
||||||
|
|
||||||
ff = "0.12"
|
ff = "0.12"
|
||||||
group = "0.12"
|
group = "0.12"
|
||||||
|
|
||||||
|
dalek-ff-group = { path = "../dalek-ff-group", version = "^0.1.2", optional = true }
|
||||||
|
|
||||||
elliptic-curve = { version = "0.12", features = ["hash2curve"], optional = true }
|
elliptic-curve = { version = "0.12", features = ["hash2curve"], optional = true }
|
||||||
p256 = { version = "0.11", features = ["arithmetic", "bits", "hash2curve"], optional = true }
|
p256 = { version = "0.11", features = ["arithmetic", "bits", "hash2curve"], optional = true }
|
||||||
k256 = { version = "0.11", features = ["arithmetic", "bits", "hash2curve"], optional = true }
|
k256 = { version = "0.11", features = ["arithmetic", "bits", "hash2curve"], optional = true }
|
||||||
dalek-ff-group = { path = "../dalek-ff-group", version = "^0.1.2", optional = true }
|
|
||||||
|
minimal-ed448 = { path = "../ed448", version = "0.1", optional = true }
|
||||||
|
|
||||||
transcript = { package = "flexible-transcript", path = "../transcript", features = ["recommended"], version = "^0.1.3" }
|
transcript = { package = "flexible-transcript", path = "../transcript", features = ["recommended"], version = "^0.1.3" }
|
||||||
|
|
||||||
|
@ -38,10 +42,12 @@ sha2 = "0.10"
|
||||||
dalek-ff-group = { path = "../dalek-ff-group" }
|
dalek-ff-group = { path = "../dalek-ff-group" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
curves = ["sha2"] # All officially denoted curves use the SHA2 family of hashes
|
dalek = ["sha2", "dalek-ff-group"]
|
||||||
kp256 = ["elliptic-curve", "curves"]
|
|
||||||
p256 = ["kp256", "dep:p256"]
|
|
||||||
secp256k1 = ["kp256", "k256"]
|
|
||||||
dalek = ["curves", "dalek-ff-group"]
|
|
||||||
ed25519 = ["dalek"]
|
ed25519 = ["dalek"]
|
||||||
ristretto = ["dalek"]
|
ristretto = ["dalek"]
|
||||||
|
|
||||||
|
kp256 = ["sha2", "elliptic-curve"]
|
||||||
|
p256 = ["kp256", "dep:p256"]
|
||||||
|
secp256k1 = ["kp256", "k256"]
|
||||||
|
|
||||||
|
ed448 = ["sha3", "minimal-ed448"]
|
||||||
|
|
|
@ -2,6 +2,7 @@ use zeroize::Zeroize;
|
||||||
|
|
||||||
use sha2::{Digest, Sha512};
|
use sha2::{Digest, Sha512};
|
||||||
|
|
||||||
|
use group::Group;
|
||||||
use dalek_ff_group::Scalar;
|
use dalek_ff_group::Scalar;
|
||||||
|
|
||||||
use crate::{curve::Curve, algorithm::Hram};
|
use crate::{curve::Curve, algorithm::Hram};
|
||||||
|
@ -12,13 +13,11 @@ macro_rules! dalek_curve {
|
||||||
$Hram: ident,
|
$Hram: ident,
|
||||||
$Point: ident,
|
$Point: ident,
|
||||||
|
|
||||||
$POINT: ident,
|
|
||||||
|
|
||||||
$ID: literal,
|
$ID: literal,
|
||||||
$CONTEXT: literal,
|
$CONTEXT: literal,
|
||||||
$chal: literal,
|
$chal: literal,
|
||||||
) => {
|
) => {
|
||||||
use dalek_ff_group::{$Point, $POINT};
|
use dalek_ff_group::$Point;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||||
pub struct $Curve;
|
pub struct $Curve;
|
||||||
|
@ -35,7 +34,7 @@ macro_rules! dalek_curve {
|
||||||
const ID: &'static [u8] = $ID;
|
const ID: &'static [u8] = $ID;
|
||||||
|
|
||||||
fn generator() -> Self::G {
|
fn generator() -> Self::G {
|
||||||
$POINT
|
$Point::generator()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash_to_vec(dst: &[u8], data: &[u8]) -> Vec<u8> {
|
fn hash_to_vec(dst: &[u8], data: &[u8]) -> Vec<u8> {
|
||||||
|
@ -69,7 +68,6 @@ dalek_curve!(
|
||||||
Ristretto,
|
Ristretto,
|
||||||
IetfRistrettoHram,
|
IetfRistrettoHram,
|
||||||
RistrettoPoint,
|
RistrettoPoint,
|
||||||
RISTRETTO_BASEPOINT_POINT,
|
|
||||||
b"ristretto",
|
b"ristretto",
|
||||||
b"FROST-RISTRETTO255-SHA512-v8",
|
b"FROST-RISTRETTO255-SHA512-v8",
|
||||||
b"chal",
|
b"chal",
|
||||||
|
@ -80,7 +78,6 @@ dalek_curve!(
|
||||||
Ed25519,
|
Ed25519,
|
||||||
IetfEd25519Hram,
|
IetfEd25519Hram,
|
||||||
EdwardsPoint,
|
EdwardsPoint,
|
||||||
ED25519_BASEPOINT_POINT,
|
|
||||||
b"edwards25519",
|
b"edwards25519",
|
||||||
b"FROST-ED25519-SHA512-v8",
|
b"FROST-ED25519-SHA512-v8",
|
||||||
b"",
|
b"",
|
||||||
|
|
62
crypto/frost/src/curve/ed448.rs
Normal file
62
crypto/frost/src/curve/ed448.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,11 @@ pub use kp256::{Secp256k1, IetfSecp256k1Hram};
|
||||||
#[cfg(feature = "p256")]
|
#[cfg(feature = "p256")]
|
||||||
pub use kp256::{P256, IetfP256Hram};
|
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
|
/// Set of errors for curve-related operations, namely encoding and decoding
|
||||||
#[derive(Clone, Error, Debug)]
|
#[derive(Clone, Error, Debug)]
|
||||||
pub enum CurveError {
|
pub enum CurveError {
|
||||||
|
|
|
@ -19,13 +19,10 @@ fn keys_serialization<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_curve<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
// Test successful multiexp, with enough pairs to trigger its variety of algorithms
|
||||||
// TODO: Test the Curve functions themselves
|
// 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
|
||||||
// Test successful multiexp, with enough pairs to trigger its variety of algorithms
|
pub fn test_multiexp<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
||||||
// 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 pairs = Vec::with_capacity(1000);
|
||||||
let mut sum = C::G::identity();
|
let mut sum = C::G::identity();
|
||||||
for _ in 0 .. 10 {
|
for _ in 0 .. 10 {
|
||||||
|
@ -36,7 +33,12 @@ pub fn test_curve<R: RngCore + CryptoRng, C: Curve>(rng: &mut R) {
|
||||||
assert_eq!(multiexp::multiexp(&pairs), sum);
|
assert_eq!(multiexp::multiexp(&pairs), sum);
|
||||||
assert_eq!(multiexp::multiexp_vartime(&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_multiexp::<_, C>(rng);
|
||||||
|
|
||||||
// Test FROST key generation and serialization of FrostCore works as expected
|
// Test FROST key generation and serialization of FrostCore works as expected
|
||||||
key_generation::<_, C>(rng);
|
key_generation::<_, C>(rng);
|
||||||
|
|
132
crypto/frost/src/tests/literal/ed448.rs
Normal file
132
crypto/frost/src/tests/literal/ed448.rs
Normal 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",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
use rand_core::OsRng;
|
use rand_core::OsRng;
|
||||||
|
|
||||||
#[cfg(any(feature = "secp256k1", feature = "p256"))]
|
|
||||||
use crate::tests::vectors::{Vectors, test_with_vectors};
|
use crate::tests::vectors::{Vectors, test_with_vectors};
|
||||||
|
|
||||||
#[cfg(feature = "secp256k1")]
|
#[cfg(feature = "secp256k1")]
|
||||||
|
@ -11,7 +10,7 @@ use crate::curve::{P256, IetfP256Hram};
|
||||||
|
|
||||||
#[cfg(feature = "secp256k1")]
|
#[cfg(feature = "secp256k1")]
|
||||||
#[test]
|
#[test]
|
||||||
fn secp256k1_ietf() {
|
fn secp256k1_vectors() {
|
||||||
test_with_vectors::<_, Secp256k1, IetfSecp256k1Hram>(
|
test_with_vectors::<_, Secp256k1, IetfSecp256k1Hram>(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
Vectors {
|
Vectors {
|
||||||
|
|
|
@ -2,3 +2,5 @@
|
||||||
mod dalek;
|
mod dalek;
|
||||||
#[cfg(feature = "kp256")]
|
#[cfg(feature = "kp256")]
|
||||||
mod kp256;
|
mod kp256;
|
||||||
|
#[cfg(feature = "ed448")]
|
||||||
|
mod ed448;
|
||||||
|
|
Loading…
Reference in a new issue