mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-08-11 08:59:18 +00:00
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>