/*
  This file is part of TALER
  Copyright (C) 2023-2026 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_post-withdraw.c
 * @brief Implementation of /withdraw requests
 * @author Özgür Kesim
 */
#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/taler_curl_lib.h"
#include "taler/taler_error_codes.h"
#include "taler/taler_json_lib.h"
#include "exchange_api_common.h"
#include "exchange_api_handle.h"
#include "taler/taler_signatures.h"
#include "taler/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;

};


/**
 * 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];
};


/**
 * Per-CS-coin data needed to complete the coin after /blinding-prepare.
 */
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 CoinData.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;

};


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

  /**
   * 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_PostWithdrawCallback callback;

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

  /**
   * The handler for the call to /blinding-prepare, needed for CS denominations.
   * NULL until _start is called for CS denominations, or when no CS denoms.
   */
  struct TALER_EXCHANGE_PostBlindingPrepareHandle *blinding_prepare_handle;

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

  /**
   * Number of CS denomination coin entries in @e bp_coins.
   * Zero if no CS denominations.
   */
  size_t num_bp_coins;

  /**
   * Array of @e num_bp_coins coin data for the blinding-prepare step.
   */
  struct BlindingPrepareCoinData *bp_coins;

  /**
   * Number of nonces in @e bp_nonces.
   */
  size_t num_bp_nonces;

  /**
   * Array of @e num_bp_nonces nonces for CS denominations.
   */
  union GNUNET_CRYPTO_BlindSessionNonce *bp_nonces;

  /**
   * Nonce keys for the blinding-prepare call.
   */
  struct TALER_EXCHANGE_NonceKey *bp_nonce_keys;

  /**
   * Number of nonce keys in @e bp_nonce_keys.
   */
  size_t num_bp_nonce_keys;

  /**
   * Array of @e init_num_coins denomination public keys.
   * NULL after _start is called.
   */
  struct TALER_EXCHANGE_DenomPublicKey *init_denoms_pub;

  /**
   * Number of coins provided in @e init_denoms_pub.
   */
  size_t init_num_coins;

  struct
  {

    /**
     * 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;

  } options;
};


/**
 * @brief Callback to copy the results from the call to post_withdraw_blinded
 * in the non-age-restricted case to the result for the originating call.
 *
 * @param cls struct TALER_EXCHANGE_PostWithdrawHandle
 * @param wbr The response
 */
static void
copy_results (
  void *cls,
  const struct TALER_EXCHANGE_PostWithdrawBlindedResponse *wbr)
{
  /* The original handle from the top-level call to withdraw */
  struct TALER_EXCHANGE_PostWithdrawHandle *wh = cls;
  struct TALER_EXCHANGE_PostWithdrawResponse 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_post_withdraw_cancel (wh);
}


/**
 * @brief Callback to copy the results from the call to post_withdraw_blinded
 * in the age-restricted case.
 *
 * @param cls struct TALER_EXCHANGE_PostWithdrawHandle
 * @param wbr The response
 */
static void
copy_results_with_age_proof (
  void *cls,
  const struct TALER_EXCHANGE_PostWithdrawBlindedResponse *wbr)
{
  /* The original handle from the top-level call to withdraw */
  struct TALER_EXCHANGE_PostWithdrawHandle *wh = cls;
  struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details[wh->num_coins];
  struct TALER_EXCHANGE_PostWithdrawResponse 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_CREATED:
    {
      uint8_t k =  wbr->details.created.noreveal_index;

      GNUNET_assert (k < TALER_CNC_KAPPA);
      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;
  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    resp.details.unavailable_for_legal_reasons =
      wbr->details.unavailable_for_legal_reasons;
    break;
  default:
    break;
  }

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


/**
 * @brief Prepares and starts the actual TALER_EXCHANGE_post_withdraw_blinded
 * operation once all blinding-prepare steps are done (or immediately if
 * there are no CS denominations).
 *
 * @param wh The withdraw handle
 * @return #TALER_EC_NONE on success, error code on failure
 */
static enum TALER_ErrorCode
call_withdraw_blinded (
  struct TALER_EXCHANGE_PostWithdrawHandle *wh)
{
  enum TALER_ErrorCode ec;

  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_post_withdraw_blinded_create (
        wh->curl_ctx,
        wh->keys,
        wh->exchange_url,
        wh->reserve_priv,
        wh->has_blinding_seed ? &wh->blinding_seed : NULL,
        wh->num_coins,
        input);
    if (NULL == wh->withdraw_blinded_handle)
      return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    ec = TALER_EXCHANGE_post_withdraw_blinded_start (
      wh->withdraw_blinded_handle,
      &copy_results,
      wh);
    if (TALER_EC_NONE != ec)
    {
      TALER_EXCHANGE_post_withdraw_blinded_cancel (
        wh->withdraw_blinded_handle);
      wh->withdraw_blinded_handle = NULL;
      return ec;
    }
  }
  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_post_withdraw_blinded_create (
        wh->curl_ctx,
        wh->keys,
        wh->exchange_url,
        wh->reserve_priv,
        wh->has_blinding_seed ? &wh->blinding_seed : NULL,
        wh->num_coins,
        NULL);
    if (NULL == wh->withdraw_blinded_handle)
      return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    TALER_EXCHANGE_post_withdraw_blinded_set_options (
      wh->withdraw_blinded_handle,
      TALER_EXCHANGE_post_withdraw_blinded_option_with_age_proof (
        wh->max_age,
        ari));
    ec = TALER_EXCHANGE_post_withdraw_blinded_start (
      wh->withdraw_blinded_handle,
      &copy_results_with_age_proof,
      wh);
    if (TALER_EC_NONE != ec)
    {
      TALER_EXCHANGE_post_withdraw_blinded_cancel (wh->withdraw_blinded_handle);
      wh->withdraw_blinded_handle = NULL;
      return ec;
    }
  }
  return TALER_EC_NONE;
}


