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