diff --git a/src/exchange/taler-exchange-httpd_age-withdraw.c b/src/exchange/taler-exchange-httpd_age-withdraw.c new file mode 100644 index 000000000..9c7703af7 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_age-withdraw.c @@ -0,0 +1,397 @@ +/* + 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General + Public License along with TALER; see the file COPYING. If not, + see +*/ +/** + * @file taler-exchange-httpd_age-withdraw.c + * @brief Handle /reserves/$RESERVE_PUB/age-withdraw requests + * @author Özgür Kesim + */ +#include "platform.h" +#include +#include +#include "taler_json_lib.h" +#include "taler_kyclogic_lib.h" +#include "taler_mhd_lib.h" +#include "taler-exchange-httpd_age-withdraw.h" +#include "taler-exchange-httpd_responses.h" +#include "taler-exchange-httpd_keys.h" + +/** + * Send a response to a "age-withdraw" request. + * + * @param connection the connection to send the response to + * @param ach value the client committed to + * @param noreveal_index which index will the client not have to reveal + * @return a MHD status code + */ +static MHD_RESULT +reply_age_withdraw_success ( + struct MHD_Connection *connection, + const struct TALER_AgeWithdrawCommitmentHashP *ach, + uint32_t noreveal_index) +{ + struct TALER_ExchangePublicKeyP pub; + struct TALER_ExchangeSignatureP sig; + enum TALER_ErrorCode ec = + TALER_exchange_online_age_withdraw_confirmation_sign ( + &TEH_keys_exchange_sign_, + ach, + noreveal_index, + &pub, + &sig); + + if (TALER_EC_NONE != ec) + return TALER_MHD_reply_with_ec (connection, + ec, + NULL); + + return TALER_MHD_REPLY_JSON_PACK (connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_uint64 ("noreveal_index", + noreveal_index), + GNUNET_JSON_pack_data_auto ("exchange_sig", + &sig), + GNUNET_JSON_pack_data_auto ("exchange_pub", + &pub)); +} + + +/** + * Context for #age_withdraw_transaction. + */ +struct AgeWithdrawContext +{ + /** + * KYC status for the operation. + */ + struct TALER_EXCHANGEDB_KycStatus kyc; + + /** + * Hash of the wire source URL, needed when kyc is needed. + */ + struct TALER_PaytoHashP h_payto; + + /** + * The data from the age-withdraw request + */ + struct TALER_EXCHANGEDB_AgeWithdrawCommitment commitment; + + /** + * Current time for the DB transaction. + */ + struct GNUNET_TIME_Timestamp now; +}; + + +/** + * Function called to iterate over KYC-relevant + * transaction amounts for a particular time range. + * Called within a database transaction, so must + * not start a new one. + * + * @param cls closure, identifies the event type and + * account to iterate over events for + * @param limit maximum time-range for which events + * should be fetched (timestamp in the past) + * @param cb function to call on each event found, + * events must be returned in reverse chronological + * order + * @param cb_cls closure for @a cb + */ +static void +age_withdraw_amount_cb (void *cls, + struct GNUNET_TIME_Absolute limit, + TALER_EXCHANGEDB_KycAmountCallback cb, + void *cb_cls) +{ + struct AgeWithdrawContext *awc = cls; + enum GNUNET_DB_QueryStatus qs; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Signaling amount %s for KYC check during age-withdrawal\n", + TALER_amount2s (&awc->commitment.amount_with_fee)); + if (GNUNET_OK != + cb (cb_cls, + &awc->commitment.amount_with_fee, + awc->now.abs_time)) + return; + qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (TEH_plugin->cls, + &awc->h_payto, + limit, + cb, + cb_cls); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Got %d additional transactions for this age-withdrawal and limit %llu\n", + qs, + (unsigned long long) limit.abs_value_us); + GNUNET_break (qs >= 0); +} + + +/** + * Function implementing age withdraw transaction. Runs the + * transaction logic; IF it returns a non-error code, the transaction + * logic MUST NOT queue a MHD response. IF it returns an hard error, + * the transaction logic MUST queue a MHD response and set @a mhd_ret. + * IF it returns the soft error code, the function MAY be called again + * to retry and MUST not queue a MHD response. + * + * Note that "awc->commitment.sig" is set before entering this function as we + * signed before entering the transaction. + * + * @param cls a `struct AgeWithdrawContext *` + * @param connection MHD request which triggered the transaction + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!) + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +age_withdraw_transaction (void *cls, + struct MHD_Connection *connection, + MHD_RESULT *mhd_ret) +{ + struct AgeWithdrawContext *awc = cls; + enum GNUNET_DB_QueryStatus qs; + bool found = false; + bool balance_ok = false; + uint64_t ruuid; + + awc->now = GNUNET_TIME_timestamp_get (); + qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls, + &awc->commitment.reserve_pub, + &awc->h_payto); + if (qs < 0) + return qs; + + /* If no results, reserve was created by merge, + in which case no KYC check is required as the + merge already did that. */ + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + { + char *kyc_required; + + qs = TALER_KYCLOGIC_kyc_test_required ( + TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW, + &awc->h_payto, + TEH_plugin->select_satisfied_kyc_processes, + TEH_plugin->cls, + &age_withdraw_amount_cb, + awc, + &kyc_required); + + if (qs < 0) + { + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + GNUNET_break (0); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "kyc_test_required"); + } + return qs; + } + + if (NULL != kyc_required) + { + /* insert KYC requirement into DB! */ + awc->kyc.ok = false; + return TEH_plugin->insert_kyc_requirement_for_account ( + TEH_plugin->cls, + kyc_required, + &awc->h_payto, + &awc->kyc.requirement_row); + } + } + + awc->kyc.ok = true; + qs = TEH_plugin->do_age_withdraw (TEH_plugin->cls, + &awc->commitment, + awc->now, + &found, + &balance_ok, + &ruuid); + if (0 > qs) + { + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "do_age_withdraw"); + return qs; + } + else if (! found) + { + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, + NULL); + return GNUNET_DB_STATUS_HARD_ERROR; + } + else if (! balance_ok) + { + TEH_plugin->rollback (TEH_plugin->cls); + *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance ( + connection, + TALER_EC_EXCHANGE_AGE_WITHDRAW_INSUFFICIENT_FUNDS, + &awc->commitment.amount_with_fee, + &awc->commitment.reserve_pub); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + TEH_METRICS_num_success[TEH_MT_SUCCESS_AGE_WITHDRAW]++; + return qs; +} + + +/** + * Check if the @a rc is replayed and we already have an + * answer. If so, replay the existing answer and return the + * HTTP response. + * + * @param rc request context + * @param[in,out] awc parsed request data + * @param[out] mret HTTP status, set if we return true + * @return true if the request is idempotent with an existing request + * false if we did not find the request in the DB and did not set @a mret + */ +static bool +request_is_idempotent (struct TEH_RequestContext *rc, + struct AgeWithdrawContext *awc, + MHD_RESULT *mret) +{ + enum GNUNET_DB_QueryStatus qs; + struct TALER_EXCHANGEDB_AgeWithdrawCommitment commitment; + + qs = TEH_plugin->get_age_withdraw_info (TEH_plugin->cls, + &awc->commitment.reserve_pub, + &awc->commitment.h_commitment, + &commitment); + if (0 > qs) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + *mret = TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_age_withdraw_info"); + return true; /* well, kind-of */ + } + + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + return false; + + /* generate idempotent reply */ + TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_AGE_WITHDRAW]++; + *mret = reply_age_withdraw_success (rc->connection, + &commitment.h_commitment, + commitment.noreveal_index); + return true; +} + + +MHD_RESULT +TEH_handler_age_withdraw (struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub, + const json_t *root) +{ + MHD_RESULT mhd_ret; + struct AgeWithdrawContext awc; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("reserve_sig", + &awc.commitment.reserve_sig), + GNUNET_JSON_spec_fixed_auto ("h_commitment", + &awc.commitment.h_commitment), + TALER_JSON_spec_amount ("amount", + TEH_currency, + &awc.commitment.amount_with_fee), + GNUNET_JSON_spec_uint32 ("max_age_group", + &awc.commitment.max_age_group), + GNUNET_JSON_spec_end () + }; + + memset (&awc, 0, sizeof (awc)); + awc.commitment.reserve_pub = *reserve_pub; + + + /* Parse the JSON body */ + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (rc->connection, + root, + spec); + if (GNUNET_OK != res) + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + do { + /* If request was made before successfully, return the previous answer */ + if (request_is_idempotent (rc, + &awc, + &mhd_ret)) + break; + + /* Verify the signature of the request body with the reserve key */ + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; + if (GNUNET_OK != + TALER_wallet_age_withdraw_verify (&awc.commitment.h_commitment, + &awc.commitment.amount_with_fee, + awc.commitment.max_age_group, + &awc.commitment.reserve_pub, + &awc.commitment.reserve_sig)) + { + GNUNET_break_op (0); + mhd_ret = TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID, + NULL); + break; + } + + /* Run the transaction */ + if (GNUNET_OK != + TEH_DB_run_transaction (rc->connection, + "run age withdraw", + TEH_MT_REQUEST_AGE_WITHDRAW, + &mhd_ret, + &age_withdraw_transaction, + &awc)) + break; + + /* Clean up and send back final response */ + GNUNET_JSON_parse_free (spec); + + if (! awc.kyc.ok) + return TEH_RESPONSE_reply_kyc_required (rc->connection, + &awc.h_payto, + &awc.kyc); + + return reply_age_withdraw_success (rc->connection, + &awc.commitment.h_commitment, + awc.commitment.noreveal_index); + } while(0); + + GNUNET_JSON_parse_free (spec); + return mhd_ret; + +} + + +/* end of taler-exchange-httpd_age-withdraw.c */ diff --git a/src/exchange/taler-exchange-httpd_age-withdraw.h b/src/exchange/taler-exchange-httpd_age-withdraw.h new file mode 100644 index 000000000..a76779190 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_age-withdraw.h @@ -0,0 +1,47 @@ +/* + 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_age-withdraw.h + * @brief Handle /reserve/$RESERVE_PUB/age-withdraw requests + * @author Özgür Kesim + */ +#ifndef TALER_EXCHANGE_HTTPD_AGE_WITHDRAW_H +#define TALER_EXCHANGE_HTTPD_AGE_WITHDRAW_H + +#include +#include "taler-exchange-httpd.h" + + +/** + * Handle a "/reserves/$RESERVE_PUB/age-withdraw" request. + * + * Parses the batch of commitments to withdraw age restricted coins, and checks + * that the signature "reserve_sig" makes this a valid withdrawal request from + * the specified reserve. If the request is valid, the response contains a + * noreveal_index which the client has to use for the subsequent call to + * /age-withdraw/$ACH/reveal. + * + * @param rc request context + * @param root uploaded JSON data + * @param reserve_pub public key of the reserve + * @return MHD result code + */ +MHD_RESULT +TEH_handler_age_withdraw (struct TEH_RequestContext *rc, + const struct TALER_ReservePublicKeyP *reserve_pub, + const json_t *root); + +#endif diff --git a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c new file mode 100644 index 000000000..65bbb4326 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c @@ -0,0 +1,303 @@ +/* + 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_age-withdraw_reveal.c + * @brief Handle /age-withdraw/$ACH/reveal requests + * @author Özgür Kesim + */ +#include "platform.h" +#include +#include +#include +#include "taler_mhd_lib.h" +#include "taler-exchange-httpd_mhd.h" +#include "taler-exchange-httpd_age-withdraw_reveal.h" +#include "taler-exchange-httpd_responses.h" +#include "taler-exchange-httpd_keys.h" + +/** + * State for an /age-withdraw/$ACH/reveal operation. + */ +struct AgeRevealContext +{ + + /** + * Commitment for the age-withdraw operation. + */ + struct TALER_AgeWithdrawCommitmentHashP ach; + + /** + * Public key of the reserve for with the age-withdraw commitment was + * originally made. This parameter is provided by the client again + * during the call to reveal in order to save a database-lookup . + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Number of coins/denonations in the reveal + */ + uint32_t num_coins; + + /** + * TODO:oec num_coins denoms + */ + struct TALER_DenominationHashP *denoms_h; + + /** + * TODO:oec num_coins blinded coins + */ + struct TALER_BlindedCoinHashP *coin_evs; + + /** + * TODO:oec num_coins*(kappa - 1) disclosed coins + */ + struct GNUNET_CRYPTO_EddsaPrivateKey *disclosed_coins; + +}; + +/** + * Helper function to free resources in the context + */ +void +age_reveal_context_free (struct AgeRevealContext *actx) +{ + GNUNET_free (actx->denoms_h); + GNUNET_free (actx->coin_evs); + GNUNET_free (actx->disclosed_coins); +} + + +/** + * Handle a "/age-withdraw/$ACH/reveal request. Parses the given JSON + * ... TODO:oec:description + * + * @param connection The MHD connection to handle + * @param actx The context of the operation, only partially built at call time + * @param j_denoms_h Array of hashes of the denominations for the withdrawal, in JSON format + * @param j_coin_evs The blinded envelopes in JSON format for the coins that are not revealed and will be signed on success + * @param j_disclosed_coins The n*(kappa-1) disclosed coins' private keys in JSON format, from which all other attributes (age restriction, blinding, nonce) will be derived from + */ +MHD_RESULT +handle_age_withdraw_reveal_json ( + struct MHD_Connection *connection, + struct AgeRevealContext *actx, + const json_t *j_denoms_h, + const json_t *j_coin_evs, + const json_t *j_disclosed_coins) +{ + MHD_RESULT mhd_ret = MHD_NO; + + /* Verify JSON-structure consistency */ + { + const char *error = NULL; + + actx->num_coins = json_array_size (j_denoms_h); /* 0, if j_denoms_h is not an array */ + + if (! json_is_array (j_denoms_h)) + error = "denoms_h must be an array"; + else if (! json_is_array (j_coin_evs)) + error = "coin_evs must be an array"; + else if (! json_is_array (j_disclosed_coins)) + error = "disclosed_coins must be an array"; + else if (actx->num_coins == 0) + error = "denoms_h must not be empty"; + else if (actx->num_coins != json_array_size (j_coin_evs)) + error = "denoms_h and coins_evs must be arrays of the same size"; + else if (actx->num_coins * (TALER_CNC_KAPPA - 1) + != json_array_size (j_disclosed_coins)) + error = "the size of array disclosed_coins must be " + TALER_CNC_KAPPA_MINUS_ONE_STR " times of the size of denoms_h"; + else if (actx->num_coins > TALER_MAX_FRESH_COINS) + /** + * FIXME?: If the user had commited to more than the maximum coins allowed, + * the reserve has been charged, but now the user can not withdraw any money + * from it. How can the user get their money back? + **/ + error = "maximum number of coins that can be withdrawn has been exceeded"; + + if (NULL != error) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + error); + } + + /* Parse denomination keys */ + { + unsigned int idx; + json_t *jh; + + actx->denoms_h = GNUNET_new_array (actx->num_coins, + struct TALER_DenominationHashP); + + json_array_foreach (j_denoms_h, idx, jh) { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto (NULL, &actx->denoms_h[idx]), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (jh, spec, NULL, NULL)) + { + char msg[256] = {0}; + GNUNET_snprintf (msg, + sizeof(msg), + "couldn't parse entry no. %d in array denoms_h", + idx + 1); + mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + msg); + goto EXIT; + } + + }; + } + + /* Parse blinded envelopes */ + { + unsigned int idx; + json_t *ce; + + actx->coin_evs = GNUNET_new_array (actx->num_coins, + struct TALER_BlindedCoinHashP); + + json_array_foreach (j_coin_evs, idx, ce) { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto (NULL, &actx->coin_evs[idx]), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (ce, spec, NULL, NULL)) + { + char msg[256] = {0}; + GNUNET_snprintf (msg, + sizeof(msg), + "couldn't parse entry no. %d in array coin_evs", + idx + 1); + mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + msg); + goto EXIT; + } + }; + } + + /* Parse diclosed keys */ + { + unsigned int idx; + json_t *dc; + + actx->disclosed_coins = GNUNET_new_array ( + actx->num_coins * (TALER_CNC_KAPPA), + struct GNUNET_CRYPTO_EddsaPrivateKey); + + json_array_foreach (j_coin_evs, idx, dc) { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto (NULL, &actx->disclosed_coins[idx]), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (dc, spec, NULL, NULL)) + { + char msg[256] = {0}; + GNUNET_snprintf (msg, + sizeof(msg), + "couldn't parse entry no. %d in array disclosed_coins", + idx + 1); + mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + msg); + goto EXIT; + } + }; + + } + + /* TODO:oec: find commitment */ + /* TODO:oec: check validity of denoms */ + /* TODO:oec: check amount total against denoms */ + /* TODO:oec: compute the disclosed blinded coins */ + /* TODO:oec: generate h_commitment_comp */ + /* TODO:oec: compare h_commitment_comp against h_commitment */ + /* TODO:oec: sign the coins */ + /* TODO:oec: send response */ + + + /* TODO */ +EXIT: + age_reveal_context_free (actx); + return mhd_ret; +} + + +MHD_RESULT +TEH_handler_age_withdraw_reveal ( + struct TEH_RequestContext *rc, + const struct TALER_AgeWithdrawCommitmentHashP *ach, + const json_t *root) +{ + struct AgeRevealContext actx = {0}; + json_t *j_denoms_h; + json_t *j_coin_evs; + json_t *j_disclosed_coins; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("reserve_pub", &actx.reserve_pub), + GNUNET_JSON_spec_json ("denoms_h", &j_denoms_h), + GNUNET_JSON_spec_json ("coin_evs", &j_coin_evs), + GNUNET_JSON_spec_json ("disclosed_coins", &j_disclosed_coins), + GNUNET_JSON_spec_end () + }; + + actx.ach = *ach; + + /* Parse JSON body*/ + { + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (rc->connection, + root, + spec); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + } + + + /* handle reveal request */ + { + MHD_RESULT res; + + res = handle_age_withdraw_reveal_json (rc->connection, + &actx, + j_denoms_h, + j_coin_evs, + j_disclosed_coins); + + GNUNET_JSON_parse_free (spec); + return res; + } + +} + + +/* end of taler-exchange-httpd_age-withdraw_reveal.c */ diff --git a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.h b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.h new file mode 100644 index 000000000..73ebc6fb5 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.h @@ -0,0 +1,56 @@ +/* + 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_age-withdraw_reveal.h + * @brief Handle /age-withdraw/$ACH/reveal requests + * @author Özgür Kesim + */ +#ifndef TALER_EXCHANGE_HTTPD_AGE_WITHDRAW_H +#define TALER_EXCHANGE_HTTPD_AGE_WITHDRAW_H + +#include +#include "taler-exchange-httpd.h" + + +/** + * Handle a "/age-withdraw/$ACH/reveal" request. + * + * The client got a noreveal_index in response to a previous request + * /reserve/$RESERVE_PUB/age-withdraw. It now has to reveal all n*(kappa-1) + * coin's private keys (except for the noreveal_index), from which all other + * coin-relevant data (blinding, age restriction, nonce) is derived from. + * + * The exchange computes those values, ensures that the maximum age is + * correctly applied, calculates the hash of the blinded envelopes, and - + * together with the non-disclosed blinded envelopes - compares the hash of + * those against the original commitment $ACH. + * + * If all those checks and the used denominations turn out to be correct, the + * exchange signs all blinded envelopes with their appropriate denomination + * keys. + * + * @param rc request context + * @param root uploaded JSON data + * @param ach commitment to the age restricted coints from the age-withdraw request + * @return MHD result code + */ +MHD_RESULT +TEH_handler_age_withdraw_reveal ( + struct TEH_RequestContext *rc, + const struct TALER_AgeWithdrawCommitmentHashP *ach, + const json_t *root); + +#endif diff --git a/src/exchangedb/pg_get_age_withdraw_info.c b/src/exchangedb/pg_get_age_withdraw_info.c new file mode 100644 index 000000000..7662d4b51 --- /dev/null +++ b/src/exchangedb/pg_get_age_withdraw_info.c @@ -0,0 +1,80 @@ +/* + 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 exchangedb/pg_get_age_withdraw_info.c + * @brief Implementation of the get_age_withdraw_info function for Postgres + * @author Özgür Kesim + */ +#include "platform.h" +#include "taler_error_codes.h" +#include "taler_dbevents.h" +#include "taler_pq_lib.h" +#include "pg_get_age_withdraw_info.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TEH_PG_get_age_withdraw_info ( + void *cls, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_AgeWithdrawCommitmentHashP *ach, + struct TALER_EXCHANGEDB_AgeWithdrawCommitment *awc) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (ach), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_auto_from_type ("h_commitment", + &awc->h_commitment), + GNUNET_PQ_result_spec_auto_from_type ("reserve_sig", + &awc->reserve_sig), + GNUNET_PQ_result_spec_auto_from_type ("reserve_pub", + &awc->reserve_pub), + GNUNET_PQ_result_spec_uint32 ("max_age_group", + &awc->max_age_group), + TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", + &awc->amount_with_fee), + GNUNET_PQ_result_spec_uint32 ("noreveal_index", + &awc->noreveal_index), + GNUNET_PQ_result_spec_timestamp ("timtestamp", + &awc->timestamp), + GNUNET_PQ_result_spec_end + }; + + /* Used in #postgres_get_age_withdraw_info() to + locate the response for a /reserve/$RESERVE_PUB/age-withdraw request using + the hash of the blinded message. Used to make sure + /reserve/$RESERVE_PUB/age-withdraw requests are idempotent. */ + PREPARE (pg, + "get_age_withdraw_info", + "SELECT" + " h_commitment" + ",reserve_sig" + ",reserve_pub" + ",max_age_group" + ",amount_with_fee_val" + ",amount_with_fee_frac" + ",noreveal_index" + ",timestamp" + " FROM withdraw_age_commitments" + " WHERE h_commitment=$1;"); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "get_age_withdraw_info", + params, + rs); +} diff --git a/src/exchangedb/pg_get_age_withdraw_info.h b/src/exchangedb/pg_get_age_withdraw_info.h new file mode 100644 index 000000000..0844b1a19 --- /dev/null +++ b/src/exchangedb/pg_get_age_withdraw_info.h @@ -0,0 +1,45 @@ +/* + 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 exchangedb/pg_get_age_withdraw_info.h + * @brief implementation of the get_age_withdraw_info function for Postgres + * @author Özgür KESIM + */ +#ifndef PG_GET_AGE_WITHDRAW_INFO_H +#define PG_GET_AGE_WITHDRAW_INFO_H + +#include "taler_util.h" +#include "taler_json_lib.h" +#include "taler_exchangedb_plugin.h" + +/** + * Locate the response for a age-withdraw request under a hash that uniquely + * identifies the age-withdraw operation. Used to ensure idempotency of the + * request. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param reserve_pub public key of the reserve for which the age-withdraw request is made + * @param ach hash that uniquely identifies the age-withdraw operation + * @param[out] awc corresponding details of the previous age-withdraw request if an entry was found + * @return statement execution status + */ +enum GNUNET_DB_QueryStatus +TEH_PG_get_age_withdraw_info ( + void *cls, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_AgeWithdrawCommitmentHashP *ach, + struct TALER_EXCHANGEDB_AgeWithdrawCommitment *awc); +#endif