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-04-20 21:21:27 -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->mode = NONE;
this->has_view_key = false;
this->requested_view_key = false;
this->tx_in_progress = false;
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() {
reset_buffer();
int offset = set_command_header_noopt(INS_RESET);
@@ -540,9 +560,6 @@ namespace hw {
cryptonote::account_public_address pubkey;
this->get_public_address(pubkey);
#endif
crypto::secret_key vkey;
crypto::secret_key skey;
this->get_secret_keys(vkey,skey);
return true;
}
@@ -553,6 +570,9 @@ namespace hw {
bool device_ledger::disconnect() {
hw_device.disconnect();
this->viewkey.scrub();
this->has_view_key = false;
this->requested_view_key = false;
return true;
}
@@ -688,7 +708,7 @@ namespace hw {
#ifdef DEBUG_HWDEVICE
const crypto::public_key pub_x = pub;
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;
} else {
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);
#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
//of the device), so continue that way.
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) {
if (has_view_key) {
if (this->soft_request_view_key()) {
cryptonote::account_keys keys_{keys};
keys_.m_view_secret_key = this->viewkey;
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) {
if (has_view_key) {
if (this->soft_request_view_key()) {
cryptonote::account_keys keys_{keys};
keys_.m_view_secret_key = this->viewkey;
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);
#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,
//so do that without the device and return the derivation unencrypted.
MDEBUG( "generate_key_derivation : PARSE mode with known viewkey");
@@ -1091,7 +1111,7 @@ namespace hw {
}
#ifdef DEBUG_HWDEVICE
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;
} else {
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){
#ifdef DEBUG_HWDEVICE
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;
} else {
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);
#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
//of the device), so continue that way.
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_secret(const 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;
@@ -174,6 +180,7 @@ namespace hw {
// To speed up blockchain parsing the view key maybe handle here.
crypto::secret_key viewkey = crypto::null_skey;
bool has_view_key = false;
bool requested_view_key = false;
device *controle_device;