gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: derive tipping planchets from


From: gnunet
Subject: [taler-wallet-core] branch master updated: derive tipping planchets from seed, implement backup further
Date: Tue, 15 Dec 2020 17:12:27 +0100

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 c09c5bbe derive tipping planchets from seed, implement backup further
c09c5bbe is described below

commit c09c5bbe625566fc61c811160d2ccdab263327fa
Author: Florian Dold <florian@dold.me>
AuthorDate: Tue Dec 15 17:12:22 2020 +0100

    derive tipping planchets from seed, implement backup further
---
 .../taler-wallet-core/src/crypto/talerCrypto.ts    |  19 ++
 .../src/crypto/workers/cryptoApi.ts                |  17 +-
 .../src/crypto/workers/cryptoImplementation.ts     |  26 +--
 .../taler-wallet-core/src/operations/backup.ts     | 240 +++++++++++++++++++--
 .../taler-wallet-core/src/operations/refresh.ts    |   1 +
 .../taler-wallet-core/src/operations/refund.ts     |   6 +
 packages/taler-wallet-core/src/operations/tip.ts   |  71 +++---
 .../src/operations/transactions.ts                 |   1 -
 .../taler-wallet-core/src/types/backupTypes.ts     | 146 +++++++------
 .../taler-wallet-core/src/types/cryptoTypes.ts     |  16 ++
 packages/taler-wallet-core/src/types/dbTypes.ts    |  52 ++---
 .../src/util/reserveHistoryUtil.ts                 |  11 +-
 12 files changed, 427 insertions(+), 179 deletions(-)

diff --git a/packages/taler-wallet-core/src/crypto/talerCrypto.ts 
b/packages/taler-wallet-core/src/crypto/talerCrypto.ts
index 4faa523a..09595798 100644
--- a/packages/taler-wallet-core/src/crypto/talerCrypto.ts
+++ b/packages/taler-wallet-core/src/crypto/talerCrypto.ts
@@ -390,6 +390,25 @@ export function setupRefreshPlanchet(
   };
 }
 
