Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2022 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_refreshes_reveal.c
18 : * @brief Handle /refreshes/$RCH/reveal requests
19 : * @author Florian Dold
20 : * @author Benedikt Mueller
21 : * @author Christian Grothoff
22 : */
23 : #include "platform.h"
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <jansson.h>
26 : #include <microhttpd.h>
27 : #include "taler_mhd_lib.h"
28 : #include "taler-exchange-httpd_mhd.h"
29 : #include "taler-exchange-httpd_refreshes_reveal.h"
30 : #include "taler-exchange-httpd_responses.h"
31 : #include "taler-exchange-httpd_keys.h"
32 :
33 :
34 : /**
35 : * Send a response for "/refreshes/$RCH/reveal".
36 : *
37 : * @param connection the connection to send the response to
38 : * @param num_freshcoins number of new coins for which we reveal data
39 : * @param rrcs array of @a num_freshcoins signatures revealed
40 : * @return a MHD result code
41 : */
42 : static MHD_RESULT
43 0 : reply_refreshes_reveal_success (
44 : struct MHD_Connection *connection,
45 : unsigned int num_freshcoins,
46 : const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs)
47 : {
48 : json_t *list;
49 :
50 0 : list = json_array ();
51 0 : GNUNET_assert (NULL != list);
52 0 : for (unsigned int freshcoin_index = 0;
53 : freshcoin_index < num_freshcoins;
54 0 : freshcoin_index++)
55 : {
56 : json_t *obj;
57 :
58 0 : obj = GNUNET_JSON_PACK (
59 : TALER_JSON_pack_blinded_denom_sig ("ev_sig",
60 : &rrcs[freshcoin_index].coin_sig));
61 0 : GNUNET_assert (0 ==
62 : json_array_append_new (list,
63 : obj));
64 : }
65 :
66 0 : return TALER_MHD_REPLY_JSON_PACK (
67 : connection,
68 : MHD_HTTP_OK,
69 : GNUNET_JSON_pack_array_steal ("ev_sigs",
70 : list));
71 : }
72 :
73 :
74 : /**
75 : * State for a /refreshes/$RCH/reveal operation.
76 : */
77 : struct RevealContext
78 : {
79 :
80 : /**
81 : * Commitment of the refresh operation.
82 : */
83 : struct TALER_RefreshCommitmentP rc;
84 :
85 : /**
86 : * Transfer public key at gamma.
87 : */
88 : struct TALER_TransferPublicKeyP gamma_tp;
89 :
90 : /**
91 : * Transfer private keys revealed to us.
92 : */
93 : struct TALER_TransferPrivateKeyP transfer_privs[TALER_CNC_KAPPA - 1];
94 :
95 : /**
96 : * Melt data for our session we got from the database for @e rc.
97 : */
98 : struct TALER_EXCHANGEDB_Melt melt;
99 :
100 : /**
101 : * Denominations being requested.
102 : */
103 : const struct TEH_DenominationKey **dks;
104 :
105 : /**
106 : * Age commitment that was used for the original coin. If not NULL, its hash
107 : * should be the same as melt.session.h_age_commitment.
108 : */
109 : struct TALER_AgeCommitment *old_age_commitment;
110 :
111 : /**
112 : * Array of information about fresh coins being revealed.
113 : */
114 : /* FIXME: const would be nicer here, but we initialize
115 : the 'alg_values' in the verification
116 : routine; suboptimal to be fixed... */
117 : struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs;
118 :
119 : /**
120 : * Envelopes to be signed.
121 : */
122 : struct TALER_RefreshCoinData *rcds;
123 :
124 : /**
125 : * Refresh master secret.
126 : */
127 : struct TALER_RefreshMasterSecretP rms;
128 :
129 : /**
130 : * Size of the @e dks, @e rcds and @e ev_sigs arrays (if non-NULL).
131 : */
132 : unsigned int num_fresh_coins;
133 :
134 : /**
135 : * True if @e rms was not provided.
136 : */
137 : bool no_rms;
138 : };
139 :
140 :
141 : /**
142 : * Check client's revelation against the original commitment.
143 : * The client is revealing to us the
144 : * transfer keys for @a #TALER_CNC_KAPPA-1 sets of coins. Verify that the
145 : * revealed transfer keys would allow linkage to the blinded coins.
146 : *
147 : * IF it returns #GNUNET_OK, the transaction logic MUST
148 : * NOT queue a MHD response. IF it returns an error, the
149 : * transaction logic MUST queue a MHD response and set @a mhd_ret.
150 : *
151 : * @param rctx our operation context
152 : * @param connection MHD request which triggered the transaction
153 : * @param[out] mhd_ret set to MHD response status for @a connection,
154 : * if transaction failed (!)
155 : * @return #GNUNET_OK if commitment was OK
156 : */
157 : static enum GNUNET_GenericReturnValue
158 0 : check_commitment (struct RevealContext *rctx,
159 : struct MHD_Connection *connection,
160 : MHD_RESULT *mhd_ret)
161 0 : {
162 0 : struct TALER_CsNonce nonces[rctx->num_fresh_coins];
163 0 : unsigned int aoff = 0;
164 :
165 0 : for (unsigned int j = 0; j<rctx->num_fresh_coins; j++)
166 : {
167 0 : const struct TALER_DenominationPublicKey *dk = &rctx->dks[j]->denom_pub;
168 :
169 0 : if (dk->cipher != rctx->rcds[j].blinded_planchet.cipher)
170 : {
171 0 : GNUNET_break (0);
172 0 : *mhd_ret = TALER_MHD_reply_with_error (
173 : connection,
174 : MHD_HTTP_BAD_REQUEST,
175 : TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
176 : NULL);
177 0 : return GNUNET_SYSERR;
178 : }
179 0 : switch (dk->cipher)
180 : {
181 0 : case TALER_DENOMINATION_INVALID:
182 0 : GNUNET_break (0);
183 0 : *mhd_ret = TALER_MHD_reply_with_error (
184 : connection,
185 : MHD_HTTP_INTERNAL_SERVER_ERROR,
186 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
187 : NULL);
188 0 : return GNUNET_SYSERR;
189 0 : case TALER_DENOMINATION_RSA:
190 0 : continue;
191 0 : case TALER_DENOMINATION_CS:
192 : nonces[aoff]
193 0 : = rctx->rcds[j].blinded_planchet.details.cs_blinded_planchet.nonce;
194 0 : aoff++;
195 0 : break;
196 : }
197 0 : }
198 :
199 : // OPTIMIZE: do this in batch later!
200 0 : aoff = 0;
201 0 : for (unsigned int j = 0; j<rctx->num_fresh_coins; j++)
202 : {
203 0 : const struct TALER_DenominationPublicKey *dk = &rctx->dks[j]->denom_pub;
204 0 : struct TALER_ExchangeWithdrawValues *alg_values
205 0 : = &rctx->rrcs[j].exchange_vals;
206 :
207 0 : alg_values->cipher = dk->cipher;
208 0 : switch (dk->cipher)
209 : {
210 0 : case TALER_DENOMINATION_INVALID:
211 0 : GNUNET_assert (0);
212 : return GNUNET_SYSERR;
213 0 : case TALER_DENOMINATION_RSA:
214 0 : continue;
215 0 : case TALER_DENOMINATION_CS:
216 : {
217 : enum TALER_ErrorCode ec;
218 :
219 0 : ec = TEH_keys_denomination_cs_r_pub_melt (
220 0 : &rctx->rrcs[j].h_denom_pub,
221 0 : &nonces[aoff],
222 : &alg_values->details.cs_values);
223 0 : if (TALER_EC_NONE != ec)
224 : {
225 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
226 : MHD_HTTP_INTERNAL_SERVER_ERROR,
227 : ec,
228 : NULL);
229 0 : return GNUNET_SYSERR;
230 : }
231 0 : aoff++;
232 : }
233 : }
234 : }
235 : /* Verify commitment */
236 : {
237 : /* Note that the contents of rcs[melt.session.noreveal_index]
238 : will be aliased and are *not* allocated (or deallocated) in
239 : this function -- in contrast to the other offsets! */
240 : struct TALER_RefreshCommitmentEntry rcs[TALER_CNC_KAPPA];
241 : struct TALER_RefreshCommitmentP rc_expected;
242 : unsigned int off;
243 :
244 0 : off = 0; /* did we pass session.noreveal_index yet? */
245 0 : for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
246 : {
247 0 : struct TALER_RefreshCommitmentEntry *rce = &rcs[i];
248 :
249 0 : if (i == rctx->melt.session.noreveal_index)
250 : {
251 : /* Take these coin envelopes from the client */
252 0 : rce->transfer_pub = rctx->gamma_tp;
253 0 : rce->new_coins = rctx->rcds;
254 0 : off = 1;
255 : }
256 : else
257 : {
258 : /* Reconstruct coin envelopes from transfer private key */
259 0 : const struct TALER_TransferPrivateKeyP *tpriv
260 0 : = &rctx->transfer_privs[i - off];
261 : struct TALER_TransferSecretP ts;
262 0 : struct TALER_AgeCommitmentHash h = {0};
263 0 : struct TALER_AgeCommitmentHash *hac = NULL;
264 :
265 0 : GNUNET_CRYPTO_ecdhe_key_get_public (&tpriv->ecdhe_priv,
266 : &rce->transfer_pub.ecdhe_pub);
267 0 : TEH_METRICS_num_keyexchanges[TEH_MT_KEYX_ECDH]++;
268 0 : TALER_link_reveal_transfer_secret (tpriv,
269 0 : &rctx->melt.session.coin.coin_pub,
270 : &ts);
271 0 : rce->new_coins = GNUNET_new_array (rctx->num_fresh_coins,
272 : struct TALER_RefreshCoinData);
273 0 : aoff = 0;
274 0 : for (unsigned int j = 0; j<rctx->num_fresh_coins; j++)
275 : {
276 0 : const struct TALER_DenominationPublicKey *dk
277 0 : = &rctx->dks[j]->denom_pub;
278 0 : struct TALER_RefreshCoinData *rcd = &rce->new_coins[j];
279 : struct TALER_CoinSpendPrivateKeyP coin_priv;
280 : union TALER_DenominationBlindingKeyP bks;
281 0 : const struct TALER_ExchangeWithdrawValues *alg_value
282 0 : = &rctx->rrcs[j].exchange_vals;
283 0 : struct TALER_PlanchetDetail pd = {0};
284 : struct TALER_CoinPubHashP c_hash;
285 : struct TALER_PlanchetMasterSecretP ps;
286 :
287 0 : rcd->dk = &rctx->dks[j]->denom_pub;
288 0 : TALER_transfer_secret_to_planchet_secret (&ts,
289 : j,
290 : &ps);
291 0 : TALER_planchet_setup_coin_priv (&ps,
292 : alg_value,
293 : &coin_priv);
294 0 : TALER_planchet_blinding_secret_create (&ps,
295 : alg_value,
296 : &bks);
297 : /* Calculate, if applicable, the age commitment and its hash, from
298 : * the transfer_secret and the old age commitment. */
299 0 : if (NULL != rctx->old_age_commitment)
300 : {
301 0 : struct TALER_AgeCommitmentProof acp = {
302 : /* we only need the commitment, not the proof, for the call to
303 : * TALER_age_commitment_derive. */
304 0 : .commitment = *(rctx->old_age_commitment)
305 : };
306 0 : struct TALER_AgeCommitmentProof nacp = {0};
307 :
308 0 : GNUNET_assert (GNUNET_OK ==
309 : TALER_age_commitment_derive (
310 : &acp,
311 : &ts.key,
312 : &nacp));
313 :
314 0 : TALER_age_commitment_hash (&nacp.commitment, &h);
315 0 : hac = &h;
316 : }
317 :
318 0 : GNUNET_assert (GNUNET_OK ==
319 : TALER_planchet_prepare (rcd->dk,
320 : alg_value,
321 : &bks,
322 : &coin_priv,
323 : hac,
324 : &c_hash,
325 : &pd));
326 0 : if (TALER_DENOMINATION_CS == dk->cipher)
327 : {
328 0 : pd.blinded_planchet.details.cs_blinded_planchet.nonce =
329 : nonces[aoff];
330 0 : aoff++;
331 : }
332 0 : rcd->blinded_planchet = pd.blinded_planchet;
333 : }
334 : }
335 : }
336 0 : TALER_refresh_get_commitment (&rc_expected,
337 : TALER_CNC_KAPPA,
338 0 : rctx->no_rms
339 : ? NULL
340 : : &rctx->rms,
341 : rctx->num_fresh_coins,
342 : rcs,
343 0 : &rctx->melt.session.coin.coin_pub,
344 0 : &rctx->melt.session.amount_with_fee);
345 :
346 : /* Free resources allocated above */
347 0 : for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
348 : {
349 0 : struct TALER_RefreshCommitmentEntry *rce = &rcs[i];
350 :
351 0 : if (i == rctx->melt.session.noreveal_index)
352 0 : continue; /* This offset is special: not allocated! */
353 0 : for (unsigned int j = 0; j<rctx->num_fresh_coins; j++)
354 : {
355 0 : struct TALER_RefreshCoinData *rcd = &rce->new_coins[j];
356 :
357 0 : TALER_blinded_planchet_free (&rcd->blinded_planchet);
358 : }
359 0 : GNUNET_free (rce->new_coins);
360 : }
361 :
362 : /* Verify rc_expected matches rc */
363 0 : if (0 != GNUNET_memcmp (&rctx->rc,
364 : &rc_expected))
365 : {
366 0 : GNUNET_break_op (0);
367 0 : *mhd_ret = TALER_MHD_REPLY_JSON_PACK (
368 : connection,
369 : MHD_HTTP_CONFLICT,
370 : TALER_JSON_pack_ec (
371 : TALER_EC_EXCHANGE_REFRESHES_REVEAL_COMMITMENT_VIOLATION),
372 : GNUNET_JSON_pack_data_auto ("rc_expected",
373 : &rc_expected));
374 0 : return GNUNET_SYSERR;
375 : }
376 : } /* end of checking "rc_expected" */
377 :
378 : /* check amounts add up! */
379 : {
380 : struct TALER_Amount refresh_cost;
381 :
382 0 : refresh_cost = rctx->melt.melt_fee;
383 0 : for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
384 : {
385 : struct TALER_Amount total;
386 :
387 0 : if ( (0 >
388 0 : TALER_amount_add (&total,
389 0 : &rctx->dks[i]->meta.fees.withdraw,
390 0 : &rctx->dks[i]->meta.value)) ||
391 : (0 >
392 0 : TALER_amount_add (&refresh_cost,
393 : &refresh_cost,
394 : &total)) )
395 : {
396 0 : GNUNET_break_op (0);
397 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
398 : MHD_HTTP_INTERNAL_SERVER_ERROR,
399 : TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
400 : NULL);
401 0 : return GNUNET_SYSERR;
402 : }
403 : }
404 0 : if (0 < TALER_amount_cmp (&refresh_cost,
405 0 : &rctx->melt.session.amount_with_fee))
406 : {
407 0 : GNUNET_break_op (0);
408 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
409 : MHD_HTTP_BAD_REQUEST,
410 : TALER_EC_EXCHANGE_REFRESHES_REVEAL_AMOUNT_INSUFFICIENT,
411 : NULL);
412 0 : return GNUNET_SYSERR;
413 : }
414 : }
415 0 : return GNUNET_OK;
416 : }
417 :
418 :
419 : /**
420 : * Resolve denomination hashes.
421 : *
422 : * @param connection the MHD connection to handle
423 : * @param rctx context for the operation, partially built at this time
424 : * @param link_sigs_json link signatures in JSON format
425 : * @param new_denoms_h_json requests for fresh coins to be created
426 : * @param old_age_commitment_json age commitment that went into the withdrawal, maybe NULL
427 : * @param coin_evs envelopes of gamma-selected coins to be signed
428 : * @return MHD result code
429 : */
430 : static MHD_RESULT
431 0 : resolve_refreshes_reveal_denominations (
432 : struct MHD_Connection *connection,
433 : struct RevealContext *rctx,
434 : const json_t *link_sigs_json,
435 : const json_t *new_denoms_h_json,
436 : const json_t *old_age_commitment_json,
437 : const json_t *coin_evs)
438 0 : {
439 0 : unsigned int num_fresh_coins = json_array_size (new_denoms_h_json);
440 : /* We know num_fresh_coins is bounded by #TALER_MAX_FRESH_COINS, so this is safe */
441 0 : const struct TEH_DenominationKey *dks[num_fresh_coins];
442 : const struct TEH_DenominationKey *old_dk;
443 0 : struct TALER_RefreshCoinData rcds[num_fresh_coins];
444 0 : struct TALER_EXCHANGEDB_RefreshRevealedCoin rrcs[num_fresh_coins];
445 : MHD_RESULT ret;
446 : struct TEH_KeyStateHandle *ksh;
447 : uint64_t melt_serial_id;
448 : enum GNUNET_DB_QueryStatus qs;
449 :
450 0 : memset (dks, 0, sizeof (dks));
451 0 : memset (rrcs, 0, sizeof (rrcs));
452 0 : memset (rcds, 0, sizeof (rcds));
453 0 : rctx->num_fresh_coins = num_fresh_coins;
454 :
455 0 : ksh = TEH_keys_get_state ();
456 0 : if (NULL == ksh)
457 : {
458 0 : return TALER_MHD_reply_with_error (connection,
459 : MHD_HTTP_INTERNAL_SERVER_ERROR,
460 : TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
461 : NULL);
462 : }
463 :
464 : /* lookup old_coin_pub in database */
465 : {
466 : enum GNUNET_DB_QueryStatus qs;
467 :
468 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
469 0 : (qs = TEH_plugin->get_melt (TEH_plugin->cls,
470 0 : &rctx->rc,
471 : &rctx->melt,
472 : &melt_serial_id)))
473 : {
474 0 : switch (qs)
475 : {
476 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
477 0 : ret = TALER_MHD_reply_with_error (connection,
478 : MHD_HTTP_NOT_FOUND,
479 : TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN,
480 : NULL);
481 0 : break;
482 0 : case GNUNET_DB_STATUS_HARD_ERROR:
483 0 : ret = TALER_MHD_reply_with_error (connection,
484 : MHD_HTTP_INTERNAL_SERVER_ERROR,
485 : TALER_EC_GENERIC_DB_FETCH_FAILED,
486 : "melt");
487 0 : break;
488 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
489 : default:
490 0 : GNUNET_break (0); /* should be impossible */
491 0 : ret = TALER_MHD_reply_with_error (connection,
492 : MHD_HTTP_INTERNAL_SERVER_ERROR,
493 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
494 : NULL);
495 0 : break;
496 : }
497 0 : goto cleanup;
498 : }
499 0 : if (rctx->melt.session.noreveal_index >= TALER_CNC_KAPPA)
500 : {
501 0 : GNUNET_break (0);
502 0 : ret = TALER_MHD_reply_with_error (connection,
503 : MHD_HTTP_INTERNAL_SERVER_ERROR,
504 : TALER_EC_GENERIC_DB_FETCH_FAILED,
505 : "melt");
506 0 : goto cleanup;
507 : }
508 : }
509 :
510 0 : old_dk = TEH_keys_denomination_by_hash2 (
511 : ksh,
512 0 : &rctx->melt.session.coin.denom_pub_hash,
513 : connection,
514 : &ret);
515 0 : if (NULL == old_dk)
516 0 : return ret;
517 :
518 : /* Parse denomination key hashes */
519 0 : for (unsigned int i = 0; i<num_fresh_coins; i++)
520 : {
521 : struct GNUNET_JSON_Specification spec[] = {
522 0 : GNUNET_JSON_spec_fixed_auto (NULL,
523 : &rrcs[i].h_denom_pub),
524 0 : GNUNET_JSON_spec_end ()
525 : };
526 : enum GNUNET_GenericReturnValue res;
527 :
528 0 : res = TALER_MHD_parse_json_array (connection,
529 : new_denoms_h_json,
530 : spec,
531 : i,
532 : -1);
533 0 : if (GNUNET_OK != res)
534 0 : return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
535 0 : dks[i] = TEH_keys_denomination_by_hash2 (ksh,
536 0 : &rrcs[i].h_denom_pub,
537 : connection,
538 : &ret);
539 0 : if (NULL == dks[i])
540 0 : return ret;
541 0 : if ( (TALER_DENOMINATION_CS == dks[i]->denom_pub.cipher) &&
542 0 : (rctx->no_rms) )
543 : {
544 0 : return TALER_MHD_reply_with_error (
545 : connection,
546 : MHD_HTTP_BAD_REQUEST,
547 : TALER_EC_GENERIC_PARAMETER_MISSING,
548 : "rms");
549 : }
550 0 : if (GNUNET_TIME_absolute_is_past (dks[i]->meta.expire_withdraw.abs_time))
551 : {
552 : /* This denomination is past the expiration time for withdraws */
553 0 : return TEH_RESPONSE_reply_expired_denom_pub_hash (
554 : connection,
555 0 : &rrcs[i].h_denom_pub,
556 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
557 : "REVEAL");
558 : }
559 0 : if (GNUNET_TIME_absolute_is_future (dks[i]->meta.start.abs_time))
560 : {
561 : /* This denomination is not yet valid */
562 0 : return TEH_RESPONSE_reply_expired_denom_pub_hash (
563 : connection,
564 0 : &rrcs[i].h_denom_pub,
565 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
566 : "REVEAL");
567 : }
568 0 : if (dks[i]->recoup_possible)
569 : {
570 : /* This denomination has been revoked */
571 0 : return TALER_MHD_reply_with_error (
572 : connection,
573 : MHD_HTTP_GONE,
574 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
575 : NULL);
576 : }
577 : }
578 :
579 : /* Parse coin envelopes */
580 0 : for (unsigned int i = 0; i<num_fresh_coins; i++)
581 : {
582 0 : struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
583 : struct GNUNET_JSON_Specification spec[] = {
584 0 : TALER_JSON_spec_blinded_planchet (NULL,
585 : &rrc->blinded_planchet),
586 0 : GNUNET_JSON_spec_end ()
587 : };
588 : enum GNUNET_GenericReturnValue res;
589 :
590 0 : res = TALER_MHD_parse_json_array (connection,
591 : coin_evs,
592 : spec,
593 : i,
594 : -1);
595 0 : if (GNUNET_OK != res)
596 : {
597 0 : for (unsigned int j = 0; j<i; j++)
598 0 : TALER_blinded_planchet_free (&rrcs[j].blinded_planchet);
599 0 : return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
600 : }
601 0 : TALER_coin_ev_hash (&rrc->blinded_planchet,
602 0 : &rrcs[i].h_denom_pub,
603 : &rrc->coin_envelope_hash);
604 : }
605 :
606 0 : if (TEH_age_restriction_enabled &&
607 0 : ((NULL == old_age_commitment_json) !=
608 0 : TALER_AgeCommitmentHash_isNullOrZero (
609 : &rctx->melt.session.coin.h_age_commitment)))
610 : {
611 0 : GNUNET_break (0);
612 0 : return MHD_NO;
613 : }
614 :
615 : /* Reconstruct the old age commitment and verify its hash matches the one
616 : * from the melt request */
617 0 : if (TEH_age_restriction_enabled &&
618 : (NULL != old_age_commitment_json))
619 : {
620 : enum GNUNET_GenericReturnValue res;
621 : struct TALER_AgeCommitment *oac;
622 0 : size_t ng = json_array_size (old_age_commitment_json);
623 0 : bool failed = true;
624 :
625 : /* Has been checked in handle_refreshes_reveal_json() */
626 0 : GNUNET_assert (ng ==
627 : TALER_extensions_age_restriction_num_groups ());
628 :
629 0 : rctx->old_age_commitment = GNUNET_new (struct TALER_AgeCommitment);
630 0 : oac = rctx->old_age_commitment;
631 0 : oac->mask = old_dk->meta.age_mask;
632 0 : oac->num = ng;
633 0 : oac->keys = GNUNET_new_array (ng, struct TALER_AgeCommitmentPublicKeyP);
634 :
635 : /* Extract old age commitment */
636 0 : for (unsigned int i = 0; i< ng; i++)
637 : {
638 : struct GNUNET_JSON_Specification ac_spec[] = {
639 0 : GNUNET_JSON_spec_fixed_auto (NULL,
640 : &oac->keys[i]),
641 0 : GNUNET_JSON_spec_end ()
642 : };
643 :
644 0 : res = TALER_MHD_parse_json_array (connection,
645 : old_age_commitment_json,
646 : ac_spec,
647 : i,
648 : -1);
649 :
650 0 : GNUNET_break_op (GNUNET_OK == res);
651 0 : if (GNUNET_OK != res)
652 0 : goto clean_age;
653 : }
654 :
655 : /* Sanity check: Compare hash from melting with hash of this age commitment */
656 : {
657 0 : struct TALER_AgeCommitmentHash hac = {0};
658 0 : TALER_age_commitment_hash (oac, &hac);
659 0 : if (0 != memcmp (&hac,
660 0 : &rctx->melt.session.coin.h_age_commitment,
661 : sizeof(struct TALER_AgeCommitmentHash)))
662 0 : goto clean_age;
663 : }
664 :
665 0 : failed = false;
666 :
667 0 : clean_age:
668 0 : if (failed)
669 : {
670 0 : TALER_age_commitment_free (oac);
671 0 : return TALER_MHD_reply_with_error (connection,
672 : MHD_HTTP_BAD_REQUEST,
673 : TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
674 : "old_age_commitment");
675 : }
676 : }
677 :
678 : /* Parse link signatures array */
679 0 : for (unsigned int i = 0; i<num_fresh_coins; i++)
680 : {
681 : struct GNUNET_JSON_Specification link_spec[] = {
682 0 : GNUNET_JSON_spec_fixed_auto (NULL,
683 : &rrcs[i].orig_coin_link_sig),
684 0 : GNUNET_JSON_spec_end ()
685 : };
686 : enum GNUNET_GenericReturnValue res;
687 :
688 0 : res = TALER_MHD_parse_json_array (connection,
689 : link_sigs_json,
690 : link_spec,
691 : i,
692 : -1);
693 0 : if (GNUNET_OK != res)
694 0 : return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
695 :
696 : /* Check signature */
697 0 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
698 0 : if (GNUNET_OK !=
699 0 : TALER_wallet_link_verify (
700 0 : &rrcs[i].h_denom_pub,
701 0 : &rctx->gamma_tp,
702 0 : &rrcs[i].coin_envelope_hash,
703 0 : &rctx->melt.session.coin.coin_pub,
704 0 : &rrcs[i].orig_coin_link_sig))
705 : {
706 0 : GNUNET_break_op (0);
707 0 : ret = TALER_MHD_reply_with_error (
708 : connection,
709 : MHD_HTTP_FORBIDDEN,
710 : TALER_EC_EXCHANGE_REFRESHES_REVEAL_LINK_SIGNATURE_INVALID,
711 : NULL);
712 0 : goto cleanup;
713 : }
714 : }
715 :
716 : /* prepare for check_commitment */
717 0 : for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
718 : {
719 0 : const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
720 0 : struct TALER_RefreshCoinData *rcd = &rcds[i];
721 :
722 0 : rcd->blinded_planchet = rrc->blinded_planchet;
723 0 : rcd->dk = &dks[i]->denom_pub;
724 0 : if (rcd->blinded_planchet.cipher != rcd->dk->cipher)
725 : {
726 0 : GNUNET_break_op (0);
727 0 : ret = TALER_MHD_REPLY_JSON_PACK (
728 : connection,
729 : MHD_HTTP_BAD_REQUEST,
730 : TALER_JSON_pack_ec (
731 : TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH));
732 0 : goto cleanup;
733 : }
734 : }
735 :
736 0 : rctx->dks = dks;
737 0 : rctx->rcds = rcds;
738 0 : rctx->rrcs = rrcs;
739 0 : if (GNUNET_OK !=
740 0 : check_commitment (rctx,
741 : connection,
742 : &ret))
743 0 : goto cleanup;
744 :
745 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
746 : "Creating %u signatures\n",
747 : (unsigned int) rctx->num_fresh_coins);
748 :
749 : /* create fresh coin signatures */
750 0 : for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
751 : {
752 : enum TALER_ErrorCode ec;
753 :
754 : // FIXME #7272: replace with a batch call that
755 : // passes all coins in once go!
756 0 : ec = TEH_keys_denomination_sign_melt (
757 0 : &rrcs[i].h_denom_pub,
758 0 : &rcds[i].blinded_planchet,
759 : &rrcs[i].coin_sig);
760 0 : if (TALER_EC_NONE != ec)
761 : {
762 0 : GNUNET_break (0);
763 0 : ret = TALER_MHD_reply_with_ec (connection,
764 : ec,
765 : NULL);
766 0 : goto cleanup;
767 : }
768 : }
769 :
770 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
771 : "Signatures ready, starting DB interaction\n");
772 :
773 0 : for (unsigned int r = 0; r<MAX_TRANSACTION_COMMIT_RETRIES; r++)
774 : {
775 : bool changed;
776 :
777 : /* Persist operation result in DB */
778 0 : if (GNUNET_OK !=
779 0 : TEH_plugin->start (TEH_plugin->cls,
780 : "insert_refresh_reveal batch"))
781 : {
782 0 : GNUNET_break (0);
783 0 : ret = TALER_MHD_reply_with_error (connection,
784 : MHD_HTTP_INTERNAL_SERVER_ERROR,
785 : TALER_EC_GENERIC_DB_START_FAILED,
786 : NULL);
787 0 : goto cleanup;
788 : }
789 0 : for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
790 : {
791 0 : struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
792 :
793 0 : rrc->blinded_planchet = rcds[i].blinded_planchet;
794 : }
795 0 : qs = TEH_plugin->insert_refresh_reveal (
796 0 : TEH_plugin->cls,
797 : melt_serial_id,
798 : num_fresh_coins,
799 : rrcs,
800 : TALER_CNC_KAPPA - 1,
801 0 : rctx->transfer_privs,
802 0 : &rctx->gamma_tp);
803 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
804 : {
805 0 : TEH_plugin->rollback (TEH_plugin->cls);
806 0 : continue;
807 : }
808 : /* 0 == qs is ok, as we did not check for repeated requests */
809 0 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
810 : {
811 0 : GNUNET_break (0);
812 0 : TEH_plugin->rollback (TEH_plugin->cls);
813 0 : ret = TALER_MHD_reply_with_error (connection,
814 : MHD_HTTP_INTERNAL_SERVER_ERROR,
815 : TALER_EC_GENERIC_DB_STORE_FAILED,
816 : "insert_refresh_reveal");
817 0 : goto cleanup;
818 : }
819 0 : changed = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
820 0 : qs = TEH_plugin->commit (TEH_plugin->cls);
821 0 : if (qs >= 0)
822 : {
823 0 : if (changed)
824 0 : TEH_METRICS_num_success[TEH_MT_SUCCESS_REFRESH_REVEAL]++;
825 0 : break; /* success */
826 : }
827 0 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
828 : {
829 0 : GNUNET_break (0);
830 0 : TEH_plugin->rollback (TEH_plugin->cls);
831 0 : ret = TALER_MHD_reply_with_error (connection,
832 : MHD_HTTP_INTERNAL_SERVER_ERROR,
833 : TALER_EC_GENERIC_DB_COMMIT_FAILED,
834 : NULL);
835 0 : goto cleanup;
836 : }
837 0 : TEH_plugin->rollback (TEH_plugin->cls);
838 : }
839 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
840 : {
841 0 : GNUNET_break (0);
842 0 : TEH_plugin->rollback (TEH_plugin->cls);
843 0 : ret = TALER_MHD_reply_with_error (connection,
844 : MHD_HTTP_INTERNAL_SERVER_ERROR,
845 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
846 : NULL);
847 0 : goto cleanup;
848 : }
849 : /* Generate final (positive) response */
850 0 : ret = reply_refreshes_reveal_success (connection,
851 : num_fresh_coins,
852 : rrcs);
853 0 : cleanup:
854 0 : GNUNET_break (MHD_NO != ret);
855 : /* free resources */
856 0 : for (unsigned int i = 0; i<num_fresh_coins; i++)
857 : {
858 0 : struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
859 :
860 0 : TALER_blinded_denom_sig_free (&rrc->coin_sig);
861 0 : TALER_blinded_planchet_free (&rrc->blinded_planchet);
862 : }
863 0 : return ret;
864 : }
865 :
866 :
867 : /**
868 : * Handle a "/refreshes/$RCH/reveal" request. Parses the given JSON
869 : * transfer private keys and if successful, passes everything to
870 : * #resolve_refreshes_reveal_denominations() which will verify that the
871 : * revealed information is valid then returns the signed refreshed
872 : * coins.
873 : *
874 : * If the denomination has age restriction support, the array of EDDSA public
875 : * keys, one for each age group that was activated during the withdrawal
876 : * by the parent/ward, must be provided in old_age_commitment. The hash of
877 : * this array must be the same as the h_age_commitment of the persisted reveal
878 : * request.
879 : *
880 : * @param connection the MHD connection to handle
881 : * @param rctx context for the operation, partially built at this time
882 : * @param tp_json private transfer keys in JSON format
883 : * @param link_sigs_json link signatures in JSON format
884 : * @param new_denoms_h_json requests for fresh coins to be created
885 : * @param old_age_commitment_json array of EDDSA public keys in JSON, used for age restriction, maybe NULL
886 : * @param coin_evs envelopes of gamma-selected coins to be signed
887 : * @return MHD result code
888 : */
889 : static MHD_RESULT
890 0 : handle_refreshes_reveal_json (struct MHD_Connection *connection,
891 : struct RevealContext *rctx,
892 : const json_t *tp_json,
893 : const json_t *link_sigs_json,
894 : const json_t *new_denoms_h_json,
895 : const json_t *old_age_commitment_json,
896 : const json_t *coin_evs)
897 : {
898 0 : unsigned int num_fresh_coins = json_array_size (new_denoms_h_json);
899 0 : unsigned int num_tprivs = json_array_size (tp_json);
900 :
901 0 : GNUNET_assert (num_tprivs == TALER_CNC_KAPPA - 1); /* checked just earlier */
902 0 : if ( (num_fresh_coins >= TALER_MAX_FRESH_COINS) ||
903 : (0 == num_fresh_coins) )
904 : {
905 0 : GNUNET_break_op (0);
906 0 : return TALER_MHD_reply_with_error (connection,
907 : MHD_HTTP_BAD_REQUEST,
908 : TALER_EC_EXCHANGE_GENERIC_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE,
909 : NULL);
910 :
911 : }
912 0 : if (json_array_size (new_denoms_h_json) !=
913 0 : json_array_size (coin_evs))
914 : {
915 0 : GNUNET_break_op (0);
916 0 : return TALER_MHD_reply_with_error (connection,
917 : MHD_HTTP_BAD_REQUEST,
918 : TALER_EC_EXCHANGE_REFRESHES_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISMATCH,
919 : "new_denoms/coin_evs");
920 : }
921 0 : if (json_array_size (new_denoms_h_json) !=
922 0 : json_array_size (link_sigs_json))
923 : {
924 0 : GNUNET_break_op (0);
925 0 : return TALER_MHD_reply_with_error (connection,
926 : MHD_HTTP_BAD_REQUEST,
927 : TALER_EC_EXCHANGE_REFRESHES_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISMATCH,
928 : "new_denoms/link_sigs");
929 : }
930 :
931 : /* Sanity check of age commitment: If it was provided, it _must_ be an array
932 : * of the size the # of age groups */
933 0 : if (NULL != old_age_commitment_json
934 0 : && TALER_extensions_age_restriction_num_groups () !=
935 0 : json_array_size (old_age_commitment_json))
936 : {
937 0 : GNUNET_break_op (0);
938 0 : return TALER_MHD_reply_with_error (connection,
939 : MHD_HTTP_BAD_REQUEST,
940 : TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
941 : "old_age_commitment");
942 : }
943 :
944 : /* Parse transfer private keys array */
945 0 : for (unsigned int i = 0; i<num_tprivs; i++)
946 : {
947 : struct GNUNET_JSON_Specification trans_spec[] = {
948 0 : GNUNET_JSON_spec_fixed_auto (NULL,
949 : &rctx->transfer_privs[i]),
950 0 : GNUNET_JSON_spec_end ()
951 : };
952 : enum GNUNET_GenericReturnValue res;
953 :
954 0 : res = TALER_MHD_parse_json_array (connection,
955 : tp_json,
956 : trans_spec,
957 : i,
958 : -1);
959 0 : if (GNUNET_OK != res)
960 0 : return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
961 : }
962 :
963 0 : return resolve_refreshes_reveal_denominations (connection,
964 : rctx,
965 : link_sigs_json,
966 : new_denoms_h_json,
967 : old_age_commitment_json,
968 : coin_evs);
969 : }
970 :
971 :
972 : MHD_RESULT
973 0 : TEH_handler_reveal (struct TEH_RequestContext *rc,
974 : const json_t *root,
975 : const char *const args[2])
976 : {
977 : json_t *coin_evs;
978 : json_t *transfer_privs;
979 : json_t *link_sigs;
980 : json_t *new_denoms_h;
981 : json_t *old_age_commitment;
982 : struct RevealContext rctx;
983 : struct GNUNET_JSON_Specification spec[] = {
984 0 : GNUNET_JSON_spec_fixed_auto ("transfer_pub",
985 : &rctx.gamma_tp),
986 0 : GNUNET_JSON_spec_json ("transfer_privs",
987 : &transfer_privs),
988 0 : GNUNET_JSON_spec_json ("link_sigs",
989 : &link_sigs),
990 0 : GNUNET_JSON_spec_json ("coin_evs",
991 : &coin_evs),
992 0 : GNUNET_JSON_spec_json ("new_denoms_h",
993 : &new_denoms_h),
994 0 : GNUNET_JSON_spec_mark_optional (
995 : GNUNET_JSON_spec_json ("old_age_commitment",
996 : &old_age_commitment),
997 : NULL),
998 0 : GNUNET_JSON_spec_mark_optional (
999 : GNUNET_JSON_spec_fixed_auto ("rms",
1000 : &rctx.rms),
1001 : &rctx.no_rms),
1002 0 : GNUNET_JSON_spec_end ()
1003 : };
1004 :
1005 0 : memset (&rctx,
1006 : 0,
1007 : sizeof (rctx));
1008 0 : if (GNUNET_OK !=
1009 0 : GNUNET_STRINGS_string_to_data (args[0],
1010 : strlen (args[0]),
1011 : &rctx.rc,
1012 : sizeof (rctx.rc)))
1013 : {
1014 0 : GNUNET_break_op (0);
1015 0 : return TALER_MHD_reply_with_error (rc->connection,
1016 : MHD_HTTP_BAD_REQUEST,
1017 : TALER_EC_EXCHANGE_REFRESHES_REVEAL_INVALID_RCH,
1018 : args[0]);
1019 : }
1020 0 : if (0 != strcmp (args[1],
1021 : "reveal"))
1022 : {
1023 0 : GNUNET_break_op (0);
1024 0 : return TALER_MHD_reply_with_error (rc->connection,
1025 : MHD_HTTP_BAD_REQUEST,
1026 : TALER_EC_EXCHANGE_REFRESHES_REVEAL_OPERATION_INVALID,
1027 0 : args[1]);
1028 : }
1029 :
1030 : {
1031 : enum GNUNET_GenericReturnValue res;
1032 :
1033 0 : res = TALER_MHD_parse_json_data (rc->connection,
1034 : root,
1035 : spec);
1036 0 : if (GNUNET_OK != res)
1037 : {
1038 0 : GNUNET_break_op (0);
1039 0 : return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
1040 : }
1041 : }
1042 :
1043 : /* Check we got enough transfer private keys */
1044 : /* Note we do +1 as 1 row (cut-and-choose!) is missing! */
1045 0 : if (TALER_CNC_KAPPA != json_array_size (transfer_privs) + 1)
1046 : {
1047 0 : GNUNET_JSON_parse_free (spec);
1048 0 : GNUNET_break_op (0);
1049 0 : return TALER_MHD_reply_with_error (rc->connection,
1050 : MHD_HTTP_BAD_REQUEST,
1051 : TALER_EC_EXCHANGE_REFRESHES_REVEAL_CNC_TRANSFER_ARRAY_SIZE_INVALID,
1052 : NULL);
1053 : }
1054 :
1055 : {
1056 : MHD_RESULT res;
1057 :
1058 0 : res = handle_refreshes_reveal_json (rc->connection,
1059 : &rctx,
1060 : transfer_privs,
1061 : link_sigs,
1062 : new_denoms_h,
1063 : old_age_commitment,
1064 : coin_evs);
1065 0 : GNUNET_JSON_parse_free (spec);
1066 0 : return res;
1067 : }
1068 : }
1069 :
1070 :
1071 : /* end of taler-exchange-httpd_refreshes_reveal.c */
|