/*
  This file is part of TALER
  Copyright (C) 2014-2019 Taler Systems SA
  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU Affero 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 Affero General Public License for more details.
  You should have received a copy of the GNU Affero General Public License along with
  TALER; see the file COPYING.  If not, see 
*/
/**
 * @file taler-exchange-httpd_refresh_reveal.c
 * @brief Handle /refresh/reveal requests
 * @author Florian Dold
 * @author Benedikt Mueller
 * @author Christian Grothoff
 */
#include "platform.h"
#include 
#include 
#include 
#include "taler_mhd_lib.h"
#include "taler-exchange-httpd_mhd.h"
#include "taler-exchange-httpd_refresh_reveal.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_keystate.h"
/**
 * Maximum number of fresh coins we allow per refresh operation.
 */
#define MAX_FRESH_COINS 256
/**
 * How often do we at most retry the reveal transaction sequence?
 * Twice should really suffice in all cases (as the possible conflict
 * cannot happen more than once).
 */
#define MAX_REVEAL_RETRIES 2
/**
 * Send a response for "/refresh/reveal".
 *
 * @param connection the connection to send the response to
 * @param num_newcoins number of new coins for which we reveal data
 * @param sigs array of @a num_newcoins signatures revealed
 * @return a MHD result code
 */
static int
reply_refresh_reveal_success (struct MHD_Connection *connection,
                              unsigned int num_newcoins,
                              const struct TALER_DenominationSignature *sigs)
{
  json_t *list;
  int ret;
  list = json_array ();
  for (unsigned int newcoin_index = 0;
       newcoin_index < num_newcoins;
       newcoin_index++)
  {
    json_t *obj;
    obj = json_object ();
    json_object_set_new (obj,
                         "ev_sig",
                         GNUNET_JSON_from_rsa_signature (
                           sigs[newcoin_index].rsa_signature));
    GNUNET_assert (0 ==
                   json_array_append_new (list,
                                          obj));
  }
  {
    json_t *root;
    root = json_object ();
    json_object_set_new (root,
                         "ev_sigs",
                         list);
    ret = TALER_MHD_reply_json (connection,
                                root,
                                MHD_HTTP_OK);
    json_decref (root);
  }
  return ret;
}
/**
 * Send a response for a failed "/refresh/reveal", where the
 * revealed value(s) do not match the original commitment.
 *
 * @param connection the connection to send the response to
 * @param rc commitment computed by the exchange
 * @return a MHD result code
 */
static int
reply_refresh_reveal_mismatch (struct MHD_Connection *connection,
                               const struct TALER_RefreshCommitmentP *rc)
{
  return TALER_MHD_reply_json_pack (connection,
                                    MHD_HTTP_CONFLICT,
                                    "{s:s, s:I, s:o}",
                                    "hint", "commitment violation",
                                    "code",
                                    (json_int_t)
                                    TALER_EC_REFRESH_REVEAL_COMMITMENT_VIOLATION,
                                    "rc_expected",
                                    GNUNET_JSON_from_data_auto (rc));
}
/**
 * State for a /refresh/reveal operation.
 */
struct RevealContext
{
  /**
   * Commitment of the refresh operaton.
   */
  struct TALER_RefreshCommitmentP rc;
  /**
   * Transfer public key at gamma.
   */
  struct TALER_TransferPublicKeyP gamma_tp;
  /**
   * Transfer private keys revealed to us.
   */
  struct TALER_TransferPrivateKeyP transfer_privs[TALER_CNC_KAPPA - 1];
  /**
   * Denominations being requested.
   */
  const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation **dkis;
  /**
   * Envelopes to be signed.
   */
  const struct TALER_RefreshCoinData *rcds;
  /**
   * Signatures over the link data (of type
   * #TALER_SIGNATURE_WALLET_COIN_LINK)
   */
  const struct TALER_CoinSpendSignatureP *link_sigs;
  /**
   * Envelopes with the signatures to be returned.  Initially NULL.
   */
  struct TALER_DenominationSignature *ev_sigs;
  /**
   * Size of the @e dkis, @e rcds and @e ev_sigs arrays (if non-NULL).
   */
  unsigned int num_fresh_coins;
  /**
   * Result from preflight checks. #GNUNET_NO for no result,
   * #GNUNET_YES if preflight found previous successful operation,
   * #GNUNET_SYSERR if prefight check failed hard (and generated
   * an MHD response already).
   */
  int preflight_ok;
};
/**
 * Function called with information about a refresh order we already
 * persisted.  Stores the result in @a cls so we don't do the calculation
 * again.
 *
 * @param cls closure with a `struct RevealContext`
 * @param num_newcoins size of the @a rrcs array
 * @param rrcs array of @a num_newcoins information about coins to be created
 * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1
 * @param tprivs array of @e num_tprivs transfer private keys
 * @param tp transfer public key information
 */
