aboutsummaryrefslogtreecommitdiff
path: root/src/device_trezor
diff options
context:
space:
mode:
Diffstat (limited to 'src/device_trezor')
-rw-r--r--src/device_trezor/device_trezor.cpp360
-rw-r--r--src/device_trezor/device_trezor.hpp98
-rw-r--r--src/device_trezor/device_trezor_base.cpp51
-rw-r--r--src/device_trezor/device_trezor_base.hpp30
-rw-r--r--src/device_trezor/trezor/protocol.cpp439
-rw-r--r--src/device_trezor/trezor/protocol.hpp39
6 files changed, 832 insertions, 185 deletions
diff --git a/src/device_trezor/device_trezor.cpp b/src/device_trezor/device_trezor.cpp
index e1b079044..0c3f1bd31 100644
--- a/src/device_trezor/device_trezor.cpp
+++ b/src/device_trezor/device_trezor.cpp
@@ -57,7 +57,9 @@ namespace 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() {
@@ -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 */
/* ======================================================================= */
@@ -126,7 +211,7 @@ namespace trezor {
std::shared_ptr<messages::monero::MoneroAddress> device_trezor::get_address(
const boost::optional<std::vector<uint32_t>> & path,
const boost::optional<cryptonote::network_type> & network_type){
- AUTO_LOCK_CMD();
+ TREZOR_AUTO_LOCK_CMD();
require_connected();
device_state_reset_unsafe();
require_initialized();
@@ -142,7 +227,7 @@ namespace trezor {
std::shared_ptr<messages::monero::MoneroWatchKey> device_trezor::get_view_key(
const boost::optional<std::vector<uint32_t>> & path,
const boost::optional<cryptonote::network_type> & network_type){
- AUTO_LOCK_CMD();
+ TREZOR_AUTO_LOCK_CMD();
require_connected();
device_state_reset_unsafe();
require_initialized();
@@ -155,11 +240,43 @@ namespace trezor {
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,
const std::vector<tools::wallet2::transfer_details> & transfers,
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();
device_state_reset_unsafe();
require_initialized();
@@ -171,6 +288,7 @@ namespace trezor {
protocol::ki::key_image_data(wallet, transfers, mtds);
protocol::ki::generate_commitment(mtds, transfers, req);
+ EVENT_PROGRESS(0.);
this->set_msg_addr<messages::monero::MoneroKeyImageExportInitRequest>(req.get());
auto ack1 = this->client_exchange<messages::monero::MoneroKeyImageExportInitAck>(req);
@@ -194,27 +312,160 @@ namespace trezor {
}
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_ack = this->client_exchange<messages::monero::MoneroKeyImageSyncFinalAck>(final_req);
ski.reserve(kis.size());
for(auto & sub : kis){
- char buff[32*3];
+ ::crypto::signature sig{};
+ ::crypto::key_image ki;
+ char buff[sizeof(ki.data)*3];
+
+ 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);
+ reinterpret_cast<const uint8_t *>(sub.iv().data()), buff, &buff_len);
+ CHECK_AND_ASSERT_THROW_MES(buff_len == sizeof(buff), "Plaintext size invalid");
- ::crypto::signature sig{};
- ::crypto::key_image ki;
- memcpy(ki.data, buff, 32);
- memcpy(sig.c.data, buff + 32, 32);
- memcpy(sig.r.data, buff + 64, 32);
+ 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));
}
+#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,
const tools::wallet2::unsigned_tx_set & unsigned_tx,
@@ -222,7 +473,15 @@ namespace trezor {
hw::tx_aux_data & aux_data)
{
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.resize(unsigned_tx.transfers.second.size());
@@ -267,6 +526,10 @@ namespace trezor {
cpend.key_images = key_images;
// 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();
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");
@@ -276,12 +539,19 @@ namespace trezor {
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];
- 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");
+
+ const auto vini = boost::get<cryptonote::txin_to_key>(cdata.tx.vin[src_idx]);
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,
@@ -290,10 +560,16 @@ namespace trezor {
hw::tx_aux_data & aux_data,
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();
- device_state_reset_unsafe();
+ if (idx > 0)
+ device_state_reset_unsafe();
+
require_initialized();
+ EVENT_PROGRESS(0, 1, 1);
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);
@@ -305,6 +581,7 @@ namespace trezor {
auto init_msg = signer->step_init();
this->set_msg_addr(init_msg.get());
transaction_pre_check(init_msg);
+ EVENT_PROGRESS(1, 1, 1);
auto response = this->client_exchange<messages::monero::MoneroTransactionInitAck>(init_msg);
signer->step_init_ack(response);
@@ -314,6 +591,7 @@ namespace trezor {
auto src = signer->step_set_input(cur_src);
auto ack = this->client_exchange<messages::monero::MoneroTransactionSetInputAck>(src);
signer->step_set_input_ack(ack);
+ EVENT_PROGRESS(2, cur_src, num_sources);
}
// Step: sort
@@ -322,44 +600,82 @@ namespace trezor {
auto perm_ack = this->client_exchange<messages::monero::MoneroTransactionInputsPermutationAck>(perm_req);
signer->step_permutation_ack(perm_ack);
}
+ EVENT_PROGRESS(3, 1, 1);
// Step: input_vini
- if (!signer->in_memory()){
- for(size_t cur_src = 0; cur_src < num_sources; ++cur_src){
- auto src = signer->step_set_vini_input(cur_src);
- auto ack = this->client_exchange<messages::monero::MoneroTransactionInputViniAck>(src);
- signer->step_set_vini_input_ack(ack);
- }
+ for(size_t cur_src = 0; cur_src < num_sources; ++cur_src){
+ auto src = signer->step_set_vini_input(cur_src);
+ auto ack = this->client_exchange<messages::monero::MoneroTransactionInputViniAck>(src);
+ signer->step_set_vini_input_ack(ack);
+ EVENT_PROGRESS(4, cur_src, num_sources);
}
// 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);
signer->step_all_inputs_set_ack(ack_all_inputs);
+ EVENT_PROGRESS(5, 1, 1);
// Step: outputs
for(size_t cur_dst = 0; cur_dst < num_outputs; ++cur_dst){
auto src = signer->step_set_output(cur_dst);
auto ack = this->client_exchange<messages::monero::MoneroTransactionSetOutputAck>(src);
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
auto all_out_set = signer->step_all_outs_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);
+ EVENT_PROGRESS(7, 1, 1);
// Step: sign each input
for(size_t cur_src = 0; cur_src < num_sources; ++cur_src){
auto src = signer->step_sign_input(cur_src);
auto ack_sign = this->client_exchange<messages::monero::MoneroTransactionSignInputAck>(src);
signer->step_sign_input_ack(ack_sign);
+ EVENT_PROGRESS(8, cur_src, num_sources);
}
// Step: final
auto final_msg = signer->step_final();
auto ack_final = this->client_exchange<messages::monero::MoneroTransactionFinalAck>(final_msg);
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)
@@ -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 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){
const std::string & payment_id = tdata.tsx_data.payment_id();
diff --git a/src/device_trezor/device_trezor.hpp b/src/device_trezor/device_trezor.hpp
index 1f08be887..24960cc1f 100644
--- a/src/device_trezor/device_trezor.hpp
+++ b/src/device_trezor/device_trezor.hpp
@@ -30,18 +30,21 @@
#ifndef MONERO_DEVICE_TREZOR_H
#define MONERO_DEVICE_TREZOR_H
+#include "trezor.hpp"
+#include "device/device.hpp"
+#ifdef WITH_DEVICE_TREZOR
#include <cstddef>
#include <string>
-#include "device/device.hpp"
-#include "device/device_default.hpp"
-#include "device/device_cold.hpp"
#include <boost/scope_exit.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/recursive_mutex.hpp>
+
+#include "device/device_default.hpp"
+#include "device/device_cold.hpp"
#include "cryptonote_config.h"
-#include "trezor.hpp"
#include "device_trezor_base.hpp"
+#endif
namespace hw {
namespace trezor {
@@ -57,8 +60,29 @@ namespace trezor {
*/
class device_trezor : public hw::trezor::device_trezor_base, public hw::device_cold {
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_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:
device_trezor();
@@ -69,11 +93,17 @@ namespace trezor {
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; };
bool has_ki_cold_sync() 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_live_refresh_enabled(bool enabled) { m_live_refresh_enabled = enabled; }
+ bool live_refresh_enabled() const { return m_live_refresh_enabled; }
/* ======================================================================= */
/* WALLET & ADDRESS */
@@ -100,20 +130,68 @@ namespace trezor {
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.
*/
void ki_sync(wallet_shim * wallet,
const std::vector<::tools::wallet2::transfer_details> & transfers,
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,
- const ::tools::wallet2::unsigned_tx_set & unsigned_tx,
- size_t idx,
- hw::tx_aux_data & aux_data,
- std::shared_ptr<protocol::tx::Signer> & signer);
+ void computing_key_images(bool started) override;
+
+ /**
+ * Implements hw::device interface
+ * 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.
diff --git a/src/device_trezor/device_trezor_base.cpp b/src/device_trezor/device_trezor_base.cpp
index 1f9395622..66a00c213 100644
--- a/src/device_trezor/device_trezor_base.cpp
+++ b/src/device_trezor/device_trezor_base.cpp
@@ -109,6 +109,7 @@ namespace trezor {
disconnect();
// Enumerate all available devices
+ TREZOR_AUTO_LOCK_DEVICE();
try {
hw::trezor::t_transport_vect trans;
@@ -145,6 +146,7 @@ namespace trezor {
}
bool device_trezor_base::disconnect() {
+ TREZOR_AUTO_LOCK_DEVICE();
m_device_state.clear();
m_features.reset();
@@ -203,13 +205,13 @@ namespace trezor {
/* Helpers */
/* ======================================================================= */
- void device_trezor_base::require_connected(){
+ void device_trezor_base::require_connected() const {
if (!m_transport){
throw exc::NotConnectedException();
}
}
- void device_trezor_base::require_initialized(){
+ void device_trezor_base::require_initialized() const {
if (!m_features){
throw exc::TrezorException("Device state not initialized");
}
@@ -330,7 +332,7 @@ namespace trezor {
/* ======================================================================= */
bool device_trezor_base::ping() {
- AUTO_LOCK_CMD();
+ TREZOR_AUTO_LOCK_CMD();
if (!m_transport){
MINFO("Ping failed, device not connected");
return false;
@@ -364,7 +366,7 @@ namespace trezor {
void device_trezor_base::device_state_reset()
{
- AUTO_LOCK_CMD();
+ TREZOR_AUTO_LOCK_CMD();
device_state_reset_unsafe();
}
@@ -373,6 +375,10 @@ namespace trezor {
if (m_debug_callback) m_debug_callback->method(__VA_ARGS__); \
if (m_callback) m_callback->method(__VA_ARGS__); \
}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(){
if (!m_debug){
@@ -392,6 +398,7 @@ namespace trezor {
#else
#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
void device_trezor_base::on_button_request(GenericMessage & resp, const messages::common::ButtonRequest * msg)
@@ -402,7 +409,7 @@ namespace trezor {
messages::common::ButtonAck ack;
write_raw(&ack);
- TREZOR_CALLBACK(on_button_request);
+ TREZOR_CALLBACK(on_button_request, msg->code());
resp = read_raw();
}
@@ -411,13 +418,18 @@ namespace trezor {
MDEBUG("on_pin_request");
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
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);
}
@@ -425,14 +437,19 @@ namespace trezor {
{
CHECK_AND_ASSERT_THROW_MES(msg, "Empty message");
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;
- if (!msg->on_device()){
+ if (!msg->on_device() && passphrase){
// 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()){
@@ -494,16 +511,16 @@ namespace trezor {
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();
}
- 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) {
diff --git a/src/device_trezor/device_trezor_base.hpp b/src/device_trezor/device_trezor_base.hpp
index 7b9d92b7e..d0535a137 100644
--- a/src/device_trezor/device_trezor_base.hpp
+++ b/src/device_trezor/device_trezor_base.hpp
@@ -47,14 +47,15 @@
#endif
//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*/ \
boost::lock(device_locker, command_locker); \
/* 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::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 trezor {
@@ -62,14 +63,14 @@ namespace trezor {
class device_trezor_base;
#ifdef WITH_TREZOR_DEBUGGING
- class trezor_debug_callback {
+ class trezor_debug_callback : public hw::i_device_callback {
public:
trezor_debug_callback()=default;
explicit trezor_debug_callback(std::shared_ptr<Transport> & debug_transport);
- void on_button_request();
- void on_pin_request(epee::wipeable_string &pin);
- void on_passphrase_request(bool on_device, epee::wipeable_string &passphrase);
+ void on_button_request(uint64_t code=0) override;
+ boost::optional<epee::wipeable_string> on_pin_request() override;
+ boost::optional<epee::wipeable_string> on_passphrase_request(bool on_device) override;
void on_passphrase_state_request(const std::string &state);
void on_disconnect();
protected:
@@ -95,6 +96,8 @@ namespace trezor {
std::vector<unsigned int> m_wallet_deriv_path;
std::string m_device_state; // returned after passphrase entry, session
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;
@@ -109,11 +112,11 @@ namespace trezor {
// Internal methods
//
- void require_connected();
- void require_initialized();
+ void require_connected() const;
+ void require_initialized() const;
void call_ping_unsafe();
void test_ping();
- void device_state_reset_unsafe();
+ virtual void device_state_reset_unsafe();
void ensure_derivation_path() noexcept;
// Communication methods
@@ -265,6 +268,15 @@ namespace trezor {
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 */
/* ======================================================================= */
diff --git a/src/device_trezor/trezor/protocol.cpp b/src/device_trezor/trezor/protocol.cpp
index 13506a67f..b241987d3 100644
--- a/src/device_trezor/trezor/protocol.cpp
+++ b/src/device_trezor/trezor/protocol.cpp
@@ -33,6 +33,8 @@
#include <utility>
#include <boost/endian/conversion.hpp>
#include <common/apply_permutation.h>
+#include <common/json_util.h>
+#include <crypto/hmac-keccak.h>
#include <ringct/rctSigs.h>
#include <ringct/bulletproofs.h>
#include "cryptonote_config.h"
@@ -40,6 +42,37 @@
#include <sodium/crypto_verify_32.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 trezor{
namespace protocol{
@@ -84,19 +117,22 @@ namespace protocol{
namespace crypto {
namespace chacha {
- void decrypt(const void* ciphertext, size_t length, const uint8_t* key, const uint8_t* iv, char* plaintext){
- if (length < 16){
- throw std::invalid_argument("Ciphertext length too small");
- }
+ void decrypt(const void* ciphertext, size_t length, const uint8_t* key, const uint8_t* iv, char* plaintext, size_t *plaintext_len){
+ CHECK_AND_ASSERT_THROW_MES(length >= TAG_SIZE, "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(
- 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);
if (r != 0){
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
@@ -198,6 +277,8 @@ namespace tx {
void translate_dst_entry(MoneroTransactionDestinationEntry * dst, const cryptonote::tx_destination_entry * src){
dst->set_amount(src->amount);
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));
}
@@ -267,9 +348,29 @@ namespace tx {
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() {
- in_memory = false;
rsig_type = 0;
+ bp_version = 0;
cur_input_idx = 0;
cur_output_idx = 0;
cur_batch_idx = 0;
@@ -283,6 +384,7 @@ namespace tx {
m_tx_idx = tx_idx;
m_ct.tx_data = cur_tx();
m_multisig = false;
+ m_client_version = 1;
}
void Signer::extract_payment_id(){
@@ -392,8 +494,10 @@ namespace tx {
m_ct.tx.version = 2;
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_client_version(client_version());
tsx_data.set_unlock_time(tx.unlock_time);
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));
@@ -404,6 +508,10 @@ namespace tx {
auto rsig_data = tsx_data.mutable_rsig_data();
m_ct.rsig_type = get_rsig_type(tx.use_bulletproofs, tx.splitted_dsts.size());
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());
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){
- m_ct.in_memory = false;
if (ack->has_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(){
sort_ki();
- if (in_memory()){
- return nullptr;
- }
-
auto res = std::make_shared<messages::monero::MoneroTransactionInputsPermutationRequest>();
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){
- if (in_memory()){
- return;
- }
+
}
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.vin.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]));
res->set_vini(cryptonote::t_serializable_object_to_blob(vini));
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_hmac.size(), "Invalid transaction index");
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){
- if (in_memory()){
- return;
- }
+
}
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){
- if (is_offloading()){
- // If offloading, expect rsig configuration.
- if (!ack->has_rsig_data()){
- throw exc::ProtocolException("Rsig offloading requires rsig param");
- }
+ if (client_version() > 0 || !is_offloading()){
+ return;
+ }
- auto & rsig_data = ack->rsig_data();
- if (!rsig_data.has_mask()){
- throw exc::ProtocolException("Gamma masks not present in offloaded version");
- }
+ // If offloading, expect rsig configuration.
+ if (!ack->has_rsig_data()){
+ throw exc::ProtocolException("Rsig offloading requires rsig param");
+ }
- auto & mask = rsig_data.mask();
- if (mask.size() != 32 * num_outputs()){
- throw exc::ProtocolException("Invalid number of gamma masks");
- }
+ auto & rsig_data = ack->rsig_data();
+ if (!rsig_data.has_mask()){
+ throw exc::ProtocolException("Gamma masks not present in offloaded version");
+ }
- 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);
- }
+ auto & mask = rsig_data.mask();
+ if (mask.size() != 32 * num_outputs()){
+ throw exc::ProtocolException("Invalid number of gamma masks");
+ }
+
+ 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){
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(is_req_bulletproof(), "Borromean rsig not supported");
m_ct.cur_output_idx = idx;
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]);
// Range sig offloading to the host
- if (!is_offloading()) {
- return res;
- }
-
- CHECK_AND_ASSERT_THROW_MES(m_ct.cur_batch_idx < m_ct.grouping_vct.size(), "Invalid batch index");
- 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);
+ // ClientV0 sends offloaded BP with the last message in the batch.
+ // 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();
+ compute_bproof(*rsig_data);
}
return res;
@@ -644,7 +707,6 @@ namespace tx {
void Signer::step_set_output_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetOutputAck> ack){
cryptonote::tx_out tx_out;
- rct::rangeSig range_sig{};
rct::Bulletproof bproof{};
rct::ctkey out_pk{};
rct::ecdhTuple ecdh{};
@@ -658,12 +720,12 @@ namespace tx {
if (rsig_data.has_rsig() && !rsig_data.rsig().empty()){
has_rsig = true;
rsig_buff = rsig_data.rsig();
+ }
- } else if (rsig_data.rsig_parts_size() > 0){
- has_rsig = true;
- for (const auto &it : rsig_data.rsig_parts()) {
- rsig_buff += it;
- }
+ if (client_version() >= 1 && rsig_data.has_mask()){
+ rct::key cmask{};
+ string_to_key(cmask, rsig_data.mask());
+ m_ct.rsig_gamma.emplace_back(cmask);
}
}
@@ -675,12 +737,13 @@ namespace tx {
throw exc::ProtocolException("Cannot deserialize out_pk");
}
- 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)){
- throw exc::ProtocolException("Cannot deserialize rangesig");
+ if (m_ct.bp_version <= 1) {
+ if (!cn_deserialize(ack->ecdh_info(), ecdh)){
+ throw exc::ProtocolException("Cannot deserialize ecdhtuple");
+ }
+ } else {
+ 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)){
@@ -692,35 +755,77 @@ namespace tx {
m_ct.tx_out_pk.emplace_back(out_pk);
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;
}
- if (is_req_bulletproof()){
- 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");
+ process_bproof(bproof);
+ m_ct.cur_batch_idx += 1;
+ m_ct.cur_output_in_batch_idx = 0;
+ }
- rct::key commitment = m_ct.tx_out_pk[bidx].mask;
- commitment = rct::scalarmultKey(commitment, rct::INV_EIGHT);
- bproof.V.push_back(commitment);
- }
+ bool Signer::should_compute_bp_now() const {
+ CHECK_AND_ASSERT_THROW_MES(m_ct.cur_batch_idx < m_ct.grouping_vct.size(), "Invalid batch index");
+ 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);
- if (!rct::bulletproof_VERIFY(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs.back()))) {
- throw exc::ProtocolException("Returned range signature is invalid");
- }
+ void Signer::compute_bproof(messages::monero::MoneroTransactionRsigData & rsig_data){
+ auto batch_size = m_ct.grouping_vct[m_ct.cur_batch_idx];
+ 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 {
- m_ct.tx_out_rsigs.emplace_back(range_sig);
+ 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_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()))) {
- throw exc::ProtocolException("Returned range signature is invalid");
- }
+ 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);
+ }
+
+ 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_output_in_batch_idx = 0;
}
@@ -814,12 +919,11 @@ namespace tx {
res->set_vini_hmac(m_ct.tx_in_hmacs[idx]);
res->set_pseudo_out_alpha(m_ct.alphas[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_hmac.size(), "Invalid transaction index");
- res->set_pseudo_out(m_ct.pseudo_outs[idx]);
- res->set_pseudo_out_hmac(m_ct.pseudo_outs_hmac[idx]);
- }
+
+ 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");
+ res->set_pseudo_out(m_ct.pseudo_outs[idx]);
+ res->set_pseudo_out_hmac(m_ct.pseudo_outs_hmac[idx]);
return res;
}
@@ -829,6 +933,19 @@ namespace tx {
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);
}
@@ -841,14 +958,14 @@ namespace tx {
if (m_multisig){
auto & cout_key = ack->cout_key();
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");
}
char buff[32];
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);
}
}
@@ -887,6 +1004,82 @@ namespace tx {
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);
+ }
}
}
diff --git a/src/device_trezor/trezor/protocol.hpp b/src/device_trezor/trezor/protocol.hpp
index ce0361640..42d523dad 100644
--- a/src/device_trezor/trezor/protocol.hpp
+++ b/src/device_trezor/trezor/protocol.hpp
@@ -92,11 +92,14 @@ namespace protocol{
// Crypto / encryption
namespace crypto {
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.
*/
- 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,
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
@@ -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 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);
+ ::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;
@@ -164,8 +176,8 @@ namespace tx {
TsxData tsx_data;
tx_construction_data tx_data;
cryptonote::transaction tx;
- bool in_memory;
unsigned rsig_type;
+ int bp_version;
std::vector<uint64_t> grouping_vct;
std::shared_ptr<MoneroRsigData> rsig_param;
size_t cur_input_idx;
@@ -206,6 +218,7 @@ namespace tx {
const unsigned_tx_set * m_unsigned_tx;
hw::tx_aux_data * m_aux_data;
+ unsigned m_client_version;
bool m_multisig;
const tx_construction_data & cur_tx(){
@@ -215,6 +228,9 @@ namespace tx {
void extract_payment_id();
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:
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);
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();
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();
- bool in_memory() const {
- return m_ct.in_memory;
+ unsigned client_version() const {
+ return m_client_version;
}
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
+ );
}
}