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 : }
|