/*
  This file is part of TALER
  Copyright (C) 2023-2025 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_withdraw.c
 * @brief Implementation of /withdraw requests
 * @author Özgür Kesim
 */
/**
 * We want the "dangerous" exports here as these are OUR exports
 * and we want to check that the prototypes match.
 */
#define TALER_TESTING_EXPORTS_DANGEROUS 1
#include "platform.h"
#include <gnunet/gnunet_common.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 <sys/wait.h>
#include "taler_curl_lib.h"
#include "taler_error_codes.h"
#include "taler_json_lib.h"
#include "taler_exchange_service.h"
#include "exchange_api_common.h"
#include "exchange_api_handle.h"
#include "taler_signatures.h"
#include "exchange_api_curl_defaults.h"
#include "taler_util.h"

/**
 * A CoinCandidate is populated from a master secret.
 * The data is copied from and generated out of the client's input.
 */
struct CoinCandidate
{
  /**
   * The details derived form the master secrets
   */
  struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details;

  /**
   * Blinded hash of the coin
   **/
  struct TALER_BlindedCoinHashP blinded_coin_h;

};


/**
 * Closure for a call to /blinding-prepare, contains data that is needed to process
 * the result.
 */
struct BlindingPrepareClosure
{
  /**
   * Number of coins in the blinding-prepare step.
   * Not that this number might be smaller than the total number
   * of coins in the withdraw, as the prepare is only necessary
   * for CS denominations
   */
  size_t num_prepare_coins;

  /**
   * Array of @e num_prepare_coins of data per coin
   */
  struct BlindingPrepareCoinData
  {
    /**
     * Pointer to the candidate in CoinData.candidates,
     * to continue to build its contents based on the results from /blinding-prepare
     */
    struct CoinCandidate *candidate;

    /**
     * Planchet to finally generate in the corresponding candidate
     * in CoindData.planchet_details
     */
    struct TALER_PlanchetDetail *planchet;

    /**
     * Denomination information, needed for the
     * step after /blinding-prepare
     */
    const struct TALER_DenominationPublicKey *denom_pub;

    /**
     * True, if denomination supports age restriction
     */
    bool age_denom;

    /**
     * The index into the array of returned values from the call to
     * /blinding-prepare that are to be used for this coin.
     */
    size_t cs_idx;

  } *coins;

  /**
   * Number of seeds requested.  This may differ from @e num_prepare_coins
   * in case of a withdraw with required age proof, in which case
   * @e num_prepare_coins = TALER_CNC_KAPPA * @e num_seeds
   */
  size_t num_nonces;

  /**
   * Array of @e num_nonces calculated nonces.
   */
  union GNUNET_CRYPTO_BlindSessionNonce *nonces;

  /**
   * Handler to the originating call to /withdraw, needed to either
   * cancel the running withdraw request (on failure of the current call
   * to /blinding-prepare), or to eventually perform the protocol, once all
   * blinding-prepare requests have successfully finished.
   */
  struct TALER_EXCHANGE_WithdrawHandle *withdraw_handle;

};


/**
 * Data we keep per coin in the batch.
 * This is copied from and generated out of the input provided
 * by the client.
 */
struct CoinData
{
  /**
   * The denomination of the coin.
   */
  struct TALER_EXCHANGE_DenomPublicKey denom_pub;

  /**
   * The Candidates for the coin.  If the batch is not age-restricted,
   * only index 0 is used.
   */
  struct CoinCandidate candidates[TALER_CNC_KAPPA];

  /**
   * Details of the planchet(s).  If the batch is not age-restricted,
   * only index 0 is used.
   */
  struct TALER_PlanchetDetail planchet_details[TALER_CNC_KAPPA];
};


/**
 * A /withdraw request-handle for calls with pre-blinded planchets.
 * Returned by TALER_EXCHANGE_withdraw_blinded.
 */
struct TALER_EXCHANGE_WithdrawBlindedHandle
{

  /**
   * Reserve private key.
   */
  const struct TALER_ReservePrivateKeyP *reserve_priv;

  /**
   * Reserve public key, calculated
   */
  struct TALER_ReservePublicKeyP reserve_pub;

  /**
   * Signature of the reserve for the request, calculated after all
   * parameters for the coins are collected.
   */
  struct TALER_ReserveSignatureP reserve_sig;

  /*
   * The denomination keys of the exchange
   */
  struct TALER_EXCHANGE_Keys *keys;

  /**
   * The hash of all the planchets
   */
  struct TALER_HashBlindedPlanchetsP planchets_h;

  /**
   * Seed used for the derival of blinding factors for denominations
   * with Clause-Schnorr cipher.  We derive this from the master seed
   * for the withdraw, but independent from the other planchet seeds.
   */
  const struct TALER_BlindingMasterSeedP *blinding_seed;

  /**
   * Total amount requested (without fee).
   */
  struct TALER_Amount amount;

  /**
   * Total withdraw fee
   */
  struct TALER_Amount fee;

  /**
   * Is this call for age-restriced coins, with age proof?
   */
  bool with_age_proof;

  /**
   * If @e with_age_proof is true or @max_age is > 0,
   *  the age mask to use, extracted from the denominations.
   * MUST be the same for all denominations.
   */
  struct TALER_AgeMask age_mask;

  /**
   * The maximum age to commit to.  If @e with_age_proof
   * is true, the client will need to proof the correct setting
   * of age-restriction on the coins via an additional call
   * to /reveal-withdraw.
   */
  uint8_t max_age;

  /**
   * If @e with_age_proof is true, the hash of all the selected planchets
   */
  struct TALER_HashBlindedPlanchetsP selected_h;

  /**
   * Length of the either the @e blinded.input or
   * the @e blinded.with_age_proof_input array,
   * depending on @e with_age_proof.
   */
  size_t num_input;

  union
  {
    /**
     * The blinded planchet input candidates for age-restricted coins
     * for the call to /withdraw
     */
    const struct
    TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput *with_age_proof_input;

    /**
     * The blinded planchet input for the call to /withdraw via
     * TALER_EXCHANGE_withdraw_blinded, for age-unrestricted coins.
     */
    const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *input;

  } blinded;

  /**
   * The url for this request.
   */
  char *request_url;

  /**
   * Context for curl.
   */
  struct GNUNET_CURL_Context *curl_ctx;

  /**
   * CURL handle for the request job.
   */
  struct GNUNET_CURL_Job *job;

  /**
   * Post Context
   */
  struct TALER_CURL_PostContext post_ctx;

  /**
   * Function to call with withdraw response results.
   */
  TALER_EXCHANGE_WithdrawBlindedCallback callback;

  /**
   * Closure for @e blinded_callback
   */
  void *callback_cls;
};

/**
 * A /withdraw request-handle for calls from
 * a wallet, i. e. when blinding data is available.
 */
struct TALER_EXCHANGE_WithdrawHandle
{

  /**
   * The base-URL of the exchange.
   */
  const char *exchange_url;

