gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] branch master updated: [age restriction] progress 10/n


From: gnunet
Subject: [taler-exchange] branch master updated: [age restriction] progress 10/n
Date: Sat, 08 Jan 2022 14:41:03 +0100

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

oec pushed a commit to branch master
in repository exchange.

The following commit(s) were added to refs/heads/master by this push:
     new cc7d7707 [age restriction] progress 10/n
cc7d7707 is described below

commit cc7d7707ab2bd43bc9e95c0eeec9ce95cdc0c523
Author: Özgür Kesim <oec-taler@kesim.org>
AuthorDate: Sat Jan 8 14:40:20 2022 +0100

    [age restriction] progress 10/n
    
    More work towards support for extensions:
    - Prepared statements and DB-plugin-functions for setting and retrieving
      configurations from the database added.
    - primitive "registry" of extensions for age restrictions and peer2peer
      (stub)
    - TALER_Extensions now with FP for parsing, setting and converting a
      configuration.
    - /management/extensions handler now verifies signature of the (opaque)
      json object for all extensions.
    - /management/extensions handler calls the FP in the corrensponding
      TALER_Extension for parsing and setting the configuration of a
      particular extension
    
    More work towards age restriction:
    - TALER_Extensions interfaces for config-parser, -setter and converter
      implemented for age restriction
    - DB event handler now retrieves config from database, parses it and
      sets it (the age mask) in the global extension.
    - load_age_mask now loads age mask from the global extension (and not
      from the config file)
    - add age_restricted_denoms to /keys response
---
 src/exchange/taler-exchange-httpd.c                |  56 ++-
 src/exchange/taler-exchange-httpd.h                |   7 +-
 src/exchange/taler-exchange-httpd_extensions.c     | 219 ++++++++++--
 src/exchange/taler-exchange-httpd_extensions.h     |   8 -
 src/exchange/taler-exchange-httpd_keys.c           | 111 ++++--
 .../taler-exchange-httpd_management_extensions.c   | 377 +++++++++++----------
 src/exchangedb/plugin_exchangedb_postgres.c        | 153 +++++++--
 src/include/taler_crypto_lib.h                     |  30 +-
 src/include/taler_exchangedb_plugin.h              |  29 +-
 src/include/taler_extensions.h                     |  63 +++-
 src/include/taler_json_lib.h                       |  13 +-
 src/include/taler_signatures.h                     |  26 +-
 src/json/json.c                                    |  10 +
 src/lib/exchange_api_management_post_extensions.c  |  23 +-
 src/util/Makefile.am                               |   1 +
 src/util/extension_age_restriction.c               |   5 +-
 src/util/extensions.c                              |  49 +++
 src/util/offline_signatures.c                      |  56 +--
 18 files changed, 823 insertions(+), 413 deletions(-)

diff --git a/src/exchange/taler-exchange-httpd.c 
b/src/exchange/taler-exchange-httpd.c
index b435ee4a..59398c6f 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -1,18 +1,18 @@
 /*
-  This file is part of TALER
-  Copyright (C) 2014-2021 Taler Systems SA
+   This file is part of TALER
+   Copyright (C) 2014-2021 Taler Systems SA
 
-  TALER is free software; you can redistribute it and/or modify it under the
-  terms of the GNU Affero General Public License as published by the Free 
Software
-  Foundation; either version 3, or (at your option) any later version.
+   TALER is free software; you can redistribute it and/or modify it under the
+   terms of the GNU Affero 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 Affero General Public License for more 
details.
+   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 Affero General Public License for more 
details.
 
-  You should have received a copy of the GNU Affero General Public License 
along with
-  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
-*/
+   You should have received a copy of the GNU Affero General Public License 
along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
 /**
  * @file taler-exchange-httpd.c
  * @brief Serve the HTTP interface of the exchange
@@ -148,23 +148,9 @@ int TEH_check_invariants_flag;
 bool TEH_suicide;
 
 /**
- * The global manifest with the list supported extensions, sorted by
- * TALER_Extension_Type.
- **/
-const struct TALER_Extension TEH_extensions[TALER_Extension_Max] = {
-  [TALER_Extension_Peer2Peer] = {
-    .type = TALER_Extension_Peer2Peer,
-    .name = "peer2peer",
-    .critical = false,
-    .config = NULL, // disabled per default
-  },
-  [TALER_Extension_AgeRestriction] = {
-    .type = TALER_Extension_AgeRestriction,
-    .name = "age_restriction",
-    .critical = false,
-    .config = NULL, // disabled per default
-  },
-};
+ * Global register of extensions
+ */
+struct TALER_Extension **TEH_extensions;
 
 /**
  * Value to return from main()
@@ -485,7 +471,7 @@ proceed_with_handler (struct TEH_RequestContext *rc,
     if (GNUNET_SYSERR == res)
     {
       GNUNET_assert (NULL == root);
-      return MHD_NO;  /* bad upload, could not even generate error */
+      return MHD_NO; /* bad upload, could not even generate error */
     }
     if ( (GNUNET_NO == res) ||
          (NULL == root) )
@@ -528,8 +514,8 @@ proceed_with_handler (struct TEH_RequestContext *rc,
                          sizeof (emsg),
                          "Got %u/%u segments for %s request ('%s')",
                          (NULL == args[i - 1])
-                         ? i - 1
-                         : i + ((NULL != fin) ? 1 : 0),
+       ? i - 1
+       : i + ((NULL != fin) ? 1 : 0),
                          rh->nargs,
                          rh->url,
                          url);
@@ -553,7 +539,7 @@ proceed_with_handler (struct TEH_RequestContext *rc,
                               root,
                               args);
     else /* We also only have "POST" or "GET" in the API for at this point
-            (OPTIONS/HEAD are taken care of earlier) */
+      (OPTIONS/HEAD are taken care of earlier) */
       ret = rh->handler.get (rc,
                              args);
   }
