Skip to content

Commit f0186fa

Browse files
committed
Various fixes and improvements to hash2curve
1 parent 6fbdc17 commit f0186fa

File tree

4 files changed

+111
-50
lines changed

4 files changed

+111
-50
lines changed

elliptic-curve/src/hash2curve/hash2field.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@
44
55
mod expand_msg;
66

7+
use core::num::NonZeroUsize;
8+
79
pub use expand_msg::{xmd::*, xof::*, *};
810

911
use crate::{Error, Result};
10-
use hybrid_array::{Array, ArraySize, typenum::Unsigned};
12+
use hybrid_array::{
13+
Array, ArraySize,
14+
typenum::{NonZero, Unsigned},
15+
};
1116

1217
/// The trait for helping to convert to a field element.
1318
pub trait FromOkm {
1419
/// The number of bytes needed to convert to a field element.
15-
type Length: ArraySize;
20+
type Length: ArraySize + NonZero;
1621

1722
/// Convert a byte sequence into a field element.
1823
fn from_okm(data: &Array<u8, Self::Length>) -> Self;
@@ -37,7 +42,10 @@ where
3742
E: ExpandMsg<'a>,
3843
T: FromOkm + Default,
3944
{
40-
let len_in_bytes = T::Length::to_usize().checked_mul(out.len()).ok_or(Error)?;
45+
let len_in_bytes = T::Length::to_usize()
46+
.checked_mul(out.len())
47+
.and_then(NonZeroUsize::new)
48+
.ok_or(Error)?;
4149
let mut tmp = Array::<u8, <T as FromOkm>::Length>::default();
4250
let mut expander = E::expand_message(data, domain, len_in_bytes)?;
4351
for o in out.iter_mut() {

elliptic-curve/src/hash2curve/hash2field/expand_msg.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
pub(super) mod xmd;
44
pub(super) mod xof;
55

6+
use core::num::NonZero;
7+
68
use crate::{Error, Result};
79
use digest::{Digest, ExtendableOutput, Update, XofReader};
810
use hybrid_array::typenum::{IsLess, U256};
@@ -28,7 +30,7 @@ pub trait ExpandMsg<'a> {
2830
fn expand_message(
2931
msgs: &[&[u8]],
3032
dsts: &'a [&'a [u8]],
31-
len_in_bytes: usize,
33+
len_in_bytes: NonZero<usize>,
3234
) -> Result<Self::Expander>;
3335
}
3436

elliptic-curve/src/hash2curve/hash2field/expand_msg/xmd.rs

+36-19
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,71 @@
11
//! `expand_message_xmd` based on a hash function.
22
3-
use core::marker::PhantomData;
3+
use core::{marker::PhantomData, num::NonZero, ops::Mul};
44

55
use super::{Domain, ExpandMsg, Expander};
66
use crate::{Error, Result};
77
use digest::{
88
FixedOutput, HashMarker,
99
array::{
1010
Array,
11-
typenum::{IsLess, IsLessOrEqual, U256, Unsigned},
11+
typenum::{IsGreaterOrEqual, IsLess, IsLessOrEqual, U2, U8, U256, Unsigned},
1212
},
1313
core_api::BlockSizeUser,
1414
};
1515

16-
/// Placeholder type for implementing `expand_message_xmd` based on a hash function
16+
/// Implements `expand_message_xof` via the [`ExpandMsg`] trait:
17+
/// <https://www.rfc-editor.org/rfc/rfc9380.html#name-expand_message_xmd>
18+
///
19+
/// `K` is the target security level in bits:
20+
/// <https://www.rfc-editor.org/rfc/rfc9380.html#section-8.9-2.2>
21+
/// <https://www.rfc-editor.org/rfc/rfc9380.html#name-target-security-levels>
1722
///
1823
/// # Errors
1924
/// - `dst.is_empty()`
20-
/// - `len_in_bytes == 0`
2125
/// - `len_in_bytes > u16::MAX`
2226
/// - `len_in_bytes > 255 * HashT::OutputSize`
2327
#[derive(Debug)]
24-
pub struct ExpandMsgXmd<HashT>(PhantomData<HashT>)
28+
pub struct ExpandMsgXmd<HashT, K>(PhantomData<(HashT, K)>)
2529
where
2630
HashT: BlockSizeUser + Default + FixedOutput + HashMarker,
2731
HashT::OutputSize: IsLess<U256>,
28-
HashT::OutputSize: IsLessOrEqual<HashT::BlockSize>;
32+
HashT::OutputSize: IsLessOrEqual<HashT::BlockSize>,
33+
HashT::OutputSize: Mul<U8>,
34+
U2: Mul<K>,
35+
<HashT::OutputSize as Mul<U8>>::Output: IsGreaterOrEqual<<U2 as Mul<K>>::Output>;
2936

30-
/// ExpandMsgXmd implements expand_message_xmd for the ExpandMsg trait
31-
impl<'a, HashT> ExpandMsg<'a> for ExpandMsgXmd<HashT>
37+
impl<'a, HashT, K> ExpandMsg<'a> for ExpandMsgXmd<HashT, K>
3238
where
3339
HashT: BlockSizeUser + Default + FixedOutput + HashMarker,
34-
// If `len_in_bytes` is bigger then 256, length of the `DST` will depend on
35-
// the output size of the hash, which is still not allowed to be bigger then 256:
40+
// If DST is larger than 255 bytes, the length of the computed DST will depend on the output
41+
// size of the hash, which is still not allowed to be larger than 256:
3642
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html#section-5.4.1-6
3743
HashT::OutputSize: IsLess<U256>,
3844
// Constraint set by `expand_message_xmd`:
3945
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html#section-5.4.1-4
4046
HashT::OutputSize: IsLessOrEqual<HashT::BlockSize>,
47+
// The number of bits output by `HashT` MUST be larger or equal to `2 * K`:
48+
// https://www.rfc-editor.org/rfc/rfc9380.html#section-5.3.1-2.1
49+
HashT::OutputSize: Mul<U8>,
50+
U2: Mul<K>,
51+
<HashT::OutputSize as Mul<U8>>::Output: IsGreaterOrEqual<<U2 as Mul<K>>::Output>,
4152
{
4253
type Expander = ExpanderXmd<'a, HashT>;
4354

4455
fn expand_message(
4556
msgs: &[&[u8]],
4657
dsts: &'a [&'a [u8]],
47-
len_in_bytes: usize,
58+
len_in_bytes: NonZero<usize>,
4859
) -> Result<Self::Expander> {
49-
if len_in_bytes == 0 {
60+
let len_in_bytes_u16 = u16::try_from(len_in_bytes.get()).map_err(|_| Error)?;
61+
62+
// `255 * <b_in_bytes>` can not exceed `u16::MAX`
63+
if len_in_bytes_u16 > 255 * HashT::OutputSize::to_u16() {
5064
return Err(Error);
5165
}
5266

53-
let len_in_bytes_u16 = u16::try_from(len_in_bytes).map_err(|_| Error)?;
54-
5567
let b_in_bytes = HashT::OutputSize::to_usize();
56-
let ell = u8::try_from(len_in_bytes.div_ceil(b_in_bytes)).map_err(|_| Error)?;
68+
let ell = u8::try_from(len_in_bytes.get().div_ceil(b_in_bytes)).map_err(|_| Error)?;
5769

5870
let domain = Domain::xmd::<HashT>(dsts)?;
5971
let mut b_0 = HashT::default();
@@ -157,7 +169,7 @@ mod test {
157169
use hex_literal::hex;
158170
use hybrid_array::{
159171
ArraySize,
160-
typenum::{U32, U128},
172+
typenum::{U8, U32, U128},
161173
};
162174
use sha2::Sha256;
163175

@@ -209,13 +221,18 @@ mod test {
209221
) -> Result<()>
210222
where
211223
HashT: BlockSizeUser + Default + FixedOutput + HashMarker,
212-
HashT::OutputSize: IsLess<U256> + IsLessOrEqual<HashT::BlockSize>,
224+
HashT::OutputSize: IsLess<U256> + IsLessOrEqual<HashT::BlockSize> + Mul<U8>,
225+
U2: Mul<U32>,
226+
<HashT::OutputSize as Mul<U8>>::Output: IsGreaterOrEqual<<U2 as Mul<U32>>::Output>,
213227
{
214228
assert_message::<HashT>(self.msg, domain, L::to_u16(), self.msg_prime);
215229

216230
let dst = [dst];
217-
let mut expander =
218-
ExpandMsgXmd::<HashT>::expand_message(&[self.msg], &dst, L::to_usize())?;
231+
let mut expander = ExpandMsgXmd::<HashT, U32>::expand_message(
232+
&[self.msg],
233+
&dst,
234+
NonZero::new(L::to_usize()).ok_or(Error)?,
235+
)?;
219236

220237
let mut uniform_bytes = Array::<u8, L>::default();
221238
expander.fill_bytes(&mut uniform_bytes);

elliptic-curve/src/hash2curve/hash2field/expand_msg/xof.rs

+61-27
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,45 @@
22
33
use super::{Domain, ExpandMsg, Expander};
44
use crate::{Error, Result};
5-
use core::fmt;
6-
use digest::{ExtendableOutput, Update, XofReader};
7-
use hybrid_array::typenum::U32;
8-
9-
/// Placeholder type for implementing `expand_message_xof` based on an extendable output function
5+
use core::{
6+
fmt,
7+
marker::PhantomData,
8+
num::NonZero,
9+
ops::{Div, Mul},
10+
};
11+
use digest::{ExtendableOutput, HashMarker, Update, XofReader};
12+
use hybrid_array::{
13+
ArraySize,
14+
typenum::{IsLess, U2, U8, U256},
15+
};
16+
17+
/// Implements `expand_message_xof` via the [`ExpandMsg`] trait:
18+
/// <https://www.rfc-editor.org/rfc/rfc9380.html#name-expand_message_xof>
19+
///
20+
/// `K` is the target security level in bits:
21+
/// <https://www.rfc-editor.org/rfc/rfc9380.html#section-8.9-2.2>
22+
/// <https://www.rfc-editor.org/rfc/rfc9380.html#name-target-security-levels>
1023
///
1124
/// # Errors
1225
/// - `dst.is_empty()`
13-
/// - `len_in_bytes == 0`
1426
/// - `len_in_bytes > u16::MAX`
15-
pub struct ExpandMsgXof<HashT>
27+
pub struct ExpandMsgXof<HashT, K>
1628
where
17-
HashT: Default + ExtendableOutput + Update,
29+
HashT: Default + ExtendableOutput + Update + HashMarker,
30+
U2: Mul<K>,
31+
<U2 as Mul<K>>::Output: Div<U8>,
32+
HashSize<K>: ArraySize + IsLess<U256>,
1833
{
1934
reader: <HashT as ExtendableOutput>::Reader,
35+
_k: PhantomData<K>,
2036
}
2137

22-
impl<HashT> fmt::Debug for ExpandMsgXof<HashT>
38+
impl<HashT, K> fmt::Debug for ExpandMsgXof<HashT, K>
2339
where
24-
HashT: Default + ExtendableOutput + Update,
40+
HashT: Default + ExtendableOutput + Update + HashMarker,
41+
U2: Mul<K>,
42+
<U2 as Mul<K>>::Output: Div<U8>,
43+
HashSize<K>: ArraySize + IsLess<U256>,
2544
<HashT as ExtendableOutput>::Reader: fmt::Debug,
2645
{
2746
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -31,25 +50,28 @@ where
3150
}
3251
}
3352

34-
/// ExpandMsgXof implements `expand_message_xof` for the [`ExpandMsg`] trait
35-
impl<'a, HashT> ExpandMsg<'a> for ExpandMsgXof<HashT>
53+
type HashSize<K> = <<U2 as Mul<K>>::Output as Div<U8>>::Output;
54+
55+
impl<'a, HashT, K> ExpandMsg<'a> for ExpandMsgXof<HashT, K>
3656
where
37-
HashT: Default + ExtendableOutput + Update,
57+
HashT: Default + ExtendableOutput + Update + HashMarker,
58+
// If DST is larger than 255 bytes, the length of the computed DST is calculated by
59+
// `2 * k / 8`.
60+
// https://www.rfc-editor.org/rfc/rfc9380.html#section-5.3.1-2.1
61+
U2: Mul<K>,
62+
<U2 as Mul<K>>::Output: Div<U8>,
63+
HashSize<K>: ArraySize + IsLess<U256>,
3864
{
3965
type Expander = Self;
4066

4167
fn expand_message(
4268
msgs: &[&[u8]],
4369
dsts: &'a [&'a [u8]],
44-
len_in_bytes: usize,
70+
len_in_bytes: NonZero<usize>,
4571
) -> Result<Self::Expander> {
46-
if len_in_bytes == 0 {
47-
return Err(Error);
48-
}
49-
50-
let len_in_bytes = u16::try_from(len_in_bytes).map_err(|_| Error)?;
72+
let len_in_bytes = u16::try_from(len_in_bytes.get()).map_err(|_| Error)?;
5173

52-
let domain = Domain::<U32>::xof::<HashT>(dsts)?;
74+
let domain = Domain::<HashSize<K>>::xof::<HashT>(dsts)?;
5375
let mut reader = HashT::default();
5476

5577
for msg in msgs {
@@ -60,13 +82,19 @@ where
6082
domain.update_hash(&mut reader);
6183
reader.update(&[domain.len()]);
6284
let reader = reader.finalize_xof();
63-
Ok(Self { reader })
85+
Ok(Self {
86+
reader,
87+
_k: PhantomData,
88+
})
6489
}
6590
}
6691

67-
impl<HashT> Expander for ExpandMsgXof<HashT>
92+
impl<HashT, K> Expander for ExpandMsgXof<HashT, K>
6893
where
69-
HashT: Default + ExtendableOutput + Update,
94+
HashT: Default + ExtendableOutput + Update + HashMarker,
95+
U2: Mul<K>,
96+
<U2 as Mul<K>>::Output: Div<U8>,
97+
HashSize<K>: ArraySize + IsLess<U256>,
7098
{
7199
fn fill_bytes(&mut self, okm: &mut [u8]) {
72100
self.reader.read(okm);
@@ -78,7 +106,10 @@ mod test {
78106
use super::*;
79107
use core::mem::size_of;
80108
use hex_literal::hex;
81-
use hybrid_array::{Array, ArraySize, typenum::U128};
109+
use hybrid_array::{
110+
Array, ArraySize,
111+
typenum::{U32, U128},
112+
};
82113
use sha3::Shake128;
83114

84115
fn assert_message(msg: &[u8], domain: &Domain<'_, U32>, len_in_bytes: u16, bytes: &[u8]) {
@@ -110,13 +141,16 @@ mod test {
110141
#[allow(clippy::panic_in_result_fn)]
111142
fn assert<HashT, L>(&self, dst: &'static [u8], domain: &Domain<'_, U32>) -> Result<()>
112143
where
113-
HashT: Default + ExtendableOutput + Update,
144+
HashT: Default + ExtendableOutput + Update + HashMarker,
114145
L: ArraySize,
115146
{
116147
assert_message(self.msg, domain, L::to_u16(), self.msg_prime);
117148

118-
let mut expander =
119-
ExpandMsgXof::<HashT>::expand_message(&[self.msg], &[dst], L::to_usize())?;
149+
let mut expander = ExpandMsgXof::<HashT, U128>::expand_message(
150+
&[self.msg],
151+
&[dst],
152+
NonZero::new(L::to_usize()).ok_or(Error)?,
153+
)?;
120154

121155
let mut uniform_bytes = Array::<u8, L>::default();
122156
expander.fill_bytes(&mut uniform_bytes);

0 commit comments

Comments
 (0)