  /**
   * Seed to derive of all seeds for the coins.
   */
  struct TALER_WithdrawMasterSeedP seed;

  /**
   * If @e with_age_proof is true, the derived TALER_CNC_KAPPA many
   * seeds for candidate batches.
   */
  struct TALER_KappaWithdrawMasterSeedP kappa_seed;

  /**
   * True if @e blinding_seed is filled, that is, if
   * any of the denominations is of cipher type CS
   */
  bool has_blinding_seed;

  /**
   * Seed used for the derivation of blinding factors for denominations
   * with Clause-Schnorr cipher.  We derive this from the master seed
   * for the withdraw, but independent from the other planchet seeds.
   * Only valid when @e has_blinding_seed is true;
   */
  struct TALER_BlindingMasterSeedP blinding_seed;

  /**
   * Reserve private key.
   */
  const struct TALER_ReservePrivateKeyP *reserve_priv;

  /**
   * Reserve public key, calculated
   */
  struct TALER_ReservePublicKeyP reserve_pub;

  /**
   * Signature of the reserve for the request, calculated after all
   * parameters for the coins are collected.
   */
  struct TALER_ReserveSignatureP reserve_sig;

  /*
   * The denomination keys of the exchange
   */
  struct TALER_EXCHANGE_Keys *keys;

  /**
   * True, if the withdraw is for age-restricted coins, with age-proof.
   * The denominations MUST support age restriction.
   */
  bool with_age_proof;

  /**
   * If @e with_age_proof is true, the age mask, extracted
   * from the denominations.
   * MUST be the same for all denominations.
   *
   */
  struct TALER_AgeMask age_mask;

  /**
   * The maximum age to commit to.  If @e with_age_proof
   * is true, the client will need to proof the correct setting
   * of age-restriction on the coins via an additional call
   * to /reveal-withdraw.
   */
  uint8_t max_age;

  /**
   * Length of the @e coin_data Array
   */
  size_t num_coins;

  /**
   * Array of per-coin data
   */
  struct CoinData *coin_data;

  /**
   * Context for curl.
   */
  struct GNUNET_CURL_Context *curl_ctx;

  /**
   * Function to call with withdraw response results.
   */
  TALER_EXCHANGE_WithdrawCallback callback;

  /**
   * Closure for @e callback
   */
  void *callback_cls;

  /* The handler for the call to /blinding-prepare, needed for CS denominations */
  struct TALER_EXCHANGE_BlindingPrepareHandle *blinding_prepare_handle;

  /* The Handler for the actual call to the exchange */
  struct TALER_EXCHANGE_WithdrawBlindedHandle *withdraw_blinded_handle;
};


/**
 * We got a 200 OK response for the /withdraw operation.
 * Extract the signatures and return them to the caller.
 *
 * @param wbh operation handle
 * @param j_response reply from the exchange
 * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
 */
static enum GNUNET_GenericReturnValue
withdraw_blinded_ok (
  struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh,
  const json_t *j_response)
{
  struct TALER_EXCHANGE_WithdrawBlindedResponse response = {
    .hr.reply = j_response,
    .hr.http_status = MHD_HTTP_OK,
  };
  const json_t *j_sigs;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_array_const ("ev_sigs",
                                  &j_sigs),
    GNUNET_JSON_spec_end ()
  };

  if (GNUNET_OK !=
      GNUNET_JSON_parse (j_response,
                         spec,
                         NULL, NULL))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }

  if (wbh->num_input != json_array_size (j_sigs))
  {
    /* Number of coins generated does not match our expectation */
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }

  {
    struct TALER_BlindedDenominationSignature denoms_sig[wbh->num_input];

    memset (denoms_sig,
            0,
            sizeof(denoms_sig));

    /* Reconstruct the coins and unblind the signatures */
    {
      json_t *j_sig;
      size_t i;

      json_array_foreach (j_sigs, i, j_sig)
      {
        struct GNUNET_JSON_Specification ispec[] = {
          TALER_JSON_spec_blinded_denom_sig (NULL,
                                             &denoms_sig[i]),
          GNUNET_JSON_spec_end ()
        };

        if (GNUNET_OK !=
            GNUNET_JSON_parse (j_sig,
                               ispec,
                               NULL, NULL))
        {
          GNUNET_break_op (0);
          return GNUNET_SYSERR;
        }
      }
    }

    response.details.ok.num_sigs = wbh->num_input;
    response.details.ok.blinded_denom_sigs = denoms_sig;
    response.details.ok.planchets_h = wbh->planchets_h;
    wbh->callback (
      wbh->callback_cls,
      &response);
    /* Make sure the callback isn't called again */
    wbh->callback = NULL;
    /* Free resources */
    for (size_t i = 0; i < wbh->num_input; i++)
      TALER_blinded_denom_sig_free (&denoms_sig[i]);
  }

  return GNUNET_OK;
}


/**
 * We got a 201 CREATED response for the /withdraw operation.
 * Extract the noreveal_index and return it to the caller.
 *
 * @param wbh operation handle
 * @param j_response reply from the exchange
 * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
 */
static enum GNUNET_GenericReturnValue
withdraw_blinded_created (
  struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh,
  const json_t *j_response)
{
  struct TALER_EXCHANGE_WithdrawBlindedResponse response = {
    .hr.reply = j_response,
    .hr.http_status = MHD_HTTP_CREATED,
    .details.created.planchets_h = wbh->planchets_h,
    .details.created.num_coins = wbh->num_input,
  };
  struct TALER_ExchangeSignatureP exchange_sig;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_uint8 ("noreveal_index",
                            &response.details.created.noreveal_index),
    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
                                 &exchange_sig),
    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
                                 &response.details.created.exchange_pub),
    GNUNET_JSON_spec_end ()
  };

  if (GNUNET_OK!=
      GNUNET_JSON_parse (j_response,
                         spec,
                         NULL, NULL))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }

  if (GNUNET_OK !=
      TALER_exchange_online_withdraw_age_confirmation_verify (
        &wbh->planchets_h,
        response.details.created.noreveal_index,
        &response.details.created.exchange_pub,
        &exchange_sig))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;

  }

  wbh->callback (wbh->callback_cls,
                 &response);
  /* make sure the callback isn't called again */
  wbh->callback = NULL;

  return GNUNET_OK;
}


/**
 * Function called when we're done processing the
 * HTTP /withdraw request.
 *
 * @param cls the `struct TALER_EXCHANGE_WithdrawBlindedHandle`
 * @param response_code The HTTP response code
 * @param response response data
 */
