diff --git a/src/crypto/crypto-ops.c b/src/crypto/crypto-ops.c index 12745cd68..5a87d7964 100644 --- a/src/crypto/crypto-ops.c +++ b/src/crypto/crypto-ops.c @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2024, The Monero Project +// Copyright (c) 2014-2026, The Monero Project // // All rights reserved. // @@ -3904,6 +3904,39 @@ int sc_isnonzero(const unsigned char *s) { s[27] | s[28] | s[29] | s[30] | s[31]) - 1) >> 8) + 1; } +static int edwardsYZ_to_x25519(unsigned char *xbytes, const fe Y, const fe Z) { + //! @see Section 4.1 of RFC 7748: https://www.rfc-editor.org/rfc/rfc7748.html#section-4.1 + // + // y = Y/Z + // x_mont = (1 + y) / (1 - y) + // = (1 + Y/Z) / (1 - Y/Z) + // = (Z + Y) / (Z - Y) + + fe tmp0; + fe tmp1; + int r; + fe_add(tmp0, Z, Y); // Z + Y + fe_sub(tmp1, Z, Y); // Z - Y + r = -!fe_isnonzero(tmp1); // succeed iff 0 != (Z - Y). AKA fail if identity point or some invalid reprs + fe_invert(tmp1, tmp1); // 1/(Z - Y) + fe_mul(tmp0, tmp0, tmp1); // (Z + Y) / (Z - Y) + fe_tobytes(xbytes, tmp0); // tobytes((Z + Y) / (Z - Y)) + return r; // 0 on success, otherwise -1 +} + +int ge_p3_to_x25519(unsigned char *xbytes, const ge_p3 *h) { + return edwardsYZ_to_x25519(xbytes, h->Y, h->Z); +} + +int edwards_bytes_to_x25519_vartime(unsigned char *xbytes, const unsigned char *s) { + ge_p3 h; + const int r = ge_frombytes_vartime(&h, s); + if (0 != r) + return r; + + return edwardsYZ_to_x25519(xbytes, h.Y, h.Z); +} + int ge_p3_is_point_at_infinity_vartime(const ge_p3 *p) { // https://eprint.iacr.org/2008/522 // X == T == 0 and Y/Z == 1 diff --git a/src/crypto/crypto-ops.h b/src/crypto/crypto-ops.h index 7b6b9ffc2..45f2566d6 100644 --- a/src/crypto/crypto-ops.h +++ b/src/crypto/crypto-ops.h @@ -165,6 +165,30 @@ void sc_muladd(unsigned char *s, const unsigned char *a, const unsigned char *b, int sc_check(const unsigned char *); int sc_isnonzero(const unsigned char *); /* Doesn't normalize */ +/** + * @brief Convert Ed25519 y-coord to X25519 x-coord, AKA "ConvertPointE()" in the Carrot spec + * @param[out] xbytes X25519 x-coord as a 255-bit little endian integer + * @param h Ed25519 point in projective representation + * @return 0 on success, otherwise non-0 on failure + * + * Returns failure on Ed25519's identity point. Assumes `h` has valid field element representations, + * that Z != 0, and that Y/Z is a valid y-coordinate for Ed25519. Returns success iff Y != Z (Y == Z + * corresponds with the identity point in valid point representations). The runtime is constant. + */ +int ge_p3_to_x25519(unsigned char *xbytes, const ge_p3 *h); +/** + * @brief Convert Ed25519 y-coord to X25519 x-coord, AKA "ConvertPointE()" in the Carrot spec + * @param[out] xbytes X25519 x-coord as a 255-bit little endian integer + * @param s Ed25519 point in compressed Y representation + * @return 0 on success, otherwise non-0 on failure + * + * Returns failure on Ed25519's identity point (repr {1, 0, 0, ...}). Otherwise, returns success iff + * `s` is a valid compressed Y representation of an Ed25519 point. The runtime is variable *only* in + * whether `s` is a valid representation. In other words, for all valid Ed25519 points, the runtime + * is constant. + */ +int edwards_bytes_to_x25519_vartime(unsigned char *xbytes, const unsigned char *s); + // internal uint64_t load_3(const unsigned char *in); uint64_t load_4(const unsigned char *in);