Skip to content

Commit e728ece

Browse files
authored
ExpandMsg improvements (#1862)
## Changes to `ExpandMsg` - Move generic parameter `K` from `ExpandMsg` implementers to `trait ExpandMsg` itself. This was necessary to be able to enforce the correct `K` instead of letting users insert an arbitrary one. E.g. `GroupDigest::hash_from_bytes()` can now `where X: ExpandMsg<Self::K>` instead of users calling `hash_from_bytes::<ExpandMsgXmd<Sha256, U0>>()`. However, this made calling `ExpandMsg` implementers directly more difficult. E.g. instead of `ExpandMsgXmd::<Sha256, U32>::expand_msg(...)` users now have to write `<ExpandMsgXmd<Sha256> as ExpandMsg<U32>>::expand_message()`. If we want to address this, I propose adding `RawExpandMsgXmd`. - Add `CollisionResistance` requirement to `ExpandMsgXof`. - Move the lifetime on `ExpandMsg` to the associated `type Expander<'dst>`. - Fix `dst` not actually being checked to be empty, but instead checked for number of slices. - Move `GroupDigest`s `ProjectivePoint: CofactorGroup` bound to super trait bounds. This makes it less "poisoning" so downstream users don't have to write `where ProjectivePoint: CofactorGroup` every time they use `GroupDigest`. - Move `GroupDigest::hash_to_scalar()`s `Scalar: FromOkm` bounds to `GroupDigest`. I believe this was a historical leftover when `FromOkm` wasn't implemented for `Scalar`s yet. - Improved some documentation around hash2curve and updated all mentions of the draft to RFC9380. - Rename parameter names `msgs` and `dsts` to singular `msg` and `dst`. This is to avoid confusion: even though the type is `&[&[u8]]`, it doesn't represent multiple messages or DSTs, but single concated ones. - Remove non-functioning examples. While I don't think the examples are necessary, I'm happy to re-add them if desired, but I would have to add `GroupDigest` to the `Dev` curve. ## Changes to VOPRF While I was at it, I also adjusted a couple of things around `VoprfParameters` (but I'm happy to split this into its own PR): - Renamed all mentions of VOPRF to OPRF. VOPRF was the old name of the draft when it was just a single "mode", the RFC is split into three modes: OPRF, VOPRF and POPRF. The RFC itself is called ["Oblivious Pseudorandom Functions (OPRFs)"](https://www.rfc-editor.org/rfc/rfc9497.html). - Changed associated `const ID` from `&str` to `&[u8]`. - Changed associated `type Hash` from requiring `Digest` to `Default + FixedOutput + Update`. - Updated all mentions of the VOPRF draft to RFC9497. --- Related: RustCrypto/hashes#694. Companion PR: RustCrypto/elliptic-curves#1203.
1 parent 4de33de commit e728ece

File tree

15 files changed

+139
-159
lines changed

15 files changed

+139
-159
lines changed

.github/workflows/elliptic-curve.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,16 @@ jobs:
4545
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features ecdh
4646
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features hash2curve
4747
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features jwk
48+
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features oprf
4849
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features pem
4950
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features pkcs8
5051
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features sec1
5152
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features serde
52-
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features voprf
5353
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,arithmetic
5454
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,arithmetic,pkcs8
5555
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,serde
5656
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features arithmetic,serde
57-
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,digest,ecdh,hash2curve,jwk,pem,pkcs8,sec1,serde,voprf
57+
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features alloc,digest,ecdh,hash2curve,jwk,oprf,pem,pkcs8,sec1,serde
5858

5959
minimal-versions:
6060
# Temporarily disabled until elliptic-curve 0.13.0-pre.0 is published

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

elliptic-curve/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ hash2curve = ["arithmetic", "digest"]
6666
ecdh = ["arithmetic", "digest", "dep:hkdf"]
6767
group = ["dep:group", "ff"]
6868
jwk = ["dep:base64ct", "dep:serde_json", "alloc", "serde", "zeroize/alloc"]
69+
oprf = ["digest", "hash2curve"]
6970
pkcs8 = ["dep:pkcs8", "sec1"]
7071
pem = ["dep:pem-rfc7468", "alloc", "arithmetic", "pkcs8", "sec1/pem"]
7172
serde = ["dep:serdect", "alloc", "pkcs8", "sec1/serde"]
72-
voprf = ["digest", "hash2curve"]
7373

7474
[package.metadata.docs.rs]
75-
features = ["bits", "ecdh", "hash2curve", "jwk", "pem", "std", "voprf"]
75+
features = ["bits", "ecdh", "hash2curve", "jwk", "oprf", "pem", "std"]

elliptic-curve/src/hash2curve.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Traits for hashing byte sequences to curve points.
22
//!
3-
//! <https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve>
3+
//! <https://www.rfc-editor.org/rfc/rfc9380.html>
44
55
mod group_digest;
66
mod hash2field;
Lines changed: 17 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Traits for handling hash to curve.
22
3-
use super::{ExpandMsg, FromOkm, MapToCurve, hash_to_field};
3+
use super::{ExpandMsg, MapToCurve, hash_to_field};
44
use crate::{ProjectivePoint, Result};
55
use hybrid_array::typenum::Unsigned;
66

@@ -13,7 +13,7 @@ pub trait GroupDigest: MapToCurve {
1313

1414
/// Computes the hash to curve routine.
1515
///
16-
/// From <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html>:
16+
/// From <https://www.rfc-editor.org/rfc/rfc9380.html>:
1717
///
1818
/// > Uniform encoding from byte strings to points in G.
1919
/// > That is, the distribution of its output is statistically close
@@ -22,20 +22,6 @@ pub trait GroupDigest: MapToCurve {
2222
/// > oracle returning points in G assuming a cryptographically secure
2323
/// > hash function is used.
2424
///
25-
/// # Examples
26-
///
27-
/// ## Using a fixed size hash function
28-
///
29-
/// ```ignore
30-
/// let pt = ProjectivePoint::hash_from_bytes::<ExpandMsgXmd<sha2::Sha256>>(b"test data", b"CURVE_XMD:SHA-256_SSWU_RO_");
31-
/// ```
32-
///
33-
/// ## Using an extendable output function
34-
///
35-
/// ```ignore
36-
/// let pt = ProjectivePoint::hash_from_bytes::<ExpandMsgXof<sha3::Shake256>>(b"test data", b"CURVE_XOF:SHAKE-256_SSWU_RO_");
37-
/// ```
38-
///
3925
/// # Errors
4026
/// See implementors of [`ExpandMsg`] for errors:
4127
/// - [`ExpandMsgXmd`]
@@ -45,20 +31,20 @@ pub trait GroupDigest: MapToCurve {
4531
///
4632
/// [`ExpandMsgXmd`]: crate::hash2curve::ExpandMsgXmd
4733
/// [`ExpandMsgXof`]: crate::hash2curve::ExpandMsgXof
48-
fn hash_from_bytes<'a, X: ExpandMsg<'a>>(
49-
msgs: &[&[u8]],
50-
dsts: &'a [&'a [u8]],
51-
) -> Result<ProjectivePoint<Self>> {
34+
fn hash_from_bytes<X>(msg: &[&[u8]], dst: &[&[u8]]) -> Result<ProjectivePoint<Self>>
35+
where
36+
X: ExpandMsg<Self::K>,
37+
{
5238
let mut u = [Self::FieldElement::default(), Self::FieldElement::default()];
53-
hash_to_field::<X, _>(msgs, dsts, &mut u)?;
39+
hash_to_field::<X, _, _>(msg, dst, &mut u)?;
5440
let q0 = Self::map_to_curve(u[0]);
5541
let q1 = Self::map_to_curve(u[1]);
5642
Ok(Self::add_and_map_to_subgroup(q0, q1))
5743
}
5844

5945
/// Computes the encode to curve routine.
6046
///
61-
/// From <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html>:
47+
/// From <https://www.rfc-editor.org/rfc/rfc9380.html>:
6248
///
6349
/// > Nonuniform encoding from byte strings to
6450
/// > points in G. That is, the distribution of its output is not
@@ -75,18 +61,18 @@ pub trait GroupDigest: MapToCurve {
7561
///
7662
/// [`ExpandMsgXmd`]: crate::hash2curve::ExpandMsgXmd
7763
/// [`ExpandMsgXof`]: crate::hash2curve::ExpandMsgXof
78-
fn encode_from_bytes<'a, X: ExpandMsg<'a>>(
79-
msgs: &[&[u8]],
80-
dsts: &'a [&'a [u8]],
81-
) -> Result<ProjectivePoint<Self>> {
64+
fn encode_from_bytes<X>(msg: &[&[u8]], dst: &[&[u8]]) -> Result<ProjectivePoint<Self>>
65+
where
66+
X: ExpandMsg<Self::K>,
67+
{
8268
let mut u = [Self::FieldElement::default()];
83-
hash_to_field::<X, _>(msgs, dsts, &mut u)?;
69+
hash_to_field::<X, _, _>(msg, dst, &mut u)?;
8470
let q0 = Self::map_to_curve(u[0]);
8571
Ok(Self::map_to_subgroup(q0))
8672
}
8773

8874
/// Computes the hash to field routine according to
89-
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-13.html#section-5>
75+
/// <https://www.rfc-editor.org/rfc/rfc9380.html#section-5-4>
9076
/// and returns a scalar.
9177
///
9278
/// # Errors
@@ -98,15 +84,12 @@ pub trait GroupDigest: MapToCurve {
9884
///
9985
/// [`ExpandMsgXmd`]: crate::hash2curve::ExpandMsgXmd
10086
/// [`ExpandMsgXof`]: crate::hash2curve::ExpandMsgXof
101-
fn hash_to_scalar<'a, X: ExpandMsg<'a>>(
102-
msgs: &[&[u8]],
103-
dsts: &'a [&'a [u8]],
104-
) -> Result<Self::Scalar>
87+
fn hash_to_scalar<X>(msg: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Scalar>
10588
where
106-
Self::Scalar: FromOkm,
89+
X: ExpandMsg<Self::K>,
10790
{
10891
let mut u = [Self::Scalar::default()];
109-
hash_to_field::<X, _>(msgs, dsts, &mut u)?;
92+
hash_to_field::<X, _, _>(msg, dst, &mut u)?;
11093
Ok(u[0])
11194
}
11295
}

elliptic-curve/src/hash2curve/hash2field.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Traits for hashing to field elements.
22
//!
3-
//! <https://datatracker.ietf.org/doc/draft-irtf-cfrg-hash-to-curve>
3+
//! <https://www.rfc-editor.org/rfc/rfc9380.html>
44
55
mod expand_msg;
66

@@ -25,7 +25,7 @@ pub trait FromOkm {
2525

2626
/// Convert an arbitrary byte sequence into a field element.
2727
///
28-
/// <https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-11#section-5.3>
28+
/// <https://www.rfc-editor.org/rfc/rfc9380.html#name-hash_to_field-implementatio>
2929
///
3030
/// # Errors
3131
/// See implementors of [`ExpandMsg`] for errors:
@@ -37,9 +37,9 @@ pub trait FromOkm {
3737
/// [`ExpandMsgXmd`]: crate::hash2field::ExpandMsgXmd
3838
/// [`ExpandMsgXof`]: crate::hash2field::ExpandMsgXof
3939
#[doc(hidden)]
40-
pub fn hash_to_field<'a, E, T>(data: &[&[u8]], domain: &'a [&'a [u8]], out: &mut [T]) -> Result<()>
40+
pub fn hash_to_field<E, K, T>(data: &[&[u8]], domain: &[&[u8]], out: &mut [T]) -> Result<()>
4141
where
42-
E: ExpandMsg<'a>,
42+
E: ExpandMsg<K>,
4343
T: FromOkm + Default,
4444
{
4545
let len_in_bytes = T::Length::to_usize()

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

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,25 @@ const MAX_DST_LEN: usize = 255;
1717

1818
/// Trait for types implementing expand_message interface for `hash_to_field`.
1919
///
20+
/// `K` is the target security level in bytes:
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>
23+
///
2024
/// # Errors
2125
/// See implementors of [`ExpandMsg`] for errors.
22-
pub trait ExpandMsg<'a> {
26+
pub trait ExpandMsg<K> {
2327
/// Type holding data for the [`Expander`].
24-
type Expander: Expander + Sized;
28+
type Expander<'dst>: Expander + Sized;
2529

2630
/// Expands `msg` to the required number of bytes.
2731
///
2832
/// Returns an expander that can be used to call `read` until enough
2933
/// bytes have been consumed
30-
fn expand_message(
31-
msgs: &[&[u8]],
32-
dsts: &'a [&'a [u8]],
34+
fn expand_message<'dst>(
35+
msg: &[&[u8]],
36+
dst: &'dst [&[u8]],
3337
len_in_bytes: NonZero<usize>,
34-
) -> Result<Self::Expander>;
38+
) -> Result<Self::Expander<'dst>>;
3539
}
3640

3741
/// Expander that, call `read` until enough bytes have been consumed.
@@ -42,9 +46,9 @@ pub trait Expander {
4246

4347
/// The domain separation tag
4448
///
45-
/// Implements [section 5.4.3 of `draft-irtf-cfrg-hash-to-curve-13`][dst].
49+
/// Implements [section 5.3.3 of RFC9380][dst].
4650
///
47-
/// [dst]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-13#section-5.4.3
51+
/// [dst]: https://www.rfc-editor.org/rfc/rfc9380.html#name-using-dsts-longer-than-255-
4852
#[derive(Debug)]
4953
pub(crate) enum Domain<'a, L>
5054
where
@@ -60,48 +64,50 @@ impl<'a, L> Domain<'a, L>
6064
where
6165
L: ArraySize + IsLess<U256, Output = True>,
6266
{
63-
pub fn xof<X>(dsts: &'a [&'a [u8]]) -> Result<Self>
67+
pub fn xof<X>(dst: &'a [&'a [u8]]) -> Result<Self>
6468
where
6569
X: Default + ExtendableOutput + Update,
6670
{
67-
if dsts.is_empty() {
71+
// https://www.rfc-editor.org/rfc/rfc9380.html#section-3.1-4.2
72+
if dst.iter().map(|slice| slice.len()).sum::<usize>() == 0 {
6873
Err(Error)
69-
} else if dsts.iter().map(|dst| dst.len()).sum::<usize>() > MAX_DST_LEN {
74+
} else if dst.iter().map(|slice| slice.len()).sum::<usize>() > MAX_DST_LEN {
7075
let mut data = Array::<u8, L>::default();
7176
let mut hash = X::default();
7277
hash.update(OVERSIZE_DST_SALT);
7378

74-
for dst in dsts {
75-
hash.update(dst);
79+
for slice in dst {
80+
hash.update(slice);
7681
}
7782

7883
hash.finalize_xof().read(&mut data);
7984

8085
Ok(Self::Hashed(data))
8186
} else {
82-
Ok(Self::Array(dsts))
87+
Ok(Self::Array(dst))
8388
}
8489
}
8590

86-
pub fn xmd<X>(dsts: &'a [&'a [u8]]) -> Result<Self>
91+
pub fn xmd<X>(dst: &'a [&'a [u8]]) -> Result<Self>
8792
where
8893
X: Digest<OutputSize = L>,
8994
{
90-
if dsts.is_empty() {
95+
// https://www.rfc-editor.org/rfc/rfc9380.html#section-3.1-4.2
96+
if dst.iter().map(|slice| slice.len()).sum::<usize>() == 0 {
9197
Err(Error)
92-
} else if dsts.iter().map(|dst| dst.len()).sum::<usize>() > MAX_DST_LEN {
98+
} else if dst.iter().map(|slice| slice.len()).sum::<usize>() > MAX_DST_LEN {
9399
Ok(Self::Hashed({
94100
let mut hash = X::new();
95101
hash.update(OVERSIZE_DST_SALT);
96102

97-
for dst in dsts {
98-
hash.update(dst);
103+
for slice in dst {
104+
hash.update(slice);
99105
}
100106

101107
hash.finalize()
102108
}))
103109
} else {
104-
Ok(Self::Array(dsts))
110+
Ok(Self::Array(dst))
105111
}
106112
}
107113

0 commit comments

Comments
 (0)