From c33ed919719845f518d6491ef37df6ae16820dd0 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 16 Nov 2021 17:20:36 +0100 Subject: wallet: experiment with C-based crypto worker for some primitives --- packages/anastasis-core/src/crypto.ts | 2 - packages/taler-util/src/talerCrypto.ts | 4 - packages/taler-wallet-cli/src/bench1.ts | 1 + .../src/crypto/workers/cryptoApi.ts | 2 +- .../src/crypto/workers/cryptoImplementation.ts | 74 ++++++++++--- .../src/crypto/workers/cryptoWorker.ts | 8 -- .../src/crypto/workers/cryptoWorkerInterface.ts | 8 ++ .../src/crypto/workers/nodeThreadWorker.ts | 4 +- .../src/crypto/workers/synchronousWorker.ts | 114 +++++++++++++++++++-- packages/taler-wallet-core/src/headless/helpers.ts | 26 +++-- packages/taler-wallet-core/src/index.ts | 2 +- .../src/browserWorkerEntry.ts | 2 +- 12 files changed, 198 insertions(+), 49 deletions(-) delete mode 100644 packages/taler-wallet-core/src/crypto/workers/cryptoWorker.ts create mode 100644 packages/taler-wallet-core/src/crypto/workers/cryptoWorkerInterface.ts (limited to 'packages') diff --git a/packages/anastasis-core/src/crypto.ts b/packages/anastasis-core/src/crypto.ts index 206d9eca8..75bd4b323 100644 --- a/packages/anastasis-core/src/crypto.ts +++ b/packages/anastasis-core/src/crypto.ts @@ -11,8 +11,6 @@ import { stringToBytes, secretbox_open, hash, - Logger, - j2s, } from "@gnu-taler/taler-util"; import { argon2id } from "hash-wasm"; diff --git a/packages/taler-util/src/talerCrypto.ts b/packages/taler-util/src/talerCrypto.ts index 536c4dc48..d8ac75dc0 100644 --- a/packages/taler-util/src/talerCrypto.ts +++ b/packages/taler-util/src/talerCrypto.ts @@ -161,10 +161,6 @@ interface RsaPub { e: bigint.BigInteger; } -interface RsaBlindingKey { - r: bigint.BigInteger; -} - /** * KDF modulo a big integer. */ diff --git a/packages/taler-wallet-cli/src/bench1.ts b/packages/taler-wallet-cli/src/bench1.ts index ec0430d8d..ad95eebc7 100644 --- a/packages/taler-wallet-cli/src/bench1.ts +++ b/packages/taler-wallet-cli/src/bench1.ts @@ -40,6 +40,7 @@ export async function runBench1(configJson: any): Promise { const b1conf = codecForBench1Config().decode(configJson); const myHttpLib = new NodeHttpLib(); + myHttpLib.setThrottling(false); const numIter = b1conf.iterations ?? 1; 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..169d1d9b9 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 */ @@ -37,9 +35,9 @@ import { import { buildSigPS, CoinDepositPermission, + FreshCoin, RecoupRequest, RefreshPlanchetInfo, - SignaturePurposeBuilder, TalerSignaturePurpose, } from "@gnu-taler/taler-util"; // FIXME: These types should be internal to the wallet! @@ -128,9 +126,27 @@ 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. @@ -246,7 +262,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 { const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_FEES) .put(hash(stringToBytes(type + "\0"))) .put(timestampRoundedToBuffer(wf.startStamp)) @@ -256,13 +276,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 { const p = buildSigPS(TalerSignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY) .put(decodeCrock(masterPub)) .put(timestampRoundedToBuffer(denom.stampStart)) @@ -377,9 +409,9 @@ export class CryptoImplementation { return s; } - deriveRefreshSession( + async deriveRefreshSession( req: DeriveRefreshSessionRequest, - ): DerivedRefreshSession { + ): Promise { const { newCoinDenoms, feeRefresh: meltFee, @@ -435,17 +467,33 @@ 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); const ev = rsaBlind(pubHash, blindingFactor, denomPub); diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoWorker.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoWorker.ts deleted file mode 100644 index 9f3ee6f50..000000000 --- a/packages/taler-wallet-core/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/packages/taler-wallet-core/src/crypto/workers/cryptoWorkerInterface.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoWorkerInterface.ts new file mode 100644 index 000000000..9f3ee6f50 --- /dev/null +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoWorkerInterface.ts @@ -0,0 +1,8 @@ +export interface CryptoWorker { + postMessage(message: any): void; + + terminate(): void; + + onmessage: ((m: any) => void) | undefined; + onerror: ((m: any) => void) | undefined; +} 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 */ -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; + 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 { + const p = openPromise(); + 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 { - 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/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-webextension/src/browserWorkerEntry.ts b/packages/taler-wallet-webextension/src/browserWorkerEntry.ts index b5c26a7bb..7829e6d65 100644 --- a/packages/taler-wallet-webextension/src/browserWorkerEntry.ts +++ b/packages/taler-wallet-webextension/src/browserWorkerEntry.ts @@ -42,7 +42,7 @@ async function handleRequest( } try { - const result = (impl as any)[operation](...args); + const result = await (impl as any)[operation](...args); worker.postMessage({ result, id }); } catch (e) { logger.error("error during operation", e); -- cgit v1.2.3