exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

exchange_api_post-reveal-melt.c (13505B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see
     15   <http://www.gnu.org/licenses/>
     16 */
     17 /**
     18  * @file lib/exchange_api_post-reveal-melt.c
     19  * @brief Implementation of the /reveal-melt request
     20  * @author Özgür Kesim
     21  */
     22 #include <jansson.h>
     23 #include <microhttpd.h> /* just for HTTP status codes */
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include <gnunet/gnunet_json_lib.h>
     26 #include <gnunet/gnunet_curl_lib.h>
     27 #include "taler/taler_json_lib.h"
     28 #include "exchange_api_common.h"
     29 #include "exchange_api_handle.h"
     30 #include "taler/taler_signatures.h"
     31 #include "exchange_api_curl_defaults.h"
     32 #include "exchange_api_refresh_common.h"
     33 
     34 
     35 /**
     36  * Handler for a running reveal-melt request
     37  */
     38 struct TALER_EXCHANGE_PostRevealMeltHandle
     39 {
     40   /**
     41    * The url for the request
     42    */
     43   char *request_url;
     44 
     45   /**
     46    * The exchange base URL.
     47    */
     48   char *exchange_url;
     49 
     50   /**
     51    * CURL handle for the request job.
     52    */
     53   struct GNUNET_CURL_Job *job;
     54 
     55   /**
     56    * Post Context
     57    */
     58   struct TALER_CURL_PostContext post_ctx;
     59 
     60   /**
     61    * Number of coins to expect
     62    */
     63   size_t num_expected_coins;
     64 
     65   /**
     66    * The input provided
     67    */
     68   const struct TALER_EXCHANGE_RevealMeltInput *reveal_input;
     69 
     70   /**
     71    * The melt data
     72    */
     73   struct MeltData md;
     74 
     75   /**
     76    * The curl context
     77    */
     78   struct GNUNET_CURL_Context *curl_ctx;
     79 
     80   /**
     81    * Callback to pass the result onto
     82    */
     83   TALER_EXCHANGE_PostRevealMeltCallback callback;
     84 
     85   /**
     86    * Closure for @e callback
     87    */
     88   void *callback_cls;
     89 
     90 };
     91 
     92 /**
     93  * We got a 200 OK response for the /reveal-melt operation.
     94  * Extract the signed blinded coins and return it to the caller.
     95  *
     96  * @param mrh operation handle
     97  * @param j_response reply from the exchange
     98  * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
     99  */
    100 static enum GNUNET_GenericReturnValue
    101 reveal_melt_ok (
    102   struct TALER_EXCHANGE_PostRevealMeltHandle *mrh,
    103   const json_t *j_response)
    104 {
    105   struct TALER_EXCHANGE_PostRevealMeltResponse response = {
    106     .hr.reply = j_response,
    107     .hr.http_status = MHD_HTTP_OK,
    108   };
    109   struct TALER_BlindedDenominationSignature blind_sigs[mrh->num_expected_coins];
    110   struct GNUNET_JSON_Specification spec[] = {
    111     TALER_JSON_spec_array_of_blinded_denom_sigs ("ev_sigs",
    112                                                  mrh->num_expected_coins,
    113                                                  blind_sigs),
    114     GNUNET_JSON_spec_end ()
    115   };
    116   if (GNUNET_OK !=
    117       GNUNET_JSON_parse (j_response,
    118                          spec,
    119                          NULL, NULL))
    120   {
    121     GNUNET_break_op (0);
    122     return GNUNET_SYSERR;
    123   }
    124 
    125   {
    126     struct TALER_EXCHANGE_RevealedCoinInfo coins[mrh->num_expected_coins];
    127 
    128     /* Reconstruct the coins and unblind the signatures */
    129     for (unsigned int i = 0; i<mrh->num_expected_coins; i++)
    130     {
    131       struct TALER_EXCHANGE_RevealedCoinInfo *rci = &coins[i];
    132       const struct FreshCoinData *fcd = &mrh->md.fcds[i];
    133       const struct TALER_DenominationPublicKey *pk;
    134       struct TALER_CoinSpendPublicKeyP coin_pub;
    135       struct TALER_CoinPubHashP coin_hash;
    136       struct TALER_FreshCoin coin;
    137       union GNUNET_CRYPTO_BlindingSecretP bks;
    138       const struct TALER_AgeCommitmentHashP *pah = NULL;
    139 
    140       rci->ps = fcd->ps[mrh->reveal_input->noreveal_index];
    141       rci->bks = fcd->bks[mrh->reveal_input->noreveal_index];
    142       rci->age_commitment_proof = NULL;
    143       pk = &fcd->fresh_pk;
    144       if (NULL != mrh->md.melted_coin.age_commitment_proof)
    145       {
    146         rci->age_commitment_proof
    147           = fcd->age_commitment_proofs[mrh->reveal_input->noreveal_index];
    148         TALER_age_commitment_hash (
    149           &rci->age_commitment_proof->commitment,
    150           &rci->h_age_commitment);
    151         pah = &rci->h_age_commitment;
    152       }
    153 
    154       TALER_planchet_setup_coin_priv (&rci->ps,
    155                                       &mrh->reveal_input->blinding_values[i],
    156                                       &rci->coin_priv);
    157       TALER_planchet_blinding_secret_create (&rci->ps,
    158                                              &mrh->reveal_input->blinding_values
    159                                              [i],
    160                                              &bks);
    161       /* needed to verify the signature, and we didn't store it earlier,
    162          hence recomputing it here... */
    163       GNUNET_CRYPTO_eddsa_key_get_public (&rci->coin_priv.eddsa_priv,
    164                                           &coin_pub.eddsa_pub);
    165       TALER_coin_pub_hash (&coin_pub,
    166                            pah,
    167                            &coin_hash);
    168       if (GNUNET_OK !=
    169           TALER_planchet_to_coin (pk,
    170                                   &blind_sigs[i],
    171                                   &bks,
    172                                   &rci->coin_priv,
    173                                   pah,
    174                                   &coin_hash,
    175                                   &mrh->reveal_input->blinding_values[i],
    176                                   &coin))
    177       {
    178         GNUNET_break_op (0);
    179         for (unsigned int k = 0; k < i; k++)
    180           TALER_denom_sig_free (&coins[k].sig);
    181         for (unsigned int k = 0; k < mrh->num_expected_coins; k++)
    182           TALER_blinded_denom_sig_free (&blind_sigs[k]);
    183         GNUNET_JSON_parse_free (spec);
    184         return GNUNET_SYSERR;
    185       }
    186       rci->sig = coin.sig;
    187     }
    188 
    189     response.details.ok.num_coins = mrh->num_expected_coins;
    190     response.details.ok.coins = coins;
    191     mrh->callback (mrh->callback_cls,
    192                    &response);
    193     /* Make sure the callback isn't called again */
    194     mrh->callback = NULL;
    195     /* Free resources */
    196     for (size_t i = 0; i < mrh->num_expected_coins; i++)
    197     {
    198       struct TALER_EXCHANGE_RevealedCoinInfo *rci = &coins[i];
    199 
    200       TALER_denom_sig_free (&rci->sig);
    201     }
    202     GNUNET_JSON_parse_free (spec);
    203   }
    204 
    205   return GNUNET_OK;
    206 }
    207 
    208 
    209 /**
    210  * Function called when we're done processing the
    211  * HTTP /reveal-melt request.
    212  *
    213  * @param cls the `struct TALER_EXCHANGE_RevealMeltHandle`
    214  * @param response_code The HTTP response code
    215  * @param response response data
    216  */
    217 static void
    218 handle_reveal_melt_finished (
    219   void *cls,
    220   long response_code,
    221   const void *response)
    222 {
    223   struct TALER_EXCHANGE_PostRevealMeltHandle *mrh = cls;
    224   const json_t *j_response = response;
    225   struct TALER_EXCHANGE_PostRevealMeltResponse awr = {
    226     .hr.reply = j_response,
    227     .hr.http_status = (unsigned int) response_code
    228   };
    229 
    230   mrh->job = NULL;
    231   switch (response_code)
    232   {
    233   case 0:
    234     awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    235     break;
    236   case MHD_HTTP_OK:
    237     {
    238       enum GNUNET_GenericReturnValue ret;
    239 
    240       ret = reveal_melt_ok (mrh,
    241                             j_response);
    242       if (GNUNET_OK != ret)
    243       {
    244         GNUNET_break_op (0);
    245         awr.hr.http_status = 0;
    246         awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    247         break;
    248       }
    249       GNUNET_assert (NULL == mrh->callback);
    250       TALER_EXCHANGE_post_reveal_melt_cancel (mrh);
    251       return;
    252     }
    253   case MHD_HTTP_BAD_REQUEST:
    254     /* This should never happen, either us or the exchange is buggy
    255        (or API version conflict); just pass JSON reply to the application */
    256     awr.hr.ec = TALER_JSON_get_error_code (j_response);
    257     awr.hr.hint = TALER_JSON_get_error_hint (j_response);
    258     break;
    259   case MHD_HTTP_NOT_FOUND:
    260     /* Nothing really to verify, the exchange basically just says
    261        that it doesn't know this age-melt commitment. */
    262     awr.hr.ec = TALER_JSON_get_error_code (j_response);
    263     awr.hr.hint = TALER_JSON_get_error_hint (j_response);
    264     break;
    265   case MHD_HTTP_CONFLICT:
    266     /* An age commitment for one of the coins did not fulfill
    267      * the required maximum age requirement of the corresponding
    268      * reserve.
    269      * Error code: TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE
    270      * or TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH.
    271      */
    272     awr.hr.ec = TALER_JSON_get_error_code (j_response);
    273     awr.hr.hint = TALER_JSON_get_error_hint (j_response);
    274     break;
    275   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    276     /* Server had an internal issue; we should retry, but this API
    277        leaves this to the application */
    278     awr.hr.ec = TALER_JSON_get_error_code (j_response);
    279     awr.hr.hint = TALER_JSON_get_error_hint (j_response);
    280     break;
    281   default:
    282     /* unexpected response code */
    283     GNUNET_break_op (0);
    284     awr.hr.ec = TALER_JSON_get_error_code (j_response);
    285     awr.hr.hint = TALER_JSON_get_error_hint (j_response);
    286     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    287                 "Unexpected response code %u/%d for exchange melt\n",
    288                 (unsigned int) response_code,
    289                 (int) awr.hr.ec);
    290     break;
    291   }
    292   mrh->callback (mrh->callback_cls,
    293                  &awr);
    294   TALER_EXCHANGE_post_reveal_melt_cancel (mrh);
    295 }
    296 
    297 
    298 /**
    299  * Call /reveal-melt
    300  *
    301  * @param curl_ctx The context for CURL
    302  * @param mrh The handler
    303  */
    304 static void
    305 perform_protocol (
    306   struct GNUNET_CURL_Context *curl_ctx,
    307   struct TALER_EXCHANGE_PostRevealMeltHandle *mrh)
    308 {
    309   CURL *curlh;
    310   json_t *j_batch_seeds;
    311 
    312 
    313   j_batch_seeds = json_array ();
    314   GNUNET_assert (NULL != j_batch_seeds);
    315 
    316   for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
    317   {
    318     if (mrh->reveal_input->noreveal_index == k)
    319       continue;
    320 
    321     GNUNET_assert (0 == json_array_append_new (
    322                      j_batch_seeds,
    323                      GNUNET_JSON_from_data_auto (
    324                        &mrh->md.kappa_batch_seeds.tuple[k])));
    325   }
    326   {
    327     json_t *j_request_body;
    328 
    329     j_request_body = GNUNET_JSON_PACK (
    330       GNUNET_JSON_pack_data_auto ("rc",
    331                                   &mrh->md.rc),
    332       GNUNET_JSON_pack_array_steal ("batch_seeds",
    333                                     j_batch_seeds));
    334     GNUNET_assert (NULL != j_request_body);
    335 
    336     if (NULL != mrh->reveal_input->melt_input->melt_age_commitment_proof)
    337     {
    338       json_t *j_age = GNUNET_JSON_PACK (
    339         TALER_JSON_pack_age_commitment (
    340           "age_commitment",
    341           &mrh->reveal_input->melt_input->melt_age_commitment_proof->commitment)
    342         );
    343       GNUNET_assert (NULL != j_age);
    344       GNUNET_assert (0 ==
    345                      json_object_update_new (j_request_body,
    346                                              j_age));
    347     }
    348     curlh = TALER_EXCHANGE_curl_easy_get_ (mrh->request_url);
    349     GNUNET_assert (NULL != curlh);
    350     GNUNET_assert (GNUNET_OK ==
    351                    TALER_curl_easy_post (&mrh->post_ctx,
    352                                          curlh,
    353                                          j_request_body));
    354     json_decref (j_request_body);
    355   }
    356   mrh->job = GNUNET_CURL_job_add2 (
    357     curl_ctx,
    358     curlh,
    359     mrh->post_ctx.headers,
    360     &handle_reveal_melt_finished,
    361     mrh);
    362   if (NULL == mrh->job)
    363   {
    364     GNUNET_break (0);
    365     if (NULL != curlh)
    366       curl_easy_cleanup (curlh);
    367     /* caller must call _cancel to free mrh */
    368   }
    369 }
    370 
    371 
    372 struct TALER_EXCHANGE_PostRevealMeltHandle *
    373 TALER_EXCHANGE_post_reveal_melt_create (
    374   struct GNUNET_CURL_Context *curl_ctx,
    375   const char *exchange_url,
    376   const struct TALER_EXCHANGE_RevealMeltInput *reveal_melt_input)
    377 {
    378   struct TALER_EXCHANGE_PostRevealMeltHandle *mrh;
    379 
    380   if (reveal_melt_input->num_blinding_values !=
    381       reveal_melt_input->melt_input->num_fresh_denom_pubs)
    382   {
    383     GNUNET_break (0);
    384     return NULL;
    385   }
    386   mrh = GNUNET_new (struct TALER_EXCHANGE_PostRevealMeltHandle);
    387   mrh->reveal_input = reveal_melt_input;
    388   mrh->num_expected_coins = reveal_melt_input->melt_input->num_fresh_denom_pubs;
    389   mrh->curl_ctx = curl_ctx;
    390   mrh->exchange_url = GNUNET_strdup (exchange_url);
    391   if (GNUNET_OK !=
    392       TALER_EXCHANGE_get_melt_data (
    393         reveal_melt_input->rms,
    394         reveal_melt_input->melt_input,
    395         reveal_melt_input->blinding_seed,
    396         reveal_melt_input->blinding_values,
    397         &mrh->md))
    398   {
    399     GNUNET_break (0);
    400     GNUNET_free (mrh->exchange_url);
    401     GNUNET_free (mrh);
    402     return NULL;
    403   }
    404   return mrh;
    405 }
    406 
    407 
    408 enum TALER_ErrorCode
    409 TALER_EXCHANGE_post_reveal_melt_start (
    410   struct TALER_EXCHANGE_PostRevealMeltHandle *mrh,
    411   TALER_EXCHANGE_PostRevealMeltCallback reveal_cb,
    412   TALER_EXCHANGE_POST_REVEAL_MELT_RESULT_CLOSURE *reveal_cb_cls)
    413 {
    414   mrh->callback = reveal_cb;
    415   mrh->callback_cls = reveal_cb_cls;
    416   mrh->request_url = TALER_url_join (mrh->exchange_url,
    417                                      "reveal-melt",
    418                                      NULL);
    419   if (NULL == mrh->request_url)
    420   {
    421     GNUNET_break (0);
    422     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    423   }
    424   perform_protocol (mrh->curl_ctx,
    425                     mrh);
    426   if (NULL == mrh->job)
    427   {
    428     GNUNET_break (0);
    429     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    430   }
    431   return TALER_EC_NONE;
    432 }
    433 
    434 
    435 void
    436 TALER_EXCHANGE_post_reveal_melt_cancel (
    437   struct TALER_EXCHANGE_PostRevealMeltHandle *mrh)
    438 {
    439   if (NULL != mrh->job)
    440   {
    441     GNUNET_CURL_job_cancel (mrh->job);
    442     mrh->job = NULL;
    443   }
    444   TALER_curl_easy_post_finished (&mrh->post_ctx);
    445   TALER_EXCHANGE_free_melt_data (&mrh->md);
    446   GNUNET_free (mrh->request_url);
    447   GNUNET_free (mrh->exchange_url);
    448   GNUNET_free (mrh);
    449 }