From 55a895d65a18f0cca0fb2cd5e46c1dcc15e4af83 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 5 Jun 2022 16:08:51 -0400 Subject: [PATCH 1/3] Add first party support for k256 and p256 under feature flags Given the lack of vectors for k256, it's currently a match of the p256 spec (with a distinct context string), yet p256 is still always used when testing. --- crypto/frost/Cargo.toml | 8 + crypto/frost/src/curves/kp256.rs | 133 +++++++++++ crypto/frost/src/curves/mod.rs | 48 ++++ crypto/frost/src/lib.rs | 2 + .../frost/src/tests/literal/expand_message.rs | 15 ++ crypto/frost/src/tests/literal/kp256.rs | 80 +++++++ crypto/frost/src/tests/literal/mod.rs | 3 +- crypto/frost/src/tests/literal/p256.rs | 219 ------------------ 8 files changed, 288 insertions(+), 220 deletions(-) create mode 100644 crypto/frost/src/curves/kp256.rs create mode 100644 crypto/frost/src/curves/mod.rs create mode 100644 crypto/frost/src/tests/literal/expand_message.rs create mode 100644 crypto/frost/src/tests/literal/kp256.rs delete mode 100644 crypto/frost/src/tests/literal/p256.rs diff --git a/crypto/frost/Cargo.toml b/crypto/frost/Cargo.toml index d5f5f2dc..2ed0bb3c 100644 --- a/crypto/frost/Cargo.toml +++ b/crypto/frost/Cargo.toml @@ -15,6 +15,10 @@ 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 } + transcript = { path = "../transcript" } multiexp = { path = "../multiexp", features = ["batch"] } @@ -23,3 +27,7 @@ multiexp = { path = "../multiexp", features = ["batch"] } rand = "0.8" sha2 = "0.10" p256 = { version = "0.10", features = ["arithmetic"] } + +[features] +p256 = ["sha2", "dep:p256"] +k256 = ["sha2", "dep:k256"] diff --git a/crypto/frost/src/curves/kp256.rs b/crypto/frost/src/curves/kp256.rs new file mode 100644 index 00000000..4fcdecb8 --- /dev/null +++ b/crypto/frost/src/curves/kp256.rs @@ -0,0 +1,133 @@ +use core::{marker::PhantomData, convert::TryInto}; + +use rand_core::{RngCore, CryptoRng}; + +use ff::{Field, PrimeField}; +use group::{Group, GroupEncoding}; + +use sha2::{digest::Update, Digest, Sha256}; + +#[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}; + +#[allow(non_snake_case)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct KP256 { + _P: PhantomData

+} + +pub(crate) trait KP256Instance

{ + const CONTEXT: &'static [u8]; + const ID: &'static [u8]; + const GENERATOR: P; +} + +#[cfg(any(test, feature = "p256"))] +pub type P256 = KP256; +#[cfg(any(test, feature = "p256"))] +impl KP256Instance 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; +#[cfg(feature = "k256")] +impl KP256Instance 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 Curve for KP256

where + KP256

: KP256Instance

