diff options
Diffstat (limited to 'packages')
34 files changed, 1396 insertions, 362 deletions
diff --git a/packages/taler-util/src/talerCrypto.ts b/packages/taler-util/src/talerCrypto.ts index 358da9dac..4d6e73671 100644 --- a/packages/taler-util/src/talerCrypto.ts +++ b/packages/taler-util/src/talerCrypto.ts @@ -785,6 +785,22 @@ export function setupRefreshTransferPub( }; } +/** + * + * @param paytoUri + * @param salt 16-byte salt + * @returns + */ +export function hashWire(paytoUri: string, salt: string): string { + const r = kdf( + 64, + stringToBytes(paytoUri + "\0"), + decodeCrock(salt), + stringToBytes("merchant-wire-signature"), + ); + return encodeCrock(r); +} + export enum TalerSignaturePurpose { MERCHANT_TRACK_TRANSACTION = 1103, WALLET_RESERVE_WITHDRAW = 1200, diff --git a/packages/taler-util/src/talerTypes.ts b/packages/taler-util/src/talerTypes.ts index b38f788af..4ccfffce0 100644 --- a/packages/taler-util/src/talerTypes.ts +++ b/packages/taler-util/src/talerTypes.ts @@ -951,6 +951,15 @@ export interface MerchantPayResponse { sig: string; } +export interface ExchangeMeltRequest { + coin_pub: CoinPublicKeyString; + confirm_sig: EddsaSignatureString; + denom_pub_hash: HashCodeString; + denom_sig: UnblindedSignature; + rc: string; + value_with_fee: AmountString; +} + export interface ExchangeMeltResponse { /** * Which of the kappa indices does the client not have to reveal. @@ -1710,3 +1719,40 @@ export interface ExchangeRefreshRevealRequest { link_sigs: EddsaSignatureString[]; } + +export interface DepositSuccess { + // Optional base URL of the exchange for looking up wire transfers + // associated with this transaction. If not given, + // the base URL is the same as the one used for this request. + // Can be used if the base URL for /transactions/ differs from that + // for /coins/, i.e. for load balancing. Clients SHOULD + // respect the transaction_base_url if provided. Any HTTP server + // belonging to an exchange MUST generate a 307 or 308 redirection + // to the correct base URL should a client uses the wrong base + // URL, or if the base URL has changed since the deposit. + transaction_base_url?: string; + + // timestamp when the deposit was received by the exchange. + exchange_timestamp: Timestamp; + + // the EdDSA signature of TALER_DepositConfirmationPS using a current + // signing key of the exchange affirming the successful + // deposit and that the exchange will transfer the funds after the refund + // deadline, or as soon as possible if the refund deadline is zero. + exchange_sig: string; + + // public EdDSA key of the exchange that was used to + // generate the signature. + // Should match one of the exchange's signing keys from /keys. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: string; +} + +export const codecForDepositSuccess = (): Codec<DepositSuccess> => + buildCodecForObject<DepositSuccess>() + .property("exchange_pub", codecForString()) + .property("exchange_sig", codecForString()) + .property("exchange_timestamp", codecForTimestamp) + .property("transaction_base_url", codecOptional(codecForString())) + .build("DepositSuccess"); diff --git a/packages/taler-util/src/time.ts b/packages/taler-util/src/time.ts index 5fef0bf47..3b80b4ee0 100644 --- a/packages/taler-util/src/time.ts +++ b/packages/taler-util/src/time.ts @@ -78,6 +78,9 @@ export namespace Duration { return Math.ceil(d.d_ms / 1000 / 60 / 60 / 24 / 365); } export const fromSpec = durationFromSpec; + export function getForever(): Duration { + return { d_ms: "forever" }; + } } export namespace Timestamp { diff --git a/packages/taler-util/src/walletTypes.ts b/packages/taler-util/src/walletTypes.ts index b8433e261..444fac154 100644 --- a/packages/taler-util/src/walletTypes.ts +++ b/packages/taler-util/src/walletTypes.ts @@ -458,7 +458,7 @@ export interface TalerErrorDetails { details: unknown; } -export interface PlanchetCreationResult { +export interface WithdrawalPlanchet { coinPub: string; coinPriv: string; reservePub: string; diff --git a/packages/taler-wallet-cli/src/bench2.ts b/packages/taler-wallet-cli/src/bench2.ts new file mode 100644 index 000000000..884708207 --- /dev/null +++ b/packages/taler-wallet-cli/src/bench2.ts @@ -0,0 +1,106 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Imports. + */ +import { + buildCodecForObject, + codecForNumber, + codecForString, + codecOptional, + j2s, + Logger, +} from "@gnu-taler/taler-util"; +import { + getDefaultNodeWallet2, + NodeHttpLib, + WalletApiOperation, + Wallet, + AccessStats, + downloadExchangeInfo, +} from "@gnu-taler/taler-wallet-core"; + +/** + * Entry point for the benchmark. + * + * The benchmark runs against an existing Taler deployment and does not + * set up its own services. + */ +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 myHttpLib = new NodeHttpLib(); + myHttpLib.setThrottling(false); + + const exchangeInfo = await downloadExchangeInfo( + benchConf.exchange, + myHttpLib, + ); +} + +/** + * Format of the configuration file passed to the benchmark + */ +interface Bench2Config { + /** + * Base URL of the bank. + */ + bank: string; + + /** + * Payto url for deposits. + */ + payto: string; + + /** + * Base URL of the exchange. + */ + exchange: string; + + /** + * How many withdraw/deposit iterations should be made? + * Defaults to 1. + */ + iterations?: number; + + 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 = () => + buildCodecForObject<Bench2Config>() + .property("bank", codecForString()) + .property("payto", codecForString()) + .property("exchange", codecForString()) + .property("iterations", codecOptional(codecForNumber())) + .property("deposits", codecOptional(codecForNumber())) + .property("currency", codecForString()) + .property("restartAfter", codecOptional(codecForNumber())) + .build("Bench1Config"); diff --git a/packages/taler-wallet-cli/src/harness/harness.ts b/packages/taler-wallet-cli/src/harness/harness.ts index f4e422690..63bb17fcc 100644 --- a/packages/taler-wallet-cli/src/harness/harness.ts +++ b/packages/taler-wallet-cli/src/harness/harness.ts @@ -45,6 +45,9 @@ import { MerchantInstancesResponse, } from "./merchantApiTypes"; import { + BankServiceHandle, + HarnessExchangeBankAccount, + NodeHttpLib, openPromise, OperationFailedError, WalletCoreApiClient, @@ -468,164 +471,6 @@ export async function pingProc( } } -export interface HarnessExchangeBankAccount { - accountName: string; - accountPassword: string; - accountPaytoUri: string; - wireGatewayApiBaseUrl: string; -} - -export interface BankServiceInterface { - readonly baseUrl: string; - readonly port: number; -} - -export enum CreditDebitIndicator { - Credit = "credit", - Debit = "debit", -} - -export interface BankAccountBalanceResponse { - balance: { - amount: AmountString; - credit_debit_indicator: CreditDebitIndicator; - }; -} - -export namespace BankAccessApi { - export async function getAccountBalance( - bank: BankServiceInterface, - bankUser: BankUser, - ): Promise<BankAccountBalanceResponse> { - const url = new URL(`accounts/${bankUser.username}`, bank.baseUrl); - const resp = await axios.get(url.href, { - auth: bankUser, - }); - return resp.data; - } - - export async function createWithdrawalOperation( - bank: BankServiceInterface, - bankUser: BankUser, - amount: string, - ): Promise<WithdrawalOperationInfo> { - const url = new URL( - `accounts/${bankUser.username}/withdrawals`, - bank.baseUrl, - ); - const resp = await axios.post( - url.href, - { - amount, - }, - { - auth: bankUser, - }, - ); - return codecForWithdrawalOperationInfo().decode(resp.data); - } -} - -export namespace BankApi { - export async function registerAccount( - bank: BankServiceInterface, - username: string, - password: string, - ): Promise<BankUser> { - const url = new URL("testing/register", bank.baseUrl); - let resp = await axios.post(url.href, { - username, - password, - }); - let paytoUri = `payto://x-taler-bank/localhost/${username}`; - if (process.env.WALLET_HARNESS_WITH_EUFIN) { - paytoUri = resp.data.paytoUri; - } - return { - password, - username, - accountPaytoUri: paytoUri, - }; - } - - export async function createRandomBankUser( - bank: BankServiceInterface, - ): Promise<BankUser> { - const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase(); - const password = "pw-" + encodeCrock(getRandomBytes(10)).toLowerCase(); - return await registerAccount(bank, username, password); - } - - export async function adminAddIncoming( - bank: BankServiceInterface, - params: { - exchangeBankAccount: HarnessExchangeBankAccount; - amount: string; - reservePub: string; - debitAccountPayto: string; - }, - ) { - let maybeBaseUrl = bank.baseUrl; - if (process.env.WALLET_HARNESS_WITH_EUFIN) { - maybeBaseUrl = (bank as EufinBankService).baseUrlDemobank; - } - let url = new URL( - `taler-wire-gateway/${params.exchangeBankAccount.accountName}/admin/add-incoming`, - maybeBaseUrl, - ); - await axios.post( - url.href, - { - amount: params.amount, - reserve_pub: params.reservePub, - debit_account: params.debitAccountPayto, - }, - { - auth: { - username: params.exchangeBankAccount.accountName, - password: params.exchangeBankAccount.accountPassword, - }, - }, - ); - } - - export async function confirmWithdrawalOperation( - bank: BankServiceInterface, - bankUser: BankUser, - wopi: WithdrawalOperationInfo, - ): Promise<void> { - const url = new URL( - `accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/confirm`, - bank.baseUrl, - ); - await axios.post( - url.href, - {}, - { - auth: bankUser, - }, - ); - } - - export async function abortWithdrawalOperation( - bank: BankServiceInterface, - bankUser: BankUser, - wopi: WithdrawalOperationInfo, - ): Promise<void> { - const url = new URL( - `accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/abort`, - bank.baseUrl, - ); - await axios.post( - url.href, - {}, - { - auth: bankUser, - }, - ); - } -} - class BankServiceBase { proc: ProcessWrapper | undefined; @@ -640,10 +485,12 @@ class BankServiceBase { * Work in progress. The key point is that both Sandbox and Nexus * will be configured and started by this class. */ -class EufinBankService extends BankServiceBase implements BankServiceInterface { +class EufinBankService extends BankServiceBase implements BankServiceHandle { sandboxProc: ProcessWrapper | undefined; nexusProc: ProcessWrapper | undefined; + http = new NodeHttpLib(); + static async create( gc: GlobalTestState, bc: BankConfig, @@ -914,9 +761,11 @@ class EufinBankService extends BankServiceBase implements BankServiceInterface { } } -class PybankService extends BankServiceBase implements BankServiceInterface { +class PybankService extends BankServiceBase implements BankServiceHandle { proc: ProcessWrapper | undefined; + http = new NodeHttpLib(); + static async create( gc: GlobalTestState, bc: BankConfig, @@ -955,6 +804,7 @@ class PybankService extends BankServiceBase implements BankServiceInterface { const config = Configuration.load(this.configFile); config.setString("bank", "suggested_exchange", e.baseUrl); config.setString("bank", "suggested_exchange_payto", exchangePayto); + config.write(this.configFile); } get baseUrl(): string { @@ -1087,23 +937,6 @@ export class FakeBankService { } } -export interface BankUser { - username: string; - password: string; - accountPaytoUri: string; -} - -export interface WithdrawalOperationInfo { - withdrawal_id: string; - taler_withdraw_uri: string; -} - -const codecForWithdrawalOperationInfo = (): Codec<WithdrawalOperationInfo> => - buildCodecForObject<WithdrawalOperationInfo>() - .property("withdrawal_id", codecForString()) - .property("taler_withdraw_uri", codecForString()) - .build("WithdrawalOperationInfo"); - export interface ExchangeConfig { name: string; currency: string; diff --git a/packages/taler-wallet-cli/src/harness/helpers.ts b/packages/taler-wallet-cli/src/harness/helpers.ts index f19c6a115..117bcdcf8 100644 --- a/packages/taler-wallet-cli/src/harness/helpers.ts +++ b/packages/taler-wallet-cli/src/harness/helpers.ts @@ -30,22 +30,19 @@ import { Duration, PreparePayResultType, } from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { BankAccessApi, BankApi, HarnessExchangeBankAccount, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { CoinConfig, defaultCoinConfig } from "./denomStructures.js"; import { FaultInjectedExchangeService, FaultInjectedMerchantService, } from "./faultInjection.js"; import { - BankAccessApi, - BankApi, BankService, DbInfo, ExchangeService, ExchangeServiceInterface, getPayto, GlobalTestState, - HarnessExchangeBankAccount, MerchantPrivateApi, MerchantService, MerchantServiceInterface, diff --git a/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts b/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts index 2259dd8bb..8e4109752 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts @@ -24,13 +24,15 @@ import { setupDb, BankService, MerchantService, - BankApi, - BankAccessApi, - CreditDebitIndicator, - getPayto + getPayto, } from "../harness/harness.js"; import { createEddsaKeyPair, encodeCrock } from "@gnu-taler/taler-util"; import { defaultCoinConfig } from "../harness/denomStructures"; +import { + BankApi, + BankAccessApi, + CreditDebitIndicator, +} from "@gnu-taler/taler-wallet-core"; /** * Run test for basic, bank-integrated withdrawal. @@ -97,8 +99,6 @@ export async function runBankApiTest(t: GlobalTestState) { console.log("setup done!"); - const wallet = new WalletCli(t); - const bankUser = await BankApi.registerAccount(bank, "user1", "pw1"); // Make sure that registering twice results in a 409 Conflict diff --git a/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts b/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts index 91e9bdec5..f9c7c4b99 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts @@ -24,11 +24,13 @@ import { BankService, ExchangeService, MerchantService, + getPayto, +} from "../harness/harness.js"; +import { + WalletApiOperation, BankApi, BankAccessApi, - getPayto -} from "../harness/harness.js"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +} from "@gnu-taler/taler-wallet-core"; import { ExchangesListRespose, URL, diff --git a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts index 3f7e1a9d1..33aad80d2 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts @@ -19,15 +19,16 @@ */ import { ContractTerms, - CoreApiResponse, getTimestampNow, timestampTruncateToSecond, } from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { + WalletApiOperation, + HarnessExchangeBankAccount, +} from "@gnu-taler/taler-wallet-core"; import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures"; import { DbInfo, - HarnessExchangeBankAccount, ExchangeService, GlobalTestState, MerchantService, @@ -233,13 +234,8 @@ export async function createLibeufinTestEnvironment( export async function runLibeufinBasicTest(t: GlobalTestState) { // Set up test environment - const { - wallet, - exchange, - merchant, - libeufinSandbox, - libeufinNexus, - } = await createLibeufinTestEnvironment(t); + const { wallet, exchange, merchant, libeufinSandbox, libeufinNexus } = + await createLibeufinTestEnvironment(t); await wallet.client.call(WalletApiOperation.AddExchange, { exchangeBaseUrl: exchange.baseUrl, diff --git a/packages/taler-wallet-cli/src/integrationtests/test-merchant-refund-api.ts b/packages/taler-wallet-cli/src/integrationtests/test-merchant-refund-api.ts index 466b1efbd..a9dbeef9a 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-merchant-refund-api.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-merchant-refund-api.ts @@ -20,25 +20,30 @@ import { GlobalTestState, MerchantPrivateApi, - BankServiceInterface, MerchantServiceInterface, WalletCli, ExchangeServiceInterface, } from "../harness/harness.js"; -import { createSimpleTestkudosEnvironment, withdrawViaBank } from "../harness/helpers.js"; +import { + createSimpleTestkudosEnvironment, + withdrawViaBank, +} from "../harness/helpers.js"; import { URL, durationFromSpec, PreparePayResultType, } from "@gnu-taler/taler-util"; import axios from "axios"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { + WalletApiOperation, + BankServiceHandle, +} from "@gnu-taler/taler-wallet-core"; async function testRefundApiWithFulfillmentUrl( t: GlobalTestState, env: { merchant: MerchantServiceInterface; - bank: BankServiceInterface; + bank: BankServiceHandle; wallet: WalletCli; exchange: ExchangeServiceInterface; }, @@ -152,7 +157,7 @@ async function testRefundApiWithFulfillmentMessage( t: GlobalTestState, env: { merchant: MerchantServiceInterface; - bank: BankServiceInterface; + bank: BankServiceHandle; wallet: WalletCli; exchange: ExchangeServiceInterface; }, @@ -267,12 +272,8 @@ async function testRefundApiWithFulfillmentMessage( export async function runMerchantRefundApiTest(t: GlobalTestState) { // Set up test environment - const { - wallet, - bank, - exchange, - merchant, - } = await createSimpleTestkudosEnvironment(t); + const { wallet, bank, exchange, merchant } = + await createSimpleTestkudosEnvironment(t); // Withdraw digital cash into the wallet. diff --git a/packages/taler-wallet-cli/src/integrationtests/test-payment-fault.ts b/packages/taler-wallet-cli/src/integrationtests/test-payment-fault.ts index 7e421cc35..c78f030c8 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-payment-fault.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-payment-fault.ts @@ -29,9 +29,7 @@ import { BankService, WalletCli, MerchantPrivateApi, - BankApi, - BankAccessApi, - getPayto + getPayto, } from "../harness/harness.js"; import { FaultInjectedExchangeService, @@ -40,7 +38,11 @@ import { } from "../harness/faultInjection"; import { CoreApiResponse } from "@gnu-taler/taler-util"; import { defaultCoinConfig } from "../harness/denomStructures"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { + WalletApiOperation, + BankApi, + BankAccessApi, +} from "@gnu-taler/taler-wallet-core"; /** * Run test for basic, bank-integrated withdrawal. @@ -146,7 +148,6 @@ export async function runPaymentFaultTest(t: GlobalTestState) { await wallet.runUntilDone(); - // Check balance await wallet.client.call(WalletApiOperation.GetBalances, {}); diff --git a/packages/taler-wallet-cli/src/integrationtests/test-payment-on-demo.ts b/packages/taler-wallet-cli/src/integrationtests/test-payment-on-demo.ts index 1d419fd9a..50a18944b 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-payment-on-demo.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-payment-on-demo.ts @@ -17,31 +17,33 @@ /** * Imports. */ +import { GlobalTestState, WalletCli } from "../harness/harness.js"; +import { makeTestPayment } from "../harness/helpers.js"; import { - GlobalTestState, + WalletApiOperation, BankApi, - WalletCli, - BankAccessApi -} from "../harness/harness.js"; -import { - makeTestPayment, -} from "../harness/helpers.js"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; + BankAccessApi, + BankServiceHandle, + NodeHttpLib, +} from "@gnu-taler/taler-wallet-core"; /** * Run test for basic, bank-integrated withdrawal and payment. */ export async function runPaymentDemoTest(t: GlobalTestState) { - // Withdraw digital cash into the wallet. - let bankInterface = { + let bankInterface: BankServiceHandle = { baseUrl: "https://bank.demo.taler.net/", - port: 0 // unused. + http: new NodeHttpLib(), }; let user = await BankApi.createRandomBankUser(bankInterface); - let wop = await BankAccessApi.createWithdrawalOperation(bankInterface, user, "KUDOS:20"); + let wop = await BankAccessApi.createWithdrawalOperation( + bankInterface, + user, + "KUDOS:20", + ); - let wallet = new WalletCli(t); + let wallet = new WalletCli(t); await wallet.client.call(WalletApiOperation.GetWithdrawalDetailsForUri, { talerWithdrawUri: wop.taler_withdraw_uri, }); @@ -60,7 +62,10 @@ export async function runPaymentDemoTest(t: GlobalTestState) { }); await wallet.runUntilDone(); - let balanceBefore = await wallet.client.call(WalletApiOperation.GetBalances, {}); + let balanceBefore = await wallet.client.call( + WalletApiOperation.GetBalances, + {}, + ); t.assertTrue(balanceBefore["balances"].length == 1); const order = { @@ -70,7 +75,7 @@ export async function runPaymentDemoTest(t: GlobalTestState) { }; let merchant = { - makeInstanceBaseUrl: function(instanceName?: string) { + makeInstanceBaseUrl: function (instanceName?: string) { return "https://backend.demo.taler.net/instances/donations/"; }, port: 0, @@ -82,17 +87,26 @@ export async function runPaymentDemoTest(t: GlobalTestState) { await makeTestPayment( t, { - merchant, wallet, order + merchant, + wallet, + order, }, { - "Authorization": `Bearer ${process.env["TALER_ENV_FRONTENDS_APITOKEN"]}`, - }); + Authorization: `Bearer ${process.env["TALER_ENV_FRONTENDS_APITOKEN"]}`, + }, + ); await wallet.runUntilDone(); - let balanceAfter = await wallet.client.call(WalletApiOperation.GetBalances, {}); + let balanceAfter = await wallet.client.call( + WalletApiOperation.GetBalances, + {}, + ); t.assertTrue(balanceAfter["balances"].length == 1); - t.assertTrue(balanceBefore["balances"][0]["available"] > balanceAfter["balances"][0]["available"]); + t.assertTrue( + balanceBefore["balances"][0]["available"] > + balanceAfter["balances"][0]["available"], + ); } runPaymentDemoTest.excludeByDefault = true; diff --git a/packages/taler-wallet-cli/src/integrationtests/test-tipping.ts b/packages/taler-wallet-cli/src/integrationtests/test-tipping.ts index f31220e24..f04293ed8 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-tipping.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-tipping.ts @@ -17,8 +17,12 @@ /** * Imports. */ -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { GlobalTestState, MerchantPrivateApi, BankApi, getWireMethod } from "../harness/harness.js"; +import { WalletApiOperation, BankApi } from "@gnu-taler/taler-wallet-core"; +import { + GlobalTestState, + MerchantPrivateApi, + getWireMethod, +} from "../harness/harness.js"; import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; /** @@ -27,13 +31,8 @@ import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; export async function runTippingTest(t: GlobalTestState) { // Set up test environment - const { - wallet, - bank, - exchange, - merchant, - exchangeBankAccount, - } = await createSimpleTestkudosEnvironment(t); + const { wallet, bank, exchange, merchant, exchangeBankAccount } = + await createSimpleTestkudosEnvironment(t); const mbu = await BankApi.createRandomBankUser(bank); diff --git a/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts b/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts new file mode 100644 index 000000000..9ff605df5 --- /dev/null +++ b/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts @@ -0,0 +1,358 @@ +/* + This file is part of GNU Taler + (C) 2020 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Imports. + */ +import { + 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, + CryptoApi, + DenominationRecord, + downloadExchangeInfo, + ExchangeInfo, + getBankWithdrawalInfo, + HttpRequestLibrary, + isWithdrawableDenom, + NodeHttpLib, + OperationFailedError, + readSuccessResponseJsonOrThrow, + SynchronousCryptoWorkerFactory, +} 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. + */ +export async function runWalletDblessTest(t: GlobalTestState) { + // Set up test environment + + const { bank, exchange } = await createSimpleTestkudosEnvironment(t); + + const http = new NodeHttpLib(); + const cryptoApi = new CryptoApi(new SynchronousCryptoWorkerFactory()); + + try { + // Withdraw digital cash into the wallet. + + const exchangeInfo = await downloadExchangeInfo(exchange.baseUrl, http); + + const reserveKeyPair = generateReserveKeypair(); + + await topupReserveWithDemobank( + reserveKeyPair.reservePub, + bank.baseUrl, + exchangeInfo, + "TESTKUDOS:10", + ); + + await exchange.runWirewatchOnce(); + + const d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8"); + + const coin = await withdrawCoin({ + http, + cryptoApi, + reserveKeyPair, + denom: d1, + exchangeBaseUrl: exchange.baseUrl, + }); + + await depositCoin({ + amount: "TESTKUDOS:4", + coin: coin, + cryptoApi, + exchangeBaseUrl: exchange.baseUrl, + http, + }); + + const refreshDenoms = [ + findDenomOrThrow(exchangeInfo, "TESTKUDOS:1"), + findDenomOrThrow(exchangeInfo, "TESTKUDOS:1"), + ]; + + const freshCoins = await refreshCoin({ + oldCoin: coin, + cryptoApi, + http, + newDenoms: refreshDenoms, + }); + } catch (e) { + if (e instanceof OperationFailedError) { + console.log(e); + console.log(j2s(e.operationError)); + } else { + console.log(e); + } + throw e; + } +} + +runWalletDblessTest.suites = ["wallet"]; diff --git a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts index 5ba1fa893..19668d760 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts @@ -18,8 +18,12 @@ * Imports. */ import { TalerErrorCode } from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { GlobalTestState, BankApi, BankAccessApi } from "../harness/harness.js"; +import { + WalletApiOperation, + BankApi, + BankAccessApi, +} from "@gnu-taler/taler-wallet-core"; +import { GlobalTestState } from "../harness/harness.js"; import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; /** diff --git a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-bank-integrated.ts b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-bank-integrated.ts index 25df19e46..e8a8c5028 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-bank-integrated.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-bank-integrated.ts @@ -17,10 +17,13 @@ /** * Imports. */ -import { GlobalTestState, BankApi, BankAccessApi } from "../harness/harness.js"; +import { GlobalTestState } from "../harness/harness.js"; import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; -import { codecForBalancesResponse } from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { + WalletApiOperation, + BankApi, + BankAccessApi, +} from "@gnu-taler/taler-wallet-core"; /** * Run test for basic, bank-integrated withdrawal. @@ -41,18 +44,24 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) { // Hand it to the wallet - const r1 = await wallet.client.call(WalletApiOperation.GetWithdrawalDetailsForUri, { - talerWithdrawUri: wop.taler_withdraw_uri, - }); + const r1 = await wallet.client.call( + WalletApiOperation.GetWithdrawalDetailsForUri, + { + talerWithdrawUri: wop.taler_withdraw_uri, + }, + ); await wallet.runPending(); // Withdraw - const r2 = await wallet.client.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, { - exchangeBaseUrl: exchange.baseUrl, - talerWithdrawUri: wop.taler_withdraw_uri, - }); + const r2 = await wallet.client.call( + WalletApiOperation.AcceptBankIntegratedWithdrawal, + { + exchangeBaseUrl: exchange.baseUrl, + talerWithdrawUri: wop.taler_withdraw_uri, + }, + ); await wallet.runPending(); // Confirm it diff --git a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-fakebank.ts b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-fakebank.ts index abd25d282..5860aaf88 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-fakebank.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-fakebank.ts @@ -19,13 +19,11 @@ */ import { GlobalTestState, - BankApi, WalletCli, setupDb, ExchangeService, FakeBankService, } from "../harness/harness.js"; -import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; import { URL } from "@gnu-taler/taler-util"; diff --git a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-manual.ts b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-manual.ts index 2f88b3024..6ae0e65e7 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-manual.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-manual.ts @@ -17,9 +17,9 @@ /** * Imports. */ -import { GlobalTestState, BankApi } from "../harness/harness.js"; +import { GlobalTestState } from "../harness/harness.js"; import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { WalletApiOperation, BankApi } from "@gnu-taler/taler-wallet-core"; /** * Run test for basic, bank-integrated withdrawal. @@ -27,12 +27,8 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; export async function runTestWithdrawalManualTest(t: GlobalTestState) { // Set up test environment - const { - wallet, - bank, - exchange, - exchangeBankAccount, - } = await createSimpleTestkudosEnvironment(t); + const { wallet, bank, exchange, exchangeBankAccount } = + await createSimpleTestkudosEnvironment(t); // Create a withdrawal operation @@ -42,11 +38,13 @@ export async function runTestWithdrawalManualTest(t: GlobalTestState) { exchangeBaseUrl: exchange.baseUrl, }); - - const wres = await wallet.client.call(WalletApiOperation.AcceptManualWithdrawal, { - exchangeBaseUrl: exchange.baseUrl, - amount: "TESTKUDOS:10", - }); + const wres = await wallet.client.call( + WalletApiOperation.AcceptManualWithdrawal, + { + exchangeBaseUrl: exchange.baseUrl, + amount: "TESTKUDOS:10", + }, + ); const reservePub: string = wres.reservePub; diff --git a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts index 844904132..3839266c0 100644 --- a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts +++ b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts @@ -87,6 +87,7 @@ import { runExchangeTimetravelTest } from "./test-exchange-timetravel.js"; import { runDenomUnofferedTest } from "./test-denom-unoffered.js"; import { runWithdrawalFakebankTest } from "./test-withdrawal-fakebank.js"; import { runClauseSchnorrTest } from "./test-clause-schnorr.js"; +import { runWalletDblessTest } from "./test-wallet-dbless.js"; /** * Test runner. @@ -162,6 +163,7 @@ const allTests: TestMainFunction[] = [ runWalletBackupBasicTest, runWalletBackupDoublespendTest, runWallettestingTest, + runWalletDblessTest, runWithdrawalAbortBankTest, runWithdrawalBankIntegratedTest, ]; diff --git a/packages/taler-wallet-core/src/bank-api-client.ts b/packages/taler-wallet-core/src/bank-api-client.ts new file mode 100644 index 000000000..744c3b833 --- /dev/null +++ b/packages/taler-wallet-core/src/bank-api-client.ts @@ -0,0 +1,249 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Client for the Taler (demo-)bank. + */ + +/** + * Imports. + */ +import { + AmountString, + buildCodecForObject, + Codec, + codecForString, + encodeCrock, + getRandomBytes, +} from "@gnu-taler/taler-util"; +import { + HttpRequestLibrary, + readSuccessResponseJsonOrErrorCode, + readSuccessResponseJsonOrThrow, +} from "./index.browser.js"; + +export enum CreditDebitIndicator { + Credit = "credit", + Debit = "debit", +} + +export interface BankAccountBalanceResponse { + balance: { + amount: AmountString; + credit_debit_indicator: CreditDebitIndicator; + }; +} + +export interface BankServiceHandle { + readonly baseUrl: string; + readonly http: HttpRequestLibrary; +} + +export interface BankUser { + username: string; + password: string; + accountPaytoUri: string; +} + +export interface WithdrawalOperationInfo { + withdrawal_id: string; + taler_withdraw_uri: string; +} + +/** + * FIXME: Rename, this is not part of the integration test harness anymore. + */ +export interface HarnessExchangeBankAccount { + accountName: string; + accountPassword: string; + accountPaytoUri: string; + wireGatewayApiBaseUrl: string; +} + +/** + * Helper function to generate the "Authorization" HTTP header. + */ +function makeBasicAuthHeader(username: string, password: string): string { + const auth = `${username}:${password}`; + const authEncoded: string = Buffer.from(auth).toString("base64"); + return `Basic ${authEncoded}`; +} + +const codecForWithdrawalOperationInfo = (): Codec<WithdrawalOperationInfo> => + buildCodecForObject<WithdrawalOperationInfo>() + .property("withdrawal_id", codecForString()) + .property("taler_withdraw_uri", codecForString()) + .build("WithdrawalOperationInfo"); + +export namespace BankApi { + export async function registerAccount( + bank: BankServiceHandle, + username: string, + password: string, + ): Promise<BankUser> { + const url = new URL("testing/register", bank.baseUrl); + 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) { + throw new Error(); + } + try { + const respJson = await resp.json(); + // LibEuFin demobank returns payto URI in response + if (respJson.paytoUri) { + paytoUri = respJson.paytoUri; + } + } catch (e) {} + return { + password, + username, + accountPaytoUri: paytoUri, + }; + } + + export async function createRandomBankUser( + bank: BankServiceHandle, + ): Promise<BankUser> { + const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase(); + const password = "pw-" + encodeCrock(getRandomBytes(10)).toLowerCase(); + return await registerAccount(bank, username, password); + } + + export async function adminAddIncoming( + bank: BankServiceHandle, + params: { + exchangeBankAccount: HarnessExchangeBankAccount; + amount: string; + reservePub: string; + debitAccountPayto: string; + }, + ) { + let maybeBaseUrl = bank.baseUrl; + let url = new URL( + `taler-wire-gateway/${params.exchangeBankAccount.accountName}/admin/add-incoming`, + maybeBaseUrl, + ); + await bank.http.postJson( + url.href, + { + amount: params.amount, + reserve_pub: params.reservePub, + debit_account: params.debitAccountPayto, + }, + { + headers: { + Authorization: makeBasicAuthHeader( + params.exchangeBankAccount.accountName, + params.exchangeBankAccount.accountPassword, + ), + }, + }, + ); + } + + export async function confirmWithdrawalOperation( + bank: BankServiceHandle, + bankUser: BankUser, + wopi: WithdrawalOperationInfo, + ): Promise<void> { + const url = new URL( + `accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/confirm`, + bank.baseUrl, + ); + await bank.http.postJson( + url.href, + {}, + { + headers: { + Authorization: makeBasicAuthHeader( + bankUser.username, + bankUser.password, + ), + }, + }, + ); + } + + export async function abortWithdrawalOperation( + bank: BankServiceHandle, + bankUser: BankUser, + wopi: WithdrawalOperationInfo, + ): Promise<void> { + const url = new URL( + `accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/abort`, + bank.baseUrl, + ); + await bank.http.postJson( + url.href, + {}, + { + headers: { + Authorization: makeBasicAuthHeader( + bankUser.username, + bankUser.password, + ), + }, + }, + ); + } +} + +export namespace BankAccessApi { + export async function getAccountBalance( + bank: BankServiceHandle, + bankUser: BankUser, + ): Promise<BankAccountBalanceResponse> { + const url = new URL(`accounts/${bankUser.username}`, bank.baseUrl); + const resp = await bank.http.get(url.href, { + headers: { + Authorization: makeBasicAuthHeader( + bankUser.username, + bankUser.password, + ), + }, + }); + return await resp.json(); + } + + export async function createWithdrawalOperation( + bank: BankServiceHandle, + bankUser: BankUser, + amount: string, + ): Promise<WithdrawalOperationInfo> { + const url = new URL( + `accounts/${bankUser.username}/withdrawals`, + bank.baseUrl, + ); + const resp = await bank.http.postJson( + url.href, + { + amount, + }, + { + headers: { + Authorization: makeBasicAuthHeader( + bankUser.username, + bankUser.password, + ), + }, + }, + ); + return readSuccessResponseJsonOrThrow( + resp, + codecForWithdrawalOperationInfo(), + ); + } +} diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts index 16446bb9e..b5a5950b1 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts @@ -22,20 +22,22 @@ /** * Imports. */ -import { CoinRecord, DenominationRecord, WireFee } from "../../db.js"; +import { DenominationRecord, WireFee } from "../../db.js"; import { CryptoWorker } from "./cryptoWorkerInterface.js"; import { + BlindedDenominationSignature, CoinDepositPermission, CoinEnvelope, RecoupRefreshRequest, RecoupRequest, + UnblindedSignature, } from "@gnu-taler/taler-util"; import { BenchmarkResult, - PlanchetCreationResult, + WithdrawalPlanchet, PlanchetCreationRequest, DepositInfo, MakeSyncSignatureRequest, @@ -324,10 +326,19 @@ export class CryptoApi { return p; } - createPlanchet( - req: PlanchetCreationRequest, - ): Promise<PlanchetCreationResult> { - return this.doRpc<PlanchetCreationResult>("createPlanchet", 1, req); + createPlanchet(req: PlanchetCreationRequest): Promise<WithdrawalPlanchet> { + return this.doRpc<WithdrawalPlanchet>("createPlanchet", 1, req); + } + + unblindDenominationSignature(req: { + planchet: WithdrawalPlanchet; + evSig: BlindedDenominationSignature; + }): Promise<UnblindedSignature> { + return this.doRpc<UnblindedSignature>( + "unblindDenominationSignature", + 1, + req, + ); } createTipPlanchet(req: DeriveTipRequest): Promise<DerivedTipPlanchet> { diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts index af77e2be4..15a086ae1 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts @@ -53,7 +53,7 @@ import { Logger, MakeSyncSignatureRequest, PlanchetCreationRequest, - PlanchetCreationResult, + WithdrawalPlanchet, randomBytes, RecoupRefreshRequest, RecoupRequest, @@ -70,6 +70,9 @@ import { Timestamp, timestampTruncateToSecond, typedArrayConcat, + BlindedDenominationSignature, + RsaUnblindedSignature, + UnblindedSignature, } from "@gnu-taler/taler-util"; import bigint from "big-integer"; import { DenominationRecord, WireFee } from "../../db.js"; @@ -169,7 +172,7 @@ export class CryptoImplementation { */ async createPlanchet( req: PlanchetCreationRequest, - ): Promise<PlanchetCreationResult> { + ): Promise<WithdrawalPlanchet> { const denomPub = req.denomPub; if (denomPub.cipher === DenomKeyType.Rsa) { const reservePub = decodeCrock(req.reservePub); @@ -200,7 +203,7 @@ export class CryptoImplementation { priv: req.reservePriv, }); - const planchet: PlanchetCreationResult = { + const planchet: WithdrawalPlanchet = { blindingKey: encodeCrock(derivedPlanchet.bks), coinEv, coinPriv: encodeCrock(derivedPlanchet.coinPriv), @@ -428,6 +431,30 @@ export class CryptoImplementation { }; } + unblindDenominationSignature(req: { + planchet: WithdrawalPlanchet; + evSig: BlindedDenominationSignature; + }): UnblindedSignature { + if (req.evSig.cipher === DenomKeyType.Rsa) { + if (req.planchet.denomPub.cipher !== DenomKeyType.Rsa) { + throw new Error( + "planchet cipher does not match blind signature cipher", + ); + } + const denomSig = rsaUnblind( + decodeCrock(req.evSig.blinded_rsa_signature), + decodeCrock(req.planchet.denomPub.rsa_public_key), + decodeCrock(req.planchet.blindingKey), + ); + return { + cipher: DenomKeyType.Rsa, + rsa_signature: encodeCrock(denomSig), + }; + } else { + throw Error(`unblinding for cipher ${req.evSig.cipher} not implemented`); + } + } + /** * Unblind a blindly signed value. */ diff --git a/packages/taler-wallet-core/src/index.ts b/packages/taler-wallet-core/src/index.ts index 179ba6b8f..c657290f1 100644 --- a/packages/taler-wallet-core/src/index.ts +++ b/packages/taler-wallet-core/src/index.ts @@ -36,7 +36,7 @@ export * from "./db-utils.js"; export { CryptoImplementation } from "./crypto/workers/cryptoImplementation.js"; export type { CryptoWorker } from "./crypto/workers/cryptoWorkerInterface.js"; export { CryptoWorkerFactory, CryptoApi } from "./crypto/workers/cryptoApi.js"; -export { SynchronousCryptoWorker } from "./crypto/workers/synchronousWorker.js" +export { SynchronousCryptoWorker } from "./crypto/workers/synchronousWorker.js"; export * from "./pending-types.js"; @@ -47,3 +47,12 @@ export * from "./wallet.js"; export * from "./operations/backup/index.js"; export { makeEventId } from "./operations/transactions.js"; + +export * from "./operations/exchanges.js"; + +export * from "./bank-api-client.js"; + +export * from "./operations/reserves.js"; +export * from "./operations/withdraw.js"; + +export * from "./crypto/workers/synchronousWorkerFactory.js"; diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index e45da7b4c..a5d6c93cf 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -20,6 +20,7 @@ import { buildCodecForObject, canonicalJson, Codec, + codecForDepositSuccess, codecForString, codecForTimestamp, codecOptional, @@ -32,6 +33,7 @@ import { GetFeeForDepositRequest, getRandomBytes, getTimestampNow, + hashWire, Logger, NotificationType, parsePaytoUri, @@ -57,7 +59,6 @@ import { generateDepositPermissions, getCandidatePayCoins, getTotalPaymentCost, - hashWire, } from "./pay.js"; import { getTotalRefreshCost } from "./refresh.js"; @@ -66,43 +67,6 @@ import { getTotalRefreshCost } from "./refresh.js"; */ const logger = new Logger("deposits.ts"); -interface DepositSuccess { - // Optional base URL of the exchange for looking up wire transfers - // associated with this transaction. If not given, - // the base URL is the same as the one used for this request. - // Can be used if the base URL for /transactions/ differs from that - // for /coins/, i.e. for load balancing. Clients SHOULD - // respect the transaction_base_url if provided. Any HTTP server - // belonging to an exchange MUST generate a 307 or 308 redirection - // to the correct base URL should a client uses the wrong base - // URL, or if the base URL has changed since the deposit. - transaction_base_url?: string; - - // timestamp when the deposit was received by the exchange. - exchange_timestamp: Timestamp; - - // the EdDSA signature of TALER_DepositConfirmationPS using a current - // signing key of the exchange affirming the successful - // deposit and that the exchange will transfer the funds after the refund - // deadline, or as soon as possible if the refund deadline is zero. - exchange_sig: string; - - // public EdDSA key of the exchange that was used to - // generate the signature. - // Should match one of the exchange's signing keys from /keys. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: string; -} - -const codecForDepositSuccess = (): Codec<DepositSuccess> => - buildCodecForObject<DepositSuccess>() - .property("exchange_pub", codecForString()) - .property("exchange_sig", codecForString()) - .property("exchange_timestamp", codecForTimestamp) - .property("transaction_base_url", codecOptional(codecForString())) - .build("DepositSuccess"); - async function resetDepositGroupRetry( ws: InternalWalletState, depositGroupId: string, @@ -202,7 +166,6 @@ async function processDepositGroupImpl( } const perm = depositPermissions[i]; let requestBody: any; - logger.info("creating v10 deposit request"); requestBody = { contribution: Amounts.stringify(perm.contribution), merchant_payto_uri: depositGroup.wire.payto_uri, diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 9d4a56fff..2006b792f 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -43,6 +43,7 @@ import { codecForAny, DenominationPubKey, DenomKeyType, + ExchangeKeysJson, } from "@gnu-taler/taler-util"; import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util"; import { CryptoApi } from "../crypto/workers/cryptoApi.js"; @@ -292,12 +293,37 @@ async function validateWireInfo( }; } +export interface ExchangeInfo { + wire: ExchangeWireJson; + keys: ExchangeKeysDownloadResult; +} + +export async function downloadExchangeInfo( + exchangeBaseUrl: string, + http: HttpRequestLibrary, +): Promise<ExchangeInfo> { + const wireInfo = await downloadExchangeWireInfo( + exchangeBaseUrl, + http, + Duration.getForever(), + ); + const keysInfo = await downloadExchangeKeysInfo( + exchangeBaseUrl, + http, + Duration.getForever(), + ); + return { + keys: keysInfo, + wire: wireInfo, + }; +} + /** * Fetch wire information for an exchange. * * @param exchangeBaseUrl Exchange base URL, assumed to be already normalized. */ -async function downloadExchangeWithWireInfo( +async function downloadExchangeWireInfo( exchangeBaseUrl: string, http: HttpRequestLibrary, timeout: Duration, @@ -374,7 +400,7 @@ interface ExchangeKeysDownloadResult { /** * Download and validate an exchange's /keys data. */ -async function downloadKeysInfo( +async function downloadExchangeKeysInfo( baseUrl: string, http: HttpRequestLibrary, timeout: Duration, @@ -526,10 +552,10 @@ async function updateExchangeFromUrlImpl( const timeout = getExchangeRequestTimeout(); - const keysInfo = await downloadKeysInfo(baseUrl, ws.http, timeout); + const keysInfo = await downloadExchangeKeysInfo(baseUrl, ws.http, timeout); logger.info("updating exchange /wire info"); - const wireInfoDownload = await downloadExchangeWithWireInfo( + const wireInfoDownload = await downloadExchangeWireInfo( baseUrl, ws.http, timeout, diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index 6001cac4f..9844dc52e 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -113,19 +113,6 @@ import { createRefreshGroup, getTotalRefreshCost } from "./refresh.js"; const logger = new Logger("pay.ts"); /** - * FIXME: Move this to crypto worker or at least talerCrypto.ts - */ -export function hashWire(paytoUri: string, salt: string): string { - const r = kdf( - 64, - stringToBytes(paytoUri + "\0"), - decodeCrock(salt), - stringToBytes("merchant-wire-signature"), - ); - return encodeCrock(r); -} - -/** * Compute the total cost of a payment to the customer. * * This includes the amount taken by the merchant, fees (wire/deposit) contributed diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 550119de1..cc2a1c566 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -17,6 +17,7 @@ import { DenomKeyType, encodeCrock, + ExchangeMeltRequest, ExchangeProtocolVersion, ExchangeRefreshRevealRequest, getRandomBytes, @@ -394,17 +395,14 @@ async function refreshMelt( `coins/${oldCoin.coinPub}/melt`, oldCoin.exchangeBaseUrl, ); - let meltReqBody: any; - if (oldDenom.denomPub.cipher === DenomKeyType.Rsa) { - meltReqBody = { - coin_pub: oldCoin.coinPub, - confirm_sig: derived.confirmSig, - denom_pub_hash: oldCoin.denomPubHash, - denom_sig: oldCoin.denomSig, - rc: derived.hash, - value_with_fee: Amounts.stringify(derived.meltValueWithFee), - }; - } + const meltReqBody: ExchangeMeltRequest = { + coin_pub: oldCoin.coinPub, + confirm_sig: derived.confirmSig, + denom_pub_hash: oldCoin.denomPubHash, + denom_sig: oldCoin.denomSig, + rc: derived.hash, + value_with_fee: Amounts.stringify(derived.meltValueWithFee), + }; const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], async () => { return await ws.http.postJson(reqUrl.href, meltReqBody, { diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts index a16d3ec31..d91ce89f1 100644 --- a/packages/taler-wallet-core/src/operations/reserves.ts +++ b/packages/taler-wallet-core/src/operations/reserves.ts @@ -780,7 +780,7 @@ export async function createTalerWithdrawReserve( selectedExchange: string, ): Promise<AcceptWithdrawalResponse> { await updateExchangeFromUrl(ws, selectedExchange); - const withdrawInfo = await getBankWithdrawalInfo(ws, talerWithdrawUri); + const withdrawInfo = await getBankWithdrawalInfo(ws.http, talerWithdrawUri); const exchangePaytoUri = await getExchangePaytoUri( ws, selectedExchange, diff --git a/packages/taler-wallet-core/src/operations/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts index d6f0626dd..93f48fb83 100644 --- a/packages/taler-wallet-core/src/operations/testing.ts +++ b/packages/taler-wallet-core/src/operations/testing.ts @@ -74,7 +74,7 @@ function makeId(length: number): string { /** * Helper function to generate the "Authorization" HTTP header. */ -function makeAuth(username: string, password: string): string { +function makeBasicAuthHeader(username: string, password: string): string { const auth = `${username}:${password}`; const authEncoded: string = Buffer.from(auth).toString("base64"); return `Basic ${authEncoded}`; @@ -89,7 +89,7 @@ export async function withdrawTestBalance( const bankUser = await registerRandomBankUser(ws.http, bankBaseUrl); logger.trace(`Registered bank user ${JSON.stringify(bankUser)}`); - const wresp = await createBankWithdrawalUri( + const wresp = await createDemoBankWithdrawalUri( ws.http, bankBaseUrl, bankUser, @@ -119,7 +119,11 @@ function getMerchantAuthHeader(m: MerchantBackendInfo): Record<string, string> { return {}; } -async function createBankWithdrawalUri( +/** + * Use the testing API of a demobank to create a taler://withdraw URI + * that the wallet can then use to make a withdrawal. + */ +export async function createDemoBankWithdrawalUri( http: HttpRequestLibrary, bankBaseUrl: string, bankUser: BankUser, @@ -136,7 +140,7 @@ async function createBankWithdrawalUri( }, { headers: { - Authorization: makeAuth(bankUser.username, bankUser.password), + Authorization: makeBasicAuthHeader(bankUser.username, bankUser.password), }, }, ); @@ -159,7 +163,7 @@ async function confirmBankWithdrawalUri( {}, { headers: { - Authorization: makeAuth(bankUser.username, bankUser.password), + Authorization: makeBasicAuthHeader(bankUser.username, bankUser.password), }, }, ); diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index ae3763a02..392cecf0b 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -59,7 +59,10 @@ import { WithdrawalGroupRecord, } from "../db.js"; import { walletCoreDebugFlags } from "../util/debugFlags.js"; -import { readSuccessResponseJsonOrThrow } from "../util/http.js"; +import { + HttpRequestLibrary, + readSuccessResponseJsonOrThrow, +} from "../util/http.js"; import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; import { guardOperationException, @@ -271,9 +274,11 @@ export function selectWithdrawalDenominations( /** * Get information about a withdrawal from * a taler://withdraw URI by asking the bank. + * + * FIXME: Move into bank client. */ export async function getBankWithdrawalInfo( - ws: InternalWalletState, + http: HttpRequestLibrary, talerWithdrawUri: string, ): Promise<BankWithdrawDetails> { const uriResult = parseWithdrawUri(talerWithdrawUri); @@ -283,7 +288,7 @@ export async function getBankWithdrawalInfo( const configReqUrl = new URL("config", uriResult.bankIntegrationApiBaseUrl); - const configResp = await ws.http.get(configReqUrl.href); + const configResp = await http.get(configReqUrl.href); const config = await readSuccessResponseJsonOrThrow( configResp, codecForTalerConfigResponse(), @@ -309,7 +314,7 @@ export async function getBankWithdrawalInfo( `withdrawal-operation/${uriResult.withdrawalOperationId}`, uriResult.bankIntegrationApiBaseUrl, ); - const resp = await ws.http.get(reqUrl.href); + const resp = await http.get(reqUrl.href); const status = await readSuccessResponseJsonOrThrow( resp, codecForWithdrawOperationStatusResponse(), @@ -1076,7 +1081,7 @@ export async function getWithdrawalDetailsForUri( talerWithdrawUri: string, ): Promise<WithdrawUriInfoResponse> { logger.trace(`getting withdrawal details for URI ${talerWithdrawUri}`); - const info = await getBankWithdrawalInfo(ws, talerWithdrawUri); + const info = await getBankWithdrawalInfo(ws.http, talerWithdrawUri); logger.trace(`got bank info`); if (info.suggestedExchange) { // FIXME: right now the exchange gets permanently added, diff --git a/packages/taler-wallet-core/src/util/http.ts b/packages/taler-wallet-core/src/util/http.ts index 3a7062c99..43fe29bba 100644 --- a/packages/taler-wallet-core/src/util/http.ts +++ b/packages/taler-wallet-core/src/util/http.ts @@ -34,6 +34,7 @@ import { timestampMax, TalerErrorDetails, Codec, + j2s, } from "@gnu-taler/taler-util"; import { TalerErrorCode } from "@gnu-taler/taler-util"; @@ -131,6 +132,11 @@ export async function readTalerErrorResponse( const errJson = await httpResponse.json(); const talerErrorCode = errJson.code; if (typeof talerErrorCode !== "number") { + logger.warn( + `malformed error response (status ${httpResponse.status}): ${j2s( + errJson, + )}`, + ); throw new OperationFailedError( makeErrorDetails( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, diff --git a/packages/taler-wallet-webextension/src/i18n/taler-wallet-webextension.pot b/packages/taler-wallet-webextension/src/i18n/taler-wallet-webextension.pot new file mode 100644 index 000000000..4fd500d25 --- /dev/null +++ b/packages/taler-wallet-webextension/src/i18n/taler-wallet-webextension.pot @@ -0,0 +1,366 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-11-23 00:00+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/NavigationBar.tsx:86 +#, c-format +msgid "Balance" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/NavigationBar.tsx:87 +#, c-format +msgid "Pending" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/NavigationBar.tsx:88 +#, c-format +msgid "Backup" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/NavigationBar.tsx:89 +#, c-format +msgid "Settings" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/NavigationBar.tsx:90 +#, c-format +msgid "Dev" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx:127 +#, c-format +msgid "Add provider" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx:137 +#, c-format +msgid "Sync all backups" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx:139 +#, c-format +msgid "Sync now" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/popup/BalancePage.tsx:79 +#, c-format +msgid "You have no balance to show. Need some %1$s getting started?" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx:145 +#, c-format +msgid "< Back" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx:156 +#, c-format +msgid "Next" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx:210 +#, c-format +msgid "< Back" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx:213 +#, c-format +msgid "Add provider" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:57 +#, c-format +msgid "Loading..." +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:64 +#, c-format +msgid "There was an error loading the provider detail for "%1$s"" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:75 +#, c-format +msgid "There is not known provider with url "%1$s". Redirecting back..." +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:131 +#, c-format +msgid "Back up" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:142 +#, c-format +msgid "Extend" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:148 +#, c-format +msgid "" +"terms has changed, extending the service will imply accepting the new terms of " +"service" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:158 +#, c-format +msgid "old" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:162 +#, c-format +msgid "new" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:169 +#, c-format +msgid "fee" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:177 +#, c-format +msgid "storage" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:190 +#, c-format +msgid "< back" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:194 +#, c-format +msgid "remove provider" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:213 +#, c-format +msgid "There is conflict with another backup from %1$s" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:228 +#, c-format +msgid "Unknown backup problem: %1$s" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx:247 +#, c-format +msgid "service paid" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/popup/Settings.tsx:46 +#, c-format +msgid "Permissions" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:37 +#, c-format +msgid "Exchange doesn't have terms of service" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:49 +#, c-format +msgid "Exchange doesn't have terms of service" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:56 +#, c-format +msgid "Review exchange terms of service" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:63 +#, c-format +msgid "Review new version of terms of service" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:75 +#, c-format +msgid "Show terms of service" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:83 +#, c-format +msgid "I accept the exchange terms of service" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:127 +#, c-format +msgid "Hide terms of service" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/TermsOfServiceSection.tsx:136 +#, c-format +msgid "I accept the exchange terms of service" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx:110 +#, c-format +msgid "Cancel" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx:114 +#, c-format +msgid "Loading terms.." +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx:121 +#, c-format +msgid "Add exchange" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx:126 +#, c-format +msgid "Add exchange" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx:131 +#, c-format +msgid "Add exchange anyway" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx:133 +#, c-format +msgid "Cancel" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx:149 +#, c-format +msgid "Next" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/BalancePage.tsx:83 +#, c-format +msgid "You have no balance to show. Need some %1$s getting started?" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx:104 +#, c-format +msgid "Add exchange" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx:144 +#, c-format +msgid "Add exchange" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Settings.tsx:84 +#, c-format +msgid "Permissions" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Settings.tsx:95 +#, c-format +msgid "Known exchanges" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Transaction.tsx:154 +#, c-format +msgid "< Back" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Transaction.tsx:159 +#, c-format +msgid "retry" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Transaction.tsx:163 +#, c-format +msgid "Forget" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Transaction.tsx:194 +#, c-format +msgid "Cancel" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/wallet/Transaction.tsx:198 +#, c-format +msgid "Confirm" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Pay.tsx:211 +#, c-format +msgid "Pay with a mobile phone" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Pay.tsx:211 +#, c-format +msgid "Hide QR" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Pay.tsx:241 +#, c-format +msgid "Pay" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Pay.tsx:265 +#, c-format +msgid "Withdraw digital cash" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Pay.tsx:295 +#, c-format +msgid "Digital cash payment" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:101 +#, c-format +msgid "Digital cash withdrawal" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:149 +#, c-format +msgid "Cancel exchange selection" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:150 +#, c-format +msgid "Confirm exchange selection" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:155 +#, c-format +msgid "Switch exchange" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:174 +#, c-format +msgid "Confirm withdrawal" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:183 +#, c-format +msgid "Withdraw anyway" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Withdraw.tsx:310 +#, c-format +msgid "missing withdraw uri" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Deposit.tsx:119 +#, c-format +msgid "Digital cash payment" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Deposit.tsx:133 +#, c-format +msgid "Digital cash payment" +msgstr "" + +#: /home/dold/repos/taler/wallet-core/packages/taler-wallet-webextension/src/cta/Deposit.tsx:186 +#, c-format +msgid "Digital cash deposit" +msgstr "" + diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index c306b17a9..31b46d88d 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -38,7 +38,7 @@ import { RemoveBackupProviderRequest } from "@gnu-taler/taler-wallet-core"; import { DepositFee } from "@gnu-taler/taler-wallet-core/src/operations/deposits"; -import { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core/src/operations/withdraw"; +import type { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core/src/operations/withdraw"; import { MessageFromBackend } from "./wxBackend"; /** |