exchange

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

exchange_api_coins_history.c (37873B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2023 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_coins_history.c
     19  * @brief Implementation of the POST /coins/$COIN_PUB/history requests
     20  * @author Christian Grothoff
     21  *
     22  * NOTE: this is an incomplete draft, never finished!
     23  */
     24 #include "taler/platform.h"
     25 #include <jansson.h>
     26 #include <microhttpd.h> /* just for HTTP history codes */
     27 #include <gnunet/gnunet_util_lib.h>
     28 #include <gnunet/gnunet_json_lib.h>
     29 #include <gnunet/gnunet_curl_lib.h>
     30 #include "taler/taler_exchange_service.h"
     31 #include "taler/taler_json_lib.h"
     32 #include "exchange_api_handle.h"
     33 #include "taler/taler_signatures.h"
     34 #include "exchange_api_curl_defaults.h"
     35 
     36 
     37 /**
     38  * @brief A /coins/$RID/history Handle
     39  */
     40 struct TALER_EXCHANGE_CoinsHistoryHandle
     41 {
     42 
     43   /**
     44    * The url for this request.
     45    */
     46   char *url;
     47 
     48   /**
     49    * Handle for the request.
     50    */
     51   struct GNUNET_CURL_Job *job;
     52 
     53   /**
     54    * Context for #TEH_curl_easy_post(). Keeps the data that must
     55    * persist for Curl to make the upload.
     56    */
     57   struct TALER_CURL_PostContext post_ctx;
     58 
     59   /**
     60    * Function to call with the result.
     61    */
     62   TALER_EXCHANGE_CoinsHistoryCallback cb;
     63 
     64   /**
     65    * Public key of the coin we are querying.
     66    */
     67   struct TALER_CoinSpendPublicKeyP coin_pub;
     68 
     69   /**
     70    * Closure for @a cb.
     71    */
     72   void *cb_cls;
     73 
     74 };
     75 
     76 
     77 /**
     78  * Context for coin helpers.
     79  */
     80 struct CoinHistoryParseContext
     81 {
     82 
     83   /**
     84    * Keys of the exchange.
     85    */
     86   struct TALER_EXCHANGE_Keys *keys;
     87 
     88   /**
     89    * Denomination of the coin.
     90    */
     91   const struct TALER_EXCHANGE_DenomPublicKey *dk;
     92 
     93   /**
     94    * Our coin public key.
     95    */
     96   const struct TALER_CoinSpendPublicKeyP *coin_pub;
     97 
     98   /**
     99    * Where to sum up total refunds.
    100    */
    101   struct TALER_Amount *total_in;
    102 
    103   /**
    104    * Total amount encountered.
    105    */
    106   struct TALER_Amount *total_out;
    107 
    108 };
    109 
    110 
    111 /**
    112  * Signature of functions that operate on one of
    113  * the coin's history entries.
    114  *
    115  * @param[in,out] pc overall context
    116  * @param[out] rh where to write the history entry
    117  * @param amount main amount of this operation
    118  * @param transaction JSON details for the operation
    119  * @return #GNUNET_SYSERR on error,
    120  *         #GNUNET_OK to add, #GNUNET_NO to subtract
    121  */
    122 typedef enum GNUNET_GenericReturnValue
    123 (*CoinCheckHelper)(struct CoinHistoryParseContext *pc,
    124                    struct TALER_EXCHANGE_CoinHistoryEntry *rh,
    125                    const struct TALER_Amount *amount,
    126                    json_t *transaction);
    127 
    128 
    129 /**
    130  * Handle deposit entry in the coin's history.
    131  *
    132  * @param[in,out] pc overall context
    133  * @param[out] rh history entry to initialize
    134  * @param amount main amount of this operation
    135  * @param transaction JSON details for the operation
    136  * @return #GNUNET_SYSERR on error,
    137  *         #GNUNET_OK to add, #GNUNET_NO to subtract
    138  */
    139 static enum GNUNET_GenericReturnValue
    140 help_deposit (struct CoinHistoryParseContext *pc,
    141               struct TALER_EXCHANGE_CoinHistoryEntry *rh,
    142               const struct TALER_Amount *amount,
    143               json_t *transaction)
    144 {
    145   struct GNUNET_JSON_Specification spec[] = {
    146     TALER_JSON_spec_amount_any ("deposit_fee",
    147                                 &rh->details.deposit.deposit_fee),
    148     GNUNET_JSON_spec_fixed_auto ("merchant_pub",
    149                                  &rh->details.deposit.merchant_pub),
    150     GNUNET_JSON_spec_timestamp ("timestamp",
    151                                 &rh->details.deposit.wallet_timestamp),
    152     GNUNET_JSON_spec_mark_optional (
    153       GNUNET_JSON_spec_timestamp ("refund_deadline",
    154                                   &rh->details.deposit.refund_deadline),
    155       NULL),
    156     GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
    157                                  &rh->details.deposit.h_contract_terms),
    158     GNUNET_JSON_spec_fixed_auto ("h_wire",
    159                                  &rh->details.deposit.h_wire),
    160     GNUNET_JSON_spec_mark_optional (
    161       GNUNET_JSON_spec_fixed_auto ("h_policy",
    162                                    &rh->details.deposit.h_policy),
    163       &rh->details.deposit.no_h_policy),
    164     GNUNET_JSON_spec_mark_optional (
    165       GNUNET_JSON_spec_fixed_auto ("wallet_data_hash",
    166                                    &rh->details.deposit.wallet_data_hash),
    167       &rh->details.deposit.no_wallet_data_hash),
    168     GNUNET_JSON_spec_mark_optional (
    169       GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
    170                                    &rh->details.deposit.hac),
    171       &rh->details.deposit.no_hac),
    172     GNUNET_JSON_spec_fixed_auto ("coin_sig",
    173                                  &rh->details.deposit.sig),
    174     GNUNET_JSON_spec_end ()
    175   };
    176 
    177   rh->details.deposit.refund_deadline = GNUNET_TIME_UNIT_ZERO_TS;
    178   if (GNUNET_OK !=
    179       GNUNET_JSON_parse (transaction,
    180                          spec,
    181                          NULL, NULL))
    182   {
    183     GNUNET_break_op (0);
    184     return GNUNET_SYSERR;
    185   }
    186   if (GNUNET_OK !=
    187       TALER_wallet_deposit_verify (
    188         amount,
    189         &rh->details.deposit.deposit_fee,
    190         &rh->details.deposit.h_wire,
    191         &rh->details.deposit.h_contract_terms,
    192         rh->details.deposit.no_wallet_data_hash
    193         ? NULL
    194         : &rh->details.deposit.wallet_data_hash,
    195         rh->details.deposit.no_hac
    196         ? NULL
    197         : &rh->details.deposit.hac,
    198         rh->details.deposit.no_h_policy
    199         ? NULL
    200         : &rh->details.deposit.h_policy,
    201         &pc->dk->h_key,
    202         rh->details.deposit.wallet_timestamp,
    203         &rh->details.deposit.merchant_pub,
    204         rh->details.deposit.refund_deadline,
    205         pc->coin_pub,
    206         &rh->details.deposit.sig))
    207   {
    208     GNUNET_break_op (0);
    209     return GNUNET_SYSERR;
    210   }
    211   /* check that deposit fee matches our expectations from /keys! */
    212   if ( (GNUNET_YES !=
    213         TALER_amount_cmp_currency (&rh->details.deposit.deposit_fee,
    214                                    &pc->dk->fees.deposit)) ||
    215        (0 !=
    216         TALER_amount_cmp (&rh->details.deposit.deposit_fee,
    217                           &pc->dk->fees.deposit)) )
    218   {
    219     GNUNET_break_op (0);
    220     return GNUNET_SYSERR;
    221   }
    222   return GNUNET_YES;
    223 }
    224 
    225 
    226 /**
    227  * Handle melt entry in the coin's history.
    228  *
    229  * @param[in,out] pc overall context
    230  * @param[out] rh history entry to initialize
    231  * @param amount main amount of this operation
    232  * @param transaction JSON details for the operation
    233  * @return #GNUNET_SYSERR on error,
    234  *         #GNUNET_OK to add, #GNUNET_NO to subtract
    235  */
    236 static enum GNUNET_GenericReturnValue
    237 help_melt (struct CoinHistoryParseContext *pc,
    238            struct TALER_EXCHANGE_CoinHistoryEntry *rh,
    239            const struct TALER_Amount *amount,
    240            json_t *transaction)
    241 {
    242   struct GNUNET_JSON_Specification spec[] = {
    243     TALER_JSON_spec_amount_any ("melt_fee",
    244                                 &rh->details.melt.melt_fee),
    245     GNUNET_JSON_spec_fixed_auto ("rc",
    246                                  &rh->details.melt.rc),
    247     // FIXME: also return refresh_seed?
    248     // FIXME: also return blinding_seed?
    249     GNUNET_JSON_spec_mark_optional (
    250       GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
    251                                    &rh->details.melt.h_age_commitment),
    252       &rh->details.melt.no_hac),
    253     GNUNET_JSON_spec_fixed_auto ("coin_sig",
    254                                  &rh->details.melt.sig),
    255     GNUNET_JSON_spec_end ()
    256   };
    257 
    258   if (GNUNET_OK !=
    259       GNUNET_JSON_parse (transaction,
    260                          spec,
    261                          NULL, NULL))
    262   {
    263     GNUNET_break_op (0);
    264     return GNUNET_SYSERR;
    265   }
    266 
    267   /* check that melt fee matches our expectations from /keys! */
    268   if ( (GNUNET_YES !=
    269         TALER_amount_cmp_currency (&rh->details.melt.melt_fee,
    270                                    &pc->dk->fees.refresh)) ||
    271        (0 !=
    272         TALER_amount_cmp (&rh->details.melt.melt_fee,
    273                           &pc->dk->fees.refresh)) )
    274   {
    275     GNUNET_break_op (0);
    276     return GNUNET_SYSERR;
    277   }
    278   if (GNUNET_OK !=
    279       TALER_wallet_melt_verify (
    280         amount,
    281         &rh->details.melt.melt_fee,
    282         &rh->details.melt.rc,
    283         &pc->dk->h_key,
    284         rh->details.melt.no_hac
    285         ? NULL
    286         : &rh->details.melt.h_age_commitment,
    287         pc->coin_pub,
    288         &rh->details.melt.sig))
    289   {
    290     GNUNET_break_op (0);
    291     return GNUNET_SYSERR;
    292   }
    293   return GNUNET_YES;
    294 }
    295 
    296 
    297 /**
    298  * Handle refund entry in the coin's history.
    299  *
    300  * @param[in,out] pc overall context
    301  * @param[out] rh history entry to initialize
    302  * @param amount main amount of this operation
    303  * @param transaction JSON details for the operation
    304  * @return #GNUNET_SYSERR on error,
    305  *         #GNUNET_OK to add, #GNUNET_NO to subtract
    306  */
    307 static enum GNUNET_GenericReturnValue
    308 help_refund (struct CoinHistoryParseContext *pc,
    309              struct TALER_EXCHANGE_CoinHistoryEntry *rh,
    310              const struct TALER_Amount *amount,
    311              json_t *transaction)
    312 {
    313   struct GNUNET_JSON_Specification spec[] = {
    314     TALER_JSON_spec_amount_any ("refund_fee",
    315                                 &rh->details.refund.refund_fee),
    316     GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
    317                                  &rh->details.refund.h_contract_terms),
    318     GNUNET_JSON_spec_fixed_auto ("merchant_pub",
    319                                  &rh->details.refund.merchant_pub),
    320     GNUNET_JSON_spec_uint64 ("rtransaction_id",
    321                              &rh->details.refund.rtransaction_id),
    322     GNUNET_JSON_spec_fixed_auto ("merchant_sig",
    323                                  &rh->details.refund.sig),
    324     GNUNET_JSON_spec_end ()
    325   };
    326 
    327   if (GNUNET_OK !=
    328       GNUNET_JSON_parse (transaction,
    329                          spec,
    330                          NULL, NULL))
    331   {
    332     GNUNET_break_op (0);
    333     return GNUNET_SYSERR;
    334   }
    335   if (0 >
    336       TALER_amount_add (&rh->details.refund.sig_amount,
    337                         &rh->details.refund.refund_fee,
    338                         amount))
    339   {
    340     GNUNET_break_op (0);
    341     return GNUNET_SYSERR;
    342   }
    343   if (GNUNET_OK !=
    344       TALER_merchant_refund_verify (pc->coin_pub,
    345                                     &rh->details.refund.h_contract_terms,
    346                                     rh->details.refund.rtransaction_id,
    347                                     &rh->details.refund.sig_amount,
    348                                     &rh->details.refund.merchant_pub,
    349                                     &rh->details.refund.sig))
    350   {
    351     GNUNET_break_op (0);
    352     return GNUNET_SYSERR;
    353   }
    354   /* NOTE: theoretically, we could also check that the given
    355      merchant_pub and h_contract_terms appear in the
    356      history under deposits.  However, there is really no benefit
    357      for the exchange to lie here, so not checking is probably OK
    358      (an auditor ought to check, though). Then again, we similarly
    359      had no reason to check the merchant's signature (other than a
    360      well-formendess check). */
    361 
    362   /* check that refund fee matches our expectations from /keys! */
    363   if ( (GNUNET_YES !=
    364         TALER_amount_cmp_currency (&rh->details.refund.refund_fee,
    365                                    &pc->dk->fees.refund)) ||
    366        (0 !=
    367         TALER_amount_cmp (&rh->details.refund.refund_fee,
    368                           &pc->dk->fees.refund)) )
    369   {
    370     GNUNET_break_op (0);
    371     return GNUNET_SYSERR;
    372   }
    373   return GNUNET_NO;
    374 }
    375 
    376 
    377 /**
    378  * Handle recoup entry in the coin's history.
    379  *
    380  * @param[in,out] pc overall context
    381  * @param[out] rh history entry to initialize
    382  * @param amount main amount of this operation
    383  * @param transaction JSON details for the operation
    384  * @return #GNUNET_SYSERR on error,
    385  *         #GNUNET_OK to add, #GNUNET_NO to subtract
    386  */
    387 static enum GNUNET_GenericReturnValue
    388 help_recoup (struct CoinHistoryParseContext *pc,
    389              struct TALER_EXCHANGE_CoinHistoryEntry *rh,
    390              const struct TALER_Amount *amount,
    391              json_t *transaction)
    392 {
    393   struct GNUNET_JSON_Specification spec[] = {
    394     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    395                                  &rh->details.recoup.exchange_sig),
    396     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    397                                  &rh->details.recoup.exchange_pub),
    398     GNUNET_JSON_spec_fixed_auto ("reserve_pub",
    399                                  &rh->details.recoup.reserve_pub),
    400     GNUNET_JSON_spec_fixed_auto ("coin_sig",
    401                                  &rh->details.recoup.coin_sig),
    402     GNUNET_JSON_spec_fixed_auto ("coin_blind",
    403                                  &rh->details.recoup.coin_bks),
    404     GNUNET_JSON_spec_timestamp ("timestamp",
    405                                 &rh->details.recoup.timestamp),
    406     GNUNET_JSON_spec_end ()
    407   };
    408 
    409   if (GNUNET_OK !=
    410       GNUNET_JSON_parse (transaction,
    411                          spec,
    412                          NULL, NULL))
    413   {
    414     GNUNET_break_op (0);
    415     return GNUNET_SYSERR;
    416   }
    417   if (GNUNET_OK !=
    418       TALER_exchange_online_confirm_recoup_verify (
    419         rh->details.recoup.timestamp,
    420         amount,
    421         pc->coin_pub,
    422         &rh->details.recoup.reserve_pub,
    423         &rh->details.recoup.exchange_pub,
    424         &rh->details.recoup.exchange_sig))
    425   {
    426     GNUNET_break_op (0);
    427     return GNUNET_SYSERR;
    428   }
    429   if (GNUNET_OK !=
    430       TALER_wallet_recoup_verify (&pc->dk->h_key,
    431                                   &rh->details.recoup.coin_bks,
    432                                   pc->coin_pub,
    433                                   &rh->details.recoup.coin_sig))
    434   {
    435     GNUNET_break_op (0);
    436     return GNUNET_SYSERR;
    437   }
    438   return GNUNET_YES;
    439 }
    440 
    441 
    442 /**
    443  * Handle recoup-refresh entry in the coin's history.
    444  * This is the coin that was subjected to a recoup,
    445  * the value being credited to the old coin.
    446  *
    447  * @param[in,out] pc overall context
    448  * @param[out] rh history entry to initialize
    449  * @param amount main amount of this operation
    450  * @param transaction JSON details for the operation
    451  * @return #GNUNET_SYSERR on error,
    452  *         #GNUNET_OK to add, #GNUNET_NO to subtract
    453  */
    454 static enum GNUNET_GenericReturnValue
    455 help_recoup_refresh (struct CoinHistoryParseContext *pc,
    456                      struct TALER_EXCHANGE_CoinHistoryEntry *rh,
    457                      const struct TALER_Amount *amount,
    458                      json_t *transaction)
    459 {
    460   struct GNUNET_JSON_Specification spec[] = {
    461     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    462                                  &rh->details.recoup_refresh.exchange_sig),
    463     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    464                                  &rh->details.recoup_refresh.exchange_pub),
    465     GNUNET_JSON_spec_fixed_auto ("coin_sig",
    466                                  &rh->details.recoup_refresh.coin_sig),
    467     GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
    468                                  &rh->details.recoup_refresh.old_coin_pub),
    469     GNUNET_JSON_spec_fixed_auto ("coin_blind",
    470                                  &rh->details.recoup_refresh.coin_bks),
    471     GNUNET_JSON_spec_timestamp ("timestamp",
    472                                 &rh->details.recoup_refresh.timestamp),
    473     GNUNET_JSON_spec_end ()
    474   };
    475 
    476   if (GNUNET_OK !=
    477       GNUNET_JSON_parse (transaction,
    478                          spec,
    479                          NULL, NULL))
    480   {
    481     GNUNET_break_op (0);
    482     return GNUNET_SYSERR;
    483   }
    484   if (GNUNET_OK !=
    485       TALER_exchange_online_confirm_recoup_refresh_verify (
    486         rh->details.recoup_refresh.timestamp,
    487         amount,
    488         pc->coin_pub,
    489         &rh->details.recoup_refresh.old_coin_pub,
    490         &rh->details.recoup_refresh.exchange_pub,
    491         &rh->details.recoup_refresh.exchange_sig))
    492   {
    493     GNUNET_break_op (0);
    494     return GNUNET_SYSERR;
    495   }
    496   if (GNUNET_OK !=
    497       TALER_wallet_recoup_verify (&pc->dk->h_key,
    498                                   &rh->details.recoup_refresh.coin_bks,
    499                                   pc->coin_pub,
    500                                   &rh->details.recoup_refresh.coin_sig))
    501   {
    502     GNUNET_break_op (0);
    503     return GNUNET_SYSERR;
    504   }
    505   return GNUNET_YES;
    506 }
    507 
    508 
    509 /**
    510  * Handle old coin recoup entry in the coin's history.
    511  * This is the coin that was credited in a recoup,
    512  * the value being credited to the this coin.
    513  *
    514  * @param[in,out] pc overall context
    515  * @param[out] rh history entry to initialize
    516  * @param amount main amount of this operation
    517  * @param transaction JSON details for the operation
    518  * @return #GNUNET_SYSERR on error,
    519  *         #GNUNET_OK to add, #GNUNET_NO to subtract
    520  */
    521 static enum GNUNET_GenericReturnValue
    522 help_old_coin_recoup (struct CoinHistoryParseContext *pc,
    523                       struct TALER_EXCHANGE_CoinHistoryEntry *rh,
    524                       const struct TALER_Amount *amount,
    525                       json_t *transaction)
    526 {
    527   struct GNUNET_JSON_Specification spec[] = {
    528     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    529                                  &rh->details.old_coin_recoup.exchange_sig),
    530     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    531                                  &rh->details.old_coin_recoup.exchange_pub),
    532     GNUNET_JSON_spec_fixed_auto ("coin_pub",
    533                                  &rh->details.old_coin_recoup.new_coin_pub),
    534     GNUNET_JSON_spec_timestamp ("timestamp",
    535                                 &rh->details.old_coin_recoup.timestamp),
    536     GNUNET_JSON_spec_end ()
    537   };
    538 
    539   if (GNUNET_OK !=
    540       GNUNET_JSON_parse (transaction,
    541                          spec,
    542                          NULL, NULL))
    543   {
    544     GNUNET_break_op (0);
    545     return GNUNET_SYSERR;
    546   }
    547   if (GNUNET_OK !=
    548       TALER_exchange_online_confirm_recoup_refresh_verify (
    549         rh->details.old_coin_recoup.timestamp,
    550         amount,
    551         &rh->details.old_coin_recoup.new_coin_pub,
    552         pc->coin_pub,
    553         &rh->details.old_coin_recoup.exchange_pub,
    554         &rh->details.old_coin_recoup.exchange_sig))
    555   {
    556     GNUNET_break_op (0);
    557     return GNUNET_SYSERR;
    558   }
    559   return GNUNET_NO;
    560 }
    561 
    562 
    563 /**
    564  * Handle purse deposit entry in the coin's history.
    565  *
    566  * @param[in,out] pc overall context
    567  * @param[out] rh history entry to initialize
    568  * @param amount main amount of this operation
    569  * @param transaction JSON details for the operation
    570  * @return #GNUNET_SYSERR on error,
    571  *         #GNUNET_OK to add, #GNUNET_NO to subtract
    572  */
    573 static enum GNUNET_GenericReturnValue
    574 help_purse_deposit (struct CoinHistoryParseContext *pc,
    575                     struct TALER_EXCHANGE_CoinHistoryEntry *rh,
    576                     const struct TALER_Amount *amount,
    577                     json_t *transaction)
    578 {
    579   struct GNUNET_JSON_Specification spec[] = {
    580     TALER_JSON_spec_web_url ("exchange_base_url",
    581                              &rh->details.purse_deposit.exchange_base_url),
    582     GNUNET_JSON_spec_mark_optional (
    583       GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
    584                                    &rh->details.purse_deposit.phac),
    585       NULL),
    586     // FIXME: return deposit_fee?
    587     GNUNET_JSON_spec_fixed_auto ("purse_pub",
    588                                  &rh->details.purse_deposit.purse_pub),
    589     GNUNET_JSON_spec_bool ("refunded",
    590                            &rh->details.purse_deposit.refunded),
    591     GNUNET_JSON_spec_fixed_auto ("coin_sig",
    592                                  &rh->details.purse_deposit.coin_sig),
    593     GNUNET_JSON_spec_end ()
    594   };
    595 
    596   if (GNUNET_OK !=
    597       GNUNET_JSON_parse (transaction,
    598                          spec,
    599                          NULL, NULL))
    600   {
    601     GNUNET_break_op (0);
    602     return GNUNET_SYSERR;
    603   }
    604   if (GNUNET_OK !=
    605       TALER_wallet_purse_deposit_verify (
    606         rh->details.purse_deposit.exchange_base_url,
    607         &rh->details.purse_deposit.purse_pub,
    608         amount,
    609         &pc->dk->h_key,
    610         &rh->details.purse_deposit.phac,
    611         pc->coin_pub,
    612         &rh->details.purse_deposit.coin_sig))
    613   {
    614     GNUNET_break_op (0);
    615     return GNUNET_SYSERR;
    616   }
    617   if (rh->details.purse_deposit.refunded)
    618   {
    619     /* We wave the deposit fee. */
    620     if (0 >
    621         TALER_amount_add (pc->total_in,
    622                           pc->total_in,
    623                           &pc->dk->fees.deposit))
    624     {
    625       /* overflow in refund history? inconceivable! Bad exchange! */
    626       GNUNET_break_op (0);
    627       return GNUNET_SYSERR;
    628     }
    629   }
    630   return GNUNET_YES;
    631 }
    632 
    633 
    634 /**
    635  * Handle purse refund entry in the coin's history.
    636  *
    637  * @param[in,out] pc overall context
    638  * @param[out] rh history entry to initialize
    639  * @param amount main amount of this operation
    640  * @param transaction JSON details for the operation
    641  * @return #GNUNET_SYSERR on error,
    642  *         #GNUNET_OK to add, #GNUNET_NO to subtract
    643  */
    644 static enum GNUNET_GenericReturnValue
    645 help_purse_refund (struct CoinHistoryParseContext *pc,
    646                    struct TALER_EXCHANGE_CoinHistoryEntry *rh,
    647                    const struct TALER_Amount *amount,
    648                    json_t *transaction)
    649 {
    650   struct GNUNET_JSON_Specification spec[] = {
    651     TALER_JSON_spec_amount_any ("refund_fee",
    652                                 &rh->details.purse_refund.refund_fee),
    653     GNUNET_JSON_spec_fixed_auto ("exchange_sig",
    654                                  &rh->details.purse_refund.exchange_sig),
    655     GNUNET_JSON_spec_fixed_auto ("exchange_pub",
    656                                  &rh->details.purse_refund.exchange_pub),
    657     GNUNET_JSON_spec_fixed_auto ("purse_pub",
    658                                  &rh->details.purse_refund.purse_pub),
    659     GNUNET_JSON_spec_end ()
    660   };
    661 
    662   if (GNUNET_OK !=
    663       GNUNET_JSON_parse (transaction,
    664                          spec,
    665                          NULL, NULL))
    666   {
    667     GNUNET_break_op (0);
    668     return GNUNET_SYSERR;
    669   }
    670   if (GNUNET_OK !=
    671       TALER_exchange_online_purse_refund_verify (
    672         amount,
    673         &rh->details.purse_refund.refund_fee,
    674         pc->coin_pub,
    675         &rh->details.purse_refund.purse_pub,
    676         &rh->details.purse_refund.exchange_pub,
    677         &rh->details.purse_refund.exchange_sig))
    678   {
    679     GNUNET_break_op (0);
    680     return GNUNET_SYSERR;
    681   }
    682   if ( (GNUNET_YES !=
    683         TALER_amount_cmp_currency (&rh->details.purse_refund.refund_fee,
    684                                    &pc->dk->fees.refund)) ||
    685        (0 !=
    686         TALER_amount_cmp (&rh->details.purse_refund.refund_fee,
    687                           &pc->dk->fees.refund)) )
    688   {
    689     GNUNET_break_op (0);
    690     return GNUNET_SYSERR;
    691   }
    692   return GNUNET_NO;
    693 }
    694 
    695 
    696 /**
    697  * Handle reserve deposit entry in the coin's history.
    698  *
    699  * @param[in,out] pc overall context
    700  * @param[out] rh history entry to initialize
    701  * @param amount main amount of this operation
    702  * @param transaction JSON details for the operation
    703  * @return #GNUNET_SYSERR on error,
    704  *         #GNUNET_OK to add, #GNUNET_NO to subtract
    705  */
    706 static enum GNUNET_GenericReturnValue
    707 help_reserve_open_deposit (struct CoinHistoryParseContext *pc,
    708                            struct TALER_EXCHANGE_CoinHistoryEntry *rh,
    709                            const struct TALER_Amount *amount,
    710                            json_t *transaction)
    711 {
    712   struct GNUNET_JSON_Specification spec[] = {
    713     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
    714                                  &rh->details.reserve_open_deposit.reserve_sig),
    715     GNUNET_JSON_spec_fixed_auto ("coin_sig",
    716                                  &rh->details.reserve_open_deposit.coin_sig),
    717     GNUNET_JSON_spec_end ()
    718   };
    719 
    720   if (GNUNET_OK !=
    721       GNUNET_JSON_parse (transaction,
    722                          spec,
    723                          NULL, NULL))
    724   {
    725     GNUNET_break_op (0);
    726     return GNUNET_SYSERR;
    727   }
    728   if (GNUNET_OK !=
    729       TALER_wallet_reserve_open_deposit_verify (
    730         amount,
    731         &rh->details.reserve_open_deposit.reserve_sig,
    732         pc->coin_pub,
    733         &rh->details.reserve_open_deposit.coin_sig))
    734   {
    735     GNUNET_break_op (0);
    736     return GNUNET_SYSERR;
    737   }
    738   return GNUNET_YES;
    739 }
    740 
    741 
    742 enum GNUNET_GenericReturnValue
    743 TALER_EXCHANGE_parse_coin_history (
    744   const struct TALER_EXCHANGE_Keys *keys,
    745   const struct TALER_EXCHANGE_DenomPublicKey *dk,
    746   const json_t *history,
    747   const struct TALER_CoinSpendPublicKeyP *coin_pub,
    748   struct TALER_Amount *total_in,
    749   struct TALER_Amount *total_out,
    750   unsigned int rlen,
    751   struct TALER_EXCHANGE_CoinHistoryEntry rhistory[static rlen])
    752 {
    753   const struct
    754   {
    755     const char *type;
    756     CoinCheckHelper helper;
    757     enum TALER_EXCHANGE_CoinTransactionType ctt;
    758   } map[] = {
    759     { "DEPOSIT",
    760       &help_deposit,
    761       TALER_EXCHANGE_CTT_DEPOSIT },
    762     { "MELT",
    763       &help_melt,
    764       TALER_EXCHANGE_CTT_MELT },
    765     { "REFUND",
    766       &help_refund,
    767       TALER_EXCHANGE_CTT_REFUND },
    768     { "RECOUP",
    769       &help_recoup,
    770       TALER_EXCHANGE_CTT_RECOUP },
    771     { "RECOUP-REFRESH",
    772       &help_recoup_refresh,
    773       TALER_EXCHANGE_CTT_RECOUP_REFRESH },
    774     { "OLD-COIN-RECOUP",
    775       &help_old_coin_recoup,
    776       TALER_EXCHANGE_CTT_OLD_COIN_RECOUP },
    777     { "PURSE-DEPOSIT",
    778       &help_purse_deposit,
    779       TALER_EXCHANGE_CTT_PURSE_DEPOSIT },
    780     { "PURSE-REFUND",
    781       &help_purse_refund,
    782       TALER_EXCHANGE_CTT_PURSE_REFUND },
    783     { "RESERVE-OPEN-DEPOSIT",
    784       &help_reserve_open_deposit,
    785       TALER_EXCHANGE_CTT_RESERVE_OPEN_DEPOSIT },
    786     { NULL, NULL, TALER_EXCHANGE_CTT_NONE }
    787   };
    788   struct CoinHistoryParseContext pc = {
    789     .dk = dk,
    790     .coin_pub = coin_pub,
    791     .total_out = total_out,
    792     .total_in = total_in
    793   };
    794   size_t len;
    795 
    796   if (NULL == history)
    797   {
    798     GNUNET_break_op (0);
    799     return GNUNET_SYSERR;
    800   }
    801   len = json_array_size (history);
    802   if (0 == len)
    803   {
    804     GNUNET_break_op (0);
    805     return GNUNET_SYSERR;
    806   }
    807   *total_in = dk->value;
    808   GNUNET_assert (GNUNET_OK ==
    809                  TALER_amount_set_zero (total_in->currency,
    810                                         total_out));
    811   for (size_t off = 0; off<len; off++)
    812   {
    813     struct TALER_EXCHANGE_CoinHistoryEntry *rh = &rhistory[off];
    814     json_t *transaction = json_array_get (history,
    815                                           off);
    816     enum GNUNET_GenericReturnValue add;
    817     const char *type;
    818     struct GNUNET_JSON_Specification spec_glob[] = {
    819       TALER_JSON_spec_amount_any ("amount",
    820                                   &rh->amount),
    821       GNUNET_JSON_spec_string ("type",
    822                                &type),
    823       GNUNET_JSON_spec_uint64 ("history_offset",
    824                                &rh->history_offset),
    825       GNUNET_JSON_spec_end ()
    826     };
    827 
    828     if (GNUNET_OK !=
    829         GNUNET_JSON_parse (transaction,
    830                            spec_glob,
    831                            NULL, NULL))
    832     {
    833       GNUNET_break_op (0);
    834       return GNUNET_SYSERR;
    835     }
    836     if (GNUNET_YES !=
    837         TALER_amount_cmp_currency (&rh->amount,
    838                                    total_in))
    839     {
    840       GNUNET_break_op (0);
    841       return GNUNET_SYSERR;
    842     }
    843     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    844                 "Operation of type %s with amount %s\n",
    845                 type,
    846                 TALER_amount2s (&rh->amount));
    847     add = GNUNET_SYSERR;
    848     for (unsigned int i = 0; NULL != map[i].type; i++)
    849     {
    850       if (0 == strcasecmp (type,
    851                            map[i].type))
    852       {
    853         rh->type = map[i].ctt;
    854         add = map[i].helper (&pc,
    855                              rh,
    856                              &rh->amount,
    857                              transaction);
    858         break;
    859       }
    860     }
    861     switch (add)
    862     {
    863     case GNUNET_SYSERR:
    864       /* entry type not supported, new version on server? */
    865       rh->type = TALER_EXCHANGE_CTT_NONE;
    866       GNUNET_break_op (0);
    867       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    868                   "Unexpected type `%s' in response\n",
    869                   type);
    870       return GNUNET_SYSERR;
    871     case GNUNET_YES:
    872       /* This amount should be debited from the coin */
    873       if (0 >
    874           TALER_amount_add (total_out,
    875                             total_out,
    876                             &rh->amount))
    877       {
    878         /* overflow in history already!? inconceivable! Bad exchange! */
    879         GNUNET_break_op (0);
    880         return GNUNET_SYSERR;
    881       }
    882       break;
    883     case GNUNET_NO:
    884       /* This amount should be credited to the coin. */
    885       if (0 >
    886           TALER_amount_add (total_in,
    887                             total_in,
    888                             &rh->amount))
    889       {
    890         /* overflow in refund history? inconceivable! Bad exchange! */
    891         GNUNET_break_op (0);
    892         return GNUNET_SYSERR;
    893       }
    894       break;
    895     } /* end of switch(add) */
    896   }
    897   return GNUNET_OK;
    898 }
    899 
    900 
    901 /**
    902  * We received an #MHD_HTTP_OK history code. Handle the JSON
    903  * response.
    904  *
    905  * @param rsh handle of the request
    906  * @param j JSON response
    907  * @return #GNUNET_OK on success
    908  */
    909 static enum GNUNET_GenericReturnValue
    910 handle_coins_history_ok (struct TALER_EXCHANGE_CoinsHistoryHandle *rsh,
    911                          const json_t *j)
    912 {
    913   struct TALER_EXCHANGE_CoinHistory rs = {
    914     .hr.reply = j,
    915     .hr.http_status = MHD_HTTP_OK
    916   };
    917   struct GNUNET_JSON_Specification spec[] = {
    918     TALER_JSON_spec_amount_any ("balance",
    919                                 &rs.details.ok.balance),
    920     GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
    921                                  &rs.details.ok.h_denom_pub),
    922     GNUNET_JSON_spec_array_const ("history",
    923                                   &rs.details.ok.history),
    924     GNUNET_JSON_spec_end ()
    925   };
    926 
    927   if (GNUNET_OK !=
    928       GNUNET_JSON_parse (j,
    929                          spec,
    930                          NULL,
    931                          NULL))
    932   {
    933     GNUNET_break_op (0);
    934     return GNUNET_SYSERR;
    935   }
    936   if (NULL != rsh->cb)
    937   {
    938     rsh->cb (rsh->cb_cls,
    939              &rs);
    940     rsh->cb = NULL;
    941   }
    942   GNUNET_JSON_parse_free (spec);
    943   return GNUNET_OK;
    944 }
    945 
    946 
    947 /**
    948  * Function called when we're done processing the
    949  * HTTP /coins/$RID/history request.
    950  *
    951  * @param cls the `struct TALER_EXCHANGE_CoinsHistoryHandle`
    952  * @param response_code HTTP response code, 0 on error
    953  * @param response parsed JSON result, NULL on error
    954  */
    955 static void
    956 handle_coins_history_finished (void *cls,
    957                                long response_code,
    958                                const void *response)
    959 {
    960   struct TALER_EXCHANGE_CoinsHistoryHandle *rsh = cls;
    961   const json_t *j = response;
    962   struct TALER_EXCHANGE_CoinHistory rs = {
    963     .hr.reply = j,
    964     .hr.http_status = (unsigned int) response_code
    965   };
    966 
    967   rsh->job = NULL;
    968   switch (response_code)
    969   {
    970   case 0:
    971     rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    972     break;
    973   case MHD_HTTP_OK:
    974     if (GNUNET_OK !=
    975         handle_coins_history_ok (rsh,
    976                                  j))
    977     {
    978       rs.hr.http_status = 0;
    979       rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    980     }
    981     break;
    982   case MHD_HTTP_BAD_REQUEST:
    983     /* This should never happen, either us or the exchange is buggy
    984        (or API version conflict); just pass JSON reply to the application */
    985     GNUNET_break (0);
    986     rs.hr.ec = TALER_JSON_get_error_code (j);
    987     rs.hr.hint = TALER_JSON_get_error_hint (j);
    988     break;
    989   case MHD_HTTP_FORBIDDEN:
    990     /* This should never happen, either us or the exchange is buggy
    991        (or API version conflict); just pass JSON reply to the application */
    992     GNUNET_break (0);
    993     rs.hr.ec = TALER_JSON_get_error_code (j);
    994     rs.hr.hint = TALER_JSON_get_error_hint (j);
    995     break;
    996   case MHD_HTTP_NOT_FOUND:
    997     /* Nothing really to verify, this should never
    998        happen, we should pass the JSON reply to the application */
    999     rs.hr.ec = TALER_JSON_get_error_code (j);
   1000     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1001     break;
   1002   case MHD_HTTP_INTERNAL_SERVER_ERROR:
   1003     /* Server had an internal issue; we should retry, but this API
   1004        leaves this to the application */
   1005     rs.hr.ec = TALER_JSON_get_error_code (j);
   1006     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1007     break;
   1008   default:
   1009     /* unexpected response code */
   1010     GNUNET_break_op (0);
   1011     rs.hr.ec = TALER_JSON_get_error_code (j);
   1012     rs.hr.hint = TALER_JSON_get_error_hint (j);
   1013     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1014                 "Unexpected response code %u/%d for coins history\n",
   1015                 (unsigned int) response_code,
   1016                 (int) rs.hr.ec);
   1017     break;
   1018   }
   1019   if (NULL != rsh->cb)
   1020   {
   1021     rsh->cb (rsh->cb_cls,
   1022              &rs);
   1023     rsh->cb = NULL;
   1024   }
   1025   TALER_EXCHANGE_coins_history_cancel (rsh);
   1026 }
   1027 
   1028 
   1029 struct TALER_EXCHANGE_CoinsHistoryHandle *
   1030 TALER_EXCHANGE_coins_history (
   1031   struct GNUNET_CURL_Context *ctx,
   1032   const char *url,
   1033   const struct TALER_CoinSpendPrivateKeyP *coin_priv,
   1034   uint64_t start_off,
   1035   TALER_EXCHANGE_CoinsHistoryCallback cb,
   1036   void *cb_cls)
   1037 {
   1038   struct TALER_EXCHANGE_CoinsHistoryHandle *rsh;
   1039   CURL *eh;
   1040   char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 64];
   1041   struct curl_slist *job_headers;
   1042 
   1043   rsh = GNUNET_new (struct TALER_EXCHANGE_CoinsHistoryHandle);
   1044   rsh->cb = cb;
   1045   rsh->cb_cls = cb_cls;
   1046   GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
   1047                                       &rsh->coin_pub.eddsa_pub);
   1048   {
   1049     char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
   1050     char *end;
   1051 
   1052     end = GNUNET_STRINGS_data_to_string (
   1053       &rsh->coin_pub,
   1054       sizeof (rsh->coin_pub),
   1055       pub_str,
   1056       sizeof (pub_str));
   1057     *end = '\0';
   1058     if (0 != start_off)
   1059       GNUNET_snprintf (arg_str,
   1060                        sizeof (arg_str),
   1061                        "coins/%s/history?start=%llu",
   1062                        pub_str,
   1063                        (unsigned long long) start_off);
   1064     else
   1065       GNUNET_snprintf (arg_str,
   1066                        sizeof (arg_str),
   1067                        "coins/%s/history",
   1068                        pub_str);
   1069   }
   1070   rsh->url = TALER_url_join (url,
   1071                              arg_str,
   1072                              NULL);
   1073   if (NULL == rsh->url)
   1074   {
   1075     GNUNET_free (rsh);
   1076     return NULL;
   1077   }
   1078   eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
   1079   if (NULL == eh)
   1080   {
   1081     GNUNET_break (0);
   1082     GNUNET_free (rsh->url);
   1083     GNUNET_free (rsh);
   1084     return NULL;
   1085   }
   1086 
   1087   {
   1088     struct TALER_CoinSpendSignatureP coin_sig;
   1089     char *sig_hdr;
   1090     char *hdr;
   1091 
   1092     TALER_wallet_coin_history_sign (start_off,
   1093                                     coin_priv,
   1094                                     &coin_sig);
   1095 
   1096     sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
   1097       &coin_sig,
   1098       sizeof (coin_sig));
   1099     GNUNET_asprintf (&hdr,
   1100                      "%s: %s",
   1101                      TALER_COIN_HISTORY_SIGNATURE_HEADER,
   1102                      sig_hdr);
   1103     GNUNET_free (sig_hdr);
   1104     job_headers = curl_slist_append (NULL,
   1105                                      hdr);
   1106     GNUNET_free (hdr);
   1107     if (NULL == job_headers)
   1108     {
   1109       GNUNET_break (0);
   1110       curl_easy_cleanup (eh);
   1111       return NULL;
   1112     }
   1113   }
   1114 
   1115   rsh->job = GNUNET_CURL_job_add2 (ctx,
   1116                                    eh,
   1117                                    job_headers,
   1118                                    &handle_coins_history_finished,
   1119                                    rsh);
   1120   curl_slist_free_all (job_headers);
   1121   return rsh;
   1122 }
   1123 
   1124 
   1125 void
   1126 TALER_EXCHANGE_coins_history_cancel (
   1127   struct TALER_EXCHANGE_CoinsHistoryHandle *rsh)
   1128 {
   1129   if (NULL != rsh->job)
   1130   {
   1131     GNUNET_CURL_job_cancel (rsh->job);
   1132     rsh->job = NULL;
   1133   }
   1134   TALER_curl_easy_post_finished (&rsh->post_ctx);
   1135   GNUNET_free (rsh->url);
   1136   GNUNET_free (rsh);
   1137 }
   1138 
   1139 
   1140 /**
   1141  * Verify that @a coin_sig does NOT appear in the @a history of a coin's
   1142  * transactions and thus whatever transaction is authorized by @a coin_sig is
   1143  * a conflict with @a proof.
   1144  *
   1145  * @param history coin history to check
   1146  * @param coin_sig signature that must not be in @a history
   1147  * @return #GNUNET_OK if @a coin_sig is not in @a history
   1148  */
   1149 enum GNUNET_GenericReturnValue
   1150 TALER_EXCHANGE_check_coin_signature_conflict (
   1151   const json_t *history,
   1152   const struct TALER_CoinSpendSignatureP *coin_sig)
   1153 {
   1154   size_t off;
   1155   json_t *entry;
   1156 
   1157   json_array_foreach (history, off, entry)
   1158   {
   1159     struct TALER_CoinSpendSignatureP cs;
   1160     struct GNUNET_JSON_Specification spec[] = {
   1161       GNUNET_JSON_spec_fixed_auto ("coin_sig",
   1162                                    &cs),
   1163       GNUNET_JSON_spec_end ()
   1164     };
   1165 
   1166     if (GNUNET_OK !=
   1167         GNUNET_JSON_parse (entry,
   1168                            spec,
   1169                            NULL, NULL))
   1170       continue; /* entry without coin signature */
   1171     if (0 ==
   1172         GNUNET_memcmp (&cs,
   1173                        coin_sig))
   1174     {
   1175       GNUNET_break_op (0);
   1176       return GNUNET_SYSERR;
   1177     }
   1178   }
   1179   return GNUNET_OK;
   1180 }
   1181 
   1182 
   1183 #if FIXME_IMPLEMENT /* #9422 */
   1184 /**
   1185  * FIXME-Oec-#9422: we need some specific routines that show
   1186  * that certain coin operations are indeed in conflict,
   1187  * for example that the coin is of a different denomination
   1188  * or different age restrictions.
   1189  * This relates to unimplemented error handling for
   1190  * coins in the exchange!
   1191  *
   1192  * Check that the provided @a proof indeeds indicates
   1193  * a conflict for @a coin_pub.
   1194  *
   1195  * @param keys exchange keys
   1196  * @param proof provided conflict proof
   1197  * @param dk denomination of @a coin_pub that the client
   1198  *           used
   1199  * @param coin_pub public key of the coin
   1200  * @param required balance required on the coin for the operation
   1201  * @return #GNUNET_OK if @a proof holds
   1202  */
   1203 // FIXME-#9422: should be properly defined and implemented!
   1204 enum GNUNET_GenericReturnValue
   1205 TALER_EXCHANGE_check_coin_conflict_ (
   1206   const struct TALER_EXCHANGE_Keys *keys,
   1207   const json_t *proof,
   1208   const struct TALER_EXCHANGE_DenomPublicKey *dk,
   1209   const struct TALER_CoinSpendPublicKeyP *coin_pub,
   1210   const struct TALER_Amount *required)
   1211 {
   1212   enum TALER_ErrorCode ec;
   1213 
   1214   ec = TALER_JSON_get_error_code (proof);
   1215   switch (ec)
   1216   {
   1217   case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
   1218     /* Nothing to check anymore here, proof needs to be
   1219        checked in the GET /coins/$COIN_PUB handler */
   1220     break;
   1221   case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
   1222     // FIXME-#9422: write check!
   1223     break;
   1224   default:
   1225     GNUNET_break_op (0);
   1226     return GNUNET_SYSERR;
   1227   }
   1228   return GNUNET_OK;
   1229 }
   1230 
   1231 
   1232 #endif
   1233 
   1234 
   1235 /* end of exchange_api_coins_history.c */