View Issue Details

IDProjectCategoryView StatusLast Update
0006416Talerauditorpublic2020-07-10 23:24
ReporteroecAssigned ToChristian Grothoff 
PrioritynormalSeverityminorReproducibilityalways
Status resolvedResolutionfixed 
Product Version0.7.0 
Target Version0.8Fixed in Version0.8 
Summary0006416: Same coin_pub with multiple denom_sigs - a problem?
DescriptionTaler uses a cache for fast lookups of coin_pub -> (denom_pub, denom_sig), the table known_coins. The table is populated via TEH_DB_know_coin_transaction before deposit, melt and recoup operations, i.e. independent of the outcome of those operations.

Consider the scenario where the same coin_pub is signed with different denomination keys. The first usage of one of those coins would lock the denomination value in the known_coins table. However, it is not clear (to me) what would happen if the same coin_pub than is used later with a _different_ (but also validly signed) denomination for any of the operations.

I have not come up with a particular attack to the advantage of a customer (i.e. gain profit). But maybe leaving the exchange in a confused state that the auditor might notice and complain about could lead to DoS?

Would it make sense to have (coin_pub, denom_pub) as an index for the known_coins and allow multiple entries with the same coin_pub in it?
TagsNo tags attached.

Activities

Christian Grothoff

2020-06-25 11:48

manager   ~0016366

Great find. Discussed with Florian, we will change the index to span coin_pub and denom_h and make sure all SELECT()s also select on both columns.

Christian Grothoff

2020-07-07 00:28

manager   ~0016391

Proposed DB change attached. May be incomplete (see FIXMEs), also requires modest changes to the rest of the exchange code.

db.patch (85,066 bytes)
diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c
index 2e8f0a02..929f3c15 100644
--- a/src/exchange/taler-exchange-httpd_db.c
+++ b/src/exchange/taler-exchange-httpd_db.c
@@ -66,7 +66,8 @@ TEH_DB_know_coin_transaction (void *cls,
   GNUNET_assert (NULL != mhd_ret);
   qs = TEH_plugin->ensure_coin_known (TEH_plugin->cls,
                                       session,
-                                      kcc->coin);
+                                      kcc->coin,
+                                      &kcc->known_coin_id);
   if (GNUNET_DB_STATUS_HARD_ERROR == qs)
   {
     *mhd_ret
diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c
index 95143758..a17e6a18 100644
--- a/src/exchange/taler-exchange-httpd_deposit.c
+++ b/src/exchange/taler-exchange-httpd_deposit.c
@@ -121,6 +121,11 @@ struct DepositContext
    */
   struct TALER_Amount value;
 
+  /**
+   * ID of the coin in the database.
+   */
+  uint64_t coin_id;
+
 };
 
 
@@ -237,7 +242,7 @@ deposit_transaction (void *cls,
 
     qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
                                             session,
-                                            &deposit->coin.coin_pub,
+                                            dc->coin_id,
                                             GNUNET_NO,
                                             &tl);
     if (0 > qs)
@@ -271,11 +276,11 @@ deposit_transaction (void *cls,
     {
       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                   "Deposited coin has insufficient funds left!\n");
-      *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (connection,
-                                                             TALER_EC_DEPOSIT_INSUFFICIENT_FUNDS,
-                                                             &deposit->coin.
-                                                             coin_pub,
-                                                             tl);
+      *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
+        connection,
+        TALER_EC_DEPOSIT_INSUFFICIENT_FUNDS,
+        &deposit->coin.coin_pub,
+        tl);
       TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
                                               tl);
       return GNUNET_DB_STATUS_HARD_ERROR;
@@ -286,6 +291,7 @@ deposit_transaction (void *cls,
   qs = TEH_plugin->insert_deposit (TEH_plugin->cls,
                                    session,
                                    dc->exchange_timestamp,
+                                   dc->coin_id,
                                    deposit);
   if (GNUNET_DB_STATUS_HARD_ERROR == qs)
   {
@@ -511,6 +517,7 @@ TEH_handler_deposit (struct MHD_Connection *connection,
       GNUNET_JSON_parse_free (spec);
       return mhd_ret;
     }
+    dc.coin_id = kcc.known_coin_id;
   }
 
   /* check deposit signature */
diff --git a/src/exchange/taler-exchange-httpd_melt.c b/src/exchange/taler-exchange-httpd_melt.c
index 3a0195cf..e13e6775 100644
--- a/src/exchange/taler-exchange-httpd_melt.c
+++ b/src/exchange/taler-exchange-httpd_melt.c
@@ -150,6 +150,11 @@ struct MeltContext
    */
   struct TALER_Amount coin_refresh_fee;
 
+  /**
+   * Unique ID of the coin this melt is about.
+   */
+  uint64_t coin_id;
+
   /**
    * Set to #GNUNET_YES if this coin's denomination was revoked and the operation
    * is thus only allowed for zombie coins where the transaction
@@ -187,7 +192,7 @@ refresh_check_melt (struct MHD_Connection *connection,
      we might be a zombie coin */
   qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
                                           session,
-                                          &rmc->refresh_session.coin.coin_pub,
+                                          rmc->coin_id,
                                           GNUNET_YES,
                                           &tl);
   if (0 > qs)
@@ -343,6 +348,7 @@ melt_transaction (void *cls,
   if (0 >=
       (qs = TEH_plugin->insert_melt (TEH_plugin->cls,
                                      session,
+                                     rmc->coin_id,
                                      &rmc->refresh_session)))
   {
     if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
@@ -583,6 +589,7 @@ check_for_denomination_key (struct MHD_Connection *connection,
                                 &TEH_DB_know_coin_transaction,
                                 &kcc))
       return mhd_ret;
+    rmc->coin_id = kcc.known_coin_id;
   }
 
   /* sanity-check that "total melt amount > melt fee" */
diff --git a/src/exchange/taler-exchange-httpd_recoup.c b/src/exchange/taler-exchange-httpd_recoup.c
index c1f6ff33..52a59356 100644
--- a/src/exchange/taler-exchange-httpd_recoup.c
+++ b/src/exchange/taler-exchange-httpd_recoup.c
@@ -93,6 +93,11 @@ struct RecoupContext
    */
   struct GNUNET_TIME_Absolute now;
 
+  /**
+   * Which coin is this about?
+   */
+  uint64_t coin_id;
+
   /**
    * #GNUNET_YES if the client claims the coin originated from a refresh.
    */
@@ -187,7 +192,7 @@ recoup_transaction (void *cls,
   /* Calculate remaining balance, including recoups already applied. */
   qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
                                           session,
-                                          &pc->coin->coin_pub,
+                                          pc->coin_id,
                                           GNUNET_YES,
                                           &tl);
   if (0 > qs)