static void
handle_withdraw_blinded_finished (
  void *cls,
  long response_code,
  const void *response)
{
  struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh = cls;
  const json_t *j_response = response;
  struct TALER_EXCHANGE_WithdrawBlindedResponse wbr = {
    .hr.reply = j_response,
    .hr.http_status = (unsigned int) response_code
  };

  wbh->job = NULL;
  switch (response_code)
  {
  case 0:
    wbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    break;
  case MHD_HTTP_OK:
    {
      if (GNUNET_OK !=
          withdraw_blinded_ok (
            wbh,
            j_response))
      {
        GNUNET_break_op (0);
        wbr.hr.http_status = 0;
        wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
        break;
      }
      GNUNET_assert (NULL == wbh->callback);
      TALER_EXCHANGE_withdraw_blinded_cancel (wbh);
      return;
    }
  case MHD_HTTP_CREATED:
    if (GNUNET_OK !=
        withdraw_blinded_created (
          wbh,
          j_response))
    {
      GNUNET_break_op (0);
      wbr.hr.http_status = 0;
      wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
      break;
    }
    GNUNET_assert (NULL == wbh->callback);
    TALER_EXCHANGE_withdraw_blinded_cancel (wbh);
    return;
  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 */
    wbr.hr.ec = TALER_JSON_get_error_code (j_response);
    wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
    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 */
    wbr.hr.ec = TALER_JSON_get_error_code (j_response);
    wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
    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. */
    wbr.hr.ec = TALER_JSON_get_error_code (j_response);
    wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
    break;
  case MHD_HTTP_CONFLICT:
    /* The age requirements might not have been met */
    wbr.hr.ec = TALER_JSON_get_error_code (j_response);
    wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
    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 */
    wbr.hr.ec = TALER_JSON_get_error_code (j_response);
    wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
    break;
  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    /* only validate reply is well-formed */
    {
      struct GNUNET_JSON_Specification spec[] = {
        GNUNET_JSON_spec_fixed_auto (
          "h_payto",
          &wbr.details.unavailable_for_legal_reasons.h_payto),
        GNUNET_JSON_spec_uint64 (
          "requirement_row",
          &wbr.details.unavailable_for_legal_reasons.requirement_row),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (j_response,
                             spec,
                             NULL, NULL))
      {
        GNUNET_break_op (0);
        wbr.hr.http_status = 0;
        wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
        break;
      }
      break;
    }
  case MHD_HTTP_INTERNAL_SERVER_ERROR:
    /* Server had an internal issue; we should retry, but this API
       leaves this to the application */
    wbr.hr.ec = TALER_JSON_get_error_code (j_response);
    wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
    break;
  default:
    /* unexpected response code */
    GNUNET_break_op (0);
    wbr.hr.ec = TALER_JSON_get_error_code (j_response);
    wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u/%d for exchange withdraw\n",
                (unsigned int) response_code,
                (int) wbr.hr.ec);
    break;
  }
  wbh->callback (wbh->callback_cls,
                 &wbr);
  TALER_EXCHANGE_withdraw_blinded_cancel (wbh);
}


/**
 * Runs the actual withdraw operation with the blinded planchets.
 *
 * @param[in,out] wbh  withdraw blinded handle
 */
static void
perform_withdraw_protocol (
  struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh)
{
#define FAIL_IF(cond) \
        do { \
          if ((cond)) \
          { \
            GNUNET_break (! (cond)); \
            goto ERROR; \
          } \
        } while (0)

  json_t *j_denoms = NULL;
  json_t *j_planchets = NULL;
  json_t *j_request_body = NULL;
  CURL *curlh = NULL;
  struct GNUNET_HashContext *coins_hctx = NULL;
  struct TALER_BlindedCoinHashP bch;

  GNUNET_assert (0 < wbh->num_input);

  FAIL_IF (GNUNET_OK !=
           TALER_amount_set_zero (wbh->keys->currency,
                                  &wbh->amount));
  FAIL_IF (GNUNET_OK !=
           TALER_amount_set_zero (wbh->keys->currency,
                                  &wbh->fee));

  /* Accumulate total value with fees */
  for (size_t i = 0; i < wbh->num_input; i++)
  {
    const struct TALER_EXCHANGE_DenomPublicKey *dpub =
      wbh->with_age_proof ?
      wbh->blinded.with_age_proof_input[i].denom_pub :
      wbh->blinded.input[i].denom_pub;

    FAIL_IF (0 >
             TALER_amount_add (&wbh->amount,
                               &wbh->amount,
                               &dpub->value));
    FAIL_IF (0 >
             TALER_amount_add (&wbh->fee,
                               &wbh->fee,
                               &dpub->fees.withdraw));

    if (GNUNET_CRYPTO_BSA_CS ==
        dpub->key.bsign_pub_key->cipher)
      GNUNET_assert (NULL != wbh->blinding_seed);

  }

  if (wbh->with_age_proof || wbh->max_age > 0)
  {
    wbh->age_mask =
      wbh->blinded.with_age_proof_input[0].denom_pub->key.age_mask;

    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Attempting to withdraw from reserve %s with maximum age %d to proof\n",
                TALER_B2S (&wbh->reserve_pub),
                wbh->max_age);
  }
  else
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Attempting to withdraw from reserve %s\n",
                TALER_B2S (&wbh->reserve_pub));
  }

  coins_hctx = GNUNET_CRYPTO_hash_context_start ();
  FAIL_IF (NULL == coins_hctx);

  j_denoms = json_array ();
  j_planchets = json_array ();
  FAIL_IF ((NULL == j_denoms) ||
           (NULL == j_planchets));

  for (size_t i  = 0; i< wbh->num_input; i++)
  {
    /* Build the denomination array */
    const struct TALER_EXCHANGE_DenomPublicKey *denom_pub =
      wbh->with_age_proof ?
      wbh->blinded.with_age_proof_input[i].denom_pub :
      wbh->blinded.input[i].denom_pub;
    const struct TALER_DenominationHashP *denom_h = &denom_pub->h_key;
    json_t *jdenom;

    /* The mask must be the same for all coins */
    FAIL_IF (wbh->with_age_proof &&
             (wbh->age_mask.bits != denom_pub->key.age_mask.bits));

    jdenom = GNUNET_JSON_from_data_auto (denom_h);
    FAIL_IF (NULL == jdenom);
    FAIL_IF (0 > json_array_append_new (j_denoms,
                                        jdenom));
  }


  /* Build the planchet array and calculate the hash over all planchets. */
  if (! wbh->with_age_proof)
  {
    for (size_t i  = 0; i< wbh->num_input; i++)
    {
      const struct TALER_PlanchetDetail *planchet =
        &wbh->blinded.input[i].planchet_details;
      json_t *jc = GNUNET_JSON_PACK (
        TALER_JSON_pack_blinded_planchet (
          NULL,
          &planchet->blinded_planchet));
      FAIL_IF (NULL == jc);
      FAIL_IF (0 > json_array_append_new (j_planchets,
                                          jc));

      TALER_coin_ev_hash (&planchet->blinded_planchet,
                          &planchet->denom_pub_hash,
                          &bch);

      GNUNET_CRYPTO_hash_context_read (coins_hctx,
                                       &bch,
                                       sizeof(bch));
    }
  }
  else
  { /* Age restricted case with required age-proof. */

    /**
     * We collect the run of all coin candidates for the same γ index
     * first, then γ+1 etc.
     */
    for (size_t k = 0; k < TALER_CNC_KAPPA; k++)
    {
      struct GNUNET_HashContext *batch_ctx;
      struct TALER_BlindedCoinHashP batch_h;

      batch_ctx = GNUNET_CRYPTO_hash_context_start ();
      FAIL_IF (NULL == batch_ctx);

      for (size_t i  = 0; i< wbh->num_input; i++)
      {
        const struct TALER_PlanchetDetail *planchet =
          &wbh->blinded.with_age_proof_input[i].planchet_details[k];
        json_t *jc = GNUNET_JSON_PACK (
          TALER_JSON_pack_blinded_planchet (
            NULL,
            &planchet->blinded_planchet));

        FAIL_IF (NULL == jc);
        FAIL_IF (0 > json_array_append_new (
                   j_planchets,
                   jc));

        TALER_coin_ev_hash (
          &planchet->blinded_planchet,
          &planchet->denom_pub_hash,
          &bch);

        GNUNET_CRYPTO_hash_context_read (
          batch_ctx,
          &bch,
          sizeof(bch));
      }

      GNUNET_CRYPTO_hash_context_finish (
        batch_ctx,
        &batch_h.hash);
      GNUNET_CRYPTO_hash_context_read (
        coins_hctx,
        &batch_h,
        sizeof(batch_h));
    }
  }

  /* Build the hash of the planchets */
  GNUNET_CRYPTO_hash_context_finish (
    coins_hctx,
    &wbh->planchets_h.hash);
  coins_hctx = NULL;

  /* Sign the request */
  TALER_wallet_withdraw_sign (
    &wbh->amount,
    &wbh->fee,
    &wbh->planchets_h,
    wbh->blinding_seed,
    wbh->with_age_proof ? &wbh->age_mask: NULL,
    wbh->with_age_proof ? wbh->max_age : 0,
    wbh->reserve_priv,
    &wbh->reserve_sig);

  /* Initiate the POST-request */
  j_request_body = GNUNET_JSON_PACK (
    GNUNET_JSON_pack_string ("cipher",
                             "ED25519"),
    GNUNET_JSON_pack_data_auto ("reserve_pub",
                                &wbh->reserve_pub),
    GNUNET_JSON_pack_array_steal ("denoms_h",
                                  j_denoms),
    GNUNET_JSON_pack_array_steal ("coin_evs",
                                  j_planchets),
    GNUNET_JSON_pack_allow_null (
      wbh->with_age_proof
      ? GNUNET_JSON_pack_int64 ("max_age",
                                wbh->max_age)
      : GNUNET_JSON_pack_string ("max_age",
                                 NULL) ),
    GNUNET_JSON_pack_data_auto ("reserve_sig",
                                &wbh->reserve_sig));
  FAIL_IF (NULL == j_request_body);

  if (NULL != wbh->blinding_seed)
  {
    json_t *j_seed = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_data_auto ("blinding_seed",
                                  wbh->blinding_seed));
    GNUNET_assert (NULL != j_seed);
    GNUNET_assert (0 ==
                   json_object_update_new (
                     j_request_body,
                     j_seed));
  }

  curlh = TALER_EXCHANGE_curl_easy_get_ (wbh->request_url);
  FAIL_IF (NULL == curlh);
  FAIL_IF (GNUNET_OK !=
           TALER_curl_easy_post (
             &wbh->post_ctx,
             curlh,
             j_request_body));
  json_decref (j_request_body);
  j_request_body = NULL;

  wbh->job = GNUNET_CURL_job_add2 (
    wbh->curl_ctx,
    curlh,
    wbh->post_ctx.headers,
    &handle_withdraw_blinded_finished,
    wbh);
  FAIL_IF (NULL == wbh->job);

  /* No errors, return */
  return;