static void
check_exists_cb (void *cls,
                 uint32_t num_newcoins,
                 const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
                 unsigned int num_tprivs,
                 const struct TALER_TransferPrivateKeyP *tprivs,
                 const struct TALER_TransferPublicKeyP *tp)
{
  struct RevealContext *rctx = cls;
  if (0 == num_newcoins)
  {
    GNUNET_break (0);
    return;
  }
  GNUNET_break (TALER_CNC_KAPPA - 1 == num_tprivs);
  GNUNET_break_op (0 ==
                   GNUNET_memcmp (tp,
                                  &rctx->gamma_tp));
  GNUNET_break_op (0 ==
                   memcmp (tprivs,
                           &rctx->transfer_privs,
                           sizeof (struct TALER_TransferPrivateKeyP)
                           * num_tprivs));
  /* We usually sign early (optimistic!), but in case we change that *and*
     we do find the operation in the database, we could use this: */
  if (NULL == rctx->ev_sigs)
  {
    rctx->ev_sigs = GNUNET_new_array (num_newcoins,
                                      struct TALER_DenominationSignature);
    for (unsigned int i = 0; iev_sigs[i].rsa_signature
        = GNUNET_CRYPTO_rsa_signature_dup (rrcs[i].coin_sig.rsa_signature);
  }
}
/**
 * Check if the "/refresh/reveal" was already successful before.
 * If so, just return the old result.
 *
 * @param cls closure of type `struct RevealContext`
 * @param connection MHD request which triggered the transaction
 * @param session database session to use
 * @param[out] mhd_ret set to MHD response status for @a connection,
 *             if transaction failed (!)
 * @return transaction status
 */
static enum GNUNET_DB_QueryStatus
refresh_reveal_preflight (void *cls,
                          struct MHD_Connection *connection,
                          struct TALER_EXCHANGEDB_Session *session,
                          int *mhd_ret)
{
  struct RevealContext *rctx = cls;
  enum GNUNET_DB_QueryStatus qs;
  /* Try to see if we already have given an answer before. */
  qs = TEH_plugin->get_refresh_reveal (TEH_plugin->cls,
                                       session,
                                       &rctx->rc,
                                       &check_exists_cb,
                                       rctx);
  switch (qs)
  {
  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    return qs; /* continue normal execution */
  case GNUNET_DB_STATUS_SOFT_ERROR:
    return qs;
  case GNUNET_DB_STATUS_HARD_ERROR:
    GNUNET_break (qs);
    *mhd_ret = TALER_MHD_reply_with_error (connection,
                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
                                           TALER_EC_REFRESH_REVEAL_DB_FETCH_REVEAL_ERROR,
                                           "failed to fetch reveal data");
    rctx->preflight_ok = GNUNET_SYSERR;
    return GNUNET_DB_STATUS_HARD_ERROR;
  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
  default:
    /* Hossa, already found our reply! */
    GNUNET_assert (NULL != rctx->ev_sigs);
    rctx->preflight_ok = GNUNET_YES;
    return qs;
  }
}
/**
 * Execute a "/refresh/reveal".  The client is revealing to us the
 * transfer keys for @a #TALER_CNC_KAPPA-1 sets of coins.  Verify that the
 * revealed transfer keys would allow linkage to the blinded coins.
 *
 * IF it returns a non-error code, the transaction logic MUST
 * NOT queue a MHD response.  IF it returns an hard error, the
 * transaction logic MUST queue a MHD response and set @a mhd_ret.  IF
 * it returns the soft error code, the function MAY be called again to
 * retry and MUST not queue a MHD response.
 *
 * @param cls closure of type `struct RevealContext`
 * @param connection MHD request which triggered the transaction
 * @param session database session to use
 * @param[out] mhd_ret set to MHD response status for @a connection,
 *             if transaction failed (!)
 * @return transaction status
 */
static enum GNUNET_DB_QueryStatus
refresh_reveal_transaction (void *cls,
                            struct MHD_Connection *connection,
                            struct TALER_EXCHANGEDB_Session *session,
                            int *mhd_ret)
{
  struct RevealContext *rctx = cls;
  struct TALER_EXCHANGEDB_RefreshMelt refresh_melt;
  enum GNUNET_DB_QueryStatus qs;
  /* Obtain basic information about the refresh operation and what
     gamma we committed to. */
  qs = TEH_plugin->get_melt (TEH_plugin->cls,
                             session,
                             &rctx->rc,
                             &refresh_melt);
  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
  {
    *mhd_ret = TALER_MHD_reply_with_error (connection,
                                           MHD_HTTP_NOT_FOUND,
                                           TALER_EC_REFRESH_REVEAL_SESSION_UNKNOWN,
                                           "rc");
    return GNUNET_DB_STATUS_HARD_ERROR;
  }
  if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    return qs;
  if ( (GNUNET_DB_STATUS_HARD_ERROR == qs) ||
       (refresh_melt.session.noreveal_index >= TALER_CNC_KAPPA) )
  {
    GNUNET_break (0);
    *mhd_ret = TALER_MHD_reply_with_error (connection,
                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
                                           TALER_EC_REFRESH_REVEAL_DB_FETCH_SESSION_ERROR,
                                           "failed to fetch valid challenge from database");
    return GNUNET_DB_STATUS_HARD_ERROR;
  }
  /* Verify commitment */
  {
    /* Note that the contents of rcs[refresh_melt.session.noreveal_index]
       will be aliased and are *not* allocated (or deallocated) in
       this function -- in contrast to the other offsets! */
    struct TALER_RefreshCommitmentEntry rcs[TALER_CNC_KAPPA];
    struct TALER_RefreshCommitmentP rc_expected;
    unsigned int off;
    off = 0; /* did we pass session.noreveal_index yet? */
    for (unsigned int i = 0; itransfer_pub = rctx->gamma_tp;
        rce->new_coins = (struct TALER_RefreshCoinData *) rctx->rcds;
        off = 1;
      }
      else
      {
        /* Reconstruct coin envelopes from transfer private key */
        struct TALER_TransferPrivateKeyP *tpriv = &rctx->transfer_privs[i
                                                                        - off];
        struct TALER_TransferSecretP ts;
        GNUNET_CRYPTO_ecdhe_key_get_public (&tpriv->ecdhe_priv,
                                            &rce->transfer_pub.ecdhe_pub);
        TALER_link_reveal_transfer_secret (tpriv,
                                           &refresh_melt.session.coin.coin_pub,
                                           &ts);
        rce->new_coins = GNUNET_new_array (rctx->num_fresh_coins,
                                           struct TALER_RefreshCoinData);
        for (unsigned int j = 0; jnum_fresh_coins; j++)
        {
          struct TALER_RefreshCoinData *rcd = &rce->new_coins[j];
          struct TALER_PlanchetSecretsP ps;
          struct TALER_PlanchetDetail pd;
          rcd->dk = &rctx->dkis[j]->denom_pub;
          TALER_planchet_setup_refresh (&ts,
                                        j,
                                        &ps);
          GNUNET_assert (GNUNET_OK ==
                         TALER_planchet_prepare (rcd->dk,
                                                 &ps,
                                                 &pd));
          rcd->coin_ev = pd.coin_ev;
          rcd->coin_ev_size = pd.coin_ev_size;
        }
      }
    }
    TALER_refresh_get_commitment (&rc_expected,
                                  TALER_CNC_KAPPA,
                                  rctx->num_fresh_coins,
                                  rcs,
                                  &refresh_melt.session.coin.coin_pub,
                                  &refresh_melt.session.amount_with_fee);
    /* Free resources allocated above */
    for (unsigned int i = 0; inum_fresh_coins; j++)
      {
        struct TALER_RefreshCoinData *rcd = &rce->new_coins[j];
        GNUNET_free (rcd->coin_ev);
      }
      GNUNET_free (rce->new_coins);
    }
    /* Verify rc_expected matches rc */
    if (0 != GNUNET_memcmp (&rctx->rc,
                            &rc_expected))
    {
      GNUNET_break_op (0);
      *mhd_ret = reply_refresh_reveal_mismatch (connection,
                                                &rc_expected);
      return GNUNET_DB_STATUS_HARD_ERROR;
    }
  } /* end of checking "rc_expected" */
  /* check amounts add up! */
  {
    struct TALER_Amount refresh_cost;
    refresh_cost = refresh_melt.melt_fee;
    for (unsigned int i = 0; inum_fresh_coins; i++)
    {
      struct TALER_Amount fee_withdraw;
      struct TALER_Amount value;
      struct TALER_Amount total;
      TALER_amount_ntoh (&fee_withdraw,
                         &rctx->dkis[i]->issue.properties.fee_withdraw);
      TALER_amount_ntoh (&value,
                         &rctx->dkis[i]->issue.properties.value);
      if ( (GNUNET_OK !=
            TALER_amount_add (&total,
                              &fee_withdraw,
                              &value)) ||
           (GNUNET_OK !=
            TALER_amount_add (&refresh_cost,
                              &refresh_cost,
                              &total)) )
      {
        GNUNET_break_op (0);
        *mhd_ret = TALER_MHD_reply_with_error (connection,
                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
                                               TALER_EC_REFRESH_REVEAL_COST_CALCULATION_OVERFLOW,
                                               "failed to add up refresh costs");
        return GNUNET_DB_STATUS_HARD_ERROR;
      }
    }
    if (0 < TALER_amount_cmp (&refresh_cost,
                              &refresh_melt.session.amount_with_fee))
    {
      GNUNET_break_op (0);
      *mhd_ret = TALER_MHD_reply_with_error (connection,
                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
                                             TALER_EC_REFRESH_REVEAL_AMOUNT_INSUFFICIENT,
                                             "melted coin value is insufficient to cover cost of operation");
      return GNUNET_DB_STATUS_HARD_ERROR;
    }
  }
  return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
}
/**
 * Persist result of a "/refresh/reveal".
 *
 * @param cls closure of type `struct RevealContext`
 * @param connection MHD request which triggered the transaction
 * @param session database session to use
 * @param[out] mhd_ret set to MHD response status for @a connection,
 *             if transaction failed (!)
 * @return transaction status
 */
