diff --git a/src/exchange/taler-exchange-httpd_age-withdraw.c b/src/exchange/taler-exchange-httpd_age-withdraw.c index c451e5266..2f30ad8f6 100644 --- a/src/exchange/taler-exchange-httpd_age-withdraw.c +++ b/src/exchange/taler-exchange-httpd_age-withdraw.c @@ -69,6 +69,7 @@ struct AgeWithdrawContext /** * kappa * #num_coins hashes of blinded coin planchets. + * FIXME[oec]: Make the [][] structure more explicit. */ struct TALER_BlindedPlanchet *coin_evs; diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index 3af5d326d..3ad441cb9 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -439,7 +439,9 @@ struct TALER_AgeCommitmentPublicKeyP /* - * @brief Hash to represent the commitment to n*kappa blinded keys during a age-withdrawal. + * @brief Hash to represent the commitment to n*kappa blinded keys during a + * age-withdrawal. It is the running SHA512 hash over the hashes of the blinded + * envelopes of n*kappa coins. */ struct TALER_AgeWithdrawCommitmentHashP { @@ -3726,7 +3728,7 @@ TALER_wallet_withdraw_verify ( /** * Sign age-withdraw request. * - * @param h_commitment hash all n*kappa blinded coins in the commitment for the age-withdraw + * @param h_commitment hash over all n*kappa blinded coins in the commitment for the age-withdraw * @param amount_with_fee amount to debit the reserve for * @param mask the mask that defines the age groups * @param max_age maximum age from which the age group is derived, that the withdrawn coins must be restricted to. @@ -3762,7 +3764,6 @@ TALER_wallet_age_withdraw_verify ( const struct TALER_ReservePublicKeyP *reserve_pub, const struct TALER_ReserveSignatureP *reserve_sig); - /** * Verify exchange melt confirmation. * @@ -4871,6 +4872,22 @@ TALER_exchange_online_age_withdraw_confirmation_sign ( struct TALER_ExchangeSignatureP *sig); +/** + * Verfiy an exchange age-withdraw confirmation + * + * @param h_commitment Commitment over all n*kappa coin candidates from the original request to age-withdraw + * @param noreveal_index The index returned by the exchange + * @param exchange_pub The public key used for signing + * @param exchange_sig The signature from the exchange + */ +enum GNUNET_GenericReturnValue +TALER_exchange_online_age_withdraw_confirmation_verify ( + const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, + uint32_t noreveal_index, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const struct TALER_ExchangeSignatureP *exchange_sig); + + /* ********************* offline signing ************************** */ diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index bc6a230b6..d4efc15d5 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -18,6 +18,7 @@ * @brief C interface of libtalerexchange, a C library to use exchange's HTTP API * @author Sree Harsha Totakura * @author Christian Grothoff + * @author Özgür Kesim */ #ifndef _TALER_EXCHANGE_SERVICE_H #define _TALER_EXCHANGE_SERVICE_H @@ -1618,7 +1619,8 @@ typedef void /** * Get a CS R using a /csr-withdraw request. * - * @param exchange the exchange handle; the exchange must be ready to operate + * @param curl_ctx The curl context to use for the requests + * @param exchange_url Base-URL to the excnange * @param pk Which denomination key is the /csr request for * @param nonce client nonce for the request * @param res_cb the callback to call when the final result for this request is available @@ -1629,7 +1631,8 @@ typedef void */ struct TALER_EXCHANGE_CsRWithdrawHandle * TALER_EXCHANGE_csr_withdraw ( - struct TALER_EXCHANGE_Handle *exchange, + struct GNUNET_CURL_Context *curl_ctx, + const char *exchange_url, const struct TALER_EXCHANGE_DenomPublicKey *pk, const struct TALER_CsNonce *nonce, TALER_EXCHANGE_CsRWithdrawCallback res_cb, @@ -2448,7 +2451,9 @@ typedef void * disk before calling, and be ready to repeat the request with the * same arguments in case of failures. * - * @param exchange the exchange handle; the exchange must be ready to operate + * @param curl_ctx The curl context to use + * @param exchange_url The base-URL of the exchange + * @param keys The /keys material from the exchange * @param reserve_priv private key of the reserve to withdraw from * @param wci inputs that determine the planchet * @param res_cb the callback to call when the final result for this request is available @@ -2459,7 +2464,9 @@ typedef void */ struct TALER_EXCHANGE_WithdrawHandle * TALER_EXCHANGE_withdraw ( - struct TALER_EXCHANGE_Handle *exchange, + 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_WithdrawCoinInput *wci, TALER_EXCHANGE_WithdrawCallback res_cb, @@ -2575,7 +2582,9 @@ typedef void * disk before calling, and be ready to repeat the request with the * same arguments in case of failures. * - * @param exchange the exchange handle; the exchange must be ready to operate + * @param curl_ctx The curl context to use + * @param exchange_url The base-URL of the exchange + * @param keys The /keys material from the exchange * @param reserve_priv private key of the reserve to withdraw from * @param wcis inputs that determine the planchets * @param wci_length number of entries in @a wcis @@ -2587,7 +2596,9 @@ typedef void */ struct TALER_EXCHANGE_BatchWithdrawHandle * TALER_EXCHANGE_batch_withdraw ( - struct TALER_EXCHANGE_Handle *exchange, + struct GNUNET_CURL_Context *curl_ctx, + const char *exchange_url, + const struct TALER_EXCHANGE_Keys *keys, const struct TALER_ReservePrivateKeyP *reserve_priv, const struct TALER_EXCHANGE_WithdrawCoinInput *wcis, unsigned int wci_length, @@ -2668,7 +2679,9 @@ struct TALER_EXCHANGE_Withdraw2Handle; * disk before calling, and be ready to repeat the request with the * same arguments in case of failures. * - * @param exchange the exchange handle; the exchange must be ready to operate + * @param curl_ctx The curl-context to use + * @param exchange_url The base-URL of the exchange + * @param keys The /keys material from the exchange * @param pd planchet details of the planchet to withdraw * @param reserve_priv private key of the reserve to withdraw from * @param res_cb the callback to call when the final result for this request is available @@ -2679,7 +2692,9 @@ struct TALER_EXCHANGE_Withdraw2Handle; */ struct TALER_EXCHANGE_Withdraw2Handle * TALER_EXCHANGE_withdraw2 ( - struct TALER_EXCHANGE_Handle *exchange, + struct GNUNET_CURL_Context *curl_ctx, + const char *exchange_url, + struct TALER_EXCHANGE_Keys *keys, const struct TALER_PlanchetDetail *pd, const struct TALER_ReservePrivateKeyP *reserve_priv, TALER_EXCHANGE_Withdraw2Callback res_cb, @@ -2765,7 +2780,9 @@ struct TALER_EXCHANGE_BatchWithdraw2Handle; * disk before calling, and be ready to repeat the request with the * same arguments in case of failures. * - * @param exchange the exchange handle; the exchange must be ready to operate + * @param curl_ctx The curl context to use + * @param exchange_url The base-URL of the exchange + * @param keys The /keys material from the exchange * @param pds array of planchet details of the planchet to withdraw * @param pds_length number of entries in the @a pds array * @param reserve_priv private key of the reserve to withdraw from @@ -2777,7 +2794,9 @@ struct TALER_EXCHANGE_BatchWithdraw2Handle; */ struct TALER_EXCHANGE_BatchWithdraw2Handle * TALER_EXCHANGE_batch_withdraw2 ( - struct TALER_EXCHANGE_Handle *exchange, + struct GNUNET_CURL_Context *curl_ctx, + const char *exchange_url, + const struct TALER_EXCHANGE_Keys *keys, const struct TALER_ReservePrivateKeyP *reserve_priv, const struct TALER_PlanchetDetail *pds, unsigned int pds_length, @@ -2796,6 +2815,119 @@ TALER_EXCHANGE_batch_withdraw2_cancel ( struct TALER_EXCHANGE_BatchWithdraw2Handle *wh); +/* ********************* /reserve/$RESERVE_PUB/age-withdraw *************** */ + +/** + * @brief Information needed to withdraw age restricted coins. + */ +struct TALER_EXCHANGE_AgeWithdrawCoinInput +{ + /* The master secret from which we derive all other relevant values for + * the coin: private key, nonces (if applicable) and age restriction + */ + const struct TALER_PlanchetMasterSecretP secret[TALER_CNC_KAPPA]; + + /* The denomination of the coin. Must support age restriction, i.e + * its .keys.age_mask MUST not be 0 */ + const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; +}; + +/** + * @brief A handle to a /reserves/$RESERVE_PUB/age-withdraw request + */ +struct TALER_EXCHANGE_AgeWithdrawHandle; + +/** + * @brief Details about the response for a age withdraw request. + */ +struct TALER_EXCHANGE_AgeWithdrawResponse +{ + /** + * HTTP response data. + */ + struct TALER_EXCHANGE_HttpResponse hr; + + /** + * Details about the response + */ + union + { + /** + * Details if the status is #MHD_HTTP_OK. + */ + struct + { + /** + * Index that should not be revealed during the age-withdraw reveal phase. + * The struct TALER_PlanchetMasterSecretP * from the request + * with this index are the ones to keep. + */ + uint8_t noreveal_index; + + /** + * Signature of the exchange over the origina TALER_AgeWithdrawRequestPS + */ + struct TALER_ExchangeSignatureP exchange_sig; + + /** + * Key used by the exchange for @e exchange_sig + */ + struct TALER_ExchangePublicKeyP exchange_pub; + + } ok; + /* FIXME[oec]: error cases */ + } details; +}; + +typedef void +(*TALER_EXCHANGE_AgeWithdrawCallback)( + void *cls, + const struct TALER_EXCHANGE_AgeWithdrawResponse *awr); + +/** + * Submit an age-withdraw request to the exchange and get the exchange's + * response. + * + * This API is typically used by a wallet. Note that to ensure that + * no money is lost in case of hardware failures, the provided + * argument @a rd should be committed to persistent storage + * prior to calling this function. + * + * @param curl_ctx The curl context + * @param exchange_url The base url of the exchange + * @parm keys The denomination keys from the exchange + * @param reserve_priv The pivate key to the reserve + * @param coin_inputs The input for the coins to withdraw + * @param num_coins The number of elements in @e coin_inputs + * @param max_age The maximum age we commit to. + * @param res_cb A callback for the result, maybe NULL + * @param res_cb_cls A closure for @e res_cb, maybe NULL + * @return a handle for this request; NULL if the argument was invalid. + * In this case, the callback will not be called. + */ +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); + +/** + * Cancel a age-withdraw request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param awh the age-withdraw handle + */ +void +TALER_EXCHANGE_age_withdraw_cancel ( + struct TALER_EXCHANGE_AgeWithdrawHandle *awh); + + /* ********************* /refresh/melt+reveal ***************************** */ @@ -3565,7 +3697,7 @@ TALER_EXCHANGE_verify_coin_history ( */ enum GNUNET_GenericReturnValue TALER_EXCHANGE_parse_reserve_history ( - struct TALER_EXCHANGE_Keys *keys, + const struct TALER_EXCHANGE_Keys *keys, const json_t *history, const struct TALER_ReservePublicKeyP *reserve_pub, const char *currency, diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index c4fcad562..1b1a657c2 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -1215,8 +1215,7 @@ struct TALER_EXCHANGEDB_AgeWithdraw /** * Signature confirming the age withdrawal commitment, matching @e - * reserve_pub, @e maximum_age_group and @e h_commitment and @e - * total_amount_with_fee. + * reserve_pub, @e max_age and @e h_commitment and @e amount_with_fee. */ struct TALER_ReserveSignatureP reserve_sig; diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 53190bc5b..762a3f805 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -22,6 +22,7 @@ libtalerexchange_la_LDFLAGS = \ -no-undefined libtalerexchange_la_SOURCES = \ exchange_api_add_aml_decision.c \ + exchange_api_age_withdraw.c \ exchange_api_auditor_add_denomination.c \ exchange_api_batch_deposit.c \ exchange_api_batch_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 */ diff --git a/src/lib/exchange_api_batch_withdraw.c b/src/lib/exchange_api_batch_withdraw.c index 4817ae403..07ad37f63 100644 --- a/src/lib/exchange_api_batch_withdraw.c +++ b/src/lib/exchange_api_batch_withdraw.c @@ -97,9 +97,20 @@ struct TALER_EXCHANGE_BatchWithdrawHandle { /** - * The connection to exchange this request handle will use + * The curl context to use */ - struct TALER_EXCHANGE_Handle *exchange; + struct GNUNET_CURL_Context *curl_ctx; + + /** + * The base URL to the exchange + */ + const char *exchange_url; + + /** + * The /keys information from the exchange + */ + const struct TALER_EXCHANGE_Keys *keys; + /** * Handle for the actual (internal) batch withdraw operation. @@ -255,7 +266,9 @@ phase_two (struct TALER_EXCHANGE_BatchWithdrawHandle *wh) pds[i] = cd->pd; } wh->wh2 = TALER_EXCHANGE_batch_withdraw2 ( - wh->exchange, + wh->curl_ctx, + wh->exchange_url, + wh->keys, wh->reserve_priv, pds, wh->num_coins, @@ -322,7 +335,9 @@ withdraw_cs_stage_two_callback ( struct TALER_EXCHANGE_BatchWithdrawHandle * TALER_EXCHANGE_batch_withdraw ( - struct TALER_EXCHANGE_Handle *exchange, + struct GNUNET_CURL_Context *curl_ctx, + const char *exchange_url, + const struct TALER_EXCHANGE_Keys *keys, const struct TALER_ReservePrivateKeyP *reserve_priv, const struct TALER_EXCHANGE_WithdrawCoinInput *wcis, unsigned int wci_length, @@ -332,7 +347,9 @@ TALER_EXCHANGE_batch_withdraw ( struct TALER_EXCHANGE_BatchWithdrawHandle *wh; wh = GNUNET_new (struct TALER_EXCHANGE_BatchWithdrawHandle); - wh->exchange = exchange; + wh->curl_ctx = curl_ctx; + wh->exchange_url = exchange_url; + wh->keys = keys; wh->cb = res_cb; wh->cb_cls = res_cb_cls; wh->reserve_priv = reserve_priv; @@ -386,7 +403,8 @@ TALER_EXCHANGE_batch_withdraw ( will be done after the /csr-withdraw request! */ cd->pd.blinded_planchet.cipher = TALER_DENOMINATION_CS; cd->csrh = TALER_EXCHANGE_csr_withdraw ( - exchange, + curl_ctx, + exchange_url, &cd->pk, &cd->pd.blinded_planchet.details.cs_blinded_planchet.nonce, &withdraw_cs_stage_two_callback, diff --git a/src/lib/exchange_api_batch_withdraw2.c b/src/lib/exchange_api_batch_withdraw2.c index 6dd421cea..a855fc57e 100644 --- a/src/lib/exchange_api_batch_withdraw2.c +++ b/src/lib/exchange_api_batch_withdraw2.c @@ -38,16 +38,16 @@ struct TALER_EXCHANGE_BatchWithdraw2Handle { - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - /** * The url for this request. */ char *url; + /** + * The /keys material from the exchange + */ + const struct TALER_EXCHANGE_Keys *keys; + /** * Handle for the request. */ @@ -219,7 +219,7 @@ reserve_batch_withdraw_payment_required ( if (GNUNET_OK != TALER_EXCHANGE_parse_reserve_history ( - TALER_EXCHANGE_get_keys (wh->exchange), + wh->keys, history, &wh->reserve_pub, balance.currency, @@ -387,7 +387,9 @@ handle_reserve_batch_withdraw_finished (void *cls, struct TALER_EXCHANGE_BatchWithdraw2Handle * TALER_EXCHANGE_batch_withdraw2 ( - struct TALER_EXCHANGE_Handle *exchange, + struct GNUNET_CURL_Context *curl_ctx, + const char *exchange_url, + const struct TALER_EXCHANGE_Keys *keys, const struct TALER_ReservePrivateKeyP *reserve_priv, const struct TALER_PlanchetDetail *pds, unsigned int pds_length, @@ -395,21 +397,15 @@ TALER_EXCHANGE_batch_withdraw2 ( void *res_cb_cls) { struct TALER_EXCHANGE_BatchWithdraw2Handle *wh; - const struct TALER_EXCHANGE_Keys *keys; const struct TALER_EXCHANGE_DenomPublicKey *dk; struct TALER_ReserveSignatureP reserve_sig; char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; struct TALER_BlindedCoinHashP bch; json_t *jc; - keys = TALER_EXCHANGE_get_keys (exchange); - if (NULL == keys) - { - GNUNET_break (0); - return NULL; - } + GNUNET_assert (NULL != keys); wh = GNUNET_new (struct TALER_EXCHANGE_BatchWithdraw2Handle); - wh->exchange = exchange; + wh->keys = keys; wh->cb = res_cb; wh->cb_cls = res_cb_cls; wh->num_coins = pds_length; @@ -430,14 +426,15 @@ TALER_EXCHANGE_batch_withdraw2 ( *end = '\0'; GNUNET_snprintf (arg_str, sizeof (arg_str), - "/reserves/%s/batch-withdraw", + "reserves/%s/batch-withdraw", pub_str); } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Attempting to batch-withdraw from reserve %s\n", TALER_B2S (&wh->reserve_pub)); - wh->url = TEAH_path_to_url (exchange, - arg_str); + wh->url = TALER_url_join (exchange_url, + arg_str, + NULL); if (NULL == wh->url) { GNUNET_break (0); @@ -513,13 +510,11 @@ TALER_EXCHANGE_batch_withdraw2 ( } { CURL *eh; - struct GNUNET_CURL_Context *ctx; json_t *req; req = GNUNET_JSON_PACK ( GNUNET_JSON_pack_array_steal ("planchets", jc)); - ctx = TEAH_handle_to_context (exchange); eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); if ( (NULL == eh) || (GNUNET_OK != @@ -535,7 +530,7 @@ TALER_EXCHANGE_batch_withdraw2 ( return NULL; } json_decref (req); - wh->job = GNUNET_CURL_job_add2 (ctx, + wh->job = GNUNET_CURL_job_add2 (curl_ctx, eh, wh->post_ctx.headers, &handle_reserve_batch_withdraw_finished, diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c index f43673d76..77a5cf816 100644 --- a/src/lib/exchange_api_common.c +++ b/src/lib/exchange_api_common.c @@ -36,7 +36,7 @@ struct HistoryParseContext /** * Keys of the exchange we use. */ - struct TALER_EXCHANGE_Keys *keys; + const struct TALER_EXCHANGE_Keys *keys; /** * Our reserve public key. @@ -647,7 +647,7 @@ parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh, enum GNUNET_GenericReturnValue TALER_EXCHANGE_parse_reserve_history ( - struct TALER_EXCHANGE_Keys *keys, + const struct TALER_EXCHANGE_Keys *keys, const json_t *history, const struct TALER_ReservePublicKeyP *reserve_pub, const char *currency, diff --git a/src/lib/exchange_api_csr_withdraw.c b/src/lib/exchange_api_csr_withdraw.c index fca3fff8e..4c1d83a9a 100644 --- a/src/lib/exchange_api_csr_withdraw.c +++ b/src/lib/exchange_api_csr_withdraw.c @@ -38,11 +38,6 @@ */ struct TALER_EXCHANGE_CsRWithdrawHandle { - /** - * The connection to exchange this request handle will use - */ - struct TALER_EXCHANGE_Handle *exchange; - /** * Function to call with the result. */ @@ -204,11 +199,13 @@ handle_csr_finished (void *cls, struct TALER_EXCHANGE_CsRWithdrawHandle * -TALER_EXCHANGE_csr_withdraw (struct TALER_EXCHANGE_Handle *exchange, - const struct TALER_EXCHANGE_DenomPublicKey *pk, - const struct TALER_CsNonce *nonce, - TALER_EXCHANGE_CsRWithdrawCallback res_cb, - void *res_cb_cls) +TALER_EXCHANGE_csr_withdraw ( + struct GNUNET_CURL_Context *curl_ctx, + const char *exchange_url, + const struct TALER_EXCHANGE_DenomPublicKey *pk, + const struct TALER_CsNonce *nonce, + TALER_EXCHANGE_CsRWithdrawCallback res_cb, + void *res_cb_cls) { struct TALER_EXCHANGE_CsRWithdrawHandle *csrh; @@ -218,11 +215,11 @@ TALER_EXCHANGE_csr_withdraw (struct TALER_EXCHANGE_Handle *exchange, return NULL; } csrh = GNUNET_new (struct TALER_EXCHANGE_CsRWithdrawHandle); - csrh->exchange = exchange; csrh->cb = res_cb; csrh->cb_cls = res_cb_cls; - csrh->url = TEAH_path_to_url (exchange, - "/csr-withdraw"); + csrh->url = TALER_url_join (exchange_url, + "csr-withdraw", + NULL); if (NULL == csrh->url) { GNUNET_free (csrh); @@ -231,7 +228,6 @@ TALER_EXCHANGE_csr_withdraw (struct TALER_EXCHANGE_Handle *exchange, { CURL *eh; - struct GNUNET_CURL_Context *ctx; json_t *req; req = GNUNET_JSON_PACK ( @@ -242,7 +238,6 @@ TALER_EXCHANGE_csr_withdraw (struct TALER_EXCHANGE_Handle *exchange, &pk->h_key, sizeof(struct TALER_DenominationHashP))); GNUNET_assert (NULL != req); - ctx = TEAH_handle_to_context (exchange); eh = TALER_EXCHANGE_curl_easy_get_ (csrh->url); if ( (NULL == eh) || (GNUNET_OK != @@ -259,7 +254,7 @@ TALER_EXCHANGE_csr_withdraw (struct TALER_EXCHANGE_Handle *exchange, return NULL; } json_decref (req); - csrh->job = GNUNET_CURL_job_add2 (ctx, + csrh->job = GNUNET_CURL_job_add2 (curl_ctx, eh, csrh->post_ctx.headers, &handle_csr_finished, diff --git a/src/lib/exchange_api_curl_defaults.h b/src/lib/exchange_api_curl_defaults.h index 009d72ab8..c4ba04fc5 100644 --- a/src/lib/exchange_api_curl_defaults.h +++ b/src/lib/exchange_api_curl_defaults.h @@ -25,7 +25,6 @@ #define _TALER_CURL_DEFAULTS_H -#include "platform.h" #include diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index fe73f0504..0bb3c2085 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -1229,6 +1229,8 @@ TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange, bool force_download = 0 != (flags & TALER_EXCHANGE_CKF_FORCE_DOWNLOAD); bool pull_all_keys = 0 != (flags & TALER_EXCHANGE_CKF_PULL_ALL_KEYS); + GNUNET_assert (NULL != exchange); + if ( (NULL != cb) && ( (exchange->cert_cb != cb) || (exchange->cert_cb_cls != cb_cls) ) ) diff --git a/src/lib/exchange_api_melt.c b/src/lib/exchange_api_melt.c index 1da4184d2..f19f98c54 100644 --- a/src/lib/exchange_api_melt.c +++ b/src/lib/exchange_api_melt.c @@ -504,6 +504,7 @@ csr_cb (void *cls, } +/* FIXME: refactor this to use struct TALER_EXCHANGE_Handle */ struct TALER_EXCHANGE_MeltHandle * TALER_EXCHANGE_melt ( struct GNUNET_CURL_Context *ctx, diff --git a/src/lib/exchange_api_refreshes_reveal.c b/src/lib/exchange_api_refreshes_reveal.c index 9b6f9b19c..e698a2977 100644 --- a/src/lib/exchange_api_refreshes_reveal.c +++ b/src/lib/exchange_api_refreshes_reveal.c @@ -303,6 +303,7 @@ handle_refresh_reveal_finished (void *cls, } +/* FIXME: refactor this to use struct TALER_EXCHANGE_Handle */ struct TALER_EXCHANGE_RefreshesRevealHandle * TALER_EXCHANGE_refreshes_reveal ( struct GNUNET_CURL_Context *ctx, diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c index 2a3c095a6..87218989a 100644 --- a/src/lib/exchange_api_withdraw.c +++ b/src/lib/exchange_api_withdraw.c @@ -39,9 +39,19 @@ struct TALER_EXCHANGE_WithdrawHandle { /** - * The connection to exchange this request handle will use + * The curl context to use */ - struct TALER_EXCHANGE_Handle *exchange; + struct GNUNET_CURL_Context *curl_ctx; + + /** + * The base-URL to the exchange + */ + const char *exchange_url; + + /** + * The /keys material from the exchange + */ + struct TALER_EXCHANGE_Keys *keys; /** * Handle for the actual (internal) withdraw operation. @@ -232,7 +242,9 @@ withdraw_cs_stage_two_callback ( GNUNET_break (0); break; } - wh->wh2 = TALER_EXCHANGE_withdraw2 (wh->exchange, + wh->wh2 = TALER_EXCHANGE_withdraw2 (wh->curl_ctx, + wh->exchange_url, + wh->keys, &wh->pd, wh->reserve_priv, &handle_reserve_withdraw_finished, @@ -249,7 +261,9 @@ withdraw_cs_stage_two_callback ( struct TALER_EXCHANGE_WithdrawHandle * TALER_EXCHANGE_withdraw ( - struct TALER_EXCHANGE_Handle *exchange, + 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_WithdrawCoinInput *wci, TALER_EXCHANGE_WithdrawCallback res_cb, @@ -258,7 +272,9 @@ TALER_EXCHANGE_withdraw ( struct TALER_EXCHANGE_WithdrawHandle *wh; wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle); - wh->exchange = exchange; + wh->keys = TALER_EXCHANGE_keys_incref (keys); + wh->exchange_url = exchange_url; + wh->curl_ctx = curl_ctx; wh->cb = res_cb; wh->cb_cls = res_cb_cls; wh->reserve_priv = reserve_priv; @@ -292,7 +308,9 @@ TALER_EXCHANGE_withdraw ( GNUNET_free (wh); return NULL; } - wh->wh2 = TALER_EXCHANGE_withdraw2 (exchange, + wh->wh2 = TALER_EXCHANGE_withdraw2 (curl_ctx, + exchange_url, + keys, &wh->pd, wh->reserve_priv, &handle_reserve_withdraw_finished, @@ -309,7 +327,8 @@ TALER_EXCHANGE_withdraw ( will be done after the /csr-withdraw request! */ wh->pd.blinded_planchet.cipher = TALER_DENOMINATION_CS; wh->csrh = TALER_EXCHANGE_csr_withdraw ( - exchange, + curl_ctx, + exchange_url, &wh->pk, &wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce, &withdraw_cs_stage_two_callback, @@ -339,6 +358,7 @@ TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh) TALER_EXCHANGE_withdraw2_cancel (wh->wh2); wh->wh2 = NULL; } + TALER_EXCHANGE_keys_decref (wh->keys); TALER_denom_pub_free (&wh->pk.key); GNUNET_free (wh); } diff --git a/src/lib/exchange_api_withdraw2.c b/src/lib/exchange_api_withdraw2.c index daac429c7..11028ed5b 100644 --- a/src/lib/exchange_api_withdraw2.c +++ b/src/lib/exchange_api_withdraw2.c @@ -39,9 +39,9 @@ struct TALER_EXCHANGE_Withdraw2Handle { /** - * The connection to exchange this request handle will use + * The /keys material from the exchange */ - struct TALER_EXCHANGE_Handle *exchange; + struct TALER_EXCHANGE_Keys *keys; /** * The url for this request. @@ -192,8 +192,7 @@ reserve_withdraw_payment_required ( } if (GNUNET_OK != - TALER_EXCHANGE_parse_reserve_history (TALER_EXCHANGE_get_keys ( - wh->exchange), + TALER_EXCHANGE_parse_reserve_history (wh->keys, history, &wh->reserve_pub, balance.currency, @@ -361,25 +360,21 @@ handle_reserve_withdraw_finished (void *cls, struct TALER_EXCHANGE_Withdraw2Handle * TALER_EXCHANGE_withdraw2 ( - struct TALER_EXCHANGE_Handle *exchange, + struct GNUNET_CURL_Context *curl_ctx, + const char *exchange_url, + struct TALER_EXCHANGE_Keys *keys, const struct TALER_PlanchetDetail *pd, const struct TALER_ReservePrivateKeyP *reserve_priv, TALER_EXCHANGE_Withdraw2Callback res_cb, void *res_cb_cls) { struct TALER_EXCHANGE_Withdraw2Handle *wh; - const struct TALER_EXCHANGE_Keys *keys; const struct TALER_EXCHANGE_DenomPublicKey *dk; struct TALER_ReserveSignatureP reserve_sig; char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; struct TALER_BlindedCoinHashP bch; - keys = TALER_EXCHANGE_get_keys (exchange); - if (NULL == keys) - { - GNUNET_break (0); - return NULL; - } + GNUNET_assert (NULL != keys); dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys, &pd->denom_pub_hash); if (NULL == dk) @@ -388,7 +383,7 @@ TALER_EXCHANGE_withdraw2 ( return NULL; } wh = GNUNET_new (struct TALER_EXCHANGE_Withdraw2Handle); - wh->exchange = exchange; + wh->keys = TALER_EXCHANGE_keys_incref (keys); wh->cb = res_cb; wh->cb_cls = res_cb_cls; /* Compute how much we expected to charge to the reserve */ @@ -418,7 +413,7 @@ TALER_EXCHANGE_withdraw2 ( *end = '\0'; GNUNET_snprintf (arg_str, sizeof (arg_str), - "/reserves/%s/withdraw", + "reserves/%s/withdraw", pub_str); } @@ -448,8 +443,9 @@ TALER_EXCHANGE_withdraw2 ( GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Attempting to withdraw from reserve %s\n", TALER_B2S (&wh->reserve_pub)); - wh->url = TEAH_path_to_url (exchange, - arg_str); + wh->url = TALER_url_join (exchange_url, + arg_str, + NULL); if (NULL == wh->url) { json_decref (withdraw_obj); @@ -458,9 +454,7 @@ TALER_EXCHANGE_withdraw2 ( } { CURL *eh; - struct GNUNET_CURL_Context *ctx; - ctx = TEAH_handle_to_context (exchange); eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); if ( (NULL == eh) || (GNUNET_OK != @@ -477,7 +471,7 @@ TALER_EXCHANGE_withdraw2 ( return NULL; } json_decref (withdraw_obj); - wh->job = GNUNET_CURL_job_add2 (ctx, + wh->job = GNUNET_CURL_job_add2 (curl_ctx, eh, wh->post_ctx.headers, &handle_reserve_withdraw_finished, @@ -498,5 +492,6 @@ TALER_EXCHANGE_withdraw2_cancel (struct TALER_EXCHANGE_Withdraw2Handle *wh) } GNUNET_free (wh->url); TALER_curl_easy_post_finished (&wh->post_ctx); + TALER_EXCHANGE_keys_decref (wh->keys); GNUNET_free (wh); } diff --git a/src/testing/testing_api_cmd_batch_withdraw.c b/src/testing/testing_api_cmd_batch_withdraw.c index e0b8285ae..9283f0325 100644 --- a/src/testing/testing_api_cmd_batch_withdraw.c +++ b/src/testing/testing_api_cmd_batch_withdraw.c @@ -321,12 +321,15 @@ batch_withdraw_run (void *cls, wci->ps = &cs->ps; wci->ach = cs->h_age_commitment; } - ws->wsh = TALER_EXCHANGE_batch_withdraw (exchange, - rp, - wcis, - ws->num_coins, - &reserve_batch_withdraw_cb, - ws); + ws->wsh = TALER_EXCHANGE_batch_withdraw ( + TALER_TESTING_interpreter_get_context (is), + TALER_TESTING_get_exchange_url (is), + TALER_TESTING_get_keys (is), + rp, + wcis, + ws->num_coins, + &reserve_batch_withdraw_cb, + ws); if (NULL == ws->wsh) { GNUNET_break (0); diff --git a/src/testing/testing_api_cmd_withdraw.c b/src/testing/testing_api_cmd_withdraw.c index 8188eeae9..a6315f91e 100644 --- a/src/testing/testing_api_cmd_withdraw.c +++ b/src/testing/testing_api_cmd_withdraw.c @@ -410,7 +410,7 @@ withdraw_run (void *cls, if (NULL == ws->pk) { - dpk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (exchange), + dpk = TALER_TESTING_find_pk (TALER_TESTING_get_keys (is), &ws->amount, ws->age > 0); if (NULL == dpk) @@ -443,11 +443,14 @@ withdraw_run (void *cls, .ps = &ws->ps, .ach = ws->h_age_commitment }; - ws->wsh = TALER_EXCHANGE_withdraw (exchange, - rp, - &wci, - &reserve_withdraw_cb, - ws); + ws->wsh = TALER_EXCHANGE_withdraw ( + TALER_TESTING_interpreter_get_context (is), + TALER_TESTING_get_exchange_url (is), + TALER_TESTING_get_keys (is), + rp, + &wci, + &reserve_withdraw_cb, + ws); } if (NULL == ws->wsh) { diff --git a/src/util/exchange_signatures.c b/src/util/exchange_signatures.c index 6f8ebdaff..3aa464aa7 100644 --- a/src/util/exchange_signatures.c +++ b/src/util/exchange_signatures.c @@ -413,6 +413,34 @@ TALER_exchange_online_age_withdraw_confirmation_sign ( } +enum GNUNET_GenericReturnValue +TALER_exchange_online_age_withdraw_confirmation_verify ( + const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, + uint32_t noreveal_index, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const struct TALER_ExchangeSignatureP *exchange_sig) +{ + struct TALER_AgeWithdrawConfirmationPS confirm = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_AGE_WITHDRAW), + .purpose.size = htonl (sizeof (confirm)), + .h_commitment = *h_commitment, + .noreveal_index = htonl (noreveal_index) + }; + + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_EXCHANGE_CONFIRM_AGE_WITHDRAW, + &confirm, + &exchange_sig->eddsa_signature, + &exchange_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + /* TODO:oec: add signature for age-withdraw, age-reveal */ diff --git a/src/util/wallet_signatures.c b/src/util/wallet_signatures.c index 34d097d77..823641ed4 100644 --- a/src/util/wallet_signatures.c +++ b/src/util/wallet_signatures.c @@ -22,6 +22,7 @@ #include "platform.h" #include "taler_util.h" #include "taler_signatures.h" +#include GNUNET_NETWORK_STRUCT_BEGIN @@ -621,9 +622,9 @@ struct TALER_AgeWithdrawRequestPS struct GNUNET_CRYPTO_EccSignaturePurpose purpose; /** - * Hash of the commitment of n*kappa coins + * The reserve's public key */ - struct TALER_AgeWithdrawCommitmentHashP h_commitment GNUNET_PACKED; + struct TALER_ReservePublicKeyP reserve_pub; /** * Value of the coin being exchanged (matching the denomination key) @@ -635,7 +636,12 @@ struct TALER_AgeWithdrawRequestPS struct TALER_AmountNBO amount_with_fee; /** - * The mask that defines the age groups + * Running SHA512 hash of the commitment of n*kappa coins + */ + struct TALER_AgeWithdrawCommitmentHashP h_commitment; + + /** + * The mask that defines the age groups. MUST be the same for all denominations. */ struct TALER_AgeMask mask; @@ -665,6 +671,8 @@ TALER_wallet_age_withdraw_sign ( .max_age_group = TALER_get_age_group (mask, max_age) }; + GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + &req.reserve_pub.eddsa_pub); TALER_amount_hton (&req.amount_with_fee, amount_with_fee); GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv, @@ -685,6 +693,7 @@ TALER_wallet_age_withdraw_verify ( struct TALER_AgeWithdrawRequestPS awsrd = { .purpose.size = htonl (sizeof (awsrd)), .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW), + .reserve_pub = *reserve_pub, .h_commitment = *h_commitment, .mask = *mask, .max_age_group = TALER_get_age_group (mask, max_age)