exchange_api_post-reveal-withdraw.c (10723B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2023-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-reveal-withdraw.c 19 * @brief Implementation of POST /reveal-withdraw requests 20 * @author Özgür Kesim 21 */ 22 23 #include <gnunet/gnunet_common.h> 24 #include <jansson.h> 25 #include <microhttpd.h> /* just for HTTP status codes */ 26 #include <gnunet/gnunet_util_lib.h> 27 #include <gnunet/gnunet_json_lib.h> 28 #include <gnunet/gnunet_curl_lib.h> 29 #include "taler/taler_curl_lib.h" 30 #include "taler/taler_json_lib.h" 31 #include "exchange_api_common.h" 32 #include "exchange_api_handle.h" 33 #include "taler/taler_signatures.h" 34 #include "exchange_api_curl_defaults.h" 35 36 37 /** 38 * Handler for a running POST /reveal-withdraw request 39 */ 40 struct TALER_EXCHANGE_PostRevealWithdrawHandle 41 { 42 /** 43 * The commitment from the previous call to withdraw 44 */ 45 struct TALER_HashBlindedPlanchetsP planchets_h; 46 47 /** 48 * Number of coins for which to reveal tuples of seeds 49 */ 50 size_t num_coins; 51 52 /** 53 * The TALER_CNC_KAPPA-1 tuple of seeds to reveal 54 */ 55 struct TALER_RevealWithdrawMasterSeedsP seeds; 56 57 /** 58 * The exchange base URL. 59 */ 60 char *exchange_url; 61 62 /** 63 * The url for the reveal request 64 */ 65 char *request_url; 66 67 /** 68 * The curl context 69 */ 70 struct GNUNET_CURL_Context *curl_ctx; 71 72 /** 73 * CURL handle for the request job. 74 */ 75 struct GNUNET_CURL_Job *job; 76 77 /** 78 * Post Context 79 */ 80 struct TALER_CURL_PostContext post_ctx; 81 82 /** 83 * Callback to pass the result to 84 */ 85 TALER_EXCHANGE_PostRevealWithdrawCallback callback; 86 87 /** 88 * Closure for @e callback 89 */ 90 void *callback_cls; 91 }; 92 93 94 /** 95 * We got a 200 OK response for the /reveal-withdraw operation. 96 * Extract the signed blinded coins and return it to the caller. 97 * 98 * @param wrh operation handle 99 * @param j_response reply from the exchange 100 * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors 101 */ 102 static enum GNUNET_GenericReturnValue 103 reveal_withdraw_ok ( 104 struct TALER_EXCHANGE_PostRevealWithdrawHandle *wrh, 105 const json_t *j_response) 106 { 107 struct TALER_EXCHANGE_PostRevealWithdrawResponse response = { 108 .hr.reply = j_response, 109 .hr.http_status = MHD_HTTP_OK, 110 }; 111 const json_t *j_sigs; 112 struct GNUNET_JSON_Specification spec[] = { 113 GNUNET_JSON_spec_array_const ("ev_sigs", 114 &j_sigs), 115 GNUNET_JSON_spec_end () 116 }; 117 118 if (GNUNET_OK != 119 GNUNET_JSON_parse (j_response, 120 spec, 121 NULL, NULL)) 122 { 123 GNUNET_break_op (0); 124 return GNUNET_SYSERR; 125 } 126 127 if (wrh->num_coins != json_array_size (j_sigs)) 128 { 129 /* Number of coins generated does not match our expectation */ 130 GNUNET_break_op (0); 131 return GNUNET_SYSERR; 132 } 133 134 { 135 struct TALER_BlindedDenominationSignature denom_sigs[wrh->num_coins]; 136 json_t *j_sig; 137 size_t n; 138 139 /* Reconstruct the coins and unblind the signatures */ 140 json_array_foreach (j_sigs, n, j_sig) 141 { 142 struct GNUNET_JSON_Specification ispec[] = { 143 TALER_JSON_spec_blinded_denom_sig (NULL, 144 &denom_sigs[n]), 145 GNUNET_JSON_spec_end () 146 }; 147 148 if (GNUNET_OK != 149 GNUNET_JSON_parse (j_sig, 150 ispec, 151 NULL, NULL)) 152 { 153 GNUNET_break_op (0); 154 for (size_t k = 0; k < n; k++) 155 TALER_blinded_denom_sig_free (&denom_sigs[k]); 156 return GNUNET_SYSERR; 157 } 158 } 159 160 response.details.ok.num_sigs = wrh->num_coins; 161 response.details.ok.blinded_denom_sigs = denom_sigs; 162 wrh->callback (wrh->callback_cls, 163 &response); 164 /* Make sure the callback isn't called again */ 165 wrh->callback = NULL; 166 /* Free resources */ 167 for (size_t i = 0; i < wrh->num_coins; i++) 168 TALER_blinded_denom_sig_free (&denom_sigs[i]); 169 } 170 171 return GNUNET_OK; 172 } 173 174 175 /** 176 * Function called when we're done processing the 177 * HTTP /reveal-withdraw request. 178 * 179 * @param cls the `struct TALER_EXCHANGE_PostRevealWithdrawHandle` 180 * @param response_code The HTTP response code 181 * @param response response data 182 */ 183 static void 184 handle_reveal_withdraw_finished ( 185 void *cls, 186 long response_code, 187 const void *response) 188 { 189 struct TALER_EXCHANGE_PostRevealWithdrawHandle *wrh = cls; 190 const json_t *j_response = response; 191 struct TALER_EXCHANGE_PostRevealWithdrawResponse awr = { 192 .hr.reply = j_response, 193 .hr.http_status = (unsigned int) response_code 194 }; 195 196 wrh->job = NULL; 197 switch (response_code) 198 { 199 case 0: 200 awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 201 break; 202 case MHD_HTTP_OK: 203 { 204 enum GNUNET_GenericReturnValue ret; 205 206 ret = reveal_withdraw_ok (wrh, 207 j_response); 208 if (GNUNET_OK != ret) 209 { 210 GNUNET_break_op (0); 211 awr.hr.http_status = 0; 212 awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 213 break; 214 } 215 GNUNET_assert (NULL == wrh->callback); 216 TALER_EXCHANGE_post_reveal_withdraw_cancel (wrh); 217 return; 218 } 219 case MHD_HTTP_BAD_REQUEST: 220 /* This should never happen, either us or the exchange is buggy 221 (or API version conflict); just pass JSON reply to the application */ 222 awr.hr.ec = TALER_JSON_get_error_code (j_response); 223 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 224 break; 225 case MHD_HTTP_NOT_FOUND: 226 /* Nothing really to verify, the exchange basically just says 227 that it doesn't know this age-withdraw commitment. */ 228 awr.hr.ec = TALER_JSON_get_error_code (j_response); 229 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 230 break; 231 case MHD_HTTP_CONFLICT: 232 /* An age commitment for one of the coins did not fulfill 233 * the required maximum age requirement of the corresponding 234 * reserve. 235 * Error code: TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE 236 * or TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH. 237 */ 238 awr.hr.ec = TALER_JSON_get_error_code (j_response); 239 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 240 break; 241 case MHD_HTTP_INTERNAL_SERVER_ERROR: 242 /* Server had an internal issue; we should retry, but this API 243 leaves this to the application */ 244 awr.hr.ec = TALER_JSON_get_error_code (j_response); 245 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 246 break; 247 default: 248 /* unexpected response code */ 249 GNUNET_break_op (0); 250 awr.hr.ec = TALER_JSON_get_error_code (j_response); 251 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 252 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 253 "Unexpected response code %u/%d for exchange age-withdraw\n", 254 (unsigned int) response_code, 255 (int) awr.hr.ec); 256 break; 257 } 258 wrh->callback (wrh->callback_cls, 259 &awr); 260 TALER_EXCHANGE_post_reveal_withdraw_cancel (wrh); 261 } 262 263 264 /** 265 * Call /reveal-withdraw 266 * 267 * @param wrh The handler 268 */ 269 static void 270 perform_protocol ( 271 struct TALER_EXCHANGE_PostRevealWithdrawHandle *wrh) 272 { 273 CURL *curlh; 274 json_t *j_array_of_secrets; 275 276 j_array_of_secrets = json_array (); 277 GNUNET_assert (NULL != j_array_of_secrets); 278 279 for (uint8_t k = 0; k < TALER_CNC_KAPPA - 1; k++) 280 { 281 json_t *j_sec = GNUNET_JSON_from_data_auto (&wrh->seeds.tuple[k]); 282 GNUNET_assert (NULL != j_sec); 283 GNUNET_assert (0 == json_array_append_new (j_array_of_secrets, 284 j_sec)); 285 } 286 { 287 json_t *j_request_body; 288 289 j_request_body = GNUNET_JSON_PACK ( 290 GNUNET_JSON_pack_data_auto ("planchets_h", 291 &wrh->planchets_h), 292 GNUNET_JSON_pack_array_steal ("disclosed_batch_seeds", 293 j_array_of_secrets)); 294 GNUNET_assert (NULL != j_request_body); 295 296 curlh = TALER_EXCHANGE_curl_easy_get_ (wrh->request_url); 297 GNUNET_assert (NULL != curlh); 298 GNUNET_assert (GNUNET_OK == 299 TALER_curl_easy_post (&wrh->post_ctx, 300 curlh, 301 j_request_body)); 302 303 json_decref (j_request_body); 304 } 305 306 wrh->job = GNUNET_CURL_job_add2 ( 307 wrh->curl_ctx, 308 curlh, 309 wrh->post_ctx.headers, 310 &handle_reveal_withdraw_finished, 311 wrh); 312 if (NULL == wrh->job) 313 { 314 GNUNET_break (0); 315 if (NULL != curlh) 316 curl_easy_cleanup (curlh); 317 /* caller must call _cancel to free wrh */ 318 } 319 } 320 321 322 struct TALER_EXCHANGE_PostRevealWithdrawHandle * 323 TALER_EXCHANGE_post_reveal_withdraw_create ( 324 struct GNUNET_CURL_Context *curl_ctx, 325 const char *exchange_url, 326 size_t num_coins, 327 const struct TALER_HashBlindedPlanchetsP *h_planchets, 328 const struct TALER_RevealWithdrawMasterSeedsP *seeds) 329 { 330 struct TALER_EXCHANGE_PostRevealWithdrawHandle *wrh; 331 332 wrh = GNUNET_new (struct TALER_EXCHANGE_PostRevealWithdrawHandle); 333 wrh->curl_ctx = curl_ctx; 334 wrh->exchange_url = GNUNET_strdup (exchange_url); 335 wrh->num_coins = num_coins; 336 wrh->planchets_h = *h_planchets; 337 wrh->seeds = *seeds; 338 return wrh; 339 } 340 341 342 enum TALER_ErrorCode 343 TALER_EXCHANGE_post_reveal_withdraw_start ( 344 struct TALER_EXCHANGE_PostRevealWithdrawHandle *prwh, 345 TALER_EXCHANGE_PostRevealWithdrawCallback cb, 346 TALER_EXCHANGE_POST_REVEAL_WITHDRAW_RESULT_CLOSURE *cb_cls) 347 { 348 prwh->callback = cb; 349 prwh->callback_cls = cb_cls; 350 prwh->request_url = TALER_url_join (prwh->exchange_url, 351 "reveal-withdraw", 352 NULL); 353 if (NULL == prwh->request_url) 354 { 355 GNUNET_break (0); 356 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 357 } 358 perform_protocol (prwh); 359 if (NULL == prwh->job) 360 { 361 GNUNET_break (0); 362 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 363 } 364 return TALER_EC_NONE; 365 } 366 367 368 void 369 TALER_EXCHANGE_post_reveal_withdraw_cancel ( 370 struct TALER_EXCHANGE_PostRevealWithdrawHandle *prwh) 371 { 372 if (NULL != prwh->job) 373 { 374 GNUNET_CURL_job_cancel (prwh->job); 375 prwh->job = NULL; 376 } 377 TALER_curl_easy_post_finished (&prwh->post_ctx); 378 GNUNET_free (prwh->request_url); 379 GNUNET_free (prwh->exchange_url); 380 GNUNET_free (prwh); 381 } 382 383 384 /* exchange_api_post-reveal-withdraw.c */