ERROR:
  if (NULL != coins_hctx)
    GNUNET_CRYPTO_hash_context_abort (coins_hctx);
  if (NULL != j_denoms)
    json_decref (j_denoms);
  if (NULL != j_planchets)
    json_decref (j_planchets);
  if (NULL != j_request_body)
    json_decref (j_request_body);
  if (NULL != curlh)
    curl_easy_cleanup (curlh);
  TALER_EXCHANGE_withdraw_blinded_cancel (wbh);
  return;
#undef FAIL_IF
}


/**
 * @brief Callback to copy the results from the call to TALER_withdraw_blinded
 * in the non-age-restricted case to the result for the originating call from TALER_withdraw.
 *
 * @param cls struct TALER_WithdrawHandle
 * @param wbr The response
 */
static void
copy_results (
  void *cls,
  const struct TALER_EXCHANGE_WithdrawBlindedResponse *wbr)
{
  /* The original handle from the top-level call to withdraw */
  struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
  struct TALER_EXCHANGE_WithdrawResponse resp = {
    .hr = wbr->hr,
  };

  wh->withdraw_blinded_handle = NULL;

  /**
   * The withdraw protocol has been performed with blinded data.
   * Now the response can be copied as is, except for the MHD_HTTP_OK case,
   * in which we now need to perform the unblinding.
   */
  switch (wbr->hr.http_status)
  {
  case MHD_HTTP_OK:
    {
      struct TALER_EXCHANGE_WithdrawCoinPrivateDetails
        details[GNUNET_NZL (wh->num_coins)];
      bool ok = true;

      GNUNET_assert (wh->num_coins == wbr->details.ok.num_sigs);
      memset (details,
              0,
              sizeof(details));
      resp.details.ok.num_sigs = wbr->details.ok.num_sigs;
      resp.details.ok.coin_details = details;
      resp.details.ok.planchets_h = wbr->details.ok.planchets_h;
      for (size_t n = 0; n<wh->num_coins; n++)
      {
        const struct TALER_BlindedDenominationSignature *bsig =
          &wbr->details.ok.blinded_denom_sigs[n];
        struct CoinData *cd = &wh->coin_data[n];
        struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *coin = &details[n];
        struct TALER_FreshCoin fresh_coin;

        *coin = wh->coin_data[n].candidates[0].details;
        coin->planchet = wh->coin_data[n].planchet_details[0];
        GNUNET_CRYPTO_eddsa_key_get_public (
          &coin->coin_priv.eddsa_priv,
          &coin->coin_pub.eddsa_pub);

        if (GNUNET_OK !=
            TALER_planchet_to_coin (&cd->denom_pub.key,
                                    bsig,
                                    &coin->blinding_key,
                                    &coin->coin_priv,
                                    &coin->h_age_commitment,
                                    &coin->h_coin_pub,
                                    &coin->blinding_values,
                                    &fresh_coin))
        {
          resp.hr.http_status = 0;
          resp.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE;
          GNUNET_break_op (0);
          ok = false;
          break;
        }
        coin->denom_sig = fresh_coin.sig;
      }
      if (ok)
      {
        wh->callback (
          wh->callback_cls,
          &resp);
        wh->callback = NULL;
      }
      for (size_t n = 0; n<wh->num_coins; n++)
      {
        struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *coin = &details[n];

        TALER_denom_sig_free (&coin->denom_sig);
      }
      break;
    }
  case MHD_HTTP_CREATED:
    resp.details.created  = wbr->details.created;
    break;

  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    resp.details.unavailable_for_legal_reasons =
      wbr->details.unavailable_for_legal_reasons;
    break;

  default:
    /* nothing to do here, .hr.ec and .hr.hint are all set already from previous response */
    break;
  }
  if (NULL != wh->callback)
  {
    wh->callback (
      wh->callback_cls,
      &resp);
    wh->callback = NULL;
  }
  TALER_EXCHANGE_withdraw_cancel (wh);
}


