wallet: add a --generate-from-json flag

It takes a filename containing JSON data to generate a wallet.
The following fields are valid:

  version: integer, should be 1
  filename: string, path/filename for the newly created wallet
  scan_from_height: 64 bit unsigned integer, optional
  password: string, optional
  viewkey: string, hex representation
  spendkey: string, hex representation
  seed: string, optional, list of words separated by spaces

Either seed or private keys should be given. If using private
keys, the spend key may be omitted (the wallet will not be
able to spend, but will see incoming transactions).

If scan_from_height is given, blocks below this height will not
be checked for transactions as an optimization.
This commit is contained in:
moneromooo-monero 2016-03-25 00:48:11 +00:00
parent eb1b87d239
commit a2e378b91b
No known key found for this signature in database
GPG key ID: 686F07454D6CEFC3
4 changed files with 192 additions and 10 deletions

View file

@ -57,6 +57,7 @@
#include "version.h" #include "version.h"
#include "crypto/crypto.h" // for crypto::secret_key definition #include "crypto/crypto.h" // for crypto::secret_key definition
#include "mnemonics/electrum-words.h" #include "mnemonics/electrum-words.h"
#include "rapidjson/document.h"
#include <stdexcept> #include <stdexcept>
#if defined(WIN32) #if defined(WIN32)
@ -81,6 +82,7 @@ namespace
const command_line::arg_descriptor<std::string> arg_generate_new_wallet = {"generate-new-wallet", sw::tr("Generate new wallet and save it to <arg> or <address>.wallet by default"), ""}; const command_line::arg_descriptor<std::string> arg_generate_new_wallet = {"generate-new-wallet", sw::tr("Generate new wallet and save it to <arg> or <address>.wallet by default"), ""};
const command_line::arg_descriptor<std::string> arg_generate_from_view_key = {"generate-from-view-key", sw::tr("Generate incoming-only wallet from view key"), ""}; const command_line::arg_descriptor<std::string> arg_generate_from_view_key = {"generate-from-view-key", sw::tr("Generate incoming-only wallet from view key"), ""};
const command_line::arg_descriptor<std::string> arg_generate_from_keys = {"generate-from-keys", sw::tr("Generate wallet from private keys"), ""}; const command_line::arg_descriptor<std::string> arg_generate_from_keys = {"generate-from-keys", sw::tr("Generate wallet from private keys"), ""};
const command_line::arg_descriptor<std::string> arg_generate_from_json = {"generate-from-json", sw::tr("Generate wallet from JSON format file"), ""};
const command_line::arg_descriptor<std::string> arg_daemon_address = {"daemon-address", sw::tr("Use daemon instance at <host>:<port>"), ""}; const command_line::arg_descriptor<std::string> arg_daemon_address = {"daemon-address", sw::tr("Use daemon instance at <host>:<port>"), ""};
const command_line::arg_descriptor<std::string> arg_daemon_host = {"daemon-host", sw::tr("Use daemon instance at host <arg> instead of localhost"), ""}; const command_line::arg_descriptor<std::string> arg_daemon_host = {"daemon-host", sw::tr("Use daemon instance at host <arg> instead of localhost"), ""};
const command_line::arg_descriptor<std::string> arg_password = {"password", sw::tr("Wallet password"), "", true}; const command_line::arg_descriptor<std::string> 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 // add logic to error out if new wallet requested but named wallet file exists
if (keys_file_exists || 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."); fail_msg_writer() << tr("attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting.");
return false; 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) 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)) 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"); 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; 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<const crypto::secret_key*>(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<const crypto::secret_key*>(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) 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; 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; 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; 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)) if (!get_password(vm, true, pwd_container))
return false; 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 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); bool r = new_wallet(m_wallet_file, pwd_container.password(), address, spendkey, viewkey, testnet);
CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); 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 else
{ {
bool r = new_wallet(m_wallet_file, pwd_container.password(), m_recovery_key, m_restore_deterministic_wallet, 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_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_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_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_address = command_line::get_arg(vm, arg_daemon_address);
m_daemon_host = command_line::get_arg(vm, arg_daemon_host); m_daemon_host = command_line::get_arg(vm, arg_daemon_host);
m_daemon_port = command_line::get_arg(vm, arg_daemon_port); 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_new_wallet);
command_line::add_arg(desc_params, arg_generate_from_view_key); 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_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);
command_line::add_arg(desc_params, arg_password_file); command_line::add_arg(desc_params, arg_password_file);
command_line::add_arg(desc_params, arg_daemon_address); command_line::add_arg(desc_params, arg_daemon_address);
@ -2707,11 +2871,21 @@ int main(int argc, char* argv[])
if (daemon_address.empty()) if (daemon_address.empty())
daemon_address = std::string("http://") + daemon_host + ":" + std::to_string(daemon_port); 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); tools::wallet2 wal(testnet,restricted);
try try
{ {
LOG_PRINT_L0(sw::tr("Loading wallet...")); LOG_PRINT_L0(sw::tr("Loading wallet..."));
wal.load(wallet_file, pwd_container.password()); wal.load(wallet_file, password);
wal.init(daemon_address); wal.init(daemon_address);
wal.refresh(); wal.refresh();
LOG_PRINT_GREEN(sw::tr("Loaded ok"), LOG_LEVEL_0); LOG_PRINT_GREEN(sw::tr("Loaded ok"), LOG_LEVEL_0);

View file

@ -69,6 +69,7 @@ namespace cryptonote
bool run(); bool run();
void stop(); void stop();
void interrupt(); void interrupt();
bool generate_from_json(const boost::program_options::variables_map& vm, std::string &wallet_file, std::string &password);
//wallet *create_wallet(); //wallet *create_wallet();
bool process_command(const std::vector<std::string> &args); bool process_command(const std::vector<std::string> &args);
@ -221,6 +222,7 @@ namespace cryptonote
std::string m_generate_new; std::string m_generate_new;
std::string m_generate_from_view_key; std::string m_generate_from_view_key;
std::string m_generate_from_keys; std::string m_generate_from_keys;
std::string m_generate_from_json;
std::string m_import_path; std::string m_import_path;
std::string m_electrum_seed; // electrum-style seed parameter std::string m_electrum_seed; // electrum-style seed parameter

View file

@ -490,7 +490,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry
//handle transactions from new block //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 //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); TIME_MEASURE_START(miner_tx_handle_time);
process_new_transaction(b.miner_tx, height, true); process_new_transaction(b.miner_tx, height, true);

View file

@ -88,10 +88,10 @@ namespace tools
}; };
private: 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: 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 struct transfer_details
{ {
uint64_t m_block_height; uint64_t m_block_height;
@ -226,6 +226,8 @@ namespace tools
cryptonote::account_base& get_account(){return m_account;} cryptonote::account_base& get_account(){return m_account;}
const cryptonote::account_base& get_account()const{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 // upper_transaction_size_limit as defined below is set to
// approximately 125% of the fixed minimum allowable penalty // approximately 125% of the fixed minimum allowable penalty
// free block size. TODO: fix this so that it actually takes // free block size. TODO: fix this so that it actually takes
@ -319,6 +321,9 @@ namespace tools
if(ver < 9) if(ver < 9)
return; return;
a & m_confirmed_txs; 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; uint32_t m_default_mixin;
RefreshType m_refresh_type; RefreshType m_refresh_type;
bool m_auto_refresh; 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::payment_details, 0)
BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 2) BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 2)
BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 1) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 1)