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


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