mirror of
https://codeberg.org/anoncontributorxmr/monero.git
synced 2024-12-23 13:47:47 +00:00
Reserve proof
This commit is contained in:
parent
35d5aa36c9
commit
6d40a92026
10 changed files with 580 additions and 0 deletions
|
@ -1690,6 +1690,16 @@ simple_wallet::simple_wallet()
|
|||
boost::bind(&simple_wallet::check_spend_proof, this, _1),
|
||||
tr("check_spend_proof <txid> <signature_file> [<message>]"),
|
||||
tr("Check a signature proving that the signer generated <txid>, optionally with a challenge string <message>."));
|
||||
m_cmd_binder.set_handler("get_reserve_proof",
|
||||
boost::bind(&simple_wallet::get_reserve_proof, this, _1),
|
||||
tr("get_reserve_proof (all|<amount>) [<message>]"),
|
||||
tr("Generate a signature proving that you own at least this much, optionally with a challenge string <message>.\n"
|
||||
"If 'all' is specified, you prove the entire sum of all of your existing accounts' balances.\n"
|
||||
"Otherwise, you prove the reserve of the smallest possible amount above <amount> available in your current account."));
|
||||
m_cmd_binder.set_handler("check_reserve_proof",
|
||||
boost::bind(&simple_wallet::check_reserve_proof, this, _1),
|
||||
tr("check_reserve_proof <address> <signature_file> [<message>]"),
|
||||
tr("Check a signature proving that the owner of <address> holds at least this much, optionally with a challenge string <message>."));
|
||||
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>]]"),
|
||||
|
@ -5125,6 +5135,110 @@ bool simple_wallet::check_spend_proof(const std::vector<std::string> &args)
|
|||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args)
|
||||
{
|
||||
if(args.size() != 1 && args.size() != 2) {
|
||||
fail_msg_writer() << tr("usage: get_reserve_proof (all|<amount>) [<message>]");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_wallet->watch_only() || m_wallet->multisig())
|
||||
{
|
||||
fail_msg_writer() << tr("The reserve proof can be generated only by a full wallet");
|
||||
return true;
|
||||
}
|
||||
|
||||
boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve;
|
||||
if (args[0] != "all")
|
||||
{
|
||||
account_minreserve = std::pair<uint32_t, uint64_t>();
|
||||
account_minreserve->first = m_current_subaddress_account;
|
||||
if (!cryptonote::parse_amount(account_minreserve->second, args[0]))
|
||||
{
|
||||
fail_msg_writer() << tr("amount is wrong: ") << args[0];
|
||||
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; }
|
||||
|
||||
LOCK_IDLE_SCOPE();
|
||||
|
||||
try
|
||||
{
|
||||
const std::string sig_str = m_wallet->get_reserve_proof(account_minreserve, args.size() == 2 ? args[1] : "");
|
||||
const std::string filename = "monero_reserve_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_reserve_proof(const std::vector<std::string> &args)
|
||||
{
|
||||
if(args.size() != 2 && args.size() != 3) {
|
||||
fail_msg_writer() << tr("usage: check_reserve_proof <address> <signature_file> [<message>]");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!try_connect_to_daemon())
|
||||
{
|
||||
fail_msg_writer() << tr("failed to connect to the daemon");
|
||||
return true;
|
||||
}
|
||||
|
||||
cryptonote::address_parse_info info;
|
||||
if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), args[0], oa_prompter))
|
||||
{
|
||||
fail_msg_writer() << tr("failed to parse address");
|
||||
return true;
|
||||
}
|
||||
if (info.is_subaddress)
|
||||
{
|
||||
fail_msg_writer() << tr("Address must not be a subaddress");
|
||||
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;
|
||||
}
|
||||
|
||||
LOCK_IDLE_SCOPE();
|
||||
|
||||
try
|
||||
{
|
||||
uint64_t total, spent;
|
||||
if (m_wallet->check_reserve_proof(info.address, args.size() == 3 ? args[2] : "", sig_str, total, spent))
|
||||
{
|
||||
success_msg_writer() << boost::format(tr("Good signature -- total: %s, spent: %s, unspent: %s")) % print_money(total) % print_money(spent) % print_money(total - spent);
|
||||
}
|
||||
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];
|
||||
|
|
|
@ -170,6 +170,8 @@ namespace cryptonote
|
|||
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 get_reserve_proof(const std::vector<std::string> &args);
|
||||
bool check_reserve_proof(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 rescan_blockchain(const std::vector<std::string> &args);
|
||||
|
|
|
@ -1576,6 +1576,55 @@ bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string
|
|||
}
|
||||
}
|
||||
|
||||
std::string WalletImpl::getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const {
|
||||
try
|
||||
{
|
||||
m_status = Status_Ok;
|
||||
boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve;
|
||||
if (!all)
|
||||
{
|
||||
account_minreserve = std::make_pair(account_index, amount);
|
||||
}
|
||||
return m_wallet->get_reserve_proof(account_minreserve, message);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
m_status = Status_Error;
|
||||
m_errorString = e.what();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
bool WalletImpl::checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) const {
|
||||
cryptonote::address_parse_info info;
|
||||
if (!cryptonote::get_account_address_from_str(info, m_wallet->testnet(), address))
|
||||
{
|
||||
m_status = Status_Error;
|
||||
m_errorString = tr("Failed to parse address");
|
||||
return false;
|
||||
}
|
||||
if (info.is_subaddress)
|
||||
{
|
||||
m_status = Status_Error;
|
||||
m_errorString = tr("Address must not be a subaddress");
|
||||
return false;
|
||||
}
|
||||
|
||||
good = false;
|
||||
try
|
||||
{
|
||||
m_status = Status_Ok;
|
||||
good = m_wallet->check_reserve_proof(info.address, message, signature, total, spent);
|
||||
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);
|
||||
|
|
|
@ -142,6 +142,8 @@ public:
|
|||
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 getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const;
|
||||
virtual bool checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) 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();
|
||||
|
|
|
@ -706,6 +706,12 @@ struct Wallet
|
|||
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 getReserveProof - Generates a proof that proves the reserve of unspent funds
|
||||
* Parameters `account_index` and `amount` are ignored when `all` is true
|
||||
*/
|
||||
virtual std::string getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const = 0;
|
||||
virtual bool checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) const = 0;
|
||||
|
||||
/*
|
||||
* \brief signMessage - sign a message with the spend private key
|
||||
|
|
|
@ -7947,6 +7947,251 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account
|
|||
return false;
|
||||
}
|
||||
|
||||
std::string wallet2::get_reserve_proof(const boost::optional<std::pair<uint32_t, uint64_t>> &account_minreserve, const std::string &message)
|
||||
{
|
||||
THROW_WALLET_EXCEPTION_IF(m_watch_only || m_multisig, error::wallet_internal_error, "Reserve proof can only be generated by a full wallet");
|
||||
THROW_WALLET_EXCEPTION_IF(balance_all() == 0, error::wallet_internal_error, "Zero balance");
|
||||
THROW_WALLET_EXCEPTION_IF(account_minreserve && balance(account_minreserve->first) < account_minreserve->second, error::wallet_internal_error,
|
||||
"Not enough balance in this account for the requested minimum reserve amount");
|
||||
|
||||
// determine which outputs to include in the proof
|
||||
std::vector<size_t> selected_transfers;
|
||||
for (size_t i = 0; i < m_transfers.size(); ++i)
|
||||
{
|
||||
const transfer_details &td = m_transfers[i];
|
||||
if (!td.m_spent && (!account_minreserve || account_minreserve->first == td.m_subaddr_index.major))
|
||||
selected_transfers.push_back(i);
|
||||
}
|
||||
|
||||
if (account_minreserve)
|
||||
{
|
||||
// minimize the number of outputs included in the proof, by only picking the N largest outputs that can cover the requested min reserve amount
|
||||
std::sort(selected_transfers.begin(), selected_transfers.end(), [&](const size_t a, const size_t b)
|
||||
{ return m_transfers[a].amount() > m_transfers[b].amount(); });
|
||||
while (selected_transfers.size() >= 2 && m_transfers[selected_transfers[1]].amount() >= account_minreserve->second)
|
||||
selected_transfers.erase(selected_transfers.begin());
|
||||
size_t sz = 0;
|
||||
uint64_t total = 0;
|
||||
while (total < account_minreserve->second)
|
||||
{
|
||||
total += m_transfers[selected_transfers[sz]].amount();
|
||||
++sz;
|
||||
}
|
||||
selected_transfers.resize(sz);
|
||||
}
|
||||
|
||||
// compute signature prefix hash
|
||||
std::string prefix_data = message;
|
||||
prefix_data.append((const char*)&m_account.get_keys().m_account_address, sizeof(cryptonote::account_public_address));
|
||||
for (size_t i = 0; i < selected_transfers.size(); ++i)
|
||||
{
|
||||
prefix_data.append((const char*)&m_transfers[selected_transfers[i]].m_key_image, sizeof(crypto::key_image));
|
||||
}
|
||||
crypto::hash prefix_hash;
|
||||
crypto::cn_fast_hash(prefix_data.data(), prefix_data.size(), prefix_hash);
|
||||
|
||||
// generate proof entries
|
||||
std::vector<reserve_proof_entry> proofs(selected_transfers.size());
|
||||
std::unordered_set<cryptonote::subaddress_index> subaddr_indices = { {0,0} };
|
||||
for (size_t i = 0; i < selected_transfers.size(); ++i)
|
||||
{
|
||||
const transfer_details &td = m_transfers[selected_transfers[i]];
|
||||
reserve_proof_entry& proof = proofs[i];
|
||||
proof.txid = td.m_txid;
|
||||
proof.index_in_tx = td.m_internal_output_index;
|
||||
proof.key_image = td.m_key_image;
|
||||
subaddr_indices.insert(td.m_subaddr_index);
|
||||
|
||||
// get tx pub key
|
||||
const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(td.m_tx, td.m_pk_index);
|
||||
THROW_WALLET_EXCEPTION_IF(tx_pub_key == crypto::null_pkey, error::wallet_internal_error, "The tx public key isn't found");
|
||||
const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx);
|
||||
|
||||
// determine which tx pub key was used for deriving the output key
|
||||
const crypto::public_key *tx_pub_key_used = &tx_pub_key;
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
proof.shared_secret = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(*tx_pub_key_used), rct::sk2rct(m_account.get_keys().m_view_secret_key)));
|
||||
crypto::key_derivation derivation;
|
||||
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(proof.shared_secret, rct::rct2sk(rct::I), derivation),
|
||||
error::wallet_internal_error, "Failed to generate key derivation");
|
||||
crypto::public_key subaddress_spendkey;
|
||||
THROW_WALLET_EXCEPTION_IF(!derive_subaddress_public_key(td.get_public_key(), derivation, proof.index_in_tx, subaddress_spendkey),
|
||||
error::wallet_internal_error, "Failed to derive subaddress public key");
|
||||
if (m_subaddresses.count(subaddress_spendkey) == 1)
|
||||
break;
|
||||
THROW_WALLET_EXCEPTION_IF(additional_tx_pub_keys.empty(), error::wallet_internal_error,
|
||||
"Normal tx pub key doesn't derive the expected output, while the additional tx pub keys are empty");
|
||||
THROW_WALLET_EXCEPTION_IF(i == 1, error::wallet_internal_error,
|
||||
"Neither normal tx pub key nor additional tx pub key derive the expected output key");
|
||||
tx_pub_key_used = &additional_tx_pub_keys[proof.index_in_tx];
|
||||
}
|
||||
|
||||
// generate signature for shared secret
|
||||
crypto::generate_tx_proof(prefix_hash, m_account.get_keys().m_account_address.m_view_public_key, *tx_pub_key_used, boost::none, proof.shared_secret, m_account.get_keys().m_view_secret_key, proof.shared_secret_sig);
|
||||
|
||||
// derive ephemeral secret key
|
||||
crypto::key_image ki;
|
||||
cryptonote::keypair ephemeral;
|
||||
const bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, td.get_public_key(), tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, ephemeral, ki);
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image");
|
||||
THROW_WALLET_EXCEPTION_IF(ephemeral.pub != td.get_public_key(), error::wallet_internal_error, "Derived public key doesn't agree with the stored one");
|
||||
|
||||
// generate signature for key image
|
||||
const std::vector<const crypto::public_key*> pubs = { &ephemeral.pub };
|
||||
crypto::generate_ring_signature(prefix_hash, td.m_key_image, &pubs[0], 1, ephemeral.sec, 0, &proof.key_image_sig);
|
||||
}
|
||||
|
||||
// collect all subaddress spend keys that received those outputs and generate their signatures
|
||||
std::unordered_map<crypto::public_key, crypto::signature> subaddr_spendkeys;
|
||||
for (const cryptonote::subaddress_index &index : subaddr_indices)
|
||||
{
|
||||
crypto::secret_key subaddr_spend_skey = m_account.get_keys().m_spend_secret_key;
|
||||
if (!index.is_zero())
|
||||
{
|
||||
crypto::secret_key m = cryptonote::get_subaddress_secret_key(m_account.get_keys().m_view_secret_key, index);
|
||||
crypto::secret_key tmp = subaddr_spend_skey;
|
||||
sc_add((unsigned char*)&subaddr_spend_skey, (unsigned char*)&m, (unsigned char*)&tmp);
|
||||
}
|
||||
crypto::public_key subaddr_spend_pkey;
|
||||
secret_key_to_public_key(subaddr_spend_skey, subaddr_spend_pkey);
|
||||
crypto::generate_signature(prefix_hash, subaddr_spend_pkey, subaddr_spend_skey, subaddr_spendkeys[subaddr_spend_pkey]);
|
||||
}
|
||||
|
||||
// serialize & encode
|
||||
std::ostringstream oss;
|
||||
boost::archive::portable_binary_oarchive ar(oss);
|
||||
ar << proofs << subaddr_spendkeys;
|
||||
return "ReserveProofV1" + tools::base58::encode(oss.str());
|
||||
}
|
||||
|
||||
bool wallet2::check_reserve_proof(const cryptonote::account_public_address &address, const std::string &message, const std::string &sig_str, uint64_t &total, uint64_t &spent)
|
||||
{
|
||||
uint32_t rpc_version;
|
||||
THROW_WALLET_EXCEPTION_IF(!check_connection(&rpc_version), error::wallet_internal_error, "Failed to connect to daemon: " + get_daemon_address());
|
||||
THROW_WALLET_EXCEPTION_IF(rpc_version < MAKE_CORE_RPC_VERSION(1, 0), error::wallet_internal_error, "Daemon RPC version is too old");
|
||||
|
||||
static constexpr char header[] = "ReserveProofV1";
|
||||
THROW_WALLET_EXCEPTION_IF(!boost::string_ref{sig_str}.starts_with(header), error::wallet_internal_error,
|
||||
"Signature header check error");
|
||||
|
||||
std::string sig_decoded;
|
||||
THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(std::strlen(header)), sig_decoded), error::wallet_internal_error,
|
||||
"Signature decoding error");
|
||||
|
||||
std::istringstream iss(sig_decoded);
|
||||
boost::archive::portable_binary_iarchive ar(iss);
|
||||
std::vector<reserve_proof_entry> proofs;
|
||||
std::unordered_map<crypto::public_key, crypto::signature> subaddr_spendkeys;
|
||||
ar >> proofs >> subaddr_spendkeys;
|
||||
|
||||
THROW_WALLET_EXCEPTION_IF(subaddr_spendkeys.count(address.m_spend_public_key) == 0, error::wallet_internal_error,
|
||||
"The given address isn't found in the proof");
|
||||
|
||||
// compute signature prefix hash
|
||||
std::string prefix_data = message;
|
||||
prefix_data.append((const char*)&address, sizeof(cryptonote::account_public_address));
|
||||
for (size_t i = 0; i < proofs.size(); ++i)
|
||||
{
|
||||
prefix_data.append((const char*)&proofs[i].key_image, sizeof(crypto::key_image));
|
||||
}
|
||||
crypto::hash prefix_hash;
|
||||
crypto::cn_fast_hash(prefix_data.data(), prefix_data.size(), prefix_hash);
|
||||
|
||||
// fetch txes from daemon
|
||||
COMMAND_RPC_GET_TRANSACTIONS::request gettx_req;
|
||||
COMMAND_RPC_GET_TRANSACTIONS::response gettx_res;
|
||||
for (size_t i = 0; i < proofs.size(); ++i)
|
||||
gettx_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(proofs[i].txid));
|
||||
m_daemon_rpc_mutex.lock();
|
||||
bool ok = net_utils::invoke_http_json("/gettransactions", gettx_req, gettx_res, m_http_client);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
THROW_WALLET_EXCEPTION_IF(!ok || gettx_res.txs.size() != proofs.size(),
|
||||
error::wallet_internal_error, "Failed to get transaction from daemon");
|
||||
|
||||
// check spent status
|
||||
COMMAND_RPC_IS_KEY_IMAGE_SPENT::request kispent_req;
|
||||
COMMAND_RPC_IS_KEY_IMAGE_SPENT::response kispent_res;
|
||||
for (size_t i = 0; i < proofs.size(); ++i)
|
||||
kispent_req.key_images.push_back(epee::string_tools::pod_to_hex(proofs[i].key_image));
|
||||
m_daemon_rpc_mutex.lock();
|
||||
ok = epee::net_utils::invoke_http_json("/is_key_image_spent", kispent_req, kispent_res, m_http_client, rpc_timeout);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
THROW_WALLET_EXCEPTION_IF(!ok || kispent_res.spent_status.size() != proofs.size(),
|
||||
error::wallet_internal_error, "Failed to get key image spent status from daemon");
|
||||
|
||||
total = spent = 0;
|
||||
for (size_t i = 0; i < proofs.size(); ++i)
|
||||
{
|
||||
const reserve_proof_entry& proof = proofs[i];
|
||||
THROW_WALLET_EXCEPTION_IF(gettx_res.txs[i].in_pool, error::wallet_internal_error, "Tx is unconfirmed");
|
||||
|
||||
cryptonote::blobdata tx_data;
|
||||
ok = string_tools::parse_hexstr_to_binbuff(gettx_res.txs[i].as_hex, tx_data);
|
||||
THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
|
||||
|
||||
crypto::hash tx_hash, tx_prefix_hash;
|
||||
cryptonote::transaction tx;
|
||||
THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error,
|
||||
"Failed to validate transaction from daemon");
|
||||
THROW_WALLET_EXCEPTION_IF(tx_hash != proof.txid, error::wallet_internal_error, "Failed to get the right transaction from daemon");
|
||||
|
||||
THROW_WALLET_EXCEPTION_IF(proof.index_in_tx >= tx.vout.size(), error::wallet_internal_error, "index_in_tx is out of bound");
|
||||
|
||||
const cryptonote::txout_to_key* const out_key = boost::get<cryptonote::txout_to_key>(std::addressof(tx.vout[proof.index_in_tx].target));
|
||||
THROW_WALLET_EXCEPTION_IF(!out_key, error::wallet_internal_error, "Output key wasn't found")
|
||||
|
||||
// get tx pub key
|
||||
const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx);
|
||||
THROW_WALLET_EXCEPTION_IF(tx_pub_key == crypto::null_pkey, error::wallet_internal_error, "The tx public key isn't found");
|
||||
const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx);
|
||||
|
||||
// check singature for shared secret
|
||||
ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, proof.shared_secret, proof.shared_secret_sig);
|
||||
if (!ok && additional_tx_pub_keys.size() == tx.vout.size())
|
||||
ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[proof.index_in_tx], boost::none, proof.shared_secret, proof.shared_secret_sig);
|
||||
if (!ok)
|
||||
return false;
|
||||
|
||||
// check signature for key image
|
||||
const std::vector<const crypto::public_key*> pubs = { &out_key->key };
|
||||
ok = crypto::check_ring_signature(prefix_hash, proof.key_image, &pubs[0], 1, &proof.key_image_sig);
|
||||
if (!ok)
|
||||
return false;
|
||||
|
||||
// check if the address really received the fund
|
||||
crypto::key_derivation derivation;
|
||||
THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(proof.shared_secret, rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation");
|
||||
crypto::public_key subaddr_spendkey;
|
||||
crypto::derive_subaddress_public_key(out_key->key, derivation, proof.index_in_tx, subaddr_spendkey);
|
||||
THROW_WALLET_EXCEPTION_IF(subaddr_spendkeys.count(subaddr_spendkey) == 0, error::wallet_internal_error,
|
||||
"The address doesn't seem to have received the fund");
|
||||
|
||||
// check amount
|
||||
uint64_t amount = tx.vout[proof.index_in_tx].amount;
|
||||
if (amount == 0)
|
||||
{
|
||||
// decode rct
|
||||
crypto::secret_key shared_secret;
|
||||
crypto::derivation_to_scalar(derivation, proof.index_in_tx, shared_secret);
|
||||
rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[proof.index_in_tx];
|
||||
rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret));
|
||||
amount = rct::h2d(ecdh_info.amount);
|
||||
}
|
||||
total += amount;
|
||||
if (kispent_res.spent_status[i])
|
||||
spent += amount;
|
||||
}
|
||||
|
||||
// check signatures for all subaddress spend keys
|
||||
for (const auto &i : subaddr_spendkeys)
|
||||
{
|
||||
if (!crypto::check_signature(prefix_hash, i.first, i.second))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string wallet2::get_wallet_file() const
|
||||
{
|
||||
return m_wallet_file;
|
||||
|
|
|
@ -433,6 +433,16 @@ namespace tools
|
|||
bool m_is_subaddress;
|
||||
};
|
||||
|
||||
struct reserve_proof_entry
|
||||
{
|
||||
crypto::hash txid;
|
||||
uint64_t index_in_tx;
|
||||
crypto::public_key shared_secret;
|
||||
crypto::key_image key_image;
|
||||
crypto::signature shared_secret_sig;
|
||||
crypto::signature key_image_sig;
|
||||
};
|
||||
|
||||
typedef std::tuple<uint64_t, crypto::public_key, rct::key> get_outs_entry;
|
||||
|
||||
/*!
|
||||
|
@ -836,6 +846,26 @@ namespace tools
|
|||
|
||||
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 Generates a proof that proves the reserve of unspent funds
|
||||
* \param account_minreserve When specified, collect outputs only belonging to the given account and prove the smallest reserve above the given amount
|
||||
* When unspecified, proves for all unspent outputs across all accounts
|
||||
* \param message Arbitrary challenge message to be signed together
|
||||
* \return Signature string
|
||||
*/
|
||||
std::string get_reserve_proof(const boost::optional<std::pair<uint32_t, uint64_t>> &account_minreserve, const std::string &message);
|
||||
/*!
|
||||
* \brief Verifies a proof of reserve
|
||||
* \param address The signer's address
|
||||
* \param message Challenge message used for signing
|
||||
* \param sig_str Signature string
|
||||
* \param total [OUT] the sum of funds included in the signature
|
||||
* \param spent [OUT] the sum of spent funds included in the signature
|
||||
* \return true if the signature verifies correctly
|
||||
*/
|
||||
bool check_reserve_proof(const cryptonote::account_public_address &address, const std::string &message, const std::string &sig_str, uint64_t &total, uint64_t &spent);
|
||||
|
||||
/*!
|
||||
* \brief GUI Address book get/store
|
||||
*/
|
||||
|
@ -1119,6 +1149,7 @@ BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1)
|
|||
BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 7)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 5)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::reserve_proof_entry, 0)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 2)
|
||||
|
@ -1401,6 +1432,17 @@ namespace boost
|
|||
a & x.m_is_subaddress;
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
inline void serialize(Archive& a, tools::wallet2::reserve_proof_entry& x, const boost::serialization::version_type ver)
|
||||
{
|
||||
a & x.txid;
|
||||
a & x.index_in_tx;
|
||||
a & x.shared_secret;
|
||||
a & x.key_image;
|
||||
a & x.shared_secret_sig;
|
||||
a & x.key_image_sig;
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
inline void serialize(Archive &a, tools::wallet2::unsigned_tx_set &x, const boost::serialization::version_type ver)
|
||||
{
|
||||
|
|
|
@ -1721,6 +1721,66 @@ namespace tools
|
|||
return true;
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
bool wallet_rpc_server::on_get_reserve_proof(const wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::response& res, epee::json_rpc::error& er)
|
||||
{
|
||||
if (!m_wallet) return not_open(er);
|
||||
|
||||
boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve;
|
||||
if (!req.all)
|
||||
{
|
||||
if (req.account_index >= m_wallet->get_num_subaddress_accounts())
|
||||
{
|
||||
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
|
||||
er.message = "Account index is out of bound";
|
||||
return false;
|
||||
}
|
||||
account_minreserve = std::make_pair(req.account_index, req.amount);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
res.signature = m_wallet->get_reserve_proof(account_minreserve, 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_reserve_proof(const wallet_rpc::COMMAND_RPC_CHECK_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_RESERVE_PROOF::response& res, epee::json_rpc::error& er)
|
||||
{
|
||||
if (!m_wallet) return not_open(er);
|
||||
|
||||
cryptonote::address_parse_info info;
|
||||
if (!get_account_address_from_str(info, m_wallet->testnet(), req.address))
|
||||
{
|
||||
er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS;
|
||||
er.message = "Invalid address";
|
||||
return false;
|
||||
}
|
||||
if (info.is_subaddress)
|
||||
{
|
||||
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
|
||||
er.message = "Address must not be a subaddress";
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
res.good = m_wallet->check_reserve_proof(info.address, req.message, req.signature, res.total, res.spent);
|
||||
}
|
||||
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);
|
||||
|
|
|
@ -104,6 +104,8 @@ namespace tools
|
|||
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_reserve_proof", on_get_reserve_proof, wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF)
|
||||
MAP_JON_RPC_WE("check_reserve_proof", on_check_reserve_proof, wallet_rpc::COMMAND_RPC_CHECK_RESERVE_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)
|
||||
|
@ -170,6 +172,8 @@ namespace tools
|
|||
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_reserve_proof(const wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::response& res, epee::json_rpc::error& er);
|
||||
bool on_check_reserve_proof(const wallet_rpc::COMMAND_RPC_CHECK_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_RESERVE_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);
|
||||
|
|
|
@ -1180,6 +1180,62 @@ namespace wallet_rpc
|
|||
};
|
||||
};
|
||||
|
||||
struct COMMAND_RPC_GET_RESERVE_PROOF
|
||||
{
|
||||
struct request
|
||||
{
|
||||
bool all;
|
||||
uint32_t account_index; // ignored when `all` is true
|
||||
uint64_t amount; // ignored when `all` is true
|
||||
std::string message;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(all)
|
||||
KV_SERIALIZE(account_index)
|
||||
KV_SERIALIZE(amount)
|
||||
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_RESERVE_PROOF
|
||||
{
|
||||
struct request
|
||||
{
|
||||
std::string address;
|
||||
std::string message;
|
||||
std::string signature;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(address)
|
||||
KV_SERIALIZE(message)
|
||||
KV_SERIALIZE(signature)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
struct response
|
||||
{
|
||||
bool good;
|
||||
uint64_t total;
|
||||
uint64_t spent;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(good)
|
||||
KV_SERIALIZE(total)
|
||||
KV_SERIALIZE(spent)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
};
|
||||
|
||||
struct COMMAND_RPC_GET_TRANSFERS
|
||||
{
|
||||
struct request
|
||||
|
|
Loading…
Reference in a new issue