diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 33224aa19..3159e85d4 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -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 - Change current log detalization 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("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("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"); @@ -680,7 +681,8 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa try { 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()); // If the wallet file is deprecated, we should ask for mnemonic language again and store // everything in the new format. @@ -760,6 +762,30 @@ bool simple_wallet::save(const std::vector &args) return true; } +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::save_watch_only(const std::vector &args/* = std::vector()*/) +{ + 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& args) { @@ -1104,6 +1130,12 @@ bool simple_wallet::transfer(const std::vector &args_) return true; } + if(m_wallet->watch_only()) + { + fail_msg_writer() << "This is a watch only wallet"; + return true; + } + std::vector extra; if (1 == local_args.size() % 2) { @@ -1316,6 +1348,12 @@ bool simple_wallet::sweep_dust(const std::vector &args_) if (!try_connect_to_daemon()) return true; + if(m_wallet->watch_only()) + { + fail_msg_writer() << "This is a watch only wallet"; + return true; + } + try { uint64_t total_dust = m_wallet->unlocked_dust_balance(tools::tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD)); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index d4ef0703b..fa16fd386 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -109,6 +109,7 @@ namespace cryptonote ); bool print_address(const std::vector &args = std::vector()); bool save(const std::vector &args); + bool save_watch_only(const std::vector &args); bool set_variable(const std::vector &args); bool set_log(const std::vector &args); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index a3deb5ac5..151da5602 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -472,12 +472,17 @@ bool wallet2::clear() * \brief Stores wallet information to wallet file. * \param keys_file_name Name 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. */ -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; - 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"); wallet2::keys_file_data keys_file_data = boost::value_initialized(); @@ -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()); } + 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 rapidjson::StringBuffer buffer; rapidjson::Writer 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()) { is_old_file_format = true; + m_watch_only = false; } 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() + 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(); 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_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); } @@ -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; - 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); 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); 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); - 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); } +/*! + * \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) { diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index a57501786..4a07de8d1 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -151,6 +151,7 @@ namespace tools * \param password Password for wallet file */ 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 store(); @@ -198,6 +199,7 @@ namespace tools bool testnet() const { return m_testnet; } bool restricted() const { return m_restricted; } + bool watch_only() const { return m_watch_only; } uint64_t balance() const; uint64_t unlocked_balance() const; @@ -260,9 +262,10 @@ namespace tools * \brief Stores wallet information to wallet file. * \param keys_file_name Name 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. */ - 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. * \param keys_file_name Name of wallet file @@ -306,6 +309,7 @@ namespace tools bool m_restricted; 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 m_watch_only; /*!< no spend key */ }; } BOOST_CLASS_VERSION(tools::wallet2, 7)