Skip to content

Spec reorg: current NUTs only, everything pre-v3 in legacy/ - #430

Draft
robwoodgate wants to merge 14 commits into
cashubtc:nut-transactionsfrom
robwoodgate:nuts-v3-only
Draft

robwoodgate wants to merge 14 commits into
cashubtc:nut-transactionsfrom
robwoodgate:nuts-v3-only

Conversation

@robwoodgate

@robwoodgate robwoodgate commented Aug 20, 2026 •

Copy link
Copy Markdown
Collaborator

Draft, for a read on the approach rather than a merge.

Stacks on #443, based on its branch so the diff here is the reorg alone. Retargets to main once #443 merges.

What this does

The NUTs have been accumulating generations: three derivations in NUT-13, two locking schemes in NUT-10, and whole NUTs (11, 12, 14) that v3 keysets never touch. This moves everything pre-v3 into legacy/, in NUT order, so each document in the root describes exactly one protocol.

Moved whole NUT-11, NUT-12, NUT-14, with their vectors
Split NUT-00 (secp256k1 BDHKE, V3 tokens), NUT-02 (keyset IDs V1 and V2), NUT-10 (JSON spending conditions), NUT-13 (V1 and V2 derivation), NUT-20 (pre-v3 quote message)
Untouched everything with no pre-v3 half

legacy/README.md maps each document to the specification that supersedes it. Nine commits, sliced per NUT.

The pass is deliberately mechanical: sections move, links follow. Where prose had to be written it is because the split left a statement wrong, and each case is called out in its commit message.

Calls worth arguing about

NUT-10 and NUT-20 are now mandatory. A v3 proof cannot be spent without the transaction transcript and signing rule in NUT-10, and a mint must reject minting an unlocked quote onto a v3 keyset. optional reads wrong once the pre-v3 alternatives are gone. Both move to the mandatory table in the README.

NUT-20 stays a NUT. It still owns the pubkey and signature wire shape and the v3 lock key derivation, and defers the message to NUT-10's transcript. Dissolving it into NUT-04, NUT-10 and NUT-13 is the more aggressive simplification, and is a one-line change from here if that is preferred.

TokenV3 is treated as legacy. Not keyset-versioned, but deprecated, and "V3 token" against "v3 keyset" is a live confusion in a v3-only spec.

Two things the split exposed, already fixed in #443

The TypeScript NUT-13 example built its V3 KDF message with the unframed V2 helper, so it derived different values from the Python beside it. Three orphaned links, in NUT-00, NUT-11 and the NUT-13 vectors.

Not included

The mkdocs nav lives on gh-pages-builder and needs matching entries, or the published site loses the moved pages. Worth knowing it is already stale: it stops at NUT-22 and is missing 23 through 30.

Checks

prettier@3.9.1 --check clean. check-links.py, added in the last commit, verifies every relative link and anchor across the tree and greps for pre-v3 prose left in a current NUT. It is meant to run after each rebase onto #443: a legacy paragraph added upstream to a file this branch does not touch applies with no conflict at all, which is the failure mode worth guarding.

@github-project-automation github-project-automation Bot moved this to Backlog in nuts Aug 20, 2026
@github-project-automation github-project-automation Bot moved this from Backlog to Done in nuts Aug 20, 2026
@robwoodgate robwoodgate reopened this Aug 20, 2026
@robwoodgate
robwoodgate requested a review from callebtc August 21, 2026 07:25
@robwoodgate
robwoodgate force-pushed the taproot-secrets branch 3 times, most recently from 29c64d5 to 499b049 Compare August 21, 2026 13:12
@robwoodgate
robwoodgate force-pushed the nuts-v3-only branch 10 times, most recently from 50581b4 to acec90b Compare August 24, 2026 20:21
@robwoodgate
robwoodgate force-pushed the nuts-v3-only branch 2 times, most recently from 09beeea to 2127f0f Compare August 25, 2026 15:42
@robwoodgate
robwoodgate force-pushed the nuts-v3-only branch 5 times, most recently from 1857655 to 202d4db Compare August 31, 2026 13:42
@robwoodgate
robwoodgate force-pushed the nuts-v3-only branch 2 times, most recently from a332989 to 9e086b5 Compare October 8, 2026 14:32
These three apply to keysets with version byte 00 or 01 only. On v3
keysets their conditions are NUT-10 taproot leaves and their signature
check is the pairing equality, so nothing in them is current.

Files move whole, badged legacy, with their test vectors. Reference
tables across the current NUTs repoint at legacy/.
The legacy protocol section and the V3 token format move out with their
vectors, and a new heading holds the models, protocol and serialization
that were nested under the legacy H1.

Two corrections the split forces. The secret is named P, matching NUT-10,
now that x no longer does double duty across both generations. The Proof
model described a utf-8 random string and omitted witness: it is a 66
character hex point secret, and a witness travels with a spent input.
Only the V3 derivation is current. The unit charset rule applied to both
generations, so it is restated in each file.
The well-known JSON Secret and its mint info setting are pre-v3 only.
The intro loses its two-family table for a sentence, and the NUT is
mandatory: a v3 proof cannot be spent without the transcript and the
signing rule defined here.
Both derivations, the keyset-id integer mapping and the P2PK path move
out with their vectors, and the code examples lose their dispatcher.
The V2 helper goes with them, leaving baseMessageV3 as the only one.
The BIP32 lock path, the Cashu_MintQuoteSig_v1 aggregation and the mint
info setting are pre-v3. What remains is the wire shape, the v3 lock key
derivation, and a pointer to the NUT-10 transcript for the message.

Marked mandatory and pubkey made required: a mint MUST reject minting an
unlocked quote onto a v3 keyset, so neither is optional there.
With NUT-12 in legacy/, four current NUTs still read as though every
proof carries a dleq. Each now names the pairing equality for v3 and
DLEQ for earlier keysets: the V4 token d field, NUT-18 payee validation,
and the NUT-22 AuthProof and mint response. NUT-24 declared uses: NUT-12
without mentioning DLEQ anywhere in its body, so the line goes.
A Legacy section replaces the moved rows, and legacy/README.md maps each
document to the specification that supersedes it. NUT-10 and NUT-20 move
to the mandatory table.
Runs over every markdown file: relative links resolve, anchors exist, and
no current NUT gains pre-v3 prose. The last is the one a rebase needs. A
legacy paragraph added upstream to a file this branch did not touch
applies without a conflict, so nothing else reports it.

The allow list names the files where a pre-v3 mention is correct: a new
entry is a decision, not a silent pass.
The nut10 payment request option requests a pre-v3 well-known JSON
Secret, which v3 keysets have no form of. Its NUT-26 sub-TLV goes with
it; tag 0x08 is marked retired rather than freed, so nothing reallocates
it.

The nutroot section absorbs the general framing and the payee validation
note, both of which applied to either generation.

The Complete Payment Request vector moves whole: it embeds nut10 inside
its encoded creqA, and a replacement belongs to whichever implementation
regenerates it, not to this pass.
The NUT-18/26 split moved the NUT10Option definition into legacy/ but
left NUT-24's field bullet pointing at the live tree.
With quote lock derivation owned by NUT-13 and the v3 quote witness by
NUT-04, nothing v3-only remained at the root. legacy/20.md is now the
complete pre-v3 NUT with the derivation path marked deprecated; the
current-spec pointers go to NUT-04 and NUT-13.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants