exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-exchange-httpd_post-withdraw.c (57254B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU Affero General Public License as
      7   published by the Free Software Foundation; either version 3,
      8   or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful,
     11   but WITHOUT ANY WARRANTY; without even the implied warranty
     12   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
     13   See the GNU Affero General Public License for more details.
     14 
     15   You should have received a copy of the GNU Affero General
     16   Public License along with TALER; see the file COPYING.  If not,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 /**
     20  * @file taler-exchange-httpd_post-withdraw.c
     21  * @brief Code to handle /withdraw requests
     22  * @note This endpoint is active since v26 of the protocol API
     23  * @author Özgür Kesim
     24  */
     25 
     26 #include <gnunet/gnunet_util_lib.h>
     27 #include <jansson.h>
     28 #include "taler-exchange-httpd.h"
     29 #include "exchange-database/iterate_withdraw_amounts_for_kyc_check.h"
     30 #include "taler/taler_json_lib.h"
     31 #include "taler/taler_kyclogic_lib.h"
     32 #include "taler/taler_mhd_lib.h"
     33 #include "taler-exchange-httpd_post-withdraw.h"
     34 #include "taler-exchange-httpd_common_kyc.h"
     35 #include "taler-exchange-httpd_responses.h"
     36 #include "taler-exchange-httpd_get-keys.h"
     37 #include "taler-exchange-httpd_secmod-helpers.h"
     38 #include "taler/taler_util.h"
     39 #include "exchange-database/do_withdraw.h"
     40 #include "exchange-database/get_withdraw.h"
     41 #include "exchange-database/get_reserve_origin.h"
     42 #include "exchange-database/rollback.h"
     43 
     44 /**
     45  * The different type of errors that might occur, sorted by name.
     46  * Some of them require idempotency checks, which are marked
     47  * in @e idempotency_check_required below.
     48  */
     49 enum WithdrawError
     50 {
     51   WITHDRAW_ERROR_NONE,
     52   WITHDRAW_ERROR_AGE_MASK_MISMATCH,
     53   WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
     54   WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED,
     55   WITHDRAW_ERROR_AMOUNT_OVERFLOW,
     56   WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW,
     57   WITHDRAW_ERROR_BLINDING_SEED_REQUIRED,
     58   WITHDRAW_ERROR_CIPHER_MISMATCH,
     59   WITHDRAW_ERROR_CONFIRMATION_SIGN,
     60   WITHDRAW_ERROR_DB_FETCH_FAILED,
     61   WITHDRAW_ERROR_DB_INVARIANT_FAILURE,
     62   WITHDRAW_ERROR_DENOMINATION_EXPIRED,
     63   WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN,
     64   WITHDRAW_ERROR_DENOMINATION_REVOKED,
     65   WITHDRAW_ERROR_DENOMINATION_SIGN,
     66   WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
     67   WITHDRAW_ERROR_FEE_OVERFLOW,
     68   WITHDRAW_ERROR_IDEMPOTENT_PLANCHET,
     69   WITHDRAW_ERROR_INSUFFICIENT_FUNDS,
     70   WITHDRAW_ERROR_CRYPTO_HELPER,
     71   WITHDRAW_ERROR_KEYS_MISSING,
     72   WITHDRAW_ERROR_KYC_REQUIRED,
     73   WITHDRAW_ERROR_LEGITIMIZATION_RESULT,
     74   WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE,
     75   WITHDRAW_ERROR_NONCE_REUSE,
     76   WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED,
     77   WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN,
     78   WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID,
     79   WITHDRAW_ERROR_RESERVE_UNKNOWN,
     80 };
     81 
     82 /**
     83  * With the bits set in this value will be mark the errors
     84  * that require a check for idempotency before actually
     85  * returning an error.
     86  */
     87 static const uint64_t idempotency_check_required =
     88   0
     89   | (1LLU << WITHDRAW_ERROR_DENOMINATION_EXPIRED)
     90   | (1LLU << WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN)
     91   | (1LLU << WITHDRAW_ERROR_DENOMINATION_REVOKED)
     92   | (1LLU << WITHDRAW_ERROR_INSUFFICIENT_FUNDS)
     93   | (1LLU << WITHDRAW_ERROR_KEYS_MISSING)
     94   | (1LLU << WITHDRAW_ERROR_KYC_REQUIRED);
     95 
     96 #define IDEMPOTENCY_CHECK_REQUIRED(ec) \
     97         (0LLU != (idempotency_check_required & (1LLU << (ec))))
     98 
     99 
    100 /**
    101  * Context for a /withdraw requests
    102  */
    103 struct WithdrawContext
    104 {
    105 
    106   /**
    107    * This struct is kept in a DLL.
    108    */
    109   struct WithdrawContext *prev;
    110   struct WithdrawContext *next;
    111 
    112   /**
    113      * Processing phase we are in.
    114      * The ordering here partially matters, as we progress through
    115      * them by incrementing the phase in the happy path.
    116      */
    117   enum
    118   {
    119     WITHDRAW_PHASE_PARSE = 0,
    120     WITHDRAW_PHASE_CHECK_KEYS,
    121     WITHDRAW_PHASE_CHECK_RESERVE_SIGNATURE,
    122     WITHDRAW_PHASE_RUN_LEGI_CHECK,
    123     WITHDRAW_PHASE_SUSPENDED,
    124     WITHDRAW_PHASE_CHECK_KYC_RESULT,
    125     WITHDRAW_PHASE_PREPARE_TRANSACTION,
    126     WITHDRAW_PHASE_RUN_TRANSACTION,
    127     WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS,
    128     WITHDRAW_PHASE_GENERATE_REPLY_ERROR,
    129     WITHDRAW_PHASE_RETURN_NO,
    130     WITHDRAW_PHASE_RETURN_YES,
    131   } phase;
    132 
    133 
    134   /**
    135    * Handle for the legitimization check.
    136    */
    137   struct TEH_LegitimizationCheckHandle *lch;
    138 
    139   /**
    140    * Request context
    141    */
    142   const struct TEH_RequestContext *rc;
    143 
    144   /**
    145    * KYC status for the operation.
    146    */
    147   struct TALER_EXCHANGEDB_KycStatus kyc;
    148 
    149   /**
    150    * Current time for the DB transaction.
    151    */
    152   struct GNUNET_TIME_Timestamp now;
    153 
    154   /**
    155    * Set to the hash of the normalized payto URI that established
    156    * the reserve.
    157    */
    158   struct TALER_NormalizedPaytoHashP h_normalized_payto;
    159 
    160   /**
    161    * Captures all parameters provided in the JSON request
    162    */
    163   struct
    164   {
    165     /**
    166      * All fields (from the request or computed)
    167      * that we persist in the database.
    168      */
    169     struct TALER_EXCHANGEDB_Withdraw withdraw;
    170 
    171     /**
    172      * In some error cases we check for idempotency.
    173      * If we find an entry in the database, we mark this here.
    174      */
    175     bool is_idempotent;
    176 
    177     /**
    178      * In some error conditions the request is checked
    179      * for idempotency and the result from the database
    180      * is stored here.
    181      */
    182     struct TALER_EXCHANGEDB_Withdraw withdraw_idem;
    183 
    184     /**
    185      * Array of ``withdraw.num_coins`` hashes of the public keys
    186      * of the denominations to withdraw.
    187      */
    188     struct TALER_DenominationHashP *denoms_h;
    189 
    190     /**
    191      * Number of planchets.  If ``withdraw.max_age`` was _not_ set, this is equal to ``num_coins``.
    192      * Otherwise (``withdraw.max_age`` was set) it is ``withdraw.num_coins * kappa``.
    193      */
    194     size_t num_planchets;
    195 
    196     /**
    197      * Array of ``withdraw.num_planchets`` coin planchets.
    198      * Note that the size depends on the age restriction:
    199      * If ``withdraw.age_proof_required`` is false,
    200      * this is an array of length ``withdraw.num_coins``.
    201      * Otherwise it is an array of length ``kappa*withdraw.num_coins``,
    202      * arranged in runs of ``num_coins`` coins,
    203      * [0..num_coins)..[0..num_coins),
    204      * one for each #TALER_CNC_KAPPA value.
    205      */
    206     struct TALER_BlindedPlanchet *planchets;
    207 
    208     /**
    209      * If proof of age-restriction is required, the #TALER_CNC_KAPPA hashes
    210      * of the batches of ``withdraw.num_coins`` coins.
    211      */
    212     struct TALER_HashBlindedPlanchetsP kappa_planchets_h[TALER_CNC_KAPPA];
    213 
    214     /**
    215      * Total (over all coins) amount (excluding fee) committed to withdraw
    216      */
    217     struct TALER_Amount amount;
    218 
    219     /**
    220      * Total fees for the withdraw
    221      */
    222     struct TALER_Amount fee;
    223 
    224     /**
    225      * Array of length ``withdraw.num_cs_r_values`` of indices into
    226      * @e denoms_h of CS denominations.
    227      */
    228     uint32_t *cs_indices;
    229 
    230   } request;
    231 
    232 
    233   /**
    234    * Errors occurring during evaluation of the request are captured in this
    235    * struct.  In phase WITHDRAW_PHASE_GENERATE_REPLY_ERROR an appropriate error
    236    * message is prepared and sent to the client.
    237    */
    238   struct
    239   {
    240     /* The (internal) error code */
    241     enum WithdrawError code;
    242 
    243     /**
    244      * Some errors require details to be sent to the client.
    245      * These are captured in this union.
    246      * Each field is named according to the error that is using it, except
    247      * commented otherwise.
    248      */
    249     union
    250     {
    251       const char *request_parameter_malformed;
    252 
    253       const char *reserve_cipher_unknown;
    254 
    255       /**
    256        * For all errors related to a particular denomination, i.e.
    257        *  WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN,
    258        *  WITHDRAW_ERROR_DENOMINATION_EXPIRED,
    259        *  WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
    260        *  WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
    261        *  WITHDRAW_ERROR_AGE_MASK_MISMATCH,
    262        * we use this one field.
    263        */
    264       const struct TALER_DenominationHashP *denom_h;
    265 
    266       const char *db_fetch_context;
    267 
    268       struct
    269       {
    270         uint16_t max_allowed;
    271         uint32_t birthday;
    272       } maximum_age_too_large;
    273 
    274       /**
    275        * The lowest age required
    276        */
    277       uint16_t age_restriction_required;
    278 
    279       /**
    280        * Balance of the reserve
    281        */
    282       struct TALER_Amount insufficient_funds;
    283 
    284       enum TALER_ErrorCode ec_confirmation_sign;
    285 
    286       enum TALER_ErrorCode ec_denomination_sign;
    287 
    288       struct
    289       {
    290         struct MHD_Response *response;
    291         unsigned int http_status;
    292       } legitimization_result;
    293 
    294     } details;
    295   } error;
    296 };
    297 
    298 /**
    299  * The following macros set the given error code,
    300  * set the phase to WITHDRAW_PHASE_GENERATE_REPLY_ERROR,
    301  * and optionally set the given field (with an optionally given value).
    302  */
    303 #define SET_ERROR(wc, ec) \
    304         do \
    305         { GNUNET_static_assert (WITHDRAW_ERROR_NONE != ec); \
    306           (wc)->error.code = (ec);                          \
    307           (wc)->phase = WITHDRAW_PHASE_GENERATE_REPLY_ERROR; } while (0)
    308 
    309 #define SET_ERROR_WITH_FIELD(wc, ec, field) \
    310         do \
    311         { GNUNET_static_assert (WITHDRAW_ERROR_NONE != ec); \
    312           (wc)->error.code = (ec);                          \
    313           (wc)->error.details.field = (field);              \
    314           (wc)->phase = WITHDRAW_PHASE_GENERATE_REPLY_ERROR; } while (0)
    315 
    316 #define SET_ERROR_WITH_DETAIL(wc, ec, field, value) \
    317         do \
    318         { GNUNET_static_assert (WITHDRAW_ERROR_NONE != ec); \
    319           (wc)->error.code = (ec);                          \
    320           (wc)->error.details.field = (value);              \
    321           (wc)->phase = WITHDRAW_PHASE_GENERATE_REPLY_ERROR; } while (0)
    322 
    323 
    324 /**
    325  * All withdraw context is kept in a DLL.
    326  */
    327 static struct WithdrawContext *wc_head;
    328 static struct WithdrawContext *wc_tail;
    329 
    330 
    331 void
    332 TEH_withdraw_cleanup ()
    333 {
    334   struct WithdrawContext *wc;
    335 
    336   while (NULL != (wc = wc_head))
    337   {
    338     GNUNET_CONTAINER_DLL_remove (wc_head,
    339                                  wc_tail,
    340                                  wc);
    341     wc->phase = WITHDRAW_PHASE_RETURN_NO;
    342     MHD_resume_connection (wc->rc->connection);
    343   }
    344 }
    345 
    346 
    347 /**
    348  * Terminate the main loop by returning the final
    349  * result.
    350  *
    351  * @param[in,out] wc context to update phase for
    352  * @param mres MHD status to return
    353  */
    354 static void
    355 finish_loop (struct WithdrawContext *wc,
    356              enum MHD_Result mres)
    357 {
    358   wc->phase = (MHD_YES == mres)
    359     ? WITHDRAW_PHASE_RETURN_YES
    360     : WITHDRAW_PHASE_RETURN_NO;
    361 }
    362 
    363 
    364 /**
    365  * Check if the withdraw request is replayed
    366  * and we already have an answer.
    367  * If so, replay the existing answer and return the HTTP response.
    368  *
    369  * @param[in,out] wc parsed request data
    370  * @return true if the request is idempotent with an existing request
    371  *    false if we did not find the request in the DB and did not set @a mret
    372  */
    373 static bool
    374 withdraw_is_idempotent (
    375   struct WithdrawContext *wc)
    376 {
    377   enum GNUNET_DB_QueryStatus qs;
    378   uint8_t max_retries = 3;
    379 
    380   /* We should at most be called once */
    381   GNUNET_assert (! wc->request.is_idempotent);
    382   while (0 < max_retries--)
    383   {
    384     qs = TALER_EXCHANGEDB_get_withdraw (
    385       TEH_pg,
    386       &wc->request.withdraw.reserve_pub,
    387       &wc->request.withdraw.planchets_h,
    388       &wc->request.withdraw_idem);
    389     if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
    390       break;
    391   }
    392 
    393   if (0 > qs)
    394   {
    395     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    396     GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
    397     SET_ERROR_WITH_DETAIL (wc,
    398                            WITHDRAW_ERROR_DB_FETCH_FAILED,
    399                            db_fetch_context,
    400                            "get_withdraw");
    401     return true; /* Well, kind-of. */
    402   }
    403   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    404     return false;
    405 
    406   wc->request.is_idempotent = true;
    407   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    408               "request is idempotent\n");
    409 
    410   /* Generate idempotent reply */
    411   TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW]++;
    412   wc->phase = WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS;
    413   return true;
    414 }
    415 
    416 
    417 /**
    418  * Function implementing withdraw transaction.  Runs the
    419  * transaction logic; IF it returns a non-error code, the transaction
    420  * logic MUST NOT queue a MHD response.  IF it returns an hard error,
    421  * the transaction logic MUST queue a MHD response and set @a mhd_ret.
    422  * IF it returns the soft error code, the function MAY be called again
    423  * to retry and MUST not queue a MHD response.
    424  *
    425  * @param cls a `struct WithdrawContext *`
    426  * @param connection MHD request which triggered the transaction
    427  * @param[out] mhd_ret set to MHD response status for @a connection,
    428  *             if transaction failed (!)
    429  * @return transaction status
    430  */
    431 static enum GNUNET_DB_QueryStatus
    432 withdraw_transaction (
    433   void *cls,
    434   struct MHD_Connection *connection,
    435   enum MHD_Result *mhd_ret)
    436 {
    437   struct WithdrawContext *wc = cls;
    438   enum GNUNET_DB_QueryStatus qs;
    439   bool balance_ok;
    440   bool age_ok;
    441   bool found;
    442   uint16_t noreveal_index;
    443   bool nonce_reuse;
    444   uint16_t allowed_maximum_age;
    445   uint32_t reserve_birthday;
    446   struct TALER_Amount insufficient_funds;
    447 
    448   qs = TALER_EXCHANGEDB_do_withdraw (TEH_pg,
    449                                      &wc->request.withdraw,
    450                                      &wc->now,
    451                                      &balance_ok,
    452                                      &insufficient_funds,
    453                                      &age_ok,
    454                                      &allowed_maximum_age,
    455                                      &reserve_birthday,
    456                                      &found,
    457                                      &noreveal_index,
    458                                      &nonce_reuse);
    459   if (0 > qs)
    460   {
    461     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
    462       SET_ERROR_WITH_DETAIL (wc,
    463                              WITHDRAW_ERROR_DB_FETCH_FAILED,
    464                              db_fetch_context,
    465                              "do_withdraw");
    466     return qs;
    467   }
    468   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    469   {
    470     SET_ERROR (wc,
    471                WITHDRAW_ERROR_RESERVE_UNKNOWN);
    472     return GNUNET_DB_STATUS_HARD_ERROR;
    473   }
    474 
    475   if (found)
    476   {
    477     /**
    478      * The request was idempotent and we got the previous noreveal_index.
    479      * We simply overwrite that value in our current withdraw object and
    480      * move on to reply success.
    481      */
    482     wc->request.withdraw.noreveal_index = noreveal_index;
    483     wc->phase = WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS;
    484     return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    485   }
    486 
    487   if (! age_ok)
    488   {
    489     if (wc->request.withdraw.age_proof_required)
    490     {
    491       wc->error.details.maximum_age_too_large.max_allowed = allowed_maximum_age;
    492       wc->error.details.maximum_age_too_large.birthday = reserve_birthday;
    493       SET_ERROR (wc,
    494                  WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE);
    495     }
    496     else
    497     {
    498       wc->error.details.age_restriction_required = allowed_maximum_age;
    499       SET_ERROR (wc,
    500                  WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED);
    501     }
    502     return GNUNET_DB_STATUS_HARD_ERROR;
    503   }
    504 
    505   if (! balance_ok)
    506   {
    507     TALER_EXCHANGEDB_rollback (TEH_pg);
    508     SET_ERROR_WITH_FIELD (wc,
    509                           WITHDRAW_ERROR_INSUFFICIENT_FUNDS,
    510                           insufficient_funds);
    511     return GNUNET_DB_STATUS_HARD_ERROR;
    512   }
    513 
    514   if (nonce_reuse)
    515   {
    516     GNUNET_break (0);
    517     SET_ERROR (wc,
    518                WITHDRAW_ERROR_NONCE_REUSE);
    519     return GNUNET_DB_STATUS_HARD_ERROR;
    520   }
    521 
    522   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
    523     TEH_METRICS_num_success[TEH_MT_SUCCESS_WITHDRAW]++;
    524   return qs;
    525 }
    526 
    527 
    528 /**
    529  * The request was prepared successfully.
    530  * Run the main DB transaction.
    531  *
    532  * @param wc The context for the current withdraw request
    533  */
    534 static void
    535 phase_run_transaction (
    536   struct WithdrawContext *wc)
    537 {
    538   enum MHD_Result mhd_ret;
    539   enum GNUNET_GenericReturnValue ret;
    540 
    541   GNUNET_assert (WITHDRAW_PHASE_RUN_TRANSACTION ==
    542                  wc->phase);
    543   ret = TEH_DB_run_transaction (wc->rc->connection,
    544                                 "run withdraw",
    545                                 TEH_MT_REQUEST_WITHDRAW,
    546                                 &mhd_ret,
    547                                 &withdraw_transaction,
    548                                 wc);
    549   /* If the transaction has changed the phase, we don't alter it and return.*/
    550   if (WITHDRAW_PHASE_RUN_TRANSACTION != wc->phase)
    551     return;
    552   if (GNUNET_OK != ret)
    553   {
    554     /* The transaction failed without setting an error phase (preflight,
    555        start, hard commit failure or too many soft failures); a response
    556        was already queued by TEH_DB_run_transaction(). */
    557     GNUNET_break (0);
    558     finish_loop (wc,
    559                  mhd_ret);
    560     return;
    561   }
    562   wc->phase++;
    563 }
    564 
    565 
    566 /**
    567  * The request for withdraw was parsed successfully.
    568  * Sign and persist the chosen blinded coins for the reveal step.
    569  *
    570  * @param wc The context for the current withdraw request
    571  */
    572 static void
    573 phase_prepare_transaction (
    574   struct WithdrawContext *wc)
    575 {
    576   size_t offset = 0;
    577 
    578   wc->request.withdraw.denom_sigs
    579     = GNUNET_new_array (
    580         wc->request.withdraw.num_coins,
    581         struct TALER_BlindedDenominationSignature);
    582   /* Pick the challenge in case of age restriction  */
    583   if (wc->request.withdraw.age_proof_required)
    584   {
    585     wc->request.withdraw.noreveal_index =
    586       GNUNET_CRYPTO_random_u32 (TALER_CNC_KAPPA);
    587     /**
    588      * In case of age restriction, we use the corresponding offset in the planchet
    589      * array to the beginning of the coins corresponding to the noreveal_index.
    590      */
    591     offset = wc->request.withdraw.noreveal_index
    592              * wc->request.withdraw.num_coins;
    593     GNUNET_assert (offset + wc->request.withdraw.num_coins <=
    594                    wc->request.num_planchets);
    595   }
    596 
    597   /* Choose and sign the coins */
    598   {
    599     struct TEH_SECMOD_CoinSignData csds[wc->request.withdraw.num_coins];
    600     enum TALER_ErrorCode ec_denomination_sign;
    601 
    602     memset (csds,
    603             0,
    604             sizeof(csds));
    605 
    606     /* Pick the chosen blinded coins */
    607     for (uint32_t i = 0; i<wc->request.withdraw.num_coins; i++)
    608     {
    609       csds[i].bp = &wc->request.planchets[i + offset];
    610       csds[i].h_denom_pub = &wc->request.denoms_h[i];
    611     }
    612 
    613     ec_denomination_sign = TEH_SECMOD_denom_batch_sign (
    614       wc->request.withdraw.num_coins,
    615       csds,
    616       false,
    617       wc->request.withdraw.denom_sigs);
    618     if (TALER_EC_NONE != ec_denomination_sign)
    619     {
    620       GNUNET_break (0);
    621       SET_ERROR_WITH_FIELD (wc,
    622                             WITHDRAW_ERROR_DENOMINATION_SIGN,
    623                             ec_denomination_sign);
    624       return;
    625     }
    626 
    627     /* Save the hash value of the selected batch of coins */
    628     wc->request.withdraw.selected_h =
    629       wc->request.kappa_planchets_h[wc->request.withdraw.noreveal_index];
    630   }
    631 
    632   /**
    633    * For the denominations with cipher CS, calculate the R-values
    634    * and save the choices we made now, as at a later point, the
    635    * private keys for the denominations might now be available anymore
    636    * to make the same choice again.
    637    */
    638   if (0 <  wc->request.withdraw.num_cs_r_values)
    639   {
    640     size_t num_cs_r_values = wc->request.withdraw.num_cs_r_values;
    641     struct TEH_SECMOD_CsDeriveData cdds[num_cs_r_values];
    642     struct GNUNET_CRYPTO_CsSessionNonce nonces[num_cs_r_values];
    643 
    644     memset (nonces,
    645             0,
    646             sizeof(nonces));
    647     wc->request.withdraw.cs_r_values
    648       = GNUNET_new_array (
    649           num_cs_r_values,
    650           struct GNUNET_CRYPTO_CSPublicRPairP);
    651     wc->request.withdraw.cs_r_choices = 0;
    652 
    653     GNUNET_assert (! wc->request.withdraw.no_blinding_seed);
    654     TALER_cs_derive_nonces_from_seed (
    655       &wc->request.withdraw.blinding_seed,
    656       false,   /* not for melt */
    657       num_cs_r_values,
    658       wc->request.cs_indices,
    659       nonces);
    660 
    661     for (size_t i = 0; i < num_cs_r_values; i++)
    662     {
    663       size_t idx = wc->request.cs_indices[i];
    664 
    665       GNUNET_assert (idx < wc->request.withdraw.num_coins);
    666       cdds[i].h_denom_pub = &wc->request.denoms_h[idx];
    667       cdds[i].nonce = &nonces[i];
    668     }
    669 
    670     /**
    671      * Let the crypto helper generate the R-values and make the choices.
    672      */
    673     if (TALER_EC_NONE !=
    674         TEH_SECMOD_denom_cs_batch_r_pub_simple (
    675           wc->request.withdraw.num_cs_r_values,
    676           cdds,
    677           false,
    678           wc->request.withdraw.cs_r_values))
    679     {
    680       GNUNET_break (0);
    681       SET_ERROR (wc,
    682                  WITHDRAW_ERROR_CRYPTO_HELPER);
    683       return;
    684     }
    685 
    686     /* This invariant should hold because
    687        num_coins <= TALER_MAX_COINS. Still good
    688        to check explicitly. */
    689     GNUNET_assert (num_cs_r_values <= 64);
    690     /* Now save the choices for the selected bits */
    691     for (size_t i = 0; i < num_cs_r_values; i++)
    692     {
    693       size_t idx = wc->request.cs_indices[i];
    694       struct TALER_BlindedDenominationSignature *sig =
    695         &wc->request.withdraw.denom_sigs[idx];
    696       uint64_t bit = sig->blinded_sig->details.blinded_cs_answer.b;
    697 
    698       GNUNET_static_assert (
    699         TALER_MAX_COINS <=
    700         sizeof(wc->request.withdraw.cs_r_choices) * 8);
    701       GNUNET_assert (bit <= 1); /* well, should actually be 0 or 1 */
    702       wc->request.withdraw.cs_r_choices |= bit << i;
    703     }
    704   }
    705   wc->phase++;
    706 }
    707 
    708 
    709 /**
    710  * Check the KYC result.
    711  *
    712  * @param wc context for request processing
    713  */
    714 static void
    715 phase_check_kyc_result (struct WithdrawContext *wc)
    716 {
    717   /* return final positive response */
    718   if (! wc->kyc.ok)
    719   {
    720     SET_ERROR (wc,
    721                WITHDRAW_ERROR_KYC_REQUIRED);
    722     return;
    723   }
    724   wc->phase++;
    725 }
    726 
    727 
    728 /**
    729  * Function called with the result of a legitimization
    730  * check.
    731  *
    732  * @param cls closure
    733  * @param lcr legitimization check result
    734  */
    735 static void
    736 withdraw_legi_cb (
    737   void *cls,
    738   const struct TEH_LegitimizationCheckResult *lcr)
    739 {
    740   struct WithdrawContext *wc = cls;
    741 
    742   wc->lch = NULL;
    743   GNUNET_assert (WITHDRAW_PHASE_SUSPENDED ==
    744                  wc->phase);
    745   MHD_resume_connection (wc->rc->connection);
    746   GNUNET_CONTAINER_DLL_remove (wc_head,
    747                                wc_tail,
    748                                wc);
    749   TALER_MHD_daemon_trigger ();
    750   if (NULL != lcr->response)
    751   {
    752     wc->error.details.legitimization_result.response = lcr->response;
    753     wc->error.details.legitimization_result.http_status = lcr->http_status;
    754     SET_ERROR (wc,
    755                WITHDRAW_ERROR_LEGITIMIZATION_RESULT);
    756     return;
    757   }
    758   wc->kyc = lcr->kyc;
    759   wc->phase = WITHDRAW_PHASE_CHECK_KYC_RESULT;
    760 }
    761 
    762 
    763 /**
    764  * Function called to iterate over KYC-relevant transaction amounts for a
    765  * particular time range. Called within a database transaction, so must
    766  * not start a new one.
    767  *
    768  * @param cls closure, identifies the event type and account to iterate
    769  *        over events for
    770  * @param limit maximum time-range for which events should be fetched
    771  *        (timestamp in the past)
    772  * @param cb function to call on each event found, events must be returned
    773  *        in reverse chronological order
    774  * @param cb_cls closure for @a cb, of type struct WithdrawContext
    775  * @return transaction status
    776  */
    777 static enum GNUNET_DB_QueryStatus
    778 withdraw_amount_cb (
    779   void *cls,
    780   struct GNUNET_TIME_Absolute limit,
    781   TALER_KYCLOGIC_KycAmountCallback cb,
    782   void *cb_cls)
    783 {
    784   struct WithdrawContext *wc = cls;
    785   enum GNUNET_GenericReturnValue ret;
    786   enum GNUNET_DB_QueryStatus qs;
    787 
    788   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    789               "Signaling amount %s for KYC check during witdrawal\n",
    790               TALER_amount2s (&wc->request.withdraw.amount_with_fee));
    791 
    792   ret = cb (cb_cls,
    793             &wc->request.withdraw.amount_with_fee,
    794             wc->now.abs_time);
    795   GNUNET_break (GNUNET_SYSERR != ret);
    796   if (GNUNET_OK != ret)
    797     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
    798 
    799   qs = TALER_EXCHANGEDB_iterate_withdraw_amounts_for_kyc_check (
    800     TEH_pg,
    801     &wc->h_normalized_payto,
    802     limit,
    803     cb,
    804     cb_cls);
    805   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    806               "Got %d additional transactions for this withdrawal and limit %llu\n",
    807               qs,
    808               (unsigned long long) limit.abs_value_us);
    809   GNUNET_break (qs >= 0);
    810   return qs;
    811 }
    812 
    813 
    814 /**
    815  * Do legitimization check.
    816  *
    817  * @param wc operation context
    818  */
    819 static void
    820 phase_run_legi_check (struct WithdrawContext *wc)
    821 {
    822   enum GNUNET_DB_QueryStatus qs;
    823   struct TALER_FullPayto payto_uri;
    824   struct TALER_FullPaytoHashP h_full_payto;
    825 
    826   /* Check if the money came from a wire transfer */
    827   qs = TALER_EXCHANGEDB_get_reserve_origin (
    828     TEH_pg,
    829     &wc->request.withdraw.reserve_pub,
    830     &h_full_payto,
    831     &payto_uri);
    832   if (qs < 0)
    833   {
    834     SET_ERROR_WITH_DETAIL (wc,
    835                            WITHDRAW_ERROR_DB_FETCH_FAILED,
    836                            db_fetch_context,
    837                            "reserves_get_origin");
    838     return;
    839   }
    840   /* If _no_ results, reserve was created by merge,
    841      in which case no KYC check is required as the
    842      merge already did that. */
    843   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    844   {
    845     wc->phase = WITHDRAW_PHASE_PREPARE_TRANSACTION;
    846     return;
    847   }
    848   TALER_full_payto_normalize_and_hash (payto_uri,
    849                                        &wc->h_normalized_payto);
    850   wc->lch = TEH_legitimization_check (
    851     &wc->rc->async_scope_id,
    852     TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
    853     payto_uri,
    854     &wc->h_normalized_payto,
    855     NULL, /* no account pub: this is about the origin account */
    856     &withdraw_amount_cb,
    857     wc,
    858     &withdraw_legi_cb,
    859     wc);
    860   GNUNET_assert (NULL != wc->lch);
    861   GNUNET_free (payto_uri.full_payto);
    862   GNUNET_CONTAINER_DLL_insert (wc_head,
    863                                wc_tail,
    864                                wc);
    865   MHD_suspend_connection (wc->rc->connection);
    866   wc->phase = WITHDRAW_PHASE_SUSPENDED;
    867 }
    868 
    869 
    870 /**
    871  * Check if the given denomination is still or already valid, has not been
    872  * revoked and potentically supports age restriction.
    873  *
    874  * @param[in,out] wc context for the withdraw operation
    875  * @param ksh The handle to the current state of (denomination) keys in the exchange
    876  * @param denom_h Hash of the denomination key to check
    877  * @param[out] pdk denomination key found, might be NULL
    878  * @return true when denomation was found and valid,
    879  *         false when denomination was not valid and the state machine was advanced
    880  */
    881 static enum GNUNET_GenericReturnValue
    882 find_denomination (
    883   struct WithdrawContext *wc,
    884   struct TEH_KeyStateHandle *ksh,
    885   const struct TALER_DenominationHashP *denom_h,
    886   struct TEH_DenominationKey **pdk)
    887 {
    888   struct TEH_DenominationKey *dk;
    889 
    890   *pdk = NULL;
    891   dk = TEH_keys_denomination_by_hash_from_state (
    892     ksh,
    893     denom_h,
    894     NULL,
    895     NULL);
    896   if (NULL == dk)
    897   {
    898     SET_ERROR_WITH_FIELD (wc,
    899                           WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN,
    900                           denom_h);
    901     return false;
    902   }
    903   if (GNUNET_TIME_absolute_is_past (
    904         dk->meta.expire_withdraw.abs_time))
    905   {
    906     SET_ERROR_WITH_FIELD (wc,
    907                           WITHDRAW_ERROR_DENOMINATION_EXPIRED,
    908                           denom_h);
    909     return false;
    910   }
    911   if (GNUNET_TIME_absolute_is_future (
    912         dk->meta.start.abs_time))
    913   {
    914     GNUNET_break_op (0);
    915     SET_ERROR_WITH_FIELD (wc,
    916                           WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
    917                           denom_h);
    918     return false;
    919   }
    920   if (dk->recoup_possible)
    921   {
    922     SET_ERROR (wc,
    923                WITHDRAW_ERROR_DENOMINATION_REVOKED);
    924     return false;
    925   }
    926   /* In case of age withdraw, make sure that the denomination supports age restriction */
    927   if (wc->request.withdraw.age_proof_required)
    928   {
    929     if (0 == dk->denom_pub.age_mask.bits)
    930     {
    931       GNUNET_break_op (0);
    932       SET_ERROR_WITH_FIELD (wc,
    933                             WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
    934                             denom_h);
    935       return false;
    936     }
    937   }
    938   *pdk = dk;
    939   return true;
    940 }
    941 
    942 
    943 /**
    944  * Check if the given array of hashes of denomination_keys
    945  * a) belong to valid denominations
    946  * b) those are marked as age restricted, if the request is age restricted
    947  * c) calculate the total amount of the denominations including fees
    948  * for withdraw.
    949  *
    950  * @param wc context of the age withdrawal to check keys for
    951  */
    952 static void
    953 phase_check_keys (
    954   struct WithdrawContext *wc)
    955 {
    956   struct TEH_KeyStateHandle *ksh;
    957   bool is_cs_denom[wc->request.withdraw.num_coins];
    958   struct TALER_AgeMask age_mask = { 0 };
    959 
    960   memset (is_cs_denom,
    961           0,
    962           sizeof(is_cs_denom));
    963   ksh = TEH_keys_get_state ();
    964   if (NULL == ksh)
    965   {
    966     GNUNET_break (0);
    967     SET_ERROR (wc,
    968                WITHDRAW_ERROR_KEYS_MISSING);
    969     return;
    970   }
    971   wc->request.withdraw.denom_serials =
    972     GNUNET_new_array (wc->request.withdraw.num_coins,
    973                       uint64_t);
    974   GNUNET_assert (GNUNET_OK ==
    975                  TALER_amount_set_zero (TEH_currency,
    976                                         &wc->request.amount));
    977   GNUNET_assert (GNUNET_OK ==
    978                  TALER_amount_set_zero (TEH_currency,
    979                                         &wc->request.fee));
    980   GNUNET_assert (GNUNET_OK ==
    981                  TALER_amount_set_zero (TEH_currency,
    982                                         &wc->request.withdraw.amount_with_fee));
    983 
    984   for (unsigned int i = 0; i < wc->request.withdraw.num_coins; i++)
    985   {
    986     struct TEH_DenominationKey *dk;
    987 
    988     if (! find_denomination (wc,
    989                              ksh,
    990                              &wc->request.denoms_h[i],
    991                              &dk))
    992       return;
    993     /* All coins of an age-restricted withdraw must use the same age
    994        groups, as /reveal-withdraw derives their age commitments with
    995        one mask. */
    996     if (wc->request.withdraw.age_proof_required)
    997     {
    998       if (0 == i)
    999         age_mask = dk->meta.age_mask;
   1000       if (age_mask.bits != dk->meta.age_mask.bits)
   1001       {
   1002         GNUNET_break_op (0);
   1003         SET_ERROR_WITH_DETAIL (wc,
   1004                                WITHDRAW_ERROR_AGE_MASK_MISMATCH,
   1005                                denom_h,
   1006                                &wc->request.denoms_h[i]);
   1007         return;
   1008       }
   1009     }
   1010     switch (dk->denom_pub.bsign_pub_key->cipher)
   1011     {
   1012     case GNUNET_CRYPTO_BSA_INVALID:
   1013       /* This should never happen (memory corruption?) */
   1014       GNUNET_assert (0);
   1015     case GNUNET_CRYPTO_BSA_RSA:
   1016       /* nothing to do here */
   1017       break;
   1018     case GNUNET_CRYPTO_BSA_CS:
   1019       if (wc->request.withdraw.no_blinding_seed)
   1020       {
   1021         GNUNET_break_op (0);
   1022         SET_ERROR (wc,
   1023                    WITHDRAW_ERROR_BLINDING_SEED_REQUIRED);
   1024         return;
   1025       }
   1026       wc->request.withdraw.num_cs_r_values++;
   1027       is_cs_denom[i] = true;
   1028       break;
   1029     }
   1030 
   1031     /* Ensure the ciphers from the planchets match the denominations'. */
   1032     if (wc->request.withdraw.age_proof_required)
   1033     {
   1034       for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
   1035       {
   1036         size_t off = k * wc->request.withdraw.num_coins;
   1037 
   1038         if (dk->denom_pub.bsign_pub_key->cipher !=
   1039             wc->request.planchets[i + off].blinded_message->cipher)
   1040         {
   1041           GNUNET_break_op (0);
   1042           SET_ERROR (wc,
   1043                      WITHDRAW_ERROR_CIPHER_MISMATCH);
   1044           return;
   1045         }
   1046       }
   1047     }
   1048     else
   1049     {
   1050       if (dk->denom_pub.bsign_pub_key->cipher !=
   1051           wc->request.planchets[i].blinded_message->cipher)
   1052       {
   1053         GNUNET_break_op (0);
   1054         SET_ERROR (wc,
   1055                    WITHDRAW_ERROR_CIPHER_MISMATCH);
   1056         return;
   1057       }
   1058     }
   1059 
   1060     /* Accumulate the values */
   1061     if (0 > TALER_amount_add (&wc->request.amount,
   1062                               &wc->request.amount,
   1063                               &dk->meta.value))
   1064     {
   1065       GNUNET_break_op (0);
   1066       SET_ERROR (wc,
   1067                  WITHDRAW_ERROR_AMOUNT_OVERFLOW);
   1068       return;
   1069     }
   1070 
   1071     /* Accumulate the withdraw fees */
   1072     if (0 > TALER_amount_add (&wc->request.fee,
   1073                               &wc->request.fee,
   1074                               &dk->meta.fees.withdraw))
   1075     {
   1076       GNUNET_break_op (0);
   1077       SET_ERROR (wc,
   1078                  WITHDRAW_ERROR_FEE_OVERFLOW);
   1079       return;
   1080     }
   1081     wc->request.withdraw.denom_serials[i] = dk->meta.serial;
   1082   }
   1083 
   1084   /* Save the total amount including fees */
   1085   if (0 >  TALER_amount_add (
   1086         &wc->request.withdraw.amount_with_fee,
   1087         &wc->request.amount,
   1088         &wc->request.fee))
   1089   {
   1090     GNUNET_break_op (0);
   1091     SET_ERROR (wc,
   1092                WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW);
   1093     return;
   1094   }
   1095 
   1096   /* Save the indices of CS denominations */
   1097   if (0 < wc->request.withdraw.num_cs_r_values)
   1098   {
   1099     size_t j = 0;
   1100 
   1101     wc->request.cs_indices = GNUNET_new_array (
   1102       wc->request.withdraw.num_cs_r_values,
   1103       uint32_t);
   1104 
   1105     for (size_t i = 0; i < wc->request.withdraw.num_coins; i++)
   1106     {
   1107       if (is_cs_denom[i])
   1108         wc->request.cs_indices[j++] = i;
   1109     }
   1110   }
   1111 
   1112   wc->phase++;
   1113 }
   1114 
   1115 
   1116 /**
   1117  * Check that the client signature authorizing the withdrawal is valid.
   1118  *
   1119  * @param[in,out] wc request context to check
   1120  */
   1121 static void
   1122 phase_check_reserve_signature (
   1123   struct WithdrawContext *wc)
   1124 {
   1125   TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
   1126   if (GNUNET_OK !=
   1127       TALER_wallet_withdraw_verify (
   1128         &wc->request.amount,
   1129         &wc->request.fee,
   1130         &wc->request.withdraw.planchets_h,
   1131         wc->request.withdraw.no_blinding_seed
   1132         ? NULL
   1133         : &wc->request.withdraw.blinding_seed,
   1134         (wc->request.withdraw.age_proof_required)
   1135         ? &TEH_age_restriction_mask
   1136         : NULL,
   1137         (wc->request.withdraw.age_proof_required)
   1138         ? wc->request.withdraw.max_age
   1139         : 0,
   1140         &wc->request.withdraw.reserve_pub,
   1141         &wc->request.withdraw.reserve_sig))
   1142   {
   1143     GNUNET_break_op (0);
   1144     SET_ERROR (wc,
   1145                WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID);
   1146     return;
   1147   }
   1148   wc->phase++;
   1149 }
   1150 
   1151 
   1152 /**
   1153  * Free data inside of @a wd, but not @a wd itself.
   1154  *
   1155  * @param[in] wd withdraw data to free
   1156  */
   1157 static void
   1158 free_db_withdraw_data (struct TALER_EXCHANGEDB_Withdraw *wd)
   1159 {
   1160   if (NULL != wd->denom_sigs)
   1161   {
   1162     for (unsigned int i = 0; i<wd->num_coins; i++)
   1163       TALER_blinded_denom_sig_free (&wd->denom_sigs[i]);
   1164     GNUNET_free (wd->denom_sigs);
   1165   }
   1166   GNUNET_free (wd->denom_serials);
   1167   GNUNET_free (wd->cs_r_values);
   1168 }
   1169 
   1170 
   1171 /**
   1172  * Cleanup routine for withdraw request.
   1173  * The function is called upon completion of the request
   1174  * that should clean up @a rh_ctx. Can be NULL.
   1175  *
   1176  * @param rc request context to clean up
   1177  */
   1178 static void
   1179 clean_withdraw_rc (struct TEH_RequestContext *rc)
   1180 {
   1181   struct WithdrawContext *wc = rc->rh_ctx;
   1182 
   1183   if (NULL != wc->lch)
   1184   {
   1185     TEH_legitimization_check_cancel (wc->lch);
   1186     wc->lch = NULL;
   1187   }
   1188   GNUNET_free (wc->request.denoms_h);
   1189   if (NULL != wc->request.planchets)
   1190   {
   1191     /* num_planchets is set long before planchets is allocated,
   1192        so this needs the above guard */
   1193     for (unsigned int i = 0; i<wc->request.num_planchets; i++)
   1194       TALER_blinded_planchet_free (&wc->request.planchets[i]);
   1195     GNUNET_free (wc->request.planchets);
   1196   }
   1197   free_db_withdraw_data (&wc->request.withdraw);
   1198   GNUNET_free (wc->request.cs_indices);
   1199   if (wc->request.is_idempotent)
   1200     free_db_withdraw_data (&wc->request.withdraw_idem);
   1201   if ( (WITHDRAW_ERROR_LEGITIMIZATION_RESULT == wc->error.code) &&
   1202        (NULL != wc->error.details.legitimization_result.response) )
   1203   {
   1204     MHD_destroy_response (wc->error.details.legitimization_result.response);
   1205     wc->error.details.legitimization_result.response = NULL;
   1206   }
   1207   GNUNET_free (wc);
   1208 }
   1209 
   1210 
   1211 /**
   1212  * Generates response for the withdraw request.
   1213  *
   1214  * @param wc withdraw operation context
   1215  */
   1216 static void
   1217 phase_generate_reply_success (struct WithdrawContext *wc)
   1218 {
   1219   struct TALER_EXCHANGEDB_Withdraw *db_obj;
   1220 
   1221   db_obj = wc->request.is_idempotent
   1222     ? &wc->request.withdraw_idem
   1223     : &wc->request.withdraw;
   1224 
   1225   if (wc->request.withdraw.age_proof_required)
   1226   {
   1227     struct TALER_ExchangePublicKeyP pub;
   1228     struct TALER_ExchangeSignatureP sig;
   1229     enum TALER_ErrorCode ec_confirmation_sign;
   1230 
   1231     ec_confirmation_sign =
   1232       TALER_exchange_online_withdraw_age_confirmation_sign (
   1233         &TEH_keys_exchange_sign_,
   1234         &db_obj->planchets_h,
   1235         db_obj->noreveal_index,
   1236         &pub,
   1237         &sig);
   1238     if (TALER_EC_NONE != ec_confirmation_sign)
   1239     {
   1240       SET_ERROR_WITH_FIELD (wc,
   1241                             WITHDRAW_ERROR_CONFIRMATION_SIGN,
   1242                             ec_confirmation_sign);
   1243       return;
   1244     }
   1245 
   1246     finish_loop (wc,
   1247                  TALER_MHD_REPLY_JSON_PACK (
   1248                    wc->rc->connection,
   1249                    MHD_HTTP_CREATED,
   1250                    GNUNET_JSON_pack_uint64 ("noreveal_index",
   1251                                             db_obj->noreveal_index),
   1252                    GNUNET_JSON_pack_data_auto ("exchange_sig",
   1253                                                &sig),
   1254                    GNUNET_JSON_pack_data_auto ("exchange_pub",
   1255                                                &pub)));
   1256   }
   1257   else /* not age restricted */
   1258   {
   1259     json_t *sigs;
   1260 
   1261     sigs = json_array ();
   1262     GNUNET_assert (NULL != sigs);
   1263     for (unsigned int i = 0; i<db_obj->num_coins; i++)
   1264     {
   1265       GNUNET_assert (
   1266         0 ==
   1267         json_array_append_new (
   1268           sigs,
   1269           GNUNET_JSON_PACK (
   1270             TALER_JSON_pack_blinded_denom_sig (
   1271               NULL,
   1272               &db_obj->denom_sigs[i]))));
   1273     }
   1274     finish_loop (wc,
   1275                  TALER_MHD_REPLY_JSON_PACK (
   1276                    wc->rc->connection,
   1277                    MHD_HTTP_OK,
   1278                    GNUNET_JSON_pack_array_steal ("ev_sigs",
   1279                                                  sigs)));
   1280   }
   1281 
   1282   TEH_METRICS_withdraw_num_coins += db_obj->num_coins;
   1283 }
   1284 
   1285 
   1286 /**
   1287  * Reports an error, potentially with details.
   1288  * That is, it puts a error-type specific response into the MHD queue.
   1289  * It will do a idempotency check first, if needed for the error type.
   1290  *
   1291  * @param wc withdraw context
   1292  */
   1293 static void
   1294 phase_generate_reply_error (
   1295   struct WithdrawContext *wc)
   1296 {
   1297   GNUNET_assert (WITHDRAW_PHASE_GENERATE_REPLY_ERROR == wc->phase);
   1298   if (IDEMPOTENCY_CHECK_REQUIRED (wc->error.code) &&
   1299       withdraw_is_idempotent (wc))
   1300   {
   1301     return;
   1302   }
   1303 
   1304   switch (wc->error.code)
   1305   {
   1306   case WITHDRAW_ERROR_NONE:
   1307     break;
   1308   case WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED:
   1309     finish_loop (wc,
   1310                  TALER_MHD_reply_with_error (
   1311                    wc->rc->connection,
   1312                    MHD_HTTP_BAD_REQUEST,
   1313                    TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1314                    wc->error.details.request_parameter_malformed));
   1315     return;
   1316   case WITHDRAW_ERROR_KEYS_MISSING:
   1317     finish_loop (wc,
   1318                  TALER_MHD_reply_with_error (
   1319                    wc->rc->connection,
   1320                    MHD_HTTP_SERVICE_UNAVAILABLE,
   1321                    TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
   1322                    NULL));
   1323     return;
   1324   case WITHDRAW_ERROR_DB_FETCH_FAILED:
   1325     finish_loop (wc,
   1326                  TALER_MHD_reply_with_error (
   1327                    wc->rc->connection,
   1328                    MHD_HTTP_INTERNAL_SERVER_ERROR,
   1329                    TALER_EC_GENERIC_DB_FETCH_FAILED,
   1330                    wc->error.details.db_fetch_context));
   1331     return;
   1332   case WITHDRAW_ERROR_DB_INVARIANT_FAILURE:
   1333     finish_loop (wc,
   1334                  TALER_MHD_reply_with_error (
   1335                    wc->rc->connection,
   1336                    MHD_HTTP_INTERNAL_SERVER_ERROR,
   1337                    TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
   1338                    NULL));
   1339     return;
   1340   case WITHDRAW_ERROR_RESERVE_UNKNOWN:
   1341     finish_loop (wc,
   1342                  TALER_MHD_reply_with_error (
   1343                    wc->rc->connection,
   1344                    MHD_HTTP_NOT_FOUND,
   1345                    TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
   1346                    NULL));
   1347     return;
   1348   case WITHDRAW_ERROR_DENOMINATION_SIGN:
   1349     finish_loop (wc,
   1350                  TALER_MHD_reply_with_ec (
   1351                    wc->rc->connection,
   1352                    wc->error.details.ec_denomination_sign,
   1353                    NULL));
   1354     return;
   1355   case WITHDRAW_ERROR_KYC_REQUIRED:
   1356     finish_loop (wc,
   1357                  TEH_RESPONSE_reply_kyc_required (
   1358                    wc->rc->connection,
   1359                    &wc->h_normalized_payto,
   1360                    &wc->kyc,
   1361                    false));
   1362     return;
   1363   case WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN:
   1364     GNUNET_break_op (0);
   1365     finish_loop (wc,
   1366                  TEH_RESPONSE_reply_unknown_denom_pub_hash (
   1367                    wc->rc->connection,
   1368                    wc->error.details.denom_h));
   1369     return;
   1370   case WITHDRAW_ERROR_DENOMINATION_EXPIRED:
   1371     GNUNET_break_op (0);
   1372     finish_loop (wc,
   1373                  TEH_RESPONSE_reply_expired_denom_pub_hash (
   1374                    wc->rc->connection,
   1375                    wc->error.details.denom_h,
   1376                    TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
   1377                    "WITHDRAW"));
   1378     return;
   1379   case WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE:
   1380     finish_loop (wc,
   1381                  TEH_RESPONSE_reply_expired_denom_pub_hash (
   1382                    wc->rc->connection,
   1383                    wc->error.details.denom_h,
   1384                    TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
   1385                    "WITHDRAW"));
   1386     return;
   1387   case WITHDRAW_ERROR_DENOMINATION_REVOKED:
   1388     GNUNET_break_op (0);
   1389     finish_loop (wc,
   1390                  TALER_MHD_reply_with_error (
   1391                    wc->rc->connection,
   1392                    MHD_HTTP_GONE,
   1393                    TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
   1394                    NULL));
   1395     return;
   1396   case WITHDRAW_ERROR_CIPHER_MISMATCH:
   1397     finish_loop (wc,
   1398                  TALER_MHD_reply_with_error (
   1399                    wc->rc->connection,
   1400                    MHD_HTTP_BAD_REQUEST,
   1401                    TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
   1402                    NULL));
   1403     return;
   1404   case WITHDRAW_ERROR_BLINDING_SEED_REQUIRED:
   1405     finish_loop (wc,
   1406                  TALER_MHD_reply_with_error (
   1407                    wc->rc->connection,
   1408                    MHD_HTTP_BAD_REQUEST,
   1409                    TALER_EC_GENERIC_PARAMETER_MISSING,
   1410                    "blinding_seed"));
   1411     return;
   1412   case WITHDRAW_ERROR_CRYPTO_HELPER:
   1413     finish_loop (wc,
   1414                  TALER_MHD_reply_with_error (
   1415                    wc->rc->connection,
   1416                    MHD_HTTP_INTERNAL_SERVER_ERROR,
   1417                    TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
   1418                    NULL));
   1419     return;
   1420   case WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN:
   1421     finish_loop (wc,
   1422                  TALER_MHD_reply_with_error (
   1423                    wc->rc->connection,
   1424                    MHD_HTTP_BAD_REQUEST,
   1425                    TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
   1426                    "cipher"));
   1427     return;
   1428   case WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION:
   1429     {
   1430       char msg[256];
   1431 
   1432       GNUNET_snprintf (msg,
   1433                        sizeof(msg),
   1434                        "denomination %s does not support age restriction",
   1435                        GNUNET_h2s (&wc->error.details.denom_h->hash));
   1436       finish_loop (wc,
   1437                    TALER_MHD_reply_with_error (
   1438                      wc->rc->connection,
   1439                      MHD_HTTP_NOT_FOUND,
   1440                      TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
   1441                      msg));
   1442       return;
   1443     }
   1444   case WITHDRAW_ERROR_AGE_MASK_MISMATCH:
   1445     {
   1446       char msg[256];
   1447 
   1448       GNUNET_snprintf (msg,
   1449                        sizeof(msg),
   1450                        "age mask of denomination %s differs from that of the first denomination",
   1451                        GNUNET_h2s (&wc->error.details.denom_h->hash));
   1452       finish_loop (wc,
   1453                    TALER_MHD_reply_with_error (
   1454                      wc->rc->connection,
   1455                      MHD_HTTP_BAD_REQUEST,
   1456                      TALER_EC_EXCHANGE_WITHDRAW_AGE_MASK_MISMATCH,
   1457                      msg));
   1458       return;
   1459     }
   1460   case WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE:
   1461     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1462                 "Generating JSON response with code %d\n",
   1463                 (int) TALER_EC_EXCHANGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE);
   1464     finish_loop (wc,
   1465                  TALER_MHD_REPLY_JSON_PACK (
   1466                    wc->rc->connection,
   1467                    MHD_HTTP_CONFLICT,
   1468                    TALER_MHD_PACK_EC (
   1469                      TALER_EC_EXCHANGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE),
   1470                    GNUNET_JSON_pack_uint64 (
   1471                      "allowed_maximum_age",
   1472                      wc->error.details.maximum_age_too_large.max_allowed),
   1473                    GNUNET_JSON_pack_uint64 (
   1474                      "reserve_birthday",
   1475                      wc->error.details.maximum_age_too_large.birthday)));
   1476     return;
   1477   case WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED:
   1478     finish_loop (wc,
   1479                  TEH_RESPONSE_reply_reserve_age_restriction_required (
   1480                    wc->rc->connection,
   1481                    wc->error.details.age_restriction_required));
   1482     return;
   1483   case WITHDRAW_ERROR_AMOUNT_OVERFLOW:
   1484     finish_loop (wc,
   1485                  TALER_MHD_reply_with_error (
   1486                    wc->rc->connection,
   1487                    MHD_HTTP_BAD_REQUEST,
   1488                    TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_OVERFLOW,
   1489                    "amount"));
   1490     return;
   1491   case WITHDRAW_ERROR_FEE_OVERFLOW:
   1492     finish_loop (wc,
   1493                  TALER_MHD_reply_with_error (
   1494                    wc->rc->connection,
   1495                    MHD_HTTP_BAD_REQUEST,
   1496                    TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_OVERFLOW,
   1497                    "fee"));
   1498     return;
   1499   case WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW:
   1500     finish_loop (wc,
   1501                  TALER_MHD_reply_with_error (
   1502                    wc->rc->connection,
   1503                    MHD_HTTP_INTERNAL_SERVER_ERROR,
   1504                    TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW,
   1505                    "amount+fee"));
   1506     return;
   1507   case WITHDRAW_ERROR_CONFIRMATION_SIGN:
   1508     finish_loop (wc,
   1509                  TALER_MHD_reply_with_ec (
   1510                    wc->rc->connection,
   1511                    wc->error.details.ec_confirmation_sign,
   1512                    NULL));
   1513     return;
   1514   case WITHDRAW_ERROR_INSUFFICIENT_FUNDS:
   1515     finish_loop (wc,
   1516                  TEH_RESPONSE_reply_reserve_insufficient_balance (
   1517                    wc->rc->connection,
   1518                    TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS,
   1519                    &wc->error.details.insufficient_funds,
   1520                    &wc->request.withdraw.amount_with_fee,
   1521                    &wc->request.withdraw.reserve_pub));
   1522     return;
   1523   case WITHDRAW_ERROR_IDEMPOTENT_PLANCHET:
   1524     finish_loop (wc,
   1525                  TALER_MHD_reply_with_error (
   1526                    wc->rc->connection,
   1527                    MHD_HTTP_BAD_REQUEST,
   1528                    TALER_EC_EXCHANGE_WITHDRAW_IDEMPOTENT_PLANCHET,
   1529                    NULL));
   1530     return;
   1531   case WITHDRAW_ERROR_NONCE_REUSE:
   1532     finish_loop (wc,
   1533                  TALER_MHD_reply_with_error (
   1534                    wc->rc->connection,
   1535                    MHD_HTTP_CONFLICT,
   1536                    TALER_EC_EXCHANGE_WITHDRAW_NONCE_REUSE,
   1537                    NULL));
   1538     return;
   1539   case WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID:
   1540     finish_loop (wc,
   1541                  TALER_MHD_reply_with_error (
   1542                    wc->rc->connection,
   1543                    MHD_HTTP_FORBIDDEN,
   1544                    TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID,
   1545                    NULL));
   1546     return;
   1547   case WITHDRAW_ERROR_LEGITIMIZATION_RESULT: {
   1548       finish_loop (
   1549         wc,
   1550         MHD_queue_response (wc->rc->connection,
   1551                             wc->error.details.legitimization_result.http_status,
   1552                             wc->error.details.legitimization_result.response));
   1553       return;
   1554     }
   1555   }
   1556   GNUNET_break (0);
   1557   finish_loop (wc,
   1558                TALER_MHD_reply_with_error (
   1559                  wc->rc->connection,
   1560                  MHD_HTTP_INTERNAL_SERVER_ERROR,
   1561                  TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
   1562                  "error phase without error"));
   1563 }
   1564 
   1565 
   1566 /**
   1567  * Initializes the new context for the incoming withdraw request
   1568  *
   1569  * @param[in,out] wc withdraw request context
   1570  * @param root json body of the request
   1571  */
   1572 static void
   1573 withdraw_phase_parse (
   1574   struct WithdrawContext *wc,
   1575   const json_t *root)
   1576 {
   1577   const json_t *j_denoms_h;
   1578   const json_t *j_coin_evs;
   1579   const char *cipher;
   1580   bool no_max_age;
   1581   struct GNUNET_JSON_Specification spec[] = {
   1582     GNUNET_JSON_spec_string ("cipher",
   1583                              &cipher),
   1584     GNUNET_JSON_spec_fixed_auto  ("reserve_pub",
   1585                                   &wc->request.withdraw.reserve_pub),
   1586     GNUNET_JSON_spec_array_const ("denoms_h",
   1587                                   &j_denoms_h),
   1588     GNUNET_JSON_spec_array_const ("coin_evs",
   1589                                   &j_coin_evs),
   1590     GNUNET_JSON_spec_mark_optional (
   1591       GNUNET_JSON_spec_uint16 ("max_age",
   1592                                &wc->request.withdraw.max_age),
   1593       &no_max_age),
   1594     GNUNET_JSON_spec_mark_optional (
   1595       GNUNET_JSON_spec_fixed_auto ("blinding_seed",
   1596                                    &wc->request.withdraw.blinding_seed),
   1597       &wc->request.withdraw.no_blinding_seed),
   1598     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
   1599                                  &wc->request.withdraw.reserve_sig),
   1600     GNUNET_JSON_spec_end ()
   1601   };
   1602   enum GNUNET_GenericReturnValue res;
   1603 
   1604   res = TALER_MHD_parse_json_data (wc->rc->connection,
   1605                                    root,
   1606                                    spec);
   1607   if (GNUNET_YES != res)
   1608   {
   1609     GNUNET_break_op (0);
   1610     wc->phase = (GNUNET_SYSERR == res)
   1611       ? WITHDRAW_PHASE_RETURN_NO
   1612       : WITHDRAW_PHASE_RETURN_YES;
   1613     return;
   1614   }
   1615 
   1616   /* For now, we only support cipher "ED25519" for signatures by the reserve */
   1617   if (0 != strcmp ("ED25519",
   1618                    cipher))
   1619   {
   1620     GNUNET_break_op (0);
   1621     SET_ERROR_WITH_DETAIL (wc,
   1622                            WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN,
   1623                            reserve_cipher_unknown,
   1624                            cipher);
   1625     return;
   1626   }
   1627 
   1628   wc->request.withdraw.age_proof_required = ! no_max_age;
   1629 
   1630   if (wc->request.withdraw.age_proof_required)
   1631   {
   1632     /* The age value MUST be on the beginning of an age group */
   1633     if (wc->request.withdraw.max_age !=
   1634         TALER_get_lowest_age (&TEH_age_restriction_mask,
   1635                               wc->request.withdraw.max_age))
   1636     {
   1637       GNUNET_break_op (0);
   1638       SET_ERROR_WITH_DETAIL (
   1639         wc,
   1640         WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED,
   1641         request_parameter_malformed,
   1642         "max_age must be the lower edge of an age group");
   1643       return;
   1644     }
   1645   }
   1646 
   1647   /* validate array size */
   1648   {
   1649     size_t num_coins = json_array_size (j_denoms_h);
   1650     size_t array_size = json_array_size (j_coin_evs);
   1651     const char *error;
   1652 
   1653     GNUNET_static_assert (
   1654       TALER_MAX_COINS < INT_MAX / TALER_CNC_KAPPA);
   1655 
   1656 #define BAIL_IF(cond, msg) \
   1657         if ((cond)) { \
   1658           GNUNET_break_op (0); \
   1659           error = (msg); break; \
   1660         }
   1661 
   1662     do {
   1663       BAIL_IF (0 == num_coins,
   1664                "denoms_h must not be empty")
   1665 
   1666       /**
   1667        * The wallet had committed to more than the maximum coins allowed, the
   1668        * reserve has been charged, but now the user can not withdraw any money
   1669        * from it.  Note that the user can't get their money back in this case!
   1670        */
   1671       BAIL_IF (num_coins > TALER_MAX_COINS,
   1672                "maximum number of coins that can be withdrawn has been exceeded")
   1673 
   1674       BAIL_IF ((! wc->request.withdraw.age_proof_required) &&
   1675                (num_coins != array_size),
   1676                "denoms_h and coin_evs must be arrays of the same size")
   1677 
   1678       BAIL_IF (wc->request.withdraw.age_proof_required &&
   1679                ((TALER_CNC_KAPPA * num_coins) != array_size),
   1680                "coin_evs must be an array of length "
   1681                TALER_CNC_KAPPA_STR
   1682                "*len(denoms_h)")
   1683 
   1684       wc->request.withdraw.num_coins = num_coins;
   1685       wc->request.num_planchets = array_size;
   1686       error = NULL;
   1687 
   1688     } while (0);
   1689 #undef BAIL_IF
   1690 
   1691     if (NULL != error)
   1692     {
   1693       SET_ERROR_WITH_DETAIL (wc,
   1694                              WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED,
   1695                              request_parameter_malformed,
   1696                              error);
   1697       return;
   1698     }
   1699   }
   1700   /* extract the denomination hashes */
   1701   {
   1702     size_t idx;
   1703     json_t *value;
   1704 
   1705     wc->request.denoms_h
   1706       = GNUNET_new_array (wc->request.withdraw.num_coins,
   1707                           struct TALER_DenominationHashP);
   1708 
   1709     json_array_foreach (j_denoms_h, idx, value) {
   1710       struct GNUNET_JSON_Specification ispec[] = {
   1711         GNUNET_JSON_spec_fixed_auto (NULL,
   1712                                      &wc->request.denoms_h[idx]),
   1713         GNUNET_JSON_spec_end ()
   1714       };
   1715 
   1716       res = TALER_MHD_parse_json_data (wc->rc->connection,
   1717                                        value,
   1718                                        ispec);
   1719       if (GNUNET_YES != res)
   1720       {
   1721         GNUNET_break_op (0);
   1722         wc->phase = (GNUNET_SYSERR == res)
   1723           ? WITHDRAW_PHASE_RETURN_NO
   1724           : WITHDRAW_PHASE_RETURN_YES;
   1725         return;
   1726       }
   1727     }
   1728   }
   1729   /* Parse the blinded coin envelopes */
   1730   {
   1731     json_t *j_cev;
   1732     size_t idx;
   1733 
   1734     wc->request.planchets =
   1735       GNUNET_new_array (wc->request.num_planchets,
   1736                         struct  TALER_BlindedPlanchet);
   1737     json_array_foreach (j_coin_evs, idx, j_cev)
   1738     {
   1739       /* Now parse the individual envelopes and calculate the hash of
   1740        * the commitment along the way. */
   1741       struct GNUNET_JSON_Specification kspec[] = {
   1742         TALER_JSON_spec_blinded_planchet (NULL,
   1743                                           &wc->request.planchets[idx]),
   1744         GNUNET_JSON_spec_end ()
   1745       };
   1746 
   1747       res = TALER_MHD_parse_json_data (wc->rc->connection,
   1748                                        j_cev,
   1749                                        kspec);
   1750       if (GNUNET_OK != res)
   1751       {
   1752         GNUNET_break_op (0);
   1753         wc->phase = (GNUNET_SYSERR == res)
   1754           ? WITHDRAW_PHASE_RETURN_NO
   1755           : WITHDRAW_PHASE_RETURN_YES;
   1756         return;
   1757       }
   1758 
   1759       /* Check for duplicate planchets. Technically a bug on
   1760        * the client side that is harmless for us, but still
   1761        * not allowed per protocol */
   1762       for (size_t i = 0; i < idx; i++)
   1763       {
   1764         if (0 ==
   1765             TALER_blinded_planchet_cmp (
   1766               &wc->request.planchets[idx],
   1767               &wc->request.planchets[i]))
   1768         {
   1769           GNUNET_break_op (0);
   1770           SET_ERROR (wc,
   1771                      WITHDRAW_ERROR_IDEMPOTENT_PLANCHET);
   1772           return;
   1773         }
   1774       }       /* end duplicate check */
   1775     }       /* json_array_foreach over j_coin_evs */
   1776   }       /* scope of j_kappa_planchets, idx */
   1777 
   1778   /* Save the hash of the batch of planchets.  Needed for idempotency
   1779      checks. */
   1780   if (! wc->request.withdraw.age_proof_required)
   1781   {
   1782     TALER_wallet_blinded_planchets_hash (
   1783       wc->request.withdraw.num_coins,
   1784       wc->request.planchets,
   1785       wc->request.denoms_h,
   1786       &wc->request.withdraw.planchets_h);
   1787   }
   1788   else
   1789   {
   1790     struct GNUNET_HashContext *ctx;
   1791 
   1792     /**
   1793      * The age-proof-required case is a bit more involved,
   1794      * because we need to calculate and remember kappa hashes
   1795      * for each batch of coins.
   1796      */
   1797     ctx = GNUNET_CRYPTO_hash_context_start ();
   1798     GNUNET_assert (NULL != ctx);
   1799 
   1800     for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
   1801     {
   1802       size_t off = k * wc->request.withdraw.num_coins;
   1803 
   1804       TALER_wallet_blinded_planchets_hash (
   1805         wc->request.withdraw.num_coins,
   1806         &wc->request.planchets[off],
   1807         wc->request.denoms_h,
   1808         &wc->request.kappa_planchets_h[k]);
   1809       GNUNET_CRYPTO_hash_context_read (
   1810         ctx,
   1811         &wc->request.kappa_planchets_h[k],
   1812         sizeof(wc->request.kappa_planchets_h[k]));
   1813     }
   1814     GNUNET_CRYPTO_hash_context_finish (
   1815       ctx,
   1816       &wc->request.withdraw.planchets_h.hash);
   1817   }
   1818   wc->phase = WITHDRAW_PHASE_CHECK_KEYS;
   1819 }
   1820 
   1821 
   1822 enum MHD_Result
   1823 TEH_handler_withdraw (
   1824   struct TEH_RequestContext *rc,
   1825   const json_t *root,
   1826   const char *const args[0])
   1827 {
   1828   struct WithdrawContext *wc = rc->rh_ctx;
   1829 
   1830   (void) args;
   1831   if (NULL == wc)
   1832   {
   1833     wc = GNUNET_new (struct WithdrawContext);
   1834     rc->rh_ctx = wc;
   1835     rc->rh_cleaner = &clean_withdraw_rc;
   1836     wc->rc = rc;
   1837     wc->now = GNUNET_TIME_timestamp_get ();
   1838   }
   1839   while (true)
   1840   {
   1841     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1842                 "withdrawal%s processing in phase %d\n",
   1843                 wc->request.withdraw.age_proof_required
   1844                      ? " (with required age proof)"
   1845                      : "",
   1846                 wc->phase);
   1847     switch (wc->phase)
   1848     {
   1849     case WITHDRAW_PHASE_PARSE:
   1850       withdraw_phase_parse (wc,
   1851                             root);
   1852       break;
   1853     case WITHDRAW_PHASE_CHECK_KEYS:
   1854       phase_check_keys (wc);
   1855       break;
   1856     case WITHDRAW_PHASE_CHECK_RESERVE_SIGNATURE:
   1857       phase_check_reserve_signature (wc);
   1858       break;
   1859     case WITHDRAW_PHASE_RUN_LEGI_CHECK:
   1860       phase_run_legi_check (wc);
   1861       break;
   1862     case WITHDRAW_PHASE_SUSPENDED:
   1863       return MHD_YES;
   1864     case WITHDRAW_PHASE_CHECK_KYC_RESULT:
   1865       phase_check_kyc_result (wc);
   1866       break;
   1867     case WITHDRAW_PHASE_PREPARE_TRANSACTION:
   1868       phase_prepare_transaction (wc);
   1869       break;
   1870     case WITHDRAW_PHASE_RUN_TRANSACTION:
   1871       phase_run_transaction (wc);
   1872       break;
   1873     case WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS:
   1874       phase_generate_reply_success (wc);
   1875       break;
   1876     case WITHDRAW_PHASE_GENERATE_REPLY_ERROR:
   1877       phase_generate_reply_error (wc);
   1878       break;
   1879     case WITHDRAW_PHASE_RETURN_YES:
   1880       return MHD_YES;
   1881     case WITHDRAW_PHASE_RETURN_NO:
   1882       return MHD_NO;
   1883     }
   1884   }
   1885 }