taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

commit fdc36ed73a352738674a14cc9daf5f263050edf7
parent d89d45bd527e441c68706392cac5faa0d9ffde52
Author: Marc Stibane <marc@taler.net>
Date:   Wed, 19 Aug 2026 07:54:59 +0200

AI: prepare Exchange master-key rotation

Diffstat:
MTalerWallet1/Model/Model+Exchange.swift | 36++++++++++++++++++++++++++++++++++++
MTalerWallet1/Model/Model+Withdraw.swift | 1+
2 files changed, 37 insertions(+), 0 deletions(-)

diff --git a/TalerWallet1/Model/Model+Exchange.swift b/TalerWallet1/Model/Model+Exchange.swift @@ -58,6 +58,18 @@ struct ExchangeState: Codable, Hashable { var tosStatus: ExchangeTosStatus } +/// Set on an Exchange when it changed its master key and the user has not +/// confirmed the change yet. Withdrawals are refused while this is present +/// (wallet-core throws WALLET_EXCHANGE_KEYS_NOT_ACCEPTED), until the app calls +/// confirmExchangeKeyChange(). +struct ExchangeKeyChangeInfo: Codable, Hashable { + var currentMasterPub: String + var currentCurrency: String + var supersededMasterPub: String + var supersededCurrency: String + var sharesDenominations: Bool +} + struct ExchangeTransition: Codable { // Notification var type: String // exchange-state-transition var exchangeBaseUrl: String @@ -109,6 +121,7 @@ struct Exchange: Codable, Hashable, Identifiable { var bankComplianceLanguage: BankDialect? var lastUpdateTimestamp: Timestamp? var lastUpdateErrorInfo: OperationErrorInfo? + var unconfirmedKeyChange: ExchangeKeyChangeInfo? // if set, withdrawals are refused until confirmed // walletKycStatus?: ExchangeWalletKycStatus; // walletKycReservePub?: string; @@ -232,6 +245,21 @@ fileprivate struct DeleteExchange: WalletBackendFormattedRequest { struct Response: Decodable {} // no result - getting no error back means success } +/// A request to confirm that a changed exchange key set is legitimate. +/// Needed before a withdrawal can proceed once `unconfirmedKeyChange` is set. +fileprivate struct ConfirmExchangeKeyChange: WalletBackendFormattedRequest { + var operation: String { "confirmExchangeKeyChange" } + func args() -> Args { Args(exchangeBaseUrl: exchangeBaseUrl, currentMasterPub: currentMasterPub) } + + var exchangeBaseUrl: String + var currentMasterPub: String + struct Args: Encodable { + var exchangeBaseUrl: String + var currentMasterPub: String + } + struct Response: Decodable {} // no result - getting no error back means success +} + /// A request to get info about a currency fileprivate struct GetCurrencySpecification: WalletBackendFormattedRequest { var operation: String { "getCurrencySpecification" } @@ -342,6 +370,14 @@ extension WalletModel { _ = try await sendRequest(request, viewHandles: viewHandles) } + /// confirm a changed exchange master key so withdrawals are allowed again + nonisolated func confirmExchangeKeyChange(exchangeBaseUrl: String, currentMasterPub: String, viewHandles: Bool = false) + async throws { + let request = ConfirmExchangeKeyChange(exchangeBaseUrl: exchangeBaseUrl, currentMasterPub: currentMasterPub) + logger.info("confirming exchange key change: \(exchangeBaseUrl, privacy: .public)") + _ = try await sendRequest(request, viewHandles: viewHandles) + } + nonisolated func getCurrencyInfo(scope: ScopeInfo, viewHandles: Bool = false) async throws -> CurrencyInfo { let request = GetCurrencySpecification(scope: scope) diff --git a/TalerWallet1/Model/Model+Withdraw.swift b/TalerWallet1/Model/Model+Withdraw.swift @@ -140,6 +140,7 @@ struct WithdrawalDetailsForAmount: Decodable { var withdrawalAccountsList: AccountDetailsArray? var ageRestrictionOptions: [Int]? // Array of ages var scopeInfo: ScopeInfo + var unconfirmedKeyChange: ExchangeKeyChangeInfo? // if set, accepting the withdrawal will fail until confirmed } /// A request to get an exchange's withdrawal details. fileprivate struct GetWithdrawalDetailsForAmount: WalletBackendFormattedRequest {