Skip to content

Commit c819f1e

Browse files
committed
Add hash2curve to ProjectiveMontgomeryXpoint
1 parent d6785c7 commit c819f1e

File tree

3 files changed

+157
-2
lines changed

3 files changed

+157
-2
lines changed

ed448-goldilocks/benches/bench.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
22
use ed448_goldilocks::{
33
Decaf448, DecafPoint, DecafScalar, EdwardsPoint, EdwardsScalar, MontgomeryScalar,
4-
MontgomeryXpoint,
4+
MontgomeryXpoint, ProjectiveMontgomeryXpoint,
55
};
66
use elliptic_curve::group::GroupEncoding;
77
use elliptic_curve::{Field, Group};
@@ -146,6 +146,18 @@ pub fn x448(c: &mut Criterion) {
146146
)
147147
});
148148

149+
group.bench_function("encode_to_curve", |b| {
150+
b.iter_batched(
151+
|| {
152+
let mut msg = [0; 64];
153+
OsRng.try_fill_bytes(&mut msg).unwrap();
154+
msg
155+
},
156+
|msg| ProjectiveMontgomeryXpoint::encode_with_defaults(&msg, b"test DST"),
157+
BatchSize::SmallInput,
158+
)
159+
});
160+
149161
group.finish();
150162
}
151163

ed448-goldilocks/src/field/element.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,25 @@ impl FieldElement {
467467
MontgomeryPoint::new(x, y)
468468
}
469469

470+
// See https://www.rfc-editor.org/rfc/rfc9380.html#name-curve448-q-3-mod-4-k-1.
471+
// Without y-coordinate.
472+
pub(crate) fn map_to_curve_elligator2_curve448_x(&self) -> FieldElement {
473+
let mut t1 = self.square(); // 1. t1 = u^2
474+
t1 *= Self::Z; // 2. t1 = Z * t1 // Z * u^2
475+
let e1 = t1.ct_eq(&Self::MINUS_ONE); // 3. e1 = t1 == -1 // exceptional case: Z * u^2 == -1
476+
t1.conditional_assign(&Self::ZERO, e1); // 4. t1 = CMOV(t1, 0, e1) // if t1 == -1, set t1 = 0
477+
let mut x1 = t1 + Self::ONE; // 5. x1 = t1 + 1
478+
x1 = x1.invert(); // 6. x1 = inv0(x1)
479+
x1 *= -Self::J; // 7. x1 = -A * x1 // x1 = -A / (1 + Z * u^2)
480+
let mut gx1 = x1 + Self::J; // 8. gx1 = x1 + A
481+
gx1 *= x1; // 9. gx1 = gx1 * x1
482+
gx1 += Self::ONE; // 10. gx1 = gx1 + B
483+
gx1 *= x1; // 11. gx1 = gx1 * x1 // gx1 = x1^3 + A * x1^2 + B * x1
484+
let x2 = -x1 - Self::J; // 12. x2 = -x1 - A
485+
let e2 = gx1.is_square(); // 14. e2 = is_square(gx1)
486+
Self::conditional_select(&x2, &x1, e2) // 15. x = CMOV(x2, x1, e2) // If is_square(gx1), x = x1, else x = x2
487+
}
488+
470489
// See https://www.shiftleft.org/papers/decaf/decaf.pdf#section.A.3.
471490
// Implementation copied from <https://sourceforge.net/p/ed448goldilocks/code/ci/e5cc6240690d3ffdfcbdb1e4e851954b789cd5d9/tree/src/per_curve/elligator.tmpl.c#l28>.
472491
pub(crate) fn map_to_curve_decaf448(&self) -> TwistedExtendedPoint {

ed448-goldilocks/src/montgomery/x.rs

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
// use crate::constants::A_PLUS_TWO_OVER_FOUR;
22
use super::{MontgomeryPoint, MontgomeryScalar, ProjectiveMontgomeryPoint};
3-
use crate::AffinePoint;
43
use crate::field::{ConstMontyType, FieldElement};
4+
use crate::{AffinePoint, Curve448};
55
use core::fmt;
66
use core::ops::Mul;
7+
use elliptic_curve::array::Array;
78
use elliptic_curve::bigint::U448;
9+
use elliptic_curve::consts::{U28, U84};
10+
use elliptic_curve::ops::Reduce;
11+
use hash2curve::{ExpandMsg, ExpandMsgXof, ExpandMsgXofError, Expander, MapToCurve};
12+
use sha3::Shake256;
813
use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq};
914

