mirror of
https://github.com/serai-dex/serai.git
synced 2024-11-17 01:17:36 +00:00
Merge pull request #21 from serai-dex/curves
Add first-party support for Ristretto, ed25519, secp256k1, and P-256
This commit is contained in:
commit
ad14d0bfd0
17 changed files with 692 additions and 636 deletions
|
@ -16,16 +16,15 @@ rand = "0.8"
|
|||
rand_distr = "0.4"
|
||||
|
||||
tiny-keccak = { version = "2", features = ["keccak"] }
|
||||
blake2 = "0.10"
|
||||
blake2 = { version = "0.10", optional = true }
|
||||
|
||||
curve25519-dalek = { version = "3", features = ["std"] }
|
||||
|
||||
ff = { version = "0.11", optional = true }
|
||||
group = { version = "0.11", optional = true }
|
||||
group = { version = "0.12", optional = true }
|
||||
|
||||
dalek-ff-group = { path = "../../crypto/dalek-ff-group", optional = true }
|
||||
transcript = { path = "../../crypto/transcript", optional = true }
|
||||
frost = { path = "../../crypto/frost", optional = true }
|
||||
frost = { path = "../../crypto/frost", features = ["ed25519"], optional = true }
|
||||
|
||||
monero = "0.16"
|
||||
|
||||
|
@ -37,7 +36,7 @@ reqwest = { version = "0.11", features = ["json"] }
|
|||
|
||||
[features]
|
||||
experimental = []
|
||||
multisig = ["ff", "group", "rand_chacha", "transcript", "frost", "dalek-ff-group"]
|
||||
multisig = ["rand_chacha", "blake2", "group", "dalek-ff-group", "transcript", "frost"]
|
||||
|
||||
[dev-dependencies]
|
||||
sha2 = "0.10"
|
||||
|
|
|
@ -1,22 +1,17 @@
|
|||
use core::{convert::TryInto, fmt::{Formatter, Debug}};
|
||||
use std::marker::PhantomData;
|
||||
use core::convert::TryInto;
|
||||
|
||||
use thiserror::Error;
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use blake2::{digest::{generic_array::typenum::U64, Digest}, Blake2b512};
|
||||
|
||||
use curve25519_dalek::{
|
||||
constants::ED25519_BASEPOINT_TABLE as DTable,
|
||||
scalar::Scalar as DScalar,
|
||||
edwards::EdwardsPoint as DPoint
|
||||
};
|
||||
|
||||
use ff::PrimeField;
|
||||
use group::Group;
|
||||
|
||||
use transcript::{Transcript as TranscriptTrait, DigestTranscript};
|
||||
use frost::{CurveError, Curve};
|
||||
use frost::Curve;
|
||||
pub use frost::curves::dalek::Ed25519;
|
||||
use dalek_ff_group as dfg;
|
||||
|
||||
use crate::random_scalar;
|
||||
|
@ -33,109 +28,6 @@ pub enum MultisigError {
|
|||
InvalidKeyImage(u16)
|
||||
}
|
||||
|
||||
// Accept a parameterized hash function in order to check against the FROST vectors while still
|
||||
// allowing Blake2b to be used with wide reduction in practice
|
||||
pub struct Ed25519Internal<D: Digest<OutputSize = U64>, const WIDE: bool> {
|
||||
_digest: PhantomData<D>
|
||||
}
|
||||
|
||||
// Removed requirements for D to have all of these
|
||||
impl<D: Digest<OutputSize = U64>, const WIDE: bool> Clone for Ed25519Internal<D, WIDE> {
|
||||
fn clone(&self) -> Self { *self }
|
||||
}
|
||||
impl<D: Digest<OutputSize = U64>, const WIDE: bool> Copy for Ed25519Internal<D, WIDE> {}
|
||||
impl<D: Digest<OutputSize = U64>, const WIDE: bool> PartialEq for Ed25519Internal<D, WIDE> {
|
||||
fn eq(&self, _: &Self) -> bool { true }
|
||||
}
|
||||
impl<D: Digest<OutputSize = U64>, const WIDE: bool> Eq for Ed25519Internal<D, WIDE> {}
|
||||
impl<D: Digest<OutputSize = U64>, const WIDE: bool> Debug for Ed25519Internal<D, WIDE> {
|
||||
fn fmt(&self, _: &mut Formatter<'_>) -> Result<(), core::fmt::Error> { Ok(()) }
|
||||
}
|
||||
|
||||
impl<D: Digest<OutputSize = U64>, const WIDE: bool> Curve for Ed25519Internal<D, WIDE> {
|
||||
type F = dfg::Scalar;
|
||||
type G = dfg::EdwardsPoint;
|
||||
type T = &'static dfg::EdwardsBasepointTable;
|
||||
|
||||
const ID: &'static [u8] = b"edwards25519";
|
||||
|
||||
const GENERATOR: Self::G = dfg::ED25519_BASEPOINT_POINT;
|
||||
const GENERATOR_TABLE: Self::T = &dfg::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> {
|
||||
D::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 {
|
||||
let digest = D::new().chain_update(dst).chain_update(msg);
|
||||
if WIDE {
|
||||
dfg::Scalar::from_hash(digest)
|
||||
} else {
|
||||
dfg::Scalar::from_bytes_mod_order(digest.finalize()[32 ..].try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
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 = dfg::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()
|
||||
}
|
||||
}
|
||||
|
||||
pub type Ed25519 = Ed25519Internal<Blake2b512, true>;
|
||||
|
||||
// Used to prove legitimacy of key images and nonces which both involve other basepoints
|
||||
#[derive(Clone)]
|
||||
pub struct DLEqProof {
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
use rand::rngs::OsRng;
|
||||
|
||||
use sha2::Sha512;
|
||||
|
||||
use dalek_ff_group as dfg;
|
||||
use frost::{
|
||||
Curve,
|
||||
algorithm::Hram,
|
||||
tests::{curve::test_curve, schnorr::test_schnorr, vectors::{Vectors, vectors}}
|
||||
};
|
||||
|
||||
use crate::frost::{Ed25519, Ed25519Internal};
|
||||
|
||||
#[test]
|
||||
fn frost_ed25519_curve() {
|
||||
test_curve::<_, Ed25519>(&mut OsRng);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frost_ed25519_schnorr() {
|
||||
test_schnorr::<_, Ed25519>(&mut OsRng);
|
||||
}
|
||||
|
||||
// Not spec-compliant, as this shouldn't use wide reduction
|
||||
// Is vectors compliant, which is why the below tests pass
|
||||
// See https://github.com/cfrg/draft-irtf-cfrg-frost/issues/204
|
||||
//type TestEd25519 = Ed25519Internal<Sha512, false>;
|
||||
// If this is kept, we can remove WIDE
|
||||
type TestEd25519 = Ed25519Internal<Sha512, true>;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct IetfEd25519Hram {}
|
||||
impl Hram<TestEd25519> for IetfEd25519Hram {
|
||||
#[allow(non_snake_case)]
|
||||
fn hram(R: &dfg::EdwardsPoint, A: &dfg::EdwardsPoint, m: &[u8]) -> dfg::Scalar {
|
||||
TestEd25519::hash_to_F(
|
||||
b"",
|
||||
&[&R.compress().to_bytes(), &A.compress().to_bytes(), m].concat()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frost_ed25519_vectors() {
|
||||
vectors::<TestEd25519, 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"
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,4 +1 @@
|
|||
#[cfg(feature = "multisig")]
|
||||
mod frost;
|
||||
|
||||
mod clsag;
|
||||
|
|
|
@ -12,7 +12,6 @@ digest = "0.10"
|
|||
|
||||
subtle = "2.4"
|
||||
|
||||
ff = "0.11"
|
||||
group = "0.11"
|
||||
group = "0.12"
|
||||
|
||||
curve25519-dalek = "3.2"
|
||||
|
|
|
@ -16,89 +16,123 @@ use dalek::{
|
|||
traits::Identity,
|
||||
scalar::Scalar as DScalar,
|
||||
edwards::{
|
||||
EdwardsPoint as DPoint,
|
||||
EdwardsBasepointTable as DTable,
|
||||
CompressedEdwardsY as DCompressed
|
||||
EdwardsPoint as DEdwardsPoint,
|
||||
EdwardsBasepointTable as DEdwardsBasepointTable,
|
||||
CompressedEdwardsY as DCompressedEdwards
|
||||
},
|
||||
ristretto::{
|
||||
RistrettoPoint as DRistrettoPoint,
|
||||
RistrettoBasepointTable as DRistrettoBasepointTable,
|
||||
CompressedRistretto as DCompressedRistretto
|
||||
}
|
||||
};
|
||||
|
||||
use ff::{Field, PrimeField};
|
||||
use group::Group;
|
||||
use group::{ff::{Field, PrimeField}, Group};
|
||||
|
||||
macro_rules! deref_borrow {
|
||||
($Source: ident, $Target: ident) => {
|
||||
impl Deref for $Source {
|
||||
type Target = $Target;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<$Target> for $Source {
|
||||
fn borrow(&self) -> &$Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<$Target> for &$Source {
|
||||
fn borrow(&self) -> &$Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! math {
|
||||
($Value: ident, $Factor: ident, $Product: ident) => {
|
||||
impl Add<$Value> for $Value {
|
||||
type Output = Self;
|
||||
fn add(self, other: $Value) -> Self::Output { Self(self.0 + other.0) }
|
||||
}
|
||||
impl AddAssign for $Value {
|
||||
fn add_assign(&mut self, other: $Value) { self.0 += other.0 }
|
||||
}
|
||||
|
||||
impl<'a> Add<&'a $Value> for $Value {
|
||||
type Output = Self;
|
||||
fn add(self, other: &'a $Value) -> Self::Output { Self(self.0 + other.0) }
|
||||
}
|
||||
impl<'a> AddAssign<&'a $Value> for $Value {
|
||||
fn add_assign(&mut self, other: &'a $Value) { self.0 += other.0 }
|
||||
}
|
||||
|
||||
impl Sub<$Value> for $Value {
|
||||
type Output = Self;
|
||||
fn sub(self, other: $Value) -> Self::Output { Self(self.0 - other.0) }
|
||||
}
|
||||
impl SubAssign for $Value {
|
||||
fn sub_assign(&mut self, other: $Value) { self.0 -= other.0 }
|
||||
}
|
||||
|
||||
impl<'a> Sub<&'a $Value> for $Value {
|
||||
type Output = Self;
|
||||
fn sub(self, other: &'a $Value) -> Self::Output { Self(self.0 - other.0) }
|
||||
}
|
||||
impl<'a> SubAssign<&'a $Value> for $Value {
|
||||
fn sub_assign(&mut self, other: &'a $Value) { self.0 -= other.0 }
|
||||
}
|
||||
|
||||
impl Neg for $Value {
|
||||
type Output = Self;
|
||||
fn neg(self) -> Self::Output { Self(-self.0) }
|
||||
}
|
||||
|
||||
impl Mul<$Factor> for $Value {
|
||||
type Output = $Product;
|
||||
fn mul(self, other: $Factor) -> Self::Output { Self(self.0 * other.0) }
|
||||
}
|
||||
impl MulAssign<$Factor> for $Value {
|
||||
fn mul_assign(&mut self, other: $Factor) { self.0 *= other.0 }
|
||||
}
|
||||
|
||||
impl<'a> Mul<&'a $Factor> for $Value {
|
||||
type Output = Self;
|
||||
fn mul(self, b: &'a $Factor) -> $Product { Self(b.0 * self.0) }
|
||||
}
|
||||
impl<'a> MulAssign<&'a $Factor> for $Value {
|
||||
fn mul_assign(&mut self, other: &'a $Factor) { self.0 *= other.0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
|
||||
pub struct Scalar(pub DScalar);
|
||||
deref_borrow!(Scalar, DScalar);
|
||||
math!(Scalar, Scalar, Scalar);
|
||||
|
||||
impl Deref for Scalar {
|
||||
type Target = DScalar;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
impl Scalar {
|
||||
pub fn from_canonical_bytes(bytes: [u8; 32]) -> Option<Scalar> {
|
||||
DScalar::from_canonical_bytes(bytes).map(|x| Self(x))
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<DScalar> for Scalar {
|
||||
fn borrow(&self) -> &DScalar {
|
||||
&self.0
|
||||
pub fn from_bytes_mod_order(bytes: [u8; 32]) -> Scalar {
|
||||
Self(DScalar::from_bytes_mod_order(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<DScalar> for &Scalar {
|
||||
fn borrow(&self) -> &DScalar {
|
||||
&self.0
|
||||
pub fn from_bytes_mod_order_wide(bytes: &[u8; 64]) -> Scalar {
|
||||
Self(DScalar::from_bytes_mod_order_wide(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Scalar> for Scalar {
|
||||
type Output = Self;
|
||||
fn add(self, other: Scalar) -> Scalar { Self(self.0 + other.0) }
|
||||
}
|
||||
impl AddAssign for Scalar {
|
||||
fn add_assign(&mut self, other: Scalar) { self.0 += other.0 }
|
||||
}
|
||||
|
||||
impl<'a> Add<&'a Scalar> for Scalar {
|
||||
type Output = Self;
|
||||
fn add(self, other: &'a Scalar) -> Scalar { Self(self.0 + other.0) }
|
||||
}
|
||||
impl<'a> AddAssign<&'a Scalar> for Scalar {
|
||||
fn add_assign(&mut self, other: &'a Scalar) { self.0 += other.0 }
|
||||
}
|
||||
|
||||
impl Sub<Scalar> for Scalar {
|
||||
type Output = Self;
|
||||
fn sub(self, other: Scalar) -> Scalar { Self(self.0 - other.0) }
|
||||
}
|
||||
impl SubAssign for Scalar {
|
||||
fn sub_assign(&mut self, other: Scalar) { self.0 -= other.0 }
|
||||
}
|
||||
|
||||
impl<'a> Sub<&'a Scalar> for Scalar {
|
||||
type Output = Self;
|
||||
fn sub(self, other: &'a Scalar) -> Scalar { Self(self.0 - other.0) }
|
||||
}
|
||||
impl<'a> SubAssign<&'a Scalar> for Scalar {
|
||||
fn sub_assign(&mut self, other: &'a Scalar) { self.0 -= other.0 }
|
||||
}
|
||||
|
||||
impl Neg for Scalar {
|
||||
type Output = Self;
|
||||
fn neg(self) -> Scalar { Self(-self.0) }
|
||||
}
|
||||
|
||||
impl Mul<Scalar> for Scalar {
|
||||
type Output = Self;
|
||||
fn mul(self, other: Scalar) -> Scalar { Self(self.0 * other.0) }
|
||||
}
|
||||
impl MulAssign for Scalar {
|
||||
fn mul_assign(&mut self, other: Scalar) { self.0 *= other.0 }
|
||||
}
|
||||
|
||||
impl<'a> Mul<&'a Scalar> for Scalar {
|
||||
type Output = Self;
|
||||
fn mul(self, other: &'a Scalar) -> Scalar { Self(self.0 * other.0) }
|
||||
}
|
||||
impl<'a> MulAssign<&'a Scalar> for Scalar {
|
||||
fn mul_assign(&mut self, other: &'a Scalar) { self.0 *= other.0 }
|
||||
pub fn from_hash<D: Digest<OutputSize = U64>>(hash: D) -> Scalar {
|
||||
let mut output = [0u8; 64];
|
||||
output.copy_from_slice(&hash.finalize());
|
||||
Scalar(DScalar::from_bytes_mod_order_wide(&output))
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstantTimeEq for Scalar {
|
||||
|
@ -154,163 +188,106 @@ impl PrimeField for Scalar {
|
|||
fn root_of_unity() -> Self { unimplemented!() }
|
||||
}
|
||||
|
||||
impl Scalar {
|
||||
pub fn from_hash<D: Digest<OutputSize = U64>>(hash: D) -> Scalar {
|
||||
let mut output = [0u8; 64];
|
||||
output.copy_from_slice(&hash.finalize());
|
||||
Scalar(DScalar::from_bytes_mod_order_wide(&output))
|
||||
}
|
||||
}
|
||||
macro_rules! dalek_group {
|
||||
(
|
||||
$Point: ident,
|
||||
$DPoint: ident,
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct EdwardsPoint(pub DPoint);
|
||||
pub const ED25519_BASEPOINT_POINT: EdwardsPoint = EdwardsPoint(constants::ED25519_BASEPOINT_POINT);
|
||||
$Table: ident,
|
||||
$DTable: ident,
|
||||
|
||||
impl Deref for EdwardsPoint {
|
||||
type Target = DPoint;
|
||||
$Compressed: ident,
|
||||
$DCompressed: ident,
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
$BASEPOINT_POINT: ident,
|
||||
$BASEPOINT_TABLE: ident
|
||||
) => {
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct $Point(pub $DPoint);
|
||||
deref_borrow!($Point, $DPoint);
|
||||
math!($Point, Scalar, $Point);
|
||||
|
||||
pub const $BASEPOINT_POINT: $Point = $Point(constants::$BASEPOINT_POINT);
|
||||
|
||||
impl Sum<$Point> for $Point {
|
||||
fn sum<I: Iterator<Item = $Point>>(iter: I) -> $Point { Self($DPoint::sum(iter)) }
|
||||
}
|
||||
impl<'a> Sum<&'a $Point> for $Point {
|
||||
fn sum<I: Iterator<Item = &'a $Point>>(iter: I) -> $Point { Self($DPoint::sum(iter)) }
|
||||
}
|
||||
|
||||
impl Group for $Point {
|
||||
type Scalar = Scalar;
|
||||
fn random(rng: impl RngCore) -> Self { &$BASEPOINT_TABLE * Scalar::random(rng) }
|
||||
fn identity() -> Self { Self($DPoint::identity()) }
|
||||
fn generator() -> Self { $BASEPOINT_POINT }
|
||||
fn is_identity(&self) -> Choice { self.0.ct_eq(&$DPoint::identity()) }
|
||||
fn double(&self) -> Self { *self + self }
|
||||
}
|
||||
|
||||
pub struct $Compressed(pub $DCompressed);
|
||||
deref_borrow!($Compressed, $DCompressed);
|
||||
impl $Compressed {
|
||||
pub fn new(y: [u8; 32]) -> $Compressed {
|
||||
Self($DCompressed(y))
|
||||
}
|
||||
|
||||
pub fn decompress(&self) -> Option<$Point> {
|
||||
self.0.decompress().map(|x| $Point(x))
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> [u8; 32] {
|
||||
self.0.to_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl $Point {
|
||||
pub fn compress(&self) -> $Compressed {
|
||||
$Compressed(self.0.compress())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct $Table(pub $DTable);
|
||||
deref_borrow!($Table, $DTable);
|
||||
pub const $BASEPOINT_TABLE: $Table = $Table(constants::$BASEPOINT_TABLE);
|
||||
|
||||
impl Mul<Scalar> for &$Table {
|
||||
type Output = $Point;
|
||||
fn mul(self, b: Scalar) -> $Point { $Point(&b.0 * &self.0) }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl Borrow<DPoint> for EdwardsPoint {
|
||||
fn borrow(&self) -> &DPoint {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
dalek_group!(
|
||||
EdwardsPoint,
|
||||
DEdwardsPoint,
|
||||
|
||||
impl Borrow<DPoint> for &EdwardsPoint {
|
||||
fn borrow(&self) -> &DPoint {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
EdwardsBasepointTable,
|
||||
DEdwardsBasepointTable,
|
||||
|
||||
impl Add<EdwardsPoint> for EdwardsPoint {
|
||||
type Output = Self;
|
||||
fn add(self, b: EdwardsPoint) -> EdwardsPoint { Self(self.0 + b.0) }
|
||||
}
|
||||
impl AddAssign<EdwardsPoint> for EdwardsPoint {
|
||||
fn add_assign(&mut self, other: EdwardsPoint) { self.0 += other.0 }
|
||||
}
|
||||
impl Sum<EdwardsPoint> for EdwardsPoint {
|
||||
fn sum<I: Iterator<Item = EdwardsPoint>>(iter: I) -> EdwardsPoint { Self(DPoint::sum(iter)) }
|
||||
}
|
||||
CompressedEdwardsY,
|
||||
DCompressedEdwards,
|
||||
|
||||
impl<'a> Add<&'a EdwardsPoint> for EdwardsPoint {
|
||||
type Output = Self;
|
||||
fn add(self, b: &'a EdwardsPoint) -> EdwardsPoint { Self(self.0 + b.0) }
|
||||
}
|
||||
impl<'a> AddAssign<&'a EdwardsPoint> for EdwardsPoint {
|
||||
fn add_assign(&mut self, other: &'a EdwardsPoint) { self.0 += other.0 }
|
||||
}
|
||||
impl<'a> Sum<&'a EdwardsPoint> for EdwardsPoint {
|
||||
fn sum<I: Iterator<Item = &'a EdwardsPoint>>(iter: I) -> EdwardsPoint { Self(DPoint::sum(iter)) }
|
||||
}
|
||||
|
||||
impl Sub<EdwardsPoint> for EdwardsPoint {
|
||||
type Output = Self;
|
||||
fn sub(self, b: EdwardsPoint) -> EdwardsPoint { Self(self.0 - b.0) }
|
||||
}
|
||||
impl SubAssign<EdwardsPoint> for EdwardsPoint {
|
||||
fn sub_assign(&mut self, other: EdwardsPoint) { self.0 -= other.0 }
|
||||
}
|
||||
|
||||
impl<'a> Sub<&'a EdwardsPoint> for EdwardsPoint {
|
||||
type Output = Self;
|
||||
fn sub(self, b: &'a EdwardsPoint) -> EdwardsPoint { Self(self.0 - b.0) }
|
||||
}
|
||||
impl<'a> SubAssign<&'a EdwardsPoint> for EdwardsPoint {
|
||||
fn sub_assign(&mut self, other: &'a EdwardsPoint) { self.0 -= other.0 }
|
||||
}
|
||||
|
||||
impl Neg for EdwardsPoint {
|
||||
type Output = Self;
|
||||
fn neg(self) -> EdwardsPoint { Self(-self.0) }
|
||||
}
|
||||
|
||||
impl Mul<Scalar> for EdwardsPoint {
|
||||
type Output = Self;
|
||||
fn mul(self, b: Scalar) -> EdwardsPoint { Self(b.0 * self.0) }
|
||||
}
|
||||
impl MulAssign<Scalar> for EdwardsPoint {
|
||||
fn mul_assign(&mut self, other: Scalar) { self.0 *= other.0 }
|
||||
}
|
||||
|
||||
impl<'a> Mul<&'a Scalar> for EdwardsPoint {
|
||||
type Output = Self;
|
||||
fn mul(self, b: &'a Scalar) -> EdwardsPoint { Self(b.0 * self.0) }
|
||||
}
|
||||
impl<'a> MulAssign<&'a Scalar> for EdwardsPoint {
|
||||
fn mul_assign(&mut self, other: &'a Scalar) { self.0 *= other.0 }
|
||||
}
|
||||
|
||||
impl Group for EdwardsPoint {
|
||||
type Scalar = Scalar;
|
||||
fn random(rng: impl RngCore) -> Self { &ED25519_BASEPOINT_TABLE * Scalar::random(rng) }
|
||||
fn identity() -> Self { Self(DPoint::identity()) }
|
||||
fn generator() -> Self { ED25519_BASEPOINT_POINT }
|
||||
fn is_identity(&self) -> Choice { self.0.ct_eq(&DPoint::identity()) }
|
||||
fn double(&self) -> Self { *self + self }
|
||||
}
|
||||
|
||||
impl Scalar {
|
||||
pub fn from_canonical_bytes(bytes: [u8; 32]) -> Option<Scalar> {
|
||||
DScalar::from_canonical_bytes(bytes).map(|x| Self(x))
|
||||
}
|
||||
pub fn from_bytes_mod_order(bytes: [u8; 32]) -> Scalar {
|
||||
Self(DScalar::from_bytes_mod_order(bytes))
|
||||
}
|
||||
pub fn from_bytes_mod_order_wide(bytes: &[u8; 64]) -> Scalar {
|
||||
Self(DScalar::from_bytes_mod_order_wide(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CompressedEdwardsY(pub DCompressed);
|
||||
impl CompressedEdwardsY {
|
||||
pub fn new(y: [u8; 32]) -> CompressedEdwardsY {
|
||||
Self(DCompressed(y))
|
||||
}
|
||||
|
||||
pub fn decompress(&self) -> Option<EdwardsPoint> {
|
||||
self.0.decompress().map(|x| EdwardsPoint(x))
|
||||
}
|
||||
|
||||
pub fn to_bytes(&self) -> [u8; 32] {
|
||||
self.0.to_bytes()
|
||||
}
|
||||
}
|
||||
ED25519_BASEPOINT_POINT,
|
||||
ED25519_BASEPOINT_TABLE
|
||||
);
|
||||
|
||||
impl EdwardsPoint {
|
||||
pub fn is_torsion_free(&self) -> bool {
|
||||
self.0.is_torsion_free()
|
||||
}
|
||||
|
||||
pub fn compress(&self) -> CompressedEdwardsY {
|
||||
CompressedEdwardsY(self.0.compress())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EdwardsBasepointTable(pub DTable);
|
||||
pub const ED25519_BASEPOINT_TABLE: EdwardsBasepointTable = EdwardsBasepointTable(
|
||||
constants::ED25519_BASEPOINT_TABLE
|
||||
dalek_group!(
|
||||
RistrettoPoint,
|
||||
DRistrettoPoint,
|
||||
|
||||
RistrettoBasepointTable,
|
||||
DRistrettoBasepointTable,
|
||||
|
||||
CompressedRistretto,
|
||||
DCompressedRistretto,
|
||||
|
||||
RISTRETTO_BASEPOINT_POINT,
|
||||
RISTRETTO_BASEPOINT_TABLE
|
||||
);
|
||||
|
||||
impl Deref for EdwardsBasepointTable {
|
||||
type Target = DTable;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<DTable> for &EdwardsBasepointTable {
|
||||
fn borrow(&self) -> &DTable {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Scalar> for &EdwardsBasepointTable {
|
||||
type Output = EdwardsPoint;
|
||||
fn mul(self, b: Scalar) -> EdwardsPoint { EdwardsPoint(&b.0 * &self.0) }
|
||||
}
|
||||
|
|
|
@ -12,8 +12,15 @@ thiserror = "1"
|
|||
rand_core = "0.6"
|
||||
hex = "0.4"
|
||||
|
||||
ff = "0.11"
|
||||
group = "0.11"
|
||||
sha2 = { 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" }
|
||||
|
||||
|
@ -21,5 +28,15 @@ multiexp = { path = "../multiexp", features = ["batch"] }
|
|||
|
||||
[dev-dependencies]
|
||||
rand = "0.8"
|
||||
|
||||
sha2 = "0.10"
|
||||
p256 = { version = "0.10", features = ["arithmetic"] }
|
||||
dalek-ff-group = { path = "../dalek-ff-group" }
|
||||
|
||||
[features]
|
||||
curves = ["sha2"] # All officially denoted curves use the SHA2 family of hashes
|
||||
kp256 = ["elliptic-curve", "curves"]
|
||||
p256 = ["dep:p256", "kp256"]
|
||||
k256 = ["dep:k256", "kp256"]
|
||||
dalek = ["curves", "dalek-ff-group"]
|
||||
ed25519 = ["dalek"]
|
||||
ristretto = ["dalek"]
|
||||
|
|
163
crypto/frost/src/curves/dalek.rs
Normal file
163
crypto/frost/src/curves/dalek.rs
Normal file
|
@ -0,0 +1,163 @@
|
|||
use core::convert::TryInto;
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use sha2::{Digest, Sha512};
|
||||
|
||||
use ff::PrimeField;
|
||||
use group::Group;
|
||||
|
||||
use dalek_ff_group::Scalar;
|
||||
|
||||
use crate::{CurveError, Curve, algorithm::Hram};
|
||||
|
||||
macro_rules! dalek_curve {
|
||||
(
|
||||
$Curve: ident,
|
||||
$Hram: ident,
|
||||
$Point: ident,
|
||||
$Compressed: ident,
|
||||
$Table: ident,
|
||||
|
||||
$POINT: ident,
|
||||
$TABLE: ident,
|
||||
|
||||
$torsioned: expr,
|
||||
|
||||
$ID: literal,
|
||||
$CONTEXT: literal,
|
||||
$chal: literal,
|
||||
$digest: literal,
|
||||
) => {
|
||||
use dalek_ff_group::{$Point, $Compressed, $Table, $POINT, $TABLE};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct $Curve;
|
||||
impl Curve for $Curve {
|
||||
type F = Scalar;
|
||||
type G = $Point;
|
||||
type T = &'static $Table;
|
||||
|
||||
const ID: &'static [u8] = $ID;
|
||||
|
||||
const GENERATOR: Self::G = $POINT;
|
||||
const GENERATOR_TABLE: Self::T = &$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::new()
|
||||
.chain_update($CONTEXT)
|
||||
.chain_update($digest)
|
||||
.chain_update(msg)
|
||||
.finalize()
|
||||
.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($CONTEXT).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 = $Compressed::new(bytes).decompress();
|
||||
|
||||
if let Some(point) = point {
|
||||
// Ban identity
|
||||
if point.is_identity().into() {
|
||||
Err(CurveError::InvalidPoint)?;
|
||||
}
|
||||
// Ban torsioned points to meet the prime order group requirement
|
||||
if $torsioned(point) {
|
||||
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 $Hram;
|
||||
impl Hram<$Curve> for $Hram {
|
||||
#[allow(non_snake_case)]
|
||||
fn hram(R: &$Point, A: &$Point, m: &[u8]) -> Scalar {
|
||||
$Curve::hash_to_F($chal, &[&R.compress().to_bytes(), &A.compress().to_bytes(), m].concat())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ed25519")]
|
||||
dalek_curve!(
|
||||
Ed25519,
|
||||
IetfEd25519Hram,
|
||||
EdwardsPoint,
|
||||
CompressedEdwardsY,
|
||||
EdwardsBasepointTable,
|
||||
ED25519_BASEPOINT_POINT,
|
||||
ED25519_BASEPOINT_TABLE,
|
||||
|point: EdwardsPoint| !bool::from(point.is_torsion_free()),
|
||||
b"edwards25519",
|
||||
b"",
|
||||
b"",
|
||||
b"",
|
||||
);
|
||||
|
||||
#[cfg(any(test, feature = "ristretto"))]
|
||||
dalek_curve!(
|
||||
Ristretto,
|
||||
IetfRistrettoHram,
|
||||
RistrettoPoint,
|
||||
CompressedRistretto,
|
||||
RistrettoBasepointTable,
|
||||
RISTRETTO_BASEPOINT_POINT,
|
||||
RISTRETTO_BASEPOINT_TABLE,
|
||||
|_| false,
|
||||
b"ristretto",
|
||||
b"FROST-RISTRETTO255-SHA512-v5",
|
||||
b"chal",
|
||||
b"digest",
|
||||
);
|
154
crypto/frost/src/curves/kp256.rs
Normal file
154
crypto/frost/src/curves/kp256.rs
Normal file
|
@ -0,0 +1,154 @@
|
|||
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 elliptic_curve::{bigint::{Encoding, U384}, hash2curve::{Expander, ExpandMsg, ExpandMsgXmd}};
|
||||
|
||||
use crate::{CurveError, Curve};
|
||||
#[cfg(feature = "p256")]
|
||||
use crate::algorithm::Hram;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct KP256<G: Group> {
|
||||
_G: PhantomData<G>
|
||||
}
|
||||
|
||||
pub(crate) trait KP256Instance<G> {
|
||||
const CONTEXT: &'static [u8];
|
||||
const ID: &'static [u8];
|
||||
const GENERATOR: G;
|
||||
}
|
||||
|
||||
#[cfg(feature = "p256")]
|
||||
pub type P256 = KP256<p256::ProjectivePoint>;
|
||||
#[cfg(feature = "p256")]
|
||||
impl KP256Instance<p256::ProjectivePoint> for P256 {
|
||||
const CONTEXT: &'static [u8] = b"FROST-P256-SHA256-v5";
|
||||
const ID: &'static [u8] = b"P-256";
|
||||
const GENERATOR: p256::ProjectivePoint = p256::ProjectivePoint::GENERATOR;
|
||||
}
|
||||
|
||||
#[cfg(feature = "k256")]
|
||||
pub type K256 = KP256<k256::ProjectivePoint>;
|
||||
#[cfg(feature = "k256")]
|
||||
impl KP256Instance<k256::ProjectivePoint> for K256 {
|
||||
const CONTEXT: &'static [u8] = b"FROST-secp256k1-SHA256-v5";
|
||||
const ID: &'static [u8] = b"secp256k1";
|
||||
const GENERATOR: k256::ProjectivePoint = k256::ProjectivePoint::GENERATOR;
|
||||
}
|
||||
|
||||
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<G>>::ID;
|
||||
|
||||
const GENERATOR: Self::G = <Self as KP256Instance<G>>::GENERATOR;
|
||||
const GENERATOR_TABLE: Self::G = <Self as KP256Instance<G>>::GENERATOR;
|
||||
|
||||
const LITTLE_ENDIAN: bool = false;
|
||||
|
||||
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_repr().as_ref());
|
||||
Self::hash_to_F(&[Self::CONTEXT, b"nonce"].concat(), &seed)
|
||||
}
|
||||
|
||||
fn hash_msg(msg: &[u8]) -> Vec<u8> {
|
||||
(&Sha256::new()
|
||||
.chain(Self::CONTEXT)
|
||||
.chain(b"digest")
|
||||
.chain(msg)
|
||||
.finalize()
|
||||
).to_vec()
|
||||
}
|
||||
|
||||
fn hash_binding_factor(binding: &[u8]) -> Self::F {
|
||||
Self::hash_to_F(&[Self::CONTEXT, b"rho"].concat(), binding)
|
||||
}
|
||||
|
||||
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(&{
|
||||
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()
|
||||
}
|
||||
|
||||
fn F_len() -> usize {
|
||||
32
|
||||
}
|
||||
|
||||
fn G_len() -> usize {
|
||||
33
|
||||
}
|
||||
|
||||
fn F_from_slice(slice: &[u8]) -> Result<Self::F, CurveError> {
|
||||
let bytes: [u8; 32] = slice.try_into()
|
||||
.map_err(|_| CurveError::InvalidLength(32, slice.len()))?;
|
||||
|
||||
let scalar = Self::F::from_repr(bytes.into());
|
||||
if scalar.is_none().into() {
|
||||
Err(CurveError::InvalidScalar)?;
|
||||
}
|
||||
|
||||
Ok(scalar.unwrap())
|
||||
}
|
||||
|
||||
fn G_from_slice(slice: &[u8]) -> Result<Self::G, CurveError> {
|
||||
let bytes: [u8; 33] = slice.try_into()
|
||||
.map_err(|_| CurveError::InvalidLength(33, slice.len()))?;
|
||||
|
||||
let point = Self::G::from_bytes(&bytes.into());
|
||||
if point.is_none().into() || point.unwrap().is_identity().into() {
|
||||
Err(CurveError::InvalidPoint)?;
|
||||
}
|
||||
|
||||
Ok(point.unwrap())
|
||||
}
|
||||
|
||||
fn F_to_bytes(f: &Self::F) -> Vec<u8> {
|
||||
f.to_repr().as_ref().to_vec()
|
||||
}
|
||||
|
||||
fn G_to_bytes(g: &Self::G) -> Vec<u8> {
|
||||
g.to_bytes().as_ref().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "p256")]
|
||||
#[derive(Clone)]
|
||||
pub struct IetfP256Hram;
|
||||
#[cfg(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()
|
||||
)
|
||||
}
|
||||
}
|
5
crypto/frost/src/curves/mod.rs
Normal file
5
crypto/frost/src/curves/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
#[cfg(any(test, feature = "dalek"))]
|
||||
pub mod dalek;
|
||||
|
||||
#[cfg(feature = "kp256")]
|
||||
pub mod kp256;
|
|
@ -13,6 +13,8 @@ mod schnorr;
|
|||
pub mod key_gen;
|
||||
pub mod algorithm;
|
||||
pub mod sign;
|
||||
#[cfg(any(test, feature = "curves"))]
|
||||
pub mod curves;
|
||||
|
||||
pub mod tests;
|
||||
|
||||
|
|
77
crypto/frost/src/tests/literal/dalek.rs
Normal file
77
crypto/frost/src/tests/literal/dalek.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
use rand::rngs::OsRng;
|
||||
|
||||
use crate::{curves::dalek, tests::vectors::{Vectors, test_with_vectors}};
|
||||
|
||||
#[cfg(any(test, feature = "ristretto"))]
|
||||
#[test]
|
||||
fn ristretto_vectors() {
|
||||
test_with_vectors::<_, dalek::Ristretto, dalek::IetfRistrettoHram>(
|
||||
&mut OsRng,
|
||||
Vectors {
|
||||
threshold: 2,
|
||||
shares: &[
|
||||
"5c3430d391552f6e60ecdc093ff9f6f4488756aa6cebdbad75a768010b8f830e",
|
||||
"b06fc5eac20b4f6e1b271d9df2343d843e1e1fb03c4cbb673f2872d459ce6f01",
|
||||
"f17e505f0e2581c6acfe54d3846a622834b5e7b50cad9a2109a97ba7a80d5c04"
|
||||
],
|
||||
group_secret: "1b25a55e463cfd15cf14a5d3acc3d15053f08da49c8afcf3ab265f2ebc4f970b",
|
||||
group_key: "e2a62f39eede11269e3bd5a7d97554f5ca384f9f6d3dd9c3c0d05083c7254f57",
|
||||
|
||||
msg: "74657374",
|
||||
included: &[1, 3],
|
||||
nonces: &[
|
||||
[
|
||||
"b358743151e33d84bf00c12f71808f4103957c3e2cabab7b895c436b5e70f90c",
|
||||
"7bd112153b9ae1ab9b31f5e78f61f5c4ca9ee67b7ea6d1181799c409d14c350c"
|
||||
],
|
||||
[
|
||||
"22acad88478e0d0373a991092a322ebd1b9a2dad90451a976d0db3215426af0e",
|
||||
"9155e3d7bcf7cd468b980c7e20b2c77cbdfbe33a1dcae031fd8bc6b1403f4b04"
|
||||
]
|
||||
],
|
||||
sig_shares: &[
|
||||
"ff801b4e0839faa67f16dee4127b9f7fbcf5fd007900257b0e2bbc02cbe5e709",
|
||||
"afdf5481023c855bf3411a5c8a5fafa92357296a078c3b80dc168f294cb4f504"
|
||||
],
|
||||
sig: "deae61af10e8ee48ba492573592fba547f5debeff6bd6e2024e8673584746f5e".to_owned() +
|
||||
"ae6070cf0a757f027358f8409dda4e29e04c276b808c60fbea414b2c179add0e"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ed25519")]
|
||||
#[test]
|
||||
fn ed25519_vectors() {
|
||||
test_with_vectors::<_, dalek::Ed25519, dalek::IetfEd25519Hram>(
|
||||
&mut OsRng,
|
||||
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"
|
||||
}
|
||||
);
|
||||
}
|
55
crypto/frost/src/tests/literal/kp256.rs
Normal file
55
crypto/frost/src/tests/literal/kp256.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use rand::rngs::OsRng;
|
||||
|
||||
#[cfg(feature = "k256")]
|
||||
use crate::tests::{curve::test_curve, schnorr::test_schnorr};
|
||||
#[cfg(feature = "k256")]
|
||||
use crate::curves::kp256::K256;
|
||||
|
||||
#[cfg(feature = "p256")]
|
||||
use crate::tests::vectors::{Vectors, test_with_vectors};
|
||||
#[cfg(feature = "p256")]
|
||||
use crate::curves::kp256::{P256, IetfP256Hram};
|
||||
|
||||
#[cfg(feature = "k256")]
|
||||
#[test]
|
||||
fn k256_not_ietf() {
|
||||
test_curve::<_, K256>(&mut OsRng);
|
||||
test_schnorr::<_, K256>(&mut OsRng);
|
||||
}
|
||||
|
||||
#[cfg(feature = "p256")]
|
||||
#[test]
|
||||
fn p256_vectors() {
|
||||
test_with_vectors::<_, P256, IetfP256Hram>(
|
||||
&mut OsRng,
|
||||
Vectors {
|
||||
threshold: 2,
|
||||
shares: &[
|
||||
"0c9c1a0fe806c184add50bbdcac913dda73e482daf95dcb9f35dbb0d8a9f7731",
|
||||
"8d8e787bef0ff6c2f494ca45f4dad198c6bee01212d6c84067159c52e1863ad5",
|
||||
"0e80d6e8f6192c003b5488ce1eec8f5429587d48cf001541e713b2d53c09d928"
|
||||
],
|
||||
group_secret: "8ba9bba2e0fd8c4767154d35a0b7562244a4aaf6f36c8fb8735fa48b301bd8de",
|
||||
group_key: "023a309ad94e9fe8a7ba45dfc58f38bf091959d3c99cfbd02b4dc00585ec45ab70",
|
||||
|
||||
msg: "74657374",
|
||||
included: &[1, 3],
|
||||
nonces: &[
|
||||
[
|
||||
"081617b24375e069b39f649d4c4ce2fba6e38b73e7c16759de0b6079a22c4c7e",
|
||||
"4de5fb77d99f03a2491a83a6a4cb91ca3c82a3f34ce94cec939174f47c9f95dd"
|
||||
],
|
||||
[
|
||||
"d186ea92593f83ea83181b184d41aa93493301ac2bc5b4b1767e94d2db943e38",
|
||||
"486e2ee25a3fbc8e6399d748b077a2755fde99fa85cc24fa647ea4ebf5811a15"
|
||||
]
|
||||
],
|
||||
sig_shares: &[
|
||||
"9e4d8865faf8c7b3193a3b35eda3d9e12118447114b1e7d5b4809ea28067f8a9",
|
||||
"b7d094eab6305ae74daeed1acd31abba9ab81f638d38b72c132cb25a5dfae1fc"
|
||||
],
|
||||
sig: "0342c14c77f9d4ef9b8bd64fb0d7bbfdb9f8216a44e5f7bbe6ac0f3ed5e1a57367".to_owned() +
|
||||
"561e1d51b129229966e92850bad5859bfee96926fad3007cd3f38639e1ffb554"
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1 +1,4 @@
|
|||
mod p256;
|
||||
#[cfg(any(test, feature = "dalek"))]
|
||||
mod dalek;
|
||||
#[cfg(feature = "kp256")]
|
||||
mod kp256;
|
||||
|
|
|
@ -1,219 +0,0 @@
|
|||
use core::convert::TryInto;
|
||||
|
||||
use rand::{RngCore, CryptoRng, rngs::OsRng};
|
||||
|
||||
use ff::{Field, PrimeField};
|
||||
use group::{Group, GroupEncoding};
|
||||
|
||||
use sha2::{digest::Update, Digest, Sha256};
|
||||
|
||||
use p256::{elliptic_curve::bigint::{Encoding, U384}, Scalar, ProjectivePoint};
|
||||
|
||||
use crate::{
|
||||
CurveError, Curve,
|
||||
algorithm::Hram,
|
||||
tests::{curve::test_curve, schnorr::test_schnorr, vectors::{Vectors, vectors}}
|
||||
};
|
||||
|
||||
const CONTEXT_STRING: &[u8] = b"FROST-P256-SHA256-v5";
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
#[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"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct P256;
|
||||
impl Curve for P256 {
|
||||
type F = Scalar;
|
||||
type G = ProjectivePoint;
|
||||
type T = ProjectivePoint;
|
||||
|
||||
const ID: &'static [u8] = b"P-256";
|
||||
|
||||
const GENERATOR: Self::G = Self::G::GENERATOR;
|
||||
const GENERATOR_TABLE: Self::G = Self::G::GENERATOR;
|
||||
|
||||
const LITTLE_ENDIAN: bool = false;
|
||||
|
||||
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_repr());
|
||||
Self::hash_to_F(&[CONTEXT_STRING, b"nonce"].concat(), &seed)
|
||||
}
|
||||
|
||||
fn hash_msg(msg: &[u8]) -> Vec<u8> {
|
||||
(&Sha256::new()
|
||||
.chain(CONTEXT_STRING)
|
||||
.chain(b"digest")
|
||||
.chain(msg)
|
||||
.finalize()
|
||||
).to_vec()
|
||||
}
|
||||
|
||||
fn hash_binding_factor(binding: &[u8]) -> Self::F {
|
||||
Self::hash_to_F(&[CONTEXT_STRING, b"rho"].concat(), binding)
|
||||
}
|
||||
|
||||
fn hash_to_F(dst: &[u8], msg: &[u8]) -> Self::F {
|
||||
let mut modulus = vec![0; 16];
|
||||
modulus.extend(&(Scalar::zero() - Scalar::one()).to_repr());
|
||||
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 ..]
|
||||
).unwrap()
|
||||
}
|
||||
|
||||
fn F_len() -> usize {
|
||||
32
|
||||
}
|
||||
|
||||
fn G_len() -> usize {
|
||||
33
|
||||
}
|
||||
|
||||
fn F_from_slice(slice: &[u8]) -> Result<Self::F, CurveError> {
|
||||
let bytes: [u8; 32] = slice.try_into()
|
||||
.map_err(|_| CurveError::InvalidLength(32, slice.len()))?;
|
||||
|
||||
let scalar = Scalar::from_repr(bytes.into());
|
||||
if scalar.is_none().into() {
|
||||
Err(CurveError::InvalidScalar)?;
|
||||
}
|
||||
|
||||
Ok(scalar.unwrap())
|
||||
}
|
||||
|
||||
fn G_from_slice(slice: &[u8]) -> Result<Self::G, CurveError> {
|
||||
let bytes: [u8; 33] = slice.try_into()
|
||||
.map_err(|_| CurveError::InvalidLength(33, slice.len()))?;
|
||||
|
||||
let point = ProjectivePoint::from_bytes(&bytes.into());
|
||||
if point.is_none().into() || point.unwrap().is_identity().into() {
|
||||
Err(CurveError::InvalidPoint)?;
|
||||
}
|
||||
|
||||
Ok(point.unwrap())
|
||||
}
|
||||
|
||||
fn F_to_bytes(f: &Self::F) -> Vec<u8> {
|
||||
(&f.to_bytes()).to_vec()
|
||||
}
|
||||
|
||||
fn G_to_bytes(g: &Self::G) -> Vec<u8> {
|
||||
(&g.to_bytes()).to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn p256_curve() {
|
||||
test_curve::<_, P256>(&mut OsRng);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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: &ProjectivePoint, A: &ProjectivePoint, m: &[u8]) -> Scalar {
|
||||
P256::hash_to_F(
|
||||
&[CONTEXT_STRING, b"chal"].concat(),
|
||||
&[&P256::G_to_bytes(R), &P256::G_to_bytes(A), m].concat()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn p256_vectors() {
|
||||
vectors::<P256, IetfP256Hram>(
|
||||
Vectors {
|
||||
threshold: 2,
|
||||
shares: &[
|
||||
"0c9c1a0fe806c184add50bbdcac913dda73e482daf95dcb9f35dbb0d8a9f7731",
|
||||
"8d8e787bef0ff6c2f494ca45f4dad198c6bee01212d6c84067159c52e1863ad5",
|
||||
"0e80d6e8f6192c003b5488ce1eec8f5429587d48cf001541e713b2d53c09d928"
|
||||
],
|
||||
group_secret: "8ba9bba2e0fd8c4767154d35a0b7562244a4aaf6f36c8fb8735fa48b301bd8de",
|
||||
group_key: "023a309ad94e9fe8a7ba45dfc58f38bf091959d3c99cfbd02b4dc00585ec45ab70",
|
||||
|
||||
msg: "74657374",
|
||||
included: &[1, 3],
|
||||
nonces: &[
|
||||
[
|
||||
"081617b24375e069b39f649d4c4ce2fba6e38b73e7c16759de0b6079a22c4c7e",
|
||||
"4de5fb77d99f03a2491a83a6a4cb91ca3c82a3f34ce94cec939174f47c9f95dd"
|
||||
],
|
||||
[
|
||||
"d186ea92593f83ea83181b184d41aa93493301ac2bc5b4b1767e94d2db943e38",
|
||||
"486e2ee25a3fbc8e6399d748b077a2755fde99fa85cc24fa647ea4ebf5811a15"
|
||||
]
|
||||
],
|
||||
sig_shares: &[
|
||||
"9e4d8865faf8c7b3193a3b35eda3d9e12118447114b1e7d5b4809ea28067f8a9",
|
||||
"b7d094eab6305ae74daeed1acd31abba9ab81f638d38b72c132cb25a5dfae1fc"
|
||||
],
|
||||
sig: "0342c14c77f9d4ef9b8bd64fb0d7bbfdb9f8216a44e5f7bbe6ac0f3ed5e1a57367".to_owned() +
|
||||
"561e1d51b129229966e92850bad5859bfee96926fad3007cd3f38639e1ffb554"
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
use std::{sync::Arc, collections::HashMap};
|
||||
|
||||
use rand_core::{RngCore, CryptoRng};
|
||||
|
||||
use crate::{
|
||||
Curve, MultisigKeys,
|
||||
algorithm::{Schnorr, Hram},
|
||||
sign::{PreprocessPackage, StateMachine, AlgorithmMachine},
|
||||
tests::recover
|
||||
tests::{curve::test_curve, schnorr::test_schnorr, recover}
|
||||
};
|
||||
|
||||
pub struct Vectors {
|
||||
|
@ -55,7 +57,16 @@ fn vectors_to_multisig_keys<C: Curve>(vectors: &Vectors) -> HashMap<u16, Multisi
|
|||
keys
|
||||
}
|
||||
|
||||
pub fn vectors<C: Curve, H: Hram<C>>(vectors: Vectors) {
|
||||
pub fn test_with_vectors<
|
||||
R: RngCore + CryptoRng,
|
||||
C: Curve,
|
||||
H: Hram<C>
|
||||
>(rng: &mut R, vectors: Vectors) {
|
||||
// Do basic tests before trying the vectors
|
||||
test_curve::<_, C>(&mut *rng);
|
||||
test_schnorr::<_, C>(rng);
|
||||
|
||||
// Test against the vectors
|
||||
let keys = vectors_to_multisig_keys::<C>(&vectors);
|
||||
let group_key = C::G_from_slice(&hex::decode(vectors.group_key).unwrap()).unwrap();
|
||||
assert_eq!(
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
Loading…
Reference in a new issue