exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

exchange_api_post-withdraw_blinded.c (24725B)


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