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 | |
parent | e21c1b31928cd6bfe90150ea2de19799b6359c40 (diff) |
wallet: simplify crypto workers
27 files changed, 784 insertions, 714 deletions
diff --git a/packages/taler-wallet-cli/src/bench2.ts b/packages/taler-wallet-cli/src/bench2.ts index 43c28882e..c1fa674c4 100644 --- a/packages/taler-wallet-cli/src/bench2.ts +++ b/packages/taler-wallet-cli/src/bench2.ts @@ -27,7 +27,7 @@ import { import { checkReserve, createFakebankReserve, - CryptoApi, + CryptoDispatcher, depositCoin, downloadExchangeInfo, findDenomOrThrow, @@ -50,7 +50,8 @@ export async function runBench2(configJson: any): Promise<void> { // Validate the configuration file for this benchmark. const benchConf = codecForBench2Config().decode(configJson); const curr = benchConf.currency; - const cryptoApi = new CryptoApi(new SynchronousCryptoWorkerFactory()); + const cryptoDisp = new CryptoDispatcher(new SynchronousCryptoWorkerFactory()); + const cryptoApi = cryptoDisp.cryptoApi; const http = new NodeHttpLib(); http.setThrottling(false); diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts index 0ed935a15..3b79f78b8 100644 --- a/packages/taler-wallet-cli/src/index.ts +++ b/packages/taler-wallet-cli/src/index.ts @@ -50,18 +50,21 @@ import { NodeHttpLib, getDefaultNodeWallet, NodeThreadCryptoWorkerFactory, - CryptoApi, walletCoreDebugFlags, WalletApiOperation, WalletCoreApiClient, Wallet, getErrorDetailFromException, + CryptoDispatcher, + SynchronousCryptoWorkerFactory, + nativeCrypto, } from "@gnu-taler/taler-wallet-core"; import { lintExchangeDeployment } from "./lint.js"; import { runBench1 } from "./bench1.js"; import { runEnv1 } from "./env1.js"; import { GlobalTestState, runTestWithState } from "./harness/harness.js"; import { runBench2 } from "./bench2.js"; +import { TalerCryptoInterface, TalerCryptoInterfaceR } from "@gnu-taler/taler-wallet-core/src/crypto/cryptoImplementation"; // This module also serves as the entry point for the crypto // thread worker, and thus must expose these two handlers. @@ -1121,14 +1124,30 @@ testCli.subcommand("tvgcheck", "tvgcheck").action(async (args) => { console.log("check passed!"); }); -testCli.subcommand("cryptoworker", "cryptoworker").action(async (args) => { - const workerFactory = new NodeThreadCryptoWorkerFactory(); - const cryptoApi = new CryptoApi(workerFactory); - const input = "foo"; - console.log(`testing crypto worker by hashing string '${input}'`); - const res = await cryptoApi.hashString(input); - console.log(res); -}); +testCli + .subcommand("cryptoworker", "cryptoworker") + .maybeOption("impl", ["--impl"], clk.STRING) + .action(async (args) => { + let cryptoApi: TalerCryptoInterface; + if (!args.cryptoworker.impl || args.cryptoworker.impl === "node") { + const workerFactory = new NodeThreadCryptoWorkerFactory(); + const cryptoDisp = new CryptoDispatcher(workerFactory); + cryptoApi = cryptoDisp.cryptoApi; + } else if (args.cryptoworker.impl === "sync") { + const workerFactory = new SynchronousCryptoWorkerFactory(); + const cryptoDisp = new CryptoDispatcher(workerFactory); + cryptoApi = cryptoDisp.cryptoApi; + } else if (args.cryptoworker.impl === "none") { + cryptoApi = nativeCrypto; + } else { + throw Error("invalid impl"); + } + + const input = "foo"; + console.log(`testing crypto worker by hashing string '${input}'`); + const res = await cryptoApi.hashString({ str: input }); + console.log(res); + }); export function main() { if (process.env["TALER_WALLET_DEBUG_DENOMSEL_ALLOW_LATE"]) { diff --git a/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts b/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts index 146603f3a..d8abae136 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts @@ -20,7 +20,7 @@ import { j2s } from "@gnu-taler/taler-util"; import { checkReserve, - CryptoApi, + CryptoDispatcher, depositCoin, downloadExchangeInfo, findDenomOrThrow, @@ -44,7 +44,8 @@ export async function runWalletDblessTest(t: GlobalTestState) { const { bank, exchange } = await createSimpleTestkudosEnvironment(t); const http = new NodeHttpLib(); - const cryptoApi = new CryptoApi(new SynchronousCryptoWorkerFactory()); + const cryptiDisp = new CryptoDispatcher(new SynchronousCryptoWorkerFactory()); + const cryptoApi = cryptiDisp.cryptoApi; try { // Withdraw digital cash into the wallet. diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts index b27067885..63b2687b6 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts @@ -44,7 +44,6 @@ import { ExchangeProtocolVersion, FreshCoin, hash, - HashCodeString, hashCoinEv, hashCoinEvInner, hashDenomPub, @@ -67,15 +66,13 @@ import { 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 { DenominationRecord, WireFee } from "../db.js"; import { CreateRecoupRefreshReqRequest, CreateRecoupReqRequest, @@ -84,90 +81,288 @@ import { DeriveRefreshSessionRequest, DeriveTipRequest, SignTrackTransactionRequest, -} from "../cryptoTypes.js"; +} from "./cryptoTypes.js"; -const logger = new Logger("cryptoImplementation.ts"); +//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); +/** + * Interface for (asynchronous) cryptographic operations that + * Taler uses. + */ +export interface TalerCryptoInterface { + /** + * Create a pre-coin of the given denomination to be withdrawn from then given + * reserve. + */ + createPlanchet(req: PlanchetCreationRequest): Promise<WithdrawalPlanchet>; - return u8buf; + eddsaSign(req: EddsaSignRequest): Promise<EddsaSignResponse>; + + /** + * Create a planchet used for tipping, including the private keys. + */ + createTipPlanchet(req: DeriveTipRequest): Promise<DerivedTipPlanchet>; + + signTrackTransaction( + req: SignTrackTransactionRequest, + ): Promise<EddsaSigningResult>; + + createRecoupRequest(req: CreateRecoupReqRequest): Promise<RecoupRequest>; + + createRecoupRefreshRequest( + req: CreateRecoupRefreshReqRequest, + ): Promise<RecoupRefreshRequest>; + + isValidPaymentSignature( + req: PaymentSignatureValidationRequest, + ): Promise<ValidationResult>; + + isValidWireFee(req: WireFeeValidationRequest): Promise<ValidationResult>; + + isValidDenom(req: DenominationValidationRequest): Promise<ValidationResult>; + + isValidWireAccount( + req: WireAccountValidationRequest, + ): Promise<ValidationResult>; + + isValidContractTermsSignature( + req: ContractTermsValidationRequest, + ): Promise<ValidationResult>; + + createEddsaKeypair(req: {}): Promise<EddsaKeypair>; + + eddsaGetPublic(req: EddsaGetPublicRequest): Promise<EddsaKeypair>; + + unblindDenominationSignature( + req: UnblindDenominationSignatureRequest, + ): Promise<UnblindedSignature>; + + rsaUnblind(req: RsaUnblindRequest): Promise<RsaUnblindResponse>; + + rsaVerify(req: RsaVerificationRequest): Promise<ValidationResult>; + + signDepositPermission( + depositInfo: DepositInfo, + ): Promise<CoinDepositPermission>; + + deriveRefreshSession( + req: DeriveRefreshSessionRequest, + ): Promise<DerivedRefreshSession>; + + hashString(req: HashStringRequest): Promise<HashStringResult>; + + signCoinLink(req: SignCoinLinkRequest): Promise<EddsaSigningResult>; + + makeSyncSignature(req: MakeSyncSignatureRequest): Promise<EddsaSigningResult>; } -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); +/** + * Implementation of the Taler crypto interface where every function + * always throws. Only useful in practice as a way to iterate through + * all possible crypto functions. + * + * (This list can be easily auto-generated by your favorite IDE). + */ +export const nullCrypto: TalerCryptoInterface = { + createPlanchet: function ( + req: PlanchetCreationRequest, + ): Promise<WithdrawalPlanchet> { + throw new Error("Function not implemented."); + }, + eddsaSign: function (req: EddsaSignRequest): Promise<EddsaSignResponse> { + throw new Error("Function not implemented."); + }, + createTipPlanchet: function ( + req: DeriveTipRequest, + ): Promise<DerivedTipPlanchet> { + throw new Error("Function not implemented."); + }, + signTrackTransaction: function ( + req: SignTrackTransactionRequest, + ): Promise<EddsaSigningResult> { + throw new Error("Function not implemented."); + }, + createRecoupRequest: function ( + req: CreateRecoupReqRequest, + ): Promise<RecoupRequest> { + throw new Error("Function not implemented."); + }, + createRecoupRefreshRequest: function ( + req: CreateRecoupRefreshReqRequest, + ): Promise<RecoupRefreshRequest> { + throw new Error("Function not implemented."); + }, + isValidPaymentSignature: function ( + req: PaymentSignatureValidationRequest, + ): Promise<ValidationResult> { + throw new Error("Function not implemented."); + }, + isValidWireFee: function ( + req: WireFeeValidationRequest, + ): Promise<ValidationResult> { + throw new Error("Function not implemented."); + }, + isValidDenom: function ( + req: DenominationValidationRequest, + ): Promise<ValidationResult> { + throw new Error("Function not implemented."); + }, + isValidWireAccount: function ( + req: WireAccountValidationRequest, + ): Promise<ValidationResult> { + throw new Error("Function not implemented."); + }, + isValidContractTermsSignature: function ( + req: ContractTermsValidationRequest, + ): Promise<ValidationResult> { + throw new Error("Function not implemented."); + }, + createEddsaKeypair: function (req: {}): Promise<EddsaKeypair> { + throw new Error("Function not implemented."); + }, + eddsaGetPublic: function (req: EddsaGetPublicRequest): Promise<EddsaKeypair> { + throw new Error("Function not implemented."); + }, + unblindDenominationSignature: function ( + req: UnblindDenominationSignatureRequest, + ): Promise<UnblindedSignature> { + throw new Error("Function not implemented."); + }, + rsaUnblind: function (req: RsaUnblindRequest): Promise<RsaUnblindResponse> { + throw new Error("Function not implemented."); + }, + rsaVerify: function (req: RsaVerificationRequest): Promise<ValidationResult> { + throw new Error("Function not implemented."); + }, + signDepositPermission: function ( + depositInfo: DepositInfo, + ): Promise<CoinDepositPermission> { + throw new Error("Function not implemented."); + }, + deriveRefreshSession: function ( + req: DeriveRefreshSessionRequest, + ): Promise<DerivedRefreshSession> { + throw new Error("Function not implemented."); + }, + hashString: function (req: HashStringRequest): Promise<HashStringResult> { + throw new Error("Function not implemented."); + }, + signCoinLink: function ( + req: SignCoinLinkRequest, + ): Promise<EddsaSigningResult> { + throw new Error("Function not implemented."); + }, + makeSyncSignature: function ( + req: MakeSyncSignatureRequest, + ): Promise<EddsaSigningResult> { + throw new Error("Function not implemented."); + }, +}; + +export type WithArg<X> = X extends (req: infer T) => infer R + ? (tci: TalerCryptoInterfaceR, req: T) => R + : never; + +export type TalerCryptoInterfaceR = { + [x in keyof TalerCryptoInterface]: WithArg<TalerCryptoInterface[x]>; +}; + +export interface SignCoinLinkRequest { + oldCoinPriv: string; + newDenomHash: string; + oldCoinPub: string; + transferPub: string; + coinEv: CoinEnvelope; } -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 }>; +export interface RsaVerificationRequest { + hm: string; + sig: string; + pk: 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 interface EddsaSigningResult { + sig: string; } -export class CryptoImplementation { - static enableTracing = false; +export interface ValidationResult { + valid: boolean; +} - constructor(private primitiveWorker?: PrimitiveWorker) {} +export interface HashStringRequest { + str: string; +} + +export interface HashStringResult { + h: string; +} + +export interface WireFeeValidationRequest { + type: string; + wf: WireFee; + masterPub: string; +} + +export interface DenominationValidationRequest { + denom: DenominationRecord; + masterPub: string; +} + +export interface PaymentSignatureValidationRequest { + sig: string; + contractHash: string; + merchantPub: string; +} + +export interface ContractTermsValidationRequest { + contractTermsHash: string; + sig: string; + merchantPub: string; +} + +export interface WireAccountValidationRequest { + versionCurrent: ExchangeProtocolVersion; + paytoUri: string; + sig: string; + masterPub: string; +} + +export interface EddsaKeypair { + priv: string; + pub: string; +} + +export interface EddsaGetPublicRequest { + priv: string; +} + +export interface UnblindDenominationSignatureRequest { + planchet: PlanchetUnblindInfo; + evSig: BlindedDenominationSignature; +} + +export interface RsaUnblindRequest { + blindedSig: string; + bk: string; + pk: string; +} + +export interface RsaUnblindResponse { + sig: string; +} + +export const nativeCryptoR: TalerCryptoInterfaceR = { + async eddsaSign( + tci: TalerCryptoInterfaceR, + req: EddsaSignRequest, + ): Promise<EddsaSignResponse> { + return { + sig: encodeCrock(eddsaSign(decodeCrock(req.msg), decodeCrock(req.priv))), + }; + }, - /** - * Create a pre-coin of the given denomination to be withdrawn from then given - * reserve. - */ async createPlanchet( + tci: TalerCryptoInterfaceR, req: PlanchetCreationRequest, ): Promise<WithdrawalPlanchet> { const denomPub = req.denomPub; @@ -195,7 +390,7 @@ export class CryptoImplementation { .put(evHash) .build(); - const sigResult = await myEddsaSign(this.primitiveWorker, { + const sigResult = await tci.eddsaSign(tci, { msg: encodeCrock(withdrawRequest), priv: req.reservePriv, }); @@ -216,12 +411,12 @@ export class CryptoImplementation { } else { throw Error("unsupported cipher, unable to create planchet"); } - } + }, - /** - * Create a planchet used for tipping, including the private keys. - */ - createTipPlanchet(req: DeriveTipRequest): DerivedTipPlanchet { + async createTipPlanchet( + tci: TalerCryptoInterfaceR, + req: DeriveTipRequest, + ): Promise<DerivedTipPlanchet> { if (req.denomPub.cipher !== DenomKeyType.Rsa) { throw Error(`unsupported cipher (${req.denomPub.cipher})`); } @@ -243,22 +438,28 @@ export class CryptoImplementation { coinPub: encodeCrock(fc.coinPub), }; return tipPlanchet; - } + }, - signTrackTransaction(req: SignTrackTransactionRequest): string { + async signTrackTransaction( + tci: TalerCryptoInterfaceR, + req: SignTrackTransactionRequest, + ): Promise<EddsaSigningResult> { 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))); - } + return { sig: encodeCrock(eddsaSign(p, decodeCrock(req.merchantPriv))) }; + }, /** * Create and sign a message to recoup a coin. */ - createRecoupRequest(req: CreateRecoupReqRequest): RecoupRequest { + async createRecoupRequest( + tci: TalerCryptoInterfaceR, + req: CreateRecoupReqRequest, + ): Promise<RecoupRequest> { const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP) .put(decodeCrock(req.denomPubHash)) .put(decodeCrock(req.blindingKey)) @@ -281,14 +482,15 @@ export class CryptoImplementation { } else { throw new Error(); } - } + }, /** * Create and sign a message to recoup a coin. */ - createRecoupRefreshRequest( + async createRecoupRefreshRequest( + tci: TalerCryptoInterfaceR, req: CreateRecoupRefreshReqRequest, - ): RecoupRefreshRequest { + ): Promise<RecoupRefreshRequest> { const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP_REFRESH) .put(decodeCrock(req.denomPubHash)) .put(decodeCrock(req.blindingKey)) @@ -311,32 +513,32 @@ export class CryptoImplementation { } else { throw new Error(); } - } + }, /** * Check if a payment signature is valid. */ - isValidPaymentSignature( - sig: string, - contractHash: string, - merchantPub: string, - ): boolean { + async isValidPaymentSignature( + tci: TalerCryptoInterfaceR, + req: PaymentSignatureValidationRequest, + ): Promise<ValidationResult> { + const { contractHash, sig, merchantPub } = req; const p = buildSigPS(TalerSignaturePurpose.MERCHANT_PAYMENT_OK) .put(decodeCrock(contractHash)) .build(); const sigBytes = decodeCrock(sig); const pubBytes = decodeCrock(merchantPub); - return eddsaVerify(p, sigBytes, pubBytes); - } + return { valid: eddsaVerify(p, sigBytes, pubBytes) }; + }, /** * Check if a wire fee is correctly signed. */ async isValidWireFee( - type: string, - wf: WireFee, - masterPub: string, - ): Promise<boolean> { + tci: TalerCryptoInterfaceR, + req: WireFeeValidationRequest, + ): Promise<ValidationResult> { + const { type, wf, masterPub } = req; const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_FEES) .put(hash(stringToBytes(type + "\0"))) .put(timestampRoundedToBuffer(wf.startStamp)) @@ -347,25 +549,17 @@ 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); - } + return { valid: eddsaVerify(p, sig, pub) }; + }, /** * Check if the signature of a denomination is valid. */ async isValidDenom( - denom: DenominationRecord, - masterPub: string, - ): Promise<boolean> { + tci: TalerCryptoInterfaceR, + req: DenominationValidationRequest, + ): Promise<ValidationResult> { + const { masterPub, denom } = req; const p = buildSigPS(TalerSignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY) .put(decodeCrock(masterPub)) .put(timestampRoundedToBuffer(denom.stampStart)) @@ -382,56 +576,59 @@ export class CryptoImplementation { 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 { + return { valid: res }; + }, + + async isValidWireAccount( + tci: TalerCryptoInterfaceR, + req: WireAccountValidationRequest, + ): Promise<ValidationResult> { + const { sig, masterPub, paytoUri } = req; 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); + return { valid: eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub)) }; + }, + + async isValidContractTermsSignature( + tci: TalerCryptoInterfaceR, + req: ContractTermsValidationRequest, + ): Promise<ValidationResult> { + const cthDec = decodeCrock(req.contractTermsHash); const p = buildSigPS(TalerSignaturePurpose.MERCHANT_CONTRACT) .put(cthDec) .build(); - return eddsaVerify(p, decodeCrock(sig), decodeCrock(merchantPub)); - } + return { + valid: eddsaVerify(p, decodeCrock(req.sig), decodeCrock(req.merchantPub)), + }; + }, /** * Create a new EdDSA key pair. */ - createEddsaKeypair(): { priv: string; pub: string } { + async createEddsaKeypair(tci: TalerCryptoInterfaceR): Promise<EddsaKeypair> { const pair = createEddsaKeyPair(); return { priv: encodeCrock(pair.eddsaPriv), pub: encodeCrock(pair.eddsaPub), }; - } + }, - eddsaGetPublic(key: string): { priv: string; pub: string } { + async eddsaGetPublic( + tci: TalerCryptoInterfaceR, + req: EddsaGetPublicRequest, + ): Promise<EddsaKeypair> { return { - priv: key, - pub: encodeCrock(eddsaGetPublic(decodeCrock(key))), + priv: req.priv, + pub: encodeCrock(eddsaGetPublic(decodeCrock(req.priv))), }; - } + }, - unblindDenominationSignature(req: { - planchet: PlanchetUnblindInfo; - evSig: BlindedDenominationSignature; - }): UnblindedSignature { + async unblindDenominationSignature( + tci: TalerCryptoInterfaceR, + req: UnblindDenominationSignatureRequest, + ): Promise<UnblindedSignature> { if (req.evSig.cipher === DenomKeyType.Rsa) { if (req.planchet.denomPub.cipher !== DenomKeyType.Rsa) { throw new Error( @@ -450,32 +647,45 @@ export class CryptoImplementation { } else { throw Error(`unblinding for cipher ${req.evSig.cipher} not implemented`); } - } + }, /** * Unblind a blindly signed value. */ - rsaUnblind(blindedSig: string, bk: string, pk: string): string { + async rsaUnblind( + tci: TalerCryptoInterfaceR, + req: RsaUnblindRequest, + ): Promise<RsaUnblindResponse> { const denomSig = rsaUnblind( - decodeCrock(blindedSig), - decodeCrock(pk), - decodeCrock(bk), + decodeCrock(req.blindedSig), + decodeCrock(req.pk), + decodeCrock(req.bk), ); - return encodeCrock(denomSig); - } + return { sig: encodeCrock(denomSig) }; + }, /** * Unblind a blindly signed value. */ - rsaVerify(hm: string, sig: string, pk: string): boolean { - return rsaVerify(hash(decodeCrock(hm)), decodeCrock(sig), decodeCrock(pk)); - } + async rsaVerify( + tci: TalerCryptoInterfaceR, + req: RsaVerificationRequest, + ): Promise<ValidationResult> { + return { + valid: rsaVerify( + hash(decodeCrock(req.hm)), + decodeCrock(req.sig), + decodeCrock(req.pk), + ), + }; + }, /** * Generate updated coins (to store in the database) * and deposit permissions for each given coin. */ async signDepositPermission( + tci: TalerCryptoInterfaceR, depositInfo: DepositInfo, ): Promise<CoinDepositPermission> { // FIXME: put extensions here if used @@ -498,7 +708,7 @@ export class CryptoImplementation { } else { throw Error("unsupported exchange protocol version"); } - const coinSigRes = await myEddsaSign(this.primitiveWorker, { + const coinSigRes = await this.eddsaSign(tci, { msg: encodeCrock(d), priv: depositInfo.coinPriv, }); @@ -521,9 +731,10 @@ export class CryptoImplementation { `unsupported denomination cipher (${depositInfo.denomKeyType})`, ); } - } + }, async deriveRefreshSession( + tci: TalerCryptoInterfaceR, req: DeriveRefreshSessionRequest, ): Promise<DerivedRefreshSession> { const { @@ -596,24 +807,14 @@ export class CryptoImplementation { 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; - } + // FIXME: make setupRefreshPlanchet a crypto api fn + 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"); @@ -654,7 +855,7 @@ export class CryptoImplementation { .put(amountToBuffer(meltFee)) .build(); - const confirmSigResp = await myEddsaSign(this.primitiveWorker, { + const confirmSigResp = await tci.eddsaSign(tci, { msg: encodeCrock(confirmData), priv: meltCoinPriv, }); @@ -670,102 +871,42 @@ export class CryptoImplementation { }; 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 hashString( + tci: TalerCryptoInterfaceR, + req: HashStringRequest, + ): Promise<HashStringResult> { + const b = stringToBytes(req.str + "\0"); + return { h: encodeCrock(hash(b)) }; + }, async signCoinLink( - oldCoinPriv: string, - newDenomHash: string, - oldCoinPub: string, - transferPub: string, - coinEv: CoinEnvelope, - ): Promise<string> { - const coinEvHash = hashCoinEv(coinEv, newDenomHash); + tci: TalerCryptoInterfaceR, + req: SignCoinLinkRequest, + ): Promise<EddsaSigningResult> { + const coinEvHash = hashCoinEv(req.coinEv, req.newDenomHash); // FIXME: fill in const hAgeCommitment = new Uint8Array(32); const coinLink = buildSigPS(TalerSignaturePurpose.WALLET_COIN_LINK) - .put(decodeCrock(newDenomHash)) - .put(decodeCrock(transferPub)) + .put(decodeCrock(req.newDenomHash)) + .put(decodeCrock(req.transferPub)) .put(hAgeCommitment) .put(coinEvHash) .build(); - const sig = await myEddsaSign(this.primitiveWorker, { + return tci.eddsaSign(tci, { msg: encodeCrock(coinLink), - priv: oldCoinPriv, + priv: req.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 { + async makeSyncSignature( + tci: TalerCryptoInterfaceR, + req: MakeSyncSignatureRequest, + ): Promise<EddsaSigningResult> { const hNew = decodeCrock(req.newHash); let hOld: Uint8Array; if (req.oldHash) { @@ -778,6 +919,64 @@ export class CryptoImplementation { .put(hNew) .build(); const uploadSig = eddsaSign(sigBlob, decodeCrock(req.accountPriv)); - return encodeCrock(uploadSig); + return { sig: encodeCrock(uploadSig) }; + }, +}; + +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 EddsaSignRequest { + msg: string; + priv: string; } + +export interface EddsaSignResponse { + sig: string; +} + +export const nativeCrypto: TalerCryptoInterface = Object.fromEntries( + Object.keys(nativeCryptoR).map((name) => { + return [ + name, + (req: any) => + nativeCryptoR[name as keyof TalerCryptoInterfaceR](nativeCryptoR, req), + ]; + }), +) as any; diff --git a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts index 3b3396046..deff15071 100644 --- a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts +++ b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts @@ -29,7 +29,6 @@ */ import { AmountJson, - AmountString, CoinEnvelope, DenominationPubKey, ExchangeProtocolVersion, 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/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 { diff --git a/packages/taler-wallet-core/src/dbless.ts b/packages/taler-wallet-core/src/dbless.ts index 854a3ea09..ea5a42323 100644 --- a/packages/taler-wallet-core/src/dbless.ts +++ b/packages/taler-wallet-core/src/dbless.ts @@ -47,10 +47,10 @@ import { AbsoluteTime, UnblindedSignature, } from "@gnu-taler/taler-util"; +import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js"; import { DenominationRecord } from "./db.js"; import { assembleRefreshRevealRequest, - CryptoApi, ExchangeInfo, getBankWithdrawalInfo, HttpRequestLibrary, @@ -149,7 +149,7 @@ export async function topupReserveWithDemobank( export async function withdrawCoin(args: { http: HttpRequestLibrary; - cryptoApi: CryptoApi; + cryptoApi: TalerCryptoInterface; reserveKeyPair: ReserveKeypair; denom: DenominationRecord; exchangeBaseUrl: string; @@ -212,7 +212,7 @@ export function findDenomOrThrow( export async function depositCoin(args: { http: HttpRequestLibrary; - cryptoApi: CryptoApi; + cryptoApi: TalerCryptoInterface; exchangeBaseUrl: string; coin: CoinInfo; amount: AmountString; @@ -263,7 +263,7 @@ export async function depositCoin(args: { export async function refreshCoin(req: { http: HttpRequestLibrary; - cryptoApi: CryptoApi; + cryptoApi: TalerCryptoInterface; oldCoin: CoinInfo; newDenoms: DenominationRecord[]; }): Promise<void> { diff --git a/packages/taler-wallet-core/src/headless/helpers.ts b/packages/taler-wallet-core/src/headless/helpers.ts index 120c4cd46..7bc8235fd 100644 --- a/packages/taler-wallet-core/src/headless/helpers.ts +++ b/packages/taler-wallet-core/src/headless/helpers.ts @@ -25,7 +25,9 @@ import type { IDBFactory } from "@gnu-taler/idb-bridge"; // eslint-disable-next-line no-duplicate-imports import { - BridgeIDBFactory, MemoryBackend, shimIndexedDB + BridgeIDBFactory, + MemoryBackend, + shimIndexedDB, } from "@gnu-taler/idb-bridge"; import { AccessStats } from "@gnu-taler/idb-bridge/src/MemoryBackend"; import { Logger, WalletNotification } from "@gnu-taler/taler-util"; diff --git a/packages/taler-wallet-core/src/index.ts b/packages/taler-wallet-core/src/index.ts index ca701820e..979d631c0 100644 --- a/packages/taler-wallet-core/src/index.ts +++ b/packages/taler-wallet-core/src/index.ts @@ -33,9 +33,11 @@ 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/cryptoWorkerInterface.js"; -export { CryptoWorkerFactory, CryptoApi } from "./crypto/workers/cryptoApi.js"; +export { + CryptoWorkerFactory, + CryptoDispatcher, +} from "./crypto/workers/cryptoDispatcher.js"; export { SynchronousCryptoWorker } from "./crypto/workers/synchronousWorker.js"; export * from "./pending-types.js"; @@ -58,3 +60,8 @@ export * from "./operations/refresh.js"; export * from "./dbless.js"; +export { + nativeCryptoR, + nativeCrypto, + nullCrypto, +} from "./crypto/cryptoImplementation.js"; diff --git a/packages/taler-wallet-core/src/internal-wallet-state.ts b/packages/taler-wallet-core/src/internal-wallet-state.ts index 904398722..5ecf796ed 100644 --- a/packages/taler-wallet-core/src/internal-wallet-state.ts +++ b/packages/taler-wallet-core/src/internal-wallet-state.ts @@ -36,7 +36,8 @@ import { DenominationPubKey, TalerProtocolTimestamp, } from "@gnu-taler/taler-util"; -import { CryptoApi } from "./crypto/workers/cryptoApi.js"; +import { CryptoDispatcher } from "./crypto/workers/cryptoDispatcher.js"; +import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js"; import { ExchangeDetailsRecord, ExchangeRecord, WalletStoresV1 } from "./db.js"; import { PendingOperationsResponse } from "./pending-types.js"; import { AsyncOpMemoMap, AsyncOpMemoSingle } from "./util/asyncMemo.js"; @@ -200,7 +201,7 @@ export interface InternalWalletState { memoProcessRefresh: AsyncOpMemoMap<void>; memoProcessRecoup: AsyncOpMemoMap<void>; memoProcessDeposit: AsyncOpMemoMap<void>; - cryptoApi: CryptoApi; + cryptoApi: TalerCryptoInterface; timerGroup: TimerGroup; stopped: boolean; diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts index 8ddc0c064..5013b9032 100644 --- a/packages/taler-wallet-core/src/operations/backup/index.ts +++ b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -69,7 +69,7 @@ import { rsaBlind, stringToBytes, } from "@gnu-taler/taler-util"; -import { CryptoApi } from "../../crypto/workers/cryptoApi.js"; +import { CryptoDispatcher } from "../../crypto/workers/cryptoDispatcher.js"; import { BackupProviderRecord, BackupProviderState, @@ -99,6 +99,7 @@ import { exportBackup } from "./export.js"; import { BackupCryptoPrecomputedData, importBackup } from "./import.js"; import { getWalletBackupState, provideBackupState } from "./state.js"; import { guardOperationException } from "../common.js"; +import { TalerCryptoInterface } from "../../crypto/cryptoImplementation.js"; const logger = new Logger("operations/backup.ts"); @@ -154,7 +155,7 @@ export async function encryptBackup( * FIXME: Move computations into crypto worker. */ async function computeBackupCryptoData( - cryptoApi: CryptoApi, + cryptoApi: TalerCryptoInterface, backupContent: WalletBackupContentV1, ): Promise<BackupCryptoPrecomputedData> { const cryptoData: BackupCryptoPrecomputedData = { @@ -193,18 +194,18 @@ async function computeBackupCryptoData( } } for (const prop of backupContent.proposals) { - const contractTermsHash = await cryptoApi.hashString( - canonicalJson(prop.contract_terms_raw), - ); + const { h: contractTermsHash } = await cryptoApi.hashString({ + str: canonicalJson(prop.contract_terms_raw), + }); const noncePub = encodeCrock(eddsaGetPublic(decodeCrock(prop.nonce_priv))); cryptoData.proposalNoncePrivToPub[prop.nonce_priv] = noncePub; cryptoData.proposalIdToContractTermsHash[prop.proposal_id] = contractTermsHash; } for (const purch of backupContent.purchases) { - const contractTermsHash = await cryptoApi.hashString( - canonicalJson(purch.contract_terms_raw), - ); + const { h: contractTermsHash } = await cryptoApi.hashString({ + str: canonicalJson(purch.contract_terms_raw), + }); const noncePub = encodeCrock(eddsaGetPublic(decodeCrock(purch.nonce_priv))); cryptoData.proposalNoncePrivToPub[purch.nonce_priv] = noncePub; cryptoData.proposalIdToContractTermsHash[purch.proposal_id] = @@ -286,13 +287,13 @@ async function runBackupCycleForProvider( logger.trace(`trying to upload backup to ${provider.baseUrl}`); logger.trace(`old hash ${oldHash}, new hash ${newHash}`); - const syncSig = await ws.cryptoApi.makeSyncSignature({ + const syncSigResp = await ws.cryptoApi.makeSyncSignature({ newHash: encodeCrock(currentBackupHash), oldHash: provider.lastBackupHash, accountPriv: encodeCrock(accountKeyPair.eddsaPriv), }); - logger.trace(`sync signature is ${syncSig}`); + logger.trace(`sync signature is ${syncSigResp}`); const accountBackupUrl = new URL( `/backups/${encodeCrock(accountKeyPair.eddsaPub)}`, @@ -304,7 +305,7 @@ async function runBackupCycleForProvider( body: encBackup, headers: { "content-type": "application/octet-stream", - "sync-signature": syncSig, + "sync-signature": syncSigResp.sig, "if-none-match": newHash, ...(provider.lastBackupHash ? { diff --git a/packages/taler-wallet-core/src/operations/backup/state.ts b/packages/taler-wallet-core/src/operations/backup/state.ts index f25cc170a..293f56137 100644 --- a/packages/taler-wallet-core/src/operations/backup/state.ts +++ b/packages/taler-wallet-core/src/operations/backup/state.ts @@ -41,7 +41,7 @@ export async function provideBackupState( } // We need to generate the key outside of the transaction // due to how IndexedDB works. - const k = await ws.cryptoApi.createEddsaKeypair(); + const k = await ws.cryptoApi.createEddsaKeypair({}); const d = getRandomBytes(5); // FIXME: device ID should be configured when wallet is initialized // and be based on hostname diff --git a/packages/taler-wallet-core/src/operations/common.ts b/packages/taler-wallet-core/src/operations/common.ts index 5525b4deb..5261b114d 100644 --- a/packages/taler-wallet-core/src/operations/common.ts +++ b/packages/taler-wallet-core/src/operations/common.ts @@ -15,7 +15,7 @@ */ import { TalerErrorDetail, TalerErrorCode } from "@gnu-taler/taler-util"; -import { CryptoApiStoppedError } from "../crypto/workers/cryptoApi.js"; +import { CryptoApiStoppedError } from "../crypto/workers/cryptoDispatcher.js"; import { TalerError, getErrorDetailFromException } from "../errors.js"; /** diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index ad3f614f3..2e14afdf1 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -254,14 +254,14 @@ export async function trackDepositGroup( `deposits/${wireHash}/${depositGroup.merchantPub}/${depositGroup.contractTermsHash}/${dp.coin_pub}`, dp.exchange_url, ); - const sig = await ws.cryptoApi.signTrackTransaction({ + const sigResp = await ws.cryptoApi.signTrackTransaction({ coinPub: dp.coin_pub, contractTermsHash: depositGroup.contractTermsHash, merchantPriv: depositGroup.merchantPriv, merchantPub: depositGroup.merchantPub, wireHash, }); - url.searchParams.set("merchant_sig", sig); + url.searchParams.set("merchant_sig", sigResp.sig); const httpResp = await ws.http.get(url.href); const body = await httpResp.json(); responses.push({ @@ -391,8 +391,8 @@ export async function createDepositGroup( const now = AbsoluteTime.now(); const nowRounded = AbsoluteTime.toTimestamp(now); - const noncePair = await ws.cryptoApi.createEddsaKeypair(); - const merchantPair = await ws.cryptoApi.createEddsaKeypair(); + const noncePair = await ws.cryptoApi.createEddsaKeypair({}); + const merchantPair = await ws.cryptoApi.createEddsaKeypair({}); const wireSalt = encodeCrock(getRandomBytes(16)); const wireHash = hashWire(req.depositPaytoUri, wireSalt); const contractTerms: ContractTerms = { @@ -421,9 +421,9 @@ export async function createDepositGroup( refund_deadline: TalerProtocolTimestamp.zero(), }; - const contractTermsHash = await ws.cryptoApi.hashString( - canonicalJson(contractTerms), - ); + const { h: contractTermsHash } = await ws.cryptoApi.hashString({ + str: canonicalJson(contractTerms), + }); const contractData = extractContractData( contractTerms, diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 94159369b..51b5c7806 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -46,7 +46,7 @@ import { TalerProtocolDuration, } from "@gnu-taler/taler-util"; import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util"; -import { CryptoApi } from "../crypto/workers/cryptoApi.js"; +import { CryptoDispatcher } from "../crypto/workers/cryptoDispatcher.js"; import { DenominationRecord, DenominationVerificationStatus, @@ -243,12 +243,13 @@ async function validateWireInfo( if (ws.insecureTrustExchange) { isValid = true; } else { - isValid = await ws.cryptoApi.isValidWireAccount( + const { valid: v } = await ws.cryptoApi.isValidWireAccount({ + masterPub: masterPublicKey, + paytoUri: a.payto_uri, + sig: a.master_sig, versionCurrent, - a.payto_uri, - a.master_sig, - masterPublicKey, - ); + }); + isValid = v; } if (!isValid) { throw Error("exchange acct signature invalid"); @@ -272,11 +273,12 @@ async function validateWireInfo( if (ws.insecureTrustExchange) { isValid = true; } else { - isValid = await ws.cryptoApi.isValidWireFee( - wireMethod, - fee, - masterPublicKey, - ); + const { valid: v } = await ws.cryptoApi.isValidWireFee({ + masterPub: masterPublicKey, + type: wireMethod, + wf: fee, + }); + isValid = v; } if (!isValid) { throw Error("exchange wire fee signature invalid"); diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index 1c1a0f506..97f38bae6 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -55,7 +55,10 @@ import { TransactionType, URL, } from "@gnu-taler/taler-util"; -import { EXCHANGE_COINS_LOCK, InternalWalletState } from "../internal-wallet-state.js"; +import { + EXCHANGE_COINS_LOCK, + InternalWalletState, +} from "../internal-wallet-state.js"; import { AbortStatus, AllowedAuditorInfo, @@ -100,6 +103,7 @@ import { import { getExchangeDetails } from "./exchanges.js"; import { createRefreshGroup, getTotalRefreshCost } from "./refresh.js"; import { guardOperationException } from "./common.js"; +import { EddsaKeypair } from "../crypto/cryptoImplementation.js"; /** * Logger. @@ -795,11 +799,11 @@ async function processDownloadProposalImpl( ); } - const sigValid = await ws.cryptoApi.isValidContractTermsSignature( + const sigValid = await ws.cryptoApi.isValidContractTermsSignature({ contractTermsHash, - proposalResp.sig, - parsedContractTerms.merchant_pub, - ); + merchantPub: parsedContractTerms.merchant_pub, + sig: proposalResp.sig, + }); if (!sigValid) { const err = makeErrorDetail( @@ -921,9 +925,14 @@ async function startDownloadProposal( return oldProposal.proposalId; } - const { priv, pub } = await (noncePriv - ? ws.cryptoApi.eddsaGetPublic(noncePriv) - : ws.cryptoApi.createEddsaKeypair()); + let noncePair: EddsaKeypair; + if (noncePriv) { + noncePair = await ws.cryptoApi.eddsaGetPublic({ priv: noncePriv }); + } else { + noncePair = await ws.cryptoApi.createEddsaKeypair({}); + } + + const { priv, pub } = noncePair; const proposalId = encodeCrock(getRandomBytes(32)); const proposalRecord: ProposalRecord = { @@ -1673,11 +1682,11 @@ async function processPurchasePayImpl( logger.trace("got success from pay URL", merchantResp); const merchantPub = purchase.download.contractData.merchantPub; - const valid: boolean = await ws.cryptoApi.isValidPaymentSignature( - merchantResp.sig, - purchase.download.contractData.contractTermsHash, + const { valid } = await ws.cryptoApi.isValidPaymentSignature({ + contractHash: purchase.download.contractData.contractTermsHash, merchantPub, - ); + sig: merchantResp.sig, + }); if (!valid) { logger.error("merchant payment signature invalid"); diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index c422674a9..a77738262 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -76,9 +76,9 @@ import { RefreshNewDenomInfo, } from "../crypto/cryptoTypes.js"; import { GetReadWriteAccess } from "../util/query.js"; -import { CryptoApi } from "../index.browser.js"; import { guardOperationException } from "./common.js"; -import { CryptoApiStoppedError } from "../crypto/workers/cryptoApi.js"; +import { CryptoApiStoppedError } from "../crypto/workers/cryptoDispatcher.js"; +import { TalerCryptoInterface } from "../crypto/cryptoImplementation.js"; const logger = new Logger("refresh.ts"); @@ -461,7 +461,7 @@ async function refreshMelt( } export async function assembleRefreshRevealRequest(args: { - cryptoApi: CryptoApi; + cryptoApi: TalerCryptoInterface; derived: DerivedRefreshSession; norevealIndex: number; oldCoinPub: CoinPublicKeyString; @@ -494,14 +494,14 @@ export async function assembleRefreshRevealRequest(args: { const dsel = newDenoms[i]; for (let j = 0; j < dsel.count; j++) { const newCoinIndex = linkSigs.length; - const linkSig = await cryptoApi.signCoinLink( - oldCoinPriv, - dsel.denomPubHash, - oldCoinPub, - derived.transferPubs[norevealIndex], - planchets[newCoinIndex].coinEv, - ); - linkSigs.push(linkSig); + const linkSig = await cryptoApi.signCoinLink({ + coinEv: planchets[newCoinIndex].coinEv, + newDenomHash: dsel.denomPubHash, + oldCoinPriv: oldCoinPriv, + oldCoinPub: oldCoinPub, + transferPub: derived.transferPubs[norevealIndex], + }); + linkSigs.push(linkSig.sig); newDenomsFlat.push(dsel.denomPubHash); } } diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts index dd0fa5423..9cbd63c45 100644 --- a/packages/taler-wallet-core/src/operations/reserves.ts +++ b/packages/taler-wallet-core/src/operations/reserves.ts @@ -170,7 +170,7 @@ export async function createReserve( ws: InternalWalletState, req: CreateReserveRequest, ): Promise<CreateReserveResponse> { - const keypair = await ws.cryptoApi.createEddsaKeypair(); + const keypair = await ws.cryptoApi.createEddsaKeypair({}); const now = AbsoluteTime.toTimestamp(AbsoluteTime.now()); const canonExchange = canonicalizeBaseUrl(req.exchange); diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index 7bd81b825..cd29f8a86 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -336,17 +336,17 @@ async function processTipImpl( throw Error("unsupported cipher"); } - const denomSigRsa = await ws.cryptoApi.rsaUnblind( - blindedSig.blinded_rsa_signature, - planchet.blindingKey, - denom.denomPub.rsa_public_key, - ); + const denomSigRsa = await ws.cryptoApi.rsaUnblind({ + bk: planchet.blindingKey, + blindedSig: blindedSig.blinded_rsa_signature, + pk: denom.denomPub.rsa_public_key, + }); - const isValid = await ws.cryptoApi.rsaVerify( - planchet.coinPub, - denomSigRsa, - denom.denomPub.rsa_public_key, - ); + const isValid = await ws.cryptoApi.rsaVerify({ + hm: planchet.coinPub, + pk: denom.denomPub.rsa_public_key, + sig: denomSigRsa.sig, + }); if (!isValid) { await ws.db @@ -377,7 +377,7 @@ async function processTipImpl( }, currentAmount: denom.value, denomPubHash: denom.denomPubHash, - denomSig: { cipher: DenomKeyType.Rsa, rsa_signature: denomSigRsa }, + denomSig: { cipher: DenomKeyType.Rsa, rsa_signature: denomSigRsa.sig }, exchangeBaseUrl: tipRecord.exchangeBaseUrl, status: CoinStatus.Fresh, suspended: false, diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index b7feae06a..7685ede73 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -603,17 +603,17 @@ async function processPlanchetVerifyAndStoreCoin( throw Error("unsupported cipher"); } - const denomSigRsa = await ws.cryptoApi.rsaUnblind( - evSig.blinded_rsa_signature, - planchet.blindingKey, - planchetDenomPub.rsa_public_key, - ); + const denomSigRsa = await ws.cryptoApi.rsaUnblind({ + bk: planchet.blindingKey, + blindedSig: evSig.blinded_rsa_signature, + pk: planchetDenomPub.rsa_public_key, + }); - const isValid = await ws.cryptoApi.rsaVerify( - planchet.coinPub, - denomSigRsa, - planchetDenomPub.rsa_public_key, - ); + const isValid = await ws.cryptoApi.rsaVerify({ + hm: planchet.coinPub, + pk: planchetDenomPub.rsa_public_key, + sig: denomSigRsa.sig, + }); if (!isValid) { await ws.db @@ -640,7 +640,7 @@ async function processPlanchetVerifyAndStoreCoin( if (planchetDenomPub.cipher === DenomKeyType.Rsa) { denomSig = { cipher: planchetDenomPub.cipher, - rsa_signature: denomSigRsa, + rsa_signature: denomSigRsa.sig, }; } else { throw Error("unsupported cipher"); @@ -759,10 +759,11 @@ export async function updateWithdrawalDenoms( if (ws.insecureTrustExchange) { valid = true; } else { - valid = await ws.cryptoApi.isValidDenom( + const res = await ws.cryptoApi.isValidDenom({ denom, - exchangeDetails.masterPublicKey, - ); + masterPub: exchangeDetails.masterPublicKey, + }); + valid = res.valid; } logger.trace(`Done validating ${denom.denomPubHash}`); if (!valid) { diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 5e71cfbc7..d1e9aa646 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -90,7 +90,10 @@ import { RecoupOperations, ReserveOperations, } from "./internal-wallet-state.js"; -import { CryptoApi, CryptoWorkerFactory } from "./crypto/workers/cryptoApi.js"; +import { + CryptoDispatcher, + CryptoWorkerFactory, +} from "./crypto/workers/cryptoDispatcher.js"; import { AuditorTrustRecord, CoinSourceType, @@ -99,10 +102,7 @@ import { ReserveRecordStatus, WalletStoresV1, } from "./db.js"; -import { - getErrorDetailFromException, - TalerError, -} from "./errors.js"; +import { getErrorDetailFromException, TalerError } from "./errors.js"; import { exportBackup } from "./operations/backup/export.js"; import { addBackupProvider, @@ -193,6 +193,10 @@ import { import { DbAccess, GetReadWriteAccess } from "./util/query.js"; import { TimerGroup } from "./util/timer.js"; import { WalletCoreApiClient } from "./wallet-api-types.js"; +import { + TalerCryptoInterface, + TalerCryptoInterfaceR, +} from "./crypto/cryptoImplementation.js"; const builtinAuditors: AuditorTrustRecord[] = [ { @@ -1158,7 +1162,8 @@ class InternalWalletStateImpl implements InternalWalletState { memoProcessRefresh: AsyncOpMemoMap<void> = new AsyncOpMemoMap(); memoProcessRecoup: AsyncOpMemoMap<void> = new AsyncOpMemoMap(); memoProcessDeposit: AsyncOpMemoMap<void> = new AsyncOpMemoMap(); - cryptoApi: CryptoApi; + cryptoApi: TalerCryptoInterface; + cryptoDispatcher: CryptoDispatcher; merchantInfoCache: Record<string, MerchantInfo> = {}; @@ -1213,7 +1218,8 @@ class InternalWalletStateImpl implements InternalWalletState { public http: HttpRequestLibrary, cryptoWorkerFactory: CryptoWorkerFactory, ) { - this.cryptoApi = new CryptoApi(cryptoWorkerFactory); + this.cryptoDispatcher = new CryptoDispatcher(cryptoWorkerFactory); + this.cryptoApi = this.cryptoDispatcher.cryptoApi; } async getDenomInfo( @@ -1258,7 +1264,7 @@ class InternalWalletStateImpl implements InternalWalletState { stop(): void { this.stopped = true; this.timerGroup.stopCurrentAndFutureTimers(); - this.cryptoApi.stop(); + this.cryptoDispatcher.stop(); } async runUntilDone( diff --git a/packages/taler-wallet-webextension/src/browserWorkerEntry.ts b/packages/taler-wallet-webextension/src/browserWorkerEntry.ts index 7829e6d65..4494b9af8 100644 --- a/packages/taler-wallet-webextension/src/browserWorkerEntry.ts +++ b/packages/taler-wallet-webextension/src/browserWorkerEntry.ts @@ -23,18 +23,18 @@ */ import { Logger } from "@gnu-taler/taler-util"; -import { CryptoImplementation } from "@gnu-taler/taler-wallet-core"; +import { nativeCrypto } from "@gnu-taler/taler-wallet-core"; const logger = new Logger("browserWorkerEntry.ts"); -const worker: Worker = (self as any) as Worker; +const worker: Worker = self as any as Worker; async function handleRequest( operation: string, id: number, args: string[], ): Promise<void> { - const impl = new CryptoImplementation(); + const impl = nativeCrypto; if (!(operation in impl)) { console.error(`crypto operation '${operation}' not found`); diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index 4b0383f88..dd53a4feb 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -97,10 +97,10 @@ export interface UpgradeResponse { async function callBackend(operation: string, payload: any): Promise<any> { let response: CoreApiResponse; try { - response = await platform.setMessageToWalletBackground(operation, payload) + response = await platform.setMessageToWalletBackground(operation, payload); } catch (e) { console.log("Error calling backend"); - throw new Error(`Error contacting backend: ${e}`) + throw new Error(`Error contacting backend: ${e}`); } console.log("got response", response); if (response.type === "error") { @@ -413,12 +413,15 @@ export function importDB(dump: any): Promise<void> { return callBackend("importDb", { dump }); } -export function onUpdateNotification(messageTypes: Array<NotificationType>, doCallback: () => void): () => void { +export function onUpdateNotification( + messageTypes: Array<NotificationType>, + doCallback: () => void, +): () => void { const listener = (message: MessageFromBackend): void => { const shouldNotify = messageTypes.includes(message.type); if (shouldNotify) { doCallback(); } }; - return platform.listenToWalletNotifications(listener) + return platform.listenToWalletNotifications(listener); } |