gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-donau] branch master updated (fb51789 -> f49c763)


From: gnunet
Subject: [taler-donau] branch master updated (fb51789 -> f49c763)
Date: Sat, 06 Jan 2024 23:18:09 +0100

This is an automated email from the git hooks/post-receive script.

pius-loosli pushed a change to branch master
in repository donau.

    from fb51789  [db] fix errors
     new afc7557  [deps] add json from exchange (can be removed again if json 
is linked against json from exchange)
     new 995ba60  [donau] First iteration on /keys: adapt structs to donau 
starting from exchange /management/keys
     new f49c763  [donau] first iteration on /keys: adapt structs and includes 
from exchange get /keys as well

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/donau/Makefile.am          |    1 -
 src/donau/donau-httpd.c        |   56 +-
 src/donau/donau-httpd_keys.c   |  740 ++++++++++++++----
 src/donau/donau-httpd_keys.h   |  121 ++-
 src/include/donau_crypto_lib.h |    1 +
 src/json/Makefile.am           |    5 +-
 src/json/json.c                |  763 ++++++++++++++++++-
 src/json/json_helper.c         | 1621 ++++++++++++++++++++++++++++++++++++++++
 src/json/json_pack.c           |  324 ++++++++
 9 files changed, 3457 insertions(+), 175 deletions(-)
 create mode 100644 src/json/json_helper.c
 create mode 100644 src/json/json_pack.c

diff --git a/src/donau/Makefile.am b/src/donau/Makefile.am
index 02cb450..b294804 100644
--- a/src/donau/Makefile.am
+++ b/src/donau/Makefile.am
@@ -27,7 +27,6 @@ donau_httpd_LDADD = \
   -lmicrohttpd \
   -ltalermhd   \
   -ltalerutil  \
-  -ltalertemplating \
   -lgnunetcurl \
   -lgnunetutil \
   -lgnunetjson \
diff --git a/src/donau/donau-httpd.c b/src/donau/donau-httpd.c
index 9bae4f4..54c750d 100644
--- a/src/donau/donau-httpd.c
+++ b/src/donau/donau-httpd.c
@@ -50,7 +50,7 @@
  * Above what request latency do we start to log?
  */
  #define WARN_LATENCY GNUNET_TIME_relative_multiply ( \
-           GNUNET_TIME_UNIT_MILLISECONDS, 500)
+    GNUNET_TIME_UNIT_MILLISECONDS, 500)
 
 /**
  * Are clients allowed to request /keys for times other than the
@@ -176,15 +176,15 @@ typedef MHD_RESULT
  * @param connection where to send the reply on
  * @param details details for the error message, can be NULL
  */
-static MHD_RESULT
-r404 (struct MHD_Connection *connection,
-      const char *details)
-{
-  return TALER_MHD_reply_with_error (connection,
-                                     MHD_HTTP_NOT_FOUND,
-                                     
TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
-                                     details);
-}
+// static MHD_RESULT
+// r404 (struct MHD_Connection *connection,
+//       const char *details)
+// {
+//   return TALER_MHD_reply_with_error (connection,
+//                                      MHD_HTTP_NOT_FOUND,
+//                                      
TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
+//                                      details);
+// }
 
 
 /**
@@ -386,31 +386,6 @@ proceed_with_handler (struct DH_RequestContext *rc,
 }
 
 
-/**
- * Handle a GET "/management" request.
- *
- * @param rc request context
- * @param args array of additional options (must be [0] == "keys")
- * @return MHD result code
- */
-static MHD_RESULT
-handle_get_management (struct DH_RequestContext *rc,
-                       const char *const args[2])
-{
-  if ( (NULL != args[0]) &&
-       (0 == strcmp (args[0],
-                     "keys")) &&
-       (NULL == args[1]) )
-  {
-    return DH_keys_management_get_keys_handler (rc->rh,
-                                                rc->connection);
-  }
-  GNUNET_break_op (0);
-  return r404 (rc->connection,
-               "/management/*");
-}
-
-
 /**
  * Handle incoming HTTP request.
  *
@@ -450,13 +425,18 @@ handle_mhd_request (void *cls,
       .method = MHD_HTTP_METHOD_GET,
       .handler.get = &DH_handler_config
     },
-    /* GET management endpoints (we only really have "/management/keys") */
+    /* GET keys endpoints (we only really have "/keys") */
     {
-      .url = "management",
+      .url = "keys",
       .method = MHD_HTTP_METHOD_GET,
-      .handler.get = &handle_get_management,
+      .handler.get = &DH_keys_get_handler,
       .nargs = 1
     },
+    /**
+    etc
+
+    Add routes here
+    */
     /* mark end of list */
     {
       .url = NULL
diff --git a/src/donau/donau-httpd_keys.c b/src/donau/donau-httpd_keys.c
index 11ce156..6558bfc 100644
--- a/src/donau/donau-httpd_keys.c
+++ b/src/donau/donau-httpd_keys.c
@@ -24,17 +24,233 @@
 #include "taler/taler_mhd_lib.h"
 #include "donau-httpd.h"
 #include "donau-httpd_keys.h"
-// #include "donau-httpd_config.h"
-// #include "donau-httpd_responses.h"
+#include "donau-httpd_config.h"
 #include "donaudb_plugin.h"
 
-
 /**
  * How many /keys request do we hold in suspension at
  * most at any time?
  */
 #define SKR_LIMIT 32
 
+/**
+ * When do we forcefully timeout a /keys request?
+ */
+#define KEYS_TIMEOUT GNUNET_TIME_UNIT_MINUTES
+
+/**
+ * Number of entries in the @e skr_head DLL.
+ */
+static unsigned int skr_size;
+
+/**
+ * Handle to a connection that should be force-resumed
+ * with a hard error due to @a skr_size hitting
+ * #SKR_LIMIT.
+ */
+static struct MHD_Connection *skr_connection;
+
+/**
+ * Entry of /keys requests that are currently suspended because we are
+ * waiting for /keys to become ready.
+ */
+struct SuspendedKeysRequests
+{
+  /**
+   * Kept in a DLL.
+   */
+  struct SuspendedKeysRequests *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct SuspendedKeysRequests *prev;
+
+  /**
+   * The suspended connection.
+   */
+  struct MHD_Connection *connection;
+
+  /**
+   * When does this request timeout?
+   */
+  struct GNUNET_TIME_Absolute timeout;
+};
+
+/**
+ * Head of DLL of suspended /keys requests.
+ */
+static struct SuspendedKeysRequests *skr_head;
+
+/**
+ * Tail of DLL of suspended /keys requests.
+ */
+static struct SuspendedKeysRequests *skr_tail;
+
+/**
+ * Task to force timeouts on /keys requests.
+ */
+static struct GNUNET_SCHEDULER_Task *keys_tt;
+
+/**
+ * Are we shutting down?
+ */
+static bool terminating;
+
+/**
+ * Function called to forcefully resume suspended keys requests.
+ *
+ * @param cls unused, NULL
+ */
+static void
+keys_timeout_cb (void *cls)
+{
+  struct SuspendedKeysRequests *skr;
+
+  (void) cls;
+  keys_tt = NULL;
+  while (NULL != (skr = skr_head))
+  {
+    if (GNUNET_TIME_absolute_is_future (skr->timeout))
+      break;
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Resuming /keys request due to timeout\n");
+    GNUNET_CONTAINER_DLL_remove (skr_head,
+                                 skr_tail,
+                                 skr);
+    MHD_resume_connection (skr->connection);
+    TALER_MHD_daemon_trigger ();
+    GNUNET_free (skr);
+  }
+  if (NULL == skr)
+    return;
+  keys_tt = GNUNET_SCHEDULER_add_at (skr->timeout,
+                                     &keys_timeout_cb,
+                                     NULL);
+}
+
+
+/**
+ * Suspend /keys request while we (hopefully) are waiting to be
+ * provisioned with key material.
+ *
+ * @param[in] connection to suspend
+ */
+static MHD_RESULT
+suspend_request (struct MHD_Connection *connection)
+{
+  struct SuspendedKeysRequests *skr;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Suspending /keys request until key material changes\n");
+  if (terminating)
+  {
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+                                       "Exchange terminating");
+  }
+  skr = GNUNET_new (struct SuspendedKeysRequests);
+  skr->connection = connection;
+  MHD_suspend_connection (connection);
+  GNUNET_CONTAINER_DLL_insert (skr_head,
+                               skr_tail,
+                               skr);
+  skr->timeout = GNUNET_TIME_relative_to_absolute (KEYS_TIMEOUT);
+  if (NULL == keys_tt)
+  {
+    keys_tt = GNUNET_SCHEDULER_add_at (skr->timeout,
+                                       &keys_timeout_cb,
+                                       NULL);
+  }
+  skr_size++;
+  if (skr_size > SKR_LIMIT)
+  {
+    skr = skr_tail;
+    GNUNET_CONTAINER_DLL_remove (skr_head,
+                                 skr_tail,
+                                 skr);
+    skr_size--;
+    skr_connection = skr->connection;
+    MHD_resume_connection (skr->connection);
+    TALER_MHD_daemon_trigger ();
+    GNUNET_free (skr);
+  }
+  return MHD_YES;
+}
+
+
+/**
+ * Information about a donation unit on offer by the donation unit helper.
+ */
+struct HelperDonationUnit
+{
+
+  /**
+   * When will the helper start to use this key for signing?
+   */
+  struct GNUNET_TIME_Timestamp start_time;
+
+  /**
+   * For how long will the helper allow signing? 0 if
+   * the key was revoked or purged.
+   */
+  struct GNUNET_TIME_Relative validity_duration;
+
+  /**
+   * Hash of the full donation unit key.
+   */
+  struct DONAU_DonationUnitHashP h_donation_unit_pub;
+
+  /**
+   * The (full) public key.
+   */
+  struct DONAU_DonationUnitPublicKey donation_unit_pub;
+
+  /**
+   * Details depend on the @e donation_unit_pub.cipher type.
+   */
+  union
+  {
+
+    /**
+     * Hash of the RSA key.
+     */
+    struct TALER_RsaPubHashP h_rsa;
+
+    /**
+     * Hash of the CS key.
+     */
+    struct TALER_CsPubHashP h_cs;
+
+  } h_details;
+
+  /**
+   * Name in configuration section for this donation unit type.
+   */
+  char *section_name;
+
+
+};
+
+/**
+ * Information about a signing key on offer by the sign helper.
+ */
+struct HelperSignkey
+{
+  /**
+   * When will the helper start to use this key for signing?
+   */
+  // struct GNUNET_TIME_Timestamp start_time;
+  int year;
+
+  /**
+   * The public key.
+   */
+  struct DONAU_DonauPublicKeyP donau_pub;
+
+};
+
 /**
  * Counter incremented whenever we have a reason to re-build the keys because
  * something external changed.  See #DH_keys_get_state() and
@@ -45,17 +261,17 @@ static uint64_t key_generation;
 /**
  * RSA security module public key, all zero if not known.
  */
-static struct TALER_SecurityModulePublicKeyP donation_unit_rsa_sm_pub;
+// static struct TALER_SecurityModulePublicKeyP donation_unit_rsa_sm_pub;
 
 /**
  * CS security module public key, all zero if not known.
  */
-static struct TALER_SecurityModulePublicKeyP donation_unit_cs_sm_pub;
+// static struct TALER_SecurityModulePublicKeyP donation_unit_cs_sm_pub;
 
 /**
  * EdDSA security module public key, all zero if not known.
  */
-static struct TALER_SecurityModulePublicKeyP esign_sm_pub;
+// static struct TALER_SecurityModulePublicKeyP esign_sm_pub;
 
 
 /**
@@ -63,13 +279,6 @@ static struct TALER_SecurityModulePublicKeyP esign_sm_pub;
  */
 #define KEYS_TIMEOUT GNUNET_TIME_UNIT_MINUTES
 
-/**
- * Obtain the key state if we should NOT run finish_keys_response() because we
- * only need the state for the /management/keys API
- */
-struct DH_KeyStateHandle *
-DH_keys_get_state_for_management_only (void);
-
 /**
  * Stores the latest generation of our key state.
  */
