Skip to content

NUT-29: Fix batch minting signature message separator#375

Open
a1denvalu3 wants to merge 8 commits into
cashubtc:mainfrom
a1denvalu3:fix-nut-29-batch-minting
Open

NUT-29: Fix batch minting signature message separator#375
a1denvalu3 wants to merge 8 commits into
cashubtc:mainfrom
a1denvalu3:fix-nut-29-batch-minting

Conversation

@a1denvalu3

@a1denvalu3 a1denvalu3 commented May 22, 2026

Copy link
Copy Markdown
Contributor

Summary

@robwoodgate robwoodgate left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Length as a separator may be better as it precludes use of the separator and assures injectivity - eg:

msg = b"Cashu_MintQuoteSig_v1"                       // DST
      ‖ len32(quote_id) ‖ quote_id                   // quote_id = UTF-8 bytes
      ‖ for each output i (in request order):
            len32(amount_i) ‖ amount_i               // amount_i = canonical minimal big-endian
          ‖ len32(B_i)      ‖ B_i                    // B_i = 33-byte secp / 48-byte BLS compressed point

might be safer?

@robwoodgate

robwoodgate commented May 22, 2026

Copy link
Copy Markdown
Collaborator

As an addition, we should do the same for NUT-20 message aggregation due to the variable B_ lengths between SECP and BLS, and to ensure output amount is bound into the signature.

@robwoodgate

This comment was marked as outdated.

robwoodgate added a commit to cashubtc/cashu-ts that referenced this pull request May 26, 2026
Revert NUT-20 to its retro-compatible message; NUT-29 batch minting now uses its
own domain-separated, length-framed message committing to each output's amount and
point (cashubtc/nuts#375).
robwoodgate added a commit to cashubtc/cashu-ts that referenced this pull request May 26, 2026
Revert NUT-20 to its retro-compatible message; NUT-29 batch minting now uses its
own domain-separated, length-framed message committing to each output's amount and
point (cashubtc/nuts#375).
robwoodgate and others added 2 commits June 7, 2026 22:34
…7 quote ids

Replace the legacy `quote || B_0 || ... || B_(n-1)` mint-quote signature message
with a domain-separated, length-framed, amount-committing `msg_to_sign`. This is a
breaking change: mints no longer accept the legacy message. The message does not
commit the keyset `id`, so a wallet can re-target a rotated keyset without a new
signature.

- NUT-04: quote ids MUST be a UUIDv7.
- NUT-20: define the hardened `msg_to_sign` (replaces the concatenation).
- NUT-29: each locked quote is signed independently per NUT-20 over the
  consolidated outputs; mixed-method batches are rejected.
- tests/20-test.md: hardened-message test vector.
@a1denvalu3 a1denvalu3 force-pushed the fix-nut-29-batch-minting branch from 1225c9b to 91abdbb Compare June 7, 2026 21:53
robwoodgate added a commit to cashubtc/cashu-ts that referenced this pull request Jun 12, 2026
Quotes are not versioned, so prepareBatchMint picks the signature format from
the mint's advertised implementation/version: Nutshell and cdk-mintd below the
first release that verifies the amended message (cashubtc/nuts#375) get the
legacy NUT-20-style message; everything else gets the amended format. Threshold
versions are placeholders until the upstream releases are known.

Adds MintInfo.isImplementationBelow() to parse and compare the NUT-06 version
string.
robwoodgate added a commit to cashubtc/cashu-ts that referenced this pull request Jun 12, 2026
Quotes are not versioned, so prepareBatchMint picks the signature format from
the mint's advertised implementation/version: Nutshell and cdk-mintd below the
first release that verifies the amended message (cashubtc/nuts#375) get the
legacy NUT-20-style message; everything else gets the amended format. Threshold
versions are placeholders until the upstream releases are known.

Adds MintInfo.isImplementationBelow() to parse and compare the NUT-06 version
string.
robwoodgate added a commit to cashubtc/cashu-ts that referenced this pull request Jun 12, 2026
The current head of cashubtc/nuts#375 hardens the NUT-20 message itself, so the
single-quote path now picks its signature format the same way the batch path
does. The version threshold table and gate move to wallet/mintCompat.ts as
requiresLegacyQuoteSignature(), one place to collect (and later delete)
version-gated shims. NUT20/NUT29 modules document the legacy/amended split.

(cherry picked from commit f184c98c96a1f447457edf1b11b7e402b377f976)
robwoodgate added a commit to cashubtc/cashu-ts that referenced this pull request Jun 12, 2026
The current head of cashubtc/nuts#375 hardens the NUT-20 message itself, so the
single-quote path now picks its signature format the same way the batch path
does. The version threshold table and gate move to wallet/mintCompat.ts as
requiresLegacyQuoteSignature(), one place to collect (and later delete)
version-gated shims. NUT20/NUT29 modules document the legacy/amended split.
robwoodgate added a commit to cashubtc/cashu-ts that referenced this pull request Jun 12, 2026
cashubtc/nuts#375 defines one mint-quote message for NUT-20 single and NUT-29
batch minting, so the NUT29 module dissolves into NUT20.ts: signMintQuote /
verifyMintQuoteSignature now produce the amended format, and the pre-amendment
concatenation moves to signMintQuoteLegacy / verifyMintQuoteSignatureLegacy.
The transition cleanup is then pure deletion of the Legacy-suffixed pair.
robwoodgate added a commit to cashubtc/cashu-ts that referenced this pull request Jun 12, 2026
…ir internal

cashubtc/nuts#375 defines one mint-quote message for NUT-20 single and NUT-29
batch minting, so the NUT29 module dissolves into NUT20.ts. On v4 the released
signMintQuote/verifyMintQuoteSignature keep their legacy bytes and the amended
pair (signMintQuoteAmended/verifyMintQuoteSignatureAmended) stays out of the
public barrel; v5 exports the amended pair as signMintQuote directly. Schnorr
plumbing now reuses core.ts (new schnorrVerifyDigest counterpart).
@robwoodgate

robwoodgate commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Cashu-TS now supports both legacy and new NUT-20 MTS using a try and fallback approach.

So when this merges, wallets will continue to work with both new and older mints in both v4 and (upcoming) v5 CTS.

See: cashubtc/cashu-ts#675 cashubtc/cashu-ts#674

Assuming mints also fallback to checking signatures against legacy MTS for a time, this NUT will not be a breaking change at all.

@robwoodgate

robwoodgate commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

@a1denvalu3 - can you add an implementation supported section to your desc so we can track support pls.

Comment thread 20.md Outdated
Comment thread 20.md
Comment thread 20.md Outdated
Comment thread 20.md Outdated
Comment thread 20.md Outdated
Comment thread 29.md Outdated
Comment thread 29.md
a1denvalu3 and others added 5 commits June 18, 2026 11:01
Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
@a1denvalu3 a1denvalu3 requested a review from callebtc June 18, 2026 09:18

@robwoodgate robwoodgate left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK @ a5a7c70

The only nit is the message_to_sign blocks are quite wide, and have scroll bars. As the items are described under the formula in both NUT 20/29, we could probably remove or truncate the inline formula comments:

eg:

msg_to_sign = "Cashu_MintQuoteSig_v1"
    || len32(quote) || quote
    || for each output i (in request order):
        len32(amount_i) || amount_i
        || len32(B_i) || B_i

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

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

3 participants