exchange_api_post-reveal-withdraw.c (10697B)
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 "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 /** 40 * Handler for a running POST /reveal-withdraw request 41 */ 42 struct TALER_EXCHANGE_PostRevealWithdrawHandle 43 { 44 /** 45 * The commitment from the previous call to withdraw 46 */ 47 struct TALER_HashBlindedPlanchetsP planchets_h; 48 49 /** 50 * Number of coins for which to reveal tuples of seeds 51 */ 52 size_t num_coins; 53 54 /** 55 * The TALER_CNC_KAPPA-1 tuple of seeds to reveal 56 */ 57 struct TALER_RevealWithdrawMasterSeedsP seeds; 58 59 /** 60 * The exchange base URL. 61 */ 62 char *exchange_url; 63 64 /** 65 * The url for the reveal request 66 */ 67 char *request_url; 68 69 /** 70 * The curl context 71 */ 72 struct GNUNET_CURL_Context *curl_ctx; 73 74 /** 75 * CURL handle for the request job. 76 */ 77 struct GNUNET_CURL_Job *job; 78 79 /** 80 * Post Context 81 */ 82 struct TALER_CURL_PostContext post_ctx; 83 84 /** 85 * Callback to pass the result to 86 */ 87 TALER_EXCHANGE_PostRevealWithdrawCallback callback; 88 89 /** 90 * Closure for @e callback 91 */ 92 void *callback_cls; 93 }; 94 95 96 /** 97 * We got a 200 OK response for the /reveal-withdraw operation. 98 * Extract the signed blinded coins and return it to the caller. 99 * 100 * @param wrh operation handle 101 * @param j_response reply from the exchange 102 * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors 103 */ 104 static enum GNUNET_GenericReturnValue 105 reveal_withdraw_ok ( 106 struct TALER_EXCHANGE_PostRevealWithdrawHandle *wrh, 107 const json_t *j_response) 108 { 109 struct TALER_EXCHANGE_PostRevealWithdrawResponse response = { 110 .hr.reply = j_response, 111 .hr.http_status = MHD_HTTP_OK, 112 }; 113 const json_t *j_sigs; 114 struct GNUNET_JSON_Specification spec[] = { 115 GNUNET_JSON_spec_array_const ("ev_sigs", 116 &j_sigs), 117 GNUNET_JSON_spec_end () 118 }; 119 120 if (GNUNET_OK != 121 GNUNET_JSON_parse (j_response, 122 spec, 123 NULL, NULL)) 124 { 125 GNUNET_break_op (0); 126 return GNUNET_SYSERR; 127 } 128 129 if (wrh->num_coins != json_array_size (j_sigs)) 130 { 131 /* Number of coins generated does not match our expectation */ 132 GNUNET_break_op (0); 133 return GNUNET_SYSERR; 134 } 135 136 { 137 struct TALER_BlindedDenominationSignature denom_sigs[wrh->num_coins]; 138 json_t *j_sig; 139 size_t n; 140 141 /* Reconstruct the coins and unblind the signatures */ 142 json_array_foreach (j_sigs, n, j_sig) 143 { 144 struct GNUNET_JSON_Specification ispec[] = { 145 TALER_JSON_spec_blinded_denom_sig (NULL, 146 &denom_sigs[n]), 147 GNUNET_JSON_spec_end () 148 }; 149 150 if (GNUNET_OK != 151 GNUNET_JSON_parse (j_sig, 152 ispec, 153 NULL, NULL)) 154 { 155 GNUNET_break_op (0); 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 */