@@ -1120,7 +1106,7 @@ handle_mhd_request (void *cls,
 
   if (0 == strcasecmp (method,
                        MHD_HTTP_METHOD_HEAD))
-    method = MHD_HTTP_METHOD_GET;   /* treat HEAD as GET here, MHD will do the 
rest */
+    method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the 
rest */
 
   /* parse first part of URL */
   {
@@ -1954,8 +1940,8 @@ run (void *cls,
                           MHD_OPTION_CONNECTION_TIMEOUT,
                           connection_timeout,
                           (0 == allow_address_reuse)
-                          ? MHD_OPTION_END
-                          : MHD_OPTION_LISTENING_ADDRESS_REUSE,
+                    ? MHD_OPTION_END
+                    : MHD_OPTION_LISTENING_ADDRESS_REUSE,
                           (unsigned int) allow_address_reuse,
                           MHD_OPTION_END);
   if (NULL == mhd)
diff --git a/src/exchange/taler-exchange-httpd.h 
b/src/exchange/taler-exchange-httpd.h
index fa47af6f..4f04029e 100644
--- a/src/exchange/taler-exchange-httpd.h
+++ b/src/exchange/taler-exchange-httpd.h
@@ -202,9 +202,12 @@ extern volatile bool MHD_terminating;
 extern struct GNUNET_CURL_Context *TEH_curl_ctx;
 
 /**
- * The manifest of the available extensions
+ * The manifest of the available extensions, NULL terminated
  */
-extern const struct TALER_Extension TEH_extensions[TALER_Extension_Max];
+extern struct TALER_Extension **TEH_extensions;
+
+#define TEH_extension_enabled(ext) (0 <= ext && TALER_Extension_Max > ext && \
+                                    NULL != TEH_extensions[ext]->config)
 
 /**
  * @brief Struct describing an URL and the handler for it.
diff --git a/src/exchange/taler-exchange-httpd_extensions.c 
b/src/exchange/taler-exchange-httpd_extensions.c
index 98092bd0..8723bebc 100644
--- a/src/exchange/taler-exchange-httpd_extensions.c
+++ b/src/exchange/taler-exchange-httpd_extensions.c
@@ -24,8 +24,107 @@
 #include "taler-exchange-httpd_extensions.h"
 #include "taler_json_lib.h"
 #include "taler_mhd_lib.h"
+#include "taler_extensions.h"
 #include <jansson.h>
 
+/**
+ * @brief implements the TALER_Extension.parse_and_set_config interface.
+ */
+static enum GNUNET_GenericReturnValue
+age_restriction_parse_and_set_config (struct TALER_Extension *this,
+                                      const json_t *config)
+{
+  enum GNUNET_GenericReturnValue ret;
+  struct TALER_AgeMask mask = {0};
+
+  ret = TALER_agemask_parse_json (config, &mask);
+  if (GNUNET_OK != ret)
+    return ret;
+
+  if (this != NULL && TALER_Extension_AgeRestriction == this->type)
+  {
+    if (NULL != this->config)
+    {
+      GNUNET_free (this->config);
+    }
+    this->config = GNUNET_malloc (sizeof(struct TALER_AgeMask));
+    GNUNET_memcpy (this->config, &mask, sizeof(struct TALER_AgeMask));
+  }
+
+  return GNUNET_OK;
+}
+
+
+/**
+ * @brief implements the TALER_Extension.test_config interface.
+ */
+static enum GNUNET_GenericReturnValue
+age_restriction_test_config (const json_t *config)
+{
+  return age_restriction_parse_and_set_config (NULL, config);
+}
+
+
+/**
+ * @brief implements the TALER_Extension.config_to_json interface.
+ */
+static json_t *
+age_restriction_config_to_json (const struct TALER_Extension *this)
+{
+  const struct TALER_AgeMask *mask;
+  if (NULL == this || TALER_Extension_AgeRestriction != this->type)
+    return NULL;
+
+  mask = (struct TALER_AgeMask *) this->config;
+  json_t *config =  GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_string ("extension", this->name),
+    GNUNET_JSON_pack_string ("mask",
+                             TALER_age_mask_to_string (mask))
+    );
+
+  return config;
+}
+
+
+/* The extension for age restriction */
+static struct TALER_Extension extension_age_restriction = {
+  .type = TALER_Extension_AgeRestriction,
+  .name = "age_restriction",
+  .critical = false,
+  .config = NULL,   // disabled per default
+  .test_config = &age_restriction_test_config,
+  .parse_and_set_config = &age_restriction_parse_and_set_config,
+  .config_to_json = &age_restriction_config_to_json,
+};
+
+/* TODO: The extension for peer2peer */
+static struct TALER_Extension extension_peer2peer = {
+  .type = TALER_Extension_Peer2Peer,
+  .name = "peer2peer",
+  .critical = false,
+  .config = NULL,   // disabled per default
+  .test_config = NULL, // TODO
+  .parse_and_set_config = NULL, // TODO
+  .config_to_json = NULL, // TODO
+};
+
+
+/**
+ * Create a list with the extensions for Age Restriction and Peer2Peer
+ */
+static struct TALER_Extension **
+get_known_extensions ()
+{
+
+  struct TALER_Extension **list = GNUNET_new_array (TALER_Extension_Max + 1,
+                                                    struct TALER_Extension *);
+  list[TALER_Extension_AgeRestriction] = &extension_age_restriction;
+  list[TALER_Extension_Peer2Peer] = &extension_peer2peer;
+  list[TALER_Extension_Max] = NULL;
+
+  return list;
+}
+
 
 /**
  * Handler listening for extensions updates by other exchange
@@ -33,7 +132,6 @@
  */
 static struct GNUNET_DB_EventHandler *extensions_eh;
 
-
 /**
  * Function called whenever another exchange process has updated
  * the extensions data in the database.
@@ -48,30 +146,99 @@ extension_update_event_cb (void *cls,
                            size_t extra_size)
 {
   (void) cls;
-  (void) extra;
-  (void) extra_size;
+  enum TALER_Extension_Type type;
+
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Received /management/extensions update event\n");
+              "Received extensions update event\n");
+
+  if (sizeof(enum TALER_Extension_Type) != extra_size)
+  {
+    GNUNET_break (0);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Oops, incorrect size of extra for TALER_Extension_type\n");
+    return;
+  }
+
+  type = *(enum TALER_Extension_Type *) extra;
+  if (type <0 || type >= TALER_Extension_Max)
+  {
+    GNUNET_break (0);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Oops, incorrect type for TALER_Extension_type\n");
+    return;
+  }
+
+  // Get the config from the database as string
+  {
+    char *config_str;
+    enum GNUNET_DB_QueryStatus qs;
+    struct TALER_Extension *extension;
+    json_error_t err;
+    json_t *config;
+    enum GNUNET_GenericReturnValue ret;
+
+    // TODO: make this a safe lookup
+    extension  = TEH_extensions[type];
+
+    qs = TEH_plugin->get_extension_config (TEH_plugin->cls,
+                                           extension->name,
+                                           &config_str);
+
+    if (qs < 0)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Couldn't get extension config\n");
+      GNUNET_break (0);
+      return;
+    }
+
+    // Parse the string as JSON
+    config = json_loads (config_str, JSON_DECODE_ANY, &err);
+    if (NULL == config)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Failed to parse config for extension `%s' as JSON: %s 
(%s)\n",
+                  extension->name,
+                  err.text,
+                  err.source);
+      GNUNET_break (0);
+      return;
+    }
+
+    // Call the parser for the extension
+    ret = extension->parse_and_set_config (extension, config);
+    if (GNUNET_OK != ret)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Couldn't parse configuration for extension %s from the 
database",
+                  extension->name);
+      GNUNET_break (0);
+    }
+  }
 }
 
 
 enum GNUNET_GenericReturnValue
 TEH_extensions_init ()
 {
-  struct GNUNET_DB_EventHeaderP es = {
-    .size = htons (sizeof (es)),
-    .type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED),
-  };
-
-  extensions_eh = TEH_plugin->event_listen (TEH_plugin->cls,
-                                            GNUNET_TIME_UNIT_FOREVER_REL,
-                                            &es,
-                                            &extension_update_event_cb,
-                                            NULL);
-  if (NULL == extensions_eh)
+  TEH_extensions = get_known_extensions ();
+
   {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
+    struct GNUNET_DB_EventHeaderP ev = {
+      .size = htons (sizeof (ev)),
+      .type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED),
+    };
+
+    extensions_eh = TEH_plugin->event_listen (TEH_plugin->cls,
+                                              GNUNET_TIME_UNIT_FOREVER_REL,
+                                              &ev,
+                                              &extension_update_event_cb,
+                                              NULL);
+    if (NULL == extensions_eh)
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
   }
   return GNUNET_OK;
 }
@@ -89,22 +256,4 @@ TEH_extensions_done ()
 }
 
 
-void
-TEH_extensions_update_state (void)
-{
-  /* TODO */
-#if 0
-  struct GNUNET_DB_EventHeaderP es = {
-    .size = htons (sizeof (es)),
-    .type = htons (TALER_DBEVENT_EXCHANGE_WIRE_UPDATED),
-  };
-
-  TEH_plugin->event_notify (TEH_plugin->cls,
-                            &es,
-                            NULL,
-                            0);
-#endif
-}
-
-
 /* end of taler-exchange-httpd_extensions.c */
diff --git a/src/exchange/taler-exchange-httpd_extensions.h 
b/src/exchange/taler-exchange-httpd_extensions.h
index 3c86e266..4659b653 100644
--- a/src/exchange/taler-exchange-httpd_extensions.h
+++ b/src/exchange/taler-exchange-httpd_extensions.h
@@ -40,12 +40,4 @@ TEH_extensions_init (void);
 void
 TEH_extensions_done (void);
 
-/**
- * Something changed in the database. Rebuild the extension state metadata.
- * This function should be called if the exchange learns about a new signature
- * from our master key.
- */
-void
-TEH_extensions_update_state (void);
-
 #endif
diff --git a/src/exchange/taler-exchange-httpd_keys.c 
b/src/exchange/taler-exchange-httpd_keys.c
index 5d747677..30bbe8eb 100644
--- a/src/exchange/taler-exchange-httpd_keys.c
+++ b/src/exchange/taler-exchange-httpd_keys.c
@@ -736,10 +736,6 @@ destroy_key_helpers (struct HelperState *hs)
  * Looks up the AGE_RESTRICTED setting for a denomination in the config and
  * returns the age restriction (mask) accordingly.
  *
- * FIXME: The mask is currently taken from the config.  However, It MUST come
- * from the database where it has been persisted after a signed call to the
- * /management/extension API (TODO).
- *
  * @param section_name Section in the configuration for the particular
  *    denomination.
  */
