feat: add Cashu ecash protocol and cashu-ts library skills

Add comprehensive skill documentation for:
- Cashu protocol: BDHKE cryptography, NUT specifications (00-27),
  token lifecycle, spending conditions (P2PK, HTLC), wallet recovery
- cashu-ts: Wallet class API, WalletOps builder pattern, minting,
  melting, sending/receiving tokens, deterministic secrets
This commit is contained in:
Claude
2026-01-23 22:36:56 +00:00
parent e008d76021
commit f60964d210
2 changed files with 1211 additions and 0 deletions

View File

@@ -0,0 +1,641 @@
---
name: cashu-ts
description: This skill should be used when building Cashu wallets in JavaScript/TypeScript using the @cashu/cashu-ts library. Provides comprehensive knowledge of the Wallet and Mint classes, WalletOps builder pattern, token encoding/decoding, P2PK conditions, deterministic secrets, and all wallet operations like minting, melting, sending, and receiving tokens.
---
# cashu-ts TypeScript Library Expert
## Purpose
This skill provides expert-level assistance with `@cashu/cashu-ts`, the official TypeScript library for building Cashu ecash wallets. The library implements the Cashu protocol for minting, sending, receiving, and melting ecash tokens backed by Bitcoin Lightning.
## When to Use
Activate this skill when:
- Building a Cashu wallet in JavaScript/TypeScript
- Implementing ecash token operations (mint, melt, send, receive)
- Working with the WalletOps builder pattern
- Encoding/decoding Cashu tokens
- Implementing P2PK or HTLC spending conditions
- Managing deterministic secrets with BIP39 seeds
- Handling keyset management and rotation
- Integrating Cashu payments into web or Node.js apps
## Installation
```bash
npm install @cashu/cashu-ts
```
**Browser (IIFE build):** Available via GitHub Releases for non-bundler usage.
## Core Architecture
### Key Principles
1. **Stateless Wallets**: Wallet classes are mostly stateless - your app manages proof storage
2. **Always call `loadMint()`**: Required after instantiation before any operations
3. **Proof Management**: You must persist proofs in your database
4. **Counter Tracking**: For deterministic secrets, persist counter values
### Main Classes
| Class | Purpose |
|-------|---------|
| `Wallet` | Primary wallet operations (v3 API) |
| `CashuWallet` | Legacy wallet class (v2 API) |
| `CashuMint` | Direct mint API interactions |
| `WalletOps` | Fluent builder for transactions |
## Quick Start
### Basic Wallet Setup
```typescript
import { Wallet } from '@cashu/cashu-ts';
const mintUrl = 'https://mint.example.com';
const wallet = new Wallet(mintUrl);
await wallet.loadMint();
// Now ready for operations
```
### With Cached Data (Faster Initialization)
```typescript
// First time - save cache
const wallet1 = new Wallet(mintUrl);
await wallet1.loadMint();
const keyChainCache = wallet1.keyChain.cache;
const mintInfoCache = wallet1.getMintInfo().cache;
// Persist these to storage...
// Later - restore from cache
const wallet2 = new Wallet(mintUrl, { unit: 'sat' });
wallet2.loadMintFromCache(mintInfoCache, keyChainCache);
```
### With Deterministic Secrets (BIP39)
```typescript
import { Wallet } from '@cashu/cashu-ts';
import { mnemonicToSeedSync } from '@scure/bip39';
const mnemonic = 'abandon abandon abandon...'; // 12 words
const bip39seed = mnemonicToSeedSync(mnemonic);
const wallet = new Wallet(mintUrl, {
unit: 'sat',
bip39seed,
keysetId: 'preferred_keyset_id', // optional
counterInit: { '009a1f293253e41e': 0 } // keyset -> counter
});
await wallet.loadMint();
```
## Wallet Operations
### Minting Tokens (Deposit Bitcoin)
```typescript
import { Wallet, MintQuoteState } from '@cashu/cashu-ts';
const wallet = new Wallet(mintUrl);
await wallet.loadMint();
// 1. Create mint quote (get Lightning invoice)
const quote = await wallet.createMintQuote(1000); // 1000 sats
console.log('Pay this invoice:', quote.request);
console.log('Quote ID:', quote.quote);
// 2. Wait for payment, check status
const status = await wallet.checkMintQuote(quote.quote);
if (status.state === MintQuoteState.PAID) {
// 3. Mint the tokens
const proofs = await wallet.mintProofs(1000, quote.quote);
// Store proofs in your database
console.log('Minted proofs:', proofs);
}
```
### Melting Tokens (Pay Lightning Invoice)
```typescript
const invoice = 'lnbc10u1p...'; // Lightning invoice to pay
// 1. Create melt quote
const quote = await wallet.createMeltQuote(invoice);
console.log('Amount:', quote.amount);
console.log('Fee reserve:', quote.fee_reserve);
// 2. Select proofs to spend
const totalNeeded = quote.amount + quote.fee_reserve;
const proofsToSpend = selectProofs(myProofs, totalNeeded);
// 3. Melt (pay the invoice)
const result = await wallet.meltProofs(quote, proofsToSpend);
if (result.quote.state === 'PAID') {
console.log('Payment successful!');
console.log('Preimage:', result.quote.payment_preimage);
// Handle fee change if any
if (result.change) {
// Store change proofs
}
}
```
### Sending Tokens
```typescript
// Simple send - get token string for recipient
const { keep, send } = await wallet.send(100, myProofs);
// keep = change proofs to store
// send = proofs to encode and share
// Encode for sharing
import { getEncodedTokenV4 } from '@cashu/cashu-ts';
const token = getEncodedTokenV4({
mint: mintUrl,
proofs: send,
memo: 'Coffee payment'
});
// Share token string: cashuBxxx...
```
### Receiving Tokens
```typescript
import { getDecodedToken } from '@cashu/cashu-ts';
const tokenString = 'cashuBxxx...';
// 1. Decode and validate
const decoded = getDecodedToken(tokenString);
console.log('From mint:', decoded.mint);
console.log('Amount:', decoded.proofs.reduce((a, p) => a + p.amount, 0));
// 2. Create wallet for that mint
const wallet = new Wallet(decoded.mint);
await wallet.loadMint();
// 3. Receive (swap to invalidate sender's copy)
const newProofs = await wallet.receive(tokenString);
// Store newProofs in your database
```
### Swapping Tokens
```typescript
// Swap to change denominations or refresh tokens
const { keep } = await wallet.swap(myProofs);
// keep contains new proofs with same total value
```
### Checking Token States
```typescript
const states = await wallet.checkProofsStates(proofs);
states.forEach((state, i) => {
console.log(`Proof ${i}: ${state.state}`); // UNSPENT, PENDING, SPENT
if (state.witness) {
console.log('Witness:', state.witness);
}
});
```
## WalletOps Builder Pattern
The fluent `WalletOps` API provides readable, chainable transaction building.
### Access WalletOps
```typescript
// From wallet instance
const ops = wallet.ops;
// Or standalone
import { WalletOps } from '@cashu/cashu-ts';
const ops = new WalletOps(wallet);
```
### Send Operations
```typescript
// Simple send
const { keep, send } = await wallet.ops.send(100, proofs).run();
// With deterministic outputs
const { keep, send } = await wallet.ops
.send(100, proofs)
.asDeterministic(0, [64, 32, 4]) // counter=0 auto-reserves
.run();
// Keep as random (change proofs)
const { keep, send } = await wallet.ops
.send(100, proofs)
.asDeterministic(0, [64, 32, 4])
.keepAsRandom()
.run();
// Offline exact match (no mint contact)
try {
const result = await wallet.ops
.send(100, proofs)
.offlineExactOnly()
.run();
} catch (e) {
// Falls back if exact match impossible
}
```
### Receive Operations
```typescript
// Simple receive
const proofs = await wallet.ops.receive(token).run();
// With P2PK unlock
const proofs = await wallet.ops
.receive(token)
.privkey(['privkey_hex'])
.run();
// Lock received proofs to your pubkey
const proofs = await wallet.ops
.receive(token)
.asP2PK({ pubkey: myPubkey })
.run();
```
### Mint Operations
```typescript
const proofs = await wallet.ops
.mint(1000, quoteId)
.asDeterministic(0) // auto-reserve counters
.run();
```
### Melt Operations
```typescript
const result = await wallet.ops
.melt(quote, proofs)
.run();
```
## P2PK (Pay-to-Pubkey)
Lock tokens to a public key requiring signature to spend.
### Using P2PKBuilder
```typescript
import { P2PKBuilder } from '@cashu/cashu-ts';
const p2pkOptions = new P2PKBuilder()
.addLockPubkey('02abc123...')
.lockUntil(1712345678) // Unix timestamp
.addRefundPubkey('02def456...')
.setMinSigs(2)
.toOptions();
// Send with P2PK lock
const { send } = await wallet.ops
.send(100, proofs)
.asP2PK(p2pkOptions)
.run();
```
### Receiving P2PK Tokens
```typescript
// Unlock with your private key
const proofs = await wallet.ops
.receive(token)
.privkey(['your_private_key_hex'])
.run();
// Or with wallet.receive()
const proofs = await wallet.receive(token, {
privkey: 'your_private_key_hex'
});
```
## Token Encoding/Decoding
### Encode Tokens
```typescript
import { getEncodedTokenV4 } from '@cashu/cashu-ts';
const token = getEncodedTokenV4({
mint: 'https://mint.example.com',
proofs: proofs,
memo: 'Payment for coffee',
unit: 'sat'
});
// Returns: cashuBxxx...
```
### Decode Tokens
```typescript
import { getDecodedToken } from '@cashu/cashu-ts';
try {
const decoded = getDecodedToken(tokenString);
console.log('Mint:', decoded.mint);
console.log('Unit:', decoded.unit);
console.log('Memo:', decoded.memo);
console.log('Proofs:', decoded.proofs);
console.log('Total:', decoded.proofs.reduce((a, p) => a + p.amount, 0));
} catch (e) {
console.error('Invalid token');
}
```
### Token Types
```typescript
// V4 Token (current) - CBOR encoded
interface Token {
mint: string;
proofs: Proof[];
memo?: string;
unit?: string;
}
// Proof structure
interface Proof {
amount: number;
id: string; // keyset ID
secret: string;
C: string; // signature point
witness?: string; // for spending conditions
}
```
## Deterministic Secrets & Recovery
### Counter Management
```typescript
// Get current counter state
const snapshot = wallet.counters.snapshot();
// { '009a1f293253e41e': 42, ... }
// Set counter (for migrations)
wallet.counters.setNext('009a1f293253e41e', 100);
// Subscribe to counter reservations
wallet.on.countersReserved(({ keysetId, start, count, next }) => {
// Persist 'next' value to your database
saveCounter(keysetId, next);
});
```
### Wallet Recovery
```typescript
// Restore wallet from seed
const wallet = new Wallet(mintUrl, {
bip39seed: seedFromMnemonic,
counterInit: loadedCounters
});
await wallet.loadMint();
// Restore proofs using NUT-09
const restoredProofs = await wallet.restore(startCounter, endCounter);
```
## Keyset Management
### Get Keysets
```typescript
// Get all keysets from mint
const keysets = await wallet.getKeysets();
// Get specific keyset keys
const keys = await wallet.getKeys('009a1f293253e41e');
// Force refresh from mint
const freshKeys = await wallet.getKeys(keysetId, true);
```
### Keyset Structure
```typescript
interface MintKeyset {
id: string; // keyset identifier
unit: string; // 'sat', 'usd', etc.
active: boolean; // mint signs with this keyset
input_fee_ppk?: number; // fee in parts per thousand
}
interface Keys {
id: string;
unit: string;
keys: { [amount: number]: string }; // amount -> pubkey
}
```
## Mint Information
```typescript
const info = wallet.getMintInfo();
console.log('Name:', info.name);
console.log('Version:', info.version);
console.log('Supported NUTs:', Object.keys(info.nuts));
console.log('Contact:', info.contact);
// Check feature support
if (info.nuts['10']?.supported) {
console.log('Spending conditions supported');
}
if (info.nuts['12']?.supported) {
console.log('DLEQ proofs supported');
}
```
## CashuMint Class (Direct API)
For low-level mint interactions:
```typescript
import { CashuMint } from '@cashu/cashu-ts';
const mint = new CashuMint(mintUrl);
// Get mint info
const info = await mint.getInfo();
// Get active keysets
const keysets = await mint.getKeySets();
// Get keys for keyset
const keys = await mint.getKeys(keysetId);
// Swap proofs
const response = await mint.swap(inputs, outputs);
// Check proof states
const states = await mint.check({ Ys: proofYs });
```
## Events & Logging
### Wallet Events
```typescript
// Subscribe to counter reservations
wallet.on.countersReserved(({ keysetId, start, count, next }) => {
console.log(`Reserved ${count} counters for ${keysetId}`);
});
// Unsubscribe
const unsub = wallet.on.countersReserved(handler);
unsub(); // cleanup
```
### Logging
```typescript
import { Wallet, ConsoleLogger } from '@cashu/cashu-ts';
// Enable console logging
const wallet = new Wallet(mintUrl, {
logger: new ConsoleLogger()
});
// Custom logger
const wallet = new Wallet(mintUrl, {
logger: {
debug: (msg) => myLogger.debug(msg),
info: (msg) => myLogger.info(msg),
warn: (msg) => myLogger.warn(msg),
error: (msg) => myLogger.error(msg)
}
});
```
## Error Handling
```typescript
import { CashuError } from '@cashu/cashu-ts';
try {
const proofs = await wallet.receive(token);
} catch (e) {
if (e instanceof CashuError) {
console.error('Cashu error:', e.code, e.message);
// Handle specific error codes
switch (e.code) {
case 10000: // Token already spent
console.error('Token was already redeemed');
break;
case 11001: // Quote not found
console.error('Quote expired or invalid');
break;
}
} else {
throw e;
}
}
```
## Type Exports
```typescript
import type {
// Core types
Proof,
Token,
Keys,
MintKeyset,
// Quote types
MintQuoteResponse,
MeltQuoteResponse,
MintQuoteState,
MeltQuoteState,
// Response types
SwapResponse,
MintResponse,
MeltResponse,
CheckStateResponse,
ProofState,
// Blinding types
BlindedMessage,
BlindSignature,
BlindingData,
// Options
P2PKOptions,
OutputAmounts,
// Wallet types
SendResponse,
ReceiveResponse
} from '@cashu/cashu-ts';
```
## Migration Notes
### v2 to v3
- `CashuWallet``Wallet` (new class)
- Improved WalletOps builder API
- Better TypeScript types
- HTLC support (NUT-14)
- Transaction preview functionality
### v1 to v2
- `mintTokens()``mintProofs()` (returns `Proof[]` directly)
- `meltTokens()``meltProofs()`
- `checkProofsSpent()``checkProofsStates()`
- `returnChange``keep` in SendResponse
- BIP39 must be converted to seed externally
- `OutputAmounts` object replaces `AmountPreference` array
## Best Practices
1. **Always swap received tokens** - Invalidates sender's copy immediately
2. **Persist proofs securely** - They are bearer instruments
3. **Track counters** - Essential for deterministic wallet recovery
4. **Check mint support** - Verify NUT support before using features
5. **Handle errors gracefully** - Network and mint errors are common
6. **Use DLEQ verification** - When receiving tokens offline
7. **Implement proper backup** - Seed + counters for full recovery
## Official Resources
- **GitHub**: https://github.com/cashubtc/cashu-ts
- **Documentation**: https://cashubtc.github.io/cashu-ts/docs/
- **npm**: https://www.npmjs.com/package/@cashu/cashu-ts
- **Releases**: https://github.com/cashubtc/cashu-ts/releases
## Quick Reference
| Operation | Method |
|-----------|--------|
| Initialize | `new Wallet(url)` + `loadMint()` |
| Mint quote | `createMintQuote(amount)` |
| Check quote | `checkMintQuote(quoteId)` |
| Mint tokens | `mintProofs(amount, quoteId)` |
| Melt quote | `createMeltQuote(invoice)` |
| Melt tokens | `meltProofs(quote, proofs)` |
| Send | `send(amount, proofs)` or `ops.send().run()` |
| Receive | `receive(token)` or `ops.receive().run()` |
| Swap | `swap(proofs)` |
| Check state | `checkProofsStates(proofs)` |
| Encode | `getEncodedTokenV4({...})` |
| Decode | `getDecodedToken(token)` |

