Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
38 changes: 38 additions & 0 deletions main/core/psbt.c
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading