From fcaf640bcbe79bb91bc4240abfebf66b91c74b48 Mon Sep 17 00:00:00 2001 From: auruya Date: Mon, 17 Nov 2025 19:58:40 +0300 Subject: [PATCH] Add carrot tx proof support (get_tx_proof and check_tx_proof) --- src/wallet/wallet2.cpp | 90 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 22d1fd034..200d6f38d 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -13220,6 +13220,58 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt std::vector shared_secret; std::vector sig; std::string sig_str; + + // Carrot proof + if (address.m_is_carrot) + { + std::vector derivations; + + if (is_out) + { + // Sender has tx_key + const size_t num_derivs = 1 + additional_tx_keys.size(); + derivations.resize(num_derivs); + + // Main derivation + mx25519_pubkey s_sender_receiver_unctx; + bool success = carrot::make_carrot_uncontextualized_shared_key_sender( + tx_key, + address.m_view_public_key, + s_sender_receiver_unctx); + THROW_WALLET_EXCEPTION_IF(!success, error::wallet_internal_error, + "Failed to generate X25519 key derivation for carrot proof (main)"); + derivations[0] = carrot::raw_byte_convert(s_sender_receiver_unctx); + + // Additional derivations + for (size_t i = 0; i < additional_tx_keys.size(); ++i) + { + mx25519_pubkey additional_s_sender_receiver_unctx; + success = carrot::make_carrot_uncontextualized_shared_key_sender( + additional_tx_keys[i], + address.m_view_public_key, + additional_s_sender_receiver_unctx); + THROW_WALLET_EXCEPTION_IF(!success, error::wallet_internal_error, + "Failed to generate X25519 key derivation for carrot proof (additional)"); + derivations[i + 1] = carrot::raw_byte_convert(additional_s_sender_receiver_unctx); + } + + sig_str = std::string("CarrotOutProofV1"); + } + else + { + // view key can be used + + sig_str = std::string("CarrotInProofV1"); + } + + // derivation encoding + for (size_t i = 0; i < derivations.size(); ++i) + sig_str += tools::base58::encode(std::string((const char *)&derivations[i], sizeof(crypto::key_derivation))); + + return sig_str; + } + + // Legacy proof if (is_out) { const size_t num_sigs = 1 + additional_tx_keys.size(); @@ -13372,6 +13424,44 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account bool wallet2::check_tx_proof(const cryptonote::transaction &tx, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, const std::string &sig_str, uint64_t &received) const { + + // + if (sig_str.substr(0, 6) == "Carrot") + { + THROW_WALLET_EXCEPTION_IF(!address.m_is_carrot, error::wallet_internal_error, + "Carrot proof provided but address is not a carrot address"); + const bool is_out = sig_str.substr(6, 3) == "Out"; + std::vector derivations(1); + const std::string header = is_out ? "CarrotOutProofV1" : "CarrotInProofV1"; + const size_t header_len = header.size(); + THROW_WALLET_EXCEPTION_IF(sig_str.size() < header_len || sig_str.substr(0, header_len) != header, error::wallet_internal_error, + "Signature header check error"); + const size_t deriv_len = tools::base58::encode(std::string((const char *)&derivations[0], sizeof(crypto::key_derivation))).size(); + const size_t num_derivs = (sig_str.size() - header_len) / deriv_len; + derivations.resize(num_derivs); + + // Decode all derivations from base58 + for (size_t i = 0; i < num_derivs; ++i) + { + std::string deriv_decoded; + const size_t offset = header_len + i * deriv_len; + THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(offset, deriv_len), deriv_decoded), error::wallet_internal_error, + "Derivation decoding error"); + THROW_WALLET_EXCEPTION_IF(sizeof(crypto::key_derivation) != deriv_decoded.size(), error::wallet_internal_error, + "Derivation decoding error"); + memcpy(&derivations[i], deriv_decoded.data(), sizeof(crypto::key_derivation)); + } + + std::vector additional_derivations(derivations.begin() + 1, derivations.end()); + check_tx_key_helper(tx, derivations[0], additional_derivations, address, received); + + return true; + } + + // Legacy proof handling + THROW_WALLET_EXCEPTION_IF(address.m_is_carrot, error::wallet_internal_error, + "Legacy proof provided but address is a carrot address"); + // InProofV1, InProofV2, OutProofV1, OutProofV2 const bool is_out = sig_str.substr(0, 3) == "Out"; const std::string header = is_out ? sig_str.substr(0,10) : sig_str.substr(0,9);