From 40629e89920267dadba39f5f7f2ab3d844088a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr=20Kesim?= Date: Mon, 3 Jul 2023 16:18:40 +0200 Subject: [age-withdraw] added library function for age-withdraw - Added TALER_EXCHANGE_age_withdraw - Also: Change TALER_EXCHANGE_batch_withdraw and related functions to use GNUNET_CURL_ctx, TALER_EXCHANGE_keys and const char *echange_url --- src/lib/exchange_api_age_withdraw.c | 1033 +++++++++++++++++++++++++++++++++++ 1 file changed, 1033 insertions(+) create mode 100644 src/lib/exchange_api_age_withdraw.c (limited to 'src/lib/exchange_api_age_withdraw.c') diff --git a/src/lib/exchange_api_age_withdraw.c b/src/lib/exchange_api_age_withdraw.c new file mode 100644 index 000000000..4e146c15c --- /dev/null +++ b/src/lib/exchange_api_age_withdraw.c @@ -0,0 +1,1033 @@ +/* + 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 + +*/ +/** + * @file lib/exchange_api_age_withdraw.c + * @brief Implementation of /reserves/$RESERVE_PUB/age-withdraw requests + * @author Özgür Kesim + */ + +#include "platform.h" +#include +#include +#include /* just for HTTP status codes */ +#include +#include +#include +#include "taler_curl_lib.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" + +struct CoinCandidate +{ + /** + * Master key material for the coin candidates. + */ + struct TALER_PlanchetMasterSecretP secret; + + /** + * Age commitment for the coin candidates, calculated from the @e ps and a + * given maximum age + */ + struct TALER_AgeCommitmentProof age_commitment_proof; + + /** + * Age commitment for the coin. + */ + struct TALER_AgeCommitmentHash h_age_commitment; + + /** + * blinding secret + */ + union TALER_DenominationBlindingKeyP blinding_key; + + /** + * Private key of the coin we are withdrawing. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + + /** + * Details of the planchet. + */ + struct TALER_PlanchetDetail planchet_detail; + + /** + * Values of the @cipher selected + */ + struct TALER_ExchangeWithdrawValues alg_values; + + /** + * Hash of the public key of the coin we are signing. + */ + struct TALER_CoinPubHashP h_coin_pub; + + /* Blinded hash of the coin */ + struct TALER_BlindedCoinHashP blinded_coin_h; + + /** + * The following fields are needed as closure for the call to /csr-withdrwaw + * per coin-candidate. + */ + + /* Denomination information, needed for CS coins for the step after /csr-withdraw */ + struct TALER_EXCHANGE_DenomPublicKey *denom_pub; + + /** + * Handler for the CS R request (only used for TALER_DENOMINATION_CS denominations) + */ + struct TALER_EXCHANGE_CsRWithdrawHandle *csr_withdraw_handle; + + /* Needed in the closure for csr-withdraw calls */ + struct TALER_EXCHANGE_AgeWithdrawHandle *age_withdraw_handle; + +}; + + +/** + * Data we keep per coin in the batch. + */ +struct CoinData +{ + + /** + * Denomination key we are withdrawing. + */ + struct TALER_EXCHANGE_DenomPublicKey denom_pub; + + /** + * The Candidates for the coin + */ + struct CoinCandidate coin_candidates[TALER_CNC_KAPPA]; + +}; + + +/** + * @brief A /reserves/$RESERVE_PUB/age-withdraw request-handle + */ +struct TALER_EXCHANGE_AgeWithdrawHandle +{ + + /** + * 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 base-URL of the exchange. + */ + const char *exchange_url; + + /** + * The age mask, extacted from the denominations. + * MUST be the same for all denominations + * + */ + struct TALER_AgeMask age_mask; + + /** + * Maximum age to commit to. + */ + uint8_t max_age; + + /** + * Length of the @e coin_data Array + */ + size_t num_coins; + + /** + * Array of per-coin data + */ + struct CoinData *coin_data; + + /** + * 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; + + /** + * Number of /csr-withdraw requests still pending. + */ + unsigned int csr_pending; + + /** + * 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_AgeWithdrawCallback callback; + + /** + * Closure for @e age_withdraw_cb + */ + void *callback_cls; + +}; + +/** + * 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 awh 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_AgeWithdrawHandle *awh, + const json_t *j_response) +{ + struct TALER_EXCHANGE_AgeWithdrawResponse response = { + .hr.reply = j_response, + .hr.http_status = MHD_HTTP_OK + }; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint8 ("noreaveal_index", + &response.details.ok.noreveal_index), + GNUNET_JSON_spec_fixed_auto ("exchange_sig", + &response.details.ok.exchange_sig), + GNUNET_JSON_spec_fixed_auto ("exchange_pub", + &response.details.ok.exchange_pub) + }; + + 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 ( + &awh->h_commitment, + response.details.ok.noreveal_index, + &response.details.ok.exchange_pub, + &response.details.ok.exchange_sig)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + + } + awh->callback (awh->callback_cls, + &response); + /* make sure the callback isn't called again */ + awh->callback = NULL; + + return GNUNET_OK; +} + + +/** + * FIXME: This function should be common to batch- and age-withdraw + * + * We got a 409 CONFLICT response for the /reserves/$RESERVE_PUB/age-withdraw operation. + * Check the signatures on the batch withdraw transactions in the provided + * history and that the balances add up. We don't do anything directly + * with the information, as the JSON will be returned to the application. + * However, our job is ensuring that the exchange followed the protocol, and + * this in particular means checking all of the signatures in the history. + * + * @param keys The denomination keys from the exchange + * @param reserve_pub The reserve's public key + * @param requested_amount The requested amount + * @param json reply from the exchange + * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors + */ +static enum GNUNET_GenericReturnValue +reserve_age_withdraw_payment_required ( + struct TALER_EXCHANGE_Keys *keys, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *requested_amount, + const json_t *json) +{ + struct TALER_Amount balance; + struct TALER_Amount total_in_from_history; + struct TALER_Amount total_out_from_history; + json_t *history; + size_t len; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount_any ("balance", + &balance), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (json, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + history = json_object_get (json, + "history"); + if (NULL == history) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* go over transaction history and compute + total incoming and outgoing amounts */ + len = json_array_size (history); + { + struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory; + + /* Use heap allocation as "len" may be very big and thus this may + not fit on the stack. Use "GNUNET_malloc_large" as a malicious + exchange may theoretically try to crash us by giving a history + that does not fit into our memory. */ + rhistory = GNUNET_malloc_large ( + sizeof (struct TALER_EXCHANGE_ReserveHistoryEntry) + * len); + if (NULL == rhistory) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + TALER_EXCHANGE_parse_reserve_history ( + keys, + history, + reserve_pub, + balance.currency, + &total_in_from_history, + &total_out_from_history, + len, + rhistory)) + { + GNUNET_break_op (0); + TALER_EXCHANGE_free_reserve_history (rhistory, + len); + return GNUNET_SYSERR; + } + TALER_EXCHANGE_free_reserve_history (rhistory, + len); + } + + /* Check that funds were really insufficient */ + if (0 >= TALER_amount_cmp (requested_amount, + &balance)) + { + /* Requested amount is smaller or equal to reported balance, + so this should not have failed. */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + 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 aw2r response data + */ +static void +handle_reserve_age_withdraw_finished ( + void *cls, + long response_code, + const void *response) +{ + struct TALER_EXCHANGE_AgeWithdrawHandle *awh = cls; + const json_t *j_response = response; + struct TALER_EXCHANGE_AgeWithdrawResponse awr = { + .hr.reply = j_response, + .hr.http_status = (unsigned int) response_code + }; + + awh->job = NULL; + switch (response_code) + { + case 0: + awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + if (GNUNET_OK != + reserve_age_withdraw_ok (awh, + j_response)) + { + GNUNET_break_op (0); + awr.hr.http_status = 0; + awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + GNUNET_assert (NULL == awh->callback); + TALER_EXCHANGE_age_withdraw_cancel (awh); + return; + 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 ("legitimization_uuid", + &ptu), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j_response, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + awr.hr.http_status = 0; + awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + } + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the exchange is buggy + (or API version conflict); just pass JSON reply to the application */ + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.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 */ + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.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. */ + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_CONFLICT: + /* The exchange says that the reserve has insufficient funds; + check the signatures in the history... */ + if (GNUNET_OK != + reserve_age_withdraw_payment_required (awh->keys, + &awh->reserve_pub, + &awh->amount_with_fee, + j_response)) + { + GNUNET_break_op (0); + awr.hr.http_status = 0; + awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + } + else + { + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.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 */ + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.hr.hint = TALER_JSON_get_error_hint (j_response); + break; + default: + /* unexpected response code */ + GNUNET_break_op (0); + awr.hr.ec = TALER_JSON_get_error_code (j_response); + awr.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) awr.hr.ec); + break; + } + awh->callback (awh->callback_cls, + &awr); + TALER_EXCHANGE_age_withdraw_cancel (awh); +} + + +/** + * Runs the actual age-withdraw operation. If there were CS-denominations + * involved, started once the all calls to /csr-withdraw are done. + * + * @param[in,out] awh age withdraw handler + */ +static void +perform_protocol ( + struct TALER_EXCHANGE_AgeWithdrawHandle *awh) +{ +#define FAIL_IF(cond) \ + do { \ + if ((cond)) \ + { \ + GNUNET_break (! (cond)); \ + goto ERROR; \ + } \ + } while(0) + + struct GNUNET_HashContext *coins_hctx; + json_t *j_denoms = NULL; + json_t *j_array_candidates = NULL; + json_t *j_request_body = NULL; + CURL *curlh = NULL; + + + GNUNET_assert (0 == awh->csr_pending); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Attempting to age-withdraw from reserve %s with maximum age %d\n", + TALER_B2S (&awh->reserve_pub), + awh->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< awh->num_coins; i++) + { + /* Build the denomination array */ + { + struct TALER_EXCHANGE_DenomPublicKey *denom = + &awh->coin_data[i].denom_pub; + json_t *jdenom = GNUNET_JSON_PACK ( + TALER_JSON_pack_denom_pub (NULL, + &denom->key)); + + FAIL_IF (NULL == jdenom); + FAIL_IF (0 < json_array_append_new (j_denoms, + jdenom)); + + /* Build the candidate array */ + { + const struct CoinCandidate *can = awh->coin_data[i].coin_candidates; + json_t *j_can = json_array (); + FAIL_IF (NULL == j_can); + + for (size_t k = 0; k < TALER_CNC_KAPPA; k++) + { + json_t *jc = GNUNET_JSON_PACK ( + TALER_JSON_pack_blinded_planchet ( + NULL, + &can->planchet_detail.blinded_planchet)); + + FAIL_IF (NULL == jc); + FAIL_IF (0 < json_array_append_new (j_can, + jc)); + + GNUNET_CRYPTO_hash_context_read (coins_hctx, + &can->blinded_coin_h, + sizeof(can->blinded_coin_h)); + } + } + } + } + + /* Sign the request */ + { + struct TALER_AgeWithdrawCommitmentHashP coins_commitment_h; + + GNUNET_CRYPTO_hash_context_finish (coins_hctx, + &coins_commitment_h.hash); + + TALER_wallet_age_withdraw_sign (&coins_commitment_h, + &awh->amount_with_fee, + &awh->age_mask, + awh->max_age, + awh->reserve_priv, + &awh->reserve_sig); + } + + /* Initiate the POST-request */ + j_request_body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_steal ("denoms_h", j_denoms), + GNUNET_JSON_pack_array_steal ("blinded_coin_evs", j_array_candidates), + GNUNET_JSON_pack_uint64 ("max_age", awh->max_age), + GNUNET_JSON_pack_data_auto ("reserve_sig", &awh->reserve_sig)); + FAIL_IF (NULL == j_request_body); + + curlh = TALER_EXCHANGE_curl_easy_get_ (awh->request_url); + FAIL_IF (NULL == curlh); + FAIL_IF (GNUNET_OK != + TALER_curl_easy_post (&awh->post_ctx, + curlh, + j_request_body)); + json_decref (j_request_body); + j_request_body = NULL; + + awh->job = GNUNET_CURL_job_add2 (awh->curl_ctx, + curlh, + awh->post_ctx.headers, + &handle_reserve_age_withdraw_finished, + awh); + FAIL_IF (NULL == awh->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); + TALER_EXCHANGE_age_withdraw_cancel (awh); + return; +#undef FAIL_IF +} + + +/** + * Prepares the request URL for the age-withdraw request + * + * @param awh The handler + */ +static +enum GNUNET_GenericReturnValue +prepare_url ( + struct TALER_EXCHANGE_AgeWithdrawHandle *awh, + 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 ( + &awh->reserve_pub, + sizeof (awh->reserve_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + GNUNET_snprintf (arg_str, + sizeof (arg_str), + "reserves/%s/age-withdraw", + pub_str); + + awh->request_url = TALER_url_join (exchange_url, + arg_str, + NULL); + if (NULL == awh->request_url) + { + GNUNET_break (0); + TALER_EXCHANGE_age_withdraw_cancel (awh); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +/** + * @brief Function called when CSR withdraw retrieval is finished + * + * @param cls the `struct CoinCandidate *` + * @param csrr replies from the /csr-withdraw request + */ +static void +csr_withdraw_done ( + void *cls, + const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr) +{ + struct CoinCandidate *can = cls; + struct TALER_EXCHANGE_AgeWithdrawHandle *awh = can->age_withdraw_handle; + struct TALER_EXCHANGE_AgeWithdrawResponse awr = { .hr = csrr->hr }; + + can->csr_withdraw_handle = NULL; + + switch (csrr->hr.http_status) + { + case MHD_HTTP_OK: + { + bool success = true; + /* Complete the initialization of the coin with CS denomination */ + can->alg_values = csrr->details.ok.alg_values; + TALER_planchet_setup_coin_priv (&can->secret, + &can->alg_values, + &can->coin_priv); + TALER_planchet_blinding_secret_create (&can->secret, + &can->alg_values, + &can->blinding_key); + /* This initializes the 2nd half of the + can->planchet_detail.blinded_planchet! */ + if (GNUNET_OK != + TALER_planchet_prepare (&can->denom_pub->key, + &can->alg_values, + &can->blinding_key, + &can->coin_priv, + &can->h_age_commitment, + &can->h_coin_pub, + &can->planchet_detail)) + { + GNUNET_break (0); + success = false; + TALER_EXCHANGE_age_withdraw_cancel (awh); + } + + if (GNUNET_OK != + TALER_coin_ev_hash (&can->planchet_detail.blinded_planchet, + &can->planchet_detail.denom_pub_hash, + &can->blinded_coin_h)) + { + GNUNET_break (0); + success = false; + TALER_EXCHANGE_age_withdraw_cancel (awh); + } + + 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) + perform_protocol (awh); + return; + } + default: + break; + } + + awh->callback (awh->callback_cls, + &awr); + 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 coin_inputs The input for the individial coin(-candidates) + * @param num_coins The number of coins in @e coin_inputs + * + * @return GNUNET_OK on success, GNUNET_SYSERR on failure + */ +static +enum GNUNET_GenericReturnValue +prepare_coins ( + struct TALER_EXCHANGE_AgeWithdrawHandle *awh, + const struct TALER_EXCHANGE_AgeWithdrawCoinInput *coin_inputs, + size_t num_coins) +{ + + if (GNUNET_OK != TALER_amount_set_zero ( + awh->keys->currency, + &awh->amount_with_fee)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + awh->coin_data = GNUNET_new_array (awh->num_coins, + struct CoinData); + + GNUNET_assert (0 < num_coins); + awh->age_mask = coin_inputs[0].denom_pub->key.age_mask; + + 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 */ + if (awh->age_mask.bits != input->denom_pub->key.age_mask.bits) + { + GNUNET_break (0); + TALER_EXCHANGE_age_withdraw_cancel (awh); + return GNUNET_SYSERR; + } + + TALER_denom_pub_deep_copy (&cd->denom_pub.key, + &input->denom_pub->key); + + /* Accumulate total value with fees */ + { + struct TALER_Amount coin_total; + + if (0 > + TALER_amount_add (&coin_total, + &cd->denom_pub.fees.withdraw, + &cd->denom_pub.value)) + { + GNUNET_break (0); + TALER_EXCHANGE_age_withdraw_cancel (awh); + return GNUNET_SYSERR; + } + + if (0 > + TALER_amount_add (&awh->amount_with_fee, + &awh->amount_with_fee, + &coin_total)) + { + /* Overflow here? Very strange, our CPU must be fried... */ + GNUNET_break (0); + TALER_EXCHANGE_age_withdraw_cancel (awh); + return GNUNET_SYSERR; + } + } + + for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) + { + struct CoinCandidate *can = &cd->coin_candidates[k]; + + can->secret = input->secret[k]; + + /* Derive the age restriction from the given secret and + * the maximum age */ + GNUNET_assert (GNUNET_OK == + TALER_age_restriction_from_secret ( + &can->secret, + &input->denom_pub->key.age_mask, + awh->max_age, + &can->age_commitment_proof)); + TALER_age_commitment_hash (&can->age_commitment_proof.commitment, + &can->h_age_commitment); + + switch (input->denom_pub->key.cipher) + { + case TALER_DENOMINATION_RSA: + { + can->alg_values.cipher = TALER_DENOMINATION_RSA; + TALER_planchet_setup_coin_priv (&can->secret, + &can->alg_values, + &can->coin_priv); + TALER_planchet_blinding_secret_create (&can->secret, + &can->alg_values, + &can->blinding_key); + if (GNUNET_OK != + TALER_planchet_prepare (&cd->denom_pub.key, + &can->alg_values, + &can->blinding_key, + &can->coin_priv, + &can->h_age_commitment, + &can->h_coin_pub, + &can->planchet_detail)) + { + GNUNET_break (0); + TALER_EXCHANGE_age_withdraw_cancel (awh); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_coin_ev_hash (&can->planchet_detail.blinded_planchet, + &can->planchet_detail.denom_pub_hash, + &can->blinded_coin_h)) + { + GNUNET_break (0); + TALER_EXCHANGE_age_withdraw_cancel (awh); + return GNUNET_SYSERR; + } + break; + } + case TALER_DENOMINATION_CS: + { + /** + * Save the handler and the denomination for the callback + * after the call to csr-withdraw */ + can->age_withdraw_handle = awh; + can->denom_pub = &cd->denom_pub; + + TALER_cs_withdraw_nonce_derive ( + &can->secret, + &can->planchet_detail + .blinded_planchet + .details + .cs_blinded_planchet + .nonce); + + /* Note that we only initialize the first half + of the blinded_planchet here; the other part + will be done after the /csr-withdraw request! */ + can->planchet_detail.blinded_planchet.cipher = TALER_DENOMINATION_CS; + can->csr_withdraw_handle = NULL; + TALER_EXCHANGE_csr_withdraw ( + awh->curl_ctx, + awh->exchange_url, + &cd->denom_pub, + &can->planchet_detail + .blinded_planchet + .details + .cs_blinded_planchet + .nonce, + &csr_withdraw_done, + &can); + if (NULL == can->csr_withdraw_handle) + { + GNUNET_break (0); + TALER_EXCHANGE_age_withdraw_cancel (awh); + return GNUNET_SYSERR; + } + + awh->csr_pending++; + break; + } + default: + GNUNET_break (0); + TALER_EXCHANGE_age_withdraw_cancel (awh); + return GNUNET_SYSERR; + } + } + } + return GNUNET_OK; +}; + +struct TALER_EXCHANGE_AgeWithdrawHandle * +TALER_EXCHANGE_age_withdraw ( + struct GNUNET_CURL_Context *curl_ctx, + const char *exchange_url, + struct TALER_EXCHANGE_Keys *keys, + const struct TALER_ReservePrivateKeyP *reserve_priv, + const struct TALER_EXCHANGE_AgeWithdrawCoinInput *coin_inputs, + size_t 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; + + GNUNET_CRYPTO_eddsa_key_get_public (&awh->reserve_priv->eddsa_priv, + &awh->reserve_pub.eddsa_pub); + + + if (GNUNET_OK != prepare_url (awh, + exchange_url)) + return NULL; + + if (GNUNET_OK != prepare_coins (awh, + coin_inputs, + num_coins)) + 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) + perform_protocol (awh); + + return awh; +} + + +void +TALER_EXCHANGE_age_withdraw_cancel ( + struct TALER_EXCHANGE_AgeWithdrawHandle *awh) +{ + /* Cleanup coin data */ + for (unsigned int i = 0; inum_coins; i++) + { + struct CoinData *cd = &awh->coin_data[i]; + + for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++) + { + struct CoinCandidate *can = &cd->coin_candidates[k]; + + if (NULL != can->csr_withdraw_handle) + { + TALER_EXCHANGE_csr_withdraw_cancel (can->csr_withdraw_handle); + can->csr_withdraw_handle = NULL; + } + TALER_blinded_planchet_free (&can->planchet_detail.blinded_planchet); + } + TALER_denom_pub_free (&cd->denom_pub.key); + } + GNUNET_free (awh->coin_data); + + /* Cleanup CURL job data */ + if (NULL != awh->job) + { + GNUNET_CURL_job_cancel (awh->job); + awh->job = NULL; + } + TALER_curl_easy_post_finished (&awh->post_ctx); + TALER_EXCHANGE_keys_decref (awh->keys); + GNUNET_free (awh->request_url); + GNUNET_free (awh); + +} + + +/* exchange_api_age_withdraw.c */ -- cgit v1.2.3