@@ -98,13 +307,13 @@ struct DH_KeyStateHandle
 {
 
   /**
-   * Mapping from denomination keys to denomination key issue struct.
+   * Mapping from donation unit keys to donation unit key issue struct.
    * Used to lookup the key by hash.
    */
   struct GNUNET_CONTAINER_MultiHashMap *denomkey_map;
 
   /**
-   * Map from `struct TALER_ExchangePublicKey` to `struct SigningKey`
+   * Map from `struct DONAU_DonauPublicKey` to `struct SigningKey`
    * entries.  Based on the fact that a `struct GNUNET_PeerIdentity` is also
    * an EdDSA public key.
    */
@@ -239,23 +448,18 @@ struct HelperState
 };
 
 /**
- * Closure for #add_denom_key_cb.
+ * Closure for #add_donation_unit_key_cb.
  */
-struct DenomKeyCtx
+struct DonationUnitKeyCtx
 {
   /**
-   * Heap for sorting active denomination keys by start time.
+   * Heap for sorting active donation unit keys by start time.
    */
   struct GNUNET_CONTAINER_Heap *heap;
 
-  /**
-   * JSON array of revoked denomination keys.
-   */
-  json_t *recoup;
-
   /**
    * What is the minimum key rotation frequency of
-   * valid denomination keys?
+   * valid donation unit keys?
    */
   struct GNUNET_TIME_Relative min_dk_frequency;
 };
@@ -373,7 +577,7 @@ finish_keys_response (struct DH_KeyStateHandle *ksh)
   enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
   json_t *recoup;
   struct SignKeyCtx sctx;
-  json_t *grouped_denominations = NULL;
+  json_t *grouped_donation_units = NULL;
   struct GNUNET_TIME_Timestamp last_cherry_pick_date;
   struct GNUNET_CONTAINER_Heap *heap;
   struct GNUNET_HashContext *hash_context = NULL;
@@ -389,11 +593,10 @@ finish_keys_response (struct DH_KeyStateHandle *ksh)
   GNUNET_assert (NULL != recoup);
   heap = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MAX);
   {
-    struct DenomKeyCtx dkc = {
-      .recoup = recoup,
-      .heap = heap,
-      .min_dk_frequency = GNUNET_TIME_UNIT_FOREVER_REL,
-    };
+    // struct DonationUnitKeyCtx dkc = {
+    // .heap = heap,
+    // .min_dk_frequency = GNUNET_TIME_UNIT_FOREVER_REL,
+    // };
 
     // GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
     //                                       &add_denom_key_cb,
@@ -405,8 +608,8 @@ finish_keys_response (struct DH_KeyStateHandle *ksh)
 
   hash_context = GNUNET_CRYPTO_hash_context_start ();
 
-  grouped_denominations = json_array ();
-  GNUNET_assert (NULL != grouped_denominations);
+  grouped_donation_units = json_array ();
+  GNUNET_assert (NULL != grouped_donation_units);
 
   last_cherry_pick_date = GNUNET_TIME_UNIT_ZERO_TS;
 
@@ -436,14 +639,14 @@ finish_keys_response (struct DH_KeyStateHandle *ksh)
   else
   {
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "No denomination keys available. Refusing to generate /keys 
response.\n");
+                "No donation unit keys available. Refusing to generate /keys 
response.\n");
     GNUNET_CRYPTO_hash_context_abort (hash_context);
   }
 
   ret = GNUNET_OK;
 
-CLEANUP:
-  json_decref (grouped_denominations);
+// CLEANUP:
+  json_decref (grouped_donation_units);
   json_decref (sctx.signkeys);
   json_decref (recoup);
   return ret;
@@ -451,17 +654,17 @@ CLEANUP:
 
 
 /**
- * Free denomination key data.
+ * Free donation unit key data.
  *
  * @param cls a `struct DH_KeyStateHandle`, unused
- * @param h_donation_unit_pub hash of the denomination public key, unused
+ * @param h_donation_unit_pub hash of the donation unit public key, unused
  * @param value a `struct DH_DonationUnitKey` to free
  * @return #GNUNET_OK (continue to iterate)
  */
 static enum GNUNET_GenericReturnValue
-clear_denomination_cb (void *cls,
-                       const struct GNUNET_HashCode *h_donation_unit_pub,
-                       void *value)
+clear_donation_unit_cb (void *cls,
+                        const struct GNUNET_HashCode *h_donation_unit_pub,
+                        void *value)
 {
   struct DH_DonationUnitKey *dk = value;
 
@@ -474,7 +677,7 @@ clear_denomination_cb (void *cls,
 
 
 /**
- * Free denomination key data.
+ * Free donation unit key data.
  *
  * @param cls a `struct DH_KeyStateHandle`, unused
  * @param pid the online signing key (type-disguised), unused
@@ -594,7 +797,7 @@ destroy_key_state (struct DH_KeyStateHandle *ksh,
   //  GNUNET_free (gf);
   // }
   GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
-                                         &clear_denomination_cb,
+                                         &clear_donation_unit_cb,
                                          ksh);
   GNUNET_CONTAINER_multihashmap_destroy (ksh->denomkey_map);
   GNUNET_CONTAINER_multipeermap_iterate (ksh->signkey_map,
@@ -677,11 +880,11 @@ setup_key_helpers (struct HelperState *hs)
  * @return NULL on error (i.e. failed to access database)
  */
 static struct DH_KeyStateHandle *
-build_key_state (struct HelperState *hs,
-                 bool management_only)
+build_key_state (struct HelperState *hs /*,
+                 bool management_only*/)
 {
   struct DH_KeyStateHandle *ksh;
-  enum GNUNET_DB_QueryStatus qs;
+  // enum GNUNET_DB_QueryStatus qs;
 
   ksh = GNUNET_new (struct DH_KeyStateHandle);
   ksh->signature_expires = GNUNET_TIME_UNIT_FOREVER_TS;
@@ -743,11 +946,11 @@ build_key_state (struct HelperState *hs,
   //  return NULL;
   // }
 
-  if (management_only)
-  {
-    ksh->management_only = true;
-    return ksh;
-  }
+  // if (management_only)
+  // {
+  //   ksh->management_only = true;
+  //   return ksh;
+  // }
 
   if (GNUNET_OK !=
       finish_keys_response (ksh))
@@ -781,14 +984,14 @@ DH_keys_update_states ()
 
 
 static struct DH_KeyStateHandle *
-keys_get_state (bool management_only)
+DH_keys_get_state (/*bool management_only*/)
 {
   struct DH_KeyStateHandle *old_ksh;
   struct DH_KeyStateHandle *ksh;
   old_ksh = key_state;
   if (NULL == old_ksh)
   {
-    // ksh = build_key_state (NULL, management_only);
+    ksh = build_key_state (NULL /*, management_only*/);
     ksh = NULL;
     if (NULL == ksh)
       return NULL;
@@ -802,8 +1005,8 @@ keys_get_state (bool management_only)
                 "Rebuilding /keys, generation upgrade from %llu to %llu\n",
                 (unsigned long long) old_ksh->key_generation,
                 (unsigned long long) key_generation);
-    ksh = build_key_state (old_ksh->helpers,
-                           management_only);
+    ksh = build_key_state (old_ksh->helpers /*,
+                           management_only*/);
     key_state = ksh;
     old_ksh->helpers = NULL;
     destroy_key_state (old_ksh,
@@ -815,92 +1018,375 @@ keys_get_state (bool management_only)
 }
 
 
-struct DH_KeyStateHandle *
-DH_keys_get_state_for_management_only (void)
+/**
+ * Closure for #add_future_donation_unit_cb and #add_future_signkey_cb.
+ */
+struct FutureBuilderContext
+{
+  /**
+   * Our key state.
+   */
+  struct DH_KeyStateHandle *ksh;
+
+  /**
+   * Array of donation unit keys.
+   */
+  json_t *donation_units;
+
+  /**
+   * Array of signing keys.
+   */
+  json_t *signkeys;
+
+};
+
+// /**
+//  * Function called on all of our current and future donation unit keys
+//  * known to the helper process. Filters out those that are current
+//  * and adds the remaining donation unit keys (with their configuration
+//  * data) to the JSON array.
+//  *
+//  * @param cls the `struct FutureBuilderContext *`
+//  * @param h_donation_unit_pub hash of the donation unit public key
+//  * @param value a `struct HelperDenomination`
+//  * @return #GNUNET_OK (continue to iterate)
+//  */
+// static enum GNUNET_GenericReturnValue
+// add_donation_unitkey_cb (void *cls,
+//                          const struct GNUNET_HashCode *h_donation_unit_pub,
+//                          void *value)
+// {
+//   struct FutureBuilderContext *fbc = cls;
+//   struct HelperDonationUnit *helper_donation_unit = value;
+//   struct DH_DonationUnitKey *donation_unit_key;
+//   struct DONAUDB_DonationUnitKeyMetaData meta = {0};
+
+//   donation_unit_key = GNUNET_CONTAINER_multihashmap_get 
(fbc->ksh->denomkey_map,
+//                                                          
h_donation_unit_pub);
+//   if (NULL != donation_unit_key)
+//     return GNUNET_OK; /* skip: this key is already active! */
+//   // if (GNUNET_TIME_relative_is_zero (hd->validity_duration))
+//   // return GNUNET_OK; /* this key already expired! */
+
+//   GNUNET_assert (
+//     0 ==
+//     json_array_append_new (
+//       fbc->donation_units,
+//       GNUNET_JSON_PACK (
+//         TALER_JSON_pack_amount ("value",
+//                                 &meta.value),
+//         GNUNET_JSON_pack_uint64 ("year",
+//                                  meta.validity_year),
+//         GNUNET_JSON_pack_data_auto ("donation_unit_pub",
+//                                     
&helper_donation_unit->donation_unit_pub)
+//         // GNUNET_JSON_pack_string ("section_name",
+//         //  helper_donation_unit->section_name)
+//         )));
+//   return GNUNET_OK;
+// }
+
+
+// /**
+//  * Function called on all of our current and future exchange signing keys
+//  * known to the helper process. Filters out those that are current
+//  * and adds the remaining signing keys (with their configuration
+//  * data) to the JSON array.
+//  *
+//  * @param cls the `struct FutureBuilderContext *`
+//  * @param pid actually the exchange public key (type disguised)
+//  * @param value a `struct HelperDenomination`
+//  * @return #GNUNET_OK (continue to iterate)
+//  */
+// static enum GNUNET_GenericReturnValue
+// add_signkey_cb (void *cls,
+//                 const struct GNUNET_PeerIdentity *pid,
+//                 void *value)
+// {
+//   struct FutureBuilderContext *fbc = cls;
+//   struct HelperSignkey *hsk = value;
+//   struct SigningKey *sk;
+//   // struct GNUNET_TIME_Timestamp stamp_expire;
+//   // struct GNUNET_TIME_Timestamp legal_end;
+
+//   sk = GNUNET_CONTAINER_multipeermap_get (fbc->ksh->signkey_map,
+//                                           pid);
+//   if (NULL != sk)
+//     return GNUNET_OK; /* skip: this key is already active */
+//   // if (GNUNET_TIME_relative_is_zero (hsk->validity_duration))
+//   // return GNUNET_OK; /* this key already expired! */
+//   // stamp_expire = GNUNET_TIME_absolute_to_timestamp (
+//   // GNUNET_TIME_absolute_add (hsk->start_time.abs_time,
+//   // hsk->validity_duration));
+//   // legal_end = GNUNET_TIME_absolute_to_timestamp (
+//   // GNUNET_TIME_absolute_add (stamp_expire.abs_time,
+//   // signkey_legal_duration));
+//   GNUNET_assert (0 ==
+//                  json_array_append_new (
+//                    fbc->signkeys,
+//                    GNUNET_JSON_PACK (
+//                      GNUNET_JSON_pack_data_auto ("key",
+//                                                  &hsk->donau_pub),
+//                      //  GNUNET_JSON_pack_timestamp ("stamp_end",
+//                      //  legal_end),
+//                      GNUNET_JSON_pack_data_auto ("year",
+//                                                  &hsk->year)
+//                      //  GNUNET_JSON_pack_data_auto ("signkey_secmod_sig",
+//                      //  &hsk->sm_sig)
+//                      )));
+//   return GNUNET_OK;
+// }
+
+
+// MHD_RESULT
+// DH_get_keys_handler (const struct DH_RequestHandler *rh,
+//                      struct MHD_Connection *connection)
+// {
+//   struct DH_KeyStateHandle *ksh;
+//   json_t *reply;
+
+//   (void) rh;
+//   ksh = keys_get_state (true);
+//   if (NULL == ksh)
+//   {
+//     return TALER_MHD_reply_with_error (connection,
+//                                        MHD_HTTP_SERVICE_UNAVAILABLE,
+//                                        
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+//                                        "no key state");
+//   }
+//   sync_key_helpers (ksh->helpers);
+//   if (NULL == ksh->management_keys_reply)
+//   {
+//     struct FutureBuilderContext fbc = {
+//       .ksh = ksh,
+//       .donation_units = json_array (),
+//       .signkeys = json_array ()
+//     };
+//     if ( (GNUNET_is_zero (&donation_unit_rsa_sm_pub)) &&
+//          (GNUNET_is_zero (&donation_unit_cs_sm_pub)) )
+//     {
+//       /* Either IPC failed, or neither helper had any donation_unit 
configured. */
+//       return TALER_MHD_reply_with_error (connection,
+//                                          MHD_HTTP_BAD_GATEWAY,
+//                                          
TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE,
+//                                          NULL);
+//     }
+//     if (GNUNET_is_zero (&esign_sm_pub))
+//     {
+//       return TALER_MHD_reply_with_error (connection,
+//                                          MHD_HTTP_BAD_GATEWAY,
+//                                          
TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE,
+//                                          NULL);
+//     }
+//     GNUNET_assert (NULL != fbc.donation_units);
+//     GNUNET_assert (NULL != fbc.signkeys);
+//     GNUNET_CONTAINER_multihashmap_iterate (ksh->helpers->donation_unit_keys,
+//                                            &add_donation_unitkey_cb,
+//                                            &fbc);
+//     GNUNET_CONTAINER_multipeermap_iterate (ksh->helpers->esign_keys,
+//                                            &add_signkey_cb,
+//                                            &fbc);
+//     reply = GNUNET_JSON_PACK (
+//       GNUNET_JSON_pack_array_steal ("future_donation_units",
+//                                     fbc.donation_units),
+//       GNUNET_JSON_pack_array_steal ("future_signkeys",
+//                                     fbc.signkeys),
+//       GNUNET_JSON_pack_data_auto ("donation_unit_secmod_public_key",
+//                                   &donation_unit_rsa_sm_pub),
+//       GNUNET_JSON_pack_data_auto ("donation_unit_secmod_cs_public_key",
+//                                   &donation_unit_cs_sm_pub),
+//       GNUNET_JSON_pack_data_auto ("signkey_secmod_public_key",
+//                                   &esign_sm_pub));
+//     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+//                 "Returning GET /keys response:\n");
+//     if (NULL == reply)
+//       return TALER_MHD_reply_with_error (connection,
+//                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
+//                                          
TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
+//                                          NULL);
+//     GNUNET_assert (NULL == ksh->management_keys_reply);
+//     ksh->management_keys_reply = reply;
+//   }
+//   else
+//   {
+//     reply = ksh->management_keys_reply;
+//   }
+//   return TALER_MHD_reply_json (connection,
+//                                reply,
+//                                MHD_HTTP_OK);
+// }
+
+/**
+ * Comparator used for a binary search by cherry_pick_date for @a key in the
+ * `struct KeysResponseData` array. See libc's qsort() and bsearch() functions.
+ *
+ * @param key pointer to a `struct GNUNET_TIME_Timestamp`
+ * @param value pointer to a `struct KeysResponseData` array entry
+ * @return 0 if time matches, -1 if key is smaller, 1 if key is larger
+ */
+static int
+krd_search_comparator (const void *key,
+                       const void *value)
+{
+  const struct GNUNET_TIME_Timestamp *kd = key;
+  const struct KeysResponseData *krd = value;
+
+  if (GNUNET_TIME_timestamp_cmp (*kd,
+                                 >,
+                                 krd->cherry_pick_date))
+    return -1;
+  if (GNUNET_TIME_timestamp_cmp (*kd,
+                                 <,
+                                 krd->cherry_pick_date))
+    return 1;
+  return 0;
+}
+
+
+/**
+ * Callback used to set headers in a response.
+ *
+ * @param cls closure
+ * @param[in,out] resp response to modify
+ */
+typedef void
+(*TEH_RESPONSE_SetHeaders)(void *cls,
+                           struct MHD_Response *resp);
+
+
+MHD_RESULT
+DH_RESPONSE_reply_not_modified (
+  struct MHD_Connection *connection,
+  const char *etags,
+  TEH_RESPONSE_SetHeaders cb,
+  void *cb_cls)
 {
-  return keys_get_state (true);
+  MHD_RESULT ret;
+  struct MHD_Response *resp;
+
+  resp = MHD_create_response_from_buffer (0,
+                                          NULL,
+                                          MHD_RESPMEM_PERSISTENT);
+  cb (cb_cls,
+      resp);
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (resp,
+                                         MHD_HTTP_HEADER_ETAG,
+                                         etags));
+  ret = MHD_queue_response (connection,
+                            MHD_HTTP_NOT_MODIFIED,
+                            resp);
+  GNUNET_break (MHD_YES == ret);
+  MHD_destroy_response (resp);
+  return ret;
 }
 
 
 MHD_RESULT
-DH_keys_management_get_keys_handler (const struct DH_RequestHandler *rh,
-                                     struct MHD_Connection *connection)
+DH_keys_get_handler (struct DH_RequestContext *rc,
+                     const char *const args[])
 {
-  struct DH_KeyStateHandle *ksh;
-  json_t *reply;
+  struct GNUNET_TIME_Timestamp last_issue_date;
+  const char *etag;
+
+  etag = MHD_lookup_connection_value (rc->connection,
+                                      MHD_HEADER_KIND,
+                                      MHD_HTTP_HEADER_IF_NONE_MATCH);
+  (void) args;
+  // {
+  //   const char *have_cherrypick;
+
+  //   have_cherrypick = MHD_lookup_connection_value (rc->connection,
+  //                                                  MHD_GET_ARGUMENT_KIND,
+  //                                                  "last_issue_date");
+  //   if (NULL != have_cherrypick)
+  //   {
+  //     unsigned long long cherrypickn;
+
+  //     if (1 !=
+  //         sscanf (have_cherrypick,
+  //                 "%llu",
+  //                 &cherrypickn))
+  //     {
+  //       GNUNET_break_op (0);
+  //       return TALER_MHD_reply_with_error (rc->connection,
+  //                                          MHD_HTTP_BAD_REQUEST,
+  //                                          
TALER_EC_GENERIC_PARAMETER_MALFORMED,
+  //                                          have_cherrypick);
+  //     }
+  //     /* The following multiplication may overflow; but this should not 
really
+  //        be a problem, as giving back 'older' data than what the client 
asks for
+  //        (given that the client asks for data in the distant future) is not
+  //        problematic */
+  //     last_issue_date = GNUNET_TIME_timestamp_from_s (cherrypickn);
+  //   }
+  //   else
+  //   {
+  //     last_issue_date = GNUNET_TIME_UNIT_ZERO_TS;
+  //   }
+  // }
 
-  (void) rh;
-  // ksh = DH_keys_get_state_for_management_only ();
-  ksh = NULL;
-  if (NULL == ksh)
-  {
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_SERVICE_UNAVAILABLE,
-                                       TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
-                                       "no key state");
-  }
-  sync_key_helpers (ksh->helpers);
-  if (NULL == ksh->management_keys_reply)
   {
-    // struct FutureBuilderContext fbc = {
-    //  .ksh = ksh,
-    //  .donation_units = json_array (),
-    //  .signkeys = json_array ()
-    // };
-    if ( (GNUNET_is_zero (&donation_unit_rsa_sm_pub)) &&
-         (GNUNET_is_zero (&donation_unit_cs_sm_pub)) )
+    struct DH_KeyStateHandle *ksh;
+    const struct KeysResponseData *krd;
+
+    ksh = DH_keys_get_state ();
+    if ( (NULL == ksh) ||
+         (0 == ksh->krd_array_length) )
     {
-      /* Either IPC failed, or neither helper had any donation_unitinations 
configured. */
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_GATEWAY,
-                                         
TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE,
-                                         NULL);
+      if ( ( (SKR_LIMIT == skr_size) &&
+             (rc->connection == skr_connection) ) ||
+           DH_suicide)
+      {
+        return TALER_MHD_reply_with_error (
+          rc->connection,
+          MHD_HTTP_SERVICE_UNAVAILABLE,
+          TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+          DH_suicide
+          ? "server terminating"
+          : "too many connections suspended waiting on /keys");
+      }
+      return suspend_request (rc->connection);
     }
-    if (GNUNET_is_zero (&esign_sm_pub))
+    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) )
     {
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_GATEWAY,
-                                         
TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE,
-                                         NULL);
+      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];
     }
