add get carrot tx proof support for sender

This commit is contained in:
auruya
2025-11-21 13:20:02 +03:00
parent fcaf640bcb
commit 6243e992cf
8 changed files with 198 additions and 104 deletions
+90
View File
@@ -41,6 +41,7 @@
#include "common/varint.h"
#include "warnings.h"
#include "crypto.h"
#include "mx25519.h"
#include "hash.h"
#include "cryptonote_config.h"
@@ -504,6 +505,95 @@ namespace crypto {
memwipe(&k, sizeof(k));
}
void crypto_ops::generate_carrot_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const secret_key &r, signature &sig) {
// sanity check
ge_p3 R_p3;
ge_p3 A_p3;
ge_p3 B_p3;
if (ge_frombytes_vartime(&R_p3, &R) != 0) throw std::runtime_error("tx pubkey is invalid");
if (ge_frombytes_vartime(&A_p3, &A) != 0) throw std::runtime_error("recipient view pubkey is invalid");
if (B && ge_frombytes_vartime(&B_p3, &*B) != 0) throw std::runtime_error("recipient spend pubkey is invalid");
#if !defined(NDEBUG)
{
assert(sc_check(&r) == 0);
// check R == r*G or R == r*B
public_key dbg_R;
if (B)
{
ge_p2 dbg_R_p2;
ge_scalarmult(&dbg_R_p2, &r, &B_p3);
ge_tobytes(&dbg_R, &dbg_R_p2);
}
else
{
ge_p3 dbg_R_p3;
ge_scalarmult_base(&dbg_R_p3, &r);
ge_p3_tobytes(&dbg_R, &dbg_R_p3);
}
assert(R == dbg_R);
// check D == r*A // move here to wallet2.cpp later
ge_p2 dbg_D_p2;
ge_scalarmult(&dbg_D_p2, &r, &A_p3);
public_key dbg_D;
ge_tobytes(&dbg_D, &dbg_D_p2);
// convert D to x25519 format for comparison
ge_p3 D_p3;
ge_frombytes_vartime(&D_p3, &dbg_D);
mx25519_pubkey D_x25519;
ge_p3_to_x25519(D_x25519.data, &D_p3);
assert(memcmp(D.data, D_x25519.data, 32) == 0);
}
#endif
// pick random k
ec_scalar k;
random_scalar(k);
// if B is not present
static const ec_point zero = {{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }};
s_comm_2 buf;
buf.msg = prefix_hash;
buf.D = D;
buf.R = R;
buf.A = A;
if (B)
buf.B = *B;
else
buf.B = zero;
cn_fast_hash(config::HASH_KEY_TXPROOF_V2, sizeof(config::HASH_KEY_TXPROOF_V2)-1, buf.sep);
if (B)
{
// compute X = k*B
ge_p2 X_p2;
ge_scalarmult(&X_p2, &k, &B_p3);
ge_tobytes(&buf.X, &X_p2);
}
else
{
// compute X = k*G
ge_p3 X_p3;
ge_scalarmult_base(&X_p3, &k);
ge_p3_tobytes(&buf.X, &X_p3);
}
// compute Y = k*A
ge_p2 Y_p2;
ge_scalarmult(&Y_p2, &k, &A_p3);
ge_tobytes(&buf.Y, &Y_p2);
// sig.c = Hs(Msg || D || X || Y || sep || R || A || B)
hash_to_scalar(&buf, sizeof(buf), sig.c);
// sig.r = k - sig.c*r
sc_mulsub(&sig.r, &sig.c, &unwrap(r), &k);
memwipe(&k, sizeof(k));
}
// Verify a proof: either v1 (version == 1) or v2 (version == 2)
bool crypto_ops::check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const signature &sig, const int version) {
// sanity check
+8
View File
@@ -131,6 +131,8 @@ namespace crypto {
friend void generate_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &);
static void generate_tx_proof_v1(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &);
friend void generate_tx_proof_v1(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &);
static void generate_carrot_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &);
friend void generate_carrot_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &);
static bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const signature &, const int);
friend bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const signature &, const int);
static void derive_key_image_generator(const public_key &, ec_point &);
@@ -260,6 +262,12 @@ namespace crypto {
inline void generate_tx_proof_v1(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const secret_key &r, signature &sig) {
crypto_ops::generate_tx_proof_v1(prefix_hash, R, A, B, D, r, sig);
}
/* Generation of a carrot tx proof; for carrot transactions, D is in X25519 domain (D = r*ConvertPointE(A))
* instead of Ed25519 domain (D = r*A). This version applies ConvertPointE transformation.
*/
inline void generate_carrot_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const secret_key &r, signature &sig) {
crypto_ops::generate_carrot_tx_proof(prefix_hash, R, A, B, D, r, sig);
}
inline bool check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const signature &sig, const int version) {
return crypto_ops::check_tx_proof(prefix_hash, R, A, B, D, sig, version);
}
+4
View File
@@ -202,6 +202,10 @@ namespace hw {
const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
crypto::signature &sig) = 0;
virtual void generate_carrot_tx_proof(const crypto::hash &prefix_hash,
const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
crypto::signature &sig) = 0;
virtual bool open_tx(crypto::secret_key &tx_key) = 0;
virtual void get_transaction_prefix_hash(const cryptonote::transaction_prefix& tx, crypto::hash& h) = 0;
+6
View File
@@ -282,6 +282,12 @@ namespace hw {
crypto::generate_tx_proof(prefix_hash, R, A, B, D, r, sig);
}
void device_default::generate_carrot_tx_proof(const crypto::hash &prefix_hash,
const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
crypto::signature &sig) {
crypto::generate_carrot_tx_proof(prefix_hash, R, A, B, D, r, sig);
}
bool device_default::open_tx(crypto::secret_key &tx_key) {
cryptonote::keypair txkey = cryptonote::keypair::generate(*this);
tx_key = txkey.sec;
+4
View File
@@ -112,6 +112,10 @@ namespace hw {
const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
crypto::signature &sig) override;
void generate_carrot_tx_proof(const crypto::hash &prefix_hash,
const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
crypto::signature &sig) override;
bool open_tx(crypto::secret_key &tx_key) override;
void get_transaction_prefix_hash(const cryptonote::transaction_prefix& tx, crypto::hash& h) override;
+8
View File
@@ -1433,6 +1433,14 @@ namespace hw {
#endif
}
void device_ledger::generate_carrot_tx_proof(const crypto::hash &prefix_hash,
const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
crypto::signature &sig) {
// to-do: For now, carrot tx proofs are not supported
AUTO_LOCK_CMD();
crypto::generate_carrot_tx_proof(prefix_hash, R, A, B, D, r, sig);
}
bool device_ledger::open_tx(crypto::secret_key &tx_key) {
AUTO_LOCK_CMD();
this->lock();
+4
View File
@@ -253,6 +253,10 @@ namespace hw {
const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
crypto::signature &sig) override;
void generate_carrot_tx_proof(const crypto::hash &prefix_hash,
const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
crypto::signature &sig) override;
bool open_tx(crypto::secret_key &tx_key) override;
void get_transaction_prefix_hash(const cryptonote::transaction_prefix& tx, crypto::hash& h) override;
+74 -104
View File
@@ -13209,7 +13209,7 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt
hw::device &hwdev = m_account.get_device();
rct::key aP;
// determine if the address is found in the subaddress hash table (i.e. whether the proof is outbound or inbound)
const bool is_out = m_subaddresses.count(address.m_spend_public_key) == 0;
const bool is_out = m_account.get_subaddress_map_ref().count(address.m_spend_public_key) == 0;
const crypto::hash txid = cryptonote::get_transaction_hash(tx);
std::string prefix_data((const char*)&txid, sizeof(crypto::hash));
@@ -13220,19 +13220,15 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt
std::vector<crypto::public_key> shared_secret;
std::vector<crypto::signature> sig;
std::string sig_str;
// Carrot proof
if (address.m_is_carrot)
if (is_out)
{
std::vector<crypto::key_derivation> derivations;
if (is_out)
const size_t num_sigs = 1 + additional_tx_keys.size();
shared_secret.resize(num_sigs);
sig.resize(num_sigs);
if (address.m_is_carrot)
{
// 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,
@@ -13240,48 +13236,19 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt
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<crypto::key_derivation>(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<crypto::key_derivation>(additional_s_sender_receiver_unctx);
}
sig_str = std::string("CarrotOutProofV1");
aP = carrot::raw_byte_convert<rct::key>(s_sender_receiver_unctx);
} else {
hwdev.scalarmultKey(aP, rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key));
}
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();
shared_secret.resize(num_sigs);
sig.resize(num_sigs);
hwdev.scalarmultKey(aP, rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key));
shared_secret[0] = rct::rct2pk(aP);
crypto::public_key tx_pub_key;
if (is_subaddress)
if (is_subaddress && !address.m_is_carrot)
{
hwdev.scalarmultKey(aP, rct::pk2rct(address.m_spend_public_key), rct::sk2rct(tx_key));
tx_pub_key = rct2pk(aP);
hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], tx_key, sig[0]);
}
else if (is_subaddress && address.m_is_carrot)
{
hwdev.scalarmultKey(aP, rct::pk2rct(address.m_spend_public_key), rct::sk2rct(tx_key));
tx_pub_key = rct2pk(aP);
@@ -13290,27 +13257,42 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt
else
{
hwdev.secret_key_to_public_key(tx_key, tx_pub_key);
hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], tx_key, sig[0]);
if (address.m_is_carrot) {
hwdev.generate_carrot_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], tx_key, sig[0]);
} else {
hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], tx_key, sig[0]);
}
}
for (size_t i = 1; i < num_sigs; ++i)
{
hwdev.scalarmultKey(aP, rct::pk2rct(address.m_view_public_key), rct::sk2rct(additional_tx_keys[i - 1]));
shared_secret[i] = rct::rct2pk(aP);
if (is_subaddress)
if (is_subaddress && !address.m_is_carrot)
{
hwdev.scalarmultKey(aP, rct::pk2rct(address.m_spend_public_key), rct::sk2rct(additional_tx_keys[i - 1]));
tx_pub_key = rct2pk(aP);
hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[i], additional_tx_keys[i - 1], sig[i]);
}
else if (is_subaddress && address.m_is_carrot)
{
hwdev.scalarmultKey(aP, rct::pk2rct(address.m_spend_public_key), rct::sk2rct(additional_tx_keys[i - 1]));
tx_pub_key = rct2pk(aP);
hwdev.generate_carrot_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[i], additional_tx_keys[i - 1], sig[i]);
}
else
{
hwdev.secret_key_to_public_key(additional_tx_keys[i - 1], tx_pub_key);
hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[i], additional_tx_keys[i - 1], sig[i]);
if (address.m_is_carrot) {
hwdev.generate_carrot_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[i], additional_tx_keys[i - 1], sig[i]);
} else {
hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[i], additional_tx_keys[i - 1], sig[i]);
}
}
}
sig_str = std::string("OutProofV2");
}
else
else // is_out == false
{
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");
@@ -13320,7 +13302,7 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt
shared_secret.resize(num_sigs);
sig.resize(num_sigs);
const crypto::secret_key& a = m_account.get_keys().m_view_secret_key;
const crypto::secret_key& a = address.m_is_carrot ? m_account.get_keys().k_view_incoming : m_account.get_keys().m_view_secret_key;
hwdev.scalarmultKey(aP, rct::pk2rct(tx_pub_key), rct::sk2rct(a));
shared_secret[0] = rct2pk(aP);
if (is_subaddress)
@@ -13350,10 +13332,23 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt
// check if this address actually received any funds
crypto::key_derivation derivation;
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[0], rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation");
std::vector<crypto::key_derivation> additional_derivations(num_sigs - 1);
for (size_t i = 1; i < num_sigs; ++i)
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[i], rct::rct2sk(rct::I), additional_derivations[i - 1]), error::wallet_internal_error, "Failed to generate key derivation");
if (address.m_is_carrot)
{
// For carrot addresses, shared_secret is already in x25519 format and can be used directly as derivation
memcpy(&derivation, &shared_secret[0], sizeof(crypto::key_derivation));
for (size_t i = 1; i < num_sigs; ++i)
memcpy(&additional_derivations[i - 1], &shared_secret[i], sizeof(crypto::key_derivation));
}
else
{
// For regular addresses, generate key derivation from shared secret
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[0], rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation");
for (size_t i = 1; i < num_sigs; ++i)
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[i], rct::rct2sk(rct::I), additional_derivations[i - 1]), error::wallet_internal_error, "Failed to generate key derivation");
}
uint64_t received;
check_tx_key_helper(tx, derivation, additional_derivations, address, received);
// SRCG: if this returns 0 received, but it's an AUDIT TX, then that is EXPECTED
@@ -13424,44 +13419,6 @@ 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<crypto::key_derivation> 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<crypto::key_derivation> 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);
@@ -13542,15 +13499,28 @@ bool wallet2::check_tx_proof(const cryptonote::transaction &tx, const cryptonote
if (std::any_of(good_signature.begin(), good_signature.end(), [](int i) { return i > 0; }))
{
// obtain key derivation by multiplying scalar 1 to the shared secret
// obtain key derivation by multiplying scalar 1 to the shared secret (or use directly for carrot)
crypto::key_derivation derivation;
if (good_signature[0])
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[0], rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation");
std::vector<crypto::key_derivation> additional_derivations(num_sigs - 1);
for (size_t i = 1; i < num_sigs; ++i)
if (good_signature[i])
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[i], rct::rct2sk(rct::I), additional_derivations[i - 1]), error::wallet_internal_error, "Failed to generate key derivation");
if (address.m_is_carrot)
{
// For carrot addresses, shared_secret is already in x25519 format and can be used directly as derivation
if (good_signature[0])
memcpy(&derivation, &shared_secret[0], sizeof(crypto::key_derivation));
for (size_t i = 1; i < num_sigs; ++i)
if (good_signature[i])
memcpy(&additional_derivations[i - 1], &shared_secret[i], sizeof(crypto::key_derivation));
}
else
{
// For regular addresses, generate key derivation from shared secret
if (good_signature[0])
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[0], rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation");
for (size_t i = 1; i < num_sigs; ++i)
if (good_signature[i])
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[i], rct::rct2sk(rct::I), additional_derivations[i - 1]), error::wallet_internal_error, "Failed to generate key derivation");
}
check_tx_key_helper(tx, derivation, additional_derivations, address, received);
return true;