/**
 * @brief Function called when /blinding-prepare is finished.
 *
 * @param cls the `struct TALER_EXCHANGE_PostWithdrawHandle *`
 * @param bpr replies from the /blinding-prepare request
 */
static void
blinding_prepare_done (
  void *cls,
  const struct TALER_EXCHANGE_PostBlindingPrepareResponse *bpr)
{
  struct TALER_EXCHANGE_PostWithdrawHandle *wh = cls;

  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 == wh->num_bp_nonces);
      for (size_t i = 0; i < wh->num_bp_coins; i++)
      {
        struct TALER_PlanchetDetail *planchet = wh->bp_coins[i].planchet;
        struct CoinCandidate *can = wh->bp_coins[i].candidate;
        size_t cs_idx = wh->bp_coins[i].cs_idx;

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

        /* Complete the initialization of the coin with CS denomination */
        GNUNET_assert (cs_idx < bpr->details.ok.num_blinding_values);
        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 (
              wh->bp_coins[i].denom_pub,
              &can->details.blinding_values,
              &can->details.blinding_key,
              &wh->bp_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)
      {
        enum TALER_ErrorCode ec = call_withdraw_blinded (wh);

        if (TALER_EC_NONE != ec)
        {
          struct TALER_EXCHANGE_PostWithdrawResponse resp = {
            .hr.ec = ec,
            .hr.http_status = 0,
          };

          wh->callback (
            wh->callback_cls,
            &resp);
          wh->callback = NULL;
          TALER_EXCHANGE_post_withdraw_cancel (wh);
        }
        return;
      }
      else
      {
        /* prepare completed but coin setup failed */
        struct TALER_EXCHANGE_PostWithdrawResponse resp = {
          .hr.ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
          .hr.http_status = 0,
        };

        wh->callback (
          wh->callback_cls,
          &resp);
        wh->callback = NULL;
        TALER_EXCHANGE_post_withdraw_cancel (wh);
        return;
      }
    }
  default:
    {
      /* We got an error condition during blinding prepare that we need to report */
      struct TALER_EXCHANGE_PostWithdrawResponse resp = {
        .hr = bpr->hr
      };

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


/**
 * @brief Prepares coins for the call to withdraw:
 * Performs synchronous crypto for RSA denominations, and stores
 * the data needed for the async /blinding-prepare step for CS denominations.
 * Does NOT start any async operations.
 *
 * @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
 * @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_PostWithdrawHandle *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;
  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));
      wh->num_bp_coins = cs_num * kappa;
      GNUNET_assert ((1 == kappa) || (cs_num * kappa > cs_num));
      wh->bp_coins =
        GNUNET_new_array (wh->num_bp_coins,
                          struct BlindingPrepareCoinData);
      wh->num_bp_nonces = cs_num;
      wh->bp_nonces =
        GNUNET_new_array (wh->num_bp_nonces,
                          union GNUNET_CRYPTO_BlindSessionNonce);
      wh->num_bp_nonce_keys = cs_num;
      wh->bp_nonce_keys =
        GNUNET_new_array (wh->num_bp_nonce_keys,
                          struct TALER_EXCHANGE_NonceKey);
    }

    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;
        wh->bp_nonce_keys[cs_denom_idx].cnc_num = i;
        wh->bp_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 this is regardless of whether
         * 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 */
            wh->bp_coins[cs_coin_idx].candidate = can;
            wh->bp_coins[cs_coin_idx].planchet = planchet;
            wh->bp_coins[cs_coin_idx].denom_pub = &cd->denom_pub.key;
            wh->bp_coins[cs_coin_idx].cs_idx = cs_denom_idx - 1;
            wh->bp_coins[cs_coin_idx].age_denom = age_denom;
            cs_coin_idx++;
            break;
          }
        default:
          FAIL_IF (1);
        }
      } /* for k in [0..KAPPA) */
    } /* for i in [0 .. wh->num_coins) */

    if (0 < cs_num)
    {
      if (wh->options.has_blinding_seed)
      {
        wh->blinding_seed = wh->options.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,
        wh->bp_nonces);
    }
  }
  return GNUNET_OK;

