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


From: gnunet
Subject: [taler-exchange] branch master updated: [age restriction] progress 16/n - refresh/reveal/link tests
Date: Tue, 22 Feb 2022 14:35:25 +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 26158fc7 [age restriction] progress 16/n - refresh/reveal/link tests
26158fc7 is described below

commit 26158fc72505be6323282dc39509fd531c10a290
Author: Özgür Kesim <oec-taler@kesim.org>
AuthorDate: Tue Feb 22 14:27:15 2022 +0100

    [age restriction] progress 16/n - refresh/reveal/link tests
    
    Age restriction works now with withdraw, melt/refresh/reveal and link,
    including tests.
    
    However, there is still a problem with the tests:  The melting operation
    "refresh-melt-failing-age" that should fail (because of conflict), but
    currently fails for other reasons.  I decided to disable that particular
    test (and the next) and submit the patch I have so far.
---
 src/benchmark/taler-aggregator-benchmark.c         |   6 +-
 src/exchange/taler-exchange-httpd_melt.c           |   9 +-
 .../taler-exchange-httpd_refreshes_reveal.c        |  29 ++---
 src/exchangedb/exchange-0001.sql                   |  11 +-
 src/exchangedb/irbt_callbacks.c                    |   2 +
 src/exchangedb/plugin_exchangedb_postgres.c        |  48 +++++---
 src/include/taler_crypto_lib.h                     |  10 +-
 src/include/taler_exchange_service.h               |  12 +-
 src/include/taler_exchangedb_plugin.h              |   8 +-
 src/lib/exchange_api_common.c                      |  30 +++--
 src/lib/exchange_api_link.c                        |  36 +++---
 src/lib/exchange_api_melt.c                        |   7 ++
 src/lib/exchange_api_refresh_common.c              |  38 +++----
 src/lib/exchange_api_refresh_common.h              |   4 +-
 src/lib/exchange_api_refreshes_reveal.c            |  21 ++++
 src/testing/test_exchange_api.c                    | 121 ++++++++++++++++++++-
 src/testing/testing_api_cmd_deposit.c              |  14 +--
 src/testing/testing_api_cmd_refresh.c              |  66 +++++------
 src/testing/testing_api_cmd_withdraw.c             |  10 +-
 src/util/crypto.c                                  | 102 +++++++++--------
 src/util/wallet_signatures.c                       |   8 +-
 21 files changed, 381 insertions(+), 211 deletions(-)

