Line data Source code
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 5 : age_withdraw_cb (
197 : struct AgeWithdrawState *aws,
198 : const struct TALER_EXCHANGE_PostWithdrawResponse *response)
199 : {
200 5 : struct TALER_TESTING_Interpreter *is = aws->is;
201 :
202 5 : aws->handle = NULL;
203 5 : if (aws->expected_response_code != response->hr.http_status)
204 : {
205 0 : TALER_TESTING_unexpected_status_with_body (is,
206 : response->hr.http_status,
207 : aws->expected_response_code,
208 : response->hr.reply);
209 0 : return;
210 : }
211 :
212 5 : switch (response->hr.http_status)
213 : {
214 3 : case MHD_HTTP_CREATED:
215 3 : aws->noreveal_index = response->details.created.noreveal_index;
216 3 : aws->planchets_h = response->details.created.planchets_h;
217 3 : aws->selected_h = response->details.created.selected_h;
218 3 : aws->reserve_history.details.withdraw.planchets_h = aws->planchets_h;
219 3 : aws->reserve_history.details.withdraw.selected_h = aws->selected_h;
220 3 : aws->reserve_history.details.withdraw.noreveal_index = aws->noreveal_index;
221 3 : aws->kappa_seed = response->details.created.kappa_seed;
222 :
223 3 : GNUNET_assert (aws->num_coins == response->details.created.num_coins);
224 3 : GNUNET_assert (aws->noreveal_index < TALER_CNC_KAPPA);
225 3 : aws->selected_secrets = GNUNET_new_array (aws->num_coins,
226 : struct TALER_PlanchetMasterSecretP);
227 3 : TALER_withdraw_expand_secrets (aws->num_coins,
228 3 : &aws->kappa_seed.tuple[aws->noreveal_index],
229 : aws->selected_secrets);
230 10 : for (size_t n = 0; n < aws->num_coins; n++)
231 : {
232 7 : aws->coin_outputs[n].details = response->details.created.coin_details[n];
233 7 : TALER_age_commitment_proof_deep_copy (
234 7 : &aws->coin_outputs[n].details.age_commitment_proof,
235 7 : &response->details.created.coin_details[n].age_commitment_proof);
236 7 : TALER_denom_ewv_copy (
237 7 : &aws->coin_outputs[n].details.blinding_values,
238 7 : &response->details.created.coin_details[n].blinding_values);
239 : }
240 3 : break;
241 0 : case MHD_HTTP_FORBIDDEN:
242 : case MHD_HTTP_NOT_FOUND:
243 : case MHD_HTTP_GONE:
244 : /* nothing to check */
245 0 : break;
246 2 : case MHD_HTTP_CONFLICT:
247 : /* FIXME[oec]: Add this to the response-type and handle it here */
248 2 : break;
249 0 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
250 : default:
251 : /* Unsupported status code (by test harness) */
252 0 : 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 0 : GNUNET_break (0);
258 0 : break;
259 : }
260 :
261 : /* We are done with this command, pick the next one */
262 5 : TALER_TESTING_interpreter_next (is);
263 : }
264 :
265 :
266 : /**
267 : * Run the command for age-withdraw.
268 : */
269 : static void
270 5 : age_withdraw_run (
271 : void *cls,
272 : const struct TALER_TESTING_Command *cmd,
273 : struct TALER_TESTING_Interpreter *is)
274 : {
275 5 : struct AgeWithdrawState *aws = cls;
276 5 : 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 5 : aws->is = is;
282 :
283 : /* Prepare the reserve related data */
284 : create_reserve
285 5 : = TALER_TESTING_interpreter_lookup_command (
286 : is,
287 : aws->reserve_reference);
288 :
289 5 : if (NULL == create_reserve)
290 : {
291 0 : GNUNET_break (0);
292 0 : TALER_TESTING_interpreter_fail (is);
293 0 : return;
294 : }
295 5 : if (GNUNET_OK !=
296 5 : TALER_TESTING_get_trait_reserve_priv (create_reserve,
297 : &rp))
298 : {
299 0 : GNUNET_break (0);
300 0 : TALER_TESTING_interpreter_fail (is);
301 0 : return;
302 : }
303 5 : if (NULL == aws->exchange_url)
304 : aws->exchange_url
305 5 : = GNUNET_strdup (TALER_TESTING_get_exchange_url (is));
306 5 : aws->reserve_priv = *rp;
307 5 : GNUNET_CRYPTO_eddsa_key_get_public (&aws->reserve_priv.eddsa_priv,
308 : &aws->reserve_pub.eddsa_pub);
309 : aws->reserve_payto_uri
310 5 : = TALER_reserve_make_payto (aws->exchange_url,
311 5 : &aws->reserve_pub);
312 :
313 5 : aws->denoms_pub = GNUNET_new_array (aws->num_coins,
314 : struct TALER_EXCHANGE_DenomPublicKey);
315 :
316 5 : GNUNET_CRYPTO_random_block (&aws->seed,
317 : sizeof(aws->seed));
318 :
319 14 : for (unsigned int i = 0; i<aws->num_coins; i++)
320 : {
321 9 : struct TALER_EXCHANGE_DenomPublicKey *denom_pub = &aws->denoms_pub[i];
322 9 : struct CoinOutputState *cos = &aws->coin_outputs[i];
323 :
324 : /* Find denomination */
325 9 : dpk = TALER_TESTING_find_pk (keys,
326 9 : &cos->amount,
327 : true); /* _always_ use denominations with age-striction */
328 9 : if (NULL == dpk)
329 : {
330 0 : 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 0 : GNUNET_break (0);
334 0 : TALER_TESTING_interpreter_fail (is);
335 0 : return;
336 : }
337 :
338 : /* We copy the denomination key, as re-querying /keys
339 : * would free the old one. */
340 9 : *denom_pub = *dpk;
341 9 : TALER_denom_pub_copy (&denom_pub->key,
342 : &dpk->key);
343 :
344 : /* Accumulate the expected total amount and fee for the history */
345 9 : if (i == 0)
346 : {
347 5 : GNUNET_assert (GNUNET_OK ==
348 : TALER_amount_set_zero (
349 : cos->amount.currency,
350 : &aws->reserve_history.amount));
351 5 : GNUNET_assert (GNUNET_OK ==
352 : TALER_amount_set_zero (
353 : denom_pub->fees.withdraw.currency,
354 : &aws->reserve_history.details.withdraw.fee));
355 : }
356 9 : GNUNET_assert (0 <=
357 : TALER_amount_add (&aws->reserve_history.amount,
358 : &aws->reserve_history.amount,
359 : &cos->amount));
360 9 : GNUNET_assert (0 <=
361 : TALER_amount_add (&aws->reserve_history.amount,
362 : &aws->reserve_history.amount,
363 : &denom_pub->fees.withdraw));
364 9 : 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 5 : aws->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL;
372 5 : aws->reserve_history.details.withdraw.age_restricted = true;
373 5 : aws->reserve_history.details.withdraw.max_age = aws->max_age;
374 :
375 :
376 : /* Execute the age-restricted variant of withdraw protocol */
377 5 : aws->handle =
378 5 : 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 5 : aws->denoms_pub,
385 5 : &aws->seed,
386 5 : aws->max_age);
387 5 : if (NULL == aws->handle)
388 : {
389 0 : GNUNET_break (0);
390 0 : TALER_TESTING_interpreter_fail (is);
391 0 : return;
392 : }
393 5 : 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 5 : 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 5 : age_withdraw_cleanup (
414 : void *cls,
415 : const struct TALER_TESTING_Command *cmd)
416 : {
417 5 : struct AgeWithdrawState *aws = cls;
418 :
419 5 : if (NULL != aws->handle)
420 : {
421 0 : TALER_TESTING_command_incomplete (aws->is,
422 : cmd->label);
423 0 : TALER_EXCHANGE_post_withdraw_cancel (aws->handle);
424 0 : aws->handle = NULL;
425 : }
426 :
427 5 : if (NULL != aws->denoms_pub)
428 : {
429 14 : for (size_t n = 0; n < aws->num_coins; n++)
430 9 : TALER_denom_pub_free (&aws->denoms_pub[n].key);
431 :
432 5 : GNUNET_free (aws->denoms_pub);
433 5 : aws->denoms_pub = NULL;
434 : }
435 :
436 5 : if (NULL != aws->coin_outputs)
437 : {
438 14 : for (size_t n = 0; n < aws->num_coins; n++)
439 : {
440 9 : struct CoinOutputState *out = &aws->coin_outputs[n];
441 9 : TALER_age_commitment_proof_free (&out->details.age_commitment_proof);
442 9 : TALER_denom_ewv_free (&out->details.blinding_values);
443 : }
444 5 : GNUNET_free (aws->coin_outputs);
445 5 : aws->coin_outputs = NULL;
446 : }
447 5 : GNUNET_free (aws->selected_secrets);
448 5 : aws->selected_secrets = NULL;
449 :
450 5 : GNUNET_free (aws->exchange_url);
451 5 : aws->exchange_url = NULL;
452 5 : GNUNET_free (aws->reserve_payto_uri.normalized_payto);
453 5 : aws->reserve_payto_uri.normalized_payto = NULL;
454 5 : GNUNET_free (aws);
455 5 : }
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 54 : age_withdraw_traits (
469 : void *cls,
470 : const void **ret,
471 : const char *trait,
472 : unsigned int idx)
473 : {
474 54 : struct AgeWithdrawState *aws = cls;
475 54 : struct CoinOutputState *out = &aws->coin_outputs[idx];
476 54 : struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *details =
477 54 : &aws->coin_outputs[idx].details;
478 : struct TALER_TESTING_Trait traits[] = {
479 : /* history entry MUST be first due to response code logic below! */
480 54 : TALER_TESTING_make_trait_reserve_history (idx,
481 54 : &aws->reserve_history),
482 54 : TALER_TESTING_make_trait_denom_pub (idx,
483 54 : &aws->denoms_pub[idx]),
484 54 : TALER_TESTING_make_trait_reserve_priv (&aws->reserve_priv),
485 54 : TALER_TESTING_make_trait_reserve_pub (&aws->reserve_pub),
486 54 : TALER_TESTING_make_trait_withdraw_commitment (&aws->planchets_h),
487 54 : TALER_TESTING_make_trait_amounts (idx,
488 54 : &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 54 : TALER_TESTING_make_trait_normalized_payto_uri (&aws->reserve_payto_uri),
494 54 : TALER_TESTING_make_trait_exchange_url (aws->exchange_url),
495 54 : TALER_TESTING_make_trait_coin_priv (idx,
496 54 : &details->coin_priv),
497 54 : TALER_TESTING_make_trait_withdraw_seed (&aws->seed),
498 54 : TALER_TESTING_make_trait_planchet_secrets (
499 : idx,
500 54 : (NULL != aws->selected_secrets)
501 52 : ? &aws->selected_secrets[idx]
502 : : NULL),
503 54 : TALER_TESTING_make_trait_blinding_key (idx,
504 54 : &details->blinding_key),
505 54 : TALER_TESTING_make_trait_exchange_blinding_values (idx,
506 54 : &details->blinding_values
507 : ),
508 54 : TALER_TESTING_make_trait_age_commitment_proof (
509 : idx,
510 54 : &details->age_commitment_proof),
511 54 : TALER_TESTING_make_trait_h_age_commitment (
512 : idx,
513 54 : &details->h_age_commitment),
514 54 : TALER_TESTING_trait_end ()
515 : };
516 :
517 54 : if (idx >= aws->num_coins)
518 0 : return GNUNET_NO;
519 :
520 54 : 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 5 : 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 5 : aws = GNUNET_new (struct AgeWithdrawState);
543 5 : aws->reserve_reference = reserve_reference;
544 5 : aws->expected_response_code = expected_response_code;
545 5 : aws->mask = TALER_testing_age_restriction_mask;
546 5 : aws->max_age = TALER_get_lowest_age (&aws->mask,
547 : max_age);
548 5 : cnt = 1;
549 5 : va_start (ap, amount);
550 9 : while (NULL != (va_arg (ap, const char *)))
551 4 : cnt++;
552 5 : aws->num_coins = cnt;
553 5 : aws->coin_outputs = GNUNET_new_array (cnt,
554 : struct CoinOutputState);
555 5 : va_end (ap);
556 5 : va_start (ap, amount);
557 :
558 14 : for (unsigned int i = 0; i<aws->num_coins; i++)
559 : {
560 9 : struct CoinOutputState *out = &aws->coin_outputs[i];
561 9 : if (GNUNET_OK !=
562 9 : TALER_string_to_amount (amount,
563 : &out->amount))
564 : {
565 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
566 : "Failed to parse amount `%s' at %s\n",
567 : amount,
568 : label);
569 0 : GNUNET_assert (0);
570 : }
571 : /* move on to next vararg! */
572 9 : amount = va_arg (ap, const char *);
573 : }
574 :
575 5 : GNUNET_assert (NULL == amount);
576 5 : va_end (ap);
577 :
578 : {
579 5 : 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 5 : 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 3 : age_reveal_withdraw_cb (
645 : struct AgeRevealWithdrawState *awrs,
646 : const struct TALER_EXCHANGE_PostRevealWithdrawResponse *response)
647 : {
648 3 : struct TALER_TESTING_Interpreter *is = awrs->is;
649 :
650 3 : awrs->handle = NULL;
651 3 : if (awrs->expected_response_code != response->hr.http_status)
652 : {
653 0 : TALER_TESTING_unexpected_status_with_body (is,
654 : response->hr.http_status,
655 : awrs->expected_response_code,
656 : response->hr.reply);
657 0 : return;
658 : }
659 3 : switch (response->hr.http_status)
660 : {
661 3 : case MHD_HTTP_OK:
662 : {
663 3 : const struct AgeWithdrawState *aws = awrs->aws;
664 :
665 3 : GNUNET_assert (awrs->num_coins == response->details.ok.num_sigs);
666 3 : awrs->denom_sigs = GNUNET_new_array (awrs->num_coins,
667 : struct TALER_DenominationSignature);
668 10 : for (size_t n = 0; n < awrs->num_coins; n++)
669 : {
670 7 : 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 3 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
681 : "age-withdraw reveal success!\n");
682 : }
683 3 : break;
684 0 : case MHD_HTTP_NOT_FOUND:
685 : case MHD_HTTP_FORBIDDEN:
686 : /* nothing to check */
687 0 : break;
688 : /* FIXME[oec]: handle more cases !? */
689 0 : default:
690 : /* Unsupported status code (by test harness) */
691 0 : 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 0 : GNUNET_break (0);
695 0 : break;
696 : }
697 :
698 : /* We are done with this command, pick the next one */
699 3 : TALER_TESTING_interpreter_next (is);
700 : }
701 :
702 :
703 : /**
704 : * Run the command for age-withdraw-reveal
705 : */
706 : static void
707 3 : age_reveal_withdraw_run (
708 : void *cls,
709 : const struct TALER_TESTING_Command *cmd,
710 : struct TALER_TESTING_Interpreter *is)
711 : {
712 3 : struct AgeRevealWithdrawState *awrs = cls;
713 : const struct TALER_TESTING_Command *age_withdraw_cmd;
714 : const struct AgeWithdrawState *aws;
715 :
716 : (void) cmd;
717 3 : awrs->is = is;
718 :
719 : /*
720 : * Get the command and state for the previous call to "age witdraw"
721 : */
722 : age_withdraw_cmd =
723 3 : TALER_TESTING_interpreter_lookup_command (is,
724 : awrs->age_withdraw_reference);
725 3 : if (NULL == age_withdraw_cmd)
726 : {
727 0 : GNUNET_break (0);
728 0 : TALER_TESTING_interpreter_fail (is);
729 0 : return;
730 : }
731 3 : GNUNET_assert (age_withdraw_cmd->run == age_withdraw_run);
732 3 : aws = age_withdraw_cmd->cls;
733 3 : awrs->aws = aws;
734 3 : awrs->num_coins = aws->num_coins;
735 :
736 : {
737 : struct TALER_RevealWithdrawMasterSeedsP revealed_seeds;
738 3 : size_t j = 0;
739 12 : for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
740 : {
741 9 : if (aws->noreveal_index == k)
742 3 : continue;
743 :
744 6 : revealed_seeds.tuple[j] = aws->kappa_seed.tuple[k];
745 6 : j++;
746 : }
747 :
748 3 : awrs->handle =
749 3 : TALER_EXCHANGE_post_reveal_withdraw_create (
750 : TALER_TESTING_interpreter_get_context (is),
751 : TALER_TESTING_get_exchange_url (is),
752 3 : aws->num_coins,
753 : &aws->reserve_pub,
754 : &aws->planchets_h,
755 : &revealed_seeds);
756 3 : GNUNET_assert (NULL != awrs->handle);
757 3 : GNUNET_assert (TALER_EC_NONE ==
758 : TALER_EXCHANGE_post_reveal_withdraw_start (
759 : awrs->handle,
760 : &age_reveal_withdraw_cb,
761 : awrs));
762 : }
763 : }
764 :
765 :
766 : /**
767 : * Free the state of a "age-withdraw-reveal" CMD, and possibly
768 : * cancel a pending operation thereof
769 : *
770 : * @param cls Closure of type `struct AgeRevealWithdrawState`
771 : * @param cmd The command being freed.
772 : */
773 : static void
774 3 : age_reveal_withdraw_cleanup (
775 : void *cls,
776 : const struct TALER_TESTING_Command *cmd)
777 : {
778 3 : struct AgeRevealWithdrawState *awrs = cls;
779 :
780 3 : if (NULL != awrs->handle)
781 : {
782 0 : TALER_TESTING_command_incomplete (awrs->is,
783 : cmd->label);
784 0 : TALER_EXCHANGE_post_reveal_withdraw_cancel (awrs->handle);
785 0 : awrs->handle = NULL;
786 : }
787 3 : if (NULL != awrs->denom_sigs)
788 : {
789 10 : for (size_t n = 0; n < awrs->num_coins; n++)
790 : {
791 7 : TALER_denom_sig_free (&awrs->denom_sigs[n]);
792 : }
793 3 : GNUNET_free (awrs->denom_sigs);
794 : }
795 3 : GNUNET_free (awrs);
796 3 : }
797 :
798 :
799 : /**
800 : * Offer internal data of a "age withdraw reveal" CMD state to other commands.
801 : *
802 : * @param cls Closure of they `struct AgeRevealWithdrawState`
803 : * @param[out] ret result (could be anything)
804 : * @param trait name of the trait
805 : * @param idx index number of the object to offer.
806 : * @return #GNUNET_OK on success
807 : */
808 : static enum GNUNET_GenericReturnValue
809 50 : age_reveal_withdraw_traits (
810 : void *cls,
811 : const void **ret,
812 : const char *trait,
813 : unsigned int idx)
814 : {
815 50 : struct AgeRevealWithdrawState *awrs = cls;
816 : struct TALER_TESTING_Trait traits[] = {
817 50 : TALER_TESTING_make_trait_denom_sig (idx,
818 50 : &awrs->denom_sigs[idx]),
819 50 : TALER_TESTING_trait_end ()
820 : };
821 :
822 50 : if (NULL == awrs->denom_sigs)
823 0 : return GNUNET_NO;
824 50 : if (idx >= awrs->num_coins)
825 2 : return GNUNET_NO;
826 48 : if (GNUNET_OK ==
827 48 : TALER_TESTING_get_trait (traits,
828 : ret,
829 : trait,
830 : idx))
831 4 : return GNUNET_OK;
832 : /* everything else about the coins comes from the withdraw command */
833 44 : return age_withdraw_traits ((void *) awrs->aws,
834 : ret,
835 : trait,
836 : idx);
837 : }
838 :
839 :
840 : struct TALER_TESTING_Command
841 3 : TALER_TESTING_cmd_withdraw_reveal_age_proof (
842 : const char *label,
843 : const char *age_withdraw_reference,
844 : unsigned int expected_response_code)
845 : {
846 : struct AgeRevealWithdrawState *awrs =
847 3 : GNUNET_new (struct AgeRevealWithdrawState);
848 :
849 3 : awrs->age_withdraw_reference = age_withdraw_reference;
850 3 : awrs->expected_response_code = expected_response_code;
851 : {
852 3 : struct TALER_TESTING_Command cmd = {
853 : .cls = awrs,
854 : .label = label,
855 : .run = age_reveal_withdraw_run,
856 : .cleanup = age_reveal_withdraw_cleanup,
857 : .traits = age_reveal_withdraw_traits,
858 : };
859 :
860 3 : return cmd;
861 : }
862 : }
863 :
864 :
865 : /* end of testing_api_cmd_age_withdraw.c */
|