diff options
Diffstat (limited to 'packages')
21 files changed, 299 insertions, 117 deletions
diff --git a/packages/taler-util/src/backupTypes.ts b/packages/taler-util/src/backupTypes.ts index 70e52e63b..ecdd6fdf8 100644 --- a/packages/taler-util/src/backupTypes.ts +++ b/packages/taler-util/src/backupTypes.ts @@ -53,6 +53,7 @@  /**   * Imports.   */ +import { DenominationPubKey, UnblindedSignature } from "./talerTypes.js";  import { Duration, Timestamp } from "./time.js";  /** @@ -440,7 +441,7 @@ export interface BackupCoin {    /**     * Unblinded signature by the exchange.     */ -  denom_sig: string; +  denom_sig: UnblindedSignature;    /**     * Amount that's left on the coin. @@ -831,7 +832,7 @@ export interface BackupDenomination {    /**     * The denomination public key.     */ -  denom_pub: string; +  denom_pub: DenominationPubKey;    /**     * Fee for withdrawing. diff --git a/packages/taler-util/src/helpers.ts b/packages/taler-util/src/helpers.ts index 089602c9d..6c836c482 100644 --- a/packages/taler-util/src/helpers.ts +++ b/packages/taler-util/src/helpers.ts @@ -94,7 +94,7 @@ export function canonicalJson(obj: any): string {  /**   * Lexically compare two strings.   */ -export function strcmp(s1: string, s2: string): number { +export function strcmp(s1: string, s2: string): -1 | 0 | 1 {    if (s1 < s2) {      return -1;    } @@ -113,15 +113,14 @@ export function j2s(x: any): string {  /**   * Use this to filter null or undefined from an array in a type-safe fashion - *  + *   * example:   * const array: Array<T | undefined> = [undefined, null]   * const filtered: Array<T> = array.filter(notEmpty) - *  - * @param value  - * @returns  + * + * @param value + * @returns   */  export function notEmpty<T>(value: T | null | undefined): value is T {    return value !== null && value !== undefined;  } - diff --git a/packages/taler-util/src/talerCrypto.ts b/packages/taler-util/src/talerCrypto.ts index d8ac75dc0..b107786cd 100644 --- a/packages/taler-util/src/talerCrypto.ts +++ b/packages/taler-util/src/talerCrypto.ts @@ -24,6 +24,7 @@  import * as nacl from "./nacl-fast.js";  import { kdf } from "./kdf.js";  import bigint from "big-integer"; +import { DenominationPubKey } from "./talerTypes.js";  export function getRandomBytes(n: number): Uint8Array {    return nacl.randomBytes(n); @@ -348,6 +349,20 @@ export function hash(d: Uint8Array): Uint8Array {    return nacl.hash(d);  } +export function hashDenomPub(pub: DenominationPubKey): Uint8Array { +  if (pub.cipher !== 1) { +    throw Error("unsupported cipher"); +  } +  const pubBuf = decodeCrock(pub.rsa_public_key); +  const hashInputBuf = new ArrayBuffer(pubBuf.length + 4 + 4); +  const uint8ArrayBuf = new Uint8Array(hashInputBuf); +  const dv = new DataView(hashInputBuf); +  dv.setUint32(0, pub.age_mask ?? 0); +  dv.setUint32(4, pub.cipher); +  uint8ArrayBuf.set(pubBuf, 8); +  return nacl.hash(uint8ArrayBuf); +} +  export function eddsaSign(msg: Uint8Array, eddsaPriv: Uint8Array): Uint8Array {    const pair = nacl.crypto_sign_keyPair_fromSeed(eddsaPriv);    return nacl.sign_detached(msg, pair.secretKey); diff --git a/packages/taler-util/src/talerTypes.ts b/packages/taler-util/src/talerTypes.ts index 56110ec1e..04d700483 100644 --- a/packages/taler-util/src/talerTypes.ts +++ b/packages/taler-util/src/talerTypes.ts @@ -59,7 +59,7 @@ export class Denomination {    /**     * Public signing key of the denomination.     */ -  denom_pub: string; +  denom_pub: DenominationPubKey;    /**     * Fee for withdrawing. @@ -158,7 +158,7 @@ export interface RecoupRequest {    /**     * Signature over the coin public key by the denomination.     */ -  denom_sig: string; +  denom_sig: UnblindedSignature;    /**     * Coin public key of the coin we want to refund. @@ -198,6 +198,11 @@ export interface RecoupConfirmation {    old_coin_pub?: string;  } +export interface UnblindedSignature { +  cipher: DenomKeyType.Rsa; +  rsa_signature: string; +} +  /**   * Deposit permission for a single coin.   */ @@ -213,7 +218,7 @@ export interface CoinDepositPermission {    /**     * Signature made by the denomination public key.     */ -  ub_sig: string; +  ub_sig: UnblindedSignature;    /**     * The denomination public key associated with this coin.     */ @@ -779,8 +784,38 @@ export class TipPickupGetResponse {    expiration: Timestamp;  } +export enum DenomKeyType { +  Rsa = 1, +  ClauseSchnorr = 2, +} + +export interface RsaBlindedDenominationSignature { +  cipher: DenomKeyType.Rsa; +  blinded_rsa_signature: string; +} + +export interface CSBlindedDenominationSignature { +  cipher: DenomKeyType.ClauseSchnorr; +} + +export type BlindedDenominationSignature = +  | RsaBlindedDenominationSignature +  | CSBlindedDenominationSignature; + +export const codecForBlindedDenominationSignature = () => +  buildCodecForUnion<BlindedDenominationSignature>() +    .discriminateOn("cipher") +    .alternative(1, codecForRsaBlindedDenominationSignature()) +    .build("BlindedDenominationSignature"); + +export const codecForRsaBlindedDenominationSignature = () => +  buildCodecForObject<RsaBlindedDenominationSignature>() +    .property("cipher", codecForConstNumber(1)) +    .property("blinded_rsa_signature", codecForString()) +    .build("RsaBlindedDenominationSignature"); +  export class WithdrawResponse { -  ev_sig: string; +  ev_sig: BlindedDenominationSignature;  }  /** @@ -792,7 +827,7 @@ export interface CoinDumpJson {      /**       * The coin's denomination's public key.       */ -    denom_pub: string; +    denom_pub: DenominationPubKey;      /**       * Hash of denom_pub.       */ @@ -875,7 +910,7 @@ export interface ExchangeMeltResponse {  }  export interface ExchangeRevealItem { -  ev_sig: string; +  ev_sig: BlindedDenominationSignature;  }  export interface ExchangeRevealResponse { @@ -994,6 +1029,30 @@ export interface BankWithdrawalOperationPostResponse {    transfer_done: boolean;  } +export type DenominationPubKey = RsaDenominationPubKey | CsDenominationPubKey; + +export interface RsaDenominationPubKey { +  cipher: 1; +  rsa_public_key: string; +  age_mask?: number; +} + +export interface CsDenominationPubKey { +  cipher: 2; +} + +export const codecForDenominationPubKey = () => +  buildCodecForUnion<DenominationPubKey>() +    .discriminateOn("cipher") +    .alternative(1, codecForRsaDenominationPubKey()) +    .build("DenominationPubKey"); + +export const codecForRsaDenominationPubKey = () => +  buildCodecForObject<RsaDenominationPubKey>() +    .property("cipher", codecForConstNumber(1)) +    .property("rsa_public_key", codecForString()) +    .build("DenominationPubKey"); +  export const codecForBankWithdrawalOperationPostResponse = (): Codec<BankWithdrawalOperationPostResponse> =>    buildCodecForObject<BankWithdrawalOperationPostResponse>()      .property("transfer_done", codecForBoolean()) @@ -1008,7 +1067,7 @@ export type CoinPublicKeyString = string;  export const codecForDenomination = (): Codec<Denomination> =>    buildCodecForObject<Denomination>()      .property("value", codecForString()) -    .property("denom_pub", codecForString()) +    .property("denom_pub", codecForDenominationPubKey())      .property("fee_withdraw", codecForString())      .property("fee_deposit", codecForString())      .property("fee_refresh", codecForString()) @@ -1242,7 +1301,7 @@ export const codecForRecoupConfirmation = (): Codec<RecoupConfirmation> =>  export const codecForWithdrawResponse = (): Codec<WithdrawResponse> =>    buildCodecForObject<WithdrawResponse>() -    .property("ev_sig", codecForString()) +    .property("ev_sig", codecForBlindedDenominationSignature())      .build("WithdrawResponse");  export const codecForMerchantPayResponse = (): Codec<MerchantPayResponse> => @@ -1260,7 +1319,7 @@ export const codecForExchangeMeltResponse = (): Codec<ExchangeMeltResponse> =>  export const codecForExchangeRevealItem = (): Codec<ExchangeRevealItem> =>    buildCodecForObject<ExchangeRevealItem>() -    .property("ev_sig", codecForString()) +    .property("ev_sig", codecForBlindedDenominationSignature())      .build("ExchangeRevealItem");  export const codecForExchangeRevealResponse = (): Codec<ExchangeRevealResponse> => diff --git a/packages/taler-util/src/walletTypes.ts b/packages/taler-util/src/walletTypes.ts index 6e68ee080..879640e82 100644 --- a/packages/taler-util/src/walletTypes.ts +++ b/packages/taler-util/src/walletTypes.ts @@ -48,6 +48,8 @@ import {    AmountString,    codecForContractTerms,    ContractTerms, +  DenominationPubKey, +  UnblindedSignature,  } from "./talerTypes.js";  import { OrderShortInfo, codecForOrderShortInfo } from "./transactionsTypes.js";  import { BackupRecovery } from "./backupTypes.js"; @@ -454,7 +456,7 @@ export interface PlanchetCreationResult {    coinPriv: string;    reservePub: string;    denomPubHash: string; -  denomPub: string; +  denomPub: DenominationPubKey;    blindingKey: string;    withdrawSig: string;    coinEv: string; @@ -467,7 +469,7 @@ export interface PlanchetCreationRequest {    coinIndex: number;    value: AmountJson;    feeWithdraw: AmountJson; -  denomPub: string; +  denomPub: DenominationPubKey;    reservePub: string;    reservePriv: string;  } @@ -514,7 +516,7 @@ export interface DepositInfo {    feeDeposit: AmountJson;    wireInfoHash: string;    denomPubHash: string; -  denomSig: string; +  denomSig: UnblindedSignature;  }  export interface ExchangesListRespose { diff --git a/packages/taler-wallet-core/package.json b/packages/taler-wallet-core/package.json index d8b344f2c..3f20811ff 100644 --- a/packages/taler-wallet-core/package.json +++ b/packages/taler-wallet-core/package.json @@ -58,7 +58,7 @@      "rollup-plugin-sourcemaps": "^0.6.3",      "source-map-resolve": "^0.6.0",      "typedoc": "^0.20.16", -    "typescript": "^4.1.3" +    "typescript": "^4.4.4"    },    "dependencies": {      "@gnu-taler/idb-bridge": "workspace:*", diff --git a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts index 922fbbfac..7d616ecb6 100644 --- a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts +++ b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts @@ -27,13 +27,13 @@  /**   * Imports.   */ -import { AmountJson } from "@gnu-taler/taler-util"; +import { AmountJson, DenominationPubKey } from "@gnu-taler/taler-util";  export interface RefreshNewDenomInfo {    count: number;    value: AmountJson;    feeWithdraw: AmountJson; -  denomPub: string; +  denomPub: DenominationPubKey;  }  /** @@ -117,7 +117,7 @@ export interface DerivedRefreshSession {  export interface DeriveTipRequest {    secretSeed: string; -  denomPub: string; +  denomPub: DenominationPubKey;    planchetIndex: number;  } diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts index 169d1d9b9..389b98b22 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts @@ -35,7 +35,9 @@ import {  import {    buildSigPS,    CoinDepositPermission, +  DenomKeyType,    FreshCoin, +  hashDenomPub,    RecoupRequest,    RefreshPlanchetInfo,    TalerSignaturePurpose, @@ -152,17 +154,20 @@ export class CryptoImplementation {     * reserve.     */    createPlanchet(req: PlanchetCreationRequest): PlanchetCreationResult { +    if (req.denomPub.cipher !== 1) { +      throw Error("unsupported cipher"); +    }      const reservePub = decodeCrock(req.reservePub);      const reservePriv = decodeCrock(req.reservePriv); -    const denomPub = decodeCrock(req.denomPub); +    const denomPubRsa = decodeCrock(req.denomPub.rsa_public_key);      const derivedPlanchet = setupWithdrawPlanchet(        decodeCrock(req.secretSeed),        req.coinIndex,      );      const coinPubHash = hash(derivedPlanchet.coinPub); -    const ev = rsaBlind(coinPubHash, derivedPlanchet.bks, denomPub); +    const ev = rsaBlind(coinPubHash, derivedPlanchet.bks, denomPubRsa);      const amountWithFee = Amounts.add(req.value, req.feeWithdraw).amount; -    const denomPubHash = hash(denomPub); +    const denomPubHash = hashDenomPub(req.denomPub);      const evHash = hash(ev);      const withdrawRequest = buildSigPS( @@ -182,7 +187,10 @@ export class CryptoImplementation {        coinPriv: encodeCrock(derivedPlanchet.coinPriv),        coinPub: encodeCrock(derivedPlanchet.coinPub),        coinValue: req.value, -      denomPub: encodeCrock(denomPub), +      denomPub: { +        cipher: 1, +        rsa_public_key: encodeCrock(denomPubRsa), +      },        denomPubHash: encodeCrock(denomPubHash),        reservePub: encodeCrock(reservePub),        withdrawSig: encodeCrock(sig), @@ -195,8 +203,11 @@ export class CryptoImplementation {     * Create a planchet used for tipping, including the private keys.     */    createTipPlanchet(req: DeriveTipRequest): DerivedTipPlanchet { +    if (req.denomPub.cipher !== 1) { +      throw Error("unsupported cipher"); +    }      const fc = setupTipPlanchet(decodeCrock(req.secretSeed), req.planchetIndex); -    const denomPub = decodeCrock(req.denomPub); +    const denomPub = decodeCrock(req.denomPub.rsa_public_key);      const coinPubHash = hash(fc.coinPub);      const ev = rsaBlind(coinPubHash, fc.bks, denomPub); @@ -319,14 +330,9 @@ export class CryptoImplementation {      sig: string,      masterPub: string,    ): boolean { -    const h = kdf( -      64, -      stringToBytes("exchange-wire-signature"), -      stringToBytes(paytoUri + "\0"), -      new Uint8Array(0), -    ); +    const paytoHash = hash(stringToBytes(paytoUri + "\0"));      const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_DETAILS) -      .put(h) +      .put(paytoHash)        .build();      return eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub));    } @@ -385,8 +391,11 @@ export class CryptoImplementation {     * and deposit permissions for each given coin.     */    signDepositPermission(depositInfo: DepositInfo): CoinDepositPermission { +    // FIXME: put extensions here if used +    const hExt = new Uint8Array(64);      const d = buildSigPS(TalerSignaturePurpose.WALLET_COIN_DEPOSIT)        .put(decodeCrock(depositInfo.contractTermsHash)) +      .put(hExt)        .put(decodeCrock(depositInfo.wireInfoHash))        .put(decodeCrock(depositInfo.denomPubHash))        .put(timestampRoundedToBuffer(depositInfo.timestamp)) @@ -394,7 +403,6 @@ export class CryptoImplementation {        .put(amountToBuffer(depositInfo.spendAmount))        .put(amountToBuffer(depositInfo.feeDeposit))        .put(decodeCrock(depositInfo.merchantPub)) -      .put(decodeCrock(depositInfo.coinPub))        .build();      const coinSig = eddsaSign(d, decodeCrock(depositInfo.coinPriv)); @@ -404,7 +412,10 @@ export class CryptoImplementation {        contribution: Amounts.stringify(depositInfo.spendAmount),        h_denom: depositInfo.denomPubHash,        exchange_url: depositInfo.exchangeBaseUrl, -      ub_sig: depositInfo.denomSig, +      ub_sig: { +        cipher: DenomKeyType.Rsa, +        rsa_signature: depositInfo.denomSig.rsa_signature, +      },      };      return s;    } @@ -455,8 +466,10 @@ export class CryptoImplementation {      for (const denomSel of newCoinDenoms) {        for (let i = 0; i < denomSel.count; i++) { -        const r = decodeCrock(denomSel.denomPub); -        sessionHc.update(r); +        if (denomSel.denomPub.cipher !== 1) { +          throw Error("unsupported cipher"); +        } +        sessionHc.update(hashDenomPub(denomSel.denomPub));        }      } @@ -495,7 +508,10 @@ export class CryptoImplementation {              blindingFactor = fresh.bks;            }            const pubHash = hash(coinPub); -          const denomPub = decodeCrock(denomSel.denomPub); +          if (denomSel.denomPub.cipher !== 1) { +            throw Error("unsupported cipher"); +          } +          const denomPub = decodeCrock(denomSel.denomPub.rsa_public_key);            const ev = rsaBlind(pubHash, blindingFactor, denomPub);            const planchet: RefreshPlanchetInfo = {              blindingKey: encodeCrock(blindingFactor), diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 902f749cf..483cb16c2 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -28,6 +28,7 @@ import {    Auditor,    CoinDepositPermission,    ContractTerms, +  DenominationPubKey,    Duration,    ExchangeSignKeyJson,    InternationalizedString, @@ -36,6 +37,7 @@ import {    RefreshReason,    TalerErrorDetails,    Timestamp, +  UnblindedSignature,  } from "@gnu-taler/taler-util";  import { RetryInfo } from "./util/retries.js";  import { PayCoinSelection } from "./util/coinSelection.js"; @@ -310,7 +312,7 @@ export interface DenominationRecord {    /**     * The denomination public key.     */ -  denomPub: string; +  denomPub: DenominationPubKey;    /**     * Hash of the denomination public key. @@ -452,7 +454,7 @@ export interface ExchangeDetailsRecord {    /**     * content-type of the last downloaded termsOfServiceText.     */ -   termsOfServiceContentType: string | undefined; +  termsOfServiceContentType: string | undefined;    /**     * ETag for last terms of service download. @@ -578,7 +580,8 @@ export interface PlanchetRecord {    denomPubHash: string; -  denomPub: string; +  // FIXME: maybe too redundant? +  denomPub: DenominationPubKey;    blindingKey: string; @@ -668,7 +671,7 @@ export interface CoinRecord {    /**     * Key used by the exchange used to sign the coin.     */ -  denomPub: string; +  denomPub: DenominationPubKey;    /**     * Hash of the public key that signs the coin. @@ -678,7 +681,7 @@ export interface CoinRecord {    /**     * Unblinded signature by the exchange.     */ -  denomSig: string; +  denomSig: UnblindedSignature;    /**     * Amount that's left on the coin. diff --git a/packages/taler-wallet-core/src/errors.ts b/packages/taler-wallet-core/src/errors.ts index d788405ff..3109644ac 100644 --- a/packages/taler-wallet-core/src/errors.ts +++ b/packages/taler-wallet-core/src/errors.ts @@ -93,7 +93,7 @@ export async function guardOperationException<T>(  ): Promise<T> {    try {      return await op(); -  } catch (e) { +  } catch (e: any) {      if (e instanceof OperationFailedAndReportedError) {        throw e;      } diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts index 7623ab189..e8e1de0b9 100644 --- a/packages/taler-wallet-core/src/operations/backup/import.ts +++ b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -202,7 +202,7 @@ export interface CompletedCoin {   * as the async crypto worker communication would auto-close the database transaction.   */  export interface BackupCryptoPrecomputedData { -  denomPubToHash: Record<string, string>; +  rsaDenomPubToHash: Record<string, string>;    coinPrivToCompletedCoin: Record<string, CompletedCoin>;    proposalNoncePrivToPub: { [priv: string]: string };    proposalIdToContractTermsHash: { [proposalId: string]: string }; @@ -330,8 +330,13 @@ export async function importBackup(          }          for (const backupDenomination of backupExchangeDetails.denominations) { +          if (backupDenomination.denom_pub.cipher !== 1) { +            throw Error("unsupported cipher"); +          }            const denomPubHash = -            cryptoComp.denomPubToHash[backupDenomination.denom_pub]; +            cryptoComp.rsaDenomPubToHash[ +              backupDenomination.denom_pub.rsa_public_key +            ];            checkLogicInvariant(!!denomPubHash);            const existingDenom = await tx.denominations.get([              backupExchangeDetails.base_url, diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts index 3f4c02274..9027625cd 100644 --- a/packages/taler-wallet-core/src/operations/backup/index.ts +++ b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -40,6 +40,7 @@ import {    ConfirmPayResultType,    durationFromSpec,    getTimestampNow, +  hashDenomPub,    HttpStatusCode,    j2s,    Logger, @@ -57,10 +58,7 @@ import {  import { gunzipSync, gzipSync } from "fflate";  import { InternalWalletState } from "../../common.js";  import { kdf } from "@gnu-taler/taler-util"; -import { -  secretbox, -  secretbox_open, -} from "@gnu-taler/taler-util"; +import { secretbox, secretbox_open } from "@gnu-taler/taler-util";  import {    bytesToString,    decodeCrock, @@ -162,13 +160,16 @@ async function computeBackupCryptoData(  ): Promise<BackupCryptoPrecomputedData> {    const cryptoData: BackupCryptoPrecomputedData = {      coinPrivToCompletedCoin: {}, -    denomPubToHash: {}, +    rsaDenomPubToHash: {},      proposalIdToContractTermsHash: {},      proposalNoncePrivToPub: {},      reservePrivToPub: {},    };    for (const backupExchangeDetails of backupContent.exchange_details) {      for (const backupDenom of backupExchangeDetails.denominations) { +      if (backupDenom.denom_pub.cipher !== 1) { +        throw Error("unsupported cipher"); +      }        for (const backupCoin of backupDenom.coins) {          const coinPub = encodeCrock(            eddsaGetPublic(decodeCrock(backupCoin.coin_priv)), @@ -176,16 +177,16 @@ async function computeBackupCryptoData(          const blindedCoin = rsaBlind(            hash(decodeCrock(backupCoin.coin_priv)),            decodeCrock(backupCoin.blinding_key), -          decodeCrock(backupDenom.denom_pub), +          decodeCrock(backupDenom.denom_pub.rsa_public_key),          );          cryptoData.coinPrivToCompletedCoin[backupCoin.coin_priv] = {            coinEvHash: encodeCrock(hash(blindedCoin)),            coinPub,          };        } -      cryptoData.denomPubToHash[backupDenom.denom_pub] = encodeCrock( -        hash(decodeCrock(backupDenom.denom_pub)), -      ); +      cryptoData.rsaDenomPubToHash[ +        backupDenom.denom_pub.rsa_public_key +      ] = encodeCrock(hashDenomPub(backupDenom.denom_pub));      }      for (const backupReserve of backupExchangeDetails.reserves) {        cryptoData.reservePrivToPub[backupReserve.reserve_priv] = encodeCrock( diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index 740242050..8fe3702f5 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -25,6 +25,7 @@ import {    ContractTerms,    CreateDepositGroupRequest,    CreateDepositGroupResponse, +  decodeCrock,    durationFromSpec,    getTimestampNow,    Logger, @@ -106,7 +107,7 @@ function hashWire(paytoUri: string, salt: string): string {    const r = kdf(      64,      stringToBytes(paytoUri + "\0"), -    stringToBytes(salt + "\0"), +    decodeCrock(salt),      stringToBytes("merchant-wire-signature"),    );    return encodeCrock(r); @@ -213,8 +214,8 @@ async function processDepositGroupImpl(      const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url);      const httpResp = await ws.http.postJson(url.href, {        contribution: Amounts.stringify(perm.contribution), -      wire: depositGroup.wire, -      h_wire: depositGroup.contractTermsRaw.h_wire, +      merchant_payto_uri: depositGroup.wire.payto_uri, +      wire_salt: depositGroup.wire.salt,        h_contract_terms: depositGroup.contractTermsHash,        ub_sig: perm.ub_sig,        timestamp: depositGroup.contractTermsRaw.timestamp, @@ -355,7 +356,7 @@ export async function createDepositGroup(    const timestampRound = timestampTruncateToSecond(timestamp);    const noncePair = await ws.cryptoApi.createEddsaKeypair();    const merchantPair = await ws.cryptoApi.createEddsaKeypair(); -  const wireSalt = encodeCrock(getRandomBytes(64)); +  const wireSalt = encodeCrock(getRandomBytes(16));    const wireHash = hashWire(req.depositPaytoUri, wireSalt);    const contractTerms: ContractTerms = {      auditors: [], diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 629957efb..c170c5469 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -39,6 +39,7 @@ import {    URL,    TalerErrorDetails,    Timestamp, +  hashDenomPub,  } from "@gnu-taler/taler-util";  import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util";  import { CryptoApi } from "../crypto/workers/cryptoApi.js"; @@ -78,7 +79,7 @@ function denominationRecordFromKeys(    listIssueDate: Timestamp,    denomIn: Denomination,  ): DenominationRecord { -  const denomPubHash = encodeCrock(hash(decodeCrock(denomIn.denom_pub))); +  const denomPubHash = encodeCrock(hashDenomPub(denomIn.denom_pub));    const d: DenominationRecord = {      denomPub: denomIn.denom_pub,      denomPubHash, @@ -472,26 +473,29 @@ async function updateExchangeFromUrlImpl(    let tosFound: ExchangeTosDownloadResult | undefined;    //Remove this when exchange supports multiple content-type in accept header -  if (acceptedFormat) for (const format of acceptedFormat) { -    const resp = await downloadExchangeWithTermsOfService( -      baseUrl, -      ws.http, -      timeout, -      format -    ); -    if (resp.tosContentType === format) { -      tosFound = resp -      break +  if (acceptedFormat) +    for (const format of acceptedFormat) { +      const resp = await downloadExchangeWithTermsOfService( +        baseUrl, +        ws.http, +        timeout, +        format, +      ); +      if (resp.tosContentType === format) { +        tosFound = resp; +        break; +      }      } -  }    // If none of the specified format was found try text/plain -  const tosDownload = tosFound !== undefined ? tosFound : -    await downloadExchangeWithTermsOfService( -      baseUrl, -      ws.http, -      timeout, -      "text/plain" -    ); +  const tosDownload = +    tosFound !== undefined +      ? tosFound +      : await downloadExchangeWithTermsOfService( +          baseUrl, +          ws.http, +          timeout, +          "text/plain", +        );    let recoupGroupId: string | undefined = undefined; diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index d727bd06f..956e4d65a 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -14,7 +14,12 @@   GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>   */ -import { encodeCrock, getRandomBytes, HttpStatusCode } from "@gnu-taler/taler-util"; +import { +  DenomKeyType, +  encodeCrock, +  getRandomBytes, +  HttpStatusCode, +} from "@gnu-taler/taler-util";  import {    CoinRecord,    CoinSourceType, @@ -599,10 +604,17 @@ async function refreshReveal(          continue;        }        const pc = derived.planchetsForGammas[norevealIndex][newCoinIndex]; -      const denomSig = await ws.cryptoApi.rsaUnblind( -        reveal.ev_sigs[newCoinIndex].ev_sig, +      if (denom.denomPub.cipher !== 1) { +        throw Error("cipher unsupported"); +      } +      const evSig = reveal.ev_sigs[newCoinIndex].ev_sig; +      if (evSig.cipher !== DenomKeyType.Rsa) { +        throw Error("unsupported cipher"); +      } +      const denomSigRsa = await ws.cryptoApi.rsaUnblind( +        evSig.blinded_rsa_signature,          pc.blindingKey, -        denom.denomPub, +        denom.denomPub.rsa_public_key,        );        const coin: CoinRecord = {          blindingKey: pc.blindingKey, @@ -611,7 +623,10 @@ async function refreshReveal(          currentAmount: denom.value,          denomPub: denom.denomPub,          denomPubHash: denom.denomPubHash, -        denomSig, +        denomSig: { +          cipher: DenomKeyType.Rsa, +          rsa_signature: denomSigRsa, +        },          exchangeBaseUrl: oldCoin.exchangeBaseUrl,          status: CoinStatus.Fresh,          coinSource: { diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index a90e5270f..07ce00d2e 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -30,6 +30,7 @@ import {    codecForTipResponse,    Logger,    URL, +  DenomKeyType,  } from "@gnu-taler/taler-util";  import { DerivedTipPlanchet } from "../crypto/cryptoTypes.js";  import { @@ -322,16 +323,20 @@ async function processTipImpl(      const planchet = planchets[i];      checkLogicInvariant(!!planchet); -    const denomSig = await ws.cryptoApi.rsaUnblind( +    if (denom.denomPub.cipher !== 1) { +      throw Error("unsupported cipher"); +    } + +    const denomSigRsa = await ws.cryptoApi.rsaUnblind(        blindedSig,        planchet.blindingKey, -      denom.denomPub, +      denom.denomPub.rsa_public_key,      );      const isValid = await ws.cryptoApi.rsaVerify(        planchet.coinPub, -      denomSig, -      denom.denomPub, +      denomSigRsa, +      denom.denomPub.rsa_public_key,      );      if (!isValid) { @@ -364,7 +369,7 @@ async function processTipImpl(        currentAmount: denom.value,        denomPub: denom.denomPub,        denomPubHash: denom.denomPubHash, -      denomSig: denomSig, +      denomSig: { cipher: DenomKeyType.Rsa, rsa_signature: denomSigRsa },        exchangeBaseUrl: tipRecord.exchangeBaseUrl,        status: CoinStatus.Fresh,        suspended: false, diff --git a/packages/taler-wallet-core/src/operations/withdraw.test.ts b/packages/taler-wallet-core/src/operations/withdraw.test.ts index b4f0d35e6..179852966 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.test.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.test.ts @@ -28,8 +28,11 @@ test("withdrawal selection bug repro", (t) => {    const denoms: DenominationRecord[] = [      { -      denomPub: -        "040000XT67C8KBD6B75TTQ3SK8FWXMNQW4372T3BDDGPAMB9RFCA03638W8T3F71WFEFK9NP32VKYVNFXPYRWQ1N1HDKV5J0DFEKHBPJCYSWCBJDRNWD7G8BN8PT97FA9AMV75MYEK4X54D1HGJ207JSVJBGFCATSPNTEYNHEQF1F220W00TBZR1HNPDQFD56FG0DJQ9KGHM8EC33H6AY9YN9CNX5R3Z4TZ4Q23W47SBHB13H6W74FQJG1F50X38VRSC4SR8RWBAFB7S4K8D2H4NMRFSQT892A3T0BTBW7HM5C0H2CK6FRKG31F7W9WP1S29013K5CXYE55CT8TH6N8J9B780R42Y5S3ZB6J6E9H76XBPSGH4TGYSR2VZRB98J417KCQMZKX1BB67E7W5KVE37TC9SJ904002", +      denomPub: { +        cipher: 1, +        rsa_public_key: +          "040000XT67C8KBD6B75TTQ3SK8FWXMNQW4372T3BDDGPAMB9RFCA03638W8T3F71WFEFK9NP32VKYVNFXPYRWQ1N1HDKV5J0DFEKHBPJCYSWCBJDRNWD7G8BN8PT97FA9AMV75MYEK4X54D1HGJ207JSVJBGFCATSPNTEYNHEQF1F220W00TBZR1HNPDQFD56FG0DJQ9KGHM8EC33H6AY9YN9CNX5R3Z4TZ4Q23W47SBHB13H6W74FQJG1F50X38VRSC4SR8RWBAFB7S4K8D2H4NMRFSQT892A3T0BTBW7HM5C0H2CK6FRKG31F7W9WP1S29013K5CXYE55CT8TH6N8J9B780R42Y5S3ZB6J6E9H76XBPSGH4TGYSR2VZRB98J417KCQMZKX1BB67E7W5KVE37TC9SJ904002", +      },        denomPubHash:          "Q21FQSSG4FXNT96Z14CHXM8N1RZAG9GPHAV8PRWS0PZAAVWH7PBW6R97M2CH19KKP65NNSWXY7B6S53PT3CBM342E357ZXDDJ8RDVW8",        exchangeBaseUrl: "https://exchange.demo.taler.net/", @@ -79,8 +82,12 @@ test("withdrawal selection bug repro", (t) => {        listIssueDate: { t_ms: 0 },      },      { -      denomPub: -        "040000Y63CF78QFPKRY77BRK9P557Q1GQWX3NCZ3HSYSK0Z7TT0KGRA7N4SKBKEHSTVHX1Z9DNXMJR4EXSY1TXCKV0GJ3T3YYC6Z0JNMJFVYQAV4FX5J90NZH1N33MZTV8HS9SMNAA9S6K73G4P99GYBB01B0P6M1KXZ5JRDR7VWBR3MEJHHGJ6QBMCJR3NWJRE3WJW9PRY8QPQ2S7KFWTWRESH2DBXCXWBD2SRN6P9YX8GRAEMFEGXC9V5GVJTEMH6ZDGNXFPWZE3JVJ2Q4N9GDYKBCHZCJ7M7M2RJ9ZV4Y64NAN9BT6XDC68215GKKRHTW1BBF1MYY6AR3JCTT9HYAM923RMVQR3TAEB7SDX8J76XRZWYH3AGJCZAQGMN5C8SSH9AHQ9RNQJQ15CN45R37X4YNFJV904002", +      denomPub: { +        cipher: 1, +        rsa_public_key: +          "040000Y63CF78QFPKRY77BRK9P557Q1GQWX3NCZ3HSYSK0Z7TT0KGRA7N4SKBKEHSTVHX1Z9DNXMJR4EXSY1TXCKV0GJ3T3YYC6Z0JNMJFVYQAV4FX5J90NZH1N33MZTV8HS9SMNAA9S6K73G4P99GYBB01B0P6M1KXZ5JRDR7VWBR3MEJHHGJ6QBMCJR3NWJRE3WJW9PRY8QPQ2S7KFWTWRESH2DBXCXWBD2SRN6P9YX8GRAEMFEGXC9V5GVJTEMH6ZDGNXFPWZE3JVJ2Q4N9GDYKBCHZCJ7M7M2RJ9ZV4Y64NAN9BT6XDC68215GKKRHTW1BBF1MYY6AR3JCTT9HYAM923RMVQR3TAEB7SDX8J76XRZWYH3AGJCZAQGMN5C8SSH9AHQ9RNQJQ15CN45R37X4YNFJV904002", +      }, +        denomPubHash:          "447WA23SCBATMABHA0793F92MYTBYVPYMMQHCPKMKVY5P7RZRFMQ6VRW0Y8HRA7177GTBT0TBT08R21DZD129AJ995H9G09XBFE55G8",        exchangeBaseUrl: "https://exchange.demo.taler.net/", @@ -130,8 +137,11 @@ test("withdrawal selection bug repro", (t) => {        listIssueDate: { t_ms: 0 },      },      { -      denomPub: -        "040000YDESWC2B962DA4WK356SC50MA3N9KV0ZSGY3RC48JCTY258W909C7EEMT5BTC5KZ5T4CERCZ141P9QF87EK2BD1XEEM5GB07MB3H19WE4CQGAS8X84JBWN83PQGQXVMWE5HFA992KMGHC566GT9ZS2QPHZB6X89C4A80Z663PYAAPXP728VHAKATGNNBQ01ZZ2XD1CH9Y38YZBSPJ4K7GB2J76GBCYAVD9ENHDVWXJAXYRPBX4KSS5TXRR3K5NEN9ZV3AJD2V65K7ABRZDF5D5V1FJZZMNJ5XZ4FEREEKEBV9TDFPGJTKDEHEC60K3DN24DAATRESDJ1ZYYSYSRCAT4BT2B62ARGVMJTT5N2R126DRW9TGRWCW0ZAF2N2WET1H4NJEW77X0QT46Z5R3MZ0XPHD04002", +      denomPub: { +        cipher: 1, +        rsa_public_key: +          "040000YDESWC2B962DA4WK356SC50MA3N9KV0ZSGY3RC48JCTY258W909C7EEMT5BTC5KZ5T4CERCZ141P9QF87EK2BD1XEEM5GB07MB3H19WE4CQGAS8X84JBWN83PQGQXVMWE5HFA992KMGHC566GT9ZS2QPHZB6X89C4A80Z663PYAAPXP728VHAKATGNNBQ01ZZ2XD1CH9Y38YZBSPJ4K7GB2J76GBCYAVD9ENHDVWXJAXYRPBX4KSS5TXRR3K5NEN9ZV3AJD2V65K7ABRZDF5D5V1FJZZMNJ5XZ4FEREEKEBV9TDFPGJTKDEHEC60K3DN24DAATRESDJ1ZYYSYSRCAT4BT2B62ARGVMJTT5N2R126DRW9TGRWCW0ZAF2N2WET1H4NJEW77X0QT46Z5R3MZ0XPHD04002", +      },        denomPubHash:          "JS61DTKAFM0BX8Q4XV3ZSKB921SM8QK745Z2AFXTKFMBHHFNBD8TQ5ETJHFNDGBGX22FFN2A2ERNYG1SGSDQWNQHQQ2B14DBVJYJG8R",        exchangeBaseUrl: "https://exchange.demo.taler.net/", @@ -181,8 +191,12 @@ test("withdrawal selection bug repro", (t) => {        listIssueDate: { t_ms: 0 },      },      { -      denomPub: -        "040000YG3T1ADB8DVA6BD3EPV6ZHSHTDW35DEN4VH1AE6CSB7P1PSDTNTJG866PHF6QB1CCWYCVRGA0FVBJ9Q0G7KV7AD9010GDYBQH0NNPHW744MTNXVXWBGGGRGQGYK4DTYN1DSWQ1FZNDSZZPB5BEKG2PDJ93NX2JTN06Y8QMS2G734Z9XHC10EENBG2KVB7EJ3CM8PV1T32RC7AY62F3496E8D8KRHJQQTT67DSGMNKK86QXVDTYW677FG27DP20E8XY3M6FQD53NDJ1WWES91401MV1A3VXVPGC76GZVDD62W3WTJ1YMKHTTA3MRXX3VEAAH3XTKDN1ER7X6CZPMYTF8VK735VP2B2TZGTF28TTW4FZS32SBS64APCDF6SZQ427N5538TJC7SRE71YSP5ET8GS904002", +      denomPub: { +        cipher: 1, +        rsa_public_key: +          "040000YG3T1ADB8DVA6BD3EPV6ZHSHTDW35DEN4VH1AE6CSB7P1PSDTNTJG866PHF6QB1CCWYCVRGA0FVBJ9Q0G7KV7AD9010GDYBQH0NNPHW744MTNXVXWBGGGRGQGYK4DTYN1DSWQ1FZNDSZZPB5BEKG2PDJ93NX2JTN06Y8QMS2G734Z9XHC10EENBG2KVB7EJ3CM8PV1T32RC7AY62F3496E8D8KRHJQQTT67DSGMNKK86QXVDTYW677FG27DP20E8XY3M6FQD53NDJ1WWES91401MV1A3VXVPGC76GZVDD62W3WTJ1YMKHTTA3MRXX3VEAAH3XTKDN1ER7X6CZPMYTF8VK735VP2B2TZGTF28TTW4FZS32SBS64APCDF6SZQ427N5538TJC7SRE71YSP5ET8GS904002", +      }, +        denomPubHash:          "8T51NEY81VMPQ180EQ5WR0YH7GMNNT90W55Q0514KZM18AZT71FHJGJHQXGK0WTA7ACN1X2SD0S53XPBQ1A9KH960R48VCVVM6E3TH8",        exchangeBaseUrl: "https://exchange.demo.taler.net/", @@ -232,8 +246,11 @@ test("withdrawal selection bug repro", (t) => {        listIssueDate: { t_ms: 0 },      },      { -      denomPub: -        "040000ZC0G60E9QQ5PD81TSDWD9GV5Y6P8Z05NSPA696DP07NGQQVSRQXBA76Q6PRB0YFX295RG4MTQJXAZZ860ET307HSC2X37XAVGQXRVB8Q4F1V7NP5ZEVKTX75DZK1QRAVHEZGQYKSSH6DBCJNQF6V9WNQF3GEYVA4KCBHA7JF772KHXM9642C28Z0AS4XXXV2PABAN5C8CHYD5H7JDFNK3920W5Q69X0BS84XZ4RE2PW6HM1WZ6KGZ3MKWWWCPKQ1FSFABRBWKAB09PF563BEBXKY6M38QETPH5EDWGANHD0SC3QV0WXYVB7BNHNNQ0J5BNV56K563SYHM4E5ND260YRJSYA1GN5YSW2B1J5T1A1EBNYF2DN6JNJKWXWEQ42G5YS17ZSZ5EWDRA9QKV8EGTCNAD04002", +      denomPub: { +        cipher: 1, +        rsa_public_key: +          "040000ZC0G60E9QQ5PD81TSDWD9GV5Y6P8Z05NSPA696DP07NGQQVSRQXBA76Q6PRB0YFX295RG4MTQJXAZZ860ET307HSC2X37XAVGQXRVB8Q4F1V7NP5ZEVKTX75DZK1QRAVHEZGQYKSSH6DBCJNQF6V9WNQF3GEYVA4KCBHA7JF772KHXM9642C28Z0AS4XXXV2PABAN5C8CHYD5H7JDFNK3920W5Q69X0BS84XZ4RE2PW6HM1WZ6KGZ3MKWWWCPKQ1FSFABRBWKAB09PF563BEBXKY6M38QETPH5EDWGANHD0SC3QV0WXYVB7BNHNNQ0J5BNV56K563SYHM4E5ND260YRJSYA1GN5YSW2B1J5T1A1EBNYF2DN6JNJKWXWEQ42G5YS17ZSZ5EWDRA9QKV8EGTCNAD04002", +      },        denomPubHash:          "A41HW0Q2H9PCNMEWW0C0N45QAYVXZ8SBVRRAHE4W6X24SV1TH38ANTWDT80JXEBW9Z8PVPGT9GFV2EYZWJ5JW5W1N34NFNKHQSZ1PFR",        exchangeBaseUrl: "https://exchange.demo.taler.net/", @@ -283,8 +300,11 @@ test("withdrawal selection bug repro", (t) => {        listIssueDate: { t_ms: 0 },      },      { -      denomPub: -        "040000ZSK2PMVY6E3NBQ52KXMW029M60F4BWYTDS0FZSD0PE53CNZ9H6TM3GQK1WRTEKQ5GRWJ1J9DY6Y42SP47QVT1XD1G0W05SQ5F3F7P5KSWR0FJBJ9NZBXQEVN8Q4JRC94X3JJ3XV3KBYTZ2HTDFV28C3H2SRR0XGNZB4FY85NDZF1G4AEYJJ9QB3C0V8H70YB8RV3FKTNH7XS4K4HFNZHJ5H9VMX5SM9Z2DX37HA5WFH0E2MJBVVF2BWWA5M0HPPSB365RAE2AMD42Q65A96WD80X27SB2ZNQZ8WX0K13FWF85GZ6YNYAJGE1KGN06JDEKE9QD68Z651D7XE8V6664TVVC8M68S7WD0DSXMJQKQ0BNJXNDE29Q7MRX6DA3RW0PZ44B3TKRK0294FPVZTNSTA6XF04002", +      denomPub: { +        cipher: 1, +        rsa_public_key: +          "040000ZSK2PMVY6E3NBQ52KXMW029M60F4BWYTDS0FZSD0PE53CNZ9H6TM3GQK1WRTEKQ5GRWJ1J9DY6Y42SP47QVT1XD1G0W05SQ5F3F7P5KSWR0FJBJ9NZBXQEVN8Q4JRC94X3JJ3XV3KBYTZ2HTDFV28C3H2SRR0XGNZB4FY85NDZF1G4AEYJJ9QB3C0V8H70YB8RV3FKTNH7XS4K4HFNZHJ5H9VMX5SM9Z2DX37HA5WFH0E2MJBVVF2BWWA5M0HPPSB365RAE2AMD42Q65A96WD80X27SB2ZNQZ8WX0K13FWF85GZ6YNYAJGE1KGN06JDEKE9QD68Z651D7XE8V6664TVVC8M68S7WD0DSXMJQKQ0BNJXNDE29Q7MRX6DA3RW0PZ44B3TKRK0294FPVZTNSTA6XF04002", +      },        denomPubHash:          "F5NGBX33DTV4595XZZVK0S2MA1VMXFEJQERE5EBP5DS4QQ9EFRANN7YHWC1TKSHT2K6CQWDBRES8D3DWR0KZF5RET40B4AZXZ0RW1ZG",        exchangeBaseUrl: "https://exchange.demo.taler.net/", diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index 620ad88be..57bd49d23 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -41,6 +41,7 @@ import {    URL,    WithdrawUriInfoResponse,    VersionMatchResult, +  DenomKeyType,  } from "@gnu-taler/taler-util";  import {    CoinRecord, @@ -495,7 +496,7 @@ async function processPlanchetExchangeRequest(        ]);        if (!denom) { -        console.error("db inconsistent: denom for planchet not found"); +        logger.error("db inconsistent: denom for planchet not found");          return;        } @@ -589,16 +590,26 @@ async function processPlanchetVerifyAndStoreCoin(    const { planchet, exchangeBaseUrl } = d; -  const denomSig = await ws.cryptoApi.rsaUnblind( -    resp.ev_sig, +  const planchetDenomPub = planchet.denomPub; +  if (planchetDenomPub.cipher !== DenomKeyType.Rsa) { +    throw Error("cipher not supported"); +  } + +  const evSig = resp.ev_sig; +  if (evSig.cipher !== DenomKeyType.Rsa) { +    throw Error("unsupported cipher"); +  } + +  const denomSigRsa = await ws.cryptoApi.rsaUnblind( +    evSig.blinded_rsa_signature,      planchet.blindingKey, -    planchet.denomPub, +    planchetDenomPub.rsa_public_key,    );    const isValid = await ws.cryptoApi.rsaVerify(      planchet.coinPub, -    denomSig, -    planchet.denomPub, +    denomSigRsa, +    planchetDenomPub.rsa_public_key,    );    if (!isValid) { @@ -629,7 +640,10 @@ async function processPlanchetVerifyAndStoreCoin(      currentAmount: planchet.coinValue,      denomPub: planchet.denomPub,      denomPubHash: planchet.denomPubHash, -    denomSig, +    denomSig: { +      cipher: DenomKeyType.Rsa, +      rsa_signature: denomSigRsa, +    },      coinEvHash: planchet.coinEvHash,      exchangeBaseUrl: exchangeBaseUrl,      status: CoinStatus.Fresh, @@ -728,7 +742,9 @@ export async function updateWithdrawalDenoms(        batchIdx++, current++      ) {        const denom = denominations[current]; -      if (denom.verificationStatus === DenominationVerificationStatus.Unverified) { +      if ( +        denom.verificationStatus === DenominationVerificationStatus.Unverified +      ) {          logger.trace(            `Validating denomination (${current + 1}/${              denominations.length @@ -745,7 +761,8 @@ export async function updateWithdrawalDenoms(            );            denom.verificationStatus = DenominationVerificationStatus.VerifiedBad;          } else { -          denom.verificationStatus = DenominationVerificationStatus.VerifiedGood; +          denom.verificationStatus = +            DenominationVerificationStatus.VerifiedGood;          }          updatedDenominations.push(denom);        } diff --git a/packages/taler-wallet-core/src/util/coinSelection.test.ts b/packages/taler-wallet-core/src/util/coinSelection.test.ts index ed48b8dd1..b4dc2a18b 100644 --- a/packages/taler-wallet-core/src/util/coinSelection.test.ts +++ b/packages/taler-wallet-core/src/util/coinSelection.test.ts @@ -33,7 +33,10 @@ function fakeAci(current: string, feeDeposit: string): AvailableCoinInfo {    return {      availableAmount: a(current),      coinPub: "foobar", -    denomPub: "foobar", +    denomPub: { +      cipher: 1, +      rsa_public_key: "foobar", +    },      feeDeposit: a(feeDeposit),      exchangeBaseUrl: "https://example.com/",    }; diff --git a/packages/taler-wallet-core/src/util/coinSelection.ts b/packages/taler-wallet-core/src/util/coinSelection.ts index 500cee5d8..ba26c98fe 100644 --- a/packages/taler-wallet-core/src/util/coinSelection.ts +++ b/packages/taler-wallet-core/src/util/coinSelection.ts @@ -23,7 +23,7 @@  /**   * Imports.   */ -import { AmountJson, Amounts } from "@gnu-taler/taler-util"; +import { AmountJson, Amounts, DenominationPubKey } from "@gnu-taler/taler-util";  import { strcmp, Logger } from "@gnu-taler/taler-util";  const logger = new Logger("coinSelection.ts"); @@ -72,7 +72,7 @@ export interface AvailableCoinInfo {    /**     * Coin's denomination public key.     */ -  denomPub: string; +  denomPub: DenominationPubKey;    /**     * Amount still remaining (typically the full amount, @@ -206,6 +206,21 @@ function tallyFees(    };  } +function denomPubCmp( +  p1: DenominationPubKey, +  p2: DenominationPubKey, +): -1 | 0 | 1 { +  if (p1.cipher < p2.cipher) { +    return -1; +  } else if (p1.cipher > p2.cipher) { +    return +1; +  } +  if (p1.cipher !== 1 || p2.cipher !== 1) { +    throw Error("unsupported cipher"); +  } +  return strcmp(p1.rsa_public_key, p2.rsa_public_key); +} +  /**   * Given a list of candidate coins, select coins to spend under the merchant's   * constraints. @@ -272,7 +287,7 @@ export function selectPayCoins(      (o1, o2) =>        -Amounts.cmp(o1.availableAmount, o2.availableAmount) ||        Amounts.cmp(o1.feeDeposit, o2.feeDeposit) || -      strcmp(o1.denomPub, o2.denomPub), +      denomPubCmp(o1.denomPub, o2.denomPub),    );    // FIXME:  Here, we should select coins in a smarter way. diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 32e3945e8..cd2dd7f1e 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -387,6 +387,7 @@ async function runTaskLoop(          } catch (e) {            if (e instanceof OperationFailedAndReportedError) {              logger.warn("operation processed resulted in reported error"); +            logger.warn(`reporred error was: ${j2s(e.operationError)}`);            } else {              logger.error("Uncaught exception", e);              ws.notify({ @@ -929,7 +930,7 @@ async function dispatchRequestInternal(        }        const components = pt.targetPath.split("/");        const creditorAcct = components[components.length - 1]; -      logger.info(`making testbank transfer to '${creditorAcct}''`) +      logger.info(`making testbank transfer to '${creditorAcct}''`);        const fbReq = await ws.http.postJson(          new URL(`${creditorAcct}/admin/add-incoming`, req.bank).href,          {  | 
