device/trezor: HF10 support added, wallet::API
- import only key images generated by cold signing process - wallet_api: trezor methods added - wallet: button request code added - const added to methods - wallet2::get_tx_key_device() tries to decrypt stored tx private keys using the device. - simplewallet supports get_tx_key and get_tx_proof on hw device using the get_tx_key feature - live refresh enables refresh with trezor i.e. computing key images on the fly. More convenient and efficient for users. - device: has_ki_live_refresh added - a thread is watching whether live refresh is being computed, if not for 30 seconds, it terminates the live refresh process - switches Trezor state
This commit is contained in:
parent
d74d26f2c9
commit
a1fd1d499c
|
@ -1 +1 @@
|
||||||
Subproject commit 588f8e03f5ac111adf719f0a437de67481a26aed
|
Subproject commit cb238cb1f134accc4200217d9511115a8f61c6cb
|
|
@ -282,6 +282,11 @@ namespace cryptonote
|
||||||
//---------------------------------------------------------------
|
//---------------------------------------------------------------
|
||||||
bool generate_key_image_helper_precomp(const account_keys& ack, const crypto::public_key& out_key, const crypto::key_derivation& recv_derivation, size_t real_output_index, const subaddress_index& received_index, keypair& in_ephemeral, crypto::key_image& ki, hw::device &hwdev)
|
bool generate_key_image_helper_precomp(const account_keys& ack, const crypto::public_key& out_key, const crypto::key_derivation& recv_derivation, size_t real_output_index, const subaddress_index& received_index, keypair& in_ephemeral, crypto::key_image& ki, hw::device &hwdev)
|
||||||
{
|
{
|
||||||
|
if (hwdev.compute_key_image(ack, out_key, recv_derivation, real_output_index, received_index, in_ephemeral, ki))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (ack.m_spend_secret_key == crypto::null_skey)
|
if (ack.m_spend_secret_key == crypto::null_skey)
|
||||||
{
|
{
|
||||||
// for watch-only wallet, simply copy the known output pubkey
|
// for watch-only wallet, simply copy the known output pubkey
|
||||||
|
|
|
@ -70,6 +70,7 @@ namespace cryptonote
|
||||||
struct account_keys;
|
struct account_keys;
|
||||||
struct subaddress_index;
|
struct subaddress_index;
|
||||||
struct tx_destination_entry;
|
struct tx_destination_entry;
|
||||||
|
struct keypair;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace hw {
|
namespace hw {
|
||||||
|
@ -81,11 +82,18 @@ namespace hw {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class device_progress {
|
||||||
|
public:
|
||||||
|
virtual double progress() const { return 0; }
|
||||||
|
virtual bool indeterminate() const { return false; }
|
||||||
|
};
|
||||||
|
|
||||||
class i_device_callback {
|
class i_device_callback {
|
||||||
public:
|
public:
|
||||||
virtual void on_button_request() {}
|
virtual void on_button_request(uint64_t code=0) {}
|
||||||
virtual void on_pin_request(epee::wipeable_string & pin) {}
|
virtual boost::optional<epee::wipeable_string> on_pin_request() { return boost::none; }
|
||||||
virtual void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) {}
|
virtual boost::optional<epee::wipeable_string> on_passphrase_request(bool on_device) { return boost::none; }
|
||||||
|
virtual void on_progress(const device_progress& event) {}
|
||||||
virtual ~i_device_callback() = default;
|
virtual ~i_device_callback() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -141,6 +149,9 @@ namespace hw {
|
||||||
virtual void set_callback(i_device_callback * callback) {};
|
virtual void set_callback(i_device_callback * callback) {};
|
||||||
virtual void set_derivation_path(const std::string &derivation_path) {};
|
virtual void set_derivation_path(const std::string &derivation_path) {};
|
||||||
|
|
||||||
|
virtual void set_pin(const epee::wipeable_string & pin) {}
|
||||||
|
virtual void set_passphrase(const epee::wipeable_string & passphrase) {}
|
||||||
|
|
||||||
/* ======================================================================= */
|
/* ======================================================================= */
|
||||||
/* LOCKER */
|
/* LOCKER */
|
||||||
/* ======================================================================= */
|
/* ======================================================================= */
|
||||||
|
@ -229,7 +240,9 @@ namespace hw {
|
||||||
|
|
||||||
virtual bool has_ki_cold_sync(void) const { return false; }
|
virtual bool has_ki_cold_sync(void) const { return false; }
|
||||||
virtual bool has_tx_cold_sign(void) const { return false; }
|
virtual bool has_tx_cold_sign(void) const { return false; }
|
||||||
|
virtual bool has_ki_live_refresh(void) const { return true; }
|
||||||
|
virtual bool compute_key_image(const cryptonote::account_keys& ack, const crypto::public_key& out_key, const crypto::key_derivation& recv_derivation, size_t real_output_index, const cryptonote::subaddress_index& received_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki) { return false; }
|
||||||
|
virtual void computing_key_images(bool started) {};
|
||||||
virtual void set_network_type(cryptonote::network_type network_type) { }
|
virtual void set_network_type(cryptonote::network_type network_type) { }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#define MONERO_DEVICE_COLD_H
|
#define MONERO_DEVICE_COLD_H
|
||||||
|
|
||||||
#include "wallet/wallet2.h"
|
#include "wallet/wallet2.h"
|
||||||
|
#include <boost/optional/optional.hpp>
|
||||||
#include <boost/function.hpp>
|
#include <boost/function.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,6 +45,8 @@ namespace hw {
|
||||||
public:
|
public:
|
||||||
std::vector<std::string> tx_device_aux; // device generated aux data
|
std::vector<std::string> tx_device_aux; // device generated aux data
|
||||||
std::vector<cryptonote::address_parse_info> tx_recipients; // as entered by user
|
std::vector<cryptonote::address_parse_info> tx_recipients; // as entered by user
|
||||||
|
boost::optional<int> bp_version; // BP version to use
|
||||||
|
boost::optional<unsigned> client_version; // Signing client version to use (testing)
|
||||||
};
|
};
|
||||||
|
|
||||||
class device_cold {
|
class device_cold {
|
||||||
|
@ -51,6 +54,53 @@ namespace hw {
|
||||||
|
|
||||||
using exported_key_image = std::vector<std::pair<crypto::key_image, crypto::signature>>;
|
using exported_key_image = std::vector<std::pair<crypto::key_image, crypto::signature>>;
|
||||||
|
|
||||||
|
class op_progress : public hw::device_progress {
|
||||||
|
public:
|
||||||
|
op_progress():m_progress(0), m_indeterminate(false) {};
|
||||||
|
explicit op_progress(double progress, bool indeterminate=false): m_progress(progress), m_indeterminate(indeterminate){}
|
||||||
|
|
||||||
|
double progress() const override { return m_progress; }
|
||||||
|
bool indeterminate() const override { return m_indeterminate; }
|
||||||
|
protected:
|
||||||
|
double m_progress;
|
||||||
|
bool m_indeterminate;
|
||||||
|
};
|
||||||
|
|
||||||
|
class tx_progress : public op_progress {
|
||||||
|
public:
|
||||||
|
tx_progress():
|
||||||
|
m_cur_tx(0), m_max_tx(1),
|
||||||
|
m_cur_step(0), m_max_step(1),
|
||||||
|
m_cur_substep(0), m_max_substep(1){};
|
||||||
|
|
||||||
|
tx_progress(size_t cur_tx, size_t max_tx, size_t cur_step, size_t max_step, size_t cur_substep, size_t max_substep):
|
||||||
|
m_cur_tx(cur_tx), m_max_tx(max_tx),
|
||||||
|
m_cur_step(cur_tx), m_max_step(max_tx),
|
||||||
|
m_cur_substep(cur_tx), m_max_substep(max_tx){}
|
||||||
|
|
||||||
|
double progress() const override {
|
||||||
|
return std::max(1.0, (double)m_cur_tx / m_max_tx
|
||||||
|
+ (double)m_cur_step / (m_max_tx * m_max_step)
|
||||||
|
+ (double)m_cur_substep / (m_max_tx * m_max_step * m_max_substep));
|
||||||
|
}
|
||||||
|
bool indeterminate() const override { return false; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
size_t m_cur_tx;
|
||||||
|
size_t m_max_tx;
|
||||||
|
size_t m_cur_step;
|
||||||
|
size_t m_max_step;
|
||||||
|
size_t m_cur_substep;
|
||||||
|
size_t m_max_substep;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
std::string salt1;
|
||||||
|
std::string salt2;
|
||||||
|
std::string tx_enc_keys;
|
||||||
|
std::string tx_prefix_hash;
|
||||||
|
} tx_key_data_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key image sync with the cold protocol.
|
* Key image sync with the cold protocol.
|
||||||
*/
|
*/
|
||||||
|
@ -65,6 +115,52 @@ namespace hw {
|
||||||
const ::tools::wallet2::unsigned_tx_set & unsigned_tx,
|
const ::tools::wallet2::unsigned_tx_set & unsigned_tx,
|
||||||
::tools::wallet2::signed_tx_set & signed_tx,
|
::tools::wallet2::signed_tx_set & signed_tx,
|
||||||
tx_aux_data & aux_data) =0;
|
tx_aux_data & aux_data) =0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get tx key support check.
|
||||||
|
*/
|
||||||
|
virtual bool is_get_tx_key_supported() const { return false; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads TX aux data required for tx key.
|
||||||
|
*/
|
||||||
|
virtual void load_tx_key_data(tx_key_data_t & res, const std::string & tx_aux_data) =0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts TX keys.
|
||||||
|
*/
|
||||||
|
virtual void get_tx_key(
|
||||||
|
std::vector<::crypto::secret_key> & tx_keys,
|
||||||
|
const tx_key_data_t & tx_aux_data,
|
||||||
|
const ::crypto::secret_key & view_key_priv) =0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Live refresh support check
|
||||||
|
*/
|
||||||
|
virtual bool is_live_refresh_supported() const { return false; };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts live refresh process with the device
|
||||||
|
*/
|
||||||
|
virtual void live_refresh_start() =0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One live refresh step
|
||||||
|
*/
|
||||||
|
virtual void live_refresh(
|
||||||
|
const ::crypto::secret_key & view_key_priv,
|
||||||
|
const crypto::public_key& out_key,
|
||||||
|
const crypto::key_derivation& recv_derivation,
|
||||||
|
size_t real_output_index,
|
||||||
|
const cryptonote::subaddress_index& received_index,
|
||||||
|
cryptonote::keypair& in_ephemeral,
|
||||||
|
crypto::key_image& ki
|
||||||
|
) =0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Live refresh process termination
|
||||||
|
*/
|
||||||
|
virtual void live_refresh_finish() =0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,9 @@ namespace trezor {
|
||||||
}
|
}
|
||||||
|
|
||||||
device_trezor::device_trezor() {
|
device_trezor::device_trezor() {
|
||||||
|
m_live_refresh_in_progress = false;
|
||||||
|
m_live_refresh_enabled = true;
|
||||||
|
m_live_refresh_thread_running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
device_trezor::~device_trezor() {
|
device_trezor::~device_trezor() {
|
||||||
|
@ -69,6 +71,89 @@ namespace trezor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool device_trezor::init()
|
||||||
|
{
|
||||||
|
m_live_refresh_in_progress = false;
|
||||||
|
bool r = device_trezor_base::init();
|
||||||
|
if (r && !m_live_refresh_thread)
|
||||||
|
{
|
||||||
|
m_live_refresh_thread_running = true;
|
||||||
|
m_live_refresh_thread.reset(new boost::thread(boost::bind(&device_trezor::live_refresh_thread_main, this)));
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool device_trezor::release()
|
||||||
|
{
|
||||||
|
m_live_refresh_in_progress = false;
|
||||||
|
m_live_refresh_thread_running = false;
|
||||||
|
if (m_live_refresh_thread)
|
||||||
|
{
|
||||||
|
m_live_refresh_thread->join();
|
||||||
|
m_live_refresh_thread = nullptr;
|
||||||
|
}
|
||||||
|
return device_trezor_base::release();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool device_trezor::disconnect()
|
||||||
|
{
|
||||||
|
m_live_refresh_in_progress = false;
|
||||||
|
return device_trezor_base::disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
void device_trezor::device_state_reset_unsafe()
|
||||||
|
{
|
||||||
|
require_connected();
|
||||||
|
if (m_live_refresh_in_progress)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
live_refresh_finish_unsafe();
|
||||||
|
}
|
||||||
|
catch(const std::exception & e)
|
||||||
|
{
|
||||||
|
MERROR("Live refresh could not be terminated: " << e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_live_refresh_in_progress = false;
|
||||||
|
device_trezor_base::device_state_reset_unsafe();
|
||||||
|
}
|
||||||
|
|
||||||
|
void device_trezor::live_refresh_thread_main()
|
||||||
|
{
|
||||||
|
while(m_live_refresh_thread_running)
|
||||||
|
{
|
||||||
|
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
|
||||||
|
if (!m_live_refresh_in_progress)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TREZOR_AUTO_LOCK_DEVICE();
|
||||||
|
if (!m_transport || !m_live_refresh_in_progress)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto current_time = std::chrono::steady_clock::now();
|
||||||
|
if (current_time - m_last_live_refresh_time <= std::chrono::seconds(20))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MTRACE("Closing live refresh process due to inactivity");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
live_refresh_finish();
|
||||||
|
}
|
||||||
|
catch(const std::exception &e)
|
||||||
|
{
|
||||||
|
MWARNING("Live refresh auto-finish failed: " << e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ======================================================================= */
|
/* ======================================================================= */
|
||||||
/* WALLET & ADDRESS */
|
/* WALLET & ADDRESS */
|
||||||
/* ======================================================================= */
|
/* ======================================================================= */
|
||||||
|
@ -126,7 +211,7 @@ namespace trezor {
|
||||||
std::shared_ptr<messages::monero::MoneroAddress> device_trezor::get_address(
|
std::shared_ptr<messages::monero::MoneroAddress> device_trezor::get_address(
|
||||||
const boost::optional<std::vector<uint32_t>> & path,
|
const boost::optional<std::vector<uint32_t>> & path,
|
||||||
const boost::optional<cryptonote::network_type> & network_type){
|
const boost::optional<cryptonote::network_type> & network_type){
|
||||||
AUTO_LOCK_CMD();
|
TREZOR_AUTO_LOCK_CMD();
|
||||||
require_connected();
|
require_connected();
|
||||||
device_state_reset_unsafe();
|
device_state_reset_unsafe();
|
||||||
require_initialized();
|
require_initialized();
|
||||||
|
@ -142,7 +227,7 @@ namespace trezor {
|
||||||
std::shared_ptr<messages::monero::MoneroWatchKey> device_trezor::get_view_key(
|
std::shared_ptr<messages::monero::MoneroWatchKey> device_trezor::get_view_key(
|
||||||
const boost::optional<std::vector<uint32_t>> & path,
|
const boost::optional<std::vector<uint32_t>> & path,
|
||||||
const boost::optional<cryptonote::network_type> & network_type){
|
const boost::optional<cryptonote::network_type> & network_type){
|
||||||
AUTO_LOCK_CMD();
|
TREZOR_AUTO_LOCK_CMD();
|
||||||
require_connected();
|
require_connected();
|
||||||
device_state_reset_unsafe();
|
device_state_reset_unsafe();
|
||||||
require_initialized();
|
require_initialized();
|
||||||
|
@ -155,11 +240,43 @@ namespace trezor {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool device_trezor::is_get_tx_key_supported() const
|
||||||
|
{
|
||||||
|
require_initialized();
|
||||||
|
return get_version() > pack_version(2, 0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
void device_trezor::load_tx_key_data(::hw::device_cold::tx_key_data_t & res, const std::string & tx_aux_data)
|
||||||
|
{
|
||||||
|
protocol::tx::load_tx_key_data(res, tx_aux_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void device_trezor::get_tx_key(
|
||||||
|
std::vector<::crypto::secret_key> & tx_keys,
|
||||||
|
const ::hw::device_cold::tx_key_data_t & tx_aux_data,
|
||||||
|
const ::crypto::secret_key & view_key_priv)
|
||||||
|
{
|
||||||
|
TREZOR_AUTO_LOCK_CMD();
|
||||||
|
require_connected();
|
||||||
|
device_state_reset_unsafe();
|
||||||
|
require_initialized();
|
||||||
|
|
||||||
|
auto req = protocol::tx::get_tx_key(tx_aux_data);
|
||||||
|
this->set_msg_addr<messages::monero::MoneroGetTxKeyRequest>(req.get());
|
||||||
|
|
||||||
|
auto response = this->client_exchange<messages::monero::MoneroGetTxKeyAck>(req);
|
||||||
|
MTRACE("Get TX key response received");
|
||||||
|
|
||||||
|
protocol::tx::get_tx_key_ack(tx_keys, tx_aux_data.tx_prefix_hash, view_key_priv, response);
|
||||||
|
}
|
||||||
|
|
||||||
void device_trezor::ki_sync(wallet_shim * wallet,
|
void device_trezor::ki_sync(wallet_shim * wallet,
|
||||||
const std::vector<tools::wallet2::transfer_details> & transfers,
|
const std::vector<tools::wallet2::transfer_details> & transfers,
|
||||||
hw::device_cold::exported_key_image & ski)
|
hw::device_cold::exported_key_image & ski)
|
||||||
{
|
{
|
||||||
AUTO_LOCK_CMD();
|
#define EVENT_PROGRESS(P) do { if (m_callback) {(m_callback)->on_progress(device_cold::op_progress(P)); } }while(0)
|
||||||
|
|
||||||
|
TREZOR_AUTO_LOCK_CMD();
|
||||||
require_connected();
|
require_connected();
|
||||||
device_state_reset_unsafe();
|
device_state_reset_unsafe();
|
||||||
require_initialized();
|
require_initialized();
|
||||||
|
@ -171,6 +288,7 @@ namespace trezor {
|
||||||
protocol::ki::key_image_data(wallet, transfers, mtds);
|
protocol::ki::key_image_data(wallet, transfers, mtds);
|
||||||
protocol::ki::generate_commitment(mtds, transfers, req);
|
protocol::ki::generate_commitment(mtds, transfers, req);
|
||||||
|
|
||||||
|
EVENT_PROGRESS(0.);
|
||||||
this->set_msg_addr<messages::monero::MoneroKeyImageExportInitRequest>(req.get());
|
this->set_msg_addr<messages::monero::MoneroKeyImageExportInitRequest>(req.get());
|
||||||
auto ack1 = this->client_exchange<messages::monero::MoneroKeyImageExportInitAck>(req);
|
auto ack1 = this->client_exchange<messages::monero::MoneroKeyImageExportInitAck>(req);
|
||||||
|
|
||||||
|
@ -194,27 +312,160 @@ namespace trezor {
|
||||||
}
|
}
|
||||||
|
|
||||||
MTRACE("Batch " << cur << " / " << num_batches << " batches processed");
|
MTRACE("Batch " << cur << " / " << num_batches << " batches processed");
|
||||||
|
EVENT_PROGRESS((double)cur * batch_size / mtds.size());
|
||||||
}
|
}
|
||||||
|
EVENT_PROGRESS(1.);
|
||||||
|
|
||||||
auto final_req = std::make_shared<messages::monero::MoneroKeyImageSyncFinalRequest>();
|
auto final_req = std::make_shared<messages::monero::MoneroKeyImageSyncFinalRequest>();
|
||||||
auto final_ack = this->client_exchange<messages::monero::MoneroKeyImageSyncFinalAck>(final_req);
|
auto final_ack = this->client_exchange<messages::monero::MoneroKeyImageSyncFinalAck>(final_req);
|
||||||
ski.reserve(kis.size());
|
ski.reserve(kis.size());
|
||||||
|
|
||||||
for(auto & sub : kis){
|
for(auto & sub : kis){
|
||||||
char buff[32*3];
|
|
||||||
protocol::crypto::chacha::decrypt(sub.blob().data(), sub.blob().size(),
|
|
||||||
reinterpret_cast<const uint8_t *>(final_ack->enc_key().data()),
|
|
||||||
reinterpret_cast<const uint8_t *>(sub.iv().data()), buff);
|
|
||||||
|
|
||||||
::crypto::signature sig{};
|
::crypto::signature sig{};
|
||||||
::crypto::key_image ki;
|
::crypto::key_image ki;
|
||||||
memcpy(ki.data, buff, 32);
|
char buff[sizeof(ki.data)*3];
|
||||||
memcpy(sig.c.data, buff + 32, 32);
|
|
||||||
memcpy(sig.r.data, buff + 64, 32);
|
size_t buff_len = sizeof(buff);
|
||||||
|
|
||||||
|
protocol::crypto::chacha::decrypt(sub.blob().data(), sub.blob().size(),
|
||||||
|
reinterpret_cast<const uint8_t *>(final_ack->enc_key().data()),
|
||||||
|
reinterpret_cast<const uint8_t *>(sub.iv().data()), buff, &buff_len);
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(buff_len == sizeof(buff), "Plaintext size invalid");
|
||||||
|
|
||||||
|
memcpy(ki.data, buff, sizeof(ki.data));
|
||||||
|
memcpy(sig.c.data, buff + sizeof(ki.data), sizeof(ki.data));
|
||||||
|
memcpy(sig.r.data, buff + 2*sizeof(ki.data), sizeof(ki.data));
|
||||||
ski.push_back(std::make_pair(ki, sig));
|
ski.push_back(std::make_pair(ki, sig));
|
||||||
}
|
}
|
||||||
|
#undef EVENT_PROGRESS
|
||||||
|
}
|
||||||
|
|
||||||
|
bool device_trezor::is_live_refresh_supported() const
|
||||||
|
{
|
||||||
|
require_initialized();
|
||||||
|
return get_version() > pack_version(2, 0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool device_trezor::is_live_refresh_enabled() const
|
||||||
|
{
|
||||||
|
return is_live_refresh_supported() && (mode == NONE || mode == TRANSACTION_PARSE) && m_live_refresh_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool device_trezor::has_ki_live_refresh() const
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
return is_live_refresh_enabled();
|
||||||
|
} catch(const std::exception & e){
|
||||||
|
MERROR("Could not detect if live refresh is enabled: " << e.what());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void device_trezor::live_refresh_start()
|
||||||
|
{
|
||||||
|
TREZOR_AUTO_LOCK_CMD();
|
||||||
|
require_connected();
|
||||||
|
live_refresh_start_unsafe();
|
||||||
|
}
|
||||||
|
|
||||||
|
void device_trezor::live_refresh_start_unsafe()
|
||||||
|
{
|
||||||
|
device_state_reset_unsafe();
|
||||||
|
require_initialized();
|
||||||
|
|
||||||
|
auto req = std::make_shared<messages::monero::MoneroLiveRefreshStartRequest>();
|
||||||
|
this->set_msg_addr<messages::monero::MoneroLiveRefreshStartRequest>(req.get());
|
||||||
|
this->client_exchange<messages::monero::MoneroLiveRefreshStartAck>(req);
|
||||||
|
m_live_refresh_in_progress = true;
|
||||||
|
m_last_live_refresh_time = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
void device_trezor::live_refresh(
|
||||||
|
const ::crypto::secret_key & view_key_priv,
|
||||||
|
const crypto::public_key& out_key,
|
||||||
|
const crypto::key_derivation& recv_derivation,
|
||||||
|
size_t real_output_index,
|
||||||
|
const cryptonote::subaddress_index& received_index,
|
||||||
|
cryptonote::keypair& in_ephemeral,
|
||||||
|
crypto::key_image& ki
|
||||||
|
)
|
||||||
|
{
|
||||||
|
TREZOR_AUTO_LOCK_CMD();
|
||||||
|
require_connected();
|
||||||
|
|
||||||
|
if (!m_live_refresh_in_progress)
|
||||||
|
{
|
||||||
|
live_refresh_start_unsafe();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_last_live_refresh_time = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
auto req = std::make_shared<messages::monero::MoneroLiveRefreshStepRequest>();
|
||||||
|
req->set_out_key(out_key.data, 32);
|
||||||
|
req->set_recv_deriv(recv_derivation.data, 32);
|
||||||
|
req->set_real_out_idx(real_output_index);
|
||||||
|
req->set_sub_addr_major(received_index.major);
|
||||||
|
req->set_sub_addr_minor(received_index.minor);
|
||||||
|
|
||||||
|
auto ack = this->client_exchange<messages::monero::MoneroLiveRefreshStepAck>(req);
|
||||||
|
protocol::ki::live_refresh_ack(view_key_priv, out_key, ack, in_ephemeral, ki);
|
||||||
|
}
|
||||||
|
|
||||||
|
void device_trezor::live_refresh_finish_unsafe()
|
||||||
|
{
|
||||||
|
auto req = std::make_shared<messages::monero::MoneroLiveRefreshFinalRequest>();
|
||||||
|
this->client_exchange<messages::monero::MoneroLiveRefreshFinalAck>(req);
|
||||||
|
m_live_refresh_in_progress = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void device_trezor::live_refresh_finish()
|
||||||
|
{
|
||||||
|
TREZOR_AUTO_LOCK_CMD();
|
||||||
|
require_connected();
|
||||||
|
if (m_live_refresh_in_progress)
|
||||||
|
{
|
||||||
|
live_refresh_finish_unsafe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void device_trezor::computing_key_images(bool started)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!is_live_refresh_enabled())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// React only on termination as the process can auto-start itself.
|
||||||
|
if (!started && m_live_refresh_in_progress)
|
||||||
|
{
|
||||||
|
live_refresh_finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(const std::exception & e)
|
||||||
|
{
|
||||||
|
MWARNING("KI computation state change failed, started: " << started << ", e: " << e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool device_trezor::compute_key_image(
|
||||||
|
const ::cryptonote::account_keys& ack,
|
||||||
|
const ::crypto::public_key& out_key,
|
||||||
|
const ::crypto::key_derivation& recv_derivation,
|
||||||
|
size_t real_output_index,
|
||||||
|
const ::cryptonote::subaddress_index& received_index,
|
||||||
|
::cryptonote::keypair& in_ephemeral,
|
||||||
|
::crypto::key_image& ki)
|
||||||
|
{
|
||||||
|
if (!is_live_refresh_enabled())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
live_refresh(ack.m_view_secret_key, out_key, recv_derivation, real_output_index, received_index, in_ephemeral, ki);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void device_trezor::tx_sign(wallet_shim * wallet,
|
void device_trezor::tx_sign(wallet_shim * wallet,
|
||||||
const tools::wallet2::unsigned_tx_set & unsigned_tx,
|
const tools::wallet2::unsigned_tx_set & unsigned_tx,
|
||||||
|
@ -222,7 +473,15 @@ namespace trezor {
|
||||||
hw::tx_aux_data & aux_data)
|
hw::tx_aux_data & aux_data)
|
||||||
{
|
{
|
||||||
CHECK_AND_ASSERT_THROW_MES(unsigned_tx.transfers.first == 0, "Unsuported non zero offset");
|
CHECK_AND_ASSERT_THROW_MES(unsigned_tx.transfers.first == 0, "Unsuported non zero offset");
|
||||||
size_t num_tx = unsigned_tx.txes.size();
|
|
||||||
|
TREZOR_AUTO_LOCK_CMD();
|
||||||
|
require_connected();
|
||||||
|
device_state_reset_unsafe();
|
||||||
|
require_initialized();
|
||||||
|
transaction_versions_check(unsigned_tx, aux_data);
|
||||||
|
|
||||||
|
const size_t num_tx = unsigned_tx.txes.size();
|
||||||
|
m_num_transations_to_sign = num_tx;
|
||||||
signed_tx.key_images.clear();
|
signed_tx.key_images.clear();
|
||||||
signed_tx.key_images.resize(unsigned_tx.transfers.second.size());
|
signed_tx.key_images.resize(unsigned_tx.transfers.second.size());
|
||||||
|
|
||||||
|
@ -267,6 +526,10 @@ namespace trezor {
|
||||||
cpend.key_images = key_images;
|
cpend.key_images = key_images;
|
||||||
|
|
||||||
// KI sync
|
// KI sync
|
||||||
|
for(size_t cidx=0, trans_max=unsigned_tx.transfers.second.size(); cidx < trans_max; ++cidx){
|
||||||
|
signed_tx.key_images[cidx] = unsigned_tx.transfers.second[cidx].m_key_image;
|
||||||
|
}
|
||||||
|
|
||||||
size_t num_sources = cdata.tx_data.sources.size();
|
size_t num_sources = cdata.tx_data.sources.size();
|
||||||
CHECK_AND_ASSERT_THROW_MES(num_sources == cdata.source_permutation.size(), "Invalid permutation size");
|
CHECK_AND_ASSERT_THROW_MES(num_sources == cdata.source_permutation.size(), "Invalid permutation size");
|
||||||
CHECK_AND_ASSERT_THROW_MES(num_sources == cdata.tx.vin.size(), "Invalid tx.vin size");
|
CHECK_AND_ASSERT_THROW_MES(num_sources == cdata.tx.vin.size(), "Invalid tx.vin size");
|
||||||
|
@ -276,12 +539,19 @@ namespace trezor {
|
||||||
CHECK_AND_ASSERT_THROW_MES(src_idx < cdata.tx.vin.size(), "Invalid idx_mapped");
|
CHECK_AND_ASSERT_THROW_MES(src_idx < cdata.tx.vin.size(), "Invalid idx_mapped");
|
||||||
|
|
||||||
size_t idx_map_src = cdata.tx_data.selected_transfers[idx_mapped];
|
size_t idx_map_src = cdata.tx_data.selected_transfers[idx_mapped];
|
||||||
auto vini = boost::get<cryptonote::txin_to_key>(cdata.tx.vin[src_idx]);
|
CHECK_AND_ASSERT_THROW_MES(idx_map_src >= unsigned_tx.transfers.first, "Invalid offset");
|
||||||
|
|
||||||
|
idx_map_src -= unsigned_tx.transfers.first;
|
||||||
CHECK_AND_ASSERT_THROW_MES(idx_map_src < signed_tx.key_images.size(), "Invalid key image index");
|
CHECK_AND_ASSERT_THROW_MES(idx_map_src < signed_tx.key_images.size(), "Invalid key image index");
|
||||||
|
|
||||||
|
const auto vini = boost::get<cryptonote::txin_to_key>(cdata.tx.vin[src_idx]);
|
||||||
signed_tx.key_images[idx_map_src] = vini.k_image;
|
signed_tx.key_images[idx_map_src] = vini.k_image;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_callback){
|
||||||
|
m_callback->on_progress(device_cold::tx_progress(m_num_transations_to_sign, m_num_transations_to_sign, 1, 1, 1, 1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void device_trezor::tx_sign(wallet_shim * wallet,
|
void device_trezor::tx_sign(wallet_shim * wallet,
|
||||||
|
@ -290,10 +560,16 @@ namespace trezor {
|
||||||
hw::tx_aux_data & aux_data,
|
hw::tx_aux_data & aux_data,
|
||||||
std::shared_ptr<protocol::tx::Signer> & signer)
|
std::shared_ptr<protocol::tx::Signer> & signer)
|
||||||
{
|
{
|
||||||
AUTO_LOCK_CMD();
|
#define EVENT_PROGRESS(S, SUB, SUBMAX) do { if (m_callback) { \
|
||||||
|
(m_callback)->on_progress(device_cold::tx_progress(idx, m_num_transations_to_sign, S, 10, SUB, SUBMAX)); \
|
||||||
|
} }while(0)
|
||||||
|
|
||||||
require_connected();
|
require_connected();
|
||||||
device_state_reset_unsafe();
|
if (idx > 0)
|
||||||
|
device_state_reset_unsafe();
|
||||||
|
|
||||||
require_initialized();
|
require_initialized();
|
||||||
|
EVENT_PROGRESS(0, 1, 1);
|
||||||
|
|
||||||
CHECK_AND_ASSERT_THROW_MES(idx < unsigned_tx.txes.size(), "Invalid transaction index");
|
CHECK_AND_ASSERT_THROW_MES(idx < unsigned_tx.txes.size(), "Invalid transaction index");
|
||||||
signer = std::make_shared<protocol::tx::Signer>(wallet, &unsigned_tx, idx, &aux_data);
|
signer = std::make_shared<protocol::tx::Signer>(wallet, &unsigned_tx, idx, &aux_data);
|
||||||
|
@ -305,6 +581,7 @@ namespace trezor {
|
||||||
auto init_msg = signer->step_init();
|
auto init_msg = signer->step_init();
|
||||||
this->set_msg_addr(init_msg.get());
|
this->set_msg_addr(init_msg.get());
|
||||||
transaction_pre_check(init_msg);
|
transaction_pre_check(init_msg);
|
||||||
|
EVENT_PROGRESS(1, 1, 1);
|
||||||
|
|
||||||
auto response = this->client_exchange<messages::monero::MoneroTransactionInitAck>(init_msg);
|
auto response = this->client_exchange<messages::monero::MoneroTransactionInitAck>(init_msg);
|
||||||
signer->step_init_ack(response);
|
signer->step_init_ack(response);
|
||||||
|
@ -314,6 +591,7 @@ namespace trezor {
|
||||||
auto src = signer->step_set_input(cur_src);
|
auto src = signer->step_set_input(cur_src);
|
||||||
auto ack = this->client_exchange<messages::monero::MoneroTransactionSetInputAck>(src);
|
auto ack = this->client_exchange<messages::monero::MoneroTransactionSetInputAck>(src);
|
||||||
signer->step_set_input_ack(ack);
|
signer->step_set_input_ack(ack);
|
||||||
|
EVENT_PROGRESS(2, cur_src, num_sources);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step: sort
|
// Step: sort
|
||||||
|
@ -322,44 +600,82 @@ namespace trezor {
|
||||||
auto perm_ack = this->client_exchange<messages::monero::MoneroTransactionInputsPermutationAck>(perm_req);
|
auto perm_ack = this->client_exchange<messages::monero::MoneroTransactionInputsPermutationAck>(perm_req);
|
||||||
signer->step_permutation_ack(perm_ack);
|
signer->step_permutation_ack(perm_ack);
|
||||||
}
|
}
|
||||||
|
EVENT_PROGRESS(3, 1, 1);
|
||||||
|
|
||||||
// Step: input_vini
|
// Step: input_vini
|
||||||
if (!signer->in_memory()){
|
for(size_t cur_src = 0; cur_src < num_sources; ++cur_src){
|
||||||
for(size_t cur_src = 0; cur_src < num_sources; ++cur_src){
|
auto src = signer->step_set_vini_input(cur_src);
|
||||||
auto src = signer->step_set_vini_input(cur_src);
|
auto ack = this->client_exchange<messages::monero::MoneroTransactionInputViniAck>(src);
|
||||||
auto ack = this->client_exchange<messages::monero::MoneroTransactionInputViniAck>(src);
|
signer->step_set_vini_input_ack(ack);
|
||||||
signer->step_set_vini_input_ack(ack);
|
EVENT_PROGRESS(4, cur_src, num_sources);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step: all inputs set
|
// Step: all inputs set
|
||||||
auto all_inputs_set = signer->step_all_inputs_set();
|
auto all_inputs_set = signer->step_all_inputs_set();
|
||||||
auto ack_all_inputs = this->client_exchange<messages::monero::MoneroTransactionAllInputsSetAck>(all_inputs_set);
|
auto ack_all_inputs = this->client_exchange<messages::monero::MoneroTransactionAllInputsSetAck>(all_inputs_set);
|
||||||
signer->step_all_inputs_set_ack(ack_all_inputs);
|
signer->step_all_inputs_set_ack(ack_all_inputs);
|
||||||
|
EVENT_PROGRESS(5, 1, 1);
|
||||||
|
|
||||||
// Step: outputs
|
// Step: outputs
|
||||||
for(size_t cur_dst = 0; cur_dst < num_outputs; ++cur_dst){
|
for(size_t cur_dst = 0; cur_dst < num_outputs; ++cur_dst){
|
||||||
auto src = signer->step_set_output(cur_dst);
|
auto src = signer->step_set_output(cur_dst);
|
||||||
auto ack = this->client_exchange<messages::monero::MoneroTransactionSetOutputAck>(src);
|
auto ack = this->client_exchange<messages::monero::MoneroTransactionSetOutputAck>(src);
|
||||||
signer->step_set_output_ack(ack);
|
signer->step_set_output_ack(ack);
|
||||||
|
|
||||||
|
// If BP is offloaded to host, another step with computed BP may be needed.
|
||||||
|
auto offloaded_bp = signer->step_rsig(cur_dst);
|
||||||
|
if (offloaded_bp){
|
||||||
|
auto bp_ack = this->client_exchange<messages::monero::MoneroTransactionSetOutputAck>(offloaded_bp);
|
||||||
|
signer->step_set_rsig_ack(ack);
|
||||||
|
}
|
||||||
|
|
||||||
|
EVENT_PROGRESS(6, cur_dst, num_outputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step: all outs set
|
// Step: all outs set
|
||||||
auto all_out_set = signer->step_all_outs_set();
|
auto all_out_set = signer->step_all_outs_set();
|
||||||
auto ack_all_out_set = this->client_exchange<messages::monero::MoneroTransactionAllOutSetAck>(all_out_set);
|
auto ack_all_out_set = this->client_exchange<messages::monero::MoneroTransactionAllOutSetAck>(all_out_set);
|
||||||
signer->step_all_outs_set_ack(ack_all_out_set, *this);
|
signer->step_all_outs_set_ack(ack_all_out_set, *this);
|
||||||
|
EVENT_PROGRESS(7, 1, 1);
|
||||||
|
|
||||||
// Step: sign each input
|
// Step: sign each input
|
||||||
for(size_t cur_src = 0; cur_src < num_sources; ++cur_src){
|
for(size_t cur_src = 0; cur_src < num_sources; ++cur_src){
|
||||||
auto src = signer->step_sign_input(cur_src);
|
auto src = signer->step_sign_input(cur_src);
|
||||||
auto ack_sign = this->client_exchange<messages::monero::MoneroTransactionSignInputAck>(src);
|
auto ack_sign = this->client_exchange<messages::monero::MoneroTransactionSignInputAck>(src);
|
||||||
signer->step_sign_input_ack(ack_sign);
|
signer->step_sign_input_ack(ack_sign);
|
||||||
|
EVENT_PROGRESS(8, cur_src, num_sources);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step: final
|
// Step: final
|
||||||
auto final_msg = signer->step_final();
|
auto final_msg = signer->step_final();
|
||||||
auto ack_final = this->client_exchange<messages::monero::MoneroTransactionFinalAck>(final_msg);
|
auto ack_final = this->client_exchange<messages::monero::MoneroTransactionFinalAck>(final_msg);
|
||||||
signer->step_final_ack(ack_final);
|
signer->step_final_ack(ack_final);
|
||||||
|
EVENT_PROGRESS(9, 1, 1);
|
||||||
|
#undef EVENT_PROGRESS
|
||||||
|
}
|
||||||
|
|
||||||
|
void device_trezor::transaction_versions_check(const ::tools::wallet2::unsigned_tx_set & unsigned_tx, hw::tx_aux_data & aux_data)
|
||||||
|
{
|
||||||
|
auto trezor_version = get_version();
|
||||||
|
unsigned client_version = 1; // default client version for tx
|
||||||
|
|
||||||
|
if (trezor_version <= pack_version(2, 0, 10)){
|
||||||
|
client_version = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aux_data.client_version){
|
||||||
|
auto wanted_client_version = aux_data.client_version.get();
|
||||||
|
if (wanted_client_version > client_version){
|
||||||
|
throw exc::TrezorException("Trezor firmware 2.0.10 and lower does not support current transaction sign protocol. Please update.");
|
||||||
|
} else {
|
||||||
|
client_version = wanted_client_version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aux_data.client_version = client_version;
|
||||||
|
|
||||||
|
if (client_version == 0 && aux_data.bp_version && aux_data.bp_version.get() != 1){
|
||||||
|
throw exc::TrezorException("Trezor firmware 2.0.10 and lower does not support current transaction sign protocol (BPv2+). Please update.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void device_trezor::transaction_pre_check(std::shared_ptr<messages::monero::MoneroTransactionInitRequest> init_msg)
|
void device_trezor::transaction_pre_check(std::shared_ptr<messages::monero::MoneroTransactionInitRequest> init_msg)
|
||||||
|
@ -396,7 +712,7 @@ namespace trezor {
|
||||||
|
|
||||||
const bool nonce_required = tdata.tsx_data.has_payment_id() && tdata.tsx_data.payment_id().size() > 0;
|
const bool nonce_required = tdata.tsx_data.has_payment_id() && tdata.tsx_data.payment_id().size() > 0;
|
||||||
const bool has_nonce = cryptonote::find_tx_extra_field_by_type(tx_extra_fields, nonce);
|
const bool has_nonce = cryptonote::find_tx_extra_field_by_type(tx_extra_fields, nonce);
|
||||||
CHECK_AND_ASSERT_THROW_MES(has_nonce == nonce_required, "Transaction nonce presence inconsistent");
|
CHECK_AND_ASSERT_THROW_MES(has_nonce || !nonce_required, "Transaction nonce not present");
|
||||||
|
|
||||||
if (nonce_required){
|
if (nonce_required){
|
||||||
const std::string & payment_id = tdata.tsx_data.payment_id();
|
const std::string & payment_id = tdata.tsx_data.payment_id();
|
||||||
|
|
|
@ -30,18 +30,21 @@
|
||||||
#ifndef MONERO_DEVICE_TREZOR_H
|
#ifndef MONERO_DEVICE_TREZOR_H
|
||||||
#define MONERO_DEVICE_TREZOR_H
|
#define MONERO_DEVICE_TREZOR_H
|
||||||
|
|
||||||
|
#include "trezor.hpp"
|
||||||
|
#include "device/device.hpp"
|
||||||
|
|
||||||
|
#ifdef WITH_DEVICE_TREZOR
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "device/device.hpp"
|
|
||||||
#include "device/device_default.hpp"
|
|
||||||
#include "device/device_cold.hpp"
|
|
||||||
#include <boost/scope_exit.hpp>
|
#include <boost/scope_exit.hpp>
|
||||||
#include <boost/thread/mutex.hpp>
|
#include <boost/thread/mutex.hpp>
|
||||||
#include <boost/thread/recursive_mutex.hpp>
|
#include <boost/thread/recursive_mutex.hpp>
|
||||||
|
|
||||||
|
#include "device/device_default.hpp"
|
||||||
|
#include "device/device_cold.hpp"
|
||||||
#include "cryptonote_config.h"
|
#include "cryptonote_config.h"
|
||||||
#include "trezor.hpp"
|
|
||||||
#include "device_trezor_base.hpp"
|
#include "device_trezor_base.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace hw {
|
namespace hw {
|
||||||
namespace trezor {
|
namespace trezor {
|
||||||
|
@ -57,8 +60,29 @@ namespace trezor {
|
||||||
*/
|
*/
|
||||||
class device_trezor : public hw::trezor::device_trezor_base, public hw::device_cold {
|
class device_trezor : public hw::trezor::device_trezor_base, public hw::device_cold {
|
||||||
protected:
|
protected:
|
||||||
|
std::atomic<bool> m_live_refresh_in_progress;
|
||||||
|
std::chrono::steady_clock::time_point m_last_live_refresh_time;
|
||||||
|
std::unique_ptr<boost::thread> m_live_refresh_thread;
|
||||||
|
std::atomic<bool> m_live_refresh_thread_running;
|
||||||
|
bool m_live_refresh_enabled;
|
||||||
|
size_t m_num_transations_to_sign;
|
||||||
|
|
||||||
|
void transaction_versions_check(const ::tools::wallet2::unsigned_tx_set & unsigned_tx, hw::tx_aux_data & aux_data);
|
||||||
void transaction_pre_check(std::shared_ptr<messages::monero::MoneroTransactionInitRequest> init_msg);
|
void transaction_pre_check(std::shared_ptr<messages::monero::MoneroTransactionInitRequest> init_msg);
|
||||||
void transaction_check(const protocol::tx::TData & tdata, const hw::tx_aux_data & aux_data);
|
void transaction_check(const protocol::tx::TData & tdata, const hw::tx_aux_data & aux_data);
|
||||||
|
void device_state_reset_unsafe() override;
|
||||||
|
void live_refresh_start_unsafe();
|
||||||
|
void live_refresh_finish_unsafe();
|
||||||
|
void live_refresh_thread_main();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signs particular transaction idx in the unsigned set, keeps state in the signer
|
||||||
|
*/
|
||||||
|
virtual void tx_sign(wallet_shim * wallet,
|
||||||
|
const ::tools::wallet2::unsigned_tx_set & unsigned_tx,
|
||||||
|
size_t idx,
|
||||||
|
hw::tx_aux_data & aux_data,
|
||||||
|
std::shared_ptr<protocol::tx::Signer> & signer);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
device_trezor();
|
device_trezor();
|
||||||
|
@ -69,11 +93,17 @@ namespace trezor {
|
||||||
|
|
||||||
explicit operator bool() const override {return true;}
|
explicit operator bool() const override {return true;}
|
||||||
|
|
||||||
|
bool init() override;
|
||||||
|
bool release() override;
|
||||||
|
bool disconnect() override;
|
||||||
|
|
||||||
device_protocol_t device_protocol() const override { return PROTOCOL_COLD; };
|
device_protocol_t device_protocol() const override { return PROTOCOL_COLD; };
|
||||||
|
|
||||||
bool has_ki_cold_sync() const override { return true; }
|
bool has_ki_cold_sync() const override { return true; }
|
||||||
bool has_tx_cold_sign() const override { return true; }
|
bool has_tx_cold_sign() const override { return true; }
|
||||||
void set_network_type(cryptonote::network_type network_type) override { this->network_type = network_type; }
|
void set_network_type(cryptonote::network_type network_type) override { this->network_type = network_type; }
|
||||||
|
void set_live_refresh_enabled(bool enabled) { m_live_refresh_enabled = enabled; }
|
||||||
|
bool live_refresh_enabled() const { return m_live_refresh_enabled; }
|
||||||
|
|
||||||
/* ======================================================================= */
|
/* ======================================================================= */
|
||||||
/* WALLET & ADDRESS */
|
/* WALLET & ADDRESS */
|
||||||
|
@ -99,6 +129,24 @@ namespace trezor {
|
||||||
const boost::optional<std::vector<uint32_t>> & path = boost::none,
|
const boost::optional<std::vector<uint32_t>> & path = boost::none,
|
||||||
const boost::optional<cryptonote::network_type> & network_type = boost::none);
|
const boost::optional<cryptonote::network_type> & network_type = boost::none);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get_tx_key support check
|
||||||
|
*/
|
||||||
|
bool is_get_tx_key_supported() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads tx aux data
|
||||||
|
*/
|
||||||
|
void load_tx_key_data(::hw::device_cold::tx_key_data_t & res, const std::string & tx_aux_data) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TX key load with the Trezor
|
||||||
|
*/
|
||||||
|
void get_tx_key(
|
||||||
|
std::vector<::crypto::secret_key> & tx_keys,
|
||||||
|
const ::hw::device_cold::tx_key_data_t & tx_aux_data,
|
||||||
|
const ::crypto::secret_key & view_key_priv) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key image sync with the Trezor.
|
* Key image sync with the Trezor.
|
||||||
*/
|
*/
|
||||||
|
@ -106,14 +154,44 @@ namespace trezor {
|
||||||
const std::vector<::tools::wallet2::transfer_details> & transfers,
|
const std::vector<::tools::wallet2::transfer_details> & transfers,
|
||||||
hw::device_cold::exported_key_image & ski) override;
|
hw::device_cold::exported_key_image & ski) override;
|
||||||
|
|
||||||
|
bool is_live_refresh_supported() const override;
|
||||||
|
|
||||||
|
bool is_live_refresh_enabled() const;
|
||||||
|
|
||||||
|
bool has_ki_live_refresh() const override;
|
||||||
|
|
||||||
|
void live_refresh_start() override;
|
||||||
|
|
||||||
|
void live_refresh(
|
||||||
|
const ::crypto::secret_key & view_key_priv,
|
||||||
|
const crypto::public_key& out_key,
|
||||||
|
const crypto::key_derivation& recv_derivation,
|
||||||
|
size_t real_output_index,
|
||||||
|
const cryptonote::subaddress_index& received_index,
|
||||||
|
cryptonote::keypair& in_ephemeral,
|
||||||
|
crypto::key_image& ki
|
||||||
|
) override;
|
||||||
|
|
||||||
|
void live_refresh_finish() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signs particular transaction idx in the unsigned set, keeps state in the signer
|
* Letting device know the KI computation started / ended.
|
||||||
|
* During refresh
|
||||||
*/
|
*/
|
||||||
void tx_sign(wallet_shim * wallet,
|
void computing_key_images(bool started) override;
|
||||||
const ::tools::wallet2::unsigned_tx_set & unsigned_tx,
|
|
||||||
size_t idx,
|
/**
|
||||||
hw::tx_aux_data & aux_data,
|
* Implements hw::device interface
|
||||||
std::shared_ptr<protocol::tx::Signer> & signer);
|
* called from generate_key_image_helper_precomp()
|
||||||
|
*/
|
||||||
|
bool compute_key_image(
|
||||||
|
const ::cryptonote::account_keys& ack,
|
||||||
|
const ::crypto::public_key& out_key,
|
||||||
|
const ::crypto::key_derivation& recv_derivation,
|
||||||
|
size_t real_output_index,
|
||||||
|
const ::cryptonote::subaddress_index& received_index,
|
||||||
|
::cryptonote::keypair& in_ephemeral,
|
||||||
|
::crypto::key_image& ki) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signs unsigned transaction with the Trezor.
|
* Signs unsigned transaction with the Trezor.
|
||||||
|
|
|
@ -109,6 +109,7 @@ namespace trezor {
|
||||||
disconnect();
|
disconnect();
|
||||||
|
|
||||||
// Enumerate all available devices
|
// Enumerate all available devices
|
||||||
|
TREZOR_AUTO_LOCK_DEVICE();
|
||||||
try {
|
try {
|
||||||
hw::trezor::t_transport_vect trans;
|
hw::trezor::t_transport_vect trans;
|
||||||
|
|
||||||
|
@ -145,6 +146,7 @@ namespace trezor {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool device_trezor_base::disconnect() {
|
bool device_trezor_base::disconnect() {
|
||||||
|
TREZOR_AUTO_LOCK_DEVICE();
|
||||||
m_device_state.clear();
|
m_device_state.clear();
|
||||||
m_features.reset();
|
m_features.reset();
|
||||||
|
|
||||||
|
@ -203,13 +205,13 @@ namespace trezor {
|
||||||
/* Helpers */
|
/* Helpers */
|
||||||
/* ======================================================================= */
|
/* ======================================================================= */
|
||||||
|
|
||||||
void device_trezor_base::require_connected(){
|
void device_trezor_base::require_connected() const {
|
||||||
if (!m_transport){
|
if (!m_transport){
|
||||||
throw exc::NotConnectedException();
|
throw exc::NotConnectedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void device_trezor_base::require_initialized(){
|
void device_trezor_base::require_initialized() const {
|
||||||
if (!m_features){
|
if (!m_features){
|
||||||
throw exc::TrezorException("Device state not initialized");
|
throw exc::TrezorException("Device state not initialized");
|
||||||
}
|
}
|
||||||
|
@ -330,7 +332,7 @@ namespace trezor {
|
||||||
/* ======================================================================= */
|
/* ======================================================================= */
|
||||||
|
|
||||||
bool device_trezor_base::ping() {
|
bool device_trezor_base::ping() {
|
||||||
AUTO_LOCK_CMD();
|
TREZOR_AUTO_LOCK_CMD();
|
||||||
if (!m_transport){
|
if (!m_transport){
|
||||||
MINFO("Ping failed, device not connected");
|
MINFO("Ping failed, device not connected");
|
||||||
return false;
|
return false;
|
||||||
|
@ -364,7 +366,7 @@ namespace trezor {
|
||||||
|
|
||||||
void device_trezor_base::device_state_reset()
|
void device_trezor_base::device_state_reset()
|
||||||
{
|
{
|
||||||
AUTO_LOCK_CMD();
|
TREZOR_AUTO_LOCK_CMD();
|
||||||
device_state_reset_unsafe();
|
device_state_reset_unsafe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,6 +375,10 @@ namespace trezor {
|
||||||
if (m_debug_callback) m_debug_callback->method(__VA_ARGS__); \
|
if (m_debug_callback) m_debug_callback->method(__VA_ARGS__); \
|
||||||
if (m_callback) m_callback->method(__VA_ARGS__); \
|
if (m_callback) m_callback->method(__VA_ARGS__); \
|
||||||
}while(0)
|
}while(0)
|
||||||
|
#define TREZOR_CALLBACK_GET(VAR, method, ...) do { \
|
||||||
|
if (m_debug_callback) VAR = m_debug_callback->method(__VA_ARGS__); \
|
||||||
|
if (m_callback) VAR = m_callback->method(__VA_ARGS__); \
|
||||||
|
}while(0)
|
||||||
|
|
||||||
void device_trezor_base::setup_debug(){
|
void device_trezor_base::setup_debug(){
|
||||||
if (!m_debug){
|
if (!m_debug){
|
||||||
|
@ -392,6 +398,7 @@ namespace trezor {
|
||||||
|
|
||||||
#else
|
#else
|
||||||
#define TREZOR_CALLBACK(method, ...) do { if (m_callback) m_callback->method(__VA_ARGS__); } while(0)
|
#define TREZOR_CALLBACK(method, ...) do { if (m_callback) m_callback->method(__VA_ARGS__); } while(0)
|
||||||
|
#define TREZOR_CALLBACK_GET(VAR, method, ...) VAR = (m_callback ? m_callback->method(__VA_ARGS__) : boost::none)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void device_trezor_base::on_button_request(GenericMessage & resp, const messages::common::ButtonRequest * msg)
|
void device_trezor_base::on_button_request(GenericMessage & resp, const messages::common::ButtonRequest * msg)
|
||||||
|
@ -402,7 +409,7 @@ namespace trezor {
|
||||||
messages::common::ButtonAck ack;
|
messages::common::ButtonAck ack;
|
||||||
write_raw(&ack);
|
write_raw(&ack);
|
||||||
|
|
||||||
TREZOR_CALLBACK(on_button_request);
|
TREZOR_CALLBACK(on_button_request, msg->code());
|
||||||
resp = read_raw();
|
resp = read_raw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,13 +418,18 @@ namespace trezor {
|
||||||
MDEBUG("on_pin_request");
|
MDEBUG("on_pin_request");
|
||||||
CHECK_AND_ASSERT_THROW_MES(msg, "Empty message");
|
CHECK_AND_ASSERT_THROW_MES(msg, "Empty message");
|
||||||
|
|
||||||
epee::wipeable_string pin;
|
boost::optional<epee::wipeable_string> pin;
|
||||||
|
TREZOR_CALLBACK_GET(pin, on_pin_request);
|
||||||
|
|
||||||
TREZOR_CALLBACK(on_pin_request, pin);
|
if (!pin && m_pin){
|
||||||
|
pin = m_pin;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: remove PIN from memory
|
// TODO: remove PIN from memory
|
||||||
messages::common::PinMatrixAck m;
|
messages::common::PinMatrixAck m;
|
||||||
m.set_pin(pin.data(), pin.size());
|
if (pin) {
|
||||||
|
m.set_pin(pin.get().data(), pin.get().size());
|
||||||
|
}
|
||||||
resp = call_raw(&m);
|
resp = call_raw(&m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,14 +437,19 @@ namespace trezor {
|
||||||
{
|
{
|
||||||
CHECK_AND_ASSERT_THROW_MES(msg, "Empty message");
|
CHECK_AND_ASSERT_THROW_MES(msg, "Empty message");
|
||||||
MDEBUG("on_passhprase_request, on device: " << msg->on_device());
|
MDEBUG("on_passhprase_request, on device: " << msg->on_device());
|
||||||
epee::wipeable_string passphrase;
|
boost::optional<epee::wipeable_string> passphrase;
|
||||||
|
TREZOR_CALLBACK_GET(passphrase, on_passphrase_request, msg->on_device());
|
||||||
|
|
||||||
TREZOR_CALLBACK(on_passphrase_request, msg->on_device(), passphrase);
|
if (!passphrase && m_passphrase){
|
||||||
|
passphrase = m_passphrase;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_passphrase = boost::none;
|
||||||
|
|
||||||
messages::common::PassphraseAck m;
|
messages::common::PassphraseAck m;
|
||||||
if (!msg->on_device()){
|
if (!msg->on_device() && passphrase){
|
||||||
// TODO: remove passphrase from memory
|
// TODO: remove passphrase from memory
|
||||||
m.set_passphrase(passphrase.data(), passphrase.size());
|
m.set_passphrase(passphrase.get().data(), passphrase.get().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_device_state.empty()){
|
if (!m_device_state.empty()){
|
||||||
|
@ -494,16 +511,16 @@ namespace trezor {
|
||||||
m_debug_link->init(debug_transport);
|
m_debug_link->init(debug_transport);
|
||||||
}
|
}
|
||||||
|
|
||||||
void trezor_debug_callback::on_button_request() {
|
void trezor_debug_callback::on_button_request(uint64_t code) {
|
||||||
if (m_debug_link) m_debug_link->press_yes();
|
if (m_debug_link) m_debug_link->press_yes();
|
||||||
}
|
}
|
||||||
|
|
||||||
void trezor_debug_callback::on_pin_request(epee::wipeable_string &pin) {
|
boost::optional<epee::wipeable_string> trezor_debug_callback::on_pin_request() {
|
||||||
|
return boost::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
void trezor_debug_callback::on_passphrase_request(bool on_device, epee::wipeable_string &passphrase) {
|
boost::optional<epee::wipeable_string> trezor_debug_callback::on_passphrase_request(bool on_device) {
|
||||||
|
return boost::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
void trezor_debug_callback::on_passphrase_state_request(const std::string &state) {
|
void trezor_debug_callback::on_passphrase_state_request(const std::string &state) {
|
||||||
|
|
|
@ -47,14 +47,15 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//automatic lock one more level on device ensuring the current thread is allowed to use it
|
//automatic lock one more level on device ensuring the current thread is allowed to use it
|
||||||
#define AUTO_LOCK_CMD() \
|
#define TREZOR_AUTO_LOCK_CMD() \
|
||||||
/* lock both mutexes without deadlock*/ \
|
/* lock both mutexes without deadlock*/ \
|
||||||
boost::lock(device_locker, command_locker); \
|
boost::lock(device_locker, command_locker); \
|
||||||
/* make sure both already-locked mutexes are unlocked at the end of scope */ \
|
/* make sure both already-locked mutexes are unlocked at the end of scope */ \
|
||||||
boost::lock_guard<boost::recursive_mutex> lock1(device_locker, boost::adopt_lock); \
|
boost::lock_guard<boost::recursive_mutex> lock1(device_locker, boost::adopt_lock); \
|
||||||
boost::lock_guard<boost::mutex> lock2(command_locker, boost::adopt_lock)
|
boost::lock_guard<boost::mutex> lock2(command_locker, boost::adopt_lock)
|
||||||
|
|
||||||
|
#define TREZOR_AUTO_LOCK_DEVICE() boost::lock_guard<boost::recursive_mutex> lock1_device(device_locker)
|
||||||
|
|
||||||
namespace hw {
|
namespace hw {
|
||||||
namespace trezor {
|
namespace trezor {
|
||||||
|
|
||||||
|
@ -62,14 +63,14 @@ namespace trezor {
|
||||||
class device_trezor_base;
|
class device_trezor_base;
|
||||||
|
|
||||||
#ifdef WITH_TREZOR_DEBUGGING
|
#ifdef WITH_TREZOR_DEBUGGING
|
||||||
class trezor_debug_callback {
|
class trezor_debug_callback : public hw::i_device_callback {
|
||||||
public:
|
public:
|
||||||
trezor_debug_callback()=default;
|
trezor_debug_callback()=default;
|
||||||
explicit trezor_debug_callback(std::shared_ptr<Transport> & debug_transport);
|
explicit trezor_debug_callback(std::shared_ptr<Transport> & debug_transport);
|
||||||
|
|
||||||
void on_button_request();
|
void on_button_request(uint64_t code=0) override;
|
||||||
void on_pin_request(epee::wipeable_string &pin);
|
boost::optional<epee::wipeable_string> on_pin_request() override;
|
||||||
void on_passphrase_request(bool on_device, epee::wipeable_string &passphrase);
|
boost::optional<epee::wipeable_string> on_passphrase_request(bool on_device) override;
|
||||||
void on_passphrase_state_request(const std::string &state);
|
void on_passphrase_state_request(const std::string &state);
|
||||||
void on_disconnect();
|
void on_disconnect();
|
||||||
protected:
|
protected:
|
||||||
|
@ -95,6 +96,8 @@ namespace trezor {
|
||||||
std::vector<unsigned int> m_wallet_deriv_path;
|
std::vector<unsigned int> m_wallet_deriv_path;
|
||||||
std::string m_device_state; // returned after passphrase entry, session
|
std::string m_device_state; // returned after passphrase entry, session
|
||||||
std::shared_ptr<messages::management::Features> m_features; // features from the last device reset
|
std::shared_ptr<messages::management::Features> m_features; // features from the last device reset
|
||||||
|
boost::optional<epee::wipeable_string> m_pin;
|
||||||
|
boost::optional<epee::wipeable_string> m_passphrase;
|
||||||
|
|
||||||
cryptonote::network_type network_type;
|
cryptonote::network_type network_type;
|
||||||
|
|
||||||
|
@ -109,11 +112,11 @@ namespace trezor {
|
||||||
// Internal methods
|
// Internal methods
|
||||||
//
|
//
|
||||||
|
|
||||||
void require_connected();
|
void require_connected() const;
|
||||||
void require_initialized();
|
void require_initialized() const;
|
||||||
void call_ping_unsafe();
|
void call_ping_unsafe();
|
||||||
void test_ping();
|
void test_ping();
|
||||||
void device_state_reset_unsafe();
|
virtual void device_state_reset_unsafe();
|
||||||
void ensure_derivation_path() noexcept;
|
void ensure_derivation_path() noexcept;
|
||||||
|
|
||||||
// Communication methods
|
// Communication methods
|
||||||
|
@ -265,6 +268,15 @@ namespace trezor {
|
||||||
|
|
||||||
void set_derivation_path(const std::string &deriv_path) override;
|
void set_derivation_path(const std::string &deriv_path) override;
|
||||||
|
|
||||||
|
virtual bool has_ki_live_refresh(void) const override { return false; }
|
||||||
|
|
||||||
|
virtual void set_pin(const epee::wipeable_string & pin) override {
|
||||||
|
m_pin = pin;
|
||||||
|
}
|
||||||
|
virtual void set_passphrase(const epee::wipeable_string & passphrase) override {
|
||||||
|
m_passphrase = passphrase;
|
||||||
|
}
|
||||||
|
|
||||||
/* ======================================================================= */
|
/* ======================================================================= */
|
||||||
/* SETUP/TEARDOWN */
|
/* SETUP/TEARDOWN */
|
||||||
/* ======================================================================= */
|
/* ======================================================================= */
|
||||||
|
|
|
@ -33,6 +33,8 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <boost/endian/conversion.hpp>
|
#include <boost/endian/conversion.hpp>
|
||||||
#include <common/apply_permutation.h>
|
#include <common/apply_permutation.h>
|
||||||
|
#include <common/json_util.h>
|
||||||
|
#include <crypto/hmac-keccak.h>
|
||||||
#include <ringct/rctSigs.h>
|
#include <ringct/rctSigs.h>
|
||||||
#include <ringct/bulletproofs.h>
|
#include <ringct/bulletproofs.h>
|
||||||
#include "cryptonote_config.h"
|
#include "cryptonote_config.h"
|
||||||
|
@ -40,6 +42,37 @@
|
||||||
#include <sodium/crypto_verify_32.h>
|
#include <sodium/crypto_verify_32.h>
|
||||||
#include <sodium/crypto_aead_chacha20poly1305.h>
|
#include <sodium/crypto_aead_chacha20poly1305.h>
|
||||||
|
|
||||||
|
#define GET_FIELD_STRING(name, type, jtype) field_##name = std::string(json[#name].GetString(), json[#name].GetStringLength())
|
||||||
|
#define GET_FIELD_OTHER(name, type, jtype) field_##name = static_cast<type>(json[#name].Get##jtype())
|
||||||
|
|
||||||
|
#define GET_STRING_FROM_JSON(json, name, type, mandatory, def) \
|
||||||
|
GET_FIELD_FROM_JSON_EX(json, name, type, String, mandatory, def, GET_FIELD_STRING)
|
||||||
|
|
||||||
|
#define GET_FIELD_FROM_JSON(json, name, type, jtype, mandatory, def) \
|
||||||
|
GET_FIELD_FROM_JSON_EX(json, name, type, jtype, mandatory, def, GET_FIELD_OTHER)
|
||||||
|
|
||||||
|
#define GET_FIELD_FROM_JSON_EX(json, name, type, jtype, mandatory, def, VAL) \
|
||||||
|
type field_##name = static_cast<type>(def); \
|
||||||
|
bool field_##name##_found = false; \
|
||||||
|
(void)field_##name##_found; \
|
||||||
|
do if (json.HasMember(#name)) \
|
||||||
|
{ \
|
||||||
|
if (json[#name].Is##jtype()) \
|
||||||
|
{ \
|
||||||
|
VAL(name, type, jtype); \
|
||||||
|
field_##name##_found = true; \
|
||||||
|
} \
|
||||||
|
else \
|
||||||
|
{ \
|
||||||
|
throw std::invalid_argument("Field " #name " found in JSON, but not " #jtype); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
else if (mandatory) \
|
||||||
|
{ \
|
||||||
|
throw std::invalid_argument("Field " #name " not found in JSON");\
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
|
||||||
namespace hw{
|
namespace hw{
|
||||||
namespace trezor{
|
namespace trezor{
|
||||||
namespace protocol{
|
namespace protocol{
|
||||||
|
@ -84,19 +117,22 @@ namespace protocol{
|
||||||
namespace crypto {
|
namespace crypto {
|
||||||
namespace chacha {
|
namespace chacha {
|
||||||
|
|
||||||
void decrypt(const void* ciphertext, size_t length, const uint8_t* key, const uint8_t* iv, char* plaintext){
|
void decrypt(const void* ciphertext, size_t length, const uint8_t* key, const uint8_t* iv, char* plaintext, size_t *plaintext_len){
|
||||||
if (length < 16){
|
CHECK_AND_ASSERT_THROW_MES(length >= TAG_SIZE, "Ciphertext length too small");
|
||||||
throw std::invalid_argument("Ciphertext length too small");
|
CHECK_AND_ASSERT_THROW_MES(!plaintext_len || *plaintext_len >= (length - TAG_SIZE), "Plaintext length too small");
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long long int cip_len = length;
|
unsigned long long int res_len = plaintext_len ? *plaintext_len : length;
|
||||||
auto r = crypto_aead_chacha20poly1305_ietf_decrypt(
|
auto r = crypto_aead_chacha20poly1305_ietf_decrypt(
|
||||||
reinterpret_cast<unsigned char *>(plaintext), &cip_len, nullptr,
|
reinterpret_cast<unsigned char *>(plaintext), &res_len, nullptr,
|
||||||
static_cast<const unsigned char *>(ciphertext), length, nullptr, 0, iv, key);
|
static_cast<const unsigned char *>(ciphertext), length, nullptr, 0, iv, key);
|
||||||
|
|
||||||
if (r != 0){
|
if (r != 0){
|
||||||
throw exc::Poly1305TagInvalid();
|
throw exc::Poly1305TagInvalid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (plaintext_len){
|
||||||
|
*plaintext_len = (size_t) res_len;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -185,6 +221,49 @@ namespace ki {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void live_refresh_ack(const ::crypto::secret_key & view_key_priv,
|
||||||
|
const ::crypto::public_key& out_key,
|
||||||
|
const std::shared_ptr<messages::monero::MoneroLiveRefreshStepAck> & ack,
|
||||||
|
::cryptonote::keypair& in_ephemeral,
|
||||||
|
::crypto::key_image& ki)
|
||||||
|
{
|
||||||
|
std::string str_out_key(out_key.data, sizeof(out_key.data));
|
||||||
|
auto enc_key = protocol::tx::compute_enc_key(view_key_priv, str_out_key, ack->salt());
|
||||||
|
|
||||||
|
const size_t len_ciphertext = ack->key_image().size(); // IV || keys
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(len_ciphertext > crypto::chacha::IV_SIZE + crypto::chacha::TAG_SIZE, "Invalid size");
|
||||||
|
|
||||||
|
size_t ki_len = len_ciphertext - crypto::chacha::IV_SIZE - crypto::chacha::TAG_SIZE;
|
||||||
|
std::unique_ptr<uint8_t[]> plaintext(new uint8_t[ki_len]);
|
||||||
|
uint8_t * buff = plaintext.get();
|
||||||
|
|
||||||
|
protocol::crypto::chacha::decrypt(
|
||||||
|
ack->key_image().data() + crypto::chacha::IV_SIZE,
|
||||||
|
len_ciphertext - crypto::chacha::IV_SIZE,
|
||||||
|
reinterpret_cast<const uint8_t *>(enc_key.data),
|
||||||
|
reinterpret_cast<const uint8_t *>(ack->key_image().data()),
|
||||||
|
reinterpret_cast<char *>(buff), &ki_len);
|
||||||
|
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(ki_len == 3*32, "Invalid size");
|
||||||
|
::crypto::signature sig{};
|
||||||
|
memcpy(ki.data, buff, 32);
|
||||||
|
memcpy(sig.c.data, buff + 32, 32);
|
||||||
|
memcpy(sig.r.data, buff + 64, 32);
|
||||||
|
in_ephemeral.pub = out_key;
|
||||||
|
in_ephemeral.sec = ::crypto::null_skey;
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
std::vector<const ::crypto::public_key*> pkeys;
|
||||||
|
pkeys.push_back(&out_key);
|
||||||
|
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(rct::scalarmultKey(rct::ki2rct(ki), rct::curveOrder()) == rct::identity(),
|
||||||
|
"Key image out of validity domain: key image " << epee::string_tools::pod_to_hex(ki));
|
||||||
|
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(::crypto::check_ring_signature((const ::crypto::hash&)ki, ki, pkeys, &sig),
|
||||||
|
"Signature failed for key image " << epee::string_tools::pod_to_hex(ki)
|
||||||
|
<< ", signature " + epee::string_tools::pod_to_hex(sig)
|
||||||
|
<< ", pubkey " + epee::string_tools::pod_to_hex(*pkeys[0]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cold transaction signing
|
// Cold transaction signing
|
||||||
|
@ -198,6 +277,8 @@ namespace tx {
|
||||||
void translate_dst_entry(MoneroTransactionDestinationEntry * dst, const cryptonote::tx_destination_entry * src){
|
void translate_dst_entry(MoneroTransactionDestinationEntry * dst, const cryptonote::tx_destination_entry * src){
|
||||||
dst->set_amount(src->amount);
|
dst->set_amount(src->amount);
|
||||||
dst->set_is_subaddress(src->is_subaddress);
|
dst->set_is_subaddress(src->is_subaddress);
|
||||||
|
dst->set_is_integrated(src->is_integrated);
|
||||||
|
dst->set_original(src->original);
|
||||||
translate_address(dst->mutable_addr(), &(src->addr));
|
translate_address(dst->mutable_addr(), &(src->addr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,9 +348,29 @@ namespace tx {
|
||||||
return std::string(buff, offset);
|
return std::string(buff, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::crypto::secret_key compute_enc_key(const ::crypto::secret_key & private_view_key, const std::string & aux, const std::string & salt)
|
||||||
|
{
|
||||||
|
uint8_t hash[32];
|
||||||
|
KECCAK_CTX ctx;
|
||||||
|
::crypto::secret_key res;
|
||||||
|
|
||||||
|
keccak_init(&ctx);
|
||||||
|
keccak_update(&ctx, (const uint8_t *) private_view_key.data, sizeof(private_view_key.data));
|
||||||
|
if (!aux.empty()){
|
||||||
|
keccak_update(&ctx, (const uint8_t *) aux.data(), aux.size());
|
||||||
|
}
|
||||||
|
keccak_finish(&ctx, hash);
|
||||||
|
keccak(hash, sizeof(hash), hash, sizeof(hash));
|
||||||
|
|
||||||
|
hmac_keccak_hash(hash, (const uint8_t *) salt.data(), salt.size(), hash, sizeof(hash));
|
||||||
|
memcpy(res.data, hash, sizeof(hash));
|
||||||
|
memwipe(hash, sizeof(hash));
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
TData::TData() {
|
TData::TData() {
|
||||||
in_memory = false;
|
|
||||||
rsig_type = 0;
|
rsig_type = 0;
|
||||||
|
bp_version = 0;
|
||||||
cur_input_idx = 0;
|
cur_input_idx = 0;
|
||||||
cur_output_idx = 0;
|
cur_output_idx = 0;
|
||||||
cur_batch_idx = 0;
|
cur_batch_idx = 0;
|
||||||
|
@ -283,6 +384,7 @@ namespace tx {
|
||||||
m_tx_idx = tx_idx;
|
m_tx_idx = tx_idx;
|
||||||
m_ct.tx_data = cur_tx();
|
m_ct.tx_data = cur_tx();
|
||||||
m_multisig = false;
|
m_multisig = false;
|
||||||
|
m_client_version = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Signer::extract_payment_id(){
|
void Signer::extract_payment_id(){
|
||||||
|
@ -392,8 +494,10 @@ namespace tx {
|
||||||
|
|
||||||
m_ct.tx.version = 2;
|
m_ct.tx.version = 2;
|
||||||
m_ct.tx.unlock_time = tx.unlock_time;
|
m_ct.tx.unlock_time = tx.unlock_time;
|
||||||
|
m_client_version = (m_aux_data->client_version ? m_aux_data->client_version.get() : 1);
|
||||||
|
|
||||||
tsx_data.set_version(1);
|
tsx_data.set_version(1);
|
||||||
|
tsx_data.set_client_version(client_version());
|
||||||
tsx_data.set_unlock_time(tx.unlock_time);
|
tsx_data.set_unlock_time(tx.unlock_time);
|
||||||
tsx_data.set_num_inputs(static_cast<google::protobuf::uint32>(tx.sources.size()));
|
tsx_data.set_num_inputs(static_cast<google::protobuf::uint32>(tx.sources.size()));
|
||||||
tsx_data.set_mixin(static_cast<google::protobuf::uint32>(tx.sources[0].outputs.size() - 1));
|
tsx_data.set_mixin(static_cast<google::protobuf::uint32>(tx.sources[0].outputs.size() - 1));
|
||||||
|
@ -404,6 +508,10 @@ namespace tx {
|
||||||
auto rsig_data = tsx_data.mutable_rsig_data();
|
auto rsig_data = tsx_data.mutable_rsig_data();
|
||||||
m_ct.rsig_type = get_rsig_type(tx.use_bulletproofs, tx.splitted_dsts.size());
|
m_ct.rsig_type = get_rsig_type(tx.use_bulletproofs, tx.splitted_dsts.size());
|
||||||
rsig_data->set_rsig_type(m_ct.rsig_type);
|
rsig_data->set_rsig_type(m_ct.rsig_type);
|
||||||
|
if (tx.use_bulletproofs){
|
||||||
|
m_ct.bp_version = (m_aux_data->bp_version ? m_aux_data->bp_version.get() : 1);
|
||||||
|
rsig_data->set_bp_version((uint32_t) m_ct.bp_version);
|
||||||
|
}
|
||||||
|
|
||||||
generate_rsig_batch_sizes(m_ct.grouping_vct, m_ct.rsig_type, tx.splitted_dsts.size());
|
generate_rsig_batch_sizes(m_ct.grouping_vct, m_ct.rsig_type, tx.splitted_dsts.size());
|
||||||
assign_to_repeatable(rsig_data->mutable_grouping(), m_ct.grouping_vct.begin(), m_ct.grouping_vct.end());
|
assign_to_repeatable(rsig_data->mutable_grouping(), m_ct.grouping_vct.begin(), m_ct.grouping_vct.end());
|
||||||
|
@ -437,7 +545,6 @@ namespace tx {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Signer::step_init_ack(std::shared_ptr<const messages::monero::MoneroTransactionInitAck> ack){
|
void Signer::step_init_ack(std::shared_ptr<const messages::monero::MoneroTransactionInitAck> ack){
|
||||||
m_ct.in_memory = false;
|
|
||||||
if (ack->has_rsig_data()){
|
if (ack->has_rsig_data()){
|
||||||
m_ct.rsig_param = std::make_shared<MoneroRsigData>(ack->rsig_data());
|
m_ct.rsig_param = std::make_shared<MoneroRsigData>(ack->rsig_data());
|
||||||
}
|
}
|
||||||
|
@ -505,10 +612,6 @@ namespace tx {
|
||||||
std::shared_ptr<messages::monero::MoneroTransactionInputsPermutationRequest> Signer::step_permutation(){
|
std::shared_ptr<messages::monero::MoneroTransactionInputsPermutationRequest> Signer::step_permutation(){
|
||||||
sort_ki();
|
sort_ki();
|
||||||
|
|
||||||
if (in_memory()){
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto res = std::make_shared<messages::monero::MoneroTransactionInputsPermutationRequest>();
|
auto res = std::make_shared<messages::monero::MoneroTransactionInputsPermutationRequest>();
|
||||||
assign_to_repeatable(res->mutable_perm(), m_ct.source_permutation.begin(), m_ct.source_permutation.end());
|
assign_to_repeatable(res->mutable_perm(), m_ct.source_permutation.begin(), m_ct.source_permutation.end());
|
||||||
|
|
||||||
|
@ -516,15 +619,10 @@ namespace tx {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Signer::step_permutation_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputsPermutationAck> ack){
|
void Signer::step_permutation_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputsPermutationAck> ack){
|
||||||
if (in_memory()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<messages::monero::MoneroTransactionInputViniRequest> Signer::step_set_vini_input(size_t idx){
|
std::shared_ptr<messages::monero::MoneroTransactionInputViniRequest> Signer::step_set_vini_input(size_t idx){
|
||||||
if (in_memory()){
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_data.sources.size(), "Invalid transaction index");
|
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_data.sources.size(), "Invalid transaction index");
|
||||||
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx.vin.size(), "Invalid transaction index");
|
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx.vin.size(), "Invalid transaction index");
|
||||||
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_in_hmacs.size(), "Invalid transaction index");
|
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_in_hmacs.size(), "Invalid transaction index");
|
||||||
|
@ -536,7 +634,8 @@ namespace tx {
|
||||||
translate_src_entry(res->mutable_src_entr(), &(tx.sources[idx]));
|
translate_src_entry(res->mutable_src_entr(), &(tx.sources[idx]));
|
||||||
res->set_vini(cryptonote::t_serializable_object_to_blob(vini));
|
res->set_vini(cryptonote::t_serializable_object_to_blob(vini));
|
||||||
res->set_vini_hmac(m_ct.tx_in_hmacs[idx]);
|
res->set_vini_hmac(m_ct.tx_in_hmacs[idx]);
|
||||||
if (!in_memory()) {
|
|
||||||
|
if (client_version() == 0) {
|
||||||
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs.size(), "Invalid transaction index");
|
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs.size(), "Invalid transaction index");
|
||||||
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs_hmac.size(), "Invalid transaction index");
|
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs_hmac.size(), "Invalid transaction index");
|
||||||
res->set_pseudo_out(m_ct.pseudo_outs[idx]);
|
res->set_pseudo_out(m_ct.pseudo_outs[idx]);
|
||||||
|
@ -547,9 +646,7 @@ namespace tx {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Signer::step_set_vini_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputViniAck> ack){
|
void Signer::step_set_vini_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputViniAck> ack){
|
||||||
if (in_memory()){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<messages::monero::MoneroTransactionAllInputsSetRequest> Signer::step_all_inputs_set(){
|
std::shared_ptr<messages::monero::MoneroTransactionAllInputsSetRequest> Signer::step_all_inputs_set(){
|
||||||
|
@ -557,34 +654,37 @@ namespace tx {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Signer::step_all_inputs_set_ack(std::shared_ptr<const messages::monero::MoneroTransactionAllInputsSetAck> ack){
|
void Signer::step_all_inputs_set_ack(std::shared_ptr<const messages::monero::MoneroTransactionAllInputsSetAck> ack){
|
||||||
if (is_offloading()){
|
if (client_version() > 0 || !is_offloading()){
|
||||||
// If offloading, expect rsig configuration.
|
return;
|
||||||
if (!ack->has_rsig_data()){
|
}
|
||||||
throw exc::ProtocolException("Rsig offloading requires rsig param");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto & rsig_data = ack->rsig_data();
|
// If offloading, expect rsig configuration.
|
||||||
if (!rsig_data.has_mask()){
|
if (!ack->has_rsig_data()){
|
||||||
throw exc::ProtocolException("Gamma masks not present in offloaded version");
|
throw exc::ProtocolException("Rsig offloading requires rsig param");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto & mask = rsig_data.mask();
|
auto & rsig_data = ack->rsig_data();
|
||||||
if (mask.size() != 32 * num_outputs()){
|
if (!rsig_data.has_mask()){
|
||||||
throw exc::ProtocolException("Invalid number of gamma masks");
|
throw exc::ProtocolException("Gamma masks not present in offloaded version");
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ct.rsig_gamma.reserve(num_outputs());
|
auto & mask = rsig_data.mask();
|
||||||
for(size_t c=0; c < num_outputs(); ++c){
|
if (mask.size() != 32 * num_outputs()){
|
||||||
rct::key cmask{};
|
throw exc::ProtocolException("Invalid number of gamma masks");
|
||||||
memcpy(cmask.bytes, mask.data() + c * 32, 32);
|
}
|
||||||
m_ct.rsig_gamma.emplace_back(cmask);
|
|
||||||
}
|
m_ct.rsig_gamma.reserve(num_outputs());
|
||||||
|
for(size_t c=0; c < num_outputs(); ++c){
|
||||||
|
rct::key cmask{};
|
||||||
|
memcpy(cmask.bytes, mask.data() + c * 32, 32);
|
||||||
|
m_ct.rsig_gamma.emplace_back(cmask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<messages::monero::MoneroTransactionSetOutputRequest> Signer::step_set_output(size_t idx){
|
std::shared_ptr<messages::monero::MoneroTransactionSetOutputRequest> Signer::step_set_output(size_t idx){
|
||||||
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_data.splitted_dsts.size(), "Invalid transaction index");
|
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_data.splitted_dsts.size(), "Invalid transaction index");
|
||||||
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_out_entr_hmacs.size(), "Invalid transaction index");
|
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_out_entr_hmacs.size(), "Invalid transaction index");
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(is_req_bulletproof(), "Borromean rsig not supported");
|
||||||
|
|
||||||
m_ct.cur_output_idx = idx;
|
m_ct.cur_output_idx = idx;
|
||||||
m_ct.cur_output_in_batch_idx += 1; // assumes sequential call to step_set_output()
|
m_ct.cur_output_in_batch_idx += 1; // assumes sequential call to step_set_output()
|
||||||
|
@ -595,48 +695,11 @@ namespace tx {
|
||||||
res->set_dst_entr_hmac(m_ct.tx_out_entr_hmacs[idx]);
|
res->set_dst_entr_hmac(m_ct.tx_out_entr_hmacs[idx]);
|
||||||
|
|
||||||
// Range sig offloading to the host
|
// Range sig offloading to the host
|
||||||
if (!is_offloading()) {
|
// ClientV0 sends offloaded BP with the last message in the batch.
|
||||||
return res;
|
// ClientV1 needs additional message after the last message in the batch as BP uses deterministic masks.
|
||||||
}
|
if (client_version() == 0 && is_offloading() && should_compute_bp_now()) {
|
||||||
|
auto rsig_data = res->mutable_rsig_data();
|
||||||
CHECK_AND_ASSERT_THROW_MES(m_ct.cur_batch_idx < m_ct.grouping_vct.size(), "Invalid batch index");
|
compute_bproof(*rsig_data);
|
||||||
if (m_ct.grouping_vct[m_ct.cur_batch_idx] > m_ct.cur_output_in_batch_idx) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto rsig_data = res->mutable_rsig_data();
|
|
||||||
auto batch_size = m_ct.grouping_vct[m_ct.cur_batch_idx];
|
|
||||||
|
|
||||||
if (!is_req_bulletproof()){
|
|
||||||
if (batch_size > 1){
|
|
||||||
throw std::invalid_argument("Borromean cannot batch outputs");
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.rsig_gamma.size(), "Invalid gamma index");
|
|
||||||
rct::key C{}, mask = m_ct.rsig_gamma[idx];
|
|
||||||
auto genRsig = rct::proveRange(C, mask, cur_dst.amount); // TODO: rsig with given mask
|
|
||||||
auto serRsig = cn_serialize(genRsig);
|
|
||||||
m_ct.tx_out_rsigs.emplace_back(genRsig);
|
|
||||||
rsig_data->set_rsig(serRsig);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
std::vector<uint64_t> amounts;
|
|
||||||
rct::keyV masks;
|
|
||||||
CHECK_AND_ASSERT_THROW_MES(idx + 1 >= batch_size, "Invalid index for batching");
|
|
||||||
|
|
||||||
for(size_t i = 0; i < batch_size; ++i){
|
|
||||||
const size_t bidx = 1 + idx - batch_size + i;
|
|
||||||
CHECK_AND_ASSERT_THROW_MES(bidx < m_ct.tx_data.splitted_dsts.size(), "Invalid gamma index");
|
|
||||||
CHECK_AND_ASSERT_THROW_MES(bidx < m_ct.rsig_gamma.size(), "Invalid gamma index");
|
|
||||||
|
|
||||||
amounts.push_back(m_ct.tx_data.splitted_dsts[bidx].amount);
|
|
||||||
masks.push_back(m_ct.rsig_gamma[bidx]);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto bp = bulletproof_PROVE(amounts, masks);
|
|
||||||
auto serRsig = cn_serialize(bp);
|
|
||||||
m_ct.tx_out_rsigs.emplace_back(bp);
|
|
||||||
rsig_data->set_rsig(serRsig);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
@ -644,7 +707,6 @@ namespace tx {
|
||||||
|
|
||||||
void Signer::step_set_output_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetOutputAck> ack){
|
void Signer::step_set_output_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetOutputAck> ack){
|
||||||
cryptonote::tx_out tx_out;
|
cryptonote::tx_out tx_out;
|
||||||
rct::rangeSig range_sig{};
|
|
||||||
rct::Bulletproof bproof{};
|
rct::Bulletproof bproof{};
|
||||||
rct::ctkey out_pk{};
|
rct::ctkey out_pk{};
|
||||||
rct::ecdhTuple ecdh{};
|
rct::ecdhTuple ecdh{};
|
||||||
|
@ -658,12 +720,12 @@ namespace tx {
|
||||||
if (rsig_data.has_rsig() && !rsig_data.rsig().empty()){
|
if (rsig_data.has_rsig() && !rsig_data.rsig().empty()){
|
||||||
has_rsig = true;
|
has_rsig = true;
|
||||||
rsig_buff = rsig_data.rsig();
|
rsig_buff = rsig_data.rsig();
|
||||||
|
}
|
||||||
|
|
||||||
} else if (rsig_data.rsig_parts_size() > 0){
|
if (client_version() >= 1 && rsig_data.has_mask()){
|
||||||
has_rsig = true;
|
rct::key cmask{};
|
||||||
for (const auto &it : rsig_data.rsig_parts()) {
|
string_to_key(cmask, rsig_data.mask());
|
||||||
rsig_buff += it;
|
m_ct.rsig_gamma.emplace_back(cmask);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -675,12 +737,13 @@ namespace tx {
|
||||||
throw exc::ProtocolException("Cannot deserialize out_pk");
|
throw exc::ProtocolException("Cannot deserialize out_pk");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cn_deserialize(ack->ecdh_info(), ecdh)){
|
if (m_ct.bp_version <= 1) {
|
||||||
throw exc::ProtocolException("Cannot deserialize ecdhtuple");
|
if (!cn_deserialize(ack->ecdh_info(), ecdh)){
|
||||||
}
|
throw exc::ProtocolException("Cannot deserialize ecdhtuple");
|
||||||
|
}
|
||||||
if (has_rsig && !is_req_bulletproof() && !cn_deserialize(rsig_buff, range_sig)){
|
} else {
|
||||||
throw exc::ProtocolException("Cannot deserialize rangesig");
|
CHECK_AND_ASSERT_THROW_MES(8 == ack->ecdh_info().size(), "Invalid ECDH.amount size");
|
||||||
|
memcpy(ecdh.amount.bytes, ack->ecdh_info().data(), 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (has_rsig && is_req_bulletproof() && !cn_deserialize(rsig_buff, bproof)){
|
if (has_rsig && is_req_bulletproof() && !cn_deserialize(rsig_buff, bproof)){
|
||||||
|
@ -692,35 +755,77 @@ namespace tx {
|
||||||
m_ct.tx_out_pk.emplace_back(out_pk);
|
m_ct.tx_out_pk.emplace_back(out_pk);
|
||||||
m_ct.tx_out_ecdh.emplace_back(ecdh);
|
m_ct.tx_out_ecdh.emplace_back(ecdh);
|
||||||
|
|
||||||
if (!has_rsig){
|
// ClientV0, if no rsig was generated on Trezor, do not continue.
|
||||||
|
// ClientV1+ generates BP after all masks in the current batch are generated
|
||||||
|
if (!has_rsig || (client_version() >= 1 && is_offloading())){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_req_bulletproof()){
|
process_bproof(bproof);
|
||||||
CHECK_AND_ASSERT_THROW_MES(m_ct.cur_batch_idx < m_ct.grouping_vct.size(), "Invalid batch index");
|
m_ct.cur_batch_idx += 1;
|
||||||
auto batch_size = m_ct.grouping_vct[m_ct.cur_batch_idx];
|
m_ct.cur_output_in_batch_idx = 0;
|
||||||
for (size_t i = 0; i < batch_size; ++i){
|
}
|
||||||
const size_t bidx = 1 + m_ct.cur_output_idx - batch_size + i;
|
|
||||||
CHECK_AND_ASSERT_THROW_MES(bidx < m_ct.tx_out_pk.size(), "Invalid out index");
|
|
||||||
|
|
||||||
rct::key commitment = m_ct.tx_out_pk[bidx].mask;
|
bool Signer::should_compute_bp_now() const {
|
||||||
commitment = rct::scalarmultKey(commitment, rct::INV_EIGHT);
|
CHECK_AND_ASSERT_THROW_MES(m_ct.cur_batch_idx < m_ct.grouping_vct.size(), "Invalid batch index");
|
||||||
bproof.V.push_back(commitment);
|
return m_ct.grouping_vct[m_ct.cur_batch_idx] <= m_ct.cur_output_in_batch_idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ct.tx_out_rsigs.emplace_back(bproof);
|
void Signer::compute_bproof(messages::monero::MoneroTransactionRsigData & rsig_data){
|
||||||
if (!rct::bulletproof_VERIFY(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs.back()))) {
|
auto batch_size = m_ct.grouping_vct[m_ct.cur_batch_idx];
|
||||||
throw exc::ProtocolException("Returned range signature is invalid");
|
std::vector<uint64_t> amounts;
|
||||||
}
|
rct::keyV masks;
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(m_ct.cur_output_idx + 1 >= batch_size, "Invalid index for batching");
|
||||||
|
|
||||||
} else {
|
for(size_t i = 0; i < batch_size; ++i){
|
||||||
m_ct.tx_out_rsigs.emplace_back(range_sig);
|
const size_t bidx = 1 + m_ct.cur_output_idx - batch_size + i;
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(bidx < m_ct.tx_data.splitted_dsts.size(), "Invalid gamma index");
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(bidx < m_ct.rsig_gamma.size(), "Invalid gamma index");
|
||||||
|
|
||||||
if (!rct::verRange(out_pk.mask, boost::get<rct::rangeSig>(m_ct.tx_out_rsigs.back()))) {
|
amounts.push_back(m_ct.tx_data.splitted_dsts[bidx].amount);
|
||||||
throw exc::ProtocolException("Returned range signature is invalid");
|
masks.push_back(m_ct.rsig_gamma[bidx]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto bp = bulletproof_PROVE(amounts, masks);
|
||||||
|
auto serRsig = cn_serialize(bp);
|
||||||
|
m_ct.tx_out_rsigs.emplace_back(bp);
|
||||||
|
rsig_data.set_rsig(serRsig);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Signer::process_bproof(rct::Bulletproof & bproof){
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(m_ct.cur_batch_idx < m_ct.grouping_vct.size(), "Invalid batch index");
|
||||||
|
auto batch_size = m_ct.grouping_vct[m_ct.cur_batch_idx];
|
||||||
|
for (size_t i = 0; i < batch_size; ++i){
|
||||||
|
const size_t bidx = 1 + m_ct.cur_output_idx - batch_size + i;
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(bidx < m_ct.tx_out_pk.size(), "Invalid out index");
|
||||||
|
|
||||||
|
rct::key commitment = m_ct.tx_out_pk[bidx].mask;
|
||||||
|
commitment = rct::scalarmultKey(commitment, rct::INV_EIGHT);
|
||||||
|
bproof.V.push_back(commitment);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ct.tx_out_rsigs.emplace_back(bproof);
|
||||||
|
if (!rct::bulletproof_VERIFY(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs.back()))) {
|
||||||
|
throw exc::ProtocolException("Returned range signature is invalid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<messages::monero::MoneroTransactionSetOutputRequest> Signer::step_rsig(size_t idx){
|
||||||
|
if (client_version() == 0 || !is_offloading() || !should_compute_bp_now()){
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto res = std::make_shared<messages::monero::MoneroTransactionSetOutputRequest>();
|
||||||
|
auto & cur_dst = m_ct.tx_data.splitted_dsts[idx];
|
||||||
|
translate_dst_entry(res->mutable_dst_entr(), &cur_dst);
|
||||||
|
res->set_dst_entr_hmac(m_ct.tx_out_entr_hmacs[idx]);
|
||||||
|
|
||||||
|
compute_bproof(*(res->mutable_rsig_data()));
|
||||||
|
res->set_is_offloaded_bp(true);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Signer::step_set_rsig_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetOutputAck> ack){
|
||||||
m_ct.cur_batch_idx += 1;
|
m_ct.cur_batch_idx += 1;
|
||||||
m_ct.cur_output_in_batch_idx = 0;
|
m_ct.cur_output_in_batch_idx = 0;
|
||||||
}
|
}
|
||||||
|
@ -814,12 +919,11 @@ namespace tx {
|
||||||
res->set_vini_hmac(m_ct.tx_in_hmacs[idx]);
|
res->set_vini_hmac(m_ct.tx_in_hmacs[idx]);
|
||||||
res->set_pseudo_out_alpha(m_ct.alphas[idx]);
|
res->set_pseudo_out_alpha(m_ct.alphas[idx]);
|
||||||
res->set_spend_key(m_ct.spend_encs[idx]);
|
res->set_spend_key(m_ct.spend_encs[idx]);
|
||||||
if (!in_memory()){
|
|
||||||
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs.size(), "Invalid transaction index");
|
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs.size(), "Invalid transaction index");
|
||||||
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs_hmac.size(), "Invalid transaction index");
|
CHECK_AND_ASSERT_THROW_MES(idx < m_ct.pseudo_outs_hmac.size(), "Invalid transaction index");
|
||||||
res->set_pseudo_out(m_ct.pseudo_outs[idx]);
|
res->set_pseudo_out(m_ct.pseudo_outs[idx]);
|
||||||
res->set_pseudo_out_hmac(m_ct.pseudo_outs_hmac[idx]);
|
res->set_pseudo_out_hmac(m_ct.pseudo_outs_hmac[idx]);
|
||||||
}
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -829,6 +933,19 @@ namespace tx {
|
||||||
throw exc::ProtocolException("Cannot deserialize mg[i]");
|
throw exc::ProtocolException("Cannot deserialize mg[i]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync updated pseudo_outputs, client_version>=1, HF10+
|
||||||
|
if (client_version() >= 1 && ack->has_pseudo_out()){
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(m_ct.cur_input_idx < m_ct.pseudo_outs.size(), "Invalid pseudo-out index");
|
||||||
|
m_ct.pseudo_outs[m_ct.cur_input_idx] = ack->pseudo_out();
|
||||||
|
if (is_bulletproof()){
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(m_ct.cur_input_idx < m_ct.rv->p.pseudoOuts.size(), "Invalid pseudo-out index");
|
||||||
|
string_to_key(m_ct.rv->p.pseudoOuts[m_ct.cur_input_idx], ack->pseudo_out());
|
||||||
|
} else {
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(m_ct.cur_input_idx < m_ct.rv->pseudoOuts.size(), "Invalid pseudo-out index");
|
||||||
|
string_to_key(m_ct.rv->pseudoOuts[m_ct.cur_input_idx], ack->pseudo_out());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_ct.rv->p.MGs.push_back(mg);
|
m_ct.rv->p.MGs.push_back(mg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -841,14 +958,14 @@ namespace tx {
|
||||||
if (m_multisig){
|
if (m_multisig){
|
||||||
auto & cout_key = ack->cout_key();
|
auto & cout_key = ack->cout_key();
|
||||||
for(auto & cur : m_ct.couts){
|
for(auto & cur : m_ct.couts){
|
||||||
if (cur.size() != 12 + 32){
|
if (cur.size() != crypto::chacha::IV_SIZE + 32){
|
||||||
throw std::invalid_argument("Encrypted cout has invalid length");
|
throw std::invalid_argument("Encrypted cout has invalid length");
|
||||||
}
|
}
|
||||||
|
|
||||||
char buff[32];
|
char buff[32];
|
||||||
auto data = cur.data();
|
auto data = cur.data();
|
||||||
|
|
||||||
crypto::chacha::decrypt(data + 12, 32, reinterpret_cast<const uint8_t *>(cout_key.data()), reinterpret_cast<const uint8_t *>(data), buff);
|
crypto::chacha::decrypt(data + crypto::chacha::IV_SIZE, 32, reinterpret_cast<const uint8_t *>(cout_key.data()), reinterpret_cast<const uint8_t *>(data), buff);
|
||||||
m_ct.couts_dec.emplace_back(buff, 32);
|
m_ct.couts_dec.emplace_back(buff, 32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -887,6 +1004,82 @@ namespace tx {
|
||||||
return sb.GetString();
|
return sb.GetString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void load_tx_key_data(hw::device_cold::tx_key_data_t & res, const std::string & data)
|
||||||
|
{
|
||||||
|
rapidjson::Document json;
|
||||||
|
|
||||||
|
// The contents should be JSON if the wallet follows the new format.
|
||||||
|
if (json.Parse(data.c_str()).HasParseError())
|
||||||
|
{
|
||||||
|
throw std::invalid_argument("Data parsing error");
|
||||||
|
}
|
||||||
|
else if(!json.IsObject())
|
||||||
|
{
|
||||||
|
throw std::invalid_argument("Data parsing error - not an object");
|
||||||
|
}
|
||||||
|
|
||||||
|
GET_FIELD_FROM_JSON(json, version, int, Int, true, -1);
|
||||||
|
GET_STRING_FROM_JSON(json, salt1, std::string, true, std::string());
|
||||||
|
GET_STRING_FROM_JSON(json, salt2, std::string, true, std::string());
|
||||||
|
GET_STRING_FROM_JSON(json, enc_keys, std::string, true, std::string());
|
||||||
|
GET_STRING_FROM_JSON(json, tx_prefix_hash, std::string, false, std::string());
|
||||||
|
|
||||||
|
if (field_version != 1)
|
||||||
|
{
|
||||||
|
throw std::invalid_argument("Unknown version");
|
||||||
|
}
|
||||||
|
|
||||||
|
res.salt1 = field_salt1;
|
||||||
|
res.salt2 = field_salt2;
|
||||||
|
res.tx_enc_keys = field_enc_keys;
|
||||||
|
res.tx_prefix_hash = field_tx_prefix_hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<messages::monero::MoneroGetTxKeyRequest> get_tx_key(
|
||||||
|
const hw::device_cold::tx_key_data_t & tx_data)
|
||||||
|
{
|
||||||
|
auto req = std::make_shared<messages::monero::MoneroGetTxKeyRequest>();
|
||||||
|
req->set_salt1(tx_data.salt1);
|
||||||
|
req->set_salt2(tx_data.salt2);
|
||||||
|
req->set_tx_enc_keys(tx_data.tx_enc_keys);
|
||||||
|
req->set_tx_prefix_hash(tx_data.tx_prefix_hash);
|
||||||
|
req->set_reason(0);
|
||||||
|
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_tx_key_ack(
|
||||||
|
std::vector<::crypto::secret_key> & tx_keys,
|
||||||
|
const std::string & tx_prefix_hash,
|
||||||
|
const ::crypto::secret_key & view_key_priv,
|
||||||
|
std::shared_ptr<const messages::monero::MoneroGetTxKeyAck> ack
|
||||||
|
)
|
||||||
|
{
|
||||||
|
auto enc_key = protocol::tx::compute_enc_key(view_key_priv, tx_prefix_hash, ack->salt());
|
||||||
|
auto & encrypted_keys = ack->has_tx_derivations() ? ack->tx_derivations() : ack->tx_keys();
|
||||||
|
|
||||||
|
const size_t len_ciphertext = encrypted_keys.size(); // IV || keys || TAG
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(len_ciphertext > crypto::chacha::IV_SIZE + crypto::chacha::TAG_SIZE, "Invalid size");
|
||||||
|
|
||||||
|
size_t keys_len = len_ciphertext - crypto::chacha::IV_SIZE - crypto::chacha::TAG_SIZE;
|
||||||
|
std::unique_ptr<uint8_t[]> plaintext(new uint8_t[keys_len]);
|
||||||
|
|
||||||
|
protocol::crypto::chacha::decrypt(
|
||||||
|
encrypted_keys.data() + crypto::chacha::IV_SIZE,
|
||||||
|
len_ciphertext - crypto::chacha::IV_SIZE,
|
||||||
|
reinterpret_cast<const uint8_t *>(enc_key.data),
|
||||||
|
reinterpret_cast<const uint8_t *>(encrypted_keys.data()),
|
||||||
|
reinterpret_cast<char *>(plaintext.get()), &keys_len);
|
||||||
|
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(keys_len % 32 == 0, "Invalid size");
|
||||||
|
tx_keys.resize(keys_len / 32);
|
||||||
|
|
||||||
|
for(unsigned i = 0; i < keys_len / 32; ++i)
|
||||||
|
{
|
||||||
|
memcpy(tx_keys[i].data, plaintext.get() + 32 * i, 32);
|
||||||
|
}
|
||||||
|
memwipe(plaintext.get(), keys_len);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,11 +92,14 @@ namespace protocol{
|
||||||
// Crypto / encryption
|
// Crypto / encryption
|
||||||
namespace crypto {
|
namespace crypto {
|
||||||
namespace chacha {
|
namespace chacha {
|
||||||
|
// Constants as defined in RFC 7539.
|
||||||
|
const unsigned IV_SIZE = 12;
|
||||||
|
const unsigned TAG_SIZE = 16; // crypto_aead_chacha20poly1305_IETF_ABYTES;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chacha20Poly1305 decryption with tag verification. RFC 7539.
|
* Chacha20Poly1305 decryption with tag verification. RFC 7539.
|
||||||
*/
|
*/
|
||||||
void decrypt(const void* ciphertext, size_t length, const uint8_t* key, const uint8_t* iv, char* plaintext);
|
void decrypt(const void* ciphertext, size_t length, const uint8_t* key, const uint8_t* iv, char* plaintext, size_t *plaintext_len=nullptr);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,6 +132,14 @@ namespace ki {
|
||||||
const std::vector<tools::wallet2::transfer_details> & transfers,
|
const std::vector<tools::wallet2::transfer_details> & transfers,
|
||||||
std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req);
|
std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes Live refresh step response, parses KI, checks the signature
|
||||||
|
*/
|
||||||
|
void live_refresh_ack(const ::crypto::secret_key & view_key_priv,
|
||||||
|
const ::crypto::public_key& out_key,
|
||||||
|
const std::shared_ptr<messages::monero::MoneroLiveRefreshStepAck> & ack,
|
||||||
|
::cryptonote::keypair& in_ephemeral,
|
||||||
|
::crypto::key_image& ki);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cold transaction signing
|
// Cold transaction signing
|
||||||
|
@ -153,6 +164,7 @@ namespace tx {
|
||||||
std::string hash_addr(const MoneroAccountPublicAddress * addr, boost::optional<uint64_t> amount = boost::none, boost::optional<bool> is_subaddr = boost::none);
|
std::string hash_addr(const MoneroAccountPublicAddress * addr, boost::optional<uint64_t> amount = boost::none, boost::optional<bool> is_subaddr = boost::none);
|
||||||
std::string hash_addr(const std::string & spend_key, const std::string & view_key, boost::optional<uint64_t> amount = boost::none, boost::optional<bool> is_subaddr = boost::none);
|
std::string hash_addr(const std::string & spend_key, const std::string & view_key, boost::optional<uint64_t> amount = boost::none, boost::optional<bool> is_subaddr = boost::none);
|
||||||
std::string hash_addr(const ::crypto::public_key * spend_key, const ::crypto::public_key * view_key, boost::optional<uint64_t> amount = boost::none, boost::optional<bool> is_subaddr = boost::none);
|
std::string hash_addr(const ::crypto::public_key * spend_key, const ::crypto::public_key * view_key, boost::optional<uint64_t> amount = boost::none, boost::optional<bool> is_subaddr = boost::none);
|
||||||
|
::crypto::secret_key compute_enc_key(const ::crypto::secret_key & private_view_key, const std::string & aux, const std::string & salt);
|
||||||
|
|
||||||
typedef boost::variant<rct::rangeSig, rct::Bulletproof> rsig_v;
|
typedef boost::variant<rct::rangeSig, rct::Bulletproof> rsig_v;
|
||||||
|
|
||||||
|
@ -164,8 +176,8 @@ namespace tx {
|
||||||
TsxData tsx_data;
|
TsxData tsx_data;
|
||||||
tx_construction_data tx_data;
|
tx_construction_data tx_data;
|
||||||
cryptonote::transaction tx;
|
cryptonote::transaction tx;
|
||||||
bool in_memory;
|
|
||||||
unsigned rsig_type;
|
unsigned rsig_type;
|
||||||
|
int bp_version;
|
||||||
std::vector<uint64_t> grouping_vct;
|
std::vector<uint64_t> grouping_vct;
|
||||||
std::shared_ptr<MoneroRsigData> rsig_param;
|
std::shared_ptr<MoneroRsigData> rsig_param;
|
||||||
size_t cur_input_idx;
|
size_t cur_input_idx;
|
||||||
|
@ -206,6 +218,7 @@ namespace tx {
|
||||||
const unsigned_tx_set * m_unsigned_tx;
|
const unsigned_tx_set * m_unsigned_tx;
|
||||||
hw::tx_aux_data * m_aux_data;
|
hw::tx_aux_data * m_aux_data;
|
||||||
|
|
||||||
|
unsigned m_client_version;
|
||||||
bool m_multisig;
|
bool m_multisig;
|
||||||
|
|
||||||
const tx_construction_data & cur_tx(){
|
const tx_construction_data & cur_tx(){
|
||||||
|
@ -215,6 +228,9 @@ namespace tx {
|
||||||
|
|
||||||
void extract_payment_id();
|
void extract_payment_id();
|
||||||
void compute_integrated_indices(TsxData * tsx_data);
|
void compute_integrated_indices(TsxData * tsx_data);
|
||||||
|
bool should_compute_bp_now() const;
|
||||||
|
void compute_bproof(messages::monero::MoneroTransactionRsigData & rsig_data);
|
||||||
|
void process_bproof(rct::Bulletproof & bproof);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Signer(wallet_shim * wallet2, const unsigned_tx_set * unsigned_tx, size_t tx_idx = 0, hw::tx_aux_data * aux_data = nullptr);
|
Signer(wallet_shim * wallet2, const unsigned_tx_set * unsigned_tx, size_t tx_idx = 0, hw::tx_aux_data * aux_data = nullptr);
|
||||||
|
@ -238,6 +254,9 @@ namespace tx {
|
||||||
std::shared_ptr<messages::monero::MoneroTransactionSetOutputRequest> step_set_output(size_t idx);
|
std::shared_ptr<messages::monero::MoneroTransactionSetOutputRequest> step_set_output(size_t idx);
|
||||||
void step_set_output_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetOutputAck> ack);
|
void step_set_output_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetOutputAck> ack);
|
||||||
|
|
||||||
|
std::shared_ptr<messages::monero::MoneroTransactionSetOutputRequest> step_rsig(size_t idx);
|
||||||
|
void step_set_rsig_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetOutputAck> ack);
|
||||||
|
|
||||||
std::shared_ptr<messages::monero::MoneroTransactionAllOutSetRequest> step_all_outs_set();
|
std::shared_ptr<messages::monero::MoneroTransactionAllOutSetRequest> step_all_outs_set();
|
||||||
void step_all_outs_set_ack(std::shared_ptr<const messages::monero::MoneroTransactionAllOutSetAck> ack, hw::device &hwdev);
|
void step_all_outs_set_ack(std::shared_ptr<const messages::monero::MoneroTransactionAllOutSetAck> ack, hw::device &hwdev);
|
||||||
|
|
||||||
|
@ -249,8 +268,8 @@ namespace tx {
|
||||||
|
|
||||||
std::string store_tx_aux_info();
|
std::string store_tx_aux_info();
|
||||||
|
|
||||||
bool in_memory() const {
|
unsigned client_version() const {
|
||||||
return m_ct.in_memory;
|
return m_client_version;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_simple() const {
|
bool is_simple() const {
|
||||||
|
@ -290,6 +309,18 @@ namespace tx {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TX Key decryption
|
||||||
|
void load_tx_key_data(hw::device_cold::tx_key_data_t & res, const std::string & data);
|
||||||
|
|
||||||
|
std::shared_ptr<messages::monero::MoneroGetTxKeyRequest> get_tx_key(
|
||||||
|
const hw::device_cold::tx_key_data_t & tx_data);
|
||||||
|
|
||||||
|
void get_tx_key_ack(
|
||||||
|
std::vector<::crypto::secret_key> & tx_keys,
|
||||||
|
const std::string & tx_prefix_hash,
|
||||||
|
const ::crypto::secret_key & view_key_priv,
|
||||||
|
std::shared_ptr<const messages::monero::MoneroGetTxKeyAck> ack
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2049,7 +2049,7 @@ bool simple_wallet::cold_sign_tx(const std::vector<tools::wallet2::pending_tx>&
|
||||||
m_wallet->cold_tx_aux_import(exported_txs.ptx, tx_aux);
|
m_wallet->cold_tx_aux_import(exported_txs.ptx, tx_aux);
|
||||||
|
|
||||||
// import key images
|
// import key images
|
||||||
return m_wallet->import_key_images(exported_txs.key_images);
|
return m_wallet->import_key_images(exported_txs, 0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
|
bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
|
||||||
|
@ -4686,12 +4686,12 @@ boost::optional<epee::wipeable_string> simple_wallet::on_get_password(const char
|
||||||
return pwd_container->password();
|
return pwd_container->password();
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
void simple_wallet::on_button_request()
|
void simple_wallet::on_device_button_request(uint64_t code)
|
||||||
{
|
{
|
||||||
message_writer(console_color_white, false) << tr("Device requires attention");
|
message_writer(console_color_white, false) << tr("Device requires attention");
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
void simple_wallet::on_pin_request(epee::wipeable_string & pin)
|
boost::optional<epee::wipeable_string> simple_wallet::on_device_pin_request()
|
||||||
{
|
{
|
||||||
#ifdef HAVE_READLINE
|
#ifdef HAVE_READLINE
|
||||||
rdln::suspend_readline pause_readline;
|
rdln::suspend_readline pause_readline;
|
||||||
|
@ -4699,14 +4699,14 @@ void simple_wallet::on_pin_request(epee::wipeable_string & pin)
|
||||||
std::string msg = tr("Enter device PIN");
|
std::string msg = tr("Enter device PIN");
|
||||||
auto pwd_container = tools::password_container::prompt(false, msg.c_str());
|
auto pwd_container = tools::password_container::prompt(false, msg.c_str());
|
||||||
THROW_WALLET_EXCEPTION_IF(!pwd_container, tools::error::password_entry_failed, tr("Failed to read device PIN"));
|
THROW_WALLET_EXCEPTION_IF(!pwd_container, tools::error::password_entry_failed, tr("Failed to read device PIN"));
|
||||||
pin = pwd_container->password();
|
return pwd_container->password();
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
void simple_wallet::on_passphrase_request(bool on_device, epee::wipeable_string & passphrase)
|
boost::optional<epee::wipeable_string> simple_wallet::on_device_passphrase_request(bool on_device)
|
||||||
{
|
{
|
||||||
if (on_device){
|
if (on_device){
|
||||||
message_writer(console_color_white, true) << tr("Please enter the device passphrase on the device");
|
message_writer(console_color_white, true) << tr("Please enter the device passphrase on the device");
|
||||||
return;
|
return boost::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_READLINE
|
#ifdef HAVE_READLINE
|
||||||
|
@ -4715,13 +4715,13 @@ void simple_wallet::on_passphrase_request(bool on_device, epee::wipeable_string
|
||||||
std::string msg = tr("Enter device passphrase");
|
std::string msg = tr("Enter device passphrase");
|
||||||
auto pwd_container = tools::password_container::prompt(false, msg.c_str());
|
auto pwd_container = tools::password_container::prompt(false, msg.c_str());
|
||||||
THROW_WALLET_EXCEPTION_IF(!pwd_container, tools::error::password_entry_failed, tr("Failed to read device passphrase"));
|
THROW_WALLET_EXCEPTION_IF(!pwd_container, tools::error::password_entry_failed, tr("Failed to read device passphrase"));
|
||||||
passphrase = pwd_container->password();
|
return pwd_container->password();
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
void simple_wallet::on_refresh_finished(uint64_t start_height, uint64_t fetched_blocks, bool is_init, bool received_money)
|
void simple_wallet::on_refresh_finished(uint64_t start_height, uint64_t fetched_blocks, bool is_init, bool received_money)
|
||||||
{
|
{
|
||||||
// Key image sync after the first refresh
|
// Key image sync after the first refresh
|
||||||
if (!m_wallet->get_account().get_device().has_tx_cold_sign()) {
|
if (!m_wallet->get_account().get_device().has_tx_cold_sign() || m_wallet->get_account().get_device().has_ki_live_refresh()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6752,7 +6752,7 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_)
|
||||||
{
|
{
|
||||||
std::vector<std::string> local_args = args_;
|
std::vector<std::string> local_args = args_;
|
||||||
|
|
||||||
if (m_wallet->key_on_device())
|
if (m_wallet->key_on_device() && m_wallet->get_account().get_device().get_type() != hw::device::TREZOR)
|
||||||
{
|
{
|
||||||
fail_msg_writer() << tr("command not supported by HW wallet");
|
fail_msg_writer() << tr("command not supported by HW wallet");
|
||||||
return true;
|
return true;
|
||||||
|
@ -6773,7 +6773,9 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_)
|
||||||
|
|
||||||
crypto::secret_key tx_key;
|
crypto::secret_key tx_key;
|
||||||
std::vector<crypto::secret_key> additional_tx_keys;
|
std::vector<crypto::secret_key> additional_tx_keys;
|
||||||
if (m_wallet->get_tx_key(txid, tx_key, additional_tx_keys))
|
|
||||||
|
bool found_tx_key = m_wallet->get_tx_key(txid, tx_key, additional_tx_keys);
|
||||||
|
if (found_tx_key)
|
||||||
{
|
{
|
||||||
ostringstream oss;
|
ostringstream oss;
|
||||||
oss << epee::string_tools::pod_to_hex(tx_key);
|
oss << epee::string_tools::pod_to_hex(tx_key);
|
||||||
|
@ -6849,7 +6851,7 @@ bool simple_wallet::set_tx_key(const std::vector<std::string> &args_)
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
bool simple_wallet::get_tx_proof(const std::vector<std::string> &args)
|
bool simple_wallet::get_tx_proof(const std::vector<std::string> &args)
|
||||||
{
|
{
|
||||||
if (m_wallet->key_on_device())
|
if (m_wallet->key_on_device() && m_wallet->get_account().get_device().get_type() != hw::device::TREZOR)
|
||||||
{
|
{
|
||||||
fail_msg_writer() << tr("command not supported by HW wallet");
|
fail_msg_writer() << tr("command not supported by HW wallet");
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -300,9 +300,9 @@ namespace cryptonote
|
||||||
virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index);
|
virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index);
|
||||||
virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx);
|
virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx);
|
||||||
virtual boost::optional<epee::wipeable_string> on_get_password(const char *reason);
|
virtual boost::optional<epee::wipeable_string> on_get_password(const char *reason);
|
||||||
virtual void on_button_request();
|
virtual void on_device_button_request(uint64_t code);
|
||||||
virtual void on_pin_request(epee::wipeable_string & pin);
|
virtual boost::optional<epee::wipeable_string> on_device_pin_request();
|
||||||
virtual void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase);
|
virtual boost::optional<epee::wipeable_string> on_device_passphrase_request(bool on_device);
|
||||||
//----------------------------------------------------------
|
//----------------------------------------------------------
|
||||||
|
|
||||||
friend class refresh_progress_reporter_t;
|
friend class refresh_progress_reporter_t;
|
||||||
|
|
|
@ -109,6 +109,23 @@ bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite)
|
||||||
}
|
}
|
||||||
|
|
||||||
m_wallet.pauseRefresh();
|
m_wallet.pauseRefresh();
|
||||||
|
|
||||||
|
const bool tx_cold_signed = m_wallet.m_wallet->get_account().get_device().has_tx_cold_sign();
|
||||||
|
if (tx_cold_signed){
|
||||||
|
std::unordered_set<size_t> selected_transfers;
|
||||||
|
for(const tools::wallet2::pending_tx & ptx : m_pending_tx){
|
||||||
|
for(size_t s : ptx.selected_transfers){
|
||||||
|
selected_transfers.insert(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_wallet.m_wallet->cold_tx_aux_import(m_pending_tx, m_tx_device_aux);
|
||||||
|
bool r = m_wallet.m_wallet->import_key_images(m_key_images, 0, selected_transfers);
|
||||||
|
if (!r){
|
||||||
|
throw runtime_error("Cold sign transaction submit failed - key image sync fail");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (!m_pending_tx.empty()) {
|
while (!m_pending_tx.empty()) {
|
||||||
auto & ptx = m_pending_tx.back();
|
auto & ptx = m_pending_tx.back();
|
||||||
m_wallet.m_wallet->commit_tx(ptx);
|
m_wallet.m_wallet->commit_tx(ptx);
|
||||||
|
|
|
@ -67,6 +67,8 @@ private:
|
||||||
std::string m_errorString;
|
std::string m_errorString;
|
||||||
std::vector<tools::wallet2::pending_tx> m_pending_tx;
|
std::vector<tools::wallet2::pending_tx> m_pending_tx;
|
||||||
std::unordered_set<crypto::public_key> m_signers;
|
std::unordered_set<crypto::public_key> m_signers;
|
||||||
|
std::vector<std::string> m_tx_device_aux;
|
||||||
|
std::vector<crypto::key_image> m_key_images;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -242,6 +242,42 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void on_device_button_request(uint64_t code)
|
||||||
|
{
|
||||||
|
if (m_listener) {
|
||||||
|
m_listener->onDeviceButtonRequest(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual boost::optional<epee::wipeable_string> on_device_pin_request()
|
||||||
|
{
|
||||||
|
if (m_listener) {
|
||||||
|
auto pin = m_listener->onDevicePinRequest();
|
||||||
|
if (pin){
|
||||||
|
return boost::make_optional(epee::wipeable_string((*pin).data(), (*pin).size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual boost::optional<epee::wipeable_string> on_device_passphrase_request(bool on_device)
|
||||||
|
{
|
||||||
|
if (m_listener) {
|
||||||
|
auto passphrase = m_listener->onDevicePassphraseRequest(on_device);
|
||||||
|
if (!on_device && passphrase) {
|
||||||
|
return boost::make_optional(epee::wipeable_string((*passphrase).data(), (*passphrase).size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void on_device_progress(const hw::device_progress & event)
|
||||||
|
{
|
||||||
|
if (m_listener) {
|
||||||
|
m_listener->onDeviceProgress(DeviceProgress(event.progress(), event.indeterminate()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WalletListener * m_listener;
|
WalletListener * m_listener;
|
||||||
WalletImpl * m_wallet;
|
WalletImpl * m_wallet;
|
||||||
};
|
};
|
||||||
|
@ -785,6 +821,28 @@ bool WalletImpl::setPassword(const std::string &password)
|
||||||
return status() == Status_Ok;
|
return status() == Status_Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WalletImpl::setDevicePin(const std::string &pin)
|
||||||
|
{
|
||||||
|
clearStatus();
|
||||||
|
try {
|
||||||
|
m_wallet->get_account().get_device().set_pin(epee::wipeable_string(pin.data(), pin.size()));
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
setStatusError(e.what());
|
||||||
|
}
|
||||||
|
return status() == Status_Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WalletImpl::setDevicePassphrase(const std::string &passphrase)
|
||||||
|
{
|
||||||
|
clearStatus();
|
||||||
|
try {
|
||||||
|
m_wallet->get_account().get_device().set_passphrase(epee::wipeable_string(passphrase.data(), passphrase.size()));
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
setStatusError(e.what());
|
||||||
|
}
|
||||||
|
return status() == Status_Ok;
|
||||||
|
}
|
||||||
|
|
||||||
std::string WalletImpl::address(uint32_t accountIndex, uint32_t addressIndex) const
|
std::string WalletImpl::address(uint32_t accountIndex, uint32_t addressIndex) const
|
||||||
{
|
{
|
||||||
return m_wallet->get_subaddress_as_str({accountIndex, addressIndex});
|
return m_wallet->get_subaddress_as_str({accountIndex, addressIndex});
|
||||||
|
@ -1428,6 +1486,8 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const
|
||||||
extra, subaddr_account, subaddr_indices);
|
extra, subaddr_account, subaddr_indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pendingTxPostProcess(transaction);
|
||||||
|
|
||||||
if (multisig().isMultisig) {
|
if (multisig().isMultisig) {
|
||||||
transaction->m_signers = m_wallet->make_multisig_tx_set(transaction->m_pending_tx).m_signers;
|
transaction->m_signers = m_wallet->make_multisig_tx_set(transaction->m_pending_tx).m_signers;
|
||||||
}
|
}
|
||||||
|
@ -1511,6 +1571,7 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction()
|
||||||
do {
|
do {
|
||||||
try {
|
try {
|
||||||
transaction->m_pending_tx = m_wallet->create_unmixable_sweep_transactions();
|
transaction->m_pending_tx = m_wallet->create_unmixable_sweep_transactions();
|
||||||
|
pendingTxPostProcess(transaction);
|
||||||
|
|
||||||
} catch (const tools::error::daemon_busy&) {
|
} catch (const tools::error::daemon_busy&) {
|
||||||
// TODO: make it translatable with "tr"?
|
// TODO: make it translatable with "tr"?
|
||||||
|
@ -2093,6 +2154,21 @@ bool WalletImpl::isNewWallet() const
|
||||||
return !(blockChainHeight() > 1 || m_recoveringFromSeed || m_recoveringFromDevice || m_rebuildWalletCache) && !watchOnly();
|
return !(blockChainHeight() > 1 || m_recoveringFromSeed || m_recoveringFromDevice || m_rebuildWalletCache) && !watchOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WalletImpl::pendingTxPostProcess(PendingTransactionImpl * pending)
|
||||||
|
{
|
||||||
|
// If the device being used is HW device with cold signing protocol, cold sign then.
|
||||||
|
if (!m_wallet->get_account().get_device().has_tx_cold_sign()){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tools::wallet2::signed_tx_set exported_txs;
|
||||||
|
std::vector<cryptonote::address_parse_info> dsts_info;
|
||||||
|
|
||||||
|
m_wallet->cold_sign_tx(pending->m_pending_tx, exported_txs, dsts_info, pending->m_tx_device_aux);
|
||||||
|
pending->m_key_images = exported_txs.key_images;
|
||||||
|
pending->m_pending_tx = exported_txs.ptx;
|
||||||
|
}
|
||||||
|
|
||||||
bool WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction_size_limit, bool ssl)
|
bool WalletImpl::doInit(const string &daemon_address, uint64_t upper_transaction_size_limit, bool ssl)
|
||||||
{
|
{
|
||||||
// claim RPC so there's no in-memory encryption for now
|
// claim RPC so there's no in-memory encryption for now
|
||||||
|
@ -2325,6 +2401,11 @@ bool WalletImpl::isKeysFileLocked()
|
||||||
{
|
{
|
||||||
return m_wallet->is_keys_file_locked();
|
return m_wallet->is_keys_file_locked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t WalletImpl::coldKeyImageSync(uint64_t &spent, uint64_t &unspent)
|
||||||
|
{
|
||||||
|
return m_wallet->cold_key_image_sync(spent, unspent);
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace Bitmonero = Monero;
|
namespace Bitmonero = Monero;
|
||||||
|
|
|
@ -89,6 +89,8 @@ public:
|
||||||
std::string errorString() const override;
|
std::string errorString() const override;
|
||||||
void statusWithErrorString(int& status, std::string& errorString) const override;
|
void statusWithErrorString(int& status, std::string& errorString) const override;
|
||||||
bool setPassword(const std::string &password) override;
|
bool setPassword(const std::string &password) override;
|
||||||
|
bool setDevicePin(const std::string &password) override;
|
||||||
|
bool setDevicePassphrase(const std::string &password) override;
|
||||||
std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const override;
|
std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const override;
|
||||||
std::string integratedAddress(const std::string &payment_id) const override;
|
std::string integratedAddress(const std::string &payment_id) const override;
|
||||||
std::string secretViewKey() const override;
|
std::string secretViewKey() const override;
|
||||||
|
@ -198,6 +200,7 @@ public:
|
||||||
virtual bool lockKeysFile() override;
|
virtual bool lockKeysFile() override;
|
||||||
virtual bool unlockKeysFile() override;
|
virtual bool unlockKeysFile() override;
|
||||||
virtual bool isKeysFileLocked() override;
|
virtual bool isKeysFileLocked() override;
|
||||||
|
virtual uint64_t coldKeyImageSync(uint64_t &spent, uint64_t &unspent) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void clearStatus() const;
|
void clearStatus() const;
|
||||||
|
@ -209,6 +212,7 @@ private:
|
||||||
bool daemonSynced() const;
|
bool daemonSynced() const;
|
||||||
void stopRefresh();
|
void stopRefresh();
|
||||||
bool isNewWallet() const;
|
bool isNewWallet() const;
|
||||||
|
void pendingTxPostProcess(PendingTransactionImpl * pending);
|
||||||
bool doInit(const std::string &daemon_address, uint64_t upper_transaction_size_limit = 0, bool ssl = false);
|
bool doInit(const std::string &daemon_address, uint64_t upper_transaction_size_limit = 0, bool ssl = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -324,6 +324,19 @@ struct MultisigState {
|
||||||
uint32_t total;
|
uint32_t total;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct DeviceProgress {
|
||||||
|
DeviceProgress(): m_progress(0), m_indeterminate(false) {}
|
||||||
|
DeviceProgress(double progress, bool indeterminate=false): m_progress(progress), m_indeterminate(indeterminate) {}
|
||||||
|
|
||||||
|
virtual double progress() const { return m_progress; }
|
||||||
|
virtual bool indeterminate() const { return m_indeterminate; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
double m_progress;
|
||||||
|
bool m_indeterminate;
|
||||||
|
};
|
||||||
|
|
||||||
struct WalletListener
|
struct WalletListener
|
||||||
{
|
{
|
||||||
virtual ~WalletListener() = 0;
|
virtual ~WalletListener() = 0;
|
||||||
|
@ -364,6 +377,31 @@ struct WalletListener
|
||||||
* @brief refreshed - called when wallet refreshed by background thread or explicitly refreshed by calling "refresh" synchronously
|
* @brief refreshed - called when wallet refreshed by background thread or explicitly refreshed by calling "refresh" synchronously
|
||||||
*/
|
*/
|
||||||
virtual void refreshed() = 0;
|
virtual void refreshed() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief called by device if the action is required
|
||||||
|
*/
|
||||||
|
virtual void onDeviceButtonRequest(uint64_t code) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief called by device when PIN is needed
|
||||||
|
*/
|
||||||
|
virtual optional<std::string> onDevicePinRequest() {
|
||||||
|
throw std::runtime_error("Not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief called by device when passphrase entry is needed
|
||||||
|
*/
|
||||||
|
virtual optional<std::string> onDevicePassphraseRequest(bool on_device) {
|
||||||
|
if (!on_device) throw std::runtime_error("Not supported");
|
||||||
|
return optional<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Signalizes device operation progress
|
||||||
|
*/
|
||||||
|
virtual void onDeviceProgress(const DeviceProgress & event) {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -375,7 +413,8 @@ struct Wallet
|
||||||
{
|
{
|
||||||
enum Device {
|
enum Device {
|
||||||
Device_Software = 0,
|
Device_Software = 0,
|
||||||
Device_Ledger = 1
|
Device_Ledger = 1,
|
||||||
|
Device_Trezor = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Status {
|
enum Status {
|
||||||
|
@ -401,6 +440,8 @@ struct Wallet
|
||||||
//! returns both error and error string atomically. suggested to use in instead of status() and errorString()
|
//! returns both error and error string atomically. suggested to use in instead of status() and errorString()
|
||||||
virtual void statusWithErrorString(int& status, std::string& errorString) const = 0;
|
virtual void statusWithErrorString(int& status, std::string& errorString) const = 0;
|
||||||
virtual bool setPassword(const std::string &password) = 0;
|
virtual bool setPassword(const std::string &password) = 0;
|
||||||
|
virtual bool setDevicePin(const std::string &password) { return false; };
|
||||||
|
virtual bool setDevicePassphrase(const std::string &password) { return false; };
|
||||||
virtual std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const = 0;
|
virtual std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const = 0;
|
||||||
std::string mainAddress() const { return address(0, 0); }
|
std::string mainAddress() const { return address(0, 0); }
|
||||||
virtual std::string path() const = 0;
|
virtual std::string path() const = 0;
|
||||||
|
@ -947,6 +988,9 @@ struct Wallet
|
||||||
* \return Device they are on
|
* \return Device they are on
|
||||||
*/
|
*/
|
||||||
virtual Device getDeviceType() const = 0;
|
virtual Device getDeviceType() const = 0;
|
||||||
|
|
||||||
|
//! cold-device protocol key image sync
|
||||||
|
virtual uint64_t coldKeyImageSync(uint64_t &spent, uint64_t &unspent) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -929,22 +929,30 @@ wallet_keys_unlocker::~wallet_keys_unlocker()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void wallet_device_callback::on_button_request()
|
void wallet_device_callback::on_button_request(uint64_t code)
|
||||||
{
|
{
|
||||||
if (wallet)
|
if (wallet)
|
||||||
wallet->on_button_request();
|
wallet->on_device_button_request(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
void wallet_device_callback::on_pin_request(epee::wipeable_string & pin)
|
boost::optional<epee::wipeable_string> wallet_device_callback::on_pin_request()
|
||||||
{
|
{
|
||||||
if (wallet)
|
if (wallet)
|
||||||
wallet->on_pin_request(pin);
|
return wallet->on_device_pin_request();
|
||||||
|
return boost::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
void wallet_device_callback::on_passphrase_request(bool on_device, epee::wipeable_string & passphrase)
|
boost::optional<epee::wipeable_string> wallet_device_callback::on_passphrase_request(bool on_device)
|
||||||
{
|
{
|
||||||
if (wallet)
|
if (wallet)
|
||||||
wallet->on_passphrase_request(on_device, passphrase);
|
return wallet->on_device_passphrase_request(on_device);
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wallet_device_callback::on_progress(const hw::device_progress& event)
|
||||||
|
{
|
||||||
|
if (wallet)
|
||||||
|
wallet->on_device_progress(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
|
wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
|
||||||
|
@ -2884,6 +2892,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
|
||||||
std::vector<parsed_block> parsed_blocks;
|
std::vector<parsed_block> parsed_blocks;
|
||||||
bool refreshed = false;
|
bool refreshed = false;
|
||||||
std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> output_tracker_cache;
|
std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> output_tracker_cache;
|
||||||
|
hw::device &hwdev = m_account.get_device();
|
||||||
|
|
||||||
// pull the first set of blocks
|
// pull the first set of blocks
|
||||||
get_short_chain_history(short_chain_history, (m_first_refresh_done || trusted_daemon) ? 1 : FIRST_REFRESH_GRANULARITY);
|
get_short_chain_history(short_chain_history, (m_first_refresh_done || trusted_daemon) ? 1 : FIRST_REFRESH_GRANULARITY);
|
||||||
|
@ -3040,6 +3049,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
|
||||||
LOG_PRINT_L1("Failed to check pending transactions");
|
LOG_PRINT_L1("Failed to check pending transactions");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hwdev.computing_key_images(false);
|
||||||
m_first_refresh_done = true;
|
m_first_refresh_done = true;
|
||||||
|
|
||||||
LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << ", balance (all accounts): " << print_money(balance_all()) << ", unlocked: " << print_money(unlocked_balance_all()));
|
LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << ", balance (all accounts): " << print_money(balance_all()) << ", unlocked: " << print_money(unlocked_balance_all()));
|
||||||
|
@ -9644,6 +9654,7 @@ void wallet2::cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_
|
||||||
hw::wallet_shim wallet_shim;
|
hw::wallet_shim wallet_shim;
|
||||||
setup_shim(&wallet_shim, this);
|
setup_shim(&wallet_shim, this);
|
||||||
aux_data.tx_recipients = dsts_info;
|
aux_data.tx_recipients = dsts_info;
|
||||||
|
aux_data.bp_version = use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1;
|
||||||
dev_cold->tx_sign(&wallet_shim, txs, exported_txs, aux_data);
|
dev_cold->tx_sign(&wallet_shim, txs, exported_txs, aux_data);
|
||||||
tx_device_aux = aux_data.tx_device_aux;
|
tx_device_aux = aux_data.tx_device_aux;
|
||||||
|
|
||||||
|
@ -9860,7 +9871,7 @@ void wallet2::discard_unmixable_outputs()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const
|
bool wallet2::get_tx_key_cached(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const
|
||||||
{
|
{
|
||||||
additional_tx_keys.clear();
|
additional_tx_keys.clear();
|
||||||
const std::unordered_map<crypto::hash, crypto::secret_key>::const_iterator i = m_tx_keys.find(txid);
|
const std::unordered_map<crypto::hash, crypto::secret_key>::const_iterator i = m_tx_keys.find(txid);
|
||||||
|
@ -9873,6 +9884,82 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
|
bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys)
|
||||||
|
{
|
||||||
|
bool r = get_tx_key_cached(txid, tx_key, additional_tx_keys);
|
||||||
|
if (r)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto & hwdev = get_account().get_device();
|
||||||
|
|
||||||
|
// So far only Cold protocol devices are supported.
|
||||||
|
if (hwdev.device_protocol() != hw::device::PROTOCOL_COLD)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto tx_data_it = m_tx_device.find(txid);
|
||||||
|
if (tx_data_it == m_tx_device.end())
|
||||||
|
{
|
||||||
|
MDEBUG("Aux data not found for txid: " << txid);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev);
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement cold signing interface");
|
||||||
|
if (!dev_cold->is_get_tx_key_supported())
|
||||||
|
{
|
||||||
|
MDEBUG("get_tx_key not supported by the device");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hw::device_cold::tx_key_data_t tx_key_data;
|
||||||
|
dev_cold->load_tx_key_data(tx_key_data, tx_data_it->second);
|
||||||
|
|
||||||
|
// Load missing tx prefix hash
|
||||||
|
if (tx_key_data.tx_prefix_hash.empty())
|
||||||
|
{
|
||||||
|
COMMAND_RPC_GET_TRANSACTIONS::request req;
|
||||||
|
COMMAND_RPC_GET_TRANSACTIONS::response res;
|
||||||
|
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
|
||||||
|
req.decode_as_json = false;
|
||||||
|
req.prune = true;
|
||||||
|
m_daemon_rpc_mutex.lock();
|
||||||
|
bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client);
|
||||||
|
m_daemon_rpc_mutex.unlock();
|
||||||
|
THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1),
|
||||||
|
error::wallet_internal_error, "Failed to get transaction from daemon");
|
||||||
|
|
||||||
|
cryptonote::transaction tx;
|
||||||
|
crypto::hash tx_hash{};
|
||||||
|
cryptonote::blobdata tx_data;
|
||||||
|
crypto::hash tx_prefix_hash{};
|
||||||
|
ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data);
|
||||||
|
THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
|
||||||
|
THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash),
|
||||||
|
error::wallet_internal_error, "Failed to validate transaction from daemon");
|
||||||
|
THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error,
|
||||||
|
"Failed to get the right transaction from daemon");
|
||||||
|
|
||||||
|
tx_key_data.tx_prefix_hash = std::string(tx_prefix_hash.data, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<crypto::secret_key> tx_keys;
|
||||||
|
dev_cold->get_tx_key(tx_keys, tx_key_data, m_account.get_keys().m_view_secret_key);
|
||||||
|
if (tx_keys.empty())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx_key = tx_keys[0];
|
||||||
|
tx_keys.erase(tx_keys.begin());
|
||||||
|
additional_tx_keys = tx_keys;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//----------------------------------------------------------------------------------------------------
|
||||||
void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys)
|
void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys)
|
||||||
{
|
{
|
||||||
// fetch tx from daemon and check if secret keys agree with corresponding public keys
|
// fetch tx from daemon and check if secret keys agree with corresponding public keys
|
||||||
|
@ -10275,7 +10362,8 @@ std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::ac
|
||||||
{
|
{
|
||||||
crypto::secret_key tx_key;
|
crypto::secret_key tx_key;
|
||||||
std::vector<crypto::secret_key> additional_tx_keys;
|
std::vector<crypto::secret_key> additional_tx_keys;
|
||||||
THROW_WALLET_EXCEPTION_IF(!get_tx_key(txid, tx_key, additional_tx_keys), error::wallet_internal_error, "Tx secret key wasn't found in the wallet file.");
|
bool found_tx_key = get_tx_key(txid, tx_key, additional_tx_keys);
|
||||||
|
THROW_WALLET_EXCEPTION_IF(!found_tx_key, error::wallet_internal_error, "Tx secret key wasn't found in the wallet file.");
|
||||||
|
|
||||||
const size_t num_sigs = 1 + additional_tx_keys.size();
|
const size_t num_sigs = 1 + additional_tx_keys.size();
|
||||||
shared_secret.resize(num_sigs);
|
shared_secret.resize(num_sigs);
|
||||||
|
@ -11448,29 +11536,48 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool wallet2::import_key_images(std::vector<crypto::key_image> key_images)
|
bool wallet2::import_key_images(std::vector<crypto::key_image> key_images, size_t offset, boost::optional<std::unordered_set<size_t>> selected_transfers)
|
||||||
{
|
{
|
||||||
if (key_images.size() > m_transfers.size())
|
if (key_images.size() + offset > m_transfers.size())
|
||||||
{
|
{
|
||||||
LOG_PRINT_L1("More key images returned that we know outputs for");
|
LOG_PRINT_L1("More key images returned that we know outputs for");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < key_images.size(); ++i)
|
for (size_t ki_idx = 0; ki_idx < key_images.size(); ++ki_idx)
|
||||||
{
|
{
|
||||||
transfer_details &td = m_transfers[i];
|
const size_t transfer_idx = ki_idx + offset;
|
||||||
if (td.m_key_image_known && !td.m_key_image_partial && td.m_key_image != key_images[i])
|
if (selected_transfers && selected_transfers.get().find(transfer_idx) == selected_transfers.get().end())
|
||||||
LOG_PRINT_L0("WARNING: imported key image differs from previously known key image at index " << i << ": trusting imported one");
|
continue;
|
||||||
td.m_key_image = key_images[i];
|
|
||||||
m_key_images[m_transfers[i].m_key_image] = i;
|
transfer_details &td = m_transfers[transfer_idx];
|
||||||
|
if (td.m_key_image_known && !td.m_key_image_partial && td.m_key_image != key_images[ki_idx])
|
||||||
|
LOG_PRINT_L0("WARNING: imported key image differs from previously known key image at index " << ki_idx << ": trusting imported one");
|
||||||
|
td.m_key_image = key_images[ki_idx];
|
||||||
|
m_key_images[td.m_key_image] = transfer_idx;
|
||||||
td.m_key_image_known = true;
|
td.m_key_image_known = true;
|
||||||
td.m_key_image_request = false;
|
td.m_key_image_request = false;
|
||||||
td.m_key_image_partial = false;
|
td.m_key_image_partial = false;
|
||||||
m_pub_keys[m_transfers[i].get_public_key()] = i;
|
m_pub_keys[td.get_public_key()] = transfer_idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool wallet2::import_key_images(signed_tx_set & signed_tx, size_t offset, bool only_selected_transfers)
|
||||||
|
{
|
||||||
|
std::unordered_set<size_t> selected_transfers;
|
||||||
|
if (only_selected_transfers)
|
||||||
|
{
|
||||||
|
for (const pending_tx & ptx : signed_tx.ptx)
|
||||||
|
{
|
||||||
|
for (const size_t s: ptx.selected_transfers)
|
||||||
|
selected_transfers.insert(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return import_key_images(signed_tx.key_images, offset, only_selected_transfers ? boost::make_optional(selected_transfers) : boost::none);
|
||||||
|
}
|
||||||
|
|
||||||
wallet2::payment_container wallet2::export_payments() const
|
wallet2::payment_container wallet2::export_payments() const
|
||||||
{
|
{
|
||||||
payment_container payments;
|
payment_container payments;
|
||||||
|
@ -12451,22 +12558,30 @@ wallet_device_callback * wallet2::get_device_callback()
|
||||||
}
|
}
|
||||||
return m_device_callback.get();
|
return m_device_callback.get();
|
||||||
}//----------------------------------------------------------------------------------------------------
|
}//----------------------------------------------------------------------------------------------------
|
||||||
void wallet2::on_button_request()
|
void wallet2::on_device_button_request(uint64_t code)
|
||||||
{
|
{
|
||||||
if (0 != m_callback)
|
if (nullptr != m_callback)
|
||||||
m_callback->on_button_request();
|
m_callback->on_device_button_request(code);
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
void wallet2::on_pin_request(epee::wipeable_string & pin)
|
boost::optional<epee::wipeable_string> wallet2::on_device_pin_request()
|
||||||
{
|
{
|
||||||
if (0 != m_callback)
|
if (nullptr != m_callback)
|
||||||
m_callback->on_pin_request(pin);
|
return m_callback->on_device_pin_request();
|
||||||
|
return boost::none;
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
void wallet2::on_passphrase_request(bool on_device, epee::wipeable_string & passphrase)
|
boost::optional<epee::wipeable_string> wallet2::on_device_passphrase_request(bool on_device)
|
||||||
{
|
{
|
||||||
if (0 != m_callback)
|
if (nullptr != m_callback)
|
||||||
m_callback->on_passphrase_request(on_device, passphrase);
|
return m_callback->on_device_passphrase_request(on_device);
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
//----------------------------------------------------------------------------------------------------
|
||||||
|
void wallet2::on_device_progress(const hw::device_progress& event)
|
||||||
|
{
|
||||||
|
if (nullptr != m_callback)
|
||||||
|
m_callback->on_device_progress(event);
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
std::string wallet2::get_rpc_status(const std::string &s) const
|
std::string wallet2::get_rpc_status(const std::string &s) const
|
||||||
|
|
|
@ -103,9 +103,10 @@ namespace tools
|
||||||
virtual void on_lw_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) {}
|
virtual void on_lw_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) {}
|
||||||
virtual void on_lw_money_spent(uint64_t height, const crypto::hash &txid, uint64_t amount) {}
|
virtual void on_lw_money_spent(uint64_t height, const crypto::hash &txid, uint64_t amount) {}
|
||||||
// Device callbacks
|
// Device callbacks
|
||||||
virtual void on_button_request() {}
|
virtual void on_device_button_request(uint64_t code) {}
|
||||||
virtual void on_pin_request(epee::wipeable_string & pin) {}
|
virtual boost::optional<epee::wipeable_string> on_device_pin_request() { return boost::none; }
|
||||||
virtual void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) {}
|
virtual boost::optional<epee::wipeable_string> on_device_passphrase_request(bool on_device) { return boost::none; }
|
||||||
|
virtual void on_device_progress(const hw::device_progress& event) {};
|
||||||
// Common callbacks
|
// Common callbacks
|
||||||
virtual void on_pool_tx_removed(const crypto::hash &txid) {}
|
virtual void on_pool_tx_removed(const crypto::hash &txid) {}
|
||||||
virtual ~i_wallet2_callback() {}
|
virtual ~i_wallet2_callback() {}
|
||||||
|
@ -115,9 +116,10 @@ namespace tools
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
wallet_device_callback(wallet2 * wallet): wallet(wallet) {};
|
wallet_device_callback(wallet2 * wallet): wallet(wallet) {};
|
||||||
void on_button_request() override;
|
void on_button_request(uint64_t code=0) override;
|
||||||
void on_pin_request(epee::wipeable_string & pin) override;
|
boost::optional<epee::wipeable_string> on_pin_request() override;
|
||||||
void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) override;
|
boost::optional<epee::wipeable_string> on_passphrase_request(bool on_device) override;
|
||||||
|
void on_progress(const hw::device_progress& event) override;
|
||||||
private:
|
private:
|
||||||
wallet2 * wallet;
|
wallet2 * wallet;
|
||||||
};
|
};
|
||||||
|
@ -1004,8 +1006,9 @@ namespace tools
|
||||||
const std::string & device_derivation_path() const { return m_device_derivation_path; }
|
const std::string & device_derivation_path() const { return m_device_derivation_path; }
|
||||||
void device_derivation_path(const std::string &device_derivation_path) { m_device_derivation_path = device_derivation_path; }
|
void device_derivation_path(const std::string &device_derivation_path) { m_device_derivation_path = device_derivation_path; }
|
||||||
|
|
||||||
bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const;
|
bool get_tx_key_cached(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const;
|
||||||
void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys);
|
void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys);
|
||||||
|
bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys);
|
||||||
void check_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations);
|
void check_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations);
|
||||||
void check_tx_key_helper(const crypto::hash &txid, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations);
|
void check_tx_key_helper(const crypto::hash &txid, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations);
|
||||||
std::string get_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message);
|
std::string get_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message);
|
||||||
|
@ -1127,7 +1130,8 @@ namespace tools
|
||||||
std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> export_key_images(bool all = false) const;
|
std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> export_key_images(bool all = false) const;
|
||||||
uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, size_t offset, uint64_t &spent, uint64_t &unspent, bool check_spent = true);
|
uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, size_t offset, uint64_t &spent, uint64_t &unspent, bool check_spent = true);
|
||||||
uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent);
|
uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent);
|
||||||
bool import_key_images(std::vector<crypto::key_image> key_images);
|
bool import_key_images(std::vector<crypto::key_image> key_images, size_t offset=0, boost::optional<std::unordered_set<size_t>> selected_transfers=boost::none);
|
||||||
|
bool import_key_images(signed_tx_set & signed_tx, size_t offset=0, bool only_selected_transfers=false);
|
||||||
crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;
|
crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;
|
||||||
|
|
||||||
void update_pool_state(bool refreshed = false);
|
void update_pool_state(bool refreshed = false);
|
||||||
|
@ -1333,9 +1337,10 @@ namespace tools
|
||||||
void create_keys_file(const std::string &wallet_, bool watch_only, const epee::wipeable_string &password, bool create_address_file);
|
void create_keys_file(const std::string &wallet_, bool watch_only, const epee::wipeable_string &password, bool create_address_file);
|
||||||
|
|
||||||
wallet_device_callback * get_device_callback();
|
wallet_device_callback * get_device_callback();
|
||||||
void on_button_request();
|
void on_device_button_request(uint64_t code);
|
||||||
void on_pin_request(epee::wipeable_string & pin);
|
boost::optional<epee::wipeable_string> on_device_pin_request();
|
||||||
void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase);
|
boost::optional<epee::wipeable_string> on_device_passphrase_request(bool on_device);
|
||||||
|
void on_device_progress(const hw::device_progress& event);
|
||||||
|
|
||||||
std::string get_rpc_status(const std::string &s) const;
|
std::string get_rpc_status(const std::string &s) const;
|
||||||
void throw_on_rpc_response_error(const boost::optional<std::string> &status, const char *method) const;
|
void throw_on_rpc_response_error(const boost::optional<std::string> &status, const char *method) const;
|
||||||
|
|
Loading…
Reference in New Issue