diff --git a/CHANGELOG.md b/CHANGELOG.md index 62369f8..769270a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [0.0.10] - 2026-05-30 + +### Added +- Login screen branding: pulsing logo, dice icon, and an About info button in the top-right corner +- Menu items can carry an icon +- CrowPanel 10.1 case + +### Changed +- Landscape layouts for square devices, menus, and the public key page (2x2 grid) +- Harmonized page titles, corner buttons, two-tier buttons, and theme-based dialog spacing +- Danger dialogs use a warning icon and semantic colours +- Bumped libwally-core to 1.5.3 + +### Fixed +- Taproot PSBTs: testnet is detected correctly (no longer misread as mainnet) and change/self-transfer outputs are recognized instead of being treated as spends +- Theme paddings and button widths scale to the smallest screen dimension +- Mnemonic word-grid and battery icon alignment +- About page logo/QR sizing in landscape + ## [0.0.9] - 2026-05-23 ### Fixed diff --git a/main/core/psbt.c b/main/core/psbt.c index b6b0585..8c861de 100644 --- a/main/core/psbt.c +++ b/main/core/psbt.c @@ -441,6 +441,30 @@ output_ownership_t psbt_classify_output(const struct wally_psbt *psbt, size_t i, } } + /* Taproot outputs — like the input classifier, libwally stores + * PSBT_OUT_TAP_BIP32_DERIVATION entries in a separate map whose value has + * the same fp(4)|path shape as the segwit keypaths. Without this, taproot + * change/self-transfer outputs are mistaken for external spends. */ + const struct wally_map *tp_paths = &psbt->outputs[i].taproot_leaf_paths; + for (size_t j = 0; j < tp_paths->num_items; j++) { + const struct wally_map_item *item = &tp_paths->items[j]; + const unsigned char *val = item->value; + size_t val_len = item->value_len; + if (!keypath_matches_fingerprint(val, val_len, our_fp)) + continue; + + claim_t claim = {0}; + remember_raw_keypath(result.raw_keypath, sizeof(result.raw_keypath), + &result.raw_keypath_len, val, val_len); + if (try_match_claim(val, val_len, is_testnet, out_script, out_script_len, + NULL, 0, &claim)) { + result.ownership = PSBT_OWNERSHIP_OWNED_SAFE; + result.source = claim; + wally_tx_free(global_tx); + return result; + } + } + /* fp matched but no whitelist/registry claim verified. Same harness * split as the input classifier: OWNED_UNSAFE if derive verifies, * EXPECTED_OWNED otherwise. */ @@ -496,6 +520,14 @@ bool psbt_detect_network(const struct wally_psbt *psbt) { check_keypath_network(keypath, keypath_len, &is_testnet)) { return is_testnet; } + // Taproot outputs store derivation in a separate map (PSBT_OUT_TAP_BIP32), + // whose value has the same fp(4)|path shape as the segwit keypaths. + const struct wally_map *tp = &psbt->outputs[i].taproot_leaf_paths; + if (tp->num_items > 0 && + check_keypath_network(tp->items[0].value, tp->items[0].value_len, + &is_testnet)) { + return is_testnet; + } } // Check inputs as fallback @@ -511,6 +543,12 @@ bool psbt_detect_network(const struct wally_psbt *psbt) { check_keypath_network(keypath, keypath_len, &is_testnet)) { return is_testnet; } + const struct wally_map *tp = &psbt->inputs[i].taproot_leaf_paths; + if (tp->num_items > 0 && + check_keypath_network(tp->items[0].value, tp->items[0].value_len, + &is_testnet)) { + return is_testnet; + } } return false; // Default to mainnet