ledger: lazy view key loading

Defer loading of private view key from device until first time it is needed.
Do not fail if this fails. This has two effects:

1. The prompt to export the view key is only needed once when creating a `cryptonote::account_base`
2. The call to `connect()` doesn't fail if the user decies to not export the viewkey, and thus usage of the device without exporting view keys is possible

This is a small convenience for Ledger users, but will be an even larger convenience for Ledger device testing
This commit is contained in:
jeffro256
2026-06-04 13:52:33 -05:00
parent 1a65f025ea
commit 03018306a9
2 changed files with 38 additions and 11 deletions
+31 -11
View File
@@ -303,6 +303,7 @@ namespace hw {
this->reset_buffer(); this->reset_buffer();
this->mode = NONE; this->mode = NONE;
this->has_view_key = false; this->has_view_key = false;
this->requested_view_key = false;
this->tx_in_progress = false; this->tx_in_progress = false;
MDEBUG( "Device "<<this->id <<" Created"); MDEBUG( "Device "<<this->id <<" Created");
} }
@@ -440,6 +441,25 @@ namespace hw {
} }
} }
bool device_ledger::soft_request_view_key() {
const std::lock_guard lock_dev(this->device_locker); // recursive is needed due to call to get_secret_keys()
if (this->has_view_key)
return true;
else if (this->requested_view_key)
return false;
this->requested_view_key = true;
try
{
crypto::secret_key v, s;
this->get_secret_keys(v, s);
}
catch (...)
{ /* error message is logged in get_secret_keys()*/ }
return this->has_view_key;
}
bool device_ledger::reset() { bool device_ledger::reset() {
reset_buffer(); reset_buffer();
int offset = set_command_header_noopt(INS_RESET); int offset = set_command_header_noopt(INS_RESET);
@@ -540,9 +560,6 @@ namespace hw {
cryptonote::account_public_address pubkey; cryptonote::account_public_address pubkey;
this->get_public_address(pubkey); this->get_public_address(pubkey);
#endif #endif
crypto::secret_key vkey;
crypto::secret_key skey;
this->get_secret_keys(vkey,skey);
return true; return true;
} }
@@ -553,6 +570,9 @@ namespace hw {
bool device_ledger::disconnect() { bool device_ledger::disconnect() {
hw_device.disconnect(); hw_device.disconnect();
this->viewkey.scrub();
this->has_view_key = false;
this->requested_view_key = false;
return true; return true;
} }
@@ -688,7 +708,7 @@ namespace hw {
#ifdef DEBUG_HWDEVICE #ifdef DEBUG_HWDEVICE
const crypto::public_key pub_x = pub; const crypto::public_key pub_x = pub;
crypto::key_derivation derivation_x; crypto::key_derivation derivation_x;
if ((this->mode == TRANSACTION_PARSE) && has_view_key) { if ((this->mode == TRANSACTION_PARSE) && this->soft_request_view_key()) {
derivation_x = derivation; derivation_x = derivation;
} else { } else {
derivation_x = hw::ledger::decrypt(derivation); derivation_x = hw::ledger::decrypt(derivation);
@@ -703,7 +723,7 @@ namespace hw {
log_hexbuffer("derive_subaddress_public_key: [[OUT]] derived_pub", derived_pub_x.data, 32); log_hexbuffer("derive_subaddress_public_key: [[OUT]] derived_pub", derived_pub_x.data, 32);
#endif #endif
if ((this->mode == TRANSACTION_PARSE) && has_view_key) { if ((this->mode == TRANSACTION_PARSE) && this->soft_request_view_key()) {
//If we are in TRANSACTION_PARSE, the given derivation has been retrieved unencrypted (without the help //If we are in TRANSACTION_PARSE, the given derivation has been retrieved unencrypted (without the help
//of the device), so continue that way. //of the device), so continue that way.
MDEBUG( "derive_subaddress_public_key : PARSE mode with known viewkey"); MDEBUG( "derive_subaddress_public_key : PARSE mode with known viewkey");
@@ -739,7 +759,7 @@ namespace hw {
} }
crypto::public_key device_ledger::get_subaddress_spend_public_key(const cryptonote::account_keys& keys, const cryptonote::subaddress_index &index) { crypto::public_key device_ledger::get_subaddress_spend_public_key(const cryptonote::account_keys& keys, const cryptonote::subaddress_index &index) {
if (has_view_key) { if (this->soft_request_view_key()) {
cryptonote::account_keys keys_{keys}; cryptonote::account_keys keys_{keys};
keys_.m_view_secret_key = this->viewkey; keys_.m_view_secret_key = this->viewkey;
return this->controle_device->get_subaddress_spend_public_key(keys_, index); return this->controle_device->get_subaddress_spend_public_key(keys_, index);
@@ -796,7 +816,7 @@ namespace hw {
} }
cryptonote::account_public_address device_ledger::get_subaddress(const cryptonote::account_keys& keys, const cryptonote::subaddress_index &index) { cryptonote::account_public_address device_ledger::get_subaddress(const cryptonote::account_keys& keys, const cryptonote::subaddress_index &index) {
if (has_view_key) { if (this->soft_request_view_key()) {
cryptonote::account_keys keys_{keys}; cryptonote::account_keys keys_{keys};
keys_.m_view_secret_key = this->viewkey; keys_.m_view_secret_key = this->viewkey;
return this->controle_device->get_subaddress(keys_, index); return this->controle_device->get_subaddress(keys_, index);
@@ -1063,7 +1083,7 @@ namespace hw {
log_hexbuffer("generate_key_derivation: [[OUT]] derivation", derivation_x.data, 32); log_hexbuffer("generate_key_derivation: [[OUT]] derivation", derivation_x.data, 32);
#endif #endif
if ((this->mode == TRANSACTION_PARSE) && has_view_key) { if ((this->mode == TRANSACTION_PARSE) && this->soft_request_view_key()) {
//A derivation is requested in PARSE mode and we have the 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. //so do that without the device and return the derivation unencrypted.
MDEBUG( "generate_key_derivation : PARSE mode with known viewkey"); MDEBUG( "generate_key_derivation : PARSE mode with known viewkey");
@@ -1091,7 +1111,7 @@ namespace hw {
} }
#ifdef DEBUG_HWDEVICE #ifdef DEBUG_HWDEVICE
crypto::key_derivation derivation_clear ; crypto::key_derivation derivation_clear ;
if ((this->mode == TRANSACTION_PARSE) && has_view_key) { if ((this->mode == TRANSACTION_PARSE) && this->soft_request_view_key()) {
derivation_clear = derivation; derivation_clear = derivation;
} else { } else {
derivation_clear = hw::ledger::decrypt(derivation); derivation_clear = hw::ledger::decrypt(derivation);
@@ -1315,7 +1335,7 @@ namespace hw {
bool device_ledger::derive_view_tag(const crypto::key_derivation &derivation, const std::size_t output_index, crypto::view_tag &view_tag){ bool device_ledger::derive_view_tag(const crypto::key_derivation &derivation, const std::size_t output_index, crypto::view_tag &view_tag){
#ifdef DEBUG_HWDEVICE #ifdef DEBUG_HWDEVICE
crypto::key_derivation derivation_x; crypto::key_derivation derivation_x;
if ((this->mode == TRANSACTION_PARSE) && has_view_key) { if ((this->mode == TRANSACTION_PARSE) && this->soft_request_view_key()) {
derivation_x = derivation; derivation_x = derivation;
} else { } else {
derivation_x = hw::ledger::decrypt(derivation); derivation_x = hw::ledger::decrypt(derivation);
@@ -1328,7 +1348,7 @@ namespace hw {
log_hexbuffer("derive_view_tag: [[OUT]] view_tag ", &view_tag_x.data, 1); log_hexbuffer("derive_view_tag: [[OUT]] view_tag ", &view_tag_x.data, 1);
#endif #endif
if ((this->mode == TRANSACTION_PARSE) && has_view_key) { if ((this->mode == TRANSACTION_PARSE) && this->soft_request_view_key()) {
//If we are in TRANSACTION_PARSE, the given derivation has been retrieved unencrypted (without the help //If we are in TRANSACTION_PARSE, the given derivation has been retrieved unencrypted (without the help
//of the device), so continue that way. //of the device), so continue that way.
MDEBUG( "derive_view_tag : PARSE mode with known viewkey"); MDEBUG( "derive_view_tag : PARSE mode with known viewkey");
+7
View File
@@ -160,6 +160,12 @@ namespace hw {
void send_simple(unsigned char ins, unsigned char p1 = 0x00); void send_simple(unsigned char ins, unsigned char p1 = 0x00);
void send_secret(const unsigned char sec[32], int &offset); void send_secret(const unsigned char sec[32], int &offset);
void receive_secret(unsigned char sec[32], int &offset); void receive_secret(unsigned char sec[32], int &offset);
/**
* @brief Request user export view privkey if not requested before, then return whether it has been exported
*
* Thread-safe. Result in this->viewkey iff this->has_view_key. I/O is performed iff !this->requested_view_key
*/
bool soft_request_view_key();
bool tx_in_progress; bool tx_in_progress;
@@ -174,6 +180,7 @@ namespace hw {
// To speed up blockchain parsing the view key maybe handle here. // To speed up blockchain parsing the view key maybe handle here.
crypto::secret_key viewkey = crypto::null_skey; crypto::secret_key viewkey = crypto::null_skey;
bool has_view_key = false; bool has_view_key = false;
bool requested_view_key = false;
device *controle_device; device *controle_device;