@@ -748,15 +744,13 @@ load_age_mask (const char*section_name)
 {
   static const struct TALER_AgeMask null_mask = {0};
   struct TALER_AgeMask age_mask = {0};
+  const struct TALER_Extension *age_ext =
+    TEH_extensions[TALER_Extension_AgeRestriction];
 
-  /* FIXME-oec: get age_mask from database, not from config */
-  if (TALER_Extension_OK != TALER_get_age_mask (TEH_cfg, &age_mask))
+  // Get the age mask from the extension, if configured
+  if (NULL != age_ext->config)
   {
-    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
-                               TALER_EXTENSION_SECTION_AGE_RESTRICTION,
-                               "AGE_GROUPS",
-                               "must be of form a:b:...:n:m, where 
0<a<b<...<n<m<32\n");
-    return null_mask;
+    age_mask = *(struct TALER_AgeMask *) age_ext->config;
   }
 
   if (age_mask.mask == 0)
@@ -1450,7 +1444,6 @@ struct DenomKeyCtx
    * valid denomination keys?
    */
   struct GNUNET_TIME_Relative min_dk_frequency;
-
 };
 
 
@@ -1613,6 +1606,7 @@ setup_general_response_headers (struct TEH_KeyStateHandle 
*ksh,
  * @param signkeys list of sign keys to return
  * @param recoup list of revoked keys to return
  * @param denoms list of denominations to return
+ * @param age_restricted_denoms list of age restricted denominations to 
return, can be NULL
  * @return #GNUNET_OK on success
  */
 static enum GNUNET_GenericReturnValue
@@ -1621,7 +1615,8 @@ create_krd (struct TEH_KeyStateHandle *ksh,
             struct GNUNET_TIME_Timestamp last_cpd,
             json_t *signkeys,
             json_t *recoup,
-            json_t *denoms)
+            json_t *denoms,
+            json_t *age_restricted_denoms)
 {
   struct KeysResponseData krd;
   struct TALER_ExchangePublicKeyP exchange_pub;
@@ -1693,6 +1688,8 @@ create_krd (struct TEH_KeyStateHandle *ksh,
     GNUNET_JSON_pack_data_auto ("eddsa_sig",
                                 &exchange_sig));
   GNUNET_assert (NULL != keys);
+
+  // Set wallet limit if KYC is configured
   if ( (TEH_KYC_NONE != TEH_kyc_config.mode) &&
        (GNUNET_OK ==
         TALER_amount_is_valid (&TEH_kyc_config.wallet_balance_limit)) )
@@ -1706,6 +1703,40 @@ create_krd (struct TEH_KeyStateHandle *ksh,
           &TEH_kyc_config.wallet_balance_limit)));
   }
 
+  // Signal support for the age-restriction extension, if so configured, and
+  // add the array of age-restricted denominations.
+  if (TEH_extension_enabled (TALER_Extension_AgeRestriction) &&
+      NULL != age_restricted_denoms)
+  {
+    struct TALER_AgeMask *mask;
+    json_t *config;
+
+    mask = (struct
+            TALER_AgeMask *) TEH_extensions[TALER_Extension_AgeRestriction]->
+           config;
+    config = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_bool ("critical", false),
+      GNUNET_JSON_pack_string ("version", "1"),
+      GNUNET_JSON_pack_string ("age_groups", TALER_age_mask_to_string (mask)));
+    GNUNET_assert (NULL != config);
+    GNUNET_assert (
+      0 ==
+      json_object_set_new (
+        keys,
+        "age_restriction",
+        config));
+
+    GNUNET_assert (
+      0 ==
+      json_object_set_new (
+        keys,
+        "age_restricted_denoms",
+        age_restricted_denoms));
+  }
+
+  // TODO: signal support and configuration for the P2P extension, once
+  // implemented.
+
   {
     char *keys_json;
     void *keys_jsonz;
@@ -1772,7 +1803,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
 {
   json_t *recoup;
   struct SignKeyCtx sctx;
-  json_t *denoms;
+  json_t *denoms = NULL;
+  json_t *age_restricted_denoms = NULL;
   struct GNUNET_TIME_Timestamp last_cpd;
   struct GNUNET_CONTAINER_Heap *heap;
   struct GNUNET_HashContext *hash_context;
@@ -1802,6 +1834,14 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
   }
   denoms = json_array ();
   GNUNET_assert (NULL != denoms);
+
+  // If age restriction is enabled, initialize the array of age restricted 
denoms.
+  if (TEH_extension_enabled (TALER_Extension_AgeRestriction))
+  {
+    age_restricted_denoms = json_array ();
+    GNUNET_assert (NULL != age_restricted_denoms);
+  }
+
   last_cpd = GNUNET_TIME_UNIT_ZERO_TS;
   hash_context = GNUNET_CRYPTO_hash_context_start ();
   {
@@ -1826,7 +1866,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
                         last_cpd,
                         sctx.signkeys,
                         recoup,
-                        denoms))
+                        denoms,
+                        age_restricted_denoms))
         {
           GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                       "Failed to generate key response data for %s\n",
@@ -1837,6 +1878,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
             /* intentionally empty */;
           GNUNET_CONTAINER_heap_destroy (heap);
           json_decref (denoms);
+          if (NULL != age_restricted_denoms)
+            json_decref (age_restricted_denoms);
           json_decref (sctx.signkeys);
           json_decref (recoup);
           return GNUNET_SYSERR;
@@ -1846,10 +1889,12 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
       GNUNET_CRYPTO_hash_context_read (hash_context,
                                        &dk->h_denom_pub,
                                        sizeof (struct GNUNET_HashCode));
-      GNUNET_assert (
-        0 ==
-        json_array_append_new (
-          denoms,
+
+      {
+        json_t *denom;
+        json_t *array;
+
+        denom =
           GNUNET_JSON_PACK (
             GNUNET_JSON_pack_data_auto ("master_sig",
                                         &dk->master_sig),
@@ -1872,7 +1917,26 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
             TALER_JSON_pack_amount ("fee_refresh",
                                     &dk->meta.fee_refresh),
             TALER_JSON_pack_amount ("fee_refund",
-                                    &dk->meta.fee_refund))));
+                                    &dk->meta.fee_refund));
+
+        /* Put the denom into the correct array - denoms or 
age_restricted_denoms -
+         * depending on the settings and the properties of the denomination */
+        if (NULL != age_restricted_denoms &&
+            0 != dk->meta.age_restrictions.mask)
+        {
+          array = age_restricted_denoms;
+        }
+        else
+        {
+          array = denoms;
+        }
+
+        GNUNET_assert (
+          0 ==
+          json_array_append_new (
+            array,
+            denom));
+      }
     }
   }
   GNUNET_CONTAINER_heap_destroy (heap);
@@ -1888,12 +1952,15 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
                     last_cpd,
                     sctx.signkeys,
                     recoup,
-                    denoms))
+                    denoms,
+                    age_restricted_denoms))
     {
       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                   "Failed to generate key response data for %s\n",
                   GNUNET_TIME_timestamp2s (last_cpd));
       json_decref (denoms);
+      if (NULL != age_restricted_denoms)
+        json_decref (age_restricted_denoms);
       json_decref (sctx.signkeys);
       json_decref (recoup);
       return GNUNET_SYSERR;
@@ -1909,6 +1976,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
   json_decref (sctx.signkeys);
   json_decref (recoup);
   json_decref (denoms);
+  if (NULL != age_restricted_denoms)
+    json_decref (age_restricted_denoms);
   return GNUNET_OK;
 }
 
diff --git a/src/exchange/taler-exchange-httpd_management_extensions.c 
b/src/exchange/taler-exchange-httpd_management_extensions.c
index 6a771bf4..96b855c3 100644
--- a/src/exchange/taler-exchange-httpd_management_extensions.c
+++ b/src/exchange/taler-exchange-httpd_management_extensions.c
@@ -29,21 +29,17 @@
 #include "taler-exchange-httpd_management.h"
 #include "taler-exchange-httpd_responses.h"
 #include "taler_extensions.h"
