commit d6507a9a86198a4b2f1f884d6fd630aa4a3b921b
parent ca846b09671c742b2f8275bbb9f1a975efdebc0f
Author: Christian Grothoff <grothoff@gnu.org>
Date: Sun, 22 Feb 2026 21:46:14 +0100
rename src/lib/ files to match exchange_api_$METHOD-$ENDPOINT convention
Rename all exchange API source files in src/lib/ to follow the naming
convention established by the headers in src/include/taler/taler-exchange/,
using the pattern exchange_api_$METHOD-$ENDPOINT.c. Update @file comments
and Makefile.am accordingly.
Diffstat:
101 files changed, 21769 insertions(+), 21769 deletions(-)
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
@@ -21,63 +21,63 @@ libtalerexchange_la_LDFLAGS = \
-version-info 18:0:0 \
-no-undefined
libtalerexchange_la_SOURCES = \
- exchange_api_add_aml_decision.c \
- exchange_api_aml_legitimizations_get.c \
- exchange_api_auditor_add_denomination.c \
- exchange_api_batch_deposit.c \
- exchange_api_blinding_prepare.c \
- exchange_api_coins_history.c \
+ exchange_api_delete-purses-PURSE_PUB.c \
+ exchange_api_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c \
+ exchange_api_get-aml-OFFICER_PUB-decisions.c \
+ exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c \
+ exchange_api_get-aml-OFFICER_PUB-legitimizations.c \
+ exchange_api_get-aml-OFFICER_PUB-measures.c \
+ exchange_api_get-coins-COIN_PUB-history.c \
+ exchange_api_get-contracts-CONTRACT_PUB.c \
+ exchange_api_get-deposits-H_WIRE-MERCHANT_PUB-H_CONTRACT_TERMS-COIN_PUB.c \
+ exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c \
+ exchange_api_get-kyc-info-ACCESS_TOKEN.c \
+ exchange_api_get-kyc-proof-PROVIDER_NAME.c \
+ exchange_api_get-management-keys.c \
+ exchange_api_get-purses-PURSE_PUB-merge.c \
+ exchange_api_get-reserves-attest-RESERVE_PUB.c \
+ exchange_api_get-reserves-RESERVE_PUB.c \
+ exchange_api_get-reserves-RESERVE_PUB-history.c \
+ exchange_api_get-transfers-WTID.c \
exchange_api_common.c exchange_api_common.h \
- exchange_api_contracts_get.c \
exchange_api_curl_defaults.c exchange_api_curl_defaults.h \
- exchange_api_deposits_get.c \
- exchange_api_get_aml_measures.c \
- exchange_api_get_kyc_statistics.c \
exchange_api_handle.c exchange_api_handle.h \
- exchange_api_kyc_check.c \
- exchange_api_kyc_info.c \
- exchange_api_kyc_proof.c \
- exchange_api_kyc_start.c \
- exchange_api_kyc_wallet.c \
- exchange_api_lookup_aml_decisions.c \
- exchange_api_lookup_kyc_attributes.c \
- exchange_api_management_add_partner.c \
- exchange_api_management_auditor_disable.c \
- exchange_api_management_auditor_enable.c \
- exchange_api_management_drain_profits.c \
- exchange_api_management_get_keys.c \
- exchange_api_management_post_extensions.c \
- exchange_api_management_post_keys.c \
- exchange_api_management_revoke_denomination_key.c \
- exchange_api_management_revoke_signing_key.c \
- exchange_api_management_set_global_fee.c \
- exchange_api_management_set_wire_fee.c \
+ exchange_api_post-aml-OFFICER_PUB-decision.c \
+ exchange_api_post-auditors-AUDITOR_PUB-H_DENOM_PUB.c \
+ exchange_api_post-batch-deposit.c \
+ exchange_api_post-blinding-prepare.c \
+ exchange_api_post-coins-COIN_PUB-refund.c \
+ exchange_api_post-kyc-start-ID.c \
+ exchange_api_post-kyc-wallet.c \
exchange_api_post-management-aml-officers.c \
- exchange_api_management_wire_disable.c \
- exchange_api_management_wire_enable.c \
- exchange_api_melt.c \
- exchange_api_purse_create_with_deposit.c \
- exchange_api_purse_create_with_merge.c \
- exchange_api_purse_delete.c \
- exchange_api_purse_deposit.c \
- exchange_api_purse_merge.c \
- exchange_api_purses_get.c \
- exchange_api_recoup.c \
- exchange_api_recoup_refresh.c \
+ exchange_api_post-management-auditors-AUDITOR_PUB-disable.c \
+ exchange_api_post-management-auditors.c \
+ exchange_api_post-management-denominations-H_DENOM_PUB-revoke.c \
+ exchange_api_post-management-drain.c \
+ exchange_api_post-management-extensions.c \
+ exchange_api_post-management-global-fees.c \
+ exchange_api_post-management-keys.c \
+ exchange_api_post-management-partners.c \
+ exchange_api_post-management-signkeys-EXCHANGE_PUB-revoke.c \
+ exchange_api_post-management-wire-disable.c \
+ exchange_api_post-management-wire-fee.c \
+ exchange_api_post-management-wire.c \
+ exchange_api_post-melt.c \
+ exchange_api_post-purses-PURSE_PUB-create.c \
+ exchange_api_post-purses-PURSE_PUB-deposit.c \
+ exchange_api_post-purses-PURSE_PUB-merge.c \
+ exchange_api_post-recoup-refresh.c \
+ exchange_api_post-recoup-withdraw.c \
+ exchange_api_post-reserves-attest-RESERVE_PUB.c \
+ exchange_api_post-reserves-RESERVE_PUB-close.c \
+ exchange_api_post-reserves-RESERVE_PUB-open.c \
+ exchange_api_post-reserves-RESERVE_PUB-purse.c \
+ exchange_api_post-reveal-melt.c \
+ exchange_api_post-reveal-withdraw.c \
+ exchange_api_post-withdraw.c \
exchange_api_refresh_common.c exchange_api_refresh_common.h \
- exchange_api_refund.c \
- exchange_api_reserves_attest.c \
- exchange_api_reserves_close.c \
- exchange_api_reserves_get.c \
- exchange_api_reserves_get_attestable.c \
- exchange_api_reserves_history.c \
- exchange_api_reserves_open.c \
exchange_api_restrictions.c \
- exchange_api_reveal_withdraw.c \
- exchange_api_reveal_melt.c \
- exchange_api_stefan.c \
- exchange_api_transfers_get.c \
- exchange_api_withdraw.c
+ exchange_api_stefan.c
libtalerexchange_la_LIBADD = \
libtalerauditor.la \
diff --git a/src/lib/exchange_api_add_aml_decision.c b/src/lib/exchange_api_add_aml_decision.c
@@ -1,350 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2023, 2024 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_add_aml_decision.c
- * @brief functions to add an AML decision by an AML officer
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include <microhttpd.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_curl_defaults.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_curl_lib.h"
-#include "taler/taler_json_lib.h"
-
-
-struct TALER_EXCHANGE_AddAmlDecision
-{
-
- /**
- * 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_AddAmlDecisionCallback 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 /aml/$OFFICER_PUB/decision request.
- *
- * @param cls the `struct TALER_EXCHANGE_AddAmlDecision *`
- * @param response_code HTTP response code, 0 on error
- * @param response response body, NULL if not in JSON
- */
-static void
-handle_add_aml_decision_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_AddAmlDecision *wh = cls;
- const json_t *json = response;
- struct TALER_EXCHANGE_AddAmlDecisionResponse adr = {
- .hr.http_status = (unsigned int) response_code,
- .hr.reply = json
- };
-
- wh->job = NULL;
- switch (response_code)
- {
- case 0:
- /* no reply */
- adr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- adr.hr.hint = "server offline?";
- break;
- 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_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 AML decision\n",
- (unsigned int) response_code,
- (int) adr.hr.ec);
- break;
- }
- if (NULL != wh->cb)
- {
- wh->cb (wh->cb_cls,
- &adr);
- wh->cb = NULL;
- }
- TALER_EXCHANGE_post_aml_decision_cancel (wh);
-}
-
-
-struct TALER_EXCHANGE_AddAmlDecision *
-TALER_EXCHANGE_post_aml_decision (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- const struct TALER_NormalizedPaytoHashP *h_payto,
- const struct TALER_FullPayto payto_uri,
- struct GNUNET_TIME_Timestamp decision_time,
- const char *successor_measure,
- const char *new_measures,
- struct GNUNET_TIME_Timestamp expiration_time,
- unsigned int num_rules,
- const struct TALER_EXCHANGE_AccountRule *rules,
- unsigned int num_measures,
- const struct TALER_EXCHANGE_MeasureInformation *measures,
- const json_t *properties,
- bool keep_investigating,
- const char *justification,
- const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
- unsigned int num_events,
- const char **events,
- TALER_EXCHANGE_AddAmlDecisionCallback cb,
- void *cb_cls)
-{
- struct TALER_AmlOfficerPublicKeyP officer_pub;
- struct TALER_AmlOfficerSignatureP officer_sig;
- struct TALER_EXCHANGE_AddAmlDecision *wh;
- CURL *eh;
- json_t *body;
- json_t *new_rules;
- json_t *jrules;
- json_t *jmeasures;
- json_t *jevents = NULL;
-
- if (0 != num_events)
- {
- jevents = json_array ();
- GNUNET_assert (NULL != jevents);
- for (unsigned int i = 0; i<num_events; i++)
- GNUNET_assert (0 ==
- json_array_append_new (jevents,
- json_string (events[i])));
- }
- jrules = json_array ();
- GNUNET_assert (NULL != jrules);
- for (unsigned int i = 0; i<num_rules; i++)
- {
- const struct TALER_EXCHANGE_AccountRule *al = &rules[i];
- json_t *rule;
- json_t *ameasures;
-
- ameasures = json_array ();
- GNUNET_assert (NULL != ameasures);
- for (unsigned int j = 0; j<al->num_measures; j++)
- GNUNET_assert (0 ==
- json_array_append_new (ameasures,
- json_string (al->measures[j])));
- rule = GNUNET_JSON_PACK (
- TALER_JSON_pack_kycte ("operation_type",
- al->operation_type),
- TALER_JSON_pack_amount ("threshold",
- &al->threshold),
- GNUNET_JSON_pack_time_rel ("timeframe",
- al->timeframe),
- GNUNET_JSON_pack_array_steal ("measures",
- ameasures),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_array_steal ("events",
- jevents)),
- GNUNET_JSON_pack_bool ("exposed",
- al->exposed),
- GNUNET_JSON_pack_bool ("is_and_combinator",
- al->is_and_combinator),
- GNUNET_JSON_pack_uint64 ("display_priority",
- al->display_priority)
- );
- GNUNET_break (0 ==
- json_array_append_new (jrules,
- rule));
- }
-
- jmeasures = json_object ();
- GNUNET_assert (NULL != jmeasures);
- for (unsigned int i = 0; i<num_measures; i++)
- {
- const struct TALER_EXCHANGE_MeasureInformation *mi = &measures[i];
- json_t *measure;
-
- measure = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("check_name",
- mi->check_name),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("prog_name",
- mi->prog_name)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref ("context",
- (json_t *) mi->context))
- );
- GNUNET_break (0 ==
- json_object_set_new (jmeasures,
- mi->measure_name,
- measure));
- }
-
- new_rules = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_timestamp ("expiration_time",
- expiration_time),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("successor_measure",
- successor_measure)),
- GNUNET_JSON_pack_array_steal ("rules",
- jrules),
- GNUNET_JSON_pack_object_steal ("custom_measures",
- jmeasures)
- );
-
- GNUNET_CRYPTO_eddsa_key_get_public (
- &officer_priv->eddsa_priv,
- &officer_pub.eddsa_pub);
- TALER_officer_aml_decision_sign (justification,
- decision_time,
- h_payto,
- new_rules,
- properties,
- new_measures,
- keep_investigating,
- officer_priv,
- &officer_sig);
- wh = GNUNET_new (struct TALER_EXCHANGE_AddAmlDecision);
- wh->cb = cb;
- wh->cb_cls = cb_cls;
- wh->ctx = ctx;
- {
- char *path;
- char opus[sizeof (officer_pub) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- &officer_pub,
- sizeof (officer_pub),
- opus,
- sizeof (opus));
- *end = '\0';
- GNUNET_asprintf (&path,
- "aml/%s/decision",
- opus);
- wh->url = TALER_url_join (url,
- path,
- NULL);
- GNUNET_free (path);
- }
- if (NULL == wh->url)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not construct request URL.\n");
- GNUNET_free (wh);
- json_decref (new_rules);
- return NULL;
- }
- body = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("justification",
- justification),
- GNUNET_JSON_pack_data_auto ("h_payto",
- h_payto),
- GNUNET_JSON_pack_allow_null (
- TALER_JSON_pack_full_payto ("payto_uri",
- payto_uri)),
- GNUNET_JSON_pack_object_steal ("new_rules",
- new_rules),
- GNUNET_JSON_pack_object_incref ("properties",
- (json_t *) properties),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("new_measures",
- new_measures)),
- GNUNET_JSON_pack_bool ("keep_investigating",
- keep_investigating),
- GNUNET_JSON_pack_data_auto ("officer_sig",
- &officer_sig),
- GNUNET_JSON_pack_timestamp ("decision_time",
- decision_time));
- 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_aml_decision_finished,
- wh);
- if (NULL == wh->job)
- {
- TALER_EXCHANGE_post_aml_decision_cancel (wh);
- return NULL;
- }
- return wh;
-}
-
-
-void
-TALER_EXCHANGE_post_aml_decision_cancel (
- struct TALER_EXCHANGE_AddAmlDecision *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_aml_legitimizations_get.c b/src/lib/exchange_api_aml_legitimizations_get.c
@@ -1,510 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2025 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_aml_legitimizations_get.c
- * @brief Implementation of the GET /aml/$OFFICER_PUB/legitimizations requests
- * @author Christian Grothoff
- */
-#include "taler/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/taler_exchange_service.h"
-#include "taler/taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * Handle for an operation to GET /aml/$OFFICER_PUB/legitimizations.
- */
-struct TALER_EXCHANGE_GetAmlLegitimizationsHandle
-{
-
- /**
- * The exchange base URL for this request.
- */
- char *exchange_base_url;
-
- /**
- * Our execution context.
- */
- struct GNUNET_CURL_Context *ctx;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Signature of the AML officer.
- */
- struct TALER_AmlOfficerSignatureP officer_sig;
-
- /**
- * Public key of the AML officer.
- */
- struct TALER_AmlOfficerPublicKeyP officer_pub;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_GetAmlLegitimizationsCallback cb;
-
- /**
- * Closure for @a cb.
- */
- TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_RESULT_CLOSURE *cb_cls;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * HTTP headers for the job.
- */
- struct curl_slist *job_headers;
-
- /**
- * Request options.
- */
- struct
- {
- /**
- * Limit on number of results.
- */
- int64_t limit;
-
- /**
- * Row offset from which to return results.
- */
- uint64_t offset;
-
- /**
- * Hash of payto URI to filter by, NULL for no filter.
- */
- const struct TALER_NormalizedPaytoHashP *h_payto;
-
- /**
- * Activity filter.
- */
- enum TALER_EXCHANGE_YesNoAll active;
-
- } options;
-
-};
-
-
-/**
- * Parse a single measure details entry from JSON.
- *
- * @param md_json JSON object to parse
- * @param[out] md where to store the result
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_measure_details (
- const json_t *md_json,
- struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails *md)
-{
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("h_payto",
- &md->h_payto),
- GNUNET_JSON_spec_uint64 ("rowid",
- &md->rowid),
- GNUNET_JSON_spec_timestamp ("start_time",
- &md->start_time),
- GNUNET_JSON_spec_object_const ("measures",
- &md->measures),
- GNUNET_JSON_spec_bool ("is_finished",
- &md->is_finished),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (md_json,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * We received an #MHD_HTTP_OK status code. Handle the JSON
- * response.
- *
- * @param algh handle of the request
- * @param j JSON response
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_aml_legitimizations_get_ok (
- struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh,
- const json_t *j)
-{
- struct TALER_EXCHANGE_GetAmlLegitimizationsResponse result = {
- .hr.reply = j,
- .hr.http_status = MHD_HTTP_OK
- };
- const json_t *measures_array;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_array_const ("measures",
- &measures_array),
- GNUNET_JSON_spec_end ()
- };
- struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails *measures;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- result.details.ok.measures_length = json_array_size (measures_array);
- if (0 == result.details.ok.measures_length)
- {
- measures = NULL;
- }
- else
- {
- measures
- = GNUNET_new_array (
- result.details.ok.measures_length,
- struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails);
- }
- for (size_t i = 0; i < result.details.ok.measures_length; i++)
- {
- const json_t *measure_json = json_array_get (measures_array,
- i);
-
- if (GNUNET_OK !=
- parse_measure_details (measure_json,
- &measures[i]))
- {
- GNUNET_free (measures);
- return GNUNET_SYSERR;
- }
- }
- result.details.ok.measures = measures;
- algh->cb (algh->cb_cls,
- &result);
- algh->cb = NULL;
- GNUNET_free (measures);
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /aml/$OFFICER_PUB/legitimizations GET request.
- *
- * @param cls the `struct TALER_EXCHANGE_GetAmlLegitimizationsHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_aml_legitimizations_get_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_GetAmlLegitimizationsResponse result = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
-
- algh->job = NULL;
- switch (response_code)
- {
- case 0:
- result.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- if (GNUNET_OK !=
- handle_aml_legitimizations_get_ok (algh,
- j))
- {
- result.hr.http_status = 0;
- result.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- }
- break;
- case MHD_HTTP_NO_CONTENT:
- /* can happen */
- 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 */
- result.hr.ec = TALER_JSON_get_error_code (j);
- result.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_UNAUTHORIZED:
- /* Invalid officer credentials */
- result.hr.ec = TALER_JSON_get_error_code (j);
- result.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_FORBIDDEN:
- /* Officer not authorized for this operation */
- result.hr.ec = TALER_JSON_get_error_code (j);
- result.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_NOT_FOUND:
- /* Officer not found */
- result.hr.ec = TALER_JSON_get_error_code (j);
- result.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 */
- result.hr.ec = TALER_JSON_get_error_code (j);
- result.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- result.hr.ec = TALER_JSON_get_error_code (j);
- result.hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for GET %s\n",
- (unsigned int) response_code,
- (int) result.hr.ec,
- algh->url);
- break;
- }
- if (NULL != algh->cb)
- {
- algh->cb (algh->cb_cls,
- &result);
- algh->cb = NULL;
- }
- TALER_EXCHANGE_get_aml_legitimizations_cancel (algh);
-}
-
-
-struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *
-TALER_EXCHANGE_get_aml_legitimizations_create (
- struct GNUNET_CURL_Context *ctx,
- const char *exchange_base_url,
- const struct TALER_AmlOfficerPrivateKeyP *officer_priv)
-{
- struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh;
-
- algh = GNUNET_new (struct TALER_EXCHANGE_GetAmlLegitimizationsHandle);
- algh->ctx = ctx;
- algh->exchange_base_url = GNUNET_strdup (exchange_base_url);
- GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
- &algh->officer_pub.eddsa_pub);
- TALER_officer_aml_query_sign (officer_priv,
- &algh->officer_sig);
- algh->options.limit = -20; /* Default to last 20 entries */
- algh->options.offset = INT64_MAX; /* Default to maximum row id */
- algh->options.active = TALER_EXCHANGE_YNA_ALL; /* Default to all */
- return algh;
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_get_aml_legitimizations_set_options_ (
- struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh,
- unsigned int num_options,
- const struct TALER_EXCHANGE_GetAmlLegitimizationsOptionValue *options)
-{
- for (unsigned int i = 0; i < num_options; i++)
- {
- switch (options[i].option)
- {
- case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_END:
- return GNUNET_OK;
- case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_LIMIT:
- algh->options.limit = options[i].details.limit;
- break;
- case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_OFFSET:
- algh->options.offset = options[i].details.offset;
- break;
- case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_H_PAYTO:
- algh->options.h_payto = options[i].details.h_payto;
- break;
- case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_ACTIVE:
- algh->options.active = options[i].details.active;
- break;
- default:
- GNUNET_break (0);
- return GNUNET_NO;
- }
- }
- return GNUNET_OK;
-}
-
-
-enum TALER_EXCHANGE_AmlLegitimizationsGetStartError
-TALER_EXCHANGE_get_aml_legitimizations_start (
- struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh,
- TALER_EXCHANGE_GetAmlLegitimizationsCallback cb,
- TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_RESULT_CLOSURE *cb_cls)
-{
- char officer_pub_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2];
- char arg_str[sizeof (officer_pub_str) + 64];
- char limit_str[24];
- char offset_str[24];
- char paytoh_str[sizeof (struct TALER_NormalizedPaytoHashP) * 2];
-
- if (NULL != algh->job)
- {
- GNUNET_break (0);
- return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_E_AGAIN;
- }
- algh->cb = cb;
- algh->cb_cls = cb_cls;
- if (algh->options.offset > INT64_MAX)
- {
- GNUNET_break (0);
- algh->options.offset = INT64_MAX;
- }
- {
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- &algh->officer_pub,
- sizeof (algh->officer_pub),
- officer_pub_str,
- sizeof (officer_pub_str));
- *end = '\0';
- }
- if (NULL != algh->options.h_payto)
- {
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- algh->options.h_payto,
- sizeof (struct TALER_NormalizedPaytoHashP),
- paytoh_str,
- sizeof (paytoh_str));
- *end = '\0';
- }
- /* Build query parameters */
- GNUNET_snprintf (offset_str,
- sizeof (offset_str),
- "%llu",
- (unsigned long long) algh->options.offset);
- GNUNET_snprintf (limit_str,
- sizeof (limit_str),
- "%lld",
- (long long) algh->options.limit);
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "aml/%s/legitimizations",
- officer_pub_str);
- algh->url = TALER_url_join (algh->exchange_base_url,
- arg_str,
- "limit",
- limit_str,
- "offset",
- ( (algh->options.limit > 0) &&
- (0 == algh->options.offset) ) ||
- ( (algh->options.limit <= 0) &&
- (INT64_MAX <= algh->options.offset) )
- ? NULL
- : offset_str,
- "h_payto",
- NULL == algh->options.h_payto
- ? NULL
- : paytoh_str,
- "active",
- TALER_EXCHANGE_YNA_ALL == algh->options.active
- ? NULL
- : TALER_yna_to_string (algh->options.active),
- NULL);
- if (NULL == algh->url)
- {
- GNUNET_break (0);
- return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_E_INTERNAL;
- }
-
- {
- CURL *eh;
-
- eh = TALER_EXCHANGE_curl_easy_get_ (algh->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- GNUNET_free (algh->url);
- algh->url = NULL;
- return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_E_INTERNAL;
- }
-
- /* Add authentication header for AML officer */
- {
- char *hdr;
- char sig_str[sizeof (algh->officer_sig) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- &algh->officer_sig,
- sizeof (algh->officer_sig),
- sig_str,
- sizeof (sig_str));
- *end = '\0';
- GNUNET_asprintf (&hdr,
- "%s: %s",
- TALER_AML_OFFICER_SIGNATURE_HEADER,
- sig_str);
- algh->job_headers = curl_slist_append (NULL,
- hdr);
- GNUNET_free (hdr);
- }
- algh->job
- = GNUNET_CURL_job_add2 (
- algh->ctx,
- eh,
- algh->job_headers,
- &handle_aml_legitimizations_get_finished,
- algh);
- }
- return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_OK;
-}
-
-
-void
-TALER_EXCHANGE_get_aml_legitimizations_cancel (
- struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh)
-{
- if (NULL != algh->job)
- {
- GNUNET_CURL_job_cancel (algh->job);
- algh->job = NULL;
- }
- curl_slist_free_all (algh->job_headers);
- GNUNET_free (algh->exchange_base_url);
- GNUNET_free (algh->url);
- GNUNET_free (algh);
-}
-
-
-/* end of exchange_api_aml_legitimizations_get.c */
diff --git a/src/lib/exchange_api_auditor_add_denomination.c b/src/lib/exchange_api_auditor_add_denomination.c
@@ -1,239 +0,0 @@
-/*
- 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 "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include <microhttpd.h>
-#include "taler/taler_exchange_service.h"
-#include "auditor_api_curl_defaults.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_curl_lib.h"
-#include "taler/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_batch_deposit.c b/src/lib/exchange_api_batch_deposit.c
@@ -1,820 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2024 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_batch_deposit.c
- * @brief Implementation of the /batch-deposit request of the exchange's HTTP API
- * @author Sree Harsha Totakura <sreeharsha@totakura.in>
- * @author Christian Grothoff
- */
-#include "taler/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/taler_json_lib.h"
-#include "taler/taler_auditor_service.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_common.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * 1:#AUDITOR_CHANCE is the probability that we report deposits
- * to the auditor.
- *
- * 20==5% of going to auditor. This is possibly still too high, but set
- * deliberately this high for testing
- */
-#define AUDITOR_CHANCE 20
-
-
-/**
- * Entry in list of ongoing interactions with an auditor.
- */
-struct TEAH_AuditorInteractionEntry
-{
- /**
- * DLL entry.
- */
- struct TEAH_AuditorInteractionEntry *next;
-
- /**
- * DLL entry.
- */
- struct TEAH_AuditorInteractionEntry *prev;
-
- /**
- * URL of our auditor. For logging.
- */
- const char *auditor_url;
-
- /**
- * Interaction state.
- */
- struct TALER_AUDITOR_DepositConfirmationHandle *dch;
-
- /**
- * Batch deposit this is for.
- */
- struct TALER_EXCHANGE_BatchDepositHandle *dh;
-};
-
-
-/**
- * @brief A Deposit Handle
- */
-struct TALER_EXCHANGE_BatchDepositHandle
-{
-
- /**
- * The keys of the exchange.
- */
- struct TALER_EXCHANGE_Keys *keys;
-
- /**
- * Context for our curl request(s).
- */
- struct GNUNET_CURL_Context *ctx;
-
- /**
- * 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 post_ctx;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_BatchDepositResultCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Details about the contract.
- */
- struct TALER_EXCHANGE_DepositContractDetail dcd;
-
- /**
- * Array with details about the coins.
- */
- struct TALER_EXCHANGE_CoinDepositDetail *cdds;
-
- /**
- * Hash of the merchant's wire details.
- */
- struct TALER_MerchantWireHashP h_wire;
-
- /**
- * Hash over the extensions, or all zero.
- */
- struct TALER_ExtensionPolicyHashP h_policy;
-
- /**
- * Time when this confirmation was generated / when the exchange received
- * the deposit request.
- */
- struct GNUNET_TIME_Timestamp exchange_timestamp;
-
- /**
- * Exchange signature, set for #auditor_cb.
- */
- struct TALER_ExchangeSignatureP exchange_sig;
-
- /**
- * Head of DLL of interactions with this auditor.
- */
- struct TEAH_AuditorInteractionEntry *ai_head;
-
- /**
- * Tail of DLL of interactions with this auditor.
- */
- struct TEAH_AuditorInteractionEntry *ai_tail;
-
- /**
- * Result to return to the application once @e ai_head is empty.
- */
- struct TALER_EXCHANGE_BatchDepositResult dr;
-
- /**
- * Exchange signing public key, set for #auditor_cb.
- */
- struct TALER_ExchangePublicKeyP exchange_pub;
-
- /**
- * Total amount deposited without fees as calculated by us.
- */
- struct TALER_Amount total_without_fee;
-
- /**
- * Response object to free at the end.
- */
- json_t *response;
-
- /**
- * Chance that we will inform the auditor about the deposit
- * is 1:n, where the value of this field is "n".
- */
- unsigned int auditor_chance;
-
- /**
- * Length of the @e cdds array.
- */
- unsigned int num_cdds;
-
-};
-
-
-/**
- * Finish batch deposit operation by calling the callback.
- *
- * @param[in] dh handle to finished batch deposit operation
- */
-static void
-finish_dh (struct TALER_EXCHANGE_BatchDepositHandle *dh)
-{
- dh->cb (dh->cb_cls,
- &dh->dr);
- TALER_EXCHANGE_batch_deposit_cancel (dh);
-}
-
-
-/**
- * Function called with the result from our call to the
- * auditor's /deposit-confirmation handler.
- *
- * @param cls closure of type `struct TEAH_AuditorInteractionEntry *`
- * @param dcr response
- */
-static void
-acc_confirmation_cb (
- void *cls,
- const struct TALER_AUDITOR_DepositConfirmationResponse *dcr)
-{
- struct TEAH_AuditorInteractionEntry *aie = cls;
- struct TALER_EXCHANGE_BatchDepositHandle *dh = aie->dh;
-
- if (MHD_HTTP_OK != dcr->hr.http_status)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Failed to submit deposit confirmation to auditor `%s' with HTTP status %d (EC: %d). This is acceptable if it does not happen often.\n",
- aie->auditor_url,
- dcr->hr.http_status,
- dcr->hr.ec);
- }
- GNUNET_CONTAINER_DLL_remove (dh->ai_head,
- dh->ai_tail,
- aie);
- GNUNET_free (aie);
- if (NULL == dh->ai_head)
- finish_dh (dh);
-}
-
-
-/**
- * Function called for each auditor to give us a chance to possibly
- * launch a deposit confirmation interaction.
- *
- * @param cls closure
- * @param auditor_url base URL of the auditor
- * @param auditor_pub public key of the auditor
- */
-static void
-auditor_cb (void *cls,
- const char *auditor_url,
- const struct TALER_AuditorPublicKeyP *auditor_pub)
-{
- struct TALER_EXCHANGE_BatchDepositHandle *dh = cls;
- const struct TALER_EXCHANGE_SigningPublicKey *spk;
- struct TEAH_AuditorInteractionEntry *aie;
- const struct TALER_CoinSpendSignatureP *csigs[GNUNET_NZL (
- dh->num_cdds)];
- const struct TALER_CoinSpendPublicKeyP *cpubs[GNUNET_NZL (
- dh->num_cdds)];
-
- for (unsigned int i = 0; i<dh->num_cdds; i++)
- {
- const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &dh->cdds[i];
-
- csigs[i] = &cdd->coin_sig;
- cpubs[i] = &cdd->coin_pub;
- }
-
- if (0 !=
- GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
- dh->auditor_chance))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Not providing deposit confirmation to auditor\n");
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Will provide deposit confirmation to auditor `%s'\n",
- TALER_B2S (auditor_pub));
- spk = TALER_EXCHANGE_get_signing_key_info (dh->keys,
- &dh->exchange_pub);
- if (NULL == spk)
- {
- GNUNET_break_op (0);
- return;
- }
- aie = GNUNET_new (struct TEAH_AuditorInteractionEntry);
- aie->dh = dh;
- aie->auditor_url = auditor_url;
- aie->dch = TALER_AUDITOR_deposit_confirmation (
- dh->ctx,
- auditor_url,
- &dh->h_wire,
- &dh->h_policy,
- &dh->dcd.h_contract_terms,
- dh->exchange_timestamp,
- dh->dcd.wire_deadline,
- dh->dcd.refund_deadline,
- &dh->total_without_fee,
- dh->num_cdds,
- cpubs,
- csigs,
- &dh->dcd.merchant_pub,
- &dh->exchange_pub,
- &dh->exchange_sig,
- &dh->keys->master_pub,
- spk->valid_from,
- spk->valid_until,
- spk->valid_legal,
- &spk->master_sig,
- &acc_confirmation_cb,
- aie);
- GNUNET_CONTAINER_DLL_insert (dh->ai_head,
- dh->ai_tail,
- aie);
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /deposit request.
- *
- * @param cls the `struct TALER_EXCHANGE_BatchDepositHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_deposit_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_BatchDepositHandle *dh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_BatchDepositResult *dr = &dh->dr;
-
- dh->job = NULL;
- dh->response = json_incref ((json_t*) j);
- dr->hr.reply = dh->response;
- dr->hr.http_status = (unsigned int) response_code;
- switch (response_code)
- {
- case 0:
- dr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &dh->exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &dh->exchange_pub),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_web_url ("transaction_base_url",
- &dr->details.ok.transaction_base_url),
- NULL),
- GNUNET_JSON_spec_timestamp ("exchange_timestamp",
- &dh->exchange_timestamp),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- dr->hr.http_status = 0;
- dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (dh->keys,
- &dh->exchange_pub))
- {
- GNUNET_break_op (0);
- dr->hr.http_status = 0;
- dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
- break;
- }
- {
- const struct TALER_CoinSpendSignatureP *csigs[
- GNUNET_NZL (dh->num_cdds)];
-
- for (unsigned int i = 0; i<dh->num_cdds; i++)
- csigs[i] = &dh->cdds[i].coin_sig;
- if (GNUNET_OK !=
- TALER_exchange_online_deposit_confirmation_verify (
- &dh->dcd.h_contract_terms,
- &dh->h_wire,
- &dh->h_policy,
- dh->exchange_timestamp,
- dh->dcd.wire_deadline,
- dh->dcd.refund_deadline,
- &dh->total_without_fee,
- dh->num_cdds,
- csigs,
- &dh->dcd.merchant_pub,
- &dh->exchange_pub,
- &dh->exchange_sig))
- {
- GNUNET_break_op (0);
- dr->hr.http_status = 0;
- dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
- break;
- }
- }
- TEAH_get_auditors_for_dc (dh->keys,
- &auditor_cb,
- dh);
- }
- dr->details.ok.exchange_sig = &dh->exchange_sig;
- dr->details.ok.exchange_pub = &dh->exchange_pub;
- dr->details.ok.deposit_timestamp = dh->exchange_timestamp;
- 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 */
- dr->hr.ec = TALER_JSON_get_error_code (j);
- dr->hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_FORBIDDEN:
- dr->hr.ec = TALER_JSON_get_error_code (j);
- dr->hr.hint = TALER_JSON_get_error_hint (j);
- /* 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 */
- break;
- case MHD_HTTP_NOT_FOUND:
- dr->hr.ec = TALER_JSON_get_error_code (j);
- dr->hr.hint = TALER_JSON_get_error_hint (j);
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- break;
- case MHD_HTTP_CONFLICT:
- {
- dr->hr.ec = TALER_JSON_get_error_code (j);
- dr->hr.hint = TALER_JSON_get_error_hint (j);
- switch (dr->hr.ec)
- {
- case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto (
- "coin_pub",
- &dr->details.conflict.details
- .insufficient_funds.coin_pub),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- dr->hr.http_status = 0;
- dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- }
- break;
- case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH:
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto (
- "coin_pub",
- &dr->details.conflict.details
- .coin_conflicting_age_hash.coin_pub),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- dr->hr.http_status = 0;
- dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- }
- break;
- case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto (
- "coin_pub",
- &dr->details.conflict.details
- .coin_conflicting_denomination_key.coin_pub),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- dr->hr.http_status = 0;
- dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- }
- break;
- case TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT:
- break;
- default:
- GNUNET_break_op (0);
- break;
- }
- }
- 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 */
- dr->hr.ec = TALER_JSON_get_error_code (j);
- dr->hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto (
- "h_payto",
- &dr->details.unavailable_for_legal_reasons.h_payto),
- GNUNET_JSON_spec_uint64 (
- "requirement_row",
- &dr->details.unavailable_for_legal_reasons.requirement_row),
- GNUNET_JSON_spec_bool (
- "bad_kyc_auth",
- &dr->details.unavailable_for_legal_reasons.bad_kyc_auth),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- dr->hr.http_status = 0;
- dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- }
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- dr->hr.ec = TALER_JSON_get_error_code (j);
- dr->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 */
- dr->hr.ec = TALER_JSON_get_error_code (j);
- dr->hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange deposit\n",
- (unsigned int) response_code,
- dr->hr.ec);
- GNUNET_break_op (0);
- break;
- }
- if (NULL != dh->ai_head)
- return;
- finish_dh (dh);
-}
-
-
-struct TALER_EXCHANGE_BatchDepositHandle *
-TALER_EXCHANGE_batch_deposit (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_EXCHANGE_DepositContractDetail *dcd,
- unsigned int num_cdds,
- const struct TALER_EXCHANGE_CoinDepositDetail cdds[static num_cdds],
- TALER_EXCHANGE_BatchDepositResultCallback cb,
- void *cb_cls,
- enum TALER_ErrorCode *ec)
-{
- struct TALER_EXCHANGE_BatchDepositHandle *dh;
- json_t *deposit_obj;
- json_t *deposits;
- CURL *eh;
- const struct GNUNET_HashCode *wallet_data_hashp;
-
- if (0 == num_cdds)
- {
- GNUNET_break (0);
- return NULL;
- }
- if (GNUNET_TIME_timestamp_cmp (dcd->refund_deadline,
- >,
- dcd->wire_deadline))
- {
- GNUNET_break_op (0);
- *ec = TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE;
- return NULL;
- }
- dh = GNUNET_new (struct TALER_EXCHANGE_BatchDepositHandle);
- dh->auditor_chance = AUDITOR_CHANCE;
- dh->cb = cb;
- dh->cb_cls = cb_cls;
- dh->cdds = GNUNET_memdup (cdds,
- num_cdds * sizeof (*cdds));
- dh->num_cdds = num_cdds;
- dh->dcd = *dcd;
- if (NULL != dcd->policy_details)
- TALER_deposit_policy_hash (dcd->policy_details,
- &dh->h_policy);
- TALER_merchant_wire_signature_hash (dcd->merchant_payto_uri,
- &dcd->wire_salt,
- &dh->h_wire);
- deposits = json_array ();
- GNUNET_assert (NULL != deposits);
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (cdds[0].amount.currency,
- &dh->total_without_fee));
- for (unsigned int i = 0; i<num_cdds; i++)
- {
- const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &cdds[i];
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
- const struct TALER_AgeCommitmentHashP *h_age_commitmentp;
- struct TALER_Amount amount_without_fee;
-
- dki = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
- &cdd->h_denom_pub);
- if (NULL == dki)
- {
- *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
- GNUNET_break_op (0);
- json_decref (deposits);
- return NULL;
- }
- if (0 >
- TALER_amount_subtract (&amount_without_fee,
- &cdd->amount,
- &dki->fees.deposit))
- {
- *ec = TALER_EC_EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT;
- GNUNET_break_op (0);
- GNUNET_free (dh->cdds);
- GNUNET_free (dh);
- json_decref (deposits);
- return NULL;
- }
- GNUNET_assert (0 <=
- TALER_amount_add (&dh->total_without_fee,
- &dh->total_without_fee,
- &amount_without_fee));
- if (GNUNET_OK !=
- TALER_EXCHANGE_verify_deposit_signature_ (dcd,
- &dh->h_policy,
- &dh->h_wire,
- cdd,
- dki))
- {
- *ec = TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID;
- GNUNET_break_op (0);
- GNUNET_free (dh->cdds);
- GNUNET_free (dh);
- json_decref (deposits);
- return NULL;
- }
- if (! GNUNET_is_zero (&dcd->merchant_sig))
- {
- /* FIXME #9185: check merchant_sig!? */
- }
- if (GNUNET_is_zero (&cdd->h_age_commitment))
- h_age_commitmentp = NULL;
- else
- h_age_commitmentp = &cdd->h_age_commitment;
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- deposits,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("contribution",
- &cdd->amount),
- GNUNET_JSON_pack_data_auto ("denom_pub_hash",
- &cdd->h_denom_pub),
- TALER_JSON_pack_denom_sig ("ub_sig",
- &cdd->denom_sig),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- &cdd->coin_pub),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_auto ("h_age_commitment",
- h_age_commitmentp)),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &cdd->coin_sig)
- )));
- }
- dh->url = TALER_url_join (url,
- "batch-deposit",
- NULL);
- if (NULL == dh->url)
- {
- GNUNET_break (0);
- *ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
- GNUNET_free (dh->url);
- GNUNET_free (dh->cdds);
- GNUNET_free (dh);
- json_decref (deposits);
- return NULL;
- }
-
- if (GNUNET_is_zero (&dcd->wallet_data_hash))
- wallet_data_hashp = NULL;
- else
- wallet_data_hashp = &dcd->wallet_data_hash;
-
- deposit_obj = GNUNET_JSON_PACK (
- TALER_JSON_pack_full_payto ("merchant_payto_uri",
- dcd->merchant_payto_uri),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_auto ("extra_wire_subject_metadata",
- dcd->extra_wire_subject_metadata)),
- GNUNET_JSON_pack_data_auto ("wire_salt",
- &dcd->wire_salt),
- GNUNET_JSON_pack_data_auto ("h_contract_terms",
- &dcd->h_contract_terms),
- GNUNET_JSON_pack_array_steal ("coins",
- deposits),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_auto ("wallet_data_hash",
- wallet_data_hashp)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_steal ("policy_details",
- (json_t *) dcd->policy_details)),
- GNUNET_JSON_pack_timestamp ("timestamp",
- dcd->wallet_timestamp),
- GNUNET_JSON_pack_data_auto ("merchant_pub",
- &dcd->merchant_pub),
- GNUNET_JSON_pack_data_auto ("merchant_sig",
- &dcd->merchant_sig),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_timestamp ("refund_deadline",
- dcd->refund_deadline)),
- GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
- dcd->wire_deadline));
- GNUNET_assert (NULL != deposit_obj);
- eh = TALER_EXCHANGE_curl_easy_get_ (dh->url);
- if ( (NULL == eh) ||
- (GNUNET_OK !=
- TALER_curl_easy_post (&dh->post_ctx,
- eh,
- deposit_obj)) )
- {
- *ec = TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE;
- GNUNET_break (0);
- if (NULL != eh)
- curl_easy_cleanup (eh);
- json_decref (deposit_obj);
- GNUNET_free (dh->cdds);
- GNUNET_free (dh->url);
- GNUNET_free (dh);
- return NULL;
- }
- json_decref (deposit_obj);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "URL for deposit: `%s'\n",
- dh->url);
- dh->ctx = ctx;
- dh->keys = TALER_EXCHANGE_keys_incref (keys);
- dh->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- dh->post_ctx.headers,
- &handle_deposit_finished,
- dh);
- return dh;
-}
-
-
-void
-TALER_EXCHANGE_batch_deposit_force_dc (
- struct TALER_EXCHANGE_BatchDepositHandle *deposit)
-{
- deposit->auditor_chance = 1;
-}
-
-
-void
-TALER_EXCHANGE_batch_deposit_cancel (
- struct TALER_EXCHANGE_BatchDepositHandle *deposit)
-{
- struct TEAH_AuditorInteractionEntry *aie;
-
- while (NULL != (aie = deposit->ai_head))
- {
- GNUNET_assert (aie->dh == deposit);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Not sending deposit confirmation to auditor `%s' due to cancellation\n",
- aie->auditor_url);
- TALER_AUDITOR_deposit_confirmation_cancel (aie->dch);
- GNUNET_CONTAINER_DLL_remove (deposit->ai_head,
- deposit->ai_tail,
- aie);
- GNUNET_free (aie);
- }
- if (NULL != deposit->job)
- {
- GNUNET_CURL_job_cancel (deposit->job);
- deposit->job = NULL;
- }
- TALER_EXCHANGE_keys_decref (deposit->keys);
- GNUNET_free (deposit->url);
- GNUNET_free (deposit->cdds);
- TALER_curl_easy_post_finished (&deposit->post_ctx);
- json_decref (deposit->response);
- GNUNET_free (deposit);
-}
-
-
-/* end of exchange_api_batch_deposit.c */
diff --git a/src/lib/exchange_api_blinding_prepare.c b/src/lib/exchange_api_blinding_prepare.c
@@ -1,422 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2025 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_blinding_prepare.c
- * @brief Implementation of /blinding-prepare requests
- * @author Özgür Kesim
- */
-
-#include "taler/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/taler_curl_lib.h"
-#include "taler/taler_error_codes.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_common.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-#include "taler/taler_util.h"
-
-/**
- * A /blinding-prepare request-handle
- */
-struct TALER_EXCHANGE_BlindingPrepareHandle
-{
- /**
- * number of elements to prepare
- */
- size_t num;
-
- /**
- * True, if this operation is for melting (or withdraw otherwise).
- */
- bool for_melt;
-
- /**
- * The seed for the batch of nonces.
- */
- const struct TALER_BlindingMasterSeedP *seed;
-
- /**
- * The url for this request.
- */
- char *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 withdraw response results.
- */
- TALER_EXCHANGE_BlindingPrepareCallback callback;
-
- /**
- * Closure for @e callback
- */
- void *callback_cls;
-
-};
-
-
-/**
- * We got a 200 OK response for the /blinding-prepare operation.
- * Extract the r_pub values and return them to the caller via the callback
- *
- * @param handle operation handle
- * @param response response details
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static enum GNUNET_GenericReturnValue
-blinding_prepare_ok (struct TALER_EXCHANGE_BlindingPrepareHandle *handle,
- struct TALER_EXCHANGE_BlindingPrepareResponse *response)
-{
- const json_t *j_r_pubs;
- const char *cipher;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("cipher",
- &cipher),
- GNUNET_JSON_spec_array_const ("r_pubs",
- &j_r_pubs),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (response->hr.reply,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- if (strcmp ("CS", cipher))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- if (json_array_size (j_r_pubs)
- != handle->num)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- {
- size_t num = handle->num;
- const json_t *j_pair;
- size_t idx;
- struct TALER_ExchangeBlindingValues blinding_values[GNUNET_NZL (num)];
-
- memset (blinding_values,
- 0,
- sizeof(blinding_values));
-
- json_array_foreach (j_r_pubs, idx, j_pair) {
- struct GNUNET_CRYPTO_BlindingInputValues *bi =
- GNUNET_new (struct GNUNET_CRYPTO_BlindingInputValues);
- struct GNUNET_CRYPTO_CSPublicRPairP *csv = &bi->details.cs_values;
- struct GNUNET_JSON_Specification tuple[] = {
- GNUNET_JSON_spec_fixed (NULL,
- &csv->r_pub[0],
- sizeof(csv->r_pub[0])),
- GNUNET_JSON_spec_fixed (NULL,
- &csv->r_pub[1],
- sizeof(csv->r_pub[1])),
- GNUNET_JSON_spec_end ()
- };
- struct GNUNET_JSON_Specification jspec[] = {
- TALER_JSON_spec_tuple_of (NULL, tuple),
- GNUNET_JSON_spec_end ()
- };
- const char *err_msg;
- unsigned int err_line;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j_pair,
- jspec,
- &err_msg,
- &err_line))
- {
- GNUNET_break_op (0);
- GNUNET_free (bi);
- for (size_t i=0; i < idx; i++)
- TALER_denom_ewv_free (&blinding_values[i]);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Error while parsing response: in line %d: %s",
- err_line,
- err_msg);
- return GNUNET_SYSERR;
- }
-
- bi->cipher = GNUNET_CRYPTO_BSA_CS;
- bi->rc = 1;
- blinding_values[idx].blinding_inputs = bi;
- }
-
- response->details.ok.blinding_values = blinding_values;
- response->details.ok.num_blinding_values = num;
-
- handle->callback (
- handle->callback_cls,
- response);
-
- for (size_t i = 0; i < num; i++)
- TALER_denom_ewv_free (&blinding_values[i]);
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the HTTP /blinding-prepare request.
- *
- * @param cls the `struct TALER_EXCHANGE_BlindingPrepareHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_blinding_prepare_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_BlindingPrepareHandle *handle = cls;
- const json_t *j_response = response;
- struct TALER_EXCHANGE_BlindingPrepareResponse bpr = {
- .hr = {
- .reply = j_response,
- .http_status = (unsigned int) response_code
- },
- };
-
- handle->job = NULL;
-
- switch (response_code)
- {
- case 0:
- bpr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
-
- case MHD_HTTP_OK:
- {
- if (GNUNET_OK !=
- blinding_prepare_ok (handle,
- &bpr))
- {
- GNUNET_break_op (0);
- bpr.hr.http_status = 0;
- bpr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- }
- TALER_EXCHANGE_blinding_prepare_cancel (handle);
- 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 */
- bpr.hr.ec = TALER_JSON_get_error_code (j_response);
- bpr.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 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. */
- bpr.hr.ec = TALER_JSON_get_error_code (j_response);
- bpr.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 */
- bpr.hr.ec = TALER_JSON_get_error_code (j_response);
- bpr.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 */
- bpr.hr.ec = TALER_JSON_get_error_code (j_response);
- bpr.hr.hint = TALER_JSON_get_error_hint (j_response);
- break;
-
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- bpr.hr.ec = TALER_JSON_get_error_code (j_response);
- bpr.hr.hint = TALER_JSON_get_error_hint (j_response);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for the blinding-prepare request\n",
- (unsigned int) response_code,
- (int) bpr.hr.ec);
- break;
-
- }
-
- handle->callback (handle->callback_cls,
- &bpr);
- handle->callback = NULL;
- TALER_EXCHANGE_blinding_prepare_cancel (handle);
-}
-
-
-struct TALER_EXCHANGE_BlindingPrepareHandle *
-TALER_EXCHANGE_blinding_prepare (
- struct GNUNET_CURL_Context *curl_ctx,
- const char *exchange_url,
- const struct TALER_BlindingMasterSeedP *seed,
- bool for_melt,
- size_t num,
- const struct TALER_EXCHANGE_NonceKey nonce_keys[static num],
- TALER_EXCHANGE_BlindingPrepareCallback callback,
- void *callback_cls)
-{
- struct TALER_EXCHANGE_BlindingPrepareHandle *bph;
-
- if (0 == num)
- {
- GNUNET_break (0);
- return NULL;
- }
- for (unsigned int i = 0; i<num; i++)
- if (GNUNET_CRYPTO_BSA_CS !=
- nonce_keys[i].pk->key.bsign_pub_key->cipher)
- {
- GNUNET_break (0);
- return NULL;
- }
- bph = GNUNET_new (struct TALER_EXCHANGE_BlindingPrepareHandle);
- bph->num = num;
- bph->callback = callback;
- bph->for_melt = for_melt;
- bph->callback_cls = callback_cls;
- bph->url = TALER_url_join (exchange_url,
- "blinding-prepare",
- NULL);
- if (NULL == bph->url)
- {
- GNUNET_break (0);
- GNUNET_free (bph);
- return NULL;
- }
- {
- CURL *eh;
- json_t *j_nks;
- json_t *j_request = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("cipher",
- "CS"),
- GNUNET_JSON_pack_string ("operation",
- for_melt ? "melt" : "withdraw"),
- GNUNET_JSON_pack_data_auto ("seed",
- seed));
- GNUNET_assert (NULL != j_request);
-
- j_nks = json_array ();
- GNUNET_assert (NULL != j_nks);
-
- for (size_t i = 0; i<num; i++)
- {
- const struct TALER_EXCHANGE_NonceKey *nk = &nonce_keys[i];
- json_t *j_entry = 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 != j_entry);
- GNUNET_assert (0 ==
- json_array_append_new (j_nks,
- j_entry));
- }
- GNUNET_assert (0 ==
- json_object_set_new (j_request,
- "nks",
- j_nks));
- eh = TALER_EXCHANGE_curl_easy_get_ (bph->url);
- if ( (NULL == eh) ||
- (GNUNET_OK !=
- TALER_curl_easy_post (&bph->post_ctx,
- eh,
- j_request)))
- {
- GNUNET_break (0);
- if (NULL != eh)
- curl_easy_cleanup (eh);
- json_decref (j_request);
- GNUNET_free (bph->url);
- GNUNET_free (bph);
- return NULL;
- }
-
- json_decref (j_request);
- bph->job = GNUNET_CURL_job_add2 (curl_ctx,
- eh,
- bph->post_ctx.headers,
- &handle_blinding_prepare_finished,
- bph);
- if (NULL == bph->job)
- {
- GNUNET_break (0);
- TALER_EXCHANGE_blinding_prepare_cancel (bph);
- return NULL;
- }
- }
- return bph;
-}
-
-
-void
-TALER_EXCHANGE_blinding_prepare_cancel (
- struct TALER_EXCHANGE_BlindingPrepareHandle *bph)
-{
- if (NULL == bph)
- return;
- if (NULL != bph->job)
- {
- GNUNET_CURL_job_cancel (bph->job);
- bph->job = NULL;
- }
- GNUNET_free (bph->url);
- TALER_curl_easy_post_finished (&bph->post_ctx);
- GNUNET_free (bph);
-}
-
-
-/* end of lib/exchange_api_blinding_prepare.c */
diff --git a/src/lib/exchange_api_coins_history.c b/src/lib/exchange_api_coins_history.c
@@ -1,1235 +0,0 @@
-/*
- 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_coins_history.c
- * @brief Implementation of the POST /coins/$COIN_PUB/history requests
- * @author Christian Grothoff
- *
- * NOTE: this is an incomplete draft, never finished!
- */
-#include "taler/platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP history codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A /coins/$RID/history Handle
- */
-struct TALER_EXCHANGE_CoinsHistoryHandle
-{
-
- /**
- * 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;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_CoinsHistoryCallback cb;
-
- /**
- * Public key of the coin we are querying.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
-};
-
-
-/**
- * Context for coin helpers.
- */
-struct CoinHistoryParseContext
-{
-
- /**
- * Keys of the exchange.
- */
- struct TALER_EXCHANGE_Keys *keys;
-
- /**
- * Denomination of the coin.
- */
- const struct TALER_EXCHANGE_DenomPublicKey *dk;
-
- /**
- * Our coin public key.
- */
- const struct TALER_CoinSpendPublicKeyP *coin_pub;
-
- /**
- * Where to sum up total refunds.
- */
- struct TALER_Amount *total_in;
-
- /**
- * Total amount encountered.
- */
- struct TALER_Amount *total_out;
-
-};
-
-
-/**
- * Signature of functions that operate on one of
- * the coin's history entries.
- *
- * @param[in,out] pc overall context
- * @param[out] rh where to write the history entry
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- * #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-typedef enum GNUNET_GenericReturnValue
-(*CoinCheckHelper)(struct CoinHistoryParseContext *pc,
- struct TALER_EXCHANGE_CoinHistoryEntry *rh,
- const struct TALER_Amount *amount,
- json_t *transaction);
-
-
-/**
- * Handle deposit entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @param[out] rh history entry to initialize
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- * #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_deposit (struct CoinHistoryParseContext *pc,
- struct TALER_EXCHANGE_CoinHistoryEntry *rh,
- const struct TALER_Amount *amount,
- json_t *transaction)
-{
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("deposit_fee",
- &rh->details.deposit.deposit_fee),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &rh->details.deposit.merchant_pub),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &rh->details.deposit.wallet_timestamp),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("refund_deadline",
- &rh->details.deposit.refund_deadline),
- NULL),
- GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
- &rh->details.deposit.h_contract_terms),
- GNUNET_JSON_spec_fixed_auto ("h_wire",
- &rh->details.deposit.h_wire),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("h_policy",
- &rh->details.deposit.h_policy),
- &rh->details.deposit.no_h_policy),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("wallet_data_hash",
- &rh->details.deposit.wallet_data_hash),
- &rh->details.deposit.no_wallet_data_hash),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
- &rh->details.deposit.hac),
- &rh->details.deposit.no_hac),
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &rh->details.deposit.sig),
- GNUNET_JSON_spec_end ()
- };
-
- rh->details.deposit.refund_deadline = GNUNET_TIME_UNIT_ZERO_TS;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_wallet_deposit_verify (
- amount,
- &rh->details.deposit.deposit_fee,
- &rh->details.deposit.h_wire,
- &rh->details.deposit.h_contract_terms,
- rh->details.deposit.no_wallet_data_hash
- ? NULL
- : &rh->details.deposit.wallet_data_hash,
- rh->details.deposit.no_hac
- ? NULL
- : &rh->details.deposit.hac,
- rh->details.deposit.no_h_policy
- ? NULL
- : &rh->details.deposit.h_policy,
- &pc->dk->h_key,
- rh->details.deposit.wallet_timestamp,
- &rh->details.deposit.merchant_pub,
- rh->details.deposit.refund_deadline,
- pc->coin_pub,
- &rh->details.deposit.sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- /* check that deposit fee matches our expectations from /keys! */
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&rh->details.deposit.deposit_fee,
- &pc->dk->fees.deposit)) ||
- (0 !=
- TALER_amount_cmp (&rh->details.deposit.deposit_fee,
- &pc->dk->fees.deposit)) )
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_YES;
-}
-
-
-/**
- * Handle melt entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @param[out] rh history entry to initialize
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- * #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_melt (struct CoinHistoryParseContext *pc,
- struct TALER_EXCHANGE_CoinHistoryEntry *rh,
- const struct TALER_Amount *amount,
- json_t *transaction)
-{
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("melt_fee",
- &rh->details.melt.melt_fee),
- GNUNET_JSON_spec_fixed_auto ("rc",
- &rh->details.melt.rc),
- // FIXME: also return refresh_seed?
- // FIXME: also return blinding_seed?
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
- &rh->details.melt.h_age_commitment),
- &rh->details.melt.no_hac),
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &rh->details.melt.sig),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- /* check that melt fee matches our expectations from /keys! */
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&rh->details.melt.melt_fee,
- &pc->dk->fees.refresh)) ||
- (0 !=
- TALER_amount_cmp (&rh->details.melt.melt_fee,
- &pc->dk->fees.refresh)) )
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_wallet_melt_verify (
- amount,
- &rh->details.melt.melt_fee,
- &rh->details.melt.rc,
- &pc->dk->h_key,
- rh->details.melt.no_hac
- ? NULL
- : &rh->details.melt.h_age_commitment,
- pc->coin_pub,
- &rh->details.melt.sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_YES;
-}
-
-
-/**
- * Handle refund entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @param[out] rh history entry to initialize
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- * #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_refund (struct CoinHistoryParseContext *pc,
- struct TALER_EXCHANGE_CoinHistoryEntry *rh,
- const struct TALER_Amount *amount,
- json_t *transaction)
-{
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("refund_fee",
- &rh->details.refund.refund_fee),
- GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
- &rh->details.refund.h_contract_terms),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &rh->details.refund.merchant_pub),
- GNUNET_JSON_spec_uint64 ("rtransaction_id",
- &rh->details.refund.rtransaction_id),
- GNUNET_JSON_spec_fixed_auto ("merchant_sig",
- &rh->details.refund.sig),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (0 >
- TALER_amount_add (&rh->details.refund.sig_amount,
- &rh->details.refund.refund_fee,
- amount))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_merchant_refund_verify (pc->coin_pub,
- &rh->details.refund.h_contract_terms,
- rh->details.refund.rtransaction_id,
- &rh->details.refund.sig_amount,
- &rh->details.refund.merchant_pub,
- &rh->details.refund.sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- /* NOTE: theoretically, we could also check that the given
- merchant_pub and h_contract_terms appear in the
- history under deposits. However, there is really no benefit
- for the exchange to lie here, so not checking is probably OK
- (an auditor ought to check, though). Then again, we similarly
- had no reason to check the merchant's signature (other than a
- well-formendess check). */
-
- /* check that refund fee matches our expectations from /keys! */
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&rh->details.refund.refund_fee,
- &pc->dk->fees.refund)) ||
- (0 !=
- TALER_amount_cmp (&rh->details.refund.refund_fee,
- &pc->dk->fees.refund)) )
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_NO;
-}
-
-
-/**
- * Handle recoup entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @param[out] rh history entry to initialize
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- * #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_recoup (struct CoinHistoryParseContext *pc,
- struct TALER_EXCHANGE_CoinHistoryEntry *rh,
- const struct TALER_Amount *amount,
- json_t *transaction)
-{
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &rh->details.recoup.exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &rh->details.recoup.exchange_pub),
- GNUNET_JSON_spec_fixed_auto ("reserve_pub",
- &rh->details.recoup.reserve_pub),
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &rh->details.recoup.coin_sig),
- GNUNET_JSON_spec_fixed_auto ("coin_blind",
- &rh->details.recoup.coin_bks),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &rh->details.recoup.timestamp),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_exchange_online_confirm_recoup_verify (
- rh->details.recoup.timestamp,
- amount,
- pc->coin_pub,
- &rh->details.recoup.reserve_pub,
- &rh->details.recoup.exchange_pub,
- &rh->details.recoup.exchange_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_wallet_recoup_verify (&pc->dk->h_key,
- &rh->details.recoup.coin_bks,
- pc->coin_pub,
- &rh->details.recoup.coin_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_YES;
-}
-
-
-/**
- * Handle recoup-refresh entry in the coin's history.
- * This is the coin that was subjected to a recoup,
- * the value being credited to the old coin.
- *
- * @param[in,out] pc overall context
- * @param[out] rh history entry to initialize
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- * #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_recoup_refresh (struct CoinHistoryParseContext *pc,
- struct TALER_EXCHANGE_CoinHistoryEntry *rh,
- const struct TALER_Amount *amount,
- json_t *transaction)
-{
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &rh->details.recoup_refresh.exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &rh->details.recoup_refresh.exchange_pub),
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &rh->details.recoup_refresh.coin_sig),
- GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
- &rh->details.recoup_refresh.old_coin_pub),
- GNUNET_JSON_spec_fixed_auto ("coin_blind",
- &rh->details.recoup_refresh.coin_bks),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &rh->details.recoup_refresh.timestamp),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_exchange_online_confirm_recoup_refresh_verify (
- rh->details.recoup_refresh.timestamp,
- amount,
- pc->coin_pub,
- &rh->details.recoup_refresh.old_coin_pub,
- &rh->details.recoup_refresh.exchange_pub,
- &rh->details.recoup_refresh.exchange_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_wallet_recoup_verify (&pc->dk->h_key,
- &rh->details.recoup_refresh.coin_bks,
- pc->coin_pub,
- &rh->details.recoup_refresh.coin_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_YES;
-}
-
-
-/**
- * Handle old coin recoup entry in the coin's history.
- * This is the coin that was credited in a recoup,
- * the value being credited to the this coin.
- *
- * @param[in,out] pc overall context
- * @param[out] rh history entry to initialize
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- * #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_old_coin_recoup (struct CoinHistoryParseContext *pc,
- struct TALER_EXCHANGE_CoinHistoryEntry *rh,
- const struct TALER_Amount *amount,
- json_t *transaction)
-{
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &rh->details.old_coin_recoup.exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &rh->details.old_coin_recoup.exchange_pub),
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &rh->details.old_coin_recoup.new_coin_pub),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &rh->details.old_coin_recoup.timestamp),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_exchange_online_confirm_recoup_refresh_verify (
- rh->details.old_coin_recoup.timestamp,
- amount,
- &rh->details.old_coin_recoup.new_coin_pub,
- pc->coin_pub,
- &rh->details.old_coin_recoup.exchange_pub,
- &rh->details.old_coin_recoup.exchange_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_NO;
-}
-
-
-/**
- * Handle purse deposit entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @param[out] rh history entry to initialize
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- * #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_purse_deposit (struct CoinHistoryParseContext *pc,
- struct TALER_EXCHANGE_CoinHistoryEntry *rh,
- const struct TALER_Amount *amount,
- json_t *transaction)
-{
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_web_url ("exchange_base_url",
- &rh->details.purse_deposit.exchange_base_url),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
- &rh->details.purse_deposit.phac),
- NULL),
- // FIXME: return deposit_fee?
- GNUNET_JSON_spec_fixed_auto ("purse_pub",
- &rh->details.purse_deposit.purse_pub),
- GNUNET_JSON_spec_bool ("refunded",
- &rh->details.purse_deposit.refunded),
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &rh->details.purse_deposit.coin_sig),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_wallet_purse_deposit_verify (
- rh->details.purse_deposit.exchange_base_url,
- &rh->details.purse_deposit.purse_pub,
- amount,
- &pc->dk->h_key,
- &rh->details.purse_deposit.phac,
- pc->coin_pub,
- &rh->details.purse_deposit.coin_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (rh->details.purse_deposit.refunded)
- {
- /* We wave the deposit fee. */
- if (0 >
- TALER_amount_add (pc->total_in,
- pc->total_in,
- &pc->dk->fees.deposit))
- {
- /* overflow in refund history? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- return GNUNET_YES;
-}
-
-
-/**
- * Handle purse refund entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @param[out] rh history entry to initialize
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- * #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_purse_refund (struct CoinHistoryParseContext *pc,
- struct TALER_EXCHANGE_CoinHistoryEntry *rh,
- const struct TALER_Amount *amount,
- json_t *transaction)
-{
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("refund_fee",
- &rh->details.purse_refund.refund_fee),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &rh->details.purse_refund.exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &rh->details.purse_refund.exchange_pub),
- GNUNET_JSON_spec_fixed_auto ("purse_pub",
- &rh->details.purse_refund.purse_pub),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_exchange_online_purse_refund_verify (
- amount,
- &rh->details.purse_refund.refund_fee,
- pc->coin_pub,
- &rh->details.purse_refund.purse_pub,
- &rh->details.purse_refund.exchange_pub,
- &rh->details.purse_refund.exchange_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&rh->details.purse_refund.refund_fee,
- &pc->dk->fees.refund)) ||
- (0 !=
- TALER_amount_cmp (&rh->details.purse_refund.refund_fee,
- &pc->dk->fees.refund)) )
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_NO;
-}
-
-
-/**
- * Handle reserve deposit entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @param[out] rh history entry to initialize
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- * #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_reserve_open_deposit (struct CoinHistoryParseContext *pc,
- struct TALER_EXCHANGE_CoinHistoryEntry *rh,
- const struct TALER_Amount *amount,
- json_t *transaction)
-{
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &rh->details.reserve_open_deposit.reserve_sig),
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &rh->details.reserve_open_deposit.coin_sig),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_wallet_reserve_open_deposit_verify (
- amount,
- &rh->details.reserve_open_deposit.reserve_sig,
- pc->coin_pub,
- &rh->details.reserve_open_deposit.coin_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_YES;
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_parse_coin_history (
- const struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_EXCHANGE_DenomPublicKey *dk,
- const json_t *history,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- struct TALER_Amount *total_in,
- struct TALER_Amount *total_out,
- unsigned int rlen,
- struct TALER_EXCHANGE_CoinHistoryEntry rhistory[static rlen])
-{
- const struct
- {
- const char *type;
- CoinCheckHelper helper;
- enum TALER_EXCHANGE_CoinTransactionType ctt;
- } map[] = {
- { "DEPOSIT",
- &help_deposit,
- TALER_EXCHANGE_CTT_DEPOSIT },
- { "MELT",
- &help_melt,
- TALER_EXCHANGE_CTT_MELT },
- { "REFUND",
- &help_refund,
- TALER_EXCHANGE_CTT_REFUND },
- { "RECOUP",
- &help_recoup,
- TALER_EXCHANGE_CTT_RECOUP },
- { "RECOUP-REFRESH",
- &help_recoup_refresh,
- TALER_EXCHANGE_CTT_RECOUP_REFRESH },
- { "OLD-COIN-RECOUP",
- &help_old_coin_recoup,
- TALER_EXCHANGE_CTT_OLD_COIN_RECOUP },
- { "PURSE-DEPOSIT",
- &help_purse_deposit,
- TALER_EXCHANGE_CTT_PURSE_DEPOSIT },
- { "PURSE-REFUND",
- &help_purse_refund,
- TALER_EXCHANGE_CTT_PURSE_REFUND },
- { "RESERVE-OPEN-DEPOSIT",
- &help_reserve_open_deposit,
- TALER_EXCHANGE_CTT_RESERVE_OPEN_DEPOSIT },
- { NULL, NULL, TALER_EXCHANGE_CTT_NONE }
- };
- struct CoinHistoryParseContext pc = {
- .dk = dk,
- .coin_pub = coin_pub,
- .total_out = total_out,
- .total_in = total_in
- };
- size_t len;
-
- if (NULL == history)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- len = json_array_size (history);
- if (0 == len)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- *total_in = dk->value;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (total_in->currency,
- total_out));
- for (size_t off = 0; off<len; off++)
- {
- struct TALER_EXCHANGE_CoinHistoryEntry *rh = &rhistory[off];
- json_t *transaction = json_array_get (history,
- off);
- enum GNUNET_GenericReturnValue add;
- const char *type;
- struct GNUNET_JSON_Specification spec_glob[] = {
- TALER_JSON_spec_amount_any ("amount",
- &rh->amount),
- GNUNET_JSON_spec_string ("type",
- &type),
- GNUNET_JSON_spec_uint64 ("history_offset",
- &rh->history_offset),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec_glob,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_YES !=
- TALER_amount_cmp_currency (&rh->amount,
- total_in))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Operation of type %s with amount %s\n",
- type,
- TALER_amount2s (&rh->amount));
- add = GNUNET_SYSERR;
- for (unsigned int i = 0; NULL != map[i].type; i++)
- {
- if (0 == strcasecmp (type,
- map[i].type))
- {
- rh->type = map[i].ctt;
- add = map[i].helper (&pc,
- rh,
- &rh->amount,
- transaction);
- break;
- }
- }
- switch (add)
- {
- case GNUNET_SYSERR:
- /* entry type not supported, new version on server? */
- rh->type = TALER_EXCHANGE_CTT_NONE;
- GNUNET_break_op (0);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected type `%s' in response\n",
- type);
- return GNUNET_SYSERR;
- case GNUNET_YES:
- /* This amount should be debited from the coin */
- if (0 >
- TALER_amount_add (total_out,
- total_out,
- &rh->amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- break;
- case GNUNET_NO:
- /* This amount should be credited to the coin. */
- if (0 >
- TALER_amount_add (total_in,
- total_in,
- &rh->amount))
- {
- /* overflow in refund history? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- break;
- } /* end of switch(add) */
- }
- return GNUNET_OK;
-}
-
-
-/**
- * We received an #MHD_HTTP_OK history code. Handle the JSON
- * response.
- *
- * @param rsh handle of the request
- * @param j JSON response
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_coins_history_ok (struct TALER_EXCHANGE_CoinsHistoryHandle *rsh,
- const json_t *j)
-{
- struct TALER_EXCHANGE_CoinHistory rs = {
- .hr.reply = j,
- .hr.http_status = MHD_HTTP_OK
- };
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("balance",
- &rs.details.ok.balance),
- GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
- &rs.details.ok.h_denom_pub),
- GNUNET_JSON_spec_array_const ("history",
- &rs.details.ok.history),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (NULL != rsh->cb)
- {
- rsh->cb (rsh->cb_cls,
- &rs);
- rsh->cb = NULL;
- }
- GNUNET_JSON_parse_free (spec);
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /coins/$RID/history request.
- *
- * @param cls the `struct TALER_EXCHANGE_CoinsHistoryHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_coins_history_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_CoinsHistoryHandle *rsh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_CoinHistory rs = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
-
- rsh->job = NULL;
- switch (response_code)
- {
- case 0:
- rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- if (GNUNET_OK !=
- handle_coins_history_ok (rsh,
- j))
- {
- rs.hr.http_status = 0;
- rs.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 */
- GNUNET_break (0);
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_FORBIDDEN:
- /* This should never happen, either us or the exchange is buggy
- (or API version conflict); just pass JSON reply to the application */
- GNUNET_break (0);
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.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 */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.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 */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for coins history\n",
- (unsigned int) response_code,
- (int) rs.hr.ec);
- break;
- }
- if (NULL != rsh->cb)
- {
- rsh->cb (rsh->cb_cls,
- &rs);
- rsh->cb = NULL;
- }
- TALER_EXCHANGE_coins_history_cancel (rsh);
-}
-
-
-struct TALER_EXCHANGE_CoinsHistoryHandle *
-TALER_EXCHANGE_coins_history (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- const struct TALER_CoinSpendPrivateKeyP *coin_priv,
- uint64_t start_off,
- TALER_EXCHANGE_CoinsHistoryCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_CoinsHistoryHandle *rsh;
- CURL *eh;
- char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 64];
- struct curl_slist *job_headers;
-
- rsh = GNUNET_new (struct TALER_EXCHANGE_CoinsHistoryHandle);
- rsh->cb = cb;
- rsh->cb_cls = cb_cls;
- GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
- &rsh->coin_pub.eddsa_pub);
- {
- char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- &rsh->coin_pub,
- sizeof (rsh->coin_pub),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- if (0 != start_off)
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "coins/%s/history?start=%llu",
- pub_str,
- (unsigned long long) start_off);
- else
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "coins/%s/history",
- pub_str);
- }
- rsh->url = TALER_url_join (url,
- arg_str,
- NULL);
- if (NULL == rsh->url)
- {
- GNUNET_free (rsh);
- return NULL;
- }
- eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- GNUNET_free (rsh->url);
- GNUNET_free (rsh);
- return NULL;
- }
-
- {
- struct TALER_CoinSpendSignatureP coin_sig;
- char *sig_hdr;
- char *hdr;
-
- TALER_wallet_coin_history_sign (start_off,
- coin_priv,
- &coin_sig);
-
- sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
- &coin_sig,
- sizeof (coin_sig));
- GNUNET_asprintf (&hdr,
- "%s: %s",
- TALER_COIN_HISTORY_SIGNATURE_HEADER,
- sig_hdr);
- GNUNET_free (sig_hdr);
- job_headers = curl_slist_append (NULL,
- hdr);
- GNUNET_free (hdr);
- if (NULL == job_headers)
- {
- GNUNET_break (0);
- curl_easy_cleanup (eh);
- return NULL;
- }
- }
-
- rsh->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- job_headers,
- &handle_coins_history_finished,
- rsh);
- curl_slist_free_all (job_headers);
- return rsh;
-}
-
-
-void
-TALER_EXCHANGE_coins_history_cancel (
- struct TALER_EXCHANGE_CoinsHistoryHandle *rsh)
-{
- if (NULL != rsh->job)
- {
- GNUNET_CURL_job_cancel (rsh->job);
- rsh->job = NULL;
- }
- TALER_curl_easy_post_finished (&rsh->post_ctx);
- GNUNET_free (rsh->url);
- GNUNET_free (rsh);
-}
-
-
-/**
- * Verify that @a coin_sig does NOT appear in the @a history of a coin's
- * transactions and thus whatever transaction is authorized by @a coin_sig is
- * a conflict with @a proof.
- *
- * @param history coin history to check
- * @param coin_sig signature that must not be in @a history
- * @return #GNUNET_OK if @a coin_sig is not in @a history
- */
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_check_coin_signature_conflict (
- const json_t *history,
- const struct TALER_CoinSpendSignatureP *coin_sig)
-{
- size_t off;
- json_t *entry;
-
- json_array_foreach (history, off, entry)
- {
- struct TALER_CoinSpendSignatureP cs;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &cs),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (entry,
- spec,
- NULL, NULL))
- continue; /* entry without coin signature */
- if (0 ==
- GNUNET_memcmp (&cs,
- coin_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- return GNUNET_OK;
-}
-
-
-#if FIXME_IMPLEMENT /* #9422 */
-/**
- * FIXME-Oec-#9422: we need some specific routines that show
- * that certain coin operations are indeed in conflict,
- * for example that the coin is of a different denomination
- * or different age restrictions.
- * This relates to unimplemented error handling for
- * coins in the exchange!
- *
- * Check that the provided @a proof indeeds indicates
- * a conflict for @a coin_pub.
- *
- * @param keys exchange keys
- * @param proof provided conflict proof
- * @param dk denomination of @a coin_pub that the client
- * used
- * @param coin_pub public key of the coin
- * @param required balance required on the coin for the operation
- * @return #GNUNET_OK if @a proof holds
- */
-// FIXME-#9422: should be properly defined and implemented!
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_check_coin_conflict_ (
- const struct TALER_EXCHANGE_Keys *keys,
- const json_t *proof,
- const struct TALER_EXCHANGE_DenomPublicKey *dk,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *required)
-{
- enum TALER_ErrorCode ec;
-
- ec = TALER_JSON_get_error_code (proof);
- switch (ec)
- {
- case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
- /* Nothing to check anymore here, proof needs to be
- checked in the GET /coins/$COIN_PUB handler */
- break;
- case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
- // FIXME-#9422: write check!
- break;
- default:
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-#endif
-
-
-/* end of exchange_api_coins_history.c */
diff --git a/src/lib/exchange_api_contracts_get.c b/src/lib/exchange_api_contracts_get.c
@@ -1,262 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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_contracts_get.c
- * @brief Implementation of the /contracts/ GET request
- * @author Christian Grothoff
- */
-#include "taler/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/taler_json_lib.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A Contract Get Handle
- */
-struct TALER_EXCHANGE_ContractsGetHandle
-{
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_ContractGetCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Private key needed to decrypt the contract.
- */
- struct TALER_ContractDiffiePrivateP contract_priv;
-
- /**
- * Public key matching @e contract_priv.
- */
- struct TALER_ContractDiffiePublicP cpub;
-
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP /track/transaction request.
- *
- * @param cls the `struct TALER_EXCHANGE_ContractsGetHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_contract_get_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_ContractsGetHandle *cgh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_ContractGetResponse dr = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
-
- cgh->job = NULL;
- switch (response_code)
- {
- case 0:
- dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- void *econtract;
- size_t econtract_size;
- struct TALER_PurseContractSignatureP econtract_sig;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("purse_pub",
- &dr.details.ok.purse_pub),
- GNUNET_JSON_spec_fixed_auto ("econtract_sig",
- &econtract_sig),
- GNUNET_JSON_spec_varsize ("econtract",
- &econtract,
- &econtract_size),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (GNUNET_OK !=
- TALER_wallet_econtract_upload_verify (
- econtract,
- econtract_size,
- &cgh->cpub,
- &dr.details.ok.purse_pub,
- &econtract_sig))
- {
- GNUNET_break (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_EXCHANGE_CONTRACTS_SIGNATURE_INVALID;
- GNUNET_JSON_parse_free (spec);
- break;
- }
- dr.details.ok.econtract = econtract;
- dr.details.ok.econtract_size = econtract_size;
- cgh->cb (cgh->cb_cls,
- &dr);
- GNUNET_JSON_parse_free (spec);
- TALER_EXCHANGE_contract_get_cancel (cgh);
- return;
- }
- case MHD_HTTP_BAD_REQUEST:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.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:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- /* 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 */
- break;
- case MHD_HTTP_NOT_FOUND:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- /* Exchange does not know about transaction;
- we should pass the reply to the application */
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.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 */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange GET contracts\n",
- (unsigned int) response_code,
- (int) dr.hr.ec);
- GNUNET_break_op (0);
- break;
- }
- cgh->cb (cgh->cb_cls,
- &dr);
- TALER_EXCHANGE_contract_get_cancel (cgh);
-}
-
-
-struct TALER_EXCHANGE_ContractsGetHandle *
-TALER_EXCHANGE_contract_get (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- const struct TALER_ContractDiffiePrivateP *contract_priv,
- TALER_EXCHANGE_ContractGetCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_ContractsGetHandle *cgh;
- CURL *eh;
- char arg_str[sizeof (cgh->cpub) * 2 + 48];
-
- cgh = GNUNET_new (struct TALER_EXCHANGE_ContractsGetHandle);
- cgh->cb = cb;
- cgh->cb_cls = cb_cls;
- GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv,
- &cgh->cpub.ecdhe_pub);
- {
- char cpub_str[sizeof (cgh->cpub) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (&cgh->cpub,
- sizeof (cgh->cpub),
- cpub_str,
- sizeof (cpub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "contracts/%s",
- cpub_str);
- }
-
- cgh->url = TALER_url_join (url,
- arg_str,
- NULL);
- if (NULL == cgh->url)
- {
- GNUNET_free (cgh);
- return NULL;
- }
- cgh->contract_priv = *contract_priv;
-
- eh = TALER_EXCHANGE_curl_easy_get_ (cgh->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- GNUNET_free (cgh->url);
- GNUNET_free (cgh);
- return NULL;
- }
- cgh->job = GNUNET_CURL_job_add (ctx,
- eh,
- &handle_contract_get_finished,
- cgh);
- return cgh;
-}
-
-
-void
-TALER_EXCHANGE_contract_get_cancel (
- struct TALER_EXCHANGE_ContractsGetHandle *cgh)
-{
- if (NULL != cgh->job)
- {
- GNUNET_CURL_job_cancel (cgh->job);
- cgh->job = NULL;
- }
- GNUNET_free (cgh->url);
- GNUNET_free (cgh);
-}
-
-
-/* end of exchange_api_contracts_get.c */
diff --git a/src/lib/exchange_api_delete-purses-PURSE_PUB.c b/src/lib/exchange_api_delete-purses-PURSE_PUB.c
@@ -0,0 +1,243 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_delete-purses-PURSE_PUB.c
+ * @brief Implementation of the client to delete a purse
+ * into an account
+ * @author Christian Grothoff
+ */
+#include "taler/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/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "exchange_api_common.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A purse delete with deposit handle
+ */
+struct TALER_EXCHANGE_PurseDeleteHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_PurseDeleteCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Header with the purse_sig.
+ */
+ struct curl_slist *xhdr;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP DELETE /purse/$PID request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_PurseDeleteHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_purse_delete_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_PurseDeleteHandle *pdh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_PurseDeleteResponse dr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ pdh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ 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 */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* 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 */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.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 */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange deposit\n",
+ (unsigned int) response_code,
+ dr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ pdh->cb (pdh->cb_cls,
+ &dr);
+ TALER_EXCHANGE_purse_delete_cancel (pdh);
+}
+
+
+struct TALER_EXCHANGE_PurseDeleteHandle *
+TALER_EXCHANGE_purse_delete (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_PurseContractPrivateKeyP *purse_priv,
+ TALER_EXCHANGE_PurseDeleteCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_PurseDeleteHandle *pdh;
+ CURL *eh;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PurseContractSignatureP purse_sig;
+ char arg_str[sizeof (purse_pub) * 2 + 32];
+
+ pdh = GNUNET_new (struct TALER_EXCHANGE_PurseDeleteHandle);
+ pdh->cb = cb;
+ pdh->cb_cls = cb_cls;
+ GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv,
+ &purse_pub.eddsa_pub);
+ {
+ char pub_str[sizeof (purse_pub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (&purse_pub,
+ sizeof (purse_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "purses/%s",
+ pub_str);
+ }
+ pdh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == pdh->url)
+ {
+ GNUNET_break (0);
+ GNUNET_free (pdh);
+ return NULL;
+ }
+ TALER_wallet_purse_delete_sign (purse_priv,
+ &purse_sig);
+ {
+ char *delete_str;
+ char *xhdr;
+
+ delete_str =
+ GNUNET_STRINGS_data_to_string_alloc (&purse_sig,
+ sizeof (purse_sig));
+ GNUNET_asprintf (&xhdr,
+ "Taler-Purse-Signature: %s",
+ delete_str);
+ GNUNET_free (delete_str);
+ pdh->xhdr = curl_slist_append (NULL,
+ xhdr);
+ GNUNET_free (xhdr);
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (pdh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ curl_slist_free_all (pdh->xhdr);
+ GNUNET_free (pdh->url);
+ GNUNET_free (pdh);
+ return NULL;
+ }
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_CUSTOMREQUEST,
+ MHD_HTTP_METHOD_DELETE));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for purse delete: `%s'\n",
+ pdh->url);
+ pdh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ pdh->xhdr,
+ &handle_purse_delete_finished,
+ pdh);
+ return pdh;
+}
+
+
+void
+TALER_EXCHANGE_purse_delete_cancel (
+ struct TALER_EXCHANGE_PurseDeleteHandle *pdh)
+{
+ if (NULL != pdh->job)
+ {
+ GNUNET_CURL_job_cancel (pdh->job);
+ pdh->job = NULL;
+ }
+ curl_slist_free_all (pdh->xhdr);
+ GNUNET_free (pdh->url);
+ GNUNET_free (pdh);
+}
+
+
+/* end of exchange_api_purse_delete.c */
diff --git a/src/lib/exchange_api_deposits_get.c b/src/lib/exchange_api_deposits_get.c
@@ -1,411 +0,0 @@
-/*
- 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_deposits_get.c
- * @brief Implementation of the /deposits/ GET request
- * @author Christian Grothoff
- */
-#include "taler/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/taler_json_lib.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A Deposit Get Handle
- */
-struct TALER_EXCHANGE_DepositGetHandle
-{
-
- /**
- * The keys of the 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_DepositGetCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Hash over the wiring information of the merchant.
- */
- struct TALER_MerchantWireHashP h_wire;
-
- /**
- * Hash over the contract for which this deposit is made.
- */
- 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;
-
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP /track/transaction request.
- *
- * @param cls the `struct TALER_EXCHANGE_DepositGetHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_deposit_wtid_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_DepositGetHandle *dwh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_GetDepositResponse dr = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
-
- dwh->job = NULL;
- switch (response_code)
- {
- case 0:
- dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto (
- "wtid",
- &dr.details.ok.wtid),
- GNUNET_JSON_spec_timestamp (
- "execution_time",
- &dr.details.ok.execution_time),
- TALER_JSON_spec_amount_any (
- "coin_contribution",
- &dr.details.ok.coin_contribution),
- GNUNET_JSON_spec_fixed_auto (
- "exchange_sig",
- &dr.details.ok.exchange_sig),
- GNUNET_JSON_spec_fixed_auto (
- "exchange_pub",
- &dr.details.ok.exchange_pub),
- GNUNET_JSON_spec_end ()
- };
- const struct TALER_EXCHANGE_Keys *key_state;
-
- key_state = dwh->keys;
- GNUNET_assert (NULL != key_state);
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
- &dr.details.ok.exchange_pub))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE;
- break;
- }
- if (GNUNET_OK !=
- TALER_exchange_online_confirm_wire_verify (
- &dwh->h_wire,
- &dwh->h_contract_terms,
- &dr.details.ok.wtid,
- &dwh->coin_pub,
- dr.details.ok.execution_time,
- &dr.details.ok.coin_contribution,
- &dr.details.ok.exchange_pub,
- &dr.details.ok.exchange_sig))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE;
- break;
- }
- dwh->cb (dwh->cb_cls,
- &dr);
- TALER_EXCHANGE_deposits_get_cancel (dwh);
- return;
- }
- case MHD_HTTP_ACCEPTED:
- {
- /* Transaction known, but not executed yet */
- bool no_legi = false;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_timestamp (
- "execution_time",
- &dr.details.accepted.execution_time),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto (
- "account_pub",
- &dr.details.accepted.account_pub),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint64 (
- "requirement_row",
- &dr.details.accepted.requirement_row),
- &no_legi),
- GNUNET_JSON_spec_bool (
- "kyc_ok",
- &dr.details.accepted.kyc_ok),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (no_legi)
- dr.details.accepted.requirement_row = 0;
- dwh->cb (dwh->cb_cls,
- &dr);
- TALER_EXCHANGE_deposits_get_cancel (dwh);
- return;
- }
- case MHD_HTTP_BAD_REQUEST:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.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:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- /* 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 */
- break;
- case MHD_HTTP_NOT_FOUND:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- /* Exchange does not know about transaction;
- we should pass the reply to the application */
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.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 */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange GET deposits\n",
- (unsigned int) response_code,
- (int) dr.hr.ec);
- GNUNET_break_op (0);
- break;
- }
- dwh->cb (dwh->cb_cls,
- &dr);
- TALER_EXCHANGE_deposits_get_cancel (dwh);
-}
-
-
-struct TALER_EXCHANGE_DepositGetHandle *
-TALER_EXCHANGE_deposits_get (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_MerchantPrivateKeyP *merchant_priv,
- const struct TALER_MerchantWireHashP *h_wire,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- struct GNUNET_TIME_Relative timeout,
- TALER_EXCHANGE_DepositGetCallback cb,
- void *cb_cls)
-{
- struct TALER_MerchantPublicKeyP merchant;
- struct TALER_MerchantSignatureP merchant_sig;
- struct TALER_EXCHANGE_DepositGetHandle *dwh;
- CURL *eh;
- char arg_str[(sizeof (struct TALER_CoinSpendPublicKeyP)
- + sizeof (struct TALER_MerchantWireHashP)
- + sizeof (struct TALER_MerchantPublicKeyP)
- + sizeof (struct TALER_PrivateContractHashP)
- + sizeof (struct TALER_MerchantSignatureP)) * 2 + 48];
- unsigned int tms
- = (unsigned int) timeout.rel_value_us
- / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
-
- GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
- &merchant.eddsa_pub);
- TALER_merchant_deposit_sign (h_contract_terms,
- h_wire,
- coin_pub,
- merchant_priv,
- &merchant_sig);
- {
- char cpub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
- char mpub_str[sizeof (struct TALER_MerchantPublicKeyP) * 2];
- char msig_str[sizeof (struct TALER_MerchantSignatureP) * 2];
- char chash_str[sizeof (struct TALER_PrivateContractHashP) * 2];
- char whash_str[sizeof (struct TALER_MerchantWireHashP) * 2];
- char timeout_str[24];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (h_wire,
- sizeof (*h_wire),
- whash_str,
- sizeof (whash_str));
- *end = '\0';
- end = GNUNET_STRINGS_data_to_string (&merchant,
- sizeof (merchant),
- mpub_str,
- sizeof (mpub_str));
- *end = '\0';
- end = GNUNET_STRINGS_data_to_string (h_contract_terms,
- sizeof (*h_contract_terms),
- chash_str,
- sizeof (chash_str));
- *end = '\0';
- end = GNUNET_STRINGS_data_to_string (coin_pub,
- sizeof (*coin_pub),
- cpub_str,
- sizeof (cpub_str));
- *end = '\0';
- end = GNUNET_STRINGS_data_to_string (&merchant_sig,
- sizeof (merchant_sig),
- msig_str,
- sizeof (msig_str));
- *end = '\0';
- if (GNUNET_TIME_relative_is_zero (timeout))
- {
- timeout_str[0] = '\0';
- }
- else
- {
- GNUNET_snprintf (
- timeout_str,
- sizeof (timeout_str),
- "%u",
- tms);
- }
-
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "deposits/%s/%s/%s/%s?merchant_sig=%s%s%s",
- whash_str,
- mpub_str,
- chash_str,
- cpub_str,
- msig_str,
- 0 == tms
- ? ""
- : "&timeout_ms=",
- timeout_str);
- }
-
- dwh = GNUNET_new (struct TALER_EXCHANGE_DepositGetHandle);
- dwh->cb = cb;
- dwh->cb_cls = cb_cls;
- dwh->url = TALER_url_join (url,
- arg_str,
- NULL);
- if (NULL == dwh->url)
- {
- GNUNET_free (dwh);
- return NULL;
- }
- dwh->h_wire = *h_wire;
- dwh->h_contract_terms = *h_contract_terms;
- dwh->coin_pub = *coin_pub;
- eh = TALER_EXCHANGE_curl_easy_get_ (dwh->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- GNUNET_free (dwh->url);
- GNUNET_free (dwh);
- return NULL;
- }
- if (0 != tms)
- {
- GNUNET_break (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_TIMEOUT_MS,
- (long) (tms + 100L)));
- }
- dwh->job = GNUNET_CURL_job_add (ctx,
- eh,
- &handle_deposit_wtid_finished,
- dwh);
- dwh->keys = TALER_EXCHANGE_keys_incref (keys);
- return dwh;
-}
-
-
-void
-TALER_EXCHANGE_deposits_get_cancel (struct TALER_EXCHANGE_DepositGetHandle *dwh)
-{
- if (NULL != dwh->job)
- {
- GNUNET_CURL_job_cancel (dwh->job);
- dwh->job = NULL;
- }
- GNUNET_free (dwh->url);
- TALER_curl_easy_post_finished (&dwh->ctx);
- TALER_EXCHANGE_keys_decref (dwh->keys);
- GNUNET_free (dwh);
-}
-
-
-/* end of exchange_api_deposits_get.c */
diff --git a/src/lib/exchange_api_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c b/src/lib/exchange_api_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c
@@ -0,0 +1,386 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023, 2024 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_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c
+ * @brief Implementation of the /aml/$OFFICER_PUB/attributes request
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A GET /aml/$OFFICER_PUB/attributes Handle
+ */
+struct TALER_EXCHANGE_LookupKycAttributes
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_LookupKycAttributesCallback attributes_cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *attributes_cb_cls;
+
+ /**
+ * HTTP headers for the job.
+ */
+ struct curl_slist *job_headers;
+
+};
+
+
+/**
+ * Parse AML decision summary array.
+ *
+ * @param[in,out] lh handle to use for allocations
+ * @param jdetails JSON array with AML decision summaries
+ * @param[out] detail_ar where to write the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_kyc_attributes (
+ struct TALER_EXCHANGE_LookupKycAttributes *lh,
+ const json_t *jdetails,
+ struct TALER_EXCHANGE_KycAttributeDetail *detail_ar)
+{
+ json_t *obj;
+ size_t idx;
+
+ json_array_foreach (jdetails, idx, obj)
+ {
+ struct TALER_EXCHANGE_KycAttributeDetail *detail
+ = &detail_ar[idx];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint64 ("rowid",
+ &detail->row_id),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("provider_name",
+ &detail->provider_name),
+ NULL),
+ GNUNET_JSON_spec_object_const ("attributes",
+ &detail->attributes),
+ GNUNET_JSON_spec_timestamp ("collection_time",
+ &detail->collection_time),
+ 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_attributes_ok (struct TALER_EXCHANGE_LookupKycAttributes *lh,
+ const json_t *json)
+{
+ struct TALER_EXCHANGE_KycAttributesResponse lr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ const json_t *jdetails;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("details",
+ &jdetails),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ lr.details.ok.kyc_attributes_length
+ = json_array_size (jdetails);
+ {
+ struct TALER_EXCHANGE_KycAttributeDetail details[
+ GNUNET_NZL (lr.details.ok.kyc_attributes_length)];
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+
+ memset (details,
+ 0,
+ sizeof (details));
+ lr.details.ok.kyc_attributes = details;
+ ret = parse_kyc_attributes (lh,
+ jdetails,
+ details);
+ if (GNUNET_OK == ret)
+ {
+ lh->attributes_cb (lh->attributes_cb_cls,
+ &lr);
+ lh->attributes_cb = NULL;
+ }
+ return ret;
+ }
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /aml/$OFFICER_PUB/attributes request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_LookupKycAttributes`
+ * @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_LookupKycAttributes *lh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_KycAttributesResponse 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_attributes_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->attributes_cb);
+ TALER_EXCHANGE_lookup_kyc_attributes_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 attributes\n",
+ (unsigned int) response_code,
+ (int) lr.hr.ec);
+ break;
+ }
+ if (NULL != lh->attributes_cb)
+ lh->attributes_cb (lh->attributes_cb_cls,
+ &lr);
+ TALER_EXCHANGE_lookup_kyc_attributes_cancel (lh);
+}
+
+
+struct TALER_EXCHANGE_LookupKycAttributes *
+TALER_EXCHANGE_lookup_kyc_attributes (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_url,
+ const struct TALER_NormalizedPaytoHashP *h_payto,
+ uint64_t offset,
+ int64_t limit,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ TALER_EXCHANGE_LookupKycAttributesCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_LookupKycAttributes *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 payto_s[sizeof (*h_payto) * 2];
+ char pub_str[sizeof (officer_pub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ h_payto,
+ sizeof (*h_payto),
+ payto_s,
+ sizeof (payto_s));
+ *end = '\0';
+ 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/attributes/%s",
+ pub_str,
+ payto_s);
+ }
+ lh = GNUNET_new (struct TALER_EXCHANGE_LookupKycAttributes);
+ lh->attributes_cb = cb;
+ lh->attributes_cb_cls = cb_cls;
+ {
+ char limit_s[24];
+ char offset_s[24];
+
+ GNUNET_snprintf (limit_s,
+ sizeof (limit_s),
+ "%lld",
+ (long long) limit);
+ GNUNET_snprintf (offset_s,
+ sizeof (offset_s),
+ "%llu",
+ (unsigned long long) offset);
+ lh->url = TALER_url_join (
+ exchange_url,
+ arg_str,
+ "limit",
+ limit_s,
+ "offset",
+ offset_s,
+ "h_payto",
+ 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_kyc_attributes_cancel (
+ struct TALER_EXCHANGE_LookupKycAttributes *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_kyc_attributes.c */
diff --git a/src/lib/exchange_api_get-aml-OFFICER_PUB-decisions.c b/src/lib/exchange_api_get-aml-OFFICER_PUB-decisions.c
@@ -0,0 +1,626 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023, 2024 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_get-aml-OFFICER_PUB-decisions.c
+ * @brief Implementation of the /aml/$OFFICER_PUB/decisions request
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A GET /aml/$OFFICER_PUB/decisions 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;
+
+ /**
+ * Array with measure information.
+ */
+ struct TALER_EXCHANGE_MeasureInformation *mip;
+
+ /**
+ * Array with rule information.
+ */
+ struct TALER_EXCHANGE_KycRule *rp;
+
+ /**
+ * Array with all the measures (of all the rules!).
+ */
+ const char **mp;
+};
+
+
+/**
+ * Parse AML limits array.
+ *
+ * @param[in,out] lh handle to use for allocations
+ * @param jlimits JSON array with AML rules
+ * @param[out] ds where to write the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_limits (struct TALER_EXCHANGE_LookupAmlDecisions *lh,
+ const json_t *jlimits,
+ struct TALER_EXCHANGE_AmlDecision *ds)
+{
+ struct TALER_EXCHANGE_LegitimizationRuleSet *limits
+ = &ds->limits;
+ const json_t *jrules;
+ const json_t *jmeasures;
+ size_t mip_len;
+ size_t rule_len;
+ size_t total;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("expiration_time",
+ &limits->expiration_time),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("successor_measure",
+ &limits->successor_measure),
+ NULL),
+ GNUNET_JSON_spec_array_const ("rules",
+ &jrules),
+ GNUNET_JSON_spec_object_const ("custom_measures",
+ &jmeasures),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jlimits,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ mip_len = json_object_size (jmeasures);
+ lh->mip = GNUNET_new_array (mip_len,
+ struct TALER_EXCHANGE_MeasureInformation);
+ limits->measures = lh->mip;
+ limits->measures_length = mip_len;
+
+ {
+ const char *measure_name;
+ const json_t *jmeasure;
+
+ json_object_foreach ((json_t*) jmeasures,
+ measure_name,
+ jmeasure)
+ {
+ struct TALER_EXCHANGE_MeasureInformation *mi
+ = &lh->mip[--mip_len];
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("check_name",
+ &mi->check_name),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("prog_name",
+ &mi->prog_name),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("context",
+ &mi->context),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jmeasure,
+ ispec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ mi->measure_name = measure_name;
+ }
+ }
+
+ total = 0;
+
+ {
+ const json_t *rule;
+ size_t idx;
+
+ json_array_foreach ((json_t *) jrules,
+ idx,
+ rule)
+ {
+ total += json_array_size (json_object_get (rule,
+ "measures"));
+ }
+ }
+
+ rule_len = json_array_size (jrules);
+ lh->rp = GNUNET_new_array (rule_len,
+ struct TALER_EXCHANGE_KycRule);
+ lh->mp = GNUNET_new_array (total,
+ const char *);
+
+ {
+ const json_t *rule;
+ size_t idx;
+
+ json_array_foreach ((json_t *) jrules,
+ idx,
+ rule)
+ {
+ const json_t *smeasures;
+ struct TALER_EXCHANGE_KycRule *r
+ = &lh->rp[--rule_len];
+ struct GNUNET_JSON_Specification ispec[] = {
+ TALER_JSON_spec_kycte ("operation_type",
+ &r->operation_type),
+ TALER_JSON_spec_amount_any ("threshold",
+ &r->threshold),
+ GNUNET_JSON_spec_relative_time ("timeframe",
+ &r->timeframe),
+ GNUNET_JSON_spec_array_const ("measures",
+ &smeasures),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("exposed",
+ &r->exposed),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("is_and_combinator",
+ &r->is_and_combinator),
+ NULL),
+ GNUNET_JSON_spec_uint32 ("display_priority",
+ &r->display_priority),
+ GNUNET_JSON_spec_end ()
+ };
+ size_t mlen;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (rule,
+ ispec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ mlen = json_array_size (smeasures);
+ GNUNET_assert (mlen <= total);
+ total -= mlen;
+
+ {
+ size_t midx;
+ const json_t *smeasure;
+
+ json_array_foreach (smeasures,
+ midx,
+ smeasure)
+ {
+ const char *sval = json_string_value (smeasure);
+
+ if (NULL == sval)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ lh->mp[total + midx] = sval;
+ if (0 == strcasecmp (sval,
+ "verboten"))
+ r->verboten = true;
+ }
+ }
+ r->measures = &lh->mp[total];
+ r->measures_length = r->verboten ? 0 : total;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse AML decision summary array.
+ *
+ * @param[in,out] lh handle to use for allocations
+ * @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 (
+ struct TALER_EXCHANGE_LookupAmlDecisions *lh,
+ const json_t *decisions,
+ struct TALER_EXCHANGE_AmlDecision *decision_ar)
+{
+ json_t *obj;
+ size_t idx;
+
+ json_array_foreach (decisions, idx, obj)
+ {
+ struct TALER_EXCHANGE_AmlDecision *decision = &decision_ar[idx];
+ const json_t *jlimits;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_payto",
+ &decision->h_payto),
+ GNUNET_JSON_spec_uint64 ("rowid",
+ &decision->rowid),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("justification",
+ &decision->justification),
+ NULL),
+ GNUNET_JSON_spec_timestamp ("decision_time",
+ &decision->decision_time),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("properties",
+ &decision->jproperties),
+ NULL),
+ GNUNET_JSON_spec_object_const ("limits",
+ &jlimits),
+ GNUNET_JSON_spec_bool ("to_investigate",
+ &decision->to_investigate),
+ GNUNET_JSON_spec_bool ("is_active",
+ &decision->is_active),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (obj,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ parse_limits (lh,
+ jlimits,
+ decision))
+ {
+ 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_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_AmlDecision decisions[
+ GNUNET_NZL (lr.details.ok.decisions_length)];
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+
+ memset (decisions,
+ 0,
+ sizeof (decisions));
+ lr.details.ok.decisions = decisions;
+ ret = parse_aml_decisions (lh,
+ records,
+ decisions);
+ if (GNUNET_OK == ret)
+ {
+ lh->decisions_cb (lh->decisions_cb_cls,
+ &lr);
+ lh->decisions_cb = NULL;
+ }
+ GNUNET_free (lh->mip);
+ GNUNET_free (lh->rp);
+ GNUNET_free (lh->mp);
+ 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:
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ 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,
+ const struct TALER_NormalizedPaytoHashP *h_payto,
+ enum TALER_EXCHANGE_YesNoAll investigation_only,
+ enum TALER_EXCHANGE_YesNoAll active_only,
+ uint64_t offset,
+ int64_t limit,
+ 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];
+
+ 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",
+ pub_str);
+ }
+ lh = GNUNET_new (struct TALER_EXCHANGE_LookupAmlDecisions);
+ lh->decisions_cb = cb;
+ lh->decisions_cb_cls = cb_cls;
+ {
+ char limit_s[24];
+ char offset_s[24];
+ char payto_s[sizeof (*h_payto) * 2];
+ char *end;
+
+ if (NULL != h_payto)
+ {
+ end = GNUNET_STRINGS_data_to_string (
+ h_payto,
+ sizeof (*h_payto),
+ payto_s,
+ sizeof (payto_s));
+ *end = '\0';
+ }
+ GNUNET_snprintf (limit_s,
+ sizeof (limit_s),
+ "%lld",
+ (long long) limit);
+ GNUNET_snprintf (offset_s,
+ sizeof (offset_s),
+ "%llu",
+ (unsigned long long) offset);
+ lh->url = TALER_url_join (
+ exchange_url,
+ arg_str,
+ "limit",
+ limit_s,
+ "offset",
+ ( ( (limit < 0) && (UINT64_MAX == offset) ) ||
+ ( (limit > 0) && (0 == offset) ) )
+ ? NULL
+ : offset_s,
+ "h_payto",
+ NULL != h_payto
+ ? payto_s
+ : NULL,
+ "active",
+ TALER_EXCHANGE_YNA_ALL != active_only
+ ? TALER_yna_to_string (active_only)
+ : NULL,
+ "investigation",
+ TALER_EXCHANGE_YNA_ALL != investigation_only
+ ? TALER_yna_to_string (investigation_only)
+ : 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_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_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c b/src/lib/exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c
@@ -0,0 +1,317 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023, 2024 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_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c
+ * @brief Implementation of the /aml/$OFFICER_PUB/kyc-statistics/$NAME request
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A GET /aml/$OFFICER_PUB/kyc-statistics/$NAME Handle
+ */
+struct TALER_EXCHANGE_KycGetStatisticsHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_KycStatisticsCallback stats_cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *stats_cb_cls;
+
+ /**
+ * HTTP headers for the job.
+ */
+ struct curl_slist *job_headers;
+
+};
+
+
+/**
+ * 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_stats_ok (
+ struct TALER_EXCHANGE_KycGetStatisticsHandle *lh,
+ const json_t *json)
+{
+ struct TALER_EXCHANGE_KycGetStatisticsResponse lr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ uint64_t cnt;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint64 ("counter",
+ &cnt),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ lr.details.ok.counter = (unsigned long long) cnt;
+ lh->stats_cb (lh->stats_cb_cls,
+ &lr);
+ lh->stats_cb = NULL;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /aml/$OFFICER_PUB/kyc-statistics/$NAME request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_KycGetStatisticsHandle`
+ * @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_KycGetStatisticsHandle *lh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_KycGetStatisticsResponse 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_stats_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->stats_cb);
+ TALER_EXCHANGE_kyc_get_statistics_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_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 GET KYC statistics\n",
+ (unsigned int) response_code,
+ (int) lr.hr.ec);
+ break;
+ }
+ if (NULL != lh->stats_cb)
+ lh->stats_cb (lh->stats_cb_cls,
+ &lr);
+ TALER_EXCHANGE_kyc_get_statistics_cancel (lh);
+}
+
+
+struct TALER_EXCHANGE_KycGetStatisticsHandle *
+TALER_EXCHANGE_kyc_get_statistics (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_url,
+ const char *name,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ TALER_EXCHANGE_KycStatisticsCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_KycGetStatisticsHandle *lh;
+ CURL *eh;
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+ struct TALER_AmlOfficerSignatureP officer_sig;
+ char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2
+ + 32];
+ char sd_str[32];
+ char ed_str[32];
+ const char *sd = NULL;
+ const char *ed = NULL;
+
+ if (! GNUNET_TIME_absolute_is_zero (start_date.abs_time))
+ {
+ unsigned long long sec;
+
+ sec = (unsigned long long) GNUNET_TIME_timestamp_to_s (start_date);
+ GNUNET_snprintf (sd_str,
+ sizeof (sd_str),
+ "%llu",
+ sec);
+ sd = sd_str;
+ }
+ if (! GNUNET_TIME_absolute_is_never (end_date.abs_time))
+ {
+ unsigned long long sec;
+
+ sec = (unsigned long long) GNUNET_TIME_timestamp_to_s (end_date);
+ GNUNET_snprintf (ed_str,
+ sizeof (ed_str),
+ "%llu",
+ sec);
+ ed = ed_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/kyc-statistics/%s",
+ pub_str,
+ name);
+ }
+ lh = GNUNET_new (struct TALER_EXCHANGE_KycGetStatisticsHandle);
+ lh->stats_cb = cb;
+ lh->stats_cb_cls = cb_cls;
+ lh->url = TALER_url_join (exchange_url,
+ arg_str,
+ "start_date",
+ sd,
+ "end_date",
+ ed,
+ 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_kyc_get_statistics_cancel (
+ struct TALER_EXCHANGE_KycGetStatisticsHandle *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_get_kyc_statistics.c */
diff --git a/src/lib/exchange_api_get-aml-OFFICER_PUB-legitimizations.c b/src/lib/exchange_api_get-aml-OFFICER_PUB-legitimizations.c
@@ -0,0 +1,510 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 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_get-aml-OFFICER_PUB-legitimizations.c
+ * @brief Implementation of the GET /aml/$OFFICER_PUB/legitimizations requests
+ * @author Christian Grothoff
+ */
+#include "taler/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/taler_exchange_service.h"
+#include "taler/taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * Handle for an operation to GET /aml/$OFFICER_PUB/legitimizations.
+ */
+struct TALER_EXCHANGE_GetAmlLegitimizationsHandle
+{
+
+ /**
+ * The exchange base URL for this request.
+ */
+ char *exchange_base_url;
+
+ /**
+ * Our execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Signature of the AML officer.
+ */
+ struct TALER_AmlOfficerSignatureP officer_sig;
+
+ /**
+ * Public key of the AML officer.
+ */
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_GetAmlLegitimizationsCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_RESULT_CLOSURE *cb_cls;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * HTTP headers for the job.
+ */
+ struct curl_slist *job_headers;
+
+ /**
+ * Request options.
+ */
+ struct
+ {
+ /**
+ * Limit on number of results.
+ */
+ int64_t limit;
+
+ /**
+ * Row offset from which to return results.
+ */
+ uint64_t offset;
+
+ /**
+ * Hash of payto URI to filter by, NULL for no filter.
+ */
+ const struct TALER_NormalizedPaytoHashP *h_payto;
+
+ /**
+ * Activity filter.
+ */
+ enum TALER_EXCHANGE_YesNoAll active;
+
+ } options;
+
+};
+
+
+/**
+ * Parse a single measure details entry from JSON.
+ *
+ * @param md_json JSON object to parse
+ * @param[out] md where to store the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_measure_details (
+ const json_t *md_json,
+ struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails *md)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_payto",
+ &md->h_payto),
+ GNUNET_JSON_spec_uint64 ("rowid",
+ &md->rowid),
+ GNUNET_JSON_spec_timestamp ("start_time",
+ &md->start_time),
+ GNUNET_JSON_spec_object_const ("measures",
+ &md->measures),
+ GNUNET_JSON_spec_bool ("is_finished",
+ &md->is_finished),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (md_json,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * We received an #MHD_HTTP_OK status code. Handle the JSON
+ * response.
+ *
+ * @param algh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_aml_legitimizations_get_ok (
+ struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_GetAmlLegitimizationsResponse result = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ const json_t *measures_array;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("measures",
+ &measures_array),
+ GNUNET_JSON_spec_end ()
+ };
+ struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails *measures;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ result.details.ok.measures_length = json_array_size (measures_array);
+ if (0 == result.details.ok.measures_length)
+ {
+ measures = NULL;
+ }
+ else
+ {
+ measures
+ = GNUNET_new_array (
+ result.details.ok.measures_length,
+ struct TALER_EXCHANGE_GetAmlLegitimizationsMeasureDetails);
+ }
+ for (size_t i = 0; i < result.details.ok.measures_length; i++)
+ {
+ const json_t *measure_json = json_array_get (measures_array,
+ i);
+
+ if (GNUNET_OK !=
+ parse_measure_details (measure_json,
+ &measures[i]))
+ {
+ GNUNET_free (measures);
+ return GNUNET_SYSERR;
+ }
+ }
+ result.details.ok.measures = measures;
+ algh->cb (algh->cb_cls,
+ &result);
+ algh->cb = NULL;
+ GNUNET_free (measures);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /aml/$OFFICER_PUB/legitimizations GET request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_GetAmlLegitimizationsHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_aml_legitimizations_get_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_GetAmlLegitimizationsResponse result = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ algh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ result.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ handle_aml_legitimizations_get_ok (algh,
+ j))
+ {
+ result.hr.http_status = 0;
+ result.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ }
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ /* can happen */
+ 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 */
+ result.hr.ec = TALER_JSON_get_error_code (j);
+ result.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ /* Invalid officer credentials */
+ result.hr.ec = TALER_JSON_get_error_code (j);
+ result.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* Officer not authorized for this operation */
+ result.hr.ec = TALER_JSON_get_error_code (j);
+ result.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Officer not found */
+ result.hr.ec = TALER_JSON_get_error_code (j);
+ result.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 */
+ result.hr.ec = TALER_JSON_get_error_code (j);
+ result.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ result.hr.ec = TALER_JSON_get_error_code (j);
+ result.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for GET %s\n",
+ (unsigned int) response_code,
+ (int) result.hr.ec,
+ algh->url);
+ break;
+ }
+ if (NULL != algh->cb)
+ {
+ algh->cb (algh->cb_cls,
+ &result);
+ algh->cb = NULL;
+ }
+ TALER_EXCHANGE_get_aml_legitimizations_cancel (algh);
+}
+
+
+struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *
+TALER_EXCHANGE_get_aml_legitimizations_create (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_base_url,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv)
+{
+ struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh;
+
+ algh = GNUNET_new (struct TALER_EXCHANGE_GetAmlLegitimizationsHandle);
+ algh->ctx = ctx;
+ algh->exchange_base_url = GNUNET_strdup (exchange_base_url);
+ GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
+ &algh->officer_pub.eddsa_pub);
+ TALER_officer_aml_query_sign (officer_priv,
+ &algh->officer_sig);
+ algh->options.limit = -20; /* Default to last 20 entries */
+ algh->options.offset = INT64_MAX; /* Default to maximum row id */
+ algh->options.active = TALER_EXCHANGE_YNA_ALL; /* Default to all */
+ return algh;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_get_aml_legitimizations_set_options_ (
+ struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh,
+ unsigned int num_options,
+ const struct TALER_EXCHANGE_GetAmlLegitimizationsOptionValue *options)
+{
+ for (unsigned int i = 0; i < num_options; i++)
+ {
+ switch (options[i].option)
+ {
+ case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_END:
+ return GNUNET_OK;
+ case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_LIMIT:
+ algh->options.limit = options[i].details.limit;
+ break;
+ case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_OFFSET:
+ algh->options.offset = options[i].details.offset;
+ break;
+ case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_H_PAYTO:
+ algh->options.h_payto = options[i].details.h_payto;
+ break;
+ case TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_OPTION_ACTIVE:
+ algh->options.active = options[i].details.active;
+ break;
+ default:
+ GNUNET_break (0);
+ return GNUNET_NO;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+enum TALER_EXCHANGE_AmlLegitimizationsGetStartError
+TALER_EXCHANGE_get_aml_legitimizations_start (
+ struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh,
+ TALER_EXCHANGE_GetAmlLegitimizationsCallback cb,
+ TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_RESULT_CLOSURE *cb_cls)
+{
+ char officer_pub_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2];
+ char arg_str[sizeof (officer_pub_str) + 64];
+ char limit_str[24];
+ char offset_str[24];
+ char paytoh_str[sizeof (struct TALER_NormalizedPaytoHashP) * 2];
+
+ if (NULL != algh->job)
+ {
+ GNUNET_break (0);
+ return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_E_AGAIN;
+ }
+ algh->cb = cb;
+ algh->cb_cls = cb_cls;
+ if (algh->options.offset > INT64_MAX)
+ {
+ GNUNET_break (0);
+ algh->options.offset = INT64_MAX;
+ }
+ {
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &algh->officer_pub,
+ sizeof (algh->officer_pub),
+ officer_pub_str,
+ sizeof (officer_pub_str));
+ *end = '\0';
+ }
+ if (NULL != algh->options.h_payto)
+ {
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ algh->options.h_payto,
+ sizeof (struct TALER_NormalizedPaytoHashP),
+ paytoh_str,
+ sizeof (paytoh_str));
+ *end = '\0';
+ }
+ /* Build query parameters */
+ GNUNET_snprintf (offset_str,
+ sizeof (offset_str),
+ "%llu",
+ (unsigned long long) algh->options.offset);
+ GNUNET_snprintf (limit_str,
+ sizeof (limit_str),
+ "%lld",
+ (long long) algh->options.limit);
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "aml/%s/legitimizations",
+ officer_pub_str);
+ algh->url = TALER_url_join (algh->exchange_base_url,
+ arg_str,
+ "limit",
+ limit_str,
+ "offset",
+ ( (algh->options.limit > 0) &&
+ (0 == algh->options.offset) ) ||
+ ( (algh->options.limit <= 0) &&
+ (INT64_MAX <= algh->options.offset) )
+ ? NULL
+ : offset_str,
+ "h_payto",
+ NULL == algh->options.h_payto
+ ? NULL
+ : paytoh_str,
+ "active",
+ TALER_EXCHANGE_YNA_ALL == algh->options.active
+ ? NULL
+ : TALER_yna_to_string (algh->options.active),
+ NULL);
+ if (NULL == algh->url)
+ {
+ GNUNET_break (0);
+ return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_E_INTERNAL;
+ }
+
+ {
+ CURL *eh;
+
+ eh = TALER_EXCHANGE_curl_easy_get_ (algh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (algh->url);
+ algh->url = NULL;
+ return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_E_INTERNAL;
+ }
+
+ /* Add authentication header for AML officer */
+ {
+ char *hdr;
+ char sig_str[sizeof (algh->officer_sig) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &algh->officer_sig,
+ sizeof (algh->officer_sig),
+ sig_str,
+ sizeof (sig_str));
+ *end = '\0';
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ TALER_AML_OFFICER_SIGNATURE_HEADER,
+ sig_str);
+ algh->job_headers = curl_slist_append (NULL,
+ hdr);
+ GNUNET_free (hdr);
+ }
+ algh->job
+ = GNUNET_CURL_job_add2 (
+ algh->ctx,
+ eh,
+ algh->job_headers,
+ &handle_aml_legitimizations_get_finished,
+ algh);
+ }
+ return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_OK;
+}
+
+
+void
+TALER_EXCHANGE_get_aml_legitimizations_cancel (
+ struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh)
+{
+ if (NULL != algh->job)
+ {
+ GNUNET_CURL_job_cancel (algh->job);
+ algh->job = NULL;
+ }
+ curl_slist_free_all (algh->job_headers);
+ GNUNET_free (algh->exchange_base_url);
+ GNUNET_free (algh->url);
+ GNUNET_free (algh);
+}
+
+
+/* end of exchange_api_aml_legitimizations_get.c */
diff --git a/src/lib/exchange_api_get-aml-OFFICER_PUB-measures.c b/src/lib/exchange_api_get-aml-OFFICER_PUB-measures.c
@@ -0,0 +1,650 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023, 2024 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_get-aml-OFFICER_PUB-measures.c
+ * @brief Implementation of the GET /aml/$OFFICER_PUB/measures request
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * Scrap buffer of temporary arrays.
+ */
+struct Scrap
+{
+ /**
+ * Kept in DLL.
+ */
+ struct Scrap *next;
+
+ /**
+ * Kept in DLL.
+ */
+ struct Scrap *prev;
+
+ /**
+ * Pointer to our allocation.
+ */
+ const char **ptr;
+};
+
+
+/**
+ * @brief A GET /aml/$OFFICER_PUB/measures Handle
+ */
+struct TALER_EXCHANGE_AmlGetMeasuresHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_AmlMeasuresCallback measures_cb;
+
+ /**
+ * Closure for @e measures_cb.
+ */
+ void *measures_cb_cls;
+
+ /**
+ * HTTP headers for the job.
+ */
+ struct curl_slist *job_headers;
+
+ /**
+ * Head of scrap list.
+ */
+ struct Scrap *scrap_head;
+
+ /**
+ * Tail of scrap list.
+ */
+ struct Scrap *scrap_tail;
+};
+
+
+/**
+ * Parse AML measures.
+ *
+ * @param jroots JSON object with measure data
+ * @param[out] roots where to write the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_aml_roots (
+ const json_t *jroots,
+ struct TALER_EXCHANGE_AvailableAmlMeasures *roots)
+{
+ const json_t *obj;
+ const char *name;
+ size_t idx = 0;
+
+ json_object_foreach ((json_t *) jroots, name, obj)
+ {
+ struct TALER_EXCHANGE_AvailableAmlMeasures *root
+ = &roots[idx++];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("check_name",
+ &root->check_name),
+ GNUNET_JSON_spec_string ("prog_name",
+ &root->prog_name),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("context",
+ &root->context),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (obj,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ root->measure_name = name;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Create array of length @a len in scrap book.
+ *
+ * @param[in,out] lh context for allocations
+ * @param len length of array
+ * @return scrap array
+ */
+static const char **
+make_scrap (
+ struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh,
+ unsigned int len)
+{
+ struct Scrap *s = GNUNET_new (struct Scrap);
+
+ s->ptr = GNUNET_new_array (len,
+ const char *);
+ GNUNET_CONTAINER_DLL_insert (lh->scrap_head,
+ lh->scrap_tail,
+ s);
+ return s->ptr;
+}
+
+
+/**
+ * Free all scrap space.
+ *
+ * @param[in,out] lh scrap context
+ */
+static void
+free_scrap (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh)
+{
+ struct Scrap *s;
+
+ while (NULL != (s = lh->scrap_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (lh->scrap_head,
+ lh->scrap_tail,
+ s);
+ GNUNET_free (s->ptr);
+ GNUNET_free (s);
+ }
+}
+
+
+/**
+ * Convert JSON array of strings to string array.
+ *
+ * @param j JSON array to convert
+ * @param[out] a array to initialize
+ * @return true on success
+ */
+static bool
+j_to_a (const json_t *j,
+ const char **a)
+{
+ const json_t *e;
+ size_t idx;
+
+ json_array_foreach ((json_t *) j, idx, e)
+ {
+ if (NULL == (a[idx] = json_string_value (e)))
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * Parse AML programs.
+ *
+ * @param[in,out] lh context for allocations
+ * @param jprogs JSON object with program data
+ * @param[out] progs where to write the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_aml_programs (
+ struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh,
+ const json_t *jprogs,
+ struct TALER_EXCHANGE_AvailableAmlPrograms *progs)
+{
+ const json_t *obj;
+ const char *name;
+ size_t idx = 0;
+
+ json_object_foreach ((json_t *) jprogs, name, obj)
+ {
+ struct TALER_EXCHANGE_AvailableAmlPrograms *prog
+ = &progs[idx++];
+ const json_t *jcontext;
+ const json_t *jinputs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("description",
+ &prog->description),
+ GNUNET_JSON_spec_array_const ("context",
+ &jcontext),
+ GNUNET_JSON_spec_array_const ("inputs",
+ &jinputs),
+ GNUNET_JSON_spec_end ()
+ };
+ unsigned int len;
+ const char **ptr;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (obj,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ prog->prog_name = name;
+ prog->contexts_length
+ = (unsigned int) json_array_size (jcontext);
+ prog->inputs_length
+ = (unsigned int) json_array_size (jinputs);
+ len = prog->contexts_length + prog->inputs_length;
+ if ( ((unsigned long long) len) !=
+ (unsigned long long) json_array_size (jcontext)
+ + (unsigned long long) json_array_size (jinputs) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ ptr = make_scrap (lh,
+ len);
+ prog->contexts = ptr;
+ if (! j_to_a (jcontext,
+ prog->contexts))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ prog->inputs = &ptr[prog->contexts_length];
+ if (! j_to_a (jinputs,
+ prog->inputs))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse AML measures.
+ *
+ * @param[in,out] lh context for allocations
+ * @param jchecks JSON object with measure data
+ * @param[out] checks where to write the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_aml_checks (
+ struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh,
+ const json_t *jchecks,
+ struct TALER_EXCHANGE_AvailableKycChecks *checks)
+{
+ const json_t *obj;
+ const char *name;
+ size_t idx = 0;
+
+ json_object_foreach ((json_t *) jchecks, name, obj)
+ {
+ struct TALER_EXCHANGE_AvailableKycChecks *check
+ = &checks[idx++];
+ const json_t *jrequires;
+ const json_t *joutputs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("description",
+ &check->description),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("description_i18n",
+ &check->description_i18n),
+ NULL),
+ GNUNET_JSON_spec_array_const ("requires",
+ &jrequires),
+ GNUNET_JSON_spec_array_const ("outputs",
+ &joutputs),
+ GNUNET_JSON_spec_string ("fallback",
+ &check->fallback),
+ GNUNET_JSON_spec_end ()
+ };
+ unsigned int len;
+ const char **ptr;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (obj,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ check->check_name = name;
+
+ check->requires_length
+ = (unsigned int) json_array_size (jrequires);
+ check->outputs_length
+ = (unsigned int) json_array_size (joutputs);
+ len = check->requires_length + check->outputs_length;
+ if ( ((unsigned long long) len) !=
+ (unsigned long long) json_array_size (jrequires)
+ + (unsigned long long) json_array_size (joutputs) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ ptr = make_scrap (lh,
+ len);
+ check->requires = ptr;
+ if (! j_to_a (jrequires,
+ check->requires))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ check->outputs = &ptr[check->requires_length];
+ if (! j_to_a (joutputs,
+ check->outputs))
+ {
+ 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_measures_ok (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh,
+ const json_t *json)
+{
+ struct TALER_EXCHANGE_AmlGetMeasuresResponse lr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ const json_t *jroots;
+ const json_t *jprograms;
+ const json_t *jchecks;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_object_const ("roots",
+ &jroots),
+ GNUNET_JSON_spec_object_const ("programs",
+ &jprograms),
+ GNUNET_JSON_spec_object_const ("checks",
+ &jchecks),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ lr.details.ok.roots_length
+ = (unsigned int) json_object_size (jroots);
+ lr.details.ok.programs_length
+ = (unsigned int) json_object_size (jprograms);
+ lr.details.ok.checks_length
+ = (unsigned int) json_object_size (jchecks);
+ if ( ( ((size_t) lr.details.ok.roots_length)
+ != json_object_size (jroots)) ||
+ ( ((size_t) lr.details.ok.programs_length)
+ != json_object_size (jprograms)) ||
+ ( ((size_t) lr.details.ok.checks_length)
+ != json_object_size (jchecks)) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ struct TALER_EXCHANGE_AvailableAmlMeasures roots[
+ GNUNET_NZL (lr.details.ok.roots_length)];
+ struct TALER_EXCHANGE_AvailableAmlPrograms progs[
+ GNUNET_NZL (lr.details.ok.programs_length)];
+ struct TALER_EXCHANGE_AvailableKycChecks checks[
+ GNUNET_NZL (lr.details.ok.checks_length)];
+ enum GNUNET_GenericReturnValue ret;
+
+ memset (roots,
+ 0,
+ sizeof (roots));
+ memset (progs,
+ 0,
+ sizeof (progs));
+ memset (checks,
+ 0,
+ sizeof (checks));
+ lr.details.ok.roots = roots;
+ lr.details.ok.programs = progs;
+ lr.details.ok.checks = checks;
+ ret = parse_aml_roots (jroots,
+ roots);
+ if (GNUNET_OK == ret)
+ ret = parse_aml_programs (lh,
+ jprograms,
+ progs);
+ if (GNUNET_OK == ret)
+ ret = parse_aml_checks (lh,
+ jchecks,
+ checks);
+ if (GNUNET_OK == ret)
+ {
+ lh->measures_cb (lh->measures_cb_cls,
+ &lr);
+ lh->measures_cb = NULL;
+ }
+ free_scrap (lh);
+ return ret;
+ }
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /aml/$OFFICER_PUB/measures request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_AmlGetMeasuresHandle`
+ * @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_AmlGetMeasuresHandle *lh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_AmlGetMeasuresResponse 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_measures_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->measures_cb);
+ TALER_EXCHANGE_aml_get_measures_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_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 get AML measures\n",
+ (unsigned int) response_code,
+ (int) lr.hr.ec);
+ break;
+ }
+ if (NULL != lh->measures_cb)
+ lh->measures_cb (lh->measures_cb_cls,
+ &lr);
+ TALER_EXCHANGE_aml_get_measures_cancel (lh);
+}
+
+
+struct TALER_EXCHANGE_AmlGetMeasuresHandle *
+TALER_EXCHANGE_aml_get_measures (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_url,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ TALER_EXCHANGE_AmlMeasuresCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh;
+ CURL *eh;
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+ struct TALER_AmlOfficerSignatureP officer_sig;
+ char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 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 *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/measures",
+ pub_str);
+ }
+ lh = GNUNET_new (struct TALER_EXCHANGE_AmlGetMeasuresHandle);
+ lh->measures_cb = cb;
+ lh->measures_cb_cls = cb_cls;
+ lh->url = TALER_url_join (exchange_url,
+ arg_str,
+ 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_aml_get_measures_cancel (
+ struct TALER_EXCHANGE_AmlGetMeasuresHandle *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_get_aml_measures.c */
diff --git a/src/lib/exchange_api_get-coins-COIN_PUB-history.c b/src/lib/exchange_api_get-coins-COIN_PUB-history.c
@@ -0,0 +1,1235 @@
+/*
+ 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_get-coins-COIN_PUB-history.c
+ * @brief Implementation of the POST /coins/$COIN_PUB/history requests
+ * @author Christian Grothoff
+ *
+ * NOTE: this is an incomplete draft, never finished!
+ */
+#include "taler/platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP history codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /coins/$RID/history Handle
+ */
+struct TALER_EXCHANGE_CoinsHistoryHandle
+{
+
+ /**
+ * 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;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_CoinsHistoryCallback cb;
+
+ /**
+ * Public key of the coin we are querying.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Context for coin helpers.
+ */
+struct CoinHistoryParseContext
+{
+
+ /**
+ * Keys of the exchange.
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * Denomination of the coin.
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey *dk;
+
+ /**
+ * Our coin public key.
+ */
+ const struct TALER_CoinSpendPublicKeyP *coin_pub;
+
+ /**
+ * Where to sum up total refunds.
+ */
+ struct TALER_Amount *total_in;
+
+ /**
+ * Total amount encountered.
+ */
+ struct TALER_Amount *total_out;
+
+};
+
+
+/**
+ * Signature of functions that operate on one of
+ * the coin's history entries.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh where to write the history entry
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+typedef enum GNUNET_GenericReturnValue
+(*CoinCheckHelper)(struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction);
+
+
+/**
+ * Handle deposit entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_deposit (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("deposit_fee",
+ &rh->details.deposit.deposit_fee),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &rh->details.deposit.merchant_pub),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &rh->details.deposit.wallet_timestamp),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("refund_deadline",
+ &rh->details.deposit.refund_deadline),
+ NULL),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &rh->details.deposit.h_contract_terms),
+ GNUNET_JSON_spec_fixed_auto ("h_wire",
+ &rh->details.deposit.h_wire),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_policy",
+ &rh->details.deposit.h_policy),
+ &rh->details.deposit.no_h_policy),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("wallet_data_hash",
+ &rh->details.deposit.wallet_data_hash),
+ &rh->details.deposit.no_wallet_data_hash),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &rh->details.deposit.hac),
+ &rh->details.deposit.no_hac),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &rh->details.deposit.sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rh->details.deposit.refund_deadline = GNUNET_TIME_UNIT_ZERO_TS;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_deposit_verify (
+ amount,
+ &rh->details.deposit.deposit_fee,
+ &rh->details.deposit.h_wire,
+ &rh->details.deposit.h_contract_terms,
+ rh->details.deposit.no_wallet_data_hash
+ ? NULL
+ : &rh->details.deposit.wallet_data_hash,
+ rh->details.deposit.no_hac
+ ? NULL
+ : &rh->details.deposit.hac,
+ rh->details.deposit.no_h_policy
+ ? NULL
+ : &rh->details.deposit.h_policy,
+ &pc->dk->h_key,
+ rh->details.deposit.wallet_timestamp,
+ &rh->details.deposit.merchant_pub,
+ rh->details.deposit.refund_deadline,
+ pc->coin_pub,
+ &rh->details.deposit.sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* check that deposit fee matches our expectations from /keys! */
+ if ( (GNUNET_YES !=
+ TALER_amount_cmp_currency (&rh->details.deposit.deposit_fee,
+ &pc->dk->fees.deposit)) ||
+ (0 !=
+ TALER_amount_cmp (&rh->details.deposit.deposit_fee,
+ &pc->dk->fees.deposit)) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_YES;
+}
+
+
+/**
+ * Handle melt entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_melt (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("melt_fee",
+ &rh->details.melt.melt_fee),
+ GNUNET_JSON_spec_fixed_auto ("rc",
+ &rh->details.melt.rc),
+ // FIXME: also return refresh_seed?
+ // FIXME: also return blinding_seed?
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &rh->details.melt.h_age_commitment),
+ &rh->details.melt.no_hac),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &rh->details.melt.sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* check that melt fee matches our expectations from /keys! */
+ if ( (GNUNET_YES !=
+ TALER_amount_cmp_currency (&rh->details.melt.melt_fee,
+ &pc->dk->fees.refresh)) ||
+ (0 !=
+ TALER_amount_cmp (&rh->details.melt.melt_fee,
+ &pc->dk->fees.refresh)) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_melt_verify (
+ amount,
+ &rh->details.melt.melt_fee,
+ &rh->details.melt.rc,
+ &pc->dk->h_key,
+ rh->details.melt.no_hac
+ ? NULL
+ : &rh->details.melt.h_age_commitment,
+ pc->coin_pub,
+ &rh->details.melt.sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_YES;
+}
+
+
+/**
+ * Handle refund entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_refund (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("refund_fee",
+ &rh->details.refund.refund_fee),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &rh->details.refund.h_contract_terms),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &rh->details.refund.merchant_pub),
+ GNUNET_JSON_spec_uint64 ("rtransaction_id",
+ &rh->details.refund.rtransaction_id),
+ GNUNET_JSON_spec_fixed_auto ("merchant_sig",
+ &rh->details.refund.sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 >
+ TALER_amount_add (&rh->details.refund.sig_amount,
+ &rh->details.refund.refund_fee,
+ amount))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_merchant_refund_verify (pc->coin_pub,
+ &rh->details.refund.h_contract_terms,
+ rh->details.refund.rtransaction_id,
+ &rh->details.refund.sig_amount,
+ &rh->details.refund.merchant_pub,
+ &rh->details.refund.sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* NOTE: theoretically, we could also check that the given
+ merchant_pub and h_contract_terms appear in the
+ history under deposits. However, there is really no benefit
+ for the exchange to lie here, so not checking is probably OK
+ (an auditor ought to check, though). Then again, we similarly
+ had no reason to check the merchant's signature (other than a
+ well-formendess check). */
+
+ /* check that refund fee matches our expectations from /keys! */
+ if ( (GNUNET_YES !=
+ TALER_amount_cmp_currency (&rh->details.refund.refund_fee,
+ &pc->dk->fees.refund)) ||
+ (0 !=
+ TALER_amount_cmp (&rh->details.refund.refund_fee,
+ &pc->dk->fees.refund)) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_NO;
+}
+
+
+/**
+ * Handle recoup entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_recoup (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rh->details.recoup.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rh->details.recoup.exchange_pub),
+ GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+ &rh->details.recoup.reserve_pub),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &rh->details.recoup.coin_sig),
+ GNUNET_JSON_spec_fixed_auto ("coin_blind",
+ &rh->details.recoup.coin_bks),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &rh->details.recoup.timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_confirm_recoup_verify (
+ rh->details.recoup.timestamp,
+ amount,
+ pc->coin_pub,
+ &rh->details.recoup.reserve_pub,
+ &rh->details.recoup.exchange_pub,
+ &rh->details.recoup.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_recoup_verify (&pc->dk->h_key,
+ &rh->details.recoup.coin_bks,
+ pc->coin_pub,
+ &rh->details.recoup.coin_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_YES;
+}
+
+
+/**
+ * Handle recoup-refresh entry in the coin's history.
+ * This is the coin that was subjected to a recoup,
+ * the value being credited to the old coin.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_recoup_refresh (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rh->details.recoup_refresh.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rh->details.recoup_refresh.exchange_pub),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &rh->details.recoup_refresh.coin_sig),
+ GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
+ &rh->details.recoup_refresh.old_coin_pub),
+ GNUNET_JSON_spec_fixed_auto ("coin_blind",
+ &rh->details.recoup_refresh.coin_bks),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &rh->details.recoup_refresh.timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_confirm_recoup_refresh_verify (
+ rh->details.recoup_refresh.timestamp,
+ amount,
+ pc->coin_pub,
+ &rh->details.recoup_refresh.old_coin_pub,
+ &rh->details.recoup_refresh.exchange_pub,
+ &rh->details.recoup_refresh.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_recoup_verify (&pc->dk->h_key,
+ &rh->details.recoup_refresh.coin_bks,
+ pc->coin_pub,
+ &rh->details.recoup_refresh.coin_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_YES;
+}
+
+
+/**
+ * Handle old coin recoup entry in the coin's history.
+ * This is the coin that was credited in a recoup,
+ * the value being credited to the this coin.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_old_coin_recoup (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rh->details.old_coin_recoup.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rh->details.old_coin_recoup.exchange_pub),
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &rh->details.old_coin_recoup.new_coin_pub),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &rh->details.old_coin_recoup.timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_confirm_recoup_refresh_verify (
+ rh->details.old_coin_recoup.timestamp,
+ amount,
+ &rh->details.old_coin_recoup.new_coin_pub,
+ pc->coin_pub,
+ &rh->details.old_coin_recoup.exchange_pub,
+ &rh->details.old_coin_recoup.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_NO;
+}
+
+
+/**
+ * Handle purse deposit entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_purse_deposit (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_web_url ("exchange_base_url",
+ &rh->details.purse_deposit.exchange_base_url),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &rh->details.purse_deposit.phac),
+ NULL),
+ // FIXME: return deposit_fee?
+ GNUNET_JSON_spec_fixed_auto ("purse_pub",
+ &rh->details.purse_deposit.purse_pub),
+ GNUNET_JSON_spec_bool ("refunded",
+ &rh->details.purse_deposit.refunded),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &rh->details.purse_deposit.coin_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_purse_deposit_verify (
+ rh->details.purse_deposit.exchange_base_url,
+ &rh->details.purse_deposit.purse_pub,
+ amount,
+ &pc->dk->h_key,
+ &rh->details.purse_deposit.phac,
+ pc->coin_pub,
+ &rh->details.purse_deposit.coin_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (rh->details.purse_deposit.refunded)
+ {
+ /* We wave the deposit fee. */
+ if (0 >
+ TALER_amount_add (pc->total_in,
+ pc->total_in,
+ &pc->dk->fees.deposit))
+ {
+ /* overflow in refund history? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_YES;
+}
+
+
+/**
+ * Handle purse refund entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_purse_refund (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("refund_fee",
+ &rh->details.purse_refund.refund_fee),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rh->details.purse_refund.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rh->details.purse_refund.exchange_pub),
+ GNUNET_JSON_spec_fixed_auto ("purse_pub",
+ &rh->details.purse_refund.purse_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_purse_refund_verify (
+ amount,
+ &rh->details.purse_refund.refund_fee,
+ pc->coin_pub,
+ &rh->details.purse_refund.purse_pub,
+ &rh->details.purse_refund.exchange_pub,
+ &rh->details.purse_refund.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if ( (GNUNET_YES !=
+ TALER_amount_cmp_currency (&rh->details.purse_refund.refund_fee,
+ &pc->dk->fees.refund)) ||
+ (0 !=
+ TALER_amount_cmp (&rh->details.purse_refund.refund_fee,
+ &pc->dk->fees.refund)) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_NO;
+}
+
+
+/**
+ * Handle reserve deposit entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param[out] rh history entry to initialize
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ * #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_reserve_open_deposit (struct CoinHistoryParseContext *pc,
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh,
+ const struct TALER_Amount *amount,
+ json_t *transaction)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &rh->details.reserve_open_deposit.reserve_sig),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &rh->details.reserve_open_deposit.coin_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_reserve_open_deposit_verify (
+ amount,
+ &rh->details.reserve_open_deposit.reserve_sig,
+ pc->coin_pub,
+ &rh->details.reserve_open_deposit.coin_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_YES;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_parse_coin_history (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_EXCHANGE_DenomPublicKey *dk,
+ const json_t *history,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_Amount *total_in,
+ struct TALER_Amount *total_out,
+ unsigned int rlen,
+ struct TALER_EXCHANGE_CoinHistoryEntry rhistory[static rlen])
+{
+ const struct
+ {
+ const char *type;
+ CoinCheckHelper helper;
+ enum TALER_EXCHANGE_CoinTransactionType ctt;
+ } map[] = {
+ { "DEPOSIT",
+ &help_deposit,
+ TALER_EXCHANGE_CTT_DEPOSIT },
+ { "MELT",
+ &help_melt,
+ TALER_EXCHANGE_CTT_MELT },
+ { "REFUND",
+ &help_refund,
+ TALER_EXCHANGE_CTT_REFUND },
+ { "RECOUP",
+ &help_recoup,
+ TALER_EXCHANGE_CTT_RECOUP },
+ { "RECOUP-REFRESH",
+ &help_recoup_refresh,
+ TALER_EXCHANGE_CTT_RECOUP_REFRESH },
+ { "OLD-COIN-RECOUP",
+ &help_old_coin_recoup,
+ TALER_EXCHANGE_CTT_OLD_COIN_RECOUP },
+ { "PURSE-DEPOSIT",
+ &help_purse_deposit,
+ TALER_EXCHANGE_CTT_PURSE_DEPOSIT },
+ { "PURSE-REFUND",
+ &help_purse_refund,
+ TALER_EXCHANGE_CTT_PURSE_REFUND },
+ { "RESERVE-OPEN-DEPOSIT",
+ &help_reserve_open_deposit,
+ TALER_EXCHANGE_CTT_RESERVE_OPEN_DEPOSIT },
+ { NULL, NULL, TALER_EXCHANGE_CTT_NONE }
+ };
+ struct CoinHistoryParseContext pc = {
+ .dk = dk,
+ .coin_pub = coin_pub,
+ .total_out = total_out,
+ .total_in = total_in
+ };
+ size_t len;
+
+ if (NULL == history)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ len = json_array_size (history);
+ if (0 == len)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ *total_in = dk->value;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (total_in->currency,
+ total_out));
+ for (size_t off = 0; off<len; off++)
+ {
+ struct TALER_EXCHANGE_CoinHistoryEntry *rh = &rhistory[off];
+ json_t *transaction = json_array_get (history,
+ off);
+ enum GNUNET_GenericReturnValue add;
+ const char *type;
+ struct GNUNET_JSON_Specification spec_glob[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &rh->amount),
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ GNUNET_JSON_spec_uint64 ("history_offset",
+ &rh->history_offset),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ spec_glob,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_YES !=
+ TALER_amount_cmp_currency (&rh->amount,
+ total_in))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Operation of type %s with amount %s\n",
+ type,
+ TALER_amount2s (&rh->amount));
+ add = GNUNET_SYSERR;
+ for (unsigned int i = 0; NULL != map[i].type; i++)
+ {
+ if (0 == strcasecmp (type,
+ map[i].type))
+ {
+ rh->type = map[i].ctt;
+ add = map[i].helper (&pc,
+ rh,
+ &rh->amount,
+ transaction);
+ break;
+ }
+ }
+ switch (add)
+ {
+ case GNUNET_SYSERR:
+ /* entry type not supported, new version on server? */
+ rh->type = TALER_EXCHANGE_CTT_NONE;
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected type `%s' in response\n",
+ type);
+ return GNUNET_SYSERR;
+ case GNUNET_YES:
+ /* This amount should be debited from the coin */
+ if (0 >
+ TALER_amount_add (total_out,
+ total_out,
+ &rh->amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ break;
+ case GNUNET_NO:
+ /* This amount should be credited to the coin. */
+ if (0 >
+ TALER_amount_add (total_in,
+ total_in,
+ &rh->amount))
+ {
+ /* overflow in refund history? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ break;
+ } /* end of switch(add) */
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * We received an #MHD_HTTP_OK history code. Handle the JSON
+ * response.
+ *
+ * @param rsh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_coins_history_ok (struct TALER_EXCHANGE_CoinsHistoryHandle *rsh,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_CoinHistory rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("balance",
+ &rs.details.ok.balance),
+ GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+ &rs.details.ok.h_denom_pub),
+ GNUNET_JSON_spec_array_const ("history",
+ &rs.details.ok.history),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (NULL != rsh->cb)
+ {
+ rsh->cb (rsh->cb_cls,
+ &rs);
+ rsh->cb = NULL;
+ }
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /coins/$RID/history request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_CoinsHistoryHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_coins_history_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_CoinsHistoryHandle *rsh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_CoinHistory rs = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ rsh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ handle_coins_history_ok (rsh,
+ j))
+ {
+ rs.hr.http_status = 0;
+ rs.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 */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.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 */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.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 */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for coins history\n",
+ (unsigned int) response_code,
+ (int) rs.hr.ec);
+ break;
+ }
+ if (NULL != rsh->cb)
+ {
+ rsh->cb (rsh->cb_cls,
+ &rs);
+ rsh->cb = NULL;
+ }
+ TALER_EXCHANGE_coins_history_cancel (rsh);
+}
+
+
+struct TALER_EXCHANGE_CoinsHistoryHandle *
+TALER_EXCHANGE_coins_history (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ uint64_t start_off,
+ TALER_EXCHANGE_CoinsHistoryCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_CoinsHistoryHandle *rsh;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 64];
+ struct curl_slist *job_headers;
+
+ rsh = GNUNET_new (struct TALER_EXCHANGE_CoinsHistoryHandle);
+ rsh->cb = cb;
+ rsh->cb_cls = cb_cls;
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
+ &rsh->coin_pub.eddsa_pub);
+ {
+ char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &rsh->coin_pub,
+ sizeof (rsh->coin_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ if (0 != start_off)
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "coins/%s/history?start=%llu",
+ pub_str,
+ (unsigned long long) start_off);
+ else
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "coins/%s/history",
+ pub_str);
+ }
+ rsh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == rsh->url)
+ {
+ GNUNET_free (rsh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (rsh->url);
+ GNUNET_free (rsh);
+ return NULL;
+ }
+
+ {
+ struct TALER_CoinSpendSignatureP coin_sig;
+ char *sig_hdr;
+ char *hdr;
+
+ TALER_wallet_coin_history_sign (start_off,
+ coin_priv,
+ &coin_sig);
+
+ sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
+ &coin_sig,
+ sizeof (coin_sig));
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ TALER_COIN_HISTORY_SIGNATURE_HEADER,
+ sig_hdr);
+ GNUNET_free (sig_hdr);
+ job_headers = curl_slist_append (NULL,
+ hdr);
+ GNUNET_free (hdr);
+ if (NULL == job_headers)
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ return NULL;
+ }
+ }
+
+ rsh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ job_headers,
+ &handle_coins_history_finished,
+ rsh);
+ curl_slist_free_all (job_headers);
+ return rsh;
+}
+
+
+void
+TALER_EXCHANGE_coins_history_cancel (
+ struct TALER_EXCHANGE_CoinsHistoryHandle *rsh)
+{
+ if (NULL != rsh->job)
+ {
+ GNUNET_CURL_job_cancel (rsh->job);
+ rsh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&rsh->post_ctx);
+ GNUNET_free (rsh->url);
+ GNUNET_free (rsh);
+}
+
+
+/**
+ * Verify that @a coin_sig does NOT appear in the @a history of a coin's
+ * transactions and thus whatever transaction is authorized by @a coin_sig is
+ * a conflict with @a proof.
+ *
+ * @param history coin history to check
+ * @param coin_sig signature that must not be in @a history
+ * @return #GNUNET_OK if @a coin_sig is not in @a history
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_signature_conflict (
+ const json_t *history,
+ const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ size_t off;
+ json_t *entry;
+
+ json_array_foreach (history, off, entry)
+ {
+ struct TALER_CoinSpendSignatureP cs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &cs),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (entry,
+ spec,
+ NULL, NULL))
+ continue; /* entry without coin signature */
+ if (0 ==
+ GNUNET_memcmp (&cs,
+ coin_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+#if FIXME_IMPLEMENT /* #9422 */
+/**
+ * FIXME-Oec-#9422: we need some specific routines that show
+ * that certain coin operations are indeed in conflict,
+ * for example that the coin is of a different denomination
+ * or different age restrictions.
+ * This relates to unimplemented error handling for
+ * coins in the exchange!
+ *
+ * Check that the provided @a proof indeeds indicates
+ * a conflict for @a coin_pub.
+ *
+ * @param keys exchange keys
+ * @param proof provided conflict proof
+ * @param dk denomination of @a coin_pub that the client
+ * used
+ * @param coin_pub public key of the coin
+ * @param required balance required on the coin for the operation
+ * @return #GNUNET_OK if @a proof holds
+ */
+// FIXME-#9422: should be properly defined and implemented!
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_conflict_ (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const json_t *proof,
+ const struct TALER_EXCHANGE_DenomPublicKey *dk,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *required)
+{
+ enum TALER_ErrorCode ec;
+
+ ec = TALER_JSON_get_error_code (proof);
+ switch (ec)
+ {
+ case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
+ /* Nothing to check anymore here, proof needs to be
+ checked in the GET /coins/$COIN_PUB handler */
+ break;
+ case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
+ // FIXME-#9422: write check!
+ break;
+ default:
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+#endif
+
+
+/* end of exchange_api_coins_history.c */
diff --git a/src/lib/exchange_api_get-contracts-CONTRACT_PUB.c b/src/lib/exchange_api_get-contracts-CONTRACT_PUB.c
@@ -0,0 +1,262 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_get-contracts-CONTRACT_PUB.c
+ * @brief Implementation of the /contracts/ GET request
+ * @author Christian Grothoff
+ */
+#include "taler/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/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Contract Get Handle
+ */
+struct TALER_EXCHANGE_ContractsGetHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ContractGetCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Private key needed to decrypt the contract.
+ */
+ struct TALER_ContractDiffiePrivateP contract_priv;
+
+ /**
+ * Public key matching @e contract_priv.
+ */
+ struct TALER_ContractDiffiePublicP cpub;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /track/transaction request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ContractsGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_contract_get_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ContractsGetHandle *cgh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_ContractGetResponse dr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ cgh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ void *econtract;
+ size_t econtract_size;
+ struct TALER_PurseContractSignatureP econtract_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("purse_pub",
+ &dr.details.ok.purse_pub),
+ GNUNET_JSON_spec_fixed_auto ("econtract_sig",
+ &econtract_sig),
+ GNUNET_JSON_spec_varsize ("econtract",
+ &econtract,
+ &econtract_size),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_econtract_upload_verify (
+ econtract,
+ econtract_size,
+ &cgh->cpub,
+ &dr.details.ok.purse_pub,
+ &econtract_sig))
+ {
+ GNUNET_break (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_CONTRACTS_SIGNATURE_INVALID;
+ GNUNET_JSON_parse_free (spec);
+ break;
+ }
+ dr.details.ok.econtract = econtract;
+ dr.details.ok.econtract_size = econtract_size;
+ cgh->cb (cgh->cb_cls,
+ &dr);
+ GNUNET_JSON_parse_free (spec);
+ TALER_EXCHANGE_contract_get_cancel (cgh);
+ return;
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.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:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* 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 */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Exchange does not know about transaction;
+ we should pass the reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.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 */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange GET contracts\n",
+ (unsigned int) response_code,
+ (int) dr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ cgh->cb (cgh->cb_cls,
+ &dr);
+ TALER_EXCHANGE_contract_get_cancel (cgh);
+}
+
+
+struct TALER_EXCHANGE_ContractsGetHandle *
+TALER_EXCHANGE_contract_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_ContractDiffiePrivateP *contract_priv,
+ TALER_EXCHANGE_ContractGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ContractsGetHandle *cgh;
+ CURL *eh;
+ char arg_str[sizeof (cgh->cpub) * 2 + 48];
+
+ cgh = GNUNET_new (struct TALER_EXCHANGE_ContractsGetHandle);
+ cgh->cb = cb;
+ cgh->cb_cls = cb_cls;
+ GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv,
+ &cgh->cpub.ecdhe_pub);
+ {
+ char cpub_str[sizeof (cgh->cpub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (&cgh->cpub,
+ sizeof (cgh->cpub),
+ cpub_str,
+ sizeof (cpub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "contracts/%s",
+ cpub_str);
+ }
+
+ cgh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == cgh->url)
+ {
+ GNUNET_free (cgh);
+ return NULL;
+ }
+ cgh->contract_priv = *contract_priv;
+
+ eh = TALER_EXCHANGE_curl_easy_get_ (cgh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (cgh->url);
+ GNUNET_free (cgh);
+ return NULL;
+ }
+ cgh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_contract_get_finished,
+ cgh);
+ return cgh;
+}
+
+
+void
+TALER_EXCHANGE_contract_get_cancel (
+ struct TALER_EXCHANGE_ContractsGetHandle *cgh)
+{
+ if (NULL != cgh->job)
+ {
+ GNUNET_CURL_job_cancel (cgh->job);
+ cgh->job = NULL;
+ }
+ GNUNET_free (cgh->url);
+ GNUNET_free (cgh);
+}
+
+
+/* end of exchange_api_contracts_get.c */
diff --git a/src/lib/exchange_api_get-deposits-H_WIRE-MERCHANT_PUB-H_CONTRACT_TERMS-COIN_PUB.c b/src/lib/exchange_api_get-deposits-H_WIRE-MERCHANT_PUB-H_CONTRACT_TERMS-COIN_PUB.c
@@ -0,0 +1,411 @@
+/*
+ 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_get-deposits-H_WIRE-MERCHANT_PUB-H_CONTRACT_TERMS-COIN_PUB.c
+ * @brief Implementation of the /deposits/ GET request
+ * @author Christian Grothoff
+ */
+#include "taler/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/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Deposit Get Handle
+ */
+struct TALER_EXCHANGE_DepositGetHandle
+{
+
+ /**
+ * The keys of the 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_DepositGetCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Hash over the wiring information of the merchant.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+ /**
+ * Hash over the contract for which this deposit is made.
+ */
+ 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;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /track/transaction request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_DepositGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_deposit_wtid_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_DepositGetHandle *dwh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_GetDepositResponse dr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ dwh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "wtid",
+ &dr.details.ok.wtid),
+ GNUNET_JSON_spec_timestamp (
+ "execution_time",
+ &dr.details.ok.execution_time),
+ TALER_JSON_spec_amount_any (
+ "coin_contribution",
+ &dr.details.ok.coin_contribution),
+ GNUNET_JSON_spec_fixed_auto (
+ "exchange_sig",
+ &dr.details.ok.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto (
+ "exchange_pub",
+ &dr.details.ok.exchange_pub),
+ GNUNET_JSON_spec_end ()
+ };
+ const struct TALER_EXCHANGE_Keys *key_state;
+
+ key_state = dwh->keys;
+ GNUNET_assert (NULL != key_state);
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (key_state,
+ &dr.details.ok.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_confirm_wire_verify (
+ &dwh->h_wire,
+ &dwh->h_contract_terms,
+ &dr.details.ok.wtid,
+ &dwh->coin_pub,
+ dr.details.ok.execution_time,
+ &dr.details.ok.coin_contribution,
+ &dr.details.ok.exchange_pub,
+ &dr.details.ok.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE;
+ break;
+ }
+ dwh->cb (dwh->cb_cls,
+ &dr);
+ TALER_EXCHANGE_deposits_get_cancel (dwh);
+ return;
+ }
+ case MHD_HTTP_ACCEPTED:
+ {
+ /* Transaction known, but not executed yet */
+ bool no_legi = false;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp (
+ "execution_time",
+ &dr.details.accepted.execution_time),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto (
+ "account_pub",
+ &dr.details.accepted.account_pub),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &dr.details.accepted.requirement_row),
+ &no_legi),
+ GNUNET_JSON_spec_bool (
+ "kyc_ok",
+ &dr.details.accepted.kyc_ok),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ if (no_legi)
+ dr.details.accepted.requirement_row = 0;
+ dwh->cb (dwh->cb_cls,
+ &dr);
+ TALER_EXCHANGE_deposits_get_cancel (dwh);
+ return;
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.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:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* 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 */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Exchange does not know about transaction;
+ we should pass the reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.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 */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange GET deposits\n",
+ (unsigned int) response_code,
+ (int) dr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ dwh->cb (dwh->cb_cls,
+ &dr);
+ TALER_EXCHANGE_deposits_get_cancel (dwh);
+}
+
+
+struct TALER_EXCHANGE_DepositGetHandle *
+TALER_EXCHANGE_deposits_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_MerchantPrivateKeyP *merchant_priv,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_EXCHANGE_DepositGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MerchantPublicKeyP merchant;
+ struct TALER_MerchantSignatureP merchant_sig;
+ struct TALER_EXCHANGE_DepositGetHandle *dwh;
+ CURL *eh;
+ char arg_str[(sizeof (struct TALER_CoinSpendPublicKeyP)
+ + sizeof (struct TALER_MerchantWireHashP)
+ + sizeof (struct TALER_MerchantPublicKeyP)
+ + sizeof (struct TALER_PrivateContractHashP)
+ + sizeof (struct TALER_MerchantSignatureP)) * 2 + 48];
+ unsigned int tms
+ = (unsigned int) timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
+ &merchant.eddsa_pub);
+ TALER_merchant_deposit_sign (h_contract_terms,
+ h_wire,
+ coin_pub,
+ merchant_priv,
+ &merchant_sig);
+ {
+ char cpub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+ char mpub_str[sizeof (struct TALER_MerchantPublicKeyP) * 2];
+ char msig_str[sizeof (struct TALER_MerchantSignatureP) * 2];
+ char chash_str[sizeof (struct TALER_PrivateContractHashP) * 2];
+ char whash_str[sizeof (struct TALER_MerchantWireHashP) * 2];
+ char timeout_str[24];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (h_wire,
+ sizeof (*h_wire),
+ whash_str,
+ sizeof (whash_str));
+ *end = '\0';
+ end = GNUNET_STRINGS_data_to_string (&merchant,
+ sizeof (merchant),
+ mpub_str,
+ sizeof (mpub_str));
+ *end = '\0';
+ end = GNUNET_STRINGS_data_to_string (h_contract_terms,
+ sizeof (*h_contract_terms),
+ chash_str,
+ sizeof (chash_str));
+ *end = '\0';
+ end = GNUNET_STRINGS_data_to_string (coin_pub,
+ sizeof (*coin_pub),
+ cpub_str,
+ sizeof (cpub_str));
+ *end = '\0';
+ end = GNUNET_STRINGS_data_to_string (&merchant_sig,
+ sizeof (merchant_sig),
+ msig_str,
+ sizeof (msig_str));
+ *end = '\0';
+ if (GNUNET_TIME_relative_is_zero (timeout))
+ {
+ timeout_str[0] = '\0';
+ }
+ else
+ {
+ GNUNET_snprintf (
+ timeout_str,
+ sizeof (timeout_str),
+ "%u",
+ tms);
+ }
+
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "deposits/%s/%s/%s/%s?merchant_sig=%s%s%s",
+ whash_str,
+ mpub_str,
+ chash_str,
+ cpub_str,
+ msig_str,
+ 0 == tms
+ ? ""
+ : "&timeout_ms=",
+ timeout_str);
+ }
+
+ dwh = GNUNET_new (struct TALER_EXCHANGE_DepositGetHandle);
+ dwh->cb = cb;
+ dwh->cb_cls = cb_cls;
+ dwh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == dwh->url)
+ {
+ GNUNET_free (dwh);
+ return NULL;
+ }
+ dwh->h_wire = *h_wire;
+ dwh->h_contract_terms = *h_contract_terms;
+ dwh->coin_pub = *coin_pub;
+ eh = TALER_EXCHANGE_curl_easy_get_ (dwh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (dwh->url);
+ GNUNET_free (dwh);
+ return NULL;
+ }
+ if (0 != tms)
+ {
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) (tms + 100L)));
+ }
+ dwh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_deposit_wtid_finished,
+ dwh);
+ dwh->keys = TALER_EXCHANGE_keys_incref (keys);
+ return dwh;
+}
+
+
+void
+TALER_EXCHANGE_deposits_get_cancel (struct TALER_EXCHANGE_DepositGetHandle *dwh)
+{
+ if (NULL != dwh->job)
+ {
+ GNUNET_CURL_job_cancel (dwh->job);
+ dwh->job = NULL;
+ }
+ GNUNET_free (dwh->url);
+ TALER_curl_easy_post_finished (&dwh->ctx);
+ TALER_EXCHANGE_keys_decref (dwh->keys);
+ GNUNET_free (dwh);
+}
+
+
+/* end of exchange_api_deposits_get.c */
diff --git a/src/lib/exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c b/src/lib/exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c
@@ -0,0 +1,415 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021-2024 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_get-kyc-check-H_NORMALIZED_PAYTO.c
+ * @brief Implementation of the /kyc-check request
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <microhttpd.h> /* just for HTTP check codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler/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;
+
+ /**
+ * 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;
+
+};
+
+
+static enum GNUNET_GenericReturnValue
+parse_account_status (
+ struct TALER_EXCHANGE_KycCheckHandle *kch,
+ const json_t *j,
+ struct TALER_EXCHANGE_KycStatus *ks,
+ struct TALER_EXCHANGE_AccountKycStatus *status)
+{
+ const json_t *limits = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_bool ("aml_review",
+ &status->aml_review),
+ GNUNET_JSON_spec_uint64 ("rule_gen",
+ &status->rule_gen),
+ GNUNET_JSON_spec_fixed_auto ("access_token",
+ &status->access_token),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("limits",
+ &limits),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if ( (NULL != limits) &&
+ (0 != json_array_size (limits)) )
+ {
+ size_t limit_length = json_array_size (limits);
+ struct TALER_EXCHANGE_AccountLimit ala[GNUNET_NZL (limit_length)];
+ size_t i;
+ json_t *limit;
+
+ json_array_foreach (limits, i, limit)
+ {
+ struct TALER_EXCHANGE_AccountLimit *al = &ala[i];
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("soft_limit",
+ &al->soft_limit),
+ NULL),
+ GNUNET_JSON_spec_relative_time ("timeframe",
+ &al->timeframe),
+ TALER_JSON_spec_kycte ("operation_type",
+ &al->operation_type),
+ TALER_JSON_spec_amount_any ("threshold",
+ &al->threshold),
+ GNUNET_JSON_spec_end ()
+ };
+
+ al->soft_limit = false;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (limit,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ status->limits = ala;
+ status->limits_length = limit_length;
+ kch->cb (kch->cb_cls,
+ ks);
+ }
+ else
+ {
+ kch->cb (kch->cb_cls,
+ ks);
+ }
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * 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 = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ kch->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ if (GNUNET_OK !=
+ parse_account_status (kch,
+ j,
+ &ks,
+ &ks.details.ok))
+ {
+ GNUNET_break_op (0);
+ ks.hr.http_status = 0;
+ ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ TALER_EXCHANGE_kyc_check_cancel (kch);
+ return;
+ }
+ case MHD_HTTP_ACCEPTED:
+ {
+ if (GNUNET_OK !=
+ parse_account_status (kch,
+ j,
+ &ks,
+ &ks.details.accepted))
+ {
+ GNUNET_break_op (0);
+ ks.hr.http_status = 0;
+ ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ TALER_EXCHANGE_kyc_check_cancel (kch);
+ return;
+ }
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ ks.hr.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:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "expected_account_pub",
+ &ks.details.forbidden.expected_account_pub),
+ TALER_JSON_spec_ec ("code",
+ &ks.hr.ec),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ks.hr.http_status = 0;
+ ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ break;
+ }
+ case MHD_HTTP_NOT_FOUND:
+ ks.hr.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ ks.hr.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ ks.hr.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.hr.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.hr.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,
+ const struct TALER_NormalizedPaytoHashP *h_payto,
+ const union TALER_AccountPrivateKeyP *account_priv,
+ uint64_t known_rule_gen,
+ enum TALER_EXCHANGE_KycLongPollTarget lpt,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_EXCHANGE_KycStatusCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_KycCheckHandle *kch;
+ CURL *eh;
+ char arg_str[128];
+ char timeout_ms[32];
+ char lpt_str[32];
+ char krg_str[32];
+ struct curl_slist *job_headers = NULL;
+ unsigned long long tms;
+
+ {
+ char *hps;
+
+ hps = GNUNET_STRINGS_data_to_string_alloc (
+ h_payto,
+ sizeof (*h_payto));
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "kyc-check/%s",
+ hps);
+ GNUNET_free (hps);
+ }
+ tms = timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
+ GNUNET_snprintf (timeout_ms,
+ sizeof (timeout_ms),
+ "%llu",
+ tms);
+ GNUNET_snprintf (krg_str,
+ sizeof (krg_str),
+ "%llu",
+ (unsigned long long) known_rule_gen);
+ GNUNET_snprintf (lpt_str,
+ sizeof (lpt_str),
+ "%d",
+ (int) lpt);
+ kch = GNUNET_new (struct TALER_EXCHANGE_KycCheckHandle);
+ kch->cb = cb;
+ kch->cb_cls = cb_cls;
+ kch->url
+ = TALER_url_join (
+ url,
+ arg_str,
+ "timeout_ms",
+ GNUNET_TIME_relative_is_zero (timeout)
+ ? NULL
+ : timeout_ms,
+ "min_rule",
+ 0 == known_rule_gen
+ ? NULL
+ : krg_str,
+ "lpt",
+ TALER_EXCHANGE_KLPT_NONE == lpt
+ ? NULL
+ : lpt_str,
+ NULL);
+ 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;
+ }
+ if (0 != tms)
+ {
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) (tms + 500L)));
+ }
+ job_headers
+ = curl_slist_append (
+ job_headers,
+ "Content-Type: application/json");
+ {
+ union TALER_AccountPublicKeyP account_pub;
+ union TALER_AccountSignatureP account_sig;
+ char *sig_hdr;
+ char *pub_hdr;
+ char *hdr;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (
+ &account_priv->reserve_priv.eddsa_priv,
+ &account_pub.reserve_pub.eddsa_pub);
+ TALER_account_kyc_auth_sign (account_priv,
+ &account_sig);
+
+ sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
+ &account_sig,
+ sizeof (account_sig));
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE,
+ sig_hdr);
+ GNUNET_free (sig_hdr);
+ job_headers = curl_slist_append (job_headers,
+ hdr);
+ GNUNET_free (hdr);
+
+ pub_hdr = GNUNET_STRINGS_data_to_string_alloc (
+ &account_pub,
+ sizeof (account_pub));
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ TALER_HTTP_HEADER_ACCOUNT_OWNER_PUBKEY,
+ pub_hdr);
+ GNUNET_free (pub_hdr);
+ job_headers = curl_slist_append (job_headers,
+ hdr);
+ GNUNET_free (hdr);
+ if (NULL == job_headers)
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ return NULL;
+ }
+ }
+ kch->job
+ = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ job_headers,
+ &handle_kyc_check_finished,
+ kch);
+ curl_slist_free_all (job_headers);
+ 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;
+ }
+ GNUNET_free (kch->url);
+ GNUNET_free (kch);
+}
+
+
+/* end of exchange_api_kyc_check.c */
diff --git a/src/lib/exchange_api_get-kyc-info-ACCESS_TOKEN.c b/src/lib/exchange_api_get-kyc-info-ACCESS_TOKEN.c
@@ -0,0 +1,392 @@
+/*
+ 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_get-kyc-info-ACCESS_TOKEN.c
+ * @brief Implementation of the /kyc-info/$AT request
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /kyc-info Handle
+ */
+struct TALER_EXCHANGE_KycInfoHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_KycInfoCallback kyc_info_cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *kyc_info_cb_cls;
+
+};
+
+
+/**
+ * Parse the provided kyc_infoage data from the "200 OK" response
+ * for one of the coins.
+ *
+ * @param[in,out] lh kyc_info 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_kyc_info_ok (struct TALER_EXCHANGE_KycInfoHandle *lh,
+ const json_t *json)
+{
+ const json_t *jrequirements = NULL;
+ const json_t *jvoluntary_checks = NULL;
+ struct TALER_EXCHANGE_KycProcessClientInformation lr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("requirements",
+ &jrequirements),
+ GNUNET_JSON_spec_bool ("is_and_combinator",
+ &lr.details.ok.is_and_combinator),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("voluntary_checks",
+ &jvoluntary_checks),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ lr.details.ok.vci_length
+ = (unsigned int) json_object_size (jvoluntary_checks);
+ if ( ((size_t) lr.details.ok.vci_length)
+ != json_object_size (jvoluntary_checks))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ lr.details.ok.requirements_length
+ = json_array_size (jrequirements);
+ if ( ((size_t) lr.details.ok.requirements_length)
+ != json_array_size (jrequirements))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ struct TALER_EXCHANGE_VoluntaryCheckInformation vci[
+ GNUNET_NZL (lr.details.ok.vci_length)];
+ struct TALER_EXCHANGE_RequirementInformation requirements[
+ GNUNET_NZL (lr.details.ok.requirements_length)];
+ const char *name;
+ const json_t *jreq;
+ const json_t *jvc;
+ size_t off;
+
+ memset (vci,
+ 0,
+ sizeof (vci));
+ memset (requirements,
+ 0,
+ sizeof (requirements));
+
+ json_array_foreach ((json_t *) jrequirements, off, jreq)
+ {
+ struct TALER_EXCHANGE_RequirementInformation *req = &requirements[off];
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("form",
+ &req->form),
+ GNUNET_JSON_spec_string ("description",
+ &req->description),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("description_i18n",
+ &req->description_i18n),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("id",
+ &req->id),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jreq,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ GNUNET_assert (off == lr.details.ok.requirements_length);
+
+ off = 0;
+ json_object_foreach ((json_t *) jvoluntary_checks, name, jvc)
+ {
+ struct TALER_EXCHANGE_VoluntaryCheckInformation *vc = &vci[off++];
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("description",
+ &vc->description),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("description_i18n",
+ &vc->description_i18n),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ vc->name = name;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jvc,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ GNUNET_assert (off == lr.details.ok.vci_length);
+
+ lr.details.ok.vci = vci;
+ lr.details.ok.requirements = requirements;
+ lh->kyc_info_cb (lh->kyc_info_cb_cls,
+ &lr);
+ lh->kyc_info_cb = NULL;
+ return GNUNET_OK;
+ }
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /kyc-info/$AT request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_KycInfoHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_kyc_info_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_KycInfoHandle *lh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_KycProcessClientInformation 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_kyc_info_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->kyc_info_cb);
+ TALER_EXCHANGE_kyc_info_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);
+ 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 /kyc-info\n",
+ (unsigned int) response_code,
+ (int) lr.hr.ec);
+ break;
+ }
+ if (NULL != lh->kyc_info_cb)
+ lh->kyc_info_cb (lh->kyc_info_cb_cls,
+ &lr);
+ TALER_EXCHANGE_kyc_info_cancel (lh);
+}
+
+
+struct TALER_EXCHANGE_KycInfoHandle *
+TALER_EXCHANGE_kyc_info (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_AccountAccessTokenP *token,
+ const char *if_none_match,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_EXCHANGE_KycInfoCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_KycInfoHandle *lh;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_AccountAccessTokenP) * 2 + 32];
+ unsigned int tms
+ = (unsigned int) timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
+ struct curl_slist *job_headers = NULL;
+
+ {
+ char at_str[sizeof (struct TALER_AccountAccessTokenP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ token,
+ sizeof (*token),
+ at_str,
+ sizeof (at_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "kyc-info/%s",
+ at_str);
+ }
+ lh = GNUNET_new (struct TALER_EXCHANGE_KycInfoHandle);
+ lh->kyc_info_cb = cb;
+ lh->kyc_info_cb_cls = cb_cls;
+ {
+ char timeout_str[32];
+
+ GNUNET_snprintf (timeout_str,
+ sizeof (timeout_str),
+ "%u",
+ tms);
+ lh->url = TALER_url_join (url,
+ arg_str,
+ "timeout_ms",
+ (0 == tms)
+ ? NULL
+ : timeout_str,
+ 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;
+ }
+ if (0 != tms)
+ {
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) (tms + 100L)));
+ }
+
+ job_headers = curl_slist_append (job_headers,
+ "Content-Type: application/json");
+ if (NULL != if_none_match)
+ {
+ char *hdr;
+
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ MHD_HTTP_HEADER_IF_NONE_MATCH,
+ if_none_match);
+ job_headers = curl_slist_append (job_headers,
+ hdr);
+ GNUNET_free (hdr);
+ }
+ lh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ job_headers,
+ &handle_kyc_info_finished,
+ lh);
+ curl_slist_free_all (job_headers);
+ return lh;
+}
+
+
+void
+TALER_EXCHANGE_kyc_info_cancel (struct TALER_EXCHANGE_KycInfoHandle *lh)
+{
+ if (NULL != lh->job)
+ {
+ GNUNET_CURL_job_cancel (lh->job);
+ lh->job = NULL;
+ }
+
+ GNUNET_free (lh->url);
+ GNUNET_free (lh);
+}
+
+
+/* end of exchange_api_kyc_info.c */
diff --git a/src/lib/exchange_api_get-kyc-proof-PROVIDER_NAME.c b/src/lib/exchange_api_get-kyc-proof-PROVIDER_NAME.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_get-kyc-proof-PROVIDER_NAME.c
+ * @brief Implementation of the /kyc-proof request
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <microhttpd.h> /* just for HTTP proof codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler/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 = {
+ .hr.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_NormalizedPaytoHashP *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 (*h_payto) * 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_get-management-keys.c b/src/lib/exchange_api_get-management-keys.c
@@ -0,0 +1,427 @@
+/*
+ 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_get-management-keys.c
+ * @brief functions to obtain future online keys of the exchange
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include <microhttpd.h>
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_curl_lib.h"
+#include "taler/taler_util.h"
+#include "taler/taler_json_lib.h"
+
+/**
+ * Set to 1 for extra debug logging.
+ */
+#define DEBUG 0
+
+
+/**
+ * @brief Handle for a GET /management/keys request.
+ */
+struct TALER_EXCHANGE_GetManagementKeysHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementGetKeysCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Handle the case that the response was of type #MHD_HTTP_OK.
+ *
+ * @param[in,out] gh request handle
+ * @param response the response
+ * @return #GNUNET_OK if the response was well-formed
+ */
+static enum GNUNET_GenericReturnValue
+handle_ok (struct TALER_EXCHANGE_GetManagementKeysHandle *gh,
+ const json_t *response)
+{
+ struct TALER_EXCHANGE_ManagementGetKeysResponse gkr = {
+ .hr.http_status = MHD_HTTP_OK,
+ .hr.reply = response,
+ };
+ struct TALER_EXCHANGE_FutureKeys *fk
+ = &gkr.details.ok.keys;
+ const json_t *sk;
+ const json_t *dk;
+ bool ok;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("future_denoms",
+ &dk),
+ GNUNET_JSON_spec_array_const ("future_signkeys",
+ &sk),
+ GNUNET_JSON_spec_fixed_auto ("master_pub",
+ &fk->master_pub),
+ GNUNET_JSON_spec_fixed_auto ("denom_secmod_public_key",
+ &fk->denom_secmod_public_key),
+ GNUNET_JSON_spec_fixed_auto ("denom_secmod_cs_public_key",
+ &fk->denom_secmod_cs_public_key),
+ GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key",
+ &fk->signkey_secmod_public_key),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ fk->num_sign_keys = json_array_size (sk);
+ fk->num_denom_keys = json_array_size (dk);
+ fk->sign_keys = GNUNET_new_array (
+ fk->num_sign_keys,
+ struct TALER_EXCHANGE_FutureSigningPublicKey);
+ fk->denom_keys = GNUNET_new_array (
+ fk->num_denom_keys,
+ struct TALER_EXCHANGE_FutureDenomPublicKey);
+ ok = true;
+ for (unsigned int i = 0; i<fk->num_sign_keys; i++)
+ {
+ json_t *j = json_array_get (sk,
+ i);
+ struct TALER_EXCHANGE_FutureSigningPublicKey *sign_key
+ = &fk->sign_keys[i];
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto ("key",
+ &sign_key->key),
+ GNUNET_JSON_spec_fixed_auto ("signkey_secmod_sig",
+ &sign_key->signkey_secmod_sig),
+ GNUNET_JSON_spec_timestamp ("stamp_start",
+ &sign_key->valid_from),
+ GNUNET_JSON_spec_timestamp ("stamp_expire",
+ &sign_key->valid_until),
+ GNUNET_JSON_spec_timestamp ("stamp_end",
+ &sign_key->valid_legal),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ok = false;
+ break;
+ }
+ {
+ struct GNUNET_TIME_Relative duration
+ = GNUNET_TIME_absolute_get_difference (sign_key->valid_from.abs_time,
+ sign_key->valid_until.abs_time);
+
+ if (GNUNET_OK !=
+ TALER_exchange_secmod_eddsa_verify (
+ &sign_key->key,
+ sign_key->valid_from,
+ duration,
+ &fk->signkey_secmod_public_key,
+ &sign_key->signkey_secmod_sig))
+ {
+ GNUNET_break_op (0);
+ ok = false;
+ break;
+ }
+ }
+ }
+ for (unsigned int i = 0; i<fk->num_denom_keys; i++)
+ {
+ json_t *j = json_array_get (dk,
+ i);
+ struct TALER_EXCHANGE_FutureDenomPublicKey *denom_key
+ = &fk->denom_keys[i];
+ const char *section_name;
+ struct GNUNET_JSON_Specification ispec[] = {
+ TALER_JSON_spec_amount_any ("value",
+ &denom_key->value),
+ GNUNET_JSON_spec_timestamp ("stamp_start",
+ &denom_key->valid_from),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
+ &denom_key->withdraw_valid_until),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
+ &denom_key->expire_deposit),
+ GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
+ &denom_key->expire_legal),
+ TALER_JSON_spec_denom_pub ("denom_pub",
+ &denom_key->key),
+ TALER_JSON_spec_amount_any ("fee_withdraw",
+ &denom_key->fee_withdraw),
+ TALER_JSON_spec_amount_any ("fee_deposit",
+ &denom_key->fee_deposit),
+ TALER_JSON_spec_amount_any ("fee_refresh",
+ &denom_key->fee_refresh),
+ TALER_JSON_spec_amount_any ("fee_refund",
+ &denom_key->fee_refund),
+ GNUNET_JSON_spec_fixed_auto ("denom_secmod_sig",
+ &denom_key->denom_secmod_sig),
+ GNUNET_JSON_spec_string ("section_name",
+ §ion_name),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+#if DEBUG
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+#endif
+ ok = false;
+ break;
+ }
+
+ {
+ struct TALER_DenominationHashP h_denom_pub;
+ struct GNUNET_TIME_Relative duration
+ = GNUNET_TIME_absolute_get_difference (
+ denom_key->valid_from.abs_time,
+ denom_key->withdraw_valid_until.abs_time);
+
+ TALER_denom_pub_hash (&denom_key->key,
+ &h_denom_pub);
+ switch (denom_key->key.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ {
+ struct TALER_RsaPubHashP h_rsa;
+
+ TALER_rsa_pub_hash (
+ denom_key->key.bsign_pub_key->details.rsa_public_key,
+ &h_rsa);
+ if (GNUNET_OK !=
+ TALER_exchange_secmod_rsa_verify (&h_rsa,
+ section_name,
+ denom_key->valid_from,
+ duration,
+ &fk->denom_secmod_public_key,
+ &denom_key->denom_secmod_sig))
+ {
+ GNUNET_break_op (0);
+ ok = false;
+ break;
+ }
+ }
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ {
+ struct TALER_CsPubHashP h_cs;
+
+ TALER_cs_pub_hash (
+ &denom_key->key.bsign_pub_key->details.cs_public_key,
+ &h_cs);
+ if (GNUNET_OK !=
+ TALER_exchange_secmod_cs_verify (&h_cs,
+ section_name,
+ denom_key->valid_from,
+ duration,
+ &fk->denom_secmod_cs_public_key,
+ &denom_key->denom_secmod_sig))
+ {
+ GNUNET_break_op (0);
+ ok = false;
+ break;
+ }
+ }
+ break;
+ default:
+ GNUNET_break_op (0);
+ ok = false;
+ break;
+ }
+ }
+ if (! ok)
+ break;
+ }
+ if (ok)
+ {
+ gh->cb (gh->cb_cls,
+ &gkr);
+ }
+ for (unsigned int i = 0; i<fk->num_denom_keys; i++)
+ TALER_denom_pub_free (&fk->denom_keys[i].key);
+ GNUNET_free (fk->sign_keys);
+ GNUNET_free (fk->denom_keys);
+ return (ok) ? GNUNET_OK : GNUNET_SYSERR;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP GET /management/keys request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_GetManagementKeysHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_get_keys_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_GetManagementKeysHandle *gh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementGetKeysResponse gkr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ gh->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ if (GNUNET_OK ==
+ handle_ok (gh,
+ response))
+ {
+ gh->cb = NULL;
+ }
+ else
+ {
+ response_code = 0;
+ }
+ 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",
+ gh->url);
+ if (NULL != json)
+ {
+ gkr.hr.ec = TALER_JSON_get_error_code (json);
+ gkr.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ gkr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ gkr.hr.hint = TALER_ErrorCode_get_hint (gkr.hr.ec);
+ }
+ break;
+ default:
+ /* unexpected response code */
+ if (NULL != json)
+ {
+ gkr.hr.ec = TALER_JSON_get_error_code (json);
+ gkr.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ gkr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ gkr.hr.hint = TALER_ErrorCode_get_hint (gkr.hr.ec);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management get keys\n",
+ (unsigned int) response_code,
+ (int) gkr.hr.ec);
+ break;
+ }
+ if (NULL != gh->cb)
+ {
+ gh->cb (gh->cb_cls,
+ &gkr);
+ gh->cb = NULL;
+ }
+ TALER_EXCHANGE_get_management_keys_cancel (gh);
+};
+
+
+struct TALER_EXCHANGE_GetManagementKeysHandle *
+TALER_EXCHANGE_get_management_keys (struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ TALER_EXCHANGE_ManagementGetKeysCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_GetManagementKeysHandle *gh;
+ CURL *eh;
+
+ gh = GNUNET_new (struct TALER_EXCHANGE_GetManagementKeysHandle);
+ gh->cb = cb;
+ gh->cb_cls = cb_cls;
+ gh->ctx = ctx;
+ gh->url = TALER_url_join (url,
+ "management/keys",
+ NULL);
+ if (NULL == gh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (gh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (gh->url);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ gh->url);
+ gh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_get_keys_finished,
+ gh);
+ if (NULL == gh->job)
+ {
+ TALER_EXCHANGE_get_management_keys_cancel (gh);
+ return NULL;
+ }
+ return gh;
+}
+
+
+void
+TALER_EXCHANGE_get_management_keys_cancel (
+ struct TALER_EXCHANGE_GetManagementKeysHandle *gh)
+{
+ if (NULL != gh->job)
+ {
+ GNUNET_CURL_job_cancel (gh->job);
+ gh->job = NULL;
+ }
+ GNUNET_free (gh->url);
+ GNUNET_free (gh);
+}
diff --git a/src/lib/exchange_api_get-purses-PURSE_PUB-merge.c b/src/lib/exchange_api_get-purses-PURSE_PUB-merge.c
@@ -0,0 +1,300 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-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_get-purses-PURSE_PUB-merge.c
+ * @brief Implementation of the /purses/ GET request
+ * @author Christian Grothoff
+ */
+#include "taler/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/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Contract Get Handle
+ */
+struct TALER_EXCHANGE_PurseGetHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_PurseGetCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /purses/$PID GET request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_PurseGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_purse_get_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_PurseGetHandle *pgh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_PurseGetResponse dr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ pgh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ bool no_merge = false;
+ bool no_deposit = false;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("merge_timestamp",
+ &dr.details.ok.merge_timestamp),
+ &no_merge),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("deposit_timestamp",
+ &dr.details.ok.deposit_timestamp),
+ &no_deposit),
+ TALER_JSON_spec_amount_any ("balance",
+ &dr.details.ok.balance),
+ GNUNET_JSON_spec_timestamp ("purse_expiration",
+ &dr.details.ok.purse_expiration),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &exchange_pub),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (pgh->keys,
+ &exchange_pub))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_purse_status_verify (
+ dr.details.ok.merge_timestamp,
+ dr.details.ok.deposit_timestamp,
+ &dr.details.ok.balance,
+ &exchange_pub,
+ &exchange_sig))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE;
+ break;
+ }
+ pgh->cb (pgh->cb_cls,
+ &dr);
+ TALER_EXCHANGE_purse_get_cancel (pgh);
+ return;
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.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:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* 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 */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Exchange does not know about transaction;
+ we should pass the reply to the application */
+ break;
+ case MHD_HTTP_GONE:
+ /* purse expired */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.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 */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange GET purses\n",
+ (unsigned int) response_code,
+ (int) dr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ pgh->cb (pgh->cb_cls,
+ &dr);
+ TALER_EXCHANGE_purse_get_cancel (pgh);
+}
+
+
+struct TALER_EXCHANGE_PurseGetHandle *
+TALER_EXCHANGE_purse_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Relative timeout,
+ bool wait_for_merge,
+ TALER_EXCHANGE_PurseGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_PurseGetHandle *pgh;
+ CURL *eh;
+ char arg_str[sizeof (*purse_pub) * 2 + 64];
+ unsigned int tms
+ = (unsigned int) timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
+
+ pgh = GNUNET_new (struct TALER_EXCHANGE_PurseGetHandle);
+ pgh->cb = cb;
+ pgh->cb_cls = cb_cls;
+ {
+ char cpub_str[sizeof (*purse_pub) * 2];
+ char *end;
+ char timeout_str[32];
+
+ end = GNUNET_STRINGS_data_to_string (purse_pub,
+ sizeof (*purse_pub),
+ cpub_str,
+ sizeof (cpub_str));
+ *end = '\0';
+ GNUNET_snprintf (timeout_str,
+ sizeof (timeout_str),
+ "%u",
+ tms);
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "purses/%s/%s",
+ cpub_str,
+ wait_for_merge
+ ? "merge"
+ : "deposit");
+ pgh->url = TALER_url_join (url,
+ arg_str,
+ "timeout_ms",
+ (0 == tms)
+ ? NULL
+ : timeout_str,
+ NULL);
+ }
+ if (NULL == pgh->url)
+ {
+ GNUNET_free (pgh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (pgh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (pgh->url);
+ GNUNET_free (pgh);
+ return NULL;
+ }
+ if (0 != tms)
+ {
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) (tms + 100L)));
+ }
+ pgh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_purse_get_finished,
+ pgh);
+ pgh->keys = TALER_EXCHANGE_keys_incref (keys);
+ return pgh;
+}
+
+
+void
+TALER_EXCHANGE_purse_get_cancel (
+ struct TALER_EXCHANGE_PurseGetHandle *pgh)
+{
+ if (NULL != pgh->job)
+ {
+ GNUNET_CURL_job_cancel (pgh->job);
+ pgh->job = NULL;
+ }
+ GNUNET_free (pgh->url);
+ TALER_EXCHANGE_keys_decref (pgh->keys);
+ GNUNET_free (pgh);
+}
+
+
+/* end of exchange_api_purses_get.c */
diff --git a/src/lib/exchange_api_get-reserves-RESERVE_PUB-history.c b/src/lib/exchange_api_get-reserves-RESERVE_PUB-history.c
@@ -0,0 +1,1258 @@
+/*
+ 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_get-reserves-RESERVE_PUB-history.c
+ * @brief Implementation of the POST /reserves/$RESERVE_PUB/history requests
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP history codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /reserves/$RID/history Handle
+ */
+struct TALER_EXCHANGE_ReservesHistoryHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * 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;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ReservesHistoryCallback cb;
+
+ /**
+ * Public key of the reserve we are querying.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Where to store the etag (if any).
+ */
+ uint64_t etag;
+
+};
+
+
+/**
+ * Context for history entry helpers.
+ */
+struct HistoryParseContext
+{
+
+ /**
+ * Keys of the exchange we use.
+ */
+ const struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * Our reserve public key.
+ */
+ const struct TALER_ReservePublicKeyP *reserve_pub;
+
+ /**
+ * Array of UUIDs.
+ */
+ struct GNUNET_HashCode *uuids;
+
+ /**
+ * Where to sum up total inbound amounts.
+ */
+ struct TALER_Amount *total_in;
+
+ /**
+ * Where to sum up total outbound amounts.
+ */
+ struct TALER_Amount *total_out;
+
+ /**
+ * Number of entries already used in @e uuids.
+ */
+ unsigned int uuid_off;
+};
+
+
+/**
+ * Type of a function called to parse a reserve history
+ * entry @a rh.
+ *
+ * @param[in,out] rh where to write the result
+ * @param[in,out] uc UUID context for duplicate detection
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+typedef enum GNUNET_GenericReturnValue
+(*ParseHelper)(struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+ struct HistoryParseContext *uc,
+ const json_t *transaction);
+
+
+/**
+ * Parse "credit" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_credit (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+ struct HistoryParseContext *uc,
+ const json_t *transaction)
+{
+ struct TALER_FullPayto wire_uri;
+ uint64_t wire_reference;
+ struct GNUNET_TIME_Timestamp timestamp;
+ struct GNUNET_JSON_Specification withdraw_spec[] = {
+ GNUNET_JSON_spec_uint64 ("wire_reference",
+ &wire_reference),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ ×tamp),
+ TALER_JSON_spec_full_payto_uri ("sender_account_url",
+ &wire_uri),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rh->type = TALER_EXCHANGE_RTT_CREDIT;
+ if (0 >
+ TALER_amount_add (uc->total_in,
+ uc->total_in,
+ &rh->amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ withdraw_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rh->details.in_details.sender_url.full_payto
+ = GNUNET_strdup (wire_uri.full_payto);
+ rh->details.in_details.wire_reference = wire_reference;
+ rh->details.in_details.timestamp = timestamp;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse "withdraw" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+ struct HistoryParseContext *uc,
+ const json_t *transaction)
+{
+ uint16_t num_coins;
+ struct TALER_Amount withdraw_fee;
+ struct TALER_Amount withdraw_amount;
+ struct TALER_Amount amount_without_fee;
+ uint8_t max_age = 0;
+ uint8_t noreveal_index = 0;
+ struct TALER_HashBlindedPlanchetsP planchets_h;
+ struct TALER_HashBlindedPlanchetsP selected_h;
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct TALER_BlindingMasterSeedP blinding_seed;
+ struct TALER_DenominationHashP *denom_pub_hashes;
+ size_t num_denom_pub_hashes;
+ bool no_max_age;
+ bool no_noreveal_index;
+ bool no_blinding_seed;
+ bool no_selected_h;
+ struct GNUNET_JSON_Specification withdraw_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &reserve_sig),
+ GNUNET_JSON_spec_uint16 ("num_coins",
+ &num_coins),
+ GNUNET_JSON_spec_fixed_auto ("planchets_h",
+ &planchets_h),
+ TALER_JSON_spec_amount_any ("amount",
+ &withdraw_amount),
+ TALER_JSON_spec_amount_any ("withdraw_fee",
+ &withdraw_fee),
+ TALER_JSON_spec_array_of_denom_pub_h ("denom_pub_hashes",
+ &num_denom_pub_hashes,
+ &denom_pub_hashes),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("selected_h",
+ &selected_h),
+ &no_selected_h),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint8 ("max_age",
+ &max_age),
+ &no_max_age),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("blinding_seed",
+ &blinding_seed),
+ &no_blinding_seed),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint8 ("noreveal_index",
+ &noreveal_index),
+ &no_noreveal_index),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rh->type = TALER_EXCHANGE_RTT_WITHDRAWAL;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ withdraw_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if ((no_max_age != no_noreveal_index) ||
+ (no_max_age != no_selected_h))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ rh->details.withdraw.age_restricted = ! no_max_age;
+
+ if (num_coins != num_denom_pub_hashes)
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+
+ /* Check that the signature is a valid withdraw request */
+ if (0>TALER_amount_subtract (
+ &amount_without_fee,
+ &withdraw_amount,
+ &withdraw_fee))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ TALER_wallet_withdraw_verify (
+ &amount_without_fee,
+ &withdraw_fee,
+ &planchets_h,
+ no_blinding_seed ? NULL : &blinding_seed,
+ no_max_age ? NULL : &uc->keys->age_mask,
+ no_max_age ? 0 : max_age,
+ uc->reserve_pub,
+ &reserve_sig))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+
+ rh->details.withdraw.num_coins = num_coins;
+ rh->details.withdraw.age_restricted = ! no_max_age;
+ rh->details.withdraw.max_age = max_age;
+ rh->details.withdraw.planchets_h = planchets_h;
+ rh->details.withdraw.selected_h = selected_h;
+ rh->details.withdraw.noreveal_index = noreveal_index;
+ rh->details.withdraw.no_blinding_seed = no_blinding_seed;
+ if (! no_blinding_seed)
+ rh->details.withdraw.blinding_seed = blinding_seed;
+
+ /* check that withdraw fee matches expectations! */
+ {
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct TALER_Amount fee_acc;
+ struct TALER_Amount amount_acc;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (withdraw_amount.currency,
+ &fee_acc));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (withdraw_amount.currency,
+ &amount_acc));
+
+ key_state = uc->keys;
+
+ /* accumulate the withdraw fees */
+ for (size_t i=0; i < num_coins; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *dki;
+
+ dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
+ &denom_pub_hashes[i]);
+ if (NULL == dki)
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&fee_acc,
+ &fee_acc,
+ &dki->fees.withdraw));
+ GNUNET_assert (0 <=
+ TALER_amount_add (&amount_acc,
+ &amount_acc,
+ &dki->value));
+ }
+
+ if ( (GNUNET_YES !=
+ TALER_amount_cmp_currency (&fee_acc,
+ &withdraw_fee)) ||
+ (0 !=
+ TALER_amount_cmp (&amount_acc,
+ &amount_without_fee)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ rh->details.withdraw.fee = withdraw_fee;
+ }
+
+ #pragma message "is out_authorization_sig still needed? Not set anywhere"
+ rh->details.withdraw.out_authorization_sig
+ = json_object_get (transaction,
+ "signature");
+ /* Check check that the same withdraw transaction
+ isn't listed twice by the exchange. We use the
+ "uuid" array to remember the hashes of all
+ signatures, and compare the hashes to find
+ duplicates. */
+ GNUNET_CRYPTO_hash (&reserve_sig,
+ sizeof (reserve_sig),
+ &uc->uuids[uc->uuid_off]);
+ for (unsigned int i = 0; i<uc->uuid_off; i++)
+ {
+ if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off],
+ &uc->uuids[i]))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ }
+ uc->uuid_off++;
+
+ if (0 >
+ TALER_amount_add (uc->total_out,
+ uc->total_out,
+ &rh->amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse "recoup" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+ struct HistoryParseContext *uc,
+ const json_t *transaction)
+{
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct GNUNET_JSON_Specification recoup_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &rh->details.recoup_details.coin_pub),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rh->details.recoup_details.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rh->details.recoup_details.exchange_pub),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &rh->details.recoup_details.timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rh->type = TALER_EXCHANGE_RTT_RECOUP;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ recoup_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ key_state = uc->keys;
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (key_state,
+ &rh->details.
+ recoup_details.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_confirm_recoup_verify (
+ rh->details.recoup_details.timestamp,
+ &rh->amount,
+ &rh->details.recoup_details.coin_pub,
+ uc->reserve_pub,
+ &rh->details.recoup_details.exchange_pub,
+ &rh->details.recoup_details.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 >
+ TALER_amount_add (uc->total_in,
+ uc->total_in,
+ &rh->amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse "closing" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+ struct HistoryParseContext *uc,
+ const json_t *transaction)
+{
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct TALER_FullPayto receiver_uri;
+ struct GNUNET_JSON_Specification closing_spec[] = {
+ TALER_JSON_spec_full_payto_uri (
+ "receiver_account_details",
+ &receiver_uri),
+ GNUNET_JSON_spec_fixed_auto ("wtid",
+ &rh->details.close_details.wtid),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rh->details.close_details.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rh->details.close_details.exchange_pub),
+ TALER_JSON_spec_amount_any ("closing_fee",
+ &rh->details.close_details.fee),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &rh->details.close_details.timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rh->type = TALER_EXCHANGE_RTT_CLOSING;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ closing_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ key_state = uc->keys;
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (
+ key_state,
+ &rh->details.close_details.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_reserve_closed_verify (
+ rh->details.close_details.timestamp,
+ &rh->amount,
+ &rh->details.close_details.fee,
+ receiver_uri,
+ &rh->details.close_details.wtid,
+ uc->reserve_pub,
+ &rh->details.close_details.exchange_pub,
+ &rh->details.close_details.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 >
+ TALER_amount_add (uc->total_out,
+ uc->total_out,
+ &rh->amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rh->details.close_details.receiver_account_details.full_payto
+ = GNUNET_strdup (receiver_uri.full_payto);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse "merge" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+ struct HistoryParseContext *uc,
+ const json_t *transaction)
+{
+ uint32_t flags32;
+ struct GNUNET_JSON_Specification merge_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &rh->details.merge_details.h_contract_terms),
+ GNUNET_JSON_spec_fixed_auto ("merge_pub",
+ &rh->details.merge_details.merge_pub),
+ GNUNET_JSON_spec_fixed_auto ("purse_pub",
+ &rh->details.merge_details.purse_pub),
+ GNUNET_JSON_spec_uint32 ("min_age",
+ &rh->details.merge_details.min_age),
+ GNUNET_JSON_spec_uint32 ("flags",
+ &flags32),
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &rh->details.merge_details.reserve_sig),
+ TALER_JSON_spec_amount_any ("purse_fee",
+ &rh->details.merge_details.purse_fee),
+ GNUNET_JSON_spec_timestamp ("merge_timestamp",
+ &rh->details.merge_details.merge_timestamp),
+ GNUNET_JSON_spec_timestamp ("purse_expiration",
+ &rh->details.merge_details.purse_expiration),
+ GNUNET_JSON_spec_bool ("merged",
+ &rh->details.merge_details.merged),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rh->type = TALER_EXCHANGE_RTT_MERGE;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ merge_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rh->details.merge_details.flags =
+ (enum TALER_WalletAccountMergeFlags) flags32;
+ if (GNUNET_OK !=
+ TALER_wallet_account_merge_verify (
+ rh->details.merge_details.merge_timestamp,
+ &rh->details.merge_details.purse_pub,
+ rh->details.merge_details.purse_expiration,
+ &rh->details.merge_details.h_contract_terms,
+ &rh->amount,
+ &rh->details.merge_details.purse_fee,
+ rh->details.merge_details.min_age,
+ rh->details.merge_details.flags,
+ uc->reserve_pub,
+ &rh->details.merge_details.reserve_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (rh->details.merge_details.merged)
+ {
+ if (0 >
+ TALER_amount_add (uc->total_in,
+ uc->total_in,
+ &rh->amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ else
+ {
+ if (0 >
+ TALER_amount_add (uc->total_out,
+ uc->total_out,
+ &rh->details.merge_details.purse_fee))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse "open" reserve open entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+ struct HistoryParseContext *uc,
+ const json_t *transaction)
+{
+ struct GNUNET_JSON_Specification open_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &rh->details.open_request.reserve_sig),
+ TALER_JSON_spec_amount_any ("open_payment",
+ &rh->details.open_request.reserve_payment),
+ GNUNET_JSON_spec_uint32 ("requested_min_purses",
+ &rh->details.open_request.purse_limit),
+ GNUNET_JSON_spec_timestamp ("request_timestamp",
+ &rh->details.open_request.request_timestamp),
+ GNUNET_JSON_spec_timestamp ("requested_expiration",
+ &rh->details.open_request.reserve_expiration),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rh->type = TALER_EXCHANGE_RTT_OPEN;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ open_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_reserve_open_verify (
+ &rh->amount,
+ rh->details.open_request.request_timestamp,
+ rh->details.open_request.reserve_expiration,
+ rh->details.open_request.purse_limit,
+ uc->reserve_pub,
+ &rh->details.open_request.reserve_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 >
+ TALER_amount_add (uc->total_out,
+ uc->total_out,
+ &rh->amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse "close" reserve close entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+ struct HistoryParseContext *uc,
+ const json_t *transaction)
+{
+ struct GNUNET_JSON_Specification close_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &rh->details.close_request.reserve_sig),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_payto",
+ &rh->details.close_request.
+ target_account_h_payto),
+ NULL),
+ GNUNET_JSON_spec_timestamp ("request_timestamp",
+ &rh->details.close_request.request_timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rh->type = TALER_EXCHANGE_RTT_CLOSE;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ close_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* force amount to invalid */
+ memset (&rh->amount,
+ 0,
+ sizeof (rh->amount));
+ if (GNUNET_OK !=
+ TALER_wallet_reserve_close_verify (
+ rh->details.close_request.request_timestamp,
+ &rh->details.close_request.target_account_h_payto,
+ uc->reserve_pub,
+ &rh->details.close_request.reserve_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+static void
+free_reserve_history (
+ unsigned int len,
+ struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len])
+{
+ for (unsigned int i = 0; i<len; i++)
+ {
+ struct TALER_EXCHANGE_ReserveHistoryEntry *rhi = &rhistory[i];
+
+ switch (rhi->type)
+ {
+ case TALER_EXCHANGE_RTT_CREDIT:
+ GNUNET_free (rhi->details.in_details.sender_url.full_payto);
+ break;
+ case TALER_EXCHANGE_RTT_WITHDRAWAL:
+ break;
+ case TALER_EXCHANGE_RTT_RECOUP:
+ break;
+ case TALER_EXCHANGE_RTT_CLOSING:
+ break;
+ case TALER_EXCHANGE_RTT_MERGE:
+ break;
+ case TALER_EXCHANGE_RTT_OPEN:
+ break;
+ case TALER_EXCHANGE_RTT_CLOSE:
+ GNUNET_free (rhi->details.close_details
+ .receiver_account_details.full_payto);
+ break;
+ }
+ }
+ GNUNET_free (rhistory);
+}
+
+
+/**
+ * Parse history given in JSON format and return it in binary
+ * format.
+ *
+ * @param keys exchange keys
+ * @param history JSON array with the history
+ * @param reserve_pub public key of the reserve to inspect
+ * @param currency currency we expect the balance to be in
+ * @param[out] total_in set to value of credits to reserve
+ * @param[out] total_out set to value of debits from reserve
+ * @param history_length number of entries in @a history
+ * @param[out] rhistory array of length @a history_length, set to the
+ * parsed history entries
+ * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
+ * were set,
+ * #GNUNET_SYSERR if there was a protocol violation in @a history
+ */
+static enum GNUNET_GenericReturnValue
+parse_reserve_history (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const json_t *history,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const char *currency,
+ struct TALER_Amount *total_in,
+ struct TALER_Amount *total_out,
+ unsigned int history_length,
+ struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length])
+{
+ const struct
+ {
+ const char *type;
+ ParseHelper helper;
+ } map[] = {
+ { "CREDIT", &parse_credit },
+ { "WITHDRAW", &parse_withdraw },
+ { "RECOUP", &parse_recoup },
+ { "MERGE", &parse_merge },
+ { "CLOSING", &parse_closing },
+ { "OPEN", &parse_open },
+ { "CLOSE", &parse_close },
+ { NULL, NULL }
+ };
+ struct GNUNET_HashCode uuid[history_length];
+ struct HistoryParseContext uc = {
+ .keys = keys,
+ .reserve_pub = reserve_pub,
+ .uuids = uuid,
+ .total_in = total_in,
+ .total_out = total_out
+ };
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (currency,
+ total_in));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (currency,
+ total_out));
+ for (unsigned int off = 0; off<history_length; off++)
+ {
+ struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off];
+ json_t *transaction;
+ struct TALER_Amount amount;
+ const char *type;
+ struct GNUNET_JSON_Specification hist_spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ TALER_JSON_spec_amount_any ("amount",
+ &amount),
+ /* 'wire' and 'signature' are optional depending on 'type'! */
+ GNUNET_JSON_spec_end ()
+ };
+ bool found = false;
+
+ transaction = json_array_get (history,
+ off);
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ hist_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ json_dumpf (transaction,
+ stderr,
+ JSON_INDENT (2));
+ return GNUNET_SYSERR;
+ }
+ rh->amount = amount;
+ if (GNUNET_YES !=
+ TALER_amount_cmp_currency (&amount,
+ total_in))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ for (unsigned int i = 0; NULL != map[i].type; i++)
+ {
+ if (0 == strcasecmp (map[i].type,
+ type))
+ {
+ found = true;
+ if (GNUNET_OK !=
+ map[i].helper (rh,
+ &uc,
+ transaction))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ break;
+ }
+ }
+ if (! found)
+ {
+ /* unexpected 'type', protocol incompatibility, complain! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle HTTP header received by curl.
+ *
+ * @param buffer one line of HTTP header data
+ * @param size size of an item
+ * @param nitems number of items passed
+ * @param userdata our `struct TALER_EXCHANGE_ReservesHistoryHandle *`
+ * @return `size * nitems`
+ */
+static size_t
+handle_header (char *buffer,
+ size_t size,
+ size_t nitems,
+ void *userdata)
+{
+ struct TALER_EXCHANGE_ReservesHistoryHandle *rhh = userdata;
+ size_t total = size * nitems;
+ char *ndup;
+ const char *hdr_type;
+ char *hdr_val;
+ char *sp;
+
+ ndup = GNUNET_strndup (buffer,
+ total);
+ hdr_type = strtok_r (ndup,
+ ":",
+ &sp);
+ if (NULL == hdr_type)
+ {
+ GNUNET_free (ndup);
+ return total;
+ }
+ hdr_val = strtok_r (NULL,
+ "\n\r",
+ &sp);
+ if (NULL == hdr_val)
+ {
+ GNUNET_free (ndup);
+ return total;
+ }
+ if (' ' == *hdr_val)
+ hdr_val++;
+ if (0 == strcasecmp (hdr_type,
+ MHD_HTTP_HEADER_ETAG))
+ {
+ unsigned long long tval;
+ char dummy;
+
+ if (1 !=
+ sscanf (hdr_val,
+ "\"%llu\"%c",
+ &tval,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (ndup);
+ return 0;
+ }
+ rhh->etag = (uint64_t) tval;
+ }
+ GNUNET_free (ndup);
+ return total;
+}
+
+
+/**
+ * We received an #MHD_HTTP_OK history code. Handle the JSON
+ * response.
+ *
+ * @param rsh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rsh,
+ const json_t *j)
+{
+ const json_t *history;
+ unsigned int len;
+ struct TALER_EXCHANGE_ReserveHistory rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_OK,
+ .details.ok.etag = rsh->etag
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("balance",
+ &rs.details.ok.balance),
+ GNUNET_JSON_spec_array_const ("history",
+ &history),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ len = json_array_size (history);
+ {
+ struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
+
+ rhistory = GNUNET_new_array (len,
+ struct TALER_EXCHANGE_ReserveHistoryEntry);
+ if (GNUNET_OK !=
+ parse_reserve_history (rsh->keys,
+ history,
+ &rsh->reserve_pub,
+ rs.details.ok.balance.currency,
+ &rs.details.ok.total_in,
+ &rs.details.ok.total_out,
+ len,
+ rhistory))
+ {
+ GNUNET_break_op (0);
+ free_reserve_history (len,
+ rhistory);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ if (NULL != rsh->cb)
+ {
+ rs.details.ok.history = rhistory;
+ rs.details.ok.history_len = len;
+ rsh->cb (rsh->cb_cls,
+ &rs);
+ rsh->cb = NULL;
+ }
+ free_reserve_history (len,
+ rhistory);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RID/history request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReservesHistoryHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserves_history_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ReservesHistoryHandle *rsh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_ReserveHistory rs = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ rsh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ handle_reserves_history_ok (rsh,
+ j))
+ {
+ rs.hr.http_status = 0;
+ rs.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 */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.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 */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.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 */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for reserves history\n",
+ (unsigned int) response_code,
+ (int) rs.hr.ec);
+ break;
+ }
+ if (NULL != rsh->cb)
+ {
+ rsh->cb (rsh->cb_cls,
+ &rs);
+ rsh->cb = NULL;
+ }
+ TALER_EXCHANGE_reserves_history_cancel (rsh);
+}
+
+
+struct TALER_EXCHANGE_ReservesHistoryHandle *
+TALER_EXCHANGE_reserves_history (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ uint64_t start_off,
+ TALER_EXCHANGE_ReservesHistoryCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ReservesHistoryHandle *rsh;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 64];
+ struct curl_slist *job_headers;
+
+ rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle);
+ rsh->cb = cb;
+ rsh->cb_cls = cb_cls;
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &rsh->reserve_pub.eddsa_pub);
+ {
+ char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &rsh->reserve_pub,
+ sizeof (rsh->reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ if (0 != start_off)
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s/history?start=%llu",
+ pub_str,
+ (unsigned long long) start_off);
+ else
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s/history",
+ pub_str);
+ }
+ rsh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == rsh->url)
+ {
+ GNUNET_free (rsh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (rsh->url);
+ GNUNET_free (rsh);
+ return NULL;
+ }
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERFUNCTION,
+ &handle_header));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERDATA,
+ rsh));
+ {
+ struct TALER_ReserveSignatureP reserve_sig;
+ char *sig_hdr;
+ char *hdr;
+
+ TALER_wallet_reserve_history_sign (start_off,
+ reserve_priv,
+ &reserve_sig);
+
+ sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
+ &reserve_sig,
+ sizeof (reserve_sig));
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ TALER_RESERVE_HISTORY_SIGNATURE_HEADER,
+ sig_hdr);
+ GNUNET_free (sig_hdr);
+ job_headers = curl_slist_append (NULL,
+ hdr);
+ GNUNET_free (hdr);
+ if (NULL == job_headers)
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ return NULL;
+ }
+ }
+
+ rsh->keys = TALER_EXCHANGE_keys_incref (keys);
+ rsh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ job_headers,
+ &handle_reserves_history_finished,
+ rsh);
+ curl_slist_free_all (job_headers);
+ return rsh;
+}
+
+
+void
+TALER_EXCHANGE_reserves_history_cancel (
+ struct TALER_EXCHANGE_ReservesHistoryHandle *rsh)
+{
+ if (NULL != rsh->job)
+ {
+ GNUNET_CURL_job_cancel (rsh->job);
+ rsh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&rsh->post_ctx);
+ GNUNET_free (rsh->url);
+ TALER_EXCHANGE_keys_decref (rsh->keys);
+ GNUNET_free (rsh);
+}
+
+
+/* end of exchange_api_reserves_history.c */
diff --git a/src/lib/exchange_api_get-reserves-RESERVE_PUB.c b/src/lib/exchange_api_get-reserves-RESERVE_PUB.c
@@ -0,0 +1,279 @@
+/*
+ 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_get-reserves-RESERVE_PUB.c
+ * @brief Implementation of the GET /reserves/$RESERVE_PUB requests
+ * @author Christian Grothoff
+ */
+#include "taler/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/taler_exchange_service.h"
+#include "taler/taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /reserves/ GET Handle
+ */
+struct TALER_EXCHANGE_ReservesGetHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ReservesGetCallback cb;
+
+ /**
+ * Public key of the reserve we are querying.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * We received an #MHD_HTTP_OK status code. Handle the JSON
+ * response.
+ *
+ * @param rgh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_get_ok (struct TALER_EXCHANGE_ReservesGetHandle *rgh,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_ReserveSummary rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("balance",
+ &rs.details.ok.balance),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string (
+ "last_origin",
+ (const char **) &rs.details.ok.last_origin.full_payto),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rgh->cb (rgh->cb_cls,
+ &rs);
+ rgh->cb = NULL;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/ GET request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReservesGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserves_get_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ReservesGetHandle *rgh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_ReserveSummary rs = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ rgh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ handle_reserves_get_ok (rgh,
+ j))
+ {
+ rs.hr.http_status = 0;
+ rs.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 */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.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 */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.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 */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for GET %s\n",
+ (unsigned int) response_code,
+ (int) rs.hr.ec,
+ rgh->url);
+ break;
+ }
+ if (NULL != rgh->cb)
+ {
+ rgh->cb (rgh->cb_cls,
+ &rs);
+ rgh->cb = NULL;
+ }
+ TALER_EXCHANGE_reserves_get_cancel (rgh);
+}
+
+
+struct TALER_EXCHANGE_ReservesGetHandle *
+TALER_EXCHANGE_reserves_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_EXCHANGE_ReservesGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ReservesGetHandle *rgh;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 16 + 32];
+ unsigned int tms
+ = (unsigned int) timeout.rel_value_us
+ / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
+
+ {
+ char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+ char *end;
+ char timeout_str[32];
+
+ end = GNUNET_STRINGS_data_to_string (
+ reserve_pub,
+ sizeof (*reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (timeout_str,
+ sizeof (timeout_str),
+ "%u",
+ tms);
+ if (0 == tms)
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s",
+ pub_str);
+ else
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s?timeout_ms=%s",
+ pub_str,
+ timeout_str);
+ }
+ rgh = GNUNET_new (struct TALER_EXCHANGE_ReservesGetHandle);
+ rgh->cb = cb;
+ rgh->cb_cls = cb_cls;
+ rgh->reserve_pub = *reserve_pub;
+ rgh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == rgh->url)
+ {
+ GNUNET_free (rgh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (rgh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (rgh->url);
+ GNUNET_free (rgh);
+ return NULL;
+ }
+ if (0 != tms)
+ {
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT_MS,
+ (long) (tms + 100L)));
+ }
+ rgh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_reserves_get_finished,
+ rgh);
+ return rgh;
+}
+
+
+void
+TALER_EXCHANGE_reserves_get_cancel (
+ struct TALER_EXCHANGE_ReservesGetHandle *rgh)
+{
+ if (NULL != rgh->job)
+ {
+ GNUNET_CURL_job_cancel (rgh->job);
+ rgh->job = NULL;
+ }
+ GNUNET_free (rgh->url);
+ GNUNET_free (rgh);
+}
+
+
+/* end of exchange_api_reserves_get.c */
diff --git a/src/lib/exchange_api_get-reserves-attest-RESERVE_PUB.c b/src/lib/exchange_api_get-reserves-attest-RESERVE_PUB.c
@@ -0,0 +1,276 @@
+/*
+ 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_get-reserves-attest-RESERVE_PUB.c
+ * @brief Implementation of the GET_ATTESTABLE /reserves/$RESERVE_PUB requests
+ * @author Christian Grothoff
+ */
+#include "taler/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/taler_exchange_service.h"
+#include "taler/taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /reserves/ GET_ATTESTABLE Handle
+ */
+struct TALER_EXCHANGE_ReservesGetAttestHandle
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ReservesGetAttestCallback cb;
+
+ /**
+ * Public key of the reserve we are querying.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * We received an #MHD_HTTP_OK status code. Handle the JSON
+ * response.
+ *
+ * @param rgah handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_get_attestable_ok (
+ struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_ReserveGetAttestResult rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ const json_t *details;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("details",
+ &details),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ {
+ unsigned int dlen = json_array_size (details);
+ const char *attributes[GNUNET_NZL (dlen)];
+
+ for (unsigned int i = 0; i<dlen; i++)
+ {
+ json_t *detail = json_array_get (details,
+ i);
+ attributes[i] = json_string_value (detail);
+ if (NULL == attributes[i])
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ rs.details.ok.attributes_length = dlen;
+ rs.details.ok.attributes = attributes;
+ rgah->cb (rgah->cb_cls,
+ &rs);
+ rgah->cb = NULL;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP GET /reserves-attest/$RID request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReservesGetAttestableHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserves_get_attestable_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_ReserveGetAttestResult rs = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ rgah->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ handle_reserves_get_attestable_ok (rgah,
+ j))
+ {
+ rs.hr.http_status = 0;
+ rs.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 */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.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 */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.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 */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for reserves get_attestable\n",
+ (unsigned int) response_code,
+ (int) rs.hr.ec);
+ break;
+ }
+ if (NULL != rgah->cb)
+ {
+ rgah->cb (rgah->cb_cls,
+ &rs);
+ rgah->cb = NULL;
+ }
+ TALER_EXCHANGE_reserves_get_attestable_cancel (rgah);
+}
+
+
+struct TALER_EXCHANGE_ReservesGetAttestHandle *
+TALER_EXCHANGE_reserves_get_attestable (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ TALER_EXCHANGE_ReservesGetAttestCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah;
+ CURL *eh;
+ 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 (
+ reserve_pub,
+ sizeof (*reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves-attest/%s",
+ pub_str);
+ }
+ rgah = GNUNET_new (struct TALER_EXCHANGE_ReservesGetAttestHandle);
+ rgah->cb = cb;
+ rgah->cb_cls = cb_cls;
+ rgah->reserve_pub = *reserve_pub;
+ rgah->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == rgah->url)
+ {
+ GNUNET_free (rgah);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (rgah->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (rgah->url);
+ GNUNET_free (rgah);
+ return NULL;
+ }
+ rgah->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_reserves_get_attestable_finished,
+ rgah);
+ return rgah;
+}
+
+
+void
+TALER_EXCHANGE_reserves_get_attestable_cancel (
+ struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah)
+{
+ if (NULL != rgah->job)
+ {
+ GNUNET_CURL_job_cancel (rgah->job);
+ rgah->job = NULL;
+ }
+ GNUNET_free (rgah->url);
+ GNUNET_free (rgah);
+}
+
+
+/* end of exchange_api_reserves_get_attestable.c */
diff --git a/src/lib/exchange_api_get-transfers-WTID.c b/src/lib/exchange_api_get-transfers-WTID.c
@@ -0,0 +1,400 @@
+/*
+ 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_get-transfers-WTID.c
+ * @brief Implementation of the GET /transfers/ request
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /transfers/ GET Handle
+ */
+struct TALER_EXCHANGE_TransfersGetHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_TransfersGetCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * We got a #MHD_HTTP_OK response for the /transfers/ request.
+ * Check that the response is well-formed and if it is, call the
+ * callback. If not, return an error code.
+ *
+ * This code is very similar to
+ * merchant_api_track_transfer.c::check_transfers_get_response_ok.
+ * Any changes should likely be reflected there as well.
+ *
+ * @param wdh handle to the operation
+ * @param json response we got
+ * @return #GNUNET_OK if we are done and all is well,
+ * #GNUNET_SYSERR if the response was bogus
+ */
+static enum GNUNET_GenericReturnValue
+check_transfers_get_response_ok (
+ struct TALER_EXCHANGE_TransfersGetHandle *wdh,
+ const json_t *json)
+{
+ const json_t *details_j;
+ struct TALER_Amount total_expected;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct TALER_EXCHANGE_TransfersGetResponse tgr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ struct TALER_EXCHANGE_TransferData *td
+ = &tgr.details.ok.td;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("total",
+ &td->total_amount),
+ TALER_JSON_spec_amount_any ("wire_fee",
+ &td->wire_fee),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &merchant_pub),
+ GNUNET_JSON_spec_fixed_auto ("h_payto",
+ &td->h_payto),
+ GNUNET_JSON_spec_timestamp ("execution_time",
+ &td->execution_time),
+ GNUNET_JSON_spec_array_const ("deposits",
+ &details_j),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &td->exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &td->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_amount_set_zero (td->total_amount.currency,
+ &total_expected))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (
+ wdh->keys,
+ &td->exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ td->details_length = json_array_size (details_j);
+ {
+ struct GNUNET_HashContext *hash_context;
+ struct TALER_TrackTransferDetails *details;
+
+ details = GNUNET_new_array (td->details_length,
+ struct TALER_TrackTransferDetails);
+ td->details = details;
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+ for (unsigned int i = 0; i<td->details_length; i++)
+ {
+ struct TALER_TrackTransferDetails *detail = &details[i];
+ struct json_t *detail_j = json_array_get (details_j, i);
+ struct GNUNET_JSON_Specification spec_detail[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &detail->h_contract_terms),
+ GNUNET_JSON_spec_fixed_auto ("coin_pub", &detail->coin_pub),
+ TALER_JSON_spec_amount ("deposit_value",
+ total_expected.currency,
+ &detail->coin_value),
+ TALER_JSON_spec_amount ("deposit_fee",
+ total_expected.currency,
+ &detail->coin_fee),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount ("refund_total",
+ total_expected.currency,
+ &detail->refund_total),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (td->total_amount.currency,
+ &detail->refund_total));
+ if ( (GNUNET_OK !=
+ GNUNET_JSON_parse (detail_j,
+ spec_detail,
+ NULL, NULL)) ||
+ (0 >
+ TALER_amount_add (&total_expected,
+ &total_expected,
+ &detail->coin_value)) ||
+ (0 >
+ TALER_amount_subtract (&total_expected,
+ &total_expected,
+ &detail->coin_fee)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_CRYPTO_hash_context_abort (hash_context);
+ GNUNET_free (details);
+ return GNUNET_SYSERR;
+ }
+ /* build up big hash for signature checking later */
+ TALER_exchange_online_wire_deposit_append (
+ hash_context,
+ &detail->h_contract_terms,
+ td->execution_time,
+ &detail->coin_pub,
+ &detail->coin_value,
+ &detail->coin_fee);
+ }
+ /* Check signature */
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ &td->h_details);
+ if (GNUNET_OK !=
+ TALER_exchange_online_wire_deposit_verify (
+ &td->total_amount,
+ &td->wire_fee,
+ &merchant_pub,
+ &td->h_payto,
+ &td->h_details,
+ &td->exchange_pub,
+ &td->exchange_sig))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (details);
+ return GNUNET_SYSERR;
+ }
+ if (0 >
+ TALER_amount_subtract (&total_expected,
+ &total_expected,
+ &td->wire_fee))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (details);
+ return GNUNET_SYSERR;
+ }
+ if (0 !=
+ TALER_amount_cmp (&total_expected,
+ &td->total_amount))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (details);
+ return GNUNET_SYSERR;
+ }
+ wdh->cb (wdh->cb_cls,
+ &tgr);
+ GNUNET_free (details);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /transfers/ request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_TransfersGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_transfers_get_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_TransfersGetHandle *wdh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_TransfersGetResponse tgr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ wdh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK ==
+ check_transfers_get_response_ok (wdh,
+ j))
+ {
+ TALER_EXCHANGE_transfers_get_cancel (wdh);
+ return;
+ }
+ GNUNET_break_op (0);
+ tgr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ tgr.hr.http_status = 0;
+ 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 */
+ tgr.hr.ec = TALER_JSON_get_error_code (j);
+ tgr.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 */
+ tgr.hr.ec = TALER_JSON_get_error_code (j);
+ tgr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Exchange does not know about transaction;
+ we should pass the reply to the application */
+ tgr.hr.ec = TALER_JSON_get_error_code (j);
+ tgr.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 */
+ tgr.hr.ec = TALER_JSON_get_error_code (j);
+ tgr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ tgr.hr.ec = TALER_JSON_get_error_code (j);
+ tgr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for transfers get\n",
+ (unsigned int) response_code,
+ (int) tgr.hr.ec);
+ break;
+ }
+ wdh->cb (wdh->cb_cls,
+ &tgr);
+ TALER_EXCHANGE_transfers_get_cancel (wdh);
+}
+
+
+struct TALER_EXCHANGE_TransfersGetHandle *
+TALER_EXCHANGE_transfers_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ TALER_EXCHANGE_TransfersGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_TransfersGetHandle *wdh;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2 + 32];
+
+ wdh = GNUNET_new (struct TALER_EXCHANGE_TransfersGetHandle);
+ wdh->cb = cb;
+ wdh->cb_cls = cb_cls;
+
+ {
+ char wtid_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (wtid,
+ sizeof (struct
+ TALER_WireTransferIdentifierRawP),
+ wtid_str,
+ sizeof (wtid_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "transfers/%s",
+ wtid_str);
+ }
+ wdh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == wdh->url)
+ {
+ GNUNET_free (wdh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (wdh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (wdh->url);
+ GNUNET_free (wdh);
+ return NULL;
+ }
+ wdh->keys = TALER_EXCHANGE_keys_incref (keys);
+ wdh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
+ eh,
+ &handle_transfers_get_finished,
+ wdh);
+ return wdh;
+}
+
+
+/**
+ * Cancel wire deposits request. This function cannot be used on a request
+ * handle if a response is already served for it.
+ *
+ * @param wdh the wire deposits request handle
+ */
+void
+TALER_EXCHANGE_transfers_get_cancel (
+ struct TALER_EXCHANGE_TransfersGetHandle *wdh)
+{
+ if (NULL != wdh->job)
+ {
+ GNUNET_CURL_job_cancel (wdh->job);
+ wdh->job = NULL;
+ }
+ GNUNET_free (wdh->url);
+ TALER_EXCHANGE_keys_decref (wdh->keys);
+ GNUNET_free (wdh);
+}
+
+
+/* end of exchange_api_transfers_get.c */
diff --git a/src/lib/exchange_api_get_aml_measures.c b/src/lib/exchange_api_get_aml_measures.c
@@ -1,650 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2023, 2024 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_get_aml_measures.c
- * @brief Implementation of the GET /aml/$OFFICER_PUB/measures request
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * Scrap buffer of temporary arrays.
- */
-struct Scrap
-{
- /**
- * Kept in DLL.
- */
- struct Scrap *next;
-
- /**
- * Kept in DLL.
- */
- struct Scrap *prev;
-
- /**
- * Pointer to our allocation.
- */
- const char **ptr;
-};
-
-
-/**
- * @brief A GET /aml/$OFFICER_PUB/measures Handle
- */
-struct TALER_EXCHANGE_AmlGetMeasuresHandle
-{
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_AmlMeasuresCallback measures_cb;
-
- /**
- * Closure for @e measures_cb.
- */
- void *measures_cb_cls;
-
- /**
- * HTTP headers for the job.
- */
- struct curl_slist *job_headers;
-
- /**
- * Head of scrap list.
- */
- struct Scrap *scrap_head;
-
- /**
- * Tail of scrap list.
- */
- struct Scrap *scrap_tail;
-};
-
-
-/**
- * Parse AML measures.
- *
- * @param jroots JSON object with measure data
- * @param[out] roots where to write the result
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_aml_roots (
- const json_t *jroots,
- struct TALER_EXCHANGE_AvailableAmlMeasures *roots)
-{
- const json_t *obj;
- const char *name;
- size_t idx = 0;
-
- json_object_foreach ((json_t *) jroots, name, obj)
- {
- struct TALER_EXCHANGE_AvailableAmlMeasures *root
- = &roots[idx++];
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("check_name",
- &root->check_name),
- GNUNET_JSON_spec_string ("prog_name",
- &root->prog_name),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_const ("context",
- &root->context),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (obj,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- root->measure_name = name;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Create array of length @a len in scrap book.
- *
- * @param[in,out] lh context for allocations
- * @param len length of array
- * @return scrap array
- */
-static const char **
-make_scrap (
- struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh,
- unsigned int len)
-{
- struct Scrap *s = GNUNET_new (struct Scrap);
-
- s->ptr = GNUNET_new_array (len,
- const char *);
- GNUNET_CONTAINER_DLL_insert (lh->scrap_head,
- lh->scrap_tail,
- s);
- return s->ptr;
-}
-
-
-/**
- * Free all scrap space.
- *
- * @param[in,out] lh scrap context
- */
-static void
-free_scrap (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh)
-{
- struct Scrap *s;
-
- while (NULL != (s = lh->scrap_head))
- {
- GNUNET_CONTAINER_DLL_remove (lh->scrap_head,
- lh->scrap_tail,
- s);
- GNUNET_free (s->ptr);
- GNUNET_free (s);
- }
-}
-
-
-/**
- * Convert JSON array of strings to string array.
- *
- * @param j JSON array to convert
- * @param[out] a array to initialize
- * @return true on success
- */
-static bool
-j_to_a (const json_t *j,
- const char **a)
-{
- const json_t *e;
- size_t idx;
-
- json_array_foreach ((json_t *) j, idx, e)
- {
- if (NULL == (a[idx] = json_string_value (e)))
- return false;
- }
- return true;
-}
-
-
-/**
- * Parse AML programs.
- *
- * @param[in,out] lh context for allocations
- * @param jprogs JSON object with program data
- * @param[out] progs where to write the result
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_aml_programs (
- struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh,
- const json_t *jprogs,
- struct TALER_EXCHANGE_AvailableAmlPrograms *progs)
-{
- const json_t *obj;
- const char *name;
- size_t idx = 0;
-
- json_object_foreach ((json_t *) jprogs, name, obj)
- {
- struct TALER_EXCHANGE_AvailableAmlPrograms *prog
- = &progs[idx++];
- const json_t *jcontext;
- const json_t *jinputs;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("description",
- &prog->description),
- GNUNET_JSON_spec_array_const ("context",
- &jcontext),
- GNUNET_JSON_spec_array_const ("inputs",
- &jinputs),
- GNUNET_JSON_spec_end ()
- };
- unsigned int len;
- const char **ptr;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (obj,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- prog->prog_name = name;
- prog->contexts_length
- = (unsigned int) json_array_size (jcontext);
- prog->inputs_length
- = (unsigned int) json_array_size (jinputs);
- len = prog->contexts_length + prog->inputs_length;
- if ( ((unsigned long long) len) !=
- (unsigned long long) json_array_size (jcontext)
- + (unsigned long long) json_array_size (jinputs) )
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- ptr = make_scrap (lh,
- len);
- prog->contexts = ptr;
- if (! j_to_a (jcontext,
- prog->contexts))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- prog->inputs = &ptr[prog->contexts_length];
- if (! j_to_a (jinputs,
- prog->inputs))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Parse AML measures.
- *
- * @param[in,out] lh context for allocations
- * @param jchecks JSON object with measure data
- * @param[out] checks where to write the result
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_aml_checks (
- struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh,
- const json_t *jchecks,
- struct TALER_EXCHANGE_AvailableKycChecks *checks)
-{
- const json_t *obj;
- const char *name;
- size_t idx = 0;
-
- json_object_foreach ((json_t *) jchecks, name, obj)
- {
- struct TALER_EXCHANGE_AvailableKycChecks *check
- = &checks[idx++];
- const json_t *jrequires;
- const json_t *joutputs;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("description",
- &check->description),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_const ("description_i18n",
- &check->description_i18n),
- NULL),
- GNUNET_JSON_spec_array_const ("requires",
- &jrequires),
- GNUNET_JSON_spec_array_const ("outputs",
- &joutputs),
- GNUNET_JSON_spec_string ("fallback",
- &check->fallback),
- GNUNET_JSON_spec_end ()
- };
- unsigned int len;
- const char **ptr;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (obj,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- check->check_name = name;
-
- check->requires_length
- = (unsigned int) json_array_size (jrequires);
- check->outputs_length
- = (unsigned int) json_array_size (joutputs);
- len = check->requires_length + check->outputs_length;
- if ( ((unsigned long long) len) !=
- (unsigned long long) json_array_size (jrequires)
- + (unsigned long long) json_array_size (joutputs) )
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- ptr = make_scrap (lh,
- len);
- check->requires = ptr;
- if (! j_to_a (jrequires,
- check->requires))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- check->outputs = &ptr[check->requires_length];
- if (! j_to_a (joutputs,
- check->outputs))
- {
- 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_measures_ok (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh,
- const json_t *json)
-{
- struct TALER_EXCHANGE_AmlGetMeasuresResponse lr = {
- .hr.reply = json,
- .hr.http_status = MHD_HTTP_OK
- };
- const json_t *jroots;
- const json_t *jprograms;
- const json_t *jchecks;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_object_const ("roots",
- &jroots),
- GNUNET_JSON_spec_object_const ("programs",
- &jprograms),
- GNUNET_JSON_spec_object_const ("checks",
- &jchecks),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- lr.details.ok.roots_length
- = (unsigned int) json_object_size (jroots);
- lr.details.ok.programs_length
- = (unsigned int) json_object_size (jprograms);
- lr.details.ok.checks_length
- = (unsigned int) json_object_size (jchecks);
- if ( ( ((size_t) lr.details.ok.roots_length)
- != json_object_size (jroots)) ||
- ( ((size_t) lr.details.ok.programs_length)
- != json_object_size (jprograms)) ||
- ( ((size_t) lr.details.ok.checks_length)
- != json_object_size (jchecks)) )
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- {
- struct TALER_EXCHANGE_AvailableAmlMeasures roots[
- GNUNET_NZL (lr.details.ok.roots_length)];
- struct TALER_EXCHANGE_AvailableAmlPrograms progs[
- GNUNET_NZL (lr.details.ok.programs_length)];
- struct TALER_EXCHANGE_AvailableKycChecks checks[
- GNUNET_NZL (lr.details.ok.checks_length)];
- enum GNUNET_GenericReturnValue ret;
-
- memset (roots,
- 0,
- sizeof (roots));
- memset (progs,
- 0,
- sizeof (progs));
- memset (checks,
- 0,
- sizeof (checks));
- lr.details.ok.roots = roots;
- lr.details.ok.programs = progs;
- lr.details.ok.checks = checks;
- ret = parse_aml_roots (jroots,
- roots);
- if (GNUNET_OK == ret)
- ret = parse_aml_programs (lh,
- jprograms,
- progs);
- if (GNUNET_OK == ret)
- ret = parse_aml_checks (lh,
- jchecks,
- checks);
- if (GNUNET_OK == ret)
- {
- lh->measures_cb (lh->measures_cb_cls,
- &lr);
- lh->measures_cb = NULL;
- }
- free_scrap (lh);
- return ret;
- }
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /aml/$OFFICER_PUB/measures request.
- *
- * @param cls the `struct TALER_EXCHANGE_AmlGetMeasuresHandle`
- * @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_AmlGetMeasuresHandle *lh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_AmlGetMeasuresResponse 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_measures_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->measures_cb);
- TALER_EXCHANGE_aml_get_measures_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_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 get AML measures\n",
- (unsigned int) response_code,
- (int) lr.hr.ec);
- break;
- }
- if (NULL != lh->measures_cb)
- lh->measures_cb (lh->measures_cb_cls,
- &lr);
- TALER_EXCHANGE_aml_get_measures_cancel (lh);
-}
-
-
-struct TALER_EXCHANGE_AmlGetMeasuresHandle *
-TALER_EXCHANGE_aml_get_measures (
- struct GNUNET_CURL_Context *ctx,
- const char *exchange_url,
- const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
- TALER_EXCHANGE_AmlMeasuresCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh;
- CURL *eh;
- struct TALER_AmlOfficerPublicKeyP officer_pub;
- struct TALER_AmlOfficerSignatureP officer_sig;
- char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 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 *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/measures",
- pub_str);
- }
- lh = GNUNET_new (struct TALER_EXCHANGE_AmlGetMeasuresHandle);
- lh->measures_cb = cb;
- lh->measures_cb_cls = cb_cls;
- lh->url = TALER_url_join (exchange_url,
- arg_str,
- 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_aml_get_measures_cancel (
- struct TALER_EXCHANGE_AmlGetMeasuresHandle *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_get_aml_measures.c */
diff --git a/src/lib/exchange_api_get_kyc_statistics.c b/src/lib/exchange_api_get_kyc_statistics.c
@@ -1,317 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2023, 2024 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_get_kyc_statistics.c
- * @brief Implementation of the /aml/$OFFICER_PUB/kyc-statistics/$NAME request
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A GET /aml/$OFFICER_PUB/kyc-statistics/$NAME Handle
- */
-struct TALER_EXCHANGE_KycGetStatisticsHandle
-{
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_KycStatisticsCallback stats_cb;
-
- /**
- * Closure for @e cb.
- */
- void *stats_cb_cls;
-
- /**
- * HTTP headers for the job.
- */
- struct curl_slist *job_headers;
-
-};
-
-
-/**
- * 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_stats_ok (
- struct TALER_EXCHANGE_KycGetStatisticsHandle *lh,
- const json_t *json)
-{
- struct TALER_EXCHANGE_KycGetStatisticsResponse lr = {
- .hr.reply = json,
- .hr.http_status = MHD_HTTP_OK
- };
- uint64_t cnt;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint64 ("counter",
- &cnt),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- lr.details.ok.counter = (unsigned long long) cnt;
- lh->stats_cb (lh->stats_cb_cls,
- &lr);
- lh->stats_cb = NULL;
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /aml/$OFFICER_PUB/kyc-statistics/$NAME request.
- *
- * @param cls the `struct TALER_EXCHANGE_KycGetStatisticsHandle`
- * @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_KycGetStatisticsHandle *lh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_KycGetStatisticsResponse 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_stats_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->stats_cb);
- TALER_EXCHANGE_kyc_get_statistics_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_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 GET KYC statistics\n",
- (unsigned int) response_code,
- (int) lr.hr.ec);
- break;
- }
- if (NULL != lh->stats_cb)
- lh->stats_cb (lh->stats_cb_cls,
- &lr);
- TALER_EXCHANGE_kyc_get_statistics_cancel (lh);
-}
-
-
-struct TALER_EXCHANGE_KycGetStatisticsHandle *
-TALER_EXCHANGE_kyc_get_statistics (
- struct GNUNET_CURL_Context *ctx,
- const char *exchange_url,
- const char *name,
- struct GNUNET_TIME_Timestamp start_date,
- struct GNUNET_TIME_Timestamp end_date,
- const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
- TALER_EXCHANGE_KycStatisticsCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_KycGetStatisticsHandle *lh;
- CURL *eh;
- struct TALER_AmlOfficerPublicKeyP officer_pub;
- struct TALER_AmlOfficerSignatureP officer_sig;
- char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2
- + 32];
- char sd_str[32];
- char ed_str[32];
- const char *sd = NULL;
- const char *ed = NULL;
-
- if (! GNUNET_TIME_absolute_is_zero (start_date.abs_time))
- {
- unsigned long long sec;
-
- sec = (unsigned long long) GNUNET_TIME_timestamp_to_s (start_date);
- GNUNET_snprintf (sd_str,
- sizeof (sd_str),
- "%llu",
- sec);
- sd = sd_str;
- }
- if (! GNUNET_TIME_absolute_is_never (end_date.abs_time))
- {
- unsigned long long sec;
-
- sec = (unsigned long long) GNUNET_TIME_timestamp_to_s (end_date);
- GNUNET_snprintf (ed_str,
- sizeof (ed_str),
- "%llu",
- sec);
- ed = ed_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/kyc-statistics/%s",
- pub_str,
- name);
- }
- lh = GNUNET_new (struct TALER_EXCHANGE_KycGetStatisticsHandle);
- lh->stats_cb = cb;
- lh->stats_cb_cls = cb_cls;
- lh->url = TALER_url_join (exchange_url,
- arg_str,
- "start_date",
- sd,
- "end_date",
- ed,
- 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_kyc_get_statistics_cancel (
- struct TALER_EXCHANGE_KycGetStatisticsHandle *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_get_kyc_statistics.c */
diff --git a/src/lib/exchange_api_kyc_check.c b/src/lib/exchange_api_kyc_check.c
@@ -1,415 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2021-2024 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 "taler/platform.h"
-#include <microhttpd.h> /* just for HTTP check codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler/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;
-
- /**
- * 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;
-
-};
-
-
-static enum GNUNET_GenericReturnValue
-parse_account_status (
- struct TALER_EXCHANGE_KycCheckHandle *kch,
- const json_t *j,
- struct TALER_EXCHANGE_KycStatus *ks,
- struct TALER_EXCHANGE_AccountKycStatus *status)
-{
- const json_t *limits = NULL;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_bool ("aml_review",
- &status->aml_review),
- GNUNET_JSON_spec_uint64 ("rule_gen",
- &status->rule_gen),
- GNUNET_JSON_spec_fixed_auto ("access_token",
- &status->access_token),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_array_const ("limits",
- &limits),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if ( (NULL != limits) &&
- (0 != json_array_size (limits)) )
- {
- size_t limit_length = json_array_size (limits);
- struct TALER_EXCHANGE_AccountLimit ala[GNUNET_NZL (limit_length)];
- size_t i;
- json_t *limit;
-
- json_array_foreach (limits, i, limit)
- {
- struct TALER_EXCHANGE_AccountLimit *al = &ala[i];
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_bool ("soft_limit",
- &al->soft_limit),
- NULL),
- GNUNET_JSON_spec_relative_time ("timeframe",
- &al->timeframe),
- TALER_JSON_spec_kycte ("operation_type",
- &al->operation_type),
- TALER_JSON_spec_amount_any ("threshold",
- &al->threshold),
- GNUNET_JSON_spec_end ()
- };
-
- al->soft_limit = false;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (limit,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- status->limits = ala;
- status->limits_length = limit_length;
- kch->cb (kch->cb_cls,
- ks);
- }
- else
- {
- kch->cb (kch->cb_cls,
- ks);
- }
- GNUNET_JSON_parse_free (spec);
- return GNUNET_OK;
-}
-
-
-/**
- * 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 = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
-
- kch->job = NULL;
- switch (response_code)
- {
- case 0:
- ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- if (GNUNET_OK !=
- parse_account_status (kch,
- j,
- &ks,
- &ks.details.ok))
- {
- GNUNET_break_op (0);
- ks.hr.http_status = 0;
- ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
- TALER_EXCHANGE_kyc_check_cancel (kch);
- return;
- }
- case MHD_HTTP_ACCEPTED:
- {
- if (GNUNET_OK !=
- parse_account_status (kch,
- j,
- &ks,
- &ks.details.accepted))
- {
- GNUNET_break_op (0);
- ks.hr.http_status = 0;
- ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
- TALER_EXCHANGE_kyc_check_cancel (kch);
- return;
- }
- case MHD_HTTP_NO_CONTENT:
- break;
- case MHD_HTTP_BAD_REQUEST:
- ks.hr.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:
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto (
- "expected_account_pub",
- &ks.details.forbidden.expected_account_pub),
- TALER_JSON_spec_ec ("code",
- &ks.hr.ec),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- ks.hr.http_status = 0;
- ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
- break;
- }
- case MHD_HTTP_NOT_FOUND:
- ks.hr.ec = TALER_JSON_get_error_code (j);
- break;
- case MHD_HTTP_CONFLICT:
- ks.hr.ec = TALER_JSON_get_error_code (j);
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- ks.hr.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.hr.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.hr.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,
- const struct TALER_NormalizedPaytoHashP *h_payto,
- const union TALER_AccountPrivateKeyP *account_priv,
- uint64_t known_rule_gen,
- enum TALER_EXCHANGE_KycLongPollTarget lpt,
- struct GNUNET_TIME_Relative timeout,
- TALER_EXCHANGE_KycStatusCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_KycCheckHandle *kch;
- CURL *eh;
- char arg_str[128];
- char timeout_ms[32];
- char lpt_str[32];
- char krg_str[32];
- struct curl_slist *job_headers = NULL;
- unsigned long long tms;
-
- {
- char *hps;
-
- hps = GNUNET_STRINGS_data_to_string_alloc (
- h_payto,
- sizeof (*h_payto));
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "kyc-check/%s",
- hps);
- GNUNET_free (hps);
- }
- tms = timeout.rel_value_us
- / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
- GNUNET_snprintf (timeout_ms,
- sizeof (timeout_ms),
- "%llu",
- tms);
- GNUNET_snprintf (krg_str,
- sizeof (krg_str),
- "%llu",
- (unsigned long long) known_rule_gen);
- GNUNET_snprintf (lpt_str,
- sizeof (lpt_str),
- "%d",
- (int) lpt);
- kch = GNUNET_new (struct TALER_EXCHANGE_KycCheckHandle);
- kch->cb = cb;
- kch->cb_cls = cb_cls;
- kch->url
- = TALER_url_join (
- url,
- arg_str,
- "timeout_ms",
- GNUNET_TIME_relative_is_zero (timeout)
- ? NULL
- : timeout_ms,
- "min_rule",
- 0 == known_rule_gen
- ? NULL
- : krg_str,
- "lpt",
- TALER_EXCHANGE_KLPT_NONE == lpt
- ? NULL
- : lpt_str,
- NULL);
- 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;
- }
- if (0 != tms)
- {
- GNUNET_break (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_TIMEOUT_MS,
- (long) (tms + 500L)));
- }
- job_headers
- = curl_slist_append (
- job_headers,
- "Content-Type: application/json");
- {
- union TALER_AccountPublicKeyP account_pub;
- union TALER_AccountSignatureP account_sig;
- char *sig_hdr;
- char *pub_hdr;
- char *hdr;
-
- GNUNET_CRYPTO_eddsa_key_get_public (
- &account_priv->reserve_priv.eddsa_priv,
- &account_pub.reserve_pub.eddsa_pub);
- TALER_account_kyc_auth_sign (account_priv,
- &account_sig);
-
- sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
- &account_sig,
- sizeof (account_sig));
- GNUNET_asprintf (&hdr,
- "%s: %s",
- TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE,
- sig_hdr);
- GNUNET_free (sig_hdr);
- job_headers = curl_slist_append (job_headers,
- hdr);
- GNUNET_free (hdr);
-
- pub_hdr = GNUNET_STRINGS_data_to_string_alloc (
- &account_pub,
- sizeof (account_pub));
- GNUNET_asprintf (&hdr,
- "%s: %s",
- TALER_HTTP_HEADER_ACCOUNT_OWNER_PUBKEY,
- pub_hdr);
- GNUNET_free (pub_hdr);
- job_headers = curl_slist_append (job_headers,
- hdr);
- GNUNET_free (hdr);
- if (NULL == job_headers)
- {
- GNUNET_break (0);
- curl_easy_cleanup (eh);
- return NULL;
- }
- }
- kch->job
- = GNUNET_CURL_job_add2 (ctx,
- eh,
- job_headers,
- &handle_kyc_check_finished,
- kch);
- curl_slist_free_all (job_headers);
- 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;
- }
- GNUNET_free (kch->url);
- GNUNET_free (kch);
-}
-
-
-/* end of exchange_api_kyc_check.c */
diff --git a/src/lib/exchange_api_kyc_info.c b/src/lib/exchange_api_kyc_info.c
@@ -1,392 +0,0 @@
-/*
- 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_kyc_info.c
- * @brief Implementation of the /kyc-info/$AT request
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A /kyc-info Handle
- */
-struct TALER_EXCHANGE_KycInfoHandle
-{
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_KycInfoCallback kyc_info_cb;
-
- /**
- * Closure for @e cb.
- */
- void *kyc_info_cb_cls;
-
-};
-
-
-/**
- * Parse the provided kyc_infoage data from the "200 OK" response
- * for one of the coins.
- *
- * @param[in,out] lh kyc_info 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_kyc_info_ok (struct TALER_EXCHANGE_KycInfoHandle *lh,
- const json_t *json)
-{
- const json_t *jrequirements = NULL;
- const json_t *jvoluntary_checks = NULL;
- struct TALER_EXCHANGE_KycProcessClientInformation lr = {
- .hr.reply = json,
- .hr.http_status = MHD_HTTP_OK
- };
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_array_const ("requirements",
- &jrequirements),
- GNUNET_JSON_spec_bool ("is_and_combinator",
- &lr.details.ok.is_and_combinator),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_const ("voluntary_checks",
- &jvoluntary_checks),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- lr.details.ok.vci_length
- = (unsigned int) json_object_size (jvoluntary_checks);
- if ( ((size_t) lr.details.ok.vci_length)
- != json_object_size (jvoluntary_checks))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- lr.details.ok.requirements_length
- = json_array_size (jrequirements);
- if ( ((size_t) lr.details.ok.requirements_length)
- != json_array_size (jrequirements))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- {
- struct TALER_EXCHANGE_VoluntaryCheckInformation vci[
- GNUNET_NZL (lr.details.ok.vci_length)];
- struct TALER_EXCHANGE_RequirementInformation requirements[
- GNUNET_NZL (lr.details.ok.requirements_length)];
- const char *name;
- const json_t *jreq;
- const json_t *jvc;
- size_t off;
-
- memset (vci,
- 0,
- sizeof (vci));
- memset (requirements,
- 0,
- sizeof (requirements));
-
- json_array_foreach ((json_t *) jrequirements, off, jreq)
- {
- struct TALER_EXCHANGE_RequirementInformation *req = &requirements[off];
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_string ("form",
- &req->form),
- GNUNET_JSON_spec_string ("description",
- &req->description),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_const ("description_i18n",
- &req->description_i18n),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("id",
- &req->id),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (jreq,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- GNUNET_assert (off == lr.details.ok.requirements_length);
-
- off = 0;
- json_object_foreach ((json_t *) jvoluntary_checks, name, jvc)
- {
- struct TALER_EXCHANGE_VoluntaryCheckInformation *vc = &vci[off++];
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_string ("description",
- &vc->description),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_const ("description_i18n",
- &vc->description_i18n),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- vc->name = name;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (jvc,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- GNUNET_assert (off == lr.details.ok.vci_length);
-
- lr.details.ok.vci = vci;
- lr.details.ok.requirements = requirements;
- lh->kyc_info_cb (lh->kyc_info_cb_cls,
- &lr);
- lh->kyc_info_cb = NULL;
- return GNUNET_OK;
- }
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /kyc-info/$AT request.
- *
- * @param cls the `struct TALER_EXCHANGE_KycInfoHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_kyc_info_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_KycInfoHandle *lh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_KycProcessClientInformation 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_kyc_info_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->kyc_info_cb);
- TALER_EXCHANGE_kyc_info_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);
- 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 /kyc-info\n",
- (unsigned int) response_code,
- (int) lr.hr.ec);
- break;
- }
- if (NULL != lh->kyc_info_cb)
- lh->kyc_info_cb (lh->kyc_info_cb_cls,
- &lr);
- TALER_EXCHANGE_kyc_info_cancel (lh);
-}
-
-
-struct TALER_EXCHANGE_KycInfoHandle *
-TALER_EXCHANGE_kyc_info (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- const struct TALER_AccountAccessTokenP *token,
- const char *if_none_match,
- struct GNUNET_TIME_Relative timeout,
- TALER_EXCHANGE_KycInfoCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_KycInfoHandle *lh;
- CURL *eh;
- char arg_str[sizeof (struct TALER_AccountAccessTokenP) * 2 + 32];
- unsigned int tms
- = (unsigned int) timeout.rel_value_us
- / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
- struct curl_slist *job_headers = NULL;
-
- {
- char at_str[sizeof (struct TALER_AccountAccessTokenP) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- token,
- sizeof (*token),
- at_str,
- sizeof (at_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "kyc-info/%s",
- at_str);
- }
- lh = GNUNET_new (struct TALER_EXCHANGE_KycInfoHandle);
- lh->kyc_info_cb = cb;
- lh->kyc_info_cb_cls = cb_cls;
- {
- char timeout_str[32];
-
- GNUNET_snprintf (timeout_str,
- sizeof (timeout_str),
- "%u",
- tms);
- lh->url = TALER_url_join (url,
- arg_str,
- "timeout_ms",
- (0 == tms)
- ? NULL
- : timeout_str,
- 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;
- }
- if (0 != tms)
- {
- GNUNET_break (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_TIMEOUT_MS,
- (long) (tms + 100L)));
- }
-
- job_headers = curl_slist_append (job_headers,
- "Content-Type: application/json");
- if (NULL != if_none_match)
- {
- char *hdr;
-
- GNUNET_asprintf (&hdr,
- "%s: %s",
- MHD_HTTP_HEADER_IF_NONE_MATCH,
- if_none_match);
- job_headers = curl_slist_append (job_headers,
- hdr);
- GNUNET_free (hdr);
- }
- lh->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- job_headers,
- &handle_kyc_info_finished,
- lh);
- curl_slist_free_all (job_headers);
- return lh;
-}
-
-
-void
-TALER_EXCHANGE_kyc_info_cancel (struct TALER_EXCHANGE_KycInfoHandle *lh)
-{
- if (NULL != lh->job)
- {
- GNUNET_CURL_job_cancel (lh->job);
- lh->job = NULL;
- }
-
- GNUNET_free (lh->url);
- GNUNET_free (lh);
-}
-
-
-/* end of exchange_api_kyc_info.c */
diff --git a/src/lib/exchange_api_kyc_proof.c b/src/lib/exchange_api_kyc_proof.c
@@ -1,217 +0,0 @@
-/*
- 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 "taler/platform.h"
-#include <microhttpd.h> /* just for HTTP proof codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler/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 = {
- .hr.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_NormalizedPaytoHashP *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 (*h_payto) * 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_start.c b/src/lib/exchange_api_kyc_start.c
@@ -1,220 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2024 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_start.c
- * @brief functions to start a KYC process
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include <microhttpd.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_curl_defaults.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_curl_lib.h"
-#include "taler/taler_json_lib.h"
-
-
-struct TALER_EXCHANGE_KycStartHandle
-{
-
- /**
- * 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_KycStartCallback 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 /kyc-start/$ID request.
- *
- * @param cls the `struct TALER_EXCHANGE_KycStartHandle *`
- * @param response_code HTTP response code, 0 on error
- * @param response response body, NULL if not in JSON
- */
-static void
-handle_kyc_start_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_KycStartHandle *wh = cls;
- const json_t *json = response;
- struct TALER_EXCHANGE_KycStartResponse adr = {
- .hr.http_status = (unsigned int) response_code,
- .hr.reply = json
- };
-
- wh->job = NULL;
- switch (response_code)
- {
- case 0:
- /* no reply */
- adr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- adr.hr.hint = "server offline?";
- break;
- case MHD_HTTP_OK:
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string (
- "redirect_url",
- &adr.details.ok.redirect_url),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- adr.hr.http_status = 0;
- adr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- }
- break;
- case MHD_HTTP_NOT_FOUND:
- 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 AML decision\n",
- (unsigned int) response_code,
- (int) adr.hr.ec);
- break;
- }
- if (NULL != wh->cb)
- {
- wh->cb (wh->cb_cls,
- &adr);
- wh->cb = NULL;
- }
- TALER_EXCHANGE_kyc_start_cancel (wh);
-}
-
-
-struct TALER_EXCHANGE_KycStartHandle *
-TALER_EXCHANGE_kyc_start (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- const char *id,
- TALER_EXCHANGE_KycStartCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_KycStartHandle *wh;
- CURL *eh;
- json_t *body;
-
- wh = GNUNET_new (struct TALER_EXCHANGE_KycStartHandle);
- wh->cb = cb;
- wh->cb_cls = cb_cls;
- wh->ctx = ctx;
- {
- char *path;
-
- GNUNET_asprintf (&path,
- "kyc-start/%s",
- id);
- wh->url = TALER_url_join (url,
- path,
- NULL);
- GNUNET_free (path);
- }
- if (NULL == wh->url)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not construct request URL.\n");
- GNUNET_free (wh);
- return NULL;
- }
- body = json_object (); /* as per spec: empty! */
- GNUNET_assert (NULL != body);
- 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_kyc_start_finished,
- wh);
- if (NULL == wh->job)
- {
- TALER_EXCHANGE_kyc_start_cancel (wh);
- return NULL;
- }
- return wh;
-}
-
-
-void
-TALER_EXCHANGE_kyc_start_cancel (
- struct TALER_EXCHANGE_KycStartHandle *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_kyc_wallet.c b/src/lib/exchange_api_kyc_wallet.c
@@ -1,257 +0,0 @@
-/*
- 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 "taler/platform.h"
-#include <microhttpd.h> /* just for HTTP wallet codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler/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 = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
-
- kwh->job = NULL;
- switch (response_code)
- {
- case 0:
- ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount_any (
- "next_threshold",
- &ks.details.ok.next_threshold),
- NULL),
- GNUNET_JSON_spec_timestamp (
- "expiration_time",
- &ks.details.ok.expiration_time),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- ks.hr.http_status = 0;
- ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
- break;
- }
- case MHD_HTTP_NO_CONTENT:
- break;
- case MHD_HTTP_BAD_REQUEST:
- ks.hr.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.hr.ec = TALER_JSON_get_error_code (j);
- break;
- case MHD_HTTP_NOT_FOUND:
- ks.hr.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.hr.http_status = 0;
- ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
- break;
- }
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- ks.hr.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.hr.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.hr.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_decisions.c b/src/lib/exchange_api_lookup_aml_decisions.c
@@ -1,626 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2023, 2024 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 "taler/platform.h"
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A GET /aml/$OFFICER_PUB/decisions 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;
-
- /**
- * Array with measure information.
- */
- struct TALER_EXCHANGE_MeasureInformation *mip;
-
- /**
- * Array with rule information.
- */
- struct TALER_EXCHANGE_KycRule *rp;
-
- /**
- * Array with all the measures (of all the rules!).
- */
- const char **mp;
-};
-
-
-/**
- * Parse AML limits array.
- *
- * @param[in,out] lh handle to use for allocations
- * @param jlimits JSON array with AML rules
- * @param[out] ds where to write the result
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_limits (struct TALER_EXCHANGE_LookupAmlDecisions *lh,
- const json_t *jlimits,
- struct TALER_EXCHANGE_AmlDecision *ds)
-{
- struct TALER_EXCHANGE_LegitimizationRuleSet *limits
- = &ds->limits;
- const json_t *jrules;
- const json_t *jmeasures;
- size_t mip_len;
- size_t rule_len;
- size_t total;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_timestamp ("expiration_time",
- &limits->expiration_time),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("successor_measure",
- &limits->successor_measure),
- NULL),
- GNUNET_JSON_spec_array_const ("rules",
- &jrules),
- GNUNET_JSON_spec_object_const ("custom_measures",
- &jmeasures),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (jlimits,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- mip_len = json_object_size (jmeasures);
- lh->mip = GNUNET_new_array (mip_len,
- struct TALER_EXCHANGE_MeasureInformation);
- limits->measures = lh->mip;
- limits->measures_length = mip_len;
-
- {
- const char *measure_name;
- const json_t *jmeasure;
-
- json_object_foreach ((json_t*) jmeasures,
- measure_name,
- jmeasure)
- {
- struct TALER_EXCHANGE_MeasureInformation *mi
- = &lh->mip[--mip_len];
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_string ("check_name",
- &mi->check_name),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("prog_name",
- &mi->prog_name),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_const ("context",
- &mi->context),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (jmeasure,
- ispec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- mi->measure_name = measure_name;
- }
- }
-
- total = 0;
-
- {
- const json_t *rule;
- size_t idx;
-
- json_array_foreach ((json_t *) jrules,
- idx,
- rule)
- {
- total += json_array_size (json_object_get (rule,
- "measures"));
- }
- }
-
- rule_len = json_array_size (jrules);
- lh->rp = GNUNET_new_array (rule_len,
- struct TALER_EXCHANGE_KycRule);
- lh->mp = GNUNET_new_array (total,
- const char *);
-
- {
- const json_t *rule;
- size_t idx;
-
- json_array_foreach ((json_t *) jrules,
- idx,
- rule)
- {
- const json_t *smeasures;
- struct TALER_EXCHANGE_KycRule *r
- = &lh->rp[--rule_len];
- struct GNUNET_JSON_Specification ispec[] = {
- TALER_JSON_spec_kycte ("operation_type",
- &r->operation_type),
- TALER_JSON_spec_amount_any ("threshold",
- &r->threshold),
- GNUNET_JSON_spec_relative_time ("timeframe",
- &r->timeframe),
- GNUNET_JSON_spec_array_const ("measures",
- &smeasures),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_bool ("exposed",
- &r->exposed),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_bool ("is_and_combinator",
- &r->is_and_combinator),
- NULL),
- GNUNET_JSON_spec_uint32 ("display_priority",
- &r->display_priority),
- GNUNET_JSON_spec_end ()
- };
- size_t mlen;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (rule,
- ispec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- mlen = json_array_size (smeasures);
- GNUNET_assert (mlen <= total);
- total -= mlen;
-
- {
- size_t midx;
- const json_t *smeasure;
-
- json_array_foreach (smeasures,
- midx,
- smeasure)
- {
- const char *sval = json_string_value (smeasure);
-
- if (NULL == sval)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- lh->mp[total + midx] = sval;
- if (0 == strcasecmp (sval,
- "verboten"))
- r->verboten = true;
- }
- }
- r->measures = &lh->mp[total];
- r->measures_length = r->verboten ? 0 : total;
- }
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Parse AML decision summary array.
- *
- * @param[in,out] lh handle to use for allocations
- * @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 (
- struct TALER_EXCHANGE_LookupAmlDecisions *lh,
- const json_t *decisions,
- struct TALER_EXCHANGE_AmlDecision *decision_ar)
-{
- json_t *obj;
- size_t idx;
-
- json_array_foreach (decisions, idx, obj)
- {
- struct TALER_EXCHANGE_AmlDecision *decision = &decision_ar[idx];
- const json_t *jlimits;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("h_payto",
- &decision->h_payto),
- GNUNET_JSON_spec_uint64 ("rowid",
- &decision->rowid),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("justification",
- &decision->justification),
- NULL),
- GNUNET_JSON_spec_timestamp ("decision_time",
- &decision->decision_time),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_const ("properties",
- &decision->jproperties),
- NULL),
- GNUNET_JSON_spec_object_const ("limits",
- &jlimits),
- GNUNET_JSON_spec_bool ("to_investigate",
- &decision->to_investigate),
- GNUNET_JSON_spec_bool ("is_active",
- &decision->is_active),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (obj,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- parse_limits (lh,
- jlimits,
- decision))
- {
- 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_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_AmlDecision decisions[
- GNUNET_NZL (lr.details.ok.decisions_length)];
- enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
-
- memset (decisions,
- 0,
- sizeof (decisions));
- lr.details.ok.decisions = decisions;
- ret = parse_aml_decisions (lh,
- records,
- decisions);
- if (GNUNET_OK == ret)
- {
- lh->decisions_cb (lh->decisions_cb_cls,
- &lr);
- lh->decisions_cb = NULL;
- }
- GNUNET_free (lh->mip);
- GNUNET_free (lh->rp);
- GNUNET_free (lh->mp);
- 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:
- json_dumpf (j,
- stderr,
- JSON_INDENT (2));
- 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,
- const struct TALER_NormalizedPaytoHashP *h_payto,
- enum TALER_EXCHANGE_YesNoAll investigation_only,
- enum TALER_EXCHANGE_YesNoAll active_only,
- uint64_t offset,
- int64_t limit,
- 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];
-
- 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",
- pub_str);
- }
- lh = GNUNET_new (struct TALER_EXCHANGE_LookupAmlDecisions);
- lh->decisions_cb = cb;
- lh->decisions_cb_cls = cb_cls;
- {
- char limit_s[24];
- char offset_s[24];
- char payto_s[sizeof (*h_payto) * 2];
- char *end;
-
- if (NULL != h_payto)
- {
- end = GNUNET_STRINGS_data_to_string (
- h_payto,
- sizeof (*h_payto),
- payto_s,
- sizeof (payto_s));
- *end = '\0';
- }
- GNUNET_snprintf (limit_s,
- sizeof (limit_s),
- "%lld",
- (long long) limit);
- GNUNET_snprintf (offset_s,
- sizeof (offset_s),
- "%llu",
- (unsigned long long) offset);
- lh->url = TALER_url_join (
- exchange_url,
- arg_str,
- "limit",
- limit_s,
- "offset",
- ( ( (limit < 0) && (UINT64_MAX == offset) ) ||
- ( (limit > 0) && (0 == offset) ) )
- ? NULL
- : offset_s,
- "h_payto",
- NULL != h_payto
- ? payto_s
- : NULL,
- "active",
- TALER_EXCHANGE_YNA_ALL != active_only
- ? TALER_yna_to_string (active_only)
- : NULL,
- "investigation",
- TALER_EXCHANGE_YNA_ALL != investigation_only
- ? TALER_yna_to_string (investigation_only)
- : 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_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_lookup_kyc_attributes.c b/src/lib/exchange_api_lookup_kyc_attributes.c
@@ -1,386 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2023, 2024 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_kyc_attributes.c
- * @brief Implementation of the /aml/$OFFICER_PUB/attributes request
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A GET /aml/$OFFICER_PUB/attributes Handle
- */
-struct TALER_EXCHANGE_LookupKycAttributes
-{
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_LookupKycAttributesCallback attributes_cb;
-
- /**
- * Closure for @e cb.
- */
- void *attributes_cb_cls;
-
- /**
- * HTTP headers for the job.
- */
- struct curl_slist *job_headers;
-
-};
-
-
-/**
- * Parse AML decision summary array.
- *
- * @param[in,out] lh handle to use for allocations
- * @param jdetails JSON array with AML decision summaries
- * @param[out] detail_ar where to write the result
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_kyc_attributes (
- struct TALER_EXCHANGE_LookupKycAttributes *lh,
- const json_t *jdetails,
- struct TALER_EXCHANGE_KycAttributeDetail *detail_ar)
-{
- json_t *obj;
- size_t idx;
-
- json_array_foreach (jdetails, idx, obj)
- {
- struct TALER_EXCHANGE_KycAttributeDetail *detail
- = &detail_ar[idx];
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint64 ("rowid",
- &detail->row_id),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("provider_name",
- &detail->provider_name),
- NULL),
- GNUNET_JSON_spec_object_const ("attributes",
- &detail->attributes),
- GNUNET_JSON_spec_timestamp ("collection_time",
- &detail->collection_time),
- 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_attributes_ok (struct TALER_EXCHANGE_LookupKycAttributes *lh,
- const json_t *json)
-{
- struct TALER_EXCHANGE_KycAttributesResponse lr = {
- .hr.reply = json,
- .hr.http_status = MHD_HTTP_OK
- };
- const json_t *jdetails;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_array_const ("details",
- &jdetails),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- lr.details.ok.kyc_attributes_length
- = json_array_size (jdetails);
- {
- struct TALER_EXCHANGE_KycAttributeDetail details[
- GNUNET_NZL (lr.details.ok.kyc_attributes_length)];
- enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
-
- memset (details,
- 0,
- sizeof (details));
- lr.details.ok.kyc_attributes = details;
- ret = parse_kyc_attributes (lh,
- jdetails,
- details);
- if (GNUNET_OK == ret)
- {
- lh->attributes_cb (lh->attributes_cb_cls,
- &lr);
- lh->attributes_cb = NULL;
- }
- return ret;
- }
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /aml/$OFFICER_PUB/attributes request.
- *
- * @param cls the `struct TALER_EXCHANGE_LookupKycAttributes`
- * @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_LookupKycAttributes *lh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_KycAttributesResponse 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_attributes_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->attributes_cb);
- TALER_EXCHANGE_lookup_kyc_attributes_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 attributes\n",
- (unsigned int) response_code,
- (int) lr.hr.ec);
- break;
- }
- if (NULL != lh->attributes_cb)
- lh->attributes_cb (lh->attributes_cb_cls,
- &lr);
- TALER_EXCHANGE_lookup_kyc_attributes_cancel (lh);
-}
-
-
-struct TALER_EXCHANGE_LookupKycAttributes *
-TALER_EXCHANGE_lookup_kyc_attributes (
- struct GNUNET_CURL_Context *ctx,
- const char *exchange_url,
- const struct TALER_NormalizedPaytoHashP *h_payto,
- uint64_t offset,
- int64_t limit,
- const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
- TALER_EXCHANGE_LookupKycAttributesCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_LookupKycAttributes *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 payto_s[sizeof (*h_payto) * 2];
- char pub_str[sizeof (officer_pub) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- h_payto,
- sizeof (*h_payto),
- payto_s,
- sizeof (payto_s));
- *end = '\0';
- 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/attributes/%s",
- pub_str,
- payto_s);
- }
- lh = GNUNET_new (struct TALER_EXCHANGE_LookupKycAttributes);
- lh->attributes_cb = cb;
- lh->attributes_cb_cls = cb_cls;
- {
- char limit_s[24];
- char offset_s[24];
-
- GNUNET_snprintf (limit_s,
- sizeof (limit_s),
- "%lld",
- (long long) limit);
- GNUNET_snprintf (offset_s,
- sizeof (offset_s),
- "%llu",
- (unsigned long long) offset);
- lh->url = TALER_url_join (
- exchange_url,
- arg_str,
- "limit",
- limit_s,
- "offset",
- offset_s,
- "h_payto",
- 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_kyc_attributes_cancel (
- struct TALER_EXCHANGE_LookupKycAttributes *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_kyc_attributes.c */
diff --git a/src/lib/exchange_api_management_add_partner.c b/src/lib/exchange_api_management_add_partner.c
@@ -1,219 +0,0 @@
-/*
- 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 "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include <microhttpd.h>
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_curl_defaults.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_curl_lib.h"
-#include "taler/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
@@ -1,220 +0,0 @@
-/*
- 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 "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include <microhttpd.h>
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_curl_defaults.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_curl_lib.h"
-#include "taler/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
@@ -1,227 +0,0 @@
-/*
- 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 "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include <microhttpd.h>
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_curl_defaults.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_curl_lib.h"
-#include "taler/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 auditor_enable_response
- = {
- .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:
- auditor_enable_response.hr.ec = TALER_JSON_get_error_code (json);
- auditor_enable_response.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)
- {
- auditor_enable_response.hr.ec = TALER_JSON_get_error_code (json);
- auditor_enable_response.hr.hint = TALER_JSON_get_error_hint (json);
- }
- else
- {
- auditor_enable_response.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- auditor_enable_response.hr.hint = TALER_ErrorCode_get_hint (
- auditor_enable_response.hr.ec);
- }
- break;
- case MHD_HTTP_CONFLICT:
- auditor_enable_response.hr.ec = TALER_JSON_get_error_code (json);
- auditor_enable_response.hr.hint = TALER_JSON_get_error_hint (json);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- auditor_enable_response.hr.ec = TALER_JSON_get_error_code (json);
- auditor_enable_response.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) auditor_enable_response.hr.ec);
- break;
- }
- if (NULL != ah->cb)
- {
- ah->cb (ah->cb_cls,
- &auditor_enable_response);
- 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
@@ -1,214 +0,0 @@
-/*
- 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 "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include <microhttpd.h>
-#include "exchange_api_curl_defaults.h"
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_curl_lib.h"
-#include "taler/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 struct TALER_FullPayto 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),
- TALER_JSON_pack_full_payto ("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_get_keys.c b/src/lib/exchange_api_management_get_keys.c
@@ -1,427 +0,0 @@
-/*
- 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_get_keys.c
- * @brief functions to obtain future online keys of the exchange
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include <microhttpd.h>
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_curl_defaults.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_curl_lib.h"
-#include "taler/taler_util.h"
-#include "taler/taler_json_lib.h"
-
-/**
- * Set to 1 for extra debug logging.
- */
-#define DEBUG 0
-
-
-/**
- * @brief Handle for a GET /management/keys request.
- */
-struct TALER_EXCHANGE_GetManagementKeysHandle
-{
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_ManagementGetKeysCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Reference to the execution context.
- */
- struct GNUNET_CURL_Context *ctx;
-};
-
-
-/**
- * Handle the case that the response was of type #MHD_HTTP_OK.
- *
- * @param[in,out] gh request handle
- * @param response the response
- * @return #GNUNET_OK if the response was well-formed
- */
-static enum GNUNET_GenericReturnValue
-handle_ok (struct TALER_EXCHANGE_GetManagementKeysHandle *gh,
- const json_t *response)
-{
- struct TALER_EXCHANGE_ManagementGetKeysResponse gkr = {
- .hr.http_status = MHD_HTTP_OK,
- .hr.reply = response,
- };
- struct TALER_EXCHANGE_FutureKeys *fk
- = &gkr.details.ok.keys;
- const json_t *sk;
- const json_t *dk;
- bool ok;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_array_const ("future_denoms",
- &dk),
- GNUNET_JSON_spec_array_const ("future_signkeys",
- &sk),
- GNUNET_JSON_spec_fixed_auto ("master_pub",
- &fk->master_pub),
- GNUNET_JSON_spec_fixed_auto ("denom_secmod_public_key",
- &fk->denom_secmod_public_key),
- GNUNET_JSON_spec_fixed_auto ("denom_secmod_cs_public_key",
- &fk->denom_secmod_cs_public_key),
- GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key",
- &fk->signkey_secmod_public_key),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (response,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- fk->num_sign_keys = json_array_size (sk);
- fk->num_denom_keys = json_array_size (dk);
- fk->sign_keys = GNUNET_new_array (
- fk->num_sign_keys,
- struct TALER_EXCHANGE_FutureSigningPublicKey);
- fk->denom_keys = GNUNET_new_array (
- fk->num_denom_keys,
- struct TALER_EXCHANGE_FutureDenomPublicKey);
- ok = true;
- for (unsigned int i = 0; i<fk->num_sign_keys; i++)
- {
- json_t *j = json_array_get (sk,
- i);
- struct TALER_EXCHANGE_FutureSigningPublicKey *sign_key
- = &fk->sign_keys[i];
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_fixed_auto ("key",
- &sign_key->key),
- GNUNET_JSON_spec_fixed_auto ("signkey_secmod_sig",
- &sign_key->signkey_secmod_sig),
- GNUNET_JSON_spec_timestamp ("stamp_start",
- &sign_key->valid_from),
- GNUNET_JSON_spec_timestamp ("stamp_expire",
- &sign_key->valid_until),
- GNUNET_JSON_spec_timestamp ("stamp_end",
- &sign_key->valid_legal),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- ok = false;
- break;
- }
- {
- struct GNUNET_TIME_Relative duration
- = GNUNET_TIME_absolute_get_difference (sign_key->valid_from.abs_time,
- sign_key->valid_until.abs_time);
-
- if (GNUNET_OK !=
- TALER_exchange_secmod_eddsa_verify (
- &sign_key->key,
- sign_key->valid_from,
- duration,
- &fk->signkey_secmod_public_key,
- &sign_key->signkey_secmod_sig))
- {
- GNUNET_break_op (0);
- ok = false;
- break;
- }
- }
- }
- for (unsigned int i = 0; i<fk->num_denom_keys; i++)
- {
- json_t *j = json_array_get (dk,
- i);
- struct TALER_EXCHANGE_FutureDenomPublicKey *denom_key
- = &fk->denom_keys[i];
- const char *section_name;
- struct GNUNET_JSON_Specification ispec[] = {
- TALER_JSON_spec_amount_any ("value",
- &denom_key->value),
- GNUNET_JSON_spec_timestamp ("stamp_start",
- &denom_key->valid_from),
- GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
- &denom_key->withdraw_valid_until),
- GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
- &denom_key->expire_deposit),
- GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
- &denom_key->expire_legal),
- TALER_JSON_spec_denom_pub ("denom_pub",
- &denom_key->key),
- TALER_JSON_spec_amount_any ("fee_withdraw",
- &denom_key->fee_withdraw),
- TALER_JSON_spec_amount_any ("fee_deposit",
- &denom_key->fee_deposit),
- TALER_JSON_spec_amount_any ("fee_refresh",
- &denom_key->fee_refresh),
- TALER_JSON_spec_amount_any ("fee_refund",
- &denom_key->fee_refund),
- GNUNET_JSON_spec_fixed_auto ("denom_secmod_sig",
- &denom_key->denom_secmod_sig),
- GNUNET_JSON_spec_string ("section_name",
- §ion_name),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
-#if DEBUG
- json_dumpf (j,
- stderr,
- JSON_INDENT (2));
-#endif
- ok = false;
- break;
- }
-
- {
- struct TALER_DenominationHashP h_denom_pub;
- struct GNUNET_TIME_Relative duration
- = GNUNET_TIME_absolute_get_difference (
- denom_key->valid_from.abs_time,
- denom_key->withdraw_valid_until.abs_time);
-
- TALER_denom_pub_hash (&denom_key->key,
- &h_denom_pub);
- switch (denom_key->key.bsign_pub_key->cipher)
- {
- case GNUNET_CRYPTO_BSA_RSA:
- {
- struct TALER_RsaPubHashP h_rsa;
-
- TALER_rsa_pub_hash (
- denom_key->key.bsign_pub_key->details.rsa_public_key,
- &h_rsa);
- if (GNUNET_OK !=
- TALER_exchange_secmod_rsa_verify (&h_rsa,
- section_name,
- denom_key->valid_from,
- duration,
- &fk->denom_secmod_public_key,
- &denom_key->denom_secmod_sig))
- {
- GNUNET_break_op (0);
- ok = false;
- break;
- }
- }
- break;
- case GNUNET_CRYPTO_BSA_CS:
- {
- struct TALER_CsPubHashP h_cs;
-
- TALER_cs_pub_hash (
- &denom_key->key.bsign_pub_key->details.cs_public_key,
- &h_cs);
- if (GNUNET_OK !=
- TALER_exchange_secmod_cs_verify (&h_cs,
- section_name,
- denom_key->valid_from,
- duration,
- &fk->denom_secmod_cs_public_key,
- &denom_key->denom_secmod_sig))
- {
- GNUNET_break_op (0);
- ok = false;
- break;
- }
- }
- break;
- default:
- GNUNET_break_op (0);
- ok = false;
- break;
- }
- }
- if (! ok)
- break;
- }
- if (ok)
- {
- gh->cb (gh->cb_cls,
- &gkr);
- }
- for (unsigned int i = 0; i<fk->num_denom_keys; i++)
- TALER_denom_pub_free (&fk->denom_keys[i].key);
- GNUNET_free (fk->sign_keys);
- GNUNET_free (fk->denom_keys);
- return (ok) ? GNUNET_OK : GNUNET_SYSERR;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP GET /management/keys request.
- *
- * @param cls the `struct TALER_EXCHANGE_GetManagementKeysHandle *`
- * @param response_code HTTP response code, 0 on error
- * @param response response body, NULL if not in JSON
- */
-static void
-handle_get_keys_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_GetManagementKeysHandle *gh = cls;
- const json_t *json = response;
- struct TALER_EXCHANGE_ManagementGetKeysResponse gkr = {
- .hr.http_status = (unsigned int) response_code,
- .hr.reply = json
- };
-
- gh->job = NULL;
- switch (response_code)
- {
- case MHD_HTTP_OK:
- if (GNUNET_OK ==
- handle_ok (gh,
- response))
- {
- gh->cb = NULL;
- }
- else
- {
- response_code = 0;
- }
- 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",
- gh->url);
- if (NULL != json)
- {
- gkr.hr.ec = TALER_JSON_get_error_code (json);
- gkr.hr.hint = TALER_JSON_get_error_hint (json);
- }
- else
- {
- gkr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- gkr.hr.hint = TALER_ErrorCode_get_hint (gkr.hr.ec);
- }
- break;
- default:
- /* unexpected response code */
- if (NULL != json)
- {
- gkr.hr.ec = TALER_JSON_get_error_code (json);
- gkr.hr.hint = TALER_JSON_get_error_hint (json);
- }
- else
- {
- gkr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- gkr.hr.hint = TALER_ErrorCode_get_hint (gkr.hr.ec);
- }
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange management get keys\n",
- (unsigned int) response_code,
- (int) gkr.hr.ec);
- break;
- }
- if (NULL != gh->cb)
- {
- gh->cb (gh->cb_cls,
- &gkr);
- gh->cb = NULL;
- }
- TALER_EXCHANGE_get_management_keys_cancel (gh);
-};
-
-
-struct TALER_EXCHANGE_GetManagementKeysHandle *
-TALER_EXCHANGE_get_management_keys (struct GNUNET_CURL_Context *ctx,
- const char *url,
- TALER_EXCHANGE_ManagementGetKeysCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_GetManagementKeysHandle *gh;
- CURL *eh;
-
- gh = GNUNET_new (struct TALER_EXCHANGE_GetManagementKeysHandle);
- gh->cb = cb;
- gh->cb_cls = cb_cls;
- gh->ctx = ctx;
- gh->url = TALER_url_join (url,
- "management/keys",
- NULL);
- if (NULL == gh->url)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Could not construct request URL.\n");
- GNUNET_free (gh);
- return NULL;
- }
- eh = TALER_EXCHANGE_curl_easy_get_ (gh->url);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Requesting URL '%s'\n",
- gh->url);
- gh->job = GNUNET_CURL_job_add (ctx,
- eh,
- &handle_get_keys_finished,
- gh);
- if (NULL == gh->job)
- {
- TALER_EXCHANGE_get_management_keys_cancel (gh);
- return NULL;
- }
- return gh;
-}
-
-
-void
-TALER_EXCHANGE_get_management_keys_cancel (
- struct TALER_EXCHANGE_GetManagementKeysHandle *gh)
-{
- if (NULL != gh->job)
- {
- GNUNET_CURL_job_cancel (gh->job);
- gh->job = NULL;
- }
- GNUNET_free (gh->url);
- GNUNET_free (gh);
-}
diff --git a/src/lib/exchange_api_management_post_extensions.c b/src/lib/exchange_api_management_post_extensions.c
@@ -1,213 +0,0 @@
-/*
- 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 "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_extensions.h"
-#include "exchange_api_curl_defaults.h"
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_curl_lib.h"
-#include "taler/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
@@ -1,238 +0,0 @@
-/*
- 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 "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include <microhttpd.h>
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_curl_defaults.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_curl_lib.h"
-#include "taler/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
@@ -1,223 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2015-2024 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 "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include <microhttpd.h>
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_curl_defaults.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_curl_lib.h"
-#include "taler/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);
- GNUNET_free (rh);
- 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
@@ -1,213 +0,0 @@
-/*
- 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 "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include <microhttpd.h>
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_curl_defaults.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_curl_lib.h"
-#include "taler/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);
- GNUNET_free (rh);
- 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
@@ -1,237 +0,0 @@
-/*
- 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 "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include <microhttpd.h>
-#include "exchange_api_curl_defaults.h"
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_curl_lib.h"
-#include "taler/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);
- GNUNET_free (sgfh);
- 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
@@ -1,229 +0,0 @@
-/*
- 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 "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include <microhttpd.h>
-#include "exchange_api_curl_defaults.h"
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_curl_lib.h"
-#include "taler/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);
- GNUNET_free (swfh);
- 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_wire_disable.c b/src/lib/exchange_api_management_wire_disable.c
@@ -1,222 +0,0 @@
-/*
- 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 "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include <microhttpd.h>
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_curl_defaults.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_curl_lib.h"
-#include "taler/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 struct TALER_FullPayto 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 (
- TALER_JSON_pack_full_payto ("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);
- GNUNET_free (wh);
- 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
@@ -1,254 +0,0 @@
-/*
- 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 "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include <microhttpd.h>
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_curl_defaults.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_curl_lib.h"
-#include "taler/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 struct TALER_FullPayto 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,
- const char *bank_label,
- int64_t priority,
- 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 (
- TALER_JSON_pack_full_payto ("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_allow_null (
- GNUNET_JSON_pack_string ("bank_label",
- bank_label)),
- GNUNET_JSON_pack_int64 ("priority",
- priority),
- 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);
- GNUNET_free (wh);
- 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
@@ -1,645 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2025 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 /melt request
- * @author Özgür Kesim
- */
-#include "taler/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/taler_json_lib.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_common.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-#include "exchange_api_refresh_common.h"
-
-
-/**
- * @brief A /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 seed for the melt operation.
- */
- struct TALER_PublicRefreshMasterSeedP rms;
-
- /**
- * Details about the characteristics of the requested melt operation.
- */
- const struct TALER_EXCHANGE_MeltInput *rd;
-
- /**
- * True, if no blinding_seed is needed (no CS denominations involved)
- */
- bool no_blinding_seed;
-
- /**
- * If @e no_blinding_seed is false, the blinding seed for the intermediate
- * call to /blinding-prepare, in order to retrieve the R-values from the
- * exchange for the blind Clause-Schnorr signature.
- */
- struct TALER_BlindingMasterSeedP blinding_seed;
-
- /**
- * Array of `num_fresh_denom_pubs` per-coin values
- * returned from melt operation.
- */
- struct TALER_ExchangeBlindingValues *melt_blinding_values;
-
- /**
- * Handle for the preflight request, or NULL.
- */
- struct TALER_EXCHANGE_BlindingPrepareHandle *bpr;
-
- /**
- * 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;
-
-};
-
-
-/**
- * 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;
- 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 */
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (mh->keys,
- 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 /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_melt_blinding_values = mh->rd->num_fresh_denom_pubs;
- mr.details.ok.melt_blinding_values = mh->melt_blinding_values;
- mr.details.ok.blinding_seed = mh->no_blinding_seed
- ? NULL
- : &mh->blinding_seed;
- 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)
-{
- json_t *j_request_body;
- json_t *j_transfer_pubs;
- json_t *j_coin_evs;
- CURL *eh;
- struct TALER_DenominationHashP h_denom_pub;
-
- if (GNUNET_OK !=
- TALER_EXCHANGE_get_melt_data (&mh->rms,
- mh->rd,
- mh->no_blinding_seed
- ? NULL
- : &mh->blinding_seed,
- mh->melt_blinding_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);
- mh->dki = TALER_EXCHANGE_get_denomination_key (
- mh->keys,
- &mh->md.melted_coin.pub_key);
- j_request_body = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("old_coin_pub",
- &mh->coin_pub),
- GNUNET_JSON_pack_data_auto ("old_denom_pub_h",
- &h_denom_pub),
- TALER_JSON_pack_denom_sig ("old_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_allow_null (
- (NULL != mh->md.melted_coin.h_age_commitment)
- ? GNUNET_JSON_pack_data_auto ("old_age_commitment_h",
- mh->md.melted_coin.h_age_commitment)
- : GNUNET_JSON_pack_string ("old_age_commitment_h",
- NULL)),
- GNUNET_JSON_pack_data_auto ("refresh_seed",
- &mh->md.refresh_seed),
- GNUNET_JSON_pack_allow_null (
- (mh->md.no_blinding_seed)
- ? GNUNET_JSON_pack_string ("blinding_seed",
- NULL)
- : GNUNET_JSON_pack_data_auto ("blinding_seed",
- &mh->md.blinding_seed)),
- TALER_JSON_pack_array_of_data_auto ("denoms_h",
- mh->md.num_fresh_coins,
- mh->md.denoms_h)
- );
- GNUNET_assert (NULL != j_request_body);
- GNUNET_assert (NULL !=
- (j_transfer_pubs = json_array ()));
- GNUNET_assert (NULL !=
- (j_coin_evs = json_array ()));
- /**
- * Fill the kappa array of coin envelopes and
- * the array of transfer pubs.
- */
- for (uint8_t k=0; k<TALER_CNC_KAPPA; k++)
- {
- json_t *j_envs;
- json_t *j_tbs = GNUNET_JSON_PACK (
- TALER_JSON_pack_array_of_data_auto (NULL,
- mh->md.num_fresh_coins,
- mh->md.kappa_transfer_pubs[k])
- );
-
- GNUNET_assert (NULL != (j_envs = json_array ()));
- GNUNET_assert (NULL !=j_tbs);
-
- for (size_t i = 0; i < mh->md.num_fresh_coins; i++)
- {
- json_t *j_coin = GNUNET_JSON_PACK (
- TALER_JSON_pack_blinded_planchet (NULL,
- &mh->md.kappa_blinded_planchets[k][i]));
- GNUNET_assert (NULL != j_coin);
- GNUNET_assert (0 ==
- json_array_append_new (j_envs, j_coin));
- }
- GNUNET_assert (0 ==
- json_array_append_new (j_coin_evs, j_envs));
- GNUNET_assert (0 ==
- json_array_append_new (j_transfer_pubs, j_tbs));
- }
- GNUNET_assert (0 ==
- json_object_set_new (j_request_body,
- "coin_evs",
- j_coin_evs));
- GNUNET_assert (0 ==
- json_object_set_new (j_request_body,
- "transfer_pubs",
- j_transfer_pubs));
- /* and now we can at last begin the actual request handling */
- mh->url = TALER_url_join (mh->exchange_url,
- "melt",
- NULL);
- if (NULL == mh->url)
- {
- json_decref (j_request_body);
- return GNUNET_SYSERR;
- }
- eh = TALER_EXCHANGE_curl_easy_get_ (mh->url);
- if ( (NULL == eh) ||
- (GNUNET_OK !=
- TALER_curl_easy_post (&mh->ctx,
- eh,
- j_request_body)) )
- {
- GNUNET_break (0);
- if (NULL != eh)
- curl_easy_cleanup (eh);
- json_decref (j_request_body);
- return GNUNET_SYSERR;
- }
- json_decref (j_request_body);
- 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
- * /blinding-prepare request to a exchange.
- *
- * @param cls closure with our `struct TALER_EXCHANGE_MeltHandle *`
- * @param bpr response details
- */
-static void
-blinding_prepare_cb (void *cls,
- const struct TALER_EXCHANGE_BlindingPrepareResponse *bpr)
-{
- struct TALER_EXCHANGE_MeltHandle *mh = cls;
- unsigned int nks_off = 0;
-
- mh->bpr = NULL;
- if (MHD_HTTP_OK != bpr->hr.http_status)
- {
- struct TALER_EXCHANGE_MeltResponse mr = {
- .hr = bpr->hr
- };
-
- mr.hr.hint = "/blinding-prepare failed";
- mh->melt_cb (mh->melt_cb_cls,
- &mr);
- TALER_EXCHANGE_melt_cancel (mh);
- return;
- }
- for (unsigned int i = 0; i<mh->rd->num_fresh_denom_pubs; i++)
- {
- const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk =
- &mh->rd->fresh_denom_pubs[i];
- struct TALER_ExchangeBlindingValues *wv = &mh->melt_blinding_values[i];
-
- switch (fresh_pk->key.bsign_pub_key->cipher)
- {
- case GNUNET_CRYPTO_BSA_INVALID:
- GNUNET_break (0);
- fail_mh (mh,
- TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR);
- return;
- case GNUNET_CRYPTO_BSA_RSA:
- break;
- case GNUNET_CRYPTO_BSA_CS:
- TALER_denom_ewv_copy (wv,
- &bpr->details.ok.blinding_values[nks_off]);
- nks_off++;
- break;
- }
- }
- 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_PublicRefreshMasterSeedP *rms,
- const struct TALER_EXCHANGE_MeltInput *rd,
- TALER_EXCHANGE_MeltCallback melt_cb,
- void *melt_cb_cls)
-{
- struct TALER_EXCHANGE_NonceKey nks[GNUNET_NZL (rd->num_fresh_denom_pubs)];
- unsigned int nks_off = 0;
- struct TALER_EXCHANGE_MeltHandle *mh;
-
- if (0 == rd->num_fresh_denom_pubs)
- {
- 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->no_blinding_seed = true;
- mh->melt_blinding_values =
- GNUNET_new_array (rd->num_fresh_denom_pubs,
- struct TALER_ExchangeBlindingValues);
- for (unsigned int i = 0; i<rd->num_fresh_denom_pubs; i++)
- {
- const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk =
- &rd->fresh_denom_pubs[i];
-
- switch (fresh_pk->key.bsign_pub_key->cipher)
- {
- case GNUNET_CRYPTO_BSA_INVALID:
- GNUNET_break (0);
- GNUNET_free (mh->melt_blinding_values);
- GNUNET_free (mh);
- return NULL;
- case GNUNET_CRYPTO_BSA_RSA:
- TALER_denom_ewv_copy (&mh->melt_blinding_values[i],
- TALER_denom_ewv_rsa_singleton ());
- break;
- case GNUNET_CRYPTO_BSA_CS:
- nks[nks_off].pk = fresh_pk;
- nks[nks_off].cnc_num = i;
- nks_off++;
- break;
- }
- }
- mh->keys = TALER_EXCHANGE_keys_incref (keys);
- if (0 != nks_off)
- {
- mh->no_blinding_seed = false;
- TALER_cs_refresh_seed_to_blinding_seed (
- rms,
- &mh->md.melted_coin.coin_priv,
- &mh->blinding_seed);
- mh->bpr = TALER_EXCHANGE_blinding_prepare_for_melt (ctx,
- url,
- &mh->blinding_seed,
- nks_off,
- nks,
- &blinding_prepare_cb,
- mh);
- if (NULL == mh->bpr)
- {
- 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)
-{
- for (unsigned int i = 0; i<mh->rd->num_fresh_denom_pubs; i++)
- TALER_denom_ewv_free (&mh->melt_blinding_values[i]);
- if (NULL != mh->job)
- {
- GNUNET_CURL_job_cancel (mh->job);
- mh->job = NULL;
- }
- if (NULL != mh->bpr)
- {
- TALER_EXCHANGE_blinding_prepare_cancel (mh->bpr);
- mh->bpr = NULL;
- }
- TALER_EXCHANGE_free_melt_data (&mh->md); /* does not free 'md' itself */
- GNUNET_free (mh->melt_blinding_values);
- 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_post-aml-OFFICER_PUB-decision.c b/src/lib/exchange_api_post-aml-OFFICER_PUB-decision.c
@@ -0,0 +1,350 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023, 2024 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_post-aml-OFFICER_PUB-decision.c
+ * @brief functions to add an AML decision by an AML officer
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include <microhttpd.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_curl_lib.h"
+#include "taler/taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_AddAmlDecision
+{
+
+ /**
+ * 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_AddAmlDecisionCallback 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 /aml/$OFFICER_PUB/decision request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_AddAmlDecision *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_add_aml_decision_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_AddAmlDecision *wh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_AddAmlDecisionResponse adr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ wh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* no reply */
+ adr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ adr.hr.hint = "server offline?";
+ break;
+ 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_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 AML decision\n",
+ (unsigned int) response_code,
+ (int) adr.hr.ec);
+ break;
+ }
+ if (NULL != wh->cb)
+ {
+ wh->cb (wh->cb_cls,
+ &adr);
+ wh->cb = NULL;
+ }
+ TALER_EXCHANGE_post_aml_decision_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_AddAmlDecision *
+TALER_EXCHANGE_post_aml_decision (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_NormalizedPaytoHashP *h_payto,
+ const struct TALER_FullPayto payto_uri,
+ struct GNUNET_TIME_Timestamp decision_time,
+ const char *successor_measure,
+ const char *new_measures,
+ struct GNUNET_TIME_Timestamp expiration_time,
+ unsigned int num_rules,
+ const struct TALER_EXCHANGE_AccountRule *rules,
+ unsigned int num_measures,
+ const struct TALER_EXCHANGE_MeasureInformation *measures,
+ const json_t *properties,
+ bool keep_investigating,
+ const char *justification,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ unsigned int num_events,
+ const char **events,
+ TALER_EXCHANGE_AddAmlDecisionCallback cb,
+ void *cb_cls)
+{
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+ struct TALER_AmlOfficerSignatureP officer_sig;
+ struct TALER_EXCHANGE_AddAmlDecision *wh;
+ CURL *eh;
+ json_t *body;
+ json_t *new_rules;
+ json_t *jrules;
+ json_t *jmeasures;
+ json_t *jevents = NULL;
+
+ if (0 != num_events)
+ {
+ jevents = json_array ();
+ GNUNET_assert (NULL != jevents);
+ for (unsigned int i = 0; i<num_events; i++)
+ GNUNET_assert (0 ==
+ json_array_append_new (jevents,
+ json_string (events[i])));
+ }
+ jrules = json_array ();
+ GNUNET_assert (NULL != jrules);
+ for (unsigned int i = 0; i<num_rules; i++)
+ {
+ const struct TALER_EXCHANGE_AccountRule *al = &rules[i];
+ json_t *rule;
+ json_t *ameasures;
+
+ ameasures = json_array ();
+ GNUNET_assert (NULL != ameasures);
+ for (unsigned int j = 0; j<al->num_measures; j++)
+ GNUNET_assert (0 ==
+ json_array_append_new (ameasures,
+ json_string (al->measures[j])));
+ rule = GNUNET_JSON_PACK (
+ TALER_JSON_pack_kycte ("operation_type",
+ al->operation_type),
+ TALER_JSON_pack_amount ("threshold",
+ &al->threshold),
+ GNUNET_JSON_pack_time_rel ("timeframe",
+ al->timeframe),
+ GNUNET_JSON_pack_array_steal ("measures",
+ ameasures),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("events",
+ jevents)),
+ GNUNET_JSON_pack_bool ("exposed",
+ al->exposed),
+ GNUNET_JSON_pack_bool ("is_and_combinator",
+ al->is_and_combinator),
+ GNUNET_JSON_pack_uint64 ("display_priority",
+ al->display_priority)
+ );
+ GNUNET_break (0 ==
+ json_array_append_new (jrules,
+ rule));
+ }
+
+ jmeasures = json_object ();
+ GNUNET_assert (NULL != jmeasures);
+ for (unsigned int i = 0; i<num_measures; i++)
+ {
+ const struct TALER_EXCHANGE_MeasureInformation *mi = &measures[i];
+ json_t *measure;
+
+ measure = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("check_name",
+ mi->check_name),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("prog_name",
+ mi->prog_name)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("context",
+ (json_t *) mi->context))
+ );
+ GNUNET_break (0 ==
+ json_object_set_new (jmeasures,
+ mi->measure_name,
+ measure));
+ }
+
+ new_rules = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_timestamp ("expiration_time",
+ expiration_time),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("successor_measure",
+ successor_measure)),
+ GNUNET_JSON_pack_array_steal ("rules",
+ jrules),
+ GNUNET_JSON_pack_object_steal ("custom_measures",
+ jmeasures)
+ );
+
+ GNUNET_CRYPTO_eddsa_key_get_public (
+ &officer_priv->eddsa_priv,
+ &officer_pub.eddsa_pub);
+ TALER_officer_aml_decision_sign (justification,
+ decision_time,
+ h_payto,
+ new_rules,
+ properties,
+ new_measures,
+ keep_investigating,
+ officer_priv,
+ &officer_sig);
+ wh = GNUNET_new (struct TALER_EXCHANGE_AddAmlDecision);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ctx = ctx;
+ {
+ char *path;
+ char opus[sizeof (officer_pub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &officer_pub,
+ sizeof (officer_pub),
+ opus,
+ sizeof (opus));
+ *end = '\0';
+ GNUNET_asprintf (&path,
+ "aml/%s/decision",
+ opus);
+ wh->url = TALER_url_join (url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ }
+ if (NULL == wh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (wh);
+ json_decref (new_rules);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("justification",
+ justification),
+ GNUNET_JSON_pack_data_auto ("h_payto",
+ h_payto),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_full_payto ("payto_uri",
+ payto_uri)),
+ GNUNET_JSON_pack_object_steal ("new_rules",
+ new_rules),
+ GNUNET_JSON_pack_object_incref ("properties",
+ (json_t *) properties),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("new_measures",
+ new_measures)),
+ GNUNET_JSON_pack_bool ("keep_investigating",
+ keep_investigating),
+ GNUNET_JSON_pack_data_auto ("officer_sig",
+ &officer_sig),
+ GNUNET_JSON_pack_timestamp ("decision_time",
+ decision_time));
+ 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_aml_decision_finished,
+ wh);
+ if (NULL == wh->job)
+ {
+ TALER_EXCHANGE_post_aml_decision_cancel (wh);
+ return NULL;
+ }
+ return wh;
+}
+
+
+void
+TALER_EXCHANGE_post_aml_decision_cancel (
+ struct TALER_EXCHANGE_AddAmlDecision *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_post-auditors-AUDITOR_PUB-H_DENOM_PUB.c b/src/lib/exchange_api_post-auditors-AUDITOR_PUB-H_DENOM_PUB.c
@@ -0,0 +1,239 @@
+/*
+ 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_post-auditors-AUDITOR_PUB-H_DENOM_PUB.c
+ * @brief functions for the auditor to add its signature for denomination at the exchange
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include <microhttpd.h>
+#include "taler/taler_exchange_service.h"
+#include "auditor_api_curl_defaults.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_curl_lib.h"
+#include "taler/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_post-batch-deposit.c b/src/lib/exchange_api_post-batch-deposit.c
@@ -0,0 +1,820 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2024 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_post-batch-deposit.c
+ * @brief Implementation of the /batch-deposit request of the exchange's HTTP API
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "taler/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/taler_json_lib.h"
+#include "taler/taler_auditor_service.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * 1:#AUDITOR_CHANCE is the probability that we report deposits
+ * to the auditor.
+ *
+ * 20==5% of going to auditor. This is possibly still too high, but set
+ * deliberately this high for testing
+ */
+#define AUDITOR_CHANCE 20
+
+
+/**
+ * Entry in list of ongoing interactions with an auditor.
+ */
+struct TEAH_AuditorInteractionEntry
+{
+ /**
+ * DLL entry.
+ */
+ struct TEAH_AuditorInteractionEntry *next;
+
+ /**
+ * DLL entry.
+ */
+ struct TEAH_AuditorInteractionEntry *prev;
+
+ /**
+ * URL of our auditor. For logging.
+ */
+ const char *auditor_url;
+
+ /**
+ * Interaction state.
+ */
+ struct TALER_AUDITOR_DepositConfirmationHandle *dch;
+
+ /**
+ * Batch deposit this is for.
+ */
+ struct TALER_EXCHANGE_BatchDepositHandle *dh;
+};
+
+
+/**
+ * @brief A Deposit Handle
+ */
+struct TALER_EXCHANGE_BatchDepositHandle
+{
+
+ /**
+ * The keys of the exchange.
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * Context for our curl request(s).
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * 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 post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_BatchDepositResultCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Details about the contract.
+ */
+ struct TALER_EXCHANGE_DepositContractDetail dcd;
+
+ /**
+ * Array with details about the coins.
+ */
+ struct TALER_EXCHANGE_CoinDepositDetail *cdds;
+
+ /**
+ * Hash of the merchant's wire details.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+ /**
+ * Hash over the extensions, or all zero.
+ */
+ struct TALER_ExtensionPolicyHashP h_policy;
+
+ /**
+ * Time when this confirmation was generated / when the exchange received
+ * the deposit request.
+ */
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+ /**
+ * Exchange signature, set for #auditor_cb.
+ */
+ struct TALER_ExchangeSignatureP exchange_sig;
+
+ /**
+ * Head of DLL of interactions with this auditor.
+ */
+ struct TEAH_AuditorInteractionEntry *ai_head;
+
+ /**
+ * Tail of DLL of interactions with this auditor.
+ */
+ struct TEAH_AuditorInteractionEntry *ai_tail;
+
+ /**
+ * Result to return to the application once @e ai_head is empty.
+ */
+ struct TALER_EXCHANGE_BatchDepositResult dr;
+
+ /**
+ * Exchange signing public key, set for #auditor_cb.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Total amount deposited without fees as calculated by us.
+ */
+ struct TALER_Amount total_without_fee;
+
+ /**
+ * Response object to free at the end.
+ */
+ json_t *response;
+
+ /**
+ * Chance that we will inform the auditor about the deposit
+ * is 1:n, where the value of this field is "n".
+ */
+ unsigned int auditor_chance;
+
+ /**
+ * Length of the @e cdds array.
+ */
+ unsigned int num_cdds;
+
+};
+
+
+/**
+ * Finish batch deposit operation by calling the callback.
+ *
+ * @param[in] dh handle to finished batch deposit operation
+ */
+static void
+finish_dh (struct TALER_EXCHANGE_BatchDepositHandle *dh)
+{
+ dh->cb (dh->cb_cls,
+ &dh->dr);
+ TALER_EXCHANGE_batch_deposit_cancel (dh);
+}
+
+
+/**
+ * Function called with the result from our call to the
+ * auditor's /deposit-confirmation handler.
+ *
+ * @param cls closure of type `struct TEAH_AuditorInteractionEntry *`
+ * @param dcr response
+ */
+static void
+acc_confirmation_cb (
+ void *cls,
+ const struct TALER_AUDITOR_DepositConfirmationResponse *dcr)
+{
+ struct TEAH_AuditorInteractionEntry *aie = cls;
+ struct TALER_EXCHANGE_BatchDepositHandle *dh = aie->dh;
+
+ if (MHD_HTTP_OK != dcr->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to submit deposit confirmation to auditor `%s' with HTTP status %d (EC: %d). This is acceptable if it does not happen often.\n",
+ aie->auditor_url,
+ dcr->hr.http_status,
+ dcr->hr.ec);
+ }
+ GNUNET_CONTAINER_DLL_remove (dh->ai_head,
+ dh->ai_tail,
+ aie);
+ GNUNET_free (aie);
+ if (NULL == dh->ai_head)
+ finish_dh (dh);
+}
+
+
+/**
+ * Function called for each auditor to give us a chance to possibly
+ * launch a deposit confirmation interaction.
+ *
+ * @param cls closure
+ * @param auditor_url base URL of the auditor
+ * @param auditor_pub public key of the auditor
+ */
+static void
+auditor_cb (void *cls,
+ const char *auditor_url,
+ const struct TALER_AuditorPublicKeyP *auditor_pub)
+{
+ struct TALER_EXCHANGE_BatchDepositHandle *dh = cls;
+ const struct TALER_EXCHANGE_SigningPublicKey *spk;
+ struct TEAH_AuditorInteractionEntry *aie;
+ const struct TALER_CoinSpendSignatureP *csigs[GNUNET_NZL (
+ dh->num_cdds)];
+ const struct TALER_CoinSpendPublicKeyP *cpubs[GNUNET_NZL (
+ dh->num_cdds)];
+
+ for (unsigned int i = 0; i<dh->num_cdds; i++)
+ {
+ const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &dh->cdds[i];
+
+ csigs[i] = &cdd->coin_sig;
+ cpubs[i] = &cdd->coin_pub;
+ }
+
+ if (0 !=
+ GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
+ dh->auditor_chance))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Not providing deposit confirmation to auditor\n");
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Will provide deposit confirmation to auditor `%s'\n",
+ TALER_B2S (auditor_pub));
+ spk = TALER_EXCHANGE_get_signing_key_info (dh->keys,
+ &dh->exchange_pub);
+ if (NULL == spk)
+ {
+ GNUNET_break_op (0);
+ return;
+ }
+ aie = GNUNET_new (struct TEAH_AuditorInteractionEntry);
+ aie->dh = dh;
+ aie->auditor_url = auditor_url;
+ aie->dch = TALER_AUDITOR_deposit_confirmation (
+ dh->ctx,
+ auditor_url,
+ &dh->h_wire,
+ &dh->h_policy,
+ &dh->dcd.h_contract_terms,
+ dh->exchange_timestamp,
+ dh->dcd.wire_deadline,
+ dh->dcd.refund_deadline,
+ &dh->total_without_fee,
+ dh->num_cdds,
+ cpubs,
+ csigs,
+ &dh->dcd.merchant_pub,
+ &dh->exchange_pub,
+ &dh->exchange_sig,
+ &dh->keys->master_pub,
+ spk->valid_from,
+ spk->valid_until,
+ spk->valid_legal,
+ &spk->master_sig,
+ &acc_confirmation_cb,
+ aie);
+ GNUNET_CONTAINER_DLL_insert (dh->ai_head,
+ dh->ai_tail,
+ aie);
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /deposit request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_BatchDepositHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_deposit_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_BatchDepositHandle *dh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_BatchDepositResult *dr = &dh->dr;
+
+ dh->job = NULL;
+ dh->response = json_incref ((json_t*) j);
+ dr->hr.reply = dh->response;
+ dr->hr.http_status = (unsigned int) response_code;
+ switch (response_code)
+ {
+ case 0:
+ dr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &dh->exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &dh->exchange_pub),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("transaction_base_url",
+ &dr->details.ok.transaction_base_url),
+ NULL),
+ GNUNET_JSON_spec_timestamp ("exchange_timestamp",
+ &dh->exchange_timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr->hr.http_status = 0;
+ dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (dh->keys,
+ &dh->exchange_pub))
+ {
+ GNUNET_break_op (0);
+ dr->hr.http_status = 0;
+ dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
+ break;
+ }
+ {
+ const struct TALER_CoinSpendSignatureP *csigs[
+ GNUNET_NZL (dh->num_cdds)];
+
+ for (unsigned int i = 0; i<dh->num_cdds; i++)
+ csigs[i] = &dh->cdds[i].coin_sig;
+ if (GNUNET_OK !=
+ TALER_exchange_online_deposit_confirmation_verify (
+ &dh->dcd.h_contract_terms,
+ &dh->h_wire,
+ &dh->h_policy,
+ dh->exchange_timestamp,
+ dh->dcd.wire_deadline,
+ dh->dcd.refund_deadline,
+ &dh->total_without_fee,
+ dh->num_cdds,
+ csigs,
+ &dh->dcd.merchant_pub,
+ &dh->exchange_pub,
+ &dh->exchange_sig))
+ {
+ GNUNET_break_op (0);
+ dr->hr.http_status = 0;
+ dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
+ break;
+ }
+ }
+ TEAH_get_auditors_for_dc (dh->keys,
+ &auditor_cb,
+ dh);
+ }
+ dr->details.ok.exchange_sig = &dh->exchange_sig;
+ dr->details.ok.exchange_pub = &dh->exchange_pub;
+ dr->details.ok.deposit_timestamp = dh->exchange_timestamp;
+ 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 */
+ dr->hr.ec = TALER_JSON_get_error_code (j);
+ dr->hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ dr->hr.ec = TALER_JSON_get_error_code (j);
+ dr->hr.hint = TALER_JSON_get_error_hint (j);
+ /* 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 */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dr->hr.ec = TALER_JSON_get_error_code (j);
+ dr->hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ {
+ dr->hr.ec = TALER_JSON_get_error_code (j);
+ dr->hr.hint = TALER_JSON_get_error_hint (j);
+ switch (dr->hr.ec)
+ {
+ case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "coin_pub",
+ &dr->details.conflict.details
+ .insufficient_funds.coin_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr->hr.http_status = 0;
+ dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ break;
+ case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "coin_pub",
+ &dr->details.conflict.details
+ .coin_conflicting_age_hash.coin_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr->hr.http_status = 0;
+ dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ break;
+ case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "coin_pub",
+ &dr->details.conflict.details
+ .coin_conflicting_denomination_key.coin_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr->hr.http_status = 0;
+ dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ break;
+ case TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT:
+ break;
+ default:
+ GNUNET_break_op (0);
+ break;
+ }
+ }
+ 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 */
+ dr->hr.ec = TALER_JSON_get_error_code (j);
+ dr->hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "h_payto",
+ &dr->details.unavailable_for_legal_reasons.h_payto),
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &dr->details.unavailable_for_legal_reasons.requirement_row),
+ GNUNET_JSON_spec_bool (
+ "bad_kyc_auth",
+ &dr->details.unavailable_for_legal_reasons.bad_kyc_auth),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr->hr.http_status = 0;
+ dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dr->hr.ec = TALER_JSON_get_error_code (j);
+ dr->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 */
+ dr->hr.ec = TALER_JSON_get_error_code (j);
+ dr->hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange deposit\n",
+ (unsigned int) response_code,
+ dr->hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ if (NULL != dh->ai_head)
+ return;
+ finish_dh (dh);
+}
+
+
+struct TALER_EXCHANGE_BatchDepositHandle *
+TALER_EXCHANGE_batch_deposit (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_EXCHANGE_DepositContractDetail *dcd,
+ unsigned int num_cdds,
+ const struct TALER_EXCHANGE_CoinDepositDetail cdds[static num_cdds],
+ TALER_EXCHANGE_BatchDepositResultCallback cb,
+ void *cb_cls,
+ enum TALER_ErrorCode *ec)
+{
+ struct TALER_EXCHANGE_BatchDepositHandle *dh;
+ json_t *deposit_obj;
+ json_t *deposits;
+ CURL *eh;
+ const struct GNUNET_HashCode *wallet_data_hashp;
+
+ if (0 == num_cdds)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (GNUNET_TIME_timestamp_cmp (dcd->refund_deadline,
+ >,
+ dcd->wire_deadline))
+ {
+ GNUNET_break_op (0);
+ *ec = TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE;
+ return NULL;
+ }
+ dh = GNUNET_new (struct TALER_EXCHANGE_BatchDepositHandle);
+ dh->auditor_chance = AUDITOR_CHANCE;
+ dh->cb = cb;
+ dh->cb_cls = cb_cls;
+ dh->cdds = GNUNET_memdup (cdds,
+ num_cdds * sizeof (*cdds));
+ dh->num_cdds = num_cdds;
+ dh->dcd = *dcd;
+ if (NULL != dcd->policy_details)
+ TALER_deposit_policy_hash (dcd->policy_details,
+ &dh->h_policy);
+ TALER_merchant_wire_signature_hash (dcd->merchant_payto_uri,
+ &dcd->wire_salt,
+ &dh->h_wire);
+ deposits = json_array ();
+ GNUNET_assert (NULL != deposits);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (cdds[0].amount.currency,
+ &dh->total_without_fee));
+ for (unsigned int i = 0; i<num_cdds; i++)
+ {
+ const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &cdds[i];
+ const struct TALER_EXCHANGE_DenomPublicKey *dki;
+ const struct TALER_AgeCommitmentHashP *h_age_commitmentp;
+ struct TALER_Amount amount_without_fee;
+
+ dki = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
+ &cdd->h_denom_pub);
+ if (NULL == dki)
+ {
+ *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+ GNUNET_break_op (0);
+ json_decref (deposits);
+ return NULL;
+ }
+ if (0 >
+ TALER_amount_subtract (&amount_without_fee,
+ &cdd->amount,
+ &dki->fees.deposit))
+ {
+ *ec = TALER_EC_EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT;
+ GNUNET_break_op (0);
+ GNUNET_free (dh->cdds);
+ GNUNET_free (dh);
+ json_decref (deposits);
+ return NULL;
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&dh->total_without_fee,
+ &dh->total_without_fee,
+ &amount_without_fee));
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_verify_deposit_signature_ (dcd,
+ &dh->h_policy,
+ &dh->h_wire,
+ cdd,
+ dki))
+ {
+ *ec = TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID;
+ GNUNET_break_op (0);
+ GNUNET_free (dh->cdds);
+ GNUNET_free (dh);
+ json_decref (deposits);
+ return NULL;
+ }
+ if (! GNUNET_is_zero (&dcd->merchant_sig))
+ {
+ /* FIXME #9185: check merchant_sig!? */
+ }
+ if (GNUNET_is_zero (&cdd->h_age_commitment))
+ h_age_commitmentp = NULL;
+ else
+ h_age_commitmentp = &cdd->h_age_commitment;
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ deposits,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("contribution",
+ &cdd->amount),
+ GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+ &cdd->h_denom_pub),
+ TALER_JSON_pack_denom_sig ("ub_sig",
+ &cdd->denom_sig),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &cdd->coin_pub),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ h_age_commitmentp)),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &cdd->coin_sig)
+ )));
+ }
+ dh->url = TALER_url_join (url,
+ "batch-deposit",
+ NULL);
+ if (NULL == dh->url)
+ {
+ GNUNET_break (0);
+ *ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
+ GNUNET_free (dh->url);
+ GNUNET_free (dh->cdds);
+ GNUNET_free (dh);
+ json_decref (deposits);
+ return NULL;
+ }
+
+ if (GNUNET_is_zero (&dcd->wallet_data_hash))
+ wallet_data_hashp = NULL;
+ else
+ wallet_data_hashp = &dcd->wallet_data_hash;
+
+ deposit_obj = GNUNET_JSON_PACK (
+ TALER_JSON_pack_full_payto ("merchant_payto_uri",
+ dcd->merchant_payto_uri),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("extra_wire_subject_metadata",
+ dcd->extra_wire_subject_metadata)),
+ GNUNET_JSON_pack_data_auto ("wire_salt",
+ &dcd->wire_salt),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &dcd->h_contract_terms),
+ GNUNET_JSON_pack_array_steal ("coins",
+ deposits),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("wallet_data_hash",
+ wallet_data_hashp)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_steal ("policy_details",
+ (json_t *) dcd->policy_details)),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ dcd->wallet_timestamp),
+ GNUNET_JSON_pack_data_auto ("merchant_pub",
+ &dcd->merchant_pub),
+ GNUNET_JSON_pack_data_auto ("merchant_sig",
+ &dcd->merchant_sig),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("refund_deadline",
+ dcd->refund_deadline)),
+ GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
+ dcd->wire_deadline));
+ GNUNET_assert (NULL != deposit_obj);
+ eh = TALER_EXCHANGE_curl_easy_get_ (dh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&dh->post_ctx,
+ eh,
+ deposit_obj)) )
+ {
+ *ec = TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE;
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (deposit_obj);
+ GNUNET_free (dh->cdds);
+ GNUNET_free (dh->url);
+ GNUNET_free (dh);
+ return NULL;
+ }
+ json_decref (deposit_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for deposit: `%s'\n",
+ dh->url);
+ dh->ctx = ctx;
+ dh->keys = TALER_EXCHANGE_keys_incref (keys);
+ dh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ dh->post_ctx.headers,
+ &handle_deposit_finished,
+ dh);
+ return dh;
+}
+
+
+void
+TALER_EXCHANGE_batch_deposit_force_dc (
+ struct TALER_EXCHANGE_BatchDepositHandle *deposit)
+{
+ deposit->auditor_chance = 1;
+}
+
+
+void
+TALER_EXCHANGE_batch_deposit_cancel (
+ struct TALER_EXCHANGE_BatchDepositHandle *deposit)
+{
+ struct TEAH_AuditorInteractionEntry *aie;
+
+ while (NULL != (aie = deposit->ai_head))
+ {
+ GNUNET_assert (aie->dh == deposit);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Not sending deposit confirmation to auditor `%s' due to cancellation\n",
+ aie->auditor_url);
+ TALER_AUDITOR_deposit_confirmation_cancel (aie->dch);
+ GNUNET_CONTAINER_DLL_remove (deposit->ai_head,
+ deposit->ai_tail,
+ aie);
+ GNUNET_free (aie);
+ }
+ if (NULL != deposit->job)
+ {
+ GNUNET_CURL_job_cancel (deposit->job);
+ deposit->job = NULL;
+ }
+ TALER_EXCHANGE_keys_decref (deposit->keys);
+ GNUNET_free (deposit->url);
+ GNUNET_free (deposit->cdds);
+ TALER_curl_easy_post_finished (&deposit->post_ctx);
+ json_decref (deposit->response);
+ GNUNET_free (deposit);
+}
+
+
+/* end of exchange_api_batch_deposit.c */
diff --git a/src/lib/exchange_api_post-blinding-prepare.c b/src/lib/exchange_api_post-blinding-prepare.c
@@ -0,0 +1,422 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 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_post-blinding-prepare.c
+ * @brief Implementation of /blinding-prepare requests
+ * @author Özgür Kesim
+ */
+
+#include "taler/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/taler_curl_lib.h"
+#include "taler/taler_error_codes.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_util.h"
+
+/**
+ * A /blinding-prepare request-handle
+ */
+struct TALER_EXCHANGE_BlindingPrepareHandle
+{
+ /**
+ * number of elements to prepare
+ */
+ size_t num;
+
+ /**
+ * True, if this operation is for melting (or withdraw otherwise).
+ */
+ bool for_melt;
+
+ /**
+ * The seed for the batch of nonces.
+ */
+ const struct TALER_BlindingMasterSeedP *seed;
+
+ /**
+ * The url for this request.
+ */
+ char *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 withdraw response results.
+ */
+ TALER_EXCHANGE_BlindingPrepareCallback callback;
+
+ /**
+ * Closure for @e callback
+ */
+ void *callback_cls;
+
+};
+
+
+/**
+ * We got a 200 OK response for the /blinding-prepare operation.
+ * Extract the r_pub values and return them to the caller via the callback
+ *
+ * @param handle operation handle
+ * @param response response details
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+blinding_prepare_ok (struct TALER_EXCHANGE_BlindingPrepareHandle *handle,
+ struct TALER_EXCHANGE_BlindingPrepareResponse *response)
+{
+ const json_t *j_r_pubs;
+ const char *cipher;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("cipher",
+ &cipher),
+ GNUNET_JSON_spec_array_const ("r_pubs",
+ &j_r_pubs),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (response->hr.reply,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (strcmp ("CS", cipher))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (json_array_size (j_r_pubs)
+ != handle->num)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ size_t num = handle->num;
+ const json_t *j_pair;
+ size_t idx;
+ struct TALER_ExchangeBlindingValues blinding_values[GNUNET_NZL (num)];
+
+ memset (blinding_values,
+ 0,
+ sizeof(blinding_values));
+
+ json_array_foreach (j_r_pubs, idx, j_pair) {
+ struct GNUNET_CRYPTO_BlindingInputValues *bi =
+ GNUNET_new (struct GNUNET_CRYPTO_BlindingInputValues);
+ struct GNUNET_CRYPTO_CSPublicRPairP *csv = &bi->details.cs_values;
+ struct GNUNET_JSON_Specification tuple[] = {
+ GNUNET_JSON_spec_fixed (NULL,
+ &csv->r_pub[0],
+ sizeof(csv->r_pub[0])),
+ GNUNET_JSON_spec_fixed (NULL,
+ &csv->r_pub[1],
+ sizeof(csv->r_pub[1])),
+ GNUNET_JSON_spec_end ()
+ };
+ struct GNUNET_JSON_Specification jspec[] = {
+ TALER_JSON_spec_tuple_of (NULL, tuple),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *err_msg;
+ unsigned int err_line;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j_pair,
+ jspec,
+ &err_msg,
+ &err_line))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (bi);
+ for (size_t i=0; i < idx; i++)
+ TALER_denom_ewv_free (&blinding_values[i]);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error while parsing response: in line %d: %s",
+ err_line,
+ err_msg);
+ return GNUNET_SYSERR;
+ }
+
+ bi->cipher = GNUNET_CRYPTO_BSA_CS;
+ bi->rc = 1;
+ blinding_values[idx].blinding_inputs = bi;
+ }
+
+ response->details.ok.blinding_values = blinding_values;
+ response->details.ok.num_blinding_values = num;
+
+ handle->callback (
+ handle->callback_cls,
+ response);
+
+ for (size_t i = 0; i < num; i++)
+ TALER_denom_ewv_free (&blinding_values[i]);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the HTTP /blinding-prepare request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_BlindingPrepareHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_blinding_prepare_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_BlindingPrepareHandle *handle = cls;
+ const json_t *j_response = response;
+ struct TALER_EXCHANGE_BlindingPrepareResponse bpr = {
+ .hr = {
+ .reply = j_response,
+ .http_status = (unsigned int) response_code
+ },
+ };
+
+ handle->job = NULL;
+
+ switch (response_code)
+ {
+ case 0:
+ bpr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+
+ case MHD_HTTP_OK:
+ {
+ if (GNUNET_OK !=
+ blinding_prepare_ok (handle,
+ &bpr))
+ {
+ GNUNET_break_op (0);
+ bpr.hr.http_status = 0;
+ bpr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ TALER_EXCHANGE_blinding_prepare_cancel (handle);
+ 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 */
+ bpr.hr.ec = TALER_JSON_get_error_code (j_response);
+ bpr.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 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. */
+ bpr.hr.ec = TALER_JSON_get_error_code (j_response);
+ bpr.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 */
+ bpr.hr.ec = TALER_JSON_get_error_code (j_response);
+ bpr.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 */
+ bpr.hr.ec = TALER_JSON_get_error_code (j_response);
+ bpr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ bpr.hr.ec = TALER_JSON_get_error_code (j_response);
+ bpr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for the blinding-prepare request\n",
+ (unsigned int) response_code,
+ (int) bpr.hr.ec);
+ break;
+
+ }
+
+ handle->callback (handle->callback_cls,
+ &bpr);
+ handle->callback = NULL;
+ TALER_EXCHANGE_blinding_prepare_cancel (handle);
+}
+
+
+struct TALER_EXCHANGE_BlindingPrepareHandle *
+TALER_EXCHANGE_blinding_prepare (
+ struct GNUNET_CURL_Context *curl_ctx,
+ const char *exchange_url,
+ const struct TALER_BlindingMasterSeedP *seed,
+ bool for_melt,
+ size_t num,
+ const struct TALER_EXCHANGE_NonceKey nonce_keys[static num],
+ TALER_EXCHANGE_BlindingPrepareCallback callback,
+ void *callback_cls)
+{
+ struct TALER_EXCHANGE_BlindingPrepareHandle *bph;
+
+ if (0 == num)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ for (unsigned int i = 0; i<num; i++)
+ if (GNUNET_CRYPTO_BSA_CS !=
+ nonce_keys[i].pk->key.bsign_pub_key->cipher)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ bph = GNUNET_new (struct TALER_EXCHANGE_BlindingPrepareHandle);
+ bph->num = num;
+ bph->callback = callback;
+ bph->for_melt = for_melt;
+ bph->callback_cls = callback_cls;
+ bph->url = TALER_url_join (exchange_url,
+ "blinding-prepare",
+ NULL);
+ if (NULL == bph->url)
+ {
+ GNUNET_break (0);
+ GNUNET_free (bph);
+ return NULL;
+ }
+ {
+ CURL *eh;
+ json_t *j_nks;
+ json_t *j_request = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ "CS"),
+ GNUNET_JSON_pack_string ("operation",
+ for_melt ? "melt" : "withdraw"),
+ GNUNET_JSON_pack_data_auto ("seed",
+ seed));
+ GNUNET_assert (NULL != j_request);
+
+ j_nks = json_array ();
+ GNUNET_assert (NULL != j_nks);
+
+ for (size_t i = 0; i<num; i++)
+ {
+ const struct TALER_EXCHANGE_NonceKey *nk = &nonce_keys[i];
+ json_t *j_entry = 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 != j_entry);
+ GNUNET_assert (0 ==
+ json_array_append_new (j_nks,
+ j_entry));
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (j_request,
+ "nks",
+ j_nks));
+ eh = TALER_EXCHANGE_curl_easy_get_ (bph->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&bph->post_ctx,
+ eh,
+ j_request)))
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (j_request);
+ GNUNET_free (bph->url);
+ GNUNET_free (bph);
+ return NULL;
+ }
+
+ json_decref (j_request);
+ bph->job = GNUNET_CURL_job_add2 (curl_ctx,
+ eh,
+ bph->post_ctx.headers,
+ &handle_blinding_prepare_finished,
+ bph);
+ if (NULL == bph->job)
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_blinding_prepare_cancel (bph);
+ return NULL;
+ }
+ }
+ return bph;
+}
+
+
+void
+TALER_EXCHANGE_blinding_prepare_cancel (
+ struct TALER_EXCHANGE_BlindingPrepareHandle *bph)
+{
+ if (NULL == bph)
+ return;
+ if (NULL != bph->job)
+ {
+ GNUNET_CURL_job_cancel (bph->job);
+ bph->job = NULL;
+ }
+ GNUNET_free (bph->url);
+ TALER_curl_easy_post_finished (&bph->post_ctx);
+ GNUNET_free (bph);
+}
+
+
+/* end of lib/exchange_api_blinding_prepare.c */
diff --git a/src/lib/exchange_api_post-coins-COIN_PUB-refund.c b/src/lib/exchange_api_post-coins-COIN_PUB-refund.c
@@ -0,0 +1,484 @@
+/*
+ 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_post-coins-COIN_PUB-refund.c
+ * @brief Implementation of the /refund request of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#include "taler/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/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "taler/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_FAILED_DEPENDENCY:
+ 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_post-kyc-start-ID.c b/src/lib/exchange_api_post-kyc-start-ID.c
@@ -0,0 +1,220 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 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_post-kyc-start-ID.c
+ * @brief functions to start a KYC process
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include <microhttpd.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_curl_lib.h"
+#include "taler/taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_KycStartHandle
+{
+
+ /**
+ * 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_KycStartCallback 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 /kyc-start/$ID request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_KycStartHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_kyc_start_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_KycStartHandle *wh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_KycStartResponse adr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ wh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* no reply */
+ adr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ adr.hr.hint = "server offline?";
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string (
+ "redirect_url",
+ &adr.details.ok.redirect_url),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ adr.hr.http_status = 0;
+ adr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ 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 AML decision\n",
+ (unsigned int) response_code,
+ (int) adr.hr.ec);
+ break;
+ }
+ if (NULL != wh->cb)
+ {
+ wh->cb (wh->cb_cls,
+ &adr);
+ wh->cb = NULL;
+ }
+ TALER_EXCHANGE_kyc_start_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_KycStartHandle *
+TALER_EXCHANGE_kyc_start (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const char *id,
+ TALER_EXCHANGE_KycStartCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_KycStartHandle *wh;
+ CURL *eh;
+ json_t *body;
+
+ wh = GNUNET_new (struct TALER_EXCHANGE_KycStartHandle);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ctx = ctx;
+ {
+ char *path;
+
+ GNUNET_asprintf (&path,
+ "kyc-start/%s",
+ id);
+ wh->url = TALER_url_join (url,
+ path,
+ NULL);
+ GNUNET_free (path);
+ }
+ if (NULL == wh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (wh);
+ return NULL;
+ }
+ body = json_object (); /* as per spec: empty! */
+ GNUNET_assert (NULL != body);
+ 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_kyc_start_finished,
+ wh);
+ if (NULL == wh->job)
+ {
+ TALER_EXCHANGE_kyc_start_cancel (wh);
+ return NULL;
+ }
+ return wh;
+}
+
+
+void
+TALER_EXCHANGE_kyc_start_cancel (
+ struct TALER_EXCHANGE_KycStartHandle *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_post-kyc-wallet.c b/src/lib/exchange_api_post-kyc-wallet.c
@@ -0,0 +1,257 @@
+/*
+ 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_post-kyc-wallet.c
+ * @brief Implementation of the /kyc-wallet request
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <microhttpd.h> /* just for HTTP wallet codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler/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 = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ kwh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any (
+ "next_threshold",
+ &ks.details.ok.next_threshold),
+ NULL),
+ GNUNET_JSON_spec_timestamp (
+ "expiration_time",
+ &ks.details.ok.expiration_time),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ks.hr.http_status = 0;
+ ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ break;
+ }
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ ks.hr.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.hr.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ ks.hr.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.hr.http_status = 0;
+ ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ break;
+ }
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ ks.hr.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.hr.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.hr.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_post-management-auditors-AUDITOR_PUB-disable.c b/src/lib/exchange_api_post-management-auditors-AUDITOR_PUB-disable.c
@@ -0,0 +1,220 @@
+/*
+ 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_post-management-auditors-AUDITOR_PUB-disable.c
+ * @brief functions to disable an auditor
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include <microhttpd.h>
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_curl_lib.h"
+#include "taler/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_post-management-auditors.c b/src/lib/exchange_api_post-management-auditors.c
@@ -0,0 +1,227 @@
+/*
+ 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_post-management-auditors.c
+ * @brief functions to enable an auditor
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include <microhttpd.h>
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_curl_lib.h"
+#include "taler/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 auditor_enable_response
+ = {
+ .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:
+ auditor_enable_response.hr.ec = TALER_JSON_get_error_code (json);
+ auditor_enable_response.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)
+ {
+ auditor_enable_response.hr.ec = TALER_JSON_get_error_code (json);
+ auditor_enable_response.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ auditor_enable_response.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ auditor_enable_response.hr.hint = TALER_ErrorCode_get_hint (
+ auditor_enable_response.hr.ec);
+ }
+ break;
+ case MHD_HTTP_CONFLICT:
+ auditor_enable_response.hr.ec = TALER_JSON_get_error_code (json);
+ auditor_enable_response.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ auditor_enable_response.hr.ec = TALER_JSON_get_error_code (json);
+ auditor_enable_response.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) auditor_enable_response.hr.ec);
+ break;
+ }
+ if (NULL != ah->cb)
+ {
+ ah->cb (ah->cb_cls,
+ &auditor_enable_response);
+ 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_post-management-denominations-H_DENOM_PUB-revoke.c b/src/lib/exchange_api_post-management-denominations-H_DENOM_PUB-revoke.c
@@ -0,0 +1,223 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2024 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_post-management-denominations-H_DENOM_PUB-revoke.c
+ * @brief functions to revoke an exchange denomination key
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include <microhttpd.h>
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_curl_lib.h"
+#include "taler/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);
+ GNUNET_free (rh);
+ 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_post-management-drain.c b/src/lib/exchange_api_post-management-drain.c
@@ -0,0 +1,214 @@
+/*
+ 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_post-management-drain.c
+ * @brief functions to set wire fees at an exchange
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include <microhttpd.h>
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_curl_lib.h"
+#include "taler/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 struct TALER_FullPayto 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),
+ TALER_JSON_pack_full_payto ("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_post-management-extensions.c b/src/lib/exchange_api_post-management-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_post-management-extensions.c
+ * @brief functions to handle the settings for extensions (p2p and age restriction)
+ * @author Özgür Kesim
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_extensions.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_curl_lib.h"
+#include "taler/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_post-management-global-fees.c b/src/lib/exchange_api_post-management-global-fees.c
@@ -0,0 +1,237 @@
+/*
+ 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_post-management-global-fees.c
+ * @brief functions to set global fees at an exchange
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include <microhttpd.h>
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_curl_lib.h"
+#include "taler/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);
+ GNUNET_free (sgfh);
+ 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_post-management-keys.c b/src/lib/exchange_api_post-management-keys.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_post-management-keys.c
+ * @brief functions to affirm the validity of exchange keys using the master private key
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include <microhttpd.h>
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_curl_lib.h"
+#include "taler/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_post-management-partners.c b/src/lib/exchange_api_post-management-partners.c
@@ -0,0 +1,219 @@
+/*
+ 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_post-management-partners.c
+ * @brief functions to add an partner by an AML officer
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include <microhttpd.h>
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_curl_lib.h"
+#include "taler/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_post-management-signkeys-EXCHANGE_PUB-revoke.c b/src/lib/exchange_api_post-management-signkeys-EXCHANGE_PUB-revoke.c
@@ -0,0 +1,213 @@
+/*
+ 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_post-management-signkeys-EXCHANGE_PUB-revoke.c
+ * @brief functions to revoke an exchange online signing key
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include <microhttpd.h>
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_curl_lib.h"
+#include "taler/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);
+ GNUNET_free (rh);
+ 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_post-management-wire-disable.c b/src/lib/exchange_api_post-management-wire-disable.c
@@ -0,0 +1,222 @@
+/*
+ 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_post-management-wire-disable.c
+ * @brief functions to disable an exchange wire method / bank account
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include <microhttpd.h>
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_curl_lib.h"
+#include "taler/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 struct TALER_FullPayto 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 (
+ TALER_JSON_pack_full_payto ("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);
+ GNUNET_free (wh);
+ 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_post-management-wire-fee.c b/src/lib/exchange_api_post-management-wire-fee.c
@@ -0,0 +1,229 @@
+/*
+ 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_post-management-wire-fee.c
+ * @brief functions to set wire fees at an exchange
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include <microhttpd.h>
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_curl_lib.h"
+#include "taler/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);
+ GNUNET_free (swfh);
+ 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_post-management-wire.c b/src/lib/exchange_api_post-management-wire.c
@@ -0,0 +1,254 @@
+/*
+ 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_post-management-wire.c
+ * @brief functions to enable an exchange wire method / bank account
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include <microhttpd.h>
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_curl_lib.h"
+#include "taler/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 struct TALER_FullPayto 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,
+ const char *bank_label,
+ int64_t priority,
+ 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 (
+ TALER_JSON_pack_full_payto ("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_allow_null (
+ GNUNET_JSON_pack_string ("bank_label",
+ bank_label)),
+ GNUNET_JSON_pack_int64 ("priority",
+ priority),
+ 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);
+ GNUNET_free (wh);
+ 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_post-melt.c b/src/lib/exchange_api_post-melt.c
@@ -0,0 +1,645 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 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_post-melt.c
+ * @brief Implementation of the /melt request
+ * @author Özgür Kesim
+ */
+#include "taler/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/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+#include "exchange_api_refresh_common.h"
+
+
+/**
+ * @brief A /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 seed for the melt operation.
+ */
+ struct TALER_PublicRefreshMasterSeedP rms;
+
+ /**
+ * Details about the characteristics of the requested melt operation.
+ */
+ const struct TALER_EXCHANGE_MeltInput *rd;
+
+ /**
+ * True, if no blinding_seed is needed (no CS denominations involved)
+ */
+ bool no_blinding_seed;
+
+ /**
+ * If @e no_blinding_seed is false, the blinding seed for the intermediate
+ * call to /blinding-prepare, in order to retrieve the R-values from the
+ * exchange for the blind Clause-Schnorr signature.
+ */
+ struct TALER_BlindingMasterSeedP blinding_seed;
+
+ /**
+ * Array of `num_fresh_denom_pubs` per-coin values
+ * returned from melt operation.
+ */
+ struct TALER_ExchangeBlindingValues *melt_blinding_values;
+
+ /**
+ * Handle for the preflight request, or NULL.
+ */
+ struct TALER_EXCHANGE_BlindingPrepareHandle *bpr;
+
+ /**
+ * 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;
+
+};
+
+
+/**
+ * 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;
+ 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 */
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (mh->keys,
+ 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 /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_melt_blinding_values = mh->rd->num_fresh_denom_pubs;
+ mr.details.ok.melt_blinding_values = mh->melt_blinding_values;
+ mr.details.ok.blinding_seed = mh->no_blinding_seed
+ ? NULL
+ : &mh->blinding_seed;
+ 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)
+{
+ json_t *j_request_body;
+ json_t *j_transfer_pubs;
+ json_t *j_coin_evs;
+ CURL *eh;
+ struct TALER_DenominationHashP h_denom_pub;
+
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_get_melt_data (&mh->rms,
+ mh->rd,
+ mh->no_blinding_seed
+ ? NULL
+ : &mh->blinding_seed,
+ mh->melt_blinding_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);
+ mh->dki = TALER_EXCHANGE_get_denomination_key (
+ mh->keys,
+ &mh->md.melted_coin.pub_key);
+ j_request_body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("old_coin_pub",
+ &mh->coin_pub),
+ GNUNET_JSON_pack_data_auto ("old_denom_pub_h",
+ &h_denom_pub),
+ TALER_JSON_pack_denom_sig ("old_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_allow_null (
+ (NULL != mh->md.melted_coin.h_age_commitment)
+ ? GNUNET_JSON_pack_data_auto ("old_age_commitment_h",
+ mh->md.melted_coin.h_age_commitment)
+ : GNUNET_JSON_pack_string ("old_age_commitment_h",
+ NULL)),
+ GNUNET_JSON_pack_data_auto ("refresh_seed",
+ &mh->md.refresh_seed),
+ GNUNET_JSON_pack_allow_null (
+ (mh->md.no_blinding_seed)
+ ? GNUNET_JSON_pack_string ("blinding_seed",
+ NULL)
+ : GNUNET_JSON_pack_data_auto ("blinding_seed",
+ &mh->md.blinding_seed)),
+ TALER_JSON_pack_array_of_data_auto ("denoms_h",
+ mh->md.num_fresh_coins,
+ mh->md.denoms_h)
+ );
+ GNUNET_assert (NULL != j_request_body);
+ GNUNET_assert (NULL !=
+ (j_transfer_pubs = json_array ()));
+ GNUNET_assert (NULL !=
+ (j_coin_evs = json_array ()));
+ /**
+ * Fill the kappa array of coin envelopes and
+ * the array of transfer pubs.
+ */
+ for (uint8_t k=0; k<TALER_CNC_KAPPA; k++)
+ {
+ json_t *j_envs;
+ json_t *j_tbs = GNUNET_JSON_PACK (
+ TALER_JSON_pack_array_of_data_auto (NULL,
+ mh->md.num_fresh_coins,
+ mh->md.kappa_transfer_pubs[k])
+ );
+
+ GNUNET_assert (NULL != (j_envs = json_array ()));
+ GNUNET_assert (NULL !=j_tbs);
+
+ for (size_t i = 0; i < mh->md.num_fresh_coins; i++)
+ {
+ json_t *j_coin = GNUNET_JSON_PACK (
+ TALER_JSON_pack_blinded_planchet (NULL,
+ &mh->md.kappa_blinded_planchets[k][i]));
+ GNUNET_assert (NULL != j_coin);
+ GNUNET_assert (0 ==
+ json_array_append_new (j_envs, j_coin));
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (j_coin_evs, j_envs));
+ GNUNET_assert (0 ==
+ json_array_append_new (j_transfer_pubs, j_tbs));
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (j_request_body,
+ "coin_evs",
+ j_coin_evs));
+ GNUNET_assert (0 ==
+ json_object_set_new (j_request_body,
+ "transfer_pubs",
+ j_transfer_pubs));
+ /* and now we can at last begin the actual request handling */
+ mh->url = TALER_url_join (mh->exchange_url,
+ "melt",
+ NULL);
+ if (NULL == mh->url)
+ {
+ json_decref (j_request_body);
+ return GNUNET_SYSERR;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (mh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&mh->ctx,
+ eh,
+ j_request_body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (j_request_body);
+ return GNUNET_SYSERR;
+ }
+ json_decref (j_request_body);
+ 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
+ * /blinding-prepare request to a exchange.
+ *
+ * @param cls closure with our `struct TALER_EXCHANGE_MeltHandle *`
+ * @param bpr response details
+ */
+static void
+blinding_prepare_cb (void *cls,
+ const struct TALER_EXCHANGE_BlindingPrepareResponse *bpr)
+{
+ struct TALER_EXCHANGE_MeltHandle *mh = cls;
+ unsigned int nks_off = 0;
+
+ mh->bpr = NULL;
+ if (MHD_HTTP_OK != bpr->hr.http_status)
+ {
+ struct TALER_EXCHANGE_MeltResponse mr = {
+ .hr = bpr->hr
+ };
+
+ mr.hr.hint = "/blinding-prepare failed";
+ mh->melt_cb (mh->melt_cb_cls,
+ &mr);
+ TALER_EXCHANGE_melt_cancel (mh);
+ return;
+ }
+ for (unsigned int i = 0; i<mh->rd->num_fresh_denom_pubs; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk =
+ &mh->rd->fresh_denom_pubs[i];
+ struct TALER_ExchangeBlindingValues *wv = &mh->melt_blinding_values[i];
+
+ switch (fresh_pk->key.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ GNUNET_break (0);
+ fail_mh (mh,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR);
+ return;
+ case GNUNET_CRYPTO_BSA_RSA:
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ TALER_denom_ewv_copy (wv,
+ &bpr->details.ok.blinding_values[nks_off]);
+ nks_off++;
+ break;
+ }
+ }
+ 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_PublicRefreshMasterSeedP *rms,
+ const struct TALER_EXCHANGE_MeltInput *rd,
+ TALER_EXCHANGE_MeltCallback melt_cb,
+ void *melt_cb_cls)
+{
+ struct TALER_EXCHANGE_NonceKey nks[GNUNET_NZL (rd->num_fresh_denom_pubs)];
+ unsigned int nks_off = 0;
+ struct TALER_EXCHANGE_MeltHandle *mh;
+
+ if (0 == rd->num_fresh_denom_pubs)
+ {
+ 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->no_blinding_seed = true;
+ mh->melt_blinding_values =
+ GNUNET_new_array (rd->num_fresh_denom_pubs,
+ struct TALER_ExchangeBlindingValues);
+ for (unsigned int i = 0; i<rd->num_fresh_denom_pubs; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk =
+ &rd->fresh_denom_pubs[i];
+
+ switch (fresh_pk->key.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ GNUNET_break (0);
+ GNUNET_free (mh->melt_blinding_values);
+ GNUNET_free (mh);
+ return NULL;
+ case GNUNET_CRYPTO_BSA_RSA:
+ TALER_denom_ewv_copy (&mh->melt_blinding_values[i],
+ TALER_denom_ewv_rsa_singleton ());
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ nks[nks_off].pk = fresh_pk;
+ nks[nks_off].cnc_num = i;
+ nks_off++;
+ break;
+ }
+ }
+ mh->keys = TALER_EXCHANGE_keys_incref (keys);
+ if (0 != nks_off)
+ {
+ mh->no_blinding_seed = false;
+ TALER_cs_refresh_seed_to_blinding_seed (
+ rms,
+ &mh->md.melted_coin.coin_priv,
+ &mh->blinding_seed);
+ mh->bpr = TALER_EXCHANGE_blinding_prepare_for_melt (ctx,
+ url,
+ &mh->blinding_seed,
+ nks_off,
+ nks,
+ &blinding_prepare_cb,
+ mh);
+ if (NULL == mh->bpr)
+ {
+ 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)
+{
+ for (unsigned int i = 0; i<mh->rd->num_fresh_denom_pubs; i++)
+ TALER_denom_ewv_free (&mh->melt_blinding_values[i]);
+ if (NULL != mh->job)
+ {
+ GNUNET_CURL_job_cancel (mh->job);
+ mh->job = NULL;
+ }
+ if (NULL != mh->bpr)
+ {
+ TALER_EXCHANGE_blinding_prepare_cancel (mh->bpr);
+ mh->bpr = NULL;
+ }
+ TALER_EXCHANGE_free_melt_data (&mh->md); /* does not free 'md' itself */
+ GNUNET_free (mh->melt_blinding_values);
+ 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_post-purses-PURSE_PUB-create.c b/src/lib/exchange_api_post-purses-PURSE_PUB-create.c
@@ -0,0 +1,656 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-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_post-purses-PURSE_PUB-create.c
+ * @brief Implementation of the client to create a purse with
+ * an initial set of deposits (and a contract)
+ * @author Christian Grothoff
+ */
+#include "taler/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/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "exchange_api_common.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * Information we track per deposited coin.
+ */
+struct Deposit
+{
+ /**
+ * Coin's public key.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Signature made with the coin.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
+ * Coin's denomination.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * Age restriction hash for the coin.
+ */
+ struct TALER_AgeCommitmentHashP ahac;
+
+ /**
+ * How much did we say the coin contributed.
+ */
+ struct TALER_Amount contribution;
+};
+
+
+/**
+ * @brief A purse create with deposit handle
+ */
+struct TALER_EXCHANGE_PurseCreateDepositHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * The base URL of the exchange.
+ */
+ char *exchange_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_PurseCreateDepositCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Expected value in the purse after fees.
+ */
+ struct TALER_Amount purse_value_after_fees;
+
+ /**
+ * Our encrypted contract (if we had any).
+ */
+ struct TALER_EncryptedContract econtract;
+
+ /**
+ * Public key of the merge capability.
+ */
+ struct TALER_PurseMergePublicKeyP merge_pub;
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Signature with the purse key on the request.
+ */
+ struct TALER_PurseContractSignatureP purse_sig;
+
+ /**
+ * Hash over the purse's contract terms.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * When does the purse expire.
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * Array of @e num_deposit deposits.
+ */
+ struct Deposit *deposits;
+
+ /**
+ * How many deposits did we make?
+ */
+ unsigned int num_deposits;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /deposit request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_DepositHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_purse_create_deposit_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_PurseCreateDepositHandle *pch = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_PurseCreateDepositResponse dr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+ const struct TALER_EXCHANGE_Keys *keys = pch->keys;
+
+ pch->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct GNUNET_TIME_Timestamp etime;
+ struct TALER_Amount total_deposited;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ 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_timestamp ("exchange_timestamp",
+ &etime),
+ TALER_JSON_spec_amount ("total_deposited",
+ pch->purse_value_after_fees.currency,
+ &total_deposited),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (keys,
+ &exchange_pub))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_purse_created_verify (
+ etime,
+ pch->purse_expiration,
+ &pch->purse_value_after_fees,
+ &total_deposited,
+ &pch->purse_pub,
+ &pch->h_contract_terms,
+ &exchange_pub,
+ &exchange_sig))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID;
+ break;
+ }
+ }
+ 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 */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* 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 */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ {
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ switch (dr.hr.ec)
+ {
+ case TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA:
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_check_purse_create_conflict_ (
+ &pch->purse_sig,
+ &pch->purse_pub,
+ j))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ break;
+ case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
+ /* Nothing to check anymore here, proof needs to be
+ checked in the GET /coins/$COIN_PUB handler */
+ break;
+ case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
+ // FIXME #7267: write check (add to exchange_api_common! */
+ break;
+ case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA:
+ {
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_AgeCommitmentHashP phac;
+ bool found = false;
+
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_check_purse_coin_conflict_ (
+ &pch->purse_pub,
+ pch->exchange_url,
+ j,
+ &h_denom_pub,
+ &phac,
+ &coin_pub,
+ &coin_sig))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ for (unsigned int i = 0; i<pch->num_deposits; i++)
+ {
+ struct Deposit *deposit = &pch->deposits[i];
+
+ if (0 !=
+ GNUNET_memcmp (&coin_pub,
+ &deposit->coin_pub))
+ continue;
+ if (0 !=
+ GNUNET_memcmp (&deposit->h_denom_pub,
+ &h_denom_pub))
+ {
+ found = true;
+ break;
+ }
+ if (0 !=
+ GNUNET_memcmp (&deposit->ahac,
+ &phac))
+ {
+ found = true;
+ break;
+ }
+ if (0 ==
+ GNUNET_memcmp (&coin_sig,
+ &deposit->coin_sig))
+ {
+ GNUNET_break_op (0);
+ continue;
+ }
+ found = true;
+ break;
+ }
+ if (! found)
+ {
+ /* conflict is for a different coin! */
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA:
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_check_purse_econtract_conflict_ (
+ &pch->econtract.econtract_sig,
+ &pch->purse_pub,
+ j))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected error code %d for conflcting deposit\n",
+ dr.hr.ec);
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ }
+ }
+ 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 */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.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 */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange deposit\n",
+ (unsigned int) response_code,
+ dr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ pch->cb (pch->cb_cls,
+ &dr);
+ TALER_EXCHANGE_purse_create_with_deposit_cancel (pch);
+}
+
+
+struct TALER_EXCHANGE_PurseCreateDepositHandle *
+TALER_EXCHANGE_purse_create_with_deposit (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_PurseContractPrivateKeyP *purse_priv,
+ const struct TALER_PurseMergePrivateKeyP *merge_priv,
+ const struct TALER_ContractDiffiePrivateP *contract_priv,
+ const json_t *contract_terms,
+ unsigned int num_deposits,
+ const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits],
+ bool upload_contract,
+ TALER_EXCHANGE_PurseCreateDepositCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_PurseCreateDepositHandle *pch;
+ json_t *create_obj;
+ json_t *deposit_arr;
+ CURL *eh;
+ char arg_str[sizeof (pch->purse_pub) * 2 + 32];
+ uint32_t min_age = 0;
+
+ pch = GNUNET_new (struct TALER_EXCHANGE_PurseCreateDepositHandle);
+ pch->cb = cb;
+ pch->cb_cls = cb_cls;
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("pay_deadline",
+ &pch->purse_expiration),
+ TALER_JSON_spec_amount_any ("amount",
+ &pch->purse_value_after_fees),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("minimum_age",
+ &min_age),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (contract_terms,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ }
+ if (GNUNET_OK !=
+ TALER_JSON_contract_hash (contract_terms,
+ &pch->h_contract_terms))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv,
+ &pch->purse_pub.eddsa_pub);
+ {
+ char pub_str[sizeof (pch->purse_pub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &pch->purse_pub,
+ sizeof (pch->purse_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "purses/%s/create",
+ pub_str);
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
+ &pch->merge_pub.eddsa_pub);
+ pch->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == pch->url)
+ {
+ GNUNET_break (0);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ pch->num_deposits = num_deposits;
+ pch->deposits = GNUNET_new_array (num_deposits,
+ struct Deposit);
+ deposit_arr = json_array ();
+ GNUNET_assert (NULL != deposit_arr);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing with URL `%s'\n",
+ url);
+ for (unsigned int i = 0; i<num_deposits; i++)
+ {
+ const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i];
+ const struct TALER_AgeCommitmentProof *acp = deposit->age_commitment_proof;
+ struct Deposit *d = &pch->deposits[i];
+ json_t *jdeposit;
+ struct TALER_AgeCommitmentHashP *aghp = NULL;
+ struct TALER_AgeAttestationP attest;
+ struct TALER_AgeAttestationP *attestp = NULL;
+
+ if (NULL != acp)
+ {
+ TALER_age_commitment_hash (&acp->commitment,
+ &d->ahac);
+ aghp = &d->ahac;
+ if (GNUNET_OK !=
+ TALER_age_commitment_attest (acp,
+ min_age,
+ &attest))
+ {
+ GNUNET_break (0);
+ GNUNET_array_grow (pch->deposits,
+ pch->num_deposits,
+ 0);
+ GNUNET_free (pch->url);
+ json_decref (deposit_arr);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ }
+ d->contribution = deposit->amount;
+ d->h_denom_pub = deposit->h_denom_pub;
+ GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv,
+ &d->coin_pub.eddsa_pub);
+ TALER_wallet_purse_deposit_sign (
+ url,
+ &pch->purse_pub,
+ &deposit->amount,
+ &d->h_denom_pub,
+ &d->ahac,
+ &deposit->coin_priv,
+ &d->coin_sig);
+ jdeposit = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ aghp)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("age_attestation",
+ attestp)),
+ TALER_JSON_pack_amount ("amount",
+ &deposit->amount),
+ GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+ &deposit->h_denom_pub),
+ TALER_JSON_pack_denom_sig ("ub_sig",
+ &deposit->denom_sig),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &d->coin_sig),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &d->coin_pub));
+ GNUNET_assert (0 ==
+ json_array_append_new (deposit_arr,
+ jdeposit));
+ }
+ TALER_wallet_purse_create_sign (pch->purse_expiration,
+ &pch->h_contract_terms,
+ &pch->merge_pub,
+ min_age,
+ &pch->purse_value_after_fees,
+ purse_priv,
+ &pch->purse_sig);
+ if (upload_contract)
+ {
+ TALER_CRYPTO_contract_encrypt_for_merge (&pch->purse_pub,
+ contract_priv,
+ merge_priv,
+ contract_terms,
+ &pch->econtract.econtract,
+ &pch->econtract.econtract_size);
+ GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv,
+ &pch->econtract.contract_pub.ecdhe_pub);
+ TALER_wallet_econtract_upload_sign (pch->econtract.econtract,
+ pch->econtract.econtract_size,
+ &pch->econtract.contract_pub,
+ purse_priv,
+ &pch->econtract.econtract_sig);
+ }
+ create_obj = GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("amount",
+ &pch->purse_value_after_fees),
+ GNUNET_JSON_pack_uint64 ("min_age",
+ min_age),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_econtract ("econtract",
+ upload_contract
+ ? &pch->econtract
+ : NULL)),
+ GNUNET_JSON_pack_data_auto ("purse_sig",
+ &pch->purse_sig),
+ GNUNET_JSON_pack_data_auto ("merge_pub",
+ &pch->merge_pub),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &pch->h_contract_terms),
+ GNUNET_JSON_pack_timestamp ("purse_expiration",
+ pch->purse_expiration),
+ GNUNET_JSON_pack_array_steal ("deposits",
+ deposit_arr));
+ GNUNET_assert (NULL != create_obj);
+ eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&pch->ctx,
+ eh,
+ create_obj)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (create_obj);
+ GNUNET_free (pch->econtract.econtract);
+ GNUNET_array_grow (pch->deposits,
+ pch->num_deposits,
+ 0);
+ GNUNET_free (pch->url);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ json_decref (create_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for purse create with deposit: `%s'\n",
+ pch->url);
+ pch->keys = TALER_EXCHANGE_keys_incref (keys);
+ pch->exchange_url = GNUNET_strdup (url);
+ pch->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ pch->ctx.headers,
+ &handle_purse_create_deposit_finished,
+ pch);
+ return pch;
+}
+
+
+void
+TALER_EXCHANGE_purse_create_with_deposit_cancel (
+ struct TALER_EXCHANGE_PurseCreateDepositHandle *pch)
+{
+ if (NULL != pch->job)
+ {
+ GNUNET_CURL_job_cancel (pch->job);
+ pch->job = NULL;
+ }
+ GNUNET_free (pch->econtract.econtract);
+ GNUNET_free (pch->exchange_url);
+ GNUNET_free (pch->url);
+ GNUNET_array_grow (pch->deposits,
+ pch->num_deposits,
+ 0);
+ TALER_EXCHANGE_keys_decref (pch->keys);
+ TALER_curl_easy_post_finished (&pch->ctx);
+ GNUNET_free (pch);
+}
+
+
+/* end of exchange_api_purse_create_with_deposit.c */
diff --git a/src/lib/exchange_api_post-purses-PURSE_PUB-deposit.c b/src/lib/exchange_api_post-purses-PURSE_PUB-deposit.c
@@ -0,0 +1,520 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-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_post-purses-PURSE_PUB-deposit.c
+ * @brief Implementation of the client to create a purse with
+ * an initial set of deposits (and a contract)
+ * @author Christian Grothoff
+ */
+#include "taler/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/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * Information we track per coin.
+ */
+struct Coin
+{
+ /**
+ * Coin's public key.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Signature made with the coin.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
+ * Coin's denomination.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * Age restriction hash for the coin.
+ */
+ struct TALER_AgeCommitmentHashP ahac;
+
+ /**
+ * How much did we say the coin contributed.
+ */
+ struct TALER_Amount contribution;
+};
+
+
+/**
+ * @brief A purse create with deposit handle
+ */
+struct TALER_EXCHANGE_PurseDepositHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * The base url of the exchange we are talking to.
+ */
+ char *base_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_PurseDepositCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Array of @e num_deposits coins we are depositing.
+ */
+ struct Coin *coins;
+
+ /**
+ * Number of coins we are depositing.
+ */
+ unsigned int num_deposits;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /purses/$PID/deposit request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_PurseDepositHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_purse_deposit_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_PurseDepositHandle *pch = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_PurseDepositResponse dr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+ const struct TALER_EXCHANGE_Keys *keys = pch->keys;
+
+ pch->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct GNUNET_TIME_Timestamp etime;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ 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_fixed_auto ("h_contract_terms",
+ &dr.details.ok.h_contract_terms),
+ GNUNET_JSON_spec_timestamp ("exchange_timestamp",
+ &etime),
+ GNUNET_JSON_spec_timestamp ("purse_expiration",
+ &dr.details.ok.purse_expiration),
+ TALER_JSON_spec_amount ("total_deposited",
+ keys->currency,
+ &dr.details.ok.total_deposited),
+ TALER_JSON_spec_amount ("purse_value_after_fees",
+ keys->currency,
+ &dr.details.ok.purse_value_after_fees),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (keys,
+ &exchange_pub))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_PURSE_DEPOSIT_EXCHANGE_SIGNATURE_INVALID;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_purse_created_verify (
+ etime,
+ dr.details.ok.purse_expiration,
+ &dr.details.ok.purse_value_after_fees,
+ &dr.details.ok.total_deposited,
+ &pch->purse_pub,
+ &dr.details.ok.h_contract_terms,
+ &exchange_pub,
+ &exchange_sig))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_PURSE_DEPOSIT_EXCHANGE_SIGNATURE_INVALID;
+ break;
+ }
+ }
+ 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 */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ /* 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 */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ switch (dr.hr.ec)
+ {
+ case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA:
+ {
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_AgeCommitmentHashP phac;
+ bool found = false;
+
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_check_purse_coin_conflict_ (
+ &pch->purse_pub,
+ pch->base_url,
+ j,
+ &h_denom_pub,
+ &phac,
+ &coin_pub,
+ &coin_sig))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ for (unsigned int i = 0; i<pch->num_deposits; i++)
+ {
+ struct Coin *coin = &pch->coins[i];
+ if (0 != GNUNET_memcmp (&coin_pub,
+ &coin->coin_pub))
+ continue;
+ if (0 !=
+ GNUNET_memcmp (&coin->h_denom_pub,
+ &h_denom_pub))
+ {
+ found = true;
+ break;
+ }
+ if (0 !=
+ GNUNET_memcmp (&coin->ahac,
+ &phac))
+ {
+ found = true;
+ break;
+ }
+ if (0 == GNUNET_memcmp (&coin_sig,
+ &coin->coin_sig))
+ {
+ /* identical signature => not a conflict */
+ continue;
+ }
+ found = true;
+ break;
+ }
+ if (! found)
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ /* meta data conflict is real! */
+ break;
+ }
+ case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
+ /* Nothing to check anymore here, proof needs to be
+ checked in the GET /coins/$COIN_PUB handler */
+ break;
+ case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
+ break;
+ default:
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ } /* ec switch */
+ break;
+ case MHD_HTTP_GONE:
+ /* could happen if denomination was revoked or purse expired */
+ /* Note: one might want to check /keys for revocation
+ signature here, alas tricky in case our /keys
+ is outdated => left to clients */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dr.hr.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 */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange deposit\n",
+ (unsigned int) response_code,
+ dr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ if (TALER_EC_NONE == dr.hr.ec)
+ dr.hr.hint = NULL;
+ else
+ dr.hr.hint = TALER_ErrorCode_get_hint (dr.hr.ec);
+ pch->cb (pch->cb_cls,
+ &dr);
+ TALER_EXCHANGE_purse_deposit_cancel (pch);
+}
+
+
+struct TALER_EXCHANGE_PurseDepositHandle *
+TALER_EXCHANGE_purse_deposit (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *purse_exchange_url,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ uint8_t min_age,
+ unsigned int num_deposits,
+ const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits],
+ TALER_EXCHANGE_PurseDepositCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_PurseDepositHandle *pch;
+ json_t *create_obj;
+ json_t *deposit_arr;
+ CURL *eh;
+ char arg_str[sizeof (pch->purse_pub) * 2 + 32];
+
+ // FIXME: use purse_exchange_url for wad transfers (#7271)
+ (void) purse_exchange_url;
+ if (0 == num_deposits)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ pch = GNUNET_new (struct TALER_EXCHANGE_PurseDepositHandle);
+ pch->purse_pub = *purse_pub;
+ pch->cb = cb;
+ pch->cb_cls = cb_cls;
+ {
+ char pub_str[sizeof (pch->purse_pub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &pch->purse_pub,
+ sizeof (pch->purse_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "purses/%s/deposit",
+ pub_str);
+ }
+ pch->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == pch->url)
+ {
+ GNUNET_break (0);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ deposit_arr = json_array ();
+ GNUNET_assert (NULL != deposit_arr);
+ pch->base_url = GNUNET_strdup (url);
+ pch->num_deposits = num_deposits;
+ pch->coins = GNUNET_new_array (num_deposits,
+ struct Coin);
+ for (unsigned int i = 0; i<num_deposits; i++)
+ {
+ const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i];
+ const struct TALER_AgeCommitmentProof *acp = deposit->age_commitment_proof;
+ struct Coin *coin = &pch->coins[i];
+ json_t *jdeposit;
+ struct TALER_AgeCommitmentHashP *achp = NULL;
+ struct TALER_AgeAttestationP attest;
+ struct TALER_AgeAttestationP *attestp = NULL;
+
+ if (NULL != acp)
+ {
+ TALER_age_commitment_hash (&acp->commitment,
+ &coin->ahac);
+ achp = &coin->ahac;
+ if (GNUNET_OK !=
+ TALER_age_commitment_attest (acp,
+ min_age,
+ &attest))
+ {
+ GNUNET_break (0);
+ json_decref (deposit_arr);
+ GNUNET_free (pch->base_url);
+ GNUNET_free (pch->coins);
+ GNUNET_free (pch->url);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ attestp = &attest;
+ }
+ GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv,
+ &coin->coin_pub.eddsa_pub);
+ coin->h_denom_pub = deposit->h_denom_pub;
+ coin->contribution = deposit->amount;
+ TALER_wallet_purse_deposit_sign (
+ pch->base_url,
+ &pch->purse_pub,
+ &deposit->amount,
+ &coin->h_denom_pub,
+ &coin->ahac,
+ &deposit->coin_priv,
+ &coin->coin_sig);
+ jdeposit = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ achp)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("age_attestation",
+ attestp)),
+ TALER_JSON_pack_amount ("amount",
+ &deposit->amount),
+ GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+ &deposit->h_denom_pub),
+ TALER_JSON_pack_denom_sig ("ub_sig",
+ &deposit->denom_sig),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &coin->coin_pub),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &coin->coin_sig));
+ GNUNET_assert (0 ==
+ json_array_append_new (deposit_arr,
+ jdeposit));
+ }
+ create_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("deposits",
+ deposit_arr));
+ GNUNET_assert (NULL != create_obj);
+ eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&pch->ctx,
+ eh,
+ create_obj)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (create_obj);
+ GNUNET_free (pch->base_url);
+ GNUNET_free (pch->url);
+ GNUNET_free (pch->coins);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ json_decref (create_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for purse deposit: `%s'\n",
+ pch->url);
+ pch->keys = TALER_EXCHANGE_keys_incref (keys);
+ pch->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ pch->ctx.headers,
+ &handle_purse_deposit_finished,
+ pch);
+ return pch;
+}
+
+
+void
+TALER_EXCHANGE_purse_deposit_cancel (
+ struct TALER_EXCHANGE_PurseDepositHandle *pch)
+{
+ if (NULL != pch->job)
+ {
+ GNUNET_CURL_job_cancel (pch->job);
+ pch->job = NULL;
+ }
+ GNUNET_free (pch->base_url);
+ GNUNET_free (pch->url);
+ GNUNET_free (pch->coins);
+ TALER_EXCHANGE_keys_decref (pch->keys);
+ TALER_curl_easy_post_finished (&pch->ctx);
+ GNUNET_free (pch);
+}
+
+
+/* end of exchange_api_purse_deposit.c */
diff --git a/src/lib/exchange_api_post-purses-PURSE_PUB-merge.c b/src/lib/exchange_api_post-purses-PURSE_PUB-merge.c
@@ -0,0 +1,454 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-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_post-purses-PURSE_PUB-merge.c
+ * @brief Implementation of the client to merge a purse
+ * into an account
+ * @author Christian Grothoff
+ */
+#include "taler/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/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "exchange_api_common.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A purse merge with deposit handle
+ */
+struct TALER_EXCHANGE_AccountMergeHandle
+{
+
+ /**
+ * 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_AccountMergeCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Base URL of the provider hosting the @e reserve_pub.
+ */
+ char *provider_url;
+
+ /**
+ * Signature for our operation.
+ */
+ struct TALER_PurseMergeSignatureP merge_sig;
+
+ /**
+ * Expected value in the purse after fees.
+ */
+ struct TALER_Amount purse_value_after_fees;
+
+ /**
+ * Public key of the reserve public key.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Hash over the purse's contrac terms.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * When does the purse expire.
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * Our merge key.
+ */
+ struct TALER_PurseMergePrivateKeyP merge_priv;
+
+ /**
+ * Reserve signature affirming the merge.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /purse/$PID/merge request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_AccountMergeHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_purse_merge_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_AccountMergeHandle *pch = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_AccountMergeResponse dr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code,
+ .reserve_sig = &pch->reserve_sig
+ };
+
+ pch->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct TALER_Amount total_deposited;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &dr.details.ok.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &dr.details.ok.exchange_pub),
+ GNUNET_JSON_spec_timestamp ("exchange_timestamp",
+ &dr.details.ok.etime),
+ TALER_JSON_spec_amount ("merge_amount",
+ pch->purse_value_after_fees.currency,
+ &total_deposited),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (pch->keys,
+ &dr.details.ok.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_purse_merged_verify (
+ dr.details.ok.etime,
+ pch->purse_expiration,
+ &pch->purse_value_after_fees,
+ &pch->purse_pub,
+ &pch->h_contract_terms,
+ &pch->reserve_pub,
+ pch->provider_url,
+ &dr.details.ok.exchange_pub,
+ &dr.details.ok.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID;
+ break;
+ }
+ }
+ 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 */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_PAYMENT_REQUIRED:
+ /* purse was not (yet) full */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* 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 */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ {
+ struct TALER_PurseMergePublicKeyP merge_pub;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&pch->merge_priv.eddsa_priv,
+ &merge_pub.eddsa_pub);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_check_purse_merge_conflict_ (
+ &pch->merge_sig,
+ &merge_pub,
+ &pch->purse_pub,
+ pch->provider_url,
+ j))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ break;
+ }
+ 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 */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &dr.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);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.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 */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange deposit\n",
+ (unsigned int) response_code,
+ dr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ pch->cb (pch->cb_cls,
+ &dr);
+ TALER_EXCHANGE_account_merge_cancel (pch);
+}
+
+
+struct TALER_EXCHANGE_AccountMergeHandle *
+TALER_EXCHANGE_account_merge (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *reserve_exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergePrivateKeyP *merge_priv,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ uint8_t min_age,
+ const struct TALER_Amount *purse_value_after_fees,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ TALER_EXCHANGE_AccountMergeCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_AccountMergeHandle *pch;
+ json_t *merge_obj;
+ CURL *eh;
+ char arg_str[sizeof (pch->purse_pub) * 2 + 32];
+ struct TALER_NormalizedPayto reserve_url;
+
+ pch = GNUNET_new (struct TALER_EXCHANGE_AccountMergeHandle);
+ pch->merge_priv = *merge_priv;
+ pch->cb = cb;
+ pch->cb_cls = cb_cls;
+ pch->purse_pub = *purse_pub;
+ pch->h_contract_terms = *h_contract_terms;
+ pch->purse_expiration = purse_expiration;
+ pch->purse_value_after_fees = *purse_value_after_fees;
+ if (NULL == reserve_exchange_url)
+ pch->provider_url = GNUNET_strdup (url);
+ else
+ pch->provider_url = GNUNET_strdup (reserve_exchange_url);
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &pch->reserve_pub.eddsa_pub);
+
+ {
+ char pub_str[sizeof (*purse_pub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ purse_pub,
+ sizeof (*purse_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "purses/%s/merge",
+ pub_str);
+ }
+ reserve_url = TALER_reserve_make_payto (pch->provider_url,
+ &pch->reserve_pub);
+ if (NULL == reserve_url.normalized_payto)
+ {
+ GNUNET_break (0);
+ GNUNET_free (pch->provider_url);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ pch->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == pch->url)
+ {
+ GNUNET_break (0);
+ GNUNET_free (reserve_url.normalized_payto);
+ GNUNET_free (pch->provider_url);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ TALER_wallet_purse_merge_sign (reserve_url,
+ merge_timestamp,
+ purse_pub,
+ merge_priv,
+ &pch->merge_sig);
+ {
+ struct TALER_Amount zero_purse_fee;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (purse_value_after_fees->currency,
+ &zero_purse_fee));
+ TALER_wallet_account_merge_sign (merge_timestamp,
+ purse_pub,
+ purse_expiration,
+ h_contract_terms,
+ purse_value_after_fees,
+ &zero_purse_fee,
+ min_age,
+ TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
+ reserve_priv,
+ &pch->reserve_sig);
+ }
+ merge_obj = GNUNET_JSON_PACK (
+ TALER_JSON_pack_normalized_payto ("payto_uri",
+ reserve_url),
+ GNUNET_JSON_pack_data_auto ("merge_sig",
+ &pch->merge_sig),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &pch->reserve_sig),
+ GNUNET_JSON_pack_timestamp ("merge_timestamp",
+ merge_timestamp));
+ GNUNET_assert (NULL != merge_obj);
+ GNUNET_free (reserve_url.normalized_payto);
+ eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&pch->ctx,
+ eh,
+ merge_obj)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (merge_obj);
+ GNUNET_free (pch->provider_url);
+ GNUNET_free (pch->url);
+ GNUNET_free (pch);
+ return NULL;
+ }
+ json_decref (merge_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for purse merge: `%s'\n",
+ pch->url);
+ pch->keys = TALER_EXCHANGE_keys_incref (keys);
+ pch->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ pch->ctx.headers,
+ &handle_purse_merge_finished,
+ pch);
+ return pch;
+}
+
+
+void
+TALER_EXCHANGE_account_merge_cancel (
+ struct TALER_EXCHANGE_AccountMergeHandle *pch)
+{
+ if (NULL != pch->job)
+ {
+ GNUNET_CURL_job_cancel (pch->job);
+ pch->job = NULL;
+ }
+ GNUNET_free (pch->url);
+ GNUNET_free (pch->provider_url);
+ TALER_curl_easy_post_finished (&pch->ctx);
+ TALER_EXCHANGE_keys_decref (pch->keys);
+ GNUNET_free (pch);
+}
+
+
+/* end of exchange_api_purse_merge.c */
diff --git a/src/lib/exchange_api_post-recoup-refresh.c b/src/lib/exchange_api_post-recoup-refresh.c
@@ -0,0 +1,361 @@
+/*
+ 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_post-recoup-refresh.c
+ * @brief Implementation of the /recoup-refresh request of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#include "taler/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/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler/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);
+}
+
+
+#pragma message "TODO[oec]: rewrite TALER_EXCHANGE_RecoupRefreshHandle"
+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_ExchangeBlindingValues *blinding_values,
+ const struct TALER_PublicRefreshMasterSeedP *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 GNUNET_CRYPTO_BlindingSecretP 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,
+ blinding_values,
+ &coin_priv);
+ TALER_planchet_blinding_secret_create (ps,
+ blinding_values,
+ &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_blinding_values ("ewv",
+ blinding_values),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &ph->coin_sig),
+ GNUNET_JSON_pack_data_auto ("coin_blind_key_secret",
+ &bks));
+
+ switch (denom_sig->unblinded_sig->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ json_decref (recoup_obj);
+ GNUNET_break (0);
+ GNUNET_free (ph);
+ return NULL;
+ case GNUNET_CRYPTO_BSA_RSA:
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ {
+
+ }
+ break;
+ }
+
+ {
+ 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_post-recoup-withdraw.c b/src/lib/exchange_api_post-recoup-withdraw.c
@@ -0,0 +1,382 @@
+/*
+ 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_post-recoup-withdraw.c
+ * @brief Implementation of the /recoup request of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#include "taler/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/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler/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_ExchangeBlindingValues *blinding_values,
+ const struct TALER_PlanchetMasterSecretP *ps,
+ const struct TALER_HashBlindedPlanchetsP *h_planchets,
+ 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 GNUNET_CRYPTO_BlindingSecretP bks;
+
+ ph = GNUNET_new (struct TALER_EXCHANGE_RecoupHandle);
+ TALER_planchet_setup_coin_priv (ps,
+ blinding_values,
+ &coin_priv);
+ TALER_planchet_blinding_secret_create (ps,
+ blinding_values,
+ &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_blinding_values ("ewv",
+ blinding_values),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &ph->coin_sig),
+ GNUNET_JSON_pack_data_auto ("h_planchets",
+ h_planchets),
+ GNUNET_JSON_pack_data_auto ("coin_blind_key_secret",
+ &bks));
+ switch (denom_sig->unblinded_sig->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ json_decref (recoup_obj);
+ GNUNET_break (0);
+ GNUNET_free (ph);
+ return NULL;
+ case GNUNET_CRYPTO_BSA_RSA:
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ {
+ union GNUNET_CRYPTO_BlindSessionNonce 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.cs_nonce);
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (recoup_obj,
+ "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_post-reserves-RESERVE_PUB-close.c b/src/lib/exchange_api_post-reserves-RESERVE_PUB-close.c
@@ -0,0 +1,373 @@
+/*
+ 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_post-reserves-RESERVE_PUB-close.c
+ * @brief Implementation of the POST /reserves/$RESERVE_PUB/close requests
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP close codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /reserves/$RID/close Handle
+ */
+struct TALER_EXCHANGE_ReservesCloseHandle
+{
+
+ /**
+ * 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;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ReservesCloseCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Public key of the reserve we are querying.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Our signature.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * When did we make the request.
+ */
+ struct GNUNET_TIME_Timestamp ts;
+
+};
+
+
+/**
+ * We received an #MHD_HTTP_OK close code. Handle the JSON
+ * response.
+ *
+ * @param rch handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_close_ok (struct TALER_EXCHANGE_ReservesCloseHandle *rch,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_ReserveCloseResult rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_OK,
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("wire_amount",
+ &rs.details.ok.wire_amount),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rch->cb (rch->cb_cls,
+ &rs);
+ rch->cb = NULL;
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS close code. Handle the JSON
+ * response.
+ *
+ * @param rch handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_close_kyc (struct TALER_EXCHANGE_ReservesCloseHandle *rch,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_ReserveCloseResult rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "h_payto",
+ &rs.details.unavailable_for_legal_reasons.h_payto),
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &rs.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);
+ return GNUNET_SYSERR;
+ }
+ rch->cb (rch->cb_cls,
+ &rs);
+ rch->cb = NULL;
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RID/close request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReservesCloseHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserves_close_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ReservesCloseHandle *rch = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_ReserveCloseResult rs = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ rch->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ handle_reserves_close_ok (rch,
+ j))
+ {
+ GNUNET_break_op (0);
+ rs.hr.http_status = 0;
+ rs.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 */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.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 */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* Insufficient balance to inquire for reserve close */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ if (GNUNET_OK !=
+ handle_reserves_close_kyc (rch,
+ j))
+ {
+ GNUNET_break_op (0);
+ rs.hr.http_status = 0;
+ rs.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 */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for reserves close\n",
+ (unsigned int) response_code,
+ (int) rs.hr.ec);
+ break;
+ }
+ if (NULL != rch->cb)
+ {
+ rch->cb (rch->cb_cls,
+ &rs);
+ rch->cb = NULL;
+ }
+ TALER_EXCHANGE_reserves_close_cancel (rch);
+}
+
+
+struct TALER_EXCHANGE_ReservesCloseHandle *
+TALER_EXCHANGE_reserves_close (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_FullPayto target_payto_uri,
+ TALER_EXCHANGE_ReservesCloseCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ReservesCloseHandle *rch;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+ struct TALER_FullPaytoHashP h_payto;
+
+ rch = GNUNET_new (struct TALER_EXCHANGE_ReservesCloseHandle);
+ rch->cb = cb;
+ rch->cb_cls = cb_cls;
+ rch->ts = GNUNET_TIME_timestamp_get ();
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &rch->reserve_pub.eddsa_pub);
+ {
+ char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &rch->reserve_pub,
+ sizeof (rch->reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s/close",
+ pub_str);
+ }
+ rch->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == rch->url)
+ {
+ GNUNET_free (rch);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (rch->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (rch->url);
+ GNUNET_free (rch);
+ return NULL;
+ }
+ if (NULL != target_payto_uri.full_payto)
+ TALER_full_payto_hash (target_payto_uri,
+ &h_payto);
+ TALER_wallet_reserve_close_sign (rch->ts,
+ (NULL != target_payto_uri.full_payto)
+ ? &h_payto
+ : NULL,
+ reserve_priv,
+ &rch->reserve_sig);
+ {
+ json_t *close_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_full_payto ("payto_uri",
+ target_payto_uri)),
+ GNUNET_JSON_pack_timestamp ("request_timestamp",
+ rch->ts),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &rch->reserve_sig));
+
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&rch->post_ctx,
+ eh,
+ close_obj))
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ json_decref (close_obj);
+ GNUNET_free (rch->url);
+ GNUNET_free (rch);
+ return NULL;
+ }
+ json_decref (close_obj);
+ }
+ rch->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ rch->post_ctx.headers,
+ &handle_reserves_close_finished,
+ rch);
+ return rch;
+}
+
+
+void
+TALER_EXCHANGE_reserves_close_cancel (
+ struct TALER_EXCHANGE_ReservesCloseHandle *rch)
+{
+ if (NULL != rch->job)
+ {
+ GNUNET_CURL_job_cancel (rch->job);
+ rch->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&rch->post_ctx);
+ GNUNET_free (rch->url);
+ GNUNET_free (rch);
+}
+
+
+/* end of exchange_api_reserves_close.c */
diff --git a/src/lib/exchange_api_post-reserves-RESERVE_PUB-open.c b/src/lib/exchange_api_post-reserves-RESERVE_PUB-open.c
@@ -0,0 +1,567 @@
+/*
+ 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_post-reserves-RESERVE_PUB-open.c
+ * @brief Implementation of the POST /reserves/$RESERVE_PUB/open requests
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP open codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_json_lib.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * Information we keep per coin to validate the reply.
+ */
+struct CoinData
+{
+ /**
+ * Public key of the coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Signature by the coin.
+ */
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
+ * The hash of the denomination's public key
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * How much did this coin contribute.
+ */
+ struct TALER_Amount contribution;
+};
+
+
+/**
+ * @brief A /reserves/$RID/open Handle
+ */
+struct TALER_EXCHANGE_ReservesOpenHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * 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;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ReservesOpenCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Information we keep per coin to validate the reply.
+ */
+ struct CoinData *coins;
+
+ /**
+ * Length of the @e coins array.
+ */
+ unsigned int num_coins;
+
+ /**
+ * Public key of the reserve we are querying.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Our signature.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * When did we make the request.
+ */
+ struct GNUNET_TIME_Timestamp ts;
+
+};
+
+
+/**
+ * We received an #MHD_HTTP_OK open code. Handle the JSON
+ * response.
+ *
+ * @param roh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_open_ok (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_ReserveOpenResult rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_OK,
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("open_cost",
+ &rs.details.ok.open_cost),
+ GNUNET_JSON_spec_timestamp ("reserve_expiration",
+ &rs.details.ok.expiration_time),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ roh->cb (roh->cb_cls,
+ &rs);
+ roh->cb = NULL;
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * We received an #MHD_HTTP_PAYMENT_REQUIRED open code. Handle the JSON
+ * response.
+ *
+ * @param roh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_open_pr (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_ReserveOpenResult rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_PAYMENT_REQUIRED,
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("open_cost",
+ &rs.details.payment_required.open_cost),
+ GNUNET_JSON_spec_timestamp ("reserve_expiration",
+ &rs.details.payment_required.expiration_time),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ roh->cb (roh->cb_cls,
+ &rs);
+ roh->cb = NULL;
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS open code. Handle the JSON
+ * response.
+ *
+ * @param roh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_open_kyc (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_ReserveOpenResult rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "h_payto",
+ &rs.details.unavailable_for_legal_reasons.h_payto),
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &rs.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);
+ return GNUNET_SYSERR;
+ }
+ roh->cb (roh->cb_cls,
+ &rs);
+ roh->cb = NULL;
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RID/open request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReservesOpenHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserves_open_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ReservesOpenHandle *roh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_ReserveOpenResult rs = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ roh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ handle_reserves_open_ok (roh,
+ j))
+ {
+ GNUNET_break_op (0);
+ rs.hr.http_status = 0;
+ rs.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 */
+ GNUNET_break (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_PAYMENT_REQUIRED:
+ if (GNUNET_OK !=
+ handle_reserves_open_pr (roh,
+ j))
+ {
+ GNUNET_break_op (0);
+ rs.hr.http_status = 0;
+ rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ }
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.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 */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ {
+ const struct CoinData *cd = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &rs.details.conflict.coin_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ rs.hr.http_status = 0;
+ rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ for (unsigned int i = 0; i<roh->num_coins; i++)
+ {
+ const struct CoinData *cdi = &roh->coins[i];
+
+ if (0 == GNUNET_memcmp (&rs.details.conflict.coin_pub,
+ &cdi->coin_pub))
+ {
+ cd = cdi;
+ break;
+ }
+ }
+ if (NULL == cd)
+ {
+ GNUNET_break_op (0);
+ rs.hr.http_status = 0;
+ rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ }
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ if (GNUNET_OK !=
+ handle_reserves_open_kyc (roh,
+ j))
+ {
+ GNUNET_break_op (0);
+ rs.hr.http_status = 0;
+ rs.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 */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for reserves open\n",
+ (unsigned int) response_code,
+ (int) rs.hr.ec);
+ break;
+ }
+ if (NULL != roh->cb)
+ {
+ roh->cb (roh->cb_cls,
+ &rs);
+ roh->cb = NULL;
+ }
+ TALER_EXCHANGE_reserves_open_cancel (roh);
+}
+
+
+struct TALER_EXCHANGE_ReservesOpenHandle *
+TALER_EXCHANGE_reserves_open (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_Amount *reserve_contribution,
+ unsigned int coin_payments_length,
+ const struct TALER_EXCHANGE_PurseDeposit coin_payments[
+ static coin_payments_length],
+ struct GNUNET_TIME_Timestamp expiration_time,
+ uint32_t min_purses,
+ TALER_EXCHANGE_ReservesOpenCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ReservesOpenHandle *roh;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+ json_t *cpa;
+
+ roh = GNUNET_new (struct TALER_EXCHANGE_ReservesOpenHandle);
+ roh->cb = cb;
+ roh->cb_cls = cb_cls;
+ roh->ts = GNUNET_TIME_timestamp_get ();
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &roh->reserve_pub.eddsa_pub);
+ {
+ char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &roh->reserve_pub,
+ sizeof (roh->reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s/open",
+ pub_str);
+ }
+ roh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == roh->url)
+ {
+ GNUNET_free (roh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (roh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (roh->url);
+ GNUNET_free (roh);
+ return NULL;
+ }
+ TALER_wallet_reserve_open_sign (reserve_contribution,
+ roh->ts,
+ expiration_time,
+ min_purses,
+ reserve_priv,
+ &roh->reserve_sig);
+ roh->coins = GNUNET_new_array (coin_payments_length,
+ struct CoinData);
+ cpa = json_array ();
+ GNUNET_assert (NULL != cpa);
+ for (unsigned int i = 0; i<coin_payments_length; i++)
+ {
+ const struct TALER_EXCHANGE_PurseDeposit *pd = &coin_payments[i];
+ const struct TALER_AgeCommitmentProof *acp = pd->age_commitment_proof;
+ struct TALER_AgeCommitmentHashP ahac;
+ struct TALER_AgeCommitmentHashP *achp = NULL;
+ struct CoinData *cd = &roh->coins[i];
+ json_t *cp;
+
+ cd->contribution = pd->amount;
+ cd->h_denom_pub = pd->h_denom_pub;
+ if (NULL != acp)
+ {
+ TALER_age_commitment_hash (&acp->commitment,
+ &ahac);
+ achp = &ahac;
+ }
+ TALER_wallet_reserve_open_deposit_sign (&pd->amount,
+ &roh->reserve_sig,
+ &pd->coin_priv,
+ &cd->coin_sig);
+ GNUNET_CRYPTO_eddsa_key_get_public (&pd->coin_priv.eddsa_priv,
+ &cd->coin_pub.eddsa_pub);
+
+ cp = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ achp)),
+ TALER_JSON_pack_amount ("amount",
+ &pd->amount),
+ GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+ &pd->h_denom_pub),
+ TALER_JSON_pack_denom_sig ("ub_sig",
+ &pd->denom_sig),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &cd->coin_pub),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &cd->coin_sig));
+ GNUNET_assert (0 ==
+ json_array_append_new (cpa,
+ cp));
+ }
+ {
+ json_t *open_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_timestamp ("request_timestamp",
+ roh->ts),
+ GNUNET_JSON_pack_timestamp ("reserve_expiration",
+ expiration_time),
+ GNUNET_JSON_pack_array_steal ("payments",
+ cpa),
+ TALER_JSON_pack_amount ("reserve_payment",
+ reserve_contribution),
+ GNUNET_JSON_pack_uint64 ("purse_limit",
+ min_purses),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &roh->reserve_sig));
+
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&roh->post_ctx,
+ eh,
+ open_obj))
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ json_decref (open_obj);
+ GNUNET_free (roh->coins);
+ GNUNET_free (roh->url);
+ GNUNET_free (roh);
+ return NULL;
+ }
+ json_decref (open_obj);
+ }
+ roh->keys = TALER_EXCHANGE_keys_incref (keys);
+ roh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ roh->post_ctx.headers,
+ &handle_reserves_open_finished,
+ roh);
+ return roh;
+}
+
+
+void
+TALER_EXCHANGE_reserves_open_cancel (
+ struct TALER_EXCHANGE_ReservesOpenHandle *roh)
+{
+ if (NULL != roh->job)
+ {
+ GNUNET_CURL_job_cancel (roh->job);
+ roh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&roh->post_ctx);
+ GNUNET_free (roh->coins);
+ GNUNET_free (roh->url);
+ TALER_EXCHANGE_keys_decref (roh->keys);
+ GNUNET_free (roh);
+}
+
+
+/* end of exchange_api_reserves_open.c */
diff --git a/src/lib/exchange_api_post-reserves-RESERVE_PUB-purse.c b/src/lib/exchange_api_post-reserves-RESERVE_PUB-purse.c
@@ -0,0 +1,580 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-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_post-reserves-RESERVE_PUB-purse.c
+ * @brief Implementation of the client to create a
+ * purse for an account
+ * @author Christian Grothoff
+ */
+#include "taler/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/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "exchange_api_common.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A purse create with merge handle
+ */
+struct TALER_EXCHANGE_PurseCreateMergeHandle
+{
+
+ /**
+ * The keys of the exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * The exchange base URL.
+ */
+ char *exchange_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_PurseCreateMergeCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * The encrypted contract (if any).
+ */
+ struct TALER_EncryptedContract econtract;
+
+ /**
+ * Expected value in the purse after fees.
+ */
+ struct TALER_Amount purse_value_after_fees;
+
+ /**
+ * Public key of the reserve public key.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Reserve signature affirming our merge.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * Merge capability key.
+ */
+ struct TALER_PurseMergePublicKeyP merge_pub;
+
+ /**
+ * Our merge signature (if any).
+ */
+ struct TALER_PurseMergeSignatureP merge_sig;
+
+ /**
+ * Public key of the purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Request data we signed over.
+ */
+ struct TALER_PurseContractSignatureP purse_sig;
+
+ /**
+ * Hash over the purse's contrac terms.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * When does the purse expire.
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * When does the purse get merged/created.
+ */
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RID/purse request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_PurseCreateMergeHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_purse_create_with_merge_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_PurseCreateMergeResponse dr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code,
+ .reserve_sig = &pcm->reserve_sig
+ };
+
+ pcm->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct GNUNET_TIME_Timestamp etime;
+ struct TALER_Amount total_deposited;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("total_deposited",
+ &total_deposited),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &exchange_pub),
+ GNUNET_JSON_spec_timestamp ("exchange_timestamp",
+ &etime),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (pcm->keys,
+ &exchange_pub))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_purse_created_verify (
+ etime,
+ pcm->purse_expiration,
+ &pcm->purse_value_after_fees,
+ &total_deposited,
+ &pcm->purse_pub,
+ &pcm->h_contract_terms,
+ &exchange_pub,
+ &exchange_sig))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ 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 */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* 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 */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ switch (dr.hr.ec)
+ {
+ case TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_CONFLICTING_META_DATA:
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_check_purse_create_conflict_ (
+ &pcm->purse_sig,
+ &pcm->purse_pub,
+ j))
+ {
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ break;
+ case TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_CONFLICTING_META_DATA:
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_check_purse_merge_conflict_ (
+ &pcm->merge_sig,
+ &pcm->merge_pub,
+ &pcm->purse_pub,
+ pcm->exchange_url,
+ j))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ break;
+ case TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_INSUFFICIENT_FUNDS:
+ /* nothing to verify */
+ break;
+ case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA:
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_check_purse_econtract_conflict_ (
+ &pcm->econtract.econtract_sig,
+ &pcm->purse_pub,
+ j))
+ {
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ break;
+ default:
+ /* unexpected EC! */
+ GNUNET_break_op (0);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ } /* end inner (EC) switch */
+ 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 */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &dr.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);
+ dr.hr.http_status = 0;
+ dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.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 */
+ dr.hr.ec = TALER_JSON_get_error_code (j);
+ dr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange deposit\n",
+ (unsigned int) response_code,
+ dr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ pcm->cb (pcm->cb_cls,
+ &dr);
+ TALER_EXCHANGE_purse_create_with_merge_cancel (pcm);
+}
+
+
+struct TALER_EXCHANGE_PurseCreateMergeHandle *
+TALER_EXCHANGE_purse_create_with_merge (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_PurseContractPrivateKeyP *purse_priv,
+ const struct TALER_PurseMergePrivateKeyP *merge_priv,
+ const struct TALER_ContractDiffiePrivateP *contract_priv,
+ const json_t *contract_terms,
+ bool upload_contract,
+ bool pay_for_purse,
+ struct GNUNET_TIME_Timestamp merge_timestamp,
+ TALER_EXCHANGE_PurseCreateMergeCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm;
+ json_t *create_with_merge_obj;
+ CURL *eh;
+ char arg_str[sizeof (pcm->reserve_pub) * 2 + 32];
+ uint32_t min_age = 0;
+ struct TALER_Amount purse_fee;
+ enum TALER_WalletAccountMergeFlags flags;
+
+ pcm = GNUNET_new (struct TALER_EXCHANGE_PurseCreateMergeHandle);
+ pcm->cb = cb;
+ pcm->cb_cls = cb_cls;
+ if (GNUNET_OK !=
+ TALER_JSON_contract_hash (contract_terms,
+ &pcm->h_contract_terms))
+ {
+ GNUNET_break (0);
+ GNUNET_free (pcm);
+ return NULL;
+ }
+ pcm->merge_timestamp = merge_timestamp;
+ GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv,
+ &pcm->purse_pub.eddsa_pub);
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &pcm->reserve_pub.eddsa_pub);
+ GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
+ &pcm->merge_pub.eddsa_pub);
+
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount_any ("amount",
+ &pcm->purse_value_after_fees),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("minimum_age",
+ &min_age),
+ NULL),
+ GNUNET_JSON_spec_timestamp ("pay_deadline",
+ &pcm->purse_expiration),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (contract_terms,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ GNUNET_free (pcm);
+ return NULL;
+ }
+ }
+ if (pay_for_purse)
+ {
+ const struct TALER_EXCHANGE_GlobalFee *gf;
+
+ gf = TALER_EXCHANGE_get_global_fee (
+ keys,
+ GNUNET_TIME_timestamp_get ());
+ purse_fee = gf->fees.purse;
+ flags = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
+ }
+ else
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pcm->purse_value_after_fees.currency,
+ &purse_fee));
+ flags = TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA;
+ }
+
+ {
+ char pub_str[sizeof (pcm->reserve_pub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &pcm->reserve_pub,
+ sizeof (pcm->reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s/purse",
+ pub_str);
+ }
+ pcm->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == pcm->url)
+ {
+ GNUNET_break (0);
+ GNUNET_free (pcm);
+ return NULL;
+ }
+ TALER_wallet_purse_create_sign (pcm->purse_expiration,
+ &pcm->h_contract_terms,
+ &pcm->merge_pub,
+ min_age,
+ &pcm->purse_value_after_fees,
+ purse_priv,
+ &pcm->purse_sig);
+ {
+ struct TALER_NormalizedPayto payto_uri;
+
+ payto_uri = TALER_reserve_make_payto (url,
+ &pcm->reserve_pub);
+ TALER_wallet_purse_merge_sign (payto_uri,
+ merge_timestamp,
+ &pcm->purse_pub,
+ merge_priv,
+ &pcm->merge_sig);
+ GNUNET_free (payto_uri.normalized_payto);
+ }
+ TALER_wallet_account_merge_sign (merge_timestamp,
+ &pcm->purse_pub,
+ pcm->purse_expiration,
+ &pcm->h_contract_terms,
+ &pcm->purse_value_after_fees,
+ &purse_fee,
+ min_age,
+ flags,
+ reserve_priv,
+ &pcm->reserve_sig);
+ if (upload_contract)
+ {
+ TALER_CRYPTO_contract_encrypt_for_deposit (
+ &pcm->purse_pub,
+ contract_priv,
+ contract_terms,
+ &pcm->econtract.econtract,
+ &pcm->econtract.econtract_size);
+ GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv,
+ &pcm->econtract.contract_pub.ecdhe_pub);
+ TALER_wallet_econtract_upload_sign (
+ pcm->econtract.econtract,
+ pcm->econtract.econtract_size,
+ &pcm->econtract.contract_pub,
+ purse_priv,
+ &pcm->econtract.econtract_sig);
+ }
+ create_with_merge_obj = GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("purse_value",
+ &pcm->purse_value_after_fees),
+ GNUNET_JSON_pack_uint64 ("min_age",
+ min_age),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_econtract ("econtract",
+ upload_contract
+ ? &pcm->econtract
+ : NULL)),
+ GNUNET_JSON_pack_allow_null (
+ pay_for_purse
+ ? TALER_JSON_pack_amount ("purse_fee",
+ &purse_fee)
+ : GNUNET_JSON_pack_string ("dummy2",
+ NULL)),
+ GNUNET_JSON_pack_data_auto ("merge_pub",
+ &pcm->merge_pub),
+ GNUNET_JSON_pack_data_auto ("merge_sig",
+ &pcm->merge_sig),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &pcm->reserve_sig),
+ GNUNET_JSON_pack_data_auto ("purse_pub",
+ &pcm->purse_pub),
+ GNUNET_JSON_pack_data_auto ("purse_sig",
+ &pcm->purse_sig),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &pcm->h_contract_terms),
+ GNUNET_JSON_pack_timestamp ("merge_timestamp",
+ merge_timestamp),
+ GNUNET_JSON_pack_timestamp ("purse_expiration",
+ pcm->purse_expiration));
+ GNUNET_assert (NULL != create_with_merge_obj);
+ eh = TALER_EXCHANGE_curl_easy_get_ (pcm->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&pcm->ctx,
+ eh,
+ create_with_merge_obj)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (create_with_merge_obj);
+ GNUNET_free (pcm->econtract.econtract);
+ GNUNET_free (pcm->url);
+ GNUNET_free (pcm);
+ return NULL;
+ }
+ json_decref (create_with_merge_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "URL for purse create_with_merge: `%s'\n",
+ pcm->url);
+ pcm->keys = TALER_EXCHANGE_keys_incref (keys);
+ pcm->exchange_url = GNUNET_strdup (url);
+ pcm->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ pcm->ctx.headers,
+ &handle_purse_create_with_merge_finished,
+ pcm);
+ return pcm;
+}
+
+
+void
+TALER_EXCHANGE_purse_create_with_merge_cancel (
+ struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm)
+{
+ if (NULL != pcm->job)
+ {
+ GNUNET_CURL_job_cancel (pcm->job);
+ pcm->job = NULL;
+ }
+ GNUNET_free (pcm->url);
+ GNUNET_free (pcm->exchange_url);
+ TALER_curl_easy_post_finished (&pcm->ctx);
+ TALER_EXCHANGE_keys_decref (pcm->keys);
+ GNUNET_free (pcm->econtract.econtract);
+ GNUNET_free (pcm);
+}
+
+
+/* end of exchange_api_purse_create_with_merge.c */
diff --git a/src/lib/exchange_api_post-reserves-attest-RESERVE_PUB.c b/src/lib/exchange_api_post-reserves-attest-RESERVE_PUB.c
@@ -0,0 +1,365 @@
+/*
+ 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_post-reserves-attest-RESERVE_PUB.c
+ * @brief Implementation of the POST /reserves-attest/$RESERVE_PUB requests
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP attest codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler/taler_exchange_service.h"
+#include "taler/taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /reserves-attest/$RID Handle
+ */
+struct TALER_EXCHANGE_ReservesAttestHandle
+{
+
+ /**
+ * The keys of the this request handle will use
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * 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;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ReservesPostAttestCallback cb;
+
+ /**
+ * Public key of the reserve we are querying.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * We received an #MHD_HTTP_OK attest code. Handle the JSON
+ * response.
+ *
+ * @param rsh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_attest_ok (struct TALER_EXCHANGE_ReservesAttestHandle *rsh,
+ const json_t *j)
+{
+ struct TALER_EXCHANGE_ReservePostAttestResult rs = {
+ .hr.reply = j,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ const json_t *attributes;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("exchange_timestamp",
+ &rs.details.ok.exchange_time),
+ GNUNET_JSON_spec_timestamp ("expiration_time",
+ &rs.details.ok.expiration_time),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rs.details.ok.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rs.details.ok.exchange_pub),
+ GNUNET_JSON_spec_object_const ("attributes",
+ &attributes),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (rsh->keys,
+ &rs.details.ok.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ rs.hr.http_status = 0;
+ rs.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE;
+ rsh->cb (rsh->cb_cls,
+ &rs);
+ rsh->cb = NULL;
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ rs.details.ok.attributes = attributes;
+ if (GNUNET_OK !=
+ TALER_exchange_online_reserve_attest_details_verify (
+ rs.details.ok.exchange_time,
+ rs.details.ok.expiration_time,
+ &rsh->reserve_pub,
+ attributes,
+ &rs.details.ok.exchange_pub,
+ &rs.details.ok.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ rsh->cb (rsh->cb_cls,
+ &rs);
+ rsh->cb = NULL;
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves-attest/$RID request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReservesAttestHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserves_attest_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ReservesAttestHandle *rsh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_ReservePostAttestResult rs = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ rsh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ handle_reserves_attest_ok (rsh,
+ j))
+ {
+ rs.hr.http_status = 0;
+ rs.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 */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ GNUNET_break (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.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 */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* Server doesn't have the requested attributes */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.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 */
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ rs.hr.ec = TALER_JSON_get_error_code (j);
+ rs.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for reserves attest\n",
+ (unsigned int) response_code,
+ (int) rs.hr.ec);
+ break;
+ }
+ if (NULL != rsh->cb)
+ {
+ rsh->cb (rsh->cb_cls,
+ &rs);
+ rsh->cb = NULL;
+ }
+ TALER_EXCHANGE_reserves_attest_cancel (rsh);
+}
+
+
+struct TALER_EXCHANGE_ReservesAttestHandle *
+TALER_EXCHANGE_reserves_attest (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ unsigned int attributes_length,
+ const char *attributes[const static attributes_length],
+ TALER_EXCHANGE_ReservesPostAttestCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ReservesAttestHandle *rsh;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+ struct TALER_ReserveSignatureP reserve_sig;
+ json_t *details;
+ struct GNUNET_TIME_Timestamp ts;
+
+ if (0 == attributes_length)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ details = json_array ();
+ GNUNET_assert (NULL != details);
+ for (unsigned int i = 0; i<attributes_length; i++)
+ {
+ GNUNET_assert (0 ==
+ json_array_append_new (details,
+ json_string (attributes[i])));
+ }
+ rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesAttestHandle);
+ rsh->cb = cb;
+ rsh->cb_cls = cb_cls;
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &rsh->reserve_pub.eddsa_pub);
+ {
+ char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &rsh->reserve_pub,
+ sizeof (rsh->reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves-attest/%s",
+ pub_str);
+ }
+ rsh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
+ if (NULL == rsh->url)
+ {
+ json_decref (details);
+ GNUNET_free (rsh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ json_decref (details);
+ GNUNET_free (rsh->url);
+ GNUNET_free (rsh);
+ return NULL;
+ }
+ ts = GNUNET_TIME_timestamp_get ();
+ TALER_wallet_reserve_attest_request_sign (ts,
+ details,
+ reserve_priv,
+ &reserve_sig);
+ {
+ json_t *attest_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &reserve_sig),
+ GNUNET_JSON_pack_timestamp ("request_timestamp",
+ ts),
+ GNUNET_JSON_pack_array_steal ("details",
+ details));
+
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&rsh->post_ctx,
+ eh,
+ attest_obj))
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ json_decref (attest_obj);
+ GNUNET_free (rsh->url);
+ GNUNET_free (rsh);
+ return NULL;
+ }
+ json_decref (attest_obj);
+ }
+ rsh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ rsh->post_ctx.headers,
+ &handle_reserves_attest_finished,
+ rsh);
+ rsh->keys = TALER_EXCHANGE_keys_incref (keys);
+ return rsh;
+}
+
+
+void
+TALER_EXCHANGE_reserves_attest_cancel (
+ struct TALER_EXCHANGE_ReservesAttestHandle *rsh)
+{
+ if (NULL != rsh->job)
+ {
+ GNUNET_CURL_job_cancel (rsh->job);
+ rsh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&rsh->post_ctx);
+ TALER_EXCHANGE_keys_decref (rsh->keys);
+ GNUNET_free (rsh->url);
+ GNUNET_free (rsh);
+}
+
+
+/* end of exchange_api_reserves_attest.c */
diff --git a/src/lib/exchange_api_post-reveal-melt.c b/src/lib/exchange_api_post-reveal-melt.c
@@ -0,0 +1,416 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 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_post-reveal-melt.c
+ * @brief Implementation of the /reveal-melt request
+ * @author Özgür Kesim
+ */
+#include "taler/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/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+#include "exchange_api_refresh_common.h"
+
+
+/**
+ * Handler for a running reveal-melt request
+ */
+struct TALER_EXCHANGE_RevealMeltHandle
+{
+ /**
+ * The url for the request
+ */
+ char *request_url;
+
+ /**
+ * CURL handle for the request job.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Post Context
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Number of coins to expect
+ */
+ size_t num_expected_coins;
+
+ /**
+ * The input provided
+ */
+ const struct TALER_EXCHANGE_RevealMeltInput *reveal_input;
+
+ /**
+ * The melt data
+ */
+ struct MeltData md;
+
+ /**
+ * Callback to pass the result onto
+ */
+ TALER_EXCHANGE_RevealMeltCallback callback;
+
+ /**
+ * Closure for @e callback
+ */
+ void *callback_cls;
+
+};
+
+/**
+ * We got a 200 OK response for the /reveal-melt operation.
+ * Extract the signed blinded coins and return it to the caller.
+ *
+ * @param mrh operation handle
+ * @param j_response reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+reveal_melt_ok (
+ struct TALER_EXCHANGE_RevealMeltHandle *mrh,
+ const json_t *j_response)
+{
+ struct TALER_EXCHANGE_RevealMeltResponse response = {
+ .hr.reply = j_response,
+ .hr.http_status = MHD_HTTP_OK,
+ };
+ struct TALER_BlindedDenominationSignature blind_sigs[mrh->num_expected_coins];
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_array_of_blinded_denom_sigs ("ev_sigs",
+ mrh->num_expected_coins,
+ blind_sigs),
+ GNUNET_JSON_spec_end ()
+ };
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ struct TALER_EXCHANGE_RevealedCoinInfo coins[mrh->num_expected_coins];
+
+ /* Reconstruct the coins and unblind the signatures */
+ for (unsigned int i = 0; i<mrh->num_expected_coins; i++)
+ {
+ struct TALER_EXCHANGE_RevealedCoinInfo *rci = &coins[i];
+ const struct FreshCoinData *fcd = &mrh->md.fcds[i];
+ const struct TALER_DenominationPublicKey *pk;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinPubHashP coin_hash;
+ struct TALER_FreshCoin coin;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ const struct TALER_AgeCommitmentHashP *pah = NULL;
+
+ rci->ps = fcd->ps[mrh->reveal_input->noreveal_index];
+ rci->bks = fcd->bks[mrh->reveal_input->noreveal_index];
+ rci->age_commitment_proof = NULL;
+ pk = &fcd->fresh_pk;
+ if (NULL != mrh->md.melted_coin.age_commitment_proof)
+ {
+ rci->age_commitment_proof
+ = fcd->age_commitment_proofs[mrh->reveal_input->noreveal_index];
+ TALER_age_commitment_hash (
+ &rci->age_commitment_proof->commitment,
+ &rci->h_age_commitment);
+ pah = &rci->h_age_commitment;
+ }
+
+ TALER_planchet_setup_coin_priv (&rci->ps,
+ &mrh->reveal_input->blinding_values[i],
+ &rci->coin_priv);
+ TALER_planchet_blinding_secret_create (&rci->ps,
+ &mrh->reveal_input->blinding_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_sigs[i],
+ &bks,
+ &rci->coin_priv,
+ pah,
+ &coin_hash,
+ &mrh->reveal_input->blinding_values[i],
+ &coin))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_JSON_parse_free (spec);
+ rci->sig = coin.sig;
+ }
+
+ response.details.ok.num_coins = mrh->num_expected_coins;
+ response.details.ok.coins = coins;
+ mrh->callback (mrh->callback_cls,
+ &response);
+ /* Make sure the callback isn't called again */
+ mrh->callback = NULL;
+ /* Free resources */
+ for (size_t i = 0; i < mrh->num_expected_coins; i++)
+ {
+ struct TALER_EXCHANGE_RevealedCoinInfo *rci = &coins[i];
+
+ TALER_denom_sig_free (&rci->sig);
+ TALER_blinded_denom_sig_free (&blind_sigs[i]);
+ }
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reveal-melt request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_RevealMeltHandle`
+ * @param response_code The HTTP response code
+ * @param response response data
+ */
+static void
+handle_reveal_melt_finished (
+ void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_RevealMeltHandle *mrh = cls;
+ const json_t *j_response = response;
+ struct TALER_EXCHANGE_RevealMeltResponse awr = {
+ .hr.reply = j_response,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ mrh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = reveal_melt_ok (mrh,
+ 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 == mrh->callback);
+ TALER_EXCHANGE_reveal_melt_cancel (mrh);
+ 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 */
+ 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-melt 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
+ * or TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH.
+ */
+ 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 melt\n",
+ (unsigned int) response_code,
+ (int) awr.hr.ec);
+ break;
+ }
+ mrh->callback (mrh->callback_cls,
+ &awr);
+ TALER_EXCHANGE_reveal_melt_cancel (mrh);
+}
+
+
+/**
+ * Call /reveal-melt
+ *
+ * @param curl_ctx The context for CURL
+ * @param mrh The handler
+ */
+static void
+perform_protocol (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_RevealMeltHandle *mrh)
+{
+ CURL *curlh;
+ json_t *j_batch_seeds;
+
+
+ j_batch_seeds = json_array ();
+ GNUNET_assert (NULL != j_batch_seeds);
+
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ if (mrh->reveal_input->noreveal_index == k)
+ continue;
+
+ GNUNET_assert (0 == json_array_append_new (
+ j_batch_seeds,
+ GNUNET_JSON_from_data_auto (
+ &mrh->md.kappa_batch_seeds.tuple[k])));
+ }
+ {
+ json_t *j_request_body;
+
+ j_request_body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("rc",
+ &mrh->md.rc),
+ GNUNET_JSON_pack_array_steal ("batch_seeds",
+ j_batch_seeds));
+ GNUNET_assert (NULL != j_request_body);
+
+ if (NULL != mrh->reveal_input->melt_input->melt_age_commitment_proof)
+ {
+ json_t *j_age = GNUNET_JSON_PACK (
+ TALER_JSON_pack_age_commitment (
+ "age_commitment",
+ &mrh->reveal_input->melt_input->melt_age_commitment_proof->commitment)
+ );
+ GNUNET_assert (NULL != j_age);
+ GNUNET_assert (0 ==
+ json_object_update_new (j_request_body,
+ j_age));
+ }
+ curlh = TALER_EXCHANGE_curl_easy_get_ (mrh->request_url);
+ GNUNET_assert (NULL != curlh);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_curl_easy_post (&mrh->post_ctx,
+ curlh,
+ j_request_body));
+ json_decref (j_request_body);
+ }
+ mrh->job = GNUNET_CURL_job_add2 (
+ curl_ctx,
+ curlh,
+ mrh->post_ctx.headers,
+ &handle_reveal_melt_finished,
+ mrh);
+ if (NULL == mrh->job)
+ {
+ GNUNET_break (0);
+ if (NULL != curlh)
+ curl_easy_cleanup (curlh);
+ TALER_EXCHANGE_reveal_melt_cancel (mrh);
+ }
+}
+
+
+struct TALER_EXCHANGE_RevealMeltHandle *
+TALER_EXCHANGE_reveal_melt (
+ struct GNUNET_CURL_Context *curl_ctx,
+ const char *exchange_url,
+ const struct TALER_EXCHANGE_RevealMeltInput *reveal_melt_input,
+ TALER_EXCHANGE_RevealMeltCallback reveal_cb,
+ void *reveal_cb_cls)
+{
+ struct TALER_EXCHANGE_RevealMeltHandle *mrh =
+ GNUNET_new (struct TALER_EXCHANGE_RevealMeltHandle);
+ mrh->callback = reveal_cb;
+ mrh->callback_cls = reveal_cb_cls;
+ mrh->reveal_input = reveal_melt_input;
+ mrh->num_expected_coins = reveal_melt_input->melt_input->num_fresh_denom_pubs;
+ mrh->request_url = TALER_url_join (exchange_url,
+ "reveal-melt",
+ NULL);
+ if (NULL == mrh->request_url)
+ {
+ GNUNET_break (0);
+ GNUNET_free (mrh);
+ return NULL;
+ }
+ if (reveal_melt_input->num_blinding_values !=
+ reveal_melt_input->melt_input->num_fresh_denom_pubs)
+ {
+ GNUNET_break (0);
+ GNUNET_free (mrh);
+ return NULL;
+ }
+ TALER_EXCHANGE_get_melt_data (
+ reveal_melt_input->rms,
+ reveal_melt_input->melt_input,
+ reveal_melt_input->blinding_seed,
+ reveal_melt_input->blinding_values,
+ &mrh->md);
+ perform_protocol (curl_ctx,
+ mrh);
+ return mrh;
+}
+
+
+void
+TALER_EXCHANGE_reveal_melt_cancel (
+ struct TALER_EXCHANGE_RevealMeltHandle *mrh)
+{
+ if (NULL != mrh->job)
+ {
+ GNUNET_CURL_job_cancel (mrh->job);
+ mrh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&mrh->post_ctx);
+ TALER_EXCHANGE_free_melt_data (&mrh->md);
+ GNUNET_free (mrh->request_url);
+ GNUNET_free (mrh);
+}
diff --git a/src/lib/exchange_api_post-reveal-withdraw.c b/src/lib/exchange_api_post-reveal-withdraw.c
@@ -0,0 +1,366 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023-2025 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_post-reveal-withdraw.c
+ * @brief Implementation of /reveal-withdraw requests
+ * @author Özgür Kesim
+ */
+
+#include "taler/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/taler_curl_lib.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+/**
+ * Handler for a running reveal-withdraw request
+ */
+struct TALER_EXCHANGE_RevealWithdrawHandle
+{
+ /**
+ * The commitment from the previous call withdraw
+ */
+ const struct TALER_HashBlindedPlanchetsP *planchets_h;
+
+ /**
+ * Number of coins for which to reveal tuples of seeds
+ */
+ size_t num_coins;
+
+ /**
+ * The TALER_CNC_KAPPA-1 tuple of seeds to reveal
+ */
+ struct TALER_RevealWithdrawMasterSeedsP seeds;
+
+ /**
+ * The url for the reveal request
+ */
+ 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_RevealWithdrawCallback callback;
+
+ /**
+ * Reveal
+ */
+ void *callback_cls;
+};
+
+
+/**
+ * We got a 200 OK response for the /reveal-withdraw operation.
+ * Extract the signed blindedcoins and return it to the caller.
+ *
+ * @param wrh operation handle
+ * @param j_response reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+reveal_withdraw_ok (
+ struct TALER_EXCHANGE_RevealWithdrawHandle *wrh,
+ const json_t *j_response)
+{
+ struct TALER_EXCHANGE_RevealWithdrawResponse 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 (wrh->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[wrh->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 ispec[] = {
+ TALER_JSON_spec_blinded_denom_sig (NULL,
+ &denom_sigs[n]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j_sig,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ response.details.ok.num_sigs = wrh->num_coins;
+ response.details.ok.blinded_denom_sigs = denom_sigs;
+ wrh->callback (wrh->callback_cls,
+ &response);
+ /* Make sure the callback isn't called again */
+ wrh->callback = NULL;
+ /* Free resources */
+ for (size_t i = 0; i < wrh->num_coins; i++)
+ TALER_blinded_denom_sig_free (&denom_sigs[i]);
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reveal-withdraw request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_RevealWithdrawHandle`
+ * @param response_code The HTTP response code
+ * @param response response data
+ */
+static void
+handle_reveal_withdraw_finished (
+ void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_RevealWithdrawHandle *wrh = cls;
+ const json_t *j_response = response;
+ struct TALER_EXCHANGE_RevealWithdrawResponse awr = {
+ .hr.reply = j_response,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ wrh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = reveal_withdraw_ok (wrh,
+ 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 == wrh->callback);
+ TALER_EXCHANGE_reveal_withdraw_cancel (wrh);
+ 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 */
+ 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
+ * or TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH.
+ */
+ 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;
+ }
+ wrh->callback (wrh->callback_cls,
+ &awr);
+ TALER_EXCHANGE_reveal_withdraw_cancel (wrh);
+}
+
+
+/**
+ * Call /reveal-withdraw
+ *
+ * @param curl_ctx The context for CURL
+ * @param wrh The handler
+ */
+static void
+perform_protocol (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_RevealWithdrawHandle *wrh)
+{
+ CURL *curlh;
+ json_t *j_array_of_secrets;
+
+ j_array_of_secrets = json_array ();
+ GNUNET_assert (NULL != j_array_of_secrets);
+
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA - 1; k++)
+ {
+ json_t *j_sec = GNUNET_JSON_from_data_auto (&wrh->seeds.tuple[k]);
+ GNUNET_assert (NULL != j_sec);
+ GNUNET_assert (0 == json_array_append_new (j_array_of_secrets,
+ j_sec));
+ }
+ {
+ json_t *j_request_body;
+
+ j_request_body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("planchets_h",
+ wrh->planchets_h),
+ GNUNET_JSON_pack_array_steal ("disclosed_batch_seeds",
+ j_array_of_secrets));
+ GNUNET_assert (NULL != j_request_body);
+
+ curlh = TALER_EXCHANGE_curl_easy_get_ (wrh->request_url);
+ GNUNET_assert (NULL != curlh);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_curl_easy_post (&wrh->post_ctx,
+ curlh,
+ j_request_body));
+
+ json_decref (j_request_body);
+ }
+
+ wrh->job = GNUNET_CURL_job_add2 (
+ curl_ctx,
+ curlh,
+ wrh->post_ctx.headers,
+ &handle_reveal_withdraw_finished,
+ wrh);
+ if (NULL == wrh->job)
+ {
+ GNUNET_break (0);
+ if (NULL != curlh)
+ curl_easy_cleanup (curlh);
+ TALER_EXCHANGE_reveal_withdraw_cancel (wrh);
+ }
+
+ return;
+}
+
+
+struct TALER_EXCHANGE_RevealWithdrawHandle *
+TALER_EXCHANGE_reveal_withdraw (
+ struct GNUNET_CURL_Context *curl_ctx,
+ const char *exchange_url,
+ size_t num_coins,
+ const struct TALER_HashBlindedPlanchetsP *planchets_h,
+ const struct TALER_RevealWithdrawMasterSeedsP *seeds,
+ TALER_EXCHANGE_RevealWithdrawCallback reveal_cb,
+ void *reveal_cb_cls)
+{
+ struct TALER_EXCHANGE_RevealWithdrawHandle *wrh =
+ GNUNET_new (struct TALER_EXCHANGE_RevealWithdrawHandle);
+ wrh->planchets_h = planchets_h;
+ wrh->num_coins = num_coins;
+ wrh->seeds = *seeds;
+ wrh->callback = reveal_cb;
+ wrh->callback_cls = reveal_cb_cls;
+ wrh->request_url = TALER_url_join (exchange_url,
+ "reveal-withdraw",
+ NULL);
+ if (NULL == wrh->request_url)
+ {
+ GNUNET_break (0);
+ GNUNET_free (wrh);
+ return NULL;
+ }
+
+ perform_protocol (curl_ctx, wrh);
+
+ return wrh;
+}
+
+
+void
+TALER_EXCHANGE_reveal_withdraw_cancel (
+ struct TALER_EXCHANGE_RevealWithdrawHandle *wrh)
+{
+ if (NULL != wrh->job)
+ {
+ GNUNET_CURL_job_cancel (wrh->job);
+ wrh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&wrh->post_ctx);
+
+ if (NULL != wrh->request_url)
+ GNUNET_free (wrh->request_url);
+
+ GNUNET_free (wrh);
+}
+
+
+/* exchange_api_reveal_withdraw.c */
diff --git a/src/lib/exchange_api_post-withdraw.c b/src/lib/exchange_api_post-withdraw.c
@@ -0,0 +1,1928 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023-2025 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_post-withdraw.c
+ * @brief Implementation of /withdraw requests
+ * @author Özgür Kesim
+ */
+/**
+ * We want the "dangerous" exports here as these are OUR exports
+ * and we want to check that the prototypes match.
+ */
+#define TALER_TESTING_EXPORTS_DANGEROUS 1
+#include "taler/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/taler_curl_lib.h"
+#include "taler/taler_error_codes.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler/taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler/taler_util.h"
+
+/**
+ * A CoinCandidate is populated from a master secret.
+ * The data is copied from and generated out of the client's input.
+ */
+struct CoinCandidate
+{
+ /**
+ * The details derived form the master secrets
+ */
+ struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details;
+
+ /**
+ * Blinded hash of the coin
+ **/
+ struct TALER_BlindedCoinHashP blinded_coin_h;
+
+};
+
+
+/**
+ * Closure for a call to /blinding-prepare, contains data that is needed to process
+ * the result.
+ */
+struct BlindingPrepareClosure
+{
+ /**
+ * Number of coins in the blinding-prepare step.
+ * Not that this number might be smaller than the total number
+ * of coins in the withdraw, as the prepare is only necessary
+ * for CS denominations
+ */
+ size_t num_prepare_coins;
+
+ /**
+ * Array of @e num_prepare_coins of data per coin
+ */
+ struct BlindingPrepareCoinData
+ {
+ /**
+ * Pointer to the candidate in CoinData.candidates,
+ * to continue to build its contents based on the results from /blinding-prepare
+ */
+ struct CoinCandidate *candidate;
+
+ /**
+ * Planchet to finally generate in the corresponding candidate
+ * in CoindData.planchet_details
+ */
+ struct TALER_PlanchetDetail *planchet;
+
+ /**
+ * Denomination information, needed for the
+ * step after /blinding-prepare
+ */
+ const struct TALER_DenominationPublicKey *denom_pub;
+
+ /**
+ * True, if denomination supports age restriction
+ */
+ bool age_denom;
+
+ /**
+ * The index into the array of returned values from the call to
+ * /blinding-prepare that are to be used for this coin.
+ */
+ size_t cs_idx;
+
+ } *coins;
+
+ /**
+ * Number of seeds requested. This may differ from @e num_prepare_coins
+ * in case of a withdraw with required age proof, in which case
+ * @e num_prepare_coins = TALER_CNC_KAPPA * @e num_seeds
+ */
+ size_t num_nonces;
+
+ /**
+ * Array of @e num_nonces calculated nonces.
+ */
+ union GNUNET_CRYPTO_BlindSessionNonce *nonces;
+
+ /**
+ * Handler to the originating call to /withdraw, needed to either
+ * cancel the running withdraw request (on failure of the current call
+ * to /blinding-prepare), or to eventually perform the protocol, once all
+ * blinding-prepare requests have successfully finished.
+ */
+ struct TALER_EXCHANGE_WithdrawHandle *withdraw_handle;
+
+};
+
+
+/**
+ * Data we keep per coin in the batch.
+ * This is copied from and generated out of the input provided
+ * by the client.
+ */
+struct CoinData
+{
+ /**
+ * The denomination of the coin.
+ */
+ struct TALER_EXCHANGE_DenomPublicKey denom_pub;
+
+ /**
+ * The Candidates for the coin. If the batch is not age-restricted,
+ * only index 0 is used.
+ */
+ struct CoinCandidate candidates[TALER_CNC_KAPPA];
+
+ /**
+ * Details of the planchet(s). If the batch is not age-restricted,
+ * only index 0 is used.
+ */
+ struct TALER_PlanchetDetail planchet_details[TALER_CNC_KAPPA];
+};
+
+
+/**
+ * A /withdraw request-handle for calls with pre-blinded planchets.
+ * Returned by TALER_EXCHANGE_withdraw_blinded.
+ */
+struct TALER_EXCHANGE_WithdrawBlindedHandle
+{
+
+ /**
+ * 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 hash of all the planchets
+ */
+ struct TALER_HashBlindedPlanchetsP planchets_h;
+
+ /**
+ * Seed used for the derival of blinding factors for denominations
+ * with Clause-Schnorr cipher. We derive this from the master seed
+ * for the withdraw, but independent from the other planchet seeds.
+ */
+ const struct TALER_BlindingMasterSeedP *blinding_seed;
+
+ /**
+ * Total amount requested (without fee).
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Total withdraw fee
+ */
+ struct TALER_Amount fee;
+
+ /**
+ * Is this call for age-restriced coins, with age proof?
+ */
+ bool with_age_proof;
+
+ /**
+ * If @e with_age_proof is true or @max_age is > 0,
+ * the age mask to use, extracted from the denominations.
+ * MUST be the same for all denominations.
+ */
+ struct TALER_AgeMask age_mask;
+
+ /**
+ * The maximum age to commit to. If @e with_age_proof
+ * is true, the client will need to proof the correct setting
+ * of age-restriction on the coins via an additional call
+ * to /reveal-withdraw.
+ */
+ uint8_t max_age;
+
+ /**
+ * If @e with_age_proof is true, the hash of all the selected planchets
+ */
+ struct TALER_HashBlindedPlanchetsP selected_h;
+
+ /**
+ * Length of the either the @e blinded.input or
+ * the @e blinded.with_age_proof_input array,
+ * depending on @e with_age_proof.
+ */
+ size_t num_input;
+
+ union
+ {
+ /**
+ * The blinded planchet input candidates for age-restricted coins
+ * for the call to /withdraw
+ */
+ const struct
+ TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput *with_age_proof_input;
+
+ /**
+ * The blinded planchet input for the call to /withdraw via
+ * TALER_EXCHANGE_withdraw_blinded, for age-unrestricted coins.
+ */
+ const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *input;
+
+ } blinded;
+
+ /**
+ * 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 withdraw response results.
+ */
+ TALER_EXCHANGE_WithdrawBlindedCallback callback;
+
+ /**
+ * Closure for @e blinded_callback
+ */
+ void *callback_cls;
+};
+
+/**
+ * A /withdraw request-handle for calls from
+ * a wallet, i. e. when blinding data is available.
+ */
+struct TALER_EXCHANGE_WithdrawHandle
+{
+
+ /**
+ * The base-URL of the exchange.
+ */
+ const char *exchange_url;
+
+ /**
+ * Seed to derive of all seeds for the coins.
+ */
+ struct TALER_WithdrawMasterSeedP seed;
+
+ /**
+ * If @e with_age_proof is true, the derived TALER_CNC_KAPPA many
+ * seeds for candidate batches.
+ */
+ struct TALER_KappaWithdrawMasterSeedP kappa_seed;
+
+ /**
+ * True if @e blinding_seed is filled, that is, if
+ * any of the denominations is of cipher type CS
+ */
+ bool has_blinding_seed;
+
+ /**
+ * Seed used for the derivation of blinding factors for denominations
+ * with Clause-Schnorr cipher. We derive this from the master seed
+ * for the withdraw, but independent from the other planchet seeds.
+ * Only valid when @e has_blinding_seed is true;
+ */
+ struct TALER_BlindingMasterSeedP blinding_seed;
+
+ /**
+ * 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;
+
+ /**
+ * True, if the withdraw is for age-restricted coins, with age-proof.
+ * The denominations MUST support age restriction.
+ */
+ bool with_age_proof;
+
+ /**
+ * If @e with_age_proof is true, the age mask, extracted
+ * from the denominations.
+ * MUST be the same for all denominations.
+ *
+ */
+ struct TALER_AgeMask age_mask;
+
+ /**
+ * The maximum age to commit to. If @e with_age_proof
+ * is true, the client will need to proof the correct setting
+ * of age-restriction on the coins via an additional call
+ * to /reveal-withdraw.
+ */
+ uint8_t max_age;
+
+ /**
+ * Length of the @e coin_data Array
+ */
+ size_t num_coins;
+
+ /**
+ * Array of per-coin data
+ */
+ struct CoinData *coin_data;
+
+ /**
+ * Context for curl.
+ */
+ struct GNUNET_CURL_Context *curl_ctx;
+
+ /**
+ * Function to call with withdraw response results.
+ */
+ TALER_EXCHANGE_WithdrawCallback callback;
+
+ /**
+ * Closure for @e callback
+ */
+ void *callback_cls;
+
+ /* The handler for the call to /blinding-prepare, needed for CS denominations */
+ struct TALER_EXCHANGE_BlindingPrepareHandle *blinding_prepare_handle;
+
+ /* The Handler for the actual call to the exchange */
+ struct TALER_EXCHANGE_WithdrawBlindedHandle *withdraw_blinded_handle;
+};
+
+
+/**
+ * We got a 200 OK response for the /withdraw operation.
+ * Extract the signatures and return them to the caller.
+ *
+ * @param wbh operation handle
+ * @param j_response reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+withdraw_blinded_ok (
+ struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh,
+ const json_t *j_response)
+{
+ struct TALER_EXCHANGE_WithdrawBlindedResponse 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 (wbh->num_input != json_array_size (j_sigs))
+ {
+ /* Number of coins generated does not match our expectation */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ struct TALER_BlindedDenominationSignature denoms_sig[wbh->num_input];
+
+ memset (denoms_sig,
+ 0,
+ sizeof(denoms_sig));
+
+ /* Reconstruct the coins and unblind the signatures */
+ {
+ json_t *j_sig;
+ size_t i;
+
+ json_array_foreach (j_sigs, i, j_sig)
+ {
+ struct GNUNET_JSON_Specification ispec[] = {
+ TALER_JSON_spec_blinded_denom_sig (NULL,
+ &denoms_sig[i]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j_sig,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+
+ response.details.ok.num_sigs = wbh->num_input;
+ response.details.ok.blinded_denom_sigs = denoms_sig;
+ response.details.ok.planchets_h = wbh->planchets_h;
+ wbh->callback (
+ wbh->callback_cls,
+ &response);
+ /* Make sure the callback isn't called again */
+ wbh->callback = NULL;
+ /* Free resources */
+ for (size_t i = 0; i < wbh->num_input; i++)
+ TALER_blinded_denom_sig_free (&denoms_sig[i]);
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * We got a 201 CREATED response for the /withdraw operation.
+ * Extract the noreveal_index and return it to the caller.
+ *
+ * @param wbh operation handle
+ * @param j_response reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+withdraw_blinded_created (
+ struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh,
+ const json_t *j_response)
+{
+ struct TALER_EXCHANGE_WithdrawBlindedResponse response = {
+ .hr.reply = j_response,
+ .hr.http_status = MHD_HTTP_CREATED,
+ .details.created.planchets_h = wbh->planchets_h,
+ .details.created.num_coins = wbh->num_input,
+ };
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint8 ("noreveal_index",
+ &response.details.created.noreveal_index),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &response.details.created.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_withdraw_age_confirmation_verify (
+ &wbh->planchets_h,
+ response.details.created.noreveal_index,
+ &response.details.created.exchange_pub,
+ &exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+
+ }
+
+ wbh->callback (wbh->callback_cls,
+ &response);
+ /* make sure the callback isn't called again */
+ wbh->callback = NULL;
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /withdraw request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_WithdrawBlindedHandle`
+ * @param response_code The HTTP response code
+ * @param response response data
+ */
+static void
+handle_withdraw_blinded_finished (
+ void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh = cls;
+ const json_t *j_response = response;
+ struct TALER_EXCHANGE_WithdrawBlindedResponse wbr = {
+ .hr.reply = j_response,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ wbh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ wbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ if (GNUNET_OK !=
+ withdraw_blinded_ok (
+ wbh,
+ j_response))
+ {
+ GNUNET_break_op (0);
+ wbr.hr.http_status = 0;
+ wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == wbh->callback);
+ TALER_EXCHANGE_withdraw_blinded_cancel (wbh);
+ return;
+ }
+ case MHD_HTTP_CREATED:
+ if (GNUNET_OK !=
+ withdraw_blinded_created (
+ wbh,
+ j_response))
+ {
+ GNUNET_break_op (0);
+ wbr.hr.http_status = 0;
+ wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == wbh->callback);
+ TALER_EXCHANGE_withdraw_blinded_cancel (wbh);
+ 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 */
+ wbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ wbr.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 */
+ wbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ wbr.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. */
+ wbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* The age requirements might not have been met */
+ wbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ wbr.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 */
+ wbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ /* only validate reply is well-formed */
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "h_payto",
+ &wbr.details.unavailable_for_legal_reasons.h_payto),
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &wbr.details.unavailable_for_legal_reasons.requirement_row),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ wbr.hr.http_status = 0;
+ wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ break;
+ }
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ wbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ wbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange withdraw\n",
+ (unsigned int) response_code,
+ (int) wbr.hr.ec);
+ break;
+ }
+ wbh->callback (wbh->callback_cls,
+ &wbr);
+ TALER_EXCHANGE_withdraw_blinded_cancel (wbh);
+}
+
+
+/**
+ * Runs the actual withdraw operation with the blinded planchets.
+ *
+ * @param[in,out] wbh withdraw blinded handle
+ */
+static void
+perform_withdraw_protocol (
+ struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh)
+{
+#define FAIL_IF(cond) \
+ do { \
+ if ((cond)) \
+ { \
+ GNUNET_break (! (cond)); \
+ goto ERROR; \
+ } \
+ } while (0)
+
+ json_t *j_denoms = NULL;
+ json_t *j_planchets = NULL;
+ json_t *j_request_body = NULL;
+ CURL *curlh = NULL;
+ struct GNUNET_HashContext *coins_hctx = NULL;
+ struct TALER_BlindedCoinHashP bch;
+
+ GNUNET_assert (0 < wbh->num_input);
+
+ FAIL_IF (GNUNET_OK !=
+ TALER_amount_set_zero (wbh->keys->currency,
+ &wbh->amount));
+ FAIL_IF (GNUNET_OK !=
+ TALER_amount_set_zero (wbh->keys->currency,
+ &wbh->fee));
+
+ /* Accumulate total value with fees */
+ for (size_t i = 0; i < wbh->num_input; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *dpub =
+ wbh->with_age_proof ?
+ wbh->blinded.with_age_proof_input[i].denom_pub :
+ wbh->blinded.input[i].denom_pub;
+
+ FAIL_IF (0 >
+ TALER_amount_add (&wbh->amount,
+ &wbh->amount,
+ &dpub->value));
+ FAIL_IF (0 >
+ TALER_amount_add (&wbh->fee,
+ &wbh->fee,
+ &dpub->fees.withdraw));
+
+ if (GNUNET_CRYPTO_BSA_CS ==
+ dpub->key.bsign_pub_key->cipher)
+ GNUNET_assert (NULL != wbh->blinding_seed);
+
+ }
+
+ if (wbh->with_age_proof || wbh->max_age > 0)
+ {
+ wbh->age_mask =
+ wbh->blinded.with_age_proof_input[0].denom_pub->key.age_mask;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Attempting to withdraw from reserve %s with maximum age %d to proof\n",
+ TALER_B2S (&wbh->reserve_pub),
+ wbh->max_age);
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Attempting to withdraw from reserve %s\n",
+ TALER_B2S (&wbh->reserve_pub));
+ }
+
+ coins_hctx = GNUNET_CRYPTO_hash_context_start ();
+ FAIL_IF (NULL == coins_hctx);
+
+ j_denoms = json_array ();
+ j_planchets = json_array ();
+ FAIL_IF ((NULL == j_denoms) ||
+ (NULL == j_planchets));
+
+ for (size_t i = 0; i< wbh->num_input; i++)
+ {
+ /* Build the denomination array */
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub =
+ wbh->with_age_proof ?
+ wbh->blinded.with_age_proof_input[i].denom_pub :
+ wbh->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 (wbh->with_age_proof &&
+ (wbh->age_mask.bits != denom_pub->key.age_mask.bits));
+
+ jdenom = GNUNET_JSON_from_data_auto (denom_h);
+ FAIL_IF (NULL == jdenom);
+ FAIL_IF (0 > json_array_append_new (j_denoms,
+ jdenom));
+ }
+
+
+ /* Build the planchet array and calculate the hash over all planchets. */
+ if (! wbh->with_age_proof)
+ {
+ for (size_t i = 0; i< wbh->num_input; i++)
+ {
+ const struct TALER_PlanchetDetail *planchet =
+ &wbh->blinded.input[i].planchet_details;
+ 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_planchets,
+ jc));
+
+ TALER_coin_ev_hash (&planchet->blinded_planchet,
+ &planchet->denom_pub_hash,
+ &bch);
+
+ GNUNET_CRYPTO_hash_context_read (coins_hctx,
+ &bch,
+ sizeof(bch));
+ }
+ }
+ else
+ { /* Age restricted case with required age-proof. */
+
+ /**
+ * We collect the run of all coin candidates for the same γ index
+ * first, then γ+1 etc.
+ */
+ for (size_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ struct GNUNET_HashContext *batch_ctx;
+ struct TALER_BlindedCoinHashP batch_h;
+
+ batch_ctx = GNUNET_CRYPTO_hash_context_start ();
+ FAIL_IF (NULL == batch_ctx);
+
+ for (size_t i = 0; i< wbh->num_input; i++)
+ {
+ const struct TALER_PlanchetDetail *planchet =
+ &wbh->blinded.with_age_proof_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_planchets,
+ jc));
+
+ TALER_coin_ev_hash (
+ &planchet->blinded_planchet,
+ &planchet->denom_pub_hash,
+ &bch);
+
+ GNUNET_CRYPTO_hash_context_read (
+ batch_ctx,
+ &bch,
+ sizeof(bch));
+ }
+
+ GNUNET_CRYPTO_hash_context_finish (
+ batch_ctx,
+ &batch_h.hash);
+ GNUNET_CRYPTO_hash_context_read (
+ coins_hctx,
+ &batch_h,
+ sizeof(batch_h));
+ }
+ }
+
+ /* Build the hash of the planchets */
+ GNUNET_CRYPTO_hash_context_finish (
+ coins_hctx,
+ &wbh->planchets_h.hash);
+ coins_hctx = NULL;
+
+ /* Sign the request */
+ TALER_wallet_withdraw_sign (
+ &wbh->amount,
+ &wbh->fee,
+ &wbh->planchets_h,
+ wbh->blinding_seed,
+ wbh->with_age_proof ? &wbh->age_mask: NULL,
+ wbh->with_age_proof ? wbh->max_age : 0,
+ wbh->reserve_priv,
+ &wbh->reserve_sig);
+
+ /* Initiate the POST-request */
+ j_request_body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ "ED25519"),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &wbh->reserve_pub),
+ GNUNET_JSON_pack_array_steal ("denoms_h",
+ j_denoms),
+ GNUNET_JSON_pack_array_steal ("coin_evs",
+ j_planchets),
+ GNUNET_JSON_pack_allow_null (
+ wbh->with_age_proof
+ ? GNUNET_JSON_pack_int64 ("max_age",
+ wbh->max_age)
+ : GNUNET_JSON_pack_string ("max_age",
+ NULL) ),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &wbh->reserve_sig));
+ FAIL_IF (NULL == j_request_body);
+
+ if (NULL != wbh->blinding_seed)
+ {
+ json_t *j_seed = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("blinding_seed",
+ wbh->blinding_seed));
+ GNUNET_assert (NULL != j_seed);
+ GNUNET_assert (0 ==
+ json_object_update_new (
+ j_request_body,
+ j_seed));
+ }
+
+ curlh = TALER_EXCHANGE_curl_easy_get_ (wbh->request_url);
+ FAIL_IF (NULL == curlh);
+ FAIL_IF (GNUNET_OK !=
+ TALER_curl_easy_post (
+ &wbh->post_ctx,
+ curlh,
+ j_request_body));
+ json_decref (j_request_body);
+ j_request_body = NULL;
+
+ wbh->job = GNUNET_CURL_job_add2 (
+ wbh->curl_ctx,
+ curlh,
+ wbh->post_ctx.headers,
+ &handle_withdraw_blinded_finished,
+ wbh);
+ FAIL_IF (NULL == wbh->job);
+
+ /* No errors, return */
+ return;
+
+ERROR:
+ if (NULL != coins_hctx)
+ GNUNET_CRYPTO_hash_context_abort (coins_hctx);
+ if (NULL != j_denoms)
+ json_decref (j_denoms);
+ if (NULL != j_planchets)
+ json_decref (j_planchets);
+ if (NULL != j_request_body)
+ json_decref (j_request_body);
+ if (NULL != curlh)
+ curl_easy_cleanup (curlh);
+ TALER_EXCHANGE_withdraw_blinded_cancel (wbh);
+ return;
+#undef FAIL_IF
+}
+
+
+/**
+ * @brief Callback to copy the results from the call to TALER_withdraw_blinded
+ * in the non-age-restricted case to the result for the originating call from TALER_withdraw.
+ *
+ * @param cls struct TALER_WithdrawHandle
+ * @param wbr The response
+ */
+static void
+copy_results (
+ void *cls,
+ const struct TALER_EXCHANGE_WithdrawBlindedResponse *wbr)
+{
+ /* The original handle from the top-level call to withdraw */
+ struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
+ struct TALER_EXCHANGE_WithdrawResponse resp = {
+ .hr = wbr->hr,
+ };
+
+ wh->withdraw_blinded_handle = NULL;
+
+ /**
+ * The withdraw protocol has been performed with blinded data.
+ * Now the response can be copied as is, except for the MHD_HTTP_OK case,
+ * in which we now need to perform the unblinding.
+ */
+ switch (wbr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ {
+ struct TALER_EXCHANGE_WithdrawCoinPrivateDetails
+ details[GNUNET_NZL (wh->num_coins)];
+ bool ok = true;
+
+ GNUNET_assert (wh->num_coins == wbr->details.ok.num_sigs);
+ memset (details,
+ 0,
+ sizeof(details));
+ resp.details.ok.num_sigs = wbr->details.ok.num_sigs;
+ resp.details.ok.coin_details = details;
+ resp.details.ok.planchets_h = wbr->details.ok.planchets_h;
+ for (size_t n = 0; n<wh->num_coins; n++)
+ {
+ const struct TALER_BlindedDenominationSignature *bsig =
+ &wbr->details.ok.blinded_denom_sigs[n];
+ struct CoinData *cd = &wh->coin_data[n];
+ struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *coin = &details[n];
+ struct TALER_FreshCoin fresh_coin;
+
+ *coin = wh->coin_data[n].candidates[0].details;
+ coin->planchet = wh->coin_data[n].planchet_details[0];
+ GNUNET_CRYPTO_eddsa_key_get_public (
+ &coin->coin_priv.eddsa_priv,
+ &coin->coin_pub.eddsa_pub);
+
+ if (GNUNET_OK !=
+ TALER_planchet_to_coin (&cd->denom_pub.key,
+ bsig,
+ &coin->blinding_key,
+ &coin->coin_priv,
+ &coin->h_age_commitment,
+ &coin->h_coin_pub,
+ &coin->blinding_values,
+ &fresh_coin))
+ {
+ resp.hr.http_status = 0;
+ resp.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE;
+ GNUNET_break_op (0);
+ ok = false;
+ break;
+ }
+ coin->denom_sig = fresh_coin.sig;
+ }
+ if (ok)
+ {
+ wh->callback (
+ wh->callback_cls,
+ &resp);
+ wh->callback = NULL;
+ }
+ for (size_t n = 0; n<wh->num_coins; n++)
+ {
+ struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *coin = &details[n];
+
+ TALER_denom_sig_free (&coin->denom_sig);
+ }
+ break;
+ }
+ case MHD_HTTP_CREATED:
+ resp.details.created = wbr->details.created;
+ break;
+
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ resp.details.unavailable_for_legal_reasons =
+ wbr->details.unavailable_for_legal_reasons;
+ break;
+
+ default:
+ /* nothing to do here, .hr.ec and .hr.hint are all set already from previous response */
+ break;
+ }
+ if (NULL != wh->callback)
+ {
+ wh->callback (
+ wh->callback_cls,
+ &resp);
+ wh->callback = NULL;
+ }
+ TALER_EXCHANGE_withdraw_cancel (wh);
+}
+
+
+/**
+ * @brief Callback to copy the results from the call to TALER_withdraw_blinded
+ * in the age-restricted case to the result for the originating call from TALER_withdraw.
+ *
+ * @param cls struct TALER_WithdrawHandle
+ * @param wbr The response
+ */
+static void
+copy_results_with_age_proof (
+ void *cls,
+ const struct TALER_EXCHANGE_WithdrawBlindedResponse *wbr)
+{
+ /* The original handle from the top-level call to withdraw */
+ struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
+ uint8_t k = wbr->details.created.noreveal_index;
+ struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details[wh->num_coins];
+ struct TALER_EXCHANGE_WithdrawResponse resp = {
+ .hr = wbr->hr,
+ };
+
+ wh->withdraw_blinded_handle = NULL;
+
+ switch (wbr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ /* in the age-restricted case, this should not happen */
+ GNUNET_break_op (0);
+ break;
+
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ resp.details.unavailable_for_legal_reasons =
+ wbr->details.unavailable_for_legal_reasons;
+ break;
+
+ case MHD_HTTP_CREATED:
+ {
+ GNUNET_assert (wh->num_coins == wbr->details.created.num_coins);
+ resp.details.created = wbr->details.created;
+ resp.details.created.coin_details = details;
+ resp.details.created.kappa_seed = wh->kappa_seed;
+ memset (details,
+ 0,
+ sizeof(details));
+ for (size_t n = 0; n< wh->num_coins; n++)
+ {
+ details[n] = wh->coin_data[n].candidates[k].details;
+ details[n].planchet = wh->coin_data[n].planchet_details[k];
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ wh->callback (
+ wh->callback_cls,
+ &resp);
+ wh->callback = NULL;
+ TALER_EXCHANGE_withdraw_cancel (wh);
+}
+
+
+/**
+ * @brief Prepares and executes TALER_EXCHANGE_withdraw_blinded.
+ * If there were CS-denominations involved, started once the all calls
+ * to /blinding-prepare are done.
+ */
+static void
+call_withdraw_blinded (
+ struct TALER_EXCHANGE_WithdrawHandle *wh)
+{
+
+ GNUNET_assert (NULL == wh->blinding_prepare_handle);
+
+ if (! wh->with_age_proof)
+ {
+ struct TALER_EXCHANGE_WithdrawBlindedCoinInput input[wh->num_coins];
+
+ memset (input,
+ 0,
+ sizeof(input));
+
+ /* Prepare the blinded planchets as input */
+ for (size_t n = 0; n < wh->num_coins; n++)
+ {
+ input[n].denom_pub =
+ &wh->coin_data[n].denom_pub;
+ input[n].planchet_details =
+ *wh->coin_data[n].planchet_details;
+ }
+
+ wh->withdraw_blinded_handle =
+ TALER_EXCHANGE_withdraw_blinded (
+ wh->curl_ctx,
+ wh->keys,
+ wh->exchange_url,
+ wh->reserve_priv,
+ wh->has_blinding_seed ? &wh->blinding_seed : NULL,
+ wh->num_coins,
+ input,
+ ©_results,
+ wh);
+ }
+ else
+ { /* age restricted case */
+ struct TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput
+ ari[wh->num_coins];
+
+ memset (ari,
+ 0,
+ sizeof(ari));
+
+ /* Prepare the blinded planchets as input */
+ for (size_t n = 0; n < wh->num_coins; n++)
+ {
+ ari[n].denom_pub = &wh->coin_data[n].denom_pub;
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ ari[n].planchet_details[k] =
+ wh->coin_data[n].planchet_details[k];
+ }
+
+ wh->withdraw_blinded_handle =
+ TALER_EXCHANGE_withdraw_blinded_with_age_proof (
+ wh->curl_ctx,
+ wh->keys,
+ wh->exchange_url,
+ wh->reserve_priv,
+ wh->has_blinding_seed ? &wh->blinding_seed : NULL,
+ wh->max_age,
+ wh->num_coins,
+ ari,
+ ©_results_with_age_proof,
+ wh);
+ }
+}
+
+
+/**
+ * @brief Function called when /blinding-prepare is finished
+ *
+ * @param cls the `struct BlindingPrepareClosure *`
+ * @param bpr replies from the /blinding-prepare request
+ */
+static void
+blinding_prepare_done (
+ void *cls,
+ const struct TALER_EXCHANGE_BlindingPrepareResponse *bpr)
+{
+ struct BlindingPrepareClosure *bpcls = cls;
+ struct TALER_EXCHANGE_WithdrawHandle *wh = bpcls->withdraw_handle;
+
+ wh->blinding_prepare_handle = NULL;
+ switch (bpr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ {
+ bool success = false;
+ size_t num = bpr->details.ok.num_blinding_values;
+
+ GNUNET_assert (0 != num);
+ GNUNET_assert (num == bpcls->num_nonces);
+ for (size_t i = 0; i < bpcls->num_prepare_coins; i++)
+ {
+ struct TALER_PlanchetDetail *planchet = bpcls->coins[i].planchet;
+ struct CoinCandidate *can = bpcls->coins[i].candidate;
+ size_t cs_idx = bpcls->coins[i].cs_idx;
+
+ GNUNET_assert (NULL != can);
+ GNUNET_assert (NULL != planchet);
+ success = false;
+
+ /* Complete the initialization of the coin with CS denomination */
+ TALER_denom_ewv_copy (
+ &can->details.blinding_values,
+ &bpr->details.ok.blinding_values[cs_idx]);
+
+ GNUNET_assert (GNUNET_CRYPTO_BSA_CS ==
+ can->details.blinding_values.blinding_inputs->cipher);
+
+ TALER_planchet_setup_coin_priv (
+ &can->details.secret,
+ &can->details.blinding_values,
+ &can->details.coin_priv);
+
+ TALER_planchet_blinding_secret_create (
+ &can->details.secret,
+ &can->details.blinding_values,
+ &can->details.blinding_key);
+
+ /* This initializes the 2nd half of the
+ can->planchet_detail.blinded_planchet */
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (
+ bpcls->coins[i].denom_pub,
+ &can->details.blinding_values,
+ &can->details.blinding_key,
+ &bpcls->nonces[cs_idx],
+ &can->details.coin_priv,
+ &can->details.h_age_commitment,
+ &can->details.h_coin_pub,
+ planchet))
+ {
+ GNUNET_break (0);
+ break;
+ }
+
+ TALER_coin_ev_hash (&planchet->blinded_planchet,
+ &planchet->denom_pub_hash,
+ &can->blinded_coin_h);
+ success = true;
+ }
+
+ /* /blinding-prepare is done, we can now perform the
+ * actual withdraw operation */
+ if (success)
+ call_withdraw_blinded (wh);
+ goto cleanup;
+ }
+ default:
+ {
+ /* We got an error condition during blinding prepare that we need to report */
+ struct TALER_EXCHANGE_WithdrawResponse resp = {
+ .hr = bpr->hr
+ };
+
+ wh->callback (
+ wh->callback_cls,
+ &resp);
+
+ wh->callback = NULL;
+ break;
+ }
+ }
+ TALER_EXCHANGE_withdraw_cancel (wh);
+cleanup:
+ GNUNET_free (bpcls->coins);
+ GNUNET_free (bpcls->nonces);
+ GNUNET_free (bpcls);
+}
+
+
+/**
+ * @brief Prepares non age-restricted coins for the call to withdraw and
+ * calculates the total amount with fees.
+ * For denomination with CS as cipher, initiates the preflight to retrieve the
+ * bpcls-parameter via /blinding-prepare.
+ * Note that only one of the three parameters seed, tuples or secrets must not be NULL
+ *
+ * @param wh The handler to the withdraw
+ * @param num_coins Number of coins to withdraw
+ * @param max_age The maximum age to commit to
+ * @param denoms_pub Array @e num_coins of denominations
+ * @param seed master seed from which to derive @e num_coins secrets and blinding, if @e blinding_seed is NULL
+ * @param blinding_seed master seed for the blinding. Might be NULL, in which case the blinding_seed is derived from @e seed
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
+ */
+static enum GNUNET_GenericReturnValue
+prepare_coins (
+ struct TALER_EXCHANGE_WithdrawHandle *wh,
+ size_t num_coins,
+ uint8_t max_age,
+ const struct TALER_EXCHANGE_DenomPublicKey *denoms_pub,
+ const struct TALER_WithdrawMasterSeedP *seed,
+ const struct TALER_BlindingMasterSeedP *blinding_seed)
+{
+ size_t cs_num = 0;
+ struct BlindingPrepareClosure *cs_closure;
+ uint8_t kappa;
+
+#define FAIL_IF(cond) \
+ do \
+ { \
+ if ((cond)) \
+ { \
+ GNUNET_break (! (cond)); \
+ goto ERROR; \
+ } \
+ } while (0)
+
+ GNUNET_assert (0 < num_coins);
+
+ wh->num_coins = num_coins;
+ wh->max_age = max_age;
+ wh->age_mask = denoms_pub[0].key.age_mask;
+ wh->coin_data = GNUNET_new_array (
+ wh->num_coins,
+ struct CoinData);
+
+ /* First, figure out how many Clause-Schnorr denominations we have */
+ for (size_t i =0; i< wh->num_coins; i++)
+ {
+ if (GNUNET_CRYPTO_BSA_CS ==
+ denoms_pub[i].key.bsign_pub_key->cipher)
+ cs_num++;
+ }
+
+ if (wh->with_age_proof)
+ {
+ kappa = TALER_CNC_KAPPA;
+ }
+ else
+ {
+ kappa = 1;
+ }
+
+ {
+ struct TALER_PlanchetMasterSecretP secrets[kappa][num_coins];
+ struct TALER_EXCHANGE_NonceKey cs_nonce_keys[GNUNET_NZL (cs_num)];
+ uint32_t cs_indices[GNUNET_NZL (cs_num)];
+
+ size_t cs_denom_idx = 0;
+ size_t cs_coin_idx = 0;
+
+ if (wh->with_age_proof)
+ {
+ TALER_withdraw_expand_kappa_seed (seed,
+ &wh->kappa_seed);
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ TALER_withdraw_expand_secrets (
+ num_coins,
+ &wh->kappa_seed.tuple[k],
+ secrets[k]);
+ }
+ }
+ else
+ {
+ TALER_withdraw_expand_secrets (
+ num_coins,
+ seed,
+ secrets[0]);
+ }
+
+ if (0 < cs_num)
+ {
+ memset (cs_nonce_keys,
+ 0,
+ sizeof(cs_nonce_keys));
+ cs_closure = GNUNET_new (struct BlindingPrepareClosure);
+ cs_closure->withdraw_handle = wh;
+ cs_closure->num_prepare_coins = cs_num * kappa;
+ GNUNET_assert ((1 == kappa) || (cs_num * kappa > cs_num));
+ cs_closure->coins =
+ GNUNET_new_array (cs_closure->num_prepare_coins,
+ struct BlindingPrepareCoinData);
+ cs_closure->num_nonces = cs_num;
+ cs_closure->nonces =
+ GNUNET_new_array (cs_closure->num_nonces,
+ union GNUNET_CRYPTO_BlindSessionNonce);
+ }
+ else
+ {
+ cs_closure = NULL;
+ }
+
+ for (uint32_t i = 0; i < wh->num_coins; i++)
+ {
+ struct CoinData *cd = &wh->coin_data[i];
+ bool age_denom = (0 != denoms_pub[i].key.age_mask.bits);
+
+ cd->denom_pub = denoms_pub[i];
+ /* The age mask must be the same for all coins */
+ FAIL_IF (wh->with_age_proof &&
+ (0 == denoms_pub[i].key.age_mask.bits));
+ FAIL_IF (wh->age_mask.bits !=
+ denoms_pub[i].key.age_mask.bits);
+ TALER_denom_pub_copy (&cd->denom_pub.key,
+ &denoms_pub[i].key);
+
+ /* Mark the indices of the coins which are of type Clause-Schnorr
+ * and add their denomination public key hash to the list.
+ */
+ if (GNUNET_CRYPTO_BSA_CS ==
+ cd->denom_pub.key.bsign_pub_key->cipher)
+ {
+ GNUNET_assert (cs_denom_idx<cs_num);
+ cs_indices[cs_denom_idx] = i;
+ cs_nonce_keys[cs_denom_idx].cnc_num = i;
+ cs_nonce_keys[cs_denom_idx].pk = &cd->denom_pub;
+ cs_denom_idx++;
+ }
+
+ /*
+ * Note that we "loop" here either only once (if with_age_proof is false),
+ * or TALER_CNC_KAPPA times.
+ */
+ for (uint8_t k = 0; k < kappa; k++)
+ {
+ struct CoinCandidate *can = &cd->candidates[k];
+ struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
+
+ can->details.secret = secrets[k][i];
+ /*
+ * The age restriction needs to be set on a coin if the denomination
+ * support age restriction. Note that his is regardless of weither
+ * with_age_proof is set or not.
+ */
+ if (age_denom)
+ {
+ /* Derive the age restriction from the given secret and
+ * the maximum age */
+ TALER_age_restriction_from_secret (
+ &can->details.secret,
+ &wh->age_mask,
+ wh->max_age,
+ &can->details.age_commitment_proof);
+
+ TALER_age_commitment_hash (
+ &can->details.age_commitment_proof.commitment,
+ &can->details.h_age_commitment);
+ }
+
+ switch (cd->denom_pub.key.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ TALER_denom_ewv_copy (&can->details.blinding_values,
+ TALER_denom_ewv_rsa_singleton ());
+ TALER_planchet_setup_coin_priv (&can->details.secret,
+ &can->details.blinding_values,
+ &can->details.coin_priv);
+ TALER_planchet_blinding_secret_create (&can->details.secret,
+ &can->details.blinding_values,
+ &can->details.blinding_key);
+ FAIL_IF (GNUNET_OK !=
+ TALER_planchet_prepare (&cd->denom_pub.key,
+ &can->details.blinding_values,
+ &can->details.blinding_key,
+ NULL,
+ &can->details.coin_priv,
+ (age_denom)
+ ? &can->details.h_age_commitment
+ : NULL,
+ &can->details.h_coin_pub,
+ planchet));
+ TALER_coin_ev_hash (&planchet->blinded_planchet,
+ &planchet->denom_pub_hash,
+ &can->blinded_coin_h);
+
+ break;
+
+ case GNUNET_CRYPTO_BSA_CS:
+ {
+ /**
+ * Prepare the nonce and save the index and the denomination for the callback
+ * after the call to blinding-prepare
+ */
+ cs_closure->coins[cs_coin_idx].candidate = can;
+ cs_closure->coins[cs_coin_idx].planchet = planchet;
+ cs_closure->coins[cs_coin_idx].denom_pub = &cd->denom_pub.key;
+ cs_closure->coins[cs_coin_idx].cs_idx = i;
+ cs_closure->coins[cs_coin_idx].age_denom = age_denom;
+ cs_coin_idx++;
+ break;
+ }
+ default:
+ FAIL_IF (1);
+ }
+ }
+ }
+
+ if (0 < cs_num)
+ {
+ if (NULL != blinding_seed)
+ {
+ wh->blinding_seed = *blinding_seed;
+ }
+ else
+ {
+ TALER_cs_withdraw_seed_to_blinding_seed (
+ seed,
+ &wh->blinding_seed);
+ }
+ wh->has_blinding_seed = true;
+
+ TALER_cs_derive_only_cs_blind_nonces_from_seed (
+ &wh->blinding_seed,
+ false, /* not for melt */
+ cs_num,
+ cs_indices,
+ cs_closure->nonces);
+
+ wh->blinding_prepare_handle =
+ TALER_EXCHANGE_blinding_prepare_for_withdraw (
+ wh->curl_ctx,
+ wh->exchange_url,
+ &wh->blinding_seed,
+ cs_num,
+ cs_nonce_keys,
+ &blinding_prepare_done,
+ cs_closure);
+ FAIL_IF (NULL == wh->blinding_prepare_handle);
+ }
+ }
+ return GNUNET_OK;
+
+ERROR:
+ if (0<cs_num)
+ {
+ GNUNET_free (cs_closure->nonces);
+ GNUNET_free (cs_closure);
+ }
+ TALER_EXCHANGE_withdraw_cancel (wh);
+ return GNUNET_SYSERR;
+#undef FAIL_IF
+
+}
+
+
+/**
+ * Prepare a withdraw handle for both, the non-restricted
+ * and age-restricted case.
+ *
+ * @param curl_ctx The curl context to use
+ * @param keys The keys from the exchange
+ * @param exchange_url The base url to the exchange
+ * @param reserve_priv The private key of the exchange
+ * @param res_cb The callback to call on response
+ * @param res_cb_cls The closure to pass to the callback
+ */
+static struct TALER_EXCHANGE_WithdrawHandle *
+setup_withdraw_handle (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ TALER_EXCHANGE_WithdrawCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_WithdrawHandle *wh;
+
+ wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle);
+ wh->exchange_url = exchange_url;
+ wh->keys = TALER_EXCHANGE_keys_incref (keys);
+ wh->curl_ctx = curl_ctx;
+ wh->reserve_priv = reserve_priv;
+ wh->callback = res_cb;
+ wh->callback_cls = res_cb_cls;
+
+ return wh;
+}
+
+
+struct TALER_EXCHANGE_WithdrawHandle *
+TALER_EXCHANGE_withdraw_extra_blinding_seed (
+ 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_DenomPublicKey denoms_pub[static num_coins],
+ const struct TALER_WithdrawMasterSeedP *seed,
+ const struct TALER_BlindingMasterSeedP *blinding_seed,
+ uint8_t opaque_max_age,
+ TALER_EXCHANGE_WithdrawCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_WithdrawHandle *wh;
+
+ wh = setup_withdraw_handle (curl_ctx,
+ keys,
+ exchange_url,
+ reserve_priv,
+ res_cb,
+ res_cb_cls);
+ GNUNET_assert (NULL != wh);
+ wh->with_age_proof = false;
+
+ if (GNUNET_OK !=
+ prepare_coins (wh,
+ num_coins,
+ opaque_max_age,
+ denoms_pub,
+ seed,
+ blinding_seed))
+ {
+ GNUNET_free (wh);
+ return NULL;
+ }
+
+ /* If there were no CS denominations, we can now perform the actual
+ * withdraw protocol. Otherwise, there are calls to /blinding-prepare
+ * in flight and once they finish, the withdraw-protocol will be
+ * called from within the blinding_prepare_done-function.
+ */
+ if (NULL == wh->blinding_prepare_handle)
+ call_withdraw_blinded (wh);
+
+ return wh;
+}
+
+
+struct TALER_EXCHANGE_WithdrawHandle *
+TALER_EXCHANGE_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_DenomPublicKey denoms_pub[static num_coins],
+ const struct TALER_WithdrawMasterSeedP *seed,
+ uint8_t opaque_max_age,
+ TALER_EXCHANGE_WithdrawCallback res_cb,
+ void *res_cb_cls)
+{
+ return TALER_EXCHANGE_withdraw_extra_blinding_seed (
+ curl_ctx,
+ keys,
+ exchange_url,
+ reserve_priv,
+ num_coins,
+ denoms_pub,
+ seed,
+ NULL,
+ opaque_max_age,
+ res_cb,
+ res_cb_cls
+ );
+}
+
+
+struct TALER_EXCHANGE_WithdrawHandle *
+TALER_EXCHANGE_withdraw_with_age_proof_extra_blinding_seed (
+ 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_DenomPublicKey denoms_pub[static num_coins],
+ const struct TALER_WithdrawMasterSeedP *seed,
+ const struct TALER_BlindingMasterSeedP *blinding_seed,
+ uint8_t max_age,
+ TALER_EXCHANGE_WithdrawCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_WithdrawHandle *wh;
+
+ wh = setup_withdraw_handle (curl_ctx,
+ keys,
+ exchange_url,
+ reserve_priv,
+ res_cb,
+ res_cb_cls);
+ GNUNET_assert (NULL != wh);
+
+ wh->with_age_proof = true;
+
+ if (GNUNET_OK !=
+ prepare_coins (wh,
+ num_coins,
+ max_age,
+ denoms_pub,
+ seed,
+ blinding_seed))
+ {
+ GNUNET_free (wh);
+ return NULL;
+ }
+
+ /* If there were no CS denominations, we can now perform the actual
+ * withdraw protocol. Otherwise, there are calls to /blinding-prepare
+ * in flight and once they finish, the withdraw-protocol will be
+ * called from within the blinding_prepare_done-function.
+ */
+ if (NULL == wh->blinding_prepare_handle)
+ call_withdraw_blinded (wh);
+
+ return wh;
+}
+
+
+struct TALER_EXCHANGE_WithdrawHandle *
+TALER_EXCHANGE_withdraw_with_age_proof (
+ 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_DenomPublicKey denoms_pub[static num_coins],
+ const struct TALER_WithdrawMasterSeedP *seed,
+ uint8_t max_age,
+ TALER_EXCHANGE_WithdrawCallback res_cb,
+ void *res_cb_cls)
+{
+ return TALER_EXCHANGE_withdraw_with_age_proof_extra_blinding_seed (
+ curl_ctx,
+ keys,
+ exchange_url,
+ reserve_priv,
+ num_coins,
+ denoms_pub,
+ seed,
+ NULL,
+ max_age,
+ res_cb,
+ res_cb_cls);
+}
+
+
+void
+TALER_EXCHANGE_withdraw_cancel (
+ struct TALER_EXCHANGE_WithdrawHandle *wh)
+{
+ uint8_t kappa = wh->with_age_proof ? TALER_CNC_KAPPA : 1;
+
+ /* Cleanup coin data */
+ for (unsigned int i = 0; i<wh->num_coins; i++)
+ {
+ struct CoinData *cd = &wh->coin_data[i];
+
+ for (uint8_t k = 0; k < kappa; k++)
+ {
+ struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
+ struct CoinCandidate *can = &cd->candidates[k];
+
+ TALER_blinded_planchet_free (&planchet->blinded_planchet);
+ TALER_denom_ewv_free (&can->details.blinding_values);
+ TALER_age_commitment_proof_free (&can->details.age_commitment_proof);
+ }
+ TALER_denom_pub_free (&cd->denom_pub.key);
+ }
+
+ TALER_EXCHANGE_blinding_prepare_cancel (wh->blinding_prepare_handle);
+ TALER_EXCHANGE_withdraw_blinded_cancel (wh->withdraw_blinded_handle);
+ wh->blinding_prepare_handle = NULL;
+ wh->withdraw_blinded_handle = NULL;
+
+ GNUNET_free (wh->coin_data);
+ TALER_EXCHANGE_keys_decref (wh->keys);
+ GNUNET_free (wh);
+}
+
+
+/**
+ * @brief Prepare the handler for blinded withdraw
+ *
+ * Allocates the handler struct and prepares all fields of the handler
+ * except the blinded planchets,
+ * which depend on them being age-restricted or not.
+ *
+ * @param curl_ctx the context for curl
+ * @param keys the exchange keys
+ * @param exchange_url the url to the exchange
+ * @param reserve_priv the reserve's private key
+ * @param res_cb the callback on result
+ * @param res_cb_cls the closure to pass on to the callback
+ * @return the handler
+ */
+static struct TALER_EXCHANGE_WithdrawBlindedHandle *
+setup_handler_common (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ TALER_EXCHANGE_WithdrawBlindedCallback res_cb,
+ void *res_cb_cls)
+{
+
+ struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh =
+ GNUNET_new (struct TALER_EXCHANGE_WithdrawBlindedHandle);
+
+ wbh->keys = TALER_EXCHANGE_keys_incref (keys);
+ wbh->curl_ctx = curl_ctx;
+ wbh->reserve_priv = reserve_priv;
+ wbh->callback = res_cb;
+ wbh->callback_cls = res_cb_cls;
+ wbh->request_url = TALER_url_join (exchange_url,
+ "withdraw",
+ NULL);
+ GNUNET_CRYPTO_eddsa_key_get_public (
+ &wbh->reserve_priv->eddsa_priv,
+ &wbh->reserve_pub.eddsa_pub);
+
+ return wbh;
+}
+
+
+struct TALER_EXCHANGE_WithdrawBlindedHandle *
+TALER_EXCHANGE_withdraw_blinded (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_BlindingMasterSeedP *blinding_seed,
+ size_t num_input,
+ const struct TALER_EXCHANGE_WithdrawBlindedCoinInput
+ blinded_input[static num_input],
+ TALER_EXCHANGE_WithdrawBlindedCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh =
+ setup_handler_common (curl_ctx,
+ keys,
+ exchange_url,
+ reserve_priv,
+ res_cb,
+ res_cb_cls);
+
+ wbh->with_age_proof = false;
+ wbh->num_input = num_input;
+ wbh->blinded.input = blinded_input;
+ wbh->blinding_seed = blinding_seed;
+
+ perform_withdraw_protocol (wbh);
+ return wbh;
+}
+
+
+struct TALER_EXCHANGE_WithdrawBlindedHandle *
+TALER_EXCHANGE_withdraw_blinded_with_age_proof (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_BlindingMasterSeedP *blinding_seed,
+ uint8_t max_age,
+ unsigned int num_input,
+ const struct TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput
+ blinded_input[static num_input],
+ TALER_EXCHANGE_WithdrawBlindedCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh =
+ setup_handler_common (curl_ctx,
+ keys,
+ exchange_url,
+ reserve_priv,
+ res_cb,
+ res_cb_cls);
+
+ wbh->with_age_proof = true;
+ wbh->max_age = max_age;
+ wbh->num_input = num_input;
+ wbh->blinded.with_age_proof_input = blinded_input;
+ wbh->blinding_seed = blinding_seed;
+
+ perform_withdraw_protocol (wbh);
+ return wbh;
+}
+
+
+void
+TALER_EXCHANGE_withdraw_blinded_cancel (
+ struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh)
+{
+ if (NULL == wbh)
+ return;
+ if (NULL != wbh->job)
+ {
+ GNUNET_CURL_job_cancel (wbh->job);
+ wbh->job = NULL;
+ }
+ GNUNET_free (wbh->request_url);
+ TALER_EXCHANGE_keys_decref (wbh->keys);
+ TALER_curl_easy_post_finished (&wbh->post_ctx);
+ GNUNET_free (wbh);
+}
+
+
+/* exchange_api_withdraw.c */
diff --git a/src/lib/exchange_api_purse_create_with_deposit.c b/src/lib/exchange_api_purse_create_with_deposit.c
@@ -1,656 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022-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_purse_create_with_deposit.c
- * @brief Implementation of the client to create a purse with
- * an initial set of deposits (and a contract)
- * @author Christian Grothoff
- */
-#include "taler/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/taler_json_lib.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_handle.h"
-#include "exchange_api_common.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * Information we track per deposited coin.
- */
-struct Deposit
-{
- /**
- * Coin's public key.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * Signature made with the coin.
- */
- struct TALER_CoinSpendSignatureP coin_sig;
-
- /**
- * Coin's denomination.
- */
- struct TALER_DenominationHashP h_denom_pub;
-
- /**
- * Age restriction hash for the coin.
- */
- struct TALER_AgeCommitmentHashP ahac;
-
- /**
- * How much did we say the coin contributed.
- */
- struct TALER_Amount contribution;
-};
-
-
-/**
- * @brief A purse create with deposit handle
- */
-struct TALER_EXCHANGE_PurseCreateDepositHandle
-{
-
- /**
- * The keys of the exchange this request handle will use
- */
- struct TALER_EXCHANGE_Keys *keys;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * The base URL of the exchange.
- */
- char *exchange_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_PurseCreateDepositCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Expected value in the purse after fees.
- */
- struct TALER_Amount purse_value_after_fees;
-
- /**
- * Our encrypted contract (if we had any).
- */
- struct TALER_EncryptedContract econtract;
-
- /**
- * Public key of the merge capability.
- */
- struct TALER_PurseMergePublicKeyP merge_pub;
-
- /**
- * Public key of the purse.
- */
- struct TALER_PurseContractPublicKeyP purse_pub;
-
- /**
- * Signature with the purse key on the request.
- */
- struct TALER_PurseContractSignatureP purse_sig;
-
- /**
- * Hash over the purse's contract terms.
- */
- struct TALER_PrivateContractHashP h_contract_terms;
-
- /**
- * When does the purse expire.
- */
- struct GNUNET_TIME_Timestamp purse_expiration;
-
- /**
- * Array of @e num_deposit deposits.
- */
- struct Deposit *deposits;
-
- /**
- * How many deposits did we make?
- */
- unsigned int num_deposits;
-
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP /deposit request.
- *
- * @param cls the `struct TALER_EXCHANGE_DepositHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_purse_create_deposit_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_PurseCreateDepositHandle *pch = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_PurseCreateDepositResponse dr = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
- const struct TALER_EXCHANGE_Keys *keys = pch->keys;
-
- pch->job = NULL;
- switch (response_code)
- {
- case 0:
- dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- struct GNUNET_TIME_Timestamp etime;
- struct TALER_Amount total_deposited;
- struct TALER_ExchangeSignatureP exchange_sig;
- struct TALER_ExchangePublicKeyP exchange_pub;
- 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_timestamp ("exchange_timestamp",
- &etime),
- TALER_JSON_spec_amount ("total_deposited",
- pch->purse_value_after_fees.currency,
- &total_deposited),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (keys,
- &exchange_pub))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID;
- break;
- }
- if (GNUNET_OK !=
- TALER_exchange_online_purse_created_verify (
- etime,
- pch->purse_expiration,
- &pch->purse_value_after_fees,
- &total_deposited,
- &pch->purse_pub,
- &pch->h_contract_terms,
- &exchange_pub,
- &exchange_sig))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_EXCHANGE_PURSE_CREATE_EXCHANGE_SIGNATURE_INVALID;
- break;
- }
- }
- 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 */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_FORBIDDEN:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- /* 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 */
- break;
- case MHD_HTTP_NOT_FOUND:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- break;
- case MHD_HTTP_CONFLICT:
- {
- dr.hr.ec = TALER_JSON_get_error_code (j);
- switch (dr.hr.ec)
- {
- case TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA:
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_purse_create_conflict_ (
- &pch->purse_sig,
- &pch->purse_pub,
- j))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- break;
- case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
- /* Nothing to check anymore here, proof needs to be
- checked in the GET /coins/$COIN_PUB handler */
- break;
- case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
- // FIXME #7267: write check (add to exchange_api_common! */
- break;
- case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA:
- {
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_CoinSpendSignatureP coin_sig;
- struct TALER_DenominationHashP h_denom_pub;
- struct TALER_AgeCommitmentHashP phac;
- bool found = false;
-
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_purse_coin_conflict_ (
- &pch->purse_pub,
- pch->exchange_url,
- j,
- &h_denom_pub,
- &phac,
- &coin_pub,
- &coin_sig))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- for (unsigned int i = 0; i<pch->num_deposits; i++)
- {
- struct Deposit *deposit = &pch->deposits[i];
-
- if (0 !=
- GNUNET_memcmp (&coin_pub,
- &deposit->coin_pub))
- continue;
- if (0 !=
- GNUNET_memcmp (&deposit->h_denom_pub,
- &h_denom_pub))
- {
- found = true;
- break;
- }
- if (0 !=
- GNUNET_memcmp (&deposit->ahac,
- &phac))
- {
- found = true;
- break;
- }
- if (0 ==
- GNUNET_memcmp (&coin_sig,
- &deposit->coin_sig))
- {
- GNUNET_break_op (0);
- continue;
- }
- found = true;
- break;
- }
- if (! found)
- {
- /* conflict is for a different coin! */
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- }
- case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA:
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_purse_econtract_conflict_ (
- &pch->econtract.econtract_sig,
- &pch->purse_pub,
- j))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- break;
- default:
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected error code %d for conflcting deposit\n",
- dr.hr.ec);
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- }
- }
- 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 */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.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 */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange deposit\n",
- (unsigned int) response_code,
- dr.hr.ec);
- GNUNET_break_op (0);
- break;
- }
- pch->cb (pch->cb_cls,
- &dr);
- TALER_EXCHANGE_purse_create_with_deposit_cancel (pch);
-}
-
-
-struct TALER_EXCHANGE_PurseCreateDepositHandle *
-TALER_EXCHANGE_purse_create_with_deposit (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_PurseContractPrivateKeyP *purse_priv,
- const struct TALER_PurseMergePrivateKeyP *merge_priv,
- const struct TALER_ContractDiffiePrivateP *contract_priv,
- const json_t *contract_terms,
- unsigned int num_deposits,
- const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits],
- bool upload_contract,
- TALER_EXCHANGE_PurseCreateDepositCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_PurseCreateDepositHandle *pch;
- json_t *create_obj;
- json_t *deposit_arr;
- CURL *eh;
- char arg_str[sizeof (pch->purse_pub) * 2 + 32];
- uint32_t min_age = 0;
-
- pch = GNUNET_new (struct TALER_EXCHANGE_PurseCreateDepositHandle);
- pch->cb = cb;
- pch->cb_cls = cb_cls;
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_timestamp ("pay_deadline",
- &pch->purse_expiration),
- TALER_JSON_spec_amount_any ("amount",
- &pch->purse_value_after_fees),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint32 ("minimum_age",
- &min_age),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (contract_terms,
- spec,
- NULL, NULL))
- {
- GNUNET_break (0);
- return NULL;
- }
- }
- if (GNUNET_OK !=
- TALER_JSON_contract_hash (contract_terms,
- &pch->h_contract_terms))
- {
- GNUNET_break (0);
- return NULL;
- }
- GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv,
- &pch->purse_pub.eddsa_pub);
- {
- char pub_str[sizeof (pch->purse_pub) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- &pch->purse_pub,
- sizeof (pch->purse_pub),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "purses/%s/create",
- pub_str);
- }
- GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
- &pch->merge_pub.eddsa_pub);
- pch->url = TALER_url_join (url,
- arg_str,
- NULL);
- if (NULL == pch->url)
- {
- GNUNET_break (0);
- GNUNET_free (pch);
- return NULL;
- }
- pch->num_deposits = num_deposits;
- pch->deposits = GNUNET_new_array (num_deposits,
- struct Deposit);
- deposit_arr = json_array ();
- GNUNET_assert (NULL != deposit_arr);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Signing with URL `%s'\n",
- url);
- for (unsigned int i = 0; i<num_deposits; i++)
- {
- const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i];
- const struct TALER_AgeCommitmentProof *acp = deposit->age_commitment_proof;
- struct Deposit *d = &pch->deposits[i];
- json_t *jdeposit;
- struct TALER_AgeCommitmentHashP *aghp = NULL;
- struct TALER_AgeAttestationP attest;
- struct TALER_AgeAttestationP *attestp = NULL;
-
- if (NULL != acp)
- {
- TALER_age_commitment_hash (&acp->commitment,
- &d->ahac);
- aghp = &d->ahac;
- if (GNUNET_OK !=
- TALER_age_commitment_attest (acp,
- min_age,
- &attest))
- {
- GNUNET_break (0);
- GNUNET_array_grow (pch->deposits,
- pch->num_deposits,
- 0);
- GNUNET_free (pch->url);
- json_decref (deposit_arr);
- GNUNET_free (pch);
- return NULL;
- }
- }
- d->contribution = deposit->amount;
- d->h_denom_pub = deposit->h_denom_pub;
- GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv,
- &d->coin_pub.eddsa_pub);
- TALER_wallet_purse_deposit_sign (
- url,
- &pch->purse_pub,
- &deposit->amount,
- &d->h_denom_pub,
- &d->ahac,
- &deposit->coin_priv,
- &d->coin_sig);
- jdeposit = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_auto ("h_age_commitment",
- aghp)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_auto ("age_attestation",
- attestp)),
- TALER_JSON_pack_amount ("amount",
- &deposit->amount),
- GNUNET_JSON_pack_data_auto ("denom_pub_hash",
- &deposit->h_denom_pub),
- TALER_JSON_pack_denom_sig ("ub_sig",
- &deposit->denom_sig),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &d->coin_sig),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- &d->coin_pub));
- GNUNET_assert (0 ==
- json_array_append_new (deposit_arr,
- jdeposit));
- }
- TALER_wallet_purse_create_sign (pch->purse_expiration,
- &pch->h_contract_terms,
- &pch->merge_pub,
- min_age,
- &pch->purse_value_after_fees,
- purse_priv,
- &pch->purse_sig);
- if (upload_contract)
- {
- TALER_CRYPTO_contract_encrypt_for_merge (&pch->purse_pub,
- contract_priv,
- merge_priv,
- contract_terms,
- &pch->econtract.econtract,
- &pch->econtract.econtract_size);
- GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv,
- &pch->econtract.contract_pub.ecdhe_pub);
- TALER_wallet_econtract_upload_sign (pch->econtract.econtract,
- pch->econtract.econtract_size,
- &pch->econtract.contract_pub,
- purse_priv,
- &pch->econtract.econtract_sig);
- }
- create_obj = GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("amount",
- &pch->purse_value_after_fees),
- GNUNET_JSON_pack_uint64 ("min_age",
- min_age),
- GNUNET_JSON_pack_allow_null (
- TALER_JSON_pack_econtract ("econtract",
- upload_contract
- ? &pch->econtract
- : NULL)),
- GNUNET_JSON_pack_data_auto ("purse_sig",
- &pch->purse_sig),
- GNUNET_JSON_pack_data_auto ("merge_pub",
- &pch->merge_pub),
- GNUNET_JSON_pack_data_auto ("h_contract_terms",
- &pch->h_contract_terms),
- GNUNET_JSON_pack_timestamp ("purse_expiration",
- pch->purse_expiration),
- GNUNET_JSON_pack_array_steal ("deposits",
- deposit_arr));
- GNUNET_assert (NULL != create_obj);
- eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
- if ( (NULL == eh) ||
- (GNUNET_OK !=
- TALER_curl_easy_post (&pch->ctx,
- eh,
- create_obj)) )
- {
- GNUNET_break (0);
- if (NULL != eh)
- curl_easy_cleanup (eh);
- json_decref (create_obj);
- GNUNET_free (pch->econtract.econtract);
- GNUNET_array_grow (pch->deposits,
- pch->num_deposits,
- 0);
- GNUNET_free (pch->url);
- GNUNET_free (pch);
- return NULL;
- }
- json_decref (create_obj);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "URL for purse create with deposit: `%s'\n",
- pch->url);
- pch->keys = TALER_EXCHANGE_keys_incref (keys);
- pch->exchange_url = GNUNET_strdup (url);
- pch->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- pch->ctx.headers,
- &handle_purse_create_deposit_finished,
- pch);
- return pch;
-}
-
-
-void
-TALER_EXCHANGE_purse_create_with_deposit_cancel (
- struct TALER_EXCHANGE_PurseCreateDepositHandle *pch)
-{
- if (NULL != pch->job)
- {
- GNUNET_CURL_job_cancel (pch->job);
- pch->job = NULL;
- }
- GNUNET_free (pch->econtract.econtract);
- GNUNET_free (pch->exchange_url);
- GNUNET_free (pch->url);
- GNUNET_array_grow (pch->deposits,
- pch->num_deposits,
- 0);
- TALER_EXCHANGE_keys_decref (pch->keys);
- TALER_curl_easy_post_finished (&pch->ctx);
- GNUNET_free (pch);
-}
-
-
-/* end of exchange_api_purse_create_with_deposit.c */
diff --git a/src/lib/exchange_api_purse_create_with_merge.c b/src/lib/exchange_api_purse_create_with_merge.c
@@ -1,580 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022-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_purse_create_with_merge.c
- * @brief Implementation of the client to create a
- * purse for an account
- * @author Christian Grothoff
- */
-#include "taler/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/taler_json_lib.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_handle.h"
-#include "exchange_api_common.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A purse create with merge handle
- */
-struct TALER_EXCHANGE_PurseCreateMergeHandle
-{
-
- /**
- * The keys of the exchange this request handle will use
- */
- struct TALER_EXCHANGE_Keys *keys;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * The exchange base URL.
- */
- char *exchange_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_PurseCreateMergeCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * The encrypted contract (if any).
- */
- struct TALER_EncryptedContract econtract;
-
- /**
- * Expected value in the purse after fees.
- */
- struct TALER_Amount purse_value_after_fees;
-
- /**
- * Public key of the reserve public key.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Reserve signature affirming our merge.
- */
- struct TALER_ReserveSignatureP reserve_sig;
-
- /**
- * Merge capability key.
- */
- struct TALER_PurseMergePublicKeyP merge_pub;
-
- /**
- * Our merge signature (if any).
- */
- struct TALER_PurseMergeSignatureP merge_sig;
-
- /**
- * Public key of the purse.
- */
- struct TALER_PurseContractPublicKeyP purse_pub;
-
- /**
- * Request data we signed over.
- */
- struct TALER_PurseContractSignatureP purse_sig;
-
- /**
- * Hash over the purse's contrac terms.
- */
- struct TALER_PrivateContractHashP h_contract_terms;
-
- /**
- * When does the purse expire.
- */
- struct GNUNET_TIME_Timestamp purse_expiration;
-
- /**
- * When does the purse get merged/created.
- */
- struct GNUNET_TIME_Timestamp merge_timestamp;
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP /reserves/$RID/purse request.
- *
- * @param cls the `struct TALER_EXCHANGE_PurseCreateMergeHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_purse_create_with_merge_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_PurseCreateMergeResponse dr = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code,
- .reserve_sig = &pcm->reserve_sig
- };
-
- pcm->job = NULL;
- switch (response_code)
- {
- case 0:
- dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- struct GNUNET_TIME_Timestamp etime;
- struct TALER_Amount total_deposited;
- struct TALER_ExchangeSignatureP exchange_sig;
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("total_deposited",
- &total_deposited),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &exchange_pub),
- GNUNET_JSON_spec_timestamp ("exchange_timestamp",
- &etime),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (pcm->keys,
- &exchange_pub))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (GNUNET_OK !=
- TALER_exchange_online_purse_created_verify (
- etime,
- pcm->purse_expiration,
- &pcm->purse_value_after_fees,
- &total_deposited,
- &pcm->purse_pub,
- &pcm->h_contract_terms,
- &exchange_pub,
- &exchange_sig))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- }
- 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 */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_FORBIDDEN:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- /* 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 */
- break;
- case MHD_HTTP_NOT_FOUND:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- break;
- case MHD_HTTP_CONFLICT:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- switch (dr.hr.ec)
- {
- case TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_CONFLICTING_META_DATA:
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_purse_create_conflict_ (
- &pcm->purse_sig,
- &pcm->purse_pub,
- j))
- {
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- break;
- case TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_CONFLICTING_META_DATA:
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_purse_merge_conflict_ (
- &pcm->merge_sig,
- &pcm->merge_pub,
- &pcm->purse_pub,
- pcm->exchange_url,
- j))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- break;
- case TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_INSUFFICIENT_FUNDS:
- /* nothing to verify */
- break;
- case TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA:
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_purse_econtract_conflict_ (
- &pcm->econtract.econtract_sig,
- &pcm->purse_pub,
- j))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- break;
- default:
- /* unexpected EC! */
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- } /* end inner (EC) switch */
- 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 */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint64 (
- "requirement_row",
- &dr.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);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- }
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.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 */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange deposit\n",
- (unsigned int) response_code,
- dr.hr.ec);
- GNUNET_break_op (0);
- break;
- }
- pcm->cb (pcm->cb_cls,
- &dr);
- TALER_EXCHANGE_purse_create_with_merge_cancel (pcm);
-}
-
-
-struct TALER_EXCHANGE_PurseCreateMergeHandle *
-TALER_EXCHANGE_purse_create_with_merge (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- const struct TALER_PurseContractPrivateKeyP *purse_priv,
- const struct TALER_PurseMergePrivateKeyP *merge_priv,
- const struct TALER_ContractDiffiePrivateP *contract_priv,
- const json_t *contract_terms,
- bool upload_contract,
- bool pay_for_purse,
- struct GNUNET_TIME_Timestamp merge_timestamp,
- TALER_EXCHANGE_PurseCreateMergeCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm;
- json_t *create_with_merge_obj;
- CURL *eh;
- char arg_str[sizeof (pcm->reserve_pub) * 2 + 32];
- uint32_t min_age = 0;
- struct TALER_Amount purse_fee;
- enum TALER_WalletAccountMergeFlags flags;
-
- pcm = GNUNET_new (struct TALER_EXCHANGE_PurseCreateMergeHandle);
- pcm->cb = cb;
- pcm->cb_cls = cb_cls;
- if (GNUNET_OK !=
- TALER_JSON_contract_hash (contract_terms,
- &pcm->h_contract_terms))
- {
- GNUNET_break (0);
- GNUNET_free (pcm);
- return NULL;
- }
- pcm->merge_timestamp = merge_timestamp;
- GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv,
- &pcm->purse_pub.eddsa_pub);
- GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
- &pcm->reserve_pub.eddsa_pub);
- GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
- &pcm->merge_pub.eddsa_pub);
-
- {
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("amount",
- &pcm->purse_value_after_fees),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint32 ("minimum_age",
- &min_age),
- NULL),
- GNUNET_JSON_spec_timestamp ("pay_deadline",
- &pcm->purse_expiration),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (contract_terms,
- spec,
- NULL, NULL))
- {
- GNUNET_break (0);
- GNUNET_free (pcm);
- return NULL;
- }
- }
- if (pay_for_purse)
- {
- const struct TALER_EXCHANGE_GlobalFee *gf;
-
- gf = TALER_EXCHANGE_get_global_fee (
- keys,
- GNUNET_TIME_timestamp_get ());
- purse_fee = gf->fees.purse;
- flags = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
- }
- else
- {
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (pcm->purse_value_after_fees.currency,
- &purse_fee));
- flags = TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA;
- }
-
- {
- char pub_str[sizeof (pcm->reserve_pub) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- &pcm->reserve_pub,
- sizeof (pcm->reserve_pub),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "reserves/%s/purse",
- pub_str);
- }
- pcm->url = TALER_url_join (url,
- arg_str,
- NULL);
- if (NULL == pcm->url)
- {
- GNUNET_break (0);
- GNUNET_free (pcm);
- return NULL;
- }
- TALER_wallet_purse_create_sign (pcm->purse_expiration,
- &pcm->h_contract_terms,
- &pcm->merge_pub,
- min_age,
- &pcm->purse_value_after_fees,
- purse_priv,
- &pcm->purse_sig);
- {
- struct TALER_NormalizedPayto payto_uri;
-
- payto_uri = TALER_reserve_make_payto (url,
- &pcm->reserve_pub);
- TALER_wallet_purse_merge_sign (payto_uri,
- merge_timestamp,
- &pcm->purse_pub,
- merge_priv,
- &pcm->merge_sig);
- GNUNET_free (payto_uri.normalized_payto);
- }
- TALER_wallet_account_merge_sign (merge_timestamp,
- &pcm->purse_pub,
- pcm->purse_expiration,
- &pcm->h_contract_terms,
- &pcm->purse_value_after_fees,
- &purse_fee,
- min_age,
- flags,
- reserve_priv,
- &pcm->reserve_sig);
- if (upload_contract)
- {
- TALER_CRYPTO_contract_encrypt_for_deposit (
- &pcm->purse_pub,
- contract_priv,
- contract_terms,
- &pcm->econtract.econtract,
- &pcm->econtract.econtract_size);
- GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv,
- &pcm->econtract.contract_pub.ecdhe_pub);
- TALER_wallet_econtract_upload_sign (
- pcm->econtract.econtract,
- pcm->econtract.econtract_size,
- &pcm->econtract.contract_pub,
- purse_priv,
- &pcm->econtract.econtract_sig);
- }
- create_with_merge_obj = GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("purse_value",
- &pcm->purse_value_after_fees),
- GNUNET_JSON_pack_uint64 ("min_age",
- min_age),
- GNUNET_JSON_pack_allow_null (
- TALER_JSON_pack_econtract ("econtract",
- upload_contract
- ? &pcm->econtract
- : NULL)),
- GNUNET_JSON_pack_allow_null (
- pay_for_purse
- ? TALER_JSON_pack_amount ("purse_fee",
- &purse_fee)
- : GNUNET_JSON_pack_string ("dummy2",
- NULL)),
- GNUNET_JSON_pack_data_auto ("merge_pub",
- &pcm->merge_pub),
- GNUNET_JSON_pack_data_auto ("merge_sig",
- &pcm->merge_sig),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &pcm->reserve_sig),
- GNUNET_JSON_pack_data_auto ("purse_pub",
- &pcm->purse_pub),
- GNUNET_JSON_pack_data_auto ("purse_sig",
- &pcm->purse_sig),
- GNUNET_JSON_pack_data_auto ("h_contract_terms",
- &pcm->h_contract_terms),
- GNUNET_JSON_pack_timestamp ("merge_timestamp",
- merge_timestamp),
- GNUNET_JSON_pack_timestamp ("purse_expiration",
- pcm->purse_expiration));
- GNUNET_assert (NULL != create_with_merge_obj);
- eh = TALER_EXCHANGE_curl_easy_get_ (pcm->url);
- if ( (NULL == eh) ||
- (GNUNET_OK !=
- TALER_curl_easy_post (&pcm->ctx,
- eh,
- create_with_merge_obj)) )
- {
- GNUNET_break (0);
- if (NULL != eh)
- curl_easy_cleanup (eh);
- json_decref (create_with_merge_obj);
- GNUNET_free (pcm->econtract.econtract);
- GNUNET_free (pcm->url);
- GNUNET_free (pcm);
- return NULL;
- }
- json_decref (create_with_merge_obj);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "URL for purse create_with_merge: `%s'\n",
- pcm->url);
- pcm->keys = TALER_EXCHANGE_keys_incref (keys);
- pcm->exchange_url = GNUNET_strdup (url);
- pcm->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- pcm->ctx.headers,
- &handle_purse_create_with_merge_finished,
- pcm);
- return pcm;
-}
-
-
-void
-TALER_EXCHANGE_purse_create_with_merge_cancel (
- struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm)
-{
- if (NULL != pcm->job)
- {
- GNUNET_CURL_job_cancel (pcm->job);
- pcm->job = NULL;
- }
- GNUNET_free (pcm->url);
- GNUNET_free (pcm->exchange_url);
- TALER_curl_easy_post_finished (&pcm->ctx);
- TALER_EXCHANGE_keys_decref (pcm->keys);
- GNUNET_free (pcm->econtract.econtract);
- GNUNET_free (pcm);
-}
-
-
-/* end of exchange_api_purse_create_with_merge.c */
diff --git a/src/lib/exchange_api_purse_delete.c b/src/lib/exchange_api_purse_delete.c
@@ -1,243 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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_purse_delete.c
- * @brief Implementation of the client to delete a purse
- * into an account
- * @author Christian Grothoff
- */
-#include "taler/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/taler_json_lib.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_handle.h"
-#include "exchange_api_common.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A purse delete with deposit handle
- */
-struct TALER_EXCHANGE_PurseDeleteHandle
-{
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_PurseDeleteCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Header with the purse_sig.
- */
- struct curl_slist *xhdr;
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP DELETE /purse/$PID request.
- *
- * @param cls the `struct TALER_EXCHANGE_PurseDeleteHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_purse_delete_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_PurseDeleteHandle *pdh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_PurseDeleteResponse dr = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
-
- pdh->job = NULL;
- switch (response_code)
- {
- case 0:
- dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_NO_CONTENT:
- 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 */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_FORBIDDEN:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- /* 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 */
- break;
- case MHD_HTTP_NOT_FOUND:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- break;
- case MHD_HTTP_CONFLICT:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.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 */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange deposit\n",
- (unsigned int) response_code,
- dr.hr.ec);
- GNUNET_break_op (0);
- break;
- }
- pdh->cb (pdh->cb_cls,
- &dr);
- TALER_EXCHANGE_purse_delete_cancel (pdh);
-}
-
-
-struct TALER_EXCHANGE_PurseDeleteHandle *
-TALER_EXCHANGE_purse_delete (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- const struct TALER_PurseContractPrivateKeyP *purse_priv,
- TALER_EXCHANGE_PurseDeleteCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_PurseDeleteHandle *pdh;
- CURL *eh;
- struct TALER_PurseContractPublicKeyP purse_pub;
- struct TALER_PurseContractSignatureP purse_sig;
- char arg_str[sizeof (purse_pub) * 2 + 32];
-
- pdh = GNUNET_new (struct TALER_EXCHANGE_PurseDeleteHandle);
- pdh->cb = cb;
- pdh->cb_cls = cb_cls;
- GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv->eddsa_priv,
- &purse_pub.eddsa_pub);
- {
- char pub_str[sizeof (purse_pub) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (&purse_pub,
- sizeof (purse_pub),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "purses/%s",
- pub_str);
- }
- pdh->url = TALER_url_join (url,
- arg_str,
- NULL);
- if (NULL == pdh->url)
- {
- GNUNET_break (0);
- GNUNET_free (pdh);
- return NULL;
- }
- TALER_wallet_purse_delete_sign (purse_priv,
- &purse_sig);
- {
- char *delete_str;
- char *xhdr;
-
- delete_str =
- GNUNET_STRINGS_data_to_string_alloc (&purse_sig,
- sizeof (purse_sig));
- GNUNET_asprintf (&xhdr,
- "Taler-Purse-Signature: %s",
- delete_str);
- GNUNET_free (delete_str);
- pdh->xhdr = curl_slist_append (NULL,
- xhdr);
- GNUNET_free (xhdr);
- }
- eh = TALER_EXCHANGE_curl_easy_get_ (pdh->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- curl_slist_free_all (pdh->xhdr);
- GNUNET_free (pdh->url);
- GNUNET_free (pdh);
- return NULL;
- }
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_CUSTOMREQUEST,
- MHD_HTTP_METHOD_DELETE));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "URL for purse delete: `%s'\n",
- pdh->url);
- pdh->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- pdh->xhdr,
- &handle_purse_delete_finished,
- pdh);
- return pdh;
-}
-
-
-void
-TALER_EXCHANGE_purse_delete_cancel (
- struct TALER_EXCHANGE_PurseDeleteHandle *pdh)
-{
- if (NULL != pdh->job)
- {
- GNUNET_CURL_job_cancel (pdh->job);
- pdh->job = NULL;
- }
- curl_slist_free_all (pdh->xhdr);
- GNUNET_free (pdh->url);
- GNUNET_free (pdh);
-}
-
-
-/* end of exchange_api_purse_delete.c */
diff --git a/src/lib/exchange_api_purse_deposit.c b/src/lib/exchange_api_purse_deposit.c
@@ -1,520 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022-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_purse_deposit.c
- * @brief Implementation of the client to create a purse with
- * an initial set of deposits (and a contract)
- * @author Christian Grothoff
- */
-#include "taler/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/taler_json_lib.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_common.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * Information we track per coin.
- */
-struct Coin
-{
- /**
- * Coin's public key.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * Signature made with the coin.
- */
- struct TALER_CoinSpendSignatureP coin_sig;
-
- /**
- * Coin's denomination.
- */
- struct TALER_DenominationHashP h_denom_pub;
-
- /**
- * Age restriction hash for the coin.
- */
- struct TALER_AgeCommitmentHashP ahac;
-
- /**
- * How much did we say the coin contributed.
- */
- struct TALER_Amount contribution;
-};
-
-
-/**
- * @brief A purse create with deposit handle
- */
-struct TALER_EXCHANGE_PurseDepositHandle
-{
-
- /**
- * The keys of the exchange this request handle will use
- */
- struct TALER_EXCHANGE_Keys *keys;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * The base url of the exchange we are talking to.
- */
- char *base_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_PurseDepositCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Public key of the purse.
- */
- struct TALER_PurseContractPublicKeyP purse_pub;
-
- /**
- * Array of @e num_deposits coins we are depositing.
- */
- struct Coin *coins;
-
- /**
- * Number of coins we are depositing.
- */
- unsigned int num_deposits;
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP /purses/$PID/deposit request.
- *
- * @param cls the `struct TALER_EXCHANGE_PurseDepositHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_purse_deposit_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_PurseDepositHandle *pch = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_PurseDepositResponse dr = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
- const struct TALER_EXCHANGE_Keys *keys = pch->keys;
-
- pch->job = NULL;
- switch (response_code)
- {
- case 0:
- dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- struct GNUNET_TIME_Timestamp etime;
- struct TALER_ExchangeSignatureP exchange_sig;
- struct TALER_ExchangePublicKeyP exchange_pub;
- 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_fixed_auto ("h_contract_terms",
- &dr.details.ok.h_contract_terms),
- GNUNET_JSON_spec_timestamp ("exchange_timestamp",
- &etime),
- GNUNET_JSON_spec_timestamp ("purse_expiration",
- &dr.details.ok.purse_expiration),
- TALER_JSON_spec_amount ("total_deposited",
- keys->currency,
- &dr.details.ok.total_deposited),
- TALER_JSON_spec_amount ("purse_value_after_fees",
- keys->currency,
- &dr.details.ok.purse_value_after_fees),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (keys,
- &exchange_pub))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_EXCHANGE_PURSE_DEPOSIT_EXCHANGE_SIGNATURE_INVALID;
- break;
- }
- if (GNUNET_OK !=
- TALER_exchange_online_purse_created_verify (
- etime,
- dr.details.ok.purse_expiration,
- &dr.details.ok.purse_value_after_fees,
- &dr.details.ok.total_deposited,
- &pch->purse_pub,
- &dr.details.ok.h_contract_terms,
- &exchange_pub,
- &exchange_sig))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_EXCHANGE_PURSE_DEPOSIT_EXCHANGE_SIGNATURE_INVALID;
- break;
- }
- }
- 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 */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- break;
- case MHD_HTTP_FORBIDDEN:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- /* 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 */
- break;
- case MHD_HTTP_NOT_FOUND:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- break;
- case MHD_HTTP_CONFLICT:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- switch (dr.hr.ec)
- {
- case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA:
- {
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_CoinSpendSignatureP coin_sig;
- struct TALER_DenominationHashP h_denom_pub;
- struct TALER_AgeCommitmentHashP phac;
- bool found = false;
-
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_purse_coin_conflict_ (
- &pch->purse_pub,
- pch->base_url,
- j,
- &h_denom_pub,
- &phac,
- &coin_pub,
- &coin_sig))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- for (unsigned int i = 0; i<pch->num_deposits; i++)
- {
- struct Coin *coin = &pch->coins[i];
- if (0 != GNUNET_memcmp (&coin_pub,
- &coin->coin_pub))
- continue;
- if (0 !=
- GNUNET_memcmp (&coin->h_denom_pub,
- &h_denom_pub))
- {
- found = true;
- break;
- }
- if (0 !=
- GNUNET_memcmp (&coin->ahac,
- &phac))
- {
- found = true;
- break;
- }
- if (0 == GNUNET_memcmp (&coin_sig,
- &coin->coin_sig))
- {
- /* identical signature => not a conflict */
- continue;
- }
- found = true;
- break;
- }
- if (! found)
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- /* meta data conflict is real! */
- break;
- }
- case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
- /* Nothing to check anymore here, proof needs to be
- checked in the GET /coins/$COIN_PUB handler */
- break;
- case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
- break;
- default:
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- } /* ec switch */
- break;
- case MHD_HTTP_GONE:
- /* could happen if denomination was revoked or purse expired */
- /* Note: one might want to check /keys for revocation
- signature here, alas tricky in case our /keys
- is outdated => left to clients */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- dr.hr.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 */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange deposit\n",
- (unsigned int) response_code,
- dr.hr.ec);
- GNUNET_break_op (0);
- break;
- }
- if (TALER_EC_NONE == dr.hr.ec)
- dr.hr.hint = NULL;
- else
- dr.hr.hint = TALER_ErrorCode_get_hint (dr.hr.ec);
- pch->cb (pch->cb_cls,
- &dr);
- TALER_EXCHANGE_purse_deposit_cancel (pch);
-}
-
-
-struct TALER_EXCHANGE_PurseDepositHandle *
-TALER_EXCHANGE_purse_deposit (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- struct TALER_EXCHANGE_Keys *keys,
- const char *purse_exchange_url,
- const struct TALER_PurseContractPublicKeyP *purse_pub,
- uint8_t min_age,
- unsigned int num_deposits,
- const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits],
- TALER_EXCHANGE_PurseDepositCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_PurseDepositHandle *pch;
- json_t *create_obj;
- json_t *deposit_arr;
- CURL *eh;
- char arg_str[sizeof (pch->purse_pub) * 2 + 32];
-
- // FIXME: use purse_exchange_url for wad transfers (#7271)
- (void) purse_exchange_url;
- if (0 == num_deposits)
- {
- GNUNET_break (0);
- return NULL;
- }
- pch = GNUNET_new (struct TALER_EXCHANGE_PurseDepositHandle);
- pch->purse_pub = *purse_pub;
- pch->cb = cb;
- pch->cb_cls = cb_cls;
- {
- char pub_str[sizeof (pch->purse_pub) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- &pch->purse_pub,
- sizeof (pch->purse_pub),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "purses/%s/deposit",
- pub_str);
- }
- pch->url = TALER_url_join (url,
- arg_str,
- NULL);
- if (NULL == pch->url)
- {
- GNUNET_break (0);
- GNUNET_free (pch);
- return NULL;
- }
- deposit_arr = json_array ();
- GNUNET_assert (NULL != deposit_arr);
- pch->base_url = GNUNET_strdup (url);
- pch->num_deposits = num_deposits;
- pch->coins = GNUNET_new_array (num_deposits,
- struct Coin);
- for (unsigned int i = 0; i<num_deposits; i++)
- {
- const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i];
- const struct TALER_AgeCommitmentProof *acp = deposit->age_commitment_proof;
- struct Coin *coin = &pch->coins[i];
- json_t *jdeposit;
- struct TALER_AgeCommitmentHashP *achp = NULL;
- struct TALER_AgeAttestationP attest;
- struct TALER_AgeAttestationP *attestp = NULL;
-
- if (NULL != acp)
- {
- TALER_age_commitment_hash (&acp->commitment,
- &coin->ahac);
- achp = &coin->ahac;
- if (GNUNET_OK !=
- TALER_age_commitment_attest (acp,
- min_age,
- &attest))
- {
- GNUNET_break (0);
- json_decref (deposit_arr);
- GNUNET_free (pch->base_url);
- GNUNET_free (pch->coins);
- GNUNET_free (pch->url);
- GNUNET_free (pch);
- return NULL;
- }
- attestp = &attest;
- }
- GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv,
- &coin->coin_pub.eddsa_pub);
- coin->h_denom_pub = deposit->h_denom_pub;
- coin->contribution = deposit->amount;
- TALER_wallet_purse_deposit_sign (
- pch->base_url,
- &pch->purse_pub,
- &deposit->amount,
- &coin->h_denom_pub,
- &coin->ahac,
- &deposit->coin_priv,
- &coin->coin_sig);
- jdeposit = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_auto ("h_age_commitment",
- achp)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_auto ("age_attestation",
- attestp)),
- TALER_JSON_pack_amount ("amount",
- &deposit->amount),
- GNUNET_JSON_pack_data_auto ("denom_pub_hash",
- &deposit->h_denom_pub),
- TALER_JSON_pack_denom_sig ("ub_sig",
- &deposit->denom_sig),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- &coin->coin_pub),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &coin->coin_sig));
- GNUNET_assert (0 ==
- json_array_append_new (deposit_arr,
- jdeposit));
- }
- create_obj = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_array_steal ("deposits",
- deposit_arr));
- GNUNET_assert (NULL != create_obj);
- eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
- if ( (NULL == eh) ||
- (GNUNET_OK !=
- TALER_curl_easy_post (&pch->ctx,
- eh,
- create_obj)) )
- {
- GNUNET_break (0);
- if (NULL != eh)
- curl_easy_cleanup (eh);
- json_decref (create_obj);
- GNUNET_free (pch->base_url);
- GNUNET_free (pch->url);
- GNUNET_free (pch->coins);
- GNUNET_free (pch);
- return NULL;
- }
- json_decref (create_obj);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "URL for purse deposit: `%s'\n",
- pch->url);
- pch->keys = TALER_EXCHANGE_keys_incref (keys);
- pch->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- pch->ctx.headers,
- &handle_purse_deposit_finished,
- pch);
- return pch;
-}
-
-
-void
-TALER_EXCHANGE_purse_deposit_cancel (
- struct TALER_EXCHANGE_PurseDepositHandle *pch)
-{
- if (NULL != pch->job)
- {
- GNUNET_CURL_job_cancel (pch->job);
- pch->job = NULL;
- }
- GNUNET_free (pch->base_url);
- GNUNET_free (pch->url);
- GNUNET_free (pch->coins);
- TALER_EXCHANGE_keys_decref (pch->keys);
- TALER_curl_easy_post_finished (&pch->ctx);
- GNUNET_free (pch);
-}
-
-
-/* end of exchange_api_purse_deposit.c */
diff --git a/src/lib/exchange_api_purse_merge.c b/src/lib/exchange_api_purse_merge.c
@@ -1,454 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022-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_purse_merge.c
- * @brief Implementation of the client to merge a purse
- * into an account
- * @author Christian Grothoff
- */
-#include "taler/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/taler_json_lib.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_handle.h"
-#include "exchange_api_common.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A purse merge with deposit handle
- */
-struct TALER_EXCHANGE_AccountMergeHandle
-{
-
- /**
- * 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_AccountMergeCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Base URL of the provider hosting the @e reserve_pub.
- */
- char *provider_url;
-
- /**
- * Signature for our operation.
- */
- struct TALER_PurseMergeSignatureP merge_sig;
-
- /**
- * Expected value in the purse after fees.
- */
- struct TALER_Amount purse_value_after_fees;
-
- /**
- * Public key of the reserve public key.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Public key of the purse.
- */
- struct TALER_PurseContractPublicKeyP purse_pub;
-
- /**
- * Hash over the purse's contrac terms.
- */
- struct TALER_PrivateContractHashP h_contract_terms;
-
- /**
- * When does the purse expire.
- */
- struct GNUNET_TIME_Timestamp purse_expiration;
-
- /**
- * Our merge key.
- */
- struct TALER_PurseMergePrivateKeyP merge_priv;
-
- /**
- * Reserve signature affirming the merge.
- */
- struct TALER_ReserveSignatureP reserve_sig;
-
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP /purse/$PID/merge request.
- *
- * @param cls the `struct TALER_EXCHANGE_AccountMergeHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_purse_merge_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_AccountMergeHandle *pch = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_AccountMergeResponse dr = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code,
- .reserve_sig = &pch->reserve_sig
- };
-
- pch->job = NULL;
- switch (response_code)
- {
- case 0:
- dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- struct TALER_Amount total_deposited;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &dr.details.ok.exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &dr.details.ok.exchange_pub),
- GNUNET_JSON_spec_timestamp ("exchange_timestamp",
- &dr.details.ok.etime),
- TALER_JSON_spec_amount ("merge_amount",
- pch->purse_value_after_fees.currency,
- &total_deposited),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (pch->keys,
- &dr.details.ok.exchange_pub))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID;
- break;
- }
- if (GNUNET_OK !=
- TALER_exchange_online_purse_merged_verify (
- dr.details.ok.etime,
- pch->purse_expiration,
- &pch->purse_value_after_fees,
- &pch->purse_pub,
- &pch->h_contract_terms,
- &pch->reserve_pub,
- pch->provider_url,
- &dr.details.ok.exchange_pub,
- &dr.details.ok.exchange_sig))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_EXCHANGE_PURSE_MERGE_EXCHANGE_SIGNATURE_INVALID;
- break;
- }
- }
- 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 */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_PAYMENT_REQUIRED:
- /* purse was not (yet) full */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_FORBIDDEN:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- /* 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 */
- break;
- case MHD_HTTP_NOT_FOUND:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- break;
- case MHD_HTTP_CONFLICT:
- {
- struct TALER_PurseMergePublicKeyP merge_pub;
-
- GNUNET_CRYPTO_eddsa_key_get_public (&pch->merge_priv.eddsa_priv,
- &merge_pub.eddsa_pub);
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_purse_merge_conflict_ (
- &pch->merge_sig,
- &merge_pub,
- &pch->purse_pub,
- pch->provider_url,
- j))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- break;
- }
- 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 */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint64 (
- "requirement_row",
- &dr.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);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- }
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.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 */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange deposit\n",
- (unsigned int) response_code,
- dr.hr.ec);
- GNUNET_break_op (0);
- break;
- }
- pch->cb (pch->cb_cls,
- &dr);
- TALER_EXCHANGE_account_merge_cancel (pch);
-}
-
-
-struct TALER_EXCHANGE_AccountMergeHandle *
-TALER_EXCHANGE_account_merge (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- struct TALER_EXCHANGE_Keys *keys,
- const char *reserve_exchange_url,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- const struct TALER_PurseContractPublicKeyP *purse_pub,
- const struct TALER_PurseMergePrivateKeyP *merge_priv,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- uint8_t min_age,
- const struct TALER_Amount *purse_value_after_fees,
- struct GNUNET_TIME_Timestamp purse_expiration,
- struct GNUNET_TIME_Timestamp merge_timestamp,
- TALER_EXCHANGE_AccountMergeCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_AccountMergeHandle *pch;
- json_t *merge_obj;
- CURL *eh;
- char arg_str[sizeof (pch->purse_pub) * 2 + 32];
- struct TALER_NormalizedPayto reserve_url;
-
- pch = GNUNET_new (struct TALER_EXCHANGE_AccountMergeHandle);
- pch->merge_priv = *merge_priv;
- pch->cb = cb;
- pch->cb_cls = cb_cls;
- pch->purse_pub = *purse_pub;
- pch->h_contract_terms = *h_contract_terms;
- pch->purse_expiration = purse_expiration;
- pch->purse_value_after_fees = *purse_value_after_fees;
- if (NULL == reserve_exchange_url)
- pch->provider_url = GNUNET_strdup (url);
- else
- pch->provider_url = GNUNET_strdup (reserve_exchange_url);
- GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
- &pch->reserve_pub.eddsa_pub);
-
- {
- char pub_str[sizeof (*purse_pub) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- purse_pub,
- sizeof (*purse_pub),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "purses/%s/merge",
- pub_str);
- }
- reserve_url = TALER_reserve_make_payto (pch->provider_url,
- &pch->reserve_pub);
- if (NULL == reserve_url.normalized_payto)
- {
- GNUNET_break (0);
- GNUNET_free (pch->provider_url);
- GNUNET_free (pch);
- return NULL;
- }
- pch->url = TALER_url_join (url,
- arg_str,
- NULL);
- if (NULL == pch->url)
- {
- GNUNET_break (0);
- GNUNET_free (reserve_url.normalized_payto);
- GNUNET_free (pch->provider_url);
- GNUNET_free (pch);
- return NULL;
- }
- TALER_wallet_purse_merge_sign (reserve_url,
- merge_timestamp,
- purse_pub,
- merge_priv,
- &pch->merge_sig);
- {
- struct TALER_Amount zero_purse_fee;
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (purse_value_after_fees->currency,
- &zero_purse_fee));
- TALER_wallet_account_merge_sign (merge_timestamp,
- purse_pub,
- purse_expiration,
- h_contract_terms,
- purse_value_after_fees,
- &zero_purse_fee,
- min_age,
- TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
- reserve_priv,
- &pch->reserve_sig);
- }
- merge_obj = GNUNET_JSON_PACK (
- TALER_JSON_pack_normalized_payto ("payto_uri",
- reserve_url),
- GNUNET_JSON_pack_data_auto ("merge_sig",
- &pch->merge_sig),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &pch->reserve_sig),
- GNUNET_JSON_pack_timestamp ("merge_timestamp",
- merge_timestamp));
- GNUNET_assert (NULL != merge_obj);
- GNUNET_free (reserve_url.normalized_payto);
- eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
- if ( (NULL == eh) ||
- (GNUNET_OK !=
- TALER_curl_easy_post (&pch->ctx,
- eh,
- merge_obj)) )
- {
- GNUNET_break (0);
- if (NULL != eh)
- curl_easy_cleanup (eh);
- json_decref (merge_obj);
- GNUNET_free (pch->provider_url);
- GNUNET_free (pch->url);
- GNUNET_free (pch);
- return NULL;
- }
- json_decref (merge_obj);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "URL for purse merge: `%s'\n",
- pch->url);
- pch->keys = TALER_EXCHANGE_keys_incref (keys);
- pch->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- pch->ctx.headers,
- &handle_purse_merge_finished,
- pch);
- return pch;
-}
-
-
-void
-TALER_EXCHANGE_account_merge_cancel (
- struct TALER_EXCHANGE_AccountMergeHandle *pch)
-{
- if (NULL != pch->job)
- {
- GNUNET_CURL_job_cancel (pch->job);
- pch->job = NULL;
- }
- GNUNET_free (pch->url);
- GNUNET_free (pch->provider_url);
- TALER_curl_easy_post_finished (&pch->ctx);
- TALER_EXCHANGE_keys_decref (pch->keys);
- GNUNET_free (pch);
-}
-
-
-/* end of exchange_api_purse_merge.c */
diff --git a/src/lib/exchange_api_purses_get.c b/src/lib/exchange_api_purses_get.c
@@ -1,300 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022-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_purses_get.c
- * @brief Implementation of the /purses/ GET request
- * @author Christian Grothoff
- */
-#include "taler/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/taler_json_lib.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A Contract Get Handle
- */
-struct TALER_EXCHANGE_PurseGetHandle
-{
-
- /**
- * The keys of the exchange this request handle will use
- */
- struct TALER_EXCHANGE_Keys *keys;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_PurseGetCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP /purses/$PID GET request.
- *
- * @param cls the `struct TALER_EXCHANGE_PurseGetHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_purse_get_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_PurseGetHandle *pgh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_PurseGetResponse dr = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
-
- pgh->job = NULL;
- switch (response_code)
- {
- case 0:
- dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- bool no_merge = false;
- bool no_deposit = false;
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct TALER_ExchangeSignatureP exchange_sig;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("merge_timestamp",
- &dr.details.ok.merge_timestamp),
- &no_merge),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("deposit_timestamp",
- &dr.details.ok.deposit_timestamp),
- &no_deposit),
- TALER_JSON_spec_amount_any ("balance",
- &dr.details.ok.balance),
- GNUNET_JSON_spec_timestamp ("purse_expiration",
- &dr.details.ok.purse_expiration),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &exchange_pub),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &exchange_sig),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
-
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (pgh->keys,
- &exchange_pub))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE;
- break;
- }
- if (GNUNET_OK !=
- TALER_exchange_online_purse_status_verify (
- dr.details.ok.merge_timestamp,
- dr.details.ok.deposit_timestamp,
- &dr.details.ok.balance,
- &exchange_pub,
- &exchange_sig))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_EXCHANGE_PURSES_GET_INVALID_SIGNATURE_BY_EXCHANGE;
- break;
- }
- pgh->cb (pgh->cb_cls,
- &dr);
- TALER_EXCHANGE_purse_get_cancel (pgh);
- return;
- }
- case MHD_HTTP_BAD_REQUEST:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.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:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- /* 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 */
- break;
- case MHD_HTTP_NOT_FOUND:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- /* Exchange does not know about transaction;
- we should pass the reply to the application */
- break;
- case MHD_HTTP_GONE:
- /* purse expired */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.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 */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange GET purses\n",
- (unsigned int) response_code,
- (int) dr.hr.ec);
- GNUNET_break_op (0);
- break;
- }
- pgh->cb (pgh->cb_cls,
- &dr);
- TALER_EXCHANGE_purse_get_cancel (pgh);
-}
-
-
-struct TALER_EXCHANGE_PurseGetHandle *
-TALER_EXCHANGE_purse_get (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_PurseContractPublicKeyP *purse_pub,
- struct GNUNET_TIME_Relative timeout,
- bool wait_for_merge,
- TALER_EXCHANGE_PurseGetCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_PurseGetHandle *pgh;
- CURL *eh;
- char arg_str[sizeof (*purse_pub) * 2 + 64];
- unsigned int tms
- = (unsigned int) timeout.rel_value_us
- / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
-
- pgh = GNUNET_new (struct TALER_EXCHANGE_PurseGetHandle);
- pgh->cb = cb;
- pgh->cb_cls = cb_cls;
- {
- char cpub_str[sizeof (*purse_pub) * 2];
- char *end;
- char timeout_str[32];
-
- end = GNUNET_STRINGS_data_to_string (purse_pub,
- sizeof (*purse_pub),
- cpub_str,
- sizeof (cpub_str));
- *end = '\0';
- GNUNET_snprintf (timeout_str,
- sizeof (timeout_str),
- "%u",
- tms);
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "purses/%s/%s",
- cpub_str,
- wait_for_merge
- ? "merge"
- : "deposit");
- pgh->url = TALER_url_join (url,
- arg_str,
- "timeout_ms",
- (0 == tms)
- ? NULL
- : timeout_str,
- NULL);
- }
- if (NULL == pgh->url)
- {
- GNUNET_free (pgh);
- return NULL;
- }
- eh = TALER_EXCHANGE_curl_easy_get_ (pgh->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- GNUNET_free (pgh->url);
- GNUNET_free (pgh);
- return NULL;
- }
- if (0 != tms)
- {
- GNUNET_break (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_TIMEOUT_MS,
- (long) (tms + 100L)));
- }
- pgh->job = GNUNET_CURL_job_add (ctx,
- eh,
- &handle_purse_get_finished,
- pgh);
- pgh->keys = TALER_EXCHANGE_keys_incref (keys);
- return pgh;
-}
-
-
-void
-TALER_EXCHANGE_purse_get_cancel (
- struct TALER_EXCHANGE_PurseGetHandle *pgh)
-{
- if (NULL != pgh->job)
- {
- GNUNET_CURL_job_cancel (pgh->job);
- pgh->job = NULL;
- }
- GNUNET_free (pgh->url);
- TALER_EXCHANGE_keys_decref (pgh->keys);
- GNUNET_free (pgh);
-}
-
-
-/* end of exchange_api_purses_get.c */
diff --git a/src/lib/exchange_api_recoup.c b/src/lib/exchange_api_recoup.c
@@ -1,382 +0,0 @@
-/*
- 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 "taler/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/taler_json_lib.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_common.h"
-#include "exchange_api_handle.h"
-#include "taler/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_ExchangeBlindingValues *blinding_values,
- const struct TALER_PlanchetMasterSecretP *ps,
- const struct TALER_HashBlindedPlanchetsP *h_planchets,
- 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 GNUNET_CRYPTO_BlindingSecretP bks;
-
- ph = GNUNET_new (struct TALER_EXCHANGE_RecoupHandle);
- TALER_planchet_setup_coin_priv (ps,
- blinding_values,
- &coin_priv);
- TALER_planchet_blinding_secret_create (ps,
- blinding_values,
- &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_blinding_values ("ewv",
- blinding_values),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &ph->coin_sig),
- GNUNET_JSON_pack_data_auto ("h_planchets",
- h_planchets),
- GNUNET_JSON_pack_data_auto ("coin_blind_key_secret",
- &bks));
- switch (denom_sig->unblinded_sig->cipher)
- {
- case GNUNET_CRYPTO_BSA_INVALID:
- json_decref (recoup_obj);
- GNUNET_break (0);
- GNUNET_free (ph);
- return NULL;
- case GNUNET_CRYPTO_BSA_RSA:
- break;
- case GNUNET_CRYPTO_BSA_CS:
- {
- union GNUNET_CRYPTO_BlindSessionNonce 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.cs_nonce);
- GNUNET_assert (
- 0 ==
- json_object_set_new (recoup_obj,
- "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
@@ -1,361 +0,0 @@
-/*
- 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 "taler/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/taler_json_lib.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_common.h"
-#include "exchange_api_handle.h"
-#include "taler/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);
-}
-
-
-#pragma message "TODO[oec]: rewrite TALER_EXCHANGE_RecoupRefreshHandle"
-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_ExchangeBlindingValues *blinding_values,
- const struct TALER_PublicRefreshMasterSeedP *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 GNUNET_CRYPTO_BlindingSecretP 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,
- blinding_values,
- &coin_priv);
- TALER_planchet_blinding_secret_create (ps,
- blinding_values,
- &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_blinding_values ("ewv",
- blinding_values),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &ph->coin_sig),
- GNUNET_JSON_pack_data_auto ("coin_blind_key_secret",
- &bks));
-
- switch (denom_sig->unblinded_sig->cipher)
- {
- case GNUNET_CRYPTO_BSA_INVALID:
- json_decref (recoup_obj);
- GNUNET_break (0);
- GNUNET_free (ph);
- return NULL;
- case GNUNET_CRYPTO_BSA_RSA:
- break;
- case GNUNET_CRYPTO_BSA_CS:
- {
-
- }
- break;
- }
-
- {
- 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_refund.c b/src/lib/exchange_api_refund.c
@@ -1,484 +0,0 @@
-/*
- 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 "taler/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/taler_json_lib.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_handle.h"
-#include "taler/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_FAILED_DEPENDENCY:
- 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_reserves_attest.c b/src/lib/exchange_api_reserves_attest.c
@@ -1,365 +0,0 @@
-/*
- 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_reserves_attest.c
- * @brief Implementation of the POST /reserves-attest/$RESERVE_PUB requests
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP attest codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A /reserves-attest/$RID Handle
- */
-struct TALER_EXCHANGE_ReservesAttestHandle
-{
-
- /**
- * The keys of the this request handle will use
- */
- struct TALER_EXCHANGE_Keys *keys;
-
- /**
- * 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;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_ReservesPostAttestCallback cb;
-
- /**
- * Public key of the reserve we are querying.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
-};
-
-
-/**
- * We received an #MHD_HTTP_OK attest code. Handle the JSON
- * response.
- *
- * @param rsh handle of the request
- * @param j JSON response
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_reserves_attest_ok (struct TALER_EXCHANGE_ReservesAttestHandle *rsh,
- const json_t *j)
-{
- struct TALER_EXCHANGE_ReservePostAttestResult rs = {
- .hr.reply = j,
- .hr.http_status = MHD_HTTP_OK
- };
- const json_t *attributes;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_timestamp ("exchange_timestamp",
- &rs.details.ok.exchange_time),
- GNUNET_JSON_spec_timestamp ("expiration_time",
- &rs.details.ok.expiration_time),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &rs.details.ok.exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &rs.details.ok.exchange_pub),
- GNUNET_JSON_spec_object_const ("attributes",
- &attributes),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (rsh->keys,
- &rs.details.ok.exchange_pub))
- {
- GNUNET_break_op (0);
- rs.hr.http_status = 0;
- rs.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE;
- rsh->cb (rsh->cb_cls,
- &rs);
- rsh->cb = NULL;
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- rs.details.ok.attributes = attributes;
- if (GNUNET_OK !=
- TALER_exchange_online_reserve_attest_details_verify (
- rs.details.ok.exchange_time,
- rs.details.ok.expiration_time,
- &rsh->reserve_pub,
- attributes,
- &rs.details.ok.exchange_pub,
- &rs.details.ok.exchange_sig))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- rsh->cb (rsh->cb_cls,
- &rs);
- rsh->cb = NULL;
- GNUNET_JSON_parse_free (spec);
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /reserves-attest/$RID request.
- *
- * @param cls the `struct TALER_EXCHANGE_ReservesAttestHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_reserves_attest_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_ReservesAttestHandle *rsh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_ReservePostAttestResult rs = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
-
- rsh->job = NULL;
- switch (response_code)
- {
- case 0:
- rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- if (GNUNET_OK !=
- handle_reserves_attest_ok (rsh,
- j))
- {
- rs.hr.http_status = 0;
- rs.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 */
- GNUNET_break (0);
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_FORBIDDEN:
- /* This should never happen, either us or the exchange is buggy
- (or API version conflict); just pass JSON reply to the application */
- GNUNET_break (0);
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.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 */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_CONFLICT:
- /* Server doesn't have the requested attributes */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.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 */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for reserves attest\n",
- (unsigned int) response_code,
- (int) rs.hr.ec);
- break;
- }
- if (NULL != rsh->cb)
- {
- rsh->cb (rsh->cb_cls,
- &rs);
- rsh->cb = NULL;
- }
- TALER_EXCHANGE_reserves_attest_cancel (rsh);
-}
-
-
-struct TALER_EXCHANGE_ReservesAttestHandle *
-TALER_EXCHANGE_reserves_attest (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- unsigned int attributes_length,
- const char *attributes[const static attributes_length],
- TALER_EXCHANGE_ReservesPostAttestCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_ReservesAttestHandle *rsh;
- CURL *eh;
- char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
- struct TALER_ReserveSignatureP reserve_sig;
- json_t *details;
- struct GNUNET_TIME_Timestamp ts;
-
- if (0 == attributes_length)
- {
- GNUNET_break (0);
- return NULL;
- }
- details = json_array ();
- GNUNET_assert (NULL != details);
- for (unsigned int i = 0; i<attributes_length; i++)
- {
- GNUNET_assert (0 ==
- json_array_append_new (details,
- json_string (attributes[i])));
- }
- rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesAttestHandle);
- rsh->cb = cb;
- rsh->cb_cls = cb_cls;
- GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
- &rsh->reserve_pub.eddsa_pub);
- {
- char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- &rsh->reserve_pub,
- sizeof (rsh->reserve_pub),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "reserves-attest/%s",
- pub_str);
- }
- rsh->url = TALER_url_join (url,
- arg_str,
- NULL);
- if (NULL == rsh->url)
- {
- json_decref (details);
- GNUNET_free (rsh);
- return NULL;
- }
- eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- json_decref (details);
- GNUNET_free (rsh->url);
- GNUNET_free (rsh);
- return NULL;
- }
- ts = GNUNET_TIME_timestamp_get ();
- TALER_wallet_reserve_attest_request_sign (ts,
- details,
- reserve_priv,
- &reserve_sig);
- {
- json_t *attest_obj = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &reserve_sig),
- GNUNET_JSON_pack_timestamp ("request_timestamp",
- ts),
- GNUNET_JSON_pack_array_steal ("details",
- details));
-
- if (GNUNET_OK !=
- TALER_curl_easy_post (&rsh->post_ctx,
- eh,
- attest_obj))
- {
- GNUNET_break (0);
- curl_easy_cleanup (eh);
- json_decref (attest_obj);
- GNUNET_free (rsh->url);
- GNUNET_free (rsh);
- return NULL;
- }
- json_decref (attest_obj);
- }
- rsh->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- rsh->post_ctx.headers,
- &handle_reserves_attest_finished,
- rsh);
- rsh->keys = TALER_EXCHANGE_keys_incref (keys);
- return rsh;
-}
-
-
-void
-TALER_EXCHANGE_reserves_attest_cancel (
- struct TALER_EXCHANGE_ReservesAttestHandle *rsh)
-{
- if (NULL != rsh->job)
- {
- GNUNET_CURL_job_cancel (rsh->job);
- rsh->job = NULL;
- }
- TALER_curl_easy_post_finished (&rsh->post_ctx);
- TALER_EXCHANGE_keys_decref (rsh->keys);
- GNUNET_free (rsh->url);
- GNUNET_free (rsh);
-}
-
-
-/* end of exchange_api_reserves_attest.c */
diff --git a/src/lib/exchange_api_reserves_close.c b/src/lib/exchange_api_reserves_close.c
@@ -1,373 +0,0 @@
-/*
- 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_reserves_close.c
- * @brief Implementation of the POST /reserves/$RESERVE_PUB/close requests
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP close codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A /reserves/$RID/close Handle
- */
-struct TALER_EXCHANGE_ReservesCloseHandle
-{
-
- /**
- * 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;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_ReservesCloseCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Public key of the reserve we are querying.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Our signature.
- */
- struct TALER_ReserveSignatureP reserve_sig;
-
- /**
- * When did we make the request.
- */
- struct GNUNET_TIME_Timestamp ts;
-
-};
-
-
-/**
- * We received an #MHD_HTTP_OK close code. Handle the JSON
- * response.
- *
- * @param rch handle of the request
- * @param j JSON response
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_reserves_close_ok (struct TALER_EXCHANGE_ReservesCloseHandle *rch,
- const json_t *j)
-{
- struct TALER_EXCHANGE_ReserveCloseResult rs = {
- .hr.reply = j,
- .hr.http_status = MHD_HTTP_OK,
- };
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("wire_amount",
- &rs.details.ok.wire_amount),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- rch->cb (rch->cb_cls,
- &rs);
- rch->cb = NULL;
- GNUNET_JSON_parse_free (spec);
- return GNUNET_OK;
-}
-
-
-/**
- * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS close code. Handle the JSON
- * response.
- *
- * @param rch handle of the request
- * @param j JSON response
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_reserves_close_kyc (struct TALER_EXCHANGE_ReservesCloseHandle *rch,
- const json_t *j)
-{
- struct TALER_EXCHANGE_ReserveCloseResult rs = {
- .hr.reply = j,
- .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
- };
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto (
- "h_payto",
- &rs.details.unavailable_for_legal_reasons.h_payto),
- GNUNET_JSON_spec_uint64 (
- "requirement_row",
- &rs.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);
- return GNUNET_SYSERR;
- }
- rch->cb (rch->cb_cls,
- &rs);
- rch->cb = NULL;
- GNUNET_JSON_parse_free (spec);
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /reserves/$RID/close request.
- *
- * @param cls the `struct TALER_EXCHANGE_ReservesCloseHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_reserves_close_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_ReservesCloseHandle *rch = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_ReserveCloseResult rs = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
-
- rch->job = NULL;
- switch (response_code)
- {
- case 0:
- rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- if (GNUNET_OK !=
- handle_reserves_close_ok (rch,
- j))
- {
- GNUNET_break_op (0);
- rs.hr.http_status = 0;
- rs.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 */
- GNUNET_break (0);
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_FORBIDDEN:
- /* This should never happen, either us or the exchange is buggy
- (or API version conflict); just pass JSON reply to the application */
- GNUNET_break (0);
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.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 */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_CONFLICT:
- /* Insufficient balance to inquire for reserve close */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
- if (GNUNET_OK !=
- handle_reserves_close_kyc (rch,
- j))
- {
- GNUNET_break_op (0);
- rs.hr.http_status = 0;
- rs.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 */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for reserves close\n",
- (unsigned int) response_code,
- (int) rs.hr.ec);
- break;
- }
- if (NULL != rch->cb)
- {
- rch->cb (rch->cb_cls,
- &rs);
- rch->cb = NULL;
- }
- TALER_EXCHANGE_reserves_close_cancel (rch);
-}
-
-
-struct TALER_EXCHANGE_ReservesCloseHandle *
-TALER_EXCHANGE_reserves_close (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- const struct TALER_FullPayto target_payto_uri,
- TALER_EXCHANGE_ReservesCloseCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_ReservesCloseHandle *rch;
- CURL *eh;
- char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
- struct TALER_FullPaytoHashP h_payto;
-
- rch = GNUNET_new (struct TALER_EXCHANGE_ReservesCloseHandle);
- rch->cb = cb;
- rch->cb_cls = cb_cls;
- rch->ts = GNUNET_TIME_timestamp_get ();
- GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
- &rch->reserve_pub.eddsa_pub);
- {
- char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- &rch->reserve_pub,
- sizeof (rch->reserve_pub),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "reserves/%s/close",
- pub_str);
- }
- rch->url = TALER_url_join (url,
- arg_str,
- NULL);
- if (NULL == rch->url)
- {
- GNUNET_free (rch);
- return NULL;
- }
- eh = TALER_EXCHANGE_curl_easy_get_ (rch->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- GNUNET_free (rch->url);
- GNUNET_free (rch);
- return NULL;
- }
- if (NULL != target_payto_uri.full_payto)
- TALER_full_payto_hash (target_payto_uri,
- &h_payto);
- TALER_wallet_reserve_close_sign (rch->ts,
- (NULL != target_payto_uri.full_payto)
- ? &h_payto
- : NULL,
- reserve_priv,
- &rch->reserve_sig);
- {
- json_t *close_obj = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_allow_null (
- TALER_JSON_pack_full_payto ("payto_uri",
- target_payto_uri)),
- GNUNET_JSON_pack_timestamp ("request_timestamp",
- rch->ts),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &rch->reserve_sig));
-
- if (GNUNET_OK !=
- TALER_curl_easy_post (&rch->post_ctx,
- eh,
- close_obj))
- {
- GNUNET_break (0);
- curl_easy_cleanup (eh);
- json_decref (close_obj);
- GNUNET_free (rch->url);
- GNUNET_free (rch);
- return NULL;
- }
- json_decref (close_obj);
- }
- rch->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- rch->post_ctx.headers,
- &handle_reserves_close_finished,
- rch);
- return rch;
-}
-
-
-void
-TALER_EXCHANGE_reserves_close_cancel (
- struct TALER_EXCHANGE_ReservesCloseHandle *rch)
-{
- if (NULL != rch->job)
- {
- GNUNET_CURL_job_cancel (rch->job);
- rch->job = NULL;
- }
- TALER_curl_easy_post_finished (&rch->post_ctx);
- GNUNET_free (rch->url);
- GNUNET_free (rch);
-}
-
-
-/* end of exchange_api_reserves_close.c */
diff --git a/src/lib/exchange_api_reserves_get.c b/src/lib/exchange_api_reserves_get.c
@@ -1,279 +0,0 @@
-/*
- 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_reserves_get.c
- * @brief Implementation of the GET /reserves/$RESERVE_PUB requests
- * @author Christian Grothoff
- */
-#include "taler/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/taler_exchange_service.h"
-#include "taler/taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A /reserves/ GET Handle
- */
-struct TALER_EXCHANGE_ReservesGetHandle
-{
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_ReservesGetCallback cb;
-
- /**
- * Public key of the reserve we are querying.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
-};
-
-
-/**
- * We received an #MHD_HTTP_OK status code. Handle the JSON
- * response.
- *
- * @param rgh handle of the request
- * @param j JSON response
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_reserves_get_ok (struct TALER_EXCHANGE_ReservesGetHandle *rgh,
- const json_t *j)
-{
- struct TALER_EXCHANGE_ReserveSummary rs = {
- .hr.reply = j,
- .hr.http_status = MHD_HTTP_OK
- };
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("balance",
- &rs.details.ok.balance),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string (
- "last_origin",
- (const char **) &rs.details.ok.last_origin.full_payto),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- rgh->cb (rgh->cb_cls,
- &rs);
- rgh->cb = NULL;
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /reserves/ GET request.
- *
- * @param cls the `struct TALER_EXCHANGE_ReservesGetHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_reserves_get_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_ReservesGetHandle *rgh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_ReserveSummary rs = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
-
- rgh->job = NULL;
- switch (response_code)
- {
- case 0:
- rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- if (GNUNET_OK !=
- handle_reserves_get_ok (rgh,
- j))
- {
- rs.hr.http_status = 0;
- rs.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 */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.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 */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.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 */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for GET %s\n",
- (unsigned int) response_code,
- (int) rs.hr.ec,
- rgh->url);
- break;
- }
- if (NULL != rgh->cb)
- {
- rgh->cb (rgh->cb_cls,
- &rs);
- rgh->cb = NULL;
- }
- TALER_EXCHANGE_reserves_get_cancel (rgh);
-}
-
-
-struct TALER_EXCHANGE_ReservesGetHandle *
-TALER_EXCHANGE_reserves_get (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- struct GNUNET_TIME_Relative timeout,
- TALER_EXCHANGE_ReservesGetCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_ReservesGetHandle *rgh;
- CURL *eh;
- char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 16 + 32];
- unsigned int tms
- = (unsigned int) timeout.rel_value_us
- / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
-
- {
- char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
- char *end;
- char timeout_str[32];
-
- end = GNUNET_STRINGS_data_to_string (
- reserve_pub,
- sizeof (*reserve_pub),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (timeout_str,
- sizeof (timeout_str),
- "%u",
- tms);
- if (0 == tms)
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "reserves/%s",
- pub_str);
- else
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "reserves/%s?timeout_ms=%s",
- pub_str,
- timeout_str);
- }
- rgh = GNUNET_new (struct TALER_EXCHANGE_ReservesGetHandle);
- rgh->cb = cb;
- rgh->cb_cls = cb_cls;
- rgh->reserve_pub = *reserve_pub;
- rgh->url = TALER_url_join (url,
- arg_str,
- NULL);
- if (NULL == rgh->url)
- {
- GNUNET_free (rgh);
- return NULL;
- }
- eh = TALER_EXCHANGE_curl_easy_get_ (rgh->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- GNUNET_free (rgh->url);
- GNUNET_free (rgh);
- return NULL;
- }
- if (0 != tms)
- {
- GNUNET_break (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_TIMEOUT_MS,
- (long) (tms + 100L)));
- }
- rgh->job = GNUNET_CURL_job_add (ctx,
- eh,
- &handle_reserves_get_finished,
- rgh);
- return rgh;
-}
-
-
-void
-TALER_EXCHANGE_reserves_get_cancel (
- struct TALER_EXCHANGE_ReservesGetHandle *rgh)
-{
- if (NULL != rgh->job)
- {
- GNUNET_CURL_job_cancel (rgh->job);
- rgh->job = NULL;
- }
- GNUNET_free (rgh->url);
- GNUNET_free (rgh);
-}
-
-
-/* end of exchange_api_reserves_get.c */
diff --git a/src/lib/exchange_api_reserves_get_attestable.c b/src/lib/exchange_api_reserves_get_attestable.c
@@ -1,276 +0,0 @@
-/*
- 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_reserves_get_attestable.c
- * @brief Implementation of the GET_ATTESTABLE /reserves/$RESERVE_PUB requests
- * @author Christian Grothoff
- */
-#include "taler/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/taler_exchange_service.h"
-#include "taler/taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A /reserves/ GET_ATTESTABLE Handle
- */
-struct TALER_EXCHANGE_ReservesGetAttestHandle
-{
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_ReservesGetAttestCallback cb;
-
- /**
- * Public key of the reserve we are querying.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
-};
-
-
-/**
- * We received an #MHD_HTTP_OK status code. Handle the JSON
- * response.
- *
- * @param rgah handle of the request
- * @param j JSON response
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_reserves_get_attestable_ok (
- struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah,
- const json_t *j)
-{
- struct TALER_EXCHANGE_ReserveGetAttestResult rs = {
- .hr.reply = j,
- .hr.http_status = MHD_HTTP_OK
- };
- const json_t *details;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_array_const ("details",
- &details),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- {
- unsigned int dlen = json_array_size (details);
- const char *attributes[GNUNET_NZL (dlen)];
-
- for (unsigned int i = 0; i<dlen; i++)
- {
- json_t *detail = json_array_get (details,
- i);
- attributes[i] = json_string_value (detail);
- if (NULL == attributes[i])
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- rs.details.ok.attributes_length = dlen;
- rs.details.ok.attributes = attributes;
- rgah->cb (rgah->cb_cls,
- &rs);
- rgah->cb = NULL;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP GET /reserves-attest/$RID request.
- *
- * @param cls the `struct TALER_EXCHANGE_ReservesGetAttestableHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_reserves_get_attestable_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_ReserveGetAttestResult rs = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
-
- rgah->job = NULL;
- switch (response_code)
- {
- case 0:
- rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- if (GNUNET_OK !=
- handle_reserves_get_attestable_ok (rgah,
- j))
- {
- rs.hr.http_status = 0;
- rs.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 */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.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 */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_CONFLICT:
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.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 */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for reserves get_attestable\n",
- (unsigned int) response_code,
- (int) rs.hr.ec);
- break;
- }
- if (NULL != rgah->cb)
- {
- rgah->cb (rgah->cb_cls,
- &rs);
- rgah->cb = NULL;
- }
- TALER_EXCHANGE_reserves_get_attestable_cancel (rgah);
-}
-
-
-struct TALER_EXCHANGE_ReservesGetAttestHandle *
-TALER_EXCHANGE_reserves_get_attestable (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- TALER_EXCHANGE_ReservesGetAttestCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah;
- CURL *eh;
- 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 (
- reserve_pub,
- sizeof (*reserve_pub),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "reserves-attest/%s",
- pub_str);
- }
- rgah = GNUNET_new (struct TALER_EXCHANGE_ReservesGetAttestHandle);
- rgah->cb = cb;
- rgah->cb_cls = cb_cls;
- rgah->reserve_pub = *reserve_pub;
- rgah->url = TALER_url_join (url,
- arg_str,
- NULL);
- if (NULL == rgah->url)
- {
- GNUNET_free (rgah);
- return NULL;
- }
- eh = TALER_EXCHANGE_curl_easy_get_ (rgah->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- GNUNET_free (rgah->url);
- GNUNET_free (rgah);
- return NULL;
- }
- rgah->job = GNUNET_CURL_job_add (ctx,
- eh,
- &handle_reserves_get_attestable_finished,
- rgah);
- return rgah;
-}
-
-
-void
-TALER_EXCHANGE_reserves_get_attestable_cancel (
- struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah)
-{
- if (NULL != rgah->job)
- {
- GNUNET_CURL_job_cancel (rgah->job);
- rgah->job = NULL;
- }
- GNUNET_free (rgah->url);
- GNUNET_free (rgah);
-}
-
-
-/* end of exchange_api_reserves_get_attestable.c */
diff --git a/src/lib/exchange_api_reserves_history.c b/src/lib/exchange_api_reserves_history.c
@@ -1,1258 +0,0 @@
-/*
- 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_reserves_history.c
- * @brief Implementation of the POST /reserves/$RESERVE_PUB/history requests
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP history codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A /reserves/$RID/history Handle
- */
-struct TALER_EXCHANGE_ReservesHistoryHandle
-{
-
- /**
- * The keys of the exchange this request handle will use
- */
- struct TALER_EXCHANGE_Keys *keys;
-
- /**
- * 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;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_ReservesHistoryCallback cb;
-
- /**
- * Public key of the reserve we are querying.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Where to store the etag (if any).
- */
- uint64_t etag;
-
-};
-
-
-/**
- * Context for history entry helpers.
- */
-struct HistoryParseContext
-{
-
- /**
- * Keys of the exchange we use.
- */
- const struct TALER_EXCHANGE_Keys *keys;
-
- /**
- * Our reserve public key.
- */
- const struct TALER_ReservePublicKeyP *reserve_pub;
-
- /**
- * Array of UUIDs.
- */
- struct GNUNET_HashCode *uuids;
-
- /**
- * Where to sum up total inbound amounts.
- */
- struct TALER_Amount *total_in;
-
- /**
- * Where to sum up total outbound amounts.
- */
- struct TALER_Amount *total_out;
-
- /**
- * Number of entries already used in @e uuids.
- */
- unsigned int uuid_off;
-};
-
-
-/**
- * Type of a function called to parse a reserve history
- * entry @a rh.
- *
- * @param[in,out] rh where to write the result
- * @param[in,out] uc UUID context for duplicate detection
- * @param transaction the transaction to parse
- * @return #GNUNET_OK on success
- */
-typedef enum GNUNET_GenericReturnValue
-(*ParseHelper)(struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
- struct HistoryParseContext *uc,
- const json_t *transaction);
-
-
-/**
- * Parse "credit" reserve history entry.
- *
- * @param[in,out] rh entry to parse
- * @param uc our context
- * @param transaction the transaction to parse
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_credit (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
- struct HistoryParseContext *uc,
- const json_t *transaction)
-{
- struct TALER_FullPayto wire_uri;
- uint64_t wire_reference;
- struct GNUNET_TIME_Timestamp timestamp;
- struct GNUNET_JSON_Specification withdraw_spec[] = {
- GNUNET_JSON_spec_uint64 ("wire_reference",
- &wire_reference),
- GNUNET_JSON_spec_timestamp ("timestamp",
- ×tamp),
- TALER_JSON_spec_full_payto_uri ("sender_account_url",
- &wire_uri),
- GNUNET_JSON_spec_end ()
- };
-
- rh->type = TALER_EXCHANGE_RTT_CREDIT;
- if (0 >
- TALER_amount_add (uc->total_in,
- uc->total_in,
- &rh->amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- withdraw_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- rh->details.in_details.sender_url.full_payto
- = GNUNET_strdup (wire_uri.full_payto);
- rh->details.in_details.wire_reference = wire_reference;
- rh->details.in_details.timestamp = timestamp;
- return GNUNET_OK;
-}
-
-
-/**
- * Parse "withdraw" reserve history entry.
- *
- * @param[in,out] rh entry to parse
- * @param uc our context
- * @param transaction the transaction to parse
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
- struct HistoryParseContext *uc,
- const json_t *transaction)
-{
- uint16_t num_coins;
- struct TALER_Amount withdraw_fee;
- struct TALER_Amount withdraw_amount;
- struct TALER_Amount amount_without_fee;
- uint8_t max_age = 0;
- uint8_t noreveal_index = 0;
- struct TALER_HashBlindedPlanchetsP planchets_h;
- struct TALER_HashBlindedPlanchetsP selected_h;
- struct TALER_ReserveSignatureP reserve_sig;
- struct TALER_BlindingMasterSeedP blinding_seed;
- struct TALER_DenominationHashP *denom_pub_hashes;
- size_t num_denom_pub_hashes;
- bool no_max_age;
- bool no_noreveal_index;
- bool no_blinding_seed;
- bool no_selected_h;
- struct GNUNET_JSON_Specification withdraw_spec[] = {
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &reserve_sig),
- GNUNET_JSON_spec_uint16 ("num_coins",
- &num_coins),
- GNUNET_JSON_spec_fixed_auto ("planchets_h",
- &planchets_h),
- TALER_JSON_spec_amount_any ("amount",
- &withdraw_amount),
- TALER_JSON_spec_amount_any ("withdraw_fee",
- &withdraw_fee),
- TALER_JSON_spec_array_of_denom_pub_h ("denom_pub_hashes",
- &num_denom_pub_hashes,
- &denom_pub_hashes),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("selected_h",
- &selected_h),
- &no_selected_h),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint8 ("max_age",
- &max_age),
- &no_max_age),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("blinding_seed",
- &blinding_seed),
- &no_blinding_seed),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint8 ("noreveal_index",
- &noreveal_index),
- &no_noreveal_index),
- GNUNET_JSON_spec_end ()
- };
-
- rh->type = TALER_EXCHANGE_RTT_WITHDRAWAL;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- withdraw_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- if ((no_max_age != no_noreveal_index) ||
- (no_max_age != no_selected_h))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
- rh->details.withdraw.age_restricted = ! no_max_age;
-
- if (num_coins != num_denom_pub_hashes)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
-
- /* Check that the signature is a valid withdraw request */
- if (0>TALER_amount_subtract (
- &amount_without_fee,
- &withdraw_amount,
- &withdraw_fee))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
-
- if (GNUNET_OK !=
- TALER_wallet_withdraw_verify (
- &amount_without_fee,
- &withdraw_fee,
- &planchets_h,
- no_blinding_seed ? NULL : &blinding_seed,
- no_max_age ? NULL : &uc->keys->age_mask,
- no_max_age ? 0 : max_age,
- uc->reserve_pub,
- &reserve_sig))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
-
- rh->details.withdraw.num_coins = num_coins;
- rh->details.withdraw.age_restricted = ! no_max_age;
- rh->details.withdraw.max_age = max_age;
- rh->details.withdraw.planchets_h = planchets_h;
- rh->details.withdraw.selected_h = selected_h;
- rh->details.withdraw.noreveal_index = noreveal_index;
- rh->details.withdraw.no_blinding_seed = no_blinding_seed;
- if (! no_blinding_seed)
- rh->details.withdraw.blinding_seed = blinding_seed;
-
- /* check that withdraw fee matches expectations! */
- {
- const struct TALER_EXCHANGE_Keys *key_state;
- struct TALER_Amount fee_acc;
- struct TALER_Amount amount_acc;
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (withdraw_amount.currency,
- &fee_acc));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (withdraw_amount.currency,
- &amount_acc));
-
- key_state = uc->keys;
-
- /* accumulate the withdraw fees */
- for (size_t i=0; i < num_coins; i++)
- {
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
-
- dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
- &denom_pub_hashes[i]);
- if (NULL == dki)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
- GNUNET_assert (0 <=
- TALER_amount_add (&fee_acc,
- &fee_acc,
- &dki->fees.withdraw));
- GNUNET_assert (0 <=
- TALER_amount_add (&amount_acc,
- &amount_acc,
- &dki->value));
- }
-
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&fee_acc,
- &withdraw_fee)) ||
- (0 !=
- TALER_amount_cmp (&amount_acc,
- &amount_without_fee)) )
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
- rh->details.withdraw.fee = withdraw_fee;
- }
-
- #pragma message "is out_authorization_sig still needed? Not set anywhere"
- rh->details.withdraw.out_authorization_sig
- = json_object_get (transaction,
- "signature");
- /* Check check that the same withdraw transaction
- isn't listed twice by the exchange. We use the
- "uuid" array to remember the hashes of all
- signatures, and compare the hashes to find
- duplicates. */
- GNUNET_CRYPTO_hash (&reserve_sig,
- sizeof (reserve_sig),
- &uc->uuids[uc->uuid_off]);
- for (unsigned int i = 0; i<uc->uuid_off; i++)
- {
- if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off],
- &uc->uuids[i]))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
- }
- uc->uuid_off++;
-
- if (0 >
- TALER_amount_add (uc->total_out,
- uc->total_out,
- &rh->amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_OK;
-}
-
-
-/**
- * Parse "recoup" reserve history entry.
- *
- * @param[in,out] rh entry to parse
- * @param uc our context
- * @param transaction the transaction to parse
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
- struct HistoryParseContext *uc,
- const json_t *transaction)
-{
- const struct TALER_EXCHANGE_Keys *key_state;
- struct GNUNET_JSON_Specification recoup_spec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &rh->details.recoup_details.coin_pub),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &rh->details.recoup_details.exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &rh->details.recoup_details.exchange_pub),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &rh->details.recoup_details.timestamp),
- GNUNET_JSON_spec_end ()
- };
-
- rh->type = TALER_EXCHANGE_RTT_RECOUP;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- recoup_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- key_state = uc->keys;
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
- &rh->details.
- recoup_details.exchange_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_exchange_online_confirm_recoup_verify (
- rh->details.recoup_details.timestamp,
- &rh->amount,
- &rh->details.recoup_details.coin_pub,
- uc->reserve_pub,
- &rh->details.recoup_details.exchange_pub,
- &rh->details.recoup_details.exchange_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (0 >
- TALER_amount_add (uc->total_in,
- uc->total_in,
- &rh->amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Parse "closing" reserve history entry.
- *
- * @param[in,out] rh entry to parse
- * @param uc our context
- * @param transaction the transaction to parse
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
- struct HistoryParseContext *uc,
- const json_t *transaction)
-{
- const struct TALER_EXCHANGE_Keys *key_state;
- struct TALER_FullPayto receiver_uri;
- struct GNUNET_JSON_Specification closing_spec[] = {
- TALER_JSON_spec_full_payto_uri (
- "receiver_account_details",
- &receiver_uri),
- GNUNET_JSON_spec_fixed_auto ("wtid",
- &rh->details.close_details.wtid),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &rh->details.close_details.exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &rh->details.close_details.exchange_pub),
- TALER_JSON_spec_amount_any ("closing_fee",
- &rh->details.close_details.fee),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &rh->details.close_details.timestamp),
- GNUNET_JSON_spec_end ()
- };
-
- rh->type = TALER_EXCHANGE_RTT_CLOSING;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- closing_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- key_state = uc->keys;
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (
- key_state,
- &rh->details.close_details.exchange_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_exchange_online_reserve_closed_verify (
- rh->details.close_details.timestamp,
- &rh->amount,
- &rh->details.close_details.fee,
- receiver_uri,
- &rh->details.close_details.wtid,
- uc->reserve_pub,
- &rh->details.close_details.exchange_pub,
- &rh->details.close_details.exchange_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (0 >
- TALER_amount_add (uc->total_out,
- uc->total_out,
- &rh->amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- rh->details.close_details.receiver_account_details.full_payto
- = GNUNET_strdup (receiver_uri.full_payto);
- return GNUNET_OK;
-}
-
-
-/**
- * Parse "merge" reserve history entry.
- *
- * @param[in,out] rh entry to parse
- * @param uc our context
- * @param transaction the transaction to parse
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
- struct HistoryParseContext *uc,
- const json_t *transaction)
-{
- uint32_t flags32;
- struct GNUNET_JSON_Specification merge_spec[] = {
- GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
- &rh->details.merge_details.h_contract_terms),
- GNUNET_JSON_spec_fixed_auto ("merge_pub",
- &rh->details.merge_details.merge_pub),
- GNUNET_JSON_spec_fixed_auto ("purse_pub",
- &rh->details.merge_details.purse_pub),
- GNUNET_JSON_spec_uint32 ("min_age",
- &rh->details.merge_details.min_age),
- GNUNET_JSON_spec_uint32 ("flags",
- &flags32),
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &rh->details.merge_details.reserve_sig),
- TALER_JSON_spec_amount_any ("purse_fee",
- &rh->details.merge_details.purse_fee),
- GNUNET_JSON_spec_timestamp ("merge_timestamp",
- &rh->details.merge_details.merge_timestamp),
- GNUNET_JSON_spec_timestamp ("purse_expiration",
- &rh->details.merge_details.purse_expiration),
- GNUNET_JSON_spec_bool ("merged",
- &rh->details.merge_details.merged),
- GNUNET_JSON_spec_end ()
- };
-
- rh->type = TALER_EXCHANGE_RTT_MERGE;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- merge_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- rh->details.merge_details.flags =
- (enum TALER_WalletAccountMergeFlags) flags32;
- if (GNUNET_OK !=
- TALER_wallet_account_merge_verify (
- rh->details.merge_details.merge_timestamp,
- &rh->details.merge_details.purse_pub,
- rh->details.merge_details.purse_expiration,
- &rh->details.merge_details.h_contract_terms,
- &rh->amount,
- &rh->details.merge_details.purse_fee,
- rh->details.merge_details.min_age,
- rh->details.merge_details.flags,
- uc->reserve_pub,
- &rh->details.merge_details.reserve_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (rh->details.merge_details.merged)
- {
- if (0 >
- TALER_amount_add (uc->total_in,
- uc->total_in,
- &rh->amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- else
- {
- if (0 >
- TALER_amount_add (uc->total_out,
- uc->total_out,
- &rh->details.merge_details.purse_fee))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Parse "open" reserve open entry.
- *
- * @param[in,out] rh entry to parse
- * @param uc our context
- * @param transaction the transaction to parse
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
- struct HistoryParseContext *uc,
- const json_t *transaction)
-{
- struct GNUNET_JSON_Specification open_spec[] = {
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &rh->details.open_request.reserve_sig),
- TALER_JSON_spec_amount_any ("open_payment",
- &rh->details.open_request.reserve_payment),
- GNUNET_JSON_spec_uint32 ("requested_min_purses",
- &rh->details.open_request.purse_limit),
- GNUNET_JSON_spec_timestamp ("request_timestamp",
- &rh->details.open_request.request_timestamp),
- GNUNET_JSON_spec_timestamp ("requested_expiration",
- &rh->details.open_request.reserve_expiration),
- GNUNET_JSON_spec_end ()
- };
-
- rh->type = TALER_EXCHANGE_RTT_OPEN;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- open_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_wallet_reserve_open_verify (
- &rh->amount,
- rh->details.open_request.request_timestamp,
- rh->details.open_request.reserve_expiration,
- rh->details.open_request.purse_limit,
- uc->reserve_pub,
- &rh->details.open_request.reserve_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (0 >
- TALER_amount_add (uc->total_out,
- uc->total_out,
- &rh->amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Parse "close" reserve close entry.
- *
- * @param[in,out] rh entry to parse
- * @param uc our context
- * @param transaction the transaction to parse
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
- struct HistoryParseContext *uc,
- const json_t *transaction)
-{
- struct GNUNET_JSON_Specification close_spec[] = {
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &rh->details.close_request.reserve_sig),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("h_payto",
- &rh->details.close_request.
- target_account_h_payto),
- NULL),
- GNUNET_JSON_spec_timestamp ("request_timestamp",
- &rh->details.close_request.request_timestamp),
- GNUNET_JSON_spec_end ()
- };
-
- rh->type = TALER_EXCHANGE_RTT_CLOSE;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- close_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- /* force amount to invalid */
- memset (&rh->amount,
- 0,
- sizeof (rh->amount));
- if (GNUNET_OK !=
- TALER_wallet_reserve_close_verify (
- rh->details.close_request.request_timestamp,
- &rh->details.close_request.target_account_h_payto,
- uc->reserve_pub,
- &rh->details.close_request.reserve_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-static void
-free_reserve_history (
- unsigned int len,
- struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len])
-{
- for (unsigned int i = 0; i<len; i++)
- {
- struct TALER_EXCHANGE_ReserveHistoryEntry *rhi = &rhistory[i];
-
- switch (rhi->type)
- {
- case TALER_EXCHANGE_RTT_CREDIT:
- GNUNET_free (rhi->details.in_details.sender_url.full_payto);
- break;
- case TALER_EXCHANGE_RTT_WITHDRAWAL:
- break;
- case TALER_EXCHANGE_RTT_RECOUP:
- break;
- case TALER_EXCHANGE_RTT_CLOSING:
- break;
- case TALER_EXCHANGE_RTT_MERGE:
- break;
- case TALER_EXCHANGE_RTT_OPEN:
- break;
- case TALER_EXCHANGE_RTT_CLOSE:
- GNUNET_free (rhi->details.close_details
- .receiver_account_details.full_payto);
- break;
- }
- }
- GNUNET_free (rhistory);
-}
-
-
-/**
- * Parse history given in JSON format and return it in binary
- * format.
- *
- * @param keys exchange keys
- * @param history JSON array with the history
- * @param reserve_pub public key of the reserve to inspect
- * @param currency currency we expect the balance to be in
- * @param[out] total_in set to value of credits to reserve
- * @param[out] total_out set to value of debits from reserve
- * @param history_length number of entries in @a history
- * @param[out] rhistory array of length @a history_length, set to the
- * parsed history entries
- * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
- * were set,
- * #GNUNET_SYSERR if there was a protocol violation in @a history
- */
-static enum GNUNET_GenericReturnValue
-parse_reserve_history (
- const struct TALER_EXCHANGE_Keys *keys,
- const json_t *history,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const char *currency,
- struct TALER_Amount *total_in,
- struct TALER_Amount *total_out,
- unsigned int history_length,
- struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length])
-{
- const struct
- {
- const char *type;
- ParseHelper helper;
- } map[] = {
- { "CREDIT", &parse_credit },
- { "WITHDRAW", &parse_withdraw },
- { "RECOUP", &parse_recoup },
- { "MERGE", &parse_merge },
- { "CLOSING", &parse_closing },
- { "OPEN", &parse_open },
- { "CLOSE", &parse_close },
- { NULL, NULL }
- };
- struct GNUNET_HashCode uuid[history_length];
- struct HistoryParseContext uc = {
- .keys = keys,
- .reserve_pub = reserve_pub,
- .uuids = uuid,
- .total_in = total_in,
- .total_out = total_out
- };
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (currency,
- total_in));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (currency,
- total_out));
- for (unsigned int off = 0; off<history_length; off++)
- {
- struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off];
- json_t *transaction;
- struct TALER_Amount amount;
- const char *type;
- struct GNUNET_JSON_Specification hist_spec[] = {
- GNUNET_JSON_spec_string ("type",
- &type),
- TALER_JSON_spec_amount_any ("amount",
- &amount),
- /* 'wire' and 'signature' are optional depending on 'type'! */
- GNUNET_JSON_spec_end ()
- };
- bool found = false;
-
- transaction = json_array_get (history,
- off);
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- hist_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- json_dumpf (transaction,
- stderr,
- JSON_INDENT (2));
- return GNUNET_SYSERR;
- }
- rh->amount = amount;
- if (GNUNET_YES !=
- TALER_amount_cmp_currency (&amount,
- total_in))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- for (unsigned int i = 0; NULL != map[i].type; i++)
- {
- if (0 == strcasecmp (map[i].type,
- type))
- {
- found = true;
- if (GNUNET_OK !=
- map[i].helper (rh,
- &uc,
- transaction))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- break;
- }
- }
- if (! found)
- {
- /* unexpected 'type', protocol incompatibility, complain! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Handle HTTP header received by curl.
- *
- * @param buffer one line of HTTP header data
- * @param size size of an item
- * @param nitems number of items passed
- * @param userdata our `struct TALER_EXCHANGE_ReservesHistoryHandle *`
- * @return `size * nitems`
- */
-static size_t
-handle_header (char *buffer,
- size_t size,
- size_t nitems,
- void *userdata)
-{
- struct TALER_EXCHANGE_ReservesHistoryHandle *rhh = userdata;
- size_t total = size * nitems;
- char *ndup;
- const char *hdr_type;
- char *hdr_val;
- char *sp;
-
- ndup = GNUNET_strndup (buffer,
- total);
- hdr_type = strtok_r (ndup,
- ":",
- &sp);
- if (NULL == hdr_type)
- {
- GNUNET_free (ndup);
- return total;
- }
- hdr_val = strtok_r (NULL,
- "\n\r",
- &sp);
- if (NULL == hdr_val)
- {
- GNUNET_free (ndup);
- return total;
- }
- if (' ' == *hdr_val)
- hdr_val++;
- if (0 == strcasecmp (hdr_type,
- MHD_HTTP_HEADER_ETAG))
- {
- unsigned long long tval;
- char dummy;
-
- if (1 !=
- sscanf (hdr_val,
- "\"%llu\"%c",
- &tval,
- &dummy))
- {
- GNUNET_break_op (0);
- GNUNET_free (ndup);
- return 0;
- }
- rhh->etag = (uint64_t) tval;
- }
- GNUNET_free (ndup);
- return total;
-}
-
-
-/**
- * We received an #MHD_HTTP_OK history code. Handle the JSON
- * response.
- *
- * @param rsh handle of the request
- * @param j JSON response
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rsh,
- const json_t *j)
-{
- const json_t *history;
- unsigned int len;
- struct TALER_EXCHANGE_ReserveHistory rs = {
- .hr.reply = j,
- .hr.http_status = MHD_HTTP_OK,
- .details.ok.etag = rsh->etag
- };
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("balance",
- &rs.details.ok.balance),
- GNUNET_JSON_spec_array_const ("history",
- &history),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- len = json_array_size (history);
- {
- struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
-
- rhistory = GNUNET_new_array (len,
- struct TALER_EXCHANGE_ReserveHistoryEntry);
- if (GNUNET_OK !=
- parse_reserve_history (rsh->keys,
- history,
- &rsh->reserve_pub,
- rs.details.ok.balance.currency,
- &rs.details.ok.total_in,
- &rs.details.ok.total_out,
- len,
- rhistory))
- {
- GNUNET_break_op (0);
- free_reserve_history (len,
- rhistory);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if (NULL != rsh->cb)
- {
- rs.details.ok.history = rhistory;
- rs.details.ok.history_len = len;
- rsh->cb (rsh->cb_cls,
- &rs);
- rsh->cb = NULL;
- }
- free_reserve_history (len,
- rhistory);
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /reserves/$RID/history request.
- *
- * @param cls the `struct TALER_EXCHANGE_ReservesHistoryHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_reserves_history_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_ReservesHistoryHandle *rsh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_ReserveHistory rs = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
-
- rsh->job = NULL;
- switch (response_code)
- {
- case 0:
- rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- if (GNUNET_OK !=
- handle_reserves_history_ok (rsh,
- j))
- {
- rs.hr.http_status = 0;
- rs.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 */
- GNUNET_break (0);
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_FORBIDDEN:
- /* This should never happen, either us or the exchange is buggy
- (or API version conflict); just pass JSON reply to the application */
- GNUNET_break (0);
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.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 */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.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 */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for reserves history\n",
- (unsigned int) response_code,
- (int) rs.hr.ec);
- break;
- }
- if (NULL != rsh->cb)
- {
- rsh->cb (rsh->cb_cls,
- &rs);
- rsh->cb = NULL;
- }
- TALER_EXCHANGE_reserves_history_cancel (rsh);
-}
-
-
-struct TALER_EXCHANGE_ReservesHistoryHandle *
-TALER_EXCHANGE_reserves_history (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- uint64_t start_off,
- TALER_EXCHANGE_ReservesHistoryCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_ReservesHistoryHandle *rsh;
- CURL *eh;
- char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 64];
- struct curl_slist *job_headers;
-
- rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle);
- rsh->cb = cb;
- rsh->cb_cls = cb_cls;
- GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
- &rsh->reserve_pub.eddsa_pub);
- {
- char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- &rsh->reserve_pub,
- sizeof (rsh->reserve_pub),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- if (0 != start_off)
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "reserves/%s/history?start=%llu",
- pub_str,
- (unsigned long long) start_off);
- else
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "reserves/%s/history",
- pub_str);
- }
- rsh->url = TALER_url_join (url,
- arg_str,
- NULL);
- if (NULL == rsh->url)
- {
- GNUNET_free (rsh);
- return NULL;
- }
- eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- GNUNET_free (rsh->url);
- GNUNET_free (rsh);
- return NULL;
- }
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_HEADERFUNCTION,
- &handle_header));
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_HEADERDATA,
- rsh));
- {
- struct TALER_ReserveSignatureP reserve_sig;
- char *sig_hdr;
- char *hdr;
-
- TALER_wallet_reserve_history_sign (start_off,
- reserve_priv,
- &reserve_sig);
-
- sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
- &reserve_sig,
- sizeof (reserve_sig));
- GNUNET_asprintf (&hdr,
- "%s: %s",
- TALER_RESERVE_HISTORY_SIGNATURE_HEADER,
- sig_hdr);
- GNUNET_free (sig_hdr);
- job_headers = curl_slist_append (NULL,
- hdr);
- GNUNET_free (hdr);
- if (NULL == job_headers)
- {
- GNUNET_break (0);
- curl_easy_cleanup (eh);
- return NULL;
- }
- }
-
- rsh->keys = TALER_EXCHANGE_keys_incref (keys);
- rsh->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- job_headers,
- &handle_reserves_history_finished,
- rsh);
- curl_slist_free_all (job_headers);
- return rsh;
-}
-
-
-void
-TALER_EXCHANGE_reserves_history_cancel (
- struct TALER_EXCHANGE_ReservesHistoryHandle *rsh)
-{
- if (NULL != rsh->job)
- {
- GNUNET_CURL_job_cancel (rsh->job);
- rsh->job = NULL;
- }
- TALER_curl_easy_post_finished (&rsh->post_ctx);
- GNUNET_free (rsh->url);
- TALER_EXCHANGE_keys_decref (rsh->keys);
- GNUNET_free (rsh);
-}
-
-
-/* end of exchange_api_reserves_history.c */
diff --git a/src/lib/exchange_api_reserves_open.c b/src/lib/exchange_api_reserves_open.c
@@ -1,567 +0,0 @@
-/*
- 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_reserves_open.c
- * @brief Implementation of the POST /reserves/$RESERVE_PUB/open requests
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP open codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_json_lib.h"
-#include "exchange_api_common.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * Information we keep per coin to validate the reply.
- */
-struct CoinData
-{
- /**
- * Public key of the coin.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * Signature by the coin.
- */
- struct TALER_CoinSpendSignatureP coin_sig;
-
- /**
- * The hash of the denomination's public key
- */
- struct TALER_DenominationHashP h_denom_pub;
-
- /**
- * How much did this coin contribute.
- */
- struct TALER_Amount contribution;
-};
-
-
-/**
- * @brief A /reserves/$RID/open Handle
- */
-struct TALER_EXCHANGE_ReservesOpenHandle
-{
-
- /**
- * The keys of the exchange this request handle will use
- */
- struct TALER_EXCHANGE_Keys *keys;
-
- /**
- * 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;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_ReservesOpenCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Information we keep per coin to validate the reply.
- */
- struct CoinData *coins;
-
- /**
- * Length of the @e coins array.
- */
- unsigned int num_coins;
-
- /**
- * Public key of the reserve we are querying.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Our signature.
- */
- struct TALER_ReserveSignatureP reserve_sig;
-
- /**
- * When did we make the request.
- */
- struct GNUNET_TIME_Timestamp ts;
-
-};
-
-
-/**
- * We received an #MHD_HTTP_OK open code. Handle the JSON
- * response.
- *
- * @param roh handle of the request
- * @param j JSON response
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_reserves_open_ok (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
- const json_t *j)
-{
- struct TALER_EXCHANGE_ReserveOpenResult rs = {
- .hr.reply = j,
- .hr.http_status = MHD_HTTP_OK,
- };
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("open_cost",
- &rs.details.ok.open_cost),
- GNUNET_JSON_spec_timestamp ("reserve_expiration",
- &rs.details.ok.expiration_time),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- roh->cb (roh->cb_cls,
- &rs);
- roh->cb = NULL;
- GNUNET_JSON_parse_free (spec);
- return GNUNET_OK;
-}
-
-
-/**
- * We received an #MHD_HTTP_PAYMENT_REQUIRED open code. Handle the JSON
- * response.
- *
- * @param roh handle of the request
- * @param j JSON response
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_reserves_open_pr (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
- const json_t *j)
-{
- struct TALER_EXCHANGE_ReserveOpenResult rs = {
- .hr.reply = j,
- .hr.http_status = MHD_HTTP_PAYMENT_REQUIRED,
- };
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("open_cost",
- &rs.details.payment_required.open_cost),
- GNUNET_JSON_spec_timestamp ("reserve_expiration",
- &rs.details.payment_required.expiration_time),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- roh->cb (roh->cb_cls,
- &rs);
- roh->cb = NULL;
- GNUNET_JSON_parse_free (spec);
- return GNUNET_OK;
-}
-
-
-/**
- * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS open code. Handle the JSON
- * response.
- *
- * @param roh handle of the request
- * @param j JSON response
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-handle_reserves_open_kyc (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
- const json_t *j)
-{
- struct TALER_EXCHANGE_ReserveOpenResult rs = {
- .hr.reply = j,
- .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
- };
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto (
- "h_payto",
- &rs.details.unavailable_for_legal_reasons.h_payto),
- GNUNET_JSON_spec_uint64 (
- "requirement_row",
- &rs.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);
- return GNUNET_SYSERR;
- }
- roh->cb (roh->cb_cls,
- &rs);
- roh->cb = NULL;
- GNUNET_JSON_parse_free (spec);
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /reserves/$RID/open request.
- *
- * @param cls the `struct TALER_EXCHANGE_ReservesOpenHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_reserves_open_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_ReservesOpenHandle *roh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_ReserveOpenResult rs = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
-
- roh->job = NULL;
- switch (response_code)
- {
- case 0:
- rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- if (GNUNET_OK !=
- handle_reserves_open_ok (roh,
- j))
- {
- GNUNET_break_op (0);
- rs.hr.http_status = 0;
- rs.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 */
- GNUNET_break (0);
- json_dumpf (j,
- stderr,
- JSON_INDENT (2));
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_PAYMENT_REQUIRED:
- if (GNUNET_OK !=
- handle_reserves_open_pr (roh,
- j))
- {
- GNUNET_break_op (0);
- rs.hr.http_status = 0;
- rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- }
- break;
- case MHD_HTTP_FORBIDDEN:
- /* This should never happen, either us or the exchange is buggy
- (or API version conflict); just pass JSON reply to the application */
- GNUNET_break (0);
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.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 */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_CONFLICT:
- {
- const struct CoinData *cd = NULL;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &rs.details.conflict.coin_pub),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- rs.hr.http_status = 0;
- rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- for (unsigned int i = 0; i<roh->num_coins; i++)
- {
- const struct CoinData *cdi = &roh->coins[i];
-
- if (0 == GNUNET_memcmp (&rs.details.conflict.coin_pub,
- &cdi->coin_pub))
- {
- cd = cdi;
- break;
- }
- }
- if (NULL == cd)
- {
- GNUNET_break_op (0);
- rs.hr.http_status = 0;
- rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- }
- case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
- if (GNUNET_OK !=
- handle_reserves_open_kyc (roh,
- j))
- {
- GNUNET_break_op (0);
- rs.hr.http_status = 0;
- rs.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 */
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- rs.hr.ec = TALER_JSON_get_error_code (j);
- rs.hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for reserves open\n",
- (unsigned int) response_code,
- (int) rs.hr.ec);
- break;
- }
- if (NULL != roh->cb)
- {
- roh->cb (roh->cb_cls,
- &rs);
- roh->cb = NULL;
- }
- TALER_EXCHANGE_reserves_open_cancel (roh);
-}
-
-
-struct TALER_EXCHANGE_ReservesOpenHandle *
-TALER_EXCHANGE_reserves_open (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- const struct TALER_Amount *reserve_contribution,
- unsigned int coin_payments_length,
- const struct TALER_EXCHANGE_PurseDeposit coin_payments[
- static coin_payments_length],
- struct GNUNET_TIME_Timestamp expiration_time,
- uint32_t min_purses,
- TALER_EXCHANGE_ReservesOpenCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_ReservesOpenHandle *roh;
- CURL *eh;
- char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
- json_t *cpa;
-
- roh = GNUNET_new (struct TALER_EXCHANGE_ReservesOpenHandle);
- roh->cb = cb;
- roh->cb_cls = cb_cls;
- roh->ts = GNUNET_TIME_timestamp_get ();
- GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
- &roh->reserve_pub.eddsa_pub);
- {
- char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- &roh->reserve_pub,
- sizeof (roh->reserve_pub),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "reserves/%s/open",
- pub_str);
- }
- roh->url = TALER_url_join (url,
- arg_str,
- NULL);
- if (NULL == roh->url)
- {
- GNUNET_free (roh);
- return NULL;
- }
- eh = TALER_EXCHANGE_curl_easy_get_ (roh->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- GNUNET_free (roh->url);
- GNUNET_free (roh);
- return NULL;
- }
- TALER_wallet_reserve_open_sign (reserve_contribution,
- roh->ts,
- expiration_time,
- min_purses,
- reserve_priv,
- &roh->reserve_sig);
- roh->coins = GNUNET_new_array (coin_payments_length,
- struct CoinData);
- cpa = json_array ();
- GNUNET_assert (NULL != cpa);
- for (unsigned int i = 0; i<coin_payments_length; i++)
- {
- const struct TALER_EXCHANGE_PurseDeposit *pd = &coin_payments[i];
- const struct TALER_AgeCommitmentProof *acp = pd->age_commitment_proof;
- struct TALER_AgeCommitmentHashP ahac;
- struct TALER_AgeCommitmentHashP *achp = NULL;
- struct CoinData *cd = &roh->coins[i];
- json_t *cp;
-
- cd->contribution = pd->amount;
- cd->h_denom_pub = pd->h_denom_pub;
- if (NULL != acp)
- {
- TALER_age_commitment_hash (&acp->commitment,
- &ahac);
- achp = &ahac;
- }
- TALER_wallet_reserve_open_deposit_sign (&pd->amount,
- &roh->reserve_sig,
- &pd->coin_priv,
- &cd->coin_sig);
- GNUNET_CRYPTO_eddsa_key_get_public (&pd->coin_priv.eddsa_priv,
- &cd->coin_pub.eddsa_pub);
-
- cp = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_auto ("h_age_commitment",
- achp)),
- TALER_JSON_pack_amount ("amount",
- &pd->amount),
- GNUNET_JSON_pack_data_auto ("denom_pub_hash",
- &pd->h_denom_pub),
- TALER_JSON_pack_denom_sig ("ub_sig",
- &pd->denom_sig),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- &cd->coin_pub),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &cd->coin_sig));
- GNUNET_assert (0 ==
- json_array_append_new (cpa,
- cp));
- }
- {
- json_t *open_obj = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_timestamp ("request_timestamp",
- roh->ts),
- GNUNET_JSON_pack_timestamp ("reserve_expiration",
- expiration_time),
- GNUNET_JSON_pack_array_steal ("payments",
- cpa),
- TALER_JSON_pack_amount ("reserve_payment",
- reserve_contribution),
- GNUNET_JSON_pack_uint64 ("purse_limit",
- min_purses),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &roh->reserve_sig));
-
- if (GNUNET_OK !=
- TALER_curl_easy_post (&roh->post_ctx,
- eh,
- open_obj))
- {
- GNUNET_break (0);
- curl_easy_cleanup (eh);
- json_decref (open_obj);
- GNUNET_free (roh->coins);
- GNUNET_free (roh->url);
- GNUNET_free (roh);
- return NULL;
- }
- json_decref (open_obj);
- }
- roh->keys = TALER_EXCHANGE_keys_incref (keys);
- roh->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- roh->post_ctx.headers,
- &handle_reserves_open_finished,
- roh);
- return roh;
-}
-
-
-void
-TALER_EXCHANGE_reserves_open_cancel (
- struct TALER_EXCHANGE_ReservesOpenHandle *roh)
-{
- if (NULL != roh->job)
- {
- GNUNET_CURL_job_cancel (roh->job);
- roh->job = NULL;
- }
- TALER_curl_easy_post_finished (&roh->post_ctx);
- GNUNET_free (roh->coins);
- GNUNET_free (roh->url);
- TALER_EXCHANGE_keys_decref (roh->keys);
- GNUNET_free (roh);
-}
-
-
-/* end of exchange_api_reserves_open.c */
diff --git a/src/lib/exchange_api_reveal_melt.c b/src/lib/exchange_api_reveal_melt.c
@@ -1,416 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2025 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_reveal_melt.c
- * @brief Implementation of the /reveal-melt request
- * @author Özgür Kesim
- */
-#include "taler/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/taler_json_lib.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_common.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-#include "exchange_api_refresh_common.h"
-
-
-/**
- * Handler for a running reveal-melt request
- */
-struct TALER_EXCHANGE_RevealMeltHandle
-{
- /**
- * The url for the request
- */
- char *request_url;
-
- /**
- * CURL handle for the request job.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Post Context
- */
- struct TALER_CURL_PostContext post_ctx;
-
- /**
- * Number of coins to expect
- */
- size_t num_expected_coins;
-
- /**
- * The input provided
- */
- const struct TALER_EXCHANGE_RevealMeltInput *reveal_input;
-
- /**
- * The melt data
- */
- struct MeltData md;
-
- /**
- * Callback to pass the result onto
- */
- TALER_EXCHANGE_RevealMeltCallback callback;
-
- /**
- * Closure for @e callback
- */
- void *callback_cls;
-
-};
-
-/**
- * We got a 200 OK response for the /reveal-melt operation.
- * Extract the signed blinded coins and return it to the caller.
- *
- * @param mrh operation handle
- * @param j_response reply from the exchange
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static enum GNUNET_GenericReturnValue
-reveal_melt_ok (
- struct TALER_EXCHANGE_RevealMeltHandle *mrh,
- const json_t *j_response)
-{
- struct TALER_EXCHANGE_RevealMeltResponse response = {
- .hr.reply = j_response,
- .hr.http_status = MHD_HTTP_OK,
- };
- struct TALER_BlindedDenominationSignature blind_sigs[mrh->num_expected_coins];
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_array_of_blinded_denom_sigs ("ev_sigs",
- mrh->num_expected_coins,
- blind_sigs),
- GNUNET_JSON_spec_end ()
- };
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j_response,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- {
- struct TALER_EXCHANGE_RevealedCoinInfo coins[mrh->num_expected_coins];
-
- /* Reconstruct the coins and unblind the signatures */
- for (unsigned int i = 0; i<mrh->num_expected_coins; i++)
- {
- struct TALER_EXCHANGE_RevealedCoinInfo *rci = &coins[i];
- const struct FreshCoinData *fcd = &mrh->md.fcds[i];
- const struct TALER_DenominationPublicKey *pk;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_CoinPubHashP coin_hash;
- struct TALER_FreshCoin coin;
- union GNUNET_CRYPTO_BlindingSecretP bks;
- const struct TALER_AgeCommitmentHashP *pah = NULL;
-
- rci->ps = fcd->ps[mrh->reveal_input->noreveal_index];
- rci->bks = fcd->bks[mrh->reveal_input->noreveal_index];
- rci->age_commitment_proof = NULL;
- pk = &fcd->fresh_pk;
- if (NULL != mrh->md.melted_coin.age_commitment_proof)
- {
- rci->age_commitment_proof
- = fcd->age_commitment_proofs[mrh->reveal_input->noreveal_index];
- TALER_age_commitment_hash (
- &rci->age_commitment_proof->commitment,
- &rci->h_age_commitment);
- pah = &rci->h_age_commitment;
- }
-
- TALER_planchet_setup_coin_priv (&rci->ps,
- &mrh->reveal_input->blinding_values[i],
- &rci->coin_priv);
- TALER_planchet_blinding_secret_create (&rci->ps,
- &mrh->reveal_input->blinding_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_sigs[i],
- &bks,
- &rci->coin_priv,
- pah,
- &coin_hash,
- &mrh->reveal_input->blinding_values[i],
- &coin))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- GNUNET_JSON_parse_free (spec);
- rci->sig = coin.sig;
- }
-
- response.details.ok.num_coins = mrh->num_expected_coins;
- response.details.ok.coins = coins;
- mrh->callback (mrh->callback_cls,
- &response);
- /* Make sure the callback isn't called again */
- mrh->callback = NULL;
- /* Free resources */
- for (size_t i = 0; i < mrh->num_expected_coins; i++)
- {
- struct TALER_EXCHANGE_RevealedCoinInfo *rci = &coins[i];
-
- TALER_denom_sig_free (&rci->sig);
- TALER_blinded_denom_sig_free (&blind_sigs[i]);
- }
- }
-
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /reveal-melt request.
- *
- * @param cls the `struct TALER_EXCHANGE_RevealMeltHandle`
- * @param response_code The HTTP response code
- * @param response response data
- */
-static void
-handle_reveal_melt_finished (
- void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_RevealMeltHandle *mrh = cls;
- const json_t *j_response = response;
- struct TALER_EXCHANGE_RevealMeltResponse awr = {
- .hr.reply = j_response,
- .hr.http_status = (unsigned int) response_code
- };
-
- mrh->job = NULL;
- switch (response_code)
- {
- case 0:
- awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- enum GNUNET_GenericReturnValue ret;
-
- ret = reveal_melt_ok (mrh,
- 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 == mrh->callback);
- TALER_EXCHANGE_reveal_melt_cancel (mrh);
- 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 */
- 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-melt 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
- * or TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH.
- */
- 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 melt\n",
- (unsigned int) response_code,
- (int) awr.hr.ec);
- break;
- }
- mrh->callback (mrh->callback_cls,
- &awr);
- TALER_EXCHANGE_reveal_melt_cancel (mrh);
-}
-
-
-/**
- * Call /reveal-melt
- *
- * @param curl_ctx The context for CURL
- * @param mrh The handler
- */
-static void
-perform_protocol (
- struct GNUNET_CURL_Context *curl_ctx,
- struct TALER_EXCHANGE_RevealMeltHandle *mrh)
-{
- CURL *curlh;
- json_t *j_batch_seeds;
-
-
- j_batch_seeds = json_array ();
- GNUNET_assert (NULL != j_batch_seeds);
-
- for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
- {
- if (mrh->reveal_input->noreveal_index == k)
- continue;
-
- GNUNET_assert (0 == json_array_append_new (
- j_batch_seeds,
- GNUNET_JSON_from_data_auto (
- &mrh->md.kappa_batch_seeds.tuple[k])));
- }
- {
- json_t *j_request_body;
-
- j_request_body = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("rc",
- &mrh->md.rc),
- GNUNET_JSON_pack_array_steal ("batch_seeds",
- j_batch_seeds));
- GNUNET_assert (NULL != j_request_body);
-
- if (NULL != mrh->reveal_input->melt_input->melt_age_commitment_proof)
- {
- json_t *j_age = GNUNET_JSON_PACK (
- TALER_JSON_pack_age_commitment (
- "age_commitment",
- &mrh->reveal_input->melt_input->melt_age_commitment_proof->commitment)
- );
- GNUNET_assert (NULL != j_age);
- GNUNET_assert (0 ==
- json_object_update_new (j_request_body,
- j_age));
- }
- curlh = TALER_EXCHANGE_curl_easy_get_ (mrh->request_url);
- GNUNET_assert (NULL != curlh);
- GNUNET_assert (GNUNET_OK ==
- TALER_curl_easy_post (&mrh->post_ctx,
- curlh,
- j_request_body));
- json_decref (j_request_body);
- }
- mrh->job = GNUNET_CURL_job_add2 (
- curl_ctx,
- curlh,
- mrh->post_ctx.headers,
- &handle_reveal_melt_finished,
- mrh);
- if (NULL == mrh->job)
- {
- GNUNET_break (0);
- if (NULL != curlh)
- curl_easy_cleanup (curlh);
- TALER_EXCHANGE_reveal_melt_cancel (mrh);
- }
-}
-
-
-struct TALER_EXCHANGE_RevealMeltHandle *
-TALER_EXCHANGE_reveal_melt (
- struct GNUNET_CURL_Context *curl_ctx,
- const char *exchange_url,
- const struct TALER_EXCHANGE_RevealMeltInput *reveal_melt_input,
- TALER_EXCHANGE_RevealMeltCallback reveal_cb,
- void *reveal_cb_cls)
-{
- struct TALER_EXCHANGE_RevealMeltHandle *mrh =
- GNUNET_new (struct TALER_EXCHANGE_RevealMeltHandle);
- mrh->callback = reveal_cb;
- mrh->callback_cls = reveal_cb_cls;
- mrh->reveal_input = reveal_melt_input;
- mrh->num_expected_coins = reveal_melt_input->melt_input->num_fresh_denom_pubs;
- mrh->request_url = TALER_url_join (exchange_url,
- "reveal-melt",
- NULL);
- if (NULL == mrh->request_url)
- {
- GNUNET_break (0);
- GNUNET_free (mrh);
- return NULL;
- }
- if (reveal_melt_input->num_blinding_values !=
- reveal_melt_input->melt_input->num_fresh_denom_pubs)
- {
- GNUNET_break (0);
- GNUNET_free (mrh);
- return NULL;
- }
- TALER_EXCHANGE_get_melt_data (
- reveal_melt_input->rms,
- reveal_melt_input->melt_input,
- reveal_melt_input->blinding_seed,
- reveal_melt_input->blinding_values,
- &mrh->md);
- perform_protocol (curl_ctx,
- mrh);
- return mrh;
-}
-
-
-void
-TALER_EXCHANGE_reveal_melt_cancel (
- struct TALER_EXCHANGE_RevealMeltHandle *mrh)
-{
- if (NULL != mrh->job)
- {
- GNUNET_CURL_job_cancel (mrh->job);
- mrh->job = NULL;
- }
- TALER_curl_easy_post_finished (&mrh->post_ctx);
- TALER_EXCHANGE_free_melt_data (&mrh->md);
- GNUNET_free (mrh->request_url);
- GNUNET_free (mrh);
-}
diff --git a/src/lib/exchange_api_reveal_withdraw.c b/src/lib/exchange_api_reveal_withdraw.c
@@ -1,366 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2023-2025 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_reveal_withdraw.c
- * @brief Implementation of /reveal-withdraw requests
- * @author Özgür Kesim
- */
-
-#include "taler/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/taler_curl_lib.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_common.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-/**
- * Handler for a running reveal-withdraw request
- */
-struct TALER_EXCHANGE_RevealWithdrawHandle
-{
- /**
- * The commitment from the previous call withdraw
- */
- const struct TALER_HashBlindedPlanchetsP *planchets_h;
-
- /**
- * Number of coins for which to reveal tuples of seeds
- */
- size_t num_coins;
-
- /**
- * The TALER_CNC_KAPPA-1 tuple of seeds to reveal
- */
- struct TALER_RevealWithdrawMasterSeedsP seeds;
-
- /**
- * The url for the reveal request
- */
- 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_RevealWithdrawCallback callback;
-
- /**
- * Reveal
- */
- void *callback_cls;
-};
-
-
-/**
- * We got a 200 OK response for the /reveal-withdraw operation.
- * Extract the signed blindedcoins and return it to the caller.
- *
- * @param wrh operation handle
- * @param j_response reply from the exchange
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static enum GNUNET_GenericReturnValue
-reveal_withdraw_ok (
- struct TALER_EXCHANGE_RevealWithdrawHandle *wrh,
- const json_t *j_response)
-{
- struct TALER_EXCHANGE_RevealWithdrawResponse 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 (wrh->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[wrh->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 ispec[] = {
- TALER_JSON_spec_blinded_denom_sig (NULL,
- &denom_sigs[n]),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j_sig,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
-
- response.details.ok.num_sigs = wrh->num_coins;
- response.details.ok.blinded_denom_sigs = denom_sigs;
- wrh->callback (wrh->callback_cls,
- &response);
- /* Make sure the callback isn't called again */
- wrh->callback = NULL;
- /* Free resources */
- for (size_t i = 0; i < wrh->num_coins; i++)
- TALER_blinded_denom_sig_free (&denom_sigs[i]);
- }
-
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /reveal-withdraw request.
- *
- * @param cls the `struct TALER_EXCHANGE_RevealWithdrawHandle`
- * @param response_code The HTTP response code
- * @param response response data
- */
-static void
-handle_reveal_withdraw_finished (
- void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_RevealWithdrawHandle *wrh = cls;
- const json_t *j_response = response;
- struct TALER_EXCHANGE_RevealWithdrawResponse awr = {
- .hr.reply = j_response,
- .hr.http_status = (unsigned int) response_code
- };
-
- wrh->job = NULL;
- switch (response_code)
- {
- case 0:
- awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- enum GNUNET_GenericReturnValue ret;
-
- ret = reveal_withdraw_ok (wrh,
- 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 == wrh->callback);
- TALER_EXCHANGE_reveal_withdraw_cancel (wrh);
- 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 */
- 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
- * or TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH.
- */
- 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;
- }
- wrh->callback (wrh->callback_cls,
- &awr);
- TALER_EXCHANGE_reveal_withdraw_cancel (wrh);
-}
-
-
-/**
- * Call /reveal-withdraw
- *
- * @param curl_ctx The context for CURL
- * @param wrh The handler
- */
-static void
-perform_protocol (
- struct GNUNET_CURL_Context *curl_ctx,
- struct TALER_EXCHANGE_RevealWithdrawHandle *wrh)
-{
- CURL *curlh;
- json_t *j_array_of_secrets;
-
- j_array_of_secrets = json_array ();
- GNUNET_assert (NULL != j_array_of_secrets);
-
- for (uint8_t k = 0; k < TALER_CNC_KAPPA - 1; k++)
- {
- json_t *j_sec = GNUNET_JSON_from_data_auto (&wrh->seeds.tuple[k]);
- GNUNET_assert (NULL != j_sec);
- GNUNET_assert (0 == json_array_append_new (j_array_of_secrets,
- j_sec));
- }
- {
- json_t *j_request_body;
-
- j_request_body = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("planchets_h",
- wrh->planchets_h),
- GNUNET_JSON_pack_array_steal ("disclosed_batch_seeds",
- j_array_of_secrets));
- GNUNET_assert (NULL != j_request_body);
-
- curlh = TALER_EXCHANGE_curl_easy_get_ (wrh->request_url);
- GNUNET_assert (NULL != curlh);
- GNUNET_assert (GNUNET_OK ==
- TALER_curl_easy_post (&wrh->post_ctx,
- curlh,
- j_request_body));
-
- json_decref (j_request_body);
- }
-
- wrh->job = GNUNET_CURL_job_add2 (
- curl_ctx,
- curlh,
- wrh->post_ctx.headers,
- &handle_reveal_withdraw_finished,
- wrh);
- if (NULL == wrh->job)
- {
- GNUNET_break (0);
- if (NULL != curlh)
- curl_easy_cleanup (curlh);
- TALER_EXCHANGE_reveal_withdraw_cancel (wrh);
- }
-
- return;
-}
-
-
-struct TALER_EXCHANGE_RevealWithdrawHandle *
-TALER_EXCHANGE_reveal_withdraw (
- struct GNUNET_CURL_Context *curl_ctx,
- const char *exchange_url,
- size_t num_coins,
- const struct TALER_HashBlindedPlanchetsP *planchets_h,
- const struct TALER_RevealWithdrawMasterSeedsP *seeds,
- TALER_EXCHANGE_RevealWithdrawCallback reveal_cb,
- void *reveal_cb_cls)
-{
- struct TALER_EXCHANGE_RevealWithdrawHandle *wrh =
- GNUNET_new (struct TALER_EXCHANGE_RevealWithdrawHandle);
- wrh->planchets_h = planchets_h;
- wrh->num_coins = num_coins;
- wrh->seeds = *seeds;
- wrh->callback = reveal_cb;
- wrh->callback_cls = reveal_cb_cls;
- wrh->request_url = TALER_url_join (exchange_url,
- "reveal-withdraw",
- NULL);
- if (NULL == wrh->request_url)
- {
- GNUNET_break (0);
- GNUNET_free (wrh);
- return NULL;
- }
-
- perform_protocol (curl_ctx, wrh);
-
- return wrh;
-}
-
-
-void
-TALER_EXCHANGE_reveal_withdraw_cancel (
- struct TALER_EXCHANGE_RevealWithdrawHandle *wrh)
-{
- if (NULL != wrh->job)
- {
- GNUNET_CURL_job_cancel (wrh->job);
- wrh->job = NULL;
- }
- TALER_curl_easy_post_finished (&wrh->post_ctx);
-
- if (NULL != wrh->request_url)
- GNUNET_free (wrh->request_url);
-
- GNUNET_free (wrh);
-}
-
-
-/* exchange_api_reveal_withdraw.c */
diff --git a/src/lib/exchange_api_transfers_get.c b/src/lib/exchange_api_transfers_get.c
@@ -1,400 +0,0 @@
-/*
- 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_transfers_get.c
- * @brief Implementation of the GET /transfers/ request
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler/taler_exchange_service.h"
-#include "taler/taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A /transfers/ GET Handle
- */
-struct TALER_EXCHANGE_TransfersGetHandle
-{
-
- /**
- * The keys of the exchange this request handle will use
- */
- struct TALER_EXCHANGE_Keys *keys;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_TransfersGetCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
-};
-
-
-/**
- * We got a #MHD_HTTP_OK response for the /transfers/ request.
- * Check that the response is well-formed and if it is, call the
- * callback. If not, return an error code.
- *
- * This code is very similar to
- * merchant_api_track_transfer.c::check_transfers_get_response_ok.
- * Any changes should likely be reflected there as well.
- *
- * @param wdh handle to the operation
- * @param json response we got
- * @return #GNUNET_OK if we are done and all is well,
- * #GNUNET_SYSERR if the response was bogus
- */
-static enum GNUNET_GenericReturnValue
-check_transfers_get_response_ok (
- struct TALER_EXCHANGE_TransfersGetHandle *wdh,
- const json_t *json)
-{
- const json_t *details_j;
- struct TALER_Amount total_expected;
- struct TALER_MerchantPublicKeyP merchant_pub;
- struct TALER_EXCHANGE_TransfersGetResponse tgr = {
- .hr.reply = json,
- .hr.http_status = MHD_HTTP_OK
- };
- struct TALER_EXCHANGE_TransferData *td
- = &tgr.details.ok.td;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("total",
- &td->total_amount),
- TALER_JSON_spec_amount_any ("wire_fee",
- &td->wire_fee),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &merchant_pub),
- GNUNET_JSON_spec_fixed_auto ("h_payto",
- &td->h_payto),
- GNUNET_JSON_spec_timestamp ("execution_time",
- &td->execution_time),
- GNUNET_JSON_spec_array_const ("deposits",
- &details_j),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &td->exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &td->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_amount_set_zero (td->total_amount.currency,
- &total_expected))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (
- wdh->keys,
- &td->exchange_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- td->details_length = json_array_size (details_j);
- {
- struct GNUNET_HashContext *hash_context;
- struct TALER_TrackTransferDetails *details;
-
- details = GNUNET_new_array (td->details_length,
- struct TALER_TrackTransferDetails);
- td->details = details;
- hash_context = GNUNET_CRYPTO_hash_context_start ();
- for (unsigned int i = 0; i<td->details_length; i++)
- {
- struct TALER_TrackTransferDetails *detail = &details[i];
- struct json_t *detail_j = json_array_get (details_j, i);
- struct GNUNET_JSON_Specification spec_detail[] = {
- GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
- &detail->h_contract_terms),
- GNUNET_JSON_spec_fixed_auto ("coin_pub", &detail->coin_pub),
- TALER_JSON_spec_amount ("deposit_value",
- total_expected.currency,
- &detail->coin_value),
- TALER_JSON_spec_amount ("deposit_fee",
- total_expected.currency,
- &detail->coin_fee),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount ("refund_total",
- total_expected.currency,
- &detail->refund_total),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (td->total_amount.currency,
- &detail->refund_total));
- if ( (GNUNET_OK !=
- GNUNET_JSON_parse (detail_j,
- spec_detail,
- NULL, NULL)) ||
- (0 >
- TALER_amount_add (&total_expected,
- &total_expected,
- &detail->coin_value)) ||
- (0 >
- TALER_amount_subtract (&total_expected,
- &total_expected,
- &detail->coin_fee)) )
- {
- GNUNET_break_op (0);
- GNUNET_CRYPTO_hash_context_abort (hash_context);
- GNUNET_free (details);
- return GNUNET_SYSERR;
- }
- /* build up big hash for signature checking later */
- TALER_exchange_online_wire_deposit_append (
- hash_context,
- &detail->h_contract_terms,
- td->execution_time,
- &detail->coin_pub,
- &detail->coin_value,
- &detail->coin_fee);
- }
- /* Check signature */
- GNUNET_CRYPTO_hash_context_finish (hash_context,
- &td->h_details);
- if (GNUNET_OK !=
- TALER_exchange_online_wire_deposit_verify (
- &td->total_amount,
- &td->wire_fee,
- &merchant_pub,
- &td->h_payto,
- &td->h_details,
- &td->exchange_pub,
- &td->exchange_sig))
- {
- GNUNET_break_op (0);
- GNUNET_free (details);
- return GNUNET_SYSERR;
- }
- if (0 >
- TALER_amount_subtract (&total_expected,
- &total_expected,
- &td->wire_fee))
- {
- GNUNET_break_op (0);
- GNUNET_free (details);
- return GNUNET_SYSERR;
- }
- if (0 !=
- TALER_amount_cmp (&total_expected,
- &td->total_amount))
- {
- GNUNET_break_op (0);
- GNUNET_free (details);
- return GNUNET_SYSERR;
- }
- wdh->cb (wdh->cb_cls,
- &tgr);
- GNUNET_free (details);
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /transfers/ request.
- *
- * @param cls the `struct TALER_EXCHANGE_TransfersGetHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_transfers_get_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_TransfersGetHandle *wdh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_TransfersGetResponse tgr = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
-
- wdh->job = NULL;
- switch (response_code)
- {
- case 0:
- tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- if (GNUNET_OK ==
- check_transfers_get_response_ok (wdh,
- j))
- {
- TALER_EXCHANGE_transfers_get_cancel (wdh);
- return;
- }
- GNUNET_break_op (0);
- tgr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- tgr.hr.http_status = 0;
- 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 */
- tgr.hr.ec = TALER_JSON_get_error_code (j);
- tgr.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 */
- tgr.hr.ec = TALER_JSON_get_error_code (j);
- tgr.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_NOT_FOUND:
- /* Exchange does not know about transaction;
- we should pass the reply to the application */
- tgr.hr.ec = TALER_JSON_get_error_code (j);
- tgr.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 */
- tgr.hr.ec = TALER_JSON_get_error_code (j);
- tgr.hr.hint = TALER_JSON_get_error_hint (j);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- tgr.hr.ec = TALER_JSON_get_error_code (j);
- tgr.hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for transfers get\n",
- (unsigned int) response_code,
- (int) tgr.hr.ec);
- break;
- }
- wdh->cb (wdh->cb_cls,
- &tgr);
- TALER_EXCHANGE_transfers_get_cancel (wdh);
-}
-
-
-struct TALER_EXCHANGE_TransfersGetHandle *
-TALER_EXCHANGE_transfers_get (
- struct GNUNET_CURL_Context *ctx,
- const char *url,
- struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- TALER_EXCHANGE_TransfersGetCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_TransfersGetHandle *wdh;
- CURL *eh;
- char arg_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2 + 32];
-
- wdh = GNUNET_new (struct TALER_EXCHANGE_TransfersGetHandle);
- wdh->cb = cb;
- wdh->cb_cls = cb_cls;
-
- {
- char wtid_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (wtid,
- sizeof (struct
- TALER_WireTransferIdentifierRawP),
- wtid_str,
- sizeof (wtid_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "transfers/%s",
- wtid_str);
- }
- wdh->url = TALER_url_join (url,
- arg_str,
- NULL);
- if (NULL == wdh->url)
- {
- GNUNET_free (wdh);
- return NULL;
- }
- eh = TALER_EXCHANGE_curl_easy_get_ (wdh->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- GNUNET_free (wdh->url);
- GNUNET_free (wdh);
- return NULL;
- }
- wdh->keys = TALER_EXCHANGE_keys_incref (keys);
- wdh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
- eh,
- &handle_transfers_get_finished,
- wdh);
- return wdh;
-}
-
-
-/**
- * Cancel wire deposits request. This function cannot be used on a request
- * handle if a response is already served for it.
- *
- * @param wdh the wire deposits request handle
- */
-void
-TALER_EXCHANGE_transfers_get_cancel (
- struct TALER_EXCHANGE_TransfersGetHandle *wdh)
-{
- if (NULL != wdh->job)
- {
- GNUNET_CURL_job_cancel (wdh->job);
- wdh->job = NULL;
- }
- GNUNET_free (wdh->url);
- TALER_EXCHANGE_keys_decref (wdh->keys);
- GNUNET_free (wdh);
-}
-
-
-/* end of exchange_api_transfers_get.c */
diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c
@@ -1,1928 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2023-2025 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_withdraw.c
- * @brief Implementation of /withdraw requests
- * @author Özgür Kesim
- */
-/**
- * We want the "dangerous" exports here as these are OUR exports
- * and we want to check that the prototypes match.
- */
-#define TALER_TESTING_EXPORTS_DANGEROUS 1
-#include "taler/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/taler_curl_lib.h"
-#include "taler/taler_error_codes.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_exchange_service.h"
-#include "exchange_api_common.h"
-#include "exchange_api_handle.h"
-#include "taler/taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-#include "taler/taler_util.h"
-
-/**
- * A CoinCandidate is populated from a master secret.
- * The data is copied from and generated out of the client's input.
- */
-struct CoinCandidate
-{
- /**
- * The details derived form the master secrets
- */
- struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details;
-
- /**
- * Blinded hash of the coin
- **/
- struct TALER_BlindedCoinHashP blinded_coin_h;
-
-};
-
-
-/**
- * Closure for a call to /blinding-prepare, contains data that is needed to process
- * the result.
- */
-struct BlindingPrepareClosure
-{
- /**
- * Number of coins in the blinding-prepare step.
- * Not that this number might be smaller than the total number
- * of coins in the withdraw, as the prepare is only necessary
- * for CS denominations
- */
- size_t num_prepare_coins;
-
- /**
- * Array of @e num_prepare_coins of data per coin
- */
- struct BlindingPrepareCoinData
- {
- /**
- * Pointer to the candidate in CoinData.candidates,
- * to continue to build its contents based on the results from /blinding-prepare
- */
- struct CoinCandidate *candidate;
-
- /**
- * Planchet to finally generate in the corresponding candidate
- * in CoindData.planchet_details
- */
- struct TALER_PlanchetDetail *planchet;
-
- /**
- * Denomination information, needed for the
- * step after /blinding-prepare
- */
- const struct TALER_DenominationPublicKey *denom_pub;
-
- /**
- * True, if denomination supports age restriction
- */
- bool age_denom;
-
- /**
- * The index into the array of returned values from the call to
- * /blinding-prepare that are to be used for this coin.
- */
- size_t cs_idx;
-
- } *coins;
-
- /**
- * Number of seeds requested. This may differ from @e num_prepare_coins
- * in case of a withdraw with required age proof, in which case
- * @e num_prepare_coins = TALER_CNC_KAPPA * @e num_seeds
- */
- size_t num_nonces;
-
- /**
- * Array of @e num_nonces calculated nonces.
- */
- union GNUNET_CRYPTO_BlindSessionNonce *nonces;
-
- /**
- * Handler to the originating call to /withdraw, needed to either
- * cancel the running withdraw request (on failure of the current call
- * to /blinding-prepare), or to eventually perform the protocol, once all
- * blinding-prepare requests have successfully finished.
- */
- struct TALER_EXCHANGE_WithdrawHandle *withdraw_handle;
-
-};
-
-
-/**
- * Data we keep per coin in the batch.
- * This is copied from and generated out of the input provided
- * by the client.
- */
-struct CoinData
-{
- /**
- * The denomination of the coin.
- */
- struct TALER_EXCHANGE_DenomPublicKey denom_pub;
-
- /**
- * The Candidates for the coin. If the batch is not age-restricted,
- * only index 0 is used.
- */
- struct CoinCandidate candidates[TALER_CNC_KAPPA];
-
- /**
- * Details of the planchet(s). If the batch is not age-restricted,
- * only index 0 is used.
- */
- struct TALER_PlanchetDetail planchet_details[TALER_CNC_KAPPA];
-};
-
-
-/**
- * A /withdraw request-handle for calls with pre-blinded planchets.
- * Returned by TALER_EXCHANGE_withdraw_blinded.
- */
-struct TALER_EXCHANGE_WithdrawBlindedHandle
-{
-
- /**
- * 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 hash of all the planchets
- */
- struct TALER_HashBlindedPlanchetsP planchets_h;
-
- /**
- * Seed used for the derival of blinding factors for denominations
- * with Clause-Schnorr cipher. We derive this from the master seed
- * for the withdraw, but independent from the other planchet seeds.
- */
- const struct TALER_BlindingMasterSeedP *blinding_seed;
-
- /**
- * Total amount requested (without fee).
- */
- struct TALER_Amount amount;
-
- /**
- * Total withdraw fee
- */
- struct TALER_Amount fee;
-
- /**
- * Is this call for age-restriced coins, with age proof?
- */
- bool with_age_proof;
-
- /**
- * If @e with_age_proof is true or @max_age is > 0,
- * the age mask to use, extracted from the denominations.
- * MUST be the same for all denominations.
- */
- struct TALER_AgeMask age_mask;
-
- /**
- * The maximum age to commit to. If @e with_age_proof
- * is true, the client will need to proof the correct setting
- * of age-restriction on the coins via an additional call
- * to /reveal-withdraw.
- */
- uint8_t max_age;
-
- /**
- * If @e with_age_proof is true, the hash of all the selected planchets
- */
- struct TALER_HashBlindedPlanchetsP selected_h;
-
- /**
- * Length of the either the @e blinded.input or
- * the @e blinded.with_age_proof_input array,
- * depending on @e with_age_proof.
- */
- size_t num_input;
-
- union
- {
- /**
- * The blinded planchet input candidates for age-restricted coins
- * for the call to /withdraw
- */
- const struct
- TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput *with_age_proof_input;
-
- /**
- * The blinded planchet input for the call to /withdraw via
- * TALER_EXCHANGE_withdraw_blinded, for age-unrestricted coins.
- */
- const struct TALER_EXCHANGE_WithdrawBlindedCoinInput *input;
-
- } blinded;
-
- /**
- * 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 withdraw response results.
- */
- TALER_EXCHANGE_WithdrawBlindedCallback callback;
-
- /**
- * Closure for @e blinded_callback
- */
- void *callback_cls;
-};
-
-/**
- * A /withdraw request-handle for calls from
- * a wallet, i. e. when blinding data is available.
- */
-struct TALER_EXCHANGE_WithdrawHandle
-{
-
- /**
- * The base-URL of the exchange.
- */
- const char *exchange_url;
-
- /**
- * Seed to derive of all seeds for the coins.
- */
- struct TALER_WithdrawMasterSeedP seed;
-
- /**
- * If @e with_age_proof is true, the derived TALER_CNC_KAPPA many
- * seeds for candidate batches.
- */
- struct TALER_KappaWithdrawMasterSeedP kappa_seed;
-
- /**
- * True if @e blinding_seed is filled, that is, if
- * any of the denominations is of cipher type CS
- */
- bool has_blinding_seed;
-
- /**
- * Seed used for the derivation of blinding factors for denominations
- * with Clause-Schnorr cipher. We derive this from the master seed
- * for the withdraw, but independent from the other planchet seeds.
- * Only valid when @e has_blinding_seed is true;
- */
- struct TALER_BlindingMasterSeedP blinding_seed;
-
- /**
- * 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;
-
- /**
- * True, if the withdraw is for age-restricted coins, with age-proof.
- * The denominations MUST support age restriction.
- */
- bool with_age_proof;
-
- /**
- * If @e with_age_proof is true, the age mask, extracted
- * from the denominations.
- * MUST be the same for all denominations.
- *
- */
- struct TALER_AgeMask age_mask;
-
- /**
- * The maximum age to commit to. If @e with_age_proof
- * is true, the client will need to proof the correct setting
- * of age-restriction on the coins via an additional call
- * to /reveal-withdraw.
- */
- uint8_t max_age;
-
- /**
- * Length of the @e coin_data Array
- */
- size_t num_coins;
-
- /**
- * Array of per-coin data
- */
- struct CoinData *coin_data;
-
- /**
- * Context for curl.
- */
- struct GNUNET_CURL_Context *curl_ctx;
-
- /**
- * Function to call with withdraw response results.
- */
- TALER_EXCHANGE_WithdrawCallback callback;
-
- /**
- * Closure for @e callback
- */
- void *callback_cls;
-
- /* The handler for the call to /blinding-prepare, needed for CS denominations */
- struct TALER_EXCHANGE_BlindingPrepareHandle *blinding_prepare_handle;
-
- /* The Handler for the actual call to the exchange */
- struct TALER_EXCHANGE_WithdrawBlindedHandle *withdraw_blinded_handle;
-};
-
-
-/**
- * We got a 200 OK response for the /withdraw operation.
- * Extract the signatures and return them to the caller.
- *
- * @param wbh operation handle
- * @param j_response reply from the exchange
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static enum GNUNET_GenericReturnValue
-withdraw_blinded_ok (
- struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh,
- const json_t *j_response)
-{
- struct TALER_EXCHANGE_WithdrawBlindedResponse 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 (wbh->num_input != json_array_size (j_sigs))
- {
- /* Number of coins generated does not match our expectation */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- {
- struct TALER_BlindedDenominationSignature denoms_sig[wbh->num_input];
-
- memset (denoms_sig,
- 0,
- sizeof(denoms_sig));
-
- /* Reconstruct the coins and unblind the signatures */
- {
- json_t *j_sig;
- size_t i;
-
- json_array_foreach (j_sigs, i, j_sig)
- {
- struct GNUNET_JSON_Specification ispec[] = {
- TALER_JSON_spec_blinded_denom_sig (NULL,
- &denoms_sig[i]),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j_sig,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- }
-
- response.details.ok.num_sigs = wbh->num_input;
- response.details.ok.blinded_denom_sigs = denoms_sig;
- response.details.ok.planchets_h = wbh->planchets_h;
- wbh->callback (
- wbh->callback_cls,
- &response);
- /* Make sure the callback isn't called again */
- wbh->callback = NULL;
- /* Free resources */
- for (size_t i = 0; i < wbh->num_input; i++)
- TALER_blinded_denom_sig_free (&denoms_sig[i]);
- }
-
- return GNUNET_OK;
-}
-
-
-/**
- * We got a 201 CREATED response for the /withdraw operation.
- * Extract the noreveal_index and return it to the caller.
- *
- * @param wbh operation handle
- * @param j_response reply from the exchange
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static enum GNUNET_GenericReturnValue
-withdraw_blinded_created (
- struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh,
- const json_t *j_response)
-{
- struct TALER_EXCHANGE_WithdrawBlindedResponse response = {
- .hr.reply = j_response,
- .hr.http_status = MHD_HTTP_CREATED,
- .details.created.planchets_h = wbh->planchets_h,
- .details.created.num_coins = wbh->num_input,
- };
- struct TALER_ExchangeSignatureP exchange_sig;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint8 ("noreveal_index",
- &response.details.created.noreveal_index),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &response.details.created.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_withdraw_age_confirmation_verify (
- &wbh->planchets_h,
- response.details.created.noreveal_index,
- &response.details.created.exchange_pub,
- &exchange_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
-
- }
-
- wbh->callback (wbh->callback_cls,
- &response);
- /* make sure the callback isn't called again */
- wbh->callback = NULL;
-
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /withdraw request.
- *
- * @param cls the `struct TALER_EXCHANGE_WithdrawBlindedHandle`
- * @param response_code The HTTP response code
- * @param response response data
- */
-static void
-handle_withdraw_blinded_finished (
- void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh = cls;
- const json_t *j_response = response;
- struct TALER_EXCHANGE_WithdrawBlindedResponse wbr = {
- .hr.reply = j_response,
- .hr.http_status = (unsigned int) response_code
- };
-
- wbh->job = NULL;
- switch (response_code)
- {
- case 0:
- wbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- if (GNUNET_OK !=
- withdraw_blinded_ok (
- wbh,
- j_response))
- {
- GNUNET_break_op (0);
- wbr.hr.http_status = 0;
- wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- GNUNET_assert (NULL == wbh->callback);
- TALER_EXCHANGE_withdraw_blinded_cancel (wbh);
- return;
- }
- case MHD_HTTP_CREATED:
- if (GNUNET_OK !=
- withdraw_blinded_created (
- wbh,
- j_response))
- {
- GNUNET_break_op (0);
- wbr.hr.http_status = 0;
- wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- GNUNET_assert (NULL == wbh->callback);
- TALER_EXCHANGE_withdraw_blinded_cancel (wbh);
- 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 */
- wbr.hr.ec = TALER_JSON_get_error_code (j_response);
- wbr.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 */
- wbr.hr.ec = TALER_JSON_get_error_code (j_response);
- wbr.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. */
- wbr.hr.ec = TALER_JSON_get_error_code (j_response);
- wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
- break;
- case MHD_HTTP_CONFLICT:
- /* The age requirements might not have been met */
- wbr.hr.ec = TALER_JSON_get_error_code (j_response);
- wbr.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 */
- wbr.hr.ec = TALER_JSON_get_error_code (j_response);
- wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
- break;
- case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
- /* only validate reply is well-formed */
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto (
- "h_payto",
- &wbr.details.unavailable_for_legal_reasons.h_payto),
- GNUNET_JSON_spec_uint64 (
- "requirement_row",
- &wbr.details.unavailable_for_legal_reasons.requirement_row),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j_response,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- wbr.hr.http_status = 0;
- wbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- break;
- }
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- wbr.hr.ec = TALER_JSON_get_error_code (j_response);
- wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- wbr.hr.ec = TALER_JSON_get_error_code (j_response);
- wbr.hr.hint = TALER_JSON_get_error_hint (j_response);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange withdraw\n",
- (unsigned int) response_code,
- (int) wbr.hr.ec);
- break;
- }
- wbh->callback (wbh->callback_cls,
- &wbr);
- TALER_EXCHANGE_withdraw_blinded_cancel (wbh);
-}
-
-
-/**
- * Runs the actual withdraw operation with the blinded planchets.
- *
- * @param[in,out] wbh withdraw blinded handle
- */
-static void
-perform_withdraw_protocol (
- struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh)
-{
-#define FAIL_IF(cond) \
- do { \
- if ((cond)) \
- { \
- GNUNET_break (! (cond)); \
- goto ERROR; \
- } \
- } while (0)
-
- json_t *j_denoms = NULL;
- json_t *j_planchets = NULL;
- json_t *j_request_body = NULL;
- CURL *curlh = NULL;
- struct GNUNET_HashContext *coins_hctx = NULL;
- struct TALER_BlindedCoinHashP bch;
-
- GNUNET_assert (0 < wbh->num_input);
-
- FAIL_IF (GNUNET_OK !=
- TALER_amount_set_zero (wbh->keys->currency,
- &wbh->amount));
- FAIL_IF (GNUNET_OK !=
- TALER_amount_set_zero (wbh->keys->currency,
- &wbh->fee));
-
- /* Accumulate total value with fees */
- for (size_t i = 0; i < wbh->num_input; i++)
- {
- const struct TALER_EXCHANGE_DenomPublicKey *dpub =
- wbh->with_age_proof ?
- wbh->blinded.with_age_proof_input[i].denom_pub :
- wbh->blinded.input[i].denom_pub;
-
- FAIL_IF (0 >
- TALER_amount_add (&wbh->amount,
- &wbh->amount,
- &dpub->value));
- FAIL_IF (0 >
- TALER_amount_add (&wbh->fee,
- &wbh->fee,
- &dpub->fees.withdraw));
-
- if (GNUNET_CRYPTO_BSA_CS ==
- dpub->key.bsign_pub_key->cipher)
- GNUNET_assert (NULL != wbh->blinding_seed);
-
- }
-
- if (wbh->with_age_proof || wbh->max_age > 0)
- {
- wbh->age_mask =
- wbh->blinded.with_age_proof_input[0].denom_pub->key.age_mask;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Attempting to withdraw from reserve %s with maximum age %d to proof\n",
- TALER_B2S (&wbh->reserve_pub),
- wbh->max_age);
- }
- else
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Attempting to withdraw from reserve %s\n",
- TALER_B2S (&wbh->reserve_pub));
- }
-
- coins_hctx = GNUNET_CRYPTO_hash_context_start ();
- FAIL_IF (NULL == coins_hctx);
-
- j_denoms = json_array ();
- j_planchets = json_array ();
- FAIL_IF ((NULL == j_denoms) ||
- (NULL == j_planchets));
-
- for (size_t i = 0; i< wbh->num_input; i++)
- {
- /* Build the denomination array */
- const struct TALER_EXCHANGE_DenomPublicKey *denom_pub =
- wbh->with_age_proof ?
- wbh->blinded.with_age_proof_input[i].denom_pub :
- wbh->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 (wbh->with_age_proof &&
- (wbh->age_mask.bits != denom_pub->key.age_mask.bits));
-
- jdenom = GNUNET_JSON_from_data_auto (denom_h);
- FAIL_IF (NULL == jdenom);
- FAIL_IF (0 > json_array_append_new (j_denoms,
- jdenom));
- }
-
-
- /* Build the planchet array and calculate the hash over all planchets. */
- if (! wbh->with_age_proof)
- {
- for (size_t i = 0; i< wbh->num_input; i++)
- {
- const struct TALER_PlanchetDetail *planchet =
- &wbh->blinded.input[i].planchet_details;
- 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_planchets,
- jc));
-
- TALER_coin_ev_hash (&planchet->blinded_planchet,
- &planchet->denom_pub_hash,
- &bch);
-
- GNUNET_CRYPTO_hash_context_read (coins_hctx,
- &bch,
- sizeof(bch));
- }
- }
- else
- { /* Age restricted case with required age-proof. */
-
- /**
- * We collect the run of all coin candidates for the same γ index
- * first, then γ+1 etc.
- */
- for (size_t k = 0; k < TALER_CNC_KAPPA; k++)
- {
- struct GNUNET_HashContext *batch_ctx;
- struct TALER_BlindedCoinHashP batch_h;
-
- batch_ctx = GNUNET_CRYPTO_hash_context_start ();
- FAIL_IF (NULL == batch_ctx);
-
- for (size_t i = 0; i< wbh->num_input; i++)
- {
- const struct TALER_PlanchetDetail *planchet =
- &wbh->blinded.with_age_proof_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_planchets,
- jc));
-
- TALER_coin_ev_hash (
- &planchet->blinded_planchet,
- &planchet->denom_pub_hash,
- &bch);
-
- GNUNET_CRYPTO_hash_context_read (
- batch_ctx,
- &bch,
- sizeof(bch));
- }
-
- GNUNET_CRYPTO_hash_context_finish (
- batch_ctx,
- &batch_h.hash);
- GNUNET_CRYPTO_hash_context_read (
- coins_hctx,
- &batch_h,
- sizeof(batch_h));
- }
- }
-
- /* Build the hash of the planchets */
- GNUNET_CRYPTO_hash_context_finish (
- coins_hctx,
- &wbh->planchets_h.hash);
- coins_hctx = NULL;
-
- /* Sign the request */
- TALER_wallet_withdraw_sign (
- &wbh->amount,
- &wbh->fee,
- &wbh->planchets_h,
- wbh->blinding_seed,
- wbh->with_age_proof ? &wbh->age_mask: NULL,
- wbh->with_age_proof ? wbh->max_age : 0,
- wbh->reserve_priv,
- &wbh->reserve_sig);
-
- /* Initiate the POST-request */
- j_request_body = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("cipher",
- "ED25519"),
- GNUNET_JSON_pack_data_auto ("reserve_pub",
- &wbh->reserve_pub),
- GNUNET_JSON_pack_array_steal ("denoms_h",
- j_denoms),
- GNUNET_JSON_pack_array_steal ("coin_evs",
- j_planchets),
- GNUNET_JSON_pack_allow_null (
- wbh->with_age_proof
- ? GNUNET_JSON_pack_int64 ("max_age",
- wbh->max_age)
- : GNUNET_JSON_pack_string ("max_age",
- NULL) ),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &wbh->reserve_sig));
- FAIL_IF (NULL == j_request_body);
-
- if (NULL != wbh->blinding_seed)
- {
- json_t *j_seed = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("blinding_seed",
- wbh->blinding_seed));
- GNUNET_assert (NULL != j_seed);
- GNUNET_assert (0 ==
- json_object_update_new (
- j_request_body,
- j_seed));
- }
-
- curlh = TALER_EXCHANGE_curl_easy_get_ (wbh->request_url);
- FAIL_IF (NULL == curlh);
- FAIL_IF (GNUNET_OK !=
- TALER_curl_easy_post (
- &wbh->post_ctx,
- curlh,
- j_request_body));
- json_decref (j_request_body);
- j_request_body = NULL;
-
- wbh->job = GNUNET_CURL_job_add2 (
- wbh->curl_ctx,
- curlh,
- wbh->post_ctx.headers,
- &handle_withdraw_blinded_finished,
- wbh);
- FAIL_IF (NULL == wbh->job);
-
- /* No errors, return */
- return;
-
-ERROR:
- if (NULL != coins_hctx)
- GNUNET_CRYPTO_hash_context_abort (coins_hctx);
- if (NULL != j_denoms)
- json_decref (j_denoms);
- if (NULL != j_planchets)
- json_decref (j_planchets);
- if (NULL != j_request_body)
- json_decref (j_request_body);
- if (NULL != curlh)
- curl_easy_cleanup (curlh);
- TALER_EXCHANGE_withdraw_blinded_cancel (wbh);
- return;
-#undef FAIL_IF
-}
-
-
-/**
- * @brief Callback to copy the results from the call to TALER_withdraw_blinded
- * in the non-age-restricted case to the result for the originating call from TALER_withdraw.
- *
- * @param cls struct TALER_WithdrawHandle
- * @param wbr The response
- */
-static void
-copy_results (
- void *cls,
- const struct TALER_EXCHANGE_WithdrawBlindedResponse *wbr)
-{
- /* The original handle from the top-level call to withdraw */
- struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
- struct TALER_EXCHANGE_WithdrawResponse resp = {
- .hr = wbr->hr,
- };
-
- wh->withdraw_blinded_handle = NULL;
-
- /**
- * The withdraw protocol has been performed with blinded data.
- * Now the response can be copied as is, except for the MHD_HTTP_OK case,
- * in which we now need to perform the unblinding.
- */
- switch (wbr->hr.http_status)
- {
- case MHD_HTTP_OK:
- {
- struct TALER_EXCHANGE_WithdrawCoinPrivateDetails
- details[GNUNET_NZL (wh->num_coins)];
- bool ok = true;
-
- GNUNET_assert (wh->num_coins == wbr->details.ok.num_sigs);
- memset (details,
- 0,
- sizeof(details));
- resp.details.ok.num_sigs = wbr->details.ok.num_sigs;
- resp.details.ok.coin_details = details;
- resp.details.ok.planchets_h = wbr->details.ok.planchets_h;
- for (size_t n = 0; n<wh->num_coins; n++)
- {
- const struct TALER_BlindedDenominationSignature *bsig =
- &wbr->details.ok.blinded_denom_sigs[n];
- struct CoinData *cd = &wh->coin_data[n];
- struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *coin = &details[n];
- struct TALER_FreshCoin fresh_coin;
-
- *coin = wh->coin_data[n].candidates[0].details;
- coin->planchet = wh->coin_data[n].planchet_details[0];
- GNUNET_CRYPTO_eddsa_key_get_public (
- &coin->coin_priv.eddsa_priv,
- &coin->coin_pub.eddsa_pub);
-
- if (GNUNET_OK !=
- TALER_planchet_to_coin (&cd->denom_pub.key,
- bsig,
- &coin->blinding_key,
- &coin->coin_priv,
- &coin->h_age_commitment,
- &coin->h_coin_pub,
- &coin->blinding_values,
- &fresh_coin))
- {
- resp.hr.http_status = 0;
- resp.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE;
- GNUNET_break_op (0);
- ok = false;
- break;
- }
- coin->denom_sig = fresh_coin.sig;
- }
- if (ok)
- {
- wh->callback (
- wh->callback_cls,
- &resp);
- wh->callback = NULL;
- }
- for (size_t n = 0; n<wh->num_coins; n++)
- {
- struct TALER_EXCHANGE_WithdrawCoinPrivateDetails *coin = &details[n];
-
- TALER_denom_sig_free (&coin->denom_sig);
- }
- break;
- }
- case MHD_HTTP_CREATED:
- resp.details.created = wbr->details.created;
- break;
-
- case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
- resp.details.unavailable_for_legal_reasons =
- wbr->details.unavailable_for_legal_reasons;
- break;
-
- default:
- /* nothing to do here, .hr.ec and .hr.hint are all set already from previous response */
- break;
- }
- if (NULL != wh->callback)
- {
- wh->callback (
- wh->callback_cls,
- &resp);
- wh->callback = NULL;
- }
- TALER_EXCHANGE_withdraw_cancel (wh);
-}
-
-
-/**
- * @brief Callback to copy the results from the call to TALER_withdraw_blinded
- * in the age-restricted case to the result for the originating call from TALER_withdraw.
- *
- * @param cls struct TALER_WithdrawHandle
- * @param wbr The response
- */
-static void
-copy_results_with_age_proof (
- void *cls,
- const struct TALER_EXCHANGE_WithdrawBlindedResponse *wbr)
-{
- /* The original handle from the top-level call to withdraw */
- struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
- uint8_t k = wbr->details.created.noreveal_index;
- struct TALER_EXCHANGE_WithdrawCoinPrivateDetails details[wh->num_coins];
- struct TALER_EXCHANGE_WithdrawResponse resp = {
- .hr = wbr->hr,
- };
-
- wh->withdraw_blinded_handle = NULL;
-
- switch (wbr->hr.http_status)
- {
- case MHD_HTTP_OK:
- /* in the age-restricted case, this should not happen */
- GNUNET_break_op (0);
- break;
-
- case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
- resp.details.unavailable_for_legal_reasons =
- wbr->details.unavailable_for_legal_reasons;
- break;
-
- case MHD_HTTP_CREATED:
- {
- GNUNET_assert (wh->num_coins == wbr->details.created.num_coins);
- resp.details.created = wbr->details.created;
- resp.details.created.coin_details = details;
- resp.details.created.kappa_seed = wh->kappa_seed;
- memset (details,
- 0,
- sizeof(details));
- for (size_t n = 0; n< wh->num_coins; n++)
- {
- details[n] = wh->coin_data[n].candidates[k].details;
- details[n].planchet = wh->coin_data[n].planchet_details[k];
- }
- break;
- }
-
- default:
- break;
- }
-
- wh->callback (
- wh->callback_cls,
- &resp);
- wh->callback = NULL;
- TALER_EXCHANGE_withdraw_cancel (wh);
-}
-
-
-/**
- * @brief Prepares and executes TALER_EXCHANGE_withdraw_blinded.
- * If there were CS-denominations involved, started once the all calls
- * to /blinding-prepare are done.
- */
-static void
-call_withdraw_blinded (
- struct TALER_EXCHANGE_WithdrawHandle *wh)
-{
-
- GNUNET_assert (NULL == wh->blinding_prepare_handle);
-
- if (! wh->with_age_proof)
- {
- struct TALER_EXCHANGE_WithdrawBlindedCoinInput input[wh->num_coins];
-
- memset (input,
- 0,
- sizeof(input));
-
- /* Prepare the blinded planchets as input */
- for (size_t n = 0; n < wh->num_coins; n++)
- {
- input[n].denom_pub =
- &wh->coin_data[n].denom_pub;
- input[n].planchet_details =
- *wh->coin_data[n].planchet_details;
- }
-
- wh->withdraw_blinded_handle =
- TALER_EXCHANGE_withdraw_blinded (
- wh->curl_ctx,
- wh->keys,
- wh->exchange_url,
- wh->reserve_priv,
- wh->has_blinding_seed ? &wh->blinding_seed : NULL,
- wh->num_coins,
- input,
- ©_results,
- wh);
- }
- else
- { /* age restricted case */
- struct TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput
- ari[wh->num_coins];
-
- memset (ari,
- 0,
- sizeof(ari));
-
- /* Prepare the blinded planchets as input */
- for (size_t n = 0; n < wh->num_coins; n++)
- {
- ari[n].denom_pub = &wh->coin_data[n].denom_pub;
- for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
- ari[n].planchet_details[k] =
- wh->coin_data[n].planchet_details[k];
- }
-
- wh->withdraw_blinded_handle =
- TALER_EXCHANGE_withdraw_blinded_with_age_proof (
- wh->curl_ctx,
- wh->keys,
- wh->exchange_url,
- wh->reserve_priv,
- wh->has_blinding_seed ? &wh->blinding_seed : NULL,
- wh->max_age,
- wh->num_coins,
- ari,
- ©_results_with_age_proof,
- wh);
- }
-}
-
-
-/**
- * @brief Function called when /blinding-prepare is finished
- *
- * @param cls the `struct BlindingPrepareClosure *`
- * @param bpr replies from the /blinding-prepare request
- */
-static void
-blinding_prepare_done (
- void *cls,
- const struct TALER_EXCHANGE_BlindingPrepareResponse *bpr)
-{
- struct BlindingPrepareClosure *bpcls = cls;
- struct TALER_EXCHANGE_WithdrawHandle *wh = bpcls->withdraw_handle;
-
- wh->blinding_prepare_handle = NULL;
- switch (bpr->hr.http_status)
- {
- case MHD_HTTP_OK:
- {
- bool success = false;
- size_t num = bpr->details.ok.num_blinding_values;
-
- GNUNET_assert (0 != num);
- GNUNET_assert (num == bpcls->num_nonces);
- for (size_t i = 0; i < bpcls->num_prepare_coins; i++)
- {
- struct TALER_PlanchetDetail *planchet = bpcls->coins[i].planchet;
- struct CoinCandidate *can = bpcls->coins[i].candidate;
- size_t cs_idx = bpcls->coins[i].cs_idx;
-
- GNUNET_assert (NULL != can);
- GNUNET_assert (NULL != planchet);
- success = false;
-
- /* Complete the initialization of the coin with CS denomination */
- TALER_denom_ewv_copy (
- &can->details.blinding_values,
- &bpr->details.ok.blinding_values[cs_idx]);
-
- GNUNET_assert (GNUNET_CRYPTO_BSA_CS ==
- can->details.blinding_values.blinding_inputs->cipher);
-
- TALER_planchet_setup_coin_priv (
- &can->details.secret,
- &can->details.blinding_values,
- &can->details.coin_priv);
-
- TALER_planchet_blinding_secret_create (
- &can->details.secret,
- &can->details.blinding_values,
- &can->details.blinding_key);
-
- /* This initializes the 2nd half of the
- can->planchet_detail.blinded_planchet */
- if (GNUNET_OK !=
- TALER_planchet_prepare (
- bpcls->coins[i].denom_pub,
- &can->details.blinding_values,
- &can->details.blinding_key,
- &bpcls->nonces[cs_idx],
- &can->details.coin_priv,
- &can->details.h_age_commitment,
- &can->details.h_coin_pub,
- planchet))
- {
- GNUNET_break (0);
- break;
- }
-
- TALER_coin_ev_hash (&planchet->blinded_planchet,
- &planchet->denom_pub_hash,
- &can->blinded_coin_h);
- success = true;
- }
-
- /* /blinding-prepare is done, we can now perform the
- * actual withdraw operation */
- if (success)
- call_withdraw_blinded (wh);
- goto cleanup;
- }
- default:
- {
- /* We got an error condition during blinding prepare that we need to report */
- struct TALER_EXCHANGE_WithdrawResponse resp = {
- .hr = bpr->hr
- };
-
- wh->callback (
- wh->callback_cls,
- &resp);
-
- wh->callback = NULL;
- break;
- }
- }
- TALER_EXCHANGE_withdraw_cancel (wh);
-cleanup:
- GNUNET_free (bpcls->coins);
- GNUNET_free (bpcls->nonces);
- GNUNET_free (bpcls);
-}
-
-
-/**
- * @brief Prepares non age-restricted coins for the call to withdraw and
- * calculates the total amount with fees.
- * For denomination with CS as cipher, initiates the preflight to retrieve the
- * bpcls-parameter via /blinding-prepare.
- * Note that only one of the three parameters seed, tuples or secrets must not be NULL
- *
- * @param wh The handler to the withdraw
- * @param num_coins Number of coins to withdraw
- * @param max_age The maximum age to commit to
- * @param denoms_pub Array @e num_coins of denominations
- * @param seed master seed from which to derive @e num_coins secrets and blinding, if @e blinding_seed is NULL
- * @param blinding_seed master seed for the blinding. Might be NULL, in which case the blinding_seed is derived from @e seed
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
- */
-static enum GNUNET_GenericReturnValue
-prepare_coins (
- struct TALER_EXCHANGE_WithdrawHandle *wh,
- size_t num_coins,
- uint8_t max_age,
- const struct TALER_EXCHANGE_DenomPublicKey *denoms_pub,
- const struct TALER_WithdrawMasterSeedP *seed,
- const struct TALER_BlindingMasterSeedP *blinding_seed)
-{
- size_t cs_num = 0;
- struct BlindingPrepareClosure *cs_closure;
- uint8_t kappa;
-
-#define FAIL_IF(cond) \
- do \
- { \
- if ((cond)) \
- { \
- GNUNET_break (! (cond)); \
- goto ERROR; \
- } \
- } while (0)
-
- GNUNET_assert (0 < num_coins);
-
- wh->num_coins = num_coins;
- wh->max_age = max_age;
- wh->age_mask = denoms_pub[0].key.age_mask;
- wh->coin_data = GNUNET_new_array (
- wh->num_coins,
- struct CoinData);
-
- /* First, figure out how many Clause-Schnorr denominations we have */
- for (size_t i =0; i< wh->num_coins; i++)
- {
- if (GNUNET_CRYPTO_BSA_CS ==
- denoms_pub[i].key.bsign_pub_key->cipher)
- cs_num++;
- }
-
- if (wh->with_age_proof)
- {
- kappa = TALER_CNC_KAPPA;
- }
- else
- {
- kappa = 1;
- }
-
- {
- struct TALER_PlanchetMasterSecretP secrets[kappa][num_coins];
- struct TALER_EXCHANGE_NonceKey cs_nonce_keys[GNUNET_NZL (cs_num)];
- uint32_t cs_indices[GNUNET_NZL (cs_num)];
-
- size_t cs_denom_idx = 0;
- size_t cs_coin_idx = 0;
-
- if (wh->with_age_proof)
- {
- TALER_withdraw_expand_kappa_seed (seed,
- &wh->kappa_seed);
- for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
- {
- TALER_withdraw_expand_secrets (
- num_coins,
- &wh->kappa_seed.tuple[k],
- secrets[k]);
- }
- }
- else
- {
- TALER_withdraw_expand_secrets (
- num_coins,
- seed,
- secrets[0]);
- }
-
- if (0 < cs_num)
- {
- memset (cs_nonce_keys,
- 0,
- sizeof(cs_nonce_keys));
- cs_closure = GNUNET_new (struct BlindingPrepareClosure);
- cs_closure->withdraw_handle = wh;
- cs_closure->num_prepare_coins = cs_num * kappa;
- GNUNET_assert ((1 == kappa) || (cs_num * kappa > cs_num));
- cs_closure->coins =
- GNUNET_new_array (cs_closure->num_prepare_coins,
- struct BlindingPrepareCoinData);
- cs_closure->num_nonces = cs_num;
- cs_closure->nonces =
- GNUNET_new_array (cs_closure->num_nonces,
- union GNUNET_CRYPTO_BlindSessionNonce);
- }
- else
- {
- cs_closure = NULL;
- }
-
- for (uint32_t i = 0; i < wh->num_coins; i++)
- {
- struct CoinData *cd = &wh->coin_data[i];
- bool age_denom = (0 != denoms_pub[i].key.age_mask.bits);
-
- cd->denom_pub = denoms_pub[i];
- /* The age mask must be the same for all coins */
- FAIL_IF (wh->with_age_proof &&
- (0 == denoms_pub[i].key.age_mask.bits));
- FAIL_IF (wh->age_mask.bits !=
- denoms_pub[i].key.age_mask.bits);
- TALER_denom_pub_copy (&cd->denom_pub.key,
- &denoms_pub[i].key);
-
- /* Mark the indices of the coins which are of type Clause-Schnorr
- * and add their denomination public key hash to the list.
- */
- if (GNUNET_CRYPTO_BSA_CS ==
- cd->denom_pub.key.bsign_pub_key->cipher)
- {
- GNUNET_assert (cs_denom_idx<cs_num);
- cs_indices[cs_denom_idx] = i;
- cs_nonce_keys[cs_denom_idx].cnc_num = i;
- cs_nonce_keys[cs_denom_idx].pk = &cd->denom_pub;
- cs_denom_idx++;
- }
-
- /*
- * Note that we "loop" here either only once (if with_age_proof is false),
- * or TALER_CNC_KAPPA times.
- */
- for (uint8_t k = 0; k < kappa; k++)
- {
- struct CoinCandidate *can = &cd->candidates[k];
- struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
-
- can->details.secret = secrets[k][i];
- /*
- * The age restriction needs to be set on a coin if the denomination
- * support age restriction. Note that his is regardless of weither
- * with_age_proof is set or not.
- */
- if (age_denom)
- {
- /* Derive the age restriction from the given secret and
- * the maximum age */
- TALER_age_restriction_from_secret (
- &can->details.secret,
- &wh->age_mask,
- wh->max_age,
- &can->details.age_commitment_proof);
-
- TALER_age_commitment_hash (
- &can->details.age_commitment_proof.commitment,
- &can->details.h_age_commitment);
- }
-
- switch (cd->denom_pub.key.bsign_pub_key->cipher)
- {
- case GNUNET_CRYPTO_BSA_RSA:
- TALER_denom_ewv_copy (&can->details.blinding_values,
- TALER_denom_ewv_rsa_singleton ());
- TALER_planchet_setup_coin_priv (&can->details.secret,
- &can->details.blinding_values,
- &can->details.coin_priv);
- TALER_planchet_blinding_secret_create (&can->details.secret,
- &can->details.blinding_values,
- &can->details.blinding_key);
- FAIL_IF (GNUNET_OK !=
- TALER_planchet_prepare (&cd->denom_pub.key,
- &can->details.blinding_values,
- &can->details.blinding_key,
- NULL,
- &can->details.coin_priv,
- (age_denom)
- ? &can->details.h_age_commitment
- : NULL,
- &can->details.h_coin_pub,
- planchet));
- TALER_coin_ev_hash (&planchet->blinded_planchet,
- &planchet->denom_pub_hash,
- &can->blinded_coin_h);
-
- break;
-
- case GNUNET_CRYPTO_BSA_CS:
- {
- /**
- * Prepare the nonce and save the index and the denomination for the callback
- * after the call to blinding-prepare
- */
- cs_closure->coins[cs_coin_idx].candidate = can;
- cs_closure->coins[cs_coin_idx].planchet = planchet;
- cs_closure->coins[cs_coin_idx].denom_pub = &cd->denom_pub.key;
- cs_closure->coins[cs_coin_idx].cs_idx = i;
- cs_closure->coins[cs_coin_idx].age_denom = age_denom;
- cs_coin_idx++;
- break;
- }
- default:
- FAIL_IF (1);
- }
- }
- }
-
- if (0 < cs_num)
- {
- if (NULL != blinding_seed)
- {
- wh->blinding_seed = *blinding_seed;
- }
- else
- {
- TALER_cs_withdraw_seed_to_blinding_seed (
- seed,
- &wh->blinding_seed);
- }
- wh->has_blinding_seed = true;
-
- TALER_cs_derive_only_cs_blind_nonces_from_seed (
- &wh->blinding_seed,
- false, /* not for melt */
- cs_num,
- cs_indices,
- cs_closure->nonces);
-
- wh->blinding_prepare_handle =
- TALER_EXCHANGE_blinding_prepare_for_withdraw (
- wh->curl_ctx,
- wh->exchange_url,
- &wh->blinding_seed,
- cs_num,
- cs_nonce_keys,
- &blinding_prepare_done,
- cs_closure);
- FAIL_IF (NULL == wh->blinding_prepare_handle);
- }
- }
- return GNUNET_OK;
-
-ERROR:
- if (0<cs_num)
- {
- GNUNET_free (cs_closure->nonces);
- GNUNET_free (cs_closure);
- }
- TALER_EXCHANGE_withdraw_cancel (wh);
- return GNUNET_SYSERR;
-#undef FAIL_IF
-
-}
-
-
-/**
- * Prepare a withdraw handle for both, the non-restricted
- * and age-restricted case.
- *
- * @param curl_ctx The curl context to use
- * @param keys The keys from the exchange
- * @param exchange_url The base url to the exchange
- * @param reserve_priv The private key of the exchange
- * @param res_cb The callback to call on response
- * @param res_cb_cls The closure to pass to the callback
- */
-static struct TALER_EXCHANGE_WithdrawHandle *
-setup_withdraw_handle (
- struct GNUNET_CURL_Context *curl_ctx,
- struct TALER_EXCHANGE_Keys *keys,
- const char *exchange_url,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- TALER_EXCHANGE_WithdrawCallback res_cb,
- void *res_cb_cls)
-{
- struct TALER_EXCHANGE_WithdrawHandle *wh;
-
- wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle);
- wh->exchange_url = exchange_url;
- wh->keys = TALER_EXCHANGE_keys_incref (keys);
- wh->curl_ctx = curl_ctx;
- wh->reserve_priv = reserve_priv;
- wh->callback = res_cb;
- wh->callback_cls = res_cb_cls;
-
- return wh;
-}
-
-
-struct TALER_EXCHANGE_WithdrawHandle *
-TALER_EXCHANGE_withdraw_extra_blinding_seed (
- 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_DenomPublicKey denoms_pub[static num_coins],
- const struct TALER_WithdrawMasterSeedP *seed,
- const struct TALER_BlindingMasterSeedP *blinding_seed,
- uint8_t opaque_max_age,
- TALER_EXCHANGE_WithdrawCallback res_cb,
- void *res_cb_cls)
-{
- struct TALER_EXCHANGE_WithdrawHandle *wh;
-
- wh = setup_withdraw_handle (curl_ctx,
- keys,
- exchange_url,
- reserve_priv,
- res_cb,
- res_cb_cls);
- GNUNET_assert (NULL != wh);
- wh->with_age_proof = false;
-
- if (GNUNET_OK !=
- prepare_coins (wh,
- num_coins,
- opaque_max_age,
- denoms_pub,
- seed,
- blinding_seed))
- {
- GNUNET_free (wh);
- return NULL;
- }
-
- /* If there were no CS denominations, we can now perform the actual
- * withdraw protocol. Otherwise, there are calls to /blinding-prepare
- * in flight and once they finish, the withdraw-protocol will be
- * called from within the blinding_prepare_done-function.
- */
- if (NULL == wh->blinding_prepare_handle)
- call_withdraw_blinded (wh);
-
- return wh;
-}
-
-
-struct TALER_EXCHANGE_WithdrawHandle *
-TALER_EXCHANGE_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_DenomPublicKey denoms_pub[static num_coins],
- const struct TALER_WithdrawMasterSeedP *seed,
- uint8_t opaque_max_age,
- TALER_EXCHANGE_WithdrawCallback res_cb,
- void *res_cb_cls)
-{
- return TALER_EXCHANGE_withdraw_extra_blinding_seed (
- curl_ctx,
- keys,
- exchange_url,
- reserve_priv,
- num_coins,
- denoms_pub,
- seed,
- NULL,
- opaque_max_age,
- res_cb,
- res_cb_cls
- );
-}
-
-
-struct TALER_EXCHANGE_WithdrawHandle *
-TALER_EXCHANGE_withdraw_with_age_proof_extra_blinding_seed (
- 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_DenomPublicKey denoms_pub[static num_coins],
- const struct TALER_WithdrawMasterSeedP *seed,
- const struct TALER_BlindingMasterSeedP *blinding_seed,
- uint8_t max_age,
- TALER_EXCHANGE_WithdrawCallback res_cb,
- void *res_cb_cls)
-{
- struct TALER_EXCHANGE_WithdrawHandle *wh;
-
- wh = setup_withdraw_handle (curl_ctx,
- keys,
- exchange_url,
- reserve_priv,
- res_cb,
- res_cb_cls);
- GNUNET_assert (NULL != wh);
-
- wh->with_age_proof = true;
-
- if (GNUNET_OK !=
- prepare_coins (wh,
- num_coins,
- max_age,
- denoms_pub,
- seed,
- blinding_seed))
- {
- GNUNET_free (wh);
- return NULL;
- }
-
- /* If there were no CS denominations, we can now perform the actual
- * withdraw protocol. Otherwise, there are calls to /blinding-prepare
- * in flight and once they finish, the withdraw-protocol will be
- * called from within the blinding_prepare_done-function.
- */
- if (NULL == wh->blinding_prepare_handle)
- call_withdraw_blinded (wh);
-
- return wh;
-}
-
-
-struct TALER_EXCHANGE_WithdrawHandle *
-TALER_EXCHANGE_withdraw_with_age_proof (
- 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_DenomPublicKey denoms_pub[static num_coins],
- const struct TALER_WithdrawMasterSeedP *seed,
- uint8_t max_age,
- TALER_EXCHANGE_WithdrawCallback res_cb,
- void *res_cb_cls)
-{
- return TALER_EXCHANGE_withdraw_with_age_proof_extra_blinding_seed (
- curl_ctx,
- keys,
- exchange_url,
- reserve_priv,
- num_coins,
- denoms_pub,
- seed,
- NULL,
- max_age,
- res_cb,
- res_cb_cls);
-}
-
-
-void
-TALER_EXCHANGE_withdraw_cancel (
- struct TALER_EXCHANGE_WithdrawHandle *wh)
-{
- uint8_t kappa = wh->with_age_proof ? TALER_CNC_KAPPA : 1;
-
- /* Cleanup coin data */
- for (unsigned int i = 0; i<wh->num_coins; i++)
- {
- struct CoinData *cd = &wh->coin_data[i];
-
- for (uint8_t k = 0; k < kappa; k++)
- {
- struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
- struct CoinCandidate *can = &cd->candidates[k];
-
- TALER_blinded_planchet_free (&planchet->blinded_planchet);
- TALER_denom_ewv_free (&can->details.blinding_values);
- TALER_age_commitment_proof_free (&can->details.age_commitment_proof);
- }
- TALER_denom_pub_free (&cd->denom_pub.key);
- }
-
- TALER_EXCHANGE_blinding_prepare_cancel (wh->blinding_prepare_handle);
- TALER_EXCHANGE_withdraw_blinded_cancel (wh->withdraw_blinded_handle);
- wh->blinding_prepare_handle = NULL;
- wh->withdraw_blinded_handle = NULL;
-
- GNUNET_free (wh->coin_data);
- TALER_EXCHANGE_keys_decref (wh->keys);
- GNUNET_free (wh);
-}
-
-
-/**
- * @brief Prepare the handler for blinded withdraw
- *
- * Allocates the handler struct and prepares all fields of the handler
- * except the blinded planchets,
- * which depend on them being age-restricted or not.
- *
- * @param curl_ctx the context for curl
- * @param keys the exchange keys
- * @param exchange_url the url to the exchange
- * @param reserve_priv the reserve's private key
- * @param res_cb the callback on result
- * @param res_cb_cls the closure to pass on to the callback
- * @return the handler
- */
-static struct TALER_EXCHANGE_WithdrawBlindedHandle *
-setup_handler_common (
- struct GNUNET_CURL_Context *curl_ctx,
- struct TALER_EXCHANGE_Keys *keys,
- const char *exchange_url,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- TALER_EXCHANGE_WithdrawBlindedCallback res_cb,
- void *res_cb_cls)
-{
-
- struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh =
- GNUNET_new (struct TALER_EXCHANGE_WithdrawBlindedHandle);
-
- wbh->keys = TALER_EXCHANGE_keys_incref (keys);
- wbh->curl_ctx = curl_ctx;
- wbh->reserve_priv = reserve_priv;
- wbh->callback = res_cb;
- wbh->callback_cls = res_cb_cls;
- wbh->request_url = TALER_url_join (exchange_url,
- "withdraw",
- NULL);
- GNUNET_CRYPTO_eddsa_key_get_public (
- &wbh->reserve_priv->eddsa_priv,
- &wbh->reserve_pub.eddsa_pub);
-
- return wbh;
-}
-
-
-struct TALER_EXCHANGE_WithdrawBlindedHandle *
-TALER_EXCHANGE_withdraw_blinded (
- struct GNUNET_CURL_Context *curl_ctx,
- struct TALER_EXCHANGE_Keys *keys,
- const char *exchange_url,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- const struct TALER_BlindingMasterSeedP *blinding_seed,
- size_t num_input,
- const struct TALER_EXCHANGE_WithdrawBlindedCoinInput
- blinded_input[static num_input],
- TALER_EXCHANGE_WithdrawBlindedCallback res_cb,
- void *res_cb_cls)
-{
- struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh =
- setup_handler_common (curl_ctx,
- keys,
- exchange_url,
- reserve_priv,
- res_cb,
- res_cb_cls);
-
- wbh->with_age_proof = false;
- wbh->num_input = num_input;
- wbh->blinded.input = blinded_input;
- wbh->blinding_seed = blinding_seed;
-
- perform_withdraw_protocol (wbh);
- return wbh;
-}
-
-
-struct TALER_EXCHANGE_WithdrawBlindedHandle *
-TALER_EXCHANGE_withdraw_blinded_with_age_proof (
- struct GNUNET_CURL_Context *curl_ctx,
- struct TALER_EXCHANGE_Keys *keys,
- const char *exchange_url,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- const struct TALER_BlindingMasterSeedP *blinding_seed,
- uint8_t max_age,
- unsigned int num_input,
- const struct TALER_EXCHANGE_WithdrawBlindedAgeRestrictedCoinInput
- blinded_input[static num_input],
- TALER_EXCHANGE_WithdrawBlindedCallback res_cb,
- void *res_cb_cls)
-{
- struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh =
- setup_handler_common (curl_ctx,
- keys,
- exchange_url,
- reserve_priv,
- res_cb,
- res_cb_cls);
-
- wbh->with_age_proof = true;
- wbh->max_age = max_age;
- wbh->num_input = num_input;
- wbh->blinded.with_age_proof_input = blinded_input;
- wbh->blinding_seed = blinding_seed;
-
- perform_withdraw_protocol (wbh);
- return wbh;
-}
-
-
-void
-TALER_EXCHANGE_withdraw_blinded_cancel (
- struct TALER_EXCHANGE_WithdrawBlindedHandle *wbh)
-{
- if (NULL == wbh)
- return;
- if (NULL != wbh->job)
- {
- GNUNET_CURL_job_cancel (wbh->job);
- wbh->job = NULL;
- }
- GNUNET_free (wbh->request_url);
- TALER_EXCHANGE_keys_decref (wbh->keys);
- TALER_curl_easy_post_finished (&wbh->post_ctx);
- GNUNET_free (wbh);
-}
-
-
-/* exchange_api_withdraw.c */