diff --git a/src/cryptonote_core/cryptonote_format_utils.cpp b/src/cryptonote_core/cryptonote_format_utils.cpp index 1b21894a0..a79c3cdd3 100644 --- a/src/cryptonote_core/cryptonote_format_utils.cpp +++ b/src/cryptonote_core/cryptonote_format_utils.cpp @@ -38,6 +38,8 @@ using namespace epee; #include "crypto/crypto.h" #include "crypto/hash.h" +#define ENCRYPTED_PAYMENT_ID_TAIL 0x8d + namespace cryptonote { //--------------------------------------------------------------- @@ -303,24 +305,92 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id) + bool remove_extra_nonce_tx_extra(std::vector& tx_extra) + { + std::string extra_str(reinterpret_cast(tx_extra.data()), tx_extra.size()); + std::istringstream iss(extra_str); + binary_archive ar(iss); + std::ostringstream oss; + binary_archive newar(oss); + + bool eof = false; + while (!eof) + { + tx_extra_field field; + bool r = ::do_serialize(ar, field); + CHECK_AND_NO_ASSERT_MES(r, false, "failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast(tx_extra.data()), tx_extra.size()))); + if (field.type() != typeid(tx_extra_nonce)) + ::do_serialize(newar, field); + + std::ios_base::iostate state = iss.rdstate(); + eof = (EOF == iss.peek()); + iss.clear(state); + } + CHECK_AND_NO_ASSERT_MES(::serialization::check_stream_state(ar), false, "failed to deserialize extra field. extra = " << string_tools::buff_to_hex_nodelimer(std::string(reinterpret_cast(tx_extra.data()), tx_extra.size()))); + tx_extra.clear(); + std::string s = oss.str(); + tx_extra.reserve(s.size()); + std::copy(s.begin(), s.end(), std::back_inserter(tx_extra)); + return true; + } + //--------------------------------------------------------------- + void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id, bool encrypted) { extra_nonce.clear(); - extra_nonce.push_back(TX_EXTRA_NONCE_PAYMENT_ID); + extra_nonce.push_back(encrypted ? TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID : TX_EXTRA_NONCE_PAYMENT_ID); const uint8_t* payment_id_ptr = reinterpret_cast(&payment_id); std::copy(payment_id_ptr, payment_id_ptr + sizeof(payment_id), std::back_inserter(extra_nonce)); } //--------------------------------------------------------------- - bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id) + bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id, bool &encrypted) { if(sizeof(crypto::hash) + 1 != extra_nonce.size()) return false; - if(TX_EXTRA_NONCE_PAYMENT_ID != extra_nonce[0]) + if(TX_EXTRA_NONCE_PAYMENT_ID != extra_nonce[0] && TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID != extra_nonce[0]) return false; payment_id = *reinterpret_cast(extra_nonce.data() + 1); + encrypted = TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID == extra_nonce[0]; return true; } //--------------------------------------------------------------- + crypto::public_key get_destination_view_key_pub(const std::vector &destinations) + { + if (destinations.empty()) + return null_pkey; + for (size_t n = 1; n < destinations.size(); ++n) + { + if (!memcmp(&destinations[n].addr, &sender_keys.m_account_address, sizeof(destinations[0].addr))) + continue; + if (memcmp(&destinations[n].addr, &destinations[0].addr, sizeof(destinations[0].addr))) + return null_pkey; + } + return destinations[0].addr.m_view_public_key; + } + //--------------------------------------------------------------- + bool encrypt_payment_id(crypto::hash &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) + { + crypto::key_derivation derivation; + crypto::hash hash; + char data[33]; /* A hash, and an extra byte */ + + if (!generate_key_derivation(public_key, secret_key, derivation)) + return false; + + memcpy(data, &derivation, 32); + data[32] = ENCRYPTED_PAYMENT_ID_TAIL; + cn_fast_hash(data, 33, hash); + + for (size_t b = 0; b < 32; ++b) + payment_id.data[b] ^= hash.data[b]; + + return true; + } + bool decrypt_payment_id(crypto::hash &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) + { + // Encryption and decryption are the same operation (xor with a key) + return encrypt_payment_id(payment_id, public_key, secret_key); + } + //--------------------------------------------------------------- bool construct_tx(const account_keys& sender_account_keys, const std::vector& sources, const std::vector& destinations, std::vector extra, transaction& tx, uint64_t unlock_time) { tx.vin.clear(); @@ -334,6 +404,50 @@ namespace cryptonote keypair txkey = keypair::generate(); add_tx_pub_key_to_extra(tx, txkey.pub); + // if we have a stealth payment id, find it and encrypt it with the tx key now + std::vector tx_extra_fields; + if (parse_tx_extra(tx.extra, tx_extra_fields)) + { + tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + crypto::hash payment_id = null_hash; + bool encrypted; + if (get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id, encrypted) && encrypted) + { + LOG_PRINT_L2("Encrypting payment id " << payment_id); + crypto::key_derivation derivation; + crypto::public_key view_key_pub = get_destination_view_key_pub(destinations); + if (view_key_pub == null_pkey) + { + LOG_ERROR("Destinations have to have exactly one output to support encrypted payment ids"); + return false; + } + + if (!encrypt_payment_id(payment_id, view_key_pub, txkey.sec)) + { + LOG_ERROR("Failed to encrypt payment id"); + return false; + } + + std::string extra_nonce; + set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id, true); + remove_extra_nonce_tx_extra(tx.extra); + if (!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce)) + { + LOG_ERROR("Failed to add encrypted payment id to tx extra"); + return false; + } + LOG_PRINT_L1("Encrypted payment ID: " << payment_id); + } + } + } + else + { + LOG_ERROR("Failed to parse tx extra"); + return false; + } + struct input_generation_context_data { keypair in_ephemeral; diff --git a/src/cryptonote_core/cryptonote_format_utils.h b/src/cryptonote_core/cryptonote_format_utils.h index fd785aafd..69baa20cf 100644 --- a/src/cryptonote_core/cryptonote_format_utils.h +++ b/src/cryptonote_core/cryptonote_format_utils.h @@ -45,6 +45,8 @@ namespace cryptonote bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash); bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx); bool construct_miner_tx(size_t height, size_t median_size, uint64_t already_generated_coins, size_t current_block_size, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce = blobdata(), size_t max_outs = 1); + bool encrypt_payment_id(crypto::hash &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key); + bool decrypt_payment_id(crypto::hash &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key); struct tx_source_entry { @@ -85,8 +87,9 @@ namespace cryptonote crypto::public_key get_tx_pub_key_from_extra(const transaction& tx); bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key); bool add_extra_nonce_to_tx_extra(std::vector& tx_extra, const blobdata& extra_nonce); - void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id); - bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id); + bool remove_extra_nonce_tx_extra(std::vector& tx_extra); + void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id, bool encrypted); + bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id, bool &encrypted); bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, size_t output_index); bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector& outs, uint64_t& money_transfered); bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector& outs, uint64_t& money_transfered); diff --git a/src/cryptonote_core/tx_extra.h b/src/cryptonote_core/tx_extra.h index ccfe4d1d9..012f41593 100644 --- a/src/cryptonote_core/tx_extra.h +++ b/src/cryptonote_core/tx_extra.h @@ -40,6 +40,7 @@ #define TX_EXTRA_MERGE_MINING_TAG 0x03 #define TX_EXTRA_NONCE_PAYMENT_ID 0x00 +#define TX_EXTRA_NONCE_ENCRYPTED_PAYMENT_ID 0x01 namespace cryptonote { diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index cad83bb45..9c2396029 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -1288,6 +1288,7 @@ bool simple_wallet::transfer_main(bool new_algorithm, const std::vector extra; bool payment_id_seen = false; + bool encrypt_payment_id = false; if (1 == local_args.size() % 2) { std::string payment_id_str = local_args.back(); @@ -1298,7 +1299,7 @@ bool simple_wallet::transfer_main(bool new_algorithm, const std::vector outs; uint64_t tx_money_got_in_outs = 0; + crypto::public_key tx_pub_key = null_pkey; std::vector tx_extra_fields; if(!parse_tx_extra(tx.extra, tx_extra_fields)) @@ -170,7 +171,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ return; } - crypto::public_key tx_pub_key = pub_key_field.pub_key; + tx_pub_key = pub_key_field.pub_key; bool r = lookup_acc_outs(m_account.get_keys(), tx, tx_pub_key, outs, tx_money_got_in_outs); THROW_WALLET_EXCEPTION_IF(!r, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); @@ -236,9 +237,26 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ crypto::hash payment_id = null_hash; if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) { - if(get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) + bool encrypted; + if(get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id, encrypted) && encrypted) { // We got a payment ID to go with this tx + LOG_PRINT_L2("Found encrypted payment ID: " << payment_id); + if (tx_pub_key != null_pkey) + { + if (!decrypt_payment_id(payment_id, tx_pub_key, m_account.get_keys().m_view_secret_key)) + { + LOG_PRINT_L0("Failed to decrypt payment ID: " << payment_id); + } + else + { + LOG_PRINT_L2("Decrypted payment ID: " << payment_id); + } + } + else + { + LOG_PRINT_L1("No public key found in tx, unable to decrypt payment id"); + } } } uint64_t received = (tx_money_spent_in_ins < tx_money_got_in_outs) ? tx_money_got_in_outs - tx_money_spent_in_ins : 0; diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 1d38695ae..4797f76a2 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -117,7 +117,7 @@ namespace tools } //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::validate_transfer(const std::list destinations, std::string payment_id, std::vector& dsts, std::vector& extra, epee::json_rpc::error& er) + bool wallet_rpc_server::validate_transfer(const std::list destinations, std::string payment_id, bool encrypt_payment_id, std::vector& dsts, std::vector& extra, epee::json_rpc::error& er) { crypto::hash integrated_payment_id = cryptonote::null_hash; for (auto it = destinations.begin(); it != destinations.end(); it++) @@ -144,6 +144,9 @@ namespace tools } integrated_payment_id = new_payment_id; } + + // integrated addresses imply encrypted payment id + encrypt_payment_id = true; } if (!payment_id.empty()) @@ -161,7 +164,7 @@ namespace tools } std::string extra_nonce; - cryptonote::set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); + cryptonote::set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id, encrypt_payment_id); /* Append Payment ID data into extra */ if (!cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce)) { @@ -189,7 +192,7 @@ namespace tools } // validate the transfer requested and populate dsts & extra - if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, er)) + if (!validate_transfer(req.destinations, req.payment_id, req.encrypt_payment_id, dsts, extra, er)) { return false; } @@ -247,7 +250,7 @@ namespace tools } // validate the transfer requested and populate dsts & extra; RPC_TRANSFER::request and RPC_TRANSFER_SPLIT::request are identical types. - if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, er)) + if (!validate_transfer(req.destinations, req.payment_id, req.encrypt_payment_id, dsts, extra, er)) { return false; } diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 73411a98d..fbe0964d1 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -79,7 +79,7 @@ namespace tools //json_rpc bool on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res, epee::json_rpc::error& er); bool on_getaddress(const wallet_rpc::COMMAND_RPC_GET_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS::response& res, epee::json_rpc::error& er); - bool validate_transfer(const std::list destinations, const std::string payment_id, std::vector& dsts, std::vector& extra, epee::json_rpc::error& er); + bool validate_transfer(const std::list destinations, const std::string payment_id, bool encrypt_payment_id, std::vector& dsts, std::vector& extra, epee::json_rpc::error& er); bool on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er); bool on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er); bool on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 7786ab009..8897397af 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -97,6 +97,7 @@ namespace wallet_rpc uint64_t mixin; uint64_t unlock_time; std::string payment_id; + bool encrypt_payment_id; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(destinations) @@ -104,6 +105,7 @@ namespace wallet_rpc KV_SERIALIZE(mixin) KV_SERIALIZE(unlock_time) KV_SERIALIZE(payment_id) + KV_SERIALIZE(encrypt_payment_id) END_KV_SERIALIZE_MAP() }; @@ -127,6 +129,7 @@ namespace wallet_rpc uint64_t unlock_time; std::string payment_id; bool new_algorithm; + bool encrypt_payment_id; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(destinations) @@ -135,6 +138,7 @@ namespace wallet_rpc KV_SERIALIZE(unlock_time) KV_SERIALIZE(payment_id) KV_SERIALIZE(new_algorithm) + KV_SERIALIZE(encrypt_payment_id) END_KV_SERIALIZE_MAP() };