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