diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index ea1993c80..1856118f7 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -57,6 +57,7 @@ #include "version.h" #include "crypto/crypto.h" // for crypto::secret_key definition #include "mnemonics/electrum-words.h" +#include "rapidjson/document.h" #include #if defined(WIN32) @@ -81,6 +82,7 @@ namespace const command_line::arg_descriptor arg_generate_new_wallet = {"generate-new-wallet", sw::tr("Generate new wallet and save it to or
.wallet by default"), ""}; const command_line::arg_descriptor arg_generate_from_view_key = {"generate-from-view-key", sw::tr("Generate incoming-only wallet from view key"), ""}; const command_line::arg_descriptor arg_generate_from_keys = {"generate-from-keys", sw::tr("Generate wallet from private keys"), ""}; + const command_line::arg_descriptor arg_generate_from_json = {"generate-from-json", sw::tr("Generate wallet from JSON format file"), ""}; const command_line::arg_descriptor arg_daemon_address = {"daemon-address", sw::tr("Use daemon instance at :"), ""}; const command_line::arg_descriptor arg_daemon_host = {"daemon-host", sw::tr("Use daemon instance at host instead of localhost"), ""}; const command_line::arg_descriptor arg_password = {"password", sw::tr("Wallet password"), "", true}; @@ -716,7 +718,7 @@ bool simple_wallet::ask_wallet_create_if_needed() // add logic to error out if new wallet requested but named wallet file exists if (keys_file_exists || wallet_file_exists) { - if (!m_generate_new.empty() || m_restore_deterministic_wallet || !m_generate_from_view_key.empty() || !m_generate_from_keys.empty()) + if (!m_generate_new.empty() || m_restore_deterministic_wallet || !m_generate_from_view_key.empty() || !m_generate_from_keys.empty() || !m_generate_from_json.empty()) { fail_msg_writer() << tr("attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting."); return false; @@ -763,6 +765,13 @@ void simple_wallet::print_seed(std::string seed) //---------------------------------------------------------------------------------------------------- static bool get_password(const boost::program_options::variables_map& vm, bool allow_entry, tools::password_container &pwd_container) { + // has_arg returns true even when the parameter is not passed ?? + const std::string gfj = command_line::get_arg(vm, arg_generate_from_json); + if (!gfj.empty()) { + // will be in the json file, if any + return true; + } + if (has_arg(vm, arg_password) && has_arg(vm, arg_password_file)) { fail_msg_writer() << tr("can't specify more than one of --password and --password-file"); @@ -808,6 +817,153 @@ static bool get_password(const boost::program_options::variables_map& vm, bool a return false; } +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::generate_from_json(const boost::program_options::variables_map& vm, std::string &wallet_file, std::string &password) +{ + std::string buf; + bool r = epee::file_io_utils::load_file_to_string(m_generate_from_json, buf); + if (!r) { + fail_msg_writer() << tr("Failed to load file ") << m_generate_from_json; + return false; + } + + rapidjson::Document json; + if (json.Parse(buf.c_str()).HasParseError()) { + fail_msg_writer() << tr("Failed to parse JSON"); + return false; + } + + if (!json.HasMember("version")) { + fail_msg_writer() << tr("Version not found in JSON"); + return false; + } + unsigned int version = json["version"].GetUint(); + const int current_version = 1; + if (version > current_version) { + fail_msg_writer() << boost::format(tr("Version %u too new, we can only grok up to %u")) % version % current_version; + return false; + } + if (!json.HasMember("filename")) { + fail_msg_writer() << tr("Filename not found in JSON"); + return false; + } + std::string filename = json["filename"].GetString(); + + bool recover = false; + uint64_t scan_from_height = 0; + if (json.HasMember("scan_from_height")) { + scan_from_height = json["scan_from_height"].GetUint64(); + recover = true; + } + + password = ""; + if (json.HasMember("password")) { + password = json["password"].GetString(); + } + + std::string viewkey_string(""); + crypto::secret_key viewkey; + if (json.HasMember("viewkey")) { + viewkey_string = json["viewkey"].GetString(); + cryptonote::blobdata viewkey_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(viewkey_string, viewkey_data)) + { + fail_msg_writer() << tr("failed to parse view key secret key"); + return false; + } + viewkey = *reinterpret_cast(viewkey_data.data()); + } + + std::string spendkey_string(""); + crypto::secret_key spendkey; + if (json.HasMember("spendkey")) { + spendkey_string = json["spendkey"].GetString(); + cryptonote::blobdata spendkey_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(spendkey_string, spendkey_data)) + { + fail_msg_writer() << tr("failed to parse spend key secret key"); + return false; + } + spendkey = *reinterpret_cast(spendkey_data.data()); + } + + std::string seed(""); + std::string old_language; + if (json.HasMember("seed")) { + seed = json["seed"].GetString(); + if (!crypto::ElectrumWords::words_to_bytes(seed, m_recovery_key, old_language)) + { + fail_msg_writer() << tr("Electrum-style word list failed verification"); + return false; + } + m_electrum_seed = seed; + m_restore_deterministic_wallet = true; + } + + // compatibility checks + if (seed.empty() && viewkey_string.empty()) { + fail_msg_writer() << tr("At least one of Electrum-style word list and private view key must be specified"); + return false; + } + if (!seed.empty() && (!viewkey_string.empty() || !spendkey_string.empty())) { + fail_msg_writer() << tr("Both Electrum-style word list and private key(s) specified"); + return false; + } + + m_wallet_file = filename; + + bool was_deprecated_wallet = m_restore_deterministic_wallet && ((old_language == crypto::ElectrumWords::old_language_name) || + crypto::ElectrumWords::get_is_old_style_seed(m_electrum_seed)); + if (was_deprecated_wallet) { + fail_msg_writer() << tr("Cannot create deprecated wallets from JSON"); + return false; + } + + bool testnet = command_line::get_arg(vm, arg_testnet); + m_wallet.reset(new tools::wallet2(testnet)); + m_wallet->callback(this); + + try + { + if (!seed.empty()) + { + m_wallet->generate(m_wallet_file, password, m_recovery_key, recover, false); + } + else + { + cryptonote::account_public_address address; + if (!crypto::secret_key_to_public_key(viewkey, address.m_view_public_key)) { + fail_msg_writer() << tr("failed to verify view key secret key"); + return false; + } + if (!crypto::secret_key_to_public_key(spendkey, address.m_spend_public_key)) { + fail_msg_writer() << tr("failed to verify spend key secret key"); + return false; + } + + if (spendkey_string.empty()) + { + m_wallet->generate(m_wallet_file, password, address, viewkey); + } + else + { + m_wallet->generate(m_wallet_file, password, address, spendkey, viewkey); + } + } + } + catch (const std::exception& e) + { + fail_msg_writer() << tr("failed to generate new wallet: ") << e.what(); + return false; + } + + m_wallet->set_refresh_from_block_height(scan_from_height); + + wallet_file = m_wallet_file; + + return r; +} + //---------------------------------------------------------------------------------------------------- bool simple_wallet::init(const boost::program_options::variables_map& vm) { @@ -820,12 +976,12 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } - if((!m_generate_new.empty()) + (!m_wallet_file.empty()) + (!m_generate_from_view_key.empty()) + (!m_generate_from_keys.empty()) > 1) + if((!m_generate_new.empty()) + (!m_wallet_file.empty()) + (!m_generate_from_view_key.empty()) + (!m_generate_from_keys.empty()) + (!m_generate_from_json.empty()) > 1) { - fail_msg_writer() << tr("can't specify more than one of --generate-new-wallet=\"wallet_name\", --wallet-file=\"wallet_name\", --generate-from-view-key=\"wallet_name\" and --generate-from-keys=\"wallet_name\""); + fail_msg_writer() << tr("can't specify more than one of --generate-new-wallet=\"wallet_name\", --wallet-file=\"wallet_name\", --generate-from-view-key=\"wallet_name\", --generate-from-json=\"jsonfilename\" and --generate-from-keys=\"wallet_name\""); return false; } - else if (m_generate_new.empty() && m_wallet_file.empty() && m_generate_from_view_key.empty() && m_generate_from_keys.empty()) + else if (m_generate_new.empty() && m_wallet_file.empty() && m_generate_from_view_key.empty() && m_generate_from_keys.empty() && m_generate_from_json.empty()) { if(!ask_wallet_create_if_needed()) return false; } @@ -847,7 +1003,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) if (!get_password(vm, true, pwd_container)) return false; - if (!m_generate_new.empty() || m_restore_deterministic_wallet || !m_generate_from_view_key.empty() || !m_generate_from_keys.empty()) + if (!m_generate_new.empty() || m_restore_deterministic_wallet || !m_generate_from_view_key.empty() || !m_generate_from_keys.empty() || !m_generate_from_json.empty()) { if (m_wallet_file.empty()) m_wallet_file = m_generate_new; // alias for simplicity later @@ -993,6 +1149,12 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) bool r = new_wallet(m_wallet_file, pwd_container.password(), address, spendkey, viewkey, testnet); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); } + else if (!m_generate_from_json.empty()) + { + std::string wallet_file, password; // we don't need to remember them + if (!generate_from_json(vm, wallet_file, password)) + return false; + } else { bool r = new_wallet(m_wallet_file, pwd_container.password(), m_recovery_key, m_restore_deterministic_wallet, @@ -1023,6 +1185,7 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ m_generate_new = command_line::get_arg(vm, arg_generate_new_wallet); m_generate_from_view_key = command_line::get_arg(vm, arg_generate_from_view_key); m_generate_from_keys = command_line::get_arg(vm, arg_generate_from_keys); + m_generate_from_json = command_line::get_arg(vm, arg_generate_from_json); m_daemon_address = command_line::get_arg(vm, arg_daemon_address); m_daemon_host = command_line::get_arg(vm, arg_daemon_host); m_daemon_port = command_line::get_arg(vm, arg_daemon_port); @@ -2575,6 +2738,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_generate_new_wallet); command_line::add_arg(desc_params, arg_generate_from_view_key); command_line::add_arg(desc_params, arg_generate_from_keys); + command_line::add_arg(desc_params, arg_generate_from_json); command_line::add_arg(desc_params, arg_password); command_line::add_arg(desc_params, arg_password_file); command_line::add_arg(desc_params, arg_daemon_address); @@ -2707,11 +2871,21 @@ int main(int argc, char* argv[]) if (daemon_address.empty()) daemon_address = std::string("http://") + daemon_host + ":" + std::to_string(daemon_port); + std::string password; + const std::string gfj = command_line::get_arg(vm, arg_generate_from_json); + if (!gfj.empty()) { + if (!w.generate_from_json(vm, wallet_file, password)) + return 1; + } + else { + password = pwd_container.password(); + } + tools::wallet2 wal(testnet,restricted); try { LOG_PRINT_L0(sw::tr("Loading wallet...")); - wal.load(wallet_file, pwd_container.password()); + wal.load(wallet_file, password); wal.init(daemon_address); wal.refresh(); LOG_PRINT_GREEN(sw::tr("Loaded ok"), LOG_LEVEL_0); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index d1617dbf4..7dadb8536 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -69,6 +69,7 @@ namespace cryptonote bool run(); void stop(); void interrupt(); + bool generate_from_json(const boost::program_options::variables_map& vm, std::string &wallet_file, std::string &password); //wallet *create_wallet(); bool process_command(const std::vector &args); @@ -221,6 +222,7 @@ namespace cryptonote std::string m_generate_new; std::string m_generate_from_view_key; std::string m_generate_from_keys; + std::string m_generate_from_json; std::string m_import_path; std::string m_electrum_seed; // electrum-style seed parameter diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 2afe08cb1..1c7187cf0 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -490,7 +490,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry //handle transactions from new block //optimization: seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup - if(b.timestamp + 60*60*24 > m_account.get_createtime()) + if(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height) { TIME_MEASURE_START(miner_tx_handle_time); process_new_transaction(b.miner_tx, height, true); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index f798f404d..e7b002925 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -88,10 +88,10 @@ namespace tools }; private: - wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers (false), m_store_tx_info(true), m_default_mixin(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true) {} + wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers (false), m_store_tx_info(true), m_default_mixin(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0) {} public: - wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_restricted(restricted), is_old_file_format(false), m_store_tx_info(true), m_default_mixin(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true) {} + wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_restricted(restricted), is_old_file_format(false), m_store_tx_info(true), m_default_mixin(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0) {} struct transfer_details { uint64_t m_block_height; @@ -226,6 +226,8 @@ namespace tools cryptonote::account_base& get_account(){return m_account;} const cryptonote::account_base& get_account()const{return m_account;} + void set_refresh_from_block_height(uint64_t height) {m_refresh_from_block_height = height;} + // upper_transaction_size_limit as defined below is set to // approximately 125% of the fixed minimum allowable penalty // free block size. TODO: fix this so that it actually takes @@ -319,6 +321,9 @@ namespace tools if(ver < 9) return; a & m_confirmed_txs; + if(ver < 11) + return; + a & m_refresh_from_block_height; } /*! @@ -430,9 +435,10 @@ namespace tools uint32_t m_default_mixin; RefreshType m_refresh_type; bool m_auto_refresh; + uint64_t m_refresh_from_block_height; }; } -BOOST_CLASS_VERSION(tools::wallet2, 10) +BOOST_CLASS_VERSION(tools::wallet2, 11) BOOST_CLASS_VERSION(tools::wallet2::payment_details, 0) BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 2) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 1)