LCOV - code coverage report
Current view: top level - lib - exchange_api_post-withdraw_blinded.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 67.4 % 282 190
Test Date: 2026-04-14 15:39:31 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 :           return GNUNET_SYSERR;
     236              :         }
     237              :       }
     238              :     }
     239              : 
     240           61 :     response.details.ok.num_sigs = wbh->num_input;
     241           61 :     response.details.ok.blinded_denom_sigs = denoms_sig;
     242           61 :     response.details.ok.planchets_h = wbh->planchets_h;
     243           61 :     wbh->callback (
     244              :       wbh->callback_cls,
     245              :       &response);
     246              :     /* Make sure the callback isn't called again */
     247           61 :     wbh->callback = NULL;
     248              :     /* Free resources */
     249          124 :     for (size_t i = 0; i < wbh->num_input; i++)
     250           63 :       TALER_blinded_denom_sig_free (&denoms_sig[i]);
     251              :   }
     252              : 
     253           61 :   return GNUNET_OK;
     254              : }
     255              : 
     256              : 
     257              : /**
     258              :  * We got a 201 CREATED response for the /withdraw operation.
     259              :  * Extract the noreveal_index and return it to the caller.
     260              :  *
     261              :  * @param wbh operation handle
     262              :  * @param j_response reply from the exchange
     263              :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
     264              :  */
     265              : static enum GNUNET_GenericReturnValue
     266            3 : withdraw_blinded_created (
     267              :   struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh,
     268              :   const json_t *j_response)
     269              : {
     270            3 :   struct TALER_EXCHANGE_PostWithdrawBlindedResponse response = {
     271              :     .hr.reply = j_response,
     272              :     .hr.http_status = MHD_HTTP_CREATED,
     273              :     .details.created.planchets_h = wbh->planchets_h,
     274            3 :     .details.created.num_coins = wbh->num_input,
     275              :   };
     276              :   struct TALER_ExchangeSignatureP exchange_sig;
     277              :   struct GNUNET_JSON_Specification spec[] = {
     278            3 :     GNUNET_JSON_spec_uint8 ("noreveal_index",
     279              :                             &response.details.created.noreveal_index),
     280            3 :     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
     281              :                                  &exchange_sig),
     282            3 :     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
     283              :                                  &response.details.created.exchange_pub),
     284            3 :     GNUNET_JSON_spec_end ()
     285              :   };
     286              : 
     287            3 :   if (GNUNET_OK!=
     288            3 :       GNUNET_JSON_parse (j_response,
     289              :                          spec,
     290              :                          NULL, NULL))
     291              :   {
     292            0 :     GNUNET_break_op (0);
     293            0 :     return GNUNET_SYSERR;
     294              :   }
     295              : 
     296            3 :   if (GNUNET_OK !=
     297            3 :       TALER_exchange_online_withdraw_age_confirmation_verify (
     298            3 :         &wbh->planchets_h,
     299            3 :         response.details.created.noreveal_index,
     300              :         &response.details.created.exchange_pub,
     301              :         &exchange_sig))
     302              :   {
     303            0 :     GNUNET_break_op (0);
     304            0 :     return GNUNET_SYSERR;
     305              : 
     306              :   }
     307              : 
     308            3 :   wbh->callback (wbh->callback_cls,
     309              :                  &response);
     310              :   /* make sure the callback isn't called again */
     311            3 :   wbh->callback = NULL;
     312              : 
     313            3 :   return GNUNET_OK;
     314              : }
     315              : 
     316              : 
     317              : /**
     318              :  * Function called when we're done processing the
     319              :  * HTTP /withdraw request.
     320              :  *
     321              :  * @param cls the `struct TALER_EXCHANGE_PostWithdrawBlindedHandle`
     322              :  * @param response_code The HTTP response code
     323              :  * @param response response data
     324              :  */
     325              : static void
     326           75 : handle_withdraw_blinded_finished (
     327              :   void *cls,
     328              :   long response_code,
     329              :   const void *response)
     330              : {
     331           75 :   struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh = cls;
     332           75 :   const json_t *j_response = response;
     333           75 :   struct TALER_EXCHANGE_PostWithdrawBlindedResponse wbr = {
     334              :     .hr.reply = j_response,
     335           75 :     .hr.http_status = (unsigned int) response_code
     336              :   };
     337              : 
     338           75 :   wbh->job = NULL;
     339           75 :   switch (response_code)
     340              :   {
     341            0 :   case 0:
     342            0 :     wbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     343            0 :     break;
     344           61 :   case MHD_HTTP_OK:
     345              :     {
     346           61 :       if (GNUNET_OK !=
     347           61 :           withdraw_blinded_ok (
     348              :             wbh,
     349              :             j_response))
     350              :       {
     351            0 :         GNUNET_break_op (0);
     352            0 :         wbr.hr.http_status = 0;
     353            0 :         wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     354            0 :         break;
     355              :       }
     356           61 :       GNUNET_assert (NULL == wbh->callback);
     357           61 :       TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
     358           64 :       return;
     359              :     }
     360            3 :   case MHD_HTTP_CREATED:
     361            3 :     if (GNUNET_OK !=
     362            3 :         withdraw_blinded_created (
     363              :           wbh,
     364              :           j_response))
     365              :     {
     366            0 :       GNUNET_break_op (0);
     367            0 :       wbr.hr.http_status = 0;
     368            0 :       wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     369            0 :       break;
     370              :     }
     371            3 :     GNUNET_assert (NULL == wbh->callback);
     372            3 :     TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
     373            3 :     return;
     374            0 :   case MHD_HTTP_BAD_REQUEST:
     375              :     /* This should never happen, either us or the exchange is buggy
     376              :        (or API version conflict); just pass JSON reply to the application */
     377            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     378            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     379            0 :     break;
     380            0 :   case MHD_HTTP_FORBIDDEN:
     381            0 :     GNUNET_break_op (0);
     382              :     /* Nothing really to verify, exchange says one of the signatures is
     383              :        invalid; as we checked them, this should never happen, we
     384              :        should pass the JSON reply to the application */
     385            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     386            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     387            0 :     break;
     388            0 :   case MHD_HTTP_NOT_FOUND:
     389              :     /* Nothing really to verify, the exchange basically just says
     390              :        that it doesn't know this reserve.  Can happen if we
     391              :        query before the wire transfer went through.
     392              :        We should simply pass the JSON reply to the application. */
     393            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     394            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     395            0 :     break;
     396            7 :   case MHD_HTTP_CONFLICT:
     397            7 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     398            7 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     399            7 :     if (TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS ==
     400            7 :         wbr.hr.ec)
     401              :     {
     402              :       struct GNUNET_JSON_Specification spec[] = {
     403            0 :         TALER_JSON_spec_amount_any (
     404              :           "balance",
     405              :           &wbr.details.conflict.details.generic_insufficient_funds.balance),
     406            0 :         TALER_JSON_spec_amount_any (
     407              :           "requested_amount",
     408              :           &wbr.details.conflict.details.generic_insufficient_funds.
     409              :           requested_amount),
     410            0 :         GNUNET_JSON_spec_end ()
     411              :       };
     412              : 
     413            0 :       if (GNUNET_OK !=
     414            0 :           GNUNET_JSON_parse (j_response,
     415              :                              spec,
     416              :                              NULL, NULL))
     417              :       {
     418            0 :         GNUNET_break_op (0);
     419            0 :         wbr.hr.http_status = 0;
     420            0 :         wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     421            0 :         break;
     422              :       }
     423              :     }
     424            7 :     break;
     425            0 :   case MHD_HTTP_GONE:
     426              :     /* could happen if denomination was revoked */
     427              :     /* Note: one might want to check /keys for revocation
     428              :        signature here, alas tricky in case our /keys
     429              :        is outdated => left to clients */
     430            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     431            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     432            0 :     break;
     433            0 :   case MHD_HTTP_PRECONDITION_FAILED:
     434              :     /* could happen if we were too early and the denomination
     435              :        is not yet available */
     436              :     /* Note: one might want to check the "Date" header to
     437              :        see if our clock is very far off */
     438            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     439            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     440            0 :     break;
     441            4 :   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
     442            4 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     443            4 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     444            4 :     if (GNUNET_OK !=
     445            4 :         TALER_EXCHANGE_parse_451 (&wbr.details.unavailable_for_legal_reasons,
     446              :                                   j_response))
     447              :     {
     448            0 :       GNUNET_break_op (0);
     449            0 :       wbr.hr.http_status = 0;
     450            0 :       wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     451            0 :       break;
     452              :     }
     453            4 :     break;
     454            0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     455              :     /* Server had an internal issue; we should retry, but this API
     456              :        leaves this to the application */
     457            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     458            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     459            0 :     break;
     460            0 :   case MHD_HTTP_NOT_IMPLEMENTED:
     461              :     /* Server does not implement a feature (usually the cipher) */
     462            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     463            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     464            0 :     break;
     465            0 :   case MHD_HTTP_BAD_GATEWAY:
     466              :     /* Server could not talk to another component, usually this
     467              :        indicates a problem with the secmod helper */
     468            0 :     wbr.hr.ec = TALER_JSON_get_error_code (j_response);
     469            0 :     wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     470            0 :     break;
     471            0 :   case MHD_HTTP_SERVICE_UNAVAILABLE:
     472              :     /* Server had an internal issue; we should retry, but this API
     473              :        leaves this to the application */
     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 :   default:
     478              :     /* unexpected response code */
     479            0 :     GNUNET_break_op (0);
     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 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     483              :                 "Unexpected response code %u/%d for exchange withdraw\n",
     484              :                 (unsigned int) response_code,
     485              :                 (int) wbr.hr.ec);
     486            0 :     break;
     487              :   }
     488           11 :   wbh->callback (wbh->callback_cls,
     489              :                  &wbr);
     490           11 :   TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
     491              : }
     492              : 
     493              : 
     494              : struct TALER_EXCHANGE_PostWithdrawBlindedHandle *
     495           75 : TALER_EXCHANGE_post_withdraw_blinded_create (
     496              :   struct GNUNET_CURL_Context *curl_ctx,
     497              :   struct TALER_EXCHANGE_Keys *keys,
     498              :   const char *exchange_url,
     499              :   const struct TALER_ReservePrivateKeyP *reserve_priv,
     500              :   const struct TALER_BlindingMasterSeedP *blinding_seed,
     501              :   size_t num_input,
     502              :   const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *blinded_input)
     503              : {
     504              :   struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh =
     505           75 :     GNUNET_new (struct TALER_EXCHANGE_PostWithdrawBlindedHandle);
     506              : 
     507           75 :   wbh->keys = TALER_EXCHANGE_keys_incref (keys);
     508           75 :   wbh->curl_ctx = curl_ctx;
     509           75 :   wbh->reserve_priv = reserve_priv;
     510           75 :   wbh->request_url = TALER_url_join (exchange_url,
     511              :                                      "withdraw",
     512              :                                      NULL);
     513           75 :   GNUNET_CRYPTO_eddsa_key_get_public (
     514           75 :     &wbh->reserve_priv->eddsa_priv,
     515              :     &wbh->reserve_pub.eddsa_pub);
     516           75 :   wbh->num_input = num_input;
     517           75 :   wbh->blinded.input = blinded_input;
     518           75 :   wbh->blinding_seed = blinding_seed;
     519              : 
     520           75 :   return wbh;
     521              : }
     522              : 
     523              : 
     524              : enum GNUNET_GenericReturnValue
     525            5 : TALER_EXCHANGE_post_withdraw_blinded_set_options_ (
     526              :   struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh,
     527              :   unsigned int num_options,
     528              :   const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue options[])
     529              : {
     530           10 :   for (unsigned int i = 0; i < num_options; i++)
     531              :   {
     532           10 :     const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue *opt =
     533           10 :       &options[i];
     534           10 :     switch (opt->option)
     535              :     {
     536            5 :     case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_END:
     537            5 :       return GNUNET_OK;
     538            5 :     case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_WITH_AGE_PROOF:
     539            5 :       pwbh->with_age_proof = true;
     540            5 :       pwbh->max_age = opt->details.with_age_proof.max_age;
     541            5 :       pwbh->blinded.with_age_proof_input = opt->details.with_age_proof.input;
     542            5 :       break;
     543              :     }
     544              :   }
     545            0 :   return GNUNET_OK;
     546              : }
     547              : 
     548              : 
     549              : enum TALER_ErrorCode
     550           75 : TALER_EXCHANGE_post_withdraw_blinded_start (
     551              :   struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh,
     552              :   TALER_EXCHANGE_PostWithdrawBlindedCallback cb,
     553              :   TALER_EXCHANGE_POST_WITHDRAW_BLINDED_RESULT_CLOSURE *cb_cls)
     554              : {
     555           75 :   json_t *j_denoms = NULL;
     556           75 :   json_t *j_planchets = NULL;
     557           75 :   json_t *j_request_body = NULL;
     558           75 :   CURL *curlh = NULL;
     559           75 :   struct GNUNET_HashContext *coins_hctx = NULL;
     560              :   struct TALER_BlindedCoinHashP bch;
     561              : 
     562           75 :   pwbh->callback = cb;
     563           75 :   pwbh->callback_cls = cb_cls;
     564              : #define FAIL_IF(cond) \
     565              :         do { \
     566              :           if ((cond)) \
     567              :           { \
     568              :             GNUNET_break (! (cond)); \
     569              :             goto ERROR; \
     570              :           } \
     571              :         } while (0)
     572              : 
     573           75 :   GNUNET_assert (0 < pwbh->num_input);
     574              : 
     575           75 :   FAIL_IF (GNUNET_OK !=
     576              :            TALER_amount_set_zero (pwbh->keys->currency,
     577              :                                   &pwbh->amount));
     578           75 :   FAIL_IF (GNUNET_OK !=
     579              :            TALER_amount_set_zero (pwbh->keys->currency,
     580              :                                   &pwbh->fee));
     581              : 
     582              :   /* Accumulate total value with fees */
     583          156 :   for (size_t i = 0; i < pwbh->num_input; i++)
     584              :   {
     585           81 :     const struct TALER_EXCHANGE_DenomPublicKey *dpub =
     586           81 :       pwbh->with_age_proof ?
     587           81 :       pwbh->blinded.with_age_proof_input[i].denom_pub :
     588           72 :       pwbh->blinded.input[i].denom_pub;
     589              : 
     590           81 :     FAIL_IF (0 >
     591              :              TALER_amount_add (&pwbh->amount,
     592              :                                &pwbh->amount,
     593              :                                &dpub->value));
     594           81 :     FAIL_IF (0 >
     595              :              TALER_amount_add (&pwbh->fee,
     596              :                                &pwbh->fee,
     597              :                                &dpub->fees.withdraw));
     598              : 
     599           81 :     if (GNUNET_CRYPTO_BSA_CS ==
     600           81 :         dpub->key.bsign_pub_key->cipher)
     601           38 :       GNUNET_assert (NULL != pwbh->blinding_seed);
     602              : 
     603              :   }
     604              : 
     605           75 :   if (pwbh->with_age_proof || pwbh->max_age > 0)
     606              :   {
     607            5 :     pwbh->age_mask =
     608            5 :       pwbh->blinded.with_age_proof_input[0].denom_pub->key.age_mask;
     609              : 
     610            5 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     611              :                 "Attempting to withdraw from reserve %s with maximum age %d to proof\n",
     612              :                 TALER_B2S (&pwbh->reserve_pub),
     613              :                 pwbh->max_age);
     614              :   }
     615              :   else
     616              :   {
     617           70 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     618              :                 "Attempting to withdraw from reserve %s\n",
     619              :                 TALER_B2S (&pwbh->reserve_pub));
     620              :   }
     621              : 
     622           75 :   coins_hctx = GNUNET_CRYPTO_hash_context_start ();
     623           75 :   FAIL_IF (NULL == coins_hctx);
     624              : 
     625           75 :   j_denoms = json_array ();
     626           75 :   j_planchets = json_array ();
     627           75 :   FAIL_IF ((NULL == j_denoms) ||
     628              :            (NULL == j_planchets));
     629              : 
     630          156 :   for (size_t i  = 0; i< pwbh->num_input; i++)
     631              :   {
     632              :     /* Build the denomination array */
     633           81 :     const struct TALER_EXCHANGE_DenomPublicKey *denom_pub =
     634           81 :       pwbh->with_age_proof ?
     635           81 :       pwbh->blinded.with_age_proof_input[i].denom_pub :
     636           72 :       pwbh->blinded.input[i].denom_pub;
     637           81 :     const struct TALER_DenominationHashP *denom_h = &denom_pub->h_key;
     638              :     json_t *jdenom;
     639              : 
     640              :     /* The mask must be the same for all coins */
     641           81 :     FAIL_IF (pwbh->with_age_proof &&
     642              :              (pwbh->age_mask.bits != denom_pub->key.age_mask.bits));
     643              : 
     644           81 :     jdenom = GNUNET_JSON_from_data_auto (denom_h);
     645           81 :     FAIL_IF (NULL == jdenom);
     646           81 :     FAIL_IF (0 > json_array_append_new (j_denoms,
     647              :                                         jdenom));
     648              :   }
     649              : 
     650              : 
     651              :   /* Build the planchet array and calculate the hash over all planchets. */
     652           75 :   if (! pwbh->with_age_proof)
     653              :   {
     654          142 :     for (size_t i  = 0; i< pwbh->num_input; i++)
     655              :     {
     656           72 :       const struct TALER_PlanchetDetail *planchet =
     657           72 :         &pwbh->blinded.input[i].planchet_details;
     658           72 :       json_t *jc = GNUNET_JSON_PACK (
     659              :         TALER_JSON_pack_blinded_planchet (
     660              :           NULL,
     661              :           &planchet->blinded_planchet));
     662           72 :       FAIL_IF (NULL == jc);
     663           72 :       FAIL_IF (0 > json_array_append_new (j_planchets,
     664              :                                           jc));
     665              : 
     666           72 :       TALER_coin_ev_hash (&planchet->blinded_planchet,
     667              :                           &planchet->denom_pub_hash,
     668              :                           &bch);
     669              : 
     670           72 :       GNUNET_CRYPTO_hash_context_read (coins_hctx,
     671              :                                        &bch,
     672              :                                        sizeof(bch));
     673              :     }
     674              :   }
     675              :   else
     676              :   { /* Age restricted case with required age-proof. */
     677              : 
     678              :     /**
     679              :      * We collect the run of all coin candidates for the same γ index
     680              :      * first, then γ+1 etc.
     681              :      */
     682           20 :     for (size_t k = 0; k < TALER_CNC_KAPPA; k++)
     683              :     {
     684              :       struct GNUNET_HashContext *batch_ctx;
     685              :       struct TALER_BlindedCoinHashP batch_h;
     686              : 
     687           15 :       batch_ctx = GNUNET_CRYPTO_hash_context_start ();
     688           15 :       FAIL_IF (NULL == batch_ctx);
     689              : 
     690           42 :       for (size_t i  = 0; i< pwbh->num_input; i++)
     691              :       {
     692           27 :         const struct TALER_PlanchetDetail *planchet =
     693           27 :           &pwbh->blinded.with_age_proof_input[i].planchet_details[k];
     694           27 :         json_t *jc = GNUNET_JSON_PACK (
     695              :           TALER_JSON_pack_blinded_planchet (
     696              :             NULL,
     697              :             &planchet->blinded_planchet));
     698              : 
     699           27 :         FAIL_IF (NULL == jc);
     700           27 :         FAIL_IF (0 > json_array_append_new (
     701              :                    j_planchets,
     702              :                    jc));
     703              : 
     704           27 :         TALER_coin_ev_hash (
     705              :           &planchet->blinded_planchet,
     706              :           &planchet->denom_pub_hash,
     707              :           &bch);
     708              : 
     709           27 :         GNUNET_CRYPTO_hash_context_read (
     710              :           batch_ctx,
     711              :           &bch,
     712              :           sizeof(bch));
     713              :       }
     714              : 
     715           15 :       GNUNET_CRYPTO_hash_context_finish (
     716              :         batch_ctx,
     717              :         &batch_h.hash);
     718           15 :       GNUNET_CRYPTO_hash_context_read (
     719              :         coins_hctx,
     720              :         &batch_h,
     721              :         sizeof(batch_h));
     722              :     }
     723              :   }
     724              : 
     725           75 :   GNUNET_CRYPTO_hash_context_finish (
     726              :     coins_hctx,
     727              :     &pwbh->planchets_h.hash);
     728           75 :   coins_hctx = NULL;
     729              : 
     730          145 :   TALER_wallet_withdraw_sign (
     731           75 :     &pwbh->amount,
     732           75 :     &pwbh->fee,
     733           75 :     &pwbh->planchets_h,
     734              :     pwbh->blinding_seed,
     735           75 :     pwbh->with_age_proof ? &pwbh->age_mask: NULL,
     736           75 :     pwbh->with_age_proof ? pwbh->max_age : 0,
     737              :     pwbh->reserve_priv,
     738              :     &pwbh->reserve_sig);
     739              : 
     740              :   /* Initiate the POST-request */
     741           75 :   j_request_body = GNUNET_JSON_PACK (
     742              :     GNUNET_JSON_pack_string ("cipher",
     743              :                              "ED25519"),
     744              :     GNUNET_JSON_pack_data_auto ("reserve_pub",
     745              :                                 &pwbh->reserve_pub),
     746              :     GNUNET_JSON_pack_array_steal ("denoms_h",
     747              :                                   j_denoms),
     748              :     GNUNET_JSON_pack_array_steal ("coin_evs",
     749              :                                   j_planchets),
     750              :     GNUNET_JSON_pack_allow_null (
     751              :       pwbh->with_age_proof
     752              :       ? GNUNET_JSON_pack_uint64 ("max_age",
     753              :                                  pwbh->max_age)
     754              :       : GNUNET_JSON_pack_string ("max_age",
     755              :                                  NULL) ),
     756              :     GNUNET_JSON_pack_data_auto ("reserve_sig",
     757              :                                 &pwbh->reserve_sig));
     758           75 :   FAIL_IF (NULL == j_request_body);
     759              : 
     760           75 :   if (NULL != pwbh->blinding_seed)
     761              :   {
     762           35 :     json_t *j_seed = GNUNET_JSON_PACK (
     763              :       GNUNET_JSON_pack_data_auto ("blinding_seed",
     764              :                                   pwbh->blinding_seed));
     765           35 :     GNUNET_assert (NULL != j_seed);
     766           35 :     GNUNET_assert (0 ==
     767              :                    json_object_update_new (
     768              :                      j_request_body,
     769              :                      j_seed));
     770              :   }
     771              : 
     772           75 :   curlh = TALER_EXCHANGE_curl_easy_get_ (pwbh->request_url);
     773           75 :   FAIL_IF (NULL == curlh);
     774           75 :   FAIL_IF (GNUNET_OK !=
     775              :            TALER_curl_easy_post (
     776              :              &pwbh->post_ctx,
     777              :              curlh,
     778              :              j_request_body));
     779           75 :   json_decref (j_request_body);
     780           75 :   j_request_body = NULL;
     781              : 
     782          150 :   pwbh->job = GNUNET_CURL_job_add2 (
     783              :     pwbh->curl_ctx,
     784              :     curlh,
     785           75 :     pwbh->post_ctx.headers,
     786              :     &handle_withdraw_blinded_finished,
     787              :     pwbh);
     788           75 :   FAIL_IF (NULL == pwbh->job);
     789              : 
     790           75 :   return TALER_EC_NONE;
     791              : 
     792            0 : ERROR:
     793            0 :   if (NULL != coins_hctx)
     794            0 :     GNUNET_CRYPTO_hash_context_abort (coins_hctx);
     795            0 :   if (NULL != j_denoms)
     796            0 :     json_decref (j_denoms);
     797            0 :   if (NULL != j_planchets)
     798            0 :     json_decref (j_planchets);
     799            0 :   if (NULL != j_request_body)
     800            0 :     json_decref (j_request_body);
     801            0 :   if (NULL != curlh)
     802            0 :     curl_easy_cleanup (curlh);
     803            0 :   return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     804              : #undef FAIL_IF
     805              : }
     806              : 
     807              : 
     808              : void
     809          150 : TALER_EXCHANGE_post_withdraw_blinded_cancel (
     810              :   struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh)
     811              : {
     812          150 :   if (NULL == pwbh)
     813           75 :     return;
     814           75 :   if (NULL != pwbh->job)
     815              :   {
     816            0 :     GNUNET_CURL_job_cancel (pwbh->job);
     817            0 :     pwbh->job = NULL;
     818              :   }
     819           75 :   GNUNET_free (pwbh->request_url);
     820           75 :   TALER_EXCHANGE_keys_decref (pwbh->keys);
     821           75 :   TALER_curl_easy_post_finished (&pwbh->post_ctx);
     822           75 :   GNUNET_free (pwbh);
     823              : }
     824              : 
     825              : 
     826              : /* exchange_api_post-withdraw_blinded.c */
        

Generated by: LCOV version 2.0-1