mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-08-11 17:07:44 +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>
25 lines
1.2 KiB
JavaScript
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
|