wallet-rpc: watch-only and cold wallet features added
- unsigned_txset, signed_txset in transfer / submit_transfer / sign_transfer
- export_outputs, import_outputs
Squashed commits:
[f4d9f3d4] wallet-rpc: do_not_relay removed from submit_transfer
[5b16a86f] wallet-rpc: review-fix - method signature changes, renaming
[b7fbb10a] wallet-rpc: naming fixes (unsigned vs signed), consts renamed
[8c7d2727] wallet-rpc: sign_transfer added
[481d024a] wallet2: sign_tx splitted to work with strings and structs, more granular
[2a474db9] wallet-rpc: wallet2::load_unsigned_tx split to load from str, file
[b1e3a018] wallet-rpc: review fix, load_tx_from_str variable rename
[1f6373be] wallet-rpc: review fix: save_tx_to_{str,file}
[2a08eafc] wallet-rpc: review comments fixes
- redundant this removed from wallet2.cpp
- load_tx_from_str, load_tx_from_file
[43498052] wallet-rpc: submit_transfer added
[9c45d1ad] wallet-rpc: watch_only check, return unsigned_txset
[62831396] wallet2: added string variants to load_tx, save_tx
- analogously to save_multisig_tx
- required for monero-wallet-rpc to support watch-only wallet
This commit is contained in:
+157
-26
@@ -110,6 +110,8 @@ using namespace cryptonote;
|
||||
|
||||
#define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001"
|
||||
|
||||
#define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003"
|
||||
|
||||
#define SEGREGATION_FORK_HEIGHT 1546000
|
||||
#define TESTNET_SEGREGATION_FORK_HEIGHT 1000000
|
||||
#define STAGENET_SEGREGATION_FORK_HEIGHT 1000000
|
||||
@@ -4659,6 +4661,15 @@ void wallet2::commit_tx(std::vector<pending_tx>& ptx_vector)
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) const
|
||||
{
|
||||
LOG_PRINT_L0("saving " << ptx_vector.size() << " transactions");
|
||||
std::string ciphertext = dump_tx_to_str(ptx_vector);
|
||||
if (ciphertext.empty())
|
||||
return false;
|
||||
return epee::file_io_utils::save_string_to_file(filename, ciphertext);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
std::string wallet2::dump_tx_to_str(const std::vector<pending_tx> &ptx_vector) const
|
||||
{
|
||||
LOG_PRINT_L0("saving " << ptx_vector.size() << " transactions");
|
||||
unsigned_tx_set txs;
|
||||
@@ -4680,11 +4691,11 @@ bool wallet2::save_tx(const std::vector<pending_tx>& ptx_vector, const std::stri
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
return std::string();
|
||||
}
|
||||
LOG_PRINT_L2("Saving unsigned tx data: " << oss.str());
|
||||
std::string ciphertext = encrypt_with_view_secret_key(oss.str());
|
||||
return epee::file_io_utils::save_string_to_file(filename, std::string(UNSIGNED_TX_PREFIX) + ciphertext);
|
||||
return std::string(UNSIGNED_TX_PREFIX) + ciphertext;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs) const
|
||||
@@ -4702,10 +4713,17 @@ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx
|
||||
LOG_PRINT_L0("Failed to load from " << unsigned_filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
return parse_unsigned_tx_from_str(s, exported_txs);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsigned_tx_set &exported_txs) const
|
||||
{
|
||||
std::string s = unsigned_tx_st;
|
||||
const size_t magiclen = strlen(UNSIGNED_TX_PREFIX) - 1;
|
||||
if (strncmp(s.c_str(), UNSIGNED_TX_PREFIX, magiclen))
|
||||
{
|
||||
LOG_PRINT_L0("Bad magic from " << unsigned_filename);
|
||||
LOG_PRINT_L0("Bad magic from unsigned tx");
|
||||
return false;
|
||||
}
|
||||
s = s.substr(magiclen);
|
||||
@@ -4721,7 +4739,7 @@ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_PRINT_L0("Failed to parse data from " << unsigned_filename);
|
||||
LOG_PRINT_L0("Failed to parse data from unsigned tx");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -4738,19 +4756,19 @@ bool wallet2::load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_PRINT_L0("Failed to parse data from " << unsigned_filename);
|
||||
LOG_PRINT_L0("Failed to parse data from unsigned tx");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
LOG_PRINT_L0("Failed to decrypt " << unsigned_filename << ": " << e.what());
|
||||
LOG_PRINT_L0("Failed to decrypt unsigned tx: " << e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_PRINT_L0("Unsupported version in " << unsigned_filename);
|
||||
LOG_PRINT_L0("Unsupported version in unsigned tx");
|
||||
return false;
|
||||
}
|
||||
LOG_PRINT_L1("Loaded tx unsigned data from binary: " << exported_txs.txes.size() << " transactions");
|
||||
@@ -4771,14 +4789,12 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s
|
||||
}
|
||||
return sign_tx(exported_txs, signed_filename, txs, export_raw);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, bool export_raw)
|
||||
bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &txs, signed_tx_set &signed_txes)
|
||||
{
|
||||
import_outputs(exported_txs.transfers);
|
||||
|
||||
// sign the transactions
|
||||
signed_tx_set signed_txes;
|
||||
for (size_t n = 0; n < exported_txs.txes.size(); ++n)
|
||||
{
|
||||
tools::wallet2::tx_construction_data &sd = exported_txs.txes[n];
|
||||
@@ -4840,19 +4856,20 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f
|
||||
signed_txes.key_images[i] = m_transfers[i].m_key_image;
|
||||
}
|
||||
|
||||
// save as binary
|
||||
std::ostringstream oss;
|
||||
boost::archive::portable_binary_oarchive ar(oss);
|
||||
try
|
||||
{
|
||||
ar << signed_txes;
|
||||
}
|
||||
catch(...)
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_filename, std::vector<wallet2::pending_tx> &txs, bool export_raw)
|
||||
{
|
||||
// sign the transactions
|
||||
signed_tx_set signed_txes;
|
||||
std::string ciphertext = sign_tx_dump_to_str(exported_txs, txs, signed_txes);
|
||||
if (ciphertext.empty())
|
||||
{
|
||||
LOG_PRINT_L0("Failed to sign unsigned_tx_set");
|
||||
return false;
|
||||
}
|
||||
LOG_PRINT_L3("Saving signed tx data (with encryption): " << oss.str());
|
||||
std::string ciphertext = encrypt_with_view_secret_key(oss.str());
|
||||
|
||||
if (!epee::file_io_utils::save_string_to_file(signed_filename, std::string(SIGNED_TX_PREFIX) + ciphertext))
|
||||
{
|
||||
LOG_PRINT_L0("Failed to save file to " << signed_filename);
|
||||
@@ -4875,6 +4892,32 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
std::string wallet2::sign_tx_dump_to_str(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &ptx, signed_tx_set &signed_txes)
|
||||
{
|
||||
// sign the transactions
|
||||
bool r = sign_tx(exported_txs, ptx, signed_txes);
|
||||
if (!r)
|
||||
{
|
||||
LOG_PRINT_L0("Failed to sign unsigned_tx_set");
|
||||
return std::string();
|
||||
}
|
||||
|
||||
// save as binary
|
||||
std::ostringstream oss;
|
||||
boost::archive::portable_binary_oarchive ar(oss);
|
||||
try
|
||||
{
|
||||
ar << signed_txes;
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return std::string();
|
||||
}
|
||||
LOG_PRINT_L3("Saving signed tx data (with encryption): " << oss.str());
|
||||
std::string ciphertext = encrypt_with_view_secret_key(oss.str());
|
||||
return std::string(SIGNED_TX_PREFIX) + ciphertext;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func)
|
||||
{
|
||||
std::string s;
|
||||
@@ -4892,10 +4935,20 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal
|
||||
LOG_PRINT_L0("Failed to load from " << signed_filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
return parse_tx_from_str(s, ptx, accept_func);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func)
|
||||
{
|
||||
std::string s = signed_tx_st;
|
||||
boost::system::error_code errcode;
|
||||
signed_tx_set signed_txs;
|
||||
|
||||
const size_t magiclen = strlen(SIGNED_TX_PREFIX) - 1;
|
||||
if (strncmp(s.c_str(), SIGNED_TX_PREFIX, magiclen))
|
||||
{
|
||||
LOG_PRINT_L0("Bad magic from " << signed_filename);
|
||||
LOG_PRINT_L0("Bad magic from signed transaction");
|
||||
return false;
|
||||
}
|
||||
s = s.substr(magiclen);
|
||||
@@ -4911,7 +4964,7 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_PRINT_L0("Failed to parse data from " << signed_filename);
|
||||
LOG_PRINT_L0("Failed to parse data from signed transaction");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -4928,23 +4981,23 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector<tools::wal
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_PRINT_L0("Failed to parse decrypted data from " << signed_filename);
|
||||
LOG_PRINT_L0("Failed to parse decrypted data from signed transaction");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
LOG_PRINT_L0("Failed to decrypt " << signed_filename << ": " << e.what());
|
||||
LOG_PRINT_L0("Failed to decrypt signed transaction: " << e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_PRINT_L0("Unsupported version in " << signed_filename);
|
||||
LOG_PRINT_L0("Unsupported version in signed transaction");
|
||||
return false;
|
||||
}
|
||||
LOG_PRINT_L0("Loaded signed tx data from binary: " << signed_txs.ptx.size() << " transactions");
|
||||
for (auto &ptx: signed_txs.ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(ptx.tx));
|
||||
for (auto &c_ptx: signed_txs.ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(c_ptx.tx));
|
||||
|
||||
if (accept_func && !accept_func(signed_txs))
|
||||
{
|
||||
@@ -9772,6 +9825,23 @@ std::vector<tools::wallet2::transfer_details> wallet2::export_outputs() const
|
||||
return outs;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
std::string wallet2::export_outputs_to_str() const
|
||||
{
|
||||
std::vector<tools::wallet2::transfer_details> outs = export_outputs();
|
||||
|
||||
std::stringstream oss;
|
||||
boost::archive::portable_binary_oarchive ar(oss);
|
||||
ar << outs;
|
||||
|
||||
std::string magic(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC));
|
||||
const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
|
||||
std::string header;
|
||||
header += std::string((const char *)&keys.m_spend_public_key, sizeof(crypto::public_key));
|
||||
header += std::string((const char *)&keys.m_view_public_key, sizeof(crypto::public_key));
|
||||
std::string ciphertext = encrypt_with_view_secret_key(header + oss.str());
|
||||
return magic + ciphertext;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_details> &outputs)
|
||||
{
|
||||
m_transfers.clear();
|
||||
@@ -9804,6 +9874,67 @@ size_t wallet2::import_outputs(const std::vector<tools::wallet2::transfer_detail
|
||||
return m_transfers.size();
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
size_t wallet2::import_outputs_from_str(const std::string &outputs_st)
|
||||
{
|
||||
std::string data = outputs_st;
|
||||
const size_t magiclen = strlen(OUTPUT_EXPORT_FILE_MAGIC);
|
||||
if (data.size() < magiclen || memcmp(data.data(), OUTPUT_EXPORT_FILE_MAGIC, magiclen))
|
||||
{
|
||||
THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad magic from outputs"));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
data = decrypt_with_view_secret_key(std::string(data, magiclen));
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt outputs: ") + e.what());
|
||||
}
|
||||
|
||||
const size_t headerlen = 2 * sizeof(crypto::public_key);
|
||||
if (data.size() < headerlen)
|
||||
{
|
||||
THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad data size for outputs"));
|
||||
}
|
||||
const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0];
|
||||
const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)];
|
||||
const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
|
||||
if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key)
|
||||
{
|
||||
THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Outputs from are for a different account"));
|
||||
}
|
||||
|
||||
size_t imported_outputs = 0;
|
||||
try
|
||||
{
|
||||
std::string body(data, headerlen);
|
||||
std::stringstream iss;
|
||||
iss << body;
|
||||
std::vector<tools::wallet2::transfer_details> outputs;
|
||||
try
|
||||
{
|
||||
boost::archive::portable_binary_iarchive ar(iss);
|
||||
ar >> outputs;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
iss.str("");
|
||||
iss << body;
|
||||
boost::archive::binary_iarchive ar(iss);
|
||||
ar >> outputs;
|
||||
}
|
||||
|
||||
imported_outputs = import_outputs(outputs);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to import outputs") + e.what());
|
||||
}
|
||||
|
||||
return imported_outputs;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
crypto::public_key wallet2::get_multisig_signer_public_key(const crypto::secret_key &spend_skey) const
|
||||
{
|
||||
crypto::public_key pkey;
|
||||
|
||||
Reference in New Issue
Block a user