@@ -293,6 +298,7 @@ recoup_transaction (void *cls,
   {
     qs = TEH_plugin->insert_recoup_refresh_request (TEH_plugin->cls,
                                                     session,
+                                                    pc->coin_id,
                                                     pc->coin,
                                                     pc->coin_sig,
                                                     pc->coin_bks,
@@ -305,6 +311,7 @@ recoup_transaction (void *cls,
     qs = TEH_plugin->insert_recoup_request (TEH_plugin->cls,
                                             session,
                                             &pc->target.reserve_pub,
+                                            pc->coin_id,
                                             pc->coin,
                                             pc->coin_sig,
                                             pc->coin_bks,
@@ -343,12 +350,12 @@ recoup_transaction (void *cls,
  * @return MHD result code
  */
 static MHD_RESULT
-verify_and_execute_recoup (struct MHD_Connection *connection,
-                           const struct TALER_CoinPublicInfo *coin,
-                           const struct
-                           TALER_DenominationBlindingKeyP *coin_bks,
-                           const struct TALER_CoinSpendSignatureP *coin_sig,
-                           int refreshed)
+verify_and_execute_recoup (
+  struct MHD_Connection *connection,
+  const struct TALER_CoinPublicInfo *coin,
+  const struct TALER_DenominationBlindingKeyP *coin_bks,
+  const struct TALER_CoinSpendSignatureP *coin_sig,
+  int refreshed)
 {
   struct RecoupContext pc;
   const struct TALER_EXCHANGEDB_DenominationKey *dki;
@@ -464,6 +471,7 @@ verify_and_execute_recoup (struct MHD_Connection *connection,
                                 &TEH_DB_know_coin_transaction,
                                 &kcc))
       return mhd_ret;
+    pc.coin_id = kcc.known_coin_id;
   }
 
   /* Perform actual recoup transaction */
diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql
index ad05e779..31c6c02d 100644
--- a/src/exchangedb/exchange-0001.sql
+++ b/src/exchangedb/exchange-0001.sql
@@ -168,9 +168,11 @@ CREATE INDEX IF NOT EXISTS reserves_out_for_get_withdraw_info
 
 
 CREATE TABLE IF NOT EXISTS known_coins
-  (coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (LENGTH(coin_pub)=32)
+  (known_coin_id BIGSERIAL PRIMARY KEY
+  ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)
   ,denom_pub_hash BYTEA NOT NULL REFERENCES denominations (denom_pub_hash) ON DELETE CASCADE
-  ,denom_sig BYTEA NOT NULL
+  ,denom_sig BYTEA NOT NULL,
+  UNIQUE (coin_pub, denom_pub_hash)
   );
 COMMENT ON TABLE known_coins
   IS 'information about coins and their signatures, so we do not have to store the signatures more than once if a coin is involved in multiple operations';
@@ -183,7 +185,7 @@ CREATE INDEX IF NOT EXISTS known_coins_by_denomination
 CREATE TABLE IF NOT EXISTS refresh_commitments
   (melt_serial_id BIGSERIAL UNIQUE
   ,rc BYTEA PRIMARY KEY CHECK (LENGTH(rc)=64)
-  ,old_coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE
+  ,old_known_coin_id INT8 NOT NULL REFERENCES known_coins (known_coin_id) ON DELETE CASCADE
   ,old_coin_sig BYTEA NOT NULL CHECK(LENGTH(old_coin_sig)=64)
   ,amount_with_fee_val INT8 NOT NULL
   ,amount_with_fee_frac INT4 NOT NULL
@@ -192,9 +194,9 @@ CREATE TABLE IF NOT EXISTS refresh_commitments
 COMMENT ON TABLE refresh_commitments
   IS 'Commitments made when melting coins and the gamma value chosen by the exchange.';
 
-CREATE INDEX IF NOT EXISTS refresh_commitments_old_coin_pub_index
+CREATE INDEX IF NOT EXISTS refresh_commitments_old_coin_index
   ON refresh_commitments
-  (old_coin_pub);
+  (old_known_coin_id);
 
 
 CREATE TABLE IF NOT EXISTS refresh_revealed_coins
@@ -221,7 +223,7 @@ COMMENT ON COLUMN refresh_revealed_coins.h_coin_ev
 COMMENT ON COLUMN refresh_revealed_coins.ev_sig
   IS 'exchange signature over the envelope';
 
-CREATE INDEX IF NOT EXISTS refresh_revealed_coins_coin_pub_index
+CREATE INDEX IF NOT EXISTS refresh_revealed_coins_denom_index
   ON refresh_revealed_coins
   (denom_pub_hash);
 
@@ -251,7 +253,7 @@ COMMENT ON INDEX refresh_transfer_keys_coin_tpub
 
 CREATE TABLE IF NOT EXISTS deposits
   (deposit_serial_id BIGSERIAL PRIMARY KEY
-  ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE
+  ,known_coin_id INT8 NOT NULL REFERENCES known_coins (known_coin_id) ON DELETE CASCADE
   ,amount_with_fee_val INT8 NOT NULL
   ,amount_with_fee_frac INT4 NOT NULL
   ,wallet_timestamp INT8 NOT NULL
@@ -265,7 +267,7 @@ CREATE TABLE IF NOT EXISTS deposits
   ,wire TEXT NOT NULL
   ,tiny BOOLEAN NOT NULL DEFAULT FALSE
   ,done BOOLEAN NOT NULL DEFAULT FALSE
-  ,UNIQUE (coin_pub, merchant_pub, h_contract_terms)
+  ,UNIQUE (known_coin_id, merchant_pub, h_contract_terms)
   );
 COMMENT ON TABLE deposits
   IS 'Deposits we have received and for which we need to make (aggregate) wire transfers (and manage refunds).';
@@ -274,13 +276,13 @@ COMMENT ON COLUMN deposits.done
 COMMENT ON COLUMN deposits.tiny
   IS 'Set to TRUE if we decided that the amount is too small to ever trigger a wire transfer by itself (requires real aggregation)';
 
-CREATE INDEX IF NOT EXISTS deposits_coin_pub_merchant_contract_index
+CREATE INDEX IF NOT EXISTS deposits_coin_merchant_contract_index
   ON deposits
-  (coin_pub
+  (known_coin_id
   ,merchant_pub
   ,h_contract_terms
   );
-COMMENT ON INDEX deposits_coin_pub_merchant_contract_index
+COMMENT ON INDEX deposits_coin_merchant_contract_index
   IS 'for get_deposit_for_wtid and test_deposit_done';
 CREATE INDEX IF NOT EXISTS deposits_get_ready_index
   ON deposits
@@ -289,7 +291,7 @@ CREATE INDEX IF NOT EXISTS deposits_get_ready_index
   ,wire_deadline
   ,refund_deadline
   );
-COMMENT ON INDEX deposits_coin_pub_merchant_contract_index
+COMMENT ON INDEX deposits_get_ready_index
   IS 'for deposits_get_ready';
 CREATE INDEX IF NOT EXISTS deposits_iterate_matching_index
   ON deposits
@@ -304,23 +306,23 @@ COMMENT ON INDEX deposits_iterate_matching_index
 
 CREATE TABLE IF NOT EXISTS refunds
   (refund_serial_id BIGSERIAL UNIQUE
-  ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE
+  ,known_coin_id INT8 NOT NULL REFERENCES known_coins (known_coin_id) ON DELETE CASCADE
   ,merchant_pub BYTEA NOT NULL CHECK(LENGTH(merchant_pub)=32)
   ,merchant_sig BYTEA NOT NULL CHECK(LENGTH(merchant_sig)=64)
   ,h_contract_terms BYTEA NOT NULL CHECK(LENGTH(h_contract_terms)=64)
   ,rtransaction_id INT8 NOT NULL
   ,amount_with_fee_val INT8 NOT NULL
   ,amount_with_fee_frac INT4 NOT NULL
-  ,PRIMARY KEY (coin_pub, merchant_pub, h_contract_terms, rtransaction_id)
+  ,PRIMARY KEY (known_coin_id, merchant_pub, h_contract_terms, rtransaction_id)
   );
 COMMENT ON TABLE refunds
-  IS 'Data on coins that were refunded. Technically, refunds always apply against specific deposit operations involving a coin. The combination of coin_pub, merchant_pub, h_contract_terms and rtransaction_id MUST be unique, and we usually select by coin_pub so that one goes first.';
+  IS 'Data on coins that were refunded. Technically, refunds always apply against specific deposit operations involving a coin. The combination of known_coin_id, merchant_pub, h_contract_terms and rtransaction_id MUST be unique, and we usually select by known_coin_id so that one goes first.';
 COMMENT ON COLUMN refunds.rtransaction_id
   IS 'used by the merchant to make refunds unique in case the same coin for the same deposit gets a subsequent (higher) refund';
 
-CREATE INDEX IF NOT EXISTS refunds_coin_pub_index
+CREATE INDEX IF NOT EXISTS refunds_coin_index
   ON refunds
-  (coin_pub);
+  (known_coin_id);
 
 
 CREATE TABLE IF NOT EXISTS wire_out
@@ -372,7 +374,7 @@ CREATE INDEX IF NOT EXISTS wire_fee_gc_index
 
 CREATE TABLE IF NOT EXISTS recoup
   (recoup_uuid BIGSERIAL UNIQUE
-  ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub)
+  ,known_coin_id INT8 NOT NULL REFERENCES known_coins (known_coin_id) ON DELETE CASCADE
   ,coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)
   ,coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32)
   ,amount_val INT8 NOT NULL
@@ -382,25 +384,25 @@ CREATE TABLE IF NOT EXISTS recoup
   );
 COMMENT ON TABLE recoup
   IS 'Information about recoups that were executed';
-COMMENT ON COLUMN recoup.coin_pub
-  IS 'Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!';
+COMMENT ON COLUMN recoup.known_coin_id
+  IS 'Do not CASCADE ON DROP on the known_coin_id, as we may keep the coin alive!';
 
 CREATE INDEX IF NOT EXISTS recoup_by_coin_index
   ON recoup
-  (coin_pub);
+  (known_coin_id);
 CREATE INDEX IF NOT EXISTS recoup_by_h_blind_ev
   ON recoup
   (h_blind_ev);
 CREATE INDEX IF NOT EXISTS recoup_for_by_reserve
   ON recoup
-  (coin_pub
+  (known_coin_id
   ,h_blind_ev
   );
 
 
 CREATE TABLE IF NOT EXISTS recoup_refresh
   (recoup_refresh_uuid BIGSERIAL UNIQUE
-  ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub)
+  ,known_coin_id INT8 NOT NULL REFERENCES known_coins (known_coin_id) ON DELETE CASCADE
   ,coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)
   ,coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32)
   ,amount_val INT8 NOT NULL
@@ -408,18 +410,18 @@ CREATE TABLE IF NOT EXISTS recoup_refresh
   ,timestamp INT8 NOT NULL
   ,h_blind_ev BYTEA NOT NULL REFERENCES refresh_revealed_coins (h_coin_ev) ON DELETE CASCADE
   );
-COMMENT ON COLUMN recoup_refresh.coin_pub
-  IS 'Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!';
+COMMENT ON COLUMN recoup_refresh.known_coin_id
+  IS 'Do not CASCADE ON DROP on the known_coin_id, as we may keep the coin alive!';
 
 CREATE INDEX IF NOT EXISTS recoup_refresh_by_coin_index
   ON recoup_refresh
-  (coin_pub);
+  (known_coin_id);
 CREATE INDEX IF NOT EXISTS recoup_refresh_by_h_blind_ev
   ON recoup_refresh
   (h_blind_ev);
 CREATE INDEX IF NOT EXISTS recoup_refresh_for_by_reserve
   ON recoup_refresh
-  (coin_pub
+  (known_coin_id
   ,h_blind_ev
   );
 
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c
index dede901f..6e25e496 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -556,17 +556,16 @@ postgres_get_session (void *cls)
                               " FROM known_coins"
                               " WHERE denom_pub_hash=$1;",
                               1),
-      /* Used in #postgres_get_known_coin() to fetch
-         the denomination public key and signature for
-         a coin known to the exchange. */
-      GNUNET_PQ_make_prepare ("get_known_coin",
+      /* Used in #postgres_check_known_coin() to fetch
+         the known_coin_id for a coin known to the exchange. */
+      GNUNET_PQ_make_prepare ("check_known_coin",
                               "SELECT"
-                              " denom_pub_hash"
-                              ",denom_sig"
+                              " known_coin_id"
                               " FROM known_coins"
                               " WHERE coin_pub=$1"
+                              "   AND denom_pub_hash=$2"
                               " FOR UPDATE;",
-                              1),
+                              2),
       /* Used in #postgres_get_coin_denomination() to fetch
          the denomination public key hash for
          a coin known to the exchange. */
@@ -592,7 +591,8 @@ postgres_get_session (void *cls)
                               ",denom_pub_hash"
                               ",denom_sig"
                               ") VALUES "
-                              "($1,$2,$3);",
+                              "($1,$2,$3) "
+                              "RETURNING known_coin_id;",
                               3),
 
       /* Used in #postgres_insert_melt() to store
@@ -600,7 +600,7 @@ postgres_get_session (void *cls)
       GNUNET_PQ_make_prepare ("insert_melt",
                               "INSERT INTO refresh_commitments "
                               "(rc "
-                              ",old_coin_pub "
+                              ",old_known_coin_id "
                               ",old_coin_sig "
                               ",amount_with_fee_val "
                               ",amount_with_fee_frac "
@@ -615,14 +615,14 @@ postgres_get_session (void *cls)
                               " kc.denom_pub_hash"
                               ",denom.fee_refresh_val"
                               ",denom.fee_refresh_frac"
-                              ",old_coin_pub"
+                              ",kc.coin_pub AS old_coin_pub"
                               ",old_coin_sig"
                               ",amount_with_fee_val"
                               ",amount_with_fee_frac"
                               ",noreveal_index"
                               " FROM refresh_commitments"
                               "   JOIN known_coins kc"
-                              "     ON (refresh_commitments.old_coin_pub = kc.coin_pub)"
+                              "     ON (refresh_commitments.old_known_coin_id = kc.known_coin_id)"
                               "   JOIN denominations denom"
                               "     ON (kc.denom_pub_hash = denom.denom_pub_hash)"
                               " WHERE rc=$1;",
@@ -640,7 +640,7 @@ postgres_get_session (void *cls)
       GNUNET_PQ_make_prepare ("audit_get_refresh_commitments_incr",
                               "SELECT"
                               " denom.denom_pub"
-                              ",old_coin_pub"
+                              ",kc.coin_pub AS old_coin_pub"
                               ",old_coin_sig"
                               ",amount_with_fee_val"
                               ",amount_with_fee_frac"
@@ -649,7 +649,7 @@ postgres_get_session (void *cls)
                               ",rc"
                               " FROM refresh_commitments"
                               "   JOIN known_coins kc"
-                              "     ON (refresh_commitments.old_coin_pub = kc.coin_pub)"
+                              "     ON (refresh_commitments.old_known_coin_id = kc.known_coin_id)"
                               "   JOIN denominations denom"
                               "     ON (kc.denom_pub_hash = denom.denom_pub_hash)"
                               " WHERE melt_serial_id>=$1"
@@ -667,9 +667,9 @@ postgres_get_session (void *cls)
                               ",melt_serial_id"
                               " FROM refresh_commitments"
                               "    JOIN known_coins "
-                              "      ON (refresh_commitments.old_coin_pub = known_coins.coin_pub)"
+                              "      ON (refresh_commitments.old_known_coin_id = known_coins.known_coin_id)"
                               "    JOIN denominations denom USING (denom_pub_hash)"
-                              " WHERE old_coin_pub=$1;",
+                              " WHERE old_known_coin_id=$1;",
                               1),
 
       /* Store information about the desired denominations for a
@@ -727,7 +727,7 @@ postgres_get_session (void *cls)
       /* Used in #postgres_insert_refund() to store refund information */
       GNUNET_PQ_make_prepare ("insert_refund",
                               "INSERT INTO refunds "
-                              "(coin_pub "
+                              "(known_coin_id "
                               ",merchant_pub "
                               ",merchant_sig "
                               ",h_contract_terms "
@@ -750,9 +750,9 @@ postgres_get_session (void *cls)
                               ",denom.fee_refund_frac "
                               ",refund_serial_id"
                               " FROM refunds"
-                              "    JOIN known_coins USING (coin_pub)"
+                              "    JOIN known_coins USING (known_coin_id)"
                               "    JOIN denominations denom USING (denom_pub_hash)"
-                              " WHERE coin_pub=$1;",
+                              " WHERE known_coin_id=$1;",
                               1),
       /* Query the 'refunds' by coin public key, merchant_pub and contract hash */
       GNUNET_PQ_make_prepare ("get_refunds_by_coin_and_contract",
@@ -760,8 +760,7 @@ postgres_get_session (void *cls)
                               " amount_with_fee_val"
                               ",amount_with_fee_frac"
                               " FROM refunds"
-                              " WHERE"
-                              "       coin_pub=$1"
+                              " WHERE known_coin_id=$1"
                               "   AND merchant_pub=$2"
                               "   AND h_contract_terms=$3;",
                               3),
