diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index f122317ff..007894d96 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -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 &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 &B, const public_key &D, const signature &sig, const int version) { // sanity check diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index a4e0e7706..04769751c 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -131,6 +131,8 @@ namespace crypto { friend void generate_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, signature &); static void generate_tx_proof_v1(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, signature &); friend void generate_tx_proof_v1(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, signature &); + static void generate_carrot_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, signature &); + friend void generate_carrot_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const secret_key &, signature &); static bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, const public_key &, const signature &, const int); friend bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional &, 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 &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 &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 &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); } diff --git a/src/device/device.hpp b/src/device/device.hpp index fc90323b7..30f0d74e5 100644 --- a/src/device/device.hpp +++ b/src/device/device.hpp @@ -202,6 +202,10 @@ namespace hw { const crypto::public_key &R, const crypto::public_key &A, const boost::optional &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 &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; diff --git a/src/device/device_default.cpp b/src/device/device_default.cpp index d13137bff..10c8914d4 100644 --- a/src/device/device_default.cpp +++ b/src/device/device_default.cpp @@ -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 &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; diff --git a/src/device/device_default.hpp b/src/device/device_default.hpp index c08ce7ac8..d240f863a 100644 --- a/src/device/device_default.hpp +++ b/src/device/device_default.hpp @@ -112,6 +112,10 @@ namespace hw { const crypto::public_key &R, const crypto::public_key &A, const boost::optional &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 &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; diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index 16bd7972e..58f15d7f3 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -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 &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(); diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp index 9fc830115..df8e2dde4 100644 --- a/src/device/device_ledger.hpp +++ b/src/device/device_ledger.hpp @@ -253,6 +253,10 @@ namespace hw { const crypto::public_key &R, const crypto::public_key &A, const boost::optional &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 &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; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 200d6f38d..99f6cd4be 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -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 shared_secret; std::vector sig; std::string sig_str; - - // Carrot proof - if (address.m_is_carrot) + + if (is_out) { - std::vector 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(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"); + aP = carrot::raw_byte_convert(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 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 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); @@ -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 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;