Files
minibits_wallet/__mocks__/react-native-quick-crypto.js
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

25 lines
1.2 KiB
JavaScript

/**
* Jest manual mock for react-native-quick-crypto.
*
* quick-crypto is a drop-in replacement for Node's `crypto`, so under jest
* (Node environment, no native module) we simply delegate to Node's crypto.
* This lets dependencies that load it at import time — e.g. @scure/bip32 via
* @cashu/cashu-ts — be required without the native `QuickCrypto` module.
*
* `__esModule` is load-bearing. Our patches to @scure/bip32 and @scure/bip39
* (see patches/) rewire them onto quick-crypto for native speed, using a DEFAULT
* import: `import quickCrypto from 'react-native-quick-crypto'` and then calling
* `quickCrypto.createHmac(...)` / `.pbkdf2Sync(...)`. Without the `__esModule`
* marker, Babel's interop wraps this CJS module again — the default import then
* resolves to `{default: crypto}` instead of `crypto`, and every call lands on
* `undefined`. With it, interop hands back `default` directly.
*/
// `node:` prefix is deliberate: the bare `crypto` specifier resolves to an empty
// shim under the react-native jest preset, which silently yields a module with no
// createHmac/pbkdf2Sync on it.
const crypto = require('node:crypto')
module.exports = crypto
module.exports.default = crypto
module.exports.__esModule = true