mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-11 13:39:25 +00:00
Add Ed25519 to FROST and remove expand_xmd for elliptic_curve's
Doesn't fully utilize ec's hash2curve module as k256 Scalar doesn't have FromOkm for some reason. The previously present bigint reduction is preserved. Updates ff/group to 0.12. Premised on https://github.com/cfrg/draft-irtf-cfrg-frost/pull/205 being merged, as while this Ed25519 is vector compliant, it's technically not spec compliant due to that conflict.
This commit is contained in:
104
crypto/frost/src/curves/ed25519.rs
Normal file
104
crypto/frost/src/curves/ed25519.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use core::convert::TryInto;
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use sha2::{Digest, Sha512};
|
||||
|
||||
use ff::PrimeField;
|
||||
use group::Group;
|
||||
|
||||
use dalek_ff_group::{
|
||||
EdwardsBasepointTable,
|
||||
ED25519_BASEPOINT_POINT, ED25519_BASEPOINT_TABLE,
|
||||
Scalar, EdwardsPoint, CompressedEdwardsY
|
||||
};
|
||||
|
||||
use crate::{CurveError, Curve, algorithm::Hram};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct Ed25519;
|
||||
impl Curve for Ed25519 {
|
||||
type F = Scalar;
|
||||
type G = EdwardsPoint;
|
||||
type T = &'static EdwardsBasepointTable;
|
||||
|
||||
const ID: &'static [u8] = b"edwards25519";
|
||||
|
||||
const GENERATOR: Self::G = ED25519_BASEPOINT_POINT;
|
||||
const GENERATOR_TABLE: Self::T = &ED25519_BASEPOINT_TABLE;
|
||||
|
||||
const LITTLE_ENDIAN: bool = true;
|
||||
|
||||
fn random_nonce<R: RngCore + CryptoRng>(secret: Self::F, rng: &mut R) -> Self::F {
|
||||
let mut seed = vec![0; 32];
|
||||
rng.fill_bytes(&mut seed);
|
||||
seed.extend(&secret.to_bytes());
|
||||
Self::hash_to_F(b"nonce", &seed)
|
||||
}
|
||||
|
||||
fn hash_msg(msg: &[u8]) -> Vec<u8> {
|
||||
Sha512::digest(msg).to_vec()
|
||||
}
|
||||
|
||||
fn hash_binding_factor(binding: &[u8]) -> Self::F {
|
||||
Self::hash_to_F(b"rho", binding)
|
||||
}
|
||||
|
||||
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
|
||||
Scalar::from_hash(Sha512::new().chain_update(dst).chain_update(msg))
|
||||
}
|
||||
|
||||
fn F_len() -> usize {
|
||||
32
|
||||
}
|
||||
|
||||
fn G_len() -> usize {
|
||||
32
|
||||
}
|
||||
|
||||
fn F_from_slice(slice: &[u8]) -> Result<Self::F, CurveError> {
|
||||
let scalar = Self::F::from_repr(
|
||||
slice.try_into().map_err(|_| CurveError::InvalidLength(32, slice.len()))?
|
||||
);
|
||||
if scalar.is_some().unwrap_u8() == 0 {
|
||||
Err(CurveError::InvalidScalar)?;
|
||||
}
|
||||
Ok(scalar.unwrap())
|
||||
}
|
||||
|
||||
fn G_from_slice(slice: &[u8]) -> Result<Self::G, CurveError> {
|
||||
let bytes = slice.try_into().map_err(|_| CurveError::InvalidLength(32, slice.len()))?;
|
||||
let point = CompressedEdwardsY::new(bytes).decompress();
|
||||
|
||||
if let Some(point) = point {
|
||||
// Ban identity and torsioned points
|
||||
if point.is_identity().into() || (!bool::from(point.is_torsion_free())) {
|
||||
Err(CurveError::InvalidPoint)?;
|
||||
}
|
||||
// Ban points which weren't canonically encoded
|
||||
if point.compress().to_bytes() != bytes {
|
||||
Err(CurveError::InvalidPoint)?;
|
||||
}
|
||||
Ok(point)
|
||||
} else {
|
||||
Err(CurveError::InvalidPoint)
|
||||
}
|
||||
}
|
||||
|
||||
fn F_to_bytes(f: &Self::F) -> Vec<u8> {
|
||||
f.to_repr().to_vec()
|
||||
}
|
||||
|
||||
fn G_to_bytes(g: &Self::G) -> Vec<u8> {
|
||||
g.compress().to_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct IetfEd25519Hram;
|
||||
impl Hram<Ed25519> for IetfEd25519Hram {
|
||||
#[allow(non_snake_case)]
|
||||
fn hram(R: &EdwardsPoint, A: &EdwardsPoint, m: &[u8]) -> Scalar {
|
||||
Ed25519::hash_to_F(b"", &[&R.compress().to_bytes(), &A.compress().to_bytes(), m].concat())
|
||||
}
|
||||
}
|
||||
@@ -2,28 +2,27 @@ use core::{marker::PhantomData, convert::TryInto};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use sha2::{digest::Update, Digest, Sha256};
|
||||
|
||||
use ff::{Field, PrimeField};
|
||||
use group::{Group, GroupEncoding};
|
||||
|
||||
use sha2::{digest::Update, Digest, Sha256};
|
||||
use elliptic_curve::{bigint::{Encoding, U384}, hash2curve::{Expander, ExpandMsg, ExpandMsgXmd}};
|
||||
|
||||
#[cfg(feature = "k256")]
|
||||
use k256::elliptic_curve::bigint::{Encoding, U384};
|
||||
#[cfg(all(not(feature = "k256"), any(test, feature = "p256")))]
|
||||
use p256::elliptic_curve::bigint::{Encoding, U384};
|
||||
|
||||
use crate::{CurveError, Curve, curves::expand_message_xmd_sha256};
|
||||
use crate::{CurveError, Curve};
|
||||
#[cfg(any(test, feature = "p256"))]
|
||||
use crate::algorithm::Hram;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct KP256<P: Group> {
|
||||
_P: PhantomData<P>
|
||||
pub struct KP256<G: Group> {
|
||||
_G: PhantomData<G>
|
||||
}
|
||||
|
||||
pub(crate) trait KP256Instance<P> {
|
||||
pub(crate) trait KP256Instance<G> {
|
||||
const CONTEXT: &'static [u8];
|
||||
const ID: &'static [u8];
|
||||
const GENERATOR: P;
|
||||
const GENERATOR: G;
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "p256"))]
|
||||
@@ -44,19 +43,19 @@ impl KP256Instance<k256::ProjectivePoint> for K256 {
|
||||
const GENERATOR: k256::ProjectivePoint = k256::ProjectivePoint::GENERATOR;
|
||||
}
|
||||
|
||||
impl<P: Group + GroupEncoding> Curve for KP256<P> where
|
||||
KP256<P>: KP256Instance<P>,
|
||||
P::Scalar: PrimeField,
|
||||
<P::Scalar as PrimeField>::Repr: From<[u8; 32]> + AsRef<[u8]>,
|
||||
P::Repr: From<[u8; 33]> + AsRef<[u8]> {
|
||||
type F = P::Scalar;
|
||||
type G = P;
|
||||
type T = P;
|
||||
impl<G: Group + GroupEncoding> Curve for KP256<G> where
|
||||
KP256<G>: KP256Instance<G>,
|
||||
G::Scalar: PrimeField,
|
||||
<G::Scalar as PrimeField>::Repr: From<[u8; 32]> + AsRef<[u8]>,
|
||||
G::Repr: From<[u8; 33]> + AsRef<[u8]> {
|
||||
type F = G::Scalar;
|
||||
type G = G;
|
||||
type T = G;
|
||||
|
||||
const ID: &'static [u8] = <Self as KP256Instance<P>>::ID;
|
||||
const ID: &'static [u8] = <Self as KP256Instance<G>>::ID;
|
||||
|
||||
const GENERATOR: Self::G = <Self as KP256Instance<P>>::GENERATOR;
|
||||
const GENERATOR_TABLE: Self::G = <Self as KP256Instance<P>>::GENERATOR;
|
||||
const GENERATOR: Self::G = <Self as KP256Instance<G>>::GENERATOR;
|
||||
const GENERATOR_TABLE: Self::G = <Self as KP256Instance<G>>::GENERATOR;
|
||||
|
||||
const LITTLE_ENDIAN: bool = false;
|
||||
|
||||
@@ -81,13 +80,21 @@ impl<P: Group + GroupEncoding> Curve for KP256<P> where
|
||||
}
|
||||
|
||||
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
|
||||
let mut dst = dst;
|
||||
let oversize = Sha256::digest([b"H2C-OVERSIZE-DST-", dst].concat());
|
||||
if dst.len() > 255 {
|
||||
dst = &oversize;
|
||||
}
|
||||
|
||||
let mut modulus = vec![0; 16];
|
||||
modulus.extend((Self::F::zero() - Self::F::one()).to_repr().as_ref());
|
||||
let modulus = U384::from_be_slice(&modulus).wrapping_add(&U384::ONE);
|
||||
Self::F_from_slice(
|
||||
&U384::from_be_slice(
|
||||
&expand_message_xmd_sha256(dst, msg, 48).unwrap()
|
||||
).reduce(&modulus).unwrap().to_be_bytes()[16 ..]
|
||||
&U384::from_be_slice(&{
|
||||
let mut bytes = [0; 48];
|
||||
ExpandMsgXmd::<Sha256>::expand_message(&[msg], dst, 48).unwrap().fill_bytes(&mut bytes);
|
||||
bytes
|
||||
}).reduce(&modulus).unwrap().to_be_bytes()[16 ..]
|
||||
).unwrap()
|
||||
}
|
||||
|
||||
@@ -131,3 +138,17 @@ impl<P: Group + GroupEncoding> Curve for KP256<P> where
|
||||
g.to_bytes().as_ref().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "p256"))]
|
||||
#[derive(Clone)]
|
||||
pub struct IetfP256Hram;
|
||||
#[cfg(any(test, feature = "p256"))]
|
||||
impl Hram<P256> for IetfP256Hram {
|
||||
#[allow(non_snake_case)]
|
||||
fn hram(R: &p256::ProjectivePoint, A: &p256::ProjectivePoint, m: &[u8]) -> p256::Scalar {
|
||||
P256::hash_to_F(
|
||||
&[P256::CONTEXT, b"chal"].concat(),
|
||||
&[&P256::G_to_bytes(R), &P256::G_to_bytes(A), m].concat()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,5 @@
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
#[cfg(any(test, feature = "kp256"))]
|
||||
pub mod kp256;
|
||||
|
||||
// TODO: Actually make proper or replace with something from another crate
|
||||
pub(crate) fn expand_message_xmd_sha256(dst: &[u8], msg: &[u8], len: u16) -> Option<Vec<u8>> {
|
||||
const OUTPUT_SIZE: u16 = 32;
|
||||
const BLOCK_SIZE: u16 = 64;
|
||||
|
||||
let blocks = ((len + OUTPUT_SIZE) - 1) / OUTPUT_SIZE;
|
||||
if blocks > 255 {
|
||||
return None;
|
||||
}
|
||||
let blocks = blocks as u8;
|
||||
|
||||
let mut dst = dst;
|
||||
let oversize = Sha256::digest([b"H2C-OVERSIZE-DST-", dst].concat());
|
||||
if dst.len() > 255 {
|
||||
dst = &oversize;
|
||||
}
|
||||
let dst_prime = &[dst, &[dst.len() as u8]].concat();
|
||||
|
||||
let mut msg_prime = vec![0; BLOCK_SIZE.into()];
|
||||
msg_prime.extend(msg);
|
||||
msg_prime.extend(len.to_be_bytes());
|
||||
msg_prime.push(0);
|
||||
msg_prime.extend(dst_prime);
|
||||
|
||||
let mut b = vec![Sha256::digest(&msg_prime).to_vec()];
|
||||
|
||||
{
|
||||
let mut b1 = b[0].clone();
|
||||
b1.push(1);
|
||||
b1.extend(dst_prime);
|
||||
b.push(Sha256::digest(&b1).to_vec());
|
||||
}
|
||||
|
||||
for i in 2 ..= blocks {
|
||||
let mut msg = b[0]
|
||||
.iter().zip(b[usize::from(i) - 1].iter())
|
||||
.map(|(a, b)| *a ^ b).collect::<Vec<_>>();
|
||||
msg.push(i);
|
||||
msg.extend(dst_prime);
|
||||
b.push(Sha256::digest(msg).to_vec());
|
||||
}
|
||||
|
||||
Some(b[1 ..].concat()[.. usize::from(len)].to_vec())
|
||||
}
|
||||
#[cfg(feature = "ed25519")]
|
||||
pub mod ed25519;
|
||||
|
||||
Reference in New Issue
Block a user