diff --git a/elliptic-curve/Cargo.lock b/elliptic-curve/Cargo.lock index 812bece07..cffaa4681 100644 --- a/elliptic-curve/Cargo.lock +++ b/elliptic-curve/Cargo.lock @@ -20,6 +20,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "block-buffer" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" +dependencies = [ + "generic-array", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -32,6 +41,15 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + [[package]] name = "crypto-bigint" version = "0.3.2" @@ -44,6 +62,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "crypto-common" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0" +dependencies = [ + "generic-array", +] + [[package]] name = "der" version = "0.5.1" @@ -56,10 +83,12 @@ dependencies = [ [[package]] name = "digest" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b" dependencies = [ + "block-buffer", + "crypto-common", "generic-array", ] @@ -80,6 +109,7 @@ dependencies = [ "sec1", "serde", "serde_json", + "sha2", "subtle", "zeroize", ] @@ -222,6 +252,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "spki" version = "0.5.3" diff --git a/elliptic-curve/Cargo.toml b/elliptic-curve/Cargo.toml index 0f48d46ab..80ad9a029 100644 --- a/elliptic-curve/Cargo.toml +++ b/elliptic-curve/Cargo.toml @@ -29,7 +29,7 @@ zeroize = { version = "1", default-features = false } # optional dependencies base64ct = { version = "1", optional = true, default-features = false } -digest = { version = "0.9", optional = true, default-features = false } +digest = { version = "0.10", optional = true } ff = { version = "0.11", optional = true, default-features = false } group = { version = "0.11", optional = true, default-features = false } hex-literal = { version = "0.3", optional = true } @@ -40,6 +40,7 @@ serde_json = { version = "1", optional = true, default-features = false, feature [dev-dependencies] hex-literal = "0.3" +sha2 = "0.10" [features] default = ["arithmetic"] diff --git a/elliptic-curve/src/hash2curve/group_digest.rs b/elliptic-curve/src/hash2curve/group_digest.rs index 736f7c935..fce3993a8 100644 --- a/elliptic-curve/src/hash2curve/group_digest.rs +++ b/elliptic-curve/src/hash2curve/group_digest.rs @@ -1,5 +1,9 @@ +use core::ops::Mul; + use super::MapToCurve; use crate::hash2field::{hash_to_field, ExpandMsg, FromOkm}; +use generic_array::typenum::{Prod, U1, U2}; +use generic_array::ArrayLength; use group::cofactor::CofactorGroup; /// Adds hashing arbitrary byte sequences to a valid group element @@ -34,9 +38,13 @@ pub trait GroupDigest { /// let pt = ProjectivePoint::hash_from_bytes::>(b"test data", b"CURVE_XOF:SHAKE-256_SSWU_RO_"); /// ``` /// - fn hash_from_bytes(msg: &[u8], dst: &'static [u8]) -> Self::Output { - let mut u = [Self::FieldElement::default(), Self::FieldElement::default()]; - hash_to_field::(msg, dst, &mut u); + fn hash_from_bytes(msg: &[u8], dst: &[u8]) -> Self::Output + where + X: ExpandMsg::Length, U2>>, + ::Length: Mul, + Prod<::Length, U2>: ArrayLength, + { + let u = hash_to_field::(msg, dst); let q0 = Self::Output::map_to_curve(u[0]); let q1 = Self::Output::map_to_curve(u[1]); // Ideally we could add and then clear cofactor once @@ -60,9 +68,13 @@ pub trait GroupDigest { /// uniformly random in G: the set of possible outputs of /// encode_to_curve is only a fraction of the points in G, and some /// points in this set are more likely to be output than others. - fn encode_from_bytes(msg: &[u8], dst: &'static [u8]) -> Self::Output { - let mut u = [Self::FieldElement::default()]; - hash_to_field::(msg, dst, &mut u); + fn encode_from_bytes(msg: &[u8], dst: &[u8]) -> Self::Output + where + X: ExpandMsg::Length, U1>>, + ::Length: Mul, + Prod<::Length, U1>: ArrayLength, + { + let u = hash_to_field::(msg, dst); let q0 = Self::Output::map_to_curve(u[0]); q0.clear_cofactor() } diff --git a/elliptic-curve/src/hash2field.rs b/elliptic-curve/src/hash2field.rs index bdf30b9a5..343f4c9c8 100644 --- a/elliptic-curve/src/hash2field.rs +++ b/elliptic-curve/src/hash2field.rs @@ -2,10 +2,13 @@ mod expand_msg; mod expand_msg_xmd; mod expand_msg_xof; +use core::ops::Mul; + pub use expand_msg::*; pub use expand_msg_xmd::*; pub use expand_msg_xof::*; -use generic_array::{typenum::Unsigned, ArrayLength, GenericArray}; +use generic_array::typenum::{Prod, Unsigned}; +use generic_array::{ArrayLength, GenericArray}; /// The trait for helping to convert to a scalar pub trait FromOkm { @@ -18,16 +21,18 @@ pub trait FromOkm { /// Convert an arbitrary byte sequence according to /// -pub fn hash_to_field(data: &[u8], domain: &'static [u8], out: &mut [T]) +pub fn hash_to_field>(data: &[u8], domain: &[u8]) -> GenericArray where - E: ExpandMsg, + E: ExpandMsg>, T: FromOkm + Default, + T::Length: Mul, + Prod: ArrayLength, { - let len_in_bytes = T::Length::to_usize() * out.len(); - let mut tmp = GenericArray::::Length>::default(); - let mut expander = E::expand_message(data, domain, len_in_bytes); - for o in out.iter_mut() { - expander.fill_bytes(&mut tmp); - *o = T::from_okm(&tmp); - } + let uniform_bytes = E::expand_message(data, domain); + + uniform_bytes + .chunks_exact(T::Length::to_usize()) + .map(GenericArray::from_slice) + .map(T::from_okm) + .collect() } diff --git a/elliptic-curve/src/hash2field/expand_msg.rs b/elliptic-curve/src/hash2field/expand_msg.rs index 9adfab766..1cf9e5e62 100644 --- a/elliptic-curve/src/hash2field/expand_msg.rs +++ b/elliptic-curve/src/hash2field/expand_msg.rs @@ -1,4 +1,5 @@ -use digest::{Digest, ExtendableOutputDirty, Update, XofReader}; +use digest::{Digest, ExtendableOutput, Update, XofReader}; +use generic_array::typenum::{IsLessOrEqual, U256}; use generic_array::{ArrayLength, GenericArray}; /// Salt when the DST is too long @@ -7,14 +8,9 @@ const OVERSIZE_DST_SALT: &[u8] = b"H2C-OVERSIZE-DST-"; const MAX_DST_LEN: usize = 255; /// Trait for types implementing expand_message interface for hash_to_field -pub trait ExpandMsg { - /// Expands `msg` to the required number of bytes - /// Returns an expander that can be used to call `read` until enough - /// bytes have been consumed - fn expand_message(msg: &[u8], dst: &'static [u8], len_in_bytes: usize) -> Self; - - /// Fill the array with the expanded bytes - fn fill_bytes(&mut self, okm: &mut [u8]); +pub trait ExpandMsg> { + /// Expands `msg` to the required number of bytes in `L` + fn expand_message(msg: &[u8], dst: &[u8]) -> GenericArray; } /// The domain separation tag @@ -22,24 +18,30 @@ pub trait ExpandMsg { /// Implements [section 5.4.3 of `draft-irtf-cfrg-hash-to-curve-13`][dst]. /// /// [dst]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-13#section-5.4.3 -pub(crate) enum Domain> { +pub(crate) enum Domain<'a, L> +where + L: ArrayLength + IsLessOrEqual, +{ /// > 255 Hashed(GenericArray), /// <= 255 - Array(&'static [u8]), + Array(&'a [u8]), } -impl> Domain { - pub fn xof(dst: &'static [u8]) -> Self +impl<'a, L> Domain<'a, L> +where + L: ArrayLength + IsLessOrEqual, +{ + pub fn xof(dst: &'a [u8]) -> Self where - X: Default + ExtendableOutputDirty + Update, + X: Default + ExtendableOutput + Update, { if dst.len() > MAX_DST_LEN { let mut data = GenericArray::::default(); X::default() .chain(OVERSIZE_DST_SALT) .chain(dst) - .finalize_xof_dirty() + .finalize_xof() .read(&mut data); Self::Hashed(data) } else { @@ -47,9 +49,9 @@ impl> Domain { } } - pub fn xmd(dst: &'static [u8]) -> Self + pub fn xmd(dst: &'a [u8]) -> Self where - X: Digest, + X: Digest + Update, { if dst.len() > MAX_DST_LEN { Self::Hashed(X::new().chain(OVERSIZE_DST_SALT).chain(dst).finalize()) @@ -65,10 +67,11 @@ impl> Domain { } } - pub fn len(&self) -> usize { + pub fn len(&self) -> u8 { match self { - Self::Hashed(_) => L::to_usize(), - Self::Array(d) => d.len(), + Self::Hashed(_) => L::to_u8(), + // Can't overflow because enforced on a type level. + Self::Array(d) => d.len() as u8, } } } diff --git a/elliptic-curve/src/hash2field/expand_msg_xmd.rs b/elliptic-curve/src/hash2field/expand_msg_xmd.rs index be599fcea..57deecff3 100644 --- a/elliptic-curve/src/hash2field/expand_msg_xmd.rs +++ b/elliptic-curve/src/hash2field/expand_msg_xmd.rs @@ -1,67 +1,42 @@ +use core::marker::PhantomData; +use core::ops::Mul; + use super::{Domain, ExpandMsg}; -use digest::{ - generic_array::{ - typenum::{IsLess, IsLessOrEqual, Unsigned, U256}, - GenericArray, - }, - BlockInput, Digest, -}; +use digest::core_api::BlockSizeUser; +use digest::{Digest, Update}; +use generic_array::typenum::{IsLess, IsLessOrEqual, NonZero, Prod, Unsigned, U255, U256, U65536}; +use generic_array::{ArrayLength, GenericArray}; +use subtle::{Choice, ConditionallySelectable}; /// Placeholder type for implementing expand_message_xmd based on a hash function -pub struct ExpandMsgXmd -where - HashT: Digest + BlockInput, - HashT::OutputSize: IsLess, - HashT::OutputSize: IsLessOrEqual, -{ - b_0: GenericArray, - b_vals: GenericArray, - domain: Domain, - index: usize, - offset: usize, - ell: usize, -} - -impl ExpandMsgXmd +pub struct ExpandMsgXmd(PhantomData) where - HashT: Digest + BlockInput, - HashT::OutputSize: IsLess, - HashT::OutputSize: IsLessOrEqual, -{ - fn next(&mut self) -> bool { - if self.index < self.ell { - self.index += 1; - self.offset = 0; - // b_0 XOR b_(idx - 1) - let mut tmp = GenericArray::::default(); - self.b_0 - .iter() - .zip(&self.b_vals[..]) - .enumerate() - .for_each(|(j, (b0val, bi1val))| tmp[j] = b0val ^ bi1val); - self.b_vals = HashT::new() - .chain(tmp) - .chain([self.index as u8]) - .chain(self.domain.data()) - .chain([self.domain.len() as u8]) - .finalize(); - true - } else { - false - } - } -} + HashT: Digest + BlockSizeUser + Update, + HashT::OutputSize: IsLessOrEqual, + HashT::OutputSize: IsLessOrEqual; /// ExpandMsgXmd implements expand_message_xmd for the ExpandMsg trait -impl ExpandMsg for ExpandMsgXmd +impl ExpandMsg for ExpandMsgXmd where - HashT: Digest + BlockInput, - HashT::OutputSize: IsLess, + HashT: Digest + BlockSizeUser + Update, + L: ArrayLength, + U255: Mul, + // If `len_in_bytes` is bigger then 256, length of the `DST` will depend on + // the output size of the hash, which is still not allowed to be bigger then 256: + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html#section-5.4.1-6 + HashT::OutputSize: IsLessOrEqual, + // Constraint set by `expand_message_xmd`: + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html#section-5.4.1-4 HashT::OutputSize: IsLessOrEqual, + // Constraint set by `expand_message_xmd`: + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html#section-5.4.1-6 + L: NonZero + IsLess> + IsLess, { - fn expand_message(msg: &[u8], dst: &'static [u8], len_in_bytes: usize) -> Self { - let b_in_bytes = HashT::OutputSize::to_usize(); - let ell = (len_in_bytes + b_in_bytes - 1) / b_in_bytes; + fn expand_message(msg: &[u8], dst: &[u8]) -> GenericArray { + let b_in_bytes = HashT::OutputSize::to_u16(); + // Can't overflow because enforced on a type level. + let ell = ((L::to_u16() + b_in_bytes - 1) / b_in_bytes) as u8; + // Enforced on the type level // if ell > 255 { // panic!("ell was too big in expand_message_xmd"); // } @@ -69,35 +44,48 @@ where let b_0 = HashT::new() .chain(GenericArray::::default()) .chain(msg) - .chain([(len_in_bytes >> 8) as u8, len_in_bytes as u8, 0u8]) + .chain(L::to_u16().to_be_bytes()) + .chain([0]) .chain(domain.data()) - .chain([domain.len() as u8]) + .chain([domain.len()]) .finalize(); - let b_vals = HashT::new() + let mut b_vals = HashT::new() .chain(&b_0[..]) .chain([1u8]) .chain(domain.data()) - .chain([domain.len() as u8]) + .chain([domain.len()]) .finalize(); - Self { - b_0, - b_vals, - domain, - index: 1, - offset: 0, - ell, - } - } + let mut buf = GenericArray::<_, L>::default(); + let mut offset = 0; - fn fill_bytes(&mut self, okm: &mut [u8]) { - for b in okm { - if self.offset == self.b_vals.len() && !self.next() { - return; + for i in 1..ell { + // b_0 XOR b_(idx - 1) + let tmp: GenericArray<_, HashT::OutputSize> = b_0 + .iter() + .zip(b_vals.as_slice()) + .map(|(b0val, bi1val)| b0val ^ bi1val) + .collect(); + for b in b_vals { + buf[offset % L::to_usize()].conditional_assign( + &b, + Choice::from(if offset < L::to_usize() { 1 } else { 0 }), + ); + offset += 1; } - *b = self.b_vals[self.offset]; - self.offset += 1; + b_vals = HashT::new() + .chain(tmp) + .chain([i + 1]) + .chain(domain.data()) + .chain([domain.len()]) + .finalize(); + } + for b in b_vals { + buf[offset % L::to_usize()] + .conditional_assign(&b, Choice::from(if offset < L::to_usize() { 1 } else { 0 })); + offset += 1; } + buf } } diff --git a/elliptic-curve/src/hash2field/expand_msg_xof.rs b/elliptic-curve/src/hash2field/expand_msg_xof.rs index 371482ae4..62f14b7a3 100644 --- a/elliptic-curve/src/hash2field/expand_msg_xof.rs +++ b/elliptic-curve/src/hash2field/expand_msg_xof.rs @@ -1,33 +1,35 @@ +use core::marker::PhantomData; + use super::ExpandMsg; use crate::hash2field::Domain; -use digest::{ExtendableOutput, ExtendableOutputDirty, Update, XofReader}; -use generic_array::typenum::U32; +use digest::{ExtendableOutput, Update, XofReader}; +use generic_array::typenum::{IsLessOrEqual, NonZero, U32, U65536}; +use generic_array::{ArrayLength, GenericArray}; /// Placeholder type for implementing expand_message_xof based on an extendable output function -pub struct ExpandMsgXof +pub struct ExpandMsgXof(PhantomData) where - HashT: Default + ExtendableOutput + ExtendableOutputDirty + Update, -{ - reader: ::Reader, -} + HashT: Default + ExtendableOutput + Update; /// ExpandMsgXof implements expand_message_xof for the ExpandMsg trait -impl ExpandMsg for ExpandMsgXof +impl ExpandMsg for ExpandMsgXof where - HashT: Default + ExtendableOutput + ExtendableOutputDirty + Update, + HashT: Default + ExtendableOutput + Update, + L: ArrayLength, + // Constraint set by `expand_message_xof`: + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html#section-5.4.2-5 + L: NonZero + IsLessOrEqual, { - fn expand_message(msg: &[u8], dst: &'static [u8], len_in_bytes: usize) -> Self { + fn expand_message(msg: &[u8], dst: &[u8]) -> GenericArray { let domain = Domain::::xof::(dst); - let reader = HashT::default() + let mut reader = HashT::default() .chain(msg) - .chain([(len_in_bytes >> 8) as u8, len_in_bytes as u8]) + .chain(L::to_u16().to_be_bytes()) .chain(domain.data()) - .chain([domain.len() as u8]) + .chain([domain.len()]) .finalize_xof(); - Self { reader } - } - - fn fill_bytes(&mut self, okm: &mut [u8]) { - self.reader.read(okm); + let mut buf = GenericArray::default(); + reader.read(&mut buf); + buf } } diff --git a/elliptic-curve/src/ops.rs b/elliptic-curve/src/ops.rs index d8038d931..8bbd622d2 100644 --- a/elliptic-curve/src/ops.rs +++ b/elliptic-curve/src/ops.rs @@ -9,7 +9,7 @@ use subtle::CtOption; use group::Group; #[cfg(feature = "digest")] -use digest::{BlockInput, Digest, FixedOutput, Reset, Update}; +use digest::{Digest, FixedOutput, Reset, Update}; /// Perform an inversion on a field element (i.e. base field element or scalar) pub trait Invert { @@ -67,7 +67,7 @@ pub trait Reduce: Sized { #[cfg_attr(docsrs, doc(cfg(feature = "digest")))] fn from_be_digest_reduced(digest: D) -> Self where - D: FixedOutput + BlockInput + Clone + Default + Reset + Update, + D: FixedOutput + Digest + Clone + Default + Reset + Update, { Self::from_be_bytes_reduced(digest.finalize()) } @@ -78,7 +78,7 @@ pub trait Reduce: Sized { #[cfg_attr(docsrs, doc(cfg(feature = "digest")))] fn from_le_digest_reduced(digest: D) -> Self where - D: FixedOutput + BlockInput + Clone + Default + Reset + Update, + D: FixedOutput + Digest + Clone + Default + Reset + Update, { Self::from_le_bytes_reduced(digest.finalize()) }