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


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