-    // GNUNET_assert (NULL != fbc.donation_units);
-    // GNUNET_assert (NULL != fbc.signkeys);
-    // GNUNET_CONTAINER_multihashmap_iterate (ksh->helpers->donation_unit_keys,
-    //                                       &add_future_donation_unitkey_cb,
-    //                                       &fbc);
-    // GNUNET_CONTAINER_multipeermap_iterate (ksh->helpers->esign_keys,
-    //                                       &add_future_signkey_cb,
-    //                                       &fbc);
-    reply = GNUNET_JSON_PACK (
-      // GNUNET_JSON_pack_array_steal ("future_donation_units",
-      //                              fbc.donation_units),
-      // GNUNET_JSON_pack_array_steal ("future_signkeys",
-      //                              fbc.signkeys),
-      // GNUNET_JSON_pack_data_auto ("master_pub",
-      //                            &DH_master_public_key),
-      GNUNET_JSON_pack_data_auto ("donation_unit_secmod_public_key",
-                                  &donation_unit_rsa_sm_pub),
-      GNUNET_JSON_pack_data_auto ("donation_unit_secmod_cs_public_key",
-                                  &donation_unit_cs_sm_pub),
-      GNUNET_JSON_pack_data_auto ("signkey_secmod_public_key",
-                                  &esign_sm_pub));
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Returning GET /management/keys response:\n");
-    if (NULL == reply)
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         
TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
-                                         NULL);
-    GNUNET_assert (NULL == ksh->management_keys_reply);
-    ksh->management_keys_reply = reply;
-  }
-  else
-  {
-    reply = ksh->management_keys_reply;
+    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 DH_RESPONSE_reply_not_modified (rc->connection,
+                                             krd->etag,
+                                             &setup_general_response_headers,
+                                             ksh);
+
+    return MHD_queue_response (rc->connection,
+                               MHD_HTTP_OK,
+                               (MHD_YES ==
+                                TALER_MHD_can_compress (rc->connection))
+                               ? krd->response_compressed
+                               : krd->response_uncompressed);
   }
-  return TALER_MHD_reply_json (connection,
-                               reply,
-                               MHD_HTTP_OK);
 }
 
 
