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 14/n


From: gnunet
Subject: [taler-exchange] branch master updated: [age restriction] progress 14/n - withdraw and deposit
Date: Wed, 16 Feb 2022 22:05:41 +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 8bdf6ab1 [age restriction] progress 14/n - withdraw and deposit
8bdf6ab1 is described below

commit 8bdf6ab19df70c16d335ecf82f2c3b2117eeb70e
Author: Özgür Kesim <oec-taler@kesim.org>
AuthorDate: Wed Feb 16 22:01:05 2022 +0100

    [age restriction] progress 14/n - withdraw and deposit
    
    Age restriction support for
      - withdraw is done and tested
      - deposit is done and tested
    
    TODOs:
      - melt/refresh/reveal
      - link
    
    ------
    
    Added functions
     - TALER_age_restriction_commit
     - TALER_age_commitment_derive
     - TALER_age_commitment_hash
     - TALER_age_restriction_commitment_free_inside
     - Hash of age commitment passed around API boundaries
    
    Exchangedb adjustments for denominations
     - all prepared statements re: denominations now handle age_mask
     - signature parameters adjusted
    
    Hash and signature verification of /keys adjusted
     - Hashes of (normal) denominations and age-restricted denominations are
       calculated seperately
     - The hash of the age-restricted ones will then be added to the other
       hash
     - The total hash is signed/verified
    
    Tests for withdraw with age restriction added
     - TALER_EXCHANGE_DenomPublickey now carries age_mask
     - TALER_TESTING_cmd_withdraw_amount* takes age parameter
     - TALER_TESTING_find_pk takes boolean age_restricted
     - WithdrawState carries age_commitment and its hash
     - withdraw_run derives new age commitment, if applicable
     - Added age parameter to testing (13 as example)
    
    Various Fixes and changes
     - Fixes of post handler for /management/extensions
     - Fixes for offline tool extensions signing
     - Slight refactoring of extensions
     - Age restriction extension simplified
       - config is now global to extension
       - added global TEH_age_restriction_enabled and TEH_age_mask in
         taler-exchange-httpd
       - helper functions and macros introduced
---
 src/auditor/taler-helper-auditor-coins.c           |   4 +
 src/benchmark/taler-aggregator-benchmark.c         |   2 +-
 src/benchmark/taler-exchange-benchmark.c           |   1 +
 src/exchange-tools/taler-exchange-offline.c        |  67 ++++-
 src/exchange/taler-exchange-httpd.c                |  12 +
 src/exchange/taler-exchange-httpd.h                |   6 +
 src/exchange/taler-exchange-httpd_db.c             |   2 +-
 src/exchange/taler-exchange-httpd_deposit.c        |   4 +
 src/exchange/taler-exchange-httpd_extensions.c     |  16 ++
 src/exchange/taler-exchange-httpd_keys.c           | 100 ++++---
 .../taler-exchange-httpd_management_extensions.c   | 110 ++++----
 .../taler-exchange-httpd_management_post_keys.c    |   2 +
 src/exchange/taler-exchange-httpd_melt.c           |   8 +
 src/exchange/taler-exchange-httpd_recoup-refresh.c |   2 +-
 src/exchange/taler-exchange-httpd_recoup.c         |   2 +-
 .../taler-exchange-httpd_refreshes_reveal.c        |  38 +++
 src/exchange/taler-exchange-httpd_responses.c      |  12 +
 src/exchangedb/exchange-0001.sql                   |   7 +-
 src/exchangedb/plugin_exchangedb_postgres.c        | 105 +++++--
 src/exchangedb/test_exchangedb.c                   |  13 +-
 src/extensions/extension_age_restriction.c         | 109 +++++++-
 src/extensions/extensions.c                        |  24 +-
 src/include/taler_crypto_lib.h                     | 213 ++++++++++++---
 src/include/taler_exchange_service.h               |  10 +-
 src/include/taler_exchangedb_plugin.h              |  28 +-
 src/include/taler_extensions.h                     |  78 +++++-
 src/include/taler_json_lib.h                       |  11 -
 src/include/taler_signatures.h                     |  18 ++
 src/include/taler_testing_lib.h                    |  37 ++-
 src/json/json_helper.c                             |  31 ---
 src/lib/exchange_api_common.c                      |  34 ++-
 src/lib/exchange_api_deposit.c                     |  24 +-
 src/lib/exchange_api_handle.c                      |  58 ++--
 src/lib/exchange_api_link.c                        |  12 +
 src/lib/exchange_api_management_get_keys.c         |   2 +-
 src/lib/exchange_api_management_post_extensions.c  |   4 +-
 src/lib/exchange_api_refresh_common.c              |   1 +
 src/lib/exchange_api_refresh_common.h              |  13 +
 src/lib/exchange_api_refreshes_reveal.c            |  11 +-
 src/lib/exchange_api_refund.c                      |   5 +
 src/lib/exchange_api_withdraw.c                    |  10 +
 src/testing/Makefile.am                            |   3 +
 src/testing/test_auditor_api.c                     |  24 +-
 src/testing/test_exchange_api-cs.conf              |  10 +-
 src/testing/test_exchange_api-rsa.conf             |  10 +-
 src/testing/test_exchange_api.c                    |  93 ++++++-
 src/testing/test_exchange_api_revocation.c         |   2 +
 src/testing/test_kyc_api.c                         |   4 +
 src/testing/testing_api_cmd_deposit.c              |  27 +-
 src/testing/testing_api_cmd_insert_deposit.c       |   2 +-
 .../testing_api_cmd_offline_sign_extensions.c      | 164 +++++++++++
 src/testing/testing_api_cmd_refresh.c              |  56 +++-
 src/testing/testing_api_cmd_withdraw.c             |  91 ++++++-
 src/testing/testing_api_helpers_exchange.c         |  13 +-
 src/util/crypto.c                                  | 302 ++++++++++++++++++++-
 src/util/denom.c                                   |   4 +-
 src/util/test_crypto.c                             |  10 +-
 src/util/test_helper_rsa.c                         |  11 +-
 src/util/wallet_signatures.c                       |  28 +-
 59 files changed, 1754 insertions(+), 346 deletions(-)

diff --git a/src/auditor/taler-helper-auditor-coins.c 
b/src/auditor/taler-helper-auditor-coins.c
index d425b9ea..2ed8e5a1 100644
--- a/src/auditor/taler-helper-auditor-coins.c
+++ b/src/auditor/taler-helper-auditor-coins.c
@@ -1224,6 +1224,7 @@ static int
 refresh_session_cb (void *cls,
                     uint64_t rowid,
                     const struct TALER_DenominationPublicKey *denom_pub,
+                    const struct TALER_AgeCommitmentHash *h_age_commitment,
                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
                     const struct TALER_CoinSpendSignatureP *coin_sig,
                     const struct TALER_Amount *amount_with_fee,
@@ -1286,6 +1287,7 @@ refresh_session_cb (void *cls,
                                   &fee_refresh,
                                   rc,
                                   &h_denom_pub,
+                                  h_age_commitment,
                                   coin_pub,
                                   coin_sig))
     {
@@ -1612,6 +1614,7 @@ deposit_cb (void *cls,
     struct TALER_MerchantWireHash h_wire;
     struct TALER_DenominationHash h_denom_pub;
     struct TALER_Amount deposit_fee;
+    struct TALER_AgeCommitmentHash *h_age_commitment = NULL; /* FIXME-oec */
 
     TALER_denom_pub_hash (denom_pub,
                           &h_denom_pub);
@@ -1628,6 +1631,7 @@ deposit_cb (void *cls,
                                      &deposit_fee,
                                      &h_wire,
                                      &deposit->h_contract_terms,
+                                     h_age_commitment, /* FIXME-oec */
                                      NULL /* h_extensions! */,
                                      &h_denom_pub,
                                      deposit->timestamp,
diff --git a/src/benchmark/taler-aggregator-benchmark.c 
b/src/benchmark/taler-aggregator-benchmark.c
index 005acfef..6452d6fc 100644
--- a/src/benchmark/taler-aggregator-benchmark.c
+++ b/src/benchmark/taler-aggregator-benchmark.c
@@ -300,7 +300,7 @@ add_deposit (const struct Merchant *m)
   struct TALER_EXCHANGEDB_Deposit deposit;
   uint64_t known_coin_id;
   struct TALER_DenominationHash dph;
-  struct TALER_AgeHash agh;
+  struct TALER_AgeCommitmentHash agh;
 
   RANDOMIZE (&d.coin.coin_pub);
   d.coin.denom_pub_hash = h_denom_pub;
diff --git a/src/benchmark/taler-exchange-benchmark.c 
b/src/benchmark/taler-exchange-benchmark.c
index 25c3b045..77ef94eb 100644
--- a/src/benchmark/taler-exchange-benchmark.c
+++ b/src/benchmark/taler-exchange-benchmark.c
@@ -366,6 +366,7 @@ run (void *cls,
           (TALER_TESTING_cmd_withdraw_amount (wl,
                                               create_reserve_label,
                                               amount_5,
+                                              0, /* age restriction off */
                                               MHD_HTTP_OK));
       unit[1] =
         TALER_TESTING_cmd_deposit_with_retry
diff --git a/src/exchange-tools/taler-exchange-offline.c 
b/src/exchange-tools/taler-exchange-offline.c
index c5c9584d..55720a1b 100644
--- a/src/exchange-tools/taler-exchange-offline.c
+++ b/src/exchange-tools/taler-exchange-offline.c
@@ -152,6 +152,10 @@ static char *currency;
  */
 static char *CFG_exchange_url;
 
+/**
+ * If age restriction is enabled, the age mask to be used
+ */
+static struct TALER_AgeMask age_mask = {0};
 
 /**
  * A subcommand supported by this program.
@@ -1924,6 +1928,7 @@ trigger_upload (const char *exchange_url)
       if (0 == strcasecmp (key,
                            uhs[i].key))
       {
+
         found = true;
         uhs[i].cb (exchange_url,
                    index,
@@ -3092,6 +3097,7 @@ do_show (char *const *args)
   keys = parse_keys_input ("show");
   if (NULL == keys)
     return;
+
   if (GNUNET_OK !=
       load_offline_key (GNUNET_NO))
     return;
@@ -3253,6 +3259,43 @@ sign_signkeys (const struct 
TALER_SecurityModulePublicKeyP *secm_pub,
 }
 
 
+/**
+ * Looks up the AGE_RESTRICTED setting for a denomination in the config and
+ * returns the age restriction (mask) accordingly.
+ *
+ * @param section_name Section in the configuration for the particular
+ *    denomination.
+ */
+static struct TALER_AgeMask
+load_age_mask (const char*section_name)
+{
+  static const struct TALER_AgeMask null_mask = {0};
+  enum GNUNET_GenericReturnValue ret;
+
+  if (age_mask.mask == 0)
+    return null_mask;
+
+  if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value (
+                      kcfg,
+                      section_name,
+                      "AGE_RESTRICTED")))
+    return null_mask;
+
+  ret = GNUNET_CONFIGURATION_get_value_yesno (kcfg,
+                                              section_name,
+                                              "AGE_RESTRICTED");
+  if (GNUNET_YES == ret)
+    return age_mask;
+
+  if (GNUNET_SYSERR == ret)
+    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                               section_name,
+                               "AGE_RESTRICTED",
+                               "Value must be YES or NO\n");
+  return null_mask;
+}
+
+
 /**
  * Sign @a denomkeys with offline key.
  *
@@ -3343,7 +3386,10 @@ sign_denomkeys (const struct 
TALER_SecurityModulePublicKeyP *secm_pub_rsa,
     duration = GNUNET_TIME_absolute_get_difference (
       stamp_start.abs_time,
       stamp_expire_withdraw.abs_time);
-    // FIXME-Oec: setup age mask here?
+
+    /* Load the age mask, if applicable to this denomination */
+    denom_pub.age_mask = load_age_mask (section_name);
+
     TALER_denom_pub_hash (&denom_pub,
                           &h_denom_pub);
     switch (denom_pub.cipher)
@@ -3604,14 +3650,6 @@ do_extensions_show (char *const *args)
   json_t *exts = json_object ();
   const struct TALER_Extension *it;
 
-  TALER_extensions_init ();
-  if (GNUNET_OK != TALER_extensions_load_taler_config (kcfg))
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "error while loading taler config for extensions\n");
-    return;
-  }
-
   for (it = TALER_extensions_get_head ();
        NULL != it;
        it = it->next)
@@ -3865,6 +3903,17 @@ run (void *cls,
     global_ret = EXIT_NOTCONFIGURED;
     return;
   }
+
+  /* load age mask, if age restriction is enabled */
+  TALER_extensions_init ();
+  if (GNUNET_OK != TALER_extensions_load_taler_config (kcfg))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "error while loading taler config for extensions\n");
+    return;
+  }
+  age_mask = TALER_extensions_age_restriction_ageMask ();
+
   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
                           &rc);
   rc = GNUNET_CURL_gnunet_rc_create (ctx);
diff --git a/src/exchange/taler-exchange-httpd.c 
b/src/exchange/taler-exchange-httpd.c
index 06ad7ca9..a0d0aa3b 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -127,6 +127,12 @@ char *TEH_currency;
  */
 char *TEH_base_url;
 
+/**
+ * Age restriction flags and mask
+ */
+bool TEH_age_restriction_enabled = false;
+struct TALER_AgeMask TEH_age_mask = {0};
+
 /**
  * Default timeout in seconds for HTTP requests.
  */
@@ -737,6 +743,12 @@ handle_post_management (struct TEH_RequestContext *rc,
     return TEH_handler_management_post_wire_fees (rc->connection,
                                                   root);
   }
+  if (0 == strcmp (args[0],
+                   "extensions"))
+  {
+    return TEH_handler_management_post_extensions (rc->connection,
+                                                   root);
+  }
   GNUNET_break_op (0);
   return r404 (rc->connection,
                "/management/*");
diff --git a/src/exchange/taler-exchange-httpd.h 
b/src/exchange/taler-exchange-httpd.h
index d3b1ba84..ffbce0e9 100644
--- a/src/exchange/taler-exchange-httpd.h
+++ b/src/exchange/taler-exchange-httpd.h
@@ -186,6 +186,12 @@ extern struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
  */
 extern char *TEH_currency;
 
+/*
+ * Age restriction extension state
+ */
+extern bool TEH_age_restriction_enabled;
+extern struct TALER_AgeMask TEH_age_mask;
+
 /**
  * Our (externally visible) base URL.
  */
diff --git a/src/exchange/taler-exchange-httpd_db.c 
b/src/exchange/taler-exchange-httpd_db.c
index 3600d793..f331e17d 100644
--- a/src/exchange/taler-exchange-httpd_db.c
+++ b/src/exchange/taler-exchange-httpd_db.c
@@ -50,7 +50,7 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
 {
   enum TALER_EXCHANGEDB_CoinKnownStatus cks;
   struct TALER_DenominationHash h_denom_pub;
-  struct TALER_AgeHash age_hash;
+  struct TALER_AgeCommitmentHash age_hash;
 
   /* make sure coin is 'known' in database */
   cks = TEH_plugin->ensure_coin_known (TEH_plugin->cls,
diff --git a/src/exchange/taler-exchange-httpd_deposit.c 
b/src/exchange/taler-exchange-httpd_deposit.c
index 053552f2..d750ec70 100644
--- a/src/exchange/taler-exchange-httpd_deposit.c
+++ b/src/exchange/taler-exchange-httpd_deposit.c
@@ -240,6 +240,9 @@ TEH_handler_deposit (struct MHD_Connection *connection,
                                  &deposit.merchant_pub),
     GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
                                  &deposit.h_contract_terms),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+                                   &deposit.coin.age_commitment_hash)),
     GNUNET_JSON_spec_fixed_auto ("coin_sig",
                                  &deposit.csig),
     GNUNET_JSON_spec_timestamp ("timestamp",
@@ -397,6 +400,7 @@ TEH_handler_deposit (struct MHD_Connection *connection,
                                    &deposit.deposit_fee,
                                    &h_wire,
                                    &deposit.h_contract_terms,
+                                   &deposit.coin.age_commitment_hash,
                                    NULL /* h_extensions! */,
                                    &deposit.coin.denom_pub_hash,
                                    deposit.timestamp,
diff --git a/src/exchange/taler-exchange-httpd_extensions.c 
b/src/exchange/taler-exchange-httpd_extensions.c
index 8edb24d4..6894a076 100644
--- a/src/exchange/taler-exchange-httpd_extensions.c
+++ b/src/exchange/taler-exchange-httpd_extensions.c
@@ -127,6 +127,16 @@ extension_update_event_cb (void *cls,
       GNUNET_break (0);
     }
   }
+
+  /* Special case age restriction: Update global flag and mask  */
+  if (TALER_Extension_AgeRestriction == type)
+  {
+    TEH_age_mask.mask = 0;
+    TEH_age_restriction_enabled =
+      TALER_extensions_age_restriction_is_enabled ();
+    if (TEH_age_restriction_enabled)
+      TEH_age_mask = TALER_extensions_age_restriction_ageMask ();
+  }
 }
 
 
@@ -151,6 +161,12 @@ TEH_extensions_init ()
     return GNUNET_SYSERR;
   }
 
