LCOV - code coverage report
Current view: top level - exchange - taler-exchange-httpd_withdraw.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 383 592 64.7 %
Date: 2025-07-09 07:38:29 Functions: 19 19 100.0 %

          Line data    Source code
       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_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_withdraw.h"
      34             : #include "taler-exchange-httpd_common_kyc.h"
      35             : #include "taler-exchange-httpd_responses.h"
      36             : #include "taler-exchange-httpd_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_RESUSE,
      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          21 : TEH_withdraw_cleanup ()
     326             : {
     327             :   struct WithdrawContext *wc;
     328             : 
     329          21 :   while (NULL != (wc = wc_head))
     330             :   {
     331           0 :     GNUNET_CONTAINER_DLL_remove (wc_head,
     332             :                                  wc_tail,
     333             :                                  wc);
     334           0 :     wc->phase = WITHDRAW_PHASE_RETURN_NO;
     335           0 :     MHD_resume_connection (wc->rc->connection);
     336             :   }
     337          21 : }
     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          79 : finish_loop (struct WithdrawContext *wc,
     349             :              MHD_RESULT mres)
     350             : {
     351          79 :   wc->phase = (MHD_YES == mres)
     352             :     ? WITHDRAW_PHASE_RETURN_YES
     353          79 :     : WITHDRAW_PHASE_RETURN_NO;
     354          79 : }
     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           7 : withdraw_is_idempotent (
     368             :   struct WithdrawContext *wc)
     369             : {
     370             :   enum GNUNET_DB_QueryStatus qs;
     371           7 :   uint8_t max_retries = 3;
     372             : 
     373             :   /* We should at most be called once */
     374           7 :   GNUNET_assert (! wc->request.is_idempotent);
     375           7 :   while (0 < max_retries--)
     376             :   {
     377           7 :     qs = TEH_plugin->get_withdraw (
     378           7 :       TEH_plugin->cls,
     379           7 :       &wc->request.withdraw.planchets_h,
     380             :       &wc->request.withdraw_idem);
     381           7 :     if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
     382           7 :       break;
     383             :   }
     384             : 
     385           7 :   if (0 > qs)
     386             :   {
     387           0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     388           0 :     GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
     389           0 :     SET_ERROR_WITH_DETAIL (wc,
     390             :                            WITHDRAW_ERROR_DB_FETCH_FAILED,
     391             :                            db_fetch_context,
     392             :                            "get_withdraw");
     393           0 :     return true; /* Well, kind-of. */
     394             :   }
     395           7 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     396           7 :     return false;
     397             : 
     398           0 :   wc->request.is_idempotent = true;
     399           0 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     400             :               "request is idempotent\n");
     401             : 
     402             :   /* Generate idempotent reply */
     403           0 :   TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW]++;
     404           0 :   wc->phase = WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS;
     405           0 :   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          75 : withdraw_transaction (
     425             :   void *cls,
     426             :   struct MHD_Connection *connection,
     427             :   MHD_RESULT *mhd_ret)
     428             : {
     429          75 :   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          75 :   qs = TEH_plugin->do_withdraw (TEH_plugin->cls,
     441          75 :                                 &wc->request.withdraw,
     442          75 :                                 &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          75 :   if (0 > qs)
     452             :   {
     453           0 :     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     454           0 :       SET_ERROR_WITH_DETAIL (wc,
     455             :                              WITHDRAW_ERROR_DB_FETCH_FAILED,
     456             :                              db_fetch_context,
     457             :                              "do_withdraw");
     458           0 :     return qs;
     459             :   }
     460          75 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     461             :   {
     462           0 :     SET_ERROR (wc,
     463             :                WITHDRAW_ERROR_RESERVE_UNKNOWN);
     464           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     465             :   }
     466             : 
     467          75 :   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           3 :     wc->request.withdraw.noreveal_index = noreveal_index;
     475           3 :     wc->phase = WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS;
     476           3 :     return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
     477             :   }
     478             : 
     479          72 :   if (! age_ok)
     480             :   {
     481           4 :     if (wc->request.withdraw.age_proof_required)
     482             :     {
     483           2 :       wc->error.details.maximum_age_too_large.max_allowed = allowed_maximum_age;
     484           2 :       wc->error.details.maximum_age_too_large.birthday = reserve_birthday;
     485           2 :       SET_ERROR (wc,
     486             :                  WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE);
     487             :     }
     488             :     else
     489             :     {
     490           2 :       wc->error.details.age_restriction_required = allowed_maximum_age;
     491           2 :       SET_ERROR (wc,
     492             :                  WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED);
     493             :     }
     494           4 :     return GNUNET_DB_STATUS_HARD_ERROR;
     495             :   }
     496             : 
     497          68 :   if (! balance_ok)
     498             :   {
     499           3 :     TEH_plugin->rollback (TEH_plugin->cls);
     500           3 :     SET_ERROR_WITH_FIELD (wc,
     501             :                           WITHDRAW_ERROR_INSUFFICIENT_FUNDS,
     502             :                           insufficient_funds);
     503           3 :     return GNUNET_DB_STATUS_HARD_ERROR;
     504             :   }
     505             : 
     506          65 :   if (nonce_reuse)
     507             :   {
     508           0 :     GNUNET_break (0);
     509           0 :     SET_ERROR (wc,
     510             :                WITHDRAW_ERROR_NONCE_RESUSE);
     511           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     512             :   }
     513             : 
     514          65 :   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
     515          65 :     TEH_METRICS_num_success[TEH_MT_SUCCESS_WITHDRAW]++;
     516          65 :   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          75 : phase_run_transaction (
     528             :   struct WithdrawContext *wc)
     529             : {
     530             :   MHD_RESULT mhd_ret;
     531             :   enum GNUNET_GenericReturnValue qs;
     532             : 
     533          75 :   GNUNET_assert (WITHDRAW_PHASE_RUN_TRANSACTION ==
     534             :                  wc->phase);
     535          75 :   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          75 :   if (WITHDRAW_PHASE_RUN_TRANSACTION != wc->phase)
     542          10 :     return;
     543          65 :   GNUNET_break (GNUNET_OK == qs);
     544             :   /* If the transaction has changed the phase, we don't alter it and return.*/
     545          65 :   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          75 : phase_prepare_transaction (
     557             :   struct WithdrawContext *wc)
     558             : {
     559          75 :   size_t offset = 0;
     560             : 
     561             :   wc->request.withdraw.denom_sigs
     562          75 :     = GNUNET_new_array (
     563             :         wc->request.withdraw.num_coins,
     564             :         struct TALER_BlindedDenominationSignature);
     565             :   /* Pick the challenge in case of age restriction  */
     566          75 :   if (wc->request.withdraw.age_proof_required)
     567             :   {
     568           5 :     wc->request.withdraw.noreveal_index =
     569           5 :       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           5 :     offset = wc->request.withdraw.noreveal_index
     577           5 :              * wc->request.withdraw.num_coins;
     578           5 :     GNUNET_assert (offset + wc->request.withdraw.num_coins <=
     579             :                    wc->request.num_planchets);
     580             :   }
     581             : 
     582             :   /* Choose and sign the coins */
     583          75 :   {
     584          75 :     struct TEH_CoinSignData csds[wc->request.withdraw.num_coins];
     585             :     enum TALER_ErrorCode ec_denomination_sign;
     586             : 
     587          75 :     memset (csds,
     588             :             0,
     589             :             sizeof(csds));
     590             : 
     591             :     /* Pick the chosen blinded coins */
     592         204 :     for (uint32_t i = 0; i<wc->request.withdraw.num_coins; i++)
     593             :     {
     594         129 :       csds[i].bp = &wc->request.planchets[i + offset];
     595         129 :       csds[i].h_denom_pub = &wc->request.denoms_h[i];
     596             :     }
     597             : 
     598          75 :     ec_denomination_sign = TEH_keys_denomination_batch_sign (
     599          75 :       wc->request.withdraw.num_coins,
     600             :       csds,
     601             :       false,
     602             :       wc->request.withdraw.denom_sigs);
     603          75 :     if (TALER_EC_NONE != ec_denomination_sign)
     604             :     {
     605           0 :       GNUNET_break (0);
     606           0 :       SET_ERROR_WITH_FIELD (wc,
     607             :                             WITHDRAW_ERROR_DENOMINATION_SIGN,
     608             :                             ec_denomination_sign);
     609           0 :       return;
     610             :     }
     611             : 
     612             :     /* Save the hash value of the selected batch of coins */
     613          75 :     wc->request.withdraw.selected_h =
     614          75 :       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          75 :   if (0 <  wc->request.withdraw.num_cs_r_values)
     624          34 :   {
     625          34 :     size_t num_cs_r_values = wc->request.withdraw.num_cs_r_values;
     626          34 :     struct TEH_CsDeriveData cdds[num_cs_r_values];
     627          34 :     struct GNUNET_CRYPTO_CsSessionNonce nonces[num_cs_r_values];
     628             : 
     629          34 :     memset (nonces,
     630             :             0,
     631             :             sizeof(nonces));
     632             :     wc->request.withdraw.cs_r_values
     633          34 :       = GNUNET_new_array (
     634             :           num_cs_r_values,
     635             :           struct GNUNET_CRYPTO_CSPublicRPairP);
     636          34 :     wc->request.withdraw.cs_r_choices = 0;
     637             : 
     638          34 :     GNUNET_assert (! wc->request.withdraw.no_blinding_seed);
     639          34 :     TALER_cs_derive_nonces_from_seed (
     640          34 :       &wc->request.withdraw.blinding_seed,
     641             :       false,   /* not for melt */
     642             :       num_cs_r_values,
     643          34 :       wc->request.cs_indices,
     644             :       nonces);
     645             : 
     646          71 :     for (size_t i = 0; i < num_cs_r_values; i++)
     647             :     {
     648          37 :       size_t idx = wc->request.cs_indices[i];
     649             : 
     650          37 :       GNUNET_assert (idx < wc->request.withdraw.num_coins);
     651          37 :       cdds[i].h_denom_pub = &wc->request.denoms_h[idx];
     652          37 :       cdds[i].nonce = &nonces[i];
     653             :     }
     654             : 
     655             :     /**
     656             :      * Let the crypto helper generate the R-values and make the choices.
     657             :      */
     658          34 :     if (TALER_EC_NONE !=
     659          34 :         TEH_keys_denomination_cs_batch_r_pub_simple (
     660          34 :           wc->request.withdraw.num_cs_r_values,
     661             :           cdds,
     662             :           false,
     663             :           wc->request.withdraw.cs_r_values))
     664             :     {
     665           0 :       GNUNET_break (0);
     666           0 :       SET_ERROR (wc,
     667             :                  WITHDRAW_ERROR_CRYPTO_HELPER);
     668           0 :       return;
     669             :     }
     670             : 
     671             :     /* Now save the choices for the selected bits */
     672          71 :     for (size_t i = 0; i < num_cs_r_values; i++)
     673             :     {
     674          37 :       size_t idx = wc->request.cs_indices[i];
     675             : 
     676          37 :       struct TALER_BlindedDenominationSignature *sig =
     677          37 :         &wc->request.withdraw.denom_sigs[idx];
     678          37 :       uint8_t bit = sig->blinded_sig->details.blinded_cs_answer.b;
     679             : 
     680          37 :       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          75 :   wc->phase++;
     687             : }
     688             : 
     689             : 
     690             : /**
     691             :  * Check the KYC result.
     692             :  *
     693             :  * @param wc context for request processing
     694             :  */
     695             : static void
     696          79 : phase_check_kyc_result (struct WithdrawContext *wc)
     697             : {
     698             :   /* return final positive response */
     699          79 :   if (! wc->kyc.ok)
     700             :   {
     701           4 :     SET_ERROR (wc,
     702             :                WITHDRAW_ERROR_KYC_REQUIRED);
     703           4 :     return;
     704             :   }
     705          75 :   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          79 : withdraw_legi_cb (
     718             :   void *cls,
     719             :   const struct TEH_LegitimizationCheckResult *lcr)
     720             : {
     721          79 :   struct WithdrawContext *wc = cls;
     722             : 
     723          79 :   wc->lch = NULL;
     724          79 :   GNUNET_assert (WITHDRAW_PHASE_SUSPENDED ==
     725             :                  wc->phase);
     726          79 :   MHD_resume_connection (wc->rc->connection);
     727          79 :   GNUNET_CONTAINER_DLL_remove (wc_head,
     728             :                                wc_tail,
     729             :                                wc);
     730          79 :   TALER_MHD_daemon_trigger ();
     731          79 :   if (NULL != lcr->response)
     732             :   {
     733           0 :     wc->error.details.legitimization_result.response = lcr->response;
     734           0 :     wc->error.details.legitimization_result.http_status = lcr->http_status;
     735           0 :     SET_ERROR (wc,
     736             :                WITHDRAW_ERROR_LEGITIMIZATION_RESULT);
     737           0 :     return;
     738             :   }
     739          79 :   wc->kyc = lcr->kyc;
     740          79 :   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          19 : withdraw_amount_cb (
     760             :   void *cls,
     761             :   struct GNUNET_TIME_Absolute limit,
     762             :   TALER_EXCHANGEDB_KycAmountCallback cb,
     763             :   void *cb_cls)
     764             : {
     765          19 :   struct WithdrawContext *wc = cls;
     766             :   enum GNUNET_GenericReturnValue ret;
     767             :   enum GNUNET_DB_QueryStatus qs;
     768             : 
     769          19 :   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          19 :   ret = cb (cb_cls,
     774          19 :             &wc->request.withdraw.amount_with_fee,
     775             :             wc->now.abs_time);
     776          19 :   GNUNET_break (GNUNET_SYSERR != ret);
     777          19 :   if (GNUNET_OK != ret)
     778           0 :     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
     779             : 
     780          19 :   qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (
     781          19 :     TEH_plugin->cls,
     782          19 :     &wc->h_normalized_payto,
     783             :     limit,
     784             :     cb,
     785             :     cb_cls);
     786          19 :   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          19 :   GNUNET_break (qs >= 0);
     791          19 :   return qs;
     792             : }
     793             : 
     794             : 
     795             : /**
     796             :  * Do legitimization check.
     797             :  *
     798             :  * @param wc operation context
     799             :  */
     800             : static void
     801          79 : 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          79 :   qs = TEH_plugin->reserves_get_origin (
     809          79 :     TEH_plugin->cls,
     810          79 :     &wc->request.withdraw.reserve_pub,
     811             :     &h_full_payto,
     812             :     &payto_uri);
     813          79 :   if (qs < 0)
     814             :   {
     815           0 :     SET_ERROR_WITH_DETAIL (wc,
     816             :                            WITHDRAW_ERROR_DB_FETCH_FAILED,
     817             :                            db_fetch_context,
     818             :                            "reserves_get_origin");
     819           0 :     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          79 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     825             :   {
     826           0 :     wc->phase = WITHDRAW_PHASE_PREPARE_TRANSACTION;
     827           0 :     return;
     828             :   }
     829          79 :   TALER_full_payto_normalize_and_hash (payto_uri,
     830             :                                        &wc->h_normalized_payto);
     831         158 :   wc->lch = TEH_legitimization_check (
     832          79 :     &wc->rc->async_scope_id,
     833             :     TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
     834             :     payto_uri,
     835          79 :     &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          79 :   GNUNET_assert (NULL != wc->lch);
     842          79 :   GNUNET_free (payto_uri.full_payto);
     843          79 :   GNUNET_CONTAINER_DLL_insert (wc_head,
     844             :                                wc_tail,
     845             :                                wc);
     846          79 :   MHD_suspend_connection (wc->rc->connection);
     847          79 :   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         133 : 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         133 :   *pdk = NULL;
     872         133 :   dk = TEH_keys_denomination_by_hash_from_state (
     873             :     ksh,
     874             :     denom_h,
     875             :     NULL,
     876             :     NULL);
     877         133 :   if (NULL == dk)
     878             :   {
     879           0 :     SET_ERROR_WITH_FIELD (wc,
     880             :                           WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN,
     881             :                           denom_h);
     882           0 :     return false;
     883             :   }
     884         133 :   if (GNUNET_TIME_absolute_is_past (
     885             :         dk->meta.expire_withdraw.abs_time))
     886             :   {
     887           0 :     SET_ERROR_WITH_FIELD (wc,
     888             :                           WITHDRAW_ERROR_DENOMINATION_EXPIRED,
     889             :                           denom_h);
     890           0 :     return false;
     891             :   }
     892         133 :   if (GNUNET_TIME_absolute_is_future (
     893             :         dk->meta.start.abs_time))
     894             :   {
     895           0 :     GNUNET_break_op (0);
     896           0 :     SET_ERROR_WITH_FIELD (wc,
     897             :                           WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
     898             :                           denom_h);
     899           0 :     return false;
     900             :   }
     901         133 :   if (dk->recoup_possible)
     902             :   {
     903           0 :     SET_ERROR (wc,
     904             :                WITHDRAW_ERROR_DENOMINATION_REVOKED);
     905           0 :     return false;
     906             :   }
     907             :   /* In case of age withdraw, make sure that the denomination supports age restriction */
     908         133 :   if (wc->request.withdraw.age_proof_required)
     909             :   {
     910           9 :     if (0 == dk->denom_pub.age_mask.bits)
     911             :     {
     912           0 :       GNUNET_break_op (0);
     913           0 :       SET_ERROR_WITH_FIELD (wc,
     914             :                             WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
     915             :                             denom_h);
     916           0 :       return false;
     917             :     }
     918             :   }
     919         133 :   *pdk = dk;
     920         133 :   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          79 : phase_check_keys (
     935             :   struct WithdrawContext *wc)
     936          79 : {
     937             :   struct TEH_KeyStateHandle *ksh;
     938          79 :   bool is_cs_denom[wc->request.withdraw.num_coins];
     939             : 
     940          79 :   memset (is_cs_denom,
     941             :           0,
     942             :           sizeof(is_cs_denom));
     943          79 :   ksh = TEH_keys_get_state ();
     944          79 :   if (NULL == ksh)
     945             :   {
     946           0 :     GNUNET_break (0);
     947           0 :     SET_ERROR (wc,
     948             :                WITHDRAW_ERROR_KEYS_MISSING);
     949           0 :     return;
     950             :   }
     951          79 :   wc->request.withdraw.denom_serials =
     952          79 :     GNUNET_new_array (wc->request.withdraw.num_coins,
     953             :                       uint64_t);
     954          79 :   GNUNET_assert (GNUNET_OK ==
     955             :                  TALER_amount_set_zero (TEH_currency,
     956             :                                         &wc->request.amount));
     957          79 :   GNUNET_assert (GNUNET_OK ==
     958             :                  TALER_amount_set_zero (TEH_currency,
     959             :                                         &wc->request.fee));
     960          79 :   GNUNET_assert (GNUNET_OK ==
     961             :                  TALER_amount_set_zero (TEH_currency,
     962             :                                         &wc->request.withdraw.amount_with_fee));
     963             : 
     964         212 :   for (unsigned int i = 0; i < wc->request.withdraw.num_coins; i++)
     965             :   {
     966             :     struct TEH_DenominationKey *dk;
     967             : 
     968         133 :     if (! find_denomination (wc,
     969             :                              ksh,
     970         133 :                              &wc->request.denoms_h[i],
     971             :                              &dk))
     972           0 :       return;
     973         133 :     switch (dk->denom_pub.bsign_pub_key->cipher)
     974             :     {
     975           0 :     case GNUNET_CRYPTO_BSA_INVALID:
     976             :       /* This should never happen (memory corruption?) */
     977           0 :       GNUNET_assert (0);
     978             :     case GNUNET_CRYPTO_BSA_RSA:
     979             :       /* nothing to do here */
     980          95 :       break;
     981          38 :     case GNUNET_CRYPTO_BSA_CS:
     982          38 :       if (wc->request.withdraw.no_blinding_seed)
     983             :       {
     984           0 :         GNUNET_break_op (0);
     985           0 :         SET_ERROR (wc,
     986             :                    WITHDRAW_ERROR_BLINDING_SEED_REQUIRED);
     987           0 :         return;
     988             :       }
     989          38 :       wc->request.withdraw.num_cs_r_values++;
     990          38 :       is_cs_denom[i] = true;
     991          38 :       break;
     992             :     }
     993             : 
     994             :     /* Ensure the ciphers from the planchets match the denominations'. */
     995         133 :     if (wc->request.withdraw.age_proof_required)
     996             :     {
     997          36 :       for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
     998             :       {
     999          27 :         size_t off = k * wc->request.withdraw.num_coins;
    1000             : 
    1001          27 :         if (dk->denom_pub.bsign_pub_key->cipher !=
    1002          27 :             wc->request.planchets[i + off].blinded_message->cipher)
    1003             :         {
    1004           0 :           GNUNET_break_op (0);
    1005           0 :           SET_ERROR (wc,
    1006             :                      WITHDRAW_ERROR_CIPHER_MISMATCH);
    1007           0 :           return;
    1008             :         }
    1009             :       }
    1010             :     }
    1011             :     else
    1012             :     {
    1013         124 :       if (dk->denom_pub.bsign_pub_key->cipher !=
    1014         124 :           wc->request.planchets[i].blinded_message->cipher)
    1015             :       {
    1016           0 :         GNUNET_break_op (0);
    1017           0 :         SET_ERROR (wc,
    1018             :                    WITHDRAW_ERROR_CIPHER_MISMATCH);
    1019           0 :         return;
    1020             :       }
    1021             :     }
    1022             : 
    1023             :     /* Accumulate the values */
    1024         133 :     if (0 > TALER_amount_add (&wc->request.amount,
    1025         133 :                               &wc->request.amount,
    1026         133 :                               &dk->meta.value))
    1027             :     {
    1028           0 :       GNUNET_break_op (0);
    1029           0 :       SET_ERROR (wc,
    1030             :                  WITHDRAW_ERROR_AMOUNT_OVERFLOW);
    1031           0 :       return;
    1032             :     }
    1033             : 
    1034             :     /* Accumulate the withdraw fees */
    1035         133 :     if (0 > TALER_amount_add (&wc->request.fee,
    1036         133 :                               &wc->request.fee,
    1037         133 :                               &dk->meta.fees.withdraw))
    1038             :     {
    1039           0 :       GNUNET_break_op (0);
    1040           0 :       SET_ERROR (wc,
    1041             :                  WITHDRAW_ERROR_FEE_OVERFLOW);
    1042           0 :       return;
    1043             :     }
    1044         133 :     wc->request.withdraw.denom_serials[i] = dk->meta.serial;
    1045             :   }
    1046             : 
    1047             :   /* Save the hash of the batch of planchets */
    1048          79 :   if (! wc->request.withdraw.age_proof_required)
    1049             :   {
    1050          74 :     TALER_wallet_blinded_planchets_hash (
    1051             :       wc->request.withdraw.num_coins,
    1052          74 :       wc->request.planchets,
    1053          74 :       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           5 :     ctx = GNUNET_CRYPTO_hash_context_start ();
    1066           5 :     GNUNET_assert (NULL != ctx);
    1067             : 
    1068          20 :     for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
    1069             :     {
    1070          15 :       size_t off = k * wc->request.withdraw.num_coins;
    1071             : 
    1072          15 :       TALER_wallet_blinded_planchets_hash (
    1073             :         wc->request.withdraw.num_coins,
    1074          15 :         &wc->request.planchets[off],
    1075          15 :         wc->request.denoms_h,
    1076          15 :         &wc->request.kappa_planchets_h[k]);
    1077          15 :       GNUNET_CRYPTO_hash_context_read (
    1078             :         ctx,
    1079          15 :         &wc->request.kappa_planchets_h[k],
    1080             :         sizeof(wc->request.kappa_planchets_h[k]));
    1081             :     }
    1082           5 :     GNUNET_CRYPTO_hash_context_finish (
    1083             :       ctx,
    1084             :       &wc->request.withdraw.planchets_h.hash);
    1085             :   }
    1086             : 
    1087             :   /* Save the total amount including fees */
    1088          79 :   if (0 >  TALER_amount_add (
    1089             :         &wc->request.withdraw.amount_with_fee,
    1090          79 :         &wc->request.amount,
    1091          79 :         &wc->request.fee))
    1092             :   {
    1093           0 :     GNUNET_break_op (0);
    1094           0 :     SET_ERROR (wc,
    1095             :                WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW);
    1096           0 :     return;
    1097             :   }
    1098             : 
    1099             :   /* Save the indices of CS denominations */
    1100          79 :   if (0 < wc->request.withdraw.num_cs_r_values)
    1101             :   {
    1102          35 :     size_t j = 0;
    1103             : 
    1104          35 :     wc->request.cs_indices = GNUNET_new_array (
    1105             :       wc->request.withdraw.num_cs_r_values,
    1106             :       uint32_t);
    1107             : 
    1108          73 :     for (size_t i = 0; i < wc->request.withdraw.num_coins; i++)
    1109             :     {
    1110          38 :       if (is_cs_denom[i])
    1111          38 :         wc->request.cs_indices[j++] = i;
    1112             :     }
    1113             :   }
    1114             : 
    1115          79 :   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          79 : phase_check_reserve_signature (
    1126             :   struct WithdrawContext *wc)
    1127             : {
    1128          79 :   TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
    1129          79 :   if (GNUNET_OK !=
    1130         232 :       TALER_wallet_withdraw_verify (
    1131          79 :         &wc->request.amount,
    1132          79 :         &wc->request.fee,
    1133          79 :         &wc->request.withdraw.planchets_h,
    1134          79 :         wc->request.withdraw.no_blinding_seed
    1135             :         ? NULL
    1136             :         : &wc->request.withdraw.blinding_seed,
    1137          79 :         (wc->request.withdraw.age_proof_required)
    1138             :         ? &TEH_age_restriction_config.mask
    1139             :         : NULL,
    1140          79 :         (wc->request.withdraw.age_proof_required)
    1141           5 :         ? wc->request.withdraw.max_age
    1142             :         : 0,
    1143          79 :         &wc->request.withdraw.reserve_pub,
    1144          79 :         &wc->request.withdraw.reserve_sig))
    1145             :   {
    1146           0 :     GNUNET_break_op (0);
    1147           0 :     SET_ERROR (wc,
    1148             :                WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID);
    1149           0 :     return;
    1150             :   }
    1151          79 :   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          79 : free_db_withdraw_data (struct TALER_EXCHANGEDB_Withdraw *wd)
    1162             : {
    1163          79 :   if (NULL != wd->denom_sigs)
    1164             :   {
    1165         204 :     for (unsigned int i = 0; i<wd->num_coins; i++)
    1166         129 :       TALER_blinded_denom_sig_free (&wd->denom_sigs[i]);
    1167          75 :     GNUNET_free (wd->denom_sigs);
    1168             :   }
    1169          79 :   GNUNET_free (wd->denom_serials);
    1170          79 :   GNUNET_free (wd->cs_r_values);
    1171          79 : }
    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          79 : clean_withdraw_rc (struct TEH_RequestContext *rc)
    1183             : {
    1184          79 :   struct WithdrawContext *wc = rc->rh_ctx;
    1185             : 
    1186          79 :   if (NULL != wc->lch)
    1187             :   {
    1188           0 :     TEH_legitimization_check_cancel (wc->lch);
    1189           0 :     wc->lch = NULL;
    1190             :   }
    1191          79 :   GNUNET_free (wc->request.denoms_h);
    1192         230 :   for (unsigned int i = 0; i<wc->request.num_planchets; i++)
    1193         151 :     TALER_blinded_planchet_free (&wc->request.planchets[i]);
    1194          79 :   GNUNET_free (wc->request.planchets);
    1195          79 :   free_db_withdraw_data (&wc->request.withdraw);
    1196          79 :   GNUNET_free (wc->request.cs_indices);
    1197          79 :   if (wc->request.is_idempotent)
    1198           0 :     free_db_withdraw_data (&wc->request.withdraw_idem);
    1199          79 :   if ( (WITHDRAW_ERROR_LEGITIMIZATION_RESULT == wc->error.code) &&
    1200           0 :        (NULL != wc->error.details.legitimization_result.response) )
    1201             :   {
    1202           0 :     MHD_destroy_response (wc->error.details.legitimization_result.response);
    1203           0 :     wc->error.details.legitimization_result.response = NULL;
    1204             :   }
    1205          79 :   GNUNET_free (wc);
    1206          79 : }
    1207             : 
    1208             : 
    1209             : /**
    1210             :  * Generates response for the withdraw request.
    1211             :  *
    1212             :  * @param wc withdraw operation context
    1213             :  */
    1214             : static void
    1215          68 : phase_generate_reply_success (struct WithdrawContext *wc)
    1216             : {
    1217             :   struct TALER_EXCHANGEDB_Withdraw *db_obj;
    1218             : 
    1219         136 :   db_obj = wc->request.is_idempotent
    1220             :     ? &wc->request.withdraw_idem
    1221          68 :     : &wc->request.withdraw;
    1222             : 
    1223          68 :   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           3 :       TALER_exchange_online_withdraw_age_confirmation_sign (
    1231             :         &TEH_keys_exchange_sign_,
    1232           3 :         &db_obj->planchets_h,
    1233           3 :         db_obj->noreveal_index,
    1234             :         &pub,
    1235             :         &sig);
    1236           3 :     if (TALER_EC_NONE != ec_confirmation_sign)
    1237             :     {
    1238           0 :       SET_ERROR_WITH_FIELD (wc,
    1239             :                             WITHDRAW_ERROR_CONFIRMATION_SIGN,
    1240             :                             ec_confirmation_sign);
    1241           0 :       return;
    1242             :     }
    1243             : 
    1244           6 :     finish_loop (wc,
    1245           6 :                  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          65 :     sigs = json_array ();
    1260          65 :     GNUNET_assert (NULL != sigs);
    1261         180 :     for (unsigned int i = 0; i<db_obj->num_coins; i++)
    1262             :     {
    1263         115 :       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         130 :     finish_loop (wc,
    1273         130 :                  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          68 :   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          11 : phase_generate_reply_error (
    1293             :   struct WithdrawContext *wc)
    1294             : {
    1295          11 :   GNUNET_assert (WITHDRAW_PHASE_GENERATE_REPLY_ERROR == wc->phase);
    1296          18 :   if (IDEMPOTENCY_CHECK_REQUIRED (wc->error.code) &&
    1297           7 :       withdraw_is_idempotent (wc))
    1298             :   {
    1299           0 :     return;
    1300             :   }
    1301             : 
    1302          11 :   switch (wc->error.code)
    1303             :   {
    1304           0 :   case WITHDRAW_ERROR_NONE:
    1305           0 :     break;
    1306           0 :   case WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED:
    1307           0 :     finish_loop (wc,
    1308             :                  TALER_MHD_reply_with_error (
    1309           0 :                    wc->rc->connection,
    1310             :                    MHD_HTTP_BAD_REQUEST,
    1311             :                    TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1312             :                    wc->error.details.request_parameter_malformed));
    1313          11 :     return;
    1314           0 :   case WITHDRAW_ERROR_KEYS_MISSING:
    1315           0 :     finish_loop (wc,
    1316             :                  TALER_MHD_reply_with_error (
    1317           0 :                    wc->rc->connection,
    1318             :                    MHD_HTTP_INTERNAL_SERVER_ERROR,
    1319             :                    TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
    1320             :                    NULL));
    1321           0 :     return;
    1322           0 :   case WITHDRAW_ERROR_DB_FETCH_FAILED:
    1323           0 :     finish_loop (wc,
    1324             :                  TALER_MHD_reply_with_error (
    1325           0 :                    wc->rc->connection,
    1326             :                    MHD_HTTP_INTERNAL_SERVER_ERROR,
    1327             :                    TALER_EC_GENERIC_DB_FETCH_FAILED,
    1328             :                    wc->error.details.db_fetch_context));
    1329           0 :     return;
    1330           0 :   case WITHDRAW_ERROR_DB_INVARIANT_FAILURE:
    1331           0 :     finish_loop (wc,
    1332             :                  TALER_MHD_reply_with_error (
    1333           0 :                    wc->rc->connection,
    1334             :                    MHD_HTTP_INTERNAL_SERVER_ERROR,
    1335             :                    TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
    1336             :                    NULL));
    1337           0 :     return;
    1338           0 :   case WITHDRAW_ERROR_RESERVE_UNKNOWN:
    1339           0 :     finish_loop (wc,
    1340             :                  TALER_MHD_reply_with_ec (
    1341           0 :                    wc->rc->connection,
    1342             :                    TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
    1343             :                    NULL));
    1344           0 :     return;
    1345           0 :   case WITHDRAW_ERROR_DENOMINATION_SIGN:
    1346           0 :     finish_loop (wc,
    1347             :                  TALER_MHD_reply_with_ec (
    1348           0 :                    wc->rc->connection,
    1349             :                    wc->error.details.ec_denomination_sign,
    1350             :                    NULL));
    1351           0 :     return;
    1352           4 :   case WITHDRAW_ERROR_KYC_REQUIRED:
    1353           4 :     finish_loop (wc,
    1354             :                  TEH_RESPONSE_reply_kyc_required (
    1355           4 :                    wc->rc->connection,
    1356           4 :                    &wc->h_normalized_payto,
    1357           4 :                    &wc->kyc,
    1358             :                    false));
    1359           4 :     return;
    1360           0 :   case WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN:
    1361           0 :     GNUNET_break_op (0);
    1362           0 :     finish_loop (wc,
    1363             :                  TEH_RESPONSE_reply_unknown_denom_pub_hash (
    1364           0 :                    wc->rc->connection,
    1365             :                    wc->error.details.denom_h));
    1366           0 :     return;
    1367           0 :   case WITHDRAW_ERROR_DENOMINATION_EXPIRED:
    1368           0 :     GNUNET_break_op (0);
    1369           0 :     finish_loop (wc,
    1370             :                  TEH_RESPONSE_reply_expired_denom_pub_hash (
    1371           0 :                    wc->rc->connection,
    1372             :                    wc->error.details.denom_h,
    1373             :                    TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
    1374             :                    "WITHDRAW"));
    1375           0 :     return;
    1376           0 :   case WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE:
    1377           0 :     finish_loop (wc,
    1378             :                  TEH_RESPONSE_reply_expired_denom_pub_hash (
    1379           0 :                    wc->rc->connection,
    1380             :                    wc->error.details.denom_h,
    1381             :                    TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
    1382             :                    "WITHDRAW"));
    1383           0 :     return;
    1384           0 :   case WITHDRAW_ERROR_DENOMINATION_REVOKED:
    1385           0 :     GNUNET_break_op (0);
    1386           0 :     finish_loop (wc,
    1387             :                  TALER_MHD_reply_with_ec (
    1388           0 :                    wc->rc->connection,
    1389             :                    TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
    1390             :                    NULL));
    1391           0 :     return;
    1392           0 :   case WITHDRAW_ERROR_CIPHER_MISMATCH:
    1393           0 :     finish_loop (wc,
    1394             :                  TALER_MHD_reply_with_ec (
    1395           0 :                    wc->rc->connection,
    1396             :                    TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
    1397             :                    NULL));
    1398           0 :     return;
    1399           0 :   case WITHDRAW_ERROR_BLINDING_SEED_REQUIRED:
    1400           0 :     finish_loop (wc,
    1401             :                  TALER_MHD_reply_with_ec (
    1402           0 :                    wc->rc->connection,
    1403             :                    TALER_EC_GENERIC_PARAMETER_MISSING,
    1404             :                    "blinding_seed"));
    1405           0 :     return;
    1406           0 :   case WITHDRAW_ERROR_CRYPTO_HELPER:
    1407           0 :     finish_loop (wc,
    1408             :                  TALER_MHD_reply_with_ec (
    1409           0 :                    wc->rc->connection,
    1410             :                    TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    1411             :                    NULL));
    1412           0 :     return;
    1413           0 :   case WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN:
    1414           0 :     finish_loop (wc,
    1415             :                  TALER_MHD_reply_with_ec (
    1416           0 :                    wc->rc->connection,
    1417             :                    TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
    1418             :                    "cipher"));
    1419           0 :     return;
    1420           0 :   case WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION:
    1421             :     {
    1422             :       char msg[256];
    1423             : 
    1424           0 :       GNUNET_snprintf (msg,
    1425             :                        sizeof(msg),
    1426             :                        "denomination %s does not support age restriction",
    1427           0 :                        GNUNET_h2s (&wc->error.details.denom_h->hash));
    1428           0 :       finish_loop (wc,
    1429             :                    TALER_MHD_reply_with_ec (
    1430           0 :                      wc->rc->connection,
    1431             :                      TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
    1432             :                      msg));
    1433           0 :       return;
    1434             :     }
    1435           2 :   case WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE:
    1436           4 :     finish_loop (wc,
    1437           4 :                  TALER_MHD_REPLY_JSON_PACK (
    1438             :                    wc->rc->connection,
    1439             :                    MHD_HTTP_CONFLICT,
    1440             :                    TALER_MHD_PACK_EC (
    1441             :                      TALER_EC_EXCHANGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE),
    1442             :                    GNUNET_JSON_pack_uint64 (
    1443             :                      "allowed_maximum_age",
    1444             :                      wc->error.details.maximum_age_too_large.max_allowed),
    1445             :                    GNUNET_JSON_pack_uint64 (
    1446             :                      "reserve_birthday",
    1447             :                      wc->error.details.maximum_age_too_large.birthday)));
    1448           2 :     return;
    1449           2 :   case WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED:
    1450           2 :     finish_loop (wc,
    1451             :                  TEH_RESPONSE_reply_reserve_age_restriction_required (
    1452           2 :                    wc->rc->connection,
    1453           2 :                    wc->error.details.age_restriction_required));
    1454           2 :     return;
    1455           0 :   case WITHDRAW_ERROR_AMOUNT_OVERFLOW:
    1456           0 :     finish_loop (wc,
    1457             :                  TALER_MHD_reply_with_error (
    1458           0 :                    wc->rc->connection,
    1459             :                    MHD_HTTP_BAD_REQUEST,
    1460             :                    TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_OVERFLOW,
    1461             :                    "amount"));
    1462           0 :     return;
    1463           0 :   case WITHDRAW_ERROR_FEE_OVERFLOW:
    1464           0 :     finish_loop (wc,
    1465             :                  TALER_MHD_reply_with_error (
    1466           0 :                    wc->rc->connection,
    1467             :                    MHD_HTTP_BAD_REQUEST,
    1468             :                    TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_OVERFLOW,
    1469             :                    "fee"));
    1470           0 :     return;
    1471           0 :   case WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW:
    1472           0 :     finish_loop (wc,
    1473             :                  TALER_MHD_reply_with_error (
    1474           0 :                    wc->rc->connection,
    1475             :                    MHD_HTTP_INTERNAL_SERVER_ERROR,
    1476             :                    TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW,
    1477             :                    "amount+fee"));
    1478           0 :     return;
    1479           0 :   case WITHDRAW_ERROR_CONFIRMATION_SIGN:
    1480           0 :     finish_loop (wc,
    1481             :                  TALER_MHD_reply_with_ec (
    1482           0 :                    wc->rc->connection,
    1483             :                    wc->error.details.ec_confirmation_sign,
    1484             :                    NULL));
    1485           0 :     return;
    1486           3 :   case WITHDRAW_ERROR_INSUFFICIENT_FUNDS:
    1487           3 :     finish_loop (wc,
    1488             :                  TEH_RESPONSE_reply_reserve_insufficient_balance (
    1489           3 :                    wc->rc->connection,
    1490             :                    TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS,
    1491           3 :                    &wc->error.details.insufficient_funds,
    1492           3 :                    &wc->request.withdraw.amount_with_fee,
    1493           3 :                    &wc->request.withdraw.reserve_pub));
    1494           3 :     return;
    1495           0 :   case WITHDRAW_ERROR_IDEMPOTENT_PLANCHET:
    1496           0 :     finish_loop (wc,
    1497             :                  TALER_MHD_reply_with_error (
    1498           0 :                    wc->rc->connection,
    1499             :                    MHD_HTTP_CONFLICT,
    1500             :                    TALER_EC_EXCHANGE_WITHDRAW_IDEMPOTENT_PLANCHET,
    1501             :                    NULL));
    1502           0 :     return;
    1503           0 :   case WITHDRAW_ERROR_NONCE_RESUSE:
    1504           0 :     finish_loop (wc,
    1505             :                  TALER_MHD_reply_with_error (
    1506           0 :                    wc->rc->connection,
    1507             :                    MHD_HTTP_CONFLICT,
    1508             :                    TALER_EC_EXCHANGE_WITHDRAW_NONCE_REUSE,
    1509             :                    "blinding_seed"));
    1510           0 :     return;
    1511           0 :   case WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID:
    1512           0 :     finish_loop (wc,
    1513             :                  TALER_MHD_reply_with_ec (
    1514           0 :                    wc->rc->connection,
    1515             :                    TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID,
    1516             :                    NULL));
    1517           0 :     return;
    1518           0 :   case WITHDRAW_ERROR_LEGITIMIZATION_RESULT: {
    1519           0 :       finish_loop (
    1520             :         wc,
    1521           0 :         MHD_queue_response (wc->rc->connection,
    1522             :                             wc->error.details.legitimization_result.http_status,
    1523             :                             wc->error.details.legitimization_result.response));
    1524           0 :       return;
    1525             :     }
    1526             :   }
    1527           0 :   GNUNET_break (0);
    1528           0 :   finish_loop (wc,
    1529             :                TALER_MHD_reply_with_error (
    1530           0 :                  wc->rc->connection,
    1531             :                  MHD_HTTP_INTERNAL_SERVER_ERROR,
    1532             :                  TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    1533             :                  "error phase without error"));
    1534             : }
    1535             : 
    1536             : 
    1537             : /**
    1538             :  * Initializes the new context for the incoming withdraw request
    1539             :  *
    1540             :  * @param[in,out] wc withdraw request context
    1541             :  * @param root json body of the request
    1542             :  */
    1543             : static void
    1544          79 : withdraw_phase_parse (
    1545             :   struct WithdrawContext *wc,
    1546             :   const json_t *root)
    1547             : {
    1548             :   const json_t *j_denoms_h;
    1549             :   const json_t *j_coin_evs;
    1550             :   const char *cipher;
    1551             :   bool no_max_age;
    1552             :   struct GNUNET_JSON_Specification spec[] = {
    1553          79 :     GNUNET_JSON_spec_string ("cipher",
    1554             :                              &cipher),
    1555          79 :     GNUNET_JSON_spec_fixed_auto  ("reserve_pub",
    1556             :                                   &wc->request.withdraw.reserve_pub),
    1557          79 :     GNUNET_JSON_spec_array_const ("denoms_h",
    1558             :                                   &j_denoms_h),
    1559          79 :     GNUNET_JSON_spec_array_const ("coin_evs",
    1560             :                                   &j_coin_evs),
    1561          79 :     GNUNET_JSON_spec_mark_optional (
    1562             :       GNUNET_JSON_spec_uint16 ("max_age",
    1563             :                                &wc->request.withdraw.max_age),
    1564             :       &no_max_age),
    1565          79 :     GNUNET_JSON_spec_mark_optional (
    1566          79 :       GNUNET_JSON_spec_fixed_auto ("blinding_seed",
    1567             :                                    &wc->request.withdraw.blinding_seed),
    1568             :       &wc->request.withdraw.no_blinding_seed),
    1569          79 :     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    1570             :                                  &wc->request.withdraw.reserve_sig),
    1571          79 :     GNUNET_JSON_spec_end ()
    1572             :   };
    1573             :   enum GNUNET_GenericReturnValue res;
    1574             : 
    1575          79 :   res = TALER_MHD_parse_json_data (wc->rc->connection,
    1576             :                                    root,
    1577             :                                    spec);
    1578          79 :   if (GNUNET_YES != res)
    1579             :   {
    1580           0 :     GNUNET_break_op (0);
    1581           0 :     wc->phase = (GNUNET_SYSERR == res)
    1582             :       ? WITHDRAW_PHASE_RETURN_NO
    1583           0 :       : WITHDRAW_PHASE_RETURN_YES;
    1584           0 :     return;
    1585             :   }
    1586             : 
    1587             :   /* For now, we only support cipher "ED25519" for signatures by the reserve */
    1588          79 :   if (0 != strcmp ("ED25519",
    1589             :                    cipher))
    1590             :   {
    1591           0 :     GNUNET_break_op (0);
    1592           0 :     SET_ERROR_WITH_DETAIL (wc,
    1593             :                            WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN,
    1594             :                            reserve_cipher_unknown,
    1595             :                            cipher);
    1596           0 :     return;
    1597             :   }
    1598             : 
    1599          79 :   wc->request.withdraw.age_proof_required = ! no_max_age;
    1600             : 
    1601          79 :   if (wc->request.withdraw.age_proof_required)
    1602             :   {
    1603             :     /* The age value MUST be on the beginning of an age group */
    1604          10 :     if (wc->request.withdraw.max_age !=
    1605           5 :         TALER_get_lowest_age (&TEH_age_restriction_config.mask,
    1606           5 :                               wc->request.withdraw.max_age))
    1607             :     {
    1608           0 :       GNUNET_break_op (0);
    1609           0 :       SET_ERROR_WITH_DETAIL (
    1610             :         wc,
    1611             :         WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED,
    1612             :         request_parameter_malformed,
    1613             :         "max_age must be the lower edge of an age group");
    1614           0 :       return;
    1615             :     }
    1616             :   }
    1617             : 
    1618             :   /* validate array size */
    1619             :   {
    1620          79 :     size_t num_coins = json_array_size (j_denoms_h);
    1621          79 :     size_t array_size = json_array_size (j_coin_evs);
    1622             :     const char *error;
    1623             : 
    1624             :     GNUNET_static_assert (
    1625             :       TALER_MAX_COINS < INT_MAX / TALER_CNC_KAPPA);
    1626             : 
    1627             : #define BAIL_IF(cond, msg) \
    1628             :         if ((cond)) { \
    1629             :           GNUNET_break_op (0); \
    1630             :           error = (msg); break; \
    1631             :         }
    1632             : 
    1633             :     do {
    1634          79 :       BAIL_IF (0 == num_coins,
    1635             :                "denoms_h must not be empty")
    1636             : 
    1637             :       /**
    1638             :          * The wallet had committed to more than the maximum coins allowed, the
    1639             :          * reserve has been charged, but now the user can not withdraw any money
    1640             :          * from it.  Note that the user can't get their money back in this case!
    1641             :          */
    1642          79 :       BAIL_IF (num_coins > TALER_MAX_COINS,
    1643             :                "maximum number of coins that can be withdrawn has been exceeded")
    1644             : 
    1645          79 :       BAIL_IF ((! wc->request.withdraw.age_proof_required) &&
    1646             :                (num_coins != array_size),
    1647             :                "denoms_h and coin_evs must be arrays of the same size")
    1648             : 
    1649          79 :       BAIL_IF (wc->request.withdraw.age_proof_required &&
    1650             :                ((TALER_CNC_KAPPA * num_coins) != array_size),
    1651             :                "coin_evs must be an array of length "
    1652             :                TALER_CNC_KAPPA_STR
    1653             :                "*len(denoms_h)")
    1654             : 
    1655          79 :       wc->request.withdraw.num_coins = num_coins;
    1656          79 :       wc->request.num_planchets = array_size;
    1657          79 :       error = NULL;
    1658             : 
    1659             :     } while (0);
    1660             : #undef BAIL_IF
    1661             : 
    1662          79 :     if (NULL != error)
    1663             :     {
    1664           0 :       SET_ERROR_WITH_DETAIL (wc,
    1665             :                              WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED,
    1666             :                              request_parameter_malformed,
    1667             :                              error);
    1668           0 :       return;
    1669             :     }
    1670             :   }
    1671             :   /* extract the denomination hashes */
    1672             :   {
    1673             :     size_t idx;
    1674             :     json_t *value;
    1675             : 
    1676             :     wc->request.denoms_h
    1677          79 :       = GNUNET_new_array (wc->request.withdraw.num_coins,
    1678             :                           struct TALER_DenominationHashP);
    1679             : 
    1680         212 :     json_array_foreach (j_denoms_h, idx, value) {
    1681             :       struct GNUNET_JSON_Specification ispec[] = {
    1682         133 :         GNUNET_JSON_spec_fixed_auto (NULL,
    1683             :                                      &wc->request.denoms_h[idx]),
    1684         133 :         GNUNET_JSON_spec_end ()
    1685             :       };
    1686             : 
    1687         133 :       res = TALER_MHD_parse_json_data (wc->rc->connection,
    1688             :                                        value,
    1689             :                                        ispec);
    1690         133 :       if (GNUNET_YES != res)
    1691             :       {
    1692           0 :         GNUNET_break_op (0);
    1693           0 :         wc->phase = (GNUNET_SYSERR == res)
    1694             :           ? WITHDRAW_PHASE_RETURN_NO
    1695           0 :           : WITHDRAW_PHASE_RETURN_YES;
    1696           0 :         return;
    1697             :       }
    1698             :     }
    1699             :   }
    1700             :   /* Parse the blinded coin envelopes */
    1701             :   {
    1702             :     json_t *j_cev;
    1703             :     size_t idx;
    1704             : 
    1705          79 :     wc->request.planchets =
    1706          79 :       GNUNET_new_array (wc->request.num_planchets,
    1707             :                         struct  TALER_BlindedPlanchet);
    1708         230 :     json_array_foreach (j_coin_evs, idx, j_cev)
    1709             :     {
    1710             :       /* Now parse the individual envelopes and calculate the hash of
    1711             :        * the commitment along the way. */
    1712             :       struct GNUNET_JSON_Specification kspec[] = {
    1713         151 :         TALER_JSON_spec_blinded_planchet (NULL,
    1714         151 :                                           &wc->request.planchets[idx]),
    1715         151 :         GNUNET_JSON_spec_end ()
    1716             :       };
    1717             : 
    1718         151 :       res = TALER_MHD_parse_json_data (wc->rc->connection,
    1719             :                                        j_cev,
    1720             :                                        kspec);
    1721         151 :       if (GNUNET_OK != res)
    1722             :       {
    1723           0 :         GNUNET_break_op (0);
    1724           0 :         wc->phase = (GNUNET_SYSERR == res)
    1725             :           ? WITHDRAW_PHASE_RETURN_NO
    1726           0 :           : WITHDRAW_PHASE_RETURN_YES;
    1727           0 :         return;
    1728             :       }
    1729             : 
    1730             :       /* Check for duplicate planchets. Technically a bug on
    1731             :        * the client side that is harmless for us, but still
    1732             :        * not allowed per protocol */
    1733         548 :       for (size_t i = 0; i < idx; i++)
    1734             :       {
    1735         397 :         if (0 ==
    1736         397 :             TALER_blinded_planchet_cmp (
    1737         397 :               &wc->request.planchets[idx],
    1738         397 :               &wc->request.planchets[i]))
    1739             :         {
    1740           0 :           GNUNET_break_op (0);
    1741           0 :           SET_ERROR (wc,
    1742             :                      WITHDRAW_ERROR_IDEMPOTENT_PLANCHET);
    1743           0 :           return;
    1744             :         }
    1745             :       }       /* end duplicate check */
    1746             :     }       /* json_array_foreach over j_coin_evs */
    1747             :   }       /* scope of j_kappa_planchets, idx */
    1748          79 :   wc->phase = WITHDRAW_PHASE_CHECK_KEYS;
    1749             : }
    1750             : 
    1751             : 
    1752             : MHD_RESULT
    1753         158 : TEH_handler_withdraw (
    1754             :   struct TEH_RequestContext *rc,
    1755             :   const json_t *root,
    1756             :   const char *const args[0])
    1757             : {
    1758         158 :   struct WithdrawContext *wc = rc->rh_ctx;
    1759             : 
    1760             :   (void) args;
    1761         158 :   if (NULL == wc)
    1762             :   {
    1763          79 :     wc = GNUNET_new (struct WithdrawContext);
    1764          79 :     rc->rh_ctx = wc;
    1765          79 :     rc->rh_cleaner = &clean_withdraw_rc;
    1766          79 :     wc->rc = rc;
    1767          79 :     wc->now = GNUNET_TIME_timestamp_get ();
    1768             :   }
    1769             :   while (true)
    1770             :   {
    1771        1406 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1772             :                 "withdrawal%s processing in phase %d\n",
    1773             :                 wc->request.withdraw.age_proof_required
    1774             :                      ? " (with required age proof)"
    1775             :                      : "",
    1776             :                 wc->phase);
    1777         782 :     switch (wc->phase)
    1778             :     {
    1779          79 :     case WITHDRAW_PHASE_PARSE:
    1780          79 :       withdraw_phase_parse (wc,
    1781             :                             root);
    1782          79 :       break;
    1783          79 :     case WITHDRAW_PHASE_CHECK_KEYS:
    1784          79 :       phase_check_keys (wc);
    1785          79 :       break;
    1786          79 :     case WITHDRAW_PHASE_CHECK_RESERVE_SIGNATURE:
    1787          79 :       phase_check_reserve_signature (wc);
    1788          79 :       break;
    1789          79 :     case WITHDRAW_PHASE_RUN_LEGI_CHECK:
    1790          79 :       phase_run_legi_check (wc);
    1791          79 :       break;
    1792          79 :     case WITHDRAW_PHASE_SUSPENDED:
    1793          79 :       return MHD_YES;
    1794          79 :     case WITHDRAW_PHASE_CHECK_KYC_RESULT:
    1795          79 :       phase_check_kyc_result (wc);
    1796          79 :       break;
    1797          75 :     case WITHDRAW_PHASE_PREPARE_TRANSACTION:
    1798          75 :       phase_prepare_transaction (wc);
    1799          75 :       break;
    1800          75 :     case WITHDRAW_PHASE_RUN_TRANSACTION:
    1801          75 :       phase_run_transaction (wc);
    1802          75 :       break;
    1803          68 :     case WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS:
    1804          68 :       phase_generate_reply_success (wc);
    1805          68 :       break;
    1806          11 :     case WITHDRAW_PHASE_GENERATE_REPLY_ERROR:
    1807          11 :       phase_generate_reply_error (wc);
    1808          11 :       break;
    1809          79 :     case WITHDRAW_PHASE_RETURN_YES:
    1810          79 :       return MHD_YES;
    1811           0 :     case WITHDRAW_PHASE_RETURN_NO:
    1812           0 :       return MHD_NO;
    1813             :     }
    1814             :   }
    1815             : }

Generated by: LCOV version 1.16