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


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