diff --git a/src/donau/donau-httpd_keys.h b/src/donau/donau-httpd_keys.h
index 729a863..9d46f67 100644
--- a/src/donau/donau-httpd_keys.h
+++ b/src/donau/donau-httpd_keys.h
@@ -57,16 +57,125 @@ struct DH_DonationUnitKey
 };
 
 /**
- * Function to call to handle requests to "/management/keys" by sending
- * back our future key material.
+ * Information needed to create a blind signature.
+ */
+// struct DH_CoinSignData
+// {
+/**
+   * Hash of key to sign with.
+   */
+// const struct TALER_DenominationHashP *h_denom_pub;
+
+/**
+   * Blinded planchet to sign over.
+   */
+// const struct TALER_BlindedPlanchet *bp;
+// };
+
+
+// /**
+//  * Request to sign @a csds.
+//  *
+//  * @param csds array with data to blindly sign (and keys to sign with)
+//  * @param csds_length length of @a csds array
+//  * @param for_melt true if this is for a melt operation
+//  * @param[out] bss array set to the blind signature on success; must be of 
length @a csds_length
+//  * @return #TALER_EC_NONE on success
+//  */
+// enum TALER_ErrorCode
+// TEH_keys_denomination_batch_sign (
+//   unsigned int csds_length,
+//   const struct TEH_CoinSignData csds[static csds_length],
+//   bool for_melt,
+//   struct TALER_BlindedDenominationSignature bss[static csds_length]);
+
+
+// /**
+//  * Information needed to derive the CS r_pub.
+//  */
+// struct TEH_CsDeriveData
+// {
+//   /**
+//    * Hash of key to sign with.
+//    */
+//   const struct TALER_DenominationHashP *h_denom_pub;
+
+//   /**
+//    * Nonce to use.
+//    */
+//   const struct GNUNET_CRYPTO_CsSessionNonce *nonce;
+// };
+
+
+// /**
+//  * Request to derive CS @a r_pub using the denomination and nonce from @a 
cdd.
+//  *
+//  * @param cdd data to compute @a r_pub from
+//  * @param for_melt true if this is for a melt operation
+//  * @param[out] r_pub where to write the result
+//  * @return #TALER_EC_NONE on success
+//  */
+// enum TALER_ErrorCode
+// TEH_keys_denomination_cs_r_pub (
+//   const struct TEH_CsDeriveData *cdd,
+//   bool for_melt,
+//   struct GNUNET_CRYPTO_CSPublicRPairP *r_pub);
+
+// /**
+//  * Request to derive a bunch of CS @a r_pubs using the
+//  * denominations and nonces from @a cdds.
+//  *
+//  * @param cdds array to compute @a r_pubs from
+//  * @param cdds_length length of the @a cdds array
+//  * @param for_melt true if this is for a melt operation
+//  * @param[out] r_pubs array where to write the result; must be of length @a 
cdds_length
+//  * @return #TALER_EC_NONE on success
+//  */
+// enum TALER_ErrorCode
+// TEH_keys_denomination_cs_batch_r_pub (
+//   unsigned int cdds_length,
+//   const struct TEH_CsDeriveData cdds[static cdds_length],
+//   bool for_melt,
+//   struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[static cdds_length]);
+
+
+/**
+ * Fully clean up keys subsystem.
+ */
+void
+TEH_keys_finished (void);
+
+
+/**
+ * Resumes all suspended /keys requests, we may now have key material
+ * (or are shutting down).
  *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
+ * @param do_shutdown are we shutting down?
+ */
+void
+TEH_resume_keys_requests (bool do_shutdown);
+
+
+/**
+ * Function to call to handle requests to "/keys" by sending
+ * back our current key material.
+ *
+ * @param rc request context
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 MHD_RESULT
-DH_keys_management_get_keys_handler (const struct DH_RequestHandler *rh,
-                                     struct MHD_Connection *connection);
+DH_keys_get_handler (struct DH_RequestContext *rc,
+                     const char *const args[]);
+
+
+/**
+ * Initialize keys subsystem.
+ *
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_keys_init (void);
 
 
 #endif
diff --git a/src/include/donau_crypto_lib.h b/src/include/donau_crypto_lib.h
index 917f63e..468d218 100644
--- a/src/include/donau_crypto_lib.h
+++ b/src/include/donau_crypto_lib.h
@@ -31,6 +31,7 @@
 
 #include <gnunet/gnunet_util_lib.h>
 #include "taler/taler_error_codes.h"
+#include "taler/taler_util.h"
 #include <gcrypt.h>
 #include <jansson.h>
 
diff --git a/src/json/Makefile.am b/src/json/Makefile.am
index d952331..8806c67 100644
--- a/src/json/Makefile.am
+++ b/src/json/Makefile.am
@@ -10,7 +10,10 @@ lib_LTLIBRARIES = \
   libtalerjson.la
 
 libtalerjson_la_SOURCES = \
-  json.c 
+  json.c \
+  json_helper.c \
+  json_pack.c
+
 libtalerjson_la_LDFLAGS = \
   -version-info 1:0:1 \
   -no-undefined
diff --git a/src/json/json.c b/src/json/json.c
index e71577b..8c2415c 100644
--- a/src/json/json.c
+++ b/src/json/json.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2023 Taler Systems SA
+  Copyright (C) 2024 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -21,9 +21,768 @@
  */
 #include "taler/platform.h"
 #include <gnunet/gnunet_util_lib.h>
-#include <taler/taler_util.h>
+#include "taler/taler_util.h"
 #include "taler/taler_json_lib.h"
 #include <unistr.h>
 
 
