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


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