From 55a895d65a18f0cca0fb2cd5e46c1dcc15e4af83 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sun, 5 Jun 2022 16:08:51 -0400 Subject: [PATCH] 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" - } - ); -}