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


      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_INTERNAL_SERVER_ERROR,
   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_ec (
   1341                    wc->rc->connection,
   1342                    TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
   1343                    NULL));
   1344     return;
   1345   case WITHDRAW_ERROR_DENOMINATION_SIGN:
   1346     finish_loop (wc,
   1347                  TALER_MHD_reply_with_ec (
   1348                    wc->rc->connection,
   1349                    wc->error.details.ec_denomination_sign,
   1350                    NULL));
   1351     return;
   1352   case WITHDRAW_ERROR_KYC_REQUIRED:
   1353     finish_loop (wc,
   1354                  TEH_RESPONSE_reply_kyc_required (
   1355                    wc->rc->connection,
   1356                    &wc->h_normalized_payto,
   1357                    &wc->kyc,
   1358                    false));
   1359     return;
   1360   case WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN:
   1361     GNUNET_break_op (0);
   1362     finish_loop (wc,
   1363                  TEH_RESPONSE_reply_unknown_denom_pub_hash (
   1364                    wc->rc->connection,
   1365                    wc->error.details.denom_h));
   1366     return;
   1367   case WITHDRAW_ERROR_DENOMINATION_EXPIRED:
   1368     GNUNET_break_op (0);
   1369     finish_loop (wc,
   1370                  TEH_RESPONSE_reply_expired_denom_pub_hash (
   1371                    wc->rc->connection,
   1372                    wc->error.details.denom_h,
   1373                    TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
   1374                    "WITHDRAW"));
   1375     return;
   1376   case WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE:
   1377     finish_loop (wc,
   1378                  TEH_RESPONSE_reply_expired_denom_pub_hash (
   1379                    wc->rc->connection,
   1380                    wc->error.details.denom_h,
   1381                    TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
   1382                    "WITHDRAW"));
   1383     return;
   1384   case WITHDRAW_ERROR_DENOMINATION_REVOKED:
   1385     GNUNET_break_op (0);
   1386     finish_loop (wc,
   1387                  TALER_MHD_reply_with_ec (
   1388                    wc->rc->connection,
   1389                    TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
   1390                    NULL));
   1391     return;
   1392   case WITHDRAW_ERROR_CIPHER_MISMATCH:
   1393     finish_loop (wc,
   1394                  TALER_MHD_reply_with_ec (
   1395                    wc->rc->connection,
   1396                    TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
   1397                    NULL));
   1398     return;
   1399   case WITHDRAW_ERROR_BLINDING_SEED_REQUIRED:
   1400     finish_loop (wc,
   1401                  TALER_MHD_reply_with_ec (
   1402                    wc->rc->connection,
   1403                    TALER_EC_GENERIC_PARAMETER_MISSING,
   1404                    "blinding_seed"));
   1405     return;
   1406   case WITHDRAW_ERROR_CRYPTO_HELPER:
   1407     finish_loop (wc,
   1408                  TALER_MHD_reply_with_ec (
   1409                    wc->rc->connection,
   1410                    TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
   1411                    NULL));
   1412     return;
   1413   case WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN:
   1414     finish_loop (wc,
   1415                  TALER_MHD_reply_with_ec (
   1416                    wc->rc->connection,
   1417                    TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
   1418                    "cipher"));
   1419     return;
   1420   case WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION:
   1421     {
   1422       char msg[256];
   1423 
   1424       GNUNET_snprintf (msg,
   1425                        sizeof(msg),
   1426                        "denomination %s does not support age restriction",
   1427                        GNUNET_h2s (&wc->error.details.denom_h->hash));
   1428       finish_loop (wc,
   1429                    TALER_MHD_reply_with_ec (
   1430                      wc->rc->connection,
   1431                      TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
   1432                      msg));
   1433       return;
   1434     }
   1435   case WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE:
   1436     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1437                 "Generating JSON response with code %d\n",
   1438                 (int) TALER_EC_EXCHANGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE);
   1439     finish_loop (wc,
   1440                  TALER_MHD_REPLY_JSON_PACK (
   1441                    wc->rc->connection,
   1442                    MHD_HTTP_CONFLICT,
   1443                    TALER_MHD_PACK_EC (
   1444                      TALER_EC_EXCHANGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE),
   1445                    GNUNET_JSON_pack_uint64 (
   1446                      "allowed_maximum_age",
   1447                      wc->error.details.maximum_age_too_large.max_allowed),
   1448                    GNUNET_JSON_pack_uint64 (
   1449                      "reserve_birthday",
   1450                      wc->error.details.maximum_age_too_large.birthday)));
   1451     return;
   1452   case WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED:
   1453     finish_loop (wc,
   1454                  TEH_RESPONSE_reply_reserve_age_restriction_required (
   1455                    wc->rc->connection,
   1456                    wc->error.details.age_restriction_required));
   1457     return;
   1458   case WITHDRAW_ERROR_AMOUNT_OVERFLOW:
   1459     finish_loop (wc,
   1460                  TALER_MHD_reply_with_error (
   1461                    wc->rc->connection,
   1462                    MHD_HTTP_BAD_REQUEST,
   1463                    TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_OVERFLOW,
   1464                    "amount"));
   1465     return;
   1466   case WITHDRAW_ERROR_FEE_OVERFLOW:
   1467     finish_loop (wc,
   1468                  TALER_MHD_reply_with_error (
   1469                    wc->rc->connection,
   1470                    MHD_HTTP_BAD_REQUEST,
   1471                    TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_OVERFLOW,
   1472                    "fee"));
   1473     return;
   1474   case WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW:
   1475     finish_loop (wc,
   1476                  TALER_MHD_reply_with_error (
   1477                    wc->rc->connection,
   1478                    MHD_HTTP_INTERNAL_SERVER_ERROR,
   1479                    TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW,
   1480                    "amount+fee"));
   1481     return;
   1482   case WITHDRAW_ERROR_CONFIRMATION_SIGN:
   1483     finish_loop (wc,
   1484                  TALER_MHD_reply_with_ec (
   1485                    wc->rc->connection,
   1486                    wc->error.details.ec_confirmation_sign,
   1487                    NULL));
   1488     return;
   1489   case WITHDRAW_ERROR_INSUFFICIENT_FUNDS:
   1490     finish_loop (wc,
   1491                  TEH_RESPONSE_reply_reserve_insufficient_balance (
   1492                    wc->rc->connection,
   1493                    TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS,
   1494                    &wc->error.details.insufficient_funds,
   1495                    &wc->request.withdraw.amount_with_fee,
   1496                    &wc->request.withdraw.reserve_pub));
   1497     return;
   1498   case WITHDRAW_ERROR_IDEMPOTENT_PLANCHET:
   1499     finish_loop (wc,
   1500                  TALER_MHD_reply_with_error (
   1501                    wc->rc->connection,
   1502                    MHD_HTTP_BAD_REQUEST,
   1503                    TALER_EC_EXCHANGE_WITHDRAW_IDEMPOTENT_PLANCHET,
   1504                    NULL));
   1505     return;
   1506   case WITHDRAW_ERROR_NONCE_REUSE:
   1507     finish_loop (wc,
   1508                  TALER_MHD_reply_with_error (
   1509                    wc->rc->connection,
   1510                    MHD_HTTP_CONFLICT,
   1511                    TALER_EC_EXCHANGE_WITHDRAW_NONCE_REUSE,
   1512                    NULL));
   1513     return;
   1514   case WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID:
   1515     finish_loop (wc,
   1516                  TALER_MHD_reply_with_ec (
   1517                    wc->rc->connection,
   1518                    TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID,
   1519                    NULL));
   1520     return;
   1521   case WITHDRAW_ERROR_LEGITIMIZATION_RESULT: {
   1522       finish_loop (
   1523         wc,
   1524         MHD_queue_response (wc->rc->connection,
   1525                             wc->error.details.legitimization_result.http_status,
   1526                             wc->error.details.legitimization_result.response));
   1527       return;
   1528     }
   1529   }
   1530   GNUNET_break (0);
   1531   finish_loop (wc,
   1532                TALER_MHD_reply_with_error (
   1533                  wc->rc->connection,
   1534                  MHD_HTTP_INTERNAL_SERVER_ERROR,
   1535                  TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
   1536                  "error phase without error"));
   1537 }
   1538 
   1539 
   1540 /**
   1541  * Initializes the new context for the incoming withdraw request
   1542  *
   1543  * @param[in,out] wc withdraw request context
   1544  * @param root json body of the request
   1545  */
   1546 static void
   1547 withdraw_phase_parse (
   1548   struct WithdrawContext *wc,
   1549   const json_t *root)
   1550 {
   1551   const json_t *j_denoms_h;
   1552   const json_t *j_coin_evs;
   1553   const char *cipher;
   1554   bool no_max_age;
   1555   struct GNUNET_JSON_Specification spec[] = {
   1556     GNUNET_JSON_spec_string ("cipher",
   1557                              &cipher),
   1558     GNUNET_JSON_spec_fixed_auto  ("reserve_pub",
   1559                                   &wc->request.withdraw.reserve_pub),
   1560     GNUNET_JSON_spec_array_const ("denoms_h",
   1561                                   &j_denoms_h),
   1562     GNUNET_JSON_spec_array_const ("coin_evs",
   1563                                   &j_coin_evs),
   1564     GNUNET_JSON_spec_mark_optional (
   1565       GNUNET_JSON_spec_uint16 ("max_age",
   1566                                &wc->request.withdraw.max_age),
   1567       &no_max_age),
   1568     GNUNET_JSON_spec_mark_optional (
   1569       GNUNET_JSON_spec_fixed_auto ("blinding_seed",
   1570                                    &wc->request.withdraw.blinding_seed),
   1571       &wc->request.withdraw.no_blinding_seed),
   1572     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
   1573                                  &wc->request.withdraw.reserve_sig),
   1574     GNUNET_JSON_spec_end ()
   1575   };
   1576   enum GNUNET_GenericReturnValue res;
   1577 
   1578   res = TALER_MHD_parse_json_data (wc->rc->connection,
   1579                                    root,
   1580                                    spec);
   1581   if (GNUNET_YES != res)
   1582   {
   1583     GNUNET_break_op (0);
   1584     wc->phase = (GNUNET_SYSERR == res)
   1585       ? WITHDRAW_PHASE_RETURN_NO
   1586       : WITHDRAW_PHASE_RETURN_YES;
   1587     return;
   1588   }
   1589 
   1590   /* For now, we only support cipher "ED25519" for signatures by the reserve */
   1591   if (0 != strcmp ("ED25519",
   1592                    cipher))
   1593   {
   1594     GNUNET_break_op (0);
   1595     SET_ERROR_WITH_DETAIL (wc,
   1596                            WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN,
   1597                            reserve_cipher_unknown,
   1598                            cipher);
   1599     return;
   1600   }
   1601 
   1602   wc->request.withdraw.age_proof_required = ! no_max_age;
   1603 
   1604   if (wc->request.withdraw.age_proof_required)
   1605   {
   1606     /* The age value MUST be on the beginning of an age group */
   1607     if (wc->request.withdraw.max_age !=
   1608         TALER_get_lowest_age (&TEH_age_restriction_config.mask,
   1609                               wc->request.withdraw.max_age))
   1610     {
   1611       GNUNET_break_op (0);
   1612       SET_ERROR_WITH_DETAIL (
   1613         wc,
   1614         WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED,
   1615         request_parameter_malformed,
   1616         "max_age must be the lower edge of an age group");
   1617       return;
   1618     }
   1619   }
   1620 
   1621   /* validate array size */
   1622   {
   1623     size_t num_coins = json_array_size (j_denoms_h);
   1624     size_t array_size = json_array_size (j_coin_evs);
   1625     const char *error;
   1626 
   1627     GNUNET_static_assert (
   1628       TALER_MAX_COINS < INT_MAX / TALER_CNC_KAPPA);
   1629 
   1630 #define BAIL_IF(cond, msg) \
   1631         if ((cond)) { \
   1632           GNUNET_break_op (0); \
   1633           error = (msg); break; \
   1634         }
   1635 
   1636     do {
   1637       BAIL_IF (0 == num_coins,
   1638                "denoms_h must not be empty")
   1639 
   1640       /**
   1641          * The wallet had committed to more than the maximum coins allowed, the
   1642          * reserve has been charged, but now the user can not withdraw any money
   1643          * from it.  Note that the user can't get their money back in this case!
   1644          */
   1645       BAIL_IF (num_coins > TALER_MAX_COINS,
   1646                "maximum number of coins that can be withdrawn has been exceeded")
   1647 
   1648       BAIL_IF ((! wc->request.withdraw.age_proof_required) &&
   1649                (num_coins != array_size),
   1650                "denoms_h and coin_evs must be arrays of the same size")
   1651 
   1652       BAIL_IF (wc->request.withdraw.age_proof_required &&
   1653                ((TALER_CNC_KAPPA * num_coins) != array_size),
   1654                "coin_evs must be an array of length "
   1655                TALER_CNC_KAPPA_STR
   1656                "*len(denoms_h)")
   1657 
   1658       wc->request.withdraw.num_coins = num_coins;
   1659       wc->request.num_planchets = array_size;
   1660       error = NULL;
   1661 
   1662     } while (0);
   1663 #undef BAIL_IF
   1664 
   1665     if (NULL != error)
   1666     {
   1667       SET_ERROR_WITH_DETAIL (wc,
   1668                              WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED,
   1669                              request_parameter_malformed,
   1670                              error);
   1671       return;
   1672     }
   1673   }
   1674   /* extract the denomination hashes */
   1675   {
   1676     size_t idx;
   1677     json_t *value;
   1678 
   1679     wc->request.denoms_h
   1680       = GNUNET_new_array (wc->request.withdraw.num_coins,
   1681                           struct TALER_DenominationHashP);
   1682 
   1683     json_array_foreach (j_denoms_h, idx, value) {
   1684       struct GNUNET_JSON_Specification ispec[] = {
   1685         GNUNET_JSON_spec_fixed_auto (NULL,
   1686                                      &wc->request.denoms_h[idx]),
   1687         GNUNET_JSON_spec_end ()
   1688       };
   1689 
   1690       res = TALER_MHD_parse_json_data (wc->rc->connection,
   1691                                        value,
   1692                                        ispec);
   1693       if (GNUNET_YES != res)
   1694       {
   1695         GNUNET_break_op (0);
   1696         wc->phase = (GNUNET_SYSERR == res)
   1697           ? WITHDRAW_PHASE_RETURN_NO
   1698           : WITHDRAW_PHASE_RETURN_YES;
   1699         return;
   1700       }
   1701     }
   1702   }
   1703   /* Parse the blinded coin envelopes */
   1704   {
   1705     json_t *j_cev;
   1706     size_t idx;
   1707 
   1708     wc->request.planchets =
   1709       GNUNET_new_array (wc->request.num_planchets,
   1710                         struct  TALER_BlindedPlanchet);
   1711     json_array_foreach (j_coin_evs, idx, j_cev)
   1712     {
   1713       /* Now parse the individual envelopes and calculate the hash of
   1714        * the commitment along the way. */
   1715       struct GNUNET_JSON_Specification kspec[] = {
   1716         TALER_JSON_spec_blinded_planchet (NULL,
   1717                                           &wc->request.planchets[idx]),
   1718         GNUNET_JSON_spec_end ()
   1719       };
   1720 
   1721       res = TALER_MHD_parse_json_data (wc->rc->connection,
   1722                                        j_cev,
   1723                                        kspec);
   1724       if (GNUNET_OK != res)
   1725       {
   1726         GNUNET_break_op (0);
   1727         wc->phase = (GNUNET_SYSERR == res)
   1728           ? WITHDRAW_PHASE_RETURN_NO
   1729           : WITHDRAW_PHASE_RETURN_YES;
   1730         return;
   1731       }
   1732 
   1733       /* Check for duplicate planchets. Technically a bug on
   1734        * the client side that is harmless for us, but still
   1735        * not allowed per protocol */
   1736       for (size_t i = 0; i < idx; i++)
   1737       {
   1738         if (0 ==
   1739             TALER_blinded_planchet_cmp (
   1740               &wc->request.planchets[idx],
   1741               &wc->request.planchets[i]))
   1742         {
   1743           GNUNET_break_op (0);
   1744           SET_ERROR (wc,
   1745                      WITHDRAW_ERROR_IDEMPOTENT_PLANCHET);
   1746           return;
   1747         }
   1748       }       /* end duplicate check */
   1749     }       /* json_array_foreach over j_coin_evs */
   1750   }       /* scope of j_kappa_planchets, idx */
   1751   wc->phase = WITHDRAW_PHASE_CHECK_KEYS;
   1752 }
   1753 
   1754 
   1755 MHD_RESULT
   1756 TEH_handler_withdraw (
   1757   struct TEH_RequestContext *rc,
   1758   const json_t *root,
   1759   const char *const args[0])
   1760 {
   1761   struct WithdrawContext *wc = rc->rh_ctx;
   1762 
   1763   (void) args;
   1764   if (NULL == wc)
   1765   {
   1766     wc = GNUNET_new (struct WithdrawContext);
   1767     rc->rh_ctx = wc;
   1768     rc->rh_cleaner = &clean_withdraw_rc;
   1769     wc->rc = rc;
   1770     wc->now = GNUNET_TIME_timestamp_get ();
   1771   }
   1772   while (true)
   1773   {
   1774     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1775                 "withdrawal%s processing in phase %d\n",
   1776                 wc->request.withdraw.age_proof_required
   1777                      ? " (with required age proof)"
   1778                      : "",
   1779                 wc->phase);
   1780     switch (wc->phase)
   1781     {
   1782     case WITHDRAW_PHASE_PARSE:
   1783       withdraw_phase_parse (wc,
   1784                             root);
   1785       break;
   1786     case WITHDRAW_PHASE_CHECK_KEYS:
   1787       phase_check_keys (wc);
   1788       break;
   1789     case WITHDRAW_PHASE_CHECK_RESERVE_SIGNATURE:
   1790       phase_check_reserve_signature (wc);
   1791       break;
   1792     case WITHDRAW_PHASE_RUN_LEGI_CHECK:
   1793       phase_run_legi_check (wc);
   1794       break;
   1795     case WITHDRAW_PHASE_SUSPENDED:
   1796       return MHD_YES;
   1797     case WITHDRAW_PHASE_CHECK_KYC_RESULT:
   1798       phase_check_kyc_result (wc);
   1799       break;
   1800     case WITHDRAW_PHASE_PREPARE_TRANSACTION:
   1801       phase_prepare_transaction (wc);
   1802       break;
   1803     case WITHDRAW_PHASE_RUN_TRANSACTION:
   1804       phase_run_transaction (wc);
   1805       break;
   1806     case WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS:
   1807       phase_generate_reply_success (wc);
   1808       break;
   1809     case WITHDRAW_PHASE_GENERATE_REPLY_ERROR:
   1810       phase_generate_reply_error (wc);
   1811       break;
   1812     case WITHDRAW_PHASE_RETURN_YES:
   1813       return MHD_YES;
   1814     case WITHDRAW_PHASE_RETURN_NO:
   1815       return MHD_NO;
   1816     }
   1817   }
   1818 }