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


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