@@ -778,7 +777,7 @@ postgres_get_session (void *cls)
                               ",amount_with_fee_frac"
                               ",refund_serial_id"
                               " FROM refunds"
-                              "   JOIN known_coins kc USING (coin_pub)"
+                              "   JOIN known_coins kc USING (known_coin_id)"
                               "   JOIN denominations denom ON (kc.denom_pub_hash = denom.denom_pub_hash)"
                               " WHERE refund_serial_id>=$1"
                               " ORDER BY refund_serial_id ASC;",
@@ -793,7 +792,7 @@ postgres_get_session (void *cls)
          Used in #postgres_insert_deposit(). */
       GNUNET_PQ_make_prepare ("insert_deposit",
                               "INSERT INTO deposits "
-                              "(coin_pub"
+                              "(known_coin_id"
                               ",amount_with_fee_val"
                               ",amount_with_fee_frac"
                               ",wallet_timestamp"
@@ -824,9 +823,9 @@ postgres_get_session (void *cls)
                               ",h_contract_terms"
                               ",h_wire"
                               " FROM deposits"
-                              " JOIN known_coins USING (coin_pub)"
+                              " JOIN known_coins USING (known_coin_id)"
                               " JOIN denominations USING (denom_pub_hash)"
-                              " WHERE ((coin_pub=$1)"
+                              " WHERE ((known_coin_id=$1)"
                               "    AND (merchant_pub=$3)"
                               "    AND (h_contract_terms=$2))"
                               " FOR UPDATE;",
@@ -849,7 +848,7 @@ postgres_get_session (void *cls)
                               ",done"
                               ",deposit_serial_id"
                               " FROM deposits"
-                              "    JOIN known_coins USING (coin_pub)"
+                              "    JOIN known_coins USING (known_coin_id)"
                               "    JOIN denominations denom USING (denom_pub_hash)"
                               " WHERE ("
                               "  (deposit_serial_id>=$1)"
@@ -866,14 +865,12 @@ postgres_get_session (void *cls)
                               ",denom.fee_deposit_frac"
                               ",wire_deadline"
                               " FROM deposits"
-                              "    JOIN known_coins USING (coin_pub)"
+                              "    JOIN known_coins USING (known_coin_id)"
                               "    JOIN denominations denom USING (denom_pub_hash)"
-                              " WHERE ("
-                              "      (coin_pub=$1)"
+                              " WHERE  (coin_pub=$1)"
                               "    AND (merchant_pub=$2)"
                               "    AND (h_contract_terms=$3)"
-                              "    AND (h_wire=$4)"
-                              " );",
+                              "    AND (h_wire=$4);",
                               4),
       /* Used in #postgres_get_ready_deposit() */
       GNUNET_PQ_make_prepare ("deposits_get_ready",
@@ -887,11 +884,11 @@ postgres_get_session (void *cls)
                               ",h_contract_terms"
                               ",wire"
                               ",merchant_pub"
-                              ",coin_pub"
+                              ",coin_pub" // FIXME: probably should also return known_coin_id!
                               ",exchange_timestamp"
                               ",wallet_timestamp"
                               " FROM deposits"
-                              "    JOIN known_coins USING (coin_pub)"
+                              "    JOIN known_coins USING (known_coin_id)"
                               "    JOIN denominations denom USING (denom_pub_hash)"
                               " WHERE tiny=FALSE"
                               "    AND done=FALSE"
@@ -915,7 +912,7 @@ postgres_get_session (void *cls)
                               ",coin_pub"
                               " FROM deposits"
                               "    JOIN known_coins"
-                              "      USING (coin_pub)"
+                              "      USING (known_coin_id)"
                               "    JOIN denominations denom"
                               "      USING (denom_pub_hash)"
                               " WHERE"
@@ -943,7 +940,7 @@ postgres_get_session (void *cls)
       GNUNET_PQ_make_prepare ("test_deposit_done",
                               "SELECT done"
                               " FROM deposits"
-                              " WHERE coin_pub=$1"
+                              " WHERE known_coin_id=$1"
                               "   AND merchant_pub=$2"
                               "   AND h_contract_terms=$3"
                               "   AND h_wire=$4;",
@@ -967,10 +964,10 @@ postgres_get_session (void *cls)
                               ",deposit_serial_id"
                               " FROM deposits"
                               "    JOIN known_coins"
-                              "      USING (coin_pub)"
+                              "      USING (known_coin_id)"
                               "    JOIN denominations denom"
                               "      USING (denom_pub_hash)"
-                              " WHERE coin_pub=$1"
+                              " WHERE known_coin_id=$1"
                               " FOR UPDATE;",
                               1),
 
@@ -988,7 +985,9 @@ postgres_get_session (void *cls)
                               "       USING (rc)"
                               "     JOIN denominations denoms"
                               "       ON (rrc.denom_pub_hash = denoms.denom_pub_hash)"
-                              " WHERE old_coin_pub=$1"
+                              "     JOIN known_coins kc"
+                              "       ON (old_known_coin_id = kc.known_coin_id)"
+                              " WHERE kc.coin_pub=$1"
                               " ORDER BY tp.transfer_pub",
                               1),
       /* Used in #postgres_lookup_wire_transfer */
@@ -998,7 +997,7 @@ postgres_get_session (void *cls)
                               ",deposits.h_contract_terms"
                               ",deposits.wire"
                               ",deposits.h_wire"
-                              ",deposits.coin_pub"
+                              ",known_coins.coin_pub"
                               ",deposits.merchant_pub"
                               ",wire_out.execution_date"
                               ",deposits.amount_with_fee_val"
@@ -1010,7 +1009,7 @@ postgres_get_session (void *cls)
                               "    JOIN deposits"
                               "      USING (deposit_serial_id)"
                               "    JOIN known_coins"
-                              "      USING (coin_pub)"
+                              "      USING (known_coin_id)"
                               "    JOIN denominations denom"
                               "      USING (denom_pub_hash)"
                               "    JOIN wire_out"
@@ -1030,7 +1029,7 @@ postgres_get_session (void *cls)
                               "    JOIN aggregation_tracking"
                               "      USING (deposit_serial_id)"
                               "    JOIN known_coins"
-                              "      USING (coin_pub)"
+                              "      USING (known_coin_id)"
                               "    JOIN denominations denom"
                               "      USING (denom_pub_hash)"
                               "    JOIN wire_out"
