gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: adding global fee info from e


From: gnunet
Subject: [taler-wallet-core] branch master updated: adding global fee info from exchange
Date: Wed, 12 Oct 2022 19:38:42 +0200

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

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

The following commit(s) were added to refs/heads/master by this push:
     new cb4420244 adding global fee info from exchange
cb4420244 is described below

commit cb44202440313ea4405fbc74f4588144134a0821
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Wed Oct 12 14:36:30 2022 -0300

    adding global fee info from exchange
---
 packages/taler-util/src/backupTypes.ts             |  21 +++-
 packages/taler-util/src/payto.ts                   |   8 +-
 packages/taler-util/src/talerCrypto.ts             |   1 +
 packages/taler-util/src/talerTypes.ts              | 139 ++++++++++++++++-----
 .../src/crypto/cryptoImplementation.ts             |  62 ++++++++-
 packages/taler-wallet-core/src/db.ts               |   5 +
 .../src/operations/backup/export.ts                |   1 +
 .../src/operations/backup/import.ts                |   1 +
 .../taler-wallet-core/src/operations/exchanges.ts  |  36 ++++++
 9 files changed, 233 insertions(+), 41 deletions(-)

diff --git a/packages/taler-util/src/backupTypes.ts 
b/packages/taler-util/src/backupTypes.ts
index 620f476ad..8222bdeab 100644
--- a/packages/taler-util/src/backupTypes.ts
+++ b/packages/taler-util/src/backupTypes.ts
@@ -909,7 +909,7 @@ export interface BackupPurchase {
 
   /**
    * Signature on the contract terms.
-   * 
+   *
    * FIXME: Better name needed.
    */
   merchant_sig?: string;
@@ -1086,6 +1086,23 @@ export interface BackupExchangeWireFee {
   sig: string;
 }
 
+/**
+ * Global fee as stored in the wallet's database.
+ *
+ */
+export interface BackupExchangeGlobalFees {
+  start_date: TalerProtocolTimestamp;
+  end_date: TalerProtocolTimestamp;
+  kyc_fee: BackupAmountString;
+  history_fee: BackupAmountString;
+  account_fee: BackupAmountString;
+  purse_fee: BackupAmountString;
+  history_expiration: TalerProtocolDuration;
+  account_kyc_timeout: TalerProtocolDuration;
+  purse_account_limit: number;
+  purse_timeout: TalerProtocolDuration;
+  master_sig: string;
+}
 /**
  * Structure of one exchange signing key in the /keys response.
  */
@@ -1206,6 +1223,8 @@ export interface BackupExchangeDetails {
 
   wire_fees: BackupExchangeWireFee[];
 
+  global_fees: BackupExchangeGlobalFees[];
+
   /**
    * Bank accounts offered by the exchange;
    */
diff --git a/packages/taler-util/src/payto.ts b/packages/taler-util/src/payto.ts
index c5a58022d..b474e533c 100644
--- a/packages/taler-util/src/payto.ts
+++ b/packages/taler-util/src/payto.ts
@@ -63,9 +63,9 @@ export function addPaytoQueryParams(
 ): string {
   const [acct, search] = s.slice(paytoPfx.length).split("?");
   const searchParams = new URLSearchParams(search || "");
-  const keys = Object.keys(params)
+  const keys = Object.keys(params);
   if (keys.length === 0) {
-    return paytoPfx + acct
+    return paytoPfx + acct;
   }
   for (const k of keys) {
     searchParams.set(k, params[k]);
@@ -83,9 +83,7 @@ export function stringifyPaytoUri(p: PaytoUri): string {
   const url = `${paytoPfx}${p.targetType}/${p.targetPath}`;
   const paramList = !p.params ? [] : Object.entries(p.params);
   if (paramList.length > 0) {
-    const search = paramList
-      .map(([key, value]) => `${key}=${value}`)
-      .join("&");
+    const search = paramList.map(([key, value]) => 
`${key}=${value}`).join("&");
     return `${url}?${search}`;
   }
   return url;
diff --git a/packages/taler-util/src/talerCrypto.ts 
b/packages/taler-util/src/talerCrypto.ts
index 28fdab8e3..84842a69f 100644
--- a/packages/taler-util/src/talerCrypto.ts
+++ b/packages/taler-util/src/talerCrypto.ts
@@ -793,6 +793,7 @@ export enum TalerSignaturePurpose {
   MERCHANT_TRACK_TRANSACTION = 1103,
   WALLET_RESERVE_WITHDRAW = 1200,
   WALLET_COIN_DEPOSIT = 1201,
+  GLOBAL_FEES = 1022,
   MASTER_DENOMINATION_KEY_VALIDITY = 1025,
   MASTER_WIRE_FEES = 1028,
   MASTER_WIRE_DETAILS = 1030,
diff --git a/packages/taler-util/src/talerTypes.ts 
b/packages/taler-util/src/talerTypes.ts
index 471c7e927..1cb4e2bde 100644
--- a/packages/taler-util/src/talerTypes.ts
+++ b/packages/taler-util/src/talerTypes.ts
@@ -43,6 +43,7 @@ import {
 import { strcmp } from "./helpers.js";
 import { AgeCommitmentProof, Edx25519PublicKeyEnc } from "./talerCrypto.js";
 import {
+  codecForAbsoluteTime,
   codecForDuration,
   codecForTimestamp,
   TalerProtocolDuration,
@@ -757,8 +758,64 @@ export class ExchangeKeysJson {
   version: string;
 
   reserve_closing_delay: TalerProtocolDuration;
+
+  global_fees: GlobalFees[];
 }
 
+export interface GlobalFees {
+  // What date (inclusive) does these fees go into effect?
+  start_date: TalerProtocolTimestamp;
+
+  // What date (exclusive) does this fees stop going into effect?
+  end_date: TalerProtocolTimestamp;
+
+  // KYC fee, charged when a user wants to create an account.
+  // The first year of the account_annual_fee after the KYC is
+  // always included.
+  kyc_fee: AmountString;
+
+  // Account history fee, charged when a user wants to
+  // obtain a reserve/account history.
+  history_fee: AmountString;
+
+  // Annual fee charged for having an open account at the
+  // exchange.  Charged to the account.  If the account
+  // balance is insufficient to cover this fee, the account
+  // is automatically deleted/closed. (Note that the exchange
+  // will keep the account history around for longer for
+  // regulatory reasons.)
+  account_fee: AmountString;
+
+  // Purse fee, charged only if a purse is abandoned
+  // and was not covered by the account limit.
+  purse_fee: AmountString;
+
+  // How long will the exchange preserve the account history?
+  // After an account was deleted/closed, the exchange will
+  // retain the account history for legal reasons until this time.
+  history_expiration: TalerProtocolDuration;
+
+  // How long does the exchange promise to keep funds
+  // an account for which the KYC has never happened
+  // after a purse was merged into an account? Basically,
+  // after this time funds in an account without KYC are
+  // forfeit.
+  account_kyc_timeout: TalerProtocolDuration;
+
+  // Non-negative number of concurrent purses that any
+  // account holder is allowed to create without having
+  // to pay the purse_fee.
+  purse_account_limit: number;
+
+  // How long does an exchange keep a purse around after a purse
+  // has expired (or been successfully merged)?  A 'GET' request
+  // for a purse will succeed until the purse expiration time
+  // plus this value.
+  purse_timeout: TalerProtocolDuration;
+
+  // Signature of TALER_GlobalFeesPS.
+  master_sig: string;
+}
 /**
  * Wire fees as announced by the exchange.
  */
@@ -1188,13 +1245,6 @@ export namespace DenominationPubKey {
   }
 }
 
-export const codecForDenominationPubKey = () =>
-  buildCodecForUnion<DenominationPubKey>()
-    .discriminateOn("cipher")
-    .alternative(DenomKeyType.Rsa, codecForRsaDenominationPubKey())
-    .alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey())
-    .build("DenominationPubKey");
-
 export const codecForRsaDenominationPubKey = () =>
   buildCodecForObject<RsaDenominationPubKey>()
     .property("cipher", codecForConstString(DenomKeyType.Rsa))
@@ -1209,6 +1259,13 @@ export const codecForCsDenominationPubKey = () =>
     .property("age_mask", codecForNumber())
     .build("CsDenominationPubKey");
 
+export const codecForDenominationPubKey = () =>
+  buildCodecForUnion<DenominationPubKey>()
+    .discriminateOn("cipher")
+    .alternative(DenomKeyType.Rsa, codecForRsaDenominationPubKey())
+    .alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey())
+    .build("DenominationPubKey");
+
 export const codecForBankWithdrawalOperationPostResponse =
   (): Codec<BankWithdrawalOperationPostResponse> =>
     buildCodecForObject<BankWithdrawalOperationPostResponse>()
@@ -1385,6 +1442,21 @@ export const codecForExchangeSigningKey = (): 
Codec<ExchangeSignKeyJson> =>
     .property("stamp_expire", codecForTimestamp)
     .build("ExchangeSignKeyJson");
 
+export const codecForGlobalFees = (): Codec<GlobalFees> =>
+  buildCodecForObject<GlobalFees>()
+    .property("start_date", codecForTimestamp)
+    .property("end_date", codecForTimestamp)
+    .property("kyc_fee", codecForAmountString())
+    .property("history_fee", codecForAmountString())
+    .property("account_fee", codecForAmountString())
+    .property("purse_fee", codecForAmountString())
+    .property("history_expiration", codecForDuration)
+    .property("account_kyc_timeout", codecForDuration)
+    .property("purse_account_limit", codecForNumber())
+    .property("purse_timeout", codecForDuration)
+    .property("master_sig", codecForString())
+    .build("GlobalFees");
+
 export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> =>
   buildCodecForObject<ExchangeKeysJson>()
     .property("denoms", codecForList(codecForDenomination()))
@@ -1395,6 +1467,7 @@ export const codecForExchangeKeysJson = (): 
Codec<ExchangeKeysJson> =>
     .property("signkeys", codecForList(codecForExchangeSigningKey()))
     .property("version", codecForString())
     .property("reserve_closing_delay", codecForDuration)
+    .property("global_fees", codecForList(codecForGlobalFees()))
     .build("ExchangeKeysJson");
 
 export const codecForWireFeesJson = (): Codec<WireFeesJson> =>
@@ -1583,6 +1656,32 @@ export interface AbortResponse {
   refunds: MerchantAbortPayRefundStatus[];
 }
 
+export const codecForMerchantAbortPayRefundSuccessStatus =
+  (): Codec<MerchantAbortPayRefundSuccessStatus> =>
+    buildCodecForObject<MerchantAbortPayRefundSuccessStatus>()
+      .property("exchange_pub", codecForString())
+      .property("exchange_sig", codecForString())
+      .property("exchange_status", codecForConstNumber(200))
+      .property("type", codecForConstString("success"))
+      .build("MerchantAbortPayRefundSuccessStatus");
+
+export const codecForMerchantAbortPayRefundFailureStatus =
+  (): Codec<MerchantAbortPayRefundFailureStatus> =>
+    buildCodecForObject<MerchantAbortPayRefundFailureStatus>()
+      .property("exchange_code", codecForNumber())
+      .property("exchange_reply", codecForAny())
+      .property("exchange_status", codecForNumber())
+      .property("type", codecForConstString("failure"))
+      .build("MerchantAbortPayRefundFailureStatus");
+
+export const codecForMerchantAbortPayRefundStatus =
+  (): Codec<MerchantAbortPayRefundStatus> =>
+    buildCodecForUnion<MerchantAbortPayRefundStatus>()
+      .discriminateOn("type")
+      .alternative("success", codecForMerchantAbortPayRefundSuccessStatus())
+      .alternative("failure", codecForMerchantAbortPayRefundFailureStatus())
+      .build("MerchantAbortPayRefundStatus");
+
 export const codecForAbortResponse = (): Codec<AbortResponse> =>
   buildCodecForObject<AbortResponse>()
     .property("refunds", codecForList(codecForMerchantAbortPayRefundStatus()))
@@ -1629,32 +1728,6 @@ export interface MerchantAbortPayRefundSuccessStatus {
   exchange_pub: string;
 }
 
-export const codecForMerchantAbortPayRefundSuccessStatus =
-  (): Codec<MerchantAbortPayRefundSuccessStatus> =>
-    buildCodecForObject<MerchantAbortPayRefundSuccessStatus>()
-      .property("exchange_pub", codecForString())
-      .property("exchange_sig", codecForString())
-      .property("exchange_status", codecForConstNumber(200))
-      .property("type", codecForConstString("success"))
-      .build("MerchantAbortPayRefundSuccessStatus");
-
-export const codecForMerchantAbortPayRefundFailureStatus =
-  (): Codec<MerchantAbortPayRefundFailureStatus> =>
-    buildCodecForObject<MerchantAbortPayRefundFailureStatus>()
-      .property("exchange_code", codecForNumber())
-      .property("exchange_reply", codecForAny())
-      .property("exchange_status", codecForNumber())
-      .property("type", codecForConstString("failure"))
-      .build("MerchantAbortPayRefundFailureStatus");
-
-export const codecForMerchantAbortPayRefundStatus =
-  (): Codec<MerchantAbortPayRefundStatus> =>
-    buildCodecForUnion<MerchantAbortPayRefundStatus>()
-      .discriminateOn("type")
-      .alternative("success", codecForMerchantAbortPayRefundSuccessStatus())
-      .alternative("failure", codecForMerchantAbortPayRefundFailureStatus())
-      .build("MerchantAbortPayRefundStatus");
-
 export interface TalerConfigResponse {
   name: string;
   version: string;
diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts 
b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
index bfc48d961..98bb6c9cb 100644
--- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
@@ -51,6 +51,7 @@ import {
   encryptContractForMerge,
   ExchangeProtocolVersion,
   getRandomBytes,
+  GlobalFees,
   hash,
   HashCodeString,
   hashCoinEv,
@@ -74,6 +75,7 @@ import {
   rsaVerify,
   setupTipPlanchet,
   stringToBytes,
+  TalerProtocolDuration,
   TalerProtocolTimestamp,
   TalerSignaturePurpose,
   UnblindedSignature,
@@ -142,6 +144,10 @@ export interface TalerCryptoInterface {
 
   isValidWireFee(req: WireFeeValidationRequest): Promise<ValidationResult>;
 
+  isValidGlobalFees(
+    req: GlobalFeesValidationRequest,
+  ): Promise<ValidationResult>;
+
   isValidDenom(req: DenominationValidationRequest): Promise<ValidationResult>;
 
   isValidWireAccount(
@@ -152,7 +158,7 @@ export interface TalerCryptoInterface {
     req: ContractTermsValidationRequest,
   ): Promise<ValidationResult>;
 
-  createEddsaKeypair(req: {}): Promise<EddsaKeypair>;
+  createEddsaKeypair(req: unknown): Promise<EddsaKeypair>;
 
   eddsaGetPublic(req: EddsaGetPublicRequest): Promise<EddsaGetPublicResponse>;
 
@@ -283,12 +289,17 @@ export const nullCrypto: TalerCryptoInterface = {
   ): Promise<ValidationResult> {
     throw new Error("Function not implemented.");
   },
+  isValidGlobalFees: function (
+    req: GlobalFeesValidationRequest,
+  ): Promise<ValidationResult> {
+    throw new Error("Function not implemented.");
+  },
   isValidContractTermsSignature: function (
     req: ContractTermsValidationRequest,
   ): Promise<ValidationResult> {
     throw new Error("Function not implemented.");
   },
-  createEddsaKeypair: function (req: {}): Promise<EddsaKeypair> {
+  createEddsaKeypair: function (req: unknown): Promise<EddsaKeypair> {
     throw new Error("Function not implemented.");
   },
   eddsaGetPublic: function (req: EddsaGetPublicRequest): Promise<EddsaKeypair> 
{
@@ -484,6 +495,11 @@ export interface WireFeeValidationRequest {
   masterPub: string;
 }
 
+export interface GlobalFeesValidationRequest {
+  gf: GlobalFees;
+  masterPub: string;
+}
+
 export interface DenominationValidationRequest {
   denom: DenominationRecord;
   masterPub: string;
@@ -887,6 +903,30 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
     return { valid: eddsaVerify(p, sig, pub) };
   },
 
+  /**
+   * Check if a global fee is correctly signed.
+   */
+  async isValidGlobalFees(
+    tci: TalerCryptoInterfaceR,
+    req: GlobalFeesValidationRequest,
+  ): Promise<ValidationResult> {
+    const { gf, masterPub } = req;
+    const p = buildSigPS(TalerSignaturePurpose.GLOBAL_FEES)
+      .put(timestampRoundedToBuffer(gf.start_date))
+      .put(timestampRoundedToBuffer(gf.end_date))
+      .put(durationRoundedToBuffer(gf.purse_timeout))
+      .put(durationRoundedToBuffer(gf.account_kyc_timeout))
+      .put(durationRoundedToBuffer(gf.history_expiration))
+      .put(amountToBuffer(Amounts.parseOrThrow(gf.history_fee)))
+      .put(amountToBuffer(Amounts.parseOrThrow(gf.kyc_fee)))
+      .put(amountToBuffer(Amounts.parseOrThrow(gf.account_fee)))
+      .put(amountToBuffer(Amounts.parseOrThrow(gf.purse_fee)))
+      .put(bufferForUint32(gf.purse_account_limit))
+      .build();
+    const sig = decodeCrock(gf.master_sig);
+    const pub = decodeCrock(masterPub);
+    return { valid: eddsaVerify(p, sig, pub) };
+  },
   /**
    * Check if the signature of a denomination is valid.
    */
@@ -1630,6 +1670,24 @@ function timestampRoundedToBuffer(ts: 
TalerProtocolTimestamp): Uint8Array {
   return new Uint8Array(b);
 }
 
+function durationRoundedToBuffer(ts: TalerProtocolDuration): Uint8Array {
+  const b = new ArrayBuffer(8);
+  const v = new DataView(b);
+  // The buffer we sign over represents the timestamp in microseconds.
+  if (typeof v.setBigUint64 !== "undefined") {
+    const s = BigInt(ts.d_us);
+    v.setBigUint64(0, s);
+  } else {
+    const s = ts.d_us === "forever" ? bigint.zero : bigint(ts.d_us);
+    const arr = s.toArray(2 ** 8).value;
+    let offset = 8 - arr.length;
+    for (let i = 0; i < arr.length; i++) {
+      v.setUint8(offset++, arr[i]);
+    }
+  }
+  return new Uint8Array(b);
+}
+
 export interface EddsaSignRequest {
   msg: string;
   priv: string;
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index ec11f4d47..e266275c1 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -45,6 +45,7 @@ import {
   Location,
   WireInfo,
   DenominationInfo,
+  GlobalFees,
 } from "@gnu-taler/taler-util";
 import { RetryInfo, RetryTags } from "./util/retries.js";
 import { Event, IDBDatabase } from "@gnu-taler/idb-bridge";
@@ -424,6 +425,10 @@ export interface ExchangeDetailsRecord {
 
   reserveClosingDelay: TalerProtocolDuration;
 
+  /**
+   * Fees for exchange services
+   */
+  globalFees: GlobalFees[];
   /**
    * Signing keys we got from the exchange, can also contain
    * older signing keys that are not returned by /keys anymore.
diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts 
b/packages/taler-wallet-core/src/operations/backup/export.ts
index 2e2a1c4b4..f611a2380 100644
--- a/packages/taler-wallet-core/src/operations/backup/export.ts
+++ b/packages/taler-wallet-core/src/operations/backup/export.ts
@@ -345,6 +345,7 @@ export async function exportBackup(
             stamp_expire: x.stamp_expire,
             stamp_start: x.stamp_start,
           })),
+          global_fees: ex.globalFees,
           tos_accepted_etag: ex.termsOfServiceAcceptedEtag,
           tos_accepted_timestamp: ex.termsOfServiceAcceptedTimestamp,
           denominations:
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts 
b/packages/taler-wallet-core/src/operations/backup/import.ts
index 3ee3680fe..ee8cb6f6c 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -405,6 +405,7 @@ export async function importBackup(
             masterPublicKey: backupExchangeDetails.master_public_key,
             protocolVersion: backupExchangeDetails.protocol_version,
             reserveClosingDelay: backupExchangeDetails.reserve_closing_delay,
+            globalFees: backupExchangeDetails.global_fees,
             signingKeys: backupExchangeDetails.signing_keys.map((x) => ({
               key: x.key,
               master_sig: x.master_sig,
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts 
b/packages/taler-wallet-core/src/operations/exchanges.ts
index 9a6c72577..a26c14fcc 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -32,6 +32,7 @@ import {
   ExchangeDenomination,
   ExchangeSignKeyJson,
   ExchangeWireJson,
+  GlobalFees,
   hashDenomPub,
   j2s,
   LibtoolVersion,
@@ -269,6 +270,32 @@ async function validateWireInfo(
   };
 }
 
+async function validateGlobalFees(
+  ws: InternalWalletState,
+  fees: GlobalFees[],
+  masterPub: string,
+): Promise<GlobalFees[]> {
+  for (const gf of fees) {
+    logger.trace("validating exchange global fees");
+    let isValid = false;
+    if (ws.insecureTrustExchange) {
+      isValid = true;
+    } else {
+      const { valid: v } = await ws.cryptoApi.isValidGlobalFees({
+        masterPub,
+        gf,
+      });
+      isValid = v;
+    }
+
+    if (!isValid) {
+      throw Error("exchange global fees signature invalid: " + gf.master_sig);
+    }
+  }
+
+  return fees;
+}
+
 export interface ExchangeInfo {
   wire: ExchangeWireJson;
   keys: ExchangeKeysDownloadResult;
@@ -359,6 +386,7 @@ interface ExchangeKeysDownloadResult {
   expiry: TalerProtocolTimestamp;
   recoup: Recoup[];
   listIssueDate: TalerProtocolTimestamp;
+  globalFees: GlobalFees[];
 }
 
 /**
@@ -432,6 +460,7 @@ async function downloadExchangeKeysInfo(
     ),
     recoup: exchangeKeysJsonUnchecked.recoup ?? [],
     listIssueDate: exchangeKeysJsonUnchecked.list_issue_date,
+    globalFees: exchangeKeysJsonUnchecked.global_fees,
   };
 }
 
@@ -552,6 +581,12 @@ export async function updateExchangeFromUrlHandler(
     keysInfo.masterPublicKey,
   );
 
+  const globalFees = await validateGlobalFees(
+    ws,
+    keysInfo.globalFees,
+    keysInfo.masterPublicKey,
+  );
+
   logger.info("finished validating exchange /wire info");
 
   const tosDownload = await downloadTosFromAcceptedFormat(
@@ -594,6 +629,7 @@ export async function updateExchangeFromUrlHandler(
         protocolVersion: keysInfo.protocolVersion,
         signingKeys: keysInfo.signingKeys,
         reserveClosingDelay: keysInfo.reserveClosingDelay,
+        globalFees,
         exchangeBaseUrl: r.baseUrl,
         wireInfo,
         termsOfServiceText: tosDownload.tosText,

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