wallet: simplify crypto workers

This commit is contained in:
Florian Dold 2022-03-23 21:24:23 +01:00
parent e21c1b3192
commit d881f4fd25
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
27 changed files with 789 additions and 719 deletions

View File

@ -27,7 +27,7 @@ import {
import { import {
checkReserve, checkReserve,
createFakebankReserve, createFakebankReserve,
CryptoApi, CryptoDispatcher,
depositCoin, depositCoin,
downloadExchangeInfo, downloadExchangeInfo,
findDenomOrThrow, findDenomOrThrow,
@ -50,7 +50,8 @@ export async function runBench2(configJson: any): Promise<void> {
// Validate the configuration file for this benchmark. // Validate the configuration file for this benchmark.
const benchConf = codecForBench2Config().decode(configJson); const benchConf = codecForBench2Config().decode(configJson);
const curr = benchConf.currency; const curr = benchConf.currency;
const cryptoApi = new CryptoApi(new SynchronousCryptoWorkerFactory()); const cryptoDisp = new CryptoDispatcher(new SynchronousCryptoWorkerFactory());
const cryptoApi = cryptoDisp.cryptoApi;
const http = new NodeHttpLib(); const http = new NodeHttpLib();
http.setThrottling(false); http.setThrottling(false);

View File

@ -50,18 +50,21 @@ import {
NodeHttpLib, NodeHttpLib,
getDefaultNodeWallet, getDefaultNodeWallet,
NodeThreadCryptoWorkerFactory, NodeThreadCryptoWorkerFactory,
CryptoApi,
walletCoreDebugFlags, walletCoreDebugFlags,
WalletApiOperation, WalletApiOperation,
WalletCoreApiClient, WalletCoreApiClient,
Wallet, Wallet,
getErrorDetailFromException, getErrorDetailFromException,
CryptoDispatcher,
SynchronousCryptoWorkerFactory,
nativeCrypto,
} from "@gnu-taler/taler-wallet-core"; } from "@gnu-taler/taler-wallet-core";
import { lintExchangeDeployment } from "./lint.js"; import { lintExchangeDeployment } from "./lint.js";
import { runBench1 } from "./bench1.js"; import { runBench1 } from "./bench1.js";
import { runEnv1 } from "./env1.js"; import { runEnv1 } from "./env1.js";
import { GlobalTestState, runTestWithState } from "./harness/harness.js"; import { GlobalTestState, runTestWithState } from "./harness/harness.js";
import { runBench2 } from "./bench2.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 // This module also serves as the entry point for the crypto
// thread worker, and thus must expose these two handlers. // thread worker, and thus must expose these two handlers.
@ -1121,14 +1124,30 @@ testCli.subcommand("tvgcheck", "tvgcheck").action(async (args) => {
console.log("check passed!"); console.log("check passed!");
}); });
testCli.subcommand("cryptoworker", "cryptoworker").action(async (args) => { testCli
const workerFactory = new NodeThreadCryptoWorkerFactory(); .subcommand("cryptoworker", "cryptoworker")
const cryptoApi = new CryptoApi(workerFactory); .maybeOption("impl", ["--impl"], clk.STRING)
const input = "foo"; .action(async (args) => {
console.log(`testing crypto worker by hashing string '${input}'`); let cryptoApi: TalerCryptoInterface;
const res = await cryptoApi.hashString(input); if (!args.cryptoworker.impl || args.cryptoworker.impl === "node") {
console.log(res); 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() { export function main() {
if (process.env["TALER_WALLET_DEBUG_DENOMSEL_ALLOW_LATE"]) { if (process.env["TALER_WALLET_DEBUG_DENOMSEL_ALLOW_LATE"]) {

View File

@ -20,7 +20,7 @@
import { j2s } from "@gnu-taler/taler-util"; import { j2s } from "@gnu-taler/taler-util";
import { import {
checkReserve, checkReserve,
CryptoApi, CryptoDispatcher,
depositCoin, depositCoin,
downloadExchangeInfo, downloadExchangeInfo,
findDenomOrThrow, findDenomOrThrow,
@ -44,7 +44,8 @@ export async function runWalletDblessTest(t: GlobalTestState) {
const { bank, exchange } = await createSimpleTestkudosEnvironment(t); const { bank, exchange } = await createSimpleTestkudosEnvironment(t);
const http = new NodeHttpLib(); const http = new NodeHttpLib();
const cryptoApi = new CryptoApi(new SynchronousCryptoWorkerFactory()); const cryptiDisp = new CryptoDispatcher(new SynchronousCryptoWorkerFactory());
const cryptoApi = cryptiDisp.cryptoApi;
try { try {
// Withdraw digital cash into the wallet. // Withdraw digital cash into the wallet.

View File

@ -44,7 +44,6 @@ import {
ExchangeProtocolVersion, ExchangeProtocolVersion,
FreshCoin, FreshCoin,
hash, hash,
HashCodeString,
hashCoinEv, hashCoinEv,
hashCoinEvInner, hashCoinEvInner,
hashDenomPub, hashDenomPub,
@ -67,15 +66,13 @@ import {
setupWithdrawPlanchet, setupWithdrawPlanchet,
stringToBytes, stringToBytes,
TalerSignaturePurpose, TalerSignaturePurpose,
AbsoluteTime,
BlindedDenominationSignature, BlindedDenominationSignature,
UnblindedSignature, UnblindedSignature,
PlanchetUnblindInfo, PlanchetUnblindInfo,
TalerProtocolTimestamp, TalerProtocolTimestamp,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import bigint from "big-integer"; import bigint from "big-integer";
import { DenominationRecord, WireFee } from "../../db.js"; import { DenominationRecord, WireFee } from "../db.js";
import * as timer from "../../util/timer.js";
import { import {
CreateRecoupRefreshReqRequest, CreateRecoupRefreshReqRequest,
CreateRecoupReqRequest, CreateRecoupReqRequest,
@ -84,90 +81,288 @@ import {
DeriveRefreshSessionRequest, DeriveRefreshSessionRequest,
DeriveTipRequest, DeriveTipRequest,
SignTrackTransactionRequest, 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);
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) {}
/**
* 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 * Create a pre-coin of the given denomination to be withdrawn from then given
* reserve. * reserve.
*/ */
createPlanchet(req: PlanchetCreationRequest): Promise<WithdrawalPlanchet>;
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>;
}
/**
* 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 RsaVerificationRequest {
hm: string;
sig: string;
pk: string;
}
export interface EddsaSigningResult {
sig: string;
}
export interface ValidationResult {
valid: boolean;
}
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))),
};
},
async createPlanchet( async createPlanchet(
tci: TalerCryptoInterfaceR,
req: PlanchetCreationRequest, req: PlanchetCreationRequest,
): Promise<WithdrawalPlanchet> { ): Promise<WithdrawalPlanchet> {
const denomPub = req.denomPub; const denomPub = req.denomPub;
@ -195,7 +390,7 @@ export class CryptoImplementation {
.put(evHash) .put(evHash)
.build(); .build();
const sigResult = await myEddsaSign(this.primitiveWorker, { const sigResult = await tci.eddsaSign(tci, {
msg: encodeCrock(withdrawRequest), msg: encodeCrock(withdrawRequest),
priv: req.reservePriv, priv: req.reservePriv,
}); });
@ -216,12 +411,12 @@ export class CryptoImplementation {
} else { } else {
throw Error("unsupported cipher, unable to create planchet"); throw Error("unsupported cipher, unable to create planchet");
} }
} },
/** async createTipPlanchet(
* Create a planchet used for tipping, including the private keys. tci: TalerCryptoInterfaceR,
*/ req: DeriveTipRequest,
createTipPlanchet(req: DeriveTipRequest): DerivedTipPlanchet { ): Promise<DerivedTipPlanchet> {
if (req.denomPub.cipher !== DenomKeyType.Rsa) { if (req.denomPub.cipher !== DenomKeyType.Rsa) {
throw Error(`unsupported cipher (${req.denomPub.cipher})`); throw Error(`unsupported cipher (${req.denomPub.cipher})`);
} }
@ -243,22 +438,28 @@ export class CryptoImplementation {
coinPub: encodeCrock(fc.coinPub), coinPub: encodeCrock(fc.coinPub),
}; };
return tipPlanchet; return tipPlanchet;
} },
signTrackTransaction(req: SignTrackTransactionRequest): string { async signTrackTransaction(
tci: TalerCryptoInterfaceR,
req: SignTrackTransactionRequest,
): Promise<EddsaSigningResult> {
const p = buildSigPS(TalerSignaturePurpose.MERCHANT_TRACK_TRANSACTION) const p = buildSigPS(TalerSignaturePurpose.MERCHANT_TRACK_TRANSACTION)
.put(decodeCrock(req.contractTermsHash)) .put(decodeCrock(req.contractTermsHash))
.put(decodeCrock(req.wireHash)) .put(decodeCrock(req.wireHash))
.put(decodeCrock(req.merchantPub)) .put(decodeCrock(req.merchantPub))
.put(decodeCrock(req.coinPub)) .put(decodeCrock(req.coinPub))
.build(); .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. * 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) const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP)
.put(decodeCrock(req.denomPubHash)) .put(decodeCrock(req.denomPubHash))
.put(decodeCrock(req.blindingKey)) .put(decodeCrock(req.blindingKey))
@ -281,14 +482,15 @@ export class CryptoImplementation {
} else { } else {
throw new Error(); throw new Error();
} }
} },
/** /**
* Create and sign a message to recoup a coin. * Create and sign a message to recoup a coin.
*/ */
createRecoupRefreshRequest( async createRecoupRefreshRequest(
tci: TalerCryptoInterfaceR,
req: CreateRecoupRefreshReqRequest, req: CreateRecoupRefreshReqRequest,
): RecoupRefreshRequest { ): Promise<RecoupRefreshRequest> {
const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP_REFRESH) const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP_REFRESH)
.put(decodeCrock(req.denomPubHash)) .put(decodeCrock(req.denomPubHash))
.put(decodeCrock(req.blindingKey)) .put(decodeCrock(req.blindingKey))
@ -311,32 +513,32 @@ export class CryptoImplementation {
} else { } else {
throw new Error(); throw new Error();
} }
} },
/** /**
* Check if a payment signature is valid. * Check if a payment signature is valid.
*/ */
isValidPaymentSignature( async isValidPaymentSignature(
sig: string, tci: TalerCryptoInterfaceR,
contractHash: string, req: PaymentSignatureValidationRequest,
merchantPub: string, ): Promise<ValidationResult> {
): boolean { const { contractHash, sig, merchantPub } = req;
const p = buildSigPS(TalerSignaturePurpose.MERCHANT_PAYMENT_OK) const p = buildSigPS(TalerSignaturePurpose.MERCHANT_PAYMENT_OK)
.put(decodeCrock(contractHash)) .put(decodeCrock(contractHash))
.build(); .build();
const sigBytes = decodeCrock(sig); const sigBytes = decodeCrock(sig);
const pubBytes = decodeCrock(merchantPub); const pubBytes = decodeCrock(merchantPub);
return eddsaVerify(p, sigBytes, pubBytes); return { valid: eddsaVerify(p, sigBytes, pubBytes) };
} },
/** /**
* Check if a wire fee is correctly signed. * Check if a wire fee is correctly signed.
*/ */
async isValidWireFee( async isValidWireFee(
type: string, tci: TalerCryptoInterfaceR,
wf: WireFee, req: WireFeeValidationRequest,
masterPub: string, ): Promise<ValidationResult> {
): Promise<boolean> { const { type, wf, masterPub } = req;
const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_FEES) const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_FEES)
.put(hash(stringToBytes(type + "\0"))) .put(hash(stringToBytes(type + "\0")))
.put(timestampRoundedToBuffer(wf.startStamp)) .put(timestampRoundedToBuffer(wf.startStamp))
@ -347,25 +549,17 @@ export class CryptoImplementation {
.build(); .build();
const sig = decodeCrock(wf.sig); const sig = decodeCrock(wf.sig);
const pub = decodeCrock(masterPub); const pub = decodeCrock(masterPub);
if (this.primitiveWorker) { return { valid: eddsaVerify(p, sig, pub) };
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. * Check if the signature of a denomination is valid.
*/ */
async isValidDenom( async isValidDenom(
denom: DenominationRecord, tci: TalerCryptoInterfaceR,
masterPub: string, req: DenominationValidationRequest,
): Promise<boolean> { ): Promise<ValidationResult> {
const { masterPub, denom } = req;
const p = buildSigPS(TalerSignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY) const p = buildSigPS(TalerSignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY)
.put(decodeCrock(masterPub)) .put(decodeCrock(masterPub))
.put(timestampRoundedToBuffer(denom.stampStart)) .put(timestampRoundedToBuffer(denom.stampStart))
@ -382,56 +576,59 @@ export class CryptoImplementation {
const sig = decodeCrock(denom.masterSig); const sig = decodeCrock(denom.masterSig);
const pub = decodeCrock(masterPub); const pub = decodeCrock(masterPub);
const res = eddsaVerify(p, sig, pub); const res = eddsaVerify(p, sig, pub);
return res; return { valid: res };
} },
isValidWireAccount( async isValidWireAccount(
versionCurrent: ExchangeProtocolVersion, tci: TalerCryptoInterfaceR,
paytoUri: string, req: WireAccountValidationRequest,
sig: string, ): Promise<ValidationResult> {
masterPub: string, const { sig, masterPub, paytoUri } = req;
): boolean {
const paytoHash = hashTruncate32(stringToBytes(paytoUri + "\0")); const paytoHash = hashTruncate32(stringToBytes(paytoUri + "\0"));
const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_DETAILS) const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_DETAILS)
.put(paytoHash) .put(paytoHash)
.build(); .build();
return eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub)); return { valid: eddsaVerify(p, decodeCrock(sig), decodeCrock(masterPub)) };
} },
isValidContractTermsSignature( async isValidContractTermsSignature(
contractTermsHash: string, tci: TalerCryptoInterfaceR,
sig: string, req: ContractTermsValidationRequest,
merchantPub: string, ): Promise<ValidationResult> {
): boolean { const cthDec = decodeCrock(req.contractTermsHash);
const cthDec = decodeCrock(contractTermsHash);
const p = buildSigPS(TalerSignaturePurpose.MERCHANT_CONTRACT) const p = buildSigPS(TalerSignaturePurpose.MERCHANT_CONTRACT)
.put(cthDec) .put(cthDec)
.build(); .build();
return eddsaVerify(p, decodeCrock(sig), decodeCrock(merchantPub)); return {
} valid: eddsaVerify(p, decodeCrock(req.sig), decodeCrock(req.merchantPub)),
};
},
/** /**
* Create a new EdDSA key pair. * Create a new EdDSA key pair.
*/ */
createEddsaKeypair(): { priv: string; pub: string } { async createEddsaKeypair(tci: TalerCryptoInterfaceR): Promise<EddsaKeypair> {
const pair = createEddsaKeyPair(); const pair = createEddsaKeyPair();
return { return {
priv: encodeCrock(pair.eddsaPriv), priv: encodeCrock(pair.eddsaPriv),
pub: encodeCrock(pair.eddsaPub), pub: encodeCrock(pair.eddsaPub),
}; };
} },
eddsaGetPublic(key: string): { priv: string; pub: string } { async eddsaGetPublic(
tci: TalerCryptoInterfaceR,
req: EddsaGetPublicRequest,
): Promise<EddsaKeypair> {
return { return {
priv: key, priv: req.priv,
pub: encodeCrock(eddsaGetPublic(decodeCrock(key))), pub: encodeCrock(eddsaGetPublic(decodeCrock(req.priv))),
}; };
} },
unblindDenominationSignature(req: { async unblindDenominationSignature(
planchet: PlanchetUnblindInfo; tci: TalerCryptoInterfaceR,
evSig: BlindedDenominationSignature; req: UnblindDenominationSignatureRequest,
}): UnblindedSignature { ): Promise<UnblindedSignature> {
if (req.evSig.cipher === DenomKeyType.Rsa) { if (req.evSig.cipher === DenomKeyType.Rsa) {
if (req.planchet.denomPub.cipher !== DenomKeyType.Rsa) { if (req.planchet.denomPub.cipher !== DenomKeyType.Rsa) {
throw new Error( throw new Error(
@ -450,32 +647,45 @@ export class CryptoImplementation {
} else { } else {
throw Error(`unblinding for cipher ${req.evSig.cipher} not implemented`); throw Error(`unblinding for cipher ${req.evSig.cipher} not implemented`);
} }
} },
/** /**
* Unblind a blindly signed value. * Unblind a blindly signed value.
*/ */
rsaUnblind(blindedSig: string, bk: string, pk: string): string { async rsaUnblind(
tci: TalerCryptoInterfaceR,
req: RsaUnblindRequest,
): Promise<RsaUnblindResponse> {
const denomSig = rsaUnblind( const denomSig = rsaUnblind(
decodeCrock(blindedSig), decodeCrock(req.blindedSig),
decodeCrock(pk), decodeCrock(req.pk),
decodeCrock(bk), decodeCrock(req.bk),
); );
return encodeCrock(denomSig); return { sig: encodeCrock(denomSig) };
} },
/** /**
* Unblind a blindly signed value. * Unblind a blindly signed value.
*/ */
rsaVerify(hm: string, sig: string, pk: string): boolean { async rsaVerify(
return rsaVerify(hash(decodeCrock(hm)), decodeCrock(sig), decodeCrock(pk)); 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) * Generate updated coins (to store in the database)
* and deposit permissions for each given coin. * and deposit permissions for each given coin.
*/ */
async signDepositPermission( async signDepositPermission(
tci: TalerCryptoInterfaceR,
depositInfo: DepositInfo, depositInfo: DepositInfo,
): Promise<CoinDepositPermission> { ): Promise<CoinDepositPermission> {
// FIXME: put extensions here if used // FIXME: put extensions here if used
@ -498,7 +708,7 @@ export class CryptoImplementation {
} else { } else {
throw Error("unsupported exchange protocol version"); throw Error("unsupported exchange protocol version");
} }
const coinSigRes = await myEddsaSign(this.primitiveWorker, { const coinSigRes = await this.eddsaSign(tci, {
msg: encodeCrock(d), msg: encodeCrock(d),
priv: depositInfo.coinPriv, priv: depositInfo.coinPriv,
}); });
@ -521,9 +731,10 @@ export class CryptoImplementation {
`unsupported denomination cipher (${depositInfo.denomKeyType})`, `unsupported denomination cipher (${depositInfo.denomKeyType})`,
); );
} }
} },
async deriveRefreshSession( async deriveRefreshSession(
tci: TalerCryptoInterfaceR,
req: DeriveRefreshSessionRequest, req: DeriveRefreshSessionRequest,
): Promise<DerivedRefreshSession> { ): Promise<DerivedRefreshSession> {
const { const {
@ -596,24 +807,14 @@ export class CryptoImplementation {
let coinPub: Uint8Array; let coinPub: Uint8Array;
let coinPriv: Uint8Array; let coinPriv: Uint8Array;
let blindingFactor: Uint8Array; let blindingFactor: Uint8Array;
// disabled while not implemented in the C code // FIXME: make setupRefreshPlanchet a crypto api fn
if (0 && this.primitiveWorker) { let fresh: FreshCoin = setupRefreshPlanchet(
const r = await this.primitiveWorker.setupRefreshPlanchet({ transferSecret,
transfer_secret: encodeCrock(transferSecret), coinIndex,
coin_index: coinIndex, );
}); coinPriv = fresh.coinPriv;
coinPub = decodeCrock(r.coin_pub); coinPub = fresh.coinPub;
coinPriv = decodeCrock(r.coin_priv); blindingFactor = fresh.bks;
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); const coinPubHash = hash(coinPub);
if (denomSel.denomPub.cipher !== DenomKeyType.Rsa) { if (denomSel.denomPub.cipher !== DenomKeyType.Rsa) {
throw Error("unsupported cipher, can't create refresh session"); throw Error("unsupported cipher, can't create refresh session");
@ -654,7 +855,7 @@ export class CryptoImplementation {
.put(amountToBuffer(meltFee)) .put(amountToBuffer(meltFee))
.build(); .build();
const confirmSigResp = await myEddsaSign(this.primitiveWorker, { const confirmSigResp = await tci.eddsaSign(tci, {
msg: encodeCrock(confirmData), msg: encodeCrock(confirmData),
priv: meltCoinPriv, priv: meltCoinPriv,
}); });
@ -670,102 +871,42 @@ export class CryptoImplementation {
}; };
return refreshSession; return refreshSession;
} },
/** /**
* Hash a string including the zero terminator. * Hash a string including the zero terminator.
*/ */
hashString(str: string): string { async hashString(
const b = stringToBytes(str + "\0"); tci: TalerCryptoInterfaceR,
return encodeCrock(hash(b)); req: HashStringRequest,
} ): Promise<HashStringResult> {
const b = stringToBytes(req.str + "\0");
/** return { h: encodeCrock(hash(b)) };
* Hash a crockford encoded value. },
*/
hashEncoded(encodedBytes: string): string {
return encodeCrock(hash(decodeCrock(encodedBytes)));
}
async signCoinLink( async signCoinLink(
oldCoinPriv: string, tci: TalerCryptoInterfaceR,
newDenomHash: string, req: SignCoinLinkRequest,
oldCoinPub: string, ): Promise<EddsaSigningResult> {
transferPub: string, const coinEvHash = hashCoinEv(req.coinEv, req.newDenomHash);
coinEv: CoinEnvelope,
): Promise<string> {
const coinEvHash = hashCoinEv(coinEv, newDenomHash);
// FIXME: fill in // FIXME: fill in
const hAgeCommitment = new Uint8Array(32); const hAgeCommitment = new Uint8Array(32);
const coinLink = buildSigPS(TalerSignaturePurpose.WALLET_COIN_LINK) const coinLink = buildSigPS(TalerSignaturePurpose.WALLET_COIN_LINK)
.put(decodeCrock(newDenomHash)) .put(decodeCrock(req.newDenomHash))
.put(decodeCrock(transferPub)) .put(decodeCrock(req.transferPub))
.put(hAgeCommitment) .put(hAgeCommitment)
.put(coinEvHash) .put(coinEvHash)
.build(); .build();
const sig = await myEddsaSign(this.primitiveWorker, { return tci.eddsaSign(tci, {
msg: encodeCrock(coinLink), msg: encodeCrock(coinLink),
priv: oldCoinPriv, priv: req.oldCoinPriv,
}); });
return sig.sig; },
}
benchmark(repetitions: number): BenchmarkResult { async makeSyncSignature(
let time_hash = BigInt(0); tci: TalerCryptoInterfaceR,
for (let i = 0; i < repetitions; i++) { req: MakeSyncSignatureRequest,
const start = timer.performanceNow(); ): Promise<EddsaSigningResult> {
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); const hNew = decodeCrock(req.newHash);
let hOld: Uint8Array; let hOld: Uint8Array;
if (req.oldHash) { if (req.oldHash) {
@ -778,6 +919,64 @@ export class CryptoImplementation {
.put(hNew) .put(hNew)
.build(); .build();
const uploadSig = eddsaSign(sigBlob, decodeCrock(req.accountPriv)); 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;

View File

@ -29,7 +29,6 @@
*/ */
import { import {
AmountJson, AmountJson,
AmountString,
CoinEnvelope, CoinEnvelope,
DenominationPubKey, DenominationPubKey,
ExchangeProtocolVersion, ExchangeProtocolVersion,

View File

@ -22,39 +22,10 @@
/** /**
* Imports. * 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 { Logger } from "@gnu-taler/taler-util";
import { import * as timer from "../../util/timer.js";
CreateRecoupRefreshReqRequest, import { nullCrypto, TalerCryptoInterface } from "../cryptoImplementation.js";
CreateRecoupReqRequest, import { CryptoWorker } from "./cryptoWorkerInterface.js";
DerivedRefreshSession,
DerivedTipPlanchet,
DeriveRefreshSessionRequest,
DeriveTipRequest,
SignTrackTransactionRequest,
} from "../cryptoTypes.js";
const logger = new Logger("cryptoApi.ts"); const logger = new Logger("cryptoApi.ts");
@ -80,7 +51,7 @@ interface WorkerState {
interface WorkItem { interface WorkItem {
operation: string; operation: string;
args: any[]; req: unknown;
resolve: any; resolve: any;
reject: any; reject: any;
@ -122,10 +93,9 @@ export class CryptoApiStoppedError extends Error {
} }
/** /**
* Crypto API that interfaces manages a background crypto thread * Dispatcher for cryptographic operations to underlying crypto workers.
* for the execution of expensive operations.
*/ */
export class CryptoApi { export class CryptoDispatcher {
private nextRpcId = 1; private nextRpcId = 1;
private workers: WorkerState[]; private workers: WorkerState[];
private workQueues: WorkItem[][]; private workQueues: WorkItem[][];
@ -191,7 +161,7 @@ export class CryptoApi {
} }
const msg: any = { const msg: any = {
args: work.args, req: work.req,
id: work.rpcId, id: work.rpcId,
operation: work.operation, operation: work.operation,
}; };
@ -277,7 +247,16 @@ export class CryptoApi {
currentWorkItem.resolve(msg.data.result); currentWorkItem.resolve(msg.data.result);
} }
cryptoApi: TalerCryptoInterface;
constructor(workerFactory: CryptoWorkerFactory) { 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.workerFactory = workerFactory;
this.workers = new Array<WorkerState>(workerFactory.getConcurrency()); this.workers = new Array<WorkerState>(workerFactory.getConcurrency());
@ -298,7 +277,7 @@ export class CryptoApi {
private doRpc<T>( private doRpc<T>(
operation: string, operation: string,
priority: number, priority: number,
...args: any[] req: unknown,
): Promise<T> { ): Promise<T> {
if (this.stopped) { if (this.stopped) {
throw new CryptoApiStoppedError(); throw new CryptoApiStoppedError();
@ -307,7 +286,7 @@ export class CryptoApi {
const rpcId = this.nextRpcId++; const rpcId = this.nextRpcId++;
const workItem: WorkItem = { const workItem: WorkItem = {
operation, operation,
args, req,
resolve, resolve,
reject, reject,
rpcId, 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);
}
} }

View File

@ -17,11 +17,11 @@
/** /**
* Imports * Imports
*/ */
import { CryptoWorkerFactory } from "./cryptoApi.js"; import { CryptoWorkerFactory } from "./cryptoDispatcher.js";
import { CryptoWorker } from "./cryptoWorkerInterface.js"; import { CryptoWorker } from "./cryptoWorkerInterface.js";
import os from "os"; import os from "os";
import { CryptoImplementation } from "./cryptoImplementation.js";
import { Logger } from "@gnu-taler/taler-util"; import { Logger } from "@gnu-taler/taler-util";
import { nativeCryptoR } from "../cryptoImplementation.js";
const logger = new Logger("nodeThreadWorker.ts"); const logger = new Logger("nodeThreadWorker.ts");
@ -69,9 +69,9 @@ const workerCode = `
* a message. * a message.
*/ */
export function handleWorkerMessage(msg: any): void { export function handleWorkerMessage(msg: any): void {
const args = msg.args; const req = msg.req;
if (!Array.isArray(args)) { if (typeof req !== "object") {
console.error("args must be array"); console.error("request must be an object");
return; return;
} }
const id = msg.id; const id = msg.id;
@ -86,7 +86,7 @@ export function handleWorkerMessage(msg: any): void {
} }
const handleRequest = async (): Promise<void> => { const handleRequest = async (): Promise<void> => {
const impl = new CryptoImplementation(); const impl = nativeCryptoR;
if (!(operation in impl)) { if (!(operation in impl)) {
console.error(`crypto operation '${operation}' not found`); console.error(`crypto operation '${operation}' not found`);
@ -94,12 +94,11 @@ export function handleWorkerMessage(msg: any): void {
} }
try { 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 // eslint-disable-next-line @typescript-eslint/no-var-requires
const _r = "require"; const _r = "require";
const worker_threads: typeof import("worker_threads") = module[_r]( const worker_threads: typeof import("worker_threads") =
"worker_threads", module[_r]("worker_threads");
);
// const worker_threads = require("worker_threads"); // const worker_threads = require("worker_threads");
const p = worker_threads.parentPort; const p = worker_threads.parentPort;

View File

@ -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;
}
}

View File

@ -16,11 +16,10 @@
import { Logger } from "@gnu-taler/taler-util"; import { Logger } from "@gnu-taler/taler-util";
import { import {
CryptoImplementation, nativeCryptoR,
PrimitiveWorker TalerCryptoInterfaceR,
} from "./cryptoImplementation.js"; } from "../cryptoImplementation.js";
import { CryptoRpcClient } from "./rpcClient.js";
const logger = new Logger("synchronousWorker.ts"); const logger = new Logger("synchronousWorker.ts");
@ -38,9 +37,33 @@ export class SynchronousCryptoWorker {
*/ */
onerror: undefined | ((m: any) => void); onerror: undefined | ((m: any) => void);
constructor(private primitiveWorker?: PrimitiveWorker) { cryptoImplR: TalerCryptoInterfaceR;
rpcClient: CryptoRpcClient | undefined;
constructor() {
this.onerror = undefined; this.onerror = undefined;
this.onmessage = 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( private async handleRequest(
operation: string, operation: string,
id: number, id: number,
args: string[], req: unknown,
): Promise<void> { ): Promise<void> {
const impl = new CryptoImplementation(this.primitiveWorker); const impl = this.cryptoImplR;
if (!(operation in impl)) { if (!(operation in impl)) {
console.error(`crypto operation '${operation}' not found`); console.error(`crypto operation '${operation}' not found`);
@ -77,7 +100,7 @@ export class SynchronousCryptoWorker {
let result: any; let result: any;
try { try {
result = await (impl as any)[operation](...args); result = await (impl as any)[operation](impl, req);
} catch (e) { } catch (e) {
logger.error("error during operation", e); logger.error("error during operation", e);
return; return;
@ -94,9 +117,9 @@ export class SynchronousCryptoWorker {
* Send a message to the worker thread. * Send a message to the worker thread.
*/ */
postMessage(msg: any): void { postMessage(msg: any): void {
const args = msg.args; const req = msg.req;
if (!Array.isArray(args)) { if (typeof req !== "object") {
console.error("args must be array"); console.error("request must be an object");
return; return;
} }
const id = msg.id; const id = msg.id;
@ -110,7 +133,7 @@ export class SynchronousCryptoWorker {
return; return;
} }
this.handleRequest(operation, id, args).catch((e) => { this.handleRequest(operation, id, req).catch((e) => {
console.error("Error while handling crypto request:", e); console.error("Error while handling crypto request:", e);
}); });
} }

View File

@ -14,121 +14,13 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { /**
PrimitiveWorker, * Imports.
} from "./cryptoImplementation.js"; */
import { CryptoWorkerFactory } from "./cryptoDispatcher.js";
import { CryptoWorkerFactory } from "./cryptoApi.js";
import { CryptoWorker } from "./cryptoWorkerInterface.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"; 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 * The synchronous crypto worker produced by this factory doesn't run in the
* background, but actually blocks the caller until the operation is done. * 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"); throw Error("cannot make worker, require(...) not defined");
} }
let primitiveWorker; return new SynchronousCryptoWorker();
if (process.env["TALER_WALLET_PRIMITIVE_WORKER"]) {
primitiveWorker = new MyPrimitiveWorker();
}
return new SynchronousCryptoWorker(primitiveWorker);
} }
getConcurrency(): number { getConcurrency(): number {

View File

@ -47,10 +47,10 @@ import {
AbsoluteTime, AbsoluteTime,
UnblindedSignature, UnblindedSignature,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
import { DenominationRecord } from "./db.js"; import { DenominationRecord } from "./db.js";
import { import {
assembleRefreshRevealRequest, assembleRefreshRevealRequest,
CryptoApi,
ExchangeInfo, ExchangeInfo,
getBankWithdrawalInfo, getBankWithdrawalInfo,
HttpRequestLibrary, HttpRequestLibrary,
@ -149,7 +149,7 @@ export async function topupReserveWithDemobank(
export async function withdrawCoin(args: { export async function withdrawCoin(args: {
http: HttpRequestLibrary; http: HttpRequestLibrary;
cryptoApi: CryptoApi; cryptoApi: TalerCryptoInterface;
reserveKeyPair: ReserveKeypair; reserveKeyPair: ReserveKeypair;
denom: DenominationRecord; denom: DenominationRecord;
exchangeBaseUrl: string; exchangeBaseUrl: string;
@ -212,7 +212,7 @@ export function findDenomOrThrow(
export async function depositCoin(args: { export async function depositCoin(args: {
http: HttpRequestLibrary; http: HttpRequestLibrary;
cryptoApi: CryptoApi; cryptoApi: TalerCryptoInterface;
exchangeBaseUrl: string; exchangeBaseUrl: string;
coin: CoinInfo; coin: CoinInfo;
amount: AmountString; amount: AmountString;
@ -263,7 +263,7 @@ export async function depositCoin(args: {
export async function refreshCoin(req: { export async function refreshCoin(req: {
http: HttpRequestLibrary; http: HttpRequestLibrary;
cryptoApi: CryptoApi; cryptoApi: TalerCryptoInterface;
oldCoin: CoinInfo; oldCoin: CoinInfo;
newDenoms: DenominationRecord[]; newDenoms: DenominationRecord[];
}): Promise<void> { }): Promise<void> {

View File

@ -25,7 +25,9 @@
import type { IDBFactory } from "@gnu-taler/idb-bridge"; import type { IDBFactory } from "@gnu-taler/idb-bridge";
// eslint-disable-next-line no-duplicate-imports // eslint-disable-next-line no-duplicate-imports
import { import {
BridgeIDBFactory, MemoryBackend, shimIndexedDB BridgeIDBFactory,
MemoryBackend,
shimIndexedDB,
} from "@gnu-taler/idb-bridge"; } from "@gnu-taler/idb-bridge";
import { AccessStats } from "@gnu-taler/idb-bridge/src/MemoryBackend"; import { AccessStats } from "@gnu-taler/idb-bridge/src/MemoryBackend";
import { Logger, WalletNotification } from "@gnu-taler/taler-util"; import { Logger, WalletNotification } from "@gnu-taler/taler-util";

View File

@ -33,9 +33,11 @@ export * from "./db-utils.js";
// Crypto and crypto workers // Crypto and crypto workers
// export * from "./crypto/workers/nodeThreadWorker.js"; // export * from "./crypto/workers/nodeThreadWorker.js";
export { CryptoImplementation } from "./crypto/workers/cryptoImplementation.js";
export type { CryptoWorker } from "./crypto/workers/cryptoWorkerInterface.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 { SynchronousCryptoWorker } from "./crypto/workers/synchronousWorker.js";
export * from "./pending-types.js"; export * from "./pending-types.js";
@ -58,3 +60,8 @@ export * from "./operations/refresh.js";
export * from "./dbless.js"; export * from "./dbless.js";
export {
nativeCryptoR,
nativeCrypto,
nullCrypto,
} from "./crypto/cryptoImplementation.js";

View File

@ -36,7 +36,8 @@ import {
DenominationPubKey, DenominationPubKey,
TalerProtocolTimestamp, TalerProtocolTimestamp,
} from "@gnu-taler/taler-util"; } 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 { ExchangeDetailsRecord, ExchangeRecord, WalletStoresV1 } from "./db.js";
import { PendingOperationsResponse } from "./pending-types.js"; import { PendingOperationsResponse } from "./pending-types.js";
import { AsyncOpMemoMap, AsyncOpMemoSingle } from "./util/asyncMemo.js"; import { AsyncOpMemoMap, AsyncOpMemoSingle } from "./util/asyncMemo.js";
@ -200,7 +201,7 @@ export interface InternalWalletState {
memoProcessRefresh: AsyncOpMemoMap<void>; memoProcessRefresh: AsyncOpMemoMap<void>;
memoProcessRecoup: AsyncOpMemoMap<void>; memoProcessRecoup: AsyncOpMemoMap<void>;
memoProcessDeposit: AsyncOpMemoMap<void>; memoProcessDeposit: AsyncOpMemoMap<void>;
cryptoApi: CryptoApi; cryptoApi: TalerCryptoInterface;
timerGroup: TimerGroup; timerGroup: TimerGroup;
stopped: boolean; stopped: boolean;

View File

@ -69,7 +69,7 @@ import {
rsaBlind, rsaBlind,
stringToBytes, stringToBytes,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { CryptoApi } from "../../crypto/workers/cryptoApi.js"; import { CryptoDispatcher } from "../../crypto/workers/cryptoDispatcher.js";
import { import {
BackupProviderRecord, BackupProviderRecord,
BackupProviderState, BackupProviderState,
@ -99,6 +99,7 @@ import { exportBackup } from "./export.js";
import { BackupCryptoPrecomputedData, importBackup } from "./import.js"; import { BackupCryptoPrecomputedData, importBackup } from "./import.js";
import { getWalletBackupState, provideBackupState } from "./state.js"; import { getWalletBackupState, provideBackupState } from "./state.js";
import { guardOperationException } from "../common.js"; import { guardOperationException } from "../common.js";
import { TalerCryptoInterface } from "../../crypto/cryptoImplementation.js";
const logger = new Logger("operations/backup.ts"); const logger = new Logger("operations/backup.ts");
@ -154,7 +155,7 @@ export async function encryptBackup(
* FIXME: Move computations into crypto worker. * FIXME: Move computations into crypto worker.
*/ */
async function computeBackupCryptoData( async function computeBackupCryptoData(
cryptoApi: CryptoApi, cryptoApi: TalerCryptoInterface,
backupContent: WalletBackupContentV1, backupContent: WalletBackupContentV1,
): Promise<BackupCryptoPrecomputedData> { ): Promise<BackupCryptoPrecomputedData> {
const cryptoData: BackupCryptoPrecomputedData = { const cryptoData: BackupCryptoPrecomputedData = {
@ -193,18 +194,18 @@ async function computeBackupCryptoData(
} }
} }
for (const prop of backupContent.proposals) { for (const prop of backupContent.proposals) {
const contractTermsHash = await cryptoApi.hashString( const { h: contractTermsHash } = await cryptoApi.hashString({
canonicalJson(prop.contract_terms_raw), str: canonicalJson(prop.contract_terms_raw),
); });
const noncePub = encodeCrock(eddsaGetPublic(decodeCrock(prop.nonce_priv))); const noncePub = encodeCrock(eddsaGetPublic(decodeCrock(prop.nonce_priv)));
cryptoData.proposalNoncePrivToPub[prop.nonce_priv] = noncePub; cryptoData.proposalNoncePrivToPub[prop.nonce_priv] = noncePub;
cryptoData.proposalIdToContractTermsHash[prop.proposal_id] = cryptoData.proposalIdToContractTermsHash[prop.proposal_id] =
contractTermsHash; contractTermsHash;
} }
for (const purch of backupContent.purchases) { for (const purch of backupContent.purchases) {
const contractTermsHash = await cryptoApi.hashString( const { h: contractTermsHash } = await cryptoApi.hashString({
canonicalJson(purch.contract_terms_raw), str: canonicalJson(purch.contract_terms_raw),
); });
const noncePub = encodeCrock(eddsaGetPublic(decodeCrock(purch.nonce_priv))); const noncePub = encodeCrock(eddsaGetPublic(decodeCrock(purch.nonce_priv)));
cryptoData.proposalNoncePrivToPub[purch.nonce_priv] = noncePub; cryptoData.proposalNoncePrivToPub[purch.nonce_priv] = noncePub;
cryptoData.proposalIdToContractTermsHash[purch.proposal_id] = cryptoData.proposalIdToContractTermsHash[purch.proposal_id] =
@ -286,13 +287,13 @@ async function runBackupCycleForProvider(
logger.trace(`trying to upload backup to ${provider.baseUrl}`); logger.trace(`trying to upload backup to ${provider.baseUrl}`);
logger.trace(`old hash ${oldHash}, new hash ${newHash}`); logger.trace(`old hash ${oldHash}, new hash ${newHash}`);
const syncSig = await ws.cryptoApi.makeSyncSignature({ const syncSigResp = await ws.cryptoApi.makeSyncSignature({
newHash: encodeCrock(currentBackupHash), newHash: encodeCrock(currentBackupHash),
oldHash: provider.lastBackupHash, oldHash: provider.lastBackupHash,
accountPriv: encodeCrock(accountKeyPair.eddsaPriv), accountPriv: encodeCrock(accountKeyPair.eddsaPriv),
}); });
logger.trace(`sync signature is ${syncSig}`); logger.trace(`sync signature is ${syncSigResp}`);
const accountBackupUrl = new URL( const accountBackupUrl = new URL(
`/backups/${encodeCrock(accountKeyPair.eddsaPub)}`, `/backups/${encodeCrock(accountKeyPair.eddsaPub)}`,
@ -304,7 +305,7 @@ async function runBackupCycleForProvider(
body: encBackup, body: encBackup,
headers: { headers: {
"content-type": "application/octet-stream", "content-type": "application/octet-stream",
"sync-signature": syncSig, "sync-signature": syncSigResp.sig,
"if-none-match": newHash, "if-none-match": newHash,
...(provider.lastBackupHash ...(provider.lastBackupHash
? { ? {

View File

@ -41,7 +41,7 @@ export async function provideBackupState(
} }
// We need to generate the key outside of the transaction // We need to generate the key outside of the transaction
// due to how IndexedDB works. // due to how IndexedDB works.
const k = await ws.cryptoApi.createEddsaKeypair(); const k = await ws.cryptoApi.createEddsaKeypair({});
const d = getRandomBytes(5); const d = getRandomBytes(5);
// FIXME: device ID should be configured when wallet is initialized // FIXME: device ID should be configured when wallet is initialized
// and be based on hostname // and be based on hostname

View File

@ -15,7 +15,7 @@
*/ */
import { TalerErrorDetail, TalerErrorCode } from "@gnu-taler/taler-util"; 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"; import { TalerError, getErrorDetailFromException } from "../errors.js";
/** /**

View File

@ -254,14 +254,14 @@ export async function trackDepositGroup(
`deposits/${wireHash}/${depositGroup.merchantPub}/${depositGroup.contractTermsHash}/${dp.coin_pub}`, `deposits/${wireHash}/${depositGroup.merchantPub}/${depositGroup.contractTermsHash}/${dp.coin_pub}`,
dp.exchange_url, dp.exchange_url,
); );
const sig = await ws.cryptoApi.signTrackTransaction({ const sigResp = await ws.cryptoApi.signTrackTransaction({
coinPub: dp.coin_pub, coinPub: dp.coin_pub,
contractTermsHash: depositGroup.contractTermsHash, contractTermsHash: depositGroup.contractTermsHash,
merchantPriv: depositGroup.merchantPriv, merchantPriv: depositGroup.merchantPriv,
merchantPub: depositGroup.merchantPub, merchantPub: depositGroup.merchantPub,
wireHash, wireHash,
}); });
url.searchParams.set("merchant_sig", sig); url.searchParams.set("merchant_sig", sigResp.sig);
const httpResp = await ws.http.get(url.href); const httpResp = await ws.http.get(url.href);
const body = await httpResp.json(); const body = await httpResp.json();
responses.push({ responses.push({
@ -391,8 +391,8 @@ export async function createDepositGroup(
const now = AbsoluteTime.now(); const now = AbsoluteTime.now();
const nowRounded = AbsoluteTime.toTimestamp(now); const nowRounded = AbsoluteTime.toTimestamp(now);
const noncePair = await ws.cryptoApi.createEddsaKeypair(); const noncePair = await ws.cryptoApi.createEddsaKeypair({});
const merchantPair = await ws.cryptoApi.createEddsaKeypair(); const merchantPair = await ws.cryptoApi.createEddsaKeypair({});
const wireSalt = encodeCrock(getRandomBytes(16)); const wireSalt = encodeCrock(getRandomBytes(16));
const wireHash = hashWire(req.depositPaytoUri, wireSalt); const wireHash = hashWire(req.depositPaytoUri, wireSalt);
const contractTerms: ContractTerms = { const contractTerms: ContractTerms = {
@ -421,9 +421,9 @@ export async function createDepositGroup(
refund_deadline: TalerProtocolTimestamp.zero(), refund_deadline: TalerProtocolTimestamp.zero(),
}; };
const contractTermsHash = await ws.cryptoApi.hashString( const { h: contractTermsHash } = await ws.cryptoApi.hashString({
canonicalJson(contractTerms), str: canonicalJson(contractTerms),
); });
const contractData = extractContractData( const contractData = extractContractData(
contractTerms, contractTerms,

View File

@ -46,7 +46,7 @@ import {
TalerProtocolDuration, TalerProtocolDuration,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { decodeCrock, encodeCrock, hash } 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 { import {
DenominationRecord, DenominationRecord,
DenominationVerificationStatus, DenominationVerificationStatus,
@ -243,12 +243,13 @@ async function validateWireInfo(
if (ws.insecureTrustExchange) { if (ws.insecureTrustExchange) {
isValid = true; isValid = true;
} else { } else {
isValid = await ws.cryptoApi.isValidWireAccount( const { valid: v } = await ws.cryptoApi.isValidWireAccount({
masterPub: masterPublicKey,
paytoUri: a.payto_uri,
sig: a.master_sig,
versionCurrent, versionCurrent,
a.payto_uri, });
a.master_sig, isValid = v;
masterPublicKey,
);
} }
if (!isValid) { if (!isValid) {
throw Error("exchange acct signature invalid"); throw Error("exchange acct signature invalid");
@ -272,11 +273,12 @@ async function validateWireInfo(
if (ws.insecureTrustExchange) { if (ws.insecureTrustExchange) {
isValid = true; isValid = true;
} else { } else {
isValid = await ws.cryptoApi.isValidWireFee( const { valid: v } = await ws.cryptoApi.isValidWireFee({
wireMethod, masterPub: masterPublicKey,
fee, type: wireMethod,
masterPublicKey, wf: fee,
); });
isValid = v;
} }
if (!isValid) { if (!isValid) {
throw Error("exchange wire fee signature invalid"); throw Error("exchange wire fee signature invalid");

View File

@ -55,7 +55,10 @@ import {
TransactionType, TransactionType,
URL, URL,
} from "@gnu-taler/taler-util"; } 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 { import {
AbortStatus, AbortStatus,
AllowedAuditorInfo, AllowedAuditorInfo,
@ -100,6 +103,7 @@ import {
import { getExchangeDetails } from "./exchanges.js"; import { getExchangeDetails } from "./exchanges.js";
import { createRefreshGroup, getTotalRefreshCost } from "./refresh.js"; import { createRefreshGroup, getTotalRefreshCost } from "./refresh.js";
import { guardOperationException } from "./common.js"; import { guardOperationException } from "./common.js";
import { EddsaKeypair } from "../crypto/cryptoImplementation.js";
/** /**
* Logger. * Logger.
@ -795,11 +799,11 @@ async function processDownloadProposalImpl(
); );
} }
const sigValid = await ws.cryptoApi.isValidContractTermsSignature( const sigValid = await ws.cryptoApi.isValidContractTermsSignature({
contractTermsHash, contractTermsHash,
proposalResp.sig, merchantPub: parsedContractTerms.merchant_pub,
parsedContractTerms.merchant_pub, sig: proposalResp.sig,
); });
if (!sigValid) { if (!sigValid) {
const err = makeErrorDetail( const err = makeErrorDetail(
@ -921,9 +925,14 @@ async function startDownloadProposal(
return oldProposal.proposalId; return oldProposal.proposalId;
} }
const { priv, pub } = await (noncePriv let noncePair: EddsaKeypair;
? ws.cryptoApi.eddsaGetPublic(noncePriv) if (noncePriv) {
: ws.cryptoApi.createEddsaKeypair()); noncePair = await ws.cryptoApi.eddsaGetPublic({ priv: noncePriv });
} else {
noncePair = await ws.cryptoApi.createEddsaKeypair({});
}
const { priv, pub } = noncePair;
const proposalId = encodeCrock(getRandomBytes(32)); const proposalId = encodeCrock(getRandomBytes(32));
const proposalRecord: ProposalRecord = { const proposalRecord: ProposalRecord = {
@ -1673,11 +1682,11 @@ async function processPurchasePayImpl(
logger.trace("got success from pay URL", merchantResp); logger.trace("got success from pay URL", merchantResp);
const merchantPub = purchase.download.contractData.merchantPub; const merchantPub = purchase.download.contractData.merchantPub;
const valid: boolean = await ws.cryptoApi.isValidPaymentSignature( const { valid } = await ws.cryptoApi.isValidPaymentSignature({
merchantResp.sig, contractHash: purchase.download.contractData.contractTermsHash,
purchase.download.contractData.contractTermsHash,
merchantPub, merchantPub,
); sig: merchantResp.sig,
});
if (!valid) { if (!valid) {
logger.error("merchant payment signature invalid"); logger.error("merchant payment signature invalid");

View File

@ -76,9 +76,9 @@ import {
RefreshNewDenomInfo, RefreshNewDenomInfo,
} from "../crypto/cryptoTypes.js"; } from "../crypto/cryptoTypes.js";
import { GetReadWriteAccess } from "../util/query.js"; import { GetReadWriteAccess } from "../util/query.js";
import { CryptoApi } from "../index.browser.js";
import { guardOperationException } from "./common.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"); const logger = new Logger("refresh.ts");
@ -461,7 +461,7 @@ async function refreshMelt(
} }
export async function assembleRefreshRevealRequest(args: { export async function assembleRefreshRevealRequest(args: {
cryptoApi: CryptoApi; cryptoApi: TalerCryptoInterface;
derived: DerivedRefreshSession; derived: DerivedRefreshSession;
norevealIndex: number; norevealIndex: number;
oldCoinPub: CoinPublicKeyString; oldCoinPub: CoinPublicKeyString;
@ -494,14 +494,14 @@ export async function assembleRefreshRevealRequest(args: {
const dsel = newDenoms[i]; const dsel = newDenoms[i];
for (let j = 0; j < dsel.count; j++) { for (let j = 0; j < dsel.count; j++) {
const newCoinIndex = linkSigs.length; const newCoinIndex = linkSigs.length;
const linkSig = await cryptoApi.signCoinLink( const linkSig = await cryptoApi.signCoinLink({
oldCoinPriv, coinEv: planchets[newCoinIndex].coinEv,
dsel.denomPubHash, newDenomHash: dsel.denomPubHash,
oldCoinPub, oldCoinPriv: oldCoinPriv,
derived.transferPubs[norevealIndex], oldCoinPub: oldCoinPub,
planchets[newCoinIndex].coinEv, transferPub: derived.transferPubs[norevealIndex],
); });
linkSigs.push(linkSig); linkSigs.push(linkSig.sig);
newDenomsFlat.push(dsel.denomPubHash); newDenomsFlat.push(dsel.denomPubHash);
} }
} }

View File

@ -170,7 +170,7 @@ export async function createReserve(
ws: InternalWalletState, ws: InternalWalletState,
req: CreateReserveRequest, req: CreateReserveRequest,
): Promise<CreateReserveResponse> { ): Promise<CreateReserveResponse> {
const keypair = await ws.cryptoApi.createEddsaKeypair(); const keypair = await ws.cryptoApi.createEddsaKeypair({});
const now = AbsoluteTime.toTimestamp(AbsoluteTime.now()); const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
const canonExchange = canonicalizeBaseUrl(req.exchange); const canonExchange = canonicalizeBaseUrl(req.exchange);

View File

@ -336,17 +336,17 @@ async function processTipImpl(
throw Error("unsupported cipher"); throw Error("unsupported cipher");
} }
const denomSigRsa = await ws.cryptoApi.rsaUnblind( const denomSigRsa = await ws.cryptoApi.rsaUnblind({
blindedSig.blinded_rsa_signature, bk: planchet.blindingKey,
planchet.blindingKey, blindedSig: blindedSig.blinded_rsa_signature,
denom.denomPub.rsa_public_key, pk: denom.denomPub.rsa_public_key,
); });
const isValid = await ws.cryptoApi.rsaVerify( const isValid = await ws.cryptoApi.rsaVerify({
planchet.coinPub, hm: planchet.coinPub,
denomSigRsa, pk: denom.denomPub.rsa_public_key,
denom.denomPub.rsa_public_key, sig: denomSigRsa.sig,
); });
if (!isValid) { if (!isValid) {
await ws.db await ws.db
@ -377,7 +377,7 @@ async function processTipImpl(
}, },
currentAmount: denom.value, currentAmount: denom.value,
denomPubHash: denom.denomPubHash, denomPubHash: denom.denomPubHash,
denomSig: { cipher: DenomKeyType.Rsa, rsa_signature: denomSigRsa }, denomSig: { cipher: DenomKeyType.Rsa, rsa_signature: denomSigRsa.sig },
exchangeBaseUrl: tipRecord.exchangeBaseUrl, exchangeBaseUrl: tipRecord.exchangeBaseUrl,
status: CoinStatus.Fresh, status: CoinStatus.Fresh,
suspended: false, suspended: false,

View File

@ -603,17 +603,17 @@ async function processPlanchetVerifyAndStoreCoin(
throw Error("unsupported cipher"); throw Error("unsupported cipher");
} }
const denomSigRsa = await ws.cryptoApi.rsaUnblind( const denomSigRsa = await ws.cryptoApi.rsaUnblind({
evSig.blinded_rsa_signature, bk: planchet.blindingKey,
planchet.blindingKey, blindedSig: evSig.blinded_rsa_signature,
planchetDenomPub.rsa_public_key, pk: planchetDenomPub.rsa_public_key,
); });
const isValid = await ws.cryptoApi.rsaVerify( const isValid = await ws.cryptoApi.rsaVerify({
planchet.coinPub, hm: planchet.coinPub,
denomSigRsa, pk: planchetDenomPub.rsa_public_key,
planchetDenomPub.rsa_public_key, sig: denomSigRsa.sig,
); });
if (!isValid) { if (!isValid) {
await ws.db await ws.db
@ -640,7 +640,7 @@ async function processPlanchetVerifyAndStoreCoin(
if (planchetDenomPub.cipher === DenomKeyType.Rsa) { if (planchetDenomPub.cipher === DenomKeyType.Rsa) {
denomSig = { denomSig = {
cipher: planchetDenomPub.cipher, cipher: planchetDenomPub.cipher,
rsa_signature: denomSigRsa, rsa_signature: denomSigRsa.sig,
}; };
} else { } else {
throw Error("unsupported cipher"); throw Error("unsupported cipher");
@ -759,10 +759,11 @@ export async function updateWithdrawalDenoms(
if (ws.insecureTrustExchange) { if (ws.insecureTrustExchange) {
valid = true; valid = true;
} else { } else {
valid = await ws.cryptoApi.isValidDenom( const res = await ws.cryptoApi.isValidDenom({
denom, denom,
exchangeDetails.masterPublicKey, masterPub: exchangeDetails.masterPublicKey,
); });
valid = res.valid;
} }
logger.trace(`Done validating ${denom.denomPubHash}`); logger.trace(`Done validating ${denom.denomPubHash}`);
if (!valid) { if (!valid) {

View File

@ -90,7 +90,10 @@ import {
RecoupOperations, RecoupOperations,
ReserveOperations, ReserveOperations,
} from "./internal-wallet-state.js"; } from "./internal-wallet-state.js";
import { CryptoApi, CryptoWorkerFactory } from "./crypto/workers/cryptoApi.js"; import {
CryptoDispatcher,
CryptoWorkerFactory,
} from "./crypto/workers/cryptoDispatcher.js";
import { import {
AuditorTrustRecord, AuditorTrustRecord,
CoinSourceType, CoinSourceType,
@ -99,10 +102,7 @@ import {
ReserveRecordStatus, ReserveRecordStatus,
WalletStoresV1, WalletStoresV1,
} from "./db.js"; } from "./db.js";
import { import { getErrorDetailFromException, TalerError } from "./errors.js";
getErrorDetailFromException,
TalerError,
} from "./errors.js";
import { exportBackup } from "./operations/backup/export.js"; import { exportBackup } from "./operations/backup/export.js";
import { import {
addBackupProvider, addBackupProvider,
@ -193,6 +193,10 @@ import {
import { DbAccess, GetReadWriteAccess } from "./util/query.js"; import { DbAccess, GetReadWriteAccess } from "./util/query.js";
import { TimerGroup } from "./util/timer.js"; import { TimerGroup } from "./util/timer.js";
import { WalletCoreApiClient } from "./wallet-api-types.js"; import { WalletCoreApiClient } from "./wallet-api-types.js";
import {
TalerCryptoInterface,
TalerCryptoInterfaceR,
} from "./crypto/cryptoImplementation.js";
const builtinAuditors: AuditorTrustRecord[] = [ const builtinAuditors: AuditorTrustRecord[] = [
{ {
@ -1158,7 +1162,8 @@ class InternalWalletStateImpl implements InternalWalletState {
memoProcessRefresh: AsyncOpMemoMap<void> = new AsyncOpMemoMap(); memoProcessRefresh: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
memoProcessRecoup: AsyncOpMemoMap<void> = new AsyncOpMemoMap(); memoProcessRecoup: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
memoProcessDeposit: AsyncOpMemoMap<void> = new AsyncOpMemoMap(); memoProcessDeposit: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
cryptoApi: CryptoApi; cryptoApi: TalerCryptoInterface;
cryptoDispatcher: CryptoDispatcher;
merchantInfoCache: Record<string, MerchantInfo> = {}; merchantInfoCache: Record<string, MerchantInfo> = {};
@ -1213,7 +1218,8 @@ class InternalWalletStateImpl implements InternalWalletState {
public http: HttpRequestLibrary, public http: HttpRequestLibrary,
cryptoWorkerFactory: CryptoWorkerFactory, cryptoWorkerFactory: CryptoWorkerFactory,
) { ) {
this.cryptoApi = new CryptoApi(cryptoWorkerFactory); this.cryptoDispatcher = new CryptoDispatcher(cryptoWorkerFactory);
this.cryptoApi = this.cryptoDispatcher.cryptoApi;
} }
async getDenomInfo( async getDenomInfo(
@ -1258,7 +1264,7 @@ class InternalWalletStateImpl implements InternalWalletState {
stop(): void { stop(): void {
this.stopped = true; this.stopped = true;
this.timerGroup.stopCurrentAndFutureTimers(); this.timerGroup.stopCurrentAndFutureTimers();
this.cryptoApi.stop(); this.cryptoDispatcher.stop();
} }
async runUntilDone( async runUntilDone(

View File

@ -23,18 +23,18 @@
*/ */
import { Logger } from "@gnu-taler/taler-util"; 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 logger = new Logger("browserWorkerEntry.ts");
const worker: Worker = (self as any) as Worker; const worker: Worker = self as any as Worker;
async function handleRequest( async function handleRequest(
operation: string, operation: string,
id: number, id: number,
args: string[], args: string[],
): Promise<void> { ): Promise<void> {
const impl = new CryptoImplementation(); const impl = nativeCrypto;
if (!(operation in impl)) { if (!(operation in impl)) {
console.error(`crypto operation '${operation}' not found`); console.error(`crypto operation '${operation}' not found`);

View File

@ -97,10 +97,10 @@ export interface UpgradeResponse {
async function callBackend(operation: string, payload: any): Promise<any> { async function callBackend(operation: string, payload: any): Promise<any> {
let response: CoreApiResponse; let response: CoreApiResponse;
try { try {
response = await platform.setMessageToWalletBackground(operation, payload) response = await platform.setMessageToWalletBackground(operation, payload);
} catch (e) { } catch (e) {
console.log("Error calling backend"); console.log("Error calling backend");
throw new Error(`Error contacting backend: ${e}`) throw new Error(`Error contacting backend: ${e}`);
} }
console.log("got response", response); console.log("got response", response);
if (response.type === "error") { if (response.type === "error") {
@ -413,12 +413,15 @@ export function importDB(dump: any): Promise<void> {
return callBackend("importDb", { dump }); 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 listener = (message: MessageFromBackend): void => {
const shouldNotify = messageTypes.includes(message.type); const shouldNotify = messageTypes.includes(message.type);
if (shouldNotify) { if (shouldNotify) {
doCallback(); doCallback();
} }
}; };
return platform.listenToWalletNotifications(listener) return platform.listenToWalletNotifications(listener);
} }