mirror of
https://codeberg.org/anoncontributorxmr/monero.git
synced 2024-11-27 13:23:29 +01:00
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.
This commit is contained in:
parent
83b0511731
commit
bb560dd814
@ -78,6 +78,7 @@ typedef cryptonote::simple_wallet sw;
|
|||||||
#define DEFAULT_MIX 4
|
#define DEFAULT_MIX 4
|
||||||
|
|
||||||
#define KEY_IMAGE_EXPORT_FILE_MAGIC "Monero key image export\001"
|
#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
|
// workaround for a suspected bug in pthread/kernel on MacOS X
|
||||||
#ifdef __APPLE__
|
#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("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("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("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"));
|
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<std::string> &args)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
|
bool simple_wallet::export_outputs(const std::vector<std::string> &args)
|
||||||
|
{
|
||||||
|
if (args.size() != 1)
|
||||||
|
{
|
||||||
|
fail_msg_writer() << tr("usage: export_outputs <filename>");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::string filename = args[0];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::vector<tools::wallet2::transfer_details> 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<std::string> &args)
|
||||||
|
{
|
||||||
|
if (args.size() != 1)
|
||||||
|
{
|
||||||
|
fail_msg_writer() << tr("usage: import_outputs <filename>");
|
||||||
|
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<tools::wallet2::transfer_details> outputs;
|
||||||
|
ar >> outputs;
|
||||||
|
|
||||||
|
size_t n_outputs = m_wallet->import_outputs(outputs);
|
||||||
|
success_msg_writer() << boost::lexical_cast<std::string>(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<std::string> &args)
|
bool simple_wallet::process_command(const std::vector<std::string> &args)
|
||||||
{
|
{
|
||||||
return m_cmd_binder.process_command_vec(args);
|
return m_cmd_binder.process_command_vec(args);
|
||||||
|
@ -152,6 +152,8 @@ namespace cryptonote
|
|||||||
bool verify(const std::vector<std::string> &args);
|
bool verify(const std::vector<std::string> &args);
|
||||||
bool export_key_images(const std::vector<std::string> &args);
|
bool export_key_images(const std::vector<std::string> &args);
|
||||||
bool import_key_images(const std::vector<std::string> &args);
|
bool import_key_images(const std::vector<std::string> &args);
|
||||||
|
bool export_outputs(const std::vector<std::string> &args);
|
||||||
|
bool import_outputs(const std::vector<std::string> &args);
|
||||||
|
|
||||||
uint64_t get_daemon_blockchain_height(std::string& err);
|
uint64_t get_daemon_blockchain_height(std::string& err);
|
||||||
bool try_connect_to_daemon(bool silent = false);
|
bool try_connect_to_daemon(bool silent = false);
|
||||||
|
@ -4243,7 +4243,11 @@ std::vector<std::pair<crypto::key_image, crypto::signature>> wallet2::export_key
|
|||||||
crypto::key_image ki;
|
crypto::key_image ki;
|
||||||
cryptonote::keypair in_ephemeral;
|
cryptonote::keypair in_ephemeral;
|
||||||
cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, td.m_internal_output_index, in_ephemeral, ki);
|
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");
|
error::wallet_internal_error, "key_image generated not matched with cached key image");
|
||||||
THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != pkey,
|
THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != pkey,
|
||||||
error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key");
|
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<std::pair<crypto::key_imag
|
|||||||
return m_transfers[signed_key_images.size() - 1].m_block_height;
|
return m_transfers[signed_key_images.size() - 1].m_block_height;
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
|
std::vector<tools::wallet2::transfer_details> wallet2::export_outputs() const
|
||||||
|
{
|
||||||
|
std::vector<tools::wallet2::transfer_details> 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<tools::wallet2::transfer_details> &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_field> 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<cryptonote::txout_to_key>(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) {
|
void wallet2::generate_genesis(cryptonote::block& b) {
|
||||||
if (m_testnet)
|
if (m_testnet)
|
||||||
{
|
{
|
||||||
|
@ -473,6 +473,9 @@ namespace tools
|
|||||||
std::string sign(const std::string &data) const;
|
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;
|
bool verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const;
|
||||||
|
|
||||||
|
std::vector<tools::wallet2::transfer_details> export_outputs() const;
|
||||||
|
size_t import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs);
|
||||||
|
|
||||||
std::vector<std::pair<crypto::key_image, crypto::signature>> export_key_images() const;
|
std::vector<std::pair<crypto::key_image, crypto::signature>> export_key_images() const;
|
||||||
uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent);
|
uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user