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: tipping protocol chan


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet: tipping protocol change / merchant version info
Date: Tue, 23 Nov 2021 23:51:15 +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 ae8af3f2 wallet: tipping protocol change / merchant version info
ae8af3f2 is described below

commit ae8af3f27c0ed1746c49a7608fe05af24ae8a18b
Author: Florian Dold <florian@dold.me>
AuthorDate: Tue Nov 23 23:51:12 2021 +0100

    wallet: tipping protocol change / merchant version info
---
 packages/taler-util/src/backupTypes.ts             |   2 +
 packages/taler-util/src/libtool-version.test.ts    |   2 +-
 packages/taler-util/src/libtool-version.ts         |  74 +++++++------
 packages/taler-util/src/talerCrypto.ts             |   4 +-
 packages/taler-util/src/talerTypes.ts              |  63 +++++++++--
 packages/taler-wallet-cli/src/harness/harness.ts   | 122 ++++++++++-----------
 packages/taler-wallet-core/src/common.ts           |  18 +++
 packages/taler-wallet-core/src/db.ts               |   6 +
 .../src/operations/backup/export.ts                |   1 +
 .../src/operations/backup/import.ts                |   1 +
 .../taler-wallet-core/src/operations/exchanges.ts  |   8 +-
 .../taler-wallet-core/src/operations/merchants.ts  |  68 ++++++++++++
 packages/taler-wallet-core/src/operations/tip.ts   |  46 ++++++--
 .../taler-wallet-core/src/operations/withdraw.ts   |   6 +-
 packages/taler-wallet-core/src/wallet.ts           |   9 ++
 15 files changed, 303 insertions(+), 127 deletions(-)

