exchange

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

testing_api_cmd_age_withdraw.c (24453B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2023-2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it
      6   under the terms of the GNU General Public License as published by
      7   the Free Software Foundation; either version 3, or (at your
      8   option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13   General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not, see
     17   <http://www.gnu.org/licenses/>
     18 */
     19 /**
     20  * @file testing/testing_api_cmd_age_withdraw.c
     21  * @brief implements the withdraw command for age-restricted coins
     22  * @author Özgür Kesim
     23  */
     24 
     25 #include "taler/platform.h"
     26 #include "taler/taler_exchange_service.h"
     27 #include "taler/taler_json_lib.h"
     28 #include <gnunet/gnunet_common.h>
     29 #include <microhttpd.h>
     30 #include <gnunet/gnunet_curl_lib.h>
     31 #include "taler/taler_signatures.h"
     32 #include "taler/taler_extensions.h"
     33 #include "taler/taler_testing_lib.h"
     34 
     35 /*
     36  * The output state of coin
     37  */
     38 struct CoinOutputState
     39 {
     40 
     41   /**
     42    * The calculated details during "withdraw", for the selected coin.
     43    */
     44   struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details;
     45 
     46   /**
     47    * The (wanted) value of the coin, MUST be the same as input.denom_pub.value;
     48    */
     49   struct TALER_Amount amount;
     50 
     51 };
     52 
     53 /**
     54  * State for a "age withdraw" CMD:
     55  */
     56 
     57 struct AgeWithdrawState
     58 {
     59 
     60   /**
     61    * Interpreter state (during command)
     62    */
     63   struct TALER_TESTING_Interpreter *is;
     64 
     65   /**
     66    * The age-withdraw handle
     67    */
     68   struct TALER_EXCHANGE_PostWithdrawHandle *handle;
     69 
     70   /**
     71    * Exchange base URL.  Only used as offered trait.
     72    */
     73   char *exchange_url;
     74 
     75   /**
     76    * URI of the reserve we are withdrawing from.
     77    */
     78   struct TALER_NormalizedPayto reserve_payto_uri;
     79 
     80   /**
     81    * Private key of the reserve we are withdrawing from.
     82    */
     83   struct TALER_ReservePrivateKeyP reserve_priv;
     84 
     85   /**
     86    * Public key of the reserve we are withdrawing from.
     87    */
     88   struct TALER_ReservePublicKeyP reserve_pub;
     89 
     90   /**
     91    * Which reserve should we withdraw from?
     92    */
     93   const char *reserve_reference;
     94 
     95   /**
     96    * Expected HTTP response code to the request.
     97    */
     98   unsigned int expected_response_code;
     99 
    100   /**
    101    * Age mask
    102    */
    103   struct TALER_AgeMask mask;
    104 
    105   /**
    106    * The maximum age we commit to
    107    */
    108   uint8_t max_age;
    109 
    110   /**
    111    * Number of coins to withdraw
    112    */
    113   size_t num_coins;
    114 
    115   /**
    116    * The @e num_coins denomination public keys that are provided
    117    * to the `TALER_EXCHANGE_post_withdraw()` API.
    118    */
    119   struct TALER_EXCHANGE_DenomPublicKey *denoms_pub;
    120 
    121   /**
    122    * The master seed from which all the other seeds are derived from
    123    */
    124   struct TALER_WithdrawMasterSeedP seed;
    125 
    126   /**
    127    * The #TALER_CNC_KAPPA seeds derived from @e seed
    128    */
    129   struct TALER_KappaWithdrawMasterSeedP kappa_seed;
    130 
    131   /**
    132    * The master seed from which all the other seeds are derived from
    133    */
    134   struct TALER_BlindingMasterSeedP blinding_seed;
    135 
    136   /**
    137    * The output state of @e num_coins coins, calculated during the
    138    * "age-withdraw" operation.
    139    */
    140   struct CoinOutputState *coin_outputs;
    141 
    142   /**
    143    * The index returned by the exchange for the "age-withdraw" operation,
    144    * of the kappa coin candidates that we do not disclose and keep.
    145    */
    146   uint8_t noreveal_index;
    147 
    148   /**
    149    * The hash of the commitment, needed for the reveal step.
    150    */
    151   struct TALER_HashBlindedPlanchetsP planchets_h;
    152 
    153   /**
    154      *  The hash of the selected blinded planchets
    155      */
    156   struct TALER_HashBlindedPlanchetsP selected_h;
    157 
    158   /**
    159    * Set to the KYC requirement payto hash *if* the exchange replied with a
    160    * request for KYC.
    161    */
    162   struct TALER_NormalizedPaytoHashP h_payto;
    163 
    164   /**
    165    * Set to the KYC requirement row *if* the exchange replied with
    166    * a request for KYC.
    167    */
    168   uint64_t requirement_row;
    169 
    170   /**
    171    * Reserve history entry that corresponds to this withdraw.
    172    * Will be of type #TALER_EXCHANGE_RTT_WITHDRAWAL.
    173    */
    174   struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
    175 };
    176 
    177 /**
    178  * Callback for the "age-withdraw" operation;  It checks that the response
    179  * code is expected and store the exchange signature in the state.
    180  *
    181  * @param cls Closure of type `struct AgeWithdrawState *`
    182  * @param response Response details
    183  */
    184 static void
    185 age_withdraw_cb (
    186   void *cls,
    187   const struct TALER_EXCHANGE_PostWithdrawResponse *response)
    188 {
    189   struct AgeWithdrawState *aws = cls;
    190   struct TALER_TESTING_Interpreter *is = aws->is;
    191 
    192   aws->handle = NULL;
    193   if (aws->expected_response_code != response->hr.http_status)
    194   {
    195     TALER_TESTING_unexpected_status_with_body (is,
    196                                                response->hr.http_status,
    197                                                aws->expected_response_code,
    198                                                response->hr.reply);
    199     return;
    200   }
    201 
    202   switch (response->hr.http_status)
    203   {
    204   case MHD_HTTP_CREATED:
    205     aws->noreveal_index = response->details.created.noreveal_index;
    206     aws->planchets_h = response->details.created.planchets_h;
    207     aws->selected_h = response->details.created.selected_h;
    208     aws->reserve_history.details.withdraw.planchets_h = aws->planchets_h;
    209     aws->reserve_history.details.withdraw.selected_h = aws->selected_h;
    210     aws->reserve_history.details.withdraw.noreveal_index = aws->noreveal_index;
    211     aws->kappa_seed = response->details.created.kappa_seed;
    212 
    213     GNUNET_assert (aws->num_coins == response->details.created.num_coins);
    214     for (size_t n = 0; n < aws->num_coins; n++)
    215     {
    216       aws->coin_outputs[n].details = response->details.created.coin_details[n];
    217       TALER_age_commitment_proof_deep_copy (
    218         &aws->coin_outputs[n].details.age_commitment_proof,
    219         &response->details.created.coin_details[n].age_commitment_proof);
    220       TALER_denom_ewv_copy (
    221         &aws->coin_outputs[n].details.blinding_values,
    222         &response->details.created.coin_details[n].blinding_values);
    223     }
    224     break;
    225   case MHD_HTTP_FORBIDDEN:
    226   case MHD_HTTP_NOT_FOUND:
    227   case MHD_HTTP_GONE:
    228     /* nothing to check */
    229     break;
    230   case MHD_HTTP_CONFLICT:
    231     /* FIXME[oec]: Add this to the response-type and handle it here */
    232     break;
    233   case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    234   default:
    235     /* Unsupported status code (by test harness) */
    236     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    237                 "test command for age-withdraw not support status code %u, body:\n"
    238                 ">>%s<<\n",
    239                 response->hr.http_status,
    240                 json_dumps (response->hr.reply, JSON_INDENT (2)));
    241     GNUNET_break (0);
    242     break;
    243   }
    244 
    245   /* We are done with this command, pick the next one */
    246   TALER_TESTING_interpreter_next (is);
    247 }
    248 
    249 
    250 /**
    251  * Run the command for age-withdraw.
    252  */
    253 static void
    254 age_withdraw_run (
    255   void *cls,
    256   const struct TALER_TESTING_Command *cmd,
    257   struct TALER_TESTING_Interpreter *is)
    258 {
    259   struct AgeWithdrawState *aws = cls;
    260   struct TALER_EXCHANGE_Keys *keys = TALER_TESTING_get_keys (is);
    261   const struct TALER_ReservePrivateKeyP *rp;
    262   const struct TALER_TESTING_Command *create_reserve;
    263   const struct TALER_EXCHANGE_DenomPublicKey *dpk;
    264 
    265   aws->is = is;
    266 
    267   /* Prepare the reserve related data */
    268   create_reserve
    269     = TALER_TESTING_interpreter_lookup_command (
    270         is,
    271         aws->reserve_reference);
    272 
    273   if (NULL == create_reserve)
    274   {
    275     GNUNET_break (0);
    276     TALER_TESTING_interpreter_fail (is);
    277     return;
    278   }
    279   if (GNUNET_OK !=
    280       TALER_TESTING_get_trait_reserve_priv (create_reserve,
    281                                             &rp))
    282   {
    283     GNUNET_break (0);
    284     TALER_TESTING_interpreter_fail (is);
    285     return;
    286   }
    287   if (NULL == aws->exchange_url)
    288     aws->exchange_url
    289       = GNUNET_strdup (TALER_TESTING_get_exchange_url (is));
    290   aws->reserve_priv = *rp;
    291   GNUNET_CRYPTO_eddsa_key_get_public (&aws->reserve_priv.eddsa_priv,
    292                                       &aws->reserve_pub.eddsa_pub);
    293   aws->reserve_payto_uri
    294     = TALER_reserve_make_payto (aws->exchange_url,
    295                                 &aws->reserve_pub);
    296 
    297   aws->denoms_pub = GNUNET_new_array (aws->num_coins,
    298                                       struct TALER_EXCHANGE_DenomPublicKey);
    299 
    300   GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
    301                               &aws->seed,
    302                               sizeof(aws->seed));
    303 
    304   for (unsigned int i = 0; i<aws->num_coins; i++)
    305   {
    306     struct TALER_EXCHANGE_DenomPublicKey *denom_pub = &aws->denoms_pub[i];
    307     struct CoinOutputState *cos = &aws->coin_outputs[i];
    308 
    309     /* Find denomination */
    310     dpk = TALER_TESTING_find_pk (keys,
    311                                  &cos->amount,
    312                                  true); /* _always_ use denominations with age-striction */
    313     if (NULL == dpk)
    314     {
    315       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    316                   "Failed to determine denomination key for amount at %s\n",
    317                   (NULL != cmd) ? cmd->label : "<retried command>");
    318       GNUNET_break (0);
    319       TALER_TESTING_interpreter_fail (is);
    320       return;
    321     }
    322 
    323     /* We copy the denomination key, as re-querying /keys
    324      * would free the old one. */
    325     *denom_pub = *dpk;
    326     TALER_denom_pub_copy (&denom_pub->key,
    327                           &dpk->key);
    328 
    329     /* Accumulate the expected total amount and fee for the history */
    330     GNUNET_assert (0 <=
    331                    TALER_amount_add (&aws->reserve_history.amount,
    332                                      &cos->amount,
    333                                      &denom_pub->fees.withdraw));
    334     if (i == 0)
    335       GNUNET_assert (GNUNET_OK ==
    336                      TALER_amount_set_zero (
    337                        denom_pub->fees.withdraw.currency,
    338                        &aws->reserve_history.details.withdraw.fee));
    339 
    340     GNUNET_assert (0 <=
    341                    TALER_amount_add (&aws->reserve_history.details.withdraw.fee,
    342                                      &aws->reserve_history.details.withdraw.fee,
    343                                      &denom_pub->fees.withdraw));
    344 
    345   }
    346   /* Save the expected history entry */
    347   aws->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL;
    348   aws->reserve_history.details.withdraw.age_restricted = true;
    349   aws->reserve_history.details.withdraw.max_age = aws->max_age;
    350 
    351 
    352   /* Execute the age-restricted variant of withdraw protocol */
    353   aws->handle =
    354     TALER_EXCHANGE_post_withdraw_create (
    355       TALER_TESTING_interpreter_get_context (is),
    356       TALER_TESTING_get_exchange_url (is),
    357       keys,
    358       rp,
    359       aws->num_coins,
    360       aws->denoms_pub,
    361       &aws->seed,
    362       aws->max_age);
    363   if (NULL == aws->handle)
    364   {
    365     GNUNET_break (0);
    366     TALER_TESTING_interpreter_fail (is);
    367     return;
    368   }
    369   GNUNET_assert (GNUNET_OK ==
    370                  TALER_EXCHANGE_post_withdraw_set_options (
    371                    aws->handle,
    372                    TALER_EXCHANGE_post_withdraw_option_with_age_proof (
    373                      aws->max_age)));
    374   GNUNET_assert (TALER_EC_NONE ==
    375                  TALER_EXCHANGE_post_withdraw_start (aws->handle,
    376                                                      &age_withdraw_cb,
    377                                                      aws));
    378 }
    379 
    380 
    381 /**
    382  * Free the state of a "age withdraw" CMD, and possibly cancel a
    383  * pending operation thereof
    384  *
    385  * @param cls Closure of type `struct AgeWithdrawState`
    386  * @param cmd The command being freed.
    387  */
    388 static void
    389 age_withdraw_cleanup (
    390   void *cls,
    391   const struct TALER_TESTING_Command *cmd)
    392 {
    393   struct AgeWithdrawState *aws = cls;
    394 
    395   if (NULL != aws->handle)
    396   {
    397     TALER_TESTING_command_incomplete (aws->is,
    398                                       cmd->label);
    399     TALER_EXCHANGE_post_withdraw_cancel (aws->handle);
    400     aws->handle = NULL;
    401   }
    402 
    403   if (NULL != aws->denoms_pub)
    404   {
    405     for (size_t n = 0; n < aws->num_coins; n++)
    406       TALER_denom_pub_free (&aws->denoms_pub[n].key);
    407 
    408     GNUNET_free (aws->denoms_pub);
    409     aws->denoms_pub = NULL;
    410   }
    411 
    412   if (NULL != aws->coin_outputs)
    413   {
    414     for (size_t n = 0; n < aws->num_coins; n++)
    415     {
    416       struct CoinOutputState *out = &aws->coin_outputs[n];
    417       TALER_age_commitment_proof_free (&out->details.age_commitment_proof);
    418       TALER_denom_ewv_free (&out->details.blinding_values);
    419     }
    420     GNUNET_free (aws->coin_outputs);
    421     aws->coin_outputs = NULL;
    422   }
    423 
    424   GNUNET_free (aws->exchange_url);
    425   aws->exchange_url  = NULL;
    426   GNUNET_free (aws->reserve_payto_uri.normalized_payto);
    427   aws->reserve_payto_uri.normalized_payto = NULL;
    428   GNUNET_free (aws);
    429 }
    430 
    431 
    432 /**
    433  * Offer internal data of a "age withdraw" CMD state to other commands.
    434  *
    435  * @param cls Closure of type `struct AgeWithdrawState`
    436  * @param[out] ret result (could be anything)
    437  * @param trait name of the trait
    438  * @param idx index number of the object to offer.
    439  * @return #GNUNET_OK on success
    440  */
    441 static enum GNUNET_GenericReturnValue
    442 age_withdraw_traits (
    443   void *cls,
    444   const void **ret,
    445   const char *trait,
    446   unsigned int idx)
    447 {
    448   struct AgeWithdrawState *aws = cls;
    449   struct CoinOutputState *out = &aws->coin_outputs[idx];
    450   struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *details =
    451     &aws->coin_outputs[idx].details;
    452   struct TALER_TESTING_Trait traits[] = {
    453     /* history entry MUST be first due to response code logic below! */
    454     TALER_TESTING_make_trait_reserve_history (idx,
    455                                               &aws->reserve_history),
    456     TALER_TESTING_make_trait_denom_pub (idx,
    457                                         &aws->denoms_pub[idx]),
    458     TALER_TESTING_make_trait_reserve_priv (&aws->reserve_priv),
    459     TALER_TESTING_make_trait_reserve_pub (&aws->reserve_pub),
    460     TALER_TESTING_make_trait_withdraw_commitment (&aws->planchets_h),
    461     TALER_TESTING_make_trait_amounts (idx,
    462                                       &out->amount),
    463     /* FIXME[oec]: add legal requirement to response and handle it here, as well
    464     TALER_TESTING_make_trait_legi_requirement_row (&aws->requirement_row),
    465     TALER_TESTING_make_trait_h_payto (&aws->h_payto),
    466     */
    467     TALER_TESTING_make_trait_normalized_payto_uri (&aws->reserve_payto_uri),
    468     TALER_TESTING_make_trait_exchange_url (aws->exchange_url),
    469     TALER_TESTING_make_trait_coin_priv (idx,
    470                                         &details->coin_priv),
    471     TALER_TESTING_make_trait_withdraw_seed (&aws->seed),
    472     /* FIXME[oec]: needed!?
    473     TALER_TESTING_make_trait_planchet_secrets (idx,
    474                                                &aws->secrets[k][idx]),
    475     */
    476     TALER_TESTING_make_trait_blinding_key (idx,
    477                                            &details->blinding_key),
    478     TALER_TESTING_make_trait_exchange_blinding_values (idx,
    479                                                        &details->blinding_values
    480                                                        ),
    481     TALER_TESTING_make_trait_age_commitment_proof (
    482       idx,
    483       &details->age_commitment_proof),
    484     TALER_TESTING_make_trait_h_age_commitment (
    485       idx,
    486       &details->h_age_commitment),
    487     TALER_TESTING_trait_end ()
    488   };
    489 
    490   if (idx >= aws->num_coins)
    491     return GNUNET_NO;
    492 
    493   return TALER_TESTING_get_trait ((aws->expected_response_code == MHD_HTTP_OK)
    494                                   ? &traits[0] /* we have reserve history */
    495                                   : &traits[1], /* skip reserve history */
    496                                   ret,
    497                                   trait,
    498                                   idx);
    499 }
    500 
    501 
    502 struct TALER_TESTING_Command
    503 TALER_TESTING_cmd_withdraw_with_age_proof (const char *label,
    504                                            const char *reserve_reference,
    505                                            uint8_t max_age,
    506                                            unsigned int
    507                                            expected_response_code,
    508                                            const char *amount,
    509                                            ...)
    510 {
    511   struct AgeWithdrawState *aws;
    512   unsigned int cnt;
    513   va_list ap;
    514 
    515   aws = GNUNET_new (struct AgeWithdrawState);
    516   aws->reserve_reference = reserve_reference;
    517   aws->expected_response_code = expected_response_code;
    518   aws->mask = TALER_extensions_get_age_restriction_mask ();
    519   aws->max_age = TALER_get_lowest_age (&aws->mask,
    520                                        max_age);
    521   cnt = 1;
    522   va_start (ap, amount);
    523   while (NULL != (va_arg (ap, const char *)))
    524     cnt++;
    525   aws->num_coins = cnt;
    526   aws->coin_outputs = GNUNET_new_array (cnt,
    527                                         struct CoinOutputState);
    528   va_end (ap);
    529   va_start (ap, amount);
    530 
    531   for (unsigned int i = 0; i<aws->num_coins; i++)
    532   {
    533     struct CoinOutputState *out = &aws->coin_outputs[i];
    534     if (GNUNET_OK !=
    535         TALER_string_to_amount (amount,
    536                                 &out->amount))
    537     {
    538       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    539                   "Failed to parse amount `%s' at %s\n",
    540                   amount,
    541                   label);
    542       GNUNET_assert (0);
    543     }
    544     /* move on to next vararg! */
    545     amount = va_arg (ap, const char *);
    546   }
    547 
    548   GNUNET_assert (NULL == amount);
    549   va_end (ap);
    550 
    551   {
    552     struct TALER_TESTING_Command cmd = {
    553       .cls = aws,
    554       .label = label,
    555       .run = &age_withdraw_run,
    556       .cleanup = &age_withdraw_cleanup,
    557       .traits = &age_withdraw_traits,
    558     };
    559 
    560     return cmd;
    561   }
    562 }
    563 
    564 
    565 /**
    566  * The state for the age-withdraw-reveal operation
    567  */
    568 struct AgeRevealWithdrawState
    569 {
    570   /**
    571    * The reference to the CMD resembling the previous call to age-withdraw
    572    */
    573   const char *age_withdraw_reference;
    574 
    575   /**
    576    * The state to the previous age-withdraw command
    577    */
    578   const struct AgeWithdrawState *aws;
    579 
    580   /**
    581    * The expected response code from the call to the
    582    * age-withdraw-reveal operation
    583    */
    584   unsigned int expected_response_code;
    585 
    586   /**
    587    * Interpreter state (during command)
    588    */
    589   struct TALER_TESTING_Interpreter *is;
    590 
    591   /**
    592    * The handle to the reveal-operation
    593    */
    594   struct TALER_EXCHANGE_PostRevealWithdrawHandle *handle;
    595 
    596 
    597   /**
    598    * Number of coins, extracted form the age withdraw command
    599    */
    600   size_t num_coins;
    601 
    602   /**
    603    * The signatures of the @e num_coins coins returned
    604    */
    605   struct TALER_DenominationSignature *denom_sigs;
    606 
    607 };
    608 
    609 
    610 /**
    611  * Callback for the reveal response
    612  *
    613  * @param cls Closure of type `struct AgeRevealWithdrawState`
    614  * @param response The response
    615  */
    616 static void
    617 age_reveal_withdraw_cb (
    618   void *cls,
    619   const struct TALER_EXCHANGE_PostRevealWithdrawResponse *response)
    620 {
    621   struct AgeRevealWithdrawState *awrs = cls;
    622   struct TALER_TESTING_Interpreter *is = awrs->is;
    623 
    624   awrs->handle = NULL;
    625   if (awrs->expected_response_code != response->hr.http_status)
    626   {
    627     TALER_TESTING_unexpected_status_with_body (is,
    628                                                response->hr.http_status,
    629                                                awrs->expected_response_code,
    630                                                response->hr.reply);
    631     return;
    632   }
    633   switch (response->hr.http_status)
    634   {
    635   case MHD_HTTP_OK:
    636     {
    637       const struct AgeWithdrawState *aws = awrs->aws;
    638       GNUNET_assert (awrs->num_coins == response->details.ok.num_sigs);
    639       awrs->denom_sigs = GNUNET_new_array (awrs->num_coins,
    640                                            struct TALER_DenominationSignature);
    641       for (size_t n = 0; n < awrs->num_coins; n++)
    642       {
    643         GNUNET_assert (GNUNET_OK ==
    644                        TALER_denom_sig_unblind (
    645                          &awrs->denom_sigs[n],
    646                          &response->details.ok.blinded_denom_sigs[n],
    647                          &aws->coin_outputs[n].details.blinding_key,
    648                          &aws->coin_outputs[n].details.h_coin_pub,
    649                          &aws->coin_outputs[n].details.blinding_values,
    650                          &aws->denoms_pub[n].key));
    651         TALER_denom_sig_free (&awrs->denom_sigs[n]);
    652       }
    653 
    654       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    655                   "age-withdraw reveal success!\n");
    656       GNUNET_free (awrs->denom_sigs);
    657     }
    658     break;
    659   case MHD_HTTP_NOT_FOUND:
    660   case MHD_HTTP_FORBIDDEN:
    661     /* nothing to check */
    662     break;
    663   /* FIXME[oec]: handle more cases !? */
    664   default:
    665     /* Unsupported status code (by test harness) */
    666     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    667                 "Age withdraw reveal test command does not support status code %u\n",
    668                 response->hr.http_status);
    669     GNUNET_break (0);
    670     break;
    671   }
    672 
    673   /* We are done with this command, pick the next one */
    674   TALER_TESTING_interpreter_next (is);
    675 }
    676 
    677 
    678 /**
    679  * Run the command for age-withdraw-reveal
    680  */
    681 static void
    682 age_reveal_withdraw_run (
    683   void *cls,
    684   const struct TALER_TESTING_Command *cmd,
    685   struct TALER_TESTING_Interpreter *is)
    686 {
    687   struct AgeRevealWithdrawState *awrs = cls;
    688   const struct TALER_TESTING_Command *age_withdraw_cmd;
    689   const struct AgeWithdrawState *aws;
    690 
    691   (void) cmd;
    692   awrs->is = is;
    693 
    694   /*
    695    * Get the command and state for the previous call to "age witdraw"
    696    */
    697   age_withdraw_cmd  =
    698     TALER_TESTING_interpreter_lookup_command (is,
    699                                               awrs->age_withdraw_reference);
    700   if (NULL == age_withdraw_cmd)
    701   {
    702     GNUNET_break (0);
    703     TALER_TESTING_interpreter_fail (is);
    704     return;
    705   }
    706   GNUNET_assert (age_withdraw_cmd->run == age_withdraw_run);
    707   aws = age_withdraw_cmd->cls;
    708   awrs->aws = aws;
    709   awrs->num_coins = aws->num_coins;
    710 
    711   {
    712     struct TALER_RevealWithdrawMasterSeedsP revealed_seeds;
    713     size_t j = 0;
    714     for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
    715     {
    716       if (aws->noreveal_index == k)
    717         continue;
    718 
    719       revealed_seeds.tuple[j] = aws->kappa_seed.tuple[k];
    720       j++;
    721     }
    722 
    723     awrs->handle =
    724       TALER_EXCHANGE_post_reveal_withdraw_create (
    725         TALER_TESTING_interpreter_get_context (is),
    726         TALER_TESTING_get_exchange_url (is),
    727         aws->num_coins,
    728         &aws->planchets_h,
    729         &revealed_seeds);
    730     GNUNET_assert (NULL != awrs->handle);
    731     GNUNET_assert (TALER_EC_NONE ==
    732                    TALER_EXCHANGE_post_reveal_withdraw_start (
    733                      awrs->handle,
    734                      &age_reveal_withdraw_cb,
    735                      awrs));
    736   }
    737 }
    738 
    739 
    740 /**
    741  * Free the state of a "age-withdraw-reveal" CMD, and possibly
    742  * cancel a pending operation thereof
    743  *
    744  * @param cls Closure of type `struct AgeRevealWithdrawState`
    745  * @param cmd The command being freed.
    746  */
    747 static void
    748 age_reveal_withdraw_cleanup (
    749   void *cls,
    750   const struct TALER_TESTING_Command *cmd)
    751 {
    752   struct AgeRevealWithdrawState *awrs = cls;
    753 
    754   if (NULL != awrs->handle)
    755   {
    756     TALER_TESTING_command_incomplete (awrs->is,
    757                                       cmd->label);
    758     TALER_EXCHANGE_post_reveal_withdraw_cancel (awrs->handle);
    759     awrs->handle = NULL;
    760   }
    761   GNUNET_free (awrs->denom_sigs);
    762   awrs->denom_sigs = NULL;
    763   GNUNET_free (awrs);
    764 }
    765 
    766 
    767 /**
    768  * Offer internal data of a "age withdraw reveal" CMD state to other commands.
    769  *
    770  * @param cls Closure of they `struct AgeRevealWithdrawState`
    771  * @param[out] ret result (could be anything)
    772  * @param trait name of the trait
    773  * @param idx index number of the object to offer.
    774  * @return #GNUNET_OK on success
    775  */
    776 static enum GNUNET_GenericReturnValue
    777 age_reveal_withdraw_traits (
    778   void *cls,
    779   const void **ret,
    780   const char *trait,
    781   unsigned int idx)
    782 {
    783   struct AgeRevealWithdrawState *awrs = cls;
    784   struct TALER_TESTING_Trait traits[] = {
    785     TALER_TESTING_make_trait_denom_sig (idx,
    786                                         &awrs->denom_sigs[idx]),
    787     /* FIXME: shall we provide the traits from the previous
    788      * call to "age withdraw" as well? */
    789     TALER_TESTING_trait_end ()
    790   };
    791 
    792   if (idx >= awrs->num_coins)
    793     return GNUNET_NO;
    794 
    795   return TALER_TESTING_get_trait (traits,
    796                                   ret,
    797                                   trait,
    798                                   idx);
    799 }
    800 
    801 
    802 struct TALER_TESTING_Command
    803 TALER_TESTING_cmd_withdraw_reveal_age_proof (
    804   const char *label,
    805   const char *age_withdraw_reference,
    806   unsigned int expected_response_code)
    807 {
    808   struct AgeRevealWithdrawState *awrs =
    809     GNUNET_new (struct AgeRevealWithdrawState);
    810 
    811   awrs->age_withdraw_reference = age_withdraw_reference;
    812   awrs->expected_response_code = expected_response_code;
    813   {
    814     struct TALER_TESTING_Command cmd = {
    815       .cls = awrs,
    816       .label = label,
    817       .run = age_reveal_withdraw_run,
    818       .cleanup = age_reveal_withdraw_cleanup,
    819       .traits = age_reveal_withdraw_traits,
    820     };
    821 
    822     return cmd;
    823   }
    824 }
    825 
    826 
    827 /* end of testing_api_cmd_age_withdraw.c */