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 General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see
15 : <http://www.gnu.org/licenses/>
16 : */
17 : /**
18 : * @file lib/exchange_api_reveal_melt.c
19 : * @brief Implementation of the /reveal-melt request
20 : * @author Özgür Kesim
21 : */
22 : #include "platform.h"
23 : #include <jansson.h>
24 : #include <microhttpd.h> /* just for HTTP status codes */
25 : #include <gnunet/gnunet_util_lib.h>
26 : #include <gnunet/gnunet_json_lib.h>
27 : #include <gnunet/gnunet_curl_lib.h>
28 : #include "taler_json_lib.h"
29 : #include "taler_exchange_service.h"
30 : #include "exchange_api_common.h"
31 : #include "exchange_api_handle.h"
32 : #include "taler_signatures.h"
33 : #include "exchange_api_curl_defaults.h"
34 : #include "exchange_api_refresh_common.h"
35 :
36 :
37 : /**
38 : * Handler for a running reveal-melt request
39 : */
40 : struct TALER_EXCHANGE_RevealMeltHandle
41 : {
42 : /**
43 : * The url for the request
44 : */
45 : char *request_url;
46 :
47 : /**
48 : * CURL handle for the request job.
49 : */
50 : struct GNUNET_CURL_Job *job;
51 :
52 : /**
53 : * Post Context
54 : */
55 : struct TALER_CURL_PostContext post_ctx;
56 :
57 : /**
58 : * Number of coins to expect
59 : */
60 : size_t num_expected_coins;
61 :
62 : /**
63 : * The input provided
64 : */
65 : const struct TALER_EXCHANGE_RevealMeltInput *reveal_input;
66 :
67 : /**
68 : * The melt data
69 : */
70 : struct MeltData_v27 md;
71 :
72 : /**
73 : * Callback to pass the result onto
74 : */
75 : TALER_EXCHANGE_RevealMeltCallback callback;
76 :
77 : /**
78 : * Closure for @e callback
79 : */
80 : void *callback_cls;
81 :
82 : };
83 :
84 : /**
85 : * We got a 200 OK response for the /reveal-melt operation.
86 : * Extract the signed blinded coins and return it to the caller.
87 : *
88 : * @param mrh operation handle
89 : * @param j_response reply from the exchange
90 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
91 : */
92 : static enum GNUNET_GenericReturnValue
93 14 : reveal_melt_ok (
94 : struct TALER_EXCHANGE_RevealMeltHandle *mrh,
95 : const json_t *j_response)
96 14 : {
97 14 : struct TALER_EXCHANGE_RevealMeltResponse response = {
98 : .hr.reply = j_response,
99 : .hr.http_status = MHD_HTTP_OK,
100 : };
101 14 : struct TALER_BlindedDenominationSignature blind_sigs[mrh->num_expected_coins];
102 : struct GNUNET_JSON_Specification spec[] = {
103 14 : TALER_JSON_spec_array_of_blinded_denom_sigs ("ev_sigs",
104 : mrh->num_expected_coins,
105 : blind_sigs),
106 14 : GNUNET_JSON_spec_end ()
107 : };
108 14 : if (GNUNET_OK !=
109 14 : GNUNET_JSON_parse (j_response,
110 : spec,
111 : NULL, NULL))
112 : {
113 0 : GNUNET_break_op (0);
114 0 : return GNUNET_SYSERR;
115 : }
116 :
117 14 : {
118 14 : struct TALER_EXCHANGE_RevealedCoinInfo coins[mrh->num_expected_coins];
119 :
120 : /* Reconstruct the coins and unblind the signatures */
121 70 : for (unsigned int i = 0; i<mrh->num_expected_coins; i++)
122 : {
123 56 : struct TALER_EXCHANGE_RevealedCoinInfo *rci = &coins[i];
124 56 : const struct FreshCoinData *fcd = &mrh->md.fcds[i];
125 : const struct TALER_DenominationPublicKey *pk;
126 : struct TALER_CoinSpendPublicKeyP coin_pub;
127 : struct TALER_CoinPubHashP coin_hash;
128 : struct TALER_FreshCoin coin;
129 : union GNUNET_CRYPTO_BlindingSecretP bks;
130 56 : const struct TALER_AgeCommitmentHash *pah = NULL;
131 :
132 56 : rci->ps = fcd->ps[mrh->reveal_input->noreveal_index];
133 56 : rci->bks = fcd->bks[mrh->reveal_input->noreveal_index];
134 56 : rci->age_commitment_proof = NULL;
135 56 : pk = &fcd->fresh_pk;
136 56 : if (NULL != mrh->md.melted_coin.age_commitment_proof)
137 : {
138 : rci->age_commitment_proof
139 32 : = fcd->age_commitment_proofs[mrh->reveal_input->noreveal_index];
140 32 : TALER_age_commitment_hash (
141 32 : &rci->age_commitment_proof->commitment,
142 : &rci->h_age_commitment);
143 32 : pah = &rci->h_age_commitment;
144 : }
145 :
146 56 : TALER_planchet_setup_coin_priv (&rci->ps,
147 56 : &mrh->reveal_input->blinding_values[i],
148 : &rci->coin_priv);
149 56 : TALER_planchet_blinding_secret_create (&rci->ps,
150 56 : &mrh->reveal_input->blinding_values
151 56 : [i],
152 : &bks);
153 : /* needed to verify the signature, and we didn't store it earlier,
154 : hence recomputing it here... */
155 56 : GNUNET_CRYPTO_eddsa_key_get_public (&rci->coin_priv.eddsa_priv,
156 : &coin_pub.eddsa_pub);
157 56 : TALER_coin_pub_hash (&coin_pub,
158 : pah,
159 : &coin_hash);
160 56 : if (GNUNET_OK !=
161 56 : TALER_planchet_to_coin (pk,
162 56 : &blind_sigs[i],
163 : &bks,
164 56 : &rci->coin_priv,
165 : pah,
166 : &coin_hash,
167 56 : &mrh->reveal_input->blinding_values[i],
168 : &coin))
169 : {
170 0 : GNUNET_break_op (0);
171 0 : GNUNET_JSON_parse_free (spec);
172 0 : return GNUNET_SYSERR;
173 : }
174 56 : GNUNET_JSON_parse_free (spec);
175 56 : rci->sig = coin.sig;
176 : }
177 :
178 14 : response.details.ok.num_coins = mrh->num_expected_coins;
179 14 : response.details.ok.coins = coins;
180 14 : mrh->callback (mrh->callback_cls,
181 : &response);
182 : /* Make sure the callback isn't called again */
183 14 : mrh->callback = NULL;
184 : /* Free resources */
185 70 : for (size_t i = 0; i < mrh->num_expected_coins; i++)
186 : {
187 56 : struct TALER_EXCHANGE_RevealedCoinInfo *rci = &coins[i];
188 :
189 56 : TALER_denom_sig_free (&rci->sig);
190 56 : TALER_blinded_denom_sig_free (&blind_sigs[i]);
191 : }
192 : }
193 :
194 14 : return GNUNET_OK;
195 : }
196 :
197 :
198 : /**
199 : * Function called when we're done processing the
200 : * HTTP /reveal-melt request.
201 : *
202 : * @param cls the `struct TALER_EXCHANGE_RevealMeltHandle`
203 : * @param response_code The HTTP response code
204 : * @param response response data
205 : */
206 : static void
207 14 : handle_reveal_melt_finished (
208 : void *cls,
209 : long response_code,
210 : const void *response)
211 : {
212 14 : struct TALER_EXCHANGE_RevealMeltHandle *mrh = cls;
213 14 : const json_t *j_response = response;
214 14 : struct TALER_EXCHANGE_RevealMeltResponse awr = {
215 : .hr.reply = j_response,
216 14 : .hr.http_status = (unsigned int) response_code
217 : };
218 :
219 14 : mrh->job = NULL;
220 14 : switch (response_code)
221 : {
222 0 : case 0:
223 0 : awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
224 0 : break;
225 14 : case MHD_HTTP_OK:
226 : {
227 : enum GNUNET_GenericReturnValue ret;
228 :
229 14 : ret = reveal_melt_ok (mrh,
230 : j_response);
231 14 : if (GNUNET_OK != ret)
232 : {
233 0 : GNUNET_break_op (0);
234 0 : awr.hr.http_status = 0;
235 0 : awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
236 0 : break;
237 : }
238 14 : GNUNET_assert (NULL == mrh->callback);
239 14 : TALER_EXCHANGE_reveal_melt_cancel (mrh);
240 14 : return;
241 : }
242 0 : case MHD_HTTP_BAD_REQUEST:
243 : /* This should never happen, either us or the exchange is buggy
244 : (or API version conflict); just pass JSON reply to the application */
245 0 : awr.hr.ec = TALER_JSON_get_error_code (j_response);
246 0 : awr.hr.hint = TALER_JSON_get_error_hint (j_response);
247 0 : break;
248 0 : case MHD_HTTP_NOT_FOUND:
249 : /* Nothing really to verify, the exchange basically just says
250 : that it doesn't know this age-melt commitment. */
251 0 : awr.hr.ec = TALER_JSON_get_error_code (j_response);
252 0 : awr.hr.hint = TALER_JSON_get_error_hint (j_response);
253 0 : break;
254 0 : case MHD_HTTP_CONFLICT:
255 : /* An age commitment for one of the coins did not fulfill
256 : * the required maximum age requirement of the corresponding
257 : * reserve.
258 : * Error code: TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE
259 : * or TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH.
260 : */
261 0 : awr.hr.ec = TALER_JSON_get_error_code (j_response);
262 0 : awr.hr.hint = TALER_JSON_get_error_hint (j_response);
263 0 : break;
264 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
265 : /* Server had an internal issue; we should retry, but this API
266 : leaves this to the application */
267 0 : awr.hr.ec = TALER_JSON_get_error_code (j_response);
268 0 : awr.hr.hint = TALER_JSON_get_error_hint (j_response);
269 0 : break;
270 0 : default:
271 : /* unexpected response code */
272 0 : GNUNET_break_op (0);
273 0 : awr.hr.ec = TALER_JSON_get_error_code (j_response);
274 0 : awr.hr.hint = TALER_JSON_get_error_hint (j_response);
275 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
276 : "Unexpected response code %u/%d for exchange melt\n",
277 : (unsigned int) response_code,
278 : (int) awr.hr.ec);
279 0 : break;
280 : }
281 0 : mrh->callback (mrh->callback_cls,
282 : &awr);
283 0 : TALER_EXCHANGE_reveal_melt_cancel (mrh);
284 : }
285 :
286 :
287 : /**
288 : * Call /reveal-melt
289 : *
290 : * @param curl_ctx The context for CURL
291 : * @param mrh The handler
292 : */
293 : static void
294 14 : perform_protocol (
295 : struct GNUNET_CURL_Context *curl_ctx,
296 : struct TALER_EXCHANGE_RevealMeltHandle *mrh)
297 : {
298 : CURL *curlh;
299 : json_t *j_array_of_signatures;
300 :
301 :
302 14 : j_array_of_signatures = json_array ();
303 14 : GNUNET_assert (NULL != j_array_of_signatures);
304 :
305 56 : for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
306 : {
307 42 : if (mrh->reveal_input->noreveal_index == k)
308 14 : continue;
309 :
310 28 : GNUNET_assert (0 == json_array_append_new (
311 : j_array_of_signatures,
312 : GNUNET_JSON_from_data_auto (&mrh->md.signatures[k])));
313 : }
314 : {
315 : json_t *j_request_body;
316 :
317 14 : j_request_body = GNUNET_JSON_PACK (
318 : GNUNET_JSON_pack_data_auto ("rc",
319 : &mrh->md.rc),
320 : GNUNET_JSON_pack_array_steal ("signatures",
321 : j_array_of_signatures));
322 14 : GNUNET_assert (NULL != j_request_body);
323 :
324 14 : if (NULL != mrh->reveal_input->melt_input->melt_age_commitment_proof)
325 : {
326 8 : json_t *j_age = GNUNET_JSON_PACK (
327 : TALER_JSON_pack_age_commitment (
328 : "age_commitment",
329 : &mrh->reveal_input->melt_input->melt_age_commitment_proof->commitment)
330 : );
331 8 : GNUNET_assert (NULL != j_age);
332 8 : GNUNET_assert (0 ==
333 : json_object_update_new (j_request_body,
334 : j_age));
335 : }
336 14 : curlh = TALER_EXCHANGE_curl_easy_get_ (mrh->request_url);
337 14 : GNUNET_assert (NULL != curlh);
338 14 : GNUNET_assert (GNUNET_OK ==
339 : TALER_curl_easy_post (&mrh->post_ctx,
340 : curlh,
341 : j_request_body));
342 14 : json_decref (j_request_body);
343 : }
344 :
345 28 : mrh->job = GNUNET_CURL_job_add2 (
346 : curl_ctx,
347 : curlh,
348 14 : mrh->post_ctx.headers,
349 : &handle_reveal_melt_finished,
350 : mrh);
351 14 : if (NULL == mrh->job)
352 : {
353 0 : GNUNET_break (0);
354 0 : if (NULL != curlh)
355 0 : curl_easy_cleanup (curlh);
356 0 : TALER_EXCHANGE_reveal_melt_cancel (mrh);
357 : }
358 14 : }
359 :
360 :
361 : struct TALER_EXCHANGE_RevealMeltHandle *
362 14 : TALER_EXCHANGE_reveal_melt (
363 : struct GNUNET_CURL_Context *curl_ctx,
364 : const char *exchange_url,
365 : const struct TALER_EXCHANGE_RevealMeltInput *reveal_melt_input,
366 : TALER_EXCHANGE_RevealMeltCallback reveal_cb,
367 : void *reveal_cb_cls)
368 : {
369 : struct TALER_EXCHANGE_RevealMeltHandle *mrh =
370 14 : GNUNET_new (struct TALER_EXCHANGE_RevealMeltHandle);
371 14 : mrh->callback = reveal_cb;
372 14 : mrh->callback_cls = reveal_cb_cls;
373 14 : mrh->reveal_input = reveal_melt_input;
374 14 : mrh->num_expected_coins = reveal_melt_input->melt_input->num_fresh_denom_pubs;
375 14 : mrh->request_url = TALER_url_join (exchange_url,
376 : "reveal-melt",
377 : NULL);
378 14 : if (NULL == mrh->request_url)
379 : {
380 0 : GNUNET_break (0);
381 0 : GNUNET_free (mrh);
382 0 : return NULL;
383 : }
384 14 : if (reveal_melt_input->num_blinding_values !=
385 14 : reveal_melt_input->melt_input->num_fresh_denom_pubs)
386 : {
387 0 : GNUNET_break (0);
388 0 : GNUNET_free (mrh);
389 0 : return NULL;
390 : }
391 14 : TALER_EXCHANGE_get_melt_data_v27 (
392 14 : reveal_melt_input->rms,
393 14 : reveal_melt_input->melt_input,
394 14 : reveal_melt_input->blinding_seed,
395 14 : reveal_melt_input->blinding_values,
396 : &mrh->md);
397 14 : perform_protocol (curl_ctx,
398 : mrh);
399 14 : return mrh;
400 : }
401 :
402 :
403 : void
404 14 : TALER_EXCHANGE_reveal_melt_cancel (
405 : struct TALER_EXCHANGE_RevealMeltHandle *mrh)
406 : {
407 14 : if (NULL != mrh->job)
408 : {
409 0 : GNUNET_CURL_job_cancel (mrh->job);
410 0 : mrh->job = NULL;
411 : }
412 14 : TALER_curl_easy_post_finished (&mrh->post_ctx);
413 14 : TALER_EXCHANGE_free_melt_data_v27 (&mrh->md);
414 14 : GNUNET_free (mrh->request_url);
415 14 : GNUNET_free (mrh);
416 14 : }
|