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:
moneromooo-monero 2016-10-30 19:37:09 +00:00
parent 83b0511731
commit bb560dd814
No known key found for this signature in database
GPG Key ID: 686F07454D6CEFC3
4 changed files with 154 additions and 1 deletions

View File

@ -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<std::string> &args)
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)
{
return m_cmd_binder.process_command_vec(args);

View File

@ -152,6 +152,8 @@ namespace cryptonote
bool verify(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 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);
bool try_connect_to_daemon(bool silent = false);

View File

@ -4243,7 +4243,11 @@ std::vector<std::pair<crypto::key_image, crypto::signature>> 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<std::pair<crypto::key_imag
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) {
if (m_testnet)
{

View File

@ -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<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;
uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent);