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


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