Files
minibits_wallet/__mocks__
minibits-cashandClaude Opus 4.8 c7fd763eca Add NUT-20 quote-signing foundation (Stage 1 of onchain)
NUT-30 onchain mint quotes MUST be locked to a pubkey (the mint rejects
them with 20009 otherwise), so onchain needs NUT-20 quote signing. This
is net-new: bolt11 topup does not sign quotes today.

Adds the two pieces, with no production caller yet — Stage 5 (onchain
receive) is the first consumer:

  wallet_counters (new table, migration v30)
    NUT-20's path m/129373'/20'/0'/0'/{counter} has no mint or keyset
    component, so unlike mint_counters this counter is wallet-global.
    Keyed by purpose name so future wallet-global counters need no
    migration. No seed: no NUT-20 quote has ever existed, so an absent
    row (index 0) is correct for new and upgrading wallets alike.

  walletCountersRepo
    Allocation is BURN-FORWARD: one atomic INSERT..ON CONFLICT..RETURNING
    commits the index before the caller uses it, so a failed quote request
    or a crash can only SKIP an index, never hand the same one out twice.
    Reuse is what we cannot allow — two quotes sharing a pubkey lets the
    mint link them (NUT-20 asks for a unique key per quote precisely to
    prevent that) and makes the signature ambiguous. Being atomic, it is
    also safe against concurrent foreground/NWC-background allocation.

  services/cashu/nut20.ts
    deriveQuoteKeypair() is pure (seed in, keypair out) so it works from
    the off-MST background paths that will need to sign. Only the integer
    index is ever persisted; the privkey is re-derived from the keychain
    seed at mint time, which may be days later once an onchain deposit
    confirms. No key material lands in SQLite.

Also fixes the react-native-quick-crypto jest mock, which claimed to make
bip32 work but did not: it required the bare 'crypto' specifier (an empty
shim under the RN preset) and lacked __esModule, so Babel's interop double-
wrapped it and every quickCrypto.createHmac/.pbkdf2Sync call resolved to
undefined. Our patches to @scure/bip32 and @scure/bip39 route through
quick-crypto, so nothing that derives keys could be tested until now.

Tests pin the derivation against a vector computed independently of this
codebase (a clean @scure/bip32 install), so it cross-checks the path rather
than enshrining our own output. Note the exported signMintQuote /
verifyMintQuoteSignature in cashu-ts are the LEGACY aggregation (no domain
tag, no amounts); the library's own mint path signs with the spec's
Cashu_MintQuoteSig_v1, so what we transmit is spec-correct. The wire format
is proven when a real mint accepts a signed request, in Stage 5.

209/209 tests pass; typecheck introduces no new errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 11:32:29 +02:00
..