+  /* FIXME: shall we load the extensions from the config right away?
+   * We do have to for now, as otherwise denominations with age restriction
+   * will not have the age mask set right upon initial generation.
+   */
+  TALER_extensions_load_taler_config (TEH_cfg);
+
   /* Trigger the initial load of configuration from the db */
   for (const struct TALER_Extension *it = TALER_extensions_get_head ();
        NULL != it->next;
diff --git a/src/exchange/taler-exchange-httpd_keys.c 
b/src/exchange/taler-exchange-httpd_keys.c
index 4b1a6213..d1dfb28b 100644
--- a/src/exchange/taler-exchange-httpd_keys.c
+++ b/src/exchange/taler-exchange-httpd_keys.c
@@ -795,27 +795,17 @@ static struct TALER_AgeMask
 load_age_mask (const char*section_name)
 {
   static const struct TALER_AgeMask null_mask = {0};
-  struct TALER_AgeMask age_mask = {0};
-  /* TODO: optimize by putting this into global? */
-  const struct TALER_Extension *age_ext =
-    TALER_extensions_get_by_type (TALER_Extension_AgeRestriction);
-
-  // Get the age mask from the extension, if configured
-  /* TODO: optimize by putting this into global? */
-  if (TALER_extensions_is_enabled (age_ext))
-    age_mask = *(struct TALER_AgeMask *) age_ext->config;
-  if (0 == age_mask.mask)
-  {
-    /* Age restriction support is not enabled.  Ignore the AGE_RESTRICTED field
-     * for the particular denomination and simply return the null_mask
-     */
+  struct TALER_AgeMask age_mask = TALER_extensions_age_restriction_ageMask ();
+
+  if (age_mask.mask == 0)
     return null_mask;
-  }
 
-  if (GNUNET_OK == (GNUNET_CONFIGURATION_have_value (
+  if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value (
                       TEH_cfg,
                       section_name,
                       "AGE_RESTRICTED")))
+    return null_mask;
+
   {
     enum GNUNET_GenericReturnValue ret;
 
@@ -1331,6 +1321,8 @@ denomination_info_cb (
   dk->meta = *meta;
   dk->master_sig = *master_sig;
   dk->recoup_possible = recoup_possible;
+  dk->denom_pub.age_mask = meta->age_mask;
+
   GNUNET_assert (
     GNUNET_OK ==
     GNUNET_CONTAINER_multihashmap_put (ksh->denomkey_map,
@@ -1745,7 +1737,7 @@ setup_general_response_headers (struct TEH_KeyStateHandle 
*ksh,
  * @a recoup and @a denoms.
  *
  * @param[in,out] ksh key state handle we build @a krd for
- * @param[in] denom_keys_hash hash over all the denominatoin keys in @a denoms
+ * @param[in] denom_keys_hash hash over all the denominatoin keys in @a denoms 
and age_restricted_denoms
  * @param last_cpd timestamp to use
  * @param signkeys list of sign keys to return
  * @param recoup list of revoked keys to return
@@ -1863,7 +1855,8 @@ create_krd (struct TEH_KeyStateHandle *ksh,
       int r;
 
       /* skip if not configured == disabled */
-      if (NULL == extension->config)
+      if (NULL == extension->config ||
+          NULL == extension->config_json)
         continue;
 
       /* flag our findings so far */
@@ -1899,7 +1892,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
       json_t *sig;
       int r;
 
-      r = json_object_set_new (
+      r = json_object_set (
         keys,
         "extensions",
         extensions);
@@ -1919,14 +1912,14 @@ create_krd (struct TEH_KeyStateHandle *ksh,
       json_decref (extensions);
     }
 
-    // Special case for age restrictions: if enabled, provide the lits of
+    // Special case for age restrictions: if enabled, provide the list of
     // age-restricted denominations.
     if (age_restriction_enabled &&
         NULL != age_restricted_denoms)
     {
       GNUNET_assert (
         0 ==
-        json_object_set_new (
+        json_object_set (
           keys,
           "age_restricted_denoms",
           age_restricted_denoms));
@@ -2005,7 +1998,9 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
   json_t *age_restricted_denoms = NULL;
   struct GNUNET_TIME_Timestamp last_cpd;
   struct GNUNET_CONTAINER_Heap *heap;
-  struct GNUNET_HashContext *hash_context;
+  struct GNUNET_HashContext *hash_context = NULL;
+  struct GNUNET_HashContext *hash_context_restricted = NULL;
+  bool have_age_restricted_denoms = false;
 
   sctx.signkeys = json_array ();
   GNUNET_assert (NULL != sctx.signkeys);
@@ -2030,19 +2025,23 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
       = GNUNET_TIME_relative_min (dkc.min_dk_frequency,
                                   sctx.min_sk_frequency);
   }
+
   denoms = json_array ();
   GNUNET_assert (NULL != denoms);
+  hash_context = GNUNET_CRYPTO_hash_context_start ();
 
-  // If age restriction is enabled, initialize the array of age restricted 
denoms.
-  /* TODO: optimize by putting this into global? */
-  if (TALER_extensions_is_enabled_type (TALER_Extension_AgeRestriction))
+  /* If age restriction is enabled, initialize the array of age restricted
+   denoms  and prepare a hash for them, separate from the others.  We will join
+   those hashes afterwards.*/
+  if (TEH_age_restriction_enabled)
   {
     age_restricted_denoms = json_array ();
     GNUNET_assert (NULL != age_restricted_denoms);
+    hash_context_restricted = GNUNET_CRYPTO_hash_context_start ();
   }
 
   last_cpd = GNUNET_TIME_UNIT_ZERO_TS;
-  hash_context = GNUNET_CRYPTO_hash_context_start ();
+
   {
     struct TEH_DenominationKey *dk;
 
@@ -2056,6 +2055,11 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
       {
         struct GNUNET_HashCode hc;
 
+        /* FIXME-oec: Do we need to take hash_context_restricted into account
+         * in this if-branch!?  Current tests suggests: no, (they don't fail).
+         * But something seems to be odd about only finishing hash_context.
+         */
+
         GNUNET_CRYPTO_hash_context_finish (
           GNUNET_CRYPTO_hash_context_copy (hash_context),
           &hc);
@@ -2084,14 +2088,14 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
           return GNUNET_SYSERR;
         }
       }
+
       last_cpd = dk->meta.start;
-      GNUNET_CRYPTO_hash_context_read (hash_context,
-                                       &dk->h_denom_pub,
-                                       sizeof (struct GNUNET_HashCode));
 
       {
         json_t *denom;
         json_t *array;
+        struct GNUNET_HashContext *hc;
+
 
         denom =
           GNUNET_JSON_PACK (
@@ -2118,18 +2122,26 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
             TALER_JSON_pack_amount ("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)
+        /* Put the denom into the correct array depending on the settings and
+         * the properties of the denomination.  Also, we build up the right
+         * hash for the corresponding array. */
+        if (TEH_age_restriction_enabled &&
+            (0 != dk->denom_pub.age_mask.mask))
         {
+          have_age_restricted_denoms = true;
           array = age_restricted_denoms;
+          hc = hash_context_restricted;
         }
         else
         {
           array = denoms;
+          hc = hash_context;
         }
 
+        GNUNET_CRYPTO_hash_context_read (hc,
+                                         &dk->h_denom_pub,
+                                         sizeof (struct GNUNET_HashCode));
+
         GNUNET_assert (
           0 ==
           json_array_append_new (
@@ -2138,13 +2150,27 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
       }
     }
   }
+
   GNUNET_CONTAINER_heap_destroy (heap);
   if (! GNUNET_TIME_absolute_is_zero (last_cpd.abs_time))
   {
     struct GNUNET_HashCode hc;
 
+    /* If age restriction is active and we had at least one denomination of
+     * that sort, we simply add the hash of all age restricted denominations at
+     * the end of the others. */
+    if (TEH_age_restriction_enabled && have_age_restricted_denoms)
+    {
+      struct GNUNET_HashCode hcr;
+      GNUNET_CRYPTO_hash_context_finish (hash_context_restricted, &hcr);
+      GNUNET_CRYPTO_hash_context_read (hash_context,
+                                       &hcr,
+                                       sizeof (struct GNUNET_HashCode));
+    }
+
     GNUNET_CRYPTO_hash_context_finish (hash_context,
                                        &hc);
+
     if (GNUNET_OK !=
         create_krd (ksh,
                     &hc,
@@ -2158,7 +2184,7 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
                   "Failed to generate key response data for %s\n",
                   GNUNET_TIME_timestamp2s (last_cpd));
       json_decref (denoms);
-      if (NULL != age_restricted_denoms)
+      if (TEH_age_restriction_enabled && NULL != age_restricted_denoms)
         json_decref (age_restricted_denoms);
       json_decref (sctx.signkeys);
       json_decref (recoup);
@@ -2849,7 +2875,9 @@ load_extension_data (const char *section_name,
                 TEH_currency);
     return GNUNET_SYSERR;
   }
-  meta->age_restrictions = load_age_mask (section_name);
+
+  meta->age_mask = load_age_mask (section_name);
+
   return GNUNET_OK;
 }
 
@@ -2976,7 +3004,7 @@ add_future_denomkey_cb (void *cls,
   struct FutureBuilderContext *fbc = cls;
   struct HelperDenomination *hd = value;
   struct TEH_DenominationKey *dk;
-  struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
+  struct TALER_EXCHANGEDB_DenominationKeyMetaData meta = {0};
 
   dk = GNUNET_CONTAINER_multihashmap_get (fbc->ksh->denomkey_map,
                                           h_denom_pub);
diff --git a/src/exchange/taler-exchange-httpd_management_extensions.c 
b/src/exchange/taler-exchange-httpd_management_extensions.c
index 17b00006..ab0287e3 100644
--- a/src/exchange/taler-exchange-httpd_management_extensions.c
+++ b/src/exchange/taler-exchange-httpd_management_extensions.c
@@ -31,7 +31,6 @@
 #include "taler_extensions.h"
 #include "taler_dbevents.h"
 
-
 /**
  * Extension carries the necessary data for a particular extension.
  *
@@ -91,6 +90,8 @@ set_extensions (void *cls,
       return GNUNET_DB_STATUS_HARD_ERROR;
     }
 
+    GNUNET_assert (NULL != ext->config);
+
     config = json_dumps (ext->config, JSON_COMPACT | JSON_SORT_KEYS);
     if (NULL == config)
     {
@@ -140,6 +141,57 @@ set_extensions (void *cls,
 }
 
 
+static enum GNUNET_GenericReturnValue
+verify_extensions_from_json (
+  json_t *extensions,
+  struct SetExtensionsContext *sec)
+{
+  const char*name;
+  const struct TALER_Extension *extension;
+  size_t i = 0;
+  json_t *blob;
+
+  GNUNET_assert (NULL != extensions);
+  GNUNET_assert (json_is_object (extensions));
+
+  sec->num_extensions = json_object_size (extensions);
+  sec->extensions = GNUNET_new_array (sec->num_extensions,
+                                      struct Extension);
+
+  json_object_foreach (extensions, name, blob)
+  {
+    int critical = 0;
+    json_t *config;
+    const char *version = NULL;
+
+    /* load and verify criticality, version, etc. */
+    extension = TALER_extensions_get_by_name (name);
+    if (NULL == extension)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "no such extension: %s\n", name);
+      return GNUNET_SYSERR;
+    }
+
+    if (GNUNET_OK !=
+        TALER_extensions_is_json_config (
+          blob, &critical, &version, &config))
+      return GNUNET_SYSERR;
+
+    if (critical != extension->critical
+        || 0 != strcmp (version, extension->version) // TODO: libtool compare?
+        || NULL == config
+        || GNUNET_OK != extension->test_json_config (config))
+      return GNUNET_SYSERR;
+
+    sec->extensions[i].type = extension->type;
+    sec->extensions[i].config = config;
+  }
+
+  return GNUNET_OK;
+}
+
+
 MHD_RESULT
 TEH_handler_management_post_extensions (
   struct MHD_Connection *connection,
@@ -204,57 +256,18 @@ TEH_handler_management_post_extensions (
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Received /management/extensions\n");
 
-  sec.num_extensions = json_object_size (extensions);
-  sec.extensions = GNUNET_new_array (sec.num_extensions,
-                                     struct Extension);
-
   /* Now parse individual extensions and signatures from those objects. */
+  if (GNUNET_OK !=
+      verify_extensions_from_json (extensions, &sec))
   {
-    const struct TALER_Extension *extension = NULL;
-    const char *name;
-    json_t *config;
-    int idx = 0;
-
-    json_object_foreach (extensions, name, config){
-
-      /* 1. Make sure name refers to a supported extension */
-      extension = TALER_extensions_get_by_name (name);
-      if (NULL == extension)
-      {
-        ret = TALER_MHD_reply_with_error (
-          connection,
-          MHD_HTTP_BAD_REQUEST,
-          TALER_EC_GENERIC_PARAMETER_MALFORMED,
-          "invalid extension type");
-        goto CLEANUP;
-      }
-
-      sec.extensions[idx].config = config;
-      sec.extensions[idx].type = extension->type;
-
-      /* 2. Make sure the config is sound */
-      if (GNUNET_OK !=
-          extension->test_json_config (
-            sec.extensions[idx].config))
-      {
-        ret = TALER_MHD_reply_with_error (
-          connection,
-          MHD_HTTP_BAD_REQUEST,
-          TALER_EC_GENERIC_PARAMETER_MALFORMED,
-          "invalid configuration for extension");
-        goto CLEANUP;
-      }
-
-      /* We have a validly signed JSON object for the extension.  Increment its
-       * refcount.
-       */
-      json_incref (sec.extensions[idx].config);
-      idx++;
-
-    } /* json_object_foreach */
+    GNUNET_JSON_parse_free (top_spec);
+    return TALER_MHD_reply_with_error (
+      connection,
+      MHD_HTTP_BAD_REQUEST,
+      TALER_EC_GENERIC_PARAMETER_MALFORMED,
+      "invalid object");
   }
 
-
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Received %u extensions\n",
               sec.num_extensions);
@@ -281,6 +294,7 @@ TEH_handler_management_post_extensions (
     NULL,
     0);
 
+
 CLEANUP:
   for (unsigned int i = 0; i < sec.num_extensions; i++)
   {
diff --git a/src/exchange/taler-exchange-httpd_management_post_keys.c 
b/src/exchange/taler-exchange-httpd_management_post_keys.c
index f0c3f1f3..c353a995 100644
--- a/src/exchange/taler-exchange-httpd_management_post_keys.c
+++ b/src/exchange/taler-exchange-httpd_management_post_keys.c
@@ -204,6 +204,7 @@ add_keys (void *cls,
         TALER_denom_pub_free (&denom_pub);
       return GNUNET_DB_STATUS_HARD_ERROR;
     }
+
     if (is_active)
     {
       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -211,6 +212,7 @@ add_keys (void *cls,
                   GNUNET_h2s (&d->h_denom_pub.hash));
       continue; /* skip, already known */
     }
+
     qs = TEH_plugin->add_denomination_key (
       TEH_plugin->cls,
       &d->h_denom_pub,
diff --git a/src/exchange/taler-exchange-httpd_melt.c 
b/src/exchange/taler-exchange-httpd_melt.c
index 021b629b..8bfdf8ce 100644
--- a/src/exchange/taler-exchange-httpd_melt.c
+++ b/src/exchange/taler-exchange-httpd_melt.c
@@ -279,6 +279,7 @@ check_melt_valid (struct MHD_Connection *connection,
     &mret);
   if (NULL == dk)
     return mret;
+
   if (GNUNET_TIME_absolute_is_past (dk->meta.expire_legal.abs_time))
   {
     /* Way too late now, even zombies have expired */
@@ -288,6 +289,7 @@ check_melt_valid (struct MHD_Connection *connection,
       TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
       "MELT");
   }
+
   if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
   {
     /* This denomination is not yet valid */
@@ -300,9 +302,11 @@ check_melt_valid (struct MHD_Connection *connection,
 
   rmc->coin_refresh_fee = dk->meta.fee_refresh;
   rmc->coin_value = dk->meta.value;
+
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Melted coin's denomination is worth %s\n",
               TALER_amount2s (&dk->meta.value));
+
   /* sanity-check that "total melt amount > melt fee" */
   if (0 <
       TALER_amount_cmp (&rmc->coin_refresh_fee,
@@ -332,6 +336,7 @@ check_melt_valid (struct MHD_Connection *connection,
                                 &rmc->coin_refresh_fee,
                                 &rmc->refresh_session.rc,
                                 &rmc->refresh_session.coin.denom_pub_hash,
+                                &rmc->refresh_session.coin.age_commitment_hash,
                                 &rmc->refresh_session.coin.coin_pub,
                                 &rmc->refresh_session.coin_sig))
   {
@@ -407,6 +412,9 @@ TEH_handler_melt (struct MHD_Connection *connection,
                                &rmc.refresh_session.coin.denom_sig),
     GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
                                  &rmc.refresh_session.coin.denom_pub_hash),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("age_commitment_hash",
+                                   
&rmc.refresh_session.coin.age_commitment_hash)),
     GNUNET_JSON_spec_fixed_auto ("confirm_sig",
                                  &rmc.refresh_session.coin_sig),
     TALER_JSON_spec_amount ("value_with_fee",
diff --git a/src/exchange/taler-exchange-httpd_recoup-refresh.c 
b/src/exchange/taler-exchange-httpd_recoup-refresh.c
index 829e2cbd..bbf6defe 100644
--- a/src/exchange/taler-exchange-httpd_recoup-refresh.c
+++ b/src/exchange/taler-exchange-httpd_recoup-refresh.c
@@ -251,7 +251,7 @@ verify_and_execute_recoup_refresh (
     if (GNUNET_OK !=
         TALER_denom_blind (&dk->denom_pub,
                            coin_bks,
-                           NULL, /* FIXME-Oec: TALER_AgeHash * */
+                           NULL, /* FIXME-Oec: TALER_AgeCommitmentHash * */
                            &coin->coin_pub,
                            exchange_vals,
                            &c_hash,
diff --git a/src/exchange/taler-exchange-httpd_recoup.c 
b/src/exchange/taler-exchange-httpd_recoup.c
index ea319d11..4ac997e9 100644
--- a/src/exchange/taler-exchange-httpd_recoup.c
+++ b/src/exchange/taler-exchange-httpd_recoup.c
@@ -256,7 +256,7 @@ verify_and_execute_recoup (
     if (GNUNET_OK !=
         TALER_denom_blind (&dk->denom_pub,
                            coin_bks,
-                           NULL, /* FIXME-Oec: TALER_AgeHash * */
+                           NULL, /* FIXME-Oec: TALER_AgeCommitmentHash * */
                            &coin->coin_pub,
                            exchange_vals,
                            &c_hash,
diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.c 
b/src/exchange/taler-exchange-httpd_refreshes_reveal.c
index 0d8f7bf9..1f0782aa 100644
--- a/src/exchange/taler-exchange-httpd_refreshes_reveal.c
+++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.c
@@ -281,6 +281,7 @@ check_commitment (struct RevealContext *rctx,
                                                  alg_value,
                                                  &bks,
                                                  &coin_priv,
+                                                 NULL, /* FIXME-Oec, struct 
TALER_AgeCommitmentHash * */
                                                  &c_hash,
                                                  &pd));
           if (TALER_DENOMINATION_CS == dk->cipher)
@@ -380,6 +381,7 @@ check_commitment (struct RevealContext *rctx,
  * @param rctx context for the operation, partially built at this time
  * @param link_sigs_json link signatures in JSON format
  * @param new_denoms_h_json requests for fresh coins to be created
+ * @param old_age_commitment_json age commitment that went into the 
withdrawal, maybe NULL
  * @param coin_evs envelopes of gamma-selected coins to be signed
  * @return MHD result code
  */
@@ -388,6 +390,7 @@ resolve_refreshes_reveal_denominations (struct 
MHD_Connection *connection,
                                         struct RevealContext *rctx,
                                         const json_t *link_sigs_json,
                                         const json_t *new_denoms_h_json,
+                                        const json_t *old_age_commitment_json,
                                         const json_t *coin_evs)
 {
   unsigned int num_fresh_coins = json_array_size (new_denoms_h_json);
@@ -412,6 +415,7 @@ resolve_refreshes_reveal_denominations (struct 
MHD_Connection *connection,
                                        TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
                                        NULL);
   }
+
   /* Parse denomination key hashes */
   for (unsigned int i = 0; i<num_fresh_coins; i++)
   {
@@ -537,6 +541,7 @@ resolve_refreshes_reveal_denominations (struct 
MHD_Connection *connection,
       goto cleanup;
     }
   }
+
   /* Parse link signatures array */
   for (unsigned int i = 0; i<num_fresh_coins; i++)
   {
@@ -554,6 +559,7 @@ resolve_refreshes_reveal_denominations (struct 
MHD_Connection *connection,
                                       -1);
     if (GNUNET_OK != res)
       return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+
     /* Check signature */
     if (GNUNET_OK !=
         TALER_wallet_link_verify (
@@ -561,6 +567,7 @@ resolve_refreshes_reveal_denominations (struct 
MHD_Connection *connection,
           &rctx->gamma_tp,
           &rrcs[i].coin_envelope_hash,
           &rctx->melt.session.coin.coin_pub,
+          NULL, // TODO-oec: calculate the correct h_age_commitment
           &rrcs[i].orig_coin_link_sig))
     {
       GNUNET_break_op (0);
@@ -592,6 +599,7 @@ resolve_refreshes_reveal_denominations (struct 
MHD_Connection *connection,
       goto cleanup;
     }
   }
+
   rctx->dks = dks;
   rctx->rcds = rcds;
   rctx->rrcs = rrcs;
@@ -604,6 +612,7 @@ resolve_refreshes_reveal_denominations (struct 
MHD_Connection *connection,
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Creating %u signatures\n",
               (unsigned int) rctx->num_fresh_coins);
+
   /* create fresh coin signatures */
   for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
   {
@@ -622,8 +631,10 @@ resolve_refreshes_reveal_denominations (struct 
MHD_Connection *connection,
       goto cleanup;
     }
   }
+
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Signatures ready, starting DB interaction\n");
+
   /* Persist operation result in DB */
   {
     enum GNUNET_DB_QueryStatus qs;
@@ -678,11 +689,18 @@ cleanup:
  * revealed information is valid then returns the signed refreshed
  * coins.
  *
+ * If the denomination has age restriction support, the array of EDDSA public
+ * keys, one for each age group that was activated during the withdrawal
+ * by the parent/ward, must be provided in old_age_commitment.  The hash of
+ * this array must be the same as the h_age_commitment of the persisted reveal
+ * request.
+ *
  * @param connection the MHD connection to handle
  * @param rctx context for the operation, partially built at this time
  * @param tp_json private transfer keys in JSON format
  * @param link_sigs_json link signatures in JSON format
  * @param new_denoms_h_json requests for fresh coins to be created
+ * @param old_age_commitment_json array of EDDSA public keys in JSON, used for 
age restriction, maybe NULL
  * @param coin_evs envelopes of gamma-selected coins to be signed
  * @return MHD result code
  */
@@ -692,6 +710,7 @@ handle_refreshes_reveal_json (struct MHD_Connection 
*connection,
                               const json_t *tp_json,
                               const json_t *link_sigs_json,
                               const json_t *new_denoms_h_json,
+                              const json_t *old_age_commitment_json,
                               const json_t *coin_evs)
 {
   unsigned int num_fresh_coins = json_array_size (new_denoms_h_json);
@@ -727,6 +746,19 @@ handle_refreshes_reveal_json (struct MHD_Connection 
*connection,
                                        "new_denoms/link_sigs");
   }
 
+  /* Sanity check of age commitment: If it was provided, it _must_ be an array
+   * of the size the # of age groups */
+  if (NULL != old_age_commitment_json
+      && TALER_extensions_age_restriction_num_groups () !=
+      json_array_size (old_age_commitment_json))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       
TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
+                                       "old_age_commitment");
+  }
+
   /* Parse transfer private keys array */
   for (unsigned int i = 0; i<num_tprivs; i++)
   {
@@ -750,6 +782,7 @@ handle_refreshes_reveal_json (struct MHD_Connection 
*connection,
                                                  rctx,
                                                  link_sigs_json,
                                                  new_denoms_h_json,
+                                                 old_age_commitment_json,
                                                  coin_evs);
 }
 
@@ -763,6 +796,7 @@ TEH_handler_reveal (struct TEH_RequestContext *rc,
   json_t *transfer_privs;
   json_t *link_sigs;
   json_t *new_denoms_h;
+  json_t *old_age_commitment = NULL;
   struct RevealContext rctx;
   struct GNUNET_JSON_Specification spec[] = {
     GNUNET_JSON_spec_fixed_auto ("transfer_pub",
@@ -775,6 +809,9 @@ TEH_handler_reveal (struct TEH_RequestContext *rc,
                            &coin_evs),
     GNUNET_JSON_spec_json ("new_denoms_h",
                            &new_denoms_h),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_json ("old_age_commitment",
+                             &old_age_commitment)),
     GNUNET_JSON_spec_end ()
   };
 
@@ -836,6 +873,7 @@ TEH_handler_reveal (struct TEH_RequestContext *rc,
                                         transfer_privs,
                                         link_sigs,
                                         new_denoms_h,
+                                        old_age_commitment,
                                         coin_evs);
     GNUNET_JSON_parse_free (spec);
     return res;
diff --git a/src/exchange/taler-exchange-httpd_responses.c 
b/src/exchange/taler-exchange-httpd_responses.c
index 55b23044..00f04717 100644
--- a/src/exchange/taler-exchange-httpd_responses.c
+++ b/src/exchange/taler-exchange-httpd_responses.c
@@ -73,6 +73,7 @@ TEH_RESPONSE_compile_transaction_history (
                                          &deposit->deposit_fee,
                                          &h_wire,
                                          &deposit->h_contract_terms,
+                                         NULL, /* h_age_commitment, FIXME-oec 
*/
                                          NULL /* h_extensions! */,
                                          &deposit->h_denom_pub,
                                          deposit->timestamp,
@@ -122,6 +123,7 @@ TEH_RESPONSE_compile_transaction_history (
       {
         const struct TALER_EXCHANGEDB_MeltListEntry *melt =
           pos->details.melt;
+        const struct TALER_AgeCommitmentHash *phac = NULL;
 
 #if ENABLE_SANITY_CHECKS
         if (GNUNET_OK !=
@@ -129,6 +131,7 @@ TEH_RESPONSE_compile_transaction_history (
                                       &melt->melt_fee,
                                       &melt->rc,
                                       &melt->h_denom_pub,
+                                      &melt->h_age_commitment,
                                       coin_pub,
                                       &melt->coin_sig))
         {
@@ -137,6 +140,12 @@ TEH_RESPONSE_compile_transaction_history (
           return NULL;
         }
 #endif
+
+        /* Age restriction is optional.  We communicate a NULL value to
+         * JSON_PACK below */
+        if (! TALER_AgeCommitmentHash_isNullOrZero (&melt->h_age_commitment))
+          phac = &melt->h_age_commitment;
+
         if (0 !=
             json_array_append_new (
               history,
@@ -151,6 +160,9 @@ TEH_RESPONSE_compile_transaction_history (
                                             &melt->rc),
                 GNUNET_JSON_pack_data_auto ("h_denom_pub",
                                             &melt->h_denom_pub),
+                GNUNET_JSON_pack_allow_null (
+                  GNUNET_JSON_pack_data_auto ("h_age_commitment",
+                                              phac)),
                 GNUNET_JSON_pack_data_auto ("coin_sig",
                                             &melt->coin_sig))))
         {
diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql
index 1111f381..df07e025 100644
--- a/src/exchangedb/exchange-0001.sql
+++ b/src/exchangedb/exchange-0001.sql
@@ -25,7 +25,7 @@ CREATE TABLE IF NOT EXISTS denominations
   (denominations_serial BIGSERIAL UNIQUE
   ,denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64)
   ,denom_type INT4 NOT NULL DEFAULT (1) -- 1 == RSA (for now, remove default 
later!)
-  ,age_restrictions INT4 NOT NULL DEFAULT (0)
+  ,age_mask INT4 NOT NULL DEFAULT (0)
   ,denom_pub BYTEA NOT NULL
   ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
   ,valid_from INT8 NOT NULL
@@ -47,7 +47,7 @@ COMMENT ON TABLE denominations
   IS 'Main denominations table. All the valid denominations the exchange knows 
about.';
 COMMENT ON COLUMN denominations.denom_type
   IS 'determines cipher type for blind signatures used with this denomination; 
0 is for RSA';
-COMMENT ON COLUMN denominations.age_restrictions
+COMMENT ON COLUMN denominations.age_mask
   IS 'bitmask with the age restrictions that are being used for this 
denomination; 0 if denomination does not support the use of age restrictions';
 COMMENT ON COLUMN denominations.denominations_serial
   IS 'needed for exchange-auditor replication logic';
@@ -345,6 +345,7 @@ CREATE TABLE IF NOT EXISTS refresh_commitments
   (melt_serial_id BIGSERIAL -- UNIQUE
   ,rc BYTEA PRIMARY KEY CHECK (LENGTH(rc)=64)
   ,old_coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE 
CASCADE
+  ,h_age_commitment BYTEA CHECK(LENGTH(h_age_commitment)=32)
   ,old_coin_sig BYTEA NOT NULL CHECK(LENGTH(old_coin_sig)=64)
   ,amount_with_fee_val INT8 NOT NULL
   ,amount_with_fee_frac INT4 NOT NULL
@@ -359,6 +360,8 @@ COMMENT ON COLUMN refresh_commitments.rc
   IS 'Commitment made by the client, hash over the various client inputs in 
the cut-and-choose protocol';
 COMMENT ON COLUMN refresh_commitments.old_coin_pub
   IS 'Coin being melted in the refresh process.';
+COMMENT ON COLUMN refresh_commitments.h_age_commitment
+  IS '(optional) age commitment that was involved in the minting process of 
the coin, may be NULL.';
 CREATE TABLE IF NOT EXISTS refresh_commitments_default
   PARTITION OF refresh_commitments
   FOR VALUES WITH (MODULUS 1, REMAINDER 0);
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c 
b/src/exchangedb/plugin_exchangedb_postgres.c
index 98724fa0..878c36f9 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -231,10 +231,11 @@ prepare_statements (struct PostgresClosure *pg)
       ",fee_refresh_frac"
       ",fee_refund_val"
       ",fee_refund_frac"
+      ",age_mask"
       ") VALUES "
       "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
-      " $11, $12, $13, $14, $15, $16, $17);",
-      17),
+      " $11, $12, $13, $14, $15, $16, $17, $18);",
+      18),
     /* Used in #postgres_iterate_denomination_info() */
     GNUNET_PQ_make_prepare (
       "denomination_iterate",
@@ -255,6 +256,7 @@ prepare_statements (struct PostgresClosure *pg)
       ",fee_refund_val"
       ",fee_refund_frac"
       ",denom_pub"
+      ",age_mask"
       " FROM denominations;",
       0),
     /* Used in #postgres_iterate_denominations() */
@@ -278,6 +280,7 @@ prepare_statements (struct PostgresClosure *pg)
       ",fee_refund_val"
       ",fee_refund_frac"
       ",denom_pub"
+      ",age_mask"
       " FROM denominations"
       " LEFT JOIN "
       "   denomination_revocations USING (denominations_serial);",
@@ -341,6 +344,7 @@ prepare_statements (struct PostgresClosure *pg)
       ",fee_refresh_frac"
       ",fee_refund_val"
       ",fee_refund_frac"
+      ",age_mask"
       " FROM denominations"
       " WHERE denom_pub_hash=$1;",
       1),
@@ -825,6 +829,7 @@ prepare_statements (struct PostgresClosure *pg)
       ",denoms.fee_refresh_frac"
       ",old_coin_pub"
       ",old_coin_sig"
+      ",h_age_commitment"
       ",amount_with_fee_val"
       ",amount_with_fee_frac"
       ",noreveal_index"
@@ -843,6 +848,7 @@ prepare_statements (struct PostgresClosure *pg)
       "SELECT"
       " denom.denom_pub"
       ",kc.coin_pub AS old_coin_pub"
+      ",h_age_commitment"
       ",old_coin_sig"
       ",amount_with_fee_val"
       ",amount_with_fee_frac"
@@ -1842,6 +1848,7 @@ prepare_statements (struct PostgresClosure *pg)
       ",fee_refresh_frac"
       ",fee_refund_val"
       ",fee_refund_frac"
+      ",age_mask"
       " FROM denominations"
       " WHERE denom_pub_hash=$1;",
       1),
@@ -2069,7 +2076,6 @@ prepare_statements (struct PostgresClosure *pg)
       "SELECT"
       " denominations_serial AS serial"
       ",denom_type"
-      ",age_restrictions"
       ",denom_pub"
       ",master_sig"
       ",valid_from"
@@ -2086,6 +2092,7 @@ prepare_statements (struct PostgresClosure *pg)
       ",fee_refresh_frac"
       ",fee_refund_val"
       ",fee_refund_frac"
+      ",age_mask"
       " FROM denominations"
       " WHERE denominations_serial > $1"
       " ORDER BY denominations_serial ASC;",
@@ -2389,10 +2396,11 @@ prepare_statements (struct PostgresClosure *pg)
       ",fee_refresh_frac"
       ",fee_refund_val"
       ",fee_refund_frac"
+      ",age_mask"
       ") VALUES "
       "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
-      " $11, $12, $13, $14, $15, $16, $17, $18);",
-      18),
+      " $11, $12, $13, $14, $15, $16, $17, $18, $19);",
+      19),
     GNUNET_PQ_make_prepare (
       "insert_into_table_denomination_revocations",
       "INSERT INTO denomination_revocations"
@@ -3096,9 +3104,12 @@ postgres_insert_denomination_info (
     TALER_PQ_query_param_amount_nbo (&issue->properties.fee_deposit),
     TALER_PQ_query_param_amount_nbo (&issue->properties.fee_refresh),
     TALER_PQ_query_param_amount_nbo (&issue->properties.fee_refund),
+    GNUNET_PQ_query_param_uint32 (&denom_pub->age_mask.mask),
     GNUNET_PQ_query_param_end
   };
 
+  GNUNET_assert (denom_pub->age_mask.mask == issue->age_mask.mask);
+
   GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
                    GNUNET_TIME_timestamp_ntoh (
                      issue->properties.start).abs_time));
@@ -3172,6 +3183,8 @@ postgres_get_denomination_info (
                                      &issue->properties.fee_refresh),
     TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refund",
                                      &issue->properties.fee_refund),
+    GNUNET_PQ_result_spec_uint32 ("age_mask",
+                                  &issue->age_mask.mask),
     GNUNET_PQ_result_spec_end
   };
 
@@ -3258,12 +3271,15 @@ domination_cb_helper (void *cls,
                                        &issue.properties.fee_refund),
       TALER_PQ_result_spec_denom_pub ("denom_pub",
                                       &denom_pub),
+      GNUNET_PQ_result_spec_uint32 ("age_mask",
+                                    &issue.age_mask.mask),
       GNUNET_PQ_result_spec_end
     };
 
     memset (&issue.properties.master,
             0,
             sizeof (issue.properties.master));
+
     if (GNUNET_OK !=
         GNUNET_PQ_extract_result (result,
                                   rs,
@@ -3272,6 +3288,13 @@ domination_cb_helper (void *cls,
       GNUNET_break (0);
       return;
     }
+
+    /* Unfortunately we have to carry the age mask in both, the
+     * TALER_DenominationPublicKey and
+     * TALER_EXCHANGEDB_DenominationKeyInformationP at different times.
+     * Here we use _both_ so let's make sure the values are the same. */
+    denom_pub.age_mask = issue.age_mask;
+
     issue.properties.purpose.size
       = htonl (sizeof (struct TALER_DenominationKeyValidityPS));
     issue.properties.purpose.purpose
@@ -3357,10 +3380,10 @@ dominations_cb_helper (void *cls,
 
   for (unsigned int i = 0; i<num_results; i++)
   {
-    struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
-    struct TALER_DenominationPublicKey denom_pub;
-    struct TALER_MasterSignatureP master_sig;
-    struct TALER_DenominationHash h_denom_pub;
+    struct TALER_EXCHANGEDB_DenominationKeyMetaData meta = {0};
+    struct TALER_DenominationPublicKey denom_pub = {0};
+    struct TALER_MasterSignatureP master_sig = {0};
+    struct TALER_DenominationHash h_denom_pub = {0};
     bool revoked;
     struct GNUNET_PQ_ResultSpec rs[] = {
       GNUNET_PQ_result_spec_auto_from_type ("master_sig",
@@ -3387,6 +3410,8 @@ dominations_cb_helper (void *cls,
                                    &meta.fee_refund),
       TALER_PQ_result_spec_denom_pub ("denom_pub",
                                       &denom_pub),
+      GNUNET_PQ_result_spec_uint32 ("age_mask",
+                                    &meta.age_mask.mask),
       GNUNET_PQ_result_spec_end
     };
 
@@ -3398,6 +3423,10 @@ dominations_cb_helper (void *cls,
       GNUNET_break (0);
       return;
     }
+
+    /* make sure the mask information is the same */
+    denom_pub.age_mask = meta.age_mask;
+
     TALER_denom_pub_hash (&denom_pub,
                           &h_denom_pub);
     dic->cb (dic->cb_cls,
@@ -5741,11 +5770,13 @@ postgres_ensure_coin_known (void *cls,
                             const struct TALER_CoinPublicInfo *coin,
                             uint64_t *known_coin_id,
                             struct TALER_DenominationHash *denom_hash,
-                            struct TALER_AgeHash *age_hash)
+                            struct TALER_AgeCommitmentHash *age_hash)
 {
   struct PostgresClosure *pg = cls;
   enum GNUNET_DB_QueryStatus qs;
   bool existed;
+  bool is_denom_pub_hash_null = false;
+  bool is_age_hash_null = false;
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_auto_from_type (&coin->coin_pub),
     GNUNET_PQ_query_param_auto_from_type (&coin->denom_pub_hash),
@@ -5753,24 +5784,22 @@ postgres_ensure_coin_known (void *cls,
     TALER_PQ_query_param_denom_sig (&coin->denom_sig),
     GNUNET_PQ_query_param_end
   };
-  bool is_null = false;
   struct GNUNET_PQ_ResultSpec rs[] = {
     GNUNET_PQ_result_spec_bool ("existed",
                                 &existed),
     GNUNET_PQ_result_spec_uint64 ("known_coin_id",
                                   known_coin_id),
-    GNUNET_PQ_result_spec_allow_null (
-      GNUNET_PQ_result_spec_auto_from_type ("age_hash",
-                                            age_hash),
-      &is_null),
     GNUNET_PQ_result_spec_allow_null (
       GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
                                             denom_hash),
-      &is_null),
+      &is_denom_pub_hash_null),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("age_hash",
+                                            age_hash),
+      &is_age_hash_null),
     GNUNET_PQ_result_spec_end
   };
 
-  GNUNET_break (GNUNET_is_zero (&coin->age_commitment_hash)); // FIXME-OEC
   qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
                                                  "insert_known_coin",
                                                  params,
@@ -5790,21 +5819,24 @@ postgres_ensure_coin_known (void *cls,
       return TALER_EXCHANGEDB_CKS_ADDED;
     break; /* continued below */
   }
-  if ( (! is_null) &&
-       (0 != GNUNET_memcmp (age_hash,
-                            &coin->age_commitment_hash)) )
+
+  if ( (! is_denom_pub_hash_null) &&
+       (0 != GNUNET_memcmp (&denom_hash->hash,
+                            &coin->denom_pub_hash.hash)) )
   {
-    GNUNET_break (GNUNET_is_zero (age_hash)); // FIXME-OEC
     GNUNET_break_op (0);
-    return TALER_EXCHANGEDB_CKS_AGE_CONFLICT;
+    return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT;
   }
-  if ( (! is_null) &&
-       (0 != GNUNET_memcmp (denom_hash,
-                            &coin->denom_pub_hash)) )
+
+  if ( (! is_age_hash_null) &&
+       (0 != GNUNET_memcmp (age_hash,
+                            &coin->age_commitment_hash)) )
   {
+    GNUNET_break (GNUNET_is_zero (age_hash));
     GNUNET_break_op (0);
-    return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT;
+    return TALER_EXCHANGEDB_CKS_AGE_CONFLICT;
   }
+
   return TALER_EXCHANGEDB_CKS_PRESENT;
 }
 
@@ -6030,6 +6062,7 @@ postgres_get_melt (void *cls,
                    uint64_t *melt_serial_id)
 {
   struct PostgresClosure *pg = cls;
+  bool h_age_commitment_is_null;
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_auto_from_type (rc),
     GNUNET_PQ_query_param_end
@@ -6046,6 +6079,10 @@ postgres_get_melt (void *cls,
                                           &melt->session.coin.coin_pub),
     GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
                                           &melt->session.coin_sig),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("h_age_commitment",
+                                            &melt->session.h_age_commitment),
+      &h_age_commitment_is_null),
     TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
                                  &melt->session.amount_with_fee),
     GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
@@ -6061,6 +6098,11 @@ postgres_get_melt (void *cls,
                                                  "get_melt",
                                                  params,
                                                  rs);
+  if (h_age_commitment_is_null)
+    memset (&melt->session.h_age_commitment,
+            0,
+            sizeof(melt->session.h_age_commitment));
+
   melt->session.rc = *rc;
   return qs;
 }
@@ -8225,6 +8267,8 @@ refreshs_serial_helper_cb (void *cls,
     struct TALER_DenominationPublicKey denom_pub;
     struct TALER_CoinSpendPublicKeyP coin_pub;
     struct TALER_CoinSpendSignatureP coin_sig;
+    struct TALER_AgeCommitmentHash h_age_commitment;
+    bool ac_isnull;
     struct TALER_Amount amount_with_fee;
     uint32_t noreveal_index;
     uint64_t rowid;
@@ -8232,6 +8276,10 @@ refreshs_serial_helper_cb (void *cls,
     struct GNUNET_PQ_ResultSpec rs[] = {
       TALER_PQ_result_spec_denom_pub ("denom_pub",
                                       &denom_pub),
+      GNUNET_PQ_result_spec_allow_null (
+        GNUNET_PQ_result_spec_auto_from_type ("h_age_commitment",
+                                              &h_age_commitment),
+        &ac_isnull),
       GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
                                             &coin_pub),
       GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
@@ -8257,9 +8305,11 @@ refreshs_serial_helper_cb (void *cls,
       rsc->status = GNUNET_SYSERR;
       return;
     }
+
     ret = rsc->cb (rsc->cb_cls,
                    rowid,
                    &denom_pub,
+                   ac_isnull ? NULL : &h_age_commitment,
                    &coin_pub,
                    &coin_sig,
                    &amount_with_fee,
@@ -10198,6 +10248,8 @@ postgres_lookup_denomination_key (
                                  &meta->fee_refresh),
     TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
                                  &meta->fee_refund),
+    GNUNET_PQ_result_spec_uint32 ("age_mask",
+                                  &meta->age_mask.mask),
     GNUNET_PQ_result_spec_end
   };
 
@@ -10241,6 +10293,7 @@ postgres_add_denomination_key (
     TALER_PQ_query_param_amount (&meta->fee_deposit),
     TALER_PQ_query_param_amount (&meta->fee_refresh),
     TALER_PQ_query_param_amount (&meta->fee_refund),
+    GNUNET_PQ_query_param_uint32 (&meta->age_mask.mask),
     GNUNET_PQ_query_param_end
   };
 
diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c
index 0622e069..f9e64fdc 100644
--- a/src/exchangedb/test_exchangedb.c
+++ b/src/exchangedb/test_exchangedb.c
@@ -458,6 +458,7 @@ static unsigned int auditor_row_cnt;
  * @param cls closure
  * @param rowid unique serial ID for the refresh session in our DB
  * @param denom_pub denomination of the @a coin_pub
+ * @param h_age_commitment hash of age commitment that went into the minting, 
may be NULL
  * @param coin_pub public key of the coin
  * @param coin_sig signature from the coin
  * @param amount_with_fee amount that was deposited including fee
@@ -470,6 +471,8 @@ static enum GNUNET_GenericReturnValue
 audit_refresh_session_cb (void *cls,
                           uint64_t rowid,
                           const struct TALER_DenominationPublicKey *denom_pub,
+                          const struct
+                          TALER_AgeCommitmentHash *h_age_commitment,
                           const struct TALER_CoinSpendPublicKeyP *coin_pub,
                           const struct TALER_CoinSpendSignatureP *coin_sig,
                           const struct TALER_Amount *amount_with_fee,
@@ -1475,8 +1478,8 @@ run (void *cls)
   {
     struct TALER_PlanchetDetail pd;
     struct TALER_CoinSpendPublicKeyP coin_pub;
-    struct TALER_AgeHash age_hash;
-    struct TALER_AgeHash *p_ah[2] = {
+    struct TALER_AgeCommitmentHash age_hash;
+    struct TALER_AgeCommitmentHash *p_ah[2] = {
       NULL,
       &age_hash
     };
@@ -1597,7 +1600,7 @@ run (void *cls)
   deadline = GNUNET_TIME_timestamp_get ();
   {
     struct TALER_DenominationHash dph;
-    struct TALER_AgeHash agh;
+    struct TALER_AgeCommitmentHash agh;
 
     FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
             plugin->ensure_coin_known (plugin->cls,
@@ -1847,7 +1850,7 @@ run (void *cls)
     uint64_t new_known_coin_id;
     struct TALER_CoinPublicInfo new_coin;
     struct TALER_DenominationHash dph;
-    struct TALER_AgeHash agh;
+    struct TALER_AgeCommitmentHash agh;
     bool recoup_ok;
     bool internal_failure;
 
@@ -2201,7 +2204,7 @@ run (void *cls)
   {
     uint64_t known_coin_id;
     struct TALER_DenominationHash dph;
-    struct TALER_AgeHash agh;
+    struct TALER_AgeCommitmentHash agh;
 
     FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
             plugin->ensure_coin_known (plugin->cls,
diff --git a/src/extensions/extension_age_restriction.c 
b/src/extensions/extension_age_restriction.c
index a9ffb7f1..28b2dbb1 100644
--- a/src/extensions/extension_age_restriction.c
+++ b/src/extensions/extension_age_restriction.c
@@ -23,6 +23,19 @@
 #include "taler_extensions.h"
 #include "stdint.h"
 
+/**
+ * Carries all the information we need for age restriction
+ */
+struct age_restriction_config
+{
+  struct TALER_AgeMask mask;
+  size_t num_groups;
+};
+
+/**
+ * Global config for this extension
+ */
+static struct age_restriction_config _config = {0};
 
 /**
  * @param groups String representation of the age groups. Must be of the form
@@ -146,6 +159,9 @@ age_restriction_disable (
     json_decref (this->config_json);
     this->config_json = NULL;
   }
+
+  _config.mask.mask = 0;
+  _config.num_groups = 0;
 }
 
 
@@ -197,7 +213,6 @@ age_restriction_load_taler_config (
 
 
   mask.mask = TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK;
-
   ret = GNUNET_OK;
 
   if (groups != NULL)
@@ -208,7 +223,19 @@ age_restriction_load_taler_config (
   }
 
   if (GNUNET_OK == ret)
-    this->config = (void *) (size_t) mask.mask;
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "setting age mask to %x with #groups: %d\n", mask.mask,
+                __builtin_popcount (mask.mask) - 1);
+    _config.mask.mask = mask.mask;
+    _config.num_groups = __builtin_popcount (mask.mask) - 1; /* no underflow, 
first bit always set */
+    this->config = &_config;
+
+    /* Note: we do now have _config set, however this->config_json is NOT set,
+     * i.e. the extension is not yet active! For age restriction to become
+     * active, load_json_config must have been called. */
+  }
+
 
   GNUNET_free (groups);
   return ret;
@@ -223,12 +250,12 @@ age_restriction_load_taler_config (
 static enum GNUNET_GenericReturnValue
 age_restriction_load_json_config (
   struct TALER_Extension *this,
-  json_t *config)
+  json_t *jconfig)
 {
   struct TALER_AgeMask mask = {0};
   enum GNUNET_GenericReturnValue ret;
 
-  ret = TALER_JSON_parse_agemask (config, &mask);
+  ret = TALER_JSON_parse_age_groups (jconfig, &mask);
   if (GNUNET_OK != ret)
     return ret;
 
@@ -239,16 +266,28 @@ age_restriction_load_json_config (
   if (TALER_Extension_AgeRestriction != this->type)
     return GNUNET_SYSERR;
 
-  if (NULL != this->config)
-    GNUNET_free (this->config);
+  _config.mask.mask = mask.mask;
+  _config.num_groups = 0;
+
+  if (mask.mask > 0)
+  {
+    /* if the mask is not zero, the first bit MUST be set */
+    if (0 == (mask.mask & 1))
+      return GNUNET_SYSERR;
+
+    _config.num_groups = __builtin_popcount (mask.mask) - 1;
+  }
 
-  this->config = GNUNET_malloc (sizeof(struct TALER_AgeMask));
-  GNUNET_memcpy (this->config, &mask, sizeof(struct TALER_AgeMask));
+  this->config = &_config;
 
   if (NULL != this->config_json)
     json_decref (this->config_json);
 
-  this->config_json = config;
+  this->config_json = jconfig;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+              "loaded new age restriction config with age groups: %s\n",
+              TALER_age_mask_to_string (&mask));
 
   return GNUNET_OK;
 }
@@ -263,7 +302,6 @@ json_t *
 age_restriction_config_to_json (
   const struct TALER_Extension *this)
 {
-  struct TALER_AgeMask mask;
   char *mask_str;
   json_t *conf;
 
@@ -275,8 +313,7 @@ age_restriction_config_to_json (
     return json_copy (this->config_json);
   }
 
-  mask.mask = (uint32_t) (size_t) this->config;
-  mask_str = TALER_age_mask_to_string (&mask);
+  mask_str = TALER_age_mask_to_string (&_config.mask);
   conf = GNUNET_JSON_PACK (
     GNUNET_JSON_pack_string ("age_groups", mask_str)
     );
@@ -298,7 +335,7 @@ age_restriction_test_json_config (
 {
   struct TALER_AgeMask mask = {0};
 
-  return TALER_JSON_parse_agemask (config, &mask);
+  return TALER_JSON_parse_age_groups (config, &mask);
 }
 
 
@@ -318,4 +355,50 @@ struct TALER_Extension _extension_age_restriction = {
   .load_taler_config = &age_restriction_load_taler_config,
 };
 
+bool
+TALER_extensions_age_restriction_is_configured ()
+{
+  return (0 != _config.mask.mask);
+}
+
+
+struct TALER_AgeMask
+TALER_extensions_age_restriction_ageMask ()
+{
+  return _config.mask;
+}
+
+
+size_t
+TALER_extensions_age_restriction_num_groups ()
+{
+  return _config.num_groups;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_parse_age_groups (const json_t *root,
+                             struct TALER_AgeMask *mask)
+{
+  enum GNUNET_GenericReturnValue ret;
+  const char *str;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_string ("age_groups",
+                             &str),
+    GNUNET_JSON_spec_end ()
+  };
+
+  ret = GNUNET_JSON_parse (root,
+                           spec,
+                           NULL,
+                           NULL);
+  if (GNUNET_OK == ret)
+    TALER_parse_age_group_string (str, mask);
+
+  GNUNET_JSON_parse_free (spec);
+
+  return ret;
+}
+
+
 /* end of extension_age_restriction.c */
diff --git a/src/extensions/extensions.c b/src/extensions/extensions.c
index 55d970c5..516c56a4 100644
--- a/src/extensions/extensions.c
+++ b/src/extensions/extensions.c
@@ -247,27 +247,31 @@ TALER_extensions_load_taler_config (
 }
 
 
-static enum GNUNET_GenericReturnValue
-is_json_extension_config (
+enum GNUNET_GenericReturnValue
+TALER_extensions_is_json_config (
   json_t *obj,
   int *critical,
   const char **version,
   json_t **config)
 {
   enum GNUNET_GenericReturnValue ret;
+  json_t *cfg;
   struct GNUNET_JSON_Specification spec[] = {
     GNUNET_JSON_spec_boolean ("critical",
                               critical),
     GNUNET_JSON_spec_string ("version",
                              version),
     GNUNET_JSON_spec_json ("config",
-                           config),
+                           &cfg),
     GNUNET_JSON_spec_end ()
   };
 
   ret = GNUNET_JSON_parse (obj, spec, NULL, NULL);
   if (GNUNET_OK == ret)
+  {
+    *config = json_copy (cfg);
     GNUNET_JSON_parse_free (spec);
+  }
 
   return ret;
 }
@@ -300,7 +304,7 @@ TALER_extensions_load_json_config (
 
     /* load and verify criticality, version, etc. */
     if (GNUNET_OK !=
-        is_json_extension_config (
+        TALER_extensions_is_json_config (
           blob, &critical, &version, &config))
       return GNUNET_SYSERR;
 
@@ -330,4 +334,16 @@ TALER_extensions_load_json_config (
 }
 
 
+bool
+TALER_extensions_age_restriction_is_enabled ()
+{
+  const struct TALER_Extension *age =
+    TALER_extensions_get_by_type (TALER_Extension_AgeRestriction);
+
+  return (NULL != age &&
+          NULL != age->config_json &&
+          TALER_extensions_age_restriction_is_configured ());
+}
+
+
 /* end of extensions.c */
diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h
index ab5202ba..9bbf29de 100644
--- a/src/include/taler_crypto_lib.h
+++ b/src/include/taler_crypto_lib.h
@@ -306,37 +306,6 @@ struct TALER_MasterSignatureP
   struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
 };
 
-/*
- * @brief Type of a list of age groups, represented as bit mask.
- *
- * The bits set in the mask mark the edges at the beginning of a next age
- * group.  F.e. for the age groups
- *     0-7, 8-9, 10-11, 12-14, 14-15, 16-17, 18-21, 21-*
- * the following bits are set:
- *
- *   31     24        16        8         0
- *   |      |         |         |         |
- *   oooooooo  oo1oo1o1  o1o1o1o1  ooooooo1
- *
- * A value of 0 means that the exchange does not support the extension for
- * age-restriction.
- */
-struct TALER_AgeMask
-{
-  uint32_t mask;
-};
-
-/**
- * @brief Age restriction commitment of a coin.
- */
-struct TALER_AgeHash
-{
-  /**
-   * The commitment is a SHA-256 hash code.
-   */
-  struct GNUNET_ShortHashCode shash;
-};
-
 
 /**
  * @brief Type of public keys for Taler coins.  The same key material is used
@@ -364,6 +333,29 @@ struct TALER_CoinSpendPrivateKeyP
   struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
 };
 
+/**
+ * @brief Type of private keys for age commitment in coins.
+ */
+struct TALER_AgeCommitmentPrivateKeyP
+{
+  /**
+   * Taler uses EdDSA for coins when signing age verification attestation.
+   */
+  struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+
+/**
+ * @brief Type of public keys for age commitment in coins.
+ */
+struct TALER_AgeCommitmentPublicKeyP
+{
+  /**
+   * Taler uses EdDSA for coins when signing age verification attestation.
+   */
+  struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+};
+
 
 /**
  * @brief Type of signatures made with Taler coins.
@@ -765,6 +757,46 @@ struct TALER_BlindedDenominationSignature
 
 };
 
+/* *************** Age Restriction *********************************** */
+
+/*
+ * @brief Type of a list of age groups, represented as bit mask.
+ *
+ * The bits set in the mask mark the edges at the beginning of a next age
+ * group.  F.e. for the age groups
+ *     0-7, 8-9, 10-11, 12-14, 14-15, 16-17, 18-21, 21-*
+ * the following bits are set:
+ *
+ *   31     24        16        8         0
+ *   |      |         |         |         |
+ *   oooooooo  oo1oo1o1  o1o1o1o1  ooooooo1
+ *
+ * A value of 0 means that the exchange does not support the extension for
+ * age-restriction.
+ */
+struct TALER_AgeMask
+{
+  uint32_t mask;
+};
+
+/**
+ * @brief Age commitment of a coin.
+ */
+struct TALER_AgeCommitmentHash
+{
+  /**
+   * The commitment is a SHA-256 hash code.
+   */
+  struct GNUNET_ShortHashCode shash;
+};
+
+extern const struct TALER_AgeCommitmentHash TALER_ZeroAgeCommitmentHash;
+#define TALER_AgeCommitmentHash_isNullOrZero(ph) ((NULL == ph) || \
+                                                  (0 == memcmp (ph, \
+                                                                & \
+                                                                
TALER_ZeroAgeCommitmentHash, \
+                                                                sizeof(struct \
+                                                                       
TALER_AgeCommitmentHash))))
 
 /**
  * @brief Type of public signing keys for verifying blindly signed coins.
@@ -944,9 +976,10 @@ struct TALER_CoinPublicInfo
   struct TALER_DenominationHash denom_pub_hash;
 
   /**
-   * Hash of the age commitment.
+   * Hash of the age commitment.  If no age commitment was provided, it must be
+   * set to all zeroes.
    */
-  struct TALER_AgeHash age_commitment_hash;
+  struct TALER_AgeCommitmentHash age_commitment_hash;
 
   /**
    * (Unblinded) signature over @e coin_pub with @e denom_pub,
@@ -1117,7 +1150,7 @@ TALER_denom_sig_free (struct TALER_DenominationSignature 
*denom_sig);
 enum GNUNET_GenericReturnValue
 TALER_denom_blind (const struct TALER_DenominationPublicKey *dk,
                    const union TALER_DenominationBlindingKeyP *coin_bks,
-                   const struct TALER_AgeHash *age_commitment_hash,
+                   const struct TALER_AgeCommitmentHash *age_commitment_hash,
                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
                    const struct TALER_ExchangeWithdrawValues *alg_values,
                    struct TALER_CoinPubHash *c_hash,
@@ -1349,7 +1382,7 @@ TALER_withdraw_request_hash (
  */
 void
 TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                     const struct TALER_AgeHash *age_commitment_hash,
+                     const struct TALER_AgeCommitmentHash *age_commitment_hash,
                      struct TALER_CoinPubHash *coin_h);
 
 
@@ -1402,8 +1435,9 @@ struct TALER_FreshCoin
   struct TALER_CoinSpendPrivateKeyP coin_priv;
 
   /**
-   * FIXME-Oec: Age-verification vector, as pointer: Dyn alloc!
+   * Optional hash of an age commitment bound to this coin, maybe NULL.
    */
+  const struct TALER_AgeCommitmentHash *h_age_commitment;
 };
 
 
@@ -1571,6 +1605,7 @@ TALER_planchet_blinding_secret_create (
  * @param alg_values algorithm specific values
  * @param bks blinding secrets
  * @param coin_priv coin private key
+ * @param ach hash of age commitment to bind to this coin, maybe NULL
  * @param[out] c_hash set to the hash of the public key of the coin (needed 
later)
  * @param[out] pd set to the planchet detail for TALER_MERCHANT_tip_pickup() 
and
  *               other withdraw operations, `pd->blinded_planchet.cipher` will 
be set
@@ -1582,6 +1617,7 @@ TALER_planchet_prepare (const struct 
TALER_DenominationPublicKey *dk,
                         const struct TALER_ExchangeWithdrawValues *alg_values,
                         const union TALER_DenominationBlindingKeyP *bks,
                         const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+                        const struct TALER_AgeCommitmentHash *ach,
                         struct TALER_CoinPubHash *c_hash,
                         struct TALER_PlanchetDetail *pd);
 
@@ -1613,6 +1649,7 @@ TALER_planchet_detail_free (struct TALER_PlanchetDetail 
*pd);
  * @param blind_sig blind signature from the exchange
  * @param bks blinding key secret
  * @param coin_priv private key of the coin
+ * @param ach hash of age commitment that is bound to this coin, maybe NULL
  * @param c_hash hash of the coin's public key for verification of the 
signature
  * @param alg_values values obtained from the exchange for the withdrawal
  * @param[out] coin set to the details of the fresh coin
@@ -1624,6 +1661,7 @@ TALER_planchet_to_coin (
   const struct TALER_BlindedDenominationSignature *blind_sig,
   const union TALER_DenominationBlindingKeyP *bks,
   const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  const struct TALER_AgeCommitmentHash *ach,
   const struct TALER_CoinPubHash *c_hash,
   const struct TALER_ExchangeWithdrawValues *alg_values,
   struct TALER_FreshCoin *coin);
@@ -2202,6 +2240,7 @@ TALER_wallet_account_setup_sign (
  * @param deposit_fee the deposit fee we expect to pay
  * @param h_wire hash of the merchant’s account details
  * @param h_contract_terms hash of the contact of the merchant with the 
customer (further details are never disclosed to the exchange)
+ * @param h_age_commitment hash over the age commitment, if applicable to the 
denomination (maybe NULL)
  * @param h_extensions hash over the extensions
  * @param h_denom_pub hash of the coin denomination's public key
  * @param coin_priv coin’s private key
@@ -2216,6 +2255,7 @@ TALER_wallet_deposit_sign (
   const struct TALER_Amount *deposit_fee,
   const struct TALER_MerchantWireHash *h_wire,
   const struct TALER_PrivateContractHash *h_contract_terms,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
   const struct TALER_ExtensionContractHash *h_extensions,
   const struct TALER_DenominationHash *h_denom_pub,
   struct GNUNET_TIME_Timestamp wallet_timestamp,
@@ -2232,6 +2272,7 @@ TALER_wallet_deposit_sign (
  * @param deposit_fee the deposit fee we expect to pay
  * @param h_wire hash of the merchant’s account details
  * @param h_contract_terms hash of the contact of the merchant with the 
customer (further details are never disclosed to the exchange)
+ * @param h_age_commitment hash over the age commitment (maybe all zeroes, if 
not applicable to the denomination)
  * @param h_extensions hash over the extensions
  * @param h_denom_pub hash of the coin denomination's public key
  * @param wallet_timestamp timestamp when the contract was finalized, must not 
be too far in the future
@@ -2247,6 +2288,7 @@ TALER_wallet_deposit_verify (
   const struct TALER_Amount *deposit_fee,
   const struct TALER_MerchantWireHash *h_wire,
   const struct TALER_PrivateContractHash *h_contract_terms,
+  const struct TALER_AgeCommitmentHash *h_commitment_hash,
   const struct TALER_ExtensionContractHash *h_extensions,
   const struct TALER_DenominationHash *h_denom_pub,
   struct GNUNET_TIME_Timestamp wallet_timestamp,
@@ -2283,6 +2325,7 @@ TALER_wallet_melt_sign (
  * @param melt_fee the melt fee we expect to pay
  * @param rc refresh session we are committed to
  * @param h_denom_pub hash of the coin denomination's public key
+ * @param h_age_commitment hash of the age commitment (may be NULL)
  * @param coin_pub coin’s public key
  * @param coin_sig the signature made with purpose 
#TALER_SIGNATURE_WALLET_COIN_MELT
  * @return #GNUNET_OK if the signature is valid
@@ -2293,6 +2336,7 @@ TALER_wallet_melt_verify (
   const struct TALER_Amount *melt_fee,
   const struct TALER_RefreshCommitmentP *rc,
   const struct TALER_DenominationHash *h_denom_pub,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
   const struct TALER_CoinSpendPublicKeyP *coin_pub,
   const struct TALER_CoinSpendSignatureP *coin_sig);
 
@@ -2321,6 +2365,7 @@ TALER_wallet_link_sign (const struct 
TALER_DenominationHash *h_denom_pub,
  * @param transfer_pub transfer public key
  * @param h_coin_ev hash of the coin envelope
  * @param old_coin_pub old coin key that the link signature is for
+ * @param h_age_commitment hash of age commitment. Maybe NULL, if not 
applicable.
  * @param coin_sig resulting signature
  * @return #GNUNET_OK if the signature is valid
  */
@@ -2330,6 +2375,7 @@ TALER_wallet_link_verify (
   const struct TALER_TransferPublicKeyP *transfer_pub,
   const struct TALER_BlindedCoinHash *h_coin_ev,
   const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
   const struct TALER_CoinSpendSignatureP *coin_sig);
 
 
@@ -3149,5 +3195,100 @@ TALER_exchange_offline_extension_config_hash_verify (
   const struct TALER_MasterSignatureP *master_sig
   );
 
+/*
+ * @brief Representation of an age commitment:  one public key per age group.
+ *
+ * The number of keys must be be the same as the number of bits set in the
+ * corresponding age mask.
+ */
+struct TALER_AgeCommitment
+{
+
+  /* The age mask defines the age groups that were a parameter during the
+   * generation of this age commitment */
+  struct TALER_AgeMask mask;
+
+  /* The number of public keys, which must be the same as the number of
+   * groups in the mask.
+   */
+  size_t num_pub;
+
+  /* The list of #num_pub public keys.  In must have same size as the number of
+   * age groups defined in the mask.
+   *
+   * A hash of this list is the hashed commitment that goes into FDC
+   * calculation during the withdraw and refresh operations for new coins. That
+   * way, the particular age commitment becomes mandatory and bound to a coin.
+   *
+   * The list has been allocated via GNUNET_malloc.
+   */
+  struct TALER_AgeCommitmentPublicKeyP *pub;
+
+  /* The number of private keys, which must be at most num_pub_keys.  One minus
+   * this number corresponds to the largest age group that is supported with
+   * this age commitment.
+   */
+  size_t num_priv;
+
+  /* List of #num_priv private keys.
+   *
+   * Note that the list can be _smaller_ than the corresponding list of public
+   * keys. In that case, the wallet can sign off only for a subset of the age
+   * groups.
+   *
+   * The list has been allocated via GNUNET_malloc.
+   */
+  struct TALER_AgeCommitmentPrivateKeyP *priv;
+};
+
+/*
+ * @brief Generates a hash of the public keys in the age commitment.
+ *
+ * @param commitment the age commitment - one public key per age group
+ * @param[out] hash resulting hash
+ */
+void
+TALER_age_commitment_hash (
+  const struct TALER_AgeCommitment *commitment,
+  struct TALER_AgeCommitmentHash *hash);
+
+/*
+ * @brief Generates an age commitent for the given age.
+ *
+ * @param mask The age mask the defines the age groups
+ * @param age The actual age for which an age commitment is generated
+ * @param seed The seed that goes into the key generation.  MUST be choosen 
uniformly random.
+ * @param commitment[out] The generated age commitment, ->priv and ->pub 
allocated via GNUNET_malloc on success
+ * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
+ */
+enum GNUNET_GenericReturnValue
+TALER_age_restriction_commit (
+  const struct TALER_AgeMask *mask,
+  const uint8_t age,
+  const uint32_t seed,
+  struct TALER_AgeCommitment *commitment);
+
+/*
+ * @brief Derives another, equivalent age commitment for a given one.
+ *
+ * @param orig Original age commitment
+ * @param seed Used to move the points on the elliptic curve in order to 
generate another, equivalent commitment.
+ * @param derived[out] The resulting age commitment, ->priv and ->pub 
allocated via GNUNET_malloc on success.
+ * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
+ */
+enum GNUNET_GenericReturnValue
+TALER_age_commitment_derive (
+  const struct TALER_AgeCommitment *orig,
+  const uint32_t seed,
+  struct TALER_AgeCommitment *derived);
+
+/*
+ * @brief helper function to free memory inside a struct TALER_AgeCommitment
+ * @param cmt the commitment from which internal memory should be freed.  Note
+ * that cmt itself is NOT freed!
+ */
+void
+TALER_age_restriction_commitment_free_inside (
+  struct TALER_AgeCommitment *cmt);
 
 #endif
diff --git a/src/include/taler_exchange_service.h 
b/src/include/taler_exchange_service.h
index 8c1b4bde..fef09f72 100644
--- a/src/include/taler_exchange_service.h
+++ b/src/include/taler_exchange_service.h
@@ -159,11 +159,6 @@ struct TALER_EXCHANGE_DenomPublicKey
    * revoked by the exchange.
    */
   bool revoked;
-
-  /**
-   * Is the denomination age-restricted?
-   */
-  bool age_restricted;
 };
 
 
@@ -785,6 +780,7 @@ TALER_EXCHANGE_wire_cancel (struct 
TALER_EXCHANGE_WireHandle *wh);
  * @param h_extensions hash over the extensions
  * @param h_denom_pub hash of the coin denomination's public key
  * @param coin_priv coin’s private key
+ * @param age_commitment age commitment that went into the making of the coin, 
might be NULL
  * @param wallet_timestamp timestamp when the contract was finalized, must not 
be too far in the future
  * @param merchant_pub the public key of the merchant (used to identify the 
merchant for refund requests)
  * @param refund_deadline date until which the merchant can issue a refund to 
the customer via the exchange (can be zero if refunds are not allowed); must 
not be after the @a wire_deadline
@@ -799,6 +795,7 @@ TALER_EXCHANGE_deposit_permission_sign (
   const struct TALER_ExtensionContractHash *h_extensions,
   const struct TALER_DenominationHash *h_denom_pub,
   const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  const struct TALER_AgeCommitment *age_commitment,
   struct GNUNET_TIME_Timestamp wallet_timestamp,
   const struct TALER_MerchantPublicKeyP *merchant_pub,
   struct GNUNET_TIME_Timestamp refund_deadline,
@@ -924,6 +921,7 @@ TALER_EXCHANGE_deposit (
   const char *merchant_payto_uri,
   const struct TALER_WireSaltP *wire_salt,
   const struct TALER_PrivateContractHash *h_contract_terms,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
   const json_t *extension_details,
   const struct TALER_CoinSpendPublicKeyP *coin_pub,
   const struct TALER_DenominationSignature *denom_sig,
@@ -1496,6 +1494,7 @@ typedef void
  * @param reserve_priv private key of the reserve to withdraw from
  * @param ps secrets of the planchet
  *        caller must have committed this value to disk before the call (with 
@a pk)
+ * @param ach hash of the age commitment that should be bound to this coin. 
Maybe NULL.
  * @param res_cb the callback to call when the final result for this request 
is available
  * @param res_cb_cls closure for @a res_cb
  * @return NULL
@@ -1508,6 +1507,7 @@ TALER_EXCHANGE_withdraw (
   const struct TALER_EXCHANGE_DenomPublicKey *pk,
   const struct TALER_ReservePrivateKeyP *reserve_priv,
   const struct TALER_PlanchetMasterSecretP *ps,
+  const struct TALER_AgeCommitmentHash *ach,
   TALER_EXCHANGE_WithdrawCallback res_cb,
   void *res_cb_cls);
 
diff --git a/src/include/taler_exchangedb_plugin.h 
b/src/include/taler_exchangedb_plugin.h
index ec647e9c..f0a6f8bd 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -70,6 +70,12 @@ struct TALER_EXCHANGEDB_DenominationKeyInformationP
    * Signed properties of the denomination key.
    */
   struct TALER_DenominationKeyValidityPS properties;
+
+  /**
+   * If denomination was setup for age restriction, non-zero age mask.
+   * Note that the mask is not part of the signature.
+   */
+  struct TALER_AgeMask age_mask;
 };
 
 
@@ -295,7 +301,7 @@ struct TALER_EXCHANGEDB_TableData
     struct
     {
       struct TALER_CoinSpendPublicKeyP coin_pub;
-      struct TALER_AgeHash age_hash;
+      struct TALER_AgeCommitmentHash age_hash;
       uint64_t denominations_serial;
       struct TALER_DenominationSignature denom_sig;
     } known_coins;
@@ -644,7 +650,7 @@ struct TALER_EXCHANGEDB_DenominationKeyMetaData
    * A value of 0 means that the denomination does not support the extension 
for
    * age-restriction.
    */
-  struct TALER_AgeMask age_restrictions;
+  struct TALER_AgeMask age_mask;
 };
 
 
@@ -1261,6 +1267,13 @@ struct TALER_EXCHANGEDB_Refresh
    */
   struct TALER_CoinSpendSignatureP coin_sig;
 
+  /**
+   * Hash of the age commitment used to sign the coin, if age restriction was
+   * applicable to the denomination.  May be all zeroes if no age restriction
+   * applies.
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment;
+
   /**
    * Refresh commitment this coin is melted into.
    */
@@ -1306,6 +1319,13 @@ struct TALER_EXCHANGEDB_MeltListEntry
    */
   struct TALER_DenominationHash h_denom_pub;
 
+  /**
+   * Hash of the age commitment used to sign the coin, if age restriction was
+   * applicable to the denomination.  May be all zeroes if no age restriction
+   * applies.
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment;
+
   /**
    * How much value is being melted?  This amount includes the fees,
    * so the final amount contributed to the melt is this value minus
@@ -1606,6 +1626,7 @@ typedef enum GNUNET_GenericReturnValue
  * @param cls closure
  * @param rowid unique serial ID for the refresh session in our DB
  * @param denom_pub denomination public key of @a coin_pub
+ * @param h_age_commitment age commitment that went into the signing of the 
coin, may be NULL
  * @param coin_pub public key of the coin
  * @param coin_sig signature from the coin
  * @param amount_with_fee amount that was deposited including fee
@@ -1618,6 +1639,7 @@ typedef enum GNUNET_GenericReturnValue
   void *cls,
   uint64_t rowid,
   const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
   const struct TALER_CoinSpendPublicKeyP *coin_pub,
   const struct TALER_CoinSpendSignatureP *coin_sig,
   const struct TALER_Amount *amount_with_fee,
@@ -2758,7 +2780,7 @@ struct TALER_EXCHANGEDB_Plugin
                        const struct TALER_CoinPublicInfo *coin,
                        uint64_t *known_coin_id,
                        struct TALER_DenominationHash *denom_pub_hash,
-                       struct TALER_AgeHash *age_hash);
+                       struct TALER_AgeCommitmentHash *age_hash);
 
 
   /**
diff --git a/src/include/taler_extensions.h b/src/include/taler_extensions.h
index f00f3ed5..b7b93e17 100644
--- a/src/include/taler_extensions.h
+++ b/src/include/taler_extensions.h
@@ -85,6 +85,31 @@ enum GNUNET_GenericReturnValue
 TALER_extensions_load_taler_config (
   const struct GNUNET_CONFIGURATION_Handle *cfg);
 
+/*
+ * Check the given obj to be a valid extension object and fill the fields
+ * accordingly.
+ */
+enum GNUNET_GenericReturnValue
+TALER_extensions_is_json_config (
+  json_t *obj,
+  int *critical,
+  const char **version,
+  json_t **config);
+
+/*
+ * Sets the configuration of the extensions from a given JSON object.
+ *
+ * he JSON object must be of type ExchangeKeysResponse as described in
+ * https://docs.taler.net/design-documents/006-extensions.html#exchange
+ *
+ * @param cfg JSON object containting the configuration for all extensions
+ * @return GNUNET_OK on success, GNUNET_SYSERR if unknown extensions were found
+ *         or any particular configuration couldn't be parsed.
+ */
+enum GNUNET_GenericReturnValue
+TALER_extensions_load_json_config (
+  json_t *cfg);
+
 /*
  * Returns the head of the linked list of extensions
  */
@@ -156,20 +181,6 @@ TALER_extensions_verify_json_config_signature (
   struct TALER_MasterSignatureP *extensions_sig,
   struct TALER_MasterPublicKeyP *master_pub);
 
-/*
- * Sets the configuration of the extensions from a given JSON object.
- *
- * The JSON object must be of type ExchangeKeysResponse as described in
- * https://docs.taler.net/design-documents/006-extensions.html#exchange
- *
- * @param cfg Handle to the TALER configuration
- * @return GNUNET_OK on success, GNUNET_SYSERR if unknown extensions were found
- *         or any particular configuration couldn't be parsed.
- */
-enum GNUNET_GenericReturnValue
-TALER_extensions_load_json_config (
-  json_t *extensions);
-
 
 /*
  * TALER Age Restriction Extension
@@ -221,6 +232,45 @@ char *
 TALER_age_mask_to_string (
   const struct TALER_AgeMask *mask);
 
+/**
+ * Returns true when age restriction is configured and enabled.
+ */
+bool
+TALER_extensions_age_restriction_is_enabled ();
+
+/**
+ * Returns true when age restriction is configured (might not be _enabled_,
+ * though).
+ */
+bool
+TALER_extensions_age_restriction_is_configured ();
+
+/**
+ * Returns the currently set age mask.  Note that even if age restriction is
+ * not enabled, the age mask might be have a non-zero value.
+ */
+struct TALER_AgeMask
+TALER_extensions_age_restriction_ageMask ();
+
+
+/**
+ * Returns the amount of age groups defined.  0 means no age restriction
+ * enabled.
+ */
+size_t
+TALER_extensions_age_restriction_num_groups ();
+
+/**
+ * Parses a JSON object { "age_groups": "a:b:...y:z" }.
+ *
+ * @param root is the json object
+ * @param[out] mask on succes, will contain the age mask
+ * @return #GNUNET_OK on success and #GNUNET_SYSERR on failure.
+ */
+enum GNUNET_GenericReturnValue
+TALER_JSON_parse_age_groups (const json_t *root,
+                             struct TALER_AgeMask *mask);
+
 
 /*
  * TODO: Add Peer2Peer Extension
diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h
index b7bcd845..e3e47222 100644
--- a/src/include/taler_json_lib.h
+++ b/src/include/taler_json_lib.h
@@ -607,17 +607,6 @@ enum GNUNET_GenericReturnValue
 TALER_JSON_extensions_config_hash (const json_t *config,
                                    struct TALER_ExtensionConfigHash *eh);
 
-/**
- * Parses a JSON object `{ "extension": "age_restriction", "mask": uint32 }`.
- *
- * @param root is the json object
- * @param[out] mask on succes, will contain the age mask
- * @return #GNUNET_OK on success and #GNUNET_SYSERR on failure.
- */
-enum GNUNET_GenericReturnValue
-TALER_JSON_parse_agemask (const json_t *root,
-                          struct TALER_AgeMask *mask);
-
 /**
  * Canonicalize a JSON input to a string according to RFC 8785.
  */
diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h
index 17ed4b57..e3d9a893 100644
--- a/src/include/taler_signatures.h
+++ b/src/include/taler_signatures.h
@@ -418,6 +418,11 @@ struct TALER_LinkDataPS
    */
   struct TALER_TransferPublicKeyP transfer_pub;
 
+  /**
+   * Hash of the age commitment, if applicable.  Can be all zero
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment;
+
   /**
    * Hash of the blinded new coin.
    */
@@ -476,6 +481,12 @@ struct TALER_DepositRequestPS
    */
   struct TALER_PrivateContractHash h_contract_terms GNUNET_PACKED;
 
+  /**
+   * Hash over the age commitment that went into the coin. Maybe all zero, if
+   * age commitment isn't applicable to the denomination.
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED;
+
   /**
    * Hash over extension attributes shared with the exchange.
    */
@@ -709,6 +720,13 @@ struct TALER_RefreshMeltCoinAffirmationPS
    */
   struct TALER_DenominationHash h_denom_pub GNUNET_PACKED;
 
+  /**
+   * If age commitment was provided during the withdrawal of the coin, this is
+   * the hash of the age commitment vector.  It must be all zeroes if no age
+   * commitment was provided.
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED;
+
   /**
    * How much of the value of the coin should be melted?  This amount
    * includes the fees, so the final amount contributed to the melt is
diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h
index 69cb9f68..ab8b64fc 100644
--- a/src/include/taler_testing_lib.h
+++ b/src/include/taler_testing_lib.h
@@ -66,11 +66,13 @@ TALER_TESTING_make_wire_details (const char *payto);
  *
  * @param keys array of keys to search
  * @param amount coin value to look for
+ * @param age_restricted must the denomination be age restricted?
  * @return NULL if no matching key was found
  */
 const struct TALER_EXCHANGE_DenomPublicKey *
 TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys,
-                       const struct TALER_Amount *amount);
+                       const struct TALER_Amount *amount,
+                       bool age_restricted);
 
 
 /**
@@ -1278,6 +1280,7 @@ TALER_TESTING_cmd_exec_transfer (const char *label,
  * @param label command label.
  * @param reserve_reference command providing us with a reserve to withdraw 
from
  * @param amount how much we withdraw.
+ * @param age if > 0, age restriction applies
  * @param expected_response_code which HTTP response code
  *        we expect from the exchange.
  * @return the withdraw command to be executed by the interpreter.
@@ -1286,6 +1289,7 @@ struct TALER_TESTING_Command
 TALER_TESTING_cmd_withdraw_amount (const char *label,
                                    const char *reserve_reference,
                                    const char *amount,
+                                   uint8_t age,
                                    unsigned int expected_response_code);
 
 
@@ -1298,6 +1302,7 @@ TALER_TESTING_cmd_withdraw_amount (const char *label,
  * @param label command label.
  * @param reserve_reference command providing us with a reserve to withdraw 
from
  * @param amount how much we withdraw.
+ * @param age if > 0, age restriction applies.
  * @param coin_ref reference to (withdraw/reveal) command of a coin
  *        from which we should re-use the private key
  * @param expected_response_code which HTTP response code
@@ -1309,6 +1314,7 @@ TALER_TESTING_cmd_withdraw_amount_reuse_key (
   const char *label,
   const char *reserve_reference,
   const char *amount,
+  uint8_t age,
   const char *coin_ref,
   unsigned int expected_response_code);
 
@@ -2138,6 +2144,19 @@ TALER_TESTING_cmd_wire_del (const char *label,
                             unsigned int expected_http_status,
                             bool bad_sig);
 
+/**
+ * Sign all extensions that the exchange has to offer, f. e. the extension for
+ * age restriction.  This has to be run before any withdrawal of age restricted
+ * can be performed.
+ *
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_extensions (const char *label,
+                                                const char *config_filename);
+
 
 /**
  * Sign all exchange denomination and online signing keys
@@ -2422,10 +2441,10 @@ TALER_TESTING_get_trait (const struct 
TALER_TESTING_Trait *traits,
  */
 #define TALER_TESTING_SIMPLE_TRAITS(op) \
   op (bank_row, const uint64_t)                                    \
-  op (reserve_priv, const struct TALER_ReservePrivateKeyP) \
-  op (planchet_secret, const struct TALER_PlanchetMasterSecretP) \
-  op (refresh_secret, const struct TALER_RefreshMasterSecretP) \
-  op (reserve_pub, const struct TALER_ReservePublicKeyP)         \
+  op (reserve_priv, const struct TALER_ReservePrivateKeyP)         \
+  op (planchet_secret, const struct TALER_PlanchetMasterSecretP)   \
+  op (refresh_secret, const struct TALER_RefreshMasterSecretP)     \
+  op (reserve_pub, const struct TALER_ReservePublicKeyP)           \
   op (merchant_priv, const struct TALER_MerchantPrivateKeyP)       \
   op (merchant_pub, const struct TALER_MerchantPublicKeyP)         \
   op (merchant_sig, const struct TALER_MerchantSignatureP)         \
@@ -2438,8 +2457,8 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait 
*traits,
   op (exchange_bank_account_url, const char *)                     \
   op (taler_uri, const char *)                                     \
   op (payto_uri, const char *)                                     \
-  op (kyc_url, const char *)                                     \
-  op (web_url, const char *)                                     \
+  op (kyc_url, const char *)                                       \
+  op (web_url, const char *)                                       \
   op (row, const uint64_t)                                         \
   op (payment_target_uuid, const uint64_t)                         \
   op (array_length, const unsigned int)                            \
@@ -2464,7 +2483,9 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait 
*traits,
 #define TALER_TESTING_INDEXED_TRAITS(op)                               \
   op (denom_pub, const struct TALER_EXCHANGE_DenomPublicKey)           \
   op (denom_sig, const struct TALER_DenominationSignature)             \
-  op (planchet_secrets, const struct TALER_PlanchetMasterSecretP)           \
+  op (age_commitment, struct TALER_AgeCommitment)                      \
+  op (h_age_commitment, struct TALER_AgeCommitmentHash)                \
+  op (planchet_secrets, const struct TALER_PlanchetMasterSecretP)      \
   op (exchange_wd_value, const struct TALER_ExchangeWithdrawValues)    \
   op (coin_priv, const struct TALER_CoinSpendPrivateKeyP)              \
   op (coin_pub, const struct TALER_CoinSpendPublicKeyP)                \
diff --git a/src/json/json_helper.c b/src/json/json_helper.c
index 96e41b5e..4ec9a698 100644
--- a/src/json/json_helper.c
+++ b/src/json/json_helper.c
@@ -950,35 +950,4 @@ TALER_JSON_spec_i18n_str (const char *name,
 }
 
 
-enum GNUNET_GenericReturnValue
-TALER_JSON_parse_agemask (const json_t *root,
-                          struct TALER_AgeMask *mask)
-{
-  const char *name;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_string ("extension",
-                             &name),
-    GNUNET_JSON_spec_uint32 ("mask",
-                             &mask->mask),
-    GNUNET_JSON_spec_end ()
-  };
-
-  if (GNUNET_OK != GNUNET_JSON_parse (root,
-                                      spec,
-                                      NULL,
-                                      NULL))
-  {
-    return GNUNET_SYSERR;
-  }
-
-  if (! strncmp (name,
-                 "age_restriction",
-                 sizeof("age_restriction")))
-  {
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_OK;
-}
-
-
 /* end of json/json_helper.c */
diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c
index 53a75a93..d0340924 100644
--- a/src/lib/exchange_api_common.c
+++ b/src/lib/exchange_api_common.c
@@ -477,6 +477,7 @@ TALER_EXCHANGE_verify_coin_history (
       struct TALER_MerchantPublicKeyP merchant_pub;
       struct GNUNET_TIME_Timestamp refund_deadline = {0};
       struct TALER_CoinSpendSignatureP sig;
+      struct TALER_AgeCommitmentHash *hac = NULL;
       struct GNUNET_JSON_Specification spec[] = {
         GNUNET_JSON_spec_fixed_auto ("coin_sig",
                                      &sig),
@@ -511,6 +512,7 @@ TALER_EXCHANGE_verify_coin_history (
                                        &fee,
                                        &h_wire,
                                        &h_contract_terms,
+                                       hac,
                                        NULL /* h_extensions! */,
                                        h_denom_pub,
                                        wallet_timestamp,
@@ -543,6 +545,7 @@ TALER_EXCHANGE_verify_coin_history (
     {
       struct TALER_CoinSpendSignatureP sig;
       struct TALER_RefreshCommitmentP rc;
+      struct TALER_AgeCommitmentHash h_age_commitment = {0};
       struct GNUNET_JSON_Specification spec[] = {
         GNUNET_JSON_spec_fixed_auto ("coin_sig",
                                      &sig),
@@ -550,6 +553,9 @@ TALER_EXCHANGE_verify_coin_history (
                                      &rc),
         GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
                                      h_denom_pub),
+        GNUNET_JSON_spec_mark_optional (
+          GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+                                       &h_age_commitment)),
         TALER_JSON_spec_amount_any ("melt_fee",
                                     &fee),
         GNUNET_JSON_spec_end ()
@@ -563,6 +569,7 @@ TALER_EXCHANGE_verify_coin_history (
         GNUNET_break_op (0);
         return GNUNET_SYSERR;
       }
+
       if (NULL != dk)
       {
         /* check that melt fee matches our expectations from /keys! */
@@ -577,16 +584,25 @@ TALER_EXCHANGE_verify_coin_history (
           return GNUNET_SYSERR;
         }
       }
-      if (GNUNET_OK !=
-          TALER_wallet_melt_verify (&amount,
-                                    &fee,
-                                    &rc,
-                                    h_denom_pub,
-                                    coin_pub,
-                                    &sig))
+
       {
-        GNUNET_break_op (0);
-        return GNUNET_SYSERR;
+        const struct TALER_AgeCommitmentHash *ahc = &h_age_commitment;
+
+        if (TALER_AgeCommitmentHash_isNullOrZero (ahc))
+          ahc = NULL;
+
+        if (GNUNET_OK !=
+            TALER_wallet_melt_verify (&amount,
+                                      &fee,
+                                      &rc,
+                                      h_denom_pub,
+                                      ahc,
+                                      coin_pub,
+                                      &sig))
+        {
+          GNUNET_break_op (0);
+          return GNUNET_SYSERR;
+        }
       }
       add = GNUNET_YES;
     }
diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c
index 7ff59651..2bfaaf6c 100644
--- a/src/lib/exchange_api_deposit.c
+++ b/src/lib/exchange_api_deposit.c
@@ -463,6 +463,7 @@ handle_deposit_finished (void *cls,
  * @param h_contract_terms hash of the contact of the merchant with the 
customer (further details are never disclosed to the exchange)
  * @param ech hash over contract extensions
  * @param coin_pub coin’s public key
+ * @param h_age_commitment coin’s hash of age commitment, might be NULL
  * @param denom_sig exchange’s unblinded signature of the coin
  * @param denom_pub denomination key with which the coin is signed
  * @param denom_pub_hash hash of @a denom_pub
@@ -479,6 +480,7 @@ verify_signatures (const struct 
TALER_EXCHANGE_DenomPublicKey *dki,
                    const struct TALER_PrivateContractHash *h_contract_terms,
                    const struct TALER_ExtensionContractHash *ech,
                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                   const struct TALER_AgeCommitmentHash *h_age_commitment,
                    const struct TALER_DenominationSignature *denom_sig,
                    const struct TALER_DenominationPublicKey *denom_pub,
                    const struct TALER_DenominationHash *denom_pub_hash,
@@ -492,6 +494,7 @@ verify_signatures (const struct 
TALER_EXCHANGE_DenomPublicKey *dki,
                                    &dki->fee_deposit,
                                    h_wire,
                                    h_contract_terms,
+                                   h_age_commitment,
                                    ech,
                                    denom_pub_hash,
                                    timestamp,
@@ -515,8 +518,12 @@ verify_signatures (const struct 
TALER_EXCHANGE_DenomPublicKey *dki,
       .coin_pub = *coin_pub,
       .denom_pub_hash = *denom_pub_hash,
       .denom_sig = *denom_sig,
-      .age_commitment_hash = {{{0}}} /* FIXME-Oec */
+      .age_commitment_hash = {{{0}}}
     };
+    if (NULL != h_age_commitment)
+    {
+      coin_info.age_commitment_hash = *h_age_commitment;
+    }
 
     if (GNUNET_YES !=
         TALER_test_coin_valid (&coin_info,
@@ -548,6 +555,7 @@ TALER_EXCHANGE_deposit (
   const char *merchant_payto_uri,
   const struct TALER_WireSaltP *wire_salt,
   const struct TALER_PrivateContractHash *h_contract_terms,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
   const json_t *extension_details,
   const struct TALER_CoinSpendPublicKeyP *coin_pub,
   const struct TALER_DenominationSignature *denom_sig,
@@ -600,11 +608,14 @@ TALER_EXCHANGE_deposit (
   }
   GNUNET_assert (GNUNET_YES ==
                  TEAH_handle_is_ready (exchange));
+
   /* initialize h_wire */
   TALER_merchant_wire_signature_hash (merchant_payto_uri,
                                       wire_salt,
                                       &h_wire);
+
   key_state = TALER_EXCHANGE_get_keys (exchange);
+
   dki = TALER_EXCHANGE_get_denomination_key (key_state,
                                              denom_pub);
   if (NULL == dki)
@@ -613,6 +624,7 @@ TALER_EXCHANGE_deposit (
     GNUNET_break_op (0);
     return NULL;
   }
+
   if (0 >
       TALER_amount_subtract (&amount_without_fee,
                              amount,
@@ -622,17 +634,18 @@ TALER_EXCHANGE_deposit (
     GNUNET_break_op (0);
     return NULL;
   }
+
   TALER_denom_pub_hash (denom_pub,
                         &denom_pub_hash);
+
   if (GNUNET_OK !=
       verify_signatures (dki,
                          amount,
                          &h_wire,
                          h_contract_terms,
-                         (NULL != extension_details)
-                         ? &ech
-                         : NULL,
+                         (NULL != extension_details) ? &ech : NULL,
                          coin_pub,
+                         h_age_commitment,
                          denom_sig,
                          denom_pub,
                          &denom_pub_hash,
@@ -655,6 +668,9 @@ TALER_EXCHANGE_deposit (
                                 wire_salt),
     GNUNET_JSON_pack_data_auto ("h_contract_terms",
                                 h_contract_terms),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_data_auto ("h_age_commitment",
+                                  h_age_commitment)),
     GNUNET_JSON_pack_data_auto ("denom_pub_hash",
                                 &denom_pub_hash),
     TALER_JSON_pack_denom_sig ("ub_sig",
diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c
index cf3d69d6..3243f5e9 100644
--- a/src/lib/exchange_api_handle.c
+++ b/src/lib/exchange_api_handle.c
@@ -667,7 +667,9 @@ decode_keys_json (const json_t *resp_obj,
                   enum TALER_EXCHANGE_VersionCompatibility *vc)
 {
   struct TALER_ExchangeSignatureP sig;
-  struct GNUNET_HashContext *hash_context;
+  struct GNUNET_HashContext *hash_context = NULL;
+  struct GNUNET_HashContext *hash_context_restricted = NULL;
+  bool have_age_restricted_denom = false;
   struct TALER_ExchangePublicKeyP pub;
   const char *currency;
   struct GNUNET_JSON_Specification mspec[] = {
@@ -746,7 +748,6 @@ decode_keys_json (const json_t *resp_obj,
     key_data->version = GNUNET_strdup (ver);
   }
 
-  hash_context = NULL;
   EXITIF (GNUNET_OK !=
           GNUNET_JSON_parse (resp_obj,
                              (check_sig) ? mspec : &mspec[2],
@@ -766,7 +767,10 @@ decode_keys_json (const json_t *resp_obj,
 
   /* parse the master public key and issue date of the response */
   if (check_sig)
+  {
     hash_context = GNUNET_CRYPTO_hash_context_start ();
+    hash_context_restricted = GNUNET_CRYPTO_hash_context_start ();
+  }
 
   /* parse the signing keys */
   {
@@ -829,6 +833,9 @@ decode_keys_json (const json_t *resp_obj,
       EXITIF (GNUNET_OK !=
               TALER_extensions_load_json_config (extensions));
     }
+
+    /* 4. assuming we might have now a new value for age_mask, set it in 
key_data */
+    key_data->age_mask = TALER_extensions_age_restriction_ageMask ();
   }
 
   /* parse the denomination keys, merging with the
@@ -839,9 +846,15 @@ decode_keys_json (const json_t *resp_obj,
      */
     struct
     { char *name;
-      bool is_optional_age_restriction;} hive[2] = {
-      { "denoms",                false },
-      { "age_restricted_denoms", true  },
+      struct GNUNET_HashContext *hc;
+      bool is_optional_age_restriction;}
+    hive[2] = {
+      { "denoms",
+        hash_context,
+        false },
+      { "age_restricted_denoms",
+        hash_context_restricted,
+        true  }
     };
 
     for (size_t s = 0; s < sizeof(hive) / sizeof(hive[0]); s++)
@@ -853,25 +866,19 @@ decode_keys_json (const json_t *resp_obj,
       denom_keys_array = json_object_get (resp_obj,
                                           hive[s].name);
 
-      EXITIF (NULL == denom_keys_array &&
-              ! hive[s].is_optional_age_restriction);
-
-      if (NULL == denom_keys_array &&
-          hive[s].is_optional_age_restriction)
+      if (NULL == denom_keys_array)
         continue;
 
-      /* if "age_restricted_denoms" exists, age-restriction better be enabled
-       * (that is: mask non-zero) */
-      EXITIF (NULL != denom_keys_array &&
-              hive[s].is_optional_age_restriction &&
-              0 == key_data->age_mask.mask);
-
       EXITIF (JSON_ARRAY != json_typeof (denom_keys_array));
 
       json_array_foreach (denom_keys_array, index, denom_key_obj) {
         struct TALER_EXCHANGE_DenomPublicKey dk;
         bool found = false;
 
+        /* mark that we have at least one age restricted denomination, needed
+         * for the hash calculation and signature verification below. */
+        have_age_restricted_denom |= hive[s].is_optional_age_restriction;
+
         memset (&dk,
                 0,
                 sizeof (dk));
@@ -880,12 +887,7 @@ decode_keys_json (const json_t *resp_obj,
                                      check_sig,
                                      denom_key_obj,
                                      &key_data->master_pub,
-                                     hash_context));
-
-        /* Mark age restriction according where we got this denomination from,
-         * "denoms" or "age_restricted_denoms" */
-        if (hive[s].is_optional_age_restriction)
-          dk.age_restricted = true;
+                                     hive[s].hc));
 
         for (unsigned int j = 0;
              j<key_data->num_denom_keys;
@@ -1044,6 +1046,18 @@ decode_keys_json (const json_t *resp_obj,
       .list_issue_date = GNUNET_TIME_timestamp_hton (key_data->list_issue_date)
     };
 
+    /* If we had any age restricted denominations, add their hash to the end of
+     * the normal denominations. */
+    if (have_age_restricted_denom)
+    {
+      struct GNUNET_HashCode hcr;
+      GNUNET_CRYPTO_hash_context_finish (hash_context_restricted,
+                                         &hcr);
+      GNUNET_CRYPTO_hash_context_read (hash_context,
+                                       &hcr,
+                                       sizeof(struct GNUNET_HashCode));
+    }
+
     GNUNET_CRYPTO_hash_context_finish (hash_context,
                                        &ks.hc);
     hash_context = NULL;
diff --git a/src/lib/exchange_api_link.c b/src/lib/exchange_api_link.c
index a44ccdce..10ddd471 100644
--- a/src/lib/exchange_api_link.c
+++ b/src/lib/exchange_api_link.c
@@ -113,6 +113,7 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
   struct TALER_TransferSecretP secret;
   struct TALER_PlanchetDetail pd;
   struct TALER_CoinPubHash c_hash;
+  struct TALER_AgeCommitmentHash h_age_commitment = {0}; // TODO, see below.
 
   /* parse reply */
   memset (&nonce,
@@ -143,6 +144,7 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
                               &alg_values,
                               &bks,
                               &lci->coin_priv,
+                              NULL, /* FIXME-oec. struct 
TALER_AgeCommitmentHash */
                               &c_hash,
                               &pd))
   {
@@ -179,6 +181,15 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle 
*lh,
 
     GNUNET_CRYPTO_eddsa_key_get_public (&lh->coin_priv.eddsa_priv,
                                         &old_coin_pub.eddsa_pub);
+    /*
+     * TODO-oec: Derive the age commitment vector and hash it into
+     * h_age_commitment.
+     * Questions:
+     *   - Where do we get the information about the support for age
+     *     restriction of the denomination?
+     *   - Where do we get the information bout the previous coin's age groups?
+     */
+
     TALER_coin_ev_hash (&pd.blinded_planchet,
                         &pd.denom_pub_hash,
                         &coin_envelope_hash);
@@ -187,6 +198,7 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
                                   trans_pub,
                                   &coin_envelope_hash,
                                   &old_coin_pub,
+                                  &h_age_commitment,
                                   &link_sig))
     {
       GNUNET_break_op (0);
diff --git a/src/lib/exchange_api_management_get_keys.c 
b/src/lib/exchange_api_management_get_keys.c
index 4d686633..ac419388 100644
--- a/src/lib/exchange_api_management_get_keys.c
+++ b/src/lib/exchange_api_management_get_keys.c
@@ -32,7 +32,7 @@
 /**
  * Set to 1 for extra debug logging.
  */
-#define DEBUG 0
+#define DEBUG 1  /* FIXME-oec */
 
 
 /**
diff --git a/src/lib/exchange_api_management_post_extensions.c 
b/src/lib/exchange_api_management_post_extensions.c
index c0ab143f..87b0e0be 100644
--- a/src/lib/exchange_api_management_post_extensions.c
+++ b/src/lib/exchange_api_management_post_extensions.c
@@ -151,7 +151,7 @@ TALER_EXCHANGE_management_post_extensions (
   body = GNUNET_JSON_PACK (
     GNUNET_JSON_pack_object_steal ("extensions",
                                    ped->extensions),
-    GNUNET_JSON_pack_data_auto ("extensions_sigs",
+    GNUNET_JSON_pack_data_auto ("extensions_sig",
                                 &ped->extensions_sig));
 
   eh = curl_easy_init ();
@@ -168,7 +168,7 @@ TALER_EXCHANGE_management_post_extensions (
     return NULL;
   }
   json_decref (body);
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Requesting URL '%s'\n",
               ph->url);
   GNUNET_assert (CURLE_OK == curl_easy_setopt (eh,
diff --git a/src/lib/exchange_api_refresh_common.c 
b/src/lib/exchange_api_refresh_common.c
index e944b79a..89ee1e17 100644
--- a/src/lib/exchange_api_refresh_common.c
+++ b/src/lib/exchange_api_refresh_common.c
@@ -175,6 +175,7 @@ TALER_EXCHANGE_get_melt_data_ (
                                   &alg_values[j],
                                   bks,
                                   coin_priv,
+                                  NULL, /* FIXME-oec: This needs to be setup 
!*/
                                   &c_hash,
                                   &pd))
       {
diff --git a/src/lib/exchange_api_refresh_common.h 
b/src/lib/exchange_api_refresh_common.h
index ab19ad7d..b6926b51 100644
--- a/src/lib/exchange_api_refresh_common.h
+++ b/src/lib/exchange_api_refresh_common.h
@@ -52,6 +52,12 @@ struct MeltedCoin
    */
   struct TALER_Amount original_value;
 
+  /**
+   * The original age commitment hash.  MUST be all zeroes, if no age
+   * commitment was set.
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment;
+
   /**
    * Timestamp indicating when coins of this denomination become invalid.
    */
@@ -92,6 +98,13 @@ struct FreshCoinData
    */
   struct TALER_CoinSpendPrivateKeyP coin_priv;
 
+  /**
+   * Arrays age commitments to be created, one for each cut-and-choose
+   * dimension.  The entries in each list might be NULL and indicate no age
+   * commitment/restriction on the particular coin.
+   */
+  struct TALER_AgeCommitment *age_commitment[TALER_CNC_KAPPA];
+
   /**
    * Blinding key secrets for the coins, depending on the
    * cut-and-choose.
diff --git a/src/lib/exchange_api_refreshes_reveal.c 
b/src/lib/exchange_api_refreshes_reveal.c
index 08357c14..8d04c279 100644
--- a/src/lib/exchange_api_refreshes_reveal.c
+++ b/src/lib/exchange_api_refreshes_reveal.c
@@ -142,6 +142,7 @@ refresh_reveal_ok (struct 
TALER_EXCHANGE_RefreshesRevealHandle *rrh,
       &rcis[i];
     const struct FreshCoinData *fcd = &rrh->md.fcds[i];
     const struct TALER_DenominationPublicKey *pk;
+    struct TALER_AgeCommitmentHash *ach = NULL;
     json_t *jsonai;
     struct TALER_BlindedDenominationSignature blind_sig;
     struct TALER_CoinSpendPublicKeyP coin_pub;
@@ -160,6 +161,12 @@ refresh_reveal_ok (struct 
TALER_EXCHANGE_RefreshesRevealHandle *rrh,
     jsonai = json_array_get (jsona, i);
     GNUNET_assert (NULL != jsonai);
 
+    if (! TALER_AgeCommitmentHash_isNullOrZero (
+          &rrh->md.melted_coin.h_age_commitment))
+    {
+      /* FIXME-oec:  need to pull fresh_ach from somewhere */
+    }
+
     if (GNUNET_OK !=
         GNUNET_JSON_parse (jsonai,
                            spec,
@@ -180,15 +187,15 @@ refresh_reveal_ok (struct 
TALER_EXCHANGE_RefreshesRevealHandle *rrh,
        hence recomputing it here... */
     GNUNET_CRYPTO_eddsa_key_get_public (&rci->coin_priv.eddsa_priv,
                                         &coin_pub.eddsa_pub);
-    /* FIXME-Oec: Age commitment hash. */
     TALER_coin_pub_hash (&coin_pub,
-                         NULL, /* FIXME-Oec */
+                         ach,
                          &coin_hash);
     if (GNUNET_OK !=
         TALER_planchet_to_coin (pk,
                                 &blind_sig,
                                 &bks,
                                 &rci->coin_priv,
+                                ach,
                                 &coin_hash,
                                 &rrh->alg_values[i],
                                 &coin))
diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c
index 94909470..a9510715 100644
--- a/src/lib/exchange_api_refund.c
+++ b/src/lib/exchange_api_refund.c
@@ -203,6 +203,7 @@ verify_conflict_history_ok (struct 
TALER_EXCHANGE_RefundHandle *rh,
       struct TALER_Amount deposit_fee;
       struct TALER_MerchantWireHash h_wire;
       struct TALER_PrivateContractHash h_contract_terms;
+      struct TALER_AgeCommitmentHash h_age_commitment = {{{0}}};
       // struct TALER_ExtensionContractHash h_extensions; // FIXME!
       struct TALER_DenominationHash h_denom_pub;
       struct GNUNET_TIME_Timestamp wallet_timestamp;
@@ -218,6 +219,9 @@ verify_conflict_history_ok (struct 
TALER_EXCHANGE_RefundHandle *rh,
                                      &h_wire),
         GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
                                      &h_denom_pub),
+        GNUNET_JSON_spec_mark_optional (
+          GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+                                       &h_age_commitment)),
         GNUNET_JSON_spec_timestamp ("timestamp",
                                     &wallet_timestamp),
         GNUNET_JSON_spec_timestamp ("refund_deadline",
@@ -243,6 +247,7 @@ verify_conflict_history_ok (struct 
TALER_EXCHANGE_RefundHandle *rh,
                                        &deposit_fee,
                                        &h_wire,
                                        &h_contract_terms,
+                                       &h_age_commitment,
                                        NULL /* h_extensions! */,
                                        &h_denom_pub,
                                        wallet_timestamp,
diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c
index c832699a..efc8a99c 100644
--- a/src/lib/exchange_api_withdraw.c
+++ b/src/lib/exchange_api_withdraw.c
@@ -88,6 +88,11 @@ struct TALER_EXCHANGE_WithdrawHandle
    */
   struct TALER_ExchangeWithdrawValues alg_values;
 
+  /**
+   * Hash of the age commitment for this coin, if applicable. Maybe NULL
+   */
+  const struct TALER_AgeCommitmentHash *ach;
+
   /**
    * Denomination key we are withdrawing.
    */
@@ -137,6 +142,7 @@ handle_reserve_withdraw_finished (
                                   blind_sig,
                                   &wh->bks,
                                   &wh->priv,
+                                  wh->ach,
                                   &wh->c_hash,
                                   &wh->alg_values,
                                   &fc))
@@ -222,6 +228,7 @@ withdraw_cs_stage_two_callback (void *cls,
                                 &wh->alg_values,
                                 &wh->bks,
                                 &wh->priv,
+                                wh->ach,
                                 &wh->c_hash,
                                 &wh->pd))
     {
@@ -249,6 +256,7 @@ TALER_EXCHANGE_withdraw (
   const struct TALER_EXCHANGE_DenomPublicKey *pk,
   const struct TALER_ReservePrivateKeyP *reserve_priv,
   const struct TALER_PlanchetMasterSecretP *ps,
+  const struct TALER_AgeCommitmentHash *ach,
   TALER_EXCHANGE_WithdrawCallback res_cb,
   void *res_cb_cls)
 {
@@ -260,6 +268,7 @@ TALER_EXCHANGE_withdraw (
   wh->cb_cls = res_cb_cls;
   wh->reserve_priv = reserve_priv;
   wh->ps = *ps;
+  wh->ach = ach;
   wh->pk = *pk;
   TALER_denom_pub_deep_copy (&wh->pk.key,
                              &pk->key);
@@ -280,6 +289,7 @@ TALER_EXCHANGE_withdraw (
                                   &wh->alg_values,
                                   &wh->bks,
                                   &wh->priv,
+                                  wh->ach,
                                   &wh->c_hash,
                                   &wh->pd))
       {
diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am
index a6b58270..39cc6cbe 100644
--- a/src/testing/Makefile.am
+++ b/src/testing/Makefile.am
@@ -68,6 +68,7 @@ libtalertesting_la_SOURCES = \
   testing_api_cmd_oauth.c \
   testing_api_cmd_offline_sign_fees.c \
   testing_api_cmd_offline_sign_keys.c \
+  testing_api_cmd_offline_sign_extensions.c \
   testing_api_cmd_set_wire_fee.c \
   testing_api_cmd_recoup.c \
   testing_api_cmd_recoup_refresh.c \
@@ -249,6 +250,7 @@ test_exchange_api_cs_LDADD = \
   -lgnunetcurl \
   -lgnunetutil \
   -ljansson \
+  -ltalerextensions \
   $(XLIB)
 
 test_exchange_api_rsa_SOURCES = \
@@ -265,6 +267,7 @@ test_exchange_api_rsa_LDADD = \
   -lgnunetcurl \
   -lgnunetutil \
   -ljansson \
+  -ltalerextensions \
   $(XLIB)
 
 test_exchange_api_keys_cherry_picking_cs_SOURCES = \
diff --git a/src/testing/test_auditor_api.c b/src/testing/test_auditor_api.c
index 38b1b1ab..9ab78664 100644
--- a/src/testing/test_auditor_api.c
+++ b/src/testing/test_auditor_api.c
@@ -128,6 +128,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
                                        "create-reserve-1",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_end ()
   };
@@ -168,6 +169,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-1",
                                        "refresh-create-reserve-1",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     /**
      * Try to partially spend (deposit) 1 EUR of the 5 EUR coin (in
@@ -315,6 +317,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregated",
                                        "create-reserve-unaggregated",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_deposit ("deposit-unaggregated",
                                "withdraw-coin-unaggregated",
@@ -347,6 +350,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1",
                                        "create-reserve-r1",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     /**
      * Spend 5 EUR of the 5 EUR coin (in full). Merchant would
@@ -402,6 +406,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1",
                                        "recoup-create-reserve-1",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_revoke ("revoke-1",
                               MHD_HTTP_OK,
@@ -417,6 +422,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2",
                                        "recoup-create-reserve-1",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     /**
      * These commands should close the reserve because the aggregator
@@ -447,6 +453,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2a",
                                        "recoup-create-reserve-2",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     /**
      * Withdraw a 1 EUR coin, at fee of 1 ct
@@ -454,6 +461,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2b",
                                        "recoup-create-reserve-2",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_deposit ("recoup-deposit-partial",
                                "recoup-withdraw-coin-2a",
@@ -491,42 +499,52 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-1",
                                        "massive-reserve",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-2",
                                        "massive-reserve",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-3",
                                        "massive-reserve",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-4",
                                        "massive-reserve",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-5",
                                        "massive-reserve",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-6",
                                        "massive-reserve",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-7",
                                        "massive-reserve",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-8",
                                        "massive-reserve",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-9",
                                        "massive-reserve",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-10",
                                        "massive-reserve",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_deposit (
       "massive-deposit-1",
@@ -719,7 +737,7 @@ main (int argc,
     GNUNET_break (0);
     return 1;
   case GNUNET_NO:
-    return 77;
+    return 78;
   case GNUNET_OK:
     if (GNUNET_OK !=
         /* Set up event loop and reschedule context, plus
@@ -729,11 +747,11 @@ main (int argc,
         TALER_TESTING_auditor_setup (&run,
                                      NULL,
                                      config_file))
-      return 1;
+      return 2;
     break;
   default:
     GNUNET_break (0);
-    return 1;
+    return 3;
   }
   return 0;
 }
diff --git a/src/testing/test_exchange_api-cs.conf 
b/src/testing/test_exchange_api-cs.conf
index 3fbf4c3c..79332d64 100644
--- a/src/testing/test_exchange_api-cs.conf
+++ b/src/testing/test_exchange_api-cs.conf
@@ -149,7 +149,7 @@ fee_withdraw = EUR:0.00
 fee_deposit = EUR:0.00
 fee_refresh = EUR:0.01
 fee_refund = EUR:0.01
-age_restricted = true
+age_restricted = YES
 CIPHER = CS
 
 [coin_eur_ct_10_age_restricted]
@@ -161,7 +161,7 @@ fee_withdraw = EUR:0.01
 fee_deposit = EUR:0.01
 fee_refresh = EUR:0.03
 fee_refund = EUR:0.01
-age_restricted = true
+age_restricted = YES
 CIPHER = CS
 
 [coin_eur_1_age_restricted]
@@ -173,7 +173,7 @@ fee_withdraw = EUR:0.01
 fee_deposit = EUR:0.01
 fee_refresh = EUR:0.03
 fee_refund = EUR:0.01
-age_restricted = true
+age_restricted = YES
 CIPHER = CS
 
 [coin_eur_5_age_restricted]
@@ -185,7 +185,7 @@ fee_withdraw = EUR:0.01
 fee_deposit = EUR:0.01
 fee_refresh = EUR:0.03
 fee_refund = EUR:0.01
-age_restricted = true
+age_restricted = YES
 CIPHER = CS
 
 [coin_eur_10_age_restricted]
@@ -197,5 +197,5 @@ fee_withdraw = EUR:0.01
 fee_deposit = EUR:0.01
 fee_refresh = EUR:0.03
 fee_refund = EUR:0.01
-age_restricted = true
+age_restricted = YES
 CIPHER = CS
diff --git a/src/testing/test_exchange_api-rsa.conf 
b/src/testing/test_exchange_api-rsa.conf
index cffe3b87..1d445662 100644
--- a/src/testing/test_exchange_api-rsa.conf
+++ b/src/testing/test_exchange_api-rsa.conf
@@ -155,7 +155,7 @@ fee_deposit = EUR:0.00
 fee_refresh = EUR:0.01
 fee_refund = EUR:0.01
 rsa_keysize = 1024
-age_restricted = true
+age_restricted = YES
 CIPHER = RSA
 
 [coin_eur_ct_10_age_restricted]
@@ -168,7 +168,7 @@ fee_deposit = EUR:0.01
 fee_refresh = EUR:0.03
 fee_refund = EUR:0.01
 rsa_keysize = 1024
-age_restricted = true
+age_restricted = YES
 CIPHER = RSA
 
 [coin_eur_1_age_restricted]
@@ -181,7 +181,7 @@ fee_deposit = EUR:0.01
 fee_refresh = EUR:0.03
 fee_refund = EUR:0.01
 rsa_keysize = 1024
-age_restricted = true
+age_restricted = YES
 CIPHER = RSA
 
 [coin_eur_5_age_restricted]
@@ -194,7 +194,7 @@ fee_deposit = EUR:0.01
 fee_refresh = EUR:0.03
 fee_refund = EUR:0.01
 rsa_keysize = 1024
-age_restricted = true
+age_restricted = YES
 CIPHER = RSA
 
 [coin_eur_10_age_restricted]
@@ -207,5 +207,5 @@ fee_deposit = EUR:0.01
 fee_refresh = EUR:0.03
 fee_refund = EUR:0.01
 rsa_keysize = 1024
-age_restricted = true
+age_restricted = YES
 CIPHER = RSA
diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c
index b1779a7d..957e42e8 100644
--- a/src/testing/test_exchange_api.c
+++ b/src/testing/test_exchange_api.c
@@ -34,6 +34,7 @@
 #include "taler_bank_service.h"
 #include "taler_fakebank_lib.h"
 #include "taler_testing_lib.h"
+#include "taler_extensions.h"
 
 /**
  * Configuration file we use.  One (big) configuration is used
@@ -149,6 +150,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
                                        "create-reserve-1",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     /**
      * Withdraw EUR:1 using the SAME private coin key as for the previous coin
@@ -162,6 +164,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount_reuse_key ("withdraw-coin-1x",
                                                  "create-reserve-1",
                                                  "EUR:1",
+                                                 0, /* age restriction off */
                                                  "withdraw-coin-1",
                                                  MHD_HTTP_OK),
     /**
@@ -177,6 +180,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
                                        "create-reserve-1",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_CONFLICT),
     TALER_TESTING_cmd_end ()
   };
@@ -282,6 +286,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-1",
                                        "refresh-create-reserve-1",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     /* Try to partially spend (deposit) 1 EUR of the 5 EUR coin
      * (in full) (merchant would receive EUR:0.99 due to 1 ct
@@ -358,6 +363,61 @@ run (void *cls,
     TALER_TESTING_cmd_end ()
   };
 
+  /**
+   * Test withdrawal with age restriction.  Success is expected, so it MUST be
+   * called _after_ TALER_TESTING_cmd_exec_offline_sign_extensions is called,
+   * i. e. age restriction is activated in the exchange!
+   *
+   * TODO: create a test that tries to withdraw coins with age restriction but
+   * (expectedly) fails because the exchange doesn't support age restriction
+   * yet.
+   */
+  struct TALER_TESTING_Command withdraw_age[] = {
+    /**
+     * Move money to the exchange's bank account.
+     */
+    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-age",
+                              "EUR:5.01"),
+    TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-age",
+                                                 "EUR:5.01",
+                                                 bc.user42_payto,
+                                                 bc.exchange_payto,
+                                                 "create-reserve-age"),
+    /**
+     * Make a reserve exist, according to the previous
+     * transfer.
+     */
+    CMD_EXEC_WIREWATCH ("wirewatch-age"),
+    /**
+     * Withdraw EUR:5.
+     */
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-age-1",
+                                       "create-reserve-age",
+                                       "EUR:5",
+                                       13,
+                                       MHD_HTTP_OK),
+
+    TALER_TESTING_cmd_end ()
+  };
+
+  struct TALER_TESTING_Command spend_age[] = {
+    /**
+     * Spend the coin.
+     */
+    TALER_TESTING_cmd_deposit ("deposit-simple-age",
+                               "withdraw-coin-age-1",
+                               0,
+                               bc.user42_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":1}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:4.99",
+                               MHD_HTTP_OK),
+    TALER_TESTING_cmd_deposit_replay ("deposit-simple-replay-age",
+                                      "deposit-simple-age",
+                                      MHD_HTTP_OK),
+    TALER_TESTING_cmd_end ()
+  };
+
   struct TALER_TESTING_Command track[] = {
     /* Try resolving a deposit's WTID, as we never triggered
      * execution of transactions, the answer should be that
@@ -400,6 +460,11 @@ run (void *cls,
                                            "EUR:4.98",
                                            bc.exchange_payto,
                                            bc.user42_payto),
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-499c2",
+                                           ec.exchange_url,
+                                           "EUR:4.97",
+                                           bc.exchange_payto,
+                                           bc.user42_payto),
     TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c1",
                                            ec.exchange_url,
                                            "EUR:0.98",
@@ -463,6 +528,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregated",
                                        "create-reserve-unaggregated",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_deposit ("deposit-unaggregated",
                                "withdraw-coin-unaggregated",
@@ -501,6 +567,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-aggtest",
                                        "create-reserve-aggtest",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_deposit ("deposit-aggtest-1",
                                "withdraw-coin-aggtest",
@@ -549,6 +616,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1",
                                        "create-reserve-r1",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     /**
      * Spend 5 EUR of the 5 EUR coin (in full) (merchant would
@@ -649,6 +717,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-rb",
                                        "create-reserve-rb",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_deposit ("deposit-refund-1b",
                                "withdraw-coin-rb",
@@ -698,11 +767,13 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1",
                                        "recoup-create-reserve-1",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     /* Withdraw a 10 EUR coin, at fee of 1 ct */
     TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1b",
                                        "recoup-create-reserve-1",
                                        "EUR:10",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     /* melt 10 EUR coin to get 5 EUR refreshed coin */
     TALER_TESTING_cmd_melt ("recoup-melt-coin-1b",
@@ -793,6 +864,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2",
                                        "recoup-create-reserve-1",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     /**
      * This withdrawal will test the logic to create a "recoup"
@@ -801,6 +873,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2-over",
                                        "recoup-create-reserve-1",
                                        "EUR:10",
+                                       0, /* age restriction off */
                                        MHD_HTTP_CONFLICT),
     TALER_TESTING_cmd_status ("recoup-reserve-status-2",
                               "recoup-create-reserve-1",
@@ -833,6 +906,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("expired-withdraw",
                                        "short-lived-reserve",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_CONFLICT),
     TALER_TESTING_cmd_check_bank_transfer ("check_bank_short-lived_reimburse",
                                            ec.exchange_url,
@@ -857,11 +931,13 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2a",
                                        "recoup-create-reserve-2",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     /* Withdraw a 1 EUR coin, at fee of 1 ct */
     TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2b",
                                        "recoup-create-reserve-2",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_deposit ("recoup-deposit-partial",
                                "recoup-withdraw-coin-2a",
@@ -924,6 +1000,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-3-revoked",
                                        "recoup-create-reserve-3",
                                        "EUR:1",
+                                       0, /* age restriction off */
                                        MHD_HTTP_GONE),
     /* check that we are empty before the rejection test */
     TALER_TESTING_cmd_check_bank_empty ("check-empty-again"),
@@ -970,6 +1047,8 @@ run (void *cls,
       TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
                                      MHD_HTTP_NO_CONTENT,
                                      false),
+      TALER_TESTING_cmd_exec_offline_sign_extensions 
("offline-sign-extensions",
+                                                      config_file),
       TALER_TESTING_cmd_wire_add ("add-wire-account",
                                   "payto://x-taler-bank/localhost/2",
                                   MHD_HTTP_NO_CONTENT,
@@ -990,6 +1069,10 @@ run (void *cls,
                                spend),
       TALER_TESTING_cmd_batch ("refresh",
                                refresh),
+      TALER_TESTING_cmd_batch ("withdraw-age",
+                               withdraw_age),
+      TALER_TESTING_cmd_batch ("spend-age",
+                               spend_age),
       TALER_TESTING_cmd_batch ("track",
                                track),
       TALER_TESTING_cmd_batch ("unaggregation",
@@ -1026,6 +1109,9 @@ main (int argc,
   GNUNET_log_setup (argv[0],
                     "INFO",
                     NULL);
+
+  TALER_extensions_init ();
+
   cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
   GNUNET_assert (NULL != cipher);
   uses_cs = (0 == strcmp (cipher, "cs"));
@@ -1036,6 +1122,7 @@ main (int argc,
                    "test_exchange_api_expire_reserve_now-%s.conf",
                    cipher);
   GNUNET_free (cipher);
+
   /* Check fakebank port is available and get config */
   if (GNUNET_OK !=
       TALER_TESTING_prepare_fakebank (config_file,
@@ -1054,7 +1141,7 @@ main (int argc,
     GNUNET_break (0);
     return 1;
   case GNUNET_NO:
-    return 77;
+    return 78;
   case GNUNET_OK:
     if (GNUNET_OK !=
         /* Set up event loop and reschedule context, plus
@@ -1064,11 +1151,11 @@ main (int argc,
         TALER_TESTING_setup_with_exchange (&run,
                                            NULL,
                                            config_file))
-      return 1;
+      return 2;
     break;
   default:
     GNUNET_break (0);
-    return 1;
+    return 3;
   }
   return 0;
 }
diff --git a/src/testing/test_exchange_api_revocation.c 
b/src/testing/test_exchange_api_revocation.c
index beb94dba..bb3dcc06 100644
--- a/src/testing/test_exchange_api_revocation.c
+++ b/src/testing/test_exchange_api_revocation.c
@@ -96,11 +96,13 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("withdraw-revocation-coin-1",
                                        "create-reserve-1",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     /* Withdraw another 5 EUR coin, at fee of 1 ct */
     TALER_TESTING_cmd_withdraw_amount ("withdraw-revocation-coin-2",
                                        "create-reserve-1",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     /* Try to partially spend (deposit) 1 EUR of the 5 EUR coin (in full)
      * (merchant would receive EUR:0.99 due to 1 ct deposit fee) *///
diff --git a/src/testing/test_kyc_api.c b/src/testing/test_kyc_api.c
index b9e9a9f8..ca87edd8 100644
--- a/src/testing/test_kyc_api.c
+++ b/src/testing/test_kyc_api.c
@@ -105,10 +105,12 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-no-kyc",
                                        "create-reserve-1",
                                        "EUR:10",
+                                       0, /* age restriction off */
                                        MHD_HTTP_ACCEPTED),
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
                                        "create-reserve-1",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_end ()
   };
@@ -120,6 +122,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-lacking-kyc",
                                        "create-reserve-1",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_ACCEPTED),
     TALER_TESTING_cmd_proof_kyc ("proof-kyc",
                                  "withdraw-coin-1-lacking-kyc",
@@ -129,6 +132,7 @@ run (void *cls,
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-with-kyc",
                                        "create-reserve-1",
                                        "EUR:5",
+                                       0, /* age restriction off */
                                        MHD_HTTP_OK),
     TALER_TESTING_cmd_end ()
   };
diff --git a/src/testing/testing_api_cmd_deposit.c 
b/src/testing/testing_api_cmd_deposit.c
index b2fd7ddf..d3fafc63 100644
--- a/src/testing/testing_api_cmd_deposit.c
+++ b/src/testing/testing_api_cmd_deposit.c
@@ -287,6 +287,8 @@ deposit_run (void *cls,
   const struct TALER_TESTING_Command *coin_cmd;
   const struct TALER_CoinSpendPrivateKeyP *coin_priv;
   struct TALER_CoinSpendPublicKeyP coin_pub;
+  struct TALER_AgeCommitment *age_commitment = NULL;
+  struct TALER_AgeCommitmentHash h_age_commitment = {0};
   const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
   const struct TALER_DenominationSignature *denom_pub_sig;
   struct TALER_CoinSpendSignatureP coin_sig;
@@ -382,6 +384,10 @@ deposit_run (void *cls,
         TALER_TESTING_get_trait_coin_priv (coin_cmd,
                                            ds->coin_index,
                                            &coin_priv)) ||
+       (GNUNET_OK !=
+        TALER_TESTING_get_trait_age_commitment (coin_cmd,
+                                                ds->coin_index,
+                                                &age_commitment)) ||
        (GNUNET_OK !=
         TALER_TESTING_get_trait_denom_pub (coin_cmd,
                                            ds->coin_index,
@@ -398,6 +404,12 @@ deposit_run (void *cls,
     TALER_TESTING_interpreter_fail (is);
     return;
   }
+
+  if (NULL != age_commitment)
+  {
+    TALER_age_commitment_hash (age_commitment, &h_age_commitment);
+  }
+
   ds->deposit_fee = denom_pub->fee_deposit;
   GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
                                       &coin_pub.eddsa_pub);
@@ -431,7 +443,8 @@ deposit_run (void *cls,
                                &denom_pub->fee_deposit,
                                &h_wire,
                                &h_contract_terms,
-                               NULL, /* FIXME: extension hash! */
+                               &h_age_commitment,
+                               NULL, /* FIXME: add hash of extensions */
                                &denom_pub->h_key,
                                ds->wallet_timestamp,
                                &merchant_pub,
@@ -445,7 +458,8 @@ deposit_run (void *cls,
                                    payto_uri,
                                    &wire_salt,
                                    &h_contract_terms,
-                                   NULL, /* FIXME: extension object */
+                                   &h_age_commitment,
+                                   NULL, /* FIXME: add hash of extensions */
                                    &coin_pub,
                                    denom_pub_sig,
                                    &denom_pub->key,
@@ -520,6 +534,7 @@ deposit_traits (void *cls,
   const struct TALER_TESTING_Command *coin_cmd;
   /* Will point to coin cmd internals. */
   const struct TALER_CoinSpendPrivateKeyP *coin_spent_priv;
+  struct TALER_AgeCommitment *age_commitment;
 
   if (GNUNET_YES != ds->command_initialized)
   {
@@ -540,7 +555,11 @@ deposit_traits (void *cls,
   if (GNUNET_OK !=
       TALER_TESTING_get_trait_coin_priv (coin_cmd,
                                          ds->coin_index,
-                                         &coin_spent_priv))
+                                         &coin_spent_priv) ||
+      (GNUNET_OK !=
+       TALER_TESTING_get_trait_age_commitment (coin_cmd,
+                                               ds->coin_index,
+                                               &age_commitment)))
   {
     GNUNET_break (0);
     TALER_TESTING_interpreter_fail (ds->is);
@@ -555,6 +574,8 @@ deposit_traits (void *cls,
       /* These traits are always available */
       TALER_TESTING_make_trait_coin_priv (0,
                                           coin_spent_priv),
+      TALER_TESTING_make_trait_age_commitment (0,
+                                               age_commitment),
       TALER_TESTING_make_trait_wire_details (ds->wire_details),
       TALER_TESTING_make_trait_contract_terms (ds->contract_terms),
       TALER_TESTING_make_trait_merchant_priv (&ds->merchant_priv),
diff --git a/src/testing/testing_api_cmd_insert_deposit.c 
b/src/testing/testing_api_cmd_insert_deposit.c
index be49df94..dcda7cf3 100644
--- a/src/testing/testing_api_cmd_insert_deposit.c
+++ b/src/testing/testing_api_cmd_insert_deposit.c
@@ -244,7 +244,7 @@ insert_deposit_run (void *cls,
   {
     uint64_t known_coin_id;
     struct TALER_DenominationHash dph;
-    struct TALER_AgeHash agh;
+    struct TALER_AgeCommitmentHash agh;
 
     if ( (GNUNET_OK !=
           ids->dbc->plugin->start (ids->dbc->plugin->cls,
diff --git a/src/testing/testing_api_cmd_offline_sign_extensions.c 
b/src/testing/testing_api_cmd_offline_sign_extensions.c
new file mode 100644
index 00000000..f39679f9
--- /dev/null
+++ b/src/testing/testing_api_cmd_offline_sign_extensions.c
@@ -0,0 +1,164 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2022 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 testing/testing_api_cmd_offline_sign_extensions.c
+ * @brief run the taler-exchange-offline command to sign extensions (and 
therefore activate them)
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "extensionssign" CMD.
+ */
+struct ExtensionsSignState
+{
+
+  /**
+   * Process for the "extensionssign" command.
+   */
+  struct GNUNET_OS_Process *extensionssign_proc;
+
+  /**
+   * Configuration file used by the command.
+   */
+  const char *config_filename;
+
+};
+
+
+/**
+ * Run the command; calls the `taler-exchange-offline' program.
+ *
+ * @param cls closure.
+ * @param cmd the commaind being run.
+ * @param is interpreter state.
+ */
+static void
+extensionssign_run (void *cls,
+                    const struct TALER_TESTING_Command *cmd,
+                    struct TALER_TESTING_Interpreter *is)
+{
+  struct ExtensionsSignState *ks = cls;
+
+  ks->extensionssign_proc
+    = GNUNET_OS_start_process (
+        GNUNET_OS_INHERIT_STD_ALL,
+        NULL, NULL, NULL,
+        "taler-exchange-offline",
+        "taler-exchange-offline",
+        "-c", ks->config_filename,
+        "-L", "INFO",
+        "extensions",
+        "sign",
+        "upload",
+        NULL);
+  if (NULL == ks->extensionssign_proc)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "extensionssign" CMD, and possibly kills its
+ * process if it did not terminate correctly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+extensionssign_cleanup (void *cls,
+                        const struct TALER_TESTING_Command *cmd)
+{
+  struct ExtensionsSignState *ks = cls;
+
+  (void) cmd;
+  if (NULL != ks->extensionssign_proc)
+  {
+    GNUNET_break (0 ==
+                  GNUNET_OS_process_kill (ks->extensionssign_proc,
+                                          SIGKILL));
+    GNUNET_OS_process_wait (ks->extensionssign_proc);
+    GNUNET_OS_process_destroy (ks->extensionssign_proc);
+    ks->extensionssign_proc = NULL;
+  }
+  GNUNET_free (ks);
+}
+
+
+/**
+ * Offer "extensionssign" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+extensionssign_traits (void *cls,
+                       const void **ret,
+                       const char *trait,
+                       unsigned int index)
+{
+  struct ExtensionsSignState *ks = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_process (&ks->extensionssign_proc),
+    TALER_TESTING_trait_end ()
+  };
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_extensions (const char *label,
+                                                const char *config_filename)
+{
+  struct ExtensionsSignState *ks;
+
+  ks = GNUNET_new (struct ExtensionsSignState);
+  ks->config_filename = config_filename;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ks,
+      .label = label,
+      .run = &extensionssign_run,
+      .cleanup = &extensionssign_cleanup,
+      .traits = &extensionssign_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_exec_offline_sign_extensions.c */
diff --git a/src/testing/testing_api_cmd_refresh.c 
b/src/testing/testing_api_cmd_refresh.c
index de3efd13..11c88c19 100644
--- a/src/testing/testing_api_cmd_refresh.c
+++ b/src/testing/testing_api_cmd_refresh.c
@@ -70,6 +70,11 @@ struct TALER_TESTING_FreshCoinData
    */
   struct TALER_CoinSpendPrivateKeyP coin_priv;
 
+  /*
+   * Age commitment for the coin, NULL if not applicable.
+   */
+  struct TALER_AgeCommitment *age_commitment;
+
   /**
    * The blinding key (needed for recoup operations).
    */
@@ -132,6 +137,11 @@ struct RefreshMeltState
    */
   const struct TALER_CoinSpendPrivateKeyP *melt_priv;
 
+  /*
+   * Age commitment for the coin, NULL if not applicable.
+   */
+  struct TALER_AgeCommitment *age_commitment;
+
   /**
    * Task scheduled to try later.
    */
@@ -1038,6 +1048,7 @@ melt_run (void *cls,
     const struct TALER_DenominationSignature *melt_sig;
     const struct TALER_EXCHANGE_DenomPublicKey *melt_denom_pub;
     const struct TALER_TESTING_Command *coin_command;
+    bool age_restricted;
 
     if (NULL == (coin_command
                    = TALER_TESTING_interpreter_lookup_command (
@@ -1058,6 +1069,16 @@ melt_run (void *cls,
       TALER_TESTING_interpreter_fail (rms->is);
       return;
     }
+    if (GNUNET_OK !=
+        TALER_TESTING_get_trait_age_commitment (coin_command,
+                                                0,
+                                                &rms->age_commitment))
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (rms->is);
+      return;
+    }
+
     if (GNUNET_OK !=
         TALER_TESTING_get_trait_denom_sig (coin_command,
                                            0,
@@ -1067,6 +1088,7 @@ melt_run (void *cls,
       TALER_TESTING_interpreter_fail (rms->is);
       return;
     }
+
     if (GNUNET_OK !=
         TALER_TESTING_get_trait_denom_pub (coin_command,
                                            0,
@@ -1076,9 +1098,11 @@ melt_run (void *cls,
       TALER_TESTING_interpreter_fail (rms->is);
       return;
     }
+
     /* Melt amount starts with the melt fee of the old coin; we'll add the
        values and withdraw fees of the fresh coins next */
     melt_amount = melt_denom_pub->fee_refresh;
+    age_restricted = melt_denom_pub->key.age_mask.mask != 0;
     for (unsigned int i = 0; i<num_fresh_coins; i++)
     {
       const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk;
@@ -1096,7 +1120,8 @@ melt_run (void *cls,
         return;
       }
       fresh_pk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange),
-                                        &fresh_amount);
+                                        &fresh_amount,
+                                        age_restricted);
       if (NULL == fresh_pk)
       {
         GNUNET_break (0);
@@ -1117,12 +1142,36 @@ melt_run (void *cls,
       TALER_denom_pub_deep_copy (&rms->fresh_pks[i].key,
                                  &fresh_pk->key);
     } /* end for */
+
     rms->refresh_data.melt_priv = *rms->melt_priv;
     rms->refresh_data.melt_amount = melt_amount;
     rms->refresh_data.melt_sig = *melt_sig;
     rms->refresh_data.melt_pk = *melt_denom_pub;
     rms->refresh_data.fresh_pks = rms->fresh_pks;
     rms->refresh_data.fresh_pks_len = num_fresh_coins;
+/* FIXME-oec:  is this needed _here_?
+    {
+      struct TALER_AgeCommitment *ac = NULL;
+
+      GNUNET_assert (age_restricted == (NULL != rms->age_commitment));
+
+      if (NULL != rms->age_commitment)
+      {
+        uint32_t seed = GNUNET_CRYPTO_random_u32 (
+          GNUNET_CRYPTO_QUALITY_WEAK,
+          UINT32_MAX);
+
+        GNUNET_assert (GNUNET_OK ==
+                       TALER_age_commitment_derive (
+                         rms->age_commitment,
+                         seed,
+                         ac));
+      }
+
+      rms->refresh_data.age_commitment = ac
+    }
+*/
+
     rms->rmh = TALER_EXCHANGE_melt (is->exchange,
                                     &rms->rms,
                                     &rms->refresh_data,
@@ -1207,6 +1256,8 @@ melt_traits (void *cls,
                                           &rms->fresh_pks[index]),
       TALER_TESTING_make_trait_coin_priv (0,
                                           rms->melt_priv),
+      TALER_TESTING_make_trait_age_commitment (index,
+                                               rms->age_commitment),
       TALER_TESTING_make_trait_exchange_wd_value (index,
                                                   &rms->mbds[index].alg_value),
       TALER_TESTING_make_trait_refresh_secret (&rms->rms),
@@ -1370,6 +1421,9 @@ refresh_reveal_traits (void *cls,
       TALER_TESTING_make_trait_coin_priv (
         index,
         &rrs->fresh_coins[index].coin_priv),
+      TALER_TESTING_make_trait_age_commitment (
+        index,
+        rrs->fresh_coins[index].age_commitment),
       TALER_TESTING_make_trait_denom_pub (
         index,
         rrs->fresh_coins[index].pk),
diff --git a/src/testing/testing_api_cmd_withdraw.c 
b/src/testing/testing_api_cmd_withdraw.c
index c7265c6c..e5e8adfd 100644
--- a/src/testing/testing_api_cmd_withdraw.c
+++ b/src/testing/testing_api_cmd_withdraw.c
@@ -27,6 +27,7 @@
 #include <microhttpd.h>
 #include <gnunet/gnunet_curl_lib.h>
 #include "taler_signatures.h"
+#include "taler_extensions.h"
 #include "taler_testing_lib.h"
 #include "backoff.h"
 
@@ -131,6 +132,18 @@ struct WithdrawState
    */
   struct TALER_PlanchetMasterSecretP ps;
 
+  /**
+   * An age > 0 signifies age restriction is required
+   */
+  uint8_t age;
+
+  /**
+   * If age > 0, put here the corresponding age commitment and its hash,
+   * respectivelly, NULL otherwise.
+   */
+  struct TALER_AgeCommitment *age_commitment;
+  struct TALER_AgeCommitmentHash *h_age_commitment;
+
   /**
    * Reserve history entry that corresponds to this operation.
    * Will be of type #TALER_EXCHANGE_RTT_WITHDRAWAL.
@@ -382,12 +395,14 @@ withdraw_run (void *cls,
     = TALER_TESTING_interpreter_lookup_command (
         is,
         ws->reserve_reference);
+
   if (NULL == create_reserve)
   {
     GNUNET_break (0);
     TALER_TESTING_interpreter_fail (is);
     return;
   }
+
   if (GNUNET_OK !=
       TALER_TESTING_get_trait_reserve_priv (create_reserve,
                                             &rp))
@@ -396,6 +411,7 @@ withdraw_run (void *cls,
     TALER_TESTING_interpreter_fail (is);
     return;
   }
+
   if (NULL == ws->exchange_url)
     ws->exchange_url
       = GNUNET_strdup (TALER_EXCHANGE_get_base_url (is->exchange));
@@ -405,6 +421,7 @@ withdraw_run (void *cls,
   ws->reserve_payto_uri
     = TALER_payto_from_reserve (ws->exchange_url,
                                 &ws->reserve_pub);
+
   if (NULL == ws->reuse_coin_key_ref)
   {
     TALER_planchet_master_setup_random (&ws->ps);
@@ -429,10 +446,12 @@ withdraw_run (void *cls,
                                                             &ps));
     ws->ps = *ps;
   }
+
   if (NULL == ws->pk)
   {
     dpk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange),
-                                 &ws->amount);
+                                 &ws->amount,
+                                 ws->age > 0);
     if (NULL == dpk)
     {
       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -450,18 +469,24 @@ withdraw_run (void *cls,
   {
     ws->amount = ws->pk->value;
   }
+
   ws->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL;
   GNUNET_assert (0 <=
                  TALER_amount_add (&ws->reserve_history.amount,
                                    &ws->amount,
                                    &ws->pk->fee_withdraw));
   ws->reserve_history.details.withdraw.fee = ws->pk->fee_withdraw;
-  ws->wsh = TALER_EXCHANGE_withdraw (is->exchange,
-                                     ws->pk,
-                                     rp,
-                                     &ws->ps,
-                                     &reserve_withdraw_cb,
-                                     ws);
+
+  {
+
+    ws->wsh = TALER_EXCHANGE_withdraw (is->exchange,
+                                       ws->pk,
+                                       rp,
+                                       &ws->ps,
+                                       ws->h_age_commitment,
+                                       &reserve_withdraw_cb,
+                                       ws);
+  }
   if (NULL == ws->wsh)
   {
     GNUNET_break (0);
@@ -503,6 +528,16 @@ withdraw_cleanup (void *cls,
     TALER_EXCHANGE_destroy_denomination_key (ws->pk);
     ws->pk = NULL;
   }
+  if (NULL != ws->age_commitment)
+  {
+    GNUNET_free (ws->age_commitment);
+    ws->age_commitment = NULL;
+  }
+  if (NULL != ws->h_age_commitment)
+  {
+    GNUNET_free (ws->h_age_commitment);
+    ws->h_age_commitment = NULL;
+  }
   GNUNET_free (ws->exchange_url);
   GNUNET_free (ws->reserve_payto_uri);
   GNUNET_free (ws);
@@ -538,7 +573,7 @@ withdraw_traits (void *cls,
                                                 &ws->exchange_vals),
     TALER_TESTING_make_trait_denom_pub (0 /* only one coin */,
                                         ws->pk),
-    TALER_TESTING_make_trait_denom_sig (0 /* only one coin */,
+    TALER_TESTING_make_trait_denom_sig (index /* only one coin */,
                                         &ws->sig),
     TALER_TESTING_make_trait_reserve_priv (&ws->reserve_priv),
     TALER_TESTING_make_trait_reserve_pub (&ws->reserve_pub),
@@ -548,6 +583,8 @@ withdraw_traits (void *cls,
       (const char **) &ws->reserve_payto_uri),
     TALER_TESTING_make_trait_exchange_url (
       (const char **) &ws->exchange_url),
+    TALER_TESTING_make_trait_age_commitment (index, ws->age_commitment),
+    TALER_TESTING_make_trait_h_age_commitment (index, ws->h_age_commitment),
     TALER_TESTING_trait_end ()
   };
 
@@ -567,6 +604,7 @@ withdraw_traits (void *cls,
  * @param label command label.
  * @param reserve_reference command providing us with a reserve to withdraw 
from
  * @param amount how much we withdraw.
+ * @param age if > 0, age restriction is activated
  * @param expected_response_code which HTTP response code
  *        we expect from the exchange.
  * @return the withdraw command to be executed by the interpreter.
@@ -575,11 +613,45 @@ struct TALER_TESTING_Command
 TALER_TESTING_cmd_withdraw_amount (const char *label,
                                    const char *reserve_reference,
                                    const char *amount,
+                                   const uint8_t age,
                                    unsigned int expected_response_code)
 {
   struct WithdrawState *ws;
 
   ws = GNUNET_new (struct WithdrawState);
+
+  ws->age = age;
+  if (0 < age)
+  {
+    struct TALER_AgeCommitment *ac;
+    struct TALER_AgeCommitmentHash *hac;
+    uint32_t seed;
+    struct TALER_AgeMask mask;
+
+    ac = GNUNET_new (struct TALER_AgeCommitment);
+    hac = GNUNET_new (struct TALER_AgeCommitmentHash);
+    seed = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX);
+    mask = TALER_extensions_age_restriction_ageMask ();
+
+    if (GNUNET_OK !=
+        TALER_age_restriction_commit (
+          &mask,
+          age,
+          seed,
+          ac))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Failed to generate age commitment for age %d at %s\n",
+                  age,
+                  label);
+      GNUNET_assert (0);
+    }
+
+    TALER_age_commitment_hash (ac,hac);
+    ws->age_commitment = ac;
+    ws->h_age_commitment = hac;
+  }
+
   ws->reserve_reference = reserve_reference;
   if (GNUNET_OK !=
       TALER_string_to_amount (amount,
@@ -615,6 +687,7 @@ TALER_TESTING_cmd_withdraw_amount (const char *label,
  * @param label command label.
  * @param reserve_reference command providing us with a reserve to withdraw 
from
  * @param amount how much we withdraw.
+ * @param age if > 0, age restriction is activated
  * @param coin_ref reference to (withdraw/reveal) command of a coin
  *        from which we should re-use the private key
  * @param expected_response_code which HTTP response code
@@ -626,6 +699,7 @@ TALER_TESTING_cmd_withdraw_amount_reuse_key (
   const char *label,
   const char *reserve_reference,
   const char *amount,
+  uint8_t age,
   const char *coin_ref,
   unsigned int expected_response_code)
 {
@@ -634,6 +708,7 @@ TALER_TESTING_cmd_withdraw_amount_reuse_key (
   cmd = TALER_TESTING_cmd_withdraw_amount (label,
                                            reserve_reference,
                                            amount,
+                                           age,
                                            expected_response_code);
   {
     struct WithdrawState *ws = cmd.cls;
diff --git a/src/testing/testing_api_helpers_exchange.c 
b/src/testing/testing_api_helpers_exchange.c
index 8e0e0298..1eecbfeb 100644
--- a/src/testing/testing_api_helpers_exchange.c
+++ b/src/testing/testing_api_helpers_exchange.c
@@ -27,6 +27,7 @@
 #include "taler_json_lib.h"
 #include <gnunet/gnunet_curl_lib.h>
 #include "taler_signatures.h"
+#include "taler_extensions.h"
 #include "taler_testing_lib.h"
 
 /**
@@ -312,6 +313,9 @@ sign_keys_for_exchange (void *cls,
   char *exchange_master_pub;
   int ret;
 
+  /* Load the age restriction mask from the configuration */
+  TALER_extensions_load_taler_config (cfg);
+
   if (GNUNET_OK !=
       GNUNET_CONFIGURATION_get_value_string (cfg,
                                              "exchange",
@@ -402,7 +406,8 @@ TALER_TESTING_prepare_exchange (const char *config_filename,
 
 const struct TALER_EXCHANGE_DenomPublicKey *
 TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys,
-                       const struct TALER_Amount *amount)
+                       const struct TALER_Amount *amount,
+                       bool age_restricted)
 {
   struct GNUNET_TIME_Timestamp now;
   struct TALER_EXCHANGE_DenomPublicKey *pk;
@@ -419,7 +424,8 @@ TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys 
*keys,
                                      pk->valid_from)) &&
          (GNUNET_TIME_timestamp_cmp (now,
                                      <,
-                                     pk->withdraw_valid_until)) )
+                                     pk->withdraw_valid_until)) &&
+         (age_restricted == (0 != pk->key.age_mask.mask)) )
       return pk;
   }
   /* do 2nd pass to check if expiration times are to blame for
@@ -435,7 +441,8 @@ TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys 
*keys,
                                      pk->valid_from) ||
           GNUNET_TIME_timestamp_cmp (now,
                                      >,
-                                     pk->withdraw_valid_until) ) )
+                                     pk->withdraw_valid_until) ) &&
+         (age_restricted == (0 != pk->key.age_mask.mask)) )
     {
       GNUNET_log
         (GNUNET_ERROR_TYPE_WARNING,
diff --git a/src/util/crypto.c b/src/util/crypto.c
index 13b9188c..6bea984f 100644
--- a/src/util/crypto.c
+++ b/src/util/crypto.c
@@ -20,11 +20,16 @@
  * @author Florian Dold
  * @author Benedikt Mueller
  * @author Christian Grothoff
+ * @author Özgür Kesim
  */
 #include "platform.h"
 #include "taler_util.h"
 #include <gcrypt.h>
 
+/**
+ * Used in TALER_AgeCommitmentHash_isNullOrZero for comparison
+ */
+const struct TALER_AgeCommitmentHash TALER_ZeroAgeCommitmentHash = {0};
 
 /**
  * Function called by libgcrypt on serious errors.
@@ -83,12 +88,11 @@ TALER_test_coin_valid (const struct TALER_CoinPublicInfo 
*coin_public_info,
                  GNUNET_memcmp (&d_hash,
                                 &coin_public_info->denom_pub_hash));
 #endif
-  // FIXME-Oec: replace with function that
-  // also hashes the age vector if we have
-  // one!
-  GNUNET_CRYPTO_hash (&coin_public_info->coin_pub,
-                      sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
-                      &c_hash.hash);
+
+  TALER_coin_pub_hash (&coin_public_info->coin_pub,
+                       &coin_public_info->age_commitment_hash,
+                       &c_hash);
+
   if (GNUNET_OK !=
       TALER_denom_pub_verify (denom_pub,
                               &coin_public_info->denom_sig,
@@ -251,6 +255,7 @@ TALER_planchet_prepare (const struct 
TALER_DenominationPublicKey *dk,
                         const struct TALER_ExchangeWithdrawValues *alg_values,
                         const union TALER_DenominationBlindingKeyP *bks,
                         const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+                        const struct TALER_AgeCommitmentHash *ach,
                         struct TALER_CoinPubHash *c_hash,
                         struct TALER_PlanchetDetail *pd
                         )
@@ -263,7 +268,7 @@ TALER_planchet_prepare (const struct 
TALER_DenominationPublicKey *dk,
   if (GNUNET_OK !=
       TALER_denom_blind (dk,
                          bks,
-                         NULL, /* FIXME-Oec */
+                         ach,
                          &coin_pub,
                          alg_values,
                          c_hash,
@@ -291,6 +296,7 @@ TALER_planchet_to_coin (
   const struct TALER_BlindedDenominationSignature *blind_sig,
   const union TALER_DenominationBlindingKeyP *bks,
   const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  const struct TALER_AgeCommitmentHash *ach,
   const struct TALER_CoinPubHash *c_hash,
   const struct TALER_ExchangeWithdrawValues *alg_values,
   struct TALER_FreshCoin *coin)
@@ -321,7 +327,9 @@ TALER_planchet_to_coin (
     TALER_denom_sig_free (&coin->sig);
     return GNUNET_SYSERR;
   }
+
   coin->coin_priv = *coin_priv;
+  coin->h_age_commitment = ach;
   return GNUNET_OK;
 }
 
@@ -396,10 +404,10 @@ TALER_refresh_get_commitment (struct 
TALER_RefreshCommitmentP *rc,
 
 void
 TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                     const struct TALER_AgeHash *age_commitment_hash,
+                     const struct TALER_AgeCommitmentHash *ach,
                      struct TALER_CoinPubHash *coin_h)
 {
-  if (NULL == age_commitment_hash)
+  if (TALER_AgeCommitmentHash_isNullOrZero (ach))
   {
     /* No age commitment was set */
     GNUNET_CRYPTO_hash (&coin_pub->eddsa_pub,
@@ -411,14 +419,14 @@ TALER_coin_pub_hash (const struct 
TALER_CoinSpendPublicKeyP *coin_pub,
     /* Coin comes with age commitment.  Take the hash of the age commitment
      * into account */
     const size_t key_s = sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey);
-    const size_t age_s = sizeof(struct TALER_AgeHash);
+    const size_t age_s = sizeof(struct TALER_AgeCommitmentHash);
     char data[key_s + age_s];
 
     GNUNET_memcpy (&data[0],
                    &coin_pub->eddsa_pub,
                    key_s);
     GNUNET_memcpy (&data[key_s],
-                   age_commitment_hash,
+                   ach,
                    age_s);
     GNUNET_CRYPTO_hash (&data,
                         key_s + age_s,
@@ -427,4 +435,276 @@ TALER_coin_pub_hash (const struct 
TALER_CoinSpendPublicKeyP *coin_pub,
 }
 
 
+void
+TALER_age_commitment_hash (
+  const struct TALER_AgeCommitment *commitment,
+  struct TALER_AgeCommitmentHash *ahash)
+{
+  struct GNUNET_HashContext *hash_context;
+  struct GNUNET_HashCode hash;
+
+  GNUNET_assert (NULL != ahash);
+  if (NULL == commitment)
+  {
+    memset (ahash, 0, sizeof(struct TALER_AgeCommitmentHash));
+    return;
+  }
+
+  GNUNET_assert (__builtin_popcount (commitment->mask.mask) - 1 ==
+                 commitment->num_pub);
+
+  hash_context = GNUNET_CRYPTO_hash_context_start ();
+
+  for (size_t i = 0; i < commitment->num_pub; i++)
+  {
+    GNUNET_CRYPTO_hash_context_read (hash_context,
+                                     &commitment->pub[i],
+                                     sizeof(struct
+                                            GNUNET_CRYPTO_EddsaPublicKey));
+  }
+
+  GNUNET_CRYPTO_hash_context_finish (hash_context,
+                                     &hash);
+  GNUNET_memcpy (&ahash->shash.bits,
+                 &hash.bits,
+                 sizeof(ahash->shash.bits));
+}
+
+
+/* To a given age value between 0 and 31, returns the index of the age group
+ * defined by the given mask.
+ */
+static uint8_t
+get_age_group (
+  const struct TALER_AgeMask *mask,
+  uint8_t age)
+{
+  uint32_t m = mask->mask;
+  uint8_t i = 0;
+
+  while (m > 0)
+  {
+    if (0 >= age)
+      break;
+    m = m >> 1;
+    i += m & 1;
+    age--;
+  }
+  return i;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_age_restriction_commit (
+  const struct TALER_AgeMask *mask,
+  const uint8_t age,
+  const uint32_t seed,
+  struct TALER_AgeCommitment *new)
+{
+  uint8_t num_pub = __builtin_popcount (mask->mask) - 1;
+  uint8_t num_priv = get_age_group (mask, age) - 1;
+  size_t i;
+
+  GNUNET_assert (NULL != new);
+  GNUNET_assert (mask->mask & 1); /* fist bit must have been set */
+  GNUNET_assert (0 <= num_priv);
+  GNUNET_assert (31 > num_priv);
+
+  new->mask.mask = mask->mask;
+  new->num_pub = num_pub;
+  new->num_priv = num_priv;
+
+  new->pub = GNUNET_new_array (
+    num_pub,
+    struct TALER_AgeCommitmentPublicKeyP);
+  new->priv = GNUNET_new_array (
+    num_priv,
+    struct TALER_AgeCommitmentPrivateKeyP);
+
+  /* Create as many private keys as we need */
+  for (i = 0; i < num_priv; i++)
+  {
+    uint32_t seedBE = htonl (seed + i);
+
+    if  (GNUNET_OK !=
+         GNUNET_CRYPTO_kdf (&new->priv[i],
+                            sizeof (new->priv[i]),
+                            &seedBE,
+                            sizeof (seedBE),
+                            "taler-age-commitment-derivation",
+                            strlen (
+                              "taler-age-commitment-derivation"),
+                            NULL, 0))
+      goto FAIL;
+
+    GNUNET_CRYPTO_eddsa_key_get_public (&new->priv[i].eddsa_priv,
+                                        &new->pub[i].eddsa_pub);
+  }
+
+  /* Fill the rest of the public keys with random values */
+  for (; i<num_pub; i++)
+    GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+                                &new->pub[i],
+                                sizeof(new->pub[i]));
+
+  return GNUNET_OK;
+
+FAIL:
+  GNUNET_free (new->pub);
+  GNUNET_free (new->priv);
+  return GNUNET_SYSERR;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_age_commitment_derive (
+  const struct TALER_AgeCommitment *orig,
+  const uint32_t seed,
+  struct TALER_AgeCommitment *new)
+{
+  struct GNUNET_CRYPTO_EccScalar val;
+
+  /*
+  * age commitment consists of GNUNET_CRYPTO_Eddsa{Private,Public}Key
+  *
+  * GNUNET_CRYPTO_EddsaPrivateKey is a
+  *   unsigned char d[256 / 8];
+  *
+  * GNUNET_CRYPTO_EddsaPublicKey is a
+  *   unsigned char q_y[256 / 8];
+  *
+  * We want to multiply, both, the Private Key by an integer factor and the
+  * public key (point on curve) with the equivalent scalar.
+  *
+  * From the seed we will derive
+  *   1. a scalar to multiply the public keys with
+  *   2. a factor to multiply the private key with
+  *
+  * Invariants:
+  *   point*scalar == public(private*factor)
+  *
+  * A point on a curve is GNUNET_CRYPTO_EccPoint which is
+  *   unsigned char v[256 / 8];
+  *
+  * A ECC scaler for use in point multiplications is a
+  * GNUNET_CRYPTO_EccScalar which is a
+  *   unsigned car v[256 / 8];
+  * */
+
+  GNUNET_assert (NULL != new);
+  GNUNET_assert (orig->num_pub == __builtin_popcount (orig->mask.mask) - 1);
+  GNUNET_assert (orig->num_priv <= orig->num_pub);
+
+  new->mask = orig->mask;
+  new->num_pub = orig->num_pub;
+  new->num_priv = orig->num_priv;
+  new->pub = GNUNET_new_array (
+    new->num_pub,
+    struct TALER_AgeCommitmentPublicKeyP);
+  new->priv = GNUNET_new_array (
+    new->num_priv,
+    struct TALER_AgeCommitmentPrivateKeyP);
+
+
+  GNUNET_CRYPTO_ecc_scalar_from_int (seed, &val);
+
+  /* scalar multiply the public keys on the curve */
+  for (size_t i = 0; i < orig->num_pub; i++)
+  {
+    /* We shift all keys by the same scalar */
+    struct GNUNET_CRYPTO_EccPoint *p = (struct
+                                        GNUNET_CRYPTO_EccPoint *) 
&orig->pub[i];
+    struct GNUNET_CRYPTO_EccPoint *np = (struct
+                                         GNUNET_CRYPTO_EccPoint *) 
&new->pub[i];
+    if (GNUNET_OK !=
+        GNUNET_CRYPTO_ecc_pmul_mpi (
+          p,
+          &val,
+          np))
+      goto FAIL;
+
+  }
+
+  /* multiply the private keys */
+  /* we borough ideas from GNUNET_CRYPTO_ecdsa_private_key_derive */
+  {
+    uint32_t seedBE;
+    uint8_t dc[32];
+    gcry_mpi_t f, x, d, n;
+    gcry_ctx_t ctx;
+
+    GNUNET_assert (0==gcry_mpi_ec_new (&ctx,NULL, "Ed25519"));
+    n = gcry_mpi_ec_get_mpi ("n", ctx, 1);
+
+    /* make the seed big endian */
+    seedBE = GNUNET_htonll (seed);
+
+    GNUNET_CRYPTO_mpi_scan_unsigned (&f, &seedBE, sizeof(seedBE));
+
+    for (size_t i = 0; i < orig->num_priv; i++)
+    {
+
+      /* convert to big endian for libgrypt */
+      for (size_t j = 0; j < 32; j++)
+        dc[i] = orig->priv[i].eddsa_priv.d[31 - j];
+      GNUNET_CRYPTO_mpi_scan_unsigned (&x, dc, sizeof(dc));
+
+      d = gcry_mpi_new (256);
+      gcry_mpi_mulm (d, f, x, n);
+      gcry_mpi_release (x);
+      gcry_mpi_release (d);
+      gcry_mpi_release (n);
+      gcry_mpi_release (d);
+      GNUNET_CRYPTO_mpi_print_unsigned (dc, sizeof(dc), d);
+
+      for (size_t j = 0; j <32; j++)
+        new->priv[i].eddsa_priv.d[j] = dc[31 - 1];
+
+      sodium_memzero (dc, sizeof(dc));
+
+      /* TODO:
+       * make sure that the calculated private key generate the same public
+       * keys */
+    }
+
+    gcry_mpi_release (f);
+    gcry_ctx_release (ctx);
+  }
+
+  return GNUNET_OK;
+
+FAIL:
+  GNUNET_free (new->pub);
+  GNUNET_free (new->priv);
+  return GNUNET_SYSERR;
+}
+
+
+void
+TALER_age_restriction_commmitment_free_inside (
+  struct TALER_AgeCommitment *commitment)
+{
+  if (NULL == commitment)
+    return;
+
+  if (NULL != commitment->priv)
+  {
+    GNUNET_CRYPTO_zero_keys (
+      commitment->priv,
+      sizeof(*commitment->priv) * commitment->num_priv);
+
+    GNUNET_free (commitment->priv);
+    commitment->priv = NULL;
+  }
+
+  if (NULL != commitment->pub)
+  {
+    GNUNET_free (commitment->pub);
+    commitment->priv = NULL;
+  }
+
+  /* Caller is responsible for commitment itself */
+}
+
+
 /* end of crypto.c */
diff --git a/src/util/denom.c b/src/util/denom.c
index 783e9a36..7c2c42c9 100644
--- a/src/util/denom.c
+++ b/src/util/denom.c
@@ -297,14 +297,14 @@ enum GNUNET_GenericReturnValue
 TALER_denom_blind (
   const struct TALER_DenominationPublicKey *dk,
   const union TALER_DenominationBlindingKeyP *coin_bks,
-  const struct TALER_AgeHash *age_commitment_hash,
+  const struct TALER_AgeCommitmentHash *ach,
   const struct TALER_CoinSpendPublicKeyP *coin_pub,
   const struct TALER_ExchangeWithdrawValues *alg_values,
   struct TALER_CoinPubHash *c_hash,
   struct TALER_BlindedPlanchet *blinded_planchet)
 {
   TALER_coin_pub_hash (coin_pub,
-                       age_commitment_hash,
+                       ach,
                        c_hash);
   switch (dk->cipher)
   {
diff --git a/src/util/test_crypto.c b/src/util/test_crypto.c
index fbf30e3a..cda17d9b 100644
--- a/src/util/test_crypto.c
+++ b/src/util/test_crypto.c
@@ -152,6 +152,7 @@ test_planchets_rsa (void)
                                          &alg_values,
                                          &bks,
                                          &coin_priv,
+                                         NULL, /* no age commitment */
                                          &c_hash,
                                          &pd));
   GNUNET_assert (GNUNET_OK ==
@@ -164,6 +165,7 @@ test_planchets_rsa (void)
                                          &blind_sig,
                                          &bks,
                                          &coin_priv,
+                                         NULL, /* no age commitment */
                                          &c_hash,
                                          &alg_values,
                                          &coin));
@@ -175,6 +177,8 @@ test_planchets_rsa (void)
 }
 
 
+/** FIXME-oec: Add test for planchets with age commitment hash */
+
 /**
  * @brief Function for CS signatures to derive public R_0 and R_1
  *
@@ -392,10 +396,12 @@ main (int argc,
     return 1;
   if (0 != test_planchets ())
     return 2;
-  if (0 != test_exchange_sigs ())
+  if (0 != test_planchets_with_age_commitment ())
     return 3;
-  if (0 != test_merchant_sigs ())
+  if (0 != test_exchange_sigs ())
     return 4;
+  if (0 != test_merchant_sigs ())
+    return 5;
   return 0;
 }
 
diff --git a/src/util/test_helper_rsa.c b/src/util/test_helper_rsa.c
index 679f5d7f..2ead8a6e 100644
--- a/src/util/test_helper_rsa.c
+++ b/src/util/test_helper_rsa.c
@@ -269,6 +269,7 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh)
   bool success = false;
   struct TALER_PlanchetMasterSecretP ps;
   struct TALER_ExchangeWithdrawValues alg_values;
+  struct TALER_AgeCommitmentHash ach;
   struct TALER_CoinPubHash c_hash;
   struct TALER_CoinSpendPrivateKeyP coin_priv;
   union TALER_DenominationBlindingKeyP bks;
@@ -280,6 +281,9 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh)
   alg_values.cipher = TALER_DENOMINATION_RSA;
   TALER_planchet_setup_coin_priv (&ps, &alg_values, &coin_priv);
   TALER_planchet_blinding_secret_create (&ps, &alg_values, &bks);
+  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+                              &ach,
+                              sizeof(ach));
 
   for (unsigned int i = 0; i<MAX_KEYS; i++)
   {
@@ -296,6 +300,7 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh)
                                              &alg_values,
                                              &bks,
                                              &coin_priv,
+                                             &ach,
                                              &c_hash,
                                              &pd));
       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -440,6 +445,7 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,
   struct GNUNET_TIME_Relative duration;
   struct TALER_PlanchetMasterSecretP ps;
   struct TALER_CoinSpendPrivateKeyP coin_priv;
+  struct TALER_AgeCommitmentHash ach;
   union TALER_DenominationBlindingKeyP bks;
   struct TALER_ExchangeWithdrawValues alg_values;
 
@@ -447,7 +453,9 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,
   alg_values.cipher = TALER_DENOMINATION_RSA;
   TALER_planchet_setup_coin_priv (&ps, &alg_values, &coin_priv);
   TALER_planchet_blinding_secret_create (&ps, &alg_values, &bks);
-
+  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+                              &ach,
+                              sizeof(ach));
   duration = GNUNET_TIME_UNIT_ZERO;
   TALER_CRYPTO_helper_rsa_poll (dh);
   for (unsigned int j = 0; j<NUM_SIGN_PERFS;)
@@ -477,6 +485,7 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,
                                                &alg_values,
                                                &bks,
                                                &coin_priv,
+                                               &ach,
                                                &c_hash,
                                                &pd));
         /* use this key as long as it works */
diff --git a/src/util/wallet_signatures.c b/src/util/wallet_signatures.c
index 01f33ae8..88cd8de0 100644
--- a/src/util/wallet_signatures.c
+++ b/src/util/wallet_signatures.c
@@ -29,6 +29,7 @@ TALER_wallet_deposit_sign (
   const struct TALER_Amount *deposit_fee,
   const struct TALER_MerchantWireHash *h_wire,
   const struct TALER_PrivateContractHash *h_contract_terms,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
   const struct TALER_ExtensionContractHash *h_extensions,
   const struct TALER_DenominationHash *h_denom_pub,
   struct GNUNET_TIME_Timestamp wallet_timestamp,
@@ -48,8 +49,12 @@ TALER_wallet_deposit_sign (
     .merchant = *merchant_pub
   };
 
+  if (NULL != h_age_commitment)
+    dr.h_age_commitment = *h_age_commitment;
+
   if (NULL != h_extensions)
     dr.h_extensions = *h_extensions;
+
   TALER_amount_hton (&dr.amount_with_fee,
                      amount);
   TALER_amount_hton (&dr.deposit_fee,
@@ -66,6 +71,7 @@ TALER_wallet_deposit_verify (
   const struct TALER_Amount *deposit_fee,
   const struct TALER_MerchantWireHash *h_wire,
   const struct TALER_PrivateContractHash *h_contract_terms,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
   const struct TALER_ExtensionContractHash *h_extensions,
   const struct TALER_DenominationHash *h_denom_pub,
   struct GNUNET_TIME_Timestamp wallet_timestamp,
@@ -82,11 +88,17 @@ TALER_wallet_deposit_verify (
     .h_denom_pub = *h_denom_pub,
     .wallet_timestamp = GNUNET_TIME_timestamp_hton (wallet_timestamp),
     .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline),
-    .merchant = *merchant_pub
+    .merchant = *merchant_pub,
+    .h_age_commitment = {{{0}}},
+    .h_extensions = {{{0}}}
   };
 
+  if (NULL != h_age_commitment)
+    dr.h_age_commitment = *h_age_commitment;
+
   if (NULL != h_extensions)
     dr.h_extensions = *h_extensions;
+
   TALER_amount_hton (&dr.amount_with_fee,
                      amount);
   TALER_amount_hton (&dr.deposit_fee,
@@ -131,6 +143,7 @@ TALER_wallet_link_verify (
   const struct TALER_TransferPublicKeyP *transfer_pub,
   const struct TALER_BlindedCoinHash *h_coin_ev,
   const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
   const struct TALER_CoinSpendSignatureP *coin_sig)
 {
   struct TALER_LinkDataPS ldp = {
@@ -138,9 +151,13 @@ TALER_wallet_link_verify (
     .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK),
     .h_denom_pub = *h_denom_pub,
     .transfer_pub = *transfer_pub,
-    .coin_envelope_hash = *h_coin_ev
+    .coin_envelope_hash = *h_coin_ev,
+    .h_age_commitment = {{{0}}}
   };
 
+  if (NULL != h_age_commitment)
+    ldp.h_age_commitment = *h_age_commitment;
+
   return
     GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK,
                                 &ldp,
@@ -263,6 +280,7 @@ TALER_wallet_melt_verify (
   const struct TALER_Amount *melt_fee,
   const struct TALER_RefreshCommitmentP *rc,
   const struct TALER_DenominationHash *h_denom_pub,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
   const struct TALER_CoinSpendPublicKeyP *coin_pub,
   const struct TALER_CoinSpendSignatureP *coin_sig)
 {
@@ -270,9 +288,13 @@ TALER_wallet_melt_verify (
     .purpose.size = htonl (sizeof (melt)),
     .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT),
     .rc = *rc,
-    .h_denom_pub = *h_denom_pub
+    .h_denom_pub = *h_denom_pub,
+    .h_age_commitment = {{{0}}},
   };
 
+  if (NULL != h_age_commitment)
+    melt.h_age_commitment = *h_age_commitment;
+
   TALER_amount_hton (&melt.amount_with_fee,
                      amount_with_fee);
   TALER_amount_hton (&melt.melt_fee,

-- 
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]