gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] branch master updated (8bdf6ab1 -> 51479937)


From: gnunet
Subject: [taler-exchange] branch master updated (8bdf6ab1 -> 51479937)
Date: Thu, 17 Feb 2022 15:18:15 +0100

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

grothoff pushed a change to branch master
in repository exchange.

    from 8bdf6ab1 [age restriction] progress 14/n - withdraw and deposit
     new a351bfc4 -fix CS nonce reuse check logic
     new 51479937 -add missing file

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


Summary of changes:
 src/auditor/report-lib.c                           |   2 +-
 src/auditor/taler-helper-auditor-aggregation.c     |   6 +-
 src/auditor/taler-helper-auditor-coins.c           |  34 +-
 src/auditor/taler-helper-auditor-reserves.c        |   2 +-
 src/benchmark/taler-aggregator-benchmark.c         |   8 +-
 src/exchange-tools/taler-auditor-offline.c         |  63 +---
 src/exchange-tools/taler-exchange-offline.c        |  55 +---
 src/exchange/taler-exchange-httpd.c                |  10 +-
 src/exchange/taler-exchange-httpd_auditors.c       |   5 +-
 src/exchange/taler-exchange-httpd_csr.c            | 361 ++++++++++++++-------
 src/exchange/taler-exchange-httpd_csr.h            |  25 +-
 src/exchange/taler-exchange-httpd_deposit.c        |   2 +-
 src/exchange/taler-exchange-httpd_keys.c           |  92 +-----
 .../taler-exchange-httpd_management_post_keys.c    |   5 +-
 src/exchange/taler-exchange-httpd_melt.c           |  21 +-
 src/exchange/taler-exchange-httpd_recoup.c         |  23 +-
 .../taler-exchange-httpd_refreshes_reveal.c        |  29 +-
 src/exchange/taler-exchange-httpd_refund.c         |   4 +-
 src/exchange/taler-exchange-httpd_withdraw.c       |  31 +-
 src/exchangedb/exchange-0001.sql                   | 166 ++++++++--
 src/exchangedb/irbt_callbacks.c                    |   8 +-
 src/exchangedb/lrbt_callbacks.c                    |   8 +-
 src/exchangedb/plugin_exchangedb_postgres.c        | 158 ++++-----
 src/exchangedb/test_exchangedb.c                   |  80 ++---
 src/include/taler_amount_lib.h                     |  12 +
 src/include/taler_crypto_lib.h                     | 175 ++++++----
 src/include/taler_exchange_service.h               | 153 +++++++--
 src/include/taler_exchangedb_plugin.h              | 141 +++++---
 src/include/taler_json_lib.h                       |  29 ++
 src/include/taler_signatures.h                     |  51 +--
 src/include/taler_util.h                           |  33 ++
 src/lib/Makefile.am                                |   3 +-
 src/lib/exchange_api_common.c                      |  21 +-
 ...{exchange_api_csr.c => exchange_api_csr_melt.c} |  59 ++--
 ...hange_api_csr.c => exchange_api_csr_withdraw.c} | 136 +++-----
 src/lib/exchange_api_deposit.c                     |   8 +-
 src/lib/exchange_api_handle.c                      |  43 +--
 src/lib/exchange_api_melt.c                        |  36 +-
 src/lib/exchange_api_recoup.c                      |   2 +-
 src/lib/exchange_api_recoup_refresh.c              |   2 +-
 src/lib/exchange_api_refresh_common.c              |  10 +-
 src/lib/exchange_api_refreshes_reveal.c            |   9 +
 src/lib/exchange_api_withdraw.c                    |  42 +--
 src/lib/exchange_api_withdraw2.c                   |   2 +-
 src/testing/test_exchange_api-cs.conf              |   4 +-
 .../testing_api_cmd_auditor_add_denom_sig.c        |   5 +-
 src/testing/testing_api_cmd_deposit.c              |   5 +-
 src/testing/testing_api_cmd_insert_deposit.c       |   8 +-
 src/testing/testing_api_cmd_refresh.c              |   4 +-
 src/testing/testing_api_cmd_withdraw.c             |  22 +-
 src/util/amount.c                                  |  14 +
 src/util/auditor_signatures.c                      |  32 +-
 src/util/config.c                                  |  68 ++++
 src/util/crypto.c                                  |  32 +-
 src/util/denom.c                                   |  93 +-----
 src/util/offline_signatures.c                      |  30 +-
 src/util/util.c                                    |  67 ++++
 57 files changed, 1436 insertions(+), 1113 deletions(-)
 copy src/lib/{exchange_api_csr.c => exchange_api_csr_melt.c} (84%)
 rename src/lib/{exchange_api_csr.c => exchange_api_csr_withdraw.c} (69%)

diff --git a/src/auditor/report-lib.c b/src/auditor/report-lib.c
index 97270ffb..b5984636 100644
--- a/src/auditor/report-lib.c
+++ b/src/auditor/report-lib.c
@@ -141,7 +141,7 @@ add_denomination (
                 GNUNET_h2s (&issue->denom_hash.hash),
                 TALER_amount2s (&value));
     TALER_amount_ntoh (&value,
-                       &issue->fee_withdraw);
+                       &issue->fees.withdraw);
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                 "Withdraw fee is %s\n",
                 TALER_amount2s (&value));
diff --git a/src/auditor/taler-helper-auditor-aggregation.c 
b/src/auditor/taler-helper-auditor-aggregation.c
index 7fffcd28..0b6f6557 100644
--- a/src/auditor/taler-helper-auditor-aggregation.c
+++ b/src/auditor/taler-helper-auditor-aggregation.c
@@ -471,7 +471,7 @@ check_transaction_history_for_deposit (
 
         /* Fee according to denomination data of auditor */
         TALER_amount_ntoh (&fee_expected,
-                           &issue->fee_deposit);
+                           &issue->fees.deposit);
         if (0 !=
             TALER_amount_cmp (&fee_expected,
                               fee_claimed))
@@ -496,7 +496,7 @@ check_transaction_history_for_deposit (
         struct TALER_Amount fee_expected;
 
         TALER_amount_ntoh (&fee_expected,
-                           &issue->fee_refresh);
+                           &issue->fees.refresh);
         if (0 !=
             TALER_amount_cmp (&fee_expected,
                               fee_claimed))
@@ -540,7 +540,7 @@ check_transaction_history_for_deposit (
         struct TALER_Amount fee_expected;
 
         TALER_amount_ntoh (&fee_expected,
-                           &issue->fee_refund);
+                           &issue->fees.refund);
         if (0 !=
             TALER_amount_cmp (&fee_expected,
                               fee_claimed))
