LCOV - code coverage report
Current view: top level - lib - exchange_api_post-withdraw_blinded.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 66.7 % 294 196
Test Date: 2026-09-09 15:11:34 Functions: 100.0 % 7 7

            Line data    Source code
       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-withdraw.c
      19              :  * @brief Implementation of /withdraw requests
      20              :  * @author Özgür Kesim
      21              :  */
      22              : #include <gnunet/gnunet_common.h>
      23              : #include <jansson.h>
      24              : #include <microhttpd.h> /* just for HTTP status codes */
      25              : #include <gnunet/gnunet_util_lib.h>
      26              : #include <gnunet/gnunet_json_lib.h>
      27              : #include <gnunet/gnunet_curl_lib.h>
      28              : #include <sys/wait.h>
      29              : #include "taler/taler_curl_lib.h"
      30              : #include "taler/taler_error_codes.h"
      31              : #include "taler/taler_json_lib.h"
      32              : #include "exchange_api_common.h"
      33              : #include "exchange_api_handle.h"
      34              : #include "taler/taler_signatures.h"
      35              : #include "exchange_api_curl_defaults.h"
      36              : #include "taler/taler_util.h"
      37              : 
      38              : 
      39              : /**
      40              :  * A /withdraw request-handle for calls with pre-blinded planchets.
      41              :  * Returned by TALER_EXCHANGE_post_withdraw_blinded_create.
      42              :  */
      43              : struct TALER_EXCHANGE_PostWithdrawBlindedHandle
      44              : {
      45              : 
      46              :   /**
      47              :    * Reserve private key.
      48              :    */
      49              :   const struct TALER_ReservePrivateKeyP *reserve_priv;
      50              : 
      51              :   /**
      52              :    * Reserve public key, calculated
      53              :    */
      54              :   struct TALER_ReservePublicKeyP reserve_pub;
      55              : 
      56              :   /**
      57              :    * Signature of the reserve for the request, calculated after all
      58              :    * parameters for the coins are collected.
      59              :    */
      60              :   struct TALER_ReserveSignatureP reserve_sig;
      61              : 
      62              :   /*
      63              :    * The denomination keys of the exchange
      64              :    */
      65              :   struct TALER_EXCHANGE_Keys *keys;
      66              : 
      67              :   /**
      68              :    * The hash of all the planchets
      69              :    */
      70              :   struct TALER_HashBlindedPlanchetsP planchets_h;
      71              : 
      72              :   /**
      73              :    * Seed used for the derival of blinding factors for denominations
      74              :    * with Clause-Schnorr cipher.
      75              :    */
      76              :   const struct TALER_BlindingMasterSeedP *blinding_seed;
      77              : 
      78              :   /**
      79              :    * Total amount requested (without fee).
      80              :    */
      81              :   struct TALER_Amount amount;
      82              : 
      83              :   /**
      84              :    * Total withdraw fee
      85              :    */
      86              :   struct TALER_Amount fee;
      87              : 
      88              :   /**
      89              :    * Is this call for age-restricted coins, with age proof?
      90              :    */
      91              :   bool with_age_proof;
      92              : 
      93              :   /**
      94              :    * If @e with_age_proof is true or @max_age is > 0,
      95              :    * the age mask to use, extracted from the denominations.
      96              :    * MUST be the same for all denominations.
      97              :    */
      98              :   struct TALER_AgeMask age_mask;
      99              : 
     100              :   /**
     101              :    * The maximum age to commit to.  If @e with_age_proof
     102              :    * is true, the client will need to proof the correct setting
     103              :    * of age-restriction on the coins via an additional call
     104              :    * to /reveal-withdraw.
     105              :    */
     106              :   uint8_t max_age;
     107              : 
     108              :   /**
     109              :    * If @e with_age_proof is true, the hash of all the selected planchets
     110              :    */
     111              :   struct TALER_HashBlindedPlanchetsP selected_h;
     112              : 
     113              :   /**
     114              :    * Length of the either the @e blinded.input or
     115              :    * the @e blinded.with_age_proof_input array,
     116              :    * depending on @e with_age_proof.
     117              :    */
     118              :   size_t num_input;
     119              : 
     120              :   union
     121              :   {
     122              :     /**
     123              :      * The blinded planchet input candidates for age-restricted coins
     124              :      * for the call to /withdraw
     125              :      */
     126              :     const struct
     127              :     TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput *with_age_proof_input;
     128              : 
     129              :     /**
     130              :      * The blinded planchet input for the call to /withdraw,
     131              :      * for age-unrestricted coins.
     132              :      */
     133              :     const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *input;
     134              : 
     135              :   } blinded;
     136              : 
     137              :   /**
     138              :    * The url for this request.
     139              :    */
     140              :   char *request_url;
     141              : 
     142              :   /**
     143              :    * Context for curl.
     144              :    */
     145              :   struct GNUNET_CURL_Context *curl_ctx;
     146              : 
     147              :   /**
     148              :    * CURL handle for the request job.
     149              :    */
     150              :   struct GNUNET_CURL_Job *job;
     151              : 
     152              :   /**
     153              :    * Post Context
     154              :    */
     155              :   struct TALER_CURL_PostContext post_ctx;
     156              : 
     157              :   /**
     158              :    * Function to call with withdraw response results.
     159              :    */
     160              :   TALER_EXCHANGE_PostWithdrawBlindedCallback callback;
     161              : 
     162              :   /**
     163              :    * Closure for @e callback
     164              :    */
     165              :   void *callback_cls;
     166              : };
     167              : 
     168              : 
     169              : /**
     170              :  * We got a 200 OK response for the /withdraw operation.
     171              :  * Extract the signatures and return them to the caller.
     172              :  *
     173              :  * @param wbh operation handle
     174              :  * @param j_response reply from the exchange
     175              :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
     176              :  */
     177              : static enum GNUNET_GenericReturnValue
     178           61 : withdraw_blinded_ok (
     179              :   struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh,
     180              :   const json_t *j_response)
     181              : {
     182           61 :   struct TALER_EXCHANGE_PostWithdrawBlindedResponse response = {
     183              :     .hr.reply = j_response,
     184              :     .hr.http_status = MHD_HTTP_OK,
     185              :   };
     186              :   const json_t *j_sigs;
     187              :   struct GNUNET_JSON_Specification spec[] = {
     188           61 :     GNUNET_JSON_spec_array_const ("ev_sigs",
     189              :                                   &j_sigs),
     190           61 :     GNUNET_JSON_spec_end ()
     191              :   };
     192              : 
     193           61 :   if (GNUNET_OK !=
     194           61 :       GNUNET_JSON_parse (j_response,
     195              :                          spec,
     196              :                          NULL, NULL))
     197              :   {
     198            0 :     GNUNET_break_op (0);
     199            0 :     return GNUNET_SYSERR;
     200              :   }
     201              : 
     202           61 :   if (wbh->num_input != json_array_size (j_sigs))
     203              :   {
     204              :     /* Number of coins generated does not match our expectation */
     205            0 :     GNUNET_break_op (0);
     206            0 :     return GNUNET_SYSERR;
     207              :   }
     208              : 
     209           61 :   {
     210           61 :     struct TALER_BlindedDenominationSignature denoms_sig[wbh->num_input];
     211              : 
     212           61 :     memset (denoms_sig,
     213              :             0,
     214              :             sizeof(denoms_sig));
     215              : 
     216              :     /* Reconstruct the coins and unblind the signatures */
     217              :     {
     218              :       json_t *j_sig;
     219              :       size_t i;
     220              : 
     221          124 :       json_array_foreach (j_sigs, i, j_sig)
     222              :       {
     223              :         struct GNUNET_JSON_Specification ispec[] = {
     224           63 :           TALER_JSON_spec_blinded_denom_sig (NULL,
     225              :                                              &denoms_sig[i]),
     226           63 :           GNUNET_JSON_spec_end ()
     227              :         };
     228              : 
     229           63 :         if (GNUNET_OK !=
     230           63 :             GNUNET_JSON_parse (j_sig,
     231              :                                ispec,
     232              :                                NULL, NULL))
     233              :         {
     234            0 :           GNUNET_break_op (0);
     235            0 :           for (size_t k = 0; k < i; k++)
     236            0 :             TALER_blinded_denom_sig_free (&denoms_sig[k]);
     237            0 :           return GNUNET_SYSERR;
     238              :         }
     239              :       }
     240              :     }
     241              : 
     242           61 :     response.details.ok.num_sigs = wbh->num_input;
     243           61 :     response.details.ok.blinded_denom_sigs = denoms_sig;
     244           61 :     response.details.ok.planchets_h = wbh->planchets_h;
     245           61 :     wbh->callback (
     246              :       wbh->callback_cls,
     247              :       &response);
     248              :     /* Make sure the callback isn't called again */
     249           61 :     wbh->callback = NULL;
     250              :     /* Free resources */
     251          124 :     for (size_t i = 0; i < wbh->num_input; i++)
     252           63 :       TALER_blinded_denom_sig_free (&denoms_sig[i]);
     253              :   }
     254              : 
     255           61 :   return GNUNET_OK;
     256              : }
     257              : 
     258              : 
     259              : /**
     260              :  * We got a 201 CREATED response for the /withdraw operation.
     261              :  * Extract the noreveal_index and return it to the caller.
     262              :  *
     263              :  * @param wbh operation handle
     264              :  * @param j_response reply from the exchange
     265              :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
     266              :  */
     267              : static enum GNUNET_GenericReturnValue
     268            3 : withdraw_blinded_created (
     269              :   struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh,
     270              :   const json_t *j_response)
     271              : {
     272            3 :   struct TALER_EXCHANGE_PostWithdrawBlindedResponse response = {
     273              :     .hr.reply = j_response,
     274              :     .hr.http_status = MHD_HTTP_CREATED,
     275              :     .details.created.planchets_h = wbh->planchets_h,
     276            3 :     .details.created.num_coins = wbh->num_input,
     277              :   };
     278              :   struct TALER_ExchangeSignatureP exchange_sig;
     279              :   struct GNUNET_JSON_Specification spec[] = {
     280            3 :     GNUNET_JSON_spec_uint8 ("noreveal_index",
     281              :                             &response.details.created.noreveal_index),
     282            3 :     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
     283              :                                  &exchange_sig),
     284            3 :     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
     285              :                                  &response.details.created.exchange_pub),
     286            3 :     GNUNET_JSON_spec_end ()
     287              :   };
     288              : 
     289            3 :   if (GNUNET_OK!=
     290            3 :       GNUNET_JSON_parse (j_response,
     291              :                          spec,
     292              :                          NULL, NULL))
     293              :   {
     294            0 :     GNUNET_break_op (0);
     295            0 :     return GNUNET_SYSERR;
     296              :   }
     297            3 :   if (response.details.created.noreveal_index >= TALER_CNC_KAPPA)
     298              :   {
     299            0 :     GNUNET_break_op (0);
     300            0 :     return GNUNET_SYSERR;
     301              :   }
     302            3 :   if (GNUNET_OK !=
     303            3 :       TALER_EXCHANGE_test_signing_key (
     304            3 :         wbh->keys,
     305              :         &response.details.created.exchange_pub))
     306              :   {
     307            0 :     GNUNET_break_op (0);
     308            0 :     return GNUNET_SYSERR;
     309              :   }
     310            3 :   if (GNUNET_OK !=
     311            3 :       TALER_exchange_online_withdraw_age_confirmation_verify (
     312            3 :         &wbh->planchets_h,
     313            3 :         response.details.created.noreveal_index,
     314              :         &response.details.created.exchange_pub,
     315              :         &exchange_sig))
     316              :   {
     317            0 :     GNUNET_break_op (0);
     318            0 :     return GNUNET_SYSERR;
     319              :   }
     320            3 :   wbh->callback (wbh->callback_cls,
     321              :                  &response);
     322              :   /* make sure the callback isn't called again */
     323            3 :   wbh->callback = NULL;
     324              : 
     325            3 :   return GNUNET_OK;
     326              : }
     327              : 
     328              : 
     329              : /**
     330              :  * Function called when we're done processing the
     331              :  * HTTP /withdraw request.
     332              :  *
     333              :  * @param cls the `struct TALER_EXCHANGE_PostWithdrawBlindedHandle`
     334              :  * @param response_code The HTTP response code
     335              :  * @param response response data
     336              :  */
     337              : static void
     338           75 : handle_withdraw_blinded_finished (
     339              :   void *cls,
     340              :   long response_code,
     341              :   const void *response)
     342              : {
     343           75 :   struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh = cls;
     344           75 :   const json_t *j_response = response;
     345           75 :   struct TALER_EXCHANGE_PostWithdrawBlindedResponse wbr = {
     346              :     .hr.reply = j_response,
     347           75 :     .hr.http_status = (unsigned int) response_code
     348              :   };
     349              : 
     350           75 :   wbh->job = NULL;
     351           75 :   switch (response_code)
     352              :   {
     353            0 :   case 0:
     354            0 :     wbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     355            0 :     break;
     356           61 :   case MHD_HTTP_OK:
     357              :     {
     358           61 :       if (GNUNET_OK !=
     359           61 :           withdraw_blinded_ok (
     360              :             wbh,
     361              :             j_response))
     362              :       {
     363            0 :         GNUNET_break_op (0);
     364            0 :         wbr.hr.http_status = 0;
     365            0 :         wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     366            0 :         break;
     367              :       }
     368           61 :       GNUNET_assert (NULL == wbh->callback);
     369           61 :       TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
     370           64 :       return;
     371              :     }
     372            3 :   case MHD_HTTP_CREATED:
     373            3 :     if (GNUNET_OK !=
     374            3 :         withdraw_blinded_created (
     375              :           wbh,
     376              :           j_response))
     377              :     {
     378            0 :       GNUNET_break_op (0);
     379            0 :       wbr.hr.http_status = 0;
     380            0 :       wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     381            0 :       break;
     382              :     }
     383            3 :     GNUNET_assert (NULL == wbh->callback);
     384            3 :     TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
     385            3 :     return;
     386            0 :   case MHD_HTTP_BAD_REQUEST:
     387              :     /* This should never happen, either us or the exchange is buggy
     388              :        (or API version conflict); just pass JSON reply to the application */
     389            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     390            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     391            0 :     break;
     392            0 :   case MHD_HTTP_FORBIDDEN:
     393            0 :     GNUNET_break_op (0);
     394              :     /* Nothing really to verify, exchange says one of the signatures is
     395              :        invalid; as we checked them, this should never happen, we
     396              :        should pass the JSON reply to the application */
     397            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     398            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     399            0 :     break;
     400            0 :   case MHD_HTTP_NOT_FOUND:
     401              :     /* Nothing really to verify, the exchange basically just says
     402              :        that it doesn't know this reserve.  Can happen if we
     403              :        query before the wire transfer went through.
     404              :        We should simply pass the JSON reply to the application. */
     405            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     406            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     407            0 :     break;
     408            7 :   case MHD_HTTP_CONFLICT:
     409            7 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     410            7 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     411            7 :     if (TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS ==
     412            7 :         wbr.hr.ec)
     413              :     {
     414              :       struct GNUNET_JSON_Specification spec[] = {
     415            0 :         TALER_JSON_spec_amount_any (
     416              :           "balance",
     417              :           &wbr.details.conflict.details.generic_insufficient_funds.balance),
     418            0 :         TALER_JSON_spec_amount_any (
     419              :           "requested_amount",
     420              :           &wbr.details.conflict.details.generic_insufficient_funds.
     421              :           requested_amount),
     422            0 :         GNUNET_JSON_spec_end ()
     423              :       };
     424              : 
     425            0 :       if (GNUNET_OK !=
     426            0 :           GNUNET_JSON_parse (j_response,
     427              :                              spec,
     428              :                              NULL, NULL))
     429              :       {
     430            0 :         GNUNET_break_op (0);
     431            0 :         wbr.hr.http_status = 0;
     432            0 :         wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     433            0 :         break;
     434              :       }
     435              :     }
     436            7 :     break;
     437            0 :   case MHD_HTTP_GONE:
     438              :     /* could happen if denomination was revoked */
     439              :     /* Note: one might want to check /keys for revocation
     440              :        signature here, alas tricky in case our /keys
     441              :        is outdated => left to clients */
     442            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     443            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     444            0 :     break;
     445            0 :   case MHD_HTTP_PRECONDITION_FAILED:
     446              :     /* could happen if we were too early and the denomination
     447              :        is not yet available */
     448              :     /* Note: one might want to check the "Date" header to
     449              :        see if our clock is very far off */
     450            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     451            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     452            0 :     break;
     453            4 :   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
     454            4 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     455            4 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     456            4 :     if (GNUNET_OK !=
     457            4 :         TALER_EXCHANGE_parse_451 (&wbr.details.unavailable_for_legal_reasons,
     458              :                                   j_response))
     459              :     {
     460            0 :       GNUNET_break_op (0);
     461            0 :       wbr.hr.http_status = 0;
     462            0 :       wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     463            0 :       break;
     464              :     }
     465            4 :     break;
     466            0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     467              :     /* Server had an internal issue; we should retry, but this API
     468              :        leaves this to the application */
     469            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     470            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     471            0 :     break;
     472            0 :   case MHD_HTTP_NOT_IMPLEMENTED:
     473              :     /* Server does not implement a feature (usually the cipher) */
     474            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     475            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     476            0 :     break;
     477            0 :   case MHD_HTTP_BAD_GATEWAY:
     478              :     /* Server could not talk to another component, usually this
     479              :        indicates a problem with the secmod helper */
     480            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     481            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     482            0 :     break;
     483            0 :   case MHD_HTTP_SERVICE_UNAVAILABLE:
     484              :     /* Server had an internal issue; we should retry, but this API
     485              :        leaves this to the application */
     486            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     487            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     488            0 :     break;
     489            0 :   default:
     490              :     /* unexpected response code */
     491            0 :     GNUNET_break_op (0);
     492            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     493            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     494            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     495              :                 "Unexpected response code %u/%d for exchange withdraw\n",
     496              :                 (unsigned int) response_code,
     497              :                 (int) wbr.hr.ec);
     498            0 :     break;
     499              :   }
     500           11 :   wbh->callback (wbh->callback_cls,
     501              :                  &wbr);
     502           11 :   TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
     503              : }
     504              : 
     505              : 
     506              : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *
     507           75 : TALER_EXCHANGE_post_withdraw_blinded_create (
     508              :   struct GNUNET_CURL_Context *curl_ctx,
     509              :   struct TALER_EXCHANGE_Keys *keys,
     510              :   const char *exchange_url,
     511              :   const struct TALER_ReservePrivateKeyP *reserve_priv,
     512              :   const struct TALER_BlindingMasterSeedP *blinding_seed,
     513              :   size_t num_input,
     514              :   const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *blinded_input)
     515              : {
     516              :   struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh =
     517           75 :     GNUNET_new (struct TALER_EXCHANGE_PostWithdrawBlindedHandle);
     518              : 
     519           75 :   wbh->keys = TALER_EXCHANGE_keys_incref (keys);
     520           75 :   wbh->curl_ctx = curl_ctx;
     521           75 :   wbh->reserve_priv = reserve_priv;
     522           75 :   wbh->request_url = TALER_url_join (exchange_url,
     523              :                                      "withdraw",
     524              :                                      NULL);
     525           75 :   GNUNET_CRYPTO_eddsa_key_get_public (
     526           75 :     &wbh->reserve_priv->eddsa_priv,
     527              :     &wbh->reserve_pub.eddsa_pub);
     528           75 :   wbh->num_input = num_input;
     529           75 :   wbh->blinded.input = blinded_input;
     530           75 :   wbh->blinding_seed = blinding_seed;
     531              : 
     532           75 :   return wbh;
     533              : }
     534              : 
     535              : 
     536              : enum GNUNET_GenericReturnValue
     537            5 : TALER_EXCHANGE_post_withdraw_blinded_set_options_ (
     538              :   struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh,
     539              :   unsigned int num_options,
     540              :   const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue options[])
     541              : {
     542           10 :   for (unsigned int i = 0; i < num_options; i++)
     543              :   {
     544           10 :     const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue *opt =
     545           10 :       &options[i];
     546           10 :     switch (opt->option)
     547              :     {
     548            5 :     case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_END:
     549            5 :       return GNUNET_OK;
     550            5 :     case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_WITH_AGE_PROOF:
     551            5 :       pwbh->with_age_proof = true;
     552            5 :       pwbh->max_age = opt->details.with_age_proof.max_age;
     553            5 :       pwbh->blinded.with_age_proof_input = opt->details.with_age_proof.input;
     554            5 :       break;
     555              :     }
     556              :   }
     557            0 :   return GNUNET_OK;
     558              : }
     559              : 
     560              : 
     561              : enum TALER_ErrorCode
     562           75 : TALER_EXCHANGE_post_withdraw_blinded_start (
     563              :   struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh,
     564              :   TALER_EXCHANGE_PostWithdrawBlindedCallback cb,
     565              :   TALER_EXCHANGE_POST_WITHDRAW_BLINDED_RESULT_CLOSURE *cb_cls)
     566              : {
     567           75 :   json_t *j_denoms = NULL;
     568           75 :   json_t *j_planchets = NULL;
     569           75 :   json_t *j_request_body = NULL;
     570           75 :   CURL *curlh = NULL;
     571           75 :   struct GNUNET_HashContext *coins_hctx = NULL;
     572              :   struct TALER_BlindedCoinHashP bch;
     573              : 
     574           75 :   pwbh->callback = cb;
     575           75 :   pwbh->callback_cls = cb_cls;
     576              : #define FAIL_IF(cond) \
     577              :         do { \
     578              :           if ((cond)) \
     579              :           { \
     580              :             GNUNET_break (! (cond)); \
     581              :             goto ERROR; \
     582              :           } \
     583              :         } while (0)
     584              : 
     585           75 :   GNUNET_assert (0 < pwbh->num_input);
     586              : 
     587           75 :   FAIL_IF (GNUNET_OK !=
     588              :            TALER_amount_set_zero (pwbh->keys->currency,
     589              :                                   &pwbh->amount));
     590           75 :   FAIL_IF (GNUNET_OK !=
     591              :            TALER_amount_set_zero (pwbh->keys->currency,
     592              :                                   &pwbh->fee));
     593              : 
     594              :   /* Accumulate total value with fees */
     595          156 :   for (size_t i = 0; i < pwbh->num_input; i++)
     596              :   {
     597           81 :     const struct TALER_EXCHANGE_DenomPublicKey *dpub =
     598           81 :       pwbh->with_age_proof ?
     599           81 :       pwbh->blinded.with_age_proof_input[i].denom_pub :
     600           72 :       pwbh->blinded.input[i].denom_pub;
     601              : 
     602           81 :     FAIL_IF (0 >
     603              :              TALER_amount_add (&pwbh->amount,
     604              :                                &pwbh->amount,
     605              :                                &dpub->value));
     606           81 :     FAIL_IF (0 >
     607              :              TALER_amount_add (&pwbh->fee,
     608              :                                &pwbh->fee,
     609              :                                &dpub->fees.withdraw));
     610              : 
     611           81 :     if (GNUNET_CRYPTO_BSA_CS ==
     612           81 :         dpub->key.bsign_pub_key->cipher)
     613           38 :       GNUNET_assert (NULL != pwbh->blinding_seed);
     614              : 
     615              :   }
     616              : 
     617           75 :   if (pwbh->with_age_proof || pwbh->max_age > 0)
     618              :   {
     619            5 :     pwbh->age_mask =
     620            5 :       pwbh->blinded.with_age_proof_input[0].denom_pub->key.age_mask;
     621              : 
     622            5 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     623              :                 "Attempting to withdraw from reserve %s with maximum age %d to proof\n",
     624              :                 TALER_B2S (&pwbh->reserve_pub),
     625              :                 pwbh->max_age);
     626              :   }
     627              :   else
     628              :   {
     629           70 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     630              :                 "Attempting to withdraw from reserve %s\n",
     631              :                 TALER_B2S (&pwbh->reserve_pub));
     632              :   }
     633              : 
     634           75 :   coins_hctx = GNUNET_CRYPTO_hash_context_start ();
     635           75 :   FAIL_IF (NULL == coins_hctx);
     636              : 
     637           75 :   j_denoms = json_array ();
     638           75 :   j_planchets = json_array ();
     639           75 :   FAIL_IF ((NULL == j_denoms) ||
     640              :            (NULL == j_planchets));
     641              : 
     642          156 :   for (size_t i  = 0; i< pwbh->num_input; i++)
     643              :   {
     644              :     /* Build the denomination array */
     645           81 :     const struct TALER_EXCHANGE_DenomPublicKey *denom_pub =
     646           81 :       pwbh->with_age_proof ?
     647           81 :       pwbh->blinded.with_age_proof_input[i].denom_pub :
     648           72 :       pwbh->blinded.input[i].denom_pub;
     649           81 :     const struct TALER_DenominationHashP *denom_h = &denom_pub->h_key;
     650              :     json_t *jdenom;
     651              : 
     652              :     /* The mask must be the same for all coins */
     653           81 :     FAIL_IF (pwbh->with_age_proof &&
     654              :              (pwbh->age_mask.bits != denom_pub->key.age_mask.bits));
     655              : 
     656           81 :     jdenom = GNUNET_JSON_from_data_auto (denom_h);
     657           81 :     FAIL_IF (NULL == jdenom);
     658           81 :     FAIL_IF (0 > json_array_append_new (j_denoms,
     659              :                                         jdenom));
     660              :   }
     661              : 
     662              : 
     663              :   /* Build the planchet array and calculate the hash over all planchets. */
     664           75 :   if (! pwbh->with_age_proof)
     665              :   {
     666          142 :     for (size_t i  = 0; i< pwbh->num_input; i++)
     667              :     {
     668           72 :       const struct TALER_PlanchetDetail *planchet =
     669           72 :         &pwbh->blinded.input[i].planchet_details;
     670           72 :       json_t *jc = GNUNET_JSON_PACK (
     671              :         TALER_JSON_pack_blinded_planchet (
     672              :           NULL,
     673              :           &planchet->blinded_planchet));
     674           72 :       FAIL_IF (NULL == jc);
     675           72 :       FAIL_IF (0 > json_array_append_new (j_planchets,
     676              :                                           jc));
     677              : 
     678           72 :       TALER_coin_ev_hash (&planchet->blinded_planchet,
     679              :                           &planchet->denom_pub_hash,
     680              :                           &bch);
     681              : 
     682           72 :       GNUNET_CRYPTO_hash_context_read (coins_hctx,
     683              :                                        &bch,
     684              :                                        sizeof(bch));
     685              :     }
     686              :   }
     687              :   else
     688              :   { /* Age restricted case with required age-proof. */
     689              : 
     690              :     /**
     691              :      * We collect the run of all coin candidates for the same γ index
     692              :      * first, then γ+1 etc.
     693              :      */
     694           20 :     for (size_t k = 0; k < TALER_CNC_KAPPA; k++)
     695              :     {
     696              :       struct GNUNET_HashContext *batch_ctx;
     697              :       struct TALER_BlindedCoinHashP batch_h;
     698              : 
     699           15 :       batch_ctx = GNUNET_CRYPTO_hash_context_start ();
     700           15 :       FAIL_IF (NULL == batch_ctx);
     701              : 
     702           42 :       for (size_t i  = 0; i< pwbh->num_input; i++)
     703              :       {
     704           27 :         const struct TALER_PlanchetDetail *planchet =
     705           27 :           &pwbh->blinded.with_age_proof_input[i].planchet_details[k];
     706           27 :         json_t *jc = GNUNET_JSON_PACK (
     707              :           TALER_JSON_pack_blinded_planchet (
     708              :             NULL,
     709              :             &planchet->blinded_planchet));
     710              : 
     711           27 :         FAIL_IF (NULL == jc);
     712           27 :         FAIL_IF (0 > json_array_append_new (
     713              :                    j_planchets,
     714              :                    jc));
     715              : 
     716           27 :         TALER_coin_ev_hash (
     717              :           &planchet->blinded_planchet,
     718              :           &planchet->denom_pub_hash,
     719              :           &bch);
     720              : 
     721           27 :         GNUNET_CRYPTO_hash_context_read (
     722              :           batch_ctx,
     723              :           &bch,
     724              :           sizeof(bch));
     725              :       }
     726              : 
     727           15 :       GNUNET_CRYPTO_hash_context_finish (
     728              :         batch_ctx,
     729              :         &batch_h.hash);
     730           15 :       GNUNET_CRYPTO_hash_context_read (
     731              :         coins_hctx,
     732              :         &batch_h,
     733              :         sizeof(batch_h));
     734              :     }
     735              :   }
     736              : 
     737           75 :   GNUNET_CRYPTO_hash_context_finish (
     738              :     coins_hctx,
     739              :     &pwbh->planchets_h.hash);
     740           75 :   coins_hctx = NULL;
     741              : 
     742          145 :   TALER_wallet_withdraw_sign (
     743           75 :     &pwbh->amount,
     744           75 :     &pwbh->fee,
     745           75 :     &pwbh->planchets_h,
     746              :     pwbh->blinding_seed,
     747           75 :     pwbh->with_age_proof ? &pwbh->age_mask: NULL,
     748           75 :     pwbh->with_age_proof ? pwbh->max_age : 0,
     749              :     pwbh->reserve_priv,
     750              :     &pwbh->reserve_sig);
     751              : 
     752              :   /* Initiate the POST-request */
     753           75 :   j_request_body = GNUNET_JSON_PACK (
     754              :     GNUNET_JSON_pack_string ("cipher",
     755              :                              "ED25519"),
     756              :     GNUNET_JSON_pack_data_auto ("reserve_pub",
     757              :                                 &pwbh->reserve_pub),
     758              :     GNUNET_JSON_pack_array_steal ("denoms_h",
     759              :                                   j_denoms),
     760              :     GNUNET_JSON_pack_array_steal ("coin_evs",
     761              :                                   j_planchets),
     762              :     GNUNET_JSON_pack_allow_null (
     763              :       pwbh->with_age_proof
     764              :       ? GNUNET_JSON_pack_uint64 ("max_age",
     765              :                                  pwbh->max_age)
     766              :       : GNUNET_JSON_pack_string ("max_age",
     767              :                                  NULL) ),
     768              :     GNUNET_JSON_pack_data_auto ("reserve_sig",
     769              :                                 &pwbh->reserve_sig));
     770              :   /* both arrays are now owned by j_request_body */
     771           75 :   j_denoms = NULL;
     772           75 :   j_planchets = NULL;
     773           75 :   FAIL_IF (NULL == j_request_body);
     774              : 
     775           75 :   if (NULL != pwbh->blinding_seed)
     776              :   {
     777           35 :     json_t *j_seed = GNUNET_JSON_PACK (
     778              :       GNUNET_JSON_pack_data_auto ("blinding_seed",
     779              :                                   pwbh->blinding_seed));
     780           35 :     GNUNET_assert (NULL != j_seed);
     781           35 :     GNUNET_assert (0 ==
     782              :                    json_object_update_new (
     783              :                      j_request_body,
     784              :                      j_seed));
     785              :   }
     786              : 
     787           75 :   curlh = TALER_EXCHANGE_curl_easy_get_ (pwbh->request_url);
     788           75 :   FAIL_IF (NULL == curlh);
     789           75 :   FAIL_IF (GNUNET_OK !=
     790              :            TALER_curl_easy_post (
     791              :              &pwbh->post_ctx,
     792              :              curlh,
     793              :              j_request_body));
     794           75 :   json_decref (j_request_body);
     795           75 :   j_request_body = NULL;
     796              : 
     797          150 :   pwbh->job = GNUNET_CURL_job_add2 (
     798              :     pwbh->curl_ctx,
     799              :     curlh,
     800           75 :     pwbh->post_ctx.headers,
     801              :     &handle_withdraw_blinded_finished,
     802              :     pwbh);
     803           75 :   FAIL_IF (NULL == pwbh->job);
     804              : 
     805           75 :   return TALER_EC_NONE;
     806              : 
     807            0 : ERROR:
     808            0 :   if (NULL != coins_hctx)
     809            0 :     GNUNET_CRYPTO_hash_context_abort (coins_hctx);
     810            0 :   if (NULL != j_denoms)
     811            0 :     json_decref (j_denoms);
     812            0 :   if (NULL != j_planchets)
     813            0 :     json_decref (j_planchets);
     814            0 :   if (NULL != j_request_body)
     815            0 :     json_decref (j_request_body);
     816            0 :   if (NULL != curlh)
     817            0 :     curl_easy_cleanup (curlh);
     818            0 :   return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     819              : #undef FAIL_IF
     820              : }
     821              : 
     822              : 
     823              : void
     824          150 : TALER_EXCHANGE_post_withdraw_blinded_cancel (
     825              :   struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh)
     826              : {
     827          150 :   if (NULL == pwbh)
     828           75 :     return;
     829           75 :   if (NULL != pwbh->job)
     830              :   {
     831            0 :     GNUNET_CURL_job_cancel (pwbh->job);
     832            0 :     pwbh->job = NULL;
     833              :   }
     834           75 :   GNUNET_free (pwbh->request_url);
     835           75 :   TALER_EXCHANGE_keys_decref (pwbh->keys);
     836           75 :   TALER_curl_easy_post_finished (&pwbh->post_ctx);
     837           75 :   GNUNET_free (pwbh);
     838              : }
     839              : 
     840              : 
     841              : /* exchange_api_post-withdraw_blinded.c */
        

Generated by: LCOV version 2.0-1