// Copyright (c) 2017-2019, The Monero Project // Copyright (c) 2018, The Loki 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 "common/string_util.h" #include "device/io_ledger_tcp.hpp" #include "io_hid.hpp" #include "version.h" #include "device_ledger.hpp" #include "ringct/rctOps.h" #include "cryptonote_basic/account.h" #include "cryptonote_basic/subaddress_index.h" #include "cryptonote_core/cryptonote_tx_utils.h" #include "common/lock.h" #include "common/varint.h" #include #include "logging/oxen_logger.h" #include #ifdef DEBUG_HWDEVICE #include #endif namespace hw::ledger { #ifdef WITH_DEVICE_LEDGER static auto logcat = log::Cat("device.ledger"); /* ===================================================================== */ /* === Debug ==== */ /* ===================================================================== */ namespace { bool apdu_verbose = true; #define LEDGER_STATUS(status) {status, #status##sv} constexpr std::pair status_codes[] = { LEDGER_STATUS(SW_SECURITY_PIN_LOCKED), LEDGER_STATUS(SW_SECURITY_LOAD_KEY), LEDGER_STATUS(SW_SECURITY_COMMITMENT_CONTROL), LEDGER_STATUS(SW_SECURITY_AMOUNT_CHAIN_CONTROL), LEDGER_STATUS(SW_SECURITY_COMMITMENT_CHAIN_CONTROL), LEDGER_STATUS(SW_SECURITY_OUTKEYS_CHAIN_CONTROL), LEDGER_STATUS(SW_SECURITY_MAXOUTPUT_REACHED), LEDGER_STATUS(SW_SECURITY_HMAC), LEDGER_STATUS(SW_SECURITY_RANGE_VALUE), LEDGER_STATUS(SW_SECURITY_INTERNAL), LEDGER_STATUS(SW_SECURITY_MAX_SIGNATURE_REACHED), LEDGER_STATUS(SW_SECURITY_PREFIX_HASH), LEDGER_STATUS(SW_SECURITY_LOCKED), LEDGER_STATUS(SW_COMMAND_NOT_ALLOWED), LEDGER_STATUS(SW_SUBCOMMAND_NOT_ALLOWED), LEDGER_STATUS(SW_DENY), LEDGER_STATUS(SW_KEY_NOT_SET), LEDGER_STATUS(SW_WRONG_DATA), LEDGER_STATUS(SW_WRONG_DATA_RANGE), LEDGER_STATUS(SW_IO_FULL), LEDGER_STATUS(SW_CLIENT_NOT_SUPPORTED), LEDGER_STATUS(SW_WRONG_P1P2), LEDGER_STATUS(SW_INS_NOT_SUPPORTED), LEDGER_STATUS(SW_PROTOCOL_NOT_SUPPORTED), LEDGER_STATUS(SW_UNKNOWN), }; std::string status_string(unsigned int code) { for (auto& [code_, str] : status_codes) if (code_ == code) return std::string{str}; if ((code & 0xff00) == SW_WRONG_LENGTH) return "SW_WRONG_LENGTH(" + std::to_string(code & 0xff) + ")"; return "UNKNOWN"s; } } // anon namespace void set_apdu_verbose(bool verbose) { apdu_verbose = verbose; } #ifdef DEBUG_HWDEVICE crypto::secret_key dbg_viewkey; crypto::secret_key dbg_spendkey; #endif /* ===================================================================== */ /* === hmacmap ==== */ /* ===================================================================== */ SecHMAC::SecHMAC(const uint8_t s[32], const uint8_t h[32]) { std::memcpy(sec, s, 32); std::memcpy(hmac, h, 32); } void HMACmap::find_mac(const uint8_t sec[32], uint8_t hmac[32]) { size_t sz = hmacs.size(); log_hexbuffer("find_mac: lookup for ", sec,32); for (auto& h : hmacs) { log_hexbuffer("find_mac: - try ", h.sec, 32); if (memcmp(sec, h.sec, 32) == 0) { std::memcpy(hmac, h.hmac, 32); log_hexbuffer("find_mac: - found ", h.hmac, 32); return; } } throw std::runtime_error("Protocol error: try to send untrusted secret"); } void HMACmap::add_mac(const uint8_t sec[32], const uint8_t hmac[32]) { log_hexbuffer("add_mac: sec ", sec, 32); log_hexbuffer("add_mac: hmac ", hmac, 32); hmacs.push_back(SecHMAC(sec,hmac)); } void HMACmap::clear() { hmacs.clear(); } /* ===================================================================== */ /* === Keymap ==== */ /* ===================================================================== */ ABPkeys::ABPkeys(const rct::key& A, const rct::key& B, const bool is_subaddr, const bool is_change, const bool need_additional_txkeys, const size_t real_output_index, const rct::key& P, const rct::key& AK) { Aout = A; Bout = B; is_subaddress = is_subaddr; is_change_address = is_change; additional_key = need_additional_txkeys; index = real_output_index; Pout = P; AKout = AK; } ABPkeys::ABPkeys(const ABPkeys& keys) { Aout = keys.Aout; Bout = keys.Bout; is_subaddress = keys.is_subaddress; is_change_address = keys.is_change_address; additional_key = keys.additional_key; index = keys.index; Pout = keys.Pout; AKout = keys.AKout; } ABPkeys &ABPkeys::operator=(const ABPkeys& keys) { if (&keys == this) return *this; Aout = keys.Aout; Bout = keys.Bout; is_subaddress = keys.is_subaddress; is_change_address = keys.is_change_address; additional_key = keys.additional_key; index = keys.index; Pout = keys.Pout; AKout = keys.AKout; return *this; } bool Keymap::find(const rct::key& P, ABPkeys& keys) const { for (auto& abp : ABP) { if (abp.Pout == P) { keys = abp; return true; } } return false; } void Keymap::add(const ABPkeys& keys) { ABP.push_back(keys); } void Keymap::clear() { ABP.clear(); } #ifdef DEBUG_HWDEVICE void Keymap::log() { log_message("keymap", "content"); size_t sz = ABP.size(); for (size_t i=0; i(&d0), reinterpret_cast(&d1)); } /* ===================================================================== */ /* === Device ==== */ /* ===================================================================== */ static int device_id = 0; #define PROTOCOL_VERSION 1 #ifdef NDEBUG #define LEDGER_INS(name, code) \ static constexpr uint8_t INS_##name = code #else // Reverse lookup table for commands -> names, only available in debug compilations static std::unordered_map debug_ins_names; static uint8_t debug_record_ins(uint8_t code, std::string_view name) { debug_ins_names.emplace(code, name); return code; } #define LEDGER_INS(name, code) \ static const uint8_t INS_##name = debug_record_ins(code, #name##sv) #endif LEDGER_INS(RESET, 0x02); LEDGER_INS(GET_NETWORK, 0x10); LEDGER_INS(GET_KEY, 0x20); LEDGER_INS(DISPLAY_ADDRESS, 0x21); LEDGER_INS(PUT_KEY, 0x22); LEDGER_INS(GET_CHACHA8_PREKEY, 0x24); LEDGER_INS(VERIFY_KEY, 0x26); LEDGER_INS(SECRET_KEY_TO_PUBLIC_KEY, 0x30); LEDGER_INS(GEN_KEY_DERIVATION, 0x32); LEDGER_INS(DERIVATION_TO_SCALAR, 0x34); LEDGER_INS(DERIVE_PUBLIC_KEY, 0x36); LEDGER_INS(DERIVE_SECRET_KEY, 0x38); LEDGER_INS(GEN_KEY_IMAGE, 0x3A); LEDGER_INS(SECRET_KEY_ADD, 0x3C); LEDGER_INS(SECRET_KEY_SUB, 0x3E); LEDGER_INS(GENERATE_KEYPAIR, 0x40); LEDGER_INS(SECRET_SCAL_MUL_KEY, 0x42); LEDGER_INS(SECRET_SCAL_MUL_BASE, 0x44); LEDGER_INS(DERIVE_SUBADDRESS_PUBLIC_KEY, 0x46); LEDGER_INS(GET_SUBADDRESS, 0x48); LEDGER_INS(GET_SUBADDRESS_SPEND_PUBLIC_KEY, 0x4A); LEDGER_INS(GET_SUBADDRESS_SECRET_KEY, 0x4C); LEDGER_INS(OPEN_TX, 0x70); LEDGER_INS(SET_SIGNATURE_MODE, 0x72); LEDGER_INS(GET_ADDITIONAL_KEY, 0x74); LEDGER_INS(GET_TX_SECRET_KEY, 0x75); LEDGER_INS(ENCRYPT_PAYMENT_ID, 0x76); LEDGER_INS(GEN_COMMITMENT_MASK, 0x77); LEDGER_INS(BLIND, 0x78); LEDGER_INS(UNBLIND, 0x7A); LEDGER_INS(GEN_TXOUT_KEYS, 0x7B); LEDGER_INS(PREFIX_HASH, 0x7D); LEDGER_INS(VALIDATE, 0x7C); LEDGER_INS(CLSAG, 0x7F); LEDGER_INS(CLOSE_TX, 0x80); LEDGER_INS(GET_TX_PROOF, 0xA0); LEDGER_INS(GEN_UNLOCK_SIGNATURE, 0xA2); LEDGER_INS(GEN_ONS_SIGNATURE, 0xA3); LEDGER_INS(GEN_KEY_IMAGE_SIGNATURE, 0xA4); LEDGER_INS(GET_RESPONSE, 0xc0); #define OPTION_MORE_DATA 0x80 // When we have to send a bunch of data to be keccak hashed we send in chunks of this size; we // could go up to 254, but Keccak uses 136-byte chunks so it makes some sense to send at that // size. constexpr size_t KECCAK_HASH_CHUNK_SIZE = 136; static_assert(KECCAK_HASH_CHUNK_SIZE <= 254, "Max keccak data chunk size exceeds the protocol limit"); constexpr size_t BLAKE2B_HASH_CHUNK_SIZE = 128; static_assert(BLAKE2B_HASH_CHUNK_SIZE <= 254, "Max BLAKE2b data chunk size exceeds the protocol limit"); device_ledger::device_ledger() : hw_device{std::make_unique(0x0101, 0x05, 64, 2000)} { id = device_id++; reset_buffer(); has_view_key = false; tx_in_progress = false; log::debug(logcat, "Device {} Created", id); } device_ledger::device_ledger(io::ledger_tcp&& tcp) : hw_device{std::make_unique(std::move(tcp))} { id = device_id++; reset_buffer(); has_view_key = false; tx_in_progress = false; log::debug(logcat, "Device {} (tcp) created", id); } device_ledger::~device_ledger() { release(); log::debug(logcat, "Device {} Destroyed", id); } /* ======================================================================= */ /* LOCKER */ /* ======================================================================= */ //lock the device for a long sequence void device_ledger::lock() { log::debug(logcat, "Ask for LOCKING for device {} in thread ", name); device_locker.lock(); log::debug(logcat, "Device {} LOCKed", name); } //lock the device for a long sequence bool device_ledger::try_lock() { log::debug(logcat, "Ask for LOCKING(try) for device {} in thread ", name); bool r = device_locker.try_lock(); log::debug(logcat, "Device {}{} LOCKed(try)", name, (r ? "" : " not")); return r; } //unlock the device after a long sequence void device_ledger::unlock() { log::debug(logcat, "Ask for UNLOCKING for device {} in thread ", name); device_locker.unlock(); log::debug(logcat, "Device {} UNLOCKed", name); } /* ======================================================================= */ /* IO */ /* ======================================================================= */ #define IO_SW_DENY 0x6982 #define IO_SECRET_KEY 0x02 void device_ledger::logCMD() { if (apdu_verbose) { std::ostringstream cmd; cmd << std::hex << std::setfill('0'); cmd << "v=0x" << std::setw(2) << +buffer_send[0]; cmd << " i=0x" << std::setw(2) << +buffer_send[1]; #ifndef NDEBUG if (auto it = debug_ins_names.find(buffer_send[1]); it != debug_ins_names.end()) cmd << '[' << it->second << ']'; #endif cmd << " p=(0x" << std::setw(2) << +buffer_send[2] << ",0x" << std::setw(2) << +buffer_send[3] << ')'; cmd << " sz=0x" << std::setw(2) << +buffer_send[4] << '[' << std::to_string(buffer_send[4]) << "] "; log::debug(logcat, "CMD: {}{}", cmd.str(), oxenc::to_hex(buffer_send + 5, buffer_send + length_send)); last_cmd = std::chrono::steady_clock::now(); } } void device_ledger::logRESP() { if (apdu_verbose) log::debug(logcat, "RESP (+{}): {} {}", tools::friendly_duration(std::chrono::steady_clock::now() - last_cmd), oxenc::to_hex(std::string_view{reinterpret_cast(&sw), sizeof(sw)}), oxenc::to_hex(buffer_recv, buffer_recv + length_recv)); } int device_ledger::set_command_header(unsigned char ins, unsigned char p1, unsigned char p2) { reset_buffer(); buffer_send[0] = PROTOCOL_VERSION; buffer_send[1] = ins; buffer_send[2] = p1; buffer_send[3] = p2; buffer_send[4] = 0x00; return 5; } int device_ledger::set_command_header_noopt(unsigned char ins, unsigned char p1, unsigned char p2) { int offset = set_command_header(ins, p1, p2); buffer_send[offset++] = 0; // options buffer_send[4] = offset - 5; return offset; } void device_ledger::send_simple(unsigned char ins, unsigned char p1) { length_send = set_command_header_noopt(ins, p1); bool wait = ins == INS_GET_KEY && p1 == IO_SECRET_KEY; exchange(wait); } void device_ledger::send_bytes(const void* buf, size_t size, int& offset) { CHECK_AND_ASSERT_THROW_MES(offset + size <= BUFFER_SEND_SIZE, "send_bytes: out of bounds write"); std::memmove(buffer_send+offset, buf, size); offset += size; } void device_ledger::receive_bytes(void* dest, size_t size, int& offset) { CHECK_AND_ASSERT_THROW_MES(offset + size <= BUFFER_RECV_SIZE, "receive_bytes: out of bounds read"); std::memmove(dest, buffer_recv+offset, size); offset += size; } void device_ledger::receive_bytes(void* dest, size_t size) { int offset = 0; receive_bytes(dest, size, offset); } void device_ledger::send_u32(uint32_t x, int& offset) { oxenc::host_to_big_inplace(x); send_bytes(&x, 4, offset); } void device_ledger::send_u16(uint16_t x, int& offset) { oxenc::host_to_big_inplace(x); send_bytes(&x, 2, offset); } uint32_t device_ledger::receive_u32(int& offset) { uint32_t x; receive_bytes(&x, 4, offset); oxenc::big_to_host_inplace(x); return x; } uint32_t device_ledger::receive_u32() { int offset = 0; return receive_u32(offset); } void device_ledger::send_secret(const unsigned char sec[32], int &offset) { log::debug(logcat, "send_secret: {}", tx_in_progress); send_bytes(sec, 32, offset); if (tx_in_progress) { CHECK_AND_ASSERT_THROW_MES(offset + 32 <= BUFFER_SEND_SIZE, "send_secret: out of bounds write (mac)"); hmac_map.find_mac((uint8_t*)sec, buffer_send+offset); offset += 32; } } void device_ledger::receive_secret(unsigned char sec[32], int &offset) { log::debug(logcat, "receive_secret: {}", tx_in_progress); receive_bytes(sec, 32, offset); if (tx_in_progress) { CHECK_AND_ASSERT_THROW_MES(offset + 32 <= BUFFER_RECV_SIZE, "receive_secret: out of bounds read (mac)"); hmac_map.add_mac((uint8_t*)sec, buffer_recv+offset); offset += 32; } } void device_ledger::send_finish(int& offset) { buffer_send[4] = offset-5; length_send = offset; offset = 0; } unsigned int device_ledger::finish_and_exchange(int& offset, bool wait_on_input) { send_finish(offset); return exchange(wait_on_input); } bool device_ledger::reset() { reset_buffer(); int offset = set_command_header_noopt(INS_RESET); CHECK_AND_ASSERT_THROW_MES(offset + OXEN_VERSION_STR.size() <= BUFFER_SEND_SIZE, "OXEN_VERSION_STR is too long"); send_bytes(OXEN_VERSION_STR.data(), OXEN_VERSION_STR.size(), offset); finish_and_exchange(offset); CHECK_AND_ASSERT_THROW_MES(length_recv>=3, "Communication error, less than three bytes received. Check your application version."); std::array device_version = {buffer_recv[0], buffer_recv[1], buffer_recv[2]}; CHECK_AND_ASSERT_THROW_MES(device_version >= MINIMUM_APP_VERSION, "Unsupported device application version: " << tools::join(".", device_version) << " At least " << tools::join(".", MINIMUM_APP_VERSION) << " is required."); return true; } unsigned int device_ledger::exchange(bool wait_on_input) { logCMD(); length_recv = hw_device->exchange(buffer_send, length_send, buffer_recv, BUFFER_RECV_SIZE, wait_on_input); CHECK_AND_ASSERT_THROW_MES(length_recv >= 2, "Communication error, less than two bytes received"); length_recv -= 2; sw = (buffer_recv[length_recv] << 8) | buffer_recv[length_recv+1]; logRESP(); // If we are waiting on input then we also want to be able to return a DENY if (wait_on_input && sw == IO_SW_DENY) return sw; CHECK_AND_ASSERT_THROW_MES(sw == SW_OK, "Wrong Device Status: " << "0x" << std::hex << sw << " (" << status_string(sw) << "), " << "EXPECTED 0x" << std::hex << SW_OK << " (" << status_string(SW_OK) << "), "); return sw; } void device_ledger::reset_buffer() { length_send = 0; std::memset(buffer_send, 0, BUFFER_SEND_SIZE); length_recv = 0; std::memset(buffer_recv, 0, BUFFER_RECV_SIZE); } /* ======================================================================= */ /* SETUP/TEARDOWN */ /* ======================================================================= */ bool device_ledger::set_name(std::string_view n) { name = name; return true; } std::string device_ledger::get_name() const { if (!connected()) return ""; return name; } void device_ledger::set_address(std::string_view addr) { if (addr.empty() || !hw_device) return; auto* tcp = dynamic_cast(hw_device.get()); if (!tcp) return; if (auto pos = addr.rfind(':'); pos != addr.npos) { tcp->port = addr.substr(pos + 1); addr = addr.substr(0, pos); } tcp->host = addr; } bool device_ledger::init() { #ifdef DEBUG_HWDEVICE debug_device = &get_device("default"); #endif release(); hw_device->init(); log::debug(logcat, "Device {} HIDUSB inited", id); return true; } static const std::vector known_devices { // Nano S {0x2c97, 0x1000, 0, 0xffa0}, {0x2c97, 0x1001, 0, 0xffa0}, {0x2c97, 0x1002, 0, 0xffa0}, {0x2c97, 0x1003, 0, 0xffa0}, {0x2c97, 0x1004, 0, 0xffa0}, {0x2c97, 0x1005, 0, 0xffa0}, {0x2c97, 0x1006, 0, 0xffa0}, {0x2c97, 0x1007, 0, 0xffa0}, {0x2c97, 0x1008, 0, 0xffa0}, {0x2c97, 0x1009, 0, 0xffa0}, {0x2c97, 0x100a, 0, 0xffa0}, {0x2c97, 0x100b, 0, 0xffa0}, {0x2c97, 0x100c, 0, 0xffa0}, {0x2c97, 0x100d, 0, 0xffa0}, {0x2c97, 0x100e, 0, 0xffa0}, {0x2c97, 0x100f, 0, 0xffa0}, {0x2c97, 0x1010, 0, 0xffa0}, {0x2c97, 0x1011, 0, 0xffa0}, {0x2c97, 0x1012, 0, 0xffa0}, {0x2c97, 0x1013, 0, 0xffa0}, {0x2c97, 0x1014, 0, 0xffa0}, {0x2c97, 0x1015, 0, 0xffa0}, {0x2c97, 0x1016, 0, 0xffa0}, {0x2c97, 0x1017, 0, 0xffa0}, {0x2c97, 0x1018, 0, 0xffa0}, {0x2c97, 0x1019, 0, 0xffa0}, {0x2c97, 0x101a, 0, 0xffa0}, {0x2c97, 0x101b, 0, 0xffa0}, {0x2c97, 0x101c, 0, 0xffa0}, {0x2c97, 0x101d, 0, 0xffa0}, {0x2c97, 0x101e, 0, 0xffa0}, {0x2c97, 0x101f, 0, 0xffa0}, {0x2c97, 0x1005, 0, 0xffa0}, // Nano X {0x2c97, 0x4000, 0, 0xffa0}, {0x2c97, 0x4001, 0, 0xffa0}, {0x2c97, 0x4002, 0, 0xffa0}, {0x2c97, 0x4003, 0, 0xffa0}, {0x2c97, 0x4004, 0, 0xffa0}, {0x2c97, 0x4005, 0, 0xffa0}, {0x2c97, 0x4006, 0, 0xffa0}, {0x2c97, 0x4007, 0, 0xffa0}, {0x2c97, 0x4008, 0, 0xffa0}, {0x2c97, 0x4009, 0, 0xffa0}, {0x2c97, 0x400a, 0, 0xffa0}, {0x2c97, 0x400b, 0, 0xffa0}, {0x2c97, 0x400c, 0, 0xffa0}, {0x2c97, 0x400d, 0, 0xffa0}, {0x2c97, 0x400e, 0, 0xffa0}, {0x2c97, 0x400f, 0, 0xffa0}, {0x2c97, 0x4010, 0, 0xffa0}, {0x2c97, 0x4011, 0, 0xffa0}, {0x2c97, 0x4012, 0, 0xffa0}, {0x2c97, 0x4013, 0, 0xffa0}, {0x2c97, 0x4014, 0, 0xffa0}, {0x2c97, 0x4015, 0, 0xffa0}, {0x2c97, 0x4016, 0, 0xffa0}, {0x2c97, 0x4017, 0, 0xffa0}, {0x2c97, 0x4018, 0, 0xffa0}, {0x2c97, 0x4019, 0, 0xffa0}, {0x2c97, 0x401a, 0, 0xffa0}, {0x2c97, 0x401b, 0, 0xffa0}, {0x2c97, 0x401c, 0, 0xffa0}, {0x2c97, 0x401d, 0, 0xffa0}, {0x2c97, 0x401e, 0, 0xffa0}, {0x2c97, 0x401f, 0, 0xffa0}, // Nano S Plus {0x2c97, 0x5000, 0, 0xffa0}, {0x2c97, 0x5001, 0, 0xffa0}, {0x2c97, 0x5002, 0, 0xffa0}, {0x2c97, 0x5003, 0, 0xffa0}, {0x2c97, 0x5004, 0, 0xffa0}, {0x2c97, 0x5005, 0, 0xffa0}, {0x2c97, 0x5006, 0, 0xffa0}, {0x2c97, 0x5007, 0, 0xffa0}, {0x2c97, 0x5008, 0, 0xffa0}, {0x2c97, 0x5009, 0, 0xffa0}, {0x2c97, 0x500a, 0, 0xffa0}, {0x2c97, 0x500b, 0, 0xffa0}, {0x2c97, 0x500c, 0, 0xffa0}, {0x2c97, 0x500d, 0, 0xffa0}, {0x2c97, 0x500e, 0, 0xffa0}, {0x2c97, 0x500f, 0, 0xffa0}, {0x2c97, 0x5010, 0, 0xffa0}, {0x2c97, 0x5011, 0, 0xffa0}, {0x2c97, 0x5012, 0, 0xffa0}, {0x2c97, 0x5013, 0, 0xffa0}, {0x2c97, 0x5014, 0, 0xffa0}, {0x2c97, 0x5015, 0, 0xffa0}, {0x2c97, 0x5016, 0, 0xffa0}, {0x2c97, 0x5017, 0, 0xffa0}, {0x2c97, 0x5018, 0, 0xffa0}, {0x2c97, 0x5019, 0, 0xffa0}, {0x2c97, 0x501a, 0, 0xffa0}, {0x2c97, 0x501b, 0, 0xffa0}, {0x2c97, 0x501c, 0, 0xffa0}, {0x2c97, 0x501d, 0, 0xffa0}, {0x2c97, 0x501e, 0, 0xffa0}, {0x2c97, 0x501f, 0, 0xffa0}, }; bool device_ledger::connect() { disconnect(); if (auto* hid_io = dynamic_cast(hw_device.get())) hid_io->connect(known_devices); else if (auto* tcp = dynamic_cast(hw_device.get())) tcp->connect(); else throw std::logic_error{"Invalid ledger hardware configure"}; reset(); check_network_type(); #ifdef DEBUG_HWDEVICE cryptonote::account_public_address pubkey; get_public_address(pubkey); #endif crypto::secret_key vkey; crypto::secret_key skey; get_secret_keys(vkey,skey); return true; } bool device_ledger::connected() const { return hw_device->connected(); } bool device_ledger::disconnect() { hw_device->disconnect(); return true; } bool device_ledger::release() { disconnect(); hw_device->release(); return true; } static std::string nettype_string(cryptonote::network_type n) { switch (n) { case cryptonote::network_type::MAINNET: return "mainnet"; case cryptonote::network_type::TESTNET: return "testnet"; case cryptonote::network_type::DEVNET: return "devnet"; case cryptonote::network_type::FAKECHAIN: return "fakenet"; default: return "(unknown)"; } } void device_ledger::check_network_type() { auto locks = tools::unique_locks(device_locker, command_locker); send_simple(INS_GET_NETWORK); std::string coin{reinterpret_cast(buffer_recv), 4}; auto device_nettype = static_cast(buffer_recv[4]); log::debug(logcat, "Ledger wallet is set to {} {}", coin, nettype_string(device_nettype)); if (coin != COIN_NETWORK) throw std::runtime_error{"Invalid wallet app: expected " + std::string{COIN_NETWORK} + ", got " + coin}; if (device_nettype != nettype) throw std::runtime_error{"Ledger wallet is set to the wrong network type: expected " + nettype_string(nettype) + " but the device is set to " + nettype_string(device_nettype)}; } void device_ledger::set_network_type(cryptonote::network_type set_nettype) { nettype = set_nettype; } bool device_ledger::set_mode(mode m) { auto locks = tools::unique_locks(device_locker, command_locker); switch(m) { case mode::TRANSACTION_CREATE_REAL: case mode::TRANSACTION_CREATE_FAKE: { int offset = set_command_header_noopt(INS_SET_SIGNATURE_MODE, 1); buffer_send[offset++] = static_cast(m); finish_and_exchange(offset); break; } case mode::TRANSACTION_PARSE: case mode::NONE: break; } log::debug(logcat, "Switch to mode: {}", +static_cast(m)); return device::set_mode(m); } /* ======================================================================= */ /* WALLET & ADDRESS */ /* ======================================================================= */ bool device_ledger::get_public_address(cryptonote::account_public_address &pubkey){ auto locks = tools::unique_locks(device_locker, command_locker); send_simple(INS_GET_KEY, 1); int offset = 0; receive_bytes(pubkey.m_view_public_key.data(), 32, offset); receive_bytes(pubkey.m_spend_public_key.data(), 32, offset); return true; } bool device_ledger::get_secret_keys(crypto::secret_key& vkey, crypto::secret_key& skey) { auto locks = tools::unique_locks(device_locker, command_locker); //secret key are represented as fake key on the wallet side memcpy(vkey.data(), dummy_view_key, 32); memcpy(skey.data(), dummy_spend_key, 32); //spcialkey, normal conf handled in decrypt send_simple(INS_GET_KEY, 0x02); //View key is retrieved, if allowed, to speed up blockchain parsing receive_bytes(viewkey.data(), 32); has_view_key = !is_fake_view_key(viewkey); log::debug(logcat, "{}", (has_view_key ? "Have view key" : "Have no view key")); #ifdef DEBUG_HWDEVICE send_simple(INS_GET_KEY, 0x04); int offset = 0; receive_bytes(dbg_viewkey.data(), 32, offset); receive_bytes(dbg_spendkey.data(), 32, offset); #endif return true; } bool device_ledger::generate_chacha_key(const cryptonote::account_keys &keys, crypto::chacha_key &key, uint64_t kdf_rounds) { auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE crypto::chacha_key key_x; debug_device->generate_chacha_key(decrypt(keys), key_x, kdf_rounds); #endif send_simple(INS_GET_CHACHA8_PREKEY); char prekey[200]; receive_bytes(prekey, 200); crypto::generate_chacha_key_prehashed(prekey, sizeof(prekey), key, kdf_rounds); #ifdef DEBUG_HWDEVICE check32("generate_chacha_key_prehashed", "key", key_x.data(), key.data()); #endif return true; } void device_ledger::display_address(const cryptonote::subaddress_index& index, const std::optional &payment_id) { auto locks = tools::unique_locks(device_locker, command_locker); int offset = set_command_header_noopt(INS_DISPLAY_ADDRESS, payment_id?1:0); //index send_bytes(&index, sizeof(index), offset); //payment ID send_bytes(payment_id ? payment_id->data() : crypto::null.data(), 8, offset); CHECK_AND_ASSERT_THROW_MES(finish_and_exchange(offset, true) == SW_OK, "Timeout/Error on display address."); } /* ======================================================================= */ /* SUB ADDRESS */ /* ======================================================================= */ bool device_ledger::derive_subaddress_public_key(const crypto::public_key &pub, const crypto::key_derivation &derivation, const std::size_t output_index, crypto::public_key &derived_pub){ auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE crypto::key_derivation derivation_x = (mode_ == mode::TRANSACTION_PARSE && has_view_key) ? derivation : decrypt(derivation); log_hexbuffer("derive_subaddress_public_key: [[IN]] pub ", pub.data(), 32); log_hexbuffer("derive_subaddress_public_key: [[IN]] derivation", derivation_x.data(), 32); log_message( "derive_subaddress_public_key: [[IN]] index ", std::to_string(output_index)); crypto::public_key derived_pub_x; debug_device->derive_subaddress_public_key(pub, derivation_x, output_index, derived_pub_x); log_hexbuffer("derive_subaddress_public_key: [[OUT]] derived_pub", derived_pub_x.data(), 32); #endif if (mode_ == mode::TRANSACTION_PARSE && has_view_key) { //If we are in TRANSACTION_PARSE, the given derivation has been retrieved uncrypted (wihtout the help //of the device), so continue that way. log::debug(logcat, "derive_subaddress_public_key : PARSE mode with known viewkey"); crypto::derive_subaddress_public_key(pub, derivation, output_index, derived_pub); } else { int offset = set_command_header_noopt(INS_DERIVE_SUBADDRESS_PUBLIC_KEY); //pub send_bytes(pub.data(), 32, offset); //derivation send_secret(derivation.data(), offset); //index send_u32(output_index, offset); finish_and_exchange(offset); //pub key receive_bytes(derived_pub.data(), 32); } #ifdef DEBUG_HWDEVICE check32("derive_subaddress_public_key", "derived_pub", derived_pub_x.data(), derived_pub.data()); #endif return true; } crypto::public_key device_ledger::get_subaddress_spend_public_key(const cryptonote::account_keys& keys, const cryptonote::subaddress_index &index) { auto locks = tools::unique_locks(device_locker, command_locker); crypto::public_key D; #ifdef DEBUG_HWDEVICE const cryptonote::account_keys keys_x = decrypt(keys); log_hexbuffer("get_subaddress_spend_public_key: [[IN]] keys.m_view_secret_key ", keys_x.m_view_secret_key.data(), 32); log_hexbuffer("get_subaddress_spend_public_key: [[IN]] keys.m_spend_secret_key", keys_x.m_spend_secret_key.data(), 32); log_message ("get_subaddress_spend_public_key: [[IN]] index ", std::to_string(index.major)+"."+std::to_string(index.minor)); crypto::public_key D_x = debug_device->get_subaddress_spend_public_key(keys_x, index); log_hexbuffer("get_subaddress_spend_public_key: [[OUT]] derivation ", D_x.data(), 32); #endif if (index.is_zero()) { D = keys.m_account_address.m_spend_public_key; } else { int offset = set_command_header_noopt(INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY); //index static_assert(sizeof(cryptonote::subaddress_index) == 8); send_bytes(&index, sizeof(index), offset); finish_and_exchange(offset); receive_bytes(D.data(), 32); } #ifdef DEBUG_HWDEVICE check32("get_subaddress_spend_public_key", "D", D_x.data(), D.data()); #endif return D; } std::vector device_ledger::get_subaddress_spend_public_keys(const cryptonote::account_keys &keys, uint32_t account, uint32_t begin, uint32_t end) { std::vector pkeys; cryptonote::subaddress_index index{account, begin}; for (uint32_t idx = begin; idx < end; ++idx) { index.minor = idx; pkeys.push_back(get_subaddress_spend_public_key(keys, index)); } return pkeys; } cryptonote::account_public_address device_ledger::get_subaddress(const cryptonote::account_keys& keys, const cryptonote::subaddress_index &index) { auto locks = tools::unique_locks(device_locker, command_locker); cryptonote::account_public_address address; #ifdef DEBUG_HWDEVICE const cryptonote::account_keys keys_x = decrypt(keys); log_hexbuffer("get_subaddress: [[IN]] keys.m_view_secret_key ", keys_x.m_view_secret_key.data(), 32); log_hexbuffer("get_subaddress: [[IN]] keys.m_view_public_key", keys_x.m_account_address.m_view_public_key.data(), 32); log_hexbuffer("get_subaddress: [[IN]] keys.m_spend_secret_key ", keys_x.m_spend_secret_key.data(), 32); log_hexbuffer("get_subaddress: [[IN]] keys.m_spend_public_key", keys_x.m_account_address.m_spend_public_key.data(), 32); log_message( "get_subaddress: [[IN]] index ", std::to_string(index.major)+"."+std::to_string(index.minor)); cryptonote::account_public_address address_x = debug_device->get_subaddress(keys_x, index); log_hexbuffer("get_subaddress: [[OUT]] keys.m_view_public_key ", address_x.m_view_public_key.data(), 32); log_hexbuffer("get_subaddress: [[OUT]] keys.m_spend_public_key", address_x.m_spend_public_key.data(), 32); #endif if (index.is_zero()) { address = keys.m_account_address; } else { int offset = set_command_header_noopt(INS_GET_SUBADDRESS); //index static_assert(sizeof(cryptonote::subaddress_index) == 8); send_bytes(&index, sizeof(index), offset); finish_and_exchange(offset); offset = 0; receive_bytes(address.m_view_public_key.data(), 32, offset); receive_bytes(address.m_spend_public_key.data(), 32, offset); } #ifdef DEBUG_HWDEVICE check32("get_subaddress", "address.m_view_public_key.data", address_x.m_view_public_key.data(), address.m_view_public_key.data()); check32("get_subaddress", "address.m_spend_public_key.data", address_x.m_spend_public_key.data(), address.m_spend_public_key.data()); #endif return address; } crypto::secret_key device_ledger::get_subaddress_secret_key(const crypto::secret_key &sec, const cryptonote::subaddress_index &index) { auto locks = tools::unique_locks(device_locker, command_locker); crypto::secret_key sub_sec; #ifdef DEBUG_HWDEVICE const crypto::secret_key sec_x = decrypt(sec); log_message ("get_subaddress_secret_key: [[IN]] index ", std::to_string(index.major)+"."+std::to_string(index.minor)); log_hexbuffer("get_subaddress_secret_key: [[IN]] sec ", sec_x.data(), 32); crypto::secret_key sub_sec_x = debug_device->get_subaddress_secret_key(sec_x, index); log_hexbuffer("get_subaddress_secret_key: [[OUT]] sub_sec", sub_sec_x.data(), 32); #endif int offset = set_command_header_noopt(INS_GET_SUBADDRESS_SECRET_KEY); //sec send_secret(sec.data(), offset); //index static_assert(sizeof(cryptonote::subaddress_index) == 8); send_bytes(&index, sizeof(index), offset); finish_and_exchange(offset); offset = 0; receive_secret(sub_sec.data(), offset); #ifdef DEBUG_HWDEVICE crypto::secret_key sub_sec_clear = decrypt(sub_sec); check32("get_subaddress_secret_key", "sub_sec", sub_sec_x.data(), sub_sec_clear.data()); #endif return sub_sec; } /* ======================================================================= */ /* DERIVATION & KEY */ /* ======================================================================= */ bool device_ledger::verify_keys(const crypto::secret_key &secret_key, const crypto::public_key &public_key) { auto locks = tools::unique_locks(device_locker, command_locker); int offset; offset = set_command_header_noopt(INS_VERIFY_KEY); //sec send_secret(secret_key.data(), offset); //pub send_bytes(public_key.data(), 32, offset); finish_and_exchange(offset); offset = 0; uint32_t verified = receive_u32(offset); return verified == 1; } bool device_ledger::scalarmultKey(rct::key& aP, const rct::key &P, const rct::key &a) { auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE const rct::key a_x = decrypt(a); log_hexbuffer("scalarmultKey: [[IN]] P ", P.bytes, 32); log_hexbuffer("scalarmultKey: [[IN]] a ", a_x.bytes, 32); rct::key aP_x; debug_device->scalarmultKey(aP_x, P, a_x); log_hexbuffer("scalarmultKey: [[OUT]] aP", aP_x.bytes, 32); #endif int offset = set_command_header_noopt(INS_SECRET_SCAL_MUL_KEY); //pub send_bytes(P.bytes, 32, offset); //sec send_secret(a.bytes, offset); finish_and_exchange(offset); //pub key receive_bytes(aP.bytes, 32); #ifdef DEBUG_HWDEVICE check32("scalarmultKey", "mulkey", aP_x.bytes, aP.bytes); #endif return true; } bool device_ledger::scalarmultBase(rct::key &aG, const rct::key &a) { auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE const rct::key a_x = decrypt(a); log_hexbuffer("scalarmultKey: [[IN]] a ", a_x.bytes, 32); rct::key aG_x; debug_device->scalarmultBase(aG_x, a_x); log_hexbuffer("scalarmultKey: [[OUT]] aG", aG_x.bytes, 32); #endif int offset = set_command_header_noopt(INS_SECRET_SCAL_MUL_BASE); //sec send_secret(a.bytes, offset); finish_and_exchange(offset); //pub key receive_bytes(aG.bytes, 32); #ifdef DEBUG_HWDEVICE check32("scalarmultBase", "mulkey", aG_x.bytes, aG.bytes); #endif return true; } bool device_ledger::sc_secret_add( crypto::secret_key &r, const crypto::secret_key &a, const crypto::secret_key &b) { auto locks = tools::unique_locks(device_locker, command_locker); int offset; #ifdef DEBUG_HWDEVICE const crypto::secret_key a_x = decrypt(a); const crypto::secret_key b_x = decrypt(b); log_hexbuffer("sc_secret_add: [[IN]] a ", a_x.data(), 32); log_hexbuffer("sc_secret_add: [[IN]] b ", b_x.data(), 32); crypto::secret_key r_x; rct::key aG_x; debug_device->sc_secret_add(r_x, a_x, b_x); log_hexbuffer("sc_secret_add: [[OUT]] aG", r_x.data(), 32); #endif offset = set_command_header_noopt(INS_SECRET_KEY_ADD); //sec key send_secret(a.data(), offset); //sec key send_secret(b.data(), offset); finish_and_exchange(offset); //sec key offset = 0; receive_secret(r.data(), offset); #ifdef DEBUG_HWDEVICE crypto::secret_key r_clear = decrypt(r); check32("sc_secret_add", "r", r_x.data(), r_clear.data()); #endif return true; } crypto::secret_key device_ledger::generate_keys(crypto::public_key &pub, crypto::secret_key &sec, const crypto::secret_key& recovery_key, bool recover) { auto locks = tools::unique_locks(device_locker, command_locker); int offset; if (recover) { throw std::runtime_error("device generate key does not support recover"); } #ifdef DEBUG_HWDEVICE crypto::public_key pub_x; crypto::secret_key sec_x; crypto::secret_key recovery_key_x; if (recover) { recovery_key_x = decrypt(recovery_key); log_hexbuffer("generate_keys: [[IN]] pub", recovery_key_x.data(), 32); } #endif send_simple(INS_GENERATE_KEYPAIR); offset = 0; //pub key receive_bytes(pub.data(), 32, offset); receive_secret(sec.data(), offset); #ifdef DEBUG_HWDEVICE crypto::secret_key sec_clear = decrypt(sec); sec_x = sec_clear; log_hexbuffer("generate_keys: [[OUT]] pub", pub.data(), 32); log_hexbuffer("generate_keys: [[OUT]] sec", sec_clear.data(), 32); crypto::secret_key_to_public_key(sec_x,pub_x); check32("generate_keys", "pub", pub_x.data(), pub.data()); #endif return sec; } crypto::key_derivation device_ledger::generate_key_derivation(const crypto::public_key &pub, const crypto::secret_key &sec) { //TODO: replace entirely the bool return version of this crypto::key_derivation d; generate_key_derivation(pub, sec, d); return d; } bool device_ledger::generate_key_derivation(const crypto::public_key &pub, const crypto::secret_key &sec, crypto::key_derivation &derivation) { auto locks = tools::unique_locks(device_locker, command_locker); bool r = false; #ifdef DEBUG_HWDEVICE log_hexbuffer("generate_key_derivation: [[IN]] pub ", pub.data(), 32); const crypto::secret_key sec_x = (sec == rct::rct2sk(rct::I)) ? sec : decrypt(sec); log_hexbuffer("generate_key_derivation: [[IN]] sec ", sec_x.data(), 32); crypto::key_derivation derivation_x; debug_device->generate_key_derivation(pub, sec_x, derivation_x); log_hexbuffer("generate_key_derivation: [[OUT]] derivation", derivation_x.data(), 32); #endif if (mode_ == mode::TRANSACTION_PARSE && has_view_key) { //A derivation is requested in PARSE mode and we have the view key, //so do that without the device and return the derivation unencrypted. log::debug(logcat, "generate_key_derivation : PARSE mode with known viewkey"); //Note derivation in PARSE mode can only happen with viewkey, so assert it! assert(is_fake_view_key(sec)); r = crypto::generate_key_derivation(pub, viewkey, derivation); } else { int offset = set_command_header_noopt(INS_GEN_KEY_DERIVATION); //pub send_bytes(pub.data(), 32, offset); //sec send_secret(sec.data(), offset); finish_and_exchange(offset); offset = 0; //derivation data receive_secret(derivation.data(), offset); r = true; } #ifdef DEBUG_HWDEVICE crypto::key_derivation derivation_clear = (mode_ == mode::TRANSACTION_PARSE && has_view_key) ? derivation : decrypt(derivation); check32("generate_key_derivation", "derivation", derivation_x.data(), derivation_clear.data()); #endif return r; } bool device_ledger::conceal_derivation(crypto::key_derivation &derivation, const crypto::public_key &tx_pub_key, const std::vector &additional_tx_pub_keys, const crypto::key_derivation &main_derivation, const std::vector &additional_derivations) { const crypto::public_key *pkey = nullptr; if (derivation == main_derivation) { pkey = &tx_pub_key; log::debug(logcat, "conceal derivation with main tx pub key"); } else { for (size_t n = 0; n < additional_derivations.size(); ++n) { if (derivation == additional_derivations[n]) { pkey = &additional_tx_pub_keys[n]; log::debug(logcat, "conceal derivation with additionnal tx pub key"); break; } } } CHECK_AND_ASSERT_THROW_MES(pkey, "Mismatched derivation on scan info"); return generate_key_derivation(*pkey, crypto::null, derivation); } bool device_ledger::derivation_to_scalar(const crypto::key_derivation &derivation, const size_t output_index, crypto::ec_scalar &res) { auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE const crypto::key_derivation derivation_x = decrypt(derivation); log_hexbuffer("derivation_to_scalar: [[IN]] derivation ", derivation_x.data(), 32); log_message ("derivation_to_scalar: [[IN]] output_index ", std::to_string(output_index)); crypto::ec_scalar res_x; debug_device->derivation_to_scalar(derivation_x, output_index, res_x); log_hexbuffer("derivation_to_scalar: [[OUT]] res ", res_x.data(), 32); #endif int offset = set_command_header_noopt(INS_DERIVATION_TO_SCALAR); //derivation send_secret(derivation.data(), offset); //index send_u32(output_index, offset); finish_and_exchange(offset); //derivation data offset = 0; receive_secret(res.data(), offset); #ifdef DEBUG_HWDEVICE crypto::ec_scalar res_clear = decrypt(res); check32("derivation_to_scalar", "res", res_x.data(), res_clear.data()); #endif return true; } bool device_ledger::derive_secret_key(const crypto::key_derivation &derivation, const std::size_t output_index, const crypto::secret_key &sec, crypto::secret_key &derived_sec) { auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE const crypto::key_derivation derivation_x = decrypt(derivation); const crypto::secret_key sec_x = decrypt(sec); log_hexbuffer("derive_secret_key: [[IN]] derivation ", derivation_x.data(), 32); log_message ("derive_secret_key: [[IN]] index ", std::to_string(output_index)); log_hexbuffer("derive_secret_key: [[IN]] sec ", sec_x.data(), 32); crypto::secret_key derived_sec_x; debug_device->derive_secret_key(derivation_x, output_index, sec_x, derived_sec_x); log_hexbuffer("derive_secret_key: [[OUT]] derived_sec", derived_sec_x.data(), 32); #endif int offset = set_command_header_noopt(INS_DERIVE_SECRET_KEY); //derivation send_secret(derivation.data(), offset); //index send_u32(output_index, offset); //sec send_secret(sec.data(), offset); finish_and_exchange(offset); offset = 0; //sec key receive_secret(derived_sec.data(), offset); #ifdef DEBUG_HWDEVICE crypto::secret_key derived_sec_clear = decrypt(derived_sec); check32("derive_secret_key", "derived_sec", derived_sec_x.data(), derived_sec_clear.data()); #endif return true; } bool device_ledger::derive_public_key(const crypto::key_derivation &derivation, const std::size_t output_index, const crypto::public_key &pub, crypto::public_key &derived_pub){ auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE const crypto::key_derivation derivation_x = decrypt(derivation); log_hexbuffer("derive_public_key: [[IN]] derivation ", derivation_x.data(), 32); log_message ("derive_public_key: [[IN]] output_index", std::to_string(output_index)); log_hexbuffer("derive_public_key: [[IN]] pub ", pub.data(), 32); crypto::public_key derived_pub_x; debug_device->derive_public_key(derivation_x, output_index, pub, derived_pub_x); log_hexbuffer("derive_public_key: [[OUT]] derived_pub ", derived_pub_x.data(), 32); #endif int offset = set_command_header_noopt(INS_DERIVE_PUBLIC_KEY); //derivation send_secret(derivation.data(), offset); //index send_u32(output_index, offset); //pub send_bytes(pub.data(), 32, offset); finish_and_exchange(offset); //pub key receive_bytes(derived_pub.data(), 32); #ifdef DEBUG_HWDEVICE check32("derive_public_key", "derived_pub", derived_pub_x.data(), derived_pub.data()); #endif return true; } bool device_ledger::secret_key_to_public_key(const crypto::secret_key &sec, crypto::public_key &pub) { auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE const crypto::secret_key sec_x = decrypt(sec); log_hexbuffer("secret_key_to_public_key: [[IN]] sec ", sec_x.data(), 32); crypto::public_key pub_x; bool rc = debug_device->secret_key_to_public_key(sec_x, pub_x); log_hexbuffer("secret_key_to_public_key: [[OUT]] pub", pub_x.data(), 32); if (!rc){ log_message("FAIL secret_key_to_public_key", "secret_key rejected"); } #endif int offset = set_command_header_noopt(INS_SECRET_KEY_TO_PUBLIC_KEY); //sec key send_secret(sec.data(), offset); finish_and_exchange(offset); //pub key receive_bytes(pub.data(), 32); #ifdef DEBUG_HWDEVICE check32("secret_key_to_public_key", "pub", pub_x.data(), pub.data()); #endif return true; } bool device_ledger::generate_key_image(const crypto::public_key &pub, const crypto::secret_key &sec, crypto::key_image &image){ auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE const crypto::secret_key sec_x = decrypt(sec); log_hexbuffer("generate_key_image: [[IN]] pub ", pub.data(), 32); log_hexbuffer("generate_key_image: [[IN]] sec ", sec_x.data(), 32); crypto::key_image image_x; debug_device->generate_key_image(pub, sec_x, image_x); log_hexbuffer("generate_key_image: [[OUT]] image ", image_x.data(), 32); #endif int offset = set_command_header_noopt(INS_GEN_KEY_IMAGE); //pub send_bytes(pub.data(), 32, offset); //sec send_secret(sec.data(), offset); finish_and_exchange(offset); //key image receive_bytes(image.data(), 32); #ifdef DEBUG_HWDEVICE check32("generate_key_image", "image", image_x.data(), image.data()); #endif return true; } bool device_ledger::generate_key_image_signature(const crypto::key_image& image, const crypto::public_key& pub, const crypto::secret_key& sec, crypto::signature& sig) { auto locks = tools::unique_locks(device_locker, command_locker); int offset = set_command_header_noopt(INS_GEN_KEY_IMAGE_SIGNATURE); send_bytes(image.data(), 32, offset); send_bytes(pub.data(), 32, offset); send_secret(sec.data(), offset); finish_and_exchange(offset); receive_bytes(reinterpret_cast(&sig), 64); #ifdef DEBUG_HWDEVICE // We can't check the actual returned signature byte values because a random component is // involved, but we *can* attempt to verify the signature bool good = crypto::check_key_image_signature(image, pub, sig); log_hexbuffer("generate_key_image_signature: key image", image.data(), 32); log_hexbuffer("generate_key_image_signature: pubkey", pub.data(), 32); log_hexbuffer("generate_key_image_signature: signature.c", sig.c(), 32); log_hexbuffer("generate_key_image_signature: signature.r", sig.r(), 32); log_message("generate_key_image_signature: signature returned from device", good ? "passed" : "FAILED"); #endif return true; } bool device_ledger::generate_unlock_signature(const crypto::public_key& pub, const crypto::secret_key& sec, crypto::signature& sig) { auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE log_hexbuffer("generate_unlock_signature: [[IN]] pub ", pub.data(), 32); const crypto::secret_key sec_x = decrypt(sec); log_hexbuffer("generate_unlock_signature: [[IN]] sec ", sec_x.data(), 32); #endif // Ask for confirmation: int offset = set_command_header_noopt(INS_GEN_UNLOCK_SIGNATURE); CHECK_AND_ASSERT_THROW_MES(finish_and_exchange(offset, true) == SW_OK, "Unlock denied on device."); // If we got permission then we can ask for the actual signature: offset = set_command_header_noopt(INS_GEN_UNLOCK_SIGNATURE, 1); send_bytes(pub.data(), 32, offset); send_secret(sec.data(), offset); finish_and_exchange(offset); receive_bytes(reinterpret_cast(&sig), 64); #ifdef DEBUG_HWDEVICE // We can't check the actual returned signature byte values because a random component is // involved, but we *can* attempt to verify the signature bool good = crypto::check_signature(cryptonote::tx_extra_tx_key_image_unlock::HASH, pub, sig); log_hexbuffer("generate_unlock_signature: signature.c", sig.c(), 32); log_hexbuffer("generate_unlock_signature: signature.r", sig.r(), 32); log_message("generate_unlock_signature: signature returned from device", good ? "passed" : "FAILED"); #endif return true; } bool device_ledger::generate_ons_signature(std::string_view sig_data, const cryptonote::account_keys& keys, const cryptonote::subaddress_index& index, crypto::signature& sig) { // Initialize (prompts the user): int offset = set_command_header_noopt(INS_GEN_ONS_SIGNATURE); CHECK_AND_ASSERT_THROW_MES(finish_and_exchange(offset, true) == SW_OK, "ONS denied on device."); // Send ons signature data to be hashed: exchange_multipart_data(INS_GEN_ONS_SIGNATURE, 1, sig_data, BLAKE2B_HASH_CHUNK_SIZE); // Send the subaddr indices and get the signature: offset = set_command_header_noopt(INS_GEN_ONS_SIGNATURE, 2); send_bytes(&index, sizeof(index), offset); finish_and_exchange(offset); receive_bytes(reinterpret_cast(&sig), 64); return true; } /* ======================================================================= */ /* TRANSACTION */ /* ======================================================================= */ void device_ledger::generate_tx_proof(const crypto::hash &prefix_hash, const crypto::public_key &R, const crypto::public_key &A, const std::optional &B, const crypto::public_key &D, const crypto::secret_key &r, crypto::signature &sig) { auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE const crypto::hash prefix_hash_x = prefix_hash; const crypto::public_key R_x = R; const crypto::public_key A_x = A; const std::optional B_x = B; const crypto::public_key D_x = D; const crypto::secret_key r_x = decrypt(r); crypto::signature sig_x; log_hexbuffer("generate_tx_proof: [[IN]] prefix_hash ", prefix_hash_x.data(), 32); log_hexbuffer("generate_tx_proof: [[IN]] R ", R_x.data(), 32); log_hexbuffer("generate_tx_proof: [[IN]] A ", A_x.data(), 32); if (B_x) log_hexbuffer("generate_tx_proof: [[IN]] B ", B_x->data(), 32); log_hexbuffer("generate_tx_proof: [[IN]] D ", D_x.data(), 32); log_hexbuffer("generate_tx_proof: [[IN]] r ", r_x.data(), 32); #endif int offset = set_command_header(INS_GET_TX_PROOF); //options buffer_send[offset++] = B ? 0x01 : 0x00; send_bytes(prefix_hash.data(), 32, offset); // prefix_hash send_bytes(R.data(), 32, offset); // R send_bytes(A.data(), 32, offset); // A send_bytes(B ? B->data() : crypto::null.data(), 32, offset); // B send_bytes(D.data(), 32, offset); // D send_secret(r.data(), offset); // r finish_and_exchange(offset); offset = 0; receive_bytes(sig.c(), 32, offset); receive_bytes(sig.r(), 32, offset); #ifdef DEBUG_HWDEVICE log_hexbuffer("GENERATE_TX_PROOF: **c** ", sig.c(), 32); log_hexbuffer("GENERATE_TX_PROOF: **r** ", sig.r(), 32); debug_device->generate_tx_proof(prefix_hash_x, R_x, A_x, B_x, D_x, r_x, sig_x); log::debug(logcat, "FAIL is normal if random is not fixed in proof"); check32("generate_tx_proof", "c", sig_x.c(), sig.c()); check32("generate_tx_proof", "r", sig_x.r(), sig.r()); #endif } bool device_ledger::open_tx(crypto::secret_key &tx_key, cryptonote::txversion txversion, cryptonote::txtype txtype) { auto locks = tools::unique_locks(device_locker, command_locker, *this); key_map.clear(); hmac_map.clear(); tx_in_progress = true; int offset = set_command_header_noopt(INS_OPEN_TX, 0x01); send_u16(static_cast(txversion), offset); send_u16(static_cast(txtype), offset); finish_and_exchange(offset); //skip R, receive: r, r_hmac, fake_a, a_hmac, fake_b, hmac_b unsigned char tmp[32]; offset = 32; receive_secret(tx_key.data(), offset); receive_secret(tmp, offset); receive_secret(tmp, offset); #ifdef DEBUG_HWDEVICE const crypto::secret_key r_x = decrypt(tx_key); log_hexbuffer("open_tx: [[OUT]] R ", buffer_recv, 32); log_hexbuffer("open_tx: [[OUT]] r ", r_x.data(), 32); #endif return true; } // Sends data in chunks using the given ins/p1 values, with p2 set to a sequence // 1->2->....->255->1->...->0 so that the hw device can make sure it didn't miss anything. // (Note the wrapping goes 255->1, not 255->0, as 0 always indicates the last piece). // Max chunk size is 254 bytes. void device_ledger::exchange_multipart_data(uint8_t ins, uint8_t p1, std::string_view data, uint8_t chunk_size) { assert(chunk_size <= 254); size_t cnt = 0; while (!data.empty()) { auto piece = data.substr(0, chunk_size); data.remove_prefix(piece.size()); if (data.empty()) cnt = 0; // Signals last piece else cnt = cnt == 255 ? 1 : cnt + 1; int offset = set_command_header_noopt(ins, p1, cnt); send_bytes(piece.data(), piece.size(), offset); finish_and_exchange(offset); } } void device_ledger::get_transaction_prefix_hash(const cryptonote::transaction_prefix& tx, crypto::hash& h) { auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE crypto::hash h_x; debug_device->get_transaction_prefix_hash(tx, h_x); log::debug(logcat, "get_transaction_prefix_hash [[IN]] h_x/1 {}", h_x); #endif // As of protocol version 4, we send: // - tx version // - tx type (transfer, registration, stake, ons) // - tx lock time (if the tx has multiple lock times this will be the largest one) // We then wait for confirmation from the device, and if we get it we continue by sending the // data in chunks. The last chunk will have a p2 subparameter of 0; // otherwise the p2 subparameters are in order starting at 1 (and, if we wrap, we go from 255->1). std::string tx_prefix; try { tx_prefix = serialization::dump_binary(const_cast(tx)); } catch (const std::exception& e) { auto err = "unable to serialize transaction prefix: "_format(e.what()); log::error(logcat, err); throw std::runtime_error{err}; } unsigned char* send = buffer_send + set_command_header_noopt(INS_PREFIX_HASH, 1); // version as varint tools::write_varint(send, static_cast>(tx.version)); // transaction type as varint tools::write_varint(send, static_cast>(tx.type)); // Transactions can have multiple unlock times; find the longest one and send that uint64_t max_unlock = 0; for (size_t i = 0; i < tx.vout.size(); i++) max_unlock = std::max(max_unlock, tx.get_unlock_time(i)); tools::write_varint(send, max_unlock); length_send = send - buffer_send; buffer_send[4] = length_send - 5; exchange(true); // hash the full prefix exchange_multipart_data(INS_PREFIX_HASH, 2, tx_prefix, KECCAK_HASH_CHUNK_SIZE); receive_bytes(h.data(), 32); #ifdef DEBUG_HWDEVICE check8("prefix_hash", "h", h_x.data(), h.data()); #endif } bool device_ledger::encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) { auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE const crypto::secret_key secret_key_x = decrypt(secret_key); log_hexbuffer("encrypt_payment_id: [[IN]] payment_id ", payment_id.data(), 32); log_hexbuffer("encrypt_payment_id: [[IN]] public_key ", public_key.data(), 32); log_hexbuffer("encrypt_payment_id: [[IN]] secret_key ", secret_key_x.data(), 32); crypto::hash8 payment_id_x = payment_id; debug_device->encrypt_payment_id(payment_id_x, public_key, secret_key_x); log_hexbuffer("encrypt_payment_id: [[OUT]] payment_id ", payment_id_x.data(), 32); #endif int offset = set_command_header_noopt(INS_ENCRYPT_PAYMENT_ID); send_bytes(public_key.data(), 32, offset); // pub send_secret(secret_key.data(), offset); //sec send_bytes(payment_id.data(), 8, offset); //id finish_and_exchange(offset); receive_bytes(payment_id.data(), 8); #ifdef DEBUG_HWDEVICE check8("stealth", "payment_id", payment_id_x.data(), payment_id.data()); #endif return true; } bool device_ledger::generate_output_ephemeral_keys( const size_t tx_version, bool& found_change, const cryptonote::account_keys& sender_account_keys, const crypto::public_key& txkey_pub, const crypto::secret_key& tx_key, const cryptonote::tx_destination_entry& dst_entr, const std::optional& change_addr, const size_t output_index, const bool need_additional_txkeys, const std::vector& additional_tx_keys, std::vector& additional_tx_public_keys, std::vector& amount_keys, crypto::public_key& out_eph_public_key) { auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE cryptonote::account_keys sender_account_keys_x = decrypt(sender_account_keys); std::memmove(sender_account_keys_x.m_view_secret_key.data(), dbg_viewkey.data(), 32); const crypto::secret_key tx_key_x = decrypt(tx_key); std::vector additional_tx_keys_x; for (const auto& k: additional_tx_keys) { additional_tx_keys_x.push_back(decrypt(k)); } log_message("generate_output_ephemeral_keys: [[IN]] tx_version", std::to_string(tx_version)); //log_hexbuffer("generate_output_ephemeral_keys: [[IN]] sender_account_keys.view", sender_account_keys.m_sview_secret_key.data(), 32); //log_hexbuffer("generate_output_ephemeral_keys: [[IN]] sender_account_keys.spend", sender_account_keys.m_spend_secret_key.data(), 32); log_hexbuffer("generate_output_ephemeral_keys: [[IN]] txkey_pub", txkey_pub.data(), 32); log_hexbuffer("generate_output_ephemeral_keys: [[IN]] tx_key", tx_key_x.data(), 32); log_hexbuffer("generate_output_ephemeral_keys: [[IN]] dst_entr.view", dst_entr.addr.m_view_public_key.data(), 32); log_hexbuffer("generate_output_ephemeral_keys: [[IN]] dst_entr.spend", dst_entr.addr.m_spend_public_key.data(), 32); if (change_addr) { log_hexbuffer("generate_output_ephemeral_keys: [[IN]] change_addr.view", change_addr->addr.m_view_public_key.data(), 32); log_hexbuffer("generate_output_ephemeral_keys: [[IN]] change_addr.spend", change_addr->addr.m_spend_public_key.data(), 32); } log_message("generate_output_ephemeral_keys: [[IN]] output_index", std::to_string(output_index)); log_message("generate_output_ephemeral_keys: [[IN]] need_additional_txkeys", std::to_string(need_additional_txkeys)); if (need_additional_txkeys) { log_hexbuffer("generate_output_ephemeral_keys: [[IN]] additional_tx_keys[oi]", additional_tx_keys_x[output_index].data(), 32); } std::vector additional_tx_public_keys_x; std::vector amount_keys_x; crypto::public_key out_eph_public_key_x; debug_device->generate_output_ephemeral_keys(tx_version, found_change, sender_account_keys_x, txkey_pub, tx_key_x, dst_entr, change_addr, output_index, need_additional_txkeys, additional_tx_keys_x, additional_tx_public_keys_x, amount_keys_x, out_eph_public_key_x); if(need_additional_txkeys) { log_hexbuffer("additional_tx_public_keys_x: [[OUT]] additional_tx_public_keys_x", additional_tx_public_keys_x.back().data(), 32); } log_hexbuffer("generate_output_ephemeral_keys: [[OUT]] amount_keys ", amount_keys_x.back().bytes, 32); log_hexbuffer("generate_output_ephemeral_keys: [[OUT]] out_eph_public_key ", out_eph_public_key_x.data(), 32); #endif CHECK_AND_ASSERT_THROW_MES(tx_version > 1, "TX version not supported"< 1) { CHECK_AND_ASSERT_THROW_MES(recv_len>=32, "Not enough data from device"); crypto::secret_key scalar1; receive_secret(scalar1.data(), offset); amount_keys.push_back(rct::sk2rct(scalar1)); recv_len -= 32; } CHECK_AND_ASSERT_THROW_MES(recv_len>=32, "Not enough data from device"); receive_bytes(out_eph_public_key.data(), 32, offset); recv_len -= 32; if (need_additional_txkeys) { CHECK_AND_ASSERT_THROW_MES(recv_len>=32, "Not enough data from device"); receive_bytes(additional_txkey.pub.data(), 32, offset); additional_tx_public_keys.push_back(additional_txkey.pub); recv_len -= 32; } // add ABPkeys add_output_key_mapping(dst_entr.addr.m_view_public_key, dst_entr.addr.m_spend_public_key, dst_entr.is_subaddress, is_change, need_additional_txkeys, output_index, amount_keys.back(), out_eph_public_key); #ifdef DEBUG_HWDEVICE rct::key amount_back = decrypt(amount_keys.back()); log_hexbuffer("generate_output_ephemeral_keys: clear amount_key", amount_back.bytes, 32); check32("generate_output_ephemeral_keys", "amount_key", amount_keys_x.back().bytes, amount_back.bytes); if (need_additional_txkeys) { check32("generate_output_ephemeral_keys", "additional_tx_key", additional_tx_public_keys_x.back().data(), additional_tx_public_keys.back().data()); } check32("generate_output_ephemeral_keys", "out_eph_public_key", out_eph_public_key_x.data(), out_eph_public_key.data()); #endif return true; } bool device_ledger::add_output_key_mapping(const crypto::public_key &Aout, const crypto::public_key &Bout, const bool is_subaddress, const bool is_change, const bool need_additional, const size_t real_output_index, const rct::key &amount_key, const crypto::public_key &out_eph_public_key) { key_map.add(ABPkeys(rct::pk2rct(Aout),rct::pk2rct(Bout), is_subaddress, is_change, need_additional, real_output_index, rct::pk2rct(out_eph_public_key), amount_key)); return true; } rct::key device_ledger::genCommitmentMask(const rct::key &AKout) { #ifdef DEBUG_HWDEVICE rct::key mask_x = debug_device->genCommitmentMask(decrypt(AKout)); #endif rct::key mask; int offset = set_command_header_noopt(INS_GEN_COMMITMENT_MASK); // AKout send_secret(AKout.bytes, offset); finish_and_exchange(offset); receive_bytes(mask.bytes, 32); #ifdef DEBUG_HWDEVICE check32("genCommitmentMask", "mask", mask_x.bytes, mask.bytes); #endif return mask; } bool device_ledger::ecdhEncode(rct::ecdhTuple& unmasked, const rct::key& AKout, bool short_amount) { auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE const rct::key AKout_x = decrypt(AKout); rct::ecdhTuple unmasked_x = unmasked; debug_device->ecdhEncode(unmasked_x, AKout_x, short_amount); #endif int offset = set_command_header(INS_BLIND); buffer_send[offset++] = short_amount ? 0x02 : 0x00; //options send_secret(AKout.bytes, offset); // AKout send_bytes(unmasked.mask.bytes, 32, offset); //mask k send_bytes(unmasked.amount.bytes, 32, offset); //value v finish_and_exchange(offset); offset = 0; receive_bytes(unmasked.amount.bytes, 32, offset); receive_bytes(unmasked.mask.bytes, 32, offset); #ifdef DEBUG_HWDEVICE log::debug(logcat, "ecdhEncode: Akout: {}", AKout_x); check32("ecdhEncode", "amount", unmasked_x.amount.bytes, unmasked.amount.bytes); check32("ecdhEncode", "mask", unmasked_x.mask.bytes, unmasked.mask.bytes); log_hexbuffer("Blind AKV input", &buffer_recv[64], 3*32); #endif return true; } bool device_ledger::ecdhDecode(rct::ecdhTuple& masked, const rct::key& AKout, bool short_amount) { auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE const rct::key AKout_x = decrypt(AKout); rct::ecdhTuple masked_x = masked; debug_device->ecdhDecode(masked_x, AKout_x, short_amount); #endif int offset = set_command_header(INS_UNBLIND); buffer_send[offset++] = short_amount ? 0x02 : 0x00; //options send_secret(AKout.bytes, offset); // AKout send_bytes(masked.mask.bytes, 32, offset); //mask k send_bytes(masked.amount.bytes, 32, offset); //value v finish_and_exchange(offset); offset = 0; receive_bytes(masked.amount.bytes, 32, offset); receive_bytes(masked.mask.bytes, 32, offset); #ifdef DEBUG_HWDEVICE log::debug(logcat, "ecdhEncode: Akout: {}", AKout_x); check32("ecdhDecode", "amount", masked_x.amount.bytes, masked.amount.bytes); check32("ecdhDecode", "mask", masked_x.mask.bytes, masked.mask.bytes); #endif return true; } bool device_ledger::clsag_prehash(const std::string &data, size_t inputs_size, size_t outputs_size, const rct::keyV &hashes, const rct::ctkeyV &outPk, rct::key &prehash) { auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE rct::key prehash_x; debug_device->clsag_prehash(data, inputs_size, outputs_size, hashes, outPk, prehash_x); if (inputs_size) { log_message("clsag_prehash", (std::string("inputs_size not null: ") + std::to_string(inputs_size)).c_str()); } key_map.log(); #endif // ====== u8 type, varint txnfee ====== int offset = set_command_header(INS_VALIDATE, 0x01, 0x01); //options buffer_send[offset++] = (inputs_size == 0) ? 0x00 : OPTION_MORE_DATA; buffer_send[offset++] = data[0]; // txnfee size_t data_offset = 1; while (data[data_offset] & 0x80) buffer_send[offset++] = data[data_offset++]; buffer_send[offset++] = data[data_offset++]; // check fee user input CHECK_AND_ASSERT_THROW_MES(finish_and_exchange(offset, true) == SW_OK, "Fee denied on device."); auto type = static_cast(data[0]); CHECK_AND_ASSERT_THROW_MES(type == rct::RCTType::CLSAG, "non-CLSAG generation not supported"); // ====== Aout, Bout, AKout, C, v, k ====== size_t kv_offset = data_offset; size_t C_offset = kv_offset + 8 * outputs_size; for (size_t i = 0; i < outputs_size; i++) { ABPkeys outKeys; bool found; found = key_map.find(outPk[i].dest, outKeys); if (!found) { log_hexbuffer("Pout not found", outPk[i].dest.bytes, 32); CHECK_AND_ASSERT_THROW_MES(found, "Pout not found"); } offset = set_command_header(INS_VALIDATE, 0x02, i+1); // options buffer_send[offset++] = (i < outputs_size-1 ? OPTION_MORE_DATA : 0) | 0x02; buffer_send[offset++] = outKeys.is_subaddress; //is_subaddress buffer_send[offset++] = outKeys.is_change_address; //is_change_address send_bytes(outKeys.Aout.bytes, 32, offset); //Aout send_bytes(outKeys.Bout.bytes, 32, offset); //Bout send_secret(outKeys.AKout.bytes, offset); //AKout send_bytes(&data[C_offset], 32, offset); //C C_offset += 32; send_bytes(crypto::null.data(), 32, offset); // k send_bytes(&data[kv_offset], 8, offset); // v kv_offset += 8; send_bytes(crypto::null.data(), 24, offset); // v padding // check transaction user input CHECK_AND_ASSERT_THROW_MES(finish_and_exchange(offset, true) == SW_OK, "Transaction denied on device."); #ifdef DEBUG_HWDEVICE log_hexbuffer("Prehash AKV input", &buffer_recv[64], 3*32); #endif } // ====== C[], message, proof====== C_offset = kv_offset; for (size_t i = 0; i < outputs_size; i++) { offset = set_command_header(INS_VALIDATE, 0x03, i+1); buffer_send[offset++] = 0x80; //options send_bytes(&data[C_offset], 32, offset); //C C_offset += 32; finish_and_exchange(offset); } offset = set_command_header_noopt(INS_VALIDATE, 0x03, outputs_size+1); send_bytes(hashes[0].bytes, 32, offset); //message send_bytes(hashes[2].bytes, 32, offset); //proof finish_and_exchange(offset); receive_bytes(prehash.bytes, 32); #ifdef DEBUG_HWDEVICE check32("clsag_prehash", "prehash", prehash_x.bytes, prehash.bytes); #endif return true; } bool device_ledger::clsag_prepare(const rct::key &p, const rct::key &z, rct::key &I, rct::key &D, const rct::key &H, rct::key &a, rct::key &aG, rct::key &aH) { auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE const rct::key p_x = decrypt(p); rct::key I_x; rct::key D_x; rct::key a_x; rct::key aG_x; rct::key aH_x; hw::get_device("default").clsag_prepare(p_x, z, I_x, D_x, H, a_x, aG_x, aH_x); #endif /* rct::skpkGen(a,aG); // aG = a*G rct::scalarmultKey(aH,H,a); // aH = a*H rct::scalarmultKey(I,H,p); // I = p*H rct::scalarmultKey(D,H,z); // D = z*H */ int offset = set_command_header_noopt(INS_CLSAG, 1); send_secret(p.bytes, offset); //p send_bytes(z.bytes, 32, offset); //z send_bytes(H.bytes, 32, offset); //H finish_and_exchange(offset); offset = 0; receive_secret(a.bytes, offset); //a receive_bytes(aG.bytes, 32, offset); //aG receive_bytes(aH.bytes, 32, offset); //aH receive_bytes(I.bytes, 32, offset); //I = pH receive_bytes(D.bytes, 32, offset); //D = zH #ifdef DEBUG_HWDEVICE check32("clsag_prepare", "I", I_x.bytes, I.bytes); check32("clsag_prepare", "D", D_x.bytes, D.bytes); check32("clsag_prepare", "a", a_x.bytes, a.bytes); check32("clsag_prepare", "aG", aG_x.bytes, aG.bytes); check32("clsag_prepare", "aH", aH_x.bytes, aH.bytes); #endif return true; } bool device_ledger::clsag_hash(const rct::keyV &keydata, rct::key &hash) { auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE rct::key hash_x; debug_device->clsag_hash(keydata, hash_x); #endif std::string_view data{reinterpret_cast(keydata.data()), sizeof(rct::key)*keydata.size()}; exchange_multipart_data(INS_CLSAG, 2, data, KECCAK_HASH_CHUNK_SIZE); //c/hash receive_bytes(hash.bytes, 32); #ifdef DEBUG_HWDEVICE check32("clsag_hash", "hash", hash_x.bytes, hash.bytes); #endif return true; } bool device_ledger::clsag_sign(const rct::key &c, const rct::key &a, const rct::key &p, const rct::key &z, const rct::key &mu_P, const rct::key &mu_C, rct::key &s) { auto locks = tools::unique_locks(device_locker, command_locker); #ifdef DEBUG_HWDEVICE rct::key s_x; debug_device->clsag_sign(c, decrypt(a), decrypt(p), z, mu_P, mu_C, s_x); #endif /* rct::key s0_p_mu_P; sc_mul(s0_p_mu_P.bytes,mu_P.bytes,p.bytes); rct::key s0_add_z_mu_C; sc_muladd(s0_add_z_mu_C.bytes,mu_C.bytes,z.bytes,s0_p_mu_P.bytes); sc_mulsub(s.bytes,c.bytes,s0_add_z_mu_C.bytes,a.bytes); */ int offset = set_command_header_noopt(INS_CLSAG, 3); send_secret(a.bytes, offset); //a send_secret(p.bytes, offset); //p send_bytes(z.bytes, 32, offset); //z send_bytes(mu_P.bytes, 32, offset); //mu_P send_bytes(mu_C.bytes, 32, offset); //mu_C finish_and_exchange(offset); receive_bytes(s.bytes, 32); //s #ifdef DEBUG_HWDEVICE check32("clsag_sign", "s", s_x.bytes, s.bytes); #endif return true; } bool device_ledger::update_staking_tx_secret_key(crypto::secret_key& key) { auto locks = tools::unique_locks(device_locker, command_locker); // This will fail if this isn't an open stake tx. send_simple(INS_GET_TX_SECRET_KEY); // The ledger provides us with the (unencrypted) tx secret key if we're allowed to have it receive_bytes(key.data(), 32); return true; } bool device_ledger::close_tx() { auto locks = tools::unique_locks(device_locker, command_locker); send_simple(INS_CLOSE_TX); key_map.clear(); hmac_map.clear(); tx_in_progress = false; unlock(); return true; } /* ---------------------------------------------------------- */ void register_all(std::map>& registry) { registry.emplace("Ledger", std::make_unique()); registry.emplace("LedgerTCP", std::make_unique(io::ledger_tcp{})); } #else //WITH_DEVICE_LEDGER void register_all(std::map> &) {} #endif //WITH_DEVICE_LEDGER }