diff --git a/src/auditor/taler-helper-auditor-coins.c 
b/src/auditor/taler-helper-auditor-coins.c
index 2ed8e5a1..1b45fea0 100644
--- a/src/auditor/taler-helper-auditor-coins.c
+++ b/src/auditor/taler-helper-auditor-coins.c
@@ -1281,7 +1281,7 @@ refresh_session_cb (void *cls,
     TALER_denom_pub_hash (denom_pub,
                           &h_denom_pub);
     TALER_amount_ntoh (&fee_refresh,
-                       &issue->fee_refresh);
+                       &issue->fees.refresh);
     if (GNUNET_OK !=
         TALER_wallet_melt_verify (amount_with_fee,
                                   &fee_refresh,
@@ -1375,7 +1375,7 @@ refresh_session_cb (void *cls,
       struct TALER_Amount value;
 
       TALER_amount_ntoh (&fee,
-                         &reveal_ctx.new_issues[i]->fee_withdraw);
+                         &reveal_ctx.new_issues[i]->fees.withdraw);
       TALER_amount_ntoh (&value,
                          &reveal_ctx.new_issues[i]->value);
       TALER_ARL_amount_add (&refresh_cost,
@@ -1391,7 +1391,7 @@ refresh_session_cb (void *cls,
       struct TALER_Amount melt_fee;
 
       TALER_amount_ntoh (&melt_fee,
-                         &issue->fee_refresh);
+                         &issue->fees.refresh);
       if (TALER_ARL_SR_POSITIVE !=
           TALER_ARL_amount_subtract_neg (&amount_without_fee,
                                          amount_with_fee,
@@ -1528,7 +1528,7 @@ refresh_session_cb (void *cls,
     struct TALER_Amount rfee;
 
     TALER_amount_ntoh (&rfee,
-                       &issue->fee_refresh);
+                       &issue->fees.refresh);
     TALER_ARL_amount_add (&total_melt_fee_income,
                           &total_melt_fee_income,
                           &rfee);
@@ -1622,7 +1622,7 @@ deposit_cb (void *cls,
                                         &deposit->wire_salt,
                                         &h_wire);
     TALER_amount_ntoh (&deposit_fee,
-                       &issue->fee_deposit);
+                       &issue->fees.deposit);
     /* NOTE: This is one of the operations we might eventually
        want to do in parallel in the background to improve
        auditor performance! */
@@ -1727,7 +1727,7 @@ deposit_cb (void *cls,
     struct TALER_Amount dfee;
 
     TALER_amount_ntoh (&dfee,
-                       &issue->fee_deposit);
+                       &issue->fees.deposit);
     TALER_ARL_amount_add (&total_deposit_fee_income,
                           &total_deposit_fee_income,
                           &dfee);
@@ -1822,7 +1822,7 @@ refund_cb (void *cls,
   }
 
   TALER_amount_ntoh (&refund_fee,
-                     &issue->fee_refund);
+                     &issue->fees.refund);
   if (TALER_ARL_SR_INVALID_NEGATIVE ==
       TALER_ARL_amount_subtract_neg (&amount_without_fee,
                                      amount_with_fee,
@@ -2179,10 +2179,7 @@ check_denomination (
   enum GNUNET_DB_QueryStatus qs;
   struct TALER_AuditorSignatureP auditor_sig;
   struct TALER_Amount coin_value;
-  struct TALER_Amount fee_withdraw;
-  struct TALER_Amount fee_deposit;
-  struct TALER_Amount fee_refresh;
-  struct TALER_Amount fee_refund;
+  struct TALER_DenomFeeSet fees;
   struct GNUNET_TIME_Timestamp start;
   struct GNUNET_TIME_Timestamp end;
 
@@ -2190,14 +2187,8 @@ check_denomination (
   (void) denom_pub;
   TALER_amount_ntoh (&coin_value,
                      &issue->value);
-  TALER_amount_ntoh (&fee_withdraw,
-                     &issue->fee_withdraw);
-  TALER_amount_ntoh (&fee_deposit,
-                     &issue->fee_deposit);
-  TALER_amount_ntoh (&fee_refresh,
-                     &issue->fee_refresh);
-  TALER_amount_ntoh (&fee_refund,
-                     &issue->fee_refund);
+  TALER_denom_fee_set_ntoh (&fees,
+                            &issue->fees);
   start = GNUNET_TIME_timestamp_ntoh (issue->start);
   end = GNUNET_TIME_timestamp_ntoh (issue->expire_legal);
   qs = TALER_ARL_edb->select_auditor_denom_sig (TALER_ARL_edb->cls,
@@ -2225,10 +2216,7 @@ check_denomination (
         GNUNET_TIME_timestamp_ntoh (issue->expire_deposit),
         end,
         &coin_value,
-        &fee_withdraw,
-        &fee_deposit,
-        &fee_refresh,
-        &fee_refund,
+        &fees,
         &TALER_ARL_auditor_pub,
         &auditor_sig))
   {
diff --git a/src/auditor/taler-helper-auditor-reserves.c 
b/src/auditor/taler-helper-auditor-reserves.c
index e9cd51d8..fa096fe0 100644
--- a/src/auditor/taler-helper-auditor-reserves.c
+++ b/src/auditor/taler-helper-auditor-reserves.c
@@ -594,7 +594,7 @@ handle_reserve_out (void *cls,
   }
 
   TALER_amount_ntoh (&withdraw_fee,
-                     &issue->fee_withdraw);
+                     &issue->fees.withdraw);
   TALER_amount_ntoh (&auditor_value,
                      &issue->value);
   TALER_ARL_amount_add (&auditor_amount_with_fee,
diff --git a/src/benchmark/taler-aggregator-benchmark.c 
b/src/benchmark/taler-aggregator-benchmark.c
index 6452d6fc..3eb6e7e9 100644
--- a/src/benchmark/taler-aggregator-benchmark.c
+++ b/src/benchmark/taler-aggregator-benchmark.c
@@ -505,10 +505,10 @@ run (void *cls,
     TALER_denom_pub_hash (&denom_pub,
                           &h_denom_pub);
     make_amountN (2, 0, &issue.properties.value);
-    make_amountN (0, 5, &issue.properties.fee_withdraw);
-    make_amountN (0, 5, &issue.properties.fee_deposit);
-    make_amountN (0, 5, &issue.properties.fee_refresh);
-    make_amountN (0, 5, &issue.properties.fee_refund);
+    make_amountN (0, 5, &issue.properties.fees.withdraw);
+    make_amountN (0, 5, &issue.properties.fees.deposit);
+    make_amountN (0, 5, &issue.properties.fees.refresh);
+    make_amountN (0, 5, &issue.properties.fees.refund);
     issue.properties.denom_hash = h_denom_pub;
     if (0 >=
         plugin->insert_denomination_info (plugin->cls,
diff --git a/src/exchange-tools/taler-auditor-offline.c 
b/src/exchange-tools/taler-auditor-offline.c
index afde705b..6e37fd9b 100644
--- a/src/exchange-tools/taler-auditor-offline.c
+++ b/src/exchange-tools/taler-auditor-offline.c
@@ -757,10 +757,7 @@ show_denomkeys (const json_t *denomkeys)
     struct GNUNET_TIME_Timestamp stamp_expire_deposit;
     struct GNUNET_TIME_Timestamp stamp_expire_legal;
     struct TALER_Amount coin_value;
-    struct TALER_Amount fee_withdraw;
-    struct TALER_Amount fee_deposit;
-    struct TALER_Amount fee_refresh;
-    struct TALER_Amount fee_refund;
+    struct TALER_DenomFeeSet fees;
     struct TALER_MasterSignatureP master_sig;
     struct GNUNET_JSON_Specification spec[] = {
       TALER_JSON_spec_denom_pub ("denom_pub",
@@ -768,18 +765,9 @@ show_denomkeys (const json_t *denomkeys)
       TALER_JSON_spec_amount ("value",
                               currency,
                               &coin_value),
-      TALER_JSON_spec_amount ("fee_withdraw",
-                              currency,
-                              &fee_withdraw),
-      TALER_JSON_spec_amount ("fee_deposit",
-                              currency,
-                              &fee_deposit),
-      TALER_JSON_spec_amount ("fee_refresh",
-                              currency,
-                              &fee_refresh),
-      TALER_JSON_spec_amount ("fee_refund",
-                              currency,
-                              &fee_refund),
+      TALER_JSON_SPEC_DENOM_FEES ("fee",
+                                  currency,
+                                  &fees),
       GNUNET_JSON_spec_timestamp ("stamp_start",
                                   &stamp_start),
       GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
@@ -824,10 +812,7 @@ show_denomkeys (const json_t *denomkeys)
           stamp_expire_deposit,
           stamp_expire_legal,
           &coin_value,
-          &fee_withdraw,
-          &fee_deposit,
-          &fee_refresh,
-          &fee_refund,
+          &fees,
           &master_pub,
           &master_sig))
     {
@@ -847,10 +832,10 @@ show_denomkeys (const json_t *denomkeys)
       char *deposit_s;
       char *legal_s;
 
-      withdraw_fee_s = TALER_amount_to_string (&fee_withdraw);
-      deposit_fee_s = TALER_amount_to_string (&fee_deposit);
-      refresh_fee_s = TALER_amount_to_string (&fee_refresh);
-      refund_fee_s = TALER_amount_to_string (&fee_refund);
+      withdraw_fee_s = TALER_amount_to_string (&fees.withdraw);
+      deposit_fee_s = TALER_amount_to_string (&fees.deposit);
+      refresh_fee_s = TALER_amount_to_string (&fees.refresh);
+      refund_fee_s = TALER_amount_to_string (&fees.refund);
       deposit_s = GNUNET_strdup (
         GNUNET_TIME_timestamp2s (stamp_expire_deposit));
       legal_s = GNUNET_strdup (
@@ -1058,10 +1043,7 @@ sign_denomkeys (const json_t *denomkeys)
     struct GNUNET_TIME_Timestamp stamp_expire_deposit;
     struct GNUNET_TIME_Timestamp stamp_expire_legal;
     struct TALER_Amount coin_value;
-    struct TALER_Amount fee_withdraw;
-    struct TALER_Amount fee_deposit;
-    struct TALER_Amount fee_refresh;
-    struct TALER_Amount fee_refund;
+    struct TALER_DenomFeeSet fees;
     struct TALER_MasterSignatureP master_sig;
     struct GNUNET_JSON_Specification spec[] = {
       TALER_JSON_spec_denom_pub ("denom_pub",
@@ -1069,18 +1051,9 @@ sign_denomkeys (const json_t *denomkeys)
       TALER_JSON_spec_amount ("value",
                               currency,
                               &coin_value),
-      TALER_JSON_spec_amount ("fee_withdraw",
-                              currency,
-                              &fee_withdraw),
-      TALER_JSON_spec_amount ("fee_deposit",
-                              currency,
-                              &fee_deposit),
-      TALER_JSON_spec_amount ("fee_refresh",
-                              currency,
-                              &fee_refresh),
-      TALER_JSON_spec_amount ("fee_refund",
-                              currency,
-                              &fee_refund),
+      TALER_JSON_SPEC_DENOM_FEES ("fee",
+                                  currency,
+                                  &fees),
       GNUNET_JSON_spec_timestamp ("stamp_start",
                                   &stamp_start),
       GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
@@ -1121,10 +1094,7 @@ sign_denomkeys (const json_t *denomkeys)
           stamp_expire_deposit,
           stamp_expire_legal,
           &coin_value,
-          &fee_withdraw,
-          &fee_deposit,
-          &fee_refresh,
-          &fee_refund,
+          &fees,
           &master_pub,
           &master_sig))
     {
@@ -1147,10 +1117,7 @@ sign_denomkeys (const json_t *denomkeys)
                                          stamp_expire_deposit,
                                          stamp_expire_legal,
                                          &coin_value,
-                                         &fee_withdraw,
-                                         &fee_deposit,
-                                         &fee_refresh,
-                                         &fee_refund,
+                                         &fees,
                                          &auditor_priv,
                                          &auditor_sig);
       output_operation (OP_SIGN_DENOMINATION,
diff --git a/src/exchange-tools/taler-exchange-offline.c 
b/src/exchange-tools/taler-exchange-offline.c
index 55720a1b..2446ebf3 100644
--- a/src/exchange-tools/taler-exchange-offline.c
+++ b/src/exchange-tools/taler-exchange-offline.c
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2020, 2021 Taler Systems SA
+   Copyright (C) 2020, 2021, 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
@@ -2831,10 +2831,7 @@ show_denomkeys (const struct 
TALER_SecurityModulePublicKeyP *secm_pub_rsa,
     struct GNUNET_TIME_Timestamp stamp_expire_deposit;
     struct GNUNET_TIME_Timestamp stamp_expire_legal;
     struct TALER_Amount coin_value;
-    struct TALER_Amount fee_withdraw;
-    struct TALER_Amount fee_deposit;
-    struct TALER_Amount fee_refresh;
-    struct TALER_Amount fee_refund;
+    struct TALER_DenomFeeSet fees;
     struct TALER_SecurityModuleSignatureP secm_sig;
     struct GNUNET_JSON_Specification spec[] = {
       GNUNET_JSON_spec_string ("section_name",
@@ -2844,18 +2841,9 @@ show_denomkeys (const struct 
TALER_SecurityModulePublicKeyP *secm_pub_rsa,
       TALER_JSON_spec_amount ("value",
                               currency,
                               &coin_value),
-      TALER_JSON_spec_amount ("fee_withdraw",
-                              currency,
-                              &fee_withdraw),
-      TALER_JSON_spec_amount ("fee_deposit",
-                              currency,
-                              &fee_deposit),
-      TALER_JSON_spec_amount ("fee_refresh",
-                              currency,
-                              &fee_refresh),
-      TALER_JSON_spec_amount ("fee_refund",
-                              currency,
-                              &fee_refund),
+      TALER_JSON_SPEC_DENOM_FEES ("fee",
+                                  currency,
+                                  &fees),
       GNUNET_JSON_spec_timestamp ("stamp_start",
                                   &stamp_start),
       GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
@@ -2949,10 +2937,10 @@ show_denomkeys (const struct 
TALER_SecurityModulePublicKeyP *secm_pub_rsa,
       char *deposit_s;
       char *legal_s;
 
-      withdraw_fee_s = TALER_amount_to_string (&fee_withdraw);
-      deposit_fee_s = TALER_amount_to_string (&fee_deposit);
-      refresh_fee_s = TALER_amount_to_string (&fee_refresh);
-      refund_fee_s = TALER_amount_to_string (&fee_refund);
+      withdraw_fee_s = TALER_amount_to_string (&fees.withdraw);
+      deposit_fee_s = TALER_amount_to_string (&fees.deposit);
+      refresh_fee_s = TALER_amount_to_string (&fees.refresh);
+      refund_fee_s = TALER_amount_to_string (&fees.refund);
       deposit_s = GNUNET_strdup (
         GNUNET_TIME_timestamp2s (stamp_expire_deposit));
       legal_s = GNUNET_strdup (
@@ -3324,10 +3312,7 @@ sign_denomkeys (const struct 
TALER_SecurityModulePublicKeyP *secm_pub_rsa,
     struct GNUNET_TIME_Timestamp stamp_expire_deposit;
     struct GNUNET_TIME_Timestamp stamp_expire_legal;
     struct TALER_Amount coin_value;
-    struct TALER_Amount fee_withdraw;
-    struct TALER_Amount fee_deposit;
-    struct TALER_Amount fee_refresh;
-    struct TALER_Amount fee_refund;
+    struct TALER_DenomFeeSet fees;
     struct TALER_SecurityModuleSignatureP secm_sig;
     struct GNUNET_JSON_Specification spec[] = {
       GNUNET_JSON_spec_string ("section_name",
@@ -3337,18 +3322,9 @@ sign_denomkeys (const struct 
TALER_SecurityModulePublicKeyP *secm_pub_rsa,
       TALER_JSON_spec_amount ("value",
                               currency,
                               &coin_value),
-      TALER_JSON_spec_amount ("fee_withdraw",
-                              currency,
-                              &fee_withdraw),
-      TALER_JSON_spec_amount ("fee_deposit",
-                              currency,
-                              &fee_deposit),
-      TALER_JSON_spec_amount ("fee_refresh",
-                              currency,
-                              &fee_refresh),
-      TALER_JSON_spec_amount ("fee_refund",
-                              currency,
-                              &fee_refund),
+      TALER_JSON_SPEC_DENOM_FEES ("fee",
+                                  currency,
+                                  &fees),
       GNUNET_JSON_spec_timestamp ("stamp_start",
                                   &stamp_start),
       GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
@@ -3458,10 +3434,7 @@ sign_denomkeys (const struct 
TALER_SecurityModulePublicKeyP *secm_pub_rsa,
                                                   stamp_expire_deposit,
                                                   stamp_expire_legal,
                                                   &coin_value,
-                                                  &fee_withdraw,
-                                                  &fee_deposit,
-                                                  &fee_refresh,
-                                                  &fee_refund,
+                                                  &fees,
                                                   &master_priv,
                                                   &master_sig);
       GNUNET_assert (0 ==
diff --git a/src/exchange/taler-exchange-httpd.c 
b/src/exchange/taler-exchange-httpd.c
index a0d0aa3b..efaf6311 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -925,9 +925,15 @@ handle_mhd_request (void *cls,
     },
     /* request R, used in clause schnorr withdraw and refresh */
     {
-      .url = "csr",
+      .url = "csr-melt",
       .method = MHD_HTTP_METHOD_POST,
-      .handler.post = &TEH_handler_csr,
+      .handler.post = &TEH_handler_csr_melt,
+      .nargs = 0
+    },
+    {
+      .url = "csr-withdraw",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &TEH_handler_csr_withdraw,
       .nargs = 0
     },
     /* Withdrawing coins / interaction with reserves */
diff --git a/src/exchange/taler-exchange-httpd_auditors.c 
b/src/exchange/taler-exchange-httpd_auditors.c
index 1b8af311..b9ebbe58 100644
--- a/src/exchange/taler-exchange-httpd_auditors.c
+++ b/src/exchange/taler-exchange-httpd_auditors.c
@@ -150,10 +150,7 @@ add_auditor_denom_sig (void *cls,
         meta.expire_deposit,
         meta.expire_legal,
         &meta.value,
-        &meta.fee_withdraw,
-        &meta.fee_deposit,
-        &meta.fee_refresh,
-        &meta.fee_refund,
+        &meta.fees,
         awc->auditor_pub,
         &awc->auditor_sig))
   {
diff --git a/src/exchange/taler-exchange-httpd_csr.c 
b/src/exchange/taler-exchange-httpd_csr.c
index 47694d30..42383597 100644
--- a/src/exchange/taler-exchange-httpd_csr.c
+++ b/src/exchange/taler-exchange-httpd_csr.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2021 Taler Systems SA
+  Copyright (C) 2014-2022 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as
@@ -33,15 +33,16 @@
 
 
 MHD_RESULT
-TEH_handler_csr (struct TEH_RequestContext *rc,
-                 const json_t *root,
-                 const char *const args[])
+TEH_handler_csr_melt (struct TEH_RequestContext *rc,
+                      const json_t *root,
+                      const char *const args[])
 {
+  struct TALER_RefreshMasterSecretP rms;
   unsigned int csr_requests_num;
   json_t *csr_requests;
-  json_t *csr_response_ewvs;
-  json_t *csr_response;
   struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("rms",
+                                 &rms),
     GNUNET_JSON_spec_json ("nks",
                            &csr_requests),
     GNUNET_JSON_spec_end ()
@@ -50,8 +51,7 @@ TEH_handler_csr (struct TEH_RequestContext *rc,
   struct TEH_DenominationKey *dk;
 
   (void) args;
-
-  // parse input
+  /* parse input */
   {
     enum GNUNET_GenericReturnValue res;
 
@@ -62,7 +62,8 @@ TEH_handler_csr (struct TEH_RequestContext *rc,
       return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
   }
   csr_requests_num = json_array_size (csr_requests);
-  if (TALER_MAX_FRESH_COINS <= csr_requests_num)
+  if ( (TALER_MAX_FRESH_COINS <= csr_requests_num) ||
+       (0 == csr_requests_num) )
   {
     GNUNET_JSON_parse_free (spec);
     return TALER_MHD_reply_with_error (
@@ -71,109 +72,253 @@ TEH_handler_csr (struct TEH_RequestContext *rc,
       TALER_EC_EXCHANGE_GENERIC_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE,
       NULL);
   }
-  struct TALER_CsNonce nonces[GNUNET_NZL (csr_requests_num)];
-  struct TALER_DenominationHash denom_pub_hashes[GNUNET_NZL 
(csr_requests_num)];
-  for (unsigned int i = 0; i < csr_requests_num; i++)
-  {
-    struct TALER_CsNonce *nonce = &nonces[i];
-    struct TALER_DenominationHash *denom_pub_hash = &denom_pub_hashes[i];
-    struct GNUNET_JSON_Specification csr_spec[] = {
-      GNUNET_JSON_spec_fixed ("nonce",
-                              nonce,
-                              sizeof (struct TALER_CsNonce)),
-      GNUNET_JSON_spec_fixed ("denom_pub_hash",
-                              denom_pub_hash,
-                              sizeof (struct TALER_DenominationHash)),
-      GNUNET_JSON_spec_end ()
-    };
-    enum GNUNET_GenericReturnValue res;
-
-    res = TALER_MHD_parse_json_array (rc->connection,
-                                      csr_requests,
-                                      csr_spec,
-                                      i,
-                                      -1);
-    if (GNUNET_OK != res)
-    {
-      GNUNET_JSON_parse_free (spec);
-      return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
-    }
-  }
-  GNUNET_JSON_parse_free (spec);
 
-  struct TALER_ExchangeWithdrawValues ewvs[GNUNET_NZL (csr_requests_num)];
-  for (unsigned int i = 0; i < csr_requests_num; i++)
   {
-    const struct TALER_CsNonce *nonce = &nonces[i];
-    const struct TALER_DenominationHash *denom_pub_hash = &denom_pub_hashes[i];
-    struct TALER_DenominationCSPublicRPairP *r_pub
-      = &ewvs[i].details.cs_values;
+    struct TALER_ExchangeWithdrawValues ewvs[csr_requests_num];
 
-    ewvs[i].cipher = TALER_DENOMINATION_CS;
-    // check denomination referenced by denom_pub_hash
     {
-      struct TEH_KeyStateHandle *ksh;
+      struct TALER_CsNonce nonces[csr_requests_num];
+      struct TALER_DenominationHash denom_pub_hashes[csr_requests_num];
 
-      ksh = TEH_keys_get_state ();
-      if (NULL == ksh)
+      for (unsigned int i = 0; i < csr_requests_num; i++)
       {
-        return TALER_MHD_reply_with_error (rc->connection,
-                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                           
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
-                                           NULL);
-      }
-      dk = TEH_keys_denomination_by_hash2 (ksh,
-                                           denom_pub_hash,
-                                           NULL,
-                                           NULL);
-      if (NULL == dk)
-      {
-        return TEH_RESPONSE_reply_unknown_denom_pub_hash (
-          rc->connection,
-          &denom_pub_hash[i]);
-      }
-      if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time))
-      {
-        /* This denomination is past the expiration time for 
withdraws/refreshes*/
-        return TEH_RESPONSE_reply_expired_denom_pub_hash (
-          rc->connection,
-          denom_pub_hash,
-          TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
-          "CSR");
-      }
-      if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
-      {
-        /* This denomination is not yet valid, no need to check
-           for idempotency! */
-        return TEH_RESPONSE_reply_expired_denom_pub_hash (
-          rc->connection,
-          denom_pub_hash,
-          TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
-          "CSR");
+        uint32_t coin_off;
+        struct TALER_DenominationHash *denom_pub_hash = &denom_pub_hashes[i];
+        struct GNUNET_JSON_Specification csr_spec[] = {
+          GNUNET_JSON_spec_uint32 ("coin_offset",
+                                   &coin_off),
+          GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+                                       denom_pub_hash),
+          GNUNET_JSON_spec_end ()
+        };
+        enum GNUNET_GenericReturnValue res;
+
+        res = TALER_MHD_parse_json_array (rc->connection,
+                                          csr_requests,
+                                          csr_spec,
+                                          i,
+                                          -1);
+        if (GNUNET_OK != res)
+        {
+          GNUNET_JSON_parse_free (spec);
+          return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+        }
+        TALER_cs_refresh_nonce_derive (&rms,
+                                       coin_off,
+                                       &nonces[i]);
       }
-      if (dk->recoup_possible)
+      GNUNET_JSON_parse_free (spec);
+
+      for (unsigned int i = 0; i < csr_requests_num; i++)
       {
-        /* This denomination has been revoked */
-        return TEH_RESPONSE_reply_expired_denom_pub_hash (
-          rc->connection,
-          denom_pub_hash,
-          TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
-          "CSR");
+        const struct TALER_CsNonce *nonce = &nonces[i];
+        const struct TALER_DenominationHash *denom_pub_hash =
+          &denom_pub_hashes[i];
+        struct TALER_DenominationCSPublicRPairP *r_pub
+          = &ewvs[i].details.cs_values;
+
+        ewvs[i].cipher = TALER_DENOMINATION_CS;
+        /* check denomination referenced by denom_pub_hash */
+        {
+          struct TEH_KeyStateHandle *ksh;
+
+          ksh = TEH_keys_get_state ();
+          if (NULL == ksh)
+          {
+            return TALER_MHD_reply_with_error (rc->connection,
+                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                               
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+                                               NULL);
+          }
+          dk = TEH_keys_denomination_by_hash2 (ksh,
+                                               denom_pub_hash,
+                                               NULL,
+                                               NULL);
+          if (NULL == dk)
+          {
+            return TEH_RESPONSE_reply_unknown_denom_pub_hash (
+              rc->connection,
+              &denom_pub_hash[i]);
+          }
+          if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time))
+          {
+            /* This denomination is past the expiration time for 
withdraws/refreshes*/
+            return TEH_RESPONSE_reply_expired_denom_pub_hash (
+              rc->connection,
+              denom_pub_hash,
+              TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+              "csr-melt");
+          }
+          if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+          {
+            /* This denomination is not yet valid, no need to check
+               for idempotency! */
+            return TEH_RESPONSE_reply_expired_denom_pub_hash (
+              rc->connection,
+              denom_pub_hash,
+              TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+              "csr-melt");
+          }
+          if (dk->recoup_possible)
+          {
+            /* This denomination has been revoked */
+            return TEH_RESPONSE_reply_expired_denom_pub_hash (
+              rc->connection,
+              denom_pub_hash,
+              TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+              "csr-melt");
+          }
+          if (TALER_DENOMINATION_CS != dk->denom_pub.cipher)
+          {
+            /* denomination is valid but not for CS */
+            return TEH_RESPONSE_reply_invalid_denom_cipher_for_operation (
+              rc->connection,
+              denom_pub_hash);
+          }
+        }
+
+        /* derive r_pub */
+        // FIXME: bundle all requests into one derivation request 
(TEH_keys_..., crypto helper, security module)
+        ec = TEH_keys_denomination_cs_r_pub (denom_pub_hash,
+                                             nonce,
+                                             r_pub);
+        if (TALER_EC_NONE != ec)
+        {
+          GNUNET_break (0);
+          return TALER_MHD_reply_with_ec (rc->connection,
+                                          ec,
+                                          NULL);
+        }
       }
-      if (TALER_DENOMINATION_CS != dk->denom_pub.cipher)
+    }
+
+    /* send response */
+    {
+      json_t *csr_response_ewvs;
+      json_t *csr_response;
+
+      csr_response_ewvs = json_array ();
+      for (unsigned int i = 0; i < csr_requests_num; i++)
       {
-        // denomination is valid but not CS
-        return TEH_RESPONSE_reply_invalid_denom_cipher_for_operation (
-          rc->connection,
-          denom_pub_hash);
+        json_t *csr_obj;
+
+        csr_obj = GNUNET_JSON_PACK (
+          TALER_JSON_pack_exchange_withdraw_values ("ewv",
+                                                    &ewvs[i]));
+        GNUNET_assert (NULL != csr_obj);
+        GNUNET_assert (0 ==
+                       json_array_append_new (csr_response_ewvs,
+                                              csr_obj));
       }
+      csr_response = GNUNET_JSON_PACK (
+        GNUNET_JSON_pack_array_steal ("ewvs",
+                                      csr_response_ewvs));
+      GNUNET_assert (NULL != csr_response);
+      return TALER_MHD_reply_json_steal (rc->connection,
+                                         csr_response,
+                                         MHD_HTTP_OK);
+    }
+  }
+}
+
+
+MHD_RESULT
+TEH_handler_csr_withdraw (struct TEH_RequestContext *rc,
+                          const json_t *root,
+                          const char *const args[])
+{
+  struct TALER_CsNonce nonce;
+  struct TALER_DenominationHash denom_pub_hash;
+  struct TALER_ExchangeWithdrawValues ewv = {
+    .cipher = TALER_DENOMINATION_CS
+  };
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed ("nonce",
+                            &nonce,
+                            sizeof (struct TALER_CsNonce)),
+    GNUNET_JSON_spec_fixed ("denom_pub_hash",
+                            &denom_pub_hash,
+                            sizeof (struct TALER_DenominationHash)),
+    GNUNET_JSON_spec_end ()
+  };
+  struct TEH_DenominationKey *dk;
+
+  (void) args;
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (rc->connection,
+                                     root,
+                                     spec);
+    if (GNUNET_OK != res)
+      return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
+  }
+
+  {
+    struct TEH_KeyStateHandle *ksh;
+
+    ksh = TEH_keys_get_state ();
+    if (NULL == ksh)
+    {
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+                                         NULL);
+    }
+    dk = TEH_keys_denomination_by_hash2 (ksh,
+                                         &denom_pub_hash,
+                                         NULL,
+                                         NULL);
+    if (NULL == dk)
+    {
+      return TEH_RESPONSE_reply_unknown_denom_pub_hash (
+        rc->connection,
+        &denom_pub_hash);
+    }
+    if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time))
+    {
+      /* This denomination is past the expiration time for 
withdraws/refreshes*/
+      return TEH_RESPONSE_reply_expired_denom_pub_hash (
+        rc->connection,
+        &denom_pub_hash,
+        TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+        "csr-withdraw");
+    }
+    if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+    {
+      /* This denomination is not yet valid, no need to check
+         for idempotency! */
+      return TEH_RESPONSE_reply_expired_denom_pub_hash (
+        rc->connection,
+        &denom_pub_hash,
+        TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+        "csr-withdraw");
+    }
+    if (dk->recoup_possible)
+    {
+      /* This denomination has been revoked */
+      return TEH_RESPONSE_reply_expired_denom_pub_hash (
+        rc->connection,
+        &denom_pub_hash,
+        TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+        "csr-withdraw");
+    }
+    if (TALER_DENOMINATION_CS != dk->denom_pub.cipher)
+    {
+      /* denomination is valid but not for CS */
+      return TEH_RESPONSE_reply_invalid_denom_cipher_for_operation (
+        rc->connection,
+        &denom_pub_hash);
     }
+  }
+
+  /* derive r_pub */
+  {
+    enum TALER_ErrorCode ec;
 
-    // derive r_pub
-    // FIXME: bundle all requests into one derivation request (TEH_keys_..., 
crypto helper, security module)
-    ec = TEH_keys_denomination_cs_r_pub (denom_pub_hash,
-                                         nonce,
-                                         r_pub);
+    ec = TEH_keys_denomination_cs_r_pub (&denom_pub_hash,
+                                         &nonce,
+                                         &ewv.details.cs_values);
     if (TALER_EC_NONE != ec)
     {
       GNUNET_break (0);
@@ -183,27 +328,17 @@ TEH_handler_csr (struct TEH_RequestContext *rc,
     }
   }
 
-  // send response
-  csr_response_ewvs = json_array ();
-  for (unsigned int i = 0; i < csr_requests_num; i++)
   {
     json_t *csr_obj;
 
     csr_obj = GNUNET_JSON_PACK (
       TALER_JSON_pack_exchange_withdraw_values ("ewv",
-                                                &ewvs[i]));
+                                                &ewv));
     GNUNET_assert (NULL != csr_obj);
-    GNUNET_assert (0 ==
-                   json_array_append_new (csr_response_ewvs,
-                                          csr_obj));
+    return TALER_MHD_reply_json_steal (rc->connection,
+                                       csr_obj,
+                                       MHD_HTTP_OK);
   }
-  csr_response = GNUNET_JSON_PACK (
-    GNUNET_JSON_pack_array_steal ("ewvs",
-                                  csr_response_ewvs));
-  GNUNET_assert (NULL != csr_response);
-  return TALER_MHD_reply_json_steal (rc->connection,
-                                     csr_response,
-                                     MHD_HTTP_OK);
 }
 
 
diff --git a/src/exchange/taler-exchange-httpd_csr.h 
b/src/exchange/taler-exchange-httpd_csr.h
index 3bd98742..615255f9 100644
--- a/src/exchange/taler-exchange-httpd_csr.h
+++ b/src/exchange/taler-exchange-httpd_csr.h
@@ -15,7 +15,7 @@
 */
 /**
  * @file taler-exchange-httpd_csr.h
- * @brief Handle /csr requests
+ * @brief Handle /csr-* requests
  * @author Lucien Heuzeveldt
  * @author Gian Demarmles
  */
@@ -27,8 +27,7 @@
 
 
 /**
- * Handle a "/csr" request.  Parses the "nonce"  and
- * the "denom_pub_hash" (identifying a denomination) used to derive the r_pub.
+ * Handle a "/csr-melt" request.
  *
  * @param rc request context
  * @param root uploaded JSON data
@@ -36,8 +35,22 @@
  * @return MHD result code
   */
 MHD_RESULT
-TEH_handler_csr (struct TEH_RequestContext *rc,
-                 const json_t *root,
-                 const char *const args[]);
+TEH_handler_csr_melt (struct TEH_RequestContext *rc,
+                      const json_t *root,
+                      const char *const args[]);
+
+
+/**
+ * Handle a "/csr-withdraw" request.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args empty array
+ * @return MHD result code
+  */
+MHD_RESULT
+TEH_handler_csr_withdraw (struct TEH_RequestContext *rc,
+                          const json_t *root,
+                          const char *const args[]);
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_deposit.c 
b/src/exchange/taler-exchange-httpd_deposit.c
index d750ec70..5a401abb 100644
--- a/src/exchange/taler-exchange-httpd_deposit.c
+++ b/src/exchange/taler-exchange-httpd_deposit.c
@@ -370,7 +370,7 @@ TEH_handler_deposit (struct MHD_Connection *connection,
                                          NULL);
     }
 
-    deposit.deposit_fee = dk->meta.fee_deposit;
+    deposit.deposit_fee = dk->meta.fees.deposit;
     /* check coin signature */
     if (GNUNET_YES !=
         TALER_test_coin_valid (&deposit.coin,
diff --git a/src/exchange/taler-exchange-httpd_keys.c 
b/src/exchange/taler-exchange-httpd_keys.c
index d1dfb28b..f9a79a41 100644
--- a/src/exchange/taler-exchange-httpd_keys.c
+++ b/src/exchange/taler-exchange-httpd_keys.c
@@ -2113,14 +2113,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
                                        &dk->denom_pub),
             TALER_JSON_pack_amount ("value",
                                     &dk->meta.value),
-            TALER_JSON_pack_amount ("fee_withdraw",
-                                    &dk->meta.fee_withdraw),
-            TALER_JSON_pack_amount ("fee_deposit",
-                                    &dk->meta.fee_deposit),
-            TALER_JSON_pack_amount ("fee_refresh",
-                                    &dk->meta.fee_refresh),
-            TALER_JSON_pack_amount ("fee_refund",
-                                    &dk->meta.fee_refund));
+            TALER_JSON_PACK_DENOM_FEES ("fee",
+                                        &dk->meta.fees));
 
         /* Put the denom into the correct array depending on the settings and
          * the properties of the denomination.  Also, we build up the right
@@ -2810,74 +2804,22 @@ load_extension_data (const char *section_name,
                                section_name);
     return GNUNET_SYSERR;
   }
-  if (GNUNET_OK !=
-      TALER_config_get_amount (TEH_cfg,
-                               section_name,
-                               "FEE_WITHDRAW",
-                               &meta->fee_withdraw))
-  {
-    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
-                               "Need amount for option `%s' in section `%s'\n",
-                               "FEE_WITHDRAW",
-                               section_name);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_config_get_amount (TEH_cfg,
-                               section_name,
-                               "FEE_DEPOSIT",
-                               &meta->fee_deposit))
-  {
-    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
-                               "Need amount for option `%s' in section `%s'\n",
-                               "FEE_DEPOSIT",
-                               section_name);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_config_get_amount (TEH_cfg,
-                               section_name,
-                               "FEE_REFRESH",
-                               &meta->fee_refresh))
-  {
-    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
-                               "Need amount for option `%s' in section `%s'\n",
-                               "FEE_REFRESH",
-                               section_name);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_config_get_amount (TEH_cfg,
-                               section_name,
-                               "FEE_REFUND",
-                               &meta->fee_refund))
-  {
-    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
-                               "Need amount for option `%s' in section `%s'\n",
-                               "FEE_REFUND",
-                               section_name);
-    return GNUNET_SYSERR;
-  }
-  if ( (0 != strcasecmp (TEH_currency,
-                         meta->value.currency)) ||
-       (0 != strcasecmp (TEH_currency,
-                         meta->fee_withdraw.currency)) ||
-       (0 != strcasecmp (TEH_currency,
-                         meta->fee_deposit.currency)) ||
-       (0 != strcasecmp (TEH_currency,
-                         meta->fee_refresh.currency)) ||
-       (0 != strcasecmp (TEH_currency,
-                         meta->fee_refund.currency)) )
+  if (0 != strcasecmp (TEH_currency,
+                       meta->value.currency))
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Need amounts in section `%s' to use currency `%s'\n",
+                "Need denomination value in section `%s' to use currency 
`%s'\n",
                 section_name,
                 TEH_currency);
     return GNUNET_SYSERR;
   }
-
-  meta->age_mask = load_age_mask (section_name);
-
+  if (GNUNET_OK !=
+      TALER_config_get_denom_fees (TEH_cfg,
+                                   TEH_currency,
+                                   section_name,
+                                   &meta->fees))
+    return GNUNET_SYSERR;
+  meta->age_restrictions = load_age_mask (section_name);
   return GNUNET_OK;
 }
 
@@ -3040,14 +2982,8 @@ add_future_denomkey_cb (void *cls,
                                     meta.expire_legal),
         TALER_JSON_pack_denom_pub ("denom_pub",
                                    &hd->denom_pub),
-        TALER_JSON_pack_amount ("fee_withdraw",
-                                &meta.fee_withdraw),
-        TALER_JSON_pack_amount ("fee_deposit",
-                                &meta.fee_deposit),
-        TALER_JSON_pack_amount ("fee_refresh",
-                                &meta.fee_refresh),
-        TALER_JSON_pack_amount ("fee_refund",
-                                &meta.fee_refund),
+        TALER_JSON_PACK_DENOM_FEES ("fee",
+                                    &meta.fees),
         GNUNET_JSON_pack_data_auto ("denom_secmod_sig",
                                     &hd->sm_sig),
         GNUNET_JSON_pack_string ("section_name",
diff --git a/src/exchange/taler-exchange-httpd_management_post_keys.c 
b/src/exchange/taler-exchange-httpd_management_post_keys.c
index c353a995..2e48497a 100644
--- a/src/exchange/taler-exchange-httpd_management_post_keys.c
+++ b/src/exchange/taler-exchange-httpd_management_post_keys.c
@@ -187,10 +187,7 @@ add_keys (void *cls,
           meta.expire_deposit,
           meta.expire_legal,
           &meta.value,
-          &meta.fee_withdraw,
-          &meta.fee_deposit,
-          &meta.fee_refresh,
-          &meta.fee_refund,
+          &meta.fees,
           &TEH_master_public_key,
           &d->master_sig))
     {
diff --git a/src/exchange/taler-exchange-httpd_melt.c 
b/src/exchange/taler-exchange-httpd_melt.c
index 8bfdf8ce..cada8e7b 100644
--- a/src/exchange/taler-exchange-httpd_melt.c
+++ b/src/exchange/taler-exchange-httpd_melt.c
@@ -104,6 +104,11 @@ struct MeltContext
    */
   struct TALER_Amount coin_refresh_fee;
 
+  /**
+   * Refresh master secret, if any of the fresh denominations use CS.
+   */
+  struct TALER_RefreshMasterSecretP rms;
+
   /**
    * Set to true if this coin's denomination was revoked and the operation
    * is thus only allowed for zombie coins where the transaction
@@ -117,6 +122,10 @@ struct MeltContext
    */
   bool coin_is_dirty;
 
+  /**
+   * True if @e rms is set.
+   */
+  bool have_rms;
 };
 
 
@@ -155,6 +164,9 @@ melt_transaction (void *cls,
 
   if (0 >
       (qs = TEH_plugin->do_melt (TEH_plugin->cls,
+                                 rmc->have_rms
+                                 ? &rmc->rms
+                                 : NULL,
                                  &rmc->refresh_session,
                                  rmc->known_coin_id,
                                  &rmc->zombie_required,
@@ -300,7 +312,7 @@ check_melt_valid (struct MHD_Connection *connection,
       "MELT");
   }
 
-  rmc->coin_refresh_fee = dk->meta.fee_refresh;
+  rmc->coin_refresh_fee = dk->meta.fees.refresh;
   rmc->coin_value = dk->meta.value;
 
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -422,6 +434,9 @@ TEH_handler_melt (struct MHD_Connection *connection,
                             &rmc.refresh_session.amount_with_fee),
     GNUNET_JSON_spec_fixed_auto ("rc",
                                  &rmc.refresh_session.rc),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("rms",
+                                   &rmc.rms)),
     GNUNET_JSON_spec_end ()
   };
 
@@ -429,7 +444,6 @@ TEH_handler_melt (struct MHD_Connection *connection,
           0,
           sizeof (rmc));
   rmc.refresh_session.coin.coin_pub = *coin_pub;
-
   {
     enum GNUNET_GenericReturnValue ret;
     ret = TALER_MHD_parse_json_data (connection,
@@ -438,7 +452,8 @@ 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_recoup.c 
b/src/exchange/taler-exchange-httpd_recoup.c
index 4ac997e9..32df3c4c 100644
--- a/src/exchange/taler-exchange-httpd_recoup.c
+++ b/src/exchange/taler-exchange-httpd_recoup.c
@@ -42,7 +42,7 @@ struct RecoupContext
   /**
    * Hash identifying the withdraw request.
    */
-  struct TALER_WithdrawIdentificationHash wih;
+  struct TALER_BlindedCoinHash h_coin_ev;
 
   /**
    * Set by #recoup_transaction() to the reserve that will
@@ -273,9 +273,9 @@ verify_and_execute_recoup (
       blinded_planchet.details.cs_blinded_planchet.nonce
         = *nonce;
     if (GNUNET_OK !=
-        TALER_withdraw_request_hash (&blinded_planchet,
-                                     &coin->denom_pub_hash,
-                                     &pc.wih))
+        TALER_coin_ev_hash (&blinded_planchet,
+                            &coin->denom_pub_hash,
+                            &pc.h_coin_ev))
     {
       GNUNET_break (0);
       return TALER_MHD_reply_with_error (connection,
@@ -308,10 +308,10 @@ verify_and_execute_recoup (
   {
     enum GNUNET_DB_QueryStatus qs;
 
-    qs = TEH_plugin->get_reserve_by_wih (TEH_plugin->cls,
-                                         &pc.wih,
-                                         &pc.reserve_pub,
-                                         &pc.reserve_out_serial_id);
+    qs = TEH_plugin->get_reserve_by_h_blind (TEH_plugin->cls,
+                                             &pc.h_coin_ev,
+                                             &pc.reserve_pub,
+                                             &pc.reserve_out_serial_id);
     if (0 > qs)
     {
       GNUNET_break (0);
@@ -319,13 +319,13 @@ verify_and_execute_recoup (
         connection,
         MHD_HTTP_INTERNAL_SERVER_ERROR,
         TALER_EC_GENERIC_DB_FETCH_FAILED,
-        "get_reserve_by_wih");
+        "get_reserve_by_h_blind");
     }
     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     {
       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                   "Recoup requested for unknown envelope %s\n",
-                  GNUNET_h2s (&pc.wih.hash));
+                  GNUNET_h2s (&pc.h_coin_ev.hash));
       return TALER_MHD_reply_with_error (
         connection,
         MHD_HTTP_NOT_FOUND,
@@ -409,9 +409,6 @@ TEH_handler_recoup (struct MHD_Connection *connection,
     return MHD_NO; /* hard failure */
   if (GNUNET_NO == ret)
     return MHD_YES; /* failure */
-  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-              "Recoup coin with BKS=%s\n",
-              TALER_B2S (&coin_bks));
   {
     MHD_RESULT res;
 
diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.c 
b/src/exchange/taler-exchange-httpd_refreshes_reveal.c
index 1f0782aa..9c0a665b 100644
--- a/src/exchange/taler-exchange-httpd_refreshes_reveal.c
+++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.c
@@ -115,11 +115,20 @@ struct RevealContext
    */
   struct TALER_RefreshCoinData *rcds;
 
+  /**
+   * Refresh master secret.
+   */
+  struct TALER_RefreshMasterSecretP rms;
+
   /**
    * Size of the @e dks, @e rcds and @e ev_sigs arrays (if non-NULL).
    */
   unsigned int num_fresh_coins;
 
+  /**
+   * True if @e rms was provided.
+   */
+  bool have_rms;
 };
 
 
@@ -296,6 +305,9 @@ check_commitment (struct RevealContext *rctx,
     }
     TALER_refresh_get_commitment (&rc_expected,
                                   TALER_CNC_KAPPA,
+                                  rctx->have_rms
+                                  ? &rctx->rms
+                                  : NULL,
                                   rctx->num_fresh_coins,
                                   rcs,
                                   &rctx->melt.session.coin.coin_pub,
@@ -344,7 +356,7 @@ check_commitment (struct RevealContext *rctx,
 
       if ( (0 >
             TALER_amount_add (&total,
-                              &rctx->dks[i]->meta.fee_withdraw,
+                              &rctx->dks[i]->meta.fees.withdraw,
                               &rctx->dks[i]->meta.value)) ||
            (0 >
             TALER_amount_add (&refresh_cost,
@@ -439,7 +451,15 @@ resolve_refreshes_reveal_denominations (struct 
MHD_Connection *connection,
                                              &ret);
     if (NULL == dks[i])
       return ret;
-
+    if ( (TALER_DENOMINATION_CS == dks[i]->denom_pub.cipher) &&
+         (! rctx->have_rms) )
+    {
+      return TALER_MHD_reply_with_error (
+        connection,
+        MHD_HTTP_BAD_REQUEST,
+        TALER_EC_GENERIC_PARAMETER_MISSING,
+        "rms");
+    }
     if (GNUNET_TIME_absolute_is_past (dks[i]->meta.expire_withdraw.abs_time))
     {
       /* This denomination is past the expiration time for withdraws */
@@ -812,6 +832,9 @@ TEH_handler_reveal (struct TEH_RequestContext *rc,
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_json ("old_age_commitment",
                              &old_age_commitment)),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("rms",
+                                   &rctx.rms)),
     GNUNET_JSON_spec_end ()
   };
 
@@ -852,6 +875,8 @@ TEH_handler_reveal (struct TEH_RequestContext *rc,
       return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
     }
   }
+  rctx.have_rms = (NULL != json_object_get (root,
+                                            "rms"));
 
   /* Check we got enough transfer private keys */
   /* Note we do +1 as 1 row (cut-and-choose!) is missing! */
diff --git a/src/exchange/taler-exchange-httpd_refund.c 
b/src/exchange/taler-exchange-httpd_refund.c
index 9cc019a1..628fe699 100644
--- a/src/exchange/taler-exchange-httpd_refund.c
+++ b/src/exchange/taler-exchange-httpd_refund.c
@@ -264,8 +264,8 @@ verify_and_execute_refund (struct MHD_Connection 
*connection,
       GNUNET_break (0);
       return mret;
     }
-    refund->details.refund_fee = dk->meta.fee_refund;
-    rctx.deposit_fee = dk->meta.fee_deposit;
+    refund->details.refund_fee = dk->meta.fees.refund;
+    rctx.deposit_fee = dk->meta.fees.deposit;
   }
 
   /* Finally run the actual transaction logic */
diff --git a/src/exchange/taler-exchange-httpd_withdraw.c 
b/src/exchange/taler-exchange-httpd_withdraw.c
index a3ac1de3..cc6e92ed 100644
--- a/src/exchange/taler-exchange-httpd_withdraw.c
+++ b/src/exchange/taler-exchange-httpd_withdraw.c
@@ -91,11 +91,6 @@ reply_withdraw_insufficient_funds (
 struct WithdrawContext
 {
 
-  /**
-   * Hash that uniquely identifies the withdraw request.
-   */
-  struct TALER_WithdrawIdentificationHash wih;
-
   /**
    * Hash of the (blinded) message to be signed by the Exchange.
    */
@@ -157,10 +152,17 @@ withdraw_transaction (void *cls,
   bool balance_ok = false;
   struct GNUNET_TIME_Timestamp now;
   uint64_t ruuid;
+  const struct TALER_CsNonce *nonce;
+  const struct TALER_BlindedPlanchet *bp;
 
   now = GNUNET_TIME_timestamp_get ();
+  bp = &wc->blinded_planchet;
+  nonce =
+    (TALER_DENOMINATION_CS == bp->cipher)
+    ? &bp->details.cs_blinded_planchet.nonce
+    : NULL;
   qs = TEH_plugin->do_withdraw (TEH_plugin->cls,
-                                &wc->wih,
+                                nonce,
                                 &wc->collectable,
                                 now,
                                 &found,
@@ -300,7 +302,7 @@ check_request_idempotent (struct TEH_RequestContext *rc,
   enum GNUNET_DB_QueryStatus qs;
 
   qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls,
-                                      &wc->wih,
+                                      &wc->h_coin_envelope,
                                       &wc->collectable);
   if (0 > qs)
   {
@@ -465,7 +467,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
   if (0 >
       TALER_amount_add (&wc.collectable.amount_with_fee,
                         &dk->meta.value,
-                        &dk->meta.fee_withdraw))
+                        &dk->meta.fees.withdraw))
   {
     GNUNET_JSON_parse_free (spec);
     return TALER_MHD_reply_with_error (rc->connection,
@@ -502,19 +504,6 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
                                        NULL);
   }
 
-  if (GNUNET_OK !=
-      TALER_withdraw_request_hash (&wc.blinded_planchet,
-                                   &wc.collectable.denom_pub_hash,
-                                   &wc.wih))
-  {
-    GNUNET_break (0);
-    GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (rc->connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
-                                       NULL);
-  }
-
   /* Sign before transaction! */
   ec = TEH_keys_denomination_sign (
     &wc.collectable.denom_pub_hash,
diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql
index df07e025..fdc47b7c 100644
--- a/src/exchangedb/exchange-0001.sql
+++ b/src/exchangedb/exchange-0001.sql
@@ -1,6 +1,6 @@
 --
 -- This file is part of TALER
--- Copyright (C) 2014--2021 Taler Systems SA
+-- Copyright (C) 2014--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
@@ -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_mask INT4 NOT NULL DEFAULT (0)
+  ,age_restrictions 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
@@ -196,7 +196,6 @@ CREATE INDEX IF NOT EXISTS 
reserves_close_by_reserve_pub_index
 
 CREATE TABLE IF NOT EXISTS reserves_out
   (reserve_out_serial_id BIGSERIAL -- UNIQUE
-  ,wih BYTEA PRIMARY KEY CHECK (LENGTH(wih)=64)
   ,h_blind_ev BYTEA CHECK (LENGTH(h_blind_ev)=64) -- UNIQUE
   ,denominations_serial INT8 NOT NULL REFERENCES denominations 
(denominations_serial)
   ,denom_sig BYTEA NOT NULL
@@ -206,11 +205,9 @@ CREATE TABLE IF NOT EXISTS reserves_out
   ,amount_with_fee_val INT8 NOT NULL
   ,amount_with_fee_frac INT4 NOT NULL
   )
-  PARTITION BY HASH (wih);
+  PARTITION BY HASH (h_blind_ev);
 COMMENT ON TABLE reserves_out
   IS 'Withdraw operations performed on reserves.';
-COMMENT ON COLUMN reserves_out.wih
-  IS 'Hash that uniquely identifies the withdraw request. Used to detect 
request replays (crucial for CS) and to check the withdraw existed during 
recoup.';
 COMMENT ON COLUMN reserves_out.h_blind_ev
   IS 'Hash of the blinded coin, used as primary key here so that broken 
clients that use a non-random coin or blinding factor fail to withdraw 
(otherwise they would fail on deposit when the coin is not unique there).';
 COMMENT ON COLUMN reserves_out.denominations_serial
@@ -643,7 +640,7 @@ COMMENT ON TABLE recoup
 COMMENT ON COLUMN recoup.known_coin_id
   IS 'Coin that is being debited in the recoup. Do not CASCADE ON DROP on the 
coin_pub, as we may keep the coin alive!';
 COMMENT ON COLUMN recoup.reserve_out_serial_id
-  IS 'Identifies the wih of the recouped coin and provides the link to the 
credited reserve.';
+  IS 'Identifies the h_blind_ev of the recouped coin and provides the link to 
the credited reserve.';
 COMMENT ON COLUMN recoup.coin_sig
   IS 'Signature by the coin affirming the recoup, of type 
TALER_SIGNATURE_WALLET_COIN_RECOUP';
 COMMENT ON COLUMN recoup.coin_blind
@@ -750,6 +747,23 @@ COMMENT ON COLUMN wire_accounts.last_change
 --            and is of no concern to the auditor
 
 
+CREATE TABLE IF NOT EXISTS cs_nonce_locks
+  (cs_nonce_lock_serial_id BIGSERIAL -- UNIQUE
+  ,nonce BYTEA PRIMARY KEY CHECK (LENGTH(nonce)=32)
+  ,op_hash BYTEA NOT NULL CHECK (LENGTH(op_hash)=64)
+  ,max_denomination_serial INT8 NOT NULL
+  )
+  PARTITION BY HASH (nonce);
+COMMENT ON TABLE cs_nonce_locks
+  IS 'ensures a Clause Schnorr client nonce is locked for use with an 
operation identified by a hash';
+COMMENT ON COLUMN cs_nonce_locks.nonce
+  IS 'actual nonce submitted by the client';
+COMMENT ON COLUMN cs_nonce_locks.op_hash
+  IS 'hash (RC for refresh, blind coin hash for withdraw) the nonce may be 
used with';
+COMMENT ON COLUMN cs_nonce_locks.max_denomination_serial
+  IS 'Maximum number of a CS denomination serial the nonce could be used with, 
for GC';
+
+
 CREATE TABLE IF NOT EXISTS work_shards
   (shard_serial_id BIGSERIAL UNIQUE
   ,last_attempt INT8 NOT NULL
@@ -818,7 +832,7 @@ CREATE INDEX IF NOT EXISTS 
revolving_work_shards_by_job_name_active_last_attempt
 
 
 CREATE OR REPLACE FUNCTION exchange_do_withdraw(
-  IN in_wih BYTEA,
+  IN cs_nonce BYTEA,
   IN amount_val INT8,
   IN amount_frac INT4,
   IN h_denom_pub BYTEA,
@@ -832,8 +846,7 @@ CREATE OR REPLACE FUNCTION exchange_do_withdraw(
   OUT balance_ok BOOLEAN,
   OUT kycok BOOLEAN,
   OUT account_uuid INT8,
-  OUT ruuid INT8,
-  OUT out_denom_sig BYTEA)
+  OUT ruuid INT8)
 LANGUAGE plpgsql
 AS $$
 DECLARE
@@ -851,7 +864,8 @@ BEGIN
 --         reserves_in by reserve_pub (SELECT)
 --         wire_targets by wire_target_serial_id
 
-SELECT denominations_serial INTO denom_serial
+SELECT denominations_serial
+  INTO denom_serial
   FROM denominations
  WHERE denom_pub_hash=h_denom_pub;
 
@@ -867,6 +881,7 @@ THEN
   RETURN;
 END IF;
 
+
 SELECT
    current_balance_val
   ,current_balance_frac
@@ -887,7 +902,7 @@ THEN
   balance_ok=FALSE;
   kycok=FALSE;
   account_uuid=0;
-  ruuid=0;
+  ruuid=2;
   RETURN;
 END IF;
 
@@ -895,7 +910,6 @@ END IF;
 -- the query successful due to idempotency.
 INSERT INTO reserves_out
   (h_blind_ev
-  ,wih
   ,denominations_serial
   ,denom_sig
   ,reserve_uuid
@@ -905,7 +919,6 @@ INSERT INTO reserves_out
   ,amount_with_fee_frac)
 VALUES
   (h_coin_envelope
-  ,in_wih
   ,denom_serial
   ,denom_sig
   ,ruuid
@@ -918,25 +931,6 @@ ON CONFLICT DO NOTHING;
 IF NOT FOUND
 THEN
   -- idempotent query, all constraints must be satisfied
-
-  SELECT
-     denom_sig
-  INTO
-     out_denom_sig
-  FROM reserves_in
-    WHERE wih=in_wih
-    LIMIT 1; -- limit 1 should not be required (without p2p transfers)
-
-  IF NOT FOUND
-  THEN
-    reserve_found=FALSE;
-    balance_ok=FALSE;
-    kycok=FALSE;
-    account_uuid=0;
-    ruuid=0;
-    ASSERT false, 'internal logic error';
-  END IF;
-
   reserve_found=TRUE;
   balance_ok=TRUE;
   kycok=TRUE;
@@ -983,6 +977,44 @@ WHERE
 reserve_found=TRUE;
 balance_ok=TRUE;
 
+
+
+-- Special actions needed for a CS withdraw?
+IF NOT NULL cs_nonce
+THEN
+  -- Cache CS signature to prevent replays in the future
+  -- (and check if cached signature exists at the same time).
+  INSERT INTO cs_nonce_locks
+    (nonce
+    ,max_denomination_serial
+    ,op_hash)
+  VALUES
+    (cs_nonce
+    ,denom_serial
+    ,h_coin_envelope)
+  ON CONFLICT DO NOTHING;
+
+  IF NOT FOUND
+  THEN
+    -- See if the existing entry is identical.
+    SELECT 1
+      FROM cs_nonce_locks
+     WHERE nonce=cs_nonce
+       AND op_hash=h_coin_envelope;
+    IF NOT FOUND
+    THEN
+      reserve_found=FALSE;
+      balance_ok=FALSE;
+      kycok=FALSE;
+      account_uuid=0;
+      ruuid=1; -- FIXME: return error message more nicely!
+      ASSERT false, 'nonce reuse attempted by client';
+    END IF;
+  END IF;
+END IF;
+
+
+
 -- Obtain KYC status based on the last wire transfer into
 -- this reserve. FIXME: likely not adequate for reserves that got P2P 
transfers!
 SELECT
@@ -996,9 +1028,6 @@ SELECT
  WHERE reserve_pub=rpub
  LIMIT 1; -- limit 1 should not be required (without p2p transfers)
 
--- Return denomination signature as result that
--- was given as the argument.
-out_denom_sig=denom_sig;
 
 END $$;
 
@@ -1223,6 +1252,7 @@ END $$;
 
 
 CREATE OR REPLACE FUNCTION exchange_do_melt(
+  IN in_cs_rms BYTEA,
   IN in_amount_with_fee_val INT8,
   IN in_amount_with_fee_frac INT4,
   IN in_rc BYTEA,
@@ -1236,6 +1266,8 @@ CREATE OR REPLACE FUNCTION exchange_do_melt(
   OUT out_noreveal_index INT4)
 LANGUAGE plpgsql
 AS $$
+DECLARE
+  denom_max INT8;
 BEGIN
 -- Shards: INSERT refresh_commitments (by rc)
 -- (rare:) SELECT refresh_commitments (by old_coin_pub) -- crosses shards!
@@ -1333,6 +1365,56 @@ THEN
   RETURN;
 END IF;
 
+
+
+-- Special actions needed for a CS melt?
+IF NOT NULL in_cs_rms
+THEN
+  -- Get maximum denominations serial value in
+  -- existence, this will determine how long the
+  -- nonce will be locked.
+  SELECT
+      denominations_serial
+    INTO
+      denom_max
+    FROM denominations
+      ORDER BY denominations_serial DESC
+      LIMIT 1;
+
+  -- Cache CS signature to prevent replays in the future
+  -- (and check if cached signature exists at the same time).
+  INSERT INTO cs_nonce_locks
+    (nonce
+    ,max_denomination_serial
+    ,op_hash)
+  VALUES
+    (cs_rms
+    ,denom_serial
+    ,in_rc)
+  ON CONFLICT DO NOTHING;
+
+  IF NOT FOUND
+  THEN
+    -- Record exists, make sure it is the same
+    SELECT 1
+      FROM cs_nonce_locks
+     WHERE nonce=cs_rms
+       AND op_hash=in_rc;
+
+    IF NOT FOUND
+    THEN
+       -- Nonce reuse detected
+       out_balance_ok=FALSE;
+       out_zombie_bad=FALSE;
+       out_noreveal_index=42; -- FIXME: return error message more nicely!
+       ASSERT false, 'nonce reuse attempted by client';
+    END IF;
+  END IF;
+END IF;
+
+
+
+
 -- Everything fine, return success!
 out_balance_ok=TRUE;
 out_noreveal_index=in_noreveal_index;
@@ -1806,6 +1888,8 @@ DECLARE
   deposit_min INT8; -- minimum deposit still alive
 DECLARE
   reserve_out_min INT8; -- minimum reserve_out still alive
+DECLARE
+  denom_min INT8; -- minimum denomination still alive
 BEGIN
 
 DELETE FROM prewire
@@ -1901,6 +1985,16 @@ DELETE FROM refunds
 DELETE FROM aggregation_tracking
   WHERE deposit_serial_id < deposit_min;
 
+SELECT
+     denominations_serial
+  INTO
+     denom_min
+  FROM denominations
+  ORDER BY denominations_serial ASC
+  LIMIT 1;
+
+DELETE FROM cs_nonce_locks
+  WHERE max_denomination_serial <= denom_min;
 
 END $$;
 
diff --git a/src/exchangedb/irbt_callbacks.c b/src/exchangedb/irbt_callbacks.c
index cb0685ba..8cc4e237 100644
--- a/src/exchangedb/irbt_callbacks.c
+++ b/src/exchangedb/irbt_callbacks.c
@@ -53,13 +53,13 @@ irbt_cb_table_denominations (struct PostgresClosure *pg,
       &td->details.denominations.expire_legal),
     TALER_PQ_query_param_amount (&td->details.denominations.coin),
     TALER_PQ_query_param_amount (
-      &td->details.denominations.fee_withdraw),
+      &td->details.denominations.fees.withdraw),
     TALER_PQ_query_param_amount (
-      &td->details.denominations.fee_deposit),
+      &td->details.denominations.fees.deposit),
     TALER_PQ_query_param_amount (
-      &td->details.denominations.fee_refresh),
+      &td->details.denominations.fees.refresh),
     TALER_PQ_query_param_amount (
-      &td->details.denominations.fee_refund),
+      &td->details.denominations.fees.refund),
     GNUNET_PQ_query_param_end
   };
 
diff --git a/src/exchangedb/lrbt_callbacks.c b/src/exchangedb/lrbt_callbacks.c
index dd785213..a14c212d 100644
--- a/src/exchangedb/lrbt_callbacks.c
+++ b/src/exchangedb/lrbt_callbacks.c
@@ -80,16 +80,16 @@ lrbt_cb_table_denominations (void *cls,
         &td.details.denominations.coin),
       TALER_PQ_RESULT_SPEC_AMOUNT (
         "fee_withdraw",
-        &td.details.denominations.fee_withdraw),
+        &td.details.denominations.fees.withdraw),
       TALER_PQ_RESULT_SPEC_AMOUNT (
         "fee_deposit",
-        &td.details.denominations.fee_deposit),
+        &td.details.denominations.fees.deposit),
       TALER_PQ_RESULT_SPEC_AMOUNT (
         "fee_refresh",
-        &td.details.denominations.fee_refresh),
+        &td.details.denominations.fees.refresh),
       TALER_PQ_RESULT_SPEC_AMOUNT (
         "fee_refund",
-        &td.details.denominations.fee_refund),
+        &td.details.denominations.fees.refund),
       GNUNET_PQ_result_spec_end
     };
 
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c 
b/src/exchangedb/plugin_exchangedb_postgres.c
index 878c36f9..5d8db5ce 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2014--2021 Taler Systems SA
+   Copyright (C) 2014--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
@@ -579,7 +579,6 @@ prepare_statements (struct PostgresClosure *pg)
       ",kycok AS kyc_ok"
       ",account_uuid AS payment_target_uuid"
       ",ruuid"
-      ",out_denom_sig"
       " FROM exchange_do_withdraw"
       " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);",
       10),
@@ -612,8 +611,8 @@ 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);",
-      8),
+      " ($1,$2,$3,$4,$5,$6,$7,$8,$9);",
+      9),
     /* Used in #postgres_do_refund() to refund a deposit. */
     GNUNET_PQ_make_prepare (
       "call_refund",
@@ -667,7 +666,7 @@ prepare_statements (struct PostgresClosure *pg)
       "      USING (reserve_uuid)"
       "    JOIN denominations denom"
       "      USING (denominations_serial)"
-      " WHERE wih=$1;",
+      " WHERE h_blind_ev=$1;",
       1),
     /* Used during #postgres_get_reserve_history() to
        obtain all of the /reserve/withdraw operations that
@@ -1672,16 +1671,16 @@ prepare_statements (struct PostgresClosure *pg)
       "      ON (denoms.denominations_serial = coins.denominations_serial)"
       " WHERE coins.coin_pub=$1;",
       1),
-    /* Used in #postgres_get_reserve_by_wih() */
+    /* Used in #postgres_get_reserve_by_h_blind() */
     GNUNET_PQ_make_prepare (
-      "reserve_by_wih",
+      "reserve_by_h_blind",
       "SELECT"
       " reserves.reserve_pub"
       ",reserve_out_serial_id"
       " FROM reserves_out"
       " JOIN reserves"
       "   USING (reserve_uuid)"
-      " WHERE wih=$1"
+      " WHERE h_blind_ev=$1"
       " LIMIT 1;",
       1),
     /* Used in #postgres_get_old_coin_by_h_blind() */
@@ -3100,13 +3099,13 @@ postgres_insert_denomination_info (
     GNUNET_PQ_query_param_timestamp_nbo (&issue->properties.expire_deposit),
     GNUNET_PQ_query_param_timestamp_nbo (&issue->properties.expire_legal),
     TALER_PQ_query_param_amount_nbo (&issue->properties.value),
-    TALER_PQ_query_param_amount_nbo (&issue->properties.fee_withdraw),
-    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),
+    TALER_PQ_query_param_amount_nbo (&issue->properties.fees.withdraw),
+    TALER_PQ_query_param_amount_nbo (&issue->properties.fees.deposit),
+    TALER_PQ_query_param_amount_nbo (&issue->properties.fees.refresh),
+    TALER_PQ_query_param_amount_nbo (&issue->properties.fees.refund),
     GNUNET_PQ_query_param_end
   };
+  struct TALER_DenomFeeSet fees;
 
   GNUNET_assert (denom_pub->age_mask.mask == issue->age_mask.mask);
 
@@ -3122,20 +3121,13 @@ postgres_insert_denomination_info (
   GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
                    GNUNET_TIME_timestamp_ntoh (
                      issue->properties.expire_legal).abs_time));
-  /* check fees match coin currency */
-  GNUNET_assert (GNUNET_YES ==
-                 TALER_amount_cmp_currency_nbo (&issue->properties.value,
-                                                
&issue->properties.fee_withdraw));
+  /* check fees match denomination currency */
+  TALER_denom_fee_set_ntoh (&fees,
+                            &issue->properties.fees);
   GNUNET_assert (GNUNET_YES ==
-                 TALER_amount_cmp_currency_nbo (&issue->properties.value,
-                                                
&issue->properties.fee_deposit));
-  GNUNET_assert (GNUNET_YES ==
-                 TALER_amount_cmp_currency_nbo (&issue->properties.value,
-                                                
&issue->properties.fee_refresh));
-  GNUNET_assert (GNUNET_YES ==
-                 TALER_amount_cmp_currency_nbo (&issue->properties.value,
-                                                
&issue->properties.fee_refund));
-
+                 TALER_denom_fee_check_currency (
+                   issue->properties.value.currency,
+                   &fees));
   return GNUNET_PQ_eval_prepared_non_select (pg->conn,
                                              "denomination_insert",
                                              params);
@@ -3176,13 +3168,13 @@ postgres_get_denomination_info (
     TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("coin",
                                      &issue->properties.value),
     TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_withdraw",
-                                     &issue->properties.fee_withdraw),
+                                     &issue->properties.fees.withdraw),
     TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_deposit",
-                                     &issue->properties.fee_deposit),
+                                     &issue->properties.fees.deposit),
     TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refresh",
-                                     &issue->properties.fee_refresh),
+                                     &issue->properties.fees.refresh),
     TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refund",
-                                     &issue->properties.fee_refund),
+                                     &issue->properties.fees.refund),
     GNUNET_PQ_result_spec_uint32 ("age_mask",
                                   &issue->age_mask.mask),
     GNUNET_PQ_result_spec_end
