From 1e21194f7821f930a806f34b535ab7fe3d38d5c6 Mon Sep 17 00:00:00 2001 From: odudex Date: Fri, 14 Aug 2026 17:26:55 -0300 Subject: [PATCH 1/2] chore: bump cUR and k_quirc --- components/cUR | 2 +- components/k_quirc | 2 +- main/CMakeLists.txt | 5 +++++ main/qr/scanner.c | 29 ++++++++++++++++++++++++++++- simulator/CMakeLists.txt | 3 +++ 5 files changed, 38 insertions(+), 3 deletions(-) diff --git a/components/cUR b/components/cUR index ff221acf..c664113f 160000 --- a/components/cUR +++ b/components/cUR @@ -1 +1 @@ -Subproject commit ff221acffe8ba130d948d7a26a78eef0ead359ac +Subproject commit c664113f9a19ea45009e033694b90890d6ce43db diff --git a/components/k_quirc b/components/k_quirc index 465ebc50..f1887f25 160000 --- a/components/k_quirc +++ b/components/k_quirc @@ -1 +1 @@ -Subproject commit 465ebc508becdb52dce547404c20bcef0dfa831d +Subproject commit f1887f25baee9603027302ef50d382509d6e977b diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 39d87987..e523df94 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -33,6 +33,11 @@ idf_component_register( esp_driver_ppa app_update bootloader_support ) +# Ignoring a warn_unused_result return is a build error, not a warning. The +# k_quirc and cUR entry points carry the attribute and report failure that way +# and nothing else. +target_compile_options(${COMPONENT_LIB} PRIVATE -Werror=unused-result) + if(DEFINED FONT_POLICY_WIDTH AND DEFINED FONT_POLICY_HEIGHT) find_program(PYTHON3_EXECUTABLE python3 REQUIRED) execute_process( diff --git a/main/qr/scanner.c b/main/qr/scanner.c index aa41be9b..5f472f58 100644 --- a/main/qr/scanner.c +++ b/main/qr/scanner.c @@ -7,6 +7,7 @@ #include "../ui/input_helpers.h" #include "../ui/theme_widgets.h" #include "../utils/memory_utils.h" +#include "../utils/secure_mem.h" #include "parser.h" #include #include @@ -143,6 +144,7 @@ static uint8_t *rgb565_gray_lut = NULL; static volatile bool closing = false; static volatile bool scan_completed = false; static volatile bool scan_failed = false; +static const char *volatile scan_failure_msg = NULL; static volatile bool is_fully_initialized = false; static volatile bool destruction_in_progress = false; @@ -334,7 +336,8 @@ static void completion_timer_cb(lv_timer_t *timer) { vTaskDelay(pdMS_TO_TICKS(50)); if (scan_failed) - dialog_show_error_timeout("Invalid QR sequence: checksum mismatch", + dialog_show_error_timeout(scan_failure_msg ? scan_failure_msg + : "Invalid QR sequence", return_callback, 0); else return_callback(); @@ -618,6 +621,22 @@ static void update_decode_roi(qr_decode_roi_t *roi, roi->height = target_side; } +static const char *ur_failure_message(QRPartParser *parser) { + if (!parser || parser->format != FORMAT_UR || !parser->ur_decoder) + return "Invalid QR sequence"; + + switch (ur_decoder_get_state((ur_decoder_t *)parser->ur_decoder)) { + case UR_DECODER_ERROR_INVALID_CHECKSUM: + return "Invalid QR sequence: checksum mismatch"; + case UR_DECODER_ERROR_UNSUPPORTED_SIZE: + return "QR sequence too large to decode"; + case UR_DECODER_NO_RESULT: + return "Invalid QR sequence: no result"; + default: + return "Invalid QR sequence"; + } +} + static void release_decode_frame(uint8_t *frame_buffer) { if (qr_buffer_return_queue) xQueueSend(qr_buffer_return_queue, &frame_buffer, 0); @@ -743,12 +762,18 @@ static void qr_decode_task(void *pvParameters) { } if (qr_parser_is_failed(qr_parser)) { + scan_failure_msg = ur_failure_message(qr_parser); scan_failed = true; break; } } } + // k_quirc clears its own copies on return; the decoded payload - a + // mnemonic or PSBT fragment - now lives only here, on a task stack that + // outlives the scan. + secure_memzero(&qr_result, sizeof(qr_result)); + if (!frame_decoded && roi.active) { if (num_codes > 0) { // A code was detected inside the ROI; decode failures (torn @@ -1177,6 +1202,7 @@ void qr_scanner_page_create(lv_obj_t *parent, void (*return_cb)(void)) { closing = false; scan_completed = false; scan_failed = false; + scan_failure_msg = NULL; is_fully_initialized = false; active_frame_operations = 0; @@ -1267,6 +1293,7 @@ void qr_scanner_page_destroy(void) { } scan_completed = false; scan_failed = false; + scan_failure_msg = NULL; if (camera_event_group) { xEventGroupClearBits(camera_event_group, CAMERA_EVENT_TASK_RUN); diff --git a/simulator/CMakeLists.txt b/simulator/CMakeLists.txt index b1578eb1..94f3fde7 100644 --- a/simulator/CMakeLists.txt +++ b/simulator/CMakeLists.txt @@ -411,6 +411,9 @@ target_compile_options(kern_simulator PRIVATE -Wno-sign-compare -Wno-missing-field-initializers -Wno-type-limits + # Matches the firmware build: the k_quirc and cUR entry points report + # failure only through their return value. + -Werror=unused-result -include mbedtls_compat.h # Hardening — the simulator parses untrusted input (image files, NVS # files, V4L2 frames). These flags are cheap and standard for any host From d00a33729de11ddbf592eaec3c5aa08bd5f6ee72 Mon Sep 17 00:00:00 2001 From: odudex Date: Fri, 14 Aug 2026 18:03:14 -0300 Subject: [PATCH 2/2] fix: enforce warn_unused_result and handle the failures it found cUR and k_quirc now mark their failure-signalling functions warn_unused_result and build with -Werror=unused-result. Kern's call sites into both already checked every return, so adopting the same attribute on Kern's own API was free to try: KERN_WARN_UNUSED_RESULT in main/utils/attributes.h, applied to 165 declarations across core/, qr/ and utils/. It found 42 discarded results. The one that mattered: pin_is_configured() returns false when the module is not initialized, and session_lock_boot_gate() routes that straight to unlock_finished(). pin_init()'s result was dropped in both app_main() and the simulator, so a failed init booted a device that has a PIN set straight past its own PIN gate. Both now fail closed. pin_wipe_all() discarded settings_reset_all(), storage_init() and storage_wipe_flash(), then called esp_restart() regardless, making a failed anti-brute-force wipe indistinguishable from a clean one. Each step now logs and the flash wipe retries once. Its esp_err_t return was fiction since the function never returns, so it is void now. storage_sanitize_id() formatted an uninitialised hash buffer when crypto_sha256() failed, putting stack bytes into a name shown in the UI and used to build a filename. Also handled: pin_remove() at three sites and pin_set_max_failures() reported success the storage had not accepted, registry_remove() and registry_set_label() desynced the UI from the registry, registry_add_from_string() silently dropped autoloaded descriptors, and settings.c now logs every failed write once with the key that could not be stored. Where the caller genuinely cannot act the attribute is left off rather than worked around at the call site, documented in settings.h and encoder.h: settings_set_* persist a preference already applied in session, and qr_create_optimal() returns a parent-owned widget that leaks nothing when discarded. --- main/core/base43.h | 9 +-- main/core/bip322.h | 8 ++- main/core/bip32_path.h | 26 ++++--- main/core/crypto_utils.h | 73 +++++++++++--------- main/core/descriptor_checksum.h | 14 ++-- main/core/descriptor_validator.c | 4 +- main/core/descriptor_validator.h | 9 ++- main/core/fw_update.h | 12 ++-- main/core/kef.h | 11 +-- main/core/key.h | 35 ++++++---- main/core/message_sign.h | 14 ++-- main/core/miniscript_policy.h | 15 ++-- main/core/nvs_secure.c | 17 ++++- main/core/nvs_secure.h | 7 +- main/core/pin.c | 26 +++++-- main/core/pin.h | 37 +++++----- main/core/psbt.h | 38 ++++++---- main/core/psbt_internal.h | 6 +- main/core/registry.c | 4 +- main/core/registry.h | 38 ++++++---- main/core/script_templates.h | 21 +++--- main/core/settings.c | 25 ++++--- main/core/settings.h | 36 ++++++---- main/core/ss_whitelist.h | 44 +++++++----- main/core/storage.c | 12 +++- main/core/storage.h | 54 +++++++++------ main/core/wallet.h | 13 ++-- main/main.c | 16 +++-- main/pages/login/login_scan.c | 7 +- main/pages/pin/pin_settings.c | 13 +++- main/pages/scan/psbt_sign_policy.c | 5 +- main/pages/scan/scan.c | 26 +++---- main/pages/session_lock.c | 19 ++++- main/pages/settings/registered_descriptors.c | 6 +- main/pages/shared/descriptor_loader.c | 5 +- main/pages/store_descriptor.c | 3 +- main/qr/encoder.h | 29 +++++--- main/qr/parser.h | 36 ++++++---- main/qr/scanner.h | 18 ++--- main/qr/viewer.c | 7 +- main/qr/viewer.h | 8 ++- main/utils/attributes.h | 23 ++++++ main/utils/bip39_filter.c | 16 ++--- main/utils/bip39_filter.h | 31 +++++---- simulator/src/main_sim.c | 22 ++++-- 45 files changed, 571 insertions(+), 327 deletions(-) create mode 100644 main/utils/attributes.h diff --git a/main/core/base43.h b/main/core/base43.h index 2438b168..ac0f4f58 100644 --- a/main/core/base43.h +++ b/main/core/base43.h @@ -9,6 +9,7 @@ #ifndef BASE43_H #define BASE43_H +#include "../utils/attributes.h" #include #include #include @@ -21,8 +22,8 @@ * * Returns true on success, false on invalid character or allocation failure. */ -bool base43_decode(const char *str, size_t str_len, uint8_t **out, - size_t *out_len); +KERN_WARN_UNUSED_RESULT bool base43_decode(const char *str, size_t str_len, + uint8_t **out, size_t *out_len); /* * Encode bytes to a base43 string. @@ -33,7 +34,7 @@ bool base43_decode(const char *str, size_t str_len, uint8_t **out, * * Returns true on success, false on allocation failure. */ -bool base43_encode(const uint8_t *data, size_t data_len, char **out, - size_t *out_len); +KERN_WARN_UNUSED_RESULT bool base43_encode(const uint8_t *data, size_t data_len, + char **out, size_t *out_len); #endif /* BASE43_H */ diff --git a/main/core/bip322.h b/main/core/bip322.h index 27a2a3f5..66fc698b 100644 --- a/main/core/bip322.h +++ b/main/core/bip322.h @@ -1,6 +1,7 @@ #ifndef BIP322_H #define BIP322_H +#include "../utils/attributes.h" #include struct wally_psbt; @@ -12,14 +13,15 @@ typedef struct { // True if the PSBT carries the PSBT_GLOBAL_GENERIC_SIGNED_MESSAGE (0x09) // global field, marking it as a BIP322 message-signing request. -bool bip322_detect(const struct wally_psbt *psbt); +KERN_WARN_UNUSED_RESULT bool bip322_detect(const struct wally_psbt *psbt); // Validates a BIP322 to_sign PSBT: structure (version-0 tx, single input at // vout 0, single 0-value OP_RETURN output), and that the input spends the // to_spend transaction committing to the message and the proven script. // On success fills `out` with the message and proven address. -bool bip322_parse(const struct wally_psbt *psbt, bool is_testnet, - bip322_request_t *out); +KERN_WARN_UNUSED_RESULT bool bip322_parse(const struct wally_psbt *psbt, + bool is_testnet, + bip322_request_t *out); void bip322_request_free(bip322_request_t *req); diff --git a/main/core/bip32_path.h b/main/core/bip32_path.h index 4ca1ba7b..ea54cf1e 100644 --- a/main/core/bip32_path.h +++ b/main/core/bip32_path.h @@ -1,6 +1,7 @@ #ifndef BIP32_PATH_H #define BIP32_PATH_H +#include "../utils/attributes.h" #include #include #include @@ -20,18 +21,23 @@ static inline uint32_t bip32_path_u32_le(const unsigned char *bytes) { ((uint32_t)bytes[2] << 16) | ((uint32_t)bytes[3] << 24); } -bool bip32_path_parse(const char *path, uint32_t *components_out, - size_t *depth_out, size_t max_depth); +KERN_WARN_UNUSED_RESULT bool bip32_path_parse(const char *path, + uint32_t *components_out, + size_t *depth_out, + size_t max_depth); -bool bip32_path_format(const uint32_t *components, size_t depth, char *buf, - size_t buf_size); +KERN_WARN_UNUSED_RESULT bool bip32_path_format(const uint32_t *components, + size_t depth, char *buf, + size_t buf_size); -bool bip32_path_from_keypath(const unsigned char *raw_keypath, - size_t raw_keypath_len, uint32_t *components_out, - size_t *depth_out, size_t max_depth); +KERN_WARN_UNUSED_RESULT bool +bip32_path_from_keypath(const unsigned char *raw_keypath, + size_t raw_keypath_len, uint32_t *components_out, + size_t *depth_out, size_t max_depth); -bool bip32_path_format_keypath(const unsigned char *raw_keypath, - size_t raw_keypath_len, char *buf, - size_t buf_size, size_t max_depth); +KERN_WARN_UNUSED_RESULT bool +bip32_path_format_keypath(const unsigned char *raw_keypath, + size_t raw_keypath_len, char *buf, size_t buf_size, + size_t max_depth); #endif // BIP32_PATH_H diff --git a/main/core/crypto_utils.h b/main/core/crypto_utils.h index d3a0086d..e9c8a4b3 100644 --- a/main/core/crypto_utils.h +++ b/main/core/crypto_utils.h @@ -9,6 +9,7 @@ #ifndef CRYPTO_UTILS_H #define CRYPTO_UTILS_H +#include "../utils/attributes.h" #include #include @@ -28,63 +29,68 @@ /* PBKDF2-HMAC-SHA256. * Derives key_len bytes from password + salt with given iteration count. */ -int crypto_pbkdf2_sha256(const uint8_t *password, size_t password_len, - const uint8_t *salt, size_t salt_len, - uint32_t iterations, uint8_t *key_out, size_t key_len); +KERN_WARN_UNUSED_RESULT int +crypto_pbkdf2_sha256(const uint8_t *password, size_t password_len, + const uint8_t *salt, size_t salt_len, uint32_t iterations, + uint8_t *key_out, size_t key_len); /* --- Hashing --- */ /* SHA-256 hash. hash_out must be at least CRYPTO_SHA256_SIZE bytes. */ -int crypto_sha256(const uint8_t *data, size_t data_len, uint8_t *hash_out); +KERN_WARN_UNUSED_RESULT int crypto_sha256(const uint8_t *data, size_t data_len, + uint8_t *hash_out); /* --- AES-256-ECB --- */ /* Encrypt/decrypt in ECB mode. input_len must be a multiple of 16. */ -int crypto_aes_ecb_encrypt(const uint8_t key[CRYPTO_AES_KEY_SIZE], - const uint8_t *input, size_t input_len, - uint8_t *output); +KERN_WARN_UNUSED_RESULT int +crypto_aes_ecb_encrypt(const uint8_t key[CRYPTO_AES_KEY_SIZE], + const uint8_t *input, size_t input_len, uint8_t *output); -int crypto_aes_ecb_decrypt(const uint8_t key[CRYPTO_AES_KEY_SIZE], - const uint8_t *input, size_t input_len, - uint8_t *output); +KERN_WARN_UNUSED_RESULT int +crypto_aes_ecb_decrypt(const uint8_t key[CRYPTO_AES_KEY_SIZE], + const uint8_t *input, size_t input_len, uint8_t *output); /* --- AES-256-CBC --- */ /* Encrypt/decrypt in CBC mode. input_len must be a multiple of 16. * iv is not modified (copied internally). */ -int crypto_aes_cbc_encrypt(const uint8_t key[CRYPTO_AES_KEY_SIZE], - const uint8_t iv[CRYPTO_AES_IV_SIZE], - const uint8_t *input, size_t input_len, - uint8_t *output); +KERN_WARN_UNUSED_RESULT int +crypto_aes_cbc_encrypt(const uint8_t key[CRYPTO_AES_KEY_SIZE], + const uint8_t iv[CRYPTO_AES_IV_SIZE], + const uint8_t *input, size_t input_len, uint8_t *output); -int crypto_aes_cbc_decrypt(const uint8_t key[CRYPTO_AES_KEY_SIZE], - const uint8_t iv[CRYPTO_AES_IV_SIZE], - const uint8_t *input, size_t input_len, - uint8_t *output); +KERN_WARN_UNUSED_RESULT int +crypto_aes_cbc_decrypt(const uint8_t key[CRYPTO_AES_KEY_SIZE], + const uint8_t iv[CRYPTO_AES_IV_SIZE], + const uint8_t *input, size_t input_len, uint8_t *output); /* --- AES-256-CTR --- */ /* Encrypt or decrypt in CTR mode (symmetric operation). * nonce is 12 bytes; the 4-byte counter starts at 0. Any input_len is valid. */ -int crypto_aes_ctr(const uint8_t key[CRYPTO_AES_KEY_SIZE], - const uint8_t nonce[CRYPTO_AES_CTR_NONCE_SIZE], - const uint8_t *input, size_t input_len, uint8_t *output); +KERN_WARN_UNUSED_RESULT int +crypto_aes_ctr(const uint8_t key[CRYPTO_AES_KEY_SIZE], + const uint8_t nonce[CRYPTO_AES_CTR_NONCE_SIZE], + const uint8_t *input, size_t input_len, uint8_t *output); /* --- AES-256-GCM --- */ /* Encrypt with GCM authentication. tag_len can be 4-16 bytes. */ -int crypto_aes_gcm_encrypt(const uint8_t key[CRYPTO_AES_KEY_SIZE], - const uint8_t *nonce, size_t nonce_len, - const uint8_t *input, size_t input_len, - uint8_t *output, uint8_t *tag, size_t tag_len); +KERN_WARN_UNUSED_RESULT int +crypto_aes_gcm_encrypt(const uint8_t key[CRYPTO_AES_KEY_SIZE], + const uint8_t *nonce, size_t nonce_len, + const uint8_t *input, size_t input_len, uint8_t *output, + uint8_t *tag, size_t tag_len); /* Decrypt with GCM authentication verification. * Returns CRYPTO_ERR_AUTH_FAILED if tag doesn't match. */ -int crypto_aes_gcm_decrypt(const uint8_t key[CRYPTO_AES_KEY_SIZE], - const uint8_t *nonce, size_t nonce_len, - const uint8_t *input, size_t input_len, - uint8_t *output, const uint8_t *tag, size_t tag_len); +KERN_WARN_UNUSED_RESULT int +crypto_aes_gcm_decrypt(const uint8_t key[CRYPTO_AES_KEY_SIZE], + const uint8_t *nonce, size_t nonce_len, + const uint8_t *input, size_t input_len, uint8_t *output, + const uint8_t *tag, size_t tag_len); /* --- Random --- */ @@ -95,10 +101,13 @@ void crypto_random_bytes(uint8_t *buf, size_t len); /* Apply PKCS#7 padding. output must have room for input_len + padding (up to * input_len + 16). Returns padded length, or 0 on error. */ -size_t crypto_pkcs7_pad(const uint8_t *input, size_t input_len, uint8_t *output, - size_t output_size); +KERN_WARN_UNUSED_RESULT size_t crypto_pkcs7_pad(const uint8_t *input, + size_t input_len, + uint8_t *output, + size_t output_size); /* Remove PKCS#7 padding in-place. Returns unpadded length, or 0 on error. */ -size_t crypto_pkcs7_unpad(const uint8_t *input, size_t input_len); +KERN_WARN_UNUSED_RESULT size_t crypto_pkcs7_unpad(const uint8_t *input, + size_t input_len); #endif // CRYPTO_UTILS_H diff --git a/main/core/descriptor_checksum.h b/main/core/descriptor_checksum.h index d526efbb..23eb2d80 100644 --- a/main/core/descriptor_checksum.h +++ b/main/core/descriptor_checksum.h @@ -1,17 +1,21 @@ +#include "../utils/attributes.h" #pragma once #include struct wally_descriptor; -bool descriptor_string_from_descriptor(const struct wally_descriptor *desc, - char **output); -bool descriptor_checksum_from_descriptor(const struct wally_descriptor *desc, - char out[9]); +KERN_WARN_UNUSED_RESULT bool +descriptor_string_from_descriptor(const struct wally_descriptor *desc, + char **output); +KERN_WARN_UNUSED_RESULT bool +descriptor_checksum_from_descriptor(const struct wally_descriptor *desc, + char out[9]); /* True if `s` contains an uppercase 'H' as a hardened-derivation marker * (i.e. one or more digits at a path-component boundary — after '/', '<', or * ';' — followed by 'H'). libwally accepts 'H', 'h', and '\'' interchangeably, * but the canonical form used for dedup normalizes only 'h' and '\'', so * descriptors using 'H' must be rejected at the input boundary. */ -bool descriptor_text_has_uppercase_hardened(const char *s); +KERN_WARN_UNUSED_RESULT bool +descriptor_text_has_uppercase_hardened(const char *s); diff --git a/main/core/descriptor_validator.c b/main/core/descriptor_validator.c index 2e84c254..4b604a71 100644 --- a/main/core/descriptor_validator.c +++ b/main/core/descriptor_validator.c @@ -518,7 +518,9 @@ static void session_register_current_descriptor(void) { char label[REGISTRY_LABEL_MAX_LEN]; build_session_descriptor_label(label); - registry_set_label(id, label); + // The descriptor is loaded either way; without a label it lists under its id. + if (!registry_set_label(id, label)) + ESP_LOGW(TAG, "Failed to label session descriptor '%s'", id); complete_validation(VALIDATION_SUCCESS); } diff --git a/main/core/descriptor_validator.h b/main/core/descriptor_validator.h index 21c290bc..d3fb1c30 100644 --- a/main/core/descriptor_validator.h +++ b/main/core/descriptor_validator.h @@ -1,6 +1,7 @@ #ifndef DESCRIPTOR_VALIDATOR_H #define DESCRIPTOR_VALIDATOR_H +#include "../utils/attributes.h" #include #include #include @@ -117,8 +118,9 @@ void descriptor_validate_and_load(const char *descriptor_str, * if the descriptor parses on neither (xpub keys parse only on mainnet, tpub * only on testnet, so the result is unambiguous for extended-key descriptors). */ -bool descriptor_infer_network(const char *descriptor_str, - wallet_network_t *network_out); +KERN_WARN_UNUSED_RESULT bool +descriptor_infer_network(const char *descriptor_str, + wallet_network_t *network_out); /* Watch-only (keyless) variant of descriptor_validate_and_load: validates and * loads a descriptor for address viewing without a loaded master key. Skips the @@ -137,6 +139,7 @@ void descriptor_validate_and_load_watch_only( * duplicate ID is pending (e.g. result was not DUPLICATE, or the buffer is * too small). The pending ID is reset on the next descriptor_validate_and_load * call. */ -bool descriptor_validator_get_duplicate_id(char *out, size_t out_len); +KERN_WARN_UNUSED_RESULT bool +descriptor_validator_get_duplicate_id(char *out, size_t out_len); #endif // DESCRIPTOR_VALIDATOR_H diff --git a/main/core/fw_update.h b/main/core/fw_update.h index 90a6a551..ce44e22c 100644 --- a/main/core/fw_update.h +++ b/main/core/fw_update.h @@ -14,6 +14,7 @@ #ifndef FW_UPDATE_H #define FW_UPDATE_H +#include "../utils/attributes.h" #include #include @@ -32,14 +33,17 @@ typedef void (*fw_update_progress_cb_t)(int percent, void *user_data); /* Validate the image at path without writing anything. On success fills * *info and returns 0; on failure returns -1 and *err_out points to a * static human-readable reason. */ -int fw_update_validate(const char *path, fw_update_info_t *info, - const char **err_out); +KERN_WARN_UNUSED_RESULT int fw_update_validate(const char *path, + fw_update_info_t *info, + const char **err_out); /* Stream the image at path into the inactive OTA slot, verify it and set * it as the boot partition. Returns 0 on success (caller reboots); on * failure returns -1 with *err_out set and the current firmware untouched. */ -int fw_update_apply(const char *path, fw_update_progress_cb_t progress_cb, - void *user_data, const char **err_out); +KERN_WARN_UNUSED_RESULT int fw_update_apply(const char *path, + fw_update_progress_cb_t progress_cb, + void *user_data, + const char **err_out); /* Boot-time self-test confirmation: if the running image is pending * verification after an update, mark it valid so the bootloader does not diff --git a/main/core/kef.h b/main/core/kef.h index 32da04d1..7331398e 100644 --- a/main/core/kef.h +++ b/main/core/kef.h @@ -16,6 +16,7 @@ #ifndef KEF_H #define KEF_H +#include "../utils/attributes.h" #include #include #include @@ -92,16 +93,16 @@ kef_error_t kef_parse_header(const uint8_t *envelope, size_t env_len, void kef_encode_iterations(uint32_t effective, uint8_t out[3]); /* Decode 3-byte stored value → effective iteration count. */ -uint32_t kef_decode_iterations(const uint8_t stored[3]); +KERN_WARN_UNUSED_RESULT uint32_t kef_decode_iterations(const uint8_t stored[3]); /* Human-readable error string. */ -const char *kef_error_str(kef_error_t err); +KERN_WARN_UNUSED_RESULT const char *kef_error_str(kef_error_t err); /* * Check if data looks like a valid KEF envelope. * Validates header, known version, and minimum payload size. */ -bool kef_is_envelope(const uint8_t *data, size_t len); +KERN_WARN_UNUSED_RESULT bool kef_is_envelope(const uint8_t *data, size_t len); /* * Extract a raw KEF envelope from arbitrary file bytes, accepting either a raw @@ -110,7 +111,7 @@ bool kef_is_envelope(const uint8_t *data, size_t len); * frees) and its length via out_len, or NULL if the bytes are not a KEF * envelope. */ -uint8_t *kef_envelope_from_bytes(const uint8_t *data, size_t len, - size_t *out_len); +KERN_WARN_UNUSED_RESULT uint8_t * +kef_envelope_from_bytes(const uint8_t *data, size_t len, size_t *out_len); #endif /* KEF_H */ diff --git a/main/core/key.h b/main/core/key.h index d7e0bc97..a236b562 100644 --- a/main/core/key.h +++ b/main/core/key.h @@ -1,43 +1,50 @@ #ifndef KEY_H #define KEY_H +#include "../utils/attributes.h" #include #include #include #include -bool key_init(void); -bool key_is_loaded(void); -bool key_load_from_mnemonic(const char *mnemonic, const char *passphrase, - bool is_testnet); +KERN_WARN_UNUSED_RESULT bool key_init(void); +KERN_WARN_UNUSED_RESULT bool key_is_loaded(void); +KERN_WARN_UNUSED_RESULT bool key_load_from_mnemonic(const char *mnemonic, + const char *passphrase, + bool is_testnet); void key_unload(void); /* Caller-provided buffer of BIP32_KEY_FINGERPRINT_LEN (4) bytes. */ -bool key_get_fingerprint(unsigned char *fingerprint_out); +KERN_WARN_UNUSED_RESULT bool +key_get_fingerprint(unsigned char *fingerprint_out); /* Caller-provided buffer of BIP32_KEY_FINGERPRINT_LEN*2 + 1 (9) bytes. */ -bool key_get_fingerprint_hex(char *hex_out); -bool key_mnemonic_fingerprint_hex(const char *mnemonic, char *hex_out); +KERN_WARN_UNUSED_RESULT bool key_get_fingerprint_hex(char *hex_out); +KERN_WARN_UNUSED_RESULT bool key_mnemonic_fingerprint_hex(const char *mnemonic, + char *hex_out); /* On success, *xpub_out is heap-allocated and must be freed by the caller * with wally_free_string(). Public-only -- safe to log. */ -bool key_get_xpub(const char *path, char **xpub_out); -bool key_get_master_xpub(char **xpub_out); +KERN_WARN_UNUSED_RESULT bool key_get_xpub(const char *path, char **xpub_out); +KERN_WARN_UNUSED_RESULT bool key_get_master_xpub(char **xpub_out); /* On success, *mnemonic_out is a heap-allocated copy of the active mnemonic. * SENSITIVE: caller must wipe and free with SECURE_FREE_STRING(). */ -bool key_get_mnemonic(char **mnemonic_out); +KERN_WARN_UNUSED_RESULT bool key_get_mnemonic(char **mnemonic_out); /* On success, *words_out is a heap-allocated array of *word_count_out * strdup'd words. SENSITIVE: caller must SECURE_FREE_STRING() each word, then * free() the array. */ -bool key_get_mnemonic_words(char ***words_out, size_t *word_count_out); +KERN_WARN_UNUSED_RESULT bool key_get_mnemonic_words(char ***words_out, + size_t *word_count_out); /* On success, *key_out is a wally-allocated ext_key holding the derived * PRIVATE key material. Caller must free with bip32_key_free(), which * zeroizes the private bytes before releasing the allocation. */ -bool key_get_derived_key(const char *path, struct ext_key **key_out); -bool key_get_derived_key_components(const uint32_t *path, size_t path_depth, - struct ext_key **key_out); +KERN_WARN_UNUSED_RESULT bool key_get_derived_key(const char *path, + struct ext_key **key_out); +KERN_WARN_UNUSED_RESULT bool +key_get_derived_key_components(const uint32_t *path, size_t path_depth, + struct ext_key **key_out); void key_cleanup(void); diff --git a/main/core/message_sign.h b/main/core/message_sign.h index d803c1c4..5653ce69 100644 --- a/main/core/message_sign.h +++ b/main/core/message_sign.h @@ -1,6 +1,7 @@ #ifndef MESSAGE_SIGN_H #define MESSAGE_SIGN_H +#include "../utils/attributes.h" #include typedef struct { @@ -8,11 +9,14 @@ typedef struct { char *message; // ASCII message text } parsed_sign_message_t; -bool message_sign_parse(const char *content, parsed_sign_message_t *result); +KERN_WARN_UNUSED_RESULT bool message_sign_parse(const char *content, + parsed_sign_message_t *result); void message_sign_free_parsed(parsed_sign_message_t *parsed); -bool message_sign_sign(const char *derivation_path, const char *message, - char **signature_b64_out); -bool message_sign_get_address(const char *derivation_path, bool is_testnet, - char **address_out); +KERN_WARN_UNUSED_RESULT bool message_sign_sign(const char *derivation_path, + const char *message, + char **signature_b64_out); +KERN_WARN_UNUSED_RESULT bool +message_sign_get_address(const char *derivation_path, bool is_testnet, + char **address_out); #endif // MESSAGE_SIGN_H diff --git a/main/core/miniscript_policy.h b/main/core/miniscript_policy.h index c19c0b61..9568777e 100644 --- a/main/core/miniscript_policy.h +++ b/main/core/miniscript_policy.h @@ -1,6 +1,7 @@ #ifndef MINISCRIPT_POLICY_H #define MINISCRIPT_POLICY_H +#include "../utils/attributes.h" #include #include @@ -10,18 +11,21 @@ struct wally_descriptor; * letter substitution and every screen that lists descriptor keys. */ #define MINISCRIPT_POLICY_MAX_KEYS 26 -bool miniscript_policy_is_miniscript(const struct wally_descriptor *desc); +KERN_WARN_UNUSED_RESULT bool +miniscript_policy_is_miniscript(const struct wally_descriptor *desc); /* Canonical descriptor with each key expression (origin, xpub and child * path) replaced by its letter ID ('A' + key index, matching the order keys * are listed by libwally). Returns a malloc'd string, e.g. * "wsh(or_d(pk(A),and_v(v:pkh(B),older(65535))))", or NULL on failure. */ -char *miniscript_policy_string(const struct wally_descriptor *desc); +KERN_WARN_UNUSED_RESULT char * +miniscript_policy_string(const struct wally_descriptor *desc); /* Indent a miniscript expression for display, one level per leading space, * breaking lines longer than max_line_width characters. Returns a malloc'd * '\n'-joined string, or NULL on failure. */ -char *miniscript_policy_indent(const char *expr, size_t max_line_width); +KERN_WARN_UNUSED_RESULT char *miniscript_policy_indent(const char *expr, + size_t max_line_width); typedef enum { MS_TOKEN_TEXT, // fragment names (pk, pkh, hashes), numbers @@ -52,8 +56,9 @@ typedef struct { * classified tokens for styled rendering. Timelocks get a NOTE token with an * approximate duration (older) or UTC date (after, when it is a timestamp). * Free with miniscript_policy_view_free. */ -bool miniscript_policy_view_build(const char *policy, size_t max_line_width, - ms_policy_view_t *view); +KERN_WARN_UNUSED_RESULT bool +miniscript_policy_view_build(const char *policy, size_t max_line_width, + ms_policy_view_t *view); void miniscript_policy_view_free(ms_policy_view_t *view); #endif // MINISCRIPT_POLICY_H diff --git a/main/core/nvs_secure.c b/main/core/nvs_secure.c index 0900debf..52d015fa 100644 --- a/main/core/nvs_secure.c +++ b/main/core/nvs_secure.c @@ -115,7 +115,20 @@ esp_err_t nvs_secure_provision(void) { if (err != ESP_OK) return err; - settings_init(); - pin_init(); + // Reopen both namespaces against the now-encrypted partition. Migration has + // already succeeded here, so only a failure that blocks what the caller does + // next is worth reporting: without the PIN handle pin_setup() cannot store + // anything, while settings just fall back to their defaults. + err = settings_init(); + if (err != ESP_OK) + ESP_LOGE(TAG, "Settings reopen after migration failed: %s", + esp_err_to_name(err)); + + err = pin_init(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "PIN reopen after migration failed: %s", + esp_err_to_name(err)); + return err; + } return ESP_OK; } diff --git a/main/core/nvs_secure.h b/main/core/nvs_secure.h index d4a82b40..19850f84 100644 --- a/main/core/nvs_secure.h +++ b/main/core/nvs_secure.h @@ -9,6 +9,7 @@ #ifndef NVS_SECURE_H #define NVS_SECURE_H +#include "../utils/attributes.h" #include #include @@ -24,15 +25,15 @@ nvs_secure_key_status_t nvs_secure_key_check(void); /* Boot-time NVS init. KEY4 provisioned → encrypted init (a plaintext or * corrupt partition is erased and re-initialized encrypted); KEY4 absent → * plain init that never touches the eFuse keygen path. */ -esp_err_t nvs_secure_init(void); +KERN_WARN_UNUSED_RESULT esp_err_t nvs_secure_init(void); /* Consent-gated provisioning, called from PIN setup after the user confirms. * Burns KEY4 (idempotent), read/write-protects it, then migrates NVS in * session: closes pin/settings handles, erases the partition, re-initializes * it encrypted, and reopens the handles. Erases all NVS content. */ -esp_err_t nvs_secure_provision(void); +KERN_WARN_UNUSED_RESULT esp_err_t nvs_secure_provision(void); /* True once the running session initialized NVS encrypted */ -bool nvs_secure_is_encrypted(void); +KERN_WARN_UNUSED_RESULT bool nvs_secure_is_encrypted(void); #endif // NVS_SECURE_H diff --git a/main/core/pin.c b/main/core/pin.c index 8c6e331e..76f3df78 100644 --- a/main/core/pin.c +++ b/main/core/pin.c @@ -411,7 +411,7 @@ esp_err_t pin_set_max_failures(uint8_t max) { // Wipe // --------------------------------------------------------------------------- -esp_err_t pin_wipe_all(void) { +void pin_wipe_all(void) { ESP_LOGW(TAG, "Wiping all data"); // Erase PIN NVS namespace @@ -421,12 +421,26 @@ esp_err_t pin_wipe_all(void) { } // Reset settings - settings_reset_all(); + esp_err_t err = settings_reset_all(); + if (err != ESP_OK) + ESP_LOGE(TAG, "Settings reset failed: %s", esp_err_to_name(err)); + + // Wipe flash storage. storage_wipe_flash() erases the partition directly, so + // a failed mount does not stop it - log and go on rather than skipping it. + err = storage_init(); + if (err != ESP_OK) + ESP_LOGE(TAG, "Storage mount before wipe failed: %s", esp_err_to_name(err)); - // Wipe flash storage - storage_init(); // Ensure SPIFFS is mounted - storage_wipe_flash(); + err = storage_wipe_flash(); + if (err != ESP_OK) { + // Restarting on a failed wipe would report a wipe that never happened and + // leave stored wallets on flash, so try once more before giving up. + ESP_LOGE(TAG, "Flash wipe failed: %s, retrying", esp_err_to_name(err)); + err = storage_wipe_flash(); + if (err != ESP_OK) + ESP_LOGE(TAG, "Flash wipe failed again: %s - data may remain on flash", + esp_err_to_name(err)); + } esp_restart(); - return ESP_OK; // unreachable } diff --git a/main/core/pin.h b/main/core/pin.h index 1544547a..5026847e 100644 --- a/main/core/pin.h +++ b/main/core/pin.h @@ -8,6 +8,7 @@ #ifndef PIN_H #define PIN_H +#include "../utils/attributes.h" #include #include #include @@ -34,7 +35,7 @@ typedef enum { /* Initialization — opens the "pin" NVS namespace. Safe to call multiple times. */ -esp_err_t pin_init(void); +KERN_WARN_UNUSED_RESULT esp_err_t pin_init(void); /* Close the "pin" NVS handle (required before nvs_flash_deinit) */ void pin_deinit(void); @@ -44,37 +45,39 @@ pin_efuse_status_t pin_efuse_check(void); /* Idempotent: generate random key, burn to eFuse KEY5, read-protect. * Returns ESP_OK if already provisioned or on success. */ -esp_err_t pin_efuse_provision(void); +KERN_WARN_UNUSED_RESULT esp_err_t pin_efuse_provision(void); /* Derive two BIP39 anti-phishing words + identicon data from a PIN prefix * via HMAC(KEY5). word1_out / word2_out point into the BIP39 wordlist (do not * free). identicon_out (if non-NULL) receives 3 bytes: [0..1] = cell pattern * bits, [2] = hue byte. Returns ESP_FAIL if HMAC peripheral unavailable. */ -esp_err_t pin_compute_anti_phishing(const char *prefix, size_t len, - const char **word1_out, - const char **word2_out, - uint8_t identicon_out[3]); +KERN_WARN_UNUSED_RESULT esp_err_t pin_compute_anti_phishing( + const char *prefix, size_t len, const char **word1_out, + const char **word2_out, uint8_t identicon_out[3]); /* PIN lifecycle. To change a PIN, verify the current one via pin_verify(), * then call pin_setup() with the new PIN. */ -bool pin_is_configured(void); -esp_err_t pin_setup(const char *pin, size_t len, uint8_t split_pos); +KERN_WARN_UNUSED_RESULT bool pin_is_configured(void); +KERN_WARN_UNUSED_RESULT esp_err_t pin_setup(const char *pin, size_t len, + uint8_t split_pos); pin_verify_result_t pin_verify(const char *pin, size_t len); /* Remove PIN without wiping user data (for "Disable PIN" in settings) */ -esp_err_t pin_remove(void); +KERN_WARN_UNUSED_RESULT esp_err_t pin_remove(void); /* Config getters/setters */ -uint8_t pin_get_split_position(void); -uint32_t pin_get_delay_ms(void); -uint8_t pin_get_fail_count(void); -uint8_t pin_get_max_failures(void); -bool pin_has_anti_phishing(void); -esp_err_t pin_set_max_failures(uint8_t max); +KERN_WARN_UNUSED_RESULT uint8_t pin_get_split_position(void); +KERN_WARN_UNUSED_RESULT uint32_t pin_get_delay_ms(void); +KERN_WARN_UNUSED_RESULT uint8_t pin_get_fail_count(void); +KERN_WARN_UNUSED_RESULT uint8_t pin_get_max_failures(void); +KERN_WARN_UNUSED_RESULT bool pin_has_anti_phishing(void); +KERN_WARN_UNUSED_RESULT esp_err_t pin_set_max_failures(uint8_t max); /* Nuclear wipe: erase "pin" NVS + settings + SPIFFS. - * Called when max failures reached. Device reboots to fresh state. */ -esp_err_t pin_wipe_all(void); + * Called when max failures reached. Ends in esp_restart() and does not return, + * so there is no status for a caller to act on; each step logs what it could + * not complete. */ +void pin_wipe_all(void); #endif // PIN_H diff --git a/main/core/psbt.h b/main/core/psbt.h index 931fc3de..e8221a7a 100644 --- a/main/core/psbt.h +++ b/main/core/psbt.h @@ -1,6 +1,7 @@ #ifndef PSBT_H #define PSBT_H +#include "../utils/attributes.h" #include #include #include @@ -71,15 +72,16 @@ input_ownership_t psbt_classify_input(const struct wally_psbt *psbt, size_t i, output_ownership_t psbt_classify_output(const struct wally_psbt *psbt, size_t i, bool is_testnet); -bool psbt_input_utxo_script(const struct wally_psbt *psbt, size_t input_i, - unsigned char *out, size_t out_cap, - size_t *out_len); +KERN_WARN_UNUSED_RESULT bool +psbt_input_utxo_script(const struct wally_psbt *psbt, size_t input_i, + unsigned char *out, size_t out_cap, size_t *out_len); // Format a raw keypath (4 fp bytes + N little-endian u32 components) into // the "m/44'/0'/100'/0/0" form. Returns false if the buffer is too small or // the input is malformed. -bool psbt_format_keypath(const unsigned char *raw_keypath, - size_t raw_keypath_len, char *buf, size_t buf_size); +KERN_WARN_UNUSED_RESULT bool +psbt_format_keypath(const unsigned char *raw_keypath, size_t raw_keypath_len, + char *buf, size_t buf_size); // How much an input's amount can be trusted. Only PROVEN ties the value to // the prevout txid the transaction actually spends: the full previous @@ -142,29 +144,32 @@ typedef struct { uint32_t first_sighash; } psbt_sighash_audit_t; -bool psbt_sighash_is_supported(uint32_t sighash); +KERN_WARN_UNUSED_RESULT bool psbt_sighash_is_supported(uint32_t sighash); // Short name for a sighash byte ("NONE", "SINGLE|ANYONECANPAY", ...). -const char *psbt_sighash_name(uint32_t sighash); +KERN_WARN_UNUSED_RESULT const char *psbt_sighash_name(uint32_t sighash); void psbt_audit_sighash(const struct wally_psbt *psbt, psbt_sighash_audit_t *out); // Fee as a whole-number percentage of the total input value; 0 when the total // is 0. What counts as too high is the review screen's call, not this layer's. -uint32_t psbt_fee_percent(uint64_t fee, uint64_t total_input); +KERN_WARN_UNUSED_RESULT uint32_t psbt_fee_percent(uint64_t fee, + uint64_t total_input); // Detect network from derivation paths (returns true if testnet) -bool psbt_detect_network(const struct wally_psbt *psbt); +KERN_WARN_UNUSED_RESULT bool psbt_detect_network(const struct wally_psbt *psbt); // Detect account from derivation paths // Returns the account number from PSBT derivation paths // Returns -1 if no derivation info found or inconsistent accounts -int32_t psbt_detect_account(const struct wally_psbt *psbt); +KERN_WARN_UNUSED_RESULT int32_t +psbt_detect_account(const struct wally_psbt *psbt); // Convert scriptPubKey to address string (caller must free) -char *psbt_scriptpubkey_to_address(const unsigned char *script, - size_t script_len, bool is_testnet); +KERN_WARN_UNUSED_RESULT char * +psbt_scriptpubkey_to_address(const unsigned char *script, size_t script_len, + bool is_testnet); // Per-call signing policy. Mirrors the user-facing settings toggles // (Settings > Wallet) and is enforced inside `psbt_sign()` per-input, @@ -198,11 +203,14 @@ typedef struct { // (it gates whether to proceed at all when external inputs exist), since // that's a UX decision rather than a per-input one. // `result` may be NULL. Returns number of signatures added (0 if none). -size_t psbt_sign(struct wally_psbt *psbt, bool is_testnet, - psbt_sign_policy_t policy, psbt_sign_result_t *result); +KERN_WARN_UNUSED_RESULT size_t psbt_sign(struct wally_psbt *psbt, + bool is_testnet, + psbt_sign_policy_t policy, + psbt_sign_result_t *result); // Create a trimmed PSBT containing only signatures and minimal validation data // Returns new PSBT on success (caller must free), NULL on failure -struct wally_psbt *psbt_trim(const struct wally_psbt *psbt); +KERN_WARN_UNUSED_RESULT struct wally_psbt * +psbt_trim(const struct wally_psbt *psbt); #endif // PSBT_H diff --git a/main/core/psbt_internal.h b/main/core/psbt_internal.h index d11ebb9e..69abc0cd 100644 --- a/main/core/psbt_internal.h +++ b/main/core/psbt_internal.h @@ -1,6 +1,7 @@ #ifndef PSBT_INTERNAL_H #define PSBT_INTERNAL_H +#include "../utils/attributes.h" #include "psbt.h" #include @@ -22,8 +23,9 @@ typedef struct { } expected_scripts_t; #ifdef PSBT_TESTING -bool claim_regenerate(const claim_t *claim, bool is_testnet, - expected_scripts_t *out); +KERN_WARN_UNUSED_RESULT bool claim_regenerate(const claim_t *claim, + bool is_testnet, + expected_scripts_t *out); #endif #endif // PSBT_INTERNAL_H diff --git a/main/core/registry.c b/main/core/registry.c index f0b00c7e..bfc528a5 100644 --- a/main/core/registry.c +++ b/main/core/registry.c @@ -131,7 +131,9 @@ static void registry_init_scan(storage_location_t loc) { id_len = REGISTRY_ID_MAX_LEN - 1; memcpy(id, fname, id_len); id[id_len] = '\0'; - registry_add_from_string(id, desc_str, loc, false); + if (!registry_add_from_string(id, desc_str, loc, false)) + ESP_LOGW(TAG, "Skipping stored descriptor '%s': failed to register", + id); free(desc_str); } free(data); diff --git a/main/core/registry.h b/main/core/registry.h index dfb635d1..17da1db6 100644 --- a/main/core/registry.h +++ b/main/core/registry.h @@ -1,6 +1,7 @@ #ifndef REGISTRY_H #define REGISTRY_H +#include "../utils/attributes.h" #include #include #include @@ -27,32 +28,39 @@ typedef struct { bool persisted; } registry_entry_t; -size_t registry_count(void); -const registry_entry_t *registry_get(size_t i); -const registry_entry_t *registry_find_by_id(const char *id); -bool registry_set_label(const char *id, const char *label); -bool registry_remove(const char *id); -bool registry_add_from_string(const char *id, const char *descriptor_str, - storage_location_t loc, bool persist); +KERN_WARN_UNUSED_RESULT size_t registry_count(void); +KERN_WARN_UNUSED_RESULT const registry_entry_t *registry_get(size_t i); +KERN_WARN_UNUSED_RESULT const registry_entry_t * +registry_find_by_id(const char *id); +KERN_WARN_UNUSED_RESULT bool registry_set_label(const char *id, + const char *label); +KERN_WARN_UNUSED_RESULT bool registry_remove(const char *id); +KERN_WARN_UNUSED_RESULT bool +registry_add_from_string(const char *id, const char *descriptor_str, + storage_location_t loc, bool persist); /* Watch-only (keyless) session add: registers a descriptor for address viewing * without requiring the loaded key's fingerprint to be present. `my_key_index` * is set to SIZE_MAX and the origin path is left empty. Never persisted. */ -bool registry_add_watch_only(const char *id, const char *descriptor_str, - wallet_network_t network); +KERN_WARN_UNUSED_RESULT bool registry_add_watch_only(const char *id, + const char *descriptor_str, + wallet_network_t network); /* Look up whether `descriptor_str` is already loaded in the in-memory * session registry. Compares h-normalized BIP-380 checksums and writes the * matching session id to `out_id` if non-NULL. */ -bool registry_session_has_duplicate(const char *descriptor_str, char *out_id, - size_t out_id_size); +KERN_WARN_UNUSED_RESULT bool +registry_session_has_duplicate(const char *descriptor_str, char *out_id, + size_t out_id_size); /* Same lookup when the caller already has the h-normalized BIP-380 checksum. */ -bool registry_session_has_duplicate_checksum(const char checksum[9], - char *out_id, size_t out_id_size); +KERN_WARN_UNUSED_RESULT bool +registry_session_has_duplicate_checksum(const char checksum[9], char *out_id, + size_t out_id_size); void registry_clear(void); void registry_init(bool is_testnet); -registry_entry_t *registry_match_keypath(const uint8_t *keypath, - size_t keypath_len, size_t *cursor); +KERN_WARN_UNUSED_RESULT registry_entry_t * +registry_match_keypath(const uint8_t *keypath, size_t keypath_len, + size_t *cursor); #endif // REGISTRY_H diff --git a/main/core/script_templates.h b/main/core/script_templates.h index f388e5e0..c4eb1fb8 100644 --- a/main/core/script_templates.h +++ b/main/core/script_templates.h @@ -1,6 +1,7 @@ #ifndef SCRIPT_TEMPLATES_H #define SCRIPT_TEMPLATES_H +#include "../utils/attributes.h" #include #include #include @@ -16,17 +17,17 @@ typedef enum { #define SCRIPT_TEMPLATE_P2SH_P2WPKH_REDEEM_LEN 22 #define SCRIPT_TEMPLATE_P2SH_P2WPKH_SPK_LEN 23 -bool script_template_from_pubkey(script_template_type_t type, - const uint8_t *pubkey, size_t pubkey_len, - uint8_t *spk_out, size_t *spk_len, - uint8_t *redeem_out, size_t *redeem_len); +KERN_WARN_UNUSED_RESULT bool script_template_from_pubkey( + script_template_type_t type, const uint8_t *pubkey, size_t pubkey_len, + uint8_t *spk_out, size_t *spk_len, uint8_t *redeem_out, size_t *redeem_len); -bool script_template_pubkey_matches_spk(const uint8_t *pubkey, - size_t pubkey_len, - const uint8_t *target_spk, - size_t target_spk_len); +KERN_WARN_UNUSED_RESULT bool +script_template_pubkey_matches_spk(const uint8_t *pubkey, size_t pubkey_len, + const uint8_t *target_spk, + size_t target_spk_len); -char *script_template_address_from_spk(const unsigned char *script, - size_t script_len, bool is_testnet); +KERN_WARN_UNUSED_RESULT char * +script_template_address_from_spk(const unsigned char *script, size_t script_len, + bool is_testnet); #endif // SCRIPT_TEMPLATES_H diff --git a/main/core/settings.c b/main/core/settings.c index d55b8b92..a88f0104 100644 --- a/main/core/settings.c +++ b/main/core/settings.c @@ -53,24 +53,33 @@ static bool settings_get_bool_or_default(const char *key, bool default_value) { return settings_get_u8_or_default(key, default_value ? 1 : 0) != 0; } +/* Callers of the settings_set_* family act on the new value immediately and + * have nothing to do about a failed write, so report it here once, naming the + * key, rather than leaving every call site to notice on its own. */ +static esp_err_t settings_report(const char *key, esp_err_t err) { + if (err != ESP_OK) + ESP_LOGE(TAG, "Failed to persist '%s': %s", key, esp_err_to_name(err)); + return err; +} + static esp_err_t settings_set_u8_and_commit(const char *key, uint8_t value) { if (!initialized) - return ESP_ERR_INVALID_STATE; + return settings_report(key, ESP_ERR_INVALID_STATE); esp_err_t err = nvs_set_u8(settings_nvs, key, value); if (err != ESP_OK) - return err; - return nvs_commit(settings_nvs); + return settings_report(key, err); + return settings_report(key, nvs_commit(settings_nvs)); } static esp_err_t settings_set_u16_and_commit(const char *key, uint16_t value) { if (!initialized) - return ESP_ERR_INVALID_STATE; + return settings_report(key, ESP_ERR_INVALID_STATE); esp_err_t err = nvs_set_u16(settings_nvs, key, value); if (err != ESP_OK) - return err; - return nvs_commit(settings_nvs); + return settings_report(key, err); + return settings_report(key, nvs_commit(settings_nvs)); } static esp_err_t settings_set_bool_and_commit(const char *key, bool value) { @@ -263,8 +272,8 @@ esp_err_t settings_acknowledge_disclaimer(const char *version) { esp_err_t err = nvs_set_blob(settings_nvs, KEY_DISCLAIMER_VERSION, stored, sizeof(stored)); if (err != ESP_OK) - return err; - return nvs_commit(settings_nvs); + return settings_report(KEY_DISCLAIMER_VERSION, err); + return settings_report(KEY_DISCLAIMER_VERSION, nvs_commit(settings_nvs)); } esp_err_t settings_reset_all(void) { diff --git a/main/core/settings.h b/main/core/settings.h index c0659bde..cf0419b9 100644 --- a/main/core/settings.h +++ b/main/core/settings.h @@ -3,6 +3,7 @@ #ifndef SETTINGS_H #define SETTINGS_H +#include "../utils/attributes.h" #include "wallet.h" #include @@ -25,36 +26,43 @@ #define SESSION_TIMEOUT_DEFAULT_SEC 300 #define SETTINGS_VERSION_MAX 32 -esp_err_t settings_init(void); +KERN_WARN_UNUSED_RESULT esp_err_t settings_init(void); /* Close the settings NVS handle (required before nvs_flash_deinit) */ void settings_deinit(void); + +/* The settings_set_* family deliberately carries no KERN_WARN_UNUSED_RESULT: + * these persist a preference the caller has already applied in this session, + * and no caller can do anything useful about a failed write. settings.c logs + * every failure with the key that could not be stored. Functions whose result + * a caller must act on - init and reset_all - do carry it. */ wallet_network_t settings_get_network(void); esp_err_t settings_set_network(wallet_network_t network); -uint8_t settings_get_brightness(void); +KERN_WARN_UNUSED_RESULT uint8_t settings_get_brightness(void); esp_err_t settings_set_brightness(uint8_t brightness); -uint8_t settings_get_ae_target(void); +KERN_WARN_UNUSED_RESULT uint8_t settings_get_ae_target(void); esp_err_t settings_set_ae_target(uint8_t level); -uint16_t settings_get_focus_position(void); +KERN_WARN_UNUSED_RESULT uint16_t settings_get_focus_position(void); esp_err_t settings_set_focus_position(uint16_t position); -uint16_t settings_get_qr_density(void); +KERN_WARN_UNUSED_RESULT uint16_t settings_get_qr_density(void); esp_err_t settings_set_qr_density(uint16_t chars_per_frame); -uint8_t settings_get_qr_shade(void); +KERN_WARN_UNUSED_RESULT uint8_t settings_get_qr_shade(void); esp_err_t settings_set_qr_shade(uint8_t shade); -uint8_t settings_get_qr_fps(void); +KERN_WARN_UNUSED_RESULT uint8_t settings_get_qr_fps(void); esp_err_t settings_set_qr_fps(uint8_t fps); -bool settings_get_permissive_signing(void); +KERN_WARN_UNUSED_RESULT bool settings_get_permissive_signing(void); esp_err_t settings_set_permissive_signing(bool permissive); -bool settings_get_partial_signing(void); +KERN_WARN_UNUSED_RESULT bool settings_get_partial_signing(void); esp_err_t settings_set_partial_signing(bool partial); -bool settings_get_expected_owned_signing(void); +KERN_WARN_UNUSED_RESULT bool settings_get_expected_owned_signing(void); esp_err_t settings_set_expected_owned_signing(bool enabled); -uint16_t settings_get_screensaver_timeout(void); +KERN_WARN_UNUSED_RESULT uint16_t settings_get_screensaver_timeout(void); esp_err_t settings_set_screensaver_timeout(uint16_t sec); -uint16_t settings_get_session_timeout(void); +KERN_WARN_UNUSED_RESULT uint16_t settings_get_session_timeout(void); esp_err_t settings_set_session_timeout(uint16_t sec); -bool settings_disclaimer_acknowledged(const char *version); +KERN_WARN_UNUSED_RESULT bool +settings_disclaimer_acknowledged(const char *version); esp_err_t settings_acknowledge_disclaimer(const char *version); -esp_err_t settings_reset_all(void); +KERN_WARN_UNUSED_RESULT esp_err_t settings_reset_all(void); #endif // SETTINGS_H diff --git a/main/core/ss_whitelist.h b/main/core/ss_whitelist.h index 9e9c065d..cd17915c 100644 --- a/main/core/ss_whitelist.h +++ b/main/core/ss_whitelist.h @@ -1,6 +1,7 @@ #ifndef SS_WHITELIST_H #define SS_WHITELIST_H +#include "../utils/attributes.h" #include #include #include @@ -55,8 +56,9 @@ static inline uint32_t ss_u32_le(const unsigned char *bytes) { return bip32_path_u32_le(bytes); } -bool ss_keypath_parse(const unsigned char *keypath_after_fp, - size_t keypath_len_after_fp, ss_keypath_t *out); +KERN_WARN_UNUSED_RESULT bool +ss_keypath_parse(const unsigned char *keypath_after_fp, + size_t keypath_len_after_fp, ss_keypath_t *out); /* Maximum buffer size for ss_keypath_format output ("m/86'/1'/100'/1/99\0" = 19 * bytes). */ @@ -67,14 +69,17 @@ bool ss_keypath_parse(const unsigned char *keypath_after_fp, #define SS_P2SH_P2WPKH_SPK_LEN \ SCRIPT_TEMPLATE_P2SH_P2WPKH_SPK_LEN /* OP_HASH160 <20-byte hash> OP_EQUAL */ -bool ss_keypath_format(const ss_keypath_t *kp, char *buf, size_t buf_size); +KERN_WARN_UNUSED_RESULT bool ss_keypath_format(const ss_keypath_t *kp, + char *buf, size_t buf_size); -bool ss_keypath_is_whitelisted(const ss_keypath_t *kp, bool is_testnet, - uint32_t max_index); +KERN_WARN_UNUSED_RESULT bool ss_keypath_is_whitelisted(const ss_keypath_t *kp, + bool is_testnet, + uint32_t max_index); -bool ss_scriptpubkey(ss_script_type_t script, uint32_t account, uint32_t chain, - uint32_t index, bool is_testnet, uint8_t *out, - size_t *out_len); +KERN_WARN_UNUSED_RESULT bool ss_scriptpubkey(ss_script_type_t script, + uint32_t account, uint32_t chain, + uint32_t index, bool is_testnet, + uint8_t *out, size_t *out_len); /* * Like ss_scriptpubkey but also writes the redeem script into @@ -82,19 +87,21 @@ bool ss_scriptpubkey(ss_script_type_t script, uint32_t account, uint32_t chain, * types, *redeem_len is set to 0 and behaviour matches ss_scriptpubkey. * redeem_out must have room for SS_P2SH_P2WPKH_REDEEM_LEN bytes. */ -bool ss_scriptpubkey_with_redeem(ss_script_type_t script, uint32_t account, - uint32_t chain, uint32_t index, - bool is_testnet, uint8_t *spk_out, - size_t *spk_len, uint8_t *redeem_out, - size_t *redeem_len); +KERN_WARN_UNUSED_RESULT bool +ss_scriptpubkey_with_redeem(ss_script_type_t script, uint32_t account, + uint32_t chain, uint32_t index, bool is_testnet, + uint8_t *spk_out, size_t *spk_len, + uint8_t *redeem_out, size_t *redeem_len); /* Maximum buffer size for ss_address output (covers all script types + null). */ #define SS_ADDRESS_MAX_LEN 75 -bool ss_address(ss_script_type_t script, uint32_t account, uint32_t chain, - uint32_t index, bool is_testnet, char *address_out, - size_t address_out_len); +KERN_WARN_UNUSED_RESULT bool ss_address(ss_script_type_t script, + uint32_t account, uint32_t chain, + uint32_t index, bool is_testnet, + char *address_out, + size_t address_out_len); /* * Returns true iff (purpose, outer_script) matches the fixed BIP convention: @@ -106,8 +113,9 @@ bool ss_address(ss_script_type_t script, uint32_t account, uint32_t chain, * Used by whitelist claim matching (hard enforcement — mismatch means no * claim). */ -bool purpose_script_binding_check_strict(uint32_t purpose, - ss_script_type_t outer_script); +KERN_WARN_UNUSED_RESULT bool +purpose_script_binding_check_strict(uint32_t purpose, + ss_script_type_t outer_script); /* * Inspects a parsed descriptor's outer script type (via canonicalisation) diff --git a/main/core/storage.c b/main/core/storage.c index eb16cf5c..c4b68681 100644 --- a/main/core/storage.c +++ b/main/core/storage.c @@ -184,9 +184,15 @@ void storage_sanitize_id(const char *raw_id, char *out, size_t out_size) { /* Fallback: first 8 hex chars of SHA-256(raw_id) */ if (j == 0) { uint8_t hash[CRYPTO_SHA256_SIZE]; - crypto_sha256((const uint8_t *)raw_id, strlen(raw_id), hash); - for (size_t i = 0; i < 4 && (i * 2 + 1) < max_len; i++) - snprintf(out + i * 2, 3, "%02X", hash[i]); + if (crypto_sha256((const uint8_t *)raw_id, strlen(raw_id), hash) == + CRYPTO_OK) { + for (size_t i = 0; i < 4 && (i * 2 + 1) < max_len; i++) + snprintf(out + i * 2, 3, "%02X", hash[i]); + } else { + /* Formatting the uninitialised hash would leak stack bytes into a name + * shown in the UI and used to build a filename. */ + snprintf(out, out_size, "unnamed"); + } } } diff --git a/main/core/storage.h b/main/core/storage.h index 4e7e8b95..1084c297 100644 --- a/main/core/storage.h +++ b/main/core/storage.h @@ -17,6 +17,7 @@ #ifndef STORAGE_H #define STORAGE_H +#include "../utils/attributes.h" #include #include #include @@ -42,7 +43,7 @@ typedef enum { /** * Initialize flash storage (mount SPIFFS). Safe to call multiple times. */ -esp_err_t storage_init(void); +KERN_WARN_UNUSED_RESULT esp_err_t storage_init(void); /** * Save a KEF envelope. Flash: raw binary. SD: base64-encoded. @@ -52,8 +53,9 @@ esp_err_t storage_init(void); * @param kef_envelope Binary KEF envelope * @param len Length of KEF envelope */ -esp_err_t storage_save_mnemonic(storage_location_t loc, const char *id, - const uint8_t *kef_envelope, size_t len); +KERN_WARN_UNUSED_RESULT esp_err_t +storage_save_mnemonic(storage_location_t loc, const char *id, + const uint8_t *kef_envelope, size_t len); /** * Load a mnemonic file. Flash: raw binary. SD: base64-decoded. @@ -63,8 +65,9 @@ esp_err_t storage_save_mnemonic(storage_location_t loc, const char *id, * @param kef_envelope_out Receives heap-allocated binary KEF envelope * @param len_out Receives length */ -esp_err_t storage_load_mnemonic(storage_location_t loc, const char *filename, - uint8_t **kef_envelope_out, size_t *len_out); +KERN_WARN_UNUSED_RESULT esp_err_t +storage_load_mnemonic(storage_location_t loc, const char *filename, + uint8_t **kef_envelope_out, size_t *len_out); /** * List stored mnemonic files. @@ -74,25 +77,28 @@ esp_err_t storage_load_mnemonic(storage_location_t loc, const char *filename, * with storage_free_file_list) * @param count_out Receives count */ -esp_err_t storage_list_mnemonics(storage_location_t loc, char ***filenames_out, - int *count_out); +KERN_WARN_UNUSED_RESULT esp_err_t storage_list_mnemonics(storage_location_t loc, + char ***filenames_out, + int *count_out); /** * Delete a stored mnemonic file. */ -esp_err_t storage_delete_mnemonic(storage_location_t loc, const char *filename); +KERN_WARN_UNUSED_RESULT esp_err_t +storage_delete_mnemonic(storage_location_t loc, const char *filename); /** * Securely wipe flash storage. * Unmounts SPIFFS, erases the entire partition (all bytes -> 0xFF), * then remounts with a fresh filesystem. */ -esp_err_t storage_wipe_flash(void); +KERN_WARN_UNUSED_RESULT esp_err_t storage_wipe_flash(void); /** * Check if a mnemonic with the given ID already exists. */ -bool storage_mnemonic_exists(storage_location_t loc, const char *id); +KERN_WARN_UNUSED_RESULT bool storage_mnemonic_exists(storage_location_t loc, + const char *id); /** * Sanitize a raw ID for use as a filename component. @@ -119,7 +125,8 @@ void storage_free_file_list(char **files, int count); * @param len Length of data * @return Heap-allocated ID string, or NULL on failure. Caller frees. */ -char *storage_get_kef_display_name(const uint8_t *data, size_t len); +KERN_WARN_UNUSED_RESULT char *storage_get_kef_display_name(const uint8_t *data, + size_t len); /* ---------- Descriptor storage ---------- */ @@ -133,9 +140,9 @@ char *storage_get_kef_display_name(const uint8_t *data, size_t len); * @param len Length of data * @param encrypted true for .kef, false for .txt */ -esp_err_t storage_save_descriptor(storage_location_t loc, const char *id, - const uint8_t *data, size_t len, - bool encrypted); +KERN_WARN_UNUSED_RESULT esp_err_t +storage_save_descriptor(storage_location_t loc, const char *id, + const uint8_t *data, size_t len, bool encrypted); /** * Load a descriptor file. Detects format by extension. @@ -147,27 +154,28 @@ esp_err_t storage_save_descriptor(storage_location_t loc, const char *id, * @param len_out Receives length * @param encrypted_out Receives true if file is .kef, false if .txt */ -esp_err_t storage_load_descriptor(storage_location_t loc, const char *filename, - uint8_t **data_out, size_t *len_out, - bool *encrypted_out); +KERN_WARN_UNUSED_RESULT esp_err_t storage_load_descriptor( + storage_location_t loc, const char *filename, uint8_t **data_out, + size_t *len_out, bool *encrypted_out); /** * List stored descriptor files (.kef and .txt). */ -esp_err_t storage_list_descriptors(storage_location_t loc, - char ***filenames_out, int *count_out); +KERN_WARN_UNUSED_RESULT esp_err_t storage_list_descriptors( + storage_location_t loc, char ***filenames_out, int *count_out); /** * Delete a stored descriptor file. */ -esp_err_t storage_delete_descriptor(storage_location_t loc, - const char *filename); +KERN_WARN_UNUSED_RESULT esp_err_t +storage_delete_descriptor(storage_location_t loc, const char *filename); /** * Check if a descriptor with the given ID already exists. */ -bool storage_descriptor_exists(storage_location_t loc, const char *id, - bool encrypted); +KERN_WARN_UNUSED_RESULT bool storage_descriptor_exists(storage_location_t loc, + const char *id, + bool encrypted); /** * Build the full filesystem path a descriptor with the given ID would be saved diff --git a/main/core/wallet.h b/main/core/wallet.h index 4b0ef00e..69fe672a 100644 --- a/main/core/wallet.h +++ b/main/core/wallet.h @@ -1,6 +1,7 @@ #ifndef WALLET_H #define WALLET_H +#include "../utils/attributes.h" #include #include #include @@ -28,12 +29,12 @@ struct wally_map; * KERN_DESCRIPTOR_MAX_DEPTH. Returns the libwally result code (WALLY_OK on * success); on success *output owns the descriptor (free with * wally_descriptor_free). */ -int wallet_descriptor_parse(const char *descriptor, - const struct wally_map *vars_in, uint32_t network, - struct wally_descriptor **output); +KERN_WARN_UNUSED_RESULT int +wallet_descriptor_parse(const char *descriptor, const struct wally_map *vars_in, + uint32_t network, struct wally_descriptor **output); -bool wallet_init(wallet_network_t network); -bool wallet_is_initialized(void); +KERN_WARN_UNUSED_RESULT bool wallet_init(wallet_network_t network); +KERN_WARN_UNUSED_RESULT bool wallet_is_initialized(void); wallet_network_t wallet_get_network(void); void wallet_cleanup(void); void wallet_unload(void); @@ -42,7 +43,7 @@ void wallet_unload(void); * without a loaded master key. Independent of wallet_is_initialized() (which * still implies a loaded key); only the addresses page honors it. */ void wallet_set_watch_only(wallet_network_t network); -bool wallet_is_watch_only(void); +KERN_WARN_UNUSED_RESULT bool wallet_is_watch_only(void); void wallet_clear_watch_only(void); #endif // WALLET_H diff --git a/main/main.c b/main/main.c index 6a6b389d..c13a6916 100644 --- a/main/main.c +++ b/main/main.c @@ -29,7 +29,12 @@ void app_main(void) { // provisioned, plaintext otherwise (never stock nvs_flash_init(): its // keygen path would burn KEY4 without consent) ESP_ERROR_CHECK(nvs_secure_init()); - settings_init(); + // Not fatal: every getter falls back to its default when the namespace is + // unavailable, and those defaults are the safe ones. + esp_err_t settings_ret = settings_init(); + if (settings_ret != ESP_OK) + ESP_LOGE(TAG, "Settings init failed, using defaults: %s", + esp_err_to_name(settings_ret)); bsp_display_start(); ESP_LOGI(TAG, "Display initialized successfully"); @@ -87,10 +92,13 @@ void app_main(void) { } // Initialize BIP39 wordlist (needed for anti-phishing words) - bip39_filter_init(); + if (!bip39_filter_init()) + ESP_LOGE(TAG, "BIP39 wordlist init failed"); - // Initialize PIN module - pin_init(); + // Initialize the PIN module. Fail closed: without it pin_is_configured() + // reports false, and the boot gate below would walk straight past the PIN + // of a device that has one set. + ESP_ERROR_CHECK(pin_init()); // Lock display again for modifications bsp_display_lock(0); diff --git a/main/pages/login/login_scan.c b/main/pages/login/login_scan.c index ed0323c8..fa6a3746 100644 --- a/main/pages/login/login_scan.c +++ b/main/pages/login/login_scan.c @@ -174,9 +174,10 @@ static void on_scan_done(void) { // UR type and descriptor candidate (UR crypto-output/account + plain text) // must be read while the scanner state is still valid. const char *ur_type = NULL; - qr_scanner_get_ur_result(&ur_type, NULL, NULL); - bool psbt_scanned = (ur_type && strcmp(ur_type, "crypto-psbt") == 0) || - is_psbt_content(content, len); + bool has_ur = qr_scanner_get_ur_result(&ur_type, NULL, NULL); + bool psbt_scanned = + (has_ur && ur_type && strcmp(ur_type, "crypto-psbt") == 0) || + is_psbt_content(content, len); char *desc_candidate = descriptor_extract_from_scanner(); qr_scanner_page_hide(); diff --git a/main/pages/pin/pin_settings.c b/main/pages/pin/pin_settings.c index 1472eccf..72a42622 100644 --- a/main/pages/pin/pin_settings.c +++ b/main/pages/pin/pin_settings.c @@ -57,7 +57,10 @@ static const char *threshold_options = "5\n10\n15\n20\n30\n50"; static void threshold_dropdown_cb(lv_event_t *e) { uint16_t sel = lv_dropdown_get_selected(lv_event_get_target(e)); if (sel < sizeof(threshold_values) / sizeof(threshold_values[0])) { - pin_set_max_failures((uint8_t)threshold_values[sel]); + // The dropdown would keep showing the new threshold while the device still + // wipes at the old one. + if (pin_set_max_failures((uint8_t)threshold_values[sel]) != ESP_OK) + dialog_show_error_timeout("Could not save the wipe threshold", NULL, 0); } } @@ -92,7 +95,13 @@ static void disable_confirm_result(bool confirmed, void *user_data) { (void)user_data; if (!confirmed) return; - pin_remove(); + // Returning to the menu on a failed removal would show the PIN as disabled + // while it is still required at the next boot. + if (pin_remove() != ESP_OK) { + dialog_show_error_timeout("Could not remove the PIN - it is still set", + NULL, 0); + return; + } if (return_callback) return_callback(); } diff --git a/main/pages/scan/psbt_sign_policy.c b/main/pages/scan/psbt_sign_policy.c index 18425d81..79e1c7f2 100644 --- a/main/pages/scan/psbt_sign_policy.c +++ b/main/pages/scan/psbt_sign_policy.c @@ -35,8 +35,9 @@ static void remember_flagged_path(sign_policy_review_t *review, review->flagged_index = index; review->flagged_is_input = is_input; - psbt_format_keypath(raw_keypath, raw_keypath_len, review->flagged_path, - sizeof(review->flagged_path)); + if (!psbt_format_keypath(raw_keypath, raw_keypath_len, review->flagged_path, + sizeof(review->flagged_path))) + review->flagged_path[0] = '\0'; // the review renders without a path } static void scan_inputs(struct wally_psbt *psbt, bool is_testnet, diff --git a/main/pages/scan/scan.c b/main/pages/scan/scan.c index 541c0a66..7eba2cf3 100644 --- a/main/pages/scan/scan.c +++ b/main/pages/scan/scan.c @@ -348,11 +348,10 @@ static output_type_t classify_output(size_t output_index, case PSBT_OWNERSHIP_OWNED_UNSAFE: case PSBT_OWNERSHIP_EXPECTED_OWNED: - if (path_out && path_out_size > 0) { - path_out[0] = '\0'; - psbt_format_keypath(ownership.raw_keypath, ownership.raw_keypath_len, - path_out, path_out_size); - } + if (path_out && path_out_size > 0 && + !psbt_format_keypath(ownership.raw_keypath, ownership.raw_keypath_len, + path_out, path_out_size)) + path_out[0] = '\0'; // renders no path line return ownership.ownership == PSBT_OWNERSHIP_OWNED_UNSAFE ? OUTPUT_TYPE_OWNED_UNSAFE : OUTPUT_TYPE_EXPECTED_OWNED; @@ -981,8 +980,9 @@ static void handle_mnemonic_content(const char *data, size_t len) { } // Get current fingerprint - char current_fp[9] = "????????"; - key_get_fingerprint_hex(current_fp); + char current_fp[9]; + if (!key_get_fingerprint_hex(current_fp)) + strcpy(current_fp, "????????"); // Compute new mnemonic's fingerprint without touching the loaded key wallet_network_t net = wallet_get_network(); @@ -1153,12 +1153,12 @@ static bool create_psbt_info_display(void) { format_input_policy(&own, classified_inputs[i].policy, sizeof(classified_inputs[i].policy)); classified_inputs[i].path[0] = '\0'; - if (own.ownership == PSBT_OWNERSHIP_OWNED_UNSAFE || - own.ownership == PSBT_OWNERSHIP_EXPECTED_OWNED) { - psbt_format_keypath(own.raw_keypath, own.raw_keypath_len, - classified_inputs[i].path, - sizeof(classified_inputs[i].path)); - } + if ((own.ownership == PSBT_OWNERSHIP_OWNED_UNSAFE || + own.ownership == PSBT_OWNERSHIP_EXPECTED_OWNED) && + !psbt_format_keypath(own.raw_keypath, own.raw_keypath_len, + classified_inputs[i].path, + sizeof(classified_inputs[i].path))) + classified_inputs[i].path[0] = '\0'; // renders no path line /* External inputs need their address rendered in the warning section. * Skip address decoding for owned inputs — they're not displayed. */ diff --git a/main/pages/session_lock.c b/main/pages/session_lock.c index 64049591..0ffb00f5 100644 --- a/main/pages/session_lock.c +++ b/main/pages/session_lock.c @@ -13,6 +13,9 @@ #include "screensaver.h" #include "video.h" #include +#include + +static const char *TAG = "SESSION_LOCK"; static bool device_locked = false; @@ -38,13 +41,25 @@ static bool migration_pending(void) { return pin_is_configured() && !nvs_secure_is_encrypted(); } +// Declining the migration is meant to leave the device with no PIN. If the +// removal fails the PIN is still set, so say so rather than letting the user +// believe it is gone. +static void remove_pin_or_warn(void) { + esp_err_t err = pin_remove(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "PIN removal failed: %s", esp_err_to_name(err)); + dialog_show_error_timeout("Could not remove the PIN - it is still set", + NULL, 0); + } +} + static void migration_setup_done(void) { pin_page_destroy(); unlock_finished(); } static void migration_setup_cancel(void) { - pin_remove(); + remove_pin_or_warn(); pin_page_destroy(); unlock_finished(); } @@ -52,7 +67,7 @@ static void migration_setup_cancel(void) { static void migration_confirm_result(bool confirmed, void *user_data) { (void)user_data; if (!confirmed) { - pin_remove(); + remove_pin_or_warn(); unlock_finished(); return; } diff --git a/main/pages/settings/registered_descriptors.c b/main/pages/settings/registered_descriptors.c index 92222131..400a3c1f 100644 --- a/main/pages/settings/registered_descriptors.c +++ b/main/pages/settings/registered_descriptors.c @@ -163,8 +163,10 @@ static void remove_confirmed_cb(bool confirmed, void *user_data) { if (!confirmed || pending_remove_index < 0) return; const registry_entry_t *entry = registry_get((size_t)pending_remove_index); - if (entry) - registry_remove(entry->id); + // The list is rebuilt from the registry below, so a failed removal leaves the + // entry visible rather than desyncing the UI - just say why. + if (entry && !registry_remove(entry->id)) + dialog_show_error_timeout("Could not remove the descriptor", NULL, 0); pending_remove_index = -1; selected_descriptor_index = -1; if (action_menu) { diff --git a/main/pages/shared/descriptor_loader.c b/main/pages/shared/descriptor_loader.c index d722f7a9..13420f43 100644 --- a/main/pages/shared/descriptor_loader.c +++ b/main/pages/shared/descriptor_loader.c @@ -569,8 +569,9 @@ static void descriptor_info_confirm_wrapper(const descriptor_info_t *info, int32_t scroll_w = lv_obj_get_content_width(scroll); // Get current wallet fingerprint for highlighting - char my_fp[9] = {0}; - key_get_fingerprint_hex(my_fp); + char my_fp[9]; + if (!key_get_fingerprint_hex(my_fp)) + my_fp[0] = '\0'; // matches no key, so nothing is highlighted // Key entries for (uint32_t i = 0; i < info->num_keys; i++) { diff --git a/main/pages/store_descriptor.c b/main/pages/store_descriptor.c index 94f455db..34efcc13 100644 --- a/main/pages/store_descriptor.c +++ b/main/pages/store_descriptor.c @@ -220,7 +220,8 @@ void store_descriptor_page_create_for_descriptor( dialog_show_error_timeout("No descriptor loaded", return_cb, 0); return; } - descriptor_checksum_from_descriptor(descriptor, descriptor_default_id); + if (!descriptor_checksum_from_descriptor(descriptor, descriptor_default_id)) + descriptor_default_id[0] = '\0'; // the id field opens blank to fill in const char *title = (location == STORAGE_FLASH) ? "Save to Flash" : "Save to SD Card"; diff --git a/main/qr/encoder.h b/main/qr/encoder.h index be25397a..28186866 100644 --- a/main/qr/encoder.h +++ b/main/qr/encoder.h @@ -1,6 +1,7 @@ #ifndef QR_ENCODER_H #define QR_ENCODER_H +#include "../utils/attributes.h" #include #include #include @@ -46,6 +47,10 @@ lv_result_t qr_update_optimal(lv_obj_t *qr_obj, const char *text, * @param parent Parent object * @param size Widget size in pixels * @param text Text to encode, or NULL to defer + * The widget parents itself to `parent`, so discarding the handle leaks + * nothing and carries no KERN_WARN_UNUSED_RESULT: callers that only want the + * code drawn may ignore it, and on failure the container simply stays empty. + * * @return QR widget on success, NULL on failure */ lv_obj_t *qr_create_optimal(lv_obj_t *parent, int32_t size, const char *text); @@ -84,8 +89,10 @@ void qr_set_light_color(lv_obj_t *qr_obj, lv_color_t color); * @param qr_buf Output buffer (>= QR_CODE_BUF_LEN bytes) * @return Module count on success, 0 on failure */ -int qr_encode_optimal(const char *text, uint8_t *qr_buf); -int qr_encode_binary(const uint8_t *data, size_t len, uint8_t *qr_buf); +KERN_WARN_UNUSED_RESULT int qr_encode_optimal(const char *text, + uint8_t *qr_buf); +KERN_WARN_UNUSED_RESULT int qr_encode_binary(const uint8_t *data, size_t len, + uint8_t *qr_buf); /** * @brief Draw a sub-rectangle of an encoded QR onto a widget's canvas. @@ -116,7 +123,7 @@ void qr_draw_region(lv_obj_t *qr_obj, const uint8_t *qr_buf, int x0, int y0, * @return Allocated uppercased copy (caller must free), or NULL if the * string is not a lowercase bech32 string */ -char *qr_bech32_to_upper(const char *text); +KERN_WARN_UNUSED_RESULT char *qr_bech32_to_upper(const char *text); /** * @brief Mnemonic QR code format types @@ -164,8 +171,9 @@ mnemonic_qr_format_t mnemonic_qr_detect_format(const char *data, size_t len); * @return Allocated mnemonic string on success (caller must free), * or NULL on failure */ -char *mnemonic_qr_to_mnemonic(const char *data, size_t len, - mnemonic_qr_format_t *format_out); +KERN_WARN_UNUSED_RESULT char * +mnemonic_qr_to_mnemonic(const char *data, size_t len, + mnemonic_qr_format_t *format_out); /** * @brief Convert Compact SeedQR binary data to mnemonic @@ -175,7 +183,8 @@ char *mnemonic_qr_to_mnemonic(const char *data, size_t len, * @return Allocated mnemonic string on success (caller must free), * or NULL on failure */ -char *mnemonic_qr_compact_to_mnemonic(const unsigned char *data, size_t len); +KERN_WARN_UNUSED_RESULT char * +mnemonic_qr_compact_to_mnemonic(const unsigned char *data, size_t len); /** * @brief Convert SeedQR numeric string to mnemonic @@ -188,7 +197,8 @@ char *mnemonic_qr_compact_to_mnemonic(const unsigned char *data, size_t len); * @return Allocated mnemonic string on success (caller must free), * or NULL on failure */ -char *mnemonic_qr_seedqr_to_mnemonic(const char *data, size_t len); +KERN_WARN_UNUSED_RESULT char *mnemonic_qr_seedqr_to_mnemonic(const char *data, + size_t len); /** * @brief Get a human-readable name for a format @@ -196,7 +206,8 @@ char *mnemonic_qr_seedqr_to_mnemonic(const char *data, size_t len); * @param format The format type * @return Static string with format name */ -const char *mnemonic_qr_format_name(mnemonic_qr_format_t format); +KERN_WARN_UNUSED_RESULT const char * +mnemonic_qr_format_name(mnemonic_qr_format_t format); /** * @brief Convert a BIP39 mnemonic phrase to SeedQR format @@ -208,7 +219,7 @@ const char *mnemonic_qr_format_name(mnemonic_qr_format_t format); * @return Allocated SeedQR string on success (caller must free), * or NULL on failure */ -char *mnemonic_to_seedqr(const char *mnemonic); +KERN_WARN_UNUSED_RESULT char *mnemonic_to_seedqr(const char *mnemonic); /** * @brief Convert a BIP39 mnemonic phrase to Compact SeedQR format diff --git a/main/qr/parser.h b/main/qr/parser.h index 1c08386f..f2590e77 100644 --- a/main/qr/parser.h +++ b/main/qr/parser.h @@ -1,6 +1,8 @@ #ifndef QR_PARSER_H #define QR_PARSER_H +#include "../utils/attributes.h" + #include #include #include @@ -70,7 +72,7 @@ typedef struct { * * @return Pointer to new parser instance, or NULL on failure */ -QRPartParser *qr_parser_create(void); +KERN_WARN_UNUSED_RESULT QRPartParser *qr_parser_create(void); /** * @brief Destroy parser and free all associated memory @@ -91,7 +93,7 @@ void qr_parser_destroy(QRPartParser *parser); * @param parser Parser instance * @return Number of parsed parts */ -int qr_parser_parsed_count(QRPartParser *parser); +KERN_WARN_UNUSED_RESULT int qr_parser_parsed_count(QRPartParser *parser); /** * @brief Get the number of processed parts (including duplicates) @@ -102,7 +104,8 @@ int qr_parser_parsed_count(QRPartParser *parser); * @param parser Parser instance * @return Number of processed parts */ -int qr_parser_processed_parts_count(QRPartParser *parser); +KERN_WARN_UNUSED_RESULT int +qr_parser_processed_parts_count(QRPartParser *parser); /** * @brief Get the total expected number of parts @@ -113,7 +116,7 @@ int qr_parser_processed_parts_count(QRPartParser *parser); * @param parser Parser instance * @return Total expected parts, or -1 if not yet determined */ -int qr_parser_total_count(QRPartParser *parser); +KERN_WARN_UNUSED_RESULT int qr_parser_total_count(QRPartParser *parser); /** * @brief Parse a QR code data string @@ -125,7 +128,8 @@ int qr_parser_total_count(QRPartParser *parser); * @param data QR code data string to parse * @return Part index on success, or -1 on failure */ -int qr_parser_parse(QRPartParser *parser, const char *data); +KERN_WARN_UNUSED_RESULT int qr_parser_parse(QRPartParser *parser, + const char *data); /** * @brief Parse QR code data with explicit length @@ -138,8 +142,9 @@ int qr_parser_parse(QRPartParser *parser, const char *data); * @param data_len Length of the data in bytes * @return Part index on success, or -1 on failure */ -int qr_parser_parse_with_len(QRPartParser *parser, const char *data, - size_t data_len); +KERN_WARN_UNUSED_RESULT int qr_parser_parse_with_len(QRPartParser *parser, + const char *data, + size_t data_len); /** * @brief Check if all expected parts have been received @@ -150,7 +155,7 @@ int qr_parser_parse_with_len(QRPartParser *parser, const char *data, * @param parser Parser instance * @return true if parsing is complete, false otherwise */ -bool qr_parser_is_complete(QRPartParser *parser); +KERN_WARN_UNUSED_RESULT bool qr_parser_is_complete(QRPartParser *parser); /** * @brief Check if parsing has failed permanently @@ -161,7 +166,7 @@ bool qr_parser_is_complete(QRPartParser *parser); * @param parser Parser instance * @return true if parsing can never complete, false otherwise */ -bool qr_parser_is_failed(QRPartParser *parser); +KERN_WARN_UNUSED_RESULT bool qr_parser_is_failed(QRPartParser *parser); /** * @brief Get the assembled result from all parsed parts @@ -178,7 +183,8 @@ bool qr_parser_is_failed(QRPartParser *parser); * @return Allocated string containing the result, or NULL on failure. * Caller must free the returned string. */ -char *qr_parser_result(QRPartParser *parser, size_t *result_len); +KERN_WARN_UNUSED_RESULT char *qr_parser_result(QRPartParser *parser, + size_t *result_len); /** * @brief Get the UR decoder result (for FORMAT_UR only) @@ -194,9 +200,9 @@ char *qr_parser_result(QRPartParser *parser, size_t *result_len); * @param cbor_len_out Pointer to store CBOR data length * @return true on success, false on failure */ -bool qr_parser_get_ur_result(QRPartParser *parser, const char **ur_type_out, - const uint8_t **cbor_data_out, - size_t *cbor_len_out); +KERN_WARN_UNUSED_RESULT bool +qr_parser_get_ur_result(QRPartParser *parser, const char **ur_type_out, + const uint8_t **cbor_data_out, size_t *cbor_len_out); /** * @brief Get the detected QR format @@ -206,7 +212,7 @@ bool qr_parser_get_ur_result(QRPartParser *parser, const char **ur_type_out, * @param parser Parser instance * @return QR format (FORMAT_* constants) */ -int qr_parser_get_format(QRPartParser *parser); +KERN_WARN_UNUSED_RESULT int qr_parser_get_format(QRPartParser *parser); /** * @brief Get the BBQr file type character (for FORMAT_BBQR only) @@ -226,6 +232,6 @@ char qr_parser_get_bbqr_file_type(QRPartParser *parser); * @param qr_code Encoded QR code data * @return Estimated QR code size in modules */ -int get_qr_size(const char *qr_code); +KERN_WARN_UNUSED_RESULT int get_qr_size(const char *qr_code); #endif \ No newline at end of file diff --git a/main/qr/scanner.h b/main/qr/scanner.h index 84653583..263bb09a 100644 --- a/main/qr/scanner.h +++ b/main/qr/scanner.h @@ -8,6 +8,7 @@ #define QR_SCANNER_H #include "../components/video/video.h" +#include "../utils/attributes.h" #include #include @@ -40,7 +41,7 @@ void qr_scanner_page_destroy(void); * @return Completed QR content string (caller must free), or NULL if no * completed content */ -char *qr_scanner_get_completed_content(void); +KERN_WARN_UNUSED_RESULT char *qr_scanner_get_completed_content(void); /** * @brief Get completed QR content with length information @@ -52,14 +53,15 @@ char *qr_scanner_get_completed_content(void); * @return Completed QR content (caller must free), or NULL if no completed * content */ -char *qr_scanner_get_completed_content_with_len(size_t *content_len); +KERN_WARN_UNUSED_RESULT char * +qr_scanner_get_completed_content_with_len(size_t *content_len); /** * @brief Check if QR scanner is fully initialized and ready * * @return true if scanner is ready, false otherwise */ -bool qr_scanner_is_ready(void); +KERN_WARN_UNUSED_RESULT bool qr_scanner_is_ready(void); /** * @brief Check if the scanner has completed QR content @@ -67,7 +69,7 @@ bool qr_scanner_is_ready(void); * @return true if a QR was fully scanned, false if the scanner was canceled or * no complete QR is available yet */ -bool qr_scanner_has_completed_result(void); +KERN_WARN_UNUSED_RESULT bool qr_scanner_has_completed_result(void); /** * @brief Get the detected QR code format @@ -75,7 +77,7 @@ bool qr_scanner_has_completed_result(void); * @return QR format constant (FORMAT_NONE, FORMAT_PMOFN, FORMAT_UR, etc.) * Returns -1 if no format detected yet */ -int qr_scanner_get_format(void); +KERN_WARN_UNUSED_RESULT int qr_scanner_get_format(void); /** * @brief Get the BBQr file type character (for FORMAT_BBQR only) @@ -96,8 +98,8 @@ char qr_scanner_get_bbqr_file_type(void); * @param cbor_len_out Pointer to store CBOR data length * @return true on success, false on failure */ -bool qr_scanner_get_ur_result(const char **ur_type_out, - const uint8_t **cbor_data_out, - size_t *cbor_len_out); +KERN_WARN_UNUSED_RESULT bool +qr_scanner_get_ur_result(const char **ur_type_out, + const uint8_t **cbor_data_out, size_t *cbor_len_out); #endif // QR_SCANNER_H diff --git a/main/qr/viewer.c b/main/qr/viewer.c index 0256da29..6082927a 100644 --- a/main/qr/viewer.c +++ b/main/qr/viewer.c @@ -676,8 +676,11 @@ bool qr_viewer_page_create_with_format(lv_obj_t *parent, int qr_format, void qr_viewer_page_create(lv_obj_t *parent, const char *qr_content, const char *title, void (*return_cb)(void)) { - qr_viewer_page_create_with_format(parent, FORMAT_NONE, qr_content, title, - return_cb); + // No page was built, so without this the user is left on a blank screen with + // nothing to return through. + if (!qr_viewer_page_create_with_format(parent, FORMAT_NONE, qr_content, title, + return_cb)) + dialog_show_error_timeout("Could not display the QR code", return_cb, 0); } typedef struct { diff --git a/main/qr/viewer.h b/main/qr/viewer.h index 0d329106..0bc72b8c 100644 --- a/main/qr/viewer.h +++ b/main/qr/viewer.h @@ -1,6 +1,7 @@ #ifndef QR_VIEWER_H #define QR_VIEWER_H +#include "../utils/attributes.h" #include /** @@ -22,9 +23,10 @@ void qr_viewer_page_create(lv_obj_t *parent, const char *qr_content, * @param return_cb Callback function to call when returning * @return true on success, false on failure */ -bool qr_viewer_page_create_with_format(lv_obj_t *parent, int qr_format, - const char *content, const char *title, - void (*return_cb)(void)); +KERN_WARN_UNUSED_RESULT bool +qr_viewer_page_create_with_format(lv_obj_t *parent, int qr_format, + const char *content, const char *title, + void (*return_cb)(void)); /** * Make a widget open the QR viewer fullscreen when tapped (tap again to diff --git a/main/utils/attributes.h b/main/utils/attributes.h new file mode 100644 index 00000000..767c8e38 --- /dev/null +++ b/main/utils/attributes.h @@ -0,0 +1,23 @@ +/* + * Compiler attributes used across the project. + */ + +#ifndef KERN_ATTRIBUTES_H +#define KERN_ATTRIBUTES_H + +/* + * KERN_WARN_UNUSED_RESULT - the compiler warns at any call site that discards + * the return value. Applied to functions that report failure through their + * return value and nothing else, and to queries whose answer is the only + * reason to call them. The build turns the warning into an error. + * + * NOTE: a (void) cast does not suppress this under GCC. To ignore a result + * deliberately, consume it with `if (call()) { }` and say why. + */ +#if defined(__GNUC__) || defined(__clang__) +#define KERN_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) +#else +#define KERN_WARN_UNUSED_RESULT +#endif + +#endif // KERN_ATTRIBUTES_H diff --git a/main/utils/bip39_filter.c b/main/utils/bip39_filter.c index ac8849e2..1d06c428 100644 --- a/main/utils/bip39_filter.c +++ b/main/utils/bip39_filter.c @@ -96,13 +96,15 @@ int bip39_filter_get_word_index(const char *word) { void bip39_filter_clear_last_word_cache(void) { valid_last_words_count = 0; } -static void ensure_last_word_cache(const char entered_words[24][16], - int word_count) { +/* Warms valid_last_words_cache and returns how many entries it holds. */ +static int ensure_last_word_cache(const char entered_words[24][16], + int word_count) { if (valid_last_words_count == 0) { const char *temp[MAX_VALID_LAST_WORDS]; - bip39_filter_get_valid_last_words(entered_words, word_count, temp, - MAX_VALID_LAST_WORDS); + return bip39_filter_get_valid_last_words(entered_words, word_count, temp, + MAX_VALID_LAST_WORDS); } + return valid_last_words_count; } int bip39_filter_get_valid_last_words(const char entered_words[24][16], @@ -186,8 +188,7 @@ bip39_filter_get_valid_letters_for_last_word(const char entered_words[24][16], if (!wordlist) return 0xFFFFFFFF; - ensure_last_word_cache(entered_words, word_count); - if (valid_last_words_count == 0) + if (ensure_last_word_cache(entered_words, word_count) == 0) return 0; uint32_t mask = 0; @@ -226,8 +227,7 @@ int bip39_filter_last_word_by_prefix(const char entered_words[24][16], if (!wordlist || !out_words || max_words <= 0) return 0; - ensure_last_word_cache(entered_words, word_count); - if (valid_last_words_count == 0) + if (ensure_last_word_cache(entered_words, word_count) == 0) return 0; if (!prefix || prefix_len <= 0) { diff --git a/main/utils/bip39_filter.h b/main/utils/bip39_filter.h index a23d2bf8..59b2780e 100644 --- a/main/utils/bip39_filter.h +++ b/main/utils/bip39_filter.h @@ -3,6 +3,7 @@ #ifndef BIP39_FILTER_H #define BIP39_FILTER_H +#include "attributes.h" #include #include @@ -15,7 +16,7 @@ * Safe to call multiple times (subsequent calls are no-ops). * @return true on success, false on failure */ -bool bip39_filter_init(void); +KERN_WARN_UNUSED_RESULT bool bip39_filter_init(void); /** * Get a bitmask of valid next letters for a given prefix. @@ -24,7 +25,8 @@ bool bip39_filter_init(void); * @param prefix_len Length of prefix * @return 26-bit mask (bits 0-25 for a-z), or 0xFFFFFFFF if wordlist not loaded */ -uint32_t bip39_filter_get_valid_letters(const char *prefix, int prefix_len); +KERN_WARN_UNUSED_RESULT uint32_t +bip39_filter_get_valid_letters(const char *prefix, int prefix_len); /** * Filter words by prefix and return matches. @@ -34,8 +36,10 @@ uint32_t bip39_filter_get_valid_letters(const char *prefix, int prefix_len); * @param max_words Maximum number of words to return * @return Number of words written to out_words */ -int bip39_filter_by_prefix(const char *prefix, int prefix_len, - const char **out_words, int max_words); +KERN_WARN_UNUSED_RESULT int bip39_filter_by_prefix(const char *prefix, + int prefix_len, + const char **out_words, + int max_words); /** * Count total number of words matching a prefix. @@ -43,14 +47,15 @@ int bip39_filter_by_prefix(const char *prefix, int prefix_len, * @param prefix_len Length of prefix * @return Number of matching words, or BIP39_WORDLIST_SIZE if prefix is empty */ -int bip39_filter_count_matches(const char *prefix, int prefix_len); +KERN_WARN_UNUSED_RESULT int bip39_filter_count_matches(const char *prefix, + int prefix_len); /** * Get the index (0-2047) of a BIP39 word. * @param word The word to look up * @return Word index (0-2047), or -1 if not found */ -int bip39_filter_get_word_index(const char *word); +KERN_WARN_UNUSED_RESULT int bip39_filter_get_word_index(const char *word); /** * Clear the cached valid last words. Call this when moving to the last word @@ -66,9 +71,10 @@ void bip39_filter_clear_last_word_cache(void); * @param max_words Maximum words to return * @return Number of valid last words found */ -int bip39_filter_get_valid_last_words(const char entered_words[24][16], - int word_count, const char **out_words, - int max_words); +KERN_WARN_UNUSED_RESULT int +bip39_filter_get_valid_last_words(const char entered_words[24][16], + int word_count, const char **out_words, + int max_words); /** * Get bitmask of valid keyboard letters for last word position. @@ -94,9 +100,8 @@ bip39_filter_get_valid_letters_for_last_word(const char entered_words[24][16], * @param max_words Maximum words to return * @return Number of matching valid last words */ -int bip39_filter_last_word_by_prefix(const char entered_words[24][16], - int word_count, const char *prefix, - int prefix_len, const char **out_words, - int max_words); +KERN_WARN_UNUSED_RESULT int bip39_filter_last_word_by_prefix( + const char entered_words[24][16], int word_count, const char *prefix, + int prefix_len, const char **out_words, int max_words); #endif // BIP39_FILTER_H diff --git a/simulator/src/main_sim.c b/simulator/src/main_sim.c index b53a106b..7673032b 100644 --- a/simulator/src/main_sim.c +++ b/simulator/src/main_sim.c @@ -196,8 +196,13 @@ int main(int argc, char *argv[]) { return 1; } - /* Initialize persistent settings */ - settings_init(); + /* Initialize persistent settings. Not fatal: every getter falls back to + * its default when the namespace is unavailable. */ + esp_err_t settings_ret = settings_init(); + if (settings_ret != ESP_OK) { + fprintf(stderr, "Settings init failed, using defaults: %s\n", + esp_err_to_name(settings_ret)); + } /* Initialize PMIC (simulated battery on wave_35; no-op on wave_4b) */ bsp_pmic_init(); @@ -216,8 +221,17 @@ int main(int argc, char *argv[]) { /* ----------------------------------------------------------------------- * Initialize application modules (while splash plays) * --------------------------------------------------------------------- */ - bip39_filter_init(); - pin_init(); + if (!bip39_filter_init()) { + fprintf(stderr, "BIP39 wordlist init failed\n"); + } + + /* Fail closed: without it pin_is_configured() reports false, and the boot + * gate would walk straight past the PIN of a device that has one set. */ + esp_err_t pin_ret = pin_init(); + if (pin_ret != ESP_OK) { + fprintf(stderr, "PIN init failed: %s\n", esp_err_to_name(pin_ret)); + return 1; + } /* Start inactivity monitoring (screensaver + session lock) */ session_lock_init();