Improved validation of keysets provided by the mints

This commit is contained in:
minibits-cash
2025-07-30 16:33:34 +02:00
parent 324dc07952
commit 378e85e697
6 changed files with 76 additions and 58 deletions
+1 -1
View File
@@ -1 +1 @@
0198388d-d8dc-72f2-b3e1-89f896656f07
0198586c-6212-732d-a2cb-cf9537d9aab3
+1 -1
View File
@@ -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",
+9 -6
View File
@@ -39,21 +39,24 @@ export const ErrorModal: FC<ErrorModalProps> = function ({ error }) {
}
}
const backgroundColor = useThemeColor('error')
const bg = useThemeColor('error')
return (
<BottomModal
isVisible={isErrorVisible}
onBackdropPress={onClose}
onBackButtonPress={onClose}
style={{ backgroundColor }}
onBackButtonPress={onClose}
ContentComponent={
<>
<>
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: spacing.small }}>
<Icon icon="faInfoCircle" size={spacing.large} color="white" />
<Icon icon="faTriangleExclamation" size={spacing.large} color={bg} />
<Text style={{ color: 'white', marginLeft: spacing.small }}>{error.name}</Text>
</View>
<ScrollView>
<ScrollView
style={{
//height: spacing.screenHeight * 0.1,
maxHeight: spacing.screenHeight * 0.07,
}}>
<Text style={{ color: 'white', marginBottom: spacing.small }}>{error.message}</Text>
</ScrollView>
{error.params && isObj(error.params) && (
+41 -37
View File
@@ -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
+23 -12
View File
@@ -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
}
+1 -1
View File
@@ -1164,7 +1164,7 @@ const handleInFlightByMintTask = async function (mint: Mint): Promise<WalletTask
log.trace('[handleInFlightByMintTask]', {mintUrl: mint.mintUrl, inFlightCounters})
const allInFlightRequestLength = mint.allInFLightRequests?.length
const allInFlightRequestLength = mint.allInFlightRequests?.length
const errors: string[] = []
if(inFlightCounters && inFlightCounters.length > 0) {