commit 4a83e23f67e2f5ca7acc242137e2bea94d6c4c00
parent 17c00ab24e33118d269e3725c02501322b09de06
Author: Christian Grothoff <christian@grothoff.org>
Date: Tue, 3 Mar 2026 23:26:21 +0100
split off post-withdraw and post-withdraw_blinded
Diffstat:
7 files changed, 1398 insertions(+), 1117 deletions(-)
diff --git a/src/include/taler/taler-exchange/Makefile.am b/src/include/taler/taler-exchange/Makefile.am
@@ -55,4 +55,5 @@ talerinclude_HEADERS = \
post-reserves-RESERVE_PUB-purse.h \
post-reveal-melt.h \
post-reveal-withdraw.h \
- post-withdraw.h
+ post-withdraw.h \
+ post-withdraw_blinded.h
diff --git a/src/include/taler/taler-exchange/post-withdraw.h b/src/include/taler/taler-exchange/post-withdraw.h
@@ -426,315 +426,4 @@ TALER_EXCHANGE_post_withdraw_cancel (
struct TALER_EXCHANGE_PostWithdrawHandle *pwh);
-/* *** Low-level withdraw (with pre-blinded planchets) *** */
-
-
-/**
- * @brief Information needed to withdraw coins (low-level, pre-blinded).
- */
-struct TALER_EXCHANGE_WithdrawBlindedCoinInput
-{
- /**
- * The denomination of the coin.
- */
- const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
-
- /**
- * Blinded planchet for a single coin.
- */
- struct TALER_PlanchetDetail planchet_details;
-};
-
-/**
- * @brief Information needed to withdraw age-restricted coins (low-level, pre-blinded).
- */
-struct TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput
-{
- /**
- * The denomination of the coin. MUST support age restriction.
- */
- const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
-
- /**
- * Tuple of length kappa of planchet candidates for a single coin.
- */
- struct TALER_PlanchetDetail planchet_details[TALER_CNC_KAPPA];
-};
-
-
-/**
- * Handle for an operation to POST /withdraw (low-level, pre-blinded variant).
- */
-struct TALER_EXCHANGE_PostWithdrawBlindedHandle;
-
-
-/**
- * Set up POST /withdraw operation (low-level variant with pre-blinded
- * planchets).
- * Note that you must explicitly start the operation after setup.
- *
- * This variant does not do the blinding/unblinding and only
- * fetches the blind signatures on the already blinded planchets.
- *
- * For age-restricted coins requiring a proof, pass @a blinded_input as NULL
- * and supply the age-restricted input via the
- * #TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_WITH_AGE_PROOF option.
- *
- * @param curl_ctx The curl context to use
- * @param keys The /keys material from the exchange
- * @param exchange_url The base-URL of the exchange
- * @param reserve_priv private key of the reserve to withdraw from
- * @param blinding_seed seed used for blinding of CS denominations, might be NULL
- * @param num_input number of entries in the @a blinded_input array
- * @param blinded_input array of planchet details to withdraw, or NULL if using WITH_AGE_PROOF option
- * @return handle to operation, NULL on error
- */
-struct TALER_EXCHANGE_PostWithdrawBlindedHandle *
-TALER_EXCHANGE_post_withdraw_blinded_create (
- struct GNUNET_CURL_Context *curl_ctx,
- struct TALER_EXCHANGE_Keys *keys,
- const char *exchange_url,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- const struct TALER_BlindingMasterSeedP *blinding_seed,
- size_t num_input,
- const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *blinded_input);
-
-
-/**
- * Possible options we can set for the POST /withdraw request
- * (low-level, pre-blinded variant).
- */
-enum TALER_EXCHANGE_PostWithdrawBlindedOption
-{
- /**
- * End of list of options.
- */
- TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_END = 0,
-
- /**
- * Upgrade to an age-proof withdrawal for age-restricted coins, requiring
- * an additional call to POST /reveal-withdraw.
- * The @e details.with_age_proof.max_age field gives the maximum age to
- * (provably) commit to.
- * The @e details.with_age_proof.input field gives the KAPPA planchet
- * candidates per coin.
- */
- TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_WITH_AGE_PROOF
-
-};
-
-
-/**
- * Value for an option we can set for the POST /withdraw request
- * (low-level, pre-blinded variant).
- */
-struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue
-{
- /**
- * Type of the option being set.
- */
- enum TALER_EXCHANGE_PostWithdrawBlindedOption option;
-
- /**
- * Specific option value.
- */
- union
- {
- /**
- * Value if @e option is
- * #TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_WITH_AGE_PROOF.
- */
- struct
- {
- /**
- * The maximum age to commit to.
- */
- uint8_t max_age;
-
- /**
- * Array of KAPPA planchet candidates per coin, length matches
- * the @e num_input given to _create().
- */
- const struct TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput *input;
-
- } with_age_proof;
-
- } details;
-
-};
-
-
-/**
- * Terminate the list of options.
- *
- * @return the terminating object
- */
-#define TALER_EXCHANGE_post_withdraw_blinded_option_end_() \
- (const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue) \
- { \
- .option = TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_END \
- }
-
-/**
- * Upgrade to an age-proof withdrawal for age-restricted coins.
- *
- * @param age the maximum age to commit to
- * @param inp pointer to array of KAPPA planchet candidates per coin
- * @return representation of the option
- */
-#define TALER_EXCHANGE_post_withdraw_blinded_option_with_age_proof(age, inp) \
- (const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue) \
- { \
- .option = TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_WITH_AGE_PROOF, \
- .details.with_age_proof.max_age = (age), \
- .details.with_age_proof.input = (inp) \
- }
-
-
-/**
- * Set the requested options for the operation.
- *
- * If any option fails, other options may or may not be applied.
- *
- * @param pwbh the request to set the options for
- * @param num_options length of the @a options array
- * @param options an array of options
- * @return #GNUNET_OK on success,
- * #GNUNET_NO on failure,
- * #GNUNET_SYSERR on internal error
- */
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_post_withdraw_blinded_set_options_ (
- struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh,
- unsigned int num_options,
- const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue options[]);
-
-
-/**
- * Set the requested options for the operation.
- *
- * If any option fails, other options may or may not be applied.
- *
- * @param pwbh the request to set the options for
- * @param ... the list of the options, each created by a
- * TALER_EXCHANGE_post_withdraw_blinded_option_NAME(VALUE) macro
- * @return #GNUNET_OK on success,
- * #GNUNET_NO on failure,
- * #GNUNET_SYSERR on internal error
- */
-#define TALER_EXCHANGE_post_withdraw_blinded_set_options(pwbh,...) \
- TALER_EXCHANGE_post_withdraw_blinded_set_options_ ( \
- pwbh, \
- TALER_EXCHANGE_COMMON_OPTIONS_ARRAY_MAX_SIZE, \
- ((const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue[]) \
- {__VA_ARGS__, \
- TALER_EXCHANGE_post_withdraw_blinded_option_end_ ()} \
- ))
-
-
-/**
- * Response from a POST /withdraw request (low-level, pre-blinded variant).
- */
-struct TALER_EXCHANGE_PostWithdrawBlindedResponse
-{
- /**
- * HTTP response data.
- */
- struct TALER_EXCHANGE_HttpResponse hr;
-
- /**
- * Details about the response
- */
- union
- {
- /**
- * Details if the status is #MHD_HTTP_OK
- */
- struct
- {
- /**
- * Number of signatures returned.
- */
- unsigned int num_sigs;
-
- /**
- * Array of @e num_sigs blinded denomination signatures, giving each
- * coin its value and validity. The array gives these coins in the same
- * order (and should have the same length) in which the original
- * withdraw request specified the respective denomination keys.
- */
- const struct TALER_BlindedDenominationSignature *blinded_denom_sigs;
-
- /**
- * The commitment of the withdraw request, needed for the later calls to /recoup
- */
- struct TALER_HashBlindedPlanchetsP planchets_h;
-
- } ok;
-
- /**
- * Details if the status is MHD_HTTP_CREATED, i.e. in case of
- * age-restriction. The response is input to prepare the required
- * follow-up call to /reveal-withdraw.
- */
- struct TALER_EXCHANGE_WithdrawCreated created;
-
- /**
- * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
- */
- struct TALER_EXCHANGE_KycNeededRedirect unavailable_for_legal_reasons;
-
- } details;
-};
-
-
-#ifndef TALER_EXCHANGE_POST_WITHDRAW_BLINDED_RESULT_CLOSURE
-/**
- * Type of the closure used by
- * the #TALER_EXCHANGE_PostWithdrawBlindedCallback.
- */
-#define TALER_EXCHANGE_POST_WITHDRAW_BLINDED_RESULT_CLOSURE void
-#endif /* TALER_EXCHANGE_POST_WITHDRAW_BLINDED_RESULT_CLOSURE */
-
-/**
- * Type of the function that receives the result of a
- * POST /withdraw request (low-level, pre-blinded variant).
- *
- * @param cls closure
- * @param result result returned by the HTTP server
- */
-typedef void
-(*TALER_EXCHANGE_PostWithdrawBlindedCallback)(
- TALER_EXCHANGE_POST_WITHDRAW_BLINDED_RESULT_CLOSURE *cls,
- const struct TALER_EXCHANGE_PostWithdrawBlindedResponse *result);
-
-
-/**
- * Start POST /withdraw operation (low-level, pre-blinded variant).
- *
- * @param[in,out] pwbh operation to start
- * @param cb function to call with the exchange's result
- * @param cb_cls closure for @a cb
- * @return status code, #TALER_EC_NONE on success
- */
-enum TALER_ErrorCode
-TALER_EXCHANGE_post_withdraw_blinded_start (
- struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh,
- TALER_EXCHANGE_PostWithdrawBlindedCallback cb,
- TALER_EXCHANGE_POST_WITHDRAW_BLINDED_RESULT_CLOSURE *cb_cls);
-
-
-/**
- * Cancel POST /withdraw operation (low-level, pre-blinded variant). This
- * function must not be called by clients after the
- * TALER_EXCHANGE_PostWithdrawBlindedCallback has been invoked (as in those
- * cases it'll be called internally by the implementation already).
- *
- * @param[in] pwbh operation to cancel
- */
-void
-TALER_EXCHANGE_post_withdraw_blinded_cancel (
- struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh);
-
-
#endif /* _TALER_EXCHANGE__POST_WITHDRAW_H */
diff --git a/src/include/taler/taler_exchange_service.h b/src/include/taler/taler_exchange_service.h
@@ -39,6 +39,7 @@
#include <taler/taler-exchange/get-reserves-RESERVE_PUB-history.h>
#include <taler/taler-exchange/post-blinding-prepare.h>
#include <taler/taler-exchange/post-withdraw.h>
+#include <taler/taler-exchange/post-withdraw_blinded.h>
#include <taler/taler-exchange/post-reveal-withdraw.h>
#include <taler/taler-exchange/post-reveal-melt.h>
#include <taler/taler-exchange/post-melt.h>
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
@@ -76,6 +76,7 @@ libtalerexchange_la_SOURCES = \
exchange_api_post-reveal-melt.c \
exchange_api_post-reveal-withdraw.c \
exchange_api_post-withdraw.c \
+ exchange_api_post-withdraw_blinded.c \
exchange_api_refresh_common.c exchange_api_refresh_common.h \
exchange_api_restrictions.c \
exchange_api_stefan.c
diff --git a/src/lib/exchange_api_get-keys.c b/src/lib/exchange_api_get-keys.c
@@ -0,0 +1,554 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2026 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_get-keys.c
+ * @brief Implementation of GET /keys
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <microhttpd.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_handle.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_curl_lib.h"
+
+/**
+ * If the "Expire" cache control header is missing, for
+ * how long do we assume the reply to be valid at least?
+ */
+#define DEFAULT_EXPIRATION GNUNET_TIME_UNIT_HOURS
+
+/**
+ * If the "Expire" cache control header is missing, for
+ * how long do we assume the reply to be valid at least?
+ */
+#define MINIMUM_EXPIRATION GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_MINUTES, 2)
+
+/**
+ * Define a max length for the HTTP "Expire:" header
+ */
+#define MAX_DATE_LINE_LEN 32
+
+
+/**
+ * Handle for a GET /keys request.
+ */
+struct TALER_EXCHANGE_GetKeysHandle
+{
+
+ /**
+ * The exchange base URL (i.e. "https://exchange.demo.taler.net/")
+ */
+ char *exchange_url;
+
+ /**
+ * The url for the /keys request, set during _start.
+ */
+ char *url;
+
+ /**
+ * Previous /keys response, NULL for none.
+ */
+ struct TALER_EXCHANGE_Keys *prev_keys;
+
+ /**
+ * Entry for this request with the `struct GNUNET_CURL_Context`.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Expiration time according to "Expire:" header.
+ * 0 if not provided by the server.
+ */
+ struct GNUNET_TIME_Timestamp expire;
+
+ /**
+ * Function to call with the exchange's certification data,
+ * NULL if this has already been done.
+ */
+ TALER_EXCHANGE_GetKeysCallback cert_cb;
+
+ /**
+ * Closure to pass to @e cert_cb.
+ */
+ TALER_EXCHANGE_GET_KEYS_RESULT_CLOSURE *cert_cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+};
+
+
+/**
+ * Parse HTTP timestamp.
+ *
+ * @param dateline header to parse header
+ * @param[out] at where to write the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_date_string (const char *dateline,
+ struct GNUNET_TIME_Timestamp *at)
+{
+ static const char *MONTHS[] =
+ { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL };
+ int year;
+ int mon;
+ int day;
+ int hour;
+ int min;
+ int sec;
+ char month[4];
+ struct tm tm;
+ time_t t;
+
+ /* We recognize the three formats in RFC2616, section 3.3.1. Month
+ names are always in English. The formats are:
+ Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
+ Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
+ Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
+ Note that the first is preferred.
+ */
+
+ if (strlen (dateline) > MAX_DATE_LINE_LEN)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ while (*dateline == ' ')
+ ++dateline;
+ while (*dateline && *dateline != ' ')
+ ++dateline;
+ while (*dateline == ' ')
+ ++dateline;
+ /* We just skipped over the day of the week. Now we have:*/
+ if ( (sscanf (dateline,
+ "%d %3s %d %d:%d:%d",
+ &day, month, &year, &hour, &min, &sec) != 6) &&
+ (sscanf (dateline,
+ "%d-%3s-%d %d:%d:%d",
+ &day, month, &year, &hour, &min, &sec) != 6) &&
+ (sscanf (dateline,
+ "%3s %d %d:%d:%d %d",
+ month, &day, &hour, &min, &sec, &year) != 6) )
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ /* Two digit dates are defined to be relative to 1900; all other dates
+ * are supposed to be represented as four digits. */
+ if (year < 100)
+ year += 1900;
+
+ for (mon = 0; ; mon++)
+ {
+ if (! MONTHS[mon])
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 == strcasecmp (month,
+ MONTHS[mon]))
+ break;
+ }
+
+ memset (&tm, 0, sizeof(tm));
+ tm.tm_year = year - 1900;
+ tm.tm_mon = mon;
+ tm.tm_mday = day;
+ tm.tm_hour = hour;
+ tm.tm_min = min;
+ tm.tm_sec = sec;
+
+ t = mktime (&tm);
+ if (((time_t) -1) == t)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "mktime");
+ return GNUNET_SYSERR;
+ }
+ if (t < 0)
+ t = 0; /* can happen due to timezone issues if date was 1.1.1970 */
+ *at = GNUNET_TIME_timestamp_from_s (t);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called for each header in the HTTP /keys response.
+ * Finds the "Expire:" header and parses it, storing the result
+ * in the "expire" field of the keys request.
+ *
+ * @param buffer header data received
+ * @param size size of an item in @a buffer
+ * @param nitems number of items in @a buffer
+ * @param userdata the `struct TALER_EXCHANGE_GetKeysHandle`
+ * @return `size * nitems` on success (everything else aborts)
+ */
+static size_t
+header_cb (char *buffer,
+ size_t size,
+ size_t nitems,
+ void *userdata)
+{
+ struct TALER_EXCHANGE_GetKeysHandle *kr = userdata;
+ size_t total = size * nitems;
+ char *val;
+
+ if (total < strlen (MHD_HTTP_HEADER_EXPIRES ": "))
+ return total;
+ if (0 != strncasecmp (MHD_HTTP_HEADER_EXPIRES ": ",
+ buffer,
+ strlen (MHD_HTTP_HEADER_EXPIRES ": ")))
+ return total;
+ val = GNUNET_strndup (&buffer[strlen (MHD_HTTP_HEADER_EXPIRES ": ")],
+ total - strlen (MHD_HTTP_HEADER_EXPIRES ": "));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Found %s header `%s'\n",
+ MHD_HTTP_HEADER_EXPIRES,
+ val);
+ if (GNUNET_OK !=
+ parse_date_string (val,
+ &kr->expire))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s-header `%s'\n",
+ MHD_HTTP_HEADER_EXPIRES,
+ val);
+ kr->expire = GNUNET_TIME_UNIT_ZERO_TS;
+ }
+ GNUNET_free (val);
+ return total;
+}
+
+
+/**
+ * Callback used when downloading the reply to a /keys request
+ * is complete.
+ *
+ * @param cls the `struct TALER_EXCHANGE_GetKeysHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param resp_obj parsed JSON result, NULL on error
+ */
+static void
+keys_completed_cb (void *cls,
+ long response_code,
+ const void *resp_obj)
+{
+ struct TALER_EXCHANGE_GetKeysHandle *gkh = cls;
+ const json_t *j = resp_obj;
+ struct TALER_EXCHANGE_Keys *kd = NULL;
+ struct TALER_EXCHANGE_KeysResponse kresp = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code,
+ .details.ok.compat = TALER_EXCHANGE_VC_PROTOCOL_ERROR,
+ };
+
+ gkh->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received keys from URL `%s' with status %ld and expiration %s.\n",
+ gkh->url,
+ response_code,
+ GNUNET_TIME_timestamp2s (gkh->expire));
+ if (GNUNET_TIME_absolute_is_past (gkh->expire.abs_time))
+ {
+ if (MHD_HTTP_OK == response_code)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Exchange failed to give expiration time, assuming in %s\n",
+ GNUNET_TIME_relative2s (DEFAULT_EXPIRATION,
+ true));
+ gkh->expire
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION));
+ }
+ switch (response_code)
+ {
+ case 0:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to receive /keys response from exchange %s\n",
+ gkh->exchange_url);
+ break;
+ case MHD_HTTP_OK:
+ if (NULL == j)
+ {
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ kd = GNUNET_new (struct TALER_EXCHANGE_Keys);
+ kd->exchange_url = GNUNET_strdup (gkh->exchange_url);
+ if (NULL != gkh->prev_keys)
+ {
+ const struct TALER_EXCHANGE_Keys *kd_old = gkh->prev_keys;
+
+ /* We keep the denomination keys and auditor signatures from the
+ previous iteration (/keys cherry picking) */
+ kd->num_denom_keys
+ = kd_old->num_denom_keys;
+ kd->last_denom_issue_date
+ = kd_old->last_denom_issue_date;
+ GNUNET_array_grow (kd->denom_keys,
+ kd->denom_keys_size,
+ kd->num_denom_keys);
+ /* First make a shallow copy, we then need another pass for the RSA key... */
+ GNUNET_memcpy (kd->denom_keys,
+ kd_old->denom_keys,
+ kd_old->num_denom_keys
+ * sizeof (struct TALER_EXCHANGE_DenomPublicKey));
+ for (unsigned int i = 0; i<kd_old->num_denom_keys; i++)
+ TALER_denom_pub_copy (&kd->denom_keys[i].key,
+ &kd_old->denom_keys[i].key);
+ kd->num_auditors = kd_old->num_auditors;
+ kd->auditors
+ = GNUNET_new_array (kd->num_auditors,
+ struct TALER_EXCHANGE_AuditorInformation);
+ /* Now the necessary deep copy... */
+ for (unsigned int i = 0; i<kd_old->num_auditors; i++)
+ {
+ const struct TALER_EXCHANGE_AuditorInformation *aold =
+ &kd_old->auditors[i];
+ struct TALER_EXCHANGE_AuditorInformation *anew = &kd->auditors[i];
+
+ anew->auditor_pub = aold->auditor_pub;
+ anew->auditor_url = GNUNET_strdup (aold->auditor_url);
+ anew->auditor_name = GNUNET_strdup (aold->auditor_name);
+ GNUNET_array_grow (anew->denom_keys,
+ anew->num_denom_keys,
+ aold->num_denom_keys);
+ GNUNET_memcpy (
+ anew->denom_keys,
+ aold->denom_keys,
+ aold->num_denom_keys
+ * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo));
+ }
+ }
+ /* Now decode fresh /keys response */
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_decode_keys_json_ (j,
+ true,
+ kd,
+ &kresp.details.ok.compat))
+ {
+ TALER_LOG_ERROR ("Could not decode /keys response\n");
+ kd->rc = 1;
+ TALER_EXCHANGE_keys_decref (kd);
+ kd = NULL;
+ kresp.hr.http_status = 0;
+ kresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ kd->rc = 1;
+ kd->key_data_expiration = gkh->expire;
+ if (GNUNET_TIME_relative_cmp (
+ GNUNET_TIME_absolute_get_remaining (gkh->expire.abs_time),
+ <,
+ MINIMUM_EXPIRATION))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Exchange returned keys with expiration time below %s. Compensating.\n",
+ GNUNET_TIME_relative2s (MINIMUM_EXPIRATION,
+ true));
+ kd->key_data_expiration
+ = GNUNET_TIME_relative_to_timestamp (MINIMUM_EXPIRATION);
+ }
+
+ kresp.details.ok.keys = kd;
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ case MHD_HTTP_UNAUTHORIZED:
+ case MHD_HTTP_FORBIDDEN:
+ case MHD_HTTP_NOT_FOUND:
+ if (NULL == j)
+ {
+ kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
+ }
+ else
+ {
+ kresp.hr.ec = TALER_JSON_get_error_code (j);
+ kresp.hr.hint = TALER_JSON_get_error_hint (j);
+ }
+ break;
+ default:
+ if (NULL == j)
+ {
+ kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
+ }
+ else
+ {
+ kresp.hr.ec = TALER_JSON_get_error_code (j);
+ kresp.hr.hint = TALER_JSON_get_error_hint (j);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) kresp.hr.ec);
+ break;
+ }
+ gkh->cert_cb (gkh->cert_cb_cls,
+ &kresp,
+ kd);
+ TALER_EXCHANGE_get_keys_cancel (gkh);
+}
+
+
+struct TALER_EXCHANGE_GetKeysHandle *
+TALER_EXCHANGE_get_keys_create (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url)
+{
+ struct TALER_EXCHANGE_GetKeysHandle *gkh;
+
+ gkh = GNUNET_new (struct TALER_EXCHANGE_GetKeysHandle);
+ gkh->ctx = ctx;
+ gkh->exchange_url = GNUNET_strdup (url);
+ return gkh;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_get_keys_set_options_ (
+ struct TALER_EXCHANGE_GetKeysHandle *gkh,
+ unsigned int num_options,
+ const struct TALER_EXCHANGE_GetKeysOptionValue *options)
+{
+ for (unsigned int i = 0; i < num_options; i++)
+ {
+ const struct TALER_EXCHANGE_GetKeysOptionValue *opt = &options[i];
+
+ switch (opt->option)
+ {
+ case TALER_EXCHANGE_GET_KEYS_OPTION_END:
+ return GNUNET_OK;
+ case TALER_EXCHANGE_GET_KEYS_OPTION_LAST_KEYS:
+ TALER_EXCHANGE_keys_decref (gkh->prev_keys);
+ gkh->prev_keys = NULL;
+ if (NULL != opt->details.last_keys)
+ gkh->prev_keys
+ = TALER_EXCHANGE_keys_incref (opt->details.last_keys);
+ break;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+enum TALER_ErrorCode
+TALER_EXCHANGE_get_keys_start (
+ struct TALER_EXCHANGE_GetKeysHandle *gkh,
+ TALER_EXCHANGE_GetKeysCallback cert_cb,
+ TALER_EXCHANGE_GET_KEYS_RESULT_CLOSURE *cert_cb_cls)
+{
+ CURL *eh;
+ char last_date[80] = { 0 };
+
+ gkh->cert_cb = cert_cb;
+ gkh->cert_cb_cls = cert_cb_cls;
+ if (NULL != gkh->prev_keys)
+ {
+ TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n",
+ GNUNET_TIME_timestamp2s (
+ gkh->prev_keys->last_denom_issue_date));
+ GNUNET_snprintf (last_date,
+ sizeof (last_date),
+ "%llu",
+ (unsigned long long)
+ gkh->prev_keys->last_denom_issue_date.abs_time.abs_value_us
+ / 1000000LLU);
+ }
+ gkh->url = TALER_url_join (gkh->exchange_url,
+ "keys",
+ (NULL != gkh->prev_keys)
+ ? "last_issue_date"
+ : NULL,
+ (NULL != gkh->prev_keys)
+ ? last_date
+ : NULL,
+ NULL);
+ if (NULL == gkh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ return TALER_EC_GENERIC_CONFIGURATION_INVALID;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Requesting keys with URL `%s'.\n",
+ gkh->url);
+ eh = TALER_EXCHANGE_curl_easy_get_ (gkh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (gkh->url);
+ gkh->url = NULL;
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_VERBOSE,
+ 0));
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT,
+ 120 /* seconds */));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERFUNCTION,
+ &header_cb));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERDATA,
+ gkh));
+ gkh->job = GNUNET_CURL_job_add_with_ct_json (gkh->ctx,
+ eh,
+ &keys_completed_cb,
+ gkh);
+ if (NULL == gkh->job)
+ {
+ GNUNET_free (gkh->url);
+ gkh->url = NULL;
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+ return TALER_EC_NONE;
+}
+
+
+void
+TALER_EXCHANGE_get_keys_cancel (
+ struct TALER_EXCHANGE_GetKeysHandle *gkh)
+{
+ if (NULL != gkh->job)
+ {
+ GNUNET_CURL_job_cancel (gkh->job);
+ gkh->job = NULL;
+ }
+ TALER_EXCHANGE_keys_decref (gkh->prev_keys);
+ GNUNET_free (gkh->exchange_url);
+ GNUNET_free (gkh->url);
+ GNUNET_free (gkh);
+}
+
+
+/* end of exchange_api_get-keys.c */
diff --git a/src/lib/exchange_api_post-withdraw.c b/src/lib/exchange_api_post-withdraw.c
@@ -120,136 +120,6 @@ struct BlindingPrepareCoinData
/**
- * A /withdraw request-handle for calls with pre-blinded planchets.
- * Returned by TALER_EXCHANGE_post_withdraw_blinded_create.
- */
-struct TALER_EXCHANGE_PostWithdrawBlindedHandle
-{
-
- /**
- * Reserve private key.
- */
- const struct TALER_ReservePrivateKeyP *reserve_priv;
-
- /**
- * Reserve public key, calculated
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Signature of the reserve for the request, calculated after all
- * parameters for the coins are collected.
- */
- struct TALER_ReserveSignatureP reserve_sig;
-
- /*
- * The denomination keys of the exchange
- */
- struct TALER_EXCHANGE_Keys *keys;
-
- /**
- * The hash of all the planchets
- */
- struct TALER_HashBlindedPlanchetsP planchets_h;
-
- /**
- * Seed used for the derival of blinding factors for denominations
- * with Clause-Schnorr cipher.
- */
- const struct TALER_BlindingMasterSeedP *blinding_seed;
-
- /**
- * Total amount requested (without fee).
- */
- struct TALER_Amount amount;
-
- /**
- * Total withdraw fee
- */
- struct TALER_Amount fee;
-
- /**
- * Is this call for age-restricted coins, with age proof?
- */
- bool with_age_proof;
-
- /**
- * If @e with_age_proof is true or @max_age is > 0,
- * the age mask to use, extracted from the denominations.
- * MUST be the same for all denominations.
- */
- struct TALER_AgeMask age_mask;
-
- /**
- * The maximum age to commit to. If @e with_age_proof
- * is true, the client will need to proof the correct setting
- * of age-restriction on the coins via an additional call
- * to /reveal-withdraw.
- */
- uint8_t max_age;
-
- /**
- * If @e with_age_proof is true, the hash of all the selected planchets
- */
- struct TALER_HashBlindedPlanchetsP selected_h;
-
- /**
- * Length of the either the @e blinded.input or
- * the @e blinded.with_age_proof_input array,
- * depending on @e with_age_proof.
- */
- size_t num_input;
-
- union
- {
- /**
- * The blinded planchet input candidates for age-restricted coins
- * for the call to /withdraw
- */
- const struct
- TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput *with_age_proof_input;
-
- /**
- * The blinded planchet input for the call to /withdraw,
- * for age-unrestricted coins.
- */
- const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *input;
-
- } blinded;
-
- /**
- * The url for this request.
- */
- char *request_url;
-
- /**
- * Context for curl.
- */
- struct GNUNET_CURL_Context *curl_ctx;
-
- /**
- * CURL handle for the request job.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Post Context
- */
- struct TALER_CURL_PostContext post_ctx;
-
- /**
- * Function to call with withdraw response results.
- */
- TALER_EXCHANGE_PostWithdrawBlindedCallback callback;
-
- /**
- * Closure for @e callback
- */
- void *callback_cls;
-};
-
-
-/**
* A /withdraw request-handle for calls from
* a wallet, i. e. when blinding data is available.
*/
@@ -427,564 +297,6 @@ struct TALER_EXCHANGE_PostWithdrawHandle
};
-/* Forward declarations */
-static enum TALER_ErrorCode
-call_withdraw_blinded (
- struct TALER_EXCHANGE_PostWithdrawHandle *wh);
-
-
-/**
- * We got a 200 OK response for the /withdraw operation.
- * Extract the signatures and return them to the caller.
- *
- * @param wbh operation handle
- * @param j_response reply from the exchange
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static enum GNUNET_GenericReturnValue
-withdraw_blinded_ok (
- struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh,
- const json_t *j_response)
-{
- struct TALER_EXCHANGE_PostWithdrawBlindedResponse response = {
- .hr.reply = j_response,
- .hr.http_status = MHD_HTTP_OK,
- };
- const json_t *j_sigs;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_array_const ("ev_sigs",
- &j_sigs),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j_response,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- if (wbh->num_input != json_array_size (j_sigs))
- {
- /* Number of coins generated does not match our expectation */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- {
- struct TALER_BlindedDenominationSignature denoms_sig[wbh->num_input];
-
- memset (denoms_sig,
- 0,
- sizeof(denoms_sig));
-
- /* Reconstruct the coins and unblind the signatures */
- {
- json_t *j_sig;
- size_t i;
-
- json_array_foreach (j_sigs, i, j_sig)
- {
- struct GNUNET_JSON_Specification ispec[] = {
- TALER_JSON_spec_blinded_denom_sig (NULL,
- &denoms_sig[i]),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j_sig,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- }
-
- response.details.ok.num_sigs = wbh->num_input;
- response.details.ok.blinded_denom_sigs = denoms_sig;
- response.details.ok.planchets_h = wbh->planchets_h;
- wbh->callback (
- wbh->callback_cls,
- &response);
- /* Make sure the callback isn't called again */
- wbh->callback = NULL;
- /* Free resources */
- for (size_t i = 0; i < wbh->num_input; i++)
- TALER_blinded_denom_sig_free (&denoms_sig[i]);
- }
-
- return GNUNET_OK;
-}
-
-
-/**
- * We got a 201 CREATED response for the /withdraw operation.
- * Extract the noreveal_index and return it to the caller.
- *
- * @param wbh operation handle
- * @param j_response reply from the exchange
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static enum GNUNET_GenericReturnValue
-withdraw_blinded_created (
- struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh,
- const json_t *j_response)
-{
- struct TALER_EXCHANGE_PostWithdrawBlindedResponse response = {
- .hr.reply = j_response,
- .hr.http_status = MHD_HTTP_CREATED,
- .details.created.planchets_h = wbh->planchets_h,
- .details.created.num_coins = wbh->num_input,
- };
- struct TALER_ExchangeSignatureP exchange_sig;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint8 ("noreveal_index",
- &response.details.created.noreveal_index),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &response.details.created.exchange_pub),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK!=
- GNUNET_JSON_parse (j_response,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- if (GNUNET_OK !=
- TALER_exchange_online_withdraw_age_confirmation_verify (
- &wbh->planchets_h,
- response.details.created.noreveal_index,
- &response.details.created.exchange_pub,
- &exchange_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
-
- }
-
- wbh->callback (wbh->callback_cls,
- &response);
- /* make sure the callback isn't called again */
- wbh->callback = NULL;
-
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /withdraw request.
- *
- * @param cls the `struct TALER_EXCHANGE_PostWithdrawBlindedHandle`
- * @param response_code The HTTP response code
- * @param response response data
- */
-static void
-handle_withdraw_blinded_finished (
- void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh = cls;
- const json_t *j_response = response;
- struct TALER_EXCHANGE_PostWithdrawBlindedResponse wbr = {
- .hr.reply = j_response,
- .hr.http_status = (unsigned int) response_code
- };
-
- wbh->job = NULL;
- switch (response_code)
- {
- case 0:
- wbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- if (GNUNET_OK !=
- withdraw_blinded_ok (
- wbh,
- j_response))
- {
- GNUNET_break_op (0);
- wbr.hr.http_status = 0;
- wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- GNUNET_assert (NULL == wbh->callback);
- TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
- return;
- }
- case MHD_HTTP_CREATED:
- if (GNUNET_OK !=
- withdraw_blinded_created (
- wbh,
- j_response))
- {
- GNUNET_break_op (0);
- wbr.hr.http_status = 0;
- wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- GNUNET_assert (NULL == wbh->callback);
- TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
- return;
- 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 */
- wbr.hr.ec = TALER_JSON_get_error_code (j_response);
- wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
- break;
- case MHD_HTTP_FORBIDDEN:
- GNUNET_break_op (0);
- /* Nothing really to verify, exchange says one of the signatures is
- invalid; as we checked them, this should never happen, we
- should pass the JSON reply to the application */
- wbr.hr.ec = TALER_JSON_get_error_code (j_response);
- wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
- break;
- case MHD_HTTP_NOT_FOUND:
- /* Nothing really to verify, the exchange basically just says
- that it doesn't know this reserve. Can happen if we
- query before the wire transfer went through.
- We should simply pass the JSON reply to the application. */
- wbr.hr.ec = TALER_JSON_get_error_code (j_response);
- wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
- break;
- case MHD_HTTP_CONFLICT:
- /* The age requirements might not have been met */
- wbr.hr.ec = TALER_JSON_get_error_code (j_response);
- wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
- break;
- case MHD_HTTP_GONE:
- /* could happen if denomination was revoked */
- /* Note: one might want to check /keys for revocation
- signature here, alas tricky in case our /keys
- is outdated => left to clients */
- wbr.hr.ec = TALER_JSON_get_error_code (j_response);
- wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
- break;
- case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
- /* only validate reply is well-formed */
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto (
- "h_payto",
- &wbr.details.unavailable_for_legal_reasons.h_payto),
- GNUNET_JSON_spec_uint64 (
- "requirement_row",
- &wbr.details.unavailable_for_legal_reasons.requirement_row),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j_response,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- wbr.hr.http_status = 0;
- wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- break;
- }
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- wbr.hr.ec = TALER_JSON_get_error_code (j_response);
- wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- wbr.hr.ec = TALER_JSON_get_error_code (j_response);
- wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange withdraw\n",
- (unsigned int) response_code,
- (int) wbr.hr.ec);
- break;
- }
- wbh->callback (wbh->callback_cls,
- &wbr);
- TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
-}
-
-
-/**
- * Runs the actual withdraw operation with the blinded planchets.
- *
- * @param[in,out] wbh withdraw blinded handle
- * @return #TALER_EC_NONE on success, error code on failure
- */
-static enum TALER_ErrorCode
-perform_withdraw_protocol (
- struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh)
-{
-#define FAIL_IF(cond) \
- do { \
- if ((cond)) \
- { \
- GNUNET_break (! (cond)); \
- goto ERROR; \
- } \
- } while (0)
-
- json_t * j_denoms = NULL;
- json_t *j_planchets = NULL;
- json_t *j_request_body = NULL;
- CURL *curlh = NULL;
- struct GNUNET_HashContext *coins_hctx = NULL;
- struct TALER_BlindedCoinHashP bch;
-
- GNUNET_assert (0 < wbh->num_input);
-
- FAIL_IF (GNUNET_OK !=
- TALER_amount_set_zero (wbh->keys->currency,
- &wbh->amount));
- FAIL_IF (GNUNET_OK !=
- TALER_amount_set_zero (wbh->keys->currency,
- &wbh->fee));
-
- /* Accumulate total value with fees */
- for (size_t i = 0; i < wbh->num_input; i++)
- {
- const struct TALER_EXCHANGE_DenomPublicKey *dpub =
- wbh->with_age_proof ?
- wbh->blinded.with_age_proof_input[i].denom_pub :
- wbh->blinded.input[i].denom_pub;
-
- FAIL_IF (0 >
- TALER_amount_add (&wbh->amount,
- &wbh->amount,
- &dpub->value));
- FAIL_IF (0 >
- TALER_amount_add (&wbh->fee,
- &wbh->fee,
- &dpub->fees.withdraw));
-
- if (GNUNET_CRYPTO_BSA_CS ==
- dpub->key.bsign_pub_key->cipher)
- GNUNET_assert (NULL != wbh->blinding_seed);
-
- }
-
- if (wbh->with_age_proof || wbh->max_age > 0)
- {
- wbh->age_mask =
- wbh->blinded.with_age_proof_input[0].denom_pub->key.age_mask;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Attempting to withdraw from reserve %s with maximum age %d to proof\n",
- TALER_B2S (&wbh->reserve_pub),
- wbh->max_age);
- }
- else
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Attempting to withdraw from reserve %s\n",
- TALER_B2S (&wbh->reserve_pub));
- }
-
- coins_hctx = GNUNET_CRYPTO_hash_context_start ();
- FAIL_IF (NULL == coins_hctx);
-
- j_denoms = json_array ();
- j_planchets = json_array ();
- FAIL_IF ((NULL == j_denoms) ||
- (NULL == j_planchets));
-
- for (size_t i = 0; i< wbh->num_input; i++)
- {
- /* Build the denomination array */
- const struct TALER_EXCHANGE_DenomPublicKey *denom_pub =
- wbh->with_age_proof ?
- wbh->blinded.with_age_proof_input[i].denom_pub :
- wbh->blinded.input[i].denom_pub;
- const struct TALER_DenominationHashP *denom_h = &denom_pub->h_key;
- json_t *jdenom;
-
- /* The mask must be the same for all coins */
- FAIL_IF (wbh->with_age_proof &&
- (wbh->age_mask.bits != denom_pub->key.age_mask.bits));
-
- jdenom = GNUNET_JSON_from_data_auto (denom_h);
- FAIL_IF (NULL == jdenom);
- FAIL_IF (0 > json_array_append_new (j_denoms,
- jdenom));
- }
-
-
- /* Build the planchet array and calculate the hash over all planchets. */
- if (! wbh->with_age_proof)
- {
- for (size_t i = 0; i< wbh->num_input; i++)
- {
- const struct TALER_PlanchetDetail *planchet =
- &wbh->blinded.input[i].planchet_details;
- json_t *jc = GNUNET_JSON_PACK (
- TALER_JSON_pack_blinded_planchet (
- NULL,
- &planchet->blinded_planchet));
- FAIL_IF (NULL == jc);
- FAIL_IF (0 > json_array_append_new (j_planchets,
- jc));
-
- TALER_coin_ev_hash (&planchet->blinded_planchet,
- &planchet->denom_pub_hash,
- &bch);
-
- GNUNET_CRYPTO_hash_context_read (coins_hctx,
- &bch,
- sizeof(bch));
- }
- }
- else
- { /* Age restricted case with required age-proof. */
-
- /**
- * We collect the run of all coin candidates for the same γ index
- * first, then γ+1 etc.
- */
- for (size_t k = 0; k < TALER_CNC_KAPPA; k++)
- {
- struct GNUNET_HashContext *batch_ctx;
- struct TALER_BlindedCoinHashP batch_h;
-
- batch_ctx = GNUNET_CRYPTO_hash_context_start ();
- FAIL_IF (NULL == batch_ctx);
-
- for (size_t i = 0; i< wbh->num_input; i++)
- {
- const struct TALER_PlanchetDetail *planchet =
- &wbh->blinded.with_age_proof_input[i].planchet_details[k];
- json_t *jc = GNUNET_JSON_PACK (
- TALER_JSON_pack_blinded_planchet (
- NULL,
- &planchet->blinded_planchet));
-
- FAIL_IF (NULL == jc);
- FAIL_IF (0 > json_array_append_new (
- j_planchets,
- jc));
-
- TALER_coin_ev_hash (
- &planchet->blinded_planchet,
- &planchet->denom_pub_hash,
- &bch);
-
- GNUNET_CRYPTO_hash_context_read (
- batch_ctx,
- &bch,
- sizeof(bch));
- }
-
- GNUNET_CRYPTO_hash_context_finish (
- batch_ctx,
- &batch_h.hash);
- GNUNET_CRYPTO_hash_context_read (
- coins_hctx,
- &batch_h,
- sizeof(batch_h));
- }
- }
-
- /* Build the hash of the planchets */
- GNUNET_CRYPTO_hash_context_finish (
- coins_hctx,
- &wbh->planchets_h.hash);
- coins_hctx = NULL;
-
- /* Sign the request */
- TALER_wallet_withdraw_sign (
- &wbh->amount,
- &wbh->fee,
- &wbh->planchets_h,
- wbh->blinding_seed,
- wbh->with_age_proof ? &wbh->age_mask: NULL,
- wbh->with_age_proof ? wbh->max_age : 0,
- wbh->reserve_priv,
- &wbh->reserve_sig);
-
- /* Initiate the POST-request */
- j_request_body = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("cipher",
- "ED25519"),
- GNUNET_JSON_pack_data_auto ("reserve_pub",
- &wbh->reserve_pub),
- GNUNET_JSON_pack_array_steal ("denoms_h",
- j_denoms),
- GNUNET_JSON_pack_array_steal ("coin_evs",
- j_planchets),
- GNUNET_JSON_pack_allow_null (
- wbh->with_age_proof
- ? GNUNET_JSON_pack_int64 ("max_age",
- wbh->max_age)
- : GNUNET_JSON_pack_string ("max_age",
- NULL) ),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &wbh->reserve_sig));
- FAIL_IF (NULL == j_request_body);
-
- if (NULL != wbh->blinding_seed)
- {
- json_t *j_seed = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("blinding_seed",
- wbh->blinding_seed));
- GNUNET_assert (NULL != j_seed);
- GNUNET_assert (0 ==
- json_object_update_new (
- j_request_body,
- j_seed));
- }
-
- curlh = TALER_EXCHANGE_curl_easy_get_ (wbh->request_url);
- FAIL_IF (NULL == curlh);
- FAIL_IF (GNUNET_OK !=
- TALER_curl_easy_post (
- &wbh->post_ctx,
- curlh,
- j_request_body));
- json_decref (j_request_body);
- j_request_body = NULL;
-
- wbh->job = GNUNET_CURL_job_add2 (
- wbh->curl_ctx,
- curlh,
- wbh->post_ctx.headers,
- &handle_withdraw_blinded_finished,
- wbh);
- FAIL_IF (NULL == wbh->job);
-
- /* No errors, return */
- return TALER_EC_NONE;
-
-ERROR:
- if (NULL != coins_hctx)
- GNUNET_CRYPTO_hash_context_abort (coins_hctx);
- if (NULL != j_denoms)
- json_decref (j_denoms);
- if (NULL != j_planchets)
- json_decref (j_planchets);
- if (NULL != j_request_body)
- json_decref (j_request_body);
- if (NULL != curlh)
- curl_easy_cleanup (curlh);
- return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
-#undef FAIL_IF
-}
-
-
/**
* @brief Callback to copy the results from the call to post_withdraw_blinded
* in the non-age-restricted case to the result for the originating call.
@@ -1842,121 +1154,4 @@ TALER_EXCHANGE_post_withdraw_cancel (
}
-/**
- * @brief Prepare the handler for blinded withdraw
- *
- * Allocates the handler struct and prepares all fields of the handler
- * except the blinded planchets,
- * which depend on them being age-restricted or not.
- *
- * @param curl_ctx the context for curl
- * @param keys the exchange keys
- * @param exchange_url the url to the exchange
- * @param reserve_priv the reserve's private key
- * @return the handler, NULL on error
- */
-static struct TALER_EXCHANGE_PostWithdrawBlindedHandle *
-setup_handler_common (
- struct GNUNET_CURL_Context *curl_ctx,
- struct TALER_EXCHANGE_Keys *keys,
- const char *exchange_url,
- const struct TALER_ReservePrivateKeyP *reserve_priv)
-{
- struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh =
- GNUNET_new (struct TALER_EXCHANGE_PostWithdrawBlindedHandle);
-
- wbh->keys = TALER_EXCHANGE_keys_incref (keys);
- wbh->curl_ctx = curl_ctx;
- wbh->reserve_priv = reserve_priv;
- wbh->request_url = TALER_url_join (exchange_url,
- "withdraw",
- NULL);
- GNUNET_CRYPTO_eddsa_key_get_public (
- &wbh->reserve_priv->eddsa_priv,
- &wbh->reserve_pub.eddsa_pub);
-
- return wbh;
-}
-
-
-struct TALER_EXCHANGE_PostWithdrawBlindedHandle *
-TALER_EXCHANGE_post_withdraw_blinded_create (
- struct GNUNET_CURL_Context *curl_ctx,
- struct TALER_EXCHANGE_Keys *keys,
- const char *exchange_url,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- const struct TALER_BlindingMasterSeedP *blinding_seed,
- size_t num_input,
- const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *blinded_input)
-{
- struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh =
- setup_handler_common (curl_ctx,
- keys,
- exchange_url,
- reserve_priv);
-
- wbh->with_age_proof = false;
- wbh->num_input = num_input;
- wbh->blinded.input = blinded_input;
- wbh->blinding_seed = blinding_seed;
-
- return wbh;
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_post_withdraw_blinded_set_options_ (
- struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh,
- unsigned int num_options,
- const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue options[])
-{
- for (unsigned int i = 0; i < num_options; i++)
- {
- const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue *opt =
- &options[i];
- switch (opt->option)
- {
- case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_END:
- return GNUNET_OK;
- case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_WITH_AGE_PROOF:
- pwbh->with_age_proof = true;
- pwbh->max_age = opt->details.with_age_proof.max_age;
- pwbh->blinded.with_age_proof_input = opt->details.with_age_proof.input;
- break;
- }
- }
- return GNUNET_OK;
-}
-
-
-enum TALER_ErrorCode
-TALER_EXCHANGE_post_withdraw_blinded_start (
- struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh,
- TALER_EXCHANGE_PostWithdrawBlindedCallback cb,
- TALER_EXCHANGE_POST_WITHDRAW_BLINDED_RESULT_CLOSURE *cb_cls)
-{
- pwbh->callback = cb;
- pwbh->callback_cls = cb_cls;
- return perform_withdraw_protocol (pwbh);
-}
-
-
-void
-TALER_EXCHANGE_post_withdraw_blinded_cancel (
- struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh)
-{
- if (NULL == wbh)
- return;
- if (NULL != wbh->job)
- {
- GNUNET_CURL_job_cancel (wbh->job);
- wbh->job = NULL;
- }
- GNUNET_free (wbh->request_url);
- TALER_EXCHANGE_keys_decref (wbh->keys);
- TALER_curl_easy_post_finished (&wbh->post_ctx);
- GNUNET_free (wbh);
-}
-
-
/* exchange_api_post-withdraw.c */
diff --git a/src/lib/exchange_api_post-withdraw_blinded.c b/src/lib/exchange_api_post-withdraw_blinded.c
@@ -0,0 +1,840 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023-2026 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_post-withdraw.c
+ * @brief Implementation of /withdraw requests
+ * @author Özgür Kesim
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_common.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 <sys/wait.h>
+#include "taler/taler_curl_lib.h"
+#include "taler/taler_error_codes.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 "taler/taler_util.h"
+
+
+/**
+ * A /withdraw request-handle for calls with pre-blinded planchets.
+ * Returned by TALER_EXCHANGE_post_withdraw_blinded_create.
+ */
+struct TALER_EXCHANGE_PostWithdrawBlindedHandle
+{
+
+ /**
+ * Reserve private key.
+ */
+ const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+ /**
+ * Reserve public key, calculated
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Signature of the reserve for the request, calculated after all
+ * parameters for the coins are collected.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /*
+ * The denomination keys of the exchange
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The hash of all the planchets
+ */
+ struct TALER_HashBlindedPlanchetsP planchets_h;
+
+ /**
+ * Seed used for the derival of blinding factors for denominations
+ * with Clause-Schnorr cipher.
+ */
+ const struct TALER_BlindingMasterSeedP *blinding_seed;
+
+ /**
+ * Total amount requested (without fee).
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Total withdraw fee
+ */
+ struct TALER_Amount fee;
+
+ /**
+ * Is this call for age-restricted coins, with age proof?
+ */
+ bool with_age_proof;
+
+ /**
+ * If @e with_age_proof is true or @max_age is > 0,
+ * the age mask to use, extracted from the denominations.
+ * MUST be the same for all denominations.
+ */
+ struct TALER_AgeMask age_mask;
+
+ /**
+ * The maximum age to commit to. If @e with_age_proof
+ * is true, the client will need to proof the correct setting
+ * of age-restriction on the coins via an additional call
+ * to /reveal-withdraw.
+ */
+ uint8_t max_age;
+
+ /**
+ * If @e with_age_proof is true, the hash of all the selected planchets
+ */
+ struct TALER_HashBlindedPlanchetsP selected_h;
+
+ /**
+ * Length of the either the @e blinded.input or
+ * the @e blinded.with_age_proof_input array,
+ * depending on @e with_age_proof.
+ */
+ size_t num_input;
+
+ union
+ {
+ /**
+ * The blinded planchet input candidates for age-restricted coins
+ * for the call to /withdraw
+ */
+ const struct
+ TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput *with_age_proof_input;
+
+ /**
+ * The blinded planchet input for the call to /withdraw,
+ * for age-unrestricted coins.
+ */
+ const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *input;
+
+ } blinded;
+
+ /**
+ * The url for this request.
+ */
+ char *request_url;
+
+ /**
+ * Context for curl.
+ */
+ struct GNUNET_CURL_Context *curl_ctx;
+
+ /**
+ * CURL handle for the request job.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Post Context
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Function to call with withdraw response results.
+ */
+ TALER_EXCHANGE_PostWithdrawBlindedCallback callback;
+
+ /**
+ * Closure for @e callback
+ */
+ void *callback_cls;
+};
+
+
+/**
+ * We got a 200 OK response for the /withdraw operation.
+ * Extract the signatures and return them to the caller.
+ *
+ * @param wbh operation handle
+ * @param j_response reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+withdraw_blinded_ok (
+ struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh,
+ const json_t *j_response)
+{
+ struct TALER_EXCHANGE_PostWithdrawBlindedResponse response = {
+ .hr.reply = j_response,
+ .hr.http_status = MHD_HTTP_OK,
+ };
+ const json_t *j_sigs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("ev_sigs",
+ &j_sigs),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (wbh->num_input != json_array_size (j_sigs))
+ {
+ /* Number of coins generated does not match our expectation */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ struct TALER_BlindedDenominationSignature denoms_sig[wbh->num_input];
+
+ memset (denoms_sig,
+ 0,
+ sizeof(denoms_sig));
+
+ /* Reconstruct the coins and unblind the signatures */
+ {
+ json_t *j_sig;
+ size_t i;
+
+ json_array_foreach (j_sigs, i, j_sig)
+ {
+ struct GNUNET_JSON_Specification ispec[] = {
+ TALER_JSON_spec_blinded_denom_sig (NULL,
+ &denoms_sig[i]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j_sig,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+
+ response.details.ok.num_sigs = wbh->num_input;
+ response.details.ok.blinded_denom_sigs = denoms_sig;
+ response.details.ok.planchets_h = wbh->planchets_h;
+ wbh->callback (
+ wbh->callback_cls,
+ &response);
+ /* Make sure the callback isn't called again */
+ wbh->callback = NULL;
+ /* Free resources */
+ for (size_t i = 0; i < wbh->num_input; i++)
+ TALER_blinded_denom_sig_free (&denoms_sig[i]);
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * We got a 201 CREATED response for the /withdraw operation.
+ * Extract the noreveal_index and return it to the caller.
+ *
+ * @param wbh operation handle
+ * @param j_response reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+withdraw_blinded_created (
+ struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh,
+ const json_t *j_response)
+{
+ struct TALER_EXCHANGE_PostWithdrawBlindedResponse response = {
+ .hr.reply = j_response,
+ .hr.http_status = MHD_HTTP_CREATED,
+ .details.created.planchets_h = wbh->planchets_h,
+ .details.created.num_coins = wbh->num_input,
+ };
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint8 ("noreveal_index",
+ &response.details.created.noreveal_index),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &response.details.created.exchange_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK!=
+ GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ TALER_exchange_online_withdraw_age_confirmation_verify (
+ &wbh->planchets_h,
+ response.details.created.noreveal_index,
+ &response.details.created.exchange_pub,
+ &exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+
+ }
+
+ wbh->callback (wbh->callback_cls,
+ &response);
+ /* make sure the callback isn't called again */
+ wbh->callback = NULL;
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /withdraw request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_PostWithdrawBlindedHandle`
+ * @param response_code The HTTP response code
+ * @param response response data
+ */
+static void
+handle_withdraw_blinded_finished (
+ void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh = cls;
+ const json_t *j_response = response;
+ struct TALER_EXCHANGE_PostWithdrawBlindedResponse wbr = {
+ .hr.reply = j_response,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ wbh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ wbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ if (GNUNET_OK !=
+ withdraw_blinded_ok (
+ wbh,
+ j_response))
+ {
+ GNUNET_break_op (0);
+ wbr.hr.http_status = 0;
+ wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == wbh->callback);
+ TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
+ return;
+ }
+ case MHD_HTTP_CREATED:
+ if (GNUNET_OK !=
+ withdraw_blinded_created (
+ wbh,
+ j_response))
+ {
+ GNUNET_break_op (0);
+ wbr.hr.http_status = 0;
+ wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == wbh->callback);
+ TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
+ return;
+ 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 */
+ wbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ GNUNET_break_op (0);
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ wbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, the exchange basically just says
+ that it doesn't know this reserve. Can happen if we
+ query before the wire transfer went through.
+ We should simply pass the JSON reply to the application. */
+ wbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* The age requirements might not have been met */
+ wbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_GONE:
+ /* could happen if denomination was revoked */
+ /* Note: one might want to check /keys for revocation
+ signature here, alas tricky in case our /keys
+ is outdated => left to clients */
+ wbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ /* only validate reply is well-formed */
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "h_payto",
+ &wbr.details.unavailable_for_legal_reasons.h_payto),
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &wbr.details.unavailable_for_legal_reasons.requirement_row),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ wbr.hr.http_status = 0;
+ wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ break;
+ }
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ wbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ wbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange withdraw\n",
+ (unsigned int) response_code,
+ (int) wbr.hr.ec);
+ break;
+ }
+ wbh->callback (wbh->callback_cls,
+ &wbr);
+ TALER_EXCHANGE_post_withdraw_blinded_cancel (wbh);
+}
+
+
+/**
+ * Runs the actual withdraw operation with the blinded planchets.
+ *
+ * @param[in,out] wbh withdraw blinded handle
+ * @return #TALER_EC_NONE on success, error code on failure
+ */
+static enum TALER_ErrorCode
+perform_withdraw_protocol (
+ struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh)
+{
+#define FAIL_IF(cond) \
+ do { \
+ if ((cond)) \
+ { \
+ GNUNET_break (! (cond)); \
+ goto ERROR; \
+ } \
+ } while (0)
+
+ json_t * j_denoms = NULL;
+ json_t *j_planchets = NULL;
+ json_t *j_request_body = NULL;
+ CURL *curlh = NULL;
+ struct GNUNET_HashContext *coins_hctx = NULL;
+ struct TALER_BlindedCoinHashP bch;
+
+ GNUNET_assert (0 < wbh->num_input);
+
+ FAIL_IF (GNUNET_OK !=
+ TALER_amount_set_zero (wbh->keys->currency,
+ &wbh->amount));
+ FAIL_IF (GNUNET_OK !=
+ TALER_amount_set_zero (wbh->keys->currency,
+ &wbh->fee));
+
+ /* Accumulate total value with fees */
+ for (size_t i = 0; i < wbh->num_input; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *dpub =
+ wbh->with_age_proof ?
+ wbh->blinded.with_age_proof_input[i].denom_pub :
+ wbh->blinded.input[i].denom_pub;
+
+ FAIL_IF (0 >
+ TALER_amount_add (&wbh->amount,
+ &wbh->amount,
+ &dpub->value));
+ FAIL_IF (0 >
+ TALER_amount_add (&wbh->fee,
+ &wbh->fee,
+ &dpub->fees.withdraw));
+
+ if (GNUNET_CRYPTO_BSA_CS ==
+ dpub->key.bsign_pub_key->cipher)
+ GNUNET_assert (NULL != wbh->blinding_seed);
+
+ }
+
+ if (wbh->with_age_proof || wbh->max_age > 0)
+ {
+ wbh->age_mask =
+ wbh->blinded.with_age_proof_input[0].denom_pub->key.age_mask;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Attempting to withdraw from reserve %s with maximum age %d to proof\n",
+ TALER_B2S (&wbh->reserve_pub),
+ wbh->max_age);
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Attempting to withdraw from reserve %s\n",
+ TALER_B2S (&wbh->reserve_pub));
+ }
+
+ coins_hctx = GNUNET_CRYPTO_hash_context_start ();
+ FAIL_IF (NULL == coins_hctx);
+
+ j_denoms = json_array ();
+ j_planchets = json_array ();
+ FAIL_IF ((NULL == j_denoms) ||
+ (NULL == j_planchets));
+
+ for (size_t i = 0; i< wbh->num_input; i++)
+ {
+ /* Build the denomination array */
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub =
+ wbh->with_age_proof ?
+ wbh->blinded.with_age_proof_input[i].denom_pub :
+ wbh->blinded.input[i].denom_pub;
+ const struct TALER_DenominationHashP *denom_h = &denom_pub->h_key;
+ json_t *jdenom;
+
+ /* The mask must be the same for all coins */
+ FAIL_IF (wbh->with_age_proof &&
+ (wbh->age_mask.bits != denom_pub->key.age_mask.bits));
+
+ jdenom = GNUNET_JSON_from_data_auto (denom_h);
+ FAIL_IF (NULL == jdenom);
+ FAIL_IF (0 > json_array_append_new (j_denoms,
+ jdenom));
+ }
+
+
+ /* Build the planchet array and calculate the hash over all planchets. */
+ if (! wbh->with_age_proof)
+ {
+ for (size_t i = 0; i< wbh->num_input; i++)
+ {
+ const struct TALER_PlanchetDetail *planchet =
+ &wbh->blinded.input[i].planchet_details;
+ json_t *jc = GNUNET_JSON_PACK (
+ TALER_JSON_pack_blinded_planchet (
+ NULL,
+ &planchet->blinded_planchet));
+ FAIL_IF (NULL == jc);
+ FAIL_IF (0 > json_array_append_new (j_planchets,
+ jc));
+
+ TALER_coin_ev_hash (&planchet->blinded_planchet,
+ &planchet->denom_pub_hash,
+ &bch);
+
+ GNUNET_CRYPTO_hash_context_read (coins_hctx,
+ &bch,
+ sizeof(bch));
+ }
+ }
+ else
+ { /* Age restricted case with required age-proof. */
+
+ /**
+ * We collect the run of all coin candidates for the same γ index
+ * first, then γ+1 etc.
+ */
+ for (size_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ struct GNUNET_HashContext *batch_ctx;
+ struct TALER_BlindedCoinHashP batch_h;
+
+ batch_ctx = GNUNET_CRYPTO_hash_context_start ();
+ FAIL_IF (NULL == batch_ctx);
+
+ for (size_t i = 0; i< wbh->num_input; i++)
+ {
+ const struct TALER_PlanchetDetail *planchet =
+ &wbh->blinded.with_age_proof_input[i].planchet_details[k];
+ json_t *jc = GNUNET_JSON_PACK (
+ TALER_JSON_pack_blinded_planchet (
+ NULL,
+ &planchet->blinded_planchet));
+
+ FAIL_IF (NULL == jc);
+ FAIL_IF (0 > json_array_append_new (
+ j_planchets,
+ jc));
+
+ TALER_coin_ev_hash (
+ &planchet->blinded_planchet,
+ &planchet->denom_pub_hash,
+ &bch);
+
+ GNUNET_CRYPTO_hash_context_read (
+ batch_ctx,
+ &bch,
+ sizeof(bch));
+ }
+
+ GNUNET_CRYPTO_hash_context_finish (
+ batch_ctx,
+ &batch_h.hash);
+ GNUNET_CRYPTO_hash_context_read (
+ coins_hctx,
+ &batch_h,
+ sizeof(batch_h));
+ }
+ }
+
+ /* Build the hash of the planchets */
+ GNUNET_CRYPTO_hash_context_finish (
+ coins_hctx,
+ &wbh->planchets_h.hash);
+ coins_hctx = NULL;
+
+ /* Sign the request */
+ TALER_wallet_withdraw_sign (
+ &wbh->amount,
+ &wbh->fee,
+ &wbh->planchets_h,
+ wbh->blinding_seed,
+ wbh->with_age_proof ? &wbh->age_mask: NULL,
+ wbh->with_age_proof ? wbh->max_age : 0,
+ wbh->reserve_priv,
+ &wbh->reserve_sig);
+
+ /* Initiate the POST-request */
+ j_request_body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ "ED25519"),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &wbh->reserve_pub),
+ GNUNET_JSON_pack_array_steal ("denoms_h",
+ j_denoms),
+ GNUNET_JSON_pack_array_steal ("coin_evs",
+ j_planchets),
+ GNUNET_JSON_pack_allow_null (
+ wbh->with_age_proof
+ ? GNUNET_JSON_pack_int64 ("max_age",
+ wbh->max_age)
+ : GNUNET_JSON_pack_string ("max_age",
+ NULL) ),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &wbh->reserve_sig));
+ FAIL_IF (NULL == j_request_body);
+
+ if (NULL != wbh->blinding_seed)
+ {
+ json_t *j_seed = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("blinding_seed",
+ wbh->blinding_seed));
+ GNUNET_assert (NULL != j_seed);
+ GNUNET_assert (0 ==
+ json_object_update_new (
+ j_request_body,
+ j_seed));
+ }
+
+ curlh = TALER_EXCHANGE_curl_easy_get_ (wbh->request_url);
+ FAIL_IF (NULL == curlh);
+ FAIL_IF (GNUNET_OK !=
+ TALER_curl_easy_post (
+ &wbh->post_ctx,
+ curlh,
+ j_request_body));
+ json_decref (j_request_body);
+ j_request_body = NULL;
+
+ wbh->job = GNUNET_CURL_job_add2 (
+ wbh->curl_ctx,
+ curlh,
+ wbh->post_ctx.headers,
+ &handle_withdraw_blinded_finished,
+ wbh);
+ FAIL_IF (NULL == wbh->job);
+
+ /* No errors, return */
+ return TALER_EC_NONE;
+
+ERROR:
+ if (NULL != coins_hctx)
+ GNUNET_CRYPTO_hash_context_abort (coins_hctx);
+ if (NULL != j_denoms)
+ json_decref (j_denoms);
+ if (NULL != j_planchets)
+ json_decref (j_planchets);
+ if (NULL != j_request_body)
+ json_decref (j_request_body);
+ if (NULL != curlh)
+ curl_easy_cleanup (curlh);
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+#undef FAIL_IF
+}
+
+
+/**
+ * @brief Prepare the handler for blinded withdraw
+ *
+ * Allocates the handler struct and prepares all fields of the handler
+ * except the blinded planchets,
+ * which depend on them being age-restricted or not.
+ *
+ * @param curl_ctx the context for curl
+ * @param keys the exchange keys
+ * @param exchange_url the url to the exchange
+ * @param reserve_priv the reserve's private key
+ * @return the handler, NULL on error
+ */
+static struct TALER_EXCHANGE_PostWithdrawBlindedHandle *
+setup_handler_common (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv)
+{
+ struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh =
+ GNUNET_new (struct TALER_EXCHANGE_PostWithdrawBlindedHandle);
+
+ wbh->keys = TALER_EXCHANGE_keys_incref (keys);
+ wbh->curl_ctx = curl_ctx;
+ wbh->reserve_priv = reserve_priv;
+ wbh->request_url = TALER_url_join (exchange_url,
+ "withdraw",
+ NULL);
+ GNUNET_CRYPTO_eddsa_key_get_public (
+ &wbh->reserve_priv->eddsa_priv,
+ &wbh->reserve_pub.eddsa_pub);
+
+ return wbh;
+}
+
+
+struct TALER_EXCHANGE_PostWithdrawBlindedHandle *
+TALER_EXCHANGE_post_withdraw_blinded_create (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_BlindingMasterSeedP *blinding_seed,
+ size_t num_input,
+ const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *blinded_input)
+{
+ struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh =
+ setup_handler_common (curl_ctx,
+ keys,
+ exchange_url,
+ reserve_priv);
+
+ wbh->with_age_proof = false;
+ wbh->num_input = num_input;
+ wbh->blinded.input = blinded_input;
+ wbh->blinding_seed = blinding_seed;
+
+ return wbh;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_post_withdraw_blinded_set_options_ (
+ struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh,
+ unsigned int num_options,
+ const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue options[])
+{
+ for (unsigned int i = 0; i < num_options; i++)
+ {
+ const struct TALER_EXCHANGE_PostWithdrawBlindedOptionValue *opt =
+ &options[i];
+ switch (opt->option)
+ {
+ case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_END:
+ return GNUNET_OK;
+ case TALER_EXCHANGE_POST_WITHDRAW_BLINDED_OPTION_WITH_AGE_PROOF:
+ pwbh->with_age_proof = true;
+ pwbh->max_age = opt->details.with_age_proof.max_age;
+ pwbh->blinded.with_age_proof_input = opt->details.with_age_proof.input;
+ break;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+enum TALER_ErrorCode
+TALER_EXCHANGE_post_withdraw_blinded_start (
+ struct TALER_EXCHANGE_PostWithdrawBlindedHandle *pwbh,
+ TALER_EXCHANGE_PostWithdrawBlindedCallback cb,
+ TALER_EXCHANGE_POST_WITHDRAW_BLINDED_RESULT_CLOSURE *cb_cls)
+{
+ pwbh->callback = cb;
+ pwbh->callback_cls = cb_cls;
+ return perform_withdraw_protocol (pwbh);
+}
+
+
+void
+TALER_EXCHANGE_post_withdraw_blinded_cancel (
+ struct TALER_EXCHANGE_PostWithdrawBlindedHandle *wbh)
+{
+ if (NULL == wbh)
+ return;
+ if (NULL != wbh->job)
+ {
+ GNUNET_CURL_job_cancel (wbh->job);
+ wbh->job = NULL;
+ }
+ GNUNET_free (wbh->request_url);
+ TALER_EXCHANGE_keys_decref (wbh->keys);
+ TALER_curl_easy_post_finished (&wbh->post_ctx);
+ GNUNET_free (wbh);
+}
+
+
+/* exchange_api_post-withdraw_blinded.c */