static enum GNUNET_DB_QueryStatus
refresh_reveal_persist (void *cls,
                        struct MHD_Connection *connection,
                        struct TALER_EXCHANGEDB_Session *session,
                        int *mhd_ret)
{
  struct RevealContext *rctx = cls;
  enum GNUNET_DB_QueryStatus qs;
  /* Persist operation result in DB */
  {
    struct TALER_EXCHANGEDB_RefreshRevealedCoin rrcs[rctx->num_fresh_coins];
    for (unsigned int i = 0; inum_fresh_coins; i++)
    {
      struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
      rrc->denom_pub = rctx->dkis[i]->denom_pub;
      rrc->orig_coin_link_sig = rctx->link_sigs[i];
      rrc->coin_ev = rctx->rcds[i].coin_ev;
      rrc->coin_ev_size = rctx->rcds[i].coin_ev_size;
      rrc->coin_sig = rctx->ev_sigs[i];
    }
    qs = TEH_plugin->insert_refresh_reveal (TEH_plugin->cls,
                                            session,
                                            &rctx->rc,
                                            rctx->num_fresh_coins,
                                            rrcs,
                                            TALER_CNC_KAPPA - 1,
                                            rctx->transfer_privs,
                                            &rctx->gamma_tp);
  }
  if (GNUNET_DB_STATUS_HARD_ERROR == qs)
  {
    *mhd_ret = TALER_MHD_reply_with_error (connection,
                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
                                           TALER_EC_REFRESH_REVEAL_DB_COMMIT_ERROR,
                                           "failed to persist reveal data");
  }
  return qs;
}
/**
 * Resolve denomination hashes using the @a key_state
 *
 * @param key_state the key state
 * @param connection the MHD connection to handle
 * @param rctx context for the operation, partially built at this time
 * @param link_sigs_json link signatures in JSON format
 * @param new_denoms_h_json requests for fresh coins to be created
 * @param coin_evs envelopes of gamma-selected coins to be signed
 * @return MHD result code
 */
