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 50 : 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 50 : GNUNET_JSON_spec_string ("cipher",
120 : &cipher),
121 50 : GNUNET_JSON_spec_array_const ("r_pubs",
122 : &j_r_pubs),
123 50 : GNUNET_JSON_spec_end ()
124 : };
125 :
126 50 : if (GNUNET_OK !=
127 50 : 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 50 : if (strcmp ("CS", cipher))
136 : {
137 0 : GNUNET_break_op (0);
138 0 : return GNUNET_SYSERR;
139 : }
140 :
141 50 : if (json_array_size (j_r_pubs)
142 50 : != handle->num)
143 : {
144 0 : GNUNET_break_op (0);
145 0 : return GNUNET_SYSERR;
146 : }
147 :
148 50 : {
149 50 : size_t num = handle->num;
150 : const json_t *j_pair;
151 : size_t idx;
152 50 : struct TALER_ExchangeBlindingValues blinding_values[GNUNET_NZL (num)];
153 :
154 50 : memset (blinding_values,
155 : 0,
156 : sizeof(blinding_values));
157 :
158 148 : json_array_foreach (j_r_pubs, idx, j_pair) {
159 : struct GNUNET_CRYPTO_BlindingInputValues *bi =
160 98 : GNUNET_new (struct GNUNET_CRYPTO_BlindingInputValues);
161 98 : struct GNUNET_CRYPTO_CSPublicRPairP *csv = &bi->details.cs_values;
162 : struct GNUNET_JSON_Specification tuple[] = {
163 98 : GNUNET_JSON_spec_fixed (NULL,
164 98 : &csv->r_pub[0],
165 : sizeof(csv->r_pub[0])),
166 98 : GNUNET_JSON_spec_fixed (NULL,
167 98 : &csv->r_pub[1],
168 : sizeof(csv->r_pub[1])),
169 98 : GNUNET_JSON_spec_end ()
170 : };
171 : struct GNUNET_JSON_Specification jspec[] = {
172 98 : TALER_JSON_spec_tuple_of (NULL, tuple),
173 98 : GNUNET_JSON_spec_end ()
174 : };
175 : const char *err_msg;
176 : unsigned int err_line;
177 :
178 98 : if (GNUNET_OK !=
179 98 : 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 98 : bi->cipher = GNUNET_CRYPTO_BSA_CS;
196 98 : bi->rc = 1;
197 98 : blinding_values[idx].blinding_inputs = bi;
198 : }
199 :
200 50 : response->details.ok.blinding_values = blinding_values;
201 50 : response->details.ok.num_blinding_values = num;
202 :
203 50 : handle->callback (
204 : handle->callback_cls,
205 : response);
206 :
207 148 : for (size_t i = 0; i < num; i++)
208 98 : TALER_denom_ewv_free (&blinding_values[i]);
209 : }
210 50 : 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 50 : handle_blinding_prepare_finished (void *cls,
224 : long response_code,
225 : const void *response)
226 : {
227 50 : struct TALER_EXCHANGE_PostBlindingPrepareHandle *handle = cls;
228 50 : const json_t *j_response = response;
229 50 : struct TALER_EXCHANGE_PostBlindingPrepareResponse bpr = {
230 : .hr = {
231 : .reply = j_response,
232 50 : .http_status = (unsigned int) response_code
233 : },
234 : };
235 :
236 50 : handle->job = NULL;
237 :
238 50 : switch (response_code)
239 : {
240 0 : case 0:
241 0 : bpr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
242 0 : break;
243 :
244 50 : case MHD_HTTP_OK:
245 : {
246 50 : if (GNUNET_OK !=
247 50 : 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 50 : TALER_EXCHANGE_post_blinding_prepare_cancel (handle);
257 50 : 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 0 : case MHD_HTTP_GONE:
276 : /* could happen if denomination was revoked */
277 0 : bpr.hr.ec = TALER_JSON_get_error_code (j_response);
278 0 : bpr.hr.hint = TALER_JSON_get_error_hint (j_response);
279 0 : break;
280 :
281 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
282 : /* Server had an internal issue; we should retry, but this API
283 : leaves this to the application */
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 : default:
289 : /* unexpected response code */
290 0 : GNUNET_break_op (0);
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 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
294 : "Unexpected response code %u/%d for the blinding-prepare request\n",
295 : (unsigned int) response_code,
296 : (int) bpr.hr.ec);
297 0 : break;
298 :
299 : }
300 :
301 0 : handle->callback (handle->callback_cls,
302 : &bpr);
303 0 : handle->callback = NULL;
304 0 : TALER_EXCHANGE_post_blinding_prepare_cancel (handle);
305 : }
306 :
307 :
308 : struct TALER_EXCHANGE_PostBlindingPrepareHandle *
309 50 : TALER_EXCHANGE_post_blinding_prepare_create (
310 : struct GNUNET_CURL_Context *curl_ctx,
311 : const char *exchange_url,
312 : const struct TALER_BlindingMasterSeedP *seed,
313 : bool for_melt,
314 : size_t num,
315 : const struct TALER_EXCHANGE_NonceKey nonce_keys[static num])
316 50 : {
317 : struct TALER_EXCHANGE_PostBlindingPrepareHandle *bph;
318 :
319 50 : if (0 == num)
320 : {
321 0 : GNUNET_break (0);
322 0 : return NULL;
323 : }
324 148 : for (unsigned int i = 0; i < num; i++)
325 98 : if (GNUNET_CRYPTO_BSA_CS !=
326 98 : nonce_keys[i].pk->key.bsign_pub_key->cipher)
327 : {
328 0 : GNUNET_break (0);
329 0 : return NULL;
330 : }
331 50 : bph = GNUNET_new (struct TALER_EXCHANGE_PostBlindingPrepareHandle);
332 50 : bph->num = num;
333 50 : bph->for_melt = for_melt;
334 50 : bph->seed = seed;
335 50 : bph->curl_ctx = curl_ctx;
336 50 : bph->exchange_url = GNUNET_strdup (exchange_url);
337 50 : bph->nonce_keys = GNUNET_new_array (num,
338 : struct TALER_EXCHANGE_NonceKey);
339 50 : memcpy (bph->nonce_keys,
340 : nonce_keys,
341 : num * sizeof (struct TALER_EXCHANGE_NonceKey));
342 50 : return bph;
343 : }
344 :
345 :
346 : enum TALER_ErrorCode
347 50 : TALER_EXCHANGE_post_blinding_prepare_start (
348 : struct TALER_EXCHANGE_PostBlindingPrepareHandle *bph,
349 : TALER_EXCHANGE_PostBlindingPrepareCallback cb,
350 : TALER_EXCHANGE_POST_BLINDING_PREPARE_RESULT_CLOSURE *cb_cls)
351 : {
352 : CURL *eh;
353 : json_t *j_nks;
354 : json_t *j_request;
355 :
356 50 : bph->callback = cb;
357 50 : bph->callback_cls = cb_cls;
358 :
359 50 : bph->url = TALER_url_join (bph->exchange_url,
360 : "blinding-prepare",
361 : NULL);
362 50 : if (NULL == bph->url)
363 : {
364 0 : GNUNET_break (0);
365 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
366 : }
367 :
368 50 : j_request = GNUNET_JSON_PACK (
369 : GNUNET_JSON_pack_string ("cipher",
370 : "CS"),
371 : GNUNET_JSON_pack_string ("operation",
372 : bph->for_melt ? "melt" : "withdraw"),
373 : GNUNET_JSON_pack_data_auto ("seed",
374 : bph->seed));
375 50 : GNUNET_assert (NULL != j_request);
376 :
377 50 : j_nks = json_array ();
378 50 : GNUNET_assert (NULL != j_nks);
379 :
380 148 : for (size_t i = 0; i < bph->num; i++)
381 : {
382 98 : const struct TALER_EXCHANGE_NonceKey *nk = &bph->nonce_keys[i];
383 98 : json_t *j_entry = GNUNET_JSON_PACK (
384 : GNUNET_JSON_pack_uint64 ("coin_offset",
385 : nk->cnc_num),
386 : GNUNET_JSON_pack_data_auto ("denom_pub_hash",
387 : &nk->pk->h_key));
388 :
389 98 : GNUNET_assert (NULL != j_entry);
390 98 : GNUNET_assert (0 ==
391 : json_array_append_new (j_nks,
392 : j_entry));
393 : }
394 50 : GNUNET_assert (0 ==
395 : json_object_set_new (j_request,
396 : "nks",
397 : j_nks));
398 :
399 50 : eh = TALER_EXCHANGE_curl_easy_get_ (bph->url);
400 100 : if ( (NULL == eh) ||
401 : (GNUNET_OK !=
402 50 : TALER_curl_easy_post (&bph->post_ctx,
403 : eh,
404 : j_request)))
405 : {
406 0 : GNUNET_break (0);
407 0 : if (NULL != eh)
408 0 : curl_easy_cleanup (eh);
409 0 : json_decref (j_request);
410 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
411 : }
412 :
413 50 : json_decref (j_request);
414 100 : bph->job = GNUNET_CURL_job_add2 (bph->curl_ctx,
415 : eh,
416 50 : bph->post_ctx.headers,
417 : &handle_blinding_prepare_finished,
418 : bph);
419 50 : if (NULL == bph->job)
420 : {
421 0 : GNUNET_break (0);
422 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
423 : }
424 50 : return TALER_EC_NONE;
425 : }
426 :
427 :
428 : void
429 125 : TALER_EXCHANGE_post_blinding_prepare_cancel (
430 : struct TALER_EXCHANGE_PostBlindingPrepareHandle *bph)
431 : {
432 125 : if (NULL == bph)
433 75 : return;
434 50 : if (NULL != bph->job)
435 : {
436 0 : GNUNET_CURL_job_cancel (bph->job);
437 0 : bph->job = NULL;
438 : }
439 50 : GNUNET_free (bph->url);
440 50 : GNUNET_free (bph->exchange_url);
441 50 : GNUNET_free (bph->nonce_keys);
442 50 : TALER_curl_easy_post_finished (&bph->post_ctx);
443 50 : GNUNET_free (bph);
444 : }
445 :
446 :
447 : /* end of lib/exchange_api_post-blinding-prepare.c */
|