exchange

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

exchange_api_common.c (39213B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2015-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_common.c
     19  * @brief common functions for the exchange API
     20  * @author Christian Grothoff
     21  */
     22 #include "taler/taler_json_lib.h"
     23 #include <microhttpd.h>
     24 #include <gnunet/gnunet_curl_lib.h>
     25 #include "exchange_api_common.h"
     26 #include "exchange_api_handle.h"
     27 #include "taler/taler_signatures.h"
     28 
     29 
     30 const struct TALER_EXCHANGE_SigningPublicKey *
     31 TALER_EXCHANGE_get_signing_key_info (
     32   const struct TALER_EXCHANGE_Keys *keys,
     33   const struct TALER_ExchangePublicKeyP *exchange_pub)
     34 {
     35   for (unsigned int i = 0; i<keys->num_sign_keys; i++)
     36   {
     37     const struct TALER_EXCHANGE_SigningPublicKey *spk
     38       = &keys->sign_keys[i];
     39 
     40     if (0 == GNUNET_memcmp (exchange_pub,
     41                             &spk->key))
     42       return spk;
     43   }
     44   return NULL;
     45 }
     46 
     47 
     48 enum GNUNET_GenericReturnValue
     49 TALER_EXCHANGE_check_purse_create_conflict_ (
     50   const struct TALER_PurseContractSignatureP *cpurse_sig,
     51   const struct TALER_PurseContractPublicKeyP *purse_pub,
     52   const json_t *proof)
     53 {
     54   struct TALER_Amount amount;
     55   uint32_t min_age;
     56   struct GNUNET_TIME_Timestamp purse_expiration;
     57   struct TALER_PurseContractSignatureP purse_sig;
     58   struct TALER_PrivateContractHashP h_contract_terms;
     59   struct TALER_PurseMergePublicKeyP merge_pub;
     60   struct GNUNET_JSON_Specification spec[] = {
     61     TALER_JSON_spec_amount_any ("amount",
     62                                 &amount),
     63     GNUNET_JSON_spec_uint32 ("min_age",
     64                              &min_age),
     65     GNUNET_JSON_spec_timestamp ("purse_expiration",
     66                                 &purse_expiration),
     67     GNUNET_JSON_spec_fixed_auto ("purse_sig",
     68                                  &purse_sig),
     69     GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
     70                                  &h_contract_terms),
     71     GNUNET_JSON_spec_fixed_auto ("merge_pub",
     72                                  &merge_pub),
     73     GNUNET_JSON_spec_end ()
     74   };
     75 
     76   if (GNUNET_OK !=
     77       GNUNET_JSON_parse (proof,
     78                          spec,
     79                          NULL, NULL))
     80   {
     81     GNUNET_break_op (0);
     82     return GNUNET_SYSERR;
     83   }
     84   if (GNUNET_OK !=
     85       TALER_wallet_purse_create_verify (purse_expiration,
     86                                         &h_contract_terms,
     87                                         &merge_pub,
     88                                         min_age,
     89                                         &amount,
     90                                         purse_pub,
     91                                         &purse_sig))
     92   {
     93     GNUNET_break_op (0);
     94     return GNUNET_SYSERR;
     95   }
     96   if (0 ==
     97       GNUNET_memcmp (&purse_sig,
     98                      cpurse_sig))
     99   {
    100     /* Must be the SAME data, not a conflict! */
    101     GNUNET_break_op (0);
    102     return GNUNET_SYSERR;
    103   }
    104   return GNUNET_OK;
    105 }
    106 
    107 
    108 enum GNUNET_GenericReturnValue
    109 TALER_EXCHANGE_check_purse_merge_conflict_ (
    110   const struct TALER_PurseMergeSignatureP *cmerge_sig,
    111   const struct TALER_PurseMergePublicKeyP *merge_pub,
    112   const struct TALER_PurseContractPublicKeyP *purse_pub,
    113   const char *exchange_url,
    114   const json_t *proof)
    115 {
    116   struct TALER_PurseMergeSignatureP merge_sig;
    117   struct GNUNET_TIME_Timestamp merge_timestamp;
    118   const char *partner_url = NULL;
    119   struct TALER_ReservePublicKeyP reserve_pub;
    120   struct GNUNET_JSON_Specification spec[] = {
    121     GNUNET_JSON_spec_mark_optional (
    122       TALER_JSON_spec_web_url ("partner_url",
    123                                &partner_url),
    124       NULL),
    125     GNUNET_JSON_spec_timestamp ("merge_timestamp",
    126                                 &merge_timestamp),
    127     GNUNET_JSON_spec_fixed_auto ("merge_sig",
    128                                  &merge_sig),
    129     GNUNET_JSON_spec_fixed_auto ("reserve_pub",
    130                                  &reserve_pub),
    131     GNUNET_JSON_spec_end ()
    132   };
    133   struct TALER_NormalizedPayto payto_uri;
    134 
    135   if (GNUNET_OK !=
    136       GNUNET_JSON_parse (proof,
    137                          spec,
    138                          NULL, NULL))
    139   {
    140     GNUNET_break_op (0);
    141     return GNUNET_SYSERR;
    142   }
    143   if (NULL == partner_url)
    144     partner_url = exchange_url;
    145   payto_uri = TALER_reserve_make_payto (partner_url,
    146                                         &reserve_pub);
    147   if (GNUNET_OK !=
    148       TALER_wallet_purse_merge_verify (
    149         payto_uri,
    150         merge_timestamp,
    151         purse_pub,
    152         merge_pub,
    153         &merge_sig))
    154   {
    155     GNUNET_break_op (0);
    156     GNUNET_free (payto_uri.normalized_payto);
    157     return GNUNET_SYSERR;
    158   }
    159   GNUNET_free (payto_uri.normalized_payto);
    160   if (0 ==
    161       GNUNET_memcmp (&merge_sig,
    162                      cmerge_sig))
    163   {
    164     /* Must be the SAME data, not a conflict! */
    165     GNUNET_break_op (0);
    166     return GNUNET_SYSERR;
    167   }
    168   return GNUNET_OK;
    169 }
    170 
    171 
    172 enum GNUNET_GenericReturnValue
    173 TALER_EXCHANGE_check_purse_coin_conflict_ (
    174   const struct TALER_PurseContractPublicKeyP *purse_pub,
    175   const char *exchange_url,
    176   const json_t *proof,
    177   struct TALER_DenominationHashP *h_denom_pub,
    178   struct TALER_AgeCommitmentHashP *phac,
    179   struct TALER_CoinSpendPublicKeyP *coin_pub,
    180   struct TALER_CoinSpendSignatureP *coin_sig)
    181 {
    182   const char *partner_url = NULL;
    183   struct TALER_Amount amount;
    184   struct GNUNET_JSON_Specification spec[] = {
    185     GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
    186                                  h_denom_pub),
    187     GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
    188                                  phac),
    189     GNUNET_JSON_spec_fixed_auto ("coin_sig",
    190                                  coin_sig),
    191     GNUNET_JSON_spec_fixed_auto ("coin_pub",
    192                                  coin_pub),
    193     GNUNET_JSON_spec_mark_optional (
    194       TALER_JSON_spec_web_url ("partner_url",
    195                                &partner_url),
    196       NULL),
    197     TALER_JSON_spec_amount_any ("amount",
    198                                 &amount),
    199     GNUNET_JSON_spec_end ()
    200   };
    201 
    202   if (GNUNET_OK !=
    203       GNUNET_JSON_parse (proof,
    204                          spec,
    205                          NULL, NULL))
    206   {
    207     GNUNET_break_op (0);
    208     return GNUNET_SYSERR;
    209   }
    210   if (NULL == partner_url)
    211     partner_url = exchange_url;
    212   if (GNUNET_OK !=
    213       TALER_wallet_purse_deposit_verify (
    214         partner_url,
    215         purse_pub,
    216         &amount,
    217         h_denom_pub,
    218         phac,
    219         coin_pub,
    220         coin_sig))
    221   {
    222     GNUNET_break_op (0);
    223     return GNUNET_SYSERR;
    224   }
    225   return GNUNET_OK;
    226 }
    227 
    228 
    229 enum GNUNET_GenericReturnValue
    230 TALER_EXCHANGE_check_purse_econtract_conflict_ (
    231   const struct TALER_PurseContractSignatureP *ccontract_sig,
    232   const struct TALER_PurseContractPublicKeyP *purse_pub,
    233   const json_t *proof)
    234 {
    235   struct TALER_ContractDiffiePublicP contract_pub;
    236   struct TALER_PurseContractSignatureP contract_sig;
    237   struct GNUNET_HashCode h_econtract;
    238   struct GNUNET_JSON_Specification spec[] = {
    239     GNUNET_JSON_spec_fixed_auto ("h_econtract",
    240                                  &h_econtract),
    241     GNUNET_JSON_spec_fixed_auto ("econtract_sig",
    242                                  &contract_sig),
    243     GNUNET_JSON_spec_fixed_auto ("contract_pub",
    244                                  &contract_pub),
    245     GNUNET_JSON_spec_end ()
    246   };
    247 
    248   if (GNUNET_OK !=
    249       GNUNET_JSON_parse (proof,
    250                          spec,
    251                          NULL, NULL))
    252   {
    253     GNUNET_break_op (0);
    254     return GNUNET_SYSERR;
    255   }
    256   if (GNUNET_OK !=
    257       TALER_wallet_econtract_upload_verify2 (
    258         &h_econtract,
    259         &contract_pub,
    260         purse_pub,
    261         &contract_sig))
    262   {
    263     GNUNET_break_op (0);
    264     return GNUNET_SYSERR;
    265   }
    266   if (0 ==
    267       GNUNET_memcmp (&contract_sig,
    268                      ccontract_sig))
    269   {
    270     /* Must be the SAME data, not a conflict! */
    271     GNUNET_break_op (0);
    272     return GNUNET_SYSERR;
    273   }
    274   return GNUNET_OK;
    275 }
    276 
    277 
    278 enum GNUNET_GenericReturnValue
    279 TALER_EXCHANGE_parse_coin_denomination_conflict_ (
    280   const json_t *proof,
    281   struct TALER_EXCHANGE_CoinDenominationConflict *cdc)
    282 {
    283   struct GNUNET_JSON_Specification spec[] = {
    284     GNUNET_JSON_spec_fixed_auto ("coin_pub",
    285                                  &cdc->coin_pub),
    286     TALER_JSON_spec_denom_pub ("prev_denom_pub",
    287                                &cdc->prev_denom_pub),
    288     TALER_JSON_spec_denom_sig ("prev_denom_sig",
    289                                &cdc->prev_denom_sig),
    290     GNUNET_JSON_spec_mark_optional (
    291       GNUNET_JSON_spec_fixed_auto ("prev_h_age_commitment",
    292                                    &cdc->prev_h_age_commitment),
    293       &cdc->no_prev_age_commitment),
    294     GNUNET_JSON_spec_end ()
    295   };
    296 
    297   memset (cdc,
    298           0,
    299           sizeof (*cdc));
    300   if (GNUNET_OK !=
    301       GNUNET_JSON_parse (proof,
    302                          spec,
    303                          NULL, NULL))
    304   {
    305     GNUNET_break_op (0);
    306     return GNUNET_SYSERR;
    307   }
    308   TALER_denom_pub_hash (&cdc->prev_denom_pub,
    309                         &cdc->prev_h_denom_pub);
    310   cdc->verified = false;
    311   return GNUNET_OK;
    312 }
    313 
    314 
    315 enum GNUNET_GenericReturnValue
    316 TALER_EXCHANGE_check_coin_denomination_conflict_ (
    317   const struct TALER_EXCHANGE_Keys *keys,
    318   const struct TALER_DenominationHashP *h_denom_pub,
    319   struct TALER_EXCHANGE_CoinDenominationConflict *cdc)
    320 {
    321   const struct TALER_EXCHANGE_DenomPublicKey *dk;
    322   struct TALER_CoinPublicInfo cpi = {
    323     .coin_pub = cdc->coin_pub,
    324     .denom_pub_hash = cdc->prev_h_denom_pub,
    325     .h_age_commitment = cdc->prev_h_age_commitment,
    326     .no_age_commitment = cdc->no_prev_age_commitment,
    327     .denom_sig = cdc->prev_denom_sig
    328   };
    329 
    330   cdc->verified = false;
    331   if (0 ==
    332       GNUNET_memcmp (h_denom_pub,
    333                      &cdc->prev_h_denom_pub))
    334   {
    335     /* Must be a DIFFERENT denomination, not a conflict! */
    336     GNUNET_break_op (0);
    337     return GNUNET_SYSERR;
    338   }
    339   dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
    340                                                     &cdc->prev_h_denom_pub);
    341   if (NULL == dk)
    342   {
    343     /* TODO[oec]: the exchange names a denomination that is not (or no
    344        longer) in our /keys, for example one that expired.  We cannot
    345        verify the signature, but we also cannot tell that the proof is
    346        wrong.  We accept the response as unverifiable and leave the
    347        decision to the application (see @e verified). */
    348     return GNUNET_NO;
    349   }
    350   /* the age commitment hash must be present exactly if the
    351      denomination is age-restricted */
    352   if (cdc->no_prev_age_commitment !=
    353       (0 == dk->key.age_mask.bits))
    354   {
    355     GNUNET_break_op (0);
    356     return GNUNET_SYSERR;
    357   }
    358   if (0 !=
    359       TALER_denom_pub_cmp (&dk->key,
    360                            &cdc->prev_denom_pub))
    361   {
    362     /* hash matches, key does not?! */
    363     GNUNET_break_op (0);
    364     return GNUNET_SYSERR;
    365   }
    366   if (GNUNET_OK !=
    367       TALER_test_coin_valid (&cpi,
    368                              &cdc->prev_denom_pub))
    369   {
    370     GNUNET_break_op (0);
    371     return GNUNET_SYSERR;
    372   }
    373   cdc->verified = true;
    374   return GNUNET_OK;
    375 }
    376 
    377 
    378 void
    379 TALER_EXCHANGE_free_coin_denomination_conflict_ (
    380   struct TALER_EXCHANGE_CoinDenominationConflict *cdc)
    381 {
    382   TALER_denom_pub_free (&cdc->prev_denom_pub);
    383   TALER_denom_sig_free (&cdc->prev_denom_sig);
    384 }
    385 
    386 
    387 enum GNUNET_GenericReturnValue
    388 TALER_EXCHANGE_parse_coin_age_commitment_conflict_ (
    389   const json_t *proof,
    390   struct TALER_EXCHANGE_CoinAgeCommitmentConflict *cac)
    391 {
    392   struct GNUNET_JSON_Specification spec[] = {
    393     GNUNET_JSON_spec_fixed_auto ("coin_pub",
    394                                  &cac->coin_pub),
    395     GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
    396                                  &cac->h_denom_pub),
    397     GNUNET_JSON_spec_mark_optional (
    398       GNUNET_JSON_spec_fixed_auto ("expected_age_commitment_hash",
    399                                    &cac->expected_age_commitment_hash),
    400       &cac->no_expected_age_commitment),
    401     GNUNET_JSON_spec_string ("conflict_detail",
    402                              &cac->conflict_detail),
    403     TALER_JSON_spec_denom_sig ("prev_denom_sig",
    404                                &cac->prev_denom_sig),
    405     GNUNET_JSON_spec_end ()
    406   };
    407 
    408   memset (cac,
    409           0,
    410           sizeof (*cac));
    411   if (GNUNET_OK !=
    412       GNUNET_JSON_parse (proof,
    413                          spec,
    414                          NULL, NULL))
    415   {
    416     GNUNET_break_op (0);
    417     return GNUNET_SYSERR;
    418   }
    419   cac->verified = false;
    420   return GNUNET_OK;
    421 }
    422 
    423 
    424 enum GNUNET_GenericReturnValue
    425 TALER_EXCHANGE_check_coin_age_commitment_conflict_ (
    426   const struct TALER_EXCHANGE_Keys *keys,
    427   const struct TALER_DenominationHashP *h_denom_pub,
    428   const struct TALER_AgeCommitmentHashP *h_age_commitment,
    429   struct TALER_EXCHANGE_CoinAgeCommitmentConflict *cac)
    430 {
    431   const struct TALER_EXCHANGE_DenomPublicKey *dk;
    432   struct TALER_CoinPublicInfo cpi = {
    433     .coin_pub = cac->coin_pub,
    434     .denom_pub_hash = cac->h_denom_pub,
    435     .h_age_commitment = cac->expected_age_commitment_hash,
    436     .no_age_commitment = cac->no_expected_age_commitment,
    437     .denom_sig = cac->prev_denom_sig
    438   };
    439 
    440   cac->verified = false;
    441   if (0 !=
    442       GNUNET_memcmp (h_denom_pub,
    443                      &cac->h_denom_pub))
    444   {
    445     /* an age conflict is about the SAME denomination */
    446     GNUNET_break_op (0);
    447     return GNUNET_SYSERR;
    448   }
    449   if (cac->no_expected_age_commitment == (NULL == h_age_commitment))
    450   {
    451     /* both absent, or both present: then they must differ */
    452     if ( (cac->no_expected_age_commitment) ||
    453          (0 ==
    454           GNUNET_memcmp (h_age_commitment,
    455                          &cac->expected_age_commitment_hash)) )
    456     {
    457       /* Must be a DIFFERENT age commitment, not a conflict! */
    458       GNUNET_break_op (0);
    459       return GNUNET_SYSERR;
    460     }
    461   }
    462   dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
    463                                                     &cac->h_denom_pub);
    464   if (NULL == dk)
    465   {
    466     /* TODO[oec]: as for the denomination conflict, the denomination is
    467        not in our /keys, so the signature cannot be checked; accept the
    468        response as unverifiable. */
    469     return GNUNET_NO;
    470   }
    471   if (GNUNET_OK !=
    472       TALER_test_coin_valid (&cpi,
    473                              &dk->key))
    474   {
    475     GNUNET_break_op (0);
    476     return GNUNET_SYSERR;
    477   }
    478   cac->verified = true;
    479   return GNUNET_OK;
    480 }
    481 
    482 
    483 void
    484 TALER_EXCHANGE_free_coin_age_commitment_conflict_ (
    485   struct TALER_EXCHANGE_CoinAgeCommitmentConflict *cac)
    486 {
    487   TALER_denom_sig_free (&cac->prev_denom_sig);
    488   cac->conflict_detail = NULL;
    489 }
    490 
    491 
    492 enum GNUNET_GenericReturnValue
    493 TALER_EXCHANGE_check_coin_conflict_ (
    494   const struct TALER_EXCHANGE_Keys *keys,
    495   enum TALER_ErrorCode ec,
    496   const json_t *proof,
    497   TALER_EXCHANGE_CoinLookupCallback_ lookup,
    498   void *lookup_cls,
    499   struct TALER_EXCHANGE_CoinConflict *cc)
    500 {
    501   const struct TALER_DenominationHashP *h_denom_pub = NULL;
    502   const struct TALER_AgeCommitmentHashP *h_age_commitment = NULL;
    503 
    504   cc->ec = TALER_EC_NONE;
    505   switch (ec)
    506   {
    507   case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
    508     {
    509       struct TALER_EXCHANGE_CoinDenominationConflict *cdc
    510         = &cc->details.denomination_conflict;
    511 
    512       if (GNUNET_OK !=
    513           TALER_EXCHANGE_parse_coin_denomination_conflict_ (proof,
    514                                                             cdc))
    515       {
    516         GNUNET_break_op (0);
    517         return GNUNET_SYSERR;
    518       }
    519       if ( (GNUNET_OK !=
    520             lookup (lookup_cls,
    521                     &cdc->coin_pub,
    522                     &h_denom_pub,
    523                     &h_age_commitment)) ||
    524            (GNUNET_SYSERR ==
    525             TALER_EXCHANGE_check_coin_denomination_conflict_ (keys,
    526                                                               h_denom_pub,
    527                                                               cdc)) )
    528       {
    529         GNUNET_break_op (0);
    530         TALER_EXCHANGE_free_coin_denomination_conflict_ (cdc);
    531         return GNUNET_SYSERR;
    532       }
    533       cc->ec = ec;
    534       return GNUNET_OK;
    535     }
    536   case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH:
    537     {
    538       struct TALER_EXCHANGE_CoinAgeCommitmentConflict *cac
    539         = &cc->details.age_commitment_conflict;
    540 
    541       if (GNUNET_OK !=
    542           TALER_EXCHANGE_parse_coin_age_commitment_conflict_ (proof,
    543                                                               cac))
    544       {
    545         GNUNET_break_op (0);
    546         return GNUNET_SYSERR;
    547       }
    548       if ( (GNUNET_OK !=
    549             lookup (lookup_cls,
    550                     &cac->coin_pub,
    551                     &h_denom_pub,
    552                     &h_age_commitment)) ||
    553            (GNUNET_SYSERR ==
    554             TALER_EXCHANGE_check_coin_age_commitment_conflict_ (
    555               keys,
    556               h_denom_pub,
    557               h_age_commitment,
    558               cac)) )
    559       {
    560         GNUNET_break_op (0);
    561         TALER_EXCHANGE_free_coin_age_commitment_conflict_ (cac);
    562         return GNUNET_SYSERR;
    563       }
    564       cc->ec = ec;
    565       return GNUNET_OK;
    566     }
    567   default:
    568     GNUNET_break (0);
    569     return GNUNET_SYSERR;
    570   }
    571 }
    572 
    573 
    574 void
    575 TALER_EXCHANGE_free_coin_conflict_ (
    576   const struct TALER_EXCHANGE_HttpResponse *hr,
    577   struct TALER_EXCHANGE_CoinConflict *cc)
    578 {
    579   if (MHD_HTTP_CONFLICT != hr->http_status)
    580     return;
    581   if (hr->ec != cc->ec)
    582     return;
    583   switch (cc->ec)
    584   {
    585   case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
    586     TALER_EXCHANGE_free_coin_denomination_conflict_ (
    587       &cc->details.denomination_conflict);
    588     break;
    589   case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH:
    590     TALER_EXCHANGE_free_coin_age_commitment_conflict_ (
    591       &cc->details.age_commitment_conflict);
    592     break;
    593   default:
    594     break;
    595   }
    596   cc->ec = TALER_EC_NONE;
    597 }
    598 
    599 
    600 enum GNUNET_GenericReturnValue
    601 TALER_EXCHANGE_get_min_denomination_ (
    602   const struct TALER_EXCHANGE_Keys *keys,
    603   struct TALER_Amount *min)
    604 {
    605   bool have_min = false;
    606   for (unsigned int i = 0; i<keys->num_denom_keys; i++)
    607   {
    608     const struct TALER_EXCHANGE_DenomPublicKey *dk = &keys->denom_keys[i];
    609 
    610     if (! have_min)
    611     {
    612       *min = dk->value;
    613       have_min = true;
    614       continue;
    615     }
    616     if (1 != TALER_amount_cmp (min,
    617                                &dk->value))
    618       continue;
    619     *min = dk->value;
    620   }
    621   if (! have_min)
    622   {
    623     GNUNET_break (0);
    624     return GNUNET_SYSERR;
    625   }
    626   return GNUNET_OK;
    627 }
    628 
    629 
    630 enum GNUNET_GenericReturnValue
    631 TALER_EXCHANGE_verify_deposit_signature_ (
    632   const struct TALER_EXCHANGE_DepositContractDetail *dcd,
    633   const struct TALER_ExtensionPolicyHashP *ech,
    634   const struct TALER_MerchantWireHashP *h_wire,
    635   const struct TALER_EXCHANGE_CoinDepositDetail *cdd,
    636   const struct TALER_EXCHANGE_DenomPublicKey *dki)
    637 {
    638   if (GNUNET_OK !=
    639       TALER_wallet_deposit_verify (&cdd->amount,
    640                                    &dki->fees.deposit,
    641                                    h_wire,
    642                                    &dcd->h_contract_terms,
    643                                    &dcd->wallet_data_hash,
    644                                    &cdd->h_age_commitment,
    645                                    ech,
    646                                    &cdd->h_denom_pub,
    647                                    dcd->wallet_timestamp,
    648                                    &dcd->merchant_pub,
    649                                    dcd->refund_deadline,
    650                                    &cdd->coin_pub,
    651                                    &cdd->coin_sig))
    652   {
    653     GNUNET_break_op (0);
    654     TALER_LOG_WARNING ("Invalid coin signature on /deposit request!\n");
    655     TALER_LOG_DEBUG ("... amount_with_fee was %s\n",
    656                      TALER_amount2s (&cdd->amount));
    657     TALER_LOG_DEBUG ("... deposit_fee was %s\n",
    658                      TALER_amount2s (&dki->fees.deposit));
    659     return GNUNET_SYSERR;
    660   }
    661 
    662   /* check coin signature */
    663   {
    664     struct TALER_CoinPublicInfo coin_info = {
    665       .coin_pub = cdd->coin_pub,
    666       .denom_pub_hash = cdd->h_denom_pub,
    667       .denom_sig = cdd->denom_sig,
    668       .h_age_commitment = cdd->h_age_commitment,
    669     };
    670 
    671     if (GNUNET_YES !=
    672         TALER_test_coin_valid (&coin_info,
    673                                &dki->key))
    674     {
    675       GNUNET_break_op (0);
    676       TALER_LOG_WARNING ("Invalid coin passed for /deposit\n");
    677       return GNUNET_SYSERR;
    678     }
    679   }
    680 
    681   /* Check coin does make a contribution */
    682   if (0 < TALER_amount_cmp (&dki->fees.deposit,
    683                             &cdd->amount))
    684   {
    685     GNUNET_break_op (0);
    686     TALER_LOG_WARNING ("Deposit amount smaller than fee\n");
    687     return GNUNET_SYSERR;
    688   }
    689   return GNUNET_OK;
    690 }
    691 
    692 
    693 /**
    694  * Parse account restriction in @a jrest into @a rest.
    695  *
    696  * @param jresta array of account restrictions in JSON
    697  * @param[out] resta_len set to length of @a resta
    698  * @param[out] resta account restriction array to set
    699  * @return #GNUNET_OK on success
    700  */
    701 static enum GNUNET_GenericReturnValue
    702 parse_restrictions (const json_t *jresta,
    703                     unsigned int *resta_len,
    704                     struct TALER_EXCHANGE_AccountRestriction **resta)
    705 {
    706   size_t alen;
    707 
    708   if (! json_is_array (jresta))
    709   {
    710     GNUNET_break_op (0);
    711     return GNUNET_SYSERR;
    712   }
    713   alen = json_array_size (jresta);
    714   if (0 == alen)
    715   {
    716     /* no restrictions, perfectly OK */
    717     *resta = NULL;
    718     return GNUNET_OK;
    719   }
    720   *resta_len = (unsigned int) alen;
    721   GNUNET_assert (alen == *resta_len);
    722   *resta = GNUNET_new_array (*resta_len,
    723                              struct TALER_EXCHANGE_AccountRestriction);
    724   for (unsigned int i = 0; i< *resta_len; i++)
    725   {
    726     const json_t *jr = json_array_get (jresta,
    727                                        i);
    728     struct TALER_EXCHANGE_AccountRestriction *ar = &(*resta)[i];
    729     const char *type = json_string_value (json_object_get (jr,
    730                                                            "type"));
    731 
    732     if (NULL == type)
    733     {
    734       GNUNET_break (0);
    735       goto fail;
    736     }
    737     if (0 == strcmp (type,
    738                      "deny"))
    739     {
    740       ar->type = TALER_EXCHANGE_AR_DENY;
    741       continue;
    742     }
    743     if (0 == strcmp (type,
    744                      "regex"))
    745     {
    746       const char *regex;
    747       const char *hint;
    748       struct GNUNET_JSON_Specification spec[] = {
    749         GNUNET_JSON_spec_string (
    750           "payto_regex",
    751           &regex),
    752         GNUNET_JSON_spec_string (
    753           "human_hint",
    754           &hint),
    755         GNUNET_JSON_spec_mark_optional (
    756           GNUNET_JSON_spec_json (
    757             "human_hint_i18n",
    758             &ar->details.regex.human_hint_i18n),
    759           NULL),
    760         GNUNET_JSON_spec_end ()
    761       };
    762 
    763       if (GNUNET_OK !=
    764           GNUNET_JSON_parse (jr,
    765                              spec,
    766                              NULL, NULL))
    767       {
    768         /* bogus reply */
    769         GNUNET_break_op (0);
    770         goto fail;
    771       }
    772       ar->type = TALER_EXCHANGE_AR_REGEX;
    773       ar->details.regex.posix_egrep = GNUNET_strdup (regex);
    774       ar->details.regex.human_hint = GNUNET_strdup (hint);
    775       continue;
    776     }
    777     /* unsupported type */
    778     GNUNET_break (0);
    779     goto fail;
    780   }
    781   return GNUNET_OK;
    782 fail:
    783   for (unsigned int i = 0; i<*resta_len; i++)
    784   {
    785     struct TALER_EXCHANGE_AccountRestriction *ar = &(*resta)[i];
    786 
    787     if (TALER_EXCHANGE_AR_REGEX == ar->type)
    788     {
    789       GNUNET_free (ar->details.regex.posix_egrep);
    790       GNUNET_free (ar->details.regex.human_hint);
    791       json_decref (ar->details.regex.human_hint_i18n);
    792     }
    793   }
    794   GNUNET_free (*resta);
    795   *resta_len = 0;
    796   return GNUNET_SYSERR;
    797 }
    798 
    799 
    800 enum GNUNET_GenericReturnValue
    801 TALER_EXCHANGE_parse_accounts (
    802   const struct TALER_MasterPublicKeyP *master_pub,
    803   const json_t *accounts,
    804   unsigned int was_length,
    805   struct TALER_EXCHANGE_WireAccount was[static was_length])
    806 {
    807   memset (was,
    808           0,
    809           sizeof (struct TALER_EXCHANGE_WireAccount) * was_length);
    810   GNUNET_assert (was_length ==
    811                  json_array_size (accounts));
    812   for (unsigned int i = 0;
    813        i<was_length;
    814        i++)
    815   {
    816     struct TALER_EXCHANGE_WireAccount *wa = &was[i];
    817     struct TALER_FullPayto payto_uri;
    818     const char *conversion_url = NULL;
    819     const char *open_banking_gateway = NULL;
    820     const char *prepared_transfer_url = NULL;
    821     const char *bank_label = NULL;
    822     int64_t priority = 0;
    823     const json_t *credit_restrictions;
    824     const json_t *debit_restrictions;
    825     struct GNUNET_JSON_Specification spec_account[] = {
    826       TALER_JSON_spec_full_payto_uri ("payto_uri",
    827                                       &payto_uri),
    828       GNUNET_JSON_spec_mark_optional (
    829         TALER_JSON_spec_web_url ("conversion_url",
    830                                  &conversion_url),
    831         NULL),
    832       GNUNET_JSON_spec_mark_optional (
    833         TALER_JSON_spec_web_url ("open_banking_gateway",
    834                                  &open_banking_gateway),
    835         NULL),
    836       GNUNET_JSON_spec_mark_optional (
    837         TALER_JSON_spec_web_url ("prepared_transfer_url",
    838                                  &prepared_transfer_url),
    839         NULL),
    840       GNUNET_JSON_spec_mark_optional (
    841         GNUNET_JSON_spec_int64 ("priority",
    842                                 &priority),
    843         NULL),
    844       GNUNET_JSON_spec_mark_optional (
    845         GNUNET_JSON_spec_string ("bank_label",
    846                                  &bank_label),
    847         NULL),
    848       GNUNET_JSON_spec_array_const ("credit_restrictions",
    849                                     &credit_restrictions),
    850       GNUNET_JSON_spec_array_const ("debit_restrictions",
    851                                     &debit_restrictions),
    852       GNUNET_JSON_spec_fixed_auto ("master_sig",
    853                                    &wa->master_sig),
    854       GNUNET_JSON_spec_end ()
    855     };
    856     json_t *account;
    857 
    858     account = json_array_get (accounts,
    859                               i);
    860     if (GNUNET_OK !=
    861         GNUNET_JSON_parse (account,
    862                            spec_account,
    863                            NULL, NULL))
    864     {
    865       /* bogus reply */
    866       GNUNET_break_op (0);
    867       return GNUNET_SYSERR;
    868     }
    869     if ( (NULL != master_pub) &&
    870          (! ( ( (NULL == open_banking_gateway) &&
    871                 (NULL == prepared_transfer_url) &&
    872                 (GNUNET_OK ==
    873                  TALER_exchange_wire_signature_check32 (
    874                    payto_uri,
    875                    conversion_url,
    876                    debit_restrictions,
    877                    credit_restrictions,
    878                    master_pub,
    879                    &wa->master_sig)) ) ||
    880               (GNUNET_OK ==
    881                TALER_exchange_wire_signature_check (
    882                  payto_uri,
    883                  conversion_url,
    884                  open_banking_gateway,
    885                  prepared_transfer_url,
    886                  debit_restrictions,
    887                  credit_restrictions,
    888                  master_pub,
    889                  &wa->master_sig)) ) ) )
    890     {
    891       /* bogus reply */
    892       GNUNET_break_op (0);
    893       return GNUNET_SYSERR;
    894     }
    895     if ( (GNUNET_OK !=
    896           parse_restrictions (credit_restrictions,
    897                               &wa->credit_restrictions_length,
    898                               &wa->credit_restrictions)) ||
    899          (GNUNET_OK !=
    900           parse_restrictions (debit_restrictions,
    901                               &wa->debit_restrictions_length,
    902                               &wa->debit_restrictions)) )
    903     {
    904       /* bogus reply */
    905       GNUNET_break_op (0);
    906       return GNUNET_SYSERR;
    907     }
    908     wa->fpayto_uri.full_payto
    909       = GNUNET_strdup (payto_uri.full_payto);
    910     wa->priority = priority;
    911     if (NULL != conversion_url)
    912       wa->conversion_url = GNUNET_strdup (conversion_url);
    913     if (NULL != open_banking_gateway)
    914       wa->open_banking_gateway = GNUNET_strdup (open_banking_gateway);
    915     if (NULL != prepared_transfer_url)
    916       wa->prepared_transfer_url = GNUNET_strdup (prepared_transfer_url);
    917     if (NULL != bank_label)
    918       wa->bank_label = GNUNET_strdup (bank_label);
    919   }       /* end 'for all accounts */
    920   return GNUNET_OK;
    921 }
    922 
    923 
    924 /**
    925  * Free array of account restrictions.
    926  *
    927  * @param ar_len length of @a ar
    928  * @param[in] ar array to free contents of (but not @a ar itself)
    929  */
    930 static void
    931 free_restrictions (unsigned int ar_len,
    932                    struct TALER_EXCHANGE_AccountRestriction ar[static ar_len])
    933 {
    934   for (unsigned int i = 0; i<ar_len; i++)
    935   {
    936     struct TALER_EXCHANGE_AccountRestriction *a = &ar[i];
    937     switch (a->type)
    938     {
    939     case TALER_EXCHANGE_AR_INVALID:
    940       GNUNET_break (0);
    941       break;
    942     case TALER_EXCHANGE_AR_DENY:
    943       break;
    944     case TALER_EXCHANGE_AR_REGEX:
    945       GNUNET_free (a->details.regex.posix_egrep);
    946       GNUNET_free (a->details.regex.human_hint);
    947       json_decref (a->details.regex.human_hint_i18n);
    948       break;
    949     }
    950   }
    951 }
    952 
    953 
    954 void
    955 TALER_EXCHANGE_free_accounts (
    956   unsigned int was_len,
    957   struct TALER_EXCHANGE_WireAccount was[static was_len])
    958 {
    959   for (unsigned int i = 0; i<was_len; i++)
    960   {
    961     struct TALER_EXCHANGE_WireAccount *wa = &was[i];
    962 
    963     GNUNET_free (wa->fpayto_uri.full_payto);
    964     GNUNET_free (wa->conversion_url);
    965     GNUNET_free (wa->open_banking_gateway);
    966     GNUNET_free (wa->prepared_transfer_url);
    967     GNUNET_free (wa->bank_label);
    968     free_restrictions (wa->credit_restrictions_length,
    969                        wa->credit_restrictions);
    970     GNUNET_array_grow (wa->credit_restrictions,
    971                        wa->credit_restrictions_length,
    972                        0);
    973     free_restrictions (wa->debit_restrictions_length,
    974                        wa->debit_restrictions);
    975     GNUNET_array_grow (wa->debit_restrictions,
    976                        wa->debit_restrictions_length,
    977                        0);
    978   }
    979 }
    980 
    981 
    982 enum GNUNET_GenericReturnValue
    983 TALER_EXCHANGE_keys_test_account_allowed (
    984   const struct TALER_EXCHANGE_Keys *keys,
    985   bool check_credit,
    986   const struct TALER_NormalizedPayto payto_uri)
    987 {
    988   /* For all accounts of the exchange */
    989   for (unsigned int i = 0; i<keys->accounts_len; i++)
    990   {
    991     const struct TALER_EXCHANGE_WireAccount *account
    992       = &keys->accounts[i];
    993 
    994     /* KYC auth transfers are never supported with conversion */
    995     if (NULL != account->conversion_url)
    996       continue;
    997     /* filter by source account by credit_restrictions */
    998     if (GNUNET_YES !=
    999         TALER_EXCHANGE_test_account_allowed (account,
   1000                                              check_credit,
   1001                                              payto_uri))
   1002       continue;
   1003     /* exchange account is allowed, add it */
   1004     return true;
   1005   }
   1006   return false;
   1007 }
   1008 
   1009 
   1010 /**
   1011  * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS response code.
   1012  * Parse the JSON response and initialize the @a uflr object.
   1013  *
   1014  * @param[out] uflr data structure to initialize
   1015  * @param j JSON response to parse
   1016  * @return #GNUNET_OK on success
   1017  */
   1018 enum GNUNET_GenericReturnValue
   1019 TALER_EXCHANGE_parse_451 (struct TALER_EXCHANGE_KycNeededRedirect *uflr,
   1020                           const json_t *j)
   1021 {
   1022   struct GNUNET_JSON_Specification spec[] = {
   1023     GNUNET_JSON_spec_fixed_auto (
   1024       "h_payto",
   1025       &uflr->h_payto),
   1026     GNUNET_JSON_spec_uint64 (
   1027       "requirement_row",
   1028       &uflr->requirement_row),
   1029     GNUNET_JSON_spec_mark_optional (
   1030       GNUNET_JSON_spec_fixed_auto (
   1031         "account_pub",
   1032         &uflr->account_pub),
   1033       NULL),
   1034     GNUNET_JSON_spec_mark_optional (
   1035       GNUNET_JSON_spec_bool (
   1036         "bad_kyc_auth",
   1037         &uflr->bad_kyc_auth),
   1038       NULL),
   1039     GNUNET_JSON_spec_end ()
   1040   };
   1041 
   1042   if (GNUNET_OK !=
   1043       GNUNET_JSON_parse (j,
   1044                          spec,
   1045                          NULL,
   1046                          NULL))
   1047   {
   1048     GNUNET_break_op (0);
   1049     return GNUNET_SYSERR;
   1050   }
   1051   return GNUNET_OK;
   1052 }
   1053 
   1054 
   1055 json_t *
   1056 TALER_EXCHANGE_recoup_coin_data_ (
   1057   size_t num_coins,
   1058   const struct TALER_EXCHANGE_RecoupCoin coins[static num_coins],
   1059   const struct TALER_BlindingMasterSeedP *blinding_seed,
   1060   bool for_melt,
   1061   struct TALER_CoinSpendPublicKeyP recouped_pubs[static num_coins],
   1062   struct TALER_EXCHANGE_RecoupedCoinInfo_ recouped_infos[static num_coins],
   1063   size_t *num_recouped)
   1064 {
   1065   json_t *arr = json_array ();
   1066 
   1067   GNUNET_assert (NULL != arr);
   1068   *num_recouped = 0;
   1069   for (size_t i = 0; i < num_coins; i++)
   1070   {
   1071     const struct TALER_EXCHANGE_RecoupCoin *c = &coins[i];
   1072     struct TALER_CoinSpendPrivateKeyP coin_priv;
   1073     struct TALER_CoinSpendPublicKeyP coin_pub;
   1074     union GNUNET_CRYPTO_BlindingSecretP bks;
   1075     union GNUNET_CRYPTO_BlindSessionNonce nonce;
   1076     const union GNUNET_CRYPTO_BlindSessionNonce *np = NULL;
   1077     json_t *entry;
   1078 
   1079     const struct TALER_ExchangeBlindingValues *alg_values
   1080       = c->exchange_vals;
   1081 
   1082     if ( (NULL == c->pk) ||
   1083          (NULL == c->ps) )
   1084     {
   1085       GNUNET_break (0);
   1086       json_decref (arr);
   1087       return NULL;
   1088     }
   1089     if (NULL == alg_values)
   1090     {
   1091       /* documented for RSA denominations, which have no
   1092          exchange-contributed blinding values */
   1093       if (GNUNET_CRYPTO_BSA_RSA != c->pk->key.bsign_pub_key->cipher)
   1094       {
   1095         GNUNET_break (0);
   1096         json_decref (arr);
   1097         return NULL;
   1098       }
   1099       alg_values = TALER_denom_ewv_rsa_singleton ();
   1100     }
   1101     TALER_planchet_setup_coin_priv (c->ps,
   1102                                     alg_values,
   1103                                     &coin_priv);
   1104     TALER_planchet_blinding_secret_create (c->ps,
   1105                                            alg_values,
   1106                                            &bks);
   1107     GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
   1108                                         &coin_pub.eddsa_pub);
   1109     if (GNUNET_CRYPTO_BSA_CS == c->pk->key.bsign_pub_key->cipher)
   1110     {
   1111       if (NULL == blinding_seed)
   1112       {
   1113         GNUNET_break (0);
   1114         json_decref (arr);
   1115         return NULL;
   1116       }
   1117       TALER_cs_nonce_derive_indexed (blinding_seed,
   1118                                      for_melt,
   1119                                      (uint32_t) i,
   1120                                      &nonce.cs_nonce);
   1121       np = &nonce;
   1122     }
   1123     if (NULL == c->denom_sig)
   1124     {
   1125       struct TALER_PlanchetDetail pd;
   1126       struct TALER_CoinPubHashP c_hash;
   1127       struct TALER_BlindedCoinHashP bch;
   1128 
   1129       if (GNUNET_OK !=
   1130           TALER_planchet_prepare (&c->pk->key,
   1131                                   alg_values,
   1132                                   &bks,
   1133                                   np,
   1134                                   &coin_priv,
   1135                                   c->h_age_commitment,
   1136                                   &c_hash,
   1137                                   &pd))
   1138       {
   1139         GNUNET_break (0);
   1140         json_decref (arr);
   1141         return NULL;
   1142       }
   1143       TALER_coin_ev_hash (&pd.blinded_planchet,
   1144                           &c->pk->h_key,
   1145                           &bch);
   1146       TALER_blinded_planchet_free (&pd.blinded_planchet);
   1147       entry = GNUNET_JSON_PACK (
   1148         GNUNET_JSON_pack_string ("type",
   1149                                  "hash"),
   1150         GNUNET_JSON_pack_data_auto ("h_coin_ev",
   1151                                     &bch));
   1152     }
   1153     else
   1154     {
   1155       struct TALER_CoinSpendSignatureP coin_sig;
   1156 
   1157       if (for_melt)
   1158         TALER_wallet_recoup_refresh_sign (&c->pk->h_key,
   1159                                           &bks,
   1160                                           &coin_priv,
   1161                                           &coin_sig);
   1162       else
   1163         TALER_wallet_recoup_sign (&c->pk->h_key,
   1164                                   &bks,
   1165                                   &coin_priv,
   1166                                   &coin_sig);
   1167       entry = GNUNET_JSON_PACK (
   1168         GNUNET_JSON_pack_string ("type",
   1169                                  "recoup"),
   1170         GNUNET_JSON_pack_data_auto ("coin_pub",
   1171                                     &coin_pub),
   1172         GNUNET_JSON_pack_data_auto ("denom_pub_h",
   1173                                     &c->pk->h_key),
   1174         TALER_JSON_pack_denom_sig ("denom_sig",
   1175                                    c->denom_sig),
   1176         GNUNET_JSON_pack_data_auto ("coin_blinding_secret",
   1177                                     &bks),
   1178         GNUNET_JSON_pack_allow_null (
   1179           GNUNET_JSON_pack_data_varsize ("h_age_commitment",
   1180                                          c->h_age_commitment,
   1181                                          (NULL == c->h_age_commitment)
   1182                                          ? 0
   1183                                          : sizeof (*c->h_age_commitment))),
   1184         GNUNET_JSON_pack_data_auto ("coin_sig",
   1185                                     &coin_sig));
   1186       recouped_pubs[*num_recouped] = coin_pub;
   1187       recouped_infos[*num_recouped].h_denom_pub = c->pk->h_key;
   1188       recouped_infos[*num_recouped].have_age = (NULL != c->h_age_commitment);
   1189       if (NULL != c->h_age_commitment)
   1190         recouped_infos[*num_recouped].h_age_commitment = *c->h_age_commitment;
   1191       (*num_recouped)++;
   1192     }
   1193     GNUNET_assert (0 ==
   1194                    json_array_append_new (arr,
   1195                                           entry));
   1196   }
   1197   if (0 == *num_recouped)
   1198   {
   1199     GNUNET_break (0);
   1200     json_decref (arr);
   1201     return NULL;
   1202   }
   1203   return arr;
   1204 }
   1205 
   1206 
   1207 enum GNUNET_GenericReturnValue
   1208 TALER_EXCHANGE_parse_recoups_ (
   1209   const json_t *j_recoups,
   1210   size_t num_expected,
   1211   const struct TALER_CoinSpendPublicKeyP expected_pubs[static num_expected],
   1212   const struct TALER_Amount *total_amount,
   1213   struct TALER_RecoupedCoin **recoups,
   1214   struct GNUNET_HashCode *h_recoups)
   1215 {
   1216   struct TALER_RecoupedCoin *rcs;
   1217   struct TALER_Amount sum;
   1218   size_t idx;
   1219   json_t *entry;
   1220 
   1221   *recoups = NULL;
   1222   if ( (! json_is_array (j_recoups)) ||
   1223        (num_expected != json_array_size (j_recoups)) )
   1224   {
   1225     GNUNET_break_op (0);
   1226     return GNUNET_SYSERR;
   1227   }
   1228   rcs = GNUNET_new_array (num_expected,
   1229                           struct TALER_RecoupedCoin);
   1230   GNUNET_assert (GNUNET_OK ==
   1231                  TALER_amount_set_zero (total_amount->currency,
   1232                                         &sum));
   1233   json_array_foreach (j_recoups, idx, entry)
   1234   {
   1235     struct GNUNET_JSON_Specification spec[] = {
   1236       GNUNET_JSON_spec_fixed_auto ("coin_pub",
   1237                                    &rcs[idx].coin_pub),
   1238       TALER_JSON_spec_amount_any ("amount",
   1239                                   &rcs[idx].amount),
   1240       GNUNET_JSON_spec_end ()
   1241     };
   1242 
   1243     if (GNUNET_OK !=
   1244         GNUNET_JSON_parse (entry,
   1245                            spec,
   1246                            NULL, NULL))
   1247     {
   1248       GNUNET_break_op (0);
   1249       GNUNET_free (rcs);
   1250       return GNUNET_SYSERR;
   1251     }
   1252     if (0 !=
   1253         GNUNET_memcmp (&rcs[idx].coin_pub,
   1254                        &expected_pubs[idx]))
   1255     {
   1256       GNUNET_break_op (0);
   1257       GNUNET_free (rcs);
   1258       return GNUNET_SYSERR;
   1259     }
   1260     if ( (GNUNET_OK !=
   1261           TALER_amount_cmp_currency (&sum,
   1262                                      &rcs[idx].amount)) ||
   1263          (0 >
   1264           TALER_amount_add (&sum,
   1265                             &sum,
   1266                             &rcs[idx].amount)) )
   1267     {
   1268       GNUNET_break_op (0);
   1269       GNUNET_free (rcs);
   1270       return GNUNET_SYSERR;
   1271     }
   1272   }
   1273   if ( (GNUNET_OK !=
   1274         TALER_amount_cmp_currency (&sum,
   1275                                    total_amount)) ||
   1276        (0 !=
   1277         TALER_amount_cmp (&sum,
   1278                           total_amount)) )
   1279   {
   1280     GNUNET_break_op (0);
   1281     GNUNET_free (rcs);
   1282     return GNUNET_SYSERR;
   1283   }
   1284   TALER_recoup_batch_hash (num_expected,
   1285                            rcs,
   1286                            h_recoups);
   1287   *recoups = rcs;
   1288   return GNUNET_OK;
   1289 }
   1290 
   1291 
   1292 /* end of exchange_api_common.c */