Keyset final_expiry silently kills held balances — wallets must rotate off inactive keysets (NUT-02)

Abierto
#417 3 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
5/5
Tiempo estimado
Más de una semana
Aptitud para principiantes
28/100
Tipo de issue
Nueva funcionalidad
Claridad
Bastante claro
Estado de actividad
Activo
Stack tecnológico
go
Área
backend, payments

Línea de trabajo

Start with tests/cloud-lab/run-keyset-rotation.sh and the related issues #409, #413, and #416 to understand the existing rotation and error-classification work. The issue proposes several separable directions, so confirm which one is in scope before starting; for the lifecycle monitor, inspect the keyset refresh and held-proof handling entry points. Done means the selected behavior is covered by tests, including the expired-keyset case.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Wallet hygiene: keyset final_expiry silently kills held balances — wallets must rotate off inactive keysets (NUT-02 SHOULD). Proven in the cloud lab.

What happened (lab evidence, #416): paying with proofs whose keyset was expired fails with "could not swap proofs: Keyset has expired" — the proofs are permanently unspendable at that mint. This is spec-correct mint behavior, not a bug: NUT-02 Keyset Final Expiry says that after final_expiry the mint "is no longer obliged to fulfill promises signed with the keys from that keyset" and may irrevocably delete the keyset's nullifiers. The spec even standardized the refusal: error code 12003 "Keyset has expired" (nuts PR #367). Implementations differ — cdk enforces expiry, nutshell never expires (nutshell #7, tracked in the cashu-audit divergence database) — and the spec's active flag conflates mintable vs redeemable, but none of that is ours to fix upstream.

Why it matters to TollGate: any wallet balance held across a keyset rotation+expiry window can be wiped:

  • Reseller mode: the reseller holds balances at upstream mints (min_balance in accepted_mints) between purchases — an upstream that rotates with expiry (cdk mints do) silently destroys that float.
  • Operator payouts: the merchant wallet holds received funds between payout cycles (min_payout_amount, payout_interval_seconds); a fee-charging cdk-style mint rotating keysets mid-cycle destroys receivables.
  • Customers: a customer holding a token from an expired keyset gets a refusal — currently misclassified as payment-error-mint-unreachable (see the note on #409), telling them to retry a permanently dead token.

Suggested work items (split-able):

  1. Keyset lifecycle monitor: on keyset refresh, detect active=false for keysets with held proofs and proactively swap them to the active keyset (NUT-02: wallets SHOULD prioritize this; NUT-03 swap). React to final_expiry in the keyset response with an operator warning when held value is affected.
  2. Classifier fix (already noted on #409): match "Keyset has expired" / code 12003 to a dedicated payment-error-keyset-expired notice instead of mint-unreachable, keeping the cause in the message. The pin in test_keyset_rotation.py updates consciously with it.
  3. Advertisement/trust surface (product question): consider surfacing keyset-expiry policy (e.g. keysets with final_expiry set) in mint metadata evaluation, so routers can prefer mints without expiry time bombs for float.

Repro: tests/cloud-lab/run-keyset-rotation.sh (phase A mints on a 100-ppk keyset; phase B expires it; the stashed token dies with 12003-class refusal).

Refs: #416 (rotation lane + pins), #409 (classifier), #413 (fee mint), cashu-audit issues/keyset-active-flag-semantics.md + divergences/CDK-VS-NUTSHELL-ALIGNMENT-2026-07-29.md ("#7 old keysets never expire"), nuts #367 (error 12003), NUT-02 §Keyset Final Expiry.

Lenguaje dominante
Go
Estrellas
12
Forks
14
Merge medio
3 d 20 h
PR fusionados (30 d)
261

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de OpenTollGate/tollgate-module-basic-go

Todos los issues de OpenTollGate/tollgate-module-basic-go

Issues similares

Más issues de Go

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.