oxen-core/src/device/device_ledger.cpp

1984 lines
77 KiB
C++

// 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 "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 <chrono>
#include <boost/endian/conversion.hpp>
#ifdef DEBUG_HWDEVICE
#include <sodium/crypto_generichash.h>
#endif
namespace hw::ledger {
#ifdef WITH_DEVICE_LEDGER
#undef OXEN_DEFAULT_LOG_CATEGORY
#define OXEN_DEFAULT_LOG_CATEGORY "device.ledger"
/* ===================================================================== */
/* === Debug ==== */
/* ===================================================================== */
namespace {
bool apdu_verbose = true;
#define LEDGER_STATUS(status) {status, #status##sv}
constexpr std::pair<unsigned int, std::string_view> 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<sz; i++) {
log_message(" keymap", std::to_string(i));
log_hexbuffer(" Aout", ABP[i].Aout.bytes, 32);
log_hexbuffer(" Bout", ABP[i].Bout.bytes, 32);
log_message (" is_sub", std::to_string(ABP[i].is_subaddress));
log_message (" index", std::to_string(ABP[i].index));
log_hexbuffer(" Pout", ABP[i].Pout.bytes, 32);
}
}
#endif
/* ===================================================================== */
/* === Internal Helpers ==== */
/* ===================================================================== */
static bool is_fake_view_key(const crypto::secret_key &sec) {
return sec == crypto::null_skey;
}
bool operator==(const crypto::key_derivation &d0, const crypto::key_derivation &d1) {
static_assert(sizeof(crypto::key_derivation) == 32, "key_derivation must be 32 bytes");
return !crypto_verify_32(reinterpret_cast<const unsigned char*>(&d0), reinterpret_cast<const unsigned char*>(&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<uint8_t, std::string_view> 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<io::hid>(0x0101, 0x05, 64, 2000)} {
id = device_id++;
reset_buffer();
has_view_key = false;
tx_in_progress = false;
MDEBUG("Device " << id << " Created");
}
device_ledger::device_ledger(io::ledger_tcp&& tcp) : hw_device{std::make_unique<io::ledger_tcp>(std::move(tcp))} {
id = device_id++;
reset_buffer();
has_view_key = false;
tx_in_progress = false;
MDEBUG("Device " << id << " (tcp) created");
}
device_ledger::~device_ledger() {
release();
MDEBUG("Device " << id << " Destroyed");
}
/* ======================================================================= */
/* LOCKER */
/* ======================================================================= */
//lock the device for a long sequence
void device_ledger::lock() {
MDEBUG("Ask for LOCKING for device " << name << " in thread ");
device_locker.lock();
MDEBUG("Device " << name << " LOCKed");
}
//lock the device for a long sequence
bool device_ledger::try_lock() {
MDEBUG("Ask for LOCKING(try) for device " << name << " in thread ");
bool r = device_locker.try_lock();
MDEBUG("Device " << name << (r ? "" : " not") << " LOCKed(try)");
return r;
}
//unlock the device after a long sequence
void device_ledger::unlock() {
MDEBUG("Ask for UNLOCKING for device " << name << " in thread ");
device_locker.unlock();
MDEBUG("Device " << name << " UNLOCKed");
}
/* ======================================================================= */
/* 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]) << "] ";
MDEBUG("CMD: " << cmd.str() << oxenmq::to_hex(buffer_send + 5, buffer_send + length_send));
last_cmd = std::chrono::steady_clock::now();
}
}
void device_ledger::logRESP() {
if (apdu_verbose)
MDEBUG("RESP (+" << tools::short_duration(std::chrono::steady_clock::now() - last_cmd) << "): "
<< oxenmq::to_hex(std::string_view{reinterpret_cast<const char*>(&sw), sizeof(sw)})
<< ' ' << oxenmq::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) {
boost::endian::native_to_big_inplace(x);
send_bytes(&x, 4, offset);
}
void device_ledger::send_u16(uint16_t x, int& offset) {
boost::endian::native_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);
boost::endian::big_to_native_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) {
MDEBUG("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) {
MDEBUG("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<uint8_t, 3> 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 "<disconnected:" + name + ">";
return name;
}
void device_ledger::set_address(std::string_view addr) {
if (addr.empty() || !hw_device)
return;
auto* tcp = dynamic_cast<io::ledger_tcp*>(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();
MDEBUG("Device " << id <<" HIDUSB inited");
return true;
}
static const std::vector<io::hid_conn_params> known_devices {
{0x2c97, 0x0001, 0, 0xffa0},
{0x2c97, 0x0004, 0, 0xffa0},
};
bool device_ledger::connect() {
disconnect();
if (auto* hid_io = dynamic_cast<io::hid*>(hw_device.get()))
hid_io->connect(known_devices);
else if (auto* tcp = dynamic_cast<io::ledger_tcp*>(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<const char*>(buffer_recv), 4};
auto device_nettype = static_cast<cryptonote::network_type>(buffer_recv[4]);
MDEBUG("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<unsigned char>(m);
finish_and_exchange(offset);
break;
}
case mode::TRANSACTION_PARSE:
case mode::NONE:
break;
}
MDEBUG("Switch to mode: " << +static_cast<unsigned char>(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);
MDEBUG((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<crypto::hash8> &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_hash8.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.
MDEBUG("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<crypto::public_key> device_ledger::get_subaddress_spend_public_keys(const cryptonote::account_keys &keys, uint32_t account, uint32_t begin, uint32_t end) {
std::vector<crypto::public_key> 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;
}
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.
MDEBUG( "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<crypto::public_key> &additional_tx_pub_keys, const crypto::key_derivation &main_derivation, const std::vector<crypto::key_derivation> &additional_derivations) {
const crypto::public_key *pkey = nullptr;
if (derivation == main_derivation) {
pkey = &tx_pub_key;
MDEBUG("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];
MDEBUG("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_skey, 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<char*>(&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.data, 32);
log_hexbuffer("generate_key_image_signature: signature.r", sig.r.data, 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<char*>(&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.data, 32);
log_hexbuffer("generate_unlock_signature: signature.r", sig.r.data, 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<char*>(&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<crypto::public_key> &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<crypto::public_key> 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_pkey.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.data, 32, offset);
receive_bytes(sig.r.data, 32, offset);
#ifdef DEBUG_HWDEVICE
log_hexbuffer("GENERATE_TX_PROOF: **c** ", sig.c.data, sizeof(sig.c.data));
log_hexbuffer("GENERATE_TX_PROOF: **r** ", sig.r.data, sizeof(sig.r.data));
debug_device->generate_tx_proof(prefix_hash_x, R_x, A_x, B_x, D_x, r_x, sig_x);
MDEBUG("FAIL is normal if random is not fixed in proof");
check32("generate_tx_proof", "c", sig_x.c.data, sig.c.data);
check32("generate_tx_proof", "r", sig_x.r.data, sig.r.data);
#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<uint16_t>(txversion), offset);
send_u16(static_cast<uint16_t>(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);
MDEBUG("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<cryptonote::transaction_prefix&>(tx));
} catch (const std::exception& e) {
ASSERT_MES_AND_THROW("unable to serialize transaction prefix: " << e.what());
}
unsigned char* send = buffer_send + set_command_header_noopt(INS_PREFIX_HASH, 1);
// version as varint
tools::write_varint(send, static_cast<std::underlying_type_t<cryptonote::txversion>>(tx.version));
// transaction type as varint
tools::write_varint(send, static_cast<std::underlying_type_t<cryptonote::txtype>>(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<cryptonote::tx_destination_entry>& change_addr,
const size_t output_index,
const bool need_additional_txkeys,
const std::vector<crypto::secret_key>& additional_tx_keys,
std::vector<crypto::public_key>& additional_tx_public_keys,
std::vector<rct::key>& 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<crypto::secret_key> 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<crypto::public_key> additional_tx_public_keys_x;
std::vector<rct::key> 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"<<tx_version);
// make additional tx pubkey if necessary
cryptonote::keypair additional_txkey;
if (need_additional_txkeys) {
additional_txkey.sec = additional_tx_keys[output_index];
}
bool &is_change = found_change; // NOTE(oxen): Alias our param into theirs so we don't have to change much code.
if (change_addr && dst_entr == *change_addr && !is_change)
is_change = true; // sending change to yourself; derivation = a*R
int offset = set_command_header_noopt(INS_GEN_TXOUT_KEYS);
send_u32(tx_version, offset); //tx_version
send_secret(tx_key.data, offset); //tx_key
send_bytes(txkey_pub.data, 32, offset); //txkey_pub
send_bytes(dst_entr.addr.m_view_public_key.data, 32, offset); //Aout
send_bytes(dst_entr.addr.m_spend_public_key.data, 32, offset); //Bout
send_u32(output_index, offset); //output index
buffer_send[offset++] = is_change; //is_change
buffer_send[offset++] = dst_entr.is_subaddress; //is_subaddress
buffer_send[offset++] = need_additional_txkeys; //need_additional_key
//additional_tx_key
if (need_additional_txkeys)
send_secret(additional_txkey.sec.data, offset);
finish_and_exchange(offset);
offset = 0;
unsigned int recv_len = length_recv;
//if (tx_version > 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
MDEBUG("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
MDEBUG("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<rct::RCTType>(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_hash.data, 32, offset); // k
send_bytes(&data[kv_offset], 8, offset); // v
kv_offset += 8;
send_bytes(crypto::null_hash.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<const char*>(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<std::string, std::unique_ptr<device>>& registry) {
registry.emplace("Ledger", std::make_unique<device_ledger>());
registry.emplace("LedgerTCP", std::make_unique<device_ledger>(io::ledger_tcp{}));
}
#else //WITH_DEVICE_LEDGER
void register_all(std::map<std::string, std::unique_ptr<device>> &) {}
#endif //WITH_DEVICE_LEDGER
}