diff options
Diffstat (limited to 'packages/taler-wallet-core')
24 files changed, 397 insertions, 133 deletions
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/cryptoApi.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts index 6bace01a3..e6c0290f1 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts @@ -24,7 +24,7 @@ */ import { CoinRecord, DenominationRecord, WireFee } from "../../db.js"; -import { CryptoWorker } from "./cryptoWorker.js"; +import { CryptoWorker } from "./cryptoWorkerInterface.js"; import { RecoupRequest, CoinDepositPermission } from "@gnu-taler/taler-util"; diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts index c42ece778..389b98b22 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts @@ -15,9 +15,7 @@ */ /** - * Synchronous implementation of crypto-related functions for the wallet. - * - * The functionality is parameterized over an Emscripten environment. + * Implementation of crypto-related high-level functions for the Taler wallet. * * @author Florian Dold <dold@taler.net> */ @@ -37,9 +35,11 @@ import { import { buildSigPS, CoinDepositPermission, + DenomKeyType, + FreshCoin, + hashDenomPub, RecoupRequest, RefreshPlanchetInfo, - SignaturePurposeBuilder, TalerSignaturePurpose, } from "@gnu-taler/taler-util"; // FIXME: These types should be internal to the wallet! @@ -128,25 +128,46 @@ function timestampRoundedToBuffer(ts: Timestamp): Uint8Array { return new Uint8Array(b); } +export interface PrimitiveWorker { + setupRefreshPlanchet(arg0: { + transfer_secret: string; + coin_index: number; + }): Promise<{ + coin_pub: string; + coin_priv: string; + blinding_key: string; + }>; + eddsaVerify(req: { + msg: string; + sig: string; + pub: string; + }): Promise<{ valid: boolean }>; +} + export class CryptoImplementation { static enableTracing = false; + constructor(private primitiveWorker?: PrimitiveWorker) {} + /** * Create a pre-coin of the given denomination to be withdrawn from then given * 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( @@ -166,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), @@ -179,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); @@ -246,7 +273,11 @@ export class CryptoImplementation { /** * Check if a wire fee is correctly signed. */ - isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean { + async isValidWireFee( + type: string, + wf: WireFee, + masterPub: string, + ): Promise<boolean> { const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_FEES) .put(hash(stringToBytes(type + "\0"))) .put(timestampRoundedToBuffer(wf.startStamp)) @@ -256,13 +287,25 @@ export class CryptoImplementation { .build(); const sig = decodeCrock(wf.sig); const pub = decodeCrock(masterPub); + if (this.primitiveWorker) { + return ( + await this.primitiveWorker.eddsaVerify({ + msg: encodeCrock(p), + pub: masterPub, + sig: encodeCrock(sig), + }) + ).valid; + } return eddsaVerify(p, sig, pub); } /** * Check if the signature of a denomination is valid. */ - isValidDenom(denom: DenominationRecord, masterPub: string): boolean { + async isValidDenom( + denom: DenominationRecord, + masterPub: string, + ): Promise<boolean> { const p = buildSigPS(TalerSignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY) .put(decodeCrock(masterPub)) .put(timestampRoundedToBuffer(denom.stampStart)) @@ -287,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)); } @@ -353,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)) @@ -362,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)); @@ -372,14 +412,17 @@ 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; } - deriveRefreshSession( + async deriveRefreshSession( req: DeriveRefreshSessionRequest, - ): DerivedRefreshSession { + ): Promise<DerivedRefreshSession> { const { newCoinDenoms, feeRefresh: meltFee, @@ -423,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)); } } @@ -435,19 +480,38 @@ export class CryptoImplementation { for (let j = 0; j < newCoinDenoms.length; j++) { const denomSel = newCoinDenoms[j]; for (let k = 0; k < denomSel.count; k++) { - const coinNumber = planchets.length; + const coinIndex = planchets.length; const transferPriv = decodeCrock(transferPrivs[i]); const oldCoinPub = decodeCrock(meltCoinPub); const transferSecret = keyExchangeEcdheEddsa( transferPriv, oldCoinPub, ); - const fresh = setupRefreshPlanchet(transferSecret, coinNumber); - const coinPriv = fresh.coinPriv; - const coinPub = fresh.coinPub; - const blindingFactor = fresh.bks; + let coinPub: Uint8Array; + let coinPriv: Uint8Array; + let blindingFactor: Uint8Array; + if (this.primitiveWorker) { + const r = await this.primitiveWorker.setupRefreshPlanchet({ + transfer_secret: encodeCrock(transferSecret), + coin_index: coinIndex, + }); + coinPub = decodeCrock(r.coin_pub); + coinPriv = decodeCrock(r.coin_priv); + blindingFactor = decodeCrock(r.blinding_key); + } else { + let fresh: FreshCoin = setupRefreshPlanchet( + transferSecret, + coinIndex, + ); + coinPriv = fresh.coinPriv; + coinPub = fresh.coinPub; + 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/crypto/workers/cryptoWorker.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoWorkerInterface.ts index 9f3ee6f50..9f3ee6f50 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoWorker.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoWorkerInterface.ts diff --git a/packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts b/packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts index 3f7f9e170..df57635d1 100644 --- a/packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts +++ b/packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts @@ -18,7 +18,7 @@ * Imports */ import { CryptoWorkerFactory } from "./cryptoApi.js"; -import { CryptoWorker } from "./cryptoWorker.js"; +import { CryptoWorker } from "./cryptoWorkerInterface.js"; import os from "os"; import { CryptoImplementation } from "./cryptoImplementation.js"; import { Logger } from "@gnu-taler/taler-util"; @@ -94,7 +94,7 @@ export function handleWorkerMessage(msg: any): void { } try { - const result = (impl as any)[operation](...args); + const result = await (impl as any)[operation](...args); // eslint-disable-next-line @typescript-eslint/no-var-requires const _r = "require"; const worker_threads: typeof import("worker_threads") = module[_r]( diff --git a/packages/taler-wallet-core/src/crypto/workers/synchronousWorker.ts b/packages/taler-wallet-core/src/crypto/workers/synchronousWorker.ts index f6b8ac5d7..8293bb369 100644 --- a/packages/taler-wallet-core/src/crypto/workers/synchronousWorker.ts +++ b/packages/taler-wallet-core/src/crypto/workers/synchronousWorker.ts @@ -14,10 +14,107 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { CryptoImplementation } from "./cryptoImplementation.js"; +import { + CryptoImplementation, + PrimitiveWorker, +} from "./cryptoImplementation.js"; import { CryptoWorkerFactory } from "./cryptoApi.js"; -import { CryptoWorker } from "./cryptoWorker.js"; +import { CryptoWorker } from "./cryptoWorkerInterface.js"; + +import child_process from "child_process"; +import type internal from "stream"; +import { OpenedPromise, openPromise } from "../../index.js"; +import { FreshCoin, Logger } from "@gnu-taler/taler-util"; + +const logger = new Logger("synchronousWorker.ts"); + +class MyPrimitiveWorker implements PrimitiveWorker { + proc: child_process.ChildProcessByStdio< + internal.Writable, + internal.Readable, + null + >; + requests: Array<{ + p: OpenedPromise<any>; + req: any; + }> = []; + + constructor() { + const stdoutChunks: Buffer[] = []; + this.proc = child_process.spawn("taler-crypto-worker", { + //stdio: ["pipe", "pipe", "inherit"], + stdio: ["pipe", "pipe", "inherit"], + detached: true, + }); + this.proc.on("close", function (code) { + logger.error("child process exited"); + }); + (this.proc.stdout as any).unref(); + (this.proc.stdin as any).unref(); + this.proc.unref(); + + this.proc.stdout.on("data", (x) => { + // console.log("got chunk", x.toString("utf-8")); + if (x instanceof Buffer) { + const nlIndex = x.indexOf("\n"); + if (nlIndex >= 0) { + const before = x.slice(0, nlIndex); + const after = x.slice(nlIndex + 1); + stdoutChunks.push(after); + const str = Buffer.concat([...stdoutChunks, before]).toString( + "utf-8", + ); + const req = this.requests.shift()!; + if (this.requests.length === 0) { + this.proc.unref(); + } + //logger.info(`got response: ${str}`); + req.p.resolve(JSON.parse(str)); + } else { + stdoutChunks.push(x); + } + } else { + throw Error(`unexpected data chunk type (${typeof x})`); + } + }); + } + + async setupRefreshPlanchet(req: { + transfer_secret: string; + coin_index: number; + }): Promise<{ + coin_pub: string; + coin_priv: string; + blinding_key: string; + }> { + return this.queueRequest({ + op: "setup_refresh_planchet", + args: req, + }); + } + + async queueRequest(req: any): Promise<any> { + const p = openPromise<any>(); + if (this.requests.length === 0) { + this.proc.ref(); + } + this.requests.push({ req, p }); + this.proc.stdin.write(JSON.stringify(req) + "\n"); + return p.promise; + } + + async eddsaVerify(req: { + msg: string; + sig: string; + pub: string; + }): Promise<{ valid: boolean }> { + return this.queueRequest({ + op: "eddsa_verify", + args: req, + }); + } +} /** * The synchronous crypto worker produced by this factory doesn't run in the @@ -50,9 +147,14 @@ export class SynchronousCryptoWorker { */ onerror: undefined | ((m: any) => void); + primitiveWorker: PrimitiveWorker; + constructor() { this.onerror = undefined; this.onmessage = undefined; + if (process.env["TALER_WALLET_PRIMITIVE_WORKER"]) { + this.primitiveWorker = new MyPrimitiveWorker(); + } } /** @@ -80,7 +182,7 @@ export class SynchronousCryptoWorker { id: number, args: string[], ): Promise<void> { - const impl = new CryptoImplementation(); + const impl = new CryptoImplementation(this.primitiveWorker); if (!(operation in impl)) { console.error(`crypto operation '${operation}' not found`); @@ -89,16 +191,16 @@ export class SynchronousCryptoWorker { let result: any; try { - result = (impl as any)[operation](...args); + result = await (impl as any)[operation](...args); } catch (e) { - console.log("error during operation", e); + logger.error("error during operation", e); return; } try { setTimeout(() => this.dispatchMessage({ result, id }), 0); } catch (e) { - console.log("got error during dispatch", e); + logger.error("got error during dispatch", e); } } 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/headless/helpers.ts b/packages/taler-wallet-core/src/headless/helpers.ts index f2285e149..191c48441 100644 --- a/packages/taler-wallet-core/src/headless/helpers.ts +++ b/packages/taler-wallet-core/src/headless/helpers.ts @@ -142,19 +142,23 @@ export async function getDefaultNodeWallet( const myDb = await openTalerDatabase(myIdbFactory, myVersionChange); let workerFactory; - try { - // Try if we have worker threads available, fails in older node versions. - const _r = "require"; - const worker_threads = module[_r]("worker_threads"); - // require("worker_threads"); - workerFactory = new NodeThreadCryptoWorkerFactory(); - } catch (e) { - logger.warn( - "worker threads not available, falling back to synchronous workers", - ); + if (process.env["TALER_WALLET_SYNC_CRYPTO"]) { + logger.info("using synchronous crypto worker"); workerFactory = new SynchronousCryptoWorkerFactory(); + } else { + try { + // Try if we have worker threads available, fails in older node versions. + const _r = "require"; + const worker_threads = module[_r]("worker_threads"); + // require("worker_threads"); + workerFactory = new NodeThreadCryptoWorkerFactory(); + } catch (e) { + logger.warn( + "worker threads not available, falling back to synchronous workers", + ); + workerFactory = new SynchronousCryptoWorkerFactory(); + } } - const w = await Wallet.create(myDb, myHttpLib, workerFactory); if (args.notifyHandler) { diff --git a/packages/taler-wallet-core/src/index.ts b/packages/taler-wallet-core/src/index.ts index 0b360a248..5489bd5a3 100644 --- a/packages/taler-wallet-core/src/index.ts +++ b/packages/taler-wallet-core/src/index.ts @@ -34,7 +34,7 @@ export * from "./db-utils.js"; // Crypto and crypto workers // export * from "./crypto/workers/nodeThreadWorker.js"; export { CryptoImplementation } from "./crypto/workers/cryptoImplementation.js"; -export type { CryptoWorker } from "./crypto/workers/cryptoWorker.js"; +export type { CryptoWorker } from "./crypto/workers/cryptoWorkerInterface.js"; export { CryptoWorkerFactory, CryptoApi } from "./crypto/workers/cryptoApi.js"; export * from "./pending-types.js"; 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/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index a42480f40..acc592a72 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -175,7 +175,7 @@ export async function getEffectiveDepositAmount( for (let i = 0; i < pcs.coinPubs.length; i++) { const coin = await tx.coins.get(pcs.coinPubs[i]); if (!coin) { - throw Error("can't calculate deposit amountt, coin not found"); + throw Error("can't calculate deposit amount, coin not found"); } const denom = await tx.denominations.get([ coin.exchangeBaseUrl, @@ -193,6 +193,9 @@ export async function getEffectiveDepositAmount( if (!exchangeDetails) { continue; } + // FIXME/NOTE: the line below _likely_ throws exception + // about "find method not found on undefined" when the wireType + // is not supported by the Exchange. const fee = exchangeDetails.wireInfo.feesForType[wireType].find((x) => { return timestampIsBetween( getTimestampNow(), 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/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts index d2071cd53..d6f0626dd 100644 --- a/packages/taler-wallet-core/src/operations/testing.ts +++ b/packages/taler-wallet-core/src/operations/testing.ts @@ -174,7 +174,8 @@ async function registerRandomBankUser( const reqUrl = new URL("testing/register", bankBaseUrl).href; const randId = makeId(8); const bankUser: BankUser = { - username: `testuser-${randId}`, + // euFin doesn't allow resource names to have upper case letters. + username: `testuser-${randId.toLowerCase()}`, password: `testpw-${randId}`, }; 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, { |