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