/**
 * @brief Callback to copy the results from the call to TALER_withdraw_blinded
 * in the age-restricted case to the result for the originating call from TALER_withdraw.
 *
 * @param cls struct TALER_WithdrawHandle
 * @param wbr The response
 */
static void
copy_results_with_age_proof (
  void *cls,
  const struct TALER_EXCHANGE_WithdrawBlindedResponse *wbr)
{
  /* The original handle from the top-level call to withdraw */
  struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
  uint8_t k =  wbr->details.created.noreveal_index;
  struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details[wh->num_coins];
  struct TALER_EXCHANGE_WithdrawResponse resp = {
    .hr = wbr->hr,
  };

  wh->withdraw_blinded_handle = NULL;

  switch (wbr->hr.http_status)
  {
  case MHD_HTTP_OK:
    /* in the age-restricted case, this should not happen */
    GNUNET_break_op (0);
    break;

  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    resp.details.unavailable_for_legal_reasons =
      wbr->details.unavailable_for_legal_reasons;
    break;

  case MHD_HTTP_CREATED:
    {
      GNUNET_assert (wh->num_coins == wbr->details.created.num_coins);
      resp.details.created = wbr->details.created;
      resp.details.created.coin_details = details;
      resp.details.created.kappa_seed = wh->kappa_seed;
      memset (details,
              0,
              sizeof(details));
      for (size_t n = 0; n< wh->num_coins; n++)
      {
        details[n] = wh->coin_data[n].candidates[k].details;
        details[n].planchet = wh->coin_data[n].planchet_details[k];
      }
      break;
    }

  default:
    break;
  }

  wh->callback (
    wh->callback_cls,
    &resp);
  wh->callback = NULL;
  TALER_EXCHANGE_withdraw_cancel (wh);
}


/**
 * @brief Prepares and executes TALER_EXCHANGE_withdraw_blinded.
 * If there were CS-denominations involved, started once the all calls
 * to /blinding-prepare are done.
 */
static void
call_withdraw_blinded (
  struct TALER_EXCHANGE_WithdrawHandle *wh)
{

  GNUNET_assert (NULL == wh->blinding_prepare_handle);

  if (! wh->with_age_proof)
  {
    struct TALER_EXCHANGE_WithdrawBlindedCoinInput input[wh->num_coins];

    memset (input,
            0,
            sizeof(input));

    /* Prepare the blinded planchets as input */
    for (size_t n = 0; n < wh->num_coins; n++)
    {
      input[n].denom_pub =
        &wh->coin_data[n].denom_pub;
      input[n].planchet_details =
        *wh->coin_data[n].planchet_details;
    }

    wh->withdraw_blinded_handle =
      TALER_EXCHANGE_withdraw_blinded (
        wh->curl_ctx,
        wh->keys,
        wh->exchange_url,
        wh->reserve_priv,
        wh->has_blinding_seed ? &wh->blinding_seed : NULL,
        wh->num_coins,
        input,
        &copy_results,
        wh);
  }
  else
  {  /* age restricted case */
    struct TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput
      ari[wh->num_coins];

    memset (ari,
            0,
            sizeof(ari));

    /* Prepare the blinded planchets as input */
    for (size_t n = 0; n < wh->num_coins; n++)
    {
      ari[n].denom_pub = &wh->coin_data[n].denom_pub;
      for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
        ari[n].planchet_details[k] =
          wh->coin_data[n].planchet_details[k];
    }

    wh->withdraw_blinded_handle =
      TALER_EXCHANGE_withdraw_blinded_with_age_proof (
        wh->curl_ctx,
        wh->keys,
        wh->exchange_url,
        wh->reserve_priv,
        wh->has_blinding_seed ? &wh->blinding_seed : NULL,
        wh->max_age,
        wh->num_coins,
        ari,
        &copy_results_with_age_proof,
        wh);
  }
}


/**
 * @brief Function called when /blinding-prepare is finished
 *
 * @param cls the `struct BlindingPrepareClosure *`
 * @param bpr replies from the /blinding-prepare request
 */
static void
blinding_prepare_done (
  void *cls,
  const struct TALER_EXCHANGE_BlindingPrepareResponse *bpr)
{
  struct BlindingPrepareClosure *bpcls = cls;
  struct TALER_EXCHANGE_WithdrawHandle *wh = bpcls->withdraw_handle;

  wh->blinding_prepare_handle = NULL;
  switch (bpr->hr.http_status)
  {
  case MHD_HTTP_OK:
    {
      bool success = false;
      size_t num = bpr->details.ok.num_blinding_values;

      GNUNET_assert (0 != num);
      GNUNET_assert (num == bpcls->num_nonces);
      for (size_t i = 0; i < bpcls->num_prepare_coins; i++)
      {
        struct TALER_PlanchetDetail *planchet = bpcls->coins[i].planchet;
        struct CoinCandidate *can = bpcls->coins[i].candidate;
        size_t cs_idx = bpcls->coins[i].cs_idx;

        GNUNET_assert (NULL != can);
        GNUNET_assert (NULL != planchet);
        success = false;

        /* Complete the initialization of the coin with CS denomination */
        TALER_denom_ewv_copy (
          &can->details.blinding_values,
          &bpr->details.ok.blinding_values[cs_idx]);

        GNUNET_assert (GNUNET_CRYPTO_BSA_CS ==
                       can->details.blinding_values.blinding_inputs->cipher);

        TALER_planchet_setup_coin_priv (
          &can->details.secret,
          &can->details.blinding_values,
          &can->details.coin_priv);

        TALER_planchet_blinding_secret_create (
          &can->details.secret,
          &can->details.blinding_values,
          &can->details.blinding_key);

        /* This initializes the 2nd half of the
           can->planchet_detail.blinded_planchet */
        if (GNUNET_OK !=
            TALER_planchet_prepare (
              bpcls->coins[i].denom_pub,
              &can->details.blinding_values,
              &can->details.blinding_key,
              &bpcls->nonces[cs_idx],
              &can->details.coin_priv,
              &can->details.h_age_commitment,
              &can->details.h_coin_pub,
              planchet))
        {
          GNUNET_break (0);
          break;
        }

        TALER_coin_ev_hash (&planchet->blinded_planchet,
                            &planchet->denom_pub_hash,
                            &can->blinded_coin_h);
        success = true;
      }

      /* /blinding-prepare is done, we can now perform the
       * actual withdraw operation */
      if (success)
        call_withdraw_blinded (wh);
      goto cleanup;
    }
  default:
    {
      /* We got an error condition during blinding prepare that we need to report */
      struct TALER_EXCHANGE_WithdrawResponse resp = {
        .hr = bpr->hr
      };

      wh->callback (
        wh->callback_cls,
        &resp);

      wh->callback = NULL;
      break;
    }
  }
  TALER_EXCHANGE_withdraw_cancel (wh);
cleanup:
  GNUNET_free (bpcls->coins);
  GNUNET_free (bpcls->nonces);
  GNUNET_free (bpcls);
}


