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:
parent
eb1b87d239
commit
a2e378b91b
@ -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)
|
||||
@ -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};
|
||||
@ -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<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)
|
||||
{
|
||||
@ -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);
|
||||
|
@ -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);
|
||||
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user