Skip to content

Commit

Permalink
wallet: add a --generate-from-json flag
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
moneromooo-monero committed Mar 25, 2016
1 parent eb1b87d commit a2e378b
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 10 deletions.
186 changes: 180 additions & 6 deletions src/simplewallet/simplewallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <stdexcept>

#if defined(WIN32)
Expand All @@ -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_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_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_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};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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<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)
{
Expand All @@ -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;
}
Expand All @@ -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

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/simplewallet/simplewallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> &args);
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/wallet2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
12 changes: 9 additions & 3 deletions src/wallet/wallet2.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -319,6 +321,9 @@ namespace tools
if(ver < 9)
return;
a & m_confirmed_txs;
if(ver < 11)
return;
a & m_refresh_from_block_height;
}

/*!
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit a2e378b

Please sign in to comment.