+export function setupTipPlanchet(
+  secretSeed: Uint8Array,
+  coinNumber: number,
+): FreshCoin {
+  const info = stringToBytes("taler-tip-coin-derivation");
+  const saltArrBuf = new ArrayBuffer(4);
+  const salt = new Uint8Array(saltArrBuf);
+  const saltDataView = new DataView(saltArrBuf);
+  saltDataView.setUint32(0, coinNumber);
+  const out = kdf(64, secretSeed, salt, info);
+  const coinPriv = out.slice(0, 32);
+  const bks = out.slice(32, 64);
+  return {
+    bks,
+    coinPriv,
+    coinPub: eddsaGetPublic(coinPriv),
+  };
+}
+
 export function setupRefreshTransferPub(
   secretSeed: Uint8Array,
   transferPubIndex: number,
diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts 
b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts
index 6a4264d2..ef149823 100644
--- a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts
+++ b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts
@@ -22,16 +22,7 @@
 /**
  * Imports.
  */
-import { AmountJson } from "../../util/amounts";
-
-import {
-  CoinRecord,
-  DenominationRecord,
-  RefreshSessionRecord,
-  TipPlanchet,
-  WireFee,
-  DenominationSelectionInfo,
-} from "../../types/dbTypes";
+import { CoinRecord, DenominationRecord, WireFee } from "../../types/dbTypes";
 
 import { CryptoWorker } from "./cryptoWorker";
 
@@ -49,7 +40,9 @@ import * as timer from "../../util/timer";
 import { Logger } from "../../util/logging";
 import {
   DerivedRefreshSession,
+  DerivedTipPlanchet,
   DeriveRefreshSessionRequest,
+  DeriveTipRequest,
 } from "../../types/cryptoTypes";
 
 const logger = new Logger("cryptoApi.ts");
@@ -329,8 +322,8 @@ export class CryptoApi {
     return this.doRpc<PlanchetCreationResult>("createPlanchet", 1, req);
   }
 
-  createTipPlanchet(denom: DenominationRecord): Promise<TipPlanchet> {
-    return this.doRpc<TipPlanchet>("createTipPlanchet", 1, denom);
+  createTipPlanchet(req: DeriveTipRequest): Promise<DerivedTipPlanchet> {
+    return this.doRpc<DerivedTipPlanchet>("createTipPlanchet", 1, req);
   }
 
   hashString(str: string): Promise<string> {
diff --git 
a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts 
b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
index d14f663e..deaad42b 100644
--- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts
@@ -30,11 +30,8 @@ import {
   CoinRecord,
   DenominationRecord,
   RefreshPlanchet,
-  RefreshSessionRecord,
-  TipPlanchet,
   WireFee,
   CoinSourceType,
-  DenominationSelectionInfo,
 } from "../../types/dbTypes";
 
 import { CoinDepositPermission, RecoupRequest } from "../../types/talerTypes";
@@ -59,25 +56,25 @@ import {
   rsaUnblind,
   stringToBytes,
   createHashContext,
-  createEcdheKeyPair,
   keyExchangeEcdheEddsa,
   setupRefreshPlanchet,
   rsaVerify,
-  getRandomBytes,
   setupRefreshTransferPub,
+  setupTipPlanchet,
 } from "../talerCrypto";
 import { randomBytes } from "../primitives/nacl-fast";
 import { kdf } from "../primitives/kdf";
 import {
   Timestamp,
-  getTimestampNow,
   timestampTruncateToSecond,
 } from "../../util/time";
 
 import { Logger } from "../../util/logging";
 import {
   DerivedRefreshSession,
+  DerivedTipPlanchet,
   DeriveRefreshSessionRequest,
+  DeriveTipRequest,
 } from "../../types/cryptoTypes";
 
 const logger = new Logger("cryptoImplementation.ts");
@@ -199,21 +196,18 @@ export class CryptoImplementation {
   /**
    * Create a planchet used for tipping, including the private keys.
    */
-  createTipPlanchet(denom: DenominationRecord): TipPlanchet {
-    const denomPub = decodeCrock(denom.denomPub);
-    const coinKeyPair = createEddsaKeyPair();
+  createTipPlanchet(req: DeriveTipRequest): DerivedTipPlanchet {
+    const fc = setupTipPlanchet(decodeCrock(req.secretSeed), 
req.planchetIndex);
+    const denomPub = decodeCrock(req.denomPub);
     const blindingFactor = createBlindingKeySecret();
-    const coinPubHash = hash(coinKeyPair.eddsaPub);
+    const coinPubHash = hash(fc.coinPub);
     const ev = rsaBlind(coinPubHash, blindingFactor, denomPub);
 
-    const tipPlanchet: TipPlanchet = {
+    const tipPlanchet: DerivedTipPlanchet = {
       blindingKey: encodeCrock(blindingFactor),
       coinEv: encodeCrock(ev),
-      coinPriv: encodeCrock(coinKeyPair.eddsaPriv),
-      coinPub: encodeCrock(coinKeyPair.eddsaPub),
-      coinValue: denom.value,
-      denomPub: encodeCrock(denomPub),
-      denomPubHash: encodeCrock(hash(denomPub)),
+      coinPriv: encodeCrock(fc.coinPriv),
+      coinPub: encodeCrock(fc.coinPub),
     };
     return tipPlanchet;
   }
diff --git a/packages/taler-wallet-core/src/operations/backup.ts 
b/packages/taler-wallet-core/src/operations/backup.ts
index f609d435..7ab908c4 100644
--- a/packages/taler-wallet-core/src/operations/backup.ts
+++ b/packages/taler-wallet-core/src/operations/backup.ts
@@ -26,20 +26,34 @@
  */
 import { InternalWalletState } from "./state";
 import {
+  BackupBackupProvider,
   BackupCoin,
   BackupCoinSource,
   BackupCoinSourceType,
   BackupDenomination,
   BackupExchange,
   BackupExchangeWireFee,
+  BackupProposal,
+  BackupProposalStatus,
+  BackupPurchase,
+  BackupRecoupGroup,
+  BackupRefreshGroup,
+  BackupRefreshOldCoin,
+  BackupRefreshSession,
+  BackupRefundItem,
+  BackupRefundState,
   BackupReserve,
+  BackupTip,
   WalletBackupContentV1,
 } from "../types/backupTypes";
 import { TransactionHandle } from "../util/query";
 import {
+  AbortStatus,
   CoinSourceType,
   CoinStatus,
   ConfigRecord,
+  ProposalStatus,
+  RefundState,
   Stores,
 } from "../types/dbTypes";
 import { checkDbInvariant } from "../util/invariants";
@@ -56,7 +70,7 @@ import {
 import { canonicalizeBaseUrl, canonicalJson, j2s } from "../util/helpers";
 import { getTimestampNow, Timestamp } from "../util/time";
 import { URL } from "../util/url";
-import { AmountString } from "../types/talerTypes";
+import { AmountString, TipResponse } from "../types/talerTypes";
 import {
   buildCodecForObject,
   Codec,
@@ -146,16 +160,80 @@ export async function exportBackup(
 ): Promise<WalletBackupContentV1> {
   await provideBackupState(ws);
   return ws.db.runWithWriteTransaction(
-    [Stores.config, Stores.exchanges, Stores.coins, Stores.denominations],
+    [
+      Stores.config,
+      Stores.exchanges,
+      Stores.coins,
+      Stores.denominations,
+      Stores.purchases,
+      Stores.proposals,
+      Stores.refreshGroups,
+      Stores.backupProviders,
+      Stores.tips,
+      Stores.recoupGroups,
+      Stores.reserves,
+    ],
     async (tx) => {
       const bs = await getWalletBackupState(ws, tx);
 
-      const exchanges: BackupExchange[] = [];
-      const coinsByDenom: { [dph: string]: BackupCoin[] } = {};
-      const denominationsByExchange: {
+      const backupExchanges: BackupExchange[] = [];
+      const backupCoinsByDenom: { [dph: string]: BackupCoin[] } = {};
+      const backupDenominationsByExchange: {
         [url: string]: BackupDenomination[];
       } = {};
-      const reservesByExchange: { [url: string]: BackupReserve[] } = {};
+      const backupReservesByExchange: { [url: string]: BackupReserve[] } = {};
+      const backupPurchases: BackupPurchase[] = [];
+      const backupProposals: BackupProposal[] = [];
+      const backupRefreshGroups: BackupRefreshGroup[] = [];
+      const backupBackupProviders: BackupBackupProvider[] = [];
+      const backupTips: BackupTip[] = [];
+      const backupRecoupGroups: BackupRecoupGroup[] = [];
+
+      await tx.iter(Stores.reserves).forEach((reserve) => {
+        // FIXME: implement
+      });
+
+      await tx.iter(Stores.tips).forEach((tip) => {
+        backupTips.push({
+          exchange_base_url: tip.exchangeBaseUrl,
+          merchant_base_url: tip.merchantBaseUrl,
+          merchant_tip_id: tip.merchantTipId,
+          wallet_tip_id: tip.walletTipId,
+          secret_seed: tip.secretSeed,
+          selected_denoms: tip.denomsSel.selectedDenoms.map((x) => ({
+            count: x.count,
+            denom_pub_hash: x.denomPubHash,
+          })),
+          timestam_picked_up: tip.pickedUpTimestamp,
+          timestamp_accepted: tip.acceptedTimestamp,
+          timestamp_created: tip.createdTimestamp,
+          timestamp_expiration: tip.tipExpiration,
+          tip_amount_raw: Amounts.stringify(tip.tipAmountRaw),
+        });
+      });
+
+      await tx.iter(Stores.recoupGroups).forEach((recoupGroup) => {
+        backupRecoupGroups.push({
+          recoup_group_id: recoupGroup.recoupGroupId,
+          timestamp_started: recoupGroup.timestampStarted,
+          timestamp_finished: recoupGroup.timestampFinished,
+          coins: recoupGroup.coinPubs.map((x, i) => ({
+            coin_pub: x,
+            recoup_finished: recoupGroup.recoupFinishedPerCoin[i],
+            old_amount: Amounts.stringify(recoupGroup.oldAmountPerCoin[i]),
+          })),
+        });
+      });
+
+      await tx.iter(Stores.backupProviders).forEach((bp) => {
+        backupBackupProviders.push({
+          annual_fee: Amounts.stringify(bp.annualFee),
+          base_url: canonicalizeBaseUrl(bp.baseUrl),
+          pay_proposal_ids: [],
+          storage_limit_in_megabytes: bp.storageLimitInMegabytes,
+          supported_protocol_version: bp.supportedProtocolVersion,
+        });
+      });
 
       await tx.iter(Stores.coins).forEach((coin) => {
         let bcs: BackupCoinSource;
@@ -183,7 +261,7 @@ export async function exportBackup(
             break;
         }
 
-        const coins = (coinsByDenom[coin.denomPubHash] ??= []);
+        const coins = (backupCoinsByDenom[coin.denomPubHash] ??= []);
         coins.push({
           blinding_key: coin.blindingKey,
           coin_priv: coin.coinPriv,
@@ -195,11 +273,11 @@ export async function exportBackup(
       });
 
       await tx.iter(Stores.denominations).forEach((denom) => {
-        const backupDenoms = (denominationsByExchange[
+        const backupDenoms = (backupDenominationsByExchange[
           denom.exchangeBaseUrl
         ] ??= []);
         backupDenoms.push({
-          coins: coinsByDenom[denom.denomPubHash] ?? [],
+          coins: backupCoinsByDenom[denom.denomPubHash] ?? [],
           denom_pub: denom.denomPub,
           fee_deposit: Amounts.stringify(denom.feeDeposit),
           fee_refresh: Amounts.stringify(denom.feeRefresh),
@@ -247,7 +325,7 @@ export async function exportBackup(
           }
         });
 
-        exchanges.push({
+        backupExchanges.push({
           base_url: ex.baseUrl,
           accounts: ex.wireInfo.accounts.map((x) => ({
             payto_uri: x.payto_uri,
@@ -271,8 +349,132 @@ export async function exportBackup(
           })),
           tos_etag_accepted: ex.termsOfServiceAcceptedEtag,
           tos_etag_last: ex.termsOfServiceLastEtag,
-          denominations: denominationsByExchange[ex.baseUrl] ?? [],
-          reserves: reservesByExchange[ex.baseUrl] ?? [],
+          denominations: backupDenominationsByExchange[ex.baseUrl] ?? [],
+          reserves: backupReservesByExchange[ex.baseUrl] ?? [],
+        });
+      });
+
+      const purchaseProposalIdSet = new Set<string>();
+
+      await tx.iter(Stores.purchases).forEach((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;
+          }
+        }
+
+        backupPurchases.push({
+          clock_created: 1,
+          contract_terms_raw: purch.contractTermsRaw,
+          auto_refund_deadline: purch.autoRefundDeadline,
+          merchant_pay_sig: purch.merchantPaySig,
+          pay_coins: purch.payCoinSelection.coinPubs.map((x, i) => ({
+            coin_pub: x,
+            contribution: Amounts.stringify(
+              purch.payCoinSelection.coinContributions[i],
+            ),
+          })),
+          proposal_id: purch.proposalId,
+          refunds,
+          timestamp_accept: purch.timestampAccept,
+          timestamp_first_successful_pay: purch.timestampFirstSuccessfulPay,
+          timestamp_last_refund_status: purch.timestampLastRefundStatus,
+          abort_status:
+            purch.abortStatus === AbortStatus.None
+              ? undefined
+              : purch.abortStatus,
+          nonce_priv: purch.noncePriv,
+        });
+      });
+
+      await tx.iter(Stores.proposals).forEach((prop) => {
+        if (purchaseProposalIdSet.has(prop.proposalId)) {
+          return;
+        }
+        let propStatus: BackupProposalStatus;
+        switch (prop.proposalStatus) {
+          case ProposalStatus.ACCEPTED:
+            return;
+          case ProposalStatus.DOWNLOADING:
+          case ProposalStatus.PROPOSED:
+            propStatus = BackupProposalStatus.Proposed;
+            break;
+          case ProposalStatus.PERMANENTLY_FAILED:
+            propStatus = BackupProposalStatus.PermanentlyFailed;
+            break;
+          case ProposalStatus.REFUSED:
+            propStatus = BackupProposalStatus.Refused;
+            break;
+          case ProposalStatus.REPURCHASE:
+            propStatus = BackupProposalStatus.Repurchase;
+            break;
+        }
+        backupProposals.push({
+          claim_token: prop.claimToken,
+          nonce_priv: prop.noncePriv,
+          proposal_id: prop.noncePriv,
+          proposal_status: propStatus,
+          repurchase_proposal_id: prop.repurchaseProposalId,
+          timestamp: prop.timestamp,
+          contract_terms_raw: prop.download?.contractTermsRaw,
+          download_session_id: prop.downloadSessionId,
+        });
+      });
+
+      await tx.iter(Stores.refreshGroups).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.finishedPerCoin[i],
+            input_amount: Amounts.stringify(rg.inputPerCoin[i]),
+            refresh_session: refreshSession,
+          });
+        }
+
+        backupRefreshGroups.push({
+          reason: rg.reason as any,
+          refresh_group_id: rg.refreshGroupId,
+          timestamp_started: rg.timestampCreated,
+          timestamp_finished: rg.timestampFinished,
+          old_coins: oldCoins,
         });
       });
 
@@ -284,16 +486,16 @@ export async function exportBackup(
         schema_id: "gnu-taler-wallet-backup-content",
         schema_version: 1,
         clocks: bs.clocks,
-        exchanges: exchanges,
+        exchanges: backupExchanges,
         wallet_root_pub: bs.walletRootPub,
-        backup_providers: [],
+        backup_providers: backupBackupProviders,
         current_device_id: bs.deviceId,
-        proposals: [],
+        proposals: backupProposals,
         purchase_tombstones: [],
-        purchases: [],
-        recoup_groups: [],
-        refresh_groups: [],
-        tips: [],
+        purchases: backupPurchases,
+        recoup_groups: backupRecoupGroups,
+        refresh_groups: backupRefreshGroups,
+        tips: backupTips,
         timestamp: bs.lastBackupTimestamp,
         trusted_auditors: {},
         trusted_exchanges: {},
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts 
b/packages/taler-wallet-core/src/operations/refresh.ts
index 16b691ec..71cc78fa 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -711,6 +711,7 @@ export async function createRefreshGroup(
     retryInfo: initRetryInfo(),
     inputPerCoin,
     estimatedOutputPerCoin,
+    timestampCreated: getTimestampNow(),
   };
 
   if (oldCoinPubs.length == 0) {
diff --git a/packages/taler-wallet-core/src/operations/refund.ts 
b/packages/taler-wallet-core/src/operations/refund.ts
index 36b21b23..367b644a 100644
--- a/packages/taler-wallet-core/src/operations/refund.ts
+++ b/packages/taler-wallet-core/src/operations/refund.ts
@@ -158,6 +158,8 @@ async function applySuccessfulRefund(
     refundAmount: Amounts.parseOrThrow(r.refund_amount),
     refundFee: denom.feeRefund,
     totalRefreshCostBound,
+    coinPub: r.coin_pub,
+    rtransactionId: r.rtransaction_id,
   };
 }
 
@@ -208,6 +210,8 @@ async function storePendingRefund(
     refundAmount: Amounts.parseOrThrow(r.refund_amount),
     refundFee: denom.feeRefund,
     totalRefreshCostBound,
+    coinPub: r.coin_pub,
+    rtransactionId: r.rtransaction_id,
   };
 }
 
@@ -259,6 +263,8 @@ async function storeFailedRefund(
     refundAmount: Amounts.parseOrThrow(r.refund_amount),
     refundFee: denom.feeRefund,
     totalRefreshCostBound,
+    coinPub: r.coin_pub,
+    rtransactionId: r.rtransaction_id,
   };
 
   if (p.abortStatus === AbortStatus.AbortRefund) {
diff --git a/packages/taler-wallet-core/src/operations/tip.ts 
b/packages/taler-wallet-core/src/operations/tip.ts
index a5796382..bc10e346 100644
--- a/packages/taler-wallet-core/src/operations/tip.ts
+++ b/packages/taler-wallet-core/src/operations/tip.ts
@@ -25,10 +25,10 @@ import {
 import * as Amounts from "../util/amounts";
 import {
   Stores,
-  TipPlanchet,
   CoinRecord,
   CoinSourceType,
   CoinStatus,
+  DenominationRecord,
 } from "../types/dbTypes";
 import {
   getExchangeWithdrawalInfo,
@@ -50,6 +50,7 @@ import { checkDbInvariant } from "../util/invariants";
 import { TalerErrorCode } from "../TalerErrorCode";
 import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries";
 import { j2s } from "../util/helpers";
+import { DerivedTipPlanchet } from '../types/cryptoTypes';
 
 const logger = new Logger("operations/tip.ts");
 
@@ -201,39 +202,34 @@ async function processTipImpl(
 
   const denomsForWithdraw = tipRecord.denomsSel;
 
-  if (!tipRecord.planchets) {
-    const planchets: TipPlanchet[] = [];
-
-    for (const sd of denomsForWithdraw.selectedDenoms) {
-      const denom = await ws.db.get(Stores.denominations, [
-        tipRecord.exchangeBaseUrl,
-        sd.denomPubHash,
-      ]);
-      if (!denom) {
-        throw Error("denom does not exist anymore");
-      }
-      for (let i = 0; i < sd.count; i++) {
-        const r = await ws.cryptoApi.createTipPlanchet(denom);
-        planchets.push(r);
-      }
-    }
-    await ws.db.mutate(Stores.tips, walletTipId, (r) => {
-      if (!r.planchets) {
-        r.planchets = planchets;
-      }
-      return r;
-    });
-  }
-
   tipRecord = await ws.db.get(Stores.tips, walletTipId);
   checkDbInvariant(!!tipRecord, "tip record should be in database");
-  checkDbInvariant(!!tipRecord.planchets, "tip record should have planchets");
 
+  const planchets: DerivedTipPlanchet[] = [];
   // Planchets in the form that the merchant expects
-  const planchetsDetail: TipPlanchetDetail[] = tipRecord.planchets.map((p) => 
({
-    coin_ev: p.coinEv,
-    denom_pub_hash: p.denomPubHash,
-  }));
+  const planchetsDetail: TipPlanchetDetail[] = [];
+  const denomForPlanchet: { [index: number]: DenominationRecord} = [];
+
+  for (const dh of denomsForWithdraw.selectedDenoms) {
+    const denom = await ws.db.get(Stores.denominations, [
+      tipRecord.exchangeBaseUrl,
+      dh.denomPubHash,
+    ]);
+    checkDbInvariant(!!denom, "denomination should be in database");
+    denomForPlanchet[planchets.length] = denom;
+    for (let i = 0; i < dh.count; i++) {
+      const p = await ws.cryptoApi.createTipPlanchet({
+        denomPub: dh.denomPubHash,
+        planchetIndex: planchets.length,
+        secretSeed: tipRecord.secretSeed,
+      });
+      planchets.push(p);
+      planchetsDetail.push({
+        coin_ev: p.coinEv,
+        denom_pub_hash: denom.denomPubHash,
+      });
+    }
+  }
 
   const tipStatusUrl = new URL(
     `tips/${tipRecord.merchantTipId}/pickup`,
@@ -264,7 +260,7 @@ async function processTipImpl(
     codecForTipResponse(),
   );
 
-  if (response.blind_sigs.length !== tipRecord.planchets.length) {
+  if (response.blind_sigs.length !== planchets.length) {
     throw Error("number of tip responses does not match requested planchets");
   }
 
@@ -273,18 +269,19 @@ async function processTipImpl(
   for (let i = 0; i < response.blind_sigs.length; i++) {
     const blindedSig = response.blind_sigs[i].blind_sig;
 
-    const planchet = tipRecord.planchets[i];
+    const denom = denomForPlanchet[i];
+    const planchet = planchets[i];
 
     const denomSig = await ws.cryptoApi.rsaUnblind(
       blindedSig,
       planchet.blindingKey,
-      planchet.denomPub,
+      denom.denomPub,
     );
 
     const isValid = await ws.cryptoApi.rsaVerify(
       planchet.coinPub,
       denomSig,
-      planchet.denomPub,
+      denom.denomPub,
     );
 
     if (!isValid) {
@@ -312,9 +309,9 @@ async function processTipImpl(
         coinIndex: i,
         walletTipId: walletTipId,
       },
-      currentAmount: planchet.coinValue,
-      denomPub: planchet.denomPub,
-      denomPubHash: planchet.denomPubHash,
+      currentAmount: denom.value,
+      denomPub: denom.denomPub,
+      denomPubHash: denom.denomPubHash,
       denomSig: denomSig,
       exchangeBaseUrl: tipRecord.exchangeBaseUrl,
       status: CoinStatus.Fresh,
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts 
b/packages/taler-wallet-core/src/operations/transactions.ts
index e4cbdd8c..56e07a42 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -92,7 +92,6 @@ export async function getTransactions(
       Stores.purchases,
       Stores.refreshGroups,
       Stores.reserves,
-      Stores.reserveHistory,
       Stores.tips,
       Stores.withdrawalGroups,
       Stores.planchets,
diff --git a/packages/taler-wallet-core/src/types/backupTypes.ts 
b/packages/taler-wallet-core/src/types/backupTypes.ts
index daf2fbe5..09bc4670 100644
--- a/packages/taler-wallet-core/src/types/backupTypes.ts
+++ b/packages/taler-wallet-core/src/types/backupTypes.ts
@@ -24,6 +24,20 @@
  * 1. Exchange/auditor trust isn't exported yet
  *    (see https://bugs.gnunet.org/view.php?id=6448)
  * 2. Reports to the auditor (cryptographic proofs and/or diagnostics) aren't 
exported yet
+ * 3. "Ghost spends", where a coin is spent unexpectedly by another wallet
+ *    and a corresponding transaction (that is missing some details!) should
+ *    be added to the transaction history, aren't implemented yet.
+ * 4. Clocks for denom/coin selections aren't properly modeled yet.
+ *    (Needed for re-denomination of withdrawal / re-selection of coins)
+ * 5. Preferences about how currencies are to be displayed
+ *    aren't exported yet (and not even implemented in wallet-core).
+ * 6. Returning money to own bank account isn't supported/exported yet.
+ * 7. Peer-to-peer payments aren't supported yet.
+ *
+ * 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
  *
  * General considerations / decisions:
  * 1. Information about previously occurring errors and
@@ -318,6 +332,9 @@ export interface BackupRecoupGroup {
 
   /**
    * Timestamp when the recoup finished.
+   *
+   * (That means all coins have been recouped and coins to
+   * be refreshed have been put in a refresh group.)
    */
   timestamp_finished: Timestamp | undefined;
 
@@ -326,15 +343,9 @@ export interface BackupRecoupGroup {
    */
   coins: {
     coin_pub: string;
-    finished: boolean;
+    recoup_finished: boolean;
     old_amount: BackupAmountString;
   }[];
-
-  /**
-   * Public keys of coins that should be scheduled for refreshing
-   * after all individual recoups are done.
-   */
-  recoup_refresh_coins: string[];
 }
 
 /**
@@ -465,6 +476,11 @@ export interface BackupTip {
    */
   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.
@@ -502,15 +518,6 @@ export interface BackupTip {
    */
   merchant_base_url: string;
 
-  /**
-   * Planchets, the members included in TipPlanchetDetail will be sent to the
-   * merchant.
-   */
-  planchets?: {
-    blinding_key: string;
-    coin_priv: string;
-  }[];
-
   /**
    * Selected denominations.  Determines the effective tip amount.
    */
@@ -543,7 +550,10 @@ export interface BackupRefreshSession {
   /**
    * Hased denominations of the newly requested coins.
    */
-  new_denom_hashes: string[];
+  new_denoms: {
+    count: number;
+    denom_pub_hash: string;
+  }[];
 
   /**
    * Seed used to derive the planchets and
@@ -557,6 +567,39 @@ export interface BackupRefreshSession {
   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.
  *
@@ -570,35 +613,9 @@ export interface BackupRefreshGroup {
   /**
    * Details per old coin.
    */
-  old_coins: {
-    /**
-     * 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;
+  old_coins: BackupRefreshOldCoin[];
 
-    /**
-     * 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;
-  }[];
+  timestamp_started: Timestamp;
 
   /**
    * Timestamp when the refresh group finished.
@@ -741,22 +758,23 @@ export interface BackupPurchase {
    */
   contract_terms_raw: string;
 
+  /**
+   * Private key for the nonce.  Might eventually be used
+   * to prove ownership of the contract.
+   */
+  nonce_priv: string;
+
   pay_coins: {
     /**
      * Public keys of the coins that were selected.
      */
-    coin_pubs: string[];
-
-    /**
-     * Deposit permission signature of each coin.
-     */
-    coin_sigs: string[];
+    coin_pub: string;
 
     /**
      * Amount that each coin contributes.
      */
     contribution: BackupAmountString;
-  };
+  }[];
 
   /**
    * Timestamp of the first time that sending a payment to the merchant
@@ -1132,6 +1150,9 @@ export interface BackupReserveHistoryCreditItem {
   matched_exchange_transaction?: ReserveCreditTransaction;
 }
 
+/**
+ * Reserve history item for a withdrawal
+ */
 export interface BackupReserveHistoryWithdrawItem {
   type: WalletReserveHistoryItemType.Withdraw;
 
@@ -1141,7 +1162,7 @@ export interface BackupReserveHistoryWithdrawItem {
    * Hash of the blinded coin.
    *
    * When this value is set, it indicates that a withdrawal is active
-   * in the wallet for the
+   * in the wallet for the reserve.
    */
   expected_coin_ev_hash?: string;
 
@@ -1183,13 +1204,11 @@ export type BackupReserveHistoryItem =
   | BackupReserveHistoryRecoupItem
   | BackupReserveHistoryClosingItem;
 
-export enum ProposalStatus {
-  /**
-   * Not downloaded yet.
-   */
-  Downloading = "downloading",
+export enum BackupProposalStatus {
   /**
-   * Proposal downloaded, but the user needs to accept/reject it.
+   * Proposed (and either downloaded or not,
+   * depending on whether contract terms are present),
+   * but the user needs to accept/reject it.
    */
   Proposed = "proposed",
   /**
@@ -1202,6 +1221,8 @@ export enum ProposalStatus {
   Refused = "refused",
   /**
    * Downloading or processing the proposal has failed permanently.
+   *
+   * FIXME:  Should this be modeled as a "misbehavior report" instead?
    */
   PermanentlyFailed = "permanently-failed",
   /**
@@ -1235,11 +1256,6 @@ export interface BackupProposal {
    */
   nonce_priv: string;
 
-  /**
-   * Public key for the nonce.
-   */
-  nonce_pub: string;
-
   /**
    * Claim token initially given by the merchant.
    */
@@ -1248,7 +1264,7 @@ export interface BackupProposal {
   /**
    * Status of the proposal.
    */
-  proposal_status: ProposalStatus;
+  proposal_status: BackupProposalStatus;
 
   /**
    * Proposal that this one got "redirected" to as part of
diff --git a/packages/taler-wallet-core/src/types/cryptoTypes.ts 
b/packages/taler-wallet-core/src/types/cryptoTypes.ts
index a7f51ab5..98bdf92e 100644
--- a/packages/taler-wallet-core/src/types/cryptoTypes.ts
+++ b/packages/taler-wallet-core/src/types/cryptoTypes.ts
@@ -109,3 +109,19 @@ export interface DerivedRefreshSession {
    */
   meltValueWithFee: AmountJson;
 }
+
+export interface DeriveTipRequest {
+  secretSeed: string;
+  denomPub: string;
+  planchetIndex: number;
+}
+
+/**
+ * Tipping planchet stored in the database.
+ */
+export interface DerivedTipPlanchet {
+  blindingKey: string;
+  coinEv: string;
+  coinPriv: string;
+  coinPub: string;
+}
diff --git a/packages/taler-wallet-core/src/types/dbTypes.ts 
b/packages/taler-wallet-core/src/types/dbTypes.ts
index 18a1102b..3a42b8db 100644
--- a/packages/taler-wallet-core/src/types/dbTypes.ts
+++ b/packages/taler-wallet-core/src/types/dbTypes.ts
@@ -109,14 +109,6 @@ export interface WalletReserveHistoryCreditItem {
 export interface WalletReserveHistoryWithdrawItem {
   expectedAmount?: AmountJson;
 
-  /**
-   * Hash of the blinded coin.
-   *
-   * When this value is set, it indicates that a withdrawal is active
-   * in the wallet for the
-   */
-  expectedCoinEvHash?: string;
-
   type: WalletReserveHistoryItemType.Withdraw;
 
   /**
@@ -921,11 +913,9 @@ export interface TipRecord {
   merchantBaseUrl: string;
 
   /**
-   * Planchets, the members included in TipPlanchetDetail will be sent to the
-   * merchant.
+   * Denomination selection made by the wallet for picking up
+   * this tip.
    */
-  planchets?: TipPlanchet[];
-
   denomsSel: DenomSelectionState;
 
   /**
@@ -933,6 +923,11 @@ export interface TipRecord {
    */
   walletTipId: string;
 
+  /**
+   * Secret seed used to derive planchets for this tip.
+   */
+  secretSeed: string;
+
   /**
    * The merchant's identifier for this tip.
    */
@@ -984,6 +979,8 @@ export interface RefreshGroupRecord {
    */
   finishedPerCoin: boolean[];
 
+  timestampCreated: Timestamp;
+
   /**
    * Timestamp when the refresh session finished.
    */
@@ -1023,19 +1020,6 @@ export interface RefreshSessionRecord {
   norevealIndex?: number;
 }
 
-/**
- * Tipping planchet stored in the database.
- */
-export interface TipPlanchet {
-  blindingKey: string;
-  coinEv: string;
-  coinPriv: string;
-  coinPub: string;
-  coinValue: AmountJson;
-  denomPubHash: string;
-  denomPub: string;
-}
-
 /**
  * Wire fee for one wire method as stored in the
  * wallet's database.
@@ -1106,6 +1090,7 @@ export interface WalletRefundItemCommon {
   obtainedTime: Timestamp;
 
   refundAmount: AmountJson;
+
   refundFee: AmountJson;
 
   /**
@@ -1116,6 +1101,10 @@ export interface WalletRefundItemCommon {
    * coin are refreshed in the same refresh operation.
    */
   totalRefreshCostBound: AmountJson;
+
+  coinPub: string;
+
+  rtransactionId: number;
 }
 
 /**
@@ -1266,11 +1255,24 @@ export interface PurchaseRecord {
    */
   proposalId: string;
 
+  /**
+   * Private key for the nonce.
+   */
+  noncePriv: string;
+
+  /**
+   * Public key for the nonce.
+   */
+  noncePub: string;
+
   /**
    * Contract terms we got from the merchant.
    */
   contractTermsRaw: string;
 
+  /**
+   * Parsed contract terms.
+   */
   contractData: WalletContractData;
 
   /**
diff --git a/packages/taler-wallet-core/src/util/reserveHistoryUtil.ts 
b/packages/taler-wallet-core/src/util/reserveHistoryUtil.ts
index 855b71a3..60823e1e 100644
--- a/packages/taler-wallet-core/src/util/reserveHistoryUtil.ts
+++ b/packages/taler-wallet-core/src/util/reserveHistoryUtil.ts
@@ -14,6 +14,12 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
+/**
+ * Helpers for dealing with reserve histories.
+ *
+ * @author Florian Dold <dold@taler.net>
+ */
+
 /**
  * Imports.
  */
@@ -31,11 +37,8 @@ import { deepCopy } from "./helpers";
 import { AmountJson } from "../util/amounts";
 
 /**
- * Helpers for dealing with reserve histories.
- *
- * @author Florian Dold <dold@taler.net>
+ * Result of a reserve reconciliation.
  */
-
 export interface ReserveReconciliationResult {
   /**
    * The wallet's local history reconciled with the exchange's reserve history.

-- 
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]