ERROR:
  if (0 < cs_num)
  {
    GNUNET_free (wh->bp_nonces);
    GNUNET_free (wh->bp_coins);
    GNUNET_free (wh->bp_nonce_keys);
    wh->num_bp_coins = 0;
    wh->num_bp_nonces = 0;
    wh->num_bp_nonce_keys = 0;
  }
  return GNUNET_SYSERR;
#undef FAIL_IF
}


struct TALER_EXCHANGE_PostWithdrawHandle *
TALER_EXCHANGE_post_withdraw_create (
  struct GNUNET_CURL_Context *curl_ctx,
  const char *exchange_url,
  struct TALER_EXCHANGE_Keys *keys,
  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)
{
  struct TALER_EXCHANGE_PostWithdrawHandle *wh;

  wh = GNUNET_new (struct TALER_EXCHANGE_PostWithdrawHandle);
  wh->exchange_url = exchange_url;
  wh->keys = TALER_EXCHANGE_keys_incref (keys);
  wh->curl_ctx = curl_ctx;
  wh->reserve_priv = reserve_priv;
  wh->seed = *seed;
  wh->max_age = opaque_max_age;
  wh->init_num_coins = num_coins;
  wh->init_denoms_pub = GNUNET_new_array (num_coins,
                                          struct TALER_EXCHANGE_DenomPublicKey);
  for (size_t i = 0; i < num_coins; i++)
  {
    wh->init_denoms_pub[i] = denoms_pub[i];
    TALER_denom_pub_copy (&wh->init_denoms_pub[i].key,
                          &denoms_pub[i].key);
  }

  return wh;
}


