mirror of
https://github.com/monero-project/monero.git
synced 2024-11-18 00:37:43 +00:00
Spend proof without txkey
This commit is contained in:
parent
49ce59462a
commit
b0b7e0f09a
10 changed files with 473 additions and 0 deletions
|
@ -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 <address> in <txid>"));
|
m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to <address> in <txid>"));
|
||||||
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 <address> in <txid> using the transaction/view secret key"));
|
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 <address> in <txid> 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 <address> in <txid>"));
|
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 <address> in <txid>"));
|
||||||
|
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 <txid> 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 <txid>"));
|
||||||
m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out|pending|failed|pool] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] - Show incoming/outgoing transfers within an optional height range"));
|
m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out|pending|failed|pool] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] - 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=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]] - Show unspent outputs of a specified address within an optional amount range"));
|
m_cmd_binder.set_handler("unspent_outputs", boost::bind(&simple_wallet::unspent_outputs, this, _1), tr("unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]] - 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"));
|
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<std::string> &args)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
|
bool simple_wallet::get_spend_proof(const std::vector<std::string> &args)
|
||||||
|
{
|
||||||
|
if(args.size() != 1 && args.size() != 2) {
|
||||||
|
fail_msg_writer() << tr("usage: get_spend_proof <txid> [<message>]");
|
||||||
|
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<std::string> &args)
|
||||||
|
{
|
||||||
|
if(args.size() != 2 && args.size() != 3) {
|
||||||
|
fail_msg_writer() << tr("usage: check_spend_proof <txid> <signature_file> [<message>]");
|
||||||
|
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)
|
static std::string get_human_readable_timestamp(uint64_t ts)
|
||||||
{
|
{
|
||||||
char buffer[64];
|
char buffer[64];
|
||||||
|
|
|
@ -163,6 +163,8 @@ namespace cryptonote
|
||||||
bool check_tx_key(const std::vector<std::string> &args);
|
bool check_tx_key(const std::vector<std::string> &args);
|
||||||
bool get_tx_proof(const std::vector<std::string> &args);
|
bool get_tx_proof(const std::vector<std::string> &args);
|
||||||
bool check_tx_proof(const std::vector<std::string> &args);
|
bool check_tx_proof(const std::vector<std::string> &args);
|
||||||
|
bool get_spend_proof(const std::vector<std::string> &args);
|
||||||
|
bool check_spend_proof(const std::vector<std::string> &args);
|
||||||
bool show_transfers(const std::vector<std::string> &args);
|
bool show_transfers(const std::vector<std::string> &args);
|
||||||
bool unspent_outputs(const std::vector<std::string> &args);
|
bool unspent_outputs(const std::vector<std::string> &args);
|
||||||
bool rescan_blockchain(const std::vector<std::string> &args);
|
bool rescan_blockchain(const std::vector<std::string> &args);
|
||||||
|
|
|
@ -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)
|
std::string WalletImpl::signMessage(const std::string &message)
|
||||||
{
|
{
|
||||||
return m_wallet->sign(message);
|
return m_wallet->sign(message);
|
||||||
|
|
|
@ -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 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 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 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 std::string signMessage(const std::string &message);
|
||||||
virtual bool verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const;
|
virtual bool verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const;
|
||||||
virtual void startRefresh();
|
virtual void startRefresh();
|
||||||
|
|
|
@ -6209,6 +6209,236 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s
|
||||||
additional_tx_keys = j->second;
|
additional_tx_keys = j->second;
|
||||||
return true;
|
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<boost::mutex> 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<std::vector<crypto::signature>> 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<txin_to_key>(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<txout_to_key>(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<crypto::public_key> 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<uint64_t> 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<boost::mutex> 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<const crypto::public_key *> 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<crypto::signature>());
|
||||||
|
std::vector<crypto::signature>& 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<crypto::signature>& 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<boost::mutex> 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<txin_to_key>(std::addressof(tx.vin[i]));
|
||||||
|
if (in_key != nullptr)
|
||||||
|
num_sigs += in_key->key_offsets.size();
|
||||||
|
}
|
||||||
|
std::vector<std::vector<crypto::signature>> signatures = { std::vector<crypto::signature>(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<txin_to_key>(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<std::vector<crypto::signature>>::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<txin_to_key>(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<uint64_t> 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<boost::mutex> 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<const crypto::public_key *> 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<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations)
|
void wallet2::check_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
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);
|
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
|
* \brief GUI Address book get/store
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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 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 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 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
|
* \brief signMessage - sign a message with the spend private key
|
||||||
|
|
|
@ -1559,6 +1559,56 @@ namespace tools
|
||||||
return true;
|
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)
|
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);
|
if (!m_wallet) return not_open(er);
|
||||||
|
|
|
@ -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("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("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("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_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("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)
|
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_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_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_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_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_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);
|
bool on_sign(const wallet_rpc::COMMAND_RPC_SIGN::request& req, wallet_rpc::COMMAND_RPC_SIGN::response& res, epee::json_rpc::error& er);
|
||||||
|
|
|
@ -967,6 +967,54 @@ namespace wallet_rpc
|
||||||
END_KV_SERIALIZE_MAP()
|
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 COMMAND_RPC_GET_TRANSFERS
|
||||||
{
|
{
|
||||||
struct request
|
struct request
|
||||||
|
|
Loading…
Reference in a new issue