summaryrefslogtreecommitdiff
path: root/src/exchange
diff options
context:
space:
mode:
authorÖzgür Kesim <oec-taler@kesim.org>2023-06-26 00:01:31 +0200
committerÖzgür Kesim <oec-taler@kesim.org>2023-06-26 00:01:31 +0200
commitddedf03a816e5139b235a3ebdf5b600508c5ed5f (patch)
treea65179048fc764ec82ddf645a8982186b0157448 /src/exchange
parent70bfe0ed1b9a5dbb6cc487465ef3c3df4cdb0436 (diff)
downloadexchange-ddedf03a816e5139b235a3ebdf5b600508c5ed5f.tar.gz
exchange-ddedf03a816e5139b235a3ebdf5b600508c5ed5f.tar.bz2
exchange-ddedf03a816e5139b235a3ebdf5b600508c5ed5f.zip
[age-withdraw] age-withdraw commit- and reveal-handlers implemented, 12/n
The handlers for the commit- and reveal-phases of the age-withdraw HTTP-endpoints are implemented, yet not active. Still missing: - support for age-withdraw is missing in lib/. - tests
Diffstat (limited to 'src/exchange')
-rw-r--r--src/exchange/taler-exchange-httpd_age-withdraw.c933
-rw-r--r--src/exchange/taler-exchange-httpd_age-withdraw_reveal.c932
-rw-r--r--src/exchange/taler-exchange-httpd_batch-withdraw.c35
-rw-r--r--src/exchange/taler-exchange-httpd_csr.c16
-rw-r--r--src/exchange/taler-exchange-httpd_extensions.c2
-rw-r--r--src/exchange/taler-exchange-httpd_keys.c92
-rw-r--r--src/exchange/taler-exchange-httpd_keys.h4
-rw-r--r--src/exchange/taler-exchange-httpd_refreshes_reveal.c10
-rw-r--r--src/exchange/taler-exchange-httpd_responses.c14
-rw-r--r--src/exchange/taler-exchange-httpd_responses.h14
-rw-r--r--src/exchange/taler-exchange-httpd_withdraw.c11
11 files changed, 1130 insertions, 933 deletions
diff --git a/src/exchange/taler-exchange-httpd_age-withdraw.c b/src/exchange/taler-exchange-httpd_age-withdraw.c
index d9475e8f6..e7f8b53fc 100644
--- a/src/exchange/taler-exchange-httpd_age-withdraw.c
+++ b/src/exchange/taler-exchange-httpd_age-withdraw.c
@@ -22,8 +22,13 @@
* @author Özgür Kesim
*/
#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_json_lib.h>
#include <gnunet/gnunet_util_lib.h>
#include <jansson.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+#include "taler_error_codes.h"
#include "taler_json_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h"
@@ -31,6 +36,502 @@
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_keys.h"
+
+/**
+ * Context for #age_withdraw_transaction.
+ */
+struct AgeWithdrawContext
+{
+ /**
+ * KYC status for the operation.
+ */
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+
+ /**
+ * Timestamp
+ */
+ struct GNUNET_TIME_Timestamp now;
+
+ /**
+ * Hash of the wire source URL, needed when kyc is needed.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * The data from the age-withdraw request, as we persist it
+ */
+ struct TALER_EXCHANGEDB_AgeWithdraw commitment;
+
+ /**
+ * Number of coins/denonations in the reveal
+ */
+ uint32_t num_coins;
+
+ /**
+ * kappa * #num_coins hashes of blinded coin planchets.
+ */
+ struct TALER_BlindedPlanchet *coin_evs;
+
+ /**
+ * #num_coins hashes of the denominations from which the coins are withdrawn.
+ * Those must support age restriction.
+ */
+ struct TALER_DenominationHashP *denom_hs;
+
+};
+
+/*
+ * @brief Free the resources within a AgeWithdrawContext
+ *
+ * @param awc the context to free
+ */
+static void
+free_age_withdraw_context_resources (struct AgeWithdrawContext *awc)
+{
+ GNUNET_free (awc->denom_hs);
+ GNUNET_free (awc->coin_evs);
+ GNUNET_free (awc->commitment.denom_serials);
+ /* commitment.denom_serials and .h_coin_evs are stack allocated */
+}
+
+
+/**
+ * Parse the denominations and blinded coin data of an '/age-withdraw' request.
+ *
+ * @param connection The MHD connection to handle
+ * @param j_denoms_h Array of n hashes of the denominations for the withdrawal, in JSON format
+ * @param j_blinded_coin_evs Array of n arrays of kappa blinded envelopes of in JSON format for the coins.
+ * @param[out] aws The context of the operation, only partially built at call time
+ * @param[out] mhd_ret The result if a reply is queued for MHD
+ * @return true on success, false on failure, with a reply already queued for MHD
+ */
+static enum GNUNET_GenericReturnValue
+parse_age_withdraw_json (
+ struct MHD_Connection *connection,
+ const json_t *j_denoms_h,
+ const json_t *j_blinded_coin_evs,
+ struct AgeWithdrawContext *awc,
+ MHD_RESULT *mhd_ret)
+{
+ char buf[256] = {0};
+ const char *error = NULL;
+ unsigned int idx = 0;
+ json_t *value = NULL;
+
+
+ /* The age value MUST be on the beginning of an age group */
+ if (awc->commitment.max_age !=
+ TALER_get_lowest_age (&TEH_age_restriction_config.mask,
+ awc->commitment.max_age))
+ {
+ error = "max_age must be the lower edge of an age group";
+ goto EXIT;
+ }
+
+ /* Verify JSON-structure consistency */
+ {
+ uint32_t num_coins = json_array_size (j_denoms_h);
+
+ if (! json_is_array (j_denoms_h))
+ error = "denoms_h must be an array";
+ else if (! json_is_array (j_blinded_coin_evs))
+ error = "coin_evs must be an array";
+ else if (num_coins == 0)
+ error = "denoms_h must not be empty";
+ else if (num_coins != json_array_size (j_blinded_coin_evs))
+ error = "denoms_h and coins_evs must be arrays of the same size";
+ else if (num_coins > TALER_MAX_FRESH_COINS)
+ /**
+ * The wallet had committed to more than the maximum coins allowed, the
+ * reserve has been charged, but now the user can not withdraw any money
+ * from it. Note that the user can't get their money back in this case!
+ **/
+ error = "maximum number of coins that can be withdrawn has been exceeded";
+
+ _Static_assert ((TALER_MAX_FRESH_COINS < INT_MAX / TALER_CNC_KAPPA),
+ "TALER_MAX_FRESH_COINS too large");
+
+ if (NULL != error)
+ goto EXIT;
+
+ awc->num_coins = num_coins;
+ }
+
+ /* Continue parsing the parts */
+
+ /* Parse denomination keys */
+ awc->denom_hs = GNUNET_new_array (awc->num_coins,
+ struct TALER_DenominationHashP);
+
+ json_array_foreach (j_denoms_h, idx, value) {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL, &awc->denom_hs[idx]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value, spec, NULL, NULL))
+ {
+ GNUNET_snprintf (buf,
+ sizeof(buf),
+ "couldn't parse entry no. %d in array denoms_h",
+ idx + 1);
+ error = buf;
+ goto EXIT;
+ }
+ };
+
+ /* no overflow because
+ * num_coins <= TALER_MAX_FRESH_COINS
+ * and
+ * TALER_MAX_FRESH_COINS * TALER_CNC_KAPPA < INT_MAX
+ */
+ awc->coin_evs = GNUNET_new_array (awc->num_coins * TALER_CNC_KAPPA,
+ struct TALER_BlindedPlanchet);
+
+ /* Parse blinded envelopes. */
+ json_array_foreach (j_blinded_coin_evs, idx, value) {
+ const json_t *j_kappa_coin_evs;
+ struct GNUNET_JSON_Specification aspec[] = {
+ GNUNET_JSON_spec_array_const (NULL, &j_kappa_coin_evs),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value, aspec, NULL, NULL))
+ {
+ GNUNET_snprintf (buf,
+ sizeof(buf),
+ "couldn't parse entry no. %d in array coin_evs",
+ idx + 1);
+ error = buf;
+ goto EXIT;
+ }
+
+ if (TALER_CNC_KAPPA != json_array_size (j_kappa_coin_evs))
+ {
+ GNUNET_snprintf (buf,
+ sizeof(buf),
+ "array no. %d in coin_evs not of correct size",
+ idx + 1);
+ error = buf;
+ goto EXIT;
+ }
+
+ /* Now parse the individual kappa envelopes */
+ {
+ size_t off = idx * TALER_CNC_KAPPA;
+ size_t kappa = 0;
+
+ json_array_foreach (j_kappa_coin_evs, kappa, value) {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL, &awc->coin_evs[off + kappa]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (value, spec, NULL, NULL))
+ {
+ GNUNET_snprintf (buf,
+ sizeof(buf),
+ "couldn't parse array no. %d in coin_evs",
+ idx + 1);
+ error = buf;
+ goto EXIT;
+ }
+
+ /* Check for duplicate planchets
+ * FIXME: is this needed?
+ */
+ for (unsigned int i = 0; i < off + kappa; i++)
+ {
+ if (0 == TALER_blinded_planchet_cmp (&awc->coin_evs[off + kappa],
+ &awc->coin_evs[i]))
+ {
+ error = "duplicate planchet";
+ goto EXIT;
+ }
+ }
+ }
+ }
+ }; /* json_array_foreach over j_blinded_coin_evs */
+
+ /* We successfully parsed denoms_h and blinded_coins_evs */
+ GNUNET_assert (NULL == error);
+
+ /* Finally, calculate the h_commitment from all blinded envelopes */
+ {
+ enum GNUNET_GenericReturnValue ret;
+ struct GNUNET_HashContext *hash_context;
+
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+
+ for (size_t c = 0;
+ c < TALER_CNC_KAPPA * awc->num_coins;
+ c++)
+ {
+ struct TALER_BlindedCoinHashP bch;
+
+ ret = TALER_coin_ev_hash (&awc->coin_evs[c],
+ &awc->denom_hs[c],
+ &bch);
+
+ GNUNET_assert (GNUNET_OK == ret);
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &bch,
+ sizeof(bch));
+ }
+
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ &awc->commitment.h_commitment.hash);
+ }
+
+
+EXIT:
+ if (NULL != error)
+ {
+ /* Note: resources are freed in caller */
+
+ *mhd_ret = TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ error);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check if the given denomination is still or already valid, has not been
+ * revoked and supports age restriction.
+ *
+ * @param connection HTTP-connection to the client
+ * @param ksh The handle to the current state of (denomination) keys in the exchange
+ * @param denom_h Hash of the denomination key to check
+ * @param[out] dk On success, will contain the denomination key details
+ * @param[out] result On failure, an MHD-response will be qeued and result will be set to accordingly
+ * @return true on success (denomination valid), false otherwise
+ */
+static bool
+denomination_is_valid (
+ struct MHD_Connection *connection,
+ struct TEH_KeyStateHandle *ksh,
+ const struct TALER_DenominationHashP *denom_h,
+ struct TEH_DenominationKey **pdk,
+ MHD_RESULT *result)
+{
+ struct TEH_DenominationKey *dk;
+ dk = TEH_keys_denomination_by_hash_from_state (ksh,
+ denom_h,
+ connection,
+ result);
+ if (NULL == dk)
+ {
+ /* The denomination doesn't exist */
+ /* Note: a HTTP-response has been queued and result has been set by
+ * TEH_keys_denominations_by_hash_from_state */
+ return false;
+ }
+
+ if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time))
+ {
+ /* This denomination is past the expiration time for withdraws */
+ /* FIXME[oec]: add idempotency check */
+ *result = TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ denom_h,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "age-withdraw_reveal");
+ return false;
+ }
+
+ if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+ {
+ /* This denomination is not yet valid */
+ *result = TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ denom_h,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "age-withdraw_reveal");
+ return false;
+ }
+
+ if (dk->recoup_possible)
+ {
+ /* This denomination has been revoked */
+ *result = TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+ NULL);
+ return false;
+ }
+
+ if (0 == dk->denom_pub.age_mask.bits)
+ {
+ /* This denomation does not support age restriction */
+ char msg[256] = {0};
+ GNUNET_snprintf (msg,
+ sizeof(msg),
+ "denomination %s does not support age restriction",
+ GNUNET_h2s (&denom_h->hash));
+
+ *result = TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
+ msg);
+ return false;
+ }
+
+ *pdk = dk;
+ return true;
+}
+
+
+/**
+ * Check if the given array of hashes of denomination_keys a) belong
+ * to valid denominations and b) those are marked as age restricted.
+ * Also, calculate the total amount of the denominations including fees
+ * for withdraw.
+ *
+ * @param connection The HTTP connection to the client
+ * @param len The lengths of the array @a denoms_h
+ * @param denoms_h array of hashes of denomination public keys
+ * @param coin_evs array of blinded coin planchets
+ * @param[out] denom_serials On success, will be filled with the serial-id's of the denomination keys. Caller must deallocate.
+ * @param[out] amount_with_fee On succes, will contain the committed amount including fees
+ * @param[out] result In the error cases, a response will be queued with MHD and this will be the result.
+ * @return GNUNET_OK if the denominations are valid and support age-restriction
+ * GNUNET_SYSERR otherwise
+ */
+static enum GNUNET_GenericReturnValue
+are_denominations_valid (
+ struct MHD_Connection *connection,
+ uint32_t len,
+ const struct TALER_DenominationHashP *denom_hs,
+ const struct TALER_BlindedPlanchet *coin_evs,
+ uint64_t **denom_serials,
+ struct TALER_Amount *amount_with_fee,
+ MHD_RESULT *result)
+{
+ struct TALER_Amount total_amount;
+ struct TALER_Amount total_fee;
+ struct TEH_KeyStateHandle *ksh;
+ uint64_t *serials;
+
+ GNUNET_assert (*denom_serials == NULL);
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ *denom_serials =
+ serials = GNUNET_new_array (len, uint64_t);
+
+ TALER_amount_set_zero (TEH_currency, &total_amount);
+ TALER_amount_set_zero (TEH_currency, &total_fee);
+
+ for (uint32_t i = 0; i < len; i++)
+ {
+ struct TEH_DenominationKey *dk;
+ if (! denomination_is_valid (connection,
+ ksh,
+ &denom_hs[i],
+ &dk,
+ result))
+ /* FIXME[oec]: add idempotency check */
+ return GNUNET_SYSERR;
+
+ /* Ensure the ciphers from the planchets match the denominations' */
+ if (dk->denom_pub.cipher != coin_evs[i].cipher)
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ /* Accumulate the values */
+ if (0 > TALER_amount_add (&total_amount,
+ &total_amount,
+ &dk->meta.value))
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_OVERFLOW,
+ "amount");
+ return GNUNET_SYSERR;
+ }
+
+ /* Accumulate the withdraw fees */
+ if (0 > TALER_amount_add (&total_fee,
+ &total_fee,
+ &dk->meta.fees.withdraw))
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_OVERFLOW,
+ "fee");
+ return GNUNET_SYSERR;
+ }
+
+ serials[i] = dk->meta.serial;
+ }
+
+ /* Save the total amount including fees */
+ GNUNET_assert (0 < TALER_amount_add (amount_with_fee,
+ &total_amount,
+ &total_fee));
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * @brief Verify the signature of the request body with the reserve key
+ *
+ * @param connection the connection to the client
+ * @param commitment the age withdraw commitment
+ * @param mhd_ret the response to fill in the error case
+ * @return GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+verify_reserve_signature (
+ struct MHD_Connection *connection,
+ const struct TALER_EXCHANGEDB_AgeWithdraw *commitment,
+ enum MHD_Result *mhd_ret
+ )
+{
+
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_age_withdraw_verify (&commitment->h_commitment,
+ &commitment->amount_with_fee,
+ &TEH_age_restriction_config.mask,
+ commitment->max_age,
+ &commitment->reserve_pub,
+ &commitment->reserve_sig))
+ {
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
/**
* Send a response to a "age-withdraw" request.
*
@@ -72,57 +573,62 @@ reply_age_withdraw_success (
/**
- * Context for #age_withdraw_transaction.
+ * Check if the request is replayed and we already have an
+ * answer. If so, replay the existing answer and return the
+ * HTTP response.
+ *
+ * @param con connection to the client
+ * @param[in,out] awc parsed request data
+ * @param[out] mret HTTP status, set if we return true
+ * @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
*/
-struct AgeWithdrawContext
+static bool
+request_is_idempotent (struct MHD_Connection *con,
+ struct AgeWithdrawContext *awc,
+ MHD_RESULT *mret)
{
- /**
- * KYC status for the operation.
- */
- struct TALER_EXCHANGEDB_KycStatus kyc;
-
- /**
- * Hash of the wire source URL, needed when kyc is needed.
- */
- struct TALER_PaytoHashP h_payto;
-
- /**
- * The data from the age-withdraw request
- */
- struct TALER_EXCHANGEDB_AgeWithdrawCommitment commitment;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_EXCHANGEDB_AgeWithdraw commitment;
- /**
- * Number of coins/denonations in the reveal
- */
- uint32_t num_coins;
+ qs = TEH_plugin->get_age_withdraw (TEH_plugin->cls,
+ &awc->commitment.reserve_pub,
+ &awc->commitment.h_commitment,
+ &commitment);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ *mret = TALER_MHD_reply_with_ec (con,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_age_withdraw");
+ return true; /* Well, kind-of. At least we have set mret. */
+ }
- /**
- * kappa * #num_coins hashes of blinded coin planchets.
- */
- struct TALER_BlindedPlanchet *coin_evs;
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return false;
- /**
- * #num_coins hashes of the denominations from which the coins are withdrawn.
- * Those must support age restriction.
- */
- struct TALER_DenominationHashP *denoms_h;
-};
+ /* Generate idempotent reply */
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_AGE_WITHDRAW]++;
+ *mret = reply_age_withdraw_success (con,
+ &commitment.h_commitment,
+ commitment.noreveal_index);
+ return true;
+}
/**
- * Function called to iterate over KYC-relevant
- * transaction amounts for a particular time range.
- * Called within a database transaction, so must
+ * Function called to iterate over KYC-relevant transaction amounts for a
+ * particular time range. Called within a database transaction, so must
* not start a new one.
*
- * @param cls closure, identifies the event type and
- * account to iterate over events for
- * @param limit maximum time-range for which events
- * should be fetched (timestamp in the past)
- * @param cb function to call on each event found,
- * events must be returned in reverse chronological
- * order
- * @param cb_cls closure for @a cb
+ * @param cls closure, identifies the event type and account to iterate
+ * over events for
+ * @param limit maximum time-range for which events should be fetched
+ * (timestamp in the past)
+ * @param cb function to call on each event found, events must be returned
+ * in reverse chronological order
+ * @param cb_cls closure for @a cb, of type struct AgeWithdrawContext
*/
static void
age_withdraw_amount_cb (void *cls,
@@ -162,9 +668,6 @@ age_withdraw_amount_cb (void *cls,
* IF it returns the soft error code, the function MAY be called again
* to retry and MUST not queue a MHD response.
*
- * Note that "awc->commitment.sig" is set before entering this function as we
- * signed before entering the transaction.
- *
* @param cls a `struct AgeWithdrawContext *`
* @param connection MHD request which triggered the transaction
* @param[out] mhd_ret set to MHD response status for @a connection,
@@ -178,20 +681,17 @@ age_withdraw_transaction (void *cls,
{
struct AgeWithdrawContext *awc = cls;
enum GNUNET_DB_QueryStatus qs;
- bool found = false;
- bool balance_ok = false;
- uint64_t ruuid;
- awc->now = GNUNET_TIME_timestamp_get ();
qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls,
&awc->commitment.reserve_pub,
&awc->h_payto);
if (qs < 0)
return qs;
- /* If no results, reserve was created by merge,
+ /* If _no_ results, reserve was created by merge,
in which case no KYC check is required as the
merge already did that. */
+
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{
char *kyc_required;
@@ -210,17 +710,16 @@ age_withdraw_transaction (void *cls,
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "kyc_test_required");
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "kyc_test_required");
}
return qs;
}
if (NULL != kyc_required)
{
- /* insert KYC requirement into DB! */
+ /* Mark result and return by inserting KYC requirement into DB! */
awc->kyc.ok = false;
return TEH_plugin->insert_kyc_requirement_for_account (
TEH_plugin->cls,
@@ -231,37 +730,77 @@ age_withdraw_transaction (void *cls,
}
awc->kyc.ok = true;
- qs = TEH_plugin->do_age_withdraw (TEH_plugin->cls,
- &awc->commitment,
- &found,
- &balance_ok,
- &ruuid);
- if (0 > qs)
- {
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "do_age_withdraw");
- return qs;
- }
- else if (! found)
- {
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- else if (! balance_ok)
+
+ /* KYC requirement fulfilled, do the age-withdraw transaction */
{
- TEH_plugin->rollback (TEH_plugin->cls);
- *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance (
- connection,
- TALER_EC_EXCHANGE_AGE_WITHDRAW_INSUFFICIENT_FUNDS,
- &awc->commitment.amount_with_fee,
- &awc->commitment.reserve_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
+ bool found = false;
+ bool balance_ok = false;
+ bool age_ok = false;
+ bool conflict = false;
+ uint16_t allowed_maximum_age = 0;
+ uint64_t ruuid;
+
+ qs = TEH_plugin->do_age_withdraw (TEH_plugin->cls,
+ &awc->commitment,
+ awc->now,
+ &found,
+ &balance_ok,
+ &age_ok,
+ &allowed_maximum_age,
+ &conflict,
+ &ruuid);
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "do_age_withdraw");
+ return qs;
+ }
+ else if (! found)
+ {
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ else if (! balance_ok)
+ {
+ TEH_plugin->rollback (TEH_plugin->cls);
+
+ *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance (
+ connection,
+ TALER_EC_EXCHANGE_AGE_WITHDRAW_INSUFFICIENT_FUNDS,
+ &awc->commitment.amount_with_fee,
+ &awc->commitment.reserve_pub);
+
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ else if (! age_ok)
+ {
+ enum TALER_ErrorCode ec =
+ TALER_EC_EXCHANGE_AGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE;
+
+ *mhd_ret =
+ TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ TALER_ErrorCode_get_http_status_safe (ec),
+ TALER_MHD_PACK_EC (ec),
+ GNUNET_JSON_pack_uint64 ("allowed_maximum_age",
+ allowed_maximum_age));
+
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ else if (conflict)
+ {
+ /* do_age_withdraw signaled a conflict, so there MUST be an entry
+ * in the DB. Put that into the response */
+ bool ok = request_is_idempotent (connection,
+ awc,
+ mhd_ret);
+ GNUNET_assert (ok);
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
@@ -271,48 +810,107 @@ age_withdraw_transaction (void *cls,
/**
- * Check if the @a rc is replayed and we already have an
- * answer. If so, replay the existing answer and return the
- * HTTP response.
+ * @brief Sign the chosen blinded coins, debit the reserve and persist
+ * the commitment.
*
- * @param rc request context
- * @param[in,out] awc parsed request data
- * @param[out] mret HTTP status, set if we return true
- * @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
+ * On conflict, the noreveal_index from the previous, existing
+ * commitment is returned to the client, returning success.
+ *
+ * On error (like, insufficient funds), the client is notified.
+ *
+ * Note that on success, there are two possible states:
+ * 1.) KYC is required (awc.kyc.ok == false) or
+ * 2.) age withdraw was successful.
+ *
+ * @param connection HTTP-connection to the client
+ * @param h_commitment Original commitment
+ * @param num_coins Number of coins
+ * @param coin_evs The Hashes of the undisclosed, blinded coins, @a num_coins many
+ * @param denom_keys The array of denomination keys, @a num_coins. Needed to detect Clause-Schnorr-based denominations
+ * @param[out] result On error, a HTTP-response will be queued and result set accordingly
+ * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
*/
-static bool
-request_is_idempotent (struct TEH_RequestContext *rc,
- struct AgeWithdrawContext *awc,
- MHD_RESULT *mret)
+static enum GNUNET_GenericReturnValue
+sign_and_do_age_withdraw (
+ struct MHD_Connection *connection,
+ struct AgeWithdrawContext *awc,
+ MHD_RESULT *result)
{
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_EXCHANGEDB_AgeWithdrawCommitment commitment;
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+ struct TALER_BlindedCoinHashP h_coin_evs[awc->num_coins];
+ struct TALER_BlindedDenominationSignature denom_sigs[awc->num_coins];
+ uint8_t noreveal_index;
- qs = TEH_plugin->get_age_withdraw_info (TEH_plugin->cls,
- &awc->commitment.reserve_pub,
- &awc->commitment.h_commitment,
- &commitment);
- if (0 > qs)
+ awc->now = GNUNET_TIME_timestamp_get ();
+
+ /* Pick the challenge */
+ awc->commitment.noreveal_index =
+ noreveal_index =
+ GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG,
+ TALER_CNC_KAPPA);
+
+ /* Choose and sign the coins */
{
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- *mret = TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_age_withdraw_info");
- return true; /* well, kind-of */
+ struct TEH_CoinSignData csds[awc->num_coins];
+ enum TALER_ErrorCode ec;
+
+ /* Pick the chosen blinded coins */
+ for (uint32_t i = 0; i<awc->num_coins; i++)
+ {
+ csds[i].bp = &awc->coin_evs[TALER_CNC_KAPPA * i + noreveal_index];
+ csds[i].h_denom_pub = &awc->denom_hs[i];
+ }
+
+ ec = TEH_keys_denomination_batch_sign (csds,
+ awc->num_coins,
+ false,
+ denom_sigs);
+ if (TALER_EC_NONE != ec)
+ {
+ GNUNET_break (0);
+ *result = TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+ return GNUNET_SYSERR;
+ }
}
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- return false;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Signatures ready, starting DB interaction\n");
- /* generate idempotent reply */
- TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_AGE_WITHDRAW]++;
- *mret = reply_age_withdraw_success (rc->connection,
- &commitment.h_commitment,
- commitment.noreveal_index);
- return true;
+ /* Prepare the hashes of the coins for insertion */
+ for (uint32_t i = 0; i<awc->num_coins; i++)
+ {
+ TALER_coin_ev_hash (&awc->coin_evs[i],
+ &awc->denom_hs[i],
+ &h_coin_evs[i]);
+ }
+
+ /* Run the transaction */
+ awc->commitment.h_coin_evs = h_coin_evs;
+ awc->commitment.denom_sigs = denom_sigs;
+ ret = TEH_DB_run_transaction (connection,
+ "run age withdraw",
+ TEH_MT_REQUEST_AGE_WITHDRAW,
+ result,
+ &age_withdraw_transaction,
+ awc);
+
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break (0);
+ *result = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_UNEXPECTED_REQUEST_ERROR,
+ NULL);
+ }
+
+ /* Free resources */
+ awc->commitment.h_coin_evs = NULL;
+ awc->commitment.denom_sigs = NULL;
+ for (unsigned int i = 0; i<awc->num_coins; i++)
+ TALER_blinded_denom_sig_free (&denom_sigs[i]);
+ return ret;
}
@@ -322,17 +920,18 @@ TEH_handler_age_withdraw (struct TEH_RequestContext *rc,
const json_t *root)
{
MHD_RESULT mhd_ret;
+ const json_t *j_denoms_h;
+ const json_t *j_blinded_coins_evs;
struct AgeWithdrawContext awc = {0};
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &awc.commitment.reserve_sig),
- GNUNET_JSON_spec_fixed_auto ("h_commitment",
- &awc.commitment.h_commitment),
- TALER_JSON_spec_amount ("amount",
- TEH_currency,
- &awc.commitment.amount_with_fee),
+ GNUNET_JSON_spec_array_const ("denoms_h",
+ &j_denoms_h),
+ GNUNET_JSON_spec_array_const ("blinded_coins_evs",
+ &j_blinded_coins_evs),
GNUNET_JSON_spec_uint16 ("max_age",
&awc.commitment.max_age),
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &awc.commitment.reserve_sig),
GNUNET_JSON_spec_end ()
};
@@ -351,53 +950,71 @@ TEH_handler_age_withdraw (struct TEH_RequestContext *rc,
}
do {
- /* If request was made before successfully, return the previous answer */
- if (request_is_idempotent (rc,
- &awc,
- &mhd_ret))
+ /* Note: If we break the statement here at any point,
+ * a response to the client MUST have been populated
+ * with an appropriate answer and mhd_ret MUST have
+ * been set accordingly.
+ */
+
+ /* Parse denoms_h and blinded_coins_evs, partially fill awc */
+ if (GNUNET_OK !=
+ parse_age_withdraw_json (rc->connection,
+ j_denoms_h,
+ j_blinded_coins_evs,
+ &awc,
+ &mhd_ret))
break;
- /* Verify the signature of the request body with the reserve key */
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ /* Ensure validity of denoms and calculate amounts and fees */
if (GNUNET_OK !=
- TALER_wallet_age_withdraw_verify (&awc.commitment.h_commitment,
- &awc.commitment.amount_with_fee,
- awc.commitment.max_age,
- &awc.commitment.reserve_pub,
- &awc.commitment.reserve_sig))
- {
- GNUNET_break_op (0);
- mhd_ret = TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID,
- NULL);
+ are_denominations_valid (rc->connection,
+ awc.num_coins,
+ awc.denom_hs,
+ awc.coin_evs,
+ &awc.commitment.denom_serials,
+ &awc.commitment.amount_with_fee,
+ &mhd_ret))
break;
- }
- /* Run the transaction */
+ /* Now that amount_with_fee is calculated, verify the signature of
+ * the request body with the reserve key.
+ */
if (GNUNET_OK !=
- TEH_DB_run_transaction (rc->connection,
- "run age withdraw",
- TEH_MT_REQUEST_AGE_WITHDRAW,
- &mhd_ret,
- &age_withdraw_transaction,
- &awc))
+ verify_reserve_signature (rc->connection,
+ &awc.commitment,
+ &mhd_ret))
break;
- /* Clean up and send back final response */
- GNUNET_JSON_parse_free (spec);
+ /* Sign the chosen blinded coins, persist the commitment and
+ * charge the reserve.
+ * On error (like, insufficient funds), the client is notified.
+ * On conflict, the noreveal_index from the previous, existing
+ * commitment is returned to the client, returning success.
+ * Note that on success, there are two possible states:
+ * KYC is required (awc.kyc.ok == false) or
+ * age withdraw was successful.
+ */
+ if (GNUNET_OK !=
+ sign_and_do_age_withdraw (rc->connection,
+ &awc,
+ &mhd_ret))
+ break;
+ /* Send back final response, depending on the outcome of
+ * the DB-transaction */
if (! awc.kyc.ok)
- return TEH_RESPONSE_reply_kyc_required (rc->connection,
- &awc.h_payto,
- &awc.kyc);
+ mhd_ret = TEH_RESPONSE_reply_kyc_required (rc->connection,
+ &awc.h_payto,
+ &awc.kyc);
+ else
+ mhd_ret = reply_age_withdraw_success (rc->connection,
+ &awc.commitment.h_commitment,
+ awc.commitment.noreveal_index);
- return reply_age_withdraw_success (rc->connection,
- &awc.commitment.h_commitment,
- awc.commitment.noreveal_index);
} while(0);
GNUNET_JSON_parse_free (spec);
+ free_age_withdraw_context_resources (&awc);
return mhd_ret;
}
diff --git a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c
index d604632d9..9bb6ac401 100644
--- a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c
+++ b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c
@@ -19,10 +19,12 @@
* @author Özgür Kesim
*/
#include "platform.h"
+#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_util_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include "taler-exchange-httpd_metrics.h"
+#include "taler_error_codes.h"
#include "taler_exchangedb_plugin.h"
#include "taler_mhd_lib.h"
#include "taler-exchange-httpd_mhd.h"
@@ -50,95 +52,30 @@ struct AgeRevealContext
struct TALER_ReservePublicKeyP reserve_pub;
/**
- * Number of coins/denonations in the reveal
+ * Number of coins to reveal. MUST be equal to
+ * @e num_secrets/(kappa -1).
*/
uint32_t num_coins;
/**
- * #num_coins hashes of the denominations from which the coins are withdrawn.
- * Those must support age restriction.
+ * Number of secrets in the reveal. MUST be a multiple of (kappa-1).
*/
- struct TALER_DenominationHashP *denoms_h;
+ uint32_t num_secrets;
/**
- * #num_coins denomination keys, found in the system, according to denoms_h;
- */
- struct TEH_DenominationKey *denom_keys;
-
- /**
- * Total sum of all denominations' values
- **/
- struct TALER_Amount total_amount;
-
- /**
- * Total sum of all denominations' fees
- */
- struct TALER_Amount total_fee;
-
- /**
- * #num_coins hashes of blinded coin planchets.
- */
- struct TALER_BlindedPlanchet *coin_evs;
-
- /**
- * secrets for #num_coins*(kappa - 1) disclosed coins.
+ * @e num_secrets secrets for disclosed coins.
*/
struct TALER_PlanchetMasterSecretP *disclosed_coin_secrets;
/**
* The data from the original age-withdraw. Will be retrieved from
- * the DB via @a ach.
+ * the DB via @a ach and @a reserve_pub.
*/
- struct TALER_EXCHANGEDB_AgeWithdrawCommitment commitment;
+ struct TALER_EXCHANGEDB_AgeWithdraw *commitment;
};
/**
- * Information per planchet in the batch.
- */
-struct PlanchetContext
-{
-
- /**
- * Hash of the (blinded) message to be signed by the Exchange.
- */
- struct TALER_BlindedCoinHashP h_coin_envelope;
-
- /**
- * Value of the coin being exchanged (matching the denomination key)
- * plus the transaction fee. We include this in what is being
- * signed so that we can verify a reserve's remaining total balance
- * without needing to access the respective denomination key
- * information each time.
- */
- struct TALER_Amount amount_with_fee;
-
- /**
- * Blinded planchet.
- */
- struct TALER_BlindedPlanchet blinded_planchet;
-
- /**
- * Set to the resulting signed coin data to be returned to the client.
- */
- struct TALER_EXCHANGEDB_CollectableBlindcoin collectable;
-
-};
-
-/**
- * Helper function to free resources in the context
- */
-void
-age_reveal_context_free (struct AgeRevealContext *actx)
-{
- GNUNET_free (actx->denoms_h);
- GNUNET_free (actx->denom_keys);
- GNUNET_free (actx->coin_evs);
- GNUNET_free (actx->disclosed_coin_secrets);
-}
-
-
-/**
* Parse the json body of an '/age-withdraw/$ACH/reveal' request. It extracts
* the denomination hashes, blinded coins and disclosed coins and allocates
* memory for those.
@@ -154,50 +91,40 @@ age_reveal_context_free (struct AgeRevealContext *actx)
static enum GNUNET_GenericReturnValue
parse_age_withdraw_reveal_json (
struct MHD_Connection *connection,
- const json_t *j_denoms_h,
- const json_t *j_coin_evs,
const json_t *j_disclosed_coin_secrets,
struct AgeRevealContext *actx,
MHD_RESULT *mhd_ret)
{
enum GNUNET_GenericReturnValue result = GNUNET_SYSERR;
+ size_t num_entries;
/* Verify JSON-structure consistency */
{
const char *error = NULL;
- actx->num_coins = json_array_size (j_denoms_h); /* 0, if j_denoms_h is not an array */
+ num_entries = json_array_size (j_disclosed_coin_secrets); /* 0, if not an array */
- if (! json_is_array (j_denoms_h))
- error = "denoms_h must be an array";
- else if (! json_is_array (j_coin_evs))
- error = "coin_evs must be an array";
- else if (! json_is_array (j_disclosed_coin_secrets))
+ if (! json_is_array (j_disclosed_coin_secrets))
error = "disclosed_coin_secrets must be an array";
- else if (actx->num_coins == 0)
- error = "denoms_h must not be empty";
- else if (actx->num_coins != json_array_size (j_coin_evs))
- error = "denoms_h and coins_evs must be arrays of the same size";
- else if (actx->num_coins > TALER_MAX_FRESH_COINS)
- /**
- * The wallet had committed to more than the maximum coins allowed, the
- * reserve has been charged, but now the user can not withdraw any money
- * from it. Note that the user can't get their money back in this case!
- **/
+ else if (num_entries == 0)
+ error = "disclosed_coin_secrets must not be empty";
+ else if (num_entries > TALER_MAX_FRESH_COINS * (TALER_CNC_KAPPA - 1))
error = "maximum number of coins that can be withdrawn has been exceeded";
- else if (actx->num_coins * (TALER_CNC_KAPPA - 1)
- != json_array_size (j_disclosed_coin_secrets))
- error = "the size of array disclosed_coin_secrets must be "
- TALER_CNC_KAPPA_MINUS_ONE_STR " times the size of denoms_h";
+ else if (0 != num_entries % (TALER_CNC_KAPPA - 1))
+ error = "the size of disclosed_coin_secrets must be a multiple of "
+ TALER_CNC_KAPPA_MINUS_ONE_STR;
if (NULL != error)
{
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- error);
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ error);
return GNUNET_SYSERR;
}
+
+ actx->num_secrets = num_entries;
+ actx->num_coins = num_entries / (TALER_CNC_KAPPA - 1);
+
}
/* Continue parsing the parts */
@@ -205,78 +132,10 @@ parse_age_withdraw_reveal_json (
unsigned int idx = 0;
json_t *value = NULL;
- /* Parse denomination keys */
- actx->denoms_h = GNUNET_new_array (actx->num_coins,
- struct TALER_DenominationHashP);
-
- json_array_foreach (j_denoms_h, idx, value) {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto (NULL, &actx->denoms_h[idx]),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (value, spec, NULL, NULL))
- {
- char msg[256] = {0};
- GNUNET_snprintf (msg,
- sizeof(msg),
- "couldn't parse entry no. %d in array denoms_h",
- idx + 1);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- msg);
- goto EXIT;
- }
- };
-
- /* Parse blinded envelopes */
- actx->coin_evs = GNUNET_new_array (actx->num_coins,
- struct TALER_BlindedPlanchet);
-
- json_array_foreach (j_coin_evs, idx, value) {
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_blinded_planchet (NULL, &actx->coin_evs[idx]),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (value, spec, NULL, NULL))
- {
- char msg[256] = {0};
- GNUNET_snprintf (msg,
- sizeof(msg),
- "couldn't parse entry no. %d in array coin_evs",
- idx + 1);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- msg);
- goto EXIT;
- }
-
- /* Check for duplicate planchets */
- for (unsigned int i = 0; i < idx; i++)
- {
- if (0 == TALER_blinded_planchet_cmp (&actx->coin_evs[idx],
- &actx->coin_evs[i]))
- {
- GNUNET_break_op (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "duplicate planchet");
- goto EXIT;
- }
-
- }
- };
-
/* Parse diclosed keys */
- actx->disclosed_coin_secrets = GNUNET_new_array (
- actx->num_coins * (TALER_CNC_KAPPA - 1),
- struct TALER_PlanchetMasterSecretP);
+ actx->disclosed_coin_secrets =
+ GNUNET_new_array (num_entries,
+ struct TALER_PlanchetMasterSecretP);
json_array_foreach (j_disclosed_coin_secrets, idx, value) {
struct GNUNET_JSON_Specification spec[] = {
@@ -292,18 +151,15 @@ parse_age_withdraw_reveal_json (
sizeof(msg),
"couldn't parse entry no. %d in array disclosed_coin_secrets",
idx + 1);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- msg);
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ msg);
goto EXIT;
}
};
}
result = GNUNET_OK;
- *mhd_ret = MHD_YES;
-
EXIT:
return result;
@@ -328,256 +184,181 @@ find_original_commitment (
struct MHD_Connection *connection,
const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
const struct TALER_ReservePublicKeyP *reserve_pub,
- struct TALER_EXCHANGEDB_AgeWithdrawCommitment *commitment,
+ struct TALER_EXCHANGEDB_AgeWithdraw **commitment,
MHD_RESULT *result)
{
enum GNUNET_DB_QueryStatus qs;
- qs = TEH_plugin->get_age_withdraw_info (TEH_plugin->cls,
- reserve_pub,
- h_commitment,
- commitment);
- switch (qs)
+ for (unsigned int try = 0; try < 3; try++)
{
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- return GNUNET_OK; /* Only happy case */
+ qs = TEH_plugin->get_age_withdraw (TEH_plugin->cls,
+ reserve_pub,
+ h_commitment,
+ *commitment);
+ 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 (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_AGE_WITHDRAW_COMMITMENT_UNKNOWN,
- NULL);
- break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ *result = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_AGE_WITHDRAW_COMMITMENT_UNKNOWN,
+ NULL);
+ return GNUNET_SYSERR;
- case GNUNET_DB_STATUS_HARD_ERROR:
- *result = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_age_withdraw_info");
- break;
-
- case GNUNET_DB_STATUS_SOFT_ERROR:
- /* FIXME oec: Do we queue a result in this case or retry? */
- default:
- GNUNET_break (0);
- *result = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- NULL);
- }
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_age_withdraw_info");
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ break; /* try again */
+ default:
+ GNUNET_break (0);
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ }
+ /* after unsuccessfull retries*/
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_age_withdraw_info");
return GNUNET_SYSERR;
}
/**
- * Check if the given denomination is still or already valid, has not been
- * revoked and supports age restriction.
+ * @brief Derives a age-restricted planchet from a given secret and calculates the hash
*
- * @param connection HTTP-connection to the client
- * @param ksh The handle to the current state of (denomination) keys in the exchange
- * @param denom_h Hash of the denomination key to check
- * @param[out] dks On success, will contain the denomination key details
- * @param[out] result On failure, an MHD-response will be qeued and result will be set to accordingly
- * @return true on success (denomination valid), false otherwise
+ * @param connection Connection to the client
+ * @param ksh The denomination keys in memory
+ * @param secret The secret to a planchet
+ * @param denom_pub_h The hash of the denomination for the planchet
+ * @param max_age The maximum age allowed
+ * @param[out] hc Hashcode to write
+ * @param[out] result On error, a HTTP-response will be queued and result set accordingly
+ * @return GNUNET_OK on success, GNUNET_SYSERR otherwise, with an error message
+ * written to the client and @e result set.
*/
-static bool
-denomination_is_valid (
+static enum GNUNET_GenericReturnValue
+calculate_blinded_hash (
struct MHD_Connection *connection,
- struct TEH_KeyStateHandle *ksh,
- const struct TALER_DenominationHashP *denom_h,
- struct TEH_DenominationKey *dks,
+ const struct TEH_KeyStateHandle *keys,
+ const struct TALER_PlanchetMasterSecretP *secret,
+ const struct TALER_DenominationHashP *denom_pub_h,
+ uint8_t max_age,
+ struct TALER_BlindedCoinHashP *bch,
MHD_RESULT *result)
{
- dks = TEH_keys_denomination_by_hash2 (ksh,
- denom_h,
- connection,
- result);
- if (NULL == dks)
- {
- /* The denomination doesn't exist */
- GNUNET_assert (result != NULL);
- /* Note: a HTTP-response has been queued and result has been set by
- * TEH_keys_denominations_by_hash2 */
- return false;
- }
-
- if (GNUNET_TIME_absolute_is_past (dks->meta.expire_withdraw.abs_time))
- {
- /* This denomination is past the expiration time for withdraws */
- *result = TEH_RESPONSE_reply_expired_denom_pub_hash (
- connection,
- denom_h,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
- "age-withdraw_reveal");
- return false;
- }
-
- if (GNUNET_TIME_absolute_is_future (dks->meta.start.abs_time))
- {
- /* This denomination is not yet valid */
- *result = TEH_RESPONSE_reply_expired_denom_pub_hash (
- connection,
- denom_h,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
- "age-withdraw_reveal");
- return false;
- }
-
- if (dks->recoup_possible)
- {
- /* This denomination has been revoked */
- *result = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_GONE,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
- NULL);
- return false;
- }
+ enum GNUNET_GenericReturnValue ret;
+ struct TEH_DenominationKey *denom_key;
+ struct TALER_AgeCommitmentHash ach;
+
+ /* First, retrieve denomination details */
+ denom_key = TEH_keys_denomination_by_hash_from_state (keys,
+ denom_pub_h,
+ connection,
+ result);
+ if (NULL == denom_key)
+ return GNUNET_SYSERR;
- if (0 == dks->denom_pub.age_mask.bits)
+ /* calculate age commitment hash */
{
- /* This denomation does not support age restriction */
- char msg[256] = {0};
- GNUNET_snprintf (msg,
- sizeof(msg),
- "denomination %s does not support age restriction",
- GNUNET_h2s (&denom_h->hash));
-
- *result = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
- msg);
- return false;
- }
+ struct TALER_AgeCommitmentProof acp;
+ ret = TALER_age_restriction_from_secret (secret,
+ &denom_key->denom_pub.age_mask,
+ max_age,
+ &acp);
- return true;
-}
-
-
-/**
- * Check if the given array of hashes of denomination_keys a) belong
- * to valid denominations and b) those are marked as age restricted.
- *
- * @param connection The HTTP connection to the client
- * @param len The lengths of the array @a denoms_h
- * @param denoms_h array of hashes of denomination public keys
- * @param coin_evs array of blinded coin planchets
- * @param[out] dks On success, will be filled with the denomination keys. Caller must deallocate.
- * @param amount_with_fee The committed amount including fees
- * @param[out] total_amount On success, will contain the total sum of all denominations
- * @param[out] total_fee On success, will contain the total sum of all fees
- * @param[out] result In the error cases, a response will be queued with MHD and this will be the result.
- * @return GNUNET_OK if the denominations are valid and support age-restriction
- * GNUNET_SYSERR otherwise
- */
-static enum GNUNET_GenericReturnValue
-are_denominations_valid (
- struct MHD_Connection *connection,
- uint32_t len,
- const struct TALER_DenominationHashP *denoms_h,
- const struct TALER_BlindedPlanchet *coin_evs,
- struct TEH_DenominationKey **dks,
- const struct TALER_Amount *amount_with_fee,
- struct TALER_Amount *total_amount,
- struct TALER_Amount *total_fee,
- MHD_RESULT *result)
-{
- struct TEH_KeyStateHandle *ksh;
-
- GNUNET_assert (*dks == NULL);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break (0);
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, /* FIXME[oec]: better error code */
+ "derivation of age restriction failed");
+ return ret;
+ }
- ksh = TEH_keys_get_state ();
- if (NULL == ksh)
- {
- *result = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- NULL);
- return GNUNET_SYSERR;
+ TALER_age_commitment_hash (&acp.commitment, &ach);
}
- *dks = GNUNET_new_array (len, struct TEH_DenominationKey);
- TALER_amount_set_zero (TEH_currency, total_amount);
- TALER_amount_set_zero (TEH_currency, total_fee);
-
- for (uint32_t i = 0; i < len; i++)
+ /* Next: calculate planchet */
{
- if (! denomination_is_valid (connection,
- ksh,
- &denoms_h[i],
- dks[i],
- result))
- return GNUNET_SYSERR;
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_PlanchetDetail detail;
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ union TALER_DenominationBlindingKeyP bks;
+ struct TALER_ExchangeWithdrawValues alg_values = {
+ .cipher = denom_key->denom_pub.cipher,
+ };
- /* Ensure the ciphers from the planchets match the denominations' */
- if (dks[i]->denom_pub.cipher != coin_evs[i].cipher)
+ if (TALER_DENOMINATION_CS == alg_values.cipher)
{
- GNUNET_break_op (0);
- *result = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
- NULL);
- return GNUNET_SYSERR;
- }
+ struct TALER_CsNonce nonce;
- /* Accumulate the values */
- if (0 > TALER_amount_add (
- total_amount,
- total_amount,
- &dks[i]->meta.value))
- {
- GNUNET_break_op (0);
- *result = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_OVERFLOW,
- "amount");
- return GNUNET_SYSERR;
- }
+ TALER_cs_withdraw_nonce_derive (
+ secret,
+ &nonce);
- /* Accumulate the withdraw fees */
- if (0 > TALER_amount_add (
- total_fee,
- total_fee,
- &dks[i]->meta.fees.withdraw))
- {
- GNUNET_break_op (0);
- *result = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_OVERFLOW,
- "fee");
- return GNUNET_SYSERR;
+ {
+ enum TALER_ErrorCode ec;
+ struct TEH_CsDeriveData cdd = {
+ .h_denom_pub = &denom_key->h_denom_pub,
+ .nonce = &nonce,
+ };
+
+ ec = TEH_keys_denomination_cs_r_pub (&cdd,
+ false,
+ &alg_values.details.
+ cs_values);
+ /* FIXME Handle error? */
+ GNUNET_assert (TALER_EC_NONE == ec);
+ }
}
- }
- /* Compare the committed amount against the totals */
- {
- struct TALER_Amount sum;
- TALER_amount_set_zero (TEH_currency, &sum);
+ TALER_planchet_blinding_secret_create (secret,
+ &alg_values,
+ &bks);
+
+ TALER_planchet_setup_coin_priv (secret,
+ &alg_values,
+ &coin_priv);
- GNUNET_assert (0 < TALER_amount_add (
- &sum,
- total_amount,
- total_fee));
+ ret = TALER_planchet_prepare (&denom_key->denom_pub,
+ &alg_values,
+ &bks,
+ &coin_priv,
+ &ach,
+ &c_hash,
+ &detail);
- if (0 != TALER_amount_cmp (&sum, amount_with_fee))
+ if (GNUNET_OK != ret)
{
- GNUNET_break_op (0);
- *result = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_INCORRECT,
- NULL);
- return GNUNET_SYSERR;
+ GNUNET_break (0);
+ *result = TALER_MHD_reply_json_pack (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ "{ss}",
+ "details",
+ "failed to prepare planchet from base key");
+ return ret;
}
+
+ ret = TALER_coin_ev_hash (&detail.blinded_planchet,
+ &denom_key->h_denom_pub,
+ bch);
+ GNUNET_assert (GNUNET_OK == ret);
}
- return GNUNET_OK;
+ return GNUNET_SYSERR;
}
/**
- * Checks the validity of the disclosed coins as follows:
+ * @brief Checks the validity of the disclosed coins as follows:
* - Derives and calculates the disclosed coins'
* - public keys,
* - nonces (if applicable),
@@ -594,163 +375,83 @@ are_denominations_valid (
* https://docs.taler.net/design-documents/024-age-restriction.html#withdraw
*
* @param connection HTTP-connection to the client
- * @param h_commitment_orig Original commitment
- * @param max_age Maximum age allowed for the age restriction
- * @param noreveal_idx Index that was given to the client in response to the age-withdraw request
- * @param num_coins Number of coins
- * @param coin_evs The blindet planchets of the undisclosed coins, @a num_coins many
- * @param denom_keys The array of denomination keys, @a num_coins. Needed to detect Clause-Schnorr-based denominations
+ * @param commitment Original commitment
* @param disclosed_coin_secrets The secrets of the disclosed coins, (TALER_CNC_KAPPA - 1)*num_coins many
+ * @param num_coins number of coins to reveal via @a disclosed_coin_secrets
* @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_and_max_age (
struct MHD_Connection *connection,
- const struct TALER_AgeWithdrawCommitmentHashP *h_commitment_orig,
- const uint32_t max_age,
- const uint32_t noreveal_idx,
- const uint32_t num_coins,
- const struct TALER_BlindedPlanchet *coin_evs,
- const struct TEH_DenominationKey *denom_keys,
+ const struct TALER_EXCHANGEDB_AgeWithdraw *commitment,
const struct TALER_PlanchetMasterSecretP *disclosed_coin_secrets,
+ uint32_t num_coins,
MHD_RESULT *result)
{
enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
struct GNUNET_HashContext *hash_context;
+ struct TEH_KeyStateHandle *keys;
+
+ if (num_coins != commitment->num_coins)
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "#coins");
+ return GNUNET_SYSERR;
+ }
+
+ /* We need the current keys in memory for the meta-data of the denominations */
+ keys = TEH_keys_get_state ();
+ if (NULL == keys)
+ {
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ return GNUNET_SYSERR;
+ }
hash_context = GNUNET_CRYPTO_hash_context_start ();
- for (size_t c = 0; c < num_coins; c++)
+ for (size_t coin_idx = 0; coin_idx < num_coins; coin_idx++)
{
- size_t k = 0; /* either 0 or 1, to index into coin_evs */
+ size_t i = 0; /* either 0 or 1, to index into coin_evs */
- for (size_t idx = 0; idx<TALER_CNC_KAPPA; idx++)
+ for (size_t gamma = 0; gamma<TALER_CNC_KAPPA; gamma++)
{
- if (idx == (size_t) noreveal_idx)
+ if (gamma == (size_t) commitment->noreveal_index)
{
GNUNET_CRYPTO_hash_context_read (hash_context,
- &coin_evs[c],
- sizeof(coin_evs[c]));
+ &commitment->h_coin_evs[coin_idx],
+ sizeof(commitment->h_coin_evs[coin_idx]));
}
else
{
- /* FIXME[oec] Refactor this block out into its own function */
-
- size_t j = (TALER_CNC_KAPPA - 1) * c + k; /* Index into disclosed_coin_secrets[] */
+ /* j is the index into disclosed_coin_secrets[] */
+ size_t j = (TALER_CNC_KAPPA - 1) * coin_idx + i;
const struct TALER_PlanchetMasterSecretP *secret;
- struct TALER_AgeCommitmentHash ach;
struct TALER_BlindedCoinHashP bch;
- GNUNET_assert (k<2);
+ GNUNET_assert (i<2);
GNUNET_assert ((TALER_CNC_KAPPA - 1) * num_coins > j);
secret = &disclosed_coin_secrets[j];
- k++;
+ i++;
- /* First: calculate age commitment hash */
- {
- struct TALER_AgeCommitmentProof acp;
- ret = TALER_age_restriction_from_secret (
- secret,
- &denom_keys[c].denom_pub.age_mask,
- max_age,
- &acp);
-
- if (GNUNET_OK != ret)
- {
- GNUNET_break (0);
- *result = TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- "{sssi}",
- "failed to derive age restriction from base key",
- "index",
- j);
- return ret;
- }
-
- TALER_age_commitment_hash (&acp.commitment, &ach);
- }
+ ret = calculate_blinded_hash (connection,
+ keys,
+ secret,
+ &commitment->denom_pub_hashes[coin_idx],
+ commitment->max_age,
+ &bch,
+ result);
- /* Next: calculate planchet */
+ if (GNUNET_OK != ret)
{
- struct TALER_CoinPubHashP c_hash;
- struct TALER_PlanchetDetail detail;
- struct TALER_CoinSpendPrivateKeyP coin_priv;
- union TALER_DenominationBlindingKeyP bks;
- struct TALER_ExchangeWithdrawValues alg_values = {
- .cipher = denom_keys[c].denom_pub.cipher,
- };
-
- if (TALER_DENOMINATION_CS == alg_values.cipher)
- {
- struct TALER_CsNonce nonce;
-
- TALER_cs_withdraw_nonce_derive (
- secret,
- &nonce);
-
- {
- enum TALER_ErrorCode ec;
- struct TEH_CsDeriveData cdd = {
- .h_denom_pub = &denom_keys[c].h_denom_pub,
- .nonce = &nonce,
- };
-
- ec = TEH_keys_denomination_cs_r_pub (&cdd,
- false,
- &alg_values.details.
- cs_values);
- /* FIXME Handle error? */
- GNUNET_assert (TALER_EC_NONE == ec);
- }
- }
-
- TALER_planchet_blinding_secret_create (secret,
- &alg_values,
- &bks);
-
- TALER_planchet_setup_coin_priv (secret,
- &alg_values,
- &coin_priv);
-
- ret = TALER_planchet_prepare (&denom_keys[c].denom_pub,
- &alg_values,
- &bks,
- &coin_priv,
- &ach,
- &c_hash,
- &detail);
-
- if (GNUNET_OK != ret)
- {
- GNUNET_break (0);
- *result = TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- "{sssi}",
- "details",
- "failed to prepare planchet from base key",
- "index",
- j);
- return ret;
- }
-
- ret = TALER_coin_ev_hash (&detail.blinded_planchet,
- &denom_keys[c].h_denom_pub,
- &bch);
- if (GNUNET_OK != ret)
- {
- GNUNET_break (0);
- *result = TALER_MHD_reply_json_pack (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- "{sssi}",
- "details",
- "failed to hash planchet from base key",
- "index",
- j);
- return ret;
- }
-
+ GNUNET_CRYPTO_hash_context_abort (hash_context);
+ return GNUNET_SYSERR;
}
/* Continue the running hash of all coin hashes with the calculated
@@ -768,7 +469,7 @@ verify_commitment_and_max_age (
GNUNET_CRYPTO_hash_context_finish (hash_context,
&calc_hash);
- if (0 != GNUNET_CRYPTO_hash_cmp (&h_commitment_orig->hash,
+ if (0 != GNUNET_CRYPTO_hash_cmp (&commitment->h_commitment.hash,
&calc_hash))
{
GNUNET_break_op (0);
@@ -788,26 +489,22 @@ verify_commitment_and_max_age (
* @brief Send a response for "/age-withdraw/$RCH/reveal"
*
* @param connection The http connection to the client to send the response to
- * @param num_coins Number of new coins with age restriction for which we reveal data
- * @param awrcs array of @a num_coins signatures revealed
+ * @param commitment The data from the commitment with signatures
* @return a MHD result code
*/
static MHD_RESULT
reply_age_withdraw_reveal_success (
struct MHD_Connection *connection,
- unsigned int num_coins,
- const struct TALER_EXCHANGEDB_AgeWithdrawRevealedCoin *awrcs)
+ const struct TALER_EXCHANGEDB_AgeWithdraw *commitment)
{
json_t *list = json_array ();
GNUNET_assert (NULL != list);
- for (unsigned int index = 0;
- index < num_coins;
- index++)
+ for (unsigned int i = 0; i < commitment->num_coins; i++)
{
json_t *obj = GNUNET_JSON_PACK (
TALER_JSON_pack_blinded_denom_sig ("ev_sig",
- &awrcs[index].coin_sig));
+ &commitment->denom_sigs[i]));
GNUNET_assert (0 ==
json_array_append_new (list,
obj));
@@ -821,160 +518,6 @@ reply_age_withdraw_reveal_success (
}
-/**
- * @brief Signs and persists the undisclosed coins
- *
- * @param connection HTTP-connection to the client
- * @param h_commitment Original commitment
- * @param num_coins Number of coins
- * @param coin_evs The Hashes of the undisclosed, blinded coins, @a num_coins many
- * @param denom_keys The array of denomination keys, @a num_coins. Needed to detect Clause-Schnorr-based denominations
- * @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
-sign_and_finalize_age_withdraw (
- struct MHD_Connection *connection,
- const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
- const uint32_t num_coins,
- const struct TALER_BlindedPlanchet *coin_evs,
- const struct TEH_DenominationKey *denom_keys,
- MHD_RESULT *result)
-{
- enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
- struct TEH_CoinSignData csds[num_coins];
- struct TALER_BlindedDenominationSignature bds[num_coins];
- struct TALER_EXCHANGEDB_AgeWithdrawRevealedCoin awrcs[num_coins];
- enum GNUNET_DB_QueryStatus qs;
-
- for (uint32_t i = 0; i<num_coins; i++)
- {
- csds[i].h_denom_pub = &denom_keys[i].h_denom_pub;
- csds[i].bp = &coin_evs[i];
- }
-
- /* Sign the the blinded coins first */
- {
- enum TALER_ErrorCode ec;
- ec = TEH_keys_denomination_batch_sign (csds,
- num_coins,
- false,
- bds);
- if (TALER_EC_NONE != ec)
- {
- GNUNET_break (0);
- *result = TALER_MHD_reply_with_ec (connection,
- ec,
- NULL);
- return GNUNET_SYSERR;
- }
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Signatures ready, starting DB interaction\n");
-
- /* Prepare the data for insertion */
- for (uint32_t i = 0; i<num_coins; i++)
- {
- TALER_coin_ev_hash (&coin_evs[i],
- csds[i].h_denom_pub,
- &awrcs[i].h_coin_ev);
- awrcs[i].h_denom_pub = *csds[i].h_denom_pub;
- awrcs[i].coin_sig = bds[i];
- }
-
- /* Persist operation result in DB, transactionally */
- for (unsigned int r = 0; r < MAX_TRANSACTION_COMMIT_RETRIES; r++)
- {
- bool changed = false;
-
- /* Transaction start */
- if (GNUNET_OK !=
- TEH_plugin->start (TEH_plugin->cls,
- "insert_age_withdraw_reveal batch"))
- {
- GNUNET_break (0);
- ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_START_FAILED,
- NULL);
- goto cleanup;
- }
-
- qs = TEH_plugin->insert_age_withdraw_reveal (TEH_plugin->cls,
- h_commitment,
- num_coins,
- awrcs);
-
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- TEH_plugin->rollback (TEH_plugin->cls);
- continue;
- }
- else if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- GNUNET_break (0);
- TEH_plugin->rollback (TEH_plugin->cls);
- ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "insert_age_withdraw_reveal");
- goto cleanup;
- }
-
- changed = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
-
- /* Commit the transaction */
- qs = TEH_plugin->commit (TEH_plugin->cls);
- if (qs >= 0)
- {
- if (changed)
- TEH_METRICS_num_success[TEH_MT_SUCCESS_AGE_WITHDRAW_REVEAL]++;
-
- break; /* success */
-
- }
- else if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- GNUNET_break (0);
- TEH_plugin->rollback (TEH_plugin->cls);
- ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_COMMIT_FAILED,
- NULL);
- goto cleanup;
- }
- else
- {
- TEH_plugin->rollback (TEH_plugin->cls);
- }
- } /* end of retry */
-
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- {
- GNUNET_break (0);
- TEH_plugin->rollback (TEH_plugin->cls);
- ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_SOFT_FAILURE,
- NULL);
- goto cleanup;
- }
-
- /* Generate final (positive) response */
- ret = reply_age_withdraw_reveal_success (connection,
- num_coins,
- awrcs);
-cleanup:
- GNUNET_break (GNUNET_OK != ret);
-
- /* Free resources */
- for (unsigned int i = 0; i<num_coins; i++)
- TALER_blinded_denom_sig_free (&awrcs[i].coin_sig);
- return ret;
-}
-
-
MHD_RESULT
TEH_handler_age_withdraw_reveal (
struct TEH_RequestContext *rc,
@@ -984,16 +527,10 @@ TEH_handler_age_withdraw_reveal (
MHD_RESULT result = MHD_NO;
enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
struct AgeRevealContext actx = {0};
- const json_t *j_denoms_h;
- const json_t *j_coin_evs;
const json_t *j_disclosed_coin_secrets;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("reserve_pub",
&actx.reserve_pub),
- GNUNET_JSON_spec_array_const ("denoms_h",
- &j_denoms_h),
- GNUNET_JSON_spec_array_const ("coin_evs",
- &j_coin_evs),
GNUNET_JSON_spec_array_const ("disclosed_coin_secrets",
&j_disclosed_coin_secrets),
GNUNET_JSON_spec_end ()
@@ -1017,8 +554,6 @@ TEH_handler_age_withdraw_reveal (
if (GNUNET_OK !=
parse_age_withdraw_reveal_json (
rc->connection,
- j_denoms_h,
- j_coin_evs,
j_disclosed_coin_secrets,
&actx,
&result))
@@ -1034,49 +569,24 @@ TEH_handler_age_withdraw_reveal (
&result))
break;
- /* Ensure validity of denoms and the sum of amounts and fees */
- if (GNUNET_OK !=
- are_denominations_valid (
- rc->connection,
- actx.num_coins,
- actx.denoms_h,
- actx.coin_evs,
- &actx.denom_keys,
- &actx.commitment.amount_with_fee,
- &actx.total_amount,
- &actx.total_fee,
- &result))
- break;
-
/* Verify the computed h_commitment equals the committed one and that coins
* have a maximum age group corresponding max_age (age-mask dependent) */
if (GNUNET_OK !=
verify_commitment_and_max_age (
rc->connection,
- &actx.commitment.h_commitment,
- actx.commitment.max_age,
- actx.commitment.noreveal_index,
- actx.num_coins,
- actx.coin_evs,
- actx.denom_keys,
+ actx.commitment,
actx.disclosed_coin_secrets,
- &result))
- break;
-
- /* Finally, sign and persist the coins */
- if (GNUNET_OK !=
- sign_and_finalize_age_withdraw (
- rc->connection,
- &actx.commitment.h_commitment,
actx.num_coins,
- actx.coin_evs,
- actx.denom_keys,
&result))
break;
+ /* Finally, return the signatures */
+ result = reply_age_withdraw_reveal_success (rc->connection,
+ actx.commitment);
+
} while(0);
- age_reveal_context_free (&actx);
+ GNUNET_free (actx.disclosed_coin_secrets);
return result;
}
diff --git a/src/exchange/taler-exchange-httpd_batch-withdraw.c b/src/exchange/taler-exchange-httpd_batch-withdraw.c
index 04a9d7208..7455778ad 100644
--- a/src/exchange/taler-exchange-httpd_batch-withdraw.c
+++ b/src/exchange/taler-exchange-httpd_batch-withdraw.c
@@ -26,12 +26,14 @@
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <jansson.h>
+#include "taler-exchange-httpd.h"
#include "taler_json_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h"
#include "taler-exchange-httpd_batch-withdraw.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_keys.h"
+#include "taler_util.h"
/**
@@ -306,8 +308,10 @@ batch_withdraw_transaction (void *cls,
struct BatchWithdrawContext *wc = cls;
uint64_t ruuid;
enum GNUNET_DB_QueryStatus qs;
- bool balance_ok = false;
bool found = false;
+ bool balance_ok = false;
+ bool age_ok = false;
+ uint16_t required_age = 0;
char *kyc_required;
struct TALER_PaytoHashP reserve_h_payto;
@@ -471,9 +475,11 @@ batch_withdraw_transaction (void *cls,
wc->now,
wc->reserve_pub,
&wc->batch_total,
- /* TODO[oec]: add parameter for maximum age and [out]parameter for required age */
+ TEH_age_restriction_enabled,
&found,
&balance_ok,
+ &age_ok,
+ &required_age,
&ruuid);
if (0 > qs)
{
@@ -496,7 +502,20 @@ batch_withdraw_transaction (void *cls,
return GNUNET_DB_STATUS_HARD_ERROR;
}
- /* TODO[oec]: add error handling for age restriction requirements */
+ if (! age_ok)
+ {
+ /* We respond with the lowest age in the corresponding age group
+ * of the required age */
+ uint16_t lowest_age = TALER_get_lowest_age (
+ &TEH_age_restriction_config.mask,
+ required_age);
+
+ TEH_plugin->rollback (TEH_plugin->cls);
+ *mhd_ret = TEH_RESPONSE_reply_reserve_age_restriction_required (
+ connection,
+ lowest_age);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
if (! balance_ok)
{
@@ -722,10 +741,12 @@ parse_planchets (const struct TEH_RequestContext *rc,
struct PlanchetContext *pc = &wc->planchets[i];
struct TEH_DenominationKey *dk;
- dk = TEH_keys_denomination_by_hash2 (ksh,
- &pc->collectable.denom_pub_hash,
- NULL,
- NULL);
+ dk = TEH_keys_denomination_by_hash_from_state (
+ ksh,
+ &pc->collectable.denom_pub_hash,
+ NULL,
+ NULL);
+
if (NULL == dk)
{
if (! check_request_idempotent (wc,
diff --git a/src/exchange/taler-exchange-httpd_csr.c b/src/exchange/taler-exchange-httpd_csr.c
index 3ceb319cd..64892d363 100644
--- a/src/exchange/taler-exchange-httpd_csr.c
+++ b/src/exchange/taler-exchange-httpd_csr.c
@@ -127,10 +127,10 @@ TEH_handler_csr_melt (struct TEH_RequestContext *rc,
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
NULL);
}
- dk = TEH_keys_denomination_by_hash2 (ksh,
- denom_pub_hash,
- NULL,
- NULL);
+ dk = TEH_keys_denomination_by_hash_from_state (ksh,
+ denom_pub_hash,
+ NULL,
+ NULL);
if (NULL == dk)
{
return TEH_RESPONSE_reply_unknown_denom_pub_hash (
@@ -262,10 +262,10 @@ TEH_handler_csr_withdraw (struct TEH_RequestContext *rc,
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
NULL);
}
- dk = TEH_keys_denomination_by_hash2 (ksh,
- &denom_pub_hash,
- NULL,
- NULL);
+ dk = TEH_keys_denomination_by_hash_from_state (ksh,
+ &denom_pub_hash,
+ NULL,
+ NULL);
if (NULL == dk)
{
return TEH_RESPONSE_reply_unknown_denom_pub_hash (
diff --git a/src/exchange/taler-exchange-httpd_extensions.c b/src/exchange/taler-exchange-httpd_extensions.c
index 48c3f4a94..68cc2da55 100644
--- a/src/exchange/taler-exchange-httpd_extensions.c
+++ b/src/exchange/taler-exchange-httpd_extensions.c
@@ -148,7 +148,7 @@ extension_update_event_cb (void *cls,
TEH_age_restriction_enabled = false;
if (NULL != conf)
{
- TEH_age_restriction_enabled = true;
+ TEH_age_restriction_enabled = extension->enabled;
TEH_age_restriction_config = *conf;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"[age restriction] DB event has changed the config to %s with mask: %s\n",
diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c
index b39093ec1..e53f27327 100644
--- a/src/exchange/taler-exchange-httpd_keys.c
+++ b/src/exchange/taler-exchange-httpd_keys.c
@@ -1364,7 +1364,6 @@ denomination_info_cb (
&dk->h_denom_pub.hash,
dk,
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
-
}
@@ -1753,7 +1752,7 @@ wallet_threshold_cb (void *cls,
*
* @param[in,out] ksh key state handle we build @a krd for
* @param[in] denom_keys_hash hash over all the denomination keys in @a denoms
- * @param last_cpd timestamp to use
+ * @param last_cherry_pick_date timestamp to use
* @param signkeys list of sign keys to return
* @param recoup list of revoked keys to return
* @param denoms list of denominations to return
@@ -1764,7 +1763,7 @@ wallet_threshold_cb (void *cls,
static enum GNUNET_GenericReturnValue
create_krd (struct TEH_KeyStateHandle *ksh,
const struct GNUNET_HashCode *denom_keys_hash,
- struct GNUNET_TIME_Timestamp last_cpd,
+ struct GNUNET_TIME_Timestamp last_cherry_pick_date,
json_t *signkeys,
json_t *recoup,
json_t *denoms,
@@ -1778,7 +1777,8 @@ create_krd (struct TEH_KeyStateHandle *ksh,
struct TALER_ExchangeSignatureP grouped_exchange_sig;
json_t *keys;
- GNUNET_assert (! GNUNET_TIME_absolute_is_zero (last_cpd.abs_time));
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ last_cherry_pick_date.abs_time));
GNUNET_assert (NULL != signkeys);
GNUNET_assert (NULL != recoup);
GNUNET_assert (NULL != denoms);
@@ -1788,7 +1788,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
GNUNET_assert (NULL != TEH_currency);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Creating /keys at cherry pick date %s\n",
- GNUNET_TIME_timestamp2s (last_cpd));
+ GNUNET_TIME_timestamp2s (last_cherry_pick_date));
/* Sign hash over denomination keys */
{
@@ -1799,7 +1799,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
TALER_exchange_online_key_set_sign (
&TEH_keys_exchange_sign2_,
ksh,
- last_cpd,
+ last_cherry_pick_date,
denom_keys_hash,
&exchange_pub,
&exchange_sig)))
@@ -1820,7 +1820,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
TALER_exchange_online_key_set_sign (
&TEH_keys_exchange_sign2_,
ksh,
- last_cpd,
+ last_cherry_pick_date,
h_grouped,
&grouped_exchange_pub,
&grouped_exchange_sig)))
@@ -1876,7 +1876,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
GNUNET_JSON_pack_array_incref ("global_fees",
ksh->global_fees),
GNUNET_JSON_pack_timestamp ("list_issue_date",
- last_cpd),
+ last_cherry_pick_date),
GNUNET_JSON_pack_data_auto ("eddsa_pub",
&exchange_pub),
GNUNET_JSON_pack_data_auto ("eddsa_sig",
@@ -2034,7 +2034,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
etag));
krd.etag = GNUNET_strdup (etag);
}
- krd.cherry_pick_date = last_cpd;
+ krd.cherry_pick_date = last_cherry_pick_date;
GNUNET_array_append (ksh->krd_array,
ksh->krd_array_length,
krd);
@@ -2054,14 +2054,17 @@ create_krd (struct TEH_KeyStateHandle *ksh,
static enum GNUNET_GenericReturnValue
finish_keys_response (struct TEH_KeyStateHandle *ksh)
{
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
json_t *recoup;
struct SignKeyCtx sctx;
json_t *denoms = NULL;
json_t *grouped_denominations = NULL;
- struct GNUNET_TIME_Timestamp last_cpd;
+ struct GNUNET_TIME_Timestamp last_cherry_pick_date;
struct GNUNET_CONTAINER_Heap *heap;
struct GNUNET_HashContext *hash_context = NULL;
struct GNUNET_HashCode grouped_hash_xor = {0};
+ /* Remember if we have any denomination with age restriction */
+ bool has_age_restricted_denomination = false;
sctx.signkeys = json_array ();
GNUNET_assert (NULL != sctx.signkeys);
@@ -2094,7 +2097,7 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
grouped_denominations = json_array ();
GNUNET_assert (NULL != grouped_denominations);
- last_cpd = GNUNET_TIME_UNIT_ZERO_TS;
+ last_cherry_pick_date = GNUNET_TIME_UNIT_ZERO_TS;
// FIXME: This block contains the implementation of the DEPRECATED
// "denom_pubs" array along with the new grouped "denominations".
@@ -2124,13 +2127,13 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
/* heap = min heap, sorted by start time */
while (NULL != (dk = GNUNET_CONTAINER_heap_remove_root (heap)))
{
- if (GNUNET_TIME_timestamp_cmp (last_cpd,
+ if (GNUNET_TIME_timestamp_cmp (last_cherry_pick_date,
!=,
dk->meta.start) &&
- (! GNUNET_TIME_absolute_is_zero (last_cpd.abs_time)) )
+ (! GNUNET_TIME_absolute_is_zero (last_cherry_pick_date.abs_time)) )
{
/*
- * This is not the first entry in the heap (because last_cpd !=
+ * This is not the first entry in the heap (because last_cherry_pick_date !=
* GNUNET_TIME_UNIT_ZERO_TS) and the previous entry had a different
* start time. Therefore, we create a new entry in ksh.
*/
@@ -2143,7 +2146,7 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
if (GNUNET_OK !=
create_krd (ksh,
&hc,
- last_cpd,
+ last_cherry_pick_date,
sctx.signkeys,
recoup,
denoms,
@@ -2152,21 +2155,17 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to generate key response data for %s\n",
- GNUNET_TIME_timestamp2s (last_cpd));
+ GNUNET_TIME_timestamp2s (last_cherry_pick_date));
GNUNET_CRYPTO_hash_context_abort (hash_context);
/* drain heap before destroying it */
while (NULL != (dk = GNUNET_CONTAINER_heap_remove_root (heap)))
/* intentionally empty */;
GNUNET_CONTAINER_heap_destroy (heap);
- json_decref (denoms);
- json_decref (grouped_denominations);
- json_decref (sctx.signkeys);
- json_decref (recoup);
- return GNUNET_SYSERR;
+ goto CLEANUP;
}
}
- last_cpd = dk->meta.start;
+ last_cherry_pick_date = dk->meta.start;
{
json_t *denom;
@@ -2264,7 +2263,11 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
int r = json_object_set_new (group->json,
"age_mask",
json_integer (meta.age_mask.bits));
+
GNUNET_assert (0 == r);
+
+ /* Remember that we have found at least _one_ age restricted denomination */
+ has_age_restricted_denomination = true;
}
/* Create a new array for the denominations in this group */
@@ -2386,7 +2389,7 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
}
GNUNET_CONTAINER_heap_destroy (heap);
- if (! GNUNET_TIME_absolute_is_zero (last_cpd.abs_time))
+ if (! GNUNET_TIME_absolute_is_zero (last_cherry_pick_date.abs_time))
{
struct GNUNET_HashCode hc;
@@ -2395,7 +2398,7 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
if (GNUNET_OK !=
create_krd (ksh,
&hc,
- last_cpd,
+ last_cherry_pick_date,
sctx.signkeys,
recoup,
denoms,
@@ -2404,14 +2407,25 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to generate key response data for %s\n",
- GNUNET_TIME_timestamp2s (last_cpd));
- json_decref (denoms);
- json_decref (grouped_denominations);
- json_decref (sctx.signkeys);
- json_decref (recoup);
- return GNUNET_SYSERR;
+ GNUNET_TIME_timestamp2s (last_cherry_pick_date));
+ goto CLEANUP;
}
ksh->management_only = false;
+
+ /* Sanity check: Make sure that age restriction is enabled IFF at least
+ * one age restricted denomination exist */
+ if (! has_age_restricted_denomination && TEH_age_restriction_enabled)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Age restriction is enabled, but NO denominations with age restriction found!\n");
+ goto CLEANUP;
+ }
+ else if (has_age_restricted_denomination && ! TEH_age_restriction_enabled)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Age restriction is NOT enabled, but denominations with age restriction found!\n");
+ goto CLEANUP;
+ }
}
else
{
@@ -2419,11 +2433,15 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
"No denomination keys available. Refusing to generate /keys response.\n");
GNUNET_CRYPTO_hash_context_abort (hash_context);
}
+
+ ret = GNUNET_OK;
+
+CLEANUP:
json_decref (grouped_denominations);
json_decref (sctx.signkeys);
json_decref (recoup);
json_decref (denoms);
- return GNUNET_OK;
+ return ret;
}
@@ -2733,16 +2751,16 @@ TEH_keys_denomination_by_hash (
return NULL;
}
- return TEH_keys_denomination_by_hash2 (ksh,
- h_denom_pub,
- conn,
- mret);
+ return TEH_keys_denomination_by_hash_from_state (ksh,
+ h_denom_pub,
+ conn,
+ mret);
}
struct TEH_DenominationKey *
-TEH_keys_denomination_by_hash2 (
- struct TEH_KeyStateHandle *ksh,
+TEH_keys_denomination_by_hash_from_state (
+ const struct TEH_KeyStateHandle *ksh,
const struct TALER_DenominationHashP *h_denom_pub,
struct MHD_Connection *conn,
MHD_RESULT *mret)
diff --git a/src/exchange/taler-exchange-httpd_keys.h b/src/exchange/taler-exchange-httpd_keys.h
index 8758afb71..d4a578127 100644
--- a/src/exchange/taler-exchange-httpd_keys.h
+++ b/src/exchange/taler-exchange-httpd_keys.h
@@ -234,8 +234,8 @@ TEH_keys_denomination_by_hash (
* or NULL if @a h_denom_pub could not be found
*/
struct TEH_DenominationKey *
-TEH_keys_denomination_by_hash2 (
- struct TEH_KeyStateHandle *ksh,
+TEH_keys_denomination_by_hash_from_state (
+ const struct TEH_KeyStateHandle *ksh,
const struct TALER_DenominationHashP *h_denom_pub,
struct MHD_Connection *conn,
MHD_RESULT *mret);
diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.c b/src/exchange/taler-exchange-httpd_refreshes_reveal.c
index 47926a740..4fb164077 100644
--- a/src/exchange/taler-exchange-httpd_refreshes_reveal.c
+++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.c
@@ -511,7 +511,7 @@ resolve_refreshes_reveal_denominations (
}
}
- old_dk = TEH_keys_denomination_by_hash2 (
+ old_dk = TEH_keys_denomination_by_hash_from_state (
ksh,
&rctx->melt.session.coin.denom_pub_hash,
connection,
@@ -536,10 +536,10 @@ resolve_refreshes_reveal_denominations (
-1);
if (GNUNET_OK != res)
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
- dks[i] = TEH_keys_denomination_by_hash2 (ksh,
- &rrcs[i].h_denom_pub,
- connection,
- &ret);
+ dks[i] = TEH_keys_denomination_by_hash_from_state (ksh,
+ &rrcs[i].h_denom_pub,
+ connection,
+ &ret);
if (NULL == dks[i])
return ret;
if ( (TALER_DENOMINATION_CS == dks[i]->denom_pub.cipher) &&
diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c
index 835a47713..d1636a0af 100644
--- a/src/exchange/taler-exchange-httpd_responses.c
+++ b/src/exchange/taler-exchange-httpd_responses.c
@@ -1085,6 +1085,20 @@ TEH_RESPONSE_reply_reserve_insufficient_balance (
MHD_RESULT
+TEH_RESPONSE_reply_reserve_age_restriction_required (
+ struct MHD_Connection *connection,
+ uint16_t maximum_allowed_age)
+{
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_JSON_pack_ec (TALER_EC_EXCHANGE_RESERVES_AGE_RESTRICTION_REQUIRED),
+ GNUNET_JSON_pack_uint64 ("maximum_allowed_age",
+ maximum_allowed_age));
+}
+
+
+MHD_RESULT
TEH_RESPONSE_reply_purse_created (
struct MHD_Connection *connection,
struct GNUNET_TIME_Timestamp exchange_timestamp,
diff --git a/src/exchange/taler-exchange-httpd_responses.h b/src/exchange/taler-exchange-httpd_responses.h
index 0db6968f8..a57fa495e 100644
--- a/src/exchange/taler-exchange-httpd_responses.h
+++ b/src/exchange/taler-exchange-httpd_responses.h
@@ -75,6 +75,20 @@ TEH_RESPONSE_reply_reserve_insufficient_balance (
const struct TALER_Amount *balance_required,
const struct TALER_ReservePublicKeyP *reserve_pub);
+/**
+ * Return error message indicating that a reserve requires age
+ * restriction to be set during withdraw, that is: the age-withdraw
+ * protocol MUST be used with commitment to an admissible age.
+ *
+ * @param connection connection to the client
+ * @param maximum_allowed_age the balance required for the operation
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_reserve_age_restriction_required (
+ struct MHD_Connection *connection,
+ uint16_t maximum_allowed_age);
+
/**
* Send information that a KYC check must be
diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c
index 28addba43..9c8a405cc 100644
--- a/src/exchange/taler-exchange-httpd_withdraw.c
+++ b/src/exchange/taler-exchange-httpd_withdraw.c
@@ -488,10 +488,13 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
GNUNET_JSON_parse_free (spec);
return mret;
}
- dk = TEH_keys_denomination_by_hash2 (ksh,
- &wc.collectable.denom_pub_hash,
- NULL,
- NULL);
+
+ dk = TEH_keys_denomination_by_hash_from_state (
+ ksh,
+ &wc.collectable.denom_pub_hash,
+ NULL,
+ NULL);
+
if (NULL == dk)
{
if (! check_request_idempotent (rc,