/**
 * @brief Prepares non age-restricted coins for the call to withdraw and
 * calculates the total amount with fees.
 * For denomination with CS as cipher, initiates the preflight to retrieve the
 * bpcls-parameter via /blinding-prepare.
 * Note that only one of the three parameters seed, tuples or secrets must not be NULL
 *
 * @param wh The handler to the withdraw
 * @param num_coins Number of coins to withdraw
 * @param max_age The maximum age to commit to
 * @param denoms_pub Array @e num_coins of denominations
 * @param seed master seed from which to derive @e num_coins secrets and blinding, if @e blinding_seed is NULL
 * @param blinding_seed master seed for the blinding. Might be NULL, in which case the blinding_seed is derived from @e seed
 * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
 */
static enum GNUNET_GenericReturnValue
prepare_coins (
  struct TALER_EXCHANGE_WithdrawHandle *wh,
  size_t num_coins,
  uint8_t max_age,
  const struct TALER_EXCHANGE_DenomPublicKey *denoms_pub,
  const struct TALER_WithdrawMasterSeedP *seed,
  const struct TALER_BlindingMasterSeedP *blinding_seed)
{
  size_t cs_num = 0;
  struct BlindingPrepareClosure *cs_closure;
  uint8_t kappa;

#define FAIL_IF(cond) \
        do \
        { \
          if ((cond)) \
          { \
            GNUNET_break (! (cond)); \
            goto ERROR; \
          } \
        } while (0)

  GNUNET_assert (0 < num_coins);

  wh->num_coins = num_coins;
  wh->max_age = max_age;
  wh->age_mask = denoms_pub[0].key.age_mask;
  wh->coin_data = GNUNET_new_array (
    wh->num_coins,
    struct CoinData);

  /* First, figure out how many Clause-Schnorr denominations we have */
  for (size_t i =0; i< wh->num_coins; i++)
  {
    if (GNUNET_CRYPTO_BSA_CS ==
        denoms_pub[i].key.bsign_pub_key->cipher)
      cs_num++;
  }

  if (wh->with_age_proof)
  {
    kappa = TALER_CNC_KAPPA;
  }
  else
  {
    kappa = 1;
  }

  {
    struct TALER_PlanchetMasterSecretP secrets[kappa][num_coins];
    struct TALER_EXCHANGE_NonceKey cs_nonce_keys[GNUNET_NZL (cs_num)];
    uint32_t cs_indices[GNUNET_NZL (cs_num)];

    size_t cs_denom_idx = 0;
    size_t cs_coin_idx = 0;

    if (wh->with_age_proof)
    {
      TALER_withdraw_expand_kappa_seed (seed,
                                        &wh->kappa_seed);
      for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
      {
        TALER_withdraw_expand_secrets (
          num_coins,
          &wh->kappa_seed.tuple[k],
          secrets[k]);
      }
    }
    else
    {
      TALER_withdraw_expand_secrets (
        num_coins,
        seed,
        secrets[0]);
    }

    if (0 < cs_num)
    {
      memset (cs_nonce_keys,
              0,
              sizeof(cs_nonce_keys));
      cs_closure = GNUNET_new (struct BlindingPrepareClosure);
      cs_closure->withdraw_handle = wh;
      cs_closure->num_prepare_coins = cs_num * kappa;
      GNUNET_assert ((1 == kappa) || (cs_num * kappa > cs_num));
      cs_closure->coins =
        GNUNET_new_array (cs_closure->num_prepare_coins,
                          struct BlindingPrepareCoinData);
      cs_closure->num_nonces = cs_num;
      cs_closure->nonces =
        GNUNET_new_array (cs_closure->num_nonces,
                          union GNUNET_CRYPTO_BlindSessionNonce);
    }
    else
    {
      cs_closure = NULL;
    }

    for (uint32_t i = 0; i < wh->num_coins; i++)
    {
      struct CoinData *cd = &wh->coin_data[i];
      bool age_denom = (0 != denoms_pub[i].key.age_mask.bits);

      cd->denom_pub = denoms_pub[i];
      /* The age mask must be the same for all coins */
      FAIL_IF (wh->with_age_proof &&
               (0 ==  denoms_pub[i].key.age_mask.bits));
      FAIL_IF (wh->age_mask.bits !=
               denoms_pub[i].key.age_mask.bits);
      TALER_denom_pub_copy (&cd->denom_pub.key,
                            &denoms_pub[i].key);

      /* Mark the indices of the coins which are of type Clause-Schnorr
       * and add their denomination public key hash to the list.
       */
      if (GNUNET_CRYPTO_BSA_CS ==
          cd->denom_pub.key.bsign_pub_key->cipher)
      {
        GNUNET_assert (cs_denom_idx<cs_num);
        cs_indices[cs_denom_idx] = i;
        cs_nonce_keys[cs_denom_idx].cnc_num = i;
        cs_nonce_keys[cs_denom_idx].pk = &cd->denom_pub;
        cs_denom_idx++;
      }

      /*
       * Note that we "loop" here either only once (if with_age_proof is false),
       * or TALER_CNC_KAPPA times.
       */
      for (uint8_t k = 0; k < kappa; k++)
      {
        struct CoinCandidate *can = &cd->candidates[k];
        struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];

        can->details.secret = secrets[k][i];
        /*
         * The age restriction needs to be set on a coin if the denomination
         * support age restriction. Note that his is regardless of weither
         *  with_age_proof is set or not.
         */
        if (age_denom)
        {
          /* Derive the age restriction from the given secret and
           * the maximum age */
          TALER_age_restriction_from_secret (
            &can->details.secret,
            &wh->age_mask,
            wh->max_age,
            &can->details.age_commitment_proof);

          TALER_age_commitment_hash (
            &can->details.age_commitment_proof.commitment,
            &can->details.h_age_commitment);
        }

        switch (cd->denom_pub.key.bsign_pub_key->cipher)
        {
        case GNUNET_CRYPTO_BSA_RSA:
          TALER_denom_ewv_copy (&can->details.blinding_values,
                                TALER_denom_ewv_rsa_singleton ());
          TALER_planchet_setup_coin_priv (&can->details.secret,
                                          &can->details.blinding_values,
                                          &can->details.coin_priv);
          TALER_planchet_blinding_secret_create (&can->details.secret,
                                                 &can->details.blinding_values,
                                                 &can->details.blinding_key);
          FAIL_IF (GNUNET_OK !=
                   TALER_planchet_prepare (&cd->denom_pub.key,
                                           &can->details.blinding_values,
                                           &can->details.blinding_key,
                                           NULL,
                                           &can->details.coin_priv,
                                           (age_denom)
                                           ? &can->details.h_age_commitment
                                           : NULL,
                                           &can->details.h_coin_pub,
                                           planchet));
          TALER_coin_ev_hash (&planchet->blinded_planchet,
                              &planchet->denom_pub_hash,
                              &can->blinded_coin_h);

          break;

        case GNUNET_CRYPTO_BSA_CS:
          {
            /**
             * Prepare the nonce and save the index and the denomination for the callback
             * after the call to blinding-prepare
             */
            cs_closure->coins[cs_coin_idx].candidate = can;
            cs_closure->coins[cs_coin_idx].planchet = planchet;
            cs_closure->coins[cs_coin_idx].denom_pub = &cd->denom_pub.key;
            cs_closure->coins[cs_coin_idx].cs_idx = i;
            cs_closure->coins[cs_coin_idx].age_denom = age_denom;
            cs_coin_idx++;
            break;
          }
        default:
          FAIL_IF (1);
        }
      }
    }

    if (0 < cs_num)
    {
      if (NULL != blinding_seed)
      {
        wh->blinding_seed = *blinding_seed;
      }
      else
      {
        TALER_cs_withdraw_seed_to_blinding_seed (
          seed,
          &wh->blinding_seed);
      }
      wh->has_blinding_seed = true;

      TALER_cs_derive_only_cs_blind_nonces_from_seed (
        &wh->blinding_seed,
        false, /* not for melt */
        cs_num,
        cs_indices,
        cs_closure->nonces);

      wh->blinding_prepare_handle =
        TALER_EXCHANGE_blinding_prepare_for_withdraw (
          wh->curl_ctx,
          wh->exchange_url,
          &wh->blinding_seed,
          cs_num,
          cs_nonce_keys,
          &blinding_prepare_done,
          cs_closure);
      FAIL_IF (NULL == wh->blinding_prepare_handle);
    }
  }
  return GNUNET_OK;

