Skip to content

Mint note secrets under LUD-25's own derivation - #64

Merged
TheCryptoDonkey merged 1 commit into
mainfrom
feat/lud25-cash-derivation
Sep 4, 2026
Merged

Mint note secrets under LUD-25's own derivation#64
TheCryptoDonkey merged 1 commit into
mainfrom
feat/lud25-cash-derivation

Conversation

@TheCryptoDonkey

Copy link
Copy Markdown
Member

The draft specifies a BIP-32 scheme under m/139' and the reference wallet implements it. This wallet had been using lnurlcash-kit's pre-spec HMAC scheme, which shipped four days before that section existed. The same twelve words now restore the same notes in any wallet that implements LUD-25, which was the entire point of deriving them.

(d1, d2, d3, d4) = HMAC-SHA256(key = m/139'/0, msg = "<mint host>")[0..16]
k1[i]            = m/139'/d1/d2/d3/d4/i'

Nothing already minted is lost

restoreFromMint walks both ladders in one pass and records which one found each note. A record with no scheme is a pre-spec one, which is exactly what every record written before now is, so no stored wallet needs rewriting. Those notes keep working and move onto the new ladder the first time they are rotated.

Two counters, and the old one is not repurposed

counters keeps its meaning, the pre-spec ladder's position, because that is where a restore starts looking for those notes and reading it as an m/139' position would claim rungs on a ladder nothing has used. The new ladder lives in cashCounters beside it. Both sync across devices, both merge upwards only: a counter that went backwards would hand a second device an index the first has already minted at.

Counter sync payloads are v: 2 and carry both maps. A device that predates this reads the one it knows and ignores the other, and never mints on the new ladder, so the two cannot collide. A v: 1 payload reads as no cash counters, which is the truth rather than a gap. Both directions are covered by tests.

And the thing that is easy to get wrong

The README and llms.txt now say plainly that the counter is half the backup, not an optimisation. A mint must answer a private lookup for a burned note exactly as it answers one for a note it never issued, so a walk cannot see a spent index; and since every rotate burns the index below it, a wallet that has rotated more than the gap would scan as empty without its counter. It is not secret, so it belongs in an ordinary backup.

322 passing, typecheck and build clean. Requires lnurlcash-kit 0.8.0.

The draft specifies a BIP-32 scheme under m/139' and the reference wallet
implements it. This wallet had been using lnurlcash-kit's pre-spec HMAC
scheme, which shipped four days before that section existed. The same twelve
words now restore the same notes in any wallet that implements LUD-25, which
was the entire point of deriving them.

  (d1, d2, d3, d4) = HMAC-SHA256(key = m/139'/0, msg = "<mint host>")[0..16]
  k1[i]            = m/139'/d1/d2/d3/d4/i'

Nothing already minted is lost. restoreFromMint walks both ladders in one
pass and records which one found each note; a record with no scheme is a
pre-spec one, which is exactly what every record written before now is, so no
stored wallet needs rewriting. Those notes keep working and move onto the new
ladder the first time they are rotated.

counters keeps its meaning - the pre-spec ladder's position - because that is
where a restore starts looking for those notes, and reading it as an m/139'
position would claim rungs on a ladder nothing has used. The new ladder lives
in cashCounters beside it. Both sync, both merge upwards only, since a
counter that went backwards would hand a second device an index the first has
already minted at.

Counter sync payloads are v2 and carry both maps. A device that predates this
reads the one it knows and ignores the other, and never mints on the new
ladder, so the two cannot collide. A v1 payload reads as no cash counters,
which is the truth rather than a gap.

Also says plainly in the README and llms.txt that the counter is half the
backup: a mint answers a private lookup for a burned note exactly as for one
it never issued, and every rotate burns the index below, so a wallet that has
rotated more than the gap would scan as empty without it.
@TheCryptoDonkey
TheCryptoDonkey merged commit 81e4372 into main Sep 4, 2026
2 checks passed
@TheCryptoDonkey
TheCryptoDonkey deleted the feat/lud25-cash-derivation branch September 4, 2026 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant