Skip to content

Commit 5d3e031

Browse files
authored
Various fixes and improvements to hash2curve (#1813)
This was mainly to fix a bug in handling large DSTs for `expand_message_xof`. - `ExpandMsg::expand_message()`s `len_in_bytes` parameter is now a `NonZero`, moving one run-time error to the type system. - In tandem `FromOkm::Length` now requires `typenum::NonZero`. - Added generic `K` parameter to `ExpandMsg` implementers. - `ExpandMsgXmd` only uses it to follow constraints set by the specification more closely. See https://www.rfc-editor.org/rfc/rfc9380.html#section-5.3.1-2.1. - More importantly, `ExpandMsgXof`, requires `K` to calculate the size for the computed DST if the given DST is larger than 255 bytes. This was previously not implemented correctly in that it always used a 32-byte long computed DST. - Added `type K` to the `GroupDigest` trait. This allows blanket implementations to use the right `K` for `ExpandMsgXmd` and `ExpandMsgXof`. - Added `HashMarker` to the constraints of `HashT` for `ExpandMsgXof`. - Moved documentation from `ExpandMsg` trait implementations to the actual type and added links to the specification. Cc @mikelodder7.
1 parent ef4b78d commit 5d3e031

File tree

5 files changed

+102
-50
lines changed

5 files changed

+102
-50
lines changed

elliptic-curve/src/hash2curve/group_digest.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use super::{ExpandMsg, FromOkm, MapToCurve, hash_to_field};
44
use crate::{CurveArithmetic, ProjectivePoint, Result};
55
use group::cofactor::CofactorGroup;
6+
use hybrid_array::typenum::Unsigned;
67

78
/// Adds hashing arbitrary byte sequences to a valid group element
89
pub trait GroupDigest: CurveArithmetic
@@ -12,6 +13,11 @@ where
1213
/// The field element representation for a group value with multiple elements
1314
type FieldElement: FromOkm + MapToCurve<Output = ProjectivePoint<Self>> + Default + Copy;
1415

16+
/// The target security level in bytes:
17+
/// <https://www.rfc-editor.org/rfc/rfc9380.html#section-8.9-2.2>
18+
/// <https://www.rfc-editor.org/rfc/rfc9380.html#name-target-security-levels>
19+
type K: Unsigned;
20+
1521
/// Computes the hash to curve routine.
1622
///
1723
/// From <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html>:

elliptic-curve/src/hash2curve/hash2field.rs

Lines changed: 11 additions & 3 deletions
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

Lines changed: 3 additions & 1 deletion
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

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,69 @@
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, 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 bytes:
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+
K: Mul<U2>,
34+
HashT::OutputSize: IsGreaterOrEqual<<K as Mul<U2>>::Output>;
2935

30-
/// ExpandMsgXmd implements expand_message_xmd for the ExpandMsg trait
31-
impl<'a, HashT> ExpandMsg<'a> for ExpandMsgXmd<HashT>
36+
impl<'a, HashT, K> ExpandMsg<'a> for ExpandMsgXmd<HashT, K>
3237
where
3338
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:
39+
// If DST is larger than 255 bytes, the length of the computed DST will depend on the output
40+
// size of the hash, which is still not allowed to be larger than 256:
3641
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html#section-5.4.1-6
3742
HashT::OutputSize: IsLess<U256>,
3843
// Constraint set by `expand_message_xmd`:
3944
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html#section-5.4.1-4
4045
HashT::OutputSize: IsLessOrEqual<HashT::BlockSize>,
46+
// The number of bits output by `HashT` MUST be larger or equal to `K * 2`:
47+
// https://www.rfc-editor.org/rfc/rfc9380.html#section-5.3.1-2.1
48+
K: Mul<U2>,
49+
HashT::OutputSize: IsGreaterOrEqual<<K as Mul<U2>>::Output>,
4150
{
4251
type Expander = ExpanderXmd<'a, HashT>;
4352

4453
fn expand_message(
4554
msgs: &[&[u8]],
4655
dsts: &'a [&'a [u8]],
47-
len_in_bytes: usize,
56+
len_in_bytes: NonZero<usize>,
4857
) -> Result<Self::Expander> {
49-
if len_in_bytes == 0 {
58+
let len_in_bytes_u16 = u16::try_from(len_in_bytes.get()).map_err(|_| Error)?;
59+
60+
// `255 * <b_in_bytes>` can not exceed `u16::MAX`
61+
if len_in_bytes_u16 > 255 * HashT::OutputSize::to_u16() {
5062
return Err(Error);
5163
}
5264

53-
let len_in_bytes_u16 = u16::try_from(len_in_bytes).map_err(|_| Error)?;
54-
5565
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)?;
66+
let ell = u8::try_from(len_in_bytes.get().div_ceil(b_in_bytes)).map_err(|_| Error)?;
5767

5868
let domain = Domain::xmd::<HashT>(dsts)?;
5969
let mut b_0 = HashT::default();
@@ -157,7 +167,7 @@ mod test {
157167
use hex_literal::hex;
158168
use hybrid_array::{
159169
ArraySize,
160-
typenum::{U32, U128},
170+
typenum::{U4, U8, U32, U128},
161171
};
162172
use sha2::Sha256;
163173

@@ -209,13 +219,17 @@ mod test {
209219
) -> Result<()>
210220
where
211221
HashT: BlockSizeUser + Default + FixedOutput + HashMarker,
212-
HashT::OutputSize: IsLess<U256> + IsLessOrEqual<HashT::BlockSize>,
222+
HashT::OutputSize: IsLess<U256> + IsLessOrEqual<HashT::BlockSize> + Mul<U8>,
223+
HashT::OutputSize: IsGreaterOrEqual<<U4 as Mul<U2>>::Output>,
213224
{
214225
assert_message::<HashT>(self.msg, domain, L::to_u16(), self.msg_prime);
215226

216227
let dst = [dst];
217-
let mut expander =
218-
ExpandMsgXmd::<HashT>::expand_message(&[self.msg], &dst, L::to_usize())?;
228+
let mut expander = ExpandMsgXmd::<HashT, U4>::expand_message(
229+
&[self.msg],
230+
&dst,
231+
NonZero::new(L::to_usize()).ok_or(Error)?,
232+
)?;
219233

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

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

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,38 @@
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::{fmt, marker::PhantomData, num::NonZero, ops::Mul};
6+
use digest::{ExtendableOutput, HashMarker, Update, XofReader};
7+
use hybrid_array::{
8+
ArraySize,
9+
typenum::{IsLess, U2, U256},
10+
};
11+
12+
/// Implements `expand_message_xof` via the [`ExpandMsg`] trait:
13+
/// <https://www.rfc-editor.org/rfc/rfc9380.html#name-expand_message_xof>
14+
///
15+
/// `K` is the target security level in bytes:
16+
/// <https://www.rfc-editor.org/rfc/rfc9380.html#section-8.9-2.2>
17+
/// <https://www.rfc-editor.org/rfc/rfc9380.html#name-target-security-levels>
1018
///
1119
/// # Errors
1220
/// - `dst.is_empty()`
13-
/// - `len_in_bytes == 0`
1421
/// - `len_in_bytes > u16::MAX`
15-
pub struct ExpandMsgXof<HashT>
22+
pub struct ExpandMsgXof<HashT, K>
1623
where
17-
HashT: Default + ExtendableOutput + Update,
24+
HashT: Default + ExtendableOutput + Update + HashMarker,
25+
K: Mul<U2>,
26+
<K as Mul<U2>>::Output: ArraySize + IsLess<U256>,
1827
{
1928
reader: <HashT as ExtendableOutput>::Reader,
29+
_k: PhantomData<K>,
2030
}
2131

22-
impl<HashT> fmt::Debug for ExpandMsgXof<HashT>
32+
impl<HashT, K> fmt::Debug for ExpandMsgXof<HashT, K>
2333
where
24-
HashT: Default + ExtendableOutput + Update,
34+
HashT: Default + ExtendableOutput + Update + HashMarker,
35+
K: Mul<U2>,
36+
<K as Mul<U2>>::Output: ArraySize + IsLess<U256>,
2537
<HashT as ExtendableOutput>::Reader: fmt::Debug,
2638
{
2739
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -31,25 +43,24 @@ where
3143
}
3244
}
3345

34-
/// ExpandMsgXof implements `expand_message_xof` for the [`ExpandMsg`] trait
35-
impl<'a, HashT> ExpandMsg<'a> for ExpandMsgXof<HashT>
46+
impl<'a, HashT, K> ExpandMsg<'a> for ExpandMsgXof<HashT, K>
3647
where
37-
HashT: Default + ExtendableOutput + Update,
48+
HashT: Default + ExtendableOutput + Update + HashMarker,
49+
// If DST is larger than 255 bytes, the length of the computed DST is calculated by `K * 2`.
50+
// https://www.rfc-editor.org/rfc/rfc9380.html#section-5.3.1-2.1
51+
K: Mul<U2>,
52+
<K as Mul<U2>>::Output: ArraySize + IsLess<U256>,
3853
{
3954
type Expander = Self;
4055

4156
fn expand_message(
4257
msgs: &[&[u8]],
4358
dsts: &'a [&'a [u8]],
44-
len_in_bytes: usize,
59+
len_in_bytes: NonZero<usize>,
4560
) -> 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)?;
61+
let len_in_bytes = u16::try_from(len_in_bytes.get()).map_err(|_| Error)?;
5162

52-
let domain = Domain::<U32>::xof::<HashT>(dsts)?;
63+
let domain = Domain::<<K as Mul<U2>>::Output>::xof::<HashT>(dsts)?;
5364
let mut reader = HashT::default();
5465

5566
for msg in msgs {
@@ -60,13 +71,18 @@ where
6071
domain.update_hash(&mut reader);
6172
reader.update(&[domain.len()]);
6273
let reader = reader.finalize_xof();
63-
Ok(Self { reader })
74+
Ok(Self {
75+
reader,
76+
_k: PhantomData,
77+
})
6478
}
6579
}
6680

67-
impl<HashT> Expander for ExpandMsgXof<HashT>
81+
impl<HashT, K> Expander for ExpandMsgXof<HashT, K>
6882
where
69-
HashT: Default + ExtendableOutput + Update,
83+
HashT: Default + ExtendableOutput + Update + HashMarker,
84+
K: Mul<U2>,
85+
<K as Mul<U2>>::Output: ArraySize + IsLess<U256>,
7086
{
7187
fn fill_bytes(&mut self, okm: &mut [u8]) {
7288
self.reader.read(okm);
@@ -78,7 +94,10 @@ mod test {
7894
use super::*;
7995
use core::mem::size_of;
8096
use hex_literal::hex;
81-
use hybrid_array::{Array, ArraySize, typenum::U128};
97+
use hybrid_array::{
98+
Array, ArraySize,
99+
typenum::{U16, U32, U128},
100+
};
82101
use sha3::Shake128;
83102

84103
fn assert_message(msg: &[u8], domain: &Domain<'_, U32>, len_in_bytes: u16, bytes: &[u8]) {
@@ -110,13 +129,16 @@ mod test {
110129
#[allow(clippy::panic_in_result_fn)]
111130
fn assert<HashT, L>(&self, dst: &'static [u8], domain: &Domain<'_, U32>) -> Result<()>
112131
where
113-
HashT: Default + ExtendableOutput + Update,
132+
HashT: Default + ExtendableOutput + Update + HashMarker,
114133
L: ArraySize,
115134
{
116135
assert_message(self.msg, domain, L::to_u16(), self.msg_prime);
117136

118-
let mut expander =
119-
ExpandMsgXof::<HashT>::expand_message(&[self.msg], &[dst], L::to_usize())?;
137+
let mut expander = ExpandMsgXof::<HashT, U16>::expand_message(
138+
&[self.msg],
139+
&[dst],
140+
NonZero::new(L::to_usize()).ok_or(Error)?,
141+
)?;
120142

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

0 commit comments

Comments
 (0)