From a05e891d6e1468fdd99f710301e286857a46aea3 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 11 Jan 2022 12:48:32 +0100 Subject: towards new recoup API --- packages/taler-util/src/talerCrypto.ts | 1 + packages/taler-util/src/talerTypes.ts | 310 ++++++++++++--------- packages/taler-wallet-cli/src/harness/harness.ts | 8 +- packages/taler-wallet-cli/src/index.ts | 8 +- .../taler-wallet-core/src/crypto/cryptoTypes.ts | 34 ++- .../src/crypto/workers/cryptoApi.ts | 22 +- .../src/crypto/workers/cryptoImplementation.ts | 73 +++-- .../taler-wallet-core/src/operations/exchanges.ts | 4 +- .../taler-wallet-core/src/operations/recoup.ts | 37 ++- .../taler-wallet-core/src/operations/reserves.ts | 10 +- 10 files changed, 326 insertions(+), 181 deletions(-) diff --git a/packages/taler-util/src/talerCrypto.ts b/packages/taler-util/src/talerCrypto.ts index d96c23236..5f1c2e8cc 100644 --- a/packages/taler-util/src/talerCrypto.ts +++ b/packages/taler-util/src/talerCrypto.ts @@ -479,6 +479,7 @@ export enum TalerSignaturePurpose { MERCHANT_CONTRACT = 1101, WALLET_COIN_RECOUP = 1203, WALLET_COIN_LINK = 1204, + WALLET_COIN_RECOUP_REFRESH = 1206, EXCHANGE_CONFIRM_RECOUP = 1039, EXCHANGE_CONFIRM_RECOUP_REFRESH = 1041, ANASTASIS_POLICY_UPLOAD = 1400, diff --git a/packages/taler-util/src/talerTypes.ts b/packages/taler-util/src/talerTypes.ts index e7d8a8c18..5f72e08ce 100644 --- a/packages/taler-util/src/talerTypes.ts +++ b/packages/taler-util/src/talerTypes.ts @@ -46,7 +46,7 @@ import { Duration, codecForDuration, } from "./time.js"; -import { codecForAmountString } from "./amounts.js"; +import { Amounts, codecForAmountString } from "./amounts.js"; /** * Denomination as found in the /keys response from the exchange. @@ -149,9 +149,6 @@ export class ExchangeAuditor { denomination_keys: AuditorDenomSig[]; } -/** - * Request that we send to the exchange to get a payback. - */ export interface RecoupRequest { /** * Hashed enomination public key of the coin we want to get @@ -161,31 +158,57 @@ export interface RecoupRequest { /** * Signature over the coin public key by the denomination. - * + * * The string variant is for the legacy exchange protocol. */ denom_sig: UnblindedSignature | string; /** - * Coin public key of the coin we want to refund. + * Blinding key that was used during withdraw, + * used to prove that we were actually withdrawing the coin. */ - coin_pub: string; + coin_blind_key_secret: string; /** - * Blinding key that was used during withdraw, - * used to prove that we were actually withdrawing the coin. + * Signature of TALER_RecoupRequestPS created with the coin's private key. + */ + coin_sig: string; + + /** + * Amount being recouped. + */ + amount: AmountString; +} + +export interface RecoupRefreshRequest { + /** + * Hashed enomination public key of the coin we want to get + * paid back. + */ + denom_pub_hash: string; + + /** + * Signature over the coin public key by the denomination. + * + * The string variant is for the legacy exchange protocol. + */ + denom_sig: UnblindedSignature | string; + + /** + * Coin's blinding factor. */ coin_blind_key_secret: string; /** - * Signature made by the coin, authorizing the payback. + * Signature of TALER_RecoupRefreshRequestPS created with + * the coin's private key. */ coin_sig: string; /** - * Was the coin refreshed (and thus the recoup should go to the old coin)? + * Amount being recouped. */ - refreshed: boolean; + amount: AmountString; } /** @@ -1131,10 +1154,11 @@ export const codecForLegacyRsaDenominationPubKey = () => .property("rsa_public_key", codecForString()) .build("LegacyRsaDenominationPubKey"); -export const codecForBankWithdrawalOperationPostResponse = (): Codec => - buildCodecForObject() - .property("transfer_done", codecForBoolean()) - .build("BankWithdrawalOperationPostResponse"); +export const codecForBankWithdrawalOperationPostResponse = + (): Codec => + buildCodecForObject() + .property("transfer_done", codecForBoolean()) + .build("BankWithdrawalOperationPostResponse"); export type AmountString = string; export type Base32String = string; @@ -1213,8 +1237,8 @@ export const codecForTax = (): Codec => .property("tax", codecForString()) .build("Tax"); -export const codecForInternationalizedString = (): Codec => - codecForMap(codecForString()); +export const codecForInternationalizedString = + (): Codec => codecForMap(codecForString()); export const codecForProduct = (): Codec => buildCodecForObject() @@ -1262,30 +1286,33 @@ export const codecForContractTerms = (): Codec => .property("extra", codecForAny()) .build("ContractTerms"); -export const codecForMerchantRefundPermission = (): Codec => - buildCodecForObject() - .property("refund_amount", codecForAmountString()) - .property("refund_fee", codecForAmountString()) - .property("coin_pub", codecForString()) - .property("rtransaction_id", codecForNumber()) - .property("exchange_http_status", codecForNumber()) - .property("exchange_code", codecOptional(codecForNumber())) - .property("exchange_reply", codecOptional(codecForAny())) - .property("exchange_sig", codecOptional(codecForString())) - .property("exchange_pub", codecOptional(codecForString())) - .build("MerchantRefundPermission"); - -export const codecForMerchantRefundResponse = (): Codec => - buildCodecForObject() - .property("merchant_pub", codecForString()) - .property("h_contract_terms", codecForString()) - .property("refunds", codecForList(codecForMerchantRefundPermission())) - .build("MerchantRefundResponse"); - -export const codecForMerchantBlindSigWrapperV1 = (): Codec => - buildCodecForObject() - .property("blind_sig", codecForString()) - .build("BlindSigWrapper"); +export const codecForMerchantRefundPermission = + (): Codec => + buildCodecForObject() + .property("refund_amount", codecForAmountString()) + .property("refund_fee", codecForAmountString()) + .property("coin_pub", codecForString()) + .property("rtransaction_id", codecForNumber()) + .property("exchange_http_status", codecForNumber()) + .property("exchange_code", codecOptional(codecForNumber())) + .property("exchange_reply", codecOptional(codecForAny())) + .property("exchange_sig", codecOptional(codecForString())) + .property("exchange_pub", codecOptional(codecForString())) + .build("MerchantRefundPermission"); + +export const codecForMerchantRefundResponse = + (): Codec => + buildCodecForObject() + .property("merchant_pub", codecForString()) + .property("h_contract_terms", codecForString()) + .property("refunds", codecForList(codecForMerchantRefundPermission())) + .build("MerchantRefundResponse"); + +export const codecForMerchantBlindSigWrapperV1 = + (): Codec => + buildCodecForObject() + .property("blind_sig", codecForString()) + .build("BlindSigWrapper"); export const codecForMerchantTipResponseV1 = (): Codec => buildCodecForObject() @@ -1365,17 +1392,18 @@ export const codecForCheckPaymentResponse = (): Codec => .property("contract_url", codecOptional(codecForString())) .build("CheckPaymentResponse"); -export const codecForWithdrawOperationStatusResponse = (): Codec => - buildCodecForObject() - .property("selection_done", codecForBoolean()) - .property("transfer_done", codecForBoolean()) - .property("aborted", codecForBoolean()) - .property("amount", codecForString()) - .property("sender_wire", codecOptional(codecForString())) - .property("suggested_exchange", codecOptional(codecForString())) - .property("confirm_transfer_url", codecOptional(codecForString())) - .property("wire_types", codecForList(codecForString())) - .build("WithdrawOperationStatusResponse"); +export const codecForWithdrawOperationStatusResponse = + (): Codec => + buildCodecForObject() + .property("selection_done", codecForBoolean()) + .property("transfer_done", codecForBoolean()) + .property("aborted", codecForBoolean()) + .property("amount", codecForString()) + .property("sender_wire", codecOptional(codecForString())) + .property("suggested_exchange", codecOptional(codecForString())) + .property("confirm_transfer_url", codecOptional(codecForString())) + .property("wire_types", codecForList(codecForString())) + .build("WithdrawOperationStatusResponse"); export const codecForTipPickupGetResponse = (): Codec => buildCodecForObject() @@ -1419,60 +1447,67 @@ export const codecForExchangeRevealItem = (): Codec => ) .build("ExchangeRevealItem"); -export const codecForExchangeRevealResponse = (): Codec => - buildCodecForObject() - .property("ev_sigs", codecForList(codecForExchangeRevealItem())) - .build("ExchangeRevealResponse"); - -export const codecForMerchantCoinRefundSuccessStatus = (): Codec => - buildCodecForObject() - .property("type", codecForConstString("success")) - .property("coin_pub", codecForString()) - .property("exchange_status", codecForConstNumber(200)) - .property("exchange_sig", codecForString()) - .property("rtransaction_id", codecForNumber()) - .property("refund_amount", codecForString()) - .property("exchange_pub", codecForString()) - .property("execution_time", codecForTimestamp) - .build("MerchantCoinRefundSuccessStatus"); - -export const codecForMerchantCoinRefundFailureStatus = (): Codec => - buildCodecForObject() - .property("type", codecForConstString("failure")) - .property("coin_pub", codecForString()) - .property("exchange_status", codecForNumber()) - .property("rtransaction_id", codecForNumber()) - .property("refund_amount", codecForString()) - .property("exchange_code", codecOptional(codecForNumber())) - .property("exchange_reply", codecOptional(codecForAny())) - .property("execution_time", codecForTimestamp) - .build("MerchantCoinRefundFailureStatus"); - -export const codecForMerchantCoinRefundStatus = (): Codec => - buildCodecForUnion() - .discriminateOn("type") - .alternative("success", codecForMerchantCoinRefundSuccessStatus()) - .alternative("failure", codecForMerchantCoinRefundFailureStatus()) - .build("MerchantCoinRefundStatus"); - -export const codecForMerchantOrderStatusPaid = (): Codec => - buildCodecForObject() - .property("refund_amount", codecForString()) - .property("refunded", codecForBoolean()) - .build("MerchantOrderStatusPaid"); - -export const codecForMerchantOrderRefundPickupResponse = (): Codec => - buildCodecForObject() - .property("merchant_pub", codecForString()) - .property("refund_amount", codecForString()) - .property("refunds", codecForList(codecForMerchantCoinRefundStatus())) - .build("MerchantOrderRefundPickupResponse"); - -export const codecForMerchantOrderStatusUnpaid = (): Codec => - buildCodecForObject() - .property("taler_pay_uri", codecForString()) - .property("already_paid_order_id", codecOptional(codecForString())) - .build("MerchantOrderStatusUnpaid"); +export const codecForExchangeRevealResponse = + (): Codec => + buildCodecForObject() + .property("ev_sigs", codecForList(codecForExchangeRevealItem())) + .build("ExchangeRevealResponse"); + +export const codecForMerchantCoinRefundSuccessStatus = + (): Codec => + buildCodecForObject() + .property("type", codecForConstString("success")) + .property("coin_pub", codecForString()) + .property("exchange_status", codecForConstNumber(200)) + .property("exchange_sig", codecForString()) + .property("rtransaction_id", codecForNumber()) + .property("refund_amount", codecForString()) + .property("exchange_pub", codecForString()) + .property("execution_time", codecForTimestamp) + .build("MerchantCoinRefundSuccessStatus"); + +export const codecForMerchantCoinRefundFailureStatus = + (): Codec => + buildCodecForObject() + .property("type", codecForConstString("failure")) + .property("coin_pub", codecForString()) + .property("exchange_status", codecForNumber()) + .property("rtransaction_id", codecForNumber()) + .property("refund_amount", codecForString()) + .property("exchange_code", codecOptional(codecForNumber())) + .property("exchange_reply", codecOptional(codecForAny())) + .property("execution_time", codecForTimestamp) + .build("MerchantCoinRefundFailureStatus"); + +export const codecForMerchantCoinRefundStatus = + (): Codec => + buildCodecForUnion() + .discriminateOn("type") + .alternative("success", codecForMerchantCoinRefundSuccessStatus()) + .alternative("failure", codecForMerchantCoinRefundFailureStatus()) + .build("MerchantCoinRefundStatus"); + +export const codecForMerchantOrderStatusPaid = + (): Codec => + buildCodecForObject() + .property("refund_amount", codecForString()) + .property("refunded", codecForBoolean()) + .build("MerchantOrderStatusPaid"); + +export const codecForMerchantOrderRefundPickupResponse = + (): Codec => + buildCodecForObject() + .property("merchant_pub", codecForString()) + .property("refund_amount", codecForString()) + .property("refunds", codecForList(codecForMerchantCoinRefundStatus())) + .build("MerchantOrderRefundPickupResponse"); + +export const codecForMerchantOrderStatusUnpaid = + (): Codec => + buildCodecForObject() + .property("taler_pay_uri", codecForString()) + .property("already_paid_order_id", codecOptional(codecForString())) + .build("MerchantOrderStatusUnpaid"); export interface AbortRequest { // hash of the order's contract terms (this is used to authenticate the @@ -1550,28 +1585,31 @@ export interface MerchantAbortPayRefundSuccessStatus { exchange_pub: string; } -export const codecForMerchantAbortPayRefundSuccessStatus = (): Codec => - buildCodecForObject() - .property("exchange_pub", codecForString()) - .property("exchange_sig", codecForString()) - .property("exchange_status", codecForConstNumber(200)) - .property("type", codecForConstString("success")) - .build("MerchantAbortPayRefundSuccessStatus"); - -export const codecForMerchantAbortPayRefundFailureStatus = (): Codec => - buildCodecForObject() - .property("exchange_code", codecForNumber()) - .property("exchange_reply", codecForAny()) - .property("exchange_status", codecForNumber()) - .property("type", codecForConstString("failure")) - .build("MerchantAbortPayRefundFailureStatus"); - -export const codecForMerchantAbortPayRefundStatus = (): Codec => - buildCodecForUnion() - .discriminateOn("type") - .alternative("success", codecForMerchantAbortPayRefundSuccessStatus()) - .alternative("failure", codecForMerchantAbortPayRefundFailureStatus()) - .build("MerchantAbortPayRefundStatus"); +export const codecForMerchantAbortPayRefundSuccessStatus = + (): Codec => + buildCodecForObject() + .property("exchange_pub", codecForString()) + .property("exchange_sig", codecForString()) + .property("exchange_status", codecForConstNumber(200)) + .property("type", codecForConstString("success")) + .build("MerchantAbortPayRefundSuccessStatus"); + +export const codecForMerchantAbortPayRefundFailureStatus = + (): Codec => + buildCodecForObject() + .property("exchange_code", codecForNumber()) + .property("exchange_reply", codecForAny()) + .property("exchange_status", codecForNumber()) + .property("type", codecForConstString("failure")) + .build("MerchantAbortPayRefundFailureStatus"); + +export const codecForMerchantAbortPayRefundStatus = + (): Codec => + buildCodecForUnion() + .discriminateOn("type") + .alternative("success", codecForMerchantAbortPayRefundSuccessStatus()) + .alternative("failure", codecForMerchantAbortPayRefundFailureStatus()) + .build("MerchantAbortPayRefundStatus"); export interface TalerConfigResponse { name: string; @@ -1614,13 +1652,13 @@ export interface MerchantConfigResponse { version: string; } -export const codecForMerchantConfigResponse = (): Codec => - buildCodecForObject() - .property("currency", codecForString()) - .property("name", codecForString()) - .property("version", codecForString()) - .build("MerchantConfigResponse"); - +export const codecForMerchantConfigResponse = + (): Codec => + buildCodecForObject() + .property("currency", codecForString()) + .property("name", codecForString()) + .property("version", codecForString()) + .build("MerchantConfigResponse"); export enum ExchangeProtocolVersion { V9 = 9, diff --git a/packages/taler-wallet-cli/src/harness/harness.ts b/packages/taler-wallet-cli/src/harness/harness.ts index 040bd5a6f..35162065d 100644 --- a/packages/taler-wallet-cli/src/harness/harness.ts +++ b/packages/taler-wallet-cli/src/harness/harness.ts @@ -2031,9 +2031,9 @@ export class WalletCli { `wallet-${self.name}`, `taler-wallet-cli ${ self.timetravelArg ?? "" - } --no-throttle --wallet-db '${self.dbfile}' api '${op}' ${shellWrap( - JSON.stringify(payload), - )}`, + } --no-throttle -LTRACE --wallet-db '${ + self.dbfile + }' api '${op}' ${shellWrap(JSON.stringify(payload))}`, ); console.log("--- wallet core response ---"); console.log(resp); @@ -2080,6 +2080,7 @@ export class WalletCli { [ "--no-throttle", ...this.timetravelArgArr, + "-LTRACE", "--wallet-db", this.dbfile, "run-until-done", @@ -2095,6 +2096,7 @@ export class WalletCli { "taler-wallet-cli", [ "--no-throttle", + "-LTRACE", ...this.timetravelArgArr, "--wallet-db", this.dbfile, diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts index b57e73a1c..22a2d8552 100644 --- a/packages/taler-wallet-cli/src/index.ts +++ b/packages/taler-wallet-cli/src/index.ts @@ -249,6 +249,7 @@ walletCli .action(async (args) => { await withWallet(args, async (wallet) => { let requestJson; + logger.info(`handling 'api' request (${args.api.operation})`); try { requestJson = JSON.parse(args.api.request); } catch (e) { @@ -293,12 +294,6 @@ walletCli }); }); -async function asyncSleep(milliSeconds: number): Promise { - return new Promise((resolve, reject) => { - setTimeout(() => resolve(), milliSeconds); - }); -} - walletCli .subcommand("runPendingOpt", "run-pending", { help: "Run pending operations.", @@ -330,6 +325,7 @@ walletCli .maybeOption("maxRetries", ["--max-retries"], clk.INT) .action(async (args) => { await withWallet(args, async (wallet) => { + logger.info("running until pending operations are finished"); await wallet.ws.runTaskLoop({ maxRetries: args.finishPendingOpt.maxRetries, stopWhenDone: true, diff --git a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts index 9b72dfbe2..5351815a7 100644 --- a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts +++ b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts @@ -27,7 +27,13 @@ /** * Imports. */ -import { AmountJson, DenominationPubKey, ExchangeProtocolVersion } from "@gnu-taler/taler-util"; +import { + AmountJson, + AmountString, + DenominationPubKey, + ExchangeProtocolVersion, + UnblindedSignature, +} from "@gnu-taler/taler-util"; export interface RefreshNewDenomInfo { count: number; @@ -140,3 +146,29 @@ export interface SignTrackTransactionRequest { merchantPriv: string; merchantPub: string; } + +/** + * Request to create a recoup request payload. + */ +export interface CreateRecoupReqRequest { + coinPub: string; + coinPriv: string; + blindingKey: string; + denomPub: DenominationPubKey; + denomPubHash: string; + denomSig: UnblindedSignature; + recoupAmount: AmountJson; +} + +/** + * Request to create a recoup-refresh request payload. + */ +export interface CreateRecoupRefreshReqRequest { + coinPub: string; + coinPriv: string; + blindingKey: string; + denomPub: DenominationPubKey; + denomPubHash: string; + denomSig: UnblindedSignature; + recoupAmount: AmountJson; +} diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts index e88b64c3c..29c2553a5 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts @@ -26,7 +26,11 @@ import { CoinRecord, DenominationRecord, WireFee } from "../../db.js"; import { CryptoWorker } from "./cryptoWorkerInterface.js"; -import { RecoupRequest, CoinDepositPermission } from "@gnu-taler/taler-util"; +import { + CoinDepositPermission, + RecoupRefreshRequest, + RecoupRequest, +} from "@gnu-taler/taler-util"; import { BenchmarkResult, @@ -39,6 +43,8 @@ import { import * as timer from "../../util/timer.js"; import { Logger } from "@gnu-taler/taler-util"; import { + CreateRecoupRefreshReqRequest, + CreateRecoupReqRequest, DerivedRefreshSession, DerivedTipPlanchet, DeriveRefreshSessionRequest, @@ -421,8 +427,18 @@ export class CryptoApi { ); } - createRecoupRequest(coin: CoinRecord): Promise { - return this.doRpc("createRecoupRequest", 1, coin); + createRecoupRequest(req: CreateRecoupReqRequest): Promise { + return this.doRpc("createRecoupRequest", 1, req); + } + + createRecoupRefreshRequest( + req: CreateRecoupRefreshReqRequest, + ): Promise { + return this.doRpc( + "createRecoupRefreshRequest", + 1, + req, + ); } deriveRefreshSession( diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts index 9e2dc18f3..b366fa9ec 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts @@ -25,12 +25,7 @@ */ // FIXME: Crypto should not use DB Types! -import { - CoinRecord, - DenominationRecord, - WireFee, - CoinSourceType, -} from "../../db.js"; +import { DenominationRecord, WireFee } from "../../db.js"; import { buildSigPS, @@ -39,6 +34,7 @@ import { ExchangeProtocolVersion, FreshCoin, hashDenomPub, + RecoupRefreshRequest, RecoupRequest, RefreshPlanchetInfo, TalerSignaturePurpose, @@ -78,6 +74,8 @@ import { Timestamp, timestampTruncateToSecond } from "@gnu-taler/taler-util"; import { Logger } from "@gnu-taler/taler-util"; import { + CreateRecoupRefreshReqRequest, + CreateRecoupReqRequest, DerivedRefreshSession, DerivedTipPlanchet, DeriveRefreshSessionRequest, @@ -261,33 +259,64 @@ export class CryptoImplementation { /** * Create and sign a message to recoup a coin. */ - createRecoupRequest(coin: CoinRecord): RecoupRequest { + createRecoupRequest(req: CreateRecoupReqRequest): RecoupRequest { const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP) - .put(decodeCrock(coin.coinPub)) - .put(decodeCrock(coin.denomPubHash)) - .put(decodeCrock(coin.blindingKey)) + .put(decodeCrock(req.denomPubHash)) + .put(decodeCrock(req.blindingKey)) + .put(amountToBuffer(Amounts.jsonifyAmount(req.recoupAmount))) .build(); - const coinPriv = decodeCrock(coin.coinPriv); + const coinPriv = decodeCrock(req.coinPriv); const coinSig = eddsaSign(p, coinPriv); - if (coin.denomPub.cipher === DenomKeyType.LegacyRsa) { + if (req.denomPub.cipher === DenomKeyType.LegacyRsa) { const paybackRequest: RecoupRequest = { - coin_blind_key_secret: coin.blindingKey, - coin_pub: coin.coinPub, + coin_blind_key_secret: req.blindingKey, coin_sig: encodeCrock(coinSig), - denom_pub_hash: coin.denomPubHash, - denom_sig: coin.denomSig.rsa_signature, - refreshed: coin.coinSource.type === CoinSourceType.Refresh, + denom_pub_hash: req.denomPubHash, + denom_sig: req.denomSig.rsa_signature, + amount: Amounts.stringify(req.recoupAmount), }; return paybackRequest; } else { const paybackRequest: RecoupRequest = { - coin_blind_key_secret: coin.blindingKey, - coin_pub: coin.coinPub, + coin_blind_key_secret: req.blindingKey, + coin_sig: encodeCrock(coinSig), + denom_pub_hash: req.denomPubHash, + denom_sig: req.denomSig, + amount: Amounts.stringify(req.recoupAmount), + }; + return paybackRequest; + } + } + + /** + * Create and sign a message to recoup a coin. + */ + createRecoupRefreshRequest(req: CreateRecoupRefreshReqRequest): RecoupRefreshRequest { + const p = buildSigPS(TalerSignaturePurpose.WALLET_COIN_RECOUP_REFRESH) + .put(decodeCrock(req.denomPubHash)) + .put(decodeCrock(req.blindingKey)) + .put(amountToBuffer(Amounts.jsonifyAmount(req.recoupAmount))) + .build(); + + const coinPriv = decodeCrock(req.coinPriv); + const coinSig = eddsaSign(p, coinPriv); + if (req.denomPub.cipher === DenomKeyType.LegacyRsa) { + const paybackRequest: RecoupRefreshRequest = { + coin_blind_key_secret: req.blindingKey, + coin_sig: encodeCrock(coinSig), + denom_pub_hash: req.denomPubHash, + denom_sig: req.denomSig.rsa_signature, + amount: Amounts.stringify(req.recoupAmount), + }; + return paybackRequest; + } else { + const paybackRequest: RecoupRefreshRequest = { + coin_blind_key_secret: req.blindingKey, coin_sig: encodeCrock(coinSig), - denom_pub_hash: coin.denomPubHash, - denom_sig: coin.denomSig, - refreshed: coin.coinSource.type === CoinSourceType.Refresh, + denom_pub_hash: req.denomPubHash, + denom_sig: req.denomSig, + amount: Amounts.stringify(req.recoupAmount), }; return paybackRequest; } diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 987031810..2975c860f 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -651,7 +651,7 @@ async function updateExchangeFromUrlImpl( logger.trace("denom already revoked"); continue; } - logger.trace("revoking denom", recoupInfo.h_denom_pub); + logger.info("revoking denom", recoupInfo.h_denom_pub); oldDenom.isRevoked = true; await tx.denominations.put(oldDenom); const affectedCoins = await tx.coins.indexes.byDenomPubHash @@ -662,7 +662,7 @@ async function updateExchangeFromUrlImpl( } } if (newlyRevokedCoinPubs.length != 0) { - logger.trace("recouping coins", newlyRevokedCoinPubs); + logger.info("recouping coins", newlyRevokedCoinPubs); recoupGroupId = await ws.recoupOps.createRecoupGroup( ws, tx, diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts index 8a4c2242a..559513d44 100644 --- a/packages/taler-wallet-core/src/operations/recoup.ts +++ b/packages/taler-wallet-core/src/operations/recoup.ts @@ -28,6 +28,7 @@ import { Amounts, codecForRecoupConfirmation, getTimestampNow, + j2s, NotificationType, RefreshReason, TalerErrorDetails, @@ -107,7 +108,7 @@ async function putGroupAsFinished( } } if (allFinished) { - logger.trace("all recoups of recoup group are finished"); + logger.info("all recoups of recoup group are finished"); recoupGroup.timestampFinished = getTimestampNow(); recoupGroup.retryInfo = initRetryInfo(); recoupGroup.lastError = undefined; @@ -178,8 +179,17 @@ async function recoupWithdrawCoin( type: NotificationType.RecoupStarted, }); - const recoupRequest = await ws.cryptoApi.createRecoupRequest(coin); + const recoupRequest = await ws.cryptoApi.createRecoupRequest({ + blindingKey: coin.blindingKey, + coinPriv: coin.coinPriv, + coinPub: coin.coinPub, + denomPub: coin.denomPub, + denomPubHash: coin.denomPubHash, + denomSig: coin.denomSig, + recoupAmount: coin.currentAmount, + }); const reqUrl = new URL(`/coins/${coin.coinPub}/recoup`, coin.exchangeBaseUrl); + logger.trace(`requesting recoup via ${reqUrl.href}`); const resp = await ws.http.postJson(reqUrl.href, recoupRequest, { timeout: getReserveRequestTimeout(reserve), }); @@ -188,6 +198,8 @@ async function recoupWithdrawCoin( codecForRecoupConfirmation(), ); + logger.trace(`got recoup confirmation ${j2s(recoupConfirmation)}`); + if (recoupConfirmation.reserve_pub !== reservePub) { throw Error(`Coin's reserve doesn't match reserve on recoup`); } @@ -249,7 +261,15 @@ async function recoupRefreshCoin( type: NotificationType.RecoupStarted, }); - const recoupRequest = await ws.cryptoApi.createRecoupRequest(coin); + const recoupRequest = await ws.cryptoApi.createRecoupRefreshRequest({ + blindingKey: coin.blindingKey, + coinPriv: coin.coinPriv, + coinPub: coin.coinPub, + denomPub: coin.denomPub, + denomPubHash: coin.denomPubHash, + denomSig: coin.denomSig, + recoupAmount: coin.currentAmount, + }); const reqUrl = new URL(`/coins/${coin.coinPub}/recoup`, coin.exchangeBaseUrl); logger.trace(`making recoup request for ${coin.coinPub}`); @@ -359,9 +379,14 @@ async function processRecoupGroupImpl( logger.trace("recoup group finished"); return; } - const ps = recoupGroup.coinPubs.map((x, i) => - processRecoup(ws, recoupGroupId, i), - ); + const ps = recoupGroup.coinPubs.map(async (x, i) => { + try { + processRecoup(ws, recoupGroupId, i); + } catch (e) { + logger.warn(`processRecoup failed: ${e}`); + throw e; + } + }); await Promise.all(ps); const reserveSet = new Set(); diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts index 5a9fbb405..75d517d68 100644 --- a/packages/taler-wallet-core/src/operations/reserves.ts +++ b/packages/taler-wallet-core/src/operations/reserves.ts @@ -30,6 +30,7 @@ import { encodeCrock, getRandomBytes, getTimestampNow, + j2s, Logger, NotificationType, randomBytes, @@ -538,6 +539,7 @@ async function updateReserve( resp, codecForReserveStatus(), ); + if (result.isError) { if ( resp.status === 404 && @@ -555,6 +557,8 @@ async function updateReserve( } } + logger.trace(`got reserve status ${j2s(result.response)}`); + const reserveInfo = result.response; const balance = Amounts.parseOrThrow(reserveInfo.balance); const currency = balance.currency; @@ -635,8 +639,10 @@ async function updateReserve( } } - const remainingAmount = Amounts.sub(amountReservePlus, amountReserveMinus) - .amount; + const remainingAmount = Amounts.sub( + amountReservePlus, + amountReserveMinus, + ).amount; const denomSelInfo = selectWithdrawalDenominations( remainingAmount, denoms, -- cgit v1.2.3