+#include "taler_dbevents.h"
 
 
+/**
+ * Extension carries the necessary data for a particular extension.
+ *
+ */
 struct Extension
 {
   enum TALER_Extension_Type type;
-  json_t *config_json;
-
-  // This union contains the parsed configuration for each extension.
-  union
-  {
-    // configuration for the age restriction
-    struct TALER_AgeMask mask;
-
-    /* TODO oec - peer2peer config */
-  };
+  json_t *config;
 };
 
 /**
@@ -56,6 +52,38 @@ struct SetExtensionsContext
   struct TALER_MasterSignatureP *extensions_sigs;
 };
 
+
+/**
+ * @brief verifies the signature a configuration with the offline master key.
+ *
+ * @param config configuration of an extension given as JSON object
+ * @param master_priv offline master public key of the exchange
+ * @param[out] master_sig  signature
+ * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
+ */
+static enum GNUNET_GenericReturnValue
+config_verify (
+  const json_t *config,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig
+  )
+{
+  enum GNUNET_GenericReturnValue ret;
+  struct TALER_ExtensionConfigHash h_config;
+
+  ret = TALER_extension_config_hash (config, &h_config);
+  if (GNUNET_OK != ret)
+  {
+    GNUNET_break (0);
+    return ret;
+  }
+
+  return TALER_exchange_offline_extension_config_hash_verify (h_config,
+                                                              master_pub,
+                                                              master_sig);
+}
+
+
 /**
  * Function implementing database transaction to set the configuration of
  * extensions.  It runs the transaction logic.
@@ -77,9 +105,68 @@ set_extensions (void *cls,
                 struct MHD_Connection *connection,
                 MHD_RESULT *mhd_ret)
 {
-  // struct SetExtensionContext *sec = cls;
+  struct SetExtensionsContext *sec = cls;
+
+  /* save the configurations of all extensions */
+  for (uint32_t i = 0; i<sec->num_extensions; i++)
+  {
+    struct Extension *ext = &sec->extensions[i];
+    struct TALER_MasterSignatureP *sig = &sec->extensions_sigs[i];
+    enum GNUNET_DB_QueryStatus qs;
+    char *config;
+
+    /* Sanity check.
+     * TODO: replace with general API to retrieve the extension-handler
+     */
+    if (0 > ext->type || TALER_Extension_Max <= ext->type)
+    {
+      GNUNET_break (0);
+      return GNUNET_DB_STATUS_HARD_ERROR;
+    }
+
+    config = json_dumps (ext->config, JSON_COMPACT | JSON_SORT_KEYS);
+    if (NULL == config)
+    {
+      GNUNET_break (0);
+      *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                             TALER_EC_GENERIC_JSON_INVALID,
+                                             "convert configuration to 
string");
+      return GNUNET_DB_STATUS_HARD_ERROR;
+    }
+
+    qs = TEH_plugin->set_extension_config (
+      TEH_plugin->cls,
+      TEH_extensions[ext->type]->name,
+      config,
+      sig);
+
+    if (qs < 0)
+    {
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+        return qs;
+      GNUNET_break (0);
+      *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                             TALER_EC_GENERIC_DB_STORE_FAILED,
+                                             "save extension configuration");
+    }
+
+    /* Success, trigger event */
+    {
+      enum TALER_Extension_Type *type = &sec->extensions[i].type;
+      struct GNUNET_DB_EventHeaderP ev = {
+        .size = htons (sizeof (ev)),
+        .type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED)
+      };
+      TEH_plugin->event_notify (TEH_plugin->cls,
+                                &ev,
+                                type,
+                                sizeof(*type));
+    }
+
+  }
 
-  // TODO oec
   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* only 'success', so >=0, 
matters here */
 }
 
@@ -92,50 +179,51 @@ TEH_handler_management_post_extensions (
   struct SetExtensionsContext sec = {0};
   json_t *extensions;
   json_t *extensions_sigs;
-  struct GNUNET_JSON_Specification spec[] = {
+  struct GNUNET_JSON_Specification top_spec[] = {
     GNUNET_JSON_spec_json ("extensions",
                            &extensions),
     GNUNET_JSON_spec_json ("extensions_sigs",
                            &extensions_sigs),
     GNUNET_JSON_spec_end ()
   };
-  bool ok;
   MHD_RESULT ret;
 
+  // Parse the top level json structure
   {
     enum GNUNET_GenericReturnValue res;
 
     res = TALER_MHD_parse_json_data (connection,
                                      root,
-                                     spec);
+                                     top_spec);
     if (GNUNET_SYSERR == res)
       return MHD_NO; /* hard failure */
     if (GNUNET_NO == res)
       return MHD_YES; /* failure */
   }
 
+  // Ensure we have two arrays of the same size
   if (! (json_is_array (extensions) &&
          json_is_array (extensions_sigs)) )
   {
     GNUNET_break_op (0);
-    GNUNET_JSON_parse_free (spec);
+    GNUNET_JSON_parse_free (top_spec);
     return TALER_MHD_reply_with_error (
       connection,
       MHD_HTTP_BAD_REQUEST,
       TALER_EC_GENERIC_PARAMETER_MALFORMED,
-      "array expected for extensions and extensions_sig");
+      "array expected for extensions and extensions_sigs");
   }
 
   sec.num_extensions = json_array_size (extensions_sigs);
   if (json_array_size (extensions) != sec.num_extensions)
   {
     GNUNET_break_op (0);
-    GNUNET_JSON_parse_free (spec);
+    GNUNET_JSON_parse_free (top_spec);
     return TALER_MHD_reply_with_error (
       connection,
       MHD_HTTP_BAD_REQUEST,
       TALER_EC_GENERIC_PARAMETER_MALFORMED,
-      "arrays extensions and extensions_sig are not of equal size");
+      "arrays extensions and extensions_sigs are not of the same size");
   }
 
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -145,116 +233,59 @@ TEH_handler_management_post_extensions (
                                      struct Extension);
   sec.extensions_sigs = GNUNET_new_array (sec.num_extensions,
                                           struct TALER_MasterSignatureP);
-  ok = true;
 
