gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: wallet-core: remove old sync


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet-core: remove old sync code, add stored backups skeleton
Date: Wed, 30 Aug 2023 18:01:20 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new a713d90c3 wallet-core: remove old sync code, add stored backups 
skeleton
a713d90c3 is described below

commit a713d90c3c564408309d92223d383ecc9225924f
Author: Florian Dold <florian@dold.me>
AuthorDate: Wed Aug 30 18:01:18 2023 +0200

    wallet-core: remove old sync code, add stored backups skeleton
---
 packages/taler-util/src/backup-types.ts            | 1283 --------------------
 packages/taler-util/src/wallet-types.ts            |   18 +
 packages/taler-wallet-core/src/db.ts               |   49 +
 .../src/operations/backup/export.ts                |  586 ---------
 .../src/operations/backup/import.ts                |  874 -------------
 .../src/operations/backup/index.ts                 |  188 ++-
 .../src/operations/backup/state.ts                 |   92 --
 packages/taler-wallet-core/src/wallet-api-types.ts |   42 +-
 packages/taler-wallet-core/src/wallet.ts           |   32 +-
 .../taler-wallet-webextension/src/wxBackend.ts     |    2 +-
 10 files changed, 213 insertions(+), 2953 deletions(-)

diff --git a/packages/taler-util/src/backup-types.ts 
b/packages/taler-util/src/backup-types.ts
index 0211ff740..2eba1e4ca 100644
--- a/packages/taler-util/src/backup-types.ts
+++ b/packages/taler-util/src/backup-types.ts
@@ -14,1289 +14,6 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-/**
- * Type declarations for the backup content format.
- *
- * Contains some redundancy with the other type declarations,
- * as the backup schema must remain very stable and should be self-contained.
- *
- * Future:
- * 1. Ghost spends (coin unexpectedly spent by a wallet with shared data)
- * 2. Ghost withdrawals (reserve unexpectedly emptied by another wallet with 
shared data)
- * 3. Track losses through re-denomination of payments/refreshes
- * 4. (Feature:) Payments to own bank account and P2P-payments need to be 
backed up
- * 5. Track last/next update time, so on restore we need to do less work
- * 6. Currency render preferences?
- *
- * Questions:
- * 1. What happens when two backups are merged that have
- *    the same coin in different refresh groups?
- *    => Both are added, one will eventually fail
- * 2. Should we make more information forgettable?  I.e. is
- *    the coin selection still relevant for a purchase after the coins
- *    are legally expired?
- *    => Yes, still needs to be implemented
- * 3. What about re-denominations / re-selection of payment coins?
- *    Is it enough to store a clock value for the selection?
- *    => Coin derivation should also consider denom pub hash
- *
- * General considerations / decisions:
- * 1. Information about previously occurring errors and
- *    retries is never backed up.
- * 2. The ToS text of an exchange is never backed up.
- * 3. Derived information is never backed up (hashed values, public keys
- *    when we know the private key).
- *
- * Problems:
- *
- * Withdrawal group fork/merging loses money:
- * - Before the withdrawal happens, wallet forks into two backups.
- * - Both wallets need to re-denominate the withdrawal (unlikely but possible).
- * - Because the backup doesn't store planchets where a withdrawal was 
attempted,
- *   after merging some money will be list.
- * - Fix: backup withdrawal objects also store planchets where withdrawal has 
been attempted
- *
- * @author Florian Dold <dold@taler.net>
- */
-
-/**
- * Imports.
- */
-import { DenominationPubKey, UnblindedSignature } from "./taler-types.js";
-import {
-  TalerProtocolDuration,
-  TalerProtocolTimestamp,
-  TalerPreciseTimestamp,
-} from "./time.js";
-
-export const BACKUP_TAG = "gnu-taler-wallet-backup-content" as const;
-/**
- * Major version.  Each increment means a backwards-incompatible change.
- * Typically this means that a custom converter needs to be written.
- */
-export const BACKUP_VERSION_MAJOR = 1 as const;
-
-/**
- * Minor version.  Each increment means that information is added to the backup
- * in a backwards-compatible way.
- *
- * Wallets can always import a smaller minor version than their own backup 
code version.
- * When importing a bigger version, data loss is possible and the user should 
be urged to
- * upgrade their wallet first.
- */
-export const BACKUP_VERSION_MINOR = 1 as const;
-
-/**
- * Type alias for strings that are to be treated like amounts.
- */
-type BackupAmountString = string;
-
-/**
- * A human-recognizable identifier here that is
- * reasonable unique and assigned the first time the wallet is
- * started/installed, such as:
- *
- * `${wallet-implementation} ${os} ${hostname} (${short-uid})`
- * => e.g. "GNU Taler Android iceking ABC123"
- */
-type DeviceIdString = string;
-
-/**
- * Contract terms JSON.
- */
-type RawContractTerms = any;
-
-/**
- * Unique identifier for an operation, used to either (a) reference
- * the operation in a tombstone (b) disambiguate conflicting writes.
- */
-type OperationUid = string;
-
-/**
- * Content of the backup.
- *
- * The contents of the wallet must be serialized in a deterministic
- * way across implementations, so that the normalized backup content
- * JSON is identical when the wallet's content is identical.
- */
-export interface WalletBackupContentV1 {
-  /**
-   * Magic constant to identify that this is a backup content JSON.
-   */
-  schema_id: typeof BACKUP_TAG;
-
-  /**
-   * Version of the schema.
-   */
-  schema_version: typeof BACKUP_VERSION_MAJOR;
-
-  minor_version: number;
-
-  /**
-   * Root public key of the wallet.  This field is present as
-   * a sanity check if the backup content JSON is loaded from file.
-   */
-  wallet_root_pub: string;
-
-  /**
-   * Current device identifier that "owns" the backup.
-   *
-   * This identifier allows one wallet to notice when another
-   * wallet is "alive" and connected to the same sync provider.
-   */
-  current_device_id: DeviceIdString;
-
-  /**
-   * Timestamp of the backup.
-   *
-   * This timestamp should only be advanced if the content
-   * of the backup changes.
-   */
-  timestamp: TalerPreciseTimestamp;
-
-  /**
-   * Per-exchange data sorted by exchange master public key.
-   *
-   * Sorted by the exchange public key.
-   */
-  exchanges: BackupExchange[];
-
-  exchange_details: BackupExchangeDetails[];
-
-  /**
-   * Withdrawal groups.
-   *
-   * Sorted by the withdrawal group ID.
-   */
-  withdrawal_groups: BackupWithdrawalGroup[];
-
-  /**
-   * Grouped refresh sessions.
-   *
-   * Sorted by the refresh group ID.
-   */
-  refresh_groups: BackupRefreshGroup[];
-
-  /**
-   * Tips.
-   *
-   * Sorted by the wallet tip ID.
-   */
-  tips: BackupTip[];
-
-  /**
-   * Accepted purchases.
-   *
-   * Sorted by the proposal ID.
-   */
-  purchases: BackupPurchase[];
-
-  /**
-   * All backup providers.  Backup providers
-   * in this list should be considered "active".
-   *
-   * Sorted by the provider base URL.
-   */
-  backup_providers: BackupBackupProvider[];
-
-  /**
-   * Recoup groups.
-   */
-  recoup_groups: BackupRecoupGroup[];
-
-  /**
-   * Trusted auditors, either for official (3 letter) or local (4-12 letter)
-   * currencies.
-   *
-   * Auditors are sorted by their canonicalized base URL.
-   */
-  trusted_auditors: { [currency: string]: BackupTrustAuditor[] };
-
-  /**
-   * Trusted exchange.  Only applicable for local currencies (4-12 letter 
currency code).
-   *
-   * Exchanges are sorted by their canonicalized base URL.
-   */
-  trusted_exchanges: { [currency: string]: BackupTrustExchange[] };
-
-  /**
-   * Interning table for forgettable values of contract terms.
-   *
-   * Used to reduce storage space, as many forgettable items (product image,
-   * addresses, etc.) might be shared among many contract terms.
-   */
-  intern_table: { [hash: string]: any };
-
-  /**
-   * Permanent error reports.
-   */
-  error_reports: BackupErrorReport[];
-
-  /**
-   * Deletion tombstones.  Lexically sorted.
-   */
-  tombstones: Tombstone[];
-}
-
-export enum BackupOperationStatus {
-  Cancelled = "cancelled",
-  Finished = "finished",
-  Pending = "pending",
-}
-
-export enum BackupWgType {
-  BankManual = "bank-manual",
-  BankIntegrated = "bank-integrated",
-  PeerPullCredit = "peer-pull-credit",
-  PeerPushCredit = "peer-push-credit",
-  Recoup = "recoup",
-}
-
-export type BackupWgInfo =
-  | {
-      type: BackupWgType.BankManual;
-    }
-  | {
-      type: BackupWgType.BankIntegrated;
-      taler_withdraw_uri: string;
-
-      /**
-       * URL that the user can be redirected to, and allows
-       * them to confirm (or abort) the bank-integrated withdrawal.
-       */
-      confirm_url?: string;
-
-      /**
-       * Exchange payto URI that the bank will use to fund the reserve.
-       */
-      exchange_payto_uri: string;
-
-      /**
-       * Time when the information about this reserve was posted to the bank.
-       *
-       * Only applies if bankWithdrawStatusUrl is defined.
-       *
-       * Set to undefined if that hasn't happened yet.
-       */
-      timestamp_reserve_info_posted?: TalerPreciseTimestamp;
-
-      /**
-       * Time when the reserve was confirmed by the bank.
-       *
-       * Set to undefined if not confirmed yet.
-       */
-      timestamp_bank_confirmed?: TalerPreciseTimestamp;
-    }
-  | {
-      type: BackupWgType.PeerPullCredit;
-      contract_terms: any;
-      contract_priv: string;
-    }
-  | {
-      type: BackupWgType.PeerPushCredit;
-      contract_terms: any;
-    }
-  | {
-      type: BackupWgType.Recoup;
-    };
-
-/**
- * FIXME: Open questions:
- * - Do we have to store the denomination selection?  Why?
- *   (If deterministic, amount shouldn't change. Not storing it is simpler.)
- */
-export interface BackupWithdrawalGroup {
-  withdrawal_group_id: string;
-
-  /**
-   * Detailed info based on the type of withdrawal group.
-   */
-  info: BackupWgInfo;
-
-  secret_seed: string;
-
-  reserve_priv: string;
-
-  exchange_base_url: string;
-
-  timestamp_created: TalerPreciseTimestamp;
-
-  timestamp_finish?: TalerPreciseTimestamp;
-
-  operation_status: BackupOperationStatus;
-
-  instructed_amount: BackupAmountString;
-
-  /**
-   * Amount including fees (i.e. the amount subtracted from the
-   * reserve to withdraw all coins in this withdrawal session).
-   *
-   * Note that this *includes* the amount remaining in the reserve
-   * that is too small to be withdrawn, and thus can't be derived
-   * from selectedDenoms.
-   */
-  raw_withdrawal_amount: BackupAmountString;
-
-  effective_withdrawal_amount: BackupAmountString;
-
-  /**
-   * Restrict withdrawals from this reserve to this age.
-   */
-  restrict_age?: number;
-
-  /**
-   * Multiset of denominations selected for withdrawal.
-   */
-  selected_denoms: BackupDenomSel;
-
-  selected_denoms_uid: OperationUid;
-}
-
-/**
- * Tombstone in the format "<type>:<key>"
- */
-export type Tombstone = string;
-
-/**
- * Detailed error report.
- *
- * For auditor-relevant reports with attached cryptographic proof,
- * the error report also should contain the submission status to
- * the auditor(s).
- */
-interface BackupErrorReport {
-  // FIXME: specify!
-}
-
-/**
- * Trust declaration for an auditor.
- *
- * The trust applies based on the public key of
- * the auditor, irrespective of what base URL the exchange
- * is referencing.
- */
-export interface BackupTrustAuditor {
-  /**
-   * Base URL of the auditor.
-   */
-  auditor_base_url: string;
-
-  /**
-   * Public key of the auditor.
-   */
-  auditor_pub: string;
-
-  /**
-   * UIDs for the operation of adding this auditor
-   * as a trusted auditor.
-   */
-  uids: OperationUid;
-}
-
-/**
- * Trust declaration for an exchange.
- *
- * The trust only applies for the combination of base URL
- * and public key.  If the master public key changes while the base
- * URL stays the same, the exchange has to be re-added by a wallet update
- * or by the user.
- */
-export interface BackupTrustExchange {
-  /**
-   * Canonicalized exchange base URL.
-   */
-  exchange_base_url: string;
-
-  /**
-   * Master public key of the exchange.
-   */
-  exchange_master_pub: string;
-
-  /**
-   * UIDs for the operation of adding this exchange
-   * as trusted.
-   */
-  uids: OperationUid;
-}
-
-export class BackupBackupProviderTerms {
-  /**
-   * Last known supported protocol version.
-   */
-  supported_protocol_version: string;
-
-  /**
-   * Last known annual fee.
-   */
-  annual_fee: BackupAmountString;
-
-  /**
-   * Last known storage limit.
-   */
-  storage_limit_in_megabytes: number;
-}
-
-/**
- * Backup information about one backup storage provider.
- */
-export class BackupBackupProvider {
-  /**
-   * Canonicalized base URL of the provider.
-   */
-  base_url: string;
-
-  /**
-   * Last known terms.  Might be unavailable in some situations, such
-   * as directly after restoring form a backup recovery document.
-   */
-  terms?: BackupBackupProviderTerms;
-
-  /**
-   * Proposal IDs for payments to this provider.
-   */
-  pay_proposal_ids: string[];
-
-  /**
-   * UIDs for adding this backup provider.
-   */
-  uids: OperationUid[];
-}
-
-/**
- * Status of recoup operations that were grouped together.
- *
- * The remaining amount of the corresponding coins must be set to
- * zero when the recoup group is created/imported.
- */
-export interface BackupRecoupGroup {
-  /**
-   * Unique identifier for the recoup group record.
-   */
-  recoup_group_id: string;
-
-  /**
-   * Timestamp when the recoup was started.
-   */
-  timestamp_created: TalerPreciseTimestamp;
-
-  timestamp_finish?: TalerPreciseTimestamp;
-  finish_clock?: TalerProtocolTimestamp;
-  // FIXME: Use some enum here!
-  finish_is_failure?: boolean;
-
-  /**
-   * Information about each coin being recouped.
-   */
-  coins: {
-    coin_pub: string;
-    recoup_finished: boolean;
-  }[];
-}
-
-/**
- * Types of coin sources.
- */
-export enum BackupCoinSourceType {
-  Withdraw = "withdraw",
-  Refresh = "refresh",
-  Reward = "reward",
-}
-
-/**
- * Metadata about a coin obtained via withdrawing.
- */
-export interface BackupWithdrawCoinSource {
-  type: BackupCoinSourceType.Withdraw;
-
-  /**
-   * Can be the empty string for orphaned coins.
-   */
-  withdrawal_group_id: string;
-
-  /**
-   * Index of the coin in the withdrawal session.
-   */
-  coin_index: number;
-
-  /**
-   * Reserve public key for the reserve we got this coin from.
-   */
-  reserve_pub: string;
-}
-
-/**
- * Metadata about a coin obtained from refreshing.
- *
- * FIXME:  Currently does not link to the refreshGroupId because
- * the wallet DB doesn't do this.  Not really necessary,
- * but would be more consistent.
- */
-export interface BackupRefreshCoinSource {
-  type: BackupCoinSourceType.Refresh;
-
-  /**
-   * Public key of the coin that was refreshed into this coin.
-   */
-  old_coin_pub: string;
-
-  refresh_group_id: string;
-}
-
-/**
- * Metadata about a coin obtained from a tip.
- */
-export interface BackupTipCoinSource {
-  type: BackupCoinSourceType.Reward;
-
-  /**
-   * Wallet's identifier for the tip that this coin
-   * originates from.
-   */
-  wallet_tip_id: string;
-
-  /**
-   * Index in the tip planchets of the tip.
-   */
-  coin_index: number;
-}
-
-/**
- * Metadata about a coin depending on the origin.
- */
-export type BackupCoinSource =
-  | BackupWithdrawCoinSource
-  | BackupRefreshCoinSource
-  | BackupTipCoinSource;
-
-/**
- * Backup information about a coin.
- *
- * (Always part of a BackupExchange/BackupDenom)
- */
-export interface BackupCoin {
-  /**
-   * Where did the coin come from?  Used for recouping coins.
-   */
-  coin_source: BackupCoinSource;
-
-  /**
-   * Private key to authorize operations on the coin.
-   */
-  coin_priv: string;
-
-  /**
-   * Unblinded signature by the exchange.
-   */
-  denom_sig: UnblindedSignature;
-
-  /**
-   * Information about where and how the coin was spent.
-   */
-  spend_allocation:
-    | {
-        id: string;
-        amount: BackupAmountString;
-      }
-    | undefined;
-
-  /**
-   * Blinding key used when withdrawing the coin.
-   * Potentionally used again during payback.
-   */
-  blinding_key: string;
-
-  /**
-   * Does the wallet think that the coin is still fresh?
-   *
-   * Note that even if a fresh coin is imported, it should still
-   * be refreshed in most situations.
-   */
-  fresh: boolean;
-}
-
-/**
- * Status of a tip we got from a merchant.
- */
-export interface BackupTip {
-  /**
-   * Tip ID chosen by the wallet.
-   */
-  wallet_tip_id: string;
-
-  /**
-   * The merchant's identifier for this tip.
-   */
-  merchant_tip_id: string;
-
-  /**
-   * Secret seed used for the tipping planchets.
-   */
-  secret_seed: string;
-
-  /**
-   * Has the user accepted the tip?  Only after the tip has been accepted coins
-   * withdrawn from the tip may be used.
-   */
-  timestamp_accepted: TalerPreciseTimestamp | undefined;
-
-  /**
-   * When was the tip first scanned by the wallet?
-   */
-  timestamp_created: TalerPreciseTimestamp;
-
-  timestamp_finished?: TalerPreciseTimestamp;
-  finish_is_failure?: boolean;
-
-  /**
-   * The tipped amount.
-   */
-  tip_amount_raw: BackupAmountString;
-
-  /**
-   * Timestamp, the tip can't be picked up anymore after this deadline.
-   */
-  timestamp_expiration: TalerProtocolTimestamp;
-
-  /**
-   * The exchange that will sign our coins, chosen by the merchant.
-   */
-  exchange_base_url: string;
-
-  /**
-   * Base URL of the merchant that is giving us the tip.
-   */
-  merchant_base_url: string;
-
-  /**
-   * Selected denominations.  Determines the effective tip amount.
-   */
-  selected_denoms: BackupDenomSel;
-
-  /**
-   * The url to be redirected after the tip is accepted.
-   */
-  next_url: string | undefined;
-
-  /**
-   * UID for the denomination selection.
-   * Used to disambiguate when merging.
-   */
-  selected_denoms_uid: OperationUid;
-}
-
-/**
- * Reasons for why a coin is being refreshed.
- */
-export enum BackupRefreshReason {
-  Manual = "manual",
-  Pay = "pay",
-  Refund = "refund",
-  AbortPay = "abort-pay",
-  Recoup = "recoup",
-  BackupRestored = "backup-restored",
-  Scheduled = "scheduled",
-}
-
-/**
- * Information about one refresh session, always part
- * of a refresh group.
- *
- * (Public key of the old coin is stored in the refresh group.)
- */
-export interface BackupRefreshSession {
-  /**
-   * Hashed denominations of the newly requested coins.
-   */
-  new_denoms: BackupDenomSel;
-
-  /**
-   * Seed used to derive the planchets and
-   * transfer private keys for this refresh session.
-   */
-  session_secret_seed: string;
-
-  /**
-   * The no-reveal-index after we've done the melting.
-   */
-  noreveal_index?: number;
-}
-
-/**
- * Refresh session for one coin inside a refresh group.
- */
-export interface BackupRefreshOldCoin {
-  /**
-   * Public key of the old coin,
-   */
-  coin_pub: string;
-
-  /**
-   * Requested amount to refresh.  Must be subtracted from the coin's remaining
-   * amount as soon as the coin is added to the refresh group.
-   */
-  input_amount: BackupAmountString;
-
-  /**
-   * Estimated output (may change if it takes a long time to create the
-   * actual session).
-   */
-  estimated_output_amount: BackupAmountString;
-
-  /**
-   * Did the refresh session finish (or was it unnecessary/impossible to create
-   * one)
-   */
-  finished: boolean;
-
-  /**
-   * Refresh session (if created) or undefined it not created yet.
-   */
-  refresh_session: BackupRefreshSession | undefined;
-}
-
-/**
- * Information about one refresh group.
- *
- * May span more than one exchange, but typically doesn't
- */
-export interface BackupRefreshGroup {
-  refresh_group_id: string;
-
-  reason: BackupRefreshReason;
-
-  /**
-   * Details per old coin.
-   */
-  old_coins: BackupRefreshOldCoin[];
-
-  timestamp_created: TalerPreciseTimestamp;
-
-  timestamp_finish?: TalerPreciseTimestamp;
-  finish_is_failure?: boolean;
-}
-
-export enum BackupRefundState {
-  Failed = "failed",
-  Applied = "applied",
-  Pending = "pending",
-}
-
-/**
- * Common information about a refund.
- */
-export interface BackupRefundItemCommon {
-  /**
-   * Execution time as claimed by the merchant
-   */
-  execution_time: TalerProtocolTimestamp;
-
-  /**
-   * Time when the wallet became aware of the refund.
-   */
-  obtained_time: TalerProtocolTimestamp;
-
-  /**
-   * Amount refunded for the coin.
-   */
-  refund_amount: BackupAmountString;
-
-  /**
-   * Coin being refunded.
-   */
-  coin_pub: string;
-
-  /**
-   * The refund transaction ID for the refund.
-   */
-  rtransaction_id: number;
-
-  /**
-   * Upper bound on the refresh cost incurred by
-   * applying this refund.
-   *
-   * Might be lower in practice when two refunds on the same
-   * coin are refreshed in the same refresh operation.
-   *
-   * Used to display fees, and stored since it's expensive to recompute
-   * accurately.
-   */
-  total_refresh_cost_bound: BackupAmountString;
-}
-
-/**
- * Failed refund, either because the merchant did
- * something wrong or it expired.
- */
-export interface BackupRefundFailedItem extends BackupRefundItemCommon {
-  type: BackupRefundState.Failed;
-}
-
-export interface BackupRefundPendingItem extends BackupRefundItemCommon {
-  type: BackupRefundState.Pending;
-}
-
-export interface BackupRefundAppliedItem extends BackupRefundItemCommon {
-  type: BackupRefundState.Applied;
-}
-
-/**
- * State of one refund from the merchant, maintained by the wallet.
- */
-export type BackupRefundItem =
-  | BackupRefundFailedItem
-  | BackupRefundPendingItem
-  | BackupRefundAppliedItem;
-
-/**
- * Data we store when the payment was accepted.
- */
-export interface BackupPayInfo {
-  pay_coins: {
-    /**
-     * Public keys of the coins that were selected.
-     */
-    coin_pub: string;
-
-    /**
-     * Amount that each coin contributes.
-     */
-    contribution: BackupAmountString;
-  }[];
-
-  /**
-   * Unique ID to disambiguate pay coin selection on merge.
-   */
-  pay_coins_uid: OperationUid;
-
-  /**
-   * Total cost initially shown to the user.
-   *
-   * This includes the amount taken by the merchant, fees (wire/deposit) 
contributed
-   * by the customer, refreshing fees, fees for withdraw-after-refresh and 
"trimmings"
-   * of coins that are too small to spend.
-   *
-   * Note that in rare situations, this cost might not be accurate (e.g.
-   * when the payment or refresh gets re-denominated).
-   * We might show adjustments to this later, but currently we don't do so.
-   */
-  total_pay_cost: BackupAmountString;
-}
-
-export interface BackupPurchase {
-  /**
-   * Proposal ID for this purchase.  Uniquely identifies the
-   * purchase and the proposal.
-   */
-  proposal_id: string;
-
-  /**
-   * Status of the proposal.
-   */
-  proposal_status: BackupProposalStatus;
-
-  /**
-   * Proposal that this one got "redirected" to as part of
-   * the repurchase detection.
-   */
-  repurchase_proposal_id: string | undefined;
-
-  /**
-   * Session ID we got when downloading the contract.
-   */
-  download_session_id?: string;
-
-  /**
-   * Merchant-assigned order ID of the proposal.
-   */
-  order_id: string;
-
-  /**
-   * Base URL of the merchant that proposed the purchase.
-   */
-  merchant_base_url: string;
-
-  /**
-   * Claim token initially given by the merchant.
-   */
-  claim_token: string | undefined;
-
-  /**
-   * Contract terms we got from the merchant.
-   */
-  contract_terms_raw?: RawContractTerms;
-
-  /**
-   * Signature on the contract terms.
-   *
-   * FIXME: Better name needed.
-   */
-  merchant_sig?: string;
-
-  /**
-   * Private key for the nonce.  Might eventually be used
-   * to prove ownership of the contract.
-   */
-  nonce_priv: string;
-
-  pay_info: BackupPayInfo | undefined;
-
-  /**
-   * Timestamp of the first time that sending a payment to the merchant
-   * for this purchase was successful.
-   */
-  timestamp_first_successful_pay: TalerPreciseTimestamp | undefined;
-
-  /**
-   * Signature by the merchant confirming the payment.
-   */
-  merchant_pay_sig: string | undefined;
-
-  /**
-   * Text to be shown to the point-of-sale staff as a proof of payment.
-   */
-  pos_confirmation: string | undefined;
-
-  timestamp_proposed: TalerPreciseTimestamp;
-
-  /**
-   * When was the purchase made?
-   * Refers to the time that the user accepted.
-   */
-  timestamp_accepted: TalerPreciseTimestamp | undefined;
-
-  /**
-   * Pending refunds for the purchase.  A refund is pending
-   * when the merchant reports a transient error from the exchange.
-   */
-  refunds: BackupRefundItem[];
-
-  /**
-   * Continue querying the refund status until this deadline has expired.
-   */
-  auto_refund_deadline: TalerProtocolTimestamp | undefined;
-
-  shared: boolean;
-}
-
-/**
- * Info about one denomination in the backup.
- *
- * Note that the wallet only backs up validated denominations.
- */
-export interface BackupDenomination {
-  /**
-   * Value of one coin of the denomination.
-   */
-  value: BackupAmountString;
-
-  /**
-   * The denomination public key.
-   */
-  denom_pub: DenominationPubKey;
-
-  /**
-   * Fee for withdrawing.
-   */
-  fee_withdraw: BackupAmountString;
-
-  /**
-   * Fee for depositing.
-   */
-  fee_deposit: BackupAmountString;
-
-  /**
-   * Fee for refreshing.
-   */
-  fee_refresh: BackupAmountString;
-
-  /**
-   * Fee for refunding.
-   */
-  fee_refund: BackupAmountString;
-
-  /**
-   * Validity start date of the denomination.
-   */
-  stamp_start: TalerProtocolTimestamp;
-
-  /**
-   * Date after which the currency can't be withdrawn anymore.
-   */
-  stamp_expire_withdraw: TalerProtocolTimestamp;
-
-  /**
-   * Date after the denomination officially doesn't exist anymore.
-   */
-  stamp_expire_legal: TalerProtocolTimestamp;
-
-  /**
-   * Data after which coins of this denomination can't be deposited anymore.
-   */
-  stamp_expire_deposit: TalerProtocolTimestamp;
-
-  /**
-   * Signature by the exchange's master key over the denomination
-   * information.
-   */
-  master_sig: string;
-
-  /**
-   * Was this denomination still offered by the exchange the last time
-   * we checked?
-   * Only false when the exchange redacts a previously published denomination.
-   */
-  is_offered: boolean;
-
-  /**
-   * Did the exchange revoke the denomination?
-   * When this field is set to true in the database, the same transaction
-   * should also mark all affected coins as revoked.
-   */
-  is_revoked: boolean;
-
-  /**
-   * Coins of this denomination.
-   */
-  coins: BackupCoin[];
-
-  /**
-   * The list issue date of the exchange "/keys" response
-   * that this denomination was last seen in.
-   */
-  list_issue_date: TalerProtocolTimestamp;
-}
-
-/**
- * Denomination selection.
- */
-export type BackupDenomSel = {
-  denom_pub_hash: string;
-  count: number;
-}[];
-
-/**
- * Wire fee for one wire payment target type as stored in the
- * wallet's database.
- *
- * (Flattened to a list to make the declaration simpler).
- */
-export interface BackupExchangeWireFee {
-  wire_type: string;
-
-  /**
-   * Fee for wire transfers.
-   */
-  wire_fee: string;
-
-  /**
-   * Fees to close and refund a reserve.
-   */
-  closing_fee: string;
-
-  /**
-   * Start date of the fee.
-   */
-  start_stamp: TalerProtocolTimestamp;
-
-  /**
-   * End date of the fee.
-   */
-  end_stamp: TalerProtocolTimestamp;
-
-  /**
-   * Signature made by the exchange master key.
-   */
-  sig: string;
-}
-
-/**
- * Global fee as stored in the wallet's database.
- *
- */
-export interface BackupExchangeGlobalFees {
-  startDate: TalerProtocolTimestamp;
-  endDate: TalerProtocolTimestamp;
-
-  historyFee: BackupAmountString;
-  accountFee: BackupAmountString;
-  purseFee: BackupAmountString;
-
-  historyTimeout: TalerProtocolDuration;
-  purseTimeout: TalerProtocolDuration;
-
-  purseLimit: number;
-
-  signature: string;
-}
-/**
- * Structure of one exchange signing key in the /keys response.
- */
-export class BackupExchangeSignKey {
-  stamp_start: TalerProtocolTimestamp;
-  stamp_expire: TalerProtocolTimestamp;
-  stamp_end: TalerProtocolTimestamp;
-  key: string;
-  master_sig: string;
-}
-
-/**
- * Signature by the auditor that a particular denomination key is audited.
- */
-export class BackupAuditorDenomSig {
-  /**
-   * Denomination public key's hash.
-   */
-  denom_pub_h: string;
-
-  /**
-   * The signature.
-   */
-  auditor_sig: string;
-}
-
-/**
- * Auditor information as given by the exchange in /keys.
- */
-export class BackupExchangeAuditor {
-  /**
-   * Auditor's public key.
-   */
-  auditor_pub: string;
-
-  /**
-   * Base URL of the auditor.
-   */
-  auditor_url: string;
-
-  /**
-   * List of signatures for denominations by the auditor.
-   */
-  denomination_keys: BackupAuditorDenomSig[];
-}
-
-/**
- * Backup information for an exchange.  Serves effectively
- * as a pointer to the exchange details identified by
- * the base URL, master public key and currency.
- */
-export interface BackupExchange {
-  base_url: string;
-
-  master_public_key: string;
-
-  currency: string;
-
-  /**
-   * Time when the pointer to the exchange details
-   * was last updated.
-   *
-   * Used to facilitate automatic merging.
-   */
-  update_clock: TalerPreciseTimestamp;
-}
-
-/**
- * Backup information about an exchange's details.
- *
- * Note that one base URL can have multiple exchange
- * details.  The BackupExchange stores a pointer
- * to the current exchange details.
- */
-export interface BackupExchangeDetails {
-  /**
-   * Canonicalized base url of the exchange.
-   */
-  base_url: string;
-
-  /**
-   * Master public key of the exchange.
-   */
-  master_public_key: string;
-
-  /**
-   * Auditors (partially) auditing the exchange.
-   */
-  auditors: BackupExchangeAuditor[];
-
-  /**
-   * Currency that the exchange offers.
-   */
-  currency: string;
-
-  /**
-   * Denominations offered by the exchange.
-   */
-  denominations: BackupDenomination[];
-
-  /**
-   * Last observed protocol version.
-   */
-  protocol_version: string;
-
-  /**
-   * Closing delay of reserves.
-   */
-  reserve_closing_delay: TalerProtocolDuration;
-
-  /**
-   * Signing keys we got from the exchange, can also contain
-   * older signing keys that are not returned by /keys anymore.
-   */
-  signing_keys: BackupExchangeSignKey[];
-
-  wire_fees: BackupExchangeWireFee[];
-
-  global_fees: BackupExchangeGlobalFees[];
-
-  /**
-   * Bank accounts offered by the exchange;
-   */
-  accounts: {
-    payto_uri: string;
-    master_sig: string;
-  }[];
-
-  /**
-   * ETag for last terms of service download.
-   */
-  tos_accepted_etag: string | undefined;
-
-  /**
-   * Timestamp when the ToS has been accepted.
-   */
-  tos_accepted_timestamp: TalerPreciseTimestamp | undefined;
-}
-
-export enum BackupProposalStatus {
-  /**
-   * Proposed (and either downloaded or not,
-   * depending on whether contract terms are present),
-   * but the user needs to accept/reject it.
-   */
-  Proposed = "proposed",
-  /**
-   * Proposed, other wallet may also have
-   * the purchase
-   */
-  Shared = "shared",
-  /**
-   * The user has rejected the proposal.
-   */
-  Refused = "refused",
-  /**
-   * Downloading or processing the proposal has failed permanently.
-   *
-   * FIXME:  Should this be modeled as a "misbehavior report" instead?
-   */
-  PermanentlyFailed = "permanently-failed",
-  /**
-   * Downloaded proposal was detected as a re-purchase.
-   */
-  Repurchase = "repurchase",
-
-  Paid = "paid",
-}
-
 export interface BackupRecovery {
   walletRootPriv: string;
   providers: {
diff --git a/packages/taler-util/src/wallet-types.ts 
b/packages/taler-util/src/wallet-types.ts
index 01c1838d5..accab746f 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -2655,3 +2655,21 @@ export interface TransactionRecordFilter {
   onlyState?: TransactionStateFilter;
   onlyCurrency?: string;
 }
+
+export interface StoredBackupList {
+  storedBackups: {
+    name: string;
+  }[];
+}
+
+export interface CreateStoredBackupResponse {
+  name: string;
+}
+
+export interface RecoverStoredBackupRequest {
+  name: string;
+}
+
+export interface DeleteStoredBackupRequest {
+  name: string;
+}
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index e68385267..1255e8c71 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -2769,6 +2769,24 @@ export const walletMetadataStore = {
   ),
 };
 
+export interface StoredBackupMeta {
+  name: string;
+}
+
+export interface StoredBackupData {
+  name: string;
+  data: any;
+}
+
+export const StoredBackupStores = {
+  backupMeta: describeStore(
+    "backupMeta",
+    describeContents<MetaConfigRecord>({ keyPath: "name" }),
+    {},
+  ),
+  backupData: describeStore("backupData", describeContents<any>({}), {}),
+};
+
 export interface DbDumpRecord {
   /**
    * Key, serialized with structuredEncapsulated.
@@ -2831,6 +2849,7 @@ export async function exportSingleDb(
   return new Promise((resolve, reject) => {
     const tx = myDb.transaction(Array.from(myDb.objectStoreNames));
     tx.addEventListener("complete", () => {
+      myDb.close();
       resolve(singleDbDump);
     });
     // tslint:disable-next-line:prefer-for-of
@@ -3211,6 +3230,36 @@ function onMetaDbUpgradeNeeded(
   );
 }
 
+function onStoredBackupsDbUpgradeNeeded(
+  db: IDBDatabase,
+  oldVersion: number,
+  newVersion: number,
+  upgradeTransaction: IDBTransaction,
+) {
+  upgradeFromStoreMap(
+    StoredBackupStores,
+    db,
+    oldVersion,
+    newVersion,
+    upgradeTransaction,
+  );
+}
+
+export async function openStoredBackupsDatabase(
+  idbFactory: IDBFactory,
+): Promise<DbAccess<typeof StoredBackupStores>> {
+  const backupsDbHandle = await openDatabase(
+    idbFactory,
+    TALER_WALLET_META_DB_NAME,
+    1,
+    () => {},
+    onStoredBackupsDbUpgradeNeeded,
+  );
+
+  const handle = new DbAccess(backupsDbHandle, StoredBackupStores);
+  return handle;
+}
+
 /**
  * Return a promise that resolves
  * to the taler wallet db.
diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts 
b/packages/taler-wallet-core/src/operations/backup/export.ts
deleted file mode 100644
index c9446a05f..000000000
--- a/packages/taler-wallet-core/src/operations/backup/export.ts
+++ /dev/null
@@ -1,586 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2020 Taler Systems SA
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Implementation of wallet backups (export/import/upload) and sync
- * server management.
- *
- * @author Florian Dold <dold@taler.net>
- */
-
-/**
- * Imports.
- */
-import {
-  AbsoluteTime,
-  Amounts,
-  BackupBackupProvider,
-  BackupBackupProviderTerms,
-  BackupCoin,
-  BackupCoinSource,
-  BackupCoinSourceType,
-  BackupDenomination,
-  BackupExchange,
-  BackupExchangeDetails,
-  BackupExchangeSignKey,
-  BackupExchangeWireFee,
-  BackupOperationStatus,
-  BackupPayInfo,
-  BackupProposalStatus,
-  BackupPurchase,
-  BackupRecoupGroup,
-  BackupRefreshGroup,
-  BackupRefreshOldCoin,
-  BackupRefreshSession,
-  BackupRefundItem,
-  BackupRefundState,
-  BackupTip,
-  BackupWgInfo,
-  BackupWgType,
-  BackupWithdrawalGroup,
-  BACKUP_VERSION_MAJOR,
-  BACKUP_VERSION_MINOR,
-  canonicalizeBaseUrl,
-  canonicalJson,
-  CoinStatus,
-  encodeCrock,
-  getRandomBytes,
-  hash,
-  Logger,
-  stringToBytes,
-  WalletBackupContentV1,
-  TalerPreciseTimestamp,
-} from "@gnu-taler/taler-util";
-import {
-  CoinSourceType,
-  ConfigRecordKey,
-  DenominationRecord,
-  PurchaseStatus,
-  RefreshCoinStatus,
-  WithdrawalGroupStatus,
-  WithdrawalRecordType,
-} from "../../db.js";
-import { InternalWalletState } from "../../internal-wallet-state.js";
-import { assertUnreachable } from "../../util/assertUnreachable.js";
-import { checkDbInvariant } from "../../util/invariants.js";
-import { getWalletBackupState, provideBackupState } from "./state.js";
-
-const logger = new Logger("backup/export.ts");
-
-export async function exportBackup(
-  ws: InternalWalletState,
-): Promise<WalletBackupContentV1> {
-  await provideBackupState(ws);
-  return ws.db
-    .mktx((x) => [
-      x.config,
-      x.exchanges,
-      x.exchangeDetails,
-      x.exchangeSignKeys,
-      x.coins,
-      x.contractTerms,
-      x.denominations,
-      x.purchases,
-      x.refreshGroups,
-      x.backupProviders,
-      x.rewards,
-      x.recoupGroups,
-      x.withdrawalGroups,
-    ])
-    .runReadWrite(async (tx) => {
-      const bs = await getWalletBackupState(ws, tx);
-
-      const backupExchangeDetails: BackupExchangeDetails[] = [];
-      const backupExchanges: BackupExchange[] = [];
-      const backupCoinsByDenom: { [dph: string]: BackupCoin[] } = {};
-      const backupDenominationsByExchange: {
-        [url: string]: BackupDenomination[];
-      } = {};
-      const backupPurchases: BackupPurchase[] = [];
-      const backupRefreshGroups: BackupRefreshGroup[] = [];
-      const backupBackupProviders: BackupBackupProvider[] = [];
-      const backupTips: BackupTip[] = [];
-      const backupRecoupGroups: BackupRecoupGroup[] = [];
-      const backupWithdrawalGroups: BackupWithdrawalGroup[] = [];
-
-      await tx.withdrawalGroups.iter().forEachAsync(async (wg) => {
-        let info: BackupWgInfo;
-        switch (wg.wgInfo.withdrawalType) {
-          case WithdrawalRecordType.BankIntegrated:
-            info = {
-              type: BackupWgType.BankIntegrated,
-              exchange_payto_uri: wg.wgInfo.bankInfo.exchangePaytoUri,
-              taler_withdraw_uri: wg.wgInfo.bankInfo.talerWithdrawUri,
-              confirm_url: wg.wgInfo.bankInfo.confirmUrl,
-              timestamp_bank_confirmed:
-                wg.wgInfo.bankInfo.timestampBankConfirmed,
-              timestamp_reserve_info_posted:
-                wg.wgInfo.bankInfo.timestampReserveInfoPosted,
-            };
-            break;
-          case WithdrawalRecordType.BankManual:
-            info = {
-              type: BackupWgType.BankManual,
-            };
-            break;
-          case WithdrawalRecordType.PeerPullCredit:
-            info = {
-              type: BackupWgType.PeerPullCredit,
-              contract_priv: wg.wgInfo.contractPriv,
-              contract_terms: wg.wgInfo.contractTerms,
-            };
-            break;
-          case WithdrawalRecordType.PeerPushCredit:
-            info = {
-              type: BackupWgType.PeerPushCredit,
-              contract_terms: wg.wgInfo.contractTerms,
-            };
-            break;
-          case WithdrawalRecordType.Recoup:
-            info = {
-              type: BackupWgType.Recoup,
-            };
-            break;
-          default:
-            assertUnreachable(wg.wgInfo);
-        }
-        backupWithdrawalGroups.push({
-          raw_withdrawal_amount: Amounts.stringify(wg.rawWithdrawalAmount),
-          info,
-          timestamp_created: wg.timestampStart,
-          timestamp_finish: wg.timestampFinish,
-          withdrawal_group_id: wg.withdrawalGroupId,
-          secret_seed: wg.secretSeed,
-          exchange_base_url: wg.exchangeBaseUrl,
-          instructed_amount: Amounts.stringify(wg.instructedAmount),
-          effective_withdrawal_amount: Amounts.stringify(
-            wg.effectiveWithdrawalAmount,
-          ),
-          reserve_priv: wg.reservePriv,
-          restrict_age: wg.restrictAge,
-          // FIXME: proper status conversion!
-          operation_status:
-            wg.status == WithdrawalGroupStatus.Finished
-              ? BackupOperationStatus.Finished
-              : BackupOperationStatus.Pending,
-          selected_denoms_uid: wg.denomSelUid,
-          selected_denoms: wg.denomsSel.selectedDenoms.map((x) => ({
-            count: x.count,
-            denom_pub_hash: x.denomPubHash,
-          })),
-        });
-      });
-
-      await tx.rewards.iter().forEach((tip) => {
-        backupTips.push({
-          exchange_base_url: tip.exchangeBaseUrl,
-          merchant_base_url: tip.merchantBaseUrl,
-          merchant_tip_id: tip.merchantRewardId,
-          wallet_tip_id: tip.walletRewardId,
-          next_url: tip.next_url,
-          secret_seed: tip.secretSeed,
-          selected_denoms: tip.denomsSel.selectedDenoms.map((x) => ({
-            count: x.count,
-            denom_pub_hash: x.denomPubHash,
-          })),
-          timestamp_finished: tip.pickedUpTimestamp,
-          timestamp_accepted: tip.acceptedTimestamp,
-          timestamp_created: tip.createdTimestamp,
-          timestamp_expiration: tip.rewardExpiration,
-          tip_amount_raw: Amounts.stringify(tip.rewardAmountRaw),
-          selected_denoms_uid: tip.denomSelUid,
-        });
-      });
-
-      await tx.recoupGroups.iter().forEach((recoupGroup) => {
-        backupRecoupGroups.push({
-          recoup_group_id: recoupGroup.recoupGroupId,
-          timestamp_created: recoupGroup.timestampStarted,
-          timestamp_finish: recoupGroup.timestampFinished,
-          coins: recoupGroup.coinPubs.map((x, i) => ({
-            coin_pub: x,
-            recoup_finished: recoupGroup.recoupFinishedPerCoin[i],
-          })),
-        });
-      });
-
-      await tx.backupProviders.iter().forEach((bp) => {
-        let terms: BackupBackupProviderTerms | undefined;
-        if (bp.terms) {
-          terms = {
-            annual_fee: Amounts.stringify(bp.terms.annualFee),
-            storage_limit_in_megabytes: bp.terms.storageLimitInMegabytes,
-            supported_protocol_version: bp.terms.supportedProtocolVersion,
-          };
-        }
-        backupBackupProviders.push({
-          terms,
-          base_url: canonicalizeBaseUrl(bp.baseUrl),
-          pay_proposal_ids: bp.paymentProposalIds,
-          uids: bp.uids,
-        });
-      });
-
-      await tx.coins.iter().forEach((coin) => {
-        let bcs: BackupCoinSource;
-        switch (coin.coinSource.type) {
-          case CoinSourceType.Refresh:
-            bcs = {
-              type: BackupCoinSourceType.Refresh,
-              old_coin_pub: coin.coinSource.oldCoinPub,
-              refresh_group_id: coin.coinSource.refreshGroupId,
-            };
-            break;
-          case CoinSourceType.Reward:
-            bcs = {
-              type: BackupCoinSourceType.Reward,
-              coin_index: coin.coinSource.coinIndex,
-              wallet_tip_id: coin.coinSource.walletRewardId,
-            };
-            break;
-          case CoinSourceType.Withdraw:
-            bcs = {
-              type: BackupCoinSourceType.Withdraw,
-              coin_index: coin.coinSource.coinIndex,
-              reserve_pub: coin.coinSource.reservePub,
-              withdrawal_group_id: coin.coinSource.withdrawalGroupId,
-            };
-            break;
-        }
-
-        const coins = (backupCoinsByDenom[coin.denomPubHash] ??= []);
-        coins.push({
-          blinding_key: coin.blindingKey,
-          coin_priv: coin.coinPriv,
-          coin_source: bcs,
-          fresh: coin.status === CoinStatus.Fresh,
-          spend_allocation: coin.spendAllocation
-            ? {
-                amount: coin.spendAllocation.amount,
-                id: coin.spendAllocation.id,
-              }
-            : undefined,
-          denom_sig: coin.denomSig,
-        });
-      });
-
-      await tx.denominations.iter().forEach((denom) => {
-        const backupDenoms = (backupDenominationsByExchange[
-          denom.exchangeBaseUrl
-        ] ??= []);
-        backupDenoms.push({
-          coins: backupCoinsByDenom[denom.denomPubHash] ?? [],
-          denom_pub: denom.denomPub,
-          fee_deposit: Amounts.stringify(denom.fees.feeDeposit),
-          fee_refresh: Amounts.stringify(denom.fees.feeRefresh),
-          fee_refund: Amounts.stringify(denom.fees.feeRefund),
-          fee_withdraw: Amounts.stringify(denom.fees.feeWithdraw),
-          is_offered: denom.isOffered,
-          is_revoked: denom.isRevoked,
-          master_sig: denom.masterSig,
-          stamp_expire_deposit: denom.stampExpireDeposit,
-          stamp_expire_legal: denom.stampExpireLegal,
-          stamp_expire_withdraw: denom.stampExpireWithdraw,
-          stamp_start: denom.stampStart,
-          value: Amounts.stringify(DenominationRecord.getValue(denom)),
-          list_issue_date: denom.listIssueDate,
-        });
-      });
-
-      await tx.exchanges.iter().forEachAsync(async (ex) => {
-        const dp = ex.detailsPointer;
-        if (!dp) {
-          return;
-        }
-        backupExchanges.push({
-          base_url: ex.baseUrl,
-          currency: dp.currency,
-          master_public_key: dp.masterPublicKey,
-          update_clock: dp.updateClock,
-        });
-      });
-
-      await tx.exchangeDetails.iter().forEachAsync(async (ex) => {
-        // Only back up permanently added exchanges.
-
-        const wi = ex.wireInfo;
-        const wireFees: BackupExchangeWireFee[] = [];
-
-        Object.keys(wi.feesForType).forEach((x) => {
-          for (const f of wi.feesForType[x]) {
-            wireFees.push({
-              wire_type: x,
-              closing_fee: Amounts.stringify(f.closingFee),
-              end_stamp: f.endStamp,
-              sig: f.sig,
-              start_stamp: f.startStamp,
-              wire_fee: Amounts.stringify(f.wireFee),
-            });
-          }
-        });
-        checkDbInvariant(ex.rowId != null);
-        const exchangeSk =
-          await tx.exchangeSignKeys.indexes.byExchangeDetailsRowId.getAll(
-            ex.rowId,
-          );
-        let signingKeys: BackupExchangeSignKey[] = exchangeSk.map((x) => ({
-          key: x.signkeyPub,
-          master_sig: x.masterSig,
-          stamp_end: x.stampEnd,
-          stamp_expire: x.stampExpire,
-          stamp_start: x.stampStart,
-        }));
-
-        backupExchangeDetails.push({
-          base_url: ex.exchangeBaseUrl,
-          reserve_closing_delay: ex.reserveClosingDelay,
-          accounts: ex.wireInfo.accounts.map((x) => ({
-            payto_uri: x.payto_uri,
-            master_sig: x.master_sig,
-          })),
-          auditors: ex.auditors.map((x) => ({
-            auditor_pub: x.auditor_pub,
-            auditor_url: x.auditor_url,
-            denomination_keys: x.denomination_keys,
-          })),
-          master_public_key: ex.masterPublicKey,
-          currency: ex.currency,
-          protocol_version: ex.protocolVersionRange,
-          wire_fees: wireFees,
-          signing_keys: signingKeys,
-          global_fees: ex.globalFees.map((x) => ({
-            accountFee: Amounts.stringify(x.accountFee),
-            historyFee: Amounts.stringify(x.historyFee),
-            purseFee: Amounts.stringify(x.purseFee),
-            endDate: x.endDate,
-            historyTimeout: x.historyTimeout,
-            signature: x.signature,
-            purseLimit: x.purseLimit,
-            purseTimeout: x.purseTimeout,
-            startDate: x.startDate,
-          })),
-          tos_accepted_etag: ex.tosAccepted?.etag,
-          tos_accepted_timestamp: ex.tosAccepted?.timestamp,
-          denominations:
-            backupDenominationsByExchange[ex.exchangeBaseUrl] ?? [],
-        });
-      });
-
-      const purchaseProposalIdSet = new Set<string>();
-
-      await tx.purchases.iter().forEachAsync(async (purch) => {
-        const refunds: BackupRefundItem[] = [];
-        purchaseProposalIdSet.add(purch.proposalId);
-        // for (const refundKey of Object.keys(purch.refunds)) {
-        //   const ri = purch.refunds[refundKey];
-        //   const common = {
-        //     coin_pub: ri.coinPub,
-        //     execution_time: ri.executionTime,
-        //     obtained_time: ri.obtainedTime,
-        //     refund_amount: Amounts.stringify(ri.refundAmount),
-        //     rtransaction_id: ri.rtransactionId,
-        //     total_refresh_cost_bound: Amounts.stringify(
-        //       ri.totalRefreshCostBound,
-        //     ),
-        //   };
-        //   switch (ri.type) {
-        //     case RefundState.Applied:
-        //       refunds.push({ type: BackupRefundState.Applied, ...common });
-        //       break;
-        //     case RefundState.Failed:
-        //       refunds.push({ type: BackupRefundState.Failed, ...common });
-        //       break;
-        //     case RefundState.Pending:
-        //       refunds.push({ type: BackupRefundState.Pending, ...common });
-        //       break;
-        //   }
-        // }
-
-        let propStatus: BackupProposalStatus;
-        switch (purch.purchaseStatus) {
-          case PurchaseStatus.Done:
-          case PurchaseStatus.PendingQueryingAutoRefund:
-          case PurchaseStatus.PendingQueryingRefund:
-            propStatus = BackupProposalStatus.Paid;
-            break;
-          case PurchaseStatus.PendingPayingReplay:
-          case PurchaseStatus.PendingDownloadingProposal:
-          case PurchaseStatus.DialogProposed:
-          case PurchaseStatus.PendingPaying:
-            propStatus = BackupProposalStatus.Proposed;
-            break;
-          case PurchaseStatus.DialogShared:
-            propStatus = BackupProposalStatus.Shared;
-            break;
-          case PurchaseStatus.FailedClaim:
-          case PurchaseStatus.AbortedIncompletePayment:
-            propStatus = BackupProposalStatus.PermanentlyFailed;
-            break;
-          case PurchaseStatus.AbortingWithRefund:
-          case PurchaseStatus.AbortedProposalRefused:
-            propStatus = BackupProposalStatus.Refused;
-            break;
-          case PurchaseStatus.RepurchaseDetected:
-            propStatus = BackupProposalStatus.Repurchase;
-            break;
-          default: {
-            const error = purch.purchaseStatus;
-            throw Error(`purchase status ${error} is not handled`);
-          }
-        }
-
-        const payInfo = purch.payInfo;
-        let backupPayInfo: BackupPayInfo | undefined = undefined;
-        if (payInfo) {
-          backupPayInfo = {
-            pay_coins: payInfo.payCoinSelection.coinPubs.map((x, i) => ({
-              coin_pub: x,
-              contribution: Amounts.stringify(
-                payInfo.payCoinSelection.coinContributions[i],
-              ),
-            })),
-            total_pay_cost: Amounts.stringify(payInfo.totalPayCost),
-            pay_coins_uid: payInfo.payCoinSelectionUid,
-          };
-        }
-
-        let contractTermsRaw = undefined;
-        if (purch.download) {
-          const contractTermsRecord = await tx.contractTerms.get(
-            purch.download.contractTermsHash,
-          );
-          if (contractTermsRecord) {
-            contractTermsRaw = contractTermsRecord.contractTermsRaw;
-          }
-        }
-
-        backupPurchases.push({
-          contract_terms_raw: contractTermsRaw,
-          auto_refund_deadline: purch.autoRefundDeadline,
-          merchant_pay_sig: purch.merchantPaySig,
-          pos_confirmation: purch.posConfirmation,
-          pay_info: backupPayInfo,
-          proposal_id: purch.proposalId,
-          refunds,
-          timestamp_accepted: purch.timestampAccept,
-          timestamp_first_successful_pay: purch.timestampFirstSuccessfulPay,
-          nonce_priv: purch.noncePriv,
-          merchant_sig: purch.download?.contractTermsMerchantSig,
-          claim_token: purch.claimToken,
-          merchant_base_url: purch.merchantBaseUrl,
-          order_id: purch.orderId,
-          proposal_status: propStatus,
-          repurchase_proposal_id: purch.repurchaseProposalId,
-          download_session_id: purch.downloadSessionId,
-          timestamp_proposed: purch.timestamp,
-          shared: purch.shared,
-        });
-      });
-
-      await tx.refreshGroups.iter().forEach((rg) => {
-        const oldCoins: BackupRefreshOldCoin[] = [];
-
-        for (let i = 0; i < rg.oldCoinPubs.length; i++) {
-          let refreshSession: BackupRefreshSession | undefined;
-          const s = rg.refreshSessionPerCoin[i];
-          if (s) {
-            refreshSession = {
-              new_denoms: s.newDenoms.map((x) => ({
-                count: x.count,
-                denom_pub_hash: x.denomPubHash,
-              })),
-              session_secret_seed: s.sessionSecretSeed,
-              noreveal_index: s.norevealIndex,
-            };
-          }
-          oldCoins.push({
-            coin_pub: rg.oldCoinPubs[i],
-            estimated_output_amount: Amounts.stringify(
-              rg.estimatedOutputPerCoin[i],
-            ),
-            finished: rg.statusPerCoin[i] === RefreshCoinStatus.Finished,
-            input_amount: Amounts.stringify(rg.inputPerCoin[i]),
-            refresh_session: refreshSession,
-          });
-        }
-
-        backupRefreshGroups.push({
-          reason: rg.reason as any,
-          refresh_group_id: rg.refreshGroupId,
-          timestamp_created: rg.timestampCreated,
-          timestamp_finish: rg.timestampFinished,
-          old_coins: oldCoins,
-        });
-      });
-
-      const ts = TalerPreciseTimestamp.now();
-
-      if (!bs.lastBackupTimestamp) {
-        bs.lastBackupTimestamp = ts;
-      }
-
-      const backupBlob: WalletBackupContentV1 = {
-        schema_id: "gnu-taler-wallet-backup-content",
-        schema_version: BACKUP_VERSION_MAJOR,
-        minor_version: BACKUP_VERSION_MINOR,
-        exchanges: backupExchanges,
-        exchange_details: backupExchangeDetails,
-        wallet_root_pub: bs.walletRootPub,
-        backup_providers: backupBackupProviders,
-        current_device_id: bs.deviceId,
-        purchases: backupPurchases,
-        recoup_groups: backupRecoupGroups,
-        refresh_groups: backupRefreshGroups,
-        tips: backupTips,
-        timestamp: bs.lastBackupTimestamp,
-        trusted_auditors: {},
-        trusted_exchanges: {},
-        intern_table: {},
-        error_reports: [],
-        tombstones: [],
-        // FIXME!
-        withdrawal_groups: backupWithdrawalGroups,
-      };
-
-      // If the backup changed, we change our nonce and timestamp.
-
-      let h = encodeCrock(hash(stringToBytes(canonicalJson(backupBlob))));
-      if (h !== bs.lastBackupPlainHash) {
-        logger.trace(
-          `plain backup hash changed (from ${bs.lastBackupPlainHash}to ${h})`,
-        );
-        bs.lastBackupTimestamp = ts;
-        backupBlob.timestamp = ts;
-        bs.lastBackupPlainHash = encodeCrock(
-          hash(stringToBytes(canonicalJson(backupBlob))),
-        );
-        bs.lastBackupNonce = encodeCrock(getRandomBytes(32));
-        logger.trace(
-          `setting timestamp to ${AbsoluteTime.toIsoString(
-            AbsoluteTime.fromPreciseTimestamp(ts),
-          )} and nonce to ${bs.lastBackupNonce}`,
-        );
-        await tx.config.put({
-          key: ConfigRecordKey.WalletBackupState,
-          value: bs,
-        });
-      } else {
-        logger.trace("backup hash did not change");
-      }
-
-      return backupBlob;
-    });
-}
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts 
b/packages/taler-wallet-core/src/operations/backup/import.ts
deleted file mode 100644
index 836c65643..000000000
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ /dev/null
@@ -1,874 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2020 Taler Systems SA
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-import {
-  AgeRestriction,
-  AmountJson,
-  Amounts,
-  BackupCoin,
-  BackupCoinSourceType,
-  BackupDenomSel,
-  BackupPayInfo,
-  BackupProposalStatus,
-  BackupRefreshReason,
-  BackupRefundState,
-  BackupWgType,
-  codecForMerchantContractTerms,
-  CoinStatus,
-  DenomKeyType,
-  DenomSelectionState,
-  j2s,
-  Logger,
-  PayCoinSelection,
-  RefreshReason,
-  TalerProtocolTimestamp,
-  TalerPreciseTimestamp,
-  WalletBackupContentV1,
-  WireInfo,
-} from "@gnu-taler/taler-util";
-import {
-  CoinRecord,
-  CoinSource,
-  CoinSourceType,
-  DenominationRecord,
-  DenominationVerificationStatus,
-  ProposalDownloadInfo,
-  PurchaseStatus,
-  PurchasePayInfo,
-  RefreshCoinStatus,
-  RefreshSessionRecord,
-  WalletContractData,
-  WalletStoresV1,
-  WgInfo,
-  WithdrawalGroupStatus,
-  WithdrawalRecordType,
-  RefreshOperationStatus,
-  RewardRecordStatus,
-} from "../../db.js";
-import { InternalWalletState } from "../../internal-wallet-state.js";
-import { assertUnreachable } from "../../util/assertUnreachable.js";
-import { checkLogicInvariant } from "../../util/invariants.js";
-import { GetReadOnlyAccess, GetReadWriteAccess } from "../../util/query.js";
-import {
-  constructTombstone,
-  makeCoinAvailable,
-  TombstoneTag,
-} from "../common.js";
-import { getExchangeDetails } from "../exchanges.js";
-import { extractContractData } from "../pay-merchant.js";
-import { provideBackupState } from "./state.js";
-
-const logger = new Logger("operations/backup/import.ts");
-
-function checkBackupInvariant(b: boolean, m?: string): asserts b {
-  if (!b) {
-    if (m) {
-      throw Error(`BUG: backup invariant failed (${m})`);
-    } else {
-      throw Error("BUG: backup invariant failed");
-    }
-  }
-}
-
-/**
- * Re-compute information about the coin selection for a payment.
- */
-async function recoverPayCoinSelection(
-  tx: GetReadWriteAccess<{
-    exchanges: typeof WalletStoresV1.exchanges;
-    exchangeDetails: typeof WalletStoresV1.exchangeDetails;
-    coins: typeof WalletStoresV1.coins;
-    denominations: typeof WalletStoresV1.denominations;
-  }>,
-  contractData: WalletContractData,
-  payInfo: BackupPayInfo,
-): Promise<PayCoinSelection> {
-  const coinPubs: string[] = payInfo.pay_coins.map((x) => x.coin_pub);
-  const coinContributions: AmountJson[] = payInfo.pay_coins.map((x) =>
-    Amounts.parseOrThrow(x.contribution),
-  );
-
-  const coveredExchanges: Set<string> = new Set();
-
-  let totalWireFee: AmountJson = Amounts.zeroOfAmount(contractData.amount);
-  let totalDepositFees: AmountJson = Amounts.zeroOfAmount(contractData.amount);
-
-  for (const coinPub of coinPubs) {
-    const coinRecord = await tx.coins.get(coinPub);
-    checkBackupInvariant(!!coinRecord);
-    const denom = await tx.denominations.get([
-      coinRecord.exchangeBaseUrl,
-      coinRecord.denomPubHash,
-    ]);
-    checkBackupInvariant(!!denom);
-    totalDepositFees = Amounts.add(
-      totalDepositFees,
-      denom.fees.feeDeposit,
-    ).amount;
-
-    if (!coveredExchanges.has(coinRecord.exchangeBaseUrl)) {
-      const exchangeDetails = await getExchangeDetails(
-        tx,
-        coinRecord.exchangeBaseUrl,
-      );
-      checkBackupInvariant(!!exchangeDetails);
-      let wireFee: AmountJson | undefined;
-      const feesForType = exchangeDetails.wireInfo.feesForType;
-      checkBackupInvariant(!!feesForType);
-      for (const fee of feesForType[contractData.wireMethod] || []) {
-        if (
-          fee.startStamp <= contractData.timestamp &&
-          fee.endStamp >= contractData.timestamp
-        ) {
-          wireFee = Amounts.parseOrThrow(fee.wireFee);
-          break;
-        }
-      }
-      if (wireFee) {
-        totalWireFee = Amounts.add(totalWireFee, wireFee).amount;
-      }
-      coveredExchanges.add(coinRecord.exchangeBaseUrl);
-    }
-  }
-
-  let customerWireFee: AmountJson;
-
-  const amortizedWireFee = Amounts.divide(
-    totalWireFee,
-    contractData.wireFeeAmortization,
-  );
-  if (Amounts.cmp(contractData.maxWireFee, amortizedWireFee) < 0) {
-    customerWireFee = amortizedWireFee;
-  } else {
-    customerWireFee = Amounts.zeroOfAmount(contractData.amount);
-  }
-
-  const customerDepositFees = Amounts.sub(
-    totalDepositFees,
-    contractData.maxDepositFee,
-  ).amount;
-
-  return {
-    coinPubs,
-    coinContributions: coinContributions.map((x) => Amounts.stringify(x)),
-    paymentAmount: Amounts.stringify(contractData.amount),
-    customerWireFees: Amounts.stringify(customerWireFee),
-    customerDepositFees: Amounts.stringify(customerDepositFees),
-  };
-}
-
-async function getDenomSelStateFromBackup(
-  tx: GetReadOnlyAccess<{ denominations: typeof WalletStoresV1.denominations 
}>,
-  currency: string,
-  exchangeBaseUrl: string,
-  sel: BackupDenomSel,
-): Promise<DenomSelectionState> {
-  const selectedDenoms: {
-    denomPubHash: string;
-    count: number;
-  }[] = [];
-  let totalCoinValue = Amounts.zeroOfCurrency(currency);
-  let totalWithdrawCost = Amounts.zeroOfCurrency(currency);
-  for (const s of sel) {
-    const d = await tx.denominations.get([exchangeBaseUrl, s.denom_pub_hash]);
-    checkBackupInvariant(!!d);
-    totalCoinValue = Amounts.add(
-      totalCoinValue,
-      DenominationRecord.getValue(d),
-    ).amount;
-    totalWithdrawCost = Amounts.add(
-      totalWithdrawCost,
-      DenominationRecord.getValue(d),
-      d.fees.feeWithdraw,
-    ).amount;
-  }
-  return {
-    selectedDenoms,
-    totalCoinValue: Amounts.stringify(totalCoinValue),
-    totalWithdrawCost: Amounts.stringify(totalWithdrawCost),
-  };
-}
-
-export interface CompletedCoin {
-  coinPub: string;
-  coinEvHash: string;
-}
-
-/**
- * Precomputed cryptographic material for a backup import.
- *
- * We separate this data from the backup blob as we want the backup
- * blob to be small, and we can't compute it during the database transaction,
- * as the async crypto worker communication would auto-close the database 
transaction.
- */
-export interface BackupCryptoPrecomputedData {
-  rsaDenomPubToHash: Record<string, string>;
-  coinPrivToCompletedCoin: Record<string, CompletedCoin>;
-  proposalNoncePrivToPub: { [priv: string]: string };
-  proposalIdToContractTermsHash: { [proposalId: string]: string };
-  reservePrivToPub: Record<string, string>;
-}
-
-export async function importCoin(
-  ws: InternalWalletState,
-  tx: GetReadWriteAccess<{
-    coins: typeof WalletStoresV1.coins;
-    coinAvailability: typeof WalletStoresV1.coinAvailability;
-    denominations: typeof WalletStoresV1.denominations;
-  }>,
-  cryptoComp: BackupCryptoPrecomputedData,
-  args: {
-    backupCoin: BackupCoin;
-    exchangeBaseUrl: string;
-    denomPubHash: string;
-  },
-): Promise<void> {
-  const { backupCoin, exchangeBaseUrl, denomPubHash } = args;
-  const compCoin = cryptoComp.coinPrivToCompletedCoin[backupCoin.coin_priv];
-  checkLogicInvariant(!!compCoin);
-  const existingCoin = await tx.coins.get(compCoin.coinPub);
-  if (!existingCoin) {
-    let coinSource: CoinSource;
-    switch (backupCoin.coin_source.type) {
-      case BackupCoinSourceType.Refresh:
-        coinSource = {
-          type: CoinSourceType.Refresh,
-          oldCoinPub: backupCoin.coin_source.old_coin_pub,
-          refreshGroupId: backupCoin.coin_source.refresh_group_id,
-        };
-        break;
-      case BackupCoinSourceType.Reward:
-        coinSource = {
-          type: CoinSourceType.Reward,
-          coinIndex: backupCoin.coin_source.coin_index,
-          walletRewardId: backupCoin.coin_source.wallet_tip_id,
-        };
-        break;
-      case BackupCoinSourceType.Withdraw:
-        coinSource = {
-          type: CoinSourceType.Withdraw,
-          coinIndex: backupCoin.coin_source.coin_index,
-          reservePub: backupCoin.coin_source.reserve_pub,
-          withdrawalGroupId: backupCoin.coin_source.withdrawal_group_id,
-        };
-        break;
-    }
-    const coinRecord: CoinRecord = {
-      blindingKey: backupCoin.blinding_key,
-      coinEvHash: compCoin.coinEvHash,
-      coinPriv: backupCoin.coin_priv,
-      denomSig: backupCoin.denom_sig,
-      coinPub: compCoin.coinPub,
-      exchangeBaseUrl,
-      denomPubHash,
-      status: backupCoin.fresh ? CoinStatus.Fresh : CoinStatus.Dormant,
-      coinSource,
-      // FIXME!
-      maxAge: AgeRestriction.AGE_UNRESTRICTED,
-      // FIXME!
-      ageCommitmentProof: undefined,
-      // FIXME!
-      spendAllocation: undefined,
-    };
-    if (coinRecord.status === CoinStatus.Fresh) {
-      await makeCoinAvailable(ws, tx, coinRecord);
-    } else {
-      await tx.coins.put(coinRecord);
-    }
-  }
-}
-
-export async function importBackup(
-  ws: InternalWalletState,
-  backupBlobArg: any,
-  cryptoComp: BackupCryptoPrecomputedData,
-): Promise<void> {
-  await provideBackupState(ws);
-
-  logger.info(`importing backup ${j2s(backupBlobArg)}`);
-
-  return ws.db
-    .mktx((x) => [
-      x.config,
-      x.exchangeDetails,
-      x.exchanges,
-      x.coins,
-      x.coinAvailability,
-      x.denominations,
-      x.purchases,
-      x.refreshGroups,
-      x.backupProviders,
-      x.rewards,
-      x.recoupGroups,
-      x.withdrawalGroups,
-      x.tombstones,
-      x.depositGroups,
-    ])
-    .runReadWrite(async (tx) => {
-      // FIXME: validate schema!
-      const backupBlob = backupBlobArg as WalletBackupContentV1;
-
-      // FIXME: validate version
-
-      for (const tombstone of backupBlob.tombstones) {
-        await tx.tombstones.put({
-          id: tombstone,
-        });
-      }
-
-      const tombstoneSet = new Set(
-        (await tx.tombstones.iter().toArray()).map((x) => x.id),
-      );
-
-      // FIXME:  Validate that the "details pointer" is correct
-
-      for (const backupExchange of backupBlob.exchanges) {
-        const existingExchange = await tx.exchanges.get(
-          backupExchange.base_url,
-        );
-        if (existingExchange) {
-          continue;
-        }
-        // await tx.exchanges.put({
-        //   baseUrl: backupExchange.base_url,
-        //   detailsPointer: {
-        //     currency: backupExchange.currency,
-        //     masterPublicKey: backupExchange.master_public_key,
-        //     updateClock: backupExchange.update_clock,
-        //   },
-        //   lastUpdate: undefined,
-        //   nextUpdate: TalerPreciseTimestamp.now(),
-        //   nextRefreshCheck: TalerPreciseTimestamp.now(),
-        //   lastKeysEtag: undefined,
-        //   lastWireEtag: undefined,
-        // });
-      }
-
-      for (const backupExchangeDetails of backupBlob.exchange_details) {
-        const existingExchangeDetails =
-          await tx.exchangeDetails.indexes.byPointer.get([
-            backupExchangeDetails.base_url,
-            backupExchangeDetails.currency,
-            backupExchangeDetails.master_public_key,
-          ]);
-
-        if (!existingExchangeDetails) {
-          const wireInfo: WireInfo = {
-            accounts: backupExchangeDetails.accounts.map((x) => ({
-              master_sig: x.master_sig,
-              payto_uri: x.payto_uri,
-            })),
-            feesForType: {},
-          };
-          for (const fee of backupExchangeDetails.wire_fees) {
-            const w = (wireInfo.feesForType[fee.wire_type] ??= []);
-            w.push({
-              closingFee: Amounts.stringify(fee.closing_fee),
-              endStamp: fee.end_stamp,
-              sig: fee.sig,
-              startStamp: fee.start_stamp,
-              wireFee: Amounts.stringify(fee.wire_fee),
-            });
-          }
-          let tosAccepted = undefined;
-          if (
-            backupExchangeDetails.tos_accepted_etag &&
-            backupExchangeDetails.tos_accepted_timestamp
-          ) {
-            tosAccepted = {
-              etag: backupExchangeDetails.tos_accepted_etag,
-              timestamp: backupExchangeDetails.tos_accepted_timestamp,
-            };
-          }
-          await tx.exchangeDetails.put({
-            exchangeBaseUrl: backupExchangeDetails.base_url,
-            wireInfo,
-            currency: backupExchangeDetails.currency,
-            auditors: backupExchangeDetails.auditors.map((x) => ({
-              auditor_pub: x.auditor_pub,
-              auditor_url: x.auditor_url,
-              denomination_keys: x.denomination_keys,
-            })),
-            masterPublicKey: backupExchangeDetails.master_public_key,
-            protocolVersionRange: backupExchangeDetails.protocol_version,
-            reserveClosingDelay: backupExchangeDetails.reserve_closing_delay,
-            tosCurrentEtag: backupExchangeDetails.tos_accepted_etag || "",
-            tosAccepted,
-            globalFees: backupExchangeDetails.global_fees.map((x) => ({
-              accountFee: Amounts.stringify(x.accountFee),
-              historyFee: Amounts.stringify(x.historyFee),
-              purseFee: Amounts.stringify(x.purseFee),
-              endDate: x.endDate,
-              historyTimeout: x.historyTimeout,
-              signature: x.signature,
-              purseLimit: x.purseLimit,
-              purseTimeout: x.purseTimeout,
-              startDate: x.startDate,
-            })),
-          });
-        }
-
-        for (const backupDenomination of backupExchangeDetails.denominations) {
-          if (backupDenomination.denom_pub.cipher !== DenomKeyType.Rsa) {
-            throw Error("unsupported cipher");
-          }
-          const denomPubHash =
-            cryptoComp.rsaDenomPubToHash[
-              backupDenomination.denom_pub.rsa_public_key
-            ];
-          checkLogicInvariant(!!denomPubHash);
-          const existingDenom = await tx.denominations.get([
-            backupExchangeDetails.base_url,
-            denomPubHash,
-          ]);
-          if (!existingDenom) {
-            const value = Amounts.parseOrThrow(backupDenomination.value);
-
-            await tx.denominations.put({
-              denomPub: backupDenomination.denom_pub,
-              denomPubHash: denomPubHash,
-              exchangeBaseUrl: backupExchangeDetails.base_url,
-              exchangeMasterPub: backupExchangeDetails.master_public_key,
-              fees: {
-                feeDeposit: Amounts.stringify(backupDenomination.fee_deposit),
-                feeRefresh: Amounts.stringify(backupDenomination.fee_refresh),
-                feeRefund: Amounts.stringify(backupDenomination.fee_refund),
-                feeWithdraw: 
Amounts.stringify(backupDenomination.fee_withdraw),
-              },
-              isOffered: backupDenomination.is_offered,
-              isRevoked: backupDenomination.is_revoked,
-              masterSig: backupDenomination.master_sig,
-              stampExpireDeposit: backupDenomination.stamp_expire_deposit,
-              stampExpireLegal: backupDenomination.stamp_expire_legal,
-              stampExpireWithdraw: backupDenomination.stamp_expire_withdraw,
-              stampStart: backupDenomination.stamp_start,
-              verificationStatus: DenominationVerificationStatus.VerifiedGood,
-              currency: value.currency,
-              amountFrac: value.fraction,
-              amountVal: value.value,
-              listIssueDate: backupDenomination.list_issue_date,
-            });
-          }
-          for (const backupCoin of backupDenomination.coins) {
-            await importCoin(ws, tx, cryptoComp, {
-              backupCoin,
-              denomPubHash,
-              exchangeBaseUrl: backupExchangeDetails.base_url,
-            });
-          }
-        }
-      }
-
-      for (const backupWg of backupBlob.withdrawal_groups) {
-        const reservePub = cryptoComp.reservePrivToPub[backupWg.reserve_priv];
-        checkLogicInvariant(!!reservePub);
-        const ts = constructTombstone({
-          tag: TombstoneTag.DeleteReserve,
-          reservePub,
-        });
-        if (tombstoneSet.has(ts)) {
-          continue;
-        }
-        const existingWg = await tx.withdrawalGroups.get(
-          backupWg.withdrawal_group_id,
-        );
-        if (existingWg) {
-          continue;
-        }
-        let wgInfo: WgInfo;
-        switch (backupWg.info.type) {
-          case BackupWgType.BankIntegrated:
-            wgInfo = {
-              withdrawalType: WithdrawalRecordType.BankIntegrated,
-              bankInfo: {
-                exchangePaytoUri: backupWg.info.exchange_payto_uri,
-                talerWithdrawUri: backupWg.info.taler_withdraw_uri,
-                confirmUrl: backupWg.info.confirm_url,
-                timestampBankConfirmed: backupWg.info.timestamp_bank_confirmed,
-                timestampReserveInfoPosted:
-                  backupWg.info.timestamp_reserve_info_posted,
-              },
-            };
-            break;
-          case BackupWgType.BankManual:
-            wgInfo = {
-              withdrawalType: WithdrawalRecordType.BankManual,
-            };
-            break;
-          case BackupWgType.PeerPullCredit:
-            wgInfo = {
-              withdrawalType: WithdrawalRecordType.PeerPullCredit,
-              contractTerms: backupWg.info.contract_terms,
-              contractPriv: backupWg.info.contract_priv,
-            };
-            break;
-          case BackupWgType.PeerPushCredit:
-            wgInfo = {
-              withdrawalType: WithdrawalRecordType.PeerPushCredit,
-              contractTerms: backupWg.info.contract_terms,
-            };
-            break;
-          case BackupWgType.Recoup:
-            wgInfo = {
-              withdrawalType: WithdrawalRecordType.Recoup,
-            };
-            break;
-          default:
-            assertUnreachable(backupWg.info);
-        }
-        const instructedAmount = Amounts.parseOrThrow(
-          backupWg.instructed_amount,
-        );
-        await tx.withdrawalGroups.put({
-          withdrawalGroupId: backupWg.withdrawal_group_id,
-          exchangeBaseUrl: backupWg.exchange_base_url,
-          instructedAmount: Amounts.stringify(instructedAmount),
-          secretSeed: backupWg.secret_seed,
-          denomsSel: await getDenomSelStateFromBackup(
-            tx,
-            instructedAmount.currency,
-            backupWg.exchange_base_url,
-            backupWg.selected_denoms,
-          ),
-          denomSelUid: backupWg.selected_denoms_uid,
-          rawWithdrawalAmount: Amounts.stringify(
-            backupWg.raw_withdrawal_amount,
-          ),
-          effectiveWithdrawalAmount: Amounts.stringify(
-            backupWg.effective_withdrawal_amount,
-          ),
-          reservePriv: backupWg.reserve_priv,
-          reservePub,
-          status: backupWg.timestamp_finish
-            ? WithdrawalGroupStatus.Finished
-            : WithdrawalGroupStatus.PendingQueryingStatus, // FIXME!
-          timestampStart: backupWg.timestamp_created,
-          wgInfo,
-          restrictAge: backupWg.restrict_age,
-          senderWire: undefined, // FIXME!
-          timestampFinish: backupWg.timestamp_finish,
-        });
-      }
-
-      for (const backupPurchase of backupBlob.purchases) {
-        const ts = constructTombstone({
-          tag: TombstoneTag.DeletePayment,
-          proposalId: backupPurchase.proposal_id,
-        });
-        if (tombstoneSet.has(ts)) {
-          continue;
-        }
-        const existingPurchase = await tx.purchases.get(
-          backupPurchase.proposal_id,
-        );
-        let proposalStatus: PurchaseStatus;
-        switch (backupPurchase.proposal_status) {
-          case BackupProposalStatus.Paid:
-            proposalStatus = PurchaseStatus.Done;
-            break;
-          case BackupProposalStatus.Shared:
-            proposalStatus = PurchaseStatus.DialogShared;
-            break;
-          case BackupProposalStatus.Proposed:
-            proposalStatus = PurchaseStatus.DialogProposed;
-            break;
-          case BackupProposalStatus.PermanentlyFailed:
-            proposalStatus = PurchaseStatus.AbortedIncompletePayment;
-            break;
-          case BackupProposalStatus.Refused:
-            proposalStatus = PurchaseStatus.AbortedProposalRefused;
-            break;
-          case BackupProposalStatus.Repurchase:
-            proposalStatus = PurchaseStatus.RepurchaseDetected;
-            break;
-          default: {
-            const error: never = backupPurchase.proposal_status;
-            throw Error(`backup status ${error} is not handled`);
-          }
-        }
-        if (!existingPurchase) {
-          //const refunds: { [refundKey: string]: WalletRefundItem } = {};
-          // for (const backupRefund of backupPurchase.refunds) {
-          //   const key = 
`${backupRefund.coin_pub}-${backupRefund.rtransaction_id}`;
-          //   const coin = await tx.coins.get(backupRefund.coin_pub);
-          //   checkBackupInvariant(!!coin);
-          //   const denom = await tx.denominations.get([
-          //     coin.exchangeBaseUrl,
-          //     coin.denomPubHash,
-          //   ]);
-          //   checkBackupInvariant(!!denom);
-          //   const common = {
-          //     coinPub: backupRefund.coin_pub,
-          //     executionTime: backupRefund.execution_time,
-          //     obtainedTime: backupRefund.obtained_time,
-          //     refundAmount: Amounts.stringify(backupRefund.refund_amount),
-          //     refundFee: Amounts.stringify(denom.fees.feeRefund),
-          //     rtransactionId: backupRefund.rtransaction_id,
-          //     totalRefreshCostBound: Amounts.stringify(
-          //       backupRefund.total_refresh_cost_bound,
-          //     ),
-          //   };
-          //   switch (backupRefund.type) {
-          //     case BackupRefundState.Applied:
-          //       refunds[key] = {
-          //         type: RefundState.Applied,
-          //         ...common,
-          //       };
-          //       break;
-          //     case BackupRefundState.Failed:
-          //       refunds[key] = {
-          //         type: RefundState.Failed,
-          //         ...common,
-          //       };
-          //       break;
-          //     case BackupRefundState.Pending:
-          //       refunds[key] = {
-          //         type: RefundState.Pending,
-          //         ...common,
-          //       };
-          //       break;
-          //   }
-          // }
-          const parsedContractTerms = codecForMerchantContractTerms().decode(
-            backupPurchase.contract_terms_raw,
-          );
-          const amount = Amounts.parseOrThrow(parsedContractTerms.amount);
-          const contractTermsHash =
-            cryptoComp.proposalIdToContractTermsHash[
-              backupPurchase.proposal_id
-            ];
-          let maxWireFee: AmountJson;
-          if (parsedContractTerms.max_wire_fee) {
-            maxWireFee = 
Amounts.parseOrThrow(parsedContractTerms.max_wire_fee);
-          } else {
-            maxWireFee = Amounts.zeroOfCurrency(amount.currency);
-          }
-          const download: ProposalDownloadInfo = {
-            contractTermsHash,
-            contractTermsMerchantSig: backupPurchase.merchant_sig!,
-            currency: amount.currency,
-            fulfillmentUrl: backupPurchase.contract_terms_raw.fulfillment_url,
-          };
-
-          const contractData = extractContractData(
-            backupPurchase.contract_terms_raw,
-            contractTermsHash,
-            download.contractTermsMerchantSig,
-          );
-
-          let payInfo: PurchasePayInfo | undefined = undefined;
-          if (backupPurchase.pay_info) {
-            payInfo = {
-              payCoinSelection: await recoverPayCoinSelection(
-                tx,
-                contractData,
-                backupPurchase.pay_info,
-              ),
-              payCoinSelectionUid: backupPurchase.pay_info.pay_coins_uid,
-              totalPayCost: Amounts.stringify(
-                backupPurchase.pay_info.total_pay_cost,
-              ),
-            };
-          }
-
-          await tx.purchases.put({
-            proposalId: backupPurchase.proposal_id,
-            noncePriv: backupPurchase.nonce_priv,
-            noncePub:
-              cryptoComp.proposalNoncePrivToPub[backupPurchase.nonce_priv],
-            autoRefundDeadline: TalerProtocolTimestamp.never(),
-            timestampAccept: backupPurchase.timestamp_accepted,
-            timestampFirstSuccessfulPay:
-              backupPurchase.timestamp_first_successful_pay,
-            timestampLastRefundStatus: undefined,
-            merchantPaySig: backupPurchase.merchant_pay_sig,
-            posConfirmation: backupPurchase.pos_confirmation,
-            lastSessionId: undefined,
-            download,
-            //refunds,
-            claimToken: backupPurchase.claim_token,
-            downloadSessionId: backupPurchase.download_session_id,
-            merchantBaseUrl: backupPurchase.merchant_base_url,
-            orderId: backupPurchase.order_id,
-            payInfo,
-            refundAmountAwaiting: undefined,
-            repurchaseProposalId: backupPurchase.repurchase_proposal_id,
-            purchaseStatus: proposalStatus,
-            timestamp: backupPurchase.timestamp_proposed,
-            shared: backupPurchase.shared,
-          });
-        }
-      }
-
-      for (const backupRefreshGroup of backupBlob.refresh_groups) {
-        const ts = constructTombstone({
-          tag: TombstoneTag.DeleteRefreshGroup,
-          refreshGroupId: backupRefreshGroup.refresh_group_id,
-        });
-        if (tombstoneSet.has(ts)) {
-          continue;
-        }
-        const existingRg = await tx.refreshGroups.get(
-          backupRefreshGroup.refresh_group_id,
-        );
-        if (!existingRg) {
-          let reason: RefreshReason;
-          switch (backupRefreshGroup.reason) {
-            case BackupRefreshReason.AbortPay:
-              reason = RefreshReason.AbortPay;
-              break;
-            case BackupRefreshReason.BackupRestored:
-              reason = RefreshReason.BackupRestored;
-              break;
-            case BackupRefreshReason.Manual:
-              reason = RefreshReason.Manual;
-              break;
-            case BackupRefreshReason.Pay:
-              reason = RefreshReason.PayMerchant;
-              break;
-            case BackupRefreshReason.Recoup:
-              reason = RefreshReason.Recoup;
-              break;
-            case BackupRefreshReason.Refund:
-              reason = RefreshReason.Refund;
-              break;
-            case BackupRefreshReason.Scheduled:
-              reason = RefreshReason.Scheduled;
-              break;
-          }
-          const refreshSessionPerCoin: (RefreshSessionRecord | undefined)[] =
-            [];
-          for (const oldCoin of backupRefreshGroup.old_coins) {
-            const c = await tx.coins.get(oldCoin.coin_pub);
-            checkBackupInvariant(!!c);
-            const d = await tx.denominations.get([
-              c.exchangeBaseUrl,
-              c.denomPubHash,
-            ]);
-            checkBackupInvariant(!!d);
-
-            if (oldCoin.refresh_session) {
-              const denomSel = await getDenomSelStateFromBackup(
-                tx,
-                d.currency,
-                c.exchangeBaseUrl,
-                oldCoin.refresh_session.new_denoms,
-              );
-              refreshSessionPerCoin.push({
-                sessionSecretSeed: oldCoin.refresh_session.session_secret_seed,
-                norevealIndex: oldCoin.refresh_session.noreveal_index,
-                newDenoms: oldCoin.refresh_session.new_denoms.map((x) => ({
-                  count: x.count,
-                  denomPubHash: x.denom_pub_hash,
-                })),
-                amountRefreshOutput: 
Amounts.stringify(denomSel.totalCoinValue),
-              });
-            } else {
-              refreshSessionPerCoin.push(undefined);
-            }
-          }
-          await tx.refreshGroups.put({
-            timestampFinished: backupRefreshGroup.timestamp_finish,
-            timestampCreated: backupRefreshGroup.timestamp_created,
-            refreshGroupId: backupRefreshGroup.refresh_group_id,
-            currency: Amounts.currencyOf(
-              backupRefreshGroup.old_coins[0].input_amount,
-            ),
-            reason,
-            lastErrorPerCoin: {},
-            oldCoinPubs: backupRefreshGroup.old_coins.map((x) => x.coin_pub),
-            statusPerCoin: backupRefreshGroup.old_coins.map((x) =>
-              x.finished
-                ? RefreshCoinStatus.Finished
-                : RefreshCoinStatus.Pending,
-            ),
-            operationStatus: backupRefreshGroup.timestamp_finish
-              ? RefreshOperationStatus.Finished
-              : RefreshOperationStatus.Pending,
-            inputPerCoin: backupRefreshGroup.old_coins.map(
-              (x) => x.input_amount,
-            ),
-            estimatedOutputPerCoin: backupRefreshGroup.old_coins.map(
-              (x) => x.estimated_output_amount,
-            ),
-            refreshSessionPerCoin,
-          });
-        }
-      }
-
-      for (const backupTip of backupBlob.tips) {
-        const ts = constructTombstone({
-          tag: TombstoneTag.DeleteReward,
-          walletTipId: backupTip.wallet_tip_id,
-        });
-        if (tombstoneSet.has(ts)) {
-          continue;
-        }
-        const existingTip = await tx.rewards.get(backupTip.wallet_tip_id);
-        if (!existingTip) {
-          const tipAmountRaw = Amounts.parseOrThrow(backupTip.tip_amount_raw);
-          const denomsSel = await getDenomSelStateFromBackup(
-            tx,
-            tipAmountRaw.currency,
-            backupTip.exchange_base_url,
-            backupTip.selected_denoms,
-          );
-          await tx.rewards.put({
-            acceptedTimestamp: backupTip.timestamp_accepted,
-            createdTimestamp: backupTip.timestamp_created,
-            denomsSel,
-            next_url: backupTip.next_url,
-            exchangeBaseUrl: backupTip.exchange_base_url,
-            merchantBaseUrl: backupTip.exchange_base_url,
-            merchantRewardId: backupTip.merchant_tip_id,
-            pickedUpTimestamp: backupTip.timestamp_finished,
-            secretSeed: backupTip.secret_seed,
-            rewardAmountEffective: Amounts.stringify(denomsSel.totalCoinValue),
-            rewardAmountRaw: Amounts.stringify(tipAmountRaw),
-            rewardExpiration: backupTip.timestamp_expiration,
-            walletRewardId: backupTip.wallet_tip_id,
-            denomSelUid: backupTip.selected_denoms_uid,
-            status: RewardRecordStatus.Done, // FIXME!
-          });
-        }
-      }
-
-      // We now process tombstones.
-      // The import code above should already prevent
-      // importing things that are tombstoned,
-      // but we do tombstone processing last just to be sure.
-
-      for (const tombstone of tombstoneSet) {
-        const [type, ...rest] = tombstone.split(":");
-        if (type === TombstoneTag.DeleteDepositGroup) {
-          await tx.depositGroups.delete(rest[0]);
-        } else if (type === TombstoneTag.DeletePayment) {
-          await tx.purchases.delete(rest[0]);
-        } else if (type === TombstoneTag.DeleteRefreshGroup) {
-          await tx.refreshGroups.delete(rest[0]);
-        } else if (type === TombstoneTag.DeleteRefund) {
-          // Nothing required, will just prevent display
-          // in the transactions list
-        } else if (type === TombstoneTag.DeleteReward) {
-          await tx.rewards.delete(rest[0]);
-        } else if (type === TombstoneTag.DeleteWithdrawalGroup) {
-          await tx.withdrawalGroups.delete(rest[0]);
-        } else {
-          logger.warn(`unable to process tombstone of type '${type}'`);
-        }
-      }
-    });
-}
diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts 
b/packages/taler-wallet-core/src/operations/backup/index.ts
index e35765165..a5e8dbd42 100644
--- a/packages/taler-wallet-core/src/operations/backup/index.ts
+++ b/packages/taler-wallet-core/src/operations/backup/index.ts
@@ -43,7 +43,6 @@ import {
   TalerErrorDetail,
   TalerPreciseTimestamp,
   URL,
-  WalletBackupContentV1,
   buildCodecForObject,
   buildCodecForUnion,
   bytesToString,
@@ -99,9 +98,8 @@ import {
   TaskIdentifiers,
 } from "../common.js";
 import { checkPaymentByProposalId, preparePayForUri } from 
"../pay-merchant.js";
-import { exportBackup } from "./export.js";
-import { BackupCryptoPrecomputedData, importBackup } from "./import.js";
-import { getWalletBackupState, provideBackupState } from "./state.js";
+import { WalletStoresV1 } from "../../db.js";
+import { GetReadOnlyAccess } from "../../util/query.js";
 
 const logger = new Logger("operations/backup.ts");
 
@@ -131,7 +129,7 @@ const magic = "TLRWBK01";
  */
 export async function encryptBackup(
   config: WalletBackupConfState,
-  blob: WalletBackupContentV1,
+  blob: any,
 ): Promise<Uint8Array> {
   const chunks: Uint8Array[] = [];
   chunks.push(stringToBytes(magic));
@@ -150,64 +148,6 @@ export async function encryptBackup(
   return concatArrays(chunks);
 }
 
-/**
- * Compute cryptographic values for a backup blob.
- *
- * FIXME: Take data that we already know from the DB.
- * FIXME: Move computations into crypto worker.
- */
-async function computeBackupCryptoData(
-  cryptoApi: TalerCryptoInterface,
-  backupContent: WalletBackupContentV1,
-): Promise<BackupCryptoPrecomputedData> {
-  const cryptoData: BackupCryptoPrecomputedData = {
-    coinPrivToCompletedCoin: {},
-    rsaDenomPubToHash: {},
-    proposalIdToContractTermsHash: {},
-    proposalNoncePrivToPub: {},
-    reservePrivToPub: {},
-  };
-  for (const backupExchangeDetails of backupContent.exchange_details) {
-    for (const backupDenom of backupExchangeDetails.denominations) {
-      if (backupDenom.denom_pub.cipher !== DenomKeyType.Rsa) {
-        throw Error("unsupported cipher");
-      }
-      for (const backupCoin of backupDenom.coins) {
-        const coinPub = encodeCrock(
-          eddsaGetPublic(decodeCrock(backupCoin.coin_priv)),
-        );
-        const blindedCoin = rsaBlind(
-          hash(decodeCrock(backupCoin.coin_priv)),
-          decodeCrock(backupCoin.blinding_key),
-          decodeCrock(backupDenom.denom_pub.rsa_public_key),
-        );
-        cryptoData.coinPrivToCompletedCoin[backupCoin.coin_priv] = {
-          coinEvHash: encodeCrock(hash(blindedCoin)),
-          coinPub,
-        };
-      }
-      cryptoData.rsaDenomPubToHash[backupDenom.denom_pub.rsa_public_key] =
-        encodeCrock(hashDenomPub(backupDenom.denom_pub));
-    }
-  }
-  for (const backupWg of backupContent.withdrawal_groups) {
-    cryptoData.reservePrivToPub[backupWg.reserve_priv] = encodeCrock(
-      eddsaGetPublic(decodeCrock(backupWg.reserve_priv)),
-    );
-  }
-  for (const purch of backupContent.purchases) {
-    if (!purch.contract_terms_raw) continue;
-    const { h: contractTermsHash } = await cryptoApi.hashString({
-      str: canonicalJson(purch.contract_terms_raw),
-    });
-    const noncePub = 
encodeCrock(eddsaGetPublic(decodeCrock(purch.nonce_priv)));
-    cryptoData.proposalNoncePrivToPub[purch.nonce_priv] = noncePub;
-    cryptoData.proposalIdToContractTermsHash[purch.proposal_id] =
-      contractTermsHash;
-  }
-  return cryptoData;
-}
-
 function deriveAccountKeyPair(
   bc: WalletBackupConfState,
   providerUrl: string,
@@ -262,7 +202,9 @@ async function runBackupCycleForProvider(
     return TaskRunResult.finished();
   }
 
-  const backupJson = await exportBackup(ws);
+  //const backupJson = await exportBackup(ws);
+  // FIXME: re-implement backup
+  const backupJson = {};
   const backupConfig = await provideBackupState(ws);
   const encBackup = await encryptBackup(backupConfig, backupJson);
   const currentBackupHash = hash(encBackup);
@@ -441,9 +383,9 @@ async function runBackupCycleForProvider(
     logger.info("conflicting backup found");
     const backupEnc = new Uint8Array(await resp.bytes());
     const backupConfig = await provideBackupState(ws);
-    const blob = await decryptBackup(backupConfig, backupEnc);
-    const cryptoData = await computeBackupCryptoData(ws.cryptoApi, blob);
-    await importBackup(ws, blob, cryptoData);
+    // const blob = await decryptBackup(backupConfig, backupEnc);
+    // FIXME: Re-implement backup import with merging
+    // await importBackup(ws, blob, cryptoData);
     await ws.db
       .mktx((x) => [x.backupProviders, x.operationRetries])
       .runReadWrite(async (tx) => {
@@ -789,18 +731,6 @@ export interface BackupInfo {
   providers: ProviderInfo[];
 }
 
-export async function importBackupPlain(
-  ws: InternalWalletState,
-  blob: any,
-): Promise<void> {
-  // FIXME: parse
-  const backup: WalletBackupContentV1 = blob;
-
-  const cryptoData = await computeBackupCryptoData(ws.cryptoApi, backup);
-
-  await importBackup(ws, blob, cryptoData);
-}
-
 export enum ProviderPaymentType {
   Unpaid = "unpaid",
   Pending = "pending",
@@ -1036,23 +966,10 @@ export async function loadBackupRecovery(
   }
 }
 
-export async function exportBackupEncrypted(
-  ws: InternalWalletState,
-): Promise<Uint8Array> {
-  await provideBackupState(ws);
-  const blob = await exportBackup(ws);
-  const bs = await ws.db
-    .mktx((x) => [x.config])
-    .runReadOnly(async (tx) => {
-      return await getWalletBackupState(ws, tx);
-    });
-  return encryptBackup(bs, blob);
-}
-
 export async function decryptBackup(
   backupConfig: WalletBackupConfState,
   data: Uint8Array,
-): Promise<WalletBackupContentV1> {
+): Promise<any> {
   const rMagic = bytesToString(data.slice(0, 8));
   if (rMagic != magic) {
     throw Error("invalid backup file (magic tag mismatch)");
@@ -1068,12 +985,85 @@ export async function decryptBackup(
   return JSON.parse(bytesToString(gunzipSync(dataCompressed)));
 }
 
-export async function importBackupEncrypted(
+export async function provideBackupState(
   ws: InternalWalletState,
-  data: Uint8Array,
+): Promise<WalletBackupConfState> {
+  const bs: ConfigRecord | undefined = await ws.db
+    .mktx((stores) => [stores.config])
+    .runReadOnly(async (tx) => {
+      return await tx.config.get(ConfigRecordKey.WalletBackupState);
+    });
+  if (bs) {
+    checkDbInvariant(bs.key === ConfigRecordKey.WalletBackupState);
+    return bs.value;
+  }
+  // We need to generate the key outside of the transaction
+  // due to how IndexedDB works.
+  const k = await ws.cryptoApi.createEddsaKeypair({});
+  const d = getRandomBytes(5);
+  // FIXME: device ID should be configured when wallet is initialized
+  // and be based on hostname
+  const deviceId = `wallet-core-${encodeCrock(d)}`;
+  return await ws.db
+    .mktx((x) => [x.config])
+    .runReadWrite(async (tx) => {
+      let backupStateEntry: ConfigRecord | undefined = await tx.config.get(
+        ConfigRecordKey.WalletBackupState,
+      );
+      if (!backupStateEntry) {
+        backupStateEntry = {
+          key: ConfigRecordKey.WalletBackupState,
+          value: {
+            deviceId,
+            walletRootPub: k.pub,
+            walletRootPriv: k.priv,
+            lastBackupPlainHash: undefined,
+          },
+        };
+        await tx.config.put(backupStateEntry);
+      }
+      checkDbInvariant(
+        backupStateEntry.key === ConfigRecordKey.WalletBackupState,
+      );
+      return backupStateEntry.value;
+    });
+}
+
+export async function getWalletBackupState(
+  ws: InternalWalletState,
+  tx: GetReadOnlyAccess<{ config: typeof WalletStoresV1.config }>,
+): Promise<WalletBackupConfState> {
+  const bs = await tx.config.get(ConfigRecordKey.WalletBackupState);
+  checkDbInvariant(!!bs, "wallet backup state should be in DB");
+  checkDbInvariant(bs.key === ConfigRecordKey.WalletBackupState);
+  return bs.value;
+}
+
+export async function setWalletDeviceId(
+  ws: InternalWalletState,
+  deviceId: string,
 ): Promise<void> {
-  const backupConfig = await provideBackupState(ws);
-  const blob = await decryptBackup(backupConfig, data);
-  const cryptoData = await computeBackupCryptoData(ws.cryptoApi, blob);
-  await importBackup(ws, blob, cryptoData);
+  await provideBackupState(ws);
+  await ws.db
+    .mktx((x) => [x.config])
+    .runReadWrite(async (tx) => {
+      let backupStateEntry: ConfigRecord | undefined = await tx.config.get(
+        ConfigRecordKey.WalletBackupState,
+      );
+      if (
+        !backupStateEntry ||
+        backupStateEntry.key !== ConfigRecordKey.WalletBackupState
+      ) {
+        return;
+      }
+      backupStateEntry.value.deviceId = deviceId;
+      await tx.config.put(backupStateEntry);
+    });
+}
+
+export async function getWalletDeviceId(
+  ws: InternalWalletState,
+): Promise<string> {
+  const bs = await provideBackupState(ws);
+  return bs.deviceId;
 }
diff --git a/packages/taler-wallet-core/src/operations/backup/state.ts 
b/packages/taler-wallet-core/src/operations/backup/state.ts
index fa632f44c..d02ead783 100644
--- a/packages/taler-wallet-core/src/operations/backup/state.ts
+++ b/packages/taler-wallet-core/src/operations/backup/state.ts
@@ -14,96 +14,4 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util";
-import {
-  ConfigRecord,
-  ConfigRecordKey,
-  WalletBackupConfState,
-  WalletStoresV1,
-} from "../../db.js";
-import { checkDbInvariant } from "../../util/invariants.js";
-import { GetReadOnlyAccess } from "../../util/query.js";
-import { InternalWalletState } from "../../internal-wallet-state.js";
 
-export async function provideBackupState(
-  ws: InternalWalletState,
-): Promise<WalletBackupConfState> {
-  const bs: ConfigRecord | undefined = await ws.db
-    .mktx((stores) => [stores.config])
-    .runReadOnly(async (tx) => {
-      return await tx.config.get(ConfigRecordKey.WalletBackupState);
-    });
-  if (bs) {
-    checkDbInvariant(bs.key === ConfigRecordKey.WalletBackupState);
-    return bs.value;
-  }
-  // We need to generate the key outside of the transaction
-  // due to how IndexedDB works.
-  const k = await ws.cryptoApi.createEddsaKeypair({});
-  const d = getRandomBytes(5);
-  // FIXME: device ID should be configured when wallet is initialized
-  // and be based on hostname
-  const deviceId = `wallet-core-${encodeCrock(d)}`;
-  return await ws.db
-    .mktx((x) => [x.config])
-    .runReadWrite(async (tx) => {
-      let backupStateEntry: ConfigRecord | undefined = await tx.config.get(
-        ConfigRecordKey.WalletBackupState,
-      );
-      if (!backupStateEntry) {
-        backupStateEntry = {
-          key: ConfigRecordKey.WalletBackupState,
-          value: {
-            deviceId,
-            walletRootPub: k.pub,
-            walletRootPriv: k.priv,
-            lastBackupPlainHash: undefined,
-          },
-        };
-        await tx.config.put(backupStateEntry);
-      }
-      checkDbInvariant(
-        backupStateEntry.key === ConfigRecordKey.WalletBackupState,
-      );
-      return backupStateEntry.value;
-    });
-}
-
-export async function getWalletBackupState(
-  ws: InternalWalletState,
-  tx: GetReadOnlyAccess<{ config: typeof WalletStoresV1.config }>,
-): Promise<WalletBackupConfState> {
-  const bs = await tx.config.get(ConfigRecordKey.WalletBackupState);
-  checkDbInvariant(!!bs, "wallet backup state should be in DB");
-  checkDbInvariant(bs.key === ConfigRecordKey.WalletBackupState);
-  return bs.value;
-}
-
-export async function setWalletDeviceId(
-  ws: InternalWalletState,
-  deviceId: string,
-): Promise<void> {
-  await provideBackupState(ws);
-  await ws.db
-    .mktx((x) => [x.config])
-    .runReadWrite(async (tx) => {
-      let backupStateEntry: ConfigRecord | undefined = await tx.config.get(
-        ConfigRecordKey.WalletBackupState,
-      );
-      if (
-        !backupStateEntry ||
-        backupStateEntry.key !== ConfigRecordKey.WalletBackupState
-      ) {
-        return;
-      }
-      backupStateEntry.value.deviceId = deviceId;
-      await tx.config.put(backupStateEntry);
-    });
-}
-
-export async function getWalletDeviceId(
-  ws: InternalWalletState,
-): Promise<string> {
-  const bs = await provideBackupState(ws);
-  return bs.deviceId;
-}
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts 
b/packages/taler-wallet-core/src/wallet-api-types.ts
index 06ccdf6f3..4d9d40c43 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -106,7 +106,6 @@ import {
   UserAttentionsResponse,
   ValidateIbanRequest,
   ValidateIbanResponse,
-  WalletBackupContentV1,
   WalletCoreVersion,
   WalletCurrencyInfo,
   WithdrawFakebankRequest,
@@ -116,6 +115,10 @@ import {
   SharePaymentResult,
   GetCurrencyInfoRequest,
   GetCurrencyInfoResponse,
+  StoredBackupList,
+  CreateStoredBackupResponse,
+  RecoverStoredBackupRequest,
+  DeleteStoredBackupRequest,
 } from "@gnu-taler/taler-util";
 import { AuditorTrustRecord, WalletContractData } from "./db.js";
 import {
@@ -195,7 +198,6 @@ export enum WalletApiOperation {
   GenerateDepositGroupTxId = "generateDepositGroupTxId",
   CreateDepositGroup = "createDepositGroup",
   SetWalletDeviceId = "setWalletDeviceId",
-  ExportBackupPlain = "exportBackupPlain",
   WithdrawFakebank = "withdrawFakebank",
   ImportDb = "importDb",
   ExportDb = "exportDb",
@@ -214,6 +216,10 @@ export enum WalletApiOperation {
   TestingWaitTransactionsFinal = "testingWaitTransactionsFinal",
   TestingWaitRefreshesFinal = "testingWaitRefreshesFinal",
   GetScopedCurrencyInfo = "getScopedCurrencyInfo",
+  ListStoredBackups = "listStoredBackups",
+  CreateStoredBackup = "createStoredBackup",
+  DeleteStoredBackup = "deleteStoredBackup",
+  RecoverStoredBackup = "recoverStoredBackup",
 }
 
 // group: Initialization
@@ -713,13 +719,28 @@ export type SetWalletDeviceIdOp = {
   response: EmptyObject;
 };
 
-/**
- * Export a backup JSON, mostly useful for testing.
- */
-export type ExportBackupPlainOp = {
-  op: WalletApiOperation.ExportBackupPlain;
+export type ListStoredBackupsOp = {
+  op: WalletApiOperation.ListStoredBackups;
+  request: EmptyObject;
+  response: StoredBackupList;
+};
+
+export type CreateStoredBackupsOp = {
+  op: WalletApiOperation.CreateStoredBackup;
   request: EmptyObject;
-  response: WalletBackupContentV1;
+  response: CreateStoredBackupResponse;
+};
+
+export type RecoverStoredBackupsOp = {
+  op: WalletApiOperation.RecoverStoredBackup;
+  request: RecoverStoredBackupRequest;
+  response: EmptyObject;
+};
+
+export type DeleteStoredBackupOp = {
+  op: WalletApiOperation.DeleteStoredBackup;
+  request: DeleteStoredBackupRequest;
+  response: EmptyObject;
 };
 
 // group: Peer Payments
@@ -1062,7 +1083,6 @@ export type WalletOperations = {
   [WalletApiOperation.GenerateDepositGroupTxId]: GenerateDepositGroupTxIdOp;
   [WalletApiOperation.CreateDepositGroup]: CreateDepositGroupOp;
   [WalletApiOperation.SetWalletDeviceId]: SetWalletDeviceIdOp;
-  [WalletApiOperation.ExportBackupPlain]: ExportBackupPlainOp;
   [WalletApiOperation.ExportBackupRecovery]: ExportBackupRecoveryOp;
   [WalletApiOperation.ImportBackupRecovery]: ImportBackupRecoveryOp;
   [WalletApiOperation.RunBackupCycle]: RunBackupCycleOp;
@@ -1092,6 +1112,10 @@ export type WalletOperations = {
   [WalletApiOperation.TestingWaitTransactionsFinal]: 
TestingWaitTransactionsFinal;
   [WalletApiOperation.TestingWaitRefreshesFinal]: TestingWaitRefreshesFinal;
   [WalletApiOperation.GetScopedCurrencyInfo]: GetScopedCurrencyInfoOp;
+  [WalletApiOperation.CreateStoredBackup]: CreateStoredBackupsOp;
+  [WalletApiOperation.ListStoredBackups]: ListStoredBackupsOp;
+  [WalletApiOperation.DeleteStoredBackup]: DeleteStoredBackupOp;
+  [WalletApiOperation.RecoverStoredBackup]: RecoverStoredBackupsOp;
 };
 
 export type WalletCoreRequestType<
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 9f754ed69..283539a08 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -120,6 +120,7 @@ import {
   codecForSharePaymentRequest,
   GetCurrencyInfoResponse,
   codecForGetCurrencyInfoRequest,
+  CreateStoredBackupResponse,
 } from "@gnu-taler/taler-util";
 import {
   HttpRequestLibrary,
@@ -139,6 +140,7 @@ import {
   clearDatabase,
   exportDb,
   importDb,
+  openStoredBackupsDatabase,
   openTalerDatabase,
 } from "./db.js";
 import { DevExperimentHttpLib, applyDevExperiment } from 
"./dev-experiments.js";
@@ -158,7 +160,6 @@ import {
   getUserAttentionsUnreadCount,
   markAttentionRequestAsRead,
 } from "./operations/attention.js";
-import { exportBackup } from "./operations/backup/export.js";
 import {
   addBackupProvider,
   codecForAddBackupProviderRequest,
@@ -166,13 +167,12 @@ import {
   codecForRunBackupCycle,
   getBackupInfo,
   getBackupRecovery,
-  importBackupPlain,
   loadBackupRecovery,
   processBackupForProvider,
   removeBackupProvider,
   runBackupCycle,
+  setWalletDeviceId,
 } from "./operations/backup/index.js";
-import { setWalletDeviceId } from "./operations/backup/state.js";
 import { getBalanceDetail, getBalances } from "./operations/balance.js";
 import {
   TaskIdentifiers,
@@ -1025,6 +1025,17 @@ export async function getClientFromWalletState(
   return client;
 }
 
+async function createStoredBackup(
+  ws: InternalWalletState,
+): Promise<CreateStoredBackupResponse> {
+  const backup = await exportDb(ws.idb);
+  const backupsDb = await openStoredBackupsDatabase(ws.idb);
+  const name = `backup-${new Date().getTime()}`;
+  backupsDb.mktxAll().runReadWrite(async (tx) => {});
+
+  throw Error("not implemented");
+}
+
 /**
  * Implementation of the "wallet-core" API.
  */
@@ -1041,6 +1052,14 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
   // FIXME: Can we make this more type-safe by using the request/response type
   // definitions we already have?
   switch (operation) {
+    case WalletApiOperation.CreateStoredBackup:
+      return createStoredBackup(ws);
+    case WalletApiOperation.DeleteStoredBackup:
+      return {};
+    case WalletApiOperation.ListStoredBackups:
+      return {};
+    case WalletApiOperation.RecoverStoredBackup:
+      return {};
     case WalletApiOperation.InitWallet: {
       logger.trace("initializing wallet");
       ws.initCalled = true;
@@ -1382,9 +1401,6 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       const req = codecForAcceptTipRequest().decode(payload);
       return await acceptTip(ws, req.walletRewardId);
     }
-    case WalletApiOperation.ExportBackupPlain: {
-      return exportBackup(ws);
-    }
     case WalletApiOperation.AddBackupProvider: {
       const req = codecForAddBackupProviderRequest().decode(payload);
       return await addBackupProvider(ws, req);
@@ -1535,9 +1551,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
       await clearDatabase(ws.db.idbHandle());
       return {};
     case WalletApiOperation.Recycle: {
-      const backup = await exportBackup(ws);
-      await clearDatabase(ws.db.idbHandle());
-      await importBackupPlain(ws, backup);
+      throw Error("not implemented");
       return {};
     }
     case WalletApiOperation.ExportDb: {
diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts 
b/packages/taler-wallet-webextension/src/wxBackend.ts
index f071d78df..b7484164d 100644
--- a/packages/taler-wallet-webextension/src/wxBackend.ts
+++ b/packages/taler-wallet-webextension/src/wxBackend.ts
@@ -139,7 +139,7 @@ async function runGarbageCollector(): Promise<void> {
   if (!dbBeforeGc) {
     throw Error("no current db before running gc");
   }
-  const dump = await exportDb(dbBeforeGc.idbHandle());
+  const dump = await exportDb(indexedDB as any);
 
   await deleteTalerDatabase(indexedDB as any);
   logger.info("cleaned");

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]