What changed, and why it matters
This commit adds a new cryptographic safety check for Monero's upcoming FCMP++ (Full Chain Membership Proof) system. It makes sure that certain elliptic-curve points used in transactions are free of small 'torsion' components—mathematical artifacts that can be exploited in some zero-knowledge proof attacks. The patch introduces helper functions to clear torsion and a new validation routine, plus tests. It does not by itself fix a known exploitable bug in live code; it is a defensive hardening measure for a feature that is still being integrated.
Treat as a defensive cryptographic hardening commit for FCMP++. Review that all FCMP++ input points are routed through verPointsForTorsion or equivalent clearing before proof verification, and ensure the vartime helpers are not used on attacker-controlled secret data in timing-sensitive contexts. Continue monitoring for follow-up commits that actually invoke this routine in transaction validation.
Security signals we found
Adds torsion-clearing validation for elliptic-curve points
Introduces subgroup/torsion membership check before FCMP++ usage
Rejects identity point after torsion clearing
Parallelized verification via threadpool
Adds unit tests for torsion-free vs torsion-laden hardcoded points
Evidence from the diff
The change creates src/fcmp_pp/fcmp_pp_crypto.cpp/h with Ed25519 group operations to: (1) test whether 8*P is the identity (mul8_is_identity_vartime), (2) clear the 8-torsion component by multiplying by inv(8) then 8 (clear_torsion_vartime), and (3) validate and decode a point after torsion clearing (get_valid_torsion_cleared_point_vartime). It wires a new rct::verPointsForTorsion() parallel checker into ringct/rctSigs.cpp and adds type-cast helpers between crypto::ec_point and rct::key. Unit tests verify parity with existing rct constants and that torsion-free points pass while torsion-laden points fail. The commit message frames this as ‘torsion clearing for FCMP++’, implying it is preparation/protection for the FCMP++ protocol rather than a standalone vulnerability fix.
Changed components
src/fcmp_pp/fcmp_pp_crypto.cppsrc/fcmp_pp/fcmp_pp_crypto.hsrc/ringct/rctSigs.cppsrc/ringct/rctSigs.hsrc/ringct/rctTypes.hsrc/crypto/crypto.htests/unit_tests/crypto.cppInspect captured patch +314 / −4
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 85116ae..8217a2c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -83,6 +83,7 @@ monero_add_library(version SOURCES ${CMAKE_BINARY_DIR}/version.cpp DEPENDS genve
add_subdirectory(common)
add_subdirectory(crypto)
+add_subdirectory(fcmp_pp)
add_subdirectory(ringct)
add_subdirectory(checkpoints)
add_subdirectory(cryptonote_basic)
diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h
index 1145192..4a6b20a 100644
--- a/src/crypto/crypto.h
+++ b/src/crypto/crypto.h
@@ -320,6 +320,9 @@ namespace crypto {
inline std::ostream &operator <<(std::ostream &o, const crypto::view_tag &v) {
epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
}
+ inline std::ostream &operator <<(std::ostream &o, const crypto::ec_point &v) {
+ epee::to_hex::formatted(o, epee::as_byte_span(v)); return o;
+ }
const extern crypto::public_key null_pkey;
const extern crypto::secret_key null_skey;
@@ -336,6 +339,7 @@ inline const unsigned char* to_bytes(const crypto::ec_scalar &scalar) { return &
inline unsigned char* to_bytes(crypto::ec_point &point) { return &reinterpret_cast<unsigned char&>(point); }
inline const unsigned char* to_bytes(const crypto::ec_point &point) { return &reinterpret_cast<const unsigned char&>(point); }
+CRYPTO_MAKE_HASHABLE(ec_point)
CRYPTO_MAKE_HASHABLE(public_key)
CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(secret_key)
CRYPTO_MAKE_HASHABLE_CONSTANT_TIME(public_key_memsafe)
diff --git a/src/fcmp_pp/CMakeLists.txt b/src/fcmp_pp/CMakeLists.txt
new file mode 100644
index 0000000..32ca1b4
--- /dev/null
+++ b/src/fcmp_pp/CMakeLists.txt
@@ -0,0 +1,42 @@
+# Copyright (c) 2024, The Monero Project
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification, are
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other
+# materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
+# used to endorse or promote products derived from this software without specific
+# prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+set(fcmp_pp_sources
+ fcmp_pp_crypto.cpp)
+
+monero_find_all_headers(fcmp_pp_headers "${CMAKE_CURRENT_SOURCE_DIR}")
+
+monero_add_library(fcmp_pp
+ ${fcmp_pp_sources}
+ ${fcmp_pp_headers})
+
+target_link_libraries(fcmp_pp
+ PUBLIC
+ cncrypto
+ PRIVATE
+ ${EXTRA_LIBRARIES})
diff --git a/src/fcmp_pp/fcmp_pp_crypto.cpp b/src/fcmp_pp/fcmp_pp_crypto.cpp
new file mode 100644
index 0000000..39b95db
--- /dev/null
+++ b/src/fcmp_pp/fcmp_pp_crypto.cpp
@@ -0,0 +1,68 @@
+// Copyright (c) 2024, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "fcmp_pp_crypto.h"
+
+namespace fcmp_pp
+{
+//----------------------------------------------------------------------------------------------------------------------
+bool mul8_is_identity_vartime(const ge_p3 &point) {
+ ge_p2 point_ge_p2;
+ ge_p3_to_p2(&point_ge_p2, &point);
+ ge_p1p1 point_mul8;
+ ge_mul8(&point_mul8, &point_ge_p2);
+ ge_p3 point_mul8_p3;
+ ge_p1p1_to_p3(&point_mul8_p3, &point_mul8);
+ 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;
+}
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+}//namespace fcmp_pp
diff --git a/src/fcmp_pp/fcmp_pp_crypto.h b/src/fcmp_pp/fcmp_pp_crypto.h
new file mode 100644
index 0000000..d1d2b1b
--- /dev/null
+++ b/src/fcmp_pp/fcmp_pp_crypto.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2024, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+extern "C"
+{
+#include "crypto/crypto-ops.h"
+}
+#include "crypto/crypto.h"
+
+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))
+ }};
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+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);
+//----------------------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------------------
+}//namespace fcmp_pp
diff --git a/src/ringct/CMakeLists.txt b/src/ringct/CMakeLists.txt
index 8a6ed9f..c1a6d14 100644
--- a/src/ringct/CMakeLists.txt
+++ b/src/ringct/CMakeLists.txt
@@ -70,6 +70,7 @@ target_link_libraries(ringct
common
cncrypto
device
+ fcmp_pp
PRIVATE
${OPENSSL_LIBRARIES}
${EXTRA_LIBRARIES})
diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp
index 6a2e32a..90e3c3a 100644
--- a/src/ringct/rctSigs.cpp
+++ b/src/ringct/rctSigs.cpp
@@ -30,6 +30,8 @@
#include "rctSigs.h"
+#include <atomic>
+
#include "misc_log_ex.h"
#include "misc_language.h"
#include "common/perf_timer.h"
@@ -39,6 +41,7 @@
#include "bulletproofs_plus.h"
#include "cryptonote_config.h"
#include "device/device.hpp"
+#include "fcmp_pp/fcmp_pp_crypto.h"
#include "serialization/crypto.h"
using namespace crypto;
@@ -1574,4 +1577,33 @@ namespace rct {
key mask;
return decodeRct(rv, sk, i, mask, hwdev);
}
+
+ bool verPointsForTorsion(const std::vector<key> & pts) {
+ if (pts.empty())
+ return true;
+
+ tools::threadpool& tpool = tools::threadpool::getInstanceForCompute();
+ tools::threadpool::waiter waiter(tpool);
+
+ std::atomic<bool> all_valid{true};
+ for (std::size_t i = 0; i < pts.size(); ++i)
+ {
+ tpool.submit(&waiter, [&pts, &all_valid, i]
+ {
+ 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)
+ && point == torsion_cleared_point)
+ {
+ // Point is torsion free if it's equal to itself after clearing torsion
+ return;
+ }
+ all_valid.store(false);
+ });
+ }
+
+ CHECK_AND_ASSERT_MES(waiter.wait(), false, "threadpool waiter failed in torsion check");
+ CHECK_AND_ASSERT_MES(all_valid.load(), false, "Torsion check failed");
+ return true;
+ }
}
diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h
index 6cc7e74..3b9fe09 100644
--- a/src/ringct/rctSigs.h
+++ b/src/ringct/rctSigs.h
@@ -136,6 +136,9 @@ namespace rct {
xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i, key & mask, hw::device &hwdev);
xmr_amount decodeRct(const rctSig & rv, const key & sk, unsigned int i, hw::device &hwdev);
key get_pre_mlsag_hash(const rctSig &rv, hw::device &hwdev);
+
+ // Make sure points are valid points, don't have torsion, and are not equal to identity
+ bool verPointsForTorsion(const std::vector<key> & pts);
}
#endif /* RCTSIGS_H */
diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h
index 038c2e1..608aa5b 100644
--- a/src/ringct/rctTypes.h
+++ b/src/ringct/rctTypes.h
@@ -741,10 +741,12 @@ namespace rct {
static inline const rct::key &sk2rct(const crypto::secret_key &sk) { return (const rct::key&)sk; }
static inline const rct::key &ki2rct(const crypto::key_image &ki) { return (const rct::key&)ki; }
static inline const rct::key &hash2rct(const crypto::hash &h) { return (const rct::key&)h; }
+ static inline const rct::key &pt2rct(const crypto::ec_point &pt) { return (const rct::key&)pt; }
static inline const crypto::public_key &rct2pk(const rct::key &k) { return (const crypto::public_key&)k; }
static inline const crypto::secret_key &rct2sk(const rct::key &k) { return (const crypto::secret_key&)k; }
static inline const crypto::key_image &rct2ki(const rct::key &k) { return (const crypto::key_image&)k; }
static inline const crypto::hash &rct2hash(const rct::key &k) { return (const crypto::hash&)k; }
+ static inline const crypto::ec_point &rct2pt(const rct::key &k) { return (const crypto::ec_point&)k; }
static inline bool operator==(const rct::key &k0, const crypto::public_key &k1) { return !crypto_verify_32(k0.bytes, (const unsigned char*)&k1); }
static inline bool operator!=(const rct::key &k0, const crypto::public_key &k1) { return crypto_verify_32(k0.bytes, (const unsigned char*)&k1); }
}
diff --git a/tests/crypto/main.cpp b/tests/crypto/main.cpp
index 668c04e..913f8ab 100644
--- a/tests/crypto/main.cpp
+++ b/tests/crypto/main.cpp
@@ -49,10 +49,6 @@ bool operator !=(const ec_scalar &a, const ec_scalar &b) {
return 0 != memcmp(&a, &b, sizeof(ec_scalar));
}
-bool operator !=(const ec_point &a, const ec_point &b) {
- return 0 != memcmp(&a, &b, sizeof(ec_point));
-}
-
bool operator !=(const key_derivation &a, const key_derivation &b) {
return 0 != memcmp(&a, &b, sizeof(key_derivation));
}
diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt
index dc30a24..00e4259 100644
--- a/tests/unit_tests/CMakeLists.txt
+++ b/tests/unit_tests/CMakeLists.txt
@@ -121,6 +121,7 @@ target_link_libraries(unit_tests
daemon_messages
daemon_rpc_server
blockchain_db
+ fcmp_pp
lmdb_lib
rpc
net
diff --git a/tests/unit_tests/crypto.cpp b/tests/unit_tests/crypto.cpp
index 8583185..2cd260c 100644
--- a/tests/unit_tests/crypto.cpp
+++ b/tests/unit_tests/crypto.cpp
@@ -39,8 +39,11 @@ extern "C"
}
#include "crypto/generators.h"
#include "cryptonote_basic/merge_mining.h"
+#include "fcmp_pp/fcmp_pp_crypto.h"
#include "ringct/rctOps.h"
+#include "ringct/rctSigs.h"
#include "ringct/rctTypes.h"
+#include "string_tools.h"
namespace
{
@@ -429,3 +432,92 @@ TEST(Crypto, fe_equals)
ASSERT_NE(memcmp(fe_d2_reduced, fe_d2, sizeof(fe)), 0);
ASSERT_EQ(fe_equals(fe_d2_reduced, fe_d2), 1);
}
+
+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);
+}
+
+#define CHECK_CLEARED(k, cleared) \
+ crypto::ec_point cleared2; \
+ const bool r = fcmp_pp::get_valid_torsion_cleared_point_vartime(rct::rct2pt(k), cleared2); \
+ ASSERT_TRUE(r); \
+ ASSERT_EQ(cleared, cleared2);
+
+TEST(Crypto, torsion_check_pass_random)
+{
+ std::vector<rct::key> pts;
+ for (int i = 0; i < 1000; ++i)
+ {
+ const rct::key pk = rct::pkGen();
+ ge_p3 x;
+ 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);
+ ASSERT_EQ(rct::rct2pt(pk), cleared);
+ CHECK_CLEARED(pk, cleared);
+ pts.emplace_back(pk);
+ }
+ ASSERT_TRUE(rct::verPointsForTorsion(pts));
+}
+
+TEST(Crypto, torsion_check_hardcoded)
+{
+ struct TorsionTestPoints { std::string point; bool torsion_free; };
+ static const std::vector<TorsionTestPoints> torsion_test_points = {
+ {"b10ba13e303cbe9abf7d5d44f1d417727abcc14903a74e071abd652ce1bf76dd", false},
+ {"9b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd088071", false}, // genesis (see config::GENESIS_TX)
+ {"785eda585dca4f3d27976106008ccfbca13146c8b21b8c7e4909032639a776e1", true},
+ {"9a7b10563aa266032cd075f4e347f348a3841ae4f41572633351a97dd44066b4", true},
+ };
+
+ std::vector<rct::key> all_pts, torsion_free_pts, torsioned_pts, torsion_cleared_pts;
+ for (const auto &point : torsion_test_points)
+ {
+ rct::key k;
+ epee::string_tools::hex_to_pod(point.point, k);
+ ge_p3 x;
+ 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);
+ if (point.torsion_free)
+ {
+ ASSERT_EQ(rct::rct2pt(k), cleared);
+ torsion_free_pts.push_back(k);
+ }
+ else
+ {
+ ASSERT_NE(rct::rct2pt(k), cleared);
+ torsioned_pts.push_back(k);
+ }
+ CHECK_CLEARED(k, cleared);
+ all_pts.emplace_back(k);
+ torsion_cleared_pts.emplace_back(rct::pt2rct(cleared));
+ }
+
+ ASSERT_TRUE(rct::verPointsForTorsion(torsion_free_pts));
+ ASSERT_FALSE(rct::verPointsForTorsion(torsioned_pts));
+ ASSERT_FALSE(rct::verPointsForTorsion(all_pts));
+ ASSERT_TRUE(rct::verPointsForTorsion(torsion_cleared_pts));
+}
+
+TEST(Crypto, mul8_is_identity_vartime)
+{
+ static const std::vector<rct::key> mul8_identity_points = {
+ rct::I,
+ rct::Z
+ };
+
+ for (const auto &point : mul8_identity_points)
+ {
+ ge_p3 x;
+ ASSERT_EQ(ge_frombytes_vartime(&x, point.bytes), 0);
+ 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), _));
+ }
+}
Why this scored 67/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.