Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2025-2026 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see
15 : <http://www.gnu.org/licenses/>
16 : */
17 : /**
18 : * @file lib/exchange_api_post-blinding-prepare.c
19 : * @brief Implementation of /blinding-prepare requests
20 : * @author Özgür Kesim
21 : * @author Christian Grothoff
22 : */
23 :
24 : #include <gnunet/gnunet_common.h>
25 : #include <jansson.h>
26 : #include <microhttpd.h> /* just for HTTP status codes */
27 : #include <gnunet/gnunet_util_lib.h>
28 : #include <gnunet/gnunet_json_lib.h>
29 : #include <gnunet/gnunet_curl_lib.h>
30 : #include <sys/wait.h>
31 : #include "taler/taler_curl_lib.h"
32 : #include "taler/taler_error_codes.h"
33 : #include "taler/taler_json_lib.h"
34 : #include "exchange_api_common.h"
35 : #include "exchange_api_handle.h"
36 : #include "taler/taler_signatures.h"
37 : #include "exchange_api_curl_defaults.h"
38 : #include "taler/taler_util.h"
39 :
40 : /**
41 : * A /blinding-prepare request-handle
42 : */
43 : struct TALER_EXCHANGE_PostBlindingPrepareHandle
44 : {
45 : /**
46 : * Number of elements to prepare.
47 : */
48 : size_t num;
49 :
50 : /**
51 : * True, if this operation is for melting (or withdraw otherwise).
52 : */
53 : bool for_melt;
54 :
55 : /**
56 : * The seed for the batch of nonces.
57 : */
58 : const struct TALER_BlindingMasterSeedP *seed;
59 :
60 : /**
61 : * Copy of the nonce_keys array passed to _create.
62 : */
63 : struct TALER_EXCHANGE_NonceKey *nonce_keys;
64 :
65 : /**
66 : * The exchange base URL.
67 : */
68 : char *exchange_url;
69 :
70 : /**
71 : * The url for this request (built in _start).
72 : */
73 : char *url;
74 :
75 : /**
76 : * Context for curl.
77 : */
78 : struct GNUNET_CURL_Context *curl_ctx;
79 :
80 : /**
81 : * CURL handle for the request job.
82 : */
83 : struct GNUNET_CURL_Job *job;
84 :
85 : /**
86 : * Post Context
87 : */
88 : struct TALER_CURL_PostContext post_ctx;
89 :
90 : /**
91 : * Function to call with response results.
92 : */
93 : TALER_EXCHANGE_PostBlindingPrepareCallback callback;
94 :
95 : /**
96 : * Closure for @e callback.
97 : */
98 : void *callback_cls;
99 :
100 : };
101 :
102 :
103 : /**
104 : * We got a 200 OK response for the /blinding-prepare operation.
105 : * Extract the r_pub values and return them to the caller via the callback.
106 : *
107 : * @param handle operation handle
108 : * @param response response details
109 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
110 : */
111 : static enum GNUNET_GenericReturnValue
112 78 : blinding_prepare_ok (
113 : struct TALER_EXCHANGE_PostBlindingPrepareHandle *handle,
114 : struct TALER_EXCHANGE_PostBlindingPrepareResponse *response)
115 : {
116 : const json_t *j_r_pubs;
117 : const char *cipher;
118 : struct GNUNET_JSON_Specification spec[] = {
119 78 : GNUNET_JSON_spec_string ("cipher",
120 : &cipher),
121 78 : GNUNET_JSON_spec_array_const ("r_pubs",
122 : &j_r_pubs),
123 78 : GNUNET_JSON_spec_end ()
124 : };
125 :
126 78 : if (GNUNET_OK !=
127 78 : GNUNET_JSON_parse (response->hr.reply,
128 : spec,
129 : NULL, NULL))
130 : {
131 0 : GNUNET_break_op (0);
132 0 : return GNUNET_SYSERR;
133 : }
134 :
135 78 : if (strcmp ("CS", cipher))
136 : {
137 0 : GNUNET_break_op (0);
138 0 : return GNUNET_SYSERR;
139 : }
140 :
141 78 : if (json_array_size (j_r_pubs)
142 78 : != handle->num)
143 : {
144 0 : GNUNET_break_op (0);
145 0 : return GNUNET_SYSERR;
146 : }
147 :
148 78 : {
149 78 : size_t num = handle->num;
150 : const json_t *j_pair;
151 : size_t idx;
152 78 : struct TALER_ExchangeBlindingValues blinding_values[GNUNET_NZL (num)];
153 :
154 78 : memset (blinding_values,
155 : 0,
156 : sizeof(blinding_values));
157 :
158 209 : json_array_foreach (j_r_pubs, idx, j_pair) {
159 : struct GNUNET_CRYPTO_BlindingInputValues *bi =
160 131 : GNUNET_new (struct GNUNET_CRYPTO_BlindingInputValues);
161 131 : struct GNUNET_CRYPTO_CSPublicRPairP *csv = &bi->details.cs_values;
162 : struct GNUNET_JSON_Specification tuple[] = {
163 131 : GNUNET_JSON_spec_fixed (NULL,
164 131 : &csv->r_pub[0],
165 : sizeof(csv->r_pub[0])),
166 131 : GNUNET_JSON_spec_fixed (NULL,
167 131 : &csv->r_pub[1],
168 : sizeof(csv->r_pub[1])),
169 131 : GNUNET_JSON_spec_end ()
170 : };
171 : struct GNUNET_JSON_Specification jspec[] = {
172 131 : TALER_JSON_spec_tuple_of (NULL, tuple),
173 131 : GNUNET_JSON_spec_end ()
174 : };
175 : const char *err_msg;
176 : unsigned int err_line;
177 :
178 131 : if (GNUNET_OK !=
179 131 : GNUNET_JSON_parse (j_pair,
180 : jspec,
181 : &err_msg,
182 : &err_line))
183 : {
184 0 : GNUNET_break_op (0);
185 0 : GNUNET_free (bi);
186 0 : for (size_t i=0; i < idx; i++)
187 0 : TALER_denom_ewv_free (&blinding_values[i]);
188 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
189 : "Error while parsing response: in line %d: %s",
190 : err_line,
191 : err_msg);
192 0 : return GNUNET_SYSERR;
193 : }
194 :
195 131 : bi->cipher = GNUNET_CRYPTO_BSA_CS;
196 131 : bi->rc = 1;
197 131 : blinding_values[idx].blinding_inputs = bi;
198 : }
199 :
200 78 : response->details.ok.blinding_values = blinding_values;
201 78 : response->details.ok.num_blinding_values = num;
202 :
203 78 : handle->callback (
204 : handle->callback_cls,
205 : response);
206 :
207 209 : for (size_t i = 0; i < num; i++)
208 131 : TALER_denom_ewv_free (&blinding_values[i]);
209 : }
210 78 : return GNUNET_OK;
211 : }
212 :
213 :
214 : /**
215 : * Function called when we're done processing the HTTP /blinding-prepare
216 : * request.
217 : *
218 : * @param cls the `struct TALER_EXCHANGE_PostBlindingPrepareHandle`
219 : * @param response_code HTTP response code, 0 on error
220 : * @param response parsed JSON result, NULL on error
221 : */
222 : static void
223 79 : handle_blinding_prepare_finished (void *cls,
224 : long response_code,
225 : const void *response)
226 : {
227 79 : struct TALER_EXCHANGE_PostBlindingPrepareHandle *handle = cls;
228 79 : const json_t *j_response = response;
229 79 : struct TALER_EXCHANGE_PostBlindingPrepareResponse bpr = {
230 : .hr = {
231 : .reply = j_response,
232 79 : .http_status = (unsigned int) response_code
233 : },
234 : };
235 :
236 79 : handle->job = NULL;
237 :
238 79 : switch (response_code)
239 : {
240 0 : case 0:
241 0 : bpr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
242 0 : break;
243 :
244 78 : case MHD_HTTP_OK:
245 : {
246 78 : if (GNUNET_OK !=
247 78 : blinding_prepare_ok (handle,
248 : &bpr))
249 : {
250 0 : GNUNET_break_op (0);
251 0 : bpr.hr.http_status = 0;
252 0 : bpr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
253 0 : break;
254 : }
255 : }
256 78 : TALER_EXCHANGE_post_blinding_prepare_cancel (handle);
257 78 : return;
258 :
259 0 : case MHD_HTTP_BAD_REQUEST:
260 : /* This should never happen, either us or the exchange is buggy
261 : (or API version conflict); just pass JSON reply to the application */
262 0 : bpr.hr.ec = TALER_JSON_get_error_code (j_response);
263 0 : bpr.hr.hint = TALER_JSON_get_error_hint (j_response);
264 0 : break;
265 :
266 0 : case MHD_HTTP_NOT_FOUND:
267 : /* Nothing really to verify, the exchange basically just says
268 : that it doesn't know the /csr endpoint or denomination.
269 : Can happen if the exchange doesn't support Clause Schnorr.
270 : We should simply pass the JSON reply to the application. */
271 0 : bpr.hr.ec = TALER_JSON_get_error_code (j_response);
272 0 : bpr.hr.hint = TALER_JSON_get_error_hint (j_response);
273 0 : break;
274 :
275 1 : case MHD_HTTP_GONE:
276 : /* could happen if denomination was revoked */
277 1 : bpr.hr.ec = TALER_JSON_get_error_code (j_response);
278 1 : bpr.hr.hint = TALER_JSON_get_error_hint (j_response);
279 1 : break;
280 :
281 0 : case MHD_HTTP_PRECONDITION_FAILED:
282 : /* could happen if we were too early and the denomination
283 : is not yet valid */
284 0 : bpr.hr.ec = TALER_JSON_get_error_code (j_response);
285 0 : bpr.hr.hint = TALER_JSON_get_error_hint (j_response);
286 0 : break;
287 :
288 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
289 : /* Server had an internal issue; we should retry, but this API
290 : leaves this to the application */
291 0 : bpr.hr.ec = TALER_JSON_get_error_code (j_response);
292 0 : bpr.hr.hint = TALER_JSON_get_error_hint (j_response);
293 0 : break;
294 :
295 0 : default:
296 : /* unexpected response code */
297 0 : GNUNET_break_op (0);
298 0 : bpr.hr.ec = TALER_JSON_get_error_code (j_response);
299 0 : bpr.hr.hint = TALER_JSON_get_error_hint (j_response);
300 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
301 : "Unexpected response code %u/%d for the blinding-prepare request\n",
302 : (unsigned int) response_code,
303 : (int) bpr.hr.ec);
304 0 : break;
305 :
306 : }
307 :
308 1 : handle->callback (handle->callback_cls,
309 : &bpr);
310 1 : handle->callback = NULL;
311 1 : TALER_EXCHANGE_post_blinding_prepare_cancel (handle);
312 : }
313 :
314 :
315 : struct TALER_EXCHANGE_PostBlindingPrepareHandle *
316 79 : TALER_EXCHANGE_post_blinding_prepare_create (
317 : struct GNUNET_CURL_Context *curl_ctx,
318 : const char *exchange_url,
319 : const struct TALER_BlindingMasterSeedP *seed,
320 : bool for_melt,
321 : size_t num,
322 : const struct TALER_EXCHANGE_NonceKey nonce_keys[static num])
323 79 : {
324 : struct TALER_EXCHANGE_PostBlindingPrepareHandle *bph;
325 :
326 79 : if (0 == num)
327 : {
328 0 : GNUNET_break (0);
329 0 : return NULL;
330 : }
331 211 : for (unsigned int i = 0; i < num; i++)
332 132 : if (GNUNET_CRYPTO_BSA_CS !=
333 132 : nonce_keys[i].pk->key.bsign_pub_key->cipher)
334 : {
335 0 : GNUNET_break (0);
336 0 : return NULL;
337 : }
338 79 : bph = GNUNET_new (struct TALER_EXCHANGE_PostBlindingPrepareHandle);
339 79 : bph->num = num;
340 79 : bph->for_melt = for_melt;
341 79 : bph->seed = seed;
342 79 : bph->curl_ctx = curl_ctx;
343 79 : bph->exchange_url = GNUNET_strdup (exchange_url);
344 79 : bph->nonce_keys = GNUNET_new_array (num,
345 : struct TALER_EXCHANGE_NonceKey);
346 79 : memcpy (bph->nonce_keys,
347 : nonce_keys,
348 : num * sizeof (struct TALER_EXCHANGE_NonceKey));
349 79 : return bph;
350 : }
351 :
352 :
353 : enum TALER_ErrorCode
354 79 : TALER_EXCHANGE_post_blinding_prepare_start (
355 : struct TALER_EXCHANGE_PostBlindingPrepareHandle *bph,
356 : TALER_EXCHANGE_PostBlindingPrepareCallback cb,
357 : TALER_EXCHANGE_POST_BLINDING_PREPARE_RESULT_CLOSURE *cb_cls)
358 : {
359 : CURL *eh;
360 : json_t *j_nks;
361 : json_t *j_request;
362 :
363 79 : bph->callback = cb;
364 79 : bph->callback_cls = cb_cls;
365 :
366 79 : bph->url = TALER_url_join (bph->exchange_url,
367 : "blinding-prepare",
368 : NULL);
369 79 : if (NULL == bph->url)
370 : {
371 0 : GNUNET_break (0);
372 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
373 : }
374 :
375 79 : j_request = GNUNET_JSON_PACK (
376 : GNUNET_JSON_pack_string ("cipher",
377 : "CS"),
378 : GNUNET_JSON_pack_string ("operation",
379 : bph->for_melt ? "melt" : "withdraw"),
380 : GNUNET_JSON_pack_data_auto ("seed",
381 : bph->seed));
382 79 : GNUNET_assert (NULL != j_request);
383 :
384 79 : j_nks = json_array ();
385 79 : GNUNET_assert (NULL != j_nks);
386 :
387 211 : for (size_t i = 0; i < bph->num; i++)
388 : {
389 132 : const struct TALER_EXCHANGE_NonceKey *nk = &bph->nonce_keys[i];
390 132 : json_t *j_entry = GNUNET_JSON_PACK (
391 : GNUNET_JSON_pack_uint64 ("coin_offset",
392 : nk->cnc_num),
393 : GNUNET_JSON_pack_data_auto ("denom_pub_hash",
394 : &nk->pk->h_key));
395 :
396 132 : GNUNET_assert (NULL != j_entry);
397 132 : GNUNET_assert (0 ==
398 : json_array_append_new (j_nks,
399 : j_entry));
400 : }
401 79 : GNUNET_assert (0 ==
402 : json_object_set_new (j_request,
403 : "nks",
404 : j_nks));
405 :
406 79 : eh = TALER_EXCHANGE_curl_easy_get_ (bph->url);
407 158 : if ( (NULL == eh) ||
408 : (GNUNET_OK !=
409 79 : TALER_curl_easy_post (&bph->post_ctx,
410 : eh,
411 : j_request)))
412 : {
413 0 : GNUNET_break (0);
414 0 : if (NULL != eh)
415 0 : curl_easy_cleanup (eh);
416 0 : json_decref (j_request);
417 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
418 : }
419 :
420 79 : json_decref (j_request);
421 158 : bph->job = GNUNET_CURL_job_add2 (bph->curl_ctx,
422 : eh,
423 79 : bph->post_ctx.headers,
424 : &handle_blinding_prepare_finished,
425 : bph);
426 79 : if (NULL == bph->job)
427 : {
428 0 : GNUNET_break (0);
429 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
430 : }
431 79 : return TALER_EC_NONE;
432 : }
433 :
434 :
435 : void
436 186 : TALER_EXCHANGE_post_blinding_prepare_cancel (
437 : struct TALER_EXCHANGE_PostBlindingPrepareHandle *bph)
438 : {
439 186 : if (NULL == bph)
440 107 : return;
441 79 : if (NULL != bph->job)
442 : {
443 0 : GNUNET_CURL_job_cancel (bph->job);
444 0 : bph->job = NULL;
445 : }
446 79 : GNUNET_free (bph->url);
447 79 : GNUNET_free (bph->exchange_url);
448 79 : GNUNET_free (bph->nonce_keys);
449 79 : TALER_curl_easy_post_finished (&bph->post_ctx);
450 79 : GNUNET_free (bph);
451 : }
452 :
453 :
454 : /* end of lib/exchange_api_post-blinding-prepare.c */
|