Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2018-2026 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file testing/testing_api_cmd_recoup_refresh.c
18 : * @brief Implement the /recoup-refresh test command.
19 : * @author Marcello Stanisci
20 : * @author Christian Grothoff
21 : * @author Özgür Kesim
22 : */
23 : #include "platform.h"
24 : #include "taler/taler_json_lib.h"
25 : #include <gnunet/gnunet_curl_lib.h>
26 : #include "taler/taler_testing_lib.h"
27 :
28 :
29 : /**
30 : * State for a "recoup-refresh" CMD.
31 : */
32 : struct RecoupRefreshState
33 : {
34 : /**
35 : * Expected HTTP status code.
36 : */
37 : unsigned int expected_response_code;
38 :
39 : /**
40 : * Label of the reveal-melt command the fresh coins came from.
41 : */
42 : char *reveal_reference;
43 :
44 : /**
45 : * Label of the melt command.
46 : */
47 : const char *melt_reference;
48 :
49 : /**
50 : * Indices (in the reveal command) of the coins to recoup.
51 : */
52 : unsigned int *indices;
53 :
54 : /**
55 : * Number of entries in @e indices, @e ches_new, @e ches_old
56 : * and @e coin_pubs.
57 : */
58 : size_t num_indices;
59 :
60 : /**
61 : * Amount we expect to be recouped per coin, invalid if
62 : * not specified.
63 : */
64 : struct TALER_Amount amount;
65 :
66 : /**
67 : * Interpreter state.
68 : */
69 : struct TALER_TESTING_Interpreter *is;
70 :
71 : /**
72 : * Handle to the request.
73 : */
74 : struct TALER_EXCHANGE_PostRecoupRefreshHandle *ph;
75 :
76 : /**
77 : * Old coin that is credited.
78 : */
79 : struct TALER_CoinSpendPublicKeyP old_coin_pub;
80 :
81 : /**
82 : * Coin history entries we expect for the recouped coins.
83 : */
84 : struct TALER_EXCHANGE_CoinHistoryEntry *ches_new;
85 :
86 : /**
87 : * Coin history entries we expect for the old coin, one per
88 : * recouped coin.
89 : */
90 : struct TALER_EXCHANGE_CoinHistoryEntry *ches_old;
91 :
92 : /**
93 : * Public keys of the recouped coins.
94 : */
95 : struct TALER_CoinSpendPublicKeyP *coin_pubs;
96 : };
97 :
98 :
99 : /**
100 : * Check the response.
101 : *
102 : * @param cls closure, our `struct RecoupRefreshState`
103 : * @param rrr response details
104 : */
105 : static void
106 20 : recoup_refresh_cb (void *cls,
107 : const struct TALER_EXCHANGE_PostRecoupRefreshResponse *rrr)
108 : {
109 20 : struct RecoupRefreshState *rrs = cls;
110 20 : const struct TALER_EXCHANGE_HttpResponse *hr = &rrr->hr;
111 20 : struct TALER_TESTING_Interpreter *is = rrs->is;
112 :
113 20 : rrs->ph = NULL;
114 20 : if (rrs->expected_response_code != hr->http_status)
115 : {
116 0 : TALER_TESTING_unexpected_status (is,
117 : hr->http_status,
118 : rrs->expected_response_code);
119 0 : return;
120 : }
121 20 : switch (hr->http_status)
122 : {
123 18 : case MHD_HTTP_OK:
124 18 : if (0 != GNUNET_memcmp (&rrr->details.ok.old_coin_pub,
125 : &rrs->old_coin_pub))
126 : {
127 0 : GNUNET_break (0);
128 0 : TALER_TESTING_interpreter_fail (is);
129 0 : return;
130 : }
131 18 : if (rrr->details.ok.num_recoups != rrs->num_indices)
132 : {
133 0 : GNUNET_break (0);
134 0 : TALER_TESTING_interpreter_fail (is);
135 0 : return;
136 : }
137 36 : for (size_t i = 0; i < rrs->num_indices; i++)
138 : {
139 18 : const struct TALER_RecoupedCoin *rc = &rrr->details.ok.recoups[i];
140 :
141 18 : if (0 != GNUNET_memcmp (&rc->coin_pub,
142 : &rrs->coin_pubs[i]))
143 : {
144 0 : GNUNET_break (0);
145 0 : TALER_TESTING_interpreter_fail (is);
146 0 : return;
147 : }
148 18 : if ( (GNUNET_OK ==
149 36 : TALER_amount_is_valid (&rrs->amount)) &&
150 18 : (0 != TALER_amount_cmp (&rc->amount,
151 18 : &rrs->amount)) )
152 : {
153 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
154 : "Recouped %s for coin %u, expected %s\n",
155 : TALER_amount2s (&rc->amount),
156 : rrs->indices[i],
157 : TALER_amount2s (&rrs->amount));
158 0 : GNUNET_break (0);
159 0 : TALER_TESTING_interpreter_fail (is);
160 0 : return;
161 : }
162 18 : rrs->ches_new[i].amount = rc->amount;
163 18 : rrs->ches_old[i].amount = rc->amount;
164 : }
165 18 : break;
166 2 : default:
167 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
168 : "Recoup-refresh failed as expected with HTTP status %u/%d\n",
169 : hr->http_status,
170 : (int) hr->ec);
171 2 : break;
172 : }
173 20 : TALER_TESTING_interpreter_next (is);
174 : }
175 :
176 :
177 : /**
178 : * Run the command.
179 : *
180 : * @param cls closure.
181 : * @param cmd the command to execute.
182 : * @param is the interpreter state.
183 : */
184 : static void
185 20 : recoup_refresh_run (void *cls,
186 : const struct TALER_TESTING_Command *cmd,
187 : struct TALER_TESTING_Interpreter *is)
188 : {
189 20 : struct RecoupRefreshState *rrs = cls;
190 : const struct TALER_TESTING_Command *reveal_cmd;
191 : const struct TALER_TESTING_Command *melt_cmd;
192 : const struct TALER_CoinSpendPublicKeyP *old_coin_pub;
193 : const struct TALER_RefreshCommitmentP *rc;
194 20 : const struct TALER_BlindingMasterSeedP *blinding_seed = NULL;
195 : const unsigned int *num_coins_p;
196 : size_t num_coins;
197 :
198 : (void) cmd;
199 20 : rrs->is = is;
200 20 : reveal_cmd = TALER_TESTING_interpreter_lookup_command (is,
201 20 : rrs->reveal_reference);
202 20 : melt_cmd = TALER_TESTING_interpreter_lookup_command (is,
203 : rrs->melt_reference);
204 20 : if ( (NULL == reveal_cmd) ||
205 : (NULL == melt_cmd) )
206 : {
207 0 : GNUNET_break (0);
208 0 : TALER_TESTING_interpreter_fail (is);
209 0 : return;
210 : }
211 20 : if ( (GNUNET_OK !=
212 20 : TALER_TESTING_get_trait_coin_pub (melt_cmd,
213 : 0,
214 20 : &old_coin_pub)) ||
215 : (GNUNET_OK !=
216 20 : TALER_TESTING_get_trait_refresh_commitment (melt_cmd,
217 20 : &rc)) ||
218 : (GNUNET_OK !=
219 20 : TALER_TESTING_get_trait_array_length (reveal_cmd,
220 : &num_coins_p)) )
221 : {
222 0 : GNUNET_break (0);
223 0 : TALER_TESTING_interpreter_fail (is);
224 0 : return;
225 : }
226 : /* NULL if the melt used no CS denomination */
227 20 : if (GNUNET_OK !=
228 20 : TALER_TESTING_get_trait_blinding_seed (melt_cmd,
229 : &blinding_seed))
230 0 : blinding_seed = NULL;
231 20 : rrs->old_coin_pub = *old_coin_pub;
232 20 : num_coins = *num_coins_p;
233 20 : if (0 == num_coins)
234 : {
235 0 : GNUNET_break (0);
236 0 : TALER_TESTING_interpreter_fail (is);
237 0 : return;
238 : }
239 40 : for (size_t i = 0; i < rrs->num_indices; i++)
240 20 : if (rrs->indices[i] >= num_coins)
241 : {
242 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
243 : "Coin index %u out of range for command %s (%u coins)\n",
244 : rrs->indices[i],
245 : rrs->reveal_reference,
246 : (unsigned int) num_coins);
247 0 : GNUNET_break (0);
248 0 : TALER_TESTING_interpreter_fail (is);
249 0 : return;
250 : }
251 20 : {
252 20 : struct TALER_EXCHANGE_RecoupCoin coins[num_coins];
253 20 : size_t k = 0;
254 :
255 20 : memset (coins,
256 : 0,
257 : sizeof (coins));
258 88 : for (size_t i = 0; i < num_coins; i++)
259 : {
260 68 : struct TALER_EXCHANGE_RecoupCoin *c = &coins[i];
261 68 : const struct TALER_AgeCommitmentHashP *hac = NULL;
262 68 : bool recoup = false;
263 :
264 68 : if ( (GNUNET_OK !=
265 68 : TALER_TESTING_get_trait_denom_pub (reveal_cmd,
266 : (unsigned int) i,
267 68 : &c->pk)) ||
268 : (GNUNET_OK !=
269 68 : TALER_TESTING_get_trait_planchet_secrets (reveal_cmd,
270 : (unsigned int) i,
271 68 : &c->ps)) ||
272 : (GNUNET_OK !=
273 68 : TALER_TESTING_get_trait_exchange_blinding_values (
274 : melt_cmd,
275 : (unsigned int) i,
276 : &c->exchange_vals)) )
277 : {
278 0 : GNUNET_break (0);
279 0 : TALER_TESTING_interpreter_fail (is);
280 0 : return;
281 : }
282 : /* only denominations with age restriction have a commitment */
283 68 : if ( (0 != c->pk->key.age_mask.bits) &&
284 : (GNUNET_OK ==
285 0 : TALER_TESTING_get_trait_h_age_commitment (reveal_cmd,
286 : (unsigned int) i,
287 : &hac)) )
288 0 : c->h_age_commitment = hac; /* may still be NULL */
289 136 : for (size_t j = 0; j < rrs->num_indices; j++)
290 68 : if (rrs->indices[j] == i)
291 20 : recoup = true;
292 68 : if (! recoup)
293 48 : continue;
294 20 : if (GNUNET_OK !=
295 20 : TALER_TESTING_get_trait_denom_sig (reveal_cmd,
296 : (unsigned int) i,
297 : &c->denom_sig))
298 : {
299 0 : GNUNET_break (0);
300 0 : TALER_TESTING_interpreter_fail (is);
301 0 : return;
302 : }
303 : /* what the histories should show for this coin and the old coin */
304 : {
305 20 : struct TALER_EXCHANGE_CoinHistoryEntry *che_new = &rrs->ches_new[k];
306 20 : struct TALER_EXCHANGE_CoinHistoryEntry *che_old = &rrs->ches_old[k];
307 : struct TALER_CoinSpendPrivateKeyP coin_priv;
308 20 : const struct TALER_ExchangeBlindingValues *vals = c->exchange_vals;
309 :
310 20 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
311 : "Trying to recoup-refresh coin %u of denomination '%s'\n",
312 : (unsigned int) i,
313 : TALER_B2S (&c->pk->h_key));
314 20 : TALER_planchet_setup_coin_priv (c->ps,
315 : vals,
316 : &coin_priv);
317 20 : GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
318 20 : &rrs->coin_pubs[k].eddsa_pub);
319 20 : che_new->type = TALER_EXCHANGE_CTT_RECOUP_REFRESH;
320 20 : che_new->amount = rrs->amount;
321 20 : che_new->details.recoup_refresh.old_coin_pub = rrs->old_coin_pub;
322 20 : che_new->details.recoup_refresh.rc = *rc;
323 20 : che_new->details.recoup_refresh.coin_index = (uint32_t) i;
324 20 : TALER_planchet_blinding_secret_create (
325 : c->ps,
326 : vals,
327 : &che_new->details.recoup_refresh.coin_bks);
328 : /* exercise the documented contract: the request needs no
329 : exchange values for RSA denominations */
330 20 : if (GNUNET_CRYPTO_BSA_RSA == c->pk->key.bsign_pub_key->cipher)
331 10 : c->exchange_vals = NULL;
332 20 : TALER_wallet_recoup_refresh_sign (
333 20 : &c->pk->h_key,
334 20 : &che_new->details.recoup_refresh.coin_bks,
335 : &coin_priv,
336 : &che_new->details.recoup_refresh.coin_sig);
337 20 : che_old->type = TALER_EXCHANGE_CTT_OLD_COIN_RECOUP;
338 20 : che_old->amount = rrs->amount;
339 20 : che_old->details.old_coin_recoup.new_coin_pub = rrs->coin_pubs[k];
340 20 : che_old->details.old_coin_recoup.rc = *rc;
341 20 : k++;
342 : }
343 : }
344 20 : rrs->ph = TALER_EXCHANGE_post_recoup_refresh_create (
345 : TALER_TESTING_interpreter_get_context (is),
346 : TALER_TESTING_get_exchange_url (is),
347 : TALER_TESTING_get_keys (is),
348 20 : &rrs->old_coin_pub,
349 : rc,
350 : blinding_seed,
351 : num_coins,
352 : coins);
353 : }
354 20 : if (NULL == rrs->ph)
355 : {
356 0 : GNUNET_break (0);
357 0 : TALER_TESTING_interpreter_fail (is);
358 0 : return;
359 : }
360 20 : GNUNET_assert (TALER_EC_NONE ==
361 : TALER_EXCHANGE_post_recoup_refresh_start (rrs->ph,
362 : &recoup_refresh_cb,
363 : rrs));
364 : }
365 :
366 :
367 : /**
368 : * Cleanup the state.
369 : *
370 : * @param cls closure, must be a `struct RecoupRefreshState`.
371 : * @param cmd the command which is being cleaned up.
372 : */
373 : static void
374 20 : recoup_refresh_cleanup (void *cls,
375 : const struct TALER_TESTING_Command *cmd)
376 : {
377 20 : struct RecoupRefreshState *rrs = cls;
378 :
379 : (void) cmd;
380 20 : if (NULL != rrs->ph)
381 : {
382 0 : TALER_EXCHANGE_post_recoup_refresh_cancel (rrs->ph);
383 0 : rrs->ph = NULL;
384 : }
385 20 : GNUNET_free (rrs->reveal_reference);
386 20 : GNUNET_free (rrs->indices);
387 20 : GNUNET_free (rrs->ches_new);
388 20 : GNUNET_free (rrs->ches_old);
389 20 : GNUNET_free (rrs->coin_pubs);
390 20 : GNUNET_free (rrs);
391 20 : }
392 :
393 :
394 : /**
395 : * Offer internal data to other commands: for index k below the
396 : * number of recouped coins, the recouped coin and its history
397 : * entry; for the following indices, the old coin and its history
398 : * entry for the respective recoup.
399 : *
400 : * @param cls closure
401 : * @param[out] ret result (could be anything)
402 : * @param trait name of the trait
403 : * @param index index number of the object to extract.
404 : * @return #GNUNET_OK on success
405 : */
406 : static enum GNUNET_GenericReturnValue
407 72 : recoup_refresh_traits (void *cls,
408 : const void **ret,
409 : const char *trait,
410 : unsigned int index)
411 : {
412 72 : struct RecoupRefreshState *rrs = cls;
413 : size_t k;
414 : bool old;
415 :
416 72 : if (index >= 2 * rrs->num_indices)
417 18 : return GNUNET_NO;
418 54 : old = (index >= rrs->num_indices);
419 54 : k = old ? index - rrs->num_indices : index;
420 54 : if (GNUNET_OK !=
421 54 : TALER_amount_is_valid (&rrs->ches_new[k].amount))
422 0 : return GNUNET_SYSERR; /* request did not succeed: no traits */
423 : {
424 : struct TALER_TESTING_Trait traits[] = {
425 108 : TALER_TESTING_make_trait_coin_history (index,
426 : old
427 18 : ? &rrs->ches_old[k]
428 36 : : &rrs->ches_new[k]),
429 90 : TALER_TESTING_make_trait_coin_pub (index,
430 : old
431 : ? &rrs->old_coin_pub
432 36 : : &rrs->coin_pubs[k]),
433 54 : TALER_TESTING_trait_end ()
434 : };
435 :
436 54 : return TALER_TESTING_get_trait (traits,
437 : ret,
438 : trait,
439 : index);
440 : }
441 : }
442 :
443 :
444 : /**
445 : * Create the command state shared by both constructors.
446 : *
447 : * @param label the command label
448 : * @param expected_response_code expected HTTP status code
449 : * @param reveal_reference label of the reveal-melt command
450 : * @param indices coins to recoup, becomes owned by the state
451 : * @param num_indices length of @a indices
452 : * @param melt_reference label of the melt command
453 : * @param amount expected amount per coin, NULL for none
454 : * @return the command
455 : */
456 : static struct TALER_TESTING_Command
457 20 : make_recoup_refresh_cmd (const char *label,
458 : unsigned int expected_response_code,
459 : const char *reveal_reference,
460 : unsigned int *indices,
461 : size_t num_indices,
462 : const char *melt_reference,
463 : const char *amount)
464 : {
465 : struct RecoupRefreshState *rrs;
466 :
467 20 : rrs = GNUNET_new (struct RecoupRefreshState);
468 20 : rrs->expected_response_code = expected_response_code;
469 20 : rrs->reveal_reference = GNUNET_strdup (reveal_reference);
470 20 : rrs->melt_reference = melt_reference;
471 20 : rrs->indices = indices;
472 20 : rrs->num_indices = num_indices;
473 20 : rrs->ches_new = GNUNET_new_array (num_indices,
474 : struct TALER_EXCHANGE_CoinHistoryEntry);
475 20 : rrs->ches_old = GNUNET_new_array (num_indices,
476 : struct TALER_EXCHANGE_CoinHistoryEntry);
477 20 : rrs->coin_pubs = GNUNET_new_array (num_indices,
478 : struct TALER_CoinSpendPublicKeyP);
479 40 : if ( (NULL != amount) &&
480 : (GNUNET_OK !=
481 20 : TALER_string_to_amount (amount,
482 : &rrs->amount)) )
483 : {
484 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
485 : "Failed to parse amount `%s' at %s\n",
486 : amount,
487 : label);
488 0 : GNUNET_assert (0);
489 : }
490 : {
491 20 : struct TALER_TESTING_Command cmd = {
492 : .cls = rrs,
493 : .label = label,
494 : .run = &recoup_refresh_run,
495 : .cleanup = &recoup_refresh_cleanup,
496 : .traits = &recoup_refresh_traits
497 : };
498 :
499 20 : return cmd;
500 : }
501 : }
502 :
503 :
504 : struct TALER_TESTING_Command
505 20 : TALER_TESTING_cmd_recoup_refresh (const char *label,
506 : unsigned int expected_response_code,
507 : const char *coin_reference,
508 : const char *melt_reference,
509 : const char *amount)
510 : {
511 : char *cref;
512 : unsigned int idx;
513 : unsigned int *indices;
514 : struct TALER_TESTING_Command cmd;
515 :
516 20 : GNUNET_assert (GNUNET_OK ==
517 : TALER_TESTING_parse_coin_reference (coin_reference,
518 : &cref,
519 : &idx));
520 20 : indices = GNUNET_new_array (1,
521 : unsigned int);
522 20 : indices[0] = idx;
523 20 : cmd = make_recoup_refresh_cmd (label,
524 : expected_response_code,
525 : cref,
526 : indices,
527 : 1,
528 : melt_reference,
529 : amount);
530 20 : GNUNET_free (cref);
531 20 : return cmd;
532 : }
533 :
534 :
535 : struct TALER_TESTING_Command
536 0 : TALER_TESTING_cmd_recoup_refresh_batch (const char *label,
537 : unsigned int expected_response_code,
538 : const char *reveal_reference,
539 : const char *indices,
540 : const char *melt_reference,
541 : const char *amount)
542 : {
543 : unsigned int *idx;
544 : size_t num;
545 :
546 0 : idx = TALER_TESTING_parse_index_list (indices,
547 : &num);
548 0 : GNUNET_assert (NULL != idx);
549 0 : return make_recoup_refresh_cmd (label,
550 : expected_response_code,
551 : reveal_reference,
552 : idx,
553 : num,
554 : melt_reference,
555 : amount);
556 : }
557 :
558 :
559 : /* end of testing_api_cmd_recoup_refresh.c */
|