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 }