From bb560dd814072fb34578b8fda0da34304fff5c7b Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sun, 30 Oct 2016 19:37:09 +0000 Subject: [PATCH] wallet: new import_outputs/export_outputs commands The intended use is to export outputs from a hot wallet, which can scan incoming transfers from the network, and import them in the cold wallet, which can't. The cold wallet can then compute key images for those outputs, which can then be exported with export_key_images, etc. --- src/simplewallet/simplewallet.cpp | 100 ++++++++++++++++++++++++++++++ src/simplewallet/simplewallet.h | 2 + src/wallet/wallet2.cpp | 50 ++++++++++++++- src/wallet/wallet2.h | 3 + 4 files changed, 154 insertions(+), 1 deletion(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 09c574528..cef1d6dcf 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -78,6 +78,7 @@ typedef cryptonote::simple_wallet sw; #define DEFAULT_MIX 4 #define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\001" +#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\001" // workaround for a suspected bug in pthread/kernel on MacOS X #ifdef __APPLE__ @@ -703,6 +704,8 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("verify", boost::bind(&simple_wallet::verify, this, _1), tr("Verify a signature on the contents of a file")); m_cmd_binder.set_handler("export_key_images", boost::bind(&simple_wallet::export_key_images, this, _1), tr("Export a signed set of key images")); m_cmd_binder.set_handler("import_key_images", boost::bind(&simple_wallet::import_key_images, this, _1), tr("Import signed key images list and verify their spent status")); + m_cmd_binder.set_handler("export_outputs", boost::bind(&simple_wallet::export_outputs, this, _1), tr("Export a set of outputs owned by this wallet")); + m_cmd_binder.set_handler("import_outputs", boost::bind(&simple_wallet::import_outputs, this, _1), tr("Import set of outputs owned by this wallet")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("Show this help")); } //---------------------------------------------------------------------------------------------------- @@ -4101,6 +4104,103 @@ bool simple_wallet::import_key_images(const std::vector &args) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::export_outputs(const std::vector &args) +{ + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: export_outputs "); + return true; + } + std::string filename = args[0]; + + try + { + std::vector outs = m_wallet->export_outputs(); + + std::stringstream oss; + boost::archive::binary_oarchive ar(oss); + ar << outs; + + std::string data(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC)); + const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; + data += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key)); + data += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key)); + bool r = epee::file_io_utils::save_string_to_file(filename, data + oss.str()); + if (!r) + { + fail_msg_writer() << tr("failed to save file ") << filename; + return true; + } + } + catch (const std::exception &e) + { + LOG_ERROR("Error exporting outputs: " << e.what()); + fail_msg_writer() << "Error exporting outputs: " << e.what(); + return true; + } + + success_msg_writer() << tr("Outputs exported to ") << filename; + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::import_outputs(const std::vector &args) +{ + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: import_outputs "); + return true; + } + std::string filename = args[0]; + + std::string data; + bool r = epee::file_io_utils::load_file_to_string(filename, data); + if (!r) + { + fail_msg_writer() << tr("failed to read file ") << filename; + return true; + } + const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC); + if (data.size() < magiclen || memcmp(data.data(), OUTPUT_EXPORT_FILE_MAGIC, magiclen)) + { + fail_msg_writer() << "Bad output export file magic in " << filename; + return true; + } + const size_t headerlen = magiclen + 2 * sizeof(crypto::public_key); + if (data.size() < headerlen) + { + fail_msg_writer() << "Bad data size from file " << filename; + return true; + } + const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[magiclen]; + const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[magiclen + sizeof(crypto::public_key)]; + const cryptonote::account_public_address &keys = m_wallet->get_account().get_keys().m_account_address; + if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) + { + fail_msg_writer() << "Outputs from " << filename << " are for a different account"; + return true; + } + + try + { + std::string body(data, headerlen); + std::stringstream iss; + iss << body; + boost::archive::binary_iarchive ar(iss); + std::vector outputs; + ar >> outputs; + + size_t n_outputs = m_wallet->import_outputs(outputs); + success_msg_writer() << boost::lexical_cast(n_outputs) << " outputs imported"; + } + catch (const std::exception &e) + { + fail_msg_writer() << "Failed to import outputs: " << e.what(); + return true; + } + + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::process_command(const std::vector &args) { return m_cmd_binder.process_command_vec(args); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 375716604..92cc0ddfc 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -152,6 +152,8 @@ namespace cryptonote bool verify(const std::vector &args); bool export_key_images(const std::vector &args); bool import_key_images(const std::vector &args); + bool export_outputs(const std::vector &args); + bool import_outputs(const std::vector &args); uint64_t get_daemon_blockchain_height(std::string& err); bool try_connect_to_daemon(bool silent = false); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index a02c2e4e5..ba8c65540 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4243,7 +4243,11 @@ std::vector> wallet2::export_key crypto::key_image ki; cryptonote::keypair in_ephemeral; cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, td.m_internal_output_index, in_ephemeral, ki); - THROW_WALLET_EXCEPTION_IF(ki != td.m_key_image, + + bool zero_key_image = true; + for (size_t i = 0; i < sizeof(td.m_key_image); ++i) + zero_key_image &= (td.m_key_image.data[i] == 0); + THROW_WALLET_EXCEPTION_IF(!zero_key_image && ki != td.m_key_image, error::wallet_internal_error, "key_image generated not matched with cached key image"); THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != pkey, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); @@ -4330,6 +4334,50 @@ uint64_t wallet2::import_key_images(const std::vector wallet2::export_outputs() const +{ + std::vector outs; + + outs.reserve(m_transfers.size()); + for (size_t n = 0; n < m_transfers.size(); ++n) + { + const transfer_details &td = m_transfers[n]; + + outs.push_back(td); + } + + return outs; +} +//---------------------------------------------------------------------------------------------------- +size_t wallet2::import_outputs(const std::vector &outputs) +{ + m_transfers.clear(); + m_transfers.reserve(outputs.size()); + for (size_t i = 0; i < outputs.size(); ++i) + { + transfer_details td = outputs[i]; + + // the hot wallet wouldn't have known about key images (except if we already exported them) + cryptonote::keypair in_ephemeral; + std::vector tx_extra_fields; + tx_extra_pub_key pub_key_field; + + THROW_WALLET_EXCEPTION_IF(td.m_tx.vout.empty(), error::wallet_internal_error, "tx with no outputs at index " + i); + THROW_WALLET_EXCEPTION_IF(!parse_tx_extra(td.m_tx.extra, tx_extra_fields), error::wallet_internal_error, + "Transaction extra has unsupported format at index " + i); + THROW_WALLET_EXCEPTION_IF(!find_tx_extra_field_by_type(tx_extra_fields, pub_key_field), error::wallet_internal_error, + "Public key wasn't found in the transaction extra at index " + i); + + cryptonote::generate_key_image_helper(m_account.get_keys(), pub_key_field.pub_key, td.m_internal_output_index, in_ephemeral, td.m_key_image); + THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != boost::get(td.m_tx.vout[td.m_internal_output_index].target).key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + i); + + m_transfers.push_back(td); + } + + return m_transfers.size(); +} +//---------------------------------------------------------------------------------------------------- void wallet2::generate_genesis(cryptonote::block& b) { if (m_testnet) { diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 39a2a37f1..8afe35aca 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -473,6 +473,9 @@ namespace tools std::string sign(const std::string &data) const; bool verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const; + std::vector export_outputs() const; + size_t import_outputs(const std::vector &outputs); + std::vector> export_key_images() const; uint64_t import_key_images(const std::vector> &signed_key_images, uint64_t &spent, uint64_t &unspent);