+  // Now parse individual extensions and signatures from those arrays.
   for (unsigned int i = 0; i<sec.num_extensions; i++)
   {
-
-    // 1. parse the extension
+    // 1. parse the extension out of the json
+    enum GNUNET_GenericReturnValue res;
+    const struct TALER_Extension *extension;
+    const char *name;
+    struct GNUNET_JSON_Specification ext_spec[] = {
+      GNUNET_JSON_spec_string ("extension",
+                               &name),
+      GNUNET_JSON_spec_json ("config",
+                             &sec.extensions[i].config),
+      GNUNET_JSON_spec_end ()
+    };
+
+    res = TALER_MHD_parse_json_array (connection,
+                                      extensions,
+                                      ext_spec,
+                                      i,
+                                      -1);
+    if (GNUNET_SYSERR == res)
     {
-      enum GNUNET_GenericReturnValue res;
-      const char *name;
-      struct GNUNET_JSON_Specification ispec[] = {
-        GNUNET_JSON_spec_string ("extension",
-                                 &name),
-        GNUNET_JSON_spec_json ("config",
-                               &sec.extensions[i].config_json),
-        GNUNET_JSON_spec_end ()
-      };
-
-      res = TALER_MHD_parse_json_array (connection,
-                                        extensions,
-                                        ispec,
-                                        i,
-                                        -1);
-      if (GNUNET_SYSERR == res)
-      {
-        ret = MHD_NO; /* hard failure */
-        ok = false;
-        break;
-      }
-      if (GNUNET_NO == res)
-      {
-        ret = MHD_YES;
-        ok = false;
-        break;
-      }
-
-      // Make sure name refers to a supported extension
-      {
-        bool found = false;
-        for (unsigned int k = 0; k < TALER_Extension_Max; k++)
-        {
-          if (0 == strncmp (name,
-                            TEH_extensions[k].name,
-                            strlen (TEH_extensions[k].name)))
-          {
-            sec.extensions[i].type = TEH_extensions[k].type;
-            found = true;
-            break;
-          }
-        }
-
-        if (! found)
-        {
-          GNUNET_free (sec.extensions);
-          GNUNET_free (sec.extensions_sigs);
-          GNUNET_JSON_parse_free (spec);
-          GNUNET_JSON_parse_free (ispec);
-          return TALER_MHD_reply_with_error (
-            connection,
-            MHD_HTTP_BAD_REQUEST,
-            TALER_EC_GENERIC_PARAMETER_MALFORMED,
-            "invalid extension type");
-        }
-      }
-
-      // We have a JSON object for the extension.  Increment its refcount and
-      // free the parser.
-      // TODO: is this correct?
-      json_incref (sec.extensions[i].config_json);
-      GNUNET_JSON_parse_free (ispec);
+      ret = MHD_NO; /* hard failure */
+      goto CLEANUP;
+    }
+    if (GNUNET_NO == res)
+    {
+      ret = MHD_YES;
+      goto CLEANUP;
+    }
 
-      // Make sure the config is sound
-      {
-        switch (sec.extensions[i].type)
-        {
-        case TALER_Extension_AgeRestriction:
-          if (GNUNET_OK != TALER_agemask_parse_json (
-                sec.extensions[i].config_json,
-                &sec.extensions[i].mask))
-          {
-            GNUNET_free (sec.extensions);
-            GNUNET_free (sec.extensions_sigs);
-            GNUNET_JSON_parse_free (spec);
-            return TALER_MHD_reply_with_error (
-              connection,
-              MHD_HTTP_BAD_REQUEST,
-              TALER_EC_GENERIC_PARAMETER_MALFORMED,
-              "invalid mask for age restriction");
-          }
-          break;
-
-        case TALER_Extension_Peer2Peer:   /* TODO */
-          ok = false;
-          ret = MHD_NO;
-          goto BREAK;
-
-        default:
-          /* not reachable */
-          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                      "shouldn't be reached in handler for 
/management/extensions\n");
-          ok = false;
-          ret = MHD_NO;
-          goto BREAK;
-        }
-      }
+    /* 2. Make sure name refers to a supported extension */
+    if (GNUNET_OK != TALER_extension_get_by_name (name,
+                                                  (const struct
+                                                   TALER_Extension **)
+                                                  TEH_extensions,
+                                                  &extension))
+    {
+      ret = TALER_MHD_reply_with_error (
+        connection,
+        MHD_HTTP_BAD_REQUEST,
+        TALER_EC_GENERIC_PARAMETER_MALFORMED,
+        "invalid extension type");
+      goto CLEANUP;
     }
 
-    // 2. parse the signature
+    sec.extensions[i].type = extension->type;
+
+    /* 3. Extract the signature out of the json array */
     {
       enum GNUNET_GenericReturnValue res;
-      struct GNUNET_JSON_Specification ispec[] = {
+      struct GNUNET_JSON_Specification sig_spec[] = {
         GNUNET_JSON_spec_fixed_auto (NULL,
                                      &sec.extensions_sigs[i]),
         GNUNET_JSON_spec_end ()
@@ -262,81 +293,61 @@ TEH_handler_management_post_extensions (
 
       res = TALER_MHD_parse_json_array (connection,
                                         extensions_sigs,
-                                        ispec,
+                                        sig_spec,
                                         i,
                                         -1);
       if (GNUNET_SYSERR == res)
       {
         ret = MHD_NO; /* hard failure */
-        ok = false;
-        break;
+        goto CLEANUP;
       }
       if (GNUNET_NO == res)
       {
         ret = MHD_YES;
-        ok = false;
-        break;
+        goto CLEANUP;
       }
     }
 
-    // 3. verify the signature
+    /* 4. Verify the signature of the config */
+    if (GNUNET_OK != config_verify (
+          sec.extensions[i].config,
+          &TEH_master_public_key,
+          &sec.extensions_sigs[i]))
     {
-      enum GNUNET_GenericReturnValue res;
+      ret = TALER_MHD_reply_with_error (
+        connection,
+        MHD_HTTP_BAD_REQUEST,
+        TALER_EC_GENERIC_PARAMETER_MALFORMED,
+        "invalid signature for extension");
+      goto CLEANUP;
+    }
+
+    /* 5. Make sure the config is sound */
+    if (GNUNET_OK != extension->test_config (sec.extensions[i].config))
+    {
+      GNUNET_JSON_parse_free (ext_spec);
+      ret = TALER_MHD_reply_with_error (
+        connection,
+        MHD_HTTP_BAD_REQUEST,
+        TALER_EC_GENERIC_PARAMETER_MALFORMED,
+        "invalid configuration for extension");
+      goto CLEANUP;
 
-      switch (sec.extensions[i].type)
-      {
-      case TALER_Extension_AgeRestriction:
-        res = TALER_exchange_offline_extension_agemask_verify (
-          sec.extensions[i].mask,
-          &TEH_master_public_key,
-          &sec.extensions_sigs[i]);
-        if (GNUNET_OK != res)
-        {
-          GNUNET_free (sec.extensions);
-          GNUNET_free (sec.extensions_sigs);
-          GNUNET_JSON_parse_free (spec);
-          return TALER_MHD_reply_with_error (
-            connection,
-            MHD_HTTP_BAD_REQUEST,
-            TALER_EC_GENERIC_PARAMETER_MALFORMED,
-            "invalid signature for age mask");
-        }
-        break;
-
-      case TALER_Extension_Peer2Peer:   /* TODO */
-        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                    "Peer2peer not yet supported in handler for 
/management/extensions\n");
-        ok = false;
-        ret = MHD_NO;
-        goto BREAK;
-
-      default:
-        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                    "shouldn't be reached in handler for 
/management/extensions\n");
-        ok = false;
-        ret = MHD_NO;
-        /* not reachable */
-        goto BREAK;
-      }
     }
-  }
 
-BREAK:
-  if (! ok)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Failure to handle /management/extensions\n");
-    GNUNET_free (sec.extensions);
-    GNUNET_free (sec.extensions_sigs);
-    GNUNET_JSON_parse_free (spec);
-    return ret;
-  }
+    /* We have a validly signed JSON object for the extension.
+     * Increment its refcount and free the parser for the extension.
+     */
+    json_incref (sec.extensions[i].config);
+    GNUNET_JSON_parse_free (ext_spec);
 
+  } /* for-loop */
 
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Received %u extensions\n",
               sec.num_extensions);
 
+  // now run the transaction to persist the configurations
   {
     enum GNUNET_GenericReturnValue res;
 
@@ -347,19 +358,29 @@ BREAK:
                                   &set_extensions,
                                   &sec);
 
-    GNUNET_free (sec.extensions);
-    GNUNET_free (sec.extensions_sigs);
-    GNUNET_JSON_parse_free (spec);
     if (GNUNET_SYSERR == res)
-      return ret;
+      goto CLEANUP;
   }
 
