Skip to content

Commit 571e23b

Browse files
authored
ed448-goldilocks: impl GroupDigest for Ed448 and Decaf448 (#1287)
1 parent 6a9d8f0 commit 571e23b

File tree

8 files changed

+143
-148
lines changed

8 files changed

+143
-148
lines changed

ed448-goldilocks/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ It is intended to be portable, fast, and safe.
1717
## Usage
1818

1919
```rust
20-
use ed448_goldilocks::{EdwardsPoint, CompressedEdwardsY, EdwardsScalar, sha3::Shake256};
20+
use ed448_goldilocks::{Ed448, EdwardsPoint, CompressedEdwardsY, EdwardsScalar, sha3::Shake256};
2121
use elliptic_curve::Field;
22-
use hash2curve::ExpandMsgXof;
22+
use hash2curve::{ExpandMsgXof, GroupDigest};
2323
use rand_core::OsRng;
2424

2525
let secret_key = EdwardsScalar::TWO;
@@ -33,12 +33,12 @@ let compressed_public_key = public_key.compress();
3333

3434
assert_eq!(compressed_public_key.to_bytes().len(), 57);
3535

36-
let hashed_scalar = EdwardsScalar::hash::<ExpandMsgXof<Shake256>>(b"test", b"edwards448_XOF:SHAKE256_ELL2_RO_");
36+
let hashed_scalar = Ed448::hash_to_scalar::<ExpandMsgXof<Shake256>>(&[b"test"], &[b"edwards448_XOF:SHAKE256_ELL2_RO_"]).unwrap();
3737
let input = hex_literal::hex!("c8c6c8f584e0c25efdb6af5ad234583c56dedd7c33e0c893468e96740fa0cf7f1a560667da40b7bde340a39252e89262fcf707d1180fd43400");
3838
let expected_scalar = EdwardsScalar::from_canonical_bytes(&input.into()).unwrap();
3939
assert_eq!(hashed_scalar, expected_scalar);
4040

41-
let hashed_point = EdwardsPoint::hash::<ExpandMsgXof<Shake256>>(b"test", b"edwards448_XOF:SHAKE256_ELL2_RO_");
41+
let hashed_point = Ed448::hash_from_bytes::<ExpandMsgXof<Shake256>>(&[b"test"], &[b"edwards448_XOF:SHAKE256_ELL2_RO_"]).unwrap();
4242
let expected = hex_literal::hex!("d15c4427b5c5611a53593c2be611fd3635b90272d331c7e6721ad3735e95dd8b9821f8e4e27501ce01aa3c913114052dce2e91e8ca050f4980");
4343
let expected_point = CompressedEdwardsY(expected).decompress().unwrap();
4444
assert_eq!(hashed_point, expected_point);

ed448-goldilocks/src/curve/edwards/extended.rs

Lines changed: 15 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,12 @@ use crate::field::FieldElement;
1212
use crate::*;
1313
use elliptic_curve::{
1414
CurveGroup, Error,
15-
array::{Array, typenum::Unsigned},
16-
consts::{U28, U84},
15+
array::Array,
1716
group::{Group, GroupEncoding, cofactor::CofactorGroup, prime::PrimeGroup},
1817
ops::LinearCombination,
1918
point::NonIdentity,
2019
};
21-
use hash2curve::{ExpandMsg, ExpandMsgXof, Expander, FromOkm};
20+
use hash2curve::ExpandMsgXof;
2221
use rand_core::TryRngCore;
2322
use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq, CtOption};
2423

@@ -732,72 +731,26 @@ impl EdwardsPoint {
732731

733732
/// Hash a message to a point on the curve
734733
///
735-
/// Hash using the default domain separation tag and hash function
734+
/// Hash using the default domain separation tag and hash function.
735+
/// For more control see [`GroupDigest::hash_from_bytes()`].
736736
pub fn hash_with_defaults(msg: &[u8]) -> Self {
737-
Self::hash::<ExpandMsgXof<sha3::Shake256>>(msg, DEFAULT_HASH_TO_CURVE_SUITE)
738-
}
739-
740-
/// Hash a message to a point on the curve
741-
///
742-
/// Implements hash to curve according
743-
/// see <https://datatracker.ietf.org/doc/rfc9380/>
744-
pub fn hash<X>(msg: &[u8], dst: &[u8]) -> Self
745-
where
746-
X: ExpandMsg<U28>,
747-
{
748-
type RandomLen = U84;
749-
let mut random_bytes = Array::<u8, RandomLen>::default();
750-
let dst = [dst];
751-
let mut expander = X::expand_message(
737+
Ed448::hash_from_bytes::<ExpandMsgXof<sha3::Shake256>>(
752738
&[msg],
753-
&dst,
754-
core::num::NonZero::new(RandomLen::U16 * 2)
755-
.expect("invariant violation: random is non zero length"),
739+
&[DEFAULT_HASH_TO_CURVE_SUITE],
756740
)
757-
.expect("bad dst");
758-
expander.fill_bytes(&mut random_bytes);
759-
let u0 = FieldElement::from_okm(&random_bytes);
760-
expander.fill_bytes(&mut random_bytes);
761-
let u1 = FieldElement::from_okm(&random_bytes);
762-
let mut q0 = u0.map_to_curve_elligator2();
763-
let mut q1 = u1.map_to_curve_elligator2();
764-
q0 = q0.isogeny();
765-
q1 = q1.isogeny();
766-
767-
(q0.to_edwards() + q1.to_edwards()).double().double()
741+
.expect("should never fail with the given `ExpandMsg` and `dst`")
768742
}
769743

770744
/// Encode a message to a point on the curve
771745
///
772-
/// Encode using the default domain separation tag and hash function
746+
/// Encode using the default domain separation tag and hash function.
747+
/// For more control see [`GroupDigest::encode_from_bytes()`].
773748
pub fn encode_with_defaults(msg: &[u8]) -> Self {
774-
Self::encode::<ExpandMsgXof<sha3::Shake256>>(msg, DEFAULT_ENCODE_TO_CURVE_SUITE)
775-
}
776-
777-
/// Encode a message to a point on the curve
778-
///
779-
/// Implements encode to curve according
780-
/// see <https://datatracker.ietf.org/doc/rfc9380/>
781-
pub fn encode<X>(msg: &[u8], dst: &[u8]) -> Self
782-
where
783-
X: ExpandMsg<U28>,
784-
{
785-
type RandomLen = U84;
786-
let mut random_bytes = Array::<u8, RandomLen>::default();
787-
let dst = [dst];
788-
let mut expander = X::expand_message(
749+
Ed448::encode_from_bytes::<ExpandMsgXof<sha3::Shake256>>(
789750
&[msg],
790-
&dst,
791-
core::num::NonZero::new(RandomLen::U16)
792-
.expect("invariant violation: random is non zero length"),
751+
&[DEFAULT_ENCODE_TO_CURVE_SUITE],
793752
)
794-
.expect("bad dst");
795-
expander.fill_bytes(&mut random_bytes);
796-
let u0 = FieldElement::from_okm(&random_bytes);
797-
let mut q0 = u0.map_to_curve_elligator2();
798-
q0 = q0.isogeny();
799-
800-
q0.to_edwards().double().double()
753+
.expect("should never fail with the given `ExpandMsg` and `dst`")
801754
}
802755
}
803756

@@ -1197,7 +1150,7 @@ mod tests {
11971150
];
11981151

11991152
for (msg, x, y) in MSGS {
1200-
let p = EdwardsPoint::hash::<ExpandMsgXof<sha3::Shake256>>(msg, DST);
1153+
let p = Ed448::hash_from_bytes::<ExpandMsgXof<sha3::Shake256>>(&[msg], &[DST]).unwrap();
12011154
assert_eq!(p.is_on_curve().unwrap_u8(), 1u8);
12021155
let p = p.to_affine();
12031156
let mut xx = [0u8; 56];
@@ -1234,7 +1187,8 @@ mod tests {
12341187
];
12351188

12361189
for (msg, x, y) in MSGS {
1237-
let p = EdwardsPoint::encode::<ExpandMsgXof<sha3::Shake256>>(msg, DST);
1190+
let p =
1191+
Ed448::encode_from_bytes::<ExpandMsgXof<sha3::Shake256>>(&[msg], &[DST]).unwrap();
12381192
assert_eq!(p.is_on_curve().unwrap_u8(), 1u8);
12391193
let p = p.to_affine();
12401194
let mut xx = [0u8; 56];

ed448-goldilocks/src/curve/scalar.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ impl FromOkm for EdwardsScalar {
104104
mod test {
105105
use super::*;
106106
use elliptic_curve::array::Array;
107+
use hash2curve::GroupDigest;
107108
use hex_literal::hex;
108109

109110
#[test]
@@ -312,7 +313,8 @@ mod test {
312313
fn scalar_hash() {
313314
let msg = b"hello world";
314315
let dst = b"edwards448_XOF:SHAKE256_ELL2_RO_";
315-
let res = EdwardsScalar::hash::<hash2curve::ExpandMsgXof<sha3::Shake256>>(msg, dst);
316+
let res = Ed448::hash_to_scalar::<hash2curve::ExpandMsgXof<sha3::Shake256>>(&[msg], &[dst])
317+
.unwrap();
316318
let expected: [u8; 57] = hex_literal::hex!(
317319
"2d32a08f09b88275cc5f437e625696b18de718ed94559e17e4d64aafd143a8527705132178b5ce7395ea6214735387398a35913656b4951300"
318320
);

ed448-goldilocks/src/decaf/points.rs

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@ use crate::*;
55

66
use elliptic_curve::{
77
CurveGroup, Error, Group,
8-
array::{Array, typenum::Unsigned},
9-
consts::{U32, U56, U84},
8+
array::Array,
9+
consts::U56,
1010
group::{GroupEncoding, cofactor::CofactorGroup, prime::PrimeGroup},
1111
ops::LinearCombination,
1212
point::NonIdentity,
1313
};
1414

1515
use core::fmt::{Display, Formatter, LowerHex, Result as FmtResult, UpperHex};
16-
use hash2curve::{ExpandMsg, Expander, FromOkm};
1716
use rand_core::{CryptoRng, TryRngCore};
1817
use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq, CtOption};
1918

@@ -348,35 +347,6 @@ impl DecafPoint {
348347
Self::from_uniform_bytes(&uniform_bytes)
349348
}
350349

351-
/// Construct a `DecafPoint` using `ExpandMsg`.
352-
///
353-
/// This function is similar to `hash_to_curve` in the IETF draft
354-
/// where an expand_message function can be chosen and a domain
355-
/// separation tag.
356-
pub fn hash<X>(msg: &[u8], dst: &[u8]) -> Self
357-
where
358-
X: ExpandMsg<U32>,
359-
{
360-
type RandomLen = U84;
361-
let dst = [dst];
362-
let mut random_bytes = Array::<u8, RandomLen>::default();
363-
let mut expander = X::expand_message(
364-
&[msg],
365-
&dst,
366-
core::num::NonZero::new(RandomLen::U16 * 2)
367-
.expect("invariant violation: random is non zero length"),
368-
)
369-
.expect("bad dst");
370-
expander.fill_bytes(&mut random_bytes);
371-
let u0 = FieldElement::from_okm(&random_bytes);
372-
expander.fill_bytes(&mut random_bytes);
373-
let u1 = FieldElement::from_okm(&random_bytes);
374-
375-
let q0 = u0.map_to_curve_decaf448();
376-
let q1 = u1.map_to_curve_decaf448();
377-
Self(q0.add(&q1))
378-
}
379-
380350
/// Construct a `DecafPoint` from 112 bytes of data.
381351
///
382352
/// If the input bytes are uniformly distributed, the resulting
@@ -774,7 +744,11 @@ mod test {
774744
use hash2curve::ExpandMsgXof;
775745

776746
let msg = b"Hello, world!";
777-
let point = DecafPoint::hash::<ExpandMsgXof<sha3::Shake256>>(msg, b"test_hash_to_curve");
747+
let point = Decaf448::hash_from_bytes::<ExpandMsgXof<sha3::Shake256>>(
748+
&[msg],
749+
&[b"test_hash_to_curve"],
750+
)
751+
.unwrap();
778752
assert_eq!(point.0.is_on_curve().unwrap_u8(), 1u8);
779753
assert_ne!(point, DecafPoint::IDENTITY);
780754
assert_ne!(point, DecafPoint::GENERATOR);

ed448-goldilocks/src/decaf/scalar.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ mod test {
8787
use super::*;
8888
use elliptic_curve::PrimeField;
8989
use elliptic_curve::array::Array;
90+
use hash2curve::{ExpandMsgXof, GroupDigest};
9091
use hex_literal::hex;
92+
use sha3::Shake256;
9193

9294
#[test]
9395
fn test_basic_add() {
@@ -283,10 +285,63 @@ mod test {
283285
fn scalar_hash() {
284286
let msg = b"hello world";
285287
let dst = b"decaf448_XOF:SHAKE256_D448MAP_RO_";
286-
let res = DecafScalar::hash::<hash2curve::ExpandMsgXof<sha3::Shake256>>(msg, dst);
288+
let res = Decaf448::hash_to_scalar::<ExpandMsgXof<Shake256>>(&[msg], &[dst]).unwrap();
287289
let expected: [u8; 56] = hex_literal::hex!(
288290
"55e7b59aa035db959409c6b69b817a18c8133d9ad06687665f5720672924da0a84eab7fee415ef13e7aaebdd227291ee8e156f32c507ad2e"
289291
);
290292
assert_eq!(res.to_repr(), Array::from(expected));
291293
}
294+
295+
/// Taken from <https://www.rfc-editor.org/rfc/rfc9497.html#name-decaf448-shake256>.
296+
#[test]
297+
fn hash_to_scalar_voprf() {
298+
struct TestVector {
299+
dst: &'static [u8],
300+
sk_sm: &'static [u8],
301+
}
302+
303+
const KEY_INFO: &[u8] = b"test key";
304+
const SEED: &[u8] =
305+
&hex!("a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3a3");
306+
307+
const TEST_VECTORS: &[TestVector] = &[
308+
TestVector {
309+
dst: b"DeriveKeyPairOPRFV1-\x00-decaf448-SHAKE256",
310+
sk_sm: &hex!(
311+
"e8b1375371fd11ebeb224f832dcc16d371b4188951c438f751425699ed29ecc80c6c13e558ccd67634fd82eac94aa8d1f0d7fee990695d1e"
312+
),
313+
},
314+
TestVector {
315+
dst: b"DeriveKeyPairOPRFV1-\x01-decaf448-SHAKE256",
316+
sk_sm: &hex!(
317+
"e3c01519a076a326a0eb566343e9b21c115fa18e6e85577ddbe890b33104fcc2835ddfb14a928dc3f5d79b936e17c76b99e0bf6a1680930e"
318+
),
319+
},
320+
TestVector {
321+
dst: b"DeriveKeyPairOPRFV1-\x02-decaf448-SHAKE256",
322+
sk_sm: &hex!(
323+
"792a10dcbd3ba4a52a054f6f39186623208695301e7adb9634b74709ab22de402990eb143fd7c67ac66be75e0609705ecea800992aac8e19"
324+
),
325+
},
326+
];
327+
328+
let key_info_len = u16::try_from(KEY_INFO.len()).unwrap().to_be_bytes();
329+
330+
'outer: for test_vector in TEST_VECTORS {
331+
for counter in 0_u8..=u8::MAX {
332+
let scalar = Decaf448::hash_to_scalar::<ExpandMsgXof<Shake256>>(
333+
&[SEED, &key_info_len, KEY_INFO, &counter.to_be_bytes()],
334+
&[test_vector.dst],
335+
)
336+
.unwrap();
337+
338+
if !bool::from(scalar.is_zero()) {
339+
assert_eq!(scalar.to_bytes().as_slice(), test_vector.sk_sm);
340+
continue 'outer;
341+
}
342+
}
343+
344+
panic!("deriving key failed");
345+
}
346+
}
292347
}

0 commit comments

Comments
 (0)