gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] branch master updated: -implement batch withdraw client


From: gnunet
Subject: [taler-exchange] branch master updated: -implement batch withdraw client-side logic
Date: Mon, 23 May 2022 21:12:41 +0200

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

grothoff pushed a commit to branch master
in repository exchange.

The following commit(s) were added to refs/heads/master by this push:
     new 054f2ab5 -implement batch withdraw client-side logic
054f2ab5 is described below

commit 054f2ab51c56a0dbb95babd5de97a7148e5af232
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Mon May 23 21:12:31 2022 +0200

    -implement batch withdraw client-side logic
---
 src/include/taler_exchange_service.h   | 224 ++++++++++++-
 src/lib/Makefile.am                    |   2 +
 src/lib/exchange_api_batch_withdraw.c  | 433 +++++++++++++++++++++++++
 src/lib/exchange_api_batch_withdraw2.c | 560 +++++++++++++++++++++++++++++++++
 src/lib/exchange_api_withdraw.c        |  24 +-
 src/testing/testing_api_cmd_withdraw.c |  19 +-
 6 files changed, 1235 insertions(+), 27 deletions(-)

diff --git a/src/include/taler_exchange_service.h 
b/src/include/taler_exchange_service.h
index ecb74bd6..6ff9ce5b 100644
--- a/src/include/taler_exchange_service.h
+++ b/src/include/taler_exchange_service.h
@@ -1882,6 +1882,29 @@ TALER_EXCHANGE_reserves_history_cancel (
 struct TALER_EXCHANGE_WithdrawHandle;
 
 
+/**
+ * Information input into the withdraw process per coin.
+ */
+struct TALER_EXCHANGE_WithdrawCoinInput
+{
+  /**
+   * Denomination of the coin.
+   */
+  const struct TALER_EXCHANGE_DenomPublicKey *pk;
+
+  /**
+   * Master key material for the coin.
+   */
+  const struct TALER_PlanchetMasterSecretP *ps;
+
+  /**
+   * Age commitment for the coin.
+   */
+  const struct TALER_AgeCommitmentHash *ach;
+
+};
+
+
 /**
  * All the details about a coin that are generated during withdrawal and that
  * may be needed for future operations on the coin.
@@ -1988,11 +2011,8 @@ typedef void
  * same arguments in case of failures.
  *
  * @param exchange the exchange handle; the exchange must be ready to operate
- * @param pk kind of coin to create
  * @param reserve_priv private key of the reserve to withdraw from
- * @param ps secrets of the planchet
- *        caller must have committed this value to disk before the call (with 
@a pk)
- * @param ach hash of the age commitment that should be bound to this coin. 
Maybe NULL.
+ * @param wci inputs that determine the planchet
  * @param res_cb the callback to call when the final result for this request 
is available
  * @param res_cb_cls closure for @a res_cb
  * @return NULL
@@ -2002,10 +2022,8 @@ typedef void
 struct TALER_EXCHANGE_WithdrawHandle *
 TALER_EXCHANGE_withdraw (
   struct TALER_EXCHANGE_Handle *exchange,
-  const struct TALER_EXCHANGE_DenomPublicKey *pk,
   const struct TALER_ReservePrivateKeyP *reserve_priv,
-  const struct TALER_PlanchetMasterSecretP *ps,
-  const struct TALER_AgeCommitmentHash *ach,
+  const struct TALER_EXCHANGE_WithdrawCoinInput *wci,
   TALER_EXCHANGE_WithdrawCallback res_cb,
   void *res_cb_cls);
 
@@ -2020,6 +2038,130 @@ void
 TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh);
 
 
+/**
+ * @brief A /reserves/$RESERVE_PUB/batch-withdraw Handle
+ */
+struct TALER_EXCHANGE_BatchWithdrawHandle;
+
+
+/**
+ * Details about a response for a batch withdraw request.
+ */
+struct TALER_EXCHANGE_BatchWithdrawResponse
+{
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details about the response.
+   */
+  union
+  {
+    /**
+     * Details if the status is #MHD_HTTP_OK.
+     */
+    struct
+    {
+
+      /**
+       * Array of coins returned by the batch withdraw operation.
+       */
+      struct TALER_EXCHANGE_PrivateCoinDetails *coins;
+
+      /**
+       * Length of the @e coins array.
+       */
+      unsigned int num_coins;
+    } success;
+
+    /**
+     * Details if the status is #MHD_HTTP_ACCEPTED.
+     */
+    struct
+    {
+      /**
+       * Payment target that the merchant should use
+       * to check for its KYC status.
+       */
+      uint64_t payment_target_uuid;
+    } accepted;
+
+    /**
+     * Details if the status is #MHD_HTTP_CONFLICT.
+     */
+    struct
+    {
+      /* TODO: returning full details is not implemented */
+    } conflict;
+
+    /**
+     * Details if the status is #MHD_HTTP_GONE.
+     */
+    struct
+    {
+      /* TODO: returning full details is not implemented */
+    } gone;
+
+  } details;
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * batch withdraw request to a exchange.
+ *
+ * @param cls closure
+ * @param wr response details
+ */
+typedef void
+(*TALER_EXCHANGE_BatchWithdrawCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_BatchWithdrawResponse *wr);
+
+
+/**
+ * Withdraw multiple coins from the exchange using a 
/reserves/$RESERVE_PUB/batch-withdraw
+ * request.  This API is typically used by a wallet to withdraw many coins 
from a
+ * reserve.
+ *
+ * Note that to ensure that no money is lost in case of hardware
+ * failures, the caller must have committed (most of) the arguments to
+ * disk before calling, and be ready to repeat the request with the
+ * same arguments in case of failures.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param reserve_priv private key of the reserve to withdraw from
+ * @param wcis inputs that determine the planchets
+ * @param wci_length number of entries in @a wcis
+ * @param res_cb the callback to call when the final result for this request 
is available
+ * @param res_cb_cls closure for @a res_cb
+ * @return NULL
+ *         if the inputs are invalid (i.e. denomination key not with this 
exchange).
+ *         In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_BatchWithdrawHandle *
+TALER_EXCHANGE_batch_withdraw (
+  struct TALER_EXCHANGE_Handle *exchange,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  const struct TALER_EXCHANGE_WithdrawCoinInput *wcis,
+  unsigned int wci_length,
+  TALER_EXCHANGE_BatchWithdrawCallback res_cb,
+  void *res_cb_cls);
+
+
+/**
+ * Cancel a batch withdraw status request.  This function cannot be used on a
+ * request handle if a response is already served for it.
+ *
+ * @param wh the batch withdraw handle
+ */
+void
+TALER_EXCHANGE_batch_withdraw_cancel (
+  struct TALER_EXCHANGE_BatchWithdrawHandle *wh);
+
+
 /**
  * Callbacks of this type are used to serve the result of submitting a
  * withdraw request to a exchange without the (un)blinding factor.
@@ -2082,6 +2224,74 @@ void
 TALER_EXCHANGE_withdraw2_cancel (struct TALER_EXCHANGE_Withdraw2Handle *wh);
 
 
+/**
+ * Callbacks of this type are used to serve the result of submitting a batch
+ * withdraw request to a exchange without the (un)blinding factor.
+ *
+ * @param cls closure
+ * @param hr HTTP response data
+ * @param blind_sigs array of blind signatures over the coins, NULL on error
+ * @param blind_sigs_length length of @a blind_sigs
+ */
+typedef void
+(*TALER_EXCHANGE_BatchWithdraw2Callback) (
+  void *cls,
+  const struct TALER_EXCHANGE_HttpResponse *hr,
+  const struct TALER_BlindedDenominationSignature *blind_sigs,
+  unsigned int blind_sigs_length);
+
+
+/**
+ * @brief A /reserves/$RESERVE_PUB/batch-withdraw Handle, 2nd variant.
+ * This variant does not do the blinding/unblinding and only
+ * fetches the blind signatures on the already blinded planchets.
+ * Used internally by the `struct TALER_EXCHANGE_BatchWithdrawHandle`
+ * implementation as well as for the tipping logic of merchants.
+ */
+struct TALER_EXCHANGE_BatchWithdraw2Handle;
+
+
+/**
+ * Withdraw a coin from the exchange using a 
/reserves/$RESERVE_PUB/batch-withdraw
+ * request.  This API is typically used by a merchant to withdraw a tip
+ * where the blinding factor is unknown to the merchant.
+ *
+ * Note that to ensure that no money is lost in case of hardware
+ * failures, the caller must have committed (most of) the arguments to
+ * disk before calling, and be ready to repeat the request with the
+ * same arguments in case of failures.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param pds array of planchet details of the planchet to withdraw
+ * @param pds_length number of entries in the @a pds array
+ * @param reserve_priv private key of the reserve to withdraw from
+ * @param res_cb the callback to call when the final result for this request 
is available
+ * @param res_cb_cls closure for @a res_cb
+ * @return NULL
+ *         if the inputs are invalid (i.e. denomination key not with this 
exchange).
+ *         In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_BatchWithdraw2Handle *
+TALER_EXCHANGE_batch_withdraw2 (
+  struct TALER_EXCHANGE_Handle *exchange,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  const struct TALER_PlanchetDetail *pds,
+  unsigned int pds_length,
+  TALER_EXCHANGE_BatchWithdraw2Callback res_cb,
+  void *res_cb_cls);
+
+
+/**
+ * Cancel a batch withdraw request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param wh the withdraw handle
+ */
+void
+TALER_EXCHANGE_batch_withdraw2_cancel (
+  struct TALER_EXCHANGE_BatchWithdraw2Handle *wh);
+
+
 /* ********************* /refresh/melt+reveal ***************************** */
 
 
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 0ed7b148..aa53d449 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -22,6 +22,8 @@ libtalerexchange_la_LDFLAGS = \
   -no-undefined
 libtalerexchange_la_SOURCES = \
   exchange_api_auditor_add_denomination.c \
+  exchange_api_batch_withdraw.c \
+  exchange_api_batch_withdraw2.c \
   exchange_api_curl_defaults.c exchange_api_curl_defaults.h \
   exchange_api_common.c \
   exchange_api_contracts_get.c \
diff --git a/src/lib/exchange_api_batch_withdraw.c 
b/src/lib/exchange_api_batch_withdraw.c
new file mode 100644
index 00000000..ce5de3fc
--- /dev/null
+++ b/src/lib/exchange_api_batch_withdraw.c
@@ -0,0 +1,433 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2022 Taler Systems SA
+
+  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.
+
+  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
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_batch_withdraw.c
+ * @brief Implementation of /reserves/$RESERVE_PUB/batch-withdraw requests 
with blinding/unblinding
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * Data we keep per coin in the batch.
+ */
+struct CoinData
+{
+
+  /**
+   * Denomination key we are withdrawing.
+   */
+  struct TALER_EXCHANGE_DenomPublicKey pk;
+
+  /**
+   * Master key material for the coin.
+   */
+  struct TALER_PlanchetMasterSecretP ps;
+
+  /**
+   * Age commitment for the coin.
+   */
+  const struct TALER_AgeCommitmentHash *ach;
+
+  /**
+   *  blinding secret
+   */
+  union TALER_DenominationBlindingKeyP bks;
+
+  /**
+   * Private key of the coin we are withdrawing.
+   */
+  struct TALER_CoinSpendPrivateKeyP priv;
+
+  /**
+   * Details of the planchet.
+   */
+  struct TALER_PlanchetDetail pd;
+
+  /**
+   * Values of the @cipher selected
+   */
+  struct TALER_ExchangeWithdrawValues alg_values;
+
+  /**
+   * Hash of the public key of the coin we are signing.
+   */
+  struct TALER_CoinPubHashP c_hash;
+
+  /**
+   * Handler for the CS R request (only used for TALER_DENOMINATION_CS 
denominations)
+   */
+  struct TALER_EXCHANGE_CsRWithdrawHandle *csrh;
+
+  /**
+   * Batch withdraw this coin is part of.
+   */
+  struct TALER_EXCHANGE_BatchWithdrawHandle *wh;
+};
+
+
+/**
+ * @brief A batch withdraw handle
+ */
+struct TALER_EXCHANGE_BatchWithdrawHandle
+{
+
+  /**
+   * The connection to exchange this request handle will use
+   */
+  struct TALER_EXCHANGE_Handle *exchange;
+
+  /**
+   * Handle for the actual (internal) batch withdraw operation.
+   */
+  struct TALER_EXCHANGE_BatchWithdraw2Handle *wh2;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_EXCHANGE_BatchWithdrawCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reserve private key.
+   */
+  const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+  /**
+   * Array of per-coin data.
+   */
+  struct CoinData *coins;
+
+  /**
+   * Length of the @e coins array.
+   */
+  unsigned int num_coins;
+
+  /**
+   * Number of CS requests still pending.
+   */
+  unsigned int cs_pending;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RESERVE_PUB/batch-withdraw request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_BatchWithdrawHandle`
+ * @param hr HTTP response data
+ * @param blind_sig blind signature over the coin, NULL on error
+ */
+static void
+handle_reserve_batch_withdraw_finished (
+  void *cls,
+  const struct TALER_EXCHANGE_HttpResponse *hr,
+  const struct TALER_BlindedDenominationSignature *blind_sigs,
+  unsigned int blind_sigs_length)
+{
+  struct TALER_EXCHANGE_BatchWithdrawHandle *wh = cls;
+  struct TALER_EXCHANGE_BatchWithdrawResponse wr = {
+    .hr = *hr
+  };
+  struct TALER_EXCHANGE_PrivateCoinDetails coins[wh->num_coins];
+
+  wh->wh2 = NULL;
+  if (blind_sigs_length != wh->num_coins)
+  {
+    GNUNET_break_op (0);
+    wr.hr.http_status = 0;
+    wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+  }
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    {
+      for (unsigned int i = 0; i<wh->num_coins; i++)
+      {
+        struct CoinData *cd = &wh->coins[i];
+        struct TALER_EXCHANGE_PrivateCoinDetails *coin = &coins[i];
+        struct TALER_FreshCoin fc;
+
+        if (GNUNET_OK !=
+            TALER_planchet_to_coin (&cd->pk.key,
+                                    &blind_sigs[i],
+                                    &cd->bks,
+                                    &cd->priv,
+                                    cd->ach,
+                                    &cd->c_hash,
+                                    &cd->alg_values,
+                                    &fc))
+        {
+          wr.hr.http_status = 0;
+          wr.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE;
+          break;
+        }
+        coin->coin_priv = cd->priv;
+        coin->bks = cd->bks;
+        coin->sig = fc.sig;
+        coin->exchange_vals = cd->alg_values;
+      }
+      wr.details.success.coins = coins;
+      wr.details.success.num_coins = wh->num_coins;
+      break;
+    }
+  case MHD_HTTP_ACCEPTED:
+    {
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_uint64 ("payment_target_uuid",
+                                 &wr.details.accepted.payment_target_uuid),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (hr->reply,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        wr.hr.http_status = 0;
+        wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+    }
+    break;
+  default:
+    break;
+  }
+  wh->cb (wh->cb_cls,
+          &wr);
+  for (unsigned int i = 0; i<wh->num_coins; i++)
+    TALER_denom_sig_free (&coins[i].sig);
+  TALER_EXCHANGE_batch_withdraw_cancel (wh);
+}
+
+
+/**
+ * Runs phase two, the actual withdraw operation.
+ * Started once the preparation for CS-denominations is
+ * done.
+ *
+ * @param[in,out] wh batch withdraw to start phase 2 for
+ */
+static void
+phase_two (struct TALER_EXCHANGE_BatchWithdrawHandle *wh)
+{
+  struct TALER_PlanchetDetail pds[wh->num_coins];
+
+  for (unsigned int i = 0; i<wh->num_coins; i++)
+  {
+    struct CoinData *cd = &wh->coins[i];
+
+    pds[i] = cd->pd;
+  }
+  wh->wh2 = TALER_EXCHANGE_batch_withdraw2 (
+    wh->exchange,
+    wh->reserve_priv,
+    pds,
+    wh->num_coins,
+    &handle_reserve_batch_withdraw_finished,
+    wh);
+}
+
+
+/**
+ * Function called when stage 1 of CS withdraw is finished (request r_pub's)
+ *
+ * @param cls the `struct CoinData *`
+ * @param csrr replies from the /csr-withdraw request
+ */
+static void
+withdraw_cs_stage_two_callback (
+  void *cls,
+  const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr)
+{
+  struct CoinData *cd = cls;
+  struct TALER_EXCHANGE_BatchWithdrawHandle *wh = cd->wh;
+  struct TALER_EXCHANGE_BatchWithdrawResponse wr = {
+    .hr = csrr->hr
+  };
+
+  cd->csrh = NULL;
+  GNUNET_assert (TALER_DENOMINATION_CS == cd->pk.key.cipher);
+  switch (csrr->hr.http_status)
+  {
+  case MHD_HTTP_OK:
+    cd->alg_values = csrr->details.success.alg_values;
+    TALER_planchet_setup_coin_priv (&cd->ps,
+                                    &cd->alg_values,
+                                    &cd->priv);
+    TALER_planchet_blinding_secret_create (&cd->ps,
+                                           &cd->alg_values,
+                                           &cd->bks);
+    /* This initializes the 2nd half of the
+       wh->pd.blinded_planchet! */
+    if (GNUNET_OK !=
+        TALER_planchet_prepare (&cd->pk.key,
+                                &cd->alg_values,
+                                &cd->bks,
+                                &cd->priv,
+                                cd->ach,
+                                &cd->c_hash,
+                                &cd->pd))
+    {
+      GNUNET_break (0);
+      TALER_EXCHANGE_batch_withdraw_cancel (wh);
+    }
+    wh->cs_pending--;
+    if (0 == wh->cs_pending)
+      phase_two (wh);
+    return;
+  default:
+    break;
+  }
+  wh->cb (wh->cb_cls,
+          &wr);
+  TALER_EXCHANGE_batch_withdraw_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_BatchWithdrawHandle *
+TALER_EXCHANGE_batch_withdraw (
+  struct TALER_EXCHANGE_Handle *exchange,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  const struct TALER_EXCHANGE_WithdrawCoinInput *wcis,
+  unsigned int wci_length,
+  TALER_EXCHANGE_BatchWithdrawCallback res_cb,
+  void *res_cb_cls)
+{
+  struct TALER_EXCHANGE_BatchWithdrawHandle *wh;
+
+  wh = GNUNET_new (struct TALER_EXCHANGE_BatchWithdrawHandle);
+  wh->exchange = exchange;
+  wh->cb = res_cb;
+  wh->cb_cls = res_cb_cls;
+  wh->reserve_priv = reserve_priv;
+  wh->num_coins = wci_length;
+  wh->coins = GNUNET_new_array (wh->num_coins,
+                                struct CoinData);
+  for (unsigned int i = 0; i<wci_length; i++)
+  {
+    struct CoinData *cd = &wh->coins[i];
+    const struct TALER_EXCHANGE_WithdrawCoinInput *wci = &wcis[i];
+
+    cd->wh = wh;
+    cd->ps = *wci->ps;
+    cd->ach = wci->ach;
+    cd->pk = *wci->pk;
+    TALER_denom_pub_deep_copy (&cd->pk.key,
+                               &wci->pk->key);
+    switch (wci->pk->key.cipher)
+    {
+    case TALER_DENOMINATION_RSA:
+      {
+        cd->alg_values.cipher = TALER_DENOMINATION_RSA;
+        TALER_planchet_setup_coin_priv (&cd->ps,
+                                        &cd->alg_values,
+                                        &cd->priv);
+        TALER_planchet_blinding_secret_create (&cd->ps,
+                                               &cd->alg_values,
+                                               &cd->bks);
+        if (GNUNET_OK !=
+            TALER_planchet_prepare (&cd->pk.key,
+                                    &cd->alg_values,
+                                    &cd->bks,
+                                    &cd->priv,
+                                    cd->ach,
+                                    &cd->c_hash,
+                                    &cd->pd))
+        {
+          GNUNET_break (0);
+          TALER_EXCHANGE_batch_withdraw_cancel (wh);
+          return NULL;
+        }
+        break;
+      }
+    case TALER_DENOMINATION_CS:
+      {
+        TALER_cs_withdraw_nonce_derive (
+          &cd->ps,
+          &cd->pd.blinded_planchet.details.cs_blinded_planchet.nonce);
+        /* Note that we only initialize the first half
+           of the blinded_planchet here; the other part
+           will be done after the /csr-withdraw request! */
+        cd->pd.blinded_planchet.cipher = TALER_DENOMINATION_CS;
+        cd->csrh = TALER_EXCHANGE_csr_withdraw (
+          exchange,
+          &cd->pk,
+          &cd->pd.blinded_planchet.details.cs_blinded_planchet.nonce,
+          &withdraw_cs_stage_two_callback,
+          cd);
+        if (NULL == cd->csrh)
+        {
+          GNUNET_break (0);
+          TALER_EXCHANGE_batch_withdraw_cancel (wh);
+          return NULL;
+        }
+        wh->cs_pending++;
+        break;
+      }
+    default:
+      GNUNET_break (0);
+      TALER_EXCHANGE_batch_withdraw_cancel (wh);
+      return NULL;
+    }
+  }
+  if (0 == wh->cs_pending)
+    phase_two (wh);
+  return wh;
+}
+
+
+void
+TALER_EXCHANGE_batch_withdraw_cancel (
+  struct TALER_EXCHANGE_BatchWithdrawHandle *wh)
+{
+  for (unsigned int i = 0; i<wh->num_coins; i++)
+  {
+    struct CoinData *cd = &wh->coins[i];
+
+    if (NULL != cd->csrh)
+    {
+      TALER_EXCHANGE_csr_withdraw_cancel (cd->csrh);
+      cd->csrh = NULL;
+    }
+    TALER_blinded_planchet_free (&cd->pd.blinded_planchet);
+    TALER_denom_pub_free (&cd->pk.key);
+  }
+  GNUNET_free (wh->coins);
+  if (NULL != wh->wh2)
+  {
+    TALER_EXCHANGE_batch_withdraw2_cancel (wh->wh2);
+    wh->wh2 = NULL;
+  }
+  GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_batch_withdraw2.c 
b/src/lib/exchange_api_batch_withdraw2.c
new file mode 100644
index 00000000..314bca0c
--- /dev/null
+++ b/src/lib/exchange_api_batch_withdraw2.c
@@ -0,0 +1,560 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2022 Taler Systems SA
+
+  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.
+
+  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
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_batch_withdraw2.c
+ * @brief Implementation of /reserves/$RESERVE_PUB/batch-withdraw requests 
without blinding/unblinding
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A batch withdraw handle
+ */
+struct TALER_EXCHANGE_BatchWithdraw2Handle
+{
+
+  /**
+   * The connection to exchange this request handle will use
+   */
+  struct TALER_EXCHANGE_Handle *exchange;
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_EXCHANGE_BatchWithdraw2Callback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Context for #TEH_curl_easy_post(). Keeps the data that must
+   * persist for Curl to make the upload.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+  /**
+   * Total amount requested (value plus withdraw fee).
+   */
+  struct TALER_Amount requested_amount;
+
+  /**
+   * Public key of the reserve we are withdrawing from.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Number of coins expected.
+   */
+  unsigned int num_coins;
+};
+
+
+/**
+ * We got a 200 OK response for the /reserves/$RESERVE_PUB/batch-withdraw 
operation.
+ * Extract the coin's signature and return it to the caller.  The signature we
+ * get from the exchange is for the blinded value.  Thus, we first must
+ * unblind it and then should verify its validity against our coin's hash.
+ *
+ * If everything checks out, we return the unblinded signature
+ * to the application via the callback.
+ *
+ * @param wh operation handle
+ * @param json reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+reserve_batch_withdraw_ok (struct TALER_EXCHANGE_BatchWithdraw2Handle *wh,
+                           const json_t *json)
+{
+  struct TALER_BlindedDenominationSignature blind_sigs[wh->num_coins];
+  const json_t *ja = json_object_get (json,
+                                      "ev_sigs");
+  const json_t *j;
+  unsigned int index;
+  struct TALER_EXCHANGE_HttpResponse hr = {
+    .reply = json,
+    .http_status = MHD_HTTP_OK
+  };
+
+  if ( (NULL == ja) ||
+       (! json_is_array (ja)) ||
+       (wh->num_coins != json_array_size (ja)) )
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  json_array_foreach (ja, index, j)
+  {
+    struct GNUNET_JSON_Specification spec[] = {
+      TALER_JSON_spec_blinded_denom_sig ("ev_sig",
+                                         &blind_sigs[index]),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (json,
+                           spec,
+                           NULL, NULL))
+    {
+      GNUNET_break_op (0);
+      for (unsigned int i = 0; i<index; i++)
+        TALER_blinded_denom_sig_free (&blind_sigs[i]);
+      return GNUNET_SYSERR;
+    }
+  }
+
+  /* signature is valid, return it to the application */
+  wh->cb (wh->cb_cls,
+          &hr,
+          blind_sigs,
+          wh->num_coins);
+  /* make sure callback isn't called again after return */
+  wh->cb = NULL;
+  for (unsigned int i = 0; i<wh->num_coins; i++)
+    TALER_blinded_denom_sig_free (&blind_sigs[i]);
+
+  return GNUNET_OK;
+}
+
+
+/**
+ * We got a 409 CONFLICT response for the 
/reserves/$RESERVE_PUB/batch-withdraw operation.
+ * Check the signatures on the batch withdraw transactions in the provided
+ * history and that the balances add up.  We don't do anything directly
+ * with the information, as the JSON will be returned to the application.
+ * However, our job is ensuring that the exchange followed the protocol, and
+ * this in particular means checking all of the signatures in the history.
+ *
+ * @param wh operation handle
+ * @param json reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+reserve_batch_withdraw_payment_required (
+  struct TALER_EXCHANGE_BatchWithdraw2Handle *wh,
+  const json_t *json)
+{
+  struct TALER_Amount balance;
+  struct TALER_Amount total_in_from_history;
+  struct TALER_Amount total_out_from_history;
+  json_t *history;
+  size_t len;
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("balance",
+                                &balance),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (json,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  history = json_object_get (json,
+                             "history");
+  if (NULL == history)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* go over transaction history and compute
+     total incoming and outgoing amounts */
+  len = json_array_size (history);
+  {
+    struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
+
+    /* Use heap allocation as "len" may be very big and thus this may
+       not fit on the stack. Use "GNUNET_malloc_large" as a malicious
+       exchange may theoretically try to crash us by giving a history
+       that does not fit into our memory. */
+    rhistory = GNUNET_malloc_large (
+      sizeof (struct TALER_EXCHANGE_ReserveHistoryEntry)
+      * len);
+    if (NULL == rhistory)
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+
+    if (GNUNET_OK !=
+        TALER_EXCHANGE_parse_reserve_history (wh->exchange,
+                                              history,
+                                              &wh->reserve_pub,
+                                              balance.currency,
+                                              &total_in_from_history,
+                                              &total_out_from_history,
+                                              len,
+                                              rhistory))
+    {
+      GNUNET_break_op (0);
+      TALER_EXCHANGE_free_reserve_history (rhistory,
+                                           len);
+      return GNUNET_SYSERR;
+    }
+    TALER_EXCHANGE_free_reserve_history (rhistory,
+                                         len);
+  }
+
+  /* Check that funds were really insufficient */
+  if (0 >= TALER_amount_cmp (&wh->requested_amount,
+                             &balance))
+  {
+    /* Requested amount is smaller or equal to reported balance,
+       so this should not have failed. */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RESERVE_PUB/batch-withdraw request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_BatchWithdraw2Handle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserve_batch_withdraw_finished (void *cls,
+                                        long response_code,
+                                        const void *response)
+{
+  struct TALER_EXCHANGE_BatchWithdraw2Handle *wh = cls;
+  const json_t *j = response;
+  struct TALER_EXCHANGE_HttpResponse hr = {
+    .reply = j,
+    .http_status = (unsigned int) response_code
+  };
+
+  wh->job = NULL;
+  switch (response_code)
+  {
+  case 0:
+    hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    if (GNUNET_OK !=
+        reserve_batch_withdraw_ok (wh,
+                                   j))
+    {
+      GNUNET_break_op (0);
+      hr.http_status = 0;
+      hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+      break;
+    }
+    GNUNET_assert (NULL == wh->cb);
+    TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+    return;
+  case MHD_HTTP_ACCEPTED:
+    /* only validate reply is well-formed */
+    {
+      uint64_t ptu;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_uint64 ("payment_target_uuid",
+                                 &ptu),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (j,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        hr.http_status = 0;
+        hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+    }
+    break;
+  case MHD_HTTP_BAD_REQUEST:
+    /* This should never happen, either us or the exchange is buggy
+       (or API version conflict); just pass JSON reply to the application */
+    hr.ec = TALER_JSON_get_error_code (j);
+    hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    GNUNET_break_op (0);
+    /* Nothing really to verify, exchange says one of the signatures is
+       invalid; as we checked them, this should never happen, we
+       should pass the JSON reply to the application */
+    hr.ec = TALER_JSON_get_error_code (j);
+    hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* Nothing really to verify, the exchange basically just says
+       that it doesn't know this reserve.  Can happen if we
+       query before the wire transfer went through.
+       We should simply pass the JSON reply to the application. */
+    hr.ec = TALER_JSON_get_error_code (j);
+    hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_CONFLICT:
+    /* The exchange says that the reserve has insufficient funds;
+       check the signatures in the history... */
+    if (GNUNET_OK !=
+        reserve_batch_withdraw_payment_required (wh,
+                                                 j))
+    {
+      GNUNET_break_op (0);
+      hr.http_status = 0;
+      hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+    }
+    else
+    {
+      hr.ec = TALER_JSON_get_error_code (j);
+      hr.hint = TALER_JSON_get_error_hint (j);
+    }
+    break;
+  case MHD_HTTP_GONE:
+    /* could happen if denomination was revoked */
+    /* Note: one might want to check /keys for revocation
+       signature here, alas tricky in case our /keys
+       is outdated => left to clients */
+    hr.ec = TALER_JSON_get_error_code (j);
+    hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    hr.ec = TALER_JSON_get_error_code (j);
+    hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    hr.ec = TALER_JSON_get_error_code (j);
+    hr.hint = TALER_JSON_get_error_hint (j);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d for exchange batch withdraw\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    break;
+  }
+  if (NULL != wh->cb)
+  {
+    wh->cb (wh->cb_cls,
+            &hr,
+            NULL,
+            0);
+    wh->cb = NULL;
+  }
+  TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_BatchWithdraw2Handle *
+TALER_EXCHANGE_batch_withdraw2 (
+  struct TALER_EXCHANGE_Handle *exchange,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  const struct TALER_PlanchetDetail *pds,
+  unsigned int pds_length,
+  TALER_EXCHANGE_BatchWithdraw2Callback res_cb,
+  void *res_cb_cls)
+{
+  struct TALER_EXCHANGE_BatchWithdraw2Handle *wh;
+  const struct TALER_EXCHANGE_Keys *keys;
+  const struct TALER_EXCHANGE_DenomPublicKey *dk;
+  struct TALER_ReserveSignatureP reserve_sig;
+  char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+  struct TALER_BlindedCoinHashP bch;
+  json_t *jc;
+
+  keys = TALER_EXCHANGE_get_keys (exchange);
+  if (NULL == keys)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  wh = GNUNET_new (struct TALER_EXCHANGE_BatchWithdraw2Handle);
+  wh->exchange = exchange;
+  wh->cb = res_cb;
+  wh->cb_cls = res_cb_cls;
+  wh->num_coins = pds_length;
+  TALER_amount_set_zero (keys->currency,
+                         &wh->requested_amount);
+  GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+                                      &wh->reserve_pub.eddsa_pub);
+  {
+    char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (
+      &wh->reserve_pub,
+      sizeof (struct TALER_ReservePublicKeyP),
+      pub_str,
+      sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/reserves/%s/batch-withdraw",
+                     pub_str);
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Attempting to batch-withdraw from reserve %s\n",
+              TALER_B2S (&wh->reserve_pub));
+  wh->url = TEAH_path_to_url (exchange,
+                              arg_str);
+  if (NULL == wh->url)
+  {
+    GNUNET_break (0);
+    TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+    return NULL;
+  }
+  jc = json_array ();
+  GNUNET_assert (NULL != jc);
+  for (unsigned int i = 0; i<pds_length; i++)
+  {
+    const struct TALER_PlanchetDetail *pd = &pds[i];
+    struct TALER_Amount coin_total;
+    json_t *withdraw_obj;
+
+    dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
+                                                      &pd->denom_pub_hash);
+    if (NULL == dk)
+    {
+      TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+      json_decref (jc);
+      GNUNET_break (0);
+      return NULL;
+    }
+    /* Compute how much we expected to charge to the reserve */
+    if (0 >
+        TALER_amount_add (&coin_total,
+                          &dk->fees.withdraw,
+                          &dk->value))
+    {
+      /* Overflow here? Very strange, our CPU must be fried... */
+      GNUNET_break (0);
+      TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+      json_decref (jc);
+      return NULL;
+    }
+    if (0 >
+        TALER_amount_add (&wh->requested_amount,
+                          &wh->requested_amount,
+                          &coin_total))
+    {
+      /* Overflow here? Very strange, our CPU must be fried... */
+      GNUNET_break (0);
+      TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+      json_decref (jc);
+      return NULL;
+    }
+    if (GNUNET_OK !=
+        TALER_coin_ev_hash (&pd->blinded_planchet,
+                            &pd->denom_pub_hash,
+                            &bch))
+    {
+      GNUNET_break (0);
+      TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+      json_decref (jc);
+      return NULL;
+    }
+    TALER_wallet_withdraw_sign (&pd->denom_pub_hash,
+                                &coin_total,
+                                &bch,
+                                reserve_priv,
+                                &reserve_sig);
+    withdraw_obj = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+                                  &pd->denom_pub_hash),
+      TALER_JSON_pack_blinded_planchet ("coin_ev",
+                                        &pd->blinded_planchet),
+      GNUNET_JSON_pack_data_auto ("reserve_sig",
+                                  &reserve_sig));
+    GNUNET_assert (NULL != withdraw_obj);
+    GNUNET_assert (0 ==
+                   json_array_append_new (jc,
+                                          withdraw_obj));
+  }
+  {
+    CURL *eh;
+    struct GNUNET_CURL_Context *ctx;
+    json_t *req;
+
+    req = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_array_steal ("planchets",
+                                    jc));
+    ctx = TEAH_handle_to_context (exchange);
+    eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
+    if ( (NULL == eh) ||
+         (GNUNET_OK !=
+          TALER_curl_easy_post (&wh->post_ctx,
+                                eh,
+                                req)) )
+    {
+      GNUNET_break (0);
+      if (NULL != eh)
+        curl_easy_cleanup (eh);
+      json_decref (req);
+      TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+      return NULL;
+    }
+    json_decref (req);
+    wh->job = GNUNET_CURL_job_add2 (ctx,
+                                    eh,
+                                    wh->post_ctx.headers,
+                                    &handle_reserve_batch_withdraw_finished,
+                                    wh);
+  }
+  return wh;
+}
+
+
+void
+TALER_EXCHANGE_batch_withdraw2_cancel (
+  struct TALER_EXCHANGE_BatchWithdraw2Handle *wh)
+{
+  if (NULL != wh->job)
+  {
+    GNUNET_CURL_job_cancel (wh->job);
+    wh->job = NULL;
+  }
+  GNUNET_free (wh->url);
+  TALER_curl_easy_post_finished (&wh->post_ctx);
+  GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c
index f6a60f53..6bb579c2 100644
--- a/src/lib/exchange_api_withdraw.c
+++ b/src/lib/exchange_api_withdraw.c
@@ -248,10 +248,8 @@ withdraw_cs_stage_two_callback (
 struct TALER_EXCHANGE_WithdrawHandle *
 TALER_EXCHANGE_withdraw (
   struct TALER_EXCHANGE_Handle *exchange,
-  const struct TALER_EXCHANGE_DenomPublicKey *pk,
   const struct TALER_ReservePrivateKeyP *reserve_priv,
-  const struct TALER_PlanchetMasterSecretP *ps,
-  const struct TALER_AgeCommitmentHash *ach,
+  const struct TALER_EXCHANGE_WithdrawCoinInput *wci,
   TALER_EXCHANGE_WithdrawCallback res_cb,
   void *res_cb_cls)
 {
@@ -262,25 +260,25 @@ TALER_EXCHANGE_withdraw (
   wh->cb = res_cb;
   wh->cb_cls = res_cb_cls;
   wh->reserve_priv = reserve_priv;
-  wh->ps = *ps;
-  wh->ach = ach;
-  wh->pk = *pk;
+  wh->ps = *wci->ps;
+  wh->ach = wci->ach;
+  wh->pk = *wci->pk;
   TALER_denom_pub_deep_copy (&wh->pk.key,
-                             &pk->key);
+                             &wci->pk->key);
 
-  switch (pk->key.cipher)
+  switch (wci->pk->key.cipher)
   {
   case TALER_DENOMINATION_RSA:
     {
       wh->alg_values.cipher = TALER_DENOMINATION_RSA;
-      TALER_planchet_setup_coin_priv (ps,
+      TALER_planchet_setup_coin_priv (&wh->ps,
                                       &wh->alg_values,
                                       &wh->priv);
-      TALER_planchet_blinding_secret_create (ps,
+      TALER_planchet_blinding_secret_create (&wh->ps,
                                              &wh->alg_values,
                                              &wh->bks);
       if (GNUNET_OK !=
-          TALER_planchet_prepare (&pk->key,
+          TALER_planchet_prepare (&wh->pk.key,
                                   &wh->alg_values,
                                   &wh->bks,
                                   &wh->priv,
@@ -302,7 +300,7 @@ TALER_EXCHANGE_withdraw (
   case TALER_DENOMINATION_CS:
     {
       TALER_cs_withdraw_nonce_derive (
-        ps,
+        &wh->ps,
         &wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce);
       /* Note that we only initialize the first half
          of the blinded_planchet here; the other part
@@ -310,7 +308,7 @@ TALER_EXCHANGE_withdraw (
       wh->pd.blinded_planchet.cipher = TALER_DENOMINATION_CS;
       wh->csrh = TALER_EXCHANGE_csr_withdraw (
         exchange,
-        pk,
+        &wh->pk,
         &wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce,
         &withdraw_cs_stage_two_callback,
         wh);
diff --git a/src/testing/testing_api_cmd_withdraw.c 
b/src/testing/testing_api_cmd_withdraw.c
index de862f91..6f8b3a63 100644
--- a/src/testing/testing_api_cmd_withdraw.c
+++ b/src/testing/testing_api_cmd_withdraw.c
@@ -433,13 +433,18 @@ withdraw_run (void *cls,
                                    &ws->amount,
                                    &ws->pk->fees.withdraw));
   ws->reserve_history.details.withdraw.fee = ws->pk->fees.withdraw;
-  ws->wsh = TALER_EXCHANGE_withdraw (is->exchange,
-                                     ws->pk,
-                                     rp,
-                                     &ws->ps,
-                                     ws->h_age_commitment,
-                                     &reserve_withdraw_cb,
-                                     ws);
+  {
+    struct TALER_EXCHANGE_WithdrawCoinInput wci = {
+      .pk = ws->pk,
+      .ps = &ws->ps,
+      .ach = ws->h_age_commitment
+    };
+    ws->wsh = TALER_EXCHANGE_withdraw (is->exchange,
+                                       rp,
+                                       &wci,
+                                       &reserve_withdraw_cb,
+                                       ws);
+  }
   if (NULL == ws->wsh)
   {
     GNUNET_break (0);

-- 
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]