diff --git a/src/benchmark/taler-aggregator-benchmark.c 
b/src/benchmark/taler-aggregator-benchmark.c
index 11ceec90..6d5df1e6 100644
--- a/src/benchmark/taler-aggregator-benchmark.c
+++ b/src/benchmark/taler-aggregator-benchmark.c
@@ -528,14 +528,14 @@ run (void *cls,
                                            &bks);
 
     {
-      uint32_t seed;
+      uint64_t seed;
       struct TALER_AgeMask mask = {
         .mask = 1 || 1 << 8 || 1 << 12 || 1 << 16 || 1 << 18
       };
       struct TALER_AgeCommitment ac = {0};
 
-      seed = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
-                                       UINT32_MAX);
+      seed = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
+                                       UINT64_MAX);
 
       GNUNET_assert (GNUNET_OK ==
                      TALER_age_restriction_commit (
diff --git a/src/exchange/taler-exchange-httpd_melt.c 
b/src/exchange/taler-exchange-httpd_melt.c
index 03075280..049fd09a 100644
--- a/src/exchange/taler-exchange-httpd_melt.c
+++ b/src/exchange/taler-exchange-httpd_melt.c
@@ -425,7 +425,7 @@ TEH_handler_melt (struct MHD_Connection *connection,
     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 ("h_age_commitment",
+      GNUNET_JSON_spec_fixed_auto ("age_commitment_hash",
                                    
&rmc.refresh_session.coin.h_age_commitment)),
     GNUNET_JSON_spec_fixed_auto ("confirm_sig",
                                  &rmc.refresh_session.coin_sig),
@@ -440,10 +440,9 @@ TEH_handler_melt (struct MHD_Connection *connection,
     GNUNET_JSON_spec_end ()
   };
 
-  memset (&rmc,
-          0,
-          sizeof (rmc));
+  memset (&rmc, 0, sizeof (rmc));
   rmc.refresh_session.coin.coin_pub = *coin_pub;
+
   {
     enum GNUNET_GenericReturnValue ret;
     ret = TALER_MHD_parse_json_data (connection,
@@ -452,8 +451,10 @@ TEH_handler_melt (struct MHD_Connection *connection,
     if (GNUNET_OK != ret)
       return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
   }
+
   rmc.have_rms = (NULL != json_object_get (root,
                                            "rms"));
+
   {
     MHD_RESULT res;
 
diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.c 
b/src/exchange/taler-exchange-httpd_refreshes_reveal.c
index bace776d..23620f87 100644
--- a/src/exchange/taler-exchange-httpd_refreshes_reveal.c
+++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.c
@@ -277,7 +277,7 @@ check_commitment (struct RevealContext *rctx,
           union TALER_DenominationBlindingKeyP bks;
           const struct TALER_ExchangeWithdrawValues *alg_value
             = &rctx->rrcs[j].exchange_vals;
-          struct TALER_PlanchetDetail pd;
+          struct TALER_PlanchetDetail pd = {0};
           struct TALER_AgeCommitmentHash *hac = NULL;
           struct TALER_CoinPubHashP c_hash;
           struct TALER_PlanchetMasterSecretP ps;
@@ -298,15 +298,16 @@ check_commitment (struct RevealContext *rctx,
           {
             struct TALER_AgeCommitment ac = {0};
             struct TALER_AgeCommitmentHash h = {0};
+            uint64_t seed = (uint64_t) ts.key.bits[0]
+                            | (uint64_t) ts.key.bits[1] << 32;
 
             GNUNET_assert (GNUNET_OK ==
                            TALER_age_commitment_derive (
                              rctx->old_age_commitment,
-                             ts.key.bits[0],
+                             seed,
                              &ac));
 
             TALER_age_commitment_hash (&ac, &h);
-
             hac = &h;
           }
 
@@ -590,7 +591,7 @@ resolve_refreshes_reveal_denominations (struct 
MHD_Connection *connection,
   if (TEH_age_restriction_enabled &&
       ((NULL == old_age_commitment_json) !=
        TALER_AgeCommitmentHash_isNullOrZero (
-         &rctx->melt.session.h_age_commitment)))
+         &rctx->melt.session.coin.h_age_commitment)))
   {
     GNUNET_break (0);
     return MHD_NO;
@@ -602,7 +603,7 @@ resolve_refreshes_reveal_denominations (struct 
MHD_Connection *connection,
       (NULL != old_age_commitment_json))
   {
     enum GNUNET_GenericReturnValue res;
-    struct TALER_AgeCommitment *oac = rctx->old_age_commitment;
+    struct TALER_AgeCommitment *oac;
     size_t ng = json_array_size (old_age_commitment_json);
     bool failed = true;
 
@@ -610,7 +611,8 @@ resolve_refreshes_reveal_denominations (struct 
MHD_Connection *connection,
     GNUNET_assert (ng ==
                    TALER_extensions_age_restriction_num_groups ());
 
-    oac = GNUNET_new (struct TALER_AgeCommitment);
+    rctx->old_age_commitment = GNUNET_new (struct TALER_AgeCommitment);
+    oac = rctx->old_age_commitment;
     oac->mask  =  TEH_age_mask;
     oac->num_pub = ng;
     oac->num_priv = 0; /* no private keys are needed for the reveal phase */
@@ -630,7 +632,8 @@ resolve_refreshes_reveal_denominations (struct 
MHD_Connection *connection,
                                         ac_spec,
                                         i,
                                         -1);
-      GNUNET_break (GNUNET_OK != res);
+
+      GNUNET_break_op (GNUNET_OK == res);
       if (GNUNET_OK != res)
         goto clean_age;
     }
@@ -640,12 +643,9 @@ resolve_refreshes_reveal_denominations (struct 
MHD_Connection *connection,
       struct TALER_AgeCommitmentHash hac = {0};
       TALER_age_commitment_hash (oac, &hac);
       if (0 != memcmp (&hac,
-                       &rctx->melt.session.h_age_commitment,
+                       &rctx->melt.session.coin.h_age_commitment,
                        sizeof(struct TALER_AgeCommitmentHash)))
-      {
-        GNUNET_break (0);
         goto clean_age;
-      }
     }
 
     failed = false;
@@ -654,7 +654,10 @@ clean_age:
     if (failed)
     {
       TALER_age_commitment_free (oac);
-      return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         
TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
+                                         "old_age_commitment");
     }
   }
 
@@ -913,7 +916,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;
+  json_t *old_age_commitment;
   struct RevealContext rctx;
   struct GNUNET_JSON_Specification spec[] = {
     GNUNET_JSON_spec_fixed_auto ("transfer_pub",
diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql
index 3f9979c0..0cef7d0d 100644
--- a/src/exchangedb/exchange-0001.sql
+++ b/src/exchangedb/exchange-0001.sql
@@ -311,7 +311,7 @@ CREATE TABLE IF NOT EXISTS known_coins
   (known_coin_id BIGINT GENERATED BY DEFAULT AS IDENTITY -- UNIQUE
   ,denominations_serial INT8 NOT NULL REFERENCES denominations 
(denominations_serial) ON DELETE CASCADE
   ,coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (LENGTH(coin_pub)=32)
-  ,age_hash BYTEA CHECK (LENGTH(age_hash)=32)
+  ,age_commitment_hash BYTEA CHECK (LENGTH(age_commitment_hash)=32)
   ,denom_sig BYTEA NOT NULL
   ,remaining_val INT8 NOT NULL
   ,remaining_frac INT4 NOT NULL
@@ -325,8 +325,8 @@ COMMENT ON COLUMN known_coins.coin_pub
   IS 'EdDSA public key of the coin';
 COMMENT ON COLUMN known_coins.remaining_val
   IS 'Value of the coin that remains to be spent';
-COMMENT ON COLUMN known_coins.age_hash
-  IS 'Optional hash for age restrictions as per DD 24 (active if denom_type 
has the respective bit set)';
+COMMENT ON COLUMN known_coins.age_commitment_hash
+  IS 'Optional hash of the age commitment for age restrictions as per DD 24 
(active if denom_type has the respective bit set)';
 COMMENT ON COLUMN known_coins.denom_sig
   IS 'This is the signature of the exchange that affirms that the coin is a 
valid coin. The specific signature type depends on denom_type of the 
denomination.';
 CREATE TABLE IF NOT EXISTS known_coins_default
@@ -358,7 +358,7 @@ COMMENT ON COLUMN refresh_commitments.rc
 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.';
+  IS 'The (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);
@@ -1259,6 +1259,7 @@ CREATE OR REPLACE FUNCTION exchange_do_melt(
   IN in_old_coin_pub BYTEA,
   IN in_old_coin_sig BYTEA,
   IN in_known_coin_id INT8, -- not used, but that's OK
+  IN in_h_age_commitment BYTEA,
   IN in_noreveal_index INT4,
   IN in_zombie_required BOOLEAN,
   OUT out_balance_ok BOOLEAN,
@@ -1281,6 +1282,7 @@ INSERT INTO refresh_commitments
   ,old_coin_sig
   ,amount_with_fee_val
   ,amount_with_fee_frac
+  ,h_age_commitment
   ,noreveal_index
   )
   VALUES
@@ -1289,6 +1291,7 @@ INSERT INTO refresh_commitments
   ,in_old_coin_sig
   ,in_amount_with_fee_val
   ,in_amount_with_fee_frac
+  ,in_h_age_commitment
   ,in_noreveal_index)
   ON CONFLICT DO NOTHING;
 
diff --git a/src/exchangedb/irbt_callbacks.c b/src/exchangedb/irbt_callbacks.c
index cf054942..3673c7be 100644
--- a/src/exchangedb/irbt_callbacks.c
+++ b/src/exchangedb/irbt_callbacks.c
@@ -406,6 +406,8 @@ irbt_cb_table_refresh_commitments (struct PostgresClosure 
*pg,
       &td->details.refresh_commitments.noreveal_index),
     GNUNET_PQ_query_param_auto_from_type (
       &td->details.refresh_commitments.old_coin_pub),
+    GNUNET_PQ_query_param_auto_from_type (
+      &td->details.refresh_commitments.h_age_commitment),
     GNUNET_PQ_query_param_end
   };
 
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c 
b/src/exchangedb/plugin_exchangedb_postgres.c
index b5bf71e5..2f59401c 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -611,7 +611,7 @@ prepare_statements (struct PostgresClosure *pg)
       ",out_zombie_bad AS zombie_required"
       ",out_noreveal_index AS noreveal_index"
       " FROM exchange_do_melt"
-      " ($1,$2,$3,$4,$5,$6,$7,$8,$9);",
+      " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);",
       9),
     /* Used in #postgres_do_refund() to refund a deposit. */
     GNUNET_PQ_make_prepare (
@@ -730,7 +730,7 @@ prepare_statements (struct PostgresClosure *pg)
       "get_known_coin",
       "SELECT"
       " denominations.denom_pub_hash"
-      ",age_hash"
+      ",age_commitment_hash"
       ",denom_sig"
       " FROM known_coins"
       " JOIN denominations USING (denominations_serial)"
@@ -784,7 +784,7 @@ prepare_statements (struct PostgresClosure *pg)
       "  INSERT INTO known_coins "
       "  (coin_pub"
       "  ,denominations_serial"
-      "  ,age_hash"
+      "  ,age_commitment_hash"
       "  ,denom_sig"
       "  ,remaining_val"
       "  ,remaining_frac"
@@ -804,14 +804,14 @@ prepare_statements (struct PostgresClosure *pg)
       "   FALSE AS existed"
       "  ,known_coin_id"
       "  ,NULL AS denom_pub_hash"
-      "  ,NULL AS age_hash"
+      "  ,NULL AS age_commitment_hash"
       "  FROM ins "
       "UNION ALL "
       "SELECT "
       "   TRUE AS existed"
       "  ,known_coin_id"
       "  ,denom_pub_hash"
-      "  ,kc.age_hash"
+      "  ,kc.age_commitment_hash"
       "  FROM input_rows"
       "  JOIN known_coins kc USING (coin_pub)"
       "  JOIN denominations USING (denominations_serial)"
@@ -873,6 +873,7 @@ prepare_statements (struct PostgresClosure *pg)
       ",denoms.denom_pub_hash"
       ",denoms.fee_refresh_val"
       ",denoms.fee_refresh_frac"
+      ",h_age_commitment"
       ",melt_serial_id"
       " FROM refresh_commitments"
       " JOIN known_coins kc"
@@ -1188,7 +1189,7 @@ prepare_statements (struct PostgresClosure *pg)
       ",denoms.fee_deposit_val"
       ",denoms.fee_deposit_frac"
       ",denoms.denom_pub_hash"
-      ",kc.age_hash"
+      ",kc.age_commitment_hash"
       ",wallet_timestamp"
       ",refund_deadline"
       ",wire_deadline"
@@ -2529,8 +2530,9 @@ prepare_statements (struct PostgresClosure *pg)
       ",amount_with_fee_frac"
       ",noreveal_index"
       ",old_coin_pub"
+      ",h_age_commitment"
       ") VALUES "
-      "($1, $2, $3, $4, $5, $6, $7);",
+      "($1, $2, $3, $4, $5, $6, $7, $8);",
       7),
     GNUNET_PQ_make_prepare (
       "insert_into_table_refresh_revealed_coins",
@@ -4593,6 +4595,7 @@ postgres_do_melt (
     GNUNET_PQ_query_param_auto_from_type (&refresh->coin.coin_pub),
     GNUNET_PQ_query_param_auto_from_type (&refresh->coin_sig),
     GNUNET_PQ_query_param_uint64 (&known_coin_id),
+    GNUNET_PQ_query_param_auto_from_type (&refresh->coin.h_age_commitment),
     GNUNET_PQ_query_param_uint32 (&refresh->noreveal_index),
     GNUNET_PQ_query_param_bool (*zombie_required),
     GNUNET_PQ_query_param_end
@@ -5646,7 +5649,7 @@ postgres_get_known_coin (void *cls,
     GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
                                           &coin_info->denom_pub_hash),
     GNUNET_PQ_result_spec_allow_null (
-      GNUNET_PQ_result_spec_auto_from_type ("age_hash",
+      GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
                                             &coin_info->h_age_commitment),
       &is_null),
     TALER_PQ_result_spec_denom_sig ("denom_sig",
@@ -5747,7 +5750,7 @@ postgres_count_known_coins (void *cls,
  * @param[out] known_coin_id set to the unique row of the coin
  * @param[out] denom_hash set to the denomination hash of the existing
  *             coin (for conflict error reporting)
- * @param[out] age_hash set to the conflicting age hash on conflict
+ * @param[out] h_age_commitment  set to the conflicting age commitment hash on 
conflict
  * @return database transaction status, non-negative on success
  */
 static enum TALER_EXCHANGEDB_CoinKnownStatus
@@ -5755,7 +5758,7 @@ postgres_ensure_coin_known (void *cls,
                             const struct TALER_CoinPublicInfo *coin,
                             uint64_t *known_coin_id,
                             struct TALER_DenominationHashP *denom_hash,
-                            struct TALER_AgeCommitmentHash *age_hash)
+                            struct TALER_AgeCommitmentHash *h_age_commitment)
 {
   struct PostgresClosure *pg = cls;
   enum GNUNET_DB_QueryStatus qs;
@@ -5779,8 +5782,8 @@ postgres_ensure_coin_known (void *cls,
                                             denom_hash),
       &is_denom_pub_hash_null),
     GNUNET_PQ_result_spec_allow_null (
-      GNUNET_PQ_result_spec_auto_from_type ("age_hash",
-                                            age_hash),
+      GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+                                            h_age_commitment),
       &is_age_hash_null),
     GNUNET_PQ_result_spec_end
   };
@@ -5814,10 +5817,10 @@ postgres_ensure_coin_known (void *cls,
   }
 
   if ( (! is_age_hash_null) &&
-       (0 != GNUNET_memcmp (age_hash,
+       (0 != GNUNET_memcmp (h_age_commitment,
                             &coin->h_age_commitment)) )
   {
-    GNUNET_break (GNUNET_is_zero (age_hash));
+    GNUNET_break (GNUNET_is_zero (h_age_commitment));
     GNUNET_break_op (0);
     return TALER_EXCHANGEDB_CKS_AGE_CONFLICT;
   }
@@ -6066,7 +6069,7 @@ postgres_get_melt (void *cls,
                                           &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),
+                                            
&melt->session.coin.h_age_commitment),
       &h_age_commitment_is_null),
     TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
                                  &melt->session.amount_with_fee),
@@ -6084,9 +6087,9 @@ postgres_get_melt (void *cls,
                                                  params,
                                                  rs);
   if (h_age_commitment_is_null)
-    memset (&melt->session.h_age_commitment,
+    memset (&melt->session.coin.h_age_commitment,
             0,
-            sizeof(melt->session.h_age_commitment));
+            sizeof(melt->session.coin.h_age_commitment));
 
   melt->session.rc = *rc;
   return qs;
@@ -6600,7 +6603,7 @@ add_coin_deposit (void *cls,
         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
                                               &deposit->h_denom_pub),
         GNUNET_PQ_result_spec_allow_null (
-          GNUNET_PQ_result_spec_auto_from_type ("age_hash",
+          GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
                                                 &deposit->h_age_commitment),
           &is_null),
         GNUNET_PQ_result_spec_timestamp ("wallet_timestamp",
@@ -6668,6 +6671,7 @@ add_coin_melt (void *cls,
     struct TALER_EXCHANGEDB_MeltListEntry *melt;
     struct TALER_EXCHANGEDB_TransactionList *tl;
     uint64_t serial_id;
+    bool hac_isnull;
 
     chc->have_deposit_or_melt = true;
     melt = GNUNET_new (struct TALER_EXCHANGEDB_MeltListEntry);
@@ -6684,6 +6688,10 @@ add_coin_melt (void *cls,
                                      &melt->amount_with_fee),
         TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
                                      &melt->melt_fee),
+        GNUNET_PQ_result_spec_allow_null (
+          GNUNET_PQ_result_spec_auto_from_type ("h_age_commitment",
+                                                &melt->h_age_commitment),
+          &hac_isnull),
         GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
                                       &serial_id),
         GNUNET_PQ_result_spec_end
@@ -6699,6 +6707,10 @@ add_coin_melt (void *cls,
         chc->failed = true;
         return;
       }
+
+      if (hac_isnull)
+        memset (&melt->h_age_commitment, 0, sizeof(melt->h_age_commitment));
+
     }
     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     tl->next = chc->head;
diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h
index 20b9ff30..a49b9eb5 100644
--- a/src/include/taler_crypto_lib.h
+++ b/src/include/taler_crypto_lib.h
@@ -2419,6 +2419,7 @@ TALER_wallet_deposit_verify (
  * @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_priv coin’s private key
  * @param[out] coin_sig set to the signature made with purpose 
#TALER_SIGNATURE_WALLET_COIN_MELT
  */
@@ -2428,6 +2429,7 @@ TALER_wallet_melt_sign (
   const struct TALER_Amount *melt_fee,
   const struct TALER_RefreshCommitmentP *rc,
   const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
   const struct TALER_CoinSpendPrivateKeyP *coin_priv,
   struct TALER_CoinSpendSignatureP *coin_sig);
 
@@ -3346,7 +3348,7 @@ TALER_age_commitment_hash (
  *
  * @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 salt The salt 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
  */
@@ -3354,21 +3356,21 @@ enum GNUNET_GenericReturnValue
 TALER_age_restriction_commit (
   const struct TALER_AgeMask *mask,
   const uint8_t age,
-  const uint32_t seed,
+  const uint64_t salt,
   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 salt Salt to randomly 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,
+  const uint64_t salt,
   struct TALER_AgeCommitment *derived);
 
 /*
diff --git a/src/include/taler_exchange_service.h 
b/src/include/taler_exchange_service.h
index 92e841ef..fcb0ab7f 100644
--- a/src/include/taler_exchange_service.h
+++ b/src/include/taler_exchange_service.h
@@ -1682,9 +1682,11 @@ struct TALER_EXCHANGE_RefreshData
   struct TALER_CoinSpendPrivateKeyP melt_priv;
 
   /*
-   * age commitment that went into the original coin, might be NULL
+   * age commitment and its hash that went into the original coin, might be
+   * NULL
    */
-  struct TALER_AgeCommitment *age_commitment;
+  struct TALER_AgeCommitment *melt_age_commitment;
+  struct TALER_AgeCommitmentHash *melt_h_age_commitment;
 
   /**
    * amount specifying how much the coin will contribute to the melt
@@ -1997,6 +1999,12 @@ struct TALER_EXCHANGE_LinkedCoinInfo
    */
   struct TALER_CoinSpendPrivateKeyP coin_priv;
 
+  /**
+   * Age commitment and its hash, if applicable.  Might be NULL.
+   */
+  struct TALER_AgeCommitment *age_commitment;
+  struct TALER_AgeCommitmentHash *h_age_commitment;
+
   /**
    * Master secret of this coin.
    */
diff --git a/src/include/taler_exchangedb_plugin.h 
b/src/include/taler_exchangedb_plugin.h
index 10ab1ac9..529d4943 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -309,6 +309,7 @@ struct TALER_EXCHANGEDB_TableData
       struct TALER_CoinSpendPublicKeyP old_coin_pub;
       struct TALER_CoinSpendSignatureP old_coin_sig;
       struct TALER_Amount amount_with_fee;
+      struct TALER_AgeCommitmentHash h_age_commitment;
       uint32_t noreveal_index;
     } refresh_commitments;
 
@@ -1268,13 +1269,6 @@ 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.
    */
diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c
index 17e00a81..b7a43bbc 100644
--- a/src/lib/exchange_api_common.c
+++ b/src/lib/exchange_api_common.c
@@ -585,24 +585,20 @@ TALER_EXCHANGE_verify_coin_history (
         }
       }
 
-      {
-        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;
-        }
+      if (GNUNET_OK !=
+          TALER_wallet_melt_verify (
+            &amount,
+            &fee,
+            &rc,
+            h_denom_pub,
+            TALER_AgeCommitmentHash_isNullOrZero (&h_age_commitment) ?
+            NULL : &h_age_commitment,
+            coin_pub,
+            &sig))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
       }
       add = GNUNET_YES;
     }
diff --git a/src/lib/exchange_api_link.c b/src/lib/exchange_api_link.c
index 902f2b42..ac3fecdd 100644
--- a/src/lib/exchange_api_link.c
+++ b/src/lib/exchange_api_link.c
@@ -67,7 +67,8 @@ struct TALER_EXCHANGE_LinkHandle
   struct TALER_CoinSpendPrivateKeyP coin_priv;
 
   /**
-   * Age commitment of the coin, might be NULL, required to re-generate age 
commitments
+   * Age commitment of the original coin, might be NULL.
+   * Required to derive the new age commitment
    */
   const struct TALER_AgeCommitment *age_commitment;
 
@@ -118,7 +119,6 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
   struct TALER_TransferSecretP secret;
   struct TALER_PlanchetDetail pd;
   struct TALER_CoinPubHashP c_hash;
-  struct TALER_AgeCommitmentHash *hac = NULL;
 
   /* parse reply */
   memset (&nonce,
@@ -145,28 +145,26 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle 
*lh,
                                          &alg_values,
                                          &bks);
 
+  lci->age_commitment = NULL;
+  lci->h_age_commitment = NULL;
+
   /* Derive the age commitment and calculate the hash */
   if (NULL != lh->age_commitment)
   {
-    struct TALER_AgeCommitment nac = {0};
-    struct TALER_AgeCommitmentHash h = {0};
-    uint32_t seed  = secret.key.bits[0];
+    uint64_t seed  = (uint64_t) secret.key.bits[0]
+                     | (uint64_t) secret.key.bits[1] << 32;
+    lci->age_commitment = GNUNET_new (struct TALER_AgeCommitment);
+    lci->h_age_commitment = GNUNET_new (struct TALER_AgeCommitmentHash);
 
-    if (GNUNET_OK !=
-        TALER_age_commitment_derive (
-          lh->age_commitment,
-          seed,
-          &nac))
-    {
-      GNUNET_break_op (0);
-      return GNUNET_SYSERR;
-    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_age_commitment_derive (
+                     lh->age_commitment,
+                     seed,
+                     lci->age_commitment));
 
     TALER_age_commitment_hash (
-      &nac,
-      &h);
-
-    hac = &h;
+      lci->age_commitment,
+      lci->h_age_commitment);
   }
 
   if (GNUNET_OK !=
@@ -174,7 +172,7 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
                               &alg_values,
                               &bks,
                               &lci->coin_priv,
-                              hac,
+                              lci->h_age_commitment,
                               &c_hash,
                               &pd))
   {
diff --git a/src/lib/exchange_api_melt.c b/src/lib/exchange_api_melt.c
index f7f77027..dbe77c7e 100644
--- a/src/lib/exchange_api_melt.c
+++ b/src/lib/exchange_api_melt.c
@@ -478,6 +478,7 @@ start_melt (struct TALER_EXCHANGE_MeltHandle *mh)
                           &mh->md.melted_coin.fee_melt,
                           &mh->md.rc,
                           &h_denom_pub,
+                          mh->md.melted_coin.h_age_commitment,
                           &mh->md.melted_coin.coin_priv,
                           &confirm_sig);
   GNUNET_CRYPTO_eddsa_key_get_public (&mh->md.melted_coin.coin_priv.eddsa_priv,
@@ -493,6 +494,12 @@ start_melt (struct TALER_EXCHANGE_MeltHandle *mh)
                             &mh->md.melted_coin.melt_amount_with_fee),
     GNUNET_JSON_pack_data_auto ("rc",
                                 &mh->md.rc),
