aboutsummaryrefslogtreecommitdiff
path: root/src/crypto/workers
diff options
context:
space:
mode:
Diffstat (limited to 'src/crypto/workers')
-rw-r--r--src/crypto/workers/browserWorkerEntry.ts74
-rw-r--r--src/crypto/workers/cryptoApi.ts467
-rw-r--r--src/crypto/workers/cryptoImplementation.ts581
-rw-r--r--src/crypto/workers/cryptoWorker.ts8
-rw-r--r--src/crypto/workers/nodeThreadWorker.ts183
-rw-r--r--src/crypto/workers/synchronousWorker.ts137
6 files changed, 0 insertions, 1450 deletions
diff --git a/src/crypto/workers/browserWorkerEntry.ts b/src/crypto/workers/browserWorkerEntry.ts
deleted file mode 100644
index 87cb0b28b..000000000
--- a/src/crypto/workers/browserWorkerEntry.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 GNUnet e.V.
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-
-/**
- * Web worker for crypto operations.
- */
-
-/**
- * Imports.
- */
-
-import { CryptoImplementation } from "./cryptoImplementation";
-
-const worker: Worker = (self as any) as Worker;
-
-async function handleRequest(
- operation: string,
- id: number,
- args: string[],
-): Promise<void> {
- const impl = new CryptoImplementation();
-
- if (!(operation in impl)) {
- console.error(`crypto operation '${operation}' not found`);
- return;
- }
-
- try {
- const result = (impl as any)[operation](...args);
- worker.postMessage({ result, id });
- } catch (e) {
- console.log("error during operation", e);
- return;
- }
-}
-
-worker.onmessage = (msg: MessageEvent) => {
- const args = msg.data.args;
- if (!Array.isArray(args)) {
- console.error("args must be array");
- return;
- }
- const id = msg.data.id;
- if (typeof id !== "number") {
- console.error("RPC id must be number");
- return;
- }
- const operation = msg.data.operation;
- if (typeof operation !== "string") {
- console.error("RPC operation must be string");
- return;
- }
-
- if (CryptoImplementation.enableTracing) {
- console.log("onmessage with", operation);
- }
-
- handleRequest(operation, id, args).catch((e) => {
- console.error("error in browsere worker", e);
- });
-};
diff --git a/src/crypto/workers/cryptoApi.ts b/src/crypto/workers/cryptoApi.ts
deleted file mode 100644
index 5e922ec02..000000000
--- a/src/crypto/workers/cryptoApi.ts
+++ /dev/null
@@ -1,467 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 GNUnet e.V.
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * API to access the Taler crypto worker thread.
- * @author Florian Dold
- */
-
-/**
- * Imports.
- */
-import { AmountJson } from "../../util/amounts";
-
-import {
- CoinRecord,
- DenominationRecord,
- RefreshSessionRecord,
- TipPlanchet,
- WireFee,
- DenominationSelectionInfo,
-} from "../../types/dbTypes";
-
-import { CryptoWorker } from "./cryptoWorker";
-
-import { RecoupRequest, CoinDepositPermission } from "../../types/talerTypes";
-
-import {
- BenchmarkResult,
- PlanchetCreationResult,
- PlanchetCreationRequest,
- DepositInfo,
-} from "../../types/walletTypes";
-
-import * as timer from "../../util/timer";
-import { Logger } from "../../util/logging";
-
-const logger = new Logger("cryptoApi.ts");
-
-/**
- * State of a crypto worker.
- */
-interface WorkerState {
- /**
- * The actual worker thread.
- */
- w: CryptoWorker | null;
-
- /**
- * Work we're currently executing or null if not busy.
- */
- currentWorkItem: WorkItem | null;
-
- /**
- * Timer to terminate the worker if it's not busy enough.
- */
- terminationTimerHandle: timer.TimerHandle | null;
-}
-
-interface WorkItem {
- operation: string;
- args: any[];
- resolve: any;
- reject: any;
-
- /**
- * Serial id to identify a matching response.
- */
- rpcId: number;
-
- /**
- * Time when the work was submitted to a (non-busy) worker thread.
- */
- startTime: number;
-}
-
-/**
- * Number of different priorities. Each priority p
- * must be 0 <= p < NUM_PRIO.
- */
-const NUM_PRIO = 5;
-
-export interface CryptoWorkerFactory {
- /**
- * Start a new worker.
- */
- startWorker(): CryptoWorker;
-
- /**
- * Query the number of workers that should be
- * run at the same time.
- */
- getConcurrency(): number;
-}
-
-export class BrowserCryptoWorkerFactory implements CryptoWorkerFactory {
- startWorker(): CryptoWorker {
- const workerCtor = Worker;
- const workerPath = "/browserWorkerEntry.js";
- return new workerCtor(workerPath) as CryptoWorker;
- }
-
- getConcurrency(): number {
- let concurrency = 2;
- try {
- // only works in the browser
- // tslint:disable-next-line:no-string-literal
- concurrency = (navigator as any)["hardwareConcurrency"];
- concurrency = Math.max(1, Math.ceil(concurrency / 2));
- } catch (e) {
- concurrency = 2;
- }
- return concurrency;
- }
-}
-
-/**
- * Crypto API that interfaces manages a background crypto thread
- * for the execution of expensive operations.
- */
-export class CryptoApi {
- private nextRpcId = 1;
- private workers: WorkerState[];
- private workQueues: WorkItem[][];
-
- private workerFactory: CryptoWorkerFactory;
-
- /**
- * Number of busy workers.
- */
- private numBusy = 0;
-
- /**
- * Did we stop accepting new requests?
- */
- private stopped = false;
-
- /**
- * Terminate all worker threads.
- */
- terminateWorkers(): void {
- for (const worker of this.workers) {
- if (worker.w) {
- logger.trace("terminating worker");
- worker.w.terminate();
- if (worker.terminationTimerHandle) {
- worker.terminationTimerHandle.clear();
- worker.terminationTimerHandle = null;
- }
- if (worker.currentWorkItem) {
- worker.currentWorkItem.reject(Error("explicitly terminated"));
- worker.currentWorkItem = null;
- }
- worker.w = null;
- }
- }
- }
-
- stop(): void {
- this.terminateWorkers();
- this.stopped = true;
- }
-
- /**
- * Start a worker (if not started) and set as busy.
- */
- wake(ws: WorkerState, work: WorkItem): void {
- if (this.stopped) {
- logger.trace("cryptoApi is stopped");
- return;
- }
- if (ws.currentWorkItem !== null) {
- throw Error("assertion failed");
- }
- ws.currentWorkItem = work;
- this.numBusy++;
- let worker: CryptoWorker;
- if (!ws.w) {
- worker = this.workerFactory.startWorker();
- worker.onmessage = (m: MessageEvent) => this.handleWorkerMessage(ws, m);
- worker.onerror = (e: ErrorEvent) => this.handleWorkerError(ws, e);
- ws.w = worker;
- } else {
- worker = ws.w;
- }
-
- const msg: any = {
- args: work.args,
- id: work.rpcId,
- operation: work.operation,
- };
- this.resetWorkerTimeout(ws);
- work.startTime = timer.performanceNow();
- setTimeout(() => worker.postMessage(msg), 0);
- }
-
- resetWorkerTimeout(ws: WorkerState): void {
- if (ws.terminationTimerHandle !== null) {
- ws.terminationTimerHandle.clear();
- ws.terminationTimerHandle = null;
- }
- const destroy = (): void => {
- // terminate worker if it's idle
- if (ws.w && ws.currentWorkItem === null) {
- ws.w.terminate();
- ws.w = null;
- }
- };
- ws.terminationTimerHandle = timer.after(15 * 1000, destroy);
- }
-
- handleWorkerError(ws: WorkerState, e: ErrorEvent): void {
- if (ws.currentWorkItem) {
- console.error(
- `error in worker during ${ws.currentWorkItem.operation}`,
- e,
- );
- } else {
- console.error("error in worker", e);
- }
- console.error(e.message);
- try {
- if (ws.w) {
- ws.w.terminate();
- ws.w = null;
- }
- } catch (e) {
- console.error(e);
- }
- if (ws.currentWorkItem !== null) {
- ws.currentWorkItem.reject(e);
- ws.currentWorkItem = null;
- this.numBusy--;
- }
- this.findWork(ws);
- }
-
- private findWork(ws: WorkerState): void {
- // try to find more work for this worker
- for (let i = 0; i < NUM_PRIO; i++) {
- const q = this.workQueues[NUM_PRIO - i - 1];
- if (q.length !== 0) {
- const work: WorkItem | undefined = q.shift();
- if (!work) {
- continue;
- }
- this.wake(ws, work);
- return;
- }
- }
- }
-
- handleWorkerMessage(ws: WorkerState, msg: MessageEvent): void {
- const id = msg.data.id;
- if (typeof id !== "number") {
- console.error("rpc id must be number");
- return;
- }
- const currentWorkItem = ws.currentWorkItem;
- ws.currentWorkItem = null;
- this.numBusy--;
- this.findWork(ws);
- if (!currentWorkItem) {
- console.error("unsolicited response from worker");
- return;
- }
- if (id !== currentWorkItem.rpcId) {
- console.error(`RPC with id ${id} has no registry entry`);
- return;
- }
-
- currentWorkItem.resolve(msg.data.result);
- }
-
- constructor(workerFactory: CryptoWorkerFactory) {
- this.workerFactory = workerFactory;
- this.workers = new Array<WorkerState>(workerFactory.getConcurrency());
-
- for (let i = 0; i < this.workers.length; i++) {
- this.workers[i] = {
- currentWorkItem: null,
- terminationTimerHandle: null,
- w: null,
- };
- }
-
- this.workQueues = [];
- for (let i = 0; i < NUM_PRIO; i++) {
- this.workQueues.push([]);
- }
- }
-
- private doRpc<T>(
- operation: string,
- priority: number,
- ...args: any[]
- ): Promise<T> {
- const p: Promise<T> = new Promise<T>((resolve, reject) => {
- const rpcId = this.nextRpcId++;
- const workItem: WorkItem = {
- operation,
- args,
- resolve,
- reject,
- rpcId,
- startTime: 0,
- };
-
- if (this.numBusy === this.workers.length) {
- const q = this.workQueues[priority];
- if (!q) {
- throw Error("assertion failed");
- }
- this.workQueues[priority].push(workItem);
- return;
- }
-
- for (const ws of this.workers) {
- if (ws.currentWorkItem !== null) {
- continue;
- }
- this.wake(ws, workItem);
- return;
- }
-
- throw Error("assertion failed");
- });
-
- return p;
- }
-
- createPlanchet(
- req: PlanchetCreationRequest,
- ): Promise<PlanchetCreationResult> {
- return this.doRpc<PlanchetCreationResult>("createPlanchet", 1, req);
- }
-
- createTipPlanchet(denom: DenominationRecord): Promise<TipPlanchet> {
- return this.doRpc<TipPlanchet>("createTipPlanchet", 1, denom);
- }
-
- hashString(str: string): Promise<string> {
- return this.doRpc<string>("hashString", 1, str);
- }
-
- hashEncoded(encodedBytes: string): Promise<string> {
- return this.doRpc<string>("hashEncoded", 1, encodedBytes);
- }
-
- isValidDenom(denom: DenominationRecord, masterPub: string): Promise<boolean> {
- return this.doRpc<boolean>("isValidDenom", 2, denom, masterPub);
- }
-
- isValidWireFee(
- type: string,
- wf: WireFee,
- masterPub: string,
- ): Promise<boolean> {
- return this.doRpc<boolean>("isValidWireFee", 2, type, wf, masterPub);
- }
-
- isValidPaymentSignature(
- sig: string,
- contractHash: string,
- merchantPub: string,
- ): Promise<boolean> {
- return this.doRpc<boolean>(
- "isValidPaymentSignature",
- 1,
- sig,
- contractHash,
- merchantPub,
- );
- }
-
- signDepositPermission(
- depositInfo: DepositInfo,
- ): Promise<CoinDepositPermission> {
- return this.doRpc<CoinDepositPermission>(
- "signDepositPermission",
- 3,
- depositInfo,
- );
- }
-
- createEddsaKeypair(): Promise<{ priv: string; pub: string }> {
- return this.doRpc<{ priv: string; pub: string }>("createEddsaKeypair", 1);
- }
-
- rsaUnblind(sig: string, bk: string, pk: string): Promise<string> {
- return this.doRpc<string>("rsaUnblind", 4, sig, bk, pk);
- }
-
- rsaVerify(hm: string, sig: string, pk: string): Promise<boolean> {
- return this.doRpc<boolean>("rsaVerify", 4, hm, sig, pk);
- }
-
- isValidWireAccount(
- paytoUri: string,
- sig: string,
- masterPub: string,
- ): Promise<boolean> {
- return this.doRpc<boolean>(
- "isValidWireAccount",
- 4,
- paytoUri,
- sig,
- masterPub,
- );
- }
-
- createRecoupRequest(coin: CoinRecord): Promise<RecoupRequest> {
- return this.doRpc<RecoupRequest>("createRecoupRequest", 1, coin);
- }
-
- createRefreshSession(
- exchangeBaseUrl: string,
- kappa: number,
- meltCoin: CoinRecord,
- newCoinDenoms: DenominationSelectionInfo,
- meltFee: AmountJson,
- ): Promise<RefreshSessionRecord> {
- return this.doRpc<RefreshSessionRecord>(
- "createRefreshSession",
- 4,
- exchangeBaseUrl,
- kappa,
- meltCoin,
- newCoinDenoms,
- meltFee,
- );
- }
-
- signCoinLink(
- oldCoinPriv: string,
- newDenomHash: string,
- oldCoinPub: string,
- transferPub: string,
- coinEv: string,
- ): Promise<string> {
- return this.doRpc<string>(
- "signCoinLink",
- 4,
- oldCoinPriv,
- newDenomHash,
- oldCoinPub,
- transferPub,
- coinEv,
- );
- }
-
- benchmark(repetitions: number): Promise<BenchmarkResult> {
- return this.doRpc<BenchmarkResult>("benchmark", 1, repetitions);
- }
-}
diff --git a/src/crypto/workers/cryptoImplementation.ts b/src/crypto/workers/cryptoImplementation.ts
deleted file mode 100644
index 979fe41d8..000000000
--- a/src/crypto/workers/cryptoImplementation.ts
+++ /dev/null
@@ -1,581 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019-2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Synchronous implementation of crypto-related functions for the wallet.
- *
- * The functionality is parameterized over an Emscripten environment.
- *
- * @author Florian Dold <dold@taler.net>
- */
-
-/**
- * Imports.
- */
-
-import {
- CoinRecord,
- DenominationRecord,
- RefreshPlanchetRecord,
- RefreshSessionRecord,
- TipPlanchet,
- WireFee,
- CoinSourceType,
- DenominationSelectionInfo,
-} from "../../types/dbTypes";
-
-import { CoinDepositPermission, RecoupRequest } from "../../types/talerTypes";
-import {
- BenchmarkResult,
- PlanchetCreationResult,
- PlanchetCreationRequest,
- DepositInfo,
-} from "../../types/walletTypes";
-import { AmountJson, Amounts } from "../../util/amounts";
-import * as timer from "../../util/timer";
-import {
- encodeCrock,
- decodeCrock,
- createEddsaKeyPair,
- createBlindingKeySecret,
- hash,
- rsaBlind,
- eddsaVerify,
- eddsaSign,
- rsaUnblind,
- stringToBytes,
- createHashContext,
- createEcdheKeyPair,
- keyExchangeEcdheEddsa,
- setupRefreshPlanchet,
- rsaVerify,
-} from "../talerCrypto";
-import { randomBytes } from "../primitives/nacl-fast";
-import { kdf } from "../primitives/kdf";
-import {
- Timestamp,
- getTimestampNow,
- timestampTruncateToSecond,
-} from "../../util/time";
-
-enum SignaturePurpose {
- WALLET_RESERVE_WITHDRAW = 1200,
- WALLET_COIN_DEPOSIT = 1201,
- MASTER_DENOMINATION_KEY_VALIDITY = 1025,
- MASTER_WIRE_FEES = 1028,
- MASTER_WIRE_DETAILS = 1030,
- WALLET_COIN_MELT = 1202,
- TEST = 4242,
- MERCHANT_PAYMENT_OK = 1104,
- WALLET_COIN_RECOUP = 1203,
- WALLET_COIN_LINK = 1204,
- EXCHANGE_CONFIRM_RECOUP = 1039,
- EXCHANGE_CONFIRM_RECOUP_REFRESH = 1041,
-}
-
-function amountToBuffer(amount: AmountJson): Uint8Array {
- const buffer = new ArrayBuffer(8 + 4 + 12);
- const dvbuf = new DataView(buffer);
- const u8buf = new Uint8Array(buffer);
- const te = new TextEncoder();
- const curr = te.encode(amount.currency);
- dvbuf.setBigUint64(0, BigInt(amount.value));
- dvbuf.setUint32(8, amount.fraction);
- u8buf.set(curr, 8 + 4);
-
- return u8buf;
-}
-
-function timestampRoundedToBuffer(ts: Timestamp): Uint8Array {
- const b = new ArrayBuffer(8);
- const v = new DataView(b);
- const tsRounded = timestampTruncateToSecond(ts);
- const s = BigInt(tsRounded.t_ms) * BigInt(1000);
- v.setBigUint64(0, s);
- return new Uint8Array(b);
-}
-
-class SignaturePurposeBuilder {
- private chunks: Uint8Array[] = [];
-
- constructor(private purposeNum: number) {}
-
- put(bytes: Uint8Array): SignaturePurposeBuilder {
- this.chunks.push(Uint8Array.from(bytes));
- return this;
- }
-
- build(): Uint8Array {
- let payloadLen = 0;
- for (const c of this.chunks) {
- payloadLen += c.byteLength;
- }
- const buf = new ArrayBuffer(4 + 4 + payloadLen);
- const u8buf = new Uint8Array(buf);
- let p = 8;
- for (const c of this.chunks) {
- u8buf.set(c, p);
- p += c.byteLength;
- }
- const dvbuf = new DataView(buf);
- dvbuf.setUint32(0, payloadLen + 4 + 4);
- dvbuf.setUint32(4, this.purposeNum);
- return u8buf;
- }
-}
-
-function buildSigPS(purposeNum: number): SignaturePurposeBuilder {
- return new SignaturePurposeBuilder(purposeNum);
-}
-
-export class CryptoImplementation {
- static enableTracing = false;
-
- /**
- * Create a pre-coin of the given denomination to be withdrawn from then given
- * reserve.
- */
- createPlanchet(req: PlanchetCreationRequest): PlanchetCreationResult {
- const reservePub = decodeCrock(req.reservePub);
- const reservePriv = decodeCrock(req.reservePriv);
- const denomPub = decodeCrock(req.denomPub);
- const coinKeyPair = createEddsaKeyPair();
- const blindingFactor = createBlindingKeySecret();
- const coinPubHash = hash(coinKeyPair.eddsaPub);
- const ev = rsaBlind(coinPubHash, blindingFactor, denomPub);
- const amountWithFee = Amounts.add(req.value, req.feeWithdraw).amount;
- const denomPubHash = hash(denomPub);
- const evHash = hash(ev);
-
- const withdrawRequest = buildSigPS(SignaturePurpose.WALLET_RESERVE_WITHDRAW)
- .put(reservePub)
- .put(amountToBuffer(amountWithFee))
- .put(denomPubHash)
- .put(evHash)
- .build();
-
- const sig = eddsaSign(withdrawRequest, reservePriv);
-
- const planchet: PlanchetCreationResult = {
- blindingKey: encodeCrock(blindingFactor),
- coinEv: encodeCrock(ev),
- coinPriv: encodeCrock(coinKeyPair.eddsaPriv),
- coinPub: encodeCrock(coinKeyPair.eddsaPub),
- coinValue: req.value,
- denomPub: encodeCrock(denomPub),
- denomPubHash: encodeCrock(denomPubHash),
- reservePub: encodeCrock(reservePub),
- withdrawSig: encodeCrock(sig),
- coinEvHash: encodeCrock(evHash),
- };
- return planchet;
- }
-
- /**
- * Create a planchet used for tipping, including the private keys.
- */
- createTipPlanchet(denom: DenominationRecord): TipPlanchet {
- const denomPub = decodeCrock(denom.denomPub);
- const coinKeyPair = createEddsaKeyPair();
- const blindingFactor = createBlindingKeySecret();
- const coinPubHash = hash(coinKeyPair.eddsaPub);
- const ev = rsaBlind(coinPubHash, blindingFactor, denomPub);
-
- const tipPlanchet: TipPlanchet = {
- blindingKey: encodeCrock(blindingFactor),
- coinEv: encodeCrock(ev),
- coinPriv: encodeCrock(coinKeyPair.eddsaPriv),
- coinPub: encodeCrock(coinKeyPair.eddsaPub),
- coinValue: denom.value,
- denomPub: encodeCrock(denomPub),
- denomPubHash: encodeCrock(hash(denomPub)),
- };
- return tipPlanchet;
- }
-
- /**
- * Create and sign a message to recoup a coin.
- */
- createRecoupRequest(coin: CoinRecord): RecoupRequest {
- const p = buildSigPS(SignaturePurpose.WALLET_COIN_RECOUP)
- .put(decodeCrock(coin.coinPub))
- .put(decodeCrock(coin.denomPubHash))
- .put(decodeCrock(coin.blindingKey))
- .build();
-
- const coinPriv = decodeCrock(coin.coinPriv);
- const coinSig = eddsaSign(p, coinPriv);
- const paybackRequest: RecoupRequest = {
- coin_blind_key_secret: coin.blindingKey,
- coin_pub: coin.coinPub,
- coin_sig: encodeCrock(coinSig),
- denom_pub_hash: coin.denomPubHash,
- denom_sig: coin.denomSig,
- refreshed: coin.coinSource.type === CoinSourceType.Refresh,
- };
- return paybackRequest;
- }
-
- /**
- * Check if a payment signature is valid.
- */
- isValidPaymentSignature(
- sig: string,
- contractHash: string,
- merchantPub: string,
- ): boolean {
- const p = buildSigPS(SignaturePurpose.MERCHANT_PAYMENT_OK)
- .put(decodeCrock(contractHash))
- .build();
- const sigBytes = decodeCrock(sig);
- const pubBytes = decodeCrock(merchantPub);
- return eddsaVerify(p, sigBytes, pubBytes);
- }
-
- /**
- * Check if a wire fee is correctly signed.
- */
- isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean {
- const p = buildSigPS(SignaturePurpose.MASTER_WIRE_FEES)
- .put(hash(stringToBytes(type + "\0")))
- .put(timestampRoundedToBuffer(wf.startStamp))
- .put(timestampRoundedToBuffer(wf.endStamp))
- .put(amountToBuffer(wf.wireFee))
- .put(amountToBuffer(wf.closingFee))
- .build();
- const sig = decodeCrock(wf.sig);
- const pub = decodeCrock(masterPub);
- return eddsaVerify(p, sig, pub);
- }
-
- /**
- * Check if the signature of a denomination is valid.
- */
- isValidDenom(denom: DenominationRecord, masterPub: string): boolean {
- const p = buildSigPS(SignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY)
- .put(decodeCrock(masterPub))
- .put(timestampRoundedToBuffer(denom.stampStart))
- .put(timestampRoundedToBuffer(denom.stampExpireWithdraw))
- .put(timestampRoundedToBuffer(denom.stampExpireDeposit))
- .put(timestampRoundedToBuffer(denom.stampExpireLegal))
- .put(amountToBuffer(denom.value))
- .put(amountToBuffer(denom.feeWithdraw))
- .put(amountToBuffer(denom.feeDeposit))
- .put(amountToBuffer(denom.feeRefresh))
- .put(amountToBuffer(denom.feeRefund))
- .put(decodeCrock(denom.denomPubHash))
- .build();
- const sig = decodeCrock(denom.masterSig);
- const pub = decodeCrock(masterPub);
- return eddsaVerify(p, sig, pub);
- }
-
- isValidWireAccount(
- paytoUri: string,
- sig: string,
- masterPub: string,
- ): boolean {
- const h = kdf(
- 64,
- stringToBytes("exchange-wire-signature"),
- stringToBytes(paytoUri + "\0"),
- new Uint8Array(0),
- );
- const p = buildSigPS(SignaturePurpose.MASTER_WIRE_DETAILS).put(h).build();
- return eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub));
- }
-
- /**
- * Create a new EdDSA key pair.
- */
- createEddsaKeypair(): { priv: string; pub: string } {
- const pair = createEddsaKeyPair();
- return {
- priv: encodeCrock(pair.eddsaPriv),
- pub: encodeCrock(pair.eddsaPub),
- };
- }
-
- /**
- * Unblind a blindly signed value.
- */
- rsaUnblind(blindedSig: string, bk: string, pk: string): string {
- const denomSig = rsaUnblind(
- decodeCrock(blindedSig),
- decodeCrock(pk),
- decodeCrock(bk),
- );
- return encodeCrock(denomSig);
- }
-
- /**
- * Unblind a blindly signed value.
- */
- rsaVerify(hm: string, sig: string, pk: string): boolean {
- return rsaVerify(hash(decodeCrock(hm)), decodeCrock(sig), decodeCrock(pk));
- }
-
- /**
- * Generate updated coins (to store in the database)
- * and deposit permissions for each given coin.
- */
- signDepositPermission(depositInfo: DepositInfo): CoinDepositPermission {
- const d = buildSigPS(SignaturePurpose.WALLET_COIN_DEPOSIT)
- .put(decodeCrock(depositInfo.contractTermsHash))
- .put(decodeCrock(depositInfo.wireInfoHash))
- .put(decodeCrock(depositInfo.denomPubHash))
- .put(timestampRoundedToBuffer(depositInfo.timestamp))
- .put(timestampRoundedToBuffer(depositInfo.refundDeadline))
- .put(amountToBuffer(depositInfo.spendAmount))
- .put(amountToBuffer(depositInfo.feeDeposit))
- .put(decodeCrock(depositInfo.merchantPub))
- .put(decodeCrock(depositInfo.coinPub))
- .build();
- const coinSig = eddsaSign(d, decodeCrock(depositInfo.coinPriv));
-
- const s: CoinDepositPermission = {
- coin_pub: depositInfo.coinPub,
- coin_sig: encodeCrock(coinSig),
- contribution: Amounts.stringify(depositInfo.spendAmount),
- h_denom: depositInfo.denomPubHash,
- exchange_url: depositInfo.exchangeBaseUrl,
- ub_sig: depositInfo.denomSig,
- };
- return s;
- }
-
- /**
- * Create a new refresh session.
- */
- createRefreshSession(
- exchangeBaseUrl: string,
- kappa: number,
- meltCoin: CoinRecord,
- newCoinDenoms: DenominationSelectionInfo,
- meltFee: AmountJson,
- ): RefreshSessionRecord {
- const currency = newCoinDenoms.selectedDenoms[0].denom.value.currency;
- let valueWithFee = Amounts.getZero(currency);
-
- for (const ncd of newCoinDenoms.selectedDenoms) {
- const t = Amounts.add(ncd.denom.value, ncd.denom.feeWithdraw).amount;
- valueWithFee = Amounts.add(
- valueWithFee,
- Amounts.mult(t, ncd.count).amount,
- ).amount;
- }
-
- // melt fee
- valueWithFee = Amounts.add(valueWithFee, meltFee).amount;
-
- const sessionHc = createHashContext();
-
- const transferPubs: string[] = [];
- const transferPrivs: string[] = [];
-
- const planchetsForGammas: RefreshPlanchetRecord[][] = [];
-
- for (let i = 0; i < kappa; i++) {
- const transferKeyPair = createEcdheKeyPair();
- sessionHc.update(transferKeyPair.ecdhePub);
- transferPrivs.push(encodeCrock(transferKeyPair.ecdhePriv));
- transferPubs.push(encodeCrock(transferKeyPair.ecdhePub));
- }
-
- for (const denomSel of newCoinDenoms.selectedDenoms) {
- for (let i = 0; i < denomSel.count; i++) {
- const r = decodeCrock(denomSel.denom.denomPub);
- sessionHc.update(r);
- }
- }
-
- sessionHc.update(decodeCrock(meltCoin.coinPub));
- sessionHc.update(amountToBuffer(valueWithFee));
-
- for (let i = 0; i < kappa; i++) {
- const planchets: RefreshPlanchetRecord[] = [];
- for (let j = 0; j < newCoinDenoms.selectedDenoms.length; j++) {
- const denomSel = newCoinDenoms.selectedDenoms[j];
- for (let k = 0; k < denomSel.count; k++) {
- const coinNumber = planchets.length;
- const transferPriv = decodeCrock(transferPrivs[i]);
- const oldCoinPub = decodeCrock(meltCoin.coinPub);
- const transferSecret = keyExchangeEcdheEddsa(
- transferPriv,
- oldCoinPub,
- );
- const fresh = setupRefreshPlanchet(transferSecret, coinNumber);
- const coinPriv = fresh.coinPriv;
- const coinPub = fresh.coinPub;
- const blindingFactor = fresh.bks;
- const pubHash = hash(coinPub);
- const denomPub = decodeCrock(denomSel.denom.denomPub);
- const ev = rsaBlind(pubHash, blindingFactor, denomPub);
- const planchet: RefreshPlanchetRecord = {
- blindingKey: encodeCrock(blindingFactor),
- coinEv: encodeCrock(ev),
- privateKey: encodeCrock(coinPriv),
- publicKey: encodeCrock(coinPub),
- };
- planchets.push(planchet);
- sessionHc.update(ev);
- }
- }
- planchetsForGammas.push(planchets);
- }
-
- const sessionHash = sessionHc.finish();
-
- const confirmData = buildSigPS(SignaturePurpose.WALLET_COIN_MELT)
- .put(sessionHash)
- .put(decodeCrock(meltCoin.denomPubHash))
- .put(amountToBuffer(valueWithFee))
- .put(amountToBuffer(meltFee))
- .put(decodeCrock(meltCoin.coinPub))
- .build();
-
- const confirmSig = eddsaSign(confirmData, decodeCrock(meltCoin.coinPriv));
-
- let valueOutput = Amounts.getZero(currency);
- for (const denomSel of newCoinDenoms.selectedDenoms) {
- const denom = denomSel.denom;
- for (let i = 0; i < denomSel.count; i++) {
- valueOutput = Amounts.add(valueOutput, denom.value).amount;
- }
- }
-
- const newDenoms: string[] = [];
- const newDenomHashes: string[] = [];
-
- for (const denomSel of newCoinDenoms.selectedDenoms) {
- const denom = denomSel.denom;
- for (let i = 0; i < denomSel.count; i++) {
- newDenoms.push(denom.denomPub);
- newDenomHashes.push(denom.denomPubHash);
- }
- }
-
- const refreshSession: RefreshSessionRecord = {
- confirmSig: encodeCrock(confirmSig),
- exchangeBaseUrl,
- hash: encodeCrock(sessionHash),
- meltCoinPub: meltCoin.coinPub,
- newDenomHashes,
- newDenoms,
- norevealIndex: undefined,
- planchetsForGammas: planchetsForGammas,
- transferPrivs,
- transferPubs,
- amountRefreshOutput: valueOutput,
- amountRefreshInput: valueWithFee,
- timestampCreated: getTimestampNow(),
- finishedTimestamp: undefined,
- lastError: undefined,
- };
-
- return refreshSession;
- }
-
- /**
- * Hash a string including the zero terminator.
- */
- hashString(str: string): string {
- const ts = new TextEncoder();
- const b = ts.encode(str + "\0");
- return encodeCrock(hash(b));
- }
-
- /**
- * Hash a crockford encoded value.
- */
- hashEncoded(encodedBytes: string): string {
- return encodeCrock(hash(decodeCrock(encodedBytes)));
- }
-
- signCoinLink(
- oldCoinPriv: string,
- newDenomHash: string,
- oldCoinPub: string,
- transferPub: string,
- coinEv: string,
- ): string {
- const coinEvHash = hash(decodeCrock(coinEv));
- const coinLink = buildSigPS(SignaturePurpose.WALLET_COIN_LINK)
- .put(decodeCrock(newDenomHash))
- .put(decodeCrock(oldCoinPub))
- .put(decodeCrock(transferPub))
- .put(coinEvHash)
- .build();
- const coinPriv = decodeCrock(oldCoinPriv);
- const sig = eddsaSign(coinLink, coinPriv);
- return encodeCrock(sig);
- }
-
- benchmark(repetitions: number): BenchmarkResult {
- let time_hash = 0;
- for (let i = 0; i < repetitions; i++) {
- const start = timer.performanceNow();
- this.hashString("hello world");
- time_hash += timer.performanceNow() - start;
- }
-
- let time_hash_big = 0;
- for (let i = 0; i < repetitions; i++) {
- const ba = randomBytes(4096);
- const start = timer.performanceNow();
- hash(ba);
- time_hash_big += timer.performanceNow() - start;
- }
-
- let time_eddsa_create = 0;
- for (let i = 0; i < repetitions; i++) {
- const start = timer.performanceNow();
- createEddsaKeyPair();
- time_eddsa_create += timer.performanceNow() - start;
- }
-
- let time_eddsa_sign = 0;
- const p = randomBytes(4096);
-
- const pair = createEddsaKeyPair();
-
- for (let i = 0; i < repetitions; i++) {
- const start = timer.performanceNow();
- eddsaSign(p, pair.eddsaPriv);
- time_eddsa_sign += timer.performanceNow() - start;
- }
-
- const sig = eddsaSign(p, pair.eddsaPriv);
-
- let time_eddsa_verify = 0;
- for (let i = 0; i < repetitions; i++) {
- const start = timer.performanceNow();
- eddsaVerify(p, sig, pair.eddsaPub);
- time_eddsa_verify += timer.performanceNow() - start;
- }
-
- return {
- repetitions,
- time: {
- hash_small: time_hash,
- hash_big: time_hash_big,
- eddsa_create: time_eddsa_create,
- eddsa_sign: time_eddsa_sign,
- eddsa_verify: time_eddsa_verify,
- },
- };
- }
-}
diff --git a/src/crypto/workers/cryptoWorker.ts b/src/crypto/workers/cryptoWorker.ts
deleted file mode 100644
index 9f3ee6f50..000000000
--- a/src/crypto/workers/cryptoWorker.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export interface CryptoWorker {
- postMessage(message: any): void;
-
- terminate(): void;
-
- onmessage: ((m: any) => void) | undefined;
- onerror: ((m: any) => void) | undefined;
-}
diff --git a/src/crypto/workers/nodeThreadWorker.ts b/src/crypto/workers/nodeThreadWorker.ts
deleted file mode 100644
index 6c9dfc569..000000000
--- a/src/crypto/workers/nodeThreadWorker.ts
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 GNUnet e.V.
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Imports
- */
-import { CryptoWorkerFactory } from "./cryptoApi";
-import { CryptoWorker } from "./cryptoWorker";
-import os from "os";
-import { CryptoImplementation } from "./cryptoImplementation";
-
-const f = __filename;
-
-const workerCode = `
- // Try loading the glue library for Android
- try {
- require("akono");
- } catch (e) {
- // Probably we're not on Android ...
- }
- const worker_threads = require('worker_threads');
- const parentPort = worker_threads.parentPort;
- let tw;
- try {
- tw = require("${f}");
- } catch (e) {
- console.log("could not load from ${f}");
- }
- if (!tw) {
- try {
- tw = require("taler-wallet-android");
- } catch (e) {
- console.log("could not load taler-wallet-android either");
- throw e;
- }
- }
- parentPort.on("message", tw.handleWorkerMessage);
- parentPort.on("error", tw.handleWorkerError);
-`;
-
-/**
- * This function is executed in the worker thread to handle
- * a message.
- */
-export function handleWorkerMessage(msg: any): void {
- const args = msg.args;
- if (!Array.isArray(args)) {
- console.error("args must be array");
- return;
- }
- const id = msg.id;
- if (typeof id !== "number") {
- console.error("RPC id must be number");
- return;
- }
- const operation = msg.operation;
- if (typeof operation !== "string") {
- console.error("RPC operation must be string");
- return;
- }
-
- const handleRequest = async (): Promise<void> => {
- const impl = new CryptoImplementation();
-
- if (!(operation in impl)) {
- console.error(`crypto operation '${operation}' not found`);
- return;
- }
-
- try {
- const result = (impl as any)[operation](...args);
- // eslint-disable-next-line @typescript-eslint/no-var-requires
- const worker_threads = require("worker_threads");
- const p = worker_threads.parentPort;
- worker_threads.parentPort?.postMessage;
- if (p) {
- p.postMessage({ data: { result, id } });
- } else {
- console.error("parent port not available (not running in thread?");
- }
- } catch (e) {
- console.error("error during operation", e);
- return;
- }
- };
-
- handleRequest().catch((e) => {
- console.error("error in node worker", e);
- });
-}
-
-export function handleWorkerError(e: Error): void {
- console.log("got error from worker", e);
-}
-
-export class NodeThreadCryptoWorkerFactory implements CryptoWorkerFactory {
- startWorker(): CryptoWorker {
- if (typeof require === "undefined") {
- throw Error("cannot make worker, require(...) not defined");
- }
- return new NodeThreadCryptoWorker();
- }
-
- getConcurrency(): number {
- return Math.max(1, os.cpus().length - 1);
- }
-}
-
-/**
- * Worker implementation that uses node subprocesses.
- */
-class NodeThreadCryptoWorker implements CryptoWorker {
- /**
- * Function to be called when we receive a message from the worker thread.
- */
- onmessage: undefined | ((m: any) => void);
-
- /**
- * Function to be called when we receive an error from the worker thread.
- */
- onerror: undefined | ((m: any) => void);
-
- private nodeWorker: import("worker_threads").Worker;
-
- constructor() {
- // eslint-disable-next-line @typescript-eslint/no-var-requires
- const worker_threads = require("worker_threads");
- this.nodeWorker = new worker_threads.Worker(workerCode, { eval: true });
- this.nodeWorker.on("error", (err: Error) => {
- console.error("error in node worker:", err);
- if (this.onerror) {
- this.onerror(err);
- }
- });
- this.nodeWorker.on("message", (v: any) => {
- if (this.onmessage) {
- this.onmessage(v);
- }
- });
- this.nodeWorker.unref();
- }
-
- /**
- * Add an event listener for either an "error" or "message" event.
- */
- addEventListener(event: "message" | "error", fn: (x: any) => void): void {
- switch (event) {
- case "message":
- this.onmessage = fn;
- break;
- case "error":
- this.onerror = fn;
- break;
- }
- }
-
- /**
- * Send a message to the worker thread.
- */
- postMessage(msg: any): void {
- this.nodeWorker.postMessage(msg);
- }
-
- /**
- * Forcibly terminate the worker thread.
- */
- terminate(): void {
- this.nodeWorker.terminate();
- }
-}
diff --git a/src/crypto/workers/synchronousWorker.ts b/src/crypto/workers/synchronousWorker.ts
deleted file mode 100644
index 2cc740975..000000000
--- a/src/crypto/workers/synchronousWorker.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import { CryptoImplementation } from "./cryptoImplementation";
-
-import { CryptoWorkerFactory } from "./cryptoApi";
-import { CryptoWorker } from "./cryptoWorker";
-
-/**
- * The synchronous crypto worker produced by this factory doesn't run in the
- * background, but actually blocks the caller until the operation is done.
- */
-export class SynchronousCryptoWorkerFactory implements CryptoWorkerFactory {
- startWorker(): CryptoWorker {
- if (typeof require === "undefined") {
- throw Error("cannot make worker, require(...) not defined");
- }
- const workerCtor = require("./synchronousWorker").SynchronousCryptoWorker;
- return new workerCtor();
- }
-
- getConcurrency(): number {
- return 1;
- }
-}
-
-/**
- * Worker implementation that uses node subprocesses.
- */
-export class SynchronousCryptoWorker {
- /**
- * Function to be called when we receive a message from the worker thread.
- */
- onmessage: undefined | ((m: any) => void);
-
- /**
- * Function to be called when we receive an error from the worker thread.
- */
- onerror: undefined | ((m: any) => void);
-
- constructor() {
- this.onerror = undefined;
- this.onmessage = undefined;
- }
-
- /**
- * Add an event listener for either an "error" or "message" event.
- */
- addEventListener(event: "message" | "error", fn: (x: any) => void): void {
- switch (event) {
- case "message":
- this.onmessage = fn;
- break;
- case "error":
- this.onerror = fn;
- break;
- }
- }
-
- private dispatchMessage(msg: any): void {
- if (this.onmessage) {
- this.onmessage({ data: msg });
- }
- }
-
- private async handleRequest(
- operation: string,
- id: number,
- args: string[],
- ): Promise<void> {
- const impl = new CryptoImplementation();
-
- if (!(operation in impl)) {
- console.error(`crypto operation '${operation}' not found`);
- return;
- }
-
- let result: any;
- try {
- result = (impl as any)[operation](...args);
- } catch (e) {
- console.log("error during operation", e);
- return;
- }
-
- try {
- setTimeout(() => this.dispatchMessage({ result, id }), 0);
- } catch (e) {
- console.log("got error during dispatch", e);
- }
- }
-
- /**
- * Send a message to the worker thread.
- */
- postMessage(msg: any): void {
- const args = msg.args;
- if (!Array.isArray(args)) {
- console.error("args must be array");
- return;
- }
- const id = msg.id;
- if (typeof id !== "number") {
- console.error("RPC id must be number");
- return;
- }
- const operation = msg.operation;
- if (typeof operation !== "string") {
- console.error("RPC operation must be string");
- return;
- }
-
- this.handleRequest(operation, id, args).catch((e) => {
- console.error("Error while handling crypto request:", e);
- });
- }
-
- /**
- * Forcibly terminate the worker thread.
- */
- terminate(): void {
- // This is a no-op.
- }
-}