diff --git a/packages/taler-util/src/backupTypes.ts 
b/packages/taler-util/src/backupTypes.ts
index ecdd6fdf..8663850c 100644
--- a/packages/taler-util/src/backupTypes.ts
+++ b/packages/taler-util/src/backupTypes.ts
@@ -1102,6 +1102,8 @@ export interface BackupExchange {
 
   currency: string;
 
+  protocol_version_range: string;
+
   /**
    * Time when the pointer to the exchange details
    * was last updated.
diff --git a/packages/taler-util/src/libtool-version.test.ts 
b/packages/taler-util/src/libtool-version.test.ts
index d3564251..c1683f0d 100644
--- a/packages/taler-util/src/libtool-version.test.ts
+++ b/packages/taler-util/src/libtool-version.test.ts
@@ -14,7 +14,7 @@
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import * as LibtoolVersion from "./libtool-version.js";
+import { LibtoolVersion } from "./libtool-version.js";
 
 import test from "ava";
 
diff --git a/packages/taler-util/src/libtool-version.ts 
b/packages/taler-util/src/libtool-version.ts
index 5e9d0b74..17d2bbbd 100644
--- a/packages/taler-util/src/libtool-version.ts
+++ b/packages/taler-util/src/libtool-version.ts
@@ -40,49 +40,51 @@ interface Version {
   age: number;
 }
 
-/**
- * Compare two libtool-style version strings.
- */
-export function compare(
-  me: string,
-  other: string,
-): VersionMatchResult | undefined {
-  const meVer = parseVersion(me);
-  const otherVer = parseVersion(other);
-
-  if (!(meVer && otherVer)) {
-    return undefined;
-  }
+export namespace LibtoolVersion {
+  /**
+   * Compare two libtool-style version strings.
+   */
+  export function compare(
+    me: string,
+    other: string,
+  ): VersionMatchResult | undefined {
+    const meVer = parseVersion(me);
+    const otherVer = parseVersion(other);
 
-  const compatible =
-    meVer.current - meVer.age <= otherVer.current &&
-    meVer.current >= otherVer.current - otherVer.age;
+    if (!(meVer && otherVer)) {
+      return undefined;
+    }
 
-  const currentCmp = Math.sign(meVer.current - otherVer.current);
+    const compatible =
+      meVer.current - meVer.age <= otherVer.current &&
+      meVer.current >= otherVer.current - otherVer.age;
 
-  return { compatible, currentCmp };
-}
+    const currentCmp = Math.sign(meVer.current - otherVer.current);
 
-function parseVersion(v: string): Version | undefined {
-  const [currentStr, revisionStr, ageStr, ...rest] = v.split(":");
-  if (rest.length !== 0) {
-    return undefined;
+    return { compatible, currentCmp };
   }
-  const current = Number.parseInt(currentStr);
-  const revision = Number.parseInt(revisionStr);
-  const age = Number.parseInt(ageStr);
 
-  if (Number.isNaN(current)) {
-    return undefined;
-  }
+  function parseVersion(v: string): Version | undefined {
+    const [currentStr, revisionStr, ageStr, ...rest] = v.split(":");
+    if (rest.length !== 0) {
+      return undefined;
+    }
+    const current = Number.parseInt(currentStr);
+    const revision = Number.parseInt(revisionStr);
+    const age = Number.parseInt(ageStr);
 
-  if (Number.isNaN(revision)) {
-    return undefined;
-  }
+    if (Number.isNaN(current)) {
+      return undefined;
+    }
 
-  if (Number.isNaN(age)) {
-    return undefined;
-  }
+    if (Number.isNaN(revision)) {
+      return undefined;
+    }
 
-  return { current, revision, age };
+    if (Number.isNaN(age)) {
+      return undefined;
+    }
+
+    return { current, revision, age };
+  }
 }
diff --git a/packages/taler-util/src/talerCrypto.ts 
b/packages/taler-util/src/talerCrypto.ts
index b107786c..c20ce72a 100644
--- a/packages/taler-util/src/talerCrypto.ts
+++ b/packages/taler-util/src/talerCrypto.ts
@@ -24,7 +24,7 @@
 import * as nacl from "./nacl-fast.js";
 import { kdf } from "./kdf.js";
 import bigint from "big-integer";
-import { DenominationPubKey } from "./talerTypes.js";
+import { DenominationPubKey, DenomKeyType } from "./talerTypes.js";
 
 export function getRandomBytes(n: number): Uint8Array {
   return nacl.randomBytes(n);
@@ -350,7 +350,7 @@ export function hash(d: Uint8Array): Uint8Array {
 }
 
 export function hashDenomPub(pub: DenominationPubKey): Uint8Array {
-  if (pub.cipher !== 1) {
+  if (pub.cipher !== DenomKeyType.Rsa) {
     throw Error("unsupported cipher");
   }
   const pubBuf = decodeCrock(pub.rsa_public_key);
diff --git a/packages/taler-util/src/talerTypes.ts 
b/packages/taler-util/src/talerTypes.ts
index 04d70048..bd9c67d7 100644
--- a/packages/taler-util/src/talerTypes.ts
+++ b/packages/taler-util/src/talerTypes.ts
@@ -598,9 +598,9 @@ export interface TipPickupRequest {
 
 /**
  * Reserve signature, defined as separate class to facilitate
- * schema validation with "@Checkable".
+ * schema validation.
  */
-export interface BlindSigWrapper {
+export interface MerchantBlindSigWrapperV1 {
   /**
    * Reserve signature.
    */
@@ -611,11 +611,26 @@ export interface BlindSigWrapper {
  * Response of the merchant
  * to the TipPickupRequest.
  */
-export interface TipResponse {
+export interface MerchantTipResponseV1 {
   /**
    * The order of the signatures matches the planchets list.
    */
-  blind_sigs: BlindSigWrapper[];
+  blind_sigs: MerchantBlindSigWrapperV1[];
+}
+
+export interface MerchantBlindSigWrapperV2 {
+  blind_sig: BlindedDenominationSignature;
+}
+
+/**
+ * Response of the merchant
+ * to the TipPickupRequest.
+ */
+export interface MerchantTipResponseV2 {
+  /**
+   * The order of the signatures matches the planchets list.
+   */
+  blind_sigs: MerchantBlindSigWrapperV2[];
 }
 
 /**
@@ -1032,13 +1047,14 @@ export interface BankWithdrawalOperationPostResponse {
 export type DenominationPubKey = RsaDenominationPubKey | CsDenominationPubKey;
 
 export interface RsaDenominationPubKey {
-  cipher: 1;
+  cipher: DenomKeyType.Rsa;
   rsa_public_key: string;
   age_mask?: number;
 }
 
 export interface CsDenominationPubKey {
-  cipher: 2;
+  cipher: DenomKeyType.ClauseSchnorr;
+  // FIXME: finish definition
 }
 
 export const codecForDenominationPubKey = () =>
@@ -1201,15 +1217,25 @@ export const codecForMerchantRefundResponse = (): 
Codec<MerchantRefundResponse>
     .property("refunds", codecForList(codecForMerchantRefundPermission()))
     .build("MerchantRefundResponse");
 
-export const codecForBlindSigWrapper = (): Codec<BlindSigWrapper> =>
-  buildCodecForObject<BlindSigWrapper>()
+export const codecForMerchantBlindSigWrapperV1 = (): 
Codec<MerchantBlindSigWrapperV1> =>
+  buildCodecForObject<MerchantBlindSigWrapperV1>()
     .property("blind_sig", codecForString())
     .build("BlindSigWrapper");
 
-export const codecForTipResponse = (): Codec<TipResponse> =>
-  buildCodecForObject<TipResponse>()
-    .property("blind_sigs", codecForList(codecForBlindSigWrapper()))
-    .build("TipResponse");
+export const codecForMerchantTipResponseV1 = (): Codec<MerchantTipResponseV1> 
=>
+  buildCodecForObject<MerchantTipResponseV1>()
+    .property("blind_sigs", codecForList(codecForMerchantBlindSigWrapperV1()))
+    .build("MerchantTipResponseV1");
+
+export const codecForBlindSigWrapperV2 = (): Codec<MerchantBlindSigWrapperV2> 
=>
+  buildCodecForObject<MerchantBlindSigWrapperV2>()
+    .property("blind_sig", codecForBlindedDenominationSignature())
+    .build("MerchantBlindSigWrapperV2");
+
+export const codecForMerchantTipResponseV2 = (): Codec<MerchantTipResponseV2> 
=>
+  buildCodecForObject<MerchantTipResponseV2>()
+    .property("blind_sigs", codecForList(codecForBlindSigWrapperV2()))
+    .build("MerchantTipResponseV2");
 
 export const codecForRecoup = (): Codec<Recoup> =>
   buildCodecForObject<Recoup>()
@@ -1510,3 +1536,16 @@ export const codecForKeysManagementResponse = (): 
Codec<FutureKeysResponse> =>
     .property("denom_secmod_public_key", codecForAny())
     .property("signkey_secmod_public_key", codecForAny())
     .build("FutureKeysResponse");
+
+export interface MerchantConfigResponse {
+  currency: string;
+  name: string;
+  version: string;
+}
+
+export const codecForMerchantConfigResponse = (): 
Codec<MerchantConfigResponse> =>
+  buildCodecForObject<MerchantConfigResponse>()
+    .property("currency", codecForString())
+    .property("name", codecForString())
+    .property("version", codecForString())
+    .build("MerchantConfigResponse");
diff --git a/packages/taler-wallet-cli/src/harness/harness.ts 
b/packages/taler-wallet-cli/src/harness/harness.ts
index 4944e347..9a33d572 100644
--- a/packages/taler-wallet-cli/src/harness/harness.ts
+++ b/packages/taler-wallet-cli/src/harness/harness.ts
@@ -66,7 +66,7 @@ import {
   encodeCrock,
   getRandomBytes,
   hash,
-  stringToBytes
+  stringToBytes,
 } from "@gnu-taler/taler-util";
 import { CoinConfig } from "./denomStructures.js";
 import { LibeufinNexusApi, LibeufinSandboxApi } from "./libeufin-apis.js";
@@ -445,7 +445,7 @@ export async function pingProc(
   }
   while (true) {
     try {
-      console.log(`pinging ${serviceName}`);
+      console.log(`pinging ${serviceName} at ${url}`);
       const resp = await axios.get(url);
       console.log(`service ${serviceName} available`);
       return;
@@ -556,7 +556,6 @@ export namespace BankApi {
       debitAccountPayto: string;
     },
   ) {
-
     let maybeBaseUrl = bank.baseUrl;
     if (process.env.WALLET_HARNESS_WITH_EUFIN) {
       maybeBaseUrl = (bank as EufinBankService).baseUrlDemobank;
@@ -618,7 +617,6 @@ export namespace BankApi {
   }
 }
 
-
 class BankServiceBase {
   proc: ProcessWrapper | undefined;
 
@@ -641,7 +639,6 @@ class EufinBankService extends BankServiceBase implements 
BankServiceInterface {
     gc: GlobalTestState,
     bc: BankConfig,
   ): Promise<EufinBankService> {
-  
     return new EufinBankService(gc, bc, "foo");
   }
 
@@ -650,16 +647,14 @@ class EufinBankService extends BankServiceBase implements 
BankServiceInterface {
   }
   get nexusPort() {
     return this.bankConfig.httpPort + 1000;
-  
   }
 
   get nexusDbConn(): string {
-    return 
`jdbc:sqlite:${this.globalTestState.testDir}/libeufin-nexus.sqlite3`; 
+    return 
`jdbc:sqlite:${this.globalTestState.testDir}/libeufin-nexus.sqlite3`;
   }
 
   get sandboxDbConn(): string {
-    return 
`jdbc:sqlite:${this.globalTestState.testDir}/libeufin-sandbox.sqlite3`; 
-  
+    return 
`jdbc:sqlite:${this.globalTestState.testDir}/libeufin-sandbox.sqlite3`;
   }
 
   get nexusBaseUrl(): string {
@@ -673,9 +668,9 @@ class EufinBankService extends BankServiceBase implements 
BankServiceInterface {
 
   get baseUrlAccessApi(): string {
     let url = new URL("access-api/", this.baseUrlDemobank);
-    return url.href; 
+    return url.href;
   }
-  
+
   get baseUrlNetloc(): string {
     return `http://localhost:${this.bankConfig.httpPort}/`;
   }
@@ -686,7 +681,7 @@ class EufinBankService extends BankServiceBase implements 
BankServiceInterface {
 
   async setSuggestedExchange(
     e: ExchangeServiceInterface,
-    exchangePayto: string
+    exchangePayto: string,
   ) {
     await sh(
       this.globalTestState,
@@ -712,11 +707,9 @@ class EufinBankService extends BankServiceBase implements 
BankServiceInterface {
      */
     await this.start();
     await this.pingUntilAvailable();
-    await LibeufinSandboxApi.createDemobankAccount(
-      accountName,
-      password,
-      { baseUrl: this.baseUrlAccessApi }
-    ); 
+    await LibeufinSandboxApi.createDemobankAccount(accountName, password, {
+      baseUrl: this.baseUrlAccessApi,
+    });
     let bankAccountLabel = accountName;
     await LibeufinSandboxApi.createDemobankEbicsSubscriber(
       {
@@ -725,47 +718,49 @@ class EufinBankService extends BankServiceBase implements 
BankServiceInterface {
         partnerID: "exchangeEbicsPartner",
       },
       bankAccountLabel,
-      { baseUrl: this.baseUrlDemobank }
+      { baseUrl: this.baseUrlDemobank },
     );
-     
+
     await LibeufinNexusApi.createUser(
       { baseUrl: this.nexusBaseUrl },
       {
         username: accountName,
-       password: password
-      }
+        password: password,
+      },
     );
     await LibeufinNexusApi.createEbicsBankConnection(
       { baseUrl: this.nexusBaseUrl },
       {
         name: "ebics-connection", // connection name.
-        ebicsURL: (new URL("ebicsweb", this.baseUrlNetloc)).href,
+        ebicsURL: new URL("ebicsweb", this.baseUrlNetloc).href,
         hostID: "talertestEbicsHost",
         userID: "exchangeEbicsUser",
         partnerID: "exchangeEbicsPartner",
-      }
+      },
     );
     await LibeufinNexusApi.connectBankConnection(
-      { baseUrl: this.nexusBaseUrl }, "ebics-connection"
+      { baseUrl: this.nexusBaseUrl },
+      "ebics-connection",
     );
     await LibeufinNexusApi.fetchAccounts(
-      { baseUrl: this.nexusBaseUrl }, "ebics-connection"
+      { baseUrl: this.nexusBaseUrl },
+      "ebics-connection",
     );
     await LibeufinNexusApi.importConnectionAccount(
       { baseUrl: this.nexusBaseUrl },
       "ebics-connection", // connection name
       accountName, // offered account label
-      `${accountName}-nexus-label` // bank account label at Nexus
+      `${accountName}-nexus-label`, // bank account label at Nexus
     );
     await LibeufinNexusApi.createTwgFacade(
       { baseUrl: this.nexusBaseUrl },
       {
         name: "exchange-facade",
-       connectionName: "ebics-connection",
+        connectionName: "ebics-connection",
         accountName: `${accountName}-nexus-label`,
-       currency: "EUR",
-        reserveTransferLevel: "report"
-      }
+        currency: "EUR",
+        reserveTransferLevel: "report",
+      },
     );
     await LibeufinNexusApi.postPermission(
       { baseUrl: this.nexusBaseUrl },
@@ -778,7 +773,7 @@ class EufinBankService extends BankServiceBase implements 
BankServiceInterface {
           resourceId: "exchange-facade", // facade name
           permissionName: "facade.talerWireGateway.transfer",
         },
-      }
+      },
     );
     await LibeufinNexusApi.postPermission(
       { baseUrl: this.nexusBaseUrl },
@@ -791,7 +786,7 @@ class EufinBankService extends BankServiceBase implements 
BankServiceInterface {
           resourceId: "exchange-facade", // facade name
           permissionName: "facade.talerWireGateway.history",
         },
-      }
+      },
     );
     // Set fetch task.
     await LibeufinNexusApi.postTask(
@@ -804,8 +799,9 @@ class EufinBankService extends BankServiceBase implements 
BankServiceInterface {
         params: {
           level: "all",
           rangeType: "all",
+        },
       },
-    });
+    );
     await LibeufinNexusApi.postTask(
       { baseUrl: this.nexusBaseUrl },
       `${accountName}-nexus-label`,
@@ -814,14 +810,16 @@ class EufinBankService extends BankServiceBase implements 
BankServiceInterface {
         cronspec: "* * *",
         type: "submit",
         params: {},
-      }
+      },
     );
-    let facadesResp = await LibeufinNexusApi.getAllFacades({ baseUrl: 
this.nexusBaseUrl });
+    let facadesResp = await LibeufinNexusApi.getAllFacades({
+      baseUrl: this.nexusBaseUrl,
+    });
     let accountInfoResp = await LibeufinSandboxApi.demobankAccountInfo(
       "admin",
       "secret",
       { baseUrl: this.baseUrlAccessApi },
-      accountName // bank account label.
+      accountName, // bank account label.
     );
     return {
       accountName: accountName,
@@ -840,7 +838,7 @@ class EufinBankService extends BankServiceBase implements 
BankServiceInterface {
      * them if they weren't launched earlier.
      */
 
-    // Only go ahead if BOTH aren't running. 
+    // Only go ahead if BOTH aren't running.
     if (this.sandboxProc || this.nexusProc) {
       console.log("Nexus or Sandbox already running, not taking any action.");
       return;
@@ -864,7 +862,7 @@ class EufinBankService extends BankServiceBase implements 
BankServiceInterface {
         LIBEUFIN_SANDBOX_DB_CONNECTION: this.sandboxDbConn,
         LIBEUFIN_SANDBOX_ADMIN_PASSWORD: "secret",
       },
-    ); 
+    );
     await runCommand(
       this.globalTestState,
       "libeufin-nexus-superuser",
@@ -889,7 +887,7 @@ class EufinBankService extends BankServiceBase implements 
BankServiceInterface {
     await this.pingUntilAvailable();
     LibeufinSandboxApi.createEbicsHost(
       { baseUrl: this.baseUrlNetloc },
-      "talertestEbicsHost"
+      "talertestEbicsHost",
     );
   }
 
@@ -897,12 +895,12 @@ class EufinBankService extends BankServiceBase implements 
BankServiceInterface {
     await pingProc(
       this.sandboxProc,
       `http://localhost:${this.bankConfig.httpPort}`,
-      "libeufin-sandbox"
+      "libeufin-sandbox",
     );
     await pingProc(
       this.nexusProc,
       `${this.nexusBaseUrl}/config`,
-      "libeufin-nexus"
+      "libeufin-nexus",
     );
   }
 }
@@ -999,7 +997,6 @@ class PybankService extends BankServiceBase implements 
BankServiceInterface {
   }
 }
 
-
 /**
  * Return a euFin or a pyBank implementation of
  * the exported BankService class.  This allows
@@ -1007,19 +1004,18 @@ class PybankService extends BankServiceBase implements 
BankServiceInterface {
  * on a particular env variable.
  */
 function getBankServiceImpl(): {
-  prototype: typeof PybankService.prototype,
-  create: typeof PybankService.create
+  prototype: typeof PybankService.prototype;
+  create: typeof PybankService.create;
 } {
-
-  if (process.env.WALLET_HARNESS_WITH_EUFIN) 
+  if (process.env.WALLET_HARNESS_WITH_EUFIN)
     return {
       prototype: EufinBankService.prototype,
-      create: EufinBankService.create
-    }
+      create: EufinBankService.create,
+    };
   return {
     prototype: PybankService.prototype,
-    create: PybankService.create
-  }
+    create: PybankService.create,
+  };
 }
 
 export type BankService = PybankService;