@@ -3262,13 +3254,13 @@ domination_cb_helper (void *cls,
       TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("coin",
                                        &issue.properties.value),
       TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_withdraw",
-                                       &issue.properties.fee_withdraw),
+                                       &issue.properties.fees.withdraw),
       TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_deposit",
-                                       &issue.properties.fee_deposit),
+                                       &issue.properties.fees.deposit),
       TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refresh",
-                                       &issue.properties.fee_refresh),
+                                       &issue.properties.fees.refresh),
       TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refund",
-                                       &issue.properties.fee_refund),
+                                       &issue.properties.fees.refund),
       TALER_PQ_result_spec_denom_pub ("denom_pub",
                                       &denom_pub),
       GNUNET_PQ_result_spec_uint32 ("age_mask",
@@ -3401,13 +3393,13 @@ dominations_cb_helper (void *cls,
       TALER_PQ_RESULT_SPEC_AMOUNT ("coin",
                                    &meta.value),
       TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
-                                   &meta.fee_withdraw),
+                                   &meta.fees.withdraw),
       TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
-                                   &meta.fee_deposit),
+                                   &meta.fees.deposit),
       TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
-                                   &meta.fee_refresh),
+                                   &meta.fees.refresh),
       TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
-                                   &meta.fee_refund),
+                                   &meta.fees.refund),
       TALER_PQ_result_spec_denom_pub ("denom_pub",
                                       &denom_pub),
       GNUNET_PQ_result_spec_uint32 ("age_mask",
@@ -4328,7 +4320,7 @@ postgres_reserves_in_insert (void *cls,
  * key of the hash of the blinded message.
  *
  * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param wih hash that uniquely identifies the withdraw operation
+ * @param bch hash that uniquely identifies the withdraw operation
  * @param collectable corresponding collectable coin (blind signature)
  *                    if a coin is found
  * @return statement execution status
@@ -4336,12 +4328,12 @@ postgres_reserves_in_insert (void *cls,
 static enum GNUNET_DB_QueryStatus
 postgres_get_withdraw_info (
   void *cls,
-  const struct TALER_WithdrawIdentificationHash *wih,
+  const struct TALER_BlindedCoinHash *bch,
   struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (wih),
+    GNUNET_PQ_query_param_auto_from_type (bch),
     GNUNET_PQ_query_param_end
   };
   struct GNUNET_PQ_ResultSpec rs[] = {
@@ -4374,7 +4366,7 @@ postgres_get_withdraw_info (
  * and possibly persisting the withdrawal details.
  *
  * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param wih hash that uniquely identifies the withdraw operation
+ * @param nonce client-contributed input for CS denominations that must be 
checked for idempotency, or NULL for non-CS withdrawals
  * @param[in,out] collectable corresponding collectable coin (blind signature) 
if a coin is found; possibly updated if a (different) signature exists already
  * @param now current time (rounded)
  * @param[out] found set to true if the reserve was found
@@ -4386,8 +4378,8 @@ postgres_get_withdraw_info (
 static enum GNUNET_DB_QueryStatus
 postgres_do_withdraw (
   void *cls,
-  const struct TALER_WithdrawIdentificationHash *wih,
-  struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
+  const struct TALER_CsNonce *nonce,
+  const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
   struct GNUNET_TIME_Timestamp now,
   bool *found,
   bool *balance_ok,
@@ -4397,7 +4389,9 @@ postgres_do_withdraw (
   struct PostgresClosure *pg = cls;
   struct GNUNET_TIME_Timestamp gc;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (wih),
+    NULL == nonce
+    ? GNUNET_PQ_query_param_null ()
+    : GNUNET_PQ_query_param_auto_from_type (nonce),
     TALER_PQ_query_param_amount (&collectable->amount_with_fee),
     GNUNET_PQ_query_param_auto_from_type (&collectable->denom_pub_hash),
     GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_pub),
@@ -4408,9 +4402,6 @@ postgres_do_withdraw (
     GNUNET_PQ_query_param_timestamp (&gc),
     GNUNET_PQ_query_param_end
   };
-  enum GNUNET_DB_QueryStatus qs;
-  bool no_out_sig;
-  struct TALER_BlindedDenominationSignature out_sig;
   struct GNUNET_PQ_ResultSpec rs[] = {
     GNUNET_PQ_result_spec_bool ("reserve_found",
                                 found),
@@ -4422,33 +4413,17 @@ postgres_do_withdraw (
                                   &kyc->payment_target_uuid),
     GNUNET_PQ_result_spec_uint64 ("ruuid",
                                   ruuid),
-    GNUNET_PQ_result_spec_allow_null (
-      TALER_PQ_result_spec_blinded_denom_sig ("out_denom_sig",
-                                              &out_sig),
-      &no_out_sig),
     GNUNET_PQ_result_spec_end
   };
 
-#if 0
-  memset (&out_sig,
-          0,
-          sizeof (out_sig));
-#endif
   gc = GNUNET_TIME_absolute_to_timestamp (
     GNUNET_TIME_absolute_add (now.abs_time,
                               pg->legal_reserve_expiration_time));
   kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW;
-  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                 "call_withdraw",
-                                                 params,
-                                                 rs);
-  if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
-       (! no_out_sig) )
-  {
-    TALER_blinded_denom_sig_free (&collectable->sig);
-    collectable->sig = out_sig;
-  }
-  return qs;
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "call_withdraw",
+                                                   params,
+                                                   rs);
 }
 
 
@@ -4600,6 +4575,7 @@ postgres_do_deposit (
 static enum GNUNET_DB_QueryStatus
 postgres_do_melt (
   void *cls,
+  const struct TALER_RefreshMasterSecretP *rms,
   struct TALER_EXCHANGEDB_Refresh *refresh,
   uint64_t known_coin_id,
   bool *zombie_required,
@@ -4607,6 +4583,9 @@ postgres_do_melt (
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
+    NULL == rms
+    ? GNUNET_PQ_query_param_null ()
+    : GNUNET_PQ_query_param_auto_from_type (rms),
     TALER_PQ_query_param_amount (&refresh->amount_with_fee),
     GNUNET_PQ_query_param_auto_from_type (&refresh->rc),
     GNUNET_PQ_query_param_auto_from_type (&refresh->coin.coin_pub),
@@ -9428,21 +9407,21 @@ postgres_select_reserve_closed_above_serial_id (
  * from given the hash of the blinded coin.
  *
  * @param cls closure
- * @param wih hash that uniquely identifies the withdraw request
+ * @param bch hash that uniquely identifies the withdraw request
  * @param[out] reserve_pub set to information about the reserve (on success 
only)
  * @param[out] reserve_out_serial_id set to row of the @a h_blind_ev in 
reserves_out
  * @return transaction status code
  */
 static enum GNUNET_DB_QueryStatus
-postgres_get_reserve_by_wih (
+postgres_get_reserve_by_h_blind (
   void *cls,
-  const struct TALER_WithdrawIdentificationHash *wih,
+  const struct TALER_BlindedCoinHash *bch,
   struct TALER_ReservePublicKeyP *reserve_pub,
   uint64_t *reserve_out_serial_id)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (wih),
+    GNUNET_PQ_query_param_auto_from_type (bch),
     GNUNET_PQ_query_param_end
   };
   struct GNUNET_PQ_ResultSpec rs[] = {
@@ -9454,7 +9433,7 @@ postgres_get_reserve_by_wih (
   };
 
   return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
-                                                   "reserve_by_wih",
+                                                   "reserve_by_h_blind",
                                                    params,
                                                    rs);
 }
@@ -10241,13 +10220,13 @@ postgres_lookup_denomination_key (
     TALER_PQ_RESULT_SPEC_AMOUNT ("coin",
                                  &meta->value),
     TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
-                                 &meta->fee_withdraw),
+                                 &meta->fees.withdraw),
     TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
-                                 &meta->fee_deposit),
+                                 &meta->fees.deposit),
     TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
-                                 &meta->fee_refresh),
+                                 &meta->fees.refresh),
     TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
-                                 &meta->fee_refund),
+                                 &meta->fees.refund),
     GNUNET_PQ_result_spec_uint32 ("age_mask",
                                   &meta->age_mask.mask),
     GNUNET_PQ_result_spec_end