ERROR:
  if (0<cs_num)
  {
    GNUNET_free (cs_closure->nonces);
    GNUNET_free (cs_closure);
  }
  TALER_EXCHANGE_withdraw_cancel (wh);
  return GNUNET_SYSERR;
#undef FAIL_IF

}


/**
 * Prepare a withdraw handle for both, the non-restricted
 * and age-restricted case.
 *
 * @param curl_ctx The curl context to use
 * @param keys The keys from the exchange
 * @param exchange_url The base url to the exchange
 * @param reserve_priv The private key of the exchange
 * @param res_cb The callback to call on response
 * @param res_cb_cls The closure to pass to the callback
 */
static struct TALER_EXCHANGE_WithdrawHandle *
setup_withdraw_handle (
  struct GNUNET_CURL_Context *curl_ctx,
  struct TALER_EXCHANGE_Keys *keys,
  const char *exchange_url,
  const struct TALER_ReservePrivateKeyP *reserve_priv,
  TALER_EXCHANGE_WithdrawCallback res_cb,
  void *res_cb_cls)
{
  struct TALER_EXCHANGE_WithdrawHandle *wh;

  wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle);
  wh->exchange_url = exchange_url;
  wh->keys = TALER_EXCHANGE_keys_incref (keys);
  wh->curl_ctx = curl_ctx;
  wh->reserve_priv = reserve_priv;
  wh->callback = res_cb;
  wh->callback_cls = res_cb_cls;

  return wh;
}


struct TALER_EXCHANGE_WithdrawHandle *
TALER_EXCHANGE_withdraw_extra_blinding_seed (
  struct GNUNET_CURL_Context *curl_ctx,
  struct TALER_EXCHANGE_Keys *keys,
  const char *exchange_url,
  const struct TALER_ReservePrivateKeyP *reserve_priv,
  size_t num_coins,
  const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins],
  const struct TALER_WithdrawMasterSeedP *seed,
  const struct TALER_BlindingMasterSeedP *blinding_seed,
  uint8_t opaque_max_age,
  TALER_EXCHANGE_WithdrawCallback res_cb,
  void *res_cb_cls)
{
  struct TALER_EXCHANGE_WithdrawHandle *wh;

  wh = setup_withdraw_handle (curl_ctx,
                              keys,
                              exchange_url,
                              reserve_priv,
                              res_cb,
                              res_cb_cls);
  GNUNET_assert (NULL != wh);
  wh->with_age_proof = false;

  if (GNUNET_OK !=
      prepare_coins (wh,
                     num_coins,
                     opaque_max_age,
                     denoms_pub,
                     seed,
                     blinding_seed))
  {
    GNUNET_free (wh);
    return NULL;
  }

  /* If there were no CS denominations, we can now perform the actual
   * withdraw protocol.  Otherwise, there are calls to /blinding-prepare
   * in flight and once they finish, the withdraw-protocol will be
   * called from within the blinding_prepare_done-function.
   */
  if (NULL == wh->blinding_prepare_handle)
    call_withdraw_blinded (wh);

  return wh;
}


struct TALER_EXCHANGE_WithdrawHandle *
TALER_EXCHANGE_withdraw (
  struct GNUNET_CURL_Context *curl_ctx,
  struct TALER_EXCHANGE_Keys *keys,
  const char *exchange_url,
  const struct TALER_ReservePrivateKeyP *reserve_priv,
  size_t num_coins,
  const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins],
  const struct TALER_WithdrawMasterSeedP *seed,
  uint8_t opaque_max_age,
  TALER_EXCHANGE_WithdrawCallback res_cb,
  void *res_cb_cls)
{
  return TALER_EXCHANGE_withdraw_extra_blinding_seed (
    curl_ctx,
    keys,
    exchange_url,
    reserve_priv,
    num_coins,
    denoms_pub,
    seed,
    NULL,
    opaque_max_age,
    res_cb,
    res_cb_cls
    );
}


struct TALER_EXCHANGE_WithdrawHandle *
TALER_EXCHANGE_withdraw_with_age_proof_extra_blinding_seed (
  struct GNUNET_CURL_Context *curl_ctx,
  struct TALER_EXCHANGE_Keys *keys,
  const char *exchange_url,
  const struct TALER_ReservePrivateKeyP *reserve_priv,
  size_t num_coins,
  const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins],
  const struct TALER_WithdrawMasterSeedP *seed,
  const struct TALER_BlindingMasterSeedP *blinding_seed,
  uint8_t max_age,
  TALER_EXCHANGE_WithdrawCallback res_cb,
  void *res_cb_cls)
{
  struct TALER_EXCHANGE_WithdrawHandle *wh;

  wh = setup_withdraw_handle (curl_ctx,
                              keys,
                              exchange_url,
                              reserve_priv,
                              res_cb,
                              res_cb_cls);
  GNUNET_assert (NULL != wh);

  wh->with_age_proof = true;

  if (GNUNET_OK !=
      prepare_coins (wh,
                     num_coins,
                     max_age,
                     denoms_pub,
                     seed,
                     blinding_seed))
  {
    GNUNET_free (wh);
    return NULL;
  }

  /* If there were no CS denominations, we can now perform the actual
   * withdraw protocol.  Otherwise, there are calls to /blinding-prepare
   * in flight and once they finish, the withdraw-protocol will be
   * called from within the blinding_prepare_done-function.
   */
  if (NULL == wh->blinding_prepare_handle)
    call_withdraw_blinded (wh);

  return wh;
}