static int
resolve_refresh_reveal_denominations (struct TEH_KS_StateHandle *key_state,
                                      struct MHD_Connection *connection,
                                      struct RevealContext *rctx,
                                      const json_t *link_sigs_json,
                                      const json_t *new_denoms_h_json,
                                      const json_t *coin_evs)
{
  unsigned int num_fresh_coins = json_array_size (new_denoms_h_json);
  const struct
  TALER_EXCHANGEDB_DenominationKeyIssueInformation *dkis[num_fresh_coins];
  struct GNUNET_HashCode dki_h[num_fresh_coins];
  struct TALER_RefreshCoinData rcds[num_fresh_coins];
  struct TALER_CoinSpendSignatureP link_sigs[num_fresh_coins];
  struct TALER_EXCHANGEDB_RefreshMelt refresh_melt;
  int res;
  /* Parse denomination key hashes */
  for (unsigned int i = 0; idenom_priv.rsa_private_key);
  }
  /* Parse coin envelopes */
  for (unsigned int i = 0; icoin_ev,
                                &rcd->coin_ev_size),
      GNUNET_JSON_spec_end ()
    };
    res = TALER_MHD_parse_json_array (connection,
                                      coin_evs,
                                      spec,
                                      i,
                                      -1);
    if (GNUNET_OK != res)
    {
      for (unsigned int j = 0; jdk = &dkis[i]->denom_pub;
  }
  /* lookup old_coin_pub in database */
  {
    enum GNUNET_DB_QueryStatus qs;
    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
        (qs = TEH_plugin->get_melt (TEH_plugin->cls,
                                    NULL,
                                    &rctx->rc,
                                    &refresh_melt)))
    {
      switch (qs)
      {
      case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
        res = TALER_MHD_reply_with_error (connection,
                                          MHD_HTTP_NOT_FOUND,
                                          TALER_EC_REFRESH_REVEAL_SESSION_UNKNOWN,
                                          "rc");
        break;
      case GNUNET_DB_STATUS_HARD_ERROR:
        res = TALER_MHD_reply_with_error (connection,
                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
                                          TALER_EC_REFRESH_REVEAL_DB_FETCH_SESSION_ERROR,
                                          "failed to fetch session data");
        break;
      case GNUNET_DB_STATUS_SOFT_ERROR:
      default:
        GNUNET_break (0);   /* should be impossible */
        res = TALER_MHD_reply_with_error (connection,
                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
                                          TALER_EC_INTERNAL_INVARIANT_FAILURE,
                                          "assertion failed");
        break;
      }
      goto cleanup;
    }
  }
  /* Parse link signatures array */
  for (unsigned int i = 0; igamma_tp;
      GNUNET_CRYPTO_hash (rcds[i].coin_ev,
                          rcds[i].coin_ev_size,
                          &ldp.coin_envelope_hash);
      if (GNUNET_OK !=
          GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK,
                                      &ldp.purpose,
                                      &link_sigs[i].eddsa_signature,
                                      &refresh_melt.session.coin.coin_pub.
                                      eddsa_pub))
      {
        GNUNET_break_op (0);
        res = TALER_MHD_reply_with_error (connection,
                                          MHD_HTTP_FORBIDDEN,
                                          TALER_EC_REFRESH_REVEAL_LINK_SIGNATURE_INVALID,
                                          "link_sig");
        goto cleanup;
      }
    }
  }
  rctx->num_fresh_coins = num_fresh_coins;
  rctx->rcds = rcds;
  rctx->dkis = dkis;
  rctx->link_sigs = link_sigs;
  /* sign _early_ (optimistic!) to keep out of transaction scope! */
  rctx->ev_sigs = GNUNET_new_array (rctx->num_fresh_coins,
                                    struct TALER_DenominationSignature);
  for (unsigned int i = 0; inum_fresh_coins; i++)
  {
    rctx->ev_sigs[i].rsa_signature
      = GNUNET_CRYPTO_rsa_sign_blinded (
      rctx->dkis[i]->denom_priv.rsa_private_key,
      rctx->rcds[i].coin_ev,
      rctx->rcds[i].coin_ev_size);
    if (NULL == rctx->ev_sigs[i].rsa_signature)
    {
      GNUNET_break (0);
      res = TALER_MHD_reply_with_error (connection,
                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                                        TALER_EC_REFRESH_REVEAL_SIGNING_ERROR,
                                        "internal signing error");
      goto cleanup;
    }
  }
  /* We try the three transactions a few times, as theoretically
     the pre-check might be satisfied by a concurrent transaction
     voiding our final commit due to uniqueness violation; naturally,
     on hard errors we exit immediately */
  for (unsigned int retries = 0; retries < MAX_REVEAL_RETRIES; retries++)
  {
    /* do transactional work */
    rctx->preflight_ok = GNUNET_NO;
    if ( (GNUNET_OK ==
          TEH_DB_run_transaction (connection,
                                  "reveal pre-check",
                                  &res,
                                  &refresh_reveal_preflight,
                                  rctx)) &&
         (GNUNET_YES == rctx->preflight_ok) )
    {
      /* Generate final (positive) response */
      GNUNET_assert (NULL != rctx->ev_sigs);
      res = reply_refresh_reveal_success (connection,
                                          num_fresh_coins,
                                          rctx->ev_sigs);
      GNUNET_break (MHD_NO != res);
      goto cleanup;   /* aka 'break' */
    }
    if (GNUNET_SYSERR == rctx->preflight_ok)
    {
      GNUNET_break (0);
      goto cleanup;   /* aka 'break' */
    }
    if (GNUNET_OK !=
        TEH_DB_run_transaction (connection,
                                "run reveal",
                                &res,
                                &refresh_reveal_transaction,
                                rctx))
    {
      /* reveal failed, too bad */
      GNUNET_break_op (0);
      goto cleanup;   /* aka 'break' */
    }
    if (GNUNET_OK ==
        TEH_DB_run_transaction (connection,
                                "persist reveal",
                                &res,
                                &refresh_reveal_persist,
                                rctx))
    {
      /* Generate final (positive) response */
      GNUNET_assert (NULL != rctx->ev_sigs);
      res = reply_refresh_reveal_success (connection,
                                          num_fresh_coins,
                                          rctx->ev_sigs);
      break;
    }
  }   /* end for (retries...) */
