wallet: experiment with C-based crypto worker for some primitives
This commit is contained in:
parent
1d4815c66c
commit
c33ed91971
@ -11,8 +11,6 @@ import {
|
||||
stringToBytes,
|
||||
secretbox_open,
|
||||
hash,
|
||||
Logger,
|
||||
j2s,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { argon2id } from "hash-wasm";
|
||||
|
||||
|
@ -161,10 +161,6 @@ interface RsaPub {
|
||||
e: bigint.BigInteger;
|
||||
}
|
||||
|
||||
interface RsaBlindingKey {
|
||||
r: bigint.BigInteger;
|
||||
}
|
||||
|
||||
/**
|
||||
* KDF modulo a big integer.
|
||||
*/
|
||||
|
@ -40,6 +40,7 @@ export async function runBench1(configJson: any): Promise<void> {
|
||||
const b1conf = codecForBench1Config().decode(configJson);
|
||||
|
||||
const myHttpLib = new NodeHttpLib();
|
||||
myHttpLib.setThrottling(false);
|
||||
|
||||
const numIter = b1conf.iterations ?? 1;
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
*/
|
||||
import { CoinRecord, DenominationRecord, WireFee } from "../../db.js";
|
||||
|
||||
import { CryptoWorker } from "./cryptoWorker.js";
|
||||
import { CryptoWorker } from "./cryptoWorkerInterface.js";
|
||||
|
||||
import { RecoupRequest, CoinDepositPermission } from "@gnu-taler/taler-util";
|
||||
|
||||
|
@ -15,9 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* Synchronous implementation of crypto-related functions for the wallet.
|
||||
*
|
||||
* The functionality is parameterized over an Emscripten environment.
|
||||
* Implementation of crypto-related high-level functions for the Taler wallet.
|
||||
*
|
||||
* @author Florian Dold <dold@taler.net>
|
||||
*/
|
||||
@ -37,9 +35,9 @@ import {
|
||||
import {
|
||||
buildSigPS,
|
||||
CoinDepositPermission,
|
||||
FreshCoin,
|
||||
RecoupRequest,
|
||||
RefreshPlanchetInfo,
|
||||
SignaturePurposeBuilder,
|
||||
TalerSignaturePurpose,
|
||||
} from "@gnu-taler/taler-util";
|
||||
// FIXME: These types should be internal to the wallet!
|
||||
@ -128,9 +126,27 @@ function timestampRoundedToBuffer(ts: Timestamp): Uint8Array {
|
||||
return new Uint8Array(b);
|
||||
}
|
||||
|
||||
export interface PrimitiveWorker {
|
||||
setupRefreshPlanchet(arg0: {
|
||||
transfer_secret: string;
|
||||
coin_index: number;
|
||||
}): Promise<{
|
||||
coin_pub: string;
|
||||
coin_priv: string;
|
||||
blinding_key: string;
|
||||
}>;
|
||||
eddsaVerify(req: {
|
||||
msg: string;
|
||||
sig: string;
|
||||
pub: string;
|
||||
}): Promise<{ valid: boolean }>;
|
||||
}
|
||||
|
||||
export class CryptoImplementation {
|
||||
static enableTracing = false;
|
||||
|
||||
constructor(private primitiveWorker?: PrimitiveWorker) {}
|
||||
|
||||
/**
|
||||
* Create a pre-coin of the given denomination to be withdrawn from then given
|
||||
* reserve.
|
||||
@ -246,7 +262,11 @@ export class CryptoImplementation {
|
||||
/**
|
||||
* Check if a wire fee is correctly signed.
|
||||
*/
|
||||
isValidWireFee(type: string, wf: WireFee, masterPub: string): boolean {
|
||||
async isValidWireFee(
|
||||
type: string,
|
||||
wf: WireFee,
|
||||
masterPub: string,
|
||||
): Promise<boolean> {
|
||||
const p = buildSigPS(TalerSignaturePurpose.MASTER_WIRE_FEES)
|
||||
.put(hash(stringToBytes(type + "\0")))
|
||||
.put(timestampRoundedToBuffer(wf.startStamp))
|
||||
@ -256,13 +276,25 @@ export class CryptoImplementation {
|
||||
.build();
|
||||
const sig = decodeCrock(wf.sig);
|
||||
const pub = decodeCrock(masterPub);
|
||||
if (this.primitiveWorker) {
|
||||
return (
|
||||
await this.primitiveWorker.eddsaVerify({
|
||||
msg: encodeCrock(p),
|
||||
pub: masterPub,
|
||||
sig: encodeCrock(sig),
|
||||
})
|
||||
).valid;
|
||||
}
|
||||
return eddsaVerify(p, sig, pub);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the signature of a denomination is valid.
|
||||
*/
|
||||
isValidDenom(denom: DenominationRecord, masterPub: string): boolean {
|
||||
async isValidDenom(
|
||||
denom: DenominationRecord,
|
||||
masterPub: string,
|
||||
): Promise<boolean> {
|
||||
const p = buildSigPS(TalerSignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY)
|
||||
.put(decodeCrock(masterPub))
|
||||
.put(timestampRoundedToBuffer(denom.stampStart))
|
||||
@ -377,9 +409,9 @@ export class CryptoImplementation {
|
||||
return s;
|
||||
}
|
||||
|
||||
deriveRefreshSession(
|
||||
async deriveRefreshSession(
|
||||
req: DeriveRefreshSessionRequest,
|
||||
): DerivedRefreshSession {
|
||||
): Promise<DerivedRefreshSession> {
|
||||
const {
|
||||
newCoinDenoms,
|
||||
feeRefresh: meltFee,
|
||||
@ -435,17 +467,33 @@ export class CryptoImplementation {
|
||||
for (let j = 0; j < newCoinDenoms.length; j++) {
|
||||
const denomSel = newCoinDenoms[j];
|
||||
for (let k = 0; k < denomSel.count; k++) {
|
||||
const coinNumber = planchets.length;
|
||||
const coinIndex = planchets.length;
|
||||
const transferPriv = decodeCrock(transferPrivs[i]);
|
||||
const oldCoinPub = decodeCrock(meltCoinPub);
|
||||
const transferSecret = keyExchangeEcdheEddsa(
|
||||
transferPriv,
|
||||
oldCoinPub,
|
||||
);
|
||||
const fresh = setupRefreshPlanchet(transferSecret, coinNumber);
|
||||
const coinPriv = fresh.coinPriv;
|
||||
const coinPub = fresh.coinPub;
|
||||
const blindingFactor = fresh.bks;
|
||||
let coinPub: Uint8Array;
|
||||
let coinPriv: Uint8Array;
|
||||
let blindingFactor: Uint8Array;
|
||||
if (this.primitiveWorker) {
|
||||
const r = await this.primitiveWorker.setupRefreshPlanchet({
|
||||
transfer_secret: encodeCrock(transferSecret),
|
||||
coin_index: coinIndex,
|
||||
});
|
||||
coinPub = decodeCrock(r.coin_pub);
|
||||
coinPriv = decodeCrock(r.coin_priv);
|
||||
blindingFactor = decodeCrock(r.blinding_key);
|
||||
} else {
|
||||
let fresh: FreshCoin = setupRefreshPlanchet(
|
||||
transferSecret,
|
||||
coinIndex,
|
||||
);
|
||||
coinPriv = fresh.coinPriv;
|
||||
coinPub = fresh.coinPub;
|
||||
blindingFactor = fresh.bks;
|
||||
}
|
||||
const pubHash = hash(coinPub);
|
||||
const denomPub = decodeCrock(denomSel.denomPub);
|
||||
const ev = rsaBlind(pubHash, blindingFactor, denomPub);
|
||||
|
@ -18,7 +18,7 @@
|
||||
* Imports
|
||||
*/
|
||||
import { CryptoWorkerFactory } from "./cryptoApi.js";
|
||||
import { CryptoWorker } from "./cryptoWorker.js";
|
||||
import { CryptoWorker } from "./cryptoWorkerInterface.js";
|
||||
import os from "os";
|
||||
import { CryptoImplementation } from "./cryptoImplementation.js";
|
||||
import { Logger } from "@gnu-taler/taler-util";
|
||||
@ -94,7 +94,7 @@ export function handleWorkerMessage(msg: any): void {
|
||||
}
|
||||
|
||||
try {
|
||||
const result = (impl as any)[operation](...args);
|
||||
const result = await (impl as any)[operation](...args);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const _r = "require";
|
||||
const worker_threads: typeof import("worker_threads") = module[_r](
|
||||
|
@ -14,10 +14,107 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { CryptoImplementation } from "./cryptoImplementation.js";
|
||||
import {
|
||||
CryptoImplementation,
|
||||
PrimitiveWorker,
|
||||
} from "./cryptoImplementation.js";
|
||||
|
||||
import { CryptoWorkerFactory } from "./cryptoApi.js";
|
||||
import { CryptoWorker } from "./cryptoWorker.js";
|
||||
import { CryptoWorker } from "./cryptoWorkerInterface.js";
|
||||
|
||||
import child_process from "child_process";
|
||||
import type internal from "stream";
|
||||
import { OpenedPromise, openPromise } from "../../index.js";
|
||||
import { FreshCoin, Logger } from "@gnu-taler/taler-util";
|
||||
|
||||
const logger = new Logger("synchronousWorker.ts");
|
||||
|
||||
class MyPrimitiveWorker implements PrimitiveWorker {
|
||||
proc: child_process.ChildProcessByStdio<
|
||||
internal.Writable,
|
||||
internal.Readable,
|
||||
null
|
||||
>;
|
||||
requests: Array<{
|
||||
p: OpenedPromise<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", function (code) {
|
||||
logger.error("child process exited");
|
||||
});
|
||||
(this.proc.stdout as any).unref();
|
||||
(this.proc.stdin as any).unref();
|
||||
this.proc.unref();
|
||||
|
||||
this.proc.stdout.on("data", (x) => {
|
||||
// console.log("got chunk", x.toString("utf-8"));
|
||||
if (x instanceof Buffer) {
|
||||
const nlIndex = x.indexOf("\n");
|
||||
if (nlIndex >= 0) {
|
||||
const before = x.slice(0, nlIndex);
|
||||
const after = x.slice(nlIndex + 1);
|
||||
stdoutChunks.push(after);
|
||||
const str = Buffer.concat([...stdoutChunks, before]).toString(
|
||||
"utf-8",
|
||||
);
|
||||
const req = this.requests.shift()!;
|
||||
if (this.requests.length === 0) {
|
||||
this.proc.unref();
|
||||
}
|
||||
//logger.info(`got response: ${str}`);
|
||||
req.p.resolve(JSON.parse(str));
|
||||
} else {
|
||||
stdoutChunks.push(x);
|
||||
}
|
||||
} else {
|
||||
throw Error(`unexpected data chunk type (${typeof x})`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async setupRefreshPlanchet(req: {
|
||||
transfer_secret: string;
|
||||
coin_index: number;
|
||||
}): Promise<{
|
||||
coin_pub: string;
|
||||
coin_priv: string;
|
||||
blinding_key: string;
|
||||
}> {
|
||||
return this.queueRequest({
|
||||
op: "setup_refresh_planchet",
|
||||
args: req,
|
||||
});
|
||||
}
|
||||
|
||||
async queueRequest(req: any): Promise<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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The synchronous crypto worker produced by this factory doesn't run in the
|
||||
@ -50,9 +147,14 @@ export class SynchronousCryptoWorker {
|
||||
*/
|
||||
onerror: undefined | ((m: any) => void);
|
||||
|
||||
primitiveWorker: PrimitiveWorker;
|
||||
|
||||
constructor() {
|
||||
this.onerror = undefined;
|
||||
this.onmessage = undefined;
|
||||
if (process.env["TALER_WALLET_PRIMITIVE_WORKER"]) {
|
||||
this.primitiveWorker = new MyPrimitiveWorker();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,7 +182,7 @@ export class SynchronousCryptoWorker {
|
||||
id: number,
|
||||
args: string[],
|
||||
): Promise<void> {
|
||||
const impl = new CryptoImplementation();
|
||||
const impl = new CryptoImplementation(this.primitiveWorker);
|
||||
|
||||
if (!(operation in impl)) {
|
||||
console.error(`crypto operation '${operation}' not found`);
|
||||
@ -89,16 +191,16 @@ export class SynchronousCryptoWorker {
|
||||
|
||||
let result: any;
|
||||
try {
|
||||
result = (impl as any)[operation](...args);
|
||||
result = await (impl as any)[operation](...args);
|
||||
} catch (e) {
|
||||
console.log("error during operation", e);
|
||||
logger.error("error during operation", e);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setTimeout(() => this.dispatchMessage({ result, id }), 0);
|
||||
} catch (e) {
|
||||
console.log("got error during dispatch", e);
|
||||
logger.error("got error during dispatch", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,6 +142,10 @@ export async function getDefaultNodeWallet(
|
||||
const myDb = await openTalerDatabase(myIdbFactory, myVersionChange);
|
||||
|
||||
let workerFactory;
|
||||
if (process.env["TALER_WALLET_SYNC_CRYPTO"]) {
|
||||
logger.info("using synchronous crypto worker");
|
||||
workerFactory = new SynchronousCryptoWorkerFactory();
|
||||
} else {
|
||||
try {
|
||||
// Try if we have worker threads available, fails in older node versions.
|
||||
const _r = "require";
|
||||
@ -154,7 +158,7 @@ export async function getDefaultNodeWallet(
|
||||
);
|
||||
workerFactory = new SynchronousCryptoWorkerFactory();
|
||||
}
|
||||
|
||||
}
|
||||
const w = await Wallet.create(myDb, myHttpLib, workerFactory);
|
||||
|
||||
if (args.notifyHandler) {
|
||||
|
@ -34,7 +34,7 @@ export * from "./db-utils.js";
|
||||
// Crypto and crypto workers
|
||||
// export * from "./crypto/workers/nodeThreadWorker.js";
|
||||
export { CryptoImplementation } from "./crypto/workers/cryptoImplementation.js";
|
||||
export type { CryptoWorker } from "./crypto/workers/cryptoWorker.js";
|
||||
export type { CryptoWorker } from "./crypto/workers/cryptoWorkerInterface.js";
|
||||
export { CryptoWorkerFactory, CryptoApi } from "./crypto/workers/cryptoApi.js";
|
||||
|
||||
export * from "./pending-types.js";
|
||||
|
@ -42,7 +42,7 @@ async function handleRequest(
|
||||
}
|
||||
|
||||
try {
|
||||
const result = (impl as any)[operation](...args);
|
||||
const result = await (impl as any)[operation](...args);
|
||||
worker.postMessage({ result, id });
|
||||
} catch (e) {
|
||||
logger.error("error during operation", e);
|
||||
|
Loading…
Reference in New Issue
Block a user