summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2022-06-21 12:40:12 +0200
committerFlorian Dold <florian@dold.me>2022-07-08 11:08:30 +0200
commitb214934b75418d0d01c9556577d9594f1db5a319 (patch)
treed8ec18217a16e6b89859b30003a4a825fc63a66e /packages
parent05cdbfb534bb194dbe6bdf049113ebea8139234f (diff)
downloadwallet-core-b214934b75418d0d01c9556577d9594f1db5a319.tar.gz
wallet-core-b214934b75418d0d01c9556577d9594f1db5a319.tar.bz2
wallet-core-b214934b75418d0d01c9556577d9594f1db5a319.zip
wallet-core: P2P push payments (still incomplete)
Diffstat (limited to 'packages')
-rw-r--r--packages/taler-util/src/talerCrypto.ts2
-rw-r--r--packages/taler-util/src/talerTypes.ts42
-rw-r--r--packages/taler-util/src/walletTypes.ts27
-rw-r--r--packages/taler-wallet-cli/src/harness/harness.ts30
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/test-peer-to-peer.ts53
-rw-r--r--packages/taler-wallet-cli/src/integrationtests/testrunner.ts2
-rw-r--r--packages/taler-wallet-core/src/crypto/cryptoImplementation.ts120
-rw-r--r--packages/taler-wallet-core/src/crypto/cryptoTypes.ts2
-rw-r--r--packages/taler-wallet-core/src/db.ts70
-rw-r--r--packages/taler-wallet-core/src/operations/peer-to-peer.ts222
-rw-r--r--packages/taler-wallet-core/src/wallet-api-types.ts9
-rw-r--r--packages/taler-wallet-core/src/wallet.ts6
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,