Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2018-2022 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_batch_withdraw.c
21 : * @brief implements the batch withdraw command
22 : * @author Christian Grothoff
23 : * @author Marcello Stanisci
24 : */
25 : #include "platform.h"
26 : #include "taler_json_lib.h"
27 : #include <microhttpd.h>
28 : #include <gnunet/gnunet_curl_lib.h>
29 : #include "taler_signatures.h"
30 : #include "taler_extensions.h"
31 : #include "taler_testing_lib.h"
32 :
33 : /**
34 : * Information we track per withdrawn coin.
35 : */
36 : struct CoinState
37 : {
38 :
39 : /**
40 : * String describing the denomination value we should withdraw.
41 : * A corresponding denomination key must exist in the exchange's
42 : * offerings. Can be NULL if @e pk is set instead.
43 : */
44 : struct TALER_Amount amount;
45 :
46 : /**
47 : * If @e amount is NULL, this specifies the denomination key to
48 : * use. Otherwise, this will be set (by the interpreter) to the
49 : * denomination PK matching @e amount.
50 : */
51 : struct TALER_EXCHANGE_DenomPublicKey *pk;
52 :
53 : /**
54 : * Private key of the coin.
55 : */
56 : struct TALER_CoinSpendPrivateKeyP coin_priv;
57 :
58 : /**
59 : * Blinding key used during the operation.
60 : */
61 : union TALER_DenominationBlindingKeyP bks;
62 :
63 : /**
64 : * Values contributed from the exchange during the
65 : * withdraw protocol.
66 : */
67 : struct TALER_ExchangeWithdrawValues exchange_vals;
68 :
69 : /**
70 : * Set (by the interpreter) to the exchange's signature over the
71 : * coin's public key.
72 : */
73 : struct TALER_DenominationSignature sig;
74 :
75 : /**
76 : * Private key material of the coin, set by the interpreter.
77 : */
78 : struct TALER_PlanchetMasterSecretP ps;
79 :
80 : /**
81 : * If age > 0, put here the corresponding age commitment with its proof and
82 : * its hash, respectivelly, NULL otherwise.
83 : */
84 : struct TALER_AgeCommitmentProof *age_commitment_proof;
85 : struct TALER_AgeCommitmentHash *h_age_commitment;
86 :
87 : /**
88 : * Reserve history entry that corresponds to this coin.
89 : * Will be of type #TALER_EXCHANGE_RTT_WITHDRAWAL.
90 : */
91 : struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
92 :
93 :
94 : };
95 :
96 :
97 : /**
98 : * State for a "batch withdraw" CMD.
99 : */
100 : struct BatchWithdrawState
101 : {
102 :
103 : /**
104 : * Which reserve should we withdraw from?
105 : */
106 : const char *reserve_reference;
107 :
108 : /**
109 : * Exchange base URL. Only used as offered trait.
110 : */
111 : char *exchange_url;
112 :
113 : /**
114 : * URI if the reserve we are withdrawing from.
115 : */
116 : char *reserve_payto_uri;
117 :
118 : /**
119 : * Private key of the reserve we are withdrawing from.
120 : */
121 : struct TALER_ReservePrivateKeyP reserve_priv;
122 :
123 : /**
124 : * Public key of the reserve we are withdrawing from.
125 : */
126 : struct TALER_ReservePublicKeyP reserve_pub;
127 :
128 : /**
129 : * Interpreter state (during command).
130 : */
131 : struct TALER_TESTING_Interpreter *is;
132 :
133 : /**
134 : * Withdraw handle (while operation is running).
135 : */
136 : struct TALER_EXCHANGE_BatchWithdrawHandle *wsh;
137 :
138 : /**
139 : * Array of coin states.
140 : */
141 : struct CoinState *coins;
142 :
143 : /**
144 : * Set to the KYC requirement payto hash *if* the exchange replied with a
145 : * request for KYC.
146 : */
147 : struct TALER_PaytoHashP h_payto;
148 :
149 : /**
150 : * Set to the KYC requirement row *if* the exchange replied with
151 : * a request for KYC.
152 : */
153 : uint64_t requirement_row;
154 :
155 : /**
156 : * Length of the @e coins array.
157 : */
158 : unsigned int num_coins;
159 :
160 : /**
161 : * Expected HTTP response code to the request.
162 : */
163 : unsigned int expected_response_code;
164 :
165 : /**
166 : * An age > 0 signifies age restriction is required.
167 : * Same for all coins in the batch.
168 : */
169 : uint8_t age;
170 : };
171 :
172 :
173 : /**
174 : * "batch withdraw" operation callback; checks that the
175 : * response code is expected and store the exchange signature
176 : * in the state.
177 : *
178 : * @param cls closure.
179 : * @param wr withdraw response details
180 : */
181 : static void
182 0 : reserve_batch_withdraw_cb (void *cls,
183 : const struct
184 : TALER_EXCHANGE_BatchWithdrawResponse *wr)
185 : {
186 0 : struct BatchWithdrawState *ws = cls;
187 0 : struct TALER_TESTING_Interpreter *is = ws->is;
188 :
189 0 : ws->wsh = NULL;
190 0 : if (ws->expected_response_code != wr->hr.http_status)
191 : {
192 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
193 : "Unexpected response code %u/%d to command %s in %s:%u\n",
194 : wr->hr.http_status,
195 : (int) wr->hr.ec,
196 : TALER_TESTING_interpreter_get_current_label (is),
197 : __FILE__,
198 : __LINE__);
199 0 : json_dumpf (wr->hr.reply,
200 : stderr,
201 : 0);
202 0 : GNUNET_break (0);
203 0 : TALER_TESTING_interpreter_fail (is);
204 0 : return;
205 : }
206 0 : switch (wr->hr.http_status)
207 : {
208 0 : case MHD_HTTP_OK:
209 0 : for (unsigned int i = 0; i<ws->num_coins; i++)
210 : {
211 0 : struct CoinState *cs = &ws->coins[i];
212 0 : const struct TALER_EXCHANGE_PrivateCoinDetails *pcd
213 0 : = &wr->details.success.coins[i];
214 :
215 0 : TALER_denom_sig_deep_copy (&cs->sig,
216 : &pcd->sig);
217 0 : cs->coin_priv = pcd->coin_priv;
218 0 : cs->bks = pcd->bks;
219 0 : cs->exchange_vals = pcd->exchange_vals;
220 : }
221 0 : break;
222 0 : case MHD_HTTP_FORBIDDEN:
223 : /* nothing to check */
224 0 : break;
225 0 : case MHD_HTTP_NOT_FOUND:
226 : /* nothing to check */
227 0 : break;
228 0 : case MHD_HTTP_CONFLICT:
229 : /* nothing to check */
230 0 : break;
231 0 : case MHD_HTTP_GONE:
232 : /* theoretically could check that the key was actually */
233 0 : break;
234 0 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
235 : /* nothing to check */
236 : ws->requirement_row
237 0 : = wr->details.unavailable_for_legal_reasons.requirement_row;
238 : ws->h_payto
239 0 : = wr->details.unavailable_for_legal_reasons.h_payto;
240 0 : break;
241 0 : default:
242 : /* Unsupported status code (by test harness) */
243 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
244 : "Batch withdraw test command does not support status code %u\n",
245 : wr->hr.http_status);
246 0 : GNUNET_break (0);
247 0 : break;
248 : }
249 0 : TALER_TESTING_interpreter_next (is);
250 : }
251 :
252 :
253 : /**
254 : * Run the command.
255 : */
256 : static void
257 0 : batch_withdraw_run (void *cls,
258 : const struct TALER_TESTING_Command *cmd,
259 : struct TALER_TESTING_Interpreter *is)
260 0 : {
261 0 : struct BatchWithdrawState *ws = cls;
262 : const struct TALER_ReservePrivateKeyP *rp;
263 : const struct TALER_TESTING_Command *create_reserve;
264 : const struct TALER_EXCHANGE_DenomPublicKey *dpk;
265 0 : struct TALER_EXCHANGE_WithdrawCoinInput wcis[ws->num_coins];
266 :
267 : (void) cmd;
268 0 : ws->is = is;
269 : create_reserve
270 0 : = TALER_TESTING_interpreter_lookup_command (
271 : is,
272 : ws->reserve_reference);
273 :
274 0 : if (NULL == create_reserve)
275 : {
276 0 : GNUNET_break (0);
277 0 : TALER_TESTING_interpreter_fail (is);
278 0 : return;
279 : }
280 0 : if (GNUNET_OK !=
281 0 : TALER_TESTING_get_trait_reserve_priv (create_reserve,
282 : &rp))
283 : {
284 0 : GNUNET_break (0);
285 0 : TALER_TESTING_interpreter_fail (is);
286 0 : return;
287 : }
288 0 : if (NULL == ws->exchange_url)
289 : ws->exchange_url
290 0 : = GNUNET_strdup (TALER_EXCHANGE_get_base_url (is->exchange));
291 0 : ws->reserve_priv = *rp;
292 0 : GNUNET_CRYPTO_eddsa_key_get_public (&ws->reserve_priv.eddsa_priv,
293 : &ws->reserve_pub.eddsa_pub);
294 : ws->reserve_payto_uri
295 0 : = TALER_reserve_make_payto (ws->exchange_url,
296 0 : &ws->reserve_pub);
297 :
298 0 : for (unsigned int i = 0; i<ws->num_coins; i++)
299 : {
300 0 : struct CoinState *cs = &ws->coins[i];
301 0 : struct TALER_EXCHANGE_WithdrawCoinInput *wci = &wcis[i];
302 :
303 0 : TALER_planchet_master_setup_random (&cs->ps);
304 0 : dpk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange),
305 0 : &cs->amount,
306 0 : ws->age > 0);
307 0 : if (NULL == dpk)
308 : {
309 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
310 : "Failed to determine denomination key at %s\n",
311 : (NULL != cmd) ? cmd->label : "<retried command>");
312 0 : GNUNET_break (0);
313 0 : TALER_TESTING_interpreter_fail (is);
314 0 : return;
315 : }
316 : /* We copy the denomination key, as re-querying /keys
317 : * would free the old one. */
318 0 : cs->pk = TALER_EXCHANGE_copy_denomination_key (dpk);
319 0 : cs->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL;
320 0 : GNUNET_assert (0 <=
321 : TALER_amount_add (&cs->reserve_history.amount,
322 : &cs->amount,
323 : &cs->pk->fees.withdraw));
324 0 : cs->reserve_history.details.withdraw.fee = cs->pk->fees.withdraw;
325 :
326 0 : wci->pk = cs->pk;
327 0 : wci->ps = &cs->ps;
328 0 : wci->ach = cs->h_age_commitment;
329 : }
330 0 : ws->wsh = TALER_EXCHANGE_batch_withdraw (is->exchange,
331 : rp,
332 : wcis,
333 : ws->num_coins,
334 : &reserve_batch_withdraw_cb,
335 : ws);
336 0 : if (NULL == ws->wsh)
337 : {
338 0 : GNUNET_break (0);
339 0 : TALER_TESTING_interpreter_fail (is);
340 0 : return;
341 : }
342 : }
343 :
344 :
345 : /**
346 : * Free the state of a "withdraw" CMD, and possibly cancel
347 : * a pending operation thereof.
348 : *
349 : * @param cls closure.
350 : * @param cmd the command being freed.
351 : */
352 : static void
353 0 : batch_withdraw_cleanup (void *cls,
354 : const struct TALER_TESTING_Command *cmd)
355 : {
356 0 : struct BatchWithdrawState *ws = cls;
357 :
358 0 : if (NULL != ws->wsh)
359 : {
360 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
361 : "Command %s did not complete\n",
362 : cmd->label);
363 0 : TALER_EXCHANGE_batch_withdraw_cancel (ws->wsh);
364 0 : ws->wsh = NULL;
365 : }
366 0 : for (unsigned int i = 0; i<ws->num_coins; i++)
367 : {
368 0 : struct CoinState *cs = &ws->coins[i];
369 :
370 0 : TALER_denom_sig_free (&cs->sig);
371 0 : if (NULL != cs->pk)
372 : {
373 0 : TALER_EXCHANGE_destroy_denomination_key (cs->pk);
374 0 : cs->pk = NULL;
375 : }
376 0 : if (NULL != cs->age_commitment_proof)
377 : {
378 0 : TALER_age_commitment_proof_free (cs->age_commitment_proof);
379 0 : cs->age_commitment_proof = NULL;
380 : }
381 0 : if (NULL != cs->h_age_commitment)
382 0 : GNUNET_free (cs->h_age_commitment);
383 : }
384 0 : GNUNET_free (ws->coins);
385 0 : GNUNET_free (ws->exchange_url);
386 0 : GNUNET_free (ws->reserve_payto_uri);
387 0 : GNUNET_free (ws);
388 0 : }
389 :
390 :
391 : /**
392 : * Offer internal data to a "withdraw" CMD state to other
393 : * commands.
394 : *
395 : * @param cls closure
396 : * @param[out] ret result (could be anything)
397 : * @param trait name of the trait
398 : * @param index index number of the object to offer.
399 : * @return #GNUNET_OK on success
400 : */
401 : static enum GNUNET_GenericReturnValue
402 0 : batch_withdraw_traits (void *cls,
403 : const void **ret,
404 : const char *trait,
405 : unsigned int index)
406 : {
407 0 : struct BatchWithdrawState *ws = cls;
408 0 : struct CoinState *cs = &ws->coins[index];
409 : struct TALER_TESTING_Trait traits[] = {
410 : /* history entry MUST be first due to response code logic below! */
411 0 : TALER_TESTING_make_trait_reserve_history (index,
412 0 : &cs->reserve_history),
413 0 : TALER_TESTING_make_trait_coin_priv (index,
414 0 : &cs->coin_priv),
415 0 : TALER_TESTING_make_trait_planchet_secrets (index,
416 0 : &cs->ps),
417 0 : TALER_TESTING_make_trait_blinding_key (index,
418 0 : &cs->bks),
419 0 : TALER_TESTING_make_trait_exchange_wd_value (index,
420 0 : &cs->exchange_vals),
421 0 : TALER_TESTING_make_trait_denom_pub (index,
422 0 : cs->pk),
423 0 : TALER_TESTING_make_trait_denom_sig (index,
424 0 : &cs->sig),
425 0 : TALER_TESTING_make_trait_reserve_priv (&ws->reserve_priv),
426 0 : TALER_TESTING_make_trait_reserve_pub (&ws->reserve_pub),
427 0 : TALER_TESTING_make_trait_amounts (index,
428 0 : &cs->amount),
429 0 : TALER_TESTING_make_trait_legi_requirement_row (&ws->requirement_row),
430 0 : TALER_TESTING_make_trait_h_payto (
431 0 : &ws->h_payto),
432 0 : TALER_TESTING_make_trait_payto_uri (
433 0 : (const char **) &ws->reserve_payto_uri),
434 0 : TALER_TESTING_make_trait_exchange_url (
435 0 : (const char **) &ws->exchange_url),
436 0 : TALER_TESTING_make_trait_age_commitment_proof (index,
437 0 : cs->age_commitment_proof),
438 0 : TALER_TESTING_make_trait_h_age_commitment (index,
439 0 : cs->h_age_commitment),
440 0 : TALER_TESTING_trait_end ()
441 : };
442 :
443 0 : if (index >= ws->num_coins)
444 0 : return GNUNET_NO;
445 0 : return TALER_TESTING_get_trait ((ws->expected_response_code == MHD_HTTP_OK)
446 : ? &traits[0] /* we have reserve history */
447 : : &traits[1], /* skip reserve history */
448 : ret,
449 : trait,
450 : index);
451 : }
452 :
453 :
454 : struct TALER_TESTING_Command
455 0 : TALER_TESTING_cmd_batch_withdraw (const char *label,
456 : const char *reserve_reference,
457 : uint8_t age,
458 : unsigned int expected_response_code,
459 : const char *amount,
460 : ...)
461 : {
462 : struct BatchWithdrawState *ws;
463 : unsigned int cnt;
464 : va_list ap;
465 :
466 0 : ws = GNUNET_new (struct BatchWithdrawState);
467 0 : ws->age = age;
468 0 : ws->reserve_reference = reserve_reference;
469 0 : ws->expected_response_code = expected_response_code;
470 :
471 0 : cnt = 1;
472 0 : va_start (ap, amount);
473 0 : while (NULL != (va_arg (ap, const char *)))
474 0 : cnt++;
475 0 : ws->num_coins = cnt;
476 0 : ws->coins = GNUNET_new_array (cnt,
477 : struct CoinState);
478 0 : va_end (ap);
479 0 : va_start (ap, amount);
480 0 : for (unsigned int i = 0; i<ws->num_coins; i++)
481 : {
482 0 : struct CoinState *cs = &ws->coins[i];
483 :
484 0 : if (0 < age)
485 : {
486 : struct TALER_AgeCommitmentProof *acp;
487 : struct TALER_AgeCommitmentHash *hac;
488 : struct GNUNET_HashCode seed;
489 : struct TALER_AgeMask mask;
490 :
491 0 : acp = GNUNET_new (struct TALER_AgeCommitmentProof);
492 0 : hac = GNUNET_new (struct TALER_AgeCommitmentHash);
493 0 : mask = TALER_extensions_age_restriction_ageMask ();
494 0 : GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
495 : &seed,
496 : sizeof(seed));
497 :
498 0 : if (GNUNET_OK !=
499 0 : TALER_age_restriction_commit (
500 : &mask,
501 : age,
502 : &seed,
503 : acp))
504 : {
505 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
506 : "Failed to generate age commitment for age %d at %s\n",
507 : age,
508 : label);
509 0 : GNUNET_assert (0);
510 : }
511 :
512 0 : TALER_age_commitment_hash (&acp->commitment,
513 : hac);
514 0 : cs->age_commitment_proof = acp;
515 0 : cs->h_age_commitment = hac;
516 : }
517 :
518 0 : if (GNUNET_OK !=
519 0 : TALER_string_to_amount (amount,
520 : &cs->amount))
521 : {
522 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
523 : "Failed to parse amount `%s' at %s\n",
524 : amount,
525 : label);
526 0 : GNUNET_assert (0);
527 : }
528 : /* move on to next vararg! */
529 0 : amount = va_arg (ap, const char *);
530 : }
531 0 : GNUNET_assert (NULL == amount);
532 0 : va_end (ap);
533 :
534 : {
535 0 : struct TALER_TESTING_Command cmd = {
536 : .cls = ws,
537 : .label = label,
538 : .run = &batch_withdraw_run,
539 : .cleanup = &batch_withdraw_cleanup,
540 : .traits = &batch_withdraw_traits
541 : };
542 :
543 0 : return cmd;
544 : }
545 : }
546 :
547 :
548 : /* end of testing_api_cmd_batch_withdraw.c */
|