aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/wallet2.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/wallet2.cpp')
-rw-r--r--src/wallet/wallet2.cpp110
1 files changed, 80 insertions, 30 deletions
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index b8d05e813..ae4af99f6 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -305,9 +305,9 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, address, std::string, String, false, std::string());
// compatibility checks
- if (!field_seed_found && !field_viewkey_found)
+ if (!field_seed_found && !field_viewkey_found && !field_spendkey_found)
{
- tools::fail_msg_writer() << tools::wallet2::tr("At least one of Electrum-style word list and private view key must be specified");
+ tools::fail_msg_writer() << tools::wallet2::tr("At least one of Electrum-style word list and private view key and private spend key must be specified");
return false;
}
if (field_seed_found && (field_viewkey_found || field_spendkey_found))
@@ -368,6 +368,10 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
{
wallet->generate(field_filename, field_password, recovery_key, recover, false);
}
+ else if (field_viewkey.empty() && !field_spendkey.empty())
+ {
+ wallet->generate(field_filename, field_password, spendkey, recover, false);
+ }
else
{
cryptonote::account_public_address address;
@@ -390,6 +394,11 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
}
address.m_spend_public_key = info.address.m_spend_public_key;
}
+ else
+ {
+ tools::fail_msg_writer() << tools::wallet2::tr("Address must be specified in order to create watch-only wallet");
+ return false;
+ }
wallet->generate(field_filename, field_password, address, viewkey);
}
else
@@ -444,6 +453,21 @@ std::string strjoin(const std::vector<size_t> &V, const char *sep)
return ss.str();
}
+static void emplace_or_replace(std::unordered_multimap<crypto::hash, tools::wallet2::pool_payment_details> &container,
+ const crypto::hash &key, const tools::wallet2::pool_payment_details &pd)
+{
+ auto range = container.equal_range(key);
+ for (auto i = range.first; i != range.second; ++i)
+ {
+ if (i->second.m_pd.m_tx_hash == pd.m_pd.m_tx_hash)
+ {
+ i->second = pd;
+ return;
+ }
+ }
+ container.emplace(key, pd);
+}
+
} //namespace
namespace tools
@@ -793,7 +817,7 @@ void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote
++num_vouts_received;
}
//----------------------------------------------------------------------------------------------------
-void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool)
+void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen)
{
// In this function, tx (probably) only contains the base information
// (that is, the prunable stuff may or may not be included)
@@ -1163,7 +1187,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
payment.m_timestamp = ts;
payment.m_subaddr_index = i.first;
if (pool) {
- m_unconfirmed_payments.emplace(payment_id, payment);
+ emplace_or_replace(m_unconfirmed_payments, payment_id, pool_payment_details{payment, double_spend_seen});
if (0 != m_callback)
m_callback->on_unconfirmed_money_received(height, txid, tx, payment.m_amount, payment.m_subaddr_index);
}
@@ -1241,7 +1265,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry
if(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height)
{
TIME_MEASURE_START(miner_tx_handle_time);
- process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, o_indices.indices[txidx++].indices, height, b.timestamp, true, false);
+ process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, o_indices.indices[txidx++].indices, height, b.timestamp, true, false, false);
TIME_MEASURE_FINISH(miner_tx_handle_time);
TIME_MEASURE_START(txs_handle_time);
@@ -1252,7 +1276,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry
cryptonote::transaction tx;
bool r = parse_and_validate_tx_base_from_blob(txblob, tx);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_parse_error, txblob);
- process_new_transaction(b.tx_hashes[idx], tx, o_indices.indices[txidx++].indices, height, b.timestamp, false, false);
+ process_new_transaction(b.tx_hashes[idx], tx, o_indices.indices[txidx++].indices, height, b.timestamp, false, false, false);
++idx;
}
TIME_MEASURE_FINISH(txs_handle_time);
@@ -1520,10 +1544,10 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei
void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes)
{
// remove pool txes to us that aren't in the pool anymore
- std::unordered_multimap<crypto::hash, wallet2::payment_details>::iterator uit = m_unconfirmed_payments.begin();
+ std::unordered_multimap<crypto::hash, wallet2::pool_payment_details>::iterator uit = m_unconfirmed_payments.begin();
while (uit != m_unconfirmed_payments.end())
{
- const crypto::hash &txid = uit->second.m_tx_hash;
+ const crypto::hash &txid = uit->second.m_pd.m_tx_hash;
bool found = false;
for (const auto &it2: tx_hashes)
{
@@ -1626,23 +1650,27 @@ void wallet2::update_pool_state(bool refreshed)
MDEBUG("update_pool_state done second loop");
// gather txids of new pool txes to us
- std::vector<crypto::hash> txids;
+ std::vector<std::pair<crypto::hash, bool>> txids;
for (const auto &txid: res.tx_hashes)
{
- if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end())
- {
- LOG_PRINT_L2("Already seen " << txid << ", skipped");
- continue;
- }
bool txid_found_in_up = false;
for (const auto &up: m_unconfirmed_payments)
{
- if (up.second.m_tx_hash == txid)
+ if (up.second.m_pd.m_tx_hash == txid)
{
txid_found_in_up = true;
break;
}
}
+ if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end())
+ {
+ // if it's for us, we want to keep track of whether we saw a double spend, so don't bail out
+ if (!txid_found_in_up)
+ {
+ LOG_PRINT_L2("Already seen " << txid << ", and not for us, skipped");
+ continue;
+ }
+ }
if (!txid_found_in_up)
{
LOG_PRINT_L1("Found new pool tx: " << txid);
@@ -1670,7 +1698,7 @@ void wallet2::update_pool_state(bool refreshed)
if (!found)
{
// not one of those we sent ourselves
- txids.push_back(txid);
+ txids.push_back({txid, false});
}
else
{
@@ -1680,6 +1708,7 @@ void wallet2::update_pool_state(bool refreshed)
else
{
LOG_PRINT_L1("Already saw that one, it's for us");
+ txids.push_back({txid, true});
}
}
@@ -1688,8 +1717,8 @@ void wallet2::update_pool_state(bool refreshed)
{
cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req;
cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res;
- for (const auto &txid: txids)
- req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
+ for (const auto &p: txids)
+ req.txs_hashes.push_back(epee::string_tools::pod_to_hex(p.first));
MDEBUG("asking for " << txids.size() << " transactions");
req.decode_as_json = false;
m_daemon_rpc_mutex.lock();
@@ -1711,10 +1740,11 @@ void wallet2::update_pool_state(bool refreshed)
{
if (cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash))
{
- const std::vector<crypto::hash>::const_iterator i = std::find(txids.begin(), txids.end(), tx_hash);
+ const std::vector<std::pair<crypto::hash, bool>>::const_iterator i = std::find_if(txids.begin(), txids.end(),
+ [tx_hash](const std::pair<crypto::hash, bool> &e) { return e.first == tx_hash; });
if (i != txids.end())
{
- process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true);
+ process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true, tx_entry.double_spend_seen);
m_scanned_pool_txs[0].insert(tx_hash);
if (m_scanned_pool_txs[0].size() > 5000)
{
@@ -3073,11 +3103,11 @@ void wallet2::get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wall
}
}
//----------------------------------------------------------------------------------------------------
-void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const
+void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::pool_payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const
{
for (auto i = m_unconfirmed_payments.begin(); i != m_unconfirmed_payments.end(); ++i) {
- if ((!subaddr_account || *subaddr_account == i->second.m_subaddr_index.major) &&
- (subaddr_indices.empty() || subaddr_indices.count(i->second.m_subaddr_index.minor) == 1))
+ if ((!subaddr_account || *subaddr_account == i->second.m_pd.m_subaddr_index.major) &&
+ (subaddr_indices.empty() || subaddr_indices.count(i->second.m_pd.m_subaddr_index.minor) == 1))
unconfirmed_payments.push_back(*i);
}
}
@@ -5130,7 +5160,7 @@ void wallet2::light_wallet_get_address_txs()
payments_txs.push_back(p.second.m_tx_hash);
std::vector<crypto::hash> unconfirmed_payments_txs;
for(const auto &up: m_unconfirmed_payments)
- unconfirmed_payments_txs.push_back(up.second.m_tx_hash);
+ unconfirmed_payments_txs.push_back(up.second.m_pd.m_tx_hash);
// for balance calculation
uint64_t wallet_total_sent = 0;
@@ -5196,7 +5226,11 @@ void wallet2::light_wallet_get_address_txs()
if (t.mempool) {
if (std::find(unconfirmed_payments_txs.begin(), unconfirmed_payments_txs.end(), tx_hash) == unconfirmed_payments_txs.end()) {
pool_txs.push_back(tx_hash);
- m_unconfirmed_payments.emplace(tx_hash, payment);
+ // assume false as we don't get that info from the light wallet server
+ crypto::hash payment_id;
+ THROW_WALLET_EXCEPTION_IF(!epee::string_tools::hex_to_pod(t.payment_id, payment_id),
+ error::wallet_internal_error, "Failed to parse payment id");
+ emplace_or_replace(m_unconfirmed_payments, payment_id, pool_payment_details{payment, false});
if (0 != m_callback) {
m_callback->on_lw_unconfirmed_money_received(t.height, payment.m_tx_hash, payment.m_amount);
}
@@ -5326,11 +5360,27 @@ bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image,
crypto::key_image calculated_key_image;
cryptonote::keypair in_ephemeral;
- // Subaddresses aren't supported in mymonero/openmonero yet. Using empty values.
- const std::vector<crypto::public_key> additional_tx_pub_keys;
- const crypto::public_key pkey = crypto::null_pkey;
-
- cryptonote::generate_key_image_helper(get_account().get_keys(), m_subaddresses, pkey, tx_public_key, additional_tx_pub_keys, out_index, in_ephemeral, calculated_key_image);
+ // Subaddresses aren't supported in mymonero/openmonero yet. Roll out the original scheme:
+ // compute D = a*R
+ // compute P = Hs(D || i)*G + B
+ // compute x = Hs(D || i) + b (and check if P==x*G)
+ // compute I = x*Hp(P)
+ const account_keys& ack = get_account().get_keys();
+ crypto::key_derivation derivation;
+ bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, derivation);
+ CHECK_AND_ASSERT_MES(r, false, "failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")");
+
+ r = crypto::derive_public_key(derivation, out_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub);
+ CHECK_AND_ASSERT_MES(r, false, "failed to derive_public_key (" << derivation << ", " << out_index << ", " << ack.m_account_address.m_spend_public_key << ")");
+
+ crypto::derive_secret_key(derivation, out_index, ack.m_spend_secret_key, in_ephemeral.sec);
+ crypto::public_key out_pkey_test;
+ r = crypto::secret_key_to_public_key(in_ephemeral.sec, out_pkey_test);
+ CHECK_AND_ASSERT_MES(r, false, "failed to secret_key_to_public_key(" << in_ephemeral.sec << ")");
+ CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_pkey_test, false, "derived secret key doesn't match derived public key");
+
+ crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, calculated_key_image);
+
index_keyimage_map.emplace(out_index, calculated_key_image);
m_key_image_cache.emplace(tx_public_key, index_keyimage_map);
return key_image == calculated_key_image;