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-03-10 12:10:57 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              : 
     391            4 :   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
     392            4 :     resp.details.unavailable_for_legal_reasons =
     393              :       wbr->details.unavailable_for_legal_reasons;
     394            4 :     break;
     395              : 
     396            5 :   default:
     397              :     /* nothing to do here, .hr.ec and .hr.hint are all set already from previous response */
     398            5 :     break;
     399              :   }
     400           70 :   if (NULL != wh->callback)
     401              :   {
     402            9 :     wh->callback (
     403              :       wh->callback_cls,
     404              :       &resp);
     405            9 :     wh->callback = NULL;
     406              :   }
     407           70 :   TALER_EXCHANGE_post_withdraw_cancel (wh);
     408           70 : }
     409              : 
     410              : 
     411              : /**
     412              :  * @brief Callback to copy the results from the call to post_withdraw_blinded
     413              :  * in the age-restricted case.
     414              :  *
     415              :  * @param cls struct TALER_EXCHANGE_PostWithdrawHandle
     416              :  * @param wbr The response
     417              :  */
     418              : static void
     419            5 : copy_results_with_age_proof (
     420              :   void *cls,
     421              :   const struct TALER_EXCHANGE_PostWithdrawBlindedResponse *wbr)
     422            5 : {
     423              :   /* The original handle from the top-level call to withdraw */
     424            5 :   struct TALER_EXCHANGE_PostWithdrawHandle *wh = cls;
     425            5 :   uint8_t k =  wbr->details.created.noreveal_index;
     426            5 :   struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details[wh->num_coins];
     427            5 :   struct TALER_EXCHANGE_PostWithdrawResponse resp = {
     428              :     .hr = wbr->hr,
     429              :   };
     430              : 
     431            5 :   wh->withdraw_blinded_handle = NULL;
     432            5 :   switch (wbr->hr.http_status)
     433              :   {
     434            0 :   case MHD_HTTP_OK:
     435              :     /* in the age-restricted case, this should not happen */
     436            0 :     GNUNET_break_op (0);
     437            0 :     break;
     438              : 
     439            0 :   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
     440            0 :     resp.details.unavailable_for_legal_reasons =
     441              :       wbr->details.unavailable_for_legal_reasons;
     442            0 :     break;
     443              : 
     444            3 :   case MHD_HTTP_CREATED:
     445              :     {
     446            3 :       GNUNET_assert (wh->num_coins == wbr->details.created.num_coins);
     447            3 :       resp.details.created = wbr->details.created;
     448            3 :       resp.details.created.coin_details = details;
     449            3 :       resp.details.created.kappa_seed = wh->kappa_seed;
     450            3 :       memset (details,
     451              :               0,
     452              :               sizeof(details));
     453           10 :       for (size_t n = 0; n< wh->num_coins; n++)
     454              :       {
     455            7 :         details[n] = wh->coin_data[n].candidates[k].details;
     456            7 :         details[n].planchet = wh->coin_data[n].planchet_details[k];
     457              :       }
     458            3 :       break;
     459              :     }
     460              : 
     461            2 :   default:
     462            2 :     break;
     463              :   }
     464              : 
     465            5 :   wh->callback (
     466              :     wh->callback_cls,
     467              :     &resp);
     468            5 :   wh->callback = NULL;
     469            5 :   TALER_EXCHANGE_post_withdraw_cancel (wh);
     470            5 : }
     471              : 
     472              : 
     473              : /**
     474              :  * @brief Prepares and starts the actual TALER_EXCHANGE_post_withdraw_blinded
     475              :  * operation once all blinding-prepare steps are done (or immediately if
     476              :  * there are no CS denominations).
     477              :  *
     478              :  * @param wh The withdraw handle
     479              :  * @return #TALER_EC_NONE on success, error code on failure
     480              :  */
     481              : static enum TALER_ErrorCode
     482           75 : call_withdraw_blinded (
     483              :   struct TALER_EXCHANGE_PostWithdrawHandle *wh)
     484              : {
     485              :   enum TALER_ErrorCode ec;
     486              : 
     487           75 :   GNUNET_assert (NULL == wh->blinding_prepare_handle);
     488              : 
     489           75 :   if (! wh->with_age_proof)
     490           70 :   {
     491           70 :     struct TALER_EXCHANGE_WithdrawBlindedCoinInput input[wh->num_coins];
     492              : 
     493           70 :     memset (input,
     494              :             0,
     495              :             sizeof(input));
     496              : 
     497              :     /* Prepare the blinded planchets as input */
     498          142 :     for (size_t n = 0; n < wh->num_coins; n++)
     499              :     {
     500           72 :       input[n].denom_pub =
     501           72 :         &wh->coin_data[n].denom_pub;
     502           72 :       input[n].planchet_details =
     503           72 :         *wh->coin_data[n].planchet_details;
     504              :     }
     505              : 
     506           70 :     wh->withdraw_blinded_handle =
     507           70 :       TALER_EXCHANGE_post_withdraw_blinded_create (
     508              :         wh->curl_ctx,
     509              :         wh->keys,
     510              :         wh->exchange_url,
     511              :         wh->reserve_priv,
     512           70 :         wh->has_blinding_seed ? &wh->blinding_seed : NULL,
     513              :         wh->num_coins,
     514              :         input);
     515           70 :     if (NULL == wh->withdraw_blinded_handle)
     516            0 :       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     517           70 :     ec = TALER_EXCHANGE_post_withdraw_blinded_start (
     518              :       wh->withdraw_blinded_handle,
     519              :       &copy_results,
     520              :       wh);
     521           70 :     if (TALER_EC_NONE != ec)
     522              :     {
     523            0 :       wh->withdraw_blinded_handle = NULL;
     524            0 :       return ec;
     525              :     }
     526              :   }
     527              :   else
     528            5 :   {  /* age restricted case */
     529              :     struct TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput
     530            5 :       ari[wh->num_coins];
     531              : 
     532            5 :     memset (ari,
     533              :             0,
     534              :             sizeof(ari));
     535              : 
     536              :     /* Prepare the blinded planchets as input */
     537           14 :     for (size_t n = 0; n < wh->num_coins; n++)
     538              :     {
     539            9 :       ari[n].denom_pub = &wh->coin_data[n].denom_pub;
     540           36 :       for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
     541           27 :         ari[n].planchet_details[k] =
     542           27 :           wh->coin_data[n].planchet_details[k];
     543              :     }
     544              : 
     545            5 :     wh->withdraw_blinded_handle =
     546            5 :       TALER_EXCHANGE_post_withdraw_blinded_create (
     547              :         wh->curl_ctx,
     548              :         wh->keys,
     549              :         wh->exchange_url,
     550              :         wh->reserve_priv,
     551            5 :         wh->has_blinding_seed ? &wh->blinding_seed : NULL,
     552              :         wh->num_coins,
     553              :         NULL);
     554            5 :     if (NULL == wh->withdraw_blinded_handle)
     555            0 :       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
     556            5 :     TALER_EXCHANGE_post_withdraw_blinded_set_options (
     557              :       wh->withdraw_blinded_handle,
     558              :       TALER_EXCHANGE_post_withdraw_blinded_option_with_age_proof (
     559              :         wh->max_age,
     560              :         ari));
     561            5 :     ec = TALER_EXCHANGE_post_withdraw_blinded_start (
     562              :       wh->withdraw_blinded_handle,
     563              :       &copy_results_with_age_proof,
     564              :       wh);
     565            5 :     if (TALER_EC_NONE != ec)
     566              :     {
     567            0 :       TALER_EXCHANGE_post_withdraw_blinded_cancel (wh->withdraw_blinded_handle);
     568            0 :       wh->withdraw_blinded_handle = NULL;
     569            0 :       return ec;
     570              :     }
     571              :   }
     572           75 :   return TALER_EC_NONE;
     573              : }
     574              : 
     575              : 
     576              : /**
     577              :  * @brief Function called when /blinding-prepare is finished.
     578              :  *
     579              :  * @param cls the `struct TALER_EXCHANGE_PostWithdrawHandle *`
     580              :  * @param bpr replies from the /blinding-prepare request
     581              :  */
     582              : static void
     583           35 : blinding_prepare_done (
     584              :   void *cls,
     585              :   const struct TALER_EXCHANGE_PostBlindingPrepareResponse *bpr)
     586              : {
     587           35 :   struct TALER_EXCHANGE_PostWithdrawHandle *wh = cls;
     588              : 
     589           35 :   wh->blinding_prepare_handle = NULL;
     590           35 :   switch (bpr->hr.http_status)
     591              :   {
     592           35 :   case MHD_HTTP_OK:
     593              :     {
     594           35 :       bool success = false;
     595           35 :       size_t num = bpr->details.ok.num_blinding_values;
     596              : 
     597           35 :       GNUNET_assert (0 != num);
     598           35 :       GNUNET_assert (num == wh->num_bp_nonces);
     599           81 :       for (size_t i = 0; i < wh->num_bp_coins; i++)
     600              :       {
     601           46 :         struct TALER_PlanchetDetail *planchet = wh->bp_coins[i].planchet;
     602           46 :         struct CoinCandidate *can = wh->bp_coins[i].candidate;
     603           46 :         size_t cs_idx = wh->bp_coins[i].cs_idx;
     604              : 
     605           46 :         GNUNET_assert (NULL != can);
     606           46 :         GNUNET_assert (NULL != planchet);
     607           46 :         success = false;
     608              : 
     609              :         /* Complete the initialization of the coin with CS denomination */
     610           46 :         TALER_denom_ewv_copy (
     611              :           &can->details.blinding_values,
     612           46 :           &bpr->details.ok.blinding_values[cs_idx]);
     613              : 
     614           46 :         GNUNET_assert (GNUNET_CRYPTO_BSA_CS ==
     615              :                        can->details.blinding_values.blinding_inputs->cipher);
     616              : 
     617           46 :         TALER_planchet_setup_coin_priv (
     618           46 :           &can->details.secret,
     619           46 :           &can->details.blinding_values,
     620              :           &can->details.coin_priv);
     621              : 
     622           46 :         TALER_planchet_blinding_secret_create (
     623           46 :           &can->details.secret,
     624           46 :           &can->details.blinding_values,
     625              :           &can->details.blinding_key);
     626              : 
     627              :         /* This initializes the 2nd half of the
     628              :            can->planchet_detail.blinded_planchet */
     629           46 :         if (GNUNET_OK !=
     630           46 :             TALER_planchet_prepare (
     631           46 :               wh->bp_coins[i].denom_pub,
     632           46 :               &can->details.blinding_values,
     633           46 :               &can->details.blinding_key,
     634           46 :               &wh->bp_nonces[cs_idx],
     635           46 :               &can->details.coin_priv,
     636           46 :               &can->details.h_age_commitment,
     637              :               &can->details.h_coin_pub,
     638              :               planchet))
     639              :         {
     640            0 :           GNUNET_break (0);
     641            0 :           break;
     642              :         }
     643              : 
     644           46 :         TALER_coin_ev_hash (&planchet->blinded_planchet,
     645           46 :                             &planchet->denom_pub_hash,
     646              :                             &can->blinded_coin_h);
     647           46 :         success = true;
     648              :       }
     649              : 
     650              :       /* /blinding-prepare is done, we can now perform the
     651              :        * actual withdraw operation */
     652           35 :       if (success)
     653              :       {
     654           35 :         enum TALER_ErrorCode ec = call_withdraw_blinded (wh);
     655              : 
     656           35 :         if (TALER_EC_NONE != ec)
     657              :         {
     658            0 :           struct TALER_EXCHANGE_PostWithdrawResponse resp = {
     659              :             .hr.ec = ec,
     660              :             .hr.http_status = 0,
     661              :           };
     662              : 
     663            0 :           wh->callback (
     664              :             wh->callback_cls,
     665              :             &resp);
     666            0 :           wh->callback = NULL;
     667            0 :           TALER_EXCHANGE_post_withdraw_cancel (wh);
     668              :         }
     669           35 :         return;
     670              :       }
     671              :       else
     672              :       {
     673              :         /* prepare completed but coin setup failed */
     674            0 :         struct TALER_EXCHANGE_PostWithdrawResponse resp = {
     675              :           .hr.ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     676              :           .hr.http_status = 0,
     677              :         };
     678              : 
     679            0 :         wh->callback (
     680              :           wh->callback_cls,
     681              :           &resp);
     682            0 :         wh->callback = NULL;
     683            0 :         TALER_EXCHANGE_post_withdraw_cancel (wh);
     684            0 :         return;
     685              :       }
     686              :     }
     687            0 :   default:
     688              :     {
     689              :       /* We got an error condition during blinding prepare that we need to report */
     690            0 :       struct TALER_EXCHANGE_PostWithdrawResponse resp = {
     691              :         .hr = bpr->hr
     692              :       };
     693              : 
     694            0 :       wh->callback (
     695              :         wh->callback_cls,
     696              :         &resp);
     697            0 :       wh->callback = NULL;
     698            0 :       break;
     699              :     }
     700              :   }
     701            0 :   TALER_EXCHANGE_post_withdraw_cancel (wh);
     702              : }
     703              : 
     704              : 
     705              : /**
     706              :  * @brief Prepares coins for the call to withdraw:
     707              :  * Performs synchronous crypto for RSA denominations, and stores
     708              :  * the data needed for the async /blinding-prepare step for CS denominations.
     709              :  * Does NOT start any async operations.
     710              :  *
     711              :  * @param wh The handler to the withdraw
     712              :  * @param num_coins Number of coins to withdraw
     713              :  * @param max_age The maximum age to commit to
     714              :  * @param denoms_pub Array @e num_coins of denominations
     715              :  * @param seed master seed from which to derive @e num_coins secrets
     716              :  * @param blinding_seed master seed for the blinding. Might be NULL, in which
     717              :  *        case the blinding_seed is derived from @e seed
     718              :  * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
     719              :  */
     720              : static enum GNUNET_GenericReturnValue
     721           75 : prepare_coins (
     722              :   struct TALER_EXCHANGE_PostWithdrawHandle *wh,
     723              :   size_t num_coins,
     724              :   uint8_t max_age,
     725              :   const struct TALER_EXCHANGE_DenomPublicKey *denoms_pub,
     726              :   const struct TALER_WithdrawMasterSeedP *seed,
     727              :   const struct TALER_BlindingMasterSeedP *blinding_seed)
     728              : {
     729           75 :   size_t cs_num = 0;
     730              :   uint8_t kappa;
     731              : 
     732              : #define FAIL_IF(cond) \
     733              :         do \
     734              :         { \
     735              :           if ((cond)) \
     736              :           { \
     737              :             GNUNET_break (! (cond)); \
     738              :             goto ERROR; \
     739              :           } \
     740              :         } while (0)
     741              : 
     742           75 :   GNUNET_assert (0 < num_coins);
     743              : 
     744           75 :   wh->num_coins = num_coins;
     745           75 :   wh->max_age = max_age;
     746           75 :   wh->age_mask = denoms_pub[0].key.age_mask;
     747           75 :   wh->coin_data = GNUNET_new_array (
     748              :     wh->num_coins,
     749              :     struct CoinData);
     750              : 
     751              :   /* First, figure out how many Clause-Schnorr denominations we have */
     752          156 :   for (size_t i =0; i< wh->num_coins; i++)
     753              :   {
     754           81 :     if (GNUNET_CRYPTO_BSA_CS ==
     755           81 :         denoms_pub[i].key.bsign_pub_key->cipher)
     756           38 :       cs_num++;
     757              :   }
     758              : 
     759           75 :   if (wh->with_age_proof)
     760            5 :     kappa = TALER_CNC_KAPPA;
     761              :   else
     762           70 :     kappa = 1;
     763              : 
     764           75 :   {
     765           75 :     struct TALER_PlanchetMasterSecretP secrets[kappa][num_coins];
     766           75 :     struct TALER_EXCHANGE_NonceKey cs_nonce_keys[GNUNET_NZL (cs_num)];
     767           75 :     uint32_t cs_indices[GNUNET_NZL (cs_num)];
     768              : 
     769           75 :     size_t cs_denom_idx = 0;
     770           75 :     size_t cs_coin_idx = 0;
     771              : 
     772           75 :     if (wh->with_age_proof)
     773              :     {
     774            5 :       TALER_withdraw_expand_kappa_seed (seed,
     775              :                                         &wh->kappa_seed);
     776           20 :       for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
     777              :       {
     778           15 :         TALER_withdraw_expand_secrets (
     779              :           num_coins,
     780           15 :           &wh->kappa_seed.tuple[k],
     781           15 :           secrets[k]);
     782              :       }
     783              :     }
     784              :     else
     785              :     {
     786           70 :       TALER_withdraw_expand_secrets (
     787              :         num_coins,
     788              :         seed,
     789           70 :         secrets[0]);
     790              :     }
     791              : 
     792           75 :     if (0 < cs_num)
     793              :     {
     794           35 :       memset (cs_nonce_keys,
     795              :               0,
     796              :               sizeof(cs_nonce_keys));
     797           35 :       wh->num_bp_coins = cs_num * kappa;
     798           35 :       GNUNET_assert ((1 == kappa) || (cs_num * kappa > cs_num));
     799           35 :       wh->bp_coins =
     800           35 :         GNUNET_new_array (wh->num_bp_coins,
     801              :                           struct BlindingPrepareCoinData);
     802           35 :       wh->num_bp_nonces = cs_num;
     803           35 :       wh->bp_nonces =
     804           35 :         GNUNET_new_array (wh->num_bp_nonces,
     805              :                           union GNUNET_CRYPTO_BlindSessionNonce);
     806           35 :       wh->num_bp_nonce_keys = cs_num;
     807           35 :       wh->bp_nonce_keys =
     808           35 :         GNUNET_new_array (wh->num_bp_nonce_keys,
     809              :                           struct TALER_EXCHANGE_NonceKey);
     810              :     }
     811              : 
     812          156 :     for (uint32_t i = 0; i < wh->num_coins; i++)
     813              :     {
     814           81 :       struct CoinData *cd = &wh->coin_data[i];
     815           81 :       bool age_denom = (0 != denoms_pub[i].key.age_mask.bits);
     816              : 
     817           81 :       cd->denom_pub = denoms_pub[i];
     818              :       /* The age mask must be the same for all coins */
     819           81 :       FAIL_IF (wh->with_age_proof &&
     820              :                (0 ==  denoms_pub[i].key.age_mask.bits));
     821           81 :       FAIL_IF (wh->age_mask.bits !=
     822              :                denoms_pub[i].key.age_mask.bits);
     823           81 :       TALER_denom_pub_copy (&cd->denom_pub.key,
     824           81 :                             &denoms_pub[i].key);
     825              : 
     826              :       /* Mark the indices of the coins which are of type Clause-Schnorr
     827              :        * and add their denomination public key hash to the list.
     828              :        */
     829           81 :       if (GNUNET_CRYPTO_BSA_CS ==
     830           81 :           cd->denom_pub.key.bsign_pub_key->cipher)
     831              :       {
     832           38 :         GNUNET_assert (cs_denom_idx < cs_num);
     833           38 :         cs_indices[cs_denom_idx] = i;
     834           38 :         cs_nonce_keys[cs_denom_idx].cnc_num = i;
     835           38 :         cs_nonce_keys[cs_denom_idx].pk = &cd->denom_pub;
     836           38 :         wh->bp_nonce_keys[cs_denom_idx].cnc_num = i;
     837           38 :         wh->bp_nonce_keys[cs_denom_idx].pk = &cd->denom_pub;
     838           38 :         cs_denom_idx++;
     839              :       }
     840              : 
     841              :       /*
     842              :        * Note that we "loop" here either only once (if with_age_proof is false),
     843              :        * or TALER_CNC_KAPPA times.
     844              :        */
     845          180 :       for (uint8_t k = 0; k < kappa; k++)
     846              :       {
     847           99 :         struct CoinCandidate *can = &cd->candidates[k];
     848           99 :         struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
     849              : 
     850           99 :         can->details.secret = secrets[k][i];
     851              :         /*
     852              :          * The age restriction needs to be set on a coin if the denomination
     853              :          * support age restriction. Note that this is regardless of whether
     854              :          * with_age_proof is set or not.
     855              :          */
     856           99 :         if (age_denom)
     857              :         {
     858              :           /* Derive the age restriction from the given secret and
     859              :            * the maximum age */
     860           37 :           TALER_age_restriction_from_secret (
     861           37 :             &can->details.secret,
     862           37 :             &wh->age_mask,
     863           37 :             wh->max_age,
     864              :             &can->details.age_commitment_proof);
     865              : 
     866           37 :           TALER_age_commitment_hash (
     867           37 :             &can->details.age_commitment_proof.commitment,
     868              :             &can->details.h_age_commitment);
     869              :         }
     870              : 
     871           99 :         switch (cd->denom_pub.key.bsign_pub_key->cipher)
     872              :         {
     873           53 :         case GNUNET_CRYPTO_BSA_RSA:
     874           53 :           TALER_denom_ewv_copy (&can->details.blinding_values,
     875              :                                 TALER_denom_ewv_rsa_singleton ());
     876           53 :           TALER_planchet_setup_coin_priv (&can->details.secret,
     877           53 :                                           &can->details.blinding_values,
     878              :                                           &can->details.coin_priv);
     879           53 :           TALER_planchet_blinding_secret_create (&can->details.secret,
     880           53 :                                                  &can->details.blinding_values,
     881              :                                                  &can->details.blinding_key);
     882           53 :           FAIL_IF (GNUNET_OK !=
     883              :                    TALER_planchet_prepare (&cd->denom_pub.key,
     884              :                                            &can->details.blinding_values,
     885              :                                            &can->details.blinding_key,
     886              :                                            NULL,
     887              :                                            &can->details.coin_priv,
     888              :                                            (age_denom)
     889              :                                            ? &can->details.h_age_commitment
     890              :                                            : NULL,
     891              :                                            &can->details.h_coin_pub,
     892              :                                            planchet));
     893           53 :           TALER_coin_ev_hash (&planchet->blinded_planchet,
     894           53 :                               &planchet->denom_pub_hash,
     895              :                               &can->blinded_coin_h);
     896           53 :           break;
     897              : 
     898           46 :         case GNUNET_CRYPTO_BSA_CS:
     899              :           {
     900              :             /* Prepare the nonce and save the index and the denomination for
     901              :              * the callback after the call to blinding-prepare */
     902           46 :             wh->bp_coins[cs_coin_idx].candidate = can;
     903           46 :             wh->bp_coins[cs_coin_idx].planchet = planchet;
     904           46 :             wh->bp_coins[cs_coin_idx].denom_pub = &cd->denom_pub.key;
     905           46 :             wh->bp_coins[cs_coin_idx].cs_idx = i;
     906           46 :             wh->bp_coins[cs_coin_idx].age_denom = age_denom;
     907           46 :             cs_coin_idx++;
     908           46 :             break;
     909              :           }
     910            0 :         default:
     911            0 :           FAIL_IF (1);
     912              :         }
     913              :       }
     914              :     }
     915              : 
     916           75 :     if (0 < cs_num)
     917              :     {
     918           35 :       if (wh->options.has_blinding_seed)
     919              :       {
     920           32 :         wh->blinding_seed = wh->options.blinding_seed;
     921              :       }
     922              :       else
     923              :       {
     924            3 :         TALER_cs_withdraw_seed_to_blinding_seed (
     925              :           seed,
     926              :           &wh->blinding_seed);
     927              :       }
     928           35 :       wh->has_blinding_seed = true;
     929              : 
     930           35 :       TALER_cs_derive_only_cs_blind_nonces_from_seed (
     931           35 :         &wh->blinding_seed,
     932              :         false, /* not for melt */
     933              :         cs_num,
     934              :         cs_indices,
     935              :         wh->bp_nonces);
     936              :     }
     937              :   }
     938           75 :   return GNUNET_OK;
     939              : 
     940            0 : ERROR:
     941            0 :   if (0 < cs_num)
     942              :   {
     943            0 :     GNUNET_free (wh->bp_nonces);
     944            0 :     GNUNET_free (wh->bp_coins);
     945            0 :     GNUNET_free (wh->bp_nonce_keys);
     946            0 :     wh->num_bp_coins = 0;
     947            0 :     wh->num_bp_nonces = 0;
     948            0 :     wh->num_bp_nonce_keys = 0;
     949              :   }
     950            0 :   return GNUNET_SYSERR;
     951              : #undef FAIL_IF
     952              : }
     953              : 
     954              : 
     955              : struct TALER_EXCHANGE_PostWithdrawHandle *
     956           75 : TALER_EXCHANGE_post_withdraw_create (
     957              :   struct GNUNET_CURL_Context *curl_ctx,
     958              :   const char *exchange_url,
     959              :   struct TALER_EXCHANGE_Keys *keys,
     960              :   const struct TALER_ReservePrivateKeyP *reserve_priv,
     961              :   size_t num_coins,
     962              :   const struct TALER_EXCHANGE_DenomPublicKey denoms_pub[static num_coins],
     963              :   const struct TALER_WithdrawMasterSeedP *seed,
     964              :   uint8_t opaque_max_age)
     965           75 : {
     966              :   struct TALER_EXCHANGE_PostWithdrawHandle *wh;
     967              : 
     968           75 :   wh = GNUNET_new (struct TALER_EXCHANGE_PostWithdrawHandle);
     969           75 :   wh->exchange_url = exchange_url;
     970           75 :   wh->keys = TALER_EXCHANGE_keys_incref (keys);
     971           75 :   wh->curl_ctx = curl_ctx;
     972           75 :   wh->reserve_priv = reserve_priv;
     973           75 :   wh->seed = *seed;
     974           75 :   wh->max_age = opaque_max_age;
     975           75 :   wh->init_num_coins = num_coins;
     976           75 :   wh->init_denoms_pub = GNUNET_new_array (num_coins,
     977              :                                           struct TALER_EXCHANGE_DenomPublicKey);
     978          156 :   for (size_t i = 0; i < num_coins; i++)
     979              :   {
     980           81 :     wh->init_denoms_pub[i] = denoms_pub[i];
     981           81 :     TALER_denom_pub_copy (&wh->init_denoms_pub[i].key,
     982           81 :                           &denoms_pub[i].key);
     983              :   }
     984              : 
     985           75 :   return wh;
     986              : }
     987              : 
     988              : 
     989              : enum GNUNET_GenericReturnValue
     990           73 : TALER_EXCHANGE_post_withdraw_set_options_ (
     991              :   struct TALER_EXCHANGE_PostWithdrawHandle *pwh,
     992              :   unsigned int num_options,
     993              :   const struct TALER_EXCHANGE_PostWithdrawOptionValue options[])
     994              : {
     995          146 :   for (unsigned int i = 0; i < num_options; i++)
     996              :   {
     997          146 :     const struct TALER_EXCHANGE_PostWithdrawOptionValue *opt = &options[i];
     998          146 :     switch (opt->option)
     999              :     {
    1000           73 :     case TALER_EXCHANGE_POST_WITHDRAW_OPTION_END:
    1001           73 :       return GNUNET_OK;
    1002            5 :     case TALER_EXCHANGE_POST_WITHDRAW_OPTION_WITH_AGE_PROOF:
    1003            5 :       pwh->with_age_proof = true;
    1004            5 :       pwh->max_age = opt->details.max_age;
    1005            5 :       break;
    1006           68 :     case TALER_EXCHANGE_POST_WITHDRAW_OPTION_BLINDING_SEED:
    1007           68 :       pwh->options.has_blinding_seed = true;
    1008           68 :       pwh->options.blinding_seed = opt->details.blinding_seed;
    1009           68 :       break;
    1010              :     }
    1011              :   }
    1012            0 :   return GNUNET_OK;
    1013              : }
    1014              : 
    1015              : 
    1016              : enum TALER_ErrorCode
    1017           75 : TALER_EXCHANGE_post_withdraw_start (
    1018              :   struct TALER_EXCHANGE_PostWithdrawHandle *pwh,
    1019              :   TALER_EXCHANGE_PostWithdrawCallback cb,
    1020              :   TALER_EXCHANGE_POST_WITHDRAW_RESULT_CLOSURE *cb_cls)
    1021              : {
    1022           75 :   pwh->callback = cb;
    1023           75 :   pwh->callback_cls = cb_cls;
    1024              : 
    1025              :   /* Run prepare_coins now that options have been applied */
    1026           75 :   if (GNUNET_OK !=
    1027           75 :       prepare_coins (pwh,
    1028              :                      pwh->init_num_coins,
    1029           75 :                      pwh->max_age,
    1030           75 :                      pwh->init_denoms_pub,
    1031           75 :                      &pwh->seed,
    1032           75 :                      pwh->has_blinding_seed
    1033              :                      ? &pwh->blinding_seed
    1034              :                      : NULL))
    1035              :   {
    1036            0 :     GNUNET_free (pwh->coin_data);
    1037            0 :     for (size_t i = 0; i < pwh->init_num_coins; i++)
    1038            0 :       TALER_denom_pub_free (&pwh->init_denoms_pub[i].key);
    1039            0 :     GNUNET_free (pwh->init_denoms_pub);
    1040            0 :     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    1041              :   }
    1042              :   /* Free init data - no longer needed after prepare_coins */
    1043          156 :   for (size_t i = 0; i < pwh->init_num_coins; i++)
    1044           81 :     TALER_denom_pub_free (&pwh->init_denoms_pub[i].key);
    1045           75 :   GNUNET_free (pwh->init_denoms_pub);
    1046              : 
    1047           75 :   if (0 < pwh->num_bp_coins)
    1048              :   {
    1049              :     /* There are CS denominations; start the blinding-prepare request */
    1050           35 :     pwh->blinding_prepare_handle =
    1051           35 :       TALER_EXCHANGE_post_blinding_prepare_for_withdraw_create (
    1052              :         pwh->curl_ctx,
    1053              :         pwh->exchange_url,
    1054              :         &pwh->blinding_seed,
    1055              :         pwh->num_bp_nonce_keys,
    1056              :         pwh->bp_nonce_keys);
    1057           35 :     if (NULL == pwh->blinding_prepare_handle)
    1058            0 :       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    1059              :     {
    1060              :       enum TALER_ErrorCode ec =
    1061           35 :         TALER_EXCHANGE_post_blinding_prepare_start (
    1062              :           pwh->blinding_prepare_handle,
    1063              :           &blinding_prepare_done,
    1064              :           pwh);
    1065           35 :       if (TALER_EC_NONE != ec)
    1066              :       {
    1067            0 :         pwh->blinding_prepare_handle = NULL;
    1068            0 :         return ec;
    1069              :       }
    1070              :     }
    1071           35 :     return TALER_EC_NONE;
    1072              :   }
    1073              : 
    1074              :   /* No CS denominations; proceed directly to the withdraw protocol */
    1075           40 :   return call_withdraw_blinded (pwh);
    1076              : }
    1077              : 
    1078              : 
    1079              : void
    1080           75 : TALER_EXCHANGE_post_withdraw_cancel (
    1081              :   struct TALER_EXCHANGE_PostWithdrawHandle *wh)
    1082              : {
    1083           75 :   uint8_t kappa = wh->with_age_proof ? TALER_CNC_KAPPA : 1;
    1084              : 
    1085              :   /* Cleanup init data if _start was never called (or failed) */
    1086           75 :   if (NULL != wh->init_denoms_pub)
    1087              :   {
    1088            0 :     for (size_t i = 0; i < wh->init_num_coins; i++)
    1089            0 :       TALER_denom_pub_free (&wh->init_denoms_pub[i].key);
    1090            0 :     GNUNET_free (wh->init_denoms_pub);
    1091              :   }
    1092              :   /* Cleanup coin data */
    1093           75 :   if (NULL != wh->coin_data)
    1094              :   {
    1095          156 :     for (unsigned int i = 0; i < wh->num_coins; i++)
    1096              :     {
    1097           81 :       struct CoinData *cd = &wh->coin_data[i];
    1098              : 
    1099          180 :       for (uint8_t k = 0; k < kappa; k++)
    1100              :       {
    1101           99 :         struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
    1102           99 :         struct CoinCandidate *can = &cd->candidates[k];
    1103              : 
    1104           99 :         TALER_blinded_planchet_free (&planchet->blinded_planchet);
    1105           99 :         TALER_denom_ewv_free (&can->details.blinding_values);
    1106           99 :         TALER_age_commitment_proof_free (&can->details.age_commitment_proof);
    1107              :       }
    1108           81 :       TALER_denom_pub_free (&cd->denom_pub.key);
    1109              :     }
    1110              :   }
    1111              : 
    1112           75 :   TALER_EXCHANGE_post_blinding_prepare_cancel (wh->blinding_prepare_handle);
    1113           75 :   wh->blinding_prepare_handle = NULL;
    1114           75 :   TALER_EXCHANGE_post_withdraw_blinded_cancel (wh->withdraw_blinded_handle);
    1115           75 :   wh->withdraw_blinded_handle = NULL;
    1116              : 
    1117           75 :   GNUNET_free (wh->bp_coins);
    1118           75 :   GNUNET_free (wh->bp_nonces);
    1119           75 :   GNUNET_free (wh->bp_nonce_keys);
    1120           75 :   GNUNET_free (wh->coin_data);
    1121           75 :   TALER_EXCHANGE_keys_decref (wh->keys);
    1122           75 :   GNUNET_free (wh);
    1123           75 : }
    1124              : 
    1125              : 
    1126              : /* exchange_api_post-withdraw.c */
        

Generated by: LCOV version 2.0-1