diff options
author | Florian Dold <florian@dold.me> | 2022-03-23 21:24:23 +0100 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2022-03-23 21:24:36 +0100 |
commit | d881f4fd258a27cc765a25c24e5fef9f86b6226f (patch) | |
tree | 3254444f93ef552f4ac65f14e581ed761b9df79e /packages/taler-wallet-core/src/crypto/workers | |
parent | e21c1b31928cd6bfe90150ea2de19799b6359c40 (diff) | |
download | wallet-core-d881f4fd258a27cc765a25c24e5fef9f86b6226f.tar.gz wallet-core-d881f4fd258a27cc765a25c24e5fef9f86b6226f.tar.bz2 wallet-core-d881f4fd258a27cc765a25c24e5fef9f86b6226f.zip |
wallet: simplify crypto workers
Diffstat (limited to 'packages/taler-wallet-core/src/crypto/workers')
-rw-r--r-- | packages/taler-wallet-core/src/crypto/workers/cryptoDispatcher.ts (renamed from packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts) | 216 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts | 783 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts | 19 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/crypto/workers/rpcClient.ts | 90 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/crypto/workers/synchronousWorker.ts | 49 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactory.ts | 123 |
6 files changed, 158 insertions, 1122 deletions
diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoDispatcher.ts index ca498bff1..810273cca 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoDispatcher.ts @@ -22,39 +22,10 @@ /** * Imports. */ -import { DenominationRecord, WireFee } from "../../db.js"; - -import { CryptoWorker } from "./cryptoWorkerInterface.js"; - -import { - BlindedDenominationSignature, - CoinDepositPermission, - CoinEnvelope, - PlanchetUnblindInfo, - RecoupRefreshRequest, - RecoupRequest, - UnblindedSignature, -} from "@gnu-taler/taler-util"; - -import { - BenchmarkResult, - WithdrawalPlanchet, - PlanchetCreationRequest, - DepositInfo, - MakeSyncSignatureRequest, -} from "@gnu-taler/taler-util"; - -import * as timer from "../../util/timer.js"; import { Logger } from "@gnu-taler/taler-util"; -import { - CreateRecoupRefreshReqRequest, - CreateRecoupReqRequest, - DerivedRefreshSession, - DerivedTipPlanchet, - DeriveRefreshSessionRequest, - DeriveTipRequest, - SignTrackTransactionRequest, -} from "../cryptoTypes.js"; +import * as timer from "../../util/timer.js"; +import { nullCrypto, TalerCryptoInterface } from "../cryptoImplementation.js"; +import { CryptoWorker } from "./cryptoWorkerInterface.js"; const logger = new Logger("cryptoApi.ts"); @@ -80,7 +51,7 @@ interface WorkerState { interface WorkItem { operation: string; - args: any[]; + req: unknown; resolve: any; reject: any; @@ -122,10 +93,9 @@ export class CryptoApiStoppedError extends Error { } /** - * Crypto API that interfaces manages a background crypto thread - * for the execution of expensive operations. + * Dispatcher for cryptographic operations to underlying crypto workers. */ -export class CryptoApi { +export class CryptoDispatcher { private nextRpcId = 1; private workers: WorkerState[]; private workQueues: WorkItem[][]; @@ -191,7 +161,7 @@ export class CryptoApi { } const msg: any = { - args: work.args, + req: work.req, id: work.rpcId, operation: work.operation, }; @@ -277,7 +247,16 @@ export class CryptoApi { currentWorkItem.resolve(msg.data.result); } + cryptoApi: TalerCryptoInterface; + constructor(workerFactory: CryptoWorkerFactory) { + const fns: any = {}; + for (const name of Object.keys(nullCrypto)) { + fns[name] = (x: any) => this.doRpc(name, 0, x); + } + + this.cryptoApi = fns; + this.workerFactory = workerFactory; this.workers = new Array<WorkerState>(workerFactory.getConcurrency()); @@ -298,7 +277,7 @@ export class CryptoApi { private doRpc<T>( operation: string, priority: number, - ...args: any[] + req: unknown, ): Promise<T> { if (this.stopped) { throw new CryptoApiStoppedError(); @@ -307,7 +286,7 @@ export class CryptoApi { const rpcId = this.nextRpcId++; const workItem: WorkItem = { operation, - args, + req, resolve, reject, rpcId, @@ -362,163 +341,4 @@ export class CryptoApi { }); }); } - - createPlanchet(req: PlanchetCreationRequest): Promise<WithdrawalPlanchet> { - return this.doRpc<WithdrawalPlanchet>("createPlanchet", 1, req); - } - - unblindDenominationSignature(req: { - planchet: PlanchetUnblindInfo; - evSig: BlindedDenominationSignature; - }): Promise<UnblindedSignature> { - return this.doRpc<UnblindedSignature>( - "unblindDenominationSignature", - 1, - req, - ); - } - - createTipPlanchet(req: DeriveTipRequest): Promise<DerivedTipPlanchet> { - return this.doRpc<DerivedTipPlanchet>("createTipPlanchet", 1, req); - } - - signTrackTransaction(req: SignTrackTransactionRequest): Promise<string> { - return this.doRpc<string>("signTrackTransaction", 1, req); - } - - 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); - } - - eddsaGetPublic(key: string): Promise<{ priv: string; pub: string }> { - return this.doRpc<{ priv: string; pub: string }>("eddsaGetPublic", 1, key); - } - - 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( - versionCurrent: number, - paytoUri: string, - sig: string, - masterPub: string, - ): Promise<boolean> { - return this.doRpc<boolean>( - "isValidWireAccount", - 4, - versionCurrent, - paytoUri, - sig, - masterPub, - ); - } - - isValidContractTermsSignature( - contractTermsHash: string, - sig: string, - merchantPub: string, - ): Promise<boolean> { - return this.doRpc<boolean>( - "isValidContractTermsSignature", - 4, - contractTermsHash, - sig, - merchantPub, - ); - } - - createRecoupRequest(req: CreateRecoupReqRequest): Promise<RecoupRequest> { - return this.doRpc<RecoupRequest>("createRecoupRequest", 1, req); - } - - createRecoupRefreshRequest( - req: CreateRecoupRefreshReqRequest, - ): Promise<RecoupRefreshRequest> { - return this.doRpc<RecoupRefreshRequest>( - "createRecoupRefreshRequest", - 1, - req, - ); - } - - deriveRefreshSession( - req: DeriveRefreshSessionRequest, - ): Promise<DerivedRefreshSession> { - return this.doRpc<DerivedRefreshSession>("deriveRefreshSession", 4, req); - } - - signCoinLink( - oldCoinPriv: string, - newDenomHash: string, - oldCoinPub: string, - transferPub: string, - coinEv: CoinEnvelope, - ): 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); - } - - makeSyncSignature(req: MakeSyncSignatureRequest): Promise<string> { - return this.doRpc<string>("makeSyncSignature", 3, req); - } } diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts deleted file mode 100644 index b27067885..000000000 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts +++ /dev/null @@ -1,783 +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/> - */ - -/** - * Implementation of crypto-related high-level functions for the Taler wallet. - * - * @author Florian Dold <dold@taler.net> - */ - -/** - * Imports. - */ - -// FIXME: Crypto should not use DB Types! -import { - AmountJson, - Amounts, - BenchmarkResult, - buildSigPS, - CoinDepositPermission, - CoinEnvelope, - createEddsaKeyPair, - createHashContext, - decodeCrock, - DenomKeyType, - DepositInfo, - eddsaGetPublic, - eddsaSign, - eddsaVerify, - encodeCrock, - ExchangeProtocolVersion, - FreshCoin, - hash, - HashCodeString, - hashCoinEv, - hashCoinEvInner, - hashDenomPub, - hashTruncate32, - keyExchangeEcdheEddsa, - Logger, - MakeSyncSignatureRequest, - PlanchetCreationRequest, - WithdrawalPlanchet, - randomBytes, - RecoupRefreshRequest, - RecoupRequest, - RefreshPlanchetInfo, - rsaBlind, - rsaUnblind, - rsaVerify, - setupRefreshPlanchet, - setupRefreshTransferPub, - setupTipPlanchet, - setupWithdrawPlanchet, - stringToBytes, - TalerSignaturePurpose, - AbsoluteTime, - BlindedDenominationSignature, - UnblindedSignature, - PlanchetUnblindInfo, - TalerProtocolTimestamp, -} from "@gnu-taler/taler-util"; -import bigint from "big-integer"; -import { DenominationRecord, WireFee } from "../../db.js"; -import * as timer from "../../util/timer.js"; -import { - CreateRecoupRefreshReqRequest, - CreateRecoupReqRequest, - DerivedRefreshSession, - DerivedTipPlanchet, - DeriveRefreshSessionRequest, - DeriveTipRequest, - SignTrackTransactionRequest, -} from "../cryptoTypes.js"; - -const logger = new Logger("cryptoImplementation.ts"); - -function amountToBuffer(amount: AmountJson): Uint8Array { - const buffer = new ArrayBuffer(8 + 4 + 12); - const dvbuf = new DataView(buffer); - const u8buf = new Uint8Array(buffer); - const curr = stringToBytes(amount.currency); - if (typeof dvbuf.setBigUint64 !== "undefined") { - dvbuf.setBigUint64(0, BigInt(amount.value)); - } else { - const arr = bigint(amount.value).toArray(2 ** 8).value; - let offset = 8 - arr.length; - for (let i = 0; i < arr.length; i++) { - dvbuf.setUint8(offset++, arr[i]); - } - } - dvbuf.setUint32(8, amount.fraction); - u8buf.set(curr, 8 + 4); - - return u8buf; -} - -function timestampRoundedToBuffer(ts: TalerProtocolTimestamp): Uint8Array { - const b = new ArrayBuffer(8); - const v = new DataView(b); - // The buffer we sign over represents the timestamp in microseconds. - if (typeof v.setBigUint64 !== "undefined") { - const s = BigInt(ts.t_s) * BigInt(1000 * 1000); - v.setBigUint64(0, s); - } else { - const s = - ts.t_s === "never" ? bigint.zero : bigint(ts.t_s).multiply(1000 * 1000); - const arr = s.toArray(2 ** 8).value; - let offset = 8 - arr.length; - for (let i = 0; i < arr.length; i++) { - v.setUint8(offset++, arr[i]); - } - } - 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 }>; - - eddsaSign(req: { msg: string; priv: string }): Promise<{ sig: string }>; -} - -async function myEddsaSign( - primitiveWorker: PrimitiveWorker | undefined, - req: { msg: string; priv: string }, -): Promise<{ sig: string }> { - if (primitiveWorker) { - return primitiveWorker.eddsaSign(req); - } - const sig = eddsaSign(decodeCrock(req.msg), decodeCrock(req.priv)); - return { - sig: encodeCrock(sig), - }; -} - -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. - */ - async createPlanchet( - req: PlanchetCreationRequest, - ): Promise<WithdrawalPlanchet> { - const denomPub = req.denomPub; - if (denomPub.cipher === DenomKeyType.Rsa) { - const reservePub = decodeCrock(req.reservePub); - const denomPubRsa = decodeCrock(denomPub.rsa_public_key); - const derivedPlanchet = setupWithdrawPlanchet( - decodeCrock(req.secretSeed), - req.coinIndex, - ); - const coinPubHash = hash(derivedPlanchet.coinPub); - const ev = rsaBlind(coinPubHash, derivedPlanchet.bks, denomPubRsa); - const coinEv: CoinEnvelope = { - cipher: DenomKeyType.Rsa, - rsa_blinded_planchet: encodeCrock(ev), - }; - const amountWithFee = Amounts.add(req.value, req.feeWithdraw).amount; - const denomPubHash = hashDenomPub(req.denomPub); - const evHash = hashCoinEv(coinEv, encodeCrock(denomPubHash)); - const withdrawRequest = buildSigPS( - TalerSignaturePurpose.WALLET_RESERVE_WITHDRAW, - ) - .put(amountToBuffer(amountWithFee)) - .put(denomPubHash) - .put(evHash) - .build(); - - const sigResult = await myEddsaSign(this.primitiveWorker, { - msg: encodeCrock(withdrawRequest), - priv: req.reservePriv, - }); - - const planchet: WithdrawalPlanchet = { - blindingKey: encodeCrock(derivedPlanchet.bks), - coinEv, - coinPriv: encodeCrock(derivedPlanchet.coinPriv), - coinPub: encodeCrock(derivedPlanchet.coinPub), - coinValue: req.value, - denomPub, - denomPubHash: encodeCrock(denomPubHash), - reservePub: encodeCrock(reservePub), - withdrawSig: sigResult.sig, - coinEvHash: encodeCrock(evHash), - }; - return planchet; - } else { - throw Error("unsupported cipher, unable to create planchet"); - } - } - - /** - * Create a planchet used for tipping, including the private keys. - */ - createTipPlanchet(req: DeriveTipRequest): DerivedTipPlanchet { - if (req.denomPub.cipher !== DenomKeyType.Rsa) { - throw Error(`unsupported cipher (${req.denomPub.cipher})`); - } - const fc = setupTipPlanchet(decodeCrock(req.secretSeed), req.planchetIndex); - const denomPub = decodeCrock(req.denomPub.rsa_public_key); - const coinPubHash = hash(fc.coinPub); - const ev = rsaBlind(coinPubHash, fc.bks, denomPub); - const coinEv = { - cipher: DenomKeyType.Rsa, - rsa_blinded_planchet: encodeCrock(ev), - }; - const tipPlanchet: DerivedTipPlanchet = { - blindingKey: encodeCrock(fc.bks), - coinEv, - coinEvHash: encodeCrock( - hashCoinEv(coinEv, encodeCrock(hashDenomPub(req.denomPub))), - ), - coinPriv: encodeCrock(fc.coinPriv), - coinPub: encodeCrock(fc.coinPub), - }; - return tipPlanchet; - } - - signTrackTransaction(req: SignTrackTransactionRequest): string { - const p = buildSigPS(TalerSignaturePurpose.MERCHANT_TRACK_TRANSACTION) - .put(decodeCrock(req.contractTermsHash)) - .put(decodeCrock(req.wireHash)) - .put(decodeCrock(req.merchantPub)) - .put(decodeCrock(req.coinPub)) - .build(); - return encodeCrock(eddsaSign(p, decodeCrock(req.merchantPriv))); - } - - /** - * Create and sign a message to recoup a coin. - */ - createRecoupRequest(req: CreateRecoupReqRequest): RecoupRequest { - const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP) - .put(decodeCrock(req.denomPubHash)) - .put(decodeCrock(req.blindingKey)) - .build(); - - const coinPriv = decodeCrock(req.coinPriv); - const coinSig = eddsaSign(p, coinPriv); - if (req.denomPub.cipher === DenomKeyType.Rsa) { - const paybackRequest: RecoupRequest = { - coin_blind_key_secret: req.blindingKey, - coin_sig: encodeCrock(coinSig), - denom_pub_hash: req.denomPubHash, - denom_sig: req.denomSig, - // FIXME! - ewv: { - cipher: "RSA", - }, - }; - return paybackRequest; - } else { - throw new Error(); - } - } - - /** - * Create and sign a message to recoup a coin. - */ - createRecoupRefreshRequest( - req: CreateRecoupRefreshReqRequest, - ): RecoupRefreshRequest { - const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP_REFRESH) - .put(decodeCrock(req.denomPubHash)) - .put(decodeCrock(req.blindingKey)) - .build(); - - const coinPriv = decodeCrock(req.coinPriv); - const coinSig = eddsaSign(p, coinPriv); - if (req.denomPub.cipher === DenomKeyType.Rsa) { - const recoupRequest: RecoupRefreshRequest = { - coin_blind_key_secret: req.blindingKey, - coin_sig: encodeCrock(coinSig), - denom_pub_hash: req.denomPubHash, - denom_sig: req.denomSig, - // FIXME! - ewv: { - cipher: "RSA", - }, - }; - return recoupRequest; - } else { - throw new Error(); - } - } - - /** - * Check if a payment signature is valid. - */ - isValidPaymentSignature( - sig: string, - contractHash: string, - merchantPub: string, - ): boolean { - const p = buildSigPS(TalerSignaturePurpose.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. - */ - async isValidWireFee( - type: string, - wf: WireFee, - masterPub: string, - ): Promise<boolean> { - const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_FEES) - .put(hash(stringToBytes(type + "\0"))) - .put(timestampRoundedToBuffer(wf.startStamp)) - .put(timestampRoundedToBuffer(wf.endStamp)) - .put(amountToBuffer(wf.wireFee)) - .put(amountToBuffer(wf.closingFee)) - .put(amountToBuffer(wf.wadFee)) - .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. - */ - async isValidDenom( - denom: DenominationRecord, - masterPub: string, - ): Promise<boolean> { - const p = buildSigPS(TalerSignaturePurpose.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); - const res = eddsaVerify(p, sig, pub); - return res; - } - - isValidWireAccount( - versionCurrent: ExchangeProtocolVersion, - paytoUri: string, - sig: string, - masterPub: string, - ): boolean { - const paytoHash = hashTruncate32(stringToBytes(paytoUri + "\0")); - const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_DETAILS) - .put(paytoHash) - .build(); - return eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub)); - } - - isValidContractTermsSignature( - contractTermsHash: string, - sig: string, - merchantPub: string, - ): boolean { - const cthDec = decodeCrock(contractTermsHash); - const p = buildSigPS(TalerSignaturePurpose.MERCHANT_CONTRACT) - .put(cthDec) - .build(); - return eddsaVerify(p, decodeCrock(sig), decodeCrock(merchantPub)); - } - - /** - * Create a new EdDSA key pair. - */ - createEddsaKeypair(): { priv: string; pub: string } { - const pair = createEddsaKeyPair(); - return { - priv: encodeCrock(pair.eddsaPriv), - pub: encodeCrock(pair.eddsaPub), - }; - } - - eddsaGetPublic(key: string): { priv: string; pub: string } { - return { - priv: key, - pub: encodeCrock(eddsaGetPublic(decodeCrock(key))), - }; - } - - unblindDenominationSignature(req: { - planchet: PlanchetUnblindInfo; - evSig: BlindedDenominationSignature; - }): UnblindedSignature { - if (req.evSig.cipher === DenomKeyType.Rsa) { - if (req.planchet.denomPub.cipher !== DenomKeyType.Rsa) { - throw new Error( - "planchet cipher does not match blind signature cipher", - ); - } - const denomSig = rsaUnblind( - decodeCrock(req.evSig.blinded_rsa_signature), - decodeCrock(req.planchet.denomPub.rsa_public_key), - decodeCrock(req.planchet.blindingKey), - ); - return { - cipher: DenomKeyType.Rsa, - rsa_signature: encodeCrock(denomSig), - }; - } else { - throw Error(`unblinding for cipher ${req.evSig.cipher} not implemented`); - } - } - - /** - * 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. - */ - async signDepositPermission( - depositInfo: DepositInfo, - ): Promise<CoinDepositPermission> { - // FIXME: put extensions here if used - const hExt = new Uint8Array(64); - const hAgeCommitment = new Uint8Array(32); - let d: Uint8Array; - if (depositInfo.denomKeyType === DenomKeyType.Rsa) { - d = buildSigPS(TalerSignaturePurpose.WALLET_COIN_DEPOSIT) - .put(decodeCrock(depositInfo.contractTermsHash)) - .put(hAgeCommitment) - .put(hExt) - .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)) - .build(); - } else { - throw Error("unsupported exchange protocol version"); - } - const coinSigRes = await myEddsaSign(this.primitiveWorker, { - msg: encodeCrock(d), - priv: depositInfo.coinPriv, - }); - - if (depositInfo.denomKeyType === DenomKeyType.Rsa) { - const s: CoinDepositPermission = { - coin_pub: depositInfo.coinPub, - coin_sig: coinSigRes.sig, - contribution: Amounts.stringify(depositInfo.spendAmount), - h_denom: depositInfo.denomPubHash, - exchange_url: depositInfo.exchangeBaseUrl, - ub_sig: { - cipher: DenomKeyType.Rsa, - rsa_signature: depositInfo.denomSig.rsa_signature, - }, - }; - return s; - } else { - throw Error( - `unsupported denomination cipher (${depositInfo.denomKeyType})`, - ); - } - } - - async deriveRefreshSession( - req: DeriveRefreshSessionRequest, - ): Promise<DerivedRefreshSession> { - const { - newCoinDenoms, - feeRefresh: meltFee, - kappa, - meltCoinDenomPubHash, - meltCoinPriv, - meltCoinPub, - sessionSecretSeed: refreshSessionSecretSeed, - } = req; - - const currency = newCoinDenoms[0].value.currency; - let valueWithFee = Amounts.getZero(currency); - - for (const ncd of newCoinDenoms) { - const t = Amounts.add(ncd.value, ncd.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: RefreshPlanchetInfo[][] = []; - - for (let i = 0; i < kappa; i++) { - const transferKeyPair = setupRefreshTransferPub( - decodeCrock(refreshSessionSecretSeed), - i, - ); - sessionHc.update(transferKeyPair.ecdhePub); - transferPrivs.push(encodeCrock(transferKeyPair.ecdhePriv)); - transferPubs.push(encodeCrock(transferKeyPair.ecdhePub)); - } - - for (const denomSel of newCoinDenoms) { - for (let i = 0; i < denomSel.count; i++) { - if (denomSel.denomPub.cipher === DenomKeyType.Rsa) { - const denomPubHash = hashDenomPub(denomSel.denomPub); - sessionHc.update(denomPubHash); - } else { - throw new Error(); - } - } - } - - sessionHc.update(decodeCrock(meltCoinPub)); - sessionHc.update(amountToBuffer(valueWithFee)); - - for (let i = 0; i < kappa; i++) { - const planchets: RefreshPlanchetInfo[] = []; - for (let j = 0; j < newCoinDenoms.length; j++) { - const denomSel = newCoinDenoms[j]; - for (let k = 0; k < denomSel.count; k++) { - const coinIndex = planchets.length; - const transferPriv = decodeCrock(transferPrivs[i]); - const oldCoinPub = decodeCrock(meltCoinPub); - const transferSecret = keyExchangeEcdheEddsa( - transferPriv, - oldCoinPub, - ); - let coinPub: Uint8Array; - let coinPriv: Uint8Array; - let blindingFactor: Uint8Array; - // disabled while not implemented in the C code - if (0 && 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 coinPubHash = hash(coinPub); - if (denomSel.denomPub.cipher !== DenomKeyType.Rsa) { - throw Error("unsupported cipher, can't create refresh session"); - } - const rsaDenomPub = decodeCrock(denomSel.denomPub.rsa_public_key); - const ev = rsaBlind(coinPubHash, blindingFactor, rsaDenomPub); - const coinEv: CoinEnvelope = { - cipher: DenomKeyType.Rsa, - rsa_blinded_planchet: encodeCrock(ev), - }; - const coinEvHash = hashCoinEv( - coinEv, - encodeCrock(hashDenomPub(denomSel.denomPub)), - ); - const planchet: RefreshPlanchetInfo = { - blindingKey: encodeCrock(blindingFactor), - coinEv, - coinPriv: encodeCrock(coinPriv), - coinPub: encodeCrock(coinPub), - coinEvHash: encodeCrock(coinEvHash), - }; - planchets.push(planchet); - hashCoinEvInner(coinEv, sessionHc); - } - } - planchetsForGammas.push(planchets); - } - - const sessionHash = sessionHc.finish(); - let confirmData: Uint8Array; - // FIXME: fill in age commitment - const hAgeCommitment = new Uint8Array(32); - confirmData = buildSigPS(TalerSignaturePurpose.WALLET_COIN_MELT) - .put(sessionHash) - .put(decodeCrock(meltCoinDenomPubHash)) - .put(hAgeCommitment) - .put(amountToBuffer(valueWithFee)) - .put(amountToBuffer(meltFee)) - .build(); - - const confirmSigResp = await myEddsaSign(this.primitiveWorker, { - msg: encodeCrock(confirmData), - priv: meltCoinPriv, - }); - - const refreshSession: DerivedRefreshSession = { - confirmSig: confirmSigResp.sig, - hash: encodeCrock(sessionHash), - meltCoinPub: meltCoinPub, - planchetsForGammas: planchetsForGammas, - transferPrivs, - transferPubs, - meltValueWithFee: valueWithFee, - }; - - return refreshSession; - } - - /** - * Hash a string including the zero terminator. - */ - hashString(str: string): string { - const b = stringToBytes(str + "\0"); - return encodeCrock(hash(b)); - } - - /** - * Hash a crockford encoded value. - */ - hashEncoded(encodedBytes: string): string { - return encodeCrock(hash(decodeCrock(encodedBytes))); - } - - async signCoinLink( - oldCoinPriv: string, - newDenomHash: string, - oldCoinPub: string, - transferPub: string, - coinEv: CoinEnvelope, - ): Promise<string> { - const coinEvHash = hashCoinEv(coinEv, newDenomHash); - // FIXME: fill in - const hAgeCommitment = new Uint8Array(32); - const coinLink = buildSigPS(TalerSignaturePurpose.WALLET_COIN_LINK) - .put(decodeCrock(newDenomHash)) - .put(decodeCrock(transferPub)) - .put(hAgeCommitment) - .put(coinEvHash) - .build(); - const sig = await myEddsaSign(this.primitiveWorker, { - msg: encodeCrock(coinLink), - priv: oldCoinPriv, - }); - return sig.sig; - } - - benchmark(repetitions: number): BenchmarkResult { - let time_hash = BigInt(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 = BigInt(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 = BigInt(0); - for (let i = 0; i < repetitions; i++) { - const start = timer.performanceNow(); - createEddsaKeyPair(); - time_eddsa_create += timer.performanceNow() - start; - } - - let time_eddsa_sign = BigInt(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 = BigInt(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: Number(time_hash), - hash_big: Number(time_hash_big), - eddsa_create: Number(time_eddsa_create), - eddsa_sign: Number(time_eddsa_sign), - eddsa_verify: Number(time_eddsa_verify), - }, - }; - } - - makeSyncSignature(req: MakeSyncSignatureRequest): string { - const hNew = decodeCrock(req.newHash); - let hOld: Uint8Array; - if (req.oldHash) { - hOld = decodeCrock(req.oldHash); - } else { - hOld = new Uint8Array(64); - } - const sigBlob = buildSigPS(TalerSignaturePurpose.SYNC_BACKUP_UPLOAD) - .put(hOld) - .put(hNew) - .build(); - const uploadSig = eddsaSign(sigBlob, decodeCrock(req.accountPriv)); - return encodeCrock(uploadSig); - } -} diff --git a/packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts b/packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts index df57635d1..42370fc1b 100644 --- a/packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts +++ b/packages/taler-wallet-core/src/crypto/workers/nodeThreadWorker.ts @@ -17,11 +17,11 @@ /** * Imports */ -import { CryptoWorkerFactory } from "./cryptoApi.js"; +import { CryptoWorkerFactory } from "./cryptoDispatcher.js"; import { CryptoWorker } from "./cryptoWorkerInterface.js"; import os from "os"; -import { CryptoImplementation } from "./cryptoImplementation.js"; import { Logger } from "@gnu-taler/taler-util"; +import { nativeCryptoR } from "../cryptoImplementation.js"; const logger = new Logger("nodeThreadWorker.ts"); @@ -69,9 +69,9 @@ const workerCode = ` * a message. */ export function handleWorkerMessage(msg: any): void { - const args = msg.args; - if (!Array.isArray(args)) { - console.error("args must be array"); + const req = msg.req; + if (typeof req !== "object") { + console.error("request must be an object"); return; } const id = msg.id; @@ -86,7 +86,7 @@ export function handleWorkerMessage(msg: any): void { } const handleRequest = async (): Promise<void> => { - const impl = new CryptoImplementation(); + const impl = nativeCryptoR; if (!(operation in impl)) { console.error(`crypto operation '${operation}' not found`); @@ -94,12 +94,11 @@ export function handleWorkerMessage(msg: any): void { } try { - const result = await (impl as any)[operation](...args); + const result = await (impl as any)[operation](impl, req); // eslint-disable-next-line @typescript-eslint/no-var-requires const _r = "require"; - const worker_threads: typeof import("worker_threads") = module[_r]( - "worker_threads", - ); + const worker_threads: typeof import("worker_threads") = + module[_r]("worker_threads"); // const worker_threads = require("worker_threads"); const p = worker_threads.parentPort; diff --git a/packages/taler-wallet-core/src/crypto/workers/rpcClient.ts b/packages/taler-wallet-core/src/crypto/workers/rpcClient.ts new file mode 100644 index 000000000..a8df8b4c6 --- /dev/null +++ b/packages/taler-wallet-core/src/crypto/workers/rpcClient.ts @@ -0,0 +1,90 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + 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/> + */ + +/** + * Imports. + */ +import { Logger } from "@gnu-taler/taler-util"; +import child_process from "child_process"; +import type internal from "stream"; +import { OpenedPromise, openPromise } from "../../util/promiseUtils.js"; + +const logger = new Logger("synchronousWorkerFactory.ts"); + +export class CryptoRpcClient { + proc: child_process.ChildProcessByStdio< + internal.Writable, + internal.Readable, + null + >; + requests: Array<{ + p: OpenedPromise<any>; + req: any; + }> = []; + + constructor() { + const stdoutChunks: Buffer[] = []; + this.proc = child_process.spawn("taler-crypto-worker", { + //stdio: ["pipe", "pipe", "inherit"], + stdio: ["pipe", "pipe", "inherit"], + detached: true, + }); + this.proc.on("close", (): void => { + 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 (!req) { + throw Error("request was undefined"); + } + 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 queueRequest(req: any): Promise<any> { + const p = openPromise<any>(); + if (this.requests.length === 0) { + this.proc.ref(); + } + this.requests.push({ req, p }); + this.proc.stdin.write(`${JSON.stringify(req)}\n`); + return p.promise; + } +} diff --git a/packages/taler-wallet-core/src/crypto/workers/synchronousWorker.ts b/packages/taler-wallet-core/src/crypto/workers/synchronousWorker.ts index 4d341718e..1d7539ed6 100644 --- a/packages/taler-wallet-core/src/crypto/workers/synchronousWorker.ts +++ b/packages/taler-wallet-core/src/crypto/workers/synchronousWorker.ts @@ -16,11 +16,10 @@ import { Logger } from "@gnu-taler/taler-util"; import { - CryptoImplementation, - PrimitiveWorker -} from "./cryptoImplementation.js"; - - + nativeCryptoR, + TalerCryptoInterfaceR, +} from "../cryptoImplementation.js"; +import { CryptoRpcClient } from "./rpcClient.js"; const logger = new Logger("synchronousWorker.ts"); @@ -38,9 +37,33 @@ export class SynchronousCryptoWorker { */ onerror: undefined | ((m: any) => void); - constructor(private primitiveWorker?: PrimitiveWorker) { + cryptoImplR: TalerCryptoInterfaceR; + + rpcClient: CryptoRpcClient | undefined; + + constructor() { this.onerror = undefined; this.onmessage = undefined; + + this.cryptoImplR = { ...nativeCryptoR }; + + if ( + process.env["TALER_WALLET_RPC_CRYPRO"] || + // Old name + process.env["TALER_WALLET_PRIMITIVE_WORKER"] + ) { + const rpc = (this.rpcClient = new CryptoRpcClient()); + this.cryptoImplR.eddsaSign = async (_, req) => { + logger.trace("making RPC request"); + return await rpc.queueRequest({ + op: "eddsa_sign", + args: { + msg: req.msg, + priv: req.priv, + }, + }); + }; + } } /** @@ -66,9 +89,9 @@ export class SynchronousCryptoWorker { private async handleRequest( operation: string, id: number, - args: string[], + req: unknown, ): Promise<void> { - const impl = new CryptoImplementation(this.primitiveWorker); + const impl = this.cryptoImplR; if (!(operation in impl)) { console.error(`crypto operation '${operation}' not found`); @@ -77,7 +100,7 @@ export class SynchronousCryptoWorker { let result: any; try { - result = await (impl as any)[operation](...args); + result = await (impl as any)[operation](impl, req); } catch (e) { logger.error("error during operation", e); return; @@ -94,9 +117,9 @@ export class SynchronousCryptoWorker { * 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"); + const req = msg.req; + if (typeof req !== "object") { + console.error("request must be an object"); return; } const id = msg.id; @@ -110,7 +133,7 @@ export class SynchronousCryptoWorker { return; } - this.handleRequest(operation, id, args).catch((e) => { + this.handleRequest(operation, id, req).catch((e) => { console.error("Error while handling crypto request:", e); }); } diff --git a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactory.ts b/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactory.ts index ca63c7687..47f58be13 100644 --- a/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactory.ts +++ b/packages/taler-wallet-core/src/crypto/workers/synchronousWorkerFactory.ts @@ -14,121 +14,13 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { - PrimitiveWorker, -} from "./cryptoImplementation.js"; - -import { CryptoWorkerFactory } from "./cryptoApi.js"; +/** + * Imports. + */ +import { CryptoWorkerFactory } from "./cryptoDispatcher.js"; import { CryptoWorker } from "./cryptoWorkerInterface.js"; - -import child_process from "child_process"; -import type internal from "stream"; -import { OpenedPromise, openPromise } from "../../index.js"; -import { Logger } from "@gnu-taler/taler-util"; import { SynchronousCryptoWorker } from "./synchronousWorker.js"; -const logger = new Logger("synchronousWorkerFactory.ts"); - -class MyPrimitiveWorker implements PrimitiveWorker { - proc: child_process.ChildProcessByStdio< - internal.Writable, - internal.Readable, - null - >; - requests: Array<{ - p: OpenedPromise<any>; - req: any; - }> = []; - - constructor() { - const stdoutChunks: Buffer[] = []; - this.proc = child_process.spawn("taler-crypto-worker", { - //stdio: ["pipe", "pipe", "inherit"], - stdio: ["pipe", "pipe", "inherit"], - detached: true, - }); - this.proc.on("close", (): void => { - 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 (!req) { - throw Error("request was undefined") - } - if (this.requests.length === 0) { - this.proc.unref(); - } - //logger.info(`got response: ${str}`); - req.p.resolve(JSON.parse(str)); - } else { - stdoutChunks.push(x); - } - } else { - throw Error(`unexpected data chunk type (${typeof x})`); - } - }); - } - - async setupRefreshPlanchet(req: { - transfer_secret: string; - coin_index: number; - }): Promise<{ - coin_pub: string; - coin_priv: string; - blinding_key: string; - }> { - return this.queueRequest({ - op: "setup_refresh_planchet", - args: req, - }); - } - - async queueRequest(req: any): Promise<any> { - const p = openPromise<any>(); - if (this.requests.length === 0) { - this.proc.ref(); - } - this.requests.push({ req, p }); - this.proc.stdin.write(`${JSON.stringify(req)}\n`); - return p.promise; - } - - async eddsaVerify(req: { - msg: string; - sig: string; - pub: string; - }): Promise<{ valid: boolean }> { - return this.queueRequest({ - op: "eddsa_verify", - args: req, - }); - } - - async eddsaSign(req: { - msg: string; - priv: string; - }): Promise<{ sig: string }> { - return this.queueRequest({ - op: "eddsa_sign", - args: req, - }); - } -} - /** * The synchronous crypto worker produced by this factory doesn't run in the * background, but actually blocks the caller until the operation is done. @@ -139,12 +31,7 @@ export class SynchronousCryptoWorkerFactory implements CryptoWorkerFactory { throw Error("cannot make worker, require(...) not defined"); } - let primitiveWorker; - if (process.env["TALER_WALLET_PRIMITIVE_WORKER"]) { - primitiveWorker = new MyPrimitiveWorker(); - } - - return new SynchronousCryptoWorker(primitiveWorker); + return new SynchronousCryptoWorker(); } getConcurrency(): number { |