wallet: db-less benchmarking
This commit is contained in:
parent
eb18c1f179
commit
c0be242292
@ -458,6 +458,16 @@ export interface TalerErrorDetails {
|
||||
details: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal information needed about a planchet for unblinding a signature.
|
||||
*
|
||||
* Can be a withdrawal/tipping/refresh planchet.
|
||||
*/
|
||||
export interface PlanchetUnblindInfo {
|
||||
denomPub: DenominationPubKey;
|
||||
blindingKey: string;
|
||||
}
|
||||
|
||||
export interface WithdrawalPlanchet {
|
||||
coinPub: string;
|
||||
coinPriv: string;
|
||||
|
@ -22,16 +22,20 @@ import {
|
||||
codecForNumber,
|
||||
codecForString,
|
||||
codecOptional,
|
||||
j2s,
|
||||
Logger,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import {
|
||||
getDefaultNodeWallet2,
|
||||
NodeHttpLib,
|
||||
WalletApiOperation,
|
||||
Wallet,
|
||||
AccessStats,
|
||||
checkReserve,
|
||||
createFakebankReserve,
|
||||
CryptoApi,
|
||||
depositCoin,
|
||||
downloadExchangeInfo,
|
||||
findDenomOrThrow,
|
||||
generateReserveKeypair,
|
||||
NodeHttpLib,
|
||||
refreshCoin,
|
||||
SynchronousCryptoWorkerFactory,
|
||||
withdrawCoin,
|
||||
} from "@gnu-taler/taler-wallet-core";
|
||||
|
||||
/**
|
||||
@ -44,15 +48,79 @@ export async function runBench2(configJson: any): Promise<void> {
|
||||
const logger = new Logger("Bench1");
|
||||
|
||||
// Validate the configuration file for this benchmark.
|
||||
const benchConf = codecForBench1Config().decode(configJson);
|
||||
const benchConf = codecForBench2Config().decode(configJson);
|
||||
const curr = benchConf.currency;
|
||||
const cryptoApi = new CryptoApi(new SynchronousCryptoWorkerFactory());
|
||||
|
||||
const myHttpLib = new NodeHttpLib();
|
||||
myHttpLib.setThrottling(false);
|
||||
const http = new NodeHttpLib();
|
||||
http.setThrottling(false);
|
||||
|
||||
const exchangeInfo = await downloadExchangeInfo(
|
||||
benchConf.exchange,
|
||||
myHttpLib,
|
||||
);
|
||||
const numIter = benchConf.iterations ?? 1;
|
||||
const numDeposits = benchConf.deposits ?? 5;
|
||||
|
||||
const reserveAmount = (numDeposits + 1) * 10;
|
||||
|
||||
for (let i = 0; i < numIter; i++) {
|
||||
const exchangeInfo = await downloadExchangeInfo(benchConf.exchange, http);
|
||||
|
||||
const reserveKeyPair = generateReserveKeypair();
|
||||
|
||||
console.log("creating fakebank reserve");
|
||||
|
||||
await createFakebankReserve({
|
||||
amount: `${curr}:${reserveAmount}`,
|
||||
exchangeInfo,
|
||||
fakebankBaseUrl: benchConf.bank,
|
||||
http,
|
||||
reservePub: reserveKeyPair.reservePub,
|
||||
});
|
||||
|
||||
console.log("waiting for reserve");
|
||||
|
||||
await checkReserve(http, benchConf.exchange, reserveKeyPair.reservePub);
|
||||
|
||||
console.log("reserve found");
|
||||
|
||||
const d1 = findDenomOrThrow(exchangeInfo, `${curr}:8`);
|
||||
|
||||
for (let j = 0; j < numDeposits; j++) {
|
||||
console.log("withdrawing coin");
|
||||
const coin = await withdrawCoin({
|
||||
http,
|
||||
cryptoApi,
|
||||
reserveKeyPair,
|
||||
denom: d1,
|
||||
exchangeBaseUrl: benchConf.exchange,
|
||||
});
|
||||
|
||||
console.log("depositing coin");
|
||||
|
||||
await depositCoin({
|
||||
amount: `${curr}:4`,
|
||||
coin: coin,
|
||||
cryptoApi,
|
||||
exchangeBaseUrl: benchConf.exchange,
|
||||
http,
|
||||
depositPayto: benchConf.payto,
|
||||
});
|
||||
|
||||
const refreshDenoms = [
|
||||
findDenomOrThrow(exchangeInfo, `${curr}:1`),
|
||||
findDenomOrThrow(exchangeInfo, `${curr}:1`),
|
||||
];
|
||||
|
||||
console.log("refreshing coin");
|
||||
|
||||
await refreshCoin({
|
||||
oldCoin: coin,
|
||||
cryptoApi,
|
||||
http,
|
||||
newDenoms: refreshDenoms,
|
||||
});
|
||||
|
||||
console.log("refresh done");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,18 +151,12 @@ interface Bench2Config {
|
||||
currency: string;
|
||||
|
||||
deposits?: number;
|
||||
|
||||
/**
|
||||
* How any iterations run until the wallet db gets purged
|
||||
* Defaults to 20.
|
||||
*/
|
||||
restartAfter?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema validation codec for Bench1Config.
|
||||
*/
|
||||
const codecForBench1Config = () =>
|
||||
const codecForBench2Config = () =>
|
||||
buildCodecForObject<Bench2Config>()
|
||||
.property("bank", codecForString())
|
||||
.property("payto", codecForString())
|
||||
@ -102,5 +164,4 @@ const codecForBench1Config = () =>
|
||||
.property("iterations", codecOptional(codecForNumber()))
|
||||
.property("deposits", codecOptional(codecForNumber()))
|
||||
.property("currency", codecForString())
|
||||
.property("restartAfter", codecOptional(codecForNumber()))
|
||||
.build("Bench1Config");
|
||||
.build("Bench2Config");
|
||||
|
@ -62,6 +62,7 @@ import { lintExchangeDeployment } from "./lint.js";
|
||||
import { runBench1 } from "./bench1.js";
|
||||
import { runEnv1 } from "./env1.js";
|
||||
import { GlobalTestState, runTestWithState } from "./harness/harness.js";
|
||||
import { runBench2 } from "./bench2.js";
|
||||
|
||||
// This module also serves as the entry point for the crypto
|
||||
// thread worker, and thus must expose these two handlers.
|
||||
@ -168,8 +169,7 @@ export const walletCli = clk
|
||||
},
|
||||
})
|
||||
.maybeOption("inhibit", ["--inhibit"], clk.STRING, {
|
||||
help:
|
||||
"Inhibit running certain operations, useful for debugging and testing.",
|
||||
help: "Inhibit running certain operations, useful for debugging and testing.",
|
||||
})
|
||||
.flag("noThrottle", ["--no-throttle"], {
|
||||
help: "Don't do any request throttling.",
|
||||
@ -559,8 +559,7 @@ backupCli.subcommand("status", "status").action(async (args) => {
|
||||
backupCli
|
||||
.subcommand("recoveryLoad", "load-recovery")
|
||||
.maybeOption("strategy", ["--strategy"], clk.STRING, {
|
||||
help:
|
||||
"Strategy for resolving a conflict with the existing wallet key ('theirs' or 'ours')",
|
||||
help: "Strategy for resolving a conflict with the existing wallet key ('theirs' or 'ours')",
|
||||
})
|
||||
.action(async (args) => {
|
||||
await withWallet(args, async (wallet) => {
|
||||
@ -636,8 +635,7 @@ depositCli
|
||||
});
|
||||
|
||||
const advancedCli = walletCli.subcommand("advancedArgs", "advanced", {
|
||||
help:
|
||||
"Subcommands for advanced operations (only use if you know what you're doing!).",
|
||||
help: "Subcommands for advanced operations (only use if you know what you're doing!).",
|
||||
});
|
||||
|
||||
advancedCli
|
||||
@ -655,6 +653,21 @@ advancedCli
|
||||
await runBench1(config);
|
||||
});
|
||||
|
||||
advancedCli
|
||||
.subcommand("bench2", "bench2", {
|
||||
help: "Run the 'bench2' benchmark",
|
||||
})
|
||||
.requiredOption("configJson", ["--config-json"], clk.STRING)
|
||||
.action(async (args) => {
|
||||
let config: any;
|
||||
try {
|
||||
config = JSON.parse(args.bench2.configJson);
|
||||
} catch (e) {
|
||||
console.log("Could not parse config JSON");
|
||||
}
|
||||
await runBench2(config);
|
||||
});
|
||||
|
||||
advancedCli
|
||||
.subcommand("env1", "env1", {
|
||||
help: "Run a test environment for bench1",
|
||||
|
@ -17,277 +17,24 @@
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
import { j2s } from "@gnu-taler/taler-util";
|
||||
import {
|
||||
AmountJson,
|
||||
AmountLike,
|
||||
Amounts,
|
||||
AmountString,
|
||||
codecForBankWithdrawalOperationPostResponse,
|
||||
codecForDepositSuccess,
|
||||
codecForExchangeMeltResponse,
|
||||
codecForWithdrawResponse,
|
||||
DenominationPubKey,
|
||||
eddsaGetPublic,
|
||||
encodeCrock,
|
||||
ExchangeMeltRequest,
|
||||
ExchangeProtocolVersion,
|
||||
ExchangeWithdrawRequest,
|
||||
getRandomBytes,
|
||||
getTimestampNow,
|
||||
hashWire,
|
||||
j2s,
|
||||
Timestamp,
|
||||
UnblindedSignature,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import {
|
||||
BankAccessApi,
|
||||
BankApi,
|
||||
BankServiceHandle,
|
||||
checkReserve,
|
||||
CryptoApi,
|
||||
DenominationRecord,
|
||||
depositCoin,
|
||||
downloadExchangeInfo,
|
||||
ExchangeInfo,
|
||||
getBankWithdrawalInfo,
|
||||
HttpRequestLibrary,
|
||||
isWithdrawableDenom,
|
||||
findDenomOrThrow,
|
||||
generateReserveKeypair,
|
||||
NodeHttpLib,
|
||||
OperationFailedError,
|
||||
readSuccessResponseJsonOrThrow,
|
||||
refreshCoin,
|
||||
SynchronousCryptoWorkerFactory,
|
||||
topupReserveWithDemobank,
|
||||
withdrawCoin,
|
||||
} from "@gnu-taler/taler-wallet-core";
|
||||
import { GlobalTestState } from "../harness/harness.js";
|
||||
import { createSimpleTestkudosEnvironment } from "../harness/helpers.js";
|
||||
|
||||
const httpLib = new NodeHttpLib();
|
||||
|
||||
export interface ReserveKeypair {
|
||||
reservePub: string;
|
||||
reservePriv: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Denormalized info about a coin.
|
||||
*/
|
||||
export interface CoinInfo {
|
||||
coinPub: string;
|
||||
coinPriv: string;
|
||||
exchangeBaseUrl: string;
|
||||
denomSig: UnblindedSignature;
|
||||
denomPub: DenominationPubKey;
|
||||
denomPubHash: string;
|
||||
feeDeposit: string;
|
||||
feeRefresh: string;
|
||||
}
|
||||
|
||||
export function generateReserveKeypair(): ReserveKeypair {
|
||||
const priv = getRandomBytes(32);
|
||||
const pub = eddsaGetPublic(priv);
|
||||
return {
|
||||
reservePriv: encodeCrock(priv),
|
||||
reservePub: encodeCrock(pub),
|
||||
};
|
||||
}
|
||||
|
||||
async function topupReserveWithDemobank(
|
||||
reservePub: string,
|
||||
bankBaseUrl: string,
|
||||
exchangeInfo: ExchangeInfo,
|
||||
amount: AmountString,
|
||||
) {
|
||||
const bankHandle: BankServiceHandle = {
|
||||
baseUrl: bankBaseUrl,
|
||||
http: httpLib,
|
||||
};
|
||||
const bankUser = await BankApi.createRandomBankUser(bankHandle);
|
||||
const wopi = await BankAccessApi.createWithdrawalOperation(
|
||||
bankHandle,
|
||||
bankUser,
|
||||
amount,
|
||||
);
|
||||
const bankInfo = await getBankWithdrawalInfo(
|
||||
httpLib,
|
||||
wopi.taler_withdraw_uri,
|
||||
);
|
||||
const bankStatusUrl = bankInfo.extractedStatusUrl;
|
||||
if (!bankInfo.suggestedExchange) {
|
||||
throw Error("no suggested exchange");
|
||||
}
|
||||
const plainPaytoUris =
|
||||
exchangeInfo.wire.accounts.map((x) => x.payto_uri) ?? [];
|
||||
if (plainPaytoUris.length <= 0) {
|
||||
throw new Error();
|
||||
}
|
||||
const httpResp = await httpLib.postJson(bankStatusUrl, {
|
||||
reserve_pub: reservePub,
|
||||
selected_exchange: plainPaytoUris[0],
|
||||
});
|
||||
await readSuccessResponseJsonOrThrow(
|
||||
httpResp,
|
||||
codecForBankWithdrawalOperationPostResponse(),
|
||||
);
|
||||
await BankApi.confirmWithdrawalOperation(bankHandle, bankUser, wopi);
|
||||
}
|
||||
|
||||
async function withdrawCoin(args: {
|
||||
http: HttpRequestLibrary;
|
||||
cryptoApi: CryptoApi;
|
||||
reserveKeyPair: ReserveKeypair;
|
||||
denom: DenominationRecord;
|
||||
exchangeBaseUrl: string;
|
||||
}): Promise<CoinInfo> {
|
||||
const { http, cryptoApi, reserveKeyPair, denom, exchangeBaseUrl } = args;
|
||||
const planchet = await cryptoApi.createPlanchet({
|
||||
coinIndex: 0,
|
||||
denomPub: denom.denomPub,
|
||||
feeWithdraw: denom.feeWithdraw,
|
||||
reservePriv: reserveKeyPair.reservePriv,
|
||||
reservePub: reserveKeyPair.reservePub,
|
||||
secretSeed: encodeCrock(getRandomBytes(32)),
|
||||
value: denom.value,
|
||||
});
|
||||
|
||||
const reqBody: ExchangeWithdrawRequest = {
|
||||
denom_pub_hash: planchet.denomPubHash,
|
||||
reserve_sig: planchet.withdrawSig,
|
||||
coin_ev: planchet.coinEv,
|
||||
};
|
||||
const reqUrl = new URL(
|
||||
`reserves/${planchet.reservePub}/withdraw`,
|
||||
exchangeBaseUrl,
|
||||
).href;
|
||||
|
||||
const resp = await http.postJson(reqUrl, reqBody);
|
||||
const r = await readSuccessResponseJsonOrThrow(
|
||||
resp,
|
||||
codecForWithdrawResponse(),
|
||||
);
|
||||
|
||||
const ubSig = await cryptoApi.unblindDenominationSignature({
|
||||
planchet,
|
||||
evSig: r.ev_sig,
|
||||
});
|
||||
|
||||
return {
|
||||
coinPriv: planchet.coinPriv,
|
||||
coinPub: planchet.coinPub,
|
||||
denomSig: ubSig,
|
||||
denomPub: denom.denomPub,
|
||||
denomPubHash: denom.denomPubHash,
|
||||
feeDeposit: Amounts.stringify(denom.feeDeposit),
|
||||
feeRefresh: Amounts.stringify(denom.feeRefresh),
|
||||
exchangeBaseUrl: args.exchangeBaseUrl,
|
||||
};
|
||||
}
|
||||
|
||||
function findDenomOrThrow(
|
||||
exchangeInfo: ExchangeInfo,
|
||||
amount: AmountString,
|
||||
): DenominationRecord {
|
||||
for (const d of exchangeInfo.keys.currentDenominations) {
|
||||
if (Amounts.cmp(d.value, amount) === 0 && isWithdrawableDenom(d)) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
throw new Error("no matching denomination found");
|
||||
}
|
||||
|
||||
async function depositCoin(args: {
|
||||
http: HttpRequestLibrary;
|
||||
cryptoApi: CryptoApi;
|
||||
exchangeBaseUrl: string;
|
||||
coin: CoinInfo;
|
||||
amount: AmountString;
|
||||
}) {
|
||||
const { coin, http, cryptoApi } = args;
|
||||
const depositPayto = "payto://x-taler-bank/localhost/foo";
|
||||
const wireSalt = encodeCrock(getRandomBytes(16));
|
||||
const contractTermsHash = encodeCrock(getRandomBytes(64));
|
||||
const depositTimestamp = getTimestampNow();
|
||||
const refundDeadline = getTimestampNow();
|
||||
const merchantPub = encodeCrock(getRandomBytes(32));
|
||||
const dp = await cryptoApi.signDepositPermission({
|
||||
coinPriv: coin.coinPriv,
|
||||
coinPub: coin.coinPub,
|
||||
contractTermsHash,
|
||||
denomKeyType: coin.denomPub.cipher,
|
||||
denomPubHash: coin.denomPubHash,
|
||||
denomSig: coin.denomSig,
|
||||
exchangeBaseUrl: args.exchangeBaseUrl,
|
||||
feeDeposit: Amounts.parseOrThrow(coin.feeDeposit),
|
||||
merchantPub,
|
||||
spendAmount: Amounts.parseOrThrow(args.amount),
|
||||
timestamp: depositTimestamp,
|
||||
refundDeadline: refundDeadline,
|
||||
wireInfoHash: hashWire(depositPayto, wireSalt),
|
||||
});
|
||||
const requestBody = {
|
||||
contribution: Amounts.stringify(dp.contribution),
|
||||
merchant_payto_uri: depositPayto,
|
||||
wire_salt: wireSalt,
|
||||
h_contract_terms: contractTermsHash,
|
||||
ub_sig: coin.denomSig,
|
||||
timestamp: depositTimestamp,
|
||||
wire_transfer_deadline: getTimestampNow(),
|
||||
refund_deadline: refundDeadline,
|
||||
coin_sig: dp.coin_sig,
|
||||
denom_pub_hash: dp.h_denom,
|
||||
merchant_pub: merchantPub,
|
||||
};
|
||||
const url = new URL(`coins/${dp.coin_pub}/deposit`, dp.exchange_url);
|
||||
const httpResp = await http.postJson(url.href, requestBody);
|
||||
await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess());
|
||||
}
|
||||
|
||||
async function refreshCoin(req: {
|
||||
http: HttpRequestLibrary;
|
||||
cryptoApi: CryptoApi;
|
||||
oldCoin: CoinInfo;
|
||||
newDenoms: DenominationRecord[];
|
||||
}): Promise<void> {
|
||||
const { cryptoApi, oldCoin, http } = req;
|
||||
const refreshSessionSeed = encodeCrock(getRandomBytes(32));
|
||||
const session = await cryptoApi.deriveRefreshSession({
|
||||
exchangeProtocolVersion: ExchangeProtocolVersion.V12,
|
||||
feeRefresh: Amounts.parseOrThrow(oldCoin.feeRefresh),
|
||||
kappa: 3,
|
||||
meltCoinDenomPubHash: oldCoin.denomPubHash,
|
||||
meltCoinPriv: oldCoin.coinPriv,
|
||||
meltCoinPub: oldCoin.coinPub,
|
||||
sessionSecretSeed: refreshSessionSeed,
|
||||
newCoinDenoms: req.newDenoms.map((x) => ({
|
||||
count: 1,
|
||||
denomPub: x.denomPub,
|
||||
feeWithdraw: x.feeWithdraw,
|
||||
value: x.value,
|
||||
})),
|
||||
});
|
||||
|
||||
const meltReqBody: ExchangeMeltRequest = {
|
||||
coin_pub: oldCoin.coinPub,
|
||||
confirm_sig: session.confirmSig,
|
||||
denom_pub_hash: oldCoin.denomPubHash,
|
||||
denom_sig: oldCoin.denomSig,
|
||||
rc: session.hash,
|
||||
value_with_fee: Amounts.stringify(session.meltValueWithFee),
|
||||
};
|
||||
|
||||
const reqUrl = new URL(
|
||||
`coins/${oldCoin.coinPub}/melt`,
|
||||
oldCoin.exchangeBaseUrl,
|
||||
);
|
||||
|
||||
const resp = await http.postJson(reqUrl.href, meltReqBody);
|
||||
|
||||
const meltResponse = await readSuccessResponseJsonOrThrow(
|
||||
resp,
|
||||
codecForExchangeMeltResponse(),
|
||||
);
|
||||
|
||||
const norevealIndex = meltResponse.noreveal_index;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Run test for basic, bank-integrated withdrawal and payment.
|
||||
*/
|
||||
@ -307,6 +54,7 @@ export async function runWalletDblessTest(t: GlobalTestState) {
|
||||
const reserveKeyPair = generateReserveKeypair();
|
||||
|
||||
await topupReserveWithDemobank(
|
||||
http,
|
||||
reserveKeyPair.reservePub,
|
||||
bank.baseUrl,
|
||||
exchangeInfo,
|
||||
@ -315,6 +63,8 @@ export async function runWalletDblessTest(t: GlobalTestState) {
|
||||
|
||||
await exchange.runWirewatchOnce();
|
||||
|
||||
await checkReserve(http, exchange.baseUrl, reserveKeyPair.reservePub);
|
||||
|
||||
const d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8");
|
||||
|
||||
const coin = await withdrawCoin({
|
||||
@ -338,7 +88,7 @@ export async function runWalletDblessTest(t: GlobalTestState) {
|
||||
findDenomOrThrow(exchangeInfo, "TESTKUDOS:1"),
|
||||
];
|
||||
|
||||
const freshCoins = await refreshCoin({
|
||||
await refreshCoin({
|
||||
oldCoin: coin,
|
||||
cryptoApi,
|
||||
http,
|
||||
|
@ -28,6 +28,8 @@ import {
|
||||
codecForString,
|
||||
encodeCrock,
|
||||
getRandomBytes,
|
||||
j2s,
|
||||
Logger,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import {
|
||||
HttpRequestLibrary,
|
||||
@ -35,6 +37,8 @@ import {
|
||||
readSuccessResponseJsonOrThrow,
|
||||
} from "./index.browser.js";
|
||||
|
||||
const logger = new Logger("bank-api-client.ts");
|
||||
|
||||
export enum CreditDebitIndicator {
|
||||
Credit = "credit",
|
||||
Debit = "debit",
|
||||
@ -98,6 +102,7 @@ export namespace BankApi {
|
||||
const resp = await bank.http.postJson(url.href, { username, password });
|
||||
let paytoUri = `payto://x-taler-bank/localhost/${username}`;
|
||||
if (resp.status !== 200 && resp.status !== 202) {
|
||||
logger.error(`${j2s(await resp.json())}`)
|
||||
throw new Error();
|
||||
}
|
||||
try {
|
||||
|
@ -42,6 +42,7 @@ export interface RefreshNewDenomInfo {
|
||||
value: AmountJson;
|
||||
feeWithdraw: AmountJson;
|
||||
denomPub: DenominationPubKey;
|
||||
denomPubHash: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,6 +30,7 @@ import {
|
||||
BlindedDenominationSignature,
|
||||
CoinDepositPermission,
|
||||
CoinEnvelope,
|
||||
PlanchetUnblindInfo,
|
||||
RecoupRefreshRequest,
|
||||
RecoupRequest,
|
||||
UnblindedSignature,
|
||||
@ -206,7 +207,7 @@ export class CryptoApi {
|
||||
}
|
||||
};
|
||||
ws.terminationTimerHandle = timer.after(15 * 1000, destroy);
|
||||
//ws.terminationTimerHandle.unref();
|
||||
ws.terminationTimerHandle.unref();
|
||||
}
|
||||
|
||||
handleWorkerError(ws: WorkerState, e: any): void {
|
||||
@ -331,7 +332,7 @@ export class CryptoApi {
|
||||
}
|
||||
|
||||
unblindDenominationSignature(req: {
|
||||
planchet: WithdrawalPlanchet;
|
||||
planchet: PlanchetUnblindInfo;
|
||||
evSig: BlindedDenominationSignature;
|
||||
}): Promise<UnblindedSignature> {
|
||||
return this.doRpc<UnblindedSignature>(
|
||||
|
@ -73,6 +73,7 @@ import {
|
||||
BlindedDenominationSignature,
|
||||
RsaUnblindedSignature,
|
||||
UnblindedSignature,
|
||||
PlanchetUnblindInfo,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import bigint from "big-integer";
|
||||
import { DenominationRecord, WireFee } from "../../db.js";
|
||||
@ -432,7 +433,7 @@ export class CryptoImplementation {
|
||||
}
|
||||
|
||||
unblindDenominationSignature(req: {
|
||||
planchet: WithdrawalPlanchet;
|
||||
planchet: PlanchetUnblindInfo;
|
||||
evSig: BlindedDenominationSignature;
|
||||
}): UnblindedSignature {
|
||||
if (req.evSig.cipher === DenomKeyType.Rsa) {
|
||||
|
369
packages/taler-wallet-core/src/dbless.ts
Normal file
369
packages/taler-wallet-core/src/dbless.ts
Normal file
@ -0,0 +1,369 @@
|
||||
/*
|
||||
This file is part of GNU Taler
|
||||
(C) 2021 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/>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper functions to run wallet functionality (withdrawal, deposit, refresh)
|
||||
* without a database or retry loop.
|
||||
*
|
||||
* Used for benchmarking, where we want to benchmark the exchange, but the
|
||||
* normal wallet would be too sluggish.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
import {
|
||||
Amounts,
|
||||
AmountString,
|
||||
codecForAny,
|
||||
codecForBankWithdrawalOperationPostResponse,
|
||||
codecForDepositSuccess,
|
||||
codecForExchangeMeltResponse,
|
||||
codecForExchangeRevealResponse,
|
||||
codecForWithdrawResponse,
|
||||
DenominationPubKey,
|
||||
eddsaGetPublic,
|
||||
encodeCrock,
|
||||
ExchangeMeltRequest,
|
||||
ExchangeProtocolVersion,
|
||||
ExchangeWithdrawRequest,
|
||||
getRandomBytes,
|
||||
getTimestampNow,
|
||||
hashWire,
|
||||
Logger,
|
||||
parsePaytoUri,
|
||||
UnblindedSignature,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { DenominationRecord } from "./db.js";
|
||||
import {
|
||||
assembleRefreshRevealRequest,
|
||||
CryptoApi,
|
||||
ExchangeInfo,
|
||||
getBankWithdrawalInfo,
|
||||
HttpRequestLibrary,
|
||||
isWithdrawableDenom,
|
||||
readSuccessResponseJsonOrThrow,
|
||||
} from "./index.browser.js";
|
||||
import { BankAccessApi, BankApi, BankServiceHandle } from "./index.js";
|
||||
|
||||
const logger = new Logger("dbless.ts");
|
||||
|
||||
export interface ReserveKeypair {
|
||||
reservePub: string;
|
||||
reservePriv: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Denormalized info about a coin.
|
||||
*/
|
||||
export interface CoinInfo {
|
||||
coinPub: string;
|
||||
coinPriv: string;
|
||||
exchangeBaseUrl: string;
|
||||
denomSig: UnblindedSignature;
|
||||
denomPub: DenominationPubKey;
|
||||
denomPubHash: string;
|
||||
feeDeposit: string;
|
||||
feeRefresh: string;
|
||||
}
|
||||
|
||||
export function generateReserveKeypair(): ReserveKeypair {
|
||||
const priv = getRandomBytes(32);
|
||||
const pub = eddsaGetPublic(priv);
|
||||
return {
|
||||
reservePriv: encodeCrock(priv),
|
||||
reservePub: encodeCrock(pub),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the status of a reserve, use long-polling to wait
|
||||
* until the reserve actually has been created.
|
||||
*/
|
||||
export async function checkReserve(
|
||||
http: HttpRequestLibrary,
|
||||
exchangeBaseUrl: string,
|
||||
reservePub: string,
|
||||
longpollTimeoutMs: number = 500,
|
||||
): Promise<void> {
|
||||
const reqUrl = new URL(`reserves/${reservePub}`, exchangeBaseUrl);
|
||||
if (longpollTimeoutMs) {
|
||||
reqUrl.searchParams.set("timeout_ms", `${longpollTimeoutMs}`);
|
||||
}
|
||||
const resp = await http.get(reqUrl.href);
|
||||
if (resp.status !== 200) {
|
||||
throw new Error("reserve not okay");
|
||||
}
|
||||
}
|
||||
|
||||
export async function topupReserveWithDemobank(
|
||||
http: HttpRequestLibrary,
|
||||
reservePub: string,
|
||||
bankBaseUrl: string,
|
||||
exchangeInfo: ExchangeInfo,
|
||||
amount: AmountString,
|
||||
) {
|
||||
const bankHandle: BankServiceHandle = {
|
||||
baseUrl: bankBaseUrl,
|
||||
http,
|
||||
};
|
||||
const bankUser = await BankApi.createRandomBankUser(bankHandle);
|
||||
const wopi = await BankAccessApi.createWithdrawalOperation(
|
||||
bankHandle,
|
||||
bankUser,
|
||||
amount,
|
||||
);
|
||||
const bankInfo = await getBankWithdrawalInfo(http, wopi.taler_withdraw_uri);
|
||||
const bankStatusUrl = bankInfo.extractedStatusUrl;
|
||||
if (!bankInfo.suggestedExchange) {
|
||||
throw Error("no suggested exchange");
|
||||
}
|
||||
const plainPaytoUris =
|
||||
exchangeInfo.wire.accounts.map((x) => x.payto_uri) ?? [];
|
||||
if (plainPaytoUris.length <= 0) {
|
||||
throw new Error();
|
||||
}
|
||||
const httpResp = await http.postJson(bankStatusUrl, {
|
||||
reserve_pub: reservePub,
|
||||
selected_exchange: plainPaytoUris[0],
|
||||
});
|
||||
await readSuccessResponseJsonOrThrow(
|
||||
httpResp,
|
||||
codecForBankWithdrawalOperationPostResponse(),
|
||||
);
|
||||
await BankApi.confirmWithdrawalOperation(bankHandle, bankUser, wopi);
|
||||
}
|
||||
|
||||
export async function withdrawCoin(args: {
|
||||
http: HttpRequestLibrary;
|
||||
cryptoApi: CryptoApi;
|
||||
reserveKeyPair: ReserveKeypair;
|
||||
denom: DenominationRecord;
|
||||
exchangeBaseUrl: string;
|
||||
}): Promise<CoinInfo> {
|
||||
const { http, cryptoApi, reserveKeyPair, denom, exchangeBaseUrl } = args;
|
||||
const planchet = await cryptoApi.createPlanchet({
|
||||
coinIndex: 0,
|
||||
denomPub: denom.denomPub,
|
||||
feeWithdraw: denom.feeWithdraw,
|
||||
reservePriv: reserveKeyPair.reservePriv,
|
||||
reservePub: reserveKeyPair.reservePub,
|
||||
secretSeed: encodeCrock(getRandomBytes(32)),
|
||||
value: denom.value,
|
||||
});
|
||||
|
||||
const reqBody: ExchangeWithdrawRequest = {
|
||||
denom_pub_hash: planchet.denomPubHash,
|
||||
reserve_sig: planchet.withdrawSig,
|
||||
coin_ev: planchet.coinEv,
|
||||
};
|
||||
const reqUrl = new URL(
|
||||
`reserves/${planchet.reservePub}/withdraw`,
|
||||
exchangeBaseUrl,
|
||||
).href;
|
||||
|
||||
const resp = await http.postJson(reqUrl, reqBody);
|
||||
const r = await readSuccessResponseJsonOrThrow(
|
||||
resp,
|
||||
codecForWithdrawResponse(),
|
||||
);
|
||||
|
||||
const ubSig = await cryptoApi.unblindDenominationSignature({
|
||||
planchet,
|
||||
evSig: r.ev_sig,
|
||||
});
|
||||
|
||||
return {
|
||||
coinPriv: planchet.coinPriv,
|
||||
coinPub: planchet.coinPub,
|
||||
denomSig: ubSig,
|
||||
denomPub: denom.denomPub,
|
||||
denomPubHash: denom.denomPubHash,
|
||||
feeDeposit: Amounts.stringify(denom.feeDeposit),
|
||||
feeRefresh: Amounts.stringify(denom.feeRefresh),
|
||||
exchangeBaseUrl: args.exchangeBaseUrl,
|
||||
};
|
||||
}
|
||||
|
||||
export function findDenomOrThrow(
|
||||
exchangeInfo: ExchangeInfo,
|
||||
amount: AmountString,
|
||||
): DenominationRecord {
|
||||
for (const d of exchangeInfo.keys.currentDenominations) {
|
||||
if (Amounts.cmp(d.value, amount) === 0 && isWithdrawableDenom(d)) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
throw new Error("no matching denomination found");
|
||||
}
|
||||
|
||||
export async function depositCoin(args: {
|
||||
http: HttpRequestLibrary;
|
||||
cryptoApi: CryptoApi;
|
||||
exchangeBaseUrl: string;
|
||||
coin: CoinInfo;
|
||||
amount: AmountString;
|
||||
depositPayto?: string;
|
||||
}) {
|
||||
const { coin, http, cryptoApi } = args;
|
||||
const depositPayto =
|
||||
args.depositPayto ?? "payto://x-taler-bank/localhost/foo";
|
||||
const wireSalt = encodeCrock(getRandomBytes(16));
|
||||
const contractTermsHash = encodeCrock(getRandomBytes(64));
|
||||
const depositTimestamp = getTimestampNow();
|
||||
const refundDeadline = getTimestampNow();
|
||||
const merchantPub = encodeCrock(getRandomBytes(32));
|
||||
const dp = await cryptoApi.signDepositPermission({
|
||||
coinPriv: coin.coinPriv,
|
||||
coinPub: coin.coinPub,
|
||||
contractTermsHash,
|
||||
denomKeyType: coin.denomPub.cipher,
|
||||
denomPubHash: coin.denomPubHash,
|
||||
denomSig: coin.denomSig,
|
||||
exchangeBaseUrl: args.exchangeBaseUrl,
|
||||
feeDeposit: Amounts.parseOrThrow(coin.feeDeposit),
|
||||
merchantPub,
|
||||
spendAmount: Amounts.parseOrThrow(args.amount),
|
||||
timestamp: depositTimestamp,
|
||||
refundDeadline: refundDeadline,
|
||||
wireInfoHash: hashWire(depositPayto, wireSalt),
|
||||
});
|
||||
const requestBody = {
|
||||
contribution: Amounts.stringify(dp.contribution),
|
||||
merchant_payto_uri: depositPayto,
|
||||
wire_salt: wireSalt,
|
||||
h_contract_terms: contractTermsHash,
|
||||
ub_sig: coin.denomSig,
|
||||
timestamp: depositTimestamp,
|
||||
wire_transfer_deadline: getTimestampNow(),
|
||||
refund_deadline: refundDeadline,
|
||||
coin_sig: dp.coin_sig,
|
||||
denom_pub_hash: dp.h_denom,
|
||||
merchant_pub: merchantPub,
|
||||
};
|
||||
const url = new URL(`coins/${dp.coin_pub}/deposit`, dp.exchange_url);
|
||||
const httpResp = await http.postJson(url.href, requestBody);
|
||||
await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess());
|
||||
}
|
||||
|
||||
export async function refreshCoin(req: {
|
||||
http: HttpRequestLibrary;
|
||||
cryptoApi: CryptoApi;
|
||||
oldCoin: CoinInfo;
|
||||
newDenoms: DenominationRecord[];
|
||||
}): Promise<void> {
|
||||
const { cryptoApi, oldCoin, http } = req;
|
||||
const refreshSessionSeed = encodeCrock(getRandomBytes(32));
|
||||
const session = await cryptoApi.deriveRefreshSession({
|
||||
exchangeProtocolVersion: ExchangeProtocolVersion.V12,
|
||||
feeRefresh: Amounts.parseOrThrow(oldCoin.feeRefresh),
|
||||
kappa: 3,
|
||||
meltCoinDenomPubHash: oldCoin.denomPubHash,
|
||||
meltCoinPriv: oldCoin.coinPriv,
|
||||
meltCoinPub: oldCoin.coinPub,
|
||||
sessionSecretSeed: refreshSessionSeed,
|
||||
newCoinDenoms: req.newDenoms.map((x) => ({
|
||||
count: 1,
|
||||
denomPub: x.denomPub,
|
||||
denomPubHash: x.denomPubHash,
|
||||
feeWithdraw: x.feeWithdraw,
|
||||
value: x.value,
|
||||
})),
|
||||
});
|
||||
|
||||
const meltReqBody: ExchangeMeltRequest = {
|
||||
coin_pub: oldCoin.coinPub,
|
||||
confirm_sig: session.confirmSig,
|
||||
denom_pub_hash: oldCoin.denomPubHash,
|
||||
denom_sig: oldCoin.denomSig,
|
||||
rc: session.hash,
|
||||
value_with_fee: Amounts.stringify(session.meltValueWithFee),
|
||||
};
|
||||
|
||||
logger.info("requesting melt");
|
||||
|
||||
const meltReqUrl = new URL(
|
||||
`coins/${oldCoin.coinPub}/melt`,
|
||||
oldCoin.exchangeBaseUrl,
|
||||
);
|
||||
|
||||
logger.info("requesting melt done");
|
||||
|
||||
const meltHttpResp = await http.postJson(meltReqUrl.href, meltReqBody);
|
||||
|
||||
const meltResponse = await readSuccessResponseJsonOrThrow(
|
||||
meltHttpResp,
|
||||
codecForExchangeMeltResponse(),
|
||||
);
|
||||
|
||||
const norevealIndex = meltResponse.noreveal_index;
|
||||
|
||||
const revealRequest = await assembleRefreshRevealRequest({
|
||||
cryptoApi,
|
||||
derived: session,
|
||||
newDenoms: req.newDenoms.map((x) => ({
|
||||
count: 1,
|
||||
denomPubHash: x.denomPubHash,
|
||||
})),
|
||||
norevealIndex,
|
||||
oldCoinPriv: oldCoin.coinPriv,
|
||||
oldCoinPub: oldCoin.coinPub,
|
||||
});
|
||||
|
||||
logger.info("requesting reveal");
|
||||
const reqUrl = new URL(
|
||||
`refreshes/${session.hash}/reveal`,
|
||||
oldCoin.exchangeBaseUrl,
|
||||
);
|
||||
|
||||
const revealResp = await http.postJson(reqUrl.href, revealRequest);
|
||||
|
||||
logger.info("requesting reveal done");
|
||||
|
||||
const reveal = await readSuccessResponseJsonOrThrow(
|
||||
revealResp,
|
||||
codecForExchangeRevealResponse(),
|
||||
);
|
||||
|
||||
// We could unblind here, but we only use this function to
|
||||
// benchmark the exchange.
|
||||
}
|
||||
|
||||
export async function createFakebankReserve(args: {
|
||||
http: HttpRequestLibrary;
|
||||
fakebankBaseUrl: string;
|
||||
amount: string;
|
||||
reservePub: string;
|
||||
exchangeInfo: ExchangeInfo;
|
||||
}): Promise<void> {
|
||||
const { http, fakebankBaseUrl, amount, reservePub } = args;
|
||||
const paytoUri = args.exchangeInfo.wire.accounts[0].payto_uri;
|
||||
const pt = parsePaytoUri(paytoUri);
|
||||
if (!pt) {
|
||||
throw Error("failed to parse payto URI");
|
||||
}
|
||||
const components = pt.targetPath.split("/");
|
||||
const creditorAcct = components[components.length - 1];
|
||||
const fbReq = await http.postJson(
|
||||
new URL(`${creditorAcct}/admin/add-incoming`, fakebankBaseUrl).href,
|
||||
{
|
||||
amount,
|
||||
reserve_pub: reservePub,
|
||||
debit_account: "payto://x-taler-bank/localhost/testdebtor",
|
||||
},
|
||||
);
|
||||
const fbResp = await readSuccessResponseJsonOrThrow(fbReq, codecForAny());
|
||||
}
|
@ -54,4 +54,7 @@ export * from "./bank-api-client.js";
|
||||
|
||||
export * from "./operations/reserves.js";
|
||||
export * from "./operations/withdraw.js";
|
||||
export * from "./operations/refresh.js";
|
||||
|
||||
export * from "./dbless.js";
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
CoinPublicKeyString,
|
||||
DenomKeyType,
|
||||
encodeCrock,
|
||||
ExchangeMeltRequest,
|
||||
@ -79,8 +80,12 @@ import {
|
||||
isWithdrawableDenom,
|
||||
selectWithdrawalDenominations,
|
||||
} from "./withdraw.js";
|
||||
import { RefreshNewDenomInfo } from "../crypto/cryptoTypes.js";
|
||||
import {
|
||||
DerivedRefreshSession,
|
||||
RefreshNewDenomInfo,
|
||||
} from "../crypto/cryptoTypes.js";
|
||||
import { GetReadWriteAccess } from "../util/query.js";
|
||||
import { CryptoApi } from "../index.browser.js";
|
||||
|
||||
const logger = new Logger("refresh.ts");
|
||||
|
||||
@ -357,6 +362,7 @@ async function refreshMelt(
|
||||
newCoinDenoms.push({
|
||||
count: dh.count,
|
||||
denomPub: newDenom.denomPub,
|
||||
denomPubHash: newDenom.denomPubHash,
|
||||
feeWithdraw: newDenom.feeWithdraw,
|
||||
value: newDenom.value,
|
||||
});
|
||||
@ -472,6 +478,62 @@ async function refreshMelt(
|
||||
});
|
||||
}
|
||||
|
||||
export async function assembleRefreshRevealRequest(args: {
|
||||
cryptoApi: CryptoApi;
|
||||
derived: DerivedRefreshSession;
|
||||
norevealIndex: number;
|
||||
oldCoinPub: CoinPublicKeyString;
|
||||
oldCoinPriv: string;
|
||||
newDenoms: {
|
||||
denomPubHash: string;
|
||||
count: number;
|
||||
}[];
|
||||
}): Promise<ExchangeRefreshRevealRequest> {
|
||||
const {
|
||||
derived,
|
||||
norevealIndex,
|
||||
cryptoApi,
|
||||
oldCoinPriv,
|
||||
oldCoinPub,
|
||||
newDenoms,
|
||||
} = args;
|
||||
const privs = Array.from(derived.transferPrivs);
|
||||
privs.splice(norevealIndex, 1);
|
||||
|
||||
const planchets = derived.planchetsForGammas[norevealIndex];
|
||||
if (!planchets) {
|
||||
throw Error("refresh index error");
|
||||
}
|
||||
|
||||
const newDenomsFlat: string[] = [];
|
||||
const linkSigs: string[] = [];
|
||||
|
||||
for (let i = 0; i < newDenoms.length; i++) {
|
||||
const dsel = newDenoms[i];
|
||||
for (let j = 0; j < dsel.count; j++) {
|
||||
const newCoinIndex = linkSigs.length;
|
||||
const linkSig = await cryptoApi.signCoinLink(
|
||||
oldCoinPriv,
|
||||
dsel.denomPubHash,
|
||||
oldCoinPub,
|
||||
derived.transferPubs[norevealIndex],
|
||||
planchets[newCoinIndex].coinEv,
|
||||
);
|
||||
linkSigs.push(linkSig);
|
||||
newDenomsFlat.push(dsel.denomPubHash);
|
||||
}
|
||||
}
|
||||
|
||||
const req: ExchangeRefreshRevealRequest = {
|
||||
coin_evs: planchets.map((x) => x.coinEv),
|
||||
new_denoms_h: newDenomsFlat,
|
||||
transfer_privs: privs,
|
||||
transfer_pub: derived.transferPubs[norevealIndex],
|
||||
link_sigs: linkSigs,
|
||||
};
|
||||
return req;
|
||||
}
|
||||
|
||||
async function refreshReveal(
|
||||
ws: InternalWalletState,
|
||||
refreshGroupId: string,
|
||||
@ -527,6 +589,7 @@ async function refreshReveal(
|
||||
newCoinDenoms.push({
|
||||
count: dh.count,
|
||||
denomPub: newDenom.denomPub,
|
||||
denomPubHash: newDenom.denomPubHash,
|
||||
feeWithdraw: newDenom.feeWithdraw,
|
||||
value: newDenom.value,
|
||||
});
|
||||
@ -575,46 +638,20 @@ async function refreshReveal(
|
||||
sessionSecretSeed: refreshSession.sessionSecretSeed,
|
||||
});
|
||||
|
||||
const privs = Array.from(derived.transferPrivs);
|
||||
privs.splice(norevealIndex, 1);
|
||||
|
||||
const planchets = derived.planchetsForGammas[norevealIndex];
|
||||
if (!planchets) {
|
||||
throw Error("refresh index error");
|
||||
}
|
||||
|
||||
const newDenomsFlat: string[] = [];
|
||||
const linkSigs: string[] = [];
|
||||
|
||||
for (let i = 0; i < refreshSession.newDenoms.length; i++) {
|
||||
const dsel = refreshSession.newDenoms[i];
|
||||
for (let j = 0; j < dsel.count; j++) {
|
||||
const newCoinIndex = linkSigs.length;
|
||||
const linkSig = await ws.cryptoApi.signCoinLink(
|
||||
oldCoin.coinPriv,
|
||||
dsel.denomPubHash,
|
||||
oldCoin.coinPub,
|
||||
derived.transferPubs[norevealIndex],
|
||||
planchets[newCoinIndex].coinEv,
|
||||
);
|
||||
linkSigs.push(linkSig);
|
||||
newDenomsFlat.push(dsel.denomPubHash);
|
||||
}
|
||||
}
|
||||
|
||||
const req: ExchangeRefreshRevealRequest = {
|
||||
coin_evs: planchets.map((x) => x.coinEv),
|
||||
new_denoms_h: newDenomsFlat,
|
||||
transfer_privs: privs,
|
||||
transfer_pub: derived.transferPubs[norevealIndex],
|
||||
link_sigs: linkSigs,
|
||||
};
|
||||
|
||||
const reqUrl = new URL(
|
||||
`refreshes/${derived.hash}/reveal`,
|
||||
oldCoin.exchangeBaseUrl,
|
||||
);
|
||||
|
||||
const req = await assembleRefreshRevealRequest({
|
||||
cryptoApi: ws.cryptoApi,
|
||||
derived,
|
||||
newDenoms: newCoinDenoms,
|
||||
norevealIndex: norevealIndex,
|
||||
oldCoinPriv: oldCoin.coinPriv,
|
||||
oldCoinPub: oldCoin.coinPub,
|
||||
});
|
||||
|
||||
const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], async () => {
|
||||
return await ws.http.postJson(reqUrl.href, req, {
|
||||
timeout: getRefreshRequestTimeout(refreshGroup),
|
||||
@ -629,51 +666,28 @@ async function refreshReveal(
|
||||
const coins: CoinRecord[] = [];
|
||||
|
||||
for (let i = 0; i < refreshSession.newDenoms.length; i++) {
|
||||
const ncd = newCoinDenoms[i];
|
||||
for (let j = 0; j < refreshSession.newDenoms[i].count; j++) {
|
||||
const newCoinIndex = coins.length;
|
||||
// FIXME: Look up in earlier transaction!
|
||||
const denom = await ws.db
|
||||
.mktx((x) => ({
|
||||
denominations: x.denominations,
|
||||
}))
|
||||
.runReadOnly(async (tx) => {
|
||||
return tx.denominations.get([
|
||||
oldCoin.exchangeBaseUrl,
|
||||
refreshSession.newDenoms[i].denomPubHash,
|
||||
]);
|
||||
});
|
||||
if (!denom) {
|
||||
console.error("denom not found");
|
||||
continue;
|
||||
}
|
||||
const pc = derived.planchetsForGammas[norevealIndex][newCoinIndex];
|
||||
if (denom.denomPub.cipher !== DenomKeyType.Rsa) {
|
||||
if (ncd.denomPub.cipher !== DenomKeyType.Rsa) {
|
||||
throw Error("cipher unsupported");
|
||||
}
|
||||
const evSig = reveal.ev_sigs[newCoinIndex].ev_sig;
|
||||
let rsaSig: string;
|
||||
if (typeof evSig === "string") {
|
||||
rsaSig = evSig;
|
||||
} else if (evSig.cipher === DenomKeyType.Rsa) {
|
||||
rsaSig = evSig.blinded_rsa_signature;
|
||||
} else {
|
||||
throw Error("unsupported cipher");
|
||||
}
|
||||
const denomSigRsa = await ws.cryptoApi.rsaUnblind(
|
||||
rsaSig,
|
||||
pc.blindingKey,
|
||||
denom.denomPub.rsa_public_key,
|
||||
);
|
||||
const denomSig = await ws.cryptoApi.unblindDenominationSignature({
|
||||
planchet: {
|
||||
blindingKey: pc.blindingKey,
|
||||
denomPub: ncd.denomPub,
|
||||
},
|
||||
evSig,
|
||||
});
|
||||
const coin: CoinRecord = {
|
||||
blindingKey: pc.blindingKey,
|
||||
coinPriv: pc.coinPriv,
|
||||
coinPub: pc.coinPub,
|
||||
currentAmount: denom.value,
|
||||
denomPubHash: denom.denomPubHash,
|
||||
denomSig: {
|
||||
cipher: DenomKeyType.Rsa,
|
||||
rsa_signature: denomSigRsa,
|
||||
},
|
||||
currentAmount: ncd.value,
|
||||
denomPubHash: ncd.denomPubHash,
|
||||
denomSig,
|
||||
exchangeBaseUrl: oldCoin.exchangeBaseUrl,
|
||||
status: CoinStatus.Fresh,
|
||||
coinSource: {
|
||||
|
Loading…
Reference in New Issue
Block a user