From a1fd1d499c6a967d2cd011c57eb79f6ebc5847f4 Mon Sep 17 00:00:00 2001 From: Dusan Klinec Date: Sat, 23 Feb 2019 15:28:18 +0100 Subject: 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 --- src/device_trezor/device_trezor.cpp | 360 +++++++++++++++++++++++++++++++++--- 1 file changed, 338 insertions(+), 22 deletions(-) (limited to 'src/device_trezor/device_trezor.cpp') 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 device_trezor::get_address( const boost::optional> & path, const boost::optional & 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 device_trezor::get_view_key( const boost::optional> & path, const boost::optional & 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(req.get()); + + auto response = this->client_exchange(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 & 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(req.get()); auto ack1 = this->client_exchange(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(); auto final_ack = this->client_exchange(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(final_ack->enc_key().data()), - reinterpret_cast(sub.iv().data()), buff); + reinterpret_cast(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(); + this->set_msg_addr(req.get()); + this->client_exchange(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(); + 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(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(); + this->client_exchange(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(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(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 & 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(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(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(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(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(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(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(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(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(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(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(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(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 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(); -- cgit v1.2.3