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