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


      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/platform.h"
     23 #include "taler/taler_json_lib.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 // FIXME: should be used... - #9422
    279 enum GNUNET_GenericReturnValue
    280 TALER_EXCHANGE_check_coin_denomination_conflict_ (
    281   const json_t *proof,
    282   const struct TALER_DenominationHashP *ch_denom_pub)
    283 {
    284   struct TALER_DenominationHashP h_denom_pub;
    285   struct GNUNET_JSON_Specification spec[] = {
    286     GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
    287                                  &h_denom_pub),
    288     GNUNET_JSON_spec_end ()
    289   };
    290 
    291   if (GNUNET_OK !=
    292       GNUNET_JSON_parse (proof,
    293                          spec,
    294                          NULL, NULL))
    295   {
    296     GNUNET_break_op (0);
    297     return GNUNET_SYSERR;
    298   }
    299   if (0 ==
    300       GNUNET_memcmp (ch_denom_pub,
    301                      &h_denom_pub))
    302   {
    303     GNUNET_break_op (0);
    304     return GNUNET_OK;
    305   }
    306   /* indeed, proof with different denomination key provided */
    307   return GNUNET_OK;
    308 }
    309 
    310 
    311 enum GNUNET_GenericReturnValue
    312 TALER_EXCHANGE_get_min_denomination_ (
    313   const struct TALER_EXCHANGE_Keys *keys,
    314   struct TALER_Amount *min)
    315 {
    316   bool have_min = false;
    317   for (unsigned int i = 0; i<keys->num_denom_keys; i++)
    318   {
    319     const struct TALER_EXCHANGE_DenomPublicKey *dk = &keys->denom_keys[i];
    320 
    321     if (! have_min)
    322     {
    323       *min = dk->value;
    324       have_min = true;
    325       continue;
    326     }
    327     if (1 != TALER_amount_cmp (min,
    328                                &dk->value))
    329       continue;
    330     *min = dk->value;
    331   }
    332   if (! have_min)
    333   {
    334     GNUNET_break (0);
    335     return GNUNET_SYSERR;
    336   }
    337   return GNUNET_OK;
    338 }
    339 
    340 
    341 enum GNUNET_GenericReturnValue
    342 TALER_EXCHANGE_verify_deposit_signature_ (
    343   const struct TALER_EXCHANGE_DepositContractDetail *dcd,
    344   const struct TALER_ExtensionPolicyHashP *ech,
    345   const struct TALER_MerchantWireHashP *h_wire,
    346   const struct TALER_EXCHANGE_CoinDepositDetail *cdd,
    347   const struct TALER_EXCHANGE_DenomPublicKey *dki)
    348 {
    349   if (GNUNET_OK !=
    350       TALER_wallet_deposit_verify (&cdd->amount,
    351                                    &dki->fees.deposit,
    352                                    h_wire,
    353                                    &dcd->h_contract_terms,
    354                                    &dcd->wallet_data_hash,
    355                                    &cdd->h_age_commitment,
    356                                    ech,
    357                                    &cdd->h_denom_pub,
    358                                    dcd->wallet_timestamp,
    359                                    &dcd->merchant_pub,
    360                                    dcd->refund_deadline,
    361                                    &cdd->coin_pub,
    362                                    &cdd->coin_sig))
    363   {
    364     GNUNET_break_op (0);
    365     TALER_LOG_WARNING ("Invalid coin signature on /deposit request!\n");
    366     TALER_LOG_DEBUG ("... amount_with_fee was %s\n",
    367                      TALER_amount2s (&cdd->amount));
    368     TALER_LOG_DEBUG ("... deposit_fee was %s\n",
    369                      TALER_amount2s (&dki->fees.deposit));
    370     return GNUNET_SYSERR;
    371   }
    372 
    373   /* check coin signature */
    374   {
    375     struct TALER_CoinPublicInfo coin_info = {
    376       .coin_pub = cdd->coin_pub,
    377       .denom_pub_hash = cdd->h_denom_pub,
    378       .denom_sig = cdd->denom_sig,
    379       .h_age_commitment = cdd->h_age_commitment,
    380     };
    381 
    382     if (GNUNET_YES !=
    383         TALER_test_coin_valid (&coin_info,
    384                                &dki->key))
    385     {
    386       GNUNET_break_op (0);
    387       TALER_LOG_WARNING ("Invalid coin passed for /deposit\n");
    388       return GNUNET_SYSERR;
    389     }
    390   }
    391 
    392   /* Check coin does make a contribution */
    393   if (0 < TALER_amount_cmp (&dki->fees.deposit,
    394                             &cdd->amount))
    395   {
    396     GNUNET_break_op (0);
    397     TALER_LOG_WARNING ("Deposit amount smaller than fee\n");
    398     return GNUNET_SYSERR;
    399   }
    400   return GNUNET_OK;
    401 }
    402 
    403 
    404 /**
    405  * Parse account restriction in @a jrest into @a rest.
    406  *
    407  * @param jresta array of account restrictions in JSON
    408  * @param[out] resta_len set to length of @a resta
    409  * @param[out] resta account restriction array to set
    410  * @return #GNUNET_OK on success
    411  */
    412 static enum GNUNET_GenericReturnValue
    413 parse_restrictions (const json_t *jresta,
    414                     unsigned int *resta_len,
    415                     struct TALER_EXCHANGE_AccountRestriction **resta)
    416 {
    417   size_t alen;
    418 
    419   if (! json_is_array (jresta))
    420   {
    421     GNUNET_break_op (0);
    422     return GNUNET_SYSERR;
    423   }
    424   alen = json_array_size (jresta);
    425   if (0 == alen)
    426   {
    427     /* no restrictions, perfectly OK */
    428     *resta = NULL;
    429     return GNUNET_OK;
    430   }
    431   *resta_len = (unsigned int) alen;
    432   GNUNET_assert (alen == *resta_len);
    433   *resta = GNUNET_new_array (*resta_len,
    434                              struct TALER_EXCHANGE_AccountRestriction);
    435   for (unsigned int i = 0; i<*resta_len; i++)
    436   {
    437     const json_t *jr = json_array_get (jresta,
    438                                        i);
    439     struct TALER_EXCHANGE_AccountRestriction *ar = &(*resta)[i];
    440     const char *type = json_string_value (json_object_get (jr,
    441                                                            "type"));
    442 
    443     if (NULL == type)
    444     {
    445       GNUNET_break (0);
    446       goto fail;
    447     }
    448     if (0 == strcmp (type,
    449                      "deny"))
    450     {
    451       ar->type = TALER_EXCHANGE_AR_DENY;
    452       continue;
    453     }
    454     if (0 == strcmp (type,
    455                      "regex"))
    456     {
    457       const char *regex;
    458       const char *hint;
    459       struct GNUNET_JSON_Specification spec[] = {
    460         GNUNET_JSON_spec_string (
    461           "payto_regex",
    462           &regex),
    463         GNUNET_JSON_spec_string (
    464           "human_hint",
    465           &hint),
    466         GNUNET_JSON_spec_mark_optional (
    467           GNUNET_JSON_spec_json (
    468             "human_hint_i18n",
    469             &ar->details.regex.human_hint_i18n),
    470           NULL),
    471         GNUNET_JSON_spec_end ()
    472       };
    473 
    474       if (GNUNET_OK !=
    475           GNUNET_JSON_parse (jr,
    476                              spec,
    477                              NULL, NULL))
    478       {
    479         /* bogus reply */
    480         GNUNET_break_op (0);
    481         goto fail;
    482       }
    483       ar->type = TALER_EXCHANGE_AR_REGEX;
    484       ar->details.regex.posix_egrep = GNUNET_strdup (regex);
    485       ar->details.regex.human_hint = GNUNET_strdup (hint);
    486       continue;
    487     }
    488     /* unsupported type */
    489     GNUNET_break (0);
    490     return GNUNET_SYSERR;
    491   }
    492   return GNUNET_OK;
    493 fail:
    494   GNUNET_free (*resta);
    495   *resta_len = 0;
    496   return GNUNET_SYSERR;
    497 }
    498 
    499 
    500 enum GNUNET_GenericReturnValue
    501 TALER_EXCHANGE_parse_accounts (
    502   const struct TALER_MasterPublicKeyP *master_pub,
    503   const json_t *accounts,
    504   unsigned int was_length,
    505   struct TALER_EXCHANGE_WireAccount was[static was_length])
    506 {
    507   memset (was,
    508           0,
    509           sizeof (struct TALER_EXCHANGE_WireAccount) * was_length);
    510   GNUNET_assert (was_length ==
    511                  json_array_size (accounts));
    512   for (unsigned int i = 0;
    513        i<was_length;
    514        i++)
    515   {
    516     struct TALER_EXCHANGE_WireAccount *wa = &was[i];
    517     struct TALER_FullPayto payto_uri;
    518     const char *conversion_url = NULL;
    519     const char *open_banking_gateway = NULL;
    520     const char *wire_transfer_gateway = NULL;
    521     const char *bank_label = NULL;
    522     int64_t priority = 0;
    523     const json_t *credit_restrictions;
    524     const json_t *debit_restrictions;
    525     struct GNUNET_JSON_Specification spec_account[] = {
    526       TALER_JSON_spec_full_payto_uri ("payto_uri",
    527                                       &payto_uri),
    528       GNUNET_JSON_spec_mark_optional (
    529         TALER_JSON_spec_web_url ("conversion_url",
    530                                  &conversion_url),
    531         NULL),
    532       GNUNET_JSON_spec_mark_optional (
    533         GNUNET_JSON_spec_string ("open_banking_gateway",
    534                                  &open_banking_gateway),
    535         NULL),
    536       GNUNET_JSON_spec_mark_optional (
    537         GNUNET_JSON_spec_string ("wire_transfer_gateway",
    538                                  &wire_transfer_gateway),
    539         NULL),
    540       GNUNET_JSON_spec_mark_optional (
    541         GNUNET_JSON_spec_int64 ("priority",
    542                                 &priority),
    543         NULL),
    544       GNUNET_JSON_spec_mark_optional (
    545         GNUNET_JSON_spec_string ("bank_label",
    546                                  &bank_label),
    547         NULL),
    548       GNUNET_JSON_spec_array_const ("credit_restrictions",
    549                                     &credit_restrictions),
    550       GNUNET_JSON_spec_array_const ("debit_restrictions",
    551                                     &debit_restrictions),
    552       GNUNET_JSON_spec_fixed_auto ("master_sig",
    553                                    &wa->master_sig),
    554       GNUNET_JSON_spec_end ()
    555     };
    556     json_t *account;
    557 
    558     account = json_array_get (accounts,
    559                               i);
    560     if (GNUNET_OK !=
    561         GNUNET_JSON_parse (account,
    562                            spec_account,
    563                            NULL, NULL))
    564     {
    565       /* bogus reply */
    566       GNUNET_break_op (0);
    567       return GNUNET_SYSERR;
    568     }
    569     if ( (NULL != master_pub) &&
    570          (! ( ( (NULL == open_banking_gateway) &&
    571                 (NULL == wire_transfer_gateway) &&
    572                 (GNUNET_OK ==
    573                  TALER_exchange_wire_signature_check32 (
    574                    payto_uri,
    575                    conversion_url,
    576                    debit_restrictions,
    577                    credit_restrictions,
    578                    master_pub,
    579                    &wa->master_sig)) ) ||
    580               (GNUNET_OK ==
    581                TALER_exchange_wire_signature_check (
    582                  payto_uri,
    583                  conversion_url,
    584                  open_banking_gateway,
    585                  wire_transfer_gateway,
    586                  debit_restrictions,
    587                  credit_restrictions,
    588                  master_pub,
    589                  &wa->master_sig)) ) ) )
    590     {
    591       /* bogus reply */
    592       GNUNET_break_op (0);
    593       return GNUNET_SYSERR;
    594     }
    595     if ( (GNUNET_OK !=
    596           parse_restrictions (credit_restrictions,
    597                               &wa->credit_restrictions_length,
    598                               &wa->credit_restrictions)) ||
    599          (GNUNET_OK !=
    600           parse_restrictions (debit_restrictions,
    601                               &wa->debit_restrictions_length,
    602                               &wa->debit_restrictions)) )
    603     {
    604       /* bogus reply */
    605       GNUNET_break_op (0);
    606       return GNUNET_SYSERR;
    607     }
    608     wa->fpayto_uri.full_payto
    609       = GNUNET_strdup (payto_uri.full_payto);
    610     wa->priority = priority;
    611     if (NULL != conversion_url)
    612       wa->conversion_url = GNUNET_strdup (conversion_url);
    613     if (NULL != open_banking_gateway)
    614       wa->open_banking_gateway = GNUNET_strdup (open_banking_gateway);
    615     if (NULL != wire_transfer_gateway)
    616       wa->wire_transfer_gateway = GNUNET_strdup (wire_transfer_gateway);
    617     if (NULL != bank_label)
    618       wa->bank_label = GNUNET_strdup (bank_label);
    619   }       /* end 'for all accounts */
    620   return GNUNET_OK;
    621 }
    622 
    623 
    624 /**
    625  * Free array of account restrictions.
    626  *
    627  * @param ar_len length of @a ar
    628  * @param[in] ar array to free contents of (but not @a ar itself)
    629  */
    630 static void
    631 free_restrictions (unsigned int ar_len,
    632                    struct TALER_EXCHANGE_AccountRestriction ar[static ar_len])
    633 {
    634   for (unsigned int i = 0; i<ar_len; i++)
    635   {
    636     struct TALER_EXCHANGE_AccountRestriction *a = &ar[i];
    637     switch (a->type)
    638     {
    639     case TALER_EXCHANGE_AR_INVALID:
    640       GNUNET_break (0);
    641       break;
    642     case TALER_EXCHANGE_AR_DENY:
    643       break;
    644     case TALER_EXCHANGE_AR_REGEX:
    645       GNUNET_free (ar->details.regex.posix_egrep);
    646       GNUNET_free (ar->details.regex.human_hint);
    647       json_decref (ar->details.regex.human_hint_i18n);
    648       break;
    649     }
    650   }
    651 }
    652 
    653 
    654 void
    655 TALER_EXCHANGE_free_accounts (
    656   unsigned int was_len,
    657   struct TALER_EXCHANGE_WireAccount was[static was_len])
    658 {
    659   for (unsigned int i = 0; i<was_len; i++)
    660   {
    661     struct TALER_EXCHANGE_WireAccount *wa = &was[i];
    662 
    663     GNUNET_free (wa->fpayto_uri.full_payto);
    664     GNUNET_free (wa->conversion_url);
    665     GNUNET_free (wa->open_banking_gateway);
    666     GNUNET_free (wa->wire_transfer_gateway);
    667     GNUNET_free (wa->bank_label);
    668     free_restrictions (wa->credit_restrictions_length,
    669                        wa->credit_restrictions);
    670     GNUNET_array_grow (wa->credit_restrictions,
    671                        wa->credit_restrictions_length,
    672                        0);
    673     free_restrictions (wa->debit_restrictions_length,
    674                        wa->debit_restrictions);
    675     GNUNET_array_grow (wa->debit_restrictions,
    676                        wa->debit_restrictions_length,
    677                        0);
    678   }
    679 }
    680 
    681 
    682 enum GNUNET_GenericReturnValue
    683 TALER_EXCHANGE_keys_test_account_allowed (
    684   const struct TALER_EXCHANGE_Keys *keys,
    685   bool check_credit,
    686   const struct TALER_NormalizedPayto payto_uri)
    687 {
    688   /* For all accounts of the exchange */
    689   for (unsigned int i = 0; i<keys->accounts_len; i++)
    690   {
    691     const struct TALER_EXCHANGE_WireAccount *account
    692       = &keys->accounts[i];
    693 
    694     /* KYC auth transfers are never supported with conversion */
    695     if (NULL != account->conversion_url)
    696       continue;
    697     /* filter by source account by credit_restrictions */
    698     if (GNUNET_YES !=
    699         TALER_EXCHANGE_test_account_allowed (account,
    700                                              check_credit,
    701                                              payto_uri))
    702       continue;
    703     /* exchange account is allowed, add it */
    704     return true;
    705   }
    706   return false;
    707 }
    708 
    709 
    710 /* end of exchange_api_common.c */