-  return TALER_MHD_reply_static (
+  ret = TALER_MHD_reply_static (
     connection,
     MHD_HTTP_NO_CONTENT,
     NULL,
     NULL,
     0);
+
+CLEANUP:
+  for (unsigned int i = 0; i < sec.num_extensions; i++)
+  {
+    if (NULL != sec.extensions[i].config)
+    {
+      json_decref (sec.extensions[i].config);
+    }
+  }
+  GNUNET_free (sec.extensions);
+  GNUNET_free (sec.extensions_sigs);
+  GNUNET_JSON_parse_free (top_spec);
+  return ret;
 }
 
 
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c 
b/src/exchangedb/plugin_exchangedb_postgres.c
index 97782bd1..268279f3 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -1,18 +1,18 @@
 /*
-  This file is part of TALER
-  Copyright (C) 2014--2021 Taler Systems SA
+   This file is part of TALER
+   Copyright (C) 2014--2021 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 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.
+   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/>
-*/
+   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 plugin_exchangedb_postgres.c
@@ -211,7 +211,7 @@ prepare_statements (struct PostgresClosure *pg)
   enum GNUNET_GenericReturnValue ret;
   struct GNUNET_PQ_PreparedStatement ps[] = {
     /* Used in #postgres_insert_denomination_info() and
-       #postgres_add_denomination_key() */
+     #postgres_add_denomination_key() */
     GNUNET_PQ_make_prepare (
       "denomination_insert",
       "INSERT INTO denominations "
@@ -222,8 +222,8 @@ prepare_statements (struct PostgresClosure *pg)
       ",expire_withdraw"
       ",expire_deposit"
       ",expire_legal"
-      ",coin_val"                                                              
    /* value of this denom */
-      ",coin_frac"                                                             
     /* fractional value of this denom */
+      ",coin_val"                                                /* value of 
this denom */
+      ",coin_frac"                                                /* 
fractional value of this denom */
       ",fee_withdraw_val"
       ",fee_withdraw_frac"
       ",fee_deposit_val"
@@ -245,8 +245,8 @@ prepare_statements (struct PostgresClosure *pg)
       ",expire_withdraw"
       ",expire_deposit"
       ",expire_legal"
-      ",coin_val"                                                              
    /* value of this denom */
-      ",coin_frac"                                                             
     /* fractional value of this denom */
+      ",coin_val"                                                /* value of 
this denom */
+      ",coin_frac"                                                /* 
fractional value of this denom */
       ",fee_withdraw_val"
       ",fee_withdraw_frac"
       ",fee_deposit_val"
@@ -268,8 +268,8 @@ prepare_statements (struct PostgresClosure *pg)
       ",expire_withdraw"
       ",expire_deposit"
       ",expire_legal"
-      ",coin_val"                                                              
    /* value of this denom */
-      ",coin_frac"                                                             
     /* fractional value of this denom */
+      ",coin_val"                                                /* value of 
this denom */
+      ",coin_frac"                                                /* 
fractional value of this denom */
       ",fee_withdraw_val"
       ",fee_withdraw_frac"
       ",fee_deposit_val"
@@ -332,8 +332,8 @@ prepare_statements (struct PostgresClosure *pg)
       ",expire_withdraw"
       ",expire_deposit"
       ",expire_legal"
-      ",coin_val"                                                              
    /* value of this denom */
-      ",coin_frac"                                                             
     /* fractional value of this denom */
+      ",coin_val"                                                /* value of 
this denom */
+      ",coin_frac"                                                /* 
fractional value of this denom */
       ",fee_withdraw_val"
       ",fee_withdraw_frac"
       ",fee_deposit_val"
@@ -766,7 +766,7 @@ prepare_statements (struct PostgresClosure *pg)
 
        See also:
        
https://stackoverflow.com/questions/34708509/how-to-use-returning-with-on-conflict-in-postgresql/37543015#37543015
-    */
+     */
     GNUNET_PQ_make_prepare (
       "insert_known_coin",
       "WITH dd"
@@ -2743,6 +2743,23 @@ prepare_statements (struct PostgresClosure *pg)
       "   AND start_row=$2"
       "   AND end_row=$3",
       3),
+    /* Used in #postgres_set_extension_config */
+    GNUNET_PQ_make_prepare (
+      "set_extension_config",
+      "WITH upsert AS "
+      " (UPDATE extensions "
+      "    SET  config=$2 "
+      "         config_sig=$3 "
+      "   WHERE name=$1 RETURNING *) "
+      "INSERT INTO extensions (config, config_sig) VALUES ($2, $3) "
+      "WHERE NOT EXISTS (SELECT * FROM upsert);",
+      3),
+    /* Used in #postgres_get_extension_config */
+    GNUNET_PQ_make_prepare (
+      "get_extension_config",
+      "SELECT (config) FROM extensions"
+      " WHERE name=$1;",
+      1),
     GNUNET_PQ_PREPARED_STATEMENT_END
   };
 
@@ -3396,11 +3413,11 @@ dominations_cb_helper (void *cls,
 
 
 /**
-* Function called to invoke @a cb on every known denomination key (revoked
-* and non-revoked) that has been signed by the master key. Runs in its own
-* read-only transaction.
-*
-*
+ * Function called to invoke @a cb on every known denomination key (revoked
+ * and non-revoked) that has been signed by the master key. Runs in its own
+ * read-only transaction.
+ *
+ *
  * @param cls the @e cls of this struct with the plugin-specific state
  * @param cb function to call on each denomination key
  * @param cb_cls closure for @a cb
@@ -3513,7 +3530,7 @@ postgres_iterate_active_signkeys (void *cls,
                                   void *cb_cls)
 {
   struct PostgresClosure *pg = cls;
-  struct GNUNET_TIME_Absolute now;
+  struct GNUNET_TIME_Absolute now = {0};
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_absolute_time (&now),
     GNUNET_PQ_query_param_end
@@ -3600,7 +3617,7 @@ auditors_cb_helper (void *cls,
 /**
  * Function called to invoke @a cb on every active auditor. Disabled
  * auditors are skipped. Runs in its own read-only transaction.
-  *
+ *
  * @param cls the @e cls of this struct with the plugin-specific state
  * @param cb function to call on each active auditor
  * @param cb_cls closure for @a cb
@@ -4470,6 +4487,8 @@ compute_shard (const struct TALER_MerchantPublicKeyP 
*merchant_pub)
  * Perform deposit operation, checking for sufficient balance
  * of the coin and possibly persisting the deposit details.
  *
+ * FIXME: parameters missing in description!
+ *
  * @param cls the `struct PostgresClosure` with the plugin-specific state
  * @param deposit deposit operation details
  * @param known_coin_id row of the coin in the known_coins table
@@ -4908,7 +4927,7 @@ add_bank_to_exchange (void *cls,
     tail = append_rh (rhc);
     tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE;
     tail->details.bank = bt;
-  }   /* end of 'while (0 < rows)' */
+  } /* end of 'while (0 < rows)' */
 }
 
 
@@ -5033,7 +5052,7 @@ add_recoup (void *cls,
     tail = append_rh (rhc);
     tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN;
     tail->details.recoup = recoup;
-  }   /* end of 'while (0 < rows)' */
+  } /* end of 'while (0 < rows)' */
 }
 
 
@@ -5093,7 +5112,7 @@ add_exchange_to_bank (void *cls,
     tail = append_rh (rhc);
     tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK;
     tail->details.closing = closing;
-  }   /* end of 'while (0 < rows)' */
+  } /* end of 'while (0 < rows)' */
 }
 
 
@@ -5361,7 +5380,7 @@ postgres_get_ready_deposit (void *cls,
                             void *deposit_cb_cls)
 {
   struct PostgresClosure *pg = cls;
-  struct GNUNET_TIME_Absolute now;
+  struct GNUNET_TIME_Absolute now = {0};
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_absolute_time (&now),
     GNUNET_PQ_query_param_uint64 (&start_shard_row),
@@ -6260,7 +6279,7 @@ postgres_get_refresh_reveal (void *cls,
   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     goto cleanup;
   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
-  default:   /* can have more than one result */
+  default: /* can have more than one result */
     break;
   }
   switch (grctx.qs)
@@ -6269,7 +6288,7 @@ postgres_get_refresh_reveal (void *cls,
   case GNUNET_DB_STATUS_SOFT_ERROR:
     goto cleanup;
   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
-  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:   /* should be impossible */
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* should be impossible */
     break;
   }
 
@@ -11394,6 +11413,68 @@ postgres_delete_shard_locks (void *cls)
 }
 
 
+/**
+ * Function called to save the configuration of an extension
+ * (age-restriction, peer2peer, ...).  After succesfull storage of the
+ * configuration it triggers the corresponding event.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param extension_name the name of the extension
+ * @param config JSON object of the configuration as string
+ * @param config_sig signature of the configuration by the offline master key
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+postgres_set_extension_config (void *cls,
+                               const char *extension_name,
+                               const char *config,
+                               const struct TALER_MasterSignatureP *config_sig)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (extension_name),
+    GNUNET_PQ_query_param_string (config),
+    GNUNET_PQ_query_param_auto_from_type (config_sig),
+    GNUNET_PQ_query_param_end
+  };
+
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "set_extension_config",
+                                             params);
+}
+
+
+/**
+ * Function called to get the configuration of an extension
+ * (age-restriction, peer2peer, ...)
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param extension_name the name of the extension
+ * @param[out] config JSON object of the configuration as string
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+postgres_get_extension_config (void *cls,
+                               const char *extension_name,
+                               char **config)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_string (extension_name),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_string ("config", config),
+    GNUNET_PQ_result_spec_end
+  };
+
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "get_extension_config",
+                                                   params,
+                                                   rs);
+}
+
+
 /**
  * Initialize Postgres database subsystem.
  *
@@ -11628,6 +11709,10 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
     = &postgres_release_revolving_shard;
   plugin->delete_shard_locks
     = &postgres_delete_shard_locks;
+  plugin->set_extension_config
+    = &postgres_set_extension_config;
+  plugin->get_extension_config
+    = &postgres_get_extension_config;
   return plugin;
 }
 
diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h
index 4ffee54c..e608effa 100644
--- a/src/include/taler_crypto_lib.h
+++ b/src/include/taler_crypto_lib.h
@@ -542,6 +542,19 @@ struct TALER_PickupIdentifierP
 };
 
 
+/**
+ * @brief Salted hash over the JSON object representing the configuration of an
+ * extension.
+ */
+struct TALER_ExtensionConfigHash
+{
+  /**
+   * Actual hash value.
+   */
+  struct GNUNET_HashCode hash;
+};
+
+
 GNUNET_NETWORK_STRUCT_END
 
 