cleanup:
  GNUNET_break (MHD_NO != res);
  /* free resources */
  if (NULL != rctx->ev_sigs)
  {
    for (unsigned int i = 0; iev_sigs[i].rsa_signature)
        GNUNET_CRYPTO_rsa_signature_free (rctx->ev_sigs[i].rsa_signature);
    GNUNET_free (rctx->ev_sigs);
    rctx->ev_sigs = NULL; /* just to be safe... */
  }
  for (unsigned int i = 0; i= MAX_FRESH_COINS) ||
       (0 == num_fresh_coins) )
  {
    GNUNET_break_op (0);
    return TALER_MHD_reply_with_error (connection,
                                       MHD_HTTP_BAD_REQUEST,
                                       TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE,
                                       "new_denoms_h");
  }
  if (json_array_size (new_denoms_h_json) !=
      json_array_size (coin_evs))
  {
    GNUNET_break_op (0);
    return TALER_MHD_reply_with_error (connection,
                                       MHD_HTTP_BAD_REQUEST,
                                       TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISSMATCH,
                                       "new_denoms/coin_evs");
  }
  if (json_array_size (new_denoms_h_json) !=
      json_array_size (link_sigs_json))
  {
    GNUNET_break_op (0);
    return TALER_MHD_reply_with_error (connection,
                                       MHD_HTTP_BAD_REQUEST,
                                       TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISSMATCH,
                                       "new_denoms/link_sigs");
  }
  /* Parse transfer private keys array */
  for (unsigned int i = 0; itransfer_privs[i]),
      GNUNET_JSON_spec_end ()
    };
    int res;
    res = TALER_MHD_parse_json_array (connection,
                                      tp_json,
                                      trans_spec,
                                      i,
                                      -1);
    if (GNUNET_OK != res)
      return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
  }
  {
    struct TEH_KS_StateHandle *key_state;
    int ret;
    key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ());
    if (NULL == key_state)
    {
      TALER_LOG_ERROR ("Lacking keys to operate\n");
      return TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
                                         TALER_EC_REFRESH_REVEAL_KEYS_MISSING,
                                         "exchange lacks keys");
    }
    ret = resolve_refresh_reveal_denominations (key_state,
                                                connection,
                                                rctx,
                                                link_sigs_json,
                                                new_denoms_h_json,
                                                coin_evs);
    TEH_KS_release (key_state);
    return ret;
  }
}
/**
 * Handle a "/refresh/reveal" request. This time, the client reveals the
 * private transfer keys except for the cut-and-choose value returned from
 * "/refresh/melt".  This function parses the revealed keys and secrets and
 * ultimately passes everything to #resolve_refresh_reveal_denominations()
 * which will verify that the revealed information is valid then runs the
 * transaction in #refresh_reveal_transaction() and finally returns the signed
 * refreshed coins.
 *
 * @param rh context of the handler
 * @param connection the MHD connection to handle
 * @param[in,out] connection_cls the connection's closure (can be updated)
 * @param upload_data upload data
 * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
 * @return MHD result code
  */
