exchange

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

commit 9ec6376b51ba567751a3e9f35418a5ea6357aae2
parent f936cc56585d55f73a63d598b437de3e91205cdd
Author: Christian Grothoff <christian@grothoff.org>
Date:   Mon,  2 Mar 2026 17:50:16 +0100

add complete /keys response to C API

Diffstat:
Msrc/include/taler/taler-exchange/get-keys.h | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/Makefile.am | 2+-
Msrc/lib/exchange_api_handle.c | 204++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/lib/exchange_api_post-withdraw.c | 2+-
4 files changed, 313 insertions(+), 4 deletions(-)

diff --git a/src/include/taler/taler-exchange/get-keys.h b/src/include/taler/taler-exchange/get-keys.h @@ -173,6 +173,11 @@ struct TALER_EXCHANGE_AuditorInformation char *auditor_url; /** + * Name of the auditing institution (human-readable). + */ + char *auditor_name; + + /** * Array of length @a num_denom_keys with the denomination * keys audited by this auditor. */ @@ -281,6 +286,50 @@ struct TALER_EXCHANGE_WireFeesByMethod /** + * Information about a partner exchange for wallet-to-wallet transfers. + */ +struct TALER_EXCHANGE_WadPartner +{ + /** + * Base URL of the partner exchange. + */ + char *partner_base_url; + + /** + * Public master key of the partner exchange. + */ + struct TALER_MasterPublicKeyP partner_master_pub; + + /** + * Per exchange-to-exchange transfer (wad) fee. + */ + struct TALER_Amount wad_fee; + + /** + * Exchange-to-exchange wad (wire) transfer frequency. + */ + struct GNUNET_TIME_Relative wad_frequency; + + /** + * When did this partnership begin (under these conditions)? + */ + struct GNUNET_TIME_Timestamp start_date; + + /** + * How long is this partnership expected to last? + */ + struct GNUNET_TIME_Timestamp end_date; + + /** + * Signature using the exchange's offline master key over + * TALER_WadPartnerSignaturePS with purpose + * TALER_SIGNATURE_MASTER_PARTNER_DETAILS. + */ + struct TALER_MasterSignatureP master_sig; +}; + + +/** * Type of an account restriction. */ enum TALER_EXCHANGE_AccountRestrictionType @@ -524,6 +573,34 @@ struct TALER_EXCHANGE_Keys char *asset_type; /** + * Shopping URL where users may find shops that accept + * digital cash from this exchange. NULL if not configured. + * @since protocol v21. + */ + char *shopping_url; + + /** + * Open banking gateway base URL for wallet-initiated wire + * transfers. NULL if not configured. + * @since protocol v30. + */ + char *open_banking_gateway; + + /** + * Wire transfer gateway base URL for short wire transfer + * subjects. NULL if not configured. + * @since protocol v33. + */ + char *wire_transfer_gateway; + + /** + * Bank-specific compliance language hint for wallets. + * NULL if not configured. + * @since protocol v24. + */ + char *bank_compliance_language; + + /** * Array of amounts a wallet is allowed to hold from * this exchange before it must undergo further KYC checks. * Length is given in @e wblwk_length. @@ -531,6 +608,20 @@ struct TALER_EXCHANGE_Keys struct TALER_Amount *wallet_balance_limit_without_kyc; /** + * Smallest amount that can likely be transferred to the exchange, + * used as the default for KYC authentication wire transfers. + * Only valid when @e tiny_amount_available is true. + * @since protocol v21. + */ + struct TALER_Amount tiny_amount; + + /** + * Array of partner exchanges for wallet-to-wallet transfers. + * Length is given in @e num_wad_partners. + */ + struct TALER_EXCHANGE_WadPartner *wad_partners; + + /** * Array of accounts of the exchange. */ struct TALER_EXCHANGE_WireAccount *accounts; @@ -666,9 +757,27 @@ struct TALER_EXCHANGE_Keys unsigned int rc; /** + * Length of the @e wad_partners array. + */ + unsigned int num_wad_partners; + + /** * Set to true if KYC is enabled at this exchange. */ bool kyc_enabled; + + /** + * Set to true if the @e tiny_amount field is valid. + * @since protocol v21. + */ + bool tiny_amount_available; + + /** + * Set to true if wallets should disable the direct deposit + * feature. Mainly used for regional/event currency deployments. + * @since protocol v30. + */ + bool disable_direct_deposit; }; diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am @@ -18,7 +18,7 @@ lib_LTLIBRARIES = \ libtalerexchange.la libtalerexchange_la_LDFLAGS = \ - -version-info 19:0:0 \ + -version-info 20:0:0 \ -no-undefined libtalerexchange_la_SOURCES = \ exchange_api_delete-purses-PURSE_PUB.c \ diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c @@ -556,11 +556,14 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, size_t off; size_t pos; const char *auditor_url; + const char *auditor_name; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("auditor_pub", &auditor->auditor_pub), TALER_JSON_spec_web_url ("auditor_url", &auditor_url), + GNUNET_JSON_spec_string ("auditor_name", + &auditor_name), GNUNET_JSON_spec_array_const ("denomination_keys", &keys), GNUNET_JSON_spec_end () @@ -580,6 +583,7 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor, return GNUNET_SYSERR; } auditor->auditor_url = GNUNET_strdup (auditor_url); + auditor->auditor_name = GNUNET_strdup (auditor_name); auditor->denom_keys = GNUNET_new_array (json_array_size (keys), struct TALER_EXCHANGE_AuditorDenominationInfo); @@ -799,6 +803,10 @@ parse_hard_limits (const json_t *hard_limits, &al->threshold), GNUNET_JSON_spec_relative_time ("timeframe", &al->timeframe), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("soft_limit", + &al->soft_limit), + NULL), GNUNET_JSON_spec_end () }; @@ -867,6 +875,88 @@ parse_zero_limits (const json_t *zero_limits, /** + * Parse the wads (partner exchange) array from /keys and store the + * data in @a key_data. + * + * @param[in] wads_array JSON array to parse + * @param check_sig true if we should verify signatures + * @param[out] key_data where to store the results + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static enum GNUNET_GenericReturnValue +parse_wads (const json_t *wads_array, + bool check_sig, + struct TALER_EXCHANGE_Keys *key_data) +{ + size_t n = json_array_size (wads_array); + json_t *wad_obj; + size_t index; + + if (n > UINT_MAX) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (0 == n) + return GNUNET_OK; + key_data->num_wad_partners = (unsigned int) n; + key_data->wad_partners + = GNUNET_new_array (n, + struct TALER_EXCHANGE_WadPartner); + json_array_foreach (wads_array, index, wad_obj) + { + struct TALER_EXCHANGE_WadPartner *wp + = &key_data->wad_partners[index]; + const char *partner_base_url; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_web_url ("partner_base_url", + &partner_base_url), + GNUNET_JSON_spec_fixed_auto ("partner_master_pub", + &wp->partner_master_pub), + TALER_JSON_spec_amount ("wad_fee", + key_data->currency, + &wp->wad_fee), + GNUNET_JSON_spec_relative_time ("wad_frequency", + &wp->wad_frequency), + GNUNET_JSON_spec_timestamp ("start_date", + &wp->start_date), + GNUNET_JSON_spec_timestamp ("end_date", + &wp->end_date), + GNUNET_JSON_spec_fixed_auto ("master_sig", + &wp->master_sig), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (wad_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + wp->partner_base_url = GNUNET_strdup (partner_base_url); + if (check_sig && + GNUNET_OK != + TALER_exchange_offline_partner_details_verify ( + &wp->partner_master_pub, + wp->start_date, + wp->end_date, + wp->wad_frequency, + &wp->wad_fee, + partner_base_url, + &key_data->master_pub, + &wp->master_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +/** * Decode the JSON in @a resp_obj from the /keys response * and store the data in the @a key_data. * @@ -897,6 +987,10 @@ decode_keys_json (const json_t *resp_obj, const json_t *accounts; const json_t *fees; const json_t *wads; + const char *shopping_url = NULL; + const char *open_banking_gateway = NULL; + const char *wire_transfer_gateway = NULL; + const char *bank_compliance_language = NULL; struct SignatureContext sig_ctx = { 0 }; if (JSON_OBJECT != json_typeof (resp_obj)) @@ -1009,6 +1103,26 @@ decode_keys_json (const json_t *resp_obj, "wallet_balance_limit_without_kyc", &wblwk), NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("shopping_url", + &shopping_url), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("open_banking_gateway", + &open_banking_gateway), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("wire_transfer_gateway", + &wire_transfer_gateway), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("bank_compliance_language", + &bank_compliance_language), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("disable_direct_deposit", + &key_data->disable_direct_deposit), + NULL), GNUNET_JSON_spec_end () }; const char *emsg; @@ -1029,6 +1143,7 @@ decode_keys_json (const json_t *resp_obj, { const json_t *hard_limits = NULL; const json_t *zero_limits = NULL; + bool no_tiny_amount = false; struct GNUNET_JSON_Specification sspec[] = { TALER_JSON_spec_currency_specification ( "currency_specification", @@ -1043,6 +1158,12 @@ decode_keys_json (const json_t *resp_obj, currency, &key_data->stefan_log), GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_amount ( + "tiny_amount", + currency, + &key_data->tiny_amount), + &no_tiny_amount), + GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_array_const ( "hard_limits", &hard_limits), @@ -1088,11 +1209,21 @@ decode_keys_json (const json_t *resp_obj, "Parsing hard limits of /keys failed\n"); EXITIF (1); } + key_data->tiny_amount_available = ! no_tiny_amount; } key_data->currency = GNUNET_strdup (currency); key_data->version = GNUNET_strdup (ver); key_data->asset_type = GNUNET_strdup (asset_type); + if (NULL != shopping_url) + key_data->shopping_url = GNUNET_strdup (shopping_url); + if (NULL != open_banking_gateway) + key_data->open_banking_gateway = GNUNET_strdup (open_banking_gateway); + if (NULL != wire_transfer_gateway) + key_data->wire_transfer_gateway = GNUNET_strdup (wire_transfer_gateway); + if (NULL != bank_compliance_language) + key_data->bank_compliance_language + = GNUNET_strdup (bank_compliance_language); if (! no_extensions) key_data->extensions = json_incref ((json_t *) manifests); } @@ -1189,6 +1320,12 @@ decode_keys_json (const json_t *resp_obj, "Parsed %u wire accounts from JSON\n", key_data->accounts_len); + /* Parse wad partners */ + EXITIF (GNUNET_OK != + parse_wads (wads, + check_sig, + key_data)); + /* Parse the supported extension(s): age-restriction. */ /* FIXME[Oec]: maybe lift all this into a FP in TALER_Extension ? */ @@ -1378,6 +1515,7 @@ decode_keys_json (const json_t *resp_obj, ai.num_denom_keys, 0); GNUNET_free (ai.auditor_url); + GNUNET_free (ai.auditor_name); continue; /* we are done */ } if (key_data->auditors_size == key_data->num_auditors) @@ -1560,6 +1698,7 @@ keys_completed_cb (void *cls, anew->auditor_pub = aold->auditor_pub; anew->auditor_url = GNUNET_strdup (aold->auditor_url); + anew->auditor_name = GNUNET_strdup (aold->auditor_name); GNUNET_array_grow (anew->denom_keys, anew->num_denom_keys, aold->num_denom_keys); @@ -2018,6 +2157,7 @@ TALER_EXCHANGE_keys_decref (struct TALER_EXCHANGE_Keys *keys) keys->auditors[i].num_denom_keys, 0); GNUNET_free (keys->auditors[i].auditor_url); + GNUNET_free (keys->auditors[i].auditor_name); } GNUNET_array_grow (keys->auditors, keys->auditors_size, @@ -2045,6 +2185,13 @@ TALER_EXCHANGE_keys_decref (struct TALER_EXCHANGE_Keys *keys) GNUNET_free (keys->version); GNUNET_free (keys->currency); GNUNET_free (keys->asset_type); + GNUNET_free (keys->shopping_url); + GNUNET_free (keys->open_banking_gateway); + GNUNET_free (keys->wire_transfer_gateway); + GNUNET_free (keys->bank_compliance_language); + for (unsigned int i = 0; i < keys->num_wad_partners; i++) + GNUNET_free (keys->wad_partners[i].partner_base_url); + GNUNET_free (keys->wad_partners); GNUNET_free (keys->global_fees); GNUNET_free (keys->exchange_url); GNUNET_free (keys); @@ -2253,6 +2400,7 @@ TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd) json_t *accounts; json_t *global_fees; json_t *wblwk = NULL; + json_t *wads_json; json_t *hard_limits; json_t *zero_limits; @@ -2408,6 +2556,8 @@ TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd) &ai->auditor_pub), GNUNET_JSON_pack_string ("auditor_url", ai->auditor_url), + GNUNET_JSON_pack_string ("auditor_name", + ai->auditor_name), GNUNET_JSON_pack_array_steal ("denomination_keys", adenoms)); GNUNET_assert (0 == @@ -2565,7 +2715,9 @@ TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd) GNUNET_JSON_pack_time_rel ("timeframe", al->timeframe), TALER_JSON_pack_kycte ("operation_type", - al->operation_type) + al->operation_type), + GNUNET_JSON_pack_bool ("soft_limit", + al->soft_limit) ); GNUNET_assert (0 == json_array_append_new ( @@ -2590,6 +2742,34 @@ TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd) j)); } + wads_json = json_array (); + GNUNET_assert (NULL != wads_json); + for (unsigned int i = 0; i < kd->num_wad_partners; i++) + { + const struct TALER_EXCHANGE_WadPartner *wp + = &kd->wad_partners[i]; + + GNUNET_assert ( + 0 == + json_array_append_new ( + wads_json, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("partner_base_url", + wp->partner_base_url), + GNUNET_JSON_pack_data_auto ("partner_master_pub", + &wp->partner_master_pub), + TALER_JSON_pack_amount ("wad_fee", + &wp->wad_fee), + GNUNET_JSON_pack_time_rel ("wad_frequency", + wp->wad_frequency), + GNUNET_JSON_pack_timestamp ("start_date", + wp->start_date), + GNUNET_JSON_pack_timestamp ("end_date", + wp->end_date), + GNUNET_JSON_pack_data_auto ("master_sig", + &wp->master_sig)))); + } + keys = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("version", kd->version), @@ -2604,8 +2784,28 @@ TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd) &kd->stefan_log), GNUNET_JSON_pack_double ("stefan_lin", kd->stefan_lin), + GNUNET_JSON_pack_allow_null ( + kd->tiny_amount_available + ? TALER_JSON_pack_amount ("tiny_amount", + &kd->tiny_amount) + : GNUNET_JSON_pack_string ("dummy", + NULL)), GNUNET_JSON_pack_string ("asset_type", kd->asset_type), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("shopping_url", + kd->shopping_url)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("open_banking_gateway", + kd->open_banking_gateway)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("wire_transfer_gateway", + kd->wire_transfer_gateway)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("bank_compliance_language", + kd->bank_compliance_language)), + GNUNET_JSON_pack_bool ("disable_direct_deposit", + kd->disable_direct_deposit), GNUNET_JSON_pack_data_auto ("master_public_key", &kd->master_pub), GNUNET_JSON_pack_time_rel ("reserve_closing_delay", @@ -2621,7 +2821,7 @@ TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd) GNUNET_JSON_pack_array_steal ("accounts", accounts), GNUNET_JSON_pack_array_steal ("wads", - json_array ()), + wads_json), GNUNET_JSON_pack_array_steal ("hard_limits", hard_limits), GNUNET_JSON_pack_array_steal ("zero_limits", diff --git a/src/lib/exchange_api_post-withdraw.c b/src/lib/exchange_api_post-withdraw.c @@ -1603,7 +1603,7 @@ prepare_coins ( if (0 < cs_num) { - if (NULL != wh->options.has_blinding_seed) + if (wh->options.has_blinding_seed) { wh->blinding_seed = wh->options.blinding_seed; }