merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

testing_api_cmd_fountain_withdraw.c (18673B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU General Public License as
      7   published by the Free Software Foundation; either version 3, or
      8   (at your 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
     13   GNU 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 /**
     21  * @file src/testing/testing_api_cmd_fountain_withdraw.c
     22  * @brief command simulating a wallet withdrawing tokens from a
     23  *        fountain (DD 98): fetches GET /fountain/info, prepares
     24  *        blinded envelopes, runs POST /fountain/withdraw and
     25  *        unblinds and verifies the resulting tokens
     26  * @author Bohdan Potuzhnyi
     27  */
     28 #include "platform.h"
     29 struct FountainWithdrawState;
     30 #define TALER_MERCHANT_GET_FOUNTAIN_INFO_RESULT_CLOSURE \
     31   struct FountainWithdrawState
     32 #define TALER_MERCHANT_POST_FOUNTAIN_WITHDRAW_RESULT_CLOSURE \
     33   struct FountainWithdrawState
     34 #include <gnunet/gnunet_time_lib.h>
     35 #include <taler/taler_exchange_service.h>
     36 #include <taler/taler_testing_lib.h>
     37 #include "taler/taler_merchant_service.h"
     38 #include "taler/taler_merchant_testing_lib.h"
     39 #include <taler/merchant/get-fountain-info.h>
     40 #include <taler/merchant/post-fountain-withdraw.h>
     41 
     42 
     43 /**
     44  * Client-side state of one token being withdrawn.
     45  */
     46 struct FountainToken
     47 {
     48 
     49   /**
     50    * Master secret used to derive the private key from.
     51    */
     52   struct TALER_TokenUseMasterSecretP master;
     53 
     54   /**
     55    * Private key of the token.
     56    */
     57   struct TALER_TokenUsePrivateKeyP token_priv;
     58 
     59   /**
     60    * Public key of the token.
     61    */
     62   struct TALER_TokenUsePublicKeyP token_pub;
     63 
     64   /**
     65    * Hash of the public key of the token.
     66    */
     67   struct TALER_TokenUsePublicKeyHashP h_token_pub;
     68 
     69   /**
     70    * Blinded public key of the token.
     71    */
     72   struct TALER_TokenEnvelope envelope;
     73 
     74   /**
     75    * Value used to blind the key for the signature.
     76    */
     77   union GNUNET_CRYPTO_BlindingSecretP blinding_secret;
     78 
     79   /**
     80    * Inputs needed from the merchant for blind signing.
     81    */
     82   struct TALER_TokenUseMerchantValues blinding_inputs;
     83 
     84   /**
     85    * Token issue public key.
     86    */
     87   struct TALER_TokenIssuePublicKey issue_pub;
     88 
     89   /**
     90    * Unblinded token issue signature made by the merchant.
     91    */
     92   struct TALER_TokenIssueSignature issue_sig;
     93 
     94 };
     95 
     96 
     97 /**
     98  * State of a fountain withdraw CMD.
     99  */
    100 struct FountainWithdrawState
    101 {
    102 
    103   /**
    104    * Expected status code of the withdraw request.
    105    */
    106   unsigned int http_status;
    107 
    108   /**
    109    * Handle for the "GET /fountain/info" request.
    110    */
    111   struct TALER_MERCHANT_GetFountainInfoHandle *info_handle;
    112 
    113   /**
    114    * Handle for the "POST /fountain/withdraw" request.
    115    */
    116   struct TALER_MERCHANT_PostFountainWithdrawHandle *withdraw_handle;
    117 
    118   /**
    119    * The interpreter state.
    120    */
    121   struct TALER_TESTING_Interpreter *is;
    122 
    123   /**
    124    * Base URL of the merchant serving the request.
    125    */
    126   const char *merchant_url;
    127 
    128   /**
    129    * Label of the command holding the fountain secret.
    130    */
    131   const char *fountain_reference;
    132 
    133   /**
    134    * Slug of the token family to withdraw from.
    135    */
    136   const char *token_family_slug;
    137 
    138   /**
    139    * Desired token validity time; zero for "now".
    140    */
    141   struct GNUNET_TIME_Timestamp valid_at;
    142 
    143   /**
    144    * The fountain's bearer credential (from the referenced command).
    145    */
    146   const char *fountain_secret;
    147 
    148   /**
    149    * Tokens being withdrawn.
    150    */
    151   struct FountainToken *tokens;
    152 
    153   /**
    154    * Number of tokens in @e tokens.
    155    */
    156   unsigned int num_tokens;
    157 
    158   /**
    159    * First successful response, retained to check an identical retry.
    160    */
    161   json_t *first_reply;
    162 };
    163 
    164 
    165 /**
    166  * Submit the prepared envelopes, including when replaying a request.
    167  *
    168  * @param state our command state
    169  */
    170 static void
    171 start_withdraw (struct FountainWithdrawState *state);
    172 
    173 
    174 /**
    175  * Callback for the POST /fountain/withdraw operation: unblinds and
    176  * verifies the tokens on success.
    177  *
    178  * @param state our command state
    179  * @param fwr response being processed
    180  */
    181 static void
    182 withdraw_cb (struct FountainWithdrawState *state,
    183              const struct TALER_MERCHANT_PostFountainWithdrawResponse *fwr)
    184 {
    185   state->withdraw_handle = NULL;
    186   if (state->http_status != fwr->hr.http_status)
    187   {
    188     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    189                 "Unexpected response code %u (%d) to command %s\n",
    190                 fwr->hr.http_status,
    191                 (int) fwr->hr.ec,
    192                 TALER_TESTING_interpreter_get_current_label (state->is));
    193     TALER_TESTING_interpreter_fail (state->is);
    194     return;
    195   }
    196   if (MHD_HTTP_OK == fwr->hr.http_status)
    197   {
    198     const struct TALER_MERCHANT_FountainWithdrawResult *res;
    199 
    200     if (NULL == state->first_reply)
    201     {
    202       state->first_reply = json_incref ((json_t *) fwr->hr.reply);
    203       start_withdraw (state);
    204       return;
    205     }
    206     if (! json_equal (state->first_reply,
    207                       fwr->hr.reply))
    208     {
    209       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    210                   "Fountain retry returned different signatures\n");
    211       TALER_TESTING_interpreter_fail (state->is);
    212       return;
    213     }
    214     if (1 != fwr->details.ok.results_len)
    215     {
    216       GNUNET_break (0);
    217       TALER_TESTING_interpreter_fail (state->is);
    218       return;
    219     }
    220     res = &fwr->details.ok.results[0];
    221     /* The reported issue key must be the one we blinded against;
    222        otherwise the signatures cannot verify. */
    223     if (0 != GNUNET_memcmp (&res->h_issue.hash,
    224                             &state->tokens[0].issue_pub.public_key->
    225                             pub_key_hash))
    226     {
    227       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    228                   "Merchant signed with issue key %s, but we blinded against %s\n",
    229                   GNUNET_h2s (&res->h_issue.hash),
    230                   GNUNET_h2s2 (&state->tokens[0].issue_pub.public_key->
    231                                pub_key_hash));
    232       GNUNET_break (0);
    233       TALER_TESTING_interpreter_fail (state->is);
    234       return;
    235     }
    236     if (res->num_sigs != state->num_tokens)
    237     {
    238       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    239                   "Sent %u envelopes but got %u blind signatures\n",
    240                   state->num_tokens,
    241                   res->num_sigs);
    242       TALER_TESTING_interpreter_fail (state->is);
    243       return;
    244     }
    245     for (unsigned int i = 0; i < state->num_tokens; i++)
    246     {
    247       struct FountainToken *token = &state->tokens[i];
    248 
    249       /* The signatures are in the same order as the envelopes. */
    250       if (GNUNET_OK !=
    251           TALER_token_issue_sig_unblind (&token->issue_sig,
    252                                          &res->token_sigs[i],
    253                                          &token->blinding_secret,
    254                                          &token->h_token_pub,
    255                                          &token->blinding_inputs,
    256                                          &token->issue_pub))
    257       {
    258         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    259                     "Failed to unblind token signature\n");
    260         GNUNET_break (0);
    261         TALER_TESTING_interpreter_fail (state->is);
    262         return;
    263       }
    264       if (GNUNET_OK !=
    265           TALER_token_issue_verify (&token->token_pub,
    266                                     &token->issue_pub,
    267                                     &token->issue_sig))
    268       {
    269         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    270                     "Unblinded token signature is invalid\n");
    271         GNUNET_break (0);
    272         TALER_TESTING_interpreter_fail (state->is);
    273         return;
    274       }
    275     }
    276   }
    277   TALER_TESTING_interpreter_next (state->is);
    278 }
    279 
    280 
    281 static void
    282 start_withdraw (struct FountainWithdrawState *state)
    283 {
    284   struct TALER_TokenEnvelope envelopes[state->num_tokens];
    285   struct TALER_MERCHANT_FountainWithdrawEntry entry = {
    286     .token_family_slug = state->token_family_slug,
    287     .valid_at = state->valid_at,
    288     .num_envelopes = state->num_tokens,
    289     .envelopes = envelopes
    290   };
    291   enum TALER_ErrorCode ec;
    292 
    293   for (unsigned int i = 0; i < state->num_tokens; i++)
    294     envelopes[i] = state->tokens[i].envelope;
    295   state->withdraw_handle = TALER_MERCHANT_post_fountain_withdraw_create (
    296     TALER_TESTING_interpreter_get_context (state->is),
    297     state->merchant_url,
    298     state->fountain_secret,
    299     1,
    300     &entry);
    301   ec = TALER_MERCHANT_post_fountain_withdraw_start (
    302     state->withdraw_handle,
    303     &withdraw_cb,
    304     state);
    305   GNUNET_assert (TALER_EC_NONE == ec);
    306 }
    307 
    308 
    309 /**
    310  * Callback for the GET /fountain/info operation: selects the issue
    311  * key, blinds the token envelopes and starts the withdraw request.
    312  *
    313  * @param state our command state
    314  * @param fir response being processed
    315  */
    316 static void
    317 info_cb (struct FountainWithdrawState *state,
    318          const struct TALER_MERCHANT_GetFountainInfoResponse *fir)
    319 {
    320   const struct TALER_MERCHANT_FountainWalletGrant *grant = NULL;
    321   const struct TALER_MERCHANT_ContractTokenFamilyKey *key = NULL;
    322   struct GNUNET_TIME_Timestamp valid_at = state->valid_at;
    323 
    324   state->info_handle = NULL;
    325   if (MHD_HTTP_OK != fir->hr.http_status)
    326   {
    327     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    328                 "GET /fountain/info failed with response code %u (%d) in command %s\n",
    329                 fir->hr.http_status,
    330                 (int) fir->hr.ec,
    331                 TALER_TESTING_interpreter_get_current_label (state->is));
    332     TALER_TESTING_interpreter_fail (state->is);
    333     return;
    334   }
    335   if (GNUNET_TIME_absolute_is_zero (valid_at.abs_time))
    336     valid_at = GNUNET_TIME_timestamp_get ();
    337   for (unsigned int i = 0; i < fir->details.ok.grants_len; i++)
    338   {
    339     if (0 == strcmp (fir->details.ok.grants[i].grant.token_family_slug,
    340                      state->token_family_slug))
    341     {
    342       grant = &fir->details.ok.grants[i];
    343       break;
    344     }
    345   }
    346   if (NULL == grant)
    347   {
    348     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    349                 "No grant for token family %s in fountain info\n",
    350                 state->token_family_slug);
    351     TALER_TESTING_interpreter_fail (state->is);
    352     return;
    353   }
    354   /* An explicit advertised start selects that key, even in an overlap.
    355      An omitted valid_at selects the first advertised key. */
    356   if (GNUNET_TIME_absolute_is_zero (state->valid_at.abs_time))
    357   {
    358     if (0 != grant->token_family.keys_len)
    359       key = &grant->token_family.keys[0];
    360   }
    361   else
    362     for (unsigned int i = 0; i < grant->token_family.keys_len; i++)
    363       if (GNUNET_TIME_timestamp_cmp (grant->token_family.keys[i].valid_after,
    364                                      ==,
    365                                      valid_at))
    366       {
    367         key = &grant->token_family.keys[i];
    368         break;
    369       }
    370   for (unsigned int i = 0; (NULL == key) && (i < grant->token_family.keys_len); i++)
    371   {
    372     const struct TALER_MERCHANT_ContractTokenFamilyKey *k
    373       = &grant->token_family.keys[i];
    374 
    375     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    376                 "Fountain info key %u: %s valid %llu - %llu (want %llu)\n",
    377                 i,
    378                 GNUNET_h2s (&k->pub.public_key->pub_key_hash),
    379                 (unsigned long long) k->valid_after.abs_time.abs_value_us,
    380                 (unsigned long long) k->valid_before.abs_time.abs_value_us,
    381                 (unsigned long long) valid_at.abs_time.abs_value_us);
    382     if ( (GNUNET_TIME_timestamp_cmp (k->valid_after,
    383                                      <=,
    384                                      valid_at)) &&
    385          (GNUNET_TIME_timestamp_cmp (valid_at,
    386                                      <=,
    387                                      k->valid_before)) )
    388     {
    389       key = k;
    390       break;
    391     }
    392   }
    393   if (NULL == key)
    394   {
    395     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    396                 "No issue key covering the desired validity time in fountain info\n");
    397     TALER_TESTING_interpreter_fail (state->is);
    398     return;
    399   }
    400   /* Prepare the blinded envelopes. */
    401   for (unsigned int i = 0; i < state->num_tokens; i++)
    402   {
    403     struct FountainToken *token = &state->tokens[i];
    404 
    405     TALER_token_issue_pub_copy (&token->issue_pub,
    406                                 &key->pub);
    407     /* Only RSA is supported for now. */
    408     GNUNET_assert (GNUNET_CRYPTO_BSA_RSA ==
    409                    token->issue_pub.public_key->cipher);
    410     TALER_token_blind_input_copy (&token->blinding_inputs,
    411                                   TALER_token_blind_input_rsa_singleton ());
    412     TALER_token_use_setup_random (&token->master);
    413     TALER_token_use_setup_priv (&token->master,
    414                                 &token->blinding_inputs,
    415                                 &token->token_priv);
    416     TALER_token_use_blinding_secret_create (&token->master,
    417                                             &token->blinding_inputs,
    418                                             &token->blinding_secret);
    419     GNUNET_CRYPTO_eddsa_key_get_public (
    420       &token->token_priv.private_key,
    421       &token->token_pub.public_key);
    422     GNUNET_CRYPTO_hash (&token->token_pub.public_key,
    423                         sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
    424                         &token->h_token_pub.hash);
    425     token->envelope.blinded_pub =
    426       GNUNET_CRYPTO_message_blind_to_sign (
    427         token->issue_pub.public_key,
    428         &token->blinding_secret,
    429         NULL, /* session nonce, only needed for CS */
    430         &token->h_token_pub.hash,
    431         sizeof (token->h_token_pub.hash),
    432         token->blinding_inputs.blinding_inputs);
    433     if (NULL == token->envelope.blinded_pub)
    434     {
    435       GNUNET_break (0);
    436       TALER_TESTING_interpreter_fail (state->is);
    437       return;
    438     }
    439   }
    440   start_withdraw (state);
    441 }
    442 
    443 
    444 /**
    445  * Run the fountain withdraw CMD.
    446  *
    447  * @param cls closure.
    448  * @param cmd command being run now.
    449  * @param is interpreter state.
    450  */
    451 static void
    452 fountain_withdraw_run (void *cls,
    453                        const struct TALER_TESTING_Command *cmd,
    454                        struct TALER_TESTING_Interpreter *is)
    455 {
    456   struct FountainWithdrawState *state = cls;
    457   const struct TALER_TESTING_Command *fountain_cmd;
    458 
    459   state->is = is;
    460   fountain_cmd = TALER_TESTING_interpreter_lookup_command (
    461     is,
    462     state->fountain_reference);
    463   if (NULL == fountain_cmd)
    464   {
    465     GNUNET_break (0);
    466     TALER_TESTING_interpreter_fail (is);
    467     return;
    468   }
    469   if (GNUNET_OK !=
    470       TALER_TESTING_get_trait_fountain_secret (fountain_cmd,
    471                                                &state->fountain_secret))
    472   {
    473     GNUNET_break (0);
    474     TALER_TESTING_interpreter_fail (is);
    475     return;
    476   }
    477   state->info_handle = TALER_MERCHANT_get_fountain_info_create (
    478     TALER_TESTING_interpreter_get_context (is),
    479     state->merchant_url,
    480     state->fountain_secret);
    481   {
    482     enum TALER_ErrorCode ec;
    483 
    484     ec = TALER_MERCHANT_get_fountain_info_start (
    485       state->info_handle,
    486       &info_cb,
    487       state);
    488     GNUNET_assert (TALER_EC_NONE == ec);
    489   }
    490 }
    491 
    492 
    493 /**
    494  * Offers information from the fountain withdraw CMD state to other
    495  * commands; in particular the withdrawn tokens can be spent by
    496  * referencing this command from a pay command's token reference.
    497  *
    498  * @param cls closure
    499  * @param[out] ret result (could be anything)
    500  * @param trait name of the trait
    501  * @param index index number of the object to extract.
    502  * @return #GNUNET_OK on success
    503  */
    504 static enum GNUNET_GenericReturnValue
    505 fountain_withdraw_traits (void *cls,
    506                           const void **ret,
    507                           const char *trait,
    508                           unsigned int index)
    509 {
    510   struct FountainWithdrawState *state = cls;
    511 
    512   if (index >= state->num_tokens)
    513     return GNUNET_SYSERR;
    514   {
    515     struct TALER_TESTING_Trait traits[] = {
    516       TALER_TESTING_make_trait_token_priv (
    517         index,
    518         &state->tokens[index].token_priv),
    519       TALER_TESTING_make_trait_token_issue_pub (
    520         index,
    521         &state->tokens[index].issue_pub),
    522       TALER_TESTING_make_trait_token_issue_sig (
    523         index,
    524         &state->tokens[index].issue_sig),
    525       TALER_TESTING_trait_end ()
    526     };
    527 
    528     return TALER_TESTING_get_trait (traits,
    529                                     ret,
    530                                     trait,
    531                                     index);
    532   }
    533 }
    534 
    535 
    536 /**
    537  * Free the state of a fountain withdraw CMD, and possibly cancel
    538  * pending operations thereof.
    539  *
    540  * @param cls closure.
    541  * @param cmd command being run.
    542  */
    543 static void
    544 fountain_withdraw_cleanup (void *cls,
    545                            const struct TALER_TESTING_Command *cmd)
    546 {
    547   struct FountainWithdrawState *state = cls;
    548 
    549   if (NULL != state->info_handle)
    550   {
    551     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    552                 "GET /fountain/info operation did not complete\n");
    553     TALER_MERCHANT_get_fountain_info_cancel (state->info_handle);
    554   }
    555   if (NULL != state->withdraw_handle)
    556   {
    557     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    558                 "POST /fountain/withdraw operation did not complete\n");
    559     TALER_MERCHANT_post_fountain_withdraw_cancel (state->withdraw_handle);
    560   }
    561   for (unsigned int i = 0; i < state->num_tokens; i++)
    562   {
    563     struct FountainToken *token = &state->tokens[i];
    564 
    565     if (NULL != token->issue_pub.public_key)
    566       GNUNET_CRYPTO_blind_sign_pub_decref (token->issue_pub.public_key);
    567     if (NULL != token->envelope.blinded_pub)
    568       GNUNET_CRYPTO_blinded_message_decref (token->envelope.blinded_pub);
    569     if (NULL != token->issue_sig.signature)
    570       GNUNET_CRYPTO_unblinded_sig_decref (token->issue_sig.signature);
    571     /* Note: blinding_inputs is a plain copy of the RSA singleton
    572        (a static without refcount), so it must not be decref'ed. */
    573   }
    574   GNUNET_free (state->tokens);
    575   json_decref (state->first_reply);
    576   GNUNET_free (state);
    577 }
    578 
    579 
    580 struct TALER_TESTING_Command
    581 TALER_TESTING_cmd_merchant_fountain_withdraw (
    582   const char *label,
    583   const char *merchant_url,
    584   unsigned int http_status,
    585   const char *fountain_reference,
    586   const char *token_family_slug,
    587   struct GNUNET_TIME_Timestamp valid_at,
    588   unsigned int num_tokens)
    589 {
    590   struct FountainWithdrawState *state;
    591 
    592   GNUNET_assert (num_tokens > 0);
    593   state = GNUNET_new (struct FountainWithdrawState);
    594   state->merchant_url = merchant_url;
    595   state->http_status = http_status;
    596   state->fountain_reference = fountain_reference;
    597   state->token_family_slug = token_family_slug;
    598   state->valid_at = valid_at;
    599   state->num_tokens = num_tokens;
    600   state->tokens = GNUNET_new_array (num_tokens,
    601                                     struct FountainToken);
    602   {
    603     struct TALER_TESTING_Command cmd = {
    604       .cls = state,
    605       .label = label,
    606       .run = &fountain_withdraw_run,
    607       .cleanup = &fountain_withdraw_cleanup,
    608       .traits = &fountain_withdraw_traits
    609     };
    610 
    611     return cmd;
    612   }
    613 }
    614 
    615 
    616 /* end of testing_api_cmd_fountain_withdraw.c */