testing_api_cmd_age_withdraw.c (25941B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2023-2025 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it 6 under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, see 17 <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file testing/testing_api_cmd_age_withdraw.c 21 * @brief implements the withdraw command for age-restricted coins 22 * @author Özgür Kesim 23 */ 24 25 #include "taler/taler_json_lib.h" 26 #include <gnunet/gnunet_common.h> 27 #include <microhttpd.h> 28 #include <gnunet/gnunet_curl_lib.h> 29 #include "taler/taler_signatures.h" 30 struct AgeWithdrawState; 31 #define TALER_EXCHANGE_POST_WITHDRAW_RESULT_CLOSURE struct AgeWithdrawState 32 #include "taler/exchange/post-withdraw.h" 33 struct AgeRevealWithdrawState; 34 #define TALER_EXCHANGE_POST_REVEAL_WITHDRAW_RESULT_CLOSURE \ 35 struct AgeRevealWithdrawState 36 #include "taler/exchange/post-reveal-withdraw.h" 37 #include "taler/taler_testing_lib.h" 38 39 /* 40 * The output state of coin 41 */ 42 struct CoinOutputState 43 { 44 45 /** 46 * The calculated details during "withdraw", for the selected coin. 47 */ 48 struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details; 49 50 /** 51 * The (wanted) value of the coin, MUST be the same as input.denom_pub.value; 52 */ 53 struct TALER_Amount amount; 54 55 }; 56 57 /** 58 * State for a "age withdraw" CMD: 59 */ 60 61 struct AgeWithdrawState 62 { 63 64 /** 65 * Interpreter state (during command) 66 */ 67 struct TALER_TESTING_Interpreter *is; 68 69 /** 70 * The age-withdraw handle 71 */ 72 struct TALER_EXCHANGE_PostWithdrawHandle *handle; 73 74 /** 75 * Exchange base URL. Only used as offered trait. 76 */ 77 char *exchange_url; 78 79 /** 80 * URI of the reserve we are withdrawing from. 81 */ 82 struct TALER_NormalizedPayto reserve_payto_uri; 83 84 /** 85 * Private key of the reserve we are withdrawing from. 86 */ 87 struct TALER_ReservePrivateKeyP reserve_priv; 88 89 /** 90 * Public key of the reserve we are withdrawing from. 91 */ 92 struct TALER_ReservePublicKeyP reserve_pub; 93 94 /** 95 * Which reserve should we withdraw from? 96 */ 97 const char *reserve_reference; 98 99 /** 100 * Expected HTTP response code to the request. 101 */ 102 unsigned int expected_response_code; 103 104 /** 105 * Age mask 106 */ 107 struct TALER_AgeMask mask; 108 109 /** 110 * The maximum age we commit to 111 */ 112 uint8_t max_age; 113 114 /** 115 * Number of coins to withdraw 116 */ 117 size_t num_coins; 118 119 /** 120 * The @e num_coins denomination public keys that are provided 121 * to the `TALER_EXCHANGE_post_withdraw()` API. 122 */ 123 struct TALER_EXCHANGE_DenomPublicKey *denoms_pub; 124 125 /** 126 * The master seed from which all the other seeds are derived from 127 */ 128 struct TALER_WithdrawMasterSeedP seed; 129 130 /** 131 * The #TALER_CNC_KAPPA seeds derived from @e seed 132 */ 133 struct TALER_KappaWithdrawMasterSeedP kappa_seed; 134 135 /** 136 * The master seed from which all the other seeds are derived from 137 */ 138 struct TALER_BlindingMasterSeedP blinding_seed; 139 140 /** 141 * The output state of @e num_coins coins, calculated during the 142 * "age-withdraw" operation. 143 */ 144 struct CoinOutputState *coin_outputs; 145 146 /** 147 * Array of @e num_coins planchet secrets of the batch the exchange 148 * signed (the one at @e noreveal_index), expanded from @e kappa_seed 149 * once the response is known. NULL before. 150 */ 151 struct TALER_PlanchetMasterSecretP *selected_secrets; 152 153 /** 154 * The index returned by the exchange for the "age-withdraw" operation, 155 * of the kappa coin candidates that we do not disclose and keep. 156 */ 157 uint8_t noreveal_index; 158 159 /** 160 * The hash of the commitment, needed for the reveal step. 161 */ 162 struct TALER_HashBlindedPlanchetsP planchets_h; 163 164 /** 165 * The hash of the selected blinded planchets 166 */ 167 struct TALER_HashBlindedPlanchetsP selected_h; 168 169 /** 170 * Set to the KYC requirement payto hash *if* the exchange replied with a 171 * request for KYC. 172 */ 173 struct TALER_NormalizedPaytoHashP h_payto; 174 175 /** 176 * Set to the KYC requirement row *if* the exchange replied with 177 * a request for KYC. 178 */ 179 uint64_t requirement_row; 180 181 /** 182 * Reserve history entry that corresponds to this withdraw. 183 * Will be of type #TALER_EXCHANGE_RTT_WITHDRAWAL. 184 */ 185 struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history; 186 }; 187 188 /** 189 * Callback for the "age-withdraw" operation; It checks that the response 190 * code is expected and store the exchange signature in the state. 191 * 192 * @param aws Closure of type `struct AgeWithdrawState *` 193 * @param response Response details 194 */ 195 static void 196 age_withdraw_cb ( 197 struct AgeWithdrawState *aws, 198 const struct TALER_EXCHANGE_PostWithdrawResponse *response) 199 { 200 struct TALER_TESTING_Interpreter *is = aws->is; 201 202 aws->handle = NULL; 203 if (aws->expected_response_code != response->hr.http_status) 204 { 205 TALER_TESTING_unexpected_status_with_body (is, 206 response->hr.http_status, 207 aws->expected_response_code, 208 response->hr.reply); 209 return; 210 } 211 212 switch (response->hr.http_status) 213 { 214 case MHD_HTTP_CREATED: 215 aws->noreveal_index = response->details.created.noreveal_index; 216 aws->planchets_h = response->details.created.planchets_h; 217 aws->selected_h = response->details.created.selected_h; 218 aws->reserve_history.details.withdraw.planchets_h = aws->planchets_h; 219 aws->reserve_history.details.withdraw.selected_h = aws->selected_h; 220 aws->reserve_history.details.withdraw.noreveal_index = aws->noreveal_index; 221 aws->kappa_seed = response->details.created.kappa_seed; 222 223 GNUNET_assert (aws->num_coins == response->details.created.num_coins); 224 GNUNET_assert (aws->noreveal_index < TALER_CNC_KAPPA); 225 aws->selected_secrets = GNUNET_new_array (aws->num_coins, 226 struct TALER_PlanchetMasterSecretP); 227 TALER_withdraw_expand_secrets (aws->num_coins, 228 &aws->kappa_seed.tuple[aws->noreveal_index], 229 aws->selected_secrets); 230 for (size_t n = 0; n < aws->num_coins; n++) 231 { 232 aws->coin_outputs[n].details = response->details.created.coin_details[n]; 233 TALER_age_commitment_proof_deep_copy ( 234 &aws->coin_outputs[n].details.age_commitment_proof, 235 &response->details.created.coin_details[n].age_commitment_proof); 236 TALER_denom_ewv_copy ( 237 &aws->coin_outputs[n].details.blinding_values, 238 &response->details.created.coin_details[n].blinding_values); 239 } 240 break; 241 case MHD_HTTP_FORBIDDEN: 242 case MHD_HTTP_NOT_FOUND: 243 case MHD_HTTP_GONE: 244 /* nothing to check */ 245 break; 246 case MHD_HTTP_CONFLICT: 247 /* FIXME[oec]: Add this to the response-type and handle it here */ 248 break; 249 case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: 250 default: 251 /* Unsupported status code (by test harness) */ 252 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 253 "test command for age-withdraw not support status code %u, body:\n" 254 ">>%s<<\n", 255 response->hr.http_status, 256 json_dumps (response->hr.reply, JSON_INDENT (2))); 257 GNUNET_break (0); 258 break; 259 } 260 261 /* We are done with this command, pick the next one */ 262 TALER_TESTING_interpreter_next (is); 263 } 264 265 266 /** 267 * Run the command for age-withdraw. 268 */ 269 static void 270 age_withdraw_run ( 271 void *cls, 272 const struct TALER_TESTING_Command *cmd, 273 struct TALER_TESTING_Interpreter *is) 274 { 275 struct AgeWithdrawState *aws = cls; 276 struct TALER_EXCHANGE_Keys *keys = TALER_TESTING_get_keys (is); 277 const struct TALER_ReservePrivateKeyP *rp; 278 const struct TALER_TESTING_Command *create_reserve; 279 const struct TALER_EXCHANGE_DenomPublicKey *dpk; 280 281 aws->is = is; 282 283 /* Prepare the reserve related data */ 284 create_reserve 285 = TALER_TESTING_interpreter_lookup_command ( 286 is, 287 aws->reserve_reference); 288 289 if (NULL == create_reserve) 290 { 291 GNUNET_break (0); 292 TALER_TESTING_interpreter_fail (is); 293 return; 294 } 295 if (GNUNET_OK != 296 TALER_TESTING_get_trait_reserve_priv (create_reserve, 297 &rp)) 298 { 299 GNUNET_break (0); 300 TALER_TESTING_interpreter_fail (is); 301 return; 302 } 303 if (NULL == aws->exchange_url) 304 aws->exchange_url 305 = GNUNET_strdup (TALER_TESTING_get_exchange_url (is)); 306 aws->reserve_priv = *rp; 307 GNUNET_CRYPTO_eddsa_key_get_public (&aws->reserve_priv.eddsa_priv, 308 &aws->reserve_pub.eddsa_pub); 309 aws->reserve_payto_uri 310 = TALER_reserve_make_payto (aws->exchange_url, 311 &aws->reserve_pub); 312 313 aws->denoms_pub = GNUNET_new_array (aws->num_coins, 314 struct TALER_EXCHANGE_DenomPublicKey); 315 316 GNUNET_CRYPTO_random_block (&aws->seed, 317 sizeof(aws->seed)); 318 319 for (unsigned int i = 0; i<aws->num_coins; i++) 320 { 321 struct TALER_EXCHANGE_DenomPublicKey *denom_pub = &aws->denoms_pub[i]; 322 struct CoinOutputState *cos = &aws->coin_outputs[i]; 323 324 /* Find denomination */ 325 dpk = TALER_TESTING_find_pk (keys, 326 &cos->amount, 327 true); /* _always_ use denominations with age-striction */ 328 if (NULL == dpk) 329 { 330 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 331 "Failed to determine denomination key for amount at %s\n", 332 (NULL != cmd) ? cmd->label : "<retried command>"); 333 GNUNET_break (0); 334 TALER_TESTING_interpreter_fail (is); 335 return; 336 } 337 338 /* We copy the denomination key, as re-querying /keys 339 * would free the old one. */ 340 *denom_pub = *dpk; 341 TALER_denom_pub_copy (&denom_pub->key, 342 &dpk->key); 343 344 /* Accumulate the expected total amount and fee for the history */ 345 if (i == 0) 346 { 347 GNUNET_assert (GNUNET_OK == 348 TALER_amount_set_zero ( 349 cos->amount.currency, 350 &aws->reserve_history.amount)); 351 GNUNET_assert (GNUNET_OK == 352 TALER_amount_set_zero ( 353 denom_pub->fees.withdraw.currency, 354 &aws->reserve_history.details.withdraw.fee)); 355 } 356 GNUNET_assert (0 <= 357 TALER_amount_add (&aws->reserve_history.amount, 358 &aws->reserve_history.amount, 359 &cos->amount)); 360 GNUNET_assert (0 <= 361 TALER_amount_add (&aws->reserve_history.amount, 362 &aws->reserve_history.amount, 363 &denom_pub->fees.withdraw)); 364 GNUNET_assert (0 <= 365 TALER_amount_add (&aws->reserve_history.details.withdraw.fee, 366 &aws->reserve_history.details.withdraw.fee, 367 &denom_pub->fees.withdraw)); 368 369 } 370 /* Save the expected history entry */ 371 aws->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL; 372 aws->reserve_history.details.withdraw.age_restricted = true; 373 aws->reserve_history.details.withdraw.max_age = aws->max_age; 374 375 376 /* Execute the age-restricted variant of withdraw protocol */ 377 aws->handle = 378 TALER_EXCHANGE_post_withdraw_create ( 379 TALER_TESTING_interpreter_get_context (is), 380 TALER_TESTING_get_exchange_url (is), 381 keys, 382 rp, 383 aws->num_coins, 384 aws->denoms_pub, 385 &aws->seed, 386 aws->max_age); 387 if (NULL == aws->handle) 388 { 389 GNUNET_break (0); 390 TALER_TESTING_interpreter_fail (is); 391 return; 392 } 393 GNUNET_assert (GNUNET_OK == 394 TALER_EXCHANGE_post_withdraw_set_options ( 395 aws->handle, 396 TALER_EXCHANGE_post_withdraw_option_with_age_proof ( 397 aws->max_age))); 398 GNUNET_assert (TALER_EC_NONE == 399 TALER_EXCHANGE_post_withdraw_start (aws->handle, 400 &age_withdraw_cb, 401 aws)); 402 } 403 404 405 /** 406 * Free the state of a "age withdraw" CMD, and possibly cancel a 407 * pending operation thereof 408 * 409 * @param cls Closure of type `struct AgeWithdrawState` 410 * @param cmd The command being freed. 411 */ 412 static void 413 age_withdraw_cleanup ( 414 void *cls, 415 const struct TALER_TESTING_Command *cmd) 416 { 417 struct AgeWithdrawState *aws = cls; 418 419 if (NULL != aws->handle) 420 { 421 TALER_TESTING_command_incomplete (aws->is, 422 cmd->label); 423 TALER_EXCHANGE_post_withdraw_cancel (aws->handle); 424 aws->handle = NULL; 425 } 426 427 if (NULL != aws->denoms_pub) 428 { 429 for (size_t n = 0; n < aws->num_coins; n++) 430 TALER_denom_pub_free (&aws->denoms_pub[n].key); 431 432 GNUNET_free (aws->denoms_pub); 433 aws->denoms_pub = NULL; 434 } 435 436 if (NULL != aws->coin_outputs) 437 { 438 for (size_t n = 0; n < aws->num_coins; n++) 439 { 440 struct CoinOutputState *out = &aws->coin_outputs[n]; 441 TALER_age_commitment_proof_free (&out->details.age_commitment_proof); 442 TALER_denom_ewv_free (&out->details.blinding_values); 443 } 444 GNUNET_free (aws->coin_outputs); 445 aws->coin_outputs = NULL; 446 } 447 GNUNET_free (aws->selected_secrets); 448 aws->selected_secrets = NULL; 449 450 GNUNET_free (aws->exchange_url); 451 aws->exchange_url = NULL; 452 GNUNET_free (aws->reserve_payto_uri.normalized_payto); 453 aws->reserve_payto_uri.normalized_payto = NULL; 454 GNUNET_free (aws); 455 } 456 457 458 /** 459 * Offer internal data of a "age withdraw" CMD state to other commands. 460 * 461 * @param cls Closure of type `struct AgeWithdrawState` 462 * @param[out] ret result (could be anything) 463 * @param trait name of the trait 464 * @param idx index number of the object to offer. 465 * @return #GNUNET_OK on success 466 */ 467 static enum GNUNET_GenericReturnValue 468 age_withdraw_traits ( 469 void *cls, 470 const void **ret, 471 const char *trait, 472 unsigned int idx) 473 { 474 struct AgeWithdrawState *aws = cls; 475 struct CoinOutputState *out = &aws->coin_outputs[idx]; 476 struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *details = 477 &aws->coin_outputs[idx].details; 478 struct TALER_TESTING_Trait traits[] = { 479 /* history entry MUST be first due to response code logic below! */ 480 TALER_TESTING_make_trait_reserve_history (idx, 481 &aws->reserve_history), 482 TALER_TESTING_make_trait_denom_pub (idx, 483 &aws->denoms_pub[idx]), 484 TALER_TESTING_make_trait_reserve_priv (&aws->reserve_priv), 485 TALER_TESTING_make_trait_reserve_pub (&aws->reserve_pub), 486 TALER_TESTING_make_trait_withdraw_commitment (&aws->planchets_h), 487 TALER_TESTING_make_trait_amounts (idx, 488 &out->amount), 489 /* FIXME[oec]: add legal requirement to response and handle it here, as well 490 TALER_TESTING_make_trait_legi_requirement_row (&aws->requirement_row), 491 TALER_TESTING_make_trait_h_payto (&aws->h_payto), 492 */ 493 TALER_TESTING_make_trait_normalized_payto_uri (&aws->reserve_payto_uri), 494 TALER_TESTING_make_trait_exchange_url (aws->exchange_url), 495 TALER_TESTING_make_trait_coin_priv (idx, 496 &details->coin_priv), 497 TALER_TESTING_make_trait_withdraw_seed (&aws->seed), 498 TALER_TESTING_make_trait_planchet_secrets ( 499 idx, 500 (NULL != aws->selected_secrets) 501 ? &aws->selected_secrets[idx] 502 : NULL), 503 TALER_TESTING_make_trait_blinding_key (idx, 504 &details->blinding_key), 505 TALER_TESTING_make_trait_exchange_blinding_values (idx, 506 &details->blinding_values 507 ), 508 TALER_TESTING_make_trait_age_commitment_proof ( 509 idx, 510 &details->age_commitment_proof), 511 TALER_TESTING_make_trait_h_age_commitment ( 512 idx, 513 &details->h_age_commitment), 514 TALER_TESTING_trait_end () 515 }; 516 517 if (idx >= aws->num_coins) 518 return GNUNET_NO; 519 520 return TALER_TESTING_get_trait ((aws->expected_response_code == MHD_HTTP_OK) 521 ? &traits[0] /* we have reserve history */ 522 : &traits[1], /* skip reserve history */ 523 ret, 524 trait, 525 idx); 526 } 527 528 529 struct TALER_TESTING_Command 530 TALER_TESTING_cmd_withdraw_with_age_proof (const char *label, 531 const char *reserve_reference, 532 uint8_t max_age, 533 unsigned int 534 expected_response_code, 535 const char *amount, 536 ...) 537 { 538 struct AgeWithdrawState *aws; 539 unsigned int cnt; 540 va_list ap; 541 542 aws = GNUNET_new (struct AgeWithdrawState); 543 aws->reserve_reference = reserve_reference; 544 aws->expected_response_code = expected_response_code; 545 aws->mask = TALER_testing_age_restriction_mask; 546 aws->max_age = TALER_get_lowest_age (&aws->mask, 547 max_age); 548 cnt = 1; 549 va_start (ap, amount); 550 while (NULL != (va_arg (ap, const char *))) 551 cnt++; 552 aws->num_coins = cnt; 553 aws->coin_outputs = GNUNET_new_array (cnt, 554 struct CoinOutputState); 555 va_end (ap); 556 va_start (ap, amount); 557 558 for (unsigned int i = 0; i<aws->num_coins; i++) 559 { 560 struct CoinOutputState *out = &aws->coin_outputs[i]; 561 if (GNUNET_OK != 562 TALER_string_to_amount (amount, 563 &out->amount)) 564 { 565 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 566 "Failed to parse amount `%s' at %s\n", 567 amount, 568 label); 569 GNUNET_assert (0); 570 } 571 /* move on to next vararg! */ 572 amount = va_arg (ap, const char *); 573 } 574 575 GNUNET_assert (NULL == amount); 576 va_end (ap); 577 578 { 579 struct TALER_TESTING_Command cmd = { 580 .cls = aws, 581 .label = label, 582 .run = &age_withdraw_run, 583 .cleanup = &age_withdraw_cleanup, 584 .traits = &age_withdraw_traits, 585 }; 586 587 return cmd; 588 } 589 } 590 591 592 /** 593 * The state for the age-withdraw-reveal operation 594 */ 595 struct AgeRevealWithdrawState 596 { 597 /** 598 * The reference to the CMD resembling the previous call to age-withdraw 599 */ 600 const char *age_withdraw_reference; 601 602 /** 603 * The state to the previous age-withdraw command 604 */ 605 const struct AgeWithdrawState *aws; 606 607 /** 608 * The expected response code from the call to the 609 * age-withdraw-reveal operation 610 */ 611 unsigned int expected_response_code; 612 613 /** 614 * Interpreter state (during command) 615 */ 616 struct TALER_TESTING_Interpreter *is; 617 618 /** 619 * The handle to the reveal-operation 620 */ 621 struct TALER_EXCHANGE_PostRevealWithdrawHandle *handle; 622 623 624 /** 625 * Number of coins, extracted form the age withdraw command 626 */ 627 size_t num_coins; 628 629 /** 630 * The signatures of the @e num_coins coins returned 631 */ 632 struct TALER_DenominationSignature *denom_sigs; 633 634 }; 635 636 637 /** 638 * Callback for the reveal response 639 * 640 * @param awrs Closure of type `struct AgeRevealWithdrawState` 641 * @param response The response 642 */ 643 static void 644 age_reveal_withdraw_cb ( 645 struct AgeRevealWithdrawState *awrs, 646 const struct TALER_EXCHANGE_PostRevealWithdrawResponse *response) 647 { 648 struct TALER_TESTING_Interpreter *is = awrs->is; 649 650 awrs->handle = NULL; 651 if (awrs->expected_response_code != response->hr.http_status) 652 { 653 TALER_TESTING_unexpected_status_with_body (is, 654 response->hr.http_status, 655 awrs->expected_response_code, 656 response->hr.reply); 657 return; 658 } 659 switch (response->hr.http_status) 660 { 661 case MHD_HTTP_OK: 662 { 663 const struct AgeWithdrawState *aws = awrs->aws; 664 665 GNUNET_assert (awrs->num_coins == response->details.ok.num_sigs); 666 awrs->denom_sigs = GNUNET_new_array (awrs->num_coins, 667 struct TALER_DenominationSignature); 668 for (size_t n = 0; n < awrs->num_coins; n++) 669 { 670 GNUNET_assert (GNUNET_OK == 671 TALER_denom_sig_unblind ( 672 &awrs->denom_sigs[n], 673 &response->details.ok.blinded_denom_sigs[n], 674 &aws->coin_outputs[n].details.blinding_key, 675 &aws->coin_outputs[n].details.h_coin_pub, 676 &aws->coin_outputs[n].details.blinding_values, 677 &aws->denoms_pub[n].key)); 678 } 679 680 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 681 "age-withdraw reveal success!\n"); 682 } 683 break; 684 case MHD_HTTP_NOT_FOUND: 685 case MHD_HTTP_FORBIDDEN: 686 /* nothing to check */ 687 break; 688 /* FIXME[oec]: handle more cases !? */ 689 default: 690 /* Unsupported status code (by test harness) */ 691 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 692 "Age withdraw reveal test command does not support status code %u\n", 693 response->hr.http_status); 694 GNUNET_break (0); 695 break; 696 } 697 698 /* We are done with this command, pick the next one */ 699 TALER_TESTING_interpreter_next (is); 700 } 701 702 703 /** 704 * Run the command for age-withdraw-reveal 705 */ 706 static void 707 age_reveal_withdraw_run ( 708 void *cls, 709 const struct TALER_TESTING_Command *cmd, 710 struct TALER_TESTING_Interpreter *is) 711 { 712 struct AgeRevealWithdrawState *awrs = cls; 713 const struct TALER_TESTING_Command *age_withdraw_cmd; 714 const struct AgeWithdrawState *aws; 715 716 (void) cmd; 717 awrs->is = is; 718 719 /* 720 * Get the command and state for the previous call to "age witdraw" 721 */ 722 age_withdraw_cmd = 723 TALER_TESTING_interpreter_lookup_command (is, 724 awrs->age_withdraw_reference); 725 if (NULL == age_withdraw_cmd) 726 { 727 GNUNET_break (0); 728 TALER_TESTING_interpreter_fail (is); 729 return; 730 } 731 GNUNET_assert (age_withdraw_cmd->run == age_withdraw_run); 732 aws = age_withdraw_cmd->cls; 733 awrs->aws = aws; 734 awrs->num_coins = aws->num_coins; 735 736 { 737 struct TALER_RevealWithdrawMasterSeedsP revealed_seeds; 738 size_t j = 0; 739 for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) 740 { 741 if (aws->noreveal_index == k) 742 continue; 743 744 revealed_seeds.tuple[j] = aws->kappa_seed.tuple[k]; 745 j++; 746 } 747 748 awrs->handle = 749 TALER_EXCHANGE_post_reveal_withdraw_create ( 750 TALER_TESTING_interpreter_get_context (is), 751 TALER_TESTING_get_exchange_url (is), 752 aws->num_coins, 753 &aws->planchets_h, 754 &revealed_seeds); 755 GNUNET_assert (NULL != awrs->handle); 756 GNUNET_assert (TALER_EC_NONE == 757 TALER_EXCHANGE_post_reveal_withdraw_start ( 758 awrs->handle, 759 &age_reveal_withdraw_cb, 760 awrs)); 761 } 762 } 763 764 765 /** 766 * Free the state of a "age-withdraw-reveal" CMD, and possibly 767 * cancel a pending operation thereof 768 * 769 * @param cls Closure of type `struct AgeRevealWithdrawState` 770 * @param cmd The command being freed. 771 */ 772 static void 773 age_reveal_withdraw_cleanup ( 774 void *cls, 775 const struct TALER_TESTING_Command *cmd) 776 { 777 struct AgeRevealWithdrawState *awrs = cls; 778 779 if (NULL != awrs->handle) 780 { 781 TALER_TESTING_command_incomplete (awrs->is, 782 cmd->label); 783 TALER_EXCHANGE_post_reveal_withdraw_cancel (awrs->handle); 784 awrs->handle = NULL; 785 } 786 if (NULL != awrs->denom_sigs) 787 { 788 for (size_t n = 0; n < awrs->num_coins; n++) 789 { 790 TALER_denom_sig_free (&awrs->denom_sigs[n]); 791 } 792 GNUNET_free (awrs->denom_sigs); 793 } 794 GNUNET_free (awrs); 795 } 796 797 798 /** 799 * Offer internal data of a "age withdraw reveal" CMD state to other commands. 800 * 801 * @param cls Closure of they `struct AgeRevealWithdrawState` 802 * @param[out] ret result (could be anything) 803 * @param trait name of the trait 804 * @param idx index number of the object to offer. 805 * @return #GNUNET_OK on success 806 */ 807 static enum GNUNET_GenericReturnValue 808 age_reveal_withdraw_traits ( 809 void *cls, 810 const void **ret, 811 const char *trait, 812 unsigned int idx) 813 { 814 struct AgeRevealWithdrawState *awrs = cls; 815 struct TALER_TESTING_Trait traits[] = { 816 TALER_TESTING_make_trait_denom_sig (idx, 817 &awrs->denom_sigs[idx]), 818 TALER_TESTING_trait_end () 819 }; 820 821 if (NULL == awrs->denom_sigs) 822 return GNUNET_NO; 823 if (idx >= awrs->num_coins) 824 return GNUNET_NO; 825 if (GNUNET_OK == 826 TALER_TESTING_get_trait (traits, 827 ret, 828 trait, 829 idx)) 830 return GNUNET_OK; 831 /* everything else about the coins comes from the withdraw command */ 832 return age_withdraw_traits ((void *) awrs->aws, 833 ret, 834 trait, 835 idx); 836 } 837 838 839 struct TALER_TESTING_Command 840 TALER_TESTING_cmd_withdraw_reveal_age_proof ( 841 const char *label, 842 const char *age_withdraw_reference, 843 unsigned int expected_response_code) 844 { 845 struct AgeRevealWithdrawState *awrs = 846 GNUNET_new (struct AgeRevealWithdrawState); 847 848 awrs->age_withdraw_reference = age_withdraw_reference; 849 awrs->expected_response_code = expected_response_code; 850 { 851 struct TALER_TESTING_Command cmd = { 852 .cls = awrs, 853 .label = label, 854 .run = age_reveal_withdraw_run, 855 .cleanup = age_reveal_withdraw_cleanup, 856 .traits = age_reveal_withdraw_traits, 857 }; 858 859 return cmd; 860 } 861 } 862 863 864 /* end of testing_api_cmd_age_withdraw.c */