exchange_api_post-reveal-withdraw.c (10627B)
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 return GNUNET_SYSERR; 155 } 156 } 157 158 response.details.ok.num_sigs = wrh->num_coins; 159 response.details.ok.blinded_denom_sigs = denom_sigs; 160 wrh->callback (wrh->callback_cls, 161 &response); 162 /* Make sure the callback isn't called again */ 163 wrh->callback = NULL; 164 /* Free resources */ 165 for (size_t i = 0; i < wrh->num_coins; i++) 166 TALER_blinded_denom_sig_free (&denom_sigs[i]); 167 } 168 169 return GNUNET_OK; 170 } 171 172 173 /** 174 * Function called when we're done processing the 175 * HTTP /reveal-withdraw request. 176 * 177 * @param cls the `struct TALER_EXCHANGE_PostRevealWithdrawHandle` 178 * @param response_code The HTTP response code 179 * @param response response data 180 */ 181 static void 182 handle_reveal_withdraw_finished ( 183 void *cls, 184 long response_code, 185 const void *response) 186 { 187 struct TALER_EXCHANGE_PostRevealWithdrawHandle *wrh = cls; 188 const json_t *j_response = response; 189 struct TALER_EXCHANGE_PostRevealWithdrawResponse awr = { 190 .hr.reply = j_response, 191 .hr.http_status = (unsigned int) response_code 192 }; 193 194 wrh->job = NULL; 195 switch (response_code) 196 { 197 case 0: 198 awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 199 break; 200 case MHD_HTTP_OK: 201 { 202 enum GNUNET_GenericReturnValue ret; 203 204 ret = reveal_withdraw_ok (wrh, 205 j_response); 206 if (GNUNET_OK != ret) 207 { 208 GNUNET_break_op (0); 209 awr.hr.http_status = 0; 210 awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 211 break; 212 } 213 GNUNET_assert (NULL == wrh->callback); 214 TALER_EXCHANGE_post_reveal_withdraw_cancel (wrh); 215 return; 216 } 217 case MHD_HTTP_BAD_REQUEST: 218 /* This should never happen, either us or the exchange is buggy 219 (or API version conflict); just pass JSON reply to the application */ 220 awr.hr.ec = TALER_JSON_get_error_code (j_response); 221 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 222 break; 223 case MHD_HTTP_NOT_FOUND: 224 /* Nothing really to verify, the exchange basically just says 225 that it doesn't know this age-withdraw commitment. */ 226 awr.hr.ec = TALER_JSON_get_error_code (j_response); 227 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 228 break; 229 case MHD_HTTP_CONFLICT: 230 /* An age commitment for one of the coins did not fulfill 231 * the required maximum age requirement of the corresponding 232 * reserve. 233 * Error code: TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE 234 * or TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH. 235 */ 236 awr.hr.ec = TALER_JSON_get_error_code (j_response); 237 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 238 break; 239 case MHD_HTTP_INTERNAL_SERVER_ERROR: 240 /* Server had an internal issue; we should retry, but this API 241 leaves this to the application */ 242 awr.hr.ec = TALER_JSON_get_error_code (j_response); 243 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 244 break; 245 default: 246 /* unexpected response code */ 247 GNUNET_break_op (0); 248 awr.hr.ec = TALER_JSON_get_error_code (j_response); 249 awr.hr.hint = TALER_JSON_get_error_hint (j_response); 250 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 251 "Unexpected response code %u/%d for exchange age-withdraw\n", 252 (unsigned int) response_code, 253 (int) awr.hr.ec); 254 break; 255 } 256 wrh->callback (wrh->callback_cls, 257 &awr); 258 TALER_EXCHANGE_post_reveal_withdraw_cancel (wrh); 259 } 260 261 262 /** 263 * Call /reveal-withdraw 264 * 265 * @param wrh The handler 266 */ 267 static void 268 perform_protocol ( 269 struct TALER_EXCHANGE_PostRevealWithdrawHandle *wrh) 270 { 271 CURL *curlh; 272 json_t *j_array_of_secrets; 273 274 j_array_of_secrets = json_array (); 275 GNUNET_assert (NULL != j_array_of_secrets); 276 277 for (uint8_t k = 0; k < TALER_CNC_KAPPA - 1; k++) 278 { 279 json_t *j_sec = GNUNET_JSON_from_data_auto (&wrh->seeds.tuple[k]); 280 GNUNET_assert (NULL != j_sec); 281 GNUNET_assert (0 == json_array_append_new (j_array_of_secrets, 282 j_sec)); 283 } 284 { 285 json_t *j_request_body; 286 287 j_request_body = GNUNET_JSON_PACK ( 288 GNUNET_JSON_pack_data_auto ("planchets_h", 289 &wrh->planchets_h), 290 GNUNET_JSON_pack_array_steal ("disclosed_batch_seeds", 291 j_array_of_secrets)); 292 GNUNET_assert (NULL != j_request_body); 293 294 curlh = TALER_EXCHANGE_curl_easy_get_ (wrh->request_url); 295 GNUNET_assert (NULL != curlh); 296 GNUNET_assert (GNUNET_OK == 297 TALER_curl_easy_post (&wrh->post_ctx, 298 curlh, 299 j_request_body)); 300 301 json_decref (j_request_body); 302 } 303 304 wrh->job = GNUNET_CURL_job_add2 ( 305 wrh->curl_ctx, 306 curlh, 307 wrh->post_ctx.headers, 308 &handle_reveal_withdraw_finished, 309 wrh); 310 if (NULL == wrh->job) 311 { 312 GNUNET_break (0); 313 if (NULL != curlh) 314 curl_easy_cleanup (curlh); 315 /* caller must call _cancel to free wrh */ 316 } 317 } 318 319 320 struct TALER_EXCHANGE_PostRevealWithdrawHandle * 321 TALER_EXCHANGE_post_reveal_withdraw_create ( 322 struct GNUNET_CURL_Context *curl_ctx, 323 const char *exchange_url, 324 size_t num_coins, 325 const struct TALER_HashBlindedPlanchetsP *h_planchets, 326 const struct TALER_RevealWithdrawMasterSeedsP *seeds) 327 { 328 struct TALER_EXCHANGE_PostRevealWithdrawHandle *wrh; 329 330 wrh = GNUNET_new (struct TALER_EXCHANGE_PostRevealWithdrawHandle); 331 wrh->curl_ctx = curl_ctx; 332 wrh->exchange_url = GNUNET_strdup (exchange_url); 333 wrh->num_coins = num_coins; 334 wrh->planchets_h = *h_planchets; 335 wrh->seeds = *seeds; 336 return wrh; 337 } 338 339 340 enum TALER_ErrorCode 341 TALER_EXCHANGE_post_reveal_withdraw_start ( 342 struct TALER_EXCHANGE_PostRevealWithdrawHandle *prwh, 343 TALER_EXCHANGE_PostRevealWithdrawCallback cb, 344 TALER_EXCHANGE_POST_REVEAL_WITHDRAW_RESULT_CLOSURE *cb_cls) 345 { 346 prwh->callback = cb; 347 prwh->callback_cls = cb_cls; 348 prwh->request_url = TALER_url_join (prwh->exchange_url, 349 "reveal-withdraw", 350 NULL); 351 if (NULL == prwh->request_url) 352 { 353 GNUNET_break (0); 354 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 355 } 356 perform_protocol (prwh); 357 if (NULL == prwh->job) 358 { 359 GNUNET_break (0); 360 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 361 } 362 return TALER_EC_NONE; 363 } 364 365 366 void 367 TALER_EXCHANGE_post_reveal_withdraw_cancel ( 368 struct TALER_EXCHANGE_PostRevealWithdrawHandle *prwh) 369 { 370 if (NULL != prwh->job) 371 { 372 GNUNET_CURL_job_cancel (prwh->job); 373 prwh->job = NULL; 374 } 375 TALER_curl_easy_post_finished (&prwh->post_ctx); 376 GNUNET_free (prwh->request_url); 377 GNUNET_free (prwh->exchange_url); 378 GNUNET_free (prwh); 379 } 380 381 382 /* exchange_api_post-reveal-withdraw.c */