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


      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 *bank_label = NULL;
    520     int64_t priority = 0;
    521     const json_t *credit_restrictions;
    522     const json_t *debit_restrictions;
    523     struct GNUNET_JSON_Specification spec_account[] = {
    524       TALER_JSON_spec_full_payto_uri ("payto_uri",
    525                                       &payto_uri),
    526       GNUNET_JSON_spec_mark_optional (
    527         TALER_JSON_spec_web_url ("conversion_url",
    528                                  &conversion_url),
    529         NULL),
    530       GNUNET_JSON_spec_mark_optional (
    531         GNUNET_JSON_spec_int64 ("priority",
    532                                 &priority),
    533         NULL),
    534       GNUNET_JSON_spec_mark_optional (
    535         GNUNET_JSON_spec_string ("bank_label",
    536                                  &bank_label),
    537         NULL),
    538       GNUNET_JSON_spec_array_const ("credit_restrictions",
    539                                     &credit_restrictions),
    540       GNUNET_JSON_spec_array_const ("debit_restrictions",
    541                                     &debit_restrictions),
    542       GNUNET_JSON_spec_fixed_auto ("master_sig",
    543                                    &wa->master_sig),
    544       GNUNET_JSON_spec_end ()
    545     };
    546     json_t *account;
    547 
    548     account = json_array_get (accounts,
    549                               i);
    550     if (GNUNET_OK !=
    551         GNUNET_JSON_parse (account,
    552                            spec_account,
    553                            NULL, NULL))
    554     {
    555       /* bogus reply */
    556       GNUNET_break_op (0);
    557       return GNUNET_SYSERR;
    558     }
    559     if ( (NULL != master_pub) &&
    560          (GNUNET_OK !=
    561           TALER_exchange_wire_signature_check (
    562             payto_uri,
    563             conversion_url,
    564             debit_restrictions,
    565             credit_restrictions,
    566             master_pub,
    567             &wa->master_sig)) )
    568     {
    569       /* bogus reply */
    570       GNUNET_break_op (0);
    571       return GNUNET_SYSERR;
    572     }
    573     if ( (GNUNET_OK !=
    574           parse_restrictions (credit_restrictions,
    575                               &wa->credit_restrictions_length,
    576                               &wa->credit_restrictions)) ||
    577          (GNUNET_OK !=
    578           parse_restrictions (debit_restrictions,
    579                               &wa->debit_restrictions_length,
    580                               &wa->debit_restrictions)) )
    581     {
    582       /* bogus reply */
    583       GNUNET_break_op (0);
    584       return GNUNET_SYSERR;
    585     }
    586     wa->fpayto_uri.full_payto
    587       = GNUNET_strdup (payto_uri.full_payto);
    588     wa->priority = priority;
    589     if (NULL != conversion_url)
    590       wa->conversion_url = GNUNET_strdup (conversion_url);
    591     if (NULL != bank_label)
    592       wa->bank_label = GNUNET_strdup (bank_label);
    593   }       /* end 'for all accounts */
    594   return GNUNET_OK;
    595 }
    596 
    597 
    598 /**
    599  * Free array of account restrictions.
    600  *
    601  * @param ar_len length of @a ar
    602  * @param[in] ar array to free contents of (but not @a ar itself)
    603  */
    604 static void
    605 free_restrictions (unsigned int ar_len,
    606                    struct TALER_EXCHANGE_AccountRestriction ar[static ar_len])
    607 {
    608   for (unsigned int i = 0; i<ar_len; i++)
    609   {
    610     struct TALER_EXCHANGE_AccountRestriction *a = &ar[i];
    611     switch (a->type)
    612     {
    613     case TALER_EXCHANGE_AR_INVALID:
    614       GNUNET_break (0);
    615       break;
    616     case TALER_EXCHANGE_AR_DENY:
    617       break;
    618     case TALER_EXCHANGE_AR_REGEX:
    619       GNUNET_free (ar->details.regex.posix_egrep);
    620       GNUNET_free (ar->details.regex.human_hint);
    621       json_decref (ar->details.regex.human_hint_i18n);
    622       break;
    623     }
    624   }
    625 }
    626 
    627 
    628 void
    629 TALER_EXCHANGE_free_accounts (
    630   unsigned int was_len,
    631   struct TALER_EXCHANGE_WireAccount was[static was_len])
    632 {
    633   for (unsigned int i = 0; i<was_len; i++)
    634   {
    635     struct TALER_EXCHANGE_WireAccount *wa = &was[i];
    636 
    637     GNUNET_free (wa->fpayto_uri.full_payto);
    638     GNUNET_free (wa->conversion_url);
    639     GNUNET_free (wa->bank_label);
    640     free_restrictions (wa->credit_restrictions_length,
    641                        wa->credit_restrictions);
    642     GNUNET_array_grow (wa->credit_restrictions,
    643                        wa->credit_restrictions_length,
    644                        0);
    645     free_restrictions (wa->debit_restrictions_length,
    646                        wa->debit_restrictions);
    647     GNUNET_array_grow (wa->debit_restrictions,
    648                        wa->debit_restrictions_length,
    649                        0);
    650   }
    651 }
    652 
    653 
    654 enum GNUNET_GenericReturnValue
    655 TALER_EXCHANGE_keys_test_account_allowed (
    656   const struct TALER_EXCHANGE_Keys *keys,
    657   bool check_credit,
    658   const struct TALER_NormalizedPayto payto_uri)
    659 {
    660   /* For all accounts of the exchange */
    661   for (unsigned int i = 0; i<keys->accounts_len; i++)
    662   {
    663     const struct TALER_EXCHANGE_WireAccount *account
    664       = &keys->accounts[i];
    665 
    666     /* KYC auth transfers are never supported with conversion */
    667     if (NULL != account->conversion_url)
    668       continue;
    669     /* filter by source account by credit_restrictions */
    670     if (GNUNET_YES !=
    671         TALER_EXCHANGE_test_account_allowed (account,
    672                                              check_credit,
    673                                              payto_uri))
    674       continue;
    675     /* exchange account is allowed, add it */
    676     return true;
    677   }
    678   return false;
    679 }
    680 
    681 
    682 /* end of exchange_api_common.c */