gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] 01/02: wallet-core: P2P push payments (still incompl


From: gnunet
Subject: [taler-wallet-core] 01/02: wallet-core: P2P push payments (still incomplete)
Date: Tue, 12 Jul 2022 17:51:14 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

commit b214934b75418d0d01c9556577d9594f1db5a319
Author: Florian Dold <florian@dold.me>
AuthorDate: Tue Jun 21 12:40:12 2022 +0200

    wallet-core: P2P push payments (still incomplete)
---
 packages/taler-util/src/talerCrypto.ts             |   2 +
 packages/taler-util/src/talerTypes.ts              |  42 +++-
 packages/taler-util/src/walletTypes.ts             |  27 ++-
 packages/taler-wallet-cli/src/harness/harness.ts   |  30 +++
 .../src/integrationtests/test-peer-to-peer.ts      |  53 +++++
 .../src/integrationtests/testrunner.ts             |   2 +
 .../src/crypto/cryptoImplementation.ts             | 120 +++++++++--
 .../taler-wallet-core/src/crypto/cryptoTypes.ts    |   2 +-
 packages/taler-wallet-core/src/db.ts               |  70 +++++--
 .../src/operations/peer-to-peer.ts                 | 222 +++++++++++++++++++++
 packages/taler-wallet-core/src/wallet-api-types.ts |   9 +
 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 e2360b09..188f5ec0 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 7fc3fcba..7afa76e9 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 2e5dd418..4b191116 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 74a52334..3b58219b 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 00000000..4d27f45d
--- /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 e8aef513..cafcce79 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 7c6b00bc..1d364183 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 fe5dbcec..52b96b1a 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 eefa4311..8cf5170e 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 00000000..e2ae1e66
--- /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 9acfbf10..5c0882ae 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 c7b94138..d072f9e9 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,

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]