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


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