crypto: move torsion clearing into crypto
What changed, and why it matters
This commit is a code cleanup: it moves the logic that removes small-subgroup 'torsion' from elliptic-curve points out of a newer, specialized module (fcmp_pp) and into the core cryptography library (crypto). The same mathematical operation is now shared by the ring signature code and the new FCMP++ code. There is no direct evidence in the commit that this fixes an active vulnerability; it looks like a refactoring to avoid duplicated constants and helper functions.
Treat as a routine refactoring with defensive-security value. Reviewers should verify that the moved torsion-clearing function produces byte-identical output for all inputs compared to the previous implementation, and that no call site accidentally bypasses the identity-point check. No urgent patch deployment is indicated by the commit alone.
Security signals we found
Torsion-clearing logic moved to shared crypto layer
Identity-point rejection preserved
No new cryptographic algorithm introduced
No explicit vulnerability or bug fix described in commit message
Evidence from the diff
The patch relocates torsion-clearing helpers (get_valid_torsion_cleared_point_vartime, clear_torsion_vartime) from src/fcmp_pp/fcmp_pp_crypto.{cpp,h} into src/crypto/crypto.{cpp,h} and src/crypto/crypto-ops.{c,h}. It introduces ge_clear_torsion_vartime and a shared constant sc_inv_eight in the low-level Ed25519 code, exposes crypto::EC_I, and updates call sites in curve_trees.cpp, rctSigs.cpp, and unit tests. The behavior—multiply by the inverse of 8, then by 8, to clear the 8-torsion component and reject the identity point—remains unchanged. The change reduces duplication and makes the main crypto library responsible for subgroup validation.
Changed components
src/crypto/crypto-ops-data.csrc/crypto/crypto-ops.csrc/crypto/crypto-ops.hsrc/crypto/crypto.cppsrc/crypto/crypto.hsrc/fcmp_pp/curve_trees.cppsrc/fcmp_pp/fcmp_pp_crypto.cppsrc/fcmp_pp/fcmp_pp_crypto.hsrc/ringct/rctSigs.cpptests/unit_tests/crypto.cppInspect captured patch +48 / −65
### src/crypto/crypto-ops-data.c
@@ -883,3 +883,4 @@ const ge_p3 ge_p3_H = {
/* curve order l = 2^252 + 27742317777372353535851937790883648493 */
const unsigned char sc_l[32] = {0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10};
+const unsigned char sc_inv_eight[32] = {0x79, 0x2f, 0xdc, 0xe2, 0x29, 0xe5, 0x06, 0x61, 0xd0, 0xda, 0x1c, 0x7d, 0xb3, 0x9d, 0xd3, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06};
### src/crypto/crypto-ops.c
@@ -2358,6 +2358,17 @@ void ge_mul8(ge_p1p1 *r, const ge_p2 *t) {
ge_p2_dbl(r, &u);
}
+void ge_clear_torsion_vartime(unsigned char *out, const ge_p3 *point) {
+ // mul by inv 8, then mul by 8
+ ge_p2 point_inv_8;
+ ge_scalarmult(&point_inv_8, sc_inv_eight, point);
+ ge_p1p1 point_inv_8_mul_8;
+ ge_mul8(&point_inv_8_mul_8, &point_inv_8);
+ ge_p3 torsion_cleared_point;
+ ge_p1p1_to_p3(&torsion_cleared_point, &point_inv_8_mul_8);
+ ge_p3_tobytes(out, &torsion_cleared_point);
+}
+
void ge_fromfe_frombytes_vartime(ge_p2 *r, const unsigned char *s) {
fe u, v, w, x, y, z;
unsigned char sign;
### src/crypto/crypto-ops.h
@@ -142,6 +142,7 @@ void ge_triple_scalarmult_precomp_vartime(ge_p2 *, const unsigned char *, const
void ge_double_scalarmult_precomp_vartime2(ge_p2 *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp);
void ge_double_scalarmult_precomp_vartime2_p3(ge_p3 *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp);
void ge_mul8(ge_p1p1 *, const ge_p2 *);
+void ge_clear_torsion_vartime(unsigned char *, const ge_p3 *);
extern const fe fe_a;
extern const fe fe_ma2;
extern const fe fe_ma;
@@ -154,6 +155,7 @@ extern const fe fe_c;
extern const ge_p3 ge_p3_identity;
extern const ge_p3 ge_p3_H;
extern const unsigned char sc_l[32];
+extern const unsigned char sc_inv_eight[32];
void ge_fromfe_frombytes_vartime(ge_p2 *, const unsigned char *);
void sc_0(unsigned char *);
void sc_1(unsigned char *);
### src/crypto/crypto.cpp
@@ -224,6 +224,16 @@ namespace crypto {
return ge_frombytes_vartime(&point, &key) == 0;
}
+ bool get_valid_torsion_cleared_point_vartime(const ec_point &point, ec_point &torsion_cleared_out) {
+ ge_p3 p3;
+ if (ge_frombytes_vartime(&p3, &point) != 0)
+ return false;
+ ge_clear_torsion_vartime(&torsion_cleared_out, &p3);
+ if (torsion_cleared_out == EC_I)
+ return false;
+ return true;
+ }
+
bool crypto_ops::secret_key_to_public_key(const secret_key &sec, public_key &pub) {
ge_p3 point;
if (sc_check(&unwrap(sec)) != 0) {
### src/crypto/crypto.h
@@ -98,6 +98,8 @@ namespace crypto {
sizeof(key_derivation) == 32 && sizeof(key_image) == 32 &&
sizeof(signature) == 64 && sizeof(view_tag) == 1, "Invalid structure size");
+ static const ec_point EC_I = {1};
+
class crypto_ops {
crypto_ops();
crypto_ops(const crypto_ops &);
@@ -202,6 +204,8 @@ namespace crypto {
return crypto_ops::check_key(key);
}
+ bool get_valid_torsion_cleared_point_vartime(const ec_point &point, ec_point &torsion_cleared_out);
+
/* Checks a private key and computes the corresponding public key.
*/
inline bool secret_key_to_public_key(const secret_key &sec, public_key &pub) {
### src/fcmp_pp/curve_trees.cpp
@@ -101,9 +101,9 @@ OutputTuple output_to_tuple(const OutputPair &output_pair)
{
TIME_MEASURE_NS_START(clear_torsion_ns);
- if (!fcmp_pp::get_valid_torsion_cleared_point_vartime(output_pubkey, O))
+ if (!crypto::get_valid_torsion_cleared_point_vartime(output_pubkey, O))
throw std::runtime_error("O is invalid for insertion to tree");
- if (!fcmp_pp::get_valid_torsion_cleared_point_vartime(commitment, C))
+ if (!crypto::get_valid_torsion_cleared_point_vartime(commitment, C))
throw std::runtime_error("C is invalid for insertion to tree");
if (O != output_pubkey)
@@ -121,17 +121,17 @@ OutputTuple output_to_tuple(const OutputPair &output_pair)
// Debug build safety checks
crypto::ec_point O_debug;
crypto::ec_point C_debug;
- assert(fcmp_pp::get_valid_torsion_cleared_point_vartime(output_pubkey, O_debug));
- assert(fcmp_pp::get_valid_torsion_cleared_point_vartime(commitment, C_debug));
+ assert(crypto::get_valid_torsion_cleared_point_vartime(output_pubkey, O_debug));
+ assert(crypto::get_valid_torsion_cleared_point_vartime(commitment, C_debug));
assert(O == O_debug);
assert(C == C_debug);
}
#endif
// Redundant check for safety
- if (O == fcmp_pp::EC_I)
+ if (O == crypto::EC_I)
throw std::runtime_error("O cannot equal identity");
- if (C == fcmp_pp::EC_I)
+ if (C == crypto::EC_I)
throw std::runtime_error("C cannot equal identity");
return output_tuple_from_bytes(O, I, C);
### src/fcmp_pp/fcmp_pp_crypto.cpp
@@ -44,39 +44,16 @@ bool mul8_is_identity_vartime(const ge_p3 &point) {
return ge_p3_is_point_at_infinity_vartime(&point_mul8_p3);
}
//----------------------------------------------------------------------------------------------------------------------
-crypto::ec_point clear_torsion_vartime(const ge_p3 &point) {
- // mul by inv 8, then mul by 8
- ge_p2 point_inv_8;
- ge_scalarmult(&point_inv_8, to_bytes(EC_INV_EIGHT), &point);
- ge_p1p1 point_inv_8_mul_8;
- ge_mul8(&point_inv_8_mul_8, &point_inv_8);
- ge_p3 torsion_cleared_point;
- ge_p1p1_to_p3(&torsion_cleared_point, &point_inv_8_mul_8);
- crypto::ec_point k_out;
- ge_p3_tobytes(to_bytes(k_out), &torsion_cleared_point);
- return k_out;
-}
-//----------------------------------------------------------------------------------------------------------------------
-bool get_valid_torsion_cleared_point_vartime(const crypto::ec_point &point, crypto::ec_point &torsion_cleared_out) {
- ge_p3 p3;
- if (ge_frombytes_vartime(&p3, to_bytes(point)) != 0)
- return false;
- torsion_cleared_out = fcmp_pp::clear_torsion_vartime(p3);
- if (torsion_cleared_out == EC_I)
- return false;
- return true;
-}
-//----------------------------------------------------------------------------------------------------------------------
bool point_to_ed_derivatives(const crypto::ec_point &torsion_free_point, EdDerivatives &ed_derivatives) {
- // The point SHOULD not have torsion and should pass get_valid_torsion_cleared_point_vartime
+ // The point SHOULD not have torsion and should pass crypto::get_valid_torsion_cleared_point_vartime
#if !defined(NDEBUG)
{
crypto::ec_point expected;
- assert(get_valid_torsion_cleared_point_vartime(torsion_free_point, expected));
+ assert(crypto::get_valid_torsion_cleared_point_vartime(torsion_free_point, expected));
assert(torsion_free_point == expected);
}
#endif
- if (torsion_free_point == EC_I)
+ if (torsion_free_point == crypto::EC_I)
return false;
// fe y;
ge_p3 p3;
### src/fcmp_pp/fcmp_pp_crypto.h
@@ -38,27 +38,6 @@ namespace fcmp_pp
{
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
-static const crypto::ec_point EC_I = {1};
-
-static const crypto::ec_scalar EC_INV_EIGHT = {{
- static_cast<char>(static_cast<signed char>(121)), static_cast<char>(static_cast<signed char>(47)),
- static_cast<char>(static_cast<signed char>(-36)), static_cast<char>(static_cast<signed char>(-30)),
- static_cast<char>(static_cast<signed char>(41)), static_cast<char>(static_cast<signed char>(-27)),
- static_cast<char>(static_cast<signed char>(6)), static_cast<char>(static_cast<signed char>(97)),
- static_cast<char>(static_cast<signed char>(-48)), static_cast<char>(static_cast<signed char>(-38)),
- static_cast<char>(static_cast<signed char>(28)), static_cast<char>(static_cast<signed char>(125)),
- static_cast<char>(static_cast<signed char>(-77)), static_cast<char>(static_cast<signed char>(-99)),
- static_cast<char>(static_cast<signed char>(-45)), static_cast<char>(static_cast<signed char>(7)),
- static_cast<char>(static_cast<signed char>(0)), static_cast<char>(static_cast<signed char>(0)),
- static_cast<char>(static_cast<signed char>(0)), static_cast<char>(static_cast<signed char>(0)),
- static_cast<char>(static_cast<signed char>(0)), static_cast<char>(static_cast<signed char>(0)),
- static_cast<char>(static_cast<signed char>(0)), static_cast<char>(static_cast<signed char>(0)),
- static_cast<char>(static_cast<signed char>(0)), static_cast<char>(static_cast<signed char>(0)),
- static_cast<char>(static_cast<signed char>(0)), static_cast<char>(static_cast<signed char>(0)),
- static_cast<char>(static_cast<signed char>(0)), static_cast<char>(static_cast<signed char>(0)),
- static_cast<char>(static_cast<signed char>(0)), static_cast<char>(static_cast<signed char>(6))
- }};
-//----------------------------------------------------------------------------------------------------------------------
// Field elems needed to get wei x and y coords
// Take note of the bounds, and make sure downstream field ops can take said bounds as input.
struct EdDerivatives final
@@ -70,16 +49,14 @@ struct EdDerivatives final
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
bool mul8_is_identity_vartime(const ge_p3 &point);
-crypto::ec_point clear_torsion_vartime(const ge_p3 &point);
-bool get_valid_torsion_cleared_point_vartime(const crypto::ec_point &point, crypto::ec_point &torsion_cleared_out);
/*
point_to_ed_derivatives converts an Ed25519 point to Ed25519 derivatives used for converting to
Weierstrauss coords, as per https://www.ietf.org/archive/id/draft-ietf-lwig-curve-representations-02.pdf E.2.
We expect that a point passed in this function has been validated to be in the main subgroup with no torsion,
and does not equal identity. The `torsion_free_point` param is expected to be the output of
-get_valid_torsion_cleared_point_vartime.
+crypto::get_valid_torsion_cleared_point_vartime.
*/
bool point_to_ed_derivatives(const crypto::ec_point &torsion_free_point, EdDerivatives &ed_derivatives);
@@ -94,7 +71,7 @@ point_to_wei_x_y takes a torsion free point as input, and coverts to Weierstraus
We expect that a point passed in this function has been validated to be in the main subgroup with no torsion,
and does not equal identity. The `torsion_free_point` param is expected to be the output of
-get_valid_torsion_cleared_point_vartime.
+crypto::get_valid_torsion_cleared_point_vartime.
*/
bool point_to_wei_x_y(const crypto::ec_point &torsion_free_point, crypto::ec_coord &wei_x, crypto::ec_coord &wei_y);
//----------------------------------------------------------------------------------------------------------------------
### src/ringct/rctSigs.cpp
@@ -39,7 +39,6 @@
#include "bulletproofs_plus.h"
#include "cryptonote_config.h"
#include "device/device.hpp"
-#include "fcmp_pp/fcmp_pp_crypto.h"
#include "scope_guard.h"
#include "serialization/crypto.h"
@@ -1603,7 +1602,7 @@ namespace rct {
{
const crypto::ec_point &point = rct::rct2pt(pts[i]);
crypto::ec_point torsion_cleared_point;
- if (fcmp_pp::get_valid_torsion_cleared_point_vartime(point, torsion_cleared_point)
+ if (crypto::get_valid_torsion_cleared_point_vartime(point, torsion_cleared_point)
&& point == torsion_cleared_point)
{
// Point is torsion free if it's equal to itself after clearing torsion
### tests/unit_tests/crypto.cpp
@@ -457,13 +457,13 @@ TEST(Crypto, fe_equals)
TEST(Crypto, ec_constants_rct_parity)
{
- ASSERT_TRUE(memcmp(&fcmp_pp::EC_I, &rct::I, 32) == 0);
- ASSERT_TRUE(memcmp(&fcmp_pp::EC_INV_EIGHT, &rct::INV_EIGHT, 32) == 0);
+ ASSERT_TRUE(memcmp(&crypto::EC_I, &rct::I, 32) == 0);
+ ASSERT_TRUE(memcmp(sc_inv_eight, &rct::INV_EIGHT, 32) == 0);
}
#define CHECK_CLEARED(k, cleared) \
crypto::ec_point cleared2; \
- const bool r = fcmp_pp::get_valid_torsion_cleared_point_vartime(rct::rct2pt(k), cleared2); \
+ const bool r = crypto::get_valid_torsion_cleared_point_vartime(rct::rct2pt(k), cleared2); \
ASSERT_TRUE(r); \
ASSERT_EQ(cleared, cleared2);
@@ -477,7 +477,8 @@ TEST(Crypto, torsion_check_pass_random)
ASSERT_EQ(ge_frombytes_vartime(&x, pk.bytes), 0);
ASSERT_TRUE(rct::isInMainSubgroup(pk));
ASSERT_FALSE(fcmp_pp::mul8_is_identity_vartime(x));
- const crypto::ec_point cleared = fcmp_pp::clear_torsion_vartime(x);
+ crypto::ec_point cleared;
+ ge_clear_torsion_vartime(to_bytes(cleared), &x);
ASSERT_EQ(rct::rct2pt(pk), cleared);
CHECK_CLEARED(pk, cleared);
pts.emplace_back(pk);
@@ -504,7 +505,8 @@ TEST(Crypto, torsion_check_hardcoded)
ASSERT_EQ(ge_frombytes_vartime(&x, k.bytes), 0);
ASSERT_EQ(rct::isInMainSubgroup(k), point.torsion_free);
ASSERT_FALSE(fcmp_pp::mul8_is_identity_vartime(x));
- const crypto::ec_point cleared = fcmp_pp::clear_torsion_vartime(x);
+ crypto::ec_point cleared;
+ ge_clear_torsion_vartime(to_bytes(cleared), &x);
if (point.torsion_free)
{
ASSERT_EQ(rct::rct2pt(k), cleared);
@@ -540,7 +542,7 @@ TEST(Crypto, mul8_is_identity_vartime)
ASSERT_TRUE(fcmp_pp::mul8_is_identity_vartime(x));
crypto::ec_point _;
- ASSERT_FALSE(fcmp_pp::get_valid_torsion_cleared_point_vartime(rct::rct2pt(point), _));
+ ASSERT_FALSE(crypto::get_valid_torsion_cleared_point_vartime(rct::rct2pt(point), _));
}
}
Why this scored 25/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.