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