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