aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--contrib/epee/include/serialization/keyvalue_serialization.h9
-rw-r--r--src/cryptonote_basic/account.cpp66
-rw-r--r--src/cryptonote_basic/account.h16
-rw-r--r--src/gen_multisig/gen_multisig.cpp4
-rw-r--r--src/simplewallet/simplewallet.cpp237
-rw-r--r--src/simplewallet/simplewallet.h1
-rw-r--r--src/wallet/api/wallet.cpp3
-rw-r--r--src/wallet/wallet2.cpp312
-rw-r--r--src/wallet/wallet2.h57
-rw-r--r--src/wallet/wallet_errors.h9
-rw-r--r--src/wallet/wallet_rpc_server.cpp10
-rw-r--r--tests/functional_tests/transactions_flow_test.cpp4
-rw-r--r--tests/unit_tests/CMakeLists.txt1
-rw-r--r--tests/unit_tests/account.cpp71
-rw-r--r--tests/unit_tests/multisig.cpp15
-rw-r--r--tests/unit_tests/ringdb.cpp2
16 files changed, 659 insertions, 158 deletions
diff --git a/contrib/epee/include/serialization/keyvalue_serialization.h b/contrib/epee/include/serialization/keyvalue_serialization.h
index 5791e1998..fc5a21851 100644
--- a/contrib/epee/include/serialization/keyvalue_serialization.h
+++ b/contrib/epee/include/serialization/keyvalue_serialization.h
@@ -85,6 +85,14 @@ public: \
static_assert(std::is_pod<decltype(this_ref.varialble)>::value, "t_type must be a POD type."); \
KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(varialble, val_name)
+#define KV_SERIALIZE_VAL_POD_AS_BLOB_OPT_N(varialble, val_name, default_value) \
+ do { \
+ static_assert(std::is_pod<decltype(this_ref.varialble)>::value, "t_type must be a POD type."); \
+ bool ret = KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(varialble, val_name); \
+ if (!ret) \
+ epee::serialize_default(this_ref.varialble, default_value); \
+ } while(0);
+
#define KV_SERIALIZE_CONTAINER_POD_AS_BLOB_N(varialble, val_name) \
epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(this_ref.varialble, stg, hparent_section, val_name);
@@ -92,6 +100,7 @@ public: \
#define KV_SERIALIZE(varialble) KV_SERIALIZE_N(varialble, #varialble)
#define KV_SERIALIZE_VAL_POD_AS_BLOB(varialble) KV_SERIALIZE_VAL_POD_AS_BLOB_N(varialble, #varialble)
+#define KV_SERIALIZE_VAL_POD_AS_BLOB_OPT(varialble, def) KV_SERIALIZE_VAL_POD_AS_BLOB_OPT_N(varialble, #varialble, def)
#define KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(varialble) KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE_N(varialble, #varialble) //skip is_pod compile time check
#define KV_SERIALIZE_CONTAINER_POD_AS_BLOB(varialble) KV_SERIALIZE_CONTAINER_POD_AS_BLOB_N(varialble, #varialble)
#define KV_SERIALIZE_OPT(variable,default_value) KV_SERIALIZE_OPT_N(variable, #variable, default_value)
diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp
index aac6ec22b..0aebf9223 100644
--- a/src/cryptonote_basic/account.cpp
+++ b/src/cryptonote_basic/account.cpp
@@ -44,6 +44,9 @@ extern "C"
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "account"
+#define KEYS_ENCRYPTION_SALT 'k'
+
+
using namespace std;
DISABLE_VS_WARNINGS(4244 4345)
@@ -60,7 +63,70 @@ DISABLE_VS_WARNINGS(4244 4345)
m_device = &hwdev;
MCDEBUG("device", "account_keys::set_device device type: "<<typeid(hwdev).name());
}
+ //-----------------------------------------------------------------
+ static void derive_key(const crypto::chacha_key &base_key, crypto::chacha_key &key)
+ {
+ static_assert(sizeof(base_key) == sizeof(crypto::hash), "chacha key and hash should be the same size");
+ tools::scrubbed_arr<char, sizeof(base_key)+1> data;
+ memcpy(data.data(), &base_key, sizeof(base_key));
+ data[sizeof(base_key)] = KEYS_ENCRYPTION_SALT;
+ crypto::generate_chacha_key(data.data(), sizeof(data), key, 1);
+ }
+ //-----------------------------------------------------------------
+ static epee::wipeable_string get_key_stream(const crypto::chacha_key &base_key, const crypto::chacha_iv &iv, size_t bytes)
+ {
+ // derive a new key
+ crypto::chacha_key key;
+ derive_key(base_key, key);
+ // chacha
+ epee::wipeable_string buffer0(std::string(bytes, '\0'));
+ epee::wipeable_string buffer1 = buffer0;
+ crypto::chacha20(buffer0.data(), buffer0.size(), key, iv, buffer1.data());
+ return buffer1;
+ }
+ //-----------------------------------------------------------------
+ void account_keys::xor_with_key_stream(const crypto::chacha_key &key)
+ {
+ // encrypt a large enough byte stream with chacha20
+ epee::wipeable_string key_stream = get_key_stream(key, m_encryption_iv, sizeof(crypto::secret_key) * (2 + m_multisig_keys.size()));
+ const char *ptr = key_stream.data();
+ for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
+ m_spend_secret_key.data[i] ^= *ptr++;
+ for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
+ m_view_secret_key.data[i] ^= *ptr++;
+ for (crypto::secret_key &k: m_multisig_keys)
+ {
+ for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
+ k.data[i] ^= *ptr++;
+ }
+ }
+ //-----------------------------------------------------------------
+ void account_keys::encrypt(const crypto::chacha_key &key)
+ {
+ m_encryption_iv = crypto::rand<crypto::chacha_iv>();
+ xor_with_key_stream(key);
+ }
+ //-----------------------------------------------------------------
+ void account_keys::decrypt(const crypto::chacha_key &key)
+ {
+ xor_with_key_stream(key);
+ }
+ //-----------------------------------------------------------------
+ void account_keys::encrypt_viewkey(const crypto::chacha_key &key)
+ {
+ // encrypt a large enough byte stream with chacha20
+ epee::wipeable_string key_stream = get_key_stream(key, m_encryption_iv, sizeof(crypto::secret_key) * 2);
+ const char *ptr = key_stream.data();
+ ptr += sizeof(crypto::secret_key);
+ for (size_t i = 0; i < sizeof(crypto::secret_key); ++i)
+ m_view_secret_key.data[i] ^= *ptr++;
+ }
+ //-----------------------------------------------------------------
+ void account_keys::decrypt_viewkey(const crypto::chacha_key &key)
+ {
+ encrypt_viewkey(key);
+ }
//-----------------------------------------------------------------
account_base::account_base()
{
diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h
index b5d119c46..dac66ff1a 100644
--- a/src/cryptonote_basic/account.h
+++ b/src/cryptonote_basic/account.h
@@ -44,18 +44,29 @@ namespace cryptonote
crypto::secret_key m_view_secret_key;
std::vector<crypto::secret_key> m_multisig_keys;
hw::device *m_device = &hw::get_device("default");
+ crypto::chacha_iv m_encryption_iv;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(m_account_address)
KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_spend_secret_key)
KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_view_secret_key)
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_multisig_keys)
+ const crypto::chacha_iv default_iv{{0, 0, 0, 0, 0, 0, 0, 0}};
+ KV_SERIALIZE_VAL_POD_AS_BLOB_OPT(m_encryption_iv, default_iv)
END_KV_SERIALIZE_MAP()
account_keys& operator=(account_keys const&) = default;
+ void encrypt(const crypto::chacha_key &key);
+ void decrypt(const crypto::chacha_key &key);
+ void encrypt_viewkey(const crypto::chacha_key &key);
+ void decrypt_viewkey(const crypto::chacha_key &key);
+
hw::device& get_device() const ;
void set_device( hw::device &hwdev) ;
+
+ private:
+ void xor_with_key_stream(const crypto::chacha_key &key);
};
/************************************************************************/
@@ -87,6 +98,11 @@ namespace cryptonote
void forget_spend_key();
const std::vector<crypto::secret_key> &get_multisig_keys() const { return m_keys.m_multisig_keys; }
+ void encrypt_keys(const crypto::chacha_key &key) { m_keys.encrypt(key); }
+ void decrypt_keys(const crypto::chacha_key &key) { m_keys.decrypt(key); }
+ void encrypt_viewkey(const crypto::chacha_key &key) { m_keys.encrypt_viewkey(key); }
+ void decrypt_viewkey(const crypto::chacha_key &key) { m_keys.decrypt_viewkey(key); }
+
template <class t_archive>
inline void serialize(t_archive &a, const unsigned int /*ver*/)
{
diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp
index 03e0a7946..e680a8157 100644
--- a/src/gen_multisig/gen_multisig.cpp
+++ b/src/gen_multisig/gen_multisig.cpp
@@ -92,7 +92,7 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str
{
std::string name = basename + "-" + std::to_string(n + 1);
wallets[n].reset(new tools::wallet2(nettype));
- wallets[n]->init("");
+ wallets[n]->init(false, "");
wallets[n]->generate(name, pwd_container->password(), rct::rct2sk(rct::skGen()), false, false, create_address_file);
}
@@ -101,11 +101,13 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str
std::vector<crypto::public_key> pk(total);
for (size_t n = 0; n < total; ++n)
{
+ wallets[n]->decrypt_keys(pwd_container->password());
if (!tools::wallet2::verify_multisig_info(wallets[n]->get_multisig_info(), sk[n], pk[n]))
{
tools::fail_msg_writer() << tr("Failed to verify multisig info");
return false;
}
+ wallets[n]->encrypt_keys(pwd_container->password());
}
// make the wallets multisig
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index f87c2936d..038605725 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -106,6 +106,12 @@ typedef cryptonote::simple_wallet sw;
m_auto_refresh_enabled.store(auto_refresh_enabled, std::memory_order_relaxed); \
})
+#define SCOPED_WALLET_UNLOCK() \
+ LOCK_IDLE_SCOPE(); \
+ boost::optional<tools::password_container> pwd_container = boost::none; \
+ if (m_wallet->ask_password() && !m_wallet->watch_only() && !(pwd_container = get_and_verify_password())) { return true; } \
+ tools::wallet_keys_unlocker unlocker(*m_wallet, pwd_container);
+
enum TransferType {
TransferOriginal,
TransferNew,
@@ -553,6 +559,18 @@ namespace
}
return true;
}
+
+ void print_secret_key(const crypto::secret_key &k)
+ {
+ static constexpr const char hex[] = u8"0123456789abcdef";
+ const uint8_t *ptr = (const uint8_t*)k.data;
+ for (size_t i = 0, sz = sizeof(k); i < sz; ++i)
+ {
+ putchar(hex[*ptr >> 4]);
+ putchar(hex[*ptr & 15]);
+ ++ptr;
+ }
+ }
}
bool parse_priority(const std::string& arg, uint32_t& priority)
@@ -602,12 +620,15 @@ std::string simple_wallet::get_command_usage(const std::vector<std::string> &arg
bool simple_wallet::viewkey(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
+ SCOPED_WALLET_UNLOCK();
// don't log
+ PAUSE_READLINE();
if (m_wallet->key_on_device()) {
std::cout << "secret: On device. Not available" << std::endl;
} else {
- std::cout << "secret: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_view_secret_key) << std::endl;
+ printf("secret: ");
+ print_secret_key(m_wallet->get_account().get_keys().m_view_secret_key);
+ putchar('\n');
}
std::cout << "public: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_account_address.m_view_public_key) << std::endl;
@@ -621,12 +642,15 @@ bool simple_wallet::spendkey(const std::vector<std::string> &args/* = std::vecto
fail_msg_writer() << tr("wallet is watch-only and has no spend key");
return true;
}
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
+ SCOPED_WALLET_UNLOCK();
// don't log
+ PAUSE_READLINE();
if (m_wallet->key_on_device()) {
std::cout << "secret: On device. Not available" << std::endl;
} else {
- std::cout << "secret: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_spend_secret_key) << std::endl;
+ printf("secret: ");
+ print_secret_key(m_wallet->get_account().get_keys().m_spend_secret_key);
+ putchar('\n');
}
std::cout << "public: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_account_address.m_spend_public_key) << std::endl;
@@ -649,7 +673,8 @@ bool simple_wallet::print_seed(bool encrypted)
fail_msg_writer() << tr("wallet is watch-only and has no seed");
return true;
}
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
+
+ SCOPED_WALLET_UNLOCK();
multisig = m_wallet->multisig(&ready);
if (multisig)
@@ -718,22 +743,36 @@ bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = s
fail_msg_writer() << tr("wallet is watch-only and has no seed");
return true;
}
- if (!m_wallet->is_deterministic())
- {
- fail_msg_writer() << tr("wallet is non-deterministic and has no seed");
- return true;
- }
-
- const auto pwd_container = get_and_verify_password();
- if (pwd_container)
+
+ epee::wipeable_string password;
{
- std::string mnemonic_language = get_mnemonic_language();
- if (mnemonic_language.empty())
+ SCOPED_WALLET_UNLOCK();
+
+ if (!m_wallet->is_deterministic())
+ {
+ fail_msg_writer() << tr("wallet is non-deterministic and has no seed");
return true;
+ }
- m_wallet->set_seed_language(std::move(mnemonic_language));
- m_wallet->rewrite(m_wallet_file, pwd_container->password());
+ // we need the password, even if ask-password is unset
+ if (!pwd_container)
+ {
+ pwd_container = get_and_verify_password();
+ if (pwd_container == boost::none)
+ {
+ fail_msg_writer() << tr("Incorrect password");
+ return true;
+ }
+ }
+ password = pwd_container->password();
}
+
+ std::string mnemonic_language = get_mnemonic_language();
+ if (mnemonic_language.empty())
+ return true;
+
+ m_wallet->set_seed_language(std::move(mnemonic_language));
+ m_wallet->rewrite(m_wallet_file, password);
return true;
}
@@ -754,8 +793,7 @@ bool simple_wallet::change_password(const std::vector<std::string> &args)
try
{
- m_wallet->rewrite(m_wallet_file, pwd_container->password());
- m_wallet->store();
+ m_wallet->change_password(m_wallet_file, orig_pwd_container->password(), pwd_container->password());
}
catch (const tools::error::wallet_logic_error& e)
{
@@ -858,12 +896,7 @@ bool simple_wallet::prepare_multisig(const std::vector<std::string> &args)
return true;
}
- const auto orig_pwd_container = get_and_verify_password();
- if(orig_pwd_container == boost::none)
- {
- fail_msg_writer() << tr("Your password is incorrect.");
- return true;
- }
+ SCOPED_WALLET_UNLOCK();
std::string multisig_info = m_wallet->get_multisig_info();
success_msg_writer() << multisig_info;
@@ -896,13 +929,6 @@ bool simple_wallet::make_multisig(const std::vector<std::string> &args)
return true;
}
- const auto orig_pwd_container = get_and_verify_password();
- if(orig_pwd_container == boost::none)
- {
- fail_msg_writer() << tr("Your original password was incorrect.");
- return true;
- }
-
if (args.size() < 2)
{
fail_msg_writer() << tr("usage: make_multisig <threshold> <multisiginfo1> [<multisiginfo2>...]");
@@ -917,6 +943,13 @@ bool simple_wallet::make_multisig(const std::vector<std::string> &args)
return true;
}
+ const auto orig_pwd_container = get_and_verify_password();
+ if(orig_pwd_container == boost::none)
+ {
+ fail_msg_writer() << tr("Your original password was incorrect.");
+ return true;
+ }
+
LOCK_IDLE_SCOPE();
try
@@ -958,6 +991,14 @@ bool simple_wallet::finalize_multisig(const std::vector<std::string> &args)
fail_msg_writer() << tr("command not supported by HW wallet");
return true;
}
+
+ const auto pwd_container = get_and_verify_password();
+ if(pwd_container == boost::none)
+ {
+ fail_msg_writer() << tr("Your original password was incorrect.");
+ return true;
+ }
+
if (!m_wallet->multisig(&ready))
{
fail_msg_writer() << tr("This wallet is not multisig");
@@ -969,12 +1010,7 @@ bool simple_wallet::finalize_multisig(const std::vector<std::string> &args)
return true;
}
- const auto orig_pwd_container = get_and_verify_password();
- if(orig_pwd_container == boost::none)
- {
- fail_msg_writer() << tr("Your original password was incorrect.");
- return true;
- }
+ LOCK_IDLE_SCOPE();
if (args.size() < 2)
{
@@ -984,7 +1020,7 @@ bool simple_wallet::finalize_multisig(const std::vector<std::string> &args)
try
{
- if (!m_wallet->finalize_multisig(orig_pwd_container->password(), args))
+ if (!m_wallet->finalize_multisig(pwd_container->password(), args))
{
fail_msg_writer() << tr("Failed to finalize multisig");
return true;
@@ -1022,8 +1058,8 @@ bool simple_wallet::export_multisig(const std::vector<std::string> &args)
fail_msg_writer() << tr("usage: export_multisig_info <filename>");
return true;
}
- if (m_wallet->ask_password() && !get_and_verify_password())
- return true;
+
+ SCOPED_WALLET_UNLOCK();
const std::string filename = args[0];
if (m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename))
@@ -1074,8 +1110,8 @@ bool simple_wallet::import_multisig(const std::vector<std::string> &args)
fail_msg_writer() << tr("usage: import_multisig_info <filename1> [<filename2>...] - one for each other participant");
return true;
}
- if (m_wallet->ask_password() && !get_and_verify_password())
- return true;
+
+ SCOPED_WALLET_UNLOCK();
std::vector<cryptonote::blobdata> info;
for (size_t n = 0; n < args.size(); ++n)
@@ -1091,11 +1127,11 @@ bool simple_wallet::import_multisig(const std::vector<std::string> &args)
info.push_back(std::move(data));
}
- LOCK_IDLE_SCOPE();
-
// all read and parsed, actually import
try
{
+ m_in_manual_refresh.store(true, std::memory_order_relaxed);
+ epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_in_manual_refresh.store(false, std::memory_order_relaxed);});
size_t n_outputs = m_wallet->import_multisig(info);
// Clear line "Height xxx of xxx"
std::cout << "\r \r";
@@ -1153,7 +1189,8 @@ bool simple_wallet::sign_multisig(const std::vector<std::string> &args)
fail_msg_writer() << tr("usage: sign_multisig <filename>");
return true;
}
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
+
+ SCOPED_WALLET_UNLOCK();
std::string filename = args[0];
std::vector<crypto::hash> txids;
@@ -1226,7 +1263,8 @@ bool simple_wallet::submit_multisig(const std::vector<std::string> &args)
fail_msg_writer() << tr("usage: submit_multisig <filename>");
return true;
}
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
+
+ SCOPED_WALLET_UNLOCK();
if (!try_connect_to_daemon())
return true;
@@ -1293,7 +1331,8 @@ bool simple_wallet::export_raw_multisig(const std::vector<std::string> &args)
fail_msg_writer() << tr("usage: export_raw_multisig <filename>");
return true;
}
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
+
+ SCOPED_WALLET_UNLOCK();
std::string filename = args[0];
if (m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename))
@@ -1915,6 +1954,14 @@ bool simple_wallet::set_ask_password(const std::vector<std::string> &args/* = st
if (pwd_container)
{
parse_bool_and_use(args[1], [&](bool r) {
+ const bool cur_r = m_wallet->ask_password();
+ if (!m_wallet->watch_only())
+ {
+ if (cur_r && !r)
+ m_wallet->decrypt_keys(pwd_container->password());
+ else if (!cur_r && r)
+ m_wallet->encrypt_keys(pwd_container->password());
+ }
m_wallet->ask_password(r);
m_wallet->rewrite(m_wallet_file, pwd_container->password());
});
@@ -3173,7 +3220,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
m_wallet_file = m_generate_from_json;
try
{
- m_wallet = tools::wallet2::make_from_json(vm, m_wallet_file, password_prompter);
+ m_wallet = tools::wallet2::make_from_json(vm, false, m_wallet_file, password_prompter);
}
catch (const std::exception &e)
{
@@ -3475,7 +3522,7 @@ boost::optional<tools::password_container> simple_wallet::get_and_verify_passwor
boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::program_options::variables_map& vm,
const crypto::secret_key& recovery_key, bool recover, bool two_random, const std::string &old_language)
{
- auto rc = tools::wallet2::make_new(vm, password_prompter);
+ auto rc = tools::wallet2::make_new(vm, false, password_prompter);
m_wallet = std::move(rc.first);
if (!m_wallet)
{
@@ -3530,7 +3577,10 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr
recovery_val = m_wallet->generate(m_wallet_file, std::move(rc.second).password(), recovery_key, recover, two_random, create_address_file);
message_writer(console_color_white, true) << tr("Generated new wallet: ")
<< m_wallet->get_account().get_public_address_str(m_wallet->nettype());
- std::cout << tr("View key: ") << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_view_secret_key) << ENDL;
+ PAUSE_READLINE();
+ std::cout << tr("View key: ");
+ print_secret_key(m_wallet->get_account().get_keys().m_view_secret_key);
+ putchar('\n');
}
catch (const std::exception& e)
{
@@ -3567,7 +3617,7 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr
const cryptonote::account_public_address& address, const boost::optional<crypto::secret_key>& spendkey,
const crypto::secret_key& viewkey)
{
- auto rc = tools::wallet2::make_new(vm, password_prompter);
+ auto rc = tools::wallet2::make_new(vm, false, password_prompter);
m_wallet = std::move(rc.first);
if (!m_wallet)
{
@@ -3613,7 +3663,7 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr
//----------------------------------------------------------------------------------------------------
boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::program_options::variables_map& vm,
const std::string &device_name) {
- auto rc = tools::wallet2::make_new(vm, password_prompter);
+ auto rc = tools::wallet2::make_new(vm, false, password_prompter);
m_wallet = std::move(rc.first);
if (!m_wallet)
{
@@ -3649,7 +3699,7 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr
boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::program_options::variables_map& vm,
const epee::wipeable_string &multisig_keys, const std::string &old_language)
{
- auto rc = tools::wallet2::make_new(vm, password_prompter);
+ auto rc = tools::wallet2::make_new(vm, false, password_prompter);
m_wallet = std::move(rc.first);
if (!m_wallet)
{
@@ -3720,7 +3770,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm)
epee::wipeable_string password;
try
{
- auto rc = tools::wallet2::make_from_file(vm, m_wallet_file, password_prompter);
+ auto rc = tools::wallet2::make_from_file(vm, false, m_wallet_file, password_prompter);
m_wallet = std::move(rc.first);
password = std::move(std::move(rc.second).password());
if (!m_wallet)
@@ -3747,7 +3797,12 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm)
// NOTE: this is_deprecated() refers to the wallet file format before becoming JSON. It does not refer to the "old english" seed words form of "deprecated" used elsewhere.
if (m_wallet->is_deprecated())
{
- if (m_wallet->is_deterministic())
+ bool is_deterministic;
+ {
+ SCOPED_WALLET_UNLOCK();
+ is_deterministic = m_wallet->is_deterministic();
+ }
+ if (is_deterministic)
{
message_writer(console_color_green, false) << "\n" << tr("You had been using "
"a deprecated version of the wallet. Please proceed to upgrade your wallet.\n");
@@ -3985,7 +4040,7 @@ bool simple_wallet::set_daemon(const std::vector<std::string>& args)
daemon_url = args[0];
}
LOCK_IDLE_SCOPE();
- m_wallet->init(daemon_url);
+ m_wallet->init(false, daemon_url);
if (args.size() == 2)
{
@@ -4099,6 +4154,32 @@ void simple_wallet::on_skip_transaction(uint64_t height, const crypto::hash &txi
{
}
//----------------------------------------------------------------------------------------------------
+boost::optional<epee::wipeable_string> simple_wallet::on_get_password(const char *reason)
+{
+ // can't ask for password from a background thread
+ if (!m_in_manual_refresh.load(std::memory_order_relaxed))
+ {
+ message_writer(console_color_red, false) << tr("Password needed - use the refresh command");
+ m_cmd_binder.print_prompt();
+ return boost::none;
+ }
+
+#ifdef HAVE_READLINE
+ rdln::suspend_readline pause_readline;
+#endif
+ std::string msg = tr("Enter password");
+ if (reason && *reason)
+ msg += std::string(" (") + reason + ")";
+ auto pwd_container = tools::password_container::prompt(false, msg.c_str());
+ if (!pwd_container)
+ {
+ MERROR("Failed to read password");
+ return boost::none;
+ }
+
+ return pwd_container->password();
+}
+//----------------------------------------------------------------------------------------------------
bool simple_wallet::refresh_main(uint64_t start_height, bool reset, bool is_init)
{
if (!try_connect_to_daemon(is_init))
@@ -4574,11 +4655,10 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending
bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_)
{
// "transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]"
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
if (!try_connect_to_daemon())
return true;
- LOCK_IDLE_SCOPE();
+ SCOPED_WALLET_UNLOCK();
std::vector<std::string> local_args = args_;
@@ -4981,11 +5061,11 @@ bool simple_wallet::locked_sweep_all(const std::vector<std::string> &args_)
bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
{
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
if (!try_connect_to_daemon())
return true;
- LOCK_IDLE_SCOPE();
+ SCOPED_WALLET_UNLOCK();
+
try
{
// figure out what tx will be necessary
@@ -5098,7 +5178,6 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st
return true;
}
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
if (!try_connect_to_daemon())
return true;
@@ -5262,7 +5341,7 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st
}
}
- LOCK_IDLE_SCOPE();
+ SCOPED_WALLET_UNLOCK();
try
{
@@ -5364,7 +5443,7 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st
//----------------------------------------------------------------------------------------------------
bool simple_wallet::sweep_single(const std::vector<std::string> &args_)
{
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
+ SCOPED_WALLET_UNLOCK();
if (!try_connect_to_daemon())
return true;
@@ -5790,7 +5869,8 @@ bool simple_wallet::sign_transfer(const std::vector<std::string> &args_)
fail_msg_writer() << tr("usage: sign_transfer [export_raw]");
return true;
}
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
+
+ SCOPED_WALLET_UNLOCK();
const bool export_raw = args_.size() == 1;
std::vector<tools::wallet2::pending_tx> ptx;
@@ -5879,7 +5959,6 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_)
fail_msg_writer() << tr("usage: get_tx_key <txid>");
return true;
}
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
crypto::hash txid;
if (!epee::string_tools::hex_to_pod(local_args[0], txid))
@@ -5888,7 +5967,7 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_)
return true;
}
- LOCK_IDLE_SCOPE();
+ SCOPED_WALLET_UNLOCK();
crypto::secret_key tx_key;
std::vector<crypto::secret_key> additional_tx_keys;
@@ -5993,7 +6072,7 @@ bool simple_wallet::get_tx_proof(const std::vector<std::string> &args)
return true;
}
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
+ SCOPED_WALLET_UNLOCK();
try
{
@@ -6208,7 +6287,7 @@ bool simple_wallet::get_spend_proof(const std::vector<std::string> &args)
return true;
}
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
+ SCOPED_WALLET_UNLOCK();
try
{
@@ -6303,9 +6382,7 @@ bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args)
return true;
}
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
-
- LOCK_IDLE_SCOPE();
+ SCOPED_WALLET_UNLOCK();
try
{
@@ -6558,6 +6635,9 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_)
if (pool) {
try
{
+ m_in_manual_refresh.store(true, std::memory_order_relaxed);
+ epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_in_manual_refresh.store(false, std::memory_order_relaxed);});
+
m_wallet->update_pool_state();
std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> payments;
m_wallet->get_unconfirmed_payments(payments, m_current_subaddress_account, subaddr_indices);
@@ -7406,7 +7486,8 @@ bool simple_wallet::sign(const std::vector<std::string> &args)
fail_msg_writer() << tr("This wallet is multisig and cannot sign");
return true;
}
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
+
+ SCOPED_WALLET_UNLOCK();
std::string filename = args[0];
std::string data;
bool r = epee::file_io_utils::load_file_to_string(filename, data);
@@ -7475,14 +7556,14 @@ bool simple_wallet::export_key_images(const std::vector<std::string> &args)
fail_msg_writer() << tr("wallet is watch-only and cannot export key images");
return true;
}
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
+
+ SCOPED_WALLET_UNLOCK();
std::string filename = args[0];
if (m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename))
return true;
try
{
- LOCK_IDLE_SCOPE();
if (!m_wallet->export_key_images(filename))
{
fail_msg_writer() << tr("failed to save file ") << filename;
@@ -7554,12 +7635,12 @@ bool simple_wallet::export_outputs(const std::vector<std::string> &args)
fail_msg_writer() << tr("usage: export_outputs <filename>");
return true;
}
- if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
+
+ SCOPED_WALLET_UNLOCK();
std::string filename = args[0];
if (m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename))
return true;
- LOCK_IDLE_SCOPE();
try
{
std::string data = m_wallet->export_outputs_to_str();
@@ -7605,7 +7686,7 @@ bool simple_wallet::import_outputs(const std::vector<std::string> &args)
try
{
- LOCK_IDLE_SCOPE();
+ SCOPED_WALLET_UNLOCK();
size_t n_outputs = m_wallet->import_outputs_from_str(data);
success_msg_writer() << boost::lexical_cast<std::string>(n_outputs) << " outputs imported";
}
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index c8e17ea6e..99fc19c00 100644
--- a/src/simplewallet/simplewallet.h
+++ b/src/simplewallet/simplewallet.h
@@ -262,6 +262,7 @@ namespace cryptonote
virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index);
virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index);
virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx);
+ virtual boost::optional<epee::wipeable_string> on_get_password(const char *reason);
//----------------------------------------------------------
friend class refresh_progress_reporter_t;
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index e75bee5c9..bfd0e4aff 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -2032,7 +2032,8 @@ bool WalletImpl::isNewWallet() const
bool WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction_size_limit, bool ssl)
{
- if (!m_wallet->init(daemon_address, m_daemon_login, upper_transaction_size_limit, ssl))
+ // claim RPC so there's no in-memory encryption for now
+ if (!m_wallet->init(true, daemon_address, m_daemon_login, upper_transaction_size_limit, ssl))
return false;
// in case new wallet, this will force fast-refresh (pulling hashes instead of blocks)
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index e61e437c0..ea3cca1ce 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -89,6 +89,7 @@ using namespace cryptonote;
// arbitrary, used to generate different hashes from the same input
#define CHACHA8_KEY_TAIL 0x8c
+#define CACHE_KEY_TAIL 0x8d
#define UNSIGNED_TX_PREFIX "Monero unsigned tx set\004"
#define SIGNED_TX_PREFIX "Monero signed tx set\004"
@@ -121,8 +122,6 @@ using namespace cryptonote;
static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1";
-std::atomic<unsigned int> tools::wallet2::key_ref::refs(0);
-
namespace
{
std::string get_default_ringdb_path()
@@ -197,7 +196,7 @@ std::string get_size_string(const cryptonote::blobdata &tx)
return get_size_string(tx.size());
}
-std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variables_map& vm, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
+std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variables_map& vm, bool rpc, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
{
const bool testnet = command_line::get_arg(vm, opts.testnet);
const bool stagenet = command_line::get_arg(vm, opts.stagenet);
@@ -238,7 +237,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
daemon_address = std::string("http://") + daemon_host + ":" + std::to_string(daemon_port);
std::unique_ptr<tools::wallet2> wallet(new tools::wallet2(nettype, kdf_rounds));
- wallet->init(std::move(daemon_address), std::move(login));
+ wallet->init(rpc, std::move(daemon_address), std::move(login));
boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir);
wallet->set_ring_database(ringdb_path.string());
return wallet;
@@ -273,7 +272,7 @@ boost::optional<tools::password_container> get_password(const boost::program_opt
return password_prompter(verify ? tr("Enter a new password for the wallet") : tr("Wallet password"), verify);
}
-std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const boost::program_options::variables_map& vm, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
+std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const boost::program_options::variables_map& vm, bool rpc, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
{
const bool testnet = command_line::get_arg(vm, opts.testnet);
const bool stagenet = command_line::get_arg(vm, opts.stagenet);
@@ -411,7 +410,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file,
THROW_WALLET_EXCEPTION_IF(deprecated_wallet, tools::error::wallet_internal_error,
tools::wallet2::tr("Cannot generate deprecated wallets from JSON"));
- wallet.reset(make_basic(vm, opts, password_prompter).release());
+ wallet.reset(make_basic(vm, rpc, opts, password_prompter).release());
wallet->set_refresh_from_block_height(field_scan_from_height);
wallet->explicit_refresh_from_block_height(field_scan_from_height_found);
@@ -648,6 +647,34 @@ const size_t MAX_SPLIT_ATTEMPTS = 30;
constexpr const std::chrono::seconds wallet2::rpc_timeout;
const char* wallet2::tr(const char* str) { return i18n_translate(str, "tools::wallet2"); }
+wallet_keys_unlocker::wallet_keys_unlocker(wallet2 &w, const boost::optional<tools::password_container> &password):
+ w(w),
+ locked(password != boost::none)
+{
+ if (!locked || w.is_rpc())
+ return;
+ const epee::wipeable_string pass = password->password();
+ w.generate_chacha_key_from_password(pass, key);
+ w.decrypt_keys(key);
+}
+
+wallet_keys_unlocker::wallet_keys_unlocker(wallet2 &w, bool locked, const epee::wipeable_string &password):
+ w(w),
+ locked(locked)
+{
+ if (!locked)
+ return;
+ w.generate_chacha_key_from_password(password, key);
+ w.decrypt_keys(key);
+}
+
+wallet_keys_unlocker::~wallet_keys_unlocker()
+{
+ if (!locked)
+ return;
+ w.encrypt_keys(key);
+}
+
wallet2::wallet2(network_type nettype, uint64_t kdf_rounds):
m_multisig_rescan_info(NULL),
m_multisig_rescan_k(NULL),
@@ -693,7 +720,9 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds):
m_key_on_device(false),
m_ring_history_saved(false),
m_ringdb(),
- m_last_block_reward(0)
+ m_last_block_reward(0),
+ m_encrypt_keys_after_refresh(boost::none),
+ m_rpc(false)
{
}
@@ -726,14 +755,14 @@ void wallet2::init_options(boost::program_options::options_description& desc_par
command_line::add_arg(desc_params, opts.kdf_rounds);
}
-std::unique_ptr<wallet2> wallet2::make_from_json(const boost::program_options::variables_map& vm, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
+std::unique_ptr<wallet2> wallet2::make_from_json(const boost::program_options::variables_map& vm, bool rpc, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
{
const options opts{};
- return generate_from_json(json_file, vm, opts, password_prompter);
+ return generate_from_json(json_file, vm, rpc, opts, password_prompter);
}
std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file(
- const boost::program_options::variables_map& vm, const std::string& wallet_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
+ const boost::program_options::variables_map& vm, bool rpc, const std::string& wallet_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
{
const options opts{};
auto pwd = get_password(vm, opts, password_prompter, false);
@@ -741,7 +770,7 @@ std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file(
{
return {nullptr, password_container{}};
}
- auto wallet = make_basic(vm, opts, password_prompter);
+ auto wallet = make_basic(vm, rpc, opts, password_prompter);
if (wallet)
{
wallet->load(wallet_file, pwd->password());
@@ -749,7 +778,7 @@ std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file(
return {std::move(wallet), std::move(*pwd)};
}
-std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_new(const boost::program_options::variables_map& vm, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter)
+std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_new(const boost::program_options::variables_map& vm, bool rpc, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter)
{
const options opts{};
auto pwd = get_password(vm, opts, password_prompter, true);
@@ -757,18 +786,19 @@ std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_new(const
{
return {nullptr, password_container{}};
}
- return {make_basic(vm, opts, password_prompter), std::move(*pwd)};
+ return {make_basic(vm, rpc, opts, password_prompter), std::move(*pwd)};
}
-std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::variables_map& vm, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
+std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::variables_map& vm, bool rpc, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter)
{
const options opts{};
- return make_basic(vm, opts, password_prompter);
+ return make_basic(vm, rpc, opts, password_prompter);
}
//----------------------------------------------------------------------------------------------------
-bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, uint64_t upper_transaction_size_limit, bool ssl)
+bool wallet2::init(bool rpc, std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, uint64_t upper_transaction_size_limit, bool ssl)
{
+ m_rpc = rpc;
m_checkpoints.init_default_checkpoints(m_nettype);
if(m_http_client.is_connected())
m_http_client.disconnect();
@@ -785,8 +815,7 @@ bool wallet2::is_deterministic() const
crypto::secret_key second;
keccak((uint8_t *)&get_account().get_keys().m_spend_secret_key, sizeof(crypto::secret_key), (uint8_t *)&second, sizeof(crypto::secret_key));
sc_reduce32((uint8_t *)&second);
- bool keys_deterministic = memcmp(second.data,get_account().get_keys().m_view_secret_key.data, sizeof(crypto::secret_key)) == 0;
- return keys_deterministic;
+ return memcmp(second.data,get_account().get_keys().m_view_secret_key.data, sizeof(crypto::secret_key)) == 0;
}
//----------------------------------------------------------------------------------------------------
bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeable_string &passphrase) const
@@ -1103,9 +1132,25 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation &
}
}
//----------------------------------------------------------------------------------------------------
-void wallet2::scan_output(const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs) const
+void wallet2::scan_output(const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs)
{
THROW_WALLET_EXCEPTION_IF(i >= tx.vout.size(), error::wallet_internal_error, "Invalid vout index");
+
+ // if keys are encrypted, ask for password
+ if (m_ask_password && !m_rpc && !m_watch_only && !m_multisig_rescan_k)
+ {
+ static critical_section password_lock;
+ CRITICAL_REGION_LOCAL(password_lock);
+ if (!m_encrypt_keys_after_refresh)
+ {
+ boost::optional<epee::wipeable_string> pwd = m_callback->on_get_password("output received");
+ THROW_WALLET_EXCEPTION_IF(!pwd, error::password_needed, tr("Password is needed to compute key image for incoming monero"));
+ THROW_WALLET_EXCEPTION_IF(!verify_password(*pwd), error::password_needed, tr("Invalid password: password is needed to compute key image for incoming monero"));
+ decrypt_keys(*pwd);
+ m_encrypt_keys_after_refresh = *pwd;
+ }
+ }
+
if (m_multisig)
{
tx_scan_info.in_ephemeral.pub = boost::get<cryptonote::txout_to_key>(tx.vout[i].target).key;
@@ -2063,6 +2108,14 @@ void wallet2::update_pool_state(bool refreshed)
{
MDEBUG("update_pool_state start");
+ auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() {
+ if (m_encrypt_keys_after_refresh)
+ {
+ encrypt_keys(*m_encrypt_keys_after_refresh);
+ m_encrypt_keys_after_refresh = boost::none;
+ }
+ });
+
// get the pool state
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::request req;
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::response res;
@@ -2369,8 +2422,6 @@ bool wallet2::delete_address_book_row(std::size_t row_id) {
//----------------------------------------------------------------------------------------------------
void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money)
{
- key_ref kref(*this);
-
if(m_light_wallet) {
// MyMonero get_address_info needs to be called occasionally to trigger wallet sync.
@@ -2438,6 +2489,14 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
// subsequent pulls in this refresh.
start_height = 0;
+ auto keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]() {
+ if (m_encrypt_keys_after_refresh)
+ {
+ encrypt_keys(*m_encrypt_keys_after_refresh);
+ m_encrypt_keys_after_refresh = boost::none;
+ }
+ });
+
bool first = true;
while(m_run.load(std::memory_order_relaxed))
{
@@ -2507,6 +2566,12 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
throw std::runtime_error("proxy exception in refresh thread");
}
}
+ catch (const tools::error::password_needed&)
+ {
+ blocks_fetched += added_blocks;
+ waiter.wait(&tpool);
+ throw;
+ }
catch (const std::exception&)
{
blocks_fetched += added_blocks;
@@ -2728,8 +2793,20 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
std::string multisig_signers;
cryptonote::account_base account = m_account;
+ crypto::chacha_key key;
+ crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds);
+
+ if (m_ask_password && !m_rpc && !m_watch_only)
+ {
+ account.encrypt_viewkey(key);
+ account.decrypt_keys(key);
+ }
+
if (watch_only)
account.forget_spend_key();
+
+ account.encrypt_keys(key);
+
bool r = epee::serialization::store_t_to_binary(account, account_data);
CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet keys");
wallet2::keys_file_data keys_file_data = boost::value_initialized<wallet2::keys_file_data>();
@@ -2846,6 +2923,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
value2.SetUint(m_subaddress_lookahead_minor);
json.AddMember("subaddress_lookahead_minor", value2, json.GetAllocator());
+ value2.SetUint(1);
+ json.AddMember("encrypted_secret_keys", value2, json.GetAllocator());
+
// Serialize the JSON object
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
@@ -2853,7 +2933,6 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
account_data = buffer.GetString();
// Encrypt the entire JSON object.
- crypto::chacha_key key;
crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds);
std::string cipher;
cipher.resize(account_data.size());
@@ -2871,6 +2950,35 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
return true;
}
//----------------------------------------------------------------------------------------------------
+void wallet2::setup_keys(const epee::wipeable_string &password)
+{
+ crypto::chacha_key key;
+ crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds);
+
+ // re-encrypt, but keep viewkey unencrypted
+ if (m_ask_password && !m_rpc && !m_watch_only)
+ {
+ m_account.encrypt_keys(key);
+ m_account.decrypt_viewkey(key);
+ }
+
+ static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key");
+ tools::scrubbed_arr<char, HASH_SIZE+1> cache_key_data;
+ memcpy(cache_key_data.data(), &key, HASH_SIZE);
+ cache_key_data[HASH_SIZE] = CACHE_KEY_TAIL;
+ cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&)m_cache_key);
+ get_ringdb_key();
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::change_password(const std::string &filename, const epee::wipeable_string &original_password, const epee::wipeable_string &new_password)
+{
+ if (m_ask_password && !m_rpc && !m_watch_only)
+ decrypt_keys(original_password);
+ setup_keys(new_password);
+ rewrite(filename, new_password);
+ store();
+}
+//----------------------------------------------------------------------------------------------------
/*!
* \brief Load wallet information from wallet file.
* \param keys_file_name Name of wallet file
@@ -2881,6 +2989,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
rapidjson::Document json;
wallet2::keys_file_data keys_file_data;
std::string buf;
+ bool encrypted_secret_keys = false;
bool r = epee::file_io_utils::load_file_to_string(keys_file_name, buf);
THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, keys_file_name);
@@ -2926,6 +3035,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR;
m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR;
m_key_on_device = false;
+ encrypted_secret_keys = false;
}
else if(json.IsObject())
{
@@ -3055,6 +3165,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
m_subaddress_lookahead_major = field_subaddress_lookahead_major;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_minor, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MINOR);
m_subaddress_lookahead_minor = field_subaddress_lookahead_minor;
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, encrypted_secret_keys, uint32_t, Uint, false, false);
+ encrypted_secret_keys = field_encrypted_secret_keys;
}
else
{
@@ -3071,12 +3183,39 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
m_account.set_device(hwdev);
LOG_PRINT_L0("Device inited...");
}
+
+ if (r)
+ {
+ if (encrypted_secret_keys)
+ {
+ m_account.decrypt_keys(key);
+ }
+ else
+ {
+ // rewrite with encrypted keys, ignore errors
+ if (m_ask_password && !m_rpc && !m_watch_only)
+ encrypt_keys(key);
+ bool saved_ret = store_keys(keys_file_name, password, m_watch_only);
+ if (!saved_ret)
+ {
+ // just moan a bit, but not fatal
+ MERROR("Error saving keys file with encrypted keys, not fatal");
+ }
+ if (m_ask_password && !m_rpc && !m_watch_only)
+ decrypt_keys(key);
+ m_keys_file_locker.reset();
+ }
+ }
const cryptonote::account_keys& keys = m_account.get_keys();
hw::device &hwdev = m_account.get_device();
r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
if(!m_watch_only && !m_multisig)
r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password);
+
+ if (r)
+ setup_keys(password);
+
return true;
}
@@ -3117,6 +3256,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip
rapidjson::Document json;
wallet2::keys_file_data keys_file_data;
std::string buf;
+ bool encrypted_secret_keys = false;
bool r = epee::file_io_utils::load_file_to_string(keys_file_name, buf);
THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, keys_file_name);
@@ -3140,19 +3280,50 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip
{
account_data = std::string(json["key_data"].GetString(), json["key_data"].GetString() +
json["key_data"].GetStringLength());
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, encrypted_secret_keys, uint32_t, Uint, false, false);
+ encrypted_secret_keys = field_encrypted_secret_keys;
}
cryptonote::account_base account_data_check;
r = epee::serialization::load_t_from_binary(account_data_check, account_data);
- const cryptonote::account_keys& keys = account_data_check.get_keys();
+ if (encrypted_secret_keys)
+ account_data_check.decrypt_keys(key);
+
+ const cryptonote::account_keys& keys = account_data_check.get_keys();
r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
if(!no_spend_key)
r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
return r;
}
+void wallet2::encrypt_keys(const crypto::chacha_key &key)
+{
+ m_account.encrypt_keys(key);
+ m_account.decrypt_viewkey(key);
+}
+
+void wallet2::decrypt_keys(const crypto::chacha_key &key)
+{
+ m_account.encrypt_viewkey(key);
+ m_account.decrypt_keys(key);
+}
+
+void wallet2::encrypt_keys(const epee::wipeable_string &password)
+{
+ crypto::chacha_key key;
+ crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds);
+ encrypt_keys(key);
+}
+
+void wallet2::decrypt_keys(const epee::wipeable_string &password)
+{
+ crypto::chacha_key key;
+ crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds);
+ decrypt_keys(key);
+}
+
/*!
* \brief Generates a wallet or restores one.
* \param wallet_ Name of wallet file
@@ -3227,6 +3398,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
m_multisig_threshold = threshold;
m_multisig_signers = multisig_signers;
m_key_on_device = false;
+ setup_keys(password);
if (!wallet_.empty())
{
@@ -3281,6 +3453,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip
m_multisig_threshold = 0;
m_multisig_signers.clear();
m_key_on_device = false;
+ setup_keys(password);
// calculate a starting refresh height
if(m_refresh_from_block_height == 0 && !recover){
@@ -3382,6 +3555,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
m_multisig_threshold = 0;
m_multisig_signers.clear();
m_key_on_device = false;
+ setup_keys(password);
if (!wallet_.empty())
{
@@ -3435,6 +3609,7 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string&
m_multisig_threshold = 0;
m_multisig_signers.clear();
m_key_on_device = false;
+ setup_keys(password);
if (!wallet_.empty())
{
@@ -3481,6 +3656,7 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p
m_multisig = false;
m_multisig_threshold = 0;
m_multisig_signers.clear();
+ setup_keys(password);
if (!wallet_.empty()) {
bool r = store_keys(m_keys_file, password, false);
@@ -3520,6 +3696,17 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
clear();
+ // decrypt keys
+ epee::misc_utils::auto_scope_leave_caller keys_reencryptor;
+ if (m_ask_password && !m_rpc && !m_watch_only)
+ {
+ crypto::chacha_key chacha_key;
+ crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds);
+ m_account.encrypt_viewkey(chacha_key);
+ m_account.decrypt_keys(chacha_key);
+ keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this, chacha_key]() { m_account.encrypt_keys(chacha_key); m_account.decrypt_viewkey(chacha_key); });
+ }
+
MINFO("Creating spend key...");
std::vector<crypto::secret_key> multisig_keys;
rct::key spend_pkey, spend_skey;
@@ -3580,6 +3767,9 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password,
m_multisig_signers = std::vector<crypto::public_key>(spend_keys.size() + 1, crypto::null_pkey);
}
+ // re-encrypt keys
+ keys_reencryptor = epee::misc_utils::auto_scope_leave_caller();
+
if (!m_wallet_file.empty())
{
bool r = store_keys(m_keys_file, password, false);
@@ -3662,6 +3852,17 @@ bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unor
{
CHECK_AND_ASSERT_THROW_MES(!pkeys.empty(), "empty pkeys");
+ // keys are decrypted
+ epee::misc_utils::auto_scope_leave_caller keys_reencryptor;
+ if (m_ask_password && !m_rpc && !m_watch_only)
+ {
+ crypto::chacha_key chacha_key;
+ crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds);
+ m_account.encrypt_viewkey(chacha_key);
+ m_account.decrypt_keys(chacha_key);
+ keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this, chacha_key]() { m_account.encrypt_keys(chacha_key); m_account.decrypt_viewkey(chacha_key); });
+ }
+
// add ours if not included
crypto::public_key local_signer;
CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(get_account().get_keys().m_spend_secret_key, local_signer),
@@ -3684,6 +3885,9 @@ bool wallet2::finalize_multisig(const epee::wipeable_string &password, std::unor
m_multisig_signers = signers;
std::sort(m_multisig_signers.begin(), m_multisig_signers.end(), [](const crypto::public_key &e0, const crypto::public_key &e1){ return memcmp(&e0, &e1, sizeof(e0)); });
+ // keys are encrypted again
+ keys_reencryptor = epee::misc_utils::auto_scope_leave_caller();
+
if (!m_wallet_file.empty())
{
bool r = store_keys(m_keys_file, password, false);
@@ -3995,6 +4199,11 @@ bool wallet2::generate_chacha_key_from_secret_keys(crypto::chacha_key &key) cons
return hwdev.generate_chacha_key(m_account.get_keys(), key, m_kdf_rounds);
}
//----------------------------------------------------------------------------------------------------
+void wallet2::generate_chacha_key_from_password(const epee::wipeable_string &pass, crypto::chacha_key &key) const
+{
+ crypto::generate_chacha_key(pass.data(), pass.size(), key, m_kdf_rounds);
+}
+//----------------------------------------------------------------------------------------------------
void wallet2::load(const std::string& wallet_, const epee::wipeable_string& password)
{
clear();
@@ -4015,6 +4224,8 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
LOG_PRINT_L0("Loaded wallet keys file, with public address: " << m_account.get_public_address_str(m_nettype));
lock_keys_file();
+ wallet_keys_unlocker unlocker(*this, m_ask_password && !m_rpc && !m_watch_only, password);
+
//keys loaded ok!
//try to load wallet file. but even if we failed, it is not big problem
if(!boost::filesystem::exists(m_wallet_file, e) || e)
@@ -4036,11 +4247,9 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
r = ::serialization::parse_binary(buf, cache_file_data);
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + m_wallet_file + '\"');
- crypto::chacha_key key;
- generate_chacha_key_from_secret_keys(key);
std::string cache_data;
cache_data.resize(cache_file_data.cache_data.size());
- crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cache_data[0]);
+ crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), m_cache_key, cache_file_data.iv, &cache_data[0]);
try {
std::stringstream iss;
@@ -4048,11 +4257,13 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
boost::archive::portable_binary_iarchive ar(iss);
ar >> *this;
}
- catch (...)
+ catch(...)
{
- crypto::chacha8(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cache_data[0]);
- try
- {
+ // try with previous scheme: direct from keys
+ crypto::chacha_key key;
+ generate_chacha_key_from_secret_keys(key);
+ crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cache_data[0]);
+ try {
std::stringstream iss;
iss << cache_data;
boost::archive::portable_binary_iarchive ar(iss);
@@ -4060,13 +4271,24 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
}
catch (...)
{
- LOG_PRINT_L0("Failed to open portable binary, trying unportable");
- boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists);
- std::stringstream iss;
- iss.str("");
- iss << cache_data;
- boost::archive::binary_iarchive ar(iss);
- ar >> *this;
+ crypto::chacha8(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cache_data[0]);
+ try
+ {
+ std::stringstream iss;
+ iss << cache_data;
+ boost::archive::portable_binary_iarchive ar(iss);
+ ar >> *this;
+ }
+ catch (...)
+ {
+ LOG_PRINT_L0("Failed to open portable binary, trying unportable");
+ boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists);
+ std::stringstream iss;
+ iss.str("");
+ iss << cache_data;
+ boost::archive::binary_iarchive ar(iss);
+ ar >> *this;
+ }
}
}
}
@@ -4218,12 +4440,10 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas
wallet2::cache_file_data cache_file_data = boost::value_initialized<wallet2::cache_file_data>();
cache_file_data.cache_data = oss.str();
- crypto::chacha_key key;
- generate_chacha_key_from_secret_keys(key);
std::string cipher;
cipher.resize(cache_file_data.cache_data.size());
cache_file_data.iv = crypto::rand<crypto::chacha_iv>();
- crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cipher[0]);
+ crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), m_cache_key, cache_file_data.iv, &cipher[0]);
cache_file_data.cache_data = cipher;
const std::string new_file = same_file ? m_wallet_file + ".new" : path;
@@ -5861,12 +6081,6 @@ crypto::chacha_key wallet2::get_ringdb_key()
return *m_ringdb_key;
}
-void wallet2::clear_ringdb_key()
-{
- MINFO("clearing ringdb key");
- m_ringdb_key = boost::none;
-}
-
bool wallet2::add_rings(const crypto::chacha_key &key, const cryptonote::transaction_prefix &tx)
{
if (!m_ringdb)
@@ -5877,7 +6091,6 @@ bool wallet2::add_rings(const crypto::chacha_key &key, const cryptonote::transac
bool wallet2::add_rings(const cryptonote::transaction_prefix &tx)
{
- key_ref kref(*this);
try { return add_rings(get_ringdb_key(), tx); }
catch (const std::exception &e) { return false; }
}
@@ -5886,7 +6099,6 @@ bool wallet2::remove_rings(const cryptonote::transaction_prefix &tx)
{
if (!m_ringdb)
return false;
- key_ref kref(*this);
try { return m_ringdb->remove_rings(get_ringdb_key(), tx); }
catch (const std::exception &e) { return false; }
}
@@ -5924,7 +6136,6 @@ bool wallet2::get_rings(const crypto::hash &txid, std::vector<std::pair<crypto::
bool wallet2::get_ring(const crypto::key_image &key_image, std::vector<uint64_t> &outs)
{
- key_ref kref(*this);
try { return get_ring(get_ringdb_key(), key_image, outs); }
catch (const std::exception &e) { return false; }
}
@@ -5934,7 +6145,6 @@ bool wallet2::set_ring(const crypto::key_image &key_image, const std::vector<uin
if (!m_ringdb)
return false;
- key_ref kref(*this);
try { return m_ringdb->set_ring(get_ringdb_key(), key_image, outs, relative); }
catch (const std::exception &e) { return false; }
}
@@ -5946,7 +6156,6 @@ bool wallet2::find_and_save_rings(bool force)
if (!m_ringdb)
return false;
- key_ref kref(*this);
COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req);
COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res);
@@ -10662,6 +10871,7 @@ size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs)
m_multisig_rescan_info = &info;
try
{
+
refresh(false);
}
catch (...) {}
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 86fee0da7..2d45f4e3e 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -67,6 +67,19 @@ class Serialization_portability_wallet_Test;
namespace tools
{
class ringdb;
+ class wallet2;
+
+ class wallet_keys_unlocker
+ {
+ public:
+ wallet_keys_unlocker(wallet2 &w, const boost::optional<tools::password_container> &password);
+ wallet_keys_unlocker(wallet2 &w, bool locked, const epee::wipeable_string &password);
+ ~wallet_keys_unlocker();
+ private:
+ wallet2 &w;
+ bool locked;
+ crypto::chacha_key key;
+ };
class i_wallet2_callback
{
@@ -77,6 +90,7 @@ namespace tools
virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) {}
virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index) {}
virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx) {}
+ virtual boost::optional<epee::wipeable_string> on_get_password(const char *reason) { return boost::none; }
// Light wallet callbacks
virtual void on_lw_new_block(uint64_t height) {}
virtual void on_lw_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) {}
@@ -133,9 +147,11 @@ namespace tools
std::deque<crypto::hash> m_blockchain;
};
+ class wallet_keys_unlocker;
class wallet2
{
friend class ::Serialization_portability_wallet_Test;
+ friend class wallet_keys_unlocker;
public:
static constexpr const std::chrono::seconds rpc_timeout = std::chrono::minutes(3) + std::chrono::seconds(30);
@@ -153,17 +169,17 @@ namespace tools
static void init_options(boost::program_options::options_description& desc_params);
//! Uses stdin and stdout. Returns a wallet2 if no errors.
- static std::unique_ptr<wallet2> make_from_json(const boost::program_options::variables_map& vm, const std::string& json_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter);
+ static std::unique_ptr<wallet2> make_from_json(const boost::program_options::variables_map& vm, bool rpc, const std::string& json_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter);
//! Uses stdin and stdout. Returns a wallet2 and password for `wallet_file` if no errors.
static std::pair<std::unique_ptr<wallet2>, password_container>
- make_from_file(const boost::program_options::variables_map& vm, const std::string& wallet_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter);
+ make_from_file(const boost::program_options::variables_map& vm, bool rpc, const std::string& wallet_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter);
//! Uses stdin and stdout. Returns a wallet2 and password for wallet with no file if no errors.
- static std::pair<std::unique_ptr<wallet2>, password_container> make_new(const boost::program_options::variables_map& vm, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter);
+ static std::pair<std::unique_ptr<wallet2>, password_container> make_new(const boost::program_options::variables_map& vm, bool rpc, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter);
//! Just parses variables.
- static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter);
+ static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, bool rpc, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter);
static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds);
@@ -477,16 +493,6 @@ namespace tools
std::vector<is_out_data> additional;
};
- struct key_ref
- {
- key_ref(tools::wallet2 &w): wallet(w) { ++refs; }
- ~key_ref() { if (!--refs) wallet.clear_ringdb_key(); }
-
- private:
- tools::wallet2 &wallet;
- static std::atomic<unsigned int> refs;
- };
-
/*!
* \brief Generates a wallet or restores one.
* \param wallet_ Name of wallet file
@@ -613,6 +619,11 @@ namespace tools
cryptonote::account_base& get_account(){return m_account;}
const cryptonote::account_base& get_account()const{return m_account;}
+ void encrypt_keys(const crypto::chacha_key &key);
+ void encrypt_keys(const epee::wipeable_string &password);
+ void decrypt_keys(const crypto::chacha_key &key);
+ void decrypt_keys(const epee::wipeable_string &password);
+
void set_refresh_from_block_height(uint64_t height) {m_refresh_from_block_height = height;}
uint64_t get_refresh_from_block_height() const {return m_refresh_from_block_height;}
@@ -625,7 +636,7 @@ namespace tools
// into account the current median block size rather than
// the minimum block size.
bool deinit();
- bool init(std::string daemon_address = "http://localhost:8080",
+ bool init(bool rpc, std::string daemon_address = "http://localhost:8080",
boost::optional<epee::net_utils::http::login> daemon_login = boost::none, uint64_t upper_transaction_size_limit = 0, bool ssl = false);
void stop() { m_run.store(false, std::memory_order_relaxed); }
@@ -1077,6 +1088,8 @@ namespace tools
uint64_t adjust_mixin(uint64_t mixin) const;
uint32_t adjust_priority(uint32_t priority);
+ bool is_rpc() const { return m_rpc; }
+
// Light wallet specific functions
// fetch unspent outs from lw node and store in m_transfers
void light_wallet_get_unspent_outs();
@@ -1153,6 +1166,9 @@ namespace tools
bool lock_keys_file();
bool unlock_keys_file();
bool is_keys_file_locked() const;
+
+ void change_password(const std::string &filename, const epee::wipeable_string &original_password, const epee::wipeable_string &new_password);
+
private:
/*!
* \brief Stores wallet information to wallet file.
@@ -1187,6 +1203,7 @@ namespace tools
void generate_genesis(cryptonote::block& b) const;
void check_genesis(const crypto::hash& genesis_hash) const; //throws
bool generate_chacha_key_from_secret_keys(crypto::chacha_key &key) const;
+ void generate_chacha_key_from_password(const epee::wipeable_string &pass, crypto::chacha_key &key) const;
crypto::hash get_payment_id(const pending_tx &ptx) const;
void check_acc_out_precomp(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, tx_scan_info_t &tx_scan_info) const;
void check_acc_out_precomp(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, const is_out_data *is_out_data, tx_scan_info_t &tx_scan_info) const;
@@ -1204,7 +1221,7 @@ namespace tools
crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;
bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const;
std::vector<size_t> get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const;
- void scan_output(const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs) const;
+ void scan_output(const cryptonote::transaction &tx, const crypto::public_key &tx_pub_key, size_t i, tx_scan_info_t &tx_scan_info, int &num_vouts_received, std::unordered_map<cryptonote::subaddress_index, uint64_t> &tx_money_got_in_outs, std::vector<size_t> &outs);
void trim_hashchain();
crypto::key_image get_multisig_composite_key_image(size_t n) const;
rct::multisig_kLRki get_multisig_composite_kLRki(size_t n, const crypto::public_key &ignore, std::unordered_set<rct::key> &used_L, std::unordered_set<rct::key> &new_used_L) const;
@@ -1216,8 +1233,7 @@ namespace tools
bool remove_rings(const cryptonote::transaction_prefix &tx);
bool get_ring(const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector<uint64_t> &outs);
crypto::chacha_key get_ringdb_key();
- void cache_ringdb_key();
- void clear_ringdb_key();
+ void setup_keys(const epee::wipeable_string &password);
bool get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution);
@@ -1320,6 +1336,11 @@ namespace tools
uint64_t m_last_block_reward;
std::unique_ptr<tools::file_locker> m_keys_file_locker;
+
+ crypto::chacha_key m_cache_key;
+ boost::optional<epee::wipeable_string> m_encrypt_keys_after_refresh;
+
+ bool m_rpc;
};
}
BOOST_CLASS_VERSION(tools::wallet2, 25)
diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h
index e80652750..243953280 100644
--- a/src/wallet/wallet_errors.h
+++ b/src/wallet/wallet_errors.h
@@ -53,6 +53,7 @@ namespace tools
// wallet_not_initialized
// multisig_export_needed
// multisig_import_needed
+ // password_needed
// std::logic_error
// wallet_logic_error *
// file_exists
@@ -209,6 +210,14 @@ namespace tools
}
};
//----------------------------------------------------------------------------------------------------
+ struct password_needed : public wallet_runtime_error
+ {
+ explicit password_needed(std::string&& loc, const std::string &msg = "Password needed")
+ : wallet_runtime_error(std::move(loc), msg)
+ {
+ }
+ };
+ //----------------------------------------------------------------------------------------------------
const char* const file_error_messages[] = {
"file already exists",
"file not found",
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 136cd0e2d..780f74d7f 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -163,7 +163,7 @@ namespace tools
walvars = m_wallet;
else
{
- tmpwal = tools::wallet2::make_dummy(*m_vm, password_prompter);
+ tmpwal = tools::wallet2::make_dummy(*m_vm, true, password_prompter);
walvars = tmpwal.get();
}
boost::optional<epee::net_utils::http::login> http_login{};
@@ -2638,7 +2638,7 @@ namespace tools
command_line::add_arg(desc, arg_password);
po::store(po::parse_command_line(argc, argv, desc), vm2);
}
- std::unique_ptr<tools::wallet2> wal = tools::wallet2::make_new(vm2, nullptr).first;
+ std::unique_ptr<tools::wallet2> wal = tools::wallet2::make_new(vm2, true, nullptr).first;
if (!wal)
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
@@ -2712,7 +2712,7 @@ namespace tools
}
std::unique_ptr<tools::wallet2> wal = nullptr;
try {
- wal = tools::wallet2::make_from_file(vm2, wallet_file, nullptr).first;
+ wal = tools::wallet2::make_from_file(vm2, true, wallet_file, nullptr).first;
}
catch (const std::exception& e)
{
@@ -3261,13 +3261,13 @@ int main(int argc, char** argv) {
LOG_PRINT_L0(tools::wallet_rpc_server::tr("Loading wallet..."));
if(!wallet_file.empty())
{
- wal = tools::wallet2::make_from_file(*vm, wallet_file, password_prompt).first;
+ wal = tools::wallet2::make_from_file(*vm, true, wallet_file, password_prompt).first;
}
else
{
try
{
- wal = tools::wallet2::make_from_json(*vm, from_json, password_prompt);
+ wal = tools::wallet2::make_from_json(*vm, true, from_json, password_prompt);
}
catch (const std::exception &e)
{
diff --git a/tests/functional_tests/transactions_flow_test.cpp b/tests/functional_tests/transactions_flow_test.cpp
index c36c53b89..03bfc0a9b 100644
--- a/tests/functional_tests/transactions_flow_test.cpp
+++ b/tests/functional_tests/transactions_flow_test.cpp
@@ -138,7 +138,7 @@ bool transactions_flow_test(std::string& working_folder,
return false;
}
- w1.init(daemon_addr_a);
+ w1.init(true, daemon_addr_a);
uint64_t blocks_fetched = 0;
bool received_money;
@@ -149,7 +149,7 @@ bool transactions_flow_test(std::string& working_folder,
return false;
}
- w2.init(daemon_addr_b);
+ w2.init(true, daemon_addr_b);
MGINFO_GREEN("Using wallets: " << ENDL
<< "Source: " << w1.get_account().get_public_address_str(MAINNET) << ENDL << "Path: " << working_folder + "/" + path_source_wallet << ENDL
diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt
index f47192ea9..6d4ebe47d 100644
--- a/tests/unit_tests/CMakeLists.txt
+++ b/tests/unit_tests/CMakeLists.txt
@@ -27,6 +27,7 @@
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(unit_tests_sources
+ account.cpp
apply_permutation.cpp
address_from_url.cpp
ban.cpp
diff --git a/tests/unit_tests/account.cpp b/tests/unit_tests/account.cpp
new file mode 100644
index 000000000..113622b5e
--- /dev/null
+++ b/tests/unit_tests/account.cpp
@@ -0,0 +1,71 @@
+// Copyright (c) 2014-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "gtest/gtest.h"
+
+#include "cryptonote_basic/account.h"
+
+TEST(account, encrypt_keys)
+{
+ cryptonote::keypair recovery_key = cryptonote::keypair::generate(hw::get_device("default"));
+ cryptonote::account_base account;
+ crypto::secret_key key = account.generate(recovery_key.sec);
+ const cryptonote::account_keys keys = account.get_keys();
+
+ ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address);
+ ASSERT_EQ(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key);
+ ASSERT_EQ(account.get_keys().m_view_secret_key, keys.m_view_secret_key);
+ ASSERT_EQ(account.get_keys().m_multisig_keys, keys.m_multisig_keys);
+
+ crypto::chacha_key chacha_key;
+ crypto::generate_chacha_key(&recovery_key, sizeof(recovery_key), chacha_key, 1);
+
+ account.encrypt_keys(chacha_key);
+
+ ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address);
+ ASSERT_NE(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key);
+ ASSERT_NE(account.get_keys().m_view_secret_key, keys.m_view_secret_key);
+
+ account.decrypt_viewkey(chacha_key);
+
+ ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address);
+ ASSERT_NE(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key);
+ ASSERT_EQ(account.get_keys().m_view_secret_key, keys.m_view_secret_key);
+
+ account.encrypt_viewkey(chacha_key);
+
+ ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address);
+ ASSERT_NE(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key);
+ ASSERT_NE(account.get_keys().m_view_secret_key, keys.m_view_secret_key);
+
+ account.decrypt_keys(chacha_key);
+
+ ASSERT_EQ(account.get_keys().m_account_address, keys.m_account_address);
+ ASSERT_EQ(account.get_keys().m_spend_secret_key, keys.m_spend_secret_key);
+ ASSERT_EQ(account.get_keys().m_view_secret_key, keys.m_view_secret_key);
+}
diff --git a/tests/unit_tests/multisig.cpp b/tests/unit_tests/multisig.cpp
index 922299333..eb453b960 100644
--- a/tests/unit_tests/multisig.cpp
+++ b/tests/unit_tests/multisig.cpp
@@ -61,10 +61,13 @@ static void make_wallet(unsigned int idx, tools::wallet2 &wallet)
try
{
- wallet.init("");
+ wallet.init(false, "");
wallet.set_subaddress_lookahead(1, 1);
wallet.generate("", "", spendkey, true, false);
ASSERT_TRUE(test_addresses[idx].address == wallet.get_account().get_public_address_str(cryptonote::TESTNET));
+ wallet.decrypt_keys("");
+ ASSERT_TRUE(test_addresses[idx].spendkey == epee::string_tools::pod_to_hex(wallet.get_account().get_keys().m_spend_secret_key));
+ wallet.encrypt_keys("");
}
catch (const std::exception &e)
{
@@ -83,8 +86,12 @@ static void make_M_2_wallet(tools::wallet2 &wallet0, tools::wallet2 &wallet1, un
std::vector<crypto::secret_key> sk0(1), sk1(1);
std::vector<crypto::public_key> pk0(1), pk1(1);
+ wallet0.decrypt_keys("");
std::string mi0 = wallet0.get_multisig_info();
+ wallet0.encrypt_keys("");
+ wallet1.decrypt_keys("");
std::string mi1 = wallet1.get_multisig_info();
+ wallet1.encrypt_keys("");
ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi1, sk0[0], pk0[0]));
ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi0, sk1[0], pk1[0]));
@@ -118,9 +125,15 @@ static void make_M_3_wallet(tools::wallet2 &wallet0, tools::wallet2 &wallet1, to
std::vector<crypto::secret_key> sk0(2), sk1(2), sk2(2);
std::vector<crypto::public_key> pk0(2), pk1(2), pk2(2);
+ wallet0.decrypt_keys("");
std::string mi0 = wallet0.get_multisig_info();
+ wallet0.encrypt_keys("");
+ wallet1.decrypt_keys("");
std::string mi1 = wallet1.get_multisig_info();
+ wallet1.encrypt_keys("");
+ wallet2.decrypt_keys("");
std::string mi2 = wallet2.get_multisig_info();
+ wallet2.encrypt_keys("");
ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi1, sk0[0], pk0[0]));
ASSERT_TRUE(tools::wallet2::verify_multisig_info(mi2, sk0[1], pk0[1]));
diff --git a/tests/unit_tests/ringdb.cpp b/tests/unit_tests/ringdb.cpp
index 8b0ea10d4..9b842569a 100644
--- a/tests/unit_tests/ringdb.cpp
+++ b/tests/unit_tests/ringdb.cpp
@@ -47,7 +47,7 @@ static crypto::chacha_key generate_chacha_key()
{
crypto::chacha_key chacha_key;
uint64_t password = crypto::rand<uint64_t>();
- crypto::generate_chacha_key(std::string((const char*)&password, sizeof(password)), chacha_key);
+ crypto::generate_chacha_key(std::string((const char*)&password, sizeof(password)), chacha_key, 1);
return chacha_key;
}