Ask before moving a mint's signing-key pin - #66
Merged
Conversation
LUD-25 gained the rule on 2026-09-04, in the same change that dropped previousPubkeys from the spec: "a newly advertised mintPubkey MUST NOT silently replace a pinned one, WALLET MUST require explicit holder approval". This wallet moved the pin on its own whenever the mint published the old key as retired, and mentioned it afterwards in a warning nobody has to read. That treats a published retirement as consent, and it is only evidence. Whoever controls the host controls what it publishes, so a mint someone else now runs can list the key it displaced and look exactly like a mint rotating properly. The two cases are indistinguishable from here, which is precisely why the holder is the one who decides. An announced rotation now throws KeyRotationError and writes nothing - not the pin, not the key history, not the note being received. A refusal that half-applied would be worse than either outcome: the pin would still name the old key while the history and the rotated-on date said a rotation had happened, so the next receive would look like a second one. receive --accept-key-rotation takes it. An unannounced rotation is unchanged: PinMismatchError, no approval on offer. The difference between the two errors is whether there is a decision to make at all, and the KeyRotationError message names both possibilities rather than picking one - a message that only said "rotated" would be asking for a rubber stamp. 324 tests pass.
TheCryptoDonkey
added a commit
that referenced
this pull request
Sep 6, 2026
#66 made an announced signing-key rotation a decision rather than something the wallet takes on its own. It gave the CLI a flag and left the browser with a refusal it could not show: main.ts catches BadSignatureError and rethrows anything else, so a KeyRotationError left `take()` as an unhandled rejection. The receive would fail with nothing on screen and no way past it. So the PWA gets its own refusal card. It is deliberately not the signature card with different words, because the two refusals are different in kind. A signature that does not verify is a fact about the note in your hand. A mint presenting a new signing key is a question about the mint, and one the wallet genuinely cannot answer: a mint rotating properly and a mint somebody else now runs publish the same two claims from the same server. The card says exactly that, and says the one thing a holder can actually do about it - check the new key by some route that is not this mint. It also says that notes already held keep working, because the old key is kept, so the decision is not being made under a false clock. Two taps to accept, matching the signature override, and for the same reason: the honest case for overriding is that you already know something the wallet does not. 324 tests pass, web typecheck clean. Co-authored-by: TheCryptoDonkey <TheCryptoDonkey@users.noreply.github.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
LUD-25 gained this rule on 2026-09-04, in the same change that dropped
previousPubkeysfrom the spec:This wallet moved the pin on its own whenever the mint published the old key as retired, and mentioned it afterwards in a warning nobody has to read.
Why the old behaviour was wrong
A published retirement is evidence, never consent. Whoever controls the host controls what it publishes, so a mint someone else now runs can list the key it displaced and look exactly like a mint rotating properly. The two are indistinguishable from here - which is precisely why the holder is the one who decides.
What changes
KeyRotationErrorand writes nothing: not the pin, not the key history, not the note being received.receive --accept-key-rotation(orapproveKeyRotation: trueon the API) takes it.PinMismatchError, no approval on offer. The difference between the two errors is whether there is a decision to make at all.KeyRotationErrormessage names both possibilities rather than picking one. A message that only said "rotated" would be asking for a rubber stamp.A half-applied refusal would be worse than either outcome: the pin would still name the old key while the history and the rotated-on date said a rotation had happened, so the next receive would look like a second one. There is a test for exactly that.
Verification
npm run checkcleantest/rotation.test.tsgrew to 11 cases: the two acceptance tests now prove the refusal first, plus new cases for "writes nothing when unapproved" and for the wording of the question