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 (24625B)


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