NUT-29: Fix batch minting signature message separator#375
Conversation
There was a problem hiding this comment.
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?
|
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 |
This comment was marked as outdated.
This comment was marked as outdated.
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).
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).
…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.
1225c9b to
91abdbb
Compare
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.
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.
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)
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.
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.
…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).
|
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. |
|
@a1denvalu3 - can you add an implementation supported section to your desc so we can track support pls.
|
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>
There was a problem hiding this comment.
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
Summary
Update
msg_to_signfor NUT-29 batch minting to include proper colon separators between the quote ID and the blinded messages (e.g.quote_id:B_0:B_1).Clarify that the outputs are UTF-8 encoded hex strings.
Update test vectors in
tests/29-tests.mdto reflect the new msg_to_sign and signature validation rule.Cashu-TS - supported in v4 and upcoming v5
CDK - NUT-29: Batch minting signature cdk#2149
Nutshell - feat(crypto): align NUT-20/29 signatures with spec change nutshell#1054