mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
fix(cashu): address pre-merge audit findings
- Invalidate the cached NUT-13 seed in applyEvents whenever the live kind:17375 changes, so after a P2PK key rotation (recreateNutzapKey, or a rotation from another client) deterministic secrets re-derive from the new key instead of a stale cached seed. Removes the now-redundant reset in recreateNutzapKey. - AccountSettings.updateNutzapInfo no longer backs up a mints-less kind:10019 (the "stop receiving nutzaps" tombstone), clearing the backup instead — so the empty event round-tripping back through LocalCache can't undo clearNutzapInfo() and resurrect a withdrawn nutzap advertisement on next launch. - Key keyMode's remember on isEditMode in AddCashuWalletScreen so a wallet delivered after first composition flips to KeepCurrent, preventing a silent key rotation on save in the cold-open race. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SXRAunSJS2dBx7B79qTMew
This commit is contained in:
@@ -942,6 +942,14 @@ class AccountSettings(
|
||||
|
||||
fun updateNutzapInfo(newNutzapInfo: NutzapInfoEvent?) {
|
||||
if (newNutzapInfo == null || newNutzapInfo.tags.isEmpty()) return
|
||||
// A mints-less kind:10019 is the "stop receiving nutzaps" tombstone
|
||||
// (an empty replacement carrying only an `alt` tag). Don't restore it
|
||||
// on next launch — backing it up would undo clearNutzapInfo() once the
|
||||
// empty event round-trips back through LocalCache.
|
||||
if (newNutzapInfo.mints().isEmpty()) {
|
||||
clearNutzapInfo()
|
||||
return
|
||||
}
|
||||
if (backupNutzapInfo?.id != newNutzapInfo.id) {
|
||||
backupNutzapInfo = newNutzapInfo
|
||||
saveAccountSettings()
|
||||
|
||||
+8
-4
@@ -563,6 +563,11 @@ class CashuWalletState(
|
||||
// Any wallet event resolves the "discovering" state — whether it
|
||||
// came from cache backfill or a fresh relay delivery.
|
||||
_discovering.value = false
|
||||
// The NUT-13 seed is derived from the wallet's P2PK key. A new
|
||||
// kind:17375 may carry a rotated key (our own recreateNutzapKey, or
|
||||
// a rotation from another client), so drop the cached seed and let
|
||||
// ensureSeed re-derive from whatever key the live event now holds.
|
||||
cachedSeed = null
|
||||
walletEventInternal?.let { evt ->
|
||||
_mints.value =
|
||||
runCatching { evt.mints(signer) }
|
||||
@@ -838,10 +843,9 @@ class CashuWalletState(
|
||||
p2pkPrivkeyHex = manualPrivkeyHex?.takeIf { it.isNotBlank() },
|
||||
nutzapRelays = outboxRelaysFlow.value.toList(),
|
||||
)
|
||||
// The NUT-13 seed is derived from the P2PK key, which just changed —
|
||||
// drop the cache so the next mint op re-derives from the new key
|
||||
// (re-populated lazily by ensureSeed once the new kind:17375 lands).
|
||||
cachedSeed = null
|
||||
// The NUT-13 seed (derived from the P2PK key) is invalidated by
|
||||
// applyEvents when the new kind:17375 round-trips in, so it re-derives
|
||||
// from the rotated key. No need to reset it here.
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
||||
+5
-1
@@ -99,7 +99,11 @@ fun AddCashuWalletScreen(
|
||||
// suggestions on first open instead of waiting for the next relay
|
||||
// round-trip. Cheap (one sweep over the existing cache); idempotent.
|
||||
LaunchedEffect(Unit) { LocalCache.ensureMintDirectoryBackfilled() }
|
||||
var keyMode by remember {
|
||||
// Keyed on isEditMode so that if the wallet is delivered AFTER this screen
|
||||
// first composes (existingWallet null → non-null), keyMode flips to
|
||||
// KeepCurrent. Otherwise a cold open with an undelivered wallet would keep
|
||||
// AutoGenerate and silently rotate the key on save, orphaning nutzaps.
|
||||
var keyMode by remember(isEditMode) {
|
||||
mutableStateOf(
|
||||
if (isEditMode) CashuWalletViewModel.P2pkKeyMode.KeepCurrent else CashuWalletViewModel.P2pkKeyMode.AutoGenerate,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user