+    GNUNET_JSON_pack_allow_null (
+      mh->md.melted_coin.h_age_commitment
+       ? GNUNET_JSON_pack_data_auto ("age_commitment_hash",
+                                     mh->md.melted_coin.h_age_commitment)
+       : GNUNET_JSON_pack_string ("age_commitment_hash",
+                                  NULL)),
     GNUNET_JSON_pack_allow_null (
       mh->send_rms
        ? GNUNET_JSON_pack_data_auto ("rms",
diff --git a/src/lib/exchange_api_refresh_common.c 
b/src/lib/exchange_api_refresh_common.c
index 8e9e8da3..997d1fec 100644
--- a/src/lib/exchange_api_refresh_common.c
+++ b/src/lib/exchange_api_refresh_common.c
@@ -78,7 +78,8 @@ TALER_EXCHANGE_get_melt_data_ (
   md->melted_coin.fee_melt = rd->melt_pk.fees.refresh;
   md->melted_coin.original_value = rd->melt_pk.value;
   md->melted_coin.expire_deposit = rd->melt_pk.expire_deposit;
-  md->melted_coin.age_commitment = rd->age_commitment;
+  md->melted_coin.age_commitment = rd->melt_age_commitment;
+  md->melted_coin.h_age_commitment = rd->melt_h_age_commitment;
 
   GNUNET_assert (GNUNET_OK ==
                  TALER_amount_set_zero (rd->melt_amount.currency,
@@ -184,29 +185,23 @@ TALER_EXCHANGE_get_melt_data_ (
       /* Handle age commitment, if present */
       if (NULL != md->melted_coin.age_commitment)
       {
-        struct TALER_AgeCommitment new_ac;
-        struct TALER_AgeCommitmentHash hac;
-
-        /* We use the first 4 bytes of the trans_sec to generate a new age
+        /* We use the first 8 bytes of the trans_sec to generate a new age
          * commitment */
-        uint32_t age_seed = trans_sec.key.bits[0];
-
-        if (GNUNET_OK !=
-            TALER_age_commitment_derive (
-              md->melted_coin.age_commitment,
-              age_seed + j,
-              &new_ac))
-        {
-          GNUNET_break_op (0);
-          TALER_EXCHANGE_free_melt_data_ (md);
-          return GNUNET_SYSERR;
-        }
+        uint64_t age_seed = (uint64_t) trans_sec.key.bits[0]
+                            | (uint64_t) trans_sec.key.bits[1] << 32;
 
-        TALER_age_commitment_hash (
-          &new_ac,
-          &hac);
+        fcd->age_commitment[i] = GNUNET_new (struct TALER_AgeCommitment);
+        ach = GNUNET_new (struct TALER_AgeCommitmentHash);
+
+        GNUNET_assert (GNUNET_OK ==
+                       TALER_age_commitment_derive (
+                         md->melted_coin.age_commitment,
+                         age_seed,
+                         fcd->age_commitment[i]));
 
-        ach = &hac;
+        TALER_age_commitment_hash (
+          fcd->age_commitment[i],
+          ach);
       }
 
       if (TALER_DENOMINATION_CS == alg_values[j].cipher)
@@ -225,7 +220,6 @@ TALER_EXCHANGE_get_melt_data_ (
         TALER_EXCHANGE_free_melt_data_ (md);
         return GNUNET_SYSERR;
       }
-
       rcd->blinded_planchet = pd.blinded_planchet;
       rcd->dk = &fcd->fresh_pk;
     }
diff --git a/src/lib/exchange_api_refresh_common.h 
b/src/lib/exchange_api_refresh_common.h
index a3c3e2c0..8d7eb282 100644
--- a/src/lib/exchange_api_refresh_common.h
+++ b/src/lib/exchange_api_refresh_common.h
@@ -56,8 +56,8 @@ struct MeltedCoin
    * The original age commitment and its hash.  MUST be NULL if no age
    * commitment was set.
    */
-  struct TALER_AgeCommitment *age_commitment;
-  struct TALER_AgeCommitmentHash *h_age_commitment;
+  const struct TALER_AgeCommitment *age_commitment;
+  const struct TALER_AgeCommitmentHash *h_age_commitment;
 
   /**
    * Timestamp indicating when coins of this denomination become invalid.
diff --git a/src/lib/exchange_api_refreshes_reveal.c 
b/src/lib/exchange_api_refreshes_reveal.c
index 461432db..881c7e73 100644
--- a/src/lib/exchange_api_refreshes_reveal.c
+++ b/src/lib/exchange_api_refreshes_reveal.c
@@ -336,6 +336,7 @@ TALER_EXCHANGE_refreshes_reveal (
   json_t *coin_evs;
   json_t *reveal_obj;
   json_t *link_sigs;
+  json_t *old_age_commitment = NULL;
   CURL *eh;
   struct GNUNET_CURL_Context *ctx;
   struct MeltData md;
@@ -427,6 +428,22 @@ TALER_EXCHANGE_refreshes_reveal (
                                             &md.transfer_priv[j])));
   }
 
+  /* build array of old age commitment, if applicable */
+  GNUNET_assert ((NULL == rd->melt_age_commitment) ==
+                 (NULL == rd->melt_h_age_commitment));
+  if (NULL != rd->melt_age_commitment)
+  {
+    GNUNET_assert (NULL != (old_age_commitment = json_array ()));
+
+    for (size_t i = 0; i < rd->melt_age_commitment->num_pub; i++)
+    {
+      GNUNET_assert (0 ==
+                     json_array_append_new (old_age_commitment,
+                                            GNUNET_JSON_from_data_auto (
+                                              
&rd->melt_age_commitment->pub[i])));
+    }
+  }
+
   /* build main JSON request */
   reveal_obj = GNUNET_JSON_PACK (
     GNUNET_JSON_pack_data_auto ("transfer_pub",
@@ -437,6 +454,9 @@ TALER_EXCHANGE_refreshes_reveal (
                                     rms)
       : GNUNET_JSON_pack_string ("rms",
                                  NULL)),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_array_steal ("old_age_commitment",
+                                    old_age_commitment)),
     GNUNET_JSON_pack_array_steal ("transfer_privs",
                                   transfer_privs),
     GNUNET_JSON_pack_array_steal ("link_sigs",
@@ -480,6 +500,7 @@ TALER_EXCHANGE_refreshes_reveal (
     GNUNET_free (rrh);
     return NULL;
   }
+
   eh = TALER_EXCHANGE_curl_easy_get_ (rrh->url);
   if ( (NULL == eh) ||
        (GNUNET_OK !=
diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c
index 957e42e8..b6dd39c8 100644
--- a/src/testing/test_exchange_api.c
+++ b/src/testing/test_exchange_api.c
@@ -377,9 +377,9 @@ run (void *cls,
      * Move money to the exchange's bank account.
      */
     CMD_TRANSFER_TO_EXCHANGE ("create-reserve-age",
-                              "EUR:5.01"),
+                              "EUR:6.01"),
     TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-age",
-                                                 "EUR:5.01",
+                                                 "EUR:6.01",
                                                  bc.user42_payto,
                                                  bc.exchange_payto,
                                                  "create-reserve-age"),
@@ -475,7 +475,22 @@ run (void *cls,
                                            "EUR:0.98",
                                            bc.exchange_payto,
                                            bc.user42_payto),
-    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c",
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c3",
+                                           ec.exchange_url,
+                                           "EUR:0.98",
+                                           bc.exchange_payto,
+                                           bc.user42_payto),
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c4",
+                                           ec.exchange_url,
+                                           "EUR:0.98",
+                                           bc.exchange_payto,
+                                           bc.user42_payto),
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-08c",
+                                           ec.exchange_url,
+                                           "EUR:0.08",
+                                           bc.exchange_payto,
+                                           bc.user43_payto),
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-08c2",
                                            ec.exchange_url,
                                            "EUR:0.08",
                                            bc.exchange_payto,
@@ -548,6 +563,104 @@ run (void *cls,
     TALER_TESTING_cmd_end ()
   };
 
+  struct TALER_TESTING_Command refresh_age[] = {
+    /* Fill reserve with EUR:5, 1ct is for fees. */
+    CMD_TRANSFER_TO_EXCHANGE ("refresh-create-reserve-age-1",
+                              "EUR:6.01"),
+    TALER_TESTING_cmd_check_bank_admin_transfer (
+      "ck-refresh-create-reserve-age-1",
+      "EUR:6.01",
+      bc.user42_payto,
+      bc.exchange_payto,
+      "refresh-create-reserve-age-1"),
+    /**
+     * Make previous command effective.
+     */
+    CMD_EXEC_WIREWATCH ("wirewatch-age-2"),
+    /**
+     * Withdraw EUR:7 with age restriction for age 13.
+     */
+    TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-age-1",
+                                       "refresh-create-reserve-age-1",
+                                       "EUR:5",
+                                       13,
+                                       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) *///
+    TALER_TESTING_cmd_deposit ("refresh-deposit-partial-age",
+                               "refresh-withdraw-coin-age-1",
+                               0,
+                               bc.user42_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":\"EUR:1\"}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:1",
+                               MHD_HTTP_OK),
+    /**
+     * Melt the rest of the coin's value
+     * (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */
+    TALER_TESTING_cmd_melt_double ("refresh-melt-age-1",
+                                   "refresh-withdraw-coin-age-1",
+                                   MHD_HTTP_OK,
+                                   NULL),
+    /**
+     * Complete (successful) melt operation, and
+     * withdraw the coins
+     */
+    TALER_TESTING_cmd_refresh_reveal ("refresh-reveal-age-1",
+                                      "refresh-melt-age-1",
+                                      MHD_HTTP_OK),
+    /**
+     * Do it again to check idempotency
+     */
+    TALER_TESTING_cmd_refresh_reveal ("refresh-reveal-age-1-idempotency",
+                                      "refresh-melt-age-1",
+                                      MHD_HTTP_OK),
+    /**
+     * Test that /refresh/link works
+     */
+    TALER_TESTING_cmd_refresh_link ("refresh-link-age-1",
+                                    "refresh-reveal-age-1",
+                                    MHD_HTTP_OK),
+    /**
+     * Try to spend a refreshed EUR:1 coin
+     */
+    TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-age-1a",
+                               "refresh-reveal-age-1-idempotency",
+                               0,
+                               bc.user42_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":3}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:1",
+                               MHD_HTTP_OK),
+    /**
+     * Try to spend a refreshed EUR:0.1 coin
+     */
+    TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-age-1b",
+                               "refresh-reveal-age-1",
+                               3,
+                               bc.user43_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":3}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:0.1",
+                               MHD_HTTP_OK),
+#if 0 /* FIXME oec */
+    /* Test running a failing melt operation (same operation
+     * again must fail) */
+    TALER_TESTING_cmd_melt ("refresh-melt-failing-age",
+                            "refresh-withdraw-coin-age-1",
+                            MHD_HTTP_CONFLICT,
+                            NULL),
+    /* Test running a failing melt operation (on a coin that
+       was itself revealed and subsequently deposited) */
+    TALER_TESTING_cmd_melt ("refresh-melt-failing-age-2",
+                            "refresh-reveal-age-1",
+                            MHD_HTTP_CONFLICT,
+                            NULL),
+
+#endif
+    TALER_TESTING_cmd_end ()
+  };
 
   /**
    * This block exercises the aggretation logic by making two payments
@@ -1073,6 +1186,8 @@ run (void *cls,
                                withdraw_age),
       TALER_TESTING_cmd_batch ("spend-age",
                                spend_age),
+      TALER_TESTING_cmd_batch ("refresh-age",
+                               refresh_age),
       TALER_TESTING_cmd_batch ("track",
                                track),
       TALER_TESTING_cmd_batch ("unaggregation",
diff --git a/src/testing/testing_api_cmd_deposit.c 
b/src/testing/testing_api_cmd_deposit.c
index a241c531..ad1315b2 100644
--- a/src/testing/testing_api_cmd_deposit.c
+++ b/src/testing/testing_api_cmd_deposit.c
@@ -568,23 +568,23 @@ deposit_traits (void *cls,
     struct TALER_TESTING_Trait traits[] = {
       /* First two traits are only available if
          ds->traits is #GNUNET_YES */
-      TALER_TESTING_make_trait_exchange_pub (0, &ds->exchange_pub),
-      TALER_TESTING_make_trait_exchange_sig (0, &ds->exchange_sig),
+      TALER_TESTING_make_trait_exchange_pub (index, &ds->exchange_pub),
+      TALER_TESTING_make_trait_exchange_sig (index, &ds->exchange_sig),
       /* These traits are always available */
-      TALER_TESTING_make_trait_coin_priv (0,
+      TALER_TESTING_make_trait_coin_priv (index,
                                           coin_spent_priv),
-      TALER_TESTING_make_trait_age_commitment (0,
+      TALER_TESTING_make_trait_age_commitment (index,
                                                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),
       TALER_TESTING_make_trait_deposit_amount (&ds->amount),
       TALER_TESTING_make_trait_deposit_fee_amount (&ds->deposit_fee),
-      TALER_TESTING_make_trait_timestamp (0,
+      TALER_TESTING_make_trait_timestamp (index,
                                           &ds->exchange_timestamp),
-      TALER_TESTING_make_trait_wire_deadline (0,
+      TALER_TESTING_make_trait_wire_deadline (index,
                                               &ds->wire_deadline),
-      TALER_TESTING_make_trait_refund_deadline (0,
+      TALER_TESTING_make_trait_refund_deadline (index,
                                                 &ds->refund_deadline),
       TALER_TESTING_trait_end ()
     };
diff --git a/src/testing/testing_api_cmd_refresh.c 
b/src/testing/testing_api_cmd_refresh.c
index 29ad9d2f..f287681d 100644
--- a/src/testing/testing_api_cmd_refresh.c
+++ b/src/testing/testing_api_cmd_refresh.c
@@ -71,9 +71,10 @@ struct TALER_TESTING_FreshCoinData
   struct TALER_CoinSpendPrivateKeyP coin_priv;
 
   /*
-   * Age commitment for the coin, NULL if not applicable.
+   * Fresh age commitment for the coin and its hash, NULL if not applicable.
    */
   struct TALER_AgeCommitment *age_commitment;
+  struct TALER_AgeCommitmentHash *h_age_commitment;
 
   /**
    * The blinding key (needed for recoup operations).
@@ -137,11 +138,6 @@ 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.
    */
@@ -445,6 +441,8 @@ reveal_cb (void *cls,
         return;
       }
       fc->coin_priv = coin->coin_priv;
+      fc->age_commitment = coin->age_commitment;
+      fc->h_age_commitment = coin->h_age_commitment;
 
       TALER_denom_sig_deep_copy (&fc->sig,
                                  &coin->sig);
@@ -836,7 +834,7 @@ refresh_link_run (void *cls,
   /* finally, use private key from withdraw sign command */
   rls->rlh = TALER_EXCHANGE_link (is->exchange,
                                   coin_priv,
-                                  rms->age_commitment,
+                                  rms->refresh_data.melt_age_commitment,
                                   &link_cb,
                                   rls);
 
@@ -1046,6 +1044,8 @@ melt_run (void *cls,
   {
     struct TALER_Amount melt_amount;
     struct TALER_Amount fresh_amount;
+    struct TALER_AgeCommitment *age_commitment;
+    struct TALER_AgeCommitmentHash *h_age_commitment;
     const struct TALER_DenominationSignature *melt_sig;
     const struct TALER_EXCHANGE_DenomPublicKey *melt_denom_pub;
     const struct TALER_TESTING_Command *coin_command;
@@ -1070,10 +1070,21 @@ 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))
+                                                &age_commitment))
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (rms->is);
+      return;
+    }
+
+    if (GNUNET_OK !=
+        TALER_TESTING_get_trait_h_age_commitment (coin_command,
+                                                  0,
+                                                  &h_age_commitment))
     {
       GNUNET_break (0);
       TALER_TESTING_interpreter_fail (rms->is);
@@ -1148,31 +1159,13 @@ melt_run (void *cls,
     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.melt_age_commitment = age_commitment;
+    rms->refresh_data.melt_h_age_commitment = h_age_commitment;
     rms->refresh_data.fresh_pks = rms->fresh_pks;
     rms->refresh_data.fresh_pks_len = num_fresh_coins;
-    rms->refresh_data.age_commitment = NULL;
 
     GNUNET_assert (age_restricted ==
-                   (NULL != rms->age_commitment));
-
-    if (NULL != rms->age_commitment)
-    {
-      struct TALER_AgeCommitment *ac;
-      uint32_t seed;
-
-      ac = GNUNET_new (struct TALER_AgeCommitment);
-      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;
-    }
+                   (NULL != age_commitment));
 
     rms->rmh = TALER_EXCHANGE_melt (is->exchange,
                                     &rms->rms,
@@ -1256,10 +1249,14 @@ melt_traits (void *cls,
     struct TALER_TESTING_Trait traits[] = {
       TALER_TESTING_make_trait_denom_pub (index,
                                           &rms->fresh_pks[index]),
-      TALER_TESTING_make_trait_coin_priv (0,
+      TALER_TESTING_make_trait_coin_priv (index,
                                           rms->melt_priv),
-      TALER_TESTING_make_trait_age_commitment (index,
-                                               rms->age_commitment),
+      TALER_TESTING_make_trait_age_commitment (
+        index,
+        rms->refresh_data.melt_age_commitment),
+      TALER_TESTING_make_trait_h_age_commitment (
+        index,
+        rms->refresh_data.melt_h_age_commitment),
       TALER_TESTING_make_trait_exchange_wd_value (index,
                                                   &rms->mbds[index].alg_value),
       TALER_TESTING_make_trait_refresh_secret (&rms->rms),
@@ -1418,6 +1415,7 @@ refresh_reveal_traits (void *cls,
 
   if (index >= rrs->num_fresh_coins)
     return GNUNET_SYSERR;
+
   {
     struct TALER_TESTING_Trait traits[] = {
       TALER_TESTING_make_trait_coin_priv (
@@ -1426,6 +1424,9 @@ refresh_reveal_traits (void *cls,
       TALER_TESTING_make_trait_age_commitment (
         index,
         rrs->fresh_coins[index].age_commitment),
+      TALER_TESTING_make_trait_h_age_commitment (
+        index,
+        rrs->fresh_coins[index].h_age_commitment),
       TALER_TESTING_make_trait_denom_pub (
         index,
         rrs->fresh_coins[index].pk),
@@ -1443,7 +1444,6 @@ refresh_reveal_traits (void *cls,
                                                  &rrs->psa[index]),
       TALER_TESTING_trait_end ()
     };
-
     return TALER_TESTING_get_trait (traits,
                                     ret,
                                     trait,
diff --git a/src/testing/testing_api_cmd_withdraw.c 
b/src/testing/testing_api_cmd_withdraw.c
index 14015c49..3974a105 100644
--- a/src/testing/testing_api_cmd_withdraw.c
+++ b/src/testing/testing_api_cmd_withdraw.c
@@ -526,7 +526,7 @@ withdraw_cleanup (void *cls,
   }
   if (NULL != ws->age_commitment)
   {
-    GNUNET_free (ws->age_commitment);
+    TALER_age_commitment_free (ws->age_commitment);
     ws->age_commitment = NULL;
   }
   if (NULL != ws->h_age_commitment)
@@ -569,7 +569,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 (index /* only one coin */,
+    TALER_TESTING_make_trait_denom_sig (0 /* only one coin */,
                                         &ws->sig),
     TALER_TESTING_make_trait_reserve_priv (&ws->reserve_priv),
     TALER_TESTING_make_trait_reserve_pub (&ws->reserve_pub),
@@ -579,8 +579,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_make_trait_age_commitment (0, ws->age_commitment),
+    TALER_TESTING_make_trait_h_age_commitment (0, ws->h_age_commitment),
     TALER_TESTING_trait_end ()
   };
 
@@ -626,7 +626,7 @@ TALER_TESTING_cmd_withdraw_amount (const char *label,
 
     ac = GNUNET_new (struct TALER_AgeCommitment);
     hac = GNUNET_new (struct TALER_AgeCommitmentHash);
-    seed = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX);
+    seed = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX);
     mask = TALER_extensions_age_restriction_ageMask ();
 
     if (GNUNET_OK !=
diff --git a/src/util/crypto.c b/src/util/crypto.c
index aced73f2..bf91b697 100644
--- a/src/util/crypto.c
+++ b/src/util/crypto.c
@@ -506,7 +506,7 @@ enum GNUNET_GenericReturnValue
 TALER_age_restriction_commit (
   const struct TALER_AgeMask *mask,
   const uint8_t age,
-  const uint32_t seed,
+  const uint64_t salt,
   struct TALER_AgeCommitment *new)
 {
   uint8_t num_pub = __builtin_popcount (mask->mask) - 1;
@@ -517,6 +517,7 @@ TALER_age_restriction_commit (
   GNUNET_assert (mask->mask & 1); /* fist bit must have been set */
   GNUNET_assert (0 <= num_priv);
   GNUNET_assert (31 > num_priv);
+  GNUNET_assert (num_priv <= num_pub);
 
   new->mask.mask = mask->mask;
   new->num_pub = num_pub;
@@ -529,32 +530,35 @@ TALER_age_restriction_commit (
     num_priv,
     struct TALER_AgeCommitmentPrivateKeyP);
 
-  /* Create as many private keys as we need */
-  for (i = 0; i < num_priv; i++)
+  /* Create as many private keys as we need and fill the rest of the
+   * public keys with valid curve points.
+   * We need to make sure that the public keys are proper points on the
+   * elliptic curve, so we can't simply fill the struct with random values. */
+  for (i = 0; i < num_pub; i++)
   {
-    uint32_t seedBE = htonl (seed + i);
+    uint64_t saltBE = htonl (salt + i);
+    struct TALER_AgeCommitmentPrivateKeyP key = {0};
+    struct TALER_AgeCommitmentPrivateKeyP *priv = &key;
+
+    /* Only save the private keys for age groups less than num_priv */
+    if (i < num_priv)
+      priv = &new->priv[i];
 
     if  (GNUNET_OK !=
-         GNUNET_CRYPTO_kdf (&new->priv[i],
-                            sizeof (new->priv[i]),
-                            &seedBE,
-                            sizeof (seedBE),
+         GNUNET_CRYPTO_kdf (priv,
+                            sizeof (*priv),
+                            &saltBE,
+                            sizeof (saltBE),
                             "taler-age-commitment-derivation",
                             strlen (
                               "taler-age-commitment-derivation"),
                             NULL, 0))
       goto FAIL;
 
-    GNUNET_CRYPTO_eddsa_key_get_public (&new->priv[i].eddsa_priv,
+    GNUNET_CRYPTO_eddsa_key_get_public (&priv->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:
@@ -567,10 +571,24 @@ FAIL:
 enum GNUNET_GenericReturnValue
 TALER_age_commitment_derive (
   const struct TALER_AgeCommitment *orig,
-  const uint32_t seed,
+  const uint64_t salt,
   struct TALER_AgeCommitment *new)
 {
-  struct GNUNET_CRYPTO_EccScalar val;
+  struct GNUNET_CRYPTO_EccScalar scalar;
+  uint64_t saltBT = htonl (salt);
+  int64_t factor;
+
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_CRYPTO_kdf (
+                   &factor,
+                   sizeof (factor),
+                   &saltBT,
+                   sizeof (saltBT),
+                   "taler-age-restriction-derivation",
+                   strlen ("taler-age-restriction-derivation"),
+                   NULL, 0));
+
+  GNUNET_CRYPTO_ecc_scalar_from_int (factor, &scalar);
 
   /*
   * age commitment consists of GNUNET_CRYPTO_Eddsa{Private,Public}Key
@@ -584,7 +602,7 @@ TALER_age_commitment_derive (
   * 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
+  * From the salt we will derive
   *   1. a scalar to multiply the public keys with
   *   2. a factor to multiply the private key with
   *
@@ -594,9 +612,9 @@ TALER_age_commitment_derive (
   * 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
+  * A ECC scalar for use in point multiplications is a
   * GNUNET_CRYPTO_EccScalar which is a
-  *   unsigned car v[256 / 8];
+  *   unsigned char v[256 / 8];
   * */
 
   GNUNET_assert (NULL != new);
@@ -613,9 +631,6 @@ TALER_age_commitment_derive (
     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++)
   {
@@ -627,7 +642,7 @@ TALER_age_commitment_derive (
     if (GNUNET_OK !=
         GNUNET_CRYPTO_ecc_pmul_mpi (
           p,
-          &val,
+          &scalar,
           np))
       goto FAIL;
 
@@ -636,47 +651,40 @@ TALER_age_commitment_derive (
   /* 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++)
     {
+      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);
+
+      GNUNET_CRYPTO_mpi_scan_unsigned (&f, (unsigned char*) &factor,
+                                       sizeof(factor));
 
-      /* 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));
+      gcry_mpi_release (d);
+      gcry_mpi_release (x);
+      gcry_mpi_release (n);
+      gcry_mpi_release (f);
+      gcry_ctx_release (ctx);
 
-      /* TODO:
-       * make sure that the calculated private key generate the same public
-       * keys */
+      /* TODO: add test to make sure that the calculated private key generate
+       * the same public keys */
     }
 
-    gcry_mpi_release (f);
-    gcry_ctx_release (ctx);
   }
 
   return GNUNET_OK;
diff --git a/src/util/wallet_signatures.c b/src/util/wallet_signatures.c
index a21c73be..7d07c9e7 100644
--- a/src/util/wallet_signatures.c
+++ b/src/util/wallet_signatures.c
@@ -249,6 +249,7 @@ TALER_wallet_melt_sign (
   const struct TALER_Amount *melt_fee,
   const struct TALER_RefreshCommitmentP *rc,
   const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
   const struct TALER_CoinSpendPrivateKeyP *coin_priv,
   struct TALER_CoinSpendSignatureP *coin_sig)
 {
@@ -256,9 +257,14 @@ TALER_wallet_melt_sign (
     .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT),
     .purpose.size = htonl (sizeof (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]