struct TALER_EXCHANGE_WithdrawHandle *
TALER_EXCHANGE_withdraw_with_age_proof (
  struct GNUNET_CURL_Context *curl_ctx,
  struct TALER_EXCHANGE_Keys *keys,
  const char *exchange_url,
  const struct TALER_ReservePrivateKeyP *reserve_priv,
  size_t num_coins,
  const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins],
  const struct TALER_WithdrawMasterSeedP *seed,
  uint8_t max_age,
  TALER_EXCHANGE_WithdrawCallback res_cb,
  void *res_cb_cls)
{
  return TALER_EXCHANGE_withdraw_with_age_proof_extra_blinding_seed (
    curl_ctx,
    keys,
    exchange_url,
    reserve_priv,
    num_coins,
    denoms_pub,
    seed,
    NULL,
    max_age,
    res_cb,
    res_cb_cls);
}


void
TALER_EXCHANGE_withdraw_cancel (
  struct TALER_EXCHANGE_WithdrawHandle *wh)
{
  uint8_t kappa = wh->with_age_proof ? TALER_CNC_KAPPA : 1;

  /* Cleanup coin data */
  for (unsigned int i = 0; i<wh->num_coins; i++)
  {
    struct CoinData *cd = &wh->coin_data[i];

    for (uint8_t k = 0; k < kappa; k++)
    {
      struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
      struct CoinCandidate *can = &cd->candidates[k];

      TALER_blinded_planchet_free (&planchet->blinded_planchet);
      TALER_denom_ewv_free (&can->details.blinding_values);
      TALER_age_commitment_proof_free (&can->details.age_commitment_proof);
    }
    TALER_denom_pub_free (&cd->denom_pub.key);
  }

  TALER_EXCHANGE_blinding_prepare_cancel (wh->blinding_prepare_handle);
  TALER_EXCHANGE_withdraw_blinded_cancel (wh->withdraw_blinded_handle);
  wh->blinding_prepare_handle = NULL;
  wh->withdraw_blinded_handle = NULL;

  GNUNET_free (wh->coin_data);
  TALER_EXCHANGE_keys_decref (wh->keys);
  GNUNET_free (wh);
}


/**
 * @brief Prepare the handler for blinded withdraw
 *
 * Allocates the handler struct and prepares all fields of the handler
 * except the blinded planchets,
 * which depend on them being age-restricted or not.
 *
 * @param curl_ctx the context for curl
 * @param keys the exchange keys
 * @param exchange_url the url to the exchange
 * @param reserve_priv the reserve's private key
 * @param res_cb the callback on result
 * @param res_cb_cls the closure to pass on to the callback
 * @return the handler
 */
static struct TALER_EXCHANGE_WithdrawBlindedHandle *
setup_handler_common (
  struct GNUNET_CURL_Context *curl_ctx,
  struct TALER_EXCHANGE_Keys *keys,
  const char *exchange_url,
  const struct TALER_ReservePrivateKeyP *reserve_priv,
  TALER_EXCHANGE_WithdrawBlindedCallback res_cb,
  void *res_cb_cls)
{

  struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh =
    GNUNET_new (struct TALER_EXCHANGE_WithdrawBlindedHandle);

  wbh->keys = TALER_EXCHANGE_keys_incref (keys);
  wbh->curl_ctx = curl_ctx;
  wbh->reserve_priv = reserve_priv;
  wbh->callback = res_cb;
  wbh->callback_cls = res_cb_cls;
  wbh->request_url = TALER_url_join (exchange_url,
                                     "withdraw",
                                     NULL);
  GNUNET_CRYPTO_eddsa_key_get_public (
    &wbh->reserve_priv->eddsa_priv,
    &wbh->reserve_pub.eddsa_pub);

  return wbh;
}


struct TALER_EXCHANGE_WithdrawBlindedHandle *
TALER_EXCHANGE_withdraw_blinded (
  struct GNUNET_CURL_Context *curl_ctx,
  struct TALER_EXCHANGE_Keys *keys,
  const char *exchange_url,
  const struct TALER_ReservePrivateKeyP *reserve_priv,
  const struct TALER_BlindingMasterSeedP *blinding_seed,
  size_t num_input,
  const struct TALER_EXCHANGE_WithdrawBlindedCoinInput
  blinded_input[static num_input],
  TALER_EXCHANGE_WithdrawBlindedCallback res_cb,
  void *res_cb_cls)
{
  struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh =
    setup_handler_common (curl_ctx,
                          keys,
                          exchange_url,
                          reserve_priv,
                          res_cb,
                          res_cb_cls);

  wbh->with_age_proof = false;
  wbh->num_input = num_input;
  wbh->blinded.input = blinded_input;
  wbh->blinding_seed = blinding_seed;

  perform_withdraw_protocol (wbh);
  return wbh;
}


struct TALER_EXCHANGE_WithdrawBlindedHandle *
TALER_EXCHANGE_withdraw_blinded_with_age_proof (
  struct GNUNET_CURL_Context *curl_ctx,
  struct TALER_EXCHANGE_Keys *keys,
  const char *exchange_url,
  const struct TALER_ReservePrivateKeyP *reserve_priv,
  const struct TALER_BlindingMasterSeedP *blinding_seed,
  uint8_t max_age,
  unsigned int num_input,
  const struct TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput
  blinded_input[static num_input],
  TALER_EXCHANGE_WithdrawBlindedCallback res_cb,
  void *res_cb_cls)
{
  struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh =
    setup_handler_common (curl_ctx,
                          keys,
                          exchange_url,
                          reserve_priv,
                          res_cb,
                          res_cb_cls);

  wbh->with_age_proof = true;
  wbh->max_age = max_age;
  wbh->num_input = num_input;
  wbh->blinded.with_age_proof_input = blinded_input;
  wbh->blinding_seed = blinding_seed;

  perform_withdraw_protocol (wbh);
  return wbh;
}


void
TALER_EXCHANGE_withdraw_blinded_cancel (
  struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh)
{
  if (NULL == wbh)
    return;
  if (NULL != wbh->job)
  {
    GNUNET_CURL_job_cancel (wbh->job);
    wbh->job = NULL;
  }
  GNUNET_free (wbh->request_url);
  TALER_EXCHANGE_keys_decref (wbh->keys);
  TALER_curl_easy_post_finished (&wbh->post_ctx);
  GNUNET_free (wbh);
}


/* exchange_api_withdraw.c */