enum GNUNET_GenericReturnValue
TALER_EXCHANGE_post_withdraw_set_options_ (
  struct TALER_EXCHANGE_PostWithdrawHandle *pwh,
  unsigned int num_options,
  const struct TALER_EXCHANGE_PostWithdrawOptionValue options[])
{
  for (unsigned int i = 0; i < num_options; i++)
  {
    const struct TALER_EXCHANGE_PostWithdrawOptionValue *opt = &options[i];
    switch (opt->option)
    {
    case TALER_EXCHANGE_POST_WITHDRAW_OPTION_END:
      return GNUNET_OK;
    case TALER_EXCHANGE_POST_WITHDRAW_OPTION_WITH_AGE_PROOF:
      pwh->with_age_proof = true;
      pwh->max_age = opt->details.max_age;
      break;
    case TALER_EXCHANGE_POST_WITHDRAW_OPTION_BLINDING_SEED:
      pwh->options.has_blinding_seed = true;
      pwh->options.blinding_seed = opt->details.blinding_seed;
      break;
    }
  }
  return GNUNET_OK;
}


enum TALER_ErrorCode
TALER_EXCHANGE_post_withdraw_start (
  struct TALER_EXCHANGE_PostWithdrawHandle *pwh,
  TALER_EXCHANGE_PostWithdrawCallback cb,
  TALER_EXCHANGE_POST_WITHDRAW_RESULT_CLOSURE *cb_cls)
{
  pwh->callback = cb;
  pwh->callback_cls = cb_cls;

  /* Run prepare_coins now that options have been applied */
  if (GNUNET_OK !=
      prepare_coins (pwh,
                     pwh->init_num_coins,
                     pwh->max_age,
                     pwh->init_denoms_pub,
                     &pwh->seed,
                     pwh->has_blinding_seed
                     ? &pwh->blinding_seed
                     : NULL))
  {
    GNUNET_break (0);
    return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
  }
  /* Free init data - no longer needed after prepare_coins */
  for (size_t i = 0; i < pwh->init_num_coins; i++)
    TALER_denom_pub_free (&pwh->init_denoms_pub[i].key);
  GNUNET_free (pwh->init_denoms_pub);

  if (0 < pwh->num_bp_coins)
  {
    /* There are CS denominations; start the blinding-prepare request */
    pwh->blinding_prepare_handle =
      TALER_EXCHANGE_post_blinding_prepare_for_withdraw_create (
        pwh->curl_ctx,
        pwh->exchange_url,
        &pwh->blinding_seed,
        pwh->num_bp_nonce_keys,
        pwh->bp_nonce_keys);
    if (NULL == pwh->blinding_prepare_handle)
    {
      GNUNET_break (0);
      return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    }
    {
      enum TALER_ErrorCode ec =
        TALER_EXCHANGE_post_blinding_prepare_start (
          pwh->blinding_prepare_handle,
          &blinding_prepare_done,
          pwh);
      if (TALER_EC_NONE != ec)
      {
        TALER_EXCHANGE_post_blinding_prepare_cancel (
          pwh->blinding_prepare_handle);
        pwh->blinding_prepare_handle = NULL;
        return ec;
      }
    }
    return TALER_EC_NONE;
  }

  /* No CS denominations; proceed directly to the withdraw protocol */
  return call_withdraw_blinded (pwh);
}


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

  /* Cleanup init data if _start was never called (or failed) */
  if (NULL != wh->init_denoms_pub)
  {
    for (size_t i = 0; i < wh->init_num_coins; i++)
      TALER_denom_pub_free (&wh->init_denoms_pub[i].key);
    GNUNET_free (wh->init_denoms_pub);
  }
  /* Cleanup coin data */
  if (NULL != wh->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);
    }
    GNUNET_free (wh->coin_data);
  }

  TALER_EXCHANGE_post_blinding_prepare_cancel (wh->blinding_prepare_handle);
  wh->blinding_prepare_handle = NULL;
  TALER_EXCHANGE_post_withdraw_blinded_cancel (wh->withdraw_blinded_handle);
  wh->withdraw_blinded_handle = NULL;

  GNUNET_free (wh->bp_coins);
  GNUNET_free (wh->bp_nonces);
  GNUNET_free (wh->bp_nonce_keys);
  TALER_EXCHANGE_keys_decref (wh->keys);
  GNUNET_free (wh);
}


/* exchange_api_post-withdraw.c */
