descriptor: add support for liquid confidential descriptors
What changed, and why it matters
This commit adds support for Liquid confidential descriptors to the Blockstream Jade hardware wallet. It is a feature addition, not a fix for a known vulnerability. The code introduces new validation rules that reject non-SLIP77 blinding keys on Liquid and reject confidential descriptors on Bitcoin. It also shows the blinding key on the device screen during wallet registration and management. The feature is gated so it is only active in debug builds unless explicitly enabled at compile time.
Review as a normal feature commit. Verify that descriptor_allow_liquid() gating is correct in production builds, that blinding_key memory is always freed, and that the new validation rules cannot be bypassed by malformed descriptors. No immediate security response is indicated by the diff alone.
Security signals we found
Feature-gated Liquid confidential descriptor support
New validation rejecting non-SLIP77 blinding keys on Liquid
New validation rejecting confidential descriptors on Bitcoin mainchain
Blinding key extracted from descriptor and shown in device UI
PSBT signing-value count excludes @B blinding-key placeholder
Evidence from the diff
The change extends descriptor parsing to handle Elements/Liquid confidential descriptors using ct(slip77(),
Changed components
main/descriptor.cmain/descriptor.hmain/process/dashboard.cmain/process/get_receive_address.cmain/process/register_descriptor.cmain/process/sign_psbt.cmain/process/sign_tx.cmain/selfcheck.cmain/ui/descriptor.cInspect captured patch +160 / −41
diff --git a/main/descriptor.c b/main/descriptor.c
index 19bc4cc..2a45e21 100644
--- a/main/descriptor.c
+++ b/main/descriptor.c
@@ -264,6 +264,31 @@ static bool parse_descriptor(const char* name, const descriptor_data_t* descript
goto fail;
}
+ const bool is_liquid = network_id != NETWORK_NONE && network_is_liquid(network_id);
+ if (is_liquid || (features & WALLY_MS_ANY_BLINDING_KEY)) {
+ if (!descriptor_allow_liquid()) {
+ // TODO: remove this check once liquid descriptors are enabled
+ JADE_LOGE("Descriptor '%s' appears to be for liquid network, but liquid descriptors are disabled", name);
+ *errmsg = "Descriptor wallets not supported on liquid network";
+ goto fail;
+ }
+ if (is_liquid) {
+ // Only ct(slip77(),<expression>) currently supported for Liquid.
+ // TODO: Add support for other blinding key types (ELIP-150, ELIP-151).
+ if (!(features & WALLY_MS_IS_SLIP77)) {
+ JADE_LOGE("Descriptor '%s' appears to be a non-ct/non-slip77 descriptor", name);
+ *errmsg = "Descriptor must use slip77 blinding for liquid network";
+ goto fail;
+ }
+ } else if (network_id != NETWORK_NONE) {
+ if (features & WALLY_MS_ANY_BLINDING_KEY) {
+ JADE_LOGE("Descriptor '%s' appears to be confidential", name);
+ *errmsg = "Descriptor must not be confidential for bitcoin network";
+ goto fail;
+ }
+ }
+ }
+
// Return the descriptor
*output = d;
return true;
@@ -273,16 +298,33 @@ fail:
return false;
}
-// NOTE: signers should either be sufficient to hold details for all signers, or NULL if
-// the only value of interest is the number of signers in the descriptor.
+bool descriptor_allow_liquid(void)
+{
+// Currently only available in debug builds or if explicitly enabled
+// by defining LIQUID_DESCRIPTORS when building.
+#if defined(LIQUID_DESCRIPTORS) || defined(CONFIG_DEBUG_MODE)
+ return true;
+#else
+ return false;
+#endif
+}
+
+// Parse the descriptor and get signer information
+// If `signers` is NULL then only the number of signers is returned in `written` (and `blinding_key` is ignored).
+// If `blinding_key` is non-NULL, then the blinding key hex value is returned (if present in descriptor)
+// - caller must free the blinding key with wally_free_string().
bool descriptor_get_signers(const char* name, const descriptor_data_t* descriptor, const network_t network_id,
- descriptor_type_t* deduced_type, signer_t* signers, const size_t signers_len, size_t* written, const char** errmsg)
+ descriptor_type_t* deduced_type, signer_t* signers, size_t signers_len, size_t* written, char** blinding_key,
+ const char** errmsg)
{
JADE_ASSERT(name);
JADE_ASSERT(descriptor);
JADE_ASSERT(!deduced_type || *deduced_type == DESCRIPTOR_TYPE_UNKNOWN);
JADE_ASSERT(!signers == !signers_len); // both or neither
JADE_INIT_OUT_SIZE(written);
+ if (blinding_key) {
+ JADE_INIT_OUT_PPTR(blinding_key);
+ }
JADE_INIT_OUT_PPTR(errmsg);
bool retval = false;
@@ -378,6 +420,22 @@ bool descriptor_get_signers(const char* name, const descriptor_data_t* descripto
JADE_WALLY_VERIFY(wally_free_string(str));
}
+ if (blinding_key) {
+ uint32_t features = 0;
+ if (wally_descriptor_get_features(d, &features) != WALLY_OK) {
+ *errmsg = "Failed to inspect descriptor features";
+ goto cleanup;
+ }
+
+ // Check if descriptor has SLIP77 blinding key before attempting to fetch it
+ if (features & WALLY_MS_IS_SLIP77) {
+ if (wally_descriptor_get_key(d, WALLY_MS_BLINDING_KEY_INDEX, blinding_key) != WALLY_OK || !*blinding_key) {
+ *errmsg = "Failed to get blinding key from descriptor";
+ goto cleanup;
+ }
+ }
+ }
+
// Return the number of signers written
*written = num_keys;
retval = true;
diff --git a/main/descriptor.h b/main/descriptor.h
index 5a779f5..2599a09 100644
--- a/main/descriptor.h
+++ b/main/descriptor.h
@@ -49,9 +49,15 @@ typedef struct _descriptor_data {
// Get total length of string values
size_t string_values_len(const string_value_t* datavalues, size_t num_values);
+bool descriptor_allow_liquid(void);
+
// Parse the descriptor and get signer information
+// If `signers` is NULL then only the number of signers is returned in `written` (and `blinding_key` is ignored).
+// If `blinding_key` is non-NULL, then the blinding key hex value is returned (if present in descriptor)
+// - caller must free the blinding key with wally_free_string().
bool descriptor_get_signers(const char* name, const descriptor_data_t* descriptor, const network_t network_id,
- descriptor_type_t* type, signer_t* signers, size_t signers_len, size_t* written, const char** errmsg);
+ descriptor_type_t* type, signer_t* signers, size_t signers_len, size_t* written, char** blinding_key,
+ const char** errmsg);
// Generate an address using a descriptor/miniscript expression
// On success output must be freed with wally_free_string()
diff --git a/main/process/dashboard.c b/main/process/dashboard.c
index bb3a443..9d227d3 100644
--- a/main/process/dashboard.c
+++ b/main/process/dashboard.c
@@ -222,8 +222,9 @@ bool show_multisig_activity(const char* multisig_name, bool is_sorted, size_t th
const uint8_t* wallet_fingerprint, size_t wallet_fingerprint_len, bool initial_confirmation, bool overwriting,
bool is_valid);
bool show_descriptor_activity(const char* descriptor_name, const descriptor_data_t* descriptor,
- const signer_t* signer_details, size_t num_signer_details, const uint8_t* wallet_fingerprint,
- size_t wallet_fingerprint_len, bool initial_confirmation, bool overwriting, bool is_valid);
+ const char* blinding_key, const signer_t* signer_details, size_t num_signer_details,
+ const uint8_t* wallet_fingerprint, size_t wallet_fingerprint_len, network_t network_id, bool initial_confirmation,
+ bool overwriting, bool is_valid);
gui_activity_t* make_session_activity(void);
gui_activity_t* make_ble_activity(gui_view_node_t** ble_status_item);
@@ -1077,6 +1078,7 @@ static void handle_registered_wallets(void)
uint8_t fingerprint[BIP32_KEY_FINGERPRINT_LEN];
wallet_get_fingerprint(fingerprint, sizeof(fingerprint));
signer_t* const signer_details = JADE_CALLOC(MAX_ALLOWED_SIGNERS, sizeof(signer_t));
+ char* blinding_key = NULL;
done = false;
while (!done) {
@@ -1193,10 +1195,16 @@ static void handle_registered_wallets(void)
// No option to export (atm)
JADE_ASSERT(ev_id == BTN_VIEW_WALLET);
+ // Free any blinding_key from a previous pass through the loop
+ if (blinding_key) {
+ JADE_WALLY_VERIFY(wally_free_string(blinding_key));
+ blinding_key = NULL;
+ }
+
// Get signer info from descriptor
size_t num_signer_details = 0;
if (!descriptor_get_signers(wallet_name, &descriptor, NETWORK_NONE, NULL, signer_details,
- MAX_ALLOWED_SIGNERS, &num_signer_details, &errmsg)) {
+ MAX_ALLOWED_SIGNERS, &num_signer_details, &blinding_key, &errmsg)) {
JADE_LOGE("Failed to load signer information from descriptor data");
await_error_2("Unable to load", "signer details");
continue;
@@ -1205,8 +1213,9 @@ static void handle_registered_wallets(void)
// We are not confirming or writing-to-storage
const bool initial_confirmation = false;
const bool overwriting = false;
- if (!show_descriptor_activity(wallet_name, &descriptor, signer_details, num_signer_details, fingerprint,
- sizeof(fingerprint), initial_confirmation, overwriting, is_valid)) {
+ if (!show_descriptor_activity(wallet_name, &descriptor, blinding_key, signer_details,
+ num_signer_details, fingerprint, sizeof(fingerprint), NETWORK_NONE, initial_confirmation,
+ overwriting, is_valid)) {
// Delete record ?
done = offer_delete_registered_wallet(wallet_name, is_multisig);
}
@@ -1214,8 +1223,11 @@ static void handle_registered_wallets(void)
}
}
- // Free any signer details
+ // Free any signer / blinding key details
free(signer_details);
+ if (blinding_key) {
+ JADE_WALLY_VERIFY(wally_free_string(blinding_key));
+ }
}
static void set_wallet_erase_pin(void)
diff --git a/main/process/get_receive_address.c b/main/process/get_receive_address.c
index 974119c..b56d3f4 100644
--- a/main/process/get_receive_address.c
+++ b/main/process/get_receive_address.c
@@ -93,8 +93,7 @@ void get_receive_address_process(void* process_ptr)
master_blinding_key_len = sizeof(multisig_master_blinding_key);
}
} else if (rpc_has_field_data("descriptor_name", ¶ms)) {
- // Not valid for liquid wallets atm
- if (isLiquid) {
+ if (isLiquid && !descriptor_allow_liquid()) {
jade_process_reject_message(
process, CBOR_RPC_BAD_PARAMETERS, "Descriptor wallets not supported on liquid network");
goto cleanup;
@@ -125,6 +124,12 @@ void get_receive_address_process(void* process_ptr)
jade_process_reject_message(process, CBOR_RPC_BAD_PARAMETERS, "Failed to generate valid descriptor script");
goto cleanup;
}
+
+ if (confidential) {
+ // Use the wallet's own master blinding key (the descriptor's @B blinding key)
+ p_master_blinding_key = keychain_get()->master_unblinding_key;
+ master_blinding_key_len = sizeof(keychain_get()->master_unblinding_key);
+ }
} else {
uint32_t path[MAX_PATH_LEN];
size_t path_len = 0;
diff --git a/main/process/register_descriptor.c b/main/process/register_descriptor.c
index 386d577..5a7d2d3 100644
--- a/main/process/register_descriptor.c
+++ b/main/process/register_descriptor.c
@@ -17,8 +17,9 @@
#include <sodium/utils.h>
bool show_descriptor_activity(const char* descriptor_name, const descriptor_data_t* descriptor,
- const signer_t* signer_details, size_t num_signer_details, const uint8_t* wallet_fingerprint,
- size_t wallet_fingerprint_len, bool initial_confirmation, bool overwriting, bool is_valid);
+ const char* blinding_key, const signer_t* signer_details, size_t num_signer_details,
+ const uint8_t* wallet_fingerprint, size_t wallet_fingerprint_len, network_t network_id, bool initial_confirmation,
+ bool overwriting, bool is_valid);
// Function to validate descriptor and persist the record
static int register_descriptor(
@@ -32,8 +33,7 @@ static int register_descriptor(
JADE_ASSERT(descriptor->script_len < sizeof(descriptor->script));
JADE_ASSERT(descriptor->num_values <= MAX_ALLOWED_SIGNERS);
- // Not valid for liquid wallets atm
- if (network_is_liquid(network_id)) {
+ if (network_is_liquid(network_id) && !descriptor_allow_liquid()) {
*errmsg = "Descriptor wallets not supported on liquid network";
return CBOR_RPC_BAD_PARAMETERS;
}
@@ -49,11 +49,12 @@ static int register_descriptor(
uint8_t* const registration = JADE_MALLOC(registration_len);
signer_t* const signers = JADE_CALLOC(MAX_ALLOWED_SIGNERS, sizeof(signer_t));
size_t num_signers = 0;
+ char* blinding_key = NULL;
// Get signers - this also yields the type
descriptor_type_t deduced_type = DESCRIPTOR_TYPE_UNKNOWN;
if (!descriptor_get_signers(descriptor_name, descriptor, network_id, &deduced_type, signers, MAX_ALLOWED_SIGNERS,
- &num_signers, errmsg)) {
+ &num_signers, &blinding_key, errmsg)) {
JADE_LOGE("Failed to extract signer information from descriptor");
retval = CBOR_RPC_BAD_PARAMETERS;
goto cleanup;
@@ -124,8 +125,8 @@ static int register_descriptor(
// Check to see whether user accepted or declined
const bool is_valid = true;
const bool initial_confirmation = true;
- if (!show_descriptor_activity(descriptor_name, descriptor, signers, num_signers, fingerprint, sizeof(fingerprint),
- initial_confirmation, overwriting, is_valid)) {
+ if (!show_descriptor_activity(descriptor_name, descriptor, blinding_key, signers, num_signers, fingerprint,
+ sizeof(fingerprint), network_id, initial_confirmation, overwriting, is_valid)) {
JADE_LOGW("User declined to register descriptor");
*errmsg = "User declined to register descriptor";
retval = CBOR_RPC_USER_CANCELLED;
@@ -144,6 +145,9 @@ static int register_descriptor(
cleanup:
free(signers);
+ if (blinding_key) {
+ JADE_WALLY_VERIFY(wally_free_string(blinding_key));
+ }
free(registration);
return retval;
}
diff --git a/main/process/sign_psbt.c b/main/process/sign_psbt.c
index 5b6770b..69a8dc3 100644
--- a/main/process/sign_psbt.c
+++ b/main/process/sign_psbt.c
@@ -363,8 +363,18 @@ static bool verify_descriptor_script_matches(const char* descriptor_name, const
// Ensure number of pubkeys is not less than the number of xpub signers
// (xpubs can be reused with different paths, but they cannot be left unused)
+ // Note: for Elements confidential descriptors, @B is the blinding key (not a signing xpub),
+ // so we count only values that are NOT the blinding-key placeholder.
+ size_t num_signing_values = 0;
+ for (size_t i = 0; i < descriptor->num_values; ++i) {
+ const string_value_t* const val = &descriptor->values[i];
+ const bool is_blinding_key = val->key_len == 2 && !strncmp(val->key, "@B", 2);
+ if (!is_blinding_key) {
+ ++num_signing_values;
+ }
+ }
const size_t num_keys = key_iter_get_num_keys(iter);
- if (descriptor->num_values > num_keys) {
+ if (num_signing_values > num_keys) {
JADE_LOGD("Mismatch in number of signatories");
return false;
}
@@ -494,7 +504,6 @@ static bool psbt_update_outputs(const network_t network_id, struct wally_psbt* p
const bool is_liquid = network_is_liquid(network_id);
JADE_ASSERT(!multisig_data || !descriptor); // cannot have both
- JADE_ASSERT(!is_liquid || !descriptor); // atm do not support liquid descriptors
key_iter iter; // Holds any public key in use
@@ -925,8 +934,7 @@ int sign_psbt(jade_process_t* process, CborValue* params, const network_t networ
multisig_data = NULL;
}
- // NOTE: descriptors not supported for elements atm
- if (!multisig_data && !for_liquid) {
+ if (!multisig_data && (!for_liquid || descriptor_allow_liquid())) {
descriptor = JADE_MALLOC(sizeof(descriptor_data_t));
if (get_suitable_descriptor_record(&iter, &path[path_tail_start], path_tail_len, utxo->script,
utxo->script_len, network_id, wallet_name, sizeof(wallet_name), descriptor)) {
diff --git a/main/process/sign_tx.c b/main/process/sign_tx.c
index 7167509..2e1e561 100644
--- a/main/process/sign_tx.c
+++ b/main/process/sign_tx.c
@@ -172,8 +172,7 @@ static bool params_signing_outputs(jade_process_t* process, const CborValue* par
goto cleanup;
}
} else if (rpc_has_field_data("descriptor_name", &arrayItem)) {
- // Not valid for liquid wallets atm
- if (network_is_liquid(network_id)) {
+ if (network_is_liquid(network_id) && !descriptor_allow_liquid()) {
errmsg = "Descriptor wallets not supported on liquid network";
goto cleanup;
}
diff --git a/main/selfcheck.c b/main/selfcheck.c
index d50633a..6640c5d 100644
--- a/main/selfcheck.c
+++ b/main/selfcheck.c
@@ -946,14 +946,14 @@ static bool test_miniscript_descriptors(void)
"NuUfgYqsahxTLGJq2");
// Check parsing and key iteration
- ret = descriptor_get_signers("A0", &desc, TESTNET, NULL, NULL, 0, &num_signers, &errmsg);
+ ret = descriptor_get_signers("A0", &desc, TESTNET, NULL, NULL, 0, &num_signers, NULL, &errmsg);
if (!ret) {
FAIL();
}
if (num_signers != 3) {
FAIL();
}
- ret = descriptor_get_signers("A0", &desc, TESTNET, &desc.type, signers, 3, &num_signers, &errmsg);
+ ret = descriptor_get_signers("A0", &desc, TESTNET, &desc.type, signers, 3, &num_signers, NULL, &errmsg);
if (!ret) {
FAIL();
}
@@ -1049,14 +1049,14 @@ static bool test_miniscript_descriptors(void)
"HyV8DomjCjJRT3d57");
// Check parsing and key iteration
- ret = descriptor_get_signers("B0", &desc, TESTNET, NULL, NULL, 0, &num_signers, &errmsg);
+ ret = descriptor_get_signers("B0", &desc, TESTNET, NULL, NULL, 0, &num_signers, NULL, &errmsg);
if (!ret) {
FAIL();
}
if (num_signers != 3) {
FAIL();
}
- ret = descriptor_get_signers("B0", &desc, TESTNET, &desc.type, signers, 3, &num_signers, &errmsg);
+ ret = descriptor_get_signers("B0", &desc, TESTNET, &desc.type, signers, 3, &num_signers, NULL, &errmsg);
if (!ret) {
FAIL();
}
@@ -1143,14 +1143,14 @@ static bool test_miniscript_descriptors(void)
"VHdMw7p8MnQycgAV8");
// Check parsing and key iteration
- ret = descriptor_get_signers("b0", &desc, TESTNET, NULL, NULL, 0, &num_signers, &errmsg);
+ ret = descriptor_get_signers("b0", &desc, TESTNET, NULL, NULL, 0, &num_signers, NULL, &errmsg);
if (!ret) {
FAIL();
}
if (num_signers != 3) {
FAIL();
}
- ret = descriptor_get_signers("b0", &desc, TESTNET, &desc.type, signers, 3, &num_signers, &errmsg);
+ ret = descriptor_get_signers("b0", &desc, TESTNET, &desc.type, signers, 3, &num_signers, NULL, &errmsg);
if (!ret) {
FAIL();
}
@@ -1233,14 +1233,14 @@ static bool test_miniscript_descriptors(void)
",older(30)))))");
// Check parsing and key iteration
- ret = descriptor_get_signers("C0", &desc, TESTNET, NULL, NULL, 0, &num_signers, &errmsg);
+ ret = descriptor_get_signers("C0", &desc, TESTNET, NULL, NULL, 0, &num_signers, NULL, &errmsg);
if (!ret) {
FAIL();
}
if (num_signers != 2) {
FAIL();
}
- ret = descriptor_get_signers("C0", &desc, TESTNET, &desc.type, signers, 3, &num_signers, &errmsg);
+ ret = descriptor_get_signers("C0", &desc, TESTNET, &desc.type, signers, 3, &num_signers, NULL, &errmsg);
if (!ret) {
FAIL();
}
@@ -1312,14 +1312,14 @@ static bool test_miniscript_descriptors(void)
"preHp6Xhgd42JH/0/*),a:1)");
// Check parsing and key iteration
- ret = descriptor_get_signers("L0", &desc, TESTNET, NULL, NULL, 0, &num_signers, &errmsg);
+ ret = descriptor_get_signers("L0", &desc, TESTNET, NULL, NULL, 0, &num_signers, NULL, &errmsg);
if (!ret) {
FAIL();
}
if (num_signers != 1) {
FAIL();
}
- ret = descriptor_get_signers("L0", &desc, TESTNET, &desc.type, signers, 3, &num_signers, &errmsg);
+ ret = descriptor_get_signers("L0", &desc, TESTNET, &desc.type, signers, 3, &num_signers, NULL, &errmsg);
if (!ret) {
FAIL();
}
diff --git a/main/ui/descriptor.c b/main/ui/descriptor.c
index eb223df..d2f4d68 100644
--- a/main/ui/descriptor.c
+++ b/main/ui/descriptor.c
@@ -285,8 +285,9 @@ static bool show_final_descriptor_summary_activity(
}
bool show_descriptor_activity(const char* descriptor_name, const descriptor_data_t* descriptor,
- const signer_t* signer_details, const size_t num_signer_details, const uint8_t* wallet_fingerprint,
- const size_t wallet_fingerprint_len, const bool initial_confirmation, const bool overwriting, const bool is_valid)
+ const char* blinding_key, const signer_t* signer_details, const size_t num_signer_details,
+ const uint8_t* wallet_fingerprint, const size_t wallet_fingerprint_len, const network_t network_id,
+ const bool initial_confirmation, const bool overwriting, const bool is_valid)
{
JADE_ASSERT(descriptor_name);
JADE_ASSERT(descriptor);
@@ -296,13 +297,17 @@ bool show_descriptor_activity(const char* descriptor_name, const descriptor_data
JADE_ASSERT(!overwriting || initial_confirmation);
JADE_ASSERT(!initial_confirmation || is_valid);
+ // If a blinding key is present it occupies screen 1 (signers are shifted by 1)
+ const bool has_blinding_key = blinding_key != NULL;
+ const uint8_t blinding_key_screen_offset = has_blinding_key ? 1 : 0;
+
// NOTE: because the descriptor potentially has a lot of signers/parameters and info to display
// we deal with the data values one at a time, rather than creating them all up-front.
gui_activity_t* act_clear = gui_make_activity();
bool confirmed = false;
- uint8_t screen = 0; // 0 = initial summary, 1->n = signers, n+1 = final summary
+ uint8_t screen = 0; // 0 = initial summary, 1 = blinding key (if present), 2..n = signers, n+1 = final summary
while (true) {
- JADE_ASSERT(screen <= num_signer_details + 1);
+ JADE_ASSERT(screen <= num_signer_details + blinding_key_screen_offset + 1);
if (screen == 0) {
confirmed = show_view_descriptor_activity(descriptor_name, descriptor, initial_confirmation, is_valid);
if (confirmed && is_valid) {
@@ -312,7 +317,7 @@ bool show_descriptor_activity(const char* descriptor_name, const descriptor_data
// either details not valid or record has been rejected
break;
}
- } else if (screen > num_signer_details) {
+ } else if (screen > num_signer_details + blinding_key_screen_offset) {
confirmed = show_final_descriptor_summary_activity(descriptor_name, initial_confirmation, overwriting);
if (confirmed) {
// User pressed 'confirm'
@@ -321,11 +326,33 @@ bool show_descriptor_activity(const char* descriptor_name, const descriptor_data
// User pressed 'back'
--screen;
}
+ } else if (has_blinding_key && screen == 1) {
+ // Free all existing activities before blinding key screen
+ gui_set_current_activity_ex(act_clear, true);
+
+ // Show blinding key screen - reuse signer prev/next events for back/forward navigation
+ btn_data_t hdrbtns[] = { { .txt = "=", .font = JADE_SYMBOLS_16x16_FONT, .ev_id = BTN_SIGNER_PREV },
+ { .txt = ">", .font = JADE_SYMBOLS_16x16_FONT, .ev_id = BTN_SIGNER_NEXT } };
+ const char* message[] = { blinding_key };
+ gui_activity_t* const act_bk = make_show_message_activity(message, 1, "Blinding Key", hdrbtns, 2, NULL, 0);
+ gui_set_activity_initial_selection(hdrbtns[1].btn);
+
+ gui_set_current_activity(act_bk);
+ for (;;) {
+ const int32_t ev_id = gui_activity_wait_button(act_bk, BTN_SIGNER_NEXT);
+ if (ev_id == BTN_SIGNER_NEXT) {
+ ++screen;
+ break;
+ } else if (ev_id == BTN_SIGNER_PREV) {
+ --screen;
+ break;
+ }
+ }
} else {
// Free all existing activities between signers/parameters
gui_set_current_activity_ex(act_clear, true);
- const uint8_t signer_index = screen - 1;
+ const uint8_t signer_index = screen - 1 - blinding_key_screen_offset;
const signer_t* signer = signer_details + signer_index;
const bool is_this_signer = !sodium_memcmp(signer->fingerprint, wallet_fingerprint, wallet_fingerprint_len);
if (show_signer_activity(signer, signer_index + 1, num_signer_details, is_this_signer)) {
Why this scored 29/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.