exchange

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

exchange_api_reveal_melt.c (12647B)


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