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:
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;
}