Summary
Implement NUT-20 signature verification for mint quotes to prevent output replacement attacks. This ensures the ecash outputs we receive are the ones we requested, protecting against MITM attacks.
Problem
Currently, when we mint tokens:
- We request a quote with blinded outputs
- We receive a quote ID
- We send the outputs with the quote to mint
If an attacker intercepts step 3, they could replace our outputs with their own and steal the minted tokens.
Solution: NUT-20 Signed Mint Quotes
How It Works
- Request quote with pubkey: Include a signing pubkey in mint quote request
- Sign outputs: Before minting, sign the quote ID + all B_ values with our key
- Mint verifies: Mint checks signature before signing outputs
This ensures only the holder of the private key can claim the quote.
Message Format
Mint Quote Request:
- Add pubkey field (33-byte compressed secp256k1)
- Mint echoes it back in response
Mint Request:
- Add signature field (BIP-340 Schnorr)
- Message: quote_id || B_0 || B_1 || ... || B_n
Signature Creation
We already have BIP-340 Schnorr signing via ACINQ secp256k1 (used for P2PK HTLC claims). Same pattern:
- Concatenate: UTF-8(quote_id) + hex(B_0) + hex(B_1) + ...
- SHA-256 hash the concatenation
- Sign with BIP-340 Schnorr
Implementation Plan
Phase 1: Quote Request Enhancement
Modify CashuBackend.getMintQuote():
- Generate ephemeral keypair for quote
- Include pubkey in request
- Store keypair with quote for later signing
Phase 2: Mint Request Signing
Modify CashuBackend mint operations:
- Retrieve keypair for quote
- Build message: quote || B_0 || ... || B_n
- Sign with BIP-340 Schnorr
- Include signature in mint request
Phase 3: Storage
Add to WalletStorage:
- Store quote keypairs (encrypted)
- Clean up after successful mint
- Handle quote expiry
Files to Modify
| File |
Changes |
| CashuBackend.kt |
Add pubkey to quote request, sign mint request |
| WalletStorage.kt |
Quote keypair storage |
| WalletKeyManager.kt |
Ephemeral keypair generation (or reuse existing) |
Code Pattern
Quote Request:
val ephemeralKey = generateEphemeralKey()
val request = JSONObject().apply {
put("amount", amountSats)
put("unit", "sat")
put("pubkey", ephemeralKey.publicKeyHex) // NEW
}
// Store keypair for later
walletStorage.saveQuoteKey(quoteId, ephemeralKey)
Mint Request:
val keypair = walletStorage.getQuoteKey(quoteId)
val message = buildSignatureMessage(quoteId, outputs)
val signature = keypair?.signSchnorr(sha256(message))
val request = JSONObject().apply {
put("quote", quoteId)
put("outputs", outputsArray)
put("signature", signature) // NEW
}
Privacy Consideration
Per NUT-20: Generate a unique keypair for each quote to prevent linkability.
Discovery
Check NUT-06 /v1/info for support:
- If supported, use signed quotes
- If not supported, fall back to unsigned (but log warning)
Benefits
- MITM protection: Attackers cannot steal minted tokens
- Output integrity: Guaranteed to receive requested denominations
- Accountability: Mint cannot claim output mismatch
References
Acceptance Criteria
Summary
Implement NUT-20 signature verification for mint quotes to prevent output replacement attacks. This ensures the ecash outputs we receive are the ones we requested, protecting against MITM attacks.
Problem
Currently, when we mint tokens:
If an attacker intercepts step 3, they could replace our outputs with their own and steal the minted tokens.
Solution: NUT-20 Signed Mint Quotes
How It Works
This ensures only the holder of the private key can claim the quote.
Message Format
Mint Quote Request:
Mint Request:
Signature Creation
We already have BIP-340 Schnorr signing via ACINQ secp256k1 (used for P2PK HTLC claims). Same pattern:
Implementation Plan
Phase 1: Quote Request Enhancement
Modify CashuBackend.getMintQuote():
Phase 2: Mint Request Signing
Modify CashuBackend mint operations:
Phase 3: Storage
Add to WalletStorage:
Files to Modify
Code Pattern
Quote Request:
Mint Request:
Privacy Consideration
Per NUT-20: Generate a unique keypair for each quote to prevent linkability.
Discovery
Check NUT-06 /v1/info for support:
Benefits
References
Acceptance Criteria