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:
Luke Parker
2022-06-06 02:18:25 -04:00
parent 55a895d65a
commit e0ce6e5c12
15 changed files with 189 additions and 266 deletions

View File

@@ -12,7 +12,6 @@ digest = "0.10"
subtle = "2.4"
ff = "0.11"
group = "0.11"
group = "0.12"
curve25519-dalek = "3.2"

View File

@@ -22,8 +22,7 @@ use dalek::{
}
};
use ff::{Field, PrimeField};
use group::Group;
use group::{ff::{Field, PrimeField}, Group};
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct Scalar(pub DScalar);

View File

@@ -12,12 +12,15 @@ thiserror = "1"
rand_core = "0.6"
hex = "0.4"
ff = "0.11"
group = "0.11"
sha2 = { version = "0.10", optional = true }
p256 = { version = "0.10", optional = true }
k256 = { version = "0.10", optional = true }
ff = "0.12"
group = "0.12"
elliptic-curve = { version = "0.12", features = ["hash2curve"], optional = true }
p256 = { version = "0.11", features = ["arithmetic", "hash2curve"], optional = true }
k256 = { version = "0.11", features = ["arithmetic", "hash2curve"], optional = true }
dalek-ff-group = { path = "../dalek-ff-group", optional = true }
transcript = { path = "../transcript" }
@@ -25,9 +28,14 @@ multiexp = { path = "../multiexp", features = ["batch"] }
[dev-dependencies]
rand = "0.8"
sha2 = "0.10"
p256 = { version = "0.10", features = ["arithmetic"] }
elliptic-curve = { version = "0.12", features = ["hash2curve"] }
p256 = { version = "0.11", features = ["arithmetic", "hash2curve"] }
[features]
p256 = ["sha2", "dep:p256"]
k256 = ["sha2", "dep:k256"]
curves = []
kp256 = ["elliptic-curve"]
p256 = ["curves", "kp256", "sha2", "dep:p256"]
k256 = ["curves", "kp256", "sha2", "dep:k256"]
ed25519 = ["curves", "sha2", "dalek-ff-group"]

View 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())
}
}

View File

@@ -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()
)
}
}

View File

@@ -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;

View File

@@ -13,7 +13,7 @@ mod schnorr;
pub mod key_gen;
pub mod algorithm;
pub mod sign;
#[cfg(any(test, feature = "p256", feature = "k256"))]
#[cfg(any(test, feature = "curves"))]
pub mod curves;
pub mod tests;

View File

@@ -0,0 +1,51 @@
use rand::rngs::OsRng;
use crate::{
curves::ed25519::{Ed25519, IetfEd25519Hram},
tests::{curve::test_curve, schnorr::test_schnorr, vectors::{Vectors, vectors}}
};
#[test]
fn ed25519_curve() {
test_curve::<_, Ed25519>(&mut OsRng);
}
#[test]
fn ed25519_schnorr() {
test_schnorr::<_, Ed25519>(&mut OsRng);
}
#[test]
fn ed25519_vectors() {
vectors::<Ed25519, IetfEd25519Hram>(
Vectors {
threshold: 2,
shares: &[
"929dcc590407aae7d388761cddb0c0db6f5627aea8e217f4a033f2ec83d93509",
"a91e66e012e4364ac9aaa405fcafd370402d9859f7b6685c07eed76bf409e80d",
"d3cb090a075eb154e82fdb4b3cb507f110040905468bb9c46da8bdea643a9a02"
],
group_secret: "7b1c33d3f5291d85de664833beb1ad469f7fb6025a0ec78b3a790c6e13a98304",
group_key: "15d21ccd7ee42959562fc8aa63224c8851fb3ec85a3faf66040d380fb9738673",
msg: "74657374",
included: &[1, 3],
nonces: &[
[
"8c76af04340e83bb5fc427c117d38347fc8ef86d5397feea9aa6412d96c05b0a",
"14a37ddbeae8d9e9687369e5eb3c6d54f03dc19d76bb54fb5425131bc37a600b"
],
[
"5ca39ebab6874f5e7b5089f3521819a2aa1e2cf738bae6974ee80555de2ef70e",
"0afe3650c4815ff37becd3c6948066e906e929ea9b8f546c74e10002dbcc150c"
]
],
sig_shares: &[
"4369474a398aa10357b60d683da91ea6a767dcf53fd541a8ed6b4d780827ea0a",
"32fcc690d926075e45d2dfb746bab71447943cddbefe80d122c39174aa2e1004"
],
sig: "2b8d9c6995333c5990e3a3dd6568785539d3322f7f0376452487ea35cfda587b".to_owned() +
"75650edb12b1a8619c88ed1f8463d6baeefb18d3fed3c279102fdfecb255fa0e"
}
);
}

View File

@@ -1,15 +0,0 @@
use crate::curves::expand_message_xmd_sha256;
#[test]
fn test_xmd_sha256() {
assert_eq!(
hex::encode(expand_message_xmd_sha256(b"QUUX-V01-CS02-with-expander", b"", 0x80).unwrap()),
(
"8bcffd1a3cae24cf9cd7ab85628fd111bb17e3739d3b53f8".to_owned() +
"9580d217aa79526f1708354a76a402d3569d6a9d19ef3de4d0b991" +
"e4f54b9f20dcde9b95a66824cbdf6c1a963a1913d43fd7ac443a02" +
"fc5d9d8d77e2071b86ab114a9f34150954a7531da568a1ea8c7608" +
"61c0cde2005afc2c114042ee7b5848f5303f0611cf297f"
)
);
}

View File

@@ -1,9 +1,7 @@
use rand::rngs::OsRng;
use crate::{
Curve,
curves::kp256::{KP256Instance, P256},
algorithm::Hram,
curves::kp256::{P256, IetfP256Hram},
tests::{curve::test_curve, schnorr::test_schnorr, vectors::{Vectors, vectors}}
};
@@ -20,18 +18,6 @@ fn p256_schnorr() {
test_schnorr::<_, P256>(&mut OsRng);
}
#[derive(Clone)]
pub struct IetfP256Hram;
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()
)
}
}
#[test]
fn p256_vectors() {
vectors::<P256, IetfP256Hram>(

View File

@@ -1,2 +1,3 @@
mod expand_message;
mod kp256;
#[cfg(feature = "ed25519")]
mod ed25519;

View File

@@ -7,7 +7,7 @@ authors = ["Luke Parker <lukeparker5132@gmail.com>"]
edition = "2021"
[dependencies]
group = "0.11"
group = "0.12"
rand_core = { version = "0.6", optional = true }