aboutsummaryrefslogtreecommitdiff
path: root/src/simplewallet/simplewallet.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/simplewallet/simplewallet.cpp')
-rw-r--r--src/simplewallet/simplewallet.cpp307
1 files changed, 251 insertions, 56 deletions
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index 73b56f2b7..04170df62 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 <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};
@@ -539,7 +541,7 @@ simple_wallet::simple_wallet()
m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), tr("Show blockchain height"));
m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), tr("transfer [<mixin_count>] <addr_1> <amount_1> [<addr_2> <amount_2> ... <addr_N> <amount_N>] [payment_id] - Transfer <amount_1>,... <amount_N> to <address_1>,... <address_N>, respectively. <mixin_count> is the number of extra inputs to include for untraceability (from 0 to maximum available)"));
m_cmd_binder.set_handler("transfer_new", boost::bind(&simple_wallet::transfer_new, this, _1), tr("Same as transfer, but using a new transaction building algorithm"));
- m_cmd_binder.set_handler("sweep_dust", boost::bind(&simple_wallet::sweep_dust, this, _1), tr("Send all dust outputs to yourself with mixin 0"));
+ m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with mixin 0"));
m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log <level> - Change current log detail level, <0-4>"));
m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), tr("Show current wallet public address"));
m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::print_integrated_address, this, _1), tr("integrated_address [PID] - Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID"));
@@ -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;
@@ -761,52 +763,28 @@ void simple_wallet::print_seed(std::string seed)
}
//----------------------------------------------------------------------------------------------------
-bool simple_wallet::init(const boost::program_options::variables_map& vm)
+static bool get_password(const boost::program_options::variables_map& vm, bool allow_entry, tools::password_container &pwd_container)
{
- if (!handle_command_line(vm))
- return false;
-
- if (!m_daemon_address.empty() && !m_daemon_host.empty() && 0 != m_daemon_port)
- {
- fail_msg_writer() << tr("can't specify daemon host or port more than once");
- return false;
- }
-
- if((!m_generate_new.empty()) + (!m_wallet_file.empty()) + (!m_generate_from_view_key.empty()) + (!m_generate_from_keys.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\"");
- return false;
- }
- else if (m_generate_new.empty() && m_wallet_file.empty() && m_generate_from_view_key.empty() && m_generate_from_keys.empty())
- {
- if(!ask_wallet_create_if_needed()) return false;
- }
-
- bool testnet = command_line::get_arg(vm, arg_testnet);
-
- if (m_daemon_host.empty())
- m_daemon_host = "localhost";
-
- if (!m_daemon_port)
- {
- m_daemon_port = testnet ? config::testnet::RPC_DEFAULT_PORT : config::RPC_DEFAULT_PORT;
+ // 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 (m_daemon_address.empty())
- m_daemon_address = std::string("http://") + m_daemon_host + ":" + std::to_string(m_daemon_port);
-
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");
return false;
}
- tools::password_container pwd_container;
if (command_line::has_arg(vm, arg_password))
{
pwd_container.password(command_line::get_arg(vm, arg_password));
+ return true;
}
- else if (command_line::has_arg(vm, arg_password_file))
+
+ if (command_line::has_arg(vm, arg_password_file))
{
std::string password;
bool r = epee::file_io_utils::load_file_to_string(command_line::get_arg(vm, arg_password_file),
@@ -821,8 +799,10 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
password.erase(std::remove(password.begin() - 1, password.end(), '\n'), password.end());
password.erase(std::remove(password.end() - 1, password.end(), '\r'), password.end());
pwd_container.password(password.c_str());
+ return true;
}
- else
+
+ if (allow_entry)
{
bool r = pwd_container.read_password();
if (!r)
@@ -830,9 +810,200 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
fail_msg_writer() << tr("failed to read wallet password");
return false;
}
+ return true;
}
- if (!m_generate_new.empty() || m_restore_deterministic_wallet || !m_generate_from_view_key.empty() || !m_generate_from_keys.empty())
+ fail_msg_writer() << tr("Wallet password not set.");
+ 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)
+{
+ if (!handle_command_line(vm))
+ return false;
+
+ if (!m_daemon_address.empty() && !m_daemon_host.empty() && 0 != m_daemon_port)
+ {
+ fail_msg_writer() << tr("can't specify daemon host or port more than once");
+ return false;
+ }
+
+ 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\", --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() && m_generate_from_json.empty())
+ {
+ if(!ask_wallet_create_if_needed()) return false;
+ }
+
+ bool testnet = command_line::get_arg(vm, arg_testnet);
+
+ if (m_daemon_host.empty())
+ m_daemon_host = "localhost";
+
+ if (!m_daemon_port)
+ {
+ m_daemon_port = testnet ? config::testnet::RPC_DEFAULT_PORT : config::RPC_DEFAULT_PORT;
+ }
+
+ if (m_daemon_address.empty())
+ m_daemon_address = std::string("http://") + m_daemon_host + ":" + std::to_string(m_daemon_port);
+
+ tools::password_container pwd_container;
+ 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() || !m_generate_from_json.empty())
{
if (m_wallet_file.empty()) m_wallet_file = m_generate_new; // alias for simplicity later
@@ -978,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,
@@ -1008,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);
@@ -1544,8 +1722,7 @@ bool simple_wallet::refresh(const std::vector<std::string>& args)
bool simple_wallet::show_balance(const std::vector<std::string>& args/* = std::vector<std::string>()*/)
{
success_msg_writer() << tr("Balance: ") << print_money(m_wallet->balance()) << ", "
- << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance()) << ", "
- << tr("including unlocked dust: ") << print_money(m_wallet->unlocked_dust_balance(tools::tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD)));
+ << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance());
return true;
}
//----------------------------------------------------------------------------------------------------
@@ -2030,7 +2207,7 @@ bool simple_wallet::transfer_new(const std::vector<std::string> &args_)
}
//----------------------------------------------------------------------------------------------------
-bool simple_wallet::sweep_dust(const std::vector<std::string> &args_)
+bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
{
if (!try_connect_to_daemon())
return true;
@@ -2043,28 +2220,37 @@ bool simple_wallet::sweep_dust(const std::vector<std::string> &args_)
try
{
- uint64_t total_dust = m_wallet->unlocked_dust_balance(tools::tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD));
-
// figure out what tx will be necessary
- auto ptx_vector = m_wallet->create_dust_sweep_transactions();
+ auto ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon);
+
+ if (ptx_vector.empty())
+ {
+ fail_msg_writer() << tr("No unmixable outputs found");
+ return true;
+ }
// give user total and fee, and prompt to confirm
- uint64_t total_fee = 0;
+ uint64_t total_fee = 0, total_unmixable = 0;
for (size_t n = 0; n < ptx_vector.size(); ++n)
{
total_fee += ptx_vector[n].fee;
+ for (const auto &vin: ptx_vector[n].tx.vin)
+ {
+ if (vin.type() == typeid(txin_to_key))
+ total_unmixable += boost::get<txin_to_key>(vin).amount;
+ }
}
- std::string prompt_str = tr("Sweeping ") + print_money(total_dust);
+ std::string prompt_str = tr("Sweeping ") + print_money(total_unmixable);
if (ptx_vector.size() > 1) {
prompt_str = (boost::format(tr("Sweeping %s in %llu transactions for a total fee of %s. Is this okay? (Y/Yes/N/No)")) %
- print_money(total_dust) %
+ print_money(total_unmixable) %
((unsigned long long)ptx_vector.size()) %
print_money(total_fee)).str();
}
else {
prompt_str = (boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)")) %
- print_money(total_dust) %
+ print_money(total_unmixable) %
print_money(total_fee)).str();
}
std::string accepted = command_line::input_line(prompt_str);
@@ -2107,11 +2293,12 @@ bool simple_wallet::sweep_dust(const std::vector<std::string> &args_)
}
catch (const tools::error::not_enough_money& e)
{
- fail_msg_writer() << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) %
+ fail_msg_writer() << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee).\n%s")) %
print_money(e.available()) %
print_money(e.tx_amount() + e.fee()) %
print_money(e.tx_amount()) %
- print_money(e.fee());
+ print_money(e.fee()) %
+ tr("This is usually due to dust which is so small it cannot pay for itself in fees");
}
catch (const tools::error::not_enough_outs_to_mix& e)
{
@@ -2560,6 +2747,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);
@@ -2675,16 +2863,13 @@ int main(int argc, char* argv[])
LOG_ERROR(sw::tr("Daemon address not set."));
return 1;
}
- if(!command_line::has_arg(vm, arg_password) )
- {
- LOG_ERROR(sw::tr("Wallet password not set."));
- return 1;
- }
bool testnet = command_line::get_arg(vm, arg_testnet);
bool restricted = command_line::get_arg(vm, arg_restricted);
std::string wallet_file = command_line::get_arg(vm, arg_wallet_file);
- std::string wallet_password = command_line::get_arg(vm, arg_password);
+ tools::password_container pwd_container;
+ if (!get_password(vm, false, pwd_container))
+ return 1;
std::string daemon_address = command_line::get_arg(vm, arg_daemon_address);
std::string daemon_host = command_line::get_arg(vm, arg_daemon_host);
int daemon_port = command_line::get_arg(vm, arg_daemon_port);
@@ -2695,11 +2880,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, wallet_password);
+ wal.load(wallet_file, password);
wal.init(daemon_address);
wal.refresh();
LOG_PRINT_GREEN(sw::tr("Loaded ok"), LOG_LEVEL_0);