@@ -2088,10 +2084,8 @@ export class WalletCli {
 }
 
 export function getRandomIban(salt: string | null = null): string {
-
   function getBban(salt: string | null): string {
-    if (!salt)
-      return Math.random().toString().substring(2, 6);
+    if (!salt) return Math.random().toString().substring(2, 6);
     let hashed = hash(stringToBytes(salt));
     let ret = "";
     for (let i = 0; i < hashed.length; i++) {
@@ -2101,19 +2095,21 @@ export function getRandomIban(salt: string | null = 
null): string {
   }
 
   let cc_no_check = "131400"; // == DE00
-  let bban = getBban(salt)
-  let check_digits = (98 - (Number.parseInt(`${bban}${cc_no_check}`) % 
97)).toString();
+  let bban = getBban(salt);
+  let check_digits = (
+    98 -
+    (Number.parseInt(`${bban}${cc_no_check}`) % 97)
+  ).toString();
   if (check_digits.length == 1) {
     check_digits = `0${check_digits}`;
   }
-  return `DE${check_digits}${bban}`;   
+  return `DE${check_digits}${bban}`;
 }
 
 // Only used in one tipping test.
 export function getWireMethod(): string {
-  if (process.env.WALLET_HARNESS_WITH_EUFIN)
-    return "iban"
-  return "x-taler-bank"
+  if (process.env.WALLET_HARNESS_WITH_EUFIN) return "iban";
+  return "x-taler-bank";
 }
 
 /**
@@ -2122,10 +2118,12 @@ export function getWireMethod(): string {
  */
 export function getPayto(label: string): string {
   if (process.env.WALLET_HARNESS_WITH_EUFIN)
-    return 
`payto://iban/SANDBOXX/${getRandomIban(label)}?receiver-name=${label}`
-  return `payto://x-taler-bank/${label}`
+    return `payto://iban/SANDBOXX/${getRandomIban(
+      label,
+    )}?receiver-name=${label}`;
+  return `payto://x-taler-bank/${label}`;
 }
 
 function waitMs(ms: number): Promise<void> {
-  return new Promise(resolve => setTimeout(resolve, ms));
+  return new Promise((resolve) => setTimeout(resolve, ms));
 }
diff --git a/packages/taler-wallet-core/src/common.ts 
b/packages/taler-wallet-core/src/common.ts
index dd8542de..81c43cf1 100644
--- a/packages/taler-wallet-core/src/common.ts
+++ b/packages/taler-wallet-core/src/common.ts
@@ -51,6 +51,21 @@ export interface TrustInfo {
   isAudited: boolean;
 }
 
+export interface MerchantInfo {
+  supportsMerchantProtocolV1: boolean;
+  supportsMerchantProtocolV2: boolean;
+}
+
+/**
+ * Interface for merchant-related operations.
+ */
+export interface MerchantOperations {
+  getMerchantInfo(
+    ws: InternalWalletState,
+    merchantBaseUrl: string,
+  ): Promise<MerchantInfo>;
+}
+
 /**
  * Interface for exchange-related operations.
  */
@@ -131,8 +146,11 @@ export interface InternalWalletState {
 
   initCalled: boolean;
 
+  merchantInfoCache: Record<string, MerchantInfo>;
+
   exchangeOps: ExchangeOperations;
   recoupOps: RecoupOperations;
+  merchantOps: MerchantOperations;
 
   db: DbAccess<typeof WalletStoresV1>;
   http: HttpRequestLibrary;
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index 483cb16c..ff47cf30 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -484,8 +484,14 @@ export interface WireInfo {
 
 export interface ExchangeDetailsPointer {
   masterPublicKey: string;
+
   currency: string;
 
+  /**
+   * Last observed protocol version range offered by the exchange.
+   */
+  protocolVersionRange: string;
+
   /**
    * Timestamp when the (masterPublicKey, currency) pointer
    * has been updated.
diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts 
b/packages/taler-wallet-core/src/operations/backup/export.ts
index a66bc2e8..75724dca 100644
--- a/packages/taler-wallet-core/src/operations/backup/export.ts
+++ b/packages/taler-wallet-core/src/operations/backup/export.ts
@@ -273,6 +273,7 @@ export async function exportBackup(
           currency: dp.currency,
           master_public_key: dp.masterPublicKey,
           update_clock: dp.updateClock,
+          protocol_version_range: dp.protocolVersionRange,
         });
       });
 
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts 
b/packages/taler-wallet-core/src/operations/backup/import.ts
index e8e1de0b..40fa4cde 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -267,6 +267,7 @@ export async function importBackup(
             currency: backupExchange.currency,
             masterPublicKey: backupExchange.master_public_key,
             updateClock: backupExchange.update_clock,
+            protocolVersionRange: backupExchange.protocol_version_range,
           },
           permanent: true,
           retryInfo: initRetryInfo(),
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts 
b/packages/taler-wallet-core/src/operations/exchanges.ts
index c170c546..638af813 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -23,7 +23,6 @@ import {
   canonicalizeBaseUrl,
   codecForExchangeKeysJson,
   codecForExchangeWireJson,
-  compare,
   Denomination,
   Duration,
   durationFromSpec,
@@ -40,6 +39,7 @@ import {
   TalerErrorDetails,
   Timestamp,
   hashDenomPub,
+  LibtoolVersion,
 } from "@gnu-taler/taler-util";
 import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util";
 import { CryptoApi } from "../crypto/workers/cryptoApi.js";
@@ -365,7 +365,10 @@ async function downloadKeysInfo(
 
   const protocolVersion = exchangeKeysJson.version;
 
-  const versionRes = compare(WALLET_EXCHANGE_PROTOCOL_VERSION, 
protocolVersion);
+  const versionRes = LibtoolVersion.compare(
+    WALLET_EXCHANGE_PROTOCOL_VERSION,
+    protocolVersion,
+  );
   if (versionRes?.compatible != true) {
     const opErr = makeErrorDetails(
       TalerErrorCode.WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE,
@@ -548,6 +551,7 @@ async function updateExchangeFromUrlImpl(
         masterPublicKey: details.masterPublicKey,
         // FIXME: only change if pointer really changed
         updateClock: getTimestampNow(),
+        protocolVersionRange: keysInfo.protocolVersion,
       };
       await tx.exchanges.put(r);
       await tx.exchangeDetails.put(details);
diff --git a/packages/taler-wallet-core/src/operations/merchants.ts 
b/packages/taler-wallet-core/src/operations/merchants.ts
new file mode 100644
index 00000000..d12417c7
--- /dev/null
+++ b/packages/taler-wallet-core/src/operations/merchants.ts
@@ -0,0 +1,68 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 Taler Systems S.A..
+
+ 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/>
+ */
+
+/**
+ * Imports.
+ */
+import {
+  canonicalizeBaseUrl,
+  Logger,
+  URL,
+  codecForMerchantConfigResponse,
+  LibtoolVersion,
+} from "@gnu-taler/taler-util";
+import { InternalWalletState, MerchantInfo } from "../common.js";
+import { readSuccessResponseJsonOrThrow } from "../index.js";
+
+const logger = new Logger("taler-wallet-core:merchants.ts");
+
+export async function getMerchantInfo(
+  ws: InternalWalletState,
+  merchantBaseUrl: string,
+): Promise<MerchantInfo> {
+  const canonBaseUrl = canonicalizeBaseUrl(merchantBaseUrl);
+
+  const existingInfo = ws.merchantInfoCache[canonBaseUrl];
+  if (existingInfo) {
+    return existingInfo;
+  }
+
+  const configUrl = new URL("config", canonBaseUrl);
+  const resp = await ws.http.get(configUrl.href);
+
+  const configResp = await readSuccessResponseJsonOrThrow(
+    resp,
+    codecForMerchantConfigResponse(),
+  );
+
+  logger.info(
+    `merchant "${canonBaseUrl}" reports protocol ${configResp.version}"`,
+  );
+
+  const merchantInfo: MerchantInfo = {
+    supportsMerchantProtocolV1: !!LibtoolVersion.compare(
+      "1:0:0",
+      configResp.version,
+    )?.compatible,
+    supportsMerchantProtocolV2: !!LibtoolVersion.compare(
+      "2:0:0",
+      configResp.version,
+    )?.compatible,
+  };
+
+  ws.merchantInfoCache[canonBaseUrl] = merchantInfo;
+  return merchantInfo;
+}
diff --git a/packages/taler-wallet-core/src/operations/tip.ts 
b/packages/taler-wallet-core/src/operations/tip.ts
index 07ce00d2..0253930e 100644
--- a/packages/taler-wallet-core/src/operations/tip.ts
+++ b/packages/taler-wallet-core/src/operations/tip.ts
@@ -27,10 +27,12 @@ import {
   NotificationType,
   TipPlanchetDetail,
   TalerErrorCode,
-  codecForTipResponse,
+  codecForMerchantTipResponseV1,
   Logger,
   URL,
   DenomKeyType,
+  BlindedDenominationSignature,
+  codecForMerchantTipResponseV2,
 } from "@gnu-taler/taler-util";
 import { DerivedTipPlanchet } from "../crypto/cryptoTypes.js";
 import {
@@ -304,31 +306,57 @@ async function processTipImpl(
     return;
   }
 
-  const response = await readSuccessResponseJsonOrThrow(
-    merchantResp,
-    codecForTipResponse(),
+  // FIXME: Do this earlier?
+  const merchantInfo = await ws.merchantOps.getMerchantInfo(
+    ws,
+    tipRecord.merchantBaseUrl,
   );
 
-  if (response.blind_sigs.length !== planchets.length) {
+  let blindedSigs: BlindedDenominationSignature[] = [];
+
+  if (merchantInfo.supportsMerchantProtocolV2) {
+    const response = await readSuccessResponseJsonOrThrow(
+      merchantResp,
+      codecForMerchantTipResponseV2(),
+    );
+    blindedSigs = response.blind_sigs.map((x) => x.blind_sig);
+  } else if (merchantInfo.supportsMerchantProtocolV1) {
+    const response = await readSuccessResponseJsonOrThrow(
+      merchantResp,
+      codecForMerchantTipResponseV1(),
+    );
+    blindedSigs = response.blind_sigs.map((x) => ({
+      cipher: DenomKeyType.Rsa,
+      blinded_rsa_signature: x.blind_sig,
+    }));
+  } else {
+    throw Error("unsupported merchant protocol version");
+  }
+
+  if (blindedSigs.length !== planchets.length) {
     throw Error("number of tip responses does not match requested planchets");
   }
 
   const newCoinRecords: CoinRecord[] = [];
 
-  for (let i = 0; i < response.blind_sigs.length; i++) {
-    const blindedSig = response.blind_sigs[i].blind_sig;
+  for (let i = 0; i < blindedSigs.length; i++) {
+    const blindedSig = blindedSigs[i];
 
     const denom = denomForPlanchet[i];
     checkLogicInvariant(!!denom);
     const planchet = planchets[i];
     checkLogicInvariant(!!planchet);
 
-    if (denom.denomPub.cipher !== 1) {
+    if (denom.denomPub.cipher !== DenomKeyType.Rsa) {
+      throw Error("unsupported cipher");
+    }
+
+    if (blindedSig.cipher !== DenomKeyType.Rsa) {
       throw Error("unsupported cipher");
     }
 
     const denomSigRsa = await ws.cryptoApi.rsaUnblind(
-      blindedSig,
+      blindedSig.blinded_rsa_signature,
       planchet.blindingKey,
       denom.denomPub.rsa_public_key,
     );
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index 57bd49d2..a5a8653c 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -24,7 +24,6 @@ import {
   codecForTalerConfigResponse,
   codecForWithdrawOperationStatusResponse,
   codecForWithdrawResponse,
-  compare,
   durationFromSpec,
   ExchangeListItem,
   getDurationRemaining,
@@ -42,6 +41,7 @@ import {
   WithdrawUriInfoResponse,
   VersionMatchResult,
   DenomKeyType,
+  LibtoolVersion,
 } from "@gnu-taler/taler-util";
 import {
   CoinRecord,
@@ -285,7 +285,7 @@ export async function getBankWithdrawalInfo(
     codecForTalerConfigResponse(),
   );
 
-  const versionRes = compare(
+  const versionRes = LibtoolVersion.compare(
     WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
     config.version,
   );
@@ -985,7 +985,7 @@ export async function getExchangeWithdrawalInfo(
 
   let versionMatch;
   if (exchangeDetails.protocolVersion) {
-    versionMatch = compare(
+    versionMatch = LibtoolVersion.compare(
       WALLET_EXCHANGE_PROTOCOL_VERSION,
       exchangeDetails.protocolVersion,
     );
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index cd2dd7f1..44591a26 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -99,6 +99,8 @@ import {
 import {
   ExchangeOperations,
   InternalWalletState,
+  MerchantInfo,
+  MerchantOperations,
   NotificationListener,
   RecoupOperations,
 } from "./common.js";
@@ -180,6 +182,7 @@ import {
   HttpRequestLibrary,
   readSuccessResponseJsonOrThrow,
 } from "./util/http.js";
+import { getMerchantInfo } from "./operations/merchants.js";
 
 const builtinAuditors: AuditorTrustRecord[] = [
   {
@@ -1069,6 +1072,8 @@ class InternalWalletStateImpl implements 
InternalWalletState {
   memoProcessDeposit: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
   cryptoApi: CryptoApi;
 
+  merchantInfoCache: Record<string, MerchantInfo> = {};
+
   timerGroup: TimerGroup = new TimerGroup();
   latch = new AsyncCondition();
   stopped = false;
@@ -1088,6 +1093,10 @@ class InternalWalletStateImpl implements 
InternalWalletState {
     processRecoupGroup: processRecoupGroup,
   };
 
+  merchantOps: MerchantOperations = {
+    getMerchantInfo: getMerchantInfo,
+  };
+
   /**
    * Promises that are waiting for a particular resource.
    */

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