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


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