, + P::Scalar: PrimeField, + ::Repr: From<[u8; 32]> + AsRef<[u8]>, + P::Repr: From<[u8; 33]> + AsRef<[u8]> { + type F = P::Scalar; + type G = P; + type T = P; + + const ID: &'static [u8] = >::ID; + + const GENERATOR: Self::G = >::GENERATOR; + const GENERATOR_TABLE: Self::G = >::GENERATOR; + + const LITTLE_ENDIAN: bool = false; + + fn random_nonce(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 { + (&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 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 ..] + ).unwrap() + } + + fn F_len() -> usize { + 32 + } + + fn G_len() -> usize { + 33 + } + + fn F_from_slice(slice: &[u8]) -> Result { + 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 { + 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 { + f.to_repr().as_ref().to_vec() + } + + fn G_to_bytes(g: &Self::G) -> Vec { + g.to_bytes().as_ref().to_vec() + } +} diff --git a/crypto/frost/src/curves/mod.rs b/crypto/frost/src/curves/mod.rs new file mode 100644 index 00000000..fab9f2a4 --- /dev/null +++ b/crypto/frost/src/curves/mod.rs @@ -0,0 +1,48 @@ +use sha2::{Digest, Sha256}; + +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> { + 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::>(); + msg.push(i); + msg.extend(dst_prime); + b.push(Sha256::digest(msg).to_vec()); + } + + Some(b[1 ..].concat()[.. usize::from(len)].to_vec()) +} diff --git a/crypto/frost/src/lib.rs b/crypto/frost/src/lib.rs index 3f7c2b4e..b7146247 100644 --- a/crypto/frost/src/lib.rs +++ b/crypto/frost/src/lib.rs @@ -13,6 +13,8 @@ mod schnorr; pub mod key_gen; pub mod algorithm; pub mod sign; +#[cfg(any(test, feature = "p256", feature = "k256"))] +pub mod curves; pub mod tests; diff --git a/crypto/frost/src/tests/literal/expand_message.rs b/crypto/frost/src/tests/literal/expand_message.rs new file mode 100644 index 00000000..762ab0df --- /dev/null +++ b/crypto/frost/src/tests/literal/expand_message.rs @@ -0,0 +1,15 @@ +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" + ) + ); +} diff --git a/crypto/frost/src/tests/literal/kp256.rs b/crypto/frost/src/tests/literal/kp256.rs new file mode 100644 index 00000000..3adf42dd --- /dev/null +++ b/crypto/frost/src/tests/literal/kp256.rs @@ -0,0 +1,80 @@ +use rand::rngs::OsRng; + +use crate::{ + Curve, + curves::kp256::{KP256Instance, P256}, + algorithm::Hram, + tests::{curve::test_curve, schnorr::test_schnorr, vectors::{Vectors, vectors}} +}; + +#[cfg(feature = "k256")] +use crate::curves::kp256::K256; + +#[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 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::( + 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" + } + ); +} + +#[cfg(feature = "k256")] +#[test] +fn k256_curve() { + test_curve::<_, K256>(&mut OsRng); +} + +#[cfg(feature = "k256")] +#[test] +fn k256_schnorr() { + test_schnorr::<_, K256>(&mut OsRng); +} diff --git a/crypto/frost/src/tests/literal/mod.rs b/crypto/frost/src/tests/literal/mod.rs index eea846ee..fc2aab5a 100644 --- a/crypto/frost/src/tests/literal/mod.rs +++ b/crypto/frost/src/tests/literal/mod.rs @@ -1 +1,2 @@ -mod p256; +mod expand_message; +mod kp256; diff --git a/crypto/frost/src/tests/literal/p256.rs b/crypto/frost/src/tests/literal/p256.rs deleted file mode 100644 index 1ca4ed39..00000000 --- a/crypto/frost/src/tests/literal/p256.rs +++ /dev/null @@ -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> { - 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::>(); - 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(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 { - (&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 { - 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 { - 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 { - (&f.to_bytes()).to_vec() - } - - fn G_to_bytes(g: &Self::G) -> Vec { - (&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 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::( - 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" - } - ); -} From e0ce6e5c12e3bc35f9d9821f75849c65974919f2 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 6 Jun 2022 02:18:25 -0400 Subject: [PATCH 2/3] 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. --- coins/monero/Cargo.toml | 9 +- coins/monero/src/frost.rs | 114 +----------------- coins/monero/src/tests/mod.rs | 3 - crypto/dalek-ff-group/Cargo.toml | 3 +- crypto/dalek-ff-group/src/lib.rs | 3 +- crypto/frost/Cargo.toml | 24 ++-- crypto/frost/src/curves/ed25519.rs | 104 ++++++++++++++++ crypto/frost/src/curves/kp256.rs | 71 +++++++---- crypto/frost/src/curves/mod.rs | 49 +------- crypto/frost/src/lib.rs | 2 +- .../frost/src/tests/literal/ed25519.rs | 37 +----- .../frost/src/tests/literal/expand_message.rs | 15 --- crypto/frost/src/tests/literal/kp256.rs | 16 +-- crypto/frost/src/tests/literal/mod.rs | 3 +- crypto/multiexp/Cargo.toml | 2 +- 15 files changed, 189 insertions(+), 266 deletions(-) create mode 100644 crypto/frost/src/curves/ed25519.rs rename coins/monero/src/tests/frost.rs => crypto/frost/src/tests/literal/ed25519.rs (61%) delete mode 100644 crypto/frost/src/tests/literal/expand_message.rs diff --git a/coins/monero/Cargo.toml b/coins/monero/Cargo.toml index 22e1dfe5..0934a813 100644 --- a/coins/monero/Cargo.toml +++ b/coins/monero/Cargo.toml @@ -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" diff --git a/coins/monero/src/frost.rs b/coins/monero/src/frost.rs index 4653cc3e..d16238b4 100644 --- a/coins/monero/src/frost.rs +++ b/coins/monero/src/frost.rs @@ -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::ed25519::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, const WIDE: bool> { - _digest: PhantomData -} - -// Removed requirements for D to have all of these -impl, const WIDE: bool> Clone for Ed25519Internal { - fn clone(&self) -> Self { *self } -} -impl, const WIDE: bool> Copy for Ed25519Internal {} -impl, const WIDE: bool> PartialEq for Ed25519Internal { - fn eq(&self, _: &Self) -> bool { true } -} -impl, const WIDE: bool> Eq for Ed25519Internal {} -impl, const WIDE: bool> Debug for Ed25519Internal { - fn fmt(&self, _: &mut Formatter<'_>) -> Result<(), core::fmt::Error> { Ok(()) } -} - -impl, const WIDE: bool> Curve for Ed25519Internal { - 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(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 { - 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 { - 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 { - 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 { - f.to_repr().to_vec() - } - - fn G_to_bytes(g: &Self::G) -> Vec { - g.compress().to_bytes().to_vec() - } -} - -pub type Ed25519 = Ed25519Internal; - // Used to prove legitimacy of key images and nonces which both involve other basepoints #[derive(Clone)] pub struct DLEqProof { diff --git a/coins/monero/src/tests/mod.rs b/coins/monero/src/tests/mod.rs index c964dd96..b42cbcff 100644 --- a/coins/monero/src/tests/mod.rs +++ b/coins/monero/src/tests/mod.rs @@ -1,4 +1 @@ -#[cfg(feature = "multisig")] -mod frost; - mod clsag; diff --git a/crypto/dalek-ff-group/Cargo.toml b/crypto/dalek-ff-group/Cargo.toml index 55140724..79fdceb0 100644 --- a/crypto/dalek-ff-group/Cargo.toml +++ b/crypto/dalek-ff-group/Cargo.toml @@ -12,7 +12,6 @@ digest = "0.10" subtle = "2.4" -ff = "0.11" -group = "0.11" +group = "0.12" curve25519-dalek = "3.2" diff --git a/crypto/dalek-ff-group/src/lib.rs b/crypto/dalek-ff-group/src/lib.rs index 5bf1823d..9fa622f9 100644 --- a/crypto/dalek-ff-group/src/lib.rs +++ b/crypto/dalek-ff-group/src/lib.rs @@ -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); diff --git a/crypto/frost/Cargo.toml b/crypto/frost/Cargo.toml index 2ed0bb3c..8112b97b 100644 --- a/crypto/frost/Cargo.toml +++ b/crypto/frost/Cargo.toml @@ -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"] diff --git a/crypto/frost/src/curves/ed25519.rs b/crypto/frost/src/curves/ed25519.rs new file mode 100644 index 00000000..f6e9aa52 --- /dev/null +++ b/crypto/frost/src/curves/ed25519.rs @@ -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(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 { + 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 { + 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 { + 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 { + f.to_repr().to_vec() + } + + fn G_to_bytes(g: &Self::G) -> Vec { + g.compress().to_bytes().to_vec() + } +} + +#[derive(Copy, Clone)] +pub struct IetfEd25519Hram; +impl Hram 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()) + } +} diff --git a/crypto/frost/src/curves/kp256.rs b/crypto/frost/src/curves/kp256.rs index 4fcdecb8..3abb1879 100644 --- a/crypto/frost/src/curves/kp256.rs +++ b/crypto/frost/src/curves/kp256.rs @@ -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: PhantomData

+pub struct KP256 { + _G: PhantomData } -pub(crate) trait KP256Instance

{ +pub(crate) trait KP256Instance { 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 for K256 { const GENERATOR: k256::ProjectivePoint = k256::ProjectivePoint::GENERATOR; } -impl Curve for KP256

where - KP256

: KP256Instance

, - P::Scalar: 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 Curve for KP256 where + KP256: KP256Instance, + G::Scalar: 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] = >::ID; + const ID: &'static [u8] = >::ID; - const GENERATOR: Self::G = >::GENERATOR; - const GENERATOR_TABLE: Self::G = >::GENERATOR; + const GENERATOR: Self::G = >::GENERATOR; + const GENERATOR_TABLE: Self::G = >::GENERATOR; const LITTLE_ENDIAN: bool = false; @@ -81,13 +80,21 @@ impl Curve for KP256

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::::expand_message(&[msg], dst, 48).unwrap().fill_bytes(&mut bytes); + bytes + }).reduce(&modulus).unwrap().to_be_bytes()[16 ..] ).unwrap() } @@ -131,3 +138,17 @@ impl Curve for KP256

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 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() + ) + } +} diff --git a/crypto/frost/src/curves/mod.rs b/crypto/frost/src/curves/mod.rs index fab9f2a4..890f8600 100644 --- a/crypto/frost/src/curves/mod.rs +++ b/crypto/frost/src/curves/mod.rs @@ -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> { - 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::>(); - 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; diff --git a/crypto/frost/src/lib.rs b/crypto/frost/src/lib.rs index b7146247..bf876f51 100644 --- a/crypto/frost/src/lib.rs +++ b/crypto/frost/src/lib.rs @@ -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; diff --git a/coins/monero/src/tests/frost.rs b/crypto/frost/src/tests/literal/ed25519.rs similarity index 61% rename from coins/monero/src/tests/frost.rs rename to crypto/frost/src/tests/literal/ed25519.rs index 710328f8..43b31dc4 100644 --- a/coins/monero/src/tests/frost.rs +++ b/crypto/frost/src/tests/literal/ed25519.rs @@ -1,48 +1,23 @@ use rand::rngs::OsRng; -use sha2::Sha512; - -use dalek_ff_group as dfg; -use frost::{ - Curve, - algorithm::Hram, +use crate::{ + curves::ed25519::{Ed25519, IetfEd25519Hram}, tests::{curve::test_curve, schnorr::test_schnorr, vectors::{Vectors, vectors}} }; -use crate::frost::{Ed25519, Ed25519Internal}; - #[test] -fn frost_ed25519_curve() { +fn ed25519_curve() { test_curve::<_, Ed25519>(&mut OsRng); } #[test] -fn frost_ed25519_schnorr() { +fn 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; -// If this is kept, we can remove WIDE -type TestEd25519 = Ed25519Internal; - -#[derive(Copy, Clone)] -struct IetfEd25519Hram {} -impl Hram 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::( +fn ed25519_vectors() { + vectors::( Vectors { threshold: 2, shares: &[ diff --git a/crypto/frost/src/tests/literal/expand_message.rs b/crypto/frost/src/tests/literal/expand_message.rs deleted file mode 100644 index 762ab0df..00000000 --- a/crypto/frost/src/tests/literal/expand_message.rs +++ /dev/null @@ -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" - ) - ); -} diff --git a/crypto/frost/src/tests/literal/kp256.rs b/crypto/frost/src/tests/literal/kp256.rs index 3adf42dd..60a29bee 100644 --- a/crypto/frost/src/tests/literal/kp256.rs +++ b/crypto/frost/src/tests/literal/kp256.rs @@ -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 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::( diff --git a/crypto/frost/src/tests/literal/mod.rs b/crypto/frost/src/tests/literal/mod.rs index fc2aab5a..77da3224 100644 --- a/crypto/frost/src/tests/literal/mod.rs +++ b/crypto/frost/src/tests/literal/mod.rs @@ -1,2 +1,3 @@ -mod expand_message; mod kp256; +#[cfg(feature = "ed25519")] +mod ed25519; diff --git a/crypto/multiexp/Cargo.toml b/crypto/multiexp/Cargo.toml index facc1aef..b45dbcf5 100644 --- a/crypto/multiexp/Cargo.toml +++ b/crypto/multiexp/Cargo.toml @@ -7,7 +7,7 @@ authors = ["Luke Parker "] edition = "2021" [dependencies] -group = "0.11" +group = "0.12" rand_core = { version = "0.6", optional = true } From 301634dd8ebe8775424847ff15baad7c50ec2ebe Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 6 Jun 2022 04:22:49 -0400 Subject: [PATCH 3/3] Add support for Ristretto Replaces P-256 as the curve used for testing FROST. --- coins/monero/src/frost.rs | 2 +- crypto/dalek-ff-group/src/lib.rs | 400 ++++++++++------------ crypto/frost/Cargo.toml | 15 +- crypto/frost/src/curves/dalek.rs | 163 +++++++++ crypto/frost/src/curves/ed25519.rs | 104 ------ crypto/frost/src/curves/kp256.rs | 10 +- crypto/frost/src/curves/mod.rs | 8 +- crypto/frost/src/tests/literal/dalek.rs | 77 +++++ crypto/frost/src/tests/literal/ed25519.rs | 51 --- crypto/frost/src/tests/literal/kp256.rs | 39 +-- crypto/frost/src/tests/literal/mod.rs | 5 +- crypto/frost/src/tests/vectors.rs | 15 +- 12 files changed, 477 insertions(+), 412 deletions(-) create mode 100644 crypto/frost/src/curves/dalek.rs delete mode 100644 crypto/frost/src/curves/ed25519.rs create mode 100644 crypto/frost/src/tests/literal/dalek.rs delete mode 100644 crypto/frost/src/tests/literal/ed25519.rs diff --git a/coins/monero/src/frost.rs b/coins/monero/src/frost.rs index d16238b4..8b151abc 100644 --- a/coins/monero/src/frost.rs +++ b/coins/monero/src/frost.rs @@ -11,7 +11,7 @@ use curve25519_dalek::{ use transcript::{Transcript as TranscriptTrait, DigestTranscript}; use frost::Curve; -pub use frost::curves::ed25519::Ed25519; +pub use frost::curves::dalek::Ed25519; use dalek_ff_group as dfg; use crate::random_scalar; diff --git a/crypto/dalek-ff-group/src/lib.rs b/crypto/dalek-ff-group/src/lib.rs index 9fa622f9..c919fea9 100644 --- a/crypto/dalek-ff-group/src/lib.rs +++ b/crypto/dalek-ff-group/src/lib.rs @@ -16,88 +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 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 { + DScalar::from_canonical_bytes(bytes).map(|x| Self(x)) } -} -impl Borrow 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 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 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 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 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>(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 { @@ -153,163 +188,106 @@ impl PrimeField for Scalar { fn root_of_unity() -> Self { unimplemented!() } } -impl Scalar { - pub fn from_hash>(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>(iter: I) -> $Point { Self($DPoint::sum(iter)) } } + impl<'a> Sum<&'a $Point> for $Point { + fn sum>(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 for &$Table { + type Output = $Point; + fn mul(self, b: Scalar) -> $Point { $Point(&b.0 * &self.0) } + } + }; } -impl Borrow for EdwardsPoint { - fn borrow(&self) -> &DPoint { - &self.0 - } -} +dalek_group!( + EdwardsPoint, + DEdwardsPoint, -impl Borrow for &EdwardsPoint { - fn borrow(&self) -> &DPoint { - &self.0 - } -} + EdwardsBasepointTable, + DEdwardsBasepointTable, -impl Add for EdwardsPoint { - type Output = Self; - fn add(self, b: EdwardsPoint) -> EdwardsPoint { Self(self.0 + b.0) } -} -impl AddAssign for EdwardsPoint { - fn add_assign(&mut self, other: EdwardsPoint) { self.0 += other.0 } -} -impl Sum for EdwardsPoint { - fn sum>(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>(iter: I) -> EdwardsPoint { Self(DPoint::sum(iter)) } -} - -impl Sub for EdwardsPoint { - type Output = Self; - fn sub(self, b: EdwardsPoint) -> EdwardsPoint { Self(self.0 - b.0) } -} -impl SubAssign 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 for EdwardsPoint { - type Output = Self; - fn mul(self, b: Scalar) -> EdwardsPoint { Self(b.0 * self.0) } -} -impl MulAssign 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 { - 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 { - 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 for &EdwardsBasepointTable { - fn borrow(&self) -> &DTable { - &self.0 - } -} - -impl Mul for &EdwardsBasepointTable { - type Output = EdwardsPoint; - fn mul(self, b: Scalar) -> EdwardsPoint { EdwardsPoint(&b.0 * &self.0) } -} diff --git a/crypto/frost/Cargo.toml b/crypto/frost/Cargo.toml index 8112b97b..6d9af3f6 100644 --- a/crypto/frost/Cargo.toml +++ b/crypto/frost/Cargo.toml @@ -30,12 +30,13 @@ multiexp = { path = "../multiexp", features = ["batch"] } rand = "0.8" sha2 = "0.10" -elliptic-curve = { version = "0.12", features = ["hash2curve"] } -p256 = { version = "0.11", features = ["arithmetic", "hash2curve"] } +dalek-ff-group = { path = "../dalek-ff-group" } [features] -curves = [] -kp256 = ["elliptic-curve"] -p256 = ["curves", "kp256", "sha2", "dep:p256"] -k256 = ["curves", "kp256", "sha2", "dep:k256"] -ed25519 = ["curves", "sha2", "dalek-ff-group"] +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"] diff --git a/crypto/frost/src/curves/dalek.rs b/crypto/frost/src/curves/dalek.rs new file mode 100644 index 00000000..994ac651 --- /dev/null +++ b/crypto/frost/src/curves/dalek.rs @@ -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(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 { + 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 { + 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 { + 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 { + f.to_repr().to_vec() + } + + fn G_to_bytes(g: &Self::G) -> Vec { + 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", +); diff --git a/crypto/frost/src/curves/ed25519.rs b/crypto/frost/src/curves/ed25519.rs deleted file mode 100644 index f6e9aa52..00000000 --- a/crypto/frost/src/curves/ed25519.rs +++ /dev/null @@ -1,104 +0,0 @@ -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(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 { - 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 { - 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 { - 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 { - f.to_repr().to_vec() - } - - fn G_to_bytes(g: &Self::G) -> Vec { - g.compress().to_bytes().to_vec() - } -} - -#[derive(Copy, Clone)] -pub struct IetfEd25519Hram; -impl Hram 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()) - } -} diff --git a/crypto/frost/src/curves/kp256.rs b/crypto/frost/src/curves/kp256.rs index 3abb1879..e7421c5a 100644 --- a/crypto/frost/src/curves/kp256.rs +++ b/crypto/frost/src/curves/kp256.rs @@ -10,7 +10,7 @@ use group::{Group, GroupEncoding}; use elliptic_curve::{bigint::{Encoding, U384}, hash2curve::{Expander, ExpandMsg, ExpandMsgXmd}}; use crate::{CurveError, Curve}; -#[cfg(any(test, feature = "p256"))] +#[cfg(feature = "p256")] use crate::algorithm::Hram; #[allow(non_snake_case)] @@ -25,9 +25,9 @@ pub(crate) trait KP256Instance { const GENERATOR: G; } -#[cfg(any(test, feature = "p256"))] +#[cfg(feature = "p256")] pub type P256 = KP256; -#[cfg(any(test, feature = "p256"))] +#[cfg(feature = "p256")] impl KP256Instance for P256 { const CONTEXT: &'static [u8] = b"FROST-P256-SHA256-v5"; const ID: &'static [u8] = b"P-256"; @@ -139,10 +139,10 @@ impl Curve for KP256 where } } -#[cfg(any(test, feature = "p256"))] +#[cfg(feature = "p256")] #[derive(Clone)] pub struct IetfP256Hram; -#[cfg(any(test, feature = "p256"))] +#[cfg(feature = "p256")] impl Hram for IetfP256Hram { #[allow(non_snake_case)] fn hram(R: &p256::ProjectivePoint, A: &p256::ProjectivePoint, m: &[u8]) -> p256::Scalar { diff --git a/crypto/frost/src/curves/mod.rs b/crypto/frost/src/curves/mod.rs index 890f8600..3742e1f9 100644 --- a/crypto/frost/src/curves/mod.rs +++ b/crypto/frost/src/curves/mod.rs @@ -1,5 +1,5 @@ -#[cfg(any(test, feature = "kp256"))] -pub mod kp256; +#[cfg(any(test, feature = "dalek"))] +pub mod dalek; -#[cfg(feature = "ed25519")] -pub mod ed25519; +#[cfg(feature = "kp256")] +pub mod kp256; diff --git a/crypto/frost/src/tests/literal/dalek.rs b/crypto/frost/src/tests/literal/dalek.rs new file mode 100644 index 00000000..7cd3e92f --- /dev/null +++ b/crypto/frost/src/tests/literal/dalek.rs @@ -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" + } + ); +} diff --git a/crypto/frost/src/tests/literal/ed25519.rs b/crypto/frost/src/tests/literal/ed25519.rs deleted file mode 100644 index 43b31dc4..00000000 --- a/crypto/frost/src/tests/literal/ed25519.rs +++ /dev/null @@ -1,51 +0,0 @@ -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::( - 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" - } - ); -} diff --git a/crypto/frost/src/tests/literal/kp256.rs b/crypto/frost/src/tests/literal/kp256.rs index 60a29bee..8751fbe2 100644 --- a/crypto/frost/src/tests/literal/kp256.rs +++ b/crypto/frost/src/tests/literal/kp256.rs @@ -1,26 +1,27 @@ use rand::rngs::OsRng; -use crate::{ - curves::kp256::{P256, IetfP256Hram}, - tests::{curve::test_curve, schnorr::test_schnorr, vectors::{Vectors, vectors}} -}; - +#[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 p256_curve() { - test_curve::<_, P256>(&mut OsRng); -} - -#[test] -fn p256_schnorr() { - test_schnorr::<_, P256>(&mut OsRng); +fn k256_not_ietf() { + test_curve::<_, K256>(&mut OsRng); + test_schnorr::<_, K256>(&mut OsRng); } +#[cfg(feature = "p256")] #[test] fn p256_vectors() { - vectors::( + test_with_vectors::<_, P256, IetfP256Hram>( + &mut OsRng, Vectors { threshold: 2, shares: &[ @@ -52,15 +53,3 @@ fn p256_vectors() { } ); } - -#[cfg(feature = "k256")] -#[test] -fn k256_curve() { - test_curve::<_, K256>(&mut OsRng); -} - -#[cfg(feature = "k256")] -#[test] -fn k256_schnorr() { - test_schnorr::<_, K256>(&mut OsRng); -} diff --git a/crypto/frost/src/tests/literal/mod.rs b/crypto/frost/src/tests/literal/mod.rs index 77da3224..00fe0477 100644 --- a/crypto/frost/src/tests/literal/mod.rs +++ b/crypto/frost/src/tests/literal/mod.rs @@ -1,3 +1,4 @@ +#[cfg(any(test, feature = "dalek"))] +mod dalek; +#[cfg(feature = "kp256")] mod kp256; -#[cfg(feature = "ed25519")] -mod ed25519; diff --git a/crypto/frost/src/tests/vectors.rs b/crypto/frost/src/tests/vectors.rs index c4403d07..db46de1f 100644 --- a/crypto/frost/src/tests/vectors.rs +++ b/crypto/frost/src/tests/vectors.rs @@ -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(vectors: &Vectors) -> HashMap>(vectors: Vectors) { +pub fn test_with_vectors< + R: RngCore + CryptoRng, + C: Curve, + H: Hram +>(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::(&vectors); let group_key = C::G_from_slice(&hex::decode(vectors.group_key).unwrap()).unwrap(); assert_eq!(