View File

@@ -0,0 +1,570 @@
---
name: cashu
description: This skill should be used when working with the Cashu ecash protocol, implementing Cashu wallets or mints, handling ecash tokens, or discussing Cashu NUTs (Notation, Usage, and Terminology specifications). Provides comprehensive knowledge of Cashu's Chaumian ecash system, blind signatures, token lifecycle, and all standard NUTs.
---
# Cashu Ecash Protocol Expert
## Purpose
This skill provides expert-level assistance with the Cashu protocol, an open-source Chaumian ecash system built for Bitcoin. Cashu enables instant, private digital cash payments using blind signatures, where tokens are bearer instruments stored on users' devices.
## When to Use
Activate this skill when:
- Implementing Cashu wallets or mints
- Working with ecash tokens (minting, melting, swapping)
- Handling blind signatures and BDHKE cryptography
- Implementing any Cashu NUT specification
- Building payment systems with ecash
- Integrating Cashu with Lightning Network
- Working with spending conditions (P2PK, HTLCs)
- Implementing wallet backup/recovery
- Discussing privacy-preserving payment systems
## Core Concepts
### The Protocol Foundation
Cashu operates on two main components:
1. **Wallets** - Client applications that hold and transact ecash tokens
2. **Mints** - Servers that issue and redeem tokens, backed by Bitcoin/Lightning
Key principles:
- Tokens are bearer instruments (like physical cash)
- Blind signatures provide privacy (mint can't link issuance to redemption)
- Single-use tokens prevent double-spending
- Backed by Bitcoin via Lightning Network
- No account registration required
### Blind Diffie-Hellmann Key Exchange (BDHKE)
The cryptographic foundation of Cashu on secp256k1:
**Participants:**
- **Bob (Mint)**: Private key `k`, public key `K = kG`
- **Alice (User)**: Creates and holds tokens
- **Carol (Recipient)**: Receives tokens from Alice
**Token Creation Flow:**
1. Alice generates secret `x`, computes `Y = hash_to_curve(x)`
2. Alice picks random blinding factor `r`, sends `B_ = Y + rG` to mint
3. Mint returns blind signature `C_ = kB_`
4. Alice unblinds: `C = C_ - rK = kY` (this is the proof)
5. Token is the pair `(x, C)`
**Redemption:**
- Mint verifies `k * hash_to_curve(x) == C`
- Mint marks secret `x` as spent
### Hash-to-Curve Function
```
Y = PublicKey('02' || SHA256(msg_hash || counter))
msg_hash = SHA256(DOMAIN_SEPARATOR || x)
DOMAIN_SEPARATOR = b"Secp256k1_HashToCurve_Cashu_"
counter: uint32 little-endian, incremented until valid point
```
## Data Models
### BlindedMessage (sent to mint for signing)
```json
{
"amount": 8,
"id": "009a1f293253e41e",
"B_": "02abc123..."
}
```
### BlindSignature (returned by mint)
```json
{
"amount": 8,
"id": "009a1f293253e41e",
"C_": "03def456..."
}
```
### Proof (spendable token)
```json
{
"amount": 8,
"id": "009a1f293253e41e",
"secret": "random_secret_string",
"C": "02789abc..."
}
```
### Token Serialization
**V4 Format (current):** `cashuB[base64_urlsafe_cbor]`
```
{
"m": "https://mint.example.com",
"u": "sat",
"d": "optional memo",
"t": [{"i": "keyset_id", "p": [proofs...]}]
}
```
**Binary Format:** `craw` + version + CBOR payload
**V3 Format (deprecated):** `cashuA[base64_urlsafe_json]`
## NUT Specifications Reference
### Mandatory NUTs (Core Protocol)
| NUT | Title | Description |
|-----|-------|-------------|
| **00** | Cryptography and Models | BDHKE, hash-to-curve, data models, serialization |
| **01** | Mint Public Keys | `GET /v1/keys`, keyset exchange |
| **02** | Keysets and Fees | Keyset IDs, fee calculation (ppk) |
| **03** | Swapping Tokens | `POST /v1/swap` - denomination changes |
| **04** | Minting Tokens | Quote + mint flow via Lightning |
| **05** | Melting Tokens | Redeem tokens for Lightning payments |
| **06** | Mint Info | `GET /v1/info` - capabilities, supported NUTs |
### Optional NUTs
| NUT | Title | Description |
|-----|-------|-------------|
| **07** | Token State Check | UNSPENT, PENDING, SPENT states |
| **08** | Overpaid Lightning Fees | Blank outputs for fee change |
| **09** | Signature Restore | Recover blind signatures |
| **10** | Spending Conditions | Well-known secret format |
| **11** | Pay-To-Pubkey (P2PK) | Lock to public key |
| **12** | DLEQ Proofs | Prove mint key consistency |
| **13** | Deterministic Secrets | BIP39 wallet recovery |
| **14** | HTLCs | Hash Time-Locked Contracts |
| **15** | Multi-Path Payments | Pay from multiple mints |
| **16** | Animated QR Codes | UR protocol for large tokens |
| **17** | WebSocket Subscriptions | Real-time notifications |
| **18** | Payment Requests | Receiver-initiated payments |
| **19** | Cached Responses | Idempotent operations |
| **20** | Signature on Mint Quote | Front-running prevention |
| **21** | Clear Authentication | OAuth 2.0/OIDC |
| **22** | Blind Authentication | Privacy-preserving auth |
| **23** | Payment Method: BOLT11 | Lightning invoices |
| **24** | HTTP 402 Payment Required | Inline ecash payments |
| **25** | Payment Method: BOLT12 | Lightning offers |
| **26** | Payment Request Bech32m | Compact encoding |
| **27** | Nostr Mint Backup | Encrypted backup to Nostr |
## API Endpoints
### Key Management
```
GET /v1/keys # Active keysets
GET /v1/keys/{keyset_id} # Specific keyset
GET /v1/keysets # All keyset IDs
```
### Minting (Deposit Bitcoin → Get Tokens)
```
POST /v1/mint/quote/{method} # Request quote (get invoice)
GET /v1/mint/quote/{method}/{quote_id} # Check quote status
POST /v1/mint/{method} # Execute mint (get tokens)
```
**Flow:**
1. Request quote → receive Lightning invoice
2. Pay invoice
3. Submit blinded messages → receive blind signatures
4. Unblind to get proofs
### Melting (Spend Tokens → Pay Lightning)
```
POST /v1/melt/quote/{method} # Request quote
GET /v1/melt/quote/{method}/{quote_id} # Check status
POST /v1/melt/{method} # Execute melt
```
**Flow:**
1. Request quote with Lightning invoice → get amount + fee_reserve
2. Submit proofs (amount + fee_reserve) → mint pays invoice
3. Receive change for overpaid fees
### Swapping
```
POST /v1/swap
{
"inputs": [proofs...],
"outputs": [blinded_messages...]
}
→ {"signatures": [blind_signatures...]}
```
### Token State
```
POST /v1/checkstate
{"Ys": ["hash_to_curve(secret)..."]}
→ {"states": [{"Y": "...", "state": "UNSPENT|PENDING|SPENT"}]}
```
### Restore (Wallet Recovery)
```
POST /v1/restore
{"outputs": [blinded_messages...]}
→ {"outputs": [...], "signatures": [...]}
```
### Mint Info
```
GET /v1/info
→ {
"name": "My Mint",
"version": "Nutshell/0.15.0",
"nuts": {"4": {...}, "5": {...}, ...}
}
```
## Spending Conditions
### Well-Known Secret Format (NUT-10)
```json
["<kind>", {
"nonce": "<unique_random_string>",
"data": "<condition_data>",
"tags": [["key", "value1", "value2"]]
}]
```
### P2PK (NUT-11)
Lock tokens to a public key:
```json
["P2PK", {
"nonce": "abc123",
"data": "02pubkey_hex",
"tags": [
["sigflag", "SIG_INPUTS"],
["n_sigs", "2"],
["pubkeys", "02key2", "02key3"],
["locktime", "1700000000"],
["refund", "02refund_key"]
]
}]
```
**Witness:**
```json
{"signatures": ["schnorr_sig_hex"]}
```
### HTLC (NUT-14)
Hash Time-Locked Contracts:
```json
["HTLC", {
"nonce": "xyz789",
"data": "sha256_hash_of_preimage",
"tags": [
["pubkeys", "02receiver_key"],
["locktime", "1700000000"],
["refund", "02sender_key"]
]
}]
```
**Witness:**
```json
{
"preimage": "hex_preimage",
"signatures": ["schnorr_sig_hex"]
}
```
## Fee Calculation
Fees are expressed in parts-per-thousand (ppk) per input:
```
individual_fee = input_fee_ppk // per input
total_fee = ceil(sum(individual_fees) / 1000)
// Balance equation:
sum(inputs) - total_fee = sum(outputs)
```
## DLEQ Proofs (NUT-12)
Prove mint used same private key without revealing it:
**Mint generates:**
- Random `r`, compute `R1 = rG`, `R2 = rB_`
- Challenge `e = SHA256(R1 || R2 || A || C_)`
- Response `s = r + e*a`
**User verifies:**
- `R1 = sG - eA`
- `R2 = sB_ - eC_`
- `e == SHA256(R1 || R2 || A || C_)`
## Wallet Recovery (NUT-13)
### Derivation (Keyset v01)
```
secret = HMAC-SHA256(
"Cashu_KDF_HMAC_SHA256" || keyset_id || counter || 0x00
) mod n
r = HMAC-SHA256(
"Cashu_KDF_HMAC_SHA256" || keyset_id || counter || 0x01
) mod n
```
### Recovery Flow
1. Derive secrets from BIP39 mnemonic
2. Generate BlindedMessages
3. Call `POST /v1/restore`
4. Unblind returned signatures
5. Check states via `POST /v1/checkstate`
6. Continue in batches of 100 until 3 empty batches
## Authentication
### Clear Auth (NUT-21)
- OAuth 2.0/OIDC integration
- JWT in `Clear-auth` header
- Identifies user to mint
### Blind Auth (NUT-22)
- Privacy-preserving tokens
- Unit: `auth`, single denomination
- Token in `Blind-auth` header
- Single-use, user anonymous within group
## Payment Requests (NUT-18)
**Format:** `creqA[base64_cbor]` or `creqb1...` (bech32m)
```json
{
"i": "payment_id",
"a": 1000,
"u": "sat",
"m": ["https://mint1.com", "https://mint2.com"],
"d": "Coffee payment",
"t": [{"t": "nostr", "a": "nprofile1..."}],
"nut10": {"k": "P2PK", "d": "02pubkey"}
}
```
**Transport methods:**
- `nostr` - NIP-17 direct message
- `post` - HTTP POST to URL
- Empty = in-band (HTTP header)
## WebSocket Subscriptions (NUT-17)
```json
// Subscribe
{"kind": "bolt11_mint_quote", "subId": "uuid", "filters": ["quote_id"]}
// Notification
{"subId": "uuid", "payload": {...quote_response...}}
// Unsubscribe
{"unsubscribe": "uuid"}
```
**Subscription kinds:**
- `bolt11_mint_quote` / `bolt11_melt_quote`
- `bolt12_mint_quote` / `bolt12_melt_quote`
- `proof_state`
## Nostr Integration (NUT-27)
Encrypted mint list backup:
- **Event kind:** 30078 (NIP-78)
- **Encryption:** NIP-44 v2
- **d-tag:** `mint-list`
- **Key:** `SHA256(BIP39_seed || "cashu-mint-backup")`
## Security Considerations
1. **Quote IDs are secrets** - Can be used to front-run minting
2. **Use NUT-20** - Lock mint quotes to your pubkey
3. **DLEQ proofs** - Enable offline verification
4. **Blinding factor `r`** - Never share with mint
5. **Single-use tokens** - Always swap received tokens immediately
6. **Verify mint** - Check NUT-06 info before trusting
7. **Multiple mints** - Don't keep all funds in one mint
## Implementation Best Practices
### For Wallets
1. **Verify DLEQ proofs** when receiving tokens
2. **Swap immediately** after receiving tokens from others
3. **Generate unique pubkeys** for each mint quote (NUT-20)
4. **Implement NUT-13** for seed-based recovery
5. **Use NUT-19 cached responses** for reliability
6. **Check token states** before spending (NUT-07)
7. **Handle pending states** gracefully
8. **Order outputs ascending** by amount for privacy
### For Mints
1. **Persist blind signatures** for NUT-09 restore
2. **Implement rate limiting** to prevent abuse
3. **Track pending proofs** with mutex locks
4. **Use NUT-19 caching** for idempotency
5. **Implement NUT-12 DLEQ** for trustless verification
6. **Provide NUT-06 info** with all supported features
7. **Handle key rotation** gracefully
## Common Patterns
### Minting Tokens
```typescript
// 1. Get quote
const quote = await fetch(`${mint}/v1/mint/quote/bolt11`, {
method: 'POST',
body: JSON.stringify({ amount: 1000, unit: 'sat' })
}).then(r => r.json());
// 2. Pay the invoice (quote.request)
// ... pay Lightning invoice ...
// 3. Generate blinded messages
const { blindedMessages, secrets, rs } = createBlindedMessages(amounts, keysetId);
// 4. Get signatures
const { signatures } = await fetch(`${mint}/v1/mint/bolt11`, {
method: 'POST',
body: JSON.stringify({ quote: quote.quote, outputs: blindedMessages })
}).then(r => r.json());
// 5. Unblind to get proofs
const proofs = unblindSignatures(signatures, secrets, rs, keys);
```
### Sending Tokens
```typescript
// 1. Select proofs for amount
const { send, keep } = selectProofsToSend(proofs, amount);
// 2. Swap to get exact amount (optional but recommended)
const { outputs, blindingFactors } = createBlindedMessages([amount, ...changeDenominations]);
const { signatures } = await fetch(`${mint}/v1/swap`, {
method: 'POST',
body: JSON.stringify({ inputs: send, outputs })
}).then(r => r.json());
// 3. Serialize token for recipient
const token = serializeToken({ mint, unit: 'sat', proofs: sendProofs });
// Returns: cashuBxxxxx...
```
### Receiving Tokens
```typescript
// 1. Deserialize token
const { mint, unit, proofs } = deserializeToken(tokenString);
// 2. Swap immediately (invalidates sender's copy)
const { outputs, blindingFactors } = createBlindedMessages(proofs.map(p => p.amount));
const { signatures } = await fetch(`${mint}/v1/swap`, {
method: 'POST',
body: JSON.stringify({ inputs: proofs, outputs })
}).then(r => r.json());
// 3. Unblind and store new proofs
const newProofs = unblindSignatures(signatures, secrets, blindingFactors, keys);
```
### Paying Lightning Invoice
```typescript
// 1. Get melt quote
const quote = await fetch(`${mint}/v1/melt/quote/bolt11`, {
method: 'POST',
body: JSON.stringify({ request: bolt11Invoice, unit: 'sat' })
}).then(r => r.json());
// 2. Select proofs (amount + fee_reserve)
const proofs = selectProofs(quote.amount + quote.fee_reserve);
// 3. Create blank outputs for fee change
const blankOutputs = createBlankOutputs(Math.ceil(Math.log2(quote.fee_reserve)));
// 4. Execute melt
const result = await fetch(`${mint}/v1/melt/bolt11`, {
method: 'POST',
body: JSON.stringify({ quote: quote.quote, inputs: proofs, outputs: blankOutputs })
}).then(r => r.json());
// 5. Process change if any
if (result.change) {
const changeProofs = unblindSignatures(result.change, ...);
}
```
## Error Handling
Mint errors return HTTP 400 with:
```json
{
"detail": "Human readable error message",
"code": 10000
}
```
Common error codes:
- `10000` - Token already spent
- `10001` - Transaction unbalanced
- `10002` - Unit not supported
- `11001` - Quote not found
- `11002` - Quote expired
- `20000` - Keyset not found
- `20001` - Keyset inactive
- `20008` - Quote requires signature (NUT-20)
## Official Resources
- **Website**: https://cashu.space
- **Documentation**: https://docs.cashu.space
- **NUTs Specifications**: https://cashubtc.github.io/nuts/
- **GitHub**: https://github.com/cashubtc/nuts
- **Cashu Dev Kit**: https://cashudevkit.org
- **Reference Implementation**: https://github.com/cashubtc/nutshell
## Quick Checklist
When implementing Cashu:
- [ ] BDHKE operations use secp256k1 correctly
- [ ] Hash-to-curve follows spec with domain separator
- [ ] Token serialization uses V4 CBOR format
- [ ] Keyset IDs calculated correctly (SHA256 + version prefix)
- [ ] Fee calculation uses ppk (parts per thousand)
- [ ] Proofs are swapped immediately after receiving
- [ ] DLEQ proofs verified when present
- [ ] Spending conditions parsed and validated
- [ ] Quote IDs kept secret (use NUT-20)
- [ ] Wallet recovery implemented (NUT-13)
- [ ] Error codes handled appropriately
- [ ] WebSocket subscriptions cleaned up