aboutsummaryrefslogtreecommitdiff
path: root/src/device_trezor/device_trezor.cpp
diff options
context:
space:
mode:
authorDusan Klinec <dusan.klinec@gmail.com>2019-02-23 15:28:18 +0100
committerDusan Klinec <dusan.klinec@gmail.com>2019-03-20 21:11:02 +0100
commita1fd1d499c6a967d2cd011c57eb79f6ebc5847f4 (patch)
tree9bb84d4da61e0a2c4c5552d27452397b8248cb88 /src/device_trezor/device_trezor.cpp
parentcrypto: hmac_keccak added (diff)
downloadmonero-a1fd1d499c6a967d2cd011c57eb79f6ebc5847f4.tar.xz
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
Diffstat (limited to 'src/device_trezor/device_trezor.cpp')
-rw-r--r--src/device_trezor/device_trezor.cpp360
1 files changed, 338 insertions, 22 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();