What changed, and why it matters
This Monero update hardens how public keys and transaction pubkeys are handled. It moves a low-level 'torsion clearing' routine into the core crypto library, adds checks that wallet/destination addresses are valid points on the main subgroup of the curve, and normalizes transaction public keys before they are used for payment-ID decryption, transaction proofs, and hardware-wallet communication. The changes reduce the risk that malformed or small-subgroup public keys could be used to confuse wallet logic, leak information, or cause inconsistent behavior. The commit message frames it as a fix for 'inconsistent tx pubkey handling' and a crypto cleanup, not as an active-exploit patch.
Treat as a security-hardening fix and include in the next release. Users running nodes/wallets from source should update to a build containing this commit. Review whether any downstream tools or RPC consumers parse transaction pubkeys independently and may need similar normalization. No immediate emergency response is indicated by the commit materials alone, but the changes address real cryptographic hygiene issues.
Security signals we found
Adds main-subgroup membership validation for public address keys (spend/view)
Normalizes transaction public keys before use in payment-ID decryption and tx proofs
Moves torsion-clearing primitive into core crypto layer to ensure consistent behavior
Fixes index bounds check when serializing additional tx pubkeys to Trezor
Replaces ad-hoc subgroup checks with centralized check_address() helper
Changes wallet logic to rely on recorded tx pubkey index rather than scanning all pubkeys
Evidence from the diff
The merge refactors Ed25519 torsion clearing from fcmp_pp into src/crypto, exposing ge_clear_torsion_vartime, get_valid_torsion_cleared_point_vartime, and pubkey_clear_torsion. It then uses these in wallet2.cpp for decrypt_payment_id, get_tx_proof, check_tx_proof, get_reserve_proof, check_reserve_proof, and get_tx_pub_key_from_received_outs; in trezor/protocol.cpp when serializing tx pubkeys; in cryptonote_tx_utils.cpp to validate destination addresses; in cryptonote_basic_impl.cpp via a new check_address() helper that rejects non-main-subgroup spend/view keys; and in pending_tx_validation.cpp replacing direct rct::isInMainSubgroup checks. The patch also fixes an out-of-bounds access in trezor/protocol.cpp (real_output_in_tx_index vs. additional tx keys) and tightens a size check in check_tx_key_helper. No CVE or vendor advisory is present in the supplied materials.
Changed components
src/crypto/crypto-ops-data.csrc/crypto/crypto-ops.csrc/crypto/crypto-ops.hsrc/crypto/crypto.cppsrc/crypto/crypto.hsrc/cryptonote_basic/cryptonote_basic_impl.cppsrc/cryptonote_basic/cryptonote_basic_impl.hsrc/cryptonote_core/cryptonote_tx_utils.cppsrc/device_trezor/trezor/protocol.cppsrc/fcmp_pp/curve_trees.cppsrc/fcmp_pp/fcmp_pp_crypto.cppsrc/fcmp_pp/fcmp_pp_crypto.hsrc/ringct/rctSigs.cppsrc/wallet/pending_tx_validation.cppsrc/wallet/wallet2.cpptests/unit_tests/crypto.cppInspect captured patch +178 / −150
### 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,23 @@ 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;
+ }
+
+ public_key pubkey_clear_torsion(const public_key &pubkey) {
+ public_key result;
+ if (!get_valid_torsion_cleared_point_vartime(pubkey, result))
+ static_cast<ec_point &>(result) = EC_I;
+ return result;
+ }
+
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,10 @@ namespace crypto {
return crypto_ops::check_key(key);
}
+ bool get_valid_torsion_cleared_point_vartime(const ec_point &point, ec_point &torsion_cleared_out);
+ // Return a canonical key in the prime-order subgroup, or identity on decode failure.
+ public_key pubkey_clear_torsion(const public_key &pubkey);
+
/* 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/cryptonote_basic/cryptonote_basic_impl.cpp
@@ -35,6 +35,7 @@
#include "cryptonote_config.h"
#include "common/base58.h"
#include "crypto/hash.h"
+#include "ringct/rctOps.h"
#include "int-util.h"
#include "common/dns_utils.h"
@@ -150,6 +151,15 @@ namespace cryptonote {
return tools::base58::encode_addr(integrated_address_prefix, t_serializable_object_to_blob(iadr));
}
//-----------------------------------------------------------------------
+ bool check_address(const account_public_address& adr)
+ {
+ const auto valid_key = [](const crypto::public_key& key) {
+ const rct::key point = rct::pk2rct(key);
+ return point != rct::identity() && rct::isInMainSubgroup(point);
+ };
+ return valid_key(adr.m_spend_public_key) && valid_key(adr.m_view_public_key);
+ }
+
bool get_account_address_from_str(
address_parse_info& info
, network_type nettype
@@ -210,7 +220,7 @@ namespace cryptonote {
}
}
- if (!crypto::check_key(info.address.m_spend_public_key) || !crypto::check_key(info.address.m_view_public_key))
+ if (!check_address(info.address))
{
LOG_PRINT_L1("Failed to validate address keys");
return false;
### src/cryptonote_basic/cryptonote_basic_impl.h
@@ -76,6 +76,8 @@ namespace cryptonote {
, const crypto::hash8& payment_id
);
+ bool check_address(const account_public_address& adr);
+
bool get_account_address_from_str(
address_parse_info& info
, network_type nettype
### src/cryptonote_core/cryptonote_tx_utils.cpp
@@ -250,6 +250,9 @@ namespace cryptonote
return false;
}
+ for (const tx_destination_entry& destination : destinations)
+ CHECK_AND_ASSERT_MES(check_address(destination.addr), false, "Invalid destination address keys");
+
std::optional<cryptonote::subaddress_index> recognized_change_index;
if (change_addr)
recognized_change_index = sanity_check_change_address(*change_addr, subaddresses, sender_account_keys);
### src/device_trezor/trezor/protocol.cpp
@@ -155,12 +155,12 @@ namespace ki {
res.emplace_back();
auto & cres = res.back();
cres.set_out_key(key_to_string(td.get_public_key()));
- cres.set_tx_pub_key(key_to_string(tx_pub_key));
+ cres.set_tx_pub_key(key_to_string(::crypto::pubkey_clear_torsion(tx_pub_key)));
cres.set_internal_output_index(td.m_internal_output_index);
cres.set_sub_addr_major(td.m_subaddr_index.major);
cres.set_sub_addr_minor(td.m_subaddr_index.minor);
if (!additional_tx_pub_keys.empty() && additional_tx_pub_keys.size() > td.m_internal_output_index) {
- cres.add_additional_tx_pub_keys(key_to_string(additional_tx_pub_keys[td.m_internal_output_index]));
+ cres.add_additional_tx_pub_keys(key_to_string(::crypto::pubkey_clear_torsion(additional_tx_pub_keys[td.m_internal_output_index])));
}
}
@@ -456,10 +456,10 @@ namespace tx {
}
}
- dst->set_real_out_tx_key(key_to_string(src.real_out_tx_key));
+ dst->set_real_out_tx_key(key_to_string(::crypto::pubkey_clear_torsion(src.real_out_tx_key)));
dst->set_real_output_in_tx_index(src.real_output_in_tx_index);
- if (!src.real_out_additional_tx_keys.empty()) {
- dst->add_real_out_additional_tx_keys(key_to_string(src.real_out_additional_tx_keys.at(src.real_output_in_tx_index)));
+ if (src.real_output_in_tx_index < src.real_out_additional_tx_keys.size()) {
+ dst->add_real_out_additional_tx_keys(key_to_string(::crypto::pubkey_clear_torsion(src.real_out_additional_tx_keys.at(src.real_output_in_tx_index))));
}
dst->set_amount(src.amount);
dst->set_rct(src.rct);
### 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
Weierstrass 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 converts to Weierstras
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
### src/wallet/pending_tx_validation.cpp
@@ -800,10 +800,8 @@ void sanity_check_pending_tx(const wallet2::pending_tx &ptx,
// Check that addresses keys are canonical.
// Without this check, the following duplicate address check may be weakened.
- CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(dest.addr.m_spend_public_key)),
- "sanity_check_pending_tx: destination spendkey is not in the main subgroup (suspicious!)");
- CHECK_AND_ASSERT_THROW_MES(rct::isInMainSubgroup(rct::pk2rct(dest.addr.m_view_public_key)),
- "sanity_check_pending_tx: destination viewkey is not in the main subgroup (suspicious!)");
+ CHECK_AND_ASSERT_THROW_MES(cryptonote::check_address(dest.addr),
+ "sanity_check_pending_tx: invalid destination address keys");
// Check that duplicate addresses are all either subaddresses or normal addresses (ignoring is_integrated).
const auto &dup = dest_duplicates.find(dest.addr.m_spend_public_key);
### src/wallet/wallet2.cpp
@@ -2602,24 +2602,17 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
// We got a payment ID to go with this tx
LOG_PRINT_L2("Found encrypted payment ID: " << payment_id8);
MINFO("Consider using subaddresses instead of encrypted payment IDs");
- if (tx_pub_key != null_pkey)
+ if (!m_account.get_device().decrypt_payment_id(payment_id8, crypto::pubkey_clear_torsion(tx_pub_key), m_account.get_keys().m_view_secret_key))
{
- if (!m_account.get_device().decrypt_payment_id(payment_id8, tx_pub_key, m_account.get_keys().m_view_secret_key))
- {
- LOG_PRINT_L0("Failed to decrypt payment ID: " << payment_id8);
- }
- else
- {
- LOG_PRINT_L2("Decrypted payment ID: " << payment_id8);
- // put the 64 bit decrypted payment id in the first 8 bytes
- memcpy(payment_id.data, payment_id8.data, 8);
- // rest is already 0, but guard against code changes above
- memset(payment_id.data + 8, 0, 24);
- }
+ LOG_PRINT_L0("Failed to decrypt payment ID: " << payment_id8);
}
else
{
- LOG_PRINT_L1("No public key found in tx, unable to decrypt payment id");
+ LOG_PRINT_L2("Decrypted payment ID: " << payment_id8);
+ // put the 64 bit decrypted payment id in the first 8 bytes
+ memcpy(payment_id.data, payment_id8.data, 8);
+ // rest is already 0, but guard against code changes above
+ memset(payment_id.data + 8, 0, 24);
}
}
else if (get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id))
@@ -12400,7 +12393,7 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de
THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error,
"Failed to get the right transaction from daemon");
- THROW_WALLET_EXCEPTION_IF(!additional_derivations.empty() && additional_derivations.size() != tx.vout.size(), error::wallet_internal_error,
+ THROW_WALLET_EXCEPTION_IF(additional_derivations.size() > tx.vout.size(), error::wallet_internal_error,
"The size of additional derivations is wrong");
check_tx_key_helper(tx, derivation, additional_derivations, address, received);
@@ -12562,17 +12555,50 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt
}
else
{
- crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx);
- THROW_WALLET_EXCEPTION_IF(tx_pub_key == null_pkey, error::wallet_internal_error, "Tx pubkey was not found");
+ std::vector<tx_extra_field> tx_extra_fields;
+ parse_tx_extra(tx.extra, tx_extra_fields);
+ tx_extra_pub_key pub_key_field;
+ THROW_WALLET_EXCEPTION_IF(!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field),
+ error::wallet_internal_error, "Tx pubkey was not found");
+ crypto::public_key tx_pub_key = crypto::pubkey_clear_torsion(pub_key_field.pub_key);
std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx);
+ for (crypto::public_key &key : additional_tx_pub_keys)
+ key = crypto::pubkey_clear_torsion(key);
const size_t num_sigs = 1 + additional_tx_pub_keys.size();
shared_secret.resize(num_sigs);
sig.resize(num_sigs);
const crypto::secret_key& a = m_account.get_keys().m_view_secret_key;
hwdev.scalarmultKey(aP, rct::pk2rct(tx_pub_key), rct::sk2rct(a));
shared_secret[0] = rct2pk(aP);
+
+ const auto has_received = [&](const crypto::public_key &candidate_shared_secret) {
+ crypto::key_derivation derivation;
+ THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(candidate_shared_secret, rct::rct2sk(rct::I), derivation),
+ error::wallet_internal_error, "Failed to generate key derivation");
+ uint64_t received{0};
+ check_tx_key_helper(tx, derivation, {}, address, received);
+ return received > 0;
+ };
+
+ size_t pk_index = 1;
+ if (find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, pk_index) && !has_received(shared_secret[0]))
+ {
+ do
+ {
+ const crypto::public_key candidate = crypto::pubkey_clear_torsion(pub_key_field.pub_key);
+ hwdev.scalarmultKey(aP, rct::pk2rct(candidate), rct::sk2rct(a));
+ const crypto::public_key candidate_shared_secret = rct::rct2pk(aP);
+ if (has_received(candidate_shared_secret))
+ {
+ tx_pub_key = candidate;
+ shared_secret[0] = candidate_shared_secret;
+ break;
+ }
+ } while (find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, ++pk_index));
+ }
+
if (is_subaddress)
{
hwdev.generate_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], a, sig[0]);
@@ -12695,10 +12721,16 @@ bool wallet2::check_tx_proof(const cryptonote::transaction &tx, const cryptonote
memcpy(&sig[i], sig_decoded.data(), sizeof(crypto::signature));
}
- crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx);
- THROW_WALLET_EXCEPTION_IF(tx_pub_key == null_pkey, error::wallet_internal_error, "Tx pubkey was not found");
+ std::vector<tx_extra_field> tx_extra_fields;
+ parse_tx_extra(tx.extra, tx_extra_fields);
+ tx_extra_pub_key pub_key_field;
+ THROW_WALLET_EXCEPTION_IF(!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field),
+ error::wallet_internal_error, "Tx pubkey was not found");
std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx);
+ if (!is_out)
+ for (crypto::public_key &key : additional_tx_pub_keys)
+ key = crypto::pubkey_clear_torsion(key);
THROW_WALLET_EXCEPTION_IF(additional_tx_pub_keys.size() + 1 != num_sigs, error::wallet_internal_error, "Signature size mismatch with additional tx pubkeys");
const crypto::hash txid = cryptonote::get_transaction_hash(tx);
@@ -12709,12 +12741,31 @@ bool wallet2::check_tx_proof(const cryptonote::transaction &tx, const cryptonote
// check signature
std::vector<int> good_signature(num_sigs, 0);
- if (is_out)
+ size_t pk_index = 0;
+ bool has_tx_pub_key = false;
+ while (!good_signature[0] && find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, pk_index++))
{
- good_signature[0] = is_subaddress ?
- crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], sig[0], version) :
- crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], sig[0], version);
+ const crypto::public_key tx_pub_key = is_out ? pub_key_field.pub_key : crypto::pubkey_clear_torsion(pub_key_field.pub_key);
+ if (is_out && tx_pub_key == null_pkey)
+ continue;
+ has_tx_pub_key = true;
+ if (is_out)
+ {
+ good_signature[0] = is_subaddress ?
+ crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], sig[0], version) :
+ crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], sig[0], version);
+ }
+ else
+ {
+ good_signature[0] = is_subaddress ?
+ crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], sig[0], version) :
+ crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], sig[0], version);
+ }
+ }
+ THROW_WALLET_EXCEPTION_IF(!has_tx_pub_key, error::wallet_internal_error, "Tx pubkey was not found");
+ if (is_out)
+ {
for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
{
good_signature[i + 1] = is_subaddress ?
@@ -12724,10 +12775,6 @@ bool wallet2::check_tx_proof(const cryptonote::transaction &tx, const cryptonote
}
else
{
- good_signature[0] = is_subaddress ?
- crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], sig[0], version) :
- crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], sig[0], version);
-
for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
{
good_signature[i + 1] = is_subaddress ?
@@ -12820,16 +12867,20 @@ std::string wallet2::get_reserve_proof(const boost::optional<std::pair<uint32_t,
proof.key_image = td.m_key_image;
subaddr_indices.insert(td.m_subaddr_index);
- // get tx pub key
- const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(td.m_tx, td.m_pk_index);
- THROW_WALLET_EXCEPTION_IF(tx_pub_key == crypto::null_pkey, error::wallet_internal_error, "The tx public key isn't found");
+ // get tx pub key
+ std::vector<tx_extra_field> tx_extra_fields;
+ parse_tx_extra(td.m_tx.extra, tx_extra_fields);
+ tx_extra_pub_key pub_key_field;
+ THROW_WALLET_EXCEPTION_IF(!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, td.m_pk_index),
+ error::wallet_internal_error, "The tx public key isn't found");
+ const crypto::public_key tx_pub_key = pub_key_field.pub_key;
const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
// determine which tx pub key was used for deriving the output key
- const crypto::public_key *tx_pub_key_used = &tx_pub_key;
+ crypto::public_key tx_pub_key_used = crypto::pubkey_clear_torsion(tx_pub_key);
for (int i = 0; i < 2; ++i)
{
- proof.shared_secret = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(*tx_pub_key_used), rct::sk2rct(m_account.get_keys().m_view_secret_key)));
+ proof.shared_secret = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(tx_pub_key_used), rct::sk2rct(m_account.get_keys().m_view_secret_key)));
crypto::key_derivation derivation;
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(proof.shared_secret, rct::rct2sk(rct::I), derivation),
error::wallet_internal_error, "Failed to generate key derivation");
@@ -12842,11 +12893,11 @@ std::string wallet2::get_reserve_proof(const boost::optional<std::pair<uint32_t,
"Normal tx pub key doesn't derive the expected output, and no additional tx pub key exists for this output index");
THROW_WALLET_EXCEPTION_IF(i == 1, error::wallet_internal_error,
"Neither normal tx pub key nor additional tx pub key derive the expected output key");
- tx_pub_key_used = &additional_tx_pub_keys[proof.index_in_tx];
+ tx_pub_key_used = crypto::pubkey_clear_torsion(additional_tx_pub_keys[proof.index_in_tx]);
}
// generate signature for shared secret
- crypto::generate_tx_proof(prefix_hash, m_account.get_keys().m_account_address.m_view_public_key, *tx_pub_key_used, boost::none, proof.shared_secret, m_account.get_keys().m_view_secret_key, proof.shared_secret_sig);
+ crypto::generate_tx_proof(prefix_hash, m_account.get_keys().m_account_address.m_view_public_key, tx_pub_key_used, boost::none, proof.shared_secret, m_account.get_keys().m_view_secret_key, proof.shared_secret_sig);
// derive ephemeral secret key
crypto::key_image ki;
@@ -12990,14 +13041,19 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr
THROW_WALLET_EXCEPTION_IF(!get_output_public_key(tx.vout[proof.index_in_tx], output_public_key), error::wallet_internal_error, "Output key wasn't found");
// get tx pub key
- const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx);
- THROW_WALLET_EXCEPTION_IF(tx_pub_key == crypto::null_pkey, error::wallet_internal_error, "The tx public key isn't found");
+ std::vector<tx_extra_field> tx_extra_fields;
+ parse_tx_extra(tx.extra, tx_extra_fields);
+ tx_extra_pub_key tx_pub_key;
+ THROW_WALLET_EXCEPTION_IF(!find_tx_extra_field_by_type(tx_extra_fields, tx_pub_key), error::wallet_internal_error, "The tx public key isn't found");
const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx);
// check signature for shared secret
- ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, proof.shared_secret, proof.shared_secret_sig, version);
- if (!ok && additional_tx_pub_keys.size() == tx.vout.size())
- ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[proof.index_in_tx], boost::none, proof.shared_secret, proof.shared_secret_sig, version);
+ ok = false;
+ size_t tx_pub_key_index = 0;
+ while (!ok && find_tx_extra_field_by_type(tx_extra_fields, tx_pub_key, tx_pub_key_index++))
+ ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, crypto::pubkey_clear_torsion(tx_pub_key.pub_key), boost::none, proof.shared_secret, proof.shared_secret_sig, version);
+ if (!ok && proof.index_in_tx < additional_tx_pub_keys.size())
+ ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, crypto::pubkey_clear_torsion(additional_tx_pub_keys[proof.index_in_tx]), boost::none, proof.shared_secret, proof.shared_secret_sig, version);
if (!ok)
return false;
@@ -13403,44 +13459,11 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle
// Extra may only be partially parsed, it's OK if tx_extra_fields contains public key
}
- // Due to a previous bug, there might be more than one tx pubkey in extra, one being
- // the result of a previously discarded signature.
- // For speed, since scanning for outputs is a slow process, we check whether extra
- // contains more than one pubkey. If not, the first one is returned. If yes, they're
- // checked for whether they yield at least one output
+ // Use the tx pubkey index recorded for this output.
tx_extra_pub_key pub_key_field;
- THROW_WALLET_EXCEPTION_IF(!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, 0), error::wallet_internal_error,
+ THROW_WALLET_EXCEPTION_IF(!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, td.m_pk_index), error::wallet_internal_error,
"Public key wasn't found in the transaction extra");
- const crypto::public_key tx_pub_key = pub_key_field.pub_key;
- bool two_found = find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, 1);
- if (!two_found) {
- // easy case, just one found
- return tx_pub_key;
- }
-
- // more than one, loop and search
- const cryptonote::account_keys& keys = m_account.get_keys();
- size_t pk_index = 0;
- hw::device &hwdev = m_account.get_device();
-
- while (find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, pk_index++)) {
- const crypto::public_key tx_pub_key = pub_key_field.pub_key;
- crypto::key_derivation derivation;
- bool r = hwdev.generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation);
- THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key derivation");
-
- for (size_t i = 0; i < td.m_tx.vout.size(); ++i)
- {
- tx_scan_info_t tx_scan_info;
- check_acc_out_precomp(td.m_tx.vout[i], derivation, {}, i, tx_scan_info);
- if (!tx_scan_info.error && tx_scan_info.received)
- return tx_pub_key;
- }
- }
-
- // we found no key yielding an output, but it might be in the additional
- // tx pub keys only, which we do not need to check, so return the first one
- return tx_pub_key;
+ return pub_key_field.pub_key;
}
bool wallet2::export_key_images(const std::string &filename, bool all) const
### 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 69/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.