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 (55526B)


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