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

Generated by: LCOV version 2.0-1