aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lib/auditor_api_curl_defaults.c61
-rw-r--r--src/lib/auditor_api_curl_defaults.h38
-rw-r--r--src/lib/auditor_api_deposit_confirmation.c418
-rw-r--r--src/lib/auditor_api_exchanges.c244
-rw-r--r--src/lib/auditor_api_get_config.c288
-rw-r--r--src/lib/exchange_api_age_withdraw.c1117
-rw-r--r--src/lib/exchange_api_age_withdraw_reveal.c471
-rw-r--r--src/lib/exchange_api_auditor_add_denomination.c238
-rw-r--r--src/lib/exchange_api_csr_melt.c317
-rw-r--r--src/lib/exchange_api_kyc_check.c329
-rw-r--r--src/lib/exchange_api_kyc_proof.c217
-rw-r--r--src/lib/exchange_api_kyc_wallet.c230
-rw-r--r--src/lib/exchange_api_lookup_aml_decision.c419
-rw-r--r--src/lib/exchange_api_lookup_aml_decisions.c378
-rw-r--r--src/lib/exchange_api_management_add_partner.c218
-rw-r--r--src/lib/exchange_api_management_auditor_disable.c219
-rw-r--r--src/lib/exchange_api_management_auditor_enable.c224
-rw-r--r--src/lib/exchange_api_management_drain_profits.c213
-rw-r--r--src/lib/exchange_api_management_post_extensions.c213
-rw-r--r--src/lib/exchange_api_management_post_keys.c237
-rw-r--r--src/lib/exchange_api_management_revoke_denomination_key.c221
-rw-r--r--src/lib/exchange_api_management_revoke_signing_key.c211
-rw-r--r--src/lib/exchange_api_management_set_global_fee.c235
-rw-r--r--src/lib/exchange_api_management_set_wire_fee.c227
-rw-r--r--src/lib/exchange_api_management_update_aml_officer.c229
-rw-r--r--src/lib/exchange_api_management_wire_disable.c220
-rw-r--r--src/lib/exchange_api_management_wire_enable.c245
-rw-r--r--src/lib/exchange_api_melt.c596
-rw-r--r--src/lib/exchange_api_recoup.c369
-rw-r--r--src/lib/exchange_api_recoup_refresh.c363
-rw-r--r--src/lib/exchange_api_refresh_common.c249
-rw-r--r--src/lib/exchange_api_refresh_common.h201
-rw-r--r--src/lib/exchange_api_refreshes_reveal.c531
-rw-r--r--src/lib/exchange_api_refund.c480
-rw-r--r--src/lib/exchange_api_stefan.c320
-rw-r--r--src/lib/test_stefan.c206
36 files changed, 10992 insertions, 0 deletions
diff --git a/src/lib/auditor_api_curl_defaults.c b/src/lib/auditor_api_curl_defaults.c
new file mode 100644
index 000000000..81fcd7bac
--- /dev/null
+++ b/src/lib/auditor_api_curl_defaults.c
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/auditor_api_curl_defaults.c
+ * @brief curl easy handle defaults
+ * @author Florian Dold
+ */
+#include "auditor_api_curl_defaults.h"
+
+
+CURL *
+TALER_AUDITOR_curl_easy_get_ (const char *url)
+{
+ CURL *eh;
+ struct GNUNET_AsyncScopeSave scope;
+
+ GNUNET_async_scope_get (&scope);
+
+ eh = curl_easy_init ();
+ if (NULL == eh)
+ return NULL;
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ url));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_FOLLOWLOCATION,
+ 1L));
+ /* Enable compression (using whatever curl likes), see
+ https://curl.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html */
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_ACCEPT_ENCODING,
+ ""));
+ /* limit MAXREDIRS to 5 as a simple security measure against
+ a potential infinite loop caused by a malicious target */
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_MAXREDIRS,
+ 5L));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TCP_FASTOPEN,
+ 1L));
+ return eh;
+}
diff --git a/src/lib/auditor_api_curl_defaults.h b/src/lib/auditor_api_curl_defaults.h
new file mode 100644
index 000000000..99e1e07e6
--- /dev/null
+++ b/src/lib/auditor_api_curl_defaults.h
@@ -0,0 +1,38 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/auditor_api_curl_defaults.h
+ * @brief curl easy handle defaults
+ * @author Florian Dold
+ */
+#ifndef _TALER_CURL_DEFAULTS_H
+#define _TALER_CURL_DEFAULTS_H
+
+#include "platform.h"
+#include <gnunet/gnunet_curl_lib.h>
+
+
+/**
+ * Get a curl handle with the right defaults
+ * for the auditor lib. In the future, we might manage a pool of connections here.
+ *
+ * @param url URL to query
+ */
+CURL *
+TALER_AUDITOR_curl_easy_get_ (const char *url);
+
+#endif /* _TALER_CURL_DEFAULTS_H */
diff --git a/src/lib/auditor_api_deposit_confirmation.c b/src/lib/auditor_api_deposit_confirmation.c
new file mode 100644
index 000000000..1e2ecc6cc
--- /dev/null
+++ b/src/lib/auditor_api_deposit_confirmation.c
@@ -0,0 +1,418 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/auditor_api_deposit_confirmation.c
+ * @brief Implementation of the /deposit request of the auditor's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_util.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+#include "taler_auditor_service.h"
+#include "taler_signatures.h"
+#include "auditor_api_curl_defaults.h"
+
+
+/**
+ * @brief A DepositConfirmation Handle
+ */
+struct TALER_AUDITOR_DepositConfirmationHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_AUDITOR_DepositConfirmationResultCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /deposit-confirmation request.
+ *
+ * @param cls the `struct TALER_AUDITOR_DepositConfirmationHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param djson parsed JSON result, NULL on error
+ */
+static void
+handle_deposit_confirmation_finished (void *cls,
+ long response_code,
+ const void *djson)
+{
+ const json_t *json = djson;
+ struct TALER_AUDITOR_DepositConfirmationHandle *dh = cls;
+ struct TALER_AUDITOR_DepositConfirmationResponse dcr = {
+ .hr.reply = json,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ dh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ dcr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ dcr.hr.ec = TALER_EC_NONE;
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* This should never happen, either us or the auditor is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, auditor says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_GONE:
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, auditor says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for auditor deposit confirmation\n",
+ (unsigned int) response_code,
+ dcr.hr.ec);
+ break;
+ }
+ dh->cb (dh->cb_cls,
+ &dcr);
+ TALER_AUDITOR_deposit_confirmation_cancel (dh);
+}
+
+
+/**
+ * Verify signature information about the deposit-confirmation.
+ *
+ * @param h_wire hash of merchant wire details
+ * @param h_policy hash over the policy extension, if any
+ * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the auditor)
+ * @param exchange_timestamp timestamp when the deposit was received by the wallet
+ * @param wire_deadline by what time must the amount be wired to the merchant
+ * @param refund_deadline date until which the merchant can issue a refund to the customer via the auditor (can be zero if refunds are not allowed); must not be after the @a wire_deadline
+ * @param amount_without_fee the amount confirmed to be wired by the exchange to the merchant
+ * @param coin_pub coin’s public key
+ * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
+ * @param exchange_sig the signature made with purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT
+ * @param exchange_pub the public key of the exchange that matches @a exchange_sig
+ * @param master_pub master public key of the exchange
+ * @param ep_start when does @a exchange_pub validity start
+ * @param ep_expire when does @a exchange_pub usage end
+ * @param ep_end when does @a exchange_pub legal validity end
+ * @param master_sig master signature affirming validity of @a exchange_pub
+ * @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not
+ */
+static enum GNUNET_GenericReturnValue
+verify_signatures (
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_ExtensionPolicyHashP *h_policy,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ struct GNUNET_TIME_Timestamp wire_deadline,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ const struct TALER_Amount *amount_without_fee,
+ unsigned int num_coins,
+ const struct TALER_CoinSpendSignatureP *coin_sigs[
+ static num_coins],
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ struct GNUNET_TIME_Timestamp ep_start,
+ struct GNUNET_TIME_Timestamp ep_expire,
+ struct GNUNET_TIME_Timestamp ep_end,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ if (GNUNET_OK !=
+ TALER_exchange_online_deposit_confirmation_verify (
+ h_contract_terms,
+ h_wire,
+ h_policy,
+ exchange_timestamp,
+ wire_deadline,
+ refund_deadline,
+ amount_without_fee,
+ num_coins,
+ coin_sigs,
+ merchant_pub,
+ exchange_pub,
+ exchange_sig))
+ {
+ GNUNET_break_op (0);
+ TALER_LOG_WARNING (
+ "Invalid signature on /deposit-confirmation request!\n");
+ {
+ TALER_LOG_DEBUG ("... amount_without_fee was %s\n",
+ TALER_amount2s (amount_without_fee));
+ }
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ TALER_exchange_offline_signkey_validity_verify (
+ exchange_pub,
+ ep_start,
+ ep_expire,
+ ep_end,
+ master_pub,
+ master_sig))
+ {
+ GNUNET_break (0);
+ TALER_LOG_WARNING ("Invalid signature on exchange signing key!\n");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_TIME_absolute_is_past (ep_end.abs_time))
+ {
+ GNUNET_break (0);
+ TALER_LOG_WARNING ("Exchange signing key is no longer valid!\n");
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+struct TALER_AUDITOR_DepositConfirmationHandle *
+TALER_AUDITOR_deposit_confirmation (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_ExtensionPolicyHashP *h_policy,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ struct GNUNET_TIME_Timestamp wire_deadline,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ const struct TALER_Amount *total_without_fee,
+ unsigned int num_coins,
+ const struct TALER_CoinSpendPublicKeyP *coin_pubs[
+ static num_coins],
+ const struct TALER_CoinSpendSignatureP *coin_sigs[
+ static num_coins],
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ struct GNUNET_TIME_Timestamp ep_start,
+ struct GNUNET_TIME_Timestamp ep_expire,
+ struct GNUNET_TIME_Timestamp ep_end,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_AUDITOR_DepositConfirmationResultCallback cb,
+ void *cb_cls)
+{
+ struct TALER_AUDITOR_DepositConfirmationHandle *dh;
+ json_t *deposit_confirmation_obj;
+ CURL *eh;
+ json_t *jcoin_sigs;
+ json_t *jcoin_pubs;
+
+ if (0 == num_coins)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ verify_signatures (h_wire,
+ h_policy,
+ h_contract_terms,
+ exchange_timestamp,
+ wire_deadline,
+ refund_deadline,
+ total_without_fee,
+ num_coins,
+ coin_sigs,
+ merchant_pub,
+ exchange_pub,
+ exchange_sig,
+ master_pub,
+ ep_start,
+ ep_expire,
+ ep_end,
+ master_sig))
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ jcoin_sigs = json_array ();
+ GNUNET_assert (NULL != jcoin_sigs);
+ jcoin_pubs = json_array ();
+ GNUNET_assert (NULL != jcoin_pubs);
+ for (unsigned int i = 0; i<num_coins; i++)
+ {
+ GNUNET_assert (0 ==
+ json_array_append_new (jcoin_sigs,
+ GNUNET_JSON_from_data_auto (
+ coin_sigs[i])));
+ GNUNET_assert (0 ==
+ json_array_append_new (jcoin_pubs,
+ GNUNET_JSON_from_data_auto (
+ coin_pubs[i])));
+ }
+ deposit_confirmation_obj
+ = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_wire",
+ h_wire),
+ GNUNET_JSON_pack_data_auto ("h_policy",
+ h_policy),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ h_contract_terms),
+ GNUNET_JSON_pack_timestamp ("exchange_timestamp",
+ exchange_timestamp),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("refund_deadline",
+ refund_deadline)),
+ GNUNET_JSON_pack_timestamp ("wire_deadline",
+ wire_deadline),
+ TALER_JSON_pack_amount ("total_without_fee",
+ total_without_fee),
+ GNUNET_JSON_pack_array_steal ("coin_pubs",
+ jcoin_pubs),
+ GNUNET_JSON_pack_array_steal ("coin_sigs",
+ jcoin_sigs),
+ GNUNET_JSON_pack_data_auto ("merchant_pub",
+ merchant_pub),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ exchange_sig),
+ GNUNET_JSON_pack_data_auto ("master_pub",
+ master_pub),
+ GNUNET_JSON_pack_timestamp ("ep_start",
+ ep_start),
+ GNUNET_JSON_pack_timestamp ("ep_expire",
+ ep_expire),
+ GNUNET_JSON_pack_timestamp ("ep_end",
+ ep_end),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ exchange_pub));
+ dh = GNUNET_new (struct TALER_AUDITOR_DepositConfirmationHandle);
+ dh->cb = cb;
+ dh->cb_cls = cb_cls;
+ dh->url = TALER_url_join (url,
+ "deposit-confirmation",
+ NULL);
+ if (NULL == dh->url)
+ {
+ GNUNET_free (dh);
+ return NULL;
+ }
+ eh = TALER_AUDITOR_curl_easy_get_ (dh->url);
+ if ( (NULL == eh) ||
+ (CURLE_OK !=
+ curl_easy_setopt (eh,
+ CURLOPT_CUSTOMREQUEST,
+ "PUT")) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&dh->ctx,
+ eh,
+ deposit_confirmation_obj)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (deposit_confirmation_obj);
+ GNUNET_free (dh->url);
+ GNUNET_free (dh);
+ return NULL;
+ }
+ json_decref (deposit_confirmation_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for deposit-confirmation: `%s'\n",
+ dh->url);
+ dh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ dh->ctx.headers,
+ &handle_deposit_confirmation_finished,
+ dh);
+ {
+ /* Disable 100 continue processing */
+ struct curl_slist *x_headers;
+
+ x_headers = curl_slist_append (NULL,
+ "Expect:");
+ GNUNET_CURL_extend_headers (dh->job,
+ x_headers);
+ curl_slist_free_all (x_headers);
+ }
+ return dh;
+}
+
+
+void
+TALER_AUDITOR_deposit_confirmation_cancel (
+ struct TALER_AUDITOR_DepositConfirmationHandle *deposit_confirmation)
+{
+ if (NULL != deposit_confirmation->job)
+ {
+ GNUNET_CURL_job_cancel (deposit_confirmation->job);
+ deposit_confirmation->job = NULL;
+ }
+ GNUNET_free (deposit_confirmation->url);
+ TALER_curl_easy_post_finished (&deposit_confirmation->ctx);
+ GNUNET_free (deposit_confirmation);
+}
+
+
+/* end of auditor_api_deposit_confirmation.c */
diff --git a/src/lib/auditor_api_exchanges.c b/src/lib/auditor_api_exchanges.c
new file mode 100644
index 000000000..897dfe60f
--- /dev/null
+++ b/src/lib/auditor_api_exchanges.c
@@ -0,0 +1,244 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/auditor_api_exchanges.c
+ * @brief Implementation of the /exchanges request of the auditor's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_auditor_service.h"
+#include "taler_util.h"
+#include "taler_curl_lib.h"
+#include "taler_signatures.h"
+#include "auditor_api_curl_defaults.h"
+
+/**
+ * How many exchanges do we allow a single auditor to
+ * audit at most?
+ */
+#define MAX_EXCHANGES 1024
+
+
+/**
+ * @brief A ListExchanges Handle
+ */
+struct TALER_AUDITOR_ListExchangesHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_AUDITOR_ListExchangesResultCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /exchanges request.
+ *
+ * @param cls the `struct TALER_AUDITOR_ListExchangesHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param djson parsed JSON result, NULL on error
+ */
+static void
+handle_exchanges_finished (void *cls,
+ long response_code,
+ const void *djson)
+{
+ const json_t *json = djson;
+ const json_t *ja;
+ unsigned int ja_len;
+ struct TALER_AUDITOR_ListExchangesHandle *leh = cls;
+ struct TALER_AUDITOR_ListExchangesResponse ler = {
+ .hr.reply = json,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ leh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ ler.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ ja = json_object_get (json,
+ "exchanges");
+ if ( (NULL == ja) ||
+ (! json_is_array (ja)) )
+ {
+ GNUNET_break (0);
+ ler.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ ler.hr.http_status = 0;
+ break;
+ }
+
+ ja_len = json_array_size (ja);
+ if (ja_len > MAX_EXCHANGES)
+ {
+ GNUNET_break (0);
+ ler.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ ler.hr.http_status = 0;
+ break;
+ }
+ {
+ struct TALER_AUDITOR_ExchangeInfo ei[GNUNET_NZL (ja_len)];
+ bool ok = true;
+
+ for (unsigned int i = 0; i<ja_len; i++)
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_pub",
+ &ei[i].master_pub),
+ GNUNET_JSON_spec_string ("exchange_url",
+ &ei[i].exchange_url),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (ja,
+ i),
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ok = false;
+ ler.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ ler.hr.http_status = 0;
+ break;
+ }
+ }
+ if (! ok)
+ break;
+ ler.details.ok.ei = ei;
+ ler.details.ok.num_exchanges = ja_len;
+ leh->cb (leh->cb_cls,
+ &ler);
+ TALER_AUDITOR_list_exchanges_cancel (leh);
+ return;
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the auditor is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ ler.hr.ec = TALER_JSON_get_error_code (json);
+ ler.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ ler.hr.ec = TALER_JSON_get_error_code (json);
+ ler.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ ler.hr.ec = TALER_JSON_get_error_code (json);
+ ler.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ ler.hr.ec = TALER_JSON_get_error_code (json);
+ ler.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for auditor list-exchanges request\n",
+ (unsigned int) response_code,
+ (int) ler.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ if (NULL != leh->cb)
+ leh->cb (leh->cb_cls,
+ &ler);
+ TALER_AUDITOR_list_exchanges_cancel (leh);
+}
+
+
+struct TALER_AUDITOR_ListExchangesHandle *
+TALER_AUDITOR_list_exchanges (struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ TALER_AUDITOR_ListExchangesResultCallback cb,
+ void *cb_cls)
+{
+ struct TALER_AUDITOR_ListExchangesHandle *leh;
+ CURL *eh;
+
+ leh = GNUNET_new (struct TALER_AUDITOR_ListExchangesHandle);
+ leh->cb = cb;
+ leh->cb_cls = cb_cls;
+ leh->url = TALER_url_join (url,
+ "exchanges",
+ NULL);
+ if (NULL == leh->url)
+ {
+ GNUNET_free (leh);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for list-exchanges: `%s'\n",
+ leh->url);
+ eh = TALER_AUDITOR_curl_easy_get_ (leh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (leh->url);
+ GNUNET_free (leh);
+ return NULL;
+ }
+ leh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_exchanges_finished,
+ leh);
+ return leh;
+}
+
+
+void
+TALER_AUDITOR_list_exchanges_cancel (
+ struct TALER_AUDITOR_ListExchangesHandle *leh)
+{
+ if (NULL != leh->job)
+ {
+ GNUNET_CURL_job_cancel (leh->job);
+ leh->job = NULL;
+ }
+ GNUNET_free (leh->url);
+ GNUNET_free (leh);
+}
+
+
+/* end of auditor_api_exchanges.c */
diff --git a/src/lib/auditor_api_get_config.c b/src/lib/auditor_api_get_config.c
new file mode 100644
index 000000000..c9f366568
--- /dev/null
+++ b/src/lib/auditor_api_get_config.c
@@ -0,0 +1,288 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/auditor_api_get_config.c
+ * @brief Implementation of /config for the auditor's HTTP API
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_auditor_service.h"
+#include "taler_signatures.h"
+#include "auditor_api_curl_defaults.h"
+
+
+/**
+ * Which revision of the Taler auditor protocol is implemented
+ * by this library? Used to determine compatibility.
+ */
+#define TALER_PROTOCOL_CURRENT 0
+
+/**
+ * How many revisions back are we compatible to?
+ */
+#define TALER_PROTOCOL_AGE 0
+
+
+/**
+ * Log error related to CURL operations.
+ *
+ * @param type log level
+ * @param function which function failed to run
+ * @param code what was the curl error code
+ */
+#define CURL_STRERROR(type, function, code) \
+ GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \
+ function, __FILE__, __LINE__, curl_easy_strerror (code));
+
+
+/**
+ * Handle for the get config request.
+ */
+struct TALER_AUDITOR_GetConfigHandle
+{
+ /**
+ * The context of this handle
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Function to call with the auditor's certification data,
+ * NULL if this has already been done.
+ */
+ TALER_AUDITOR_ConfigCallback config_cb;
+
+ /**
+ * Closure to pass to @e config_cb.
+ */
+ void *config_cb_cls;
+
+ /**
+ * Data for the request to get the /config of a auditor,
+ * NULL once we are past stage #MHS_INIT.
+ */
+ struct GNUNET_CURL_Job *vr;
+
+ /**
+ * The url for the @e vr job.
+ */
+ char *vr_url;
+
+};
+
+
+/* ***************** Internal /config fetching ************* */
+
+/**
+ * Decode the JSON in @a resp_obj from the /config response and store the data
+ * in the @a key_data.
+ *
+ * @param[in] resp_obj JSON object to parse
+ * @param[in,out] vi where to store the results we decoded
+ * @param[out] vc where to store config compatibility data
+ * @return #TALER_EC_NONE on success
+ */
+static enum TALER_ErrorCode
+decode_config_json (const json_t *resp_obj,
+ struct TALER_AUDITOR_ConfigInformation *vi,
+ enum TALER_AUDITOR_VersionCompatibility *vc)
+{
+ unsigned int age;
+ unsigned int revision;
+ unsigned int current;
+ char dummy;
+ const char *ver;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("version",
+ &ver),
+ GNUNET_JSON_spec_fixed_auto ("auditor_public_key",
+ &vi->auditor_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (JSON_OBJECT != json_typeof (resp_obj))
+ {
+ GNUNET_break_op (0);
+ return TALER_EC_GENERIC_JSON_INVALID;
+ }
+ /* check the config */
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (resp_obj,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return TALER_EC_GENERIC_JSON_INVALID;
+ }
+ if (3 != sscanf (ver,
+ "%u:%u:%u%c",
+ &current,
+ &revision,
+ &age,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_EC_GENERIC_VERSION_MALFORMED;
+ }
+ vi->version = ver;
+ *vc = TALER_AUDITOR_VC_MATCH;
+ if (TALER_PROTOCOL_CURRENT < current)
+ {
+ *vc |= TALER_AUDITOR_VC_NEWER;
+ if (TALER_PROTOCOL_CURRENT < current - age)
+ *vc |= TALER_AUDITOR_VC_INCOMPATIBLE;
+ }
+ if (TALER_PROTOCOL_CURRENT > current)
+ {
+ *vc |= TALER_AUDITOR_VC_OLDER;
+ if (TALER_PROTOCOL_CURRENT - TALER_PROTOCOL_AGE > current)
+ *vc |= TALER_AUDITOR_VC_INCOMPATIBLE;
+ }
+ return TALER_EC_NONE;
+}
+
+
+/**
+ * Callback used when downloading the reply to a /config request
+ * is complete.
+ *
+ * @param cls the `struct TALER_AUDITOR_GetConfigHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param gresp_obj parsed JSON result, NULL on error, must be a `const json_t *`
+ */
+static void
+config_completed_cb (void *cls,
+ long response_code,
+ const void *gresp_obj)
+{
+ struct TALER_AUDITOR_GetConfigHandle *auditor = cls;
+ const json_t *resp_obj = gresp_obj;
+ struct TALER_AUDITOR_ConfigResponse vr = {
+ .hr.reply = resp_obj,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ auditor->vr = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received config from URL `%s' with status %ld.\n",
+ auditor->vr_url,
+ response_code);
+ switch (response_code)
+ {
+ case 0:
+ GNUNET_break_op (0);
+ vr.hr.ec = TALER_EC_INVALID;
+ break;
+ case MHD_HTTP_OK:
+ if (NULL == resp_obj)
+ {
+ GNUNET_break_op (0);
+ vr.hr.http_status = 0;
+ vr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ vr.hr.ec = decode_config_json (resp_obj,
+ &vr.details.ok.vi,
+ &vr.details.ok.compat);
+ if (TALER_EC_NONE != vr.hr.ec)
+ {
+ GNUNET_break_op (0);
+ vr.hr.http_status = 0;
+ break;
+ }
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ vr.hr.ec = TALER_JSON_get_error_code (resp_obj);
+ vr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
+ break;
+ default:
+ vr.hr.ec = TALER_JSON_get_error_code (resp_obj);
+ vr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) vr.hr.ec);
+ break;
+ }
+ auditor->config_cb (auditor->config_cb_cls,
+ &vr);
+ TALER_AUDITOR_get_config_cancel (auditor);
+}
+
+
+struct TALER_AUDITOR_GetConfigHandle *
+TALER_AUDITOR_get_config (struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ TALER_AUDITOR_ConfigCallback config_cb,
+ void *config_cb_cls)
+{
+ struct TALER_AUDITOR_GetConfigHandle *auditor;
+ CURL *eh;
+
+ auditor = GNUNET_new (struct TALER_AUDITOR_GetConfigHandle);
+ auditor->config_cb = config_cb;
+ auditor->config_cb_cls = config_cb_cls;
+ auditor->ctx = ctx;
+ auditor->vr_url = TALER_url_join (url,
+ "config",
+ NULL);
+ if (NULL == auditor->vr_url)
+ {
+ GNUNET_break (0);
+ GNUNET_free (auditor->vr_url);
+ GNUNET_free (auditor);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Requesting auditor config with URL `%s'.\n",
+ auditor->vr_url);
+ eh = TALER_AUDITOR_curl_easy_get_ (auditor->vr_url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ TALER_AUDITOR_get_config_cancel (auditor);
+ return NULL;
+ }
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT,
+ (long) 300));
+ auditor->vr = GNUNET_CURL_job_add (auditor->ctx,
+ eh,
+ &config_completed_cb,
+ auditor);
+ return auditor;
+}
+
+
+void
+TALER_AUDITOR_get_config_cancel (struct TALER_AUDITOR_GetConfigHandle *auditor)
+{
+ if (NULL != auditor->vr)
+ {
+ GNUNET_CURL_job_cancel (auditor->vr);
+ auditor->vr = NULL;
+ }
+ GNUNET_free (auditor->vr_url);
+ GNUNET_free (auditor);
+}
+
+
+/* end of auditor_api_get_config.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..ea9c0371e
--- /dev/null
+++ b/src/lib/exchange_api_age_withdraw.c
@@ -0,0 +1,1117 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_age_withdraw.c
+ * @brief Implementation of /reserves/$RESERVE_PUB/age-withdraw requests
+ * @author Ă–zgĂĽr Kesim
+ */
+
+#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include <sys/wait.h>
+#include "taler_curl_lib.h"
+#include "taler_error_codes.h"
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_util.h"
+
+/**
+ * A CoinCandidate is populated from a master secret
+ */
+struct CoinCandidate
+{
+ /**
+ * Master key material for the coin candidates.
+ */
+ struct TALER_PlanchetMasterSecretP secret;
+
+ /**
+ * The details derived form the master secrets
+ */
+ struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails details;
+
+ /**
+ * Blinded hash of the coin
+ **/
+ struct TALER_BlindedCoinHashP blinded_coin_h;
+
+};
+
+
+/**
+ * Closure for a call to /csr-withdraw, contains data that is needed to process
+ * the result.
+ */
+struct CSRClosure
+{
+ /* Points to the actual candidate in CoinData.coin_candidates, to continue
+ * to build its contents based on the results from /csr-withdraw */
+ struct CoinCandidate *candidate;
+
+ /* The planchet to finally generate. Points to the corresponding candidate
+ * in CoindData.planchet_details */
+ struct TALER_PlanchetDetail *planchet;
+
+ /* Handler to the originating call to /age-withdraw, needed to either
+ * cancel the running age-withdraw request (on failure of the current call
+ * to /csr-withdraw), or to eventually perform the protocol, once all
+ * csr-withdraw requests have successfully finished. */
+ struct TALER_EXCHANGE_AgeWithdrawHandle *age_withdraw_handle;
+
+ /* Denomination information, needed for CS coins for the
+ * step after /csr-withdraw */
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+
+ /* Handler for the CS R request */
+ struct TALER_EXCHANGE_CsRWithdrawHandle *csr_withdraw_handle;
+};
+
+/**
+ * Data we keep per coin in the batch.
+ */
+struct CoinData
+{
+ /**
+ * The denomination of the coin. Must support age restriction, i.e
+ * its .keys.age_mask MUST not be 0
+ */
+ struct TALER_EXCHANGE_DenomPublicKey denom_pub;
+
+ /**
+ * The Candidates for the coin
+ */
+ struct CoinCandidate coin_candidates[TALER_CNC_KAPPA];
+
+ /**
+ * Details of the planchet(s).
+ */
+ struct TALER_PlanchetDetail planchet_details[TALER_CNC_KAPPA];
+
+ /**
+ * Closure for each candidate of type CS for the preflight request to
+ * /csr-withdraw
+ */
+ struct CSRClosure csr_cls[TALER_CNC_KAPPA];
+};
+
+/**
+ * A /reserves/$RESERVE_PUB/age-withdraw request-handle for calls with
+ * pre-blinded planchets. Returned by TALER_EXCHANGE_age_withdraw_blinded.
+ */
+struct TALER_EXCHANGE_AgeWithdrawBlindedHandle
+{
+
+ /**
+ * Reserve private key.
+ */
+ const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+ /**
+ * Reserve public key, calculated
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Signature of the reserve for the request, calculated after all
+ * parameters for the coins are collected.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /*
+ * The denomination keys of the exchange
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The age mask, 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;
+
+ /**
+ * The commitment calculated as SHA512 hash over all blinded_coin_h
+ */
+ struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+
+ /**
+ * Total amount requested (value plus withdraw fee).
+ */
+ struct TALER_Amount amount_with_fee;
+
+ /**
+ * Length of the @e blinded_input Array
+ */
+ size_t num_input;
+
+ /**
+ * The blinded planchet input for the call to /age-withdraw via
+ * TALER_EXCHANGE_age_withdraw_blinded
+ */
+ const struct TALER_EXCHANGE_AgeWithdrawBlindedInput *blinded_input;
+
+ /**
+ * The url for this request.
+ */
+ char *request_url;
+
+ /**
+ * Context for curl.
+ */
+ struct GNUNET_CURL_Context *curl_ctx;
+
+ /**
+ * CURL handle for the request job.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Post Context
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Function to call with age-withdraw response results.
+ */
+ TALER_EXCHANGE_AgeWithdrawBlindedCallback callback;
+
+ /**
+ * Closure for @e blinded_callback
+ */
+ void *callback_cls;
+};
+
+/**
+ * A /reserves/$RESERVE_PUB/age-withdraw request-handle for calls from
+ * a wallet, i. e. when blinding data is available.
+ */
+struct TALER_EXCHANGE_AgeWithdrawHandle
+{
+
+ /**
+ * Length of the @e coin_data Array
+ */
+ size_t num_coins;
+
+ /**
+ * The base-URL of the exchange.
+ */
+ const char *exchange_url;
+
+ /**
+ * Reserve private key.
+ */
+ const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+ /**
+ * Reserve public key, calculated
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Signature of the reserve for the request, calculated after all
+ * parameters for the coins are collected.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /*
+ * The denomination keys of the exchange
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The age mask, 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;
+
+ /**
+ * Array of per-coin data
+ */
+ struct CoinData *coin_data;
+
+ /**
+ * Context for curl.
+ */
+ struct GNUNET_CURL_Context *curl_ctx;
+
+ struct
+ {
+ /**
+ * Number of /csr-withdraw requests still pending.
+ */
+ unsigned int pending;
+
+ /**
+ * CURL handle for the request job.
+ */
+ struct GNUNET_CURL_Job *job;
+ } csr;
+
+
+ /**
+ * Function to call with age-withdraw response results.
+ */
+ TALER_EXCHANGE_AgeWithdrawCallback callback;
+
+ /**
+ * Closure for @e age_withdraw_cb
+ */
+ void *callback_cls;
+
+ /* The Handler for the actual call to the exchange */
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *procotol_handle;
+};
+
+/**
+ * We got a 200 OK response for the /reserves/$RESERVE_PUB/age-withdraw operation.
+ * Extract the noreveal_index and return it to the caller.
+ *
+ * @param awbh operation handle
+ * @param j_response reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+reserve_age_withdraw_ok (
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh,
+ const json_t *j_response)
+{
+ struct TALER_EXCHANGE_AgeWithdrawBlindedResponse response = {
+ .hr.reply = j_response,
+ .hr.http_status = MHD_HTTP_OK,
+ .details.ok.h_commitment = awbh->h_commitment
+ };
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint8 ("noreveal_index",
+ &response.details.ok.noreveal_index),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &response.details.ok.exchange_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK!=
+ GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ TALER_exchange_online_age_withdraw_confirmation_verify (
+ &awbh->h_commitment,
+ response.details.ok.noreveal_index,
+ &response.details.ok.exchange_pub,
+ &exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+
+ }
+
+ awbh->callback (awbh->callback_cls,
+ &response);
+ /* make sure the callback isn't called again */
+ awbh->callback = NULL;
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RESERVE_PUB/age-withdraw request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_AgeWithdrawHandle`
+ * @param response_code The HTTP response code
+ * @param response response data
+ */
+static void
+handle_reserve_age_withdraw_blinded_finished (
+ void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh = cls;
+ const json_t *j_response = response;
+ struct TALER_EXCHANGE_AgeWithdrawBlindedResponse awbr = {
+ .hr.reply = j_response,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ awbh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ awbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ reserve_age_withdraw_ok (awbh,
+ j_response))
+ {
+ GNUNET_break_op (0);
+ awbr.hr.http_status = 0;
+ awbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == awbh->callback);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh);
+ return;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ GNUNET_break_op (0);
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, the exchange basically just says
+ that it doesn't know this reserve. Can happen if we
+ query before the wire transfer went through.
+ We should simply pass the JSON reply to the application. */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* The age requirements might not have been met */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_GONE:
+ /* could happen if denomination was revoked */
+ /* Note: one might want to check /keys for revocation
+ signature here, alas tricky in case our /keys
+ is outdated => left to clients */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ /* only validate reply is well-formed */
+ {
+ uint64_t ptu;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint64 ("requirement_row",
+ &ptu),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ awbr.hr.http_status = 0;
+ awbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange age-withdraw\n",
+ (unsigned int) response_code,
+ (int) awbr.hr.ec);
+ break;
+ }
+ awbh->callback (awbh->callback_cls,
+ &awbr);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh);
+}
+
+
+/**
+ * Runs the actual age-withdraw operation with the blinded planchets.
+ *
+ * @param[in,out] awbh age withdraw handler
+ */
+static void
+perform_protocol (
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh)
+{
+#define FAIL_IF(cond) \
+ do { \
+ if ((cond)) \
+ { \
+ GNUNET_break (! (cond)); \
+ goto ERROR; \
+ } \
+ } while(0)
+
+ struct GNUNET_HashContext *coins_hctx;
+ json_t *j_denoms = NULL;
+ json_t *j_array_candidates = NULL;
+ json_t *j_request_body = NULL;
+ CURL *curlh = NULL;
+
+ GNUNET_assert (0 < awbh->num_input);
+ awbh->age_mask = awbh->blinded_input[0].denom_pub->key.age_mask;
+
+ FAIL_IF (GNUNET_OK !=
+ TALER_amount_set_zero (awbh->keys->currency,
+ &awbh->amount_with_fee));
+ /* Accumulate total value with fees */
+ for (size_t i = 0; i < awbh->num_input; i++)
+ {
+ struct TALER_Amount coin_total;
+ const struct TALER_EXCHANGE_DenomPublicKey *dpub =
+ awbh->blinded_input[i].denom_pub;
+
+ FAIL_IF (0 >
+ TALER_amount_add (&coin_total,
+ &dpub->fees.withdraw,
+ &dpub->value));
+ FAIL_IF (0 >
+ TALER_amount_add (&awbh->amount_with_fee,
+ &awbh->amount_with_fee,
+ &coin_total));
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Attempting to age-withdraw from reserve %s with maximum age %d\n",
+ TALER_B2S (&awbh->reserve_pub),
+ awbh->max_age);
+
+ coins_hctx = GNUNET_CRYPTO_hash_context_start ();
+ FAIL_IF (NULL == coins_hctx);
+
+
+ j_denoms = json_array ();
+ j_array_candidates = json_array ();
+ FAIL_IF ((NULL == j_denoms) ||
+ (NULL == j_array_candidates));
+
+ for (size_t i = 0; i< awbh->num_input; i++)
+ {
+ /* Build the denomination array */
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub =
+ awbh->blinded_input[i].denom_pub;
+ const struct TALER_DenominationHashP *denom_h = &denom_pub->h_key;
+ json_t *jdenom;
+
+ /* The mask must be the same for all coins */
+ FAIL_IF (awbh->age_mask.bits != denom_pub->key.age_mask.bits);
+
+ jdenom = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto (NULL,
+ denom_h));
+ FAIL_IF (NULL == jdenom);
+ FAIL_IF (0 < json_array_append_new (j_denoms,
+ jdenom));
+
+ /* Build the candidate array */
+ {
+ json_t *j_can = json_array ();
+ FAIL_IF (NULL == j_can);
+
+ for (size_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ struct TALER_BlindedCoinHashP bch;
+ const struct TALER_PlanchetDetail *planchet =
+ &awbh->blinded_input[i].planchet_details[k];
+ json_t *jc = GNUNET_JSON_PACK (
+ TALER_JSON_pack_blinded_planchet (
+ NULL,
+ &planchet->blinded_planchet));
+
+ FAIL_IF (NULL == jc);
+ FAIL_IF (0 < json_array_append_new (j_can,
+ jc));
+
+ TALER_coin_ev_hash (&planchet->blinded_planchet,
+ &planchet->denom_pub_hash,
+ &bch);
+
+ GNUNET_CRYPTO_hash_context_read (coins_hctx,
+ &bch,
+ sizeof(bch));
+ }
+
+ FAIL_IF (0 < json_array_append_new (j_array_candidates,
+ j_can));
+ }
+ }
+ }
+
+ /* Build the hash of the commitment */
+ GNUNET_CRYPTO_hash_context_finish (coins_hctx,
+ &awbh->h_commitment.hash);
+
+ /* Sign the request */
+ TALER_wallet_age_withdraw_sign (&awbh->h_commitment,
+ &awbh->amount_with_fee,
+ &awbh->age_mask,
+ awbh->max_age,
+ awbh->reserve_priv,
+ &awbh->reserve_sig);
+
+ /* Initiate the POST-request */
+ j_request_body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("denom_hs", j_denoms),
+ GNUNET_JSON_pack_array_steal ("blinded_coin_evs", j_array_candidates),
+ GNUNET_JSON_pack_uint64 ("max_age", awbh->max_age),
+ GNUNET_JSON_pack_data_auto ("reserve_sig", &awbh->reserve_sig));
+ FAIL_IF (NULL == j_request_body);
+
+ curlh = TALER_EXCHANGE_curl_easy_get_ (awbh->request_url);
+ FAIL_IF (NULL == curlh);
+ FAIL_IF (GNUNET_OK !=
+ TALER_curl_easy_post (&awbh->post_ctx,
+ curlh,
+ j_request_body));
+ json_decref (j_request_body);
+ j_request_body = NULL;
+
+ awbh->job = GNUNET_CURL_job_add2 (
+ awbh->curl_ctx,
+ curlh,
+ awbh->post_ctx.headers,
+ &handle_reserve_age_withdraw_blinded_finished,
+ awbh);
+ FAIL_IF (NULL == awbh->job);
+
+ /* No errors, return */
+ return;
+
+ERROR:
+ if (NULL != j_denoms)
+ json_decref (j_denoms);
+ if (NULL != j_array_candidates)
+ json_decref (j_array_candidates);
+ if (NULL != j_request_body)
+ json_decref (j_request_body);
+ if (NULL != curlh)
+ curl_easy_cleanup (curlh);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh);
+ return;
+#undef FAIL_IF
+}
+
+
+/**
+ * @brief Callback to copy the results from the call to TALER_age_withdraw_blinded
+ * to the result for the originating call from TALER_age_withdraw.
+ *
+ * @param cls struct TALER_AgeWithdrawHandle
+ * @param awbr The response
+ */
+static void
+copy_results (
+ void *cls,
+ const struct TALER_EXCHANGE_AgeWithdrawBlindedResponse *awbr)
+{
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh = cls;
+ uint8_t k = awbr->details.ok.noreveal_index;
+ struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails details[awh->num_coins];
+ struct TALER_BlindedCoinHashP blinded_coin_hs[awh->num_coins];
+ struct TALER_EXCHANGE_AgeWithdrawResponse resp = {
+ .hr = awbr->hr,
+ .details = {
+ .ok = { .noreveal_index = awbr->details.ok.noreveal_index,
+ .h_commitment = awbr->details.ok.h_commitment,
+ .exchange_pub = awbr->details.ok.exchange_pub,
+ .num_coins = awh->num_coins,
+ .coin_details = details,
+ .blinded_coin_hs = blinded_coin_hs},
+ },
+ };
+
+ for (size_t n = 0; n< awh->num_coins; n++)
+ {
+ details[n] = awh->coin_data[n].coin_candidates[k].details;
+ details[n].planchet = awh->coin_data[n].planchet_details[k];
+ blinded_coin_hs[n] = awh->coin_data[n].coin_candidates[k].blinded_coin_h;
+ }
+
+ awh->callback (awh->callback_cls,
+ &resp);
+
+ awh->callback = NULL;
+}
+
+
+/**
+ * @brief Prepares and executes TALER_EXCHANGE_age_withdraw_blinded.
+ * If there were CS-denominations involved, started once the all calls
+ * to /csr-withdraw are done.
+ */
+static void
+call_age_withdraw_blinded (
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh)
+{
+ struct TALER_EXCHANGE_AgeWithdrawBlindedInput blinded_input[awh->num_coins];
+
+ /* Prepare the blinded planchets as input */
+ for (size_t n = 0; n < awh->num_coins; n++)
+ {
+ blinded_input[n].denom_pub = &awh->coin_data[n].denom_pub;
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ blinded_input[n].planchet_details[k] =
+ awh->coin_data[n].planchet_details[k];
+ }
+
+ awh->procotol_handle =
+ TALER_EXCHANGE_age_withdraw_blinded (
+ awh->curl_ctx,
+ awh->keys,
+ awh->exchange_url,
+ awh->reserve_priv,
+ awh->max_age,
+ awh->num_coins,
+ blinded_input,
+ copy_results,
+ awh);
+}
+
+
+/**
+ * Prepares the request URL for the age-withdraw request
+ *
+ * @param awbh The handler
+ * @param exchange_url The base-URL to the exchange
+ */
+static
+enum GNUNET_GenericReturnValue
+prepare_url (
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh,
+ const char *exchange_url)
+{
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+ char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &awbh->reserve_pub,
+ sizeof (awbh->reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s/age-withdraw",
+ pub_str);
+
+ awbh->request_url = TALER_url_join (exchange_url,
+ arg_str,
+ NULL);
+ if (NULL == awbh->request_url)
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * @brief Function called when CSR withdraw retrieval is finished
+ *
+ * @param cls the `struct CSRClosure *`
+ * @param csrr replies from the /csr-withdraw request
+ */
+static void
+csr_withdraw_done (
+ void *cls,
+ const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr)
+{
+ struct CSRClosure *csr = cls;
+ struct CoinCandidate *can;
+ struct TALER_PlanchetDetail *planchet;
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh;
+
+ GNUNET_assert (NULL != csr);
+ awh = csr->age_withdraw_handle;
+ planchet = csr->planchet;
+ can = csr->candidate;
+
+ GNUNET_assert (NULL != can);
+ GNUNET_assert (NULL != planchet);
+ GNUNET_assert (NULL != awh);
+
+ csr->csr_withdraw_handle = NULL;
+
+ switch (csrr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ {
+ bool success = false;
+ /* Complete the initialization of the coin with CS denomination */
+ can->details.alg_values = csrr->details.ok.alg_values;
+ GNUNET_assert (can->details.alg_values.cipher
+ == TALER_DENOMINATION_CS);
+ TALER_planchet_setup_coin_priv (&can->secret,
+ &can->details.alg_values,
+ &can->details.coin_priv);
+ TALER_planchet_blinding_secret_create (&can->secret,
+ &can->details.alg_values,
+ &can->details.blinding_key);
+ /* This initializes the 2nd half of the
+ can->planchet_detail.blinded_planchet! */
+ do {
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&csr->denom_pub->key,
+ &can->details.alg_values,
+ &can->details.blinding_key,
+ &can->details.coin_priv,
+ &can->details.h_age_commitment,
+ &can->details.h_coin_pub,
+ planchet))
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_age_withdraw_cancel (awh);
+ break;
+ }
+
+ if (GNUNET_OK !=
+ TALER_coin_ev_hash (&planchet->blinded_planchet,
+ &planchet->denom_pub_hash,
+ &can->blinded_coin_h))
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_age_withdraw_cancel (awh);
+ break;
+ }
+ success = true;
+ } while(0);
+
+ awh->csr.pending--;
+
+ /* No more pending requests to /csr-withdraw, we can now perform the
+ * actual age-withdraw operation */
+ if (0 == awh->csr.pending && success)
+ call_age_withdraw_blinded (awh);
+ return;
+ }
+ default:
+ break;
+ }
+
+ TALER_EXCHANGE_age_withdraw_cancel (awh);
+}
+
+
+/**
+ * @brief Prepare the coins for the call to age-withdraw and calculates
+ * the total amount with fees.
+ *
+ * For denomination with CS as cipher, initiates the preflight to retrieve the
+ * csr-parameter via /csr-withdraw.
+ *
+ * @param awh The handler to the age-withdraw
+ * @param num_coins The number of coins in @e coin_inputs
+ * @param coin_inputs The input for the individual coin(-candidates)
+ * @return GNUNET_OK on success, GNUNET_SYSERR on failure
+ */
+static
+enum GNUNET_GenericReturnValue
+prepare_coins (
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh,
+ size_t num_coins,
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput coin_inputs[
+ static num_coins])
+{
+#define FAIL_IF(cond) \
+ do { \
+ if ((cond)) \
+ { \
+ GNUNET_break (! (cond)); \
+ goto ERROR; \
+ } \
+ } while(0)
+
+ GNUNET_assert (0 < num_coins);
+ awh->age_mask = coin_inputs[0].denom_pub->key.age_mask;
+
+ awh->coin_data = GNUNET_new_array (awh->num_coins,
+ struct CoinData);
+
+ for (size_t i = 0; i < num_coins; i++)
+ {
+ struct CoinData *cd = &awh->coin_data[i];
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput *input = &coin_inputs[i];
+ cd->denom_pub = *input->denom_pub;
+
+ /* The mask must be the same for all coins */
+ FAIL_IF (awh->age_mask.bits != input->denom_pub->key.age_mask.bits);
+
+ TALER_denom_pub_deep_copy (&cd->denom_pub.key,
+ &input->denom_pub->key);
+
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ struct CoinCandidate *can = &cd->coin_candidates[k];
+ struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
+
+ can->secret = input->secrets[k];
+ /* Derive the age restriction from the given secret and
+ * the maximum age */
+ TALER_age_restriction_from_secret (
+ &can->secret,
+ &input->denom_pub->key.age_mask,
+ awh->max_age,
+ &can->details.age_commitment_proof);
+
+ TALER_age_commitment_hash (&can->details.age_commitment_proof.commitment,
+ &can->details.h_age_commitment);
+
+ switch (input->denom_pub->key.cipher)
+ {
+ case TALER_DENOMINATION_RSA:
+ {
+ can->details.alg_values.cipher = TALER_DENOMINATION_RSA;
+ TALER_planchet_setup_coin_priv (&can->secret,
+ &can->details.alg_values,
+ &can->details.coin_priv);
+ TALER_planchet_blinding_secret_create (&can->secret,
+ &can->details.alg_values,
+ &can->details.blinding_key);
+ FAIL_IF (GNUNET_OK !=
+ TALER_planchet_prepare (&cd->denom_pub.key,
+ &can->details.alg_values,
+ &can->details.blinding_key,
+ &can->details.coin_priv,
+ &can->details.h_age_commitment,
+ &can->details.h_coin_pub,
+ planchet));
+ FAIL_IF (GNUNET_OK !=
+ TALER_coin_ev_hash (&planchet->blinded_planchet,
+ &planchet->denom_pub_hash,
+ &can->blinded_coin_h));
+ break;
+ }
+ case TALER_DENOMINATION_CS:
+ {
+ can->details.alg_values.cipher = TALER_DENOMINATION_CS;
+
+ struct CSRClosure *cls = &cd->csr_cls[k];
+ /**
+ * Save the handler and the denomination for the callback
+ * after the call to csr-withdraw */
+ cls->age_withdraw_handle = awh;
+ cls->candidate = can;
+ cls->planchet = planchet;
+ cls->denom_pub = &cd->denom_pub;
+
+ TALER_cs_withdraw_nonce_derive (
+ &can->secret,
+ &planchet->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! */
+ planchet->blinded_planchet.cipher = TALER_DENOMINATION_CS;
+ cls->csr_withdraw_handle =
+ TALER_EXCHANGE_csr_withdraw (
+ awh->curl_ctx,
+ awh->exchange_url,
+ &cd->denom_pub,
+ &planchet->blinded_planchet.details.cs_blinded_planchet.nonce,
+ &csr_withdraw_done,
+ cls);
+ FAIL_IF (NULL == cls->csr_withdraw_handle);
+
+ awh->csr.pending++;
+ break;
+ }
+ default:
+ FAIL_IF (1);
+ }
+ }
+ }
+ return GNUNET_OK;
+
+ERROR:
+ TALER_EXCHANGE_age_withdraw_cancel (awh);
+ return GNUNET_SYSERR;
+#undef FAIL_IF
+};
+
+struct TALER_EXCHANGE_AgeWithdrawHandle *
+TALER_EXCHANGE_age_withdraw (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ size_t num_coins,
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput coin_inputs[const static
+ num_coins],
+ uint8_t max_age,
+ TALER_EXCHANGE_AgeWithdrawCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh;
+
+ awh = GNUNET_new (struct TALER_EXCHANGE_AgeWithdrawHandle);
+ awh->exchange_url = exchange_url;
+ awh->keys = TALER_EXCHANGE_keys_incref (keys);
+ awh->curl_ctx = curl_ctx;
+ awh->reserve_priv = reserve_priv;
+ awh->callback = res_cb;
+ awh->callback_cls = res_cb_cls;
+ awh->num_coins = num_coins;
+ awh->max_age = max_age;
+
+
+ if (GNUNET_OK != prepare_coins (awh,
+ num_coins,
+ coin_inputs))
+ return NULL;
+
+ /* If there were no CS denominations, we can now perform the actual
+ * age-withdraw protocol. Otherwise, there are calls to /csr-withdraw
+ * in flight and once they finish, the age-withdraw-protocol will be
+ * called from within the csr_withdraw_done-function.
+ */
+ if (0 == awh->csr.pending)
+ call_age_withdraw_blinded (awh);
+
+ return awh;
+}
+
+
+void
+TALER_EXCHANGE_age_withdraw_cancel (
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh)
+{
+ /* Cleanup coin data */
+ for (unsigned int i = 0; i<awh->num_coins; i++)
+ {
+ struct CoinData *cd = &awh->coin_data[i];
+
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
+ struct CSRClosure *cls = &cd->csr_cls[k];
+
+ if (NULL != cls->csr_withdraw_handle)
+ {
+ TALER_EXCHANGE_csr_withdraw_cancel (cls->csr_withdraw_handle);
+ cls->csr_withdraw_handle = NULL;
+ }
+ TALER_blinded_planchet_free (&planchet->blinded_planchet);
+ }
+ TALER_denom_pub_free (&cd->denom_pub.key);
+ }
+ GNUNET_free (awh->coin_data);
+ TALER_EXCHANGE_keys_decref (awh->keys);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awh->procotol_handle);
+ awh->procotol_handle = NULL;
+ GNUNET_free (awh);
+}
+
+
+struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *
+TALER_EXCHANGE_age_withdraw_blinded (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ uint8_t max_age,
+ unsigned int num_input,
+ const struct TALER_EXCHANGE_AgeWithdrawBlindedInput blinded_input[static
+ num_input],
+ TALER_EXCHANGE_AgeWithdrawBlindedCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh =
+ GNUNET_new (struct TALER_EXCHANGE_AgeWithdrawBlindedHandle);
+
+ awbh->num_input = num_input;
+ awbh->blinded_input = blinded_input;
+ awbh->keys = TALER_EXCHANGE_keys_incref (keys);
+ awbh->curl_ctx = curl_ctx;
+ awbh->reserve_priv = reserve_priv;
+ awbh->callback = res_cb;
+ awbh->callback_cls = res_cb_cls;
+ awbh->max_age = max_age;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&awbh->reserve_priv->eddsa_priv,
+ &awbh->reserve_pub.eddsa_pub);
+
+ if (GNUNET_OK != prepare_url (awbh,
+ exchange_url))
+ return NULL;
+
+ perform_protocol (awbh);
+ return awbh;
+}
+
+
+void
+TALER_EXCHANGE_age_withdraw_blinded_cancel (
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh)
+{
+ if (NULL == awbh)
+ return;
+
+ if (NULL != awbh->job)
+ {
+ GNUNET_CURL_job_cancel (awbh->job);
+ awbh->job = NULL;
+ }
+ GNUNET_free (awbh->request_url);
+ TALER_EXCHANGE_keys_decref (awbh->keys);
+ TALER_curl_easy_post_finished (&awbh->post_ctx);
+ GNUNET_free (awbh);
+}
+
+
+/* exchange_api_age_withdraw.c */
diff --git a/src/lib/exchange_api_age_withdraw_reveal.c b/src/lib/exchange_api_age_withdraw_reveal.c
new file mode 100644
index 000000000..a448d109d
--- /dev/null
+++ b/src/lib/exchange_api_age_withdraw_reveal.c
@@ -0,0 +1,471 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_age_withdraw_reveal.c
+ * @brief Implementation of /age-withdraw/$ACH/reveal requests
+ * @author Ă–zgĂĽr Kesim
+ */
+
+#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "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"
+
+/**
+ * Handler for a running age-withdraw-reveal request
+ */
+struct TALER_EXCHANGE_AgeWithdrawRevealHandle
+{
+
+ /* The index not to be disclosed */
+ uint8_t noreveal_index;
+
+ /* The age-withdraw commitment */
+ struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+
+ /* The reserve's public key */
+ const struct TALER_ReservePublicKeyP *reserve_pub;
+
+ /* Number of coins */
+ size_t num_coins;
+
+ /* The @e num_coins * kappa coin secrets from the age-withdraw commitment */
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput *coins_input;
+
+ /* The url for the reveal request */
+ const char *request_url;
+
+ /**
+ * CURL handle for the request job.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Post Context
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /* Callback */
+ TALER_EXCHANGE_AgeWithdrawRevealCallback callback;
+
+ /* Reveal */
+ void *callback_cls;
+};
+
+
+/**
+ * We got a 200 OK response for the /age-withdraw/$ACH/reveal operation.
+ * Extract the signed blindedcoins and return it to the caller.
+ *
+ * @param awrh operation handle
+ * @param j_response reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+age_withdraw_reveal_ok (
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh,
+ const json_t *j_response)
+{
+ struct TALER_EXCHANGE_AgeWithdrawRevealResponse response = {
+ .hr.reply = j_response,
+ .hr.http_status = MHD_HTTP_OK,
+ };
+ const json_t *j_sigs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("ev_sigs",
+ &j_sigs),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK != GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (awrh->num_coins != json_array_size (j_sigs))
+ {
+ /* Number of coins generated does not match our expectation */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ struct TALER_BlindedDenominationSignature denom_sigs[awrh->num_coins];
+ json_t *j_sig;
+ size_t n;
+
+ /* Reconstruct the coins and unblind the signatures */
+ json_array_foreach (j_sigs, n, j_sig)
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_blinded_denom_sig (NULL,
+ &denom_sigs[n]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK != GNUNET_JSON_parse (j_sig,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ }
+
+ response.details.ok.num_sigs = awrh->num_coins;
+ response.details.ok.blinded_denom_sigs = denom_sigs;
+ awrh->callback (awrh->callback_cls,
+ &response);
+ /* Make sure the callback isn't called again */
+ awrh->callback = NULL;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /age-withdraw/$ACH/reveal request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_AgeWithdrawRevealHandle`
+ * @param response_code The HTTP response code
+ * @param response response data
+ */
+static void
+handle_age_withdraw_reveal_finished (
+ void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh = cls;
+ const json_t *j_response = response;
+ struct TALER_EXCHANGE_AgeWithdrawRevealResponse awr = {
+ .hr.reply = j_response,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ awrh->job = NULL;
+ /* FIXME[oec]: Only handle response-codes that are in the spec */
+ switch (response_code)
+ {
+ case 0:
+ awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = age_withdraw_reveal_ok (awrh,
+ j_response);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break_op (0);
+ awr.hr.http_status = 0;
+ awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == awrh->callback);
+ TALER_EXCHANGE_age_withdraw_reveal_cancel (awrh);
+ 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);
+ /**
+ * This should never happen, as we don't sent any signatures
+ * to the exchange to verify. 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_NOT_FOUND:
+ /* Nothing really to verify, the exchange basically just says
+ that it doesn't know this age-withdraw commitment. */
+ 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:
+ /* An age commitment for one of the coins did not fulfill
+ * the required maximum age requirement of the corresponding
+ * reserve.
+ * Error code: TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE.
+ */
+ 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;
+ }
+ awrh->callback (awrh->callback_cls,
+ &awr);
+ TALER_EXCHANGE_age_withdraw_reveal_cancel (awrh);
+}
+
+
+/**
+ * Prepares the request URL for the age-withdraw-reveal request
+ *
+ * @param exchange_url The base-URL to the exchange
+ * @param[in,out] awrh The handler
+ * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
+ */
+static
+enum GNUNET_GenericReturnValue
+prepare_url (
+ const char *exchange_url,
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh)
+{
+ char arg_str[sizeof (struct TALER_AgeWithdrawCommitmentHashP) * 2 + 32];
+ char pub_str[sizeof (struct TALER_AgeWithdrawCommitmentHashP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (&awrh->h_commitment,
+ sizeof (awrh->h_commitment),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "age-withdraw/%s/reveal",
+ pub_str);
+
+ awrh->request_url = TALER_url_join (exchange_url,
+ arg_str,
+ NULL);
+ if (NULL == awrh->request_url)
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_age_withdraw_reveal_cancel (awrh);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Call /age-withdraw/$ACH/reveal
+ *
+ * @param curl_ctx The context for CURL
+ * @param awrh The handler
+ */
+static
+void
+perform_protocol (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh)
+{
+ CURL *curlh = NULL;
+ json_t *j_request_body = NULL;
+ json_t *j_array_of_secrets = NULL;
+ json_t *j_secrets = NULL;
+ json_t *j_sec = NULL;
+
+#define FAIL_IF(cond) \
+ do { \
+ if ((cond)) \
+ { \
+ GNUNET_break (! (cond)); \
+ goto ERROR; \
+ } \
+ } while(0)
+
+ j_array_of_secrets = json_array ();
+ FAIL_IF (NULL == j_array_of_secrets);
+
+ for (size_t n = 0; n < awrh->num_coins; n++)
+ {
+ const struct TALER_PlanchetMasterSecretP *secrets =
+ awrh->coins_input[n].secrets;
+
+ j_secrets = json_array ();
+ FAIL_IF (NULL == j_secrets);
+
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ const struct TALER_PlanchetMasterSecretP *secret = &secrets[k];
+ if (awrh->noreveal_index == k)
+ continue;
+
+ j_sec = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto (NULL, secret));
+
+ FAIL_IF (NULL == j_sec);
+ FAIL_IF (0 < json_array_append_new (j_secrets,
+ j_sec));
+ }
+
+ FAIL_IF (0 < json_array_append_new (j_array_of_secrets,
+ j_secrets));
+ }
+ j_request_body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ awrh->reserve_pub),
+ GNUNET_JSON_pack_array_steal ("disclosed_coin_secrets",
+ j_array_of_secrets));
+ FAIL_IF (NULL == j_request_body);
+
+ curlh = TALER_EXCHANGE_curl_easy_get_ (awrh->request_url);
+ FAIL_IF (NULL == curlh);
+ FAIL_IF (GNUNET_OK !=
+ TALER_curl_easy_post (&awrh->post_ctx,
+ curlh,
+ j_request_body));
+ json_decref (j_request_body);
+ j_request_body = NULL;
+
+ awrh->job = GNUNET_CURL_job_add2 (curl_ctx,
+ curlh,
+ awrh->post_ctx.headers,
+ &handle_age_withdraw_reveal_finished,
+ awrh);
+ FAIL_IF (NULL == awrh->job);
+
+ /* No error, return */
+ return;
+
+ERROR:
+ if (NULL != j_sec)
+ json_decref (j_sec);
+ if (NULL != j_secrets)
+ json_decref (j_secrets);
+ if (NULL != j_array_of_secrets)
+ json_decref (j_array_of_secrets);
+ if (NULL != j_request_body)
+ json_decref (j_request_body);
+ if (NULL != curlh)
+ curl_easy_cleanup (curlh);
+ TALER_EXCHANGE_age_withdraw_reveal_cancel (awrh);
+ return;
+#undef FAIL_IF
+}
+
+
+struct TALER_EXCHANGE_AgeWithdrawRevealHandle *
+TALER_EXCHANGE_age_withdraw_reveal (
+ struct GNUNET_CURL_Context *curl_ctx,
+ const char *exchange_url,
+ size_t num_coins,
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput coins_input[static
+ num_coins],
+ uint8_t noreveal_index,
+ const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ TALER_EXCHANGE_AgeWithdrawRevealCallback reveal_cb,
+ void *reveal_cb_cls)
+{
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh =
+ GNUNET_new (struct TALER_EXCHANGE_AgeWithdrawRevealHandle);
+ awrh->noreveal_index = noreveal_index;
+ awrh->h_commitment = *h_commitment;
+ awrh->num_coins = num_coins;
+ awrh->coins_input = coins_input;
+ awrh->callback = reveal_cb;
+ awrh->callback_cls = reveal_cb_cls;
+ awrh->reserve_pub = reserve_pub;
+
+ if (GNUNET_OK !=
+ prepare_url (exchange_url,
+ awrh))
+ return NULL;
+
+ perform_protocol (curl_ctx, awrh);
+
+ return awrh;
+}
+
+
+void
+TALER_EXCHANGE_age_withdraw_reveal_cancel (
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh)
+{
+ if (NULL != awrh->job)
+ {
+ GNUNET_CURL_job_cancel (awrh->job);
+ awrh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&awrh->post_ctx);
+ /* FIXME[oec]: anything else left to cleanup!? */
+ GNUNET_free (awrh);
+}
+
+
+/* exchange_api_age_withdraw_reveal.c */
diff --git a/src/lib/exchange_api_auditor_add_denomination.c b/src/lib/exchange_api_auditor_add_denomination.c
new file mode 100644
index 000000000..89de0d7f1
--- /dev/null
+++ b/src/lib/exchange_api_auditor_add_denomination.c
@@ -0,0 +1,238 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2021 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_auditor_add_denomination.c
+ * @brief functions for the auditor to add its signature for denomination at the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "auditor_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_AuditorAddDenominationHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_AuditorAddDenominationCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /auditor/$AUDITOR_PUB/$H_DENOM_PUB request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_AuditorAddDenominationHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_auditor_add_denomination_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_AuditorAddDenominationHandle *ah = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_AuditorAddDenominationResponse adr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ ah->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_GONE:
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_PRECONDITION_FAILED:
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ if (NULL != json)
+ {
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange auditor-add-denomination at URL `%s'\n",
+ (unsigned int) response_code,
+ (int) adr.hr.ec,
+ ah->url);
+ }
+ else
+ {
+ adr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ adr.hr.hint = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected HTTP response code %u (no JSON returned) at URL `%s'\n",
+ (unsigned int) response_code,
+ ah->url);
+ }
+ break;
+ }
+ if (NULL != ah->cb)
+ {
+ ah->cb (ah->cb_cls,
+ &adr);
+ ah->cb = NULL;
+ }
+ TALER_EXCHANGE_add_auditor_denomination_cancel (ah);
+}
+
+
+struct TALER_EXCHANGE_AuditorAddDenominationHandle *
+TALER_EXCHANGE_add_auditor_denomination (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const struct TALER_AuditorSignatureP *auditor_sig,
+ TALER_EXCHANGE_AuditorAddDenominationCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_AuditorAddDenominationHandle *ah;
+ CURL *eh;
+ json_t *body;
+
+ ah = GNUNET_new (struct TALER_EXCHANGE_AuditorAddDenominationHandle);
+ ah->cb = cb;
+ ah->cb_cls = cb_cls;
+ ah->ctx = ctx;
+ {
+ char apub_str[sizeof (*auditor_pub) * 2];
+ char denom_str[sizeof (*h_denom_pub) * 2];
+ char arg_str[sizeof (apub_str) + sizeof (denom_str) + 32];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (auditor_pub,
+ sizeof (*auditor_pub),
+ apub_str,
+ sizeof (apub_str));
+ *end = '\0';
+ end = GNUNET_STRINGS_data_to_string (h_denom_pub,
+ sizeof (*h_denom_pub),
+ denom_str,
+ sizeof (denom_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "auditors/%s/%s",
+ apub_str,
+ denom_str);
+ ah->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ }
+ if (NULL == ah->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (ah);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("auditor_sig",
+ auditor_sig));
+ eh = TALER_AUDITOR_curl_easy_get_ (ah->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&ah->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (ah->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ ah->url);
+ ah->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ ah->post_ctx.headers,
+ &handle_auditor_add_denomination_finished,
+ ah);
+ if (NULL == ah->job)
+ {
+ TALER_EXCHANGE_add_auditor_denomination_cancel (ah);
+ return NULL;
+ }
+ return ah;
+}
+
+
+void
+TALER_EXCHANGE_add_auditor_denomination_cancel (
+ struct TALER_EXCHANGE_AuditorAddDenominationHandle *ah)
+{
+ if (NULL != ah->job)
+ {
+ GNUNET_CURL_job_cancel (ah->job);
+ ah->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&ah->post_ctx);
+ GNUNET_free (ah->url);
+ GNUNET_free (ah);
+}
diff --git a/src/lib/exchange_api_csr_melt.c b/src/lib/exchange_api_csr_melt.c
new file mode 100644
index 000000000..f59995af3
--- /dev/null
+++ b/src/lib/exchange_api_csr_melt.c
@@ -0,0 +1,317 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_csr_melt.c
+ * @brief Implementation of /csr-melt requests (get R in exchange used for Clause Schnorr refresh)
+ * @author Lucien Heuzeveldt
+ * @author Gian Demarmels
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Clause Schnorr R Handle
+ */
+struct TALER_EXCHANGE_CsRMeltHandle
+{
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_CsRMeltCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+};
+
+
+/**
+ * We got a 200 OK response for the /reserves/$RESERVE_PUB/withdraw operation.
+ * Extract the coin's signature and return it to the caller. The signature we
+ * get from the exchange is for the blinded value. Thus, we first must
+ * unblind it and then should verify its validity against our coin's hash.
+ *
+ * If everything checks out, we return the unblinded signature
+ * to the application via the callback.
+ *
+ * @param csrh operation handle
+ * @param arr reply from the exchange
+ * @param hr http response details
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+csr_ok (struct TALER_EXCHANGE_CsRMeltHandle *csrh,
+ const json_t *arr,
+ struct TALER_EXCHANGE_HttpResponse *hr)
+{
+ unsigned int alen = json_array_size (arr);
+ struct TALER_ExchangeWithdrawValues alg_values[GNUNET_NZL (alen)];
+ struct TALER_EXCHANGE_CsRMeltResponse csrr = {
+ .hr = *hr,
+ .details.ok.alg_values_len = alen,
+ .details.ok.alg_values = alg_values
+ };
+
+ for (unsigned int i = 0; i<alen; i++)
+ {
+ json_t *av = json_array_get (arr,
+ i);
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_exchange_withdraw_values (
+ "ewv",
+ &alg_values[i]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (av,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ csrh->cb (csrh->cb_cls,
+ &csrr);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the HTTP /csr request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_CsRMeltHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_csr_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_CsRMeltHandle *csrh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_HttpResponse hr = {
+ .reply = j,
+ .http_status = (unsigned int) response_code
+ };
+ struct TALER_EXCHANGE_CsRMeltResponse csrr = {
+ .hr = hr
+ };
+
+ csrh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ csrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ json_t *arr;
+
+ arr = json_object_get (j,
+ "ewvs");
+ if ( (NULL == arr) ||
+ (0 == json_array_size (arr)) ||
+ (GNUNET_OK !=
+ csr_ok (csrh,
+ arr,
+ &hr)) )
+ {
+ GNUNET_break_op (0);
+ csrr.hr.http_status = 0;
+ csrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ TALER_EXCHANGE_csr_melt_cancel (csrh);
+ return;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ csrr.hr.ec = TALER_JSON_get_error_code (j);
+ csrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, the exchange basically just says
+ that it doesn't know the /csr endpoint or denomination.
+ Can happen if the exchange doesn't support Clause Schnorr.
+ We should simply pass the JSON reply to the application. */
+ csrr.hr.ec = TALER_JSON_get_error_code (j);
+ csrr.hr.hint = TALER_JSON_get_error_hint (j);
+ 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 */
+ csrr.hr.ec = TALER_JSON_get_error_code (j);
+ csrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ csrr.hr.ec = TALER_JSON_get_error_code (j);
+ csrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ csrr.hr.ec = TALER_JSON_get_error_code (j);
+ csrr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for CS R request\n",
+ (unsigned int) response_code,
+ (int) hr.ec);
+ break;
+ }
+ csrh->cb (csrh->cb_cls,
+ &csrr);
+ csrh->cb = NULL;
+ TALER_EXCHANGE_csr_melt_cancel (csrh);
+}
+
+
+struct TALER_EXCHANGE_CsRMeltHandle *
+TALER_EXCHANGE_csr_melt (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_RefreshMasterSecretP *rms,
+ unsigned int nks_len,
+ struct TALER_EXCHANGE_NonceKey nks[static nks_len],
+ TALER_EXCHANGE_CsRMeltCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_CsRMeltHandle *csrh;
+ json_t *csr_arr;
+
+ if (0 == nks_len)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ for (unsigned int i = 0; i<nks_len; i++)
+ if (TALER_DENOMINATION_CS != nks[i].pk->key.cipher)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ csrh = GNUNET_new (struct TALER_EXCHANGE_CsRMeltHandle);
+ csrh->cb = res_cb;
+ csrh->cb_cls = res_cb_cls;
+ csr_arr = json_array ();
+ GNUNET_assert (NULL != csr_arr);
+ for (unsigned int i = 0; i<nks_len; i++)
+ {
+ const struct TALER_EXCHANGE_NonceKey *nk = &nks[i];
+ json_t *csr_obj;
+
+ csr_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("coin_offset",
+ nk->cnc_num),
+ GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+ &nk->pk->h_key));
+ GNUNET_assert (NULL != csr_obj);
+ GNUNET_assert (0 ==
+ json_array_append_new (csr_arr,
+ csr_obj));
+ }
+ csrh->url = TALER_url_join (url,
+ "csr-melt",
+ NULL);
+ if (NULL == csrh->url)
+ {
+ json_decref (csr_arr);
+ GNUNET_free (csrh);
+ return NULL;
+ }
+ {
+ CURL *eh;
+ json_t *req;
+
+ req = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("rms",
+ rms),
+ GNUNET_JSON_pack_array_steal ("nks",
+ csr_arr));
+ eh = TALER_EXCHANGE_curl_easy_get_ (csrh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&csrh->post_ctx,
+ eh,
+ req)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (req);
+ GNUNET_free (csrh->url);
+ GNUNET_free (csrh);
+ return NULL;
+ }
+ json_decref (req);
+ csrh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ csrh->post_ctx.headers,
+ &handle_csr_finished,
+ csrh);
+ }
+ return csrh;
+}
+
+
+void
+TALER_EXCHANGE_csr_melt_cancel (struct TALER_EXCHANGE_CsRMeltHandle *csrh)
+{
+ if (NULL != csrh->job)
+ {
+ GNUNET_CURL_job_cancel (csrh->job);
+ csrh->job = NULL;
+ }
+ GNUNET_free (csrh->url);
+ TALER_curl_easy_post_finished (&csrh->post_ctx);
+ GNUNET_free (csrh);
+}
diff --git a/src/lib/exchange_api_kyc_check.c b/src/lib/exchange_api_kyc_check.c
new file mode 100644
index 000000000..373dd89a7
--- /dev/null
+++ b/src/lib/exchange_api_kyc_check.c
@@ -0,0 +1,329 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_kyc_check.c
+ * @brief Implementation of the /kyc-check request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h> /* just for HTTP check codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A ``/kyc-check`` handle
+ */
+struct TALER_EXCHANGE_KycCheckHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Keys of the exchange.
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_KycStatusCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Hash of the payto:// URL that is being KYC'ed.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /kyc-check request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_KycCheckHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_kyc_check_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_KycCheckHandle *kch = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_KycStatus ks = {
+ .http_status = (unsigned int) response_code
+ };
+
+ kch->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ const json_t *kyc_details;
+ uint32_t status;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &ks.details.ok.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &ks.details.ok.exchange_pub),
+ GNUNET_JSON_spec_timestamp ("now",
+ &ks.details.ok.timestamp),
+ GNUNET_JSON_spec_object_const ("kyc_details",
+ &kyc_details),
+ GNUNET_JSON_spec_uint32 ("aml_status",
+ &status),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ks.http_status = 0;
+ ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ ks.details.ok.kyc_details = kyc_details;
+ ks.details.ok.aml_status
+ = (enum TALER_AmlDecisionState) status;
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (kch->keys,
+ &ks.details.ok.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ ks.http_status = 0;
+ ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ GNUNET_JSON_parse_free (spec);
+ break;
+ }
+
+ if (GNUNET_OK !=
+ TALER_exchange_online_account_setup_success_verify (
+ &kch->h_payto,
+ ks.details.ok.kyc_details,
+ ks.details.ok.timestamp,
+ &ks.details.ok.exchange_pub,
+ &ks.details.ok.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ ks.http_status = 0;
+ ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ GNUNET_JSON_parse_free (spec);
+ break;
+ }
+ kch->cb (kch->cb_cls,
+ &ks);
+ GNUNET_JSON_parse_free (spec);
+ TALER_EXCHANGE_kyc_check_cancel (kch);
+ return;
+ }
+ case MHD_HTTP_ACCEPTED:
+ {
+ uint32_t status;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("kyc_url",
+ &ks.details.accepted.kyc_url),
+ GNUNET_JSON_spec_uint32 ("aml_status",
+ &status),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ks.http_status = 0;
+ ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ ks.details.accepted.aml_status
+ = (enum TALER_AmlDecisionState) status;
+ kch->cb (kch->cb_cls,
+ &ks);
+ GNUNET_JSON_parse_free (spec);
+ TALER_EXCHANGE_kyc_check_cancel (kch);
+ return;
+ }
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ ks.ec = TALER_JSON_get_error_code (j);
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ ks.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ ks.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ {
+ uint32_t status;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint32 ("aml_status",
+ &status),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ks.http_status = 0;
+ ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ ks.details.unavailable_for_legal_reasons.aml_status
+ = (enum TALER_AmlDecisionState) status;
+ kch->cb (kch->cb_cls,
+ &ks);
+ GNUNET_JSON_parse_free (spec);
+ TALER_EXCHANGE_kyc_check_cancel (kch);
+ return;
+ }
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ ks.ec = TALER_JSON_get_error_code (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ ks.ec = TALER_JSON_get_error_code (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange kyc_check\n",
+ (unsigned int) response_code,
+ (int) ks.ec);
+ break;
+ }
+ kch->cb (kch->cb_cls,
+ &ks);
+ TALER_EXCHANGE_kyc_check_cancel (kch);
+}
+
+
+struct TALER_EXCHANGE_KycCheckHandle *
+TALER_EXCHANGE_kyc_check (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ uint64_t requirement_row,
+ const struct TALER_PaytoHashP *h_payto,
+ enum TALER_KYCLOGIC_KycUserType ut,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_EXCHANGE_KycStatusCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_KycCheckHandle *kch;
+ CURL *eh;
+ char *arg_str;
+
+ {
+ char payto_str[sizeof (*h_payto) * 2];
+ char *end;
+ unsigned long long timeout_ms;
+
+ end = GNUNET_STRINGS_data_to_string (
+ h_payto,
+ sizeof (*h_payto),
+ payto_str,
+ sizeof (payto_str) - 1);
+ *end = '\0';
+ timeout_ms = timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
+ GNUNET_asprintf (&arg_str,
+ "kyc-check/%llu/%s/%s?timeout_ms=%llu",
+ (unsigned long long) requirement_row,
+ payto_str,
+ TALER_KYCLOGIC_kyc_user_type2s (ut),
+ timeout_ms);
+ }
+ kch = GNUNET_new (struct TALER_EXCHANGE_KycCheckHandle);
+ kch->h_payto = *h_payto;
+ kch->cb = cb;
+ kch->cb_cls = cb_cls;
+ kch->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ GNUNET_free (arg_str);
+ if (NULL == kch->url)
+ {
+ GNUNET_free (kch);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (kch->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (kch->url);
+ GNUNET_free (kch);
+ return NULL;
+ }
+ kch->keys = TALER_EXCHANGE_keys_incref (keys);
+ kch->job = GNUNET_CURL_job_add_with_ct_json (ctx,
+ eh,
+ &handle_kyc_check_finished,
+ kch);
+ return kch;
+}
+
+
+void
+TALER_EXCHANGE_kyc_check_cancel (struct TALER_EXCHANGE_KycCheckHandle *kch)
+{
+ if (NULL != kch->job)
+ {
+ GNUNET_CURL_job_cancel (kch->job);
+ kch->job = NULL;
+ }
+ TALER_EXCHANGE_keys_decref (kch->keys);
+ GNUNET_free (kch->url);
+ GNUNET_free (kch);
+}
+
+
+/* end of exchange_api_kyc_check.c */
diff --git a/src/lib/exchange_api_kyc_proof.c b/src/lib/exchange_api_kyc_proof.c
new file mode 100644
index 000000000..e7cc9c4cf
--- /dev/null
+++ b/src/lib/exchange_api_kyc_proof.c
@@ -0,0 +1,217 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021, 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_kyc_proof.c
+ * @brief Implementation of the /kyc-proof request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h> /* just for HTTP proof codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A ``/kyc-proof`` handle
+ */
+struct TALER_EXCHANGE_KycProofHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle to our CURL request.
+ */
+ CURL *eh;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_KycProofCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /kyc-proof request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_KycProofHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param body response body
+ * @param body_size number of bytes in @a body
+ */
+static void
+handle_kyc_proof_finished (void *cls,
+ long response_code,
+ const void *body,
+ size_t body_size)
+{
+ struct TALER_EXCHANGE_KycProofHandle *kph = cls;
+ struct TALER_EXCHANGE_KycProofResponse kpr = {
+ .http_status = (unsigned int) response_code
+ };
+
+ (void) body;
+ (void) body_size;
+ kph->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_SEE_OTHER:
+ {
+ char *redirect_url;
+
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_getinfo (kph->eh,
+ CURLINFO_REDIRECT_URL,
+ &redirect_url));
+ kpr.details.found.redirect_url = redirect_url;
+ 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 */
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ break;
+ case MHD_HTTP_BAD_GATEWAY:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ case MHD_HTTP_GATEWAY_TIMEOUT:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u for exchange kyc_proof\n",
+ (unsigned int) response_code);
+ break;
+ }
+ kph->cb (kph->cb_cls,
+ &kpr);
+ TALER_EXCHANGE_kyc_proof_cancel (kph);
+}
+
+
+struct TALER_EXCHANGE_KycProofHandle *
+TALER_EXCHANGE_kyc_proof (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *logic,
+ const char *args,
+ TALER_EXCHANGE_KycProofCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_KycProofHandle *kph;
+ char *arg_str;
+
+ if (NULL == args)
+ args = "";
+ else
+ GNUNET_assert (args[0] == '&');
+ {
+ char hstr[sizeof (struct TALER_PaytoHashP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (h_payto,
+ sizeof (*h_payto),
+ hstr,
+ sizeof (hstr));
+ *end = '\0';
+ GNUNET_asprintf (&arg_str,
+ "kyc-proof/%s?state=%s%s",
+ logic,
+ hstr,
+ args);
+ }
+ kph = GNUNET_new (struct TALER_EXCHANGE_KycProofHandle);
+ kph->cb = cb;
+ kph->cb_cls = cb_cls;
+ kph->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ GNUNET_free (arg_str);
+ if (NULL == kph->url)
+ {
+ GNUNET_free (kph);
+ return NULL;
+ }
+ kph->eh = TALER_EXCHANGE_curl_easy_get_ (kph->url);
+ if (NULL == kph->eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (kph->url);
+ GNUNET_free (kph);
+ return NULL;
+ }
+ /* disable location following, we want to learn the
+ result of a 303 redirect! */
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (kph->eh,
+ CURLOPT_FOLLOWLOCATION,
+ 0L));
+ kph->job = GNUNET_CURL_job_add_raw (ctx,
+ kph->eh,
+ NULL,
+ &handle_kyc_proof_finished,
+ kph);
+ return kph;
+}
+
+
+void
+TALER_EXCHANGE_kyc_proof_cancel (struct TALER_EXCHANGE_KycProofHandle *kph)
+{
+ if (NULL != kph->job)
+ {
+ GNUNET_CURL_job_cancel (kph->job);
+ kph->job = NULL;
+ }
+ GNUNET_free (kph->url);
+ GNUNET_free (kph);
+}
+
+
+/* end of exchange_api_kyc_proof.c */
diff --git a/src/lib/exchange_api_kyc_wallet.c b/src/lib/exchange_api_kyc_wallet.c
new file mode 100644
index 000000000..7197694ae
--- /dev/null
+++ b/src/lib/exchange_api_kyc_wallet.c
@@ -0,0 +1,230 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_kyc_wallet.c
+ * @brief Implementation of the /kyc-wallet request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h> /* just for HTTP wallet codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A ``/kyc-wallet`` handle
+ */
+struct TALER_EXCHANGE_KycWalletHandle
+{
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_KycWalletCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /kyc-wallet request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_KycWalletHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_kyc_wallet_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_KycWalletHandle *kwh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_WalletKycResponse ks = {
+ .http_status = (unsigned int) response_code
+ };
+
+ kwh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ ks.ec = TALER_JSON_get_error_code (j);
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ ks.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ ks.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "h_payto",
+ &ks.details.unavailable_for_legal_reasons.h_payto),
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &ks.details.unavailable_for_legal_reasons.requirement_row),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ks.http_status = 0;
+ ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ break;
+ }
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ ks.ec = TALER_JSON_get_error_code (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ ks.ec = TALER_JSON_get_error_code (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange /kyc-wallet\n",
+ (unsigned int) response_code,
+ (int) ks.ec);
+ break;
+ }
+ kwh->cb (kwh->cb_cls,
+ &ks);
+ TALER_EXCHANGE_kyc_wallet_cancel (kwh);
+}
+
+
+struct TALER_EXCHANGE_KycWalletHandle *
+TALER_EXCHANGE_kyc_wallet (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_Amount *balance,
+ TALER_EXCHANGE_KycWalletCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_KycWalletHandle *kwh;
+ CURL *eh;
+ json_t *req;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &reserve_pub.eddsa_pub);
+ TALER_wallet_account_setup_sign (reserve_priv,
+ balance,
+ &reserve_sig);
+ req = GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("balance",
+ balance),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &reserve_pub),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &reserve_sig));
+ GNUNET_assert (NULL != req);
+ kwh = GNUNET_new (struct TALER_EXCHANGE_KycWalletHandle);
+ kwh->cb = cb;
+ kwh->cb_cls = cb_cls;
+ kwh->url = TALER_url_join (url,
+ "kyc-wallet",
+ NULL);
+ if (NULL == kwh->url)
+ {
+ json_decref (req);
+ GNUNET_free (kwh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (kwh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&kwh->ctx,
+ eh,
+ req)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (req);
+ GNUNET_free (kwh->url);
+ GNUNET_free (kwh);
+ return NULL;
+ }
+ json_decref (req);
+ kwh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ kwh->ctx.headers,
+ &handle_kyc_wallet_finished,
+ kwh);
+ return kwh;
+}
+
+
+void
+TALER_EXCHANGE_kyc_wallet_cancel (struct TALER_EXCHANGE_KycWalletHandle *kwh)
+{
+ if (NULL != kwh->job)
+ {
+ GNUNET_CURL_job_cancel (kwh->job);
+ kwh->job = NULL;
+ }
+ GNUNET_free (kwh->url);
+ TALER_curl_easy_post_finished (&kwh->ctx);
+ GNUNET_free (kwh);
+}
+
+
+/* end of exchange_api_kyc_wallet.c */
diff --git a/src/lib/exchange_api_lookup_aml_decision.c b/src/lib/exchange_api_lookup_aml_decision.c
new file mode 100644
index 000000000..01e98213b
--- /dev/null
+++ b/src/lib/exchange_api_lookup_aml_decision.c
@@ -0,0 +1,419 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_lookup_aml_decision.c
+ * @brief Implementation of the /aml/$OFFICER_PUB/decision request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /coins/$COIN_PUB/link Handle
+ */
+struct TALER_EXCHANGE_LookupAmlDecision
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_LookupAmlDecisionCallback decision_cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *decision_cb_cls;
+
+ /**
+ * HTTP headers for the job.
+ */
+ struct curl_slist *job_headers;
+};
+
+
+/**
+ * Parse AML decision history.
+ *
+ * @param aml_history JSON array with AML history
+ * @param[out] aml_history_ar where to write the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_aml_history (const json_t *aml_history,
+ struct TALER_EXCHANGE_AmlDecisionDetail *aml_history_ar)
+{
+ json_t *obj;
+ size_t idx;
+
+ json_array_foreach (aml_history, idx, obj)
+ {
+ struct TALER_EXCHANGE_AmlDecisionDetail *aml = &aml_history_ar[idx];
+ uint32_t state32;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("decision_time",
+ &aml->decision_time),
+ GNUNET_JSON_spec_string ("justification",
+ &aml->justification),
+ TALER_JSON_spec_amount_any ("new_threshold",
+ &aml->new_threshold),
+ GNUNET_JSON_spec_uint32 ("new_state",
+ &state32),
+ GNUNET_JSON_spec_fixed_auto ("decider_pub",
+ &aml->decider_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (obj,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ aml->new_state = (enum TALER_AmlDecisionState) state32;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse KYC response array.
+ *
+ * @param kyc_attributes JSON array with KYC details
+ * @param[out] kyc_attributes_ar where to write the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_kyc_attributes (const json_t *kyc_attributes,
+ struct TALER_EXCHANGE_KycHistoryDetail *kyc_attributes_ar)
+{
+ json_t *obj;
+ size_t idx;
+
+ json_array_foreach (kyc_attributes, idx, obj)
+ {
+ struct TALER_EXCHANGE_KycHistoryDetail *kyc = &kyc_attributes_ar[idx];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("collection_time",
+ &kyc->collection_time),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("attributes",
+ &kyc->attributes),
+ NULL),
+ GNUNET_JSON_spec_string ("provider_section",
+ &kyc->provider_section),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (obj,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse the provided decision data from the "200 OK" response.
+ *
+ * @param[in,out] lh handle (callback may be zero'ed out)
+ * @param json json reply with the data for one coin
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+static enum GNUNET_GenericReturnValue
+parse_decision_ok (struct TALER_EXCHANGE_LookupAmlDecision *lh,
+ const json_t *json)
+{
+ struct TALER_EXCHANGE_AmlDecisionResponse lr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ const json_t *aml_history;
+ const json_t *kyc_attributes;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("aml_history",
+ &aml_history),
+ GNUNET_JSON_spec_array_const ("kyc_attributes",
+ &kyc_attributes),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ lr.details.ok.aml_history_length = json_array_size (aml_history);
+ lr.details.ok.kyc_attributes_length = json_array_size (kyc_attributes);
+ {
+ struct TALER_EXCHANGE_AmlDecisionDetail aml_history_ar[
+ GNUNET_NZL (lr.details.ok.aml_history_length)];
+ struct TALER_EXCHANGE_KycHistoryDetail kyc_attributes_ar[
+ GNUNET_NZL (lr.details.ok.kyc_attributes_length)];
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+
+ memset (aml_history_ar,
+ 0,
+ sizeof (aml_history_ar));
+ memset (kyc_attributes_ar,
+ 0,
+ sizeof (kyc_attributes_ar));
+ lr.details.ok.aml_history = aml_history_ar;
+ lr.details.ok.kyc_attributes = kyc_attributes_ar;
+ ret = parse_aml_history (aml_history,
+ aml_history_ar);
+ if (GNUNET_OK == ret)
+ ret = parse_kyc_attributes (kyc_attributes,
+ kyc_attributes_ar);
+ if (GNUNET_OK == ret)
+ {
+ lh->decision_cb (lh->decision_cb_cls,
+ &lr);
+ lh->decision_cb = NULL;
+ }
+ return ret;
+ }
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /aml/$OFFICER_PUB/decision request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_LookupAmlDecision`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_lookup_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_LookupAmlDecision *lh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_AmlDecisionResponse lr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ lh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ parse_decision_ok (lh,
+ j))
+ {
+ GNUNET_break_op (0);
+ lr.hr.http_status = 0;
+ lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == lh->decision_cb);
+ TALER_EXCHANGE_lookup_aml_decision_cancel (lh);
+ return;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says this coin was not melted; we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says this coin was not melted; we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange lookup AML decision\n",
+ (unsigned int) response_code,
+ (int) lr.hr.ec);
+ break;
+ }
+ if (NULL != lh->decision_cb)
+ lh->decision_cb (lh->decision_cb_cls,
+ &lr);
+ TALER_EXCHANGE_lookup_aml_decision_cancel (lh);
+}
+
+
+struct TALER_EXCHANGE_LookupAmlDecision *
+TALER_EXCHANGE_lookup_aml_decision (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_url,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ bool history,
+ TALER_EXCHANGE_LookupAmlDecisionCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_LookupAmlDecision *lh;
+ CURL *eh;
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+ struct TALER_AmlOfficerSignatureP officer_sig;
+ char arg_str[sizeof (officer_pub) * 2
+ + sizeof (*h_payto) * 2 + 32];
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
+ &officer_pub.eddsa_pub);
+ TALER_officer_aml_query_sign (officer_priv,
+ &officer_sig);
+ {
+ char pub_str[sizeof (officer_pub) * 2];
+ char pt_str[sizeof (*h_payto) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &officer_pub,
+ sizeof (officer_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ end = GNUNET_STRINGS_data_to_string (
+ h_payto,
+ sizeof (*h_payto),
+ pt_str,
+ sizeof (pt_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "aml/%s/decision/%s",
+ pub_str,
+ pt_str);
+ }
+ lh = GNUNET_new (struct TALER_EXCHANGE_LookupAmlDecision);
+ lh->decision_cb = cb;
+ lh->decision_cb_cls = cb_cls;
+ lh->url = TALER_url_join (exchange_url,
+ arg_str,
+ "history",
+ history
+ ? "true"
+ : NULL,
+ NULL);
+ if (NULL == lh->url)
+ {
+ GNUNET_free (lh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (lh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (lh->url);
+ GNUNET_free (lh);
+ return NULL;
+ }
+ {
+ char *hdr;
+ char sig_str[sizeof (officer_sig) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &officer_sig,
+ sizeof (officer_sig),
+ sig_str,
+ sizeof (sig_str));
+ *end = '\0';
+
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ TALER_AML_OFFICER_SIGNATURE_HEADER,
+ sig_str);
+ lh->job_headers = curl_slist_append (NULL,
+ hdr);
+ GNUNET_free (hdr);
+ lh->job_headers = curl_slist_append (lh->job_headers,
+ "Content-type: application/json");
+ lh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ lh->job_headers,
+ &handle_lookup_finished,
+ lh);
+ }
+ return lh;
+}
+
+
+void
+TALER_EXCHANGE_lookup_aml_decision_cancel (
+ struct TALER_EXCHANGE_LookupAmlDecision *lh)
+{
+ if (NULL != lh->job)
+ {
+ GNUNET_CURL_job_cancel (lh->job);
+ lh->job = NULL;
+ }
+ curl_slist_free_all (lh->job_headers);
+ GNUNET_free (lh->url);
+ GNUNET_free (lh);
+}
+
+
+/* end of exchange_api_lookup_aml_decision.c */
diff --git a/src/lib/exchange_api_lookup_aml_decisions.c b/src/lib/exchange_api_lookup_aml_decisions.c
new file mode 100644
index 000000000..22222b1e4
--- /dev/null
+++ b/src/lib/exchange_api_lookup_aml_decisions.c
@@ -0,0 +1,378 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_lookup_aml_decisions.c
+ * @brief Implementation of the /aml/$OFFICER_PUB/decisions request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /coins/$COIN_PUB/link Handle
+ */
+struct TALER_EXCHANGE_LookupAmlDecisions
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_LookupAmlDecisionsCallback decisions_cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *decisions_cb_cls;
+
+ /**
+ * HTTP headers for the job.
+ */
+ struct curl_slist *job_headers;
+};
+
+
+/**
+ * Parse AML decision summary array.
+ *
+ * @param decisions JSON array with AML decision summaries
+ * @param[out] decision_ar where to write the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_aml_decisions (const json_t *decisions,
+ struct TALER_EXCHANGE_AmlDecisionSummary *decision_ar)
+{
+ json_t *obj;
+ size_t idx;
+
+ json_array_foreach (decisions, idx, obj)
+ {
+ struct TALER_EXCHANGE_AmlDecisionSummary *decision = &decision_ar[idx];
+ uint32_t state32;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_payto",
+ &decision->h_payto),
+ GNUNET_JSON_spec_uint32 ("current_state",
+ &state32),
+ TALER_JSON_spec_amount_any ("threshold",
+ &decision->threshold),
+ GNUNET_JSON_spec_uint64 ("rowid",
+ &decision->rowid),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (obj,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ decision->current_state = (enum TALER_AmlDecisionState) state32;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse the provided decision data from the "200 OK" response.
+ *
+ * @param[in,out] lh handle (callback may be zero'ed out)
+ * @param json json reply with the data for one coin
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+static enum GNUNET_GenericReturnValue
+parse_decisions_ok (struct TALER_EXCHANGE_LookupAmlDecisions *lh,
+ const json_t *json)
+{
+ struct TALER_EXCHANGE_AmlDecisionsResponse lr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ const json_t *records;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("records",
+ &records),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ lr.details.ok.decisions_length = json_array_size (records);
+ {
+ struct TALER_EXCHANGE_AmlDecisionSummary decisions[
+ GNUNET_NZL (lr.details.ok.decisions_length)];
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+
+ lr.details.ok.decisions = decisions;
+ ret = parse_aml_decisions (records,
+ decisions);
+ if (GNUNET_OK == ret)
+ {
+ lh->decisions_cb (lh->decisions_cb_cls,
+ &lr);
+ lh->decisions_cb = NULL;
+ }
+ return ret;
+ }
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /aml/$OFFICER_PUB/decisions request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_LookupAmlDecisions`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_lookup_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_LookupAmlDecisions *lh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_AmlDecisionsResponse lr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ lh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ parse_decisions_ok (lh,
+ j))
+ {
+ GNUNET_break_op (0);
+ lr.hr.http_status = 0;
+ lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == lh->decisions_cb);
+ TALER_EXCHANGE_lookup_aml_decisions_cancel (lh);
+ return;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says this coin was not melted; we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says this coin was not melted; we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for lookup AML decisions\n",
+ (unsigned int) response_code,
+ (int) lr.hr.ec);
+ break;
+ }
+ if (NULL != lh->decisions_cb)
+ lh->decisions_cb (lh->decisions_cb_cls,
+ &lr);
+ TALER_EXCHANGE_lookup_aml_decisions_cancel (lh);
+}
+
+
+struct TALER_EXCHANGE_LookupAmlDecisions *
+TALER_EXCHANGE_lookup_aml_decisions (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_url,
+ uint64_t start,
+ int delta,
+ enum TALER_AmlDecisionState state,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ TALER_EXCHANGE_LookupAmlDecisionsCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_LookupAmlDecisions *lh;
+ CURL *eh;
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+ struct TALER_AmlOfficerSignatureP officer_sig;
+ char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 + 32];
+ const char *state_str = NULL;
+
+ switch (state)
+ {
+ case TALER_AML_NORMAL:
+ state_str = "normal";
+ break;
+ case TALER_AML_PENDING:
+ state_str = "pending";
+ break;
+ case TALER_AML_FROZEN:
+ state_str = "frozen";
+ break;
+ }
+ GNUNET_assert (NULL != state_str);
+ GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
+ &officer_pub.eddsa_pub);
+ TALER_officer_aml_query_sign (officer_priv,
+ &officer_sig);
+ {
+ char pub_str[sizeof (officer_pub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &officer_pub,
+ sizeof (officer_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "aml/%s/decisions/%s",
+ pub_str,
+ state_str);
+ }
+ lh = GNUNET_new (struct TALER_EXCHANGE_LookupAmlDecisions);
+ lh->decisions_cb = cb;
+ lh->decisions_cb_cls = cb_cls;
+ {
+ char delta_s[24];
+ char start_s[24];
+
+ GNUNET_snprintf (delta_s,
+ sizeof (delta_s),
+ "%d",
+ delta);
+ GNUNET_snprintf (start_s,
+ sizeof (start_s),
+ "%llu",
+ (unsigned long long) start);
+ lh->url = TALER_url_join (exchange_url,
+ arg_str,
+ "delta",
+ delta_s,
+ "start",
+ start_s,
+ NULL);
+ }
+ if (NULL == lh->url)
+ {
+ GNUNET_free (lh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (lh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (lh->url);
+ GNUNET_free (lh);
+ return NULL;
+ }
+ {
+ char *hdr;
+ char sig_str[sizeof (officer_sig) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &officer_sig,
+ sizeof (officer_sig),
+ sig_str,
+ sizeof (sig_str));
+ *end = '\0';
+
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ TALER_AML_OFFICER_SIGNATURE_HEADER,
+ sig_str);
+ lh->job_headers = curl_slist_append (NULL,
+ hdr);
+ GNUNET_free (hdr);
+ lh->job_headers = curl_slist_append (lh->job_headers,
+ "Content-type: application/json");
+ lh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ lh->job_headers,
+ &handle_lookup_finished,
+ lh);
+ }
+ return lh;
+}
+
+
+void
+TALER_EXCHANGE_lookup_aml_decisions_cancel (
+ struct TALER_EXCHANGE_LookupAmlDecisions *lh)
+{
+ if (NULL != lh->job)
+ {
+ GNUNET_CURL_job_cancel (lh->job);
+ lh->job = NULL;
+ }
+ curl_slist_free_all (lh->job_headers);
+ GNUNET_free (lh->url);
+ GNUNET_free (lh);
+}
+
+
+/* end of exchange_api_lookup_aml_decisions.c */
diff --git a/src/lib/exchange_api_management_add_partner.c b/src/lib/exchange_api_management_add_partner.c
new file mode 100644
index 000000000..fec66c567
--- /dev/null
+++ b/src/lib/exchange_api_management_add_partner.c
@@ -0,0 +1,218 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_add_partner.c
+ * @brief functions to add an partner by an AML officer
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementAddPartner
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementAddPartnerCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /management/partners request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAddPartner *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_add_partner_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementAddPartner *wh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementAddPartnerResponse apr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ wh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* no reply */
+ apr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ apr.hr.hint = "server offline?";
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ apr.hr.ec = TALER_JSON_get_error_code (json);
+ apr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_CONFLICT:
+ apr.hr.ec = TALER_JSON_get_error_code (json);
+ apr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ apr.hr.ec = TALER_JSON_get_error_code (json);
+ apr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for adding exchange partner\n",
+ (unsigned int) response_code,
+ (int) apr.hr.ec);
+ break;
+ }
+ if (NULL != wh->cb)
+ {
+ wh->cb (wh->cb_cls,
+ &apr);
+ wh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_add_partner_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_ManagementAddPartner *
+TALER_EXCHANGE_management_add_partner (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_MasterPublicKeyP *partner_pub,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ struct GNUNET_TIME_Relative wad_frequency,
+ const struct TALER_Amount *wad_fee,
+ const char *partner_base_url,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementAddPartnerCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementAddPartner *wh;
+ CURL *eh;
+ json_t *body;
+
+ wh = GNUNET_new (struct TALER_EXCHANGE_ManagementAddPartner);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ctx = ctx;
+ wh->url = TALER_url_join (url,
+ "management/partners",
+ NULL);
+ if (NULL == wh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (wh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("partner_base_url",
+ partner_base_url),
+ GNUNET_JSON_pack_timestamp ("start_date",
+ start_date),
+ GNUNET_JSON_pack_timestamp ("end_date",
+ end_date),
+ GNUNET_JSON_pack_time_rel ("wad_frequency",
+ wad_frequency),
+ GNUNET_JSON_pack_data_auto ("partner_pub",
+ &partner_pub),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &master_sig),
+ TALER_JSON_pack_amount ("wad_fee",
+ wad_fee)
+ );
+ eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&wh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (wh->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ wh->url);
+ wh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ wh->post_ctx.headers,
+ &handle_add_partner_finished,
+ wh);
+ if (NULL == wh->job)
+ {
+ TALER_EXCHANGE_management_add_partner_cancel (wh);
+ return NULL;
+ }
+ return wh;
+}
+
+
+void
+TALER_EXCHANGE_management_add_partner_cancel (
+ struct TALER_EXCHANGE_ManagementAddPartner *wh)
+{
+ if (NULL != wh->job)
+ {
+ GNUNET_CURL_job_cancel (wh->job);
+ wh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&wh->post_ctx);
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_management_auditor_disable.c b/src/lib/exchange_api_management_auditor_disable.c
new file mode 100644
index 000000000..8bce7f74f
--- /dev/null
+++ b/src/lib/exchange_api_management_auditor_disable.c
@@ -0,0 +1,219 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2021 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_auditor_disable.c
+ * @brief functions to disable an auditor
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+/**
+ * @brief Handle for a POST /management/auditors/$AUDITOR_PUB/disable request.
+ */
+struct TALER_EXCHANGE_ManagementAuditorDisableHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementAuditorDisableCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/auditors/%s/disable request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAuditorDisableHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_auditor_disable_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementAuditorDisableHandle *ah = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementAuditorDisableResponse adr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ ah->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_CONFLICT:
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management auditor disable\n",
+ (unsigned int) response_code,
+ (int) adr.hr.ec);
+ break;
+ }
+ if (NULL != ah->cb)
+ {
+ ah->cb (ah->cb_cls,
+ &adr);
+ ah->cb = NULL;
+ }
+ TALER_EXCHANGE_management_disable_auditor_cancel (ah);
+}
+
+
+struct TALER_EXCHANGE_ManagementAuditorDisableHandle *
+TALER_EXCHANGE_management_disable_auditor (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct GNUNET_TIME_Timestamp validity_end,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementAuditorDisableCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementAuditorDisableHandle *ah;
+ CURL *eh;
+ json_t *body;
+
+ ah = GNUNET_new (struct TALER_EXCHANGE_ManagementAuditorDisableHandle);
+ ah->cb = cb;
+ ah->cb_cls = cb_cls;
+ ah->ctx = ctx;
+ {
+ char epub_str[sizeof (*auditor_pub) * 2];
+ char arg_str[sizeof (epub_str) + 64];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (auditor_pub,
+ sizeof (*auditor_pub),
+ epub_str,
+ sizeof (epub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "management/auditors/%s/disable",
+ epub_str);
+ ah->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ }
+ if (NULL == ah->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (ah);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig),
+ GNUNET_JSON_pack_timestamp ("validity_end",
+ validity_end));
+ eh = TALER_EXCHANGE_curl_easy_get_ (ah->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&ah->post_ctx,
+ eh,
+ body)))
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (ah->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ ah->url);
+ ah->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ ah->post_ctx.headers,
+ &handle_auditor_disable_finished,
+ ah);
+ if (NULL == ah->job)
+ {
+ TALER_EXCHANGE_management_disable_auditor_cancel (ah);
+ return NULL;
+ }
+ return ah;
+}
+
+
+void
+TALER_EXCHANGE_management_disable_auditor_cancel (
+ struct TALER_EXCHANGE_ManagementAuditorDisableHandle *ah)
+{
+ if (NULL != ah->job)
+ {
+ GNUNET_CURL_job_cancel (ah->job);
+ ah->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&ah->post_ctx);
+ GNUNET_free (ah->url);
+ GNUNET_free (ah);
+}
diff --git a/src/lib/exchange_api_management_auditor_enable.c b/src/lib/exchange_api_management_auditor_enable.c
new file mode 100644
index 000000000..41c5049c2
--- /dev/null
+++ b/src/lib/exchange_api_management_auditor_enable.c
@@ -0,0 +1,224 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2021 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_auditor_enable.c
+ * @brief functions to enable an auditor
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+/**
+ * @brief Handle for a POST /management/auditors request.
+ */
+struct TALER_EXCHANGE_ManagementAuditorEnableHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementAuditorEnableCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /management/auditors request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_auditor_enable_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementAuditorEnableHandle *ah = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementAuditorEnableResponse aer = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ ah->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ aer.hr.ec = TALER_JSON_get_error_code (json);
+ aer.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n",
+ ah->url);
+ if (NULL != json)
+ {
+ aer.hr.ec = TALER_JSON_get_error_code (json);
+ aer.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ aer.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ aer.hr.hint = TALER_ErrorCode_get_hint (aer.hr.ec);
+ }
+ break;
+ case MHD_HTTP_CONFLICT:
+ aer.hr.ec = TALER_JSON_get_error_code (json);
+ aer.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ aer.hr.ec = TALER_JSON_get_error_code (json);
+ aer.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management auditor enable\n",
+ (unsigned int) response_code,
+ (int) aer.hr.ec);
+ break;
+ }
+ if (NULL != ah->cb)
+ {
+ ah->cb (ah->cb_cls,
+ &aer);
+ ah->cb = NULL;
+ }
+ TALER_EXCHANGE_management_enable_auditor_cancel (ah);
+}
+
+
+struct TALER_EXCHANGE_ManagementAuditorEnableHandle *
+TALER_EXCHANGE_management_enable_auditor (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ const char *auditor_name,
+ struct GNUNET_TIME_Timestamp validity_start,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementAuditorEnableCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementAuditorEnableHandle *ah;
+ CURL *eh;
+ json_t *body;
+
+ ah = GNUNET_new (struct TALER_EXCHANGE_ManagementAuditorEnableHandle);
+ ah->cb = cb;
+ ah->cb_cls = cb_cls;
+ ah->ctx = ctx;
+ ah->url = TALER_url_join (url,
+ "management/auditors",
+ NULL);
+ if (NULL == ah->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (ah);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("auditor_url",
+ auditor_url),
+ GNUNET_JSON_pack_string ("auditor_name",
+ auditor_name),
+ GNUNET_JSON_pack_data_auto ("auditor_pub",
+ auditor_pub),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig),
+ GNUNET_JSON_pack_timestamp ("validity_start",
+ validity_start));
+ eh = TALER_EXCHANGE_curl_easy_get_ (ah->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&ah->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ json_decref (body);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ GNUNET_free (ah->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ ah->url);
+ ah->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ ah->post_ctx.headers,
+ &handle_auditor_enable_finished,
+ ah);
+ if (NULL == ah->job)
+ {
+ TALER_EXCHANGE_management_enable_auditor_cancel (ah);
+ return NULL;
+ }
+ return ah;
+}
+
+
+void
+TALER_EXCHANGE_management_enable_auditor_cancel (
+ struct TALER_EXCHANGE_ManagementAuditorEnableHandle *ah)
+{
+ if (NULL != ah->job)
+ {
+ GNUNET_CURL_job_cancel (ah->job);
+ ah->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&ah->post_ctx);
+ GNUNET_free (ah->url);
+ GNUNET_free (ah);
+}
diff --git a/src/lib/exchange_api_management_drain_profits.c b/src/lib/exchange_api_management_drain_profits.c
new file mode 100644
index 000000000..bc7232b87
--- /dev/null
+++ b/src/lib/exchange_api_management_drain_profits.c
@@ -0,0 +1,213 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_drain_profits.c
+ * @brief functions to set wire fees at an exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_curl_defaults.h"
+#include "taler_exchange_service.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementDrainProfitsHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementDrainProfitsCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/drain request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementDrainProfitsHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_drain_profits_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementDrainResponse dr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ dp->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ dr.hr.ec = TALER_JSON_get_error_code (json);
+ dr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_CONFLICT:
+ dr.hr.ec = TALER_JSON_get_error_code (json);
+ dr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_PRECONDITION_FAILED:
+ dr.hr.ec = TALER_JSON_get_error_code (json);
+ dr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ dr.hr.ec = TALER_JSON_get_error_code (json);
+ dr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management drain profits\n",
+ (unsigned int) response_code,
+ (int) dr.hr.ec);
+ break;
+ }
+ if (NULL != dp->cb)
+ {
+ dp->cb (dp->cb_cls,
+ &dr);
+ dp->cb = NULL;
+ }
+ TALER_EXCHANGE_management_drain_profits_cancel (dp);
+}
+
+
+struct TALER_EXCHANGE_ManagementDrainProfitsHandle *
+TALER_EXCHANGE_management_drain_profits (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *amount,
+ struct GNUNET_TIME_Timestamp date,
+ const char *account_section,
+ const char *payto_uri,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementDrainProfitsCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp;
+ CURL *eh;
+ json_t *body;
+
+ dp = GNUNET_new (struct TALER_EXCHANGE_ManagementDrainProfitsHandle);
+ dp->cb = cb;
+ dp->cb_cls = cb_cls;
+ dp->ctx = ctx;
+ dp->url = TALER_url_join (url,
+ "management/drain",
+ NULL);
+ if (NULL == dp->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (dp);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("debit_account_section",
+ account_section),
+ GNUNET_JSON_pack_string ("credit_payto_uri",
+ payto_uri),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ wtid),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig),
+ GNUNET_JSON_pack_timestamp ("date",
+ date),
+ TALER_JSON_pack_amount ("amount",
+ amount));
+ eh = TALER_EXCHANGE_curl_easy_get_ (dp->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&dp->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (dp->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ dp->url);
+ dp->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ dp->post_ctx.headers,
+ &handle_drain_profits_finished,
+ dp);
+ if (NULL == dp->job)
+ {
+ TALER_EXCHANGE_management_drain_profits_cancel (dp);
+ return NULL;
+ }
+ return dp;
+}
+
+
+void
+TALER_EXCHANGE_management_drain_profits_cancel (
+ struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp)
+{
+ if (NULL != dp->job)
+ {
+ GNUNET_CURL_job_cancel (dp->job);
+ dp->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&dp->post_ctx);
+ GNUNET_free (dp->url);
+ GNUNET_free (dp);
+}
diff --git a/src/lib/exchange_api_management_post_extensions.c b/src/lib/exchange_api_management_post_extensions.c
new file mode 100644
index 000000000..00d1c5e3f
--- /dev/null
+++ b/src/lib/exchange_api_management_post_extensions.c
@@ -0,0 +1,213 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file lib/exchange_api_management_post_extensions.c
+ * @brief functions to handle the settings for extensions (p2p and age restriction)
+ * @author Ă–zgĂĽr Kesim
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_extensions.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_exchange_service.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+/**
+ * @brief Handle for a POST /management/extensions request.
+ */
+struct TALER_EXCHANGE_ManagementPostExtensionsHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementPostExtensionsCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /management/extensions request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementPostExtensionsHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_post_extensions_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementPostExtensionsResponse per = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ ph->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ per.hr.ec = TALER_JSON_get_error_code (json);
+ per.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n",
+ ph->url);
+ if (NULL != json)
+ {
+ per.hr.ec = TALER_JSON_get_error_code (json);
+ per.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ per.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ per.hr.hint = TALER_ErrorCode_get_hint (per.hr.ec);
+ }
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ per.hr.ec = TALER_JSON_get_error_code (json);
+ per.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management post extensions\n",
+ (unsigned int) response_code,
+ (int) per.hr.ec);
+ break;
+ }
+ if (NULL != ph->cb)
+ {
+ ph->cb (ph->cb_cls,
+ &per);
+ ph->cb = NULL;
+ }
+ TALER_EXCHANGE_management_post_extensions_cancel (ph);
+}
+
+
+struct TALER_EXCHANGE_ManagementPostExtensionsHandle *
+TALER_EXCHANGE_management_post_extensions (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_EXCHANGE_ManagementPostExtensionsData *ped,
+ TALER_EXCHANGE_ManagementPostExtensionsCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph;
+ CURL *eh = NULL;
+ json_t *body = NULL;
+
+ ph = GNUNET_new (struct TALER_EXCHANGE_ManagementPostExtensionsHandle);
+ ph->cb = cb;
+ ph->cb_cls = cb_cls;
+ ph->ctx = ctx;
+ ph->url = TALER_url_join (url,
+ "management/extensions",
+ NULL);
+ if (NULL == ph->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (ph);
+ return NULL;
+ }
+
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_steal ("extensions",
+ (json_t *) ped->extensions),
+ GNUNET_JSON_pack_data_auto ("extensions_sig",
+ &ped->extensions_sig));
+
+ eh = TALER_EXCHANGE_curl_easy_get_ (ph->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&ph->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (ph->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Requesting URL '%s'\n",
+ ph->url);
+ ph->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ ph->post_ctx.headers,
+ &handle_post_extensions_finished,
+ ph);
+ if (NULL == ph->job)
+ {
+ TALER_EXCHANGE_management_post_extensions_cancel (ph);
+ return NULL;
+ }
+ return ph;
+}
+
+
+void
+TALER_EXCHANGE_management_post_extensions_cancel (
+ struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph)
+{
+ if (NULL != ph->job)
+ {
+ GNUNET_CURL_job_cancel (ph->job);
+ ph->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&ph->post_ctx);
+ GNUNET_free (ph->url);
+ GNUNET_free (ph);
+}
diff --git a/src/lib/exchange_api_management_post_keys.c b/src/lib/exchange_api_management_post_keys.c
new file mode 100644
index 000000000..a46124d90
--- /dev/null
+++ b/src/lib/exchange_api_management_post_keys.c
@@ -0,0 +1,237 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2021 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_post_keys.c
+ * @brief functions to affirm the validity of exchange keys using the master private key
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+/**
+ * @brief Handle for a POST /management/keys request.
+ */
+struct TALER_EXCHANGE_ManagementPostKeysHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementPostKeysCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /management/keys request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementPostKeysHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_post_keys_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementPostKeysHandle *ph = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementPostKeysResponse pkr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ ph->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ pkr.hr.ec = TALER_JSON_get_error_code (json);
+ pkr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ pkr.hr.ec = TALER_JSON_get_error_code (json);
+ pkr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_REQUEST_ENTITY_TOO_LARGE:
+ pkr.hr.ec = TALER_JSON_get_error_code (json);
+ pkr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ pkr.hr.ec = TALER_JSON_get_error_code (json);
+ pkr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management post keys\n",
+ (unsigned int) response_code,
+ (int) pkr.hr.ec);
+ break;
+ }
+ if (NULL != ph->cb)
+ {
+ ph->cb (ph->cb_cls,
+ &pkr);
+ ph->cb = NULL;
+ }
+ TALER_EXCHANGE_post_management_keys_cancel (ph);
+}
+
+
+struct TALER_EXCHANGE_ManagementPostKeysHandle *
+TALER_EXCHANGE_post_management_keys (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_EXCHANGE_ManagementPostKeysData *pkd,
+ TALER_EXCHANGE_ManagementPostKeysCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementPostKeysHandle *ph;
+ CURL *eh;
+ json_t *body;
+ json_t *denom_sigs;
+ json_t *signkey_sigs;
+
+ ph = GNUNET_new (struct TALER_EXCHANGE_ManagementPostKeysHandle);
+ ph->cb = cb;
+ ph->cb_cls = cb_cls;
+ ph->ctx = ctx;
+ ph->url = TALER_url_join (url,
+ "management/keys",
+ NULL);
+ if (NULL == ph->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (ph);
+ return NULL;
+ }
+ denom_sigs = json_array ();
+ GNUNET_assert (NULL != denom_sigs);
+ for (unsigned int i = 0; i<pkd->num_denom_sigs; i++)
+ {
+ const struct TALER_EXCHANGE_DenominationKeySignature *dks
+ = &pkd->denom_sigs[i];
+
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ denom_sigs,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &dks->h_denom_pub),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &dks->master_sig))));
+ }
+ signkey_sigs = json_array ();
+ GNUNET_assert (NULL != signkey_sigs);
+ for (unsigned int i = 0; i<pkd->num_sign_sigs; i++)
+ {
+ const struct TALER_EXCHANGE_SigningKeySignature *sks
+ = &pkd->sign_sigs[i];
+
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ signkey_sigs,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &sks->exchange_pub),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &sks->master_sig))));
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("denom_sigs",
+ denom_sigs),
+ GNUNET_JSON_pack_array_steal ("signkey_sigs",
+ signkey_sigs));
+ eh = TALER_EXCHANGE_curl_easy_get_ (ph->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&ph->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (ph->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ ph->url);
+ ph->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ ph->post_ctx.headers,
+ &handle_post_keys_finished,
+ ph);
+ if (NULL == ph->job)
+ {
+ TALER_EXCHANGE_post_management_keys_cancel (ph);
+ return NULL;
+ }
+ return ph;
+}
+
+
+void
+TALER_EXCHANGE_post_management_keys_cancel (
+ struct TALER_EXCHANGE_ManagementPostKeysHandle *ph)
+{
+ if (NULL != ph->job)
+ {
+ GNUNET_CURL_job_cancel (ph->job);
+ ph->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&ph->post_ctx);
+ GNUNET_free (ph->url);
+ GNUNET_free (ph);
+}
diff --git a/src/lib/exchange_api_management_revoke_denomination_key.c b/src/lib/exchange_api_management_revoke_denomination_key.c
new file mode 100644
index 000000000..aa4d527a7
--- /dev/null
+++ b/src/lib/exchange_api_management_revoke_denomination_key.c
@@ -0,0 +1,221 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_revoke_denomination_key.c
+ * @brief functions to revoke an exchange denomination key
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+/**
+ * @brief Handle for a POST /management/denominations/$H_DENOM_PUB/revoke request.
+ */
+struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementRevokeDenominationKeyCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/denominations/$H_DENOM_PUB/revoke request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_revoke_denomination_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementRevokeDenominationResponse rdr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ rh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* no reply */
+ rdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ rdr.hr.hint = "server offline?";
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ rdr.hr.ec = TALER_JSON_get_error_code (json);
+ rdr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rdr.hr.ec = TALER_JSON_get_error_code (json);
+ rdr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management revoke denomination\n",
+ (unsigned int) response_code,
+ (int) rdr.hr.ec);
+ break;
+ }
+ if (NULL != rh->cb)
+ {
+ rh->cb (rh->cb_cls,
+ &rdr);
+ rh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_revoke_denomination_key_cancel (rh);
+}
+
+
+struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *
+TALER_EXCHANGE_management_revoke_denomination_key (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementRevokeDenominationKeyCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh;
+ CURL *eh;
+ json_t *body;
+
+ rh = GNUNET_new (struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle);
+ rh->cb = cb;
+ rh->cb_cls = cb_cls;
+ rh->ctx = ctx;
+ {
+ char epub_str[sizeof (*h_denom_pub) * 2];
+ char arg_str[sizeof (epub_str) + 64];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (h_denom_pub,
+ sizeof (*h_denom_pub),
+ epub_str,
+ sizeof (epub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "management/denominations/%s/revoke",
+ epub_str);
+ rh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ }
+ if (NULL == rh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (rh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig));
+ if (NULL == body)
+ {
+ GNUNET_break (0);
+ GNUNET_free (rh->url);
+ GNUNET_free (rh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (rh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&rh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (rh->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ rh->url);
+ rh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ rh->post_ctx.headers,
+ &handle_revoke_denomination_finished,
+ rh);
+ if (NULL == rh->job)
+ {
+ TALER_EXCHANGE_management_revoke_denomination_key_cancel (rh);
+ return NULL;
+ }
+ return rh;
+}
+
+
+void
+TALER_EXCHANGE_management_revoke_denomination_key_cancel (
+ struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh)
+{
+ if (NULL != rh->job)
+ {
+ GNUNET_CURL_job_cancel (rh->job);
+ rh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&rh->post_ctx);
+ GNUNET_free (rh->url);
+ GNUNET_free (rh);
+}
diff --git a/src/lib/exchange_api_management_revoke_signing_key.c b/src/lib/exchange_api_management_revoke_signing_key.c
new file mode 100644
index 000000000..c4d634248
--- /dev/null
+++ b/src/lib/exchange_api_management_revoke_signing_key.c
@@ -0,0 +1,211 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2021 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_revoke_signing_key.c
+ * @brief functions to revoke an exchange online signing key
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementRevokeSigningKeyCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/signkeys/%s/revoke request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_revoke_signing_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse rsr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ rh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* no reply */
+ rsr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ rsr.hr.hint = "server offline?";
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ rsr.hr.ec = TALER_JSON_get_error_code (json);
+ rsr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rsr.hr.ec = TALER_JSON_get_error_code (json);
+ rsr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management revoke signkey\n",
+ (unsigned int) response_code,
+ (int) rsr.hr.ec);
+ break;
+ }
+ if (NULL != rh->cb)
+ {
+ rh->cb (rh->cb_cls,
+ &rsr);
+ rh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_revoke_signing_key_cancel (rh);
+}
+
+
+struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *
+TALER_EXCHANGE_management_revoke_signing_key (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementRevokeSigningKeyCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh;
+ CURL *eh;
+ json_t *body;
+
+ rh = GNUNET_new (struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle);
+ rh->cb = cb;
+ rh->cb_cls = cb_cls;
+ rh->ctx = ctx;
+ {
+ char epub_str[sizeof (*exchange_pub) * 2];
+ char arg_str[sizeof (epub_str) + 64];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (exchange_pub,
+ sizeof (*exchange_pub),
+ epub_str,
+ sizeof (epub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "management/signkeys/%s/revoke",
+ epub_str);
+ rh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ }
+ if (NULL == rh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (rh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig));
+ eh = TALER_EXCHANGE_curl_easy_get_ (rh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&rh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (rh->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ rh->url);
+ rh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ rh->post_ctx.headers,
+ &handle_revoke_signing_finished,
+ rh);
+ if (NULL == rh->job)
+ {
+ TALER_EXCHANGE_management_revoke_signing_key_cancel (rh);
+ return NULL;
+ }
+ return rh;
+}
+
+
+void
+TALER_EXCHANGE_management_revoke_signing_key_cancel (
+ struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh)
+{
+ if (NULL != rh->job)
+ {
+ GNUNET_CURL_job_cancel (rh->job);
+ rh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&rh->post_ctx);
+ GNUNET_free (rh->url);
+ GNUNET_free (rh);
+}
diff --git a/src/lib/exchange_api_management_set_global_fee.c b/src/lib/exchange_api_management_set_global_fee.c
new file mode 100644
index 000000000..54c37fd64
--- /dev/null
+++ b/src/lib/exchange_api_management_set_global_fee.c
@@ -0,0 +1,235 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_set_global_fee.c
+ * @brief functions to set global fees at an exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_curl_defaults.h"
+#include "taler_exchange_service.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementSetGlobalFeeCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/global request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_set_global_fee_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *sgfh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementSetGlobalFeeResponse sfr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ sgfh->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ sfr.hr.ec = TALER_JSON_get_error_code (json);
+ sfr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n",
+ sgfh->url);
+ if (NULL != json)
+ {
+ sfr.hr.ec = TALER_JSON_get_error_code (json);
+ sfr.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ sfr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ sfr.hr.hint = TALER_ErrorCode_get_hint (sfr.hr.ec);
+ }
+ break;
+ case MHD_HTTP_CONFLICT:
+ sfr.hr.ec = TALER_JSON_get_error_code (json);
+ sfr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_PRECONDITION_FAILED:
+ sfr.hr.ec = TALER_JSON_get_error_code (json);
+ sfr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ sfr.hr.ec = TALER_JSON_get_error_code (json);
+ sfr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management set global fee\n",
+ (unsigned int) response_code,
+ (int) sfr.hr.ec);
+ break;
+ }
+ if (NULL != sgfh->cb)
+ {
+ sgfh->cb (sgfh->cb_cls,
+ &sfr);
+ sgfh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_set_global_fees_cancel (sgfh);
+}
+
+
+struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *
+TALER_EXCHANGE_management_set_global_fees (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_base_url,
+ struct GNUNET_TIME_Timestamp validity_start,
+ struct GNUNET_TIME_Timestamp validity_end,
+ const struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative purse_timeout,
+ struct GNUNET_TIME_Relative history_expiration,
+ uint32_t purse_account_limit,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementSetGlobalFeeCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *sgfh;
+ CURL *eh;
+ json_t *body;
+
+ sgfh = GNUNET_new (struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle);
+ sgfh->cb = cb;
+ sgfh->cb_cls = cb_cls;
+ sgfh->ctx = ctx;
+ sgfh->url = TALER_url_join (exchange_base_url,
+ "management/global-fee",
+ NULL);
+ if (NULL == sgfh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (sgfh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig),
+ GNUNET_JSON_pack_timestamp ("fee_start",
+ validity_start),
+ GNUNET_JSON_pack_timestamp ("fee_end",
+ validity_end),
+ TALER_JSON_pack_amount ("history_fee",
+ &fees->history),
+ TALER_JSON_pack_amount ("account_fee",
+ &fees->account),
+ TALER_JSON_pack_amount ("purse_fee",
+ &fees->purse),
+ GNUNET_JSON_pack_time_rel ("purse_timeout",
+ purse_timeout),
+ GNUNET_JSON_pack_time_rel ("history_expiration",
+ history_expiration),
+ GNUNET_JSON_pack_uint64 ("purse_account_limit",
+ purse_account_limit));
+ eh = TALER_EXCHANGE_curl_easy_get_ (sgfh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&sgfh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (sgfh->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ sgfh->url);
+ sgfh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ sgfh->post_ctx.headers,
+ &handle_set_global_fee_finished,
+ sgfh);
+ if (NULL == sgfh->job)
+ {
+ TALER_EXCHANGE_management_set_global_fees_cancel (sgfh);
+ return NULL;
+ }
+ return sgfh;
+}
+
+
+void
+TALER_EXCHANGE_management_set_global_fees_cancel (
+ struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *sgfh)
+{
+ if (NULL != sgfh->job)
+ {
+ GNUNET_CURL_job_cancel (sgfh->job);
+ sgfh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&sgfh->post_ctx);
+ GNUNET_free (sgfh->url);
+ GNUNET_free (sgfh);
+}
diff --git a/src/lib/exchange_api_management_set_wire_fee.c b/src/lib/exchange_api_management_set_wire_fee.c
new file mode 100644
index 000000000..03cab8c99
--- /dev/null
+++ b/src/lib/exchange_api_management_set_wire_fee.c
@@ -0,0 +1,227 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_set_wire_fee.c
+ * @brief functions to set wire fees at an exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_curl_defaults.h"
+#include "taler_exchange_service.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementSetWireFeeHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementSetWireFeeCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/wire request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_set_wire_fee_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementSetWireFeeHandle *swfh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementSetWireFeeResponse swr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ swfh->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ swr.hr.ec = TALER_JSON_get_error_code (json);
+ swr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n",
+ swfh->url);
+ if (NULL != json)
+ {
+ swr.hr.ec = TALER_JSON_get_error_code (json);
+ swr.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ swr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ swr.hr.hint = TALER_ErrorCode_get_hint (swr.hr.ec);
+ }
+ break;
+ case MHD_HTTP_CONFLICT:
+ swr.hr.ec = TALER_JSON_get_error_code (json);
+ swr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_PRECONDITION_FAILED:
+ swr.hr.ec = TALER_JSON_get_error_code (json);
+ swr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ swr.hr.ec = TALER_JSON_get_error_code (json);
+ swr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management set wire fee\n",
+ (unsigned int) response_code,
+ (int) swr.hr.ec);
+ break;
+ }
+ if (NULL != swfh->cb)
+ {
+ swfh->cb (swfh->cb_cls,
+ &swr);
+ swfh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_set_wire_fees_cancel (swfh);
+}
+
+
+struct TALER_EXCHANGE_ManagementSetWireFeeHandle *
+TALER_EXCHANGE_management_set_wire_fees (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_base_url,
+ const char *wire_method,
+ struct GNUNET_TIME_Timestamp validity_start,
+ struct GNUNET_TIME_Timestamp validity_end,
+ const struct TALER_WireFeeSet *fees,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementSetWireFeeCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementSetWireFeeHandle *swfh;
+ CURL *eh;
+ json_t *body;
+
+ swfh = GNUNET_new (struct TALER_EXCHANGE_ManagementSetWireFeeHandle);
+ swfh->cb = cb;
+ swfh->cb_cls = cb_cls;
+ swfh->ctx = ctx;
+ swfh->url = TALER_url_join (exchange_base_url,
+ "management/wire-fee",
+ NULL);
+ if (NULL == swfh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (swfh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("wire_method",
+ wire_method),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig),
+ GNUNET_JSON_pack_timestamp ("fee_start",
+ validity_start),
+ GNUNET_JSON_pack_timestamp ("fee_end",
+ validity_end),
+ TALER_JSON_pack_amount ("closing_fee",
+ &fees->closing),
+ TALER_JSON_pack_amount ("wire_fee",
+ &fees->wire));
+ eh = TALER_EXCHANGE_curl_easy_get_ (swfh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&swfh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (swfh->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ swfh->url);
+ swfh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ swfh->post_ctx.headers,
+ &handle_set_wire_fee_finished,
+ swfh);
+ if (NULL == swfh->job)
+ {
+ TALER_EXCHANGE_management_set_wire_fees_cancel (swfh);
+ return NULL;
+ }
+ return swfh;
+}
+
+
+void
+TALER_EXCHANGE_management_set_wire_fees_cancel (
+ struct TALER_EXCHANGE_ManagementSetWireFeeHandle *swfh)
+{
+ if (NULL != swfh->job)
+ {
+ GNUNET_CURL_job_cancel (swfh->job);
+ swfh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&swfh->post_ctx);
+ GNUNET_free (swfh->url);
+ GNUNET_free (swfh);
+}
diff --git a/src/lib/exchange_api_management_update_aml_officer.c b/src/lib/exchange_api_management_update_aml_officer.c
new file mode 100644
index 000000000..76cbc7e8d
--- /dev/null
+++ b/src/lib/exchange_api_management_update_aml_officer.c
@@ -0,0 +1,229 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_update_aml_officer.c
+ * @brief functions to update AML officer status
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementUpdateAmlOfficer
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementUpdateAmlOfficerCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/wire request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_update_aml_officer_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *wh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementUpdateAmlOfficerResponse uar = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ wh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* no reply */
+ uar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ uar.hr.hint = "server offline?";
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ uar.hr.ec = TALER_JSON_get_error_code (json);
+ uar.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n",
+ wh->url);
+ if (NULL != json)
+ {
+ uar.hr.ec = TALER_JSON_get_error_code (json);
+ uar.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ uar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ uar.hr.hint = TALER_ErrorCode_get_hint (uar.hr.ec);
+ }
+ break;
+ case MHD_HTTP_CONFLICT:
+ uar.hr.ec = TALER_JSON_get_error_code (json);
+ uar.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ uar.hr.ec = TALER_JSON_get_error_code (json);
+ uar.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management update AML officer\n",
+ (unsigned int) response_code,
+ (int) uar.hr.ec);
+ break;
+ }
+ if (NULL != wh->cb)
+ {
+ wh->cb (wh->cb_cls,
+ &uar);
+ wh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_update_aml_officer_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *
+TALER_EXCHANGE_management_update_aml_officer (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *officer_name,
+ struct GNUNET_TIME_Timestamp change_date,
+ bool is_active,
+ bool read_only,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementUpdateAmlOfficerCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *wh;
+ CURL *eh;
+ json_t *body;
+
+ wh = GNUNET_new (struct TALER_EXCHANGE_ManagementUpdateAmlOfficer);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ctx = ctx;
+ wh->url = TALER_url_join (url,
+ "management/aml-officers",
+ NULL);
+ if (NULL == wh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (wh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("officer_name",
+ officer_name),
+ GNUNET_JSON_pack_data_auto ("officer_pub",
+ officer_pub),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig),
+ GNUNET_JSON_pack_bool ("is_active",
+ is_active),
+ GNUNET_JSON_pack_bool ("read_only",
+ read_only),
+ GNUNET_JSON_pack_timestamp ("change_date",
+ change_date));
+ eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&wh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (wh->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ wh->url);
+ wh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ wh->post_ctx.headers,
+ &handle_update_aml_officer_finished,
+ wh);
+ if (NULL == wh->job)
+ {
+ TALER_EXCHANGE_management_update_aml_officer_cancel (wh);
+ return NULL;
+ }
+ return wh;
+}
+
+
+void
+TALER_EXCHANGE_management_update_aml_officer_cancel (
+ struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *wh)
+{
+ if (NULL != wh->job)
+ {
+ GNUNET_CURL_job_cancel (wh->job);
+ wh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&wh->post_ctx);
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_management_wire_disable.c b/src/lib/exchange_api_management_wire_disable.c
new file mode 100644
index 000000000..30749b0e4
--- /dev/null
+++ b/src/lib/exchange_api_management_wire_disable.c
@@ -0,0 +1,220 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_wire_disable.c
+ * @brief functions to disable an exchange wire method / bank account
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementWireDisableHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementWireDisableCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/wire/disable request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAuditorDisableHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_auditor_disable_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementWireDisableHandle *wh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementWireDisableResponse wdr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ wh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* no reply */
+ wdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ wdr.hr.hint = "server offline?";
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ wdr.hr.ec = TALER_JSON_get_error_code (json);
+ wdr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n",
+ wh->url);
+ if (NULL != json)
+ {
+ wdr.hr.ec = TALER_JSON_get_error_code (json);
+ wdr.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ wdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ wdr.hr.hint = TALER_ErrorCode_get_hint (wdr.hr.ec);
+ }
+ break;
+ case MHD_HTTP_CONFLICT:
+ wdr.hr.ec = TALER_JSON_get_error_code (json);
+ wdr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ wdr.hr.ec = TALER_JSON_get_error_code (json);
+ wdr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d exchange management disable wire\n",
+ (unsigned int) response_code,
+ (int) wdr.hr.ec);
+ break;
+ }
+ if (NULL != wh->cb)
+ {
+ wh->cb (wh->cb_cls,
+ &wdr);
+ wh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_disable_wire_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_ManagementWireDisableHandle *
+TALER_EXCHANGE_management_disable_wire (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp validity_end,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementWireDisableCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementWireDisableHandle *wh;
+ CURL *eh;
+ json_t *body;
+
+ wh = GNUNET_new (struct TALER_EXCHANGE_ManagementWireDisableHandle);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ctx = ctx;
+ wh->url = TALER_url_join (url,
+ "management/wire/disable",
+ NULL);
+ if (NULL == wh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (wh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("payto_uri",
+ payto_uri),
+ GNUNET_JSON_pack_data_auto ("master_sig_del",
+ master_sig),
+ GNUNET_JSON_pack_timestamp ("validity_end",
+ validity_end));
+ eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&wh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (wh->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ wh->url);
+ wh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ wh->post_ctx.headers,
+ &handle_auditor_disable_finished,
+ wh);
+ if (NULL == wh->job)
+ {
+ TALER_EXCHANGE_management_disable_wire_cancel (wh);
+ return NULL;
+ }
+ return wh;
+}
+
+
+void
+TALER_EXCHANGE_management_disable_wire_cancel (
+ struct TALER_EXCHANGE_ManagementWireDisableHandle *wh)
+{
+ if (NULL != wh->job)
+ {
+ GNUNET_CURL_job_cancel (wh->job);
+ wh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&wh->post_ctx);
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_management_wire_enable.c b/src/lib/exchange_api_management_wire_enable.c
new file mode 100644
index 000000000..5add3e0b0
--- /dev/null
+++ b/src/lib/exchange_api_management_wire_enable.c
@@ -0,0 +1,245 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_wire_enable.c
+ * @brief functions to enable an exchange wire method / bank account
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementWireEnableHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementWireEnableCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/wire request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_auditor_enable_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementWireEnableHandle *wh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementWireEnableResponse wer = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ wh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* no reply */
+ wer.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ wer.hr.hint = "server offline?";
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ wer.hr.ec = TALER_JSON_get_error_code (json);
+ wer.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n",
+ wh->url);
+ if (NULL != json)
+ {
+ wer.hr.ec = TALER_JSON_get_error_code (json);
+ wer.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ wer.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ wer.hr.hint = TALER_ErrorCode_get_hint (wer.hr.ec);
+ }
+ break;
+ case MHD_HTTP_CONFLICT:
+ wer.hr.ec = TALER_JSON_get_error_code (json);
+ wer.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ wer.hr.ec = TALER_JSON_get_error_code (json);
+ wer.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management enable wire\n",
+ (unsigned int) response_code,
+ (int) wer.hr.ec);
+ break;
+ }
+ if (NULL != wh->cb)
+ {
+ wh->cb (wh->cb_cls,
+ &wer);
+ wh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_enable_wire_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_ManagementWireEnableHandle *
+TALER_EXCHANGE_management_enable_wire (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ struct GNUNET_TIME_Timestamp validity_start,
+ const struct TALER_MasterSignatureP *master_sig1,
+ const struct TALER_MasterSignatureP *master_sig2,
+ TALER_EXCHANGE_ManagementWireEnableCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementWireEnableHandle *wh;
+ CURL *eh;
+ json_t *body;
+
+ {
+ char *msg = TALER_payto_validate (payto_uri);
+
+ if (NULL != msg)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "payto URI is malformed: %s\n",
+ msg);
+ GNUNET_free (msg);
+ return NULL;
+ }
+ }
+ wh = GNUNET_new (struct TALER_EXCHANGE_ManagementWireEnableHandle);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ctx = ctx;
+ wh->url = TALER_url_join (url,
+ "management/wire",
+ NULL);
+ if (NULL == wh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (wh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("payto_uri",
+ payto_uri),
+ GNUNET_JSON_pack_array_incref ("debit_restrictions",
+ (json_t *) debit_restrictions),
+ GNUNET_JSON_pack_array_incref ("credit_restrictions",
+ (json_t *) credit_restrictions),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("conversion_url",
+ conversion_url)),
+ GNUNET_JSON_pack_data_auto ("master_sig_add",
+ master_sig1),
+ GNUNET_JSON_pack_data_auto ("master_sig_wire",
+ master_sig2),
+ GNUNET_JSON_pack_timestamp ("validity_start",
+ validity_start));
+ eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&wh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (wh->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ wh->url);
+ wh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ wh->post_ctx.headers,
+ &handle_auditor_enable_finished,
+ wh);
+ if (NULL == wh->job)
+ {
+ TALER_EXCHANGE_management_enable_wire_cancel (wh);
+ return NULL;
+ }
+ return wh;
+}
+
+
+void
+TALER_EXCHANGE_management_enable_wire_cancel (
+ struct TALER_EXCHANGE_ManagementWireEnableHandle *wh)
+{
+ if (NULL != wh->job)
+ {
+ GNUNET_CURL_job_cancel (wh->job);
+ wh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&wh->post_ctx);
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_melt.c b/src/lib/exchange_api_melt.c
new file mode 100644
index 000000000..ba4241dab
--- /dev/null
+++ b/src/lib/exchange_api_melt.c
@@ -0,0 +1,596 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_melt.c
+ * @brief Implementation of the /coins/$COIN_PUB/melt request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+#include "exchange_api_refresh_common.h"
+
+
+/**
+ * @brief A /coins/$COIN_PUB/melt Handle
+ */
+struct TALER_EXCHANGE_MeltHandle
+{
+
+ /**
+ * The keys of the this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * The exchange base url.
+ */
+ char *exchange_url;
+
+ /**
+ * Curl context.
+ */
+ struct GNUNET_CURL_Context *cctx;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with refresh melt failure results.
+ */
+ TALER_EXCHANGE_MeltCallback melt_cb;
+
+ /**
+ * Closure for @e result_cb and @e melt_failure_cb.
+ */
+ void *melt_cb_cls;
+
+ /**
+ * Actual information about the melt operation.
+ */
+ struct MeltData md;
+
+ /**
+ * The secret the entire melt operation is seeded from.
+ */
+ struct TALER_RefreshMasterSecretP rms;
+
+ /**
+ * Details about the characteristics of the requested melt operation.
+ */
+ const struct TALER_EXCHANGE_RefreshData *rd;
+
+ /**
+ * Array of `num_fresh_coins` per-coin values
+ * returned from melt operation.
+ */
+ struct TALER_EXCHANGE_MeltBlindingDetail *mbds;
+
+ /**
+ * Handle for the preflight request, or NULL.
+ */
+ struct TALER_EXCHANGE_CsRMeltHandle *csr;
+
+ /**
+ * Public key of the coin being melted.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Signature affirming the melt.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
+ * @brief Public information about the coin's denomination key
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey *dki;
+
+ /**
+ * Gamma value chosen by the exchange during melt.
+ */
+ uint32_t noreveal_index;
+
+ /**
+ * True if we need to include @e rms in our melt request.
+ */
+ bool send_rms;
+};
+
+
+/**
+ * Verify that the signature on the "200 OK" response
+ * from the exchange is valid.
+ *
+ * @param[in,out] mh melt handle
+ * @param json json reply with the signature
+ * @param[out] exchange_pub public key of the exchange used for the signature
+ * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
+ */
+static enum GNUNET_GenericReturnValue
+verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh,
+ const json_t *json,
+ struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+ struct TALER_ExchangeSignatureP exchange_sig;
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ exchange_pub),
+ GNUNET_JSON_spec_uint32 ("noreveal_index",
+ &mh->noreveal_index),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* check that exchange signing key is permitted */
+ key_state = mh->keys;
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (key_state,
+ exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* check that noreveal index is in permitted range */
+ if (TALER_CNC_KAPPA <= mh->noreveal_index)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ TALER_exchange_online_melt_confirmation_verify (
+ &mh->md.rc,
+ mh->noreveal_index,
+ exchange_pub,
+ &exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /coins/$COIN_PUB/melt request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_MeltHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_melt_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_MeltHandle *mh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_MeltResponse mr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ mh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ mr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ verify_melt_signature_ok (mh,
+ j,
+ &mr.details.ok.sign_key))
+ {
+ GNUNET_break_op (0);
+ mr.hr.http_status = 0;
+ mr.hr.ec = TALER_EC_EXCHANGE_MELT_INVALID_SIGNATURE_BY_EXCHANGE;
+ break;
+ }
+ mr.details.ok.noreveal_index = mh->noreveal_index;
+ mr.details.ok.num_mbds = mh->rd->fresh_pks_len;
+ mr.details.ok.mbds = mh->mbds;
+ mh->melt_cb (mh->melt_cb_cls,
+ &mr);
+ mh->melt_cb = NULL;
+ 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 */
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; assuming we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ mr.hr.ec = TALER_JSON_get_error_code (j);
+ mr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange melt\n",
+ (unsigned int) response_code,
+ mr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ if (NULL != mh->melt_cb)
+ mh->melt_cb (mh->melt_cb_cls,
+ &mr);
+ TALER_EXCHANGE_melt_cancel (mh);
+}
+
+
+/**
+ * Start the actual melt operation, now that we have
+ * the exchange's input values.
+ *
+ * @param[in,out] mh melt operation to run
+ * @return #GNUNET_OK if we could start the operation
+ */
+static enum GNUNET_GenericReturnValue
+start_melt (struct TALER_EXCHANGE_MeltHandle *mh)
+{
+ const struct TALER_EXCHANGE_Keys *key_state;
+ json_t *melt_obj;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_ExchangeWithdrawValues alg_values[mh->rd->fresh_pks_len];
+
+ for (unsigned int i = 0; i<mh->rd->fresh_pks_len; i++)
+ alg_values[i] = mh->mbds[i].alg_value;
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_get_melt_data_ (&mh->rms,
+ mh->rd,
+ alg_values,
+ &mh->md))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ TALER_denom_pub_hash (&mh->md.melted_coin.pub_key,
+ &h_denom_pub);
+ TALER_wallet_melt_sign (
+ &mh->md.melted_coin.melt_amount_with_fee,
+ &mh->md.melted_coin.fee_melt,
+ &mh->md.rc,
+ &h_denom_pub,
+ mh->md.melted_coin.h_age_commitment,
+ &mh->md.melted_coin.coin_priv,
+ &mh->coin_sig);
+ GNUNET_CRYPTO_eddsa_key_get_public (&mh->md.melted_coin.coin_priv.eddsa_priv,
+ &mh->coin_pub.eddsa_pub);
+ melt_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+ &h_denom_pub),
+ TALER_JSON_pack_denom_sig ("denom_sig",
+ &mh->md.melted_coin.sig),
+ GNUNET_JSON_pack_data_auto ("confirm_sig",
+ &mh->coin_sig),
+ TALER_JSON_pack_amount ("value_with_fee",
+ &mh->md.melted_coin.melt_amount_with_fee),
+ GNUNET_JSON_pack_data_auto ("rc",
+ &mh->md.rc),
+ GNUNET_JSON_pack_allow_null (
+ (NULL != mh->md.melted_coin.h_age_commitment)
+ ? GNUNET_JSON_pack_data_auto ("age_commitment_hash",
+ mh->md.melted_coin.h_age_commitment)
+ : GNUNET_JSON_pack_string ("age_commitment_hash",
+ NULL)),
+ GNUNET_JSON_pack_allow_null (
+ mh->send_rms
+ ? GNUNET_JSON_pack_data_auto ("rms",
+ &mh->rms)
+ : GNUNET_JSON_pack_string ("rms",
+ NULL)));
+ {
+ char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &mh->coin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "coins/%s/melt",
+ pub_str);
+ }
+
+ key_state = mh->keys;
+ mh->dki = TALER_EXCHANGE_get_denomination_key (key_state,
+ &mh->md.melted_coin.pub_key);
+
+ /* and now we can at last begin the actual request handling */
+
+ mh->url = TALER_url_join (mh->exchange_url,
+ arg_str,
+ NULL);
+ if (NULL == mh->url)
+ {
+ json_decref (melt_obj);
+ return GNUNET_SYSERR;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (mh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&mh->ctx,
+ eh,
+ melt_obj)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (melt_obj);
+ return GNUNET_SYSERR;
+ }
+ json_decref (melt_obj);
+ mh->job = GNUNET_CURL_job_add2 (mh->cctx,
+ eh,
+ mh->ctx.headers,
+ &handle_melt_finished,
+ mh);
+ return GNUNET_OK;
+}
+
+
+/**
+ * The melt request @a mh failed, return an error to
+ * the application and cancel the operation.
+ *
+ * @param[in] mh melt request that failed
+ * @param ec error code to fail with
+ */
+static void
+fail_mh (struct TALER_EXCHANGE_MeltHandle *mh,
+ enum TALER_ErrorCode ec)
+{
+ struct TALER_EXCHANGE_MeltResponse mr = {
+ .hr.ec = ec
+ };
+
+ mh->melt_cb (mh->melt_cb_cls,
+ &mr);
+ TALER_EXCHANGE_melt_cancel (mh);
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * CS R request to a exchange.
+ *
+ * @param cls closure with our `struct TALER_EXCHANGE_MeltHandle *`
+ * @param csrr response details
+ */
+static void
+csr_cb (void *cls,
+ const struct TALER_EXCHANGE_CsRMeltResponse *csrr)
+{
+ struct TALER_EXCHANGE_MeltHandle *mh = cls;
+ unsigned int nks_off = 0;
+
+ mh->csr = NULL;
+ if (MHD_HTTP_OK != csrr->hr.http_status)
+ {
+ struct TALER_EXCHANGE_MeltResponse mr = {
+ .hr = csrr->hr
+ };
+
+ mr.hr.hint = "/csr-melt failed";
+ mh->melt_cb (mh->melt_cb_cls,
+ &mr);
+ TALER_EXCHANGE_melt_cancel (mh);
+ return;
+ }
+ for (unsigned int i = 0; i<mh->rd->fresh_pks_len; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk =
+ &mh->rd->fresh_pks[i];
+ struct TALER_ExchangeWithdrawValues *wv = &mh->mbds[i].alg_value;
+
+ switch (fresh_pk->key.cipher)
+ {
+ case TALER_DENOMINATION_INVALID:
+ GNUNET_break (0);
+ fail_mh (mh,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR);
+ return;
+ case TALER_DENOMINATION_RSA:
+ GNUNET_assert (TALER_DENOMINATION_RSA == wv->cipher);
+ break;
+ case TALER_DENOMINATION_CS:
+ GNUNET_assert (TALER_DENOMINATION_CS == wv->cipher);
+ *wv = csrr->details.ok.alg_values[nks_off];
+ nks_off++;
+ break;
+ }
+ }
+ mh->send_rms = true;
+ if (GNUNET_OK !=
+ start_melt (mh))
+ {
+ GNUNET_break (0);
+ fail_mh (mh,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR);
+ return;
+ }
+}
+
+
+struct TALER_EXCHANGE_MeltHandle *
+TALER_EXCHANGE_melt (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_EXCHANGE_RefreshData *rd,
+ TALER_EXCHANGE_MeltCallback melt_cb,
+ void *melt_cb_cls)
+{
+ struct TALER_EXCHANGE_NonceKey nks[GNUNET_NZL (rd->fresh_pks_len)];
+ unsigned int nks_off = 0;
+ struct TALER_EXCHANGE_MeltHandle *mh;
+
+ if (0 == rd->fresh_pks_len)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ mh = GNUNET_new (struct TALER_EXCHANGE_MeltHandle);
+ mh->noreveal_index = TALER_CNC_KAPPA; /* invalid value */
+ mh->cctx = ctx;
+ mh->exchange_url = GNUNET_strdup (url);
+ mh->rd = rd;
+ mh->rms = *rms;
+ mh->melt_cb = melt_cb;
+ mh->melt_cb_cls = melt_cb_cls;
+ mh->mbds = GNUNET_new_array (rd->fresh_pks_len,
+ struct TALER_EXCHANGE_MeltBlindingDetail);
+ for (unsigned int i = 0; i<rd->fresh_pks_len; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk = &rd->fresh_pks[i];
+ struct TALER_ExchangeWithdrawValues *wv = &mh->mbds[i].alg_value;
+
+ switch (fresh_pk->key.cipher)
+ {
+ case TALER_DENOMINATION_INVALID:
+ GNUNET_break (0);
+ GNUNET_free (mh->mbds);
+ GNUNET_free (mh);
+ return NULL;
+ case TALER_DENOMINATION_RSA:
+ wv->cipher = TALER_DENOMINATION_RSA;
+ break;
+ case TALER_DENOMINATION_CS:
+ wv->cipher = TALER_DENOMINATION_CS;
+ nks[nks_off].pk = fresh_pk;
+ nks[nks_off].cnc_num = nks_off;
+ nks_off++;
+ break;
+ }
+ }
+ mh->keys = TALER_EXCHANGE_keys_incref (keys);
+ if (0 != nks_off)
+ {
+ mh->csr = TALER_EXCHANGE_csr_melt (ctx,
+ url,
+ rms,
+ nks_off,
+ nks,
+ &csr_cb,
+ mh);
+ if (NULL == mh->csr)
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_melt_cancel (mh);
+ return NULL;
+ }
+ return mh;
+ }
+ if (GNUNET_OK !=
+ start_melt (mh))
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_melt_cancel (mh);
+ return NULL;
+ }
+ return mh;
+}
+
+
+void
+TALER_EXCHANGE_melt_cancel (struct TALER_EXCHANGE_MeltHandle *mh)
+{
+ if (NULL != mh->job)
+ {
+ GNUNET_CURL_job_cancel (mh->job);
+ mh->job = NULL;
+ }
+ if (NULL != mh->csr)
+ {
+ TALER_EXCHANGE_csr_melt_cancel (mh->csr);
+ mh->csr = NULL;
+ }
+ TALER_EXCHANGE_free_melt_data_ (&mh->md); /* does not free 'md' itself */
+ GNUNET_free (mh->mbds);
+ GNUNET_free (mh->url);
+ GNUNET_free (mh->exchange_url);
+ TALER_curl_easy_post_finished (&mh->ctx);
+ TALER_EXCHANGE_keys_decref (mh->keys);
+ GNUNET_free (mh);
+}
+
+
+/* end of exchange_api_melt.c */
diff --git a/src/lib/exchange_api_recoup.c b/src/lib/exchange_api_recoup.c
new file mode 100644
index 000000000..cfd265f04
--- /dev/null
+++ b/src/lib/exchange_api_recoup.c
@@ -0,0 +1,369 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2017-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_recoup.c
+ * @brief Implementation of the /recoup request of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "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"
+
+
+/**
+ * @brief A Recoup Handle
+ */
+struct TALER_EXCHANGE_RecoupHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Denomination key of the coin.
+ */
+ struct TALER_EXCHANGE_DenomPublicKey pk;
+
+ /**
+ * Our signature requesting the recoup.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_RecoupResultCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Public key of the coin we are trying to get paid back.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+};
+
+
+/**
+ * Parse a recoup response. If it is valid, call the callback.
+ *
+ * @param ph recoup handle
+ * @param json json reply with the signature
+ * @return #GNUNET_OK if the signature is valid and we called the callback;
+ * #GNUNET_SYSERR if not (callback must still be called)
+ */
+static enum GNUNET_GenericReturnValue
+process_recoup_response (const struct TALER_EXCHANGE_RecoupHandle *ph,
+ const json_t *json)
+{
+ struct TALER_EXCHANGE_RecoupResponse rr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ struct GNUNET_JSON_Specification spec_withdraw[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+ &rr.details.ok.reserve_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec_withdraw,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ ph->cb (ph->cb_cls,
+ &rr);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /recoup request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_RecoupHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_recoup_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_RecoupHandle *ph = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_RecoupResponse rr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ ph->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ process_recoup_response (ph,
+ j))
+ {
+ GNUNET_break_op (0);
+ rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ rr.hr.http_status = 0;
+ break;
+ }
+ TALER_EXCHANGE_recoup_cancel (ph);
+ return;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ {
+ struct TALER_Amount min_key;
+
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_get_min_denomination_ (ph->keys,
+ &min_key))
+ {
+ GNUNET_break (0);
+ rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ rr.hr.http_status = 0;
+ break;
+ }
+ break;
+ }
+ case MHD_HTTP_FORBIDDEN:
+ /* 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 */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_GONE:
+ /* Kind of normal: the money was already sent to the merchant
+ (it was too late for the refund). */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange recoup\n",
+ (unsigned int) response_code,
+ (int) rr.hr.ec);
+ GNUNET_break (0);
+ break;
+ }
+ ph->cb (ph->cb_cls,
+ &rr);
+ TALER_EXCHANGE_recoup_cancel (ph);
+}
+
+
+struct TALER_EXCHANGE_RecoupHandle *
+TALER_EXCHANGE_recoup (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_EXCHANGE_DenomPublicKey *pk,
+ const struct TALER_DenominationSignature *denom_sig,
+ const struct TALER_ExchangeWithdrawValues *exchange_vals,
+ const struct TALER_PlanchetMasterSecretP *ps,
+ TALER_EXCHANGE_RecoupResultCallback recoup_cb,
+ void *recoup_cb_cls)
+{
+ struct TALER_EXCHANGE_RecoupHandle *ph;
+ struct TALER_DenominationHashP h_denom_pub;
+ json_t *recoup_obj;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ union TALER_DenominationBlindingKeyP bks;
+
+ ph = GNUNET_new (struct TALER_EXCHANGE_RecoupHandle);
+ TALER_planchet_setup_coin_priv (ps,
+ exchange_vals,
+ &coin_priv);
+ TALER_planchet_blinding_secret_create (ps,
+ exchange_vals,
+ &bks);
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
+ &ph->coin_pub.eddsa_pub);
+ TALER_denom_pub_hash (&pk->key,
+ &h_denom_pub);
+ TALER_wallet_recoup_sign (&h_denom_pub,
+ &bks,
+ &coin_priv,
+ &ph->coin_sig);
+ recoup_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+ &h_denom_pub),
+ TALER_JSON_pack_denom_sig ("denom_sig",
+ denom_sig),
+ TALER_JSON_pack_exchange_withdraw_values ("ewv",
+ exchange_vals),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &ph->coin_sig),
+ GNUNET_JSON_pack_data_auto ("coin_blind_key_secret",
+ &bks));
+ if (TALER_DENOMINATION_CS == denom_sig->cipher)
+ {
+ struct TALER_CsNonce nonce;
+
+ /* NOTE: this is not elegant, and as per the note in TALER_coin_ev_hash()
+ it is not strictly clear that the nonce is needed. Best case would be
+ to find a way to include it more 'naturally' somehow, for example with
+ the variant union version of bks! */
+ TALER_cs_withdraw_nonce_derive (ps,
+ &nonce);
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (recoup_obj,
+ "cs_nonce",
+ GNUNET_JSON_from_data_auto (
+ &nonce)));
+ }
+
+ {
+ char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &ph->coin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "coins/%s/recoup",
+ pub_str);
+ }
+
+ ph->pk = *pk;
+ memset (&ph->pk.key,
+ 0,
+ sizeof (ph->pk.key)); /* zero out, as lifetime cannot be warranted */
+ ph->cb = recoup_cb;
+ ph->cb_cls = recoup_cb_cls;
+ ph->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == ph->url)
+ {
+ json_decref (recoup_obj);
+ GNUNET_free (ph);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (ph->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&ph->ctx,
+ eh,
+ recoup_obj)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (recoup_obj);
+ GNUNET_free (ph->url);
+ GNUNET_free (ph);
+ return NULL;
+ }
+ json_decref (recoup_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for recoup: `%s'\n",
+ ph->url);
+ ph->keys = TALER_EXCHANGE_keys_incref (keys);
+ ph->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ ph->ctx.headers,
+ &handle_recoup_finished,
+ ph);
+ return ph;
+}
+
+
+void
+TALER_EXCHANGE_recoup_cancel (struct TALER_EXCHANGE_RecoupHandle *ph)
+{
+ if (NULL != ph->job)
+ {
+ GNUNET_CURL_job_cancel (ph->job);
+ ph->job = NULL;
+ }
+ GNUNET_free (ph->url);
+ TALER_curl_easy_post_finished (&ph->ctx);
+ TALER_EXCHANGE_keys_decref (ph->keys);
+ GNUNET_free (ph);
+}
+
+
+/* end of exchange_api_recoup.c */
diff --git a/src/lib/exchange_api_recoup_refresh.c b/src/lib/exchange_api_recoup_refresh.c
new file mode 100644
index 000000000..0bcd44dec
--- /dev/null
+++ b/src/lib/exchange_api_recoup_refresh.c
@@ -0,0 +1,363 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2017-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_recoup_refresh.c
+ * @brief Implementation of the /recoup-refresh request of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "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"
+
+
+/**
+ * @brief A Recoup Handle
+ */
+struct TALER_EXCHANGE_RecoupRefreshHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Denomination key of the coin.
+ */
+ struct TALER_EXCHANGE_DenomPublicKey pk;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_RecoupRefreshResultCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Public key of the coin we are trying to get paid back.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Signature affirming the recoup-refresh operation.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+};
+
+
+/**
+ * Parse a recoup-refresh response. If it is valid, call the callback.
+ *
+ * @param ph recoup handle
+ * @param json json reply with the signature
+ * @return #GNUNET_OK if the signature is valid and we called the callback;
+ * #GNUNET_SYSERR if not (callback must still be called)
+ */
+static enum GNUNET_GenericReturnValue
+process_recoup_response (
+ const struct TALER_EXCHANGE_RecoupRefreshHandle *ph,
+ const json_t *json)
+{
+ struct TALER_EXCHANGE_RecoupRefreshResponse rrr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ struct GNUNET_JSON_Specification spec_refresh[] = {
+ GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
+ &rrr.details.ok.old_coin_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec_refresh,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ ph->cb (ph->cb_cls,
+ &rrr);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /recoup-refresh request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_RecoupRefreshHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_recoup_refresh_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_RecoupRefreshHandle *ph = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_RecoupRefreshResponse rrr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ ph->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ process_recoup_response (ph,
+ j))
+ {
+ GNUNET_break_op (0);
+ rrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ rrr.hr.http_status = 0;
+ break;
+ }
+ TALER_EXCHANGE_recoup_refresh_cancel (ph);
+ return;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* 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 */
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_GONE:
+ /* Kind of normal: the money was already sent to the merchant
+ (it was too late for the refund). */
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange recoup\n",
+ (unsigned int) response_code,
+ (int) rrr.hr.ec);
+ GNUNET_break (0);
+ break;
+ }
+ ph->cb (ph->cb_cls,
+ &rrr);
+ TALER_EXCHANGE_recoup_refresh_cancel (ph);
+}
+
+
+struct TALER_EXCHANGE_RecoupRefreshHandle *
+TALER_EXCHANGE_recoup_refresh (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_EXCHANGE_DenomPublicKey *pk,
+ const struct TALER_DenominationSignature *denom_sig,
+ const struct TALER_ExchangeWithdrawValues *exchange_vals,
+ const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_PlanchetMasterSecretP *ps,
+ unsigned int idx,
+ TALER_EXCHANGE_RecoupRefreshResultCallback recoup_cb,
+ void *recoup_cb_cls)
+{
+ struct TALER_EXCHANGE_RecoupRefreshHandle *ph;
+ struct TALER_DenominationHashP h_denom_pub;
+ json_t *recoup_obj;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ union TALER_DenominationBlindingKeyP bks;
+
+ GNUNET_assert (NULL != recoup_cb);
+ ph = GNUNET_new (struct TALER_EXCHANGE_RecoupRefreshHandle);
+ ph->pk = *pk;
+ memset (&ph->pk.key,
+ 0,
+ sizeof (ph->pk.key)); /* zero out, as lifetime cannot be warranted */
+ ph->cb = recoup_cb;
+ ph->cb_cls = recoup_cb_cls;
+ TALER_planchet_setup_coin_priv (ps,
+ exchange_vals,
+ &coin_priv);
+ TALER_planchet_blinding_secret_create (ps,
+ exchange_vals,
+ &bks);
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
+ &ph->coin_pub.eddsa_pub);
+ TALER_denom_pub_hash (&pk->key,
+ &h_denom_pub);
+ TALER_wallet_recoup_refresh_sign (&h_denom_pub,
+ &bks,
+ &coin_priv,
+ &ph->coin_sig);
+ recoup_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+ &h_denom_pub),
+ TALER_JSON_pack_denom_sig ("denom_sig",
+ denom_sig),
+ TALER_JSON_pack_exchange_withdraw_values ("ewv",
+ exchange_vals),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &ph->coin_sig),
+ GNUNET_JSON_pack_data_auto ("coin_blind_key_secret",
+ &bks));
+
+ if (TALER_DENOMINATION_CS == denom_sig->cipher)
+ {
+ struct TALER_CsNonce nonce;
+
+ /* NOTE: this is not elegant, and as per the note in TALER_coin_ev_hash()
+ it is not strictly clear that the nonce is needed. Best case would be
+ to find a way to include it more 'naturally' somehow, for example with
+ the variant union version of bks! */
+ TALER_cs_refresh_nonce_derive (rms,
+ idx,
+ &nonce);
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (recoup_obj,
+ "cs_nonce",
+ GNUNET_JSON_from_data_auto (
+ &nonce)));
+ }
+
+ {
+ char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &ph->coin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "coins/%s/recoup-refresh",
+ pub_str);
+ }
+
+ ph->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == ph->url)
+ {
+ json_decref (recoup_obj);
+ GNUNET_free (ph);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (ph->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&ph->ctx,
+ eh,
+ recoup_obj)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (recoup_obj);
+ GNUNET_free (ph->url);
+ GNUNET_free (ph);
+ return NULL;
+ }
+ json_decref (recoup_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for recoup-refresh: `%s'\n",
+ ph->url);
+ ph->keys = TALER_EXCHANGE_keys_incref (keys);
+ ph->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ ph->ctx.headers,
+ &handle_recoup_refresh_finished,
+ ph);
+ return ph;
+}
+
+
+void
+TALER_EXCHANGE_recoup_refresh_cancel (
+ struct TALER_EXCHANGE_RecoupRefreshHandle *ph)
+{
+ if (NULL != ph->job)
+ {
+ GNUNET_CURL_job_cancel (ph->job);
+ ph->job = NULL;
+ }
+ GNUNET_free (ph->url);
+ TALER_curl_easy_post_finished (&ph->ctx);
+ TALER_EXCHANGE_keys_decref (ph->keys);
+ GNUNET_free (ph);
+}
+
+
+/* end of exchange_api_recoup_refresh.c */
diff --git a/src/lib/exchange_api_refresh_common.c b/src/lib/exchange_api_refresh_common.c
new file mode 100644
index 000000000..0a6665b55
--- /dev/null
+++ b/src/lib/exchange_api_refresh_common.c
@@ -0,0 +1,249 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_refresh_common.c
+ * @brief Serialization logic shared between melt and reveal steps during refreshing
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "exchange_api_refresh_common.h"
+
+
+void
+TALER_EXCHANGE_free_melt_data_ (struct MeltData *md)
+{
+ for (unsigned int i = 0; i < TALER_CNC_KAPPA; i++)
+ {
+ struct TALER_RefreshCoinData *rcds = md->rcd[i];
+
+ if (NULL == rcds)
+ continue;
+ for (unsigned int j = 0; j < md->num_fresh_coins; j++)
+ TALER_blinded_planchet_free (&rcds[j].blinded_planchet);
+ GNUNET_free (rcds);
+ }
+ TALER_denom_pub_free (&md->melted_coin.pub_key);
+ TALER_denom_sig_free (&md->melted_coin.sig);
+ if (NULL != md->fcds)
+ {
+ for (unsigned int j = 0; j<md->num_fresh_coins; j++)
+ {
+ struct FreshCoinData *fcd = &md->fcds[j];
+
+ TALER_denom_pub_free (&fcd->fresh_pk);
+ for (size_t i = 0; i < TALER_CNC_KAPPA; i++)
+ {
+ TALER_age_commitment_proof_free (fcd->age_commitment_proofs[i]);
+ GNUNET_free (fcd->age_commitment_proofs[i]);
+ }
+ }
+ GNUNET_free (md->fcds);
+ }
+ /* Finally, clean up a bit... */
+ GNUNET_CRYPTO_zero_keys (md,
+ sizeof (struct MeltData));
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_get_melt_data_ (
+ const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_EXCHANGE_RefreshData *rd,
+ const struct TALER_ExchangeWithdrawValues *alg_values,
+ struct MeltData *md)
+{
+ struct TALER_Amount total;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CsNonce nonces[rd->fresh_pks_len];
+ bool uses_cs = false;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&rd->melt_priv.eddsa_priv,
+ &coin_pub.eddsa_pub);
+ /* build up melt data structure */
+ memset (md,
+ 0,
+ sizeof (*md));
+ md->num_fresh_coins = rd->fresh_pks_len;
+ md->melted_coin.coin_priv = rd->melt_priv;
+ md->melted_coin.melt_amount_with_fee = rd->melt_amount;
+ md->melted_coin.fee_melt = rd->melt_pk.fees.refresh;
+ md->melted_coin.original_value = rd->melt_pk.value;
+ md->melted_coin.expire_deposit = rd->melt_pk.expire_deposit;
+ md->melted_coin.age_commitment_proof = rd->melt_age_commitment_proof;
+ md->melted_coin.h_age_commitment = rd->melt_h_age_commitment;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (rd->melt_amount.currency,
+ &total));
+ TALER_denom_pub_deep_copy (&md->melted_coin.pub_key,
+ &rd->melt_pk.key);
+ TALER_denom_sig_deep_copy (&md->melted_coin.sig,
+ &rd->melt_sig);
+ md->fcds = GNUNET_new_array (md->num_fresh_coins,
+ struct FreshCoinData);
+ for (unsigned int j = 0; j<rd->fresh_pks_len; j++)
+ {
+ struct FreshCoinData *fcd = &md->fcds[j];
+
+ if (alg_values[j].cipher != rd->fresh_pks[j].key.cipher)
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_free_melt_data_ (md);
+ return GNUNET_SYSERR;
+ }
+ if (TALER_DENOMINATION_CS == alg_values[j].cipher)
+ {
+ uses_cs = true;
+ TALER_cs_refresh_nonce_derive (
+ rms,
+ j,
+ &nonces[j]);
+ }
+ TALER_denom_pub_deep_copy (&fcd->fresh_pk,
+ &rd->fresh_pks[j].key);
+ if ( (0 >
+ TALER_amount_add (&total,
+ &total,
+ &rd->fresh_pks[j].value)) ||
+ (0 >
+ TALER_amount_add (&total,
+ &total,
+ &rd->fresh_pks[j].fees.withdraw)) )
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_free_melt_data_ (md);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ /* verify that melt_amount is above total cost */
+ if (1 ==
+ TALER_amount_cmp (&total,
+ &rd->melt_amount) )
+ {
+ /* Eh, this operation is more expensive than the
+ @a melt_amount. This is not OK. */
+ GNUNET_break (0);
+ TALER_EXCHANGE_free_melt_data_ (md);
+ return GNUNET_SYSERR;
+ }
+
+ /* build up coins */
+ for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
+ {
+ struct TALER_TransferSecretP trans_sec;
+
+ TALER_planchet_secret_to_transfer_priv (
+ rms,
+ &rd->melt_priv,
+ i,
+ &md->transfer_priv[i]);
+
+ GNUNET_CRYPTO_ecdhe_key_get_public (
+ &md->transfer_priv[i].ecdhe_priv,
+ &md->transfer_pub[i].ecdhe_pub);
+
+ TALER_link_derive_transfer_secret (&rd->melt_priv,
+ &md->transfer_priv[i],
+ &trans_sec);
+
+ md->rcd[i] = GNUNET_new_array (rd->fresh_pks_len,
+ struct TALER_RefreshCoinData);
+
+ for (unsigned int j = 0; j<rd->fresh_pks_len; j++)
+ {
+ struct FreshCoinData *fcd = &md->fcds[j];
+ struct TALER_CoinSpendPrivateKeyP *coin_priv = &fcd->coin_priv;
+ struct TALER_PlanchetMasterSecretP *ps = &fcd->ps[i];
+ struct TALER_RefreshCoinData *rcd = &md->rcd[i][j];
+ union TALER_DenominationBlindingKeyP *bks = &fcd->bks[i];
+ struct TALER_PlanchetDetail pd;
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_AgeCommitmentHash ach;
+ struct TALER_AgeCommitmentHash *pah = NULL;
+
+ TALER_transfer_secret_to_planchet_secret (&trans_sec,
+ j,
+ ps);
+
+ TALER_planchet_setup_coin_priv (ps,
+ &alg_values[j],
+ coin_priv);
+
+ TALER_planchet_blinding_secret_create (ps,
+ &alg_values[j],
+ bks);
+
+ if (NULL != rd->melt_age_commitment_proof)
+ {
+ fcd->age_commitment_proofs[i] = GNUNET_new (struct
+ TALER_AgeCommitmentProof);
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_age_commitment_derive (
+ md->melted_coin.age_commitment_proof,
+ &trans_sec.key,
+ fcd->age_commitment_proofs[i]));
+
+ TALER_age_commitment_hash (
+ &fcd->age_commitment_proofs[i]->commitment,
+ &ach);
+ pah = &ach;
+ }
+
+ if (TALER_DENOMINATION_CS == alg_values[j].cipher)
+ pd.blinded_planchet.details.cs_blinded_planchet.nonce = nonces[j];
+
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&fcd->fresh_pk,
+ &alg_values[j],
+ bks,
+ coin_priv,
+ pah,
+ &c_hash,
+ &pd))
+ {
+ GNUNET_break_op (0);
+ TALER_EXCHANGE_free_melt_data_ (md);
+ return GNUNET_SYSERR;
+ }
+ rcd->blinded_planchet = pd.blinded_planchet;
+ rcd->dk = &fcd->fresh_pk;
+ }
+ }
+
+ /* Finally, compute refresh commitment */
+ {
+ struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA];
+
+ for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
+ {
+ rce[i].transfer_pub = md->transfer_pub[i];
+ rce[i].new_coins = md->rcd[i];
+ }
+ TALER_refresh_get_commitment (&md->rc,
+ TALER_CNC_KAPPA,
+ uses_cs
+ ? rms
+ : NULL,
+ rd->fresh_pks_len,
+ rce,
+ &coin_pub,
+ &rd->melt_amount);
+ }
+ return GNUNET_OK;
+}
diff --git a/src/lib/exchange_api_refresh_common.h b/src/lib/exchange_api_refresh_common.h
new file mode 100644
index 000000000..0cb80f17e
--- /dev/null
+++ b/src/lib/exchange_api_refresh_common.h
@@ -0,0 +1,201 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_refresh_common.h
+ * @brief shared (serialization) logic for refresh protocol
+ * @author Christian Grothoff
+ */
+#ifndef REFRESH_COMMON_H
+#define REFRESH_COMMON_H
+#include <jansson.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "taler_signatures.h"
+
+
+/**
+ * Information about a coin we are melting.
+ */
+struct MeltedCoin
+{
+ /**
+ * Private key of the coin.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * Amount this coin contributes to the melt, including fee.
+ */
+ struct TALER_Amount melt_amount_with_fee;
+
+ /**
+ * The applicable fee for melting a coin of this denomination
+ */
+ struct TALER_Amount fee_melt;
+
+ /**
+ * The original value of the coin.
+ */
+ struct TALER_Amount original_value;
+
+ /**
+ * The original age commitment, its proof and its hash. MUST be NULL if no
+ * age commitment was set.
+ */
+ const struct TALER_AgeCommitmentProof *age_commitment_proof;
+ const struct TALER_AgeCommitmentHash *h_age_commitment;
+
+ /**
+ * Timestamp indicating when coins of this denomination become invalid.
+ */
+ struct GNUNET_TIME_Timestamp expire_deposit;
+
+ /**
+ * Denomination key of the original coin.
+ */
+ struct TALER_DenominationPublicKey pub_key;
+
+ /**
+ * Exchange's signature over the coin.
+ */
+ struct TALER_DenominationSignature sig;
+
+};
+
+
+/**
+ * Data we keep for each fresh coin created in the
+ * melt process.
+ */
+struct FreshCoinData
+{
+ /**
+ * Denomination public key of the coin.
+ */
+ struct TALER_DenominationPublicKey fresh_pk;
+
+ /**
+ * Array of planchet secrets for the coins, depending
+ * on the cut-and-choose.
+ */
+ struct TALER_PlanchetMasterSecretP ps[TALER_CNC_KAPPA];
+
+ /**
+ * Private key of the coin.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * Arrays of age commitments and proofs to be created, one for each
+ * cut-and-choose dimension. NULL if age restriction is not applicable.
+ */
+ struct TALER_AgeCommitmentProof *age_commitment_proofs[TALER_CNC_KAPPA];
+
+ /**
+ * Blinding key secrets for the coins, depending on the
+ * cut-and-choose.
+ */
+ union TALER_DenominationBlindingKeyP bks[TALER_CNC_KAPPA];
+
+};
+
+
+/**
+ * Melt data in non-serialized format for convenient processing.
+ */
+struct MeltData
+{
+
+ /**
+ * Hash over the committed data during refresh operation.
+ */
+ struct TALER_RefreshCommitmentP rc;
+
+ /**
+ * Information about the melted coin.
+ */
+ struct MeltedCoin melted_coin;
+
+ /**
+ * Array of length @e num_fresh_coins with information
+ * about each fresh coin.
+ */
+ struct FreshCoinData *fcds;
+
+ /**
+ * Transfer secrets, one per cut and choose.
+ */
+ struct TALER_TransferSecretP trans_sec[TALER_CNC_KAPPA];
+
+ /**
+ * Transfer private keys for each cut-and-choose dimension.
+ */
+ struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA];
+
+ /**
+ * Transfer public key of this commitment.
+ */
+ struct TALER_TransferPublicKeyP transfer_pub[TALER_CNC_KAPPA];
+
+ /**
+ * Transfer secrets, one per cut and choose.
+ */
+ struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA];
+
+ /**
+ * Blinded planchets and denominations of the fresh coins, depending on the cut-and-choose. Array of length
+ * @e num_fresh_coins.
+ */
+ struct TALER_RefreshCoinData *rcd[TALER_CNC_KAPPA];
+
+ /**
+ * Number of coins we are creating
+ */
+ uint16_t num_fresh_coins;
+
+};
+
+
+/**
+ * Compute the melt data from the refresh data and secret.
+ *
+ * @param rms secret internals of the refresh-reveal operation
+ * @param rd refresh data with the characteristics of the operation
+ * @param alg_values contributions from the exchange into the melt
+ * @param[out] md where to write the derived melt data
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_get_melt_data_ (
+ const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_EXCHANGE_RefreshData *rd,
+ const struct TALER_ExchangeWithdrawValues *alg_values,
+ struct MeltData *md);
+
+
+/**
+ * Free all information associated with a melting session. Note
+ * that we allow the melting session to be only partially initialized,
+ * as we use this function also when freeing melt data that was not
+ * fully initialized.
+ *
+ * @param[in] md melting data to release, the pointer itself is NOT
+ * freed (as it is typically not allocated by itself)
+ */
+void
+TALER_EXCHANGE_free_melt_data_ (struct MeltData *md);
+
+#endif
diff --git a/src/lib/exchange_api_refreshes_reveal.c b/src/lib/exchange_api_refreshes_reveal.c
new file mode 100644
index 000000000..220682992
--- /dev/null
+++ b/src/lib/exchange_api_refreshes_reveal.c
@@ -0,0 +1,531 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_refreshes_reveal.c
+ * @brief Implementation of the /refreshes/$RCH/reveal requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+#include "exchange_api_refresh_common.h"
+
+
+/**
+ * @brief A /refreshes/$RCH/reveal Handle
+ */
+struct TALER_EXCHANGE_RefreshesRevealHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Exchange-contributed values to the operation.
+ */
+ struct TALER_ExchangeWithdrawValues *alg_values;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_RefreshesRevealCallback reveal_cb;
+
+ /**
+ * Closure for @e reveal_cb.
+ */
+ void *reveal_cb_cls;
+
+ /**
+ * Actual information about the melt operation.
+ */
+ struct MeltData md;
+
+ /**
+ * The index selected by the exchange in cut-and-choose to not be revealed.
+ */
+ uint16_t noreveal_index;
+
+};
+
+
+/**
+ * We got a 200 OK response for the /refreshes/$RCH/reveal operation. Extract
+ * the coin signatures and return them to the caller. The signatures we get
+ * from the exchange is for the blinded value. Thus, we first must unblind
+ * them and then should verify their validity.
+ *
+ * If everything checks out, we return the unblinded signatures
+ * to the application via the callback.
+ *
+ * @param rrh operation handle
+ * @param json reply from the exchange
+ * @param[out] rcis array of length `num_fresh_coins`, initialized to contain the coin data
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,
+ const json_t *json,
+ struct TALER_EXCHANGE_RevealedCoinInfo *rcis)
+{
+ const json_t *jsona;
+ struct GNUNET_JSON_Specification outer_spec[] = {
+ GNUNET_JSON_spec_array_const ("ev_sigs",
+ &jsona),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ outer_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (rrh->md.num_fresh_coins != json_array_size (jsona))
+ {
+ /* Number of coins generated does not match our expectation */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ for (unsigned int i = 0; i<rrh->md.num_fresh_coins; i++)
+ {
+ struct TALER_EXCHANGE_RevealedCoinInfo *rci = &rcis[i];
+ const struct FreshCoinData *fcd = &rrh->md.fcds[i];
+ const struct TALER_DenominationPublicKey *pk;
+ json_t *jsonai;
+ struct TALER_BlindedDenominationSignature blind_sig;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinPubHashP coin_hash;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_blinded_denom_sig ("ev_sig",
+ &blind_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ struct TALER_FreshCoin coin;
+ union TALER_DenominationBlindingKeyP bks;
+ const struct TALER_AgeCommitmentHash *pah = NULL;
+
+ rci->ps = fcd->ps[rrh->noreveal_index];
+ rci->bks = fcd->bks[rrh->noreveal_index];
+ rci->age_commitment_proof = NULL;
+
+ pk = &fcd->fresh_pk;
+ jsonai = json_array_get (jsona, i);
+
+ GNUNET_assert (NULL != jsonai);
+
+ if (NULL != rrh->md.melted_coin.age_commitment_proof)
+ {
+ rci->age_commitment_proof =
+ fcd->age_commitment_proofs[rrh->noreveal_index];
+
+ TALER_age_commitment_hash (&rci->age_commitment_proof->commitment,
+ &rci->h_age_commitment);
+ pah = &rci->h_age_commitment;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jsonai,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ TALER_planchet_setup_coin_priv (&rci->ps,
+ &rrh->alg_values[i],
+ &rci->coin_priv);
+ TALER_planchet_blinding_secret_create (&rci->ps,
+ &rrh->alg_values[i],
+ &bks);
+ /* needed to verify the signature, and we didn't store it earlier,
+ hence recomputing it here... */
+ GNUNET_CRYPTO_eddsa_key_get_public (&rci->coin_priv.eddsa_priv,
+ &coin_pub.eddsa_pub);
+ TALER_coin_pub_hash (
+ &coin_pub,
+ pah,
+ &coin_hash);
+ if (GNUNET_OK !=
+ TALER_planchet_to_coin (
+ pk,
+ &blind_sig,
+ &bks,
+ &rci->coin_priv,
+ pah,
+ &coin_hash,
+ &rrh->alg_values[i],
+ &coin))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_JSON_parse_free (spec);
+ rci->sig = coin.sig;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /refreshes/$RCH/reveal request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_RefreshHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_refresh_reveal_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_RefreshesRevealHandle *rrh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_RevealResult rr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ rrh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct TALER_EXCHANGE_RevealedCoinInfo rcis[rrh->md.num_fresh_coins];
+ enum GNUNET_GenericReturnValue ret;
+
+ memset (rcis,
+ 0,
+ sizeof (rcis));
+ ret = refresh_reveal_ok (rrh,
+ j,
+ rcis);
+ if (GNUNET_OK != ret)
+ {
+ rr.hr.http_status = 0;
+ rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ else
+ {
+ GNUNET_assert (rrh->noreveal_index < TALER_CNC_KAPPA);
+ rr.details.ok.num_coins = rrh->md.num_fresh_coins;
+ rr.details.ok.coins = rcis;
+ rrh->reveal_cb (rrh->reveal_cb_cls,
+ &rr);
+ rrh->reveal_cb = NULL;
+ }
+ for (unsigned int i = 0; i<rrh->md.num_fresh_coins; i++)
+ {
+ TALER_denom_sig_free (&rcis[i].sig);
+ TALER_age_commitment_proof_free (rcis[i].age_commitment_proof);
+ }
+ TALER_EXCHANGE_refreshes_reveal_cancel (rrh);
+ return;
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* Nothing really to verify, exchange says our reveal is inconsistent
+ with our commitment, so either side is buggy; we
+ should pass the JSON reply to the application */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_GONE:
+ /* Server claims key expired or has been revoked */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange refreshes reveal\n",
+ (unsigned int) response_code,
+ (int) rr.hr.ec);
+ break;
+ }
+ if (NULL != rrh->reveal_cb)
+ rrh->reveal_cb (rrh->reveal_cb_cls,
+ &rr);
+ TALER_EXCHANGE_refreshes_reveal_cancel (rrh);
+}
+
+
+struct TALER_EXCHANGE_RefreshesRevealHandle *
+TALER_EXCHANGE_refreshes_reveal (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_EXCHANGE_RefreshData *rd,
+ unsigned int num_coins,
+ const struct TALER_ExchangeWithdrawValues alg_values[static num_coins],
+ uint32_t noreveal_index,
+ TALER_EXCHANGE_RefreshesRevealCallback reveal_cb,
+ void *reveal_cb_cls)
+{
+ struct TALER_EXCHANGE_RefreshesRevealHandle *rrh;
+ json_t *transfer_privs;
+ json_t *new_denoms_h;
+ json_t *coin_evs;
+ json_t *reveal_obj;
+ json_t *link_sigs;
+ json_t *old_age_commitment = NULL;
+ CURL *eh;
+ struct MeltData md;
+ char arg_str[sizeof (struct TALER_RefreshCommitmentP) * 2 + 32];
+ bool send_rms = false;
+
+ GNUNET_assert (num_coins == rd->fresh_pks_len);
+ if (noreveal_index >= TALER_CNC_KAPPA)
+ {
+ /* We check this here, as it would be really bad to below just
+ disclose all the transfer keys. Note that this error should
+ have been caught way earlier when the exchange replied, but maybe
+ we had some internal corruption that changed the value... */
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_get_melt_data_ (rms,
+ rd,
+ alg_values,
+ &md))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ /* now new_denoms */
+ GNUNET_assert (NULL != (new_denoms_h = json_array ()));
+ GNUNET_assert (NULL != (coin_evs = json_array ()));
+ GNUNET_assert (NULL != (link_sigs = json_array ()));
+ for (unsigned int i = 0; i<md.num_fresh_coins; i++)
+ {
+ const struct TALER_RefreshCoinData *rcd = &md.rcd[noreveal_index][i];
+ struct TALER_DenominationHashP denom_hash;
+
+ if (TALER_DENOMINATION_CS == md.fcds[i].fresh_pk.cipher)
+ send_rms = true;
+ TALER_denom_pub_hash (&md.fcds[i].fresh_pk,
+ &denom_hash);
+ GNUNET_assert (0 ==
+ json_array_append_new (new_denoms_h,
+ GNUNET_JSON_from_data_auto (
+ &denom_hash)));
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ coin_evs,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_blinded_planchet (
+ NULL,
+ &rcd->blinded_planchet))));
+ {
+ struct TALER_CoinSpendSignatureP link_sig;
+ struct TALER_BlindedCoinHashP bch;
+
+ TALER_coin_ev_hash (&rcd->blinded_planchet,
+ &denom_hash,
+ &bch);
+ TALER_wallet_link_sign (
+ &denom_hash,
+ &md.transfer_pub[noreveal_index],
+ &bch,
+ &md.melted_coin.coin_priv,
+ &link_sig);
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ link_sigs,
+ GNUNET_JSON_from_data_auto (&link_sig)));
+ }
+ }
+
+ /* build array of transfer private keys */
+ GNUNET_assert (NULL != (transfer_privs = json_array ()));
+ for (unsigned int j = 0; j<TALER_CNC_KAPPA; j++)
+ {
+ if (j == noreveal_index)
+ {
+ /* This is crucial: exclude the transfer key for the noreval index! */
+ continue;
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (transfer_privs,
+ GNUNET_JSON_from_data_auto (
+ &md.transfer_priv[j])));
+ }
+
+ /* build array of old age commitment, if applicable */
+ if (NULL != rd->melt_age_commitment_proof)
+ {
+ GNUNET_assert (NULL != rd->melt_h_age_commitment);
+ GNUNET_assert (NULL != (old_age_commitment = json_array ()));
+
+ for (size_t i = 0; i < rd->melt_age_commitment_proof->commitment.num; i++)
+ {
+ enum GNUNET_GenericReturnValue ret;
+ ret = json_array_append_new (
+ old_age_commitment,
+ GNUNET_JSON_from_data_auto (
+ &rd->melt_age_commitment_proof->commitment.keys[i]));
+ GNUNET_assert (0 == ret);
+ }
+ }
+
+ /* build main JSON request */
+ reveal_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("transfer_pub",
+ &md.transfer_pub[noreveal_index]),
+ GNUNET_JSON_pack_allow_null (
+ send_rms
+ ? GNUNET_JSON_pack_data_auto ("rms",
+ rms)
+ : GNUNET_JSON_pack_string ("rms",
+ NULL)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("old_age_commitment",
+ old_age_commitment)),
+ GNUNET_JSON_pack_array_steal ("transfer_privs",
+ transfer_privs),
+ GNUNET_JSON_pack_array_steal ("link_sigs",
+ link_sigs),
+ GNUNET_JSON_pack_array_steal ("new_denoms_h",
+ new_denoms_h),
+ GNUNET_JSON_pack_array_steal ("coin_evs",
+ coin_evs));
+ {
+ char pub_str[sizeof (struct TALER_RefreshCommitmentP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (&md.rc,
+ sizeof (md.rc),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "refreshes/%s/reveal",
+ pub_str);
+ }
+ /* finally, we can actually issue the request */
+ rrh = GNUNET_new (struct TALER_EXCHANGE_RefreshesRevealHandle);
+ rrh->noreveal_index = noreveal_index;
+ rrh->reveal_cb = reveal_cb;
+ rrh->reveal_cb_cls = reveal_cb_cls;
+ rrh->md = md;
+ rrh->alg_values
+ = GNUNET_memdup (alg_values,
+ md.num_fresh_coins
+ * sizeof (struct TALER_ExchangeWithdrawValues));
+ rrh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == rrh->url)
+ {
+ json_decref (reveal_obj);
+ TALER_EXCHANGE_free_melt_data_ (&md);
+ GNUNET_free (rrh->alg_values);
+ GNUNET_free (rrh);
+ return NULL;
+ }
+
+ eh = TALER_EXCHANGE_curl_easy_get_ (rrh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&rrh->ctx,
+ eh,
+ reveal_obj)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (reveal_obj);
+ TALER_EXCHANGE_free_melt_data_ (&md);
+ GNUNET_free (rrh->alg_values);
+ GNUNET_free (rrh->url);
+ GNUNET_free (rrh);
+ return NULL;
+ }
+ json_decref (reveal_obj);
+ rrh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ rrh->ctx.headers,
+ &handle_refresh_reveal_finished,
+ rrh);
+ return rrh;
+}
+
+
+void
+TALER_EXCHANGE_refreshes_reveal_cancel (
+ struct TALER_EXCHANGE_RefreshesRevealHandle *rrh)
+{
+ if (NULL != rrh->job)
+ {
+ GNUNET_CURL_job_cancel (rrh->job);
+ rrh->job = NULL;
+ }
+ GNUNET_free (rrh->alg_values);
+ GNUNET_free (rrh->url);
+ TALER_curl_easy_post_finished (&rrh->ctx);
+ TALER_EXCHANGE_free_melt_data_ (&rrh->md);
+ GNUNET_free (rrh);
+}
+
+
+/* exchange_api_refreshes_reveal.c */
diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c
new file mode 100644
index 000000000..7401bfe4f
--- /dev/null
+++ b/src/lib/exchange_api_refund.c
@@ -0,0 +1,480 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_refund.c
+ * @brief Implementation of the /refund request of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Refund Handle
+ */
+struct TALER_EXCHANGE_RefundHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_RefundCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Hash over the proposal data to identify the contract
+ * which is being refunded.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * The coin's public key. This is the value that must have been
+ * signed (blindly) by the Exchange.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * The Merchant's public key. Allows the merchant to later refund
+ * the transaction or to inquire about the wire transfer identifier.
+ */
+ struct TALER_MerchantPublicKeyP merchant;
+
+ /**
+ * Merchant-generated transaction ID for the refund.
+ */
+ uint64_t rtransaction_id;
+
+ /**
+ * Amount to be refunded, including refund fee charged by the
+ * exchange to the customer.
+ */
+ struct TALER_Amount refund_amount;
+
+};
+
+
+/**
+ * Verify that the signature on the "200 OK" response
+ * from the exchange is valid.
+ *
+ * @param[in,out] rh refund handle (refund fee added)
+ * @param json json reply with the signature
+ * @param[out] exchange_pub set to the exchange's public key
+ * @param[out] exchange_sig set to the exchange's signature
+ * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
+ */
+static enum GNUNET_GenericReturnValue
+verify_refund_signature_ok (struct TALER_EXCHANGE_RefundHandle *rh,
+ const json_t *json,
+ struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_ExchangeSignatureP *exchange_sig)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ exchange_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (rh->keys,
+ exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_refund_confirmation_verify (
+ &rh->h_contract_terms,
+ &rh->coin_pub,
+ &rh->merchant,
+ rh->rtransaction_id,
+ &rh->refund_amount,
+ exchange_pub,
+ exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Verify that the information on the "412 Dependency Failed" response
+ * from the exchange is valid and indeed shows that there is a refund
+ * transaction ID reuse going on.
+ *
+ * @param[in,out] rh refund handle (refund fee added)
+ * @param json json reply with the signature
+ * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
+ */
+static enum GNUNET_GenericReturnValue
+verify_failed_dependency_ok (struct TALER_EXCHANGE_RefundHandle *rh,
+ const json_t *json)
+{
+ const json_t *h;
+ json_t *e;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("history",
+ &h),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (1 != json_array_size (h))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ e = json_array_get (h, 0);
+ {
+ struct TALER_Amount amount;
+ const char *type;
+ struct TALER_MerchantSignatureP sig;
+ struct TALER_Amount refund_fee;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ uint64_t rtransaction_id;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct GNUNET_JSON_Specification ispec[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &amount),
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ TALER_JSON_spec_amount_any ("refund_fee",
+ &refund_fee),
+ GNUNET_JSON_spec_fixed_auto ("merchant_sig",
+ &sig),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &h_contract_terms),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &merchant_pub),
+ GNUNET_JSON_spec_uint64 ("rtransaction_id",
+ &rtransaction_id),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (e,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_merchant_refund_verify (&rh->coin_pub,
+ &h_contract_terms,
+ rtransaction_id,
+ &amount,
+ &merchant_pub,
+ &sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if ( (rtransaction_id != rh->rtransaction_id) ||
+ (0 != GNUNET_memcmp (&rh->h_contract_terms,
+ &h_contract_terms)) ||
+ (0 != GNUNET_memcmp (&rh->merchant,
+ &merchant_pub)) ||
+ (0 == TALER_amount_cmp (&rh->refund_amount,
+ &amount)) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /refund request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_RefundHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_refund_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_RefundHandle *rh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_RefundResponse rr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ rh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ verify_refund_signature_ok (rh,
+ j,
+ &rr.details.ok.exchange_pub,
+ &rr.details.ok.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ rr.hr.http_status = 0;
+ rr.hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_SIGNATURE_BY_EXCHANGE;
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); also can happen if the currency
+ differs (which we should obviously never support).
+ Just pass JSON reply to the application */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* 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 */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* Requested total refunds exceed deposited amount */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_GONE:
+ /* Kind of normal: the money was already sent to the merchant
+ (it was too late for the refund). */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_PRECONDITION_FAILED:
+ if (GNUNET_OK !=
+ verify_failed_dependency_ok (rh,
+ j))
+ {
+ GNUNET_break (0);
+ rr.hr.http_status = 0;
+ rr.hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE;
+ rr.hr.hint = "failed precondition proof returned by exchange is invalid";
+ break;
+ }
+ /* Two different refund requests were made about the same deposit, but
+ carrying identical refund transaction ids. */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange refund\n",
+ (unsigned int) response_code,
+ rr.hr.ec);
+ break;
+ }
+ rh->cb (rh->cb_cls,
+ &rr);
+ TALER_EXCHANGE_refund_cancel (rh);
+}
+
+
+struct TALER_EXCHANGE_RefundHandle *
+TALER_EXCHANGE_refund (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_Amount *amount,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t rtransaction_id,
+ const struct TALER_MerchantPrivateKeyP *merchant_priv,
+ TALER_EXCHANGE_RefundCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct TALER_MerchantSignatureP merchant_sig;
+ struct TALER_EXCHANGE_RefundHandle *rh;
+ json_t *refund_obj;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
+ &merchant_pub.eddsa_pub);
+ TALER_merchant_refund_sign (coin_pub,
+ h_contract_terms,
+ rtransaction_id,
+ amount,
+ merchant_priv,
+ &merchant_sig);
+ {
+ char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ coin_pub,
+ sizeof (struct TALER_CoinSpendPublicKeyP),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "coins/%s/refund",
+ pub_str);
+ }
+ refund_obj = GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("refund_amount",
+ amount),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ h_contract_terms),
+ GNUNET_JSON_pack_uint64 ("rtransaction_id",
+ rtransaction_id),
+ GNUNET_JSON_pack_data_auto ("merchant_pub",
+ &merchant_pub),
+ GNUNET_JSON_pack_data_auto ("merchant_sig",
+ &merchant_sig));
+ rh = GNUNET_new (struct TALER_EXCHANGE_RefundHandle);
+ rh->cb = cb;
+ rh->cb_cls = cb_cls;
+ rh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == rh->url)
+ {
+ json_decref (refund_obj);
+ GNUNET_free (rh);
+ return NULL;
+ }
+ rh->h_contract_terms = *h_contract_terms;
+ rh->coin_pub = *coin_pub;
+ rh->merchant = merchant_pub;
+ rh->rtransaction_id = rtransaction_id;
+ rh->refund_amount = *amount;
+ eh = TALER_EXCHANGE_curl_easy_get_ (rh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&rh->ctx,
+ eh,
+ refund_obj)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (refund_obj);
+ GNUNET_free (rh->url);
+ GNUNET_free (rh);
+ return NULL;
+ }
+ json_decref (refund_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for refund: `%s'\n",
+ rh->url);
+ rh->keys = TALER_EXCHANGE_keys_incref (keys);
+ rh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ rh->ctx.headers,
+ &handle_refund_finished,
+ rh);
+ return rh;
+}
+
+
+void
+TALER_EXCHANGE_refund_cancel (struct TALER_EXCHANGE_RefundHandle *refund)
+{
+ if (NULL != refund->job)
+ {
+ GNUNET_CURL_job_cancel (refund->job);
+ refund->job = NULL;
+ }
+ GNUNET_free (refund->url);
+ TALER_curl_easy_post_finished (&refund->ctx);
+ TALER_EXCHANGE_keys_decref (refund->keys);
+ GNUNET_free (refund);
+}
+
+
+/* end of exchange_api_refund.c */
diff --git a/src/lib/exchange_api_stefan.c b/src/lib/exchange_api_stefan.c
new file mode 100644
index 000000000..c3576cd73
--- /dev/null
+++ b/src/lib/exchange_api_stefan.c
@@ -0,0 +1,320 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_stefan.c
+ * @brief calculations on the STEFAN curve
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+#include <math.h>
+
+
+/**
+ * Determine smallest denomination in @a keys.
+ *
+ * @param keys exchange response to evaluate
+ * @return NULL on error (no denominations)
+ */
+static const struct TALER_Amount *
+get_unit (const struct TALER_EXCHANGE_Keys *keys)
+{
+ const struct TALER_Amount *min = NULL;
+
+ for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *dk
+ = &keys->denom_keys[i];
+
+ if ( (NULL == min) ||
+ (1 == TALER_amount_cmp (min,
+ /* > */
+ &dk->value)) )
+ min = &dk->value;
+ }
+ GNUNET_break (NULL != min);
+ return min;
+}
+
+
+/**
+ * Convert amount to double for STEFAN curve evaluation.
+ *
+ * @param a input amount
+ * @return (rounded) amount as a double
+ */
+static double
+amount_to_double (const struct TALER_Amount *a)
+{
+ double d = (double) a->value;
+
+ d += a->fraction / ((double) TALER_AMOUNT_FRAC_BASE);
+ return d;
+}
+
+
+/**
+ * Convert double to amount for STEFAN curve evaluation.
+ *
+ * @param dv input amount
+ * @param currency deisred currency
+ * @param[out] rval (rounded) amount as a double
+ */
+static void
+double_to_amount (double dv,
+ const char *currency,
+ struct TALER_Amount *rval)
+{
+ double rem;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (currency,
+ rval));
+ rval->value = floorl (dv);
+ rem = dv - ((double) rval->value);
+ if (rem < 0.0)
+ rem = 0.0;
+ rem *= TALER_AMOUNT_FRAC_BASE;
+ rval->fraction = floorl (rem);
+ if (rval->fraction >= TALER_AMOUNT_FRAC_BASE)
+ {
+ /* Strange, multiplication overflowed our range,
+ round up value instead */
+ rval->fraction = 0;
+ rval->value += 1;
+ }
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_keys_stefan_b2n (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_Amount *brut,
+ struct TALER_Amount *net)
+{
+ const struct TALER_Amount *min;
+ double log_d = amount_to_double (&keys->stefan_log);
+ double lin_d = keys->stefan_lin;
+ double abs_d = amount_to_double (&keys->stefan_abs);
+ double bru_d = amount_to_double (brut);
+ double min_d;
+ double fee_d;
+ double net_d;
+
+ if (TALER_amount_is_zero (brut))
+ {
+ *net = *brut;
+ return GNUNET_NO;
+ }
+ min = get_unit (keys);
+ if (NULL == min)
+ return GNUNET_SYSERR;
+ if (1.0f <= keys->stefan_lin)
+ {
+ /* This cannot work, linear STEFAN fee estimate always
+ exceed any gross amount. */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ min_d = amount_to_double (min);
+ fee_d = abs_d
+ + log_d * log2 (bru_d / min_d)
+ + lin_d * bru_d;
+ if (fee_d > bru_d)
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (brut->currency,
+ net));
+ return GNUNET_NO;
+ }
+ net_d = bru_d - fee_d;
+ double_to_amount (net_d,
+ brut->currency,
+ net);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Our function
+ * f(x) := ne + ab + lo * log2(x/mi) + li * x - x
+ * for #newton().
+ */
+static double
+eval_f (double mi,
+ double ab,
+ double lo,
+ double li,
+ double ne,
+ double x)
+{
+ return ne + ab + lo * log2 (x / mi) + li * x - x;
+}
+
+
+/**
+ * Our function
+ * f'(x) := lo / log(2) / x + li - 1
+ * for #newton().
+ */
+static double
+eval_fp (double mi,
+ double lo,
+ double li,
+ double ne,
+ double x)
+{
+ return lo / log (2) / x + li - 1;
+}
+
+
+/**
+ * Use Newton's method to find x where f(x)=0.
+ *
+ * @return x where "eval_f(x)==0".
+ */
+static double
+newton (double mi,
+ double ab,
+ double lo,
+ double li,
+ double ne)
+{
+ const double eps = 0.00000001; /* max error allowed */
+ double min_ab = ne + ab; /* result cannot be smaller than this! */
+ /* compute lower bounds by various heuristics */
+ double min_ab_li = min_ab + min_ab * li;
+ double min_ab_li_lo = min_ab_li + log2 (min_ab_li / mi) * lo;
+ double min_ab_lo = min_ab + log2 (min_ab / mi) * lo;
+ double min_ab_lo_li = min_ab_lo + min_ab_lo * li;
+ /* take global lower bound */
+ double x_min = GNUNET_MAX (min_ab_lo_li,
+ min_ab_li_lo);
+ double x = x_min; /* use lower bound as starting point */
+
+ /* Objective: invert
+ ne := br - ab - lo * log2 (br/mi) - li * br
+ to find 'br'.
+ Method: use Newton's method to find root of:
+ f(x) := ne + ab + lo * log2 (x/mi) + li * x - x
+ using also
+ f'(x) := lo / log(2) / x + li - 1
+ */
+ /* Loop to abort in case of divergence;
+ 100 is already very high, 2-4 is normal! */
+ for (unsigned int i = 0; i<100; i++)
+ {
+ double fx = eval_f (mi, ab, lo, li, ne, x);
+ double fxp = eval_fp (mi, lo, li, ne, x);
+ double x_new = x - fx / fxp;
+
+ if (fabs (x - x_new) <= eps)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Needed %u rounds from %f to result BRUT %f => NET: %f\n",
+ i,
+ x_min,
+ x_new,
+ x_new - ab - li * x_new - lo * log2 (x / mi));
+ return x_new;
+ }
+ if (x_new < x_min)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Divergence, obtained very bad estimate %f after %u rounds!\n",
+ x_new,
+ i);
+ return x_min;
+ }
+ x = x_new;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Slow convergence, returning bad estimate %f!\n",
+ x);
+ return x;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_keys_stefan_n2b (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_Amount *net,
+ struct TALER_Amount *brut)
+{
+ const struct TALER_Amount *min;
+ double lin_d = keys->stefan_lin;
+ double log_d = amount_to_double (&keys->stefan_log);
+ double abs_d = amount_to_double (&keys->stefan_abs);
+ double net_d = amount_to_double (net);
+ double min_d;
+ double brut_d;
+
+ if (TALER_amount_is_zero (net))
+ {
+ *brut = *net;
+ return GNUNET_NO;
+ }
+ min = get_unit (keys);
+ if (NULL == min)
+ return GNUNET_SYSERR;
+ if (1.0f <= keys->stefan_lin)
+ {
+ /* This cannot work, linear STEFAN fee estimate always
+ exceed any gross amount. */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ min_d = amount_to_double (min);
+ brut_d = newton (min_d,
+ abs_d,
+ log_d,
+ lin_d,
+ net_d);
+ double_to_amount (brut_d,
+ net->currency,
+ brut);
+ return GNUNET_OK;
+}
+
+
+void
+TALER_EXCHANGE_keys_stefan_round (
+ const struct TALER_EXCHANGE_Keys *keys,
+ struct TALER_Amount *val)
+{
+ const struct TALER_Amount *min;
+ uint32_t mod = 1;
+ uint32_t frac;
+ uint32_t rst;
+
+ min = get_unit (keys);
+ if (NULL == min)
+ return;
+ frac = min->fraction;
+ while (0 == frac % 10)
+ {
+ mod *= 10;
+ frac /= 10;
+ }
+ rst = val->fraction % mod;
+ if (rst < mod / 2)
+ val->fraction -= rst;
+ else
+ val->fraction += mod - rst;
+}
diff --git a/src/lib/test_stefan.c b/src/lib/test_stefan.c
new file mode 100644
index 000000000..838cca769
--- /dev/null
+++ b/src/lib/test_stefan.c
@@ -0,0 +1,206 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/test_stefan.c
+ * @brief test calculations on the STEFAN curve
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+
+
+/**
+ * Check if @a a and @a b are numerically close.
+ *
+ * @param a an amount
+ * @param b an amount
+ * @return true if both values are quite close
+ */
+static bool
+amount_close (const struct TALER_Amount *a,
+ const struct TALER_Amount *b)
+{
+ struct TALER_Amount delta;
+
+ switch (TALER_amount_cmp (a,
+ b))
+ {
+ case -1: /* a < b */
+ GNUNET_assert (0 <
+ TALER_amount_subtract (&delta,
+ b,
+ a));
+ break;
+ case 0:
+ /* perfect */
+ return true;
+ case 1: /* a > b */
+ GNUNET_assert (0 <
+ TALER_amount_subtract (&delta,
+ a,
+ b));
+ break;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Rounding error is %s\n",
+ TALER_amount2s (&delta));
+ if (delta.value > 0)
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ if (delta.fraction > 5000)
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ return true; /* let's consider this a rounding error */
+}
+
+
+int
+main (int argc,
+ char **argv)
+{
+ struct TALER_EXCHANGE_DenomPublicKey dk;
+ struct TALER_EXCHANGE_Keys keys = {
+ .denom_keys = &dk,
+ .num_denom_keys = 1
+ };
+ struct TALER_Amount brut;
+ struct TALER_Amount net;
+
+ (void) argc;
+ (void) argv;
+ GNUNET_log_setup ("test-stefan",
+ "INFO",
+ NULL);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:0.13",
+ &dk.value));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:1",
+ &keys.stefan_abs));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:0.13",
+ &keys.stefan_log));
+ keys.stefan_lin = 1.15;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:4",
+ &brut));
+ GNUNET_log_skip (1,
+ GNUNET_NO);
+ GNUNET_assert (GNUNET_SYSERR ==
+ TALER_EXCHANGE_keys_stefan_b2n (&keys,
+ &brut,
+ &net));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:4",
+ &net));
+ GNUNET_log_skip (1,
+ GNUNET_NO);
+ GNUNET_assert (GNUNET_SYSERR ==
+ TALER_EXCHANGE_keys_stefan_n2b (&keys,
+ &net,
+ &brut));
+ keys.stefan_lin = 1.0;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:4",
+ &brut));
+ GNUNET_log_skip (1,
+ GNUNET_NO);
+ GNUNET_assert (GNUNET_SYSERR ==
+ TALER_EXCHANGE_keys_stefan_b2n (&keys,
+ &brut,
+ &net));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:4",
+ &net));
+ GNUNET_log_skip (1,
+ GNUNET_NO);
+ GNUNET_assert (GNUNET_SYSERR ==
+ TALER_EXCHANGE_keys_stefan_n2b (&keys,
+ &net,
+ &brut));
+ GNUNET_assert (0 == GNUNET_get_log_skip ());
+ keys.stefan_lin = 0.1;
+
+ /* try various values for lin and log STEFAN values */
+ for (unsigned int li = 1; li < 13; li += 1)
+ {
+ keys.stefan_lin = 1.0 * li / 100.0;
+
+ for (unsigned int lx = 1; lx < 100; lx += 1)
+ {
+ keys.stefan_log.fraction = lx * TALER_AMOUNT_FRAC_BASE / 100;
+
+ /* Check brutto-to-netto is stable */
+ for (unsigned int i = 0; i<10; i++)
+ {
+ struct TALER_Amount rval;
+
+ brut.value = i;
+ brut.fraction = i * TALER_AMOUNT_FRAC_BASE / 10;
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_EXCHANGE_keys_stefan_b2n (&keys,
+ &brut,
+ &net));
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_EXCHANGE_keys_stefan_n2b (&keys,
+ &net,
+ &rval));
+ if (TALER_amount_is_zero (&net))
+ GNUNET_assert (TALER_amount_is_zero (&rval));
+ else
+ {
+ GNUNET_assert (amount_close (&brut,
+ &rval));
+ TALER_EXCHANGE_keys_stefan_round (&keys,
+ &rval);
+ GNUNET_assert (amount_close (&brut,
+ &rval));
+ }
+ }
+
+ /* Check netto-to-brutto is stable */
+ for (unsigned int i = 0; i<10; i++)
+ {
+ struct TALER_Amount rval;
+
+ net.value = i;
+ net.fraction = i * TALER_AMOUNT_FRAC_BASE / 10;
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_EXCHANGE_keys_stefan_n2b (&keys,
+ &net,
+ &brut));
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_EXCHANGE_keys_stefan_b2n (&keys,
+ &brut,
+ &rval));
+ GNUNET_assert (amount_close (&net,
+ &rval));
+ TALER_EXCHANGE_keys_stefan_round (&keys,
+ &rval);
+ GNUNET_assert (amount_close (&net,
+ &rval));
+ }
+ }
+ }
+ return 0;
+}