+/**
+ * Check if @a json contains a 'real' value anywhere.
+ *
+ * @param json json to check
+ * @return true if a real is in it somewhere
+ */
+static bool
+contains_real (const json_t *json)
+{
+  if (json_is_real (json))
+    return true;
+  if (json_is_object (json))
+  {
+    json_t *member;
+    const char *name;
+
+    json_object_foreach ((json_t *) json, name, member)
+    if (contains_real (member))
+      return true;
+    return false;
+  }
+  if (json_is_array (json))
+  {
+    json_t *member;
+    size_t index;
+
+    json_array_foreach ((json_t *) json, index, member)
+    if (contains_real (member))
+      return true;
+    return false;
+  }
+  return false;
+}
+
+
+/**
+ * Dump the @a json to a string and hash it.
+ *
+ * @param json value to hash
+ * @param salt salt value to include when using HKDF,
+ *        NULL to not use any salt and to use SHA512
+ * @param[out] hc where to store the hash
+ * @return #GNUNET_OK on success,
+ *         #GNUNET_NO if @a json was not hash-able
+ *         #GNUNET_SYSERR on failure
+ */
+static enum GNUNET_GenericReturnValue
+dump_and_hash (const json_t *json,
+               const char *salt,
+               struct GNUNET_HashCode *hc)
+{
+  char *wire_enc;
+  size_t len;
+
+  if (NULL == json)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_NO;
+  }
+  if (contains_real (json))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_NO;
+  }
+  if (NULL == (wire_enc = json_dumps (json,
+                                      JSON_ENCODE_ANY
+                                      | JSON_COMPACT
+                                      | JSON_SORT_KEYS)))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  len = TALER_rfc8785encode (&wire_enc);
+  if (NULL == salt)
+  {
+    GNUNET_CRYPTO_hash (wire_enc,
+                        len,
+                        hc);
+  }
+  else
+  {
+    if (GNUNET_YES !=
+        GNUNET_CRYPTO_kdf (hc,
+                           sizeof (*hc),
+                           salt,
+                           strlen (salt) + 1,
+                           wire_enc,
+                           len,
+                           NULL,
+                           0))
+    {
+      GNUNET_break (0);
+      free (wire_enc);
+      return GNUNET_SYSERR;
+    }
+  }
+  free (wire_enc);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Replace "forgettable" parts of a JSON object with their salted hash.
+ *
+ * @param[in] in some JSON value
+ * @param[out] out resulting JSON value
+ * @return #GNUNET_OK on success,
+ *         #GNUNET_NO if @a json was not hash-able
+ *         #GNUNET_SYSERR on failure
+ */
+static enum GNUNET_GenericReturnValue
+forget (const json_t *in,
+        json_t **out)
+{
+  if (json_is_real (in))
+  {
+    /* floating point is not allowed! */
+    GNUNET_break_op (0);
+    return GNUNET_NO;
+  }
+  if (json_is_array (in))
+  {
+    /* array is a JSON array */
+    size_t index;
+    json_t *value;
+    json_t *ret;
+
+    ret = json_array ();
+    if (NULL == ret)
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+    json_array_foreach (in, index, value) {
+      enum GNUNET_GenericReturnValue iret;
+      json_t *t;
+
+      iret = forget (value,
+                     &t);
+      if (GNUNET_OK != iret)
+      {
+        json_decref (ret);
+        return iret;
+      }
+      if (0 != json_array_append_new (ret,
+                                      t))
+      {
+        GNUNET_break (0);
+        json_decref (ret);
+        return GNUNET_SYSERR;
+      }
+    }
+    *out = ret;
+    return GNUNET_OK;
+  }
+  if (json_is_object (in))
+  {
+    json_t *ret;
+    const char *key;
+    json_t *value;
+    json_t *fg;
+    json_t *rx;
+
+    fg = json_object_get (in,
+                          "$forgettable");
+    rx = json_object_get (in,
+                          "$forgotten");
+    if (NULL != rx)
+    {
+      rx = json_deep_copy (rx); /* should be shallow
+                                   by structure, but
+                                   deep copy is safer */
+      if (NULL == rx)
+      {
+        GNUNET_break (0);
+        return GNUNET_SYSERR;
+      }
+    }
+    ret = json_object ();
+    if (NULL == ret)
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+    json_object_foreach ((json_t*) in, key, value) {
+      json_t *t;
+      json_t *salt;
+      enum GNUNET_GenericReturnValue iret;
+
+      if (fg == value)
+        continue; /* skip! */
+      if (rx == value)
+        continue; /* skip! */
+      if ( (NULL != rx) &&
+           (NULL !=
+            json_object_get (rx,
+                             key)) )
+      {
+        (void) json_object_del (ret,
+                                key);
+        continue; /* already forgotten earlier */
+      }
+      iret = forget (value,
+                     &t);
+      if (GNUNET_OK != iret)
+      {
+        json_decref (ret);
+        json_decref (rx);
+        return iret;
+      }
+      if ( (NULL != fg) &&
+           (NULL != (salt = json_object_get (fg,
+                                             key))) )
+      {
+        /* 't' is to be forgotten! */
+        struct GNUNET_HashCode hc;
+
+        if (! json_is_string (salt))
+        {
+          GNUNET_break_op (0);
+          json_decref (ret);
+          json_decref (rx);
+          json_decref (t);
+          return GNUNET_NO;
+        }
+        iret = dump_and_hash (t,
+                              json_string_value (salt),
+                              &hc);
+        if (GNUNET_OK != iret)
+        {
+          json_decref (ret);
+          json_decref (rx);
+          json_decref (t);
+          return iret;
+        }
+        json_decref (t);
+        /* scrub salt */
+        if (0 !=
+            json_object_del (fg,
+                             key))
+        {
+          GNUNET_break_op (0);
+          json_decref (ret);
+          json_decref (rx);
+          return GNUNET_NO;
+        }
+        if (NULL == rx)
+          rx = json_object ();
+        if (NULL == rx)
+        {
+          GNUNET_break (0);
+          json_decref (ret);
+          return GNUNET_SYSERR;
+        }
+        if (0 !=
+            json_object_set_new (rx,
+                                 key,
+                                 GNUNET_JSON_from_data_auto (&hc)))
+        {
+          GNUNET_break (0);
+          json_decref (ret);
+          json_decref (rx);
+          return GNUNET_SYSERR;
+        }
+      }
+      else
+      {
+        /* 't' to be used without 'forgetting' */
+        if (0 !=
+            json_object_set_new (ret,
+                                 key,
+                                 t))
+        {
+          GNUNET_break (0);
+          json_decref (ret);
+          json_decref (rx);
+          return GNUNET_SYSERR;
+        }
+      }
+    } /* json_object_foreach */
+    if ( (NULL != rx) &&
+         (0 !=
+          json_object_set_new (ret,
+                               "$forgotten",
+                               rx)) )
+    {
+      GNUNET_break (0);
+      json_decref (ret);
+      return GNUNET_SYSERR;
+    }
+    *out = ret;
+    return GNUNET_OK;
+  }
+  *out = json_incref ((json_t *) in);
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_contract_hash (const json_t *json,
+                          struct TALER_PrivateContractHashP *hc)
+{
+  enum GNUNET_GenericReturnValue ret;
+  json_t *cjson;
+  json_t *dc;
+
+  dc = json_deep_copy (json);
+  ret = forget (dc,
+                &cjson);
+  json_decref (dc);
+  if (GNUNET_OK != ret)
+    return ret;
+  ret = dump_and_hash (cjson,
+                       NULL,
+                       &hc->hash);
+  json_decref (cjson);
+  return ret;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_contract_mark_forgettable (json_t *json,
+                                      const char *field)
+{
+  json_t *fg;
+  struct GNUNET_ShortHashCode salt;
+
+  if (! json_is_object (json))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  /* check field name is legal for forgettable field */
+  for (const char *f = field; '\0' != *f; f++)
+  {
+    char c = *f;
+
+    if ( (c >= 'a') && (c <= 'z') )
+      continue;
+    if ( (c >= 'A') && (c <= 'Z') )
+      continue;
+    if ( (c >= '0') && (c <= '9') )
+      continue;
+    if ('_' == c)
+      continue;
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (NULL == json_object_get (json,
+                               field))
+  {
+    /* field must exist */
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  fg = json_object_get (json,
+                        "$forgettable");
+  if (NULL == fg)
+  {
+    fg = json_object ();
+    if (0 !=
+        json_object_set_new (json,
+                             "$forgettable",
+                             fg))
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+  }
+
+  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+                              &salt,
+                              sizeof (salt));
+  if (0 !=
+      json_object_set_new (fg,
+                           field,
+                           GNUNET_JSON_from_data_auto (&salt)))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_contract_part_forget (json_t *json,
+                                 const char *field)
+{
+  json_t *fg;
+  const json_t *part;
+  json_t *fp;
+  json_t *rx;
+  struct GNUNET_HashCode hc;
+  const char *salt;
+  enum GNUNET_GenericReturnValue ret;
+
+  if (! json_is_object (json))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (NULL == (part = json_object_get (json,
+                                       field)))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Did not find field `%s' we were asked to forget\n",
+                field);
+    return GNUNET_SYSERR;
+  }
+  fg = json_object_get (json,
+                        "$forgettable");
+  if (NULL == fg)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Did not find '$forgettable' attribute trying to forget field 
`%s'\n",
+                field);
+    return GNUNET_SYSERR;
+  }
+  rx = json_object_get (json,
+                        "$forgotten");
+  if (NULL == rx)
+  {
+    rx = json_object ();
+    if (0 !=
+        json_object_set_new (json,
+                             "$forgotten",
+                             rx))
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+  }
+  if (NULL !=
+      json_object_get (rx,
+                       field))
+  {
+    if (! json_is_null (json_object_get (json,
+                                         field)))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Field `%s' market as forgotten, but still exists!\n",
+                  field);
+      return GNUNET_SYSERR;
+    }
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Already forgot field `%s'\n",
+                field);
+    return GNUNET_NO;
+  }
+  salt = json_string_value (json_object_get (fg,
+                                             field));
+  if (NULL == salt)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Did not find required salt to forget field `%s'\n",
+                field);
+    return GNUNET_SYSERR;
+  }
+
+  /* need to recursively forget to compute 'hc' */
+  ret = forget (part,
+                &fp);
+  if (GNUNET_OK != ret)
+    return ret;
+  if (GNUNET_OK !=
+      dump_and_hash (fp,
+                     salt,
+                     &hc))
+  {
+    json_decref (fp);
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  json_decref (fp);
+  /* drop salt */
+  if (0 !=
+      json_object_del (fg,
+                       field))
+  {
+    json_decref (fp);
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* remember field as 'forgotten' */
+  if (0 !=
+      json_object_set_new (rx,
+                           field,
+                           GNUNET_JSON_from_data_auto (&hc)))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  /* finally, set 'forgotten' field to null */
+  if (0 !=
+      json_object_del (json,
+                       field))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse a json path.
+ *
+ * @param obj the object that the path is relative to.
+ * @param prev the parent of @e obj.
+ * @param path the path to parse.
+ * @param cb the callback to call, if we get to the end of @e path.
+ * @param cb_cls the closure for the callback.
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR if @e path is malformed.
+ */
+static enum GNUNET_GenericReturnValue
+parse_path (json_t *obj,
+            json_t *prev,
+            const char *path,
+            TALER_JSON_ExpandPathCallback cb,
+            void *cb_cls)
+{
+  char *id = GNUNET_strdup (path);
+  char *next_id = strchr (id,
+                          '.');
+  char *next_path;
+  char *bracket;
+  json_t *next_obj = NULL;
+  char *next_dot;
+
+  GNUNET_assert (NULL != id); /* make stupid compiler happy */
+  if (NULL == next_id)
+  {
+    cb (cb_cls,
+        id,
+        prev);
+    GNUNET_free (id);
+    return GNUNET_OK;
+  }
+  bracket = strchr (next_id,
+                    '[');
+  *next_id = '\0';
+  next_id++;
+  next_path = GNUNET_strdup (next_id);
+  next_dot = strchr (next_id,
+                     '.');
+  if (NULL != next_dot)
+    *next_dot = '\0';
+  /* If this is the first time this is called, make sure id is "$" */
+  if ( (NULL == prev) &&
+       (0 != strcmp (id,
+                     "$")))
+  {
+    GNUNET_free (id);
+    GNUNET_free (next_path);
+    return GNUNET_SYSERR;
+  }
+
+  /* Check for bracketed indices */
+  if (NULL != bracket)
+  {
+    char *end_bracket = strchr (bracket,
+                                ']');
+    if (NULL == end_bracket)
+    {
+      GNUNET_free (id);
+      GNUNET_free (next_path);
+      return GNUNET_SYSERR;
+    }
+    *end_bracket = '\0';
+
+    *bracket = '\0';
+    bracket++;
+
+    json_t *array = json_object_get (obj,
+                                     next_id);
+    if (0 == strcmp (bracket,
+                     "*"))
+    {
+      size_t index;
+      json_t *value;
+      int ret = GNUNET_OK;
+
+      json_array_foreach (array, index, value) {
+        ret = parse_path (value,
+                          obj,
+                          next_path,
+                          cb,
+                          cb_cls);
+        if (GNUNET_OK != ret)
+        {
+          GNUNET_free (id);
+          GNUNET_free (next_path);
+          return ret;
+        }
+      }
+    }
+    else
+    {
+      unsigned int index;
+      char dummy;
+
+      if (1 != sscanf (bracket,
+                       "%u%c",
+                       &index,
+                       &dummy))
+      {
+        GNUNET_free (id);
+        GNUNET_free (next_path);
+        return GNUNET_SYSERR;
+      }
+      next_obj = json_array_get (array,
+                                 index);
+    }
+  }
+  else
+  {
+    /* No brackets, so just fetch the object by name */
+    next_obj = json_object_get (obj,
+                                next_id);
+  }
+
+  if (NULL != next_obj)
+  {
+    int ret = parse_path (next_obj,
+                          obj,
+                          next_path,
+                          cb,
+                          cb_cls);
+    GNUNET_free (id);
+    GNUNET_free (next_path);
+    return ret;
+  }
+  GNUNET_free (id);
+  GNUNET_free (next_path);
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_expand_path (json_t *json,
+                        const char *path,
+                        TALER_JSON_ExpandPathCallback cb,
+                        void *cb_cls)
+{
+  return parse_path (json,
+                     NULL,
+                     path,
+                     cb,
+                     cb_cls);
+}
+
+
+enum TALER_ErrorCode
+TALER_JSON_get_error_code (const json_t *json)
+{
+  const json_t *jc;
+
+  if (NULL == json)
+    return TALER_EC_GENERIC_INVALID_RESPONSE;
+  jc = json_object_get (json, "code");
+  /* The caller already knows that the JSON represents an error,
+     so we are dealing with a missing error code here.  */
+  if (NULL == jc)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Expected Taler error code `code' in JSON, but field does not 
exist!\n");
+    return TALER_EC_INVALID;
+  }
+  if (json_is_integer (jc))
+    return (enum TALER_ErrorCode) json_integer_value (jc);
+  GNUNET_break_op (0);
+  return TALER_EC_INVALID;
+}
+
+
+const char *
+TALER_JSON_get_error_hint (const json_t *json)
+{
+  const json_t *jc;
+
+  if (NULL == json)
+    return NULL;
+  jc = json_object_get (json,
+                        "hint");
+  if (NULL == jc)
+    return NULL; /* no hint, is allowed */
+  if (! json_is_string (jc))
+  {
+    /* Hints must be strings */
+    GNUNET_break_op (0);
+    return NULL;
+  }
+  return json_string_value (jc);
+}
+
+
+enum TALER_ErrorCode
+TALER_JSON_get_error_code2 (const void *data,
+                            size_t data_size)
+{
+  json_t *json;
+  enum TALER_ErrorCode ec;
+  json_error_t err;
+
+  json = json_loadb (data,
+                     data_size,
+                     JSON_REJECT_DUPLICATES,
+                     &err);
+  if (NULL == json)
+    return TALER_EC_INVALID;
+  ec = TALER_JSON_get_error_code (json);
+  json_decref (json);
+  if (ec == TALER_EC_NONE)
+    return TALER_EC_INVALID;
+  return ec;
+}
+
+
+void
+TALER_deposit_policy_hash (const json_t *policy,
+                           struct TALER_ExtensionPolicyHashP *ech)
+{
+  GNUNET_assert (GNUNET_OK ==
+                 dump_and_hash (policy,
+                                "taler-extensions-policy",
+                                &ech->hash));
+}
+
+
+char *
+TALER_JSON_canonicalize (const json_t *input)
+{
+  char *wire_enc;
+
+  if (NULL == (wire_enc = json_dumps (input,
+                                      JSON_ENCODE_ANY
+                                      | JSON_COMPACT
+                                      | JSON_SORT_KEYS)))
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  TALER_rfc8785encode (&wire_enc);
+  return wire_enc;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_extensions_manifests_hash (const json_t *manifests,
+                                      struct TALER_ExtensionManifestsHashP 
*ech)
+{
+  return dump_and_hash (manifests,
+                        "taler-extensions-manifests",
+                        &ech->hash);
+}
+
+
 /* End of json/json.c */
diff --git a/src/json/json_helper.c b/src/json/json_helper.c
new file mode 100644
index 0000000..0126574
--- /dev/null
+++ b/src/json/json_helper.c
@@ -0,0 +1,1621 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2024 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file json/json_helper.c
+ * @brief helper functions to generate specifications to parse
+ *        Taler-specific JSON objects with libgnunetjson
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler/taler_util.h"
+#include "taler/taler_json_lib.h"
+
+
+/**
+ * Convert string value to numeric cipher value.
+ *
+ * @param cipher_s input string
+ * @return numeric cipher value
+ */
+static enum GNUNET_CRYPTO_BlindSignatureAlgorithm
+string_to_cipher (const char *cipher_s)
+{
+  if ((0 == strcasecmp (cipher_s,
+                        "RSA")) ||
+      (0 == strcasecmp (cipher_s,
+                        "RSA+age_restricted")))
+    return GNUNET_CRYPTO_BSA_RSA;
+  if ((0 == strcasecmp (cipher_s,
+                        "CS")) ||
+      (0 == strcasecmp (cipher_s,
+                        "CS+age_restricted")))
+    return GNUNET_CRYPTO_BSA_CS;
+  return GNUNET_CRYPTO_BSA_INVALID;
+}
+
+
+json_t *
+TALER_JSON_from_amount (const struct TALER_Amount *amount)
+{
+  char *amount_str = TALER_amount_to_string (amount);
+
+  GNUNET_assert (NULL != amount_str);
+  {
+    json_t *j = json_string (amount_str);
+
+    GNUNET_free (amount_str);
+    return j;
+  }
+}
+
+
+/**
+ * Parse given JSON object to Amount
+ *
+ * @param cls closure, expected currency, or NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_amount (void *cls,
+              json_t *root,
+              struct GNUNET_JSON_Specification *spec)
+{
+  const char *currency = cls;
+  struct TALER_Amount *r_amount = spec->ptr;
+
+  (void) cls;
+  if (! json_is_string (root))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_string_to_amount (json_string_value (root),
+                              r_amount))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if ( (NULL != currency) &&
+       (0 !=
+        strcasecmp (currency,
+                    r_amount->currency)) )
+  {
+    GNUNET_break_op (0);
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Expected currency `%s', but amount used currency `%s' in 
field `%s'\n",
+                currency,
+                r_amount->currency,
+                spec->field);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_amount (const char *name,
+                        const char *currency,
+                        struct TALER_Amount *r_amount)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_amount,
+    .cleaner = NULL,
+    .cls = (void *) currency,
+    .field = name,
+    .ptr = r_amount,
+    .ptr_size = 0,
+    .size_ptr = NULL
+  };
+
+  GNUNET_assert (NULL != currency);
+  return ret;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_amount_any (const char *name,
+                            struct TALER_Amount *r_amount)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_amount,
+    .cleaner = NULL,
+    .cls = NULL,
+    .field = name,
+    .ptr = r_amount,
+    .ptr_size = 0,
+    .size_ptr = NULL
+  };
+
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to currency spec.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_cspec (void *cls,
+             json_t *root,
+             struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_CurrencySpecification *r_cspec = spec->ptr;
+  const char *currency = spec->cls;
+  const char *name;
+  uint32_t fid;
+  uint32_t fnd;
+  uint32_t ftzd;
+  const json_t *map;
+  struct GNUNET_JSON_Specification gspec[] = {
+    GNUNET_JSON_spec_string ("name",
+                             &name),
+    GNUNET_JSON_spec_uint32 ("num_fractional_input_digits",
+                             &fid),
+    GNUNET_JSON_spec_uint32 ("num_fractional_normal_digits",
+                             &fnd),
+    GNUNET_JSON_spec_uint32 ("num_fractional_trailing_zero_digits",
+                             &ftzd),
+    GNUNET_JSON_spec_object_const ("alt_unit_names",
+                                   &map),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *emsg;
+  unsigned int eline;
+
+  memset (r_cspec->currency,
+          0,
+          sizeof (r_cspec->currency));
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (root,
+                         gspec,
+                         &emsg,
+                         &eline))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Failed to parse %s at %u: %s\n",
+                spec[eline].field,
+                eline,
+                emsg);
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (strlen (currency) >= TALER_CURRENCY_LEN)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if ( (fid > TALER_AMOUNT_FRAC_LEN) ||
+       (fnd > TALER_AMOUNT_FRAC_LEN) ||
+       (ftzd > TALER_AMOUNT_FRAC_LEN) )
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_check_currency (currency))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  strcpy (r_cspec->currency,
+          currency);
+  if (GNUNET_OK !=
+      TALER_check_currency_scale_map (map))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  r_cspec->name = GNUNET_strdup (name);
+  r_cspec->map_alt_unit_names = json_incref ((json_t *) map);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Cleanup data left from parsing encrypted contract.
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_cspec (void *cls,
+             struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_CurrencySpecification *cspec = spec->ptr;
+
+  (void) cls;
+  GNUNET_free (cspec->name);
+  json_decref (cspec->map_alt_unit_names);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_currency_specification (
+  const char *name,
+  const char *currency,
+  struct TALER_CurrencySpecification *r_cspec)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_cspec,
+    .cleaner = &clean_cspec,
+    .cls = (void *) currency,
+    .field = name,
+    .ptr = r_cspec,
+    .ptr_size = sizeof (*r_cspec),
+    .size_ptr = NULL
+  };
+
+  memset (r_cspec,
+          0,
+          sizeof (*r_cspec));
+  return ret;
+}
+
+
+static enum GNUNET_GenericReturnValue
+parse_denomination_group (void *cls,
+                          json_t *root,
+                          struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_DenominationGroup *group = spec->ptr;
+  const char *cipher;
+  const char *currency = cls;
+  bool age_mask_missing = false;
+  bool has_age_restricted_suffix = false;
+  struct GNUNET_JSON_Specification gspec[] = {
+    GNUNET_JSON_spec_string ("cipher",
+                             &cipher),
+    TALER_JSON_spec_amount ("value",
+                            currency,
+                            &group->value),
+    TALER_JSON_SPEC_DENOM_FEES ("fee",
+                                currency,
+                                &group->fees),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_uint32 ("age_mask",
+                               &group->age_mask.bits),
+      &age_mask_missing),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *emsg;
+  unsigned int eline;
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (root,
+                         gspec,
+                         &emsg,
+                         &eline))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Failed to parse %s at %u: %s\n",
+                spec[eline].field,
+                eline,
+                emsg);
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  group->cipher = string_to_cipher (cipher);
+  if (GNUNET_CRYPTO_BSA_INVALID == group->cipher)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* age_mask and suffix must be consistent */
+  has_age_restricted_suffix =
+    (NULL != strstr (cipher, "+age_restricted"));
+  if (has_age_restricted_suffix && age_mask_missing)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  if (age_mask_missing)
+    group->age_mask.bits = 0;
+
+  return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_denomination_group (const char *name,
+                                    const char *currency,
+                                    struct TALER_DenominationGroup *group)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .cls = (void *) currency,
+    .parser = &parse_denomination_group,
+    .field = name,
+    .ptr = group,
+    .ptr_size = sizeof(*group)
+  };
+
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to an encrypted contract.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_econtract (void *cls,
+                 json_t *root,
+                 struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_EncryptedContract *econtract = spec->ptr;
+  struct GNUNET_JSON_Specification ispec[] = {
+    GNUNET_JSON_spec_varsize ("econtract",
+                              &econtract->econtract,
+                              &econtract->econtract_size),
+    GNUNET_JSON_spec_fixed_auto ("econtract_sig",
+                                 &econtract->econtract_sig),
+    GNUNET_JSON_spec_fixed_auto ("contract_pub",
+                                 &econtract->contract_pub),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *emsg;
+  unsigned int eline;
+
+  (void) cls;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (root,
+                         ispec,
+                         &emsg,
+                         &eline))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Cleanup data left from parsing encrypted contract.
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_econtract (void *cls,
+                 struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_EncryptedContract *econtract = spec->ptr;
+
+  (void) cls;
+  GNUNET_free (econtract->econtract);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_econtract (const char *name,
+                           struct TALER_EncryptedContract *econtract)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_econtract,
+    .cleaner = &clean_econtract,
+    .field = name,
+    .ptr = econtract
+  };
+
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to an age commitmnet
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_age_commitment (void *cls,
+                      json_t *root,
+                      struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_AgeCommitment *age_commitment = spec->ptr;
+  json_t *pk;
+  unsigned int idx;
+  size_t num;
+
+  (void) cls;
+  if ( (NULL == root) ||
+       (! json_is_array (root)))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  num = json_array_size (root);
+  if (32 <= num || 0 == num)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  age_commitment->num = num;
+  age_commitment->keys =
+    GNUNET_new_array (num,
+                      struct TALER_AgeCommitmentPublicKeyP);
+
+  json_array_foreach (root, idx, pk) {
+    const char *emsg;
+    unsigned int eline;
+    struct GNUNET_JSON_Specification pkspec[] = {
+      GNUNET_JSON_spec_fixed_auto (
+        NULL,
+        &age_commitment->keys[idx].pub),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (pk,
+                           pkspec,
+                           &emsg,
+                           &eline))
+    {
+      GNUNET_break_op (0);
+      GNUNET_JSON_parse_free (spec);
+      return GNUNET_SYSERR;
+    }
+  };
+
+  return GNUNET_OK;
+}
+
+
+/**
+ * Cleanup data left from parsing age commitment
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_age_commitment (void *cls,
+                      struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_AgeCommitment *age_commitment = spec->ptr;
+
+  (void) cls;
+
+  if (NULL == age_commitment ||
+      NULL == age_commitment->keys)
+    return;
+
+  age_commitment->num = 0;
+  GNUNET_free (age_commitment->keys);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_age_commitment (const char *name,
+                                struct TALER_AgeCommitment *age_commitment)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_age_commitment,
+    .cleaner = &clean_age_commitment,
+    .field = name,
+    .ptr = age_commitment
+  };
+
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to denomination public key.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_denom_pub (void *cls,
+                 json_t *root,
+                 struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_DenominationPublicKey *denom_pub = spec->ptr;
+  struct GNUNET_CRYPTO_BlindSignPublicKey *bsign_pub;
+  const char *cipher;
+  bool age_mask_missing = false;
+  struct GNUNET_JSON_Specification dspec[] = {
+    GNUNET_JSON_spec_string ("cipher",
+                             &cipher),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_uint32 ("age_mask",
+                               &denom_pub->age_mask.bits),
+      &age_mask_missing),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *emsg;
+  unsigned int eline;
+
+  (void) cls;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (root,
+                         dspec,
+                         &emsg,
+                         &eline))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  if (age_mask_missing)
+    denom_pub->age_mask.bits = 0;
+  bsign_pub = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey);
+  bsign_pub->rc = 1;
+  bsign_pub->cipher = string_to_cipher (cipher);
+  switch (bsign_pub->cipher)
+  {
+  case GNUNET_CRYPTO_BSA_INVALID:
+    break;
+  case GNUNET_CRYPTO_BSA_RSA:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_rsa_public_key (
+          "rsa_public_key",
+          &bsign_pub->details.rsa_public_key),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        GNUNET_free (bsign_pub);
+        return GNUNET_SYSERR;
+      }
+      denom_pub->bsign_pub_key = bsign_pub;
+      return GNUNET_OK;
+    }
+  case GNUNET_CRYPTO_BSA_CS:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_fixed ("cs_public_key",
+                                &bsign_pub->details.cs_public_key,
+                                sizeof (bsign_pub->details.cs_public_key)),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        GNUNET_free (bsign_pub);
+        return GNUNET_SYSERR;
+      }
+      denom_pub->bsign_pub_key = bsign_pub;
+      return GNUNET_OK;
+    }
+  }
+  GNUNET_break_op (0);
+  GNUNET_free (bsign_pub);
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Cleanup data left from parsing denomination public key.
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_denom_pub (void *cls,
+                 struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_DenominationPublicKey *denom_pub = spec->ptr;
+
+  (void) cls;
+  TALER_denom_pub_free (denom_pub);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_denom_pub (const char *field,
+                           struct TALER_DenominationPublicKey *pk)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_denom_pub,
+    .cleaner = &clean_denom_pub,
+    .field = field,
+    .ptr = pk
+  };
+
+  pk->bsign_pub_key = NULL;
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object partially into a denomination public key.
+ *
+ * Depending on the cipher in cls, it parses the corresponding public key type.
+ *
+ * @param cls closure, enum GNUNET_CRYPTO_BlindSignatureAlgorithm
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_denom_pub_cipher (void *cls,
+                        json_t *root,
+                        struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_DenominationPublicKey *denom_pub = spec->ptr;
+  enum GNUNET_CRYPTO_BlindSignatureAlgorithm cipher =
+    (enum GNUNET_CRYPTO_BlindSignatureAlgorithm) (long) cls;
+  struct GNUNET_CRYPTO_BlindSignPublicKey *bsign_pub;
+  const char *emsg;
+  unsigned int eline;
+
+  bsign_pub = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey);
+  bsign_pub->cipher = cipher;
+  bsign_pub->rc = 1;
+  switch (cipher)
+  {
+  case GNUNET_CRYPTO_BSA_INVALID:
+    break;
+  case GNUNET_CRYPTO_BSA_RSA:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_rsa_public_key (
+          "rsa_pub",
+          &bsign_pub->details.rsa_public_key),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        GNUNET_free (bsign_pub);
+        return GNUNET_SYSERR;
+      }
+      denom_pub->bsign_pub_key = bsign_pub;
+      return GNUNET_OK;
+    }
+  case GNUNET_CRYPTO_BSA_CS:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_fixed ("cs_pub",
+                                &bsign_pub->details.cs_public_key,
+                                sizeof (bsign_pub->details.cs_public_key)),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        GNUNET_free (bsign_pub);
+        return GNUNET_SYSERR;
+      }
+      denom_pub->bsign_pub_key = bsign_pub;
+      return GNUNET_OK;
+    }
+  }
+  GNUNET_break_op (0);
+  GNUNET_free (bsign_pub);
+  return GNUNET_SYSERR;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_denom_pub_cipher (const char *field,
+                                  enum GNUNET_CRYPTO_BlindSignatureAlgorithm
+                                  cipher,
+                                  struct TALER_DenominationPublicKey *pk)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_denom_pub_cipher,
+    .cleaner = &clean_denom_pub,
+    .field = field,
+    .cls = (void *) cipher,
+    .ptr = pk
+  };
+
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to denomination signature.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_denom_sig (void *cls,
+                 json_t *root,
+                 struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_DenominationSignature *denom_sig = spec->ptr;
+  struct GNUNET_CRYPTO_UnblindedSignature *unblinded_sig;
+  const char *cipher;
+  struct GNUNET_JSON_Specification dspec[] = {
+    GNUNET_JSON_spec_string ("cipher",
+                             &cipher),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *emsg;
+  unsigned int eline;
+
+  (void) cls;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (root,
+                         dspec,
+                         &emsg,
+                         &eline))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  unblinded_sig = GNUNET_new (struct GNUNET_CRYPTO_UnblindedSignature);
+  unblinded_sig->cipher = string_to_cipher (cipher);
+  unblinded_sig->rc = 1;
+  switch (unblinded_sig->cipher)
+  {
+  case GNUNET_CRYPTO_BSA_INVALID:
+    break;
+  case GNUNET_CRYPTO_BSA_RSA:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_rsa_signature (
+          "rsa_signature",
+          &unblinded_sig->details.rsa_signature),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        GNUNET_free (unblinded_sig);
+        return GNUNET_SYSERR;
+      }
+      denom_sig->unblinded_sig = unblinded_sig;
+      return GNUNET_OK;
+    }
+  case GNUNET_CRYPTO_BSA_CS:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_fixed_auto ("cs_signature_r",
+                                     &unblinded_sig->details.cs_signature.
+                                     r_point),
+        GNUNET_JSON_spec_fixed_auto ("cs_signature_s",
+                                     &unblinded_sig->details.cs_signature.
+                                     s_scalar),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        GNUNET_free (unblinded_sig);
+        return GNUNET_SYSERR;
+      }
+      denom_sig->unblinded_sig = unblinded_sig;
+      return GNUNET_OK;
+    }
+  }
+  GNUNET_break_op (0);
+  GNUNET_free (unblinded_sig);
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Cleanup data left from parsing denomination public key.
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_denom_sig (void *cls,
+                 struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_DenominationSignature *denom_sig = spec->ptr;
+
+  (void) cls;
+  TALER_denom_sig_free (denom_sig);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_denom_sig (const char *field,
+                           struct TALER_DenominationSignature *sig)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_denom_sig,
+    .cleaner = &clean_denom_sig,
+    .field = field,
+    .ptr = sig
+  };
+
+  sig->unblinded_sig = NULL;
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to blinded denomination signature.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_blinded_denom_sig (void *cls,
+                         json_t *root,
+                         struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_BlindedDenominationSignature *denom_sig = spec->ptr;
+  struct GNUNET_CRYPTO_BlindedSignature *blinded_sig;
+  const char *cipher;
+  struct GNUNET_JSON_Specification dspec[] = {
+    GNUNET_JSON_spec_string ("cipher",
+                             &cipher),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *emsg;
+  unsigned int eline;
+
+  (void) cls;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (root,
+                         dspec,
+                         &emsg,
+                         &eline))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  blinded_sig = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature);
+  blinded_sig->cipher = string_to_cipher (cipher);
+  blinded_sig->rc = 1;
+  switch (blinded_sig->cipher)
+  {
+  case GNUNET_CRYPTO_BSA_INVALID:
+    break;
+  case GNUNET_CRYPTO_BSA_RSA:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_rsa_signature (
+          "blinded_rsa_signature",
+          &blinded_sig->details.blinded_rsa_signature),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        GNUNET_free (blinded_sig);
+        return GNUNET_SYSERR;
+      }
+      denom_sig->blinded_sig = blinded_sig;
+      return GNUNET_OK;
+    }
+  case GNUNET_CRYPTO_BSA_CS:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_uint32 ("b",
+                                 &blinded_sig->details.blinded_cs_answer.b),
+        GNUNET_JSON_spec_fixed_auto ("s",
+                                     &blinded_sig->details.blinded_cs_answer.
+                                     s_scalar),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        GNUNET_free (blinded_sig);
+        return GNUNET_SYSERR;
+      }
+      denom_sig->blinded_sig = blinded_sig;
+      return GNUNET_OK;
+    }
+  }
+  GNUNET_break_op (0);
+  GNUNET_free (blinded_sig);
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Cleanup data left from parsing denomination public key.
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_blinded_denom_sig (void *cls,
+                         struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_BlindedDenominationSignature *denom_sig = spec->ptr;
+
+  (void) cls;
+  TALER_blinded_denom_sig_free (denom_sig);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_blinded_denom_sig (
+  const char *field,
+  struct TALER_BlindedDenominationSignature *sig)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_blinded_denom_sig,
+    .cleaner = &clean_blinded_denom_sig,
+    .field = field,
+    .ptr = sig
+  };
+
+  sig->blinded_sig = NULL;
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to blinded planchet.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_blinded_planchet (void *cls,
+                        json_t *root,
+                        struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_BlindedPlanchet *blinded_planchet = spec->ptr;
+  struct GNUNET_CRYPTO_BlindedMessage *blinded_message;
+  const char *cipher;
+  struct GNUNET_JSON_Specification dspec[] = {
+    GNUNET_JSON_spec_string ("cipher",
+                             &cipher),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *emsg;
+  unsigned int eline;
+
+  (void) cls;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (root,
+                         dspec,
+                         &emsg,
+                         &eline))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  blinded_message = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage);
+  blinded_message->rc = 1;
+  blinded_message->cipher = string_to_cipher (cipher);
+  switch (blinded_message->cipher)
+  {
+  case GNUNET_CRYPTO_BSA_INVALID:
+    break;
+  case GNUNET_CRYPTO_BSA_RSA:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_varsize (
+          "rsa_blinded_planchet",
+          &blinded_message->details.rsa_blinded_message.blinded_msg,
+          &blinded_message->details.rsa_blinded_message.blinded_msg_size),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        GNUNET_free (blinded_message);
+        return GNUNET_SYSERR;
+      }
+      blinded_planchet->blinded_message = blinded_message;
+      return GNUNET_OK;
+    }
+  case GNUNET_CRYPTO_BSA_CS:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_fixed_auto (
+          "cs_nonce",
+          &blinded_message->details.cs_blinded_message.nonce),
+        GNUNET_JSON_spec_fixed_auto (
+          "cs_blinded_c0",
+          &blinded_message->details.cs_blinded_message.c[0]),
+        GNUNET_JSON_spec_fixed_auto (
+          "cs_blinded_c1",
+          &blinded_message->details.cs_blinded_message.c[1]),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        GNUNET_free (blinded_message);
+        return GNUNET_SYSERR;
+      }
+      blinded_planchet->blinded_message = blinded_message;
+      return GNUNET_OK;
+    }
+  }
+  GNUNET_break_op (0);
+  GNUNET_free (blinded_message);
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Cleanup data left from parsing blinded planchet.
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_blinded_planchet (void *cls,
+                        struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_BlindedPlanchet *blinded_planchet = spec->ptr;
+
+  (void) cls;
+  TALER_blinded_planchet_free (blinded_planchet);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_blinded_planchet (const char *field,
+                                  struct TALER_BlindedPlanchet 
*blinded_planchet)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_blinded_planchet,
+    .cleaner = &clean_blinded_planchet,
+    .field = field,
+    .ptr = blinded_planchet
+  };
+
+  blinded_planchet->blinded_message = NULL;
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to exchange withdraw values (/csr).
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_exchange_withdraw_values (void *cls,
+                                json_t *root,
+                                struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_ExchangeWithdrawValues *ewv = spec->ptr;
+  struct GNUNET_CRYPTO_BlindingInputValues *bi;
+  const char *cipher;
+  struct GNUNET_JSON_Specification dspec[] = {
+    GNUNET_JSON_spec_string ("cipher",
+                             &cipher),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *emsg;
+  unsigned int eline;
+  enum GNUNET_CRYPTO_BlindSignatureAlgorithm ci;
+
+  (void) cls;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (root,
+                         dspec,
+                         &emsg,
+                         &eline))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  ci = string_to_cipher (cipher);
+  switch (ci)
+  {
+  case GNUNET_CRYPTO_BSA_INVALID:
+    break;
+  case GNUNET_CRYPTO_BSA_RSA:
+    ewv->blinding_inputs = TALER_denom_ewv_rsa_singleton ()->blinding_inputs;
+    return GNUNET_OK;
+  case GNUNET_CRYPTO_BSA_CS:
+    bi = GNUNET_new (struct GNUNET_CRYPTO_BlindingInputValues);
+    bi->cipher = GNUNET_CRYPTO_BSA_CS;
+    bi->rc = 1;
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_fixed (
+          "r_pub_0",
+          &bi->details.cs_values.r_pub[0],
+          sizeof (struct GNUNET_CRYPTO_CsRPublic)),
+        GNUNET_JSON_spec_fixed (
+          "r_pub_1",
+          &bi->details.cs_values.r_pub[1],
+          sizeof (struct GNUNET_CRYPTO_CsRPublic)),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        GNUNET_free (bi);
+        return GNUNET_SYSERR;
+      }
+      ewv->blinding_inputs = bi;
+      return GNUNET_OK;
+    }
+  }
+  GNUNET_break_op (0);
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Cleanup data left from parsing withdraw values
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_exchange_withdraw_values (
+  void *cls,
+  struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_ExchangeWithdrawValues *ewv = spec->ptr;
+
+  (void) cls;
+  TALER_denom_ewv_free (ewv);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_exchange_withdraw_values (
+  const char *field,
+  struct TALER_ExchangeWithdrawValues *ewv)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_exchange_withdraw_values,
+    .cleaner = &clean_exchange_withdraw_values,
+    .field = field,
+    .ptr = ewv
+  };
+
+  ewv->blinding_inputs = NULL;
+  return ret;
+}
+
+
+/**
+ * Closure for #parse_i18n_string.
+ */
+struct I18nContext
+{
+  /**
+   * Language pattern to match.
+   */
+  char *lp;
+
+  /**
+   * Name of the field to match.
+   */
+  const char *field;
+};
+
+
+/**
+ * Parse given JSON object to internationalized string.
+ *
+ * @param cls closure, our `struct I18nContext *`
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_i18n_string (void *cls,
+                   json_t *root,
+                   struct GNUNET_JSON_Specification *spec)
+{
+  struct I18nContext *ctx = cls;
+  json_t *i18n;
+  json_t *val;
+
+  {
+    char *i18nf;
+
+    GNUNET_asprintf (&i18nf,
+                     "%s_i18n",
+                     ctx->field);
+    i18n = json_object_get (root,
+                            i18nf);
+    GNUNET_free (i18nf);
+  }
+
+  val = json_object_get (root,
+                         ctx->field);
+  if ( (NULL != i18n) &&
+       (NULL != ctx->lp) )
+  {
+    double best = 0.0;
+    json_t *pos;
+    const char *lang;
+
+    json_object_foreach (i18n, lang, pos)
+    {
+      double score;
+
+      score = TALER_language_matches (ctx->lp,
+                                      lang);
+      if (score > best)
+      {
+        best = score;
+        val = pos;
+      }
+    }
+  }
+
+  {
+    const char *str;
+
+    str = json_string_value (val);
+    *(const char **) spec->ptr = str;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called to clean up data from earlier parsing.
+ *
+ * @param cls closure
+ * @param spec our specification entry with data to clean.
+ */
+static void
+i18n_cleaner (void *cls,
+              struct GNUNET_JSON_Specification *spec)
+{
+  struct I18nContext *ctx = cls;
+
+  (void) spec;
+  if (NULL != ctx)
+  {
+    GNUNET_free (ctx->lp);
+    GNUNET_free (ctx);
+  }
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_i18n_string (const char *name,
+                             const char *language_pattern,
+                             const char **strptr)
+{
+  struct I18nContext *ctx = GNUNET_new (struct I18nContext);
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_i18n_string,
+    .cleaner = &i18n_cleaner,
+    .cls = ctx,
+    .field = NULL, /* we want the main object */
+    .ptr = strptr,
+    .ptr_size = 0,
+    .size_ptr = NULL
+  };
+
+  ctx->lp = (NULL != language_pattern)
+    ? GNUNET_strdup (language_pattern)
+    : NULL;
+  ctx->field = name;
+  *strptr = NULL;
+  return ret;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_i18n_str (const char *name,
+                          const char **strptr)
+{
+  const char *lang = getenv ("LANG");
+  char *dot;
+  char *l;
+  struct GNUNET_JSON_Specification ret;
+
+  if (NULL != lang)
+  {
+    dot = strchr (lang,
+                  '.');
+    if (NULL == dot)
+      l = GNUNET_strdup (lang);
+    else
+      l = GNUNET_strndup (lang,
+                          dot - lang);
+  }
+  else
+  {
+    l = NULL;
+  }
+  ret = TALER_JSON_spec_i18n_string (name,
+                                     l,
+                                     strptr);
+  GNUNET_free (l);
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object with Taler error code.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_ec (void *cls,
+          json_t *root,
+          struct GNUNET_JSON_Specification *spec)
+{
+  enum TALER_ErrorCode *ec = spec->ptr;
+  json_int_t num;
+
+  (void) cls;
+  if (! json_is_integer (root))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  num = json_integer_value (root);
+  if (num < 0)
+  {
+    GNUNET_break_op (0);
+    *ec = TALER_EC_INVALID;
+    return GNUNET_SYSERR;
+  }
+  *ec = (enum TALER_ErrorCode) num;
+  return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_ec (const char *field,
+                    enum TALER_ErrorCode *ec)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_ec,
+    .field = field,
+    .ptr = ec
+  };
+
+  *ec = TALER_EC_NONE;
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to web URL.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_web_url (void *cls,
+               json_t *root,
+               struct GNUNET_JSON_Specification *spec)
+{
+  const char *str;
+
+  (void) cls;
+  str = json_string_value (root);
+  if (NULL == str)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (! TALER_is_web_url (str))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  *(const char **) spec->ptr = str;
+  return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_web_url (const char *field,
+                         const char **url)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_web_url,
+    .field = field,
+    .ptr = url
+  };
+
+  *url = NULL;
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to payto:// URI.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_payto_uri (void *cls,
+                 json_t *root,
+                 struct GNUNET_JSON_Specification *spec)
+{
+  const char *str;
+  char *err;
+
+  (void) cls;
+  str = json_string_value (root);
+  if (NULL == str)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  err = TALER_payto_validate (str);
+  if (NULL != err)
+  {
+    GNUNET_break_op (0);
+    GNUNET_free (err);
+    return GNUNET_SYSERR;
+  }
+  *(const char **) spec->ptr = str;
+  return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_payto_uri (const char *field,
+                           const char **payto_uri)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_payto_uri,
+    .field = field,
+    .ptr = payto_uri
+  };
+
+  *payto_uri = NULL;
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object with protocol version.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_protocol_version (void *cls,
+                        json_t *root,
+                        struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_JSON_ProtocolVersion *pv = spec->ptr;
+  const char *ver;
+  char dummy;
+
+  (void) cls;
+  if (! json_is_string (root))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  ver = json_string_value (root);
+  if (3 != sscanf (ver,
+                   "%u:%u:%u%c",
+                   &pv->current,
+                   &pv->revision,
+                   &pv->age,
+                   &dummy))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_version (const char *field,
+                         struct TALER_JSON_ProtocolVersion *ver)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_protocol_version,
+    .field = field,
+    .ptr = ver
+  };
+
+  return ret;
+}
+
+
+/* end of json/json_helper.c */
diff --git a/src/json/json_pack.c b/src/json/json_pack.c
new file mode 100644
index 0000000..4357f9f
--- /dev/null
+++ b/src/json/json_pack.c
@@ -0,0 +1,324 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2024 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file json/json_pack.c
+ * @brief helper functions for JSON object packing
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler/taler_util.h"
+#include "taler/taler_json_lib.h"
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_time_abs_human (const char *name,
+                                struct GNUNET_TIME_Absolute at)
+{
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+    .object = json_string (
+      GNUNET_STRINGS_absolute_time_to_string (at))
+  };
+
+  return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_econtract (
+  const char *name,
+  const struct TALER_EncryptedContract *econtract)
+{
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+  };
+
+  if (NULL == econtract)
+    return ps;
+  ps.object
+    = GNUNET_JSON_PACK (
+        GNUNET_JSON_pack_data_varsize ("econtract",
+                                       econtract->econtract,
+                                       econtract->econtract_size),
+        GNUNET_JSON_pack_data_auto ("econtract_sig",
+                                    &econtract->econtract_sig),
+        GNUNET_JSON_pack_data_auto ("contract_pub",
+                                    &econtract->contract_pub));
+  return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_age_commitment (
+  const char *name,
+  const struct TALER_AgeCommitment *age_commitment)
+{
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+  };
+  json_t *keys;
+
+  if (NULL == age_commitment ||
+      0 == age_commitment->num)
+    return ps;
+
+  GNUNET_assert (NULL !=
+                 (keys = json_array ()));
+
+  for (size_t i = 0;
+       i < age_commitment->num;
+       i++)
+  {
+    json_t *val;
+    val = GNUNET_JSON_from_data (&age_commitment->keys[i],
+                                 sizeof(age_commitment->keys[i]));
+    GNUNET_assert (NULL != val);
+    GNUNET_assert (0 ==
+                   json_array_append_new (keys, val));
+  }
+
+  ps.object = keys;
+  return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_denom_pub (
+  const char *name,
+  const struct TALER_DenominationPublicKey *pk)
+{
+  const struct GNUNET_CRYPTO_BlindSignPublicKey *bsp;
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+  };
+
+  if (NULL == pk)
+    return ps;
+  bsp = pk->bsign_pub_key;
+  switch (bsp->cipher)
+  {
+  case GNUNET_CRYPTO_BSA_INVALID:
+    break;
+  case GNUNET_CRYPTO_BSA_RSA:
+    ps.object
+      = GNUNET_JSON_PACK (
+          GNUNET_JSON_pack_string ("cipher",
+                                   "RSA"),
+          GNUNET_JSON_pack_uint64 ("age_mask",
+                                   pk->age_mask.bits),
+          GNUNET_JSON_pack_rsa_public_key ("rsa_public_key",
+                                           bsp->details.rsa_public_key));
+    return ps;
+  case GNUNET_CRYPTO_BSA_CS:
+    ps.object
+      = GNUNET_JSON_PACK (
+          GNUNET_JSON_pack_string ("cipher",
+                                   "CS"),
+          GNUNET_JSON_pack_uint64 ("age_mask",
+                                   pk->age_mask.bits),
+          GNUNET_JSON_pack_data_varsize ("cs_public_key",
+                                         &bsp->details.cs_public_key,
+                                         sizeof (bsp->details.cs_public_key)));
+    return ps;
+  }
+  GNUNET_assert (0);
+  return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_denom_sig (
+  const char *name,
+  const struct TALER_DenominationSignature *sig)
+{
+  const struct GNUNET_CRYPTO_UnblindedSignature *bs;
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+  };
+
+  if (NULL == sig)
+    return ps;
+  bs = sig->unblinded_sig;
+  switch (bs->cipher)
+  {
+  case GNUNET_CRYPTO_BSA_INVALID:
+    break;
+  case GNUNET_CRYPTO_BSA_RSA:
+    ps.object = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("cipher",
+                               "RSA"),
+      GNUNET_JSON_pack_rsa_signature ("rsa_signature",
+                                      bs->details.rsa_signature));
+    return ps;
+  case GNUNET_CRYPTO_BSA_CS:
+    ps.object = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("cipher",
+                               "CS"),
+      GNUNET_JSON_pack_data_auto ("cs_signature_r",
+                                  &bs->details.cs_signature.r_point),
+      GNUNET_JSON_pack_data_auto ("cs_signature_s",
+                                  &bs->details.cs_signature.s_scalar));
+    return ps;
+  }
+  GNUNET_assert (0);
+  return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_exchange_withdraw_values (
+  const char *name,
+  const struct TALER_ExchangeWithdrawValues *ewv)
+{
+  const struct GNUNET_CRYPTO_BlindingInputValues *biv;
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+  };
+
+  if (NULL == ewv)
+    return ps;
+  biv = ewv->blinding_inputs;
+  switch (biv->cipher)
+  {
+  case GNUNET_CRYPTO_BSA_INVALID:
+    break;
+  case GNUNET_CRYPTO_BSA_RSA:
+    ps.object = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("cipher",
+                               "RSA"));
+    return ps;
+  case GNUNET_CRYPTO_BSA_CS:
+    ps.object = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("cipher",
+                               "CS"),
+      GNUNET_JSON_pack_data_varsize (
+        "r_pub_0",
+        &biv->details.cs_values.r_pub[0],
+        sizeof(struct GNUNET_CRYPTO_CsRPublic)),
+      GNUNET_JSON_pack_data_varsize (
+        "r_pub_1",
+        &biv->details.cs_values.r_pub[1],
+        sizeof(struct GNUNET_CRYPTO_CsRPublic))
+      );
+    return ps;
+  }
+  GNUNET_assert (0);
+  return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_blinded_denom_sig (
+  const char *name,
+  const struct TALER_BlindedDenominationSignature *sig)
+{
+  const struct GNUNET_CRYPTO_BlindedSignature *bs;
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+  };
+
+  if (NULL == sig)
+    return ps;
+  bs = sig->blinded_sig;
+  switch (bs->cipher)
+  {
+  case GNUNET_CRYPTO_BSA_INVALID:
+    break;
+  case GNUNET_CRYPTO_BSA_RSA:
+    ps.object = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("cipher",
+                               "RSA"),
+      GNUNET_JSON_pack_rsa_signature ("blinded_rsa_signature",
+                                      bs->details.blinded_rsa_signature));
+    return ps;
+  case GNUNET_CRYPTO_BSA_CS:
+    ps.object = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("cipher",
+                               "CS"),
+      GNUNET_JSON_pack_uint64 ("b",
+                               bs->details.blinded_cs_answer.b),
+      GNUNET_JSON_pack_data_auto ("s",
+                                  &bs->details.blinded_cs_answer.s_scalar));
+    return ps;
+  }
+  GNUNET_assert (0);
+  return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_blinded_planchet (
+  const char *name,
+  const struct TALER_BlindedPlanchet *blinded_planchet)
+{
+  const struct GNUNET_CRYPTO_BlindedMessage *bm;
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+  };
+
+  if (NULL == blinded_planchet)
+    return ps;
+  bm = blinded_planchet->blinded_message;
+  switch (bm->cipher)
+  {
+  case GNUNET_CRYPTO_BSA_INVALID:
+    break;
+  case GNUNET_CRYPTO_BSA_RSA:
+    ps.object = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("cipher",
+                               "RSA"),
+      GNUNET_JSON_pack_data_varsize (
+        "rsa_blinded_planchet",
+        bm->details.rsa_blinded_message.blinded_msg,
+        bm->details.rsa_blinded_message.blinded_msg_size));
+    return ps;
+  case GNUNET_CRYPTO_BSA_CS:
+    ps.object = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("cipher",
+                               "CS"),
+      GNUNET_JSON_pack_data_auto (
+        "cs_nonce",
+        &bm->details.cs_blinded_message.nonce),
+      GNUNET_JSON_pack_data_auto (
+        "cs_blinded_c0",
+        &bm->details.cs_blinded_message.c[0]),
+      GNUNET_JSON_pack_data_auto (
+        "cs_blinded_c1",
+        &bm->details.cs_blinded_message.c[1]));
+    return ps;
+  }
+  GNUNET_assert (0);
+  return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_amount (const char *name,
+                        const struct TALER_Amount *amount)
+{
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+    .object = (NULL != amount)
+              ? TALER_JSON_from_amount (amount)
+              : NULL
+  };
+
+  return ps;
+}
+
+
+/* End of json/json_pack.c */

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]