commit 5607928347bddf59f5f750611ec9d8093e6b9840
parent a8a24394de532a907f2127d6337a4c53cddb3b5f
Author: Özgür Kesim <oec@codeblau.de>
Date: Fri, 5 Dec 2025 13:41:44 +0100
[exchange] refresh vDOLDPLUS implemented
- handler: melt and reveal-melt handlers updated
- DB schema: added columns revealed and transfer_pub to refresh table
- vDOLDPLUS refresh support in lib/ added
- cleanup unused functions from (very) old refresh implementation
Tests now all pass in src/testing!
Diffstat:
45 files changed, 3619 insertions(+), 3371 deletions(-)
diff --git a/src/auditor/taler-helper-auditor-aggregation.c b/src/auditor/taler-helper-auditor-aggregation.c
@@ -567,7 +567,7 @@ check_transaction_history_for_deposit (
}
break;
}
- case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
+ case TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER:
{
const struct TALER_Amount *amount_with_fee;
@@ -580,7 +580,7 @@ check_transaction_history_for_deposit (
amount_with_fee);
break;
}
- case TALER_EXCHANGEDB_TT_RECOUP:
+ case TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW:
{
const struct TALER_Amount *amount_with_fee;
diff --git a/src/auditor/taler-helper-auditor-coins.c b/src/auditor/taler-helper-auditor-coins.c
@@ -513,13 +513,13 @@ check_coin_history (const struct TALER_CoinSpendPublicKeyP *coin_pub,
&pos->details.refund->refund_fee);
have_refund = true;
break;
- case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
+ case TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER:
/* refunded += pos->value */
TALER_ARL_amount_add (&refunded,
&refunded,
&pos->details.old_coin_recoup->value);
break;
- case TALER_EXCHANGEDB_TT_RECOUP:
+ case TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW:
/* spent += pos->value */
TALER_ARL_amount_add (&spent,
&spent,
diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am
@@ -186,7 +186,7 @@ taler_exchange_httpd_SOURCES = \
taler-exchange-httpd_management_wire_enable.c \
taler-exchange-httpd_management_wire_disable.c \
taler-exchange-httpd_management_wire_fees.c \
- taler-exchange-httpd_melt_v27.c taler-exchange-httpd_melt_v27.h \
+ taler-exchange-httpd_melt.c taler-exchange-httpd_melt.h \
taler-exchange-httpd_metrics.c taler-exchange-httpd_metrics.h \
taler-exchange-httpd_mhd.c taler-exchange-httpd_mhd.h \
taler-exchange-httpd_purses_create.c taler-exchange-httpd_purses_create.h \
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c
@@ -57,7 +57,7 @@
#include "taler-exchange-httpd_kyc-webhook.h"
#include "taler-exchange-httpd_aml-decision.h"
#include "taler-exchange-httpd_management.h"
-#include "taler-exchange-httpd_melt_v27.h"
+#include "taler-exchange-httpd_melt.h"
#include "taler-exchange-httpd_metrics.h"
#include "taler-exchange-httpd_mhd.h"
#include "taler-exchange-httpd_purses_create.h"
@@ -1745,7 +1745,7 @@ handle_mhd_request (void *cls,
{
.url = "melt",
.method = MHD_HTTP_METHOD_POST,
- .handler.post = &TEH_handler_melt_v27,
+ .handler.post = &TEH_handler_melt,
.nargs = 0
},
/* tracking transfers */
@@ -2752,7 +2752,7 @@ do_shutdown (void *cls)
TEH_resume_keys_requests (true);
TEH_batch_deposit_cleanup ();
TEH_withdraw_cleanup ();
- TEH_melt_v27_cleanup ();
+ TEH_melt_cleanup ();
TEH_reserves_close_cleanup ();
TEH_reserves_purse_cleanup ();
TEH_purses_merge_cleanup ();
diff --git a/src/exchange/taler-exchange-httpd_coins_get.c b/src/exchange/taler-exchange-httpd_coins_get.c
@@ -286,7 +286,7 @@ compile_transaction_history (
}
}
break;
- case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
+ case TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER:
{
struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr =
pos->details.old_coin_recoup;
@@ -317,7 +317,7 @@ compile_transaction_history (
history,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("type",
- "OLD-COIN-RECOUP"),
+ "RECOUP-REFRESH-RECEIVER"),
GNUNET_JSON_pack_uint64 ("history_offset",
pos->coin_history_id),
TALER_JSON_pack_amount ("amount",
@@ -337,7 +337,7 @@ compile_transaction_history (
}
break;
}
- case TALER_EXCHANGEDB_TT_RECOUP:
+ case TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW:
{
const struct TALER_EXCHANGEDB_RecoupListEntry *recoup =
pos->details.recoup;
@@ -363,7 +363,7 @@ compile_transaction_history (
history,
GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("type",
- "RECOUP"),
+ "RECOUP-WITHDRAW"),
GNUNET_JSON_pack_uint64 ("history_offset",
pos->coin_history_id),
TALER_JSON_pack_amount ("amount",
diff --git a/src/exchange/taler-exchange-httpd_melt.c b/src/exchange/taler-exchange-httpd_melt.c
@@ -0,0 +1,1861 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 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 published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-exchange-httpd_melt.c
+ * @brief Handle /melt requests
+ * @note This endpoint is active since vDOLDPLUS of the protocol API
+ * @author Özgür Kesim
+ */
+
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler-exchange-httpd.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_melt.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler/taler_util.h"
+
+/**
+ * The different type of errors that might occur, sorted by name.
+ * Some of them require idempotency checks, which are marked
+ * in @e idempotency_check_required below.
+ */
+enum MeltError
+{
+ MELT_ERROR_NONE = 0,
+ MELT_ERROR_AGE_RESTRICTION_COMMITMENT_INVALID,
+ MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
+ MELT_ERROR_AMOUNT_OVERFLOW,
+ MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW,
+ MELT_ERROR_AMOUNT_WITH_FEE_INCORRECT,
+ MELT_ERROR_BLINDING_SEED_REQUIRED,
+ MELT_ERROR_COIN_CIPHER_MISMATCH,
+ MELT_COIN_CONFLICTING_DENOMINATION_KEY,
+ MELT_ERROR_COIN_EXPIRED_NO_ZOMBIE,
+ MELT_ERROR_COIN_SIGNATURE_INVALID,
+ MELT_ERROR_COIN_UNKNOWN,
+ MELT_ERROR_CONFIRMATION_SIGN,
+ MELT_ERROR_CRYPTO_HELPER,
+ MELT_ERROR_DB_FETCH_FAILED,
+ MELT_ERROR_DB_INVARIANT_FAILURE,
+ MELT_ERROR_DB_MAKE_COIN_KNOW_FAILURE,
+ MELT_ERROR_DB_PREFLIGHT_FAILURE,
+ MELT_ERROR_DENOMINATION_EXPIRED,
+ MELT_ERROR_DENOMINATION_KEY_UNKNOWN,
+ MELT_ERROR_DENOMINATION_REVOKED,
+ MELT_ERROR_DENOMINATION_SIGN,
+ MELT_ERROR_DENOMINATION_SIGNATURE_INVALID,
+ MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
+ MELT_ERROR_DUPLICATE_PLANCHET,
+ MELT_ERROR_DUPLICATE_TRANSFER_PUB,
+ MELT_ERROR_INSUFFICIENT_FUNDS,
+ MELT_ERROR_KEYS_MISSING,
+ MELT_ERROR_FEES_EXCEED_CONTRIBUTION,
+ MELT_ERROR_NONCE_RESUSE,
+ MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
+};
+
+/**
+ * With the bits set in this value will be mark the errors
+ * that require a check for idempotency before actually
+ * returning an error.
+ */
+static const uint64_t idempotency_check_required =
+ 0
+ | (1 << MELT_ERROR_DENOMINATION_EXPIRED)
+ | (1 << MELT_ERROR_DENOMINATION_KEY_UNKNOWN)
+ | (1 << MELT_ERROR_DENOMINATION_REVOKED)
+ | (1 << MELT_ERROR_INSUFFICIENT_FUNDS) /* TODO: is this still correct? Compare exchange_do_refresh.sql */
+ | (1 << MELT_ERROR_KEYS_MISSING);
+
+#define IDEMPOTENCY_CHECK_REQUIRED(error) \
+ (0 != (idempotency_check_required & (1 << (error))))
+
+/**
+ * Context for a /melt request
+ */
+struct MeltContext
+{
+
+ /**
+ * This struct is kept in a DLL.
+ */
+ struct MeltContext *prev;
+ struct MeltContext *next;
+
+ /**
+ * Processing phase we are in.
+ * The ordering here partially matters, as we progress through
+ * them by incrementing the phase in the happy path.
+ */
+ enum MeltPhase
+ {
+ MELT_PHASE_PARSE,
+ MELT_PHASE_CHECK_MELT_VALID,
+ MELT_PHASE_CHECK_KEYS,
+ MELT_PHASE_CHECK_COIN_SIGNATURE,
+ MELT_PHASE_PREPARE_TRANSACTION,
+ MELT_PHASE_RUN_TRANSACTION,
+ MELT_PHASE_GENERATE_REPLY_SUCCESS,
+ MELT_PHASE_GENERATE_REPLY_ERROR,
+ MELT_PHASE_RETURN_NO,
+ MELT_PHASE_RETURN_YES,
+ } phase;
+
+
+ /**
+ * Request context
+ */
+ const struct TEH_RequestContext *rc;
+
+ /**
+ * Current time for the DB transaction.
+ */
+ struct GNUNET_TIME_Timestamp now;
+
+ /**
+ * The current key state
+ */
+ struct TEH_KeyStateHandle *ksh;
+
+ /**
+ * The melted coin's denomination key
+ */
+ struct TEH_DenominationKey *melted_coin_denom;
+
+ /**
+ * Set to true if this coin's denomination was revoked and the operation
+ * is thus only allowed for zombie coins where the transaction
+ * history includes a #TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP.
+ *
+ * TODO: find a better terminology. The sentences in the comments containing
+ * "zombie" make semantically _no sense_!
+ */
+ bool zombie_required;
+
+ /**
+ * We already checked and noticed that the coin is known. Hence we
+ * can skip the "ensure_coin_known" step of the transaction.
+ */
+ bool coin_is_known;
+
+ /**
+ * UUID of the coin in the known_coins table.
+ */
+ uint64_t known_coin_id;
+
+ /**
+ * Captures all parameters provided in the JSON request
+ */
+ struct
+ {
+
+ /**
+ * All fields (from the request or computed)
+ * that we persist in the database.
+ */
+ struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS refresh;
+
+ /**
+ * In some error cases we check for idempotency.
+ * If we find an entry in the database, we mark this here.
+ */
+ bool is_idempotent;
+
+ /**
+ * In some error conditions the request is checked
+ * for idempotency and the result from the database
+ * is stored here.
+ */
+ struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS refresh_idem;
+
+ /**
+ * True if @e blinding_seed is missing in the request
+ */
+ bool no_blinding_seed;
+
+ /**
+ * Array @e persis.num_coins of hashes of the public keys
+ * of the denominations to refresh.
+ */
+ struct TALER_DenominationHashP *denoms_h;
+
+ /**
+ * Array of @e refresh.num_coins blinded coin planchets, arranged
+ * in runs of @e refresh.num_coins coins, [0..num_coins)..[0..num_coins),
+ * one for each kappa value.
+ */
+ struct TALER_BlindedPlanchet *planchets[TALER_CNC_KAPPA];
+
+ /**
+ * @since vDOLDPLUS
+ * #TALER_CNC_KAPPA arrays of @e refresh.num_coins transfer public keys
+ * in runs of @e num_coins coins, [0..num_coins)..[0..num_coins),
+ * one for each kappa value.
+ *
+ * MAYBE null. If the client has NOT provided the transfer_pubs in the request,
+ * @e refresh.is_v27_refresh will be true.
+ *
+ * TODO: Once v27 clients are gone, this MUST NOT be nulls.
+ */
+ struct TALER_TransferPublicKeyP *transfer_pubs[TALER_CNC_KAPPA];
+
+ /**
+ * #TALER_CNC_KAPPA hashes of the batches of @e num_coins coins.
+ */
+ struct TALER_KappaHashBlindedPlanchetsP kappa_planchets_h;
+
+ /**
+ * Array @e withdraw.num_r_pubs of indices into @e denoms_h
+ * of CS denominations.
+ */
+ uint32_t *cs_indices;
+
+ /**
+ * Total (over all coins) amount (excluding fee) committed for the refresh
+ */
+ struct TALER_Amount amount;
+
+ } request;
+
+ /**
+ * Errors occurring during evaluation of the request are captured in this
+ * struct. In phase WITHDRAW_PHASE_GENERATE_REPLY_ERROR an appropriate error
+ * message is prepared and sent to the client.
+ */
+ struct
+ {
+ /* The (internal) error code */
+ enum MeltError code;
+
+ /**
+ * Some errors require details to be sent to the client.
+ * These are captured in this union.
+ * Each field is named according to the error that is using it, except
+ * commented otherwise.
+ */
+ union
+ {
+ const char *request_parameter_malformed;
+
+ /**
+ * For all errors related to a particular denomination, i.e.
+ * #MELT_ERROR_DENOMINATION_KEY_UNKNOWN,
+ * #MELT_ERROR_DENOMINATION_EXPIRED,
+ * #MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
+ * #MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
+ * we use this one field.
+ */
+ struct TALER_DenominationHashP denom_h;
+
+ const char *db_fetch_context;
+
+ enum TALER_ErrorCode ec_confirmation_sign;
+
+ enum TALER_ErrorCode ec_denomination_sign;
+
+ /* remaining value of the coin */
+ struct TALER_Amount insufficient_funds;
+
+ } details;
+ } error;
+};
+
+/**
+ * The following macros set the given error code,
+ * set the phase to Melt_PHASE_GENERATE_REPLY_ERROR,
+ * and optionally set the given field (with an optionally given value).
+ */
+#define SET_ERROR(mc, ec) \
+ do \
+ { GNUNET_static_assert (MELT_ERROR_NONE != ec); \
+ (mc)->error.code = (ec); \
+ (mc)->phase = MELT_PHASE_GENERATE_REPLY_ERROR; } while (0)
+
+#define SET_ERROR_WITH_FIELD(mc, ec, field) \
+ do \
+ { GNUNET_static_assert (MELT_ERROR_NONE != ec); \
+ (mc)->error.code = (ec); \
+ (mc)->error.details.field = (field); \
+ (mc)->phase = MELT_PHASE_GENERATE_REPLY_ERROR; } while (0)
+
+#define SET_ERROR_WITH_DETAIL(mc, ec, field, value) \
+ do \
+ { GNUNET_static_assert (MELT_ERROR_NONE != ec); \
+ (mc)->error.code = (ec); \
+ (mc)->error.details.field = (value); \
+ (mc)->phase = MELT_PHASE_GENERATE_REPLY_ERROR; } while (0)
+
+
+/**
+ * All melt context is kept in a DLL.
+ */
+static struct MeltContext *mc_head;
+static struct MeltContext *mc_tail;
+
+void
+TEH_melt_cleanup ()
+{
+ struct MeltContext *mc;
+
+ while (NULL != (mc = mc_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (mc_head,
+ mc_tail,
+ mc);
+ MHD_resume_connection (mc->rc->connection);
+ }
+}
+
+
+/**
+ * Terminate the main loop by returning the final result.
+ *
+ * @param[in,out] mc context to update phase for
+ * @param mres MHD status to return
+ */
+static void
+finish_loop (struct MeltContext *mc,
+ MHD_RESULT mres)
+{
+ mc->phase = (MHD_YES == mres)
+ ? MELT_PHASE_RETURN_YES
+ : MELT_PHASE_RETURN_NO;
+}
+
+
+/**
+ * Free information in @a re, but not @a re itself.
+ *
+ * @param[in] re refresh data to free
+ */
+static void
+free_refresh (struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *re)
+{
+ if (NULL != re->denom_sigs)
+ {
+ for (size_t i = 0; i<re->num_coins; i++)
+ TALER_blinded_denom_sig_free (&re->denom_sigs[i]);
+ GNUNET_free (re->denom_sigs);
+ }
+ GNUNET_free (re->cs_r_values);
+ GNUNET_free (re->denom_serials);
+ GNUNET_free (re->denom_pub_hashes);
+ TALER_denom_sig_free (&re->coin.denom_sig);
+}
+
+
+/**
+ * Cleanup routine for melt request.
+ * The function is called upon completion of the request
+ * that should clean up @a rh_ctx.
+ *
+ * @param rc request context to clean up
+ */
+static void
+clean_melt_rc (struct TEH_RequestContext *rc)
+{
+ struct MeltContext *mc = rc->rh_ctx;
+
+ GNUNET_free (mc->request.denoms_h);
+ for (uint8_t k = 0; k<TALER_CNC_KAPPA; k++)
+ {
+ for (size_t i = 0; i<mc->request.refresh.num_coins; i++)
+ TALER_blinded_planchet_free (&mc->request.planchets[k][i]);
+ GNUNET_free (mc->request.planchets[k]);
+ if (! mc->request.refresh.is_v27_refresh)
+ GNUNET_free (mc->request.transfer_pubs[k]);
+ }
+ free_refresh (&mc->request.refresh);
+ if (mc->request.is_idempotent)
+ free_refresh (&mc->request.refresh_idem);
+ GNUNET_free (mc->request.cs_indices);
+ GNUNET_free (mc);
+}
+
+
+/**
+ * Creates a new context for the incoming melt request
+ *
+ * @param mc melt request context
+ * @param root json body of the request
+ */
+static void
+phase_parse_request (
+ struct MeltContext *mc,
+ const json_t *root)
+{
+ const json_t *j_denoms_h;
+ const json_t *j_coin_evs;
+ const json_t *j_transfer_pubs;
+ enum GNUNET_GenericReturnValue res;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
+ &mc->request.refresh.coin.coin_pub),
+ GNUNET_JSON_spec_fixed_auto ("old_denom_pub_h",
+ &mc->request.refresh.coin.denom_pub_hash),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("old_age_commitment_h",
+ &mc->request.refresh.coin.h_age_commitment),
+ &mc->request.refresh.coin.no_age_commitment),
+ TALER_JSON_spec_denom_sig ("old_denom_sig",
+ &mc->request.refresh.coin.denom_sig),
+ GNUNET_JSON_spec_fixed_auto ("refresh_seed",
+ &mc->request.refresh.refresh_seed),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("blinding_seed",
+ &mc->request.refresh.blinding_seed),
+ &mc->request.refresh.no_blinding_seed),
+ TALER_JSON_spec_amount ("value_with_fee",
+ TEH_currency,
+ &mc->request.refresh.amount_with_fee),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("transfer_pubs",
+ &j_transfer_pubs),
+ &mc->request.refresh.is_v27_refresh),
+ GNUNET_JSON_spec_array_const ("denoms_h",
+ &j_denoms_h),
+ GNUNET_JSON_spec_array_const ("coin_evs",
+ &j_coin_evs),
+ GNUNET_JSON_spec_fixed_auto ("confirm_sig",
+ &mc->request.refresh.coin_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ res = TALER_MHD_parse_json_data (mc->rc->connection,
+ root,
+ spec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ mc->phase = (GNUNET_NO == res)
+ ? MELT_PHASE_RETURN_YES
+ : MELT_PHASE_RETURN_NO;
+ return;
+ }
+
+ /* validate array size */
+ GNUNET_static_assert (
+ TALER_MAX_COINS < INT_MAX / TALER_CNC_KAPPA);
+
+ mc->request.refresh.num_coins = json_array_size (j_denoms_h);
+ if (0 == mc->request.refresh.num_coins)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
+ request_parameter_malformed,
+ "denoms_h must not be empty");
+ return;
+ }
+ if (TALER_MAX_COINS < mc->request.refresh.num_coins)
+ {
+ /**
+ * The wallet had committed to more than the maximum coins allowed, the
+ * reserve has been charged, but now the user can not melt any money
+ * from it. Note that the user can't get their money back in this case!
+ */
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
+ request_parameter_malformed,
+ "maximum number of coins that can be refreshed has been exceeded");
+ return;
+ }
+ if (TALER_CNC_KAPPA != json_array_size (j_coin_evs))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
+ request_parameter_malformed,
+ "coin_evs must be an array of length "TALER_CNC_KAPPA_STR);
+ return;
+ }
+ if (! mc->request.refresh.is_v27_refresh &&
+ (TALER_CNC_KAPPA != json_array_size (j_transfer_pubs)))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
+ request_parameter_malformed,
+ "transfer_pubs must be an array of length "TALER_CNC_KAPPA_STR);
+ return;
+ }
+
+
+ /* Extract the denomination hashes */
+ {
+ size_t idx;
+ json_t *value;
+
+ mc->request.denoms_h
+ = GNUNET_new_array (mc->request.refresh.num_coins,
+ struct TALER_DenominationHashP);
+
+ json_array_foreach (j_denoms_h, idx, value) {
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL,
+ &mc->request.denoms_h[idx]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ res = TALER_MHD_parse_json_data (mc->rc->connection,
+ value,
+ ispec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0);
+ mc->phase = (GNUNET_NO == res)
+ ? MELT_PHASE_RETURN_YES
+ : MELT_PHASE_RETURN_NO;
+ return;
+ }
+ }
+ }
+
+ /* Parse blinded envelopes. */
+ {
+ json_t *j_kappa_planchets;
+ size_t kappa;
+ struct GNUNET_HashContext *ctx;
+
+ /* ctx to calculate the planchet_h */
+ ctx = GNUNET_CRYPTO_hash_context_start ();
+ GNUNET_assert (NULL != ctx);
+
+ json_array_foreach (j_coin_evs, kappa, j_kappa_planchets)
+ {
+ json_t *j_cev;
+ size_t idx;
+
+ if (mc->request.refresh.num_coins != json_array_size (j_kappa_planchets))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
+ request_parameter_malformed,
+ "coin_evs[i][] size");
+ return;
+ }
+
+ mc->request.planchets[kappa] =
+ GNUNET_new_array (mc->request.refresh.num_coins,
+ struct TALER_BlindedPlanchet);
+
+ json_array_foreach (j_kappa_planchets, idx, j_cev)
+ {
+ /* Now parse the individual envelopes and calculate the hash of
+ * the commitment along the way. */
+ struct GNUNET_JSON_Specification kspec[] = {
+ TALER_JSON_spec_blinded_planchet (NULL,
+ &mc->request.planchets[kappa][idx]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ res = TALER_MHD_parse_json_data (mc->rc->connection,
+ j_cev,
+ kspec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ mc->phase = (GNUNET_NO == res)
+ ? MELT_PHASE_RETURN_YES
+ : MELT_PHASE_RETURN_NO;
+ return;
+ }
+ }
+ /* Save the hash of the batch of planchets for index kappa */
+ TALER_wallet_blinded_planchets_hash (
+ mc->request.refresh.num_coins,
+ mc->request.planchets[kappa],
+ mc->request.denoms_h,
+ &mc->request.kappa_planchets_h.tuple[kappa]);
+ GNUNET_CRYPTO_hash_context_read (
+ ctx,
+ &mc->request.kappa_planchets_h.tuple[kappa],
+ sizeof(mc->request.kappa_planchets_h.tuple[kappa]));
+ }
+ /* Finally calculate the total hash over all planchets */
+ GNUNET_CRYPTO_hash_context_finish (
+ ctx,
+ &mc->request.refresh.planchets_h.hash);
+ }
+ /* Check for duplicate planchets. Technically a bug on
+ * the client side that is harmless for us, but still
+ * not allowed per protocol
+ */
+ {
+ size_t max_idx = TALER_CNC_KAPPA * mc->request.refresh.num_coins;
+
+ for (size_t I = 0; I < max_idx - 1; I++)
+ {
+ size_t ki = I / mc->request.refresh.num_coins;
+ size_t ni = I % mc->request.refresh.num_coins;
+
+ for (size_t J = I + 1; J < max_idx; J++)
+ {
+ size_t kj = J / mc->request.refresh.num_coins;
+ size_t nj = J % mc->request.refresh.num_coins;
+
+ if (0 == TALER_blinded_planchet_cmp (
+ &mc->request.planchets[ki][ni],
+ &mc->request.planchets[kj][nj]))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_DUPLICATE_TRANSFER_PUB);
+ return;
+ }
+ }
+ }
+ }
+
+ /* Parse the transfer public keys, if applicable */
+ if (! mc->request.refresh.is_v27_refresh)
+ {
+ json_t *j_ktp;
+ size_t kappa;
+
+ json_array_foreach (j_transfer_pubs, kappa, j_ktp)
+ {
+ if (mc->request.refresh.num_coins !=
+ json_array_size (j_ktp))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
+ request_parameter_malformed,
+ "transfer_pubs[i][] size");
+ return;
+ }
+
+ mc->request.transfer_pubs[kappa] =
+ GNUNET_new_array (mc->request.refresh.num_coins,
+ struct TALER_TransferPublicKeyP);
+
+ /* Parse the batch of @e num_coins transfer public keys
+ * at index kappa */
+ {
+ struct GNUNET_JSON_Specification ktp_spec[] = {
+ TALER_JSON_spec_array_fixed (NULL,
+ mc->request.refresh.num_coins,
+ mc->request.transfer_pubs[kappa],
+ sizeof(*mc->request.transfer_pubs[kappa])),
+ GNUNET_JSON_spec_end ()
+ };
+
+ res = TALER_MHD_parse_json_data (mc->rc->connection,
+ j_ktp,
+ ktp_spec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ mc->phase = (GNUNET_NO == res)
+ ? MELT_PHASE_RETURN_YES
+ : MELT_PHASE_RETURN_NO;
+ return;
+ }
+ }
+ }
+ /* Check for duplicate transfer public keys. Technically a bug on
+ * the client side that is harmless for us, but still
+ * not allowed per protocol
+ */
+ size_t max_idx = TALER_CNC_KAPPA * mc->request.refresh.num_coins;
+
+ for (size_t I = 0; I < max_idx - 1; I++)
+ {
+ size_t ki = I / mc->request.refresh.num_coins;
+ size_t ni = I % mc->request.refresh.num_coins;
+
+ for (size_t J = I + 1; J < max_idx; J++)
+ {
+ size_t kj = J / mc->request.refresh.num_coins;
+ size_t nj = J % mc->request.refresh.num_coins;
+
+ if (0 == GNUNET_memcmp (
+ &mc->request.transfer_pubs[ki][ni],
+ &mc->request.transfer_pubs[kj][nj]))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_DUPLICATE_TRANSFER_PUB);
+ return;
+ }
+ }
+ }
+ }
+
+ mc->ksh = TEH_keys_get_state ();
+ if (NULL == mc->ksh)
+ {
+ GNUNET_break (0);
+ SET_ERROR (mc,
+ MELT_ERROR_KEYS_MISSING);
+ return;
+ }
+ mc->phase = MELT_PHASE_CHECK_MELT_VALID;
+}
+
+
+/**
+ * Check if the given denomination is still or already valid, has not been
+ * revoked and potentically supports age restriction.
+ *
+ * @param[in,out] mc context for the melt operation
+ * @param denom_h Hash of the denomination key to check
+ * @param[out] pdk denomination key found, might be NULL
+ * @return #GNUNET_OK when denomation was found and valid,
+ * #GNUNET_NO when denomination is not valid at this time
+ * #GNUNET_SYSERR otherwise (denomination invalid), with finish_loop called.
+ */
+static enum GNUNET_GenericReturnValue
+find_denomination (
+ struct MeltContext *mc,
+ const struct TALER_DenominationHashP *denom_h,
+ struct TEH_DenominationKey **pdk)
+{
+ struct TEH_DenominationKey *dk;
+
+ *pdk = NULL;
+ GNUNET_assert (NULL != mc->ksh);
+ dk = TEH_keys_denomination_by_hash_from_state (mc->ksh,
+ denom_h,
+ NULL,
+ NULL);
+ if (NULL == dk)
+ {
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_DENOMINATION_KEY_UNKNOWN,
+ denom_h,
+ *denom_h);
+ return GNUNET_SYSERR;
+ }
+ *pdk = dk;
+
+ if (GNUNET_TIME_absolute_is_past (
+ dk->meta.expire_withdraw.abs_time))
+ {
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_DENOMINATION_EXPIRED,
+ denom_h,
+ *denom_h);
+ /**
+ * Note that we return GNUNET_NO here.
+ * This way phase_check_melt_valid can react
+ * to it as a non-error case and do the zombie check.
+ */
+ return GNUNET_NO;
+ }
+
+ if (GNUNET_TIME_absolute_is_future (
+ dk->meta.start.abs_time))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
+ denom_h,
+ *denom_h);
+ return GNUNET_SYSERR;
+ }
+
+ if (dk->recoup_possible)
+ {
+ SET_ERROR (mc,
+ MELT_ERROR_DENOMINATION_REVOKED);
+ return GNUNET_SYSERR;
+ }
+
+ /* In case of age melt, make sure that the denomination supports age restriction */
+ if (! (mc->request.refresh.coin.no_age_commitment) &&
+ (0 == dk->denom_pub.age_mask.bits))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
+ denom_h,
+ *denom_h);
+ return GNUNET_SYSERR;
+ }
+ if ((mc->request.refresh.coin.no_age_commitment) &&
+ (0 != dk->denom_pub.age_mask.bits))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_AGE_RESTRICTION_COMMITMENT_INVALID);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check if the given array of hashes of denomination_keys
+ * - belong to valid denominations
+ * - calculate the total amount of the denominations including fees
+ * for melt.
+ *
+ * @param mc context of the melt to check keys for
+ */
+static void
+phase_check_keys (
+ struct MeltContext *mc)
+{
+ bool is_cs_denom[mc->request.refresh.num_coins];
+
+ memset (is_cs_denom,
+ 0,
+ sizeof(is_cs_denom));
+
+ mc->request.refresh.denom_serials =
+ GNUNET_new_array (mc->request.refresh.num_coins,
+ uint64_t);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &mc->request.amount));
+
+ /* Calculate the total value and withdraw fees for the fresh coins */
+ for (size_t i = 0; i < mc->request.refresh.num_coins; i++)
+ {
+ struct TEH_DenominationKey *dk;
+
+ if (GNUNET_OK !=
+ find_denomination (mc,
+ &mc->request.denoms_h[i],
+ &dk))
+ return;
+
+ if (GNUNET_CRYPTO_BSA_CS ==
+ dk->denom_pub.bsign_pub_key->cipher)
+ {
+ if (mc->request.refresh.no_blinding_seed)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_BLINDING_SEED_REQUIRED);
+ return;
+ }
+ mc->request.refresh.num_cs_r_values++;
+ is_cs_denom[i] = true;
+ }
+ /* Ensure the ciphers from the planchets match the denominations'. */
+ {
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ if (dk->denom_pub.bsign_pub_key->cipher !=
+ mc->request.planchets[k][i].blinded_message->cipher)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_COIN_CIPHER_MISMATCH);
+ return;
+ }
+ }
+ }
+ /* Accumulate the values */
+ if (0 > TALER_amount_add (&mc->request.amount,
+ &mc->request.amount,
+ &dk->meta.value))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_AMOUNT_OVERFLOW);
+ return;
+ }
+ /* Accumulate the withdraw fees for the fresh coins */
+ if (0 > TALER_amount_add (&mc->request.amount,
+ &mc->request.amount,
+ &dk->meta.fees.withdraw))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW);
+ return;
+ }
+ mc->request.refresh.denom_serials[i] = dk->meta.serial;
+ }
+
+ /**
+ * Calculate the amount (with withdraw fee) plus refresh fee and
+ * compare with the value provided by the client in the request.
+ */
+ {
+ struct TALER_Amount amount_with_fee;
+
+ if (0 > TALER_amount_add (&amount_with_fee,
+ &mc->request.amount,
+ &mc->melted_coin_denom->meta.fees.refresh))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW);
+ return;
+ }
+
+ if (0 != TALER_amount_cmp (&amount_with_fee,
+ &mc->request.refresh.amount_with_fee))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_AMOUNT_WITH_FEE_INCORRECT);
+ return;
+ }
+ }
+
+ /* Save the indices of CS denominations */
+ if (0 < mc->request.refresh.num_cs_r_values)
+ {
+ size_t j = 0;
+
+ mc->request.cs_indices = GNUNET_new_array (
+ mc->request.refresh.num_cs_r_values,
+ uint32_t);
+
+ for (size_t i = 0; i < mc->request.refresh.num_coins; i++)
+ {
+ if (is_cs_denom[i])
+ mc->request.cs_indices[j++] = i;
+ }
+ }
+ mc->phase++;
+}
+
+
+/**
+ * Check that the client signature authorizing the melt is valid.
+ *
+ * @param[in,out] mc request context to check
+ */
+static void
+phase_check_coin_signature (
+ struct MeltContext *mc)
+{
+ /* We can now compute the commitment */
+ {
+ struct TALER_KappaHashBlindedPlanchetsP k_bps_h = {0};
+ struct TALER_KappaTransferPublicKeys k_transfer_pubs = {0};
+
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ TALER_wallet_blinded_planchets_hash (
+ mc->request.refresh.num_coins,
+ mc->request.planchets[k],
+ mc->request.denoms_h,
+ &k_bps_h.tuple[k]);
+
+ if (! mc->request.refresh.is_v27_refresh)
+ {
+ k_transfer_pubs.num_transfer_pubs = mc->request.refresh.num_coins;
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ k_transfer_pubs.batch[k] = &mc->request.refresh.transfer_pubs[k];
+ }
+
+ TALER_refresh_get_commitment (
+ &mc->request.refresh.rc,
+ &mc->request.refresh.refresh_seed,
+ mc->request.no_blinding_seed
+ ? NULL
+ : &mc->request.refresh.blinding_seed,
+ mc->request.refresh.is_v27_refresh
+ ? NULL
+ : &k_transfer_pubs,
+ &k_bps_h,
+ &mc->request.refresh.coin.coin_pub,
+ &mc->request.refresh.amount_with_fee);
+ }
+
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_melt_verify (
+ &mc->request.refresh.amount_with_fee,
+ &mc->melted_coin_denom->meta.fees.refresh,
+ &mc->request.refresh.rc,
+ &mc->request.refresh.coin.denom_pub_hash,
+ &mc->request.refresh.coin.h_age_commitment,
+ &mc->request.refresh.coin.coin_pub,
+ &mc->request.refresh.coin_sig))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_COIN_SIGNATURE_INVALID);
+ return;
+ }
+
+ mc->phase++;
+}
+
+
+/**
+ * Check for information about the melted coin's denomination,
+ * extracting its validity status and fee structure.
+ * Baseline: check if deposits/refreshes are generally
+ * simply still allowed for this denomination.
+ *
+ * @param mc parsed request information
+ */
+static void
+phase_check_melt_valid (struct MeltContext *mc)
+{
+ enum MeltPhase current_phase = mc->phase;
+ /**
+ * Find the old coin's denomination.
+ * Note that we return only on GNUNET_SYSERR,
+ * because GNUNET_NO for the expired denomination
+ * will be handled below, with the zombie-check.
+ */
+ if (GNUNET_SYSERR ==
+ find_denomination (mc,
+ &mc->request.refresh.coin.denom_pub_hash,
+ &mc->melted_coin_denom))
+ return;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Melted coin's denomination is worth %s\n",
+ TALER_amount2s (&mc->melted_coin_denom->meta.value));
+
+ /* sanity-check that "total melt amount > melt fee" */
+ if (0 <
+ TALER_amount_cmp (&mc->melted_coin_denom->meta.fees.refresh,
+ &mc->request.refresh.amount_with_fee))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_FEES_EXCEED_CONTRIBUTION);
+ return;
+ }
+
+ if (GNUNET_OK !=
+ TALER_test_coin_valid (&mc->request.refresh.coin,
+ &mc->melted_coin_denom->denom_pub))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_DENOMINATION_SIGNATURE_INVALID);
+ return;
+ }
+
+ /**
+ * find_denomination might have set the phase to
+ * produce an error, but we are still investigating.
+ * We reset the phase.
+ */
+ mc->phase = current_phase;
+ mc->error.code = MELT_ERROR_NONE;
+
+ if (GNUNET_TIME_absolute_is_past (
+ mc->melted_coin_denom->meta.expire_deposit.abs_time))
+ {
+ /**
+ * We are past deposit expiration time, but maybe this is a zombie?
+ */
+ struct TALER_DenominationHashP denom_hash;
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Check that the coin is dirty (we have seen it before), as we will
+ not just allow melting of a *fresh* coin where the denomination was
+ revoked (those must be recouped) */
+ qs = TEH_plugin->get_coin_denomination (
+ TEH_plugin->cls,
+ &mc->request.refresh.coin.coin_pub,
+ &mc->known_coin_id,
+ &denom_hash);
+ if (0 > qs)
+ {
+ /* There is no good reason for a serialization failure here: */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ SET_ERROR (mc,
+ MELT_ERROR_DB_FETCH_FAILED);
+ return;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ /* We never saw this coin before, so _this_ justification is not OK.
+ * Note that the error was already set in find_denominations. */
+ GNUNET_assert (MELT_ERROR_DENOMINATION_EXPIRED ==
+ mc->error.code);
+ GNUNET_assert (MELT_PHASE_GENERATE_REPLY_ERROR ==
+ mc->phase);
+ return;
+ }
+ /* sanity check */
+ if (0 !=
+ GNUNET_memcmp (&denom_hash,
+ &mc->request.refresh.coin.denom_pub_hash))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_COIN_CONFLICTING_DENOMINATION_KEY,
+ denom_h,
+ denom_hash);
+ return;
+ }
+ /* Minor optimization: no need to run the
+ "ensure_coin_known" part of the transaction */
+ mc->coin_is_known = true;
+ /* check later that zombie is satisfied */
+ mc->zombie_required = true;
+ }
+ mc->phase++;
+}
+
+
+/**
+ * The request for melt was parsed successfully.
+ * Sign and persist the chosen blinded coins for the reveal step.
+ *
+ * @param mc The context for the current melt request
+ */
+static void
+phase_prepare_transaction (
+ struct MeltContext *mc)
+{
+ mc->request.refresh.denom_sigs
+ = GNUNET_new_array (
+ mc->request.refresh.num_coins,
+ struct TALER_BlindedDenominationSignature);
+ mc->request.refresh.noreveal_index =
+ GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG,
+ TALER_CNC_KAPPA);
+
+ /* Choose and sign the coins */
+ {
+ struct TEH_CoinSignData csds[mc->request.refresh.num_coins];
+ enum TALER_ErrorCode ec_denomination_sign;
+ size_t noreveal_idx = mc->request.refresh.noreveal_index;
+
+ memset (csds,
+ 0,
+ sizeof(csds));
+
+ /* Pick the chosen blinded coins */
+ for (size_t i = 0; i<mc->request.refresh.num_coins; i++)
+ {
+ csds[i].bp = &mc->request.planchets[noreveal_idx][i];
+ csds[i].h_denom_pub = &mc->request.denoms_h[i];
+ }
+
+ ec_denomination_sign = TEH_keys_denomination_batch_sign (
+ mc->request.refresh.num_coins,
+ csds,
+ true, /* for melt */
+ mc->request.refresh.denom_sigs);
+ if (TALER_EC_NONE != ec_denomination_sign)
+ {
+ GNUNET_break (0);
+ SET_ERROR_WITH_FIELD (mc,
+ MELT_ERROR_DENOMINATION_SIGN,
+ ec_denomination_sign);
+ return;
+ }
+
+ /* Save the hash of chosen planchets */
+ mc->request.refresh.selected_h =
+ mc->request.kappa_planchets_h.tuple[noreveal_idx];
+
+ /* If applicable, save the chosen transfer public keys */
+ if (! mc->request.refresh.is_v27_refresh)
+ mc->request.refresh.transfer_pubs =
+ mc->request.transfer_pubs[noreveal_idx];
+
+ /**
+ * For the denominations with cipher CS, calculate the R-values
+ * and save the choices we made now, as at a later point, the
+ * private keys for the denominations might now be available anymore
+ * to make the same choice again.
+ */
+ if (0 < mc->request.refresh.num_cs_r_values)
+ {
+ size_t num_cs_r_values = mc->request.refresh.num_cs_r_values;
+ struct TEH_CsDeriveData cdds[num_cs_r_values];
+ struct GNUNET_CRYPTO_CsSessionNonce nonces[num_cs_r_values];
+
+ memset (nonces,
+ 0,
+ sizeof(nonces));
+ mc->request.refresh.cs_r_values =
+ GNUNET_new_array (num_cs_r_values,
+ struct GNUNET_CRYPTO_CSPublicRPairP);
+ mc->request.refresh.cs_r_choices = 0;
+
+ GNUNET_assert (! mc->request.refresh.no_blinding_seed);
+ TALER_cs_derive_nonces_from_seed (
+ &mc->request.refresh.blinding_seed,
+ true, /* for melt */
+ num_cs_r_values,
+ mc->request.cs_indices,
+ nonces);
+
+ for (size_t i = 0; i < num_cs_r_values; i++)
+ {
+ size_t idx = mc->request.cs_indices[i];
+
+ GNUNET_assert (idx < mc->request.refresh.num_coins);
+ cdds[i].h_denom_pub = &mc->request.denoms_h[idx];
+ cdds[i].nonce = &nonces[i];
+ }
+
+ /**
+ * Let the crypto helper generate the R-values and
+ * make the choices
+ */
+ if (TALER_EC_NONE !=
+ TEH_keys_denomination_cs_batch_r_pub_simple (
+ mc->request.refresh.num_cs_r_values,
+ cdds,
+ true, /* for melt */
+ mc->request.refresh.cs_r_values))
+ {
+ GNUNET_break (0);
+ SET_ERROR (mc,
+ MELT_ERROR_CRYPTO_HELPER);
+ return;
+ }
+
+ /* Now save the choices for the selected bits */
+ for (size_t i = 0; i < num_cs_r_values; i++)
+ {
+ size_t idx = mc->request.cs_indices[i];
+
+ struct TALER_BlindedDenominationSignature *sig =
+ &mc->request.refresh.denom_sigs[idx];
+ uint8_t bit = sig->blinded_sig->details.blinded_cs_answer.b;
+
+ mc->request.refresh.cs_r_choices |= bit << i;
+ GNUNET_static_assert (
+ TALER_MAX_COINS <=
+ sizeof(mc->request.refresh.cs_r_choices) * 8);
+ }
+ }
+ }
+ mc->phase++;
+}
+
+
+/**
+ * Generates response for the melt request.
+ *
+ * @param mc melt operation context
+ */
+static void
+phase_generate_reply_success (struct MeltContext *mc)
+{
+ struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *db_obj;
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+ enum TALER_ErrorCode ec_confirmation_sign;
+
+ db_obj = mc->request.is_idempotent
+ ? &mc->request.refresh_idem
+ : &mc->request.refresh;
+ ec_confirmation_sign =
+ TALER_exchange_online_melt_confirmation_sign (
+ &TEH_keys_exchange_sign_,
+ &db_obj->rc,
+ db_obj->noreveal_index,
+ &pub,
+ &sig);
+ if (TALER_EC_NONE != ec_confirmation_sign)
+ {
+ SET_ERROR_WITH_FIELD (mc,
+ MELT_ERROR_CONFIRMATION_SIGN,
+ ec_confirmation_sign);
+ return;
+ }
+
+ finish_loop (mc,
+ TALER_MHD_REPLY_JSON_PACK (
+ mc->rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_uint64 ("noreveal_index",
+ db_obj->noreveal_index),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub)));
+}
+
+
+/**
+ * Check if the melt request is replayed and we already have an answer.
+ * If so, replay the existing answer and return the HTTP response.
+ *
+ * @param[in,out] mc parsed request data
+ * @return true if the request is idempotent with an existing request
+ * false if we did not find the request in the DB and did not set @a mret
+ */
+static bool
+melt_is_idempotent (
+ struct MeltContext *mc)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->get_refresh (
+ TEH_plugin->cls,
+ &mc->request.refresh.rc,
+ &mc->request.refresh_idem);
+ if (0 > qs)
+ {
+ /* FIXME: soft error not handled correctly! */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_DB_FETCH_FAILED,
+ db_fetch_context,
+ "get_refresh");
+ return true; /* Well, kind-of. */
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return false;
+
+ mc->request.is_idempotent = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "request is idempotent\n");
+
+ /* Generate idempotent reply */
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_MELT]++;
+ mc->phase = MELT_PHASE_GENERATE_REPLY_SUCCESS;
+ mc->error.code = MELT_ERROR_NONE;
+ return true;
+}
+
+
+/**
+ * Reports an error, potentially with details.
+ * That is, it puts a error-type specific response into the MHD queue.
+ * It will do a idempotency check first, if needed for the error type.
+ *
+ * @param mc melt context
+ */
+static void
+phase_generate_reply_error (
+ struct MeltContext *mc)
+{
+ GNUNET_assert (MELT_PHASE_GENERATE_REPLY_ERROR == mc->phase);
+ GNUNET_assert (MELT_ERROR_NONE != mc->error.code);
+
+ if (IDEMPOTENCY_CHECK_REQUIRED (mc->error.code) &&
+ melt_is_idempotent (mc))
+ {
+ return;
+ }
+
+ switch (mc->error.code)
+ {
+ case MELT_ERROR_NONE:
+ break;
+ case MELT_ERROR_REQUEST_PARAMETER_MALFORMED:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ mc->error.details.request_parameter_malformed));
+ return;
+ case MELT_ERROR_KEYS_MISSING:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL));
+ return;
+ case MELT_ERROR_DB_FETCH_FAILED:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ mc->error.details.db_fetch_context));
+ return;
+ case MELT_ERROR_DB_INVARIANT_FAILURE:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ NULL));
+ return;
+ case MELT_ERROR_DB_PREFLIGHT_FAILURE:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ "make_coin_known"));
+ return;
+ case MELT_ERROR_DB_MAKE_COIN_KNOW_FAILURE:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "preflight failure"));
+ return;
+ case MELT_ERROR_COIN_UNKNOWN:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_EXCHANGE_GENERIC_COIN_UNKNOWN,
+ NULL));
+ return;
+ case MELT_COIN_CONFLICTING_DENOMINATION_KEY:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY,
+ TALER_B2S (&mc->error.details.denom_h)));
+ return;
+ case MELT_ERROR_COIN_EXPIRED_NO_ZOMBIE:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE,
+ NULL));
+ return;
+ case MELT_ERROR_DENOMINATION_SIGN:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ mc->error.details.ec_denomination_sign,
+ NULL));
+ return;
+ case MELT_ERROR_DENOMINATION_SIGNATURE_INVALID:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (mc->rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
+ NULL));
+ return;
+ case MELT_ERROR_DENOMINATION_KEY_UNKNOWN:
+ GNUNET_break_op (0);
+ finish_loop (mc,
+ TEH_RESPONSE_reply_unknown_denom_pub_hash (
+ mc->rc->connection,
+ &mc->error.details.denom_h));
+ return;
+ case MELT_ERROR_DENOMINATION_EXPIRED:
+ GNUNET_break_op (0);
+ finish_loop (mc,
+ TEH_RESPONSE_reply_expired_denom_pub_hash (
+ mc->rc->connection,
+ &mc->error.details.denom_h,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "MELT"));
+ return;
+ case MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE:
+ finish_loop (mc,
+ TEH_RESPONSE_reply_expired_denom_pub_hash (
+ mc->rc->connection,
+ &mc->error.details.denom_h,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "MELT"));
+ return;
+ case MELT_ERROR_DENOMINATION_REVOKED:
+ GNUNET_break_op (0);
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+ NULL));
+ return;
+ case MELT_ERROR_COIN_CIPHER_MISMATCH:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+ NULL));
+ return;
+ case MELT_ERROR_BLINDING_SEED_REQUIRED:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_GENERIC_PARAMETER_MISSING,
+ "blinding_seed"));
+ return;
+ case MELT_ERROR_CRYPTO_HELPER:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL));
+ return;
+ case MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION:
+ {
+ char msg[256];
+
+ GNUNET_snprintf (msg,
+ sizeof(msg),
+ "denomination %s does not support age restriction",
+ GNUNET_h2s (&mc->error.details.denom_h.hash));
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
+ msg));
+ return;
+ }
+ case MELT_ERROR_AGE_RESTRICTION_COMMITMENT_INVALID:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
+ "old_age_commitment_h"));
+ return;
+ case MELT_ERROR_AMOUNT_OVERFLOW:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
+ "amount"));
+ return;
+ case MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
+ "amount+fee"));
+ return;
+ case MELT_ERROR_FEES_EXCEED_CONTRIBUTION:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (mc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_MELT_FEES_EXCEED_CONTRIBUTION,
+ NULL));
+ return;
+ case MELT_ERROR_AMOUNT_WITH_FEE_INCORRECT:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
+ "value_with_fee incorrect"));
+ return;
+ case MELT_ERROR_CONFIRMATION_SIGN:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ mc->error.details.ec_confirmation_sign,
+ NULL));
+ return;
+ case MELT_ERROR_INSUFFICIENT_FUNDS:
+ finish_loop (mc,
+ TEH_RESPONSE_reply_coin_insufficient_funds (
+ mc->rc->connection,
+ TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+ &mc->request.refresh.coin.denom_pub_hash,
+ &mc->request.refresh.coin.coin_pub));
+ return;
+ case MELT_ERROR_DUPLICATE_PLANCHET:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED, /* FIXME: new error! */
+ "duplicate planchet"));
+ return;
+ case MELT_ERROR_DUPLICATE_TRANSFER_PUB:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED, /* FIXME: new error! */
+ "duplicate transfer_pub"));
+ return;
+ case MELT_ERROR_NONCE_RESUSE:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED, /* FIXME: new error */
+ "nonce reuse"));
+ return;
+ case MELT_ERROR_COIN_SIGNATURE_INVALID:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_EXCHANGE_MELT_COIN_SIGNATURE_INVALID,
+ NULL));
+ return;
+ }
+ GNUNET_break (0);
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "error phase without error"));
+}
+
+
+/**
+ * Function implementing melt transaction. Runs the
+ * transaction logic; IF it returns a non-error code, the transaction
+ * logic MUST NOT queue a MHD response. IF it returns an hard error,
+ * the transaction logic MUST queue a MHD response and set @a mhd_ret.
+ * IF it returns the soft error code, the function MAY be called again
+ * to retry and MUST not queue a MHD response.
+ *
+ * @param cls a `struct MeltContext *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+melt_transaction (
+ void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct MeltContext *mc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ bool balance_ok;
+ bool found;
+ bool nonce_reuse;
+ uint32_t noreveal_index;
+ struct TALER_Amount insufficient_funds;
+
+ (void) connection;
+ (void) mhd_ret;
+
+ qs = TEH_plugin->do_refresh (TEH_plugin->cls,
+ &mc->request.refresh,
+ &mc->now,
+ &found,
+ &noreveal_index,
+ &mc->zombie_required,
+ &nonce_reuse,
+ &balance_ok,
+ &insufficient_funds);
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_DB_FETCH_FAILED,
+ db_fetch_context,
+ "do_refresh");
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_COIN_UNKNOWN);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (found)
+ {
+ /**
+ * This request is idempotent, set the nonreveal_index
+ * to the previous one and reply success.
+ */
+ mc->request.refresh.noreveal_index = noreveal_index;
+ mc->phase = MELT_PHASE_GENERATE_REPLY_SUCCESS;
+ mc->error.code = MELT_ERROR_NONE;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+ if (nonce_reuse)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_NONCE_RESUSE);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! balance_ok)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_FIELD (mc,
+ MELT_ERROR_INSUFFICIENT_FUNDS,
+ insufficient_funds);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (mc->zombie_required)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_COIN_EXPIRED_NO_ZOMBIE);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_MELT]++;
+ return qs;
+}
+
+
+/**
+ * The request was prepared successfully.
+ * Run the main DB transaction.
+ *
+ * @param mc The context for the current melt request
+ */
+static void
+phase_run_transaction (
+ struct MeltContext *mc)
+{
+ if (GNUNET_SYSERR ==
+ TEH_plugin->preflight (TEH_plugin->cls))
+ {
+ GNUNET_break (0);
+ SET_ERROR (mc,
+ MELT_ERROR_DB_PREFLIGHT_FAILURE);
+ return;
+ }
+
+ /* first, make sure coin is known */
+ if (! mc->coin_is_known)
+ {
+ MHD_RESULT mhd_ret = -1;
+ enum GNUNET_DB_QueryStatus qs;
+
+ for (unsigned int tries = 0; tries<MAX_TRANSACTION_COMMIT_RETRIES; tries++)
+ {
+ qs = TEH_make_coin_known (&mc->request.refresh.coin,
+ mc->rc->connection,
+ &mc->known_coin_id,
+ &mhd_ret);
+ if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+ break;
+ }
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ /* Check if an answer has been queued */
+ switch (mhd_ret)
+ {
+ case MHD_NO:
+ mc->phase = MELT_PHASE_RETURN_NO;
+ return;
+ case MHD_YES:
+ mc->phase = MELT_PHASE_RETURN_YES;
+ return;
+ default: /* ignore */
+ ;
+ }
+ SET_ERROR (mc,
+ MELT_ERROR_DB_MAKE_COIN_KNOW_FAILURE);
+ return;
+ }
+ }
+
+ /* run main database transaction */
+ {
+ MHD_RESULT mhd_ret = -1;
+ enum GNUNET_GenericReturnValue ret;
+ enum MeltPhase current_phase = mc->phase;
+
+ GNUNET_assert (MELT_PHASE_RUN_TRANSACTION ==
+ current_phase);
+ ret = TEH_DB_run_transaction (mc->rc->connection,
+ "run melt",
+ TEH_MT_REQUEST_MELT,
+ &mhd_ret,
+ &melt_transaction,
+ mc);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break (0);
+ /* Check if an answer has been queued */
+ switch (mhd_ret)
+ {
+ case MHD_NO:
+ mc->phase = MELT_PHASE_RETURN_NO;
+ return;
+ case MHD_YES:
+ mc->phase = MELT_PHASE_RETURN_YES;
+ return;
+ default: /* ignore */
+ ;
+ }
+ GNUNET_assert (MELT_ERROR_NONE != mc->error.code);
+ GNUNET_assert (MELT_PHASE_GENERATE_REPLY_ERROR == mc->phase);
+ return;
+ }
+ /**
+ * In case of idempotency (which is not an error condition),
+ * the phase has changed in melt_transaction.
+ * We simple return.
+ */
+ if (current_phase != mc->phase)
+ return;
+ }
+ mc->phase++;
+}
+
+
+MHD_RESULT
+TEH_handler_melt (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[0])
+{
+ struct MeltContext *mc = rc->rh_ctx;
+
+ (void) args;
+ if (NULL == mc)
+ {
+ mc = GNUNET_new (struct MeltContext);
+ rc->rh_ctx = mc;
+ rc->rh_cleaner = &clean_melt_rc;
+ mc->rc = rc;
+ mc->now = GNUNET_TIME_timestamp_get ();
+ }
+
+ while (true)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "melt processing in phase %d\n",
+ mc->phase);
+ switch (mc->phase)
+ {
+ case MELT_PHASE_PARSE:
+ phase_parse_request (mc,
+ root);
+ break;
+ case MELT_PHASE_CHECK_MELT_VALID:
+ phase_check_melt_valid (mc);
+ break;
+ case MELT_PHASE_CHECK_KEYS:
+ phase_check_keys (mc);
+ break;
+ case MELT_PHASE_CHECK_COIN_SIGNATURE:
+ phase_check_coin_signature (mc);
+ break;
+ case MELT_PHASE_PREPARE_TRANSACTION:
+ phase_prepare_transaction (mc);
+ break;
+ case MELT_PHASE_RUN_TRANSACTION:
+ phase_run_transaction (mc);
+ break;
+ case MELT_PHASE_GENERATE_REPLY_SUCCESS:
+ phase_generate_reply_success (mc);
+ break;
+ case MELT_PHASE_GENERATE_REPLY_ERROR:
+ phase_generate_reply_error (mc);
+ break;
+ case MELT_PHASE_RETURN_YES:
+ return MHD_YES;
+ case MELT_PHASE_RETURN_NO:
+ return MHD_NO;
+ }
+ }
+}
diff --git a/src/exchange/taler-exchange-httpd_melt.h b/src/exchange/taler-exchange-httpd_melt.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 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 published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-exchange-httpd_melt.h
+ * @brief Handle /melt requests, starting with vDOLDPLUS of the protocol
+ * @author Özgür Kesim
+ */
+#ifndef TALER_EXCHANGE_HTTPD_MELT_H
+#define TALER_EXCHANGE_HTTPD_MELT_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+/**
+ * Resume suspended connections, we are shutting down.
+ */
+void
+TEH_melt_cleanup (void);
+
+
+/**
+ * Handle a "/melt" request. Parses the request into the JSON
+ * components and validates the melted coins, the signature and
+ * execute the melt as database transaction.
+ *
+ * @param rc the request context
+ * @param root uploaded JSON data
+ * @param args array of additional options, not used
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_melt (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[0]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_melt_v27.c b/src/exchange/taler-exchange-httpd_melt_v27.c
@@ -1,1725 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2025 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 published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-exchange-httpd_melt_v27.c
- * @brief Handle /melt requests
- * @note This endpoint is active since v27 of the protocol API
- * @author Özgür Kesim
- */
-
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include "taler-exchange-httpd.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_melt_v27.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler/taler_util.h"
-
-/**
- * The different type of errors that might occur, sorted by name.
- * Some of them require idempotency checks, which are marked
- * in @e idempotency_check_required below.
- */
-enum MeltError
-{
- MELT_ERROR_NONE = 0,
- MELT_ERROR_AGE_RESTRICTION_COMMITMENT_INVALID,
- MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
- MELT_ERROR_AMOUNT_OVERFLOW,
- MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW,
- MELT_ERROR_AMOUNT_WITH_FEE_INCORRECT,
- MELT_ERROR_BLINDING_SEED_REQUIRED,
- MELT_ERROR_COIN_CIPHER_MISMATCH,
- MELT_COIN_CONFLICTING_DENOMINATION_KEY,
- MELT_ERROR_COIN_EXPIRED_NO_ZOMBIE,
- MELT_ERROR_COIN_SIGNATURE_INVALID,
- MELT_ERROR_COIN_UNKNOWN,
- MELT_ERROR_CONFIRMATION_SIGN,
- MELT_ERROR_CRYPTO_HELPER,
- MELT_ERROR_DB_FETCH_FAILED,
- MELT_ERROR_DB_INVARIANT_FAILURE,
- MELT_ERROR_DB_MAKE_COIN_KNOW_FAILURE,
- MELT_ERROR_DB_PREFLIGHT_FAILURE,
- MELT_ERROR_DENOMINATION_EXPIRED,
- MELT_ERROR_DENOMINATION_KEY_UNKNOWN,
- MELT_ERROR_DENOMINATION_REVOKED,
- MELT_ERROR_DENOMINATION_SIGN,
- MELT_ERROR_DENOMINATION_SIGNATURE_INVALID,
- MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
- MELT_ERROR_IDEMPOTENT_PLANCHET,
- MELT_ERROR_INSUFFICIENT_FUNDS,
- MELT_ERROR_KEYS_MISSING,
- MELT_ERROR_FEES_EXCEED_CONTRIBUTION,
- MELT_ERROR_NONCE_RESUSE,
- MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
-};
-
-/**
- * With the bits set in this value will be mark the errors
- * that require a check for idempotency before actually
- * returning an error.
- */
-static const uint64_t idempotency_check_required =
- 0
- | (1 << MELT_ERROR_DENOMINATION_EXPIRED)
- | (1 << MELT_ERROR_DENOMINATION_KEY_UNKNOWN)
- | (1 << MELT_ERROR_DENOMINATION_REVOKED)
- | (1 << MELT_ERROR_INSUFFICIENT_FUNDS) /* TODO: is this still correct? Compare exchange_do_refresh.sql */
- | (1 << MELT_ERROR_KEYS_MISSING);
-
-#define IDEMPOTENCY_CHECK_REQUIRED(error) \
- (0 != (idempotency_check_required & (1 << (error))))
-
-/**
- * Context for a /melt request
- */
-struct MeltContext
-{
-
- /**
- * This struct is kept in a DLL.
- */
- struct MeltContext *prev;
- struct MeltContext *next;
-
- /**
- * Processing phase we are in.
- * The ordering here partially matters, as we progress through
- * them by incrementing the phase in the happy path.
- */
- enum MeltPhase
- {
- MELT_PHASE_PARSE,
- MELT_PHASE_CHECK_MELT_VALID,
- MELT_PHASE_CHECK_KEYS,
- MELT_PHASE_CHECK_COIN_SIGNATURE,
- MELT_PHASE_PREPARE_TRANSACTION,
- MELT_PHASE_RUN_TRANSACTION,
- MELT_PHASE_GENERATE_REPLY_SUCCESS,
- MELT_PHASE_GENERATE_REPLY_ERROR,
- MELT_PHASE_RETURN_NO,
- MELT_PHASE_RETURN_YES,
- } phase;
-
-
- /**
- * Request context
- */
- const struct TEH_RequestContext *rc;
-
- /**
- * Current time for the DB transaction.
- */
- struct GNUNET_TIME_Timestamp now;
-
- /**
- * The current key state
- */
- struct TEH_KeyStateHandle *ksh;
-
- /**
- * The melted coin's denomination key
- */
- struct TEH_DenominationKey *melted_coin_denom;
-
- /**
- * Set to true if this coin's denomination was revoked and the operation
- * is thus only allowed for zombie coins where the transaction
- * history includes a #TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP.
- */
- bool zombie_required;
-
- /**
- * We already checked and noticed that the coin is known. Hence we
- * can skip the "ensure_coin_known" step of the transaction.
- */
- bool coin_is_dirty;
-
- /**
- * UUID of the coin in the known_coins table.
- */
- uint64_t known_coin_id;
-
- /**
- * Captures all parameters provided in the JSON request
- */
- struct
- {
-
- /**
- * All fields (from the request or computed)
- * that we persist in the database.
- */
- struct TALER_EXCHANGEDB_Refresh_v27 refresh;
-
- /**
- * In some error cases we check for idempotency.
- * If we find an entry in the database, we mark this here.
- */
- bool is_idempotent;
-
- /**
- * In some error conditions the request is checked
- * for idempotency and the result from the database
- * is stored here.
- */
- struct TALER_EXCHANGEDB_Refresh_v27 refresh_idem;
-
- /**
- * True if @e blinding_seed is missing in the request
- */
- bool no_blinding_seed;
-
- /**
- * Array @e persis.num_coins of hashes of the public keys
- * of the denominations to refresh.
- */
- struct TALER_DenominationHashP *denoms_h;
-
- /**
- * Array of @e num_planchets coin planchets, arranged
- * in runs of @e num_coins coins, [0..num_coins)..[0..num_coins),
- * one for each kappa value.
- */
- struct TALER_BlindedPlanchet *planchets[TALER_CNC_KAPPA];
-
- /**
- * #TALER_CNC_KAPPA hashes of the batches of @e num_coins coins.
- */
- struct TALER_KappaHashBlindedPlanchetsP kappa_planchets_h;
-
- /**
- * Array @e withdraw.num_r_pubs of indices into @e denoms_h
- * of CS denominations.
- */
- uint32_t *cs_indices;
-
- /**
- * Total (over all coins) amount (excluding fee) committed for the refresh
- */
- struct TALER_Amount amount;
-
- } request;
-
- /**
- * Errors occurring during evaluation of the request are captured in this
- * struct. In phase WITHDRAW_PHASE_GENERATE_REPLY_ERROR an appropriate error
- * message is prepared and sent to the client.
- */
- struct
- {
- /* The (internal) error code */
- enum MeltError code;
-
- /**
- * Some errors require details to be sent to the client.
- * These are captured in this union.
- * Each field is named according to the error that is using it, except
- * commented otherwise.
- */
- union
- {
- const char *request_parameter_malformed;
-
- /**
- * For all errors related to a particular denomination, i.e.
- * #MELT_ERROR_DENOMINATION_KEY_UNKNOWN,
- * #MELT_ERROR_DENOMINATION_EXPIRED,
- * #MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
- * #MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
- * we use this one field.
- */
- struct TALER_DenominationHashP denom_h;
-
- const char *db_fetch_context;
-
- enum TALER_ErrorCode ec_confirmation_sign;
-
- enum TALER_ErrorCode ec_denomination_sign;
-
- /* remaining value of the coin */
- struct TALER_Amount insufficient_funds;
-
- } details;
- } error;
-};
-
-/**
- * The following macros set the given error code,
- * set the phase to Melt_PHASE_GENERATE_REPLY_ERROR,
- * and optionally set the given field (with an optionally given value).
- */
-#define SET_ERROR(mc, ec) \
- do \
- { GNUNET_static_assert (MELT_ERROR_NONE != ec); \
- (mc)->error.code = (ec); \
- (mc)->phase = MELT_PHASE_GENERATE_REPLY_ERROR; } while (0)
-
-#define SET_ERROR_WITH_FIELD(mc, ec, field) \
- do \
- { GNUNET_static_assert (MELT_ERROR_NONE != ec); \
- (mc)->error.code = (ec); \
- (mc)->error.details.field = (field); \
- (mc)->phase = MELT_PHASE_GENERATE_REPLY_ERROR; } while (0)
-
-#define SET_ERROR_WITH_DETAIL(mc, ec, field, value) \
- do \
- { GNUNET_static_assert (MELT_ERROR_NONE != ec); \
- (mc)->error.code = (ec); \
- (mc)->error.details.field = (value); \
- (mc)->phase = MELT_PHASE_GENERATE_REPLY_ERROR; } while (0)
-
-
-/**
- * All melt context is kept in a DLL.
- */
-static struct MeltContext *mc_head;
-static struct MeltContext *mc_tail;
-
-void
-TEH_melt_v27_cleanup ()
-{
- struct MeltContext *mc;
-
- while (NULL != (mc = mc_head))
- {
- GNUNET_CONTAINER_DLL_remove (mc_head,
- mc_tail,
- mc);
- MHD_resume_connection (mc->rc->connection);
- }
-}
-
-
-/**
- * Terminate the main loop by returning the final result.
- *
- * @param[in,out] mc context to update phase for
- * @param mres MHD status to return
- */
-static void
-finish_loop (struct MeltContext *mc,
- MHD_RESULT mres)
-{
- mc->phase = (MHD_YES == mres)
- ? MELT_PHASE_RETURN_YES
- : MELT_PHASE_RETURN_NO;
-}
-
-
-/**
- * Free information in @a re, but not @a re itself.
- *
- * @param[in] re refresh data to free
- */
-static void
-free_refresh (struct TALER_EXCHANGEDB_Refresh_v27 *re)
-{
- if (NULL != re->denom_sigs)
- {
- for (size_t i = 0; i<re->num_coins; i++)
- TALER_blinded_denom_sig_free (&re->denom_sigs[i]);
- GNUNET_free (re->denom_sigs);
- }
- GNUNET_free (re->cs_r_values);
- GNUNET_free (re->denom_serials);
- GNUNET_free (re->denom_pub_hashes);
- TALER_denom_sig_free (&re->coin.denom_sig);
-}
-
-
-/**
- * Cleanup routine for melt request.
- * The function is called upon completion of the request
- * that should clean up @a rh_ctx.
- *
- * @param rc request context to clean up
- */
-static void
-clean_melt_rc (struct TEH_RequestContext *rc)
-{
- struct MeltContext *mc = rc->rh_ctx;
-
- GNUNET_free (mc->request.denoms_h);
- for (uint8_t k = 0; k<TALER_CNC_KAPPA; k++)
- {
- for (size_t i = 0; i<mc->request.refresh.num_coins; i++)
- TALER_blinded_planchet_free (&mc->request.planchets[k][i]);
- GNUNET_free (mc->request.planchets[k]);
- }
- free_refresh (&mc->request.refresh);
- if (mc->request.is_idempotent)
- {
- free_refresh (&mc->request.refresh_idem);
- }
- GNUNET_free (mc->request.cs_indices);
- GNUNET_free (mc);
-}
-
-
-/**
- * Creates a new context for the incoming melt request
- *
- * @param mc melt request context
- * @param root json body of the request
- */
-static void
-phase_parse_request (
- struct MeltContext *mc,
- const json_t *root)
-{
- const json_t *j_denoms_h;
- const json_t *j_coin_evs;
- enum GNUNET_GenericReturnValue res;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
- &mc->request.refresh.coin.coin_pub),
- GNUNET_JSON_spec_fixed_auto ("old_denom_pub_h",
- &mc->request.refresh.coin.denom_pub_hash),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("old_age_commitment_h",
- &mc->request.refresh.coin.h_age_commitment),
- &mc->request.refresh.coin.no_age_commitment),
- TALER_JSON_spec_denom_sig ("old_denom_sig",
- &mc->request.refresh.coin.denom_sig),
- GNUNET_JSON_spec_fixed_auto ("refresh_seed",
- &mc->request.refresh.refresh_seed),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("blinding_seed",
- &mc->request.refresh.blinding_seed),
- &mc->request.refresh.no_blinding_seed),
- TALER_JSON_spec_amount ("value_with_fee",
- TEH_currency,
- &mc->request.refresh.amount_with_fee),
- GNUNET_JSON_spec_array_const ("denoms_h",
- &j_denoms_h),
- GNUNET_JSON_spec_array_const ("coin_evs",
- &j_coin_evs),
- GNUNET_JSON_spec_fixed_auto ("confirm_sig",
- &mc->request.refresh.coin_sig),
- GNUNET_JSON_spec_end ()
- };
-
- res = TALER_MHD_parse_json_data (mc->rc->connection,
- root,
- spec);
- if (GNUNET_OK != res)
- {
- GNUNET_break_op (0);
- mc->phase = (GNUNET_NO == res)
- ? MELT_PHASE_RETURN_YES
- : MELT_PHASE_RETURN_NO;
- return;
- }
-
- /* validate array size */
- GNUNET_static_assert (
- TALER_MAX_COINS < INT_MAX / TALER_CNC_KAPPA);
-
- mc->request.refresh.num_coins = json_array_size (j_denoms_h);
- if (0 == mc->request.refresh.num_coins)
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
- request_parameter_malformed,
- "denoms_h must not be empty");
- return;
- }
- else if (TALER_MAX_COINS < mc->request.refresh.num_coins)
- {
- /**
- * The wallet had committed to more than the maximum coins allowed, the
- * reserve has been charged, but now the user can not melt any money
- * from it. Note that the user can't get their money back in this case!
- */
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
- request_parameter_malformed,
- "maximum number of coins that can be refreshed has been exceeded");
- return;
- }
- else if (TALER_CNC_KAPPA != json_array_size (j_coin_evs))
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
- request_parameter_malformed,
- "coin_evs must be an array of length "TALER_CNC_KAPPA_STR);
- return;
- }
-
- /* Extract the denomination hashes */
- {
- size_t idx;
- json_t *value;
-
- mc->request.denoms_h
- = GNUNET_new_array (mc->request.refresh.num_coins,
- struct TALER_DenominationHashP);
-
- json_array_foreach (j_denoms_h, idx, value) {
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_fixed_auto (NULL,
- &mc->request.denoms_h[idx]),
- GNUNET_JSON_spec_end ()
- };
-
- res = TALER_MHD_parse_json_data (mc->rc->connection,
- value,
- ispec);
- if (GNUNET_YES != res)
- {
- GNUNET_break_op (0);
- mc->phase = (GNUNET_NO == res)
- ? MELT_PHASE_RETURN_YES
- : MELT_PHASE_RETURN_NO;
- return;
- }
- }
- }
-
- /* Calculate the hash over the blinded coin envelopes */
- for (size_t k = 0; k<TALER_CNC_KAPPA; k++)
- {
- mc->request.planchets[k] =
- GNUNET_new_array (mc->request.refresh.num_coins,
- struct TALER_BlindedPlanchet);
- }
-
- /* Parse blinded envelopes. */
- {
- json_t *j_kappa_planchets;
- size_t kappa;
- struct GNUNET_HashContext *ctx;
-
- /* ctx to calculate the planchet_h */
- ctx = GNUNET_CRYPTO_hash_context_start ();
- GNUNET_assert (NULL != ctx);
-
- json_array_foreach (j_coin_evs, kappa, j_kappa_planchets)
- {
- json_t *j_cev;
- size_t idx;
-
- if (mc->request.refresh.num_coins != json_array_size (j_kappa_planchets))
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
- request_parameter_malformed,
- "coin_evs[] size");
- return;
- }
-
- json_array_foreach (j_kappa_planchets, idx, j_cev)
- {
- /* Now parse the individual envelopes and calculate the hash of
- * the commitment along the way. */
- struct GNUNET_JSON_Specification kspec[] = {
- TALER_JSON_spec_blinded_planchet (NULL,
- &mc->request.planchets[kappa][idx]),
- GNUNET_JSON_spec_end ()
- };
-
- res = TALER_MHD_parse_json_data (mc->rc->connection,
- j_cev,
- kspec);
- if (GNUNET_OK != res)
- {
- GNUNET_break_op (0);
- mc->phase = (GNUNET_NO == res)
- ? MELT_PHASE_RETURN_YES
- : MELT_PHASE_RETURN_NO;
- return;
- }
- /* Check for duplicate planchets. Technically a bug on
- * the client side that is harmless for us, but still
- * not allowed per protocol */
- for (size_t k = 0; k <= kappa; k++)
- {
- size_t max = (k == kappa)
- ? idx
- : mc->request.refresh.num_coins;
- for (size_t i = 0; i < max; i++)
- {
- if (0 ==
- TALER_blinded_planchet_cmp (
- &mc->request.planchets[kappa][idx],
- &mc->request.planchets[k][i]))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (kspec);
- SET_ERROR (mc,
- MELT_ERROR_IDEMPOTENT_PLANCHET);
- return;
- }
- }
- }
- }
- /* Save the hash of the batch of planchets for index kappa */
- TALER_wallet_blinded_planchets_hash (
- mc->request.refresh.num_coins,
- mc->request.planchets[kappa],
- mc->request.denoms_h,
- &mc->request.kappa_planchets_h.tuple[kappa]);
- GNUNET_CRYPTO_hash_context_read (
- ctx,
- &mc->request.kappa_planchets_h.tuple[kappa],
- sizeof(mc->request.kappa_planchets_h.tuple[kappa]));
- }
-
- /* Finally calculate the total hash over all planchets */
- GNUNET_CRYPTO_hash_context_finish (
- ctx,
- &mc->request.refresh.planchets_h.hash);
- }
- mc->ksh = TEH_keys_get_state ();
- if (NULL == mc->ksh)
- {
- GNUNET_break (0);
- SET_ERROR (mc,
- MELT_ERROR_KEYS_MISSING);
- return;
- }
- mc->phase = MELT_PHASE_CHECK_MELT_VALID;
-}
-
-
-/**
- * Check if the given denomination is still or already valid, has not been
- * revoked and potentically supports age restriction.
- *
- * @param[in,out] mc context for the melt operation
- * @param denom_h Hash of the denomination key to check
- * @param[out] pdk denomination key found, might be NULL
- * @return #GNUNET_OK when denomation was found and valid,
- * #GNUNET_NO when denomination is not valid at this time
- * #GNUNET_SYSERR otherwise (denomination invalid), with finish_loop called.
- */
-static enum GNUNET_GenericReturnValue
-find_denomination (
- struct MeltContext *mc,
- const struct TALER_DenominationHashP *denom_h,
- struct TEH_DenominationKey **pdk)
-{
- struct TEH_DenominationKey *dk;
-
- *pdk = NULL;
- GNUNET_assert (NULL != mc->ksh);
- dk = TEH_keys_denomination_by_hash_from_state (
- mc->ksh,
- denom_h,
- NULL,
- NULL);
- if (NULL == dk)
- {
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_DENOMINATION_KEY_UNKNOWN,
- denom_h,
- *denom_h);
- return GNUNET_SYSERR;
- }
- *pdk = dk;
-
- if (GNUNET_TIME_absolute_is_past (
- dk->meta.expire_withdraw.abs_time))
- {
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_DENOMINATION_EXPIRED,
- denom_h,
- *denom_h);
- /**
- * Note that we return GNUNET_NO here.
- * This way phase_check_melt_valid can react
- * to it as a non-error case and do the zombie check.
- */
- return GNUNET_NO;
- }
-
- if (GNUNET_TIME_absolute_is_future (
- dk->meta.start.abs_time))
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
- denom_h,
- *denom_h);
- return GNUNET_SYSERR;
- }
-
- if (dk->recoup_possible)
- {
- SET_ERROR (mc,
- MELT_ERROR_DENOMINATION_REVOKED);
- return GNUNET_SYSERR;
- }
-
- /* In case of age melt, make sure that the denomitation supports age restriction */
- if (! (mc->request.refresh.coin.no_age_commitment) &&
- (0 == dk->denom_pub.age_mask.bits))
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
- denom_h,
- *denom_h);
- return GNUNET_SYSERR;
- }
- if ((mc->request.refresh.coin.no_age_commitment) &&
- (0 != dk->denom_pub.age_mask.bits))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_AGE_RESTRICTION_COMMITMENT_INVALID);
- return GNUNET_SYSERR;
- }
-
- return GNUNET_OK;
-}
-
-
-/**
- * Check if the given array of hashes of denomination_keys
- * - belong to valid denominations
- * - calculate the total amount of the denominations including fees
- * for melt.
- *
- * @param mc context of the melt to check keys for
- */
-static void
-phase_check_keys (
- struct MeltContext *mc)
-{
- bool is_cs_denom[mc->request.refresh.num_coins];
-
- memset (is_cs_denom,
- 0,
- sizeof(is_cs_denom));
-
- mc->request.refresh.denom_serials =
- GNUNET_new_array (mc->request.refresh.num_coins,
- uint64_t);
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &mc->request.amount));
-
- /* Calculate the total value and withdraw fees for the fresh coins */
- for (size_t i = 0; i < mc->request.refresh.num_coins; i++)
- {
- struct TEH_DenominationKey *dk;
-
- if (GNUNET_OK !=
- find_denomination (
- mc,
- &mc->request.denoms_h[i],
- &dk))
- return;
-
- if (GNUNET_CRYPTO_BSA_CS ==
- dk->denom_pub.bsign_pub_key->cipher)
- {
- if (mc->request.refresh.no_blinding_seed)
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_BLINDING_SEED_REQUIRED);
- return;
- }
- mc->request.refresh.num_cs_r_values++;
- is_cs_denom[i] = true;
- }
- /* Ensure the ciphers from the planchets match the denominations'. */
- {
- for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
- {
- if (dk->denom_pub.bsign_pub_key->cipher !=
- mc->request.planchets[k][i].blinded_message->cipher)
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_COIN_CIPHER_MISMATCH);
- return;
- }
- }
- }
- /* Accumulate the values */
- if (0 > TALER_amount_add (&mc->request.amount,
- &mc->request.amount,
- &dk->meta.value))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_AMOUNT_OVERFLOW);
- return;
- }
- /* Accumulate the withdraw fees for the fresh coins */
- if (0 > TALER_amount_add (&mc->request.amount,
- &mc->request.amount,
- &dk->meta.fees.withdraw))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW);
- return;
- }
- mc->request.refresh.denom_serials[i] = dk->meta.serial;
- }
-
- /**
- * Calculate the amount (with withdraw fee) plus refresh fee and
- * compare with the value provided by the client in the request.
- */
- {
- struct TALER_Amount amount_with_fee;
-
- if (0 > TALER_amount_add (&amount_with_fee,
- &mc->request.amount,
- &mc->melted_coin_denom->meta.fees.refresh))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW);
- return;
- }
-
- if (0 != TALER_amount_cmp (&amount_with_fee,
- &mc->request.refresh.amount_with_fee))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_AMOUNT_WITH_FEE_INCORRECT);
- return;
- }
- }
-
- /* Save the indices of CS denominations */
- if (0 < mc->request.refresh.num_cs_r_values)
- {
- size_t j = 0;
-
- mc->request.cs_indices = GNUNET_new_array (
- mc->request.refresh.num_cs_r_values,
- uint32_t);
-
- for (size_t i = 0; i < mc->request.refresh.num_coins; i++)
- {
- if (is_cs_denom[i])
- mc->request.cs_indices[j++] = i;
- }
- }
- mc->phase++;
-}
-
-
-/**
- * Check that the client signature authorizing the melt is valid.
- *
- * @param[in,out] mc request context to check
- */
-static void
-phase_check_coin_signature (
- struct MeltContext *mc)
-{
- /* We can now compute the commitment */
- {
- struct TALER_KappaHashBlindedPlanchetsP k_bps_h = {0};
-
- for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
- TALER_wallet_blinded_planchets_hash (
- mc->request.refresh.num_coins,
- mc->request.planchets[k],
- mc->request.denoms_h,
- &k_bps_h.tuple[k]);
-
- TALER_refresh_get_commitment_v27 (
- &mc->request.refresh.rc,
- &mc->request.refresh.refresh_seed,
- mc->request.no_blinding_seed
- ? NULL
- : &mc->request.refresh.blinding_seed,
- &k_bps_h,
- &mc->request.refresh.coin.coin_pub,
- &mc->request.refresh.amount_with_fee);
- }
-
-
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_wallet_melt_verify (
- &mc->request.refresh.amount_with_fee,
- &mc->melted_coin_denom->meta.fees.refresh,
- &mc->request.refresh.rc,
- &mc->request.refresh.coin.denom_pub_hash,
- &mc->request.refresh.coin.h_age_commitment,
- &mc->request.refresh.coin.coin_pub,
- &mc->request.refresh.coin_sig))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_COIN_SIGNATURE_INVALID);
- return;
- }
-
- mc->phase++;
-}
-
-
-/**
- * Check for information about the melted coin's denomination,
- * extracting its validity status and fee structure.
- * Baseline: check if deposits/refreshes are generally
- * simply still allowed for this denomination.
- *
- * @param mc parsed request information
- */
-static void
-phase_check_melt_valid (struct MeltContext *mc)
-{
- enum MeltPhase current_phase = mc->phase;
- /**
- * Find the old coin's denomination.
- * Note that we return only on GNUNET_SYSERR,
- * because GNUNET_NO for the expired denomination
- * will be handled below, with the zombie-check.
- */
- if (GNUNET_SYSERR ==
- find_denomination (mc,
- &mc->request.refresh.coin.denom_pub_hash,
- &mc->melted_coin_denom))
- return;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Melted coin's denomination is worth %s\n",
- TALER_amount2s (&mc->melted_coin_denom->meta.value));
-
- /* sanity-check that "total melt amount > melt fee" */
- if (0 <
- TALER_amount_cmp (&mc->melted_coin_denom->meta.fees.refresh,
- &mc->request.refresh.amount_with_fee))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_FEES_EXCEED_CONTRIBUTION);
- return;
- }
-
- if (GNUNET_OK !=
- TALER_test_coin_valid (&mc->request.refresh.coin,
- &mc->melted_coin_denom->denom_pub))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_DENOMINATION_SIGNATURE_INVALID);
- return;
- }
-
- /**
- * find_denomination might have set the phase to
- * produce an error, but we are still investigating.
- * We reset the phase.
- */
- mc->phase = current_phase;
- mc->error.code = MELT_ERROR_NONE;
-
- if (GNUNET_TIME_absolute_is_past (
- mc->melted_coin_denom->meta.expire_deposit.abs_time))
- {
- /**
- * We are past deposit expiration time, but maybe this is a zombie?
- */
- struct TALER_DenominationHashP denom_hash;
- enum GNUNET_DB_QueryStatus qs;
-
- /* Check that the coin is dirty (we have seen it before), as we will
- not just allow melting of a *fresh* coin where the denomination was
- revoked (those must be recouped) */
- qs = TEH_plugin->get_coin_denomination (
- TEH_plugin->cls,
- &mc->request.refresh.coin.coin_pub,
- &mc->known_coin_id,
- &denom_hash);
- if (0 > qs)
- {
- /* There is no good reason for a serialization failure here: */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- SET_ERROR (mc,
- MELT_ERROR_DB_FETCH_FAILED);
- return;
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- /* We never saw this coin before, so _this_ justification is not OK.
- * Note that the error was already set in find_denominations. */
- GNUNET_assert (MELT_ERROR_DENOMINATION_EXPIRED ==
- mc->error.code);
- GNUNET_assert (MELT_PHASE_GENERATE_REPLY_ERROR ==
- mc->phase);
- return;
- }
- /* sanity check */
- if (0 !=
- GNUNET_memcmp (&denom_hash,
- &mc->request.refresh.coin.denom_pub_hash))
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (mc,
- MELT_COIN_CONFLICTING_DENOMINATION_KEY,
- denom_h,
- denom_hash);
- return;
- }
- /* Minor optimization: no need to run the
- "ensure_coin_known" part of the transaction */
- mc->coin_is_dirty = true;
- /* check later that zombie is satisfied */
- mc->zombie_required = true;
- }
- mc->phase++;
-}
-
-
-/**
- * The request for melt was parsed successfully.
- * Sign and persist the chosen blinded coins for the reveal step.
- *
- * @param mc The context for the current melt request
- */
-static void
-phase_prepare_transaction (
- struct MeltContext *mc)
-{
- mc->request.refresh.denom_sigs
- = GNUNET_new_array (
- mc->request.refresh.num_coins,
- struct TALER_BlindedDenominationSignature);
- mc->request.refresh.noreveal_index =
- GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG,
- TALER_CNC_KAPPA);
-
- /* Choose and sign the coins */
- {
- struct TEH_CoinSignData csds[mc->request.refresh.num_coins];
- enum TALER_ErrorCode ec_denomination_sign;
- size_t noreveal_idx = mc->request.refresh.noreveal_index;
-
- memset (csds,
- 0,
- sizeof(csds));
-
- /* Pick the chosen blinded coins */
- for (size_t i = 0; i<mc->request.refresh.num_coins; i++)
- {
- csds[i].bp = &mc->request.planchets[noreveal_idx][i];
- csds[i].h_denom_pub = &mc->request.denoms_h[i];
- }
-
- ec_denomination_sign = TEH_keys_denomination_batch_sign (
- mc->request.refresh.num_coins,
- csds,
- true, /* for melt */
- mc->request.refresh.denom_sigs);
- if (TALER_EC_NONE != ec_denomination_sign)
- {
- GNUNET_break (0);
- SET_ERROR_WITH_FIELD (mc,
- MELT_ERROR_DENOMINATION_SIGN,
- ec_denomination_sign);
- return;
- }
-
- /* Save the hash of chosen planchets */
- mc->request.refresh.selected_h =
- mc->request.kappa_planchets_h.tuple[noreveal_idx];
-
- /**
- * For the denominations with cipher CS, calculate the R-values
- * and save the choices we made now, as at a later point, the
- * private keys for the denominations might now be available anymore
- * to make the same choice again.
- */
- if (0 < mc->request.refresh.num_cs_r_values)
- {
- size_t num_cs_r_values = mc->request.refresh.num_cs_r_values;
- struct TEH_CsDeriveData cdds[num_cs_r_values];
- struct GNUNET_CRYPTO_CsSessionNonce nonces[num_cs_r_values];
-
- memset (nonces, 0, sizeof(nonces));
- mc->request.refresh.cs_r_values
- = GNUNET_new_array (
- num_cs_r_values,
- struct GNUNET_CRYPTO_CSPublicRPairP);
- mc->request.refresh.cs_r_choices = 0;
-
- GNUNET_assert (! mc->request.refresh.no_blinding_seed);
- TALER_cs_derive_nonces_from_seed (
- &mc->request.refresh.blinding_seed,
- true, /* for melt */
- num_cs_r_values,
- mc->request.cs_indices,
- nonces);
-
- for (size_t i = 0; i < num_cs_r_values; i++)
- {
- size_t idx = mc->request.cs_indices[i];
-
- GNUNET_assert (idx < mc->request.refresh.num_coins);
- cdds[i].h_denom_pub = &mc->request.denoms_h[idx];
- cdds[i].nonce = &nonces[i];
- }
-
- /**
- * Let the crypto helper generate the R-values and
- * make the choices
- */
- if (TALER_EC_NONE !=
- TEH_keys_denomination_cs_batch_r_pub_simple (
- mc->request.refresh.num_cs_r_values,
- cdds,
- true, /* for melt */
- mc->request.refresh.cs_r_values))
- {
- GNUNET_break (0);
- SET_ERROR (mc,
- MELT_ERROR_CRYPTO_HELPER);
- return;
- }
-
- /* Now save the choices for the selected bits */
- for (size_t i = 0; i < num_cs_r_values; i++)
- {
- size_t idx = mc->request.cs_indices[i];
-
- struct TALER_BlindedDenominationSignature *sig =
- &mc->request.refresh.denom_sigs[idx];
- uint8_t bit = sig->blinded_sig->details.blinded_cs_answer.b;
-
- mc->request.refresh.cs_r_choices |= bit << i;
- GNUNET_static_assert (
- TALER_MAX_COINS <=
- sizeof(mc->request.refresh.cs_r_choices) * 8);
- }
- }
- }
- mc->phase++;
-}
-
-
-/**
- * Generates response for the melt request.
- *
- * @param mc melt operation context
- */
-static void
-phase_generate_reply_success (struct MeltContext *mc)
-{
- struct TALER_EXCHANGEDB_Refresh_v27 *db_obj;
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
- enum TALER_ErrorCode ec_confirmation_sign;
-
- db_obj = mc->request.is_idempotent
- ? &mc->request.refresh_idem
- : &mc->request.refresh;
- ec_confirmation_sign =
- TALER_exchange_online_melt_confirmation_sign (
- &TEH_keys_exchange_sign_,
- &db_obj->rc,
- db_obj->noreveal_index,
- &pub,
- &sig);
- if (TALER_EC_NONE != ec_confirmation_sign)
- {
- SET_ERROR_WITH_FIELD (mc,
- MELT_ERROR_CONFIRMATION_SIGN,
- ec_confirmation_sign);
- return;
- }
-
- finish_loop (mc,
- TALER_MHD_REPLY_JSON_PACK (
- mc->rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_uint64 ("noreveal_index",
- db_obj->noreveal_index),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &sig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &pub)));
-}
-
-
-/**
- * Check if the melt request is replayed and we already have an answer.
- * If so, replay the existing answer and return the HTTP response.
- *
- * @param[in,out] mc parsed request data
- * @return true if the request is idempotent with an existing request
- * false if we did not find the request in the DB and did not set @a mret
- */
-static bool
-melt_is_idempotent (
- struct MeltContext *mc)
-{
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->get_refresh (
- TEH_plugin->cls,
- &mc->request.refresh.rc,
- &mc->request.refresh_idem);
- if (0 > qs)
- {
- /* FIXME: soft error not handled correctly! */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_DB_FETCH_FAILED,
- db_fetch_context,
- "get_refresh");
- return true; /* Well, kind-of. */
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- return false;
-
- mc->request.is_idempotent = true;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "request is idempotent\n");
-
- /* Generate idempotent reply */
- TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_MELT]++;
- mc->phase = MELT_PHASE_GENERATE_REPLY_SUCCESS;
- mc->error.code = MELT_ERROR_NONE;
- return true;
-}
-
-
-/**
- * Reports an error, potentially with details.
- * That is, it puts a error-type specific response into the MHD queue.
- * It will do a idempotency check first, if needed for the error type.
- *
- * @param mc melt context
- */
-static void
-phase_generate_reply_error (
- struct MeltContext *mc)
-{
- GNUNET_assert (MELT_PHASE_GENERATE_REPLY_ERROR == mc->phase);
- GNUNET_assert (MELT_ERROR_NONE != mc->error.code);
-
- if (IDEMPOTENCY_CHECK_REQUIRED (mc->error.code) &&
- melt_is_idempotent (mc))
- {
- return;
- }
-
- switch (mc->error.code)
- {
- case MELT_ERROR_NONE:
- break;
- case MELT_ERROR_REQUEST_PARAMETER_MALFORMED:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- mc->error.details.request_parameter_malformed));
- return;
- case MELT_ERROR_KEYS_MISSING:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- NULL));
- return;
- case MELT_ERROR_DB_FETCH_FAILED:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- mc->error.details.db_fetch_context));
- return;
- case MELT_ERROR_DB_INVARIANT_FAILURE:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- NULL));
- return;
- case MELT_ERROR_DB_PREFLIGHT_FAILURE:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_COMMIT_FAILED,
- "make_coin_known"));
- return;
- case MELT_ERROR_DB_MAKE_COIN_KNOW_FAILURE:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_START_FAILED,
- "preflight failure"));
- return;
- case MELT_ERROR_COIN_UNKNOWN:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_EXCHANGE_GENERIC_COIN_UNKNOWN,
- NULL));
- return;
- case MELT_COIN_CONFLICTING_DENOMINATION_KEY:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY,
- TALER_B2S (&mc->error.details.denom_h)));
- return;
- case MELT_ERROR_COIN_EXPIRED_NO_ZOMBIE:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE,
- NULL));
- return;
- case MELT_ERROR_DENOMINATION_SIGN:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- mc->error.details.ec_denomination_sign,
- NULL));
- return;
- case MELT_ERROR_DENOMINATION_SIGNATURE_INVALID:
- finish_loop (mc,
- TALER_MHD_reply_with_error (mc->rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
- NULL));
- return;
- case MELT_ERROR_DENOMINATION_KEY_UNKNOWN:
- GNUNET_break_op (0);
- finish_loop (mc,
- TEH_RESPONSE_reply_unknown_denom_pub_hash (
- mc->rc->connection,
- &mc->error.details.denom_h));
- return;
- case MELT_ERROR_DENOMINATION_EXPIRED:
- GNUNET_break_op (0);
- finish_loop (mc,
- TEH_RESPONSE_reply_expired_denom_pub_hash (
- mc->rc->connection,
- &mc->error.details.denom_h,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
- "MELT"));
- return;
- case MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE:
- finish_loop (mc,
- TEH_RESPONSE_reply_expired_denom_pub_hash (
- mc->rc->connection,
- &mc->error.details.denom_h,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
- "MELT"));
- return;
- case MELT_ERROR_DENOMINATION_REVOKED:
- GNUNET_break_op (0);
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
- NULL));
- return;
- case MELT_ERROR_COIN_CIPHER_MISMATCH:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
- NULL));
- return;
- case MELT_ERROR_BLINDING_SEED_REQUIRED:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_GENERIC_PARAMETER_MISSING,
- "blinding_seed"));
- return;
- case MELT_ERROR_CRYPTO_HELPER:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- NULL));
- return;
- case MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION:
- {
- char msg[256];
-
- GNUNET_snprintf (msg,
- sizeof(msg),
- "denomination %s does not support age restriction",
- GNUNET_h2s (&mc->error.details.denom_h.hash));
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
- msg));
- return;
- }
- case MELT_ERROR_AGE_RESTRICTION_COMMITMENT_INVALID:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
- "old_age_commitment_h"));
- return;
- case MELT_ERROR_AMOUNT_OVERFLOW:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
- "amount"));
- return;
- case MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
- "amount+fee"));
- return;
- case MELT_ERROR_FEES_EXCEED_CONTRIBUTION:
- finish_loop (mc,
- TALER_MHD_reply_with_error (mc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_MELT_FEES_EXCEED_CONTRIBUTION,
- NULL));
- return;
- case MELT_ERROR_AMOUNT_WITH_FEE_INCORRECT:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
- "value_with_fee incorrect"));
- return;
- case MELT_ERROR_CONFIRMATION_SIGN:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- mc->error.details.ec_confirmation_sign,
- NULL));
- return;
- case MELT_ERROR_INSUFFICIENT_FUNDS:
- finish_loop (mc,
- TEH_RESPONSE_reply_coin_insufficient_funds (
- mc->rc->connection,
- TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
- &mc->request.refresh.coin.denom_pub_hash,
- &mc->request.refresh.coin.coin_pub));
- return;
- case MELT_ERROR_IDEMPOTENT_PLANCHET:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED, /* FIXME: new error! */
- "idempotent planchet"));
- return;
- case MELT_ERROR_NONCE_RESUSE:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED, /* FIXME: new error */
- "nonce reuse"));
- return;
- case MELT_ERROR_COIN_SIGNATURE_INVALID:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_EXCHANGE_MELT_COIN_SIGNATURE_INVALID,
- NULL));
- return;
- }
- GNUNET_break (0);
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "error phase without error"));
-}
-
-
-/**
- * Function implementing melt transaction. Runs the
- * transaction logic; IF it returns a non-error code, the transaction
- * logic MUST NOT queue a MHD response. IF it returns an hard error,
- * the transaction logic MUST queue a MHD response and set @a mhd_ret.
- * IF it returns the soft error code, the function MAY be called again
- * to retry and MUST not queue a MHD response.
- *
- * @param cls a `struct MeltContext *`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-melt_transaction (
- void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct MeltContext *mc = cls;
- enum GNUNET_DB_QueryStatus qs;
- bool balance_ok;
- bool found;
- bool nonce_reuse;
- uint32_t noreveal_index;
- struct TALER_Amount insufficient_funds;
-
- qs = TEH_plugin->do_refresh (TEH_plugin->cls,
- &mc->request.refresh,
- &mc->now,
- &found,
- &noreveal_index,
- &mc->zombie_required,
- &nonce_reuse,
- &balance_ok,
- &insufficient_funds);
- if (0 > qs)
- {
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_DB_FETCH_FAILED,
- db_fetch_context,
- "do_refresh");
- return qs;
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_COIN_UNKNOWN);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (found)
- {
- /**
- * This request is idempotent, set the nonreveal_index
- * to the previous one and reply success.
- */
- mc->request.refresh.noreveal_index = noreveal_index;
- mc->phase = MELT_PHASE_GENERATE_REPLY_SUCCESS;
- mc->error.code = MELT_ERROR_NONE;
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
- }
- if (nonce_reuse)
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_NONCE_RESUSE);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (! balance_ok)
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_FIELD (mc,
- MELT_ERROR_INSUFFICIENT_FUNDS,
- insufficient_funds);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (mc->zombie_required)
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_COIN_EXPIRED_NO_ZOMBIE);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- TEH_METRICS_num_success[TEH_MT_SUCCESS_MELT]++;
- return qs;
-}
-
-
-/**
- * The request was prepared successfully.
- * Run the main DB transaction.
- *
- * @param mc The context for the current melt request
- */
-static void
-phase_run_transaction (
- struct MeltContext *mc)
-{
- if (GNUNET_SYSERR ==
- TEH_plugin->preflight (TEH_plugin->cls))
- {
- GNUNET_break (0);
- SET_ERROR (mc,
- MELT_ERROR_DB_PREFLIGHT_FAILURE);
- return;
- }
-
- /* first, make sure coin is known */
- if (! mc->coin_is_dirty)
- {
- MHD_RESULT mhd_ret = -1;
- enum GNUNET_DB_QueryStatus qs;
-
- for (unsigned int tries = 0; tries<MAX_TRANSACTION_COMMIT_RETRIES; tries++)
- {
- qs = TEH_make_coin_known (&mc->request.refresh.coin,
- mc->rc->connection,
- &mc->known_coin_id,
- &mhd_ret);
- if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
- break;
- }
- if (0 > qs)
- {
- GNUNET_break (0);
- /* Check if an answer has been queued */
- switch (mhd_ret)
- {
- case MHD_NO:
- mc->phase = MELT_PHASE_RETURN_NO;
- return;
- case MHD_YES:
- mc->phase = MELT_PHASE_RETURN_YES;
- return;
- default:
- /* ignore */
- }
- SET_ERROR (mc,
- MELT_ERROR_DB_MAKE_COIN_KNOW_FAILURE);
- return;
- }
- }
-
- /* run main database transaction */
- {
- MHD_RESULT mhd_ret = -1;
- enum GNUNET_GenericReturnValue ret;
- enum MeltPhase current_phase = mc->phase;
-
- GNUNET_assert (MELT_PHASE_RUN_TRANSACTION ==
- current_phase);
- ret = TEH_DB_run_transaction (mc->rc->connection,
- "run melt",
- TEH_MT_REQUEST_MELT,
- &mhd_ret,
- &melt_transaction,
- mc);
- if (GNUNET_OK != ret)
- {
- GNUNET_break (0);
- /* Check if an answer has been queued */
- switch (mhd_ret)
- {
- case MHD_NO:
- mc->phase = MELT_PHASE_RETURN_NO;
- return;
- case MHD_YES:
- mc->phase = MELT_PHASE_RETURN_YES;
- return;
- default:
- /* ignore */
- }
- GNUNET_assert (MELT_ERROR_NONE != mc->error.code);
- GNUNET_assert (MELT_PHASE_GENERATE_REPLY_ERROR == mc->phase);
- return;
- }
- /**
- * In case of idempotency (which is not an error condition),
- * the phase has changed in melt_transaction.
- * We simple return.
- */
- if (current_phase != mc->phase)
- return;
- }
- mc->phase++;
-}
-
-
-MHD_RESULT
-TEH_handler_melt_v27 (
- struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[0])
-{
- struct MeltContext *mc = rc->rh_ctx;
-
- (void) args;
- if (NULL == mc)
- {
- mc = GNUNET_new (struct MeltContext);
- rc->rh_ctx = mc;
- rc->rh_cleaner = &clean_melt_rc;
- mc->rc = rc;
- mc->now = GNUNET_TIME_timestamp_get ();
- }
-
- while (true)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "melt processing in phase %d\n",
- mc->phase);
- switch (mc->phase)
- {
- case MELT_PHASE_PARSE:
- phase_parse_request (mc,
- root);
- break;
- case MELT_PHASE_CHECK_MELT_VALID:
- phase_check_melt_valid (mc);
- break;
- case MELT_PHASE_CHECK_KEYS:
- phase_check_keys (mc);
- break;
- case MELT_PHASE_CHECK_COIN_SIGNATURE:
- phase_check_coin_signature (mc);
- break;
- case MELT_PHASE_PREPARE_TRANSACTION:
- phase_prepare_transaction (mc);
- break;
- case MELT_PHASE_RUN_TRANSACTION:
- phase_run_transaction (mc);
- break;
- case MELT_PHASE_GENERATE_REPLY_SUCCESS:
- phase_generate_reply_success (mc);
- break;
- case MELT_PHASE_GENERATE_REPLY_ERROR:
- phase_generate_reply_error (mc);
- break;
- case MELT_PHASE_RETURN_YES:
- return MHD_YES;
- case MELT_PHASE_RETURN_NO:
- return MHD_NO;
- }
- }
-}
diff --git a/src/exchange/taler-exchange-httpd_melt_v27.h b/src/exchange/taler-exchange-httpd_melt_v27.h
@@ -1,52 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2025 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 published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-exchange-httpd_melt_v27.h
- * @brief Handle /melt requests, starting with v27 of the protocol
- * @author Özgür Kesim
- */
-#ifndef TALER_EXCHANGE_HTTPD_MELT_V27_H
-#define TALER_EXCHANGE_HTTPD_MELT_V27_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-/**
- * Resume suspended connections, we are shutting down.
- */
-void
-TEH_melt_v27_cleanup (void);
-
-
-/**
- * Handle a "/melt" request. Parses the request into the JSON
- * components and validates the melted coins, the signature and
- * execute the melt as database transaction.
- *
- * @param rc the request context
- * @param root uploaded JSON data
- * @param args array of additional options, not used
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_melt_v27 (
- struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[0]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_reveal-melt.c b/src/exchange/taler-exchange-httpd_reveal-melt.c
@@ -51,15 +51,35 @@ struct MeltRevealContext
* The data from the original melt. Will be retrieved from
* the DB via @a rc.
*/
- struct TALER_EXCHANGEDB_Refresh_v27 refresh;
+ struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS refresh;
/**
+ * True, if @e signatures were not provided in the request.
+ */
+ bool no_signatures;
+
+ /**
+ * @since v27
+ * @deprecated after vDOLDPLUS
+ *
* TALER_CNC_KAPPA-1 disclosed signatures for public refresh nonces.
*/
struct TALER_PrivateRefreshNonceSignatureP signatures[KAPPA_MINUS_1];
/**
- * False, if no age commitment was provided
+ * True, if @e transfer_secret_seeds were not provided in the request.
+ */
+ bool no_transfer_secret_seeds;
+
+ /**
+ * @since vDOLDPLUS
+ *
+ * The transfer secret seeds for the revealed batches of coins.
+ */
+ struct TALER_PrivateRefreshBatchSeedP transfer_secret_seeds[KAPPA_MINUS_1];
+
+ /**
+ * True, if no @e age_commitment was provided
*/
bool no_age_commitment;
@@ -88,7 +108,7 @@ static enum GNUNET_GenericReturnValue
find_original_refresh (
struct MHD_Connection *connection,
const struct TALER_RefreshCommitmentP *rc,
- struct TALER_EXCHANGEDB_Refresh_v27 *refresh,
+ struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *refresh,
MHD_RESULT *result)
{
enum GNUNET_DB_QueryStatus qs;
@@ -280,7 +300,7 @@ calculate_blinded_detail (
/**
* @brief Checks the validity of the disclosed signatures as follows:
* - Verifies the validity of the disclosed signatures with the old coin's public key
- * - Derives the seeds from those signatures for disclosed fresh coins
+ * - Derives the seeds for disclosed fresh coins
* - Derives the fresh coins from the seeds
* - Derives new age commitment
* - Calculates the blinded coin planchet hashes
@@ -296,16 +316,18 @@ calculate_blinded_detail (
* @param con HTTP-connection to the client
* @param rf Original refresh object from the previous /melt request
* @param old_age_commitment The age commitment of the original coin
- * @param signatures The secrets of the disclosed coins, KAPPA_MINUS_1*num_coins many
+ * @param signatures The secrets of the disclosed coins, KAPPA_MINUS_1*num_coins many, maybe NULL
+ * @param rev_batch_seeds The seeds for the transfer secrets of the disclosed coins, KAPPA_MINUS_1 many, maybe NULL
* @param[out] result On error, a HTTP-response will be queued and result set accordingly
* @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
*/
static enum GNUNET_GenericReturnValue
verify_commitment (
struct MHD_Connection *con,
- const struct TALER_EXCHANGEDB_Refresh_v27 *rf,
+ const struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *rf,
const struct TALER_AgeCommitment *old_age_commitment,
- const struct TALER_PrivateRefreshNonceSignatureP signatures[KAPPA_MINUS_1],
+ const struct TALER_PrivateRefreshNonceSignatureP (*signatures)[KAPPA_MINUS_1],
+ const struct TALER_PrivateRefreshBatchSeedP (*rev_batch_seeds)[KAPPA_MINUS_1],
MHD_RESULT *result)
{
enum GNUNET_GenericReturnValue ret;
@@ -314,7 +336,6 @@ verify_commitment (
struct TALER_DenominationHashP *denoms_h[rf->num_coins];
struct TALER_Amount total_amount;
struct TALER_Amount total_fee;
- struct TALER_KappaPublicRefreshNoncesP kappa_nonces;
bool is_cs[rf->num_coins];
size_t cs_count = 0;
@@ -333,6 +354,33 @@ verify_commitment (
sizeof(is_cs));
/**
+ * Consistency check:
+ * If the refresh was for v27, signatures must not be NULL,
+ * otherwise the revealed batch seeds must not be NULL.
+ */
+ if (rf->is_v27_refresh)
+ {
+ if (NULL == signatures)
+ {
+ *result = TALER_MHD_reply_with_error (con,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "signatures missing");
+ return GNUNET_SYSERR;
+ }
+ }
+ else /* vDOLDPLUS */
+ {
+ if (NULL == rev_batch_seeds)
+ {
+ *result = TALER_MHD_reply_with_error (con,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "batch_seeds missing");
+ return GNUNET_SYSERR;
+ }
+ }
+ /**
* We need the current keys in memory for the meta-data of the denominations
*/
keys = TEH_keys_get_state ();
@@ -394,23 +442,26 @@ verify_commitment (
NULL);
return GNUNET_SYSERR;
}
-
/**
- * We expand the provided refresh_seed from the original call to /melt,
- * into kappa many batch seeds, from which we will later use all except the
- * noreveal_index one.
- */
- TALER_refresh_expand_kappa_nonces (
- &rf->refresh_seed,
- &kappa_nonces);
-
- /**
- * First things first: Verify the signature of the old coin
+ * First things first for v27 clients: Verify the signature of the old coin
* over the refresh nonce. This proves the ownership
* for the fresh coin.
*/
+ if (rf->is_v27_refresh)
{
size_t sig_idx = 0;
+ struct TALER_KappaPublicRefreshNoncesP kappa_nonces;
+
+ GNUNET_assert (NULL != signatures);
+
+ /**
+ * We expand the provided refresh_seed from the original call to /melt,
+ * into kappa many batch seeds, from which we will later use all except the
+ * noreveal_index one.
+ */
+ TALER_refresh_expand_kappa_nonces_v27 (
+ &rf->refresh_seed,
+ &kappa_nonces);
for (uint8_t k=0; k < TALER_CNC_KAPPA; k++)
{
@@ -423,7 +474,7 @@ verify_commitment (
rf->num_coins,
denoms_h,
k,
- &signatures[sig_idx++]))
+ &(*signatures)[sig_idx++]))
{
GNUNET_break_op (0);
*result = TALER_MHD_reply_with_ec (con,
@@ -433,7 +484,6 @@ verify_commitment (
}
}
}
-
/**
* In the following scope, we start collecting blinded coin planchet hashes,
* either those persisted from the original request to /melt, or we
@@ -449,7 +499,11 @@ verify_commitment (
union GNUNET_CRYPTO_BlindSessionNonce b_nonces[GNUNET_NZL (cs_count)];
size_t cs_idx = 0; /* [0...cs_count) */
uint8_t sig_idx = 0; /* [0..KAPPA_MINUS_1) */
-
+ /* These two are only necessary for non-v27 clients, but this is the common case. */
+ struct TALER_TransferPublicKeyP k_tpbs[TALER_CNC_KAPPA][rf->num_coins];
+ struct TALER_KappaTransferPublicKeys kappa_transfer_pubs = {
+ .num_transfer_pubs = rf->num_coins
+ };
/**
* First, derive the blinding nonces for the CS denominations all at once.
*/
@@ -487,24 +541,40 @@ verify_commitment (
* the disclosed input material and generate the
* hashes of them.
*/
- struct TALER_PlanchetMasterSecretP secrets[rf->num_coins];
+ struct TALER_PlanchetMasterSecretP planchet_secrets[rf->num_coins];
struct TALER_PlanchetDetail details[rf->num_coins];
- memset (secrets,
+ memset (planchet_secrets,
0,
- sizeof(secrets));
+ sizeof(planchet_secrets));
memset (details,
0,
sizeof(details));
+
+ GNUNET_assert (sig_idx < KAPPA_MINUS_1);
+
/**
* Expand from the k-th signature all num_coin planchet secrets,
* except for the noreveal_index.
*/
- GNUNET_assert (sig_idx < KAPPA_MINUS_1);
- TALER_refresh_signature_to_secrets (
- &signatures[sig_idx++],
- rf->num_coins,
- secrets);
+ if (rf->is_v27_refresh)
+ {
+ TALER_refresh_signature_to_secrets_v27 (
+ &(*signatures)[sig_idx++],
+ rf->num_coins,
+ planchet_secrets);
+ }
+ else /* vDOLDPLUS */
+ {
+ TALER_refresh_expand_batch_seed_to_transfer_data (
+ &(*rev_batch_seeds)[sig_idx++],
+ &rf->coin.coin_pub,
+ rf->num_coins,
+ planchet_secrets,
+ k_tpbs[k]);
+
+ kappa_transfer_pubs.batch[k] = k_tpbs[k];
+ }
/**
* Reset the index for the CS denominations.
*/
@@ -529,7 +599,7 @@ verify_commitment (
}
ret = calculate_blinded_detail (con,
denom_keys[coin_idx],
- &secrets[coin_idx],
+ &planchet_secrets[coin_idx],
rp,
np,
old_age_commitment,
@@ -556,14 +626,30 @@ verify_commitment (
{
struct TALER_RefreshCommitmentP rc;
- TALER_refresh_get_commitment_v27 (&rc,
- &rf->refresh_seed,
- rf->no_blinding_seed
+ if (rf->is_v27_refresh)
+ {
+ TALER_refresh_get_commitment_v27 (&rc,
+ &rf->refresh_seed,
+ rf->no_blinding_seed
? NULL
: &rf->blinding_seed,
- &kappa_planchets_h,
- &rf->coin.coin_pub,
- &rf->amount_with_fee);
+ &kappa_planchets_h,
+ &rf->coin.coin_pub,
+ &rf->amount_with_fee);
+ }
+ else
+ {
+ TALER_refresh_get_commitment (&rc,
+ &rf->refresh_seed,
+ rf->no_blinding_seed
+ ? NULL
+ : &rf->blinding_seed,
+ &kappa_transfer_pubs,
+ &kappa_planchets_h,
+ &rf->coin.coin_pub,
+ &rf->amount_with_fee);
+ }
+
if (0 != GNUNET_CRYPTO_hash_cmp (
&rf->rc.session_hash,
&rc.session_hash))
@@ -571,7 +657,7 @@ verify_commitment (
GNUNET_break_op (0);
*result = TALER_MHD_reply_with_ec (con,
TALER_EC_EXCHANGE_REFRESHES_REVEAL_INVALID_RCH,
- NULL);
+ "rc");
return GNUNET_SYSERR;
}
}
@@ -581,6 +667,61 @@ verify_commitment (
/**
+ * @brief Commit the successful reveal to the database
+ *
+ * @param con HTTP-connection to the client
+ * @param rc Original refresh commitment from the previous /melt request
+ * @param[out] result On error, a HTTP-response will be queued and result set accordingly
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
+ */
+static enum GNUNET_GenericReturnValue
+commit_reveal (
+ struct MHD_Connection *con,
+ const struct TALER_RefreshCommitmentP *rc,
+ MHD_RESULT *result)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ for (unsigned int retry = 0; retry < 3; retry++)
+ {
+ qs = TEH_plugin->mark_refresh_reveal_success (
+ TEH_plugin->cls,
+ rc);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ return GNUNET_OK; /* Only happy case */
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ *result = TALER_MHD_reply_with_error (con,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN,
+ NULL);
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ *result = TALER_MHD_reply_with_ec (con,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "mark_refresh_reveal_success");
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ break; /* try again */
+ default:
+ GNUNET_break (0);
+ *result = TALER_MHD_reply_with_ec (con,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ }
+ /* after unsuccessful retries*/
+ *result = TALER_MHD_reply_with_ec (con,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "mark_refresh_reveal_success");
+ return GNUNET_SYSERR;
+
+}
+
+
+/**
* @brief Send a response for "/reveal-melt"
*
* @param connection The http connection to the client to send the response to
@@ -590,7 +731,7 @@ verify_commitment (
static MHD_RESULT
reply_melt_reveal_success (
struct MHD_Connection *connection,
- const struct TALER_EXCHANGEDB_Refresh_v27 *refresh)
+ const struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *refresh)
{
json_t *list = json_array ();
GNUNET_assert (NULL != list);
@@ -622,18 +763,31 @@ TEH_handler_reveal_melt (
MHD_RESULT result = MHD_NO;
enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
struct MeltRevealContext actx = {0};
- struct GNUNET_JSON_Specification tuple[] = {
+ struct GNUNET_JSON_Specification sig_tuple[] = {
GNUNET_JSON_spec_fixed_auto (NULL,
&actx.signatures[0]),
GNUNET_JSON_spec_fixed_auto (NULL,
&actx.signatures[1]),
GNUNET_JSON_spec_end ()
};
+ struct GNUNET_JSON_Specification seeds_tuple[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL,
+ &actx.transfer_secret_seeds[0]),
+ GNUNET_JSON_spec_fixed_auto (NULL,
+ &actx.transfer_secret_seeds[1]),
+ GNUNET_JSON_spec_end ()
+ };
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("rc",
&actx.rc),
- TALER_JSON_spec_tuple_of ("signatures",
- tuple),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_tuple_of ("signatures",
+ sig_tuple),
+ &actx.no_signatures),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_tuple_of ("batch_seeds",
+ seeds_tuple),
+ &actx.no_transfer_secret_seeds),
GNUNET_JSON_spec_mark_optional (
TALER_JSON_spec_age_commitment ("age_commitment",
&actx.age_commitment),
@@ -646,7 +800,8 @@ TEH_handler_reveal_melt (
* the size of TALER_CNC_KAPPA.
* Let's make sure we keep this in sync.
*/
- _Static_assert (KAPPA_MINUS_1 == 2);
+ _Static_assert (KAPPA_MINUS_1 == 2,
+ "TALER_CNC_KAPPA isn't 3!?!?");
/* Parse JSON body*/
ret = TALER_MHD_parse_json_data (rc->connection,
@@ -686,10 +841,21 @@ TEH_handler_reveal_melt (
actx.no_age_commitment
? NULL
: &actx.age_commitment,
- actx.signatures,
+ actx.no_signatures
+ ? NULL
+ : &actx.signatures,
+ actx.no_transfer_secret_seeds
+ ? NULL
+ : &actx.transfer_secret_seeds,
&result))
break;
+ if (GNUNET_OK !=
+ commit_reveal (rc->connection,
+ &actx.rc,
+ &result))
+ break;
+
/* Finally, return the signatures */
result = reply_melt_reveal_success (rc->connection,
&actx.refresh);
diff --git a/src/exchangedb/0004-refresh.sql b/src/exchangedb/0004-refresh.sql
@@ -0,0 +1,42 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2025 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION alter_table_refresh4()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ ALTER TABLE refresh
+ ADD COLUMN revealed BOOLEAN NOT NULL DEFAULT(FALSE),
+ ADD COLUMN transfer_pubs BYTEA[];
+ COMMENT ON COLUMN refresh.revealed
+ IS 'TRUE if the client has successfully revealed the secrets in the cut-and-choose step.';
+ COMMENT ON COLUMN refresh.transfer_pubs
+ IS 'The selected batch of transfer public keys, at noreveal_index';
+END $$;
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('refresh4'
+ ,'exchange-0004'
+ ,'alter'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am
@@ -29,7 +29,9 @@ sqlinputs = \
0002-*.sql \
exchange-0002.sql.in \
0003-*.sql \
- exchange-0003.sql.in
+ exchange-0003.sql.in \
+ 0004-*.sql \
+ exchange-0004.sql.in
sql_DATA = \
benchmark-0001.sql \
@@ -120,7 +122,6 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
pg_do_withdraw.h pg_do_withdraw.c \
pg_do_check_deposit_idempotent.h pg_do_check_deposit_idempotent.c \
pg_do_deposit.h pg_do_deposit.c \
- pg_do_melt.h pg_do_melt.c \
pg_do_refresh.h pg_do_refresh.c \
pg_do_purse_delete.c pg_do_purse_delete.h \
pg_do_purse_deposit.h pg_do_purse_deposit.c \
@@ -159,7 +160,6 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
pg_get_purse_deposit.h pg_get_purse_deposit.c \
pg_get_purse_request.c pg_get_purse_request.h \
pg_get_ready_deposit.h pg_get_ready_deposit.c \
- pg_get_refresh_reveal.h pg_get_refresh_reveal.c \
pg_get_reserve_balance.h pg_get_reserve_balance.c \
pg_get_reserve_by_h_planchets.h pg_get_reserve_by_h_planchets.c \
pg_get_reserve_history.c pg_get_reserve_history.h \
@@ -231,6 +231,7 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
pg_lookup_wire_fee_by_time.h pg_lookup_wire_fee_by_time.c \
pg_lookup_wire_timestamp.h pg_lookup_wire_timestamp.c \
pg_lookup_wire_transfer.h pg_lookup_wire_transfer.c \
+ pg_mark_refresh_reveal_success.h pg_mark_refresh_reveal_success.c \
pg_persist_kyc_attributes.h pg_persist_kyc_attributes.c \
pg_persist_policy_details.h pg_persist_policy_details.c \
pg_preflight.h pg_preflight.c \
diff --git a/src/exchangedb/exchange-0004.sql.in b/src/exchangedb/exchange-0004.sql.in
@@ -20,5 +20,6 @@ SELECT _v.register_patch('exchange-0004', NULL, NULL);
SET search_path TO exchange;
#include "0004-kyc_attributes.sql"
+#include "0004-refresh.sql"
COMMIT;
diff --git a/src/exchangedb/exchange_do_refresh.sql b/src/exchangedb/exchange_do_refresh.sql
@@ -21,6 +21,7 @@ CREATE FUNCTION exchange_do_refresh(
IN in_rc BYTEA,
IN in_now INT8,
IN in_refresh_seed BYTEA,
+ IN in_transfer_pubs BYTEA[],
IN in_planchets_h BYTEA,
IN in_amount_with_fee taler_amount,
IN in_blinding_seed BYTEA,
@@ -122,6 +123,7 @@ INSERT INTO exchange.refresh
,old_coin_pub
,old_coin_sig
,planchets_h
+ ,transfer_pubs
,amount_with_fee
,noreveal_index
,refresh_seed
@@ -138,6 +140,7 @@ INSERT INTO exchange.refresh
,in_old_coin_pub
,in_old_coin_sig
,in_planchets_h
+ ,in_transfer_pubs
,in_amount_with_fee
,in_noreveal_index
,in_refresh_seed
diff --git a/src/exchangedb/exchangedb_transactions.c b/src/exchangedb/exchangedb_transactions.c
@@ -86,7 +86,7 @@ TALER_EXCHANGEDB_calculate_transaction_list_totals (
}
have_refund = true;
break;
- case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
+ case TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER:
/* refunded += pos->value */
if (0 >
TALER_amount_add (&refunded,
@@ -97,7 +97,7 @@ TALER_EXCHANGEDB_calculate_transaction_list_totals (
return GNUNET_SYSERR;
}
break;
- case TALER_EXCHANGEDB_TT_RECOUP:
+ case TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW:
/* spent += pos->value */
if (0 >
TALER_amount_add (&spent,
diff --git a/src/exchangedb/perf_deposits_get_ready.c b/src/exchangedb/perf_deposits_get_ready.c
@@ -75,7 +75,7 @@ static struct TALER_MerchantWireHashP h_wire_wt;
*/
static struct DenomKeyPair **new_dkp;
-static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins;
+static struct CoinInfo *revealed_coins;
struct DenomKeyPair
{
@@ -84,6 +84,16 @@ struct DenomKeyPair
};
+struct CoinInfo
+{
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_CoinSpendSignatureP orig_coin_link_sig;
+ struct TALER_BlindedCoinHashP coin_envelope_hash;
+ struct TALER_BlindedDenominationSignature coin_sig;
+ struct TALER_ExchangeBlindingValues exchange_vals;
+ struct TALER_BlindedPlanchet blinded_planchet;
+};
+
/**
* Destroy a denomination key pair. The key is not necessarily removed from the DB.
*
@@ -201,7 +211,7 @@ run (void *cls)
struct TALER_EXCHANGEDB_Refund *ref;
unsigned int *perm;
unsigned long long duration_sq;
- struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin;
+ struct CoinInfo *ccoin;
struct TALER_DenomFeeSet fees;
struct GNUNET_CRYPTO_BlindingInputValues bi = {
.cipher = GNUNET_CRYPTO_BSA_RSA,
@@ -264,7 +274,7 @@ run (void *cls)
struct TALER_DenominationPublicKey);
revealed_coins
= GNUNET_new_array (MELT_NEW_COINS,
- struct TALER_EXCHANGEDB_RefreshRevealedCoin);
+ struct CoinInfo);
for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
{
struct GNUNET_TIME_Timestamp now;
diff --git a/src/exchangedb/perf_select_refunds_by_coin.c b/src/exchangedb/perf_select_refunds_by_coin.c
@@ -72,7 +72,7 @@ static struct TALER_MerchantWireHashP h_wire_wt;
static struct DenomKeyPair **new_dkp;
-static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins;
+static struct CoinInfo *revealed_coins;
struct DenomKeyPair
{
@@ -81,6 +81,16 @@ struct DenomKeyPair
};
+struct CoinInfo
+{
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_CoinSpendSignatureP orig_coin_link_sig;
+ struct TALER_BlindedCoinHashP coin_envelope_hash;
+ struct TALER_BlindedDenominationSignature coin_sig;
+ struct TALER_ExchangeBlindingValues exchange_vals;
+ struct TALER_BlindedPlanchet blinded_planchet;
+};
+
/**
* Destroy a denomination key pair. The key is not necessarily removed from the DB.
*
@@ -229,7 +239,7 @@ run (void *cls)
struct TALER_EXCHANGEDB_Refund *ref = NULL;
unsigned int *perm;
unsigned long long duration_sq;
- struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin;
+ struct CoinInfo *ccoin;
struct TALER_DenominationPublicKey *new_denom_pubs = NULL;
struct TALER_DenomFeeSet fees;
unsigned int count = 0;
@@ -290,7 +300,7 @@ run (void *cls)
struct TALER_DenominationPublicKey);
revealed_coins
= GNUNET_new_array (MELT_NEW_COINS,
- struct TALER_EXCHANGEDB_RefreshRevealedCoin);
+ struct CoinInfo);
for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
{
struct GNUNET_TIME_Timestamp now;
diff --git a/src/exchangedb/pg_do_melt.c b/src/exchangedb/pg_do_melt.c
@@ -1,82 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-/**
- * @file exchangedb/pg_do_melt.c
- * @brief Implementation of the do_melt function for Postgres
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include "taler/taler_error_codes.h"
-#include "taler/taler_dbevents.h"
-#include "taler/taler_pq_lib.h"
-#include "pg_do_melt.h"
-#include "pg_helper.h"
-
-
-enum GNUNET_DB_QueryStatus
-TEH_PG_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)
-{
- 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 (pg->conn,
- &refresh->amount_with_fee),
- GNUNET_PQ_query_param_auto_from_type (&refresh->rc),
- GNUNET_PQ_query_param_auto_from_type (&refresh->coin.coin_pub),
- GNUNET_PQ_query_param_auto_from_type (&refresh->coin_sig),
- GNUNET_PQ_query_param_uint64 (&known_coin_id),
- GNUNET_PQ_query_param_uint32 (&refresh->noreveal_index),
- GNUNET_PQ_query_param_bool (*zombie_required),
- GNUNET_PQ_query_param_end
- };
- bool is_null;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_bool ("balance_ok",
- balance_ok),
- GNUNET_PQ_result_spec_bool ("zombie_required",
- zombie_required),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_uint32 ("noreveal_index",
- &refresh->noreveal_index),
- &is_null),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- PREPARE (pg,
- "call_melt",
- "SELECT "
- " out_balance_ok AS balance_ok"
- ",out_zombie_bad AS zombie_required"
- ",out_noreveal_index AS noreveal_index"
- " FROM exchange_do_melt"
- " ($1,$2,$3,$4,$5,$6,$7,$8);");
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "call_melt",
- params,
- rs);
- if (is_null)
- refresh->noreveal_index = UINT32_MAX; /* set to very invalid value */
- return qs;
-}
diff --git a/src/exchangedb/pg_do_melt.h b/src/exchangedb/pg_do_melt.h
@@ -1,49 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-/**
- * @file exchangedb/pg_do_melt.h
- * @brief implementation of the do_melt function for Postgres
- * @author Christian Grothoff
- */
-#ifndef PG_DO_MELT_H
-#define PG_DO_MELT_H
-
-#include "taler/taler_util.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_exchangedb_plugin.h"
-/**
- * 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 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
- * @param[in,out] zombie_required true if the melt must only succeed if the coin is a zombie, set to false if the requirement was satisfied
- * @param[out] balance_ok set to true if the balance was sufficient
- * @return query execution status
- */
-enum GNUNET_DB_QueryStatus
-TEH_PG_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);
-
-#endif
diff --git a/src/exchangedb/pg_do_refresh.c b/src/exchangedb/pg_do_refresh.c
@@ -30,7 +30,7 @@
enum GNUNET_DB_QueryStatus
TEH_PG_do_refresh (
void *cls,
- struct TALER_EXCHANGEDB_Refresh_v27 *refresh,
+ struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *refresh,
const struct GNUNET_TIME_Timestamp *timestamp,
bool *found,
uint32_t *noreveal_index,
@@ -43,9 +43,14 @@ TEH_PG_do_refresh (
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (&refresh->rc),
GNUNET_PQ_query_param_timestamp (timestamp),
- GNUNET_PQ_query_param_auto_from_type (&refresh->refresh_seed),
+ GNUNET_PQ_query_param_auto_from_type (&refresh->refresh_seed), /* 3 */
+ (refresh->is_v27_refresh)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_array_auto_from_type (refresh->num_coins,
+ refresh->transfer_pubs,
+ pg->conn),
GNUNET_PQ_query_param_auto_from_type (&refresh->planchets_h),
- TALER_PQ_query_param_amount (pg->conn,
+ TALER_PQ_query_param_amount (pg->conn, /* 6 */
&refresh->amount_with_fee),
(refresh->no_blinding_seed)
? GNUNET_PQ_query_param_null ()
@@ -56,18 +61,18 @@ TEH_PG_do_refresh (
pg->conn)
: GNUNET_PQ_query_param_null (),
(0 < refresh->num_cs_r_values)
- ? GNUNET_PQ_query_param_uint64 (&refresh->cs_r_choices)
+ ? GNUNET_PQ_query_param_uint64 (&refresh->cs_r_choices) /* 9 */
: GNUNET_PQ_query_param_null (),
GNUNET_PQ_query_param_auto_from_type (&refresh->selected_h),
TALER_PQ_query_param_array_blinded_denom_sig (refresh->num_coins,
refresh->denom_sigs,
pg->conn),
- GNUNET_PQ_query_param_array_uint64 (refresh->num_coins,
+ GNUNET_PQ_query_param_array_uint64 (refresh->num_coins, /* 12 */
refresh->denom_serials,
pg->conn),
GNUNET_PQ_query_param_auto_from_type (&refresh->coin.coin_pub),
GNUNET_PQ_query_param_auto_from_type (&refresh->coin_sig),
- GNUNET_PQ_query_param_uint32 (&refresh->noreveal_index),
+ GNUNET_PQ_query_param_uint32 (&refresh->noreveal_index), /* 15 */
GNUNET_PQ_query_param_bool (*zombie_required),
GNUNET_PQ_query_param_end
};
@@ -108,7 +113,7 @@ TEH_PG_do_refresh (
",out_noreveal_index AS noreveal_index"
",out_coin_balance AS coin_balance"
" FROM exchange_do_refresh"
- " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15);");
+ " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16);");
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"call_refresh",
params,
diff --git a/src/exchangedb/pg_do_refresh.h b/src/exchangedb/pg_do_refresh.h
@@ -45,7 +45,7 @@
enum GNUNET_DB_QueryStatus
TEH_PG_do_refresh (
void *cls,
- struct TALER_EXCHANGEDB_Refresh_v27 *refresh,
+ struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *refresh,
const struct GNUNET_TIME_Timestamp *timestamp,
bool *found,
uint32_t *noreveal_index,
diff --git a/src/exchangedb/pg_get_coin_transactions.c b/src/exchangedb/pg_get_coin_transactions.c
@@ -489,7 +489,7 @@ add_old_coin_recoup (void *cls,
}
tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
tl->next = chc->head;
- tl->type = TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP;
+ tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER;
tl->details.old_coin_recoup = recoup;
tl->serial_id = serial_id;
tl->coin_history_id = chc->chid;
@@ -553,7 +553,7 @@ add_coin_recoup (void *cls,
}
tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
tl->next = chc->head;
- tl->type = TALER_EXCHANGEDB_TT_RECOUP;
+ tl->type = TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW;
tl->details.recoup = recoup;
tl->serial_id = serial_id;
tl->coin_history_id = chc->chid;
@@ -745,11 +745,7 @@ handle_history_entry (void *cls,
{ "refunds",
"get_refunds_by_coin",
&add_coin_refund },
- [TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP] =
- { "recoup_refresh::OLD",
- "recoup_by_old_coin",
- &add_old_coin_recoup },
- [TALER_EXCHANGEDB_TT_RECOUP] =
+ [TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW] =
{ "recoup",
"recoup_by_coin",
&add_coin_recoup },
@@ -757,6 +753,10 @@ handle_history_entry (void *cls,
{ "recoup_refresh::NEW",
"recoup_by_refreshed_coin",
&add_coin_recoup_refresh },
+ [TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER] =
+ { "recoup_refresh::OLD",
+ "recoup_by_old_coin",
+ &add_old_coin_recoup },
[TALER_EXCHANGEDB_TT_RESERVE_OPEN] =
{ "reserves_open_deposits",
"reserve_open_by_coin",
diff --git a/src/exchangedb/pg_get_refresh.c b/src/exchangedb/pg_get_refresh.c
@@ -29,7 +29,7 @@
enum GNUNET_DB_QueryStatus
TEH_PG_get_refresh (void *cls,
const struct TALER_RefreshCommitmentP *rc,
- struct TALER_EXCHANGEDB_Refresh_v27 *refresh)
+ struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *refresh)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
@@ -38,9 +38,12 @@ TEH_PG_get_refresh (void *cls,
};
bool no_cs_r_values;
bool no_cs_r_choices;
+ bool no_transfer_pubs;
size_t num_denom_sigs;
+ size_t num_transfer_pubs;
struct TALER_BlindedDenominationSignature *denom_sigs = NULL;
struct GNUNET_CRYPTO_CSPublicRPairP *cs_r_values = NULL;
+ struct TALER_TransferPublicKeyP *transfer_pubs = NULL;
uint64_t *denom_serials = NULL;
struct GNUNET_PQ_ResultSpec rs[] = {
TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
@@ -75,6 +78,13 @@ TEH_PG_get_refresh (void *cls,
&refresh->planchets_h),
GNUNET_PQ_result_spec_auto_from_type ("selected_h",
&refresh->selected_h),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_array_fixed_size (pg->conn,
+ "transfer_pubs",
+ sizeof(*transfer_pubs),
+ &num_transfer_pubs,
+ (void **) &transfer_pubs),
+ &no_transfer_pubs),
GNUNET_PQ_result_spec_array_uint64 (pg->conn,
"denom_serials",
&refresh->num_coins,
@@ -83,6 +93,8 @@ TEH_PG_get_refresh (void *cls,
"denom_sigs",
&num_denom_sigs,
&denom_sigs),
+ GNUNET_PQ_result_spec_bool ("revealed",
+ &refresh->revealed),
GNUNET_PQ_result_spec_end
};
enum GNUNET_DB_QueryStatus qs;
@@ -103,9 +115,11 @@ TEH_PG_get_refresh (void *cls,
",cs_r_values"
",cs_r_choices"
",planchets_h"
+ ",transfer_pubs"
",selected_h"
",denom_serials"
",denom_sigs"
+ ",revealed"
" FROM refresh"
" JOIN known_coins kc"
" ON (old_coin_pub = kc.coin_pub)"
@@ -138,6 +152,27 @@ TEH_PG_get_refresh (void *cls,
GNUNET_PQ_cleanup_result (rs);
return GNUNET_DB_STATUS_HARD_ERROR;
}
+ if (no_transfer_pubs)
+ {
+ refresh->is_v27_refresh = true;
+ refresh->transfer_pubs = NULL;
+ }
+ else
+ {
+ if (num_transfer_pubs != refresh->num_coins)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "got inconsistent number of transfer_pubs in refresh from DB: "
+ "num_coins=%ld, num_transfer_pubs=%ld\n",
+ refresh->num_coins,
+ num_transfer_pubs);
+ GNUNET_PQ_cleanup_result (rs);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ refresh->is_v27_refresh = false;
+ refresh->transfer_pubs = transfer_pubs;
+ }
if (refresh->no_blinding_seed != no_cs_r_values)
{
GNUNET_break (0);
@@ -164,6 +199,7 @@ TEH_PG_get_refresh (void *cls,
refresh->denom_sigs = denom_sigs;
refresh->denom_serials = denom_serials;
refresh->cs_r_values = cs_r_values;
+ transfer_pubs = NULL;
denom_sigs = NULL;
denom_serials = NULL;
cs_r_values = NULL;
diff --git a/src/exchangedb/pg_get_refresh.h b/src/exchangedb/pg_get_refresh.h
@@ -37,6 +37,6 @@
enum GNUNET_DB_QueryStatus
TEH_PG_get_refresh (void *cls,
const struct TALER_RefreshCommitmentP *rc,
- struct TALER_EXCHANGEDB_Refresh_v27 *refresh);
+ struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *refresh);
#endif
diff --git a/src/exchangedb/pg_get_refresh_reveal.c b/src/exchangedb/pg_get_refresh_reveal.c
@@ -1,213 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-/**
- * @file exchangedb/pg_get_refresh_reveal.c
- * @brief Implementation of the get_refresh_reveal function for Postgres
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include "taler/taler_error_codes.h"
-#include "taler/taler_dbevents.h"
-#include "taler/taler_pq_lib.h"
-#include "pg_get_refresh_reveal.h"
-#include "pg_helper.h"
-
-
-/**
- * Context where we aggregate data from the database.
- * Closure for #add_revealed_coins().
- */
-struct GetRevealContext
-{
- /**
- * Array of revealed coins we obtained from the DB.
- */
- struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs;
-
- /**
- * Length of the @a rrcs array.
- */
- unsigned int rrcs_len;
-
- /**
- * Set to an error code if we ran into trouble.
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct GetRevealContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-add_revealed_coins (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct GetRevealContext *grctx = cls;
-
- if (0 == num_results)
- return;
- grctx->rrcs = GNUNET_new_array (num_results,
- struct TALER_EXCHANGEDB_RefreshRevealedCoin);
- grctx->rrcs_len = num_results;
- for (unsigned int i = 0; i < num_results; i++)
- {
- uint32_t off;
- struct GNUNET_PQ_ResultSpec rso[] = {
- GNUNET_PQ_result_spec_uint32 ("freshcoin_index",
- &off),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rso,
- i))
- {
- GNUNET_break (0);
- grctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- if (off >= num_results)
- {
- GNUNET_break (0);
- grctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- {
- struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &grctx->rrcs[off];
- struct GNUNET_PQ_ResultSpec rsi[] = {
- /* NOTE: freshcoin_index selected and discarded here... */
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &rrc->h_denom_pub),
- GNUNET_PQ_result_spec_auto_from_type ("link_sig",
- &rrc->orig_coin_link_sig),
- GNUNET_PQ_result_spec_auto_from_type ("h_coin_ev",
- &rrc->coin_envelope_hash),
- TALER_PQ_result_spec_blinded_planchet ("coin_ev",
- &rrc->blinded_planchet),
- TALER_PQ_result_spec_exchange_withdraw_values ("ewv",
- &rrc->exchange_vals),
- TALER_PQ_result_spec_blinded_denom_sig ("ev_sig",
- &rrc->coin_sig),
- GNUNET_PQ_result_spec_end
- };
-
- if (NULL !=
- rrc->blinded_planchet.blinded_message)
- {
- /* duplicate offset, not allowed */
- GNUNET_break (0);
- grctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rsi,
- i))
- {
- GNUNET_break (0);
- grctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- }
- }
-}
-
-
-enum GNUNET_DB_QueryStatus
-TEH_PG_get_refresh_reveal (void *cls,
- const struct TALER_RefreshCommitmentP *rc,
- TALER_EXCHANGEDB_RefreshCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GetRevealContext grctx;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (rc),
- GNUNET_PQ_query_param_end
- };
-
- memset (&grctx,
- 0,
- sizeof (grctx));
-
- /* Obtain information about the coins created in a refresh
- operation, used in #postgres_get_refresh_reveal() */
- PREPARE (pg,
- "get_refresh_revealed_coins",
- "SELECT "
- " rrc.freshcoin_index"
- ",denom.denom_pub_hash"
- ",rrc.h_coin_ev"
- ",rrc.link_sig"
- ",rrc.coin_ev"
- ",rrc.ewv"
- ",rrc.ev_sig"
- " FROM refresh_commitments"
- " JOIN refresh_revealed_coins rrc"
- " USING (melt_serial_id)"
- " JOIN denominations denom "
- " USING (denominations_serial)"
- " WHERE rc=$1;");
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "get_refresh_revealed_coins",
- params,
- &add_revealed_coins,
- &grctx);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- goto cleanup;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- default: /* can have more than one result */
- break;
- }
- switch (grctx.qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- goto cleanup;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* should be impossible */
- break;
- }
-
- /* Pass result back to application */
- cb (cb_cls,
- grctx.rrcs_len,
- grctx.rrcs);
-cleanup:
- for (unsigned int i = 0; i < grctx.rrcs_len; i++)
- {
- struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &grctx.rrcs[i];
-
- TALER_blinded_denom_sig_free (&rrc->coin_sig);
- TALER_blinded_planchet_free (&rrc->blinded_planchet);
- TALER_denom_ewv_free (&rrc->exchange_vals);
- }
- GNUNET_free (grctx.rrcs);
- return qs;
-}
diff --git a/src/exchangedb/pg_get_refresh_reveal.h b/src/exchangedb/pg_get_refresh_reveal.h
@@ -1,44 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-/**
- * @file exchangedb/pg_get_refresh_reveal.h
- * @brief implementation of the get_refresh_reveal function for Postgres
- * @author Christian Grothoff
- */
-#ifndef PG_GET_REFRESH_REVEAL_H
-#define PG_GET_REFRESH_REVEAL_H
-
-#include "taler/taler_util.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_exchangedb_plugin.h"
-
-/**
- * Lookup in the database the coins that we want to
- * create in the given refresh operation.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param rc identify commitment and thus refresh operation
- * @param cb function to call with the results
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-enum GNUNET_DB_QueryStatus
-TEH_PG_get_refresh_reveal (void *cls,
- const struct TALER_RefreshCommitmentP *rc,
- TALER_EXCHANGEDB_RefreshCallback cb,
- void *cb_cls);
-
-#endif
diff --git a/src/exchangedb/pg_mark_refresh_reveal_success.c b/src/exchangedb/pg_mark_refresh_reveal_success.c
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_mark_refresh_reveal_success.c
+ * @brief Implementation of the mark_refresh_reveal_success function for Postgres
+ * @author Özgür Kesim
+ */
+#include "taler/platform.h"
+#include "taler/taler_error_codes.h"
+#include "taler/taler_dbevents.h"
+#include "taler/taler_pq_lib.h"
+#include "pg_mark_refresh_reveal_success.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_mark_refresh_reveal_success (
+ void *cls,
+ const struct TALER_RefreshCommitmentP *rc)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (rc),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "mark_refresh_reveal_success",
+ "UPDATE refresh"
+ " SET revealed=true"
+ " WHERE rc = $1");
+
+ return GNUNET_PQ_eval_prepared_non_select (
+ pg->conn,
+ "mark_refresh_reveal_success",
+ params);
+}
+\ No newline at end of file
diff --git a/src/exchangedb/pg_mark_refresh_reveal_success.h b/src/exchangedb/pg_mark_refresh_reveal_success.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_mark_refresh_reveal_success.h
+ * @brief Mark the successful reveal of a refresh in the database
+ * @author Özgür Kesim
+ */
+#ifndef PG_MARK_REFRESH_REVEAL_SUCCESS_H
+#define PG_MARK_REFRESH_REVEAL_SUCCESS_H
+
+#include "taler/taler_util.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_exchangedb_plugin.h"
+
+
+/**
+ * Mark a refresh, given by the commitment, as successfully revealed.
+ *
+ * @param cls closure
+ * @param rc commitment for the refresh
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_mark_refresh_reveal_success (
+ void *cls,
+ const struct TALER_RefreshCommitmentP *rc);
+
+#endif
diff --git a/src/exchangedb/plugin_exchangedb_common.c b/src/exchangedb/plugin_exchangedb_common.c
@@ -134,7 +134,7 @@ TEH_COMMON_free_coin_transaction_list (
case TALER_EXCHANGEDB_TT_MELT:
GNUNET_free (tl->details.melt);
break;
- case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
+ case TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER:
{
struct TALER_EXCHANGEDB_RecoupRefreshListEntry *rr;
@@ -146,7 +146,7 @@ TEH_COMMON_free_coin_transaction_list (
case TALER_EXCHANGEDB_TT_REFUND:
GNUNET_free (tl->details.refund);
break;
- case TALER_EXCHANGEDB_TT_RECOUP:
+ case TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW:
GNUNET_free (tl->details.recoup);
break;
case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -48,7 +48,6 @@
#include "pg_do_withdraw.h"
#include "pg_do_check_deposit_idempotent.h"
#include "pg_do_deposit.h"
-#include "pg_do_melt.h"
#include "pg_do_purse_delete.h"
#include "pg_do_purse_deposit.h"
#include "pg_do_purse_merge.h"
@@ -88,7 +87,6 @@
#include "pg_get_purse_request.h"
#include "pg_get_ready_deposit.h"
#include "pg_get_refresh.h"
-#include "pg_get_refresh_reveal.h"
#include "pg_get_reserve_balance.h"
#include "pg_get_reserve_by_h_planchets.h"
#include "pg_get_reserve_history.h"
@@ -159,6 +157,7 @@
#include "pg_lookup_wire_fee_by_time.h"
#include "pg_lookup_wire_timestamp.h"
#include "pg_lookup_wire_transfer.h"
+#include "pg_mark_refresh_reveal_success.h"
#include "pg_persist_kyc_attributes.h"
#include "pg_persist_policy_details.h"
#include "pg_preflight.h"
@@ -558,6 +557,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
= &TEH_PG_add_policy_fulfillment_proof;
plugin->do_refresh
= &TEH_PG_do_refresh;
+ plugin->mark_refresh_reveal_success
+ = &TEH_PG_mark_refresh_reveal_success;
plugin->do_refund
= &TEH_PG_do_refund;
plugin->do_recoup
diff --git a/src/include/taler/taler_crypto_lib.h b/src/include/taler/taler_crypto_lib.h
@@ -670,15 +670,43 @@ struct TALER_AmlOfficerSignatureP
struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
};
+/**
+ * @since vDOLDPLUS
+ *
+ * The seed from which a specific batch of fresh coin material is
+ * derived from #TALER_PublicRefreshMasterSeedP for a refresh request.
+ */
+struct TALER_PrivateRefreshBatchSeedP
+{
+ /**
+ * The seed is a hash code.
+ */
+ struct GNUNET_HashCode batch_seed;
+};
+
+/**
+ * @since vDOLDPLUS
+ *
+ * From a #TALER_PublicRefreshMasterSeedP, we derive first
+ * kappa many seeds for batches of n coins in the struct
+ * #TALER_KappaPrivateRefreshBatchSeedsP. Each batch seed is
+ * used to derive n individual fresh coin master secrets.
+ */
+struct TALER_KappaPrivateRefreshBatchSeedsP
+{
+ /**
+ * A tuple of #TALER_CNC_KAPPA many batch seeds.
+ */
+ struct TALER_PrivateRefreshBatchSeedP tuple[TALER_CNC_KAPPA];
+};
/**
- * The public master seed from which public nonces are derived
- * for new fresh coin candidates. The seed (and its derived nonces)
- * itself are public and are used to proof ownership of the old coin
- * by signing a message derived from those seeds. The signature
- * from that operation is then used as seed for a fresh coin.
- * See https://docs.taler.net/design-documents/062-pq-refresh.html
- * for more details.
+ * @since vDOLDPLUS
+ *
+ * The public master seed from which private coin seeds are derived
+ * for new fresh coin candidates, using the old coins private key.
+ *
+ * Note that this value has changed its meaning from v27.
*/
struct TALER_PublicRefreshMasterSeedP
{
@@ -689,6 +717,9 @@ struct TALER_PublicRefreshMasterSeedP
};
/**
+ * @since v27
+ * @deprecated after vDOLDPLUS
+ *
* A batch seed is signed by the old coin's private key
* and from that signature n fresh coin's secrets are derived.
*/
@@ -701,6 +732,9 @@ struct TALER_PublicRefreshNonceP
};
/**
+ * @since v27
+ * @deprecated after vDOLDPLUS
+ *
* From a #TALER_PublicRefreshMasterSeedP, we derive first
* kappa many seeds for batches of n coins in the struct
* #TALER_KappaPublicRefreshNoncesP.
@@ -848,20 +882,6 @@ struct TALER_PlanchetMasterSecretP
/**
* Master key material for the deriviation of
- * private coins and blinding factors.
- */
-struct TALER_RefreshMasterSecretP
-{
-
- /**
- * Key material.
- */
- uint32_t key_data[8];
-
-};
-
-/**
- * Master key material for the deriviation of
* blinding factors.
*/
struct TALER_BlindingMasterSeedP
@@ -1722,11 +1742,13 @@ TALER_cs_withdraw_seed_to_blinding_seed (
* @brief Method to derive a seed for blinding from a seed for refresh
*
* @param seed input refresh seed
+ * @param coin_priv the private key of the old coin
* @param[out] blinding_seed derived blinding seed
*/
void
-TALER_cs_refresh_secret_to_blinding_seed (
- const struct TALER_RefreshMasterSecretP *seed,
+TALER_cs_refresh_seed_to_blinding_seed (
+ const struct TALER_PublicRefreshMasterSeedP *seed,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
struct TALER_BlindingMasterSeedP *blinding_seed);
@@ -1763,6 +1785,7 @@ TALER_cs_derive_nonces_from_seed (
const uint32_t indices[static num],
struct GNUNET_CRYPTO_CsSessionNonce nonces[static num]);
+
/**
* @brief method to find the indices of denominations for CS
*
@@ -1815,21 +1838,6 @@ TALER_cs_derive_only_cs_blind_nonces_from_seed (
/**
- * @brief Method to derive /csr nonce
- * to be used during refresh/melt operation.
- *
- * @param rms secret input for the refresh operation
- * @param idx index of the fresh coin
- * @param[out] nonce set to nonce included in the request to generate R_0 and R_1
- */
-void
-TALER_cs_refresh_nonce_derive (
- const struct TALER_RefreshMasterSecretP *rms,
- uint32_t idx,
- struct GNUNET_CRYPTO_CsSessionNonce *nonce);
-
-
-/**
* Initialize denomination public-private key pair.
*
* For #GNUNET_CRYPTO_BSA_RSA, an additional "unsigned int"
@@ -2349,6 +2357,9 @@ GNUNET_NETWORK_STRUCT_END
/**
+ * @since v27
+ * @deprecated after vDOLDPLUS
+ *
* The signature of the old coin over a public nonce,
* provided during the /reveal-melt operation as
* a) proof of ownership of the old coin
@@ -2365,6 +2376,9 @@ struct TALER_PrivateRefreshNonceSignatureP
};
/**
+ * @since v27
+ * @deprecated after vDOLDPLUS
+ *
* Sign a public nonce with the old coin, to prove
* coin ownership, with purpose
* TALER_SIGNATURE_WALLET_COIN_LINK
@@ -2387,6 +2401,9 @@ TALER_wallet_refresh_nonce_sign (
/**
+ * @since v27
+ * @deprecated after vDOLDPLUS
+ *
* Verify the signature on a public nonce, provided by the old coin,
* to prove coin ownership during a melt/refresh operation.
*
@@ -2409,6 +2426,9 @@ TALER_wallet_refresh_nonce_verify (
/**
+ * @since v27
+ * @deprecated after vDOLDPLUS
+ *
* From a given signature for a refresh nonce,
* derive a fresh master secret for the planchet
* of a fresh coin.
@@ -2418,7 +2438,7 @@ TALER_wallet_refresh_nonce_verify (
* @param[out] secrets the master secrets for @a num fresh coins
*/
void
-TALER_refresh_signature_to_secrets (
+TALER_refresh_signature_to_secrets_v27 (
const struct TALER_PrivateRefreshNonceSignatureP *sig,
size_t num_secrets,
struct TALER_PlanchetMasterSecretP secrets[static num_secrets]);
@@ -2442,25 +2462,6 @@ TALER_transfer_secret_to_planchet_secret (
/**
- * Derive the @a coin_num transfer private key @a tpriv from a refresh from
- * 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);
-
-
-/**
* Setup secret information for fresh a coin to be
* withdrawn.
*
@@ -2518,19 +2519,118 @@ TALER_withdraw_expand_secrets (
*/
void
TALER_refresh_master_setup_random (
- struct TALER_RefreshMasterSecretP *rms);
+ struct TALER_PublicRefreshMasterSeedP *rms);
/**
- * Derive a master refresh seed from the refresh master secret
+ * @since vDOLDPLUS
+ *
+ * Expand a master refresh seed using the old coins private
+ * into kappa many batch seeds, key each of which expands into
+ * private refresh seeds for n coins.
+ *
+ * @param refresh_master_seed master seed to expand from
+ * @param coin_priv the old coin's private key to expand from.
+ * @param[out] kappa_refresh_seeds tuple of #TALER_CNC_KAPPA many batch nonces
*/
void
-TALER_refresh_master_secret_to_refresh_seed (
- const struct TALER_RefreshMasterSecretP *rms,
- struct TALER_PublicRefreshMasterSeedP *r_seed);
+TALER_refresh_expand_seed_to_kappa_batch_seeds (
+ const struct TALER_PublicRefreshMasterSeedP *refresh_master_seed,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_KappaPrivateRefreshBatchSeedsP *kappa_refresh_seeds);
+/**
+ * @since vDOLDPLUS
+ *
+ * Expands a secret seed for a batch of coin candidates for refresh
+ * to the array of transfer private keys.
+ *
+ * @param batch_seed secret seed for a batch of coin candidates
+ * @param num_transfer_pks number of elements in @a transfer_pks
+ * @param[out] transfer_pks output array of transfer private keys
+ */
+void
+TALER_refresh_expand_batch_seed_to_transfer_private_keys (
+ const struct TALER_PrivateRefreshBatchSeedP *batch_seed,
+ size_t num_transfer_pks,
+ struct TALER_TransferPrivateKeyP transfer_pks[num_transfer_pks]);
/**
+ * @since vDOLDPLUS
+ *
+ * Expands a secret seed for a batch of coin candidates for refresh
+ * to the array of transfer secrets.
+ *
+ * This is a simple helper function that calls under the hood
+ * TALER_refresh_expand_batch_seed_to_transfer_private_keys
+ * and TALER_link_reveal_transfer_secret.
+ *
+ * @param batch_seed secret seed for a batch of coin candidates
+ * @param coin_pub the old coin's public key
+ * @param num_transfer_secrets number of elements in @a planchet_secrets
+ * @param[out] transfer_secrets output array of transfer secrets
+ */
+void
+TALER_refresh_expand_batch_seed_to_transfer_secrets (
+ const struct TALER_PrivateRefreshBatchSeedP *batch_seed,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ size_t num_transfer_secrets,
+ struct TALER_TransferSecretP transfer_secrets[num_transfer_secrets]);
+
+
+/**
+ * @since vDOLDPLUS
+ *
+ * Expands a secret seed for a batch of coin candidates for refresh
+ * to the array of planchet master secrets.
+ *
+ * This is a simple helper function that calls under the hood
+ * TALER_refresh_expand_batch_seed_to_transfer_private_keys,
+ * TALER_link_reveal_transfer_secret and
+ * TALER_transfer_secret_to_planchet_secret.
+ *
+ * @param batch_seed secret seed for a batch of coin candidates
+ * @param coin_pub the old coin's public key
+ * @param num_planchet_secrets number of elements in @a planchet_secrets
+ * @param[out] planchet_secrets output array of planchet master secrets
+ */
+void
+TALER_refresh_expand_batch_seed_to_planchet_master_secrets (
+ const struct TALER_PrivateRefreshBatchSeedP *batch_seed,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ size_t num_planchet_secrets,
+ struct TALER_PlanchetMasterSecretP planchet_secrets[num_planchet_secrets]);
+
+/**
+ * @since vDOLDPLUS
+ *
+ * Expands a secret seed for a batch of coin candidates for refresh
+ * to the array of planchet secrets and the array of transfer public keys.
+ *
+ * This is a simple helper function that calls under the hood
+ * TALER_refresh_expand_batch_seed_to_transfer_private_keys,
+ * TALER_link_reveal_transfer_secret and
+ * TALER_transfer_secret_to_planchet_secret
+ * and the public key generation.
+ *
+ * @param batch_seed secret seed for a batch of coin candidates
+ * @param coin_pub the old coin's public key
+ * @param num number of elements in @a transfer_secrets and @a transfer_pubs
+ * @param[out] planchet_secrets output array of transfer planchet_secrets
+ * @param[out] transfer_pubs output array of transfer public keys
+ */
+void
+TALER_refresh_expand_batch_seed_to_transfer_data (
+ const struct TALER_PrivateRefreshBatchSeedP *batch_seed,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ size_t num,
+ struct TALER_PlanchetMasterSecretP planchet_secrets[num],
+ struct TALER_TransferPublicKeyP transfer_pubs[num]);
+
+/**
+ * @since v27
+ * @deprecated after vDOLDPLUS
+ *
* Expand a refresh seed into kappa many batch seeds, each
* of which expands into nonces for n coins.
*
@@ -2538,7 +2638,7 @@ TALER_refresh_master_secret_to_refresh_seed (
* @param[out] kappa_nonces tuple of #TALER_CNC_KAPPA many batch nonces
*/
void
-TALER_refresh_expand_kappa_nonces (
+TALER_refresh_expand_kappa_nonces_v27 (
const struct TALER_PublicRefreshMasterSeedP *refresh_seed,
struct TALER_KappaPublicRefreshNoncesP *kappa_nonces);
@@ -2727,29 +2827,6 @@ struct TALER_RefreshCommitmentEntry
/**
- * Compute the commitment for a /refresh/melt operation from
- * the respective public inputs.
- *
- * @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
- * @param amount_with_fee amount to be melted, including fee
- */
-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,
- const struct TALER_Amount *amount_with_fee);
-
-
-/**
* Helper struct to carry kappa many #TALER_BlindedCoinHashP.
*/
struct TALER_KappaHashBlindedPlanchetsP
@@ -2758,28 +2835,82 @@ struct TALER_KappaHashBlindedPlanchetsP
};
/**
+ * Helper struct to carry information about TALER_CNC_KAPPA
+ * many batches of transfer public keys
+ */
+struct TALER_KappaTransferPublicKeys
+{
+ /**
+ * Number of elements in each @e batch
+ */
+ size_t num_transfer_pubs;
+
+ /**
+ * Pointers to the individual batches of @e num_transfer_pubs
+ */
+ const struct TALER_TransferPublicKeyP *batch[TALER_CNC_KAPPA];
+};
+
+
+/**
+ * @since vDOLDPLUS
+ *
* Compute the commitment for a /melt operation from
- * the respective public inputs. The endpoint is available starting
- * with protocol version v27.
+ * the respective public inputs.
*
* @param[out] rc set to the value the wallet must commit to
* @param refresh_seed refresh master seed to include
* @param blinding_seed blinding master seed for CS denominations, might be NULL
+ * @param k_transfer_pubs TALER_CNC_KAPPA many batches of n transfer public keys,
* @param k_bps_h TALER_CNC_KAPPA many hashes of blinded coin envelopes,
- * one for each batch of n coin candidates.
- * Note that these were over planchets and denominations.
+ * one for each batch of n coin candidates.
+ * Note that these were over planchets and denominations.
* @param coin_pub public key of the coin to be melted
* @param amount_with_fee amount to be melted, including fee
*/
void
-TALER_refresh_get_commitment_v27 (
+TALER_refresh_get_commitment (
struct TALER_RefreshCommitmentP *rc,
const struct TALER_PublicRefreshMasterSeedP *refresh_seed,
const struct TALER_BlindingMasterSeedP *blinding_seed,
+ const struct TALER_KappaTransferPublicKeys *k_transfer_pubs,
const struct TALER_KappaHashBlindedPlanchetsP *k_bps_h,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *amount_with_fee);
+/**
+ * @since v27
+ * @deprecated vDOLDPLUS
+ *
+ * Note: this function is only relevant for the exchange as it has to
+ * support clients that still use v27 of the refresh protocol.
+ *
+ * Compute the commitment for a /melt operation from
+ * the respective public inputs, for refreshes of v27.
+ *
+ * @param[out] rc set to the value the wallet must commit to
+ * @param refresh_seed refresh master seed to include
+ * @param blinding_seed blinding master seed for CS denominations, might be NULL
+ * @param k_bps_h TALER_CNC_KAPPA many hashes of blinded coin envelopes,
+ * one for each batch of n coin candidates.
+ * Note that these were over planchets and denominations.
+ * @param coin_pub public key of the coin to be melted
+ * @param amount_with_fee amount to be melted, including fee
+ */
+#define TALER_refresh_get_commitment_v27(rc, \
+ refresh_seed, \
+ blinding_seed, \
+ k_bps_h, \
+ coin_pub, \
+ amount_with_fee) \
+ TALER_refresh_get_commitment ((rc), \
+ (refresh_seed), \
+ (blinding_seed), \
+ NULL, \
+ (k_bps_h), \
+ (coin_pub), \
+ (amount_with_fee))
+
/**
* Encrypt contract for transmission to a party that will
@@ -4598,7 +4729,7 @@ TALER_wallet_withdraw_sign (
NULL, \
0, \
(reserve_priv), \
- (reserve_sig));
+ (reserve_sig))
/**
@@ -4651,7 +4782,7 @@ TALER_wallet_withdraw_verify (
NULL, \
0, \
(reserve_pub), \
- (reserve_sig));
+ (reserve_sig))
/**
diff --git a/src/include/taler/taler_exchange_service.h b/src/include/taler/taler_exchange_service.h
@@ -2987,10 +2987,10 @@ struct TALER_EXCHANGE_MeltInput
struct TALER_EXCHANGE_RevealMeltInput
{
/**
- * The master secret for the melt/reveal, from which all other
- * secret material is derived from
+ * The public master seed for the melt/reveal, from which all other
+ * secret material is derived from, using the old coin's private key.
*/
- const struct TALER_RefreshMasterSecretP *rms;
+ const struct TALER_PublicRefreshMasterSeedP *rms;
/**
* The input data from the prior call to /melt
@@ -3064,13 +3064,13 @@ TALER_EXCHANGE_reveal_melt_cancel (
/**
* @brief A /melt Handle
*/
-struct TALER_EXCHANGE_MeltHandle_v27;
+struct TALER_EXCHANGE_MeltHandle;
/**
* Response returned to a /melt request.
*/
-struct TALER_EXCHANGE_MeltResponse_v27
+struct TALER_EXCHANGE_MeltResponse
{
/**
* Full HTTP response details.
@@ -3129,9 +3129,9 @@ struct TALER_EXCHANGE_MeltResponse_v27
* @param mr response details
*/
typedef void
-(*TALER_EXCHANGE_MeltCallback_v27) (
+(*TALER_EXCHANGE_MeltCallback) (
void *cls,
- const struct TALER_EXCHANGE_MeltResponse_v27 *mr);
+ const struct TALER_EXCHANGE_MeltResponse *mr);
/**
* Submit a melt request to the exchange and get the exchange's
@@ -3152,14 +3152,14 @@ typedef void
* @return a handle for this request; NULL if the argument was invalid.
* In this case, neither callback will be called.
*/
-struct TALER_EXCHANGE_MeltHandle_v27 *
-TALER_EXCHANGE_melt_v27 (
+struct TALER_EXCHANGE_MeltHandle *
+TALER_EXCHANGE_melt (
struct GNUNET_CURL_Context *ctx,
const char *url,
struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_PublicRefreshMasterSeedP *rms,
const struct TALER_EXCHANGE_MeltInput *melt_input,
- TALER_EXCHANGE_MeltCallback_v27 melt_cb,
+ TALER_EXCHANGE_MeltCallback melt_cb,
void *melt_cb_cls);
@@ -3170,7 +3170,7 @@ TALER_EXCHANGE_melt_v27 (
* @param mh the melt handle
*/
void
-TALER_EXCHANGE_melt_v27_cancel (struct TALER_EXCHANGE_MeltHandle_v27 *mh);
+TALER_EXCHANGE_melt_cancel (struct TALER_EXCHANGE_MeltHandle *mh);
/* ********************* /transfers/$WTID *********************** */
@@ -3621,7 +3621,7 @@ typedef void
* @param pk kind of coin to pay back
* @param denom_sig signature over the coin by the exchange using @a pk
* @param exchange_vals contribution from the exchange on the withdraw
- * @param rms melt secret of the refreshing operation
+ * @param rms the public seed of the refreshing operation
* @param ps coin-specific secrets derived for this coin during the refreshing operation
* @param idx index of the fresh coin in the refresh operation that is now being recouped
* @param recoup_cb the callback to call when the final result for this request is available
@@ -3638,7 +3638,7 @@ TALER_EXCHANGE_recoup_refresh (
const struct TALER_EXCHANGE_DenomPublicKey *pk,
const struct TALER_DenominationSignature *denom_sig,
const struct TALER_ExchangeBlindingValues *exchange_vals,
- const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_PublicRefreshMasterSeedP *rms,
const struct TALER_PlanchetMasterSecretP *ps,
unsigned int idx,
TALER_EXCHANGE_RecoupRefreshResultCallback recoup_cb,
diff --git a/src/include/taler/taler_exchangedb_plugin.h b/src/include/taler/taler_exchangedb_plugin.h
@@ -2511,10 +2511,10 @@ struct TALER_EXCHANGEDB_Melt
/**
- * Information about a melt operation since v27 of the protocol.
+ * Information about a melt operation since vDOLDPLUS of the protocol.
* This also includes the information for the reveal phase.
*/
-struct TALER_EXCHANGEDB_Refresh_v27
+struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS
{
/**
* Information about the coin that is being melted.
@@ -2532,8 +2532,23 @@ struct TALER_EXCHANGEDB_Refresh_v27
struct TALER_RefreshCommitmentP rc;
/**
- * Public nonce from which the refresh nonces per coin candidate were
- * derived from
+ * True if the client has successfully performed the reveal part
+ * of the refresh protocol, after the melt.
+ */
+ bool is_revealed;
+
+ /**
+ * @since vDOLDPLUS
+ * Mark if we have a v27 Refresh object.
+ * That is, the @a refresh_seed refers to the vDOLDPLUS master_refresh_seed
+ * from the original request, AND the client has provided transfer public keys,
+ * see below, @a transfer_public_keys
+ */
+ bool is_v27_refresh;
+
+ /**
+ * Public seed from which the refresh nonces (v27) or transfer secrets (vDOLDPLUS)
+ * per coin candidate were derived from.
*/
struct TALER_PublicRefreshMasterSeedP refresh_seed;
@@ -2569,6 +2584,13 @@ struct TALER_EXCHANGEDB_Refresh_v27
struct TALER_BlindedDenominationSignature *denom_sigs;
/**
+ * If @a is_v27_refresh is false, the client performed a vDOLDPLUS refresh,
+ * and has provided @a num_coins * kappa transfer public keys.
+ * This is the chosen (at index @a noreveal_index) array of @a num_coins transfer public keys.
+ */
+ struct TALER_TransferPublicKeyP *transfer_pubs;
+
+ /**
* Array of @a num_coins serial id's of the denominations.
* If @e coin.no_age_commitment is false, the denominations
* MUST support age restriction.
@@ -2582,6 +2604,11 @@ struct TALER_EXCHANGEDB_Refresh_v27
uint32_t noreveal_index;
/**
+ * True, if the client has successfully performed the reveal step
+ */
+ bool revealed;
+
+ /**
* If true, no @e blinding_seed is set and @e num_cs_r_values is 0.
*/
bool no_blinding_seed;
@@ -2695,12 +2722,12 @@ enum TALER_EXCHANGEDB_TransactionType
/**
* Recoup-refresh operation (on the old coin, adding to the old coin's value)
*/
- TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP = 3,
+ TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER = 3,
/**
* Recoup operation.
*/
- TALER_EXCHANGEDB_TT_RECOUP = 4,
+ TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW = 4,
/**
* Recoup-refresh operation (on the new coin, eliminating its value)
@@ -2778,13 +2805,13 @@ struct TALER_EXCHANGEDB_TransactionList
/**
* Details if transaction was a recoup-refund operation where
* this coin was the OLD coin.
- * (#TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP).
+ * (#TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER).
*/
struct TALER_EXCHANGEDB_RecoupRefreshListEntry *old_coin_recoup;
/**
* Details if transaction was a recoup operation.
- * (#TALER_EXCHANGEDB_TT_RECOUP)
+ * (#TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW)
*/
struct TALER_EXCHANGEDB_RecoupListEntry *recoup;
@@ -3095,47 +3122,6 @@ typedef enum GNUNET_GenericReturnValue
/**
- * Information about a coin that was revealed to the exchange
- * during reveal.
- */
-struct TALER_EXCHANGEDB_RefreshRevealedCoin
-{
- /**
- * Hash of the public denomination key of the coin.
- */
- struct TALER_DenominationHashP h_denom_pub;
-
- /**
- * Signature of the original coin being refreshed over the
- * link data, of type #TALER_SIGNATURE_WALLET_COIN_LINK
- */
- struct TALER_CoinSpendSignatureP orig_coin_link_sig;
-
- /**
- * Hash of the blinded new coin, that is @e coin_ev.
- */
- struct TALER_BlindedCoinHashP coin_envelope_hash;
-
- /**
- * Signature generated by the exchange over the coin (in blinded format).
- */
- struct TALER_BlindedDenominationSignature coin_sig;
-
- /**
- * Values contributed from the exchange to the
- * coin generation (see /blinding-prepare).
- */
- struct TALER_ExchangeBlindingValues exchange_vals;
-
- /**
- * Blinded message to be signed (in envelope).
- */
- struct TALER_BlindedPlanchet blinded_planchet;
-
-};
-
-
-/**
* Information per Clause-Schnorr (CS) fresh coin to
* be persisted for idempotency during refreshes-reveal.
*/
@@ -3225,20 +3211,6 @@ typedef enum GNUNET_GenericReturnValue
/**
- * Function called with information about a refresh order.
- *
- * @param cls closure
- * @param num_freshcoins size of the @a rrcs array
- * @param rrcs array of @a num_freshcoins information about coins to be created
- */
-typedef void
-(*TALER_EXCHANGEDB_RefreshCallback)(
- void *cls,
- uint32_t num_freshcoins,
- const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs);
-
-
-/**
* Function called with details about coins that were refunding,
* with the goal of auditing the refund's execution.
*
@@ -4524,7 +4496,7 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*do_refresh)(
void *cls,
- struct TALER_EXCHANGEDB_Refresh_v27 *refresh,
+ struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *refresh,
const struct GNUNET_TIME_Timestamp *timestamp,
bool *found,
uint32_t *noreveal_index,
@@ -4547,8 +4519,21 @@ struct TALER_EXCHANGEDB_Plugin
enum GNUNET_DB_QueryStatus
(*get_refresh)(void *cls,
const struct TALER_RefreshCommitmentP *rc,
- struct TALER_EXCHANGEDB_Refresh_v27 *refresh);
+ struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *refresh);
+ /**
+ * @since vDOLDPLUS
+ *
+ * Mark the successful reveal for a refresh in the database.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param rc commitment to use for the lookup
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*mark_refresh_reveal_success)(
+ void *cls,
+ const struct TALER_RefreshCommitmentP *rc);
/**
* Add a proof of fulfillment of an policy
diff --git a/src/include/taler/taler_json_lib.h b/src/include/taler/taler_json_lib.h
@@ -41,9 +41,9 @@
* @deprecated
*/
#define TALER_json_warn(error) \
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \
- "JSON parsing failed at %s:%u: %s (%s)\n", \
- __FILE__, __LINE__, error.text, error.source)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \
+ "JSON parsing failed at %s:%u: %s (%s)\n", \
+ __FILE__, __LINE__, error.text, error.source)
/**
@@ -67,8 +67,8 @@ TALER_JSON_pack_time_abs_human (
* @param ec error code to encode using canonical field names
*/
#define TALER_JSON_pack_ec(ec) \
- GNUNET_JSON_pack_string ("hint", TALER_ErrorCode_get_hint (ec)), \
- GNUNET_JSON_pack_uint64 ("code", ec)
+ GNUNET_JSON_pack_string ("hint", TALER_ErrorCode_get_hint (ec)), \
+ GNUNET_JSON_pack_uint64 ("code", ec)
/**
@@ -341,6 +341,22 @@ TALER_JSON_pack_array_of_data (
const void *data,
size_t size);
+/**
+ * Generate packer for an array of data of the same size,
+ * encoded in Crockford base32-encoding.
+ * The size is taken via sizeof().
+ *
+ * @param name name of the field to add to the object
+ * @param num number of elements in the @a array
+ * @param data pointer to the list of elements
+ * @return json pack specification
+ */
+#define TALER_JSON_pack_array_of_data_auto(name,num,data) \
+ TALER_JSON_pack_array_of_data ((name), \
+ (num), \
+ (data), \
+ sizeof(*(data)))
+
/****************** Specs For Parsing JSON *********************/
@@ -481,10 +497,10 @@ TALER_JSON_spec_kycte (const char *name,
* @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)
+ 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)
/**
@@ -495,10 +511,10 @@ TALER_JSON_spec_kycte (const char *name,
* @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)
+ 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)
/**
@@ -508,9 +524,9 @@ TALER_JSON_spec_kycte (const char *name,
* @param[out] gfs a `struct TALER_GlobalFeeSet` to initialize
*/
#define TALER_JSON_SPEC_GLOBAL_FEES(currency,gfs) \
- TALER_JSON_spec_amount ("history_fee", (currency), &(gfs)->history), \
- TALER_JSON_spec_amount ("account_fee", (currency), &(gfs)->account), \
- TALER_JSON_spec_amount ("purse_fee", (currency), &(gfs)->purse)
+ TALER_JSON_spec_amount ("history_fee", (currency), &(gfs)->history), \
+ TALER_JSON_spec_amount ("account_fee", (currency), &(gfs)->account), \
+ TALER_JSON_spec_amount ("purse_fee", (currency), &(gfs)->purse)
/**
* Macro to pack all of the global fees.
@@ -518,9 +534,9 @@ TALER_JSON_spec_kycte (const char *name,
* @param gfs a `struct TALER_GlobalFeeSet` to pack
*/
#define TALER_JSON_PACK_GLOBAL_FEES(gfs) \
- TALER_JSON_pack_amount ("history_fee", &(gfs)->history), \
- TALER_JSON_pack_amount ("account_fee", &(gfs)->account), \
- TALER_JSON_pack_amount ("purse_fee", &(gfs)->purse)
+ TALER_JSON_pack_amount ("history_fee", &(gfs)->history), \
+ TALER_JSON_pack_amount ("account_fee", &(gfs)->account), \
+ TALER_JSON_pack_amount ("purse_fee", &(gfs)->purse)
/**
diff --git a/src/include/taler/taler_testing_lib.h b/src/include/taler/taler_testing_lib.h
@@ -2803,7 +2803,7 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
op (blinding_seed, const struct TALER_BlindingMasterSeedP) \
op (withdraw_commitment, const struct TALER_HashBlindedPlanchetsP) \
op (kappa_seed, const struct TALER_KappaWithdrawMasterSeedP) \
- op (refresh_secret, const struct TALER_RefreshMasterSecretP) \
+ op (refresh_seed, const struct TALER_PublicRefreshMasterSeedP) \
op (reserve_pub, const struct TALER_ReservePublicKeyP) \
op (merchant_priv, const struct TALER_MerchantPrivateKeyP) \
op (merchant_pub, const struct TALER_MerchantPublicKeyP) \
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
@@ -55,7 +55,7 @@ libtalerexchange_la_SOURCES = \
exchange_api_management_update_aml_officer.c \
exchange_api_management_wire_disable.c \
exchange_api_management_wire_enable.c \
- exchange_api_melt_v27.c \
+ exchange_api_melt.c \
exchange_api_purse_create_with_deposit.c \
exchange_api_purse_create_with_merge.c \
exchange_api_purse_delete.c \
diff --git a/src/lib/exchange_api_melt.c b/src/lib/exchange_api_melt.c
@@ -0,0 +1,645 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_melt.c
+ * @brief Implementation of the /melt request
+ * @author Özgür Kesim
+ */
+#include "taler/platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+#include "exchange_api_refresh_common.h"
+
+
+/**
+ * @brief A /melt Handle
+ */
+struct TALER_EXCHANGE_MeltHandle
+{
+
+ /**
+ * The keys of the this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * The exchange base url.
+ */
+ char *exchange_url;
+
+ /**
+ * Curl context.
+ */
+ struct GNUNET_CURL_Context *cctx;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with refresh melt failure results.
+ */
+ TALER_EXCHANGE_MeltCallback melt_cb;
+
+ /**
+ * Closure for @e result_cb and @e melt_failure_cb.
+ */
+ void *melt_cb_cls;
+
+ /**
+ * Actual information about the melt operation.
+ */
+ struct MeltData md;
+
+ /**
+ * The seed for the melt operation.
+ */
+ struct TALER_PublicRefreshMasterSeedP rms;
+
+ /**
+ * Details about the characteristics of the requested melt operation.
+ */
+ const struct TALER_EXCHANGE_MeltInput *rd;
+
+ /**
+ * True, if no blinding_seed is needed (no CS denominations involved)
+ */
+ bool no_blinding_seed;
+
+ /**
+ * If @e no_blinding_seed is false, the blinding seed for the intermediate
+ * call to /blinding-prepare, in order to retrieve the R-values from the
+ * exchange for the blind Clause-Schnorr signature.
+ */
+ struct TALER_BlindingMasterSeedP blinding_seed;
+
+ /**
+ * Array of `num_fresh_denom_pubs` per-coin values
+ * returned from melt operation.
+ */
+ struct TALER_ExchangeBlindingValues *melt_blinding_values;
+
+ /**
+ * Handle for the preflight request, or NULL.
+ */
+ struct TALER_EXCHANGE_BlindingPrepareHandle *bpr;
+
+ /**
+ * Public key of the coin being melted.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Signature affirming the melt.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
+ * @brief Public information about the coin's denomination key
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey *dki;
+
+ /**
+ * Gamma value chosen by the exchange during melt.
+ */
+ uint32_t noreveal_index;
+
+};
+
+
+/**
+ * Verify that the signature on the "200 OK" response
+ * from the exchange is valid.
+ *
+ * @param[in,out] mh melt handle
+ * @param json json reply with the signature
+ * @param[out] exchange_pub public key of the exchange used for the signature
+ * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
+ */
+static enum GNUNET_GenericReturnValue
+verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh,
+ const json_t *json,
+ struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ exchange_pub),
+ GNUNET_JSON_spec_uint32 ("noreveal_index",
+ &mh->noreveal_index),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* check that exchange signing key is permitted */
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (mh->keys,
+ exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* check that noreveal index is in permitted range */
+ if (TALER_CNC_KAPPA <= mh->noreveal_index)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ TALER_exchange_online_melt_confirmation_verify (
+ &mh->md.rc,
+ mh->noreveal_index,
+ exchange_pub,
+ &exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /melt request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_MeltHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_melt_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_MeltHandle *mh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_MeltResponse mr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ mh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ mr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ verify_melt_signature_ok (mh,
+ j,
+ &mr.details.ok.sign_key))
+ {
+ GNUNET_break_op (0);
+ mr.hr.http_status = 0;
+ mr.hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
+ break;
+ }
+ mr.details.ok.noreveal_index = mh->noreveal_index;
+ mr.details.ok.num_melt_blinding_values = mh->rd->num_fresh_denom_pubs;
+ mr.details.ok.melt_blinding_values = mh->melt_blinding_values;
+ mr.details.ok.blinding_seed = mh->no_blinding_seed
+ ? NULL
+ : &mh->blinding_seed;
+ mh->melt_cb (mh->melt_cb_cls,
+ &mr);
+ mh->melt_cb = NULL;
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; assuming we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange melt\n",
+ (unsigned int) response_code,
+ mr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ if (NULL != mh->melt_cb)
+ mh->melt_cb (mh->melt_cb_cls,
+ &mr);
+ TALER_EXCHANGE_melt_cancel (mh);
+}
+
+
+/**
+ * Start the actual melt operation, now that we have
+ * the exchange's input values.
+ *
+ * @param[in,out] mh melt operation to run
+ * @return #GNUNET_OK if we could start the operation
+ */
+static enum GNUNET_GenericReturnValue
+start_melt (struct TALER_EXCHANGE_MeltHandle *mh)
+{
+ json_t *j_request_body;
+ json_t *j_transfer_pubs;
+ json_t *j_coin_evs;
+ CURL *eh;
+ struct TALER_DenominationHashP h_denom_pub;
+
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_get_melt_data (&mh->rms,
+ mh->rd,
+ mh->no_blinding_seed
+ ? NULL
+ : &mh->blinding_seed,
+ mh->melt_blinding_values,
+ &mh->md))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ TALER_denom_pub_hash (
+ &mh->md.melted_coin.pub_key,
+ &h_denom_pub);
+ TALER_wallet_melt_sign (
+ &mh->md.melted_coin.melt_amount_with_fee,
+ &mh->md.melted_coin.fee_melt,
+ &mh->md.rc,
+ &h_denom_pub,
+ mh->md.melted_coin.h_age_commitment,
+ &mh->md.melted_coin.coin_priv,
+ &mh->coin_sig);
+ GNUNET_CRYPTO_eddsa_key_get_public (
+ &mh->md.melted_coin.coin_priv.eddsa_priv,
+ &mh->coin_pub.eddsa_pub);
+ mh->dki = TALER_EXCHANGE_get_denomination_key (
+ mh->keys,
+ &mh->md.melted_coin.pub_key);
+ j_request_body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("old_coin_pub",
+ &mh->coin_pub),
+ GNUNET_JSON_pack_data_auto ("old_denom_pub_h",
+ &h_denom_pub),
+ TALER_JSON_pack_denom_sig ("old_denom_sig",
+ &mh->md.melted_coin.sig),
+ GNUNET_JSON_pack_data_auto ("confirm_sig",
+ &mh->coin_sig),
+ TALER_JSON_pack_amount ("value_with_fee",
+ &mh->md.melted_coin.melt_amount_with_fee),
+ GNUNET_JSON_pack_allow_null (
+ (NULL != mh->md.melted_coin.h_age_commitment)
+ ? GNUNET_JSON_pack_data_auto ("old_age_commitment_h",
+ mh->md.melted_coin.h_age_commitment)
+ : GNUNET_JSON_pack_string ("old_age_commitment_h",
+ NULL)),
+ GNUNET_JSON_pack_data_auto ("refresh_seed",
+ &mh->md.refresh_seed),
+ GNUNET_JSON_pack_allow_null (
+ (mh->md.no_blinding_seed)
+ ? GNUNET_JSON_pack_string ("blinding_seed",
+ NULL)
+ : GNUNET_JSON_pack_data_auto ("blinding_seed",
+ &mh->md.blinding_seed)),
+ TALER_JSON_pack_array_of_data_auto ("denoms_h",
+ mh->md.num_fresh_coins,
+ mh->md.denoms_h)
+ );
+ GNUNET_assert (NULL != j_request_body);
+ GNUNET_assert (NULL !=
+ (j_transfer_pubs = json_array ()));
+ GNUNET_assert (NULL !=
+ (j_coin_evs = json_array ()));
+ /**
+ * Fill the kappa array of coin envelopes and
+ * the array of transfer pubs.
+ */
+ for (uint8_t k=0; k<TALER_CNC_KAPPA; k++)
+ {
+ json_t *j_envs;
+ json_t *j_tbs = GNUNET_JSON_PACK (
+ TALER_JSON_pack_array_of_data_auto (NULL,
+ mh->md.num_fresh_coins,
+ mh->md.kappa_transfer_pubs[k])
+ );
+
+ GNUNET_assert (NULL != (j_envs = json_array ()));
+ GNUNET_assert (NULL !=j_tbs);
+
+ for (size_t i = 0; i < mh->md.num_fresh_coins; i++)
+ {
+ json_t *j_coin = GNUNET_JSON_PACK (
+ TALER_JSON_pack_blinded_planchet (NULL,
+ &mh->md.kappa_blinded_planchets[k][i]));
+ GNUNET_assert (NULL != j_coin);
+ GNUNET_assert (0 ==
+ json_array_append_new (j_envs, j_coin));
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (j_coin_evs, j_envs));
+ GNUNET_assert (0 ==
+ json_array_append_new (j_transfer_pubs, j_tbs));
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (j_request_body,
+ "coin_evs",
+ j_coin_evs));
+ GNUNET_assert (0 ==
+ json_object_set_new (j_request_body,
+ "transfer_pubs",
+ j_transfer_pubs));
+ /* and now we can at last begin the actual request handling */
+ mh->url = TALER_url_join (mh->exchange_url,
+ "melt",
+ NULL);
+ if (NULL == mh->url)
+ {
+ json_decref (j_request_body);
+ return GNUNET_SYSERR;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (mh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&mh->ctx,
+ eh,
+ j_request_body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (j_request_body);
+ return GNUNET_SYSERR;
+ }
+ json_decref (j_request_body);
+ mh->job = GNUNET_CURL_job_add2 (mh->cctx,
+ eh,
+ mh->ctx.headers,
+ &handle_melt_finished,
+ mh);
+ return GNUNET_OK;
+}
+
+
+/**
+ * The melt request @a mh failed, return an error to
+ * the application and cancel the operation.
+ *
+ * @param[in] mh melt request that failed
+ * @param ec error code to fail with
+ */
+static void
+fail_mh (struct TALER_EXCHANGE_MeltHandle *mh,
+ enum TALER_ErrorCode ec)
+{
+ struct TALER_EXCHANGE_MeltResponse mr = {
+ .hr.ec = ec
+ };
+
+ mh->melt_cb (mh->melt_cb_cls,
+ &mr);
+ TALER_EXCHANGE_melt_cancel (mh);
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * /blinding-prepare request to a exchange.
+ *
+ * @param cls closure with our `struct TALER_EXCHANGE_MeltHandle *`
+ * @param bpr response details
+ */
+static void
+blinding_prepare_cb (void *cls,
+ const struct TALER_EXCHANGE_BlindingPrepareResponse *bpr)
+{
+ struct TALER_EXCHANGE_MeltHandle *mh = cls;
+ unsigned int nks_off = 0;
+
+ mh->bpr = NULL;
+ if (MHD_HTTP_OK != bpr->hr.http_status)
+ {
+ struct TALER_EXCHANGE_MeltResponse mr = {
+ .hr = bpr->hr
+ };
+
+ mr.hr.hint = "/blinding-prepare failed";
+ mh->melt_cb (mh->melt_cb_cls,
+ &mr);
+ TALER_EXCHANGE_melt_cancel (mh);
+ return;
+ }
+ for (unsigned int i = 0; i<mh->rd->num_fresh_denom_pubs; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk =
+ &mh->rd->fresh_denom_pubs[i];
+ struct TALER_ExchangeBlindingValues *wv = &mh->melt_blinding_values[i];
+
+ switch (fresh_pk->key.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ GNUNET_break (0);
+ fail_mh (mh,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR);
+ return;
+ case GNUNET_CRYPTO_BSA_RSA:
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ TALER_denom_ewv_copy (wv,
+ &bpr->details.ok.blinding_values[nks_off]);
+ nks_off++;
+ break;
+ }
+ }
+ if (GNUNET_OK !=
+ start_melt (mh))
+ {
+ GNUNET_break (0);
+ fail_mh (mh,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR);
+ return;
+ }
+}
+
+
+struct TALER_EXCHANGE_MeltHandle *
+TALER_EXCHANGE_melt (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_PublicRefreshMasterSeedP *rms,
+ const struct TALER_EXCHANGE_MeltInput *rd,
+ TALER_EXCHANGE_MeltCallback melt_cb,
+ void *melt_cb_cls)
+{
+ struct TALER_EXCHANGE_NonceKey nks[GNUNET_NZL (rd->num_fresh_denom_pubs)];
+ unsigned int nks_off = 0;
+ struct TALER_EXCHANGE_MeltHandle *mh;
+
+ if (0 == rd->num_fresh_denom_pubs)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ mh = GNUNET_new (struct TALER_EXCHANGE_MeltHandle);
+ mh->noreveal_index = TALER_CNC_KAPPA; /* invalid value */
+ mh->cctx = ctx;
+ mh->exchange_url = GNUNET_strdup (url);
+ mh->rd = rd;
+ mh->rms = *rms;
+ mh->melt_cb = melt_cb;
+ mh->melt_cb_cls = melt_cb_cls;
+ mh->no_blinding_seed = true;
+ mh->melt_blinding_values =
+ GNUNET_new_array (rd->num_fresh_denom_pubs,
+ struct TALER_ExchangeBlindingValues);
+ for (unsigned int i = 0; i<rd->num_fresh_denom_pubs; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk =
+ &rd->fresh_denom_pubs[i];
+
+ switch (fresh_pk->key.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ GNUNET_break (0);
+ GNUNET_free (mh->melt_blinding_values);
+ GNUNET_free (mh);
+ return NULL;
+ case GNUNET_CRYPTO_BSA_RSA:
+ TALER_denom_ewv_copy (&mh->melt_blinding_values[i],
+ TALER_denom_ewv_rsa_singleton ());
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ nks[nks_off].pk = fresh_pk;
+ nks[nks_off].cnc_num = i;
+ nks_off++;
+ break;
+ }
+ }
+ mh->keys = TALER_EXCHANGE_keys_incref (keys);
+ if (0 != nks_off)
+ {
+ mh->no_blinding_seed = false;
+ TALER_cs_refresh_seed_to_blinding_seed (
+ rms,
+ &mh->md.melted_coin.coin_priv,
+ &mh->blinding_seed);
+ mh->bpr = TALER_EXCHANGE_blinding_prepare_for_melt (ctx,
+ url,
+ &mh->blinding_seed,
+ nks_off,
+ nks,
+ &blinding_prepare_cb,
+ mh);
+ if (NULL == mh->bpr)
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_melt_cancel (mh);
+ return NULL;
+ }
+ return mh;
+ }
+ if (GNUNET_OK !=
+ start_melt (mh))
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_melt_cancel (mh);
+ return NULL;
+ }
+ return mh;
+}
+
+
+void
+TALER_EXCHANGE_melt_cancel (struct TALER_EXCHANGE_MeltHandle *mh)
+{
+ for (unsigned int i = 0; i<mh->rd->num_fresh_denom_pubs; i++)
+ TALER_denom_ewv_free (&mh->melt_blinding_values[i]);
+ if (NULL != mh->job)
+ {
+ GNUNET_CURL_job_cancel (mh->job);
+ mh->job = NULL;
+ }
+ if (NULL != mh->bpr)
+ {
+ TALER_EXCHANGE_blinding_prepare_cancel (mh->bpr);
+ mh->bpr = NULL;
+ }
+ TALER_EXCHANGE_free_melt_data (&mh->md); /* does not free 'md' itself */
+ GNUNET_free (mh->melt_blinding_values);
+ GNUNET_free (mh->url);
+ GNUNET_free (mh->exchange_url);
+ TALER_curl_easy_post_finished (&mh->ctx);
+ TALER_EXCHANGE_keys_decref (mh->keys);
+ GNUNET_free (mh);
+}
+
+
+/* end of exchange_api_melt.c */
diff --git a/src/lib/exchange_api_melt_v27.c b/src/lib/exchange_api_melt_v27.c
@@ -1,626 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2025 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file lib/exchange_api_melt_v27.c
- * @brief Implementation of the /melt request
- * @author Özgür Kesim
- */
-#include "taler/platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_common.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-#include "exchange_api_refresh_common.h"
-
-
-/**
- * @brief A /melt Handle
- */
-struct TALER_EXCHANGE_MeltHandle_v27
-{
-
- /**
- * The keys of the this request handle will use
- */
- struct TALER_EXCHANGE_Keys *keys;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * The exchange base url.
- */
- char *exchange_url;
-
- /**
- * Curl context.
- */
- struct GNUNET_CURL_Context *cctx;
-
- /**
- * Context for #TEH_curl_easy_post(). Keeps the data that must
- * persist for Curl to make the upload.
- */
- struct TALER_CURL_PostContext ctx;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with refresh melt failure results.
- */
- TALER_EXCHANGE_MeltCallback_v27 melt_cb;
-
- /**
- * Closure for @e result_cb and @e melt_failure_cb.
- */
- void *melt_cb_cls;
-
- /**
- * Actual information about the melt operation.
- */
- struct MeltData_v27 md;
-
- /**
- * The secret the entire melt operation is seeded from.
- */
- struct TALER_RefreshMasterSecretP rms;
-
- /**
- * Details about the characteristics of the requested melt operation.
- */
- const struct TALER_EXCHANGE_MeltInput *rd;
-
- /**
- * True, if no blinding_seed is needed (no CS denominations involved)
- */
- bool no_blinding_seed;
-
- /**
- * If @e no_blinding_seed is false, the blinding seed for the intermediate
- * call to /blinding-prepare, in order to retrieve the R-values from the
- * exchange for the blind Clause-Schnorr signature.
- */
- struct TALER_BlindingMasterSeedP blinding_seed;
-
- /**
- * Array of `num_fresh_denom_pubs` per-coin values
- * returned from melt operation.
- */
- struct TALER_ExchangeBlindingValues *melt_blinding_values;
-
- /**
- * Handle for the preflight request, or NULL.
- */
- struct TALER_EXCHANGE_BlindingPrepareHandle *bpr;
-
- /**
- * Public key of the coin being melted.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * Signature affirming the melt.
- */
- struct TALER_CoinSpendSignatureP coin_sig;
-
- /**
- * @brief Public information about the coin's denomination key
- */
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
-
- /**
- * Gamma value chosen by the exchange during melt.
- */
- uint32_t noreveal_index;
-
-};
-
-
-/**
- * Verify that the signature on the "200 OK" response
- * from the exchange is valid.
- *
- * @param[in,out] mh melt handle
- * @param json json reply with the signature
- * @param[out] exchange_pub public key of the exchange used for the signature
- * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
- */
-static enum GNUNET_GenericReturnValue
-verify_melt_v27_signature_ok (struct TALER_EXCHANGE_MeltHandle_v27 *mh,
- const json_t *json,
- struct TALER_ExchangePublicKeyP *exchange_pub)
-{
- struct TALER_ExchangeSignatureP exchange_sig;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- exchange_pub),
- GNUNET_JSON_spec_uint32 ("noreveal_index",
- &mh->noreveal_index),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- /* check that exchange signing key is permitted */
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (mh->keys,
- exchange_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- /* check that noreveal index is in permitted range */
- if (TALER_CNC_KAPPA <= mh->noreveal_index)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- if (GNUNET_OK !=
- TALER_exchange_online_melt_confirmation_verify (
- &mh->md.rc,
- mh->noreveal_index,
- exchange_pub,
- &exchange_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /coins/$COIN_PUB/melt request.
- *
- * @param cls the `struct TALER_EXCHANGE_MeltHandle_v27`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_melt_v27_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_MeltHandle_v27 *mh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_MeltResponse_v27 mr = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
-
- mh->job = NULL;
- switch (response_code)
- {
- case 0:
- mr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- if (GNUNET_OK !=
- verify_melt_v27_signature_ok (mh,
- j,
- &mr.details.ok.sign_key))
- {
- GNUNET_break_op (0);
- mr.hr.http_status = 0;
- mr.hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
- break;
- }
- mr.details.ok.noreveal_index = mh->noreveal_index;
- mr.details.ok.num_melt_blinding_values = mh->rd->num_fresh_denom_pubs;
- mr.details.ok.melt_blinding_values = mh->melt_blinding_values;
- mr.details.ok.blinding_seed = mh->no_blinding_seed
- ? NULL
- : &mh->blinding_seed;
- mh->melt_cb (mh->melt_cb_cls,
- &mr);
- mh->melt_cb = NULL;
- break;
- case MHD_HTTP_BAD_REQUEST:
- /* This should never happen, either us or the exchange is buggy
- (or API version conflict); just pass JSON reply to the application */
- mr.hr.ec = TALER_JSON_get_error_code (j);
- mr.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_CONFLICT:
- mr.hr.ec = TALER_JSON_get_error_code (j);
- mr.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_FORBIDDEN:
- /* Nothing really to verify, exchange says one of the signatures is
- invalid; assuming we checked them, this should never happen, we
- should pass the JSON reply to the application */
- mr.hr.ec = TALER_JSON_get_error_code (j);
- mr.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_NOT_FOUND:
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- mr.hr.ec = TALER_JSON_get_error_code (j);
- mr.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- mr.hr.ec = TALER_JSON_get_error_code (j);
- mr.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- default:
- /* unexpected response code */
- mr.hr.ec = TALER_JSON_get_error_code (j);
- mr.hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange melt\n",
- (unsigned int) response_code,
- mr.hr.ec);
- GNUNET_break_op (0);
- break;
- }
- if (NULL != mh->melt_cb)
- mh->melt_cb (mh->melt_cb_cls,
- &mr);
- TALER_EXCHANGE_melt_v27_cancel (mh);
-}
-
-
-/**
- * Start the actual melt operation, now that we have
- * the exchange's input values.
- *
- * @param[in,out] mh melt operation to run
- * @return #GNUNET_OK if we could start the operation
- */
-static enum GNUNET_GenericReturnValue
-start_melt (struct TALER_EXCHANGE_MeltHandle_v27 *mh)
-{
- json_t *j_request_body;
- json_t *j_coin_evs;
- CURL *eh;
- struct TALER_DenominationHashP h_denom_pub;
-
- if (GNUNET_OK !=
- TALER_EXCHANGE_get_melt_data_v27 (&mh->rms,
- mh->rd,
- mh->no_blinding_seed
- ? NULL
- : &mh->blinding_seed,
- mh->melt_blinding_values,
- &mh->md))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- TALER_denom_pub_hash (&mh->md.melted_coin.pub_key,
- &h_denom_pub);
- TALER_wallet_melt_sign (
- &mh->md.melted_coin.melt_amount_with_fee,
- &mh->md.melted_coin.fee_melt,
- &mh->md.rc,
- &h_denom_pub,
- mh->md.melted_coin.h_age_commitment,
- &mh->md.melted_coin.coin_priv,
- &mh->coin_sig);
- GNUNET_CRYPTO_eddsa_key_get_public (
- &mh->md.melted_coin.coin_priv.eddsa_priv,
- &mh->coin_pub.eddsa_pub);
- mh->dki = TALER_EXCHANGE_get_denomination_key (mh->keys,
- &mh->md.melted_coin.pub_key);
- j_request_body = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("old_coin_pub",
- &mh->coin_pub),
- GNUNET_JSON_pack_data_auto ("old_denom_pub_h",
- &h_denom_pub),
- TALER_JSON_pack_denom_sig ("old_denom_sig",
- &mh->md.melted_coin.sig),
- GNUNET_JSON_pack_data_auto ("confirm_sig",
- &mh->coin_sig),
- TALER_JSON_pack_amount ("value_with_fee",
- &mh->md.melted_coin.melt_amount_with_fee),
- GNUNET_JSON_pack_allow_null (
- (NULL != mh->md.melted_coin.h_age_commitment)
- ? GNUNET_JSON_pack_data_auto ("old_age_commitment_h",
- mh->md.melted_coin.h_age_commitment)
- : GNUNET_JSON_pack_string ("old_age_commitment_h",
- NULL)),
- GNUNET_JSON_pack_data_auto ("refresh_seed",
- &mh->md.refresh_seed),
- GNUNET_JSON_pack_allow_null (
- (mh->md.no_blinding_seed)
- ? GNUNET_JSON_pack_string ("blinding_seed",
- NULL)
- : GNUNET_JSON_pack_data_auto ("blinding_seed",
- &mh->md.blinding_seed)),
- TALER_JSON_pack_array_of_data ("denoms_h",
- mh->md.num_fresh_coins,
- mh->md.denoms_h,
- sizeof(*mh->md.denoms_h))
- );
- GNUNET_assert (NULL != j_request_body);
- j_coin_evs = json_array ();
- GNUNET_assert (NULL != j_coin_evs);
- /**
- * Fill the kappa array of coin envelopes
- */
- for (uint8_t k=0; k<TALER_CNC_KAPPA; k++)
- {
- json_t *j_envs = json_array ();
- GNUNET_assert (NULL != j_envs);
- for (size_t i = 0; i < mh->md.num_fresh_coins; i++)
- {
- json_t *j_coin = GNUNET_JSON_PACK (
- TALER_JSON_pack_blinded_planchet (NULL,
- &mh->md.kappa_blinded_planchets[k][i])
- );
- GNUNET_assert (NULL != j_coin);
- GNUNET_assert (0 ==
- json_array_append_new (j_envs, j_coin));
- }
- GNUNET_assert (0 ==
- json_array_append_new (j_coin_evs, j_envs));
- }
- GNUNET_assert (0 ==
- json_object_set_new (j_request_body,
- "coin_evs",
- j_coin_evs));
- /* and now we can at last begin the actual request handling */
- mh->url = TALER_url_join (mh->exchange_url,
- "melt",
- NULL);
- if (NULL == mh->url)
- {
- json_decref (j_request_body);
- return GNUNET_SYSERR;
- }
- eh = TALER_EXCHANGE_curl_easy_get_ (mh->url);
- if ( (NULL == eh) ||
- (GNUNET_OK !=
- TALER_curl_easy_post (&mh->ctx,
- eh,
- j_request_body)) )
- {
- GNUNET_break (0);
- if (NULL != eh)
- curl_easy_cleanup (eh);
- json_decref (j_request_body);
- return GNUNET_SYSERR;
- }
- json_decref (j_request_body);
- mh->job = GNUNET_CURL_job_add2 (mh->cctx,
- eh,
- mh->ctx.headers,
- &handle_melt_v27_finished,
- mh);
- return GNUNET_OK;
-}
-
-
-/**
- * The melt request @a mh failed, return an error to
- * the application and cancel the operation.
- *
- * @param[in] mh melt request that failed
- * @param ec error code to fail with
- */
-static void
-fail_mh (struct TALER_EXCHANGE_MeltHandle_v27 *mh,
- enum TALER_ErrorCode ec)
-{
- struct TALER_EXCHANGE_MeltResponse_v27 mr = {
- .hr.ec = ec
- };
-
- mh->melt_cb (mh->melt_cb_cls,
- &mr);
- TALER_EXCHANGE_melt_v27_cancel (mh);
-}
-
-
-/**
- * Callbacks of this type are used to serve the result of submitting a
- * /blinding-prepare request to a exchange.
- *
- * @param cls closure with our `struct TALER_EXCHANGE_MeltHandle_v27 *`
- * @param bpr response details
- */
-static void
-blinding_prepare_cb (void *cls,
- const struct TALER_EXCHANGE_BlindingPrepareResponse *bpr)
-{
- struct TALER_EXCHANGE_MeltHandle_v27 *mh = cls;
- unsigned int nks_off = 0;
-
- mh->bpr = NULL;
- if (MHD_HTTP_OK != bpr->hr.http_status)
- {
- struct TALER_EXCHANGE_MeltResponse_v27 mr = {
- .hr = bpr->hr
- };
-
- mr.hr.hint = "/blinding-prepare failed";
- mh->melt_cb (mh->melt_cb_cls,
- &mr);
- TALER_EXCHANGE_melt_v27_cancel (mh);
- return;
- }
- for (unsigned int i = 0; i<mh->rd->num_fresh_denom_pubs; i++)
- {
- const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk =
- &mh->rd->fresh_denom_pubs[i];
- struct TALER_ExchangeBlindingValues *wv = &mh->melt_blinding_values[i];
-
- switch (fresh_pk->key.bsign_pub_key->cipher)
- {
- case GNUNET_CRYPTO_BSA_INVALID:
- GNUNET_break (0);
- fail_mh (mh,
- TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR);
- return;
- case GNUNET_CRYPTO_BSA_RSA:
- break;
- case GNUNET_CRYPTO_BSA_CS:
- TALER_denom_ewv_copy (wv,
- &bpr->details.ok.blinding_values[nks_off]);
- nks_off++;
- break;
- }
- }
- if (GNUNET_OK !=
- start_melt (mh))
- {
- GNUNET_break (0);
- fail_mh (mh,
- TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR);
- return;
- }
-}
-
-
-struct TALER_EXCHANGE_MeltHandle_v27 *
-TALER_EXCHANGE_melt_v27 (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_RefreshMasterSecretP *rms,
- const struct TALER_EXCHANGE_MeltInput *rd,
- TALER_EXCHANGE_MeltCallback_v27 melt_cb,
- void *melt_cb_cls)
-{
- struct TALER_EXCHANGE_NonceKey nks[GNUNET_NZL (rd->num_fresh_denom_pubs)];
- unsigned int nks_off = 0;
- struct TALER_EXCHANGE_MeltHandle_v27 *mh;
-
- if (0 == rd->num_fresh_denom_pubs)
- {
- GNUNET_break (0);
- return NULL;
- }
- mh = GNUNET_new (struct TALER_EXCHANGE_MeltHandle_v27);
- mh->noreveal_index = TALER_CNC_KAPPA; /* invalid value */
- mh->cctx = ctx;
- mh->exchange_url = GNUNET_strdup (url);
- mh->rd = rd;
- mh->rms = *rms;
- mh->melt_cb = melt_cb;
- mh->melt_cb_cls = melt_cb_cls;
- mh->no_blinding_seed = true;
- mh->melt_blinding_values =
- GNUNET_new_array (rd->num_fresh_denom_pubs,
- struct TALER_ExchangeBlindingValues);
- for (unsigned int i = 0; i<rd->num_fresh_denom_pubs; i++)
- {
- const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk =
- &rd->fresh_denom_pubs[i];
-
- switch (fresh_pk->key.bsign_pub_key->cipher)
- {
- case GNUNET_CRYPTO_BSA_INVALID:
- GNUNET_break (0);
- GNUNET_free (mh->melt_blinding_values);
- GNUNET_free (mh);
- return NULL;
- case GNUNET_CRYPTO_BSA_RSA:
- TALER_denom_ewv_copy (&mh->melt_blinding_values[i],
- TALER_denom_ewv_rsa_singleton ());
- break;
- case GNUNET_CRYPTO_BSA_CS:
- nks[nks_off].pk = fresh_pk;
- nks[nks_off].cnc_num = i;
- nks_off++;
- break;
- }
- }
- mh->keys = TALER_EXCHANGE_keys_incref (keys);
- if (0 != nks_off)
- {
- mh->no_blinding_seed = false;
- TALER_cs_refresh_secret_to_blinding_seed (
- rms,
- &mh->blinding_seed);
- mh->bpr = TALER_EXCHANGE_blinding_prepare_for_melt (ctx,
- url,
- &mh->blinding_seed,
- nks_off,
- nks,
- &blinding_prepare_cb,
- mh);
- if (NULL == mh->bpr)
- {
- GNUNET_break (0);
- TALER_EXCHANGE_melt_v27_cancel (mh);
- return NULL;
- }
- return mh;
- }
- if (GNUNET_OK !=
- start_melt (mh))
- {
- GNUNET_break (0);
- TALER_EXCHANGE_melt_v27_cancel (mh);
- return NULL;
- }
- return mh;
-}
-
-
-void
-TALER_EXCHANGE_melt_v27_cancel (struct TALER_EXCHANGE_MeltHandle_v27 *mh)
-{
- for (unsigned int i = 0; i<mh->rd->num_fresh_denom_pubs; i++)
- TALER_denom_ewv_free (&mh->melt_blinding_values[i]);
- if (NULL != mh->job)
- {
- GNUNET_CURL_job_cancel (mh->job);
- mh->job = NULL;
- }
- if (NULL != mh->bpr)
- {
- TALER_EXCHANGE_blinding_prepare_cancel (mh->bpr);
- mh->bpr = NULL;
- }
- TALER_EXCHANGE_free_melt_data_v27 (&mh->md); /* does not free 'md' itself */
- GNUNET_free (mh->melt_blinding_values);
- GNUNET_free (mh->url);
- GNUNET_free (mh->exchange_url);
- TALER_curl_easy_post_finished (&mh->ctx);
- TALER_EXCHANGE_keys_decref (mh->keys);
- GNUNET_free (mh);
-}
-
-
-/* end of exchange_api_melt.c */
diff --git a/src/lib/exchange_api_recoup_refresh.c b/src/lib/exchange_api_recoup_refresh.c
@@ -215,6 +215,7 @@ handle_recoup_refresh_finished (void *cls,
}
+#pragma message "TODO[oec]: rewrite TALER_EXCHANGE_RecoupRefreshHandle"
struct TALER_EXCHANGE_RecoupRefreshHandle *
TALER_EXCHANGE_recoup_refresh (
struct GNUNET_CURL_Context *ctx,
@@ -223,7 +224,7 @@ TALER_EXCHANGE_recoup_refresh (
const struct TALER_EXCHANGE_DenomPublicKey *pk,
const struct TALER_DenominationSignature *denom_sig,
const struct TALER_ExchangeBlindingValues *blinding_values,
- const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_PublicRefreshMasterSeedP *rms,
const struct TALER_PlanchetMasterSecretP *ps,
unsigned int idx,
TALER_EXCHANGE_RecoupRefreshResultCallback recoup_cb,
@@ -282,21 +283,7 @@ TALER_EXCHANGE_recoup_refresh (
break;
case GNUNET_CRYPTO_BSA_CS:
{
- union GNUNET_CRYPTO_BlindSessionNonce nonce;
-
- /* NOTE: this is not elegant, and as per the note in TALER_coin_ev_hash()
- it is not strictly clear that the nonce is needed. Best case would be
- to find a way to include it more 'naturally' somehow, for example with
- the variant union version of bks! */
- TALER_cs_refresh_nonce_derive (rms,
- idx,
- &nonce.cs_nonce);
- GNUNET_assert (
- 0 ==
- json_object_set_new (recoup_obj,
- "nonce",
- GNUNET_JSON_from_data_auto (
- &nonce)));
+
}
break;
}
diff --git a/src/lib/exchange_api_refresh_common.c b/src/lib/exchange_api_refresh_common.c
@@ -25,13 +25,14 @@
void
-TALER_EXCHANGE_free_melt_data_v27 (struct MeltData_v27 *md)
+TALER_EXCHANGE_free_melt_data (struct MeltData *md)
{
for (unsigned int k = 0; k < TALER_CNC_KAPPA; k++)
{
for (unsigned int i = 0; i < md->num_fresh_coins; i++)
TALER_blinded_planchet_free (&md->kappa_blinded_planchets[k][i]);
GNUNET_free (md->kappa_blinded_planchets[k]);
+ GNUNET_free (md->kappa_transfer_pubs[k]);
}
GNUNET_free (md->denoms_h);
GNUNET_free (md->denom_pubs);
@@ -54,23 +55,22 @@ TALER_EXCHANGE_free_melt_data_v27 (struct MeltData_v27 *md)
}
/* Finally, clean up a bit... */
GNUNET_CRYPTO_zero_keys (md,
- sizeof (struct MeltData_v27));
+ sizeof (struct MeltData));
}
enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_get_melt_data_v27 (
- const struct TALER_RefreshMasterSecretP *rms,
+TALER_EXCHANGE_get_melt_data (
+ const struct TALER_PublicRefreshMasterSeedP *rms,
const struct TALER_EXCHANGE_MeltInput *rd,
const struct TALER_BlindingMasterSeedP *blinding_seed,
const struct TALER_ExchangeBlindingValues *blinding_values,
- struct MeltData_v27 *md)
+ struct MeltData *md)
{
struct TALER_Amount total;
struct TALER_CoinSpendPublicKeyP coin_pub;
struct TALER_KappaHashBlindedPlanchetsP k_h_bps;
union GNUNET_CRYPTO_BlindSessionNonce nonces[rd->num_fresh_denom_pubs];
- const struct TALER_DenominationHashP *denoms_h[rd->num_fresh_denom_pubs];
bool is_cs[rd->num_fresh_denom_pubs];
bool uses_cs = false;
@@ -80,6 +80,7 @@ TALER_EXCHANGE_get_melt_data_v27 (
0,
sizeof (*md));
/* build up melt data structure */
+ md->refresh_seed = *rms;
md->num_fresh_coins = rd->num_fresh_denom_pubs;
md->melted_coin.coin_priv = rd->melt_priv;
md->melted_coin.melt_amount_with_fee = rd->melt_amount;
@@ -108,11 +109,10 @@ TALER_EXCHANGE_get_melt_data_v27 (
GNUNET_new_array (md->num_fresh_coins,
const struct TALER_DenominationPublicKey*);
- for (unsigned int j = 0; j<rd->num_fresh_denom_pubs; j++)
+ for (unsigned int j = 0; j<md->num_fresh_coins; j++)
{
struct FreshCoinData *fcd = &md->fcds[j];
- denoms_h[j] = &rd->fresh_denom_pubs[j].h_key;
md->denoms_h[j] = rd->fresh_denom_pubs[j].h_key;
md->denom_pubs[j] = &rd->fresh_denom_pubs[j].key;
@@ -123,14 +123,14 @@ TALER_EXCHANGE_get_melt_data_v27 (
fcd->fresh_pk.bsign_pub_key->cipher)
{
GNUNET_break (0);
- TALER_EXCHANGE_free_melt_data_v27 (md);
+ TALER_EXCHANGE_free_melt_data (md);
return GNUNET_SYSERR;
}
switch (fcd->fresh_pk.bsign_pub_key->cipher)
{
case GNUNET_CRYPTO_BSA_INVALID:
GNUNET_break (0);
- TALER_EXCHANGE_free_melt_data_v27 (md);
+ TALER_EXCHANGE_free_melt_data (md);
return GNUNET_SYSERR;
case GNUNET_CRYPTO_BSA_RSA:
is_cs[j] = false;
@@ -150,7 +150,7 @@ TALER_EXCHANGE_get_melt_data_v27 (
&rd->fresh_denom_pubs[j].fees.withdraw)) )
{
GNUNET_break (0);
- TALER_EXCHANGE_free_melt_data_v27 (md);
+ TALER_EXCHANGE_free_melt_data (md);
return GNUNET_SYSERR;
}
}
@@ -163,13 +163,13 @@ TALER_EXCHANGE_get_melt_data_v27 (
/* Eh, this operation is more expensive than the
@a melt_amount. This is not OK. */
GNUNET_break (0);
- TALER_EXCHANGE_free_melt_data_v27 (md);
+ TALER_EXCHANGE_free_melt_data (md);
return GNUNET_SYSERR;
}
/**
* Generate the blinding seeds and nonces for CS.
* Note that blinding_seed was prepared upstream,
- * in TALER_EXCHANGE_melt_v27(), as preparation for
+ * in TALER_EXCHANGE_melt(), as preparation for
* the request to `/blinding-prepare`.
*/
memset (nonces,
@@ -181,46 +181,41 @@ TALER_EXCHANGE_get_melt_data_v27 (
TALER_cs_derive_blind_nonces_from_seed (
blinding_seed,
true, /* for melt */
- rd->num_fresh_denom_pubs,
+ md->num_fresh_coins,
is_cs,
nonces);
}
/**
- * Generate from the master secret the refresh seed
- * and kappa nonces
+ * Generate the kappa private seeds for the batches.
*/
- TALER_refresh_master_secret_to_refresh_seed (
- rms,
- &md->refresh_seed);
- TALER_refresh_expand_kappa_nonces (
+ TALER_refresh_expand_seed_to_kappa_batch_seeds (
&md->refresh_seed,
- &md->kappa_nonces);
+ &rd->melt_priv,
+ &md->kappa_batch_seeds);
/**
* Build up all candidates for coin planchets
*/
for (unsigned int k = 0; k<TALER_CNC_KAPPA; k++)
{
struct TALER_PlanchetMasterSecretP
- planchet_secrets[rd->num_fresh_denom_pubs];
- struct TALER_PlanchetDetail planchet_details[rd->num_fresh_denom_pubs];
-
- TALER_wallet_refresh_nonce_sign (
- &rd->melt_priv,
- &md->kappa_nonces.tuple[k],
- rd->num_fresh_denom_pubs,
- denoms_h,
- k,
- &md->signatures[k]);
- TALER_refresh_signature_to_secrets (
- &md->signatures[k],
- rd->num_fresh_denom_pubs,
- planchet_secrets);
+ planchet_secrets[md->num_fresh_coins];
+ struct TALER_PlanchetDetail planchet_details[md->num_fresh_coins];
md->kappa_blinded_planchets[k] =
- GNUNET_new_array (rd->num_fresh_denom_pubs,
- struct TALER_BlindedPlanchet);
+ GNUNET_new_array (md->num_fresh_coins,
+ struct TALER_BlindedPlanchet);
+ md->kappa_transfer_pubs[k] =
+ GNUNET_new_array (md->num_fresh_coins,
+ struct TALER_TransferPublicKeyP);
+
+ TALER_refresh_expand_batch_seed_to_transfer_data (
+ &md->kappa_batch_seeds.tuple[k],
+ &coin_pub,
+ md->num_fresh_coins,
+ planchet_secrets,
+ md->kappa_transfer_pubs[k]);
- for (unsigned int j = 0; j<rd->num_fresh_denom_pubs; j++)
+ for (unsigned int j = 0; j<md->num_fresh_coins; j++)
{
struct FreshCoinData *fcd = &md->fcds[j];
struct TALER_CoinSpendPrivateKeyP *coin_priv = &fcd->coin_priv;
@@ -238,8 +233,8 @@ TALER_EXCHANGE_get_melt_data_v27 (
bks);
if (NULL != rd->melt_age_commitment_proof)
{
- fcd->age_commitment_proofs[k] = GNUNET_new (struct
- TALER_AgeCommitmentProof);
+ fcd->age_commitment_proofs[k] =
+ GNUNET_new (struct TALER_AgeCommitmentProof);
GNUNET_assert (GNUNET_OK ==
TALER_age_commitment_proof_derive_from_secret (
md->melted_coin.age_commitment_proof,
@@ -266,7 +261,7 @@ TALER_EXCHANGE_get_melt_data_v27 (
&planchet_details[j]))
{
GNUNET_break_op (0);
- TALER_EXCHANGE_free_melt_data_v27 (md);
+ TALER_EXCHANGE_free_melt_data (md);
return GNUNET_SYSERR;
}
md->kappa_blinded_planchets[k][j] =
@@ -282,13 +277,23 @@ TALER_EXCHANGE_get_melt_data_v27 (
}
/* Finally, compute refresh commitment */
- TALER_refresh_get_commitment_v27 (&md->rc,
- &md->refresh_seed,
- uses_cs
+ {
+ struct TALER_KappaTransferPublicKeys k_tr_pubs = {
+ .num_transfer_pubs = md->num_fresh_coins,
+ };
+
+ for (uint8_t k=0; k<TALER_CNC_KAPPA; k++)
+ k_tr_pubs.batch[k] = md->kappa_transfer_pubs[k];
+
+ TALER_refresh_get_commitment (&md->rc,
+ &md->refresh_seed,
+ uses_cs
? &md->blinding_seed
: NULL,
- &k_h_bps,
- &coin_pub,
- &rd->melt_amount);
+ &k_tr_pubs,
+ &k_h_bps,
+ &coin_pub,
+ &rd->melt_amount);
+ }
return GNUNET_OK;
}
diff --git a/src/lib/exchange_api_refresh_common.h b/src/lib/exchange_api_refresh_common.h
@@ -117,7 +117,7 @@ struct FreshCoinData
/**
* Melt data in non-serialized format for convenient processing.
*/
-struct MeltData_v27
+struct MeltData
{
/**
@@ -153,17 +153,10 @@ struct MeltData_v27
struct TALER_PublicRefreshMasterSeedP refresh_seed;
/**
- * The tuple of kappa nonces which we sign with the melted coin.
- * From those signatures, all the planchet seeds are derived from.
+ * The tuple of kappa batch seeds, derived from the @a refresh_seed
+ * and the coin's private key.
*/
- struct TALER_KappaPublicRefreshNoncesP kappa_nonces;
-
- /**
- * The tuple of kappa signatures of the @e kappa_nonces, signed
- * by the @e melted_coin, from which we derive all
- * kappa * @e num_fresh_coins planchet secrets.
- */
- struct TALER_PrivateRefreshNonceSignatureP signatures[TALER_CNC_KAPPA];
+ struct TALER_KappaPrivateRefreshBatchSeedsP kappa_batch_seeds;
/**
* Array of length @e num_fresh_coins with information
@@ -182,6 +175,13 @@ struct MeltData_v27
struct TALER_DenominationHashP *denoms_h;
/**
+ * kappa many arrays fo @e num_fresh_coins transfer public keys that are
+ * derived from the @e kappa_batch_seeds and the private key of the coin.
+ * This will be sent to the exchange during the melt request.
+ */
+ struct TALER_TransferPublicKeyP *kappa_transfer_pubs[TALER_CNC_KAPPA];
+
+ /**
* Blinded planchets of the fresh coins, depending on the cut-and-choose.
* Array of length @e num_fresh_coins.
*/
@@ -192,19 +192,19 @@ struct MeltData_v27
* Compute the melt data from the refresh data and secret
* for v27 of the protocol.
*
- * @param rms secret internals of the refresh-reveal operation
+ * @param rms public seed of the refresh-reveal operation
* @param rd refresh data with the characteristics of the operation
* @param blinding_seed seed for blinding in case of CS denominations, can be NULL
* @param alg_values contributions from the exchange into the melt
* @param[out] md where to write the derived melt data
*/
enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_get_melt_data_v27 (
- const struct TALER_RefreshMasterSecretP *rms,
+TALER_EXCHANGE_get_melt_data (
+ const struct TALER_PublicRefreshMasterSeedP *rms,
const struct TALER_EXCHANGE_MeltInput *rd,
const struct TALER_BlindingMasterSeedP *blinding_seed,
const struct TALER_ExchangeBlindingValues *alg_values,
- struct MeltData_v27 *md);
+ struct MeltData *md);
/**
* Free all information associated with a melting session. Note
@@ -216,6 +216,6 @@ TALER_EXCHANGE_get_melt_data_v27 (
* freed (as it is typically not allocated by itself)
*/
void
-TALER_EXCHANGE_free_melt_data_v27 (struct MeltData_v27 *md);
+TALER_EXCHANGE_free_melt_data (struct MeltData *md);
#endif
diff --git a/src/lib/exchange_api_reveal_melt.c b/src/lib/exchange_api_reveal_melt.c
@@ -67,7 +67,7 @@ struct TALER_EXCHANGE_RevealMeltHandle
/**
* The melt data
*/
- struct MeltData_v27 md;
+ struct MeltData md;
/**
* Callback to pass the result onto
@@ -296,11 +296,11 @@ perform_protocol (
struct TALER_EXCHANGE_RevealMeltHandle *mrh)
{
CURL *curlh;
- json_t *j_array_of_signatures;
+ json_t *j_batch_seeds;
- j_array_of_signatures = json_array ();
- GNUNET_assert (NULL != j_array_of_signatures);
+ j_batch_seeds = json_array ();
+ GNUNET_assert (NULL != j_batch_seeds);
for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
{
@@ -308,8 +308,9 @@ perform_protocol (
continue;
GNUNET_assert (0 == json_array_append_new (
- j_array_of_signatures,
- GNUNET_JSON_from_data_auto (&mrh->md.signatures[k])));
+ j_batch_seeds,
+ GNUNET_JSON_from_data_auto (
+ &mrh->md.kappa_batch_seeds.tuple[k])));
}
{
json_t *j_request_body;
@@ -317,8 +318,8 @@ perform_protocol (
j_request_body = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("rc",
&mrh->md.rc),
- GNUNET_JSON_pack_array_steal ("signatures",
- j_array_of_signatures));
+ GNUNET_JSON_pack_array_steal ("batch_seeds",
+ j_batch_seeds));
GNUNET_assert (NULL != j_request_body);
if (NULL != mrh->reveal_input->melt_input->melt_age_commitment_proof)
@@ -341,7 +342,6 @@ perform_protocol (
j_request_body));
json_decref (j_request_body);
}
-
mrh->job = GNUNET_CURL_job_add2 (
curl_ctx,
curlh,
@@ -388,7 +388,7 @@ TALER_EXCHANGE_reveal_melt (
GNUNET_free (mrh);
return NULL;
}
- TALER_EXCHANGE_get_melt_data_v27 (
+ TALER_EXCHANGE_get_melt_data (
reveal_melt_input->rms,
reveal_melt_input->melt_input,
reveal_melt_input->blinding_seed,
@@ -410,7 +410,7 @@ TALER_EXCHANGE_reveal_melt_cancel (
mrh->job = NULL;
}
TALER_curl_easy_post_finished (&mrh->post_ctx);
- TALER_EXCHANGE_free_melt_data_v27 (&mrh->md);
+ TALER_EXCHANGE_free_melt_data (&mrh->md);
GNUNET_free (mrh->request_url);
GNUNET_free (mrh);
}
diff --git a/src/testing/testing_api_cmd_recoup_refresh.c b/src/testing/testing_api_cmd_recoup_refresh.c
@@ -201,7 +201,7 @@ recoup_refresh_run (void *cls,
const struct TALER_CoinSpendPrivateKeyP *coin_priv_old;
const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
const struct TALER_DenominationSignature *coin_sig;
- const struct TALER_RefreshMasterSecretP *rplanchet;
+ const struct TALER_PublicRefreshMasterSeedP *rms;
const struct TALER_PlanchetMasterSecretP *planchet;
const struct TALER_ExchangeBlindingValues *ewv;
char *cref;
@@ -280,8 +280,8 @@ recoup_refresh_run (void *cls,
return;
}
if (GNUNET_OK !=
- TALER_TESTING_get_trait_refresh_secret (melt_cmd,
- &rplanchet))
+ TALER_TESTING_get_trait_refresh_seed (melt_cmd,
+ &rms))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
@@ -338,7 +338,7 @@ recoup_refresh_run (void *cls,
denom_pub,
coin_sig,
ewv,
- rplanchet,
+ rms,
planchet,
idx,
&recoup_refresh_cb,
diff --git a/src/testing/testing_api_cmd_refresh.c b/src/testing/testing_api_cmd_refresh.c
@@ -116,7 +116,7 @@ struct MeltState
/**
* Melt handle while operation is running.
*/
- struct TALER_EXCHANGE_MeltHandle_v27 *mh;
+ struct TALER_EXCHANGE_MeltHandle *mh;
/**
* Expected entry in the coin history created by this
@@ -169,12 +169,7 @@ struct MeltState
/**
* Entropy seed for the refresh-melt operation.
*/
- struct TALER_RefreshMasterSecretP rms;
-
- /**
- * The master seed to derive the nonces from
- */
- struct TALER_PublicRefreshMasterSeedP refresh_seed;
+ struct TALER_PublicRefreshMasterSeedP rms;
/**
* If false, @e blinding_seed contains the seed for the
@@ -461,9 +456,10 @@ reveal_cb (void *cls,
return;
}
}
- TALER_TESTING_unexpected_status (rrs->is,
- hr->http_status,
- rrs->expected_response_code);
+ TALER_TESTING_unexpected_status_with_body (rrs->is,
+ hr->http_status,
+ rrs->expected_response_code,
+ hr->reply);
return;
}
melt_cmd = TALER_TESTING_interpreter_lookup_command (rrs->is,
@@ -558,7 +554,7 @@ melt_reveal_run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct RevealMeltState *rrs = cls;
- struct MeltState *rms;
+ struct MeltState *ms;
const struct TALER_TESTING_Command *melt_cmd;
rrs->cmd = cmd;
@@ -572,19 +568,19 @@ melt_reveal_run (void *cls,
return;
}
GNUNET_assert (melt_cmd->run == &melt_run);
- rms = melt_cmd->cls;
- rms->reveal_melt_input.rms = &rms->rms;
- rms->reveal_melt_input.melt_input = &rms->melt_input;
- rms->reveal_melt_input.blinding_seed = rms->no_blinding_seed
+ ms = melt_cmd->cls;
+ ms->reveal_melt_input.rms = &ms->rms;
+ ms->reveal_melt_input.melt_input = &ms->melt_input;
+ ms->reveal_melt_input.blinding_seed = ms->no_blinding_seed
? NULL
- : &rms->blinding_seed;
- rms->reveal_melt_input.num_blinding_values = rms->num_blinding_values;
- rms->reveal_melt_input.blinding_values = rms->blinding_values;
- rms->reveal_melt_input.noreveal_index = rms->noreveal_index;
+ : &ms->blinding_seed;
+ ms->reveal_melt_input.num_blinding_values = ms->num_blinding_values;
+ ms->reveal_melt_input.blinding_values = ms->blinding_values;
+ ms->reveal_melt_input.noreveal_index = ms->noreveal_index;
rrs->rmh = TALER_EXCHANGE_reveal_melt (
TALER_TESTING_interpreter_get_context (is),
TALER_TESTING_get_exchange_url (is),
- &rms->reveal_melt_input,
+ &ms->reveal_melt_input,
&reveal_cb,
rrs);
if (NULL == rrs->rmh)
@@ -664,17 +660,17 @@ do_melt_retry (void *cls)
*/
static void
melt_cb (void *cls,
- const struct TALER_EXCHANGE_MeltResponse_v27 *mr)
+ const struct TALER_EXCHANGE_MeltResponse *mr)
{
- struct MeltState *rms = cls;
+ struct MeltState *ms = cls;
const struct TALER_EXCHANGE_HttpResponse *hr = &mr->hr;
- rms->mh = NULL;
- if (rms->expected_response_code != hr->http_status)
+ ms->mh = NULL;
+ if (ms->expected_response_code != hr->http_status)
{
- if (0 != rms->do_retry)
+ if (0 != ms->do_retry)
{
- rms->do_retry--;
+ ms->do_retry--;
if ( (0 == hr->http_status) ||
(TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec) ||
(MHD_HTTP_INTERNAL_SERVER_ERROR == hr->http_status) )
@@ -685,78 +681,78 @@ melt_cb (void *cls,
(int) hr->ec);
/* on DB conflicts, do not use backoff */
if (TALER_EC_GENERIC_DB_SOFT_FAILURE == hr->ec)
- rms->backoff = GNUNET_TIME_UNIT_ZERO;
+ ms->backoff = GNUNET_TIME_UNIT_ZERO;
else
- rms->backoff = GNUNET_TIME_randomized_backoff (rms->backoff,
- MAX_BACKOFF);
- rms->total_backoff = GNUNET_TIME_relative_add (rms->total_backoff,
- rms->backoff);
- TALER_TESTING_inc_tries (rms->is);
- rms->retry_task = GNUNET_SCHEDULER_add_delayed (rms->backoff,
- &do_melt_retry,
- rms);
+ ms->backoff = GNUNET_TIME_randomized_backoff (ms->backoff,
+ MAX_BACKOFF);
+ ms->total_backoff = GNUNET_TIME_relative_add (ms->total_backoff,
+ ms->backoff);
+ TALER_TESTING_inc_tries (ms->is);
+ ms->retry_task = GNUNET_SCHEDULER_add_delayed (ms->backoff,
+ &do_melt_retry,
+ ms);
return;
}
}
- TALER_TESTING_unexpected_status_with_body (rms->is,
+ TALER_TESTING_unexpected_status_with_body (ms->is,
hr->http_status,
- rms->expected_response_code,
+ ms->expected_response_code,
hr->reply);
return;
}
if (MHD_HTTP_OK == hr->http_status)
{
- rms->noreveal_index = mr->details.ok.noreveal_index;
- if (mr->details.ok.num_melt_blinding_values != rms->num_fresh_coins)
+ ms->noreveal_index = mr->details.ok.noreveal_index;
+ if (mr->details.ok.num_melt_blinding_values != ms->num_fresh_coins)
{
GNUNET_break (0);
- TALER_TESTING_interpreter_fail (rms->is);
+ TALER_TESTING_interpreter_fail (ms->is);
return;
}
- rms->no_blinding_seed = (NULL == mr->details.ok.blinding_seed);
+ ms->no_blinding_seed = (NULL == mr->details.ok.blinding_seed);
if (NULL != mr->details.ok.blinding_seed)
- rms->blinding_seed = *mr->details.ok.blinding_seed;
- rms->num_blinding_values = mr->details.ok.num_melt_blinding_values;
- if (NULL != rms->blinding_values)
+ ms->blinding_seed = *mr->details.ok.blinding_seed;
+ ms->num_blinding_values = mr->details.ok.num_melt_blinding_values;
+ if (NULL != ms->blinding_values)
{
GNUNET_break (0); /* can this this happen? Check! */
- for (unsigned int i = 0; i < rms->num_blinding_values; i++)
- TALER_denom_ewv_free (&rms->blinding_values[i]);
- GNUNET_free (rms->blinding_values);
+ for (unsigned int i = 0; i < ms->num_blinding_values; i++)
+ TALER_denom_ewv_free (&ms->blinding_values[i]);
+ GNUNET_free (ms->blinding_values);
}
- rms->blinding_values = GNUNET_new_array (
- rms->num_blinding_values,
+ ms->blinding_values = GNUNET_new_array (
+ ms->num_blinding_values,
struct TALER_ExchangeBlindingValues);
- for (unsigned int i = 0; i<rms->num_blinding_values; i++)
+ for (unsigned int i = 0; i<ms->num_blinding_values; i++)
{
- TALER_denom_ewv_copy (&rms->blinding_values[i],
+ TALER_denom_ewv_copy (&ms->blinding_values[i],
&mr->details.ok.melt_blinding_values[i]);
}
}
- if (0 != rms->total_backoff.rel_value_us)
+ if (0 != ms->total_backoff.rel_value_us)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Total melt backoff for %s was %s\n",
- rms->cmd->label,
- GNUNET_STRINGS_relative_time_to_string (rms->total_backoff,
+ ms->cmd->label,
+ GNUNET_STRINGS_relative_time_to_string (ms->total_backoff,
true));
}
- if (rms->double_melt)
+ if (ms->double_melt)
{
TALER_LOG_DEBUG ("Doubling the melt (%s)\n",
- rms->cmd->label);
- rms->mh = TALER_EXCHANGE_melt_v27 (
- TALER_TESTING_interpreter_get_context (rms->is),
- TALER_TESTING_get_exchange_url (rms->is),
- TALER_TESTING_get_keys (rms->is),
- &rms->rms,
- &rms->melt_input,
+ ms->cmd->label);
+ ms->mh = TALER_EXCHANGE_melt (
+ TALER_TESTING_interpreter_get_context (ms->is),
+ TALER_TESTING_get_exchange_url (ms->is),
+ TALER_TESTING_get_keys (ms->is),
+ &ms->rms,
+ &ms->melt_input,
&melt_cb,
- rms);
- rms->double_melt = false;
+ ms);
+ ms->double_melt = false;
return;
}
- TALER_TESTING_interpreter_next (rms->is);
+ TALER_TESTING_interpreter_next (ms->is);
}
@@ -943,7 +939,7 @@ melt_run (void *cls,
else
rms->che.details.melt.no_hac = true;
- rms->mh = TALER_EXCHANGE_melt_v27 (
+ rms->mh = TALER_EXCHANGE_melt (
TALER_TESTING_interpreter_get_context (is),
TALER_TESTING_get_exchange_url (is),
TALER_TESTING_get_keys (is),
@@ -980,7 +976,7 @@ melt_cleanup (void *cls,
{
TALER_TESTING_command_incomplete (rms->is,
cmd->label);
- TALER_EXCHANGE_melt_v27_cancel (rms->mh);
+ TALER_EXCHANGE_melt_cancel (rms->mh);
rms->mh = NULL;
}
if (NULL != rms->retry_task)
@@ -1043,7 +1039,7 @@ melt_traits (void *cls,
TALER_TESTING_make_trait_h_age_commitment (
index,
rms->melt_input.melt_h_age_commitment),
- TALER_TESTING_make_trait_refresh_secret (&rms->rms),
+ TALER_TESTING_make_trait_refresh_seed (&rms->rms),
(NULL != rms->reveal_melt_input.blinding_values)
? TALER_TESTING_make_trait_exchange_blinding_values (
index,
diff --git a/src/util/crypto.c b/src/util/crypto.c
@@ -221,7 +221,7 @@ TALER_withdraw_master_seed_setup_random (
void
TALER_refresh_master_setup_random (
- struct TALER_RefreshMasterSecretP *rms)
+ struct TALER_PublicRefreshMasterSeedP *rms)
{
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
rms,
@@ -251,30 +251,6 @@ 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)
-{
- uint32_t be_salt = htonl (cnc_num);
-
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_kdf (tpriv,
- sizeof (*tpriv),
- &be_salt,
- sizeof (be_salt),
- old_coin_priv,
- sizeof (*old_coin_priv),
- rms,
- sizeof (*rms),
- "taler-transfer-priv-derivation",
- strlen ("taler-transfer-priv-derivation"),
- NULL, 0));
-}
-
-
-void
TALER_cs_withdraw_nonce_derive (
const struct TALER_PlanchetMasterSecretP *ps,
struct GNUNET_CRYPTO_CsSessionNonce *nonce)
@@ -309,8 +285,9 @@ TALER_cs_withdraw_seed_to_blinding_seed (
void
-TALER_cs_refresh_secret_to_blinding_seed (
- const struct TALER_RefreshMasterSecretP *secret,
+TALER_cs_refresh_seed_to_blinding_seed (
+ const struct TALER_PublicRefreshMasterSeedP *seed,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
struct TALER_BlindingMasterSeedP *blinding_seed)
{
GNUNET_assert (GNUNET_YES ==
@@ -318,8 +295,10 @@ TALER_cs_refresh_secret_to_blinding_seed (
sizeof (*blinding_seed),
"refresh-blinding",
strlen ("refresh-blinding"),
- secret,
- sizeof(*secret),
+ coin_priv,
+ sizeof(*coin_priv),
+ seed,
+ sizeof(*seed),
NULL,
0));
}
@@ -387,28 +366,6 @@ TALER_cs_derive_only_cs_blind_nonces_from_seed (
}
-void
-TALER_cs_refresh_nonce_derive (
- const struct TALER_RefreshMasterSecretP *rms,
- uint32_t coin_num_salt,
- struct GNUNET_CRYPTO_CsSessionNonce *nonce)
-{
- uint32_t be_salt = htonl (coin_num_salt);
-
- GNUNET_assert (GNUNET_YES ==
- GNUNET_CRYPTO_kdf (nonce,
- sizeof (*nonce),
- &be_salt,
- sizeof (be_salt),
- "refresh-n",
- strlen ("refresh-n"),
- rms,
- sizeof(*rms),
- NULL,
- 0));
-}
-
-
bool
TALER_cs_mark_indices (
size_t num,
@@ -568,83 +525,11 @@ 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,
- const struct TALER_Amount *amount_with_fee)
-{
- 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++)
- {
- GNUNET_CRYPTO_hash_context_read (hash_context,
- &rcs[i].transfer_pub,
- sizeof (struct TALER_TransferPublicKeyP));
- }
- /* next, add all of the hashes from the denomination keys to the
- hash_context */
- for (unsigned int i = 0; i<num_new_coins; i++)
- {
- struct TALER_DenominationHashP denom_hash;
-
- /* The denomination keys should / must all be identical regardless
- of what offset we use, so we use [0]. */
- GNUNET_assert (kappa > 0); /* sanity check */
- TALER_denom_pub_hash (rcs[0].new_coins[i].dk,
- &denom_hash);
- GNUNET_CRYPTO_hash_context_read (hash_context,
- &denom_hash,
- sizeof (denom_hash));
- }
-
- /* next, add public key of coin and amount being refreshed */
- {
- struct TALER_AmountNBO melt_amountn;
-
- GNUNET_CRYPTO_hash_context_read (hash_context,
- coin_pub,
- sizeof (struct TALER_CoinSpendPublicKeyP));
- TALER_amount_hton (&melt_amountn,
- amount_with_fee);
- GNUNET_CRYPTO_hash_context_read (hash_context,
- &melt_amountn,
- sizeof (struct TALER_AmountNBO));
- }
-
- /* finally, add all the envelopes */
- for (unsigned int i = 0; i<kappa; i++)
- {
- const struct TALER_RefreshCommitmentEntry *rce = &rcs[i];
-
- for (unsigned int j = 0; j<num_new_coins; j++)
- {
- const struct TALER_RefreshCoinData *rcd = &rce->new_coins[j];
-
- TALER_blinded_planchet_hash_ (&rcd->blinded_planchet,
- hash_context);
- }
- }
-
- /* Conclude */
- GNUNET_CRYPTO_hash_context_finish (hash_context,
- &rc->session_hash);
-}
-
-
-void
-TALER_refresh_get_commitment_v27 (
+TALER_refresh_get_commitment (
struct TALER_RefreshCommitmentP *rc,
const struct TALER_PublicRefreshMasterSeedP *refresh_seed,
const struct TALER_BlindingMasterSeedP *blinding_seed,
+ const struct TALER_KappaTransferPublicKeys *k_tpbs,
const struct TALER_KappaHashBlindedPlanchetsP *k_bps_h,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *amount_with_fee)
@@ -701,23 +586,133 @@ TALER_refresh_get_commitment_v27 (
void
-TALER_refresh_master_secret_to_refresh_seed (
- const struct TALER_RefreshMasterSecretP *rms,
- struct TALER_PublicRefreshMasterSeedP *r_seed)
+TALER_refresh_expand_seed_to_kappa_batch_seeds (
+ const struct TALER_PublicRefreshMasterSeedP *refresh_master_seed,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_KappaPrivateRefreshBatchSeedsP *kappa_batch_seeds)
{
GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_kdf (r_seed,
- sizeof (*r_seed),
- "refresh-seed",
- strlen ("refresh-seed"),
- rms,
- sizeof (*rms),
+ GNUNET_CRYPTO_kdf (kappa_batch_seeds,
+ sizeof (*kappa_batch_seeds),
+ "refresh-batch-seeds",
+ strlen ("refresh-batch-seeds"),
+ refresh_master_seed,
+ sizeof (*refresh_master_seed),
+ coin_priv,
+ sizeof(*coin_priv),
NULL, 0));
}
void
-TALER_refresh_expand_kappa_nonces (
+TALER_refresh_expand_batch_seed_to_transfer_private_keys (
+ const struct TALER_PrivateRefreshBatchSeedP *batch_seed,
+ size_t num_transfer_pks,
+ struct TALER_TransferPrivateKeyP transfer_pks[num_transfer_pks])
+{
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CRYPTO_kdf (
+ transfer_pks,
+ sizeof (*transfer_pks) * num_transfer_pks,
+ "refresh-transfer-private-keys",
+ strlen ("refresh-transfer-private-keys"),
+ batch_seed,
+ sizeof (*batch_seed),
+ NULL, 0));
+}
+
+
+void
+TALER_refresh_expand_batch_seed_to_transfer_secrets (
+ const struct TALER_PrivateRefreshBatchSeedP *batch_seed,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ size_t num_transfer_secrets,
+ struct TALER_TransferSecretP transfer_secrets[num_transfer_secrets])
+{
+ struct TALER_TransferPrivateKeyP transfer_pks[num_transfer_secrets];
+
+ TALER_refresh_expand_batch_seed_to_transfer_private_keys (
+ batch_seed,
+ num_transfer_secrets,
+ transfer_pks);
+
+ for (size_t i = 0; i < num_transfer_secrets; i++)
+ {
+ TALER_link_reveal_transfer_secret (
+ &transfer_pks[i],
+ coin_pub,
+ &transfer_secrets[i]);
+ }
+}
+
+
+void
+TALER_refresh_expand_batch_seed_to_planchet_master_secrets (
+ const struct TALER_PrivateRefreshBatchSeedP *batch_seed,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ size_t num_planchet_secrets,
+ struct TALER_PlanchetMasterSecretP planchet_secrets[num_planchet_secrets])
+{
+ struct TALER_TransferPrivateKeyP transfer_pks[num_planchet_secrets];
+ struct TALER_TransferSecretP transfer_secrets[num_planchet_secrets];
+
+ TALER_refresh_expand_batch_seed_to_transfer_private_keys (
+ batch_seed,
+ num_planchet_secrets,
+ transfer_pks);
+
+ for (size_t i = 0; i < num_planchet_secrets; i++)
+ {
+ TALER_link_reveal_transfer_secret (
+ &transfer_pks[i],
+ coin_pub,
+ &transfer_secrets[i]);
+
+ TALER_transfer_secret_to_planchet_secret (
+ &transfer_secrets[i],
+ i,
+ &planchet_secrets[i]);
+ }
+}
+
+
+void
+TALER_refresh_expand_batch_seed_to_transfer_data (
+ const struct TALER_PrivateRefreshBatchSeedP *batch_seed,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ size_t num,
+ struct TALER_PlanchetMasterSecretP planchet_secrets[num],
+ struct TALER_TransferPublicKeyP transfer_pubs[num])
+{
+ struct TALER_TransferPrivateKeyP transfer_pks[num];
+ struct TALER_TransferSecretP transfer_secrets[num];
+
+ TALER_refresh_expand_batch_seed_to_transfer_private_keys (
+ batch_seed,
+ num,
+ transfer_pks);
+
+ for (size_t i = 0; i < num; i++)
+ {
+ TALER_link_reveal_transfer_secret (
+ &transfer_pks[i],
+ coin_pub,
+ &transfer_secrets[i]);
+
+ TALER_transfer_secret_to_planchet_secret (
+ &transfer_secrets[i],
+ i,
+ &planchet_secrets[i]);
+
+ GNUNET_CRYPTO_ecdhe_key_get_public (
+ &transfer_pks[i].ecdhe_priv,
+ &transfer_pubs[i].ecdhe_pub);
+ }
+}
+
+
+void
+TALER_refresh_expand_kappa_nonces_v27 (
const struct TALER_PublicRefreshMasterSeedP *refresh_seed,
struct TALER_KappaPublicRefreshNoncesP *kappa_nonces)
{
@@ -733,7 +728,7 @@ TALER_refresh_expand_kappa_nonces (
void
-TALER_refresh_signature_to_secrets (
+TALER_refresh_signature_to_secrets_v27 (
const struct TALER_PrivateRefreshNonceSignatureP *sig,
size_t num_secrets,
struct TALER_PlanchetMasterSecretP secrets[static num_secrets])