@@ -2521,30 +2534,31 @@ TALER_merchant_wire_signature_make (
 /* **************** /management/extensions offline signing **************** */
 
 /**
- * Create a signature for age restriction groups
+ * Create a signature for the hash of the configuration of an extension
  *
- * @param mask The bitmask representing age groups
+ * @param h_config hash of the JSON object representing the configuration
  * @param master_priv private key to sign with
  * @param[out] master_sig where to write the signature
  */
 void
-TALER_exchange_offline_extension_agemask_sign (
-  const struct TALER_AgeMask mask,
+TALER_exchange_offline_extension_config_hash_sign (
+  const struct TALER_ExtensionConfigHash h_config,
   const struct TALER_MasterPrivateKeyP *master_priv,
   struct TALER_MasterSignatureP *master_sig);
 
 
 /**
- * Verify the signature in @a master_sig.
+ * Verify the signature in @a master_sig of the given hash, taken over the JSON
+ * blob representing the configuration of an extension
  *
- * @param mask bit mask representing an age group for age restriction
+ * @param h_config hash of the JSON blob of a configuration of an extension
  * @param master_pub master public key of the exchange
  * @param master_sig signature of the exchange
  * @return #GNUNET_OK if signature is valid
  */
 enum GNUNET_GenericReturnValue
-TALER_exchange_offline_extension_agemask_verify (
-  const struct TALER_AgeMask mask,
+TALER_exchange_offline_extension_config_hash_verify (
+  const struct TALER_ExtensionConfigHash h_config,
   const struct TALER_MasterPublicKeyP *master_pub,
   const struct TALER_MasterSignatureP *master_sig
   );
diff --git a/src/include/taler_exchangedb_plugin.h 
b/src/include/taler_exchangedb_plugin.h
index ee691084..4aa80b67 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -4025,8 +4025,35 @@ struct TALER_EXCHANGEDB_Plugin
   (*delete_shard_locks)(void *cls);
 
   /**
-   * TODO-oec: add function for adding extension config
+   * Function called to save the configuration of an extension
+   * (age-restriction, peer2peer, ...)
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param extension_name the name of the extension
+   * @param config JSON object of the configuration as string
+   * @param config_sig signature of the configuration by the offline master key
+   * @return transaction status code
    */
+  enum GNUNET_DB_QueryStatus
+  (*set_extension_config)(void *cls,
+                          const char *extension_name,
+                          const char *config,
+                          const struct TALER_MasterSignatureP *config_sig);
+
+  /**
+   * Function called to retrieve the configuration of an extension
+   * (age-restriction, peer2peer, ...)
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param extension_name the name of the extension
+   * @param[out] config JSON object of the configuration as string
+   * @param[out] config_sig signature of the configuration by the master key
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_extension_config)(void *cls,
+                          const char *extension_name,
+                          char **config);
 
 };
 
diff --git a/src/include/taler_extensions.h b/src/include/taler_extensions.h
index b6d5c826..199776eb 100644
--- a/src/include/taler_extensions.h
+++ b/src/include/taler_extensions.h
@@ -23,6 +23,7 @@
 
 #include <gnunet/gnunet_util_lib.h>
 #include "taler_crypto_lib.h"
+#include "taler_json_lib.h"
 
 
 #define TALER_EXTENSION_SECTION_PREFIX "exchange-extension-"
@@ -39,22 +40,42 @@ enum TALER_Extension_Type
 {
   TALER_Extension_AgeRestriction = 0,
   TALER_Extension_Peer2Peer = 1,
-  TALER_Extension_Max = 2
+  TALER_Extension_Max = 2 // Must be last
 };
 
+/*
+ * TODO oec: documentation
+ */
 struct TALER_Extension
 {
   enum TALER_Extension_Type type;
   char *name;
   bool critical;
   void *config;
+
+  enum GNUNET_GenericReturnValue (*test_config)(const json_t *config);
+  enum GNUNET_GenericReturnValue (*parse_and_set_config)(struct
+                                                         TALER_Extension *this,
+                                                         const json_t *config);
+  json_t *(*config_to_json)(const struct TALER_Extension *this);
 };
 
-/*
- * TALER Peer2Peer Extension
- * FIXME oec
+/**
+ * Generic functions for extensions
  */
 
+/**
+ * Finds and returns a supported extension by a given name.
+ *
+ * @param name name of the extension to lookup
+ * @param extensions list of TALER_Extensions as haystack, terminated by an 
entry of type TALER_Extension_Max
+ * @param[out] ext set to the extension, if found, NULL otherwise
+ * @return GNUNET_OK if extension was found, GNUNET_NO otherwise
+ */
+enum GNUNET_GenericReturnValue
+TALER_extension_get_by_name (const char *name,
+                             const struct TALER_Extension **extensions,
+                             const struct TALER_Extension **ext);
 
 /*
  * TALER Age Restriction Extension
@@ -72,7 +93,19 @@ struct TALER_Extension
                                                 << 21)
 
 /**
- * @param groups String representation of age groups, like: 
"8:10:12:14:16:18:21"
+ * @brief Parses a string as a list of age groups.
+ *
+ * The string must consist of a colon-separated list of increasing integers
+ * between 0 and 31.  Each entry represents the beginning of a new age group.
+ * F.e. the string "8:10:12:14:16:18:21" parses into the following list of age
+ * groups
+ *   0-7, 8-9, 10-11, 12-13, 14-15, 16-17, 18-20, 21-...
+ * which then is represented as bit mask with the corresponding bits set:
+ *   31     24        16        8         0
+ *   |      |         |         |         |
+ *   oooooooo  oo1oo1o1  o1o1o1o1  ooooooo1
+ *
+ * @param groups String representation of age groups
  * @param[out] mask Mask representation for age restriction.
  * @return Error, if age groups were invalid, OK otherwise.
  */
@@ -81,6 +114,19 @@ TALER_parse_age_group_string (char *groups,
                               struct TALER_AgeMask *mask);
 
 /**
+ * Encodes the age mask into a string, like "8:10:12:14:16:18:21"
+ *
+ * @param mask Age mask
+ * @return String representation of the age mask, allocated by GNUNET_malloc.
+ *         Can be used as value in the TALER config.
+ */
+char *
+TALER_age_mask_to_string (const struct TALER_AgeMask *mask);
+
+
+/**
+ * @brief Reads the age groups from the configuration and sets the
+ * corresponding age mask.
  *
  * @param cfg
  * @param[out] mask for age restriction, will be set to 0 if age restriction 
is disabled.
@@ -90,4 +136,11 @@ TALER_parse_age_group_string (char *groups,
 enum TALER_Extension_ReturnValue
 TALER_get_age_mask (const struct GNUNET_CONFIGURATION_Handle *cfg,
                     struct TALER_AgeMask *mask);
+
+
+/*
+ * TALER Peer2Peer Extension
+ * TODO oec
+ */
+
 #endif
diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h
index ac8793eb..102b3a6f 100644
--- a/src/include/taler_json_lib.h
+++ b/src/include/taler_json_lib.h
@@ -532,7 +532,7 @@ TALER_JSON_wire_to_payto (const json_t *wire_s);
 
 
 /**
- * Hash @a extensions.
+ * Hash @a extensions in deposits.
  *
  * @param extensions contract extensions to hash
  * @param[out] ech where to write the extension hash
@@ -541,6 +541,16 @@ void
 TALER_deposit_extension_hash (const json_t *extensions,
                               struct TALER_ExtensionContractHash *ech);
 
+/**
+ * Hash the @a config of an extension, given as JSON
+ *
+ * @param config configuration of the extension
+ * @param[out] eh where to write the extension hash
+ * @return GNUNET_OK on success, GNUNET_SYSERR on failure
+ */
+enum GNUNET_GenericReturnValue
+TALER_extension_config_hash (const json_t *config,
+                             struct TALER_ExtensionConfigHash *eh);
 
 /**
  * Parses a JSON object { "extension": "age_restriction", "mask": <uint32> }.
@@ -553,7 +563,6 @@ enum GNUNET_GenericReturnValue
 TALER_agemask_parse_json (const json_t *root,
                           struct TALER_AgeMask *mask);
 
-
 #endif /* TALER_JSON_LIB_H_ */
 
 /* End of taler_json_lib.h */
diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h
index d9fa7065..947c7e83 100644
--- a/src/include/taler_signatures.h
+++ b/src/include/taler_signatures.h
@@ -967,9 +967,9 @@ struct TALER_MasterDelWirePS
 
 /*
  * @brief Signature made by the exchange offline key over the
- * configuration of the age restriction extension.
+ * configuration of an extension.
  */
-struct TALER_MasterExtensionAgeRestrictionPS
+struct TALER_MasterExtensionConfigurationPS
 {
   /**
    * Purpose is #TALER_SIGNATURE_MASTER_EXTENSION.   Signed
@@ -978,29 +978,11 @@ struct TALER_MasterExtensionAgeRestrictionPS
   struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
 
   /**
-   * Bit mask representing the lits of age groups, see TALER_AgeMask for a
-   * description.
+   * Hash of the JSON object that represents the configuration of an extension.
    */
-  struct TALER_AgeMask mask;
+  struct TALER_ExtensionConfigHash h_config GNUNET_PACKED;
 };
 
-#if 0
-/*
- * @brief Signature made by the exchange offline key over the
- * configuration of the peer2peer extension.
- */
-struct TALER_MasterExtensionPeer2PeerPS
-{
-  /**
-   * Purpose is #TALER_SIGNATURE_MASTER_EXTENSION.   Signed
-   * by a `struct TALER_MasterPublicKeyP` using EdDSA.
-   */
-  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
-
-  // TODO oec
-};
-#endif
-
 /**
  * @brief Information about a denomination key. Denomination keys
  * are used to sign coins of a certain value into existence.
diff --git a/src/json/json.c b/src/json/json.c
index af2b84e2..956aad1a 100644
--- a/src/json/json.c
+++ b/src/json/json.c
@@ -1009,4 +1009,14 @@ TALER_deposit_extension_hash (const json_t *extensions,
 }
 
 
+enum GNUNET_GenericReturnValue
+TALER_extension_config_hash (const json_t *config,
+                             struct TALER_ExtensionConfigHash *ech)
+{
+  return dump_and_hash (config,
+                        "taler-extension-configuration",
+                        &ech->hash);
+}
+
+
 /* End of json/json.c */
diff --git a/src/lib/exchange_api_management_post_extensions.c 
b/src/lib/exchange_api_management_post_extensions.c
index a2de2454..862ff711 100644
--- a/src/lib/exchange_api_management_post_extensions.c
+++ b/src/lib/exchange_api_management_post_extensions.c
@@ -153,32 +153,17 @@ TALER_EXCHANGE_management_post_extensions (
   GNUNET_assert (NULL != extensions);
   for (unsigned int i = 0; i<pkd->num_extensions; i++)
   {
-    json_t *config;
-    const struct TALER_AgeMask *mask;
+    const json_t *config;
     const struct TALER_Extension *ext = &pkd->extensions[i];
 
-    switch (ext->type)
-    {
-    // TODO: case TALER_Extension_Peer2Peer
-    case TALER_Extension_AgeRestriction:
-      mask = (const struct TALER_AgeMask *) (&ext->config);
-      config = GNUNET_JSON_PACK (
-        GNUNET_JSON_pack_string ("extension",
-                                 ext->name),
-        GNUNET_JSON_pack_data_auto ("mask",
-                                    &mask->mask));
-      GNUNET_assert (NULL != config);
-      break;
-    default:
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Extension not supported.\n");
-    }
+    config = ext->config_to_json (ext);
 
+    GNUNET_assert (NULL != config);
     GNUNET_assert (0 ==
                    json_array_append_new (
                      extensions,
                      GNUNET_JSON_PACK (
-                       GNUNET_JSON_pack_data_auto ("name",
+                       GNUNET_JSON_pack_data_auto ("extension",
                                                    &ext->name),
                        GNUNET_JSON_pack_data_auto ("config",
                                                    config)
diff --git a/src/util/Makefile.am b/src/util/Makefile.am
index cae1a205..55ebb4df 100644
--- a/src/util/Makefile.am
+++ b/src/util/Makefile.am
@@ -72,6 +72,7 @@ libtalerutil_la_SOURCES = \
   crypto_wire.c \
   denom.c \
   exchange_signatures.c \
+  extensions.c \
   extension_age_restriction.c \
   getopt.c \
   lang.c \
diff --git a/src/util/extension_age_restriction.c 
b/src/util/extension_age_restriction.c
index c0efd7cd..42a58b2e 100644
--- a/src/util/extension_age_restriction.c
+++ b/src/util/extension_age_restriction.c
@@ -23,7 +23,6 @@
 #include "taler_extensions.h"
 #include "stdint.h"
 
-
 /**
  *
  * @param cfg Handle to the GNUNET configuration
@@ -137,12 +136,14 @@ TALER_parse_age_group_string (char *groups,
 
 
 /**
+ * Encodes the age mask into a string, like "8:10:12:14:16:18:21"
+ *
  * @param mask Age mask
  * @return String representation of the age mask, allocated by GNUNET_malloc.
  *         Can be used as value in the TALER config.
  */
 char *
-TALER_age_mask_to_string (struct TALER_AgeMask *m)
+TALER_age_mask_to_string (const struct TALER_AgeMask *m)
 {
   uint32_t mask = m->mask;
   unsigned int n = 0;
diff --git a/src/util/extensions.c b/src/util/extensions.c
new file mode 100644
index 00000000..87dd16b4
--- /dev/null
+++ b/src/util/extensions.c
@@ -0,0 +1,49 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2014-2021 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 extensions.c
+ * @brief Utility functions for extensions
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_extensions.h"
+#include "stdint.h"
+
+enum GNUNET_GenericReturnValue
+TALER_extension_get_by_name (const char *name,
+                             const struct TALER_Extension **extensions,
+                             const struct TALER_Extension **ext)
+{
+
+  const struct TALER_Extension *it = *extensions;
+
+  for (; NULL != it; it++)
+  {
+    if (0 == strncmp (name,
+                      it->name,
+                      strlen (it->name)))
+    {
+      *ext = it;
+      return GNUNET_OK;
+    }
+  }
+
+  return GNUNET_NO;
+}
+
+
+/* end of extensions.c */
diff --git a/src/util/offline_signatures.c b/src/util/offline_signatures.c
index 7fbec826..1240a8bc 100644
--- a/src/util/offline_signatures.c
+++ b/src/util/offline_signatures.c
@@ -491,66 +491,40 @@ TALER_exchange_offline_wire_fee_verify (
 
 
 void
-TALER_exchange_offline_extension_agemask_sign (
-  const struct TALER_AgeMask mask,
+TALER_exchange_offline_extension_config_hash_sign (
+  const struct TALER_ExtensionConfigHash h_config,
   const struct TALER_MasterPrivateKeyP *master_priv,
   struct TALER_MasterSignatureP *master_sig)
 {
-  struct TALER_MasterExtensionAgeRestrictionPS ar = {
+  struct TALER_MasterExtensionConfigurationPS ec = {
     .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION),
-    .purpose.size = htonl (sizeof(ar)),
-    .mask = mask
+    .purpose.size = htonl (sizeof(ec)),
+    .h_config = h_config
   };
   GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
-                            &ar,
+                            &ec,
                             &master_sig->eddsa_signature);
 }
 
 
 enum GNUNET_GenericReturnValue
-TALER_exchange_offline_extension_agemask_verify (
-  const struct TALER_AgeMask mask,
+TALER_exchange_offline_extension_config_hash_verify (
+  const struct TALER_ExtensionConfigHash h_config,
   const struct TALER_MasterPublicKeyP *master_pub,
   const struct TALER_MasterSignatureP *master_sig
   )
 {
-  struct TALER_MasterExtensionAgeRestrictionPS ar = {
+  struct TALER_MasterExtensionConfigurationPS ec = {
     .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION),
-    .purpose.size = htonl (sizeof(ar)),
-    .mask = mask
+    .purpose.size = htonl (sizeof(ec)),
+    .h_config = h_config
   };
-  return
-    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_EXTENSION,
-                                &ar,
-                                &master_sig->eddsa_signature,
-                                &master_pub->eddsa_pub);
-}
-
 
-#if 0
-/* TODO peer2peer */
-void
-TALER_exchange_offline_extension_p2p_sign (
-  // TODO
-  const struct TALER_MasterPrivateKeyP *master_priv,
-  struct TALER_MasterSignatureP *master_sig)
-{
-  // TODO
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_exchange_offline_extension_p2p_verify (
-  // TODO
-  const struct TALER_MasterPublicKeyP *master_pub,
-  const struct TALER_MasterSignatureP *master_sig,
-  )
-{
-  // TODO
-  return GNUNET_FALSE;
+  return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_EXTENSION,
+                                     &ec,
+                                     &master_sig->eddsa_signature,
+                                     &master_pub->eddsa_pub);
 }
 
 
-#endif
-
 /* end of offline_signatures.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]