summaryrefslogtreecommitdiff
path: root/src/lib/exchange_api_age_withdraw.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/exchange_api_age_withdraw.c')
-rw-r--r--src/lib/exchange_api_age_withdraw.c1125
1 files changed, 1125 insertions, 0 deletions
diff --git a/src/lib/exchange_api_age_withdraw.c b/src/lib/exchange_api_age_withdraw.c
new file mode 100644
index 000000000..ca1a11cb8
--- /dev/null
+++ b/src/lib/exchange_api_age_withdraw.c
@@ -0,0 +1,1125 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 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_age_withdraw.c
+ * @brief Implementation of /reserves/$RESERVE_PUB/age-withdraw requests
+ * @author Özgür Kesim
+ */
+
+#include "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_curl_lib.h"
+#include "taler_error_codes.h"
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_util.h"
+
+/**
+ * A CoinCandidate is populated from a master secret
+ */
+struct CoinCandidate
+{
+ /**
+ * Master key material for the coin candidates.
+ */
+ struct TALER_PlanchetMasterSecretP secret;
+
+ /**
+ * The details derived form the master secrets
+ */
+ struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails details;
+
+ /**
+ * Blinded hash of the coin
+ **/
+ struct TALER_BlindedCoinHashP blinded_coin_h;
+
+};
+
+
+/**
+ * Closure for a call to /csr-withdraw, contains data that is needed to process
+ * the result.
+ */
+struct CSRClosure
+{
+ /**
+ * Points to the actual candidate in CoinData.coin_candidates, to continue
+ * to build its contents based on the results from /csr-withdraw
+ */
+ struct CoinCandidate *candidate;
+
+ /**
+ * The planchet to finally generate. Points to the corresponding candidate
+ * in CoindData.planchet_details
+ */
+ struct TALER_PlanchetDetail *planchet;
+
+ /**
+ * Handler to the originating call to /age-withdraw, needed to either
+ * cancel the running age-withdraw request (on failure of the current call
+ * to /csr-withdraw), or to eventually perform the protocol, once all
+ * csr-withdraw requests have successfully finished.
+ */
+ struct TALER_EXCHANGE_AgeWithdrawHandle *age_withdraw_handle;
+
+ /**
+ * Session nonce.
+ */
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+
+ /**
+ * Denomination information, needed for CS coins for the
+ * step after /csr-withdraw
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+
+ /**
+ * Handler for the CS R request
+ */
+ struct TALER_EXCHANGE_CsRWithdrawHandle *csr_withdraw_handle;
+};
+
+/**
+ * Data we keep per coin in the batch.
+ */
+struct CoinData
+{
+ /**
+ * The denomination of the coin. Must support age restriction, i.e
+ * its .keys.age_mask MUST not be 0
+ */
+ struct TALER_EXCHANGE_DenomPublicKey denom_pub;
+
+ /**
+ * The Candidates for the coin
+ */
+ struct CoinCandidate coin_candidates[TALER_CNC_KAPPA];
+
+ /**
+ * Details of the planchet(s).
+ */
+ struct TALER_PlanchetDetail planchet_details[TALER_CNC_KAPPA];
+
+ /**
+ * Closure for each candidate of type CS for the preflight request to
+ * /csr-withdraw
+ */
+ struct CSRClosure csr_cls[TALER_CNC_KAPPA];
+};
+
+/**
+ * A /reserves/$RESERVE_PUB/age-withdraw request-handle for calls with
+ * pre-blinded planchets. Returned by TALER_EXCHANGE_age_withdraw_blinded.
+ */
+struct TALER_EXCHANGE_AgeWithdrawBlindedHandle
+{
+
+ /**
+ * 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 age mask, extracted from the denominations.
+ * MUST be the same for all denominations
+ *
+ */
+ struct TALER_AgeMask age_mask;
+
+ /**
+ * Maximum age to commit to.
+ */
+ uint8_t max_age;
+
+ /**
+ * The commitment calculated as SHA512 hash over all blinded_coin_h
+ */
+ struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+
+ /**
+ * Total amount requested (value plus withdraw fee).
+ */
+ struct TALER_Amount amount_with_fee;
+
+ /**
+ * Length of the @e blinded_input Array
+ */
+ size_t num_input;
+
+ /**
+ * The blinded planchet input for the call to /age-withdraw via
+ * TALER_EXCHANGE_age_withdraw_blinded
+ */
+ const struct TALER_EXCHANGE_AgeWithdrawBlindedInput *blinded_input;
+
+ /**
+ * 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 age-withdraw response results.
+ */
+ TALER_EXCHANGE_AgeWithdrawBlindedCallback callback;
+
+ /**
+ * Closure for @e blinded_callback
+ */
+ void *callback_cls;
+};
+
+/**
+ * A /reserves/$RESERVE_PUB/age-withdraw request-handle for calls from
+ * a wallet, i. e. when blinding data is available.
+ */
+struct TALER_EXCHANGE_AgeWithdrawHandle
+{
+
+ /**
+ * Length of the @e coin_data Array
+ */
+ size_t num_coins;
+
+ /**
+ * The base-URL of the exchange.
+ */
+ const char *exchange_url;
+
+ /**
+ * 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 age mask, extracted from the denominations.
+ * MUST be the same for all denominations
+ *
+ */
+ struct TALER_AgeMask age_mask;
+
+ /**
+ * Maximum age to commit to.
+ */
+ uint8_t max_age;
+
+ /**
+ * Array of per-coin data
+ */
+ struct CoinData *coin_data;
+
+ /**
+ * Context for curl.
+ */
+ struct GNUNET_CURL_Context *curl_ctx;
+
+ struct
+ {
+ /**
+ * Number of /csr-withdraw requests still pending.
+ */
+ unsigned int pending;
+
+ /**
+ * CURL handle for the request job.
+ */
+ struct GNUNET_CURL_Job *job;
+ } csr;
+
+
+ /**
+ * Function to call with age-withdraw response results.
+ */
+ TALER_EXCHANGE_AgeWithdrawCallback callback;
+
+ /**
+ * Closure for @e age_withdraw_cb
+ */
+ void *callback_cls;
+
+ /* The Handler for the actual call to the exchange */
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *procotol_handle;
+};
+
+/**
+ * We got a 200 OK response for the /reserves/$RESERVE_PUB/age-withdraw operation.
+ * Extract the noreveal_index and return it to the caller.
+ *
+ * @param awbh operation handle
+ * @param j_response reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+reserve_age_withdraw_ok (
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh,
+ const json_t *j_response)
+{
+ struct TALER_EXCHANGE_AgeWithdrawBlindedResponse response = {
+ .hr.reply = j_response,
+ .hr.http_status = MHD_HTTP_OK,
+ .details.ok.h_commitment = awbh->h_commitment
+ };
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint8 ("noreveal_index",
+ &response.details.ok.noreveal_index),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &response.details.ok.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_age_withdraw_confirmation_verify (
+ &awbh->h_commitment,
+ response.details.ok.noreveal_index,
+ &response.details.ok.exchange_pub,
+ &exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+
+ }
+
+ awbh->callback (awbh->callback_cls,
+ &response);
+ /* make sure the callback isn't called again */
+ awbh->callback = NULL;
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RESERVE_PUB/age-withdraw request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_AgeWithdrawHandle`
+ * @param response_code The HTTP response code
+ * @param response response data
+ */
+static void
+handle_reserve_age_withdraw_blinded_finished (
+ void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh = cls;
+ const json_t *j_response = response;
+ struct TALER_EXCHANGE_AgeWithdrawBlindedResponse awbr = {
+ .hr.reply = j_response,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ awbh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ awbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ reserve_age_withdraw_ok (awbh,
+ j_response))
+ {
+ GNUNET_break_op (0);
+ awbr.hr.http_status = 0;
+ awbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == awbh->callback);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh);
+ 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 */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.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 */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.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. */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* The age requirements might not have been met */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.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 */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ /* only validate reply is well-formed */
+ {
+ uint64_t ptu;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint64 ("requirement_row",
+ &ptu),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ awbr.hr.http_status = 0;
+ awbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange age-withdraw\n",
+ (unsigned int) response_code,
+ (int) awbr.hr.ec);
+ break;
+ }
+ awbh->callback (awbh->callback_cls,
+ &awbr);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh);
+}
+
+
+/**
+ * Runs the actual age-withdraw operation with the blinded planchets.
+ *
+ * @param[in,out] awbh age withdraw handler
+ */
+static void
+perform_protocol (
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh)
+{
+#define FAIL_IF(cond) \
+ do { \
+ if ((cond)) \
+ { \
+ GNUNET_break (! (cond)); \
+ goto ERROR; \
+ } \
+ } while (0)
+
+ struct GNUNET_HashContext *coins_hctx = NULL;
+ json_t *j_denoms = NULL;
+ json_t *j_array_candidates = NULL;
+ json_t *j_request_body = NULL;
+ CURL *curlh = NULL;
+
+ GNUNET_assert (0 < awbh->num_input);
+ awbh->age_mask = awbh->blinded_input[0].denom_pub->key.age_mask;
+
+ FAIL_IF (GNUNET_OK !=
+ TALER_amount_set_zero (awbh->keys->currency,
+ &awbh->amount_with_fee));
+ /* Accumulate total value with fees */
+ for (size_t i = 0; i < awbh->num_input; i++)
+ {
+ struct TALER_Amount coin_total;
+ const struct TALER_EXCHANGE_DenomPublicKey *dpub =
+ awbh->blinded_input[i].denom_pub;
+
+ FAIL_IF (0 >
+ TALER_amount_add (&coin_total,
+ &dpub->fees.withdraw,
+ &dpub->value));
+ FAIL_IF (0 >
+ TALER_amount_add (&awbh->amount_with_fee,
+ &awbh->amount_with_fee,
+ &coin_total));
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Attempting to age-withdraw from reserve %s with maximum age %d\n",
+ TALER_B2S (&awbh->reserve_pub),
+ awbh->max_age);
+
+ coins_hctx = GNUNET_CRYPTO_hash_context_start ();
+ FAIL_IF (NULL == coins_hctx);
+
+
+ j_denoms = json_array ();
+ j_array_candidates = json_array ();
+ FAIL_IF ((NULL == j_denoms) ||
+ (NULL == j_array_candidates));
+
+ for (size_t i = 0; i< awbh->num_input; i++)
+ {
+ /* Build the denomination array */
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub =
+ awbh->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 (awbh->age_mask.bits != denom_pub->key.age_mask.bits);
+
+ jdenom = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto (NULL,
+ denom_h));
+ FAIL_IF (NULL == jdenom);
+ FAIL_IF (0 > json_array_append_new (j_denoms,
+ jdenom));
+
+ /* Build the candidate array */
+ {
+ json_t *j_can = json_array ();
+ FAIL_IF (NULL == j_can);
+
+ for (size_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ struct TALER_BlindedCoinHashP bch;
+ const struct TALER_PlanchetDetail *planchet =
+ &awbh->blinded_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_can,
+ jc));
+
+ TALER_coin_ev_hash (&planchet->blinded_planchet,
+ &planchet->denom_pub_hash,
+ &bch);
+
+ GNUNET_CRYPTO_hash_context_read (coins_hctx,
+ &bch,
+ sizeof(bch));
+ }
+
+ FAIL_IF (0 > json_array_append_new (j_array_candidates,
+ j_can));
+ }
+ }
+ }
+
+ /* Build the hash of the commitment */
+ GNUNET_CRYPTO_hash_context_finish (coins_hctx,
+ &awbh->h_commitment.hash);
+ coins_hctx = NULL;
+
+ /* Sign the request */
+ TALER_wallet_age_withdraw_sign (&awbh->h_commitment,
+ &awbh->amount_with_fee,
+ &awbh->age_mask,
+ awbh->max_age,
+ awbh->reserve_priv,
+ &awbh->reserve_sig);
+
+ /* Initiate the POST-request */
+ j_request_body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("denom_hs", j_denoms),
+ GNUNET_JSON_pack_array_steal ("blinded_coin_evs", j_array_candidates),
+ GNUNET_JSON_pack_uint64 ("max_age", awbh->max_age),
+ GNUNET_JSON_pack_data_auto ("reserve_sig", &awbh->reserve_sig));
+ FAIL_IF (NULL == j_request_body);
+
+ curlh = TALER_EXCHANGE_curl_easy_get_ (awbh->request_url);
+ FAIL_IF (NULL == curlh);
+ FAIL_IF (GNUNET_OK !=
+ TALER_curl_easy_post (&awbh->post_ctx,
+ curlh,
+ j_request_body));
+ json_decref (j_request_body);
+ j_request_body = NULL;
+
+ awbh->job = GNUNET_CURL_job_add2 (
+ awbh->curl_ctx,
+ curlh,
+ awbh->post_ctx.headers,
+ &handle_reserve_age_withdraw_blinded_finished,
+ awbh);
+ FAIL_IF (NULL == awbh->job);
+
+ /* No errors, return */
+ return;
+
+ERROR:
+ if (NULL != j_denoms)
+ json_decref (j_denoms);
+ if (NULL != j_array_candidates)
+ json_decref (j_array_candidates);
+ if (NULL != j_request_body)
+ json_decref (j_request_body);
+ if (NULL != curlh)
+ curl_easy_cleanup (curlh);
+ if (NULL != coins_hctx)
+ GNUNET_CRYPTO_hash_context_abort (coins_hctx);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh);
+ return;
+#undef FAIL_IF
+}
+
+
+/**
+ * @brief Callback to copy the results from the call to TALER_age_withdraw_blinded
+ * to the result for the originating call from TALER_age_withdraw.
+ *
+ * @param cls struct TALER_AgeWithdrawHandle
+ * @param awbr The response
+ */
+static void
+copy_results (
+ void *cls,
+ const struct TALER_EXCHANGE_AgeWithdrawBlindedResponse *awbr)
+{
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh = cls;
+ uint8_t k = awbr->details.ok.noreveal_index;
+ struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails details[awh->num_coins];
+ struct TALER_BlindedCoinHashP blinded_coin_hs[awh->num_coins];
+ struct TALER_EXCHANGE_AgeWithdrawResponse resp = {
+ .hr = awbr->hr,
+ .details = {
+ .ok = {
+ .noreveal_index = awbr->details.ok.noreveal_index,
+ .h_commitment = awbr->details.ok.h_commitment,
+ .exchange_pub = awbr->details.ok.exchange_pub,
+ .num_coins = awh->num_coins,
+ .coin_details = details,
+ .blinded_coin_hs = blinded_coin_hs
+ },
+ },
+ };
+
+ for (size_t n = 0; n< awh->num_coins; n++)
+ {
+ details[n] = awh->coin_data[n].coin_candidates[k].details;
+ details[n].planchet = awh->coin_data[n].planchet_details[k];
+ blinded_coin_hs[n] = awh->coin_data[n].coin_candidates[k].blinded_coin_h;
+ }
+ awh->callback (awh->callback_cls,
+ &resp);
+ awh->callback = NULL;
+}
+
+
+/**
+ * @brief Prepares and executes TALER_EXCHANGE_age_withdraw_blinded.
+ * If there were CS-denominations involved, started once the all calls
+ * to /csr-withdraw are done.
+ */
+static void
+call_age_withdraw_blinded (
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh)
+{
+ struct TALER_EXCHANGE_AgeWithdrawBlindedInput blinded_input[awh->num_coins];
+
+ /* Prepare the blinded planchets as input */
+ for (size_t n = 0; n < awh->num_coins; n++)
+ {
+ blinded_input[n].denom_pub = &awh->coin_data[n].denom_pub;
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ blinded_input[n].planchet_details[k] =
+ awh->coin_data[n].planchet_details[k];
+ }
+
+ awh->procotol_handle =
+ TALER_EXCHANGE_age_withdraw_blinded (
+ awh->curl_ctx,
+ awh->keys,
+ awh->exchange_url,
+ awh->reserve_priv,
+ awh->max_age,
+ awh->num_coins,
+ blinded_input,
+ copy_results,
+ awh);
+}
+
+
+/**
+ * Prepares the request URL for the age-withdraw request
+ *
+ * @param awbh The handler
+ * @param exchange_url The base-URL to the exchange
+ */
+static
+enum GNUNET_GenericReturnValue
+prepare_url (
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh,
+ const char *exchange_url)
+{
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+ char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &awbh->reserve_pub,
+ sizeof (awbh->reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s/age-withdraw",
+ pub_str);
+
+ awbh->request_url = TALER_url_join (exchange_url,
+ arg_str,
+ NULL);
+ if (NULL == awbh->request_url)
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * @brief Function called when CSR withdraw retrieval is finished
+ *
+ * @param cls the `struct CSRClosure *`
+ * @param csrr replies from the /csr-withdraw request
+ */
+static void
+csr_withdraw_done (
+ void *cls,
+ const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr)
+{
+ struct CSRClosure *csr = cls;
+ struct CoinCandidate *can;
+ struct TALER_PlanchetDetail *planchet;
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh;
+
+ GNUNET_assert (NULL != csr);
+ awh = csr->age_withdraw_handle;
+ planchet = csr->planchet;
+ can = csr->candidate;
+
+ GNUNET_assert (NULL != can);
+ GNUNET_assert (NULL != planchet);
+ GNUNET_assert (NULL != awh);
+
+ csr->csr_withdraw_handle = NULL;
+
+ switch (csrr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ {
+ bool success = false;
+ /* Complete the initialization of the coin with CS denomination */
+
+ TALER_denom_ewv_copy (&can->details.alg_values,
+ &csrr->details.ok.alg_values);
+ GNUNET_assert (can->details.alg_values.blinding_inputs->cipher
+ == GNUNET_CRYPTO_BSA_CS);
+ TALER_planchet_setup_coin_priv (&can->secret,
+ &can->details.alg_values,
+ &can->details.coin_priv);
+ TALER_planchet_blinding_secret_create (&can->secret,
+ &can->details.alg_values,
+ &can->details.blinding_key);
+ /* This initializes the 2nd half of the
+ can->planchet_detail.blinded_planchet! */
+ do {
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&csr->denom_pub->key,
+ &can->details.alg_values,
+ &can->details.blinding_key,
+ &csr->nonce,
+ &can->details.coin_priv,
+ &can->details.h_age_commitment,
+ &can->details.h_coin_pub,
+ planchet))
+ {
+ GNUNET_break (0);
+ break;
+ }
+
+ TALER_coin_ev_hash (&planchet->blinded_planchet,
+ &planchet->denom_pub_hash,
+ &can->blinded_coin_h);
+ success = true;
+ } while (0);
+
+ awh->csr.pending--;
+
+ /* No more pending requests to /csr-withdraw, we can now perform the
+ * actual age-withdraw operation */
+ if (0 == awh->csr.pending && success)
+ call_age_withdraw_blinded (awh);
+ return;
+ }
+ default:
+ break;
+ }
+ TALER_EXCHANGE_age_withdraw_cancel (awh);
+}
+
+
+/**
+ * @brief Prepare the coins for the call to age-withdraw and calculates
+ * the total amount with fees.
+ *
+ * For denomination with CS as cipher, initiates the preflight to retrieve the
+ * csr-parameter via /csr-withdraw.
+ *
+ * @param awh The handler to the age-withdraw
+ * @param num_coins The number of coins in @e coin_inputs
+ * @param coin_inputs The input for the individual coin(-candidates)
+ * @return GNUNET_OK on success, GNUNET_SYSERR on failure
+ */
+static
+enum GNUNET_GenericReturnValue
+prepare_coins (
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh,
+ size_t num_coins,
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput coin_inputs[
+ static num_coins])
+{
+#define FAIL_IF(cond) \
+ do { \
+ if ((cond)) \
+ { \
+ GNUNET_break (! (cond)); \
+ goto ERROR; \
+ } \
+ } while (0)
+
+ GNUNET_assert (0 < num_coins);
+ awh->age_mask = coin_inputs[0].denom_pub->key.age_mask;
+
+ awh->coin_data = GNUNET_new_array (awh->num_coins,
+ struct CoinData);
+
+ for (size_t i = 0; i < num_coins; i++)
+ {
+ struct CoinData *cd = &awh->coin_data[i];
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput *input = &coin_inputs[i];
+
+ cd->denom_pub = *input->denom_pub;
+ /* The mask must be the same for all coins */
+ FAIL_IF (awh->age_mask.bits != input->denom_pub->key.age_mask.bits);
+ TALER_denom_pub_copy (&cd->denom_pub.key,
+ &input->denom_pub->key);
+
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ struct CoinCandidate *can = &cd->coin_candidates[k];
+ struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
+
+ can->secret = input->secrets[k];
+ /* Derive the age restriction from the given secret and
+ * the maximum age */
+ TALER_age_restriction_from_secret (
+ &can->secret,
+ &input->denom_pub->key.age_mask,
+ awh->max_age,
+ &can->details.age_commitment_proof);
+
+ TALER_age_commitment_hash (&can->details.age_commitment_proof.commitment,
+ &can->details.h_age_commitment);
+
+ switch (input->denom_pub->key.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ TALER_denom_ewv_copy (&can->details.alg_values,
+ TALER_denom_ewv_rsa_singleton ());
+ TALER_planchet_setup_coin_priv (&can->secret,
+ &can->details.alg_values,
+ &can->details.coin_priv);
+ TALER_planchet_blinding_secret_create (&can->secret,
+ &can->details.alg_values,
+ &can->details.blinding_key);
+ FAIL_IF (GNUNET_OK !=
+ TALER_planchet_prepare (&cd->denom_pub.key,
+ &can->details.alg_values,
+ &can->details.blinding_key,
+ NULL,
+ &can->details.coin_priv,
+ &can->details.h_age_commitment,
+ &can->details.h_coin_pub,
+ planchet));
+ TALER_coin_ev_hash (&planchet->blinded_planchet,
+ &planchet->denom_pub_hash,
+ &can->blinded_coin_h);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ {
+ struct CSRClosure *cls = &cd->csr_cls[k];
+ /**
+ * Save the handler and the denomination for the callback
+ * after the call to csr-withdraw */
+ cls->age_withdraw_handle = awh;
+ cls->candidate = can;
+ cls->planchet = planchet;
+ cls->denom_pub = &cd->denom_pub;
+ TALER_cs_withdraw_nonce_derive (
+ &can->secret,
+ &cls->nonce.cs_nonce);
+ cls->csr_withdraw_handle =
+ TALER_EXCHANGE_csr_withdraw (
+ awh->curl_ctx,
+ awh->exchange_url,
+ &cd->denom_pub,
+ &cls->nonce.cs_nonce,
+ &csr_withdraw_done,
+ cls);
+ FAIL_IF (NULL == cls->csr_withdraw_handle);
+
+ awh->csr.pending++;
+ break;
+ }
+ default:
+ FAIL_IF (1);
+ }
+ }
+ }
+ return GNUNET_OK;
+
+ERROR:
+ TALER_EXCHANGE_age_withdraw_cancel (awh);
+ return GNUNET_SYSERR;
+#undef FAIL_IF
+};
+
+struct TALER_EXCHANGE_AgeWithdrawHandle *
+TALER_EXCHANGE_age_withdraw (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ size_t num_coins,
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput coin_inputs[const static
+ num_coins],
+ uint8_t max_age,
+ TALER_EXCHANGE_AgeWithdrawCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh;
+
+ awh = GNUNET_new (struct TALER_EXCHANGE_AgeWithdrawHandle);
+ awh->exchange_url = exchange_url;
+ awh->keys = TALER_EXCHANGE_keys_incref (keys);
+ awh->curl_ctx = curl_ctx;
+ awh->reserve_priv = reserve_priv;
+ awh->callback = res_cb;
+ awh->callback_cls = res_cb_cls;
+ awh->num_coins = num_coins;
+ awh->max_age = max_age;
+
+
+ if (GNUNET_OK != prepare_coins (awh,
+ num_coins,
+ coin_inputs))
+ {
+ GNUNET_free (awh);
+ return NULL;
+ }
+
+ /* If there were no CS denominations, we can now perform the actual
+ * age-withdraw protocol. Otherwise, there are calls to /csr-withdraw
+ * in flight and once they finish, the age-withdraw-protocol will be
+ * called from within the csr_withdraw_done-function.
+ */
+ if (0 == awh->csr.pending)
+ call_age_withdraw_blinded (awh);
+
+ return awh;
+}
+
+
+void
+TALER_EXCHANGE_age_withdraw_cancel (
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh)
+{
+ /* Cleanup coin data */
+ for (unsigned int i = 0; i<awh->num_coins; i++)
+ {
+ struct CoinData *cd = &awh->coin_data[i];
+
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
+ struct CSRClosure *cls = &cd->csr_cls[k];
+ struct CoinCandidate *can = &cd->coin_candidates[k];
+
+ if (NULL != cls->csr_withdraw_handle)
+ {
+ TALER_EXCHANGE_csr_withdraw_cancel (cls->csr_withdraw_handle);
+ cls->csr_withdraw_handle = NULL;
+ }
+ TALER_blinded_planchet_free (&planchet->blinded_planchet);
+ TALER_denom_ewv_free (&can->details.alg_values);
+ }
+ TALER_denom_pub_free (&cd->denom_pub.key);
+ }
+ GNUNET_free (awh->coin_data);
+ TALER_EXCHANGE_keys_decref (awh->keys);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awh->procotol_handle);
+ awh->procotol_handle = NULL;
+ GNUNET_free (awh);
+}
+
+
+struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *
+TALER_EXCHANGE_age_withdraw_blinded (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ uint8_t max_age,
+ unsigned int num_input,
+ const struct TALER_EXCHANGE_AgeWithdrawBlindedInput blinded_input[static
+ num_input],
+ TALER_EXCHANGE_AgeWithdrawBlindedCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh =
+ GNUNET_new (struct TALER_EXCHANGE_AgeWithdrawBlindedHandle);
+
+ awbh->num_input = num_input;
+ awbh->blinded_input = blinded_input;
+ awbh->keys = TALER_EXCHANGE_keys_incref (keys);
+ awbh->curl_ctx = curl_ctx;
+ awbh->reserve_priv = reserve_priv;
+ awbh->callback = res_cb;
+ awbh->callback_cls = res_cb_cls;
+ awbh->max_age = max_age;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&awbh->reserve_priv->eddsa_priv,
+ &awbh->reserve_pub.eddsa_pub);
+
+ if (GNUNET_OK != prepare_url (awbh,
+ exchange_url))
+ return NULL;
+
+ perform_protocol (awbh);
+ return awbh;
+}
+
+
+void
+TALER_EXCHANGE_age_withdraw_blinded_cancel (
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh)
+{
+ if (NULL == awbh)
+ return;
+
+ if (NULL != awbh->job)
+ {
+ GNUNET_CURL_job_cancel (awbh->job);
+ awbh->job = NULL;
+ }
+ GNUNET_free (awbh->request_url);
+ TALER_EXCHANGE_keys_decref (awbh->keys);
+ TALER_curl_easy_post_finished (&awbh->post_ctx);
+ GNUNET_free (awbh);
+}
+
+
+/* exchange_api_age_withdraw.c */