LCOV - code coverage report
Current view: top level - lib - exchange_api_reveal_melt.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 104 152 68.4 %
Date: 2025-06-05 21:03:14 Functions: 5 5 100.0 %

          Line data    Source code
       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 "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_json_lib.h"
      29             : #include "taler_exchange_service.h"
      30             : #include "exchange_api_common.h"
      31             : #include "exchange_api_handle.h"
      32             : #include "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          14 : reveal_melt_ok (
      94             :   struct TALER_EXCHANGE_RevealMeltHandle *mrh,
      95             :   const json_t *j_response)
      96          14 : {
      97          14 :   struct TALER_EXCHANGE_RevealMeltResponse response = {
      98             :     .hr.reply = j_response,
      99             :     .hr.http_status = MHD_HTTP_OK,
     100             :   };
     101          14 :   struct TALER_BlindedDenominationSignature blind_sigs[mrh->num_expected_coins];
     102             :   struct GNUNET_JSON_Specification spec[] = {
     103          14 :     TALER_JSON_spec_array_of_blinded_denom_sigs ("ev_sigs",
     104             :                                                  mrh->num_expected_coins,
     105             :                                                  blind_sigs),
     106          14 :     GNUNET_JSON_spec_end ()
     107             :   };
     108          14 :   if (GNUNET_OK !=
     109          14 :       GNUNET_JSON_parse (j_response,
     110             :                          spec,
     111             :                          NULL, NULL))
     112             :   {
     113           0 :     GNUNET_break_op (0);
     114           0 :     return GNUNET_SYSERR;
     115             :   }
     116             : 
     117          14 :   {
     118          14 :     struct TALER_EXCHANGE_RevealedCoinInfo coins[mrh->num_expected_coins];
     119             : 
     120             :     /* Reconstruct the coins and unblind the signatures */
     121          70 :     for (unsigned int i = 0; i<mrh->num_expected_coins; i++)
     122             :     {
     123          56 :       struct TALER_EXCHANGE_RevealedCoinInfo *rci = &coins[i];
     124          56 :       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          56 :       const struct TALER_AgeCommitmentHash *pah = NULL;
     131             : 
     132          56 :       rci->ps = fcd->ps[mrh->reveal_input->noreveal_index];
     133          56 :       rci->bks = fcd->bks[mrh->reveal_input->noreveal_index];
     134          56 :       rci->age_commitment_proof = NULL;
     135          56 :       pk = &fcd->fresh_pk;
     136          56 :       if (NULL != mrh->md.melted_coin.age_commitment_proof)
     137             :       {
     138             :         rci->age_commitment_proof
     139          32 :           = fcd->age_commitment_proofs[mrh->reveal_input->noreveal_index];
     140          32 :         TALER_age_commitment_hash (
     141          32 :           &rci->age_commitment_proof->commitment,
     142             :           &rci->h_age_commitment);
     143          32 :         pah = &rci->h_age_commitment;
     144             :       }
     145             : 
     146          56 :       TALER_planchet_setup_coin_priv (&rci->ps,
     147          56 :                                       &mrh->reveal_input->blinding_values[i],
     148             :                                       &rci->coin_priv);
     149          56 :       TALER_planchet_blinding_secret_create (&rci->ps,
     150          56 :                                              &mrh->reveal_input->blinding_values
     151          56 :                                              [i],
     152             :                                              &bks);
     153             :       /* needed to verify the signature, and we didn't store it earlier,
     154             :          hence recomputing it here... */
     155          56 :       GNUNET_CRYPTO_eddsa_key_get_public (&rci->coin_priv.eddsa_priv,
     156             :                                           &coin_pub.eddsa_pub);
     157          56 :       TALER_coin_pub_hash (&coin_pub,
     158             :                            pah,
     159             :                            &coin_hash);
     160          56 :       if (GNUNET_OK !=
     161          56 :           TALER_planchet_to_coin (pk,
     162          56 :                                   &blind_sigs[i],
     163             :                                   &bks,
     164          56 :                                   &rci->coin_priv,
     165             :                                   pah,
     166             :                                   &coin_hash,
     167          56 :                                   &mrh->reveal_input->blinding_values[i],
     168             :                                   &coin))
     169             :       {
     170           0 :         GNUNET_break_op (0);
     171           0 :         GNUNET_JSON_parse_free (spec);
     172           0 :         return GNUNET_SYSERR;
     173             :       }
     174          56 :       GNUNET_JSON_parse_free (spec);
     175          56 :       rci->sig = coin.sig;
     176             :     }
     177             : 
     178          14 :     response.details.ok.num_coins = mrh->num_expected_coins;
     179          14 :     response.details.ok.coins = coins;
     180          14 :     mrh->callback (mrh->callback_cls,
     181             :                    &response);
     182             :     /* Make sure the callback isn't called again */
     183          14 :     mrh->callback = NULL;
     184             :     /* Free resources */
     185          70 :     for (size_t i = 0; i < mrh->num_expected_coins; i++)
     186             :     {
     187          56 :       struct TALER_EXCHANGE_RevealedCoinInfo *rci = &coins[i];
     188             : 
     189          56 :       TALER_denom_sig_free (&rci->sig);
     190          56 :       TALER_blinded_denom_sig_free (&blind_sigs[i]);
     191             :     }
     192             :   }
     193             : 
     194          14 :   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          14 : handle_reveal_melt_finished (
     208             :   void *cls,
     209             :   long response_code,
     210             :   const void *response)
     211             : {
     212          14 :   struct TALER_EXCHANGE_RevealMeltHandle *mrh = cls;
     213          14 :   const json_t *j_response = response;
     214          14 :   struct TALER_EXCHANGE_RevealMeltResponse awr = {
     215             :     .hr.reply = j_response,
     216          14 :     .hr.http_status = (unsigned int) response_code
     217             :   };
     218             : 
     219          14 :   mrh->job = NULL;
     220          14 :   switch (response_code)
     221             :   {
     222           0 :   case 0:
     223           0 :     awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     224           0 :     break;
     225          14 :   case MHD_HTTP_OK:
     226             :     {
     227             :       enum GNUNET_GenericReturnValue ret;
     228             : 
     229          14 :       ret = reveal_melt_ok (mrh,
     230             :                             j_response);
     231          14 :       if (GNUNET_OK != ret)
     232             :       {
     233           0 :         GNUNET_break_op (0);
     234           0 :         awr.hr.http_status = 0;
     235           0 :         awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
     236           0 :         break;
     237             :       }
     238          14 :       GNUNET_assert (NULL == mrh->callback);
     239          14 :       TALER_EXCHANGE_reveal_melt_cancel (mrh);
     240          14 :       return;
     241             :     }
     242           0 :   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           0 :     awr.hr.ec = TALER_JSON_get_error_code (j_response);
     246           0 :     awr.hr.hint = TALER_JSON_get_error_hint (j_response);
     247           0 :     break;
     248           0 :   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           0 :     awr.hr.ec = TALER_JSON_get_error_code (j_response);
     252           0 :     awr.hr.hint = TALER_JSON_get_error_hint (j_response);
     253           0 :     break;
     254           0 :   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           0 :     awr.hr.ec = TALER_JSON_get_error_code (j_response);
     262           0 :     awr.hr.hint = TALER_JSON_get_error_hint (j_response);
     263           0 :     break;
     264           0 :   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           0 :     awr.hr.ec = TALER_JSON_get_error_code (j_response);
     268           0 :     awr.hr.hint = TALER_JSON_get_error_hint (j_response);
     269           0 :     break;
     270           0 :   default:
     271             :     /* unexpected response code */
     272           0 :     GNUNET_break_op (0);
     273           0 :     awr.hr.ec = TALER_JSON_get_error_code (j_response);
     274           0 :     awr.hr.hint = TALER_JSON_get_error_hint (j_response);
     275           0 :     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           0 :     break;
     280             :   }
     281           0 :   mrh->callback (mrh->callback_cls,
     282             :                  &awr);
     283           0 :   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          14 : 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          14 :   j_array_of_signatures = json_array ();
     303          14 :   GNUNET_assert (NULL != j_array_of_signatures);
     304             : 
     305          56 :   for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
     306             :   {
     307          42 :     if (mrh->reveal_input->noreveal_index == k)
     308          14 :       continue;
     309             : 
     310          28 :     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          14 :     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          14 :     GNUNET_assert (NULL != j_request_body);
     323             : 
     324          14 :     if (NULL != mrh->reveal_input->melt_input->melt_age_commitment_proof)
     325             :     {
     326           8 :       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           8 :       GNUNET_assert (NULL != j_age);
     332           8 :       GNUNET_assert (0 ==
     333             :                      json_object_update_new (j_request_body,
     334             :                                              j_age));
     335             :     }
     336          14 :     curlh = TALER_EXCHANGE_curl_easy_get_ (mrh->request_url);
     337          14 :     GNUNET_assert (NULL != curlh);
     338          14 :     GNUNET_assert (GNUNET_OK ==
     339             :                    TALER_curl_easy_post (&mrh->post_ctx,
     340             :                                          curlh,
     341             :                                          j_request_body));
     342          14 :     json_decref (j_request_body);
     343             :   }
     344             : 
     345          28 :   mrh->job = GNUNET_CURL_job_add2 (
     346             :     curl_ctx,
     347             :     curlh,
     348          14 :     mrh->post_ctx.headers,
     349             :     &handle_reveal_melt_finished,
     350             :     mrh);
     351          14 :   if (NULL == mrh->job)
     352             :   {
     353           0 :     GNUNET_break (0);
     354           0 :     if (NULL != curlh)
     355           0 :       curl_easy_cleanup (curlh);
     356           0 :     TALER_EXCHANGE_reveal_melt_cancel (mrh);
     357             :   }
     358          14 : }
     359             : 
     360             : 
     361             : struct TALER_EXCHANGE_RevealMeltHandle *
     362          14 : 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          14 :     GNUNET_new (struct TALER_EXCHANGE_RevealMeltHandle);
     371          14 :   mrh->callback = reveal_cb;
     372          14 :   mrh->callback_cls = reveal_cb_cls;
     373          14 :   mrh->reveal_input = reveal_melt_input;
     374          14 :   mrh->num_expected_coins = reveal_melt_input->melt_input->num_fresh_denom_pubs;
     375          14 :   mrh->request_url = TALER_url_join (exchange_url,
     376             :                                      "reveal-melt",
     377             :                                      NULL);
     378          14 :   if (NULL == mrh->request_url)
     379             :   {
     380           0 :     GNUNET_break (0);
     381           0 :     GNUNET_free (mrh);
     382           0 :     return NULL;
     383             :   }
     384          14 :   if (reveal_melt_input->num_blinding_values !=
     385          14 :       reveal_melt_input->melt_input->num_fresh_denom_pubs)
     386             :   {
     387           0 :     GNUNET_break (0);
     388           0 :     GNUNET_free (mrh);
     389           0 :     return NULL;
     390             :   }
     391          14 :   TALER_EXCHANGE_get_melt_data_v27 (
     392          14 :     reveal_melt_input->rms,
     393          14 :     reveal_melt_input->melt_input,
     394          14 :     reveal_melt_input->blinding_seed,
     395          14 :     reveal_melt_input->blinding_values,
     396             :     &mrh->md);
     397          14 :   perform_protocol (curl_ctx,
     398             :                     mrh);
     399          14 :   return mrh;
     400             : }
     401             : 
     402             : 
     403             : void
     404          14 : TALER_EXCHANGE_reveal_melt_cancel (
     405             :   struct TALER_EXCHANGE_RevealMeltHandle *mrh)
     406             : {
     407          14 :   if (NULL != mrh->job)
     408             :   {
     409           0 :     GNUNET_CURL_job_cancel (mrh->job);
     410           0 :     mrh->job = NULL;
     411             :   }
     412          14 :   TALER_curl_easy_post_finished (&mrh->post_ctx);
     413          14 :   TALER_EXCHANGE_free_melt_data_v27 (&mrh->md);
     414          14 :   GNUNET_free (mrh->request_url);
     415          14 :   GNUNET_free (mrh);
     416          14 : }

Generated by: LCOV version 1.16