wallet: add watch only wallet support
The new save_watch_only saves a copy of the keys file without the spend key. It can then be given away to be used as a normal keys file, but with no spend ability.
This commit is contained in:
parent
f7767c6508
commit
c882af63c1
4 changed files with 84 additions and 7 deletions
|
@ -299,6 +299,7 @@ simple_wallet::simple_wallet()
|
||||||
m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), "set_log <level> - Change current log detalization level, <level> is a number 0-4");
|
m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), "set_log <level> - Change current log detalization level, <level> is a number 0-4");
|
||||||
m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), "Show current wallet public address");
|
m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), "Show current wallet public address");
|
||||||
m_cmd_binder.set_handler("save", boost::bind(&simple_wallet::save, this, _1), "Save wallet synchronized data");
|
m_cmd_binder.set_handler("save", boost::bind(&simple_wallet::save, this, _1), "Save wallet synchronized data");
|
||||||
|
m_cmd_binder.set_handler("save_watch_only", boost::bind(&simple_wallet::save_watch_only, this, _1), "Save watch only keys file");
|
||||||
m_cmd_binder.set_handler("viewkey", boost::bind(&simple_wallet::viewkey, this, _1), "Get viewkey");
|
m_cmd_binder.set_handler("viewkey", boost::bind(&simple_wallet::viewkey, this, _1), "Get viewkey");
|
||||||
m_cmd_binder.set_handler("spendkey", boost::bind(&simple_wallet::spendkey, this, _1), "Get spendkey");
|
m_cmd_binder.set_handler("spendkey", boost::bind(&simple_wallet::spendkey, this, _1), "Get spendkey");
|
||||||
m_cmd_binder.set_handler("seed", boost::bind(&simple_wallet::seed, this, _1), "Get deterministic seed");
|
m_cmd_binder.set_handler("seed", boost::bind(&simple_wallet::seed, this, _1), "Get deterministic seed");
|
||||||
|
@ -680,7 +681,8 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
m_wallet->load(m_wallet_file, password);
|
m_wallet->load(m_wallet_file, password);
|
||||||
message_writer(epee::log_space::console_color_white, true) << "Opened wallet: "
|
std::string wallet_type = m_wallet->watch_only() ? "watch-only wallet" : "wallet";
|
||||||
|
message_writer(epee::log_space::console_color_white, true) << "Opened " << wallet_type << ": "
|
||||||
<< m_wallet->get_account().get_public_address_str(m_wallet->testnet());
|
<< m_wallet->get_account().get_public_address_str(m_wallet->testnet());
|
||||||
// If the wallet file is deprecated, we should ask for mnemonic language again and store
|
// If the wallet file is deprecated, we should ask for mnemonic language again and store
|
||||||
// everything in the new format.
|
// everything in the new format.
|
||||||
|
@ -760,6 +762,30 @@ bool simple_wallet::save(const std::vector<std::string> &args)
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
//----------------------------------------------------------------------------------------------------
|
||||||
|
bool simple_wallet::save_watch_only(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
|
||||||
|
{
|
||||||
|
bool success = false;
|
||||||
|
tools::password_container pwd_container;
|
||||||
|
success = pwd_container.read_password();
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
fail_msg_writer() << "failed to read wallet password";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* verify password before using so user doesn't accidentally set a new password for rewritten wallet */
|
||||||
|
success = m_wallet->verify_password(pwd_container.password());
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
fail_msg_writer() << "invalid password";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_wallet->write_watch_only_wallet(m_wallet_file, pwd_container.password());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
bool simple_wallet::start_mining(const std::vector<std::string>& args)
|
bool simple_wallet::start_mining(const std::vector<std::string>& args)
|
||||||
{
|
{
|
||||||
|
@ -1104,6 +1130,12 @@ bool simple_wallet::transfer(const std::vector<std::string> &args_)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(m_wallet->watch_only())
|
||||||
|
{
|
||||||
|
fail_msg_writer() << "This is a watch only wallet";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> extra;
|
std::vector<uint8_t> extra;
|
||||||
if (1 == local_args.size() % 2)
|
if (1 == local_args.size() % 2)
|
||||||
{
|
{
|
||||||
|
@ -1316,6 +1348,12 @@ bool simple_wallet::sweep_dust(const std::vector<std::string> &args_)
|
||||||
if (!try_connect_to_daemon())
|
if (!try_connect_to_daemon())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
if(m_wallet->watch_only())
|
||||||
|
{
|
||||||
|
fail_msg_writer() << "This is a watch only wallet";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
uint64_t total_dust = m_wallet->unlocked_dust_balance(tools::tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD));
|
uint64_t total_dust = m_wallet->unlocked_dust_balance(tools::tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD));
|
||||||
|
|
|
@ -109,6 +109,7 @@ namespace cryptonote
|
||||||
);
|
);
|
||||||
bool print_address(const std::vector<std::string> &args = std::vector<std::string>());
|
bool print_address(const std::vector<std::string> &args = std::vector<std::string>());
|
||||||
bool save(const std::vector<std::string> &args);
|
bool save(const std::vector<std::string> &args);
|
||||||
|
bool save_watch_only(const std::vector<std::string> &args);
|
||||||
bool set_variable(const std::vector<std::string> &args);
|
bool set_variable(const std::vector<std::string> &args);
|
||||||
bool set_log(const std::vector<std::string> &args);
|
bool set_log(const std::vector<std::string> &args);
|
||||||
|
|
||||||
|
|
|
@ -472,12 +472,17 @@ bool wallet2::clear()
|
||||||
* \brief Stores wallet information to wallet file.
|
* \brief Stores wallet information to wallet file.
|
||||||
* \param keys_file_name Name of wallet file
|
* \param keys_file_name Name of wallet file
|
||||||
* \param password Password of wallet file
|
* \param password Password of wallet file
|
||||||
|
* \param watch_only true to save only view key, false to save both spend and view keys
|
||||||
* \return Whether it was successful.
|
* \return Whether it was successful.
|
||||||
*/
|
*/
|
||||||
bool wallet2::store_keys(const std::string& keys_file_name, const std::string& password)
|
bool wallet2::store_keys(const std::string& keys_file_name, const std::string& password, bool watch_only)
|
||||||
{
|
{
|
||||||
std::string account_data;
|
std::string account_data;
|
||||||
bool r = epee::serialization::store_t_to_binary(m_account, account_data);
|
cryptonote::account_base account = m_account;
|
||||||
|
|
||||||
|
if (watch_only)
|
||||||
|
account.forget_spend_key();
|
||||||
|
bool r = epee::serialization::store_t_to_binary(account, account_data);
|
||||||
CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet keys");
|
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>();
|
wallet2::keys_file_data keys_file_data = boost::value_initialized<wallet2::keys_file_data>();
|
||||||
|
|
||||||
|
@ -493,6 +498,10 @@ bool wallet2::store_keys(const std::string& keys_file_name, const std::string& p
|
||||||
json.AddMember("seed_language", value, json.GetAllocator());
|
json.AddMember("seed_language", value, json.GetAllocator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rapidjson::Value value2(rapidjson::kNumberType);
|
||||||
|
value2.SetInt(watch_only ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ??
|
||||||
|
json.AddMember("watch_only", value2, json.GetAllocator());
|
||||||
|
|
||||||
// Serialize the JSON object
|
// Serialize the JSON object
|
||||||
rapidjson::StringBuffer buffer;
|
rapidjson::StringBuffer buffer;
|
||||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||||
|
@ -552,6 +561,7 @@ void wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
|
||||||
if (json.Parse(account_data.c_str(), keys_file_data.account_data.size()).HasParseError())
|
if (json.Parse(account_data.c_str(), keys_file_data.account_data.size()).HasParseError())
|
||||||
{
|
{
|
||||||
is_old_file_format = true;
|
is_old_file_format = true;
|
||||||
|
m_watch_only = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -562,12 +572,21 @@ void wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
|
||||||
set_seed_language(std::string(json["seed_language"].GetString(), json["seed_language"].GetString() +
|
set_seed_language(std::string(json["seed_language"].GetString(), json["seed_language"].GetString() +
|
||||||
json["seed_language"].GetStringLength()));
|
json["seed_language"].GetStringLength()));
|
||||||
}
|
}
|
||||||
|
if (json.HasMember("watch_only"))
|
||||||
|
{
|
||||||
|
m_watch_only = json["watch_only"].GetInt() != 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_watch_only = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cryptonote::account_keys& keys = m_account.get_keys();
|
const cryptonote::account_keys& keys = m_account.get_keys();
|
||||||
r = epee::serialization::load_t_from_binary(m_account, account_data);
|
r = epee::serialization::load_t_from_binary(m_account, account_data);
|
||||||
r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
|
r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key);
|
||||||
r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
|
if(!m_watch_only)
|
||||||
|
r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key);
|
||||||
THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password);
|
THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -642,7 +661,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const std::stri
|
||||||
|
|
||||||
m_account_public_address = m_account.get_keys().m_account_address;
|
m_account_public_address = m_account.get_keys().m_account_address;
|
||||||
|
|
||||||
bool r = store_keys(m_keys_file, password);
|
bool r = store_keys(m_keys_file, password, false);
|
||||||
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
|
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
|
||||||
|
|
||||||
r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet));
|
r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet));
|
||||||
|
@ -666,9 +685,24 @@ void wallet2::rewrite(const std::string& wallet_name, const std::string& passwor
|
||||||
prepare_file_names(wallet_name);
|
prepare_file_names(wallet_name);
|
||||||
boost::system::error_code ignored_ec;
|
boost::system::error_code ignored_ec;
|
||||||
THROW_WALLET_EXCEPTION_IF(!boost::filesystem::exists(m_keys_file, ignored_ec), error::file_not_found, m_keys_file);
|
THROW_WALLET_EXCEPTION_IF(!boost::filesystem::exists(m_keys_file, ignored_ec), error::file_not_found, m_keys_file);
|
||||||
bool r = store_keys(m_keys_file, password);
|
bool r = store_keys(m_keys_file, password, false);
|
||||||
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
|
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file);
|
||||||
}
|
}
|
||||||
|
/*!
|
||||||
|
* \brief Rewrites to the wallet file for wallet upgrade (doesn't generate key, assumes it's already there)
|
||||||
|
* \param wallet_name Name of wallet file (should exist)
|
||||||
|
* \param password Password for wallet file
|
||||||
|
*/
|
||||||
|
void wallet2::write_watch_only_wallet(const std::string& wallet_name, const std::string& password)
|
||||||
|
{
|
||||||
|
prepare_file_names(wallet_name);
|
||||||
|
boost::system::error_code ignored_ec;
|
||||||
|
std::string filename = m_keys_file + "-watchonly";
|
||||||
|
bool watch_only_keys_file_exists = boost::filesystem::exists(filename, ignored_ec);
|
||||||
|
THROW_WALLET_EXCEPTION_IF(watch_only_keys_file_exists, error::file_save_error, filename);
|
||||||
|
bool r = store_keys(filename, password, true);
|
||||||
|
THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, filename);
|
||||||
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
void wallet2::wallet_exists(const std::string& file_path, bool& keys_file_exists, bool& wallet_file_exists)
|
void wallet2::wallet_exists(const std::string& file_path, bool& keys_file_exists, bool& wallet_file_exists)
|
||||||
{
|
{
|
||||||
|
|
|
@ -151,6 +151,7 @@ namespace tools
|
||||||
* \param password Password for wallet file
|
* \param password Password for wallet file
|
||||||
*/
|
*/
|
||||||
void rewrite(const std::string& wallet_name, const std::string& password);
|
void rewrite(const std::string& wallet_name, const std::string& password);
|
||||||
|
void write_watch_only_wallet(const std::string& wallet_name, const std::string& password);
|
||||||
void load(const std::string& wallet, const std::string& password);
|
void load(const std::string& wallet, const std::string& password);
|
||||||
void store();
|
void store();
|
||||||
|
|
||||||
|
@ -198,6 +199,7 @@ namespace tools
|
||||||
|
|
||||||
bool testnet() const { return m_testnet; }
|
bool testnet() const { return m_testnet; }
|
||||||
bool restricted() const { return m_restricted; }
|
bool restricted() const { return m_restricted; }
|
||||||
|
bool watch_only() const { return m_watch_only; }
|
||||||
|
|
||||||
uint64_t balance() const;
|
uint64_t balance() const;
|
||||||
uint64_t unlocked_balance() const;
|
uint64_t unlocked_balance() const;
|
||||||
|
@ -260,9 +262,10 @@ namespace tools
|
||||||
* \brief Stores wallet information to wallet file.
|
* \brief Stores wallet information to wallet file.
|
||||||
* \param keys_file_name Name of wallet file
|
* \param keys_file_name Name of wallet file
|
||||||
* \param password Password of wallet file
|
* \param password Password of wallet file
|
||||||
|
* \param watch_only true to save only view key, false to save both spend and view keys
|
||||||
* \return Whether it was successful.
|
* \return Whether it was successful.
|
||||||
*/
|
*/
|
||||||
bool store_keys(const std::string& keys_file_name, const std::string& password);
|
bool store_keys(const std::string& keys_file_name, const std::string& password, bool watch_only = false);
|
||||||
/*!
|
/*!
|
||||||
* \brief Load wallet information from wallet file.
|
* \brief Load wallet information from wallet file.
|
||||||
* \param keys_file_name Name of wallet file
|
* \param keys_file_name Name of wallet file
|
||||||
|
@ -306,6 +309,7 @@ namespace tools
|
||||||
bool m_restricted;
|
bool m_restricted;
|
||||||
std::string seed_language; /*!< Language of the mnemonics (seed). */
|
std::string seed_language; /*!< Language of the mnemonics (seed). */
|
||||||
bool is_old_file_format; /*!< Whether the wallet file is of an old file format */
|
bool is_old_file_format; /*!< Whether the wallet file is of an old file format */
|
||||||
|
bool m_watch_only; /*!< no spend key */
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
BOOST_CLASS_VERSION(tools::wallet2, 7)
|
BOOST_CLASS_VERSION(tools::wallet2, 7)
|
||||||
|
|
Loading…
Reference in a new issue