diff options
Diffstat (limited to 'src/crypto/workers')
-rw-r--r-- | src/crypto/workers/browserWorkerEntry.ts | 74 | ||||
-rw-r--r-- | src/crypto/workers/cryptoApi.ts | 467 | ||||
-rw-r--r-- | src/crypto/workers/cryptoImplementation.ts | 581 | ||||
-rw-r--r-- | src/crypto/workers/cryptoWorker.ts | 8 | ||||
-rw-r--r-- | src/crypto/workers/nodeThreadWorker.ts | 183 | ||||
-rw-r--r-- | src/crypto/workers/synchronousWorker.ts | 137 |
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. - } -} |