@@ -10289,27 +10268,18 @@ postgres_add_denomination_key (
     GNUNET_PQ_query_param_timestamp (&meta->expire_deposit),
     GNUNET_PQ_query_param_timestamp (&meta->expire_legal),
     TALER_PQ_query_param_amount (&meta->value),
-    TALER_PQ_query_param_amount (&meta->fee_withdraw),
-    TALER_PQ_query_param_amount (&meta->fee_deposit),
-    TALER_PQ_query_param_amount (&meta->fee_refresh),
-    TALER_PQ_query_param_amount (&meta->fee_refund),
+    TALER_PQ_query_param_amount (&meta->fees.withdraw),
+    TALER_PQ_query_param_amount (&meta->fees.deposit),
+    TALER_PQ_query_param_amount (&meta->fees.refresh),
+    TALER_PQ_query_param_amount (&meta->fees.refund),
     GNUNET_PQ_query_param_uint32 (&meta->age_mask.mask),
     GNUNET_PQ_query_param_end
   };
 
   /* Sanity check: ensure fees match coin currency */
   GNUNET_assert (GNUNET_YES ==
-                 TALER_amount_cmp_currency (&meta->value,
-                                            &meta->fee_withdraw));
-  GNUNET_assert (GNUNET_YES ==
-                 TALER_amount_cmp_currency (&meta->value,
-                                            &meta->fee_deposit));
-  GNUNET_assert (GNUNET_YES ==
-                 TALER_amount_cmp_currency (&meta->value,
-                                            &meta->fee_refresh));
-  GNUNET_assert (GNUNET_YES ==
-                 TALER_amount_cmp_currency (&meta->value,
-                                            &meta->fee_refund));
+                 TALER_denom_fee_check_currency (meta->value.currency,
+                                                 &meta->fees));
   return GNUNET_PQ_eval_prepared_non_select (pg->conn,
                                              "denomination_insert",
                                              iparams);
@@ -11722,8 +11692,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
     = &postgres_select_recoup_refresh_above_serial_id;
   plugin->select_reserve_closed_above_serial_id
     = &postgres_select_reserve_closed_above_serial_id;
-  plugin->get_reserve_by_wih
-    = &postgres_get_reserve_by_wih;
+  plugin->get_reserve_by_h_blind
+    = &postgres_get_reserve_by_h_blind;
   plugin->get_old_coin_by_h_blind
     = &postgres_get_old_coin_by_h_blind;
   plugin->insert_denomination_revocation
diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c
index f9e64fdc..80740714 100644
--- a/src/exchangedb/test_exchangedb.c
+++ b/src/exchangedb/test_exchangedb.c
@@ -263,20 +263,14 @@ destroy_denom_key_pair (struct DenomKeyPair *dkp)
  *
  * @param size the size of the denomination key
  * @param now time to use for key generation, legal expiration will be 3h 
later.
- * @param fee_withdraw withdraw fee to use
- * @param fee_deposit deposit fee to use
- * @param fee_refresh refresh fee to use
- * @param fee_refund refund fee to use
+ * @param fees fees to use
  * @return the denominaiton key pair; NULL upon error
  */
 static struct DenomKeyPair *
 create_denom_key_pair (unsigned int size,
                        struct GNUNET_TIME_Timestamp now,
                        const struct TALER_Amount *value,
-                       const struct TALER_Amount *fee_withdraw,
-                       const struct TALER_Amount *fee_deposit,
-                       const struct TALER_Amount *fee_refresh,
-                       const struct TALER_Amount *fee_refund)
+                       const struct TALER_DenomFeeSet *fees)
 {
   struct DenomKeyPair *dkp;
   struct TALER_EXCHANGEDB_DenominationKey dki;
@@ -315,11 +309,10 @@ create_denom_key_pair (unsigned int size,
             (now.abs_time,
             GNUNET_TIME_relative_multiply (
               GNUNET_TIME_UNIT_HOURS, 3))));
-  TALER_amount_hton (&dki.issue.properties.value, value);
-  TALER_amount_hton (&dki.issue.properties.fee_withdraw, fee_withdraw);
-  TALER_amount_hton (&dki.issue.properties.fee_deposit, fee_deposit);
-  TALER_amount_hton (&dki.issue.properties.fee_refresh, fee_refresh);
-  TALER_amount_hton (&dki.issue.properties.fee_refund, fee_refund);
+  TALER_amount_hton (&dki.issue.properties.value,
+                     value);
+  TALER_denom_fee_set_hton (&dki.issue.properties.fees,
+                            fees);
   TALER_denom_pub_hash (&dkp->pub,
                         &dki.issue.properties.denom_hash);
 
@@ -359,10 +352,7 @@ create_denom_key_pair (unsigned int size,
 
 
 static struct TALER_Amount value;
-static struct TALER_Amount fee_withdraw;
-static struct TALER_Amount fee_deposit;
-static struct TALER_Amount fee_refresh;
-static struct TALER_Amount fee_refund;
+static struct TALER_DenomFeeSet fees;
 static struct TALER_Amount fee_closing;
 static struct TALER_Amount amount_with_fee;
 
@@ -881,10 +871,7 @@ test_gc (void)
   dkp = create_denom_key_pair (RSA_KEY_SIZE,
                                past,
                                &value,
-                               &fee_withdraw,
-                               &fee_deposit,
-                               &fee_refresh,
-                               &fee_refund);
+                               &fees);
   GNUNET_assert (NULL != dkp);
   if (GNUNET_OK !=
       plugin->gc (plugin->cls))
@@ -1080,7 +1067,7 @@ test_wire_out (const struct TALER_EXCHANGEDB_Deposit 
*deposit)
   coin_pub_wt = deposit->coin.coin_pub;
 
   coin_value_wt = deposit->amount_with_fee;
-  coin_fee_wt = fee_deposit;
+  coin_fee_wt = fees.deposit;
   GNUNET_assert (0 <
                  TALER_amount_subtract (&transfer_value_wt,
                                         &coin_value_wt,
@@ -1349,7 +1336,6 @@ run (void *cls)
   struct GNUNET_TIME_Timestamp now;
   struct TALER_WireSaltP salt;
   struct TALER_CoinPubHash c_hash;
-  struct TALER_WithdrawIdentificationHash wih;
   uint64_t known_coin_id;
   uint64_t rrc_serial;
   struct TALER_EXCHANGEDB_Refresh refresh;
@@ -1410,17 +1396,17 @@ run (void *cls)
                                          &value));
   GNUNET_assert (GNUNET_OK ==
                  TALER_string_to_amount (CURRENCY ":0.000010",
-                                         &fee_withdraw));
+                                         &fees.withdraw));
   GNUNET_assert (GNUNET_OK ==
                  TALER_string_to_amount (CURRENCY ":0.000010",
-                                         &fee_deposit));
-  deposit.deposit_fee = fee_deposit;
+                                         &fees.deposit));
+  deposit.deposit_fee = fees.deposit;
   GNUNET_assert (GNUNET_OK ==
                  TALER_string_to_amount (CURRENCY ":0.000010",
-                                         &fee_refresh));
+                                         &fees.refresh));
   GNUNET_assert (GNUNET_OK ==
                  TALER_string_to_amount (CURRENCY ":0.000010",
-                                         &fee_refund));
+                                         &fees.refund));
   GNUNET_assert (GNUNET_OK ==
                  TALER_string_to_amount (CURRENCY ":1.000010",
                                          &amount_with_fee));
@@ -1464,10 +1450,7 @@ run (void *cls)
   dkp = create_denom_key_pair (RSA_KEY_SIZE,
                                now,
                                &value,
-                               &fee_withdraw,
-                               &fee_deposit,
-                               &fee_refresh,
-                               &fee_refund);
+                               &fees);
   GNUNET_assert (NULL != dkp);
   TALER_denom_pub_hash (&dkp->pub,
                         &cbc.denom_pub_hash);
@@ -1502,10 +1485,7 @@ run (void *cls)
                      TALER_coin_ev_hash (&pd.blinded_planchet,
                                          &cbc.denom_pub_hash,
                                          &cbc.h_coin_envelope));
-      GNUNET_assert (GNUNET_OK ==
-                     TALER_withdraw_request_hash (&pd.blinded_planchet,
-                                                  &cbc.denom_pub_hash,
-                                                  &wih));      GNUNET_assert (
+      GNUNET_assert (
         GNUNET_OK ==
         TALER_denom_sign_blinded (
           &cbc.sig,
@@ -1529,7 +1509,7 @@ run (void *cls)
 
     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
             plugin->do_withdraw (plugin->cls,
-                                 &wih,
+                                 NULL,
                                  &cbc,
                                  now,
                                  &found,
@@ -1551,16 +1531,16 @@ run (void *cls)
                          value.fraction,
                          value.currency));
   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          plugin->get_reserve_by_wih (plugin->cls,
-                                      &wih,
-                                      &reserve_pub3,
-                                      &reserve_out_serial_id));
+          plugin->get_reserve_by_h_blind (plugin->cls,
+                                          &cbc.h_coin_envelope,
+                                          &reserve_pub3,
+                                          &reserve_out_serial_id));
   FAILIF (0 != GNUNET_memcmp (&reserve_pub,
                               &reserve_pub3));
 
   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
           plugin->get_withdraw_info (plugin->cls,
-                                     &wih,
+                                     &cbc.h_coin_envelope,
                                      &cbc2));
   FAILIF (0 != GNUNET_memcmp (&cbc2.reserve_sig,
                               &cbc.reserve_sig));
@@ -1649,12 +1629,12 @@ run (void *cls)
     refund.details.h_contract_terms = deposit.h_contract_terms;
     refund.details.rtransaction_id = 1;
     refund.details.refund_amount = value;
