diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 7de53371c..3de051376 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -56,6 +56,8 @@ using namespace epee; #include "rpc/core_rpc_server_commands_defs.h" #include "daemonizer/daemonizer.h" +#define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\003" + #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.rpc" @@ -2951,102 +2953,150 @@ namespace tools return true; } - //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::on_export_encrypted_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_ENCRYPTED_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_ENCRYPTED_KEY_IMAGES::response& res, epee::json_rpc::error& er, const connection_context *ctx) + /bool wallet_rpc_server::on_export_encrypted_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_ENCRYPTED_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_ENCRYPTED_KEY_IMAGES::response& res, epee::json_rpc::error& er, const connection_context *ctx) +{ + if (!m_wallet) return not_open(er); + try { - if (!m_wallet) return not_open(er); + std::pair>> ski = m_wallet->export_key_images(req.all); + res.offset = ski.first; + + std::string data; + const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; + + data.reserve(4 + sizeof(crypto::public_key) * 2 + ski.second.size() * (sizeof(crypto::key_image) + sizeof(crypto::signature))); + + data.resize(4); + data[0] = res.offset & 0xff; + data[1] = (res.offset >> 8) & 0xff; + data[2] = (res.offset >> 16) & 0xff; + data[3] = (res.offset >> 24) & 0xff; + + data += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); + data += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); + + for (const auto &item : ski.second) + { + data += std::string((const char *)&item.first, sizeof(crypto::key_image)); + data += std::string((const char *)&item.second, sizeof(crypto::signature)); + } + + std::string ciphertext = m_wallet->encrypt_with_view_secret_key(data); + + std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC)); + res.encrypted_key_images_blob = epee::string_tools::buff_to_hex_nodelimer(magic + ciphertext); + } + catch (const std::exception& e) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + + return true; +}/------------------------------------------------------------------------------------------------------------------------------ + /bool wallet_rpc_server::on_import_encrypted_key_images(const wallet_rpc::COMMAND_RPC_IMPORT_ENCRYPTED_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_IMPORT_ENCRYPTED_KEY_IMAGES::response& res, epee::json_rpc::error& er, const connection_context *ctx) +{ + if (!m_wallet) return not_open(er); + if (m_restricted) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + if (!m_wallet->is_trusted_daemon()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "This command requires a trusted daemon."; + return false; + } + try + { + std::string data; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.encrypted_key_images_blob, data)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE; + er.message = "Failed to parse encrypted key images blob"; + return false; + } + + const size_t magiclen = strlen(KEY_IMAGE_EXPORT_FILE_MAGIC); + if (data.size() < magiclen || memcmp(data.data(), KEY_IMAGE_EXPORT_FILE_MAGIC, magiclen)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE; + er.message = "Bad key image export file magic in blob"; + return false; + } + try { - std::pair>> ski = m_wallet->export_key_images(req.all); - res.offset = ski.first; - - // Serialize the key images and signatures into a single blob - std::string data; - data.reserve(ski.second.size() * (sizeof(crypto::key_image) + sizeof(crypto::signature))); - for (const auto &item : ski.second) - { - data += std::string((const char *)&item.first, sizeof(crypto::key_image)); - data += std::string((const char *)&item.second, sizeof(crypto::signature)); - } - - // Encrypt the serialized data - std::string ciphertext = m_wallet->encrypt_with_view_secret_key(data); - - // Encode the encrypted data as hex - res.encrypted_key_images_blob = epee::string_tools::buff_to_hex_nodelimer(ciphertext); + data = m_wallet->decrypt_with_view_secret_key(std::string(data, magiclen)); } catch (const std::exception& e) { - handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE; + er.message = "Failed to decrypt blob"; return false; } - return true; + const size_t headerlen = 4 + 2 * sizeof(crypto::public_key); + if (data.size() < headerlen) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE; + er.message = "Bad data size from file"; + return false; + } + + uint32_t offset = (uint8_t)data[0] | (((uint8_t)data[1]) << 8) | (((uint8_t)data[2]) << 16) | (((uint8_t)data[3]) << 24); + if (offset > m_wallet->get_num_transfer_details()) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE; + er.message = "Offset larger than known outputs"; + return false; + } + + const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[4]; + const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[4 + sizeof(crypto::public_key)]; + const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; + if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE; + er.message = "Key images from different account"; + return false; + } + + const size_t record_size = sizeof(crypto::key_image) + sizeof(crypto::signature); + if ((data.size() - headerlen) % record_size) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE; + er.message = "Bad data size from file"; + return false; + } + size_t nki = (data.size() - headerlen) / record_size; + + std::vector> ski; + ski.reserve(nki); + for (size_t n = 0; n < nki; ++n) + { + crypto::key_image key_image = *reinterpret_cast(&data[headerlen + n * record_size]); + crypto::signature signature = *reinterpret_cast(&data[headerlen + n * record_size + sizeof(crypto::key_image)]); + + ski.push_back(std::make_pair(key_image, signature)); + } + + uint64_t spent = 0, unspent = 0; + uint64_t height = m_wallet->import_key_images(ski, offset, spent, unspent); + res.height = height; + res.spent = spent; + res.unspent = unspent; } - //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::on_import_encrypted_key_images(const wallet_rpc::COMMAND_RPC_IMPORT_ENCRYPTED_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_IMPORT_ENCRYPTED_KEY_IMAGES::response& res, epee::json_rpc::error& er, const connection_context *ctx) + catch (const std::exception& e) { - if (!m_wallet) return not_open(er); - if (m_restricted) - { - er.code = WALLET_RPC_ERROR_CODE_DENIED; - er.message = "Command unavailable in restricted mode."; - return false; - } - if (!m_wallet->is_trusted_daemon()) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "This command requires a trusted daemon."; - return false; - } - try - { - // Decode the hex-encoded encrypted blob - std::string ciphertext; - if (!epee::string_tools::parse_hexstr_to_binbuff(req.encrypted_key_images_blob, ciphertext)) - { - er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE; - er.message = "Failed to parse encrypted key images blob"; - return false; - } - - // Decrypt the ciphertext - std::string decrypted_data = m_wallet->decrypt_with_view_secret_key(ciphertext); - - // Deserialize the decrypted data into key images and signatures - std::vector> ski; - const size_t record_size = sizeof(crypto::key_image) + sizeof(crypto::signature); - if (decrypted_data.size() % record_size != 0) - { - er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE; - er.message = "Decrypted data size is not a multiple of the record size"; - return false; - } - size_t num_records = decrypted_data.size() / record_size; - ski.reserve(num_records); - for (size_t i = 0; i < num_records; ++i) - { - crypto::key_image ki; - crypto::signature sig; - memcpy(&ki, &decrypted_data[i * record_size], sizeof(crypto::key_image)); - memcpy(&sig, &decrypted_data[i * record_size + sizeof(crypto::key_image)], sizeof(crypto::signature)); - ski.emplace_back(ki, sig); - } - - uint64_t spent = 0, unspent = 0; - uint64_t height = m_wallet->import_key_images(ski, req.offset, spent, unspent); - res.height = height; - res.spent = spent; - res.unspent = unspent; - } - catch (const std::exception& e) - { - handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); - return false; - } - - return true; + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; } + + return true; +}/------------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_make_uri(const wallet_rpc::COMMAND_RPC_MAKE_URI::request& req, wallet_rpc::COMMAND_RPC_MAKE_URI::response& res, epee::json_rpc::error& er, const connection_context *ctx) {