aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations/backup/state.ts
blob: 226880439ffcfadd1736e80b930b30eadc137289 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/*
 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 { Timestamp } from "@gnu-taler/taler-util";
import { ConfigRecord, WalletStoresV1 } from "../../db.js";
import { getRandomBytes, encodeCrock } from "../../index.js";
import { checkDbInvariant } from "../../util/invariants";
import { GetReadOnlyAccess } from "../../util/query.js";
import { Wallet } from "../../wallet.js";
import { InternalWalletState } from "../state";

export interface WalletBackupConfState {
  deviceId: string;
  walletRootPub: string;
  walletRootPriv: string;
  clocks: { [device_id: string]: number };

  /**
   * Last hash of the canonicalized plain-text backup.
   */
  lastBackupPlainHash?: string;

  /**
   * Timestamp stored in the last backup.
   */
  lastBackupTimestamp?: Timestamp;

  /**
   * Last time we tried to do a backup.
   */
  lastBackupCheckTimestamp?: Timestamp;
  lastBackupNonce?: string;
}

export const WALLET_BACKUP_STATE_KEY = "walletBackupState";

export async function provideBackupState(
  ws: InternalWalletState,
): Promise<WalletBackupConfState> {
  const bs: ConfigRecord<WalletBackupConfState> | undefined = await ws.db
    .mktx((x) => ({
      config: x.config,
    }))
    .runReadOnly(async (tx) => {
      return tx.config.get(WALLET_BACKUP_STATE_KEY);
    });
  if (bs) {
    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) => ({
      config: x.config,
    }))
    .runReadWrite(async (tx) => {
      let backupStateEntry:
        | ConfigRecord<WalletBackupConfState>
        | undefined = await tx.config.get(WALLET_BACKUP_STATE_KEY);
      if (!backupStateEntry) {
        backupStateEntry = {
          key: WALLET_BACKUP_STATE_KEY,
          value: {
            deviceId,
            clocks: { [deviceId]: 1 },
            walletRootPub: k.pub,
            walletRootPriv: k.priv,
            lastBackupPlainHash: undefined,
          },
        };
        await tx.config.put(backupStateEntry);
      }
      return backupStateEntry.value;
    });
}

export async function getWalletBackupState(
  ws: InternalWalletState,
  tx: GetReadOnlyAccess<{ config: typeof WalletStoresV1.config }>,
): Promise<WalletBackupConfState> {
  const bs = await tx.config.get(WALLET_BACKUP_STATE_KEY);
  checkDbInvariant(!!bs, "wallet backup state should be in DB");
  return bs.value;
}