-    refund.details.refund_fee = fee_refund;
+    refund.details.refund_fee = fees.refund;
     RND_BLK (&refund.details.merchant_sig);
     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
             plugin->do_refund (plugin->cls,
                                &refund,
-                               &fee_deposit,
+                               &fees.deposit,
                                known_coin_id,
                                &not_found,
                                &refund_ok,
@@ -1686,6 +1666,7 @@ run (void *cls)
     refresh.noreveal_index = MELT_NOREVEAL_INDEX;
     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
             plugin->do_melt (plugin->cls,
+                             NULL,
                              &refresh,
                              known_coin_id,
                              &zombie_required,
@@ -1709,7 +1690,7 @@ run (void *cls)
             TALER_amount_cmp (&refresh.amount_with_fee,
                               &ret_refresh_session.session.amount_with_fee));
     FAILIF (0 !=
-            TALER_amount_cmp (&fee_refresh,
+            TALER_amount_cmp (&fees.refresh,
                               &ret_refresh_session.melt_fee));
     FAILIF (0 !=
             GNUNET_memcmp (&refresh.rc,
@@ -1755,10 +1736,7 @@ run (void *cls)
       new_dkp[cnt] = create_denom_key_pair (RSA_KEY_SIZE,
                                             now,
                                             &value,
-                                            &fee_withdraw,
-                                            &fee_deposit,
-                                            &fee_refresh,
-                                            &fee_refund);
+                                            &fees);
       GNUNET_assert (NULL != new_dkp[cnt]);
       new_denom_pubs[cnt] = new_dkp[cnt]->pub;
       ccoin = &revealed_coins[cnt];
@@ -2174,7 +2152,7 @@ run (void *cls)
   memset (&deposit,
           0,
           sizeof (deposit));
-  deposit.deposit_fee = fee_deposit;
+  deposit.deposit_fee = fees.deposit;
   RND_BLK (&deposit.coin.coin_pub);
   TALER_denom_pub_hash (&dkp->pub,
                         &deposit.coin.denom_pub_hash);
@@ -2196,7 +2174,7 @@ run (void *cls)
     &deposit.wire_salt,
     &h_wire_wt);
   deposit.amount_with_fee = value;
-  deposit.deposit_fee = fee_deposit;
+  deposit.deposit_fee = fees.deposit;
 
   deposit.refund_deadline = deadline;
   deposit.wire_deadline = deadline;
diff --git a/src/include/taler_amount_lib.h b/src/include/taler_amount_lib.h
index c6d2f474..a529cfb8 100644
--- a/src/include/taler_amount_lib.h
+++ b/src/include/taler_amount_lib.h
@@ -184,6 +184,18 @@ enum GNUNET_GenericReturnValue
 TALER_amount_is_valid (const struct TALER_Amount *amount);
 
 
+/**
+ * Test if the given amount is in the given currency
+ *
+ * @param amount amount to check
+ * @param currency currency to check for
+ * @return #GNUNET_OK if @a amount is in @a currency
+ */
+enum GNUNET_GenericReturnValue
+TALER_amount_is_currency (const struct TALER_Amount *amount,
+                          const char *currency);
+
+
 /**
  * Convert amount from host to network representation.
  *
diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h
index 9bbf29de..2843d820 100644
--- a/src/include/taler_crypto_lib.h
+++ b/src/include/taler_crypto_lib.h
@@ -563,22 +563,6 @@ struct TALER_BlindedCoinHash
 };
 
 
-/**
- * Hash used to uniquely represent a withdraw process so as to perform
- * idempotency checks (and prevent clients from harmfully replaying withdraw
- * operations with problematic variations on the inputs).  In the CS case,
- * this is a hash over the DK and nonce, while in the RSA case, it is simply a
- * hash over the DK and the blinded coin.
- */
-struct TALER_WithdrawIdentificationHash
-{
-  /**
-   * Actual hash value.
-   */
-  struct GNUNET_HashCode hash;
-};
-
-
 /**
  * Hash used to represent the hash of the public
  * key of a coin (without blinding).
@@ -629,9 +613,99 @@ struct TALER_ExtensionConfigHash
 };
 
 
+/**
+ * Set of the fees applying to a denomination.
+ */
+struct TALER_DenomFeeSetNBOP
+{
+
+  /**
+   * The fee the exchange charges when a coin of this type is withdrawn.
+   * (can be zero).
+   */
+  struct TALER_AmountNBO withdraw;
+
+  /**
+   * The fee the exchange charges when a coin of this type is deposited.
+   * (can be zero).
+   */
+  struct TALER_AmountNBO deposit;
+
+  /**
+   * The fee the exchange charges when a coin of this type is refreshed.
+   * (can be zero).
+   */
+  struct TALER_AmountNBO refresh;
+
+  /**
+   * The fee the exchange charges when a coin of this type is refunded.
+   * (can be zero).  Note that refund fees are charged to the customer;
+   * if a refund is given, the deposit fee is also refunded.
+   */
+  struct TALER_AmountNBO refund;
+
+};
+
+
 GNUNET_NETWORK_STRUCT_END
 
 
+/**
+ * Set of the fees applying to a denomination.
+ */
+struct TALER_DenomFeeSet
+{
+
+  /**
+   * The fee the exchange charges when a coin of this type is withdrawn.
+   * (can be zero).
+   */
+  struct TALER_Amount withdraw;
+
+  /**
+   * The fee the exchange charges when a coin of this type is deposited.
+   * (can be zero).
+   */
+  struct TALER_Amount deposit;
+
+  /**
+   * The fee the exchange charges when a coin of this type is refreshed.
+   * (can be zero).
+   */
+  struct TALER_Amount refresh;
+
+  /**
+   * The fee the exchange charges when a coin of this type is refunded.
+   * (can be zero).  Note that refund fees are charged to the customer;
+   * if a refund is given, the deposit fee is also refunded.
+   */
+  struct TALER_Amount refund;
+
+};
+
+
+/**
+ * Convert fee set from host to network byte order.
+ *
+ * @param[out] nbo where to write the result
+ * @param fees fee set to convert
+ */
+void
+TALER_denom_fee_set_hton (struct TALER_DenomFeeSetNBOP *nbo,
+                          const struct TALER_DenomFeeSet *fees);
+
+
+/**
+ * Convert fee set from network to host network byte order.
+ *
+ * @param[out] fees where to write the result
+ * @param nbo fee set to convert
+ */
+void
+TALER_denom_fee_set_ntoh (struct TALER_DenomFeeSet *fees,
+                          const struct TALER_DenomFeeSetNBOP *nbo);
+
+
 /**
  * Hash @a rsa.
  *
@@ -1357,22 +1431,6 @@ TALER_coin_ev_hash (const struct TALER_BlindedPlanchet 
*blinded_planchet,
                     struct TALER_BlindedCoinHash *bch);
 
 
-/**
- * Compute the hash to uniquely identify a withdraw
- * request.
- *
- * @param blinded_planchet blinded planchet
- * @param denom_hash hash of the denomination publick key
- * @param[out] wih where to write the hash
- * @return #GNUNET_OK when successful, #GNUNET_SYSERR if an internal error 
occured
- */
-enum GNUNET_GenericReturnValue
-TALER_withdraw_request_hash (
-  const struct TALER_BlindedPlanchet *blinded_planchet,
-  const struct TALER_DenominationHash *denom_hash,
-  struct TALER_WithdrawIdentificationHash *wih);
-
-
 /**
  * Compute the hash of a coin.
  *
@@ -1548,16 +1606,19 @@ TALER_transfer_secret_to_planchet_secret (
 
 /**
  * Derive the @a coin_num transfer private key @a tpriv from a refresh from
- * the @a rms seed of the refresh operation.  The transfer private key
- * derivation is based on the @a ps with a KDF salted by the @a coin_num.
+ * the @a rms seed and the @a old_coin_pub of the refresh operation.  The
+ * transfer private key derivation is based on the @a ps with a KDF salted by
+ * the @a coin_num.
  *
  * @param rms seed to use for KDF to derive transfer keys
+ * @param old_coin_priv private key of the old coin
  * @param cnc_num cut and choose number to include in KDF
  * @param[out] tpriv value to initialize
  */
 void
 TALER_planchet_secret_to_transfer_priv (
   const struct TALER_RefreshMasterSecretP *rms,
+  const struct TALER_CoinSpendPrivateKeyP *old_coin_priv,
   uint32_t cnc_num,
   struct TALER_TransferPrivateKeyP *tpriv);
 
@@ -1675,8 +1736,8 @@ TALER_planchet_to_coin (
  * @param[in,out] hash_context hash context to use
  */
 void
-TALER_blinded_planchet_hash (const struct TALER_BlindedPlanchet *bp,
-                             struct GNUNET_HashContext *hash_context);
+TALER_blinded_planchet_hash_ (const struct TALER_BlindedPlanchet *bp,
+                              struct GNUNET_HashContext *hash_context);
 
 
 /**
@@ -1769,6 +1830,7 @@ struct TALER_RefreshCommitmentEntry
  *
  * @param[out] rc set to the value the wallet must commit to
  * @param kappa number of transfer public keys involved (must be 
#TALER_CNC_KAPPA)
+ * @param rms refresh master secret to include, can be NULL!
  * @param num_new_coins number of new coins to be created
  * @param rcs array of @a kappa commitments
  * @param coin_pub public key of the coin to be melted
@@ -1777,6 +1839,7 @@ struct TALER_RefreshCommitmentEntry
 void
 TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc,
                               uint32_t kappa,
+                              const struct TALER_RefreshMasterSecretP *rms,
                               uint32_t num_new_coins,
                               const struct TALER_RefreshCommitmentEntry *rcs,
                               const struct TALER_CoinSpendPublicKeyP *coin_pub,
@@ -2724,10 +2787,7 @@ TALER_exchange_offline_signkey_validity_verify (
  * @param stamp_expire_deposit how long does the exchange accept the deposit 
of coins with this key
  * @param stamp_expire_legal how long does the exchange preserve information 
for legal disputes with this key
  * @param coin_value what is the value of coins signed with this key
- * @param fee_withdraw what withdraw fee does the exchange charge for this 
denomination
- * @param fee_deposit what deposit fee does the exchange charge for this 
denomination
- * @param fee_refresh what refresh fee does the exchange charge for this 
denomination
- * @param fee_refund what refund fee does the exchange charge for this 
denomination
+ * @param fees fees for this denomination
  * @param master_priv private key to sign with
  * @param[out] master_sig where to write the signature
  */
@@ -2739,10 +2799,7 @@ TALER_exchange_offline_denom_validity_sign (
   struct GNUNET_TIME_Timestamp stamp_expire_deposit,
   struct GNUNET_TIME_Timestamp stamp_expire_legal,
   const struct TALER_Amount *coin_value,
-  const struct TALER_Amount *fee_withdraw,
-  const struct TALER_Amount *fee_deposit,
-  const struct TALER_Amount *fee_refresh,
-  const struct TALER_Amount *fee_refund,
+  const struct TALER_DenomFeeSet *fees,
   const struct TALER_MasterPrivateKeyP *master_priv,
   struct TALER_MasterSignatureP *master_sig);
 
@@ -2756,10 +2813,7 @@ TALER_exchange_offline_denom_validity_sign (
  * @param stamp_expire_deposit how long does the exchange accept the deposit 
of coins with this key
  * @param stamp_expire_legal how long does the exchange preserve information 
for legal disputes with this key
  * @param coin_value what is the value of coins signed with this key
- * @param fee_withdraw what withdraw fee does the exchange charge for this 
denomination
- * @param fee_deposit what deposit fee does the exchange charge for this 
denomination
- * @param fee_refresh what refresh fee does the exchange charge for this 
denomination
- * @param fee_refund what refund fee does the exchange charge for this 
denomination
+ * @param fees fees for this denomination
  * @param master_pub public key to verify against
  * @param master_sig the signature the signature
  * @return #GNUNET_OK if the signature is valid
@@ -2772,10 +2826,7 @@ TALER_exchange_offline_denom_validity_verify (
   struct GNUNET_TIME_Timestamp stamp_expire_deposit,
   struct GNUNET_TIME_Timestamp stamp_expire_legal,
   const struct TALER_Amount *coin_value,
-  const struct TALER_Amount *fee_withdraw,
-  const struct TALER_Amount *fee_deposit,
-  const struct TALER_Amount *fee_refresh,
-  const struct TALER_Amount *fee_refund,
+  const struct TALER_DenomFeeSet *fees,
   const struct TALER_MasterPublicKeyP *master_pub,
   const struct TALER_MasterSignatureP *master_sig);
 
@@ -2910,10 +2961,7 @@ TALER_exchange_secmod_cs_verify (
  * @param stamp_expire_deposit how long does the exchange accept the deposit 
of coins with this key
  * @param stamp_expire_legal how long does the exchange preserve information 
for legal disputes with this key
  * @param coin_value what is the value of coins signed with this key
- * @param fee_withdraw what withdraw fee does the exchange charge for this 
denomination
- * @param fee_deposit what deposit fee does the exchange charge for this 
denomination
- * @param fee_refresh what refresh fee does the exchange charge for this 
denomination
- * @param fee_refund what refund fee does the exchange charge for this 
denomination
+ * @param fees fees the exchange charges for this denomination
  * @param auditor_priv private key to sign with
  * @param[out] auditor_sig where to write the signature
  */
@@ -2927,10 +2975,7 @@ TALER_auditor_denom_validity_sign (
   struct GNUNET_TIME_Timestamp stamp_expire_deposit,
   struct GNUNET_TIME_Timestamp stamp_expire_legal,
   const struct TALER_Amount *coin_value,
-  const struct TALER_Amount *fee_withdraw,
-  const struct TALER_Amount *fee_deposit,
-  const struct TALER_Amount *fee_refresh,
-  const struct TALER_Amount *fee_refund,
+  const struct TALER_DenomFeeSet *fees,
   const struct TALER_AuditorPrivateKeyP *auditor_priv,
   struct TALER_AuditorSignatureP *auditor_sig);
 
@@ -2946,10 +2991,7 @@ TALER_auditor_denom_validity_sign (
  * @param stamp_expire_deposit how long does the exchange accept the deposit 
of coins with this key
  * @param stamp_expire_legal how long does the exchange preserve information 
for legal disputes with this key
  * @param coin_value what is the value of coins signed with this key
- * @param fee_withdraw what withdraw fee does the exchange charge for this 
denomination
- * @param fee_deposit what deposit fee does the exchange charge for this 
denomination
- * @param fee_refresh what refresh fee does the exchange charge for this 
denomination
- * @param fee_refund what refund fee does the exchange charge for this 
denomination
+ * @param fees fees the exchange charges for this denomination
  * @param auditor_pub public key to verify against
  * @param auditor_sig the signature the signature
  * @return #GNUNET_OK if the signature is valid
@@ -2964,10 +3006,7 @@ TALER_auditor_denom_validity_verify (
   struct GNUNET_TIME_Timestamp stamp_expire_deposit,
   struct GNUNET_TIME_Timestamp stamp_expire_legal,
   const struct TALER_Amount *coin_value,
-  const struct TALER_Amount *fee_withdraw,
-  const struct TALER_Amount *fee_deposit,
-  const struct TALER_Amount *fee_refresh,
-  const struct TALER_Amount *fee_refund,
+  const struct TALER_DenomFeeSet *fees,
   const struct TALER_AuditorPublicKeyP *auditor_pub,
   const struct TALER_AuditorSignatureP *auditor_sig);
 
diff --git a/src/include/taler_exchange_service.h 
b/src/include/taler_exchange_service.h
index fef09f72..df1d4da9 100644
--- a/src/include/taler_exchange_service.h
+++ b/src/include/taler_exchange_service.h
@@ -135,24 +135,9 @@ struct TALER_EXCHANGE_DenomPublicKey
   struct TALER_Amount value;
 
   /**
-   * The applicable fee for withdrawing a coin of this denomination
-   */
-  struct TALER_Amount fee_withdraw;
-
-  /**
-   * The applicable fee to spend a coin of this denomination
+   * The applicable fees for this denomination
    */
-  struct TALER_Amount fee_deposit;
-
-  /**
-   * The applicable fee to melt/refresh a coin of this denomination
-   */
-  struct TALER_Amount fee_refresh;
-
-  /**
-   * The applicable fee to refund a coin of this denomination
-   */
-  struct TALER_Amount fee_refund;
+  struct TALER_DenomFeeSet fees;
 
   /**
    * Set to true if this denomination key has been
@@ -1031,19 +1016,19 @@ void
 TALER_EXCHANGE_refund_cancel (struct TALER_EXCHANGE_RefundHandle *refund);
 
 
-/* ********************* POST /csr *********************** */
+/* ********************* POST /csr-melt *********************** */
 
 
 /**
- * @brief A /csr Handle
+ * @brief A /csr-melt Handle
  */
-struct TALER_EXCHANGE_CsRHandle;
+struct TALER_EXCHANGE_CsRMeltHandle;
 
 
 /**
  * Details about a response for a CS R request.
  */
-struct TALER_EXCHANGE_CsRResponse
+struct TALER_EXCHANGE_CsRMeltResponse
 {
   /**
    * HTTP response data.
@@ -1092,29 +1077,31 @@ struct TALER_EXCHANGE_CsRResponse
  * @param csrr response details
  */
 typedef void
-(*TALER_EXCHANGE_CsRCallback) (void *cls,
-                               const struct TALER_EXCHANGE_CsRResponse *csrr);
+(*TALER_EXCHANGE_CsRMeltCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_CsRMeltResponse *csrr);
 
 
 /**
- * Information we pass per coin to a /csr request.
+ * Information we pass per coin to a /csr-melt request.
  */
 struct TALER_EXCHANGE_NonceKey
 {
   /**
-   * Which denomination key is the /csr request for?
+   * Which denomination key is the /csr-melt request for?
    */
   const struct TALER_EXCHANGE_DenomPublicKey *pk;
 
   /**
-   * What is the client nonce for the request?
+   * What is number to derive the client nonce for the
+   * fresh coin?
    */
-  struct TALER_CsNonce nonce;
+  uint32_t cnc_num;
 };
 
 
 /**
- * Get a CS R using a /csr request.
+ * Get a set of CS R values using a /csr-melt request.
  *
  * @param exchange the exchange handle; the exchange must be ready to operate
  * @param nks_len length of the @a nks array
@@ -1125,23 +1112,117 @@ struct TALER_EXCHANGE_NonceKey
  *         if the inputs are invalid (i.e. denomination key not with this 
exchange).
  *         In this case, the callback is not called.
  */
-struct TALER_EXCHANGE_CsRHandle *
-TALER_EXCHANGE_csr (struct TALER_EXCHANGE_Handle *exchange,
-                    unsigned int nks_len,
-                    struct TALER_EXCHANGE_NonceKey *nks,
-                    TALER_EXCHANGE_CsRCallback res_cb,
-                    void *res_cb_cls);
+struct TALER_EXCHANGE_CsRMeltHandle *
+TALER_EXCHANGE_csr_melt (struct TALER_EXCHANGE_Handle *exchange,
+                         const struct TALER_RefreshMasterSecretP *rms,
+                         unsigned int nks_len,
+                         struct TALER_EXCHANGE_NonceKey *nks,
+                         TALER_EXCHANGE_CsRMeltCallback res_cb,
+                         void *res_cb_cls);
+
+
+/**
+ *
+ * Cancel a CS R melt request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param csrh the withdraw handle
+ */
+void
+TALER_EXCHANGE_csr_melt_cancel (struct TALER_EXCHANGE_CsRMeltHandle *csrh);
+
+
+/* ********************* POST /csr-withdraw *********************** */
+
+
+/**
+ * @brief A /csr-withdraw Handle
+ */
+struct TALER_EXCHANGE_CsRWithdrawHandle;
+
+
+/**
+ * Details about a response for a CS R request.
+ */
+struct TALER_EXCHANGE_CsRWithdrawResponse
+{
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details about the response.
+   */
+  union
+  {
+    /**
+     * Details if the status is #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * Values contributed by the exchange for the
+       * respective coin's withdraw operation.
+       */
+      struct TALER_ExchangeWithdrawValues alg_values;
+    } success;
+
+    /**
+     * Details if the status is #MHD_HTTP_GONE.
+     */
+    struct
+    {
+      /* TODO: returning full details is not implemented */
+    } gone;
+
+  } details;
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * CS R withdraw request to a exchange.
+ *
+ * @param cls closure
+ * @param csrr response details
+ */
+typedef void
+(*TALER_EXCHANGE_CsRWithdrawCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr);
+
+
+/**
+ * Get a CS R using a /csr-withdraw request.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param dk Which denomination key is the /csr request for
+ * @param nonce client nonce for the request
+ * @param res_cb the callback to call when the final result for this request 
is available
+ * @param res_cb_cls closure for the above callback
+ * @return handle for the operation on success, NULL on error, i.e.
+ *         if the inputs are invalid (i.e. denomination key not with this 
exchange).
+ *         In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_CsRWithdrawHandle *
+TALER_EXCHANGE_csr_withdraw (struct TALER_EXCHANGE_Handle *exchange,
+                             const struct TALER_EXCHANGE_DenomPublicKey *pk,
+                             const struct TALER_CsNonce *nonce,
+                             TALER_EXCHANGE_CsRWithdrawCallback res_cb,
+                             void *res_cb_cls);
 
 
 /**
  *
- * Cancel a CS R request.  This function cannot be used
+ * Cancel a CS R withdraw request.  This function cannot be used
  * on a request handle if a response is already served for it.
  *
  * @param csrh the withdraw handle
  */
 void
-TALER_EXCHANGE_csr_cancel (struct TALER_EXCHANGE_CsRHandle *csrh);
+TALER_EXCHANGE_csr_withdraw_cancel (
+  struct TALER_EXCHANGE_CsRWithdrawHandle *csrh);
 
 
 /* ********************* GET /reserves/$RESERVE_PUB *********************** */
diff --git a/src/include/taler_exchangedb_plugin.h 
b/src/include/taler_exchangedb_plugin.h
index f0a6f8bd..41231c98 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2021 Taler Systems SA
+  Copyright (C) 2014-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
@@ -208,10 +208,7 @@ struct TALER_EXCHANGEDB_TableData
       struct GNUNET_TIME_Timestamp expire_deposit;
       struct GNUNET_TIME_Timestamp expire_legal;
       struct TALER_Amount coin;
-      struct TALER_Amount fee_withdraw;
-      struct TALER_Amount fee_deposit;
-      struct TALER_Amount fee_refresh;
-      struct TALER_Amount fee_refund;
+      struct TALER_DenomFeeSet fees;
     } denominations;
 
     struct
@@ -612,29 +609,10 @@ struct TALER_EXCHANGEDB_DenominationKeyMetaData
   struct TALER_Amount value;
 
   /**
-   * The fee the exchange charges when a coin of this type is withdrawn.
-   * (can be zero).
+   * The fees the exchange charges for operations with
+   * coins of this denomination.
    */
-  struct TALER_Amount fee_withdraw;
-
-  /**
-   * The fee the exchange charges when a coin of this type is deposited.
-   * (can be zero).
-   */
-  struct TALER_Amount fee_deposit;
-
-  /**
-   * The fee the exchange charges when a coin of this type is refreshed.
-   * (can be zero).
-   */
-  struct TALER_Amount fee_refresh;
-
-  /**
-   * The fee the exchange charges when a coin of this type is refunded.
-   * (can be zero).  Note that refund fees are charged to the customer;
-   * if a refund is given, the deposit fee is also refunded.
-   */
-  struct TALER_Amount fee_refund;
+  struct TALER_DenomFeeSet fees;
 
   /**
    * Age restriction for the denomination. (can be zero). If not zero, the bits
@@ -827,6 +805,23 @@ struct TALER_EXCHANGEDB_Recoup
 };
 
 
+/**
+ * Public key to which a nonce is locked.
+ */
+union TALER_EXCHANGEDB_NonceLockTargetP
+{
+  /**
+   * Nonce is locked to this coin key.
+   */
+  struct TALER_CoinSpendPublicKeyP coin;
+
+  /**
+   * Nonce is locked to this reserve key.
+   */
+  struct TALER_ReservePublicKeyP reserve;
+};
+
+
 /**
  * Information the exchange records about a recoup request
  * in a coin history.
@@ -1702,6 +1697,33 @@ struct TALER_EXCHANGEDB_RefreshRevealedCoin
 };
 
 
+/**
+ * Information per Clause-Schnorr (CS) fresh coin to
+ * be persisted for idempotency during refreshes-reveal.
+ */
+struct TALER_EXCHANGEDB_CsRevealFreshCoinData
+{
+  /**
+   * Denomination of the fresh coin.
+   */
+  struct TALER_DenominationHash new_denom_pub_hash;
+
+  /**
+   * Blind signature of the fresh coin (possibly updated
+   * in case if a replay!).
+   */
+  struct TALER_BlindedDenominationSignature bsig;
+
+  /**
+   * Offset of the fresh coin in the reveal operation.
+   * (May not match the array offset as we may have
+   * a mixture of RSA and CS coins being created, and
+   * this request is only made for the CS subset).
+   */
+  uint32_t coin_off;
+};
+
+
 /**
  * Types of operations that require KYC checks.
  */
@@ -2497,20 +2519,36 @@ struct TALER_EXCHANGEDB_Plugin
                         uint64_t wire_reference);
 
 
+  /**
+   * Locate a nonce for use with a particular public key.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param nonce the nonce to be locked
+   * @param denom_pub_hash hash of the public key of the denomination
+   * @param target public key the nonce is to be locked to
+   * @return statement execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lock_nonce)(void *cls,
+                const struct TALER_CsNonce *nonce,
+                const struct TALER_DenominationHash *denom_pub_hash,
+                const union TALER_EXCHANGEDB_NonceLockTargetP *target);
+
+
   /**
    * Locate the response for a withdraw request under a hash that uniquely
    * identifies the withdraw operation.  Used to ensure idempotency of the
    * request.
    *
    * @param cls the @e cls of this struct with the plugin-specific state
-   * @param wih hash that uniquely identifies the withdraw operation
+   * @param bch hash that uniquely identifies the withdraw operation
    * @param[out] collectable corresponding collectable coin (blind signature)
    *                    if a coin is found
    * @return statement execution status
    */
   enum GNUNET_DB_QueryStatus
   (*get_withdraw_info)(void *cls,
-                       const struct TALER_WithdrawIdentificationHash *wih,
+                       const struct TALER_BlindedCoinHash *bch,
                        struct TALER_EXCHANGEDB_CollectableBlindcoin 
*collectable);
 
 
@@ -2519,9 +2557,8 @@ struct TALER_EXCHANGEDB_Plugin
    * and possibly persisting the withdrawal details.
    *
    * @param cls the `struct PostgresClosure` with the plugin-specific state
-   * @param wih hash that uniquely identifies the withdraw operation
-   * @param[in,out] collectable corresponding collectable coin (blind 
signature)
-   *                    if a coin is found
+   * @param nonce client-contributed input for CS denominations that must be 
checked for idempotency, or NULL for non-CS withdrawals
+   * @param collectable corresponding collectable coin (blind signature)
    * @param now current time (rounded)
    * @param[out] found set to true if the reserve was found
    * @param[out] balance_ok set to true if the balance was sufficient
@@ -2532,8 +2569,8 @@ struct TALER_EXCHANGEDB_Plugin
   enum GNUNET_DB_QueryStatus
   (*do_withdraw)(
     void *cls,
-    const struct TALER_WithdrawIdentificationHash *wih,
-    struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
+    const struct TALER_CsNonce *nonce,
+    const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
     struct GNUNET_TIME_Timestamp now,
     bool *found,
     bool *balance_ok,
@@ -2591,7 +2628,8 @@ struct TALER_EXCHANGEDB_Plugin
    * Perform melt operation, checking for sufficient balance
    * of the coin and possibly persisting the melt details.
    *
-   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param cls the plugin-specific state
+   * @param rms client-contributed input for CS denominations that must be 
checked for idempotency, or NULL for non-CS withdrawals
    * @param[in,out] refresh refresh operation details; the noreveal_index
    *                is set in case the coin was already melted before
    * @param known_coin_id row of the coin in the known_coins table
@@ -2602,12 +2640,35 @@ struct TALER_EXCHANGEDB_Plugin
   enum GNUNET_DB_QueryStatus
   (*do_melt)(
     void *cls,
+    const struct TALER_RefreshMasterSecretP *rms,
     struct TALER_EXCHANGEDB_Refresh *refresh,
     uint64_t known_coin_id,
     bool *zombie_required,
     bool *balance_ok);
 
 
+  /**
+   * Check if the given @a nonce was properly locked to the given @a 
old_coin_pub. If so, check if we already
+   * created CS signatures for the given @a nonce and @a new_denom_pub_hashes,
+   * and if so, return them in @a s_scalars.  Otherwise, persist the
+   * signatures from @a s_scalars in the database.
+   *
+   * @param cls the plugin-specific state
+   * @param nonce the client-provided nonce where we must prevent reuse
+   * @param old_coin_pub public key the nonce was locked to
+   * @param num_fresh_coins array length, number of fresh coins revealed
+   * @param[in,out] crfcds array of data about the fresh coins, of length @a 
num_fresh_coins
+   * @return query execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*cs_refreshes_reveal)(
+    void *cls,
+    const struct TALER_CsNonce *nonce,
+    const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+    unsigned int num_fresh_coins,
+    struct TALER_EXCHANGEDB_CsRevealFreshCoinData *crfcds);
+
+
   /**
    * Perform refund operation, checking for sufficient deposits
    * of the coin and possibly persisting the refund details.
@@ -3540,16 +3601,16 @@ struct TALER_EXCHANGEDB_Plugin
    * from given the hash of the blinded coin.
    *
    * @param cls closure
-   * @param wih hash identifying the withdraw operation
+   * @param bch hash identifying the withdraw operation
    * @param[out] reserve_pub set to information about the reserve (on success 
only)
    * @param[out] reserve_out_serial_id set to row of the @a h_blind_ev in 
reserves_out
    * @return transaction status code
    */
   enum GNUNET_DB_QueryStatus
-  (*get_reserve_by_wih)(void *cls,
-                        const struct TALER_WithdrawIdentificationHash *wih,
-                        struct TALER_ReservePublicKeyP *reserve_pub,
-                        uint64_t *reserve_out_serial_id);
+  (*get_reserve_by_h_blind)(void *cls,
+                            const struct TALER_BlindedCoinHash *bch,
+                            struct TALER_ReservePublicKeyP *reserve_pub,
+                            uint64_t *reserve_out_serial_id);
 
 
   /**
diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h
index e3e47222..8a7e5cd8 100644
--- a/src/include/taler_json_lib.h
+++ b/src/include/taler_json_lib.h
@@ -250,6 +250,35 @@ TALER_JSON_spec_amount_any_nbo (const char *name,
                                 struct TALER_AmountNBO *r_amount);
 
 
+/**
+ * Generate specification to parse all fees for
+ * a denomination under a prefix @a pfx.
+ *
+ * @param pfx string prefix to use
+ * @param currency which currency to expect
+ * @param[out] dfs a `struct TALER_DenomFeeSet` to initialize
+ */
+#define TALER_JSON_SPEC_DENOM_FEES(pfx,currency,dfs) \
+  TALER_JSON_spec_amount (pfx "_withdraw", (currency), &(dfs)->withdraw), \
+  TALER_JSON_spec_amount (pfx "_deposit", (currency), &(dfs)->deposit),   \
+  TALER_JSON_spec_amount (pfx "_refresh", (currency), &(dfs)->refresh),   \
+  TALER_JSON_spec_amount (pfx "_refund", (currency), &(dfs)->refund)
+
+
+/**
+ * Macro to pack all of a denominations' fees under
+ * a given @a pfx.
+ *
+ * @param pfx string prefix to use
+ * @param dfs a `struct TALER_DenomFeeSet` to pack
+ */
+#define TALER_JSON_PACK_DENOM_FEES(pfx, dfs) \
+  TALER_JSON_pack_amount (pfx "_withdraw", &(dfs)->withdraw),   \
+  TALER_JSON_pack_amount (pfx "_deposit", &(dfs)->deposit),     \
+  TALER_JSON_pack_amount (pfx "_refresh", &(dfs)->refresh),     \
+  TALER_JSON_pack_amount (pfx "_refund", &(dfs)->refund)
+
+
 /**
  * Generate line in parser specification for denomination public key.
  *
diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h
index e3d9a893..8a799eae 100644
--- a/src/include/taler_signatures.h
+++ b/src/include/taler_signatures.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2021 Taler Systems SA
+  Copyright (C) 2014-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
@@ -395,6 +395,7 @@ struct TALER_SigningKeyAnnouncementPS
 
 };
 
+
 /**
  * @brief Format used for to allow the wallet to authenticate
  * link data provided by the exchange.
@@ -999,6 +1000,7 @@ struct TALER_MasterExtensionConfigurationPS
   struct TALER_ExtensionConfigHash h_config GNUNET_PACKED;
 };
 
+
 /**
  * @brief Information about a denomination key. Denomination keys
  * are used to sign coins of a certain value into existence.
@@ -1063,29 +1065,9 @@ struct TALER_DenominationKeyValidityPS
   struct TALER_AmountNBO value;
 
   /**
-   * The fee the exchange charges when a coin of this type is withdrawn.
-   * (can be zero).
-   */
-  struct TALER_AmountNBO fee_withdraw;
-
-  /**
-   * The fee the exchange charges when a coin of this type is deposited.
-   * (can be zero).
-   */
-  struct TALER_AmountNBO fee_deposit;
-
-  /**
-   * The fee the exchange charges when a coin of this type is refreshed.
-   * (can be zero).
-   */
-  struct TALER_AmountNBO fee_refresh;
-
-  /**
-   * The fee the exchange charges when a coin of this type is refunded.
-   * (can be zero).  Note that refund fees are charged to the customer;
-   * if a refund is given, the deposit fee is also refunded.
+   * Fees for the coin.
    */
-  struct TALER_AmountNBO fee_refund;
+  struct TALER_DenomFeeSetNBOP fees;
 
   /**
    * Hash code of the denomination public key. (Used to avoid having
@@ -1166,28 +1148,9 @@ struct TALER_ExchangeKeyValidityPS
   struct TALER_AmountNBO value;
 
   /**
-   * The fee the exchange charges when a coin of this type is withdrawn.
-   * (can be zero).
-   */
-  struct TALER_AmountNBO fee_withdraw;
-
-  /**
-   * The fee the exchange charges when a coin of this type is deposited.
-   * (can be zero).
-   */
-  struct TALER_AmountNBO fee_deposit;
-
-  /**
-   * The fee the exchange charges when a coin of this type is refreshed.
-   * (can be zero).
-   */
-  struct TALER_AmountNBO fee_refresh;
-
-  /**
-   * The fee the exchange charges when a coin of this type is refreshed.
-   * (can be zero).
+   * Fees for the coin.
    */
-  struct TALER_AmountNBO fee_refund;
+  struct TALER_DenomFeeSetNBOP fees;
 
   /**
    * Hash code of the denomination public key. (Used to avoid having
diff --git a/src/include/taler_util.h b/src/include/taler_util.h
index b7b74869..1eba4e32 100644
--- a/src/include/taler_util.h
+++ b/src/include/taler_util.h
@@ -144,6 +144,39 @@ TALER_config_get_amount (const struct 
GNUNET_CONFIGURATION_Handle *cfg,
                          struct TALER_Amount *denom);
 
 
+/**
+ * Obtain denomination fee structure of a
+ * denomination from configuration file.  All
+ * fee options must start with "fee_" and have
+ * names typical for the respective fees.
+ *
+ * @param cfg configuration to extract data from
+ * @param currency expected currency
+ * @param section section of the configuration to access
+ * @param[out] fees set to the denomination fees
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+enum GNUNET_GenericReturnValue
+TALER_config_get_denom_fees (const struct GNUNET_CONFIGURATION_Handle *cfg,
+                             const char *currency,
+                             const char *section,
+                             struct TALER_DenomFeeSet *fees);
+
+
+/**
+ * Check that all denominations in @a fees use
+ * @a currency
+ *
+ * @param currency desired currency
+ * @param fees fee set to check
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_denom_fee_check_currency (
+  const char *currency,
+  const struct TALER_DenomFeeSet *fees);
+
+
 /**
  * Load our currency from the @a cfg (in section [taler]
  * the option "CURRENCY").
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index fe2a0b6b..17ad7937 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -24,7 +24,8 @@ libtalerexchange_la_SOURCES = \
   exchange_api_auditor_add_denomination.c \
   exchange_api_curl_defaults.c exchange_api_curl_defaults.h \
   exchange_api_common.c \
-  exchange_api_csr.c \
+  exchange_api_csr_melt.c \
+  exchange_api_csr_withdraw.c \
   exchange_api_handle.c exchange_api_handle.h \
   exchange_api_deposit.c \
   exchange_api_deposits_get.c \
diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c
index d0340924..4f3e878d 100644
--- a/src/lib/exchange_api_common.c
+++ b/src/lib/exchange_api_common.c
@@ -171,10 +171,10 @@ TALER_EXCHANGE_parse_reserve_history (
                                                            &h_denom_pub);
         if ( (GNUNET_YES !=
               TALER_amount_cmp_currency (&withdraw_fee,
-                                         &dki->fee_withdraw)) ||
+                                         &dki->fees.withdraw)) ||
              (0 !=
               TALER_amount_cmp (&withdraw_fee,
-                                &dki->fee_withdraw)) )
+                                &dki->fees.withdraw)) )
         {
           GNUNET_break_op (0);
           GNUNET_JSON_parse_free (withdraw_spec);
@@ -529,10 +529,10 @@ TALER_EXCHANGE_verify_coin_history (
         /* check that deposit fee matches our expectations from /keys! */
         if ( (GNUNET_YES !=
               TALER_amount_cmp_currency (&fee,
-                                         &dk->fee_deposit)) ||
+                                         &dk->fees.deposit)) ||
              (0 !=
               TALER_amount_cmp (&fee,
-                                &dk->fee_deposit)) )
+                                &dk->fees.deposit)) )
         {
           GNUNET_break_op (0);
           return GNUNET_SYSERR;
@@ -575,10 +575,10 @@ TALER_EXCHANGE_verify_coin_history (
         /* check that melt fee matches our expectations from /keys! */
         if ( (GNUNET_YES !=
               TALER_amount_cmp_currency (&fee,
-                                         &dk->fee_refresh)) ||
+                                         &dk->fees.refresh)) ||
              (0 !=
               TALER_amount_cmp (&fee,
-                                &dk->fee_refresh)) )
+                                &dk->fees.refresh)) )
         {
           GNUNET_break_op (0);
           return GNUNET_SYSERR;
@@ -669,10 +669,10 @@ TALER_EXCHANGE_verify_coin_history (
       {
         if ( (GNUNET_YES !=
               TALER_amount_cmp_currency (&refund_fee,
-                                         &dk->fee_refund)) ||
+                                         &dk->fees.refund)) ||
              (0 !=
               TALER_amount_cmp (&refund_fee,
-                                &dk->fee_refund)) )
+                                &dk->fees.refund)) )
         {
           GNUNET_break_op (0);
           return GNUNET_SYSERR;
@@ -863,6 +863,11 @@ TALER_EXCHANGE_verify_coin_history (
       }
       add = GNUNET_NO;
     }
