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,
|
stringToBytes,
|
||||||
secretbox_open,
|
secretbox_open,
|
||||||
hash,
|
hash,
|
||||||
Logger,
|
|
||||||
j2s,
|
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { argon2id } from "hash-wasm";
|
import { argon2id } from "hash-wasm";
|
||||||
|
|
||||||
|
@ -161,10 +161,6 @@ interface RsaPub {
|
|||||||
e: bigint.BigInteger;
|
e: bigint.BigInteger;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RsaBlindingKey {
|
|
||||||
r: bigint.BigInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KDF modulo a big integer.
|
* KDF modulo a big integer.
|
||||||
*/
|
*/
|
||||||
|
@ -40,6 +40,7 @@ export async function runBench1(configJson: any): Promise<void> {
|
|||||||
const b1conf = codecForBench1Config().decode(configJson);
|
const b1conf = codecForBench1Config().decode(configJson);
|
||||||
|
|
||||||
const myHttpLib = new NodeHttpLib();
|
const myHttpLib = new NodeHttpLib();
|
||||||
|
myHttpLib.setThrottling(false);
|
||||||
|
|
||||||
const numIter = b1conf.iterations ?? 1;
|
const numIter = b1conf.iterations ?? 1;
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
import { CoinRecord, DenominationRecord, WireFee } from "../../db.js";
|
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";
|
import { RecoupRequest, CoinDepositPermission } from "@gnu-taler/taler-util";
|
||||||
|
|
||||||
|
@ -15,9 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronous implementation of crypto-related functions for the wallet.
|
* Implementation of crypto-related high-level functions for the Taler wallet.
|
||||||
*
|
|
||||||
* The functionality is parameterized over an Emscripten environment.
|
|
||||||
*
|
*
|
||||||
* @author Florian Dold <dold@taler.net>
|
* @author Florian Dold <dold@taler.net>
|
||||||
*/
|
*/
|
||||||
@ -37,9 +35,9 @@ import {
|
|||||||
import {
|
import {
|
||||||
buildSigPS,
|
buildSigPS,
|
||||||
CoinDepositPermission,
|
CoinDepositPermission,
|
||||||
|
FreshCoin,
|
||||||
RecoupRequest,
|
RecoupRequest,
|
||||||
RefreshPlanchetInfo,
|
RefreshPlanchetInfo,
|
||||||
SignaturePurposeBuilder,
|
|
||||||
TalerSignaturePurpose,
|
TalerSignaturePurpose,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
// FIXME: These types should be internal to the wallet!
|
// FIXME: These types should be internal to the wallet!
|
||||||
@ -128,9 +126,27 @@ function timestampRoundedToBuffer(ts: Timestamp): Uint8Array {
|
|||||||
return new Uint8Array(b);
|
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 {
|
export class CryptoImplementation {
|
||||||
static enableTracing = false;
|
static enableTracing = false;
|
||||||
|
|
||||||
|
constructor(private primitiveWorker?: PrimitiveWorker) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@ -246,7 +262,11 @@ export class CryptoImplementation {
|
|||||||
/**
|
/**
|
||||||
* Check if a wire fee is correctly signed.
|
* 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)
|
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))
|
||||||
@ -256,13 +276,25 @@ 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 (
|
||||||
|
await this.primitiveWorker.eddsaVerify({
|
||||||
|
msg: encodeCrock(p),
|
||||||
|
pub: masterPub,
|
||||||
|
sig: encodeCrock(sig),
|
||||||
|
})
|
||||||
|
).valid;
|
||||||
|
}
|
||||||
return eddsaVerify(p, sig, pub);
|
return eddsaVerify(p, sig, pub);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the signature of a denomination is valid.
|
* 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)
|
const p = buildSigPS(TalerSignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY)
|
||||||
.put(decodeCrock(masterPub))
|
.put(decodeCrock(masterPub))
|
||||||
.put(timestampRoundedToBuffer(denom.stampStart))
|
.put(timestampRoundedToBuffer(denom.stampStart))
|
||||||
@ -377,9 +409,9 @@ export class CryptoImplementation {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
deriveRefreshSession(
|
async deriveRefreshSession(
|
||||||
req: DeriveRefreshSessionRequest,
|
req: DeriveRefreshSessionRequest,
|
||||||
): DerivedRefreshSession {
|
): Promise<DerivedRefreshSession> {
|
||||||
const {
|
const {
|
||||||
newCoinDenoms,
|
newCoinDenoms,
|
||||||
feeRefresh: meltFee,
|
feeRefresh: meltFee,
|
||||||
@ -435,17 +467,33 @@ export class CryptoImplementation {
|
|||||||
for (let j = 0; j < newCoinDenoms.length; j++) {
|
for (let j = 0; j < newCoinDenoms.length; j++) {
|
||||||
const denomSel = newCoinDenoms[j];
|
const denomSel = newCoinDenoms[j];
|
||||||
for (let k = 0; k < denomSel.count; k++) {
|
for (let k = 0; k < denomSel.count; k++) {
|
||||||
const coinNumber = planchets.length;
|
const coinIndex = planchets.length;
|
||||||
const transferPriv = decodeCrock(transferPrivs[i]);
|
const transferPriv = decodeCrock(transferPrivs[i]);
|
||||||
const oldCoinPub = decodeCrock(meltCoinPub);
|
const oldCoinPub = decodeCrock(meltCoinPub);
|
||||||
const transferSecret = keyExchangeEcdheEddsa(
|
const transferSecret = keyExchangeEcdheEddsa(
|
||||||
transferPriv,
|
transferPriv,
|
||||||
oldCoinPub,
|
oldCoinPub,
|
||||||
);
|
);
|
||||||
const fresh = setupRefreshPlanchet(transferSecret, coinNumber);
|
let coinPub: Uint8Array;
|
||||||
const coinPriv = fresh.coinPriv;
|
let coinPriv: Uint8Array;
|
||||||
const coinPub = fresh.coinPub;
|
let blindingFactor: Uint8Array;
|
||||||
const blindingFactor = fresh.bks;
|
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 pubHash = hash(coinPub);
|
||||||
const denomPub = decodeCrock(denomSel.denomPub);
|
const denomPub = decodeCrock(denomSel.denomPub);
|
||||||
const ev = rsaBlind(pubHash, blindingFactor, denomPub);
|
const ev = rsaBlind(pubHash, blindingFactor, denomPub);
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
* Imports
|
* Imports
|
||||||
*/
|
*/
|
||||||
import { CryptoWorkerFactory } from "./cryptoApi.js";
|
import { CryptoWorkerFactory } from "./cryptoApi.js";
|
||||||
import { CryptoWorker } from "./cryptoWorker.js";
|
import { CryptoWorker } from "./cryptoWorkerInterface.js";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import { CryptoImplementation } from "./cryptoImplementation.js";
|
import { CryptoImplementation } from "./cryptoImplementation.js";
|
||||||
import { Logger } from "@gnu-taler/taler-util";
|
import { Logger } from "@gnu-taler/taler-util";
|
||||||
@ -94,7 +94,7 @@ export function handleWorkerMessage(msg: any): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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
|
// 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") = module[_r](
|
||||||
|
@ -14,10 +14,107 @@
|
|||||||
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 { CryptoImplementation } from "./cryptoImplementation.js";
|
import {
|
||||||
|
CryptoImplementation,
|
||||||
|
PrimitiveWorker,
|
||||||
|
} from "./cryptoImplementation.js";
|
||||||
|
|
||||||
import { CryptoWorkerFactory } from "./cryptoApi.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
|
* 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);
|
onerror: undefined | ((m: any) => void);
|
||||||
|
|
||||||
|
primitiveWorker: PrimitiveWorker;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.onerror = undefined;
|
this.onerror = undefined;
|
||||||
this.onmessage = undefined;
|
this.onmessage = undefined;
|
||||||
|
if (process.env["TALER_WALLET_PRIMITIVE_WORKER"]) {
|
||||||
|
this.primitiveWorker = new MyPrimitiveWorker();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,7 +182,7 @@ export class SynchronousCryptoWorker {
|
|||||||
id: number,
|
id: number,
|
||||||
args: string[],
|
args: string[],
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const impl = new CryptoImplementation();
|
const impl = new CryptoImplementation(this.primitiveWorker);
|
||||||
|
|
||||||
if (!(operation in impl)) {
|
if (!(operation in impl)) {
|
||||||
console.error(`crypto operation '${operation}' not found`);
|
console.error(`crypto operation '${operation}' not found`);
|
||||||
@ -89,16 +191,16 @@ export class SynchronousCryptoWorker {
|
|||||||
|
|
||||||
let result: any;
|
let result: any;
|
||||||
try {
|
try {
|
||||||
result = (impl as any)[operation](...args);
|
result = await (impl as any)[operation](...args);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("error during operation", e);
|
logger.error("error during operation", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setTimeout(() => this.dispatchMessage({ result, id }), 0);
|
setTimeout(() => this.dispatchMessage({ result, id }), 0);
|
||||||
} catch (e) {
|
} 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);
|
const myDb = await openTalerDatabase(myIdbFactory, myVersionChange);
|
||||||
|
|
||||||
let workerFactory;
|
let workerFactory;
|
||||||
|
if (process.env["TALER_WALLET_SYNC_CRYPTO"]) {
|
||||||
|
logger.info("using synchronous crypto worker");
|
||||||
|
workerFactory = new SynchronousCryptoWorkerFactory();
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
// Try if we have worker threads available, fails in older node versions.
|
// Try if we have worker threads available, fails in older node versions.
|
||||||
const _r = "require";
|
const _r = "require";
|
||||||
@ -154,7 +158,7 @@ export async function getDefaultNodeWallet(
|
|||||||
);
|
);
|
||||||
workerFactory = new SynchronousCryptoWorkerFactory();
|
workerFactory = new SynchronousCryptoWorkerFactory();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const w = await Wallet.create(myDb, myHttpLib, workerFactory);
|
const w = await Wallet.create(myDb, myHttpLib, workerFactory);
|
||||||
|
|
||||||
if (args.notifyHandler) {
|
if (args.notifyHandler) {
|
||||||
|
@ -34,7 +34,7 @@ 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 { 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 { CryptoWorkerFactory, CryptoApi } from "./crypto/workers/cryptoApi.js";
|
||||||
|
|
||||||
export * from "./pending-types.js";
|
export * from "./pending-types.js";
|
||||||
|
@ -42,7 +42,7 @@ async function handleRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = (impl as any)[operation](...args);
|
const result = await (impl as any)[operation](...args);
|
||||||
worker.postMessage({ result, id });
|
worker.postMessage({ result, id });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("error during operation", e);
|
logger.error("error during operation", e);
|
||||||
|
Loading…
Reference in New Issue
Block a user