diff options
author | Florian Dold <florian@dold.me> | 2022-06-21 12:40:12 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2022-07-08 11:08:30 +0200 |
commit | b214934b75418d0d01c9556577d9594f1db5a319 (patch) | |
tree | d8ec18217a16e6b89859b30003a4a825fc63a66e | |
parent | 05cdbfb534bb194dbe6bdf049113ebea8139234f (diff) |
wallet-core: P2P push payments (still incomplete)
-rw-r--r-- | packages/taler-util/src/talerCrypto.ts | 2 | ||||
-rw-r--r-- | packages/taler-util/src/talerTypes.ts | 42 | ||||
-rw-r--r-- | packages/taler-util/src/walletTypes.ts | 27 | ||||
-rw-r--r-- | packages/taler-wallet-cli/src/harness/harness.ts | 30 | ||||
-rw-r--r-- | packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer.ts | 53 | ||||
-rw-r--r-- | packages/taler-wallet-cli/src/integrationtests/testrunner.ts | 2 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/crypto/cryptoImplementation.ts | 120 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/crypto/cryptoTypes.ts | 2 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/db.ts | 70 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/peer-to-peer.ts | 222 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/wallet-api-types.ts | 9 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/wallet.ts | 6 |
12 files changed, 546 insertions, 39 deletions
diff --git a/packages/taler-util/src/talerCrypto.ts b/packages/taler-util/src/talerCrypto.ts index e2360b095..188f5ec0a 100644 --- a/packages/taler-util/src/talerCrypto.ts +++ b/packages/taler-util/src/talerCrypto.ts @@ -773,6 +773,8 @@ export enum TalerSignaturePurpose { WALLET_COIN_LINK = 1204, WALLET_COIN_RECOUP_REFRESH = 1206, WALLET_AGE_ATTESTATION = 1207, + WALLET_PURSE_CREATE = 1210, + WALLET_PURSE_DEPOSIT = 1211, 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 7fc3fcba0..7afa76e9e 100644 --- a/packages/taler-util/src/talerTypes.ts +++ b/packages/taler-util/src/talerTypes.ts @@ -565,8 +565,8 @@ export interface MerchantAbortPayRefundDetails { refund_amount: string; /** - * Fee for the refund. - */ + * Fee for the refund. + */ refund_fee: string; /** @@ -1794,3 +1794,41 @@ export const codecForDepositSuccess = (): Codec<DepositSuccess> => .property("exchange_timestamp", codecForTimestamp) .property("transaction_base_url", codecOptional(codecForString())) .build("DepositSuccess"); + +export interface PurseDeposit { + /** + * Amount to be deposited, can be a fraction of the + * coin's total value. + */ + amount: AmountString; + + /** + * Hash of denomination RSA key with which the coin is signed. + */ + denom_pub_hash: HashCodeString; + + /** + * Exchange's unblinded RSA signature of the coin. + */ + ub_sig: UnblindedSignature; + + /** + * Age commitment hash for the coin, if the denomination is age-restricted. + */ + h_age_commitment?: HashCodeString; + + // FIXME-Oec: proof of age is missing. + + /** + * Signature over TALER_PurseDepositSignaturePS + * of purpose TALER_SIGNATURE_WALLET_PURSE_DEPOSIT + * made by the customer with the + * coin's private key. + */ + coin_sig: EddsaSignatureString; + + /** + * Public key of the coin being deposited into the purse. + */ + coin_pub: EddsaPublicKeyString; +} diff --git a/packages/taler-util/src/walletTypes.ts b/packages/taler-util/src/walletTypes.ts index 2e5dd418d..4b1911164 100644 --- a/packages/taler-util/src/walletTypes.ts +++ b/packages/taler-util/src/walletTypes.ts @@ -32,10 +32,7 @@ import { codecForAmountJson, codecForAmountString, } from "./amounts.js"; -import { - codecForTimestamp, - TalerProtocolTimestamp, -} from "./time.js"; +import { codecForTimestamp, TalerProtocolTimestamp } from "./time.js"; import { buildCodecForObject, codecForString, @@ -1230,15 +1227,14 @@ export interface ForcedCoinSel { } export interface TestPayResult { - payCoinSelection: PayCoinSelection, + payCoinSelection: PayCoinSelection; } - /** * Result of selecting coins, contains the exchange, and selected * coins with their denomination. */ - export interface PayCoinSelection { +export interface PayCoinSelection { /** * Amount requested by the merchant. */ @@ -1263,4 +1259,19 @@ export interface TestPayResult { * How much of the deposit fees is the customer paying? */ customerDepositFees: AmountJson; -}
\ No newline at end of file +} + +export interface InitiatePeerPushPaymentRequest { + amount: AmountString; +} + +export interface InitiatePeerPushPaymentResponse { + pursePub: string; + mergePriv: string; +} + +export const codecForInitiatePeerPushPaymentRequest = + (): Codec<InitiatePeerPushPaymentRequest> => + buildCodecForObject<InitiatePeerPushPaymentRequest>() + .property("amount", codecForAmountString()) + .build("InitiatePeerPushPaymentRequest"); diff --git a/packages/taler-wallet-cli/src/harness/harness.ts b/packages/taler-wallet-cli/src/harness/harness.ts index 74a523343..3b58219bb 100644 --- a/packages/taler-wallet-cli/src/harness/harness.ts +++ b/packages/taler-wallet-cli/src/harness/harness.ts @@ -1296,6 +1296,36 @@ export class ExchangeService implements ExchangeServiceInterface { ); } } + + await runCommand( + this.globalState, + "exchange-offline", + "taler-exchange-offline", + [ + "-c", + this.configFilename, + "global-fee", + // year + "now", + // history fee + `${this.exchangeConfig.currency}:0.01`, + // kyc fee + `${this.exchangeConfig.currency}:0.01`, + // account fee + `${this.exchangeConfig.currency}:0.01`, + // purse fee + `${this.exchangeConfig.currency}:0.01`, + // purse timeout + "1h", + // kyc timeout + "1h", + // history expiration + "1year", + // free purses per account + "5", + "upload", + ], + ); } async revokeDenomination(denomPubHash: string) { diff --git a/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer.ts b/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer.ts new file mode 100644 index 000000000..4d27f45d7 --- /dev/null +++ b/packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer.ts @@ -0,0 +1,53 @@ +/* + 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 { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { GlobalTestState } from "../harness/harness.js"; +import { + createSimpleTestkudosEnvironment, + withdrawViaBank, + makeTestPayment, +} from "../harness/helpers.js"; + +/** + * Run test for basic, bank-integrated withdrawal and payment. + */ +export async function runPeerToPeerTest(t: GlobalTestState) { + // Set up test environment + + const { wallet, bank, exchange, merchant } = + await createSimpleTestkudosEnvironment(t); + + // Withdraw digital cash into the wallet. + + await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" }); + + await wallet.runUntilDone(); + + const resp = await wallet.client.call( + WalletApiOperation.InitiatePeerPushPayment, + { + amount: "TESTKUDOS:5", + }, + ); + + console.log(resp); +} + +runPeerToPeerTest.suites = ["wallet"]; diff --git a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts index e8aef5136..cafcce79e 100644 --- a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts +++ b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts @@ -73,6 +73,7 @@ import { runPaymentDemoTest } from "./test-payment-on-demo"; import { runPaymentTransientTest } from "./test-payment-transient"; import { runPaymentZeroTest } from "./test-payment-zero.js"; import { runPaywallFlowTest } from "./test-paywall-flow"; +import { runPeerToPeerTest } from "./test-peer-to-peer.js"; import { runRefundTest } from "./test-refund"; import { runRefundAutoTest } from "./test-refund-auto"; import { runRefundGoneTest } from "./test-refund-gone"; @@ -153,6 +154,7 @@ const allTests: TestMainFunction[] = [ runPaymentZeroTest, runPayPaidTest, runPaywallFlowTest, + runPeerToPeerTest, runRefundAutoTest, runRefundGoneTest, runRefundIncrementalTest, diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts index 7c6b00bcc..1d3641836 100644 --- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts @@ -24,33 +24,44 @@ * Imports. */ -// FIXME: Crypto should not use DB Types! import { + AgeCommitmentProof, + AgeRestriction, AmountJson, Amounts, + AmountString, + BlindedDenominationSignature, + bufferForUint32, buildSigPS, CoinDepositPermission, CoinEnvelope, - createEddsaKeyPair, createHashContext, decodeCrock, DenomKeyType, DepositInfo, + ecdheGetPublic, eddsaGetPublic, + EddsaPublicKeyString, eddsaSign, eddsaVerify, encodeCrock, ExchangeProtocolVersion, + getRandomBytes, hash, + HashCodeString, hashCoinEv, hashCoinEvInner, + hashCoinPub, hashDenomPub, hashTruncate32, + kdf, + kdfKw, keyExchangeEcdheEddsa, Logger, MakeSyncSignatureRequest, PlanchetCreationRequest, - WithdrawalPlanchet, + PlanchetUnblindInfo, + PurseDeposit, RecoupRefreshRequest, RecoupRequest, RefreshPlanchetInfo, @@ -59,23 +70,14 @@ import { rsaVerify, setupTipPlanchet, stringToBytes, + TalerProtocolTimestamp, TalerSignaturePurpose, - BlindedDenominationSignature, UnblindedSignature, - PlanchetUnblindInfo, - TalerProtocolTimestamp, - kdfKw, - bufferForUint32, - kdf, - ecdheGetPublic, - getRandomBytes, - AgeCommitmentProof, - AgeRestriction, - hashCoinPub, - HashCodeString, + WithdrawalPlanchet, } from "@gnu-taler/taler-util"; import bigint from "big-integer"; -import { DenominationRecord, TipCoinSource, WireFee } from "../db.js"; +// FIXME: Crypto should not use DB Types! +import { DenominationRecord, WireFee } from "../db.js"; import { CreateRecoupRefreshReqRequest, CreateRecoupReqRequest, @@ -177,6 +179,12 @@ export interface TalerCryptoInterface { setupRefreshTransferPub( req: SetupRefreshTransferPubRequest, ): Promise<TransferPubResponse>; + + signPurseCreation(req: SignPurseCreationRequest): Promise<EddsaSigningResult>; + + signPurseDeposits( + req: SignPurseDepositsRequest, + ): Promise<SignPurseDepositsResponse>; } /** @@ -308,6 +316,16 @@ export const nullCrypto: TalerCryptoInterface = { ): Promise<TransferPubResponse> { throw new Error("Function not implemented."); }, + signPurseCreation: function ( + req: SignPurseCreationRequest, + ): Promise<EddsaSigningResult> { + throw new Error("Function not implemented."); + }, + signPurseDeposits: function ( + req: SignPurseDepositsRequest, + ): Promise<SignPurseDepositsResponse> { + throw new Error("Function not implemented."); + }, }; export type WithArg<X> = X extends (req: infer T) => infer R @@ -336,6 +354,31 @@ export interface SetupWithdrawalPlanchetRequest { coinNumber: number; } +export interface SignPurseCreationRequest { + pursePriv: string; + purseExpiration: TalerProtocolTimestamp; + purseAmount: AmountString; + hContractTerms: HashCodeString; + mergePub: EddsaPublicKeyString; + minAge: number; +} + +export interface SignPurseDepositsRequest { + pursePub: string; + exchangeBaseUrl: string; + coins: { + coinPub: string; + coinPriv: string; + contribution: AmountString; + denomPubHash: string; + denomSig: UnblindedSignature; + }[]; +} + +export interface SignPurseDepositsResponse { + deposits: PurseDeposit[]; +} + export interface RsaVerificationRequest { hm: string; sig: string; @@ -1212,6 +1255,51 @@ export const nativeCryptoR: TalerCryptoInterfaceR = { transferPub: (await tci.ecdheGetPublic(tci, { priv: transferPriv })).pub, }; }, + async signPurseCreation( + tci: TalerCryptoInterfaceR, + req: SignPurseCreationRequest, + ): Promise<EddsaSigningResult> { + const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_CREATE) + .put(timestampRoundedToBuffer(req.purseExpiration)) + .put(amountToBuffer(Amounts.parseOrThrow(req.purseAmount))) + .put(decodeCrock(req.hContractTerms)) + .put(decodeCrock(req.mergePub)) + .put(bufferForUint32(req.minAge)) + .build(); + return await tci.eddsaSign(tci, { + msg: encodeCrock(sigBlob), + priv: req.pursePriv, + }); + }, + async signPurseDeposits( + tci: TalerCryptoInterfaceR, + req: SignPurseDepositsRequest, + ): Promise<SignPurseDepositsResponse> { + const hExchangeBaseUrl = hash(stringToBytes(req.exchangeBaseUrl + "\0")); + const deposits: PurseDeposit[] = []; + for (const c of req.coins) { + const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_PURSE_DEPOSIT) + .put(amountToBuffer(Amounts.parseOrThrow(c.contribution))) + .put(decodeCrock(req.pursePub)) + .put(hExchangeBaseUrl) + .build(); + const sigResp = await tci.eddsaSign(tci, { + msg: encodeCrock(sigBlob), + priv: c.coinPriv, + }); + deposits.push({ + amount: c.contribution, + coin_pub: c.coinPub, + coin_sig: sigResp.sig, + denom_pub_hash: c.denomPubHash, + ub_sig: c.denomSig, + h_age_commitment: undefined, + }); + } + return { + deposits, + }; + }, }; function amountToBuffer(amount: AmountJson): Uint8Array { diff --git a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts index fe5dbcec6..52b96b1a5 100644 --- a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts +++ b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts @@ -148,4 +148,4 @@ export interface CreateRecoupRefreshReqRequest { denomPub: DenominationPubKey; denomPubHash: string; denomSig: UnblindedSignature; -} +}
\ No newline at end of file diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index eefa43113..8cf5170e5 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -1309,9 +1309,9 @@ export const WALLET_BACKUP_STATE_KEY = "walletBackupState"; */ export type ConfigRecord = | { - key: typeof WALLET_BACKUP_STATE_KEY; - value: WalletBackupConfState; - } + key: typeof WALLET_BACKUP_STATE_KEY; + value: WalletBackupConfState; + } | { key: "currencyDefaultsApplied"; value: boolean }; export interface WalletBackupConfState { @@ -1497,17 +1497,17 @@ export enum BackupProviderStateTag { export type BackupProviderState = | { - tag: BackupProviderStateTag.Provisional; - } + tag: BackupProviderStateTag.Provisional; + } | { - tag: BackupProviderStateTag.Ready; - nextBackupTimestamp: TalerProtocolTimestamp; - } + tag: BackupProviderStateTag.Ready; + nextBackupTimestamp: TalerProtocolTimestamp; + } | { - tag: BackupProviderStateTag.Retrying; - retryInfo: RetryInfo; - lastError?: TalerErrorDetail; - }; + tag: BackupProviderStateTag.Retrying; + retryInfo: RetryInfo; + lastError?: TalerErrorDetail; + }; export interface BackupProviderTerms { supportedProtocolVersion: string; @@ -1671,6 +1671,52 @@ export interface BalancePerCurrencyRecord { pendingOutgoing: AmountString; } +/** + * Record for a push P2P payment that this wallet initiated. + */ +export interface PeerPushPaymentInitiationRecord { + + /** + * What exchange are funds coming from? + */ + exchangeBaseUrl: string; + + amount: AmountString; + + /** + * Purse public key. Used as the primary key to look + * up this record. + */ + pursePub: string; + + /** + * Purse private key. + */ + pursePriv: string; + + /** + * Public key of the merge capability of the purse. + */ + mergePub: string; + + /** + * Private key of the merge capability of the purse. + */ + mergePriv: string; + + purseExpiration: TalerProtocolTimestamp; + + /** + * Did we successfully create the purse with the exchange? + */ + purseCreated: boolean; +} + +/** + * Record for a push P2P payment that this wallet accepted. + */ +export interface PeerPushPaymentAcceptanceRecord {} + export const WalletStoresV1 = { coins: describeStore( describeContents<CoinRecord>("coins", { diff --git a/packages/taler-wallet-core/src/operations/peer-to-peer.ts b/packages/taler-wallet-core/src/operations/peer-to-peer.ts new file mode 100644 index 000000000..e2ae1e66e --- /dev/null +++ b/packages/taler-wallet-core/src/operations/peer-to-peer.ts @@ -0,0 +1,222 @@ +/* + This file is part of GNU Taler + (C) 2019 GNUnet e.V. + + 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, + Amounts, + Logger, + InitiatePeerPushPaymentResponse, + InitiatePeerPushPaymentRequest, + strcmp, + CoinPublicKeyString, + j2s, + getRandomBytes, + Duration, + durationAdd, + TalerProtocolTimestamp, + AbsoluteTime, + encodeCrock, + AmountString, + UnblindedSignature, +} from "@gnu-taler/taler-util"; +import { CoinStatus } from "../db.js"; +import { InternalWalletState } from "../internal-wallet-state.js"; + +const logger = new Logger("operations/peer-to-peer.ts"); + +export interface PeerCoinSelection { + exchangeBaseUrl: string; + + /** + * Info of Coins that were selected. + */ + coins: { + coinPub: string; + coinPriv: string; + contribution: AmountString; + denomPubHash: string; + denomSig: UnblindedSignature; + }[]; + + /** + * How much of the deposit fees is the customer paying? + */ + depositFees: AmountJson; +} + +interface CoinInfo { + /** + * Public key of the coin. + */ + coinPub: string; + + coinPriv: string; + + /** + * Deposit fee for the coin. + */ + feeDeposit: AmountJson; + + value: AmountJson; + + denomPubHash: string; + + denomSig: UnblindedSignature; +} + +export async function initiatePeerToPeerPush( + ws: InternalWalletState, + req: InitiatePeerPushPaymentRequest, +): Promise<InitiatePeerPushPaymentResponse> { + const instructedAmount = Amounts.parseOrThrow(req.amount); + const coinSelRes: PeerCoinSelection | undefined = await ws.db + .mktx((x) => ({ + exchanges: x.exchanges, + coins: x.coins, + denominations: x.denominations, + })) + .runReadOnly(async (tx) => { + const exchanges = await tx.exchanges.iter().toArray(); + for (const exch of exchanges) { + if (exch.detailsPointer?.currency !== instructedAmount.currency) { + continue; + } + const coins = ( + await tx.coins.indexes.byBaseUrl.getAll(exch.baseUrl) + ).filter((x) => x.status === CoinStatus.Fresh); + const coinInfos: CoinInfo[] = []; + for (const coin of coins) { + const denom = await ws.getDenomInfo( + ws, + tx, + coin.exchangeBaseUrl, + coin.denomPubHash, + ); + if (!denom) { + throw Error("denom not found"); + } + coinInfos.push({ + coinPub: coin.coinPub, + feeDeposit: denom.feeDeposit, + value: denom.value, + denomPubHash: denom.denomPubHash, + coinPriv: coin.coinPriv, + denomSig: coin.denomSig, + }); + } + if (coinInfos.length === 0) { + continue; + } + coinInfos.sort( + (o1, o2) => + -Amounts.cmp(o1.value, o2.value) || + strcmp(o1.denomPubHash, o2.denomPubHash), + ); + let amountAcc = Amounts.getZero(instructedAmount.currency); + let depositFeesAcc = Amounts.getZero(instructedAmount.currency); + const resCoins: { + coinPub: string; + coinPriv: string; + contribution: AmountString; + denomPubHash: string; + denomSig: UnblindedSignature; + }[] = []; + for (const coin of coinInfos) { + if (Amounts.cmp(amountAcc, instructedAmount) >= 0) { + const res: PeerCoinSelection = { + exchangeBaseUrl: exch.baseUrl, + coins: resCoins, + depositFees: depositFeesAcc, + }; + return res; + } + const gap = Amounts.add( + coin.feeDeposit, + Amounts.sub(instructedAmount, amountAcc).amount, + ).amount; + const contrib = Amounts.min(gap, coin.value); + amountAcc = Amounts.add( + amountAcc, + Amounts.sub(contrib, coin.feeDeposit).amount, + ).amount; + depositFeesAcc = Amounts.add(depositFeesAcc, coin.feeDeposit).amount; + resCoins.push({ + coinPriv: coin.coinPriv, + coinPub: coin.coinPub, + contribution: Amounts.stringify(contrib), + denomPubHash: coin.denomPubHash, + denomSig: coin.denomSig, + }); + } + continue; + } + return undefined; + }); + logger.info(`selected p2p coins: ${j2s(coinSelRes)}`); + + if (!coinSelRes) { + throw Error("insufficient balance"); + } + + const pursePair = await ws.cryptoApi.createEddsaKeypair({}); + const mergePair = await ws.cryptoApi.createEddsaKeypair({}); + const hContractTerms = encodeCrock(getRandomBytes(64)); + const purseExpiration = AbsoluteTime.toTimestamp( + AbsoluteTime.addDuration( + AbsoluteTime.now(), + Duration.fromSpec({ days: 2 }), + ), + ); + + const purseSigResp = await ws.cryptoApi.signPurseCreation({ + hContractTerms, + mergePub: mergePair.pub, + minAge: 0, + purseAmount: Amounts.stringify(instructedAmount), + purseExpiration, + pursePriv: pursePair.priv, + }); + + const depositSigsResp = await ws.cryptoApi.signPurseDeposits({ + exchangeBaseUrl: coinSelRes.exchangeBaseUrl, + pursePub: pursePair.pub, + coins: coinSelRes.coins, + }); + + const createPurseUrl = new URL( + `purses/${pursePair.pub}/create`, + coinSelRes.exchangeBaseUrl, + ); + + const httpResp = await ws.http.postJson(createPurseUrl.href, { + amount: Amounts.stringify(instructedAmount), + merge_pub: mergePair.pub, + purse_sig: purseSigResp.sig, + h_contract_terms: hContractTerms, + purse_expiration: purseExpiration, + deposits: depositSigsResp.deposits, + min_age: 0, + }); + + const resp = await httpResp.json(); + + logger.info(`resp: ${j2s(resp)}`); + + throw Error("not yet implemented"); +} diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts index 9acfbf103..5c0882ae0 100644 --- a/packages/taler-wallet-core/src/wallet-api-types.ts +++ b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -46,6 +46,8 @@ import { GetExchangeTosResult, GetWithdrawalDetailsForAmountRequest, GetWithdrawalDetailsForUriRequest, + InitiatePeerPushPaymentRequest, + InitiatePeerPushPaymentResponse, IntegrationTestArgs, ManualWithdrawalDetails, PreparePayRequest, @@ -118,6 +120,9 @@ export enum WalletApiOperation { ExportBackupPlain = "exportBackupPlain", WithdrawFakebank = "withdrawFakebank", ExportDb = "exportDb", + InitiatePeerPushPayment = "initiatePeerPushPayment", + CheckPeerPushPayment = "checkPeerPushPayment", + AcceptPeerPushPayment = "acceptPeerPushPayment", } export type WalletOperations = { @@ -277,6 +282,10 @@ export type WalletOperations = { request: {}; response: any; }; + [WalletApiOperation.InitiatePeerPushPayment]: { + request: InitiatePeerPushPaymentRequest; + response: InitiatePeerPushPaymentResponse; + }; }; export type RequestType< diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index c7b94138e..d072f9e96 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -47,6 +47,7 @@ import { codecForGetWithdrawalDetailsForAmountRequest, codecForGetWithdrawalDetailsForUri, codecForImportDbRequest, + codecForInitiatePeerPushPaymentRequest, codecForIntegrationTestArgs, codecForListKnownBankAccounts, codecForPrepareDepositRequest, @@ -143,6 +144,7 @@ import { processDownloadProposal, processPurchasePay, } from "./operations/pay.js"; +import { initiatePeerToPeerPush } from "./operations/peer-to-peer.js"; import { getPendingOperations } from "./operations/pending.js"; import { createRecoupGroup, processRecoupGroup } from "./operations/recoup.js"; import { @@ -1049,6 +1051,10 @@ async function dispatchRequestInternal( await importDb(ws.db.idbHandle(), req.dump); return []; } + case "initiatePeerPushPayment": { + const req = codecForInitiatePeerPushPaymentRequest().decode(payload); + return await initiatePeerToPeerPush(ws, req); + } } throw TalerError.fromDetail( TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN, |