@@ -1120,7 +1119,7 @@ postgres_get_session (void *cls)
       GNUNET_PQ_make_prepare ("deposits_get_overdue",
                               "SELECT"
                               " deposit_serial_id"
-                              ",coin_pub"
+                              ",kc.coin_pub" // FIXME: also return coin_id?
                               ",amount_with_fee_val"
                               ",amount_with_fee_frac"
                               ",wire"
@@ -1128,11 +1127,13 @@ postgres_get_session (void *cls)
                               ",tiny"
                               ",done"
                               " FROM deposits"
+                              " JOIN known_coins kc"
+                              "   USING (known_coin_id)"
                               " WHERE wire_deadline >= $1"
                               " AND wire_deadline < $2"
                               " AND NOT (EXISTS (SELECT 1"
                               "            FROM refunds"
-                              "            WHERE (refunds.coin_pub = deposits.coin_pub))"
+                              "            WHERE (refunds.known_coin_id = deposits.known_coin_id))"
                               "       OR EXISTS (SELECT 1"
                               "            FROM aggregation_tracking"
                               "            WHERE (aggregation_tracking.deposit_serial_id = deposits.deposit_serial_id)))"
@@ -1168,7 +1169,7 @@ postgres_get_session (void *cls)
          information */
       GNUNET_PQ_make_prepare ("recoup_insert",
                               "INSERT INTO recoup "
-                              "(coin_pub"
+                              "(known_coin_id"
                               ",coin_sig"
                               ",coin_blind"
                               ",amount_val"
@@ -1182,7 +1183,7 @@ postgres_get_session (void *cls)
          information */
       GNUNET_PQ_make_prepare ("recoup_refresh_insert",
                               "INSERT INTO recoup_refresh "
-                              "(coin_pub"
+                              "(known_coin_id"
                               ",coin_sig"
                               ",coin_blind"
                               ",amount_val"
@@ -1209,7 +1210,7 @@ postgres_get_session (void *cls)
                               ",amount_frac"
                               " FROM recoup"
                               "    JOIN known_coins coins"
-                              "      USING (coin_pub)"
+                              "      USING (known_coin_id)"
                               "    JOIN reserves_out ro"
                               "      USING (h_blind_ev)"
                               "    JOIN denominations denoms"
@@ -1223,9 +1224,9 @@ postgres_get_session (void *cls)
                               "SELECT"
                               " recoup_refresh_uuid"
                               ",timestamp"
-                              ",rc.old_coin_pub"
+                              ",old_coins.coin_pub AS old_coin_pub"
                               ",old_coins.denom_pub_hash AS old_denom_pub_hash"
-                              ",recoup_refresh.coin_pub"
+                              ",new_coins.coin_pub"
                               ",coin_sig"
                               ",coin_blind"
                               ",denoms.denom_pub"
@@ -1240,9 +1241,9 @@ postgres_get_session (void *cls)
                               "    INNER JOIN refresh_commitments rc"
                               "      ON (rrc.rc = rc.rc)"
                               "    INNER JOIN known_coins old_coins"
-                              "      ON (rc.old_coin_pub = old_coins.coin_pub)"
+                              "      ON (rc.old_known_coin_id = old_coins.known_coin_id)"
                               "    INNER JOIN known_coins new_coins"
-                              "      ON (new_coins.coin_pub = recoup_refresh.coin_pub)"
+                              "      ON (new_coins.known_coin_id = recoup_refresh.known_coin_id)"
                               "    INNER JOIN denominations denoms"
                               "      ON (new_coins.denom_pub_hash = denoms.denom_pub_hash)"
                               " WHERE recoup_refresh_uuid>=$1"
@@ -1279,7 +1280,7 @@ postgres_get_session (void *cls)
                               ",coins.denom_sig"
                               " FROM recoup"
                               "    JOIN known_coins coins"
-                              "      USING (coin_pub)"
+                              "      USING (known_coin_id)"
                               "    JOIN reserves_out ro"
                               "      USING (h_blind_ev)"
                               " WHERE ro.reserve_pub=$1"
@@ -1289,7 +1290,8 @@ postgres_get_session (void *cls)
          affecting old coins of refreshed coins */
       GNUNET_PQ_make_prepare ("recoup_by_old_coin",
                               "SELECT"
-                              " coin_pub"
+                              " known_coin_id"
+                              ",coins.coin_pub"
                               ",coin_sig"
                               ",coin_blind"
                               ",amount_val"
@@ -1299,14 +1301,14 @@ postgres_get_session (void *cls)
                               ",coins.denom_sig"
                               ",recoup_refresh_uuid"
                               " FROM recoup_refresh"
-                              "    JOIN known_coins coins"
-                              "      USING (coin_pub)"
+                              "   JOIN known_coins coins"
+                              "     USING (known_coin_id)"
                               " WHERE h_blind_ev IN"
                               "   (SELECT rrc.h_coin_ev"
                               "    FROM refresh_commitments"
                               "       JOIN refresh_revealed_coins rrc"
                               "           USING (rc)"
-                              "    WHERE old_coin_pub=$1)"
+                              "    WHERE old_known_coin_id=$1)"
                               " FOR UPDATE;",
                               1),
       /* Used in #postgres_get_reserve_history() */
@@ -1352,19 +1354,21 @@ postgres_get_session (void *cls)
                               " FROM recoup"
                               "    JOIN reserves_out ro"
                               "      USING (h_blind_ev)"
-                              " WHERE recoup.coin_pub=$1"
+                              " WHERE recoup.known_coin_id=$1"
                               " FOR UPDATE;",
                               1),
       /* Used in #postgres_get_coin_transactions() to obtain recoup transactions
          for a refreshed coin */
       GNUNET_PQ_make_prepare ("recoup_by_refreshed_coin",
                               "SELECT"
-                              " rc.old_coin_pub"
+                              " rc.old_known_coin_id"
+                              ",old_known.coin_pub AS old_coin_pub"
                               ",coin_sig"
                               ",coin_blind"
                               ",amount_val"
                               ",amount_frac"
                               ",timestamp"
+                              ",coins.coin_pub"
                               ",coins.denom_pub_hash"
                               ",coins.denom_sig"
                               ",recoup_refresh_uuid"
@@ -1374,8 +1378,10 @@ postgres_get_session (void *cls)
                               "    JOIN refresh_commitments rc"
                               "      ON (rrc.rc = rc.rc)"
                               "    JOIN known_coins coins"
-                              "      USING (coin_pub)"
-                              " WHERE coin_pub=$1"
+                              "      USING (known_coin_id)"
+                              "    JOIN known_coins old_known"
+                              "      ON (rc.old_known_coin_id = old_known.known_coin_id)"
+                              " WHERE recoup_refresh.known_coin_id=$1"
                               " FOR UPDATE;",
                               1),
       /* Used in #postgres_get_reserve_by_h_blind() */
@@ -1390,10 +1396,12 @@ postgres_get_session (void *cls)
       /* Used in #postgres_get_old_coin_by_h_blind() */
       GNUNET_PQ_make_prepare ("old_coin_by_h_blind",
                               "SELECT"
-                              " rcom.old_coin_pub"
+                              " kc.coin_pub" // FIXME: also return coin_id?
                               " FROM refresh_revealed_coins"
                               "   JOIN refresh_commitments rcom"
                               "      USING (rc)"
+                              "   JOIN known_coins kc"
+                              "      ON (rcom.old_known_coin_id = kc.known_coin_id)"
                               " WHERE h_coin_ev=$1"
                               " LIMIT 1"
                               " FOR UPDATE;",
@@ -2595,6 +2603,7 @@ postgres_get_reserve_history (void *cls,
 static enum GNUNET_DB_QueryStatus
 postgres_have_deposit (void *cls,
                        struct TALER_EXCHANGEDB_Session *session,
+                       uint64_t coin_id,
                        const struct TALER_EXCHANGEDB_Deposit *deposit,
                        int check_extras,
                        struct TALER_Amount *deposit_fee,
@@ -2602,7 +2611,7 @@ postgres_have_deposit (void *cls,
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (&deposit->coin.coin_pub),
+    GNUNET_PQ_query_param_uint64 (&coin_id),
     GNUNET_PQ_query_param_auto_from_type (&deposit->h_contract_terms),
     GNUNET_PQ_query_param_auto_from_type (&deposit->merchant_pub),
     GNUNET_PQ_query_param_end
@@ -2711,13 +2720,13 @@ postgres_mark_deposit_tiny (void *cls,
 static enum GNUNET_DB_QueryStatus
 postgres_test_deposit_done (void *cls,
                             struct TALER_EXCHANGEDB_Session *session,
-                            const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                            uint64_t coin_id,
                             const struct TALER_MerchantPublicKeyP *merchant_pub,
                             const struct GNUNET_HashCode *h_contract_terms,
                             const struct GNUNET_HashCode *h_wire)
 {
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (coin_pub),
+    GNUNET_PQ_query_param_uint64 (&coin_id),
     GNUNET_PQ_query_param_auto_from_type (merchant_pub),
     GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
     GNUNET_PQ_query_param_auto_from_type (h_wire),
@@ -3039,84 +3048,43 @@ postgres_iterate_matching_deposits (
 
 
 /**
- * Retrieve the record for a known coin.
+ * Check if a coin is known, and if so, return the @a known_coin_id.
  *
  * @param cls the plugin closure
  * @param session the database session handle
  * @param coin_pub the public key of the coin to search for
- * @param coin_info place holder for the returned coin information object
+ * @param denom_pub_hash hash of the denomination of the coin
+ * @param[out] known_coin_id identifier of the coin in the database
  * @return transaction status code
  */
 static enum GNUNET_DB_QueryStatus
-postgres_get_known_coin (void *cls,
-                         struct TALER_EXCHANGEDB_Session *session,
-                         const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                         struct TALER_CoinPublicInfo *coin_info)
+postgres_check_known_coin (void *cls,
+                           struct TALER_EXCHANGEDB_Session *session,
+                           const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                           const struct GNUNET_HashCode *denom_pub_hash,
+                           uint64_t *known_coin_id)
 {
   struct PostgresClosure *pc = cls;
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_auto_from_type (coin_pub),
+    GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
     GNUNET_PQ_query_param_end
   };
   struct GNUNET_PQ_ResultSpec rs[] = {
-    GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
-                                          &coin_info->denom_pub_hash),
-    GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
-                                         &coin_info->denom_sig.rsa_signature),
+    GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+                                  known_coin_id),
     GNUNET_PQ_result_spec_end
   };
 
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Getting known coin data for coin %s\n",
               TALER_B2S (coin_pub));
-  coin_info->coin_pub = *coin_pub;
-  if (NULL == session)
-    session = postgres_get_session (pc);
-  if (NULL == session)
-    return GNUNET_DB_STATUS_HARD_ERROR;
-  return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
-                                                   "get_known_coin",
-                                                   params,
-                                                   rs);
-}
-
-
-/**
- * Retrieve the denomination of a known coin.
- *
- * @param cls the plugin closure
- * @param session the database session handle
- * @param coin_pub the public key of the coin to search for
- * @param[out] denom_hash where to store the hash of the coins denomination
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_coin_denomination (
-  void *cls,
-  struct TALER_EXCHANGEDB_Session *session,
-  const struct TALER_CoinSpendPublicKeyP *coin_pub,
-  struct GNUNET_HashCode *denom_hash)
-{
-  struct PostgresClosure *pc = cls;
-  struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (coin_pub),
-    GNUNET_PQ_query_param_end
-  };
-  struct GNUNET_PQ_ResultSpec rs[] = {
-    GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
-                                          denom_hash),
-    GNUNET_PQ_result_spec_end
-  };
-
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Getting coin denomination of coin %s\n",
-              TALER_B2S (coin_pub));
   if (NULL == session)
     session = postgres_get_session (pc);
   if (NULL == session)
     return GNUNET_DB_STATUS_HARD_ERROR;
   return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
-                                                   "get_coin_denomination",
+                                                   "check_known_coin",
                                                    params,
                                                    rs);
 }
@@ -3130,12 +3098,14 @@ postgres_get_coin_denomination (
  * @param cls plugin closure
  * @param session the shared database session
  * @param coin_info the public coin info
+ * @param[out] known_coin_id set to identifier of the coin in the database
  * @return query result status
  */
 static enum GNUNET_DB_QueryStatus
 insert_known_coin (void *cls,
                    struct TALER_EXCHANGEDB_Session *session,
-                   const struct TALER_CoinPublicInfo *coin_info)
+                   const struct TALER_CoinPublicInfo *coin_info,
+                   uint64_t *known_coin_id)
 {
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_auto_from_type (&coin_info->coin_pub),
@@ -3143,14 +3113,20 @@ insert_known_coin (void *cls,
     GNUNET_PQ_query_param_rsa_signature (coin_info->denom_sig.rsa_signature),
     GNUNET_PQ_query_param_end
   };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+                                  known_coin_id),
+    GNUNET_PQ_result_spec_end
+  };
 
   (void) cls;
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Creating known coin %s\n",
               TALER_B2S (&coin_info->coin_pub));
