commit 03cb0a556a7856247689bb698ca9fb0a7f2f021d
parent 7330005baae937c2eaae1e76fdbd4e66881cc0c2
Author: Florian Dold <dold@taler.net>
Date: Thu, 27 Aug 2026 16:03:03 +0200
exchange /keys: add denom filter for withdrawal
Issue: https://bugs.taler.net/n/0011750
Diffstat:
2 files changed, 716 insertions(+), 186 deletions(-)
diff --git a/src/exchange/taler-exchange-httpd_get-keys.c b/src/exchange/taler-exchange-httpd_get-keys.c
@@ -213,6 +213,16 @@ struct TEH_KeyStateHandle
unsigned int krd_array_length;
/**
+ * Pre-built response for `denom_filter=withdraw`.
+ */
+ struct KeysResponseData *withdraw_krd_array;
+
+ /**
+ * Length of @e withdraw_krd_array. Either zero or one.
+ */
+ unsigned int withdraw_krd_array_length;
+
+ /**
* Cached reply for a GET /management/keys request. Used so we do not
* re-create the reply every time.
*/
@@ -242,6 +252,11 @@ struct TEH_KeyStateHandle
struct GNUNET_TIME_Timestamp signature_expires;
/**
+ * When the membership of the withdrawal-focused response next changes.
+ */
+ struct GNUNET_TIME_Timestamp withdraw_expiration;
+
+ /**
* True if #finish_keys_response() was not yet run and this key state
* is only suitable for the /management/keys API.
*/
@@ -1024,28 +1039,45 @@ TEH_resume_keys_requests (bool do_shutdown)
/**
- * Clear memory for responses to "/keys" in @a ksh.
+ * Clear a cached array of `/keys` responses.
*
- * @param[in,out] ksh key state to update
+ * @param[in,out] array response array to free
+ * @param[in,out] array_length length of @a array, set to zero
*/
static void
-clear_response_cache (struct TEH_KeyStateHandle *ksh)
+clear_response_array (struct KeysResponseData **array,
+ unsigned int *array_length)
{
- for (unsigned int i = 0; i<ksh->krd_array_length; i++)
+ for (unsigned int i = 0; i<*array_length; i++)
{
- struct KeysResponseData *krd = &ksh->krd_array[i];
+ struct KeysResponseData *krd = &(*array)[i];
MHD_destroy_response (krd->response_compressed);
MHD_destroy_response (krd->response_uncompressed);
GNUNET_free (krd->etag);
}
- GNUNET_array_grow (ksh->krd_array,
- ksh->krd_array_length,
+ GNUNET_array_grow (*array,
+ *array_length,
0);
}
/**
+ * Clear memory for responses to "/keys" in @a ksh.
+ *
+ * @param[in,out] ksh key state to update
+ */
+static void
+clear_response_cache (struct TEH_KeyStateHandle *ksh)
+{
+ clear_response_array (&ksh->krd_array,
+ &ksh->krd_array_length);
+ clear_response_array (&ksh->withdraw_krd_array,
+ &ksh->withdraw_krd_array_length);
+}
+
+
+/**
* Free denomination key data.
*
* @param cls a `struct TEH_KeyStateHandle`, unused
@@ -1661,16 +1693,348 @@ add_denom_key_cb (void *cls,
/**
+ * Earliest future denomination start for one withdrawal family.
+ */
+struct WithdrawFamily
+{
+ /**
+ * Earliest denomination start after the key-state build time.
+ */
+ struct GNUNET_TIME_Timestamp next_start;
+};
+
+
+/**
+ * Context used while finding future denomination starts.
+ */
+struct FindWithdrawFamiliesContext
+{
+ /**
+ * Exchange time used consistently for this key-state build.
+ */
+ struct GNUNET_TIME_Timestamp now;
+
+ /**
+ * Map from family identifiers to `struct WithdrawFamily`.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *families;
+};
+
+
+/**
+ * Context used while marking withdrawal-relevant denominations.
+ */
+struct SelectWithdrawDenominationsContext
+{
+ /**
+ * Exchange time used consistently for this key-state build.
+ */
+ struct GNUNET_TIME_Timestamp now;
+
+ /**
+ * Map from family identifiers to `struct WithdrawFamily`.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *families;
+
+ /**
+ * Key state whose expiration must be capped at the next membership change.
+ */
+ struct TEH_KeyStateHandle *ksh;
+
+ /**
+ * Number of selected denomination keys.
+ */
+ unsigned int selected;
+};
+
+
+/**
+ * Compute the identifier for the withdrawal family of @a dk. Fees are
+ * deliberately zero here: changing fees creates a new response group, but
+ * not a separate denomination family for successor selection.
+ *
+ * @param dk denomination to identify
+ * @param[out] key resulting family identifier
+ */
+static void
+get_withdraw_family_key (const struct TEH_DenominationKey *dk,
+ struct GNUNET_HashCode *key)
+{
+ struct TALER_DenominationGroup meta = {
+ .cipher = dk->denom_pub.bsign_pub_key->cipher,
+ .value = dk->meta.value,
+ .fees = dk->meta.fees,
+ .age_mask = dk->meta.age_mask,
+ };
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (meta.fees.withdraw.currency,
+ &meta.fees.withdraw));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (meta.fees.deposit.currency,
+ &meta.fees.deposit));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (meta.fees.refresh.currency,
+ &meta.fees.refresh));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (meta.fees.refund.currency,
+ &meta.fees.refund));
+ TALER_denomination_group_get_key (&meta,
+ key);
+}
+
+
+/**
+ * Find the earliest future key start for every denomination family.
+ *
+ * @param cls a `struct FindWithdrawFamiliesContext *`
+ * @param h_denom_pub denomination hash, unused
+ * @param value a `struct TEH_DenominationKey *`
+ * @return #GNUNET_OK to continue iteration
+ */
+static enum GNUNET_GenericReturnValue
+find_withdraw_family_cb (void *cls,
+ const struct GNUNET_HashCode *h_denom_pub,
+ void *value)
+{
+ struct FindWithdrawFamiliesContext *ctx = cls;
+ struct TEH_DenominationKey *dk = value;
+ struct WithdrawFamily *family;
+ struct GNUNET_HashCode key;
+
+ (void) h_denom_pub;
+ dk->withdraw_relevant = false;
+ if (dk->recoup_possible ||
+ GNUNET_TIME_timestamp_cmp (dk->meta.start,
+ <=,
+ ctx->now))
+ return GNUNET_OK;
+ get_withdraw_family_key (dk,
+ &key);
+ family = GNUNET_CONTAINER_multihashmap_get (ctx->families,
+ &key);
+ if (NULL == family)
+ {
+ family = GNUNET_new (struct WithdrawFamily);
+ family->next_start = dk->meta.start;
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (
+ ctx->families,
+ &key,
+ family,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ return GNUNET_OK;
+ }
+ family->next_start = GNUNET_TIME_timestamp_min (family->next_start,
+ dk->meta.start);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Mark current denominations and each family's earliest future denomination.
+ *
+ * @param cls a `struct SelectWithdrawDenominationsContext *`
+ * @param h_denom_pub denomination hash, unused
+ * @param value a `struct TEH_DenominationKey *`
+ * @return #GNUNET_OK to continue iteration
+ */
+static enum GNUNET_GenericReturnValue
+select_withdraw_denomination_cb (void *cls,
+ const struct GNUNET_HashCode *h_denom_pub,
+ void *value)
+{
+ struct SelectWithdrawDenominationsContext *ctx = cls;
+ struct TEH_DenominationKey *dk = value;
+ struct GNUNET_TIME_Timestamp transition;
+ bool selected = false;
+
+ (void) h_denom_pub;
+ if (dk->recoup_possible)
+ return GNUNET_OK;
+ if (GNUNET_TIME_timestamp_cmp (dk->meta.start,
+ <=,
+ ctx->now) &&
+ GNUNET_TIME_timestamp_cmp (dk->meta.expire_withdraw,
+ >,
+ ctx->now))
+ {
+ selected = true;
+ transition = dk->meta.expire_withdraw;
+ }
+ else if (GNUNET_TIME_timestamp_cmp (dk->meta.start,
+ >,
+ ctx->now))
+ {
+ struct GNUNET_HashCode key;
+ const struct WithdrawFamily *family;
+
+ get_withdraw_family_key (dk,
+ &key);
+ family = GNUNET_CONTAINER_multihashmap_get (ctx->families,
+ &key);
+ GNUNET_assert (NULL != family);
+ if (GNUNET_TIME_timestamp_cmp (dk->meta.start,
+ ==,
+ family->next_start))
+ {
+ selected = true;
+ transition = dk->meta.start;
+ }
+ }
+ if (! selected)
+ return GNUNET_OK;
+ dk->withdraw_relevant = true;
+ ctx->selected++;
+ ctx->ksh->withdraw_expiration = GNUNET_TIME_timestamp_min (
+ ctx->ksh->withdraw_expiration,
+ transition);
+ ctx->ksh->signature_expires = GNUNET_TIME_timestamp_min (
+ ctx->ksh->signature_expires,
+ transition);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Free a `struct WithdrawFamily` map value.
+ *
+ * @param cls unused
+ * @param key unused
+ * @param value family to free
+ * @return #GNUNET_OK
+ */
+static int
+free_withdraw_family (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
+ (void) cls;
+ (void) key;
+ GNUNET_free (value);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Select denominations for `denom_filter=withdraw`.
+ *
+ * @param[in,out] ksh key state containing the denominations
+ * @param now exchange time to use for selection
+ * @return number of selected denomination keys
+ */
+static unsigned int
+select_withdraw_denominations (struct TEH_KeyStateHandle *ksh,
+ struct GNUNET_TIME_Timestamp now)
+{
+ struct GNUNET_CONTAINER_MultiHashMap *families;
+ struct FindWithdrawFamiliesContext fctx = {
+ .now = now,
+ };
+ struct SelectWithdrawDenominationsContext sctx = {
+ .now = now,
+ .ksh = ksh,
+ };
+
+ families = GNUNET_CONTAINER_multihashmap_create (128,
+ false);
+ fctx.families = families;
+ sctx.families = families;
+ GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
+ &find_withdraw_family_cb,
+ &fctx);
+ GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
+ &select_withdraw_denomination_cb,
+ &sctx);
+ GNUNET_CONTAINER_multihashmap_iterate (families,
+ &free_withdraw_family,
+ NULL);
+ GNUNET_CONTAINER_multihashmap_destroy (families);
+ return sctx.selected;
+}
+
+
+/**
+ * Build auditor information containing signatures only for denominations in
+ * the withdrawal-focused response. Auditor identity metadata is retained
+ * even when none of its denomination signatures is selected.
+ *
+ * @param ksh key state with full auditor information
+ * @return newly allocated JSON array
+ */
+static json_t *
+build_withdraw_auditors (const struct TEH_KeyStateHandle *ksh)
+{
+ json_t *result = json_array ();
+ json_t *auditor;
+ size_t auditor_index;
+
+ GNUNET_assert (NULL != result);
+ json_array_foreach (ksh->auditors,
+ auditor_index,
+ auditor)
+ {
+ const json_t *denomination_keys;
+ json_t *filtered_keys = json_array ();
+ json_t *filtered_auditor;
+ json_t *denom_sig;
+ size_t denom_index;
+
+ GNUNET_assert (NULL != filtered_keys);
+ denomination_keys = json_object_get (auditor,
+ "denomination_keys");
+ GNUNET_assert (json_is_array (denomination_keys));
+ json_array_foreach (denomination_keys,
+ denom_index,
+ denom_sig)
+ {
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TEH_DenominationKey *dk;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("denom_pub_h",
+ &h_denom_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_JSON_parse (denom_sig,
+ spec,
+ NULL,
+ NULL));
+ dk = GNUNET_CONTAINER_multihashmap_get (ksh->denomkey_map,
+ &h_denom_pub.hash);
+ if ( (NULL != dk) &&
+ dk->withdraw_relevant )
+ GNUNET_assert (0 ==
+ json_array_append (filtered_keys,
+ denom_sig));
+ }
+ filtered_auditor = json_deep_copy (auditor);
+ GNUNET_assert (NULL != filtered_auditor);
+ GNUNET_assert (0 ==
+ json_object_set_new (filtered_auditor,
+ "denomination_keys",
+ filtered_keys));
+ GNUNET_assert (0 ==
+ json_array_append_new (result,
+ filtered_auditor));
+ }
+ return result;
+}
+
+
+/**
* Add the headers we want to set for every /keys response.
*
* @param cls the key state to use
* @param[in,out] response the response to modify
*/
static void
-setup_general_response_headers (void *cls,
- struct MHD_Response *response)
+setup_response_headers (struct TEH_KeyStateHandle *ksh,
+ struct MHD_Response *response,
+ bool withdraw_filtered)
{
- struct TEH_KeyStateHandle *ksh = cls;
char dat[128];
TALER_MHD_add_global_headers (response,
@@ -1682,7 +2046,9 @@ setup_general_response_headers (void *cls,
GNUNET_break (MHD_YES ==
MHD_add_response_header (response,
MHD_HTTP_HEADER_CACHE_CONTROL,
- "public,must-revalidate,max-age=86400")
+ withdraw_filtered
+ ? "public,must-revalidate"
+ : "public,must-revalidate,max-age=86400")
);
if (! GNUNET_TIME_relative_is_zero (ksh->rekey_frequency))
{
@@ -1705,6 +2071,9 @@ setup_general_response_headers (void *cls,
we = GNUNET_TIME_absolute_to_timestamp (wire_state->cache_expiration);
m = GNUNET_TIME_timestamp_min (we,
km);
+ if (withdraw_filtered)
+ m = GNUNET_TIME_timestamp_min (m,
+ ksh->withdraw_expiration);
TALER_MHD_get_date_string (m.abs_time,
dat);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -1729,6 +2098,38 @@ setup_general_response_headers (void *cls,
/**
+ * Add headers for the legacy `/keys` response.
+ *
+ * @param cls the key state to use
+ * @param[in,out] response response to modify
+ */
+static void
+setup_general_response_headers (void *cls,
+ struct MHD_Response *response)
+{
+ setup_response_headers (cls,
+ response,
+ false);
+}
+
+
+/**
+ * Add headers for the withdrawal-focused `/keys` response.
+ *
+ * @param cls the key state to use
+ * @param[in,out] response response to modify
+ */
+static void
+setup_withdraw_response_headers (void *cls,
+ struct MHD_Response *response)
+{
+ setup_response_headers (cls,
+ response,
+ true);
+}
+
+
+/**
* Initialize @a krd using the given values for @a signkeys,
* @a recoup and @a denoms.
*
@@ -1738,6 +2139,10 @@ setup_general_response_headers (void *cls,
* @param[in,out] signkeys list of sign keys to return
* @param[in,out] recoup list of revoked keys to return
* @param[in,out] grouped_denominations list of grouped denominations to return
+ * @param[in] auditors auditor information to return
+ * @param[in,out] krd_array response array to append to
+ * @param[in,out] krd_array_length length of @a krd_array
+ * @param withdraw_filtered true when building the withdrawal-focused response
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
@@ -1746,7 +2151,11 @@ create_krd (struct TEH_KeyStateHandle *ksh,
struct GNUNET_TIME_Timestamp last_cherry_pick_date,
json_t *signkeys,
json_t *recoup,
- json_t *grouped_denominations)
+ json_t *grouped_denominations,
+ json_t *auditors,
+ struct KeysResponseData **krd_array,
+ unsigned int *krd_array_length,
+ bool withdraw_filtered)
{
struct KeysResponseData krd;
struct TALER_ExchangePublicKeyP exchange_pub;
@@ -1765,7 +2174,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
GNUNET_assert (NULL != signkeys);
GNUNET_assert (NULL != recoup);
GNUNET_assert (NULL != grouped_denominations);
- GNUNET_assert (NULL != ksh->auditors);
+ GNUNET_assert (NULL != auditors);
GNUNET_assert (NULL != TEH_currency);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Creating /keys at cherry pick date %s\n",
@@ -1868,7 +2277,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
GNUNET_JSON_pack_array_incref ("denominations",
grouped_denominations),
GNUNET_JSON_pack_array_incref ("auditors",
- ksh->auditors),
+ auditors),
GNUNET_JSON_pack_array_incref ("global_fees",
ksh->global_fees),
GNUNET_JSON_pack_timestamp ("list_issue_date",
@@ -1926,8 +2335,9 @@ create_krd (struct TEH_KeyStateHandle *ksh,
keys_json,
MHD_RESPMEM_MUST_FREE);
GNUNET_assert (NULL != krd.response_uncompressed);
- setup_general_response_headers (ksh,
- krd.response_uncompressed);
+ setup_response_headers (ksh,
+ krd.response_uncompressed,
+ withdraw_filtered);
/* Information is always public, revalidate after 1 day */
GNUNET_break (MHD_YES ==
MHD_add_response_header (krd.response_uncompressed,
@@ -1948,8 +2358,9 @@ create_krd (struct TEH_KeyStateHandle *ksh,
MHD_add_response_header (krd.response_compressed,
MHD_HTTP_HEADER_CONTENT_ENCODING,
"deflate")) );
- setup_general_response_headers (ksh,
- krd.response_compressed);
+ setup_response_headers (ksh,
+ krd.response_compressed,
+ withdraw_filtered);
/* Information is always public, revalidate after 1 day */
GNUNET_break (MHD_YES ==
MHD_add_response_header (krd.response_compressed,
@@ -1958,8 +2369,8 @@ create_krd (struct TEH_KeyStateHandle *ksh,
krd.etag = GNUNET_strdup (etag);
}
krd.cherry_pick_date = last_cherry_pick_date;
- GNUNET_array_append (ksh->krd_array,
- ksh->krd_array_length,
+ GNUNET_array_append (*krd_array,
+ *krd_array_length,
krd);
return GNUNET_OK;
}
@@ -2155,6 +2566,144 @@ compute_msig_hash (struct SignatureContext *sig_ctx,
/**
+ * Add one denomination to a grouped denomination response.
+ *
+ * @param[in,out] denominations_by_group map used to find existing groups
+ * @param[in,out] grouped_denominations JSON array of denomination groups
+ * @param[in,out] sig_ctx signature context for the response
+ * @param[in] dk denomination to add
+ * @param[in,out] has_age_restricted_denomination set if an age-restricted
+ * denomination is encountered
+ */
+static void
+append_denomination (
+ struct GNUNET_CONTAINER_MultiHashMap *denominations_by_group,
+ json_t *grouped_denominations,
+ struct SignatureContext *sig_ctx,
+ const struct TEH_DenominationKey *dk,
+ bool *has_age_restricted_denomination)
+{
+ struct GroupData *group;
+ json_t *entry;
+ struct GNUNET_HashCode key;
+ struct TALER_DenominationGroup meta = {
+ .cipher = dk->denom_pub.bsign_pub_key->cipher,
+ .value = dk->meta.value,
+ .fees = dk->meta.fees,
+ .age_mask = dk->meta.age_mask,
+ };
+
+ TALER_denomination_group_get_key (&meta,
+ &key);
+ group = GNUNET_CONTAINER_multihashmap_get (denominations_by_group,
+ &key);
+ if (NULL == group)
+ {
+ bool age_restricted = 0 != meta.age_mask.bits;
+ const char *cipher;
+
+ group = GNUNET_new (struct GroupData);
+ switch (meta.cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ cipher = age_restricted ? "RSA+age_restricted" : "RSA";
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ cipher = age_restricted ? "CS+age_restricted" : "CS";
+ break;
+ default:
+ GNUNET_assert (false);
+ }
+ group->list = json_array ();
+ GNUNET_assert (NULL != group->list);
+ group->json = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ cipher),
+ GNUNET_JSON_pack_array_steal ("denoms",
+ group->list),
+ TALER_JSON_PACK_DENOM_FEES ("fee",
+ &meta.fees),
+ TALER_JSON_pack_amount ("value",
+ &meta.value));
+ GNUNET_assert (NULL != group->json);
+ if (age_restricted)
+ {
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (group->json,
+ "age_mask",
+ json_integer (meta.age_mask.bits)));
+ *has_age_restricted_denomination = true;
+ }
+ group->group_off = json_array_size (grouped_denominations);
+ GNUNET_assert (0 ==
+ json_array_append_new (grouped_denominations,
+ group->json));
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (
+ denominations_by_group,
+ &key,
+ group,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ }
+
+ {
+ struct GNUNET_JSON_PackSpec key_spec;
+ bool private_key_lost;
+
+ private_key_lost
+ = TEH_SECMOD_denom_priv_check_lost (&dk->h_denom_pub);
+ switch (meta.cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ key_spec = GNUNET_JSON_pack_rsa_public_key (
+ "rsa_pub",
+ dk->denom_pub.bsign_pub_key->details.rsa_public_key);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ key_spec = GNUNET_JSON_pack_data_varsize (
+ "cs_pub",
+ &dk->denom_pub.bsign_pub_key->details.cs_public_key,
+ sizeof (dk->denom_pub.bsign_pub_key->details.cs_public_key));
+ break;
+ default:
+ GNUNET_assert (false);
+ }
+
+ entry = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &dk->master_sig),
+ GNUNET_JSON_pack_allow_null (
+ private_key_lost
+ ? GNUNET_JSON_pack_bool ("lost",
+ true)
+ : GNUNET_JSON_pack_string ("dummy",
+ NULL)),
+ GNUNET_JSON_pack_timestamp ("stamp_start",
+ dk->meta.start),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw",
+ dk->meta.expire_withdraw),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_deposit",
+ dk->meta.expire_deposit),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_legal",
+ dk->meta.expire_legal),
+ key_spec);
+ GNUNET_assert (NULL != entry);
+ }
+
+ append_signature (sig_ctx,
+ group->group_off,
+ (unsigned int) json_array_size (group->list),
+ &dk->master_sig);
+ GNUNET_assert (json_is_array (group->list));
+ GNUNET_assert (0 ==
+ json_array_append_new (group->list,
+ entry));
+}
+
+
+/**
* Update the "/keys" responses in @a ksh, computing the detailed replies.
*
* This function is to recompute all (including cherry-picked) responses we
@@ -2172,11 +2721,18 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
.min_sk_frequency = GNUNET_TIME_UNIT_FOREVER_REL
};
json_t *grouped_denominations = NULL;
+ json_t *withdraw_denominations = NULL;
+ json_t *withdraw_auditors = NULL;
struct GNUNET_TIME_Timestamp last_cherry_pick_date;
+ struct GNUNET_TIME_Timestamp withdraw_list_issue_date
+ = GNUNET_TIME_UNIT_ZERO_TS;
struct GNUNET_CONTAINER_Heap *heap;
struct SignatureContext sig_ctx = { 0 };
+ struct SignatureContext withdraw_sig_ctx = { 0 };
+ unsigned int withdraw_denomination_count;
/* Remember if we have any denomination with age restriction */
bool has_age_restricted_denomination = false;
+ bool withdraw_has_age_restricted_denomination = false;
struct WireStateHandle *wsh;
wsh = get_wire_state ();
@@ -2199,6 +2755,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
GNUNET_assert (NULL != recoup);
grouped_denominations = json_array ();
GNUNET_assert (NULL != grouped_denominations);
+ withdraw_denominations = json_array ();
+ GNUNET_assert (NULL != withdraw_denominations);
GNUNET_CONTAINER_multipeermap_iterate (ksh->signkey_map,
&add_sign_key_cb,
@@ -2225,17 +2783,25 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
= GNUNET_TIME_relative_min (dkc.min_dk_frequency,
sctx.min_sk_frequency);
}
+ withdraw_denomination_count
+ = select_withdraw_denominations (ksh,
+ ksh->reload_time);
+ withdraw_auditors = build_withdraw_auditors (ksh);
last_cherry_pick_date = GNUNET_TIME_UNIT_ZERO_TS;
{
struct TEH_DenominationKey *dk;
struct GNUNET_CONTAINER_MultiHashMap *denominations_by_group;
+ struct GNUNET_CONTAINER_MultiHashMap *withdraw_denominations_by_group;
denominations_by_group =
GNUNET_CONTAINER_multihashmap_create (1024,
GNUNET_NO /* NO, because keys are only on the stack */
);
+ withdraw_denominations_by_group
+ = GNUNET_CONTAINER_multihashmap_create (128,
+ false);
/* heap = max heap, sorted by start time */
while (NULL != (dk = GNUNET_CONTAINER_heap_remove_root (heap)))
{
@@ -2259,7 +2825,11 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
last_cherry_pick_date,
sctx.signkeys,
recoup,
- grouped_denominations))
+ grouped_denominations,
+ ksh->auditors,
+ &ksh->krd_array,
+ &ksh->krd_array_length,
+ false))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to generate key response data for %s\n",
@@ -2273,147 +2843,19 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
}
last_cherry_pick_date = dk->meta.start;
- /*
- * Group the denominations by {cipher, value, fees, age_mask}.
- *
- * For each group we save the group meta-data and the list of
- * denominations in this group as a json-blob in the multihashmap
- * denominations_by_group.
- */
- {
- struct GroupData *group;
- json_t *entry;
- struct GNUNET_HashCode key;
- struct TALER_DenominationGroup meta = {
- .cipher = dk->denom_pub.bsign_pub_key->cipher,
- .value = dk->meta.value,
- .fees = dk->meta.fees,
- .age_mask = dk->meta.age_mask,
- };
-
- /* Search the group/JSON-blob for the key */
- TALER_denomination_group_get_key (&meta,
- &key);
- group = GNUNET_CONTAINER_multihashmap_get (
- denominations_by_group,
- &key);
- if (NULL == group)
- {
- /* There is no group for this meta-data yet, so we create a new group */
- bool age_restricted = meta.age_mask.bits != 0;
- const char *cipher;
-
- group = GNUNET_new (struct GroupData);
- switch (meta.cipher)
- {
- case GNUNET_CRYPTO_BSA_RSA:
- cipher = age_restricted ? "RSA+age_restricted" : "RSA";
- break;
- case GNUNET_CRYPTO_BSA_CS:
- cipher = age_restricted ? "CS+age_restricted" : "CS";
- break;
- default:
- GNUNET_assert (false);
- }
- /* Create a new array for the denominations in this group */
- group->list = json_array ();
- GNUNET_assert (NULL != group->list);
- group->json = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("cipher",
- cipher),
- GNUNET_JSON_pack_array_steal ("denoms",
- group->list),
- TALER_JSON_PACK_DENOM_FEES ("fee",
- &meta.fees),
- TALER_JSON_pack_amount ("value",
- &meta.value));
- GNUNET_assert (NULL != group->json);
- if (age_restricted)
- {
- GNUNET_assert (
- 0 ==
- json_object_set_new (group->json,
- "age_mask",
- json_integer (
- meta.age_mask.bits)));
- /* Remember that we have found at least _one_ age restricted denomination */
- has_age_restricted_denomination = true;
- }
- group->group_off
- = json_array_size (grouped_denominations);
- GNUNET_assert (0 ==
- json_array_append_new (
+ append_denomination (denominations_by_group,
grouped_denominations,
- group->json));
- GNUNET_assert (
- GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap_put (denominations_by_group,
- &key,
- group,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
- }
-
- /* Now that we have found/created the right group, add the
- denomination to the list */
- {
- struct GNUNET_JSON_PackSpec key_spec;
- bool private_key_lost;
-
- private_key_lost
- = TEH_SECMOD_denom_priv_check_lost (&dk->h_denom_pub);
- switch (meta.cipher)
- {
- case GNUNET_CRYPTO_BSA_RSA:
- key_spec =
- GNUNET_JSON_pack_rsa_public_key (
- "rsa_pub",
- dk->denom_pub.bsign_pub_key->details.rsa_public_key);
- break;
- case GNUNET_CRYPTO_BSA_CS:
- key_spec =
- GNUNET_JSON_pack_data_varsize (
- "cs_pub",
- &dk->denom_pub.bsign_pub_key->details.cs_public_key,
- sizeof (dk->denom_pub.bsign_pub_key->details.cs_public_key));
- break;
- default:
- GNUNET_assert (false);
- }
-
- entry = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("master_sig",
- &dk->master_sig),
- GNUNET_JSON_pack_allow_null (
- private_key_lost
- ? GNUNET_JSON_pack_bool ("lost",
- true)
- : GNUNET_JSON_pack_string ("dummy",
- NULL)),
- GNUNET_JSON_pack_timestamp ("stamp_start",
- dk->meta.start),
- GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw",
- dk->meta.expire_withdraw),
- GNUNET_JSON_pack_timestamp ("stamp_expire_deposit",
- dk->meta.expire_deposit),
- GNUNET_JSON_pack_timestamp ("stamp_expire_legal",
- dk->meta.expire_legal),
- key_spec
- );
- GNUNET_assert (NULL != entry);
- }
-
- /* Build up the running hash of all master signatures of the
- denominations */
- append_signature (&sig_ctx,
- group->group_off,
- (unsigned int) json_array_size (group->list),
- &dk->master_sig);
- /* Finally, add the denomination to the list of denominations in this
- group */
- GNUNET_assert (json_is_array (group->list));
- GNUNET_assert (0 ==
- json_array_append_new (group->list,
- entry));
+ &sig_ctx,
+ dk,
+ &has_age_restricted_denomination);
+ if (dk->withdraw_relevant)
+ {
+ withdraw_list_issue_date = dk->meta.start;
+ append_denomination (withdraw_denominations_by_group,
+ withdraw_denominations,
+ &withdraw_sig_ctx,
+ dk,
+ &withdraw_has_age_restricted_denomination);
}
} /* loop over heap ends */
@@ -2421,6 +2863,12 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
&free_group,
NULL);
GNUNET_CONTAINER_multihashmap_destroy (denominations_by_group);
+ GNUNET_CONTAINER_multihashmap_iterate (
+ withdraw_denominations_by_group,
+ &free_group,
+ NULL);
+ GNUNET_CONTAINER_multihashmap_destroy (
+ withdraw_denominations_by_group);
}
GNUNET_CONTAINER_heap_destroy (heap);
@@ -2436,13 +2884,43 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
last_cherry_pick_date,
sctx.signkeys,
recoup,
- grouped_denominations))
+ grouped_denominations,
+ ksh->auditors,
+ &ksh->krd_array,
+ &ksh->krd_array_length,
+ false))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to generate key response data for %s\n",
GNUNET_TIME_timestamp2s (last_cherry_pick_date));
goto CLEANUP;
}
+ if (0 != withdraw_denomination_count)
+ {
+ struct GNUNET_HashCode withdraw_hc;
+
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ withdraw_list_issue_date.abs_time));
+ compute_msig_hash (&withdraw_sig_ctx,
+ &withdraw_hc);
+ if (GNUNET_OK !=
+ create_krd (ksh,
+ &withdraw_hc,
+ withdraw_list_issue_date,
+ sctx.signkeys,
+ recoup,
+ withdraw_denominations,
+ withdraw_auditors,
+ &ksh->withdraw_krd_array,
+ &ksh->withdraw_krd_array_length,
+ true))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to generate withdrawal-focused key response data\n");
+ goto CLEANUP;
+ }
+ GNUNET_assert (1 == ksh->withdraw_krd_array_length);
+ }
ksh->management_only = false;
/* Sanity check: Make sure that age restriction is enabled IFF at least
@@ -2472,7 +2950,13 @@ CLEANUP:
GNUNET_array_grow (sig_ctx.elements,
sig_ctx.elements_size,
0);
+ GNUNET_array_grow (withdraw_sig_ctx.elements,
+ withdraw_sig_ctx.elements_size,
+ 0);
json_decref (grouped_denominations);
+ json_decref (withdraw_denominations);
+ if (NULL != withdraw_auditors)
+ json_decref (withdraw_auditors);
if (NULL != sctx.signkeys)
json_decref (sctx.signkeys);
json_decref (recoup);
@@ -2571,6 +3055,7 @@ build_key_state (bool management_only)
ksh = GNUNET_new (struct TEH_KeyStateHandle);
ksh->signature_expires = GNUNET_TIME_UNIT_FOREVER_TS;
+ ksh->withdraw_expiration = GNUNET_TIME_UNIT_FOREVER_TS;
ksh->reload_time = GNUNET_TIME_timestamp_get ();
/* We must use the key_generation from when we STARTED the process! */
ksh->key_generation = key_generation;
@@ -2918,18 +3403,39 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc,
const char *const args[])
{
struct GNUNET_TIME_Timestamp last_issue_date;
+ const char *have_cherrypick;
+ const char *denom_filter;
+ bool withdraw_filter = false;
const char *etag;
etag = MHD_lookup_connection_value (rc->connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_IF_NONE_MATCH);
+ denom_filter = MHD_lookup_connection_value (rc->connection,
+ MHD_GET_ARGUMENT_KIND,
+ "denom_filter");
+ if (NULL != denom_filter)
+ {
+ if (0 != strcmp (denom_filter,
+ "withdraw"))
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ denom_filter);
+ withdraw_filter = true;
+ }
(void) args;
{
- const char *have_cherrypick;
-
have_cherrypick = MHD_lookup_connection_value (rc->connection,
MHD_GET_ARGUMENT_KIND,
"last_issue_date");
+ if (withdraw_filter &&
+ (NULL != have_cherrypick))
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "denom_filter and last_issue_date are mutually exclusive");
if (NULL != have_cherrypick)
{
unsigned long long cherrypickn;
@@ -2982,37 +3488,55 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc,
}
return suspend_request (rc->connection);
}
- krd = bsearch (&last_issue_date,
- ksh->krd_array,
- ksh->krd_array_length,
- sizeof (struct KeysResponseData),
- &krd_search_comparator);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Filtering /keys by cherry pick date %s found entry %u/%u\n",
- GNUNET_TIME_timestamp2s (last_issue_date),
- (unsigned int) (krd - ksh->krd_array),
- ksh->krd_array_length);
- if ( (NULL == krd) &&
- (ksh->krd_array_length > 0) )
+ if (withdraw_filter)
{
- if (! GNUNET_TIME_absolute_is_zero (last_issue_date.abs_time))
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Client provided invalid cherry picking timestamp %s, returning full response\n",
- GNUNET_TIME_timestamp2s (last_issue_date));
- krd = &ksh->krd_array[ksh->krd_array_length - 1];
+ if (0 == ksh->withdraw_krd_array_length)
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ "no current or future withdrawal denomination keys");
+ GNUNET_assert (1 == ksh->withdraw_krd_array_length);
+ krd = &ksh->withdraw_krd_array[0];
}
- if (NULL == krd)
+ else
{
- /* Likely keys not ready *yet*.
- Wait until they are. */
- return suspend_request (rc->connection);
+ krd = bsearch (&last_issue_date,
+ ksh->krd_array,
+ ksh->krd_array_length,
+ sizeof (struct KeysResponseData),
+ &krd_search_comparator);
+ if (NULL != krd)
+ GNUNET_log (
+ GNUNET_ERROR_TYPE_INFO,
+ "Filtering /keys by cherry pick date %s found entry %u/%u\n",
+ GNUNET_TIME_timestamp2s (last_issue_date),
+ (unsigned int) (krd - ksh->krd_array),
+ ksh->krd_array_length);
+ if ( (NULL == krd) &&
+ (ksh->krd_array_length > 0) )
+ {
+ if (! GNUNET_TIME_absolute_is_zero (last_issue_date.abs_time))
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Client provided invalid cherry picking timestamp %s, returning full response\n",
+ GNUNET_TIME_timestamp2s (last_issue_date));
+ krd = &ksh->krd_array[ksh->krd_array_length - 1];
+ }
+ if (NULL == krd)
+ {
+ /* Likely keys not ready *yet*.
+ Wait until they are. */
+ return suspend_request (rc->connection);
+ }
}
if ( (NULL != etag) &&
(0 == strcmp (etag,
krd->etag)) )
return TEH_RESPONSE_reply_not_modified (rc->connection,
krd->etag,
- &setup_general_response_headers,
+ withdraw_filter
+ ? &setup_withdraw_response_headers
+ : &setup_general_response_headers,
ksh);
return MHD_queue_response (
diff --git a/src/exchange/taler-exchange-httpd_get-keys.h b/src/exchange/taler-exchange-httpd_get-keys.h
@@ -79,6 +79,12 @@ struct TEH_DenominationKey
*/
bool recoup_possible;
+ /**
+ * Set while building the public key state if this denomination belongs in
+ * the withdrawal-focused `/keys` response.
+ */
+ bool withdraw_relevant;
+
};