exchange

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

exchange_api_melt_v27.c (18624B)


      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_melt_v27.c
     19  * @brief Implementation of the /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  * @brief A /melt Handle
     39  */
     40 struct TALER_EXCHANGE_MeltHandle_v27
     41 {
     42 
     43   /**
     44    * The keys of the this request handle will use
     45    */
     46   struct TALER_EXCHANGE_Keys *keys;
     47 
     48   /**
     49    * The url for this request.
     50    */
     51   char *url;
     52 
     53   /**
     54    * The exchange base url.
     55    */
     56   char *exchange_url;
     57 
     58   /**
     59    * Curl context.
     60    */
     61   struct GNUNET_CURL_Context *cctx;
     62 
     63   /**
     64    * Context for #TEH_curl_easy_post(). Keeps the data that must
     65    * persist for Curl to make the upload.
     66    */
     67   struct TALER_CURL_PostContext ctx;
     68 
     69   /**
     70    * Handle for the request.
     71    */
     72   struct GNUNET_CURL_Job *job;
     73 
     74   /**
     75    * Function to call with refresh melt failure results.
     76    */
     77   TALER_EXCHANGE_MeltCallback_v27 melt_cb;
     78 
     79   /**
     80    * Closure for @e result_cb and @e melt_failure_cb.
     81    */
     82   void *melt_cb_cls;
     83 
     84   /**
     85    * Actual information about the melt operation.
     86    */
     87   struct MeltData_v27 md;
     88 
     89   /**
     90    * The secret the entire melt operation is seeded from.
     91    */
     92   struct TALER_RefreshMasterSecretP rms;
     93 
     94   /**
     95    * Details about the characteristics of the requested melt operation.
     96    */
     97   const struct TALER_EXCHANGE_MeltInput *rd;
     98 
     99   /**
    100    * True, if no blinding_seed is needed (no CS denominations involved)
    101    */
    102   bool no_blinding_seed;
    103 
    104   /**
    105    * If @e no_blinding_seed is false, the blinding seed for the intermediate
    106    * call to /blinding-prepare, in order to retrieve the R-values from the
    107    * exchange for the blind Clause-Schnorr signature.
    108    */
    109   struct TALER_BlindingMasterSeedP blinding_seed;
    110 
    111   /**
    112    * Array of `num_fresh_denom_pubs` per-coin values
    113    * returned from melt operation.
    114    */
    115   struct TALER_ExchangeBlindingValues *melt_blinding_values;
    116 
    117   /**
    118    * Handle for the preflight request, or NULL.
    119    */
    120   struct TALER_EXCHANGE_BlindingPrepareHandle *bpr;
    121 
    122   /**
    123    * Public key of the coin being melted.
    124    */
    125   struct TALER_CoinSpendPublicKeyP coin_pub;
    126 
    127   /**
    128    * Signature affirming the melt.
    129    */
    130   struct TALER_CoinSpendSignatureP coin_sig;
    131 
    132   /**
    133    * @brief Public information about the coin's denomination key
    134    */
    135   const struct TALER_EXCHANGE_DenomPublicKey *dki;
    136 
    137   /**
    138    * Gamma value chosen by the exchange during melt.
    139    */
    140   uint32_t noreveal_index;
    141 
    142 };
    143 
    144 
    145 /**
    146  * Verify that the signature on the "200 OK" response
    147  * from the exchange is valid.
    148  *
    149  * @param[in,out] mh melt handle
    150  * @param json json reply with the signature
    151  * @param[out] exchange_pub public key of the exchange used for the signature
    152  * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
    153  */
    154 static enum GNUNET_GenericReturnValue
    155 verify_melt_v27_signature_ok (struct TALER_EXCHANGE_MeltHandle_v27 *mh,
    156                               const json_t *json,
    157                               struct TALER_ExchangePublicKeyP *exchange_pub)
    158 {
    159   struct TALER_ExchangeSignatureP exchange_sig;
    160   struct GNUNET_JSON_Specification spec[] = {
    161     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    162                                  &exchange_sig),
    163     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    164                                  exchange_pub),
    165     GNUNET_JSON_spec_uint32 ("noreveal_index",
    166                              &mh->noreveal_index),
    167     GNUNET_JSON_spec_end ()
    168   };
    169 
    170   if (GNUNET_OK !=
    171       GNUNET_JSON_parse (json,
    172                          spec,
    173                          NULL, NULL))
    174   {
    175     GNUNET_break_op (0);
    176     return GNUNET_SYSERR;
    177   }
    178   /* check that exchange signing key is permitted */
    179   if (GNUNET_OK !=
    180       TALER_EXCHANGE_test_signing_key (mh->keys,
    181                                        exchange_pub))
    182   {
    183     GNUNET_break_op (0);
    184     return GNUNET_SYSERR;
    185   }
    186 
    187   /* check that noreveal index is in permitted range */
    188   if (TALER_CNC_KAPPA <= mh->noreveal_index)
    189   {
    190     GNUNET_break_op (0);
    191     return GNUNET_SYSERR;
    192   }
    193 
    194   if (GNUNET_OK !=
    195       TALER_exchange_online_melt_confirmation_verify (
    196         &mh->md.rc,
    197         mh->noreveal_index,
    198         exchange_pub,
    199         &exchange_sig))
    200   {
    201     GNUNET_break_op (0);
    202     return GNUNET_SYSERR;
    203   }
    204   return GNUNET_OK;
    205 }
    206 
    207 
    208 /**
    209  * Function called when we're done processing the
    210  * HTTP /coins/$COIN_PUB/melt request.
    211  *
    212  * @param cls the `struct TALER_EXCHANGE_MeltHandle_v27`
    213  * @param response_code HTTP response code, 0 on error
    214  * @param response parsed JSON result, NULL on error
    215  */
    216 static void
    217 handle_melt_v27_finished (void *cls,
    218                           long response_code,
    219                           const void *response)
    220 {
    221   struct TALER_EXCHANGE_MeltHandle_v27 *mh = cls;
    222   const json_t *j = response;
    223   struct TALER_EXCHANGE_MeltResponse_v27 mr = {
    224     .hr.reply = j,
    225     .hr.http_status = (unsigned int) response_code
    226   };
    227 
    228   mh->job = NULL;
    229   switch (response_code)
    230   {
    231   case 0:
    232     mr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    233     break;
    234   case MHD_HTTP_OK:
    235     if (GNUNET_OK !=
    236         verify_melt_v27_signature_ok (mh,
    237                                       j,
    238                                       &mr.details.ok.sign_key))
    239     {
    240       GNUNET_break_op (0);
    241       mr.hr.http_status = 0;
    242       mr.hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
    243       break;
    244     }
    245     mr.details.ok.noreveal_index = mh->noreveal_index;
    246     mr.details.ok.num_melt_blinding_values = mh->rd->num_fresh_denom_pubs;
    247     mr.details.ok.melt_blinding_values = mh->melt_blinding_values;
    248     mr.details.ok.blinding_seed = mh->no_blinding_seed
    249                                                ? NULL
    250                                                : &mh->blinding_seed;
    251     mh->melt_cb (mh->melt_cb_cls,
    252                  &mr);
    253     mh->melt_cb = NULL;
    254     break;
    255   case MHD_HTTP_BAD_REQUEST:
    256     /* This should never happen, either us or the exchange is buggy
    257        (or API version conflict); just pass JSON reply to the application */
    258     mr.hr.ec = TALER_JSON_get_error_code (j);
    259     mr.hr.hint = TALER_JSON_get_error_hint (j);
    260     break;
    261   case MHD_HTTP_CONFLICT:
    262     mr.hr.ec = TALER_JSON_get_error_code (j);
    263     mr.hr.hint = TALER_JSON_get_error_hint (j);
    264     break;
    265   case MHD_HTTP_FORBIDDEN:
    266     /* Nothing really to verify, exchange says one of the signatures is
    267        invalid; assuming we checked them, this should never happen, we
    268        should pass the JSON reply to the application */
    269     mr.hr.ec = TALER_JSON_get_error_code (j);
    270     mr.hr.hint = TALER_JSON_get_error_hint (j);
    271     break;
    272   case MHD_HTTP_NOT_FOUND:
    273     /* Nothing really to verify, this should never
    274        happen, we should pass the JSON reply to the application */
    275     mr.hr.ec = TALER_JSON_get_error_code (j);
    276     mr.hr.hint = TALER_JSON_get_error_hint (j);
    277     break;
    278   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    279     /* Server had an internal issue; we should retry, but this API
    280        leaves this to the application */
    281     mr.hr.ec = TALER_JSON_get_error_code (j);
    282     mr.hr.hint = TALER_JSON_get_error_hint (j);
    283     break;
    284   default:
    285     /* unexpected response code */
    286     mr.hr.ec = TALER_JSON_get_error_code (j);
    287     mr.hr.hint = TALER_JSON_get_error_hint (j);
    288     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    289                 "Unexpected response code %u/%d for exchange melt\n",
    290                 (unsigned int) response_code,
    291                 mr.hr.ec);
    292     GNUNET_break_op (0);
    293     break;
    294   }
    295   if (NULL != mh->melt_cb)
    296     mh->melt_cb (mh->melt_cb_cls,
    297                  &mr);
    298   TALER_EXCHANGE_melt_v27_cancel (mh);
    299 }
    300 
    301 
    302 /**
    303  * Start the actual melt operation, now that we have
    304  * the exchange's input values.
    305  *
    306  * @param[in,out] mh melt operation to run
    307  * @return #GNUNET_OK if we could start the operation
    308  */
    309 static enum GNUNET_GenericReturnValue
    310 start_melt (struct TALER_EXCHANGE_MeltHandle_v27 *mh)
    311 {
    312   json_t *j_request_body;
    313   json_t *j_coin_evs;
    314   CURL *eh;
    315   struct TALER_DenominationHashP h_denom_pub;
    316 
    317   if (GNUNET_OK !=
    318       TALER_EXCHANGE_get_melt_data_v27 (&mh->rms,
    319                                         mh->rd,
    320                                         mh->no_blinding_seed
    321                                         ? NULL
    322                                         : &mh->blinding_seed,
    323                                         mh->melt_blinding_values,
    324                                         &mh->md))
    325   {
    326     GNUNET_break (0);
    327     return GNUNET_SYSERR;
    328   }
    329   TALER_denom_pub_hash (&mh->md.melted_coin.pub_key,
    330                         &h_denom_pub);
    331   TALER_wallet_melt_sign (
    332     &mh->md.melted_coin.melt_amount_with_fee,
    333     &mh->md.melted_coin.fee_melt,
    334     &mh->md.rc,
    335     &h_denom_pub,
    336     mh->md.melted_coin.h_age_commitment,
    337     &mh->md.melted_coin.coin_priv,
    338     &mh->coin_sig);
    339   GNUNET_CRYPTO_eddsa_key_get_public (
    340     &mh->md.melted_coin.coin_priv.eddsa_priv,
    341     &mh->coin_pub.eddsa_pub);
    342   mh->dki = TALER_EXCHANGE_get_denomination_key (mh->keys,
    343                                                  &mh->md.melted_coin.pub_key);
    344   j_request_body = GNUNET_JSON_PACK (
    345     GNUNET_JSON_pack_data_auto ("old_coin_pub",
    346                                 &mh->coin_pub),
    347     GNUNET_JSON_pack_data_auto ("old_denom_pub_h",
    348                                 &h_denom_pub),
    349     TALER_JSON_pack_denom_sig ("old_denom_sig",
    350                                &mh->md.melted_coin.sig),
    351     GNUNET_JSON_pack_data_auto ("confirm_sig",
    352                                 &mh->coin_sig),
    353     TALER_JSON_pack_amount ("value_with_fee",
    354                             &mh->md.melted_coin.melt_amount_with_fee),
    355     GNUNET_JSON_pack_allow_null (
    356       (NULL != mh->md.melted_coin.h_age_commitment)
    357       ? GNUNET_JSON_pack_data_auto ("old_age_commitment_h",
    358                                     mh->md.melted_coin.h_age_commitment)
    359       : GNUNET_JSON_pack_string ("old_age_commitment_h",
    360                                  NULL)),
    361     GNUNET_JSON_pack_data_auto ("refresh_seed",
    362                                 &mh->md.refresh_seed),
    363     GNUNET_JSON_pack_allow_null (
    364       (mh->md.no_blinding_seed)
    365       ? GNUNET_JSON_pack_string ("blinding_seed",
    366                                  NULL)
    367       : GNUNET_JSON_pack_data_auto ("blinding_seed",
    368                                     &mh->md.blinding_seed)),
    369     TALER_JSON_pack_array_of_data ("denoms_h",
    370                                    mh->md.num_fresh_coins,
    371                                    mh->md.denoms_h,
    372                                    sizeof(*mh->md.denoms_h))
    373     );
    374   GNUNET_assert (NULL != j_request_body);
    375   j_coin_evs = json_array ();
    376   GNUNET_assert (NULL != j_coin_evs);
    377   /**
    378    * Fill the kappa array of coin envelopes
    379    */
    380   for (uint8_t k=0; k<TALER_CNC_KAPPA; k++)
    381   {
    382     json_t *j_envs = json_array ();
    383     GNUNET_assert (NULL != j_envs);
    384     for (size_t i = 0; i < mh->md.num_fresh_coins; i++)
    385     {
    386       json_t *j_coin = GNUNET_JSON_PACK (
    387         TALER_JSON_pack_blinded_planchet (NULL,
    388                                           &mh->md.kappa_blinded_planchets[k][i])
    389         );
    390       GNUNET_assert (NULL != j_coin);
    391       GNUNET_assert (0 ==
    392                      json_array_append_new (j_envs, j_coin));
    393     }
    394     GNUNET_assert (0 ==
    395                    json_array_append_new (j_coin_evs, j_envs));
    396   }
    397   GNUNET_assert (0 ==
    398                  json_object_set_new (j_request_body,
    399                                       "coin_evs",
    400                                       j_coin_evs));
    401   /* and now we can at last begin the actual request handling */
    402   mh->url = TALER_url_join (mh->exchange_url,
    403                             "melt",
    404                             NULL);
    405   if (NULL == mh->url)
    406   {
    407     json_decref (j_request_body);
    408     return GNUNET_SYSERR;
    409   }
    410   eh = TALER_EXCHANGE_curl_easy_get_ (mh->url);
    411   if ( (NULL == eh) ||
    412        (GNUNET_OK !=
    413         TALER_curl_easy_post (&mh->ctx,
    414                               eh,
    415                               j_request_body)) )
    416   {
    417     GNUNET_break (0);
    418     if (NULL != eh)
    419       curl_easy_cleanup (eh);
    420     json_decref (j_request_body);
    421     return GNUNET_SYSERR;
    422   }
    423   json_decref (j_request_body);
    424   mh->job = GNUNET_CURL_job_add2 (mh->cctx,
    425                                   eh,
    426                                   mh->ctx.headers,
    427                                   &handle_melt_v27_finished,
    428                                   mh);
    429   return GNUNET_OK;
    430 }
    431 
    432 
    433 /**
    434  * The melt request @a mh failed, return an error to
    435  * the application and cancel the operation.
    436  *
    437  * @param[in] mh melt request that failed
    438  * @param ec error code to fail with
    439  */
    440 static void
    441 fail_mh (struct TALER_EXCHANGE_MeltHandle_v27 *mh,
    442          enum TALER_ErrorCode ec)
    443 {
    444   struct TALER_EXCHANGE_MeltResponse_v27 mr = {
    445     .hr.ec = ec
    446   };
    447 
    448   mh->melt_cb (mh->melt_cb_cls,
    449                &mr);
    450   TALER_EXCHANGE_melt_v27_cancel (mh);
    451 }
    452 
    453 
    454 /**
    455  * Callbacks of this type are used to serve the result of submitting a
    456  * /blinding-prepare request to a exchange.
    457  *
    458  * @param cls closure with our `struct TALER_EXCHANGE_MeltHandle_v27 *`
    459  * @param bpr response details
    460  */
    461 static void
    462 blinding_prepare_cb (void *cls,
    463                      const struct TALER_EXCHANGE_BlindingPrepareResponse *bpr)
    464 {
    465   struct TALER_EXCHANGE_MeltHandle_v27 *mh = cls;
    466   unsigned int nks_off = 0;
    467 
    468   mh->bpr = NULL;
    469   if (MHD_HTTP_OK != bpr->hr.http_status)
    470   {
    471     struct TALER_EXCHANGE_MeltResponse_v27 mr = {
    472       .hr = bpr->hr
    473     };
    474 
    475     mr.hr.hint = "/blinding-prepare failed";
    476     mh->melt_cb (mh->melt_cb_cls,
    477                  &mr);
    478     TALER_EXCHANGE_melt_v27_cancel (mh);
    479     return;
    480   }
    481   for (unsigned int i = 0; i<mh->rd->num_fresh_denom_pubs; i++)
    482   {
    483     const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk =
    484       &mh->rd->fresh_denom_pubs[i];
    485     struct TALER_ExchangeBlindingValues *wv = &mh->melt_blinding_values[i];
    486 
    487     switch (fresh_pk->key.bsign_pub_key->cipher)
    488     {
    489     case GNUNET_CRYPTO_BSA_INVALID:
    490       GNUNET_break (0);
    491       fail_mh (mh,
    492                TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR);
    493       return;
    494     case GNUNET_CRYPTO_BSA_RSA:
    495       break;
    496     case GNUNET_CRYPTO_BSA_CS:
    497       TALER_denom_ewv_copy (wv,
    498                             &bpr->details.ok.blinding_values[nks_off]);
    499       nks_off++;
    500       break;
    501     }
    502   }
    503   if (GNUNET_OK !=
    504       start_melt (mh))
    505   {
    506     GNUNET_break (0);
    507     fail_mh (mh,
    508              TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR);
    509     return;
    510   }
    511 }
    512 
    513 
    514 struct TALER_EXCHANGE_MeltHandle_v27 *
    515 TALER_EXCHANGE_melt_v27 (
    516   struct GNUNET_CURL_Context *ctx,
    517   const char *url,
    518   struct TALER_EXCHANGE_Keys *keys,
    519   const struct TALER_RefreshMasterSecretP *rms,
    520   const struct TALER_EXCHANGE_MeltInput *rd,
    521   TALER_EXCHANGE_MeltCallback_v27 melt_cb,
    522   void *melt_cb_cls)
    523 {
    524   struct TALER_EXCHANGE_NonceKey nks[GNUNET_NZL (rd->num_fresh_denom_pubs)];
    525   unsigned int nks_off = 0;
    526   struct TALER_EXCHANGE_MeltHandle_v27 *mh;
    527 
    528   if (0 == rd->num_fresh_denom_pubs)
    529   {
    530     GNUNET_break (0);
    531     return NULL;
    532   }
    533   mh = GNUNET_new (struct TALER_EXCHANGE_MeltHandle_v27);
    534   mh->noreveal_index = TALER_CNC_KAPPA; /* invalid value */
    535   mh->cctx = ctx;
    536   mh->exchange_url = GNUNET_strdup (url);
    537   mh->rd = rd;
    538   mh->rms = *rms;
    539   mh->melt_cb = melt_cb;
    540   mh->melt_cb_cls = melt_cb_cls;
    541   mh->no_blinding_seed = true;
    542   mh->melt_blinding_values =
    543     GNUNET_new_array (rd->num_fresh_denom_pubs,
    544                       struct TALER_ExchangeBlindingValues);
    545   for (unsigned int i = 0; i<rd->num_fresh_denom_pubs; i++)
    546   {
    547     const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk =
    548       &rd->fresh_denom_pubs[i];
    549 
    550     switch (fresh_pk->key.bsign_pub_key->cipher)
    551     {
    552     case GNUNET_CRYPTO_BSA_INVALID:
    553       GNUNET_break (0);
    554       GNUNET_free (mh->melt_blinding_values);
    555       GNUNET_free (mh);
    556       return NULL;
    557     case GNUNET_CRYPTO_BSA_RSA:
    558       TALER_denom_ewv_copy (&mh->melt_blinding_values[i],
    559                             TALER_denom_ewv_rsa_singleton ());
    560       break;
    561     case GNUNET_CRYPTO_BSA_CS:
    562       nks[nks_off].pk = fresh_pk;
    563       nks[nks_off].cnc_num = i;
    564       nks_off++;
    565       break;
    566     }
    567   }
    568   mh->keys = TALER_EXCHANGE_keys_incref (keys);
    569   if (0 != nks_off)
    570   {
    571     mh->no_blinding_seed = false;
    572     TALER_cs_refresh_secret_to_blinding_seed (
    573       rms,
    574       &mh->blinding_seed);
    575     mh->bpr = TALER_EXCHANGE_blinding_prepare_for_melt (ctx,
    576                                                         url,
    577                                                         &mh->blinding_seed,
    578                                                         nks_off,
    579                                                         nks,
    580                                                         &blinding_prepare_cb,
    581                                                         mh);
    582     if (NULL == mh->bpr)
    583     {
    584       GNUNET_break (0);
    585       TALER_EXCHANGE_melt_v27_cancel (mh);
    586       return NULL;
    587     }
    588     return mh;
    589   }
    590   if (GNUNET_OK !=
    591       start_melt (mh))
    592   {
    593     GNUNET_break (0);
    594     TALER_EXCHANGE_melt_v27_cancel (mh);
    595     return NULL;
    596   }
    597   return mh;
    598 }
    599 
    600 
    601 void
    602 TALER_EXCHANGE_melt_v27_cancel (struct TALER_EXCHANGE_MeltHandle_v27 *mh)
    603 {
    604   for (unsigned int i = 0; i<mh->rd->num_fresh_denom_pubs; i++)
    605     TALER_denom_ewv_free (&mh->melt_blinding_values[i]);
    606   if (NULL != mh->job)
    607   {
    608     GNUNET_CURL_job_cancel (mh->job);
    609     mh->job = NULL;
    610   }
    611   if (NULL != mh->bpr)
    612   {
    613     TALER_EXCHANGE_blinding_prepare_cancel (mh->bpr);
    614     mh->bpr = NULL;
    615   }
    616   TALER_EXCHANGE_free_melt_data_v27 (&mh->md); /* does not free 'md' itself */
    617   GNUNET_free (mh->melt_blinding_values);
    618   GNUNET_free (mh->url);
    619   GNUNET_free (mh->exchange_url);
    620   TALER_curl_easy_post_finished (&mh->ctx);
    621   TALER_EXCHANGE_keys_decref (mh->keys);
    622   GNUNET_free (mh);
    623 }
    624 
    625 
    626 /* end of exchange_api_melt.c */