-  return GNUNET_PQ_eval_prepared_non_select (session->conn,
-                                             "insert_known_coin",
-                                             params);
+  return GNUNET_PQ_eval_prepared_singleton_select (session->conn,
+                                                   "insert_known_coin",
+                                                   params,
+                                                   rs);
 }
 
 
@@ -3196,16 +3172,17 @@ postgres_count_known_coins (void *cls,
  * @param cls database connection plugin state
  * @param session database session
  * @param coin the coin that must be made known
+ * @param[out] known_coin_id set to identifier of the coin in the database
  * @return database transaction status, non-negative on success
  */
 static enum GNUNET_DB_QueryStatus
 postgres_ensure_coin_known (void *cls,
                             struct TALER_EXCHANGEDB_Session *session,
-                            const struct TALER_CoinPublicInfo *coin)
+                            const struct TALER_CoinPublicInfo *coin,
+                            uint64_t *known_coin_id)
 {
   struct PostgresClosure *pc = cls;
   enum GNUNET_DB_QueryStatus qs;
-  struct TALER_CoinPublicInfo known_coin;
 #if EXPLICIT_LOCKS
   struct GNUNET_PQ_QueryParam no_params[] = {
     GNUNET_PQ_query_param_end
@@ -3218,10 +3195,11 @@ postgres_ensure_coin_known (void *cls,
 #endif
 
   /* check if the coin is already known */
-  qs = postgres_get_known_coin (pc,
-                                session,
-                                &coin->coin_pub,
-                                &known_coin);
+  qs = postgres_check_known_coin (pc,
+                                  session,
+                                  &coin->coin_pub,
+                                  &coin->denom_pub_hash,
+                                  known_coin_id);
   if (0 > qs)
   {
     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
@@ -3229,14 +3207,14 @@ postgres_ensure_coin_known (void *cls,
   }
   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
   {
-    GNUNET_CRYPTO_rsa_signature_free (known_coin.denom_sig.rsa_signature);
     return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;   /* no change! */
   }
   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
   /* if not known, insert it */
   qs = insert_known_coin (pc,
                           session,
-                          coin);
+                          coin,
+                          known_coin_id);
   if (0 >= qs)
   {
     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
@@ -3254,6 +3232,7 @@ postgres_ensure_coin_known (void *cls,
  * @param cls the `struct PostgresClosure` with the plugin-specific state
  * @param session connection to the database
  * @param exchange_timestamp time the exchange received the deposit request
+ * @param coin_id which coin is this about
  * @param deposit deposit information to store
  * @return query result status
  */
@@ -3261,10 +3240,11 @@ static enum GNUNET_DB_QueryStatus
 postgres_insert_deposit (void *cls,
                          struct TALER_EXCHANGEDB_Session *session,
                          struct GNUNET_TIME_Absolute exchange_timestamp,
+                         uint64_t coin_id,
                          const struct TALER_EXCHANGEDB_Deposit *deposit)
 {
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (&deposit->coin.coin_pub),
+    GNUNET_PQ_query_param_uint64 (&coin_id),
     TALER_PQ_query_param_amount (&deposit->amount_with_fee),
     TALER_PQ_query_param_absolute_time (&deposit->timestamp),
     TALER_PQ_query_param_absolute_time (&deposit->refund_deadline),
@@ -3301,10 +3281,11 @@ postgres_insert_deposit (void *cls,
 static enum GNUNET_DB_QueryStatus
 postgres_insert_refund (void *cls,
                         struct TALER_EXCHANGEDB_Session *session,
+                        uint64_t coin_id,
                         const struct TALER_EXCHANGEDB_Refund *refund)
 {
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (&refund->coin.coin_pub),
+    GNUNET_PQ_query_param_uint64 (&coin_id),
     GNUNET_PQ_query_param_auto_from_type (&refund->details.merchant_pub),
     GNUNET_PQ_query_param_auto_from_type (&refund->details.merchant_sig),
     GNUNET_PQ_query_param_auto_from_type (&refund->details.h_contract_terms),
@@ -3397,7 +3378,7 @@ get_refunds_cb (void *cls,
  *
  * @param cls closure of plugin
  * @param session database handle to use
- * @param coin_pub coin to get refunds for
+ * @param coin_id coin to get refunds for
  * @param merchant_pub merchant to get refunds for
  * @param h_contract contract (hash) to get refunds for
  * @param cb function to call for each refund found
@@ -3408,7 +3389,7 @@ static enum GNUNET_DB_QueryStatus
 postgres_select_refunds_by_coin (
   void *cls,
   struct TALER_EXCHANGEDB_Session *session,
-  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  uint64_t coin_id,
   const struct TALER_MerchantPublicKeyP *merchant_pub,
   const struct GNUNET_HashCode *h_contract,
   TALER_EXCHANGEDB_RefundCoinCallback cb,
@@ -3417,7 +3398,7 @@ postgres_select_refunds_by_coin (
   struct PostgresClosure *pg = cls;
   enum GNUNET_DB_QueryStatus qs;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (coin_pub),
+    GNUNET_PQ_query_param_uint64 (&coin_id),
     GNUNET_PQ_query_param_auto_from_type (merchant_pub),
     GNUNET_PQ_query_param_auto_from_type (h_contract),
     GNUNET_PQ_query_param_end
@@ -3535,6 +3516,7 @@ postgres_get_melt_index (void *cls,
  *
  * @param cls the `struct PostgresClosure` with the plugin-specific state
  * @param session database handle to use
+ * @param coin_id which coin is the melt about
  * @param refresh_session session data to store
  * @return query status for the transaction
  */
@@ -3542,11 +3524,12 @@ static enum GNUNET_DB_QueryStatus
 postgres_insert_melt (
   void *cls,
   struct TALER_EXCHANGEDB_Session *session,
+  uint64_t coin_id,
   const struct TALER_EXCHANGEDB_Refresh *refresh_session)
 {
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_auto_from_type (&refresh_session->rc),
-    GNUNET_PQ_query_param_auto_from_type (&refresh_session->coin.coin_pub),
+    GNUNET_PQ_query_param_uint64 (&coin_id),
     GNUNET_PQ_query_param_auto_from_type (&refresh_session->coin_sig),
     TALER_PQ_query_param_amount (&refresh_session->amount_with_fee),
     GNUNET_PQ_query_param_uint32 (&refresh_session->noreveal_index),
@@ -4029,9 +4012,9 @@ struct CoinHistoryContext
   struct TALER_EXCHANGEDB_TransactionList *head;
 
   /**
-   * Public key of the coin we are building the history for.
+   * Unique ID of the coin we are building the history for.
    */
-  const struct TALER_CoinSpendPublicKeyP *coin_pub;
+  uint64_t known_coin_id;
 
   /**
    * Closure for all callbacks of this database plugin.
@@ -4281,6 +4264,8 @@ add_old_coin_recoup (void *cls,
     recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
     {
       struct GNUNET_PQ_ResultSpec rs[] = {
+        GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+                                      &recoup->coin_id),
         GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
                                               &recoup->coin.coin_pub),
         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
@@ -4311,7 +4296,8 @@ add_old_coin_recoup (void *cls,
         chc->failed = true;
         return;
       }
-      recoup->old_coin_pub = *chc->coin_pub;
+      // recoup->old_coin_pub = *chc->coin_pub;
+      recoup->old_coin_id = chc->known_coin_id;
     }
     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     tl->next = chc->head;
@@ -4409,8 +4395,9 @@ add_coin_recoup_refresh (void *cls,
     recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
     {
       struct GNUNET_PQ_ResultSpec rs[] = {
-        GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
-                                              &recoup->old_coin_pub),
+        GNUNET_PQ_result_spec_auto_from_type ("old_known_coin_id",
+                                              &recoup->old_coin_id),
+        // GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub", &recoup->old_coin_pub),
         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
                                               &recoup->coin_sig),
         GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
@@ -4419,6 +4406,8 @@ add_coin_recoup_refresh (void *cls,
                                      &recoup->value),
         TALER_PQ_result_spec_absolute_time ("timestamp",
                                             &recoup->timestamp),
+        GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+                                              &recoup->coin.coin_pub),
         GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
                                               &recoup->coin.denom_pub_hash),
         GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
@@ -4439,7 +4428,7 @@ add_coin_recoup_refresh (void *cls,
         chc->failed = true;
         return;
       }
-      recoup->coin.coin_pub = *chc->coin_pub;
+      recoup->coin_id = chc->known_coin_id;
     }
     tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
     tl->next = chc->head;
@@ -4474,7 +4463,7 @@ struct Work
  *
  * @param cls the `struct PostgresClosure` with the plugin-specific state
  * @param session database connection
- * @param coin_pub coin to investigate
+ * @param known_coin_id which coin to investigate
  * @param include_recoup should recoup transactions be included in the @a tlp
  * @param[out] tlp set to list of transactions, NULL if coin is fresh
  * @return database transaction status
@@ -4483,7 +4472,7 @@ static enum GNUNET_DB_QueryStatus
 postgres_get_coin_transactions (
   void *cls,
   struct TALER_EXCHANGEDB_Session *session,
-  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  uint64_t known_coin_id,
   int include_recoup,
   struct TALER_EXCHANGEDB_TransactionList **tlp)
 {
@@ -4522,23 +4511,20 @@ postgres_get_coin_transactions (
     { NULL, NULL }
   };
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (coin_pub),
+    GNUNET_PQ_query_param_uint64 (&known_coin_id),
     GNUNET_PQ_query_param_end
   };
   enum GNUNET_DB_QueryStatus qs;
   const struct Work *work;
   struct CoinHistoryContext chc = {
     .head = NULL,
-    .coin_pub = coin_pub,
+    .known_coin_id = known_coin_id, // FIXME: needed where?
     .session = session,
     .pg = pg,
     .db_cls = cls
   };
 
   work = (GNUNET_YES == include_recoup) ? work_wp : work_op;
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Getting transactions for coin %s\n",
-              TALER_B2S (coin_pub));
   for (unsigned int i = 0; NULL != work[i].statement; i++)
   {
     qs = GNUNET_PQ_eval_prepared_multi_select (session->conn,
@@ -6844,6 +6830,7 @@ postgres_select_reserve_closed_above_serial_id (
  * @param cls closure
  * @param session database connection
  * @param reserve_pub public key of the reserve that is being refunded
+ * @param coin_id unique identifier of the recouped coin
  * @param coin information about the coin
  * @param coin_sig signature of the coin of type #TALER_SIGNATURE_WALLET_COIN_RECOUP
  * @param coin_blind blinding key of the coin
@@ -6857,7 +6844,7 @@ postgres_insert_recoup_request (
   void *cls,
   struct TALER_EXCHANGEDB_Session *session,
   const struct TALER_ReservePublicKeyP *reserve_pub,
-  const struct TALER_CoinPublicInfo *coin,
+  uint64_t coin_id,
   const struct TALER_CoinSpendSignatureP *coin_sig,
   const struct TALER_DenominationBlindingKeyP *coin_blind,
   const struct TALER_Amount *amount,
@@ -6868,7 +6855,7 @@ postgres_insert_recoup_request (
   struct GNUNET_TIME_Absolute expiry;
   struct TALER_EXCHANGEDB_Reserve reserve;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (&coin->coin_pub),
+    GNUNET_PQ_query_param_uint64 (&coin_id),
     GNUNET_PQ_query_param_auto_from_type (coin_sig),
     GNUNET_PQ_query_param_auto_from_type (coin_blind),
     TALER_PQ_query_param_amount (amount),
@@ -6936,7 +6923,7 @@ postgres_insert_recoup_request (
  *
  * @param cls closure
  * @param session database connection
- * @param coin public information about the refreshed coin
+ * @param coin_id unique identifier of the recoup-refreshed coin
  * @param coin_sig signature of the coin of type #TALER_SIGNATURE_WALLET_COIN_RECOUP
  * @param coin_blind blinding key of the coin
  * @param h_blind_ev blinded envelope, as calculated by the exchange
@@ -6949,7 +6936,7 @@ static enum GNUNET_DB_QueryStatus
 postgres_insert_recoup_refresh_request (
   void *cls,
   struct TALER_EXCHANGEDB_Session *session,
-  const struct TALER_CoinPublicInfo *coin,
+  uint64_t coin_id,
   const struct TALER_CoinSpendSignatureP *coin_sig,
   const struct TALER_DenominationBlindingKeyP *coin_blind,
   const struct TALER_Amount *amount,
@@ -6957,7 +6944,7 @@ postgres_insert_recoup_refresh_request (
   struct GNUNET_TIME_Absolute timestamp)
 {
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (&coin->coin_pub),
+    GNUNET_PQ_query_param_uint64 (&coin_id),
     GNUNET_PQ_query_param_auto_from_type (coin_sig),
     GNUNET_PQ_query_param_auto_from_type (coin_blind),
     TALER_PQ_query_param_amount (amount),
@@ -7339,8 +7326,7 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
   plugin->free_reserve_history = &common_free_reserve_history;
   plugin->count_known_coins = &postgres_count_known_coins;
   plugin->ensure_coin_known = &postgres_ensure_coin_known;
-  plugin->get_known_coin = &postgres_get_known_coin;
-  plugin->get_coin_denomination = &postgres_get_coin_denomination;
+  plugin->check_known_coin = &postgres_check_known_coin;
   plugin->have_deposit = &postgres_have_deposit;
   plugin->mark_deposit_tiny = &postgres_mark_deposit_tiny;
   plugin->test_deposit_done = &postgres_test_deposit_done;
diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c
index 043095e7..714aaa7d 100644
--- a/src/exchangedb/test_exchangedb.c
+++ b/src/exchangedb/test_exchangedb.c
@@ -516,6 +516,7 @@ test_melting (struct TALER_EXCHANGEDB_Session *session)
   int ret;
   enum GNUNET_DB_QueryStatus qs;
   struct GNUNET_TIME_Absolute now;
+  uint64_t coin_id;
 
   ret = GNUNET_SYSERR;
   RND_BLK (&refresh_session);
@@ -562,10 +563,12 @@ test_melting (struct TALER_EXCHANGEDB_Session *session)
   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
           plugin->ensure_coin_known (plugin->cls,
                                      session,
-                                     &refresh_session.coin));
+                                     &refresh_session.coin,
+                                     &coin_id));
   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
           plugin->insert_melt (plugin->cls,
                                session,
+                               coin_id,
                                &refresh_session));
   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
           plugin->get_melt (plugin->cls,
@@ -581,9 +584,11 @@ test_melting (struct TALER_EXCHANGEDB_Session *session)
           TALER_amount_cmp (&fee_refresh,
                             &ret_refresh_session.melt_fee));
   FAILIF (0 !=
-          GNUNET_memcmp (&refresh_session.rc, &ret_refresh_session.session.rc));
-  FAILIF (0 != GNUNET_memcmp (&refresh_session.coin_sig,
-                              &ret_refresh_session.session.coin_sig));
+          GNUNET_memcmp (&refresh_session.rc,
+                         &ret_refresh_session.session.rc));
+  FAILIF (0 !=
+          GNUNET_memcmp (&refresh_session.coin_sig,
+                         &ret_refresh_session.session.coin_sig));
   FAILIF (NULL !=
           ret_refresh_session.session.coin.denom_sig.rsa_signature);
   FAILIF (0 != memcmp (&refresh_session.coin.coin_pub,
@@ -680,7 +685,7 @@ test_melting (struct TALER_EXCHANGEDB_Session *session)
 
     qs = plugin->get_coin_transactions (plugin->cls,
                                         session,
-                                        &refresh_session.coin.coin_pub,
+                                        coin_id,
                                         GNUNET_YES,
                                         &tl);
     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
@@ -1521,6 +1526,7 @@ run (void *cls)
   uint64_t rr;
   enum GNUNET_DB_QueryStatus qs;
   struct GNUNET_TIME_Absolute now;
+  uint64_t coin_id;
 
   dkp = NULL;
   rh = NULL;
@@ -1708,7 +1714,8 @@ run (void *cls)
   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
           plugin->ensure_coin_known (plugin->cls,
                                      session,
-                                     &deposit.coin));
+                                     &deposit.coin,
+                                     &coin_id));
   {
     struct TALER_EXCHANGEDB_Reserve pre_reserve;
     struct TALER_EXCHANGEDB_Reserve post_reserve;
@@ -1723,7 +1730,7 @@ run (void *cls)
             plugin->insert_recoup_request (plugin->cls,
                                            session,
                                            &reserve_pub,
-                                           &deposit.coin,
+                                           coin_id,
                                            &coin_sig,
                                            &coin_blind,
                                            &value,
@@ -1883,7 +1890,8 @@ run (void *cls)
   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
           plugin->ensure_coin_known (plugin->cls,
                                      session,
-                                     &deposit.coin));
+                                     &deposit.coin,
+                                     &coin_id));
   {
     struct GNUNET_TIME_Absolute now;
     struct GNUNET_TIME_Absolute r;
@@ -1895,10 +1903,12 @@ run (void *cls)
             plugin->insert_deposit (plugin->cls,
                                     session,
                                     now,
+                                    coin_id,
                                     &deposit));
     FAILIF (1 !=
             plugin->have_deposit (plugin->cls,
                                   session,
+                                  coin_id,
                                   &deposit,
                                   GNUNET_YES,
                                   &deposit_fee,
@@ -1975,7 +1985,7 @@ run (void *cls)
   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
           plugin->test_deposit_done (plugin->cls,
                                      session,
-                                     &deposit.coin.coin_pub,
+                                     coin_id,
                                      &deposit.merchant_pub,
                                      &deposit.h_contract_terms,
                                      &deposit.h_wire));
@@ -1989,7 +1999,7 @@ run (void *cls)
   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
           plugin->test_deposit_done (plugin->cls,
                                      session,
-                                     &deposit.coin.coin_pub,
+                                     coin_id,
                                      &deposit.merchant_pub,
                                      &deposit.h_contract_terms,
                                      &deposit.h_wire));
@@ -2004,19 +2014,27 @@ run (void *cls)
   {
     struct GNUNET_TIME_Absolute r;
     struct TALER_Amount deposit_fee;
+    uint64_t coin_id2;
 
     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
             plugin->have_deposit (plugin->cls,
                                   session,
+                                  coin_id,
                                   &deposit2,
                                   GNUNET_YES,
                                   &deposit_fee,
                                   &r));
     deposit2.merchant_pub = deposit.merchant_pub;
     RND_BLK (&deposit2.coin.coin_pub); /* should fail if coin is different */
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->ensure_coin_known (plugin->cls,
+                                       session,
+                                       &deposit2.coin,
+                                       &coin_id2));
     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
             plugin->have_deposit (plugin->cls,
                                   session,
+                                  coin_id2,
                                   &deposit2,
                                   GNUNET_YES,
                                   &deposit_fee,
@@ -2042,11 +2060,12 @@ run (void *cls)
   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
           plugin->insert_refund (plugin->cls,
                                  session,
+                                 coin_id,
                                  &refund));
   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
           plugin->select_refunds_by_coin (plugin->cls,
                                           session,
-                                          &refund.coin.coin_pub,
+                                          coin_id,
                                           &refund.details.merchant_pub,
                                           &refund.details.h_contract_terms,
                                           &check_refund_cb,
@@ -2102,7 +2121,7 @@ run (void *cls)
           plugin->insert_recoup_request (plugin->cls,
                                          session,
                                          &reserve_pub,
-                                         &deposit.coin,
+                                         coin_id,
                                          &coin_sig,
                                          &coin_blind,
                                          &value,
@@ -2120,7 +2139,7 @@ run (void *cls)
   FAILIF (1 != auditor_row_cnt);
   qs = plugin->get_coin_transactions (plugin->cls,
                                       session,
-                                      &refund.coin.coin_pub,
+                                      coin_id,
                                       GNUNET_YES,
                                       &tl);
   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h
index 9fb93236..c80e7e27 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -364,7 +364,12 @@ struct TALER_EXCHANGEDB_RecoupRefreshListEntry
    * Information about the coin that was paid back
    * (NOT the coin we are considering the history of!)
    */
-  struct TALER_CoinPublicInfo coin;
+  struct TALER_CoinPublicInfo coin; // FIXME: check that where 'coin' is used, 'coin_id' likely also must be used!
+
+  /**
+   * Unique ID of @e coin.
+   */
+  uint64_t coin_id;
 
   /**
    * Blinding factor supplied to prove to the exchange that
@@ -381,7 +386,12 @@ struct TALER_EXCHANGEDB_RecoupRefreshListEntry
   /**
    * Public key of the old coin that the refreshed coin was paid back to.
    */
-  struct TALER_CoinSpendPublicKeyP old_coin_pub;
+  // struct TALER_CoinSpendPublicKeyP old_coin_pub;
+
+  /**
+   * Unique ID of @e old_coin_pub.
+   */
+  uint64_t old_coin_id;
 
   /**
    * How much was the coin still worth at this time?
@@ -978,7 +988,7 @@ struct TALER_EXCHANGEDB_Session;
  * @param exchange_timestamp when did the exchange receive the deposit
  * @param wallet_timestamp when did the wallet sign the contract
  * @param merchant_pub public key of the merchant
- * @param coin_pub public key of the coin
+ * @param coin_pub public key of the coin // FIXME: not unique!
  * @param amount_with_fee amount that was deposited including fee
  * @param deposit_fee amount the exchange gets to keep as transaction fees
  * @param h_contract_terms hash of the proposal data known to merchant and customer
@@ -1029,7 +1039,7 @@ typedef void
  * @param exchange_timestamp when did the deposit happen
  * @param wallet_timestamp when did the contract happen
  * @param merchant_pub public key of the merchant
- * @param denom_pub denomination public key of @a coin_pub
+ * @param denom_pub denomination public key of @a coin_pub // FIXME: not unique!
  * @param coin_pub public key of the coin
  * @param coin_sig signature from the coin
  * @param amount_with_fee amount that was deposited including fee
@@ -1067,7 +1077,7 @@ typedef int
  *
  * @param cls closure
  * @param rowid unique serial ID for the refresh session in our DB
- * @param denom_pub denomination public key of @a coin_pub
+ * @param denom_pub denomination public key of @a coin_pub // FIXME: not unique!
  * @param coin_pub public key of the coin
  * @param coin_sig signature from the coin
  * @param amount_with_fee amount that was deposited including fee
@@ -1162,7 +1172,7 @@ typedef void
  *
  * @param cls closure
  * @param rowid unique serial ID for the refund in our DB
- * @param denom_pub denomination public key of @a coin_pub
+ * @param denom_pub denomination public key of @a coin_pub // FIXME: not unique
  * @param coin_pub public key of the coin
  * @param merchant_pub public key of the merchant
  * @param merchant_sig signature of the merchant
@@ -1281,7 +1291,7 @@ typedef void
  * @param account_details which account did the transfer go to?
  * @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls)
  * @param h_contract_terms which proposal was this payment about
- * @param denom_pub denomination of @a coin_pub
+ * @param denom_pub denomination of @a coin_pub // FIXME: not unique!
  * @param coin_pub which public key was this payment about
  * @param coin_value amount contributed by this coin in total (with fee)
  * @param coin_fee applicable fee for this coin
@@ -1351,7 +1361,7 @@ typedef int
  * @param timestamp when did we receive the recoup request
  * @param amount how much should be added back to the reserve
  * @param reserve_pub public key of the reserve
- * @param coin public information about the coin
+ * @param coin public information about the coin // FIXME: unique???
  * @param denom_pub denomination key of @a coin
  * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_COIN_RECOUP
  * @param coin_blind blinding factor used to blind the coin
@@ -1378,7 +1388,7 @@ typedef int
  * @param rowid row identifier used to uniquely identify the recoup operation
  * @param timestamp when did we receive the recoup request
  * @param amount how much should be added back to the reserve
- * @param old_coin_pub original coin that was refreshed to create @a coin
+ * @param old_coin_pub original coin that was refreshed to create @a coin // FIXME: not unique!
  * @param old_denom_pub_hash hash of public key of @a old_coin_pub
  * @param coin public information about the coin
  * @param denom_pub denomination key of @a coin
@@ -1474,7 +1484,7 @@ typedef void
  *
  * @param cls closure
  * @param rowid deposit table row of the coin's deposit
- * @param coin_pub public key of the coin
+ * @param coin_pub public key of the coin // FIXME: not unique!
  * @param amount value of the deposit, including fee
  * @param wire where should the funds be wired, including 'url' in payto://-format
  * @param deadline what was the requested wire transfer deadline
@@ -1801,12 +1811,14 @@ struct TALER_EXCHANGEDB_Plugin
    * @param cls database connection plugin state
    * @param session database session
    * @param coin the coin that must be made known
+   * @param[out] known_coin_id set to identifier of the coin in the database
    * @return database transaction status, non-negative on success
    */
   enum GNUNET_DB_QueryStatus
   (*ensure_coin_known)(void *cls,
                        struct TALER_EXCHANGEDB_Session *session,
-                       const struct TALER_CoinPublicInfo *coin);
+                       const struct TALER_CoinPublicInfo *coin,
+                       uint64_t *known_coin_id);
 
 
   /**
@@ -1814,30 +1826,17 @@ struct TALER_EXCHANGEDB_Plugin
    *
    * @param cls database connection plugin state
    * @param session database session
-   * @param coin the coin that must be made known
-   * @return database transaction status, non-negative on success
-   */
-  enum GNUNET_DB_QueryStatus
-  (*get_known_coin)(void *cls,
-                    struct TALER_EXCHANGEDB_Session *session,
-                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                    struct TALER_CoinPublicInfo *coin_info);
-
-
-  /**
-   * Retrieve the denomination of a known coin.
-   *
-   * @param cls the plugin closure
-   * @param session the database session handle
    * @param coin_pub the public key of the coin to search for
-   * @param[out] denom_hash where to store the hash of the coins denomination
-   * @return transaction status code
+   * @param denom_pub_hash hash of the denomination of the coin
+   * @param[out] known_coin_id identifier of the coin in the database
+   * @return database transaction status, non-negative on success
    */
   enum GNUNET_DB_QueryStatus
-  (*get_coin_denomination)(void *cls,
-                           struct TALER_EXCHANGEDB_Session *session,
-                           const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                           struct GNUNET_HashCode *denom_hash);
+  (*check_known_coin)(void *cls,
+                      struct TALER_EXCHANGEDB_Session *session,
+                      const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                      const struct GNUNET_HashCode *denom_pub_hash,
+                      uint64_t *known_coin_id);
 
 
   /**
@@ -1845,6 +1844,7 @@ struct TALER_EXCHANGEDB_Plugin
    *
    * @param cls the @e cls of this struct with the plugin-specific state
    * @param session database connection
+   * @param coin_id which coin is this about
    * @param deposit deposit to search for
    * @param check_extras whether to check extra fields or not
    * @param[out] deposit_fee set to the deposit fee the exchange charged
@@ -1856,6 +1856,7 @@ struct TALER_EXCHANGEDB_Plugin
   enum GNUNET_DB_QueryStatus
   (*have_deposit)(void *cls,
                   struct TALER_EXCHANGEDB_Session *session,
+                  uint64_t coin_id,
                   const struct TALER_EXCHANGEDB_Deposit *deposit,
                   int check_extras,
                   struct TALER_Amount *deposit_fee,
@@ -1868,6 +1869,7 @@ struct TALER_EXCHANGEDB_Plugin
    * @param cls the @e cls of this struct with the plugin-specific state
    * @param session connection to the database
    * @param exchange_timestamp time the exchange received the deposit request
+   * @param coin_id which coin is this about
    * @param deposit deposit information to store
    * @return query result status
    */
@@ -1875,6 +1877,7 @@ struct TALER_EXCHANGEDB_Plugin
   (*insert_deposit)(void *cls,
                     struct TALER_EXCHANGEDB_Session *session,
                     struct GNUNET_TIME_Absolute exchange_timestamp,
+                    uint64_t coin_id,
                     const struct TALER_EXCHANGEDB_Deposit *deposit);
 
 
@@ -1883,21 +1886,23 @@ struct TALER_EXCHANGEDB_Plugin
    *
    * @param cls the @e cls of this struct with the plugin-specific state
    * @param session connection to the database
+   * @param coin_id which coin is this about
    * @param refund refund information to store
    * @return query result status
    */
   enum GNUNET_DB_QueryStatus
   (*insert_refund)(void *cls,
                    struct TALER_EXCHANGEDB_Session *session,
+                   uint64_t coin_id,
                    const struct TALER_EXCHANGEDB_Refund *refund);
 
 
   /**
-   * Select refunds by @a coin_pub, @a merchant_pub and @a h_contract.
+   * Select refunds by @a coin_id, @a merchant_pub and @a h_contract.
    *
    * @param cls closure of plugin
    * @param session database handle to use
-   * @param coin_pub coin to get refunds for
+   * @param coin_id coin to get refunds for
    * @param merchant_pub merchant to get refunds for
    * @param h_contract_pub contract (hash) to get refunds for
    * @param cb function to call for each refund found
@@ -1907,7 +1912,7 @@ struct TALER_EXCHANGEDB_Plugin
   enum GNUNET_DB_QueryStatus
   (*select_refunds_by_coin)(void *cls,
                             struct TALER_EXCHANGEDB_Session *session,
-                            const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                            uint64_t coin_id,
                             const struct TALER_MerchantPublicKeyP *merchant_pub,
                             const struct GNUNET_HashCode *h_contract,
                             TALER_EXCHANGEDB_RefundCoinCallback cb,
@@ -1936,7 +1941,7 @@ struct TALER_EXCHANGEDB_Plugin
    *
    * @param cls the @e cls of this struct with the plugin-specific state
    * @param session connection to the database
-   * @param coin_pub the coin to check for deposit
+   * @param coin_id the coin to check for deposit
    * @param merchant_pub merchant to receive the deposit
    * @param h_contract_terms contract terms of the deposit
    * @param h_wire hash of the merchant's wire details
@@ -1947,7 +1952,7 @@ struct TALER_EXCHANGEDB_Plugin
   enum GNUNET_DB_QueryStatus
   (*test_deposit_done)(void *cls,
                        struct TALER_EXCHANGEDB_Session *session,
-                       const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                       uint64_t coin_id,
                        const struct TALER_MerchantPublicKeyP *merchant_pub,
                        const struct GNUNET_HashCode *h_contract_terms,
                        const struct GNUNET_HashCode *h_wire);
@@ -2029,12 +2034,14 @@ struct TALER_EXCHANGEDB_Plugin
    *
    * @param cls the @e cls of this struct with the plugin-specific state
    * @param session database handle to use
+   * @param coin_id which coin is the melt about
    * @param refresh_session operational data to store
    * @return query status for the transaction
    */
   enum GNUNET_DB_QueryStatus
   (*insert_melt)(void *cls,
                  struct TALER_EXCHANGEDB_Session *session,
+                 uint64_t coin_id,
                  const struct TALER_EXCHANGEDB_Refresh *refresh_session);
 
 
@@ -2127,9 +2134,13 @@ struct TALER_EXCHANGEDB_Plugin
    * @e get_link_data_list() enable the owner of an old coin to determine
    * the private keys of the new coins after the melt.
    *
+   * Note that if multiple coins (of different denominations) share the
+   * same @a coin_pub, we simply return the link data associated with any of
+   * the coins.
+   *
    * @param cls the @e cls of this struct with the plugin-specific state
    * @param session database connection
-   * @param coin_pub public key of the coin
+   * @param coin_pub public key of the coin(s).
    * @param ldc function to call for each session the coin was melted into
    * @param ldc_cls closure for @a tdc
    * @return statement execution status
@@ -2148,7 +2159,7 @@ struct TALER_EXCHANGEDB_Plugin
    *
    * @param cls the @e cls of this struct with the plugin-specific state
    * @param session database connection
-   * @param coin_pub coin to investigate
+   * @param known_coin_id which coin to investigate
    * @param include_recoup include recoup transactions of the coin?
    * @param[out] tlp set to list of transactions, NULL if coin is fresh
    * @return database transaction status
@@ -2156,7 +2167,7 @@ struct TALER_EXCHANGEDB_Plugin
   enum GNUNET_DB_QueryStatus
   (*get_coin_transactions)(void *cls,
                            struct TALER_EXCHANGEDB_Session *session,
-                           const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                           uint64_t known_coin_id,
                            int include_recoup,
                            struct TALER_EXCHANGEDB_TransactionList **tlp);
 
@@ -2196,6 +2207,9 @@ struct TALER_EXCHANGEDB_Plugin
    * If we did not execute the deposit yet, return when it is supposed
    * to be executed.
    *
+   * FIXME: check it is OK to do this by coin_pub (merging results
+   * from all coins with different denominations sharing the same coin_pub).
+   *
    * @param cls closure
    * @param session database connection
    * @param h_contract_terms hash of the proposal data
@@ -2652,7 +2666,7 @@ struct TALER_EXCHANGEDB_Plugin
    * @param cls closure
    * @param session database connection
    * @param reserve_pub public key of the reserve that is being refunded
-   * @param coin public information about a coin
+   * @param coin_id unique identifier of the recouped coin
    * @param coin_sig signature of the coin of type #TALER_SIGNATURE_WALLET_COIN_RECOUP
    * @param coin_blind blinding key of the coin
    * @param h_blind_ev blinded envelope, as calculated by the exchange
@@ -2666,7 +2680,7 @@ struct TALER_EXCHANGEDB_Plugin
     void *cls,
     struct TALER_EXCHANGEDB_Session *session,
     const struct TALER_ReservePublicKeyP *reserve_pub,
-    const struct TALER_CoinPublicInfo *coin,
+    uint64_t coin_id,
     const struct TALER_CoinSpendSignatureP *coin_sig,
     const struct TALER_DenominationBlindingKeyP *coin_blind,
     const struct TALER_Amount *amount,
@@ -2680,7 +2694,7 @@ struct TALER_EXCHANGEDB_Plugin
    *
    * @param cls closure
    * @param session database connection
-   * @param coin public information about the refreshed coin
+   * @param coin_id unique identifier of the recoup-refreshed coin
    * @param coin_sig signature of the coin of type #TALER_SIGNATURE_WALLET_COIN_RECOUP
    * @param coin_blind blinding key of the coin
    * @param h_blind_ev blinded envelope, as calculated by the exchange
@@ -2693,7 +2707,7 @@ struct TALER_EXCHANGEDB_Plugin
   (*insert_recoup_refresh_request)(
     void *cls,
     struct TALER_EXCHANGEDB_Session *session,
-    const struct TALER_CoinPublicInfo *coin,
+    uint64_t coin_id,
     const struct TALER_CoinSpendSignatureP *coin_sig,
     const struct TALER_DenominationBlindingKeyP *coin_blind,
     const struct TALER_Amount *amount,
@@ -2728,6 +2742,7 @@ struct TALER_EXCHANGEDB_Plugin
    * @param[out] old_coin_pub set to information about the old coin (on success only)
    * @return transaction status code
    */
+  // FIXME: check use, likely bad: may need to return old_coin_id!
   enum GNUNET_DB_QueryStatus
   (*get_old_coin_by_h_blind)(void *cls,
                              struct TALER_EXCHANGEDB_Session *session,
db.patch (85,066 bytes)

Christian Grothoff

2020-07-08 12:49

manager   ~0016396

After some internal discussion, we decided that it would be too messy (and brittle) to really allow coin_priv/pub re-use. Reasons include that having 'coin_pub' serve as a primary key is nice and simple, and that tolerating wallets that non-sensicaly re-use coin keys should really not be encouraged by allowing it per spec. So instead we will change the code/protocol to
a) detect that the coin_pub has been used before, and
b) return an error proving this.

This requires the following changes:
1) Various request signatures (deposit, melt, recoup) must include the h_denom_pub so we can prove that the same coin key was involved in different denominations, and it's not the exchange's fault.
2) The transactions must be changed to only commit to known_coins table if the rest of the transaction that includes the above signature also ends up in the DB. So we merge the two transactions (add known coin + actual work) into one transaction.
3) The transactions must be changed to detect the coin_pub re-use with a different denom_pub and then return the coin's transaction history, proving that it was previously used with the same denom_pub_h.
4) We _may_ need to return h_denom_pub with some other replies to enable verification of the signatures with the changes from (1). (not sure).

Christian Grothoff

2020-07-08 15:36

manager   ~0016398

95f5de1..8236b1e updates the docs.git specification to add the h_denom_pub where needed (deposit and melt, already present for recoup).

Christian Grothoff

2020-07-08 17:22

manager   ~0016399

1ca062fc..97dfbec0 adds the h_denom_pub to the deposit request.

Christian Grothoff

2020-07-08 18:05

manager   ~0016400

97dfbec0..8e03498a adds the h_denom_pub to the melt request.

Christian Grothoff

2020-07-08 18:27

manager   ~0016401

8e03498a..c93f6471 combines the known_coin transactions with the respective deposit/melt/recoup transactions, so that there will never be a known coin entry if the 'rest' of the transaction failed.

Christian Grothoff

2020-07-08 19:45

manager   ~0016402

c93f6471..8a1402a5 implements generation of proper errors for conflicting denomination keys on the server-side.

Christian Grothoff

2020-07-08 20:05

manager   ~0016403

8236b1e..31e66d9 documents the API change in docs.git

Christian Grothoff

2020-07-08 21:09

manager   ~0016404

31e66d9..2f705cc further updates the specs to update the coin history response to include the coin's signatures on the RECOUP request and not merely exchange signatures on the confirmation(s) where applicable.

Christian Grothoff

2020-07-08 21:30

manager   ~0016405

8a1402a5..92ac6dd1 should implement the new logic client-side as well. However, I noticed the client did previously _NOT_ handle some of the corner cases properly! Needs more testing!

Christian Grothoff

2020-07-10 23:16

manager   ~0016441

ddf95c49..7085cfef adds test cases specifically creating coins with private key re-use and checking that deposit/melt fail with this.
*recoup* handling is NOT yet tested.

Christian Grothoff

2020-07-10 23:24

manager   ~0016442

7085cfef..8ea4e50a adds a test with recoup as well. Declaring victory.

Issue History

Date Modified Username Field Change
2020-06-25 11:15 oec New Issue
2020-06-25 11:15 oec Status new => assigned
2020-06-25 11:15 oec Assigned To => Christian Grothoff
2020-06-25 11:48 Christian Grothoff Note Added: 0016366
2020-06-25 23:47 Christian Grothoff Target Version => 0.8
2020-06-25 23:48 Christian Grothoff Product Version => 0.7.0
2020-07-07 00:28 Christian Grothoff File Added: db.patch
2020-07-07 00:28 Christian Grothoff Note Added: 0016391
2020-07-08 12:49 Christian Grothoff Note Added: 0016396
2020-07-08 15:36 Christian Grothoff Note Added: 0016398
2020-07-08 17:22 Christian Grothoff Note Added: 0016399
2020-07-08 18:05 Christian Grothoff Note Added: 0016400
2020-07-08 18:27 Christian Grothoff Note Added: 0016401
2020-07-08 19:45 Christian Grothoff Note Added: 0016402
2020-07-08 20:05 Christian Grothoff Note Added: 0016403
2020-07-08 21:09 Christian Grothoff Note Added: 0016404
2020-07-08 21:30 Christian Grothoff Note Added: 0016405
2020-07-10 23:16 Christian Grothoff Note Added: 0016441
2020-07-10 23:24 Christian Grothoff Note Added: 0016442
2020-07-10 23:24 Christian Grothoff Status assigned => resolved
2020-07-10 23:24 Christian Grothoff Resolution open => fixed
2020-07-10 23:24 Christian Grothoff Fixed in Version => 0.8