+    else if (0 == strcasecmp (type,
+                              "LOCK_NONCE"))
+    {
+      GNUNET_break (0); // FIXME: implement!
+    }
     else
     {
       /* signature not supported, new version on server? */
diff --git a/src/lib/exchange_api_csr.c b/src/lib/exchange_api_csr_melt.c
similarity index 84%
copy from src/lib/exchange_api_csr.c
copy to src/lib/exchange_api_csr_melt.c
index 220dfba1..9de8cd8d 100644
--- a/src/lib/exchange_api_csr.c
+++ b/src/lib/exchange_api_csr_melt.c
@@ -15,8 +15,8 @@
   <http://www.gnu.org/licenses/>
 */
 /**
- * @file lib/exchange_api_csr.c
- * @brief Implementation of /csr requests (get R in exchange used for Clause 
Schnorr withdraw and refresh)
+ * @file lib/exchange_api_csr_melt.c
+ * @brief Implementation of /csr-melt requests (get R in exchange used for 
Clause Schnorr refresh)
  * @author Lucien Heuzeveldt
  * @author Gian Demarmels
  */
@@ -36,7 +36,7 @@
 /**
  * @brief A Clause Schnorr R Handle
  */
-struct TALER_EXCHANGE_CsRHandle
+struct TALER_EXCHANGE_CsRMeltHandle
 {
   /**
    * The connection to exchange this request handle will use
@@ -46,7 +46,7 @@ struct TALER_EXCHANGE_CsRHandle
   /**
    * Function to call with the result.
    */
-  TALER_EXCHANGE_CsRCallback cb;
+  TALER_EXCHANGE_CsRMeltCallback cb;
 
   /**
    * Closure for @a cb.
@@ -86,13 +86,13 @@ struct TALER_EXCHANGE_CsRHandle
  * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
  */
 static enum GNUNET_GenericReturnValue
-csr_ok (struct TALER_EXCHANGE_CsRHandle *csrh,
-        json_t *arr,
+csr_ok (struct TALER_EXCHANGE_CsRMeltHandle *csrh,
+        const json_t *arr,
         struct TALER_EXCHANGE_HttpResponse *hr)
 {
   unsigned int alen = json_array_size (arr);
   struct TALER_ExchangeWithdrawValues alg_values[GNUNET_NZL (alen)];
-  struct TALER_EXCHANGE_CsRResponse csrr = {
+  struct TALER_EXCHANGE_CsRMeltResponse csrr = {
     .hr = *hr,
     .details.success.alg_values_len = alen,
     .details.success.alg_values = alg_values
@@ -127,7 +127,7 @@ csr_ok (struct TALER_EXCHANGE_CsRHandle *csrh,
 /**
  * Function called when we're done processing the HTTP /csr request.
  *
- * @param cls the `struct TALER_EXCHANGE_CsRHandle`
+ * @param cls the `struct TALER_EXCHANGE_CsRMeltHandle`
  * @param response_code HTTP response code, 0 on error
  * @param response parsed JSON result, NULL on error
  */
@@ -136,13 +136,13 @@ handle_csr_finished (void *cls,
                      long response_code,
                      const void *response)
 {
-  struct TALER_EXCHANGE_CsRHandle *csrh = cls;
+  struct TALER_EXCHANGE_CsRMeltHandle *csrh = cls;
   const json_t *j = response;
   struct TALER_EXCHANGE_HttpResponse hr = {
     .reply = j,
     .http_status = (unsigned int) response_code
   };
-  struct TALER_EXCHANGE_CsRResponse csrr = {
+  struct TALER_EXCHANGE_CsRMeltResponse csrr = {
     .hr = hr
   };
 
@@ -171,7 +171,7 @@ handle_csr_finished (void *cls,
         break;
       }
     }
-    TALER_EXCHANGE_csr_cancel (csrh);
+    TALER_EXCHANGE_csr_melt_cancel (csrh);
     return;
   case MHD_HTTP_BAD_REQUEST:
     /* This should never happen, either us or the exchange is buggy
@@ -215,18 +215,19 @@ handle_csr_finished (void *cls,
   csrh->cb (csrh->cb_cls,
             &csrr);
   csrh->cb = NULL;
-  TALER_EXCHANGE_csr_cancel (csrh);
+  TALER_EXCHANGE_csr_melt_cancel (csrh);
 }
 
 
-struct TALER_EXCHANGE_CsRHandle *
-TALER_EXCHANGE_csr (struct TALER_EXCHANGE_Handle *exchange,
-                    unsigned int nks_len,
-                    struct TALER_EXCHANGE_NonceKey *nks,
-                    TALER_EXCHANGE_CsRCallback res_cb,
-                    void *res_cb_cls)
+struct TALER_EXCHANGE_CsRMeltHandle *
+TALER_EXCHANGE_csr_melt (struct TALER_EXCHANGE_Handle *exchange,
+                         const struct TALER_RefreshMasterSecretP *rms,
+                         unsigned int nks_len,
+                         struct TALER_EXCHANGE_NonceKey *nks,
+                         TALER_EXCHANGE_CsRMeltCallback res_cb,
+                         void *res_cb_cls)
 {
-  struct TALER_EXCHANGE_CsRHandle *csrh;
+  struct TALER_EXCHANGE_CsRMeltHandle *csrh;
   json_t *csr_arr;
 
   if (0 == nks_len)
@@ -240,12 +241,10 @@ TALER_EXCHANGE_csr (struct TALER_EXCHANGE_Handle 
*exchange,
       GNUNET_break (0);
       return NULL;
     }
-
-  csrh = GNUNET_new (struct TALER_EXCHANGE_CsRHandle);
+  csrh = GNUNET_new (struct TALER_EXCHANGE_CsRMeltHandle);
   csrh->exchange = exchange;
   csrh->cb = res_cb;
   csrh->cb_cls = res_cb_cls;
-
   csr_arr = json_array ();
   GNUNET_assert (NULL != csr_arr);
   for (unsigned int i = 0; i<nks_len; i++)
@@ -254,19 +253,17 @@ TALER_EXCHANGE_csr (struct TALER_EXCHANGE_Handle 
*exchange,
     json_t *csr_obj;
 
     csr_obj = GNUNET_JSON_PACK (
-      GNUNET_JSON_pack_data_varsize ("nonce",
-                                     &nk->nonce,
-                                     sizeof(struct TALER_CsNonce)),
-      GNUNET_JSON_pack_data_varsize ("denom_pub_hash",
-                                     &nk->pk->h_key,
-                                     sizeof(struct TALER_DenominationHash)));
+      GNUNET_JSON_pack_uint64 ("coin_offset",
+                               nk->cnc_num),
+      GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+                                  &nk->pk->h_key));
     GNUNET_assert (NULL != csr_obj);
     GNUNET_assert (0 ==
                    json_array_append_new (csr_arr,
                                           csr_obj));
   }
   csrh->url = TEAH_path_to_url (exchange,
-                                "/csr");
+                                "/csr-melt");
   if (NULL == csrh->url)
   {
     json_decref (csr_arr);
@@ -279,6 +276,8 @@ TALER_EXCHANGE_csr (struct TALER_EXCHANGE_Handle *exchange,
     json_t *req;
 
     req = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_data_auto ("rms",
+                                  rms),
       GNUNET_JSON_pack_array_steal ("nks",
                                     csr_arr));
     ctx = TEAH_handle_to_context (exchange);
@@ -309,7 +308,7 @@ TALER_EXCHANGE_csr (struct TALER_EXCHANGE_Handle *exchange,
 
 
 void
-TALER_EXCHANGE_csr_cancel (struct TALER_EXCHANGE_CsRHandle *csrh)
+TALER_EXCHANGE_csr_melt_cancel (struct TALER_EXCHANGE_CsRMeltHandle *csrh)
 {
   if (NULL != csrh->job)
   {
diff --git a/src/lib/exchange_api_csr.c b/src/lib/exchange_api_csr_withdraw.c
similarity index 69%
rename from src/lib/exchange_api_csr.c
rename to src/lib/exchange_api_csr_withdraw.c
index 220dfba1..d23f8ef8 100644
--- a/src/lib/exchange_api_csr.c
+++ b/src/lib/exchange_api_csr_withdraw.c
@@ -15,8 +15,8 @@
   <http://www.gnu.org/licenses/>
 */
 /**
- * @file lib/exchange_api_csr.c
- * @brief Implementation of /csr requests (get R in exchange used for Clause 
Schnorr withdraw and refresh)
+ * @file lib/exchange_api_csr_withdraw.c
+ * @brief Implementation of /csr-withdraw requests (get R in exchange used for 
Clause Schnorr withdraw and refresh)
  * @author Lucien Heuzeveldt
  * @author Gian Demarmels
  */
@@ -36,7 +36,7 @@
 /**
  * @brief A Clause Schnorr R Handle
  */
-struct TALER_EXCHANGE_CsRHandle
+struct TALER_EXCHANGE_CsRWithdrawHandle
 {
   /**
    * The connection to exchange this request handle will use
@@ -46,7 +46,7 @@ struct TALER_EXCHANGE_CsRHandle
   /**
    * Function to call with the result.
    */
-  TALER_EXCHANGE_CsRCallback cb;
+  TALER_EXCHANGE_CsRWithdrawCallback cb;
 
   /**
    * Closure for @a cb.
@@ -81,42 +81,32 @@ struct TALER_EXCHANGE_CsRHandle
  * to the application via the callback.
  *
  * @param csrh operation handle
- * @param arr reply from the exchange
+ * @param av reply from the exchange
  * @param hr http response details
  * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
  */
 static enum GNUNET_GenericReturnValue
-csr_ok (struct TALER_EXCHANGE_CsRHandle *csrh,
-        json_t *arr,
+csr_ok (struct TALER_EXCHANGE_CsRWithdrawHandle *csrh,
+        const json_t *av,
         struct TALER_EXCHANGE_HttpResponse *hr)
 {
-  unsigned int alen = json_array_size (arr);
-  struct TALER_ExchangeWithdrawValues alg_values[GNUNET_NZL (alen)];
-  struct TALER_EXCHANGE_CsRResponse csrr = {
+  struct TALER_EXCHANGE_CsRWithdrawResponse csrr = {
     .hr = *hr,
-    .details.success.alg_values_len = alen,
-    .details.success.alg_values = alg_values
+  };
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_exchange_withdraw_values (
+      "ewv",
+      &csrr.details.success.alg_values),
+    GNUNET_JSON_spec_end ()
   };
 
-  for (unsigned int i = 0; i<alen; i++)
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (av,
+                         spec,
+                         NULL, NULL))
   {
-    json_t *av = json_array_get (arr,
-                                 i);
-    struct GNUNET_JSON_Specification spec[] = {
-      TALER_JSON_spec_exchange_withdraw_values (
-        "ewv",
-        &alg_values[i]),
-      GNUNET_JSON_spec_end ()
-    };
-
-    if (GNUNET_OK !=
-        GNUNET_JSON_parse (av,
-                           spec,
-                           NULL, NULL))
-    {
-      GNUNET_break_op (0);
-      return GNUNET_SYSERR;
-    }
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
   }
   csrh->cb (csrh->cb_cls,
             &csrr);
@@ -127,7 +117,7 @@ csr_ok (struct TALER_EXCHANGE_CsRHandle *csrh,
 /**
  * Function called when we're done processing the HTTP /csr request.
  *
- * @param cls the `struct TALER_EXCHANGE_CsRHandle`
+ * @param cls the `struct TALER_EXCHANGE_CsRWithdrawHandle`
  * @param response_code HTTP response code, 0 on error
  * @param response parsed JSON result, NULL on error
  */
@@ -136,13 +126,13 @@ handle_csr_finished (void *cls,
                      long response_code,
                      const void *response)
 {
-  struct TALER_EXCHANGE_CsRHandle *csrh = cls;
+  struct TALER_EXCHANGE_CsRWithdrawHandle *csrh = cls;
   const json_t *j = response;
   struct TALER_EXCHANGE_HttpResponse hr = {
     .reply = j,
     .http_status = (unsigned int) response_code
   };
-  struct TALER_EXCHANGE_CsRResponse csrr = {
+  struct TALER_EXCHANGE_CsRWithdrawResponse csrr = {
     .hr = hr
   };
 
@@ -154,16 +144,10 @@ handle_csr_finished (void *cls,
     break;
   case MHD_HTTP_OK:
     {
-      json_t *arr;
-
-      arr = json_object_get (j,
-                             "ewvs");
-      if ( (NULL == arr) ||
-           (0 == json_array_size (arr)) ||
-           (GNUNET_OK !=
-            csr_ok (csrh,
-                    arr,
-                    &hr)) )
+      if (GNUNET_OK !=
+          csr_ok (csrh,
+                  response,
+                  &hr))
       {
         GNUNET_break_op (0);
         csrr.hr.http_status = 0;
@@ -171,7 +155,7 @@ handle_csr_finished (void *cls,
         break;
       }
     }
-    TALER_EXCHANGE_csr_cancel (csrh);
+    TALER_EXCHANGE_csr_withdraw_cancel (csrh);
     return;
   case MHD_HTTP_BAD_REQUEST:
     /* This should never happen, either us or the exchange is buggy
@@ -215,72 +199,49 @@ handle_csr_finished (void *cls,
   csrh->cb (csrh->cb_cls,
             &csrr);
   csrh->cb = NULL;
-  TALER_EXCHANGE_csr_cancel (csrh);
+  TALER_EXCHANGE_csr_withdraw_cancel (csrh);
 }
 
 
-struct TALER_EXCHANGE_CsRHandle *
-TALER_EXCHANGE_csr (struct TALER_EXCHANGE_Handle *exchange,
-                    unsigned int nks_len,
-                    struct TALER_EXCHANGE_NonceKey *nks,
-                    TALER_EXCHANGE_CsRCallback res_cb,
-                    void *res_cb_cls)
+struct TALER_EXCHANGE_CsRWithdrawHandle *
+TALER_EXCHANGE_csr_withdraw (struct TALER_EXCHANGE_Handle *exchange,
+                             const struct TALER_EXCHANGE_DenomPublicKey *pk,
+                             const struct TALER_CsNonce *nonce,
+                             TALER_EXCHANGE_CsRWithdrawCallback res_cb,
+                             void *res_cb_cls)
 {
-  struct TALER_EXCHANGE_CsRHandle *csrh;
-  json_t *csr_arr;
+  struct TALER_EXCHANGE_CsRWithdrawHandle *csrh;
 
-  if (0 == nks_len)
+  if (TALER_DENOMINATION_CS != pk->key.cipher)
   {
     GNUNET_break (0);
     return NULL;
   }
-  for (unsigned int i = 0; i<nks_len; i++)
-    if (TALER_DENOMINATION_CS != nks[i].pk->key.cipher)
-    {
-      GNUNET_break (0);
-      return NULL;
-    }
-
-  csrh = GNUNET_new (struct TALER_EXCHANGE_CsRHandle);
+  csrh = GNUNET_new (struct TALER_EXCHANGE_CsRWithdrawHandle);
   csrh->exchange = exchange;
   csrh->cb = res_cb;
   csrh->cb_cls = res_cb_cls;
-
-  csr_arr = json_array ();
-  GNUNET_assert (NULL != csr_arr);
-  for (unsigned int i = 0; i<nks_len; i++)
-  {
-    const struct TALER_EXCHANGE_NonceKey *nk = &nks[i];
-    json_t *csr_obj;
-
-    csr_obj = GNUNET_JSON_PACK (
-      GNUNET_JSON_pack_data_varsize ("nonce",
-                                     &nk->nonce,
-                                     sizeof(struct TALER_CsNonce)),
-      GNUNET_JSON_pack_data_varsize ("denom_pub_hash",
-                                     &nk->pk->h_key,
-                                     sizeof(struct TALER_DenominationHash)));
-    GNUNET_assert (NULL != csr_obj);
-    GNUNET_assert (0 ==
-                   json_array_append_new (csr_arr,
-                                          csr_obj));
-  }
   csrh->url = TEAH_path_to_url (exchange,
-                                "/csr");
+                                "/csr-withdraw");
   if (NULL == csrh->url)
   {
-    json_decref (csr_arr);
     GNUNET_free (csrh);
     return NULL;
   }
+
   {
     CURL *eh;
     struct GNUNET_CURL_Context *ctx;
     json_t *req;
 
     req = GNUNET_JSON_PACK (
-      GNUNET_JSON_pack_array_steal ("nks",
-                                    csr_arr));
+      GNUNET_JSON_pack_data_varsize ("nonce",
+                                     nonce,
+                                     sizeof(struct TALER_CsNonce)),
+      GNUNET_JSON_pack_data_varsize ("denom_pub_hash",
+                                     &pk->h_key,
+                                     sizeof(struct TALER_DenominationHash)));
+    GNUNET_assert (NULL != req);
     ctx = TEAH_handle_to_context (exchange);
     eh = TALER_EXCHANGE_curl_easy_get_ (csrh->url);
     if ( (NULL == eh) ||
@@ -309,7 +270,8 @@ TALER_EXCHANGE_csr (struct TALER_EXCHANGE_Handle *exchange,
 
 
 void
-TALER_EXCHANGE_csr_cancel (struct TALER_EXCHANGE_CsRHandle *csrh)
+TALER_EXCHANGE_csr_withdraw_cancel (struct
+                                    TALER_EXCHANGE_CsRWithdrawHandle *csrh)
 {
   if (NULL != csrh->job)
   {
diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c
index 2bfaaf6c..5a819461 100644
--- a/src/lib/exchange_api_deposit.c
+++ b/src/lib/exchange_api_deposit.c
@@ -491,7 +491,7 @@ verify_signatures (const struct 
TALER_EXCHANGE_DenomPublicKey *dki,
 {
   if (GNUNET_OK !=
       TALER_wallet_deposit_verify (amount,
-                                   &dki->fee_deposit,
+                                   &dki->fees.deposit,
                                    h_wire,
                                    h_contract_terms,
                                    h_age_commitment,
@@ -508,7 +508,7 @@ verify_signatures (const struct 
TALER_EXCHANGE_DenomPublicKey *dki,
     TALER_LOG_DEBUG ("... amount_with_fee was %s\n",
                      TALER_amount2s (amount));
     TALER_LOG_DEBUG ("... deposit_fee was %s\n",
-                     TALER_amount2s (&dki->fee_deposit));
+                     TALER_amount2s (&dki->fees.deposit));
     return GNUNET_SYSERR;
   }
 
@@ -536,7 +536,7 @@ verify_signatures (const struct 
TALER_EXCHANGE_DenomPublicKey *dki,
   }
 
   /* Check coin does make a contribution */
-  if (0 < TALER_amount_cmp (&dki->fee_deposit,
+  if (0 < TALER_amount_cmp (&dki->fees.deposit,
                             amount))
   {
     GNUNET_break_op (0);
@@ -628,7 +628,7 @@ TALER_EXCHANGE_deposit (
   if (0 >
       TALER_amount_subtract (&amount_without_fee,
                              amount,
-                             &dki->fee_deposit))
+                             &dki->fees.deposit))
   {
     *ec = TALER_EC_EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT;
     GNUNET_break_op (0);
diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c
index 3243f5e9..ee5f44a0 100644
--- a/src/lib/exchange_api_handle.c
+++ b/src/lib/exchange_api_handle.c
@@ -305,6 +305,7 @@ parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey 
*sign_key,
 /**
  * Parse a exchange's denomination key encoded in JSON.
  *
+ * @param currency expected currency of all fees
  * @param[out] denom_key where to return the result
  * @param check_sigs should we check signatures?
  * @param[in] denom_key_obj json to parse
@@ -314,7 +315,8 @@ parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey 
*sign_key,
  *        invalid or the json malformed.
  */
 static enum GNUNET_GenericReturnValue
-parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey *denom_key,
+parse_json_denomkey (const char *currency,
+                     struct TALER_EXCHANGE_DenomPublicKey *denom_key,
                      int check_sigs,
                      json_t *denom_key_obj,
                      struct TALER_MasterPublicKeyP *master_key,
@@ -331,16 +333,12 @@ parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey 
*denom_key,
                                 &denom_key->valid_from),
     GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
                                 &denom_key->expire_legal),
-    TALER_JSON_spec_amount_any ("value",
-                                &denom_key->value),
-    TALER_JSON_spec_amount_any ("fee_withdraw",
-                                &denom_key->fee_withdraw),
-    TALER_JSON_spec_amount_any ("fee_deposit",
-                                &denom_key->fee_deposit),
-    TALER_JSON_spec_amount_any ("fee_refresh",
-                                &denom_key->fee_refresh),
-    TALER_JSON_spec_amount_any ("fee_refund",
-                                &denom_key->fee_refund),
+    TALER_JSON_spec_amount ("value",
+                            currency,
+                            &denom_key->value),
+    TALER_JSON_SPEC_DENOM_FEES ("fee",
+                                currency,
+                                &denom_key->fees),
     TALER_JSON_spec_denom_pub ("denom_pub",
                                &denom_key->key),
     GNUNET_JSON_spec_end ()
@@ -372,10 +370,7 @@ parse_json_denomkey (struct TALER_EXCHANGE_DenomPublicKey 
*denom_key,
             denom_key->expire_deposit,
             denom_key->expire_legal,
             &denom_key->value,
-            &denom_key->fee_withdraw,
-            &denom_key->fee_deposit,
-            &denom_key->fee_refresh,
-            &denom_key->fee_refund,
+            &denom_key->fees,
             master_key,
             &denom_key->master_sig));
   return GNUNET_OK;
@@ -492,10 +487,7 @@ parse_json_auditor (struct 
TALER_EXCHANGE_AuditorInformation *auditor,
             dk->expire_deposit,
             dk->expire_legal,
             &dk->value,
-            &dk->fee_withdraw,
-            &dk->fee_deposit,
-            &dk->fee_refresh,
-            &dk->fee_refund,
+            &dk->fees,
             &auditor->auditor_pub,
             &auditor_sig))
       {
@@ -883,7 +875,8 @@ decode_keys_json (const json_t *resp_obj,
                 0,
                 sizeof (dk));
         EXITIF (GNUNET_SYSERR ==
-                parse_json_denomkey (&dk,
+                parse_json_denomkey (key_data->currency,
+                                     &dk,
                                      check_sig,
                                      denom_key_obj,
                                      &key_data->master_pub,
@@ -1728,14 +1721,8 @@ TALER_EXCHANGE_serialize_data (struct 
TALER_EXCHANGE_Handle *exchange)
                                   dk->expire_legal),
       TALER_JSON_pack_amount ("value",
                               &dk->value),
-      TALER_JSON_pack_amount ("fee_withdraw",
-                              &dk->fee_withdraw),
-      TALER_JSON_pack_amount ("fee_deposit",
-                              &dk->fee_deposit),
-      TALER_JSON_pack_amount ("fee_refresh",
-                              &dk->fee_refresh),
-      TALER_JSON_pack_amount ("fee_refund",
-                              &dk->fee_refund),
+      TALER_JSON_PACK_DENOM_FEES ("fee",
+                                  &dk->fees),
       GNUNET_JSON_pack_data_auto ("master_sig",
                                   &dk->master_sig),
       TALER_JSON_pack_denom_pub ("denom_pub",
diff --git a/src/lib/exchange_api_melt.c b/src/lib/exchange_api_melt.c
index 18596d89..71e6f55f 100644
--- a/src/lib/exchange_api_melt.c
+++ b/src/lib/exchange_api_melt.c
@@ -94,7 +94,7 @@ struct TALER_EXCHANGE_MeltHandle
   /**
    * Handle for the preflight request, or NULL.
    */
-  struct TALER_EXCHANGE_CsRHandle *csr;
+  struct TALER_EXCHANGE_CsRMeltHandle *csr;
 
   /**
    * Public key of the coin being melted.
@@ -111,6 +111,10 @@ struct TALER_EXCHANGE_MeltHandle
    */
   uint32_t noreveal_index;
 
+  /**
+   * True if we need to include @e rms in our melt request.
+   */
+  bool send_rms;
 };
 
 
@@ -488,7 +492,13 @@ start_melt (struct TALER_EXCHANGE_MeltHandle *mh)
     TALER_JSON_pack_amount ("value_with_fee",
                             &mh->md.melted_coin.melt_amount_with_fee),
     GNUNET_JSON_pack_data_auto ("rc",
-                                &mh->md.rc));
+                                &mh->md.rc),
+    GNUNET_JSON_pack_allow_null (
+      mh->send_rms
+       ? GNUNET_JSON_pack_data_auto ("rms",
+                                     &mh->rms)
+       : GNUNET_JSON_pack_string ("rms",
+                                  NULL)));
   {
     char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
     char *end;
@@ -571,7 +581,7 @@ fail_mh (struct TALER_EXCHANGE_MeltHandle *mh,
  */
 static void
 csr_cb (void *cls,
-        const struct TALER_EXCHANGE_CsRResponse *csrr)
+        const struct TALER_EXCHANGE_CsRMeltResponse *csrr)
 {
   struct TALER_EXCHANGE_MeltHandle *mh = cls;
   unsigned int nks_off = 0;
@@ -583,7 +593,7 @@ csr_cb (void *cls,
       .hr = csrr->hr
     };
 
-    mr.hr.hint = "/csr failed";
+    mr.hr.hint = "/csr-melt failed";
     mh->melt_cb (mh->melt_cb_cls,
                  &mr);
     TALER_EXCHANGE_melt_cancel (mh);
@@ -612,6 +622,7 @@ csr_cb (void *cls,
       break;
     }
   }
+  mh->send_rms = true;
   if (GNUNET_OK !=
       start_melt (mh))
   {
@@ -668,20 +679,19 @@ TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle 
*exchange,
     case TALER_DENOMINATION_CS:
       wv->cipher = TALER_DENOMINATION_CS;
       nks[nks_off].pk = fresh_pk;
-      TALER_cs_refresh_nonce_derive (rms,
-                                     i,
-                                     &nks[nks_off].nonce);
+      nks[nks_off].cnc_num = nks_off;
       nks_off++;
       break;
     }
   }
   if (0 != nks_off)
   {
-    mh->csr = TALER_EXCHANGE_csr (exchange,
-                                  nks_off,
-                                  nks,
-                                  &csr_cb,
-                                  mh);
+    mh->csr = TALER_EXCHANGE_csr_melt (exchange,
+                                       rms,
+                                       nks_off,
+                                       nks,
+                                       &csr_cb,
+                                       mh);
     if (NULL == mh->csr)
     {
       GNUNET_break (0);
@@ -711,7 +721,7 @@ TALER_EXCHANGE_melt_cancel (struct 
TALER_EXCHANGE_MeltHandle *mh)
   }
   if (NULL != mh->csr)
   {
-    TALER_EXCHANGE_csr_cancel (mh->csr);
+    TALER_EXCHANGE_csr_melt_cancel (mh->csr);
     mh->csr = NULL;
   }
   TALER_EXCHANGE_free_melt_data_ (&mh->md); /* does not free 'md' itself */
diff --git a/src/lib/exchange_api_recoup.c b/src/lib/exchange_api_recoup.c
index 9b7201cd..c94296c7 100644
--- a/src/lib/exchange_api_recoup.c
+++ b/src/lib/exchange_api_recoup.c
@@ -178,7 +178,7 @@ handle_recoup_finished (void *cls,
                                  "history");
       if (GNUNET_OK !=
           TALER_EXCHANGE_verify_coin_history (dki,
-                                              dki->fee_deposit.currency,
+                                              dki->fees.deposit.currency,
                                               &ph->coin_pub,
                                               history,
                                               &h_denom_pub,
diff --git a/src/lib/exchange_api_recoup_refresh.c 
b/src/lib/exchange_api_recoup_refresh.c
index 02e99415..0fff3a23 100644
--- a/src/lib/exchange_api_recoup_refresh.c
+++ b/src/lib/exchange_api_recoup_refresh.c
@@ -192,7 +192,7 @@ handle_recoup_refresh_finished (void *cls,
                                  "history");
       if (GNUNET_OK !=
           TALER_EXCHANGE_verify_coin_history (dki,
-                                              dki->fee_deposit.currency,
+                                              dki->fees.deposit.currency,
                                               &ph->coin_pub,
                                               history,
                                               &h_denom_pub,
diff --git a/src/lib/exchange_api_refresh_common.c 
b/src/lib/exchange_api_refresh_common.c
index 89ee1e17..3cd47a6f 100644
--- a/src/lib/exchange_api_refresh_common.c
+++ b/src/lib/exchange_api_refresh_common.c
@@ -64,6 +64,7 @@ TALER_EXCHANGE_get_melt_data_ (
   struct TALER_Amount total;
   struct TALER_CoinSpendPublicKeyP coin_pub;
   struct TALER_CsNonce nonces[rd->fresh_pks_len];
+  bool uses_cs = false;
 
   GNUNET_CRYPTO_eddsa_key_get_public (&rd->melt_priv.eddsa_priv,
                                       &coin_pub.eddsa_pub);
@@ -74,7 +75,7 @@ TALER_EXCHANGE_get_melt_data_ (
   md->num_fresh_coins = rd->fresh_pks_len;
   md->melted_coin.coin_priv = rd->melt_priv;
   md->melted_coin.melt_amount_with_fee = rd->melt_amount;
-  md->melted_coin.fee_melt = rd->melt_pk.fee_refresh;
+  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;
   GNUNET_assert (GNUNET_OK ==
@@ -98,6 +99,7 @@ TALER_EXCHANGE_get_melt_data_ (
     }
     if (TALER_DENOMINATION_CS == alg_values[j].cipher)
     {
+      uses_cs = true;
       TALER_cs_refresh_nonce_derive (
         rms,
         j,
@@ -112,7 +114,7 @@ TALER_EXCHANGE_get_melt_data_ (
          (0 >
           TALER_amount_add (&total,
                             &total,
-                            &rd->fresh_pks[j].fee_withdraw)) )
+                            &rd->fresh_pks[j].fees.withdraw)) )
     {
       GNUNET_break (0);
       TALER_EXCHANGE_free_melt_data_ (md);
@@ -139,6 +141,7 @@ TALER_EXCHANGE_get_melt_data_ (
 
     TALER_planchet_secret_to_transfer_priv (
       rms,
+      &rd->melt_priv,
       i,
       &md->transfer_priv[i]);
     GNUNET_CRYPTO_ecdhe_key_get_public (
@@ -199,6 +202,9 @@ TALER_EXCHANGE_get_melt_data_ (
     }
     TALER_refresh_get_commitment (&md->rc,
                                   TALER_CNC_KAPPA,
+                                  uses_cs
+                                  ? rms
+                                  : NULL,
                                   rd->fresh_pks_len,
                                   rce,
                                   &coin_pub,
diff --git a/src/lib/exchange_api_refreshes_reveal.c 
b/src/lib/exchange_api_refreshes_reveal.c
index 8d04c279..e87cae2e 100644
--- a/src/lib/exchange_api_refreshes_reveal.c
+++ b/src/lib/exchange_api_refreshes_reveal.c
@@ -333,6 +333,7 @@ TALER_EXCHANGE_refreshes_reveal (
   struct GNUNET_CURL_Context *ctx;
   struct MeltData md;
   char arg_str[sizeof (struct TALER_RefreshCommitmentP) * 2 + 32];
+  bool send_rms = false;
 
   GNUNET_assert (num_coins == rd->fresh_pks_len);
   if (noreveal_index >= TALER_CNC_KAPPA)
@@ -369,6 +370,8 @@ TALER_EXCHANGE_refreshes_reveal (
     const struct TALER_RefreshCoinData *rcd = &md.rcd[noreveal_index][i];
     struct TALER_DenominationHash denom_hash;
 
+    if (TALER_DENOMINATION_CS == md.fcds[i].fresh_pk.cipher)
+      send_rms = true;
     TALER_denom_pub_hash (&md.fcds[i].fresh_pk,
                           &denom_hash);
     GNUNET_assert (0 ==
@@ -421,6 +424,12 @@ TALER_EXCHANGE_refreshes_reveal (
   reveal_obj = GNUNET_JSON_PACK (
     GNUNET_JSON_pack_data_auto ("transfer_pub",
                                 &md.transfer_pub[noreveal_index]),
+    GNUNET_JSON_pack_allow_null (
+      send_rms
+      ? GNUNET_JSON_pack_data_auto ("rms",
+                                    rms)
+      : GNUNET_JSON_pack_string ("rms",
+                                 NULL)),
     GNUNET_JSON_pack_array_steal ("transfer_privs",
                                   transfer_privs),
     GNUNET_JSON_pack_array_steal ("link_sigs",
diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c
index efc8a99c..01b6e8ba 100644
--- a/src/lib/exchange_api_withdraw.c
+++ b/src/lib/exchange_api_withdraw.c
@@ -106,7 +106,7 @@ struct TALER_EXCHANGE_WithdrawHandle
   /**
    * Handler for the CS R request (only used for TALER_DENOMINATION_CS 
denominations)
    */
-  struct TALER_EXCHANGE_CsRHandle *csrh;
+  struct TALER_EXCHANGE_CsRWithdrawHandle *csrh;
 
 };
 
@@ -192,11 +192,12 @@ handle_reserve_withdraw_finished (
  * Function called when stage 1 of CS withdraw is finished (request r_pub's)
  *
  * @param cls the `struct TALER_EXCHANGE_WithdrawHandle`
- * @param csrr replies from the /csr request
+ * @param csrr replies from the /csr-withdraw request
  */
 static void
-withdraw_cs_stage_two_callback (void *cls,
-                                const struct TALER_EXCHANGE_CsRResponse *csrr)
+withdraw_cs_stage_two_callback (
+  void *cls,
+  const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr)
 {
   struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
   struct TALER_EXCHANGE_WithdrawResponse wr = {
@@ -208,13 +209,7 @@ withdraw_cs_stage_two_callback (void *cls,
   switch (csrr->hr.http_status)
   {
   case MHD_HTTP_OK:
-    if (1 != csrr->details.success.alg_values_len)
-    {
-      GNUNET_break (0);
-      wr.hr.http_status = 0;
-      break;
-    }
-    wh->alg_values = csrr->details.success.alg_values[0];
+    wh->alg_values = csrr->details.success.alg_values;
     TALER_planchet_setup_coin_priv (&wh->ps,
                                     &wh->alg_values,
                                     &wh->priv);
@@ -306,22 +301,19 @@ TALER_EXCHANGE_withdraw (
     }
   case TALER_DENOMINATION_CS:
     {
-      struct TALER_EXCHANGE_NonceKey nk = {
-        .pk = pk,
-      };
-
-      TALER_cs_withdraw_nonce_derive (ps,
-                                      &nk.nonce);
+      TALER_cs_withdraw_nonce_derive (
+        ps,
+        &wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce);
       /* Note that we only initialize the first half
          of the blinded_planchet here; the other part
-         will be done after the /csr request! */
+         will be done after the /csr-withdraw request! */
       wh->pd.blinded_planchet.cipher = TALER_DENOMINATION_CS;
-      wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce = nk.nonce;
-      wh->csrh = TALER_EXCHANGE_csr (exchange,
-                                     1, /* "array" length */
-                                     &nk,
-                                     &withdraw_cs_stage_two_callback,
-                                     wh);
+      wh->csrh = TALER_EXCHANGE_csr_withdraw (
+        exchange,
+        pk,
+        &wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce,
+        &withdraw_cs_stage_two_callback,
+        wh);
       break;
     }
   default:
@@ -339,7 +331,7 @@ TALER_EXCHANGE_withdraw_cancel (struct 
TALER_EXCHANGE_WithdrawHandle *wh)
   TALER_blinded_planchet_free (&wh->pd.blinded_planchet);
   if (NULL != wh->csrh)
   {
-    TALER_EXCHANGE_csr_cancel (wh->csrh);
+    TALER_EXCHANGE_csr_withdraw_cancel (wh->csrh);
     wh->csrh = NULL;
   }
   if (NULL != wh->wh2)
diff --git a/src/lib/exchange_api_withdraw2.c b/src/lib/exchange_api_withdraw2.c
index c0643b9a..2441a141 100644
--- a/src/lib/exchange_api_withdraw2.c
+++ b/src/lib/exchange_api_withdraw2.c
@@ -403,7 +403,7 @@ TALER_EXCHANGE_withdraw2 (
   if (0 >
       TALER_amount_add (&wh->requested_amount,
                         &dk->value,
-                        &dk->fee_withdraw))
+                        &dk->fees.withdraw))
   {
     /* Overflow here? Very strange, our CPU must be fried... */
     GNUNET_break (0);
diff --git a/src/testing/test_exchange_api-cs.conf 
b/src/testing/test_exchange_api-cs.conf
index 79332d64..c50b7e82 100644
--- a/src/testing/test_exchange_api-cs.conf
+++ b/src/testing/test_exchange_api-cs.conf
@@ -101,7 +101,7 @@ value = EUR:0.10
 duration_withdraw = 7 days
 duration_spend = 2 years
 duration_legal = 3 years
-fee_withdraw = EUR:0.01
+fee_withdraw = EUR:0.00
 fee_deposit = EUR:0.01
 fee_refresh = EUR:0.03
 fee_refund = EUR:0.01
@@ -157,7 +157,7 @@ value = EUR:0.10
 duration_withdraw = 7 days
 duration_spend = 2 years
 duration_legal = 3 years
-fee_withdraw = EUR:0.01
+fee_withdraw = EUR:0.00
 fee_deposit = EUR:0.01
 fee_refresh = EUR:0.03
 fee_refund = EUR:0.01
diff --git a/src/testing/testing_api_cmd_auditor_add_denom_sig.c 
b/src/testing/testing_api_cmd_auditor_add_denom_sig.c
index 33cd9575..b8feb7d3 100644
--- a/src/testing/testing_api_cmd_auditor_add_denom_sig.c
+++ b/src/testing/testing_api_cmd_auditor_add_denom_sig.c
@@ -152,10 +152,7 @@ auditor_add_run (void *cls,
       dk->expire_deposit,
       dk->expire_legal,
       &dk->value,
-      &dk->fee_withdraw,
-      &dk->fee_deposit,
-      &dk->fee_refresh,
-      &dk->fee_refund,
+      &dk->fees,
       &is->auditor_priv,
       &auditor_sig);
   }
diff --git a/src/testing/testing_api_cmd_deposit.c 
b/src/testing/testing_api_cmd_deposit.c
index d3fafc63..d3a444ee 100644
--- a/src/testing/testing_api_cmd_deposit.c
+++ b/src/testing/testing_api_cmd_deposit.c
@@ -409,8 +409,7 @@ deposit_run (void *cls,
   {
     TALER_age_commitment_hash (age_commitment, &h_age_commitment);
   }
-
-  ds->deposit_fee = denom_pub->fee_deposit;
+  ds->deposit_fee = denom_pub->fees.deposit;
   GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
                                       &coin_pub.eddsa_pub);
 
@@ -440,7 +439,7 @@ deposit_run (void *cls,
                    TALER_JSON_merchant_wire_signature_hash (ds->wire_details,
                                                             &h_wire));
     TALER_wallet_deposit_sign (&ds->amount,
-                               &denom_pub->fee_deposit,
+                               &denom_pub->fees.deposit,
                                &h_wire,
                                &h_contract_terms,
                                &h_age_commitment,
diff --git a/src/testing/testing_api_cmd_insert_deposit.c 
b/src/testing/testing_api_cmd_insert_deposit.c
index dcda7cf3..5247ccd7 100644
--- a/src/testing/testing_api_cmd_insert_deposit.c
+++ b/src/testing/testing_api_cmd_insert_deposit.c
@@ -103,16 +103,16 @@ fake_issue (struct 
TALER_EXCHANGEDB_DenominationKeyInformationP *issue)
                                              &issue->properties.value));
   GNUNET_assert (GNUNET_OK ==
                  TALER_string_to_amount_nbo ("EUR:0.1",
-                                             &issue->properties.fee_withdraw));
+                                             
&issue->properties.fees.withdraw));
   GNUNET_assert (GNUNET_OK ==
                  TALER_string_to_amount_nbo ("EUR:0.1",
-                                             &issue->properties.fee_deposit));
+                                             &issue->properties.fees.deposit));
   GNUNET_assert (GNUNET_OK ==
                  TALER_string_to_amount_nbo ("EUR:0.1",
-                                             &issue->properties.fee_refresh));
+                                             &issue->properties.fees.refresh));
   GNUNET_assert (GNUNET_OK ==
                  TALER_string_to_amount_nbo ("EUR:0.1",
-                                             &issue->properties.fee_refund));
+                                             &issue->properties.fees.refund));
 }
 
 
diff --git a/src/testing/testing_api_cmd_refresh.c 
b/src/testing/testing_api_cmd_refresh.c
index 11c88c19..a2fb4f15 100644
--- a/src/testing/testing_api_cmd_refresh.c
+++ b/src/testing/testing_api_cmd_refresh.c
@@ -1101,7 +1101,7 @@ melt_run (void *cls,
 
     /* 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;
+    melt_amount = melt_denom_pub->fees.refresh;
     age_restricted = melt_denom_pub->key.age_mask.mask != 0;
     for (unsigned int i = 0; i<num_fresh_coins; i++)
     {
@@ -1136,7 +1136,7 @@ melt_run (void *cls,
       GNUNET_assert (0 <=
                      TALER_amount_add (&melt_amount,
                                        &melt_amount,
-                                       &fresh_pk->fee_withdraw));
+                                       &fresh_pk->fees.withdraw));
       rms->fresh_pks[i] = *fresh_pk;
       /* Make a deep copy of the RSA key */
       TALER_denom_pub_deep_copy (&rms->fresh_pks[i].key,
diff --git a/src/testing/testing_api_cmd_withdraw.c 
b/src/testing/testing_api_cmd_withdraw.c
index e5e8adfd..14015c49 100644
--- a/src/testing/testing_api_cmd_withdraw.c
+++ b/src/testing/testing_api_cmd_withdraw.c
@@ -474,19 +474,15 @@ withdraw_run (void *cls,
   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,
-                                       ws->h_age_commitment,
-                                       &reserve_withdraw_cb,
-                                       ws);
-  }
+                                   &ws->pk->fees.withdraw));
+  ws->reserve_history.details.withdraw.fee = ws->pk->fees.withdraw;
+  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);
diff --git a/src/util/amount.c b/src/util/amount.c
index ae9ae652..3ce8c071 100644
--- a/src/util/amount.c
+++ b/src/util/amount.c
@@ -253,6 +253,20 @@ TALER_amount_is_zero (const struct TALER_Amount *amount)
 }
 
 
+enum GNUNET_GenericReturnValue
+TALER_amount_is_currency (const struct TALER_Amount *amount,
+                          const char *currency)
+{
+  if (GNUNET_OK !=
+      TALER_amount_is_valid (amount))
+    return GNUNET_SYSERR;
+  return (0 == strcasecmp (currency,
+                           amount->currency))
+    ? GNUNET_OK
+    : GNUNET_NO;
+}
+
+
 /**
  * Test if @a a is valid, NBO variant.
  *
diff --git a/src/util/auditor_signatures.c b/src/util/auditor_signatures.c
index 7b53c21c..2ab690a0 100644
--- a/src/util/auditor_signatures.c
+++ b/src/util/auditor_signatures.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2020 Taler Systems SA
+  Copyright (C) 2020, 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
@@ -33,10 +33,7 @@ TALER_auditor_denom_validity_sign (
   struct GNUNET_TIME_Timestamp stamp_expire_deposit,
   struct GNUNET_TIME_Timestamp stamp_expire_legal,
   const struct TALER_Amount *coin_value,
-  const struct TALER_Amount *fee_withdraw,
-  const struct TALER_Amount *fee_deposit,
-  const struct TALER_Amount *fee_refresh,
-  const struct TALER_Amount *fee_refund,
+  const struct TALER_DenomFeeSet *fees,
   const struct TALER_AuditorPrivateKeyP *auditor_priv,
   struct TALER_AuditorSignatureP *auditor_sig)
 {
@@ -53,14 +50,8 @@ TALER_auditor_denom_validity_sign (
 
   TALER_amount_hton (&kv.value,
                      coin_value);
-  TALER_amount_hton (&kv.fee_withdraw,
-                     fee_withdraw);
-  TALER_amount_hton (&kv.fee_deposit,
-                     fee_deposit);
-  TALER_amount_hton (&kv.fee_refresh,
-                     fee_refresh);
-  TALER_amount_hton (&kv.fee_refund,
-                     fee_refund);
+  TALER_denom_fee_set_hton (&kv.fees,
+                            fees);
   GNUNET_CRYPTO_hash (auditor_url,
                       strlen (auditor_url) + 1,
                       &kv.auditor_url_hash);
@@ -80,10 +71,7 @@ TALER_auditor_denom_validity_verify (
   struct GNUNET_TIME_Timestamp stamp_expire_deposit,
   struct GNUNET_TIME_Timestamp stamp_expire_legal,
   const struct TALER_Amount *coin_value,
-  const struct TALER_Amount *fee_withdraw,
-  const struct TALER_Amount *fee_deposit,
-  const struct TALER_Amount *fee_refresh,
-  const struct TALER_Amount *fee_refund,
+  const struct TALER_DenomFeeSet *fees,
   const struct TALER_AuditorPublicKeyP *auditor_pub,
   const struct TALER_AuditorSignatureP *auditor_sig)
 {
@@ -100,14 +88,8 @@ TALER_auditor_denom_validity_verify (
 
   TALER_amount_hton (&kv.value,
                      coin_value);
-  TALER_amount_hton (&kv.fee_withdraw,
-                     fee_withdraw);
-  TALER_amount_hton (&kv.fee_deposit,
-                     fee_deposit);
-  TALER_amount_hton (&kv.fee_refresh,
-                     fee_refresh);
-  TALER_amount_hton (&kv.fee_refund,
-                     fee_refund);
+  TALER_denom_fee_set_hton (&kv.fees,
+                            fees);
   GNUNET_CRYPTO_hash (auditor_url,
                       strlen (auditor_url) + 1,
                       &kv.auditor_url_hash);
diff --git a/src/util/config.c b/src/util/config.c
index 8123b734..dc342fdc 100644
--- a/src/util/config.c
+++ b/src/util/config.c
@@ -58,6 +58,74 @@ TALER_config_get_amount (const struct 
GNUNET_CONFIGURATION_Handle *cfg,
 }
 
 
+enum GNUNET_GenericReturnValue
+TALER_config_get_denom_fees (const struct GNUNET_CONFIGURATION_Handle *cfg,
+                             const char *currency,
+                             const char *section,
+                             struct TALER_DenomFeeSet *fees)
+{
+  if (GNUNET_OK !=
+      TALER_config_get_amount (cfg,
+                               section,
+                               "FEE_WITHDRAW",
+                               &fees->withdraw))
+  {
+    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                               "Need amount for option `%s' in section `%s'\n",
+                               "FEE_WITHDRAW",
+                               section);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_config_get_amount (cfg,
+                               section,
+                               "FEE_DEPOSIT",
+                               &fees->deposit))
+  {
+    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                               "Need amount for option `%s' in section `%s'\n",
+                               "FEE_DEPOSIT",
+                               section);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_config_get_amount (cfg,
+                               section,
+                               "FEE_REFRESH",
+                               &fees->refresh))
+  {
+    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                               "Need amount for option `%s' in section `%s'\n",
+                               "FEE_REFRESH",
+                               section);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_config_get_amount (cfg,
+                               section,
+                               "FEE_REFUND",
+                               &fees->refund))
+  {
+    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                               "Need amount for option `%s' in section `%s'\n",
+                               "FEE_REFUND",
+                               section);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_denom_fee_check_currency (currency,
+                                      fees))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Need fee amounts in section `%s' to use currency `%s'\n",
+                section,
+                currency);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
 enum GNUNET_GenericReturnValue
 TALER_config_get_currency (const struct GNUNET_CONFIGURATION_Handle *cfg,
                            char **currency)
diff --git a/src/util/crypto.c b/src/util/crypto.c
index 6bea984f..d3f3cd3f 100644
--- a/src/util/crypto.c
+++ b/src/util/crypto.c
@@ -193,6 +193,7 @@ TALER_transfer_secret_to_planchet_secret (
 void
 TALER_planchet_secret_to_transfer_priv (
   const struct TALER_RefreshMasterSecretP *rms,
+  const struct TALER_CoinSpendPrivateKeyP *old_coin_priv,
   uint32_t cnc_num,
   struct TALER_TransferPrivateKeyP *tpriv)
 {
@@ -203,6 +204,8 @@ TALER_planchet_secret_to_transfer_priv (
                                     sizeof (*tpriv),
                                     &be_salt,
                                     sizeof (be_salt),
+                                    old_coin_priv,
+                                    sizeof (*old_coin_priv),
                                     rms,
                                     sizeof (*rms),
                                     "taler-transfer-priv-derivation",
@@ -337,6 +340,7 @@ TALER_planchet_to_coin (
 void
 TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc,
                               uint32_t kappa,
+                              const struct TALER_RefreshMasterSecretP *rms,
                               uint32_t num_new_coins,
                               const struct TALER_RefreshCommitmentEntry *rcs,
                               const struct TALER_CoinSpendPublicKeyP *coin_pub,
@@ -345,6 +349,10 @@ TALER_refresh_get_commitment (struct 
TALER_RefreshCommitmentP *rc,
   struct GNUNET_HashContext *hash_context;
 
   hash_context = GNUNET_CRYPTO_hash_context_start ();
+  if (NULL != rms)
+    GNUNET_CRYPTO_hash_context_read (hash_context,
+                                     rms,
+                                     sizeof (*rms));
   /* first, iterate over transfer public keys for hash_context */
   for (unsigned int i = 0; i<kappa; i++)
   {
@@ -391,8 +399,8 @@ TALER_refresh_get_commitment (struct 
TALER_RefreshCommitmentP *rc,
     {
       const struct TALER_RefreshCoinData *rcd = &rce->new_coins[j];
 
-      TALER_blinded_planchet_hash (&rcd->blinded_planchet,
-                                   hash_context);
+      TALER_blinded_planchet_hash_ (&rcd->blinded_planchet,
+                                    hash_context);
     }
   }
 
@@ -702,9 +710,27 @@ TALER_age_restriction_commmitment_free_inside (
     GNUNET_free (commitment->pub);
     commitment->priv = NULL;
   }
-
   /* Caller is responsible for commitment itself */
 }
 
 
+enum GNUNET_GenericReturnValue
+TALER_coin_ev_hash (const struct TALER_BlindedPlanchet *blinded_planchet,
+                    const struct TALER_DenominationHash *denom_hash,
+                    struct TALER_BlindedCoinHash *bch)
+{
+  struct GNUNET_HashContext *hash_context;
+
+  hash_context = GNUNET_CRYPTO_hash_context_start ();
+  GNUNET_CRYPTO_hash_context_read (hash_context,
+                                   denom_hash,
+                                   sizeof(*denom_hash));
+  TALER_blinded_planchet_hash_ (blinded_planchet,
+                                hash_context);
+  GNUNET_CRYPTO_hash_context_finish (hash_context,
+                                     &bch->hash);
+  return GNUNET_OK;
+}
+
+
 /* end of crypto.c */
diff --git a/src/util/denom.c b/src/util/denom.c
index 7c2c42c9..7afc7f40 100644
--- a/src/util/denom.c
+++ b/src/util/denom.c
@@ -652,8 +652,8 @@ TALER_blinded_denom_sig_cmp (
 
 
 void
-TALER_blinded_planchet_hash (const struct TALER_BlindedPlanchet *bp,
-                             struct GNUNET_HashContext *hash_context)
+TALER_blinded_planchet_hash_ (const struct TALER_BlindedPlanchet *bp,
+                              struct GNUNET_HashContext *hash_context)
 {
   uint32_t cipher = htonl (bp->cipher);
 
@@ -771,97 +771,20 @@ TALER_blinded_planchet_free (struct TALER_BlindedPlanchet 
*blinded_planchet)
 {
   switch (blinded_planchet->cipher)
   {
+  case TALER_DENOMINATION_INVALID:
+    GNUNET_break (0);
+    return;
   case TALER_DENOMINATION_RSA:
     GNUNET_free (blinded_planchet->details.rsa_blinded_planchet.blinded_msg);
-    break;
+    return;
   case TALER_DENOMINATION_CS:
     memset (blinded_planchet,
             0,
             sizeof (*blinded_planchet));
     /* nothing to do for CS */
-    break;
-  default:
-    GNUNET_break (0);
-  }
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_coin_ev_hash (const struct TALER_BlindedPlanchet *blinded_planchet,
-                    const struct TALER_DenominationHash *denom_hash,
-                    struct TALER_BlindedCoinHash *bch)
-{
-  struct GNUNET_HashContext *hash_context;
-
-  hash_context = GNUNET_CRYPTO_hash_context_start ();
-  GNUNET_CRYPTO_hash_context_read (hash_context,
-                                   denom_hash,
-                                   sizeof(*denom_hash));
-  switch (blinded_planchet->cipher)
-  {
-  case TALER_DENOMINATION_RSA:
-    GNUNET_CRYPTO_hash_context_read (
-      hash_context,
-      blinded_planchet->details.rsa_blinded_planchet.blinded_msg,
-      blinded_planchet->details.rsa_blinded_planchet.blinded_msg_size);
-    break;
-  case TALER_DENOMINATION_CS:
-    // FIXME: c-values MUST NOT be included in idempotency check
-    // during withdraw (or recoup), but right now they are!!!
-    GNUNET_CRYPTO_hash_context_read (
-      hash_context,
-      &blinded_planchet->details.cs_blinded_planchet.c[0],
-      sizeof (struct GNUNET_CRYPTO_CsC) * 2);
-    GNUNET_CRYPTO_hash_context_read (
-      hash_context,
-      &blinded_planchet->details.cs_blinded_planchet.nonce,
-      sizeof (struct TALER_CsNonce));
-    break;
-  default:
-    GNUNET_break (0);
-    GNUNET_CRYPTO_hash_context_abort (hash_context);
-    return GNUNET_SYSERR;
-  }
-  GNUNET_CRYPTO_hash_context_finish (hash_context,
-                                     &bch->hash);
-  return GNUNET_OK;
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_withdraw_request_hash (
-  const struct TALER_BlindedPlanchet *blinded_planchet,
-  const struct TALER_DenominationHash *denom_hash,
-  struct TALER_WithdrawIdentificationHash *wih)
-{
-  struct GNUNET_HashContext *hash_context;
-
-  hash_context = GNUNET_CRYPTO_hash_context_start ();
-  GNUNET_CRYPTO_hash_context_read (hash_context,
-                                   denom_hash,
-                                   sizeof(*denom_hash));
-  switch (blinded_planchet->cipher)
-  {
-  case TALER_DENOMINATION_RSA:
-    GNUNET_CRYPTO_hash_context_read (
-      hash_context,
-      blinded_planchet->details.rsa_blinded_planchet.blinded_msg,
-      blinded_planchet->details.rsa_blinded_planchet.blinded_msg_size);
-    break;
-  case TALER_DENOMINATION_CS:
-    GNUNET_CRYPTO_hash_context_read (
-      hash_context,
-      &blinded_planchet->details.cs_blinded_planchet.nonce,
-      sizeof (struct TALER_CsNonce));
-    break;
-  default:
-    GNUNET_break (0);
-    GNUNET_CRYPTO_hash_context_abort (hash_context);
-    return GNUNET_SYSERR;
+    return;
   }
-  GNUNET_CRYPTO_hash_context_finish (hash_context,
-                                     &wih->hash);
-  return GNUNET_OK;
+  GNUNET_assert (0);
 }
 
 
diff --git a/src/util/offline_signatures.c b/src/util/offline_signatures.c
index ab298834..fa4b80fe 100644
--- a/src/util/offline_signatures.c
+++ b/src/util/offline_signatures.c
@@ -255,10 +255,7 @@ TALER_exchange_offline_denom_validity_sign (
   struct GNUNET_TIME_Timestamp stamp_expire_deposit,
   struct GNUNET_TIME_Timestamp stamp_expire_legal,
   const struct TALER_Amount *coin_value,
-  const struct TALER_Amount *fee_withdraw,
-  const struct TALER_Amount *fee_deposit,
-  const struct TALER_Amount *fee_refresh,
-  const struct TALER_Amount *fee_refund,
+  const struct TALER_DenomFeeSet *fees,
   const struct TALER_MasterPrivateKeyP *master_priv,
   struct TALER_MasterSignatureP *master_sig)
 {
@@ -278,14 +275,8 @@ TALER_exchange_offline_denom_validity_sign (
                                       &issue.master.eddsa_pub);
   TALER_amount_hton (&issue.value,
                      coin_value);
-  TALER_amount_hton (&issue.fee_withdraw,
-                     fee_withdraw);
-  TALER_amount_hton (&issue.fee_deposit,
-                     fee_deposit);
-  TALER_amount_hton (&issue.fee_refresh,
-                     fee_refresh);
-  TALER_amount_hton (&issue.fee_refund,
-                     fee_refund);
+  TALER_denom_fee_set_hton (&issue.fees,
+                            fees);
   GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
                             &issue,
                             &master_sig->eddsa_signature);
@@ -300,10 +291,7 @@ TALER_exchange_offline_denom_validity_verify (
   struct GNUNET_TIME_Timestamp stamp_expire_deposit,
   struct GNUNET_TIME_Timestamp stamp_expire_legal,
   const struct TALER_Amount *coin_value,
-  const struct TALER_Amount *fee_withdraw,
-  const struct TALER_Amount *fee_deposit,
-  const struct TALER_Amount *fee_refresh,
-  const struct TALER_Amount *fee_refund,
+  const struct TALER_DenomFeeSet *fees,
   const struct TALER_MasterPublicKeyP *master_pub,
   const struct TALER_MasterSignatureP *master_sig)
 {
@@ -321,14 +309,8 @@ TALER_exchange_offline_denom_validity_verify (
 
   TALER_amount_hton (&dkv.value,
                      coin_value);
-  TALER_amount_hton (&dkv.fee_withdraw,
-                     fee_withdraw);
-  TALER_amount_hton (&dkv.fee_deposit,
-                     fee_deposit);
-  TALER_amount_hton (&dkv.fee_refresh,
-                     fee_refresh);
-  TALER_amount_hton (&dkv.fee_refund,
-                     fee_refund);
+  TALER_denom_fee_set_hton (&dkv.fees,
+                            fees);
   return
     GNUNET_CRYPTO_eddsa_verify (
     TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY,
diff --git a/src/util/util.c b/src/util/util.c
index 2ff295b0..5b7181a1 100644
--- a/src/util/util.c
+++ b/src/util/util.c
@@ -46,6 +46,73 @@ TALER_b2s (const void *buf,
 }
 
 
+void
+TALER_denom_fee_set_hton (struct TALER_DenomFeeSetNBOP *nbo,
+                          const struct TALER_DenomFeeSet *fees)
+{
+  TALER_amount_hton (&nbo->withdraw,
+                     &fees->withdraw);
+  TALER_amount_hton (&nbo->deposit,
+                     &fees->deposit);
+  TALER_amount_hton (&nbo->refresh,
+                     &fees->refresh);
+  TALER_amount_hton (&nbo->refund,
+                     &fees->refund);
+}
+
+
+void
+TALER_denom_fee_set_ntoh (struct TALER_DenomFeeSet *fees,
+                          const struct TALER_DenomFeeSetNBOP *nbo)
+{
+  TALER_amount_ntoh (&fees->withdraw,
+                     &nbo->withdraw);
+  TALER_amount_ntoh (&fees->deposit,
+                     &nbo->deposit);
+  TALER_amount_ntoh (&fees->refresh,
+                     &nbo->refresh);
+  TALER_amount_ntoh (&fees->refund,
+                     &nbo->refund);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_denom_fee_check_currency (
+  const char *currency,
+  const struct TALER_DenomFeeSet *fees)
+{
+  if (GNUNET_YES !=
+      TALER_amount_is_currency (&fees->withdraw,
+                                currency))
+  {
+    GNUNET_break (0);
+    return GNUNET_NO;
+  }
+  if (GNUNET_YES !=
+      TALER_amount_is_currency (&fees->deposit,
+                                currency))
+  {
+    GNUNET_break (0);
+    return GNUNET_NO;
+  }
+  if (GNUNET_YES !=
+      TALER_amount_is_currency (&fees->refresh,
+                                currency))
+  {
+    GNUNET_break (0);
+    return GNUNET_NO;
+  }
+  if (GNUNET_YES !=
+      TALER_amount_is_currency (&fees->refund,
+                                currency))
+  {
+    GNUNET_break (0);
+    return GNUNET_NO;
+  }
+  return GNUNET_OK;
+}
+
+
 #ifdef __APPLE__
 char *
 strchrnul (const char *s,

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