From 378e85e697fe2434543d2f8031b4a2e95c5355ec Mon Sep 17 00:00:00 2001 From: minibits-cash Date: Wed, 30 Jul 2025 16:33:34 +0200 Subject: [PATCH] Improved validation of keysets provided by the mints --- dist/BUNDLE_ID | 2 +- package.json | 2 +- src/components/ErrorModal.tsx | 15 +++--- src/models/Mint.ts | 78 +++++++++++++++++--------------- src/services/cashu/cashuUtils.ts | 35 +++++++++----- src/services/walletService.ts | 2 +- 6 files changed, 76 insertions(+), 58 deletions(-) diff --git a/dist/BUNDLE_ID b/dist/BUNDLE_ID index f90b36c..592e950 100644 --- a/dist/BUNDLE_ID +++ b/dist/BUNDLE_ID @@ -1 +1 @@ -0198388d-d8dc-72f2-b3e1-89f896656f07 \ No newline at end of file +0198586c-6212-732d-a2cb-cf9537d9aab3 \ No newline at end of file diff --git a/package.json b/package.json index 16a0f3f..79d358c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minibits_wallet", - "version": "0.2.2-beta.29", + "version": "0.2.2-beta.30", "private": true, "scripts": { "android:clean": "cd android && ./gradlew clean", diff --git a/src/components/ErrorModal.tsx b/src/components/ErrorModal.tsx index 097c96b..2d71268 100644 --- a/src/components/ErrorModal.tsx +++ b/src/components/ErrorModal.tsx @@ -39,21 +39,24 @@ export const ErrorModal: FC = function ({ error }) { } } - const backgroundColor = useThemeColor('error') + const bg = useThemeColor('error') return ( + <> - + {error.name} - + {error.message} {error.params && isObj(error.params) && ( diff --git a/src/models/Mint.ts b/src/models/Mint.ts index 29a8751..622e308 100644 --- a/src/models/Mint.ts +++ b/src/models/Mint.ts @@ -222,51 +222,55 @@ export const MintModel = types })) .actions(self => ({ initKeyset(keyset: CashuMintKeyset, allKeysetIds: string[]) { + // ATTN: const mintsStore = getRootStore(self).mintsStore does not work here (may be because it is being called from within loop) + // Do not add unit the wallet does not have configured - try { - if(!self.isUnitSupported(keyset.unit as MintUnit)) { - throw new AppError(Err.VALIDATION_ERROR, `Unsupported unit provided by the mint: ${keyset.unit}`) - } - - const existing = self.keysets.find(k => k.id === keyset.id) + + if(!self.isUnitSupported(keyset.unit as MintUnit)) { + throw new AppError( + Err.VALIDATION_ERROR, + `Unsupported unit provided by the mint`, + {caller: 'initKeyset', unit: keyset.unit} + ) + } + + const existing = self.keysets.find(k => k.id === keyset.id) - if(existing) { - if (existing.unit !== keyset.unit) { - throw new AppError( - Err.VALIDATION_ERROR, - `Keyset unit mismatch, got ${keyset.unit}, expected ${existing.unit}`, - {caller: 'initKeyset'} - ) - } - - if(keyset.input_fee_ppk && existing.input_fee_ppk !== keyset.input_fee_ppk) { - self.setInputFeePpk(existing.id, keyset.input_fee_ppk) - } - - return existing - } - - // Prevent keysetId collision with other mints - if(CashuUtils.isCollidingKeysetId(keyset.id, allKeysetIds)) { + if(existing) { + if (existing.unit !== keyset.unit) { throw new AppError( Err.VALIDATION_ERROR, - `KeysetId validation failed, collision detected for ${keyset.id}`, - {caller: 'initKeyset'} - ) + `Keyset unit mismatch.`, + {caller: 'initKeyset', existingUnit: existing.unit, keysetUnit: keyset.unit} + ) } - if(!keyset.input_fee_ppk) { - keyset.input_fee_ppk = 0 + if(keyset.input_fee_ppk && existing.input_fee_ppk !== keyset.input_fee_ppk) { + self.setInputFeePpk(existing.id, keyset.input_fee_ppk) } - self.addKeyset(keyset) - self.addUnit(keyset.unit as MintUnit) - self.createProofsCounter(keyset) + return existing + } - log.trace('[initKeyset]', {newKeyset: keyset}) - } catch (e: any) { - throw new AppError(Err.WALLET_ERROR, '[initKeyset] ' + e.message) - } + // Prevent keysetId collision with other mints + if(CashuUtils.isCollidingKeysetId(keyset.id, allKeysetIds)) { + throw new AppError( + Err.VALIDATION_ERROR, + `KeysetId validation failed, collision detected.`, + {caller: 'initKeyset', keysetId: keyset.id} + ) + } + + if(!keyset.input_fee_ppk) { + keyset.input_fee_ppk = 0 + } + + self.addKeyset(keyset) + self.addUnit(keyset.unit as MintUnit) + self.createProofsCounter(keyset) + + log.trace('[initKeyset]', {newKeyset: keyset}) + }, initKeys(key: CashuMintKeys) { // Do not add unit the wallet does not have configured @@ -432,7 +436,7 @@ export const MintModel = types const counters = self.proofsCounters.filter(c => c.inFlightRequests && c.inFlightRequests.length > 0) return counters as MintProofsCounter[] }, - get allInFLightRequests(): InFlightRequest[] { + get allInFlightRequests(): InFlightRequest[] { const requests = self.proofsCounters .flatMap((counter) => counter.inFlightRequests) // Combine all `inFlightRequests` arrays diff --git a/src/services/cashu/cashuUtils.ts b/src/services/cashu/cashuUtils.ts index 3d46e99..ad16d2a 100644 --- a/src/services/cashu/cashuUtils.ts +++ b/src/services/cashu/cashuUtils.ts @@ -6,6 +6,7 @@ import type { PaymentRequest as CashuPaymentRequest, PaymentRequestPayload, } from '@cashu/cashu-ts' +import { bytesToHex } from '@noble/hashes/utils' import AppError, {Err} from '../../utils/AppError' import { getDecodedToken } from '@cashu/cashu-ts' import {Proof} from '../../models/Proof' @@ -13,6 +14,7 @@ import { log } from '../logService' import { decodePaymentRequest, sumProofs } from '@cashu/cashu-ts/src/utils' import { NostrClient } from '../nostrService' import { getUnixTime } from 'date-fns/getUnixTime' +import { Text } from '../../components' export {CashuProof} @@ -276,35 +278,42 @@ const validateMintKeys = function (keys: object): boolean { } } -function getKeysetIdInt(keysetIdHex: string) { - return parseInt(`0x${keysetIdHex}`, 16) % (2 ** 31 - 1) +function getKeysetIdInt(keysetId: string): bigint { + if (/^[0-9a-fA-F]+$/.test(keysetId)) { + return BigInt(`0x${keysetId}`) % BigInt(2 ** 31 - 1) + } else { + const bin = atob(keysetId) + const hex = bytesToHex(new TextEncoder().encode(bin)) + return BigInt(`0x${hex}`) % BigInt(2 ** 31 - 1) + } } function isCollidingKeysetId( - newKeysetIdHex: string, + newKeysetId: string, storedKeysetIds: string[], ) { - const newKeysetIdInt = getKeysetIdInt(newKeysetIdHex) + const newKeysetIdInt = getKeysetIdInt(newKeysetId) return storedKeysetIds.some((storedId) => { - const storedKeysetIdInt = getKeysetIdInt(storedId) - if (storedId === newKeysetIdHex) { + + if (storedId === newKeysetId) { // Colliding keyset ID! log.error('[isCollidingKeysetId] Colliding keyset ID', { - newKeysetIdHex, + newKeysetId, storedId, - newKeysetIdInt, - storedKeysetIdInt, }) return true } + + const storedKeysetIdInt = getKeysetIdInt(storedId) + if (storedKeysetIdInt === newKeysetIdInt) { // Colliding keyset ID integer! log.error('[isCollidingKeysetId] Colliding keyset ID integer', { - newKeysetIdHex, + newKeysetId, storedId, - newKeysetIdInt, - storedKeysetIdInt, + newKeysetIdInt: newKeysetIdInt.toString(), + storedKeysetIdInt: storedKeysetIdInt.toString(), }) return true @@ -397,3 +406,5 @@ export const CashuUtils = { isTokenP2PKLocked, isCollidingKeysetId } + + diff --git a/src/services/walletService.ts b/src/services/walletService.ts index ae49c68..8b27cf0 100644 --- a/src/services/walletService.ts +++ b/src/services/walletService.ts @@ -1164,7 +1164,7 @@ const handleInFlightByMintTask = async function (mint: Mint): Promise 0) {