From e8ca9229c8aa6bd2d8abb1f91a8662eed2e68fd5 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Mon, 23 Feb 2026 23:04:26 +0100 Subject: [PATCH] Expose the elligator map for Curve25519 This is the same as for Edwards25519 without the y coordinate, since it returns Montgomery coordinates, but it can be confusing to call the Edwards25519 function while working on the Curve25519 representation. New protocols such as CPACE requires the map over Curve25519. --- lib/std/crypto/25519/curve25519.zig | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/std/crypto/25519/curve25519.zig b/lib/std/crypto/25519/curve25519.zig index 0cc0dec3d1..5e84873d3a 100644 --- a/lib/std/crypto/25519/curve25519.zig +++ b/lib/std/crypto/25519/curve25519.zig @@ -101,6 +101,11 @@ pub const Curve25519 = struct { return try ladder(p, s, 256); } + /// Elligator2 map for Curve25519. + pub fn elligator2(r: Fe) Curve25519 { + return .{ .x = crypto.ecc.Edwards25519.elligator2(r).x }; + } + /// Compute the Curve25519 equivalent to an Edwards25519 point. /// /// Note that the function doesn't check that the input point is @@ -145,6 +150,23 @@ test "non-affine edwards25519 to curve25519 projection" { try std.testing.expectEqualSlices(u8, &xp.toBytes(), &expected); } +test "elligator2" { + const Fe = Curve25519.Fe; + + // RFC 9380 Appendix J.4.2 test vector (curve25519_XMD:SHA-512_ELL2_NU_) + // u = 0x608d892b641f0328523802a6603427c26e55e6f27e71a91a478148d45b5093cd + // Q.x = 0x51125222da5e763d97f3c10fcc92ea6860b9ccbbd2eb1285728f566721c1e65b + const u = Fe.fromBytes(.{ + 0xcd, 0x93, 0x50, 0x5b, 0xd4, 0x48, 0x81, 0x47, 0x1a, 0xa9, 0x71, 0x7e, 0xf2, 0xe6, 0x55, 0x6e, + 0xc2, 0x27, 0x34, 0x60, 0xa6, 0x02, 0x38, 0x52, 0x28, 0x03, 0x1f, 0x64, 0x2b, 0x89, 0x8d, 0x60, + }); + const p = Curve25519.elligator2(u); + try std.testing.expectEqual([32]u8{ + 0x5b, 0xe6, 0xc1, 0x21, 0x67, 0x56, 0x8f, 0x72, 0x85, 0x12, 0xeb, 0xd2, 0xbb, 0xcc, 0xb9, 0x60, + 0x68, 0xea, 0x92, 0xcc, 0x0f, 0xc1, 0xf3, 0x97, 0x3d, 0x76, 0x5e, 0xda, 0x22, 0x52, 0x12, 0x51, + }, p.toBytes()); +} + test "small order check" { var s: [32]u8 = [_]u8{1} ++ [_]u8{0} ** 31; const small_order_ss: [7][32]u8 = .{