diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index d971f4fed..6c694d743 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -931,6 +931,8 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to
in ")); m_cmd_binder.set_handler("get_tx_proof", boost::bind(&simple_wallet::get_tx_proof, this, _1), tr("Generate a signature proving payment/receipt of money to/by
in using the transaction/view secret key")); m_cmd_binder.set_handler("check_tx_proof", boost::bind(&simple_wallet::check_tx_proof, this, _1), tr("Check tx proof for payment going to
in ")); + m_cmd_binder.set_handler("get_spend_proof", boost::bind(&simple_wallet::get_spend_proof, this, _1), tr("Generate a signature proving that you generated using the spend secret key")); + m_cmd_binder.set_handler("check_spend_proof", boost::bind(&simple_wallet::check_spend_proof, this, _1), tr("Check a signature proving that the signer generated ")); m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out|pending|failed|pool] [index=[,,...]] [ []] - Show incoming/outgoing transfers within an optional height range")); m_cmd_binder.set_handler("unspent_outputs", boost::bind(&simple_wallet::unspent_outputs, this, _1), tr("unspent_outputs [index=[,,...]] [ []] - Show unspent outputs of a specified address within an optional amount range")); m_cmd_binder.set_handler("rescan_bc", boost::bind(&simple_wallet::rescan_blockchain, this, _1), tr("Rescan blockchain from scratch")); @@ -4152,6 +4154,91 @@ bool simple_wallet::check_tx_proof(const std::vector &args) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::get_spend_proof(const std::vector &args) +{ + if(args.size() != 1 && args.size() != 2) { + fail_msg_writer() << tr("usage: get_spend_proof []"); + return true; + } + + if (m_wallet->watch_only()) + { + fail_msg_writer() << tr("wallet is watch-only and cannot generate the proof"); + return true; + } + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(args[0], txid)) + { + fail_msg_writer() << tr("failed to parse txid"); + return true; + } + + if (!try_connect_to_daemon()) + { + fail_msg_writer() << tr("failed to connect to the daemon"); + return true; + } + + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + try + { + const std::string sig_str = m_wallet->get_spend_proof(txid, args.size() == 2 ? args[1] : ""); + const std::string filename = "monero_spend_proof"; + if (epee::file_io_utils::save_string_to_file(filename, sig_str)) + success_msg_writer() << tr("signature file saved to: ") << filename; + else + fail_msg_writer() << tr("failed to save signature file"); + } + catch (const std::exception &e) + { + fail_msg_writer() << e.what(); + } + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::check_spend_proof(const std::vector &args) +{ + if(args.size() != 2 && args.size() != 3) { + fail_msg_writer() << tr("usage: check_spend_proof []"); + return true; + } + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(args[0], txid)) + { + fail_msg_writer() << tr("failed to parse txid"); + return true; + } + + if (!try_connect_to_daemon()) + { + fail_msg_writer() << tr("failed to connect to the daemon"); + return true; + } + + std::string sig_str; + if (!epee::file_io_utils::load_file_to_string(args[1], sig_str)) + { + fail_msg_writer() << tr("failed to load signature file"); + return true; + } + + try + { + if (m_wallet->check_spend_proof(txid, args.size() == 3 ? args[2] : "", sig_str)) + success_msg_writer() << tr("Good signature"); + else + fail_msg_writer() << tr("Bad signature"); + } + catch (const std::exception& e) + { + fail_msg_writer() << e.what(); + } + return true; +} +//---------------------------------------------------------------------------------------------------- static std::string get_human_readable_timestamp(uint64_t ts) { char buffer[64]; diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 85216bddc..d6dde3ea7 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -163,6 +163,8 @@ namespace cryptonote bool check_tx_key(const std::vector &args); bool get_tx_proof(const std::vector &args); bool check_tx_proof(const std::vector &args); + bool get_spend_proof(const std::vector &args); + bool check_spend_proof(const std::vector &args); bool show_transfers(const std::vector &args); bool unspent_outputs(const std::vector &args); bool rescan_blockchain(const std::vector &args); diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index db8911042..e0326db84 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1530,6 +1530,52 @@ bool WalletImpl::checkTxProof(const std::string &txid_str, const std::string &ad } } +std::string WalletImpl::getSpendProof(const std::string &txid_str, const std::string &message) const { + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(txid_str, txid)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return ""; + } + + try + { + m_status = Status_Ok; + return m_wallet->get_spend_proof(txid, message); + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return ""; + } +} + +bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string &message, const std::string &signature, bool &good) const { + good = false; + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(txid_str, txid)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return false; + } + + try + { + m_status = Status_Ok; + good = m_wallet->check_spend_proof(txid, message, signature); + return true; + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return false; + } +} + std::string WalletImpl::signMessage(const std::string &message) { return m_wallet->sign(message); diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index ecf540f22..38e167516 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -140,6 +140,8 @@ public: virtual bool checkTxKey(const std::string &txid, std::string tx_key, const std::string &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); virtual std::string getTxProof(const std::string &txid, const std::string &address, const std::string &message, std::string &error_str) const; virtual bool checkTxProof(const std::string &txid, const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &received, bool &in_pool, uint64_t &confirmations); + virtual std::string getSpendProof(const std::string &txid, const std::string &message) const; + virtual bool checkSpendProof(const std::string &txid, const std::string &message, const std::string &signature, bool &good) const; virtual std::string signMessage(const std::string &message); virtual bool verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const; virtual void startRefresh(); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index c4124a677..bfbb653a3 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -6209,6 +6209,236 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s additional_tx_keys = j->second; return true; } +//---------------------------------------------------------------------------------------------------- +std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string &message) +{ + THROW_WALLET_EXCEPTION_IF(m_watch_only, error::wallet_internal_error, + "get_spend_proof requires spend secret key and is not available for a watch-only wallet"); + + // fetch tx from daemon + COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + req.decode_as_json = false; + COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); + bool r; + { + const boost::lock_guard lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, + "daemon returned wrong response for gettransactions, wrong txs count = " + + std::to_string(res.txs.size()) + ", expected 1"); + cryptonote::blobdata bd; + THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr"); + cryptonote::transaction tx; + crypto::hash tx_hash, tx_prefix_hash; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "txid mismatch"); + + std::vector> signatures; + + // get signature prefix hash + std::string sig_prefix_data((const char*)&txid, sizeof(crypto::hash)); + sig_prefix_data += message; + crypto::hash sig_prefix_hash; + crypto::cn_fast_hash(sig_prefix_data.data(), sig_prefix_data.size(), sig_prefix_hash); + + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get(std::addressof(tx.vin[i])); + if (in_key == nullptr) + continue; + + // check if the key image belongs to us + const auto found = m_key_images.find(in_key->k_image); + if(found == m_key_images.end()) + { + THROW_WALLET_EXCEPTION_IF(i > 0, error::wallet_internal_error, "subset of key images belong to us, very weird!"); + THROW_WALLET_EXCEPTION_IF(true, error::wallet_internal_error, "This tx wasn't generated by this wallet!"); + } + + // derive the real output keypair + const transfer_details& in_td = m_transfers[found->second]; + const txout_to_key* const in_tx_out_pkey = boost::get(std::addressof(in_td.m_tx.vout[in_td.m_internal_output_index].target)); + THROW_WALLET_EXCEPTION_IF(in_tx_out_pkey == nullptr, error::wallet_internal_error, "Output is not txout_to_key"); + const crypto::public_key in_tx_pub_key = get_tx_pub_key_from_extra(in_td.m_tx, in_td.m_pk_index); + const std::vector in_additionakl_tx_pub_keys = get_additional_tx_pub_keys_from_extra(in_td.m_tx); + keypair in_ephemeral; + crypto::key_image in_img; + THROW_WALLET_EXCEPTION_IF(!generate_key_image_helper(m_account.get_keys(), m_subaddresses, in_tx_out_pkey->key, in_tx_pub_key, in_additionakl_tx_pub_keys, in_td.m_internal_output_index, in_ephemeral, in_img), + error::wallet_internal_error, "failed to generate key image"); + THROW_WALLET_EXCEPTION_IF(in_key->k_image != in_img, error::wallet_internal_error, "key image mismatch"); + + // get output pubkeys in the ring + const std::vector absolute_offsets = cryptonote::relative_output_offsets_to_absolute(in_key->key_offsets); + const size_t ring_size = in_key->key_offsets.size(); + THROW_WALLET_EXCEPTION_IF(absolute_offsets.size() != ring_size, error::wallet_internal_error, "absolute offsets size is wrong"); + COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req); + req.outputs.resize(ring_size); + for (size_t j = 0; j < ring_size; ++j) + { + req.outputs[j].amount = in_key->amount; + req.outputs[j].index = absolute_offsets[j]; + } + COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); + bool r; + { + const boost::lock_guard lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.outs.size() != ring_size, error::wallet_internal_error, + "daemon returned wrong response for get_outs.bin, wrong amounts count = " + + std::to_string(res.outs.size()) + ", expected " + std::to_string(ring_size)); + + // copy pubkey pointers + std::vector p_output_keys; + for (const COMMAND_RPC_GET_OUTPUTS_BIN::outkey &out : res.outs) + p_output_keys.push_back(&out.key); + + // figure out real output index and secret key + size_t sec_index = -1; + for (size_t j = 0; j < ring_size; ++j) + { + if (res.outs[j].key == in_ephemeral.pub) + { + sec_index = j; + break; + } + } + THROW_WALLET_EXCEPTION_IF(sec_index >= ring_size, error::wallet_internal_error, "secret index not found"); + + // generate ring sig for this input + signatures.push_back(std::vector()); + std::vector& sigs = signatures.back(); + sigs.resize(in_key->key_offsets.size()); + crypto::generate_ring_signature(sig_prefix_hash, in_key->k_image, p_output_keys, in_ephemeral.sec, sec_index, sigs.data()); + } + + std::string sig_str = "SpendProofV1"; + for (const std::vector& ring_sig : signatures) + for (const crypto::signature& sig : ring_sig) + sig_str += tools::base58::encode(std::string((const char *)&sig, sizeof(crypto::signature))); + return sig_str; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &message, const std::string &sig_str) +{ + const std::string header = "SpendProofV1"; + 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"); + + // fetch tx from daemon + COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + req.decode_as_json = false; + COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); + bool r; + { + const boost::lock_guard lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, + "daemon returned wrong response for gettransactions, wrong txs count = " + + std::to_string(res.txs.size()) + ", expected 1"); + cryptonote::blobdata bd; + THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr"); + cryptonote::transaction tx; + crypto::hash tx_hash, tx_prefix_hash; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "txid mismatch"); + + // check signature size + size_t num_sigs = 0; + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get(std::addressof(tx.vin[i])); + if (in_key != nullptr) + num_sigs += in_key->key_offsets.size(); + } + std::vector> signatures = { std::vector(1) }; + const size_t sig_len = tools::base58::encode(std::string((const char *)&signatures[0][0], sizeof(crypto::signature))).size(); + THROW_WALLET_EXCEPTION_IF(sig_str.size() != header_len + num_sigs * sig_len, + error::wallet_internal_error, "incorrect signature size"); + + // decode base58 + signatures.clear(); + size_t offset = header_len; + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get(std::addressof(tx.vin[i])); + if (in_key == nullptr) + continue; + signatures.resize(signatures.size() + 1); + signatures.back().resize(in_key->key_offsets.size()); + for (size_t j = 0; j < in_key->key_offsets.size(); ++j) + { + std::string sig_decoded; + THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(offset, sig_len), sig_decoded), error::wallet_internal_error, "Signature decoding error"); + THROW_WALLET_EXCEPTION_IF(sizeof(crypto::signature) != sig_decoded.size(), error::wallet_internal_error, "Signature decoding error"); + memcpy(&signatures.back()[j], sig_decoded.data(), sizeof(crypto::signature)); + offset += sig_len; + } + } + + // get signature prefix hash + std::string sig_prefix_data((const char*)&txid, sizeof(crypto::hash)); + sig_prefix_data += message; + crypto::hash sig_prefix_hash; + crypto::cn_fast_hash(sig_prefix_data.data(), sig_prefix_data.size(), sig_prefix_hash); + + std::vector>::const_iterator sig_iter = signatures.cbegin(); + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get(std::addressof(tx.vin[i])); + if (in_key == nullptr) + continue; + + // get output pubkeys in the ring + COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req); + const std::vector absolute_offsets = cryptonote::relative_output_offsets_to_absolute(in_key->key_offsets); + req.outputs.resize(absolute_offsets.size()); + for (size_t j = 0; j < absolute_offsets.size(); ++j) + { + req.outputs[j].amount = in_key->amount; + req.outputs[j].index = absolute_offsets[j]; + } + COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); + bool r; + { + const boost::lock_guard lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.outs.size() != req.outputs.size(), error::wallet_internal_error, + "daemon returned wrong response for get_outs.bin, wrong amounts count = " + + std::to_string(res.outs.size()) + ", expected " + std::to_string(req.outputs.size())); + + // copy pointers + std::vector p_output_keys; + for (const COMMAND_RPC_GET_OUTPUTS_BIN::outkey &out : res.outs) + p_output_keys.push_back(&out.key); + + // check this ring + if (!crypto::check_ring_signature(sig_prefix_hash, in_key->k_image, p_output_keys, sig_iter->data())) + return false; + ++sig_iter; + } + THROW_WALLET_EXCEPTION_IF(sig_iter != signatures.cend(), error::wallet_internal_error, "Signature iterator didn't reach the end"); + return true; +} +//---------------------------------------------------------------------------------------------------- void wallet2::check_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector &additional_tx_keys, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) { diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index cff10fa11..7807f7f33 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -689,6 +689,8 @@ namespace tools std::string get_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, std::string &error_str); bool check_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, const std::string &sig_str, uint64_t &received, bool &in_pool, uint64_t &confirmations); + std::string get_spend_proof(const crypto::hash &txid, const std::string &message); + bool check_spend_proof(const crypto::hash &txid, const std::string &message, const std::string &sig_str); /*! * \brief GUI Address book get/store */ diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h index 8d6b2ffa9..101fceebb 100644 --- a/src/wallet/wallet2_api.h +++ b/src/wallet/wallet2_api.h @@ -703,6 +703,8 @@ struct Wallet virtual bool checkTxKey(const std::string &txid, std::string tx_key, const std::string &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) = 0; virtual std::string getTxProof(const std::string &txid, const std::string &address, const std::string &message, std::string &error_str) const = 0; virtual bool checkTxProof(const std::string &txid, const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &received, bool &in_pool, uint64_t &confirmations) = 0; + virtual std::string getSpendProof(const std::string &txid, const std::string &message) const = 0; + virtual bool checkSpendProof(const std::string &txid, const std::string &message, const std::string &signature, bool &good) const = 0; /* * \brief signMessage - sign a message with the spend private key diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index cf404543c..f540ba67a 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -1559,6 +1559,56 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_spend_proof(const wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + try + { + res.signature = m_wallet->get_spend_proof(txid, req.message); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_check_spend_proof(const wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + try + { + res.good = m_wallet->check_spend_proof(txid, req.message, req.signature); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_get_transfers(const wallet_rpc::COMMAND_RPC_GET_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFERS::response& res, epee::json_rpc::error& er) { if (!m_wallet) return not_open(er); diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 87e442bae..beeadba6b 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -97,6 +97,8 @@ namespace tools MAP_JON_RPC_WE("check_tx_key", on_check_tx_key, wallet_rpc::COMMAND_RPC_CHECK_TX_KEY) MAP_JON_RPC_WE("get_tx_proof", on_get_tx_proof, wallet_rpc::COMMAND_RPC_GET_TX_PROOF) MAP_JON_RPC_WE("check_tx_proof", on_check_tx_proof, wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF) + MAP_JON_RPC_WE("get_spend_proof", on_get_spend_proof, wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF) + MAP_JON_RPC_WE("check_spend_proof", on_check_spend_proof, wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF) MAP_JON_RPC_WE("get_transfers", on_get_transfers, wallet_rpc::COMMAND_RPC_GET_TRANSFERS) MAP_JON_RPC_WE("get_transfer_by_txid", on_get_transfer_by_txid, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID) MAP_JON_RPC_WE("sign", on_sign, wallet_rpc::COMMAND_RPC_SIGN) @@ -148,6 +150,8 @@ namespace tools bool on_check_tx_key(const wallet_rpc::COMMAND_RPC_CHECK_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_CHECK_TX_KEY::response& res, epee::json_rpc::error& er); bool on_get_tx_proof(const wallet_rpc::COMMAND_RPC_GET_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_TX_PROOF::response& res, epee::json_rpc::error& er); bool on_check_tx_proof(const wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF::response& res, epee::json_rpc::error& er); + bool on_get_spend_proof(const wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::response& res, epee::json_rpc::error& er); + bool on_check_spend_proof(const wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::response& res, epee::json_rpc::error& er); bool on_get_transfers(const wallet_rpc::COMMAND_RPC_GET_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFERS::response& res, epee::json_rpc::error& er); bool on_get_transfer_by_txid(const wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::response& res, epee::json_rpc::error& er); bool on_sign(const wallet_rpc::COMMAND_RPC_SIGN::request& req, wallet_rpc::COMMAND_RPC_SIGN::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 2e7c7c76f..1b15c0a99 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -967,6 +967,54 @@ namespace wallet_rpc END_KV_SERIALIZE_MAP() }; + struct COMMAND_RPC_GET_SPEND_PROOF + { + struct request + { + std::string txid; + std::string message; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + KV_SERIALIZE(message) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string signature; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(signature) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_CHECK_SPEND_PROOF + { + struct request + { + std::string txid; + std::string message; + std::string signature; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + KV_SERIALIZE(message) + KV_SERIALIZE(signature) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + bool good; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(good) + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_GET_TRANSFERS { struct request