Skip to content

Commit 13b7eaa

Browse files
committed
Implement hash2curve for Curve448
1 parent 2da32ef commit 13b7eaa

File tree

6 files changed

+146
-13
lines changed

6 files changed

+146
-13
lines changed

ed448-goldilocks/src/field/element.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
33

44
use super::ConstMontyType;
55
use crate::curve::twedwards::extended::ExtendedPoint as TwistedExtendedPoint;
6-
use crate::{AffinePoint, Decaf448, DecafPoint, Ed448, EdwardsPoint, ORDER};
6+
use crate::{
7+
AffinePoint, Curve448, Decaf448, DecafPoint, Ed448, EdwardsPoint, ORDER,
8+
ProjectiveMontgomeryPoint,
9+
};
710
use elliptic_curve::{
811
array::Array,
912
bigint::{
@@ -198,7 +201,8 @@ impl MapToCurve for Ed448 {
198201
type FieldElement = FieldElementU84;
199202

200203
fn map_to_curve(element: FieldElementU84) -> Self::CurvePoint {
201-
element.0.map_to_curve_elligator2().isogeny().to_edwards()
204+
let (x, y) = element.0.map_to_curve_elligator2();
205+
AffinePoint { x, y }.isogeny().to_edwards()
202206
}
203207

204208
fn map_to_subgroup(point: EdwardsPoint) -> EdwardsPoint {
@@ -226,6 +230,27 @@ impl MapToCurve for Decaf448 {
226230
}
227231
}
228232

233+
impl MapToCurve for Curve448 {
234+
type CurvePoint = ProjectiveMontgomeryPoint;
235+
type FieldElement = FieldElementU84;
236+
237+
fn map_to_curve(element: FieldElementU84) -> Self::CurvePoint {
238+
let (x, y) = element.0.map_to_curve_elligator2();
239+
ProjectiveMontgomeryPoint::new(x, y, FieldElement::ONE)
240+
}
241+
242+
fn map_to_subgroup(point: ProjectiveMontgomeryPoint) -> ProjectiveMontgomeryPoint {
243+
point.clear_cofactor()
244+
}
245+
246+
fn add_and_map_to_subgroup(
247+
lhs: ProjectiveMontgomeryPoint,
248+
rhs: ProjectiveMontgomeryPoint,
249+
) -> ProjectiveMontgomeryPoint {
250+
(lhs + rhs).clear_cofactor()
251+
}
252+
}
253+
229254
impl FieldElement {
230255
pub const A_PLUS_TWO_OVER_FOUR: Self = Self(ConstMontyType::new(&U448::from_be_hex(
231256
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000098aa",
@@ -384,7 +409,8 @@ impl FieldElement {
384409
(inv_sqrt_x * u, zero_u | is_res)
385410
}
386411

387-
pub(crate) fn map_to_curve_elligator2(&self) -> AffinePoint {
412+
// See https://www.rfc-editor.org/rfc/rfc9380.html#name-curve448-q-3-mod-4-k-1.
413+
pub(crate) fn map_to_curve_elligator2(&self) -> (FieldElement, FieldElement) {
388414
let mut t1 = self.square(); // 1. t1 = u^2
389415
t1 *= Self::Z; // 2. t1 = Z * t1 // Z * u^2
390416
let e1 = t1.ct_eq(&Self::MINUS_ONE); // 3. e1 = t1 == -1 // exceptional case: Z * u^2 == -1
@@ -404,7 +430,7 @@ impl FieldElement {
404430
let mut y = y2.sqrt(); // 17. y = sqrt(y2)
405431
let e3 = y.is_negative(); // 18. e3 = sgn0(y) == 1
406432
y.conditional_negate(e2 ^ e3); // y = CMOV(-y, y, e2 xor e3)
407-
AffinePoint { x, y }
433+
(x, y)
408434
}
409435

410436
// See https://www.shiftleft.org/papers/decaf/decaf.pdf#section.A.3.

ed448-goldilocks/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,7 @@ impl CurveArithmetic for Curve448 {
219219
type ProjectivePoint = ProjectiveMontgomeryPoint;
220220
type Scalar = MontgomeryScalar;
221221
}
222+
223+
impl GroupDigest for Curve448 {
224+
type K = U28;
225+
}

ed448-goldilocks/src/montgomery.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,8 @@ mod x;
1818
pub use point::{MontgomeryPoint, ProjectiveMontgomeryPoint};
1919
pub use scalar::{MontgomeryScalar, MontgomeryScalarBytes, WideMontgomeryScalarBytes};
2020
pub use x::{MontgomeryXpoint, ProjectiveMontgomeryXpoint};
21+
22+
/// The default hash to curve domain separation tag
23+
const DEFAULT_HASH_TO_CURVE_SUITE: &[u8] = b"curve448_XOF:SHAKE256_ELL2_RO_";
24+
/// The default encode to curve domain separation tag
25+
const DEFAULT_ENCODE_TO_CURVE_SUITE: &[u8] = b"curve448_XOF:SHAKE256_ELL2_NU_";

ed448-goldilocks/src/montgomery/ops.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -322,17 +322,15 @@ where
322322

323323
#[cfg(test)]
324324
mod test {
325-
use elliptic_curve::Field;
325+
use elliptic_curve::Group;
326326
use rand_core::OsRng;
327327

328328
use super::*;
329329

330330
#[test]
331331
fn mixed_addition() {
332-
let p1 = MontgomeryScalar::try_from_rng(&mut OsRng).unwrap()
333-
* ProjectiveMontgomeryPoint::GENERATOR;
334-
let p2 = MontgomeryScalar::try_from_rng(&mut OsRng).unwrap()
335-
* ProjectiveMontgomeryPoint::GENERATOR;
332+
let p1 = ProjectiveMontgomeryPoint::try_from_rng(&mut OsRng).unwrap();
333+
let p2 = ProjectiveMontgomeryPoint::try_from_rng(&mut OsRng).unwrap();
336334
let p3 = p1 + p2;
337335

338336
assert_eq!(p3.to_affine(), (p1.to_affine() + p2).to_affine());

ed448-goldilocks/src/montgomery/point.rs

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,18 @@ use elliptic_curve::{
1010
point::{AffineCoordinates, NonIdentity},
1111
zeroize::DefaultIsZeroes,
1212
};
13+
use hash2curve::{ExpandMsgXof, GroupDigest};
1314
use rand_core::TryRngCore;
15+
use sha3::Shake256;
1416
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
1517

16-
use super::{MontgomeryScalar, MontgomeryXpoint, ProjectiveMontgomeryXpoint};
18+
use super::{
19+
DEFAULT_ENCODE_TO_CURVE_SUITE, DEFAULT_HASH_TO_CURVE_SUITE, MontgomeryScalar, MontgomeryXpoint,
20+
ProjectiveMontgomeryXpoint,
21+
};
1722
use crate::constants::MONTGOMERY_BASEPOINT_ORDER;
1823
use crate::field::{ConstMontyType, FieldElement};
19-
use crate::{AffinePoint, Curve448FieldBytes};
24+
use crate::{AffinePoint, Curve448, Curve448FieldBytes};
2025

2126
/// A point in Montgomery form including the y-coordinate.
2227
#[derive(Copy, Clone, Debug, Default)]
@@ -182,6 +187,27 @@ impl ProjectiveMontgomeryPoint {
182187
pub fn to_affine_x(&self) -> MontgomeryXpoint {
183188
self.to_projective_x().to_affine()
184189
}
190+
191+
/// Hash a message to a point on the curve
192+
///
193+
/// Hash using the default domain separation tag and hash function.
194+
/// For more control see [`GroupDigest::hash_from_bytes()`].
195+
pub fn hash_with_defaults(msg: &[u8]) -> Self {
196+
Curve448::hash_from_bytes::<ExpandMsgXof<Shake256>>(&[msg], &[DEFAULT_HASH_TO_CURVE_SUITE])
197+
.expect("should never fail with the given `ExpandMsg` and `dst`")
198+
}
199+
200+
/// Encode a message to a point on the curve
201+
///
202+
/// Encode using the default domain separation tag and hash function.
203+
/// For more control see [`GroupDigest::encode_from_bytes()`].
204+
pub fn encode_with_defaults(msg: &[u8]) -> Self {
205+
Curve448::encode_from_bytes::<ExpandMsgXof<Shake256>>(
206+
&[msg],
207+
&[DEFAULT_ENCODE_TO_CURVE_SUITE],
208+
)
209+
.expect("should never fail with the given `ExpandMsg` and `dst`")
210+
}
185211
}
186212

187213
impl ConditionallySelectable for ProjectiveMontgomeryPoint {
@@ -243,11 +269,13 @@ impl CofactorGroup for ProjectiveMontgomeryPoint {
243269
impl Group for ProjectiveMontgomeryPoint {
244270
type Scalar = MontgomeryScalar;
245271

246-
fn try_from_rng<R>(_rng: &mut R) -> Result<Self, R::Error>
272+
fn try_from_rng<R>(rng: &mut R) -> Result<Self, R::Error>
247273
where
248274
R: TryRngCore + ?Sized,
249275
{
250-
todo!()
276+
let mut uniform_bytes = [0u8; 112];
277+
rng.try_fill_bytes(&mut uniform_bytes)?;
278+
Ok(Self::hash_with_defaults(&uniform_bytes))
251279
}
252280

253281
fn identity() -> Self {
@@ -397,6 +425,7 @@ impl TryFrom<ProjectiveMontgomeryPoint> for NonIdentity<ProjectiveMontgomeryPoin
397425
mod tests {
398426
use super::*;
399427
use crate::EdwardsPoint;
428+
use hex_literal::hex;
400429

401430
#[test]
402431
fn to_edwards() {
@@ -441,4 +470,56 @@ mod tests {
441470

442471
assert_eq!(identity, x_identity);
443472
}
473+
474+
#[test]
475+
fn hash_with_test_vectors() {
476+
const DST: &[u8] = b"QUUX-V01-CS02-with-curve448_XOF:SHAKE256_ELL2_RO_";
477+
const MSGS: &[(&[u8], [u8; 56], [u8; 56])] = &[
478+
(b"", hex!("5ea5ff623d27c75e73717514134e73e419f831a875ca9e82915fdfc7069d0a9f8b532cfb32b1d8dd04ddeedbe3fa1d0d681c01e825d6a9ea"), hex!("afadd8de789f8f8e3516efbbe313a7eba364c939ecba00dabf4ced5c563b18e70a284c17d8f46b564c4e6ce11784a3825d941116622128c1")),
479+
(b"abc", hex!("9b2f7ce34878d7cebf34c582db14958308ea09366d1ec71f646411d3de0ae564d082b06f40cd30dfc08d9fb7cb21df390cf207806ad9d0e4"), hex!("138a0eef0a4993ea696152ed7db61f7ddb4e8100573591e7466d61c0c568ecaec939e36a84d276f34c402526d8989a96e99760c4869ed633")),
480+
(b"abcdef0123456789", hex!("f54ecd14b85a50eeeee0618452df3a75be7bfba11da5118774ae4ea55ac204e153f77285d780c4acee6c96abe3577a0c0b00be6e790cf194"), hex!("935247a64bf78c107069943c7e3ecc52acb27ce4a3230407c8357341685ea2152e8c3da93f8cd77da1bddb5bb759c6e7ae7d516dced42850")),
481+
(b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", hex!("5bd67c4f88adf6beb10f7e0d0054659776a55c97b809ec8b3101729e104fd0f684e103792f267fd87cc4afc25a073956ef4f268fb02824d5"), hex!("da1f5cb16a352719e4cb064cf47ba72aeba7752d03e8ca2c56229f419b4ef378785a5af1a53dd7ab4d467c1f92f7b139b3752faf29c96432")),
482+
(b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", hex!("ea441c10b3636ecedd5c0dfcae96384cc40de8390a0ab648765b4508da12c586d55dc981275776507ebca0e4d1bcaa302bb69dcfa31b3451"), hex!("fee0192d49bcc0c28d954763c2cbe739b9265c4bebe3883803c64971220cfda60b9ac99ad986cd908c0534b260b5cfca46f6c2b0f3f21bda")),
483+
];
484+
485+
for (msg, x, y) in MSGS {
486+
let p = Curve448::hash_from_bytes::<ExpandMsgXof<Shake256>>(&[msg], &[DST])
487+
.unwrap()
488+
.to_affine();
489+
let mut xx = [0u8; 56];
490+
xx.copy_from_slice(&x[..]);
491+
xx.reverse();
492+
let mut yy = [0u8; 56];
493+
yy.copy_from_slice(&y[..]);
494+
yy.reverse();
495+
assert_eq!(p.x(), xx);
496+
assert_eq!(p.y(), yy);
497+
}
498+
}
499+
500+
#[test]
501+
fn encode_with_test_vectors() {
502+
const DST: &[u8] = b"QUUX-V01-CS02-with-curve448_XOF:SHAKE256_ELL2_NU_";
503+
const MSGS: &[(&[u8], [u8; 56], [u8; 56])] = &[
504+
(b"", hex!("b65e8dbb279fd656f926f68d463b13ca7a982b32f5da9c7cc58afcf6199e4729863fb75ca9ae3c95c6887d95a5102637a1c5c40ff0aafadc"), hex!("ea1ea211cf29eca11c057fe8248181591a19f6ac51d45843a65d4bb8b71bc83a64c771ed7686218a278ef1c5d620f3d26b53162188645453")),
505+
(b"abc", hex!("51aceca4fa95854bbaba58d8a5e17a86c07acadef32e1188cafda26232131800002cc2f27c7aec454e5e0c615bddffb7df6a5f7f0f14793f"), hex!("c590c9246eb28b08dee816d608ef233ea5d76e305dc458774a1e1bd880387e6734219e2018e4aa50a49486dce0ba8740065da37e6cf5212c")),
506+
(b"abcdef0123456789", hex!("c6d65987f146b8d0cb5d2c44e1872ac3af1f458f6a8bd8c232ffe8b9d09496229a5a27f350eb7d97305bcc4e0f38328718352e8e3129ed71"), hex!("4d2f901bf333fdc4135b954f20d59207e9f6a4ecf88ce5af11c892b44f79766ec4ecc9f60d669b95ca8940f39b1b7044140ac2040c1bf659")),
507+
(b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", hex!("9b8d008863beb4a02fb9e4efefd2eba867307fb1c7ce01746115d32e1db551bb254e8e3e4532d5c74a83949a69a60519ecc9178083cbe943"), hex!("346a1fca454d1e67c628437c270ec0f0c4256bb774fe6c0e49de7004ff6d9199e2cd99d8f7575a96aafc4dc8db1811ba0a44317581f41371")),
508+
(b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", hex!("8746dc34799112d1f20acda9d7f722c9abb29b1fb6b7e9e566983843c20bd7c9bfad21b45c5166b808d2f5d44e188f1fdaf29cdee8a72e4c"), hex!("7c1293484c9287c298a1a0600c64347eee8530acf563cd8705e05728274d8cd8101835f8003b6f3b78b5beb28f5be188a3d7bce1ec5a36b1")),
509+
];
510+
511+
for (msg, x, y) in MSGS {
512+
let p = Curve448::encode_from_bytes::<ExpandMsgXof<Shake256>>(&[msg], &[DST])
513+
.unwrap()
514+
.to_affine();
515+
let mut xx = [0u8; 56];
516+
xx.copy_from_slice(&x[..]);
517+
xx.reverse();
518+
let mut yy = [0u8; 56];
519+
yy.copy_from_slice(&y[..]);
520+
yy.reverse();
521+
assert_eq!(p.x(), xx);
522+
assert_eq!(p.y(), yy);
523+
}
524+
}
444525
}

ed448-goldilocks/src/montgomery/scalar.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,12 @@ impl FromOkm for MontgomeryScalar {
7777
#[cfg(test)]
7878
mod test {
7979
use super::*;
80+
use crate::montgomery::DEFAULT_HASH_TO_CURVE_SUITE;
81+
use elliptic_curve::PrimeField;
82+
use hash2curve::ExpandMsgXof;
83+
use hash2curve::GroupDigest;
8084
use hex_literal::hex;
85+
use sha3::Shake256;
8186

8287
#[test]
8388
fn test_basic_add() {
@@ -268,4 +273,18 @@ mod test {
268273
assert!(res.is_ok());
269274
assert_eq!(res.unwrap(), MontgomeryScalar::TWO_INV);
270275
}
276+
277+
#[test]
278+
fn scalar_hash() {
279+
let msg = b"hello world";
280+
let res = Curve448::hash_to_scalar::<ExpandMsgXof<Shake256>>(
281+
&[msg],
282+
&[DEFAULT_HASH_TO_CURVE_SUITE],
283+
)
284+
.unwrap();
285+
let expected: [u8; 56] = hex_literal::hex!(
286+
"287e2dd03a61fe8c38304326442016e9dab1b12c9fd7fe2e4cff4170fc7893f06746c27c35fe6fe43d350aab1d63baef8e3c99a25ab43e1e"
287+
);
288+
assert_eq!(res.to_repr(), Array::from(expected));
289+
}
271290
}

0 commit comments

Comments
 (0)