gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] 01/02: -fix CS nonce reuse check logic


From: gnunet
Subject: [taler-exchange] 01/02: -fix CS nonce reuse check logic
Date: Thu, 17 Feb 2022 15:18:16 +0100

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

grothoff pushed a commit to branch master
in repository exchange.

commit a351bfc4b4ca15ce7fd998cf9691e85cf84dc426
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Thu Feb 17 15:10:14 2022 +0100

    -fix CS nonce reuse check logic
---
 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 ++--
 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 ++++
 56 files changed, 1387 insertions(+), 1026 deletions(-)

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%
rename from src/lib/exchange_api_csr.c
rename 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_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]