Line data Source code
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 it under the
6 : terms of the GNU Affero 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 Affero General Public License for more details.
12 :
13 : You should have received a copy of the GNU Affero General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler-exchange-httpd_melt_v27.c
18 : * @brief Handle /melt requests
19 : * @note This endpoint is active since v27 of the protocol API
20 : * @author Özgür Kesim
21 : */
22 :
23 : #include "taler/platform.h"
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <jansson.h>
26 : #include "taler-exchange-httpd.h"
27 : #include "taler/taler_json_lib.h"
28 : #include "taler/taler_mhd_lib.h"
29 : #include "taler-exchange-httpd_melt_v27.h"
30 : #include "taler-exchange-httpd_responses.h"
31 : #include "taler-exchange-httpd_keys.h"
32 : #include "taler/taler_util.h"
33 :
34 : /**
35 : * The different type of errors that might occur, sorted by name.
36 : * Some of them require idempotency checks, which are marked
37 : * in @e idempotency_check_required below.
38 : */
39 : enum MeltError
40 : {
41 : MELT_ERROR_NONE = 0,
42 : MELT_ERROR_AGE_RESTRICTION_COMMITMENT_INVALID,
43 : MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
44 : MELT_ERROR_AMOUNT_OVERFLOW,
45 : MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW,
46 : MELT_ERROR_AMOUNT_WITH_FEE_INCORRECT,
47 : MELT_ERROR_BLINDING_SEED_REQUIRED,
48 : MELT_ERROR_COIN_CIPHER_MISMATCH,
49 : MELT_COIN_CONFLICTING_DENOMINATION_KEY,
50 : MELT_ERROR_COIN_EXPIRED_NO_ZOMBIE,
51 : MELT_ERROR_COIN_SIGNATURE_INVALID,
52 : MELT_ERROR_COIN_UNKNOWN,
53 : MELT_ERROR_CONFIRMATION_SIGN,
54 : MELT_ERROR_CRYPTO_HELPER,
55 : MELT_ERROR_DB_FETCH_FAILED,
56 : MELT_ERROR_DB_INVARIANT_FAILURE,
57 : MELT_ERROR_DB_MAKE_COIN_KNOW_FAILURE,
58 : MELT_ERROR_DB_PREFLIGHT_FAILURE,
59 : MELT_ERROR_DENOMINATION_EXPIRED,
60 : MELT_ERROR_DENOMINATION_KEY_UNKNOWN,
61 : MELT_ERROR_DENOMINATION_REVOKED,
62 : MELT_ERROR_DENOMINATION_SIGN,
63 : MELT_ERROR_DENOMINATION_SIGNATURE_INVALID,
64 : MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
65 : MELT_ERROR_IDEMPOTENT_PLANCHET,
66 : MELT_ERROR_INSUFFICIENT_FUNDS,
67 : MELT_ERROR_KEYS_MISSING,
68 : MELT_ERROR_FEES_EXCEED_CONTRIBUTION,
69 : MELT_ERROR_NONCE_RESUSE,
70 : MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
71 : };
72 :
73 : /**
74 : * With the bits set in this value will be mark the errors
75 : * that require a check for idempotency before actually
76 : * returning an error.
77 : */
78 : static const uint64_t idempotency_check_required =
79 : 0
80 : | (1 << MELT_ERROR_DENOMINATION_EXPIRED)
81 : | (1 << MELT_ERROR_DENOMINATION_KEY_UNKNOWN)
82 : | (1 << MELT_ERROR_DENOMINATION_REVOKED)
83 : | (1 << MELT_ERROR_INSUFFICIENT_FUNDS) /* TODO: is this still correct? Compare exchange_do_refresh.sql */
84 : | (1 << MELT_ERROR_KEYS_MISSING);
85 :
86 : #define IDEMPOTENCY_CHECK_REQUIRED(error) \
87 : (0 != (idempotency_check_required & (1 << (error))))
88 :
89 : /**
90 : * Context for a /melt request
91 : */
92 : struct MeltContext
93 : {
94 :
95 : /**
96 : * This struct is kept in a DLL.
97 : */
98 : struct MeltContext *prev;
99 : struct MeltContext *next;
100 :
101 : /**
102 : * Processing phase we are in.
103 : * The ordering here partially matters, as we progress through
104 : * them by incrementing the phase in the happy path.
105 : */
106 : enum MeltPhase
107 : {
108 : MELT_PHASE_PARSE,
109 : MELT_PHASE_CHECK_MELT_VALID,
110 : MELT_PHASE_CHECK_KEYS,
111 : MELT_PHASE_CHECK_COIN_SIGNATURE,
112 : MELT_PHASE_PREPARE_TRANSACTION,
113 : MELT_PHASE_RUN_TRANSACTION,
114 : MELT_PHASE_GENERATE_REPLY_SUCCESS,
115 : MELT_PHASE_GENERATE_REPLY_ERROR,
116 : MELT_PHASE_RETURN_NO,
117 : MELT_PHASE_RETURN_YES,
118 : } phase;
119 :
120 :
121 : /**
122 : * Request context
123 : */
124 : const struct TEH_RequestContext *rc;
125 :
126 : /**
127 : * Current time for the DB transaction.
128 : */
129 : struct GNUNET_TIME_Timestamp now;
130 :
131 : /**
132 : * The current key state
133 : */
134 : struct TEH_KeyStateHandle *ksh;
135 :
136 : /**
137 : * The melted coin's denomination key
138 : */
139 : struct TEH_DenominationKey *melted_coin_denom;
140 :
141 : /**
142 : * Set to true if this coin's denomination was revoked and the operation
143 : * is thus only allowed for zombie coins where the transaction
144 : * history includes a #TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP.
145 : */
146 : bool zombie_required;
147 :
148 : /**
149 : * We already checked and noticed that the coin is known. Hence we
150 : * can skip the "ensure_coin_known" step of the transaction.
151 : */
152 : bool coin_is_dirty;
153 :
154 : /**
155 : * UUID of the coin in the known_coins table.
156 : */
157 : uint64_t known_coin_id;
158 :
159 : /**
160 : * Captures all parameters provided in the JSON request
161 : */
162 : struct
163 : {
164 :
165 : /**
166 : * All fields (from the request or computed)
167 : * that we persist in the database.
168 : */
169 : struct TALER_EXCHANGEDB_Refresh_v27 refresh;
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_Refresh_v27 refresh_idem;
183 :
184 : /**
185 : * True if @e blinding_seed is missing in the request
186 : */
187 : bool no_blinding_seed;
188 :
189 : /**
190 : * Array @e persis.num_coins of hashes of the public keys
191 : * of the denominations to refresh.
192 : */
193 : struct TALER_DenominationHashP *denoms_h;
194 :
195 : /**
196 : * Array of @e num_planchets coin planchets, arranged
197 : * in runs of @e num_coins coins, [0..num_coins)..[0..num_coins),
198 : * one for each kappa value.
199 : */
200 : struct TALER_BlindedPlanchet *planchets[TALER_CNC_KAPPA];
201 :
202 : /**
203 : * #TALER_CNC_KAPPA hashes of the batches of @e num_coins coins.
204 : */
205 : struct TALER_KappaHashBlindedPlanchetsP kappa_planchets_h;
206 :
207 : /**
208 : * Array @e withdraw.num_r_pubs of indices into @e denoms_h
209 : * of CS denominations.
210 : */
211 : uint32_t *cs_indices;
212 :
213 : /**
214 : * Total (over all coins) amount (excluding fee) committed for the refresh
215 : */
216 : struct TALER_Amount amount;
217 :
218 : } request;
219 :
220 : /**
221 : * Errors occurring during evaluation of the request are captured in this
222 : * struct. In phase WITHDRAW_PHASE_GENERATE_REPLY_ERROR an appropriate error
223 : * message is prepared and sent to the client.
224 : */
225 : struct
226 : {
227 : /* The (internal) error code */
228 : enum MeltError code;
229 :
230 : /**
231 : * Some errors require details to be sent to the client.
232 : * These are captured in this union.
233 : * Each field is named according to the error that is using it, except
234 : * commented otherwise.
235 : */
236 : union
237 : {
238 : const char *request_parameter_malformed;
239 :
240 : /**
241 : * For all errors related to a particular denomination, i.e.
242 : * #MELT_ERROR_DENOMINATION_KEY_UNKNOWN,
243 : * #MELT_ERROR_DENOMINATION_EXPIRED,
244 : * #MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
245 : * #MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
246 : * we use this one field.
247 : */
248 : struct TALER_DenominationHashP denom_h;
249 :
250 : const char *db_fetch_context;
251 :
252 : enum TALER_ErrorCode ec_confirmation_sign;
253 :
254 : enum TALER_ErrorCode ec_denomination_sign;
255 :
256 : /* remaining value of the coin */
257 : struct TALER_Amount insufficient_funds;
258 :
259 : } details;
260 : } error;
261 : };
262 :
263 : /**
264 : * The following macros set the given error code,
265 : * set the phase to Melt_PHASE_GENERATE_REPLY_ERROR,
266 : * and optionally set the given field (with an optionally given value).
267 : */
268 : #define SET_ERROR(mc, ec) \
269 : do \
270 : { GNUNET_static_assert (MELT_ERROR_NONE != ec); \
271 : (mc)->error.code = (ec); \
272 : (mc)->phase = MELT_PHASE_GENERATE_REPLY_ERROR; } while (0)
273 :
274 : #define SET_ERROR_WITH_FIELD(mc, ec, field) \
275 : do \
276 : { GNUNET_static_assert (MELT_ERROR_NONE != ec); \
277 : (mc)->error.code = (ec); \
278 : (mc)->error.details.field = (field); \
279 : (mc)->phase = MELT_PHASE_GENERATE_REPLY_ERROR; } while (0)
280 :
281 : #define SET_ERROR_WITH_DETAIL(mc, ec, field, value) \
282 : do \
283 : { GNUNET_static_assert (MELT_ERROR_NONE != ec); \
284 : (mc)->error.code = (ec); \
285 : (mc)->error.details.field = (value); \
286 : (mc)->phase = MELT_PHASE_GENERATE_REPLY_ERROR; } while (0)
287 :
288 :
289 : /**
290 : * All melt context is kept in a DLL.
291 : */
292 : static struct MeltContext *mc_head;
293 : static struct MeltContext *mc_tail;
294 :
295 : void
296 21 : TEH_melt_v27_cleanup ()
297 : {
298 : struct MeltContext *mc;
299 :
300 21 : while (NULL != (mc = mc_head))
301 : {
302 0 : GNUNET_CONTAINER_DLL_remove (mc_head,
303 : mc_tail,
304 : mc);
305 0 : MHD_resume_connection (mc->rc->connection);
306 : }
307 21 : }
308 :
309 :
310 : /**
311 : * Terminate the main loop by returning the final result.
312 : *
313 : * @param[in,out] mc context to update phase for
314 : * @param mres MHD status to return
315 : */
316 : static void
317 37 : finish_loop (struct MeltContext *mc,
318 : MHD_RESULT mres)
319 : {
320 37 : mc->phase = (MHD_YES == mres)
321 : ? MELT_PHASE_RETURN_YES
322 37 : : MELT_PHASE_RETURN_NO;
323 37 : }
324 :
325 :
326 : /**
327 : * Free information in @a re, but not @a re itself.
328 : *
329 : * @param[in] re refresh data to free
330 : */
331 : static void
332 38 : free_refresh (struct TALER_EXCHANGEDB_Refresh_v27 *re)
333 : {
334 38 : if (NULL != re->denom_sigs)
335 : {
336 246 : for (size_t i = 0; i<re->num_coins; i++)
337 208 : TALER_blinded_denom_sig_free (&re->denom_sigs[i]);
338 38 : GNUNET_free (re->denom_sigs);
339 : }
340 38 : GNUNET_free (re->cs_r_values);
341 38 : GNUNET_free (re->denom_serials);
342 38 : GNUNET_free (re->denom_pub_hashes);
343 38 : TALER_denom_sig_free (&re->coin.denom_sig);
344 38 : }
345 :
346 :
347 : /**
348 : * Cleanup routine for melt request.
349 : * The function is called upon completion of the request
350 : * that should clean up @a rh_ctx.
351 : *
352 : * @param rc request context to clean up
353 : */
354 : static void
355 38 : clean_melt_rc (struct TEH_RequestContext *rc)
356 : {
357 38 : struct MeltContext *mc = rc->rh_ctx;
358 :
359 38 : GNUNET_free (mc->request.denoms_h);
360 152 : for (uint8_t k = 0; k<TALER_CNC_KAPPA; k++)
361 : {
362 738 : for (size_t i = 0; i<mc->request.refresh.num_coins; i++)
363 624 : TALER_blinded_planchet_free (&mc->request.planchets[k][i]);
364 114 : GNUNET_free (mc->request.planchets[k]);
365 : }
366 38 : free_refresh (&mc->request.refresh);
367 38 : if (mc->request.is_idempotent)
368 : {
369 0 : free_refresh (&mc->request.refresh_idem);
370 : }
371 38 : GNUNET_free (mc->request.cs_indices);
372 38 : GNUNET_free (mc);
373 38 : }
374 :
375 :
376 : /**
377 : * Creates a new context for the incoming melt request
378 : *
379 : * @param mc melt request context
380 : * @param root json body of the request
381 : */
382 : static void
383 38 : phase_parse_request (
384 : struct MeltContext *mc,
385 : const json_t *root)
386 : {
387 : const json_t *j_denoms_h;
388 : const json_t *j_coin_evs;
389 : enum GNUNET_GenericReturnValue res;
390 : struct GNUNET_JSON_Specification spec[] = {
391 38 : GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
392 : &mc->request.refresh.coin.coin_pub),
393 38 : GNUNET_JSON_spec_fixed_auto ("old_denom_pub_h",
394 : &mc->request.refresh.coin.denom_pub_hash),
395 38 : GNUNET_JSON_spec_mark_optional (
396 38 : GNUNET_JSON_spec_fixed_auto ("old_age_commitment_h",
397 : &mc->request.refresh.coin.h_age_commitment),
398 : &mc->request.refresh.coin.no_age_commitment),
399 38 : TALER_JSON_spec_denom_sig ("old_denom_sig",
400 : &mc->request.refresh.coin.denom_sig),
401 38 : GNUNET_JSON_spec_fixed_auto ("refresh_seed",
402 : &mc->request.refresh.refresh_seed),
403 38 : GNUNET_JSON_spec_mark_optional (
404 38 : GNUNET_JSON_spec_fixed_auto ("blinding_seed",
405 : &mc->request.refresh.blinding_seed),
406 : &mc->request.refresh.no_blinding_seed),
407 38 : TALER_JSON_spec_amount ("value_with_fee",
408 : TEH_currency,
409 : &mc->request.refresh.amount_with_fee),
410 38 : GNUNET_JSON_spec_array_const ("denoms_h",
411 : &j_denoms_h),
412 38 : GNUNET_JSON_spec_array_const ("coin_evs",
413 : &j_coin_evs),
414 38 : GNUNET_JSON_spec_fixed_auto ("confirm_sig",
415 : &mc->request.refresh.coin_sig),
416 38 : GNUNET_JSON_spec_end ()
417 : };
418 :
419 38 : res = TALER_MHD_parse_json_data (mc->rc->connection,
420 : root,
421 : spec);
422 38 : if (GNUNET_OK != res)
423 : {
424 0 : GNUNET_break_op (0);
425 0 : mc->phase = (GNUNET_NO == res)
426 : ? MELT_PHASE_RETURN_YES
427 0 : : MELT_PHASE_RETURN_NO;
428 0 : return;
429 : }
430 :
431 : /* validate array size */
432 : GNUNET_static_assert (
433 : TALER_MAX_COINS < INT_MAX / TALER_CNC_KAPPA);
434 :
435 38 : mc->request.refresh.num_coins = json_array_size (j_denoms_h);
436 38 : if (0 == mc->request.refresh.num_coins)
437 : {
438 0 : GNUNET_break_op (0);
439 0 : SET_ERROR_WITH_DETAIL (mc,
440 : MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
441 : request_parameter_malformed,
442 : "denoms_h must not be empty");
443 0 : return;
444 : }
445 38 : else if (TALER_MAX_COINS < mc->request.refresh.num_coins)
446 : {
447 : /**
448 : * The wallet had committed to more than the maximum coins allowed, the
449 : * reserve has been charged, but now the user can not melt any money
450 : * from it. Note that the user can't get their money back in this case!
451 : */
452 0 : GNUNET_break_op (0);
453 0 : SET_ERROR_WITH_DETAIL (mc,
454 : MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
455 : request_parameter_malformed,
456 : "maximum number of coins that can be refreshed has been exceeded");
457 0 : return;
458 : }
459 38 : else if (TALER_CNC_KAPPA != json_array_size (j_coin_evs))
460 : {
461 0 : GNUNET_break_op (0);
462 0 : SET_ERROR_WITH_DETAIL (mc,
463 : MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
464 : request_parameter_malformed,
465 : "coin_evs must be an array of length "TALER_CNC_KAPPA_STR);
466 0 : return;
467 : }
468 :
469 : /* Extract the denomination hashes */
470 : {
471 : size_t idx;
472 : json_t *value;
473 :
474 : mc->request.denoms_h
475 38 : = GNUNET_new_array (mc->request.refresh.num_coins,
476 : struct TALER_DenominationHashP);
477 :
478 246 : json_array_foreach (j_denoms_h, idx, value) {
479 : struct GNUNET_JSON_Specification ispec[] = {
480 208 : GNUNET_JSON_spec_fixed_auto (NULL,
481 : &mc->request.denoms_h[idx]),
482 208 : GNUNET_JSON_spec_end ()
483 : };
484 :
485 208 : res = TALER_MHD_parse_json_data (mc->rc->connection,
486 : value,
487 : ispec);
488 208 : if (GNUNET_YES != res)
489 : {
490 0 : GNUNET_break_op (0);
491 0 : mc->phase = (GNUNET_NO == res)
492 : ? MELT_PHASE_RETURN_YES
493 0 : : MELT_PHASE_RETURN_NO;
494 0 : return;
495 : }
496 : }
497 : }
498 :
499 : /* Calculate the hash over the blinded coin envelopes */
500 152 : for (size_t k = 0; k<TALER_CNC_KAPPA; k++)
501 : {
502 114 : mc->request.planchets[k] =
503 114 : GNUNET_new_array (mc->request.refresh.num_coins,
504 : struct TALER_BlindedPlanchet);
505 : }
506 :
507 : /* Parse blinded envelopes. */
508 : {
509 : json_t *j_kappa_planchets;
510 : size_t kappa;
511 : struct GNUNET_HashContext *ctx;
512 :
513 : /* ctx to calculate the planchet_h */
514 38 : ctx = GNUNET_CRYPTO_hash_context_start ();
515 38 : GNUNET_assert (NULL != ctx);
516 :
517 152 : json_array_foreach (j_coin_evs, kappa, j_kappa_planchets)
518 : {
519 : json_t *j_cev;
520 : size_t idx;
521 :
522 114 : if (mc->request.refresh.num_coins != json_array_size (j_kappa_planchets))
523 : {
524 0 : GNUNET_break_op (0);
525 0 : SET_ERROR_WITH_DETAIL (mc,
526 : MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
527 : request_parameter_malformed,
528 : "coin_evs[] size");
529 0 : return;
530 : }
531 :
532 738 : json_array_foreach (j_kappa_planchets, idx, j_cev)
533 : {
534 : /* Now parse the individual envelopes and calculate the hash of
535 : * the commitment along the way. */
536 : struct GNUNET_JSON_Specification kspec[] = {
537 624 : TALER_JSON_spec_blinded_planchet (NULL,
538 624 : &mc->request.planchets[kappa][idx]),
539 624 : GNUNET_JSON_spec_end ()
540 : };
541 :
542 624 : res = TALER_MHD_parse_json_data (mc->rc->connection,
543 : j_cev,
544 : kspec);
545 624 : if (GNUNET_OK != res)
546 : {
547 0 : GNUNET_break_op (0);
548 0 : mc->phase = (GNUNET_NO == res)
549 : ? MELT_PHASE_RETURN_YES
550 0 : : MELT_PHASE_RETURN_NO;
551 0 : return;
552 : }
553 : /* Check for duplicate planchets. Technically a bug on
554 : * the client side that is harmless for us, but still
555 : * not allowed per protocol */
556 1872 : for (size_t k = 0; k <= kappa; k++)
557 : {
558 1248 : size_t max = (k == kappa)
559 : ? idx
560 1248 : : mc->request.refresh.num_coins;
561 7470 : for (size_t i = 0; i < max; i++)
562 : {
563 6222 : if (0 ==
564 6222 : TALER_blinded_planchet_cmp (
565 6222 : &mc->request.planchets[kappa][idx],
566 6222 : &mc->request.planchets[k][i]))
567 : {
568 0 : GNUNET_break_op (0);
569 0 : GNUNET_JSON_parse_free (kspec);
570 0 : SET_ERROR (mc,
571 : MELT_ERROR_IDEMPOTENT_PLANCHET);
572 0 : return;
573 : }
574 : }
575 : }
576 : }
577 : /* Save the hash of the batch of planchets for index kappa */
578 114 : TALER_wallet_blinded_planchets_hash (
579 : mc->request.refresh.num_coins,
580 114 : mc->request.planchets[kappa],
581 114 : mc->request.denoms_h,
582 : &mc->request.kappa_planchets_h.tuple[kappa]);
583 114 : GNUNET_CRYPTO_hash_context_read (
584 : ctx,
585 114 : &mc->request.kappa_planchets_h.tuple[kappa],
586 : sizeof(mc->request.kappa_planchets_h.tuple[kappa]));
587 : }
588 :
589 : /* Finally calculate the total hash over all planchets */
590 38 : GNUNET_CRYPTO_hash_context_finish (
591 : ctx,
592 : &mc->request.refresh.planchets_h.hash);
593 : }
594 38 : mc->ksh = TEH_keys_get_state ();
595 38 : if (NULL == mc->ksh)
596 : {
597 0 : GNUNET_break (0);
598 0 : SET_ERROR (mc,
599 : MELT_ERROR_KEYS_MISSING);
600 0 : return;
601 : }
602 38 : mc->phase = MELT_PHASE_CHECK_MELT_VALID;
603 : }
604 :
605 :
606 : /**
607 : * Check if the given denomination is still or already valid, has not been
608 : * revoked and potentically supports age restriction.
609 : *
610 : * @param[in,out] mc context for the melt operation
611 : * @param denom_h Hash of the denomination key to check
612 : * @param[out] pdk denomination key found, might be NULL
613 : * @return #GNUNET_OK when denomation was found and valid,
614 : * #GNUNET_NO when denomination is not valid at this time
615 : * #GNUNET_SYSERR otherwise (denomination invalid), with finish_loop called.
616 : */
617 : static enum GNUNET_GenericReturnValue
618 246 : find_denomination (
619 : struct MeltContext *mc,
620 : const struct TALER_DenominationHashP *denom_h,
621 : struct TEH_DenominationKey **pdk)
622 : {
623 : struct TEH_DenominationKey *dk;
624 :
625 246 : *pdk = NULL;
626 246 : GNUNET_assert (NULL != mc->ksh);
627 246 : dk = TEH_keys_denomination_by_hash_from_state (
628 246 : mc->ksh,
629 : denom_h,
630 : NULL,
631 : NULL);
632 246 : if (NULL == dk)
633 : {
634 0 : SET_ERROR_WITH_DETAIL (mc,
635 : MELT_ERROR_DENOMINATION_KEY_UNKNOWN,
636 : denom_h,
637 : *denom_h);
638 0 : return GNUNET_SYSERR;
639 : }
640 246 : *pdk = dk;
641 :
642 246 : if (GNUNET_TIME_absolute_is_past (
643 : dk->meta.expire_withdraw.abs_time))
644 : {
645 0 : SET_ERROR_WITH_DETAIL (mc,
646 : MELT_ERROR_DENOMINATION_EXPIRED,
647 : denom_h,
648 : *denom_h);
649 : /**
650 : * Note that we return GNUNET_NO here.
651 : * This way phase_check_melt_valid can react
652 : * to it as a non-error case and do the zombie check.
653 : */
654 0 : return GNUNET_NO;
655 : }
656 :
657 246 : if (GNUNET_TIME_absolute_is_future (
658 : dk->meta.start.abs_time))
659 : {
660 0 : GNUNET_break_op (0);
661 0 : SET_ERROR_WITH_DETAIL (mc,
662 : MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
663 : denom_h,
664 : *denom_h);
665 0 : return GNUNET_SYSERR;
666 : }
667 :
668 246 : if (dk->recoup_possible)
669 : {
670 0 : SET_ERROR (mc,
671 : MELT_ERROR_DENOMINATION_REVOKED);
672 0 : return GNUNET_SYSERR;
673 : }
674 :
675 : /* In case of age melt, make sure that the denomitation supports age restriction */
676 246 : if (! (mc->request.refresh.coin.no_age_commitment) &&
677 80 : (0 == dk->denom_pub.age_mask.bits))
678 : {
679 0 : GNUNET_break_op (0);
680 0 : SET_ERROR_WITH_DETAIL (mc,
681 : MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
682 : denom_h,
683 : *denom_h);
684 0 : return GNUNET_SYSERR;
685 : }
686 246 : if ((mc->request.refresh.coin.no_age_commitment) &&
687 166 : (0 != dk->denom_pub.age_mask.bits))
688 : {
689 0 : GNUNET_break_op (0);
690 0 : SET_ERROR (mc,
691 : MELT_ERROR_AGE_RESTRICTION_COMMITMENT_INVALID);
692 0 : return GNUNET_SYSERR;
693 : }
694 :
695 246 : return GNUNET_OK;
696 : }
697 :
698 :
699 : /**
700 : * Check if the given array of hashes of denomination_keys
701 : * - belong to valid denominations
702 : * - calculate the total amount of the denominations including fees
703 : * for melt.
704 : *
705 : * @param mc context of the melt to check keys for
706 : */
707 : static void
708 38 : phase_check_keys (
709 : struct MeltContext *mc)
710 38 : {
711 38 : bool is_cs_denom[mc->request.refresh.num_coins];
712 :
713 38 : memset (is_cs_denom,
714 : 0,
715 : sizeof(is_cs_denom));
716 :
717 38 : mc->request.refresh.denom_serials =
718 38 : GNUNET_new_array (mc->request.refresh.num_coins,
719 : uint64_t);
720 38 : GNUNET_assert (GNUNET_OK ==
721 : TALER_amount_set_zero (TEH_currency,
722 : &mc->request.amount));
723 :
724 : /* Calculate the total value and withdraw fees for the fresh coins */
725 246 : for (size_t i = 0; i < mc->request.refresh.num_coins; i++)
726 : {
727 : struct TEH_DenominationKey *dk;
728 :
729 208 : if (GNUNET_OK !=
730 208 : find_denomination (
731 : mc,
732 208 : &mc->request.denoms_h[i],
733 : &dk))
734 0 : return;
735 :
736 208 : if (GNUNET_CRYPTO_BSA_CS ==
737 208 : dk->denom_pub.bsign_pub_key->cipher)
738 : {
739 60 : if (mc->request.refresh.no_blinding_seed)
740 : {
741 0 : GNUNET_break_op (0);
742 0 : SET_ERROR (mc,
743 : MELT_ERROR_BLINDING_SEED_REQUIRED);
744 0 : return;
745 : }
746 60 : mc->request.refresh.num_cs_r_values++;
747 60 : is_cs_denom[i] = true;
748 : }
749 : /* Ensure the ciphers from the planchets match the denominations'. */
750 : {
751 832 : for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
752 : {
753 624 : if (dk->denom_pub.bsign_pub_key->cipher !=
754 624 : mc->request.planchets[k][i].blinded_message->cipher)
755 : {
756 0 : GNUNET_break_op (0);
757 0 : SET_ERROR (mc,
758 : MELT_ERROR_COIN_CIPHER_MISMATCH);
759 0 : return;
760 : }
761 : }
762 : }
763 : /* Accumulate the values */
764 208 : if (0 > TALER_amount_add (&mc->request.amount,
765 208 : &mc->request.amount,
766 208 : &dk->meta.value))
767 : {
768 0 : GNUNET_break_op (0);
769 0 : SET_ERROR (mc,
770 : MELT_ERROR_AMOUNT_OVERFLOW);
771 0 : return;
772 : }
773 : /* Accumulate the withdraw fees for the fresh coins */
774 208 : if (0 > TALER_amount_add (&mc->request.amount,
775 208 : &mc->request.amount,
776 208 : &dk->meta.fees.withdraw))
777 : {
778 0 : GNUNET_break_op (0);
779 0 : SET_ERROR (mc,
780 : MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW);
781 0 : return;
782 : }
783 208 : mc->request.refresh.denom_serials[i] = dk->meta.serial;
784 : }
785 :
786 : /**
787 : * Calculate the amount (with withdraw fee) plus refresh fee and
788 : * compare with the value provided by the client in the request.
789 : */
790 : {
791 : struct TALER_Amount amount_with_fee;
792 :
793 38 : if (0 > TALER_amount_add (&amount_with_fee,
794 38 : &mc->request.amount,
795 38 : &mc->melted_coin_denom->meta.fees.refresh))
796 : {
797 0 : GNUNET_break_op (0);
798 0 : SET_ERROR (mc,
799 : MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW);
800 0 : return;
801 : }
802 :
803 38 : if (0 != TALER_amount_cmp (&amount_with_fee,
804 38 : &mc->request.refresh.amount_with_fee))
805 : {
806 0 : GNUNET_break_op (0);
807 0 : SET_ERROR (mc,
808 : MELT_ERROR_AMOUNT_WITH_FEE_INCORRECT);
809 0 : return;
810 : }
811 : }
812 :
813 : /* Save the indices of CS denominations */
814 38 : if (0 < mc->request.refresh.num_cs_r_values)
815 : {
816 15 : size_t j = 0;
817 :
818 15 : mc->request.cs_indices = GNUNET_new_array (
819 : mc->request.refresh.num_cs_r_values,
820 : uint32_t);
821 :
822 75 : for (size_t i = 0; i < mc->request.refresh.num_coins; i++)
823 : {
824 60 : if (is_cs_denom[i])
825 60 : mc->request.cs_indices[j++] = i;
826 : }
827 : }
828 38 : mc->phase++;
829 : }
830 :
831 :
832 : /**
833 : * Check that the client signature authorizing the melt is valid.
834 : *
835 : * @param[in,out] mc request context to check
836 : */
837 : static void
838 38 : phase_check_coin_signature (
839 : struct MeltContext *mc)
840 : {
841 : /* We can now compute the commitment */
842 : {
843 38 : struct TALER_KappaHashBlindedPlanchetsP k_bps_h = {0};
844 :
845 152 : for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
846 114 : TALER_wallet_blinded_planchets_hash (
847 : mc->request.refresh.num_coins,
848 114 : mc->request.planchets[k],
849 114 : mc->request.denoms_h,
850 114 : &k_bps_h.tuple[k]);
851 :
852 38 : TALER_refresh_get_commitment_v27 (
853 : &mc->request.refresh.rc,
854 38 : &mc->request.refresh.refresh_seed,
855 38 : mc->request.no_blinding_seed
856 : ? NULL
857 : : &mc->request.refresh.blinding_seed,
858 : &k_bps_h,
859 38 : &mc->request.refresh.coin.coin_pub,
860 38 : &mc->request.refresh.amount_with_fee);
861 : }
862 :
863 :
864 38 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
865 38 : if (GNUNET_OK !=
866 38 : TALER_wallet_melt_verify (
867 38 : &mc->request.refresh.amount_with_fee,
868 38 : &mc->melted_coin_denom->meta.fees.refresh,
869 38 : &mc->request.refresh.rc,
870 38 : &mc->request.refresh.coin.denom_pub_hash,
871 38 : &mc->request.refresh.coin.h_age_commitment,
872 38 : &mc->request.refresh.coin.coin_pub,
873 38 : &mc->request.refresh.coin_sig))
874 : {
875 0 : GNUNET_break_op (0);
876 0 : SET_ERROR (mc,
877 : MELT_ERROR_COIN_SIGNATURE_INVALID);
878 0 : return;
879 : }
880 :
881 38 : mc->phase++;
882 : }
883 :
884 :
885 : /**
886 : * Check for information about the melted coin's denomination,
887 : * extracting its validity status and fee structure.
888 : * Baseline: check if deposits/refreshes are generally
889 : * simply still allowed for this denomination.
890 : *
891 : * @param mc parsed request information
892 : */
893 : static void
894 38 : phase_check_melt_valid (struct MeltContext *mc)
895 : {
896 38 : enum MeltPhase current_phase = mc->phase;
897 : /**
898 : * Find the old coin's denomination.
899 : * Note that we return only on GNUNET_SYSERR,
900 : * because GNUNET_NO for the expired denomination
901 : * will be handled below, with the zombie-check.
902 : */
903 38 : if (GNUNET_SYSERR ==
904 38 : find_denomination (mc,
905 38 : &mc->request.refresh.coin.denom_pub_hash,
906 : &mc->melted_coin_denom))
907 0 : return;
908 :
909 38 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
910 : "Melted coin's denomination is worth %s\n",
911 : TALER_amount2s (&mc->melted_coin_denom->meta.value));
912 :
913 : /* sanity-check that "total melt amount > melt fee" */
914 38 : if (0 <
915 38 : TALER_amount_cmp (&mc->melted_coin_denom->meta.fees.refresh,
916 38 : &mc->request.refresh.amount_with_fee))
917 : {
918 0 : GNUNET_break_op (0);
919 0 : SET_ERROR (mc,
920 : MELT_ERROR_FEES_EXCEED_CONTRIBUTION);
921 0 : return;
922 : }
923 :
924 38 : if (GNUNET_OK !=
925 38 : TALER_test_coin_valid (&mc->request.refresh.coin,
926 38 : &mc->melted_coin_denom->denom_pub))
927 : {
928 0 : GNUNET_break_op (0);
929 0 : SET_ERROR (mc,
930 : MELT_ERROR_DENOMINATION_SIGNATURE_INVALID);
931 0 : return;
932 : }
933 :
934 : /**
935 : * find_denomination might have set the phase to
936 : * produce an error, but we are still investigating.
937 : * We reset the phase.
938 : */
939 38 : mc->phase = current_phase;
940 38 : mc->error.code = MELT_ERROR_NONE;
941 :
942 38 : if (GNUNET_TIME_absolute_is_past (
943 38 : mc->melted_coin_denom->meta.expire_deposit.abs_time))
944 : {
945 : /**
946 : * We are past deposit expiration time, but maybe this is a zombie?
947 : */
948 : struct TALER_DenominationHashP denom_hash;
949 : enum GNUNET_DB_QueryStatus qs;
950 :
951 : /* Check that the coin is dirty (we have seen it before), as we will
952 : not just allow melting of a *fresh* coin where the denomination was
953 : revoked (those must be recouped) */
954 0 : qs = TEH_plugin->get_coin_denomination (
955 0 : TEH_plugin->cls,
956 0 : &mc->request.refresh.coin.coin_pub,
957 : &mc->known_coin_id,
958 : &denom_hash);
959 0 : if (0 > qs)
960 : {
961 : /* There is no good reason for a serialization failure here: */
962 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
963 0 : SET_ERROR (mc,
964 : MELT_ERROR_DB_FETCH_FAILED);
965 0 : return;
966 : }
967 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
968 : {
969 : /* We never saw this coin before, so _this_ justification is not OK.
970 : * Note that the error was already set in find_denominations. */
971 0 : GNUNET_assert (MELT_ERROR_DENOMINATION_EXPIRED ==
972 : mc->error.code);
973 0 : GNUNET_assert (MELT_PHASE_GENERATE_REPLY_ERROR ==
974 : mc->phase);
975 0 : return;
976 : }
977 : /* sanity check */
978 0 : if (0 !=
979 0 : GNUNET_memcmp (&denom_hash,
980 : &mc->request.refresh.coin.denom_pub_hash))
981 : {
982 0 : GNUNET_break_op (0);
983 0 : SET_ERROR_WITH_DETAIL (mc,
984 : MELT_COIN_CONFLICTING_DENOMINATION_KEY,
985 : denom_h,
986 : denom_hash);
987 0 : return;
988 : }
989 : /* Minor optimization: no need to run the
990 : "ensure_coin_known" part of the transaction */
991 0 : mc->coin_is_dirty = true;
992 : /* check later that zombie is satisfied */
993 0 : mc->zombie_required = true;
994 : }
995 38 : mc->phase++;
996 : }
997 :
998 :
999 : /**
1000 : * The request for melt was parsed successfully.
1001 : * Sign and persist the chosen blinded coins for the reveal step.
1002 : *
1003 : * @param mc The context for the current melt request
1004 : */
1005 : static void
1006 38 : phase_prepare_transaction (
1007 : struct MeltContext *mc)
1008 : {
1009 : mc->request.refresh.denom_sigs
1010 38 : = GNUNET_new_array (
1011 : mc->request.refresh.num_coins,
1012 : struct TALER_BlindedDenominationSignature);
1013 38 : mc->request.refresh.noreveal_index =
1014 38 : GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG,
1015 : TALER_CNC_KAPPA);
1016 :
1017 : /* Choose and sign the coins */
1018 38 : {
1019 38 : struct TEH_CoinSignData csds[mc->request.refresh.num_coins];
1020 : enum TALER_ErrorCode ec_denomination_sign;
1021 38 : size_t noreveal_idx = mc->request.refresh.noreveal_index;
1022 :
1023 38 : memset (csds,
1024 : 0,
1025 : sizeof(csds));
1026 :
1027 : /* Pick the chosen blinded coins */
1028 246 : for (size_t i = 0; i<mc->request.refresh.num_coins; i++)
1029 : {
1030 208 : csds[i].bp = &mc->request.planchets[noreveal_idx][i];
1031 208 : csds[i].h_denom_pub = &mc->request.denoms_h[i];
1032 : }
1033 :
1034 38 : ec_denomination_sign = TEH_keys_denomination_batch_sign (
1035 38 : mc->request.refresh.num_coins,
1036 : csds,
1037 : true, /* for melt */
1038 : mc->request.refresh.denom_sigs);
1039 38 : if (TALER_EC_NONE != ec_denomination_sign)
1040 : {
1041 0 : GNUNET_break (0);
1042 0 : SET_ERROR_WITH_FIELD (mc,
1043 : MELT_ERROR_DENOMINATION_SIGN,
1044 : ec_denomination_sign);
1045 0 : return;
1046 : }
1047 :
1048 : /* Save the hash of chosen planchets */
1049 38 : mc->request.refresh.selected_h =
1050 : mc->request.kappa_planchets_h.tuple[noreveal_idx];
1051 :
1052 : /**
1053 : * For the denominations with cipher CS, calculate the R-values
1054 : * and save the choices we made now, as at a later point, the
1055 : * private keys for the denominations might now be available anymore
1056 : * to make the same choice again.
1057 : */
1058 38 : if (0 < mc->request.refresh.num_cs_r_values)
1059 15 : {
1060 15 : size_t num_cs_r_values = mc->request.refresh.num_cs_r_values;
1061 15 : struct TEH_CsDeriveData cdds[num_cs_r_values];
1062 15 : struct GNUNET_CRYPTO_CsSessionNonce nonces[num_cs_r_values];
1063 :
1064 15 : memset (nonces, 0, sizeof(nonces));
1065 : mc->request.refresh.cs_r_values
1066 15 : = GNUNET_new_array (
1067 : num_cs_r_values,
1068 : struct GNUNET_CRYPTO_CSPublicRPairP);
1069 15 : mc->request.refresh.cs_r_choices = 0;
1070 :
1071 15 : GNUNET_assert (! mc->request.refresh.no_blinding_seed);
1072 15 : TALER_cs_derive_nonces_from_seed (
1073 15 : &mc->request.refresh.blinding_seed,
1074 : true, /* for melt */
1075 : num_cs_r_values,
1076 15 : mc->request.cs_indices,
1077 : nonces);
1078 :
1079 75 : for (size_t i = 0; i < num_cs_r_values; i++)
1080 : {
1081 60 : size_t idx = mc->request.cs_indices[i];
1082 :
1083 60 : GNUNET_assert (idx < mc->request.refresh.num_coins);
1084 60 : cdds[i].h_denom_pub = &mc->request.denoms_h[idx];
1085 60 : cdds[i].nonce = &nonces[i];
1086 : }
1087 :
1088 : /**
1089 : * Let the crypto helper generate the R-values and
1090 : * make the choices
1091 : */
1092 15 : if (TALER_EC_NONE !=
1093 15 : TEH_keys_denomination_cs_batch_r_pub_simple (
1094 15 : mc->request.refresh.num_cs_r_values,
1095 : cdds,
1096 : true, /* for melt */
1097 : mc->request.refresh.cs_r_values))
1098 : {
1099 0 : GNUNET_break (0);
1100 0 : SET_ERROR (mc,
1101 : MELT_ERROR_CRYPTO_HELPER);
1102 0 : return;
1103 : }
1104 :
1105 : /* Now save the choices for the selected bits */
1106 75 : for (size_t i = 0; i < num_cs_r_values; i++)
1107 : {
1108 60 : size_t idx = mc->request.cs_indices[i];
1109 :
1110 60 : struct TALER_BlindedDenominationSignature *sig =
1111 60 : &mc->request.refresh.denom_sigs[idx];
1112 60 : uint8_t bit = sig->blinded_sig->details.blinded_cs_answer.b;
1113 :
1114 60 : mc->request.refresh.cs_r_choices |= bit << i;
1115 : GNUNET_static_assert (
1116 : TALER_MAX_COINS <=
1117 : sizeof(mc->request.refresh.cs_r_choices) * 8);
1118 : }
1119 : }
1120 : }
1121 38 : mc->phase++;
1122 : }
1123 :
1124 :
1125 : /**
1126 : * Generates response for the melt request.
1127 : *
1128 : * @param mc melt operation context
1129 : */
1130 : static void
1131 24 : phase_generate_reply_success (struct MeltContext *mc)
1132 : {
1133 : struct TALER_EXCHANGEDB_Refresh_v27 *db_obj;
1134 : struct TALER_ExchangePublicKeyP pub;
1135 : struct TALER_ExchangeSignatureP sig;
1136 : enum TALER_ErrorCode ec_confirmation_sign;
1137 :
1138 48 : db_obj = mc->request.is_idempotent
1139 : ? &mc->request.refresh_idem
1140 24 : : &mc->request.refresh;
1141 : ec_confirmation_sign =
1142 24 : TALER_exchange_online_melt_confirmation_sign (
1143 : &TEH_keys_exchange_sign_,
1144 24 : &db_obj->rc,
1145 : db_obj->noreveal_index,
1146 : &pub,
1147 : &sig);
1148 24 : if (TALER_EC_NONE != ec_confirmation_sign)
1149 : {
1150 0 : SET_ERROR_WITH_FIELD (mc,
1151 : MELT_ERROR_CONFIRMATION_SIGN,
1152 : ec_confirmation_sign);
1153 0 : return;
1154 : }
1155 :
1156 48 : finish_loop (mc,
1157 48 : TALER_MHD_REPLY_JSON_PACK (
1158 : mc->rc->connection,
1159 : MHD_HTTP_OK,
1160 : GNUNET_JSON_pack_uint64 ("noreveal_index",
1161 : db_obj->noreveal_index),
1162 : GNUNET_JSON_pack_data_auto ("exchange_sig",
1163 : &sig),
1164 : GNUNET_JSON_pack_data_auto ("exchange_pub",
1165 : &pub)));
1166 : }
1167 :
1168 :
1169 : /**
1170 : * Check if the melt request is replayed and we already have an answer.
1171 : * If so, replay the existing answer and return the HTTP response.
1172 : *
1173 : * @param[in,out] mc parsed request data
1174 : * @return true if the request is idempotent with an existing request
1175 : * false if we did not find the request in the DB and did not set @a mret
1176 : */
1177 : static bool
1178 13 : melt_is_idempotent (
1179 : struct MeltContext *mc)
1180 : {
1181 : enum GNUNET_DB_QueryStatus qs;
1182 :
1183 13 : qs = TEH_plugin->get_refresh (
1184 13 : TEH_plugin->cls,
1185 13 : &mc->request.refresh.rc,
1186 : &mc->request.refresh_idem);
1187 13 : if (0 > qs)
1188 : {
1189 : /* FIXME: soft error not handled correctly! */
1190 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1191 0 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
1192 0 : SET_ERROR_WITH_DETAIL (mc,
1193 : MELT_ERROR_DB_FETCH_FAILED,
1194 : db_fetch_context,
1195 : "get_refresh");
1196 0 : return true; /* Well, kind-of. */
1197 : }
1198 13 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1199 13 : return false;
1200 :
1201 0 : mc->request.is_idempotent = true;
1202 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1203 : "request is idempotent\n");
1204 :
1205 : /* Generate idempotent reply */
1206 0 : TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_MELT]++;
1207 0 : mc->phase = MELT_PHASE_GENERATE_REPLY_SUCCESS;
1208 0 : mc->error.code = MELT_ERROR_NONE;
1209 0 : return true;
1210 : }
1211 :
1212 :
1213 : /**
1214 : * Reports an error, potentially with details.
1215 : * That is, it puts a error-type specific response into the MHD queue.
1216 : * It will do a idempotency check first, if needed for the error type.
1217 : *
1218 : * @param mc melt context
1219 : */
1220 : static void
1221 13 : phase_generate_reply_error (
1222 : struct MeltContext *mc)
1223 : {
1224 13 : GNUNET_assert (MELT_PHASE_GENERATE_REPLY_ERROR == mc->phase);
1225 13 : GNUNET_assert (MELT_ERROR_NONE != mc->error.code);
1226 :
1227 26 : if (IDEMPOTENCY_CHECK_REQUIRED (mc->error.code) &&
1228 13 : melt_is_idempotent (mc))
1229 : {
1230 0 : return;
1231 : }
1232 :
1233 13 : switch (mc->error.code)
1234 : {
1235 0 : case MELT_ERROR_NONE:
1236 0 : break;
1237 0 : case MELT_ERROR_REQUEST_PARAMETER_MALFORMED:
1238 0 : finish_loop (mc,
1239 : TALER_MHD_reply_with_error (
1240 0 : mc->rc->connection,
1241 : MHD_HTTP_BAD_REQUEST,
1242 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1243 : mc->error.details.request_parameter_malformed));
1244 0 : return;
1245 0 : case MELT_ERROR_KEYS_MISSING:
1246 0 : finish_loop (mc,
1247 : TALER_MHD_reply_with_error (
1248 0 : mc->rc->connection,
1249 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1250 : TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
1251 : NULL));
1252 0 : return;
1253 0 : case MELT_ERROR_DB_FETCH_FAILED:
1254 0 : finish_loop (mc,
1255 : TALER_MHD_reply_with_error (
1256 0 : mc->rc->connection,
1257 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1258 : TALER_EC_GENERIC_DB_FETCH_FAILED,
1259 : mc->error.details.db_fetch_context));
1260 0 : return;
1261 0 : case MELT_ERROR_DB_INVARIANT_FAILURE:
1262 0 : finish_loop (mc,
1263 : TALER_MHD_reply_with_error (
1264 0 : mc->rc->connection,
1265 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1266 : TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
1267 : NULL));
1268 0 : return;
1269 0 : case MELT_ERROR_DB_PREFLIGHT_FAILURE:
1270 0 : finish_loop (mc,
1271 : TALER_MHD_reply_with_error (
1272 0 : mc->rc->connection,
1273 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1274 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
1275 : "make_coin_known"));
1276 0 : return;
1277 0 : case MELT_ERROR_DB_MAKE_COIN_KNOW_FAILURE:
1278 0 : finish_loop (mc,
1279 : TALER_MHD_reply_with_error (
1280 0 : mc->rc->connection,
1281 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1282 : TALER_EC_GENERIC_DB_START_FAILED,
1283 : "preflight failure"));
1284 0 : return;
1285 0 : case MELT_ERROR_COIN_UNKNOWN:
1286 0 : finish_loop (mc,
1287 : TALER_MHD_reply_with_ec (
1288 0 : mc->rc->connection,
1289 : TALER_EC_EXCHANGE_GENERIC_COIN_UNKNOWN,
1290 : NULL));
1291 0 : return;
1292 0 : case MELT_COIN_CONFLICTING_DENOMINATION_KEY:
1293 0 : finish_loop (mc,
1294 : TALER_MHD_reply_with_ec (
1295 0 : mc->rc->connection,
1296 : TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY,
1297 0 : TALER_B2S (&mc->error.details.denom_h)));
1298 0 : return;
1299 0 : case MELT_ERROR_COIN_EXPIRED_NO_ZOMBIE:
1300 0 : finish_loop (mc,
1301 : TALER_MHD_reply_with_error (
1302 0 : mc->rc->connection,
1303 : MHD_HTTP_BAD_REQUEST,
1304 : TALER_EC_EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE,
1305 : NULL));
1306 0 : return;
1307 0 : case MELT_ERROR_DENOMINATION_SIGN:
1308 0 : finish_loop (mc,
1309 : TALER_MHD_reply_with_ec (
1310 0 : mc->rc->connection,
1311 : mc->error.details.ec_denomination_sign,
1312 : NULL));
1313 0 : return;
1314 0 : case MELT_ERROR_DENOMINATION_SIGNATURE_INVALID:
1315 0 : finish_loop (mc,
1316 0 : TALER_MHD_reply_with_error (mc->rc->connection,
1317 : MHD_HTTP_FORBIDDEN,
1318 : TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
1319 : NULL));
1320 0 : return;
1321 0 : case MELT_ERROR_DENOMINATION_KEY_UNKNOWN:
1322 0 : GNUNET_break_op (0);
1323 0 : finish_loop (mc,
1324 : TEH_RESPONSE_reply_unknown_denom_pub_hash (
1325 0 : mc->rc->connection,
1326 0 : &mc->error.details.denom_h));
1327 0 : return;
1328 0 : case MELT_ERROR_DENOMINATION_EXPIRED:
1329 0 : GNUNET_break_op (0);
1330 0 : finish_loop (mc,
1331 : TEH_RESPONSE_reply_expired_denom_pub_hash (
1332 0 : mc->rc->connection,
1333 0 : &mc->error.details.denom_h,
1334 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
1335 : "MELT"));
1336 0 : return;
1337 0 : case MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE:
1338 0 : finish_loop (mc,
1339 : TEH_RESPONSE_reply_expired_denom_pub_hash (
1340 0 : mc->rc->connection,
1341 0 : &mc->error.details.denom_h,
1342 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
1343 : "MELT"));
1344 0 : return;
1345 0 : case MELT_ERROR_DENOMINATION_REVOKED:
1346 0 : GNUNET_break_op (0);
1347 0 : finish_loop (mc,
1348 : TALER_MHD_reply_with_ec (
1349 0 : mc->rc->connection,
1350 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
1351 : NULL));
1352 0 : return;
1353 0 : case MELT_ERROR_COIN_CIPHER_MISMATCH:
1354 0 : finish_loop (mc,
1355 : TALER_MHD_reply_with_ec (
1356 0 : mc->rc->connection,
1357 : TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
1358 : NULL));
1359 0 : return;
1360 0 : case MELT_ERROR_BLINDING_SEED_REQUIRED:
1361 0 : finish_loop (mc,
1362 : TALER_MHD_reply_with_ec (
1363 0 : mc->rc->connection,
1364 : TALER_EC_GENERIC_PARAMETER_MISSING,
1365 : "blinding_seed"));
1366 0 : return;
1367 0 : case MELT_ERROR_CRYPTO_HELPER:
1368 0 : finish_loop (mc,
1369 : TALER_MHD_reply_with_ec (
1370 0 : mc->rc->connection,
1371 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
1372 : NULL));
1373 0 : return;
1374 0 : case MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION:
1375 : {
1376 : char msg[256];
1377 :
1378 0 : GNUNET_snprintf (msg,
1379 : sizeof(msg),
1380 : "denomination %s does not support age restriction",
1381 0 : GNUNET_h2s (&mc->error.details.denom_h.hash));
1382 0 : finish_loop (mc,
1383 : TALER_MHD_reply_with_ec (
1384 0 : mc->rc->connection,
1385 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
1386 : msg));
1387 0 : return;
1388 : }
1389 0 : case MELT_ERROR_AGE_RESTRICTION_COMMITMENT_INVALID:
1390 0 : finish_loop (mc,
1391 : TALER_MHD_reply_with_ec (
1392 0 : mc->rc->connection,
1393 : TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
1394 : "old_age_commitment_h"));
1395 0 : return;
1396 0 : case MELT_ERROR_AMOUNT_OVERFLOW:
1397 0 : finish_loop (mc,
1398 : TALER_MHD_reply_with_error (
1399 0 : mc->rc->connection,
1400 : MHD_HTTP_BAD_REQUEST,
1401 : TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
1402 : "amount"));
1403 0 : return;
1404 0 : case MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW:
1405 0 : finish_loop (mc,
1406 : TALER_MHD_reply_with_error (
1407 0 : mc->rc->connection,
1408 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1409 : TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
1410 : "amount+fee"));
1411 0 : return;
1412 0 : case MELT_ERROR_FEES_EXCEED_CONTRIBUTION:
1413 0 : finish_loop (mc,
1414 0 : TALER_MHD_reply_with_error (mc->rc->connection,
1415 : MHD_HTTP_BAD_REQUEST,
1416 : TALER_EC_EXCHANGE_MELT_FEES_EXCEED_CONTRIBUTION,
1417 : NULL));
1418 0 : return;
1419 0 : case MELT_ERROR_AMOUNT_WITH_FEE_INCORRECT:
1420 0 : finish_loop (mc,
1421 : TALER_MHD_reply_with_error (
1422 0 : mc->rc->connection,
1423 : MHD_HTTP_BAD_REQUEST,
1424 : TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
1425 : "value_with_fee incorrect"));
1426 0 : return;
1427 0 : case MELT_ERROR_CONFIRMATION_SIGN:
1428 0 : finish_loop (mc,
1429 : TALER_MHD_reply_with_ec (
1430 0 : mc->rc->connection,
1431 : mc->error.details.ec_confirmation_sign,
1432 : NULL));
1433 0 : return;
1434 13 : case MELT_ERROR_INSUFFICIENT_FUNDS:
1435 13 : finish_loop (mc,
1436 : TEH_RESPONSE_reply_coin_insufficient_funds (
1437 13 : mc->rc->connection,
1438 : TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
1439 13 : &mc->request.refresh.coin.denom_pub_hash,
1440 13 : &mc->request.refresh.coin.coin_pub));
1441 13 : return;
1442 0 : case MELT_ERROR_IDEMPOTENT_PLANCHET:
1443 0 : finish_loop (mc,
1444 : TALER_MHD_reply_with_error (
1445 0 : mc->rc->connection,
1446 : MHD_HTTP_BAD_REQUEST,
1447 : TALER_EC_GENERIC_PARAMETER_MALFORMED, /* FIXME: new error! */
1448 : "idempotent planchet"));
1449 0 : return;
1450 0 : case MELT_ERROR_NONCE_RESUSE:
1451 0 : finish_loop (mc,
1452 : TALER_MHD_reply_with_error (
1453 0 : mc->rc->connection,
1454 : MHD_HTTP_BAD_REQUEST,
1455 : TALER_EC_GENERIC_PARAMETER_MALFORMED, /* FIXME: new error */
1456 : "nonce reuse"));
1457 0 : return;
1458 0 : case MELT_ERROR_COIN_SIGNATURE_INVALID:
1459 0 : finish_loop (mc,
1460 : TALER_MHD_reply_with_ec (
1461 0 : mc->rc->connection,
1462 : TALER_EC_EXCHANGE_MELT_COIN_SIGNATURE_INVALID,
1463 : NULL));
1464 0 : return;
1465 : }
1466 0 : GNUNET_break (0);
1467 0 : finish_loop (mc,
1468 : TALER_MHD_reply_with_error (
1469 0 : mc->rc->connection,
1470 : MHD_HTTP_INTERNAL_SERVER_ERROR,
1471 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
1472 : "error phase without error"));
1473 : }
1474 :
1475 :
1476 : /**
1477 : * Function implementing melt transaction. Runs the
1478 : * transaction logic; IF it returns a non-error code, the transaction
1479 : * logic MUST NOT queue a MHD response. IF it returns an hard error,
1480 : * the transaction logic MUST queue a MHD response and set @a mhd_ret.
1481 : * IF it returns the soft error code, the function MAY be called again
1482 : * to retry and MUST not queue a MHD response.
1483 : *
1484 : * @param cls a `struct MeltContext *`
1485 : * @param connection MHD request which triggered the transaction
1486 : * @param[out] mhd_ret set to MHD response status for @a connection,
1487 : * if transaction failed (!)
1488 : * @return transaction status
1489 : */
1490 : static enum GNUNET_DB_QueryStatus
1491 37 : melt_transaction (
1492 : void *cls,
1493 : struct MHD_Connection *connection,
1494 : MHD_RESULT *mhd_ret)
1495 : {
1496 37 : struct MeltContext *mc = cls;
1497 : enum GNUNET_DB_QueryStatus qs;
1498 : bool balance_ok;
1499 : bool found;
1500 : bool nonce_reuse;
1501 : uint32_t noreveal_index;
1502 : struct TALER_Amount insufficient_funds;
1503 :
1504 37 : qs = TEH_plugin->do_refresh (TEH_plugin->cls,
1505 : &mc->request.refresh,
1506 37 : &mc->now,
1507 : &found,
1508 : &noreveal_index,
1509 : &mc->zombie_required,
1510 : &nonce_reuse,
1511 : &balance_ok,
1512 : &insufficient_funds);
1513 37 : if (0 > qs)
1514 : {
1515 0 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
1516 0 : SET_ERROR_WITH_DETAIL (mc,
1517 : MELT_ERROR_DB_FETCH_FAILED,
1518 : db_fetch_context,
1519 : "do_refresh");
1520 0 : return qs;
1521 : }
1522 37 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1523 : {
1524 0 : GNUNET_break_op (0);
1525 0 : SET_ERROR (mc,
1526 : MELT_ERROR_COIN_UNKNOWN);
1527 0 : return GNUNET_DB_STATUS_HARD_ERROR;
1528 : }
1529 37 : if (found)
1530 : {
1531 : /**
1532 : * This request is idempotent, set the nonreveal_index
1533 : * to the previous one and reply success.
1534 : */
1535 8 : mc->request.refresh.noreveal_index = noreveal_index;
1536 8 : mc->phase = MELT_PHASE_GENERATE_REPLY_SUCCESS;
1537 8 : mc->error.code = MELT_ERROR_NONE;
1538 8 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
1539 : }
1540 29 : if (nonce_reuse)
1541 : {
1542 0 : GNUNET_break_op (0);
1543 0 : SET_ERROR (mc,
1544 : MELT_ERROR_NONCE_RESUSE);
1545 0 : return GNUNET_DB_STATUS_HARD_ERROR;
1546 : }
1547 29 : if (! balance_ok)
1548 : {
1549 13 : GNUNET_break_op (0);
1550 13 : SET_ERROR_WITH_FIELD (mc,
1551 : MELT_ERROR_INSUFFICIENT_FUNDS,
1552 : insufficient_funds);
1553 13 : return GNUNET_DB_STATUS_HARD_ERROR;
1554 : }
1555 16 : if (mc->zombie_required)
1556 : {
1557 0 : GNUNET_break_op (0);
1558 0 : SET_ERROR (mc,
1559 : MELT_ERROR_COIN_EXPIRED_NO_ZOMBIE);
1560 0 : return GNUNET_DB_STATUS_HARD_ERROR;
1561 : }
1562 :
1563 16 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
1564 16 : TEH_METRICS_num_success[TEH_MT_SUCCESS_MELT]++;
1565 16 : return qs;
1566 : }
1567 :
1568 :
1569 : /**
1570 : * The request was prepared successfully.
1571 : * Run the main DB transaction.
1572 : *
1573 : * @param mc The context for the current melt request
1574 : */
1575 : static void
1576 38 : phase_run_transaction (
1577 : struct MeltContext *mc)
1578 : {
1579 38 : if (GNUNET_SYSERR ==
1580 38 : TEH_plugin->preflight (TEH_plugin->cls))
1581 : {
1582 0 : GNUNET_break (0);
1583 0 : SET_ERROR (mc,
1584 : MELT_ERROR_DB_PREFLIGHT_FAILURE);
1585 0 : return;
1586 : }
1587 :
1588 : /* first, make sure coin is known */
1589 38 : if (! mc->coin_is_dirty)
1590 : {
1591 38 : MHD_RESULT mhd_ret = -1;
1592 : enum GNUNET_DB_QueryStatus qs;
1593 :
1594 38 : for (unsigned int tries = 0; tries<MAX_TRANSACTION_COMMIT_RETRIES; tries++)
1595 : {
1596 38 : qs = TEH_make_coin_known (&mc->request.refresh.coin,
1597 38 : mc->rc->connection,
1598 : &mc->known_coin_id,
1599 : &mhd_ret);
1600 38 : if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
1601 38 : break;
1602 : }
1603 38 : if (0 > qs)
1604 : {
1605 1 : GNUNET_break (0);
1606 : /* Check if an answer has been queued */
1607 1 : switch (mhd_ret)
1608 : {
1609 0 : case MHD_NO:
1610 0 : mc->phase = MELT_PHASE_RETURN_NO;
1611 1 : return;
1612 1 : case MHD_YES:
1613 1 : mc->phase = MELT_PHASE_RETURN_YES;
1614 1 : return;
1615 0 : default:
1616 : /* ignore */
1617 : }
1618 0 : SET_ERROR (mc,
1619 : MELT_ERROR_DB_MAKE_COIN_KNOW_FAILURE);
1620 0 : return;
1621 : }
1622 : }
1623 :
1624 : /* run main database transaction */
1625 : {
1626 37 : MHD_RESULT mhd_ret = -1;
1627 : enum GNUNET_GenericReturnValue ret;
1628 37 : enum MeltPhase current_phase = mc->phase;
1629 :
1630 37 : GNUNET_assert (MELT_PHASE_RUN_TRANSACTION ==
1631 : current_phase);
1632 37 : ret = TEH_DB_run_transaction (mc->rc->connection,
1633 : "run melt",
1634 : TEH_MT_REQUEST_MELT,
1635 : &mhd_ret,
1636 : &melt_transaction,
1637 : mc);
1638 37 : if (GNUNET_OK != ret)
1639 : {
1640 13 : GNUNET_break (0);
1641 : /* Check if an answer has been queued */
1642 13 : switch (mhd_ret)
1643 : {
1644 0 : case MHD_NO:
1645 0 : mc->phase = MELT_PHASE_RETURN_NO;
1646 21 : return;
1647 0 : case MHD_YES:
1648 0 : mc->phase = MELT_PHASE_RETURN_YES;
1649 0 : return;
1650 13 : default:
1651 : /* ignore */
1652 : }
1653 13 : GNUNET_assert (MELT_ERROR_NONE != mc->error.code);
1654 13 : GNUNET_assert (MELT_PHASE_GENERATE_REPLY_ERROR == mc->phase);
1655 13 : return;
1656 : }
1657 : /**
1658 : * In case of idempotency (which is not an error condition),
1659 : * the phase has changed in melt_transaction.
1660 : * We simple return.
1661 : */
1662 24 : if (current_phase != mc->phase)
1663 8 : return;
1664 : }
1665 16 : mc->phase++;
1666 : }
1667 :
1668 :
1669 : MHD_RESULT
1670 38 : TEH_handler_melt_v27 (
1671 : struct TEH_RequestContext *rc,
1672 : const json_t *root,
1673 : const char *const args[0])
1674 : {
1675 38 : struct MeltContext *mc = rc->rh_ctx;
1676 :
1677 : (void) args;
1678 38 : if (NULL == mc)
1679 : {
1680 38 : mc = GNUNET_new (struct MeltContext);
1681 38 : rc->rh_ctx = mc;
1682 38 : rc->rh_cleaner = &clean_melt_rc;
1683 38 : mc->rc = rc;
1684 38 : mc->now = GNUNET_TIME_timestamp_get ();
1685 : }
1686 :
1687 : while (true)
1688 : {
1689 568 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1690 : "melt processing in phase %d\n",
1691 : mc->phase);
1692 303 : switch (mc->phase)
1693 : {
1694 38 : case MELT_PHASE_PARSE:
1695 38 : phase_parse_request (mc,
1696 : root);
1697 38 : break;
1698 38 : case MELT_PHASE_CHECK_MELT_VALID:
1699 38 : phase_check_melt_valid (mc);
1700 38 : break;
1701 38 : case MELT_PHASE_CHECK_KEYS:
1702 38 : phase_check_keys (mc);
1703 38 : break;
1704 38 : case MELT_PHASE_CHECK_COIN_SIGNATURE:
1705 38 : phase_check_coin_signature (mc);
1706 38 : break;
1707 38 : case MELT_PHASE_PREPARE_TRANSACTION:
1708 38 : phase_prepare_transaction (mc);
1709 38 : break;
1710 38 : case MELT_PHASE_RUN_TRANSACTION:
1711 38 : phase_run_transaction (mc);
1712 38 : break;
1713 24 : case MELT_PHASE_GENERATE_REPLY_SUCCESS:
1714 24 : phase_generate_reply_success (mc);
1715 24 : break;
1716 13 : case MELT_PHASE_GENERATE_REPLY_ERROR:
1717 13 : phase_generate_reply_error (mc);
1718 13 : break;
1719 38 : case MELT_PHASE_RETURN_YES:
1720 38 : return MHD_YES;
1721 0 : case MELT_PHASE_RETURN_NO:
1722 0 : return MHD_NO;
1723 : }
1724 : }
1725 : }
|