aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core')
-rw-r--r--packages/taler-wallet-core/package.json2
-rw-r--r--packages/taler-wallet-core/src/crypto/cryptoTypes.ts6
-rw-r--r--packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts2
-rw-r--r--packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts124
-rw-r--r--packages/taler-wallet-core/src/crypto/workers/cryptoWorkerInterface.ts (renamed from packages/taler-wallet-core/src/crypto/workers/cryptoWorker.ts)0
-rw-r--r--packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts4
-rw-r--r--packages/taler-wallet-core/src/crypto/workers/synchronousWorker.ts114
-rw-r--r--packages/taler-wallet-core/src/db.ts13
-rw-r--r--packages/taler-wallet-core/src/errors.ts2
-rw-r--r--packages/taler-wallet-core/src/headless/helpers.ts26
-rw-r--r--packages/taler-wallet-core/src/index.ts2
-rw-r--r--packages/taler-wallet-core/src/operations/backup/import.ts9
-rw-r--r--packages/taler-wallet-core/src/operations/backup/index.ts19
-rw-r--r--packages/taler-wallet-core/src/operations/deposits.ts9
-rw-r--r--packages/taler-wallet-core/src/operations/exchanges.ts42
-rw-r--r--packages/taler-wallet-core/src/operations/pay.ts5
-rw-r--r--packages/taler-wallet-core/src/operations/refresh.ts25
-rw-r--r--packages/taler-wallet-core/src/operations/testing.ts3
-rw-r--r--packages/taler-wallet-core/src/operations/tip.ts15
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.test.ts44
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts35
-rw-r--r--packages/taler-wallet-core/src/util/coinSelection.test.ts5
-rw-r--r--packages/taler-wallet-core/src/util/coinSelection.ts21
-rw-r--r--packages/taler-wallet-core/src/wallet.ts3
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,
{