// Copyright (c) 2017-2019, The Monero Project // // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other // materials provided with the distribution. // // 3. Neither the name of the copyright holder nor the names of its contributors may be // used to endorse or promote products derived from this software without specific // prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // #include "device_trezor.hpp" namespace hw { namespace trezor { #ifdef WITH_DEVICE_TREZOR #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "device.trezor" #define HW_TREZOR_NAME "Trezor" static device_trezor *trezor_device = nullptr; static device_trezor *ensure_trezor_device(){ if (!trezor_device) { trezor_device = new device_trezor(); trezor_device->set_name(HW_TREZOR_NAME); } return trezor_device; } void register_all(std::map> ®istry) { registry.insert(std::make_pair(HW_TREZOR_NAME, std::unique_ptr(ensure_trezor_device()))); } void register_all() { hw::register_device(HW_TREZOR_NAME, ensure_trezor_device()); } device_trezor::device_trezor() { } device_trezor::~device_trezor() { try { disconnect(); release(); } catch(std::exception const& e){ MWARNING("Could not disconnect and release: " << e.what()); } } /* ======================================================================= */ /* WALLET & ADDRESS */ /* ======================================================================= */ bool device_trezor::get_public_address(cryptonote::account_public_address &pubkey) { try { auto res = get_address(); cryptonote::address_parse_info info{}; bool r = cryptonote::get_account_address_from_str(info, this->network_type, res->address()); CHECK_AND_ASSERT_MES(r, false, "Could not parse returned address. Address parse failed: " + res->address()); CHECK_AND_ASSERT_MES(!info.is_subaddress, false, "Trezor returned a sub address"); pubkey = info.address; return true; } catch(std::exception const& e){ MERROR("Get public address exception: " << e.what()); return false; } } bool device_trezor::get_secret_keys(crypto::secret_key &viewkey , crypto::secret_key &spendkey) { try { MDEBUG("Loading view-only key from the Trezor. Please check the Trezor for a confirmation."); auto res = get_view_key(); CHECK_AND_ASSERT_MES(res->watch_key().size() == 32, false, "Trezor returned invalid view key"); spendkey = crypto::null_skey; // not given memcpy(viewkey.data, res->watch_key().data(), 32); return true; } catch(std::exception const& e){ MERROR("Get secret keys exception: " << e.what()); return false; } } /* ======================================================================= */ /* Helpers */ /* ======================================================================= */ /* ======================================================================= */ /* TREZOR PROTOCOL */ /* ======================================================================= */ std::shared_ptr device_trezor::get_address( const boost::optional> & path, const boost::optional & network_type){ AUTO_LOCK_CMD(); require_connected(); device_state_reset_unsafe(); require_initialized(); auto req = std::make_shared(); this->set_msg_addr(req.get(), path, network_type); auto response = this->client_exchange(req); MTRACE("Get address response received"); return response; } std::shared_ptr device_trezor::get_view_key( const boost::optional> & path, const boost::optional & network_type){ AUTO_LOCK_CMD(); require_connected(); device_state_reset_unsafe(); require_initialized(); auto req = std::make_shared(); this->set_msg_addr(req.get(), path, network_type); auto response = this->client_exchange(req); MTRACE("Get watch key response received"); return response; } void device_trezor::ki_sync(wallet_shim * wallet, const std::vector & transfers, hw::device_cold::exported_key_image & ski) { AUTO_LOCK_CMD(); require_connected(); device_state_reset_unsafe(); require_initialized(); std::shared_ptr req; std::vector mtds; std::vector kis; protocol::ki::key_image_data(wallet, transfers, mtds); protocol::ki::generate_commitment(mtds, transfers, req); this->set_msg_addr(req.get()); auto ack1 = this->client_exchange(req); const auto batch_size = 10; const auto num_batches = (mtds.size() + batch_size - 1) / batch_size; for(uint64_t cur = 0; cur < num_batches; ++cur){ auto step_req = std::make_shared(); auto idx_finish = std::min(static_cast((cur + 1) * batch_size), static_cast(mtds.size())); for(uint64_t idx = cur * batch_size; idx < idx_finish; ++idx){ auto added_tdis = step_req->add_tdis(); CHECK_AND_ASSERT_THROW_MES(idx < mtds.size(), "Invalid transfer detail index"); *added_tdis = mtds[idx]; } auto step_ack = this->client_exchange(step_req); auto kis_size = step_ack->kis_size(); kis.reserve(static_cast(kis_size)); for(int i = 0; i < kis_size; ++i){ auto ckis = step_ack->kis(i); kis.push_back(ckis); } MTRACE("Batch " << cur << " / " << num_batches << " batches processed"); } 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]; protocol::crypto::chacha::decrypt(sub.blob().data(), sub.blob().size(), reinterpret_cast(final_ack->enc_key().data()), reinterpret_cast(sub.iv().data()), buff); ::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); ski.push_back(std::make_pair(ki, sig)); } } void device_trezor::tx_sign(wallet_shim * wallet, const tools::wallet2::unsigned_tx_set & unsigned_tx, tools::wallet2::signed_tx_set & signed_tx, 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(); signed_tx.key_images.clear(); signed_tx.key_images.resize(unsigned_tx.transfers.second.size()); for(size_t tx_idx = 0; tx_idx < num_tx; ++tx_idx) { std::shared_ptr signer; tx_sign(wallet, unsigned_tx, tx_idx, aux_data, signer); auto & cdata = signer->tdata(); auto aux_info_cur = signer->store_tx_aux_info(); aux_data.tx_device_aux.emplace_back(aux_info_cur); // Pending tx reconstruction signed_tx.ptx.emplace_back(); auto & cpend = signed_tx.ptx.back(); cpend.tx = cdata.tx; cpend.dust = 0; cpend.fee = 0; cpend.dust_added_to_fee = false; cpend.change_dts = cdata.tx_data.change_dts; cpend.selected_transfers = cdata.tx_data.selected_transfers; cpend.key_images = ""; cpend.dests = cdata.tx_data.dests; cpend.construction_data = cdata.tx_data; // Transaction check try { transaction_check(cdata, aux_data); } catch(const std::exception &e){ throw exc::ProtocolException(std::string("Transaction verification failed: ") + e.what()); } std::string key_images; bool all_are_txin_to_key = std::all_of(cdata.tx.vin.begin(), cdata.tx.vin.end(), [&](const cryptonote::txin_v& s_e) -> bool { CHECKED_GET_SPECIFIC_VARIANT(s_e, const cryptonote::txin_to_key, in, false); key_images += boost::to_string(in.k_image) + " "; return true; }); if(!all_are_txin_to_key) { throw std::invalid_argument("Not all are txin_to_key"); } cpend.key_images = key_images; // KI sync 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"); for(size_t src_idx = 0; src_idx < num_sources; ++src_idx){ size_t idx_mapped = cdata.source_permutation[src_idx]; CHECK_AND_ASSERT_THROW_MES(idx_mapped < cdata.tx_data.selected_transfers.size(), "Invalid idx_mapped"); CHECK_AND_ASSERT_THROW_MES(src_idx < cdata.tx.vin.size(), "Invalid idx_mapped"); size_t idx_map_src = cdata.tx_data.selected_transfers[idx_mapped]; auto vini = boost::get(cdata.tx.vin[src_idx]); CHECK_AND_ASSERT_THROW_MES(idx_map_src < signed_tx.key_images.size(), "Invalid key image index"); signed_tx.key_images[idx_map_src] = vini.k_image; } } } void device_trezor::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 & signer) { AUTO_LOCK_CMD(); require_connected(); device_state_reset_unsafe(); require_initialized(); CHECK_AND_ASSERT_THROW_MES(idx < unsigned_tx.txes.size(), "Invalid transaction index"); signer = std::make_shared(wallet, &unsigned_tx, idx, &aux_data); const tools::wallet2::tx_construction_data & cur_tx = unsigned_tx.txes[idx]; unsigned long num_sources = cur_tx.sources.size(); unsigned long num_outputs = cur_tx.splitted_dsts.size(); // Step: Init auto init_msg = signer->step_init(); this->set_msg_addr(init_msg.get()); transaction_pre_check(init_msg); auto response = this->client_exchange(init_msg); signer->step_init_ack(response); // Step: Set transaction inputs for(size_t cur_src = 0; cur_src < num_sources; ++cur_src){ auto src = signer->step_set_input(cur_src); auto ack = this->client_exchange(src); signer->step_set_input_ack(ack); } // Step: sort auto perm_req = signer->step_permutation(); if (perm_req){ auto perm_ack = this->client_exchange(perm_req); signer->step_permutation_ack(perm_ack); } // 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); } } // 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); // 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); } // 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); // 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); } // Step: final auto final_msg = signer->step_final(); auto ack_final = this->client_exchange(final_msg); signer->step_final_ack(ack_final); } void device_trezor::transaction_pre_check(std::shared_ptr init_msg) { CHECK_AND_ASSERT_THROW_MES(init_msg, "TransactionInitRequest is empty"); CHECK_AND_ASSERT_THROW_MES(init_msg->has_tsx_data(), "TransactionInitRequest has no transaction data"); CHECK_AND_ASSERT_THROW_MES(m_features, "Device state not initialized"); // make sure the caller did not reset features const bool nonce_required = init_msg->tsx_data().has_payment_id() && init_msg->tsx_data().payment_id().size() > 0; if (nonce_required){ // Versions 2.0.9 and lower do not support payment ID CHECK_AND_ASSERT_THROW_MES(m_features->has_major_version() && m_features->has_minor_version() && m_features->has_patch_version(), "Invalid Trezor firmware version information"); const uint32_t vma = m_features->major_version(); const uint32_t vmi = m_features->minor_version(); const uint32_t vpa = m_features->patch_version(); if (vma < 2 || (vma == 2 && vmi == 0 && vpa <= 9)) { throw exc::TrezorException("Trezor firmware 2.0.9 and lower does not support transactions with short payment IDs or integrated addresses. Please update."); } } } void device_trezor::transaction_check(const protocol::tx::TData & tdata, const hw::tx_aux_data & aux_data) { // Simple serialization check cryptonote::blobdata tx_blob; cryptonote::transaction tx_deserialized; bool r = cryptonote::t_serializable_object_to_blob(tdata.tx, tx_blob); CHECK_AND_ASSERT_THROW_MES(r, "Transaction serialization failed"); r = cryptonote::parse_and_validate_tx_from_blob(tx_blob, tx_deserialized); CHECK_AND_ASSERT_THROW_MES(r, "Transaction deserialization failed"); // Extras check std::vector tx_extra_fields; cryptonote::tx_extra_nonce nonce; r = cryptonote::parse_tx_extra(tdata.tx.extra, tx_extra_fields); CHECK_AND_ASSERT_THROW_MES(r, "tx.extra parsing failed"); 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"); if (nonce_required){ const std::string & payment_id = tdata.tsx_data.payment_id(); if (payment_id.size() == 32){ crypto::hash payment_id_long{}; CHECK_AND_ASSERT_THROW_MES(cryptonote::get_payment_id_from_tx_extra_nonce(nonce.nonce, payment_id_long), "Long payment ID not present"); } else if (payment_id.size() == 8){ crypto::hash8 payment_id_short{}; CHECK_AND_ASSERT_THROW_MES(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(nonce.nonce, payment_id_short), "Short payment ID not present"); } } } #else //WITH_DEVICE_TREZOR void register_all(std::map> ®istry) { } void register_all() { } #endif //WITH_DEVICE_TREZOR }}