int
TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh,
                                    struct MHD_Connection *connection,
                                    void **connection_cls,
                                    const char *upload_data,
                                    size_t *upload_data_size)
{
  int res;
  json_t *root;
  json_t *coin_evs;
  json_t *transfer_privs;
  json_t *link_sigs;
  json_t *new_denoms_h;
  struct RevealContext rctx;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_fixed_auto ("rc", &rctx.rc),
    GNUNET_JSON_spec_fixed_auto ("transfer_pub", &rctx.gamma_tp),
    GNUNET_JSON_spec_json ("transfer_privs", &transfer_privs),
    GNUNET_JSON_spec_json ("link_sigs", &link_sigs),
    GNUNET_JSON_spec_json ("coin_evs", &coin_evs),
    GNUNET_JSON_spec_json ("new_denoms_h", &new_denoms_h),
    GNUNET_JSON_spec_end ()
  };
  (void) rh;
  res = TALER_MHD_parse_post_json (connection,
                                   connection_cls,
                                   upload_data,
                                   upload_data_size,
                                   &root);
  if (GNUNET_SYSERR == res)
    return MHD_NO;
  if ( (GNUNET_NO == res) ||
       (NULL == root) )
    return MHD_YES;
  memset (&rctx,
          0,
          sizeof (rctx));
  res = TALER_MHD_parse_json_data (connection,
                                   root,
                                   spec);
  json_decref (root);
  if (GNUNET_OK != res)
  {
    GNUNET_break_op (0);
    return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
  }
  /* Check we got enough transfer private keys */
  /* Note we do +1 as 1 row (cut-and-choose!) is missing! */
  if (TALER_CNC_KAPPA != json_array_size (transfer_privs) + 1)
  {
    GNUNET_JSON_parse_free (spec);
    GNUNET_break_op (0);
    return TALER_MHD_reply_with_error (connection,
                                       MHD_HTTP_BAD_REQUEST,
                                       TALER_EC_REFRESH_REVEAL_CNC_TRANSFER_ARRAY_SIZE_INVALID,
                                       "transfer_privs");
  }
  res = handle_refresh_reveal_json (connection,
                                    &rctx,
                                    transfer_privs,
                                    link_sigs,
                                    new_denoms_h,
                                    coin_evs);
  GNUNET_JSON_parse_free (spec);
  return res;
}
/* end of taler-exchange-httpd_refresh_reveal.c */