1015
impl MontgomeryXpoint {
@@ -290,6 +295,71 @@ impl ProjectiveMontgomeryXpoint {
290295
Self { U, W }
291296
}
292297

298+
/// Hash a message to a point on the curve
299+
///
300+
/// Hash using the default `expand_message` and hash function.
301+
/// For more control see [`Self::hash()`].
302+
pub fn hash_with_defaults(msg: &[u8], dst: &[u8]) -> Result<Self, ExpandMsgXofError> {
303+
Self::hash::<ExpandMsgXof<Shake256>>(&[msg], &[dst])
304+
}
305+
306+
/// Hash a message to a point on the curve
307+
///
308+
/// Implements hash to curve according
309+
/// see <https://datatracker.ietf.org/doc/rfc9380/>
310+
pub fn hash<X>(msg: &[&[u8]], dst: &[&[u8]]) -> Result<Self, X::Error>
311+
where
312+
X: ExpandMsg<U28>,
313+
{
314+
let mut expander =
315+
X::expand_message(msg, dst, (84 * 2).try_into().expect("must not be zero"))?;
316+
let mut data = Array::<u8, U84>::default();
317+
expander
318+
.fill_bytes(&mut data)
319+
.expect("`data` must match length");
320+
let u0 = FieldElement::reduce(&data);
321+
expander
322+
.fill_bytes(&mut data)
323+
.expect("`data` must match length");
324+
let u1 = FieldElement::reduce(&data);
325+
326+
let q0 = Curve448::map_to_curve(u0);
327+
let q1 = Curve448::map_to_curve(u1);
328+
329+
Ok(Self::from(q0 + q1).double().double())
330+
}
331+
332+
/// Encode a message to a point on the curve
333+
///
334+
/// Encode using the default `expand_message` and hash function.
335+
/// For more control see [`Self::encode()`].
336+
pub fn encode_with_defaults(msg: &[u8], dst: &[u8]) -> Result<Self, ExpandMsgXofError> {
337+
Self::encode::<ExpandMsgXof<Shake256>>(&[msg], &[dst])
338+
}
339+
340+
/// Encode a message to a point on the curve
341+
///
342+
/// Implements encode to curve according
343+
/// see <https://datatracker.ietf.org/doc/rfc9380/>
344+
pub fn encode<X>(msg: &[&[u8]], dst: &[&[u8]]) -> Result<Self, X::Error>
345+
where
346+
X: ExpandMsg<U28>,
347+
{
348+
let mut expander = X::expand_message(msg, dst, 84.try_into().expect("should never fail"))?;
349+
let mut data = Array::<u8, U84>::default();
350+
expander
351+
.fill_bytes(&mut data)
352+
.expect("`data` must match length");
353+
let u = FieldElement::reduce(&data);
354+
355+
Ok(Self {
356+
U: u.map_to_curve_elligator2_curve448_x(),
357+
W: FieldElement::ONE,
358+
}
359+
.double()
360+
.double())
361+
}
362+
293363
/// Convert the point to affine form
294364
pub fn to_affine(&self) -> MontgomeryXpoint {
295365
let x = self.U * self.W.invert();
@@ -320,6 +390,8 @@ mod tests {
320390
use super::*;
321391
use crate::EdwardsPoint;
322392
use elliptic_curve::CurveGroup;
393+
use hex_literal::hex;
394+
use sha3::Shake256;
323395

324396
#[test]
325397
fn test_montgomery_edwards() {
@@ -351,4 +423,56 @@ mod tests {
351423

352424
assert_eq!(x_identity.to_extended(Choice::from(1)), identity);
353425
}
426+
427+
#[test]
428+
fn hash_with_test_vectors() {
429+
const DST: &[u8] = b"QUUX-V01-CS02-with-curve448_XOF:SHAKE256_ELL2_RO_";
430+
const MSGS: &[(&[u8], [u8; 56], [u8; 56])] = &[
431+
(b"", hex!("5ea5ff623d27c75e73717514134e73e419f831a875ca9e82915fdfc7069d0a9f8b532cfb32b1d8dd04ddeedbe3fa1d0d681c01e825d6a9ea"), hex!("afadd8de789f8f8e3516efbbe313a7eba364c939ecba00dabf4ced5c563b18e70a284c17d8f46b564c4e6ce11784a3825d941116622128c1")),
432+
(b"abc", hex!("9b2f7ce34878d7cebf34c582db14958308ea09366d1ec71f646411d3de0ae564d082b06f40cd30dfc08d9fb7cb21df390cf207806ad9d0e4"), hex!("138a0eef0a4993ea696152ed7db61f7ddb4e8100573591e7466d61c0c568ecaec939e36a84d276f34c402526d8989a96e99760c4869ed633")),
433+
(b"abcdef0123456789", hex!("f54ecd14b85a50eeeee0618452df3a75be7bfba11da5118774ae4ea55ac204e153f77285d780c4acee6c96abe3577a0c0b00be6e790cf194"), hex!("935247a64bf78c107069943c7e3ecc52acb27ce4a3230407c8357341685ea2152e8c3da93f8cd77da1bddb5bb759c6e7ae7d516dced42850")),
434+
(b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", hex!("5bd67c4f88adf6beb10f7e0d0054659776a55c97b809ec8b3101729e104fd0f684e103792f267fd87cc4afc25a073956ef4f268fb02824d5"), hex!("da1f5cb16a352719e4cb064cf47ba72aeba7752d03e8ca2c56229f419b4ef378785a5af1a53dd7ab4d467c1f92f7b139b3752faf29c96432")),
435+
(b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", hex!("ea441c10b3636ecedd5c0dfcae96384cc40de8390a0ab648765b4508da12c586d55dc981275776507ebca0e4d1bcaa302bb69dcfa31b3451"), hex!("fee0192d49bcc0c28d954763c2cbe739b9265c4bebe3883803c64971220cfda60b9ac99ad986cd908c0534b260b5cfca46f6c2b0f3f21bda")),
436+
];
437+
438+
for (msg, x, y) in MSGS {
439+
let p = ProjectiveMontgomeryXpoint::hash::<ExpandMsgXof<Shake256>>(&[msg], &[DST])
440+
.unwrap()
441+
.to_affine();
442+
let mut xx = [0u8; 56];
443+
xx.copy_from_slice(&x[..]);
444+
xx.reverse();
445+
let mut yy = [0u8; 56];
446+
yy.copy_from_slice(&y[..]);
447+
yy.reverse();
448+
assert_eq!(p.0, xx);
449+
assert!(p.y(Choice::from(0)) == yy || p.y(Choice::from(1)) == yy);
450+
}
451+
}
452+
453+
#[test]
454+
fn encode_with_test_vectors() {
455+
const DST: &[u8] = b"QUUX-V01-CS02-with-curve448_XOF:SHAKE256_ELL2_NU_";
456+
const MSGS: &[(&[u8], [u8; 56], [u8; 56])] = &[
457+
(b"", hex!("b65e8dbb279fd656f926f68d463b13ca7a982b32f5da9c7cc58afcf6199e4729863fb75ca9ae3c95c6887d95a5102637a1c5c40ff0aafadc"), hex!("ea1ea211cf29eca11c057fe8248181591a19f6ac51d45843a65d4bb8b71bc83a64c771ed7686218a278ef1c5d620f3d26b53162188645453")),
458+
(b"abc", hex!("51aceca4fa95854bbaba58d8a5e17a86c07acadef32e1188cafda26232131800002cc2f27c7aec454e5e0c615bddffb7df6a5f7f0f14793f"), hex!("c590c9246eb28b08dee816d608ef233ea5d76e305dc458774a1e1bd880387e6734219e2018e4aa50a49486dce0ba8740065da37e6cf5212c")),
459+
(b"abcdef0123456789", hex!("c6d65987f146b8d0cb5d2c44e1872ac3af1f458f6a8bd8c232ffe8b9d09496229a5a27f350eb7d97305bcc4e0f38328718352e8e3129ed71"), hex!("4d2f901bf333fdc4135b954f20d59207e9f6a4ecf88ce5af11c892b44f79766ec4ecc9f60d669b95ca8940f39b1b7044140ac2040c1bf659")),
460+
(b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", hex!("9b8d008863beb4a02fb9e4efefd2eba867307fb1c7ce01746115d32e1db551bb254e8e3e4532d5c74a83949a69a60519ecc9178083cbe943"), hex!("346a1fca454d1e67c628437c270ec0f0c4256bb774fe6c0e49de7004ff6d9199e2cd99d8f7575a96aafc4dc8db1811ba0a44317581f41371")),
461+
(b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", hex!("8746dc34799112d1f20acda9d7f722c9abb29b1fb6b7e9e566983843c20bd7c9bfad21b45c5166b808d2f5d44e188f1fdaf29cdee8a72e4c"), hex!("7c1293484c9287c298a1a0600c64347eee8530acf563cd8705e05728274d8cd8101835f8003b6f3b78b5beb28f5be188a3d7bce1ec5a36b1")),
462+
];
463+
464+
for (msg, x, y) in MSGS {
465+
let p = ProjectiveMontgomeryXpoint::encode::<ExpandMsgXof<Shake256>>(&[msg], &[DST])
466+
.unwrap()
467+
.to_affine();
468+
let mut xx = [0u8; 56];
469+
xx.copy_from_slice(&x[..]);
470+
xx.reverse();
471+
let mut yy = [0u8; 56];
472+
yy.copy_from_slice(&y[..]);
473+
yy.reverse();
474+
assert_eq!(p.0, xx);
475+
assert!(p.y(Choice::from(0)) == yy || p.y(Choice::from(1)) == yy);
476+
}
477+
}
354478
}

0 commit comments

Comments
 (0)