LCOV - code coverage report
Current view: top level - lib - exchange_api_post-withdraw.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 83.7 % 375 314
Test Date: 2026-04-04 21:36:01 Functions: 100.0 % 9 9

            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              :  * A CoinCandidate is populated from a master secret.
      42              :  * The data is copied from and generated out of the client's input.
      43              :  */
      44              : struct CoinCandidate
      45              : {
      46              :   /**
      47              :    * The details derived form the master secrets
      48              :    */
      49              :   struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details;
      50              : 
      51              :   /**
      52              :    * Blinded hash of the coin
      53              :    **/
      54              :   struct TALER_BlindedCoinHashP blinded_coin_h;
      55              : 
      56              : };
      57              : 
      58              : 
      59              : /**
      60              :  * Data we keep per coin in the batch.
      61              :  * This is copied from and generated out of the input provided
      62              :  * by the client.
      63              :  */
      64              : struct CoinData
      65              : {
      66              :   /**
      67              :    * The denomination of the coin.
      68              :    */
      69              :   struct TALER_EXCHANGE_DenomPublicKey denom_pub;
      70              : 
      71              :   /**
      72              :    * The Candidates for the coin.  If the batch is not age-restricted,
      73              :    * only index 0 is used.
      74              :    */
      75              :   struct CoinCandidate candidates[TALER_CNC_KAPPA];
      76              : 
      77              :   /**
      78              :    * Details of the planchet(s).  If the batch is not age-restricted,
      79              :    * only index 0 is used.
      80              :    */
      81              :   struct TALER_PlanchetDetail planchet_details[TALER_CNC_KAPPA];
      82              : };
      83              : 
      84              : 
      85              : /**
      86              :  * Per-CS-coin data needed to complete the coin after /blinding-prepare.
      87              :  */
      88              : struct BlindingPrepareCoinData
      89              : {
      90              :   /**
      91              :    * Pointer to the candidate in CoinData.candidates,
      92              :    * to continue to build its contents based on the results from /blinding-prepare
      93              :    */
      94              :   struct CoinCandidate *candidate;
      95              : 
      96              :   /**
      97              :    * Planchet to finally generate in the corresponding candidate
      98              :    * in CoinData.planchet_details
      99              :    */
     100              :   struct TALER_PlanchetDetail *planchet;
     101              : 
     102              :   /**
     103              :    * Denomination information, needed for the
     104              :    * step after /blinding-prepare
     105              :    */
     106              :   const struct TALER_DenominationPublicKey *denom_pub;
     107              : 
     108              :   /**
     109              :    * True, if denomination supports age restriction
     110              :    */
     111              :   bool age_denom;
     112              : 
     113              :   /**
     114              :    * The index into the array of returned values from the call to
     115              :    * /blinding-prepare that are to be used for this coin.
     116              :    */
     117              :   size_t cs_idx;
     118              : 
     119              : };
     120              : 
     121              : 
     122              : /**
     123              :  * A /withdraw request-handle for calls from
     124              :  * a wallet, i. e. when blinding data is available.
     125              :  */
     126              : struct TALER_EXCHANGE_PostWithdrawHandle
     127              : {
     128              : 
     129              :   /**
     130              :    * The base-URL of the exchange.
     131              :    */
     132              :   const char *exchange_url;
     133              : 
     134              :   /**
     135              :    * Seed to derive of all seeds for the coins.
     136              :    */
     137              :   struct TALER_WithdrawMasterSeedP seed;
     138              : 
     139              :   /**
     140              :    * If @e with_age_proof is true, the derived TALER_CNC_KAPPA many
     141              :    * seeds for candidate batches.
     142              :    */
     143              :   struct TALER_KappaWithdrawMasterSeedP kappa_seed;
     144              : 
     145              :   /**
     146              :    * True if @e blinding_seed is filled, that is, if
     147              :    * any of the denominations is of cipher type CS
     148              :    */
     149              :   bool has_blinding_seed;
     150              : 
     151              :   /**
     152              :    * Seed used for the derivation of blinding factors for denominations
     153              :    * with Clause-Schnorr cipher.  We derive this from the master seed
     154              :    * for the withdraw, but independent from the other planchet seeds.
     155              :    * Only valid when @e has_blinding_seed is true;
     156              :    */
     157              :   struct TALER_BlindingMasterSeedP blinding_seed;
     158              : 
     159              :   /**
     160              :    * Reserve private key.
     161              :    */
     162              :   const struct TALER_ReservePrivateKeyP *reserve_priv;
     163              : 
     164              :   /**
     165              :    * Reserve public key, calculated
     166              :    */
     167              :   struct TALER_ReservePublicKeyP reserve_pub;
     168              : 
     169              :   /**
     170              :    * Signature of the reserve for the request, calculated after all
     171              :    * parameters for the coins are collected.
     172              :    */
     173              :   struct TALER_ReserveSignatureP reserve_sig;
     174              : 
     175              :   /*
     176              :    * The denomination keys of the exchange
     177              :    */
     178              :   struct TALER_EXCHANGE_Keys *keys;
     179              : 
     180              :   /**
     181              :    * True, if the withdraw is for age-restricted coins, with age-proof.
     182              :    * The denominations MUST support age restriction.
     183              :    */
     184              :   bool with_age_proof;
     185              : 
     186              :   /**
     187              :    * If @e with_age_proof is true, the age mask, extracted
     188              :    * from the denominations.
     189              :    * MUST be the same for all denominations.
     190              :    */
     191              :   struct TALER_AgeMask age_mask;
     192              : 
     193              :   /**
     194              :    * The maximum age to commit to.  If @e with_age_proof
     195              :    * is true, the client will need to proof the correct setting
     196              :    * of age-restriction on the coins via an additional call
     197              :    * to /reveal-withdraw.
     198              :    */
     199              :   uint8_t max_age;
     200              : 
     201              :   /**
     202              :    * Length of the @e coin_data Array
     203              :    */
     204              :   size_t num_coins;
     205              : 
     206              :   /**
     207              :    * Array of per-coin data
     208              :    */
     209              :   struct CoinData *coin_data;
     210              : 
     211              :   /**
     212              :    * Context for curl.
     213              :    */
     214              :   struct GNUNET_CURL_Context *curl_ctx;
     215              : 
     216              :   /**
     217              :    * Function to call with withdraw response results.
     218              :    */
     219              :   TALER_EXCHANGE_PostWithdrawCallback callback;
     220              : 
     221              :   /**
     222              :    * Closure for @e callback
     223              :    */
     224              :   void *callback_cls;
     225              : 
     226              :   /**
     227              :    * The handler for the call to /blinding-prepare, needed for CS denominations.
     228              :    * NULL until _start is called for CS denominations, or when no CS denoms.
     229              :    */
     230              :   struct TALER_EXCHANGE_PostBlindingPrepareHandle *blinding_prepare_handle;
     231              : 
     232              :   /**
     233              :    * The Handler for the actual call to the exchange
     234              :    */
     235              :   struct TALER_EXCHANGE_PostWithdrawBlindedHandle *withdraw_blinded_handle;
     236              : 
     237              :   /**
     238              :    * Number of CS denomination coin entries in @e bp_coins.
     239              :    * Zero if no CS denominations.
     240              :    */
     241              :   size_t num_bp_coins;
     242              : 
     243              :   /**
     244              :    * Array of @e num_bp_coins coin data for the blinding-prepare step.
     245              :    */
     246              :   struct BlindingPrepareCoinData *bp_coins;
     247              : 
     248              :   /**
     249              :    * Number of nonces in @e bp_nonces.
     250              :    */
     251              :   size_t num_bp_nonces;
     252              : 
     253              :   /**
     254              :    * Array of @e num_bp_nonces nonces for CS denominations.
     255              :    */
     256              :   union GNUNET_CRYPTO_BlindSessionNonce *bp_nonces;
     257              : 
     258              :   /**
     259              :    * Nonce keys for the blinding-prepare call.
     260              :    */
     261              :   struct TALER_EXCHANGE_NonceKey *bp_nonce_keys;
     262              : 
     263              :   /**
     264              :    * Number of nonce keys in @e bp_nonce_keys.
     265              :    */
     266              :   size_t num_bp_nonce_keys;
     267              : 
     268              :   /**
     269              :    * Array of @e init_num_coins denomination public keys.
     270              :    * NULL after _start is called.
     271              :    */
     272              :   struct TALER_EXCHANGE_DenomPublicKey *init_denoms_pub;
     273              : 
     274              :   /**
     275              :    * Number of coins provided in @e init_denoms_pub.
     276              :    */
     277              :   size_t init_num_coins;
     278              : 
     279              :   struct
     280              :   {
     281              : 
     282              :     /**
     283              :      * True if @e blinding_seed is filled, that is, if
     284              :      * any of the denominations is of cipher type CS
     285              :      */
     286              :     bool has_blinding_seed;
     287              : 
     288              :     /**
     289              :      * Seed used for the derivation of blinding factors for denominations
     290              :      * with Clause-Schnorr cipher.  We derive this from the master seed
     291              :      * for the withdraw, but independent from the other planchet seeds.
     292              :      * Only valid when @e has_blinding_seed is true;
     293              :      */
     294              :     struct TALER_BlindingMasterSeedP blinding_seed;
     295              : 
     296              :   } options;
     297              : };
     298              : 
     299              : 
     300              : /**
     301              :  * @brief Callback to copy the results from the call to post_withdraw_blinded
     302              :  * in the non-age-restricted case to the result for the originating call.
     303              :  *
     304              :  * @param cls struct TALER_EXCHANGE_PostWithdrawHandle
     305              :  * @param wbr The response
     306              :  */
     307              : static void
     308           70 : copy_results (
     309              :   void *cls,
     310              :   const struct TALER_EXCHANGE_PostWithdrawBlindedResponse *wbr)
     311              : {
     312              :   /* The original handle from the top-level call to withdraw */
     313           70 :   struct TALER_EXCHANGE_PostWithdrawHandle *wh = cls;
     314           70 :   struct TALER_EXCHANGE_PostWithdrawResponse resp = {
     315              :     .hr = wbr->hr,
     316              :   };
     317              : 
     318           70 :   wh->withdraw_blinded_handle = NULL;
     319              : 
     320              :   /**
     321              :    * The withdraw protocol has been performed with blinded data.
     322              :    * Now the response can be copied as is, except for the MHD_HTTP_OK case,
     323              :    * in which we now need to perform the unblinding.
     324              :    */
     325           70 :   switch (wbr->hr.http_status)
     326              :   {
     327           61 :   case MHD_HTTP_OK:
     328           61 :     {
     329              :       struct TALER_EXCHANGE_WithdrawCoinPrivateDetails
     330           61 :         details[GNUNET_NZL (wh->num_coins)];
     331           61 :       bool ok = true;
     332              : 
     333           61 :       GNUNET_assert (wh->num_coins == wbr->details.ok.num_sigs);
     334           61 :       memset (details,
     335              :               0,
     336              :               sizeof(details));
     337           61 :       resp.details.ok.num_sigs = wbr->details.ok.num_sigs;
     338           61 :       resp.details.ok.coin_details = details;
     339           61 :       resp.details.ok.planchets_h = wbr->details.ok.planchets_h;
     340          124 :       for (size_t n = 0; n<wh->num_coins; n++)
     341              :       {
     342           63 :         const struct TALER_BlindedDenominationSignature *bsig =
     343           63 :           &wbr->details.ok.blinded_denom_sigs[n];
     344           63 :         struct CoinData *cd = &wh->coin_data[n];
     345           63 :         struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *coin = &details[n];
     346              :         struct TALER_FreshCoin fresh_coin;
     347              : 
     348           63 :         *coin = wh->coin_data[n].candidates[0].details;
     349           63 :         coin->planchet = wh->coin_data[n].planchet_details[0];
     350           63 :         GNUNET_CRYPTO_eddsa_key_get_public (
     351           63 :           &coin->coin_priv.eddsa_priv,
     352              :           &coin->coin_pub.eddsa_pub);
     353              : 
     354           63 :         if (GNUNET_OK !=
     355           63 :             TALER_planchet_to_coin (&cd->denom_pub.key,
     356              :                                     bsig,
     357           63 :                                     &coin->blinding_key,
     358           63 :                                     &coin->coin_priv,
     359           63 :                                     &coin->h_age_commitment,
     360           63 :                                     &coin->h_coin_pub,
     361           63 :                                     &coin->blinding_values,
     362              :                                     &fresh_coin))
     363              :         {
     364            0 :           resp.hr.http_status = 0;
     365            0 :           resp.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE;
     366            0 :           GNUNET_break_op (0);
     367            0 :           ok = false;
     368            0 :           break;
     369              :         }
     370           63 :         coin->denom_sig = fresh_coin.sig;
     371              :       }
     372           61 :       if (ok)
     373              :       {
     374           61 :         wh->callback (
     375              :           wh->callback_cls,
     376              :           &resp);
     377           61 :         wh->callback = NULL;
     378              :       }
     379          124 :       for (size_t n = 0; n<wh->num_coins; n++)
     380              :       {
     381           63 :         struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *coin = &details[n];
     382              : 
     383           63 :         TALER_denom_sig_free (&coin->denom_sig);
     384              :       }
     385           61 :       break;
     386              :     }
     387            0 :   case MHD_HTTP_CREATED:
     388            0 :     resp.details.created = wbr->details.created;
     389            0 :     break;
     390            4 :   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
     391            4 :     resp.details.unavailable_for_legal_reasons =
     392              :       wbr->details.unavailable_for_legal_reasons;
     393            4 :     break;
     394              : 
     395            5 :   default:
     396              :     /* nothing to do here, .hr.ec and .hr.hint are all set already from previous response */
     397            5 :     break;
     398              :   }
     399           70 :   if (NULL != wh->callback)
     400              :   {
     401            9 :     wh->callback (
     402              :       wh->callback_cls,
     403              :       &resp);
     404            9 :     wh->callback = NULL;
     405              :   }
     406           70 :   TALER_EXCHANGE_post_withdraw_cancel (wh);
     407           70 : }
     408              : 
     409              : 
     410              : /**
     411              :  * @brief Callback to copy the results from the call to post_withdraw_blinded
     412              :  * in the age-restricted case.
     413              :  *
     414              :  * @param cls struct TALER_EXCHANGE_PostWithdrawHandle
     415              :  * @param wbr The response
     416              :  */
     417              : static void
     418            5 : copy_results_with_age_proof (
     419              :   void *cls,
     420              :   const struct TALER_EXCHANGE_PostWithdrawBlindedResponse *wbr)
     421            5 : {
     422              :   /* The original handle from the top-level call to withdraw */
     423            5 :   struct TALER_EXCHANGE_PostWithdrawHandle *wh = cls;
     424            5 :   uint8_t k =  wbr->details.created.noreveal_index;
     425            5 :   struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details[wh->num_coins];
     426            5 :   struct TALER_EXCHANGE_PostWithdrawResponse resp = {
     427              :     .hr = wbr->hr,
     428              :   };
     429              : 
     430            5 :   wh->withdraw_blinded_handle = NULL;
     431            5 :   switch (wbr->hr.http_status)
     432              :   {
     433            0 :   case MHD_HTTP_OK:
     434              :     /* in the age-restricted case, this should not happen */
     435            0 :     GNUNET_break_op (0);
     436            0 :     break;
     437            3 :   case MHD_HTTP_CREATED:
     438              :     {
     439            3 :       GNUNET_assert (wh->num_coins == wbr->details.created.num_coins);
     440            3 :       resp.details.created = wbr->details.created;
     441            3 :       resp.details.created.coin_details = details;
     442            3 :       resp.details.created.kappa_seed = wh->kappa_seed;
     443            3 :       memset (details,
     444              :               0,
     445              :               sizeof(details));
     446           10 :       for (size_t n = 0; n< wh->num_coins; n++)
     447              :       {
     448            7 :         details[n] = wh->coin_data[n].candidates[k].details;
     449            7 :         details[n].planchet = wh->coin_data[n].planchet_details[k];
     450              :       }
     451            3 :       break;
     452              :     }
     453            0 :   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
     454            0 :     resp.details.unavailable_for_legal_reasons =
     455              :       wbr->details.unavailable_for_legal_reasons;
     456            0 :     break;
     457            2 :   default:
     458            2 :     break;
     459              :   }
     460              : 
     461            5 :   wh->callback (
     462              :     wh->callback_cls,
     463              :     &resp);
     464            5 :   wh->callback = NULL;
     465            5 :   TALER_EXCHANGE_post_withdraw_cancel (wh);
     466            5 : }
     467              : 
     468              : 
     469              : /**
     470              :  * @brief Prepares and starts the actual TALER_EXCHANGE_post_withdraw_blinded
     471              :  * operation once all blinding-prepare steps are done (or immediately if
     472              :  * there are no CS denominations).
     473              :  *
     474              :  * @param wh The withdraw handle
     475              :  * @return #TALER_EC_NONE on success, error code on failure
     476              :  */
     477              : static enum TALER_ErrorCode
     478           75 : call_withdraw_blinded (
     479              :   struct TALER_EXCHANGE_PostWithdrawHandle *wh)
     480              : {
     481              :   enum TALER_ErrorCode ec;
     482              : 
     483           75 :   GNUNET_assert (NULL == wh->blinding_prepare_handle);
     484              : 
     485           75 :   if (! wh->with_age_proof)
     486           70 :   {
     487           70 :     struct TALER_EXCHANGE_WithdrawBlindedCoinInput input[wh->num_coins];
     488              : 
     489           70 :     memset (input,
     490              :             0,
     491              :             sizeof(input));
     492              : 
     493              :     /* Prepare the blinded planchets as input */
     494          142 :     for (size_t n = 0; n < wh->num_coins; n++)
     495              :     {
     496           72 :       input[n].denom_pub =
     497           72 :         &wh->coin_data[n].denom_pub;
     498           72 :       input[n].planchet_details =
     499           72 :         *wh->coin_data[n].planchet_details;
     500              :     }
     501              : 
     502           70 :     wh->withdraw_blinded_handle =
     503           70 :       TALER_EXCHANGE_post_withdraw_blinded_create (
     504              :         wh->curl_ctx,
     505              :         wh->keys,
     506              :         wh->exchange_url,
     507              :         wh->reserve_priv,
     508           70 :         wh->has_blinding_seed ? &wh->blinding_seed : NULL,
     509              :         wh->num_coins,
     510              :         input);
     511           70 :     if (NULL == wh->withdraw_blinded_handle)
     512            0 :       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     513           70 :     ec = TALER_EXCHANGE_post_withdraw_blinded_start (
     514              :       wh->withdraw_blinded_handle,
     515              :       &copy_results,
     516              :       wh);
     517           70 :     if (TALER_EC_NONE != ec)
     518              :     {
     519            0 :       wh->withdraw_blinded_handle = NULL;
     520            0 :       return ec;
     521              :     }
     522              :   }
     523              :   else
     524            5 :   {  /* age restricted case */
     525              :     struct TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput
     526            5 :       ari[wh->num_coins];
     527              : 
     528            5 :     memset (ari,
     529              :             0,
     530              :             sizeof(ari));
     531              : 
     532              :     /* Prepare the blinded planchets as input */
     533           14 :     for (size_t n = 0; n < wh->num_coins; n++)
     534              :     {
     535            9 :       ari[n].denom_pub = &wh->coin_data[n].denom_pub;
     536           36 :       for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
     537           27 :         ari[n].planchet_details[k] =
     538           27 :           wh->coin_data[n].planchet_details[k];
     539              :     }
     540              : 
     541            5 :     wh->withdraw_blinded_handle =
     542            5 :       TALER_EXCHANGE_post_withdraw_blinded_create (
     543              :         wh->curl_ctx,
     544              :         wh->keys,
     545              :         wh->exchange_url,
     546              :         wh->reserve_priv,
     547            5 :         wh->has_blinding_seed ? &wh->blinding_seed : NULL,
     548              :         wh->num_coins,
     549              :         NULL);
     550            5 :     if (NULL == wh->withdraw_blinded_handle)
     551            0 :       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     552            5 :     TALER_EXCHANGE_post_withdraw_blinded_set_options (
     553              :       wh->withdraw_blinded_handle,
     554              :       TALER_EXCHANGE_post_withdraw_blinded_option_with_age_proof (
     555              :         wh->max_age,
     556              :         ari));
     557            5 :     ec = TALER_EXCHANGE_post_withdraw_blinded_start (
     558              :       wh->withdraw_blinded_handle,
     559              :       &copy_results_with_age_proof,
     560              :       wh);
     561            5 :     if (TALER_EC_NONE != ec)
     562              :     {
     563            0 :       TALER_EXCHANGE_post_withdraw_blinded_cancel (wh->withdraw_blinded_handle);
     564            0 :       wh->withdraw_blinded_handle = NULL;
     565            0 :       return ec;
     566              :     }
     567              :   }
     568           75 :   return TALER_EC_NONE;
     569              : }
     570              : 
     571              : 
     572              : /**
     573              :  * @brief Function called when /blinding-prepare is finished.
     574              :  *
     575              :  * @param cls the `struct TALER_EXCHANGE_PostWithdrawHandle *`
     576              :  * @param bpr replies from the /blinding-prepare request
     577              :  */
     578              : static void
     579           35 : blinding_prepare_done (
     580              :   void *cls,
     581              :   const struct TALER_EXCHANGE_PostBlindingPrepareResponse *bpr)
     582              : {
     583           35 :   struct TALER_EXCHANGE_PostWithdrawHandle *wh = cls;
     584              : 
     585           35 :   wh->blinding_prepare_handle = NULL;
     586           35 :   switch (bpr->hr.http_status)
     587              :   {
     588           35 :   case MHD_HTTP_OK:
     589              :     {
     590           35 :       bool success = false;
     591           35 :       size_t num = bpr->details.ok.num_blinding_values;
     592              : 
     593           35 :       GNUNET_assert (0 != num);
     594           35 :       GNUNET_assert (num == wh->num_bp_nonces);
     595           81 :       for (size_t i = 0; i < wh->num_bp_coins; i++)
     596              :       {
     597           46 :         struct TALER_PlanchetDetail *planchet = wh->bp_coins[i].planchet;
     598           46 :         struct CoinCandidate *can = wh->bp_coins[i].candidate;
     599           46 :         size_t cs_idx = wh->bp_coins[i].cs_idx;
     600              : 
     601           46 :         GNUNET_assert (NULL != can);
     602           46 :         GNUNET_assert (NULL != planchet);
     603           46 :         success = false;
     604              : 
     605              :         /* Complete the initialization of the coin with CS denomination */
     606           46 :         TALER_denom_ewv_copy (
     607              :           &can->details.blinding_values,
     608           46 :           &bpr->details.ok.blinding_values[cs_idx]);
     609              : 
     610           46 :         GNUNET_assert (GNUNET_CRYPTO_BSA_CS ==
     611              :                        can->details.blinding_values.blinding_inputs->cipher);
     612              : 
     613           46 :         TALER_planchet_setup_coin_priv (
     614           46 :           &can->details.secret,
     615           46 :           &can->details.blinding_values,
     616              :           &can->details.coin_priv);
     617              : 
     618           46 :         TALER_planchet_blinding_secret_create (
     619           46 :           &can->details.secret,
     620           46 :           &can->details.blinding_values,
     621              :           &can->details.blinding_key);
     622              : 
     623              :         /* This initializes the 2nd half of the
     624              :            can->planchet_detail.blinded_planchet */
     625           46 :         if (GNUNET_OK !=
     626           46 :             TALER_planchet_prepare (
     627           46 :               wh->bp_coins[i].denom_pub,
     628           46 :               &can->details.blinding_values,
     629           46 :               &can->details.blinding_key,
     630           46 :               &wh->bp_nonces[cs_idx],
     631           46 :               &can->details.coin_priv,
     632           46 :               &can->details.h_age_commitment,
     633              :               &can->details.h_coin_pub,
     634              :               planchet))
     635              :         {
     636            0 :           GNUNET_break (0);
     637            0 :           break;
     638              :         }
     639              : 
     640           46 :         TALER_coin_ev_hash (&planchet->blinded_planchet,
     641           46 :                             &planchet->denom_pub_hash,
     642              :                             &can->blinded_coin_h);
     643           46 :         success = true;
     644              :       }
     645              : 
     646              :       /* /blinding-prepare is done, we can now perform the
     647              :        * actual withdraw operation */
     648           35 :       if (success)
     649              :       {
     650           35 :         enum TALER_ErrorCode ec = call_withdraw_blinded (wh);
     651              : 
     652           35 :         if (TALER_EC_NONE != ec)
     653              :         {
     654            0 :           struct TALER_EXCHANGE_PostWithdrawResponse resp = {
     655              :             .hr.ec = ec,
     656              :             .hr.http_status = 0,
     657              :           };
     658              : 
     659            0 :           wh->callback (
     660              :             wh->callback_cls,
     661              :             &resp);
     662            0 :           wh->callback = NULL;
     663            0 :           TALER_EXCHANGE_post_withdraw_cancel (wh);
     664              :         }
     665           35 :         return;
     666              :       }
     667              :       else
     668              :       {
     669              :         /* prepare completed but coin setup failed */
     670            0 :         struct TALER_EXCHANGE_PostWithdrawResponse resp = {
     671              :           .hr.ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     672              :           .hr.http_status = 0,
     673              :         };
     674              : 
     675            0 :         wh->callback (
     676              :           wh->callback_cls,
     677              :           &resp);
     678            0 :         wh->callback = NULL;
     679            0 :         TALER_EXCHANGE_post_withdraw_cancel (wh);
     680            0 :         return;
     681              :       }
     682              :     }
     683            0 :   default:
     684              :     {
     685              :       /* We got an error condition during blinding prepare that we need to report */
     686            0 :       struct TALER_EXCHANGE_PostWithdrawResponse resp = {
     687              :         .hr = bpr->hr
     688              :       };
     689              : 
     690            0 :       wh->callback (
     691              :         wh->callback_cls,
     692              :         &resp);
     693            0 :       wh->callback = NULL;
     694            0 :       break;
     695              :     }
     696              :   }
     697            0 :   TALER_EXCHANGE_post_withdraw_cancel (wh);
     698              : }
     699              : 
     700              : 
     701              : /**
     702              :  * @brief Prepares coins for the call to withdraw:
     703              :  * Performs synchronous crypto for RSA denominations, and stores
     704              :  * the data needed for the async /blinding-prepare step for CS denominations.
     705              :  * Does NOT start any async operations.
     706              :  *
     707              :  * @param wh The handler to the withdraw
     708              :  * @param num_coins Number of coins to withdraw
     709              :  * @param max_age The maximum age to commit to
     710              :  * @param denoms_pub Array @e num_coins of denominations
     711              :  * @param seed master seed from which to derive @e num_coins secrets
     712              :  * @param blinding_seed master seed for the blinding. Might be NULL, in which
     713              :  *        case the blinding_seed is derived from @e seed
     714              :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
     715              :  */
     716              : static enum GNUNET_GenericReturnValue
     717           75 : prepare_coins (
     718              :   struct TALER_EXCHANGE_PostWithdrawHandle *wh,
     719              :   size_t num_coins,
     720              :   uint8_t max_age,
     721              :   const struct TALER_EXCHANGE_DenomPublicKey *denoms_pub,
     722              :   const struct TALER_WithdrawMasterSeedP *seed,
     723              :   const struct TALER_BlindingMasterSeedP *blinding_seed)
     724              : {
     725           75 :   size_t cs_num = 0;
     726              :   uint8_t kappa;
     727              : 
     728              : #define FAIL_IF(cond) \
     729              :         do \
     730              :         { \
     731              :           if ((cond)) \
     732              :           { \
     733              :             GNUNET_break (! (cond)); \
     734              :             goto ERROR; \
     735              :           } \
     736              :         } while (0)
     737              : 
     738           75 :   GNUNET_assert (0 < num_coins);
     739              : 
     740           75 :   wh->num_coins = num_coins;
     741           75 :   wh->max_age = max_age;
     742           75 :   wh->age_mask = denoms_pub[0].key.age_mask;
     743           75 :   wh->coin_data = GNUNET_new_array (
     744              :     wh->num_coins,
     745              :     struct CoinData);
     746              : 
     747              :   /* First, figure out how many Clause-Schnorr denominations we have */
     748          156 :   for (size_t i =0; i< wh->num_coins; i++)
     749              :   {
     750           81 :     if (GNUNET_CRYPTO_BSA_CS ==
     751           81 :         denoms_pub[i].key.bsign_pub_key->cipher)
     752           38 :       cs_num++;
     753              :   }
     754              : 
     755           75 :   if (wh->with_age_proof)
     756            5 :     kappa = TALER_CNC_KAPPA;
     757              :   else
     758           70 :     kappa = 1;
     759              : 
     760           75 :   {
     761           75 :     struct TALER_PlanchetMasterSecretP secrets[kappa][num_coins];
     762           75 :     struct TALER_EXCHANGE_NonceKey cs_nonce_keys[GNUNET_NZL (cs_num)];
     763           75 :     uint32_t cs_indices[GNUNET_NZL (cs_num)];
     764              : 
     765           75 :     size_t cs_denom_idx = 0;
     766           75 :     size_t cs_coin_idx = 0;
     767              : 
     768           75 :     if (wh->with_age_proof)
     769              :     {
     770            5 :       TALER_withdraw_expand_kappa_seed (seed,
     771              :                                         &wh->kappa_seed);
     772           20 :       for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
     773              :       {
     774           15 :         TALER_withdraw_expand_secrets (
     775              :           num_coins,
     776           15 :           &wh->kappa_seed.tuple[k],
     777           15 :           secrets[k]);
     778              :       }
     779              :     }
     780              :     else
     781              :     {
     782           70 :       TALER_withdraw_expand_secrets (
     783              :         num_coins,
     784              :         seed,
     785           70 :         secrets[0]);
     786              :     }
     787              : 
     788           75 :     if (0 < cs_num)
     789              :     {
     790           35 :       memset (cs_nonce_keys,
     791              :               0,
     792              :               sizeof(cs_nonce_keys));
     793           35 :       wh->num_bp_coins = cs_num * kappa;
     794           35 :       GNUNET_assert ((1 == kappa) || (cs_num * kappa > cs_num));
     795           35 :       wh->bp_coins =
     796           35 :         GNUNET_new_array (wh->num_bp_coins,
     797              :                           struct BlindingPrepareCoinData);
     798           35 :       wh->num_bp_nonces = cs_num;
     799           35 :       wh->bp_nonces =
     800           35 :         GNUNET_new_array (wh->num_bp_nonces,
     801              :                           union GNUNET_CRYPTO_BlindSessionNonce);
     802           35 :       wh->num_bp_nonce_keys = cs_num;
     803           35 :       wh->bp_nonce_keys =
     804           35 :         GNUNET_new_array (wh->num_bp_nonce_keys,
     805              :                           struct TALER_EXCHANGE_NonceKey);
     806              :     }
     807              : 
     808          156 :     for (uint32_t i = 0; i < wh->num_coins; i++)
     809              :     {
     810           81 :       struct CoinData *cd = &wh->coin_data[i];
     811           81 :       bool age_denom = (0 != denoms_pub[i].key.age_mask.bits);
     812              : 
     813           81 :       cd->denom_pub = denoms_pub[i];
     814              :       /* The age mask must be the same for all coins */
     815           81 :       FAIL_IF (wh->with_age_proof &&
     816              :                (0 ==  denoms_pub[i].key.age_mask.bits));
     817           81 :       FAIL_IF (wh->age_mask.bits !=
     818              :                denoms_pub[i].key.age_mask.bits);
     819           81 :       TALER_denom_pub_copy (&cd->denom_pub.key,
     820           81 :                             &denoms_pub[i].key);
     821              : 
     822              :       /* Mark the indices of the coins which are of type Clause-Schnorr
     823              :        * and add their denomination public key hash to the list.
     824              :        */
     825           81 :       if (GNUNET_CRYPTO_BSA_CS ==
     826           81 :           cd->denom_pub.key.bsign_pub_key->cipher)
     827              :       {
     828           38 :         GNUNET_assert (cs_denom_idx < cs_num);
     829           38 :         cs_indices[cs_denom_idx] = i;
     830           38 :         cs_nonce_keys[cs_denom_idx].cnc_num = i;
     831           38 :         cs_nonce_keys[cs_denom_idx].pk = &cd->denom_pub;
     832           38 :         wh->bp_nonce_keys[cs_denom_idx].cnc_num = i;
     833           38 :         wh->bp_nonce_keys[cs_denom_idx].pk = &cd->denom_pub;
     834           38 :         cs_denom_idx++;
     835              :       }
     836              : 
     837              :       /*
     838              :        * Note that we "loop" here either only once (if with_age_proof is false),
     839              :        * or TALER_CNC_KAPPA times.
     840              :        */
     841          180 :       for (uint8_t k = 0; k < kappa; k++)
     842              :       {
     843           99 :         struct CoinCandidate *can = &cd->candidates[k];
     844           99 :         struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
     845              : 
     846           99 :         can->details.secret = secrets[k][i];
     847              :         /*
     848              :          * The age restriction needs to be set on a coin if the denomination
     849              :          * support age restriction. Note that this is regardless of whether
     850              :          * with_age_proof is set or not.
     851              :          */
     852           99 :         if (age_denom)
     853              :         {
     854              :           /* Derive the age restriction from the given secret and
     855              :            * the maximum age */
     856           37 :           TALER_age_restriction_from_secret (
     857           37 :             &can->details.secret,
     858           37 :             &wh->age_mask,
     859           37 :             wh->max_age,
     860              :             &can->details.age_commitment_proof);
     861              : 
     862           37 :           TALER_age_commitment_hash (
     863           37 :             &can->details.age_commitment_proof.commitment,
     864              :             &can->details.h_age_commitment);
     865              :         }
     866              : 
     867           99 :         switch (cd->denom_pub.key.bsign_pub_key->cipher)
     868              :         {
     869           53 :         case GNUNET_CRYPTO_BSA_RSA:
     870           53 :           TALER_denom_ewv_copy (&can->details.blinding_values,
     871              :                                 TALER_denom_ewv_rsa_singleton ());
     872           53 :           TALER_planchet_setup_coin_priv (&can->details.secret,
     873           53 :                                           &can->details.blinding_values,
     874              :                                           &can->details.coin_priv);
     875           53 :           TALER_planchet_blinding_secret_create (&can->details.secret,
     876           53 :                                                  &can->details.blinding_values,
     877              :                                                  &can->details.blinding_key);
     878           53 :           FAIL_IF (GNUNET_OK !=
     879              :                    TALER_planchet_prepare (&cd->denom_pub.key,
     880              :                                            &can->details.blinding_values,
     881              :                                            &can->details.blinding_key,
     882              :                                            NULL,
     883              :                                            &can->details.coin_priv,
     884              :                                            (age_denom)
     885              :                                            ? &can->details.h_age_commitment
     886              :                                            : NULL,
     887              :                                            &can->details.h_coin_pub,
     888              :                                            planchet));
     889           53 :           TALER_coin_ev_hash (&planchet->blinded_planchet,
     890           53 :                               &planchet->denom_pub_hash,
     891              :                               &can->blinded_coin_h);
     892           53 :           break;
     893              : 
     894           46 :         case GNUNET_CRYPTO_BSA_CS:
     895              :           {
     896              :             /* Prepare the nonce and save the index and the denomination for
     897              :              * the callback after the call to blinding-prepare */
     898           46 :             wh->bp_coins[cs_coin_idx].candidate = can;
     899           46 :             wh->bp_coins[cs_coin_idx].planchet = planchet;
     900           46 :             wh->bp_coins[cs_coin_idx].denom_pub = &cd->denom_pub.key;
     901           46 :             wh->bp_coins[cs_coin_idx].cs_idx = i;
     902           46 :             wh->bp_coins[cs_coin_idx].age_denom = age_denom;
     903           46 :             cs_coin_idx++;
     904           46 :             break;
     905              :           }
     906            0 :         default:
     907            0 :           FAIL_IF (1);
     908              :         }
     909              :       }
     910              :     }
     911              : 
     912           75 :     if (0 < cs_num)
     913              :     {
     914           35 :       if (wh->options.has_blinding_seed)
     915              :       {
     916           32 :         wh->blinding_seed = wh->options.blinding_seed;
     917              :       }
     918              :       else
     919              :       {
     920            3 :         TALER_cs_withdraw_seed_to_blinding_seed (
     921              :           seed,
     922              :           &wh->blinding_seed);
     923              :       }
     924           35 :       wh->has_blinding_seed = true;
     925              : 
     926           35 :       TALER_cs_derive_only_cs_blind_nonces_from_seed (
     927           35 :         &wh->blinding_seed,
     928              :         false, /* not for melt */
     929              :         cs_num,
     930              :         cs_indices,
     931              :         wh->bp_nonces);
     932              :     }
     933              :   }
     934           75 :   return GNUNET_OK;
     935              : 
     936            0 : ERROR:
     937            0 :   if (0 < cs_num)
     938              :   {
     939            0 :     GNUNET_free (wh->bp_nonces);
     940            0 :     GNUNET_free (wh->bp_coins);
     941            0 :     GNUNET_free (wh->bp_nonce_keys);
     942            0 :     wh->num_bp_coins = 0;
     943            0 :     wh->num_bp_nonces = 0;
     944            0 :     wh->num_bp_nonce_keys = 0;
     945              :   }
     946            0 :   return GNUNET_SYSERR;
     947              : #undef FAIL_IF
     948              : }
     949              : 
     950              : 
     951              : struct TALER_EXCHANGE_PostWithdrawHandle *
     952           75 : TALER_EXCHANGE_post_withdraw_create (
     953              :   struct GNUNET_CURL_Context *curl_ctx,
     954              :   const char *exchange_url,
     955              :   struct TALER_EXCHANGE_Keys *keys,
     956              :   const struct TALER_ReservePrivateKeyP *reserve_priv,
     957              :   size_t num_coins,
     958              :   const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins],
     959              :   const struct TALER_WithdrawMasterSeedP *seed,
     960              :   uint8_t opaque_max_age)
     961           75 : {
     962              :   struct TALER_EXCHANGE_PostWithdrawHandle *wh;
     963              : 
     964           75 :   wh = GNUNET_new (struct TALER_EXCHANGE_PostWithdrawHandle);
     965           75 :   wh->exchange_url = exchange_url;
     966           75 :   wh->keys = TALER_EXCHANGE_keys_incref (keys);
     967           75 :   wh->curl_ctx = curl_ctx;
     968           75 :   wh->reserve_priv = reserve_priv;
     969           75 :   wh->seed = *seed;
     970           75 :   wh->max_age = opaque_max_age;
     971           75 :   wh->init_num_coins = num_coins;
     972           75 :   wh->init_denoms_pub = GNUNET_new_array (num_coins,
     973              :                                           struct TALER_EXCHANGE_DenomPublicKey);
     974          156 :   for (size_t i = 0; i < num_coins; i++)
     975              :   {
     976           81 :     wh->init_denoms_pub[i] = denoms_pub[i];
     977           81 :     TALER_denom_pub_copy (&wh->init_denoms_pub[i].key,
     978           81 :                           &denoms_pub[i].key);
     979              :   }
     980              : 
     981           75 :   return wh;
     982              : }
     983              : 
     984              : 
     985              : enum GNUNET_GenericReturnValue
     986           73 : TALER_EXCHANGE_post_withdraw_set_options_ (
     987              :   struct TALER_EXCHANGE_PostWithdrawHandle *pwh,
     988              :   unsigned int num_options,
     989              :   const struct TALER_EXCHANGE_PostWithdrawOptionValue options[])
     990              : {
     991          146 :   for (unsigned int i = 0; i < num_options; i++)
     992              :   {
     993          146 :     const struct TALER_EXCHANGE_PostWithdrawOptionValue *opt = &options[i];
     994          146 :     switch (opt->option)
     995              :     {
     996           73 :     case TALER_EXCHANGE_POST_WITHDRAW_OPTION_END:
     997           73 :       return GNUNET_OK;
     998            5 :     case TALER_EXCHANGE_POST_WITHDRAW_OPTION_WITH_AGE_PROOF:
     999            5 :       pwh->with_age_proof = true;
    1000            5 :       pwh->max_age = opt->details.max_age;
    1001            5 :       break;
    1002           68 :     case TALER_EXCHANGE_POST_WITHDRAW_OPTION_BLINDING_SEED:
    1003           68 :       pwh->options.has_blinding_seed = true;
    1004           68 :       pwh->options.blinding_seed = opt->details.blinding_seed;
    1005           68 :       break;
    1006              :     }
    1007              :   }
    1008            0 :   return GNUNET_OK;
    1009              : }
    1010              : 
    1011              : 
    1012              : enum TALER_ErrorCode
    1013           75 : TALER_EXCHANGE_post_withdraw_start (
    1014              :   struct TALER_EXCHANGE_PostWithdrawHandle *pwh,
    1015              :   TALER_EXCHANGE_PostWithdrawCallback cb,
    1016              :   TALER_EXCHANGE_POST_WITHDRAW_RESULT_CLOSURE *cb_cls)
    1017              : {
    1018           75 :   pwh->callback = cb;
    1019           75 :   pwh->callback_cls = cb_cls;
    1020              : 
    1021              :   /* Run prepare_coins now that options have been applied */
    1022           75 :   if (GNUNET_OK !=
    1023           75 :       prepare_coins (pwh,
    1024              :                      pwh->init_num_coins,
    1025           75 :                      pwh->max_age,
    1026           75 :                      pwh->init_denoms_pub,
    1027           75 :                      &pwh->seed,
    1028           75 :                      pwh->has_blinding_seed
    1029              :                      ? &pwh->blinding_seed
    1030              :                      : NULL))
    1031              :   {
    1032            0 :     GNUNET_free (pwh->coin_data);
    1033            0 :     for (size_t i = 0; i < pwh->init_num_coins; i++)
    1034            0 :       TALER_denom_pub_free (&pwh->init_denoms_pub[i].key);
    1035            0 :     GNUNET_free (pwh->init_denoms_pub);
    1036            0 :     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    1037              :   }
    1038              :   /* Free init data - no longer needed after prepare_coins */
    1039          156 :   for (size_t i = 0; i < pwh->init_num_coins; i++)
    1040           81 :     TALER_denom_pub_free (&pwh->init_denoms_pub[i].key);
    1041           75 :   GNUNET_free (pwh->init_denoms_pub);
    1042              : 
    1043           75 :   if (0 < pwh->num_bp_coins)
    1044              :   {
    1045              :     /* There are CS denominations; start the blinding-prepare request */
    1046           35 :     pwh->blinding_prepare_handle =
    1047           35 :       TALER_EXCHANGE_post_blinding_prepare_for_withdraw_create (
    1048              :         pwh->curl_ctx,
    1049              :         pwh->exchange_url,
    1050              :         &pwh->blinding_seed,
    1051              :         pwh->num_bp_nonce_keys,
    1052              :         pwh->bp_nonce_keys);
    1053           35 :     if (NULL == pwh->blinding_prepare_handle)
    1054            0 :       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    1055              :     {
    1056              :       enum TALER_ErrorCode ec =
    1057           35 :         TALER_EXCHANGE_post_blinding_prepare_start (
    1058              :           pwh->blinding_prepare_handle,
    1059              :           &blinding_prepare_done,
    1060              :           pwh);
    1061           35 :       if (TALER_EC_NONE != ec)
    1062              :       {
    1063            0 :         pwh->blinding_prepare_handle = NULL;
    1064            0 :         return ec;
    1065              :       }
    1066              :     }
    1067           35 :     return TALER_EC_NONE;
    1068              :   }
    1069              : 
    1070              :   /* No CS denominations; proceed directly to the withdraw protocol */
    1071           40 :   return call_withdraw_blinded (pwh);
    1072              : }
    1073              : 
    1074              : 
    1075              : void
    1076           75 : TALER_EXCHANGE_post_withdraw_cancel (
    1077              :   struct TALER_EXCHANGE_PostWithdrawHandle *wh)
    1078              : {
    1079           75 :   uint8_t kappa = wh->with_age_proof ? TALER_CNC_KAPPA : 1;
    1080              : 
    1081              :   /* Cleanup init data if _start was never called (or failed) */
    1082           75 :   if (NULL != wh->init_denoms_pub)
    1083              :   {
    1084            0 :     for (size_t i = 0; i < wh->init_num_coins; i++)
    1085            0 :       TALER_denom_pub_free (&wh->init_denoms_pub[i].key);
    1086            0 :     GNUNET_free (wh->init_denoms_pub);
    1087              :   }
    1088              :   /* Cleanup coin data */
    1089           75 :   if (NULL != wh->coin_data)
    1090              :   {
    1091          156 :     for (unsigned int i = 0; i < wh->num_coins; i++)
    1092              :     {
    1093           81 :       struct CoinData *cd = &wh->coin_data[i];
    1094              : 
    1095          180 :       for (uint8_t k = 0; k < kappa; k++)
    1096              :       {
    1097           99 :         struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
    1098           99 :         struct CoinCandidate *can = &cd->candidates[k];
    1099              : 
    1100           99 :         TALER_blinded_planchet_free (&planchet->blinded_planchet);
    1101           99 :         TALER_denom_ewv_free (&can->details.blinding_values);
    1102           99 :         TALER_age_commitment_proof_free (&can->details.age_commitment_proof);
    1103              :       }
    1104           81 :       TALER_denom_pub_free (&cd->denom_pub.key);
    1105              :     }
    1106              :   }
    1107              : 
    1108           75 :   TALER_EXCHANGE_post_blinding_prepare_cancel (wh->blinding_prepare_handle);
    1109           75 :   wh->blinding_prepare_handle = NULL;
    1110           75 :   TALER_EXCHANGE_post_withdraw_blinded_cancel (wh->withdraw_blinded_handle);
    1111           75 :   wh->withdraw_blinded_handle = NULL;
    1112              : 
    1113           75 :   GNUNET_free (wh->bp_coins);
    1114           75 :   GNUNET_free (wh->bp_nonces);
    1115           75 :   GNUNET_free (wh->bp_nonce_keys);
    1116           75 :   GNUNET_free (wh->coin_data);
    1117           75 :   TALER_EXCHANGE_keys_decref (wh->keys);
    1118           75 :   GNUNET_free (wh);
    1119           75 : }
    1120              : 
    1121              : 
    1122              : /* exchange_api_post-withdraw.c */
        

Generated by: LCOV version 2.0-1