summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/.gitignore1
-rw-r--r--src/lib/Makefile.am50
-rw-r--r--src/lib/auditor_api_curl_defaults.c14
-rw-r--r--src/lib/auditor_api_deposit_confirmation.c180
-rw-r--r--src/lib/auditor_api_exchanges.c253
-rw-r--r--src/lib/auditor_api_get_config.c278
-rw-r--r--src/lib/auditor_api_handle.c449
-rw-r--r--src/lib/auditor_api_handle.h59
-rw-r--r--src/lib/exchange_api_add_aml_decision.c246
-rw-r--r--src/lib/exchange_api_age_withdraw.c1125
-rw-r--r--src/lib/exchange_api_age_withdraw_reveal.c477
-rw-r--r--src/lib/exchange_api_auditor_add_denomination.c34
-rw-r--r--src/lib/exchange_api_batch_deposit.c467
-rw-r--r--src/lib/exchange_api_batch_withdraw.c192
-rw-r--r--src/lib/exchange_api_batch_withdraw2.c272
-rw-r--r--src/lib/exchange_api_coins_history.c1230
-rw-r--r--src/lib/exchange_api_common.c1874
-rw-r--r--src/lib/exchange_api_common.h53
-rw-r--r--src/lib/exchange_api_contracts_get.c32
-rw-r--r--src/lib/exchange_api_csr_melt.c39
-rw-r--r--src/lib/exchange_api_csr_withdraw.c41
-rw-r--r--src/lib/exchange_api_curl_defaults.c15
-rw-r--r--src/lib/exchange_api_curl_defaults.h1
-rw-r--r--src/lib/exchange_api_deposit.c557
-rw-r--r--src/lib/exchange_api_deposits_get.c94
-rw-r--r--src/lib/exchange_api_handle.c2686
-rw-r--r--src/lib/exchange_api_handle.h221
-rw-r--r--src/lib/exchange_api_kyc_check.c104
-rw-r--r--src/lib/exchange_api_kyc_proof.c46
-rw-r--r--src/lib/exchange_api_kyc_wallet.c63
-rw-r--r--src/lib/exchange_api_link.c122
-rw-r--r--src/lib/exchange_api_lookup_aml_decision.c417
-rw-r--r--src/lib/exchange_api_lookup_aml_decisions.c376
-rw-r--r--src/lib/exchange_api_management_add_partner.c218
-rw-r--r--src/lib/exchange_api_management_auditor_disable.c26
-rw-r--r--src/lib/exchange_api_management_auditor_enable.c37
-rw-r--r--src/lib/exchange_api_management_drain_profits.c28
-rw-r--r--src/lib/exchange_api_management_get_keys.c131
-rw-r--r--src/lib/exchange_api_management_post_extensions.c39
-rw-r--r--src/lib/exchange_api_management_post_keys.c26
-rw-r--r--src/lib/exchange_api_management_revoke_denomination_key.c25
-rw-r--r--src/lib/exchange_api_management_revoke_signing_key.c23
-rw-r--r--src/lib/exchange_api_management_set_global_fee.c47
-rw-r--r--src/lib/exchange_api_management_set_wire_fee.c44
-rw-r--r--src/lib/exchange_api_management_update_aml_officer.c230
-rw-r--r--src/lib/exchange_api_management_wire_disable.c44
-rw-r--r--src/lib/exchange_api_management_wire_enable.c61
-rw-r--r--src/lib/exchange_api_melt.c132
-rw-r--r--src/lib/exchange_api_purse_create_with_deposit.c166
-rw-r--r--src/lib/exchange_api_purse_create_with_merge.c78
-rw-r--r--src/lib/exchange_api_purse_delete.c243
-rw-r--r--src/lib/exchange_api_purse_deposit.c164
-rw-r--r--src/lib/exchange_api_purse_merge.c67
-rw-r--r--src/lib/exchange_api_purses_get.c76
-rw-r--r--src/lib/exchange_api_recoup.c160
-rw-r--r--src/lib/exchange_api_recoup_refresh.c159
-rw-r--r--src/lib/exchange_api_refresh_common.c66
-rw-r--r--src/lib/exchange_api_refresh_common.h7
-rw-r--r--src/lib/exchange_api_refreshes_reveal.c132
-rw-r--r--src/lib/exchange_api_refund.c435
-rw-r--r--src/lib/exchange_api_reserves_attest.c (renamed from src/lib/exchange_api_reserves_status.c)198
-rw-r--r--src/lib/exchange_api_reserves_close.c373
-rw-r--r--src/lib/exchange_api_reserves_get.c49
-rw-r--r--src/lib/exchange_api_reserves_get_attestable.c276
-rw-r--r--src/lib/exchange_api_reserves_history.c946
-rw-r--r--src/lib/exchange_api_reserves_open.c567
-rw-r--r--src/lib/exchange_api_stefan.c328
-rw-r--r--src/lib/exchange_api_transfers_get.c169
-rw-r--r--src/lib/exchange_api_wire.c488
-rw-r--r--src/lib/exchange_api_withdraw.c342
-rw-r--r--src/lib/exchange_api_withdraw2.c504
-rw-r--r--src/lib/test_stefan.c206
72 files changed, 11185 insertions, 8193 deletions
diff --git a/src/lib/.gitignore b/src/lib/.gitignore
new file mode 100644
index 000000000..6664876f2
--- /dev/null
+++ b/src/lib/.gitignore
@@ -0,0 +1 @@
+test_stefan
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 6adaac387..63dab7c80 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -18,25 +18,31 @@ lib_LTLIBRARIES = \
libtalerexchange.la
libtalerexchange_la_LDFLAGS = \
- -version-info 5:0:0 \
+ -version-info 7:0:0 \
-no-undefined
libtalerexchange_la_SOURCES = \
+ exchange_api_add_aml_decision.c \
+ exchange_api_age_withdraw.c \
+ exchange_api_age_withdraw_reveal.c \
exchange_api_auditor_add_denomination.c \
exchange_api_batch_deposit.c \
exchange_api_batch_withdraw.c \
exchange_api_batch_withdraw2.c \
exchange_api_curl_defaults.c exchange_api_curl_defaults.h \
+ exchange_api_coins_history.c \
exchange_api_common.c exchange_api_common.h \
exchange_api_contracts_get.c \
exchange_api_csr_melt.c \
exchange_api_csr_withdraw.c \
exchange_api_handle.c exchange_api_handle.h \
- exchange_api_deposit.c \
exchange_api_deposits_get.c \
exchange_api_kyc_check.c \
exchange_api_kyc_proof.c \
exchange_api_kyc_wallet.c \
exchange_api_link.c \
+ exchange_api_lookup_aml_decision.c \
+ exchange_api_lookup_aml_decisions.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 \
@@ -47,11 +53,13 @@ libtalerexchange_la_SOURCES = \
exchange_api_management_revoke_signing_key.c \
exchange_api_management_set_global_fee.c \
exchange_api_management_set_wire_fee.c \
+ exchange_api_management_update_aml_officer.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 \
@@ -60,16 +68,18 @@ libtalerexchange_la_SOURCES = \
exchange_api_refresh_common.c exchange_api_refresh_common.h \
exchange_api_refreshes_reveal.c \
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_status.c \
- exchange_api_transfers_get.c \
- exchange_api_withdraw.c \
- exchange_api_withdraw2.c \
- exchange_api_wire.c
+ exchange_api_reserves_open.c \
+ exchange_api_stefan.c \
+ exchange_api_transfers_get.c
libtalerexchange_la_LIBADD = \
libtalerauditor.la \
$(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/kyclogic/libtalerkyclogic.la \
$(top_builddir)/src/curl/libtalercurl.la \
$(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/extensions/libtalerextensions.la \
@@ -77,7 +87,8 @@ libtalerexchange_la_LIBADD = \
-lgnunetjson \
-lgnunetutil \
-ljansson \
- $(LIBGNURLCURL_LIBS) \
+ -lcurl \
+ -lm \
$(XLIB)
libtalerauditor_la_LDFLAGS = \
@@ -85,9 +96,8 @@ libtalerauditor_la_LDFLAGS = \
-no-undefined
libtalerauditor_la_SOURCES = \
auditor_api_curl_defaults.c auditor_api_curl_defaults.h \
- auditor_api_handle.c auditor_api_handle.h \
- auditor_api_deposit_confirmation.c \
- auditor_api_exchanges.c
+ auditor_api_get_config.c \
+ auditor_api_deposit_confirmation.c
libtalerauditor_la_LIBADD = \
$(top_builddir)/src/curl/libtalercurl.la \
$(top_builddir)/src/json/libtalerjson.la \
@@ -96,5 +106,21 @@ libtalerauditor_la_LIBADD = \
-lgnunetjson \
-lgnunetutil \
-ljansson \
- $(LIBGNURLCURL_LIBS) \
+ -lcurl \
+ -lm \
$(XLIB)
+
+
+check_PROGRAMS = \
+ test_stefan
+
+TESTS = \
+ $(check_PROGRAMS)
+
+
+test_stefan_SOURCES = \
+ test_stefan.c
+test_stefan_LDADD = \
+ $(top_builddir)/src/lib/libtalerexchange.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetutil
diff --git a/src/lib/auditor_api_curl_defaults.c b/src/lib/auditor_api_curl_defaults.c
index 81fcd7bac..972f28ca6 100644
--- a/src/lib/auditor_api_curl_defaults.c
+++ b/src/lib/auditor_api_curl_defaults.c
@@ -19,6 +19,8 @@
* @brief curl easy handle defaults
* @author Florian Dold
*/
+#include "platform.h"
+#include "taler_curl_lib.h"
#include "auditor_api_curl_defaults.h"
@@ -37,22 +39,14 @@ TALER_AUDITOR_curl_easy_get_ (const char *url)
curl_easy_setopt (eh,
CURLOPT_URL,
url));
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_FOLLOWLOCATION,
- 1L));
+ TALER_curl_set_secure_redirect_policy (eh,
+ url);
/* Enable compression (using whatever curl likes), see
https://curl.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html */
GNUNET_break (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_ACCEPT_ENCODING,
""));
- /* limit MAXREDIRS to 5 as a simple security measure against
- a potential infinite loop caused by a malicious target */
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_MAXREDIRS,
- 5L));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_TCP_FASTOPEN,
diff --git a/src/lib/auditor_api_deposit_confirmation.c b/src/lib/auditor_api_deposit_confirmation.c
index c4542d0eb..172a12ece 100644
--- a/src/lib/auditor_api_deposit_confirmation.c
+++ b/src/lib/auditor_api_deposit_confirmation.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
+ 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
@@ -25,9 +25,10 @@
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <gnunet/gnunet_curl_lib.h>
+#include "taler_util.h"
+#include "taler_curl_lib.h"
#include "taler_json_lib.h"
#include "taler_auditor_service.h"
-#include "auditor_api_handle.h"
#include "taler_signatures.h"
#include "auditor_api_curl_defaults.h"
@@ -39,11 +40,6 @@ struct TALER_AUDITOR_DepositConfirmationHandle
{
/**
- * The connection to auditor this request handle will use
- */
- struct TALER_AUDITOR_Handle *auditor;
-
- /**
* The url for this request.
*/
char *url;
@@ -87,64 +83,64 @@ handle_deposit_confirmation_finished (void *cls,
{
const json_t *json = djson;
struct TALER_AUDITOR_DepositConfirmationHandle *dh = cls;
- struct TALER_AUDITOR_HttpResponse hr = {
- .reply = json,
- .http_status = (unsigned int) response_code
+ struct TALER_AUDITOR_DepositConfirmationResponse dcr = {
+ .hr.reply = json,
+ .hr.http_status = (unsigned int) response_code
};
dh->job = NULL;
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ dcr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
- hr.ec = TALER_EC_NONE;
+ dcr.hr.ec = TALER_EC_NONE;
break;
case MHD_HTTP_BAD_REQUEST:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
/* This should never happen, either us or the auditor is buggy
(or API version conflict); just pass JSON reply to the application */
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, auditor says one of the signatures is
invalid; as we checked them, this should never happen, we
should pass the JSON reply to the application */
break;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, this should never
happen, we should pass the JSON reply to the application */
break;
case MHD_HTTP_GONE:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
/* Nothing really to verify, auditor says one of the signatures is
invalid; as we checked them, this should never happen, we
should pass the JSON reply to the application */
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
/* Server had an internal issue; we should retry, but this API
leaves this to the application */
break;
default:
/* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ dcr.hr.ec = TALER_JSON_get_error_code (json);
+ dcr.hr.hint = TALER_JSON_get_error_hint (json);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d for auditor deposit confirmation\n",
(unsigned int) response_code,
- hr.ec);
+ dcr.hr.ec);
break;
}
dh->cb (dh->cb_cls,
- &hr);
+ &dcr);
TALER_AUDITOR_deposit_confirmation_cancel (dh);
}
@@ -153,13 +149,14 @@ handle_deposit_confirmation_finished (void *cls,
* Verify signature information about the deposit-confirmation.
*
* @param h_wire hash of merchant wire details
- * @param h_extensions hash over the extensions, if any
+ * @param h_policy hash over the policy extension, if any
* @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the auditor)
* @param exchange_timestamp timestamp when the deposit was received by the wallet
* @param wire_deadline by what time must the amount be wired to the merchant
* @param refund_deadline date until which the merchant can issue a refund to the customer via the auditor (can be zero if refunds are not allowed); must not be after the @a wire_deadline
* @param amount_without_fee the amount confirmed to be wired by the exchange to the merchant
- * @param coin_pub coin’s public key
+ * @param num_coins number of coins involved
+ * @param coin_sigs array of @a num_coins coin signatures
* @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
* @param exchange_sig the signature made with purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT
* @param exchange_pub the public key of the exchange that matches @a exchange_sig
@@ -171,33 +168,37 @@ handle_deposit_confirmation_finished (void *cls,
* @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not
*/
static enum GNUNET_GenericReturnValue
-verify_signatures (const struct TALER_MerchantWireHashP *h_wire,
- const struct TALER_ExtensionContractHashP *h_extensions,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- struct GNUNET_TIME_Timestamp exchange_timestamp,
- struct GNUNET_TIME_Timestamp wire_deadline,
- struct GNUNET_TIME_Timestamp refund_deadline,
- const struct TALER_Amount *amount_without_fee,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- const struct TALER_ExchangeSignatureP *exchange_sig,
- const struct TALER_MasterPublicKeyP *master_pub,
- struct GNUNET_TIME_Timestamp ep_start,
- struct GNUNET_TIME_Timestamp ep_expire,
- struct GNUNET_TIME_Timestamp ep_end,
- const struct TALER_MasterSignatureP *master_sig)
+verify_signatures (
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_ExtensionPolicyHashP *h_policy,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ struct GNUNET_TIME_Timestamp wire_deadline,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ const struct TALER_Amount *amount_without_fee,
+ unsigned int num_coins,
+ const struct TALER_CoinSpendSignatureP *coin_sigs[
+ static num_coins],
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_ExchangeSignatureP *exchange_sig,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ struct GNUNET_TIME_Timestamp ep_start,
+ struct GNUNET_TIME_Timestamp ep_expire,
+ struct GNUNET_TIME_Timestamp ep_end,
+ const struct TALER_MasterSignatureP *master_sig)
{
if (GNUNET_OK !=
TALER_exchange_online_deposit_confirmation_verify (
h_contract_terms,
h_wire,
- h_extensions,
+ h_policy,
exchange_timestamp,
wire_deadline,
refund_deadline,
amount_without_fee,
- coin_pub,
+ num_coins,
+ coin_sigs,
merchant_pub,
exchange_pub,
exchange_sig))
@@ -237,15 +238,20 @@ verify_signatures (const struct TALER_MerchantWireHashP *h_wire,
struct TALER_AUDITOR_DepositConfirmationHandle *
TALER_AUDITOR_deposit_confirmation (
- struct TALER_AUDITOR_Handle *auditor,
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
const struct TALER_MerchantWireHashP *h_wire,
- const struct TALER_ExtensionContractHashP *h_extensions,
+ const struct TALER_ExtensionPolicyHashP *h_policy,
const struct TALER_PrivateContractHashP *h_contract_terms,
struct GNUNET_TIME_Timestamp exchange_timestamp,
struct GNUNET_TIME_Timestamp wire_deadline,
struct GNUNET_TIME_Timestamp refund_deadline,
- const struct TALER_Amount *amount_without_fee,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *total_without_fee,
+ unsigned int num_coins,
+ const struct TALER_CoinSpendPublicKeyP *coin_pubs[
+ static num_coins],
+ const struct TALER_CoinSpendSignatureP *coin_sigs[
+ static num_coins],
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_ExchangePublicKeyP *exchange_pub,
const struct TALER_ExchangeSignatureP *exchange_sig,
@@ -258,21 +264,26 @@ TALER_AUDITOR_deposit_confirmation (
void *cb_cls)
{
struct TALER_AUDITOR_DepositConfirmationHandle *dh;
- struct GNUNET_CURL_Context *ctx;
json_t *deposit_confirmation_obj;
CURL *eh;
+ json_t *jcoin_sigs;
+ json_t *jcoin_pubs;
- GNUNET_assert (GNUNET_YES ==
- TALER_AUDITOR_handle_is_ready_ (auditor));
+ if (0 == num_coins)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
if (GNUNET_OK !=
verify_signatures (h_wire,
- h_extensions,
+ h_policy,
h_contract_terms,
exchange_timestamp,
wire_deadline,
refund_deadline,
- amount_without_fee,
- coin_pub,
+ total_without_fee,
+ num_coins,
+ coin_sigs,
merchant_pub,
exchange_pub,
exchange_sig,
@@ -285,25 +296,42 @@ TALER_AUDITOR_deposit_confirmation (
GNUNET_break_op (0);
return NULL;
}
-
+ jcoin_sigs = json_array ();
+ GNUNET_assert (NULL != jcoin_sigs);
+ jcoin_pubs = json_array ();
+ GNUNET_assert (NULL != jcoin_pubs);
+ for (unsigned int i = 0; i<num_coins; i++)
+ {
+ GNUNET_assert (0 ==
+ json_array_append_new (jcoin_sigs,
+ GNUNET_JSON_from_data_auto (
+ coin_sigs[i])));
+ GNUNET_assert (0 ==
+ json_array_append_new (jcoin_pubs,
+ GNUNET_JSON_from_data_auto (
+ coin_pubs[i])));
+ }
deposit_confirmation_obj
= GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("h_wire",
h_wire),
- GNUNET_JSON_pack_data_auto ("h_extensions",
- h_extensions),
+ GNUNET_JSON_pack_data_auto ("h_policy",
+ h_policy),
GNUNET_JSON_pack_data_auto ("h_contract_terms",
h_contract_terms),
GNUNET_JSON_pack_timestamp ("exchange_timestamp",
exchange_timestamp),
- GNUNET_JSON_pack_timestamp ("refund_deadline",
- refund_deadline),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("refund_deadline",
+ refund_deadline)),
GNUNET_JSON_pack_timestamp ("wire_deadline",
wire_deadline),
- TALER_JSON_pack_amount ("amount_without_fee",
- amount_without_fee),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- coin_pub),
+ TALER_JSON_pack_amount ("total_without_fee",
+ total_without_fee),
+ GNUNET_JSON_pack_array_steal ("coin_pubs",
+ jcoin_pubs),
+ GNUNET_JSON_pack_array_steal ("coin_sigs",
+ jcoin_sigs),
GNUNET_JSON_pack_data_auto ("merchant_pub",
merchant_pub),
GNUNET_JSON_pack_data_auto ("exchange_sig",
@@ -321,18 +349,17 @@ TALER_AUDITOR_deposit_confirmation (
GNUNET_JSON_pack_data_auto ("exchange_pub",
exchange_pub));
dh = GNUNET_new (struct TALER_AUDITOR_DepositConfirmationHandle);
- dh->auditor = auditor;
dh->cb = cb;
dh->cb_cls = cb_cls;
- dh->url = TALER_AUDITOR_path_to_url_ (auditor,
- "/deposit-confirmation");
+ dh->url = TALER_url_join (url,
+ "deposit-confirmation",
+ NULL);
if (NULL == dh->url)
{
GNUNET_free (dh);
return NULL;
}
eh = TALER_AUDITOR_curl_easy_get_ (dh->url);
-
if ( (NULL == eh) ||
(CURLE_OK !=
curl_easy_setopt (eh,
@@ -355,16 +382,21 @@ TALER_AUDITOR_deposit_confirmation (
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"URL for deposit-confirmation: `%s'\n",
dh->url);
- ctx = TALER_AUDITOR_handle_to_context_ (auditor);
dh->job = GNUNET_CURL_job_add2 (ctx,
eh,
dh->ctx.headers,
&handle_deposit_confirmation_finished,
dh);
- /* Disable 100 continue processing */
- GNUNET_CURL_extend_headers (dh->job,
- curl_slist_append (NULL,
- "Expect:"));
+ {
+ /* Disable 100 continue processing */
+ struct curl_slist *x_headers;
+
+ x_headers = curl_slist_append (NULL,
+ "Expect:");
+ GNUNET_CURL_extend_headers (dh->job,
+ x_headers);
+ curl_slist_free_all (x_headers);
+ }
return dh;
}
diff --git a/src/lib/auditor_api_exchanges.c b/src/lib/auditor_api_exchanges.c
deleted file mode 100644
index 7327f11b2..000000000
--- a/src/lib/auditor_api_exchanges.c
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2018 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file lib/auditor_api_exchanges.c
- * @brief Implementation of the /exchanges request of the auditor's HTTP API
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_json_lib.h"
-#include "taler_auditor_service.h"
-#include "auditor_api_handle.h"
-#include "taler_signatures.h"
-#include "auditor_api_curl_defaults.h"
-
-/**
- * How many exchanges do we allow a single auditor to
- * audit at most?
- */
-#define MAX_EXCHANGES 1024
-
-
-/**
- * @brief A ListExchanges Handle
- */
-struct TALER_AUDITOR_ListExchangesHandle
-{
-
- /**
- * The connection to auditor this request handle will use
- */
- struct TALER_AUDITOR_Handle *auditor;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_AUDITOR_ListExchangesResultCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP /exchanges request.
- *
- * @param cls the `struct TALER_AUDITOR_ListExchangesHandle`
- * @param response_code HTTP response code, 0 on error
- * @param djson parsed JSON result, NULL on error
- */
-static void
-handle_exchanges_finished (void *cls,
- long response_code,
- const void *djson)
-{
- const json_t *json = djson;
- const json_t *ja;
- unsigned int ja_len;
- struct TALER_AUDITOR_ListExchangesHandle *leh = cls;
- struct TALER_AUDITOR_HttpResponse hr = {
- .reply = json,
- .http_status = (unsigned int) response_code
- };
-
- leh->job = NULL;
- switch (response_code)
- {
- case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- ja = json_object_get (json,
- "exchanges");
- if ( (NULL == ja) ||
- (! json_is_array (ja)) )
- {
- GNUNET_break (0);
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- hr.http_status = 0;
- break;
- }
-
- ja_len = json_array_size (ja);
- if (ja_len > MAX_EXCHANGES)
- {
- GNUNET_break (0);
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- hr.http_status = 0;
- break;
- }
- {
- struct TALER_AUDITOR_ExchangeInfo ei[ja_len];
- bool ok;
-
- ok = true;
- for (unsigned int i = 0; i<ja_len; i++)
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("master_pub", &ei[i].master_pub),
- GNUNET_JSON_spec_string ("exchange_url", &ei[i].exchange_url),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json_array_get (ja,
- i),
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- ok = false;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- hr.http_status = 0;
- break;
- }
- }
- if (! ok)
- break;
- leh->cb (leh->cb_cls,
- &hr,
- ja_len,
- ei);
- TALER_AUDITOR_list_exchanges_cancel (leh);
- return;
- }
- case MHD_HTTP_BAD_REQUEST:
- /* This should never happen, either us or the auditor is buggy
- (or API version conflict); just pass JSON reply to the application */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_NOT_FOUND:
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- break;
- default:
- /* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for auditor list-exchanges request\n",
- (unsigned int) response_code,
- (int) hr.ec);
- GNUNET_break_op (0);
- break;
- }
- if (NULL != leh->cb)
- leh->cb (leh->cb_cls,
- &hr,
- 0,
- NULL);
- TALER_AUDITOR_list_exchanges_cancel (leh);
-}
-
-
-struct TALER_AUDITOR_ListExchangesHandle *
-TALER_AUDITOR_list_exchanges (struct TALER_AUDITOR_Handle *auditor,
- TALER_AUDITOR_ListExchangesResultCallback cb,
- void *cb_cls)
-{
- struct TALER_AUDITOR_ListExchangesHandle *leh;
- struct GNUNET_CURL_Context *ctx;
- CURL *eh;
-
- GNUNET_assert (GNUNET_YES ==
- TALER_AUDITOR_handle_is_ready_ (auditor));
-
- leh = GNUNET_new (struct TALER_AUDITOR_ListExchangesHandle);
- leh->auditor = auditor;
- leh->cb = cb;
- leh->cb_cls = cb_cls;
- leh->url = TALER_AUDITOR_path_to_url_ (auditor,
- "/exchanges");
- if (NULL == leh->url)
- {
- GNUNET_free (leh);
- return NULL;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "URL for list-exchanges: `%s'\n",
- leh->url);
- eh = TALER_AUDITOR_curl_easy_get_ (leh->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- GNUNET_free (leh->url);
- GNUNET_free (leh);
- return NULL;
- }
- ctx = TALER_AUDITOR_handle_to_context_ (auditor);
- leh->job = GNUNET_CURL_job_add (ctx,
- eh,
- &handle_exchanges_finished,
- leh);
- return leh;
-}
-
-
-void
-TALER_AUDITOR_list_exchanges_cancel (
- struct TALER_AUDITOR_ListExchangesHandle *leh)
-{
- if (NULL != leh->job)
- {
- GNUNET_CURL_job_cancel (leh->job);
- leh->job = NULL;
- }
- GNUNET_free (leh->url);
- GNUNET_free (leh);
-}
-
-
-/* end of auditor_api_exchanges.c */
diff --git a/src/lib/auditor_api_get_config.c b/src/lib/auditor_api_get_config.c
new file mode 100644
index 000000000..1e8e0bb30
--- /dev/null
+++ b/src/lib/auditor_api_get_config.c
@@ -0,0 +1,278 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/auditor_api_get_config.c
+ * @brief Implementation of /config for the auditor's HTTP API
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_auditor_service.h"
+#include "taler_signatures.h"
+#include "auditor_api_curl_defaults.h"
+
+
+/**
+ * Which revision of the Taler auditor protocol is implemented
+ * by this library? Used to determine compatibility.
+ */
+#define TALER_PROTOCOL_CURRENT 1
+
+/**
+ * How many revisions back are we compatible to?
+ */
+#define TALER_PROTOCOL_AGE 0
+
+
+/**
+ * Log error related to CURL operations.
+ *
+ * @param type log level
+ * @param function which function failed to run
+ * @param code what was the curl error code
+ */
+#define CURL_STRERROR(type, function, code) \
+ GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \
+ function, __FILE__, __LINE__, curl_easy_strerror (code));
+
+
+/**
+ * Handle for the get config request.
+ */
+struct TALER_AUDITOR_GetConfigHandle
+{
+ /**
+ * The context of this handle
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Function to call with the auditor's certification data,
+ * NULL if this has already been done.
+ */
+ TALER_AUDITOR_ConfigCallback config_cb;
+
+ /**
+ * Closure to pass to @e config_cb.
+ */
+ void *config_cb_cls;
+
+ /**
+ * Data for the request to get the /config of a auditor,
+ * NULL once we are past stage #MHS_INIT.
+ */
+ struct GNUNET_CURL_Job *vr;
+
+ /**
+ * The url for the @e vr job.
+ */
+ char *vr_url;
+
+};
+
+
+/* ***************** Internal /config fetching ************* */
+
+/**
+ * Decode the JSON in @a resp_obj from the /config response and store the data
+ * in the @a key_data.
+ *
+ * @param[in] resp_obj JSON object to parse
+ * @param[in,out] vi where to store the results we decoded
+ * @param[out] vc where to store config compatibility data
+ * @return #TALER_EC_NONE on success
+ */
+static enum TALER_ErrorCode
+decode_config_json (const json_t *resp_obj,
+ struct TALER_AUDITOR_ConfigInformation *vi,
+ enum TALER_AUDITOR_VersionCompatibility *vc)
+{
+ struct TALER_JSON_ProtocolVersion pv;
+ const char *ver;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_version ("version",
+ &pv),
+ GNUNET_JSON_spec_string ("version",
+ &ver),
+ GNUNET_JSON_spec_fixed_auto ("exchange_master_public_key",
+ &vi->exchange_master_public_key),
+ GNUNET_JSON_spec_fixed_auto ("auditor_public_key",
+ &vi->auditor_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (JSON_OBJECT != json_typeof (resp_obj))
+ {
+ GNUNET_break_op (0);
+ return TALER_EC_GENERIC_JSON_INVALID;
+ }
+ /* check the config */
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (resp_obj,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return TALER_EC_GENERIC_JSON_INVALID;
+ }
+ vi->version = ver;
+ *vc = TALER_AUDITOR_VC_MATCH;
+ if (TALER_PROTOCOL_CURRENT < pv.current)
+ {
+ *vc |= TALER_AUDITOR_VC_NEWER;
+ if (TALER_PROTOCOL_CURRENT < pv.current - pv.age)
+ *vc |= TALER_AUDITOR_VC_INCOMPATIBLE;
+ }
+ if (TALER_PROTOCOL_CURRENT > pv.current)
+ {
+ *vc |= TALER_AUDITOR_VC_OLDER;
+ if (TALER_PROTOCOL_CURRENT - TALER_PROTOCOL_AGE > pv.current)
+ *vc |= TALER_AUDITOR_VC_INCOMPATIBLE;
+ }
+ return TALER_EC_NONE;
+}
+
+
+/**
+ * Callback used when downloading the reply to a /config request
+ * is complete.
+ *
+ * @param cls the `struct TALER_AUDITOR_GetConfigHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param gresp_obj parsed JSON result, NULL on error, must be a `const json_t *`
+ */
+static void
+config_completed_cb (void *cls,
+ long response_code,
+ const void *gresp_obj)
+{
+ struct TALER_AUDITOR_GetConfigHandle *auditor = cls;
+ const json_t *resp_obj = gresp_obj;
+ struct TALER_AUDITOR_ConfigResponse vr = {
+ .hr.reply = resp_obj,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ auditor->vr = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received config from URL `%s' with status %ld.\n",
+ auditor->vr_url,
+ response_code);
+ switch (response_code)
+ {
+ case 0:
+ GNUNET_break_op (0);
+ vr.hr.ec = TALER_EC_INVALID;
+ break;
+ case MHD_HTTP_OK:
+ if (NULL == resp_obj)
+ {
+ GNUNET_break_op (0);
+ vr.hr.http_status = 0;
+ vr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ vr.hr.ec = decode_config_json (resp_obj,
+ &vr.details.ok.vi,
+ &vr.details.ok.compat);
+ if (TALER_EC_NONE != vr.hr.ec)
+ {
+ GNUNET_break_op (0);
+ vr.hr.http_status = 0;
+ break;
+ }
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ vr.hr.ec = TALER_JSON_get_error_code (resp_obj);
+ vr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
+ break;
+ default:
+ vr.hr.ec = TALER_JSON_get_error_code (resp_obj);
+ vr.hr.hint = TALER_JSON_get_error_hint (resp_obj);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) vr.hr.ec);
+ break;
+ }
+ auditor->config_cb (auditor->config_cb_cls,
+ &vr);
+ TALER_AUDITOR_get_config_cancel (auditor);
+}
+
+
+struct TALER_AUDITOR_GetConfigHandle *
+TALER_AUDITOR_get_config (struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ TALER_AUDITOR_ConfigCallback config_cb,
+ void *config_cb_cls)
+{
+ struct TALER_AUDITOR_GetConfigHandle *auditor;
+ CURL *eh;
+
+ auditor = GNUNET_new (struct TALER_AUDITOR_GetConfigHandle);
+ auditor->config_cb = config_cb;
+ auditor->config_cb_cls = config_cb_cls;
+ auditor->ctx = ctx;
+ auditor->vr_url = TALER_url_join (url,
+ "config",
+ NULL);
+ if (NULL == auditor->vr_url)
+ {
+ GNUNET_break (0);
+ GNUNET_free (auditor);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Requesting auditor config with URL `%s'.\n",
+ auditor->vr_url);
+ eh = TALER_AUDITOR_curl_easy_get_ (auditor->vr_url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ TALER_AUDITOR_get_config_cancel (auditor);
+ return NULL;
+ }
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_TIMEOUT,
+ (long) 300));
+ auditor->vr = GNUNET_CURL_job_add (auditor->ctx,
+ eh,
+ &config_completed_cb,
+ auditor);
+ return auditor;
+}
+
+
+void
+TALER_AUDITOR_get_config_cancel (struct TALER_AUDITOR_GetConfigHandle *auditor)
+{
+ if (NULL != auditor->vr)
+ {
+ GNUNET_CURL_job_cancel (auditor->vr);
+ auditor->vr = NULL;
+ }
+ GNUNET_free (auditor->vr_url);
+ GNUNET_free (auditor);
+}
+
+
+/* end of auditor_api_get_config.c */
diff --git a/src/lib/auditor_api_handle.c b/src/lib/auditor_api_handle.c
deleted file mode 100644
index 9edb1115e..000000000
--- a/src/lib/auditor_api_handle.c
+++ /dev/null
@@ -1,449 +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/auditor_api_handle.c
- * @brief Implementation of the "handle" component of the auditor's HTTP API
- * @author Sree Harsha Totakura <sreeharsha@totakura.in>
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <microhttpd.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_json_lib.h"
-#include "taler_auditor_service.h"
-#include "taler_signatures.h"
-#include "auditor_api_handle.h"
-#include "auditor_api_curl_defaults.h"
-#include "backoff.h"
-
-/**
- * Which revision of the Taler auditor protocol is implemented
- * by this library? Used to determine compatibility.
- */
-#define TALER_PROTOCOL_CURRENT 0
-
-/**
- * How many revisions back are we compatible to?
- */
-#define TALER_PROTOCOL_AGE 0
-
-
-/**
- * Log error related to CURL operations.
- *
- * @param type log level
- * @param function which function failed to run
- * @param code what was the curl error code
- */
-#define CURL_STRERROR(type, function, code) \
- GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \
- function, __FILE__, __LINE__, curl_easy_strerror (code));
-
-/**
- * Stages of initialization for the `struct TALER_AUDITOR_Handle`
- */
-enum AuditorHandleState
-{
- /**
- * Just allocated.
- */
- MHS_INIT = 0,
-
- /**
- * Obtained the auditor's versioning data and version.
- */
- MHS_VERSION = 1,
-
- /**
- * Failed to initialize (fatal).
- */
- MHS_FAILED = 2
-};
-
-
-/**
- * Handle to the auditor
- */
-struct TALER_AUDITOR_Handle
-{
- /**
- * The context of this handle
- */
- struct GNUNET_CURL_Context *ctx;
-
- /**
- * The URL of the auditor (i.e. "http://auditor.taler.net/")
- */
- char *url;
-
- /**
- * Function to call with the auditor's certification data,
- * NULL if this has already been done.
- */
- TALER_AUDITOR_VersionCallback version_cb;
-
- /**
- * Closure to pass to @e version_cb.
- */
- void *version_cb_cls;
-
- /**
- * Data for the request to get the /version of a auditor,
- * NULL once we are past stage #MHS_INIT.
- */
- struct GNUNET_CURL_Job *vr;
-
- /**
- * The url for the @e vr job.
- */
- char *vr_url;
-
- /**
- * Task for retrying /version request.
- */
- struct GNUNET_SCHEDULER_Task *retry_task;
-
- /**
- * /version data of the auditor, only valid if
- * @e handshake_complete is past stage #MHS_VERSION.
- */
- char *version;
-
- /**
- * Version information for the callback.
- */
- struct TALER_AUDITOR_VersionInformation vi;
-
- /**
- * Retry /version frequency.
- */
- struct GNUNET_TIME_Relative retry_delay;
-
- /**
- * Stage of the auditor's initialization routines.
- */
- enum AuditorHandleState state;
-
-};
-
-
-/* ***************** Internal /version fetching ************* */
-
-/**
- * Decode the JSON in @a resp_obj from the /version response and store the data
- * in the @a key_data.
- *
- * @param[in] resp_obj JSON object to parse
- * @param[in,out] auditor where to store the results we decoded
- * @param[out] vc where to store version compatibility data
- * @return #TALER_EC_NONE on success
- */
-static enum TALER_ErrorCode
-decode_version_json (const json_t *resp_obj,
- struct TALER_AUDITOR_Handle *auditor,
- enum TALER_AUDITOR_VersionCompatibility *vc)
-{
- struct TALER_AUDITOR_VersionInformation *vi = &auditor->vi;
- unsigned int age;
- unsigned int revision;
- unsigned int current;
- char dummy;
- const char *ver;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("version",
- &ver),
- GNUNET_JSON_spec_fixed_auto ("auditor_public_key",
- &vi->auditor_pub),
- GNUNET_JSON_spec_end ()
- };
-
- if (JSON_OBJECT != json_typeof (resp_obj))
- {
- GNUNET_break_op (0);
- return TALER_EC_GENERIC_JSON_INVALID;
- }
- /* check the version */
- if (GNUNET_OK !=
- GNUNET_JSON_parse (resp_obj,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return TALER_EC_GENERIC_JSON_INVALID;
- }
- if (3 != sscanf (ver,
- "%u:%u:%u%c",
- &current,
- &revision,
- &age,
- &dummy))
- {
- GNUNET_break_op (0);
- return TALER_EC_GENERIC_VERSION_MALFORMED;
- }
- GNUNET_free (auditor->version);
- auditor->version = GNUNET_strdup (ver);
- vi->version = auditor->version;
- *vc = TALER_AUDITOR_VC_MATCH;
- if (TALER_PROTOCOL_CURRENT < current)
- {
- *vc |= TALER_AUDITOR_VC_NEWER;
- if (TALER_PROTOCOL_CURRENT < current - age)
- *vc |= TALER_AUDITOR_VC_INCOMPATIBLE;
- }
- if (TALER_PROTOCOL_CURRENT > current)
- {
- *vc |= TALER_AUDITOR_VC_OLDER;
- if (TALER_PROTOCOL_CURRENT - TALER_PROTOCOL_AGE > current)
- *vc |= TALER_AUDITOR_VC_INCOMPATIBLE;
- }
- return TALER_EC_NONE;
-}
-
-
-/**
- * Initiate download of /version from the auditor.
- *
- * @param cls auditor where to download /version from
- */
-static void
-request_version (void *cls);
-
-
-/**
- * Callback used when downloading the reply to a /version request
- * is complete.
- *
- * @param cls the `struct TALER_AUDITOR_Handle`
- * @param response_code HTTP response code, 0 on error
- * @param gresp_obj parsed JSON result, NULL on error, must be a `const json_t *`
- */
-static void
-version_completed_cb (void *cls,
- long response_code,
- const void *gresp_obj)
-{
- struct TALER_AUDITOR_Handle *auditor = cls;
- const json_t *resp_obj = gresp_obj;
- enum TALER_AUDITOR_VersionCompatibility vc;
- struct TALER_AUDITOR_HttpResponse hr = {
- .reply = resp_obj,
- .http_status = (unsigned int) response_code
- };
-
- auditor->vr = NULL;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received version from URL `%s' with status %ld.\n",
- auditor->url,
- response_code);
- vc = TALER_AUDITOR_VC_PROTOCOL_ERROR;
- switch (response_code)
- {
- case 0:
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* NOTE: this design is debatable. We MAY want to throw this error at the
- client. We may then still additionally internally re-try. */
- GNUNET_assert (NULL == auditor->retry_task);
- auditor->retry_delay = EXCHANGE_LIB_BACKOFF (auditor->retry_delay);
- auditor->retry_task = GNUNET_SCHEDULER_add_delayed (auditor->retry_delay,
- &request_version,
- auditor);
- return;
- case MHD_HTTP_OK:
- if (NULL == resp_obj)
- {
- GNUNET_break_op (0);
- TALER_LOG_WARNING ("NULL body for a 200-OK /version\n");
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
- hr.ec = decode_version_json (resp_obj,
- auditor,
- &vc);
- if (TALER_EC_NONE != hr.ec)
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- break;
- }
- auditor->retry_delay = GNUNET_TIME_UNIT_ZERO; /* restart quickly */
- break;
- default:
- hr.ec = TALER_JSON_get_error_code (resp_obj);
- hr.hint = TALER_JSON_get_error_hint (resp_obj);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d\n",
- (unsigned int) response_code,
- (int) hr.ec);
- break;
- }
- if (MHD_HTTP_OK != response_code)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "/version failed for auditor %s: %u!\n",
- auditor->url,
- (unsigned int) response_code);
- auditor->state = MHS_FAILED;
- /* notify application that we failed */
- auditor->version_cb (auditor->version_cb_cls,
- &hr,
- NULL,
- vc);
- return;
- }
- TALER_LOG_DEBUG ("Switching auditor state to 'version'\n");
- auditor->state = MHS_VERSION;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Auditor %s is now READY!\n",
- auditor->url);
- /* notify application about the key information */
- auditor->version_cb (auditor->version_cb_cls,
- &hr,
- &auditor->vi,
- vc);
-}
-
-
-/**
- * Initiate download of /version from the auditor.
- *
- * @param cls auditor where to download /version from
- */
-static void
-request_version (void *cls)
-{
- struct TALER_AUDITOR_Handle *auditor = cls;
- CURL *eh;
-
- auditor->retry_task = NULL;
- GNUNET_assert (NULL == auditor->vr);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Requesting auditor version with URL `%s'.\n",
- auditor->vr_url);
- eh = TALER_AUDITOR_curl_easy_get_ (auditor->vr_url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- auditor->retry_delay = EXCHANGE_LIB_BACKOFF (auditor->retry_delay);
- auditor->retry_task = GNUNET_SCHEDULER_add_delayed (auditor->retry_delay,
- &request_version,
- auditor);
- return;
- }
- GNUNET_break (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_TIMEOUT,
- (long) 300));
- auditor->vr = GNUNET_CURL_job_add (auditor->ctx,
- eh,
- &version_completed_cb,
- auditor);
-}
-
-
-/* ********************* library internal API ********* */
-
-
-struct GNUNET_CURL_Context *
-TALER_AUDITOR_handle_to_context_ (struct TALER_AUDITOR_Handle *h)
-{
- return h->ctx;
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_AUDITOR_handle_is_ready_ (struct TALER_AUDITOR_Handle *h)
-{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Checking if auditor at `%s` is now ready: %s\n",
- h->url,
- (MHD_VERSION == h->state) ? "yes" : "no");
- return (MHS_VERSION == h->state) ? GNUNET_YES : GNUNET_NO;
-}
-
-
-char *
-TALER_AUDITOR_path_to_url_ (struct TALER_AUDITOR_Handle *h,
- const char *path)
-{
- GNUNET_assert ('/' == path[0]);
- return TALER_url_join (h->url,
- path + 1,
- NULL);
-}
-
-
-/* ********************* public API ******************* */
-
-
-struct TALER_AUDITOR_Handle *
-TALER_AUDITOR_connect (struct GNUNET_CURL_Context *ctx,
- const char *url,
- TALER_AUDITOR_VersionCallback version_cb,
- void *version_cb_cls)
-{
- struct TALER_AUDITOR_Handle *auditor;
-
- auditor = GNUNET_new (struct TALER_AUDITOR_Handle);
- auditor->version_cb = version_cb;
- auditor->version_cb_cls = version_cb_cls;
- auditor->retry_delay = GNUNET_TIME_UNIT_SECONDS; /* start slowly */
- auditor->ctx = ctx;
- auditor->url = GNUNET_strdup (url);
- auditor->vr_url = TALER_AUDITOR_path_to_url_ (auditor,
- "/version");
- if (NULL == auditor->vr_url)
- {
- GNUNET_break (0);
- GNUNET_free (auditor->url);
- GNUNET_free (auditor);
- return NULL;
- }
- auditor->retry_task = GNUNET_SCHEDULER_add_now (&request_version,
- auditor);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Connecting to auditor at URL `%s'.\n",
- url);
- return auditor;
-}
-
-
-void
-TALER_AUDITOR_disconnect (struct TALER_AUDITOR_Handle *auditor)
-{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Disconnecting from auditor at URL `%s'.\n",
- auditor->url);
- if (NULL != auditor->vr)
- {
- GNUNET_CURL_job_cancel (auditor->vr);
- auditor->vr = NULL;
- }
- if (NULL != auditor->retry_task)
- {
- GNUNET_SCHEDULER_cancel (auditor->retry_task);
- auditor->retry_task = NULL;
- }
- GNUNET_free (auditor->version);
- GNUNET_free (auditor->vr_url);
- GNUNET_free (auditor->url);
- GNUNET_free (auditor);
-}
-
-
-/* end of auditor_api_handle.c */
diff --git a/src/lib/auditor_api_handle.h b/src/lib/auditor_api_handle.h
deleted file mode 100644
index 7ff5bfcdb..000000000
--- a/src/lib/auditor_api_handle.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014, 2015 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file lib/auditor_api_handle.h
- * @brief Internal interface to the handle part of the auditor's HTTP API
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_auditor_service.h"
-#include "taler_curl_lib.h"
-
-/**
- * Get the context of a auditor.
- *
- * @param h the auditor handle to query
- * @return ctx context to execute jobs in
- */
-struct GNUNET_CURL_Context *
-TALER_AUDITOR_handle_to_context_ (struct TALER_AUDITOR_Handle *h);
-
-
-/**
- * Check if the handle is ready to process requests.
- *
- * @param h the auditor handle to query
- * @return #GNUNET_YES if we are ready, #GNUNET_NO if not
- */
-int
-TALER_AUDITOR_handle_is_ready_ (struct TALER_AUDITOR_Handle *h);
-
-
-/**
- * Obtain the URL to use for an API request.
- *
- * @param h the auditor handle to query
- * @param path Taler API path (i.e. "/deposit-confirmation")
- * @return the full URL to use with cURL
- */
-char *
-TALER_AUDITOR_path_to_url_ (struct TALER_AUDITOR_Handle *h,
- const char *path);
-
-
-/* end of auditor_api_handle.h */
diff --git a/src/lib/exchange_api_add_aml_decision.c b/src/lib/exchange_api_add_aml_decision.c
new file mode 100644
index 000000000..342e1e3dc
--- /dev/null
+++ b/src/lib/exchange_api_add_aml_decision.c
@@ -0,0 +1,246 @@
+/*
+ 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_add_aml_decision.c
+ * @brief functions to add an AML decision by an AML officer
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_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_add_aml_decision_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_AddAmlDecision *
+TALER_EXCHANGE_add_aml_decision (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const char *justification,
+ struct GNUNET_TIME_Timestamp decision_time,
+ const struct TALER_Amount *new_threshold,
+ const struct TALER_PaytoHashP *h_payto,
+ enum TALER_AmlDecisionState new_state,
+ const json_t *kyc_requirements,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ 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;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
+ &officer_pub.eddsa_pub);
+ TALER_officer_aml_decision_sign (justification,
+ decision_time,
+ new_threshold,
+ h_payto,
+ new_state,
+ kyc_requirements,
+ 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);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("justification",
+ justification),
+ GNUNET_JSON_pack_data_auto ("officer_sig",
+ &officer_sig),
+ GNUNET_JSON_pack_data_auto ("h_payto",
+ h_payto),
+ GNUNET_JSON_pack_uint64 ("new_state",
+ (uint32_t) new_state),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_incref ("kyc_requirements",
+ (json_t *) kyc_requirements)),
+ TALER_JSON_pack_amount ("new_threshold",
+ new_threshold),
+ 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_add_aml_decision_cancel (wh);
+ return NULL;
+ }
+ return wh;
+}
+
+
+void
+TALER_EXCHANGE_add_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_age_withdraw.c b/src/lib/exchange_api_age_withdraw.c
new file mode 100644
index 000000000..ca1a11cb8
--- /dev/null
+++ b/src/lib/exchange_api_age_withdraw.c
@@ -0,0 +1,1125 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_age_withdraw.c
+ * @brief Implementation of /reserves/$RESERVE_PUB/age-withdraw requests
+ * @author Ă–zgĂĽr Kesim
+ */
+
+#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include <sys/wait.h>
+#include "taler_curl_lib.h"
+#include "taler_error_codes.h"
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_util.h"
+
+/**
+ * A CoinCandidate is populated from a master secret
+ */
+struct CoinCandidate
+{
+ /**
+ * Master key material for the coin candidates.
+ */
+ struct TALER_PlanchetMasterSecretP secret;
+
+ /**
+ * The details derived form the master secrets
+ */
+ struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails details;
+
+ /**
+ * Blinded hash of the coin
+ **/
+ struct TALER_BlindedCoinHashP blinded_coin_h;
+
+};
+
+
+/**
+ * Closure for a call to /csr-withdraw, contains data that is needed to process
+ * the result.
+ */
+struct CSRClosure
+{
+ /**
+ * Points to the actual candidate in CoinData.coin_candidates, to continue
+ * to build its contents based on the results from /csr-withdraw
+ */
+ struct CoinCandidate *candidate;
+
+ /**
+ * The planchet to finally generate. Points to the corresponding candidate
+ * in CoindData.planchet_details
+ */
+ struct TALER_PlanchetDetail *planchet;
+
+ /**
+ * Handler to the originating call to /age-withdraw, needed to either
+ * cancel the running age-withdraw request (on failure of the current call
+ * to /csr-withdraw), or to eventually perform the protocol, once all
+ * csr-withdraw requests have successfully finished.
+ */
+ struct TALER_EXCHANGE_AgeWithdrawHandle *age_withdraw_handle;
+
+ /**
+ * Session nonce.
+ */
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+
+ /**
+ * Denomination information, needed for CS coins for the
+ * step after /csr-withdraw
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+
+ /**
+ * Handler for the CS R request
+ */
+ struct TALER_EXCHANGE_CsRWithdrawHandle *csr_withdraw_handle;
+};
+
+/**
+ * Data we keep per coin in the batch.
+ */
+struct CoinData
+{
+ /**
+ * The denomination of the coin. Must support age restriction, i.e
+ * its .keys.age_mask MUST not be 0
+ */
+ struct TALER_EXCHANGE_DenomPublicKey denom_pub;
+
+ /**
+ * The Candidates for the coin
+ */
+ struct CoinCandidate coin_candidates[TALER_CNC_KAPPA];
+
+ /**
+ * Details of the planchet(s).
+ */
+ struct TALER_PlanchetDetail planchet_details[TALER_CNC_KAPPA];
+
+ /**
+ * Closure for each candidate of type CS for the preflight request to
+ * /csr-withdraw
+ */
+ struct CSRClosure csr_cls[TALER_CNC_KAPPA];
+};
+
+/**
+ * A /reserves/$RESERVE_PUB/age-withdraw request-handle for calls with
+ * pre-blinded planchets. Returned by TALER_EXCHANGE_age_withdraw_blinded.
+ */
+struct TALER_EXCHANGE_AgeWithdrawBlindedHandle
+{
+
+ /**
+ * Reserve private key.
+ */
+ const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+ /**
+ * Reserve public key, calculated
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Signature of the reserve for the request, calculated after all
+ * parameters for the coins are collected.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /*
+ * The denomination keys of the exchange
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The age mask, extracted from the denominations.
+ * MUST be the same for all denominations
+ *
+ */
+ struct TALER_AgeMask age_mask;
+
+ /**
+ * Maximum age to commit to.
+ */
+ uint8_t max_age;
+
+ /**
+ * The commitment calculated as SHA512 hash over all blinded_coin_h
+ */
+ struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+
+ /**
+ * Total amount requested (value plus withdraw fee).
+ */
+ struct TALER_Amount amount_with_fee;
+
+ /**
+ * Length of the @e blinded_input Array
+ */
+ size_t num_input;
+
+ /**
+ * The blinded planchet input for the call to /age-withdraw via
+ * TALER_EXCHANGE_age_withdraw_blinded
+ */
+ const struct TALER_EXCHANGE_AgeWithdrawBlindedInput *blinded_input;
+
+ /**
+ * The url for this request.
+ */
+ char *request_url;
+
+ /**
+ * Context for curl.
+ */
+ struct GNUNET_CURL_Context *curl_ctx;
+
+ /**
+ * CURL handle for the request job.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Post Context
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Function to call with age-withdraw response results.
+ */
+ TALER_EXCHANGE_AgeWithdrawBlindedCallback callback;
+
+ /**
+ * Closure for @e blinded_callback
+ */
+ void *callback_cls;
+};
+
+/**
+ * A /reserves/$RESERVE_PUB/age-withdraw request-handle for calls from
+ * a wallet, i. e. when blinding data is available.
+ */
+struct TALER_EXCHANGE_AgeWithdrawHandle
+{
+
+ /**
+ * Length of the @e coin_data Array
+ */
+ size_t num_coins;
+
+ /**
+ * The base-URL of the exchange.
+ */
+ const char *exchange_url;
+
+ /**
+ * Reserve private key.
+ */
+ const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+ /**
+ * Reserve public key, calculated
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Signature of the reserve for the request, calculated after all
+ * parameters for the coins are collected.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /*
+ * The denomination keys of the exchange
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * The age mask, extracted from the denominations.
+ * MUST be the same for all denominations
+ *
+ */
+ struct TALER_AgeMask age_mask;
+
+ /**
+ * Maximum age to commit to.
+ */
+ uint8_t max_age;
+
+ /**
+ * Array of per-coin data
+ */
+ struct CoinData *coin_data;
+
+ /**
+ * Context for curl.
+ */
+ struct GNUNET_CURL_Context *curl_ctx;
+
+ struct
+ {
+ /**
+ * Number of /csr-withdraw requests still pending.
+ */
+ unsigned int pending;
+
+ /**
+ * CURL handle for the request job.
+ */
+ struct GNUNET_CURL_Job *job;
+ } csr;
+
+
+ /**
+ * Function to call with age-withdraw response results.
+ */
+ TALER_EXCHANGE_AgeWithdrawCallback callback;
+
+ /**
+ * Closure for @e age_withdraw_cb
+ */
+ void *callback_cls;
+
+ /* The Handler for the actual call to the exchange */
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *procotol_handle;
+};
+
+/**
+ * We got a 200 OK response for the /reserves/$RESERVE_PUB/age-withdraw operation.
+ * Extract the noreveal_index and return it to the caller.
+ *
+ * @param awbh operation handle
+ * @param j_response reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+reserve_age_withdraw_ok (
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh,
+ const json_t *j_response)
+{
+ struct TALER_EXCHANGE_AgeWithdrawBlindedResponse response = {
+ .hr.reply = j_response,
+ .hr.http_status = MHD_HTTP_OK,
+ .details.ok.h_commitment = awbh->h_commitment
+ };
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint8 ("noreveal_index",
+ &response.details.ok.noreveal_index),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &response.details.ok.exchange_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK!=
+ GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ TALER_exchange_online_age_withdraw_confirmation_verify (
+ &awbh->h_commitment,
+ response.details.ok.noreveal_index,
+ &response.details.ok.exchange_pub,
+ &exchange_sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+
+ }
+
+ awbh->callback (awbh->callback_cls,
+ &response);
+ /* make sure the callback isn't called again */
+ awbh->callback = NULL;
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RESERVE_PUB/age-withdraw request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_AgeWithdrawHandle`
+ * @param response_code The HTTP response code
+ * @param response response data
+ */
+static void
+handle_reserve_age_withdraw_blinded_finished (
+ void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh = cls;
+ const json_t *j_response = response;
+ struct TALER_EXCHANGE_AgeWithdrawBlindedResponse awbr = {
+ .hr.reply = j_response,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ awbh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ awbr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ reserve_age_withdraw_ok (awbh,
+ j_response))
+ {
+ GNUNET_break_op (0);
+ awbr.hr.http_status = 0;
+ awbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == awbh->callback);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh);
+ return;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ GNUNET_break_op (0);
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, the exchange basically just says
+ that it doesn't know this reserve. Can happen if we
+ query before the wire transfer went through.
+ We should simply pass the JSON reply to the application. */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* The age requirements might not have been met */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_GONE:
+ /* could happen if denomination was revoked */
+ /* Note: one might want to check /keys for revocation
+ signature here, alas tricky in case our /keys
+ is outdated => left to clients */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ /* only validate reply is well-formed */
+ {
+ uint64_t ptu;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint64 ("requirement_row",
+ &ptu),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ awbr.hr.http_status = 0;
+ awbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ awbr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange age-withdraw\n",
+ (unsigned int) response_code,
+ (int) awbr.hr.ec);
+ break;
+ }
+ awbh->callback (awbh->callback_cls,
+ &awbr);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh);
+}
+
+
+/**
+ * Runs the actual age-withdraw operation with the blinded planchets.
+ *
+ * @param[in,out] awbh age withdraw handler
+ */
+static void
+perform_protocol (
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh)
+{
+#define FAIL_IF(cond) \
+ do { \
+ if ((cond)) \
+ { \
+ GNUNET_break (! (cond)); \
+ goto ERROR; \
+ } \
+ } while (0)
+
+ struct GNUNET_HashContext *coins_hctx = NULL;
+ json_t *j_denoms = NULL;
+ json_t *j_array_candidates = NULL;
+ json_t *j_request_body = NULL;
+ CURL *curlh = NULL;
+
+ GNUNET_assert (0 < awbh->num_input);
+ awbh->age_mask = awbh->blinded_input[0].denom_pub->key.age_mask;
+
+ FAIL_IF (GNUNET_OK !=
+ TALER_amount_set_zero (awbh->keys->currency,
+ &awbh->amount_with_fee));
+ /* Accumulate total value with fees */
+ for (size_t i = 0; i < awbh->num_input; i++)
+ {
+ struct TALER_Amount coin_total;
+ const struct TALER_EXCHANGE_DenomPublicKey *dpub =
+ awbh->blinded_input[i].denom_pub;
+
+ FAIL_IF (0 >
+ TALER_amount_add (&coin_total,
+ &dpub->fees.withdraw,
+ &dpub->value));
+ FAIL_IF (0 >
+ TALER_amount_add (&awbh->amount_with_fee,
+ &awbh->amount_with_fee,
+ &coin_total));
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Attempting to age-withdraw from reserve %s with maximum age %d\n",
+ TALER_B2S (&awbh->reserve_pub),
+ awbh->max_age);
+
+ coins_hctx = GNUNET_CRYPTO_hash_context_start ();
+ FAIL_IF (NULL == coins_hctx);
+
+
+ j_denoms = json_array ();
+ j_array_candidates = json_array ();
+ FAIL_IF ((NULL == j_denoms) ||
+ (NULL == j_array_candidates));
+
+ for (size_t i = 0; i< awbh->num_input; i++)
+ {
+ /* Build the denomination array */
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *denom_pub =
+ awbh->blinded_input[i].denom_pub;
+ const struct TALER_DenominationHashP *denom_h = &denom_pub->h_key;
+ json_t *jdenom;
+
+ /* The mask must be the same for all coins */
+ FAIL_IF (awbh->age_mask.bits != denom_pub->key.age_mask.bits);
+
+ jdenom = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto (NULL,
+ denom_h));
+ FAIL_IF (NULL == jdenom);
+ FAIL_IF (0 > json_array_append_new (j_denoms,
+ jdenom));
+
+ /* Build the candidate array */
+ {
+ json_t *j_can = json_array ();
+ FAIL_IF (NULL == j_can);
+
+ for (size_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ struct TALER_BlindedCoinHashP bch;
+ const struct TALER_PlanchetDetail *planchet =
+ &awbh->blinded_input[i].planchet_details[k];
+ json_t *jc = GNUNET_JSON_PACK (
+ TALER_JSON_pack_blinded_planchet (
+ NULL,
+ &planchet->blinded_planchet));
+
+ FAIL_IF (NULL == jc);
+ FAIL_IF (0 > json_array_append_new (j_can,
+ jc));
+
+ TALER_coin_ev_hash (&planchet->blinded_planchet,
+ &planchet->denom_pub_hash,
+ &bch);
+
+ GNUNET_CRYPTO_hash_context_read (coins_hctx,
+ &bch,
+ sizeof(bch));
+ }
+
+ FAIL_IF (0 > json_array_append_new (j_array_candidates,
+ j_can));
+ }
+ }
+ }
+
+ /* Build the hash of the commitment */
+ GNUNET_CRYPTO_hash_context_finish (coins_hctx,
+ &awbh->h_commitment.hash);
+ coins_hctx = NULL;
+
+ /* Sign the request */
+ TALER_wallet_age_withdraw_sign (&awbh->h_commitment,
+ &awbh->amount_with_fee,
+ &awbh->age_mask,
+ awbh->max_age,
+ awbh->reserve_priv,
+ &awbh->reserve_sig);
+
+ /* Initiate the POST-request */
+ j_request_body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("denom_hs", j_denoms),
+ GNUNET_JSON_pack_array_steal ("blinded_coin_evs", j_array_candidates),
+ GNUNET_JSON_pack_uint64 ("max_age", awbh->max_age),
+ GNUNET_JSON_pack_data_auto ("reserve_sig", &awbh->reserve_sig));
+ FAIL_IF (NULL == j_request_body);
+
+ curlh = TALER_EXCHANGE_curl_easy_get_ (awbh->request_url);
+ FAIL_IF (NULL == curlh);
+ FAIL_IF (GNUNET_OK !=
+ TALER_curl_easy_post (&awbh->post_ctx,
+ curlh,
+ j_request_body));
+ json_decref (j_request_body);
+ j_request_body = NULL;
+
+ awbh->job = GNUNET_CURL_job_add2 (
+ awbh->curl_ctx,
+ curlh,
+ awbh->post_ctx.headers,
+ &handle_reserve_age_withdraw_blinded_finished,
+ awbh);
+ FAIL_IF (NULL == awbh->job);
+
+ /* No errors, return */
+ return;
+
+ERROR:
+ if (NULL != j_denoms)
+ json_decref (j_denoms);
+ if (NULL != j_array_candidates)
+ json_decref (j_array_candidates);
+ if (NULL != j_request_body)
+ json_decref (j_request_body);
+ if (NULL != curlh)
+ curl_easy_cleanup (curlh);
+ if (NULL != coins_hctx)
+ GNUNET_CRYPTO_hash_context_abort (coins_hctx);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh);
+ return;
+#undef FAIL_IF
+}
+
+
+/**
+ * @brief Callback to copy the results from the call to TALER_age_withdraw_blinded
+ * to the result for the originating call from TALER_age_withdraw.
+ *
+ * @param cls struct TALER_AgeWithdrawHandle
+ * @param awbr The response
+ */
+static void
+copy_results (
+ void *cls,
+ const struct TALER_EXCHANGE_AgeWithdrawBlindedResponse *awbr)
+{
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh = cls;
+ uint8_t k = awbr->details.ok.noreveal_index;
+ struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails details[awh->num_coins];
+ struct TALER_BlindedCoinHashP blinded_coin_hs[awh->num_coins];
+ struct TALER_EXCHANGE_AgeWithdrawResponse resp = {
+ .hr = awbr->hr,
+ .details = {
+ .ok = {
+ .noreveal_index = awbr->details.ok.noreveal_index,
+ .h_commitment = awbr->details.ok.h_commitment,
+ .exchange_pub = awbr->details.ok.exchange_pub,
+ .num_coins = awh->num_coins,
+ .coin_details = details,
+ .blinded_coin_hs = blinded_coin_hs
+ },
+ },
+ };
+
+ for (size_t n = 0; n< awh->num_coins; n++)
+ {
+ details[n] = awh->coin_data[n].coin_candidates[k].details;
+ details[n].planchet = awh->coin_data[n].planchet_details[k];
+ blinded_coin_hs[n] = awh->coin_data[n].coin_candidates[k].blinded_coin_h;
+ }
+ awh->callback (awh->callback_cls,
+ &resp);
+ awh->callback = NULL;
+}
+
+
+/**
+ * @brief Prepares and executes TALER_EXCHANGE_age_withdraw_blinded.
+ * If there were CS-denominations involved, started once the all calls
+ * to /csr-withdraw are done.
+ */
+static void
+call_age_withdraw_blinded (
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh)
+{
+ struct TALER_EXCHANGE_AgeWithdrawBlindedInput blinded_input[awh->num_coins];
+
+ /* Prepare the blinded planchets as input */
+ for (size_t n = 0; n < awh->num_coins; n++)
+ {
+ blinded_input[n].denom_pub = &awh->coin_data[n].denom_pub;
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ blinded_input[n].planchet_details[k] =
+ awh->coin_data[n].planchet_details[k];
+ }
+
+ awh->procotol_handle =
+ TALER_EXCHANGE_age_withdraw_blinded (
+ awh->curl_ctx,
+ awh->keys,
+ awh->exchange_url,
+ awh->reserve_priv,
+ awh->max_age,
+ awh->num_coins,
+ blinded_input,
+ copy_results,
+ awh);
+}
+
+
+/**
+ * Prepares the request URL for the age-withdraw request
+ *
+ * @param awbh The handler
+ * @param exchange_url The base-URL to the exchange
+ */
+static
+enum GNUNET_GenericReturnValue
+prepare_url (
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh,
+ const char *exchange_url)
+{
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+ char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &awbh->reserve_pub,
+ sizeof (awbh->reserve_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "reserves/%s/age-withdraw",
+ pub_str);
+
+ awbh->request_url = TALER_url_join (exchange_url,
+ arg_str,
+ NULL);
+ if (NULL == awbh->request_url)
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awbh);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * @brief Function called when CSR withdraw retrieval is finished
+ *
+ * @param cls the `struct CSRClosure *`
+ * @param csrr replies from the /csr-withdraw request
+ */
+static void
+csr_withdraw_done (
+ void *cls,
+ const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr)
+{
+ struct CSRClosure *csr = cls;
+ struct CoinCandidate *can;
+ struct TALER_PlanchetDetail *planchet;
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh;
+
+ GNUNET_assert (NULL != csr);
+ awh = csr->age_withdraw_handle;
+ planchet = csr->planchet;
+ can = csr->candidate;
+
+ GNUNET_assert (NULL != can);
+ GNUNET_assert (NULL != planchet);
+ GNUNET_assert (NULL != awh);
+
+ csr->csr_withdraw_handle = NULL;
+
+ switch (csrr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ {
+ bool success = false;
+ /* Complete the initialization of the coin with CS denomination */
+
+ TALER_denom_ewv_copy (&can->details.alg_values,
+ &csrr->details.ok.alg_values);
+ GNUNET_assert (can->details.alg_values.blinding_inputs->cipher
+ == GNUNET_CRYPTO_BSA_CS);
+ TALER_planchet_setup_coin_priv (&can->secret,
+ &can->details.alg_values,
+ &can->details.coin_priv);
+ TALER_planchet_blinding_secret_create (&can->secret,
+ &can->details.alg_values,
+ &can->details.blinding_key);
+ /* This initializes the 2nd half of the
+ can->planchet_detail.blinded_planchet! */
+ do {
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&csr->denom_pub->key,
+ &can->details.alg_values,
+ &can->details.blinding_key,
+ &csr->nonce,
+ &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;
+ } while (0);
+
+ awh->csr.pending--;
+
+ /* No more pending requests to /csr-withdraw, we can now perform the
+ * actual age-withdraw operation */
+ if (0 == awh->csr.pending && success)
+ call_age_withdraw_blinded (awh);
+ return;
+ }
+ default:
+ break;
+ }
+ TALER_EXCHANGE_age_withdraw_cancel (awh);
+}
+
+
+/**
+ * @brief Prepare the coins for the call to age-withdraw and calculates
+ * the total amount with fees.
+ *
+ * For denomination with CS as cipher, initiates the preflight to retrieve the
+ * csr-parameter via /csr-withdraw.
+ *
+ * @param awh The handler to the age-withdraw
+ * @param num_coins The number of coins in @e coin_inputs
+ * @param coin_inputs The input for the individual coin(-candidates)
+ * @return GNUNET_OK on success, GNUNET_SYSERR on failure
+ */
+static
+enum GNUNET_GenericReturnValue
+prepare_coins (
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh,
+ size_t num_coins,
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput coin_inputs[
+ static num_coins])
+{
+#define FAIL_IF(cond) \
+ do { \
+ if ((cond)) \
+ { \
+ GNUNET_break (! (cond)); \
+ goto ERROR; \
+ } \
+ } while (0)
+
+ GNUNET_assert (0 < num_coins);
+ awh->age_mask = coin_inputs[0].denom_pub->key.age_mask;
+
+ awh->coin_data = GNUNET_new_array (awh->num_coins,
+ struct CoinData);
+
+ for (size_t i = 0; i < num_coins; i++)
+ {
+ struct CoinData *cd = &awh->coin_data[i];
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput *input = &coin_inputs[i];
+
+ cd->denom_pub = *input->denom_pub;
+ /* The mask must be the same for all coins */
+ FAIL_IF (awh->age_mask.bits != input->denom_pub->key.age_mask.bits);
+ TALER_denom_pub_copy (&cd->denom_pub.key,
+ &input->denom_pub->key);
+
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ struct CoinCandidate *can = &cd->coin_candidates[k];
+ struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
+
+ can->secret = input->secrets[k];
+ /* Derive the age restriction from the given secret and
+ * the maximum age */
+ TALER_age_restriction_from_secret (
+ &can->secret,
+ &input->denom_pub->key.age_mask,
+ awh->max_age,
+ &can->details.age_commitment_proof);
+
+ TALER_age_commitment_hash (&can->details.age_commitment_proof.commitment,
+ &can->details.h_age_commitment);
+
+ switch (input->denom_pub->key.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ TALER_denom_ewv_copy (&can->details.alg_values,
+ TALER_denom_ewv_rsa_singleton ());
+ TALER_planchet_setup_coin_priv (&can->secret,
+ &can->details.alg_values,
+ &can->details.coin_priv);
+ TALER_planchet_blinding_secret_create (&can->secret,
+ &can->details.alg_values,
+ &can->details.blinding_key);
+ FAIL_IF (GNUNET_OK !=
+ TALER_planchet_prepare (&cd->denom_pub.key,
+ &can->details.alg_values,
+ &can->details.blinding_key,
+ NULL,
+ &can->details.coin_priv,
+ &can->details.h_age_commitment,
+ &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:
+ {
+ struct CSRClosure *cls = &cd->csr_cls[k];
+ /**
+ * Save the handler and the denomination for the callback
+ * after the call to csr-withdraw */
+ cls->age_withdraw_handle = awh;
+ cls->candidate = can;
+ cls->planchet = planchet;
+ cls->denom_pub = &cd->denom_pub;
+ TALER_cs_withdraw_nonce_derive (
+ &can->secret,
+ &cls->nonce.cs_nonce);
+ cls->csr_withdraw_handle =
+ TALER_EXCHANGE_csr_withdraw (
+ awh->curl_ctx,
+ awh->exchange_url,
+ &cd->denom_pub,
+ &cls->nonce.cs_nonce,
+ &csr_withdraw_done,
+ cls);
+ FAIL_IF (NULL == cls->csr_withdraw_handle);
+
+ awh->csr.pending++;
+ break;
+ }
+ default:
+ FAIL_IF (1);
+ }
+ }
+ }
+ return GNUNET_OK;
+
+ERROR:
+ TALER_EXCHANGE_age_withdraw_cancel (awh);
+ return GNUNET_SYSERR;
+#undef FAIL_IF
+};
+
+struct TALER_EXCHANGE_AgeWithdrawHandle *
+TALER_EXCHANGE_age_withdraw (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ size_t num_coins,
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput coin_inputs[const static
+ num_coins],
+ uint8_t max_age,
+ TALER_EXCHANGE_AgeWithdrawCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh;
+
+ awh = GNUNET_new (struct TALER_EXCHANGE_AgeWithdrawHandle);
+ awh->exchange_url = exchange_url;
+ awh->keys = TALER_EXCHANGE_keys_incref (keys);
+ awh->curl_ctx = curl_ctx;
+ awh->reserve_priv = reserve_priv;
+ awh->callback = res_cb;
+ awh->callback_cls = res_cb_cls;
+ awh->num_coins = num_coins;
+ awh->max_age = max_age;
+
+
+ if (GNUNET_OK != prepare_coins (awh,
+ num_coins,
+ coin_inputs))
+ {
+ GNUNET_free (awh);
+ return NULL;
+ }
+
+ /* If there were no CS denominations, we can now perform the actual
+ * age-withdraw protocol. Otherwise, there are calls to /csr-withdraw
+ * in flight and once they finish, the age-withdraw-protocol will be
+ * called from within the csr_withdraw_done-function.
+ */
+ if (0 == awh->csr.pending)
+ call_age_withdraw_blinded (awh);
+
+ return awh;
+}
+
+
+void
+TALER_EXCHANGE_age_withdraw_cancel (
+ struct TALER_EXCHANGE_AgeWithdrawHandle *awh)
+{
+ /* Cleanup coin data */
+ for (unsigned int i = 0; i<awh->num_coins; i++)
+ {
+ struct CoinData *cd = &awh->coin_data[i];
+
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ struct TALER_PlanchetDetail *planchet = &cd->planchet_details[k];
+ struct CSRClosure *cls = &cd->csr_cls[k];
+ struct CoinCandidate *can = &cd->coin_candidates[k];
+
+ if (NULL != cls->csr_withdraw_handle)
+ {
+ TALER_EXCHANGE_csr_withdraw_cancel (cls->csr_withdraw_handle);
+ cls->csr_withdraw_handle = NULL;
+ }
+ TALER_blinded_planchet_free (&planchet->blinded_planchet);
+ TALER_denom_ewv_free (&can->details.alg_values);
+ }
+ TALER_denom_pub_free (&cd->denom_pub.key);
+ }
+ GNUNET_free (awh->coin_data);
+ TALER_EXCHANGE_keys_decref (awh->keys);
+ TALER_EXCHANGE_age_withdraw_blinded_cancel (awh->procotol_handle);
+ awh->procotol_handle = NULL;
+ GNUNET_free (awh);
+}
+
+
+struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *
+TALER_EXCHANGE_age_withdraw_blinded (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_Keys *keys,
+ const char *exchange_url,
+ const struct TALER_ReservePrivateKeyP *reserve_priv,
+ uint8_t max_age,
+ unsigned int num_input,
+ const struct TALER_EXCHANGE_AgeWithdrawBlindedInput blinded_input[static
+ num_input],
+ TALER_EXCHANGE_AgeWithdrawBlindedCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh =
+ GNUNET_new (struct TALER_EXCHANGE_AgeWithdrawBlindedHandle);
+
+ awbh->num_input = num_input;
+ awbh->blinded_input = blinded_input;
+ awbh->keys = TALER_EXCHANGE_keys_incref (keys);
+ awbh->curl_ctx = curl_ctx;
+ awbh->reserve_priv = reserve_priv;
+ awbh->callback = res_cb;
+ awbh->callback_cls = res_cb_cls;
+ awbh->max_age = max_age;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&awbh->reserve_priv->eddsa_priv,
+ &awbh->reserve_pub.eddsa_pub);
+
+ if (GNUNET_OK != prepare_url (awbh,
+ exchange_url))
+ return NULL;
+
+ perform_protocol (awbh);
+ return awbh;
+}
+
+
+void
+TALER_EXCHANGE_age_withdraw_blinded_cancel (
+ struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh)
+{
+ if (NULL == awbh)
+ return;
+
+ if (NULL != awbh->job)
+ {
+ GNUNET_CURL_job_cancel (awbh->job);
+ awbh->job = NULL;
+ }
+ GNUNET_free (awbh->request_url);
+ TALER_EXCHANGE_keys_decref (awbh->keys);
+ TALER_curl_easy_post_finished (&awbh->post_ctx);
+ GNUNET_free (awbh);
+}
+
+
+/* exchange_api_age_withdraw.c */
diff --git a/src/lib/exchange_api_age_withdraw_reveal.c b/src/lib/exchange_api_age_withdraw_reveal.c
new file mode 100644
index 000000000..cade528d2
--- /dev/null
+++ b/src/lib/exchange_api_age_withdraw_reveal.c
@@ -0,0 +1,477 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_age_withdraw_reveal.c
+ * @brief Implementation of /age-withdraw/$ACH/reveal requests
+ * @author Ă–zgĂĽr Kesim
+ */
+
+#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+/**
+ * Handler for a running age-withdraw-reveal request
+ */
+struct TALER_EXCHANGE_AgeWithdrawRevealHandle
+{
+
+ /* The index not to be disclosed */
+ uint8_t noreveal_index;
+
+ /* The age-withdraw commitment */
+ struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+
+ /* The reserve's public key */
+ const struct TALER_ReservePublicKeyP *reserve_pub;
+
+ /* Number of coins */
+ size_t num_coins;
+
+ /* The @e num_coins * kappa coin secrets from the age-withdraw commitment */
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput *coins_input;
+
+ /* The url for the reveal request */
+ char *request_url;
+
+ /**
+ * CURL handle for the request job.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Post Context
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /* Callback */
+ TALER_EXCHANGE_AgeWithdrawRevealCallback callback;
+
+ /* Reveal */
+ void *callback_cls;
+};
+
+
+/**
+ * We got a 200 OK response for the /age-withdraw/$ACH/reveal operation.
+ * Extract the signed blindedcoins and return it to the caller.
+ *
+ * @param awrh operation handle
+ * @param j_response reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+age_withdraw_reveal_ok (
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh,
+ const json_t *j_response)
+{
+ struct TALER_EXCHANGE_AgeWithdrawRevealResponse response = {
+ .hr.reply = j_response,
+ .hr.http_status = MHD_HTTP_OK,
+ };
+ const json_t *j_sigs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("ev_sigs",
+ &j_sigs),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK != GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (awrh->num_coins != json_array_size (j_sigs))
+ {
+ /* Number of coins generated does not match our expectation */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ struct TALER_BlindedDenominationSignature denom_sigs[awrh->num_coins];
+ json_t *j_sig;
+ size_t n;
+
+ /* Reconstruct the coins and unblind the signatures */
+ json_array_foreach (j_sigs, n, j_sig)
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_blinded_denom_sig (NULL,
+ &denom_sigs[n]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK != GNUNET_JSON_parse (j_sig,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ }
+
+ response.details.ok.num_sigs = awrh->num_coins;
+ response.details.ok.blinded_denom_sigs = denom_sigs;
+ awrh->callback (awrh->callback_cls,
+ &response);
+ /* Make sure the callback isn't called again */
+ awrh->callback = NULL;
+ /* Free resources */
+ for (size_t i = 0; i < awrh->num_coins; i++)
+ TALER_blinded_denom_sig_free (&denom_sigs[i]);
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /age-withdraw/$ACH/reveal request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_AgeWithdrawRevealHandle`
+ * @param response_code The HTTP response code
+ * @param response response data
+ */
+static void
+handle_age_withdraw_reveal_finished (
+ void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh = cls;
+ const json_t *j_response = response;
+ struct TALER_EXCHANGE_AgeWithdrawRevealResponse awr = {
+ .hr.reply = j_response,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ awrh->job = NULL;
+ /* FIXME[oec]: Only handle response-codes that are in the spec */
+ switch (response_code)
+ {
+ case 0:
+ awr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = age_withdraw_reveal_ok (awrh,
+ j_response);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break_op (0);
+ awr.hr.http_status = 0;
+ awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == awrh->callback);
+ TALER_EXCHANGE_age_withdraw_reveal_cancel (awrh);
+ return;
+ }
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ /* only validate reply is well-formed */
+ {
+ uint64_t ptu;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint64 ("legitimization_uuid",
+ &ptu),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j_response,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ awr.hr.http_status = 0;
+ awr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ awr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ GNUNET_break_op (0);
+ /**
+ * This should never happen, as we don't sent any signatures
+ * to the exchange to verify. We should simply pass the JSON reply
+ * to the application
+ **/
+ awr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, the exchange basically just says
+ that it doesn't know this age-withdraw commitment. */
+ awr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* An age commitment for one of the coins did not fulfill
+ * the required maximum age requirement of the corresponding
+ * reserve.
+ * Error code: TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE.
+ */
+ awr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_GONE:
+ /* could happen if denomination was revoked */
+ /* Note: one might want to check /keys for revocation
+ signature here, alas tricky in case our /keys
+ is outdated => left to clients */
+ awr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ awr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ awr.hr.ec = TALER_JSON_get_error_code (j_response);
+ awr.hr.hint = TALER_JSON_get_error_hint (j_response);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange age-withdraw\n",
+ (unsigned int) response_code,
+ (int) awr.hr.ec);
+ break;
+ }
+ awrh->callback (awrh->callback_cls,
+ &awr);
+ TALER_EXCHANGE_age_withdraw_reveal_cancel (awrh);
+}
+
+
+/**
+ * Prepares the request URL for the age-withdraw-reveal request
+ *
+ * @param exchange_url The base-URL to the exchange
+ * @param[in,out] awrh The handler
+ * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
+ */
+static
+enum GNUNET_GenericReturnValue
+prepare_url (
+ const char *exchange_url,
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh)
+{
+ char arg_str[sizeof (struct TALER_AgeWithdrawCommitmentHashP) * 2 + 32];
+ char pub_str[sizeof (struct TALER_AgeWithdrawCommitmentHashP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (&awrh->h_commitment,
+ sizeof (awrh->h_commitment),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "age-withdraw/%s/reveal",
+ pub_str);
+
+ awrh->request_url = TALER_url_join (exchange_url,
+ arg_str,
+ NULL);
+ if (NULL == awrh->request_url)
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_age_withdraw_reveal_cancel (awrh);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Call /age-withdraw/$ACH/reveal
+ *
+ * @param curl_ctx The context for CURL
+ * @param awrh The handler
+ */
+static
+void
+perform_protocol (
+ struct GNUNET_CURL_Context *curl_ctx,
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh)
+{
+ CURL *curlh = NULL;
+ json_t *j_request_body = NULL;
+ json_t *j_array_of_secrets = NULL;
+ json_t *j_secrets = NULL;
+ json_t *j_sec = NULL;
+
+#define FAIL_IF(cond) \
+ do { \
+ if ((cond)) \
+ { \
+ GNUNET_break (! (cond)); \
+ goto ERROR; \
+ } \
+ } while (0)
+
+ j_array_of_secrets = json_array ();
+ FAIL_IF (NULL == j_array_of_secrets);
+
+ for (size_t n = 0; n < awrh->num_coins; n++)
+ {
+ const struct TALER_PlanchetMasterSecretP *secrets =
+ awrh->coins_input[n].secrets;
+
+ j_secrets = json_array ();
+ FAIL_IF (NULL == j_secrets);
+
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ const struct TALER_PlanchetMasterSecretP *secret = &secrets[k];
+ if (awrh->noreveal_index == k)
+ continue;
+
+ j_sec = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto (NULL, secret));
+
+ FAIL_IF (NULL == j_sec);
+ FAIL_IF (0 < json_array_append_new (j_secrets,
+ j_sec));
+ }
+
+ FAIL_IF (0 < json_array_append_new (j_array_of_secrets,
+ j_secrets));
+ }
+ j_request_body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ awrh->reserve_pub),
+ GNUNET_JSON_pack_array_steal ("disclosed_coin_secrets",
+ j_array_of_secrets));
+ FAIL_IF (NULL == j_request_body);
+
+ curlh = TALER_EXCHANGE_curl_easy_get_ (awrh->request_url);
+ FAIL_IF (NULL == curlh);
+ FAIL_IF (GNUNET_OK !=
+ TALER_curl_easy_post (&awrh->post_ctx,
+ curlh,
+ j_request_body));
+ json_decref (j_request_body);
+ j_request_body = NULL;
+
+ awrh->job = GNUNET_CURL_job_add2 (curl_ctx,
+ curlh,
+ awrh->post_ctx.headers,
+ &handle_age_withdraw_reveal_finished,
+ awrh);
+ FAIL_IF (NULL == awrh->job);
+
+ /* No error, return */
+ return;
+
+ERROR:
+ if (NULL != j_sec)
+ json_decref (j_sec);
+ if (NULL != j_secrets)
+ json_decref (j_secrets);
+ if (NULL != j_array_of_secrets)
+ json_decref (j_array_of_secrets);
+ if (NULL != j_request_body)
+ json_decref (j_request_body);
+ if (NULL != curlh)
+ curl_easy_cleanup (curlh);
+ TALER_EXCHANGE_age_withdraw_reveal_cancel (awrh);
+ return;
+#undef FAIL_IF
+}
+
+
+struct TALER_EXCHANGE_AgeWithdrawRevealHandle *
+TALER_EXCHANGE_age_withdraw_reveal (
+ struct GNUNET_CURL_Context *curl_ctx,
+ const char *exchange_url,
+ size_t num_coins,
+ const struct TALER_EXCHANGE_AgeWithdrawCoinInput coins_input[static
+ num_coins],
+ uint8_t noreveal_index,
+ const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ TALER_EXCHANGE_AgeWithdrawRevealCallback reveal_cb,
+ void *reveal_cb_cls)
+{
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh =
+ GNUNET_new (struct TALER_EXCHANGE_AgeWithdrawRevealHandle);
+ awrh->noreveal_index = noreveal_index;
+ awrh->h_commitment = *h_commitment;
+ awrh->num_coins = num_coins;
+ awrh->coins_input = coins_input;
+ awrh->callback = reveal_cb;
+ awrh->callback_cls = reveal_cb_cls;
+ awrh->reserve_pub = reserve_pub;
+
+ if (GNUNET_OK !=
+ prepare_url (exchange_url,
+ awrh))
+ return NULL;
+
+ perform_protocol (curl_ctx, awrh);
+
+ return awrh;
+}
+
+
+void
+TALER_EXCHANGE_age_withdraw_reveal_cancel (
+ struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh)
+{
+ if (NULL != awrh->job)
+ {
+ GNUNET_CURL_job_cancel (awrh->job);
+ awrh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&awrh->post_ctx);
+
+ if (NULL != awrh->request_url)
+ GNUNET_free (awrh->request_url);
+
+ GNUNET_free (awrh);
+}
+
+
+/* exchange_api_age_withdraw_reveal.c */
diff --git a/src/lib/exchange_api_auditor_add_denomination.c b/src/lib/exchange_api_auditor_add_denomination.c
index 71f421d4d..89de0d7f1 100644
--- a/src/lib/exchange_api_auditor_add_denomination.c
+++ b/src/lib/exchange_api_auditor_add_denomination.c
@@ -79,9 +79,9 @@ handle_auditor_add_denomination_finished (void *cls,
{
struct TALER_EXCHANGE_AuditorAddDenominationHandle *ah = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_AuditorAddDenominationResponse adr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
ah->job = NULL;
@@ -90,37 +90,37 @@ handle_auditor_add_denomination_finished (void *cls,
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_GONE:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_PRECONDITION_FAILED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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)
{
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (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) hr.ec,
+ (int) adr.hr.ec,
ah->url);
}
else
{
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.hint = NULL;
+ 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,
@@ -131,7 +131,7 @@ handle_auditor_add_denomination_finished (void *cls,
if (NULL != ah->cb)
{
ah->cb (ah->cb_cls,
- &hr);
+ &adr);
ah->cb = NULL;
}
TALER_EXCHANGE_add_auditor_denomination_cancel (ah);
diff --git a/src/lib/exchange_api_batch_deposit.c b/src/lib/exchange_api_batch_deposit.c
index 39c1c9b4f..3dab64526 100644
--- a/src/lib/exchange_api_batch_deposit.c
+++ b/src/lib/exchange_api_batch_deposit.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ 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
@@ -44,6 +44,39 @@
*/
#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
*/
@@ -51,9 +84,14 @@ struct TALER_EXCHANGE_BatchDepositHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the exchange.
+ */
+ struct TALER_EXCHANGE_Keys *keys;
+
+ /**
+ * Context for our curl request(s).
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct GNUNET_CURL_Context *ctx;
/**
* The url for this request.
@@ -64,7 +102,7 @@ struct TALER_EXCHANGE_BatchDepositHandle
* Context for #TEH_curl_easy_post(). Keeps the data that must
* persist for Curl to make the upload.
*/
- struct TALER_CURL_PostContext ctx;
+ struct TALER_CURL_PostContext post_ctx;
/**
* Handle for the request.
@@ -99,7 +137,7 @@ struct TALER_EXCHANGE_BatchDepositHandle
/**
* Hash over the extensions, or all zero.
*/
- struct TALER_ExtensionContractHashP h_extensions;
+ struct TALER_ExtensionPolicyHashP h_policy;
/**
* Time when this confirmation was generated / when the exchange received
@@ -108,9 +146,24 @@ struct TALER_EXCHANGE_BatchDepositHandle
struct GNUNET_TIME_Timestamp exchange_timestamp;
/**
- * Exchange signatures, set for #auditor_cb.
+ * Exchange signature, set for #auditor_cb.
*/
- struct TALER_ExchangeSignatureP *exchange_sigs;
+ 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.
@@ -118,6 +171,16 @@ struct TALER_EXCHANGE_BatchDepositHandle
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".
*/
@@ -132,26 +195,79 @@ struct TALER_EXCHANGE_BatchDepositHandle
/**
+ * 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 ah handle to the auditor
+ * @param auditor_url base URL of the auditor
* @param auditor_pub public key of the auditor
- * @return NULL if no deposit confirmation interaction was launched
*/
-static struct TEAH_AuditorInteractionEntry *
+static void
auditor_cb (void *cls,
- struct TALER_AUDITOR_Handle *ah,
+ const char *auditor_url,
const struct TALER_AuditorPublicKeyP *auditor_pub)
{
struct TALER_EXCHANGE_BatchDepositHandle *dh = cls;
- const struct TALER_EXCHANGE_Keys *key_state;
const struct TALER_EXCHANGE_SigningPublicKey *spk;
struct TEAH_AuditorInteractionEntry *aie;
- struct TALER_Amount amount_without_fee;
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
- unsigned int coin;
+ 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,
@@ -159,50 +275,47 @@ auditor_cb (void *cls,
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Not providing deposit confirmation to auditor\n");
- return NULL;
+ return;
}
- coin = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
- dh->num_cdds);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Will provide deposit confirmation to auditor `%s'\n",
TALER_B2S (auditor_pub));
- key_state = TALER_EXCHANGE_get_keys (dh->exchange);
- dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
- &dh->cdds[coin].h_denom_pub);
- GNUNET_assert (NULL != dki);
- spk = TALER_EXCHANGE_get_signing_key_info (key_state,
+ spk = TALER_EXCHANGE_get_signing_key_info (dh->keys,
&dh->exchange_pub);
if (NULL == spk)
{
GNUNET_break_op (0);
- return NULL;
+ return;
}
- GNUNET_assert (0 <=
- TALER_amount_subtract (&amount_without_fee,
- &dh->cdds[coin].amount,
- &dki->fees.deposit));
aie = GNUNET_new (struct TEAH_AuditorInteractionEntry);
+ aie->dh = dh;
+ aie->auditor_url = auditor_url;
aie->dch = TALER_AUDITOR_deposit_confirmation (
- ah,
+ dh->ctx,
+ auditor_url,
&dh->h_wire,
- &dh->h_extensions,
+ &dh->h_policy,
&dh->dcd.h_contract_terms,
dh->exchange_timestamp,
dh->dcd.wire_deadline,
dh->dcd.refund_deadline,
- &amount_without_fee,
- &dh->cdds[coin].coin_pub,
+ &dh->total_without_fee,
+ dh->num_cdds,
+ cpubs,
+ csigs,
&dh->dcd.merchant_pub,
&dh->exchange_pub,
- &dh->exchange_sigs[coin],
- &key_state->master_pub,
+ &dh->exchange_sig,
+ &dh->keys->master_pub,
spk->valid_from,
spk->valid_until,
spk->valid_legal,
&spk->master_sig,
- &TEAH_acc_confirmation_cb,
+ &acc_confirmation_cb,
aie);
- return aie;
+ GNUNET_CONTAINER_DLL_insert (dh->ai_head,
+ dh->ai_tail,
+ aie);
}
@@ -221,33 +334,27 @@ handle_deposit_finished (void *cls,
{
struct TALER_EXCHANGE_BatchDepositHandle *dh = cls;
const json_t *j = response;
- struct TALER_EXCHANGE_BatchDepositResult dr = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
- const struct TALER_EXCHANGE_Keys *keys;
+ struct TALER_EXCHANGE_BatchDepositResult *dr = &dh->dr;
dh->job = NULL;
- keys = TALER_EXCHANGE_get_keys (dh->exchange);
+ 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;
+ dr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
{
- const struct TALER_EXCHANGE_Keys *key_state;
- json_t *sigs;
- json_t *sig;
- unsigned int idx;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("exchange_sigs",
- &sigs),
+ 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 (
- GNUNET_JSON_spec_string ("transaction_base_url",
- &dr.details.success.transaction_base_url),
+ TALER_JSON_spec_web_url ("transaction_base_url",
+ &dr->details.ok.transaction_base_url),
NULL),
GNUNET_JSON_spec_timestamp ("exchange_timestamp",
&dh->exchange_timestamp),
@@ -260,118 +367,80 @@ handle_deposit_finished (void *cls,
NULL, NULL))
{
GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ dr->hr.http_status = 0;
+ dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
- if (json_array_size (sigs) != dh->num_cdds)
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- dh->exchange_sigs = GNUNET_new_array (dh->num_cdds,
- struct TALER_ExchangeSignatureP);
- key_state = TALER_EXCHANGE_get_keys (dh->exchange);
if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
+ 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;
+ dr->hr.http_status = 0;
+ dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
break;
}
- json_array_foreach (sigs, idx, sig)
{
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &dh->exchange_sigs[idx]),
- GNUNET_JSON_spec_end ()
- };
- struct TALER_Amount amount_without_fee;
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (sig,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- GNUNET_JSON_parse_free (spec);
- break;
- }
- dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
- &dh->cdds[idx].
- h_denom_pub);
- GNUNET_assert (NULL != dki);
- GNUNET_assert (0 <=
- TALER_amount_subtract (&amount_without_fee,
- &dh->cdds[idx].amount,
- &dki->fees.deposit));
+ 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_extensions,
+ &dh->h_policy,
dh->exchange_timestamp,
dh->dcd.wire_deadline,
dh->dcd.refund_deadline,
- &amount_without_fee,
- &dh->cdds[idx].coin_pub,
+ &dh->total_without_fee,
+ dh->num_cdds,
+ csigs,
&dh->dcd.merchant_pub,
&dh->exchange_pub,
- &dh->exchange_sigs[idx]))
+ &dh->exchange_sig))
{
GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
+ dr->hr.http_status = 0;
+ dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
break;
}
}
- TEAH_get_auditors_for_dc (dh->exchange,
+ TEAH_get_auditors_for_dc (dh->keys,
&auditor_cb,
dh);
}
- dr.details.success.exchange_sigs = dh->exchange_sigs;
- dr.details.success.exchange_pub = &dh->exchange_pub;
- dr.details.success.deposit_timestamp = dh->exchange_timestamp;
- dr.details.success.num_signatures = dh->num_cdds;
+ 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);
+ 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);
+ 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);
+ 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:
{
- const struct TALER_EXCHANGE_Keys *key_state;
- struct TALER_CoinSpendPublicKeyP coin_pub;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &coin_pub),
+ &dr->details.conflict.coin_pub),
GNUNET_JSON_spec_end ()
};
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
- bool found = false;
if (GNUNET_OK !=
GNUNET_JSON_parse (j,
@@ -379,47 +448,12 @@ handle_deposit_finished (void *cls,
NULL, NULL))
{
GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ dr->hr.http_status = 0;
+ dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
- for (unsigned int i = 0; i<dh->num_cdds; i++)
- {
- if (0 !=
- GNUNET_memcmp (&coin_pub,
- &dh->cdds[i].coin_pub))
- continue;
- key_state = TALER_EXCHANGE_get_keys (dh->exchange);
- dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
- &dh->cdds[i].
- h_denom_pub);
- GNUNET_assert (NULL != dki);
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_coin_conflict_ (
- keys,
- j,
- dki,
- &dh->cdds[i].coin_pub,
- &dh->cdds[i].coin_sig,
- &dh->cdds[i].amount))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- found = true;
- break;
- }
- if (! found)
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
+ dr->hr.ec = TALER_JSON_get_error_code (j);
+ dr->hr.hint = TALER_JSON_get_error_hint (j);
}
break;
case MHD_HTTP_GONE:
@@ -427,52 +461,55 @@ handle_deposit_finished (void *cls,
/* 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);
+ 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);
+ 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);
+ 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);
+ dr->hr.ec);
GNUNET_break_op (0);
break;
}
- dh->cb (dh->cb_cls,
- &dr);
- TALER_EXCHANGE_batch_deposit_cancel (dh);
+ if (NULL != dh->ai_head)
+ return;
+ finish_dh (dh);
}
struct TALER_EXCHANGE_BatchDepositHandle *
TALER_EXCHANGE_batch_deposit (
- struct TALER_EXCHANGE_Handle *exchange,
+ 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,
+ const struct TALER_EXCHANGE_CoinDepositDetail cdds[static num_cdds],
TALER_EXCHANGE_BatchDepositResultCallback cb,
void *cb_cls,
enum TALER_ErrorCode *ec)
{
- const struct TALER_EXCHANGE_Keys *key_state;
struct TALER_EXCHANGE_BatchDepositHandle *dh;
- struct GNUNET_CURL_Context *ctx;
json_t *deposit_obj;
json_t *deposits;
CURL *eh;
- struct TALER_Amount amount_without_fee;
+ const struct GNUNET_HashCode *wallet_data_hashp;
- GNUNET_assert (GNUNET_YES ==
- TEAH_handle_is_ready (exchange));
+ if (0 == num_cdds)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
if (GNUNET_TIME_timestamp_cmp (dcd->refund_deadline,
>,
dcd->wire_deadline))
@@ -481,36 +518,39 @@ TALER_EXCHANGE_batch_deposit (
*ec = TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE;
return NULL;
}
- key_state = TALER_EXCHANGE_get_keys (exchange);
dh = GNUNET_new (struct TALER_EXCHANGE_BatchDepositHandle);
dh->auditor_chance = AUDITOR_CHANCE;
- dh->exchange = exchange;
dh->cb = cb;
dh->cb_cls = cb_cls;
dh->cdds = GNUNET_memdup (cdds,
- num_cdds
- * sizeof (*cdds));
+ num_cdds * sizeof (*cdds));
dh->num_cdds = num_cdds;
dh->dcd = *dcd;
- if (NULL != dcd->extension_details)
- TALER_deposit_extension_hash (dcd->extension_details,
- &dh->h_extensions);
+ 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_AgeCommitmentHash *h_age_commitmentp;
+ struct TALER_Amount amount_without_fee;
- dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
+ 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 >
@@ -520,20 +560,18 @@ TALER_EXCHANGE_batch_deposit (
{
*ec = TALER_EC_EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT;
GNUNET_break_op (0);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Amount: %s\n",
- TALER_amount2s (&cdd->amount));
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Fee: %s\n",
- TALER_amount2s (&dki->fees.deposit));
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_extensions,
+ &dh->h_policy,
&dh->h_wire,
cdd,
dki))
@@ -542,8 +580,13 @@ TALER_EXCHANGE_batch_deposit (
GNUNET_break_op (0);
GNUNET_free (dh->cdds);
GNUNET_free (dh);
+ json_decref (deposits);
return NULL;
}
+ 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 (
@@ -559,13 +602,14 @@ TALER_EXCHANGE_batch_deposit (
&cdd->coin_pub),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_data_auto ("h_age_commitment",
- &cdd->h_age_commitment)),
+ h_age_commitmentp)),
GNUNET_JSON_pack_data_auto ("coin_sig",
&cdd->coin_sig)
)));
}
- dh->url = TEAH_path_to_url (exchange,
- "/batch-deposit");
+ dh->url = TALER_url_join (url,
+ "batch-deposit",
+ NULL);
if (NULL == dh->url)
{
GNUNET_break (0);
@@ -573,9 +617,15 @@ TALER_EXCHANGE_batch_deposit (
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 (
GNUNET_JSON_pack_string ("merchant_payto_uri",
dcd->merchant_payto_uri),
@@ -586,10 +636,13 @@ TALER_EXCHANGE_batch_deposit (
GNUNET_JSON_pack_array_steal ("coins",
deposits),
GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_steal ("extension_details",
- NULL)), /* FIXME #7270-Oec */
+ 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->timestamp),
+ dcd->wallet_timestamp),
GNUNET_JSON_pack_data_auto ("merchant_pub",
&dcd->merchant_pub),
GNUNET_JSON_pack_allow_null (
@@ -601,7 +654,7 @@ TALER_EXCHANGE_batch_deposit (
eh = TALER_EXCHANGE_curl_easy_get_ (dh->url);
if ( (NULL == eh) ||
(GNUNET_OK !=
- TALER_curl_easy_post (&dh->ctx,
+ TALER_curl_easy_post (&dh->post_ctx,
eh,
deposit_obj)) )
{
@@ -619,10 +672,11 @@ TALER_EXCHANGE_batch_deposit (
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"URL for deposit: `%s'\n",
dh->url);
- ctx = TEAH_handle_to_context (exchange);
+ dh->ctx = ctx;
+ dh->keys = TALER_EXCHANGE_keys_incref (keys);
dh->job = GNUNET_CURL_job_add2 (ctx,
eh,
- dh->ctx.headers,
+ dh->post_ctx.headers,
&handle_deposit_finished,
dh);
return dh;
@@ -641,15 +695,30 @@ 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);
- GNUNET_free (deposit->exchange_sigs);
- TALER_curl_easy_post_finished (&deposit->ctx);
+ TALER_curl_easy_post_finished (&deposit->post_ctx);
+ json_decref (deposit->response);
GNUNET_free (deposit);
}
diff --git a/src/lib/exchange_api_batch_withdraw.c b/src/lib/exchange_api_batch_withdraw.c
index 4e0d6abcf..a1b21f347 100644
--- a/src/lib/exchange_api_batch_withdraw.c
+++ b/src/lib/exchange_api_batch_withdraw.c
@@ -54,9 +54,14 @@ struct CoinData
const struct TALER_AgeCommitmentHash *ach;
/**
- * blinding secret
+ * blinding secret
*/
- union TALER_DenominationBlindingKeyP bks;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+
+ /**
+ * Session nonce.
+ */
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
/**
* Private key of the coin we are withdrawing.
@@ -69,7 +74,7 @@ struct CoinData
struct TALER_PlanchetDetail pd;
/**
- * Values of the @cipher selected
+ * Values of the cipher selected
*/
struct TALER_ExchangeWithdrawValues alg_values;
@@ -79,7 +84,7 @@ struct CoinData
struct TALER_CoinPubHashP c_hash;
/**
- * Handler for the CS R request (only used for TALER_DENOMINATION_CS denominations)
+ * Handler for the CS R request (only used for GNUNET_CRYPTO_BSA_CS denominations)
*/
struct TALER_EXCHANGE_CsRWithdrawHandle *csrh;
@@ -97,9 +102,19 @@ struct TALER_EXCHANGE_BatchWithdrawHandle
{
/**
- * The connection to exchange this request handle will use
+ * The curl context to use
+ */
+ struct GNUNET_CURL_Context *curl_ctx;
+
+ /**
+ * The base URL to the exchange
+ */
+ const char *exchange_url;
+
+ /**
+ * The /keys information from the exchange
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ const struct TALER_EXCHANGE_Keys *keys;
/**
* Handle for the actual (internal) batch withdraw operation.
@@ -144,37 +159,34 @@ struct TALER_EXCHANGE_BatchWithdrawHandle
* HTTP /reserves/$RESERVE_PUB/batch-withdraw request.
*
* @param cls the `struct TALER_EXCHANGE_BatchWithdrawHandle`
- * @param hr HTTP response data
- * @param blind_sigs array of blind signatures over the coins, NULL on error
- * @param blind_sigs_length length of the @a blind_sigs array
+ * @param bw2r response data
*/
static void
handle_reserve_batch_withdraw_finished (
void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_BlindedDenominationSignature *blind_sigs,
- unsigned int blind_sigs_length)
+ const struct TALER_EXCHANGE_BatchWithdraw2Response *bw2r)
{
struct TALER_EXCHANGE_BatchWithdrawHandle *wh = cls;
struct TALER_EXCHANGE_BatchWithdrawResponse wr = {
- .hr = *hr
+ .hr = bw2r->hr
};
- struct TALER_EXCHANGE_PrivateCoinDetails coins[wh->num_coins];
+ struct TALER_EXCHANGE_PrivateCoinDetails coins[GNUNET_NZL (wh->num_coins)];
wh->wh2 = NULL;
memset (coins,
0,
sizeof (coins));
- if (blind_sigs_length != wh->num_coins)
- {
- GNUNET_break_op (0);
- wr.hr.http_status = 0;
- wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- }
- switch (hr->http_status)
+ switch (bw2r->hr.http_status)
{
case MHD_HTTP_OK:
{
+ if (bw2r->details.ok.blind_sigs_length != wh->num_coins)
+ {
+ GNUNET_break_op (0);
+ wr.hr.http_status = 0;
+ wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
for (unsigned int i = 0; i<wh->num_coins; i++)
{
struct CoinData *cd = &wh->coins[i];
@@ -183,7 +195,7 @@ handle_reserve_batch_withdraw_finished (
if (GNUNET_OK !=
TALER_planchet_to_coin (&cd->pk.key,
- &blind_sigs[i],
+ &bw2r->details.ok.blind_sigs[i],
&cd->bks,
&cd->priv,
cd->ach,
@@ -200,20 +212,24 @@ handle_reserve_batch_withdraw_finished (
coin->sig = fc.sig;
coin->exchange_vals = cd->alg_values;
}
- wr.details.success.coins = coins;
- wr.details.success.num_coins = wh->num_coins;
+ wr.details.ok.coins = coins;
+ wr.details.ok.num_coins = wh->num_coins;
break;
}
case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
{
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint64 ("payment_target_uuid",
- &wr.details.accepted.payment_target_uuid),
+ GNUNET_JSON_spec_fixed_auto (
+ "h_payto",
+ &wr.details.unavailable_for_legal_reasons.h_payto),
+ GNUNET_JSON_spec_uint64 (
+ "requirement_row",
+ &wr.details.unavailable_for_legal_reasons.requirement_row),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
- GNUNET_JSON_parse (hr->reply,
+ GNUNET_JSON_parse (bw2r->hr.reply,
spec,
NULL, NULL))
{
@@ -254,10 +270,12 @@ phase_two (struct TALER_EXCHANGE_BatchWithdrawHandle *wh)
pds[i] = cd->pd;
}
wh->wh2 = TALER_EXCHANGE_batch_withdraw2 (
- wh->exchange,
+ wh->curl_ctx,
+ wh->exchange_url,
+ wh->keys,
wh->reserve_priv,
- pds,
wh->num_coins,
+ pds,
&handle_reserve_batch_withdraw_finished,
wh);
}
@@ -281,30 +299,38 @@ withdraw_cs_stage_two_callback (
};
cd->csrh = NULL;
- GNUNET_assert (TALER_DENOMINATION_CS == cd->pk.key.cipher);
+ GNUNET_assert (GNUNET_CRYPTO_BSA_CS ==
+ cd->pk.key.bsign_pub_key->cipher);
switch (csrr->hr.http_status)
{
case MHD_HTTP_OK:
- cd->alg_values = csrr->details.success.alg_values;
+ GNUNET_assert (NULL ==
+ cd->alg_values.blinding_inputs);
+ TALER_denom_ewv_copy (&cd->alg_values,
+ &csrr->details.ok.alg_values);
TALER_planchet_setup_coin_priv (&cd->ps,
&cd->alg_values,
&cd->priv);
TALER_planchet_blinding_secret_create (&cd->ps,
&cd->alg_values,
&cd->bks);
- /* This initializes the 2nd half of the
- wh->pd.blinded_planchet! */
if (GNUNET_OK !=
TALER_planchet_prepare (&cd->pk.key,
&cd->alg_values,
&cd->bks,
+ &cd->nonce,
&cd->priv,
cd->ach,
&cd->c_hash,
&cd->pd))
{
GNUNET_break (0);
+ wr.hr.http_status = 0;
+ wr.hr.ec = TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR;
+ wh->cb (wh->cb_cls,
+ &wr);
TALER_EXCHANGE_batch_withdraw_cancel (wh);
+ return;
}
wh->cs_pending--;
if (0 == wh->cs_pending)
@@ -321,17 +347,21 @@ withdraw_cs_stage_two_callback (
struct TALER_EXCHANGE_BatchWithdrawHandle *
TALER_EXCHANGE_batch_withdraw (
- struct TALER_EXCHANGE_Handle *exchange,
+ struct GNUNET_CURL_Context *curl_ctx,
+ const char *exchange_url,
+ const struct TALER_EXCHANGE_Keys *keys,
const struct TALER_ReservePrivateKeyP *reserve_priv,
- const struct TALER_EXCHANGE_WithdrawCoinInput *wcis,
unsigned int wci_length,
+ const struct TALER_EXCHANGE_WithdrawCoinInput wcis[static wci_length],
TALER_EXCHANGE_BatchWithdrawCallback res_cb,
void *res_cb_cls)
{
struct TALER_EXCHANGE_BatchWithdrawHandle *wh;
wh = GNUNET_new (struct TALER_EXCHANGE_BatchWithdrawHandle);
- wh->exchange = exchange;
+ wh->curl_ctx = curl_ctx;
+ wh->exchange_url = exchange_url;
+ wh->keys = keys;
wh->cb = res_cb;
wh->cb_cls = res_cb_cls;
wh->reserve_priv = reserve_priv;
@@ -347,58 +377,53 @@ TALER_EXCHANGE_batch_withdraw (
cd->ps = *wci->ps;
cd->ach = wci->ach;
cd->pk = *wci->pk;
- TALER_denom_pub_deep_copy (&cd->pk.key,
- &wci->pk->key);
- switch (wci->pk->key.cipher)
+ TALER_denom_pub_copy (&cd->pk.key,
+ &wci->pk->key);
+ switch (wci->pk->key.bsign_pub_key->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
+ TALER_denom_ewv_copy (&cd->alg_values,
+ TALER_denom_ewv_rsa_singleton ());
+ TALER_planchet_setup_coin_priv (&cd->ps,
+ &cd->alg_values,
+ &cd->priv);
+ TALER_planchet_blinding_secret_create (&cd->ps,
+ &cd->alg_values,
+ &cd->bks);
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&cd->pk.key,
+ &cd->alg_values,
+ &cd->bks,
+ NULL,
+ &cd->priv,
+ cd->ach,
+ &cd->c_hash,
+ &cd->pd))
{
- cd->alg_values.cipher = TALER_DENOMINATION_RSA;
- TALER_planchet_setup_coin_priv (&cd->ps,
- &cd->alg_values,
- &cd->priv);
- TALER_planchet_blinding_secret_create (&cd->ps,
- &cd->alg_values,
- &cd->bks);
- if (GNUNET_OK !=
- TALER_planchet_prepare (&cd->pk.key,
- &cd->alg_values,
- &cd->bks,
- &cd->priv,
- cd->ach,
- &cd->c_hash,
- &cd->pd))
- {
- GNUNET_break (0);
- TALER_EXCHANGE_batch_withdraw_cancel (wh);
- return NULL;
- }
- break;
+ GNUNET_break (0);
+ TALER_EXCHANGE_batch_withdraw_cancel (wh);
+ return NULL;
}
- case TALER_DENOMINATION_CS:
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ TALER_cs_withdraw_nonce_derive (
+ &cd->ps,
+ &cd->nonce.cs_nonce);
+ cd->csrh = TALER_EXCHANGE_csr_withdraw (
+ curl_ctx,
+ exchange_url,
+ &cd->pk,
+ &cd->nonce.cs_nonce,
+ &withdraw_cs_stage_two_callback,
+ cd);
+ if (NULL == cd->csrh)
{
- TALER_cs_withdraw_nonce_derive (
- &cd->ps,
- &cd->pd.blinded_planchet.details.cs_blinded_planchet.nonce);
- /* Note that we only initialize the first half
- of the blinded_planchet here; the other part
- will be done after the /csr-withdraw request! */
- cd->pd.blinded_planchet.cipher = TALER_DENOMINATION_CS;
- cd->csrh = TALER_EXCHANGE_csr_withdraw (
- exchange,
- &cd->pk,
- &cd->pd.blinded_planchet.details.cs_blinded_planchet.nonce,
- &withdraw_cs_stage_two_callback,
- cd);
- if (NULL == cd->csrh)
- {
- GNUNET_break (0);
- TALER_EXCHANGE_batch_withdraw_cancel (wh);
- return NULL;
- }
- wh->cs_pending++;
- break;
+ GNUNET_break (0);
+ TALER_EXCHANGE_batch_withdraw_cancel (wh);
+ return NULL;
}
+ wh->cs_pending++;
+ break;
default:
GNUNET_break (0);
TALER_EXCHANGE_batch_withdraw_cancel (wh);
@@ -424,6 +449,7 @@ TALER_EXCHANGE_batch_withdraw_cancel (
TALER_EXCHANGE_csr_withdraw_cancel (cd->csrh);
cd->csrh = NULL;
}
+ TALER_denom_ewv_free (&cd->alg_values);
TALER_blinded_planchet_free (&cd->pd.blinded_planchet);
TALER_denom_pub_free (&cd->pk.key);
}
diff --git a/src/lib/exchange_api_batch_withdraw2.c b/src/lib/exchange_api_batch_withdraw2.c
index e9da21992..ff1496466 100644
--- a/src/lib/exchange_api_batch_withdraw2.c
+++ b/src/lib/exchange_api_batch_withdraw2.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ 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
@@ -39,14 +39,14 @@ struct TALER_EXCHANGE_BatchWithdraw2Handle
{
/**
- * The connection to exchange this request handle will use
+ * The url for this request.
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ char *url;
/**
- * The url for this request.
+ * The /keys material from the exchange
*/
- char *url;
+ const struct TALER_EXCHANGE_Keys *keys;
/**
* Handle for the request.
@@ -89,8 +89,8 @@ struct TALER_EXCHANGE_BatchWithdraw2Handle
/**
* We got a 200 OK response for the /reserves/$RESERVE_PUB/batch-withdraw operation.
* Extract the coin's signature and return it to the caller. The signature we
- * get from the exchange is for the blinded value. Thus, we first must
- * unblind it and then should verify its validity against our coin's hash.
+ * get from the exchange is for the blinded value. As we do not have the
+ * blinding factor, the signature CANNOT be verified.
*
* If everything checks out, we return the unblinded signature
* to the application via the callback.
@@ -103,14 +103,15 @@ static enum GNUNET_GenericReturnValue
reserve_batch_withdraw_ok (struct TALER_EXCHANGE_BatchWithdraw2Handle *wh,
const json_t *json)
{
- struct TALER_BlindedDenominationSignature blind_sigs[wh->num_coins];
+ struct TALER_BlindedDenominationSignature blind_sigs[GNUNET_NZL (
+ wh->num_coins)];
const json_t *ja = json_object_get (json,
"ev_sigs");
const json_t *j;
- unsigned int index;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = json,
- .http_status = MHD_HTTP_OK
+ size_t index;
+ struct TALER_EXCHANGE_BatchWithdraw2Response bwr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
};
if ( (NULL == ja) ||
@@ -134,17 +135,17 @@ reserve_batch_withdraw_ok (struct TALER_EXCHANGE_BatchWithdraw2Handle *wh,
NULL, NULL))
{
GNUNET_break_op (0);
- for (unsigned int i = 0; i<index; i++)
+ for (size_t i = 0; i<index; i++)
TALER_blinded_denom_sig_free (&blind_sigs[i]);
return GNUNET_SYSERR;
}
}
/* signature is valid, return it to the application */
+ bwr.details.ok.blind_sigs = blind_sigs;
+ bwr.details.ok.blind_sigs_length = wh->num_coins;
wh->cb (wh->cb_cls,
- &hr,
- blind_sigs,
- wh->num_coins);
+ &bwr);
/* make sure callback isn't called again after return */
wh->cb = NULL;
for (unsigned int i = 0; i<wh->num_coins; i++)
@@ -155,101 +156,6 @@ reserve_batch_withdraw_ok (struct TALER_EXCHANGE_BatchWithdraw2Handle *wh,
/**
- * We got a 409 CONFLICT response for the /reserves/$RESERVE_PUB/batch-withdraw operation.
- * Check the signatures on the batch withdraw transactions in the provided
- * history and that the balances add up. We don't do anything directly
- * with the information, as the JSON will be returned to the application.
- * However, our job is ensuring that the exchange followed the protocol, and
- * this in particular means checking all of the signatures in the history.
- *
- * @param wh operation handle
- * @param json reply from the exchange
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static enum GNUNET_GenericReturnValue
-reserve_batch_withdraw_payment_required (
- struct TALER_EXCHANGE_BatchWithdraw2Handle *wh,
- const json_t *json)
-{
- struct TALER_Amount balance;
- struct TALER_Amount total_in_from_history;
- struct TALER_Amount total_out_from_history;
- json_t *history;
- size_t len;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("balance",
- &balance),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- history = json_object_get (json,
- "history");
- if (NULL == history)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- /* go over transaction history and compute
- total incoming and outgoing amounts */
- len = json_array_size (history);
- {
- struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
-
- /* Use heap allocation as "len" may be very big and thus this may
- not fit on the stack. Use "GNUNET_malloc_large" as a malicious
- exchange may theoretically try to crash us by giving a history
- that does not fit into our memory. */
- rhistory = GNUNET_malloc_large (
- sizeof (struct TALER_EXCHANGE_ReserveHistoryEntry)
- * len);
- if (NULL == rhistory)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
-
- if (GNUNET_OK !=
- TALER_EXCHANGE_parse_reserve_history (wh->exchange,
- history,
- &wh->reserve_pub,
- balance.currency,
- &total_in_from_history,
- &total_out_from_history,
- len,
- rhistory))
- {
- GNUNET_break_op (0);
- TALER_EXCHANGE_free_reserve_history (rhistory,
- len);
- return GNUNET_SYSERR;
- }
- TALER_EXCHANGE_free_reserve_history (rhistory,
- len);
- }
-
- /* Check that funds were really insufficient */
- if (0 >= TALER_amount_cmp (&wh->requested_amount,
- &balance))
- {
- /* Requested amount is smaller or equal to reported balance,
- so this should not have failed. */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
* Function called when we're done processing the
* HTTP /reserves/$RESERVE_PUB/batch-withdraw request.
*
@@ -264,16 +170,16 @@ handle_reserve_batch_withdraw_finished (void *cls,
{
struct TALER_EXCHANGE_BatchWithdraw2Handle *wh = cls;
const json_t *j = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
+ struct TALER_EXCHANGE_BatchWithdraw2Response bwr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
};
wh->job = NULL;
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ bwr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
@@ -281,105 +187,91 @@ handle_reserve_batch_withdraw_finished (void *cls,
j))
{
GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ bwr.hr.http_status = 0;
+ bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
GNUNET_assert (NULL == wh->cb);
TALER_EXCHANGE_batch_withdraw2_cancel (wh);
return;
- case MHD_HTTP_ACCEPTED:
- /* only validate reply is well-formed */
- {
- uint64_t ptu;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint64 ("payment_target_uuid",
- &ptu),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ bwr.hr.ec = TALER_JSON_get_error_code (j);
+ bwr.hr.hint = TALER_JSON_get_error_hint (j);
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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ bwr.hr.ec = TALER_JSON_get_error_code (j);
+ bwr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_NOT_FOUND:
/* Nothing really to verify, the exchange basically just says
that it doesn't know this reserve. Can happen if we
query before the wire transfer went through.
We should simply pass the JSON reply to the application. */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ bwr.hr.ec = TALER_JSON_get_error_code (j);
+ bwr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_CONFLICT:
- /* The exchange says that the reserve has insufficient funds;
- check the signatures in the history... */
- if (GNUNET_OK !=
- reserve_batch_withdraw_payment_required (wh,
- j))
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- }
- else
- {
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
- }
+ bwr.hr.ec = TALER_JSON_get_error_code (j);
+ bwr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_GONE:
/* could happen if denomination was revoked */
/* Note: one might want to check /keys for revocation
signature here, alas tricky in case our /keys
is outdated => left to clients */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ bwr.hr.ec = TALER_JSON_get_error_code (j);
+ bwr.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",
+ &bwr.details.unavailable_for_legal_reasons.h_payto),
+ GNUNET_JSON_spec_uint64 ("requirement_row",
+ &bwr.details.unavailable_for_legal_reasons.
+ kyc_requirement_id),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ bwr.hr.http_status = 0;
+ bwr.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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ bwr.hr.ec = TALER_JSON_get_error_code (j);
+ bwr.hr.hint = TALER_JSON_get_error_hint (j);
break;
default:
/* unexpected response code */
GNUNET_break_op (0);
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ bwr.hr.ec = TALER_JSON_get_error_code (j);
+ bwr.hr.hint = TALER_JSON_get_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d for exchange batch withdraw\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) bwr.hr.ec);
break;
}
if (NULL != wh->cb)
{
wh->cb (wh->cb_cls,
- &hr,
- NULL,
- 0);
+ &bwr);
wh->cb = NULL;
}
TALER_EXCHANGE_batch_withdraw2_cancel (wh);
@@ -388,29 +280,25 @@ handle_reserve_batch_withdraw_finished (void *cls,
struct TALER_EXCHANGE_BatchWithdraw2Handle *
TALER_EXCHANGE_batch_withdraw2 (
- struct TALER_EXCHANGE_Handle *exchange,
+ struct GNUNET_CURL_Context *curl_ctx,
+ const char *exchange_url,
+ const struct TALER_EXCHANGE_Keys *keys,
const struct TALER_ReservePrivateKeyP *reserve_priv,
- const struct TALER_PlanchetDetail *pds,
unsigned int pds_length,
+ const struct TALER_PlanchetDetail pds[static pds_length],
TALER_EXCHANGE_BatchWithdraw2Callback res_cb,
void *res_cb_cls)
{
struct TALER_EXCHANGE_BatchWithdraw2Handle *wh;
- const struct TALER_EXCHANGE_Keys *keys;
const struct TALER_EXCHANGE_DenomPublicKey *dk;
struct TALER_ReserveSignatureP reserve_sig;
char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
struct TALER_BlindedCoinHashP bch;
json_t *jc;
- keys = TALER_EXCHANGE_get_keys (exchange);
- if (NULL == keys)
- {
- GNUNET_break (0);
- return NULL;
- }
+ GNUNET_assert (NULL != keys);
wh = GNUNET_new (struct TALER_EXCHANGE_BatchWithdraw2Handle);
- wh->exchange = exchange;
+ wh->keys = keys;
wh->cb = res_cb;
wh->cb_cls = res_cb_cls;
wh->num_coins = pds_length;
@@ -431,14 +319,15 @@ TALER_EXCHANGE_batch_withdraw2 (
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/reserves/%s/batch-withdraw",
+ "reserves/%s/batch-withdraw",
pub_str);
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Attempting to batch-withdraw from reserve %s\n",
TALER_B2S (&wh->reserve_pub));
- wh->url = TEAH_path_to_url (exchange,
- arg_str);
+ wh->url = TALER_url_join (exchange_url,
+ arg_str,
+ NULL);
if (NULL == wh->url)
{
GNUNET_break (0);
@@ -485,16 +374,9 @@ TALER_EXCHANGE_batch_withdraw2 (
json_decref (jc);
return NULL;
}
- if (GNUNET_OK !=
- TALER_coin_ev_hash (&pd->blinded_planchet,
- &pd->denom_pub_hash,
- &bch))
- {
- GNUNET_break (0);
- TALER_EXCHANGE_batch_withdraw2_cancel (wh);
- json_decref (jc);
- return NULL;
- }
+ TALER_coin_ev_hash (&pd->blinded_planchet,
+ &pd->denom_pub_hash,
+ &bch);
TALER_wallet_withdraw_sign (&pd->denom_pub_hash,
&coin_total,
&bch,
@@ -514,13 +396,11 @@ TALER_EXCHANGE_batch_withdraw2 (
}
{
CURL *eh;
- struct GNUNET_CURL_Context *ctx;
json_t *req;
req = GNUNET_JSON_PACK (
GNUNET_JSON_pack_array_steal ("planchets",
jc));
- ctx = TEAH_handle_to_context (exchange);
eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
if ( (NULL == eh) ||
(GNUNET_OK !=
@@ -536,7 +416,7 @@ TALER_EXCHANGE_batch_withdraw2 (
return NULL;
}
json_decref (req);
- wh->job = GNUNET_CURL_job_add2 (ctx,
+ wh->job = GNUNET_CURL_job_add2 (curl_ctx,
eh,
wh->post_ctx.headers,
&handle_reserve_batch_withdraw_finished,
diff --git a/src/lib/exchange_api_coins_history.c b/src/lib/exchange_api_coins_history.c
new file mode 100644
index 000000000..0999e185e
--- /dev/null
+++ b/src/lib/exchange_api_coins_history.c
@@ -0,0 +1,1230 @@
+/*
+ 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 "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_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /coins/$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[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &rh->details.deposit.sig),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &rh->details.deposit.h_contract_terms),
+ 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_fixed_auto ("h_wire",
+ &rh->details.deposit.h_wire),
+ 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_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_policy",
+ &rh->details.deposit.h_policy),
+ &rh->details.deposit.no_h_policy),
+ 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),
+ 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_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[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &rh->details.melt.sig),
+ GNUNET_JSON_spec_fixed_auto ("rc",
+ &rh->details.melt.rc),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &rh->details.melt.h_age_commitment),
+ &rh->details.melt.no_hac),
+ TALER_JSON_spec_amount_any ("melt_fee",
+ &rh->details.melt.melt_fee),
+ 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 ("merchant_sig",
+ &rh->details.refund.sig),
+ 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_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[] = {
+ GNUNET_JSON_spec_fixed_auto ("purse_pub",
+ &rh->details.purse_deposit.purse_pub),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &rh->details.purse_deposit.coin_sig),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &rh->details.purse_deposit.phac),
+ NULL),
+ TALER_JSON_spec_web_url ("exchange_base_url",
+ &rh->details.purse_deposit.exchange_base_url),
+ GNUNET_JSON_spec_bool ("refunded",
+ &rh->details.purse_deposit.refunded),
+ 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 ("purse_pub",
+ &rh->details.purse_refund.purse_pub),
+ 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_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_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
+/**
+ * FIXME-Oec: 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: 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: 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_common.c b/src/lib/exchange_api_common.c
index b64053f24..bd731ad37 100644
--- a/src/lib/exchange_api_common.c
+++ b/src/lib/exchange_api_common.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2022 Taler Systems SA
+ 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
@@ -27,1359 +27,6 @@
#include "taler_signatures.h"
-/**
- * Context for history entry helpers.
- */
-struct HistoryParseContext
-{
-
- /**
- * Exchange we use.
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
- * 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)
-{
- const char *wire_url;
- 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",
- &timestamp),
- GNUNET_JSON_spec_string ("sender_account_url",
- &wire_url),
- 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 = GNUNET_strdup (wire_url);
- rh->details.in_details.wire_reference = wire_reference;
- rh->details.in_details.timestamp = timestamp;
- return GNUNET_OK;
-}
-
-
-/**
- * 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_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
- struct HistoryParseContext *uc,
- const json_t *transaction)
-{
- struct TALER_ReserveSignatureP sig;
- struct TALER_DenominationHashP h_denom_pub;
- struct TALER_BlindedCoinHashP bch;
- struct TALER_Amount withdraw_fee;
- struct GNUNET_JSON_Specification withdraw_spec[] = {
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &sig),
- TALER_JSON_spec_amount_any ("withdraw_fee",
- &withdraw_fee),
- GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
- &h_denom_pub),
- GNUNET_JSON_spec_fixed_auto ("h_coin_envelope",
- &bch),
- 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;
- }
-
- /* Check that the signature is a valid withdraw request */
- if (GNUNET_OK !=
- TALER_wallet_withdraw_verify (&h_denom_pub,
- &rh->amount,
- &bch,
- uc->reserve_pub,
- &sig))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
- /* check that withdraw fee matches expectations! */
- {
- const struct TALER_EXCHANGE_Keys *key_state;
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
-
- key_state = TALER_EXCHANGE_get_keys (uc->exchange);
- dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
- &h_denom_pub);
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&withdraw_fee,
- &dki->fees.withdraw)) ||
- (0 !=
- TALER_amount_cmp (&withdraw_fee,
- &dki->fees.withdraw)) )
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
- rh->details.withdraw.fee = withdraw_fee;
- }
- 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 (&sig,
- sizeof (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;
- }
- 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 = TALER_EXCHANGE_get_keys (uc->exchange);
- 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 GNUNET_JSON_Specification closing_spec[] = {
- GNUNET_JSON_spec_string (
- "receiver_account_details",
- &rh->details.close_details.receiver_account_details),
- 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_CLOSE;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- closing_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- key_state = TALER_EXCHANGE_get_keys (uc->exchange);
- 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,
- rh->details.close_details.receiver_account_details,
- &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;
- }
- 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 "history" 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_history (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
- struct HistoryParseContext *uc,
- const json_t *transaction)
-{
- struct GNUNET_JSON_Specification history_spec[] = {
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &rh->details.history_details.reserve_sig),
- GNUNET_JSON_spec_timestamp ("request_timestamp",
- &rh->details.history_details.request_timestamp),
- GNUNET_JSON_spec_end ()
- };
-
- rh->type = TALER_EXCHANGE_RTT_HISTORY;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- history_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_wallet_reserve_history_verify (
- rh->details.history_details.request_timestamp,
- &rh->amount,
- uc->reserve_pub,
- &rh->details.history_details.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;
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_parse_reserve_history (
- struct TALER_EXCHANGE_Handle *exchange,
- 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)
-{
- const struct
- {
- const char *type;
- ParseHelper helper;
- } map[] = {
- { "CREDIT", &parse_credit },
- { "WITHDRAW", &parse_withdraw },
- { "RECOUP", &parse_recoup },
- { "MERGE", &parse_merge },
- { "CLOSING", &parse_closing },
- { "HISTORY", &parse_history },
- { NULL, NULL }
- };
- struct GNUNET_HashCode uuid[history_length];
- struct HistoryParseContext uc = {
- .exchange = exchange,
- .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;
-}
-
-
-void
-TALER_EXCHANGE_free_reserve_history (
- struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory,
- unsigned int len)
-{
- for (unsigned int i = 0; i<len; i++)
- {
- switch (rhistory[i].type)
- {
- case TALER_EXCHANGE_RTT_CREDIT:
- GNUNET_free (rhistory[i].details.in_details.sender_url);
- break;
- case TALER_EXCHANGE_RTT_WITHDRAWAL:
- break;
- case TALER_EXCHANGE_RTT_RECOUP:
- break;
- case TALER_EXCHANGE_RTT_CLOSE:
- break;
- case TALER_EXCHANGE_RTT_HISTORY:
- break;
- case TALER_EXCHANGE_RTT_MERGE:
- break;
- }
- }
- GNUNET_free (rhistory);
-}
-
-
-/**
- * Context for coin helpers.
- */
-struct CoinHistoryParseContext
-{
-
- /**
- * 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 rtotal;
-
- /**
- * Total amount encountered.
- */
- struct TALER_Amount *total;
-
-};
-
-
-/**
- * Signature of functions that operate on one of
- * the coin's history entries.
- *
- * @param[in,out] pc overall context
- * @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,
- const struct TALER_Amount *amount,
- json_t *transaction);
-
-
-/**
- * Handle deposit entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @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,
- const struct TALER_Amount *amount,
- json_t *transaction)
-{
- struct TALER_MerchantWireHashP h_wire;
- struct TALER_PrivateContractHashP h_contract_terms;
- // struct TALER_ExtensionContractHashP h_extensions; // FIXME #7270!
- struct GNUNET_TIME_Timestamp wallet_timestamp;
- struct TALER_MerchantPublicKeyP merchant_pub;
- struct GNUNET_TIME_Timestamp refund_deadline = {0};
- struct TALER_CoinSpendSignatureP sig;
- struct TALER_AgeCommitmentHash hac;
- bool no_hac;
- struct TALER_Amount deposit_fee;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &sig),
- GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
- &h_contract_terms),
- GNUNET_JSON_spec_fixed_auto ("h_wire",
- &h_wire),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
- &hac),
- &no_hac),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &wallet_timestamp),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("refund_deadline",
- &refund_deadline),
- NULL),
- TALER_JSON_spec_amount_any ("deposit_fee",
- &deposit_fee),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &merchant_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_wallet_deposit_verify (
- amount,
- &deposit_fee,
- &h_wire,
- &h_contract_terms,
- no_hac ? NULL : &hac,
- NULL /* h_extensions! */,
- &pc->dk->h_key,
- wallet_timestamp,
- &merchant_pub,
- refund_deadline,
- pc->coin_pub,
- &sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (NULL != pc->dk)
- {
- /* check that deposit fee matches our expectations from /keys! */
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&deposit_fee,
- &pc->dk->fees.deposit)) ||
- (0 !=
- TALER_amount_cmp (&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 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,
- const struct TALER_Amount *amount,
- json_t *transaction)
-{
- struct TALER_CoinSpendSignatureP sig;
- struct TALER_RefreshCommitmentP rc;
- struct TALER_AgeCommitmentHash h_age_commitment;
- bool no_hac;
- struct TALER_Amount melt_fee;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &sig),
- GNUNET_JSON_spec_fixed_auto ("rc",
- &rc),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
- &h_age_commitment),
- &no_hac),
- TALER_JSON_spec_amount_any ("melt_fee",
- &melt_fee),
- 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 (&melt_fee,
- &pc->dk->fees.refresh)) ||
- (0 !=
- TALER_amount_cmp (&melt_fee,
- &pc->dk->fees.refresh)) )
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_wallet_melt_verify (
- amount,
- &melt_fee,
- &rc,
- &pc->dk->h_key,
- no_hac
- ? NULL
- : &h_age_commitment,
- pc->coin_pub,
- &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 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,
- const struct TALER_Amount *amount,
- json_t *transaction)
-{
- struct TALER_PrivateContractHashP h_contract_terms;
- struct TALER_MerchantPublicKeyP merchant_pub;
- struct TALER_MerchantSignatureP sig;
- struct TALER_Amount refund_fee;
- struct TALER_Amount sig_amount;
- uint64_t rtransaction_id;
- struct GNUNET_JSON_Specification spec[] = {
- 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 (transaction,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (0 >
- TALER_amount_add (&sig_amount,
- &refund_fee,
- amount))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_merchant_refund_verify (pc->coin_pub,
- &h_contract_terms,
- rtransaction_id,
- &sig_amount,
- &merchant_pub,
- &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 (&refund_fee,
- &pc->dk->fees.refund)) ||
- (0 !=
- TALER_amount_cmp (&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 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,
- const struct TALER_Amount *amount,
- json_t *transaction)
-{
- struct TALER_ReservePublicKeyP reserve_pub;
- struct GNUNET_TIME_Timestamp timestamp;
- union TALER_DenominationBlindingKeyP coin_bks;
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct TALER_ExchangeSignatureP exchange_sig;
- struct TALER_CoinSpendSignatureP coin_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_fixed_auto ("reserve_pub",
- &reserve_pub),
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &coin_sig),
- GNUNET_JSON_spec_fixed_auto ("coin_blind",
- &coin_bks),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &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 (
- timestamp,
- amount,
- pc->coin_pub,
- &reserve_pub,
- &exchange_pub,
- &exchange_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_wallet_recoup_verify (&pc->dk->h_key,
- &coin_bks,
- pc->coin_pub,
- &coin_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_YES;
-}
-
-
-/**
- * Handle recoup-refresh entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @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,
- const struct TALER_Amount *amount,
- json_t *transaction)
-{
- /* This is the coin that was subjected to a recoup,
- the value being credited to the old coin. */
- struct TALER_CoinSpendPublicKeyP old_coin_pub;
- union TALER_DenominationBlindingKeyP coin_bks;
- struct GNUNET_TIME_Timestamp timestamp;
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct TALER_ExchangeSignatureP exchange_sig;
- struct TALER_CoinSpendSignatureP coin_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_fixed_auto ("coin_sig",
- &coin_sig),
- GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
- &old_coin_pub),
- GNUNET_JSON_spec_fixed_auto ("coin_blind",
- &coin_bks),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &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 (
- timestamp,
- amount,
- pc->coin_pub,
- &old_coin_pub,
- &exchange_pub,
- &exchange_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_wallet_recoup_verify (&pc->dk->h_key,
- &coin_bks,
- pc->coin_pub,
- &coin_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_YES;
-}
-
-
-/**
- * Handle old coin recoup entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @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,
- const struct TALER_Amount *amount,
- json_t *transaction)
-{
- /* This is the coin that was credited in a recoup,
- the value being credited to the this coin. */
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct TALER_ExchangeSignatureP exchange_sig;
- struct TALER_CoinSpendPublicKeyP new_coin_pub;
- struct GNUNET_TIME_Timestamp timestamp;
- 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 ("coin_pub",
- &new_coin_pub),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &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 (
- timestamp,
- amount,
- &new_coin_pub,
- pc->coin_pub,
- &exchange_pub,
- &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 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,
- const struct TALER_Amount *amount,
- json_t *transaction)
-{
- struct TALER_PurseContractPublicKeyP purse_pub;
- struct TALER_CoinSpendSignatureP coin_sig;
- const char *exchange_base_url;
- bool refunded;
- struct TALER_AgeCommitmentHash phac = { 0 };
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("purse_pub",
- &purse_pub),
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &coin_sig),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
- &coin_sig),
- NULL),
- GNUNET_JSON_spec_string ("exchange_base_url",
- &exchange_base_url),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
- &phac),
- NULL),
- GNUNET_JSON_spec_bool ("refunded",
- &refunded),
- 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 (
- exchange_base_url,
- &purse_pub,
- amount,
- &pc->dk->h_key,
- &phac,
- pc->coin_pub,
- &coin_sig))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (refunded)
- {
- /* We add the amount to refunds here, the original
- deposit will be added to the balance later because
- we still return GNUNET_YES, thus effectively
- cancelling out this operation with respect to
- the final balance. */
- if (0 >
- TALER_amount_add (&pc->rtotal,
- &pc->rtotal,
- amount))
- {
- /* overflow in refund history? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
- return GNUNET_YES;
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_verify_coin_history (
- const struct TALER_EXCHANGE_DenomPublicKey *dk,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- json_t *history,
- struct TALER_Amount *total)
-{
- const char *currency = dk->value.currency;
- const struct
- {
- const char *type;
- CoinCheckHelper helper;
- } map[] = {
- { "DEPOSIT", &help_deposit },
- { "MELT", &help_melt },
- { "REFUND", &help_refund },
- { "RECOUP", &help_recoup },
- { "RECOUP-REFRESH", &help_recoup_refresh },
- { "OLD-COIN-RECOUP", &help_old_coin_recoup },
- { "PURSE-DEPOSIT", &help_purse_deposit },
- { NULL, NULL }
- };
- struct CoinHistoryParseContext pc = {
- .dk = dk,
- .coin_pub = coin_pub,
- .total = total
- };
- 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;
- }
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (currency,
- total));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (currency,
- &pc.rtotal));
- for (size_t off = 0; off<len; off++)
- {
- enum GNUNET_GenericReturnValue add;
- json_t *transaction;
- struct TALER_Amount amount;
- const char *type;
- struct GNUNET_JSON_Specification spec_glob[] = {
- TALER_JSON_spec_amount_any ("amount",
- &amount),
- GNUNET_JSON_spec_string ("type",
- &type),
- GNUNET_JSON_spec_end ()
- };
-
- transaction = json_array_get (history,
- off);
- 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 (&amount,
- &pc.rtotal))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Operation of type %s with amount %s\n",
- type,
- TALER_amount2s (&amount));
- add = GNUNET_SYSERR;
- for (unsigned int i = 0; NULL != map[i].type; i++)
- {
- if (0 == strcasecmp (type,
- map[i].type))
- {
- add = map[i].helper (&pc,
- &amount,
- transaction);
- break;
- }
- }
- switch (add)
- {
- case GNUNET_SYSERR:
- /* entry type not supported, new version on server? */
- 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 added to the total */
- if (0 >
- TALER_amount_add (total,
- total,
- &amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- break;
- case GNUNET_NO:
- /* This amount should be subtracted from the total.
-
- However, for the implementation, we first *add* up all of
- these negative amounts, as we might get refunds before
- deposits from a semi-evil exchange. Then, at the end, we do
- the subtraction by calculating "total = total - rtotal" */
- if (0 >
- TALER_amount_add (&pc.rtotal,
- &pc.rtotal,
- &amount))
- {
- /* overflow in refund history? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- break;
- } /* end of switch(add) */
- }
- /* Finally, subtract 'rtotal' from total to handle the subtractions */
- if (0 >
- TALER_amount_subtract (total,
- total,
- &pc.rtotal))
- {
- /* underflow in history? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
const struct TALER_EXCHANGE_SigningPublicKey *
TALER_EXCHANGE_get_signing_key_info (
const struct TALER_EXCHANGE_Keys *keys,
@@ -1458,50 +105,6 @@ TALER_EXCHANGE_check_purse_create_conflict_ (
}
-static char *
-make_payto (const char *exchange_url,
- const struct TALER_ReservePublicKeyP *reserve_pub)
-{
- char pub_str[sizeof (*reserve_pub) * 2];
- char *end;
- bool is_http;
- char *reserve_url;
-
- end = GNUNET_STRINGS_data_to_string (
- reserve_pub,
- sizeof (*reserve_pub),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- if (0 == strncmp (exchange_url,
- "http://",
- strlen ("http://")))
- {
- is_http = true;
- exchange_url = &exchange_url[strlen ("http://")];
- }
- else if (0 == strncmp (exchange_url,
- "https://",
- strlen ("https://")))
- {
- is_http = false;
- exchange_url = &exchange_url[strlen ("https://")];
- }
- else
- {
- GNUNET_break (0);
- return NULL;
- }
- /* exchange_url includes trailing '/' */
- GNUNET_asprintf (&reserve_url,
- "payto://%s/%s%s",
- is_http ? "taler+http" : "taler",
- exchange_url,
- pub_str);
- return reserve_url;
-}
-
-
enum GNUNET_GenericReturnValue
TALER_EXCHANGE_check_purse_merge_conflict_ (
const struct TALER_PurseMergeSignatureP *cmerge_sig,
@@ -1516,7 +119,7 @@ TALER_EXCHANGE_check_purse_merge_conflict_ (
struct TALER_ReservePublicKeyP reserve_pub;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("partner_url",
+ TALER_JSON_spec_web_url ("partner_url",
&partner_url),
NULL),
GNUNET_JSON_spec_timestamp ("merge_timestamp",
@@ -1539,8 +142,8 @@ TALER_EXCHANGE_check_purse_merge_conflict_ (
}
if (NULL == partner_url)
partner_url = exchange_url;
- payto_uri = make_payto (partner_url,
- &reserve_pub);
+ payto_uri = TALER_reserve_make_payto (partner_url,
+ &reserve_pub);
if (GNUNET_OK !=
TALER_wallet_purse_merge_verify (
payto_uri,
@@ -1588,7 +191,7 @@ TALER_EXCHANGE_check_purse_coin_conflict_ (
GNUNET_JSON_spec_fixed_auto ("coin_pub",
coin_pub),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("partner_url",
+ TALER_JSON_spec_web_url ("partner_url",
&partner_url),
NULL),
TALER_JSON_spec_amount_any ("amount",
@@ -1672,120 +275,7 @@ TALER_EXCHANGE_check_purse_econtract_conflict_ (
}
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_check_coin_amount_conflict_ (
- const struct TALER_EXCHANGE_Keys *keys,
- const json_t *proof,
- struct TALER_CoinSpendPublicKeyP *coin_pub,
- struct TALER_Amount *remaining)
-{
- json_t *history;
- struct TALER_Amount total;
- struct TALER_DenominationHashP h_denom_pub;
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- coin_pub),
- GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
- &h_denom_pub),
- GNUNET_JSON_spec_json ("history",
- &history),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (proof,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- dki = TALER_EXCHANGE_get_denomination_key_by_hash (
- keys,
- &h_denom_pub);
- if (NULL == dki)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGE_verify_coin_history (dki,
- coin_pub,
- history,
- &total))
- {
- GNUNET_break_op (0);
- json_decref (history);
- return GNUNET_SYSERR;
- }
- json_decref (history);
- if (0 >
- TALER_amount_subtract (remaining,
- &dki->value,
- &total))
- {
- /* Strange 'proof': coin was double-spent
- before our transaction?! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Verify that @a coin_sig does NOT appear in
- * the history of @a proof and thus whatever transaction
- * is authorized by @a coin_sig is a conflict with
- * @a proof.
- *
- * @param proof a proof to check
- * @param coin_sig signature that must not be in @a proof
- * @return #GNUNET_OK if @a coin_sig is not in @a proof
- */
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_check_coin_signature_conflict_ (
- const json_t *proof,
- const struct TALER_CoinSpendSignatureP *coin_sig)
-{
- json_t *history;
- size_t off;
- json_t *entry;
-
- history = json_object_get (proof,
- "history");
- if (NULL == history)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- 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;
-}
-
-
+// FIXME: should be used...
enum GNUNET_GenericReturnValue
TALER_EXCHANGE_check_coin_denomination_conflict_ (
const json_t *proof,
@@ -1819,104 +309,6 @@ TALER_EXCHANGE_check_coin_denomination_conflict_ (
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_CoinSpendSignatureP *coin_sig,
- 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:
- {
- struct TALER_Amount left;
- struct TALER_CoinSpendPublicKeyP pcoin_pub;
-
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_coin_amount_conflict_ (
- keys,
- proof,
- &pcoin_pub,
- &left))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (0 !=
- GNUNET_memcmp (&pcoin_pub,
- coin_pub))
- {
- /* conflict is for a different coin! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (-1 !=
- TALER_amount_cmp (&left,
- required))
- {
- /* Balance was sufficient after all; recoup MAY have still been possible */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_coin_signature_conflict_ (
- proof,
- coin_sig))
- {
- /* Not a conflicting transaction: ours is included! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- break;
- }
- case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
- {
- struct TALER_Amount left;
- struct TALER_CoinSpendPublicKeyP pcoin_pub;
-
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_coin_amount_conflict_ (
- keys,
- proof,
- &pcoin_pub,
- &left))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (0 !=
- GNUNET_memcmp (&pcoin_pub,
- coin_pub))
- {
- /* conflict is for a different coin! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_coin_denomination_conflict_ (
- proof,
- &dk->h_key))
- {
- /* Eh, same denomination, hence no conflict */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- break;
- }
- default:
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-enum GNUNET_GenericReturnValue
TALER_EXCHANGE_get_min_denomination_ (
const struct TALER_EXCHANGE_Keys *keys,
struct TALER_Amount *min)
@@ -1949,7 +341,7 @@ TALER_EXCHANGE_get_min_denomination_ (
enum GNUNET_GenericReturnValue
TALER_EXCHANGE_verify_deposit_signature_ (
const struct TALER_EXCHANGE_DepositContractDetail *dcd,
- const struct TALER_ExtensionContractHashP *ech,
+ const struct TALER_ExtensionPolicyHashP *ech,
const struct TALER_MerchantWireHashP *h_wire,
const struct TALER_EXCHANGE_CoinDepositDetail *cdd,
const struct TALER_EXCHANGE_DenomPublicKey *dki)
@@ -1959,10 +351,11 @@ TALER_EXCHANGE_verify_deposit_signature_ (
&dki->fees.deposit,
h_wire,
&dcd->h_contract_terms,
+ &dcd->wallet_data_hash,
&cdd->h_age_commitment,
ech,
&cdd->h_denom_pub,
- dcd->timestamp,
+ dcd->wallet_timestamp,
&dcd->merchant_pub,
dcd->refund_deadline,
&cdd->coin_pub,
@@ -2008,4 +401,253 @@ TALER_EXCHANGE_verify_deposit_signature_ (
}
+/**
+ * Parse account restriction in @a jrest into @a rest.
+ *
+ * @param jresta array of account restrictions in JSON
+ * @param[out] resta_len set to length of @a resta
+ * @param[out] resta account restriction array to set
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_restrictions (const json_t *jresta,
+ unsigned int *resta_len,
+ struct TALER_EXCHANGE_AccountRestriction **resta)
+{
+ size_t alen;
+
+ if (! json_is_array (jresta))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ alen = json_array_size (jresta);
+ if (0 == alen)
+ {
+ /* no restrictions, perfectly OK */
+ *resta = NULL;
+ return GNUNET_OK;
+ }
+ *resta_len = (unsigned int) alen;
+ GNUNET_assert (alen == *resta_len);
+ *resta = GNUNET_new_array (*resta_len,
+ struct TALER_EXCHANGE_AccountRestriction);
+ for (unsigned int i = 0; i<*resta_len; i++)
+ {
+ const json_t *jr = json_array_get (jresta,
+ i);
+ struct TALER_EXCHANGE_AccountRestriction *ar = &(*resta)[i];
+ const char *type = json_string_value (json_object_get (jr,
+ "type"));
+
+ if (NULL == type)
+ {
+ GNUNET_break (0);
+ goto fail;
+ }
+ if (0 == strcmp (type,
+ "deny"))
+ {
+ ar->type = TALER_EXCHANGE_AR_DENY;
+ continue;
+ }
+ if (0 == strcmp (type,
+ "regex"))
+ {
+ const char *regex;
+ const char *hint;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string (
+ "payto_regex",
+ &regex),
+ GNUNET_JSON_spec_string (
+ "human_hint",
+ &hint),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_json (
+ "human_hint_i18n",
+ &ar->details.regex.human_hint_i18n),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jr,
+ spec,
+ NULL, NULL))
+ {
+ /* bogus reply */
+ GNUNET_break_op (0);
+ goto fail;
+ }
+ ar->type = TALER_EXCHANGE_AR_REGEX;
+ ar->details.regex.posix_egrep = GNUNET_strdup (regex);
+ ar->details.regex.human_hint = GNUNET_strdup (hint);
+ continue;
+ }
+ /* unsupported type */
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+fail:
+ GNUNET_free (*resta);
+ *resta_len = 0;
+ return GNUNET_SYSERR;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_parse_accounts (
+ const struct TALER_MasterPublicKeyP *master_pub,
+ const json_t *accounts,
+ unsigned int was_length,
+ struct TALER_EXCHANGE_WireAccount was[static was_length])
+{
+ memset (was,
+ 0,
+ sizeof (struct TALER_EXCHANGE_WireAccount) * was_length);
+ GNUNET_assert (was_length ==
+ json_array_size (accounts));
+ for (unsigned int i = 0;
+ i<was_length;
+ i++)
+ {
+ struct TALER_EXCHANGE_WireAccount *wa = &was[i];
+ const char *payto_uri;
+ const char *conversion_url = NULL;
+ const char *bank_label = NULL;
+ int64_t priority = 0;
+ const json_t *credit_restrictions;
+ const json_t *debit_restrictions;
+ struct GNUNET_JSON_Specification spec_account[] = {
+ TALER_JSON_spec_payto_uri ("payto_uri",
+ &payto_uri),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("conversion_url",
+ &conversion_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_int64 ("priority",
+ &priority),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("bank_label",
+ &bank_label),
+ NULL),
+ GNUNET_JSON_spec_array_const ("credit_restrictions",
+ &credit_restrictions),
+ GNUNET_JSON_spec_array_const ("debit_restrictions",
+ &debit_restrictions),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &wa->master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ json_t *account;
+
+ account = json_array_get (accounts,
+ i);
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (account,
+ spec_account,
+ NULL, NULL))
+ {
+ /* bogus reply */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if ( (NULL != master_pub) &&
+ (GNUNET_OK !=
+ TALER_exchange_wire_signature_check (
+ payto_uri,
+ conversion_url,
+ debit_restrictions,
+ credit_restrictions,
+ master_pub,
+ &wa->master_sig)) )
+ {
+ /* bogus reply */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if ( (GNUNET_OK !=
+ parse_restrictions (credit_restrictions,
+ &wa->credit_restrictions_length,
+ &wa->credit_restrictions)) ||
+ (GNUNET_OK !=
+ parse_restrictions (debit_restrictions,
+ &wa->debit_restrictions_length,
+ &wa->debit_restrictions)) )
+ {
+ /* bogus reply */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ wa->payto_uri = GNUNET_strdup (payto_uri);
+ wa->priority = priority;
+ if (NULL != conversion_url)
+ wa->conversion_url = GNUNET_strdup (conversion_url);
+ if (NULL != bank_label)
+ wa->bank_label = GNUNET_strdup (bank_label);
+ } /* end 'for all accounts */
+ return GNUNET_OK;
+}
+
+
+/**
+ * Free array of account restrictions.
+ *
+ * @param ar_len length of @a ar
+ * @param[in] ar array to free contents of (but not @a ar itself)
+ */
+static void
+free_restrictions (unsigned int ar_len,
+ struct TALER_EXCHANGE_AccountRestriction ar[static ar_len])
+{
+ for (unsigned int i = 0; i<ar_len; i++)
+ {
+ struct TALER_EXCHANGE_AccountRestriction *a = &ar[i];
+ switch (a->type)
+ {
+ case TALER_EXCHANGE_AR_INVALID:
+ GNUNET_break (0);
+ break;
+ case TALER_EXCHANGE_AR_DENY:
+ break;
+ case TALER_EXCHANGE_AR_REGEX:
+ GNUNET_free (ar->details.regex.posix_egrep);
+ GNUNET_free (ar->details.regex.human_hint);
+ json_decref (ar->details.regex.human_hint_i18n);
+ break;
+ }
+ }
+}
+
+
+void
+TALER_EXCHANGE_free_accounts (
+ unsigned int was_len,
+ struct TALER_EXCHANGE_WireAccount was[static was_len])
+{
+ for (unsigned int i = 0; i<was_len; i++)
+ {
+ struct TALER_EXCHANGE_WireAccount *wa = &was[i];
+
+ GNUNET_free (wa->payto_uri);
+ GNUNET_free (wa->conversion_url);
+ GNUNET_free (wa->bank_label);
+ free_restrictions (wa->credit_restrictions_length,
+ wa->credit_restrictions);
+ GNUNET_array_grow (wa->credit_restrictions,
+ wa->credit_restrictions_length,
+ 0);
+ free_restrictions (wa->debit_restrictions_length,
+ wa->debit_restrictions);
+ GNUNET_array_grow (wa->debit_restrictions,
+ wa->debit_restrictions_length,
+ 0);
+ }
+}
+
+
/* end of exchange_api_common.c */
diff --git a/src/lib/exchange_api_common.h b/src/lib/exchange_api_common.h
index a75ed3ed2..f1f0fd7fa 100644
--- a/src/lib/exchange_api_common.h
+++ b/src/lib/exchange_api_common.h
@@ -129,12 +129,10 @@ TALER_EXCHANGE_check_coin_amount_conflict_ (
/**
- * Verify that @a proof contains a coin history
- * that demonstrates that @a coin_pub was previously
- * used with a denomination key that is different
- * from @a ch_denom_pub. Note that the coin history
- * MUST have been checked before using
- * #TALER_EXCHANGE_check_coin_amount_conflict_().
+ * Verify that @a proof contains a coin history that demonstrates that @a
+ * coin_pub was previously used with a denomination key that is different from
+ * @a ch_denom_pub. Note that the coin history MUST have been checked before
+ * using #TALER_EXCHANGE_check_coin_amount_conflict_().
*
* @param proof a proof to check
* @param ch_denom_pub hash of the conflicting denomination
@@ -148,45 +146,6 @@ TALER_EXCHANGE_check_coin_denomination_conflict_ (
/**
- * Verify that @a coin_sig does NOT appear in
- * the history of @a proof and thus whatever transaction
- * is authorized by @a coin_sig is a conflict with
- * @a proof.
- *
- * @param proof a proof to check
- * @param coin_sig signature that must not be in @a proof
- * @return #GNUNET_OK if @a coin_sig is not in @a proof
- */
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_check_coin_signature_conflict_ (
- const json_t *proof,
- const struct TALER_CoinSpendSignatureP *coin_sig);
-
-
-/**
- * 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 coin_sig signature over operation that conflicted
- * @param required balance required on the coin for the operation
- * @return #GNUNET_OK if @a proof holds
- */
-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_CoinSpendSignatureP *coin_sig,
- const struct TALER_Amount *required);
-
-
-/**
* Find the smallest denomination amount in @e keys.
*
* @param keys keys to search
@@ -203,7 +162,7 @@ TALER_EXCHANGE_get_min_denomination_ (
* Verify signature information about the deposit.
*
* @param dcd contract details
- * @param ech hashed contract (passed to avoid recomputation)
+ * @param ech hashed policy (passed to avoid recomputation)
* @param h_wire hashed wire details (passed to avoid recomputation)
* @param cdd coin-specific details
* @param dki denomination of the coin
@@ -212,7 +171,7 @@ TALER_EXCHANGE_get_min_denomination_ (
enum GNUNET_GenericReturnValue
TALER_EXCHANGE_verify_deposit_signature_ (
const struct TALER_EXCHANGE_DepositContractDetail *dcd,
- const struct TALER_ExtensionContractHashP *ech,
+ const struct TALER_ExtensionPolicyHashP *ech,
const struct TALER_MerchantWireHashP *h_wire,
const struct TALER_EXCHANGE_CoinDepositDetail *cdd,
const struct TALER_EXCHANGE_DenomPublicKey *dki);
diff --git a/src/lib/exchange_api_contracts_get.c b/src/lib/exchange_api_contracts_get.c
index 263a0cbe9..aece7733a 100644
--- a/src/lib/exchange_api_contracts_get.c
+++ b/src/lib/exchange_api_contracts_get.c
@@ -39,11 +39,6 @@ struct TALER_EXCHANGE_ContractsGetHandle
{
/**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
* The url for this request.
*/
char *url;
@@ -109,7 +104,7 @@ handle_contract_get_finished (void *cls,
struct TALER_PurseContractSignatureP econtract_sig;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("purse_pub",
- &dr.details.success.purse_pub),
+ &dr.details.ok.purse_pub),
GNUNET_JSON_spec_fixed_auto ("econtract_sig",
&econtract_sig),
GNUNET_JSON_spec_varsize ("econtract",
@@ -133,7 +128,7 @@ handle_contract_get_finished (void *cls,
econtract,
econtract_size,
&cgh->cpub,
- &dr.details.success.purse_pub,
+ &dr.details.ok.purse_pub,
&econtract_sig))
{
GNUNET_break (0);
@@ -142,8 +137,8 @@ handle_contract_get_finished (void *cls,
GNUNET_JSON_parse_free (spec);
break;
}
- dr.details.success.econtract = econtract;
- dr.details.success.econtract_size = econtract_size;
+ dr.details.ok.econtract = econtract;
+ dr.details.ok.econtract_size = econtract_size;
cgh->cb (cgh->cb_cls,
&dr);
GNUNET_JSON_parse_free (spec);
@@ -194,7 +189,8 @@ handle_contract_get_finished (void *cls,
struct TALER_EXCHANGE_ContractsGetHandle *
TALER_EXCHANGE_contract_get (
- struct TALER_EXCHANGE_Handle *exchange,
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
const struct TALER_ContractDiffiePrivateP *contract_priv,
TALER_EXCHANGE_ContractGetCallback cb,
void *cb_cls)
@@ -203,14 +199,7 @@ TALER_EXCHANGE_contract_get (
CURL *eh;
char arg_str[sizeof (cgh->cpub) * 2 + 48];
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
cgh = GNUNET_new (struct TALER_EXCHANGE_ContractsGetHandle);
- cgh->exchange = exchange;
cgh->cb = cb;
cgh->cb_cls = cb_cls;
GNUNET_CRYPTO_ecdhe_key_get_public (&contract_priv->ecdhe_priv,
@@ -226,12 +215,13 @@ TALER_EXCHANGE_contract_get (
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/contracts/%s",
+ "contracts/%s",
cpub_str);
}
- cgh->url = TEAH_path_to_url (exchange,
- arg_str);
+ cgh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == cgh->url)
{
GNUNET_free (cgh);
@@ -247,7 +237,7 @@ TALER_EXCHANGE_contract_get (
GNUNET_free (cgh);
return NULL;
}
- cgh->job = GNUNET_CURL_job_add (TEAH_handle_to_context (exchange),
+ cgh->job = GNUNET_CURL_job_add (ctx,
eh,
&handle_contract_get_finished,
cgh);
diff --git a/src/lib/exchange_api_csr_melt.c b/src/lib/exchange_api_csr_melt.c
index 9de8cd8d9..bf6f4bfe1 100644
--- a/src/lib/exchange_api_csr_melt.c
+++ b/src/lib/exchange_api_csr_melt.c
@@ -38,10 +38,6 @@
*/
struct TALER_EXCHANGE_CsRMeltHandle
{
- /**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
/**
* Function to call with the result.
@@ -90,15 +86,15 @@ csr_ok (struct TALER_EXCHANGE_CsRMeltHandle *csrh,
const json_t *arr,
struct TALER_EXCHANGE_HttpResponse *hr)
{
- unsigned int alen = json_array_size (arr);
+ size_t alen = json_array_size (arr);
struct TALER_ExchangeWithdrawValues alg_values[GNUNET_NZL (alen)];
struct TALER_EXCHANGE_CsRMeltResponse csrr = {
.hr = *hr,
- .details.success.alg_values_len = alen,
- .details.success.alg_values = alg_values
+ .details.ok.alg_values_len = alen,
+ .details.ok.alg_values = alg_values
};
- for (unsigned int i = 0; i<alen; i++)
+ for (size_t i = 0; i<alen; i++)
{
json_t *av = json_array_get (arr,
i);
@@ -120,6 +116,8 @@ csr_ok (struct TALER_EXCHANGE_CsRMeltHandle *csrh,
}
csrh->cb (csrh->cb_cls,
&csrr);
+ for (size_t i = 0; i<alen; i++)
+ TALER_denom_ewv_free (&alg_values[i]);
return GNUNET_OK;
}
@@ -220,12 +218,14 @@ handle_csr_finished (void *cls,
struct TALER_EXCHANGE_CsRMeltHandle *
-TALER_EXCHANGE_csr_melt (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_RefreshMasterSecretP *rms,
- unsigned int nks_len,
- struct TALER_EXCHANGE_NonceKey *nks,
- TALER_EXCHANGE_CsRMeltCallback res_cb,
- void *res_cb_cls)
+TALER_EXCHANGE_csr_melt (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_RefreshMasterSecretP *rms,
+ unsigned int nks_len,
+ struct TALER_EXCHANGE_NonceKey nks[static nks_len],
+ TALER_EXCHANGE_CsRMeltCallback res_cb,
+ void *res_cb_cls)
{
struct TALER_EXCHANGE_CsRMeltHandle *csrh;
json_t *csr_arr;
@@ -236,13 +236,13 @@ TALER_EXCHANGE_csr_melt (struct TALER_EXCHANGE_Handle *exchange,
return NULL;
}
for (unsigned int i = 0; i<nks_len; i++)
- if (TALER_DENOMINATION_CS != nks[i].pk->key.cipher)
+ if (GNUNET_CRYPTO_BSA_CS !=
+ nks[i].pk->key.bsign_pub_key->cipher)
{
GNUNET_break (0);
return NULL;
}
csrh = GNUNET_new (struct TALER_EXCHANGE_CsRMeltHandle);
- csrh->exchange = exchange;
csrh->cb = res_cb;
csrh->cb_cls = res_cb_cls;
csr_arr = json_array ();
@@ -262,8 +262,9 @@ TALER_EXCHANGE_csr_melt (struct TALER_EXCHANGE_Handle *exchange,
json_array_append_new (csr_arr,
csr_obj));
}
- csrh->url = TEAH_path_to_url (exchange,
- "/csr-melt");
+ csrh->url = TALER_url_join (url,
+ "csr-melt",
+ NULL);
if (NULL == csrh->url)
{
json_decref (csr_arr);
@@ -272,7 +273,6 @@ TALER_EXCHANGE_csr_melt (struct TALER_EXCHANGE_Handle *exchange,
}
{
CURL *eh;
- struct GNUNET_CURL_Context *ctx;
json_t *req;
req = GNUNET_JSON_PACK (
@@ -280,7 +280,6 @@ TALER_EXCHANGE_csr_melt (struct TALER_EXCHANGE_Handle *exchange,
rms),
GNUNET_JSON_pack_array_steal ("nks",
csr_arr));
- ctx = TEAH_handle_to_context (exchange);
eh = TALER_EXCHANGE_curl_easy_get_ (csrh->url);
if ( (NULL == eh) ||
(GNUNET_OK !=
diff --git a/src/lib/exchange_api_csr_withdraw.c b/src/lib/exchange_api_csr_withdraw.c
index fa806857d..0fe731cd5 100644
--- a/src/lib/exchange_api_csr_withdraw.c
+++ b/src/lib/exchange_api_csr_withdraw.c
@@ -39,11 +39,6 @@
struct TALER_EXCHANGE_CsRWithdrawHandle
{
/**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
* Function to call with the result.
*/
TALER_EXCHANGE_CsRWithdrawCallback cb;
@@ -96,7 +91,7 @@ csr_ok (struct TALER_EXCHANGE_CsRWithdrawHandle *csrh,
struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_exchange_withdraw_values (
"ewv",
- &csrr.details.success.alg_values),
+ &csrr.details.ok.alg_values),
GNUNET_JSON_spec_end ()
};
@@ -110,6 +105,7 @@ csr_ok (struct TALER_EXCHANGE_CsRWithdrawHandle *csrh,
}
csrh->cb (csrh->cb_cls,
&csrr);
+ TALER_denom_ewv_free (&csrr.details.ok.alg_values);
return GNUNET_OK;
}
@@ -204,25 +200,28 @@ handle_csr_finished (void *cls,
struct TALER_EXCHANGE_CsRWithdrawHandle *
-TALER_EXCHANGE_csr_withdraw (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_EXCHANGE_DenomPublicKey *pk,
- const struct TALER_CsNonce *nonce,
- TALER_EXCHANGE_CsRWithdrawCallback res_cb,
- void *res_cb_cls)
+TALER_EXCHANGE_csr_withdraw (
+ struct GNUNET_CURL_Context *curl_ctx,
+ const char *exchange_url,
+ const struct TALER_EXCHANGE_DenomPublicKey *pk,
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce,
+ TALER_EXCHANGE_CsRWithdrawCallback res_cb,
+ void *res_cb_cls)
{
struct TALER_EXCHANGE_CsRWithdrawHandle *csrh;
- if (TALER_DENOMINATION_CS != pk->key.cipher)
+ if (GNUNET_CRYPTO_BSA_CS !=
+ pk->key.bsign_pub_key->cipher)
{
GNUNET_break (0);
return NULL;
}
csrh = GNUNET_new (struct TALER_EXCHANGE_CsRWithdrawHandle);
- csrh->exchange = exchange;
csrh->cb = res_cb;
csrh->cb_cls = res_cb_cls;
- csrh->url = TEAH_path_to_url (exchange,
- "/csr-withdraw");
+ csrh->url = TALER_url_join (exchange_url,
+ "csr-withdraw",
+ NULL);
if (NULL == csrh->url)
{
GNUNET_free (csrh);
@@ -231,18 +230,16 @@ TALER_EXCHANGE_csr_withdraw (struct TALER_EXCHANGE_Handle *exchange,
{
CURL *eh;
- struct GNUNET_CURL_Context *ctx;
json_t *req;
req = GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_varsize ("nonce",
nonce,
- sizeof(struct TALER_CsNonce)),
+ sizeof(*nonce)),
GNUNET_JSON_pack_data_varsize ("denom_pub_hash",
&pk->h_key,
- sizeof(struct TALER_DenominationHashP)));
+ sizeof(pk->h_key)));
GNUNET_assert (NULL != req);
- ctx = TEAH_handle_to_context (exchange);
eh = TALER_EXCHANGE_curl_easy_get_ (csrh->url);
if ( (NULL == eh) ||
(GNUNET_OK !=
@@ -259,7 +256,7 @@ TALER_EXCHANGE_csr_withdraw (struct TALER_EXCHANGE_Handle *exchange,
return NULL;
}
json_decref (req);
- csrh->job = GNUNET_CURL_job_add2 (ctx,
+ csrh->job = GNUNET_CURL_job_add2 (curl_ctx,
eh,
csrh->post_ctx.headers,
&handle_csr_finished,
@@ -270,8 +267,8 @@ TALER_EXCHANGE_csr_withdraw (struct TALER_EXCHANGE_Handle *exchange,
void
-TALER_EXCHANGE_csr_withdraw_cancel (struct
- TALER_EXCHANGE_CsRWithdrawHandle *csrh)
+TALER_EXCHANGE_csr_withdraw_cancel (
+ struct TALER_EXCHANGE_CsRWithdrawHandle *csrh)
{
if (NULL != csrh->job)
{
diff --git a/src/lib/exchange_api_curl_defaults.c b/src/lib/exchange_api_curl_defaults.c
index 9627db9ff..85a32189b 100644
--- a/src/lib/exchange_api_curl_defaults.c
+++ b/src/lib/exchange_api_curl_defaults.c
@@ -19,7 +19,8 @@
* @brief curl easy handle defaults
* @author Florian Dold
*/
-
+#include "platform.h"
+#include "taler_curl_lib.h"
#include "exchange_api_curl_defaults.h"
@@ -38,22 +39,14 @@ TALER_EXCHANGE_curl_easy_get_ (const char *url)
curl_easy_setopt (eh,
CURLOPT_URL,
url));
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_FOLLOWLOCATION,
- 1L));
+ TALER_curl_set_secure_redirect_policy (eh,
+ url);
/* Enable compression (using whatever curl likes), see
https://curl.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html */
GNUNET_break (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_ACCEPT_ENCODING,
""));
- /* limit MAXREDIRS to 5 as a simple security measure against
- a potential infinite loop caused by a malicious target */
- GNUNET_assert (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_MAXREDIRS,
- 5L));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_TCP_FASTOPEN,
diff --git a/src/lib/exchange_api_curl_defaults.h b/src/lib/exchange_api_curl_defaults.h
index 009d72ab8..c4ba04fc5 100644
--- a/src/lib/exchange_api_curl_defaults.h
+++ b/src/lib/exchange_api_curl_defaults.h
@@ -25,7 +25,6 @@
#define _TALER_CURL_DEFAULTS_H
-#include "platform.h"
#include <gnunet/gnunet_curl_lib.h>
diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c
deleted file mode 100644
index 3ba986b2d..000000000
--- a/src/lib/exchange_api_deposit.c
+++ /dev/null
@@ -1,557 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-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_deposit.c
- * @brief Implementation of the /deposit request of the exchange's HTTP API
- * @author Sree Harsha Totakura <sreeharsha@totakura.in>
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_json_lib.h"
-#include "taler_auditor_service.h"
-#include "taler_exchange_service.h"
-#include "exchange_api_common.h"
-#include "exchange_api_handle.h"
-#include "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
-
-/**
- * @brief A Deposit Handle
- */
-struct TALER_EXCHANGE_DepositHandle
-{
-
- /**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
- * 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_DepositResultCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Details about the contract.
- */
- struct TALER_EXCHANGE_DepositContractDetail dcd;
-
- /**
- * Details about the coin.
- */
- struct TALER_EXCHANGE_CoinDepositDetail cdd;
-
- /**
- * Hash of the merchant's wire details.
- */
- struct TALER_MerchantWireHashP h_wire;
-
- /**
- * Hash over the extensions, or all zero.
- */
- struct TALER_ExtensionContractHashP h_extensions;
-
- /**
- * 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;
-
- /**
- * Exchange signing public key, set for #auditor_cb.
- */
- struct TALER_ExchangePublicKeyP exchange_pub;
-
- /**
- * 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;
-
-};
-
-
-/**
- * Function called for each auditor to give us a chance to possibly
- * launch a deposit confirmation interaction.
- *
- * @param cls closure
- * @param ah handle to the auditor
- * @param auditor_pub public key of the auditor
- * @return NULL if no deposit confirmation interaction was launched
- */
-static struct TEAH_AuditorInteractionEntry *
-auditor_cb (void *cls,
- struct TALER_AUDITOR_Handle *ah,
- const struct TALER_AuditorPublicKeyP *auditor_pub)
-{
- struct TALER_EXCHANGE_DepositHandle *dh = cls;
- const struct TALER_EXCHANGE_Keys *key_state;
- const struct TALER_EXCHANGE_SigningPublicKey *spk;
- struct TEAH_AuditorInteractionEntry *aie;
- struct TALER_Amount amount_without_fee;
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
-
- 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 NULL;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Will provide deposit confirmation to auditor `%s'\n",
- TALER_B2S (auditor_pub));
- key_state = TALER_EXCHANGE_get_keys (dh->exchange);
- dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
- &dh->cdd.h_denom_pub);
- GNUNET_assert (NULL != dki);
- spk = TALER_EXCHANGE_get_signing_key_info (key_state,
- &dh->exchange_pub);
- if (NULL == spk)
- {
- GNUNET_break_op (0);
- return NULL;
- }
- GNUNET_assert (0 <=
- TALER_amount_subtract (&amount_without_fee,
- &dh->cdd.amount,
- &dki->fees.deposit));
- aie = GNUNET_new (struct TEAH_AuditorInteractionEntry);
- aie->dch = TALER_AUDITOR_deposit_confirmation (
- ah,
- &dh->h_wire,
- &dh->h_extensions,
- &dh->dcd.h_contract_terms,
- dh->exchange_timestamp,
- dh->dcd.wire_deadline,
- dh->dcd.refund_deadline,
- &amount_without_fee,
- &dh->cdd.coin_pub,
- &dh->dcd.merchant_pub,
- &dh->exchange_pub,
- &dh->exchange_sig,
- &key_state->master_pub,
- spk->valid_from,
- spk->valid_until,
- spk->valid_legal,
- &spk->master_sig,
- &TEAH_acc_confirmation_cb,
- aie);
- return aie;
-}
-
-
-/**
- * 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_deposit_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_DepositHandle *dh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_DepositResult dr = {
- .hr.reply = j,
- .hr.http_status = (unsigned int) response_code
- };
- const struct TALER_EXCHANGE_Keys *keys;
-
- dh->job = NULL;
- keys = TALER_EXCHANGE_get_keys (dh->exchange);
- switch (response_code)
- {
- case 0:
- dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- {
- const struct TALER_EXCHANGE_Keys *key_state;
- 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 (
- GNUNET_JSON_spec_string ("transaction_base_url",
- &dr.details.success.transaction_base_url),
- NULL),
- GNUNET_JSON_spec_timestamp ("exchange_timestamp",
- &dh->exchange_timestamp),
- GNUNET_JSON_spec_end ()
- };
- struct TALER_Amount amount_without_fee;
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
-
- 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;
- }
- key_state = TALER_EXCHANGE_get_keys (dh->exchange);
- dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
- &dh->cdd.h_denom_pub);
- GNUNET_assert (NULL != dki);
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
- &dh->exchange_pub))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
- break;
- }
- GNUNET_assert (0 <=
- TALER_amount_subtract (&amount_without_fee,
- &dh->cdd.amount,
- &dki->fees.deposit));
-
- if (GNUNET_OK !=
- TALER_exchange_online_deposit_confirmation_verify (
- &dh->dcd.h_contract_terms,
- &dh->h_wire,
- &dh->h_extensions,
- dh->exchange_timestamp,
- dh->dcd.wire_deadline,
- dh->dcd.refund_deadline,
- &amount_without_fee,
- &dh->cdd.coin_pub,
- &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->exchange,
- &auditor_cb,
- dh);
- }
- dr.details.success.exchange_sig = &dh->exchange_sig;
- dr.details.success.exchange_pub = &dh->exchange_pub;
- dr.details.success.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:
- {
- const struct TALER_EXCHANGE_Keys *key_state;
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
-
- key_state = TALER_EXCHANGE_get_keys (dh->exchange);
- dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
- &dh->cdd.h_denom_pub);
- GNUNET_assert (NULL != dki);
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_coin_conflict_ (
- keys,
- j,
- dki,
- &dh->cdd.coin_pub,
- &dh->cdd.coin_sig,
- &dh->cdd.amount))
- {
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- 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_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;
- }
- dh->cb (dh->cb_cls,
- &dr);
- TALER_EXCHANGE_deposit_cancel (dh);
-}
-
-
-struct TALER_EXCHANGE_DepositHandle *
-TALER_EXCHANGE_deposit (
- struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_EXCHANGE_DepositContractDetail *dcd,
- const struct TALER_EXCHANGE_CoinDepositDetail *cdd,
- TALER_EXCHANGE_DepositResultCallback cb,
- void *cb_cls,
- enum TALER_ErrorCode *ec)
-{
- const struct TALER_EXCHANGE_Keys *key_state;
- struct TALER_EXCHANGE_DepositHandle *dh;
- struct GNUNET_CURL_Context *ctx;
- json_t *deposit_obj;
- CURL *eh;
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
- struct TALER_Amount amount_without_fee;
- char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
-
- GNUNET_assert (GNUNET_YES ==
- TEAH_handle_is_ready (exchange));
- 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;
- }
- {
- char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- &cdd->coin_pub,
- sizeof (struct TALER_CoinSpendPublicKeyP),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "/coins/%s/deposit",
- pub_str);
- }
- key_state = TALER_EXCHANGE_get_keys (exchange);
- dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
- &cdd->h_denom_pub);
- if (NULL == dki)
- {
- *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
- GNUNET_break_op (0);
- 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);
- return NULL;
- }
- dh = GNUNET_new (struct TALER_EXCHANGE_DepositHandle);
- dh->auditor_chance = AUDITOR_CHANCE;
- dh->exchange = exchange;
- dh->cb = cb;
- dh->cb_cls = cb_cls;
- dh->cdd = *cdd;
- dh->dcd = *dcd;
- if (NULL != dcd->extension_details)
- TALER_deposit_extension_hash (dcd->extension_details,
- &dh->h_extensions);
- TALER_merchant_wire_signature_hash (dcd->merchant_payto_uri,
- &dcd->wire_salt,
- &dh->h_wire);
- if (GNUNET_OK !=
- TALER_EXCHANGE_verify_deposit_signature_ (dcd,
- &dh->h_extensions,
- &dh->h_wire,
- cdd,
- dki))
- {
- *ec = TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID;
- GNUNET_break_op (0);
- GNUNET_free (dh);
- return NULL;
- }
- dh->url = TEAH_path_to_url (exchange,
- arg_str);
- if (NULL == dh->url)
- {
- GNUNET_break (0);
- *ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
- GNUNET_free (dh->url);
- GNUNET_free (dh);
- return NULL;
- }
-
- deposit_obj = GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("contribution",
- &cdd->amount),
- GNUNET_JSON_pack_string ("merchant_payto_uri",
- dcd->merchant_payto_uri),
- 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_allow_null (
- GNUNET_JSON_pack_data_auto ("h_age_commitment",
- &cdd->h_age_commitment)),
- 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_timestamp ("timestamp",
- dcd->timestamp),
- GNUNET_JSON_pack_data_auto ("merchant_pub",
- &dcd->merchant_pub),
- 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_JSON_pack_data_auto ("coin_sig",
- &cdd->coin_sig));
- GNUNET_assert (NULL != deposit_obj);
- eh = TALER_EXCHANGE_curl_easy_get_ (dh->url);
- if ( (NULL == eh) ||
- (GNUNET_OK !=
- TALER_curl_easy_post (&dh->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->url);
- GNUNET_free (dh);
- return NULL;
- }
- json_decref (deposit_obj);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "URL for deposit: `%s'\n",
- dh->url);
- ctx = TEAH_handle_to_context (exchange);
- dh->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- dh->ctx.headers,
- &handle_deposit_finished,
- dh);
- return dh;
-}
-
-
-void
-TALER_EXCHANGE_deposit_force_dc (struct TALER_EXCHANGE_DepositHandle *deposit)
-{
- deposit->auditor_chance = 1;
-}
-
-
-void
-TALER_EXCHANGE_deposit_cancel (struct TALER_EXCHANGE_DepositHandle *deposit)
-{
- if (NULL != deposit->job)
- {
- GNUNET_CURL_job_cancel (deposit->job);
- deposit->job = NULL;
- }
- GNUNET_free (deposit->url);
- TALER_curl_easy_post_finished (&deposit->ctx);
- GNUNET_free (deposit);
-}
-
-
-/* end of exchange_api_deposit.c */
diff --git a/src/lib/exchange_api_deposits_get.c b/src/lib/exchange_api_deposits_get.c
index baa254a83..20eaea3d3 100644
--- a/src/lib/exchange_api_deposits_get.c
+++ b/src/lib/exchange_api_deposits_get.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
+ 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
@@ -39,9 +39,9 @@ struct TALER_EXCHANGE_DepositGetHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -118,20 +118,20 @@ handle_deposit_wtid_finished (void *cls,
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("wtid",
- &dr.details.success.wtid),
+ &dr.details.ok.wtid),
GNUNET_JSON_spec_timestamp ("execution_time",
- &dr.details.success.execution_time),
+ &dr.details.ok.execution_time),
TALER_JSON_spec_amount_any ("coin_contribution",
- &dr.details.success.coin_contribution),
+ &dr.details.ok.coin_contribution),
GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &dr.details.success.exchange_sig),
+ &dr.details.ok.exchange_sig),
GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &dr.details.success.exchange_pub),
+ &dr.details.ok.exchange_pub),
GNUNET_JSON_spec_end ()
};
const struct TALER_EXCHANGE_Keys *key_state;
- key_state = TALER_EXCHANGE_get_keys (dwh->exchange);
+ key_state = dwh->keys;
GNUNET_assert (NULL != key_state);
if (GNUNET_OK !=
GNUNET_JSON_parse (j,
@@ -145,7 +145,7 @@ handle_deposit_wtid_finished (void *cls,
}
if (GNUNET_OK !=
TALER_EXCHANGE_test_signing_key (key_state,
- &dr.details.success.exchange_pub))
+ &dr.details.ok.exchange_pub))
{
GNUNET_break_op (0);
dr.hr.http_status = 0;
@@ -156,12 +156,12 @@ handle_deposit_wtid_finished (void *cls,
TALER_exchange_online_confirm_wire_verify (
&dwh->h_wire,
&dwh->h_contract_terms,
- &dr.details.success.wtid,
+ &dr.details.ok.wtid,
&dwh->coin_pub,
- dr.details.success.execution_time,
- &dr.details.success.coin_contribution,
- &dr.details.success.exchange_pub,
- &dr.details.success.exchange_sig))
+ 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;
@@ -176,11 +176,16 @@ handle_deposit_wtid_finished (void *cls,
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_uint64 ("payment_target_uuid",
- &dr.details.accepted.payment_target_uuid),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint64 ("requirement_row",
+ &dr.details.accepted.requirement_row),
+ &no_legi),
+ TALER_JSON_spec_aml_decision ("aml_decision",
+ &dr.details.accepted.aml_decision),
GNUNET_JSON_spec_bool ("kyc_ok",
&dr.details.accepted.kyc_ok),
GNUNET_JSON_spec_end ()
@@ -196,6 +201,8 @@ handle_deposit_wtid_finished (void *cls,
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);
@@ -245,31 +252,30 @@ handle_deposit_wtid_finished (void *cls,
struct TALER_EXCHANGE_DepositGetHandle *
TALER_EXCHANGE_deposits_get (
- struct TALER_EXCHANGE_Handle *exchange,
+ 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;
- struct GNUNET_CURL_Context *ctx;
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;
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
&merchant.eddsa_pub);
TALER_merchant_deposit_sign (h_contract_terms,
@@ -283,6 +289,7 @@ TALER_EXCHANGE_deposits_get (
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,
@@ -310,23 +317,39 @@ TALER_EXCHANGE_deposits_get (
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",
+ "deposits/%s/%s/%s/%s?merchant_sig=%s%s%s",
whash_str,
mpub_str,
chash_str,
cpub_str,
- msig_str);
+ msig_str,
+ 0 == tms
+ ? ""
+ : "&timeout_ms=",
+ timeout_str);
}
dwh = GNUNET_new (struct TALER_EXCHANGE_DepositGetHandle);
- dwh->exchange = exchange;
dwh->cb = cb;
dwh->cb_cls = cb_cls;
- dwh->url = TEAH_path_to_url (exchange,
- arg_str);
+ dwh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == dwh->url)
{
GNUNET_free (dwh);
@@ -335,7 +358,6 @@ TALER_EXCHANGE_deposits_get (
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)
{
@@ -344,11 +366,18 @@ TALER_EXCHANGE_deposits_get (
GNUNET_free (dwh);
return NULL;
}
- ctx = TEAH_handle_to_context (exchange);
+ 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;
}
@@ -363,6 +392,7 @@ TALER_EXCHANGE_deposits_get_cancel (struct TALER_EXCHANGE_DepositGetHandle *dwh)
}
GNUNET_free (dwh->url);
TALER_curl_easy_post_finished (&dwh->ctx);
+ TALER_EXCHANGE_keys_decref (dwh->keys);
GNUNET_free (dwh);
}
diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c
index 488a419be..fdadc8d2a 100644
--- a/src/lib/exchange_api_handle.c
+++ b/src/lib/exchange_api_handle.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ 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
@@ -40,12 +40,17 @@
* Which version of the Taler protocol is implemented
* by this library? Used to determine compatibility.
*/
-#define EXCHANGE_PROTOCOL_CURRENT 14
+#define EXCHANGE_PROTOCOL_CURRENT 19
/**
* How many versions are we backwards compatible with?
*/
-#define EXCHANGE_PROTOCOL_AGE 0
+#define EXCHANGE_PROTOCOL_AGE 2
+
+/**
+ * Set to 1 for extra debug logging.
+ */
+#define DEBUG 0
/**
* Current version for (local) JSON serialization of persisted
@@ -54,7 +59,7 @@
#define EXCHANGE_SERIALIZATION_FORMAT_VERSION 0
/**
- * How far off do we allow key liftimes to be?
+ * How far off do we allow key lifetimes to be?
*/
#define LIFETIME_TOLERANCE GNUNET_TIME_UNIT_HOURS
@@ -65,175 +70,311 @@
#define DEFAULT_EXPIRATION GNUNET_TIME_UNIT_HOURS
/**
- * Set to 1 for extra debug logging.
- */
-#define DEBUG 0
-
-/**
- * Log error related to CURL operations.
- *
- * @param type log level
- * @param function which function failed to run
- * @param code what was the curl error code
- */
-#define CURL_STRERROR(type, function, code) \
- GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \
- function, __FILE__, __LINE__, curl_easy_strerror (code));
-
-
-/**
- * Data for the request to get the /keys of a exchange.
+ * If the "Expire" cache control header is missing, for
+ * how long do we assume the reply to be valid at least?
*/
-struct KeysRequest;
+#define MINIMUM_EXPIRATION GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_MINUTES, 2)
/**
- * Entry in DLL of auditors used by an exchange.
+ * Handle for a GET /keys request.
*/
-struct TEAH_AuditorListEntry
+struct TALER_EXCHANGE_GetKeysHandle
{
- /**
- * Next pointer of DLL.
- */
- struct TEAH_AuditorListEntry *next;
/**
- * Prev pointer of DLL.
+ * The exchange base URL (i.e. "https://exchange.demo.taler.net/")
*/
- struct TEAH_AuditorListEntry *prev;
+ char *exchange_url;
/**
- * Base URL of the auditor.
+ * The url for the /keys request.
*/
- char *auditor_url;
+ char *url;
/**
- * Handle to the auditor.
+ * Previous /keys response, NULL for none.
*/
- struct TALER_AUDITOR_Handle *ah;
+ struct TALER_EXCHANGE_Keys *prev_keys;
/**
- * Head of DLL of interactions with this auditor.
+ * Entry for this request with the `struct GNUNET_CURL_Context`.
*/
- struct TEAH_AuditorInteractionEntry *ai_head;
+ struct GNUNET_CURL_Job *job;
/**
- * Tail of DLL of interactions with this auditor.
+ * Expiration time according to "Expire:" header.
+ * 0 if not provided by the server.
*/
- struct TEAH_AuditorInteractionEntry *ai_tail;
+ struct GNUNET_TIME_Timestamp expire;
/**
- * Public key of the auditor.
+ * Function to call with the exchange's certification data,
+ * NULL if this has already been done.
*/
- struct TALER_AuditorPublicKeyP auditor_pub;
+ TALER_EXCHANGE_GetKeysCallback cert_cb;
/**
- * Flag indicating that the auditor is available and that protocol
- * version compatibility is given.
+ * Closure to pass to @e cert_cb.
*/
- bool is_up;
+ void *cert_cb_cls;
};
-/* ***************** Internal /keys fetching ************* */
-
/**
- * Data for the request to get the /keys of a exchange.
+ * Element in the `struct SignatureContext` array.
*/
-struct KeysRequest
+struct SignatureElement
{
+
/**
- * The connection to exchange this request handle will use
+ * Offset of the denomination in the group array,
+ * for sorting (2nd rank, ascending).
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ unsigned int offset;
/**
- * The url for this handle
+ * Offset of the group in the denominations array,
+ * for sorting (2nd rank, ascending).
*/
- char *url;
+ unsigned int group_offset;
/**
- * Entry for this request with the `struct GNUNET_CURL_Context`.
+ * Pointer to actual master signature to hash over.
*/
- struct GNUNET_CURL_Job *job;
+ struct TALER_MasterSignatureP master_sig;
+};
+/**
+ * Context for collecting the array of master signatures
+ * needed to verify the exchange_sig online signature.
+ */
+struct SignatureContext
+{
/**
- * Expiration time according to "Expire:" header.
- * 0 if not provided by the server.
+ * Array of signatures to hash over.
*/
- struct GNUNET_TIME_Timestamp expire;
+ struct SignatureElement *elements;
+
+ /**
+ * Write offset in the @e elements array.
+ */
+ unsigned int elements_pos;
+ /**
+ * Allocated space for @e elements.
+ */
+ unsigned int elements_size;
};
-void
-TEAH_acc_confirmation_cb (void *cls,
- const struct TALER_AUDITOR_HttpResponse *hr)
+/**
+ * Determine order to sort two elements by before
+ * we hash the master signatures. Used for
+ * sorting with qsort().
+ *
+ * @param a pointer to a `struct SignatureElement`
+ * @param b pointer to a `struct SignatureElement`
+ * @return 0 if equal, -1 if a < b, 1 if a > b.
+ */
+static int
+signature_context_sort_cb (const void *a,
+ const void *b)
{
- struct TEAH_AuditorInteractionEntry *aie = cls;
- struct TEAH_AuditorListEntry *ale = aie->ale;
+ const struct SignatureElement *sa = a;
+ const struct SignatureElement *sb = b;
- if (MHD_HTTP_OK != 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",
- ale->auditor_url,
- hr->http_status,
- hr->ec);
- }
- GNUNET_CONTAINER_DLL_remove (ale->ai_head,
- ale->ai_tail,
- aie);
- GNUNET_free (aie);
+ if (sa->group_offset < sb->group_offset)
+ return -1;
+ if (sa->group_offset > sb->group_offset)
+ return 1;
+ if (sa->offset < sb->offset)
+ return -1;
+ if (sa->offset > sb->offset)
+ return 1;
+ /* We should never have two disjoint elements
+ with same time and offset */
+ GNUNET_assert (sa == sb);
+ return 0;
}
-void
-TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h,
- TEAH_AuditorCallback ac,
- void *ac_cls)
+/**
+ * Append a @a master_sig to the @a sig_ctx using the
+ * given attributes for (later) sorting.
+ *
+ * @param[in,out] sig_ctx signature context to update
+ * @param group_offset offset for the group
+ * @param offset offset for the entry
+ * @param master_sig master signature for the entry
+ */
+static void
+append_signature (struct SignatureContext *sig_ctx,
+ unsigned int group_offset,
+ unsigned int offset,
+ const struct TALER_MasterSignatureP *master_sig)
{
- if (NULL == h->auditors_head)
+ struct SignatureElement *element;
+ unsigned int new_size;
+
+ if (sig_ctx->elements_pos == sig_ctx->elements_size)
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No auditor available for exchange `%s'. Not submitting deposit confirmations.\n",
- h->url);
- return;
+ if (0 == sig_ctx->elements_size)
+ new_size = 1024;
+ else
+ new_size = sig_ctx->elements_size * 2;
+ GNUNET_array_grow (sig_ctx->elements,
+ sig_ctx->elements_size,
+ new_size);
}
- for (struct TEAH_AuditorListEntry *ale = h->auditors_head;
- NULL != ale;
- ale = ale->next)
+ element = &sig_ctx->elements[sig_ctx->elements_pos++];
+ element->offset = offset;
+ element->group_offset = group_offset;
+ element->master_sig = *master_sig;
+}
+
+
+/**
+ * Frees @a wfm array.
+ *
+ * @param wfm fee array to release
+ * @param wfm_len length of the @a wfm array
+ */
+static void
+free_fees (struct TALER_EXCHANGE_WireFeesByMethod *wfm,
+ unsigned int wfm_len)
+{
+ for (unsigned int i = 0; i<wfm_len; i++)
{
- struct TEAH_AuditorInteractionEntry *aie;
+ struct TALER_EXCHANGE_WireFeesByMethod *wfmi = &wfm[i];
- if (! ale->is_up)
- continue;
- aie = ac (ac_cls,
- ale->ah,
- &ale->auditor_pub);
- if (NULL != aie)
+ while (NULL != wfmi->fees_head)
{
- aie->ale = ale;
- GNUNET_CONTAINER_DLL_insert (ale->ai_head,
- ale->ai_tail,
- aie);
+ struct TALER_EXCHANGE_WireAggregateFees *fe
+ = wfmi->fees_head;
+
+ wfmi->fees_head = fe->next;
+ GNUNET_free (fe);
}
+ GNUNET_free (wfmi->method);
}
+ GNUNET_free (wfm);
}
/**
- * Release memory occupied by a keys request. Note that this does not
- * cancel the request itself.
+ * Parse wire @a fees and return array.
*
- * @param kr request to free
+ * @param master_pub master public key to use to check signatures
+ * @param currency currency amounts are expected in
+ * @param fees json AggregateTransferFee to parse
+ * @param[out] fees_len set to length of returned array
+ * @return NULL on error
*/
-static void
-free_keys_request (struct KeysRequest *kr)
+static struct TALER_EXCHANGE_WireFeesByMethod *
+parse_fees (const struct TALER_MasterPublicKeyP *master_pub,
+ const char *currency,
+ const json_t *fees,
+ unsigned int *fees_len)
+{
+ struct TALER_EXCHANGE_WireFeesByMethod *fbm;
+ size_t fbml = json_object_size (fees);
+ unsigned int i = 0;
+ const char *key;
+ const json_t *fee_array;
+
+ if (UINT_MAX < fbml)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ fbm = GNUNET_new_array (fbml,
+ struct TALER_EXCHANGE_WireFeesByMethod);
+ *fees_len = (unsigned int) fbml;
+ json_object_foreach ((json_t *) fees, key, fee_array) {
+ struct TALER_EXCHANGE_WireFeesByMethod *fe = &fbm[i++];
+ size_t idx;
+ json_t *fee;
+
+ fe->method = GNUNET_strdup (key);
+ fe->fees_head = NULL;
+ json_array_foreach (fee_array, idx, fee)
+ {
+ struct TALER_EXCHANGE_WireAggregateFees *wa
+ = GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees);
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("sig",
+ &wa->master_sig),
+ TALER_JSON_spec_amount ("wire_fee",
+ currency,
+ &wa->fees.wire),
+ TALER_JSON_spec_amount ("closing_fee",
+ currency,
+ &wa->fees.closing),
+ GNUNET_JSON_spec_timestamp ("start_date",
+ &wa->start_date),
+ GNUNET_JSON_spec_timestamp ("end_date",
+ &wa->end_date),
+ GNUNET_JSON_spec_end ()
+ };
+
+ wa->next = fe->fees_head;
+ fe->fees_head = wa;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (fee,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ free_fees (fbm,
+ i);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_offline_wire_fee_verify (
+ key,
+ wa->start_date,
+ wa->end_date,
+ &wa->fees,
+ master_pub,
+ &wa->master_sig))
+ {
+ GNUNET_break_op (0);
+ free_fees (fbm,
+ i);
+ return NULL;
+ }
+ } /* for all fees over time */
+ } /* for all methods */
+ GNUNET_assert (i == fbml);
+ return fbm;
+}
+
+
+void
+TEAH_get_auditors_for_dc (
+ struct TALER_EXCHANGE_Keys *keys,
+ TEAH_AuditorCallback ac,
+ void *ac_cls)
{
- GNUNET_free (kr->url);
- GNUNET_free (kr);
+ if (0 == keys->num_auditors)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No auditor available. Not submitting deposit confirmations.\n");
+ return;
+ }
+ for (unsigned int i = 0; i<keys->num_auditors; i++)
+ {
+ const struct TALER_EXCHANGE_AuditorInformation *auditor
+ = &keys->auditors[i];
+
+ ac (ac_cls,
+ auditor->auditor_url,
+ &auditor->auditor_pub);
+ }
}
@@ -248,21 +389,20 @@ free_keys_request (struct KeysRequest *kr)
*
* @param[out] sign_key where to return the result
* @param check_sigs should we check signatures?
- * @param[in] sign_key_obj json to parse
+ * @param sign_key_obj json to parse
* @param master_key master key to use to verify signature
* @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
- * invalid or the json malformed.
+ * invalid or the @a sign_key_obj is malformed.
*/
static enum GNUNET_GenericReturnValue
parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key,
bool check_sigs,
- json_t *sign_key_obj,
+ const json_t *sign_key_obj,
const struct TALER_MasterPublicKeyP *master_key)
{
- struct TALER_MasterSignatureP sign_key_issue_sig;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("master_sig",
- &sign_key_issue_sig),
+ &sign_key->master_sig),
GNUNET_JSON_spec_fixed_auto ("key",
&sign_key->key),
GNUNET_JSON_spec_timestamp ("stamp_start",
@@ -282,7 +422,6 @@ parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
-
if (! check_sigs)
return GNUNET_OK;
if (GNUNET_OK !=
@@ -292,12 +431,11 @@ parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key,
sign_key->valid_until,
sign_key->valid_legal,
master_key,
- &sign_key_issue_sig))
+ &sign_key->master_sig))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- sign_key->master_sig = sign_key_issue_sig;
return GNUNET_OK;
}
@@ -310,24 +448,28 @@ parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key,
* prior to calling this function, otherwise the signature verification
* performed within this function will fail.
*
- * @param currency expected currency of all fees
* @param[out] denom_key where to return the result
* @param cipher cipher type to parse
* @param check_sigs should we check signatures?
- * @param[in] denom_key_obj json to parse
+ * @param denom_key_obj json to parse
* @param master_key master key to use to verify signature
- * @param hash_xor where to accumulate data for signature verification via XOR
+ * @param group_offset offset for the group
+ * @param index index of this denomination key in the group
+ * @param sig_ctx where to write details about encountered
+ * master signatures, NULL if not used
* @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
* invalid or the json malformed.
*/
static enum GNUNET_GenericReturnValue
-parse_json_denomkey_partially (const char *currency,
- struct TALER_EXCHANGE_DenomPublicKey *denom_key,
- enum TALER_DenominationCipher cipher,
- bool check_sigs,
- json_t *denom_key_obj,
- struct TALER_MasterPublicKeyP *master_key,
- struct GNUNET_HashCode *hash_xor)
+parse_json_denomkey_partially (
+ struct TALER_EXCHANGE_DenomPublicKey *denom_key,
+ enum GNUNET_CRYPTO_BlindSignatureAlgorithm cipher,
+ bool check_sigs,
+ const json_t *denom_key_obj,
+ struct TALER_MasterPublicKeyP *master_key,
+ unsigned int group_offset,
+ unsigned int index,
+ struct SignatureContext *sig_ctx)
{
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("master_sig",
@@ -340,6 +482,10 @@ parse_json_denomkey_partially (const char *currency,
&denom_key->valid_from),
GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
&denom_key->expire_legal),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("lost",
+ &denom_key->lost),
+ NULL),
TALER_JSON_spec_denom_pub_cipher (NULL,
cipher,
&denom_key->key),
@@ -356,11 +502,11 @@ parse_json_denomkey_partially (const char *currency,
}
TALER_denom_pub_hash (&denom_key->key,
&denom_key->h_key);
- if (NULL != hash_xor)
- GNUNET_CRYPTO_hash_xor (&denom_key->h_key.hash,
- hash_xor,
- hash_xor);
-
+ if (NULL != sig_ctx)
+ append_signature (sig_ctx,
+ group_offset,
+ index,
+ &denom_key->master_sig);
if (! check_sigs)
return GNUNET_OK;
EXITIF (GNUNET_SYSERR ==
@@ -376,11 +522,11 @@ parse_json_denomkey_partially (const char *currency,
&denom_key->master_sig));
return GNUNET_OK;
EXITIF_exit:
+ GNUNET_JSON_parse_free (spec);
/* invalidate denom_key, just to be sure */
memset (denom_key,
0,
sizeof (*denom_key));
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
@@ -390,7 +536,7 @@ EXITIF_exit:
*
* @param[out] auditor where to return the result
* @param check_sigs should we check signatures
- * @param[in] auditor_obj json to parse
+ * @param auditor_obj json to parse
* @param key_data information about denomination keys
* @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
* invalid or the json malformed.
@@ -398,22 +544,21 @@ EXITIF_exit:
static enum GNUNET_GenericReturnValue
parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,
bool check_sigs,
- json_t *auditor_obj,
+ const json_t *auditor_obj,
const struct TALER_EXCHANGE_Keys *key_data)
{
- json_t *keys;
+ const json_t *keys;
json_t *key;
- unsigned int len;
- unsigned int off;
- unsigned int i;
+ size_t off;
+ size_t pos;
const char *auditor_url;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("auditor_pub",
&auditor->auditor_pub),
- GNUNET_JSON_spec_string ("auditor_url",
+ TALER_JSON_spec_web_url ("auditor_url",
&auditor_url),
- GNUNET_JSON_spec_json ("denomination_keys",
- &keys),
+ GNUNET_JSON_spec_array_const ("denomination_keys",
+ &keys),
GNUNET_JSON_spec_end ()
};
@@ -431,16 +576,15 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,
return GNUNET_SYSERR;
}
auditor->auditor_url = GNUNET_strdup (auditor_url);
- len = json_array_size (keys);
- auditor->denom_keys = GNUNET_new_array (len,
- struct
- TALER_EXCHANGE_AuditorDenominationInfo);
- off = 0;
- json_array_foreach (keys, i, key) {
+ auditor->denom_keys
+ = GNUNET_new_array (json_array_size (keys),
+ struct TALER_EXCHANGE_AuditorDenominationInfo);
+ pos = 0;
+ json_array_foreach (keys, off, key) {
struct TALER_AuditorSignatureP auditor_sig;
struct TALER_DenominationHashP denom_h;
- const struct TALER_EXCHANGE_DenomPublicKey *dk;
- unsigned int dk_off;
+ const struct TALER_EXCHANGE_DenomPublicKey *dk = NULL;
+ unsigned int dk_off = UINT_MAX;
struct GNUNET_JSON_Specification kspec[] = {
GNUNET_JSON_spec_fixed_auto ("auditor_sig",
&auditor_sig),
@@ -457,8 +601,6 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,
GNUNET_break_op (0);
continue;
}
- dk = NULL;
- dk_off = UINT_MAX;
for (unsigned int j = 0; j<key_data->num_denom_keys; j++)
{
if (0 == GNUNET_memcmp (&denom_h,
@@ -493,16 +635,19 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,
&auditor_sig))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
}
- auditor->denom_keys[off].denom_key_offset = dk_off;
- auditor->denom_keys[off].auditor_sig = auditor_sig;
- off++;
+ auditor->denom_keys[pos].denom_key_offset = dk_off;
+ auditor->denom_keys[pos].auditor_sig = auditor_sig;
+ pos++;
}
- auditor->num_denom_keys = off;
- GNUNET_JSON_parse_free (spec);
+ if (pos > UINT_MAX)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ auditor->num_denom_keys = (unsigned int) pos;
return GNUNET_OK;
}
@@ -512,7 +657,7 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,
*
* @param[out] gf where to return the result
* @param check_sigs should we check signatures
- * @param[in] fee_obj json to parse
+ * @param fee_obj json to parse
* @param key_data already parsed information about the exchange
* @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
* invalid or the json malformed.
@@ -520,7 +665,7 @@ parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,
static enum GNUNET_GenericReturnValue
parse_global_fee (struct TALER_EXCHANGE_GlobalFee *gf,
bool check_sigs,
- json_t *fee_obj,
+ const json_t *fee_obj,
const struct TALER_EXCHANGE_Keys *key_data)
{
struct GNUNET_JSON_Specification spec[] = {
@@ -530,8 +675,6 @@ parse_global_fee (struct TALER_EXCHANGE_GlobalFee *gf,
&gf->end_date),
GNUNET_JSON_spec_relative_time ("purse_timeout",
&gf->purse_timeout),
- GNUNET_JSON_spec_relative_time ("account_kyc_timeout",
- &gf->kyc_timeout),
GNUNET_JSON_spec_relative_time ("history_expiration",
&gf->history_expiration),
GNUNET_JSON_spec_uint32 ("purse_account_limit",
@@ -564,7 +707,6 @@ parse_global_fee (struct TALER_EXCHANGE_GlobalFee *gf,
gf->end_date,
&gf->fees,
gf->purse_timeout,
- gf->kyc_timeout,
gf->history_expiration,
gf->purse_account_limit,
&key_data->master_pub,
@@ -581,113 +723,12 @@ parse_global_fee (struct TALER_EXCHANGE_GlobalFee *gf,
/**
- * Function called with information about the auditor. Marks an
- * auditor as 'up'.
- *
- * @param cls closure, a `struct TEAH_AuditorListEntry *`
- * @param hr http response from the auditor
- * @param vi basic information about the auditor
- * @param compat protocol compatibility information
- */
-static void
-auditor_version_cb (
- void *cls,
- const struct TALER_AUDITOR_HttpResponse *hr,
- const struct TALER_AUDITOR_VersionInformation *vi,
- enum TALER_AUDITOR_VersionCompatibility compat)
-{
- struct TEAH_AuditorListEntry *ale = cls;
-
- (void) hr;
- if (NULL == vi)
- {
- /* In this case, we don't mark the auditor as 'up' */
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Auditor `%s' gave unexpected version response.\n",
- ale->auditor_url);
- return;
- }
-
- if (0 != (TALER_AUDITOR_VC_INCOMPATIBLE & compat))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Auditor `%s' runs incompatible protocol version!\n",
- ale->auditor_url);
- if (0 != (TALER_AUDITOR_VC_OLDER & compat))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Auditor `%s' runs outdated protocol version!\n",
- ale->auditor_url);
- }
- if (0 != (TALER_AUDITOR_VC_NEWER & compat))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Auditor `%s' runs more recent incompatible version. We should upgrade!\n",
- ale->auditor_url);
- }
- return;
- }
- ale->is_up = true;
-}
-
-
-/**
- * Recalculate our auditor list, we got /keys and it may have
- * changed.
- *
- * @param exchange exchange for which to update the list.
- */
-static void
-update_auditors (struct TALER_EXCHANGE_Handle *exchange)
-{
- struct TALER_EXCHANGE_Keys *kd = &exchange->key_data;
-
- TALER_LOG_DEBUG ("Updating auditors\n");
- for (unsigned int i = 0; i<kd->num_auditors; i++)
- {
- /* Compare auditor data from /keys with auditor data
- * from owned exchange structures. */
- struct TALER_EXCHANGE_AuditorInformation *auditor = &kd->auditors[i];
- struct TEAH_AuditorListEntry *ale = NULL;
-
- for (struct TEAH_AuditorListEntry *a = exchange->auditors_head;
- NULL != a;
- a = a->next)
- {
- if (0 == GNUNET_memcmp (&auditor->auditor_pub,
- &a->auditor_pub))
- {
- ale = a;
- break;
- }
- }
- if (NULL != ale)
- continue; /* found, no need to add */
-
- /* new auditor, add */
- TALER_LOG_DEBUG ("Found new auditor %s!\n",
- auditor->auditor_url);
- ale = GNUNET_new (struct TEAH_AuditorListEntry);
- ale->auditor_pub = auditor->auditor_pub;
- ale->auditor_url = GNUNET_strdup (auditor->auditor_url);
- GNUNET_CONTAINER_DLL_insert (exchange->auditors_head,
- exchange->auditors_tail,
- ale);
- ale->ah = TALER_AUDITOR_connect (exchange->ctx,
- ale->auditor_url,
- &auditor_version_cb,
- ale);
- }
-}
-
-
-/**
* Compare two denomination keys. Ignores revocation data.
*
* @param denom1 first denomination key
* @param denom2 second denomination key
* @return 0 if the two keys are equal (not necessarily
- * the same object), 1 otherwise.
+ * the same object), non-zero otherwise.
*/
static unsigned int
denoms_cmp (const struct TALER_EXCHANGE_DenomPublicKey *denom1,
@@ -732,29 +773,21 @@ decode_keys_json (const json_t *resp_obj,
struct TALER_EXCHANGE_Keys *key_data,
enum TALER_EXCHANGE_VersionCompatibility *vc)
{
- struct TALER_ExchangeSignatureP denominations_sig;
- struct GNUNET_HashCode hash_xor = {0};
- struct TALER_ExchangePublicKeyP pub;
- const char *currency;
- struct GNUNET_JSON_Specification mspec[] = {
- GNUNET_JSON_spec_fixed_auto ("denominations_sig",
- &denominations_sig),
- GNUNET_JSON_spec_fixed_auto ("eddsa_pub",
- &pub),
- GNUNET_JSON_spec_fixed_auto ("master_public_key",
- &key_data->master_pub),
- GNUNET_JSON_spec_timestamp ("list_issue_date",
- &key_data->list_issue_date),
- GNUNET_JSON_spec_relative_time ("reserve_closing_delay",
- &key_data->reserve_closing_delay),
- GNUNET_JSON_spec_string ("currency",
- &currency),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount_any ("wallet_balance_limit_without_kyc",
- &key_data->wallet_balance_limit_without_kyc),
- NULL),
- GNUNET_JSON_spec_end ()
- };
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ const json_t *wblwk = NULL;
+ const json_t *global_fees;
+ const json_t *sign_keys_array;
+ const json_t *denominations_by_group;
+ const json_t *auditors_array;
+ const json_t *recoup_array = NULL;
+ const json_t *manifests = NULL;
+ bool no_extensions = false;
+ bool no_signature = false;
+ const json_t *accounts;
+ const json_t *fees;
+ const json_t *wads;
+ struct SignatureContext sig_ctx = { 0 };
if (JSON_OBJECT != json_typeof (resp_obj))
{
@@ -768,14 +801,10 @@ decode_keys_json (const json_t *resp_obj,
#endif
/* check the version first */
{
- const char *ver;
- unsigned int age;
- unsigned int revision;
- unsigned int current;
- char dummy;
+ struct TALER_JSON_ProtocolVersion pv;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("version",
- &ver),
+ TALER_JSON_spec_version ("version",
+ &pv),
GNUNET_JSON_spec_end ()
};
@@ -787,278 +816,378 @@ decode_keys_json (const json_t *resp_obj,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- if (3 != sscanf (ver,
- "%u:%u:%u%c",
- &current,
- &revision,
- &age,
- &dummy))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
*vc = TALER_EXCHANGE_VC_MATCH;
- if (EXCHANGE_PROTOCOL_CURRENT < current)
+ if (EXCHANGE_PROTOCOL_CURRENT < pv.current)
{
*vc |= TALER_EXCHANGE_VC_NEWER;
- if (EXCHANGE_PROTOCOL_CURRENT < current - age)
+ if (EXCHANGE_PROTOCOL_CURRENT < pv.current - pv.age)
*vc |= TALER_EXCHANGE_VC_INCOMPATIBLE;
}
- if (EXCHANGE_PROTOCOL_CURRENT > current)
+ if (EXCHANGE_PROTOCOL_CURRENT > pv.current)
{
*vc |= TALER_EXCHANGE_VC_OLDER;
- if (EXCHANGE_PROTOCOL_CURRENT - EXCHANGE_PROTOCOL_AGE > current)
+ if (EXCHANGE_PROTOCOL_CURRENT - EXCHANGE_PROTOCOL_AGE > pv.current)
*vc |= TALER_EXCHANGE_VC_INCOMPATIBLE;
}
- key_data->version = GNUNET_strdup (ver);
}
- EXITIF (GNUNET_OK !=
- GNUNET_JSON_parse (resp_obj,
- (check_sig) ? mspec : &mspec[2],
- NULL, NULL));
- key_data->currency = GNUNET_strdup (currency);
-
- if (GNUNET_OK ==
- TALER_amount_is_valid (&key_data->wallet_balance_limit_without_kyc))
{
- if (0 != strcasecmp (currency,
- key_data->wallet_balance_limit_without_kyc.currency))
+ const char *ver;
+ const char *currency;
+ const char *asset_type;
+ struct GNUNET_JSON_Specification mspec[] = {
+ GNUNET_JSON_spec_fixed_auto (
+ "exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto (
+ "exchange_pub",
+ &exchange_pub),
+ GNUNET_JSON_spec_fixed_auto (
+ "master_public_key",
+ &key_data->master_pub),
+ GNUNET_JSON_spec_array_const ("accounts",
+ &accounts),
+ GNUNET_JSON_spec_object_const ("wire_fees",
+ &fees),
+ GNUNET_JSON_spec_array_const ("wads",
+ &wads),
+ GNUNET_JSON_spec_timestamp (
+ "list_issue_date",
+ &key_data->list_issue_date),
+ GNUNET_JSON_spec_relative_time (
+ "reserve_closing_delay",
+ &key_data->reserve_closing_delay),
+ GNUNET_JSON_spec_string (
+ "currency",
+ &currency),
+ GNUNET_JSON_spec_string (
+ "asset_type",
+ &asset_type),
+ GNUNET_JSON_spec_array_const (
+ "global_fees",
+ &global_fees),
+ GNUNET_JSON_spec_array_const (
+ "signkeys",
+ &sign_keys_array),
+ GNUNET_JSON_spec_array_const (
+ "denominations",
+ &denominations_by_group),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const (
+ "recoup",
+ &recoup_array),
+ NULL),
+ GNUNET_JSON_spec_array_const (
+ "auditors",
+ &auditors_array),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool (
+ "rewards_allowed",
+ &key_data->rewards_allowed),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("extensions",
+ &manifests),
+ &no_extensions),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto (
+ "extensions_sig",
+ &key_data->extensions_sig),
+ &no_signature),
+ GNUNET_JSON_spec_string ("version",
+ &ver),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const (
+ "wallet_balance_limit_without_kyc",
+ &wblwk),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *emsg;
+ unsigned int eline;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (resp_obj,
+ (check_sig) ? mspec : &mspec[2],
+ &emsg,
+ &eline))
{
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Parsing /keys failed for `%s' (%u)\n",
+ emsg,
+ eline);
+ EXITIF (1);
+ }
+ {
+ struct GNUNET_JSON_Specification sspec[] = {
+ TALER_JSON_spec_currency_specification (
+ "currency_specification",
+ currency,
+ &key_data->cspec),
+ TALER_JSON_spec_amount (
+ "stefan_abs",
+ currency,
+ &key_data->stefan_abs),
+ TALER_JSON_spec_amount (
+ "stefan_log",
+ currency,
+ &key_data->stefan_log),
+ GNUNET_JSON_spec_double (
+ "stefan_lin",
+ &key_data->stefan_lin),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (resp_obj,
+ sspec,
+ &emsg,
+ &eline))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Parsing /keys failed for `%s' (%u)\n",
+ emsg,
+ eline);
+ EXITIF (1);
+ }
}
+
+ key_data->currency = GNUNET_strdup (currency);
+ key_data->version = GNUNET_strdup (ver);
+ key_data->asset_type = GNUNET_strdup (asset_type);
+ if (! no_extensions)
+ key_data->extensions = json_incref ((json_t *) manifests);
}
/* parse the global fees */
+ EXITIF (json_array_size (global_fees) > UINT_MAX);
+ key_data->num_global_fees
+ = (unsigned int) json_array_size (global_fees);
+ if (0 != key_data->num_global_fees)
{
- json_t *global_fees;
json_t *global_fee;
- unsigned int index;
+ size_t index;
- EXITIF (NULL == (global_fees =
- json_object_get (resp_obj,
- "global_fees")));
- EXITIF (! json_is_array (global_fees));
- if (0 != (key_data->num_global_fees =
- json_array_size (global_fees)))
+ key_data->global_fees
+ = GNUNET_new_array (key_data->num_global_fees,
+ struct TALER_EXCHANGE_GlobalFee);
+ json_array_foreach (global_fees, index, global_fee)
{
- key_data->global_fees
- = GNUNET_new_array (key_data->num_global_fees,
- struct TALER_EXCHANGE_GlobalFee);
- json_array_foreach (global_fees, index, global_fee) {
- EXITIF (GNUNET_SYSERR ==
- parse_global_fee (&key_data->global_fees[index],
- check_sig,
- global_fee,
- key_data));
- }
+ EXITIF (GNUNET_SYSERR ==
+ parse_global_fee (&key_data->global_fees[index],
+ check_sig,
+ global_fee,
+ key_data));
}
}
/* parse the signing keys */
+ EXITIF (json_array_size (sign_keys_array) > UINT_MAX);
+ key_data->num_sign_keys
+ = (unsigned int) json_array_size (sign_keys_array);
+ if (0 != key_data->num_sign_keys)
{
- json_t *sign_keys_array;
json_t *sign_key_obj;
- unsigned int index;
+ size_t index;
- EXITIF (NULL == (sign_keys_array =
- json_object_get (resp_obj,
- "signkeys")));
- EXITIF (! json_is_array (sign_keys_array));
- if (0 != (key_data->num_sign_keys =
- json_array_size (sign_keys_array)))
+ key_data->sign_keys
+ = GNUNET_new_array (key_data->num_sign_keys,
+ struct TALER_EXCHANGE_SigningPublicKey);
+ json_array_foreach (sign_keys_array, index, sign_key_obj) {
+ EXITIF (GNUNET_SYSERR ==
+ parse_json_signkey (&key_data->sign_keys[index],
+ check_sig,
+ sign_key_obj,
+ &key_data->master_pub));
+ }
+ }
+
+ /* Parse balance limits */
+ if (NULL != wblwk)
+ {
+ EXITIF (json_array_size (wblwk) > UINT_MAX);
+ key_data->wblwk_length
+ = (unsigned int) json_array_size (wblwk);
+ key_data->wallet_balance_limit_without_kyc
+ = GNUNET_new_array (key_data->wblwk_length,
+ struct TALER_Amount);
+ for (unsigned int i = 0; i<key_data->wblwk_length; i++)
{
- key_data->sign_keys
- = GNUNET_new_array (key_data->num_sign_keys,
- struct TALER_EXCHANGE_SigningPublicKey);
- json_array_foreach (sign_keys_array, index, sign_key_obj) {
- EXITIF (GNUNET_SYSERR ==
- parse_json_signkey (&key_data->sign_keys[index],
- check_sig,
- sign_key_obj,
- &key_data->master_pub));
- }
+ struct TALER_Amount *a = &key_data->wallet_balance_limit_without_kyc[i];
+ const json_t *aj = json_array_get (wblwk,
+ i);
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount (NULL,
+ key_data->currency,
+ a),
+ GNUNET_JSON_spec_end ()
+ };
+
+ EXITIF (GNUNET_OK !=
+ GNUNET_JSON_parse (aj,
+ spec,
+ NULL, NULL));
}
}
+ /* Parse wire accounts */
+ key_data->fees = parse_fees (&key_data->master_pub,
+ key_data->currency,
+ fees,
+ &key_data->fees_len);
+ EXITIF (NULL == key_data->fees);
+ /* parse accounts */
+ EXITIF (json_array_size (accounts) > UINT_MAX);
+ GNUNET_array_grow (key_data->accounts,
+ key_data->accounts_len,
+ json_array_size (accounts));
+ EXITIF (GNUNET_OK !=
+ TALER_EXCHANGE_parse_accounts (&key_data->master_pub,
+ accounts,
+ key_data->accounts_len,
+ key_data->accounts));
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Parsed %u wire accounts from JSON\n",
+ key_data->accounts_len);
+
+
/* Parse the supported extension(s): age-restriction. */
/* TODO: maybe lift all this into a FP in TALER_Extension ? */
+ if (! no_extensions)
{
- struct TALER_MasterSignatureP extensions_sig = {0};
- json_t *extensions = NULL;
- struct GNUNET_JSON_Specification ext_spec[] = {
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("extensions",
- &extensions),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto (
- "extensions_sig",
- &extensions_sig),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- /* 1. Search for extensions in the response to /keys */
- EXITIF (GNUNET_OK !=
- GNUNET_JSON_parse (resp_obj,
- ext_spec,
- NULL, NULL));
-
- if (NULL != extensions)
+ if (no_signature)
{
- /* 2. We have an extensions object. Verify its signature. */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "found extensions without signature\n");
+ }
+ else
+ {
+ /* We have an extensions object. Verify its signature. */
EXITIF (GNUNET_OK !=
- TALER_extensions_verify_json_config_signature (
- extensions,
- &extensions_sig,
+ TALER_extensions_verify_manifests_signature (
+ manifests,
+ &key_data->extensions_sig,
&key_data->master_pub));
- /* 3. Parse and set the the configuration of the extensions accordingly */
+ /* Parse and set the the configuration of the extensions accordingly */
EXITIF (GNUNET_OK !=
- TALER_extensions_load_json_config (extensions));
+ TALER_extensions_load_manifests (manifests));
}
- /* 4. assuming we might have now a new value for age_mask, set it in key_data */
- key_data->age_mask = TALER_extensions_age_restriction_ageMask ();
+ /* Assuming we might have now a new value for age_mask, set it in key_data */
+ key_data->age_mask = TALER_extensions_get_age_restriction_mask ();
}
- /**
+ /*
* Parse the denomination keys, merging with the
* possibly EXISTING array as required (/keys cherry picking).
*
* The denominations are grouped by common values of
* {cipher, value, fee, age_mask}.
- **/
+ */
{
- json_t *denominations_by_group;
json_t *group_obj;
unsigned int group_idx;
- denominations_by_group =
- json_object_get (
- resp_obj,
- "denominations");
-
- EXITIF (JSON_ARRAY !=
- json_typeof (denominations_by_group));
-
- json_array_foreach (denominations_by_group, group_idx, group_obj) {
- // Running XOR of each SHA512 hash of the denominations' public key in
- // this group. Used to compare against group.hash after all keys have
- // been parsed.
- struct GNUNET_HashCode group_hash_xor = {0};
-
- // First, parse { cipher, fees, value, age_mask, hash } of the current
- // group.
+ json_array_foreach (denominations_by_group,
+ group_idx,
+ group_obj)
+ {
+ /* First, parse { cipher, fees, value, age_mask, hash } of the current
+ group. */
struct TALER_DenominationGroup group = {0};
+ const json_t *denom_keys_array;
struct GNUNET_JSON_Specification group_spec[] = {
- TALER_JSON_spec_denomination_group (NULL, currency, &group),
+ TALER_JSON_spec_denomination_group (NULL,
+ key_data->currency,
+ &group),
+ GNUNET_JSON_spec_array_const ("denoms",
+ &denom_keys_array),
GNUNET_JSON_spec_end ()
};
+ json_t *denom_key_obj;
+ unsigned int index;
+
EXITIF (GNUNET_SYSERR ==
GNUNET_JSON_parse (group_obj,
group_spec,
NULL,
NULL));
- // Now, parse the individual denominations
+ /* Now, parse the individual denominations */
+ json_array_foreach (denom_keys_array,
+ index,
+ denom_key_obj)
{
- json_t *denom_keys_array;
- json_t *denom_key_obj;
- unsigned int index;
- denom_keys_array = json_object_get (group_obj, "denoms");
- EXITIF (JSON_ARRAY != json_typeof (denom_keys_array));
-
- json_array_foreach (denom_keys_array, index, denom_key_obj) {
- struct TALER_EXCHANGE_DenomPublicKey dk = {0};
- bool found = false;
-
- memset (&dk, 0, sizeof (dk));
-
- // Set the common fields from the group for this particular
- // denomination. Required to make the validity check inside
- // parse_json_denomkey_partially pass
- dk.key.cipher = group.cipher;
- dk.value = group.value;
- dk.fees = group.fees;
- dk.key.age_mask = group.age_mask;
-
- EXITIF (GNUNET_SYSERR ==
- parse_json_denomkey_partially (key_data->currency,
- &dk,
- group.cipher,
- check_sig,
- denom_key_obj,
- &key_data->master_pub,
- check_sig ? &hash_xor : NULL));
-
- // Build the running xor of the SHA512-hash of the public keys
- {
- struct TALER_DenominationHashP hc = {0};
- TALER_denom_pub_hash (&dk.key, &hc);
- GNUNET_CRYPTO_hash_xor (&hc.hash,
- &group_hash_xor,
- &group_hash_xor);
- }
+ /* Set the common fields from the group for this particular
+ denomination. Required to make the validity check inside
+ parse_json_denomkey_partially pass */
+ struct TALER_EXCHANGE_DenomPublicKey dk = {
+ .value = group.value,
+ .fees = group.fees,
+ .key.age_mask = group.age_mask
+ };
+ bool found = false;
- for (unsigned int j = 0;
- j<key_data->num_denom_keys;
- j++)
+ EXITIF (GNUNET_SYSERR ==
+ parse_json_denomkey_partially (&dk,
+ group.cipher,
+ check_sig,
+ denom_key_obj,
+ &key_data->master_pub,
+ group_idx,
+ index,
+ check_sig
+ ? &sig_ctx
+ : NULL));
+ for (unsigned int j = 0;
+ j<key_data->num_denom_keys;
+ j++)
+ {
+ if (0 == denoms_cmp (&dk,
+ &key_data->denom_keys[j]))
{
- if (0 == denoms_cmp (&dk,
- &key_data->denom_keys[j]))
- {
- found = true;
- break;
- }
+ found = true;
+ break;
}
+ }
- if (found)
- {
- /* 0:0:0 did not support /keys cherry picking */
- TALER_LOG_DEBUG ("Skipping denomination key: already know it\n");
- TALER_denom_pub_free (&dk.key);
- continue;
- }
+ if (found)
+ {
+ /* 0:0:0 did not support /keys cherry picking */
+ TALER_LOG_DEBUG ("Skipping denomination key: already know it\n");
+ TALER_denom_pub_free (&dk.key);
+ continue;
+ }
- if (key_data->denom_keys_size == key_data->num_denom_keys)
- GNUNET_array_grow (key_data->denom_keys,
- key_data->denom_keys_size,
- key_data->denom_keys_size * 2 + 2);
- key_data->denom_keys[key_data->num_denom_keys++] = dk;
-
- /* Update "last_denom_issue_date" */
- TALER_LOG_DEBUG ("Adding denomination key that is valid_until %s\n",
- GNUNET_TIME_timestamp2s (dk.valid_from));
- key_data->last_denom_issue_date
- = GNUNET_TIME_timestamp_max (key_data->last_denom_issue_date,
- dk.valid_from);
- }; // json_array_foreach over denominations
-
- // The calculated group_hash_xor must be the same as group.hash from
- // the json.
- EXITIF (0 !=
- GNUNET_CRYPTO_hash_cmp (&group_hash_xor, &group.hash));
-
- } // block for parsing individual denominations
- }; // json_array_foreach over groups of denominations
- }
+ if (key_data->denom_keys_size == key_data->num_denom_keys)
+ GNUNET_array_grow (key_data->denom_keys,
+ key_data->denom_keys_size,
+ key_data->denom_keys_size * 2 + 2);
+ GNUNET_assert (key_data->denom_keys_size >
+ key_data->num_denom_keys);
+ GNUNET_assert (key_data->num_denom_keys < UINT_MAX);
+ key_data->denom_keys[key_data->num_denom_keys++] = dk;
+
+ /* Update "last_denom_issue_date" */
+ TALER_LOG_DEBUG ("Adding denomination key that is valid_until %s\n",
+ GNUNET_TIME_timestamp2s (dk.valid_from));
+ key_data->last_denom_issue_date
+ = GNUNET_TIME_timestamp_max (key_data->last_denom_issue_date,
+ dk.valid_from);
+ }; /* end of json_array_foreach over denominations */
+ } /* end of json_array_foreach over groups of denominations */
+ } /* end of scope for group_ojb/group_idx */
/* parse the auditor information */
{
- json_t *auditors_array;
json_t *auditor_info;
unsigned int index;
- EXITIF (NULL == (auditors_array =
- json_object_get (resp_obj,
- "auditors")));
- EXITIF (JSON_ARRAY != json_typeof (auditors_array));
-
/* Merge with the existing auditor information we have (/keys cherry picking) */
- json_array_foreach (auditors_array, index, auditor_info) {
+ json_array_foreach (auditors_array, index, auditor_info)
+ {
struct TALER_EXCHANGE_AuditorInformation ai;
bool found = false;
@@ -1117,64 +1246,85 @@ decode_keys_json (const json_t *resp_obj,
GNUNET_array_grow (key_data->auditors,
key_data->auditors_size,
key_data->auditors_size * 2 + 2);
+ GNUNET_assert (key_data->auditors_size >
+ key_data->num_auditors);
GNUNET_assert (NULL != ai.auditor_url);
+ GNUNET_assert (key_data->num_auditors < UINT_MAX);
key_data->auditors[key_data->num_auditors++] = ai;
};
}
/* parse the revocation/recoup information */
+ if (NULL != recoup_array)
{
- json_t *recoup_array;
json_t *recoup_info;
unsigned int index;
- if (NULL != (recoup_array =
- json_object_get (resp_obj,
- "recoup")))
+ json_array_foreach (recoup_array, index, recoup_info)
{
- EXITIF (JSON_ARRAY != json_typeof (recoup_array));
-
- json_array_foreach (recoup_array, index, recoup_info) {
- struct TALER_DenominationHashP h_denom_pub;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
- &h_denom_pub),
- GNUNET_JSON_spec_end ()
- };
+ struct TALER_DenominationHashP h_denom_pub;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+ &h_denom_pub),
+ GNUNET_JSON_spec_end ()
+ };
- EXITIF (GNUNET_OK !=
- GNUNET_JSON_parse (recoup_info,
- spec,
- NULL, NULL));
- for (unsigned int j = 0;
- j<key_data->num_denom_keys;
- j++)
+ EXITIF (GNUNET_OK !=
+ GNUNET_JSON_parse (recoup_info,
+ spec,
+ NULL, NULL));
+ for (unsigned int j = 0;
+ j<key_data->num_denom_keys;
+ j++)
+ {
+ if (0 == GNUNET_memcmp (&h_denom_pub,
+ &key_data->denom_keys[j].h_key))
{
- if (0 == GNUNET_memcmp (&h_denom_pub,
- &key_data->denom_keys[j].h_key))
- {
- key_data->denom_keys[j].revoked = GNUNET_YES;
- break;
- }
+ key_data->denom_keys[j].revoked = true;
+ break;
}
- };
+ }
}
}
if (check_sig)
{
+ struct GNUNET_HashContext *hash_context;
+ struct GNUNET_HashCode hc;
+
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+ qsort (sig_ctx.elements,
+ sig_ctx.elements_pos,
+ sizeof (struct SignatureElement),
+ &signature_context_sort_cb);
+ for (unsigned int i = 0; i<sig_ctx.elements_pos; i++)
+ {
+ struct SignatureElement *element = &sig_ctx.elements[i];
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Adding %u,%u,%s\n",
+ element->group_offset,
+ element->offset,
+ TALER_B2S (&element->master_sig));
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &element->master_sig,
+ sizeof (element->master_sig));
+ }
+ GNUNET_array_grow (sig_ctx.elements,
+ sig_ctx.elements_size,
+ 0);
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ &hc);
EXITIF (GNUNET_OK !=
TALER_EXCHANGE_test_signing_key (key_data,
- &pub));
-
+ &exchange_pub));
EXITIF (GNUNET_OK !=
TALER_exchange_online_key_set_verify (
key_data->list_issue_date,
- &hash_xor,
- &pub,
- &denominations_sig));
+ &hc,
+ &exchange_pub,
+ &exchange_sig));
}
-
return GNUNET_OK;
EXITIF_exit:
@@ -1184,87 +1334,6 @@ EXITIF_exit:
/**
- * Free key data object.
- *
- * @param key_data data to free (pointer itself excluded)
- */
-static void
-free_key_data (struct TALER_EXCHANGE_Keys *key_data)
-{
- GNUNET_array_grow (key_data->sign_keys,
- key_data->num_sign_keys,
- 0);
- for (unsigned int i = 0; i<key_data->num_denom_keys; i++)
- TALER_denom_pub_free (&key_data->denom_keys[i].key);
-
- GNUNET_array_grow (key_data->denom_keys,
- key_data->denom_keys_size,
- 0);
- for (unsigned int i = 0; i<key_data->num_auditors; i++)
- {
- GNUNET_array_grow (key_data->auditors[i].denom_keys,
- key_data->auditors[i].num_denom_keys,
- 0);
- GNUNET_free (key_data->auditors[i].auditor_url);
- }
- GNUNET_array_grow (key_data->auditors,
- key_data->auditors_size,
- 0);
- GNUNET_free (key_data->version);
- GNUNET_free (key_data->currency);
- GNUNET_free (key_data->global_fees);
-}
-
-
-/**
- * Initiate download of /keys from the exchange.
- *
- * @param cls exchange where to download /keys from
- */
-static void
-request_keys (void *cls);
-
-
-void
-TALER_EXCHANGE_set_last_denom (struct TALER_EXCHANGE_Handle *exchange,
- struct GNUNET_TIME_Timestamp last_denom_new)
-{
- TALER_LOG_DEBUG (
- "Application explicitly set last denomination validity to %s\n",
- GNUNET_TIME_timestamp2s (last_denom_new));
- exchange->key_data.last_denom_issue_date = last_denom_new;
-}
-
-
-struct GNUNET_TIME_Timestamp
-TALER_EXCHANGE_check_keys_current (struct TALER_EXCHANGE_Handle *exchange,
- enum TALER_EXCHANGE_CheckKeysFlags flags)
-{
- bool force_download = 0 != (flags & TALER_EXCHANGE_CKF_FORCE_DOWNLOAD);
- bool pull_all_keys = 0 != (flags & TALER_EXCHANGE_CKF_PULL_ALL_KEYS);
-
- if (NULL != exchange->kr)
- return GNUNET_TIME_UNIT_ZERO_TS;
-
- if (pull_all_keys)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Forcing re-download of all exchange keys\n");
- GNUNET_break (GNUNET_YES == force_download);
- exchange->state = MHS_INIT;
- }
- if ( (! force_download) &&
- (GNUNET_TIME_absolute_is_future (
- exchange->key_data_expiration.abs_time)) )
- return exchange->key_data_expiration;
- if (NULL == exchange->retry_task)
- exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys,
- exchange);
- return GNUNET_TIME_UNIT_ZERO_TS;
-}
-
-
-/**
* Callback used when downloading the reply to a /keys request
* is complete.
*
@@ -1277,132 +1346,122 @@ keys_completed_cb (void *cls,
long response_code,
const void *resp_obj)
{
- struct KeysRequest *kr = cls;
- struct TALER_EXCHANGE_Handle *exchange = kr->exchange;
- struct TALER_EXCHANGE_Keys kd;
- struct TALER_EXCHANGE_Keys kd_old;
- enum TALER_EXCHANGE_VersionCompatibility vc;
+ struct TALER_EXCHANGE_GetKeysHandle *gkh = cls;
const json_t *j = resp_obj;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
+ struct TALER_EXCHANGE_Keys *kd = NULL;
+ struct TALER_EXCHANGE_KeysResponse kresp = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code,
+ .details.ok.compat = TALER_EXCHANGE_VC_PROTOCOL_ERROR,
};
+ gkh->job = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Received keys from URL `%s' with status %ld and expiration %s.\n",
- kr->url,
+ gkh->url,
response_code,
- GNUNET_TIME_timestamp2s (kr->expire));
- if (GNUNET_TIME_absolute_is_past (kr->expire.abs_time))
+ GNUNET_TIME_timestamp2s (gkh->expire));
+ if (GNUNET_TIME_absolute_is_past (gkh->expire.abs_time))
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Exchange failed to give expiration time, assuming in %s\n",
- GNUNET_TIME_relative2s (DEFAULT_EXPIRATION,
- true));
- kr->expire
+ if (MHD_HTTP_OK == response_code)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Exchange failed to give expiration time, assuming in %s\n",
+ GNUNET_TIME_relative2s (DEFAULT_EXPIRATION,
+ true));
+ gkh->expire
= GNUNET_TIME_absolute_to_timestamp (
GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION));
}
- kd_old = exchange->key_data;
- memset (&kd,
- 0,
- sizeof (struct TALER_EXCHANGE_Keys));
- vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR;
switch (response_code)
{
case 0:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to receive /keys response from exchange %s\n",
- exchange->url);
- free_keys_request (kr);
- exchange->keys_error_count++;
- exchange->kr = NULL;
- GNUNET_assert (NULL == exchange->retry_task);
- exchange->retry_delay = EXCHANGE_LIB_BACKOFF (exchange->retry_delay);
- exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay,
- &request_keys,
- exchange);
- return;
+ gkh->exchange_url);
+ break;
case MHD_HTTP_OK:
- exchange->keys_error_count = 0;
if (NULL == j)
{
+ GNUNET_break (0);
response_code = 0;
break;
}
- /* We keep the denomination keys and auditor signatures from the
- previous iteration (/keys cherry picking) */
- kd.num_denom_keys = kd_old.num_denom_keys;
- kd.last_denom_issue_date = kd_old.last_denom_issue_date;
- GNUNET_array_grow (kd.denom_keys,
- kd.denom_keys_size,
- kd.num_denom_keys);
-
- /* First make a shallow copy, we then need another pass for the RSA key... */
- memcpy (kd.denom_keys,
- kd_old.denom_keys,
- kd_old.num_denom_keys * sizeof (struct
- TALER_EXCHANGE_DenomPublicKey));
-
- for (unsigned int i = 0; i<kd_old.num_denom_keys; i++)
- TALER_denom_pub_deep_copy (&kd.denom_keys[i].key,
- &kd_old.denom_keys[i].key);
-
- kd.num_auditors = kd_old.num_auditors;
- kd.auditors = GNUNET_new_array (kd.num_auditors,
- struct TALER_EXCHANGE_AuditorInformation);
- /* Now the necessary deep copy... */
- for (unsigned int i = 0; i<kd_old.num_auditors; i++)
+ kd = GNUNET_new (struct TALER_EXCHANGE_Keys);
+ kd->exchange_url = GNUNET_strdup (gkh->exchange_url);
+ if (NULL != gkh->prev_keys)
{
- const struct TALER_EXCHANGE_AuditorInformation *aold =
- &kd_old.auditors[i];
- struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i];
-
- anew->auditor_pub = aold->auditor_pub;
- GNUNET_assert (NULL != aold->auditor_url);
- anew->auditor_url = GNUNET_strdup (aold->auditor_url);
- GNUNET_array_grow (anew->denom_keys,
- anew->num_denom_keys,
- aold->num_denom_keys);
- memcpy (anew->denom_keys,
- aold->denom_keys,
- aold->num_denom_keys
- * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo));
- }
+ const struct TALER_EXCHANGE_Keys *kd_old = gkh->prev_keys;
+
+ /* We keep the denomination keys and auditor signatures from the
+ previous iteration (/keys cherry picking) */
+ kd->num_denom_keys
+ = kd_old->num_denom_keys;
+ kd->last_denom_issue_date
+ = kd_old->last_denom_issue_date;
+ GNUNET_array_grow (kd->denom_keys,
+ kd->denom_keys_size,
+ kd->num_denom_keys);
+ /* First make a shallow copy, we then need another pass for the RSA key... */
+ GNUNET_memcpy (kd->denom_keys,
+ kd_old->denom_keys,
+ kd_old->num_denom_keys
+ * sizeof (struct TALER_EXCHANGE_DenomPublicKey));
+ for (unsigned int i = 0; i<kd_old->num_denom_keys; i++)
+ TALER_denom_pub_copy (&kd->denom_keys[i].key,
+ &kd_old->denom_keys[i].key);
+ kd->num_auditors = kd_old->num_auditors;
+ kd->auditors = GNUNET_new_array (kd->num_auditors,
+ struct TALER_EXCHANGE_AuditorInformation);
+ /* Now the necessary deep copy... */
+ for (unsigned int i = 0; i<kd_old->num_auditors; i++)
+ {
+ const struct TALER_EXCHANGE_AuditorInformation *aold =
+ &kd_old->auditors[i];
+ struct TALER_EXCHANGE_AuditorInformation *anew = &kd->auditors[i];
- /* Old auditors got just copied into new ones. */
+ anew->auditor_pub = aold->auditor_pub;
+ anew->auditor_url = GNUNET_strdup (aold->auditor_url);
+ GNUNET_array_grow (anew->denom_keys,
+ anew->num_denom_keys,
+ aold->num_denom_keys);
+ GNUNET_memcpy (
+ anew->denom_keys,
+ aold->denom_keys,
+ aold->num_denom_keys
+ * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo));
+ }
+ }
+ /* Now decode fresh /keys response */
if (GNUNET_OK !=
decode_keys_json (j,
true,
- &kd,
- &vc))
+ kd,
+ &kresp.details.ok.compat))
{
TALER_LOG_ERROR ("Could not decode /keys response\n");
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- for (unsigned int i = 0; i<kd.num_auditors; i++)
- {
- struct TALER_EXCHANGE_AuditorInformation *anew = &kd.auditors[i];
-
- GNUNET_array_grow (anew->denom_keys,
- anew->num_denom_keys,
- 0);
- GNUNET_free (anew->auditor_url);
- }
- GNUNET_free (kd.auditors);
- kd.auditors = NULL;
- kd.num_auditors = 0;
- for (unsigned int i = 0; i<kd_old.num_denom_keys; i++)
- TALER_denom_pub_free (&kd.denom_keys[i].key);
- GNUNET_array_grow (kd.denom_keys,
- kd.denom_keys_size,
- 0);
- kd.num_denom_keys = 0;
+ kd->rc = 1;
+ TALER_EXCHANGE_keys_decref (kd);
+ kd = NULL;
+ kresp.hr.http_status = 0;
+ kresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
- json_decref (exchange->key_data_raw);
- exchange->key_data_raw = json_deep_copy (j);
- exchange->retry_delay = GNUNET_TIME_UNIT_ZERO;
+ kd->rc = 1;
+ kd->key_data_expiration = gkh->expire;
+ if (GNUNET_TIME_relative_cmp (
+ GNUNET_TIME_absolute_get_remaining (gkh->expire.abs_time),
+ <,
+ MINIMUM_EXPIRATION))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Exchange returned keys with expiration time below %s. Compensating.\n",
+ GNUNET_TIME_relative2s (MINIMUM_EXPIRATION,
+ true));
+ kd->key_data_expiration
+ = GNUNET_TIME_relative_to_timestamp (MINIMUM_EXPIRATION);
+ }
+
+ kresp.details.ok.keys = kd;
break;
case MHD_HTTP_BAD_REQUEST:
case MHD_HTTP_UNAUTHORIZED:
@@ -1410,108 +1469,36 @@ keys_completed_cb (void *cls,
case MHD_HTTP_NOT_FOUND:
if (NULL == j)
{
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.hint = TALER_ErrorCode_get_hint (hr.ec);
+ kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
}
else
{
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ kresp.hr.ec = TALER_JSON_get_error_code (j);
+ kresp.hr.hint = TALER_JSON_get_error_hint (j);
}
break;
default:
- if (MHD_HTTP_GATEWAY_TIMEOUT == response_code)
- exchange->keys_error_count++;
if (NULL == j)
{
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.hint = TALER_ErrorCode_get_hint (hr.ec);
+ kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
}
else
{
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ kresp.hr.ec = TALER_JSON_get_error_code (j);
+ kresp.hr.hint = TALER_JSON_get_error_hint (j);
}
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) kresp.hr.ec);
break;
}
- exchange->key_data = kd;
- if (GNUNET_TIME_absolute_is_past (
- exchange->key_data.last_denom_issue_date.abs_time))
- TALER_LOG_WARNING ("Last DK issue date from exchange is in the past: %s\n",
- GNUNET_TIME_timestamp2s (
- exchange->key_data.last_denom_issue_date));
- else
- TALER_LOG_DEBUG ("Last DK issue date updated to: %s\n",
- GNUNET_TIME_timestamp2s (
- exchange->key_data.last_denom_issue_date));
-
-
- if (MHD_HTTP_OK != response_code)
- {
- exchange->kr = NULL;
- free_keys_request (kr);
- exchange->state = MHS_FAILED;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Exchange keys download failed\n");
- if (NULL != exchange->key_data_raw)
- {
- json_decref (exchange->key_data_raw);
- exchange->key_data_raw = NULL;
- }
- free_key_data (&kd_old);
- /* notify application that we failed */
- exchange->cert_cb (exchange->cert_cb_cls,
- &hr,
- NULL,
- vc);
- return;
- }
-
- exchange->kr = NULL;
- exchange->key_data_expiration = kr->expire;
- free_keys_request (kr);
- exchange->state = MHS_CERT;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Successfully downloaded exchange's keys\n");
- update_auditors (exchange);
- /* notify application about the key information */
- exchange->cert_cb (exchange->cert_cb_cls,
- &hr,
- &exchange->key_data,
- vc);
- free_key_data (&kd_old);
-}
-
-
-/* ********************* library internal API ********* */
-
-
-struct GNUNET_CURL_Context *
-TEAH_handle_to_context (struct TALER_EXCHANGE_Handle *h)
-{
- return h->ctx;
-}
-
-
-enum GNUNET_GenericReturnValue
-TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h)
-{
- return (MHS_CERT == h->state) ? GNUNET_YES : GNUNET_NO;
-}
-
-
-char *
-TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h,
- const char *path)
-{
- GNUNET_assert ('/' == path[0]);
- return TALER_url_join (h->url,
- path + 1,
- NULL);
+ gkh->cert_cb (gkh->cert_cb_cls,
+ &kresp,
+ kd);
+ TALER_EXCHANGE_get_keys_cancel (gkh);
}
@@ -1525,7 +1512,7 @@ TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h,
* Parse HTTP timestamp.
*
* @param dateline header to parse header
- * @param at where to write the result
+ * @param[out] at where to write the result
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
@@ -1625,7 +1612,7 @@ parse_date_string (const char *dateline,
* @param buffer header data received
* @param size size of an item in @a buffer
* @param nitems number of items in @a buffer
- * @param userdata the `struct KeysRequest`
+ * @param userdata the `struct TALER_EXCHANGE_GetKeysHandle`
* @return `size * nitems` on success (everything else aborts)
*/
static size_t
@@ -1634,7 +1621,7 @@ header_cb (char *buffer,
size_t nitems,
void *userdata)
{
- struct KeysRequest *kr = userdata;
+ struct TALER_EXCHANGE_GetKeysHandle *kr = userdata;
size_t total = size * nitems;
char *val;
@@ -1646,6 +1633,10 @@ header_cb (char *buffer,
return total;
val = GNUNET_strndup (&buffer[strlen (MHD_HTTP_HEADER_EXPIRES ": ")],
total - strlen (MHD_HTTP_HEADER_EXPIRES ": "));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Found %s header `%s'\n",
+ MHD_HTTP_HEADER_EXPIRES,
+ val);
if (GNUNET_OK !=
parse_date_string (val,
&kr->expire))
@@ -1661,409 +1652,66 @@ header_cb (char *buffer,
}
-/* ********************* public API ******************* */
-
-
-/**
- * Deserialize the key data and use it to bootstrap @a exchange to
- * more efficiently recover the state. Errors in @a data must be
- * tolerated (i.e. by re-downloading instead).
- *
- * @param exchange which exchange's key and wire data should be deserialized
- * @param data the data to deserialize
- */
-static void
-deserialize_data (struct TALER_EXCHANGE_Handle *exchange,
- const json_t *data)
-{
- enum TALER_EXCHANGE_VersionCompatibility vc;
- json_t *keys;
- const char *url;
- uint32_t version;
- struct GNUNET_TIME_Timestamp expire;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint32 ("version",
- &version),
- GNUNET_JSON_spec_json ("keys",
- &keys),
- GNUNET_JSON_spec_string ("exchange_url",
- &url),
- GNUNET_JSON_spec_timestamp ("expire",
- &expire),
- GNUNET_JSON_spec_end ()
- };
- struct TALER_EXCHANGE_Keys key_data;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .ec = TALER_EC_NONE,
- .http_status = MHD_HTTP_OK,
- .reply = data
- };
-
- if (NULL == data)
- return;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (data,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return;
- }
- if (0 != version)
- {
- GNUNET_JSON_parse_free (spec);
- return; /* unsupported version */
- }
- if (0 != strcmp (url,
- exchange->url))
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return;
- }
- memset (&key_data,
- 0,
- sizeof (struct TALER_EXCHANGE_Keys));
- if (GNUNET_OK !=
- decode_keys_json (keys,
- false,
- &key_data,
- &vc))
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return;
- }
- /* decode successful, initialize with the result */
- GNUNET_assert (NULL == exchange->key_data_raw);
- exchange->key_data_raw = json_deep_copy (keys);
- exchange->key_data = key_data;
- exchange->key_data_expiration = expire;
- exchange->state = MHS_CERT;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Successfully loaded exchange's keys via deserialization\n");
- update_auditors (exchange);
- /* notify application about the key information */
- exchange->cert_cb (exchange->cert_cb_cls,
- &hr,
- &exchange->key_data,
- vc);
- GNUNET_JSON_parse_free (spec);
-}
-
-
-json_t *
-TALER_EXCHANGE_serialize_data (struct TALER_EXCHANGE_Handle *exchange)
-{
- const struct TALER_EXCHANGE_Keys *kd = &exchange->key_data;
- struct GNUNET_TIME_Timestamp now;
- json_t *keys;
- json_t *signkeys;
- json_t *denoms;
- json_t *auditors;
-
- now = GNUNET_TIME_timestamp_get ();
- signkeys = json_array ();
- if (NULL == signkeys)
- {
- GNUNET_break (0);
- return NULL;
- }
- for (unsigned int i = 0; i<kd->num_sign_keys; i++)
- {
- const struct TALER_EXCHANGE_SigningPublicKey *sk = &kd->sign_keys[i];
- json_t *signkey;
-
- if (GNUNET_TIME_timestamp_cmp (now,
- >,
- sk->valid_until))
- continue; /* skip keys that have expired */
- signkey = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("key",
- &sk->key),
- GNUNET_JSON_pack_data_auto ("master_sig",
- &sk->master_sig),
- GNUNET_JSON_pack_timestamp ("stamp_start",
- sk->valid_from),
- GNUNET_JSON_pack_timestamp ("stamp_expire",
- sk->valid_until),
- GNUNET_JSON_pack_timestamp ("stamp_end",
- sk->valid_legal));
- if (NULL == signkey)
- {
- GNUNET_break (0);
- continue;
- }
- if (0 != json_array_append_new (signkeys,
- signkey))
- {
- GNUNET_break (0);
- json_decref (signkey);
- json_decref (signkeys);
- return NULL;
- }
- }
- denoms = json_array ();
- if (NULL == denoms)
- {
- GNUNET_break (0);
- json_decref (signkeys);
- return NULL;
- }
- for (unsigned int i = 0; i<kd->num_denom_keys; i++)
- {
- const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i];
- json_t *denom;
-
- if (GNUNET_TIME_timestamp_cmp (now,
- >,
- dk->expire_deposit))
- continue; /* skip keys that have expired */
- denom = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_timestamp ("stamp_expire_deposit",
- dk->expire_deposit),
- GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw",
- dk->withdraw_valid_until),
- GNUNET_JSON_pack_timestamp ("stamp_start",
- dk->valid_from),
- GNUNET_JSON_pack_timestamp ("stamp_expire_legal",
- dk->expire_legal),
- TALER_JSON_pack_amount ("value",
- &dk->value),
- TALER_JSON_PACK_DENOM_FEES ("fee",
- &dk->fees),
- GNUNET_JSON_pack_data_auto ("master_sig",
- &dk->master_sig),
- TALER_JSON_pack_denom_pub ("denom_pub",
- &dk->key));
- GNUNET_assert (0 ==
- json_array_append_new (denoms,
- denom));
- }
- auditors = json_array ();
- GNUNET_assert (NULL != auditors);
- for (unsigned int i = 0; i<kd->num_auditors; i++)
- {
- const struct TALER_EXCHANGE_AuditorInformation *ai = &kd->auditors[i];
- json_t *a;
- json_t *adenoms;
-
- adenoms = json_array ();
- if (NULL == adenoms)
- {
- GNUNET_break (0);
- json_decref (denoms);
- json_decref (signkeys);
- json_decref (auditors);
- return NULL;
- }
- for (unsigned int j = 0; j<ai->num_denom_keys; j++)
- {
- const struct TALER_EXCHANGE_AuditorDenominationInfo *adi =
- &ai->denom_keys[j];
- const struct TALER_EXCHANGE_DenomPublicKey *dk =
- &kd->denom_keys[adi->denom_key_offset];
- json_t *k;
-
- if (GNUNET_TIME_timestamp_cmp (now,
- >,
- dk->expire_deposit))
- continue; /* skip auditor signatures for denomination keys that have expired */
- GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys);
- k = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("denom_pub_h",
- &dk->h_key),
- GNUNET_JSON_pack_data_auto ("auditor_sig",
- &adi->auditor_sig));
- GNUNET_assert (0 ==
- json_array_append_new (adenoms,
- k));
- }
-
- a = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("auditor_pub",
- &ai->auditor_pub),
- GNUNET_JSON_pack_string ("auditor_url",
- ai->auditor_url),
- GNUNET_JSON_pack_array_steal ("denomination_keys",
- adenoms));
- GNUNET_assert (0 ==
- json_array_append_new (auditors,
- a));
- }
- keys = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("version",
- kd->version),
- GNUNET_JSON_pack_string ("currency",
- kd->currency),
- GNUNET_JSON_pack_data_auto ("master_public_key",
- &kd->master_pub),
- GNUNET_JSON_pack_time_rel ("reserve_closing_delay",
- kd->reserve_closing_delay),
- GNUNET_JSON_pack_timestamp ("list_issue_date",
- kd->list_issue_date),
- GNUNET_JSON_pack_array_steal ("signkeys",
- signkeys),
- GNUNET_JSON_pack_array_steal ("denoms",
- denoms),
- GNUNET_JSON_pack_array_steal ("auditors",
- auditors));
- return GNUNET_JSON_PACK (
- GNUNET_JSON_pack_uint64 ("version",
- EXCHANGE_SERIALIZATION_FORMAT_VERSION),
- GNUNET_JSON_pack_timestamp ("expire",
- exchange->key_data_expiration),
- GNUNET_JSON_pack_string ("exchange_url",
- exchange->url),
- GNUNET_JSON_pack_object_steal ("keys",
- keys));
-}
-
-
-struct TALER_EXCHANGE_Handle *
-TALER_EXCHANGE_connect (
+struct TALER_EXCHANGE_GetKeysHandle *
+TALER_EXCHANGE_get_keys (
struct GNUNET_CURL_Context *ctx,
const char *url,
- TALER_EXCHANGE_CertificationCallback cert_cb,
- void *cert_cb_cls,
- ...)
+ struct TALER_EXCHANGE_Keys *last_keys,
+ TALER_EXCHANGE_GetKeysCallback cert_cb,
+ void *cert_cb_cls)
{
- struct TALER_EXCHANGE_Handle *exchange;
- va_list ap;
- enum TALER_EXCHANGE_Option opt;
+ struct TALER_EXCHANGE_GetKeysHandle *gkh;
+ CURL *eh;
+ char last_date[80] = { 0 };
TALER_LOG_DEBUG ("Connecting to the exchange (%s)\n",
url);
- /* Disable 100 continue processing */
- GNUNET_break (GNUNET_OK ==
- GNUNET_CURL_append_header (ctx,
- MHD_HTTP_HEADER_EXPECT ":"));
- exchange = GNUNET_new (struct TALER_EXCHANGE_Handle);
- exchange->ctx = ctx;
- exchange->url = GNUNET_strdup (url);
- exchange->cert_cb = cert_cb;
- exchange->cert_cb_cls = cert_cb_cls;
- exchange->retry_task = GNUNET_SCHEDULER_add_now (&request_keys,
- exchange);
- va_start (ap, cert_cb_cls);
- while (TALER_EXCHANGE_OPTION_END !=
- (opt = va_arg (ap, int)))
- {
- switch (opt)
- {
- case TALER_EXCHANGE_OPTION_END:
- GNUNET_assert (0);
- break;
- case TALER_EXCHANGE_OPTION_DATA:
- {
- const json_t *data = va_arg (ap, const json_t *);
-
- deserialize_data (exchange,
- data);
- break;
- }
- default:
- GNUNET_assert (0);
- break;
- }
- }
- va_end (ap);
- return exchange;
-}
-
-
-/**
- * Compute the network timeout for the next request to /keys.
- *
- * @param exchange the exchange handle
- * @returns the timeout in seconds (for use by CURL)
- */
-static long
-get_keys_timeout_seconds (struct TALER_EXCHANGE_Handle *exchange)
-{
- unsigned int kec;
-
- /* if retry counter >= 8, do not bother to go further, we
- stop the exponential back-off at 128 anyway. */
- kec = GNUNET_MIN (7,
- exchange->keys_error_count);
- return GNUNET_MIN (120,
- 5 + (1L << kec));
-}
-
-
-/**
- * Initiate download of /keys from the exchange.
- *
- * @param cls exchange where to download /keys from
- */
-static void
-request_keys (void *cls)
-{
- struct TALER_EXCHANGE_Handle *exchange = cls;
- struct KeysRequest *kr;
- CURL *eh;
- char url[200] = "/keys?";
-
- exchange->retry_task = NULL;
- GNUNET_assert (NULL == exchange->kr);
- kr = GNUNET_new (struct KeysRequest);
- kr->exchange = exchange;
-
- if (GNUNET_YES == TEAH_handle_is_ready (exchange))
+ gkh = GNUNET_new (struct TALER_EXCHANGE_GetKeysHandle);
+ gkh->exchange_url = GNUNET_strdup (url);
+ gkh->cert_cb = cert_cb;
+ gkh->cert_cb_cls = cert_cb_cls;
+ if (NULL != last_keys)
{
+ gkh->prev_keys = TALER_EXCHANGE_keys_incref (last_keys);
TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n",
GNUNET_TIME_timestamp2s (
- exchange->key_data.last_denom_issue_date));
- sprintf (&url[strlen (url)],
- "last_issue_date=%llu&",
- (unsigned long long)
- exchange->key_data.last_denom_issue_date.abs_time.abs_value_us
- / 1000000LLU);
+ last_keys->last_denom_issue_date));
+ GNUNET_snprintf (last_date,
+ sizeof (last_date),
+ "%llu",
+ (unsigned long long)
+ last_keys->last_denom_issue_date.abs_time.abs_value_us
+ / 1000000LLU);
}
-
- /* Clean the last '&'/'?' sign that we optimistically put. */
- url[strlen (url) - 1] = '\0';
- kr->url = TEAH_path_to_url (exchange,
- url);
- if (NULL == kr->url)
- {
- struct TALER_EXCHANGE_HttpResponse hr = {
- .ec = TALER_EC_GENERIC_CONFIGURATION_INVALID
- };
-
- GNUNET_free (kr);
- exchange->keys_error_count++;
- exchange->state = MHS_FAILED;
- exchange->cert_cb (exchange->cert_cb_cls,
- &hr,
- NULL,
- TALER_EXCHANGE_VC_PROTOCOL_ERROR);
- return;
- }
-
+ gkh->url = TALER_url_join (url,
+ "keys",
+ (NULL != last_keys)
+ ? "last_issue_date"
+ : NULL,
+ (NULL != last_keys)
+ ? last_date
+ : NULL,
+ NULL);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Requesting keys with URL `%s'.\n",
- kr->url);
- eh = TALER_EXCHANGE_curl_easy_get_ (kr->url);
+ gkh->url);
+ eh = TALER_EXCHANGE_curl_easy_get_ (gkh->url);
if (NULL == eh)
{
- GNUNET_free (kr->url);
- GNUNET_free (kr);
- exchange->retry_delay = EXCHANGE_LIB_BACKOFF (exchange->retry_delay);
- exchange->retry_task = GNUNET_SCHEDULER_add_delayed (exchange->retry_delay,
- &request_keys,
- exchange);
- return;
+ GNUNET_break (0);
+ GNUNET_free (gkh->exchange_url);
+ GNUNET_free (gkh->url);
+ GNUNET_free (gkh);
+ return NULL;
}
GNUNET_break (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_VERBOSE,
- 0));
+ 1));
GNUNET_break (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_TIMEOUT,
- get_keys_timeout_seconds (exchange)));
+ 120 /* seconds */));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_HEADERFUNCTION,
@@ -2071,70 +1719,35 @@ request_keys (void *cls)
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_HEADERDATA,
- kr));
- kr->job = GNUNET_CURL_job_add_with_ct_json (exchange->ctx,
- eh,
- &keys_completed_cb,
- kr);
- exchange->kr = kr;
+ gkh));
+ gkh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
+ eh,
+ &keys_completed_cb,
+ gkh);
+ return gkh;
}
void
-TALER_EXCHANGE_disconnect (struct TALER_EXCHANGE_Handle *exchange)
+TALER_EXCHANGE_get_keys_cancel (
+ struct TALER_EXCHANGE_GetKeysHandle *gkh)
{
- struct TEAH_AuditorListEntry *ale;
-
- while (NULL != (ale = exchange->auditors_head))
- {
- struct TEAH_AuditorInteractionEntry *aie;
-
- while (NULL != (aie = ale->ai_head))
- {
- GNUNET_assert (aie->ale == ale);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Not sending deposit confirmation to auditor `%s' due to exchange disconnect\n",
- ale->auditor_url);
- TALER_AUDITOR_deposit_confirmation_cancel (aie->dch);
- GNUNET_CONTAINER_DLL_remove (ale->ai_head,
- ale->ai_tail,
- aie);
- GNUNET_free (aie);
- }
- GNUNET_CONTAINER_DLL_remove (exchange->auditors_head,
- exchange->auditors_tail,
- ale);
- TALER_LOG_DEBUG ("Disconnecting the auditor `%s'\n",
- ale->auditor_url);
- TALER_AUDITOR_disconnect (ale->ah);
- GNUNET_free (ale->auditor_url);
- GNUNET_free (ale);
- }
- if (NULL != exchange->kr)
+ if (NULL != gkh->job)
{
- GNUNET_CURL_job_cancel (exchange->kr->job);
- free_keys_request (exchange->kr);
- exchange->kr = NULL;
+ GNUNET_CURL_job_cancel (gkh->job);
+ gkh->job = NULL;
}
- free_key_data (&exchange->key_data);
- if (NULL != exchange->key_data_raw)
- {
- json_decref (exchange->key_data_raw);
- exchange->key_data_raw = NULL;
- }
- if (NULL != exchange->retry_task)
- {
- GNUNET_SCHEDULER_cancel (exchange->retry_task);
- exchange->retry_task = NULL;
- }
- GNUNET_free (exchange->url);
- GNUNET_free (exchange);
+ TALER_EXCHANGE_keys_decref (gkh->prev_keys);
+ GNUNET_free (gkh->exchange_url);
+ GNUNET_free (gkh->url);
+ GNUNET_free (gkh);
}
enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_test_signing_key (const struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_ExchangePublicKeyP *pub)
+TALER_EXCHANGE_test_signing_key (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_ExchangePublicKeyP *pub)
{
struct GNUNET_TIME_Absolute now;
@@ -2161,13 +1774,6 @@ TALER_EXCHANGE_test_signing_key (const struct TALER_EXCHANGE_Keys *keys,
}
-const char *
-TALER_EXCHANGE_get_base_url (const struct TALER_EXCHANGE_Handle *exchange)
-{
- return exchange->url;
-}
-
-
const struct TALER_EXCHANGE_DenomPublicKey *
TALER_EXCHANGE_get_denomination_key (
const struct TALER_EXCHANGE_Keys *keys,
@@ -2211,8 +1817,8 @@ TALER_EXCHANGE_copy_denomination_key (
copy = GNUNET_new (struct TALER_EXCHANGE_DenomPublicKey);
*copy = *key;
- TALER_denom_pub_deep_copy (&copy->key,
- &key->key);
+ TALER_denom_pub_copy (&copy->key,
+ &key->key);
return copy;
}
@@ -2239,21 +1845,627 @@ TALER_EXCHANGE_get_denomination_key_by_hash (
}
-const struct TALER_EXCHANGE_Keys *
-TALER_EXCHANGE_get_keys (struct TALER_EXCHANGE_Handle *exchange)
+struct TALER_EXCHANGE_Keys *
+TALER_EXCHANGE_keys_incref (struct TALER_EXCHANGE_Keys *keys)
+{
+ GNUNET_assert (keys->rc < UINT_MAX);
+ keys->rc++;
+ return keys;
+}
+
+
+void
+TALER_EXCHANGE_keys_decref (struct TALER_EXCHANGE_Keys *keys)
+{
+ if (NULL == keys)
+ return;
+ GNUNET_assert (0 < keys->rc);
+ keys->rc--;
+ if (0 != keys->rc)
+ return;
+ GNUNET_array_grow (keys->sign_keys,
+ keys->num_sign_keys,
+ 0);
+ for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+ TALER_denom_pub_free (&keys->denom_keys[i].key);
+
+ GNUNET_array_grow (keys->denom_keys,
+ keys->denom_keys_size,
+ 0);
+ for (unsigned int i = 0; i<keys->num_auditors; i++)
+ {
+ GNUNET_array_grow (keys->auditors[i].denom_keys,
+ keys->auditors[i].num_denom_keys,
+ 0);
+ GNUNET_free (keys->auditors[i].auditor_url);
+ }
+ GNUNET_array_grow (keys->auditors,
+ keys->auditors_size,
+ 0);
+ TALER_EXCHANGE_free_accounts (keys->accounts_len,
+ keys->accounts);
+ GNUNET_array_grow (keys->accounts,
+ keys->accounts_len,
+ 0);
+ free_fees (keys->fees,
+ keys->fees_len);
+ json_decref (keys->extensions);
+ GNUNET_free (keys->cspec.name);
+ json_decref (keys->cspec.map_alt_unit_names);
+ GNUNET_free (keys->wallet_balance_limit_without_kyc);
+ GNUNET_free (keys->version);
+ GNUNET_free (keys->currency);
+ GNUNET_free (keys->asset_type);
+ GNUNET_free (keys->global_fees);
+ GNUNET_free (keys->exchange_url);
+ GNUNET_free (keys);
+}
+
+
+struct TALER_EXCHANGE_Keys *
+TALER_EXCHANGE_keys_from_json (const json_t *j)
+{
+ const json_t *jkeys;
+ const char *url;
+ uint32_t version;
+ struct GNUNET_TIME_Timestamp expire
+ = GNUNET_TIME_UNIT_ZERO_TS;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint32 ("version",
+ &version),
+ GNUNET_JSON_spec_object_const ("keys",
+ &jkeys),
+ TALER_JSON_spec_web_url ("exchange_url",
+ &url),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("expire",
+ &expire),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ struct TALER_EXCHANGE_Keys *keys;
+ enum TALER_EXCHANGE_VersionCompatibility compat;
+
+ if (NULL == j)
+ return NULL;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ if (0 != version)
+ {
+ return NULL; /* unsupported version */
+ }
+ keys = GNUNET_new (struct TALER_EXCHANGE_Keys);
+ if (GNUNET_OK !=
+ decode_keys_json (jkeys,
+ false,
+ keys,
+ &compat))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ keys->rc = 1;
+ keys->key_data_expiration = expire;
+ keys->exchange_url = GNUNET_strdup (url);
+ return keys;
+}
+
+
+/**
+ * Data we track per denomination group.
+ */
+struct GroupData
+{
+ /**
+ * The json blob with the group meta-data and list of denominations
+ */
+ json_t *json;
+
+ /**
+ * Meta data for this group.
+ */
+ struct TALER_DenominationGroup meta;
+};
+
+
+/**
+ * Add denomination group represented by @a value
+ * to list of denominations in @a cls. Also frees
+ * the @a value.
+ *
+ * @param[in,out] cls a `json_t *` with an array to build
+ * @param key unused
+ * @param value a `struct GroupData *`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+add_grp (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
+ json_t *denominations_by_group = cls;
+ struct GroupData *gd = value;
+ const char *cipher;
+ json_t *ge;
+ bool age_restricted = gd->meta.age_mask.bits != 0;
+
+ (void) key;
+ switch (gd->meta.cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ cipher = age_restricted ? "RSA+age_restricted" : "RSA";
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ cipher = age_restricted ? "CS+age_restricted" : "CS";
+ break;
+ default:
+ GNUNET_assert (false);
+ }
+
+ ge = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ cipher),
+ GNUNET_JSON_pack_array_steal ("denoms",
+ gd->json),
+ TALER_JSON_PACK_DENOM_FEES ("fee",
+ &gd->meta.fees),
+ GNUNET_JSON_pack_allow_null (
+ age_restricted
+ ? GNUNET_JSON_pack_uint64 ("age_mask",
+ gd->meta.age_mask.bits)
+ : GNUNET_JSON_pack_string ("dummy",
+ NULL)),
+ TALER_JSON_pack_amount ("value",
+ &gd->meta.value));
+ GNUNET_assert (0 ==
+ json_array_append_new (denominations_by_group,
+ ge));
+ GNUNET_free (gd);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Convert array of account restrictions @a ars to JSON.
+ *
+ * @param ar_len length of @a ars
+ * @param ars account restrictions to convert
+ * @return JSON representation
+ */
+static json_t *
+ar_to_json (unsigned int ar_len,
+ const struct TALER_EXCHANGE_AccountRestriction ars[static ar_len])
{
- (void) TALER_EXCHANGE_check_keys_current (exchange,
- TALER_EXCHANGE_CKF_NONE);
- return &exchange->key_data;
+ json_t *rval;
+
+ rval = json_array ();
+ GNUNET_assert (NULL != rval);
+ for (unsigned int i = 0; i<ar_len; i++)
+ {
+ const struct TALER_EXCHANGE_AccountRestriction *ar = &ars[i];
+
+ switch (ar->type)
+ {
+ case TALER_EXCHANGE_AR_INVALID:
+ GNUNET_break (0);
+ json_decref (rval);
+ return NULL;
+ case TALER_EXCHANGE_AR_DENY:
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ rval,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "deny"))));
+ break;
+ case TALER_EXCHANGE_AR_REGEX:
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ rval,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string (
+ "type",
+ "regex"),
+ GNUNET_JSON_pack_string (
+ "payto_regex",
+ ar->details.regex.posix_egrep),
+ GNUNET_JSON_pack_string (
+ "human_hint",
+ ar->details.regex.human_hint),
+ GNUNET_JSON_pack_object_incref (
+ "human_hint_i18n",
+ (json_t *) ar->details.regex.human_hint_i18n)
+ )));
+ break;
+ }
+ }
+ return rval;
}
json_t *
-TALER_EXCHANGE_get_keys_raw (struct TALER_EXCHANGE_Handle *exchange)
+TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd)
{
- (void) TALER_EXCHANGE_check_keys_current (exchange,
- TALER_EXCHANGE_CKF_NONE);
- return json_deep_copy (exchange->key_data_raw);
+ struct GNUNET_TIME_Timestamp now;
+ json_t *keys;
+ json_t *signkeys;
+ json_t *denominations_by_group;
+ json_t *auditors;
+ json_t *recoup;
+ json_t *wire_fees;
+ json_t *accounts;
+ json_t *global_fees;
+ json_t *wblwk = NULL;
+
+ now = GNUNET_TIME_timestamp_get ();
+ signkeys = json_array ();
+ GNUNET_assert (NULL != signkeys);
+ for (unsigned int i = 0; i<kd->num_sign_keys; i++)
+ {
+ const struct TALER_EXCHANGE_SigningPublicKey *sk = &kd->sign_keys[i];
+ json_t *signkey;
+
+ if (GNUNET_TIME_timestamp_cmp (now,
+ >,
+ sk->valid_until))
+ continue; /* skip keys that have expired */
+ signkey = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("key",
+ &sk->key),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &sk->master_sig),
+ GNUNET_JSON_pack_timestamp ("stamp_start",
+ sk->valid_from),
+ GNUNET_JSON_pack_timestamp ("stamp_expire",
+ sk->valid_until),
+ GNUNET_JSON_pack_timestamp ("stamp_end",
+ sk->valid_legal));
+ GNUNET_assert (NULL != signkey);
+ GNUNET_assert (0 ==
+ json_array_append_new (signkeys,
+ signkey));
+ }
+
+ denominations_by_group = json_array ();
+ GNUNET_assert (NULL != denominations_by_group);
+ {
+ struct GNUNET_CONTAINER_MultiHashMap *dbg;
+
+ dbg = GNUNET_CONTAINER_multihashmap_create (128,
+ false);
+ for (unsigned int i = 0; i<kd->num_denom_keys; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i];
+ struct TALER_DenominationGroup meta = {
+ .cipher = dk->key.bsign_pub_key->cipher,
+ .value = dk->value,
+ .fees = dk->fees,
+ .age_mask = dk->key.age_mask
+ };
+ struct GNUNET_HashCode key;
+ struct GroupData *gd;
+ json_t *denom;
+ struct GNUNET_JSON_PackSpec key_spec;
+
+ if (GNUNET_TIME_timestamp_cmp (now,
+ >,
+ dk->expire_deposit))
+ continue; /* skip keys that have expired */
+ TALER_denomination_group_get_key (&meta,
+ &key);
+ gd = GNUNET_CONTAINER_multihashmap_get (dbg,
+ &key);
+ if (NULL == gd)
+ {
+ gd = GNUNET_new (struct GroupData);
+ gd->meta = meta;
+ gd->json = json_array ();
+ GNUNET_assert (NULL != gd->json);
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (dbg,
+ &key,
+ gd,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+
+ }
+ switch (meta.cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ key_spec =
+ GNUNET_JSON_pack_rsa_public_key (
+ "rsa_pub",
+ dk->key.bsign_pub_key->details.rsa_public_key);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ key_spec =
+ GNUNET_JSON_pack_data_varsize (
+ "cs_pub",
+ &dk->key.bsign_pub_key->details.cs_public_key,
+ sizeof (dk->key.bsign_pub_key->details.cs_public_key));
+ break;
+ default:
+ GNUNET_assert (false);
+ }
+ denom = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_timestamp ("stamp_expire_deposit",
+ dk->expire_deposit),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw",
+ dk->withdraw_valid_until),
+ GNUNET_JSON_pack_timestamp ("stamp_start",
+ dk->valid_from),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_legal",
+ dk->expire_legal),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &dk->master_sig),
+ key_spec
+ );
+ GNUNET_assert (0 ==
+ json_array_append_new (gd->json,
+ denom));
+ }
+ GNUNET_CONTAINER_multihashmap_iterate (dbg,
+ &add_grp,
+ denominations_by_group);
+ GNUNET_CONTAINER_multihashmap_destroy (dbg);
+ }
+
+ auditors = json_array ();
+ GNUNET_assert (NULL != auditors);
+ for (unsigned int i = 0; i<kd->num_auditors; i++)
+ {
+ const struct TALER_EXCHANGE_AuditorInformation *ai = &kd->auditors[i];
+ json_t *a;
+ json_t *adenoms;
+
+ adenoms = json_array ();
+ GNUNET_assert (NULL != adenoms);
+ for (unsigned int j = 0; j<ai->num_denom_keys; j++)
+ {
+ const struct TALER_EXCHANGE_AuditorDenominationInfo *adi =
+ &ai->denom_keys[j];
+ const struct TALER_EXCHANGE_DenomPublicKey *dk =
+ &kd->denom_keys[adi->denom_key_offset];
+ json_t *k;
+
+ GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys);
+ if (GNUNET_TIME_timestamp_cmp (now,
+ >,
+ dk->expire_deposit))
+ continue; /* skip auditor signatures for denomination keys that have expired */
+ GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys);
+ k = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("denom_pub_h",
+ &dk->h_key),
+ GNUNET_JSON_pack_data_auto ("auditor_sig",
+ &adi->auditor_sig));
+ GNUNET_assert (0 ==
+ json_array_append_new (adenoms,
+ k));
+ }
+
+ a = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("auditor_pub",
+ &ai->auditor_pub),
+ GNUNET_JSON_pack_string ("auditor_url",
+ ai->auditor_url),
+ GNUNET_JSON_pack_array_steal ("denomination_keys",
+ adenoms));
+ GNUNET_assert (0 ==
+ json_array_append_new (auditors,
+ a));
+ }
+
+ global_fees = json_array ();
+ GNUNET_assert (NULL != global_fees);
+ for (unsigned int i = 0; i<kd->num_global_fees; i++)
+ {
+ const struct TALER_EXCHANGE_GlobalFee *gf
+ = &kd->global_fees[i];
+
+ if (GNUNET_TIME_absolute_is_past (gf->end_date.abs_time))
+ continue;
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ global_fees,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_timestamp ("start_date",
+ gf->start_date),
+ GNUNET_JSON_pack_timestamp ("end_date",
+ gf->end_date),
+ TALER_JSON_PACK_GLOBAL_FEES (&gf->fees),
+ GNUNET_JSON_pack_time_rel ("history_expiration",
+ gf->history_expiration),
+ GNUNET_JSON_pack_time_rel ("purse_timeout",
+ gf->purse_timeout),
+ GNUNET_JSON_pack_uint64 ("purse_account_limit",
+ gf->purse_account_limit),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &gf->master_sig))));
+ }
+
+ accounts = json_array ();
+ GNUNET_assert (NULL != accounts);
+ for (unsigned int i = 0; i<kd->accounts_len; i++)
+ {
+ const struct TALER_EXCHANGE_WireAccount *acc
+ = &kd->accounts[i];
+ json_t *credit_restrictions;
+ json_t *debit_restrictions;
+
+ credit_restrictions
+ = ar_to_json (acc->credit_restrictions_length,
+ acc->credit_restrictions);
+ GNUNET_assert (NULL != credit_restrictions);
+ debit_restrictions
+ = ar_to_json (acc->debit_restrictions_length,
+ acc->debit_restrictions);
+ GNUNET_assert (NULL != debit_restrictions);
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ accounts,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("payto_uri",
+ acc->payto_uri),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("conversion_url",
+ acc->conversion_url)),
+ GNUNET_JSON_pack_int64 ("priority",
+ acc->priority),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("bank_label",
+ acc->bank_label)),
+ GNUNET_JSON_pack_array_steal ("debit_restrictions",
+ debit_restrictions),
+ GNUNET_JSON_pack_array_steal ("credit_restrictions",
+ credit_restrictions),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &acc->master_sig))));
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Serialized %u/%u wire accounts to JSON\n",
+ (unsigned int) json_array_size (accounts),
+ kd->accounts_len);
+
+ wire_fees = json_object ();
+ GNUNET_assert (NULL != wire_fees);
+ for (unsigned int i = 0; i<kd->fees_len; i++)
+ {
+ const struct TALER_EXCHANGE_WireFeesByMethod *fbw
+ = &kd->fees[i];
+ json_t *wf;
+
+ wf = json_array ();
+ GNUNET_assert (NULL != wf);
+ for (struct TALER_EXCHANGE_WireAggregateFees *p = fbw->fees_head;
+ NULL != p;
+ p = p->next)
+ {
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ wf,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("wire_fee",
+ &p->fees.wire),
+ TALER_JSON_pack_amount ("closing_fee",
+ &p->fees.closing),
+ GNUNET_JSON_pack_timestamp ("start_date",
+ p->start_date),
+ GNUNET_JSON_pack_timestamp ("end_date",
+ p->end_date),
+ GNUNET_JSON_pack_data_auto ("sig",
+ &p->master_sig))));
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (wire_fees,
+ fbw->method,
+ wf));
+ }
+
+ recoup = json_array ();
+ GNUNET_assert (NULL != recoup);
+ for (unsigned int i = 0; i<kd->num_denom_keys; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *dk
+ = &kd->denom_keys[i];
+ if (! dk->revoked)
+ continue;
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ recoup,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &dk->h_key))));
+ }
+
+ wblwk = json_array ();
+ GNUNET_assert (NULL != wblwk);
+ for (unsigned int i = 0; i<kd->wblwk_length; i++)
+ {
+ const struct TALER_Amount *a = &kd->wallet_balance_limit_without_kyc[i];
+
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ wblwk,
+ TALER_JSON_from_amount (a)));
+ }
+
+ keys = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("version",
+ kd->version),
+ GNUNET_JSON_pack_string ("currency",
+ kd->currency),
+ GNUNET_JSON_pack_object_steal ("currency_specification",
+ TALER_CONFIG_currency_specs_to_json (
+ &kd->cspec)),
+ TALER_JSON_pack_amount ("stefan_abs",
+ &kd->stefan_abs),
+ TALER_JSON_pack_amount ("stefan_log",
+ &kd->stefan_log),
+ GNUNET_JSON_pack_double ("stefan_lin",
+ kd->stefan_lin),
+ GNUNET_JSON_pack_string ("asset_type",
+ kd->asset_type),
+ GNUNET_JSON_pack_data_auto ("master_public_key",
+ &kd->master_pub),
+ GNUNET_JSON_pack_time_rel ("reserve_closing_delay",
+ kd->reserve_closing_delay),
+ GNUNET_JSON_pack_timestamp ("list_issue_date",
+ kd->list_issue_date),
+ GNUNET_JSON_pack_array_steal ("global_fees",
+ global_fees),
+ GNUNET_JSON_pack_array_steal ("signkeys",
+ signkeys),
+ GNUNET_JSON_pack_object_steal ("wire_fees",
+ wire_fees),
+ GNUNET_JSON_pack_array_steal ("accounts",
+ accounts),
+ GNUNET_JSON_pack_array_steal ("wads",
+ json_array ()),
+ GNUNET_JSON_pack_array_steal ("denominations",
+ denominations_by_group),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("recoup",
+ recoup)),
+ GNUNET_JSON_pack_array_steal ("auditors",
+ auditors),
+ GNUNET_JSON_pack_bool ("rewards_allowed",
+ kd->rewards_allowed),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("extensions",
+ kd->extensions)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_is_zero (&kd->extensions_sig)
+ ? GNUNET_JSON_pack_string ("dummy",
+ NULL)
+ : GNUNET_JSON_pack_data_auto ("extensions_sig",
+ &kd->extensions_sig)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("wallet_balance_limit_without_kyc",
+ wblwk))
+
+ );
+ return GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("version",
+ EXCHANGE_SERIALIZATION_FORMAT_VERSION),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("expire",
+ kd->key_data_expiration)),
+ GNUNET_JSON_pack_string ("exchange_url",
+ kd->exchange_url),
+ GNUNET_JSON_pack_object_steal ("keys",
+ keys));
}
diff --git a/src/lib/exchange_api_handle.h b/src/lib/exchange_api_handle.h
index 7ef88489e..7c01b9a9f 100644
--- a/src/lib/exchange_api_handle.h
+++ b/src/lib/exchange_api_handle.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014, 2015 Taler Systems SA
+ Copyright (C) 2014, 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
@@ -25,179 +25,23 @@
#include <gnunet/gnunet_curl_lib.h>
#include "taler_auditor_service.h"
#include "taler_exchange_service.h"
-#include "taler_crypto_lib.h"
+#include "taler_util.h"
#include "taler_curl_lib.h"
-/**
- * Entry in DLL of auditors used by an exchange.
- */
-struct TEAH_AuditorListEntry;
-
-
-/**
- * Entry in list of ongoing interactions with an auditor.
- */
-struct TEAH_AuditorInteractionEntry
-{
- /**
- * DLL entry.
- */
- struct TEAH_AuditorInteractionEntry *next;
-
- /**
- * DLL entry.
- */
- struct TEAH_AuditorInteractionEntry *prev;
-
- /**
- * Which auditor is this action associated with?
- */
- struct TEAH_AuditorListEntry *ale;
-
- /**
- * Interaction state.
- */
- struct TALER_AUDITOR_DepositConfirmationHandle *dch;
-};
-
-/**
- * Stages of initialization for the `struct TALER_EXCHANGE_Handle`
- */
-enum ExchangeHandleState
-{
- /**
- * Just allocated.
- */
- MHS_INIT = 0,
-
- /**
- * Obtained the exchange's certification data and keys.
- */
- MHS_CERT = 1,
-
- /**
- * Failed to initialize (fatal).
- */
- MHS_FAILED = 2
-};
-
-
-/**
- * Handle to the exchange
- */
-struct TALER_EXCHANGE_Handle
-{
- /**
- * The context of this handle
- */
- struct GNUNET_CURL_Context *ctx;
-
- /**
- * The URL of the exchange (i.e. "http://exchange.taler.net/")
- */
- char *url;
-
- /**
- * Function to call with the exchange's certification data,
- * NULL if this has already been done.
- */
- TALER_EXCHANGE_CertificationCallback cert_cb;
-
- /**
- * Closure to pass to @e cert_cb.
- */
- void *cert_cb_cls;
-
- /**
- * Data for the request to get the /keys of a exchange,
- * NULL once we are past stage #MHS_INIT.
- */
- struct KeysRequest *kr;
-
- /**
- * Task for retrying /keys request.
- */
- struct GNUNET_SCHEDULER_Task *retry_task;
-
- /**
- * Raw key data of the exchange, only valid if
- * @e handshake_complete is past stage #MHS_CERT.
- */
- json_t *key_data_raw;
-
- /**
- * Head of DLL of auditors of this exchange.
- */
- struct TEAH_AuditorListEntry *auditors_head;
-
- /**
- * Tail of DLL of auditors of this exchange.
- */
- struct TEAH_AuditorListEntry *auditors_tail;
-
- /**
- * Key data of the exchange, only valid if
- * @e handshake_complete is past stage #MHS_CERT.
- */
- struct TALER_EXCHANGE_Keys key_data;
-
- /**
- * Retry /keys frequency.
- */
- struct GNUNET_TIME_Relative retry_delay;
-
- /**
- * When does @e key_data expire?
- */
- struct GNUNET_TIME_Timestamp key_data_expiration;
-
- /**
- * Number of subsequent failed requests to /keys.
- *
- * Used to compute the CURL timeout for the request.
- */
- unsigned int keys_error_count;
-
- /**
- * Number of subsequent failed requests to /wire.
- *
- * Used to compute the CURL timeout for the request.
- */
- unsigned int wire_error_count;
-
- /**
- * Stage of the exchange's initialization routines.
- */
- enum ExchangeHandleState state;
-
-};
-
/**
* Function called for each auditor to give us a chance to possibly
* launch a deposit confirmation interaction.
*
* @param cls closure
- * @param ah handle to the auditor
+ * @param auditor_url base URL of the auditor
* @param auditor_pub public key of the auditor
- * @return NULL if no deposit confirmation interaction was launched
*/
-typedef struct TEAH_AuditorInteractionEntry *
-(*TEAH_AuditorCallback)(void *cls,
- struct TALER_AUDITOR_Handle *ah,
- const struct TALER_AuditorPublicKeyP *auditor_pub);
-
-
-/**
- * Signature of functions called with the result from our call to the
- * auditor's /deposit-confirmation handler.
- *
- * @param cls closure of type `struct TEAH_AuditorInteractionEntry *`
- * @param hr HTTP response
- */
-void
-TEAH_acc_confirmation_cb (void *cls,
- const struct TALER_AUDITOR_HttpResponse *hr);
+typedef void
+(*TEAH_AuditorCallback)(
+ void *cls,
+ const char *auditor_url,
+ const struct TALER_AuditorPublicKeyP *auditor_pub);
/**
@@ -205,55 +49,16 @@ TEAH_acc_confirmation_cb (void *cls,
* @a ac and giving it a chance to start a deposit
* confirmation interaction.
*
- * @param h exchange to go over auditors for
+ * @param keys the keys to go over auditors for
* @param ac function to call per auditor
* @param ac_cls closure for @a ac
*/
void
-TEAH_get_auditors_for_dc (struct TALER_EXCHANGE_Handle *h,
- TEAH_AuditorCallback ac,
- void *ac_cls);
-
+TEAH_get_auditors_for_dc (
+ struct TALER_EXCHANGE_Keys *keys,
+ TEAH_AuditorCallback ac,
+ void *ac_cls);
-/**
- * Get the context of a exchange.
- *
- * @param h the exchange handle to query
- * @return ctx context to execute jobs in
- */
-struct GNUNET_CURL_Context *
-TEAH_handle_to_context (struct TALER_EXCHANGE_Handle *h);
-
-
-/**
- * Check if the handle is ready to process requests.
- *
- * @param h the exchange handle to query
- * @return #GNUNET_YES if we are ready, #GNUNET_NO if not
- */
-enum GNUNET_GenericReturnValue
-TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h);
-
-/**
- * Check if the handle is ready to process requests.
- *
- * @param h the exchange handle to query
- * @return #GNUNET_YES if we are ready, #GNUNET_NO if not
- */
-enum GNUNET_GenericReturnValue
-TEAH_handle_is_ready (struct TALER_EXCHANGE_Handle *h);
-
-
-/**
- * Obtain the URL to use for an API request.
- *
- * @param h the exchange handle to query
- * @param path Taler API path (i.e. "/reserve/withdraw")
- * @return the full URL to use with cURL
- */
-char *
-TEAH_path_to_url (struct TALER_EXCHANGE_Handle *h,
- const char *path);
/* end of exchange_api_handle.h */
#endif
diff --git a/src/lib/exchange_api_kyc_check.c b/src/lib/exchange_api_kyc_check.c
index 62a1db582..5d3b3792b 100644
--- a/src/lib/exchange_api_kyc_check.c
+++ b/src/lib/exchange_api_kyc_check.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ Copyright (C) 2021-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -37,14 +37,14 @@ struct TALER_EXCHANGE_KycCheckHandle
{
/**
- * The connection to exchange this request handle will use
+ * The url for this request.
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ char *url;
/**
- * The url for this request.
+ * Keys of the exchange.
*/
- char *url;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* Handle for the request.
@@ -65,6 +65,7 @@ struct TALER_EXCHANGE_KycCheckHandle
* Hash of the payto:// URL that is being KYC'ed.
*/
struct TALER_PaytoHashP h_payto;
+
};
@@ -95,16 +96,20 @@ handle_kyc_check_finished (void *cls,
break;
case MHD_HTTP_OK:
{
+ const json_t *kyc_details;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &ks.details.kyc_ok.exchange_sig),
+ &ks.details.ok.exchange_sig),
GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &ks.details.kyc_ok.exchange_pub),
+ &ks.details.ok.exchange_pub),
GNUNET_JSON_spec_timestamp ("now",
- &ks.details.kyc_ok.timestamp),
+ &ks.details.ok.timestamp),
+ GNUNET_JSON_spec_object_const ("kyc_details",
+ &kyc_details),
+ TALER_JSON_spec_aml_decision ("aml_status",
+ &ks.details.ok.aml_status),
GNUNET_JSON_spec_end ()
};
- const struct TALER_EXCHANGE_Keys *key_state;
if (GNUNET_OK !=
GNUNET_JSON_parse (j,
@@ -116,10 +121,10 @@ handle_kyc_check_finished (void *cls,
ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
}
- key_state = TALER_EXCHANGE_get_keys (kch->exchange);
+ ks.details.ok.kyc_details = kyc_details;
if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
- &ks.details.kyc_ok.exchange_pub))
+ TALER_EXCHANGE_test_signing_key (kch->keys,
+ &ks.details.ok.exchange_pub))
{
GNUNET_break_op (0);
ks.http_status = 0;
@@ -131,9 +136,10 @@ handle_kyc_check_finished (void *cls,
if (GNUNET_OK !=
TALER_exchange_online_account_setup_success_verify (
&kch->h_payto,
- ks.details.kyc_ok.timestamp,
- &ks.details.kyc_ok.exchange_pub,
- &ks.details.kyc_ok.exchange_sig))
+ ks.details.ok.kyc_details,
+ ks.details.ok.timestamp,
+ &ks.details.ok.exchange_pub,
+ &ks.details.ok.exchange_sig))
{
GNUNET_break_op (0);
ks.http_status = 0;
@@ -150,8 +156,10 @@ handle_kyc_check_finished (void *cls,
case MHD_HTTP_ACCEPTED:
{
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("kyc_url",
- &ks.details.kyc_url),
+ TALER_JSON_spec_web_url ("kyc_url",
+ &ks.details.accepted.kyc_url),
+ TALER_JSON_spec_aml_decision ("aml_status",
+ &ks.details.accepted.aml_status),
GNUNET_JSON_spec_end ()
};
@@ -184,6 +192,31 @@ handle_kyc_check_finished (void *cls,
case MHD_HTTP_NOT_FOUND:
ks.ec = TALER_JSON_get_error_code (j);
break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_aml_decision (
+ "aml_status",
+ &ks.details.unavailable_for_legal_reasons.aml_status),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ ks.http_status = 0;
+ ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+ kch->cb (kch->cb_cls,
+ &ks);
+ GNUNET_JSON_parse_free (spec);
+ TALER_EXCHANGE_kyc_check_cancel (kch);
+ return;
+ }
case MHD_HTTP_INTERNAL_SERVER_ERROR:
ks.ec = TALER_JSON_get_error_code (j);
/* Server had an internal issue; we should retry, but this API
@@ -206,24 +239,21 @@ handle_kyc_check_finished (void *cls,
struct TALER_EXCHANGE_KycCheckHandle *
-TALER_EXCHANGE_kyc_check (struct TALER_EXCHANGE_Handle *exchange,
- uint64_t payment_target,
- const struct TALER_PaytoHashP *h_payto,
- struct GNUNET_TIME_Relative timeout,
- TALER_EXCHANGE_KycStatusCallback cb,
- void *cb_cls)
+TALER_EXCHANGE_kyc_check (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ uint64_t requirement_row,
+ const struct TALER_PaytoHashP *h_payto,
+ enum TALER_KYCLOGIC_KycUserType ut,
+ struct GNUNET_TIME_Relative timeout,
+ TALER_EXCHANGE_KycStatusCallback cb,
+ void *cb_cls)
{
struct TALER_EXCHANGE_KycCheckHandle *kch;
CURL *eh;
- struct GNUNET_CURL_Context *ctx;
char *arg_str;
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
{
char payto_str[sizeof (*h_payto) * 2];
char *end;
@@ -238,18 +268,19 @@ TALER_EXCHANGE_kyc_check (struct TALER_EXCHANGE_Handle *exchange,
timeout_ms = timeout.rel_value_us
/ GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
GNUNET_asprintf (&arg_str,
- "/kyc-check/%llu?h_payto=%s&timeout_ms=%llu",
- (unsigned long long) payment_target,
+ "kyc-check/%llu/%s/%s?timeout_ms=%llu",
+ (unsigned long long) requirement_row,
payto_str,
+ TALER_KYCLOGIC_kyc_user_type2s (ut),
timeout_ms);
}
kch = GNUNET_new (struct TALER_EXCHANGE_KycCheckHandle);
- kch->exchange = exchange;
kch->h_payto = *h_payto;
kch->cb = cb;
kch->cb_cls = cb_cls;
- kch->url = TEAH_path_to_url (exchange,
- arg_str);
+ kch->url = TALER_url_join (url,
+ arg_str,
+ NULL);
GNUNET_free (arg_str);
if (NULL == kch->url)
{
@@ -264,7 +295,7 @@ TALER_EXCHANGE_kyc_check (struct TALER_EXCHANGE_Handle *exchange,
GNUNET_free (kch);
return NULL;
}
- ctx = TEAH_handle_to_context (exchange);
+ kch->keys = TALER_EXCHANGE_keys_incref (keys);
kch->job = GNUNET_CURL_job_add_with_ct_json (ctx,
eh,
&handle_kyc_check_finished,
@@ -281,6 +312,7 @@ TALER_EXCHANGE_kyc_check_cancel (struct TALER_EXCHANGE_KycCheckHandle *kch)
GNUNET_CURL_job_cancel (kch->job);
kch->job = NULL;
}
+ TALER_EXCHANGE_keys_decref (kch->keys);
GNUNET_free (kch->url);
GNUNET_free (kch);
}
diff --git a/src/lib/exchange_api_kyc_proof.c b/src/lib/exchange_api_kyc_proof.c
index d3debbdb9..e7cc9c4cf 100644
--- a/src/lib/exchange_api_kyc_proof.c
+++ b/src/lib/exchange_api_kyc_proof.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ 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
@@ -37,11 +37,6 @@ struct TALER_EXCHANGE_KycProofHandle
{
/**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
* The url for this request.
*/
char *url;
@@ -140,24 +135,22 @@ handle_kyc_proof_finished (void *cls,
struct TALER_EXCHANGE_KycProofHandle *
-TALER_EXCHANGE_kyc_proof (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_PaytoHashP *h_payto,
- const char *code,
- const char *state,
- TALER_EXCHANGE_KycProofCallback cb,
- void *cb_cls)
+TALER_EXCHANGE_kyc_proof (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *logic,
+ const char *args,
+ TALER_EXCHANGE_KycProofCallback cb,
+ void *cb_cls)
{
struct TALER_EXCHANGE_KycProofHandle *kph;
- struct GNUNET_CURL_Context *ctx;
char *arg_str;
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
- /* TODO: any escaping of code/state needed??? */
+ if (NULL == args)
+ args = "";
+ else
+ GNUNET_assert (args[0] == '&');
{
char hstr[sizeof (struct TALER_PaytoHashP) * 2];
char *end;
@@ -168,17 +161,17 @@ TALER_EXCHANGE_kyc_proof (struct TALER_EXCHANGE_Handle *exchange,
sizeof (hstr));
*end = '\0';
GNUNET_asprintf (&arg_str,
- "/kyc-proof/%s?code=%s&state=%s",
+ "kyc-proof/%s?state=%s%s",
+ logic,
hstr,
- code,
- state);
+ args);
}
kph = GNUNET_new (struct TALER_EXCHANGE_KycProofHandle);
- kph->exchange = exchange;
kph->cb = cb;
kph->cb_cls = cb_cls;
- kph->url = TEAH_path_to_url (exchange,
- arg_str);
+ kph->url = TALER_url_join (url,
+ arg_str,
+ NULL);
GNUNET_free (arg_str);
if (NULL == kph->url)
{
@@ -199,7 +192,6 @@ TALER_EXCHANGE_kyc_proof (struct TALER_EXCHANGE_Handle *exchange,
curl_easy_setopt (kph->eh,
CURLOPT_FOLLOWLOCATION,
0L));
- ctx = TEAH_handle_to_context (exchange);
kph->job = GNUNET_CURL_job_add_raw (ctx,
kph->eh,
NULL,
diff --git a/src/lib/exchange_api_kyc_wallet.c b/src/lib/exchange_api_kyc_wallet.c
index fe5e6b702..7197694ae 100644
--- a/src/lib/exchange_api_kyc_wallet.c
+++ b/src/lib/exchange_api_kyc_wallet.c
@@ -43,11 +43,6 @@ struct TALER_EXCHANGE_KycWalletHandle
struct TALER_CURL_PostContext ctx;
/**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
* The url for this request.
*/
char *url;
@@ -95,11 +90,28 @@ handle_kyc_wallet_finished (void *cls,
case 0:
ks.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
- case MHD_HTTP_OK:
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ ks.ec = TALER_JSON_get_error_code (j);
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ ks.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ ks.ec = TALER_JSON_get_error_code (j);
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
{
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint64 ("payment_target_uuid",
- &ks.payment_target_uuid),
+ 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 ()
};
@@ -115,19 +127,6 @@ handle_kyc_wallet_finished (void *cls,
}
break;
}
- case MHD_HTTP_NO_CONTENT:
- break;
- case MHD_HTTP_BAD_REQUEST:
- ks.ec = TALER_JSON_get_error_code (j);
- /* This should never happen, either us or the exchange is buggy
- (or API version conflict); just pass JSON reply to the application */
- break;
- case MHD_HTTP_FORBIDDEN:
- ks.ec = TALER_JSON_get_error_code (j);
- break;
- case MHD_HTTP_NOT_FOUND:
- ks.ec = TALER_JSON_get_error_code (j);
- break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
ks.ec = TALER_JSON_get_error_code (j);
/* Server had an internal issue; we should retry, but this API
@@ -150,41 +149,45 @@ handle_kyc_wallet_finished (void *cls,
struct TALER_EXCHANGE_KycWalletHandle *
-TALER_EXCHANGE_kyc_wallet (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- TALER_EXCHANGE_KycWalletCallback cb,
- void *cb_cls)
+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 GNUNET_CURL_Context *ctx;
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->exchange = exchange;
kwh->cb = cb;
kwh->cb_cls = cb_cls;
- kwh->url = TEAH_path_to_url (exchange,
- "/kyc-wallet");
+ kwh->url = TALER_url_join (url,
+ "kyc-wallet",
+ NULL);
if (NULL == kwh->url)
{
json_decref (req);
GNUNET_free (kwh);
return NULL;
}
- ctx = TEAH_handle_to_context (exchange);
eh = TALER_EXCHANGE_curl_easy_get_ (kwh->url);
if ( (NULL == eh) ||
(GNUNET_OK !=
diff --git a/src/lib/exchange_api_link.c b/src/lib/exchange_api_link.c
index 9e8625ed5..4b1adc723 100644
--- a/src/lib/exchange_api_link.c
+++ b/src/lib/exchange_api_link.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2021 Taler Systems SA
+ 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
@@ -37,11 +37,6 @@ struct TALER_EXCHANGE_LinkHandle
{
/**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
* The url for this request.
*/
char *url;
@@ -94,9 +89,9 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
struct TALER_BlindedDenominationSignature bsig;
struct TALER_DenominationPublicKey rpub;
struct TALER_CoinSpendSignatureP link_sig;
- union TALER_DenominationBlindingKeyP bks;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
struct TALER_ExchangeWithdrawValues alg_values;
- struct TALER_CsNonce nonce;
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
bool no_nonce;
uint32_t coin_idx;
struct GNUNET_JSON_Specification spec[] = {
@@ -119,6 +114,7 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
struct TALER_TransferSecretP secret;
struct TALER_PlanchetDetail pd;
struct TALER_CoinPubHashP c_hash;
+ struct TALER_AgeCommitmentHash *pah = NULL;
/* parse reply */
if (GNUNET_OK !=
@@ -142,49 +138,43 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
&alg_values,
&bks);
- lci->age_commitment_proof = NULL;
- lci->h_age_commitment = NULL;
+ lci->has_age_commitment = false;
/* Derive the age commitment and calculate the hash */
if (NULL != lh->age_commitment_proof)
{
- lci->age_commitment_proof = GNUNET_new (struct TALER_AgeCommitmentProof);
- lci->h_age_commitment = GNUNET_new (struct TALER_AgeCommitmentHash);
GNUNET_assert (GNUNET_OK ==
TALER_age_commitment_derive (
lh->age_commitment_proof,
&secret.key,
- lci->age_commitment_proof));
+ &lci->age_commitment_proof));
TALER_age_commitment_hash (
- &(lci->age_commitment_proof->commitment),
- lci->h_age_commitment);
+ &lci->age_commitment_proof.commitment,
+ &lci->h_age_commitment);
+
+ lci->has_age_commitment = true;
+ pah = &lci->h_age_commitment;
}
if (GNUNET_OK !=
- TALER_planchet_prepare (&rpub,
- &alg_values,
- &bks,
- &lci->coin_priv,
- lci->h_age_commitment,
- &c_hash,
- &pd))
+ TALER_planchet_prepare (
+ &rpub,
+ &alg_values,
+ &bks,
+ no_nonce
+ ? NULL
+ : &nonce,
+ &lci->coin_priv,
+ pah,
+ &c_hash,
+ &pd))
{
GNUNET_break (0);
GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
- if (TALER_DENOMINATION_CS == alg_values.cipher)
- {
- if (no_nonce)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- pd.blinded_planchet.details.cs_blinded_planchet.nonce = nonce;
- }
/* extract coin and signature */
if (GNUNET_OK !=
TALER_denom_sig_unblind (&lci->sig,
@@ -224,8 +214,8 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
}
/* clean up */
- TALER_denom_pub_deep_copy (&lci->pub,
- &rpub);
+ TALER_denom_pub_copy (&lci->pub,
+ &rpub);
GNUNET_JSON_parse_free (spec);
return GNUNET_OK;
}
@@ -270,9 +260,10 @@ parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
whilst 'i' and 'session' track the 2d array. *///
for (session = 0; session<json_array_size (json); session++)
{
- json_t *jsona;
+ const json_t *jsona;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("new_coins", &jsona),
+ GNUNET_JSON_spec_array_const ("new_coins",
+ &jsona),
GNUNET_JSON_spec_end ()
};
@@ -285,16 +276,8 @@ parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- if (! json_is_array (jsona))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
-
/* count all coins over all sessions */
num_coins += json_array_size (jsona);
- GNUNET_JSON_parse_free (spec);
}
/* Now that we know how big the 1d array is, allocate
and fill it. */
@@ -307,11 +290,11 @@ parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
off_coin = 0;
for (session = 0; session<json_array_size (json); session++)
{
- json_t *jsona;
+ const json_t *jsona;
struct TALER_TransferPublicKeyP trans_pub;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("new_coins",
- &jsona),
+ GNUNET_JSON_spec_array_const ("new_coins",
+ &jsona),
GNUNET_JSON_spec_fixed_auto ("transfer_pub",
&trans_pub),
GNUNET_JSON_spec_end ()
@@ -326,12 +309,6 @@ parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- if (! json_is_array (jsona))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
/* decode all coins */
for (i = 0; i<json_array_size (jsona); i++)
@@ -357,16 +334,14 @@ parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
{
GNUNET_break_op (0);
ret = GNUNET_SYSERR;
- GNUNET_JSON_parse_free (spec);
break;
}
- GNUNET_JSON_parse_free (spec);
} /* end of for (session) */
if (off_coin == num_coins)
{
- lr.details.success.num_coins = num_coins;
- lr.details.success.coins = lcis;
+ lr.details.ok.num_coins = num_coins;
+ lr.details.ok.coins = lcis;
lh->link_cb (lh->link_cb_cls,
&lr);
lh->link_cb = NULL;
@@ -384,6 +359,8 @@ parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
{
TALER_denom_sig_free (&lcis[i].sig);
TALER_denom_pub_free (&lcis[i].pub);
+ if (lcis[i].has_age_commitment)
+ TALER_age_commitment_proof_free (&lcis[i].age_commitment_proof);
}
}
return ret;
@@ -466,26 +443,19 @@ handle_link_finished (void *cls,
struct TALER_EXCHANGE_LinkHandle *
-TALER_EXCHANGE_link (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_CoinSpendPrivateKeyP *coin_priv,
- const struct
- TALER_AgeCommitmentProof *age_commitment_proof,
- TALER_EXCHANGE_LinkCallback link_cb,
- void *link_cb_cls)
+TALER_EXCHANGE_link (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ const struct TALER_AgeCommitmentProof *age_commitment_proof,
+ TALER_EXCHANGE_LinkCallback link_cb,
+ void *link_cb_cls)
{
struct TALER_EXCHANGE_LinkHandle *lh;
CURL *eh;
- struct GNUNET_CURL_Context *ctx;
struct TALER_CoinSpendPublicKeyP coin_pub;
char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
-
GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
&coin_pub.eddsa_pub);
{
@@ -500,17 +470,17 @@ TALER_EXCHANGE_link (struct TALER_EXCHANGE_Handle *exchange,
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/coins/%s/link",
+ "coins/%s/link",
pub_str);
}
lh = GNUNET_new (struct TALER_EXCHANGE_LinkHandle);
- lh->exchange = exchange;
lh->link_cb = link_cb;
lh->link_cb_cls = link_cb_cls;
lh->coin_priv = *coin_priv;
lh->age_commitment_proof = age_commitment_proof;
- lh->url = TEAH_path_to_url (exchange,
- arg_str);
+ lh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == lh->url)
{
GNUNET_free (lh);
@@ -524,7 +494,6 @@ TALER_EXCHANGE_link (struct TALER_EXCHANGE_Handle *exchange,
GNUNET_free (lh);
return NULL;
}
- ctx = TEAH_handle_to_context (exchange);
lh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
eh,
&handle_link_finished,
@@ -541,6 +510,7 @@ TALER_EXCHANGE_link_cancel (struct TALER_EXCHANGE_LinkHandle *lh)
GNUNET_CURL_job_cancel (lh->job);
lh->job = NULL;
}
+
GNUNET_free (lh->url);
GNUNET_free (lh);
}
diff --git a/src/lib/exchange_api_lookup_aml_decision.c b/src/lib/exchange_api_lookup_aml_decision.c
new file mode 100644
index 000000000..501b9d185
--- /dev/null
+++ b/src/lib/exchange_api_lookup_aml_decision.c
@@ -0,0 +1,417 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_lookup_aml_decision.c
+ * @brief Implementation of the /aml/$OFFICER_PUB/decision request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /coins/$COIN_PUB/link Handle
+ */
+struct TALER_EXCHANGE_LookupAmlDecision
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_LookupAmlDecisionCallback decision_cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *decision_cb_cls;
+
+ /**
+ * HTTP headers for the job.
+ */
+ struct curl_slist *job_headers;
+};
+
+
+/**
+ * Parse AML decision history.
+ *
+ * @param aml_history JSON array with AML history
+ * @param[out] aml_history_ar where to write the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_aml_history (const json_t *aml_history,
+ struct TALER_EXCHANGE_AmlDecisionDetail *aml_history_ar)
+{
+ json_t *obj;
+ size_t idx;
+
+ json_array_foreach (aml_history, idx, obj)
+ {
+ struct TALER_EXCHANGE_AmlDecisionDetail *aml = &aml_history_ar[idx];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("decision_time",
+ &aml->decision_time),
+ GNUNET_JSON_spec_string ("justification",
+ &aml->justification),
+ TALER_JSON_spec_amount_any ("new_threshold",
+ &aml->new_threshold),
+ TALER_JSON_spec_aml_decision ("new_state",
+ &aml->new_state),
+ GNUNET_JSON_spec_fixed_auto ("decider_pub",
+ &aml->decider_pub),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (obj,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse KYC response array.
+ *
+ * @param kyc_attributes JSON array with KYC details
+ * @param[out] kyc_attributes_ar where to write the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_kyc_attributes (const json_t *kyc_attributes,
+ struct TALER_EXCHANGE_KycHistoryDetail *kyc_attributes_ar)
+{
+ json_t *obj;
+ size_t idx;
+
+ json_array_foreach (kyc_attributes, idx, obj)
+ {
+ struct TALER_EXCHANGE_KycHistoryDetail *kyc = &kyc_attributes_ar[idx];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("collection_time",
+ &kyc->collection_time),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("attributes",
+ &kyc->attributes),
+ NULL),
+ GNUNET_JSON_spec_string ("provider_section",
+ &kyc->provider_section),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (obj,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse the provided decision data from the "200 OK" response.
+ *
+ * @param[in,out] lh handle (callback may be zero'ed out)
+ * @param json json reply with the data for one coin
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+static enum GNUNET_GenericReturnValue
+parse_decision_ok (struct TALER_EXCHANGE_LookupAmlDecision *lh,
+ const json_t *json)
+{
+ struct TALER_EXCHANGE_AmlDecisionResponse lr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ const json_t *aml_history;
+ const json_t *kyc_attributes;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("aml_history",
+ &aml_history),
+ GNUNET_JSON_spec_array_const ("kyc_attributes",
+ &kyc_attributes),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ lr.details.ok.aml_history_length = json_array_size (aml_history);
+ lr.details.ok.kyc_attributes_length = json_array_size (kyc_attributes);
+ {
+ struct TALER_EXCHANGE_AmlDecisionDetail aml_history_ar[
+ GNUNET_NZL (lr.details.ok.aml_history_length)];
+ struct TALER_EXCHANGE_KycHistoryDetail kyc_attributes_ar[
+ GNUNET_NZL (lr.details.ok.kyc_attributes_length)];
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+
+ memset (aml_history_ar,
+ 0,
+ sizeof (aml_history_ar));
+ memset (kyc_attributes_ar,
+ 0,
+ sizeof (kyc_attributes_ar));
+ lr.details.ok.aml_history = aml_history_ar;
+ lr.details.ok.kyc_attributes = kyc_attributes_ar;
+ ret = parse_aml_history (aml_history,
+ aml_history_ar);
+ if (GNUNET_OK == ret)
+ ret = parse_kyc_attributes (kyc_attributes,
+ kyc_attributes_ar);
+ if (GNUNET_OK == ret)
+ {
+ lh->decision_cb (lh->decision_cb_cls,
+ &lr);
+ lh->decision_cb = NULL;
+ }
+ return ret;
+ }
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /aml/$OFFICER_PUB/decision request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_LookupAmlDecision`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_lookup_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_LookupAmlDecision *lh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_AmlDecisionResponse lr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ lh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ parse_decision_ok (lh,
+ j))
+ {
+ GNUNET_break_op (0);
+ lr.hr.http_status = 0;
+ lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == lh->decision_cb);
+ TALER_EXCHANGE_lookup_aml_decision_cancel (lh);
+ return;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says this coin was not melted; we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says this coin was not melted; we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange lookup AML decision\n",
+ (unsigned int) response_code,
+ (int) lr.hr.ec);
+ break;
+ }
+ if (NULL != lh->decision_cb)
+ lh->decision_cb (lh->decision_cb_cls,
+ &lr);
+ TALER_EXCHANGE_lookup_aml_decision_cancel (lh);
+}
+
+
+struct TALER_EXCHANGE_LookupAmlDecision *
+TALER_EXCHANGE_lookup_aml_decision (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_url,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ bool history,
+ TALER_EXCHANGE_LookupAmlDecisionCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_LookupAmlDecision *lh;
+ CURL *eh;
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+ struct TALER_AmlOfficerSignatureP officer_sig;
+ char arg_str[sizeof (officer_pub) * 2
+ + sizeof (*h_payto) * 2 + 32];
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
+ &officer_pub.eddsa_pub);
+ TALER_officer_aml_query_sign (officer_priv,
+ &officer_sig);
+ {
+ char pub_str[sizeof (officer_pub) * 2];
+ char pt_str[sizeof (*h_payto) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &officer_pub,
+ sizeof (officer_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ end = GNUNET_STRINGS_data_to_string (
+ h_payto,
+ sizeof (*h_payto),
+ pt_str,
+ sizeof (pt_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "aml/%s/decision/%s",
+ pub_str,
+ pt_str);
+ }
+ lh = GNUNET_new (struct TALER_EXCHANGE_LookupAmlDecision);
+ lh->decision_cb = cb;
+ lh->decision_cb_cls = cb_cls;
+ lh->url = TALER_url_join (exchange_url,
+ arg_str,
+ "history",
+ history
+ ? "true"
+ : NULL,
+ NULL);
+ if (NULL == lh->url)
+ {
+ GNUNET_free (lh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (lh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (lh->url);
+ GNUNET_free (lh);
+ return NULL;
+ }
+ {
+ char *hdr;
+ char sig_str[sizeof (officer_sig) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &officer_sig,
+ sizeof (officer_sig),
+ sig_str,
+ sizeof (sig_str));
+ *end = '\0';
+
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ TALER_AML_OFFICER_SIGNATURE_HEADER,
+ sig_str);
+ lh->job_headers = curl_slist_append (NULL,
+ hdr);
+ GNUNET_free (hdr);
+ lh->job_headers = curl_slist_append (lh->job_headers,
+ "Content-type: application/json");
+ lh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ lh->job_headers,
+ &handle_lookup_finished,
+ lh);
+ }
+ return lh;
+}
+
+
+void
+TALER_EXCHANGE_lookup_aml_decision_cancel (
+ struct TALER_EXCHANGE_LookupAmlDecision *lh)
+{
+ if (NULL != lh->job)
+ {
+ GNUNET_CURL_job_cancel (lh->job);
+ lh->job = NULL;
+ }
+ curl_slist_free_all (lh->job_headers);
+ GNUNET_free (lh->url);
+ GNUNET_free (lh);
+}
+
+
+/* end of exchange_api_lookup_aml_decision.c */
diff --git a/src/lib/exchange_api_lookup_aml_decisions.c b/src/lib/exchange_api_lookup_aml_decisions.c
new file mode 100644
index 000000000..bb3c18b68
--- /dev/null
+++ b/src/lib/exchange_api_lookup_aml_decisions.c
@@ -0,0 +1,376 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_lookup_aml_decisions.c
+ * @brief Implementation of the /aml/$OFFICER_PUB/decisions request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /coins/$COIN_PUB/link Handle
+ */
+struct TALER_EXCHANGE_LookupAmlDecisions
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_LookupAmlDecisionsCallback decisions_cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *decisions_cb_cls;
+
+ /**
+ * HTTP headers for the job.
+ */
+ struct curl_slist *job_headers;
+};
+
+
+/**
+ * Parse AML decision summary array.
+ *
+ * @param decisions JSON array with AML decision summaries
+ * @param[out] decision_ar where to write the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_aml_decisions (const json_t *decisions,
+ struct TALER_EXCHANGE_AmlDecisionSummary *decision_ar)
+{
+ json_t *obj;
+ size_t idx;
+
+ json_array_foreach (decisions, idx, obj)
+ {
+ struct TALER_EXCHANGE_AmlDecisionSummary *decision = &decision_ar[idx];
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_payto",
+ &decision->h_payto),
+ TALER_JSON_spec_aml_decision ("current_state",
+ &decision->current_state),
+ TALER_JSON_spec_amount_any ("threshold",
+ &decision->threshold),
+ GNUNET_JSON_spec_uint64 ("rowid",
+ &decision->rowid),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (obj,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse the provided decision data from the "200 OK" response.
+ *
+ * @param[in,out] lh handle (callback may be zero'ed out)
+ * @param json json reply with the data for one coin
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+static enum GNUNET_GenericReturnValue
+parse_decisions_ok (struct TALER_EXCHANGE_LookupAmlDecisions *lh,
+ const json_t *json)
+{
+ struct TALER_EXCHANGE_AmlDecisionsResponse lr = {
+ .hr.reply = json,
+ .hr.http_status = MHD_HTTP_OK
+ };
+ const json_t *records;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("records",
+ &records),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ lr.details.ok.decisions_length = json_array_size (records);
+ {
+ struct TALER_EXCHANGE_AmlDecisionSummary decisions[
+ GNUNET_NZL (lr.details.ok.decisions_length)];
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+
+ lr.details.ok.decisions = decisions;
+ ret = parse_aml_decisions (records,
+ decisions);
+ if (GNUNET_OK == ret)
+ {
+ lh->decisions_cb (lh->decisions_cb_cls,
+ &lr);
+ lh->decisions_cb = NULL;
+ }
+ return ret;
+ }
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /aml/$OFFICER_PUB/decisions request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_LookupAmlDecisions`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_lookup_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_LookupAmlDecisions *lh = cls;
+ const json_t *j = response;
+ struct TALER_EXCHANGE_AmlDecisionsResponse lr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
+ };
+
+ lh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ parse_decisions_ok (lh,
+ j))
+ {
+ GNUNET_break_op (0);
+ lr.hr.http_status = 0;
+ lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ break;
+ }
+ GNUNET_assert (NULL == lh->decisions_cb);
+ TALER_EXCHANGE_lookup_aml_decisions_cancel (lh);
+ return;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says this coin was not melted; we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Nothing really to verify, exchange says this coin was not melted; we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ lr.hr.ec = TALER_JSON_get_error_code (j);
+ lr.hr.hint = TALER_JSON_get_error_hint (j);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for lookup AML decisions\n",
+ (unsigned int) response_code,
+ (int) lr.hr.ec);
+ break;
+ }
+ if (NULL != lh->decisions_cb)
+ lh->decisions_cb (lh->decisions_cb_cls,
+ &lr);
+ TALER_EXCHANGE_lookup_aml_decisions_cancel (lh);
+}
+
+
+struct TALER_EXCHANGE_LookupAmlDecisions *
+TALER_EXCHANGE_lookup_aml_decisions (
+ struct GNUNET_CURL_Context *ctx,
+ const char *exchange_url,
+ uint64_t start,
+ int delta,
+ enum TALER_AmlDecisionState state,
+ const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+ TALER_EXCHANGE_LookupAmlDecisionsCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_LookupAmlDecisions *lh;
+ CURL *eh;
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+ struct TALER_AmlOfficerSignatureP officer_sig;
+ char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 + 32];
+ const char *state_str = NULL;
+
+ switch (state)
+ {
+ case TALER_AML_NORMAL:
+ state_str = "normal";
+ break;
+ case TALER_AML_PENDING:
+ state_str = "pending";
+ break;
+ case TALER_AML_FROZEN:
+ state_str = "frozen";
+ break;
+ }
+ GNUNET_assert (NULL != state_str);
+ GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
+ &officer_pub.eddsa_pub);
+ TALER_officer_aml_query_sign (officer_priv,
+ &officer_sig);
+ {
+ char pub_str[sizeof (officer_pub) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &officer_pub,
+ sizeof (officer_pub),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "aml/%s/decisions/%s",
+ pub_str,
+ state_str);
+ }
+ lh = GNUNET_new (struct TALER_EXCHANGE_LookupAmlDecisions);
+ lh->decisions_cb = cb;
+ lh->decisions_cb_cls = cb_cls;
+ {
+ char delta_s[24];
+ char start_s[24];
+
+ GNUNET_snprintf (delta_s,
+ sizeof (delta_s),
+ "%d",
+ delta);
+ GNUNET_snprintf (start_s,
+ sizeof (start_s),
+ "%llu",
+ (unsigned long long) start);
+ lh->url = TALER_url_join (exchange_url,
+ arg_str,
+ "delta",
+ delta_s,
+ "start",
+ start_s,
+ NULL);
+ }
+ if (NULL == lh->url)
+ {
+ GNUNET_free (lh);
+ return NULL;
+ }
+ eh = TALER_EXCHANGE_curl_easy_get_ (lh->url);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ GNUNET_free (lh->url);
+ GNUNET_free (lh);
+ return NULL;
+ }
+ {
+ char *hdr;
+ char sig_str[sizeof (officer_sig) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (
+ &officer_sig,
+ sizeof (officer_sig),
+ sig_str,
+ sizeof (sig_str));
+ *end = '\0';
+
+ GNUNET_asprintf (&hdr,
+ "%s: %s",
+ TALER_AML_OFFICER_SIGNATURE_HEADER,
+ sig_str);
+ lh->job_headers = curl_slist_append (NULL,
+ hdr);
+ GNUNET_free (hdr);
+ lh->job_headers = curl_slist_append (lh->job_headers,
+ "Content-type: application/json");
+ lh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ lh->job_headers,
+ &handle_lookup_finished,
+ lh);
+ }
+ return lh;
+}
+
+
+void
+TALER_EXCHANGE_lookup_aml_decisions_cancel (
+ struct TALER_EXCHANGE_LookupAmlDecisions *lh)
+{
+ if (NULL != lh->job)
+ {
+ GNUNET_CURL_job_cancel (lh->job);
+ lh->job = NULL;
+ }
+ curl_slist_free_all (lh->job_headers);
+ GNUNET_free (lh->url);
+ GNUNET_free (lh);
+}
+
+
+/* end of exchange_api_lookup_aml_decisions.c */
diff --git a/src/lib/exchange_api_management_add_partner.c b/src/lib/exchange_api_management_add_partner.c
new file mode 100644
index 000000000..fec66c567
--- /dev/null
+++ b/src/lib/exchange_api_management_add_partner.c
@@ -0,0 +1,218 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_add_partner.c
+ * @brief functions to add an partner by an AML officer
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementAddPartner
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementAddPartnerCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /management/partners request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAddPartner *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_add_partner_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementAddPartner *wh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementAddPartnerResponse apr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ wh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* no reply */
+ apr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ apr.hr.hint = "server offline?";
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ apr.hr.ec = TALER_JSON_get_error_code (json);
+ apr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_CONFLICT:
+ apr.hr.ec = TALER_JSON_get_error_code (json);
+ apr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ apr.hr.ec = TALER_JSON_get_error_code (json);
+ apr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for adding exchange partner\n",
+ (unsigned int) response_code,
+ (int) apr.hr.ec);
+ break;
+ }
+ if (NULL != wh->cb)
+ {
+ wh->cb (wh->cb_cls,
+ &apr);
+ wh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_add_partner_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_ManagementAddPartner *
+TALER_EXCHANGE_management_add_partner (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_MasterPublicKeyP *partner_pub,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ struct GNUNET_TIME_Relative wad_frequency,
+ const struct TALER_Amount *wad_fee,
+ const char *partner_base_url,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementAddPartnerCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementAddPartner *wh;
+ CURL *eh;
+ json_t *body;
+
+ wh = GNUNET_new (struct TALER_EXCHANGE_ManagementAddPartner);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ctx = ctx;
+ wh->url = TALER_url_join (url,
+ "management/partners",
+ NULL);
+ if (NULL == wh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (wh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("partner_base_url",
+ partner_base_url),
+ GNUNET_JSON_pack_timestamp ("start_date",
+ start_date),
+ GNUNET_JSON_pack_timestamp ("end_date",
+ end_date),
+ GNUNET_JSON_pack_time_rel ("wad_frequency",
+ wad_frequency),
+ GNUNET_JSON_pack_data_auto ("partner_pub",
+ &partner_pub),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &master_sig),
+ TALER_JSON_pack_amount ("wad_fee",
+ wad_fee)
+ );
+ eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&wh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (wh->url);
+ return NULL;
+ }
+ json_decref (body);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ wh->url);
+ wh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ wh->post_ctx.headers,
+ &handle_add_partner_finished,
+ wh);
+ if (NULL == wh->job)
+ {
+ TALER_EXCHANGE_management_add_partner_cancel (wh);
+ return NULL;
+ }
+ return wh;
+}
+
+
+void
+TALER_EXCHANGE_management_add_partner_cancel (
+ struct TALER_EXCHANGE_ManagementAddPartner *wh)
+{
+ if (NULL != wh->job)
+ {
+ GNUNET_CURL_job_cancel (wh->job);
+ wh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&wh->post_ctx);
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_management_auditor_disable.c b/src/lib/exchange_api_management_auditor_disable.c
index b1de80f0e..8bce7f74f 100644
--- a/src/lib/exchange_api_management_auditor_disable.c
+++ b/src/lib/exchange_api_management_auditor_disable.c
@@ -81,9 +81,9 @@ handle_auditor_disable_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementAuditorDisableHandle *ah = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementAuditorDisableResponse adr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
ah->job = NULL;
@@ -92,32 +92,32 @@ handle_auditor_disable_finished (void *cls,
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ adr.hr.ec = TALER_JSON_get_error_code (json);
+ adr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_CONFLICT:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (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 management auditor disable\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) adr.hr.ec);
break;
}
if (NULL != ah->cb)
{
ah->cb (ah->cb_cls,
- &hr);
+ &adr);
ah->cb = NULL;
}
TALER_EXCHANGE_management_disable_auditor_cancel (ah);
diff --git a/src/lib/exchange_api_management_auditor_enable.c b/src/lib/exchange_api_management_auditor_enable.c
index af75215ec..41c5049c2 100644
--- a/src/lib/exchange_api_management_auditor_enable.c
+++ b/src/lib/exchange_api_management_auditor_enable.c
@@ -82,9 +82,9 @@ handle_auditor_enable_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementAuditorEnableHandle *ah = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementAuditorEnableResponse aer = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
ah->job = NULL;
@@ -93,28 +93,43 @@ handle_auditor_enable_finished (void *cls,
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ aer.hr.ec = TALER_JSON_get_error_code (json);
+ aer.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n",
+ ah->url);
+ if (NULL != json)
+ {
+ aer.hr.ec = TALER_JSON_get_error_code (json);
+ aer.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ aer.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ aer.hr.hint = TALER_ErrorCode_get_hint (aer.hr.ec);
+ }
break;
case MHD_HTTP_CONFLICT:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ aer.hr.ec = TALER_JSON_get_error_code (json);
+ aer.hr.hint = TALER_JSON_get_error_hint (json);
break;
default:
/* unexpected response code */
GNUNET_break_op (0);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ aer.hr.ec = TALER_JSON_get_error_code (json);
+ aer.hr.hint = TALER_JSON_get_error_hint (json);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d for exchange management auditor enable\n",
(unsigned int) response_code,
- (int) hr.ec);
+ (int) aer.hr.ec);
break;
}
if (NULL != ah->cb)
{
ah->cb (ah->cb_cls,
- &hr);
+ &aer);
ah->cb = NULL;
}
TALER_EXCHANGE_management_enable_auditor_cancel (ah);
diff --git a/src/lib/exchange_api_management_drain_profits.c b/src/lib/exchange_api_management_drain_profits.c
index 9cf1af85e..bc7232b87 100644
--- a/src/lib/exchange_api_management_drain_profits.c
+++ b/src/lib/exchange_api_management_drain_profits.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2020-2022 Taler Systems SA
+ 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
@@ -79,9 +79,9 @@ handle_drain_profits_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementDrainResponse dr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
dp->job = NULL;
@@ -90,32 +90,32 @@ handle_drain_profits_finished (void *cls,
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ dr.hr.ec = TALER_JSON_get_error_code (json);
+ dr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_CONFLICT:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ dr.hr.ec = TALER_JSON_get_error_code (json);
+ dr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_PRECONDITION_FAILED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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) hr.ec);
+ (int) dr.hr.ec);
break;
}
if (NULL != dp->cb)
{
dp->cb (dp->cb_cls,
- &hr);
+ &dr);
dp->cb = NULL;
}
TALER_EXCHANGE_management_drain_profits_cancel (dp);
diff --git a/src/lib/exchange_api_management_get_keys.c b/src/lib/exchange_api_management_get_keys.c
index 8a279d1ef..b88ddc205 100644
--- a/src/lib/exchange_api_management_get_keys.c
+++ b/src/lib/exchange_api_management_get_keys.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2020 Taler Systems SA
+ 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
@@ -26,7 +26,7 @@
#include "exchange_api_curl_defaults.h"
#include "taler_signatures.h"
#include "taler_curl_lib.h"
-#include "taler_crypto_lib.h"
+#include "taler_util.h"
#include "taler_json_lib.h"
/**
@@ -75,27 +75,32 @@ struct TALER_EXCHANGE_ManagementGetKeysHandle
* @param response the response
* @return #GNUNET_OK if the response was well-formed
*/
-static int
+static enum GNUNET_GenericReturnValue
handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh,
const json_t *response)
{
- struct TALER_EXCHANGE_FutureKeys fk;
- json_t *sk;
- json_t *dk;
+ 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_json ("future_denoms",
- &dk),
- GNUNET_JSON_spec_json ("future_signkeys",
- &sk),
+ 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),
+ &fk->master_pub),
GNUNET_JSON_spec_fixed_auto ("denom_secmod_public_key",
- &fk.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),
+ &fk->denom_secmod_cs_public_key),
GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key",
- &fk.signkey_secmod_public_key),
+ &fk->signkey_secmod_public_key),
GNUNET_JSON_spec_end ()
};
@@ -107,22 +112,22 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh,
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,
+ 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,
+ 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++)
+ 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 spec[] = {
+ = &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",
@@ -138,7 +143,7 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh,
if (GNUNET_OK !=
GNUNET_JSON_parse (j,
- spec,
+ ispec,
NULL, NULL))
{
GNUNET_break_op (0);
@@ -155,7 +160,7 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh,
&sign_key->key,
sign_key->valid_from,
duration,
- &fk.signkey_secmod_public_key,
+ &fk->signkey_secmod_public_key,
&sign_key->signkey_secmod_sig))
{
GNUNET_break_op (0);
@@ -164,12 +169,12 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh,
}
}
}
- for (unsigned int i = 0; i<fk.num_denom_keys; i++)
+ 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];
+ = &fk->denom_keys[i];
const char *section_name;
struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount_any ("value",
@@ -223,20 +228,21 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh,
TALER_denom_pub_hash (&denom_key->key,
&h_denom_pub);
- switch (denom_key->key.cipher)
+ switch (denom_key->key.bsign_pub_key->cipher)
{
- case TALER_DENOMINATION_RSA:
+ case GNUNET_CRYPTO_BSA_RSA:
{
struct TALER_RsaPubHashP h_rsa;
- TALER_rsa_pub_hash (denom_key->key.details.rsa_public_key,
- &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,
+ &fk->denom_secmod_public_key,
&denom_key->denom_secmod_sig))
{
GNUNET_break_op (0);
@@ -245,18 +251,19 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh,
}
}
break;
- case TALER_DENOMINATION_CS:
+ case GNUNET_CRYPTO_BSA_CS:
{
struct TALER_CsPubHashP h_cs;
- TALER_cs_pub_hash (&denom_key->key.details.cs_public_key,
- &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,
+ &fk->denom_secmod_cs_public_key,
&denom_key->denom_secmod_sig))
{
GNUNET_break_op (0);
@@ -271,26 +278,18 @@ handle_ok (struct TALER_EXCHANGE_ManagementGetKeysHandle *gh,
break;
}
}
- GNUNET_JSON_parse_free (spec);
if (! ok)
break;
}
if (ok)
{
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = MHD_HTTP_OK,
- .reply = response
- };
-
gh->cb (gh->cb_cls,
- &hr,
- &fk);
+ &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);
- GNUNET_JSON_parse_free (spec);
+ 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;
}
@@ -310,9 +309,9 @@ handle_get_keys_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementGetKeysHandle *gh = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementGetKeysResponse gkr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
gh->job = NULL;
@@ -330,29 +329,43 @@ handle_get_keys_finished (void *cls,
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)
{
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ gkr.hr.ec = TALER_JSON_get_error_code (json);
+ gkr.hr.hint = TALER_JSON_get_error_hint (json);
}
else
{
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.hint = TALER_ErrorCode_get_hint (hr.ec);
+ 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) hr.ec);
+ (int) gkr.hr.ec);
break;
}
if (NULL != gh->cb)
{
gh->cb (gh->cb_cls,
- &hr,
- NULL);
+ &gkr);
gh->cb = NULL;
}
TALER_EXCHANGE_get_management_keys_cancel (gh);
diff --git a/src/lib/exchange_api_management_post_extensions.c b/src/lib/exchange_api_management_post_extensions.c
index abec4ef09..00d1c5e3f 100644
--- a/src/lib/exchange_api_management_post_extensions.c
+++ b/src/lib/exchange_api_management_post_extensions.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2021 Taler Systems SA
+ 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
@@ -83,9 +83,9 @@ handle_post_extensions_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementPostExtensionsResponse per = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
ph->job = NULL;
@@ -94,28 +94,39 @@ handle_post_extensions_finished (void *cls,
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ per.hr.ec = TALER_JSON_get_error_code (json);
+ per.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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) hr.ec);
+ (int) per.hr.ec);
break;
}
if (NULL != ph->cb)
{
ph->cb (ph->cb_cls,
- &hr);
+ &per);
ph->cb = NULL;
}
TALER_EXCHANGE_management_post_extensions_cancel (ph);
@@ -126,7 +137,7 @@ struct TALER_EXCHANGE_ManagementPostExtensionsHandle *
TALER_EXCHANGE_management_post_extensions (
struct GNUNET_CURL_Context *ctx,
const char *url,
- struct TALER_EXCHANGE_ManagementPostExtensionsData *ped,
+ const struct TALER_EXCHANGE_ManagementPostExtensionsData *ped,
TALER_EXCHANGE_ManagementPostExtensionsCallback cb,
void *cb_cls)
{
@@ -151,7 +162,7 @@ TALER_EXCHANGE_management_post_extensions (
body = GNUNET_JSON_PACK (
GNUNET_JSON_pack_object_steal ("extensions",
- ped->extensions),
+ (json_t *) ped->extensions),
GNUNET_JSON_pack_data_auto ("extensions_sig",
&ped->extensions_sig));
diff --git a/src/lib/exchange_api_management_post_keys.c b/src/lib/exchange_api_management_post_keys.c
index 291c0ac02..a46124d90 100644
--- a/src/lib/exchange_api_management_post_keys.c
+++ b/src/lib/exchange_api_management_post_keys.c
@@ -82,9 +82,9 @@ handle_post_keys_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementPostKeysHandle *ph = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementPostKeysResponse pkr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
ph->job = NULL;
@@ -93,32 +93,32 @@ handle_post_keys_finished (void *cls,
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ pkr.hr.ec = TALER_JSON_get_error_code (json);
+ pkr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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) hr.ec);
+ (int) pkr.hr.ec);
break;
}
if (NULL != ph->cb)
{
ph->cb (ph->cb_cls,
- &hr);
+ &pkr);
ph->cb = NULL;
}
TALER_EXCHANGE_post_management_keys_cancel (ph);
diff --git a/src/lib/exchange_api_management_revoke_denomination_key.c b/src/lib/exchange_api_management_revoke_denomination_key.c
index 8d65b6451..a57704776 100644
--- a/src/lib/exchange_api_management_revoke_denomination_key.c
+++ b/src/lib/exchange_api_management_revoke_denomination_key.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2020 Taler Systems SA
+ 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
@@ -82,9 +82,9 @@ handle_revoke_denomination_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementRevokeDenominationResponse rdr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
rh->job = NULL;
@@ -92,30 +92,30 @@ handle_revoke_denomination_finished (void *cls,
{
case 0:
/* no reply */
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.hint = "server offline?";
+ rdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ rdr.hr.hint = "server offline?";
break;
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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) hr.ec);
+ (int) rdr.hr.ec);
break;
}
if (NULL != rh->cb)
{
rh->cb (rh->cb_cls,
- &hr);
+ &rdr);
rh->cb = NULL;
}
TALER_EXCHANGE_management_revoke_denomination_key_cancel (rh);
@@ -186,6 +186,7 @@ TALER_EXCHANGE_management_revoke_denomination_key (
curl_easy_cleanup (eh);
json_decref (body);
GNUNET_free (rh->url);
+ GNUNET_free (rh);
return NULL;
}
json_decref (body);
diff --git a/src/lib/exchange_api_management_revoke_signing_key.c b/src/lib/exchange_api_management_revoke_signing_key.c
index aac27678f..d2fa78264 100644
--- a/src/lib/exchange_api_management_revoke_signing_key.c
+++ b/src/lib/exchange_api_management_revoke_signing_key.c
@@ -79,9 +79,9 @@ handle_revoke_signing_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse rsr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
rh->job = NULL;
@@ -89,30 +89,30 @@ handle_revoke_signing_finished (void *cls,
{
case 0:
/* no reply */
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.hint = "server offline?";
+ rsr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ rsr.hr.hint = "server offline?";
break;
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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) hr.ec);
+ (int) rsr.hr.ec);
break;
}
if (NULL != rh->cb)
{
rh->cb (rh->cb_cls,
- &hr);
+ &rsr);
rh->cb = NULL;
}
TALER_EXCHANGE_management_revoke_signing_key_cancel (rh);
@@ -176,6 +176,7 @@ TALER_EXCHANGE_management_revoke_signing_key (
curl_easy_cleanup (eh);
json_decref (body);
GNUNET_free (rh->url);
+ GNUNET_free (rh);
return NULL;
}
json_decref (body);
diff --git a/src/lib/exchange_api_management_set_global_fee.c b/src/lib/exchange_api_management_set_global_fee.c
index b600c8cbe..f6282a812 100644
--- a/src/lib/exchange_api_management_set_global_fee.c
+++ b/src/lib/exchange_api_management_set_global_fee.c
@@ -79,9 +79,9 @@ handle_set_global_fee_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *sgfh = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementSetGlobalFeeResponse sfr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
sgfh->job = NULL;
@@ -90,32 +90,47 @@ handle_set_global_fee_finished (void *cls,
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ sfr.hr.ec = TALER_JSON_get_error_code (json);
+ sfr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_PRECONDITION_FAILED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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) hr.ec);
+ (int) sfr.hr.ec);
break;
}
if (NULL != sgfh->cb)
{
sgfh->cb (sgfh->cb_cls,
- &hr);
+ &sfr);
sgfh->cb = NULL;
}
TALER_EXCHANGE_management_set_global_fees_cancel (sgfh);
@@ -130,7 +145,6 @@ TALER_EXCHANGE_management_set_global_fees (
struct GNUNET_TIME_Timestamp validity_end,
const struct TALER_GlobalFeeSet *fees,
struct GNUNET_TIME_Relative purse_timeout,
- struct GNUNET_TIME_Relative kyc_timeout,
struct GNUNET_TIME_Relative history_expiration,
uint32_t purse_account_limit,
const struct TALER_MasterSignatureP *master_sig,
@@ -164,16 +178,12 @@ TALER_EXCHANGE_management_set_global_fees (
validity_end),
TALER_JSON_pack_amount ("history_fee",
&fees->history),
- TALER_JSON_pack_amount ("kyc_fee",
- &fees->kyc),
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 ("kyc_timeout",
- kyc_timeout),
GNUNET_JSON_pack_time_rel ("history_expiration",
history_expiration),
GNUNET_JSON_pack_uint64 ("purse_account_limit",
@@ -190,6 +200,7 @@ TALER_EXCHANGE_management_set_global_fees (
curl_easy_cleanup (eh);
json_decref (body);
GNUNET_free (sgfh->url);
+ GNUNET_free (sgfh);
return NULL;
}
json_decref (body);
diff --git a/src/lib/exchange_api_management_set_wire_fee.c b/src/lib/exchange_api_management_set_wire_fee.c
index 5951b6e20..aaeae21f4 100644
--- a/src/lib/exchange_api_management_set_wire_fee.c
+++ b/src/lib/exchange_api_management_set_wire_fee.c
@@ -79,9 +79,9 @@ handle_set_wire_fee_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementSetWireFeeHandle *swfh = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementSetWireFeeResponse swr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
swfh->job = NULL;
@@ -90,32 +90,47 @@ handle_set_wire_fee_finished (void *cls,
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ swr.hr.ec = TALER_JSON_get_error_code (json);
+ swr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_PRECONDITION_FAILED:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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) hr.ec);
+ (int) swr.hr.ec);
break;
}
if (NULL != swfh->cb)
{
swfh->cb (swfh->cb_cls,
- &hr);
+ &swr);
swfh->cb = NULL;
}
TALER_EXCHANGE_management_set_wire_fees_cancel (swfh);
@@ -163,8 +178,6 @@ TALER_EXCHANGE_management_set_wire_fees (
validity_end),
TALER_JSON_pack_amount ("closing_fee",
&fees->closing),
- TALER_JSON_pack_amount ("wad_fee",
- &fees->wad),
TALER_JSON_pack_amount ("wire_fee",
&fees->wire));
eh = TALER_EXCHANGE_curl_easy_get_ (swfh->url);
@@ -179,6 +192,7 @@ TALER_EXCHANGE_management_set_wire_fees (
curl_easy_cleanup (eh);
json_decref (body);
GNUNET_free (swfh->url);
+ GNUNET_free (swfh);
return NULL;
}
json_decref (body);
diff --git a/src/lib/exchange_api_management_update_aml_officer.c b/src/lib/exchange_api_management_update_aml_officer.c
new file mode 100644
index 000000000..af0169b02
--- /dev/null
+++ b/src/lib/exchange_api_management_update_aml_officer.c
@@ -0,0 +1,230 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_management_update_aml_officer.c
+ * @brief functions to update AML officer status
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "exchange_api_curl_defaults.h"
+#include "taler_signatures.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+
+
+struct TALER_EXCHANGE_ManagementUpdateAmlOfficer
+{
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ManagementUpdateAmlOfficerCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /management/wire request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ManagementAuditorEnableHandle *`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_update_aml_officer_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *wh = cls;
+ const json_t *json = response;
+ struct TALER_EXCHANGE_ManagementUpdateAmlOfficerResponse uar = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ wh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ /* no reply */
+ uar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ uar.hr.hint = "server offline?";
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ uar.hr.ec = TALER_JSON_get_error_code (json);
+ uar.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Server did not find handler at `%s'. Did you configure the correct exchange base URL?\n",
+ wh->url);
+ if (NULL != json)
+ {
+ uar.hr.ec = TALER_JSON_get_error_code (json);
+ uar.hr.hint = TALER_JSON_get_error_hint (json);
+ }
+ else
+ {
+ uar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ uar.hr.hint = TALER_ErrorCode_get_hint (uar.hr.ec);
+ }
+ break;
+ case MHD_HTTP_CONFLICT:
+ uar.hr.ec = TALER_JSON_get_error_code (json);
+ uar.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_break_op (0);
+ uar.hr.ec = TALER_JSON_get_error_code (json);
+ uar.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for exchange management update AML officer\n",
+ (unsigned int) response_code,
+ (int) uar.hr.ec);
+ break;
+ }
+ if (NULL != wh->cb)
+ {
+ wh->cb (wh->cb_cls,
+ &uar);
+ wh->cb = NULL;
+ }
+ TALER_EXCHANGE_management_update_aml_officer_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *
+TALER_EXCHANGE_management_update_aml_officer (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *officer_name,
+ struct GNUNET_TIME_Timestamp change_date,
+ bool is_active,
+ bool read_only,
+ const struct TALER_MasterSignatureP *master_sig,
+ TALER_EXCHANGE_ManagementUpdateAmlOfficerCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *wh;
+ CURL *eh;
+ json_t *body;
+
+ wh = GNUNET_new (struct TALER_EXCHANGE_ManagementUpdateAmlOfficer);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ctx = ctx;
+ wh->url = TALER_url_join (url,
+ "management/aml-officers",
+ NULL);
+ if (NULL == wh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (wh);
+ return NULL;
+ }
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("officer_name",
+ officer_name),
+ GNUNET_JSON_pack_data_auto ("officer_pub",
+ officer_pub),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig),
+ GNUNET_JSON_pack_bool ("is_active",
+ is_active),
+ GNUNET_JSON_pack_bool ("read_only",
+ read_only),
+ GNUNET_JSON_pack_timestamp ("change_date",
+ change_date));
+ eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
+ if ( (NULL == eh) ||
+ (GNUNET_OK !=
+ TALER_curl_easy_post (&wh->post_ctx,
+ eh,
+ body)) )
+ {
+ GNUNET_break (0);
+ if (NULL != eh)
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ GNUNET_free (wh->url);
+ 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_update_aml_officer_finished,
+ wh);
+ if (NULL == wh->job)
+ {
+ TALER_EXCHANGE_management_update_aml_officer_cancel (wh);
+ return NULL;
+ }
+ return wh;
+}
+
+
+void
+TALER_EXCHANGE_management_update_aml_officer_cancel (
+ struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *wh)
+{
+ if (NULL != wh->job)
+ {
+ GNUNET_CURL_job_cancel (wh->job);
+ wh->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&wh->post_ctx);
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_management_wire_disable.c b/src/lib/exchange_api_management_wire_disable.c
index 5d97eef75..23b10c58c 100644
--- a/src/lib/exchange_api_management_wire_disable.c
+++ b/src/lib/exchange_api_management_wire_disable.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2021 Taler Systems SA
+ 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
@@ -79,9 +79,9 @@ handle_auditor_disable_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementWireDisableHandle *wh = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementWireDisableResponse wdr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
wh->job = NULL;
@@ -89,38 +89,49 @@ handle_auditor_disable_finished (void *cls,
{
case 0:
/* no reply */
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.hint = "server offline?";
+ wdr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ wdr.hr.hint = "server offline?";
break;
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ wdr.hr.ec = TALER_JSON_get_error_code (json);
+ wdr.hr.hint = TALER_JSON_get_error_hint (json);
break;
case MHD_HTTP_NOT_FOUND:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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) hr.ec);
+ (int) wdr.hr.ec);
break;
}
if (NULL != wh->cb)
{
wh->cb (wh->cb_cls,
- &hr);
+ &wdr);
wh->cb = NULL;
}
TALER_EXCHANGE_management_disable_wire_cancel (wh);
@@ -174,6 +185,7 @@ TALER_EXCHANGE_management_disable_wire (
curl_easy_cleanup (eh);
json_decref (body);
GNUNET_free (wh->url);
+ GNUNET_free (wh);
return NULL;
}
json_decref (body);
diff --git a/src/lib/exchange_api_management_wire_enable.c b/src/lib/exchange_api_management_wire_enable.c
index 55480474e..9a163b558 100644
--- a/src/lib/exchange_api_management_wire_enable.c
+++ b/src/lib/exchange_api_management_wire_enable.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2021 Taler Systems SA
+ 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
@@ -79,9 +79,9 @@ handle_auditor_enable_finished (void *cls,
{
struct TALER_EXCHANGE_ManagementWireEnableHandle *wh = cls;
const json_t *json = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .http_status = (unsigned int) response_code,
- .reply = json
+ struct TALER_EXCHANGE_ManagementWireEnableResponse wer = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
};
wh->job = NULL;
@@ -89,34 +89,49 @@ handle_auditor_enable_finished (void *cls,
{
case 0:
/* no reply */
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- hr.hint = "server offline?";
+ wer.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ wer.hr.hint = "server offline?";
break;
case MHD_HTTP_NO_CONTENT:
break;
case MHD_HTTP_FORBIDDEN:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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:
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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);
- hr.ec = TALER_JSON_get_error_code (json);
- hr.hint = TALER_JSON_get_error_hint (json);
+ 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) hr.ec);
+ (int) wer.hr.ec);
break;
}
if (NULL != wh->cb)
{
wh->cb (wh->cb_cls,
- &hr);
+ &wer);
wh->cb = NULL;
}
TALER_EXCHANGE_management_enable_wire_cancel (wh);
@@ -128,9 +143,14 @@ TALER_EXCHANGE_management_enable_wire (
struct GNUNET_CURL_Context *ctx,
const char *url,
const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
struct GNUNET_TIME_Timestamp validity_start,
const struct TALER_MasterSignatureP *master_sig1,
const struct TALER_MasterSignatureP *master_sig2,
+ const char *bank_label,
+ int64_t priority,
TALER_EXCHANGE_ManagementWireEnableCallback cb,
void *cb_cls)
{
@@ -167,6 +187,18 @@ TALER_EXCHANGE_management_enable_wire (
body = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("payto_uri",
payto_uri),
+ GNUNET_JSON_pack_array_incref ("debit_restrictions",
+ (json_t *) debit_restrictions),
+ GNUNET_JSON_pack_array_incref ("credit_restrictions",
+ (json_t *) credit_restrictions),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("conversion_url",
+ conversion_url)),
+ GNUNET_JSON_pack_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",
@@ -185,6 +217,7 @@ TALER_EXCHANGE_management_enable_wire (
curl_easy_cleanup (eh);
json_decref (body);
GNUNET_free (wh->url);
+ GNUNET_free (wh);
return NULL;
}
json_decref (body);
diff --git a/src/lib/exchange_api_melt.c b/src/lib/exchange_api_melt.c
index feeef4001..c2f8cefb7 100644
--- a/src/lib/exchange_api_melt.c
+++ b/src/lib/exchange_api_melt.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2022 Taler Systems SA
+ 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
@@ -41,9 +41,9 @@ struct TALER_EXCHANGE_MeltHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -51,6 +51,16 @@ struct TALER_EXCHANGE_MeltHandle
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.
*/
@@ -159,7 +169,7 @@ verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh,
return GNUNET_SYSERR;
}
/* check that exchange signing key is permitted */
- key_state = TALER_EXCHANGE_get_keys (mh->exchange);
+ key_state = mh->keys;
if (GNUNET_OK !=
TALER_EXCHANGE_test_signing_key (key_state,
exchange_pub))
@@ -208,10 +218,8 @@ handle_melt_finished (void *cls,
.hr.reply = j,
.hr.http_status = (unsigned int) response_code
};
- const struct TALER_EXCHANGE_Keys *keys;
mh->job = NULL;
- keys = TALER_EXCHANGE_get_keys (mh->exchange);
switch (response_code)
{
case 0:
@@ -221,16 +229,16 @@ handle_melt_finished (void *cls,
if (GNUNET_OK !=
verify_melt_signature_ok (mh,
j,
- &mr.details.success.sign_key))
+ &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.success.noreveal_index = mh->noreveal_index;
- mr.details.success.num_mbds = mh->rd->fresh_pks_len;
- mr.details.success.mbds = mh->mbds;
+ mr.details.ok.noreveal_index = mh->noreveal_index;
+ mr.details.ok.num_mbds = mh->rd->fresh_pks_len;
+ mr.details.ok.mbds = mh->mbds;
mh->melt_cb (mh->melt_cb_cls,
&mr);
mh->melt_cb = NULL;
@@ -244,20 +252,6 @@ handle_melt_finished (void *cls,
case MHD_HTTP_CONFLICT:
mr.hr.ec = TALER_JSON_get_error_code (j);
mr.hr.hint = TALER_JSON_get_error_hint (j);
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_coin_conflict_ (
- keys,
- j,
- mh->dki,
- &mh->coin_pub,
- &mh->coin_sig,
- &mh->md.melted_coin.melt_amount_with_fee))
- {
- GNUNET_break_op (0);
- mr.hr.http_status = 0;
- mr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
break;
case MHD_HTTP_FORBIDDEN:
/* Nothing really to verify, exchange says one of the signatures is
@@ -309,13 +303,18 @@ start_melt (struct TALER_EXCHANGE_MeltHandle *mh)
const struct TALER_EXCHANGE_Keys *key_state;
json_t *melt_obj;
CURL *eh;
- struct GNUNET_CURL_Context *ctx;
char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
struct TALER_DenominationHashP h_denom_pub;
struct TALER_ExchangeWithdrawValues alg_values[mh->rd->fresh_pks_len];
for (unsigned int i = 0; i<mh->rd->fresh_pks_len; i++)
- alg_values[i] = mh->mbds[i].alg_value;
+ {
+ if (GNUNET_CRYPTO_BSA_RSA ==
+ mh->rd->fresh_pks[i].key.bsign_pub_key->cipher)
+ alg_values[i] = *TALER_denom_ewv_rsa_singleton ();
+ else
+ alg_values[i] = mh->mbds[i].alg_value;
+ }
if (GNUNET_OK !=
TALER_EXCHANGE_get_melt_data_ (&mh->rms,
mh->rd,
@@ -327,13 +326,14 @@ start_melt (struct TALER_EXCHANGE_MeltHandle *mh)
}
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);
+ TALER_wallet_melt_sign (
+ &mh->md.melted_coin.melt_amount_with_fee,
+ &mh->md.melted_coin.fee_melt,
+ &mh->md.rc,
+ &h_denom_pub,
+ mh->md.melted_coin.h_age_commitment,
+ &mh->md.melted_coin.coin_priv,
+ &mh->coin_sig);
GNUNET_CRYPTO_eddsa_key_get_public (&mh->md.melted_coin.coin_priv.eddsa_priv,
&mh->coin_pub.eddsa_pub);
melt_obj = GNUNET_JSON_PACK (
@@ -348,7 +348,7 @@ start_melt (struct TALER_EXCHANGE_MeltHandle *mh)
GNUNET_JSON_pack_data_auto ("rc",
&mh->md.rc),
GNUNET_JSON_pack_allow_null (
- mh->md.melted_coin.h_age_commitment
+ (NULL != mh->md.melted_coin.h_age_commitment)
? GNUNET_JSON_pack_data_auto ("age_commitment_hash",
mh->md.melted_coin.h_age_commitment)
: GNUNET_JSON_pack_string ("age_commitment_hash",
@@ -371,19 +371,19 @@ start_melt (struct TALER_EXCHANGE_MeltHandle *mh)
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/coins/%s/melt",
+ "coins/%s/melt",
pub_str);
}
- ctx = TEAH_handle_to_context (mh->exchange);
- key_state = TALER_EXCHANGE_get_keys (mh->exchange);
+ key_state = mh->keys;
mh->dki = TALER_EXCHANGE_get_denomination_key (key_state,
&mh->md.melted_coin.pub_key);
/* and now we can at last begin the actual request handling */
- mh->url = TEAH_path_to_url (mh->exchange,
- arg_str);
+ mh->url = TALER_url_join (mh->exchange_url,
+ arg_str,
+ NULL);
if (NULL == mh->url)
{
json_decref (melt_obj);
@@ -403,7 +403,7 @@ start_melt (struct TALER_EXCHANGE_MeltHandle *mh)
return GNUNET_SYSERR;
}
json_decref (melt_obj);
- mh->job = GNUNET_CURL_job_add2 (ctx,
+ mh->job = GNUNET_CURL_job_add2 (mh->cctx,
eh,
mh->ctx.headers,
&handle_melt_finished,
@@ -466,19 +466,18 @@ csr_cb (void *cls,
&mh->rd->fresh_pks[i];
struct TALER_ExchangeWithdrawValues *wv = &mh->mbds[i].alg_value;
- switch (fresh_pk->key.cipher)
+ switch (fresh_pk->key.bsign_pub_key->cipher)
{
- case TALER_DENOMINATION_INVALID:
+ case GNUNET_CRYPTO_BSA_INVALID:
GNUNET_break (0);
fail_mh (mh,
TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR);
return;
- case TALER_DENOMINATION_RSA:
- GNUNET_assert (TALER_DENOMINATION_RSA == wv->cipher);
+ case GNUNET_CRYPTO_BSA_RSA:
break;
- case TALER_DENOMINATION_CS:
- GNUNET_assert (TALER_DENOMINATION_CS == wv->cipher);
- *wv = csrr->details.success.alg_values[nks_off];
+ case GNUNET_CRYPTO_BSA_CS:
+ TALER_denom_ewv_copy (wv,
+ &csrr->details.ok.alg_values[nks_off]);
nks_off++;
break;
}
@@ -496,11 +495,14 @@ csr_cb (void *cls,
struct TALER_EXCHANGE_MeltHandle *
-TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_RefreshMasterSecretP *rms,
- const struct TALER_EXCHANGE_RefreshData *rd,
- TALER_EXCHANGE_MeltCallback melt_cb,
- void *melt_cb_cls)
+TALER_EXCHANGE_melt (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_RefreshMasterSecretP *rms,
+ const struct TALER_EXCHANGE_RefreshData *rd,
+ TALER_EXCHANGE_MeltCallback melt_cb,
+ void *melt_cb_cls)
{
struct TALER_EXCHANGE_NonceKey nks[GNUNET_NZL (rd->fresh_pks_len)];
unsigned int nks_off = 0;
@@ -511,11 +513,10 @@ TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange,
GNUNET_break (0);
return NULL;
}
- GNUNET_assert (GNUNET_YES ==
- TEAH_handle_is_ready (exchange));
mh = GNUNET_new (struct TALER_EXCHANGE_MeltHandle);
mh->noreveal_index = TALER_CNC_KAPPA; /* invalid value */
- mh->exchange = exchange;
+ mh->cctx = ctx;
+ mh->exchange_url = GNUNET_strdup (url);
mh->rd = rd;
mh->rms = *rms;
mh->melt_cb = melt_cb;
@@ -525,29 +526,30 @@ TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange,
for (unsigned int i = 0; i<rd->fresh_pks_len; i++)
{
const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk = &rd->fresh_pks[i];
- struct TALER_ExchangeWithdrawValues *wv = &mh->mbds[i].alg_value;
- switch (fresh_pk->key.cipher)
+ switch (fresh_pk->key.bsign_pub_key->cipher)
{
- case TALER_DENOMINATION_INVALID:
+ case GNUNET_CRYPTO_BSA_INVALID:
GNUNET_break (0);
GNUNET_free (mh->mbds);
GNUNET_free (mh);
return NULL;
- case TALER_DENOMINATION_RSA:
- wv->cipher = TALER_DENOMINATION_RSA;
+ case GNUNET_CRYPTO_BSA_RSA:
+ TALER_denom_ewv_copy (&mh->mbds[i].alg_value,
+ TALER_denom_ewv_rsa_singleton ());
break;
- case TALER_DENOMINATION_CS:
- wv->cipher = TALER_DENOMINATION_CS;
+ case GNUNET_CRYPTO_BSA_CS:
nks[nks_off].pk = fresh_pk;
nks[nks_off].cnc_num = nks_off;
nks_off++;
break;
}
}
+ mh->keys = TALER_EXCHANGE_keys_incref (keys);
if (0 != nks_off)
{
- mh->csr = TALER_EXCHANGE_csr_melt (exchange,
+ mh->csr = TALER_EXCHANGE_csr_melt (ctx,
+ url,
rms,
nks_off,
nks,
@@ -575,6 +577,8 @@ TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange,
void
TALER_EXCHANGE_melt_cancel (struct TALER_EXCHANGE_MeltHandle *mh)
{
+ for (unsigned int i = 0; i<mh->rd->fresh_pks_len; i++)
+ TALER_denom_ewv_free (&mh->mbds[i].alg_value);
if (NULL != mh->job)
{
GNUNET_CURL_job_cancel (mh->job);
@@ -588,7 +592,9 @@ TALER_EXCHANGE_melt_cancel (struct TALER_EXCHANGE_MeltHandle *mh)
TALER_EXCHANGE_free_melt_data_ (&mh->md); /* does not free 'md' itself */
GNUNET_free (mh->mbds);
GNUNET_free (mh->url);
+ GNUNET_free (mh->exchange_url);
TALER_curl_easy_post_finished (&mh->ctx);
+ TALER_EXCHANGE_keys_decref (mh->keys);
GNUNET_free (mh);
}
diff --git a/src/lib/exchange_api_purse_create_with_deposit.c b/src/lib/exchange_api_purse_create_with_deposit.c
index ce0221aa6..fff898e57 100644
--- a/src/lib/exchange_api_purse_create_with_deposit.c
+++ b/src/lib/exchange_api_purse_create_with_deposit.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ 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
@@ -73,9 +73,9 @@ struct TALER_EXCHANGE_PurseCreateDepositHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the exchange this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -83,6 +83,11 @@ struct TALER_EXCHANGE_PurseCreateDepositHandle
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.
*/
@@ -109,7 +114,7 @@ struct TALER_EXCHANGE_PurseCreateDepositHandle
struct TALER_Amount purse_value_after_fees;
/**
- * Our encryped contract (if we had any).
+ * Our encrypted contract (if we had any).
*/
struct TALER_EncryptedContract econtract;
@@ -170,10 +175,9 @@ handle_purse_create_deposit_finished (void *cls,
.hr.reply = j,
.hr.http_status = (unsigned int) response_code
};
- const struct TALER_EXCHANGE_Keys *keys;
+ const struct TALER_EXCHANGE_Keys *keys = pch->keys;
pch->job = NULL;
- keys = TALER_EXCHANGE_get_keys (pch->exchange);
switch (response_code)
{
case 0:
@@ -181,7 +185,6 @@ handle_purse_create_deposit_finished (void *cls,
break;
case MHD_HTTP_OK:
{
- const struct TALER_EXCHANGE_Keys *key_state;
struct GNUNET_TIME_Timestamp etime;
struct TALER_Amount total_deposited;
struct TALER_ExchangeSignatureP exchange_sig;
@@ -209,9 +212,8 @@ handle_purse_create_deposit_finished (void *cls,
dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
- key_state = TALER_EXCHANGE_get_keys (pch->exchange);
if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
+ TALER_EXCHANGE_test_signing_key (keys,
&exchange_pub))
{
GNUNET_break_op (0);
@@ -275,107 +277,12 @@ handle_purse_create_deposit_finished (void *cls,
}
break;
case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
- {
- struct TALER_Amount left;
- struct TALER_CoinSpendPublicKeyP pcoin_pub;
- bool found = false;
-
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_coin_amount_conflict_ (
- keys,
- j,
- &pcoin_pub,
- &left))
- {
- 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 (&pcoin_pub,
- &deposit->coin_pub))
- continue;
- if (-1 !=
- TALER_amount_cmp (&left,
- &deposit->contribution))
- {
- /* Balance was sufficient after all; recoup MAY have still been possible */
- GNUNET_break_op (0);
- continue;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_coin_signature_conflict_ (
- j,
- &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;
- }
- break;
- }
+ /* 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:
- {
- struct TALER_Amount left;
- struct TALER_CoinSpendPublicKeyP pcoin_pub;
- bool found = false;
-
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_coin_amount_conflict_ (
- keys,
- j,
- &pcoin_pub,
- &left))
- {
- 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 (&pcoin_pub,
- &deposit->coin_pub))
- continue;
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_coin_denomination_conflict_ (
- j,
- &deposit->h_denom_pub))
- {
- /* Eh, same denomination, hence no conflict */
- GNUNET_break_op (0);
- continue;
- }
- found = true;
- }
- 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;
- }
- /* meta data conflict is real! */
- break;
- }
+ // FIXME #7267: write check (add to exchange_api_common! */
+ break;
case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA:
{
struct TALER_CoinSpendPublicKeyP coin_pub;
@@ -387,7 +294,7 @@ handle_purse_create_deposit_finished (void *cls,
if (GNUNET_OK !=
TALER_EXCHANGE_check_purse_coin_conflict_ (
&pch->purse_pub,
- pch->exchange->url,
+ pch->exchange_url,
j,
&h_denom_pub,
&phac,
@@ -496,28 +403,27 @@ handle_purse_create_deposit_finished (void *cls,
struct TALER_EXCHANGE_PurseCreateDepositHandle *
TALER_EXCHANGE_purse_create_with_deposit (
- struct TALER_EXCHANGE_Handle *exchange,
+ 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,
+ const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits],
bool upload_contract,
TALER_EXCHANGE_PurseCreateDepositCallback cb,
void *cb_cls)
{
struct TALER_EXCHANGE_PurseCreateDepositHandle *pch;
- struct GNUNET_CURL_Context *ctx;
json_t *create_obj;
json_t *deposit_arr;
CURL *eh;
char arg_str[sizeof (pch->purse_pub) * 2 + 32];
- char *url;
uint32_t min_age = 0;
pch = GNUNET_new (struct TALER_EXCHANGE_PurseCreateDepositHandle);
- pch->exchange = exchange;
pch->cb = cb;
pch->cb_cls = cb_cls;
{
@@ -542,8 +448,6 @@ TALER_EXCHANGE_purse_create_with_deposit (
return NULL;
}
}
- GNUNET_assert (GNUNET_YES ==
- TEAH_handle_is_ready (exchange));
if (GNUNET_OK !=
TALER_JSON_contract_hash (contract_terms,
&pch->h_contract_terms))
@@ -565,13 +469,14 @@ TALER_EXCHANGE_purse_create_with_deposit (
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/purses/%s/create",
+ "purses/%s/create",
pub_str);
}
GNUNET_CRYPTO_eddsa_key_get_public (&merge_priv->eddsa_priv,
&pch->merge_pub.eddsa_pub);
- pch->url = TEAH_path_to_url (exchange,
- arg_str);
+ pch->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == pch->url)
{
GNUNET_break (0);
@@ -583,8 +488,6 @@ TALER_EXCHANGE_purse_create_with_deposit (
struct Deposit);
deposit_arr = json_array ();
GNUNET_assert (NULL != deposit_arr);
- url = TEAH_path_to_url (exchange,
- "/");
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Signing with URL `%s'\n",
url);
@@ -609,8 +512,11 @@ TALER_EXCHANGE_purse_create_with_deposit (
&attest))
{
GNUNET_break (0);
+ GNUNET_array_grow (pch->deposits,
+ pch->num_deposits,
+ 0);
+ GNUNET_free (pch->url);
json_decref (deposit_arr);
- GNUNET_free (url);
GNUNET_free (pch);
return NULL;
}
@@ -648,7 +554,6 @@ TALER_EXCHANGE_purse_create_with_deposit (
json_array_append_new (deposit_arr,
jdeposit));
}
- GNUNET_free (url);
TALER_wallet_purse_create_sign (pch->purse_expiration,
&pch->h_contract_terms,
&pch->merge_pub,
@@ -705,7 +610,9 @@ TALER_EXCHANGE_purse_create_with_deposit (
curl_easy_cleanup (eh);
json_decref (create_obj);
GNUNET_free (pch->econtract.econtract);
- GNUNET_free (pch->deposits);
+ GNUNET_array_grow (pch->deposits,
+ pch->num_deposits,
+ 0);
GNUNET_free (pch->url);
GNUNET_free (pch);
return NULL;
@@ -714,7 +621,8 @@ TALER_EXCHANGE_purse_create_with_deposit (
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"URL for purse create with deposit: `%s'\n",
pch->url);
- ctx = TEAH_handle_to_context (exchange);
+ pch->keys = TALER_EXCHANGE_keys_incref (keys);
+ pch->exchange_url = GNUNET_strdup (url);
pch->job = GNUNET_CURL_job_add2 (ctx,
eh,
pch->ctx.headers,
@@ -734,8 +642,12 @@ TALER_EXCHANGE_purse_create_with_deposit_cancel (
pch->job = NULL;
}
GNUNET_free (pch->econtract.econtract);
+ GNUNET_free (pch->exchange_url);
GNUNET_free (pch->url);
- GNUNET_free (pch->deposits);
+ 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);
}
diff --git a/src/lib/exchange_api_purse_create_with_merge.c b/src/lib/exchange_api_purse_create_with_merge.c
index f3c72d4f7..0c8878342 100644
--- a/src/lib/exchange_api_purse_create_with_merge.c
+++ b/src/lib/exchange_api_purse_create_with_merge.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ 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
@@ -41,9 +41,9 @@ struct TALER_EXCHANGE_PurseCreateMergeHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the exchange this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -51,6 +51,11 @@ struct TALER_EXCHANGE_PurseCreateMergeHandle
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.
*/
@@ -157,7 +162,6 @@ handle_purse_create_with_merge_finished (void *cls,
break;
case MHD_HTTP_OK:
{
- const struct TALER_EXCHANGE_Keys *key_state;
struct GNUNET_TIME_Timestamp etime;
struct TALER_Amount total_deposited;
struct TALER_ExchangeSignatureP exchange_sig;
@@ -184,9 +188,8 @@ handle_purse_create_with_merge_finished (void *cls,
dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
- key_state = TALER_EXCHANGE_get_keys (pcm->exchange);
if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
+ TALER_EXCHANGE_test_signing_key (pcm->keys,
&exchange_pub))
{
GNUNET_break_op (0);
@@ -253,7 +256,7 @@ handle_purse_create_with_merge_finished (void *cls,
&pcm->merge_sig,
&pcm->merge_pub,
&pcm->purse_pub,
- pcm->exchange->url,
+ pcm->exchange_url,
j))
{
GNUNET_break_op (0);
@@ -295,9 +298,25 @@ handle_purse_create_with_merge_finished (void *cls,
dr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
- /* aka KYC required */
- dr.hr.ec = TALER_JSON_get_error_code (j);
- dr.hr.hint = TALER_JSON_get_error_hint (j);
+ {
+ 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);
@@ -324,7 +343,9 @@ handle_purse_create_with_merge_finished (void *cls,
struct TALER_EXCHANGE_PurseCreateMergeHandle *
TALER_EXCHANGE_purse_create_with_merge (
- struct TALER_EXCHANGE_Handle *exchange,
+ 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,
@@ -337,7 +358,6 @@ TALER_EXCHANGE_purse_create_with_merge (
void *cb_cls)
{
struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm;
- struct GNUNET_CURL_Context *ctx;
json_t *create_with_merge_obj;
CURL *eh;
char arg_str[sizeof (pcm->reserve_pub) * 2 + 32];
@@ -346,7 +366,6 @@ TALER_EXCHANGE_purse_create_with_merge (
enum TALER_WalletAccountMergeFlags flags;
pcm = GNUNET_new (struct TALER_EXCHANGE_PurseCreateMergeHandle);
- pcm->exchange = exchange;
pcm->cb = cb;
pcm->cb_cls = cb_cls;
if (GNUNET_OK !=
@@ -393,7 +412,7 @@ TALER_EXCHANGE_purse_create_with_merge (
const struct TALER_EXCHANGE_GlobalFee *gf;
gf = TALER_EXCHANGE_get_global_fee (
- TALER_EXCHANGE_get_keys (exchange),
+ keys,
GNUNET_TIME_timestamp_get ());
purse_fee = gf->fees.purse;
flags = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
@@ -406,8 +425,6 @@ TALER_EXCHANGE_purse_create_with_merge (
flags = TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA;
}
- GNUNET_assert (GNUNET_YES ==
- TEAH_handle_is_ready (exchange));
{
char pub_str[sizeof (pcm->reserve_pub) * 2];
char *end;
@@ -420,11 +437,12 @@ TALER_EXCHANGE_purse_create_with_merge (
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/reserves/%s/purse",
+ "reserves/%s/purse",
pub_str);
}
- pcm->url = TEAH_path_to_url (exchange,
- arg_str);
+ pcm->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == pcm->url)
{
GNUNET_break (0);
@@ -438,11 +456,18 @@ TALER_EXCHANGE_purse_create_with_merge (
&pcm->purse_value_after_fees,
purse_priv,
&pcm->purse_sig);
- TALER_wallet_purse_merge_sign (exchange->url,
- merge_timestamp,
- &pcm->purse_pub,
- merge_priv,
- &pcm->merge_sig);
+ {
+ char *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);
+ }
TALER_wallet_account_merge_sign (merge_timestamp,
&pcm->purse_pub,
pcm->purse_expiration,
@@ -523,7 +548,8 @@ TALER_EXCHANGE_purse_create_with_merge (
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"URL for purse create_with_merge: `%s'\n",
pcm->url);
- ctx = TEAH_handle_to_context (exchange);
+ pcm->keys = TALER_EXCHANGE_keys_incref (keys);
+ pcm->exchange_url = GNUNET_strdup (url);
pcm->job = GNUNET_CURL_job_add2 (ctx,
eh,
pcm->ctx.headers,
@@ -543,7 +569,9 @@ TALER_EXCHANGE_purse_create_with_merge_cancel (
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);
}
diff --git a/src/lib/exchange_api_purse_delete.c b/src/lib/exchange_api_purse_delete.c
new file mode 100644
index 000000000..6f8ecc381
--- /dev/null
+++ b/src/lib/exchange_api_purse_delete.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_purse_delete.c
+ * @brief Implementation of the client to delete a purse
+ * into an account
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "exchange_api_common.h"
+#include "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
index 922251012..9c5fa4e78 100644
--- a/src/lib/exchange_api_purse_deposit.c
+++ b/src/lib/exchange_api_purse_deposit.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ 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
@@ -73,9 +73,9 @@ struct TALER_EXCHANGE_PurseDepositHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the exchange this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -144,10 +144,9 @@ handle_purse_deposit_finished (void *cls,
.hr.reply = j,
.hr.http_status = (unsigned int) response_code
};
- const struct TALER_EXCHANGE_Keys *keys;
+ const struct TALER_EXCHANGE_Keys *keys = pch->keys;
pch->job = NULL;
- keys = TALER_EXCHANGE_get_keys (pch->exchange);
switch (response_code)
{
case 0:
@@ -164,17 +163,17 @@ handle_purse_deposit_finished (void *cls,
GNUNET_JSON_spec_fixed_auto ("exchange_pub",
&exchange_pub),
GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
- &dr.details.success.h_contract_terms),
+ &dr.details.ok.h_contract_terms),
GNUNET_JSON_spec_timestamp ("exchange_timestamp",
&etime),
GNUNET_JSON_spec_timestamp ("purse_expiration",
- &dr.details.success.purse_expiration),
+ &dr.details.ok.purse_expiration),
TALER_JSON_spec_amount ("total_deposited",
keys->currency,
- &dr.details.success.total_deposited),
+ &dr.details.ok.total_deposited),
TALER_JSON_spec_amount ("purse_value_after_fees",
keys->currency,
- &dr.details.success.purse_value_after_fees),
+ &dr.details.ok.purse_value_after_fees),
GNUNET_JSON_spec_end ()
};
@@ -200,11 +199,11 @@ handle_purse_deposit_finished (void *cls,
if (GNUNET_OK !=
TALER_exchange_online_purse_created_verify (
etime,
- dr.details.success.purse_expiration,
- &dr.details.success.purse_value_after_fees,
- &dr.details.success.total_deposited,
+ dr.details.ok.purse_expiration,
+ &dr.details.ok.purse_value_after_fees,
+ &dr.details.ok.total_deposited,
&pch->purse_pub,
- &dr.details.success.h_contract_terms,
+ &dr.details.ok.h_contract_terms,
&exchange_pub,
&exchange_sig))
{
@@ -298,114 +297,11 @@ handle_purse_deposit_finished (void *cls,
break;
}
case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
- {
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_Amount remaining;
- bool found = false;
- const struct Coin *my_coin;
-
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_coin_amount_conflict_ (
- keys,
- j,
- &coin_pub,
- &remaining))
- {
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- for (unsigned int i = 0; i<pch->num_deposits; i++)
- {
- if (0 == GNUNET_memcmp (&coin_pub,
- &pch->coins[i].coin_pub))
- {
- found = true;
- my_coin = &pch->coins[i];
- break;
- }
- }
- if (! found)
- {
- /* proof is about a coin we did not even deposit */
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (1 == TALER_amount_cmp (&remaining,
- &my_coin->contribution))
- {
- /* transaction should have still fit */
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_coin_signature_conflict_ (
- j,
- &my_coin->coin_sig))
- {
- /* THIS transaction must not be in the conflicting history */
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- /* everything OK, proof of double-spending was provided */
- break;
- }
+ /* 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:
- {
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_Amount remaining;
- bool found = false;
- const struct Coin *my_coin;
-
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_coin_amount_conflict_ (
- keys,
- j,
- &coin_pub,
- &remaining))
- {
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- for (unsigned int i = 0; i<pch->num_deposits; i++)
- {
- if (0 == GNUNET_memcmp (&coin_pub,
- &pch->coins[i].coin_pub))
- {
- found = true;
- my_coin = &pch->coins[i];
- break;
- }
- }
- if (! found)
- {
- /* proof is about a coin we did not even deposit */
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_coin_denomination_conflict_ (
- j,
- &my_coin->h_denom_pub))
- {
- /* no conflicting denomination detected */
- GNUNET_break_op (0);
- dr.hr.http_status = 0;
- dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- /* everything OK, proof of conflicting denomination was provided */
- break;
- }
+ break;
default:
GNUNET_break_op (0);
dr.hr.http_status = 0;
@@ -414,7 +310,7 @@ handle_purse_deposit_finished (void *cls,
} /* ec switch */
break;
case MHD_HTTP_GONE:
- /* could happen if denomination was revoked */
+ /* 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 */
@@ -447,32 +343,32 @@ handle_purse_deposit_finished (void *cls,
struct TALER_EXCHANGE_PurseDepositHandle *
TALER_EXCHANGE_purse_deposit (
- struct TALER_EXCHANGE_Handle *exchange,
+ 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,
+ const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits],
TALER_EXCHANGE_PurseDepositCallback cb,
void *cb_cls)
{
struct TALER_EXCHANGE_PurseDepositHandle *pch;
- struct GNUNET_CURL_Context *ctx;
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;
}
- GNUNET_assert (GNUNET_YES ==
- TEAH_handle_is_ready (exchange));
pch = GNUNET_new (struct TALER_EXCHANGE_PurseDepositHandle);
pch->purse_pub = *purse_pub;
- pch->exchange = exchange;
pch->cb = cb;
pch->cb_cls = cb_cls;
{
@@ -487,11 +383,12 @@ TALER_EXCHANGE_purse_deposit (
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/purses/%s/deposit",
+ "purses/%s/deposit",
pub_str);
}
- pch->url = TEAH_path_to_url (exchange,
- arg_str);
+ pch->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == pch->url)
{
GNUNET_break (0);
@@ -500,8 +397,7 @@ TALER_EXCHANGE_purse_deposit (
}
deposit_arr = json_array ();
GNUNET_assert (NULL != deposit_arr);
- pch->base_url = TEAH_path_to_url (exchange,
- "/");
+ pch->base_url = GNUNET_strdup (url);
pch->num_deposits = num_deposits;
pch->coins = GNUNET_new_array (num_deposits,
struct Coin);
@@ -529,6 +425,7 @@ TALER_EXCHANGE_purse_deposit (
json_decref (deposit_arr);
GNUNET_free (pch->base_url);
GNUNET_free (pch->coins);
+ GNUNET_free (pch->url);
GNUNET_free (pch);
return NULL;
}
@@ -592,7 +489,7 @@ TALER_EXCHANGE_purse_deposit (
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"URL for purse deposit: `%s'\n",
pch->url);
- ctx = TEAH_handle_to_context (exchange);
+ pch->keys = TALER_EXCHANGE_keys_incref (keys);
pch->job = GNUNET_CURL_job_add2 (ctx,
eh,
pch->ctx.headers,
@@ -614,6 +511,7 @@ TALER_EXCHANGE_purse_deposit_cancel (
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);
}
diff --git a/src/lib/exchange_api_purse_merge.c b/src/lib/exchange_api_purse_merge.c
index abe6d8cf9..c013b29d2 100644
--- a/src/lib/exchange_api_purse_merge.c
+++ b/src/lib/exchange_api_purse_merge.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ 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
@@ -41,9 +41,9 @@ struct TALER_EXCHANGE_AccountMergeHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the exchange this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -148,15 +148,14 @@ handle_purse_merge_finished (void *cls,
break;
case MHD_HTTP_OK:
{
- const struct TALER_EXCHANGE_Keys *key_state;
struct TALER_Amount total_deposited;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &dr.details.success.exchange_sig),
+ &dr.details.ok.exchange_sig),
GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &dr.details.success.exchange_pub),
+ &dr.details.ok.exchange_pub),
GNUNET_JSON_spec_timestamp ("exchange_timestamp",
- &dr.details.success.etime),
+ &dr.details.ok.etime),
TALER_JSON_spec_amount ("merge_amount",
pch->purse_value_after_fees.currency,
&total_deposited),
@@ -173,10 +172,9 @@ handle_purse_merge_finished (void *cls,
dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
- key_state = TALER_EXCHANGE_get_keys (pch->exchange);
if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
- &dr.details.success.exchange_pub))
+ TALER_EXCHANGE_test_signing_key (pch->keys,
+ &dr.details.ok.exchange_pub))
{
GNUNET_break_op (0);
dr.hr.http_status = 0;
@@ -185,15 +183,15 @@ handle_purse_merge_finished (void *cls,
}
if (GNUNET_OK !=
TALER_exchange_online_purse_merged_verify (
- dr.details.success.etime,
+ 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.success.exchange_pub,
- &dr.details.success.exchange_sig))
+ &dr.details.ok.exchange_pub,
+ &dr.details.ok.exchange_sig))
{
GNUNET_break_op (0);
dr.hr.http_status = 0;
@@ -232,7 +230,6 @@ handle_purse_merge_finished (void *cls,
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,
@@ -257,6 +254,27 @@ handle_purse_merge_finished (void *cls,
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);
@@ -282,7 +300,9 @@ handle_purse_merge_finished (void *cls,
struct TALER_EXCHANGE_AccountMergeHandle *
TALER_EXCHANGE_account_merge (
- struct TALER_EXCHANGE_Handle *exchange,
+ 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,
@@ -296,14 +316,12 @@ TALER_EXCHANGE_account_merge (
void *cb_cls)
{
struct TALER_EXCHANGE_AccountMergeHandle *pch;
- struct GNUNET_CURL_Context *ctx;
json_t *merge_obj;
CURL *eh;
char arg_str[sizeof (pch->purse_pub) * 2 + 32];
char *reserve_url;
pch = GNUNET_new (struct TALER_EXCHANGE_AccountMergeHandle);
- pch->exchange = exchange;
pch->merge_priv = *merge_priv;
pch->cb = cb;
pch->cb_cls = cb_cls;
@@ -312,14 +330,12 @@ TALER_EXCHANGE_account_merge (
pch->purse_expiration = purse_expiration;
pch->purse_value_after_fees = *purse_value_after_fees;
if (NULL == reserve_exchange_url)
- pch->provider_url = GNUNET_strdup (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);
- GNUNET_assert (GNUNET_YES ==
- TEAH_handle_is_ready (exchange));
{
char pub_str[sizeof (*purse_pub) * 2];
char *end;
@@ -332,7 +348,7 @@ TALER_EXCHANGE_account_merge (
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/purses/%s/merge",
+ "purses/%s/merge",
pub_str);
}
reserve_url = TALER_reserve_make_payto (pch->provider_url,
@@ -344,8 +360,9 @@ TALER_EXCHANGE_account_merge (
GNUNET_free (pch);
return NULL;
}
- pch->url = TEAH_path_to_url (exchange,
- arg_str);
+ pch->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == pch->url)
{
GNUNET_break (0);
@@ -386,6 +403,7 @@ TALER_EXCHANGE_account_merge (
GNUNET_JSON_pack_timestamp ("merge_timestamp",
merge_timestamp));
GNUNET_assert (NULL != merge_obj);
+ GNUNET_free (reserve_url);
eh = TALER_EXCHANGE_curl_easy_get_ (pch->url);
if ( (NULL == eh) ||
(GNUNET_OK !=
@@ -406,7 +424,7 @@ TALER_EXCHANGE_account_merge (
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"URL for purse merge: `%s'\n",
pch->url);
- ctx = TEAH_handle_to_context (exchange);
+ pch->keys = TALER_EXCHANGE_keys_incref (keys);
pch->job = GNUNET_CURL_job_add2 (ctx,
eh,
pch->ctx.headers,
@@ -428,6 +446,7 @@ TALER_EXCHANGE_account_merge_cancel (
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);
}
diff --git a/src/lib/exchange_api_purses_get.c b/src/lib/exchange_api_purses_get.c
index 021954c2d..dc22c75ad 100644
--- a/src/lib/exchange_api_purses_get.c
+++ b/src/lib/exchange_api_purses_get.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ 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
@@ -39,9 +39,9 @@ struct TALER_EXCHANGE_PurseGetHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the exchange this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -94,22 +94,29 @@ handle_purse_get_finished (void *cls,
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_timestamp ("merge_timestamp",
- &dr.details.success.merge_timestamp),
- GNUNET_JSON_spec_timestamp ("deposit_timestamp",
- &dr.details.success.deposit_timestamp),
+ 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.success.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 ()
};
- const struct TALER_EXCHANGE_Keys *key_state;
if (GNUNET_OK !=
GNUNET_JSON_parse (j,
@@ -122,9 +129,8 @@ handle_purse_get_finished (void *cls,
break;
}
- key_state = TALER_EXCHANGE_get_keys (pgh->exchange);
if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
+ TALER_EXCHANGE_test_signing_key (pgh->keys,
&exchange_pub))
{
GNUNET_break_op (0);
@@ -134,9 +140,9 @@ handle_purse_get_finished (void *cls,
}
if (GNUNET_OK !=
TALER_exchange_online_purse_status_verify (
- dr.details.success.merge_timestamp,
- dr.details.success.deposit_timestamp,
- &dr.details.success.balance,
+ dr.details.ok.merge_timestamp,
+ dr.details.ok.deposit_timestamp,
+ &dr.details.ok.balance,
&exchange_pub,
&exchange_sig))
{
@@ -199,7 +205,9 @@ handle_purse_get_finished (void *cls,
struct TALER_EXCHANGE_PurseGetHandle *
TALER_EXCHANGE_purse_get (
- struct TALER_EXCHANGE_Handle *exchange,
+ 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,
@@ -209,15 +217,11 @@ TALER_EXCHANGE_purse_get (
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;
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
pgh = GNUNET_new (struct TALER_EXCHANGE_PurseGetHandle);
- pgh->exchange = exchange;
pgh->cb = cb;
pgh->cb_cls = cb_cls;
{
@@ -232,26 +236,25 @@ TALER_EXCHANGE_purse_get (
*end = '\0';
GNUNET_snprintf (timeout_str,
sizeof (timeout_str),
- "%llu",
- (unsigned long long)
- (timeout.rel_value_us
- / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us));
- if (GNUNET_TIME_relative_is_zero (timeout))
+ "%u",
+ tms);
+ if (0 == tms)
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/purses/%s/%s",
+ "purses/%s/%s",
cpub_str,
wait_for_merge ? "merge" : "deposit");
else
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/purses/%s/%s?timeout_ms=%s",
+ "purses/%s/%s?timeout_ms=%s",
cpub_str,
wait_for_merge ? "merge" : "deposit",
timeout_str);
}
- pgh->url = TEAH_path_to_url (exchange,
- arg_str);
+ pgh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == pgh->url)
{
GNUNET_free (pgh);
@@ -265,10 +268,18 @@ TALER_EXCHANGE_purse_get (
GNUNET_free (pgh);
return NULL;
}
- pgh->job = GNUNET_CURL_job_add (TEAH_handle_to_context (exchange),
+ 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;
}
@@ -283,6 +294,7 @@ TALER_EXCHANGE_purse_get_cancel (
pgh->job = NULL;
}
GNUNET_free (pgh->url);
+ TALER_EXCHANGE_keys_decref (pgh->keys);
GNUNET_free (pgh);
}
diff --git a/src/lib/exchange_api_recoup.c b/src/lib/exchange_api_recoup.c
index 8de4da123..56499f381 100644
--- a/src/lib/exchange_api_recoup.c
+++ b/src/lib/exchange_api_recoup.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2017-2022 Taler Systems SA
+ 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
@@ -40,9 +40,9 @@ struct TALER_EXCHANGE_RecoupHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the exchange this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -100,16 +100,15 @@ static enum GNUNET_GenericReturnValue
process_recoup_response (const struct TALER_EXCHANGE_RecoupHandle *ph,
const json_t *json)
{
- struct TALER_ReservePublicKeyP reserve_pub;
+ 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",
- &reserve_pub),
+ &rr.details.ok.reserve_pub),
GNUNET_JSON_spec_end ()
};
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = json,
- .http_status = MHD_HTTP_OK
- };
if (GNUNET_OK !=
GNUNET_JSON_parse (json,
@@ -120,8 +119,7 @@ process_recoup_response (const struct TALER_EXCHANGE_RecoupHandle *ph,
return GNUNET_SYSERR;
}
ph->cb (ph->cb_cls,
- &hr,
- &reserve_pub);
+ &rr);
return GNUNET_OK;
}
@@ -141,18 +139,16 @@ handle_recoup_finished (void *cls,
{
struct TALER_EXCHANGE_RecoupHandle *ph = cls;
const json_t *j = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
+ struct TALER_EXCHANGE_RecoupResponse rr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
};
- const struct TALER_EXCHANGE_Keys *keys;
ph->job = NULL;
- keys = TALER_EXCHANGE_get_keys (ph->exchange);
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
@@ -160,8 +156,8 @@ handle_recoup_finished (void *cls,
j))
{
GNUNET_break_op (0);
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- hr.http_status = 0;
+ rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ rr.hr.http_status = 0;
break;
}
TALER_EXCHANGE_recoup_cancel (ph);
@@ -169,36 +165,22 @@ handle_recoup_finished (void *cls,
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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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;
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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_ (keys,
+ TALER_EXCHANGE_get_min_denomination_ (ph->keys,
&min_key))
{
GNUNET_break (0);
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- hr.http_status = 0;
- break;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_coin_conflict_ (
- keys,
- j,
- &ph->pk,
- &ph->coin_pub,
- &ph->coin_sig,
- &min_key))
- {
- GNUNET_break (0);
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- hr.http_status = 0;
+ rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ rr.hr.http_status = 0;
break;
}
break;
@@ -207,65 +189,64 @@ handle_recoup_finished (void *cls,
/* 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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). */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
default:
/* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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) hr.ec);
+ (int) rr.hr.ec);
GNUNET_break (0);
break;
}
ph->cb (ph->cb_cls,
- &hr,
- NULL);
+ &rr);
TALER_EXCHANGE_recoup_cancel (ph);
}
struct TALER_EXCHANGE_RecoupHandle *
-TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_EXCHANGE_DenomPublicKey *pk,
- const struct TALER_DenominationSignature *denom_sig,
- const struct TALER_ExchangeWithdrawValues *exchange_vals,
- const struct TALER_PlanchetMasterSecretP *ps,
- TALER_EXCHANGE_RecoupResultCallback recoup_cb,
- void *recoup_cb_cls)
+TALER_EXCHANGE_recoup (
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_EXCHANGE_DenomPublicKey *pk,
+ const struct TALER_DenominationSignature *denom_sig,
+ const struct TALER_ExchangeWithdrawValues *exchange_vals,
+ const struct TALER_PlanchetMasterSecretP *ps,
+ TALER_EXCHANGE_RecoupResultCallback recoup_cb,
+ void *recoup_cb_cls)
{
struct TALER_EXCHANGE_RecoupHandle *ph;
- struct GNUNET_CURL_Context *ctx;
struct TALER_DenominationHashP h_denom_pub;
json_t *recoup_obj;
CURL *eh;
char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
struct TALER_CoinSpendPrivateKeyP coin_priv;
- union TALER_DenominationBlindingKeyP bks;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
- GNUNET_assert (GNUNET_YES ==
- TEAH_handle_is_ready (exchange));
ph = GNUNET_new (struct TALER_EXCHANGE_RecoupHandle);
TALER_planchet_setup_coin_priv (ps,
exchange_vals,
@@ -292,22 +273,32 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
&ph->coin_sig),
GNUNET_JSON_pack_data_auto ("coin_blind_key_secret",
&bks));
- if (TALER_DENOMINATION_CS == denom_sig->cipher)
+ switch (denom_sig->unblinded_sig->cipher)
{
- struct TALER_CsNonce nonce;
-
- /* NOTE: this is not elegant, and as per the note in TALER_coin_ev_hash()
- it is not strictly clear that the nonce is needed. Best case would be
- to find a way to include it more 'naturally' somehow, for example with
- the variant union version of bks! */
- TALER_cs_withdraw_nonce_derive (ps,
- &nonce);
- GNUNET_assert (
- 0 ==
- json_object_set_new (recoup_obj,
- "cs_nonce",
- GNUNET_JSON_from_data_auto (
- &nonce)));
+ 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,
+ "cs_nonce",
+ GNUNET_JSON_from_data_auto (
+ &nonce)));
+ }
}
{
@@ -322,19 +313,19 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/coins/%s/recoup",
+ "coins/%s/recoup",
pub_str);
}
- ph->exchange = exchange;
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 = TEAH_path_to_url (exchange,
- arg_str);
+ ph->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == ph->url)
{
json_decref (recoup_obj);
@@ -360,7 +351,7 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"URL for recoup: `%s'\n",
ph->url);
- ctx = TEAH_handle_to_context (exchange);
+ ph->keys = TALER_EXCHANGE_keys_incref (keys);
ph->job = GNUNET_CURL_job_add2 (ctx,
eh,
ph->ctx.headers,
@@ -380,6 +371,7 @@ TALER_EXCHANGE_recoup_cancel (struct TALER_EXCHANGE_RecoupHandle *ph)
}
GNUNET_free (ph->url);
TALER_curl_easy_post_finished (&ph->ctx);
+ TALER_EXCHANGE_keys_decref (ph->keys);
GNUNET_free (ph);
}
diff --git a/src/lib/exchange_api_recoup_refresh.c b/src/lib/exchange_api_recoup_refresh.c
index 00eeca070..0c2e21cbf 100644
--- a/src/lib/exchange_api_recoup_refresh.c
+++ b/src/lib/exchange_api_recoup_refresh.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2017-2022 Taler Systems SA
+ 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
@@ -40,9 +40,9 @@ struct TALER_EXCHANGE_RecoupRefreshHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the exchange this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -101,16 +101,15 @@ process_recoup_response (
const struct TALER_EXCHANGE_RecoupRefreshHandle *ph,
const json_t *json)
{
- struct TALER_CoinSpendPublicKeyP old_coin_pub;
+ 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",
- &old_coin_pub),
+ &rrr.details.ok.old_coin_pub),
GNUNET_JSON_spec_end ()
};
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = json,
- .http_status = MHD_HTTP_OK
- };
if (GNUNET_OK !=
GNUNET_JSON_parse (json,
@@ -121,8 +120,7 @@ process_recoup_response (
return GNUNET_SYSERR;
}
ph->cb (ph->cb_cls,
- &hr,
- &old_coin_pub);
+ &rrr);
return GNUNET_OK;
}
@@ -142,18 +140,16 @@ handle_recoup_refresh_finished (void *cls,
{
struct TALER_EXCHANGE_RecoupRefreshHandle *ph = cls;
const json_t *j = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
+ struct TALER_EXCHANGE_RecoupRefreshResponse rrr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
};
- const struct TALER_EXCHANGE_Keys *keys;
ph->job = NULL;
- keys = TALER_EXCHANGE_get_keys (ph->exchange);
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ rrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
@@ -161,8 +157,8 @@ handle_recoup_refresh_finished (void *cls,
j))
{
GNUNET_break_op (0);
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- hr.http_status = 0;
+ rrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ rrr.hr.http_status = 0;
break;
}
TALER_EXCHANGE_recoup_refresh_cancel (ph);
@@ -170,86 +166,60 @@ handle_recoup_refresh_finished (void *cls,
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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_CONFLICT:
- {
- struct TALER_Amount min_key;
-
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
- if (GNUNET_OK !=
- TALER_EXCHANGE_get_min_denomination_ (keys,
- &min_key))
- {
- GNUNET_break (0);
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- hr.http_status = 0;
- break;
- }
- if (GNUNET_OK !=
- TALER_EXCHANGE_check_coin_conflict_ (
- keys,
- j,
- &ph->pk,
- &ph->coin_pub,
- &ph->coin_sig,
- &min_key))
- {
- GNUNET_break (0);
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- hr.http_status = 0;
- break;
- }
- break;
- }
+ 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). */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ rrr.hr.ec = TALER_JSON_get_error_code (j);
+ rrr.hr.hint = TALER_JSON_get_error_hint (j);
break;
default:
/* unexpected response code */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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) hr.ec);
+ (int) rrr.hr.ec);
GNUNET_break (0);
break;
}
ph->cb (ph->cb_cls,
- &hr,
- NULL);
+ &rrr);
TALER_EXCHANGE_recoup_refresh_cancel (ph);
}
struct TALER_EXCHANGE_RecoupRefreshHandle *
TALER_EXCHANGE_recoup_refresh (
- struct TALER_EXCHANGE_Handle *exchange,
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
+ struct TALER_EXCHANGE_Keys *keys,
const struct TALER_EXCHANGE_DenomPublicKey *pk,
const struct TALER_DenominationSignature *denom_sig,
const struct TALER_ExchangeWithdrawValues *exchange_vals,
@@ -260,19 +230,15 @@ TALER_EXCHANGE_recoup_refresh (
void *recoup_cb_cls)
{
struct TALER_EXCHANGE_RecoupRefreshHandle *ph;
- struct GNUNET_CURL_Context *ctx;
struct TALER_DenominationHashP h_denom_pub;
json_t *recoup_obj;
CURL *eh;
char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
struct TALER_CoinSpendPrivateKeyP coin_priv;
- union TALER_DenominationBlindingKeyP bks;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
GNUNET_assert (NULL != recoup_cb);
- GNUNET_assert (GNUNET_YES ==
- TEAH_handle_is_ready (exchange));
ph = GNUNET_new (struct TALER_EXCHANGE_RecoupRefreshHandle);
- ph->exchange = exchange;
ph->pk = *pk;
memset (&ph->pk.key,
0,
@@ -305,23 +271,34 @@ TALER_EXCHANGE_recoup_refresh (
GNUNET_JSON_pack_data_auto ("coin_blind_key_secret",
&bks));
- if (TALER_DENOMINATION_CS == denom_sig->cipher)
+ switch (denom_sig->unblinded_sig->cipher)
{
- struct TALER_CsNonce nonce;
-
- /* NOTE: this is not elegant, and as per the note in TALER_coin_ev_hash()
- it is not strictly clear that the nonce is needed. Best case would be
- to find a way to include it more 'naturally' somehow, for example with
- the variant union version of bks! */
- TALER_cs_refresh_nonce_derive (rms,
- idx,
- &nonce);
- GNUNET_assert (
- 0 ==
- json_object_set_new (recoup_obj,
- "cs_nonce",
- GNUNET_JSON_from_data_auto (
- &nonce)));
+ 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_refresh_nonce_derive (rms,
+ idx,
+ &nonce.cs_nonce);
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (recoup_obj,
+ "cs_nonce",
+ GNUNET_JSON_from_data_auto (
+ &nonce)));
+ }
+ break;
}
{
@@ -336,12 +313,13 @@ TALER_EXCHANGE_recoup_refresh (
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/coins/%s/recoup-refresh",
+ "coins/%s/recoup-refresh",
pub_str);
}
- ph->url = TEAH_path_to_url (exchange,
- arg_str);
+ ph->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == ph->url)
{
json_decref (recoup_obj);
@@ -367,7 +345,7 @@ TALER_EXCHANGE_recoup_refresh (
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"URL for recoup-refresh: `%s'\n",
ph->url);
- ctx = TEAH_handle_to_context (exchange);
+ ph->keys = TALER_EXCHANGE_keys_incref (keys);
ph->job = GNUNET_CURL_job_add2 (ctx,
eh,
ph->ctx.headers,
@@ -388,6 +366,7 @@ TALER_EXCHANGE_recoup_refresh_cancel (
}
GNUNET_free (ph->url);
TALER_curl_easy_post_finished (&ph->ctx);
+ TALER_EXCHANGE_keys_decref (ph->keys);
GNUNET_free (ph);
}
diff --git a/src/lib/exchange_api_refresh_common.c b/src/lib/exchange_api_refresh_common.c
index 581e21152..4369367e4 100644
--- a/src/lib/exchange_api_refresh_common.c
+++ b/src/lib/exchange_api_refresh_common.c
@@ -45,6 +45,11 @@ TALER_EXCHANGE_free_melt_data_ (struct MeltData *md)
struct FreshCoinData *fcd = &md->fcds[j];
TALER_denom_pub_free (&fcd->fresh_pk);
+ for (size_t i = 0; i < TALER_CNC_KAPPA; i++)
+ {
+ TALER_age_commitment_proof_free (fcd->age_commitment_proofs[i]);
+ GNUNET_free (fcd->age_commitment_proofs[i]);
+ }
}
GNUNET_free (md->fcds);
}
@@ -63,7 +68,7 @@ TALER_EXCHANGE_get_melt_data_ (
{
struct TALER_Amount total;
struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_CsNonce nonces[rd->fresh_pks_len];
+ union GNUNET_CRYPTO_BlindSessionNonce nonces[rd->fresh_pks_len];
bool uses_cs = false;
GNUNET_CRYPTO_eddsa_key_get_public (&rd->melt_priv.eddsa_priv,
@@ -84,32 +89,41 @@ TALER_EXCHANGE_get_melt_data_ (
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (rd->melt_amount.currency,
&total));
- TALER_denom_pub_deep_copy (&md->melted_coin.pub_key,
- &rd->melt_pk.key);
- TALER_denom_sig_deep_copy (&md->melted_coin.sig,
- &rd->melt_sig);
+ TALER_denom_pub_copy (&md->melted_coin.pub_key,
+ &rd->melt_pk.key);
+ TALER_denom_sig_copy (&md->melted_coin.sig,
+ &rd->melt_sig);
md->fcds = GNUNET_new_array (md->num_fresh_coins,
struct FreshCoinData);
for (unsigned int j = 0; j<rd->fresh_pks_len; j++)
{
struct FreshCoinData *fcd = &md->fcds[j];
- if (alg_values[j].cipher != rd->fresh_pks[j].key.cipher)
+ TALER_denom_pub_copy (&fcd->fresh_pk,
+ &rd->fresh_pks[j].key);
+ GNUNET_assert (NULL != fcd->fresh_pk.bsign_pub_key);
+ if (alg_values[j].blinding_inputs->cipher !=
+ fcd->fresh_pk.bsign_pub_key->cipher)
{
GNUNET_break (0);
TALER_EXCHANGE_free_melt_data_ (md);
return GNUNET_SYSERR;
}
- if (TALER_DENOMINATION_CS == alg_values[j].cipher)
+ switch (fcd->fresh_pk.bsign_pub_key->cipher)
{
+ case GNUNET_CRYPTO_BSA_INVALID:
+ GNUNET_break (0);
+ TALER_EXCHANGE_free_melt_data_ (md);
+ return GNUNET_SYSERR;
+ case GNUNET_CRYPTO_BSA_RSA:
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
uses_cs = true;
- TALER_cs_refresh_nonce_derive (
- rms,
- j,
- &nonces[j]);
+ TALER_cs_refresh_nonce_derive (rms,
+ j,
+ &nonces[j].cs_nonce);
+ break;
}
- TALER_denom_pub_deep_copy (&fcd->fresh_pk,
- &rd->fresh_pks[j].key);
if ( (0 >
TALER_amount_add (&total,
&total,
@@ -165,10 +179,11 @@ TALER_EXCHANGE_get_melt_data_ (
struct TALER_CoinSpendPrivateKeyP *coin_priv = &fcd->coin_priv;
struct TALER_PlanchetMasterSecretP *ps = &fcd->ps[i];
struct TALER_RefreshCoinData *rcd = &md->rcd[i][j];
- union TALER_DenominationBlindingKeyP *bks = &fcd->bks[i];
+ union GNUNET_CRYPTO_BlindingSecretP *bks = &fcd->bks[i];
struct TALER_PlanchetDetail pd;
struct TALER_CoinPubHashP c_hash;
- struct TALER_AgeCommitmentHash *ach = NULL;
+ struct TALER_AgeCommitmentHash ach;
+ struct TALER_AgeCommitmentHash *pah = NULL;
TALER_transfer_secret_to_planchet_secret (&trans_sec,
j,
@@ -182,33 +197,30 @@ TALER_EXCHANGE_get_melt_data_ (
&alg_values[j],
bks);
- /* Handle age commitment, if present */
- if (NULL != md->melted_coin.age_commitment_proof)
+ if (NULL != rd->melt_age_commitment_proof)
{
- fcd->age_commitment_proof[i] = GNUNET_new (struct
- TALER_AgeCommitmentProof);
- ach = GNUNET_new (struct TALER_AgeCommitmentHash);
+ fcd->age_commitment_proofs[i] = GNUNET_new (struct
+ TALER_AgeCommitmentProof);
GNUNET_assert (GNUNET_OK ==
TALER_age_commitment_derive (
md->melted_coin.age_commitment_proof,
&trans_sec.key,
- fcd->age_commitment_proof[i]));
+ fcd->age_commitment_proofs[i]));
TALER_age_commitment_hash (
- &fcd->age_commitment_proof[i]->commitment,
- ach);
+ &fcd->age_commitment_proofs[i]->commitment,
+ &ach);
+ pah = &ach;
}
- if (TALER_DENOMINATION_CS == alg_values[j].cipher)
- pd.blinded_planchet.details.cs_blinded_planchet.nonce = nonces[j];
-
if (GNUNET_OK !=
TALER_planchet_prepare (&fcd->fresh_pk,
&alg_values[j],
bks,
+ &nonces[j],
coin_priv,
- ach,
+ pah,
&c_hash,
&pd))
{
diff --git a/src/lib/exchange_api_refresh_common.h b/src/lib/exchange_api_refresh_common.h
index c06824fec..f596e1e90 100644
--- a/src/lib/exchange_api_refresh_common.h
+++ b/src/lib/exchange_api_refresh_common.h
@@ -101,16 +101,15 @@ struct FreshCoinData
/**
* Arrays of age commitments and proofs to be created, one for each
- * cut-and-choose dimension. The entries in each list might be NULL and
- * indicate no age commitment/restriction on the particular coin.
+ * cut-and-choose dimension. NULL if age restriction is not applicable.
*/
- struct TALER_AgeCommitmentProof *age_commitment_proof[TALER_CNC_KAPPA];
+ struct TALER_AgeCommitmentProof *age_commitment_proofs[TALER_CNC_KAPPA];
/**
* Blinding key secrets for the coins, depending on the
* cut-and-choose.
*/
- union TALER_DenominationBlindingKeyP bks[TALER_CNC_KAPPA];
+ union GNUNET_CRYPTO_BlindingSecretP bks[TALER_CNC_KAPPA];
};
diff --git a/src/lib/exchange_api_refreshes_reveal.c b/src/lib/exchange_api_refreshes_reveal.c
index cd2a1d1f4..69c53a6c9 100644
--- a/src/lib/exchange_api_refreshes_reveal.c
+++ b/src/lib/exchange_api_refreshes_reveal.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2015-2022 Taler Systems SA
+ 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
@@ -40,11 +40,6 @@ struct TALER_EXCHANGE_RefreshesRevealHandle
{
/**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
* The url for this request.
*/
char *url;
@@ -107,10 +102,10 @@ refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,
const json_t *json,
struct TALER_EXCHANGE_RevealedCoinInfo *rcis)
{
- json_t *jsona;
+ const json_t *jsona;
struct GNUNET_JSON_Specification outer_spec[] = {
- GNUNET_JSON_spec_json ("ev_sigs",
- &jsona),
+ GNUNET_JSON_spec_array_const ("ev_sigs",
+ &jsona),
GNUNET_JSON_spec_end ()
};
@@ -122,24 +117,15 @@ refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- if (! json_is_array (jsona))
- {
- /* We expected an array of coins */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (outer_spec);
- return GNUNET_SYSERR;
- }
if (rrh->md.num_fresh_coins != json_array_size (jsona))
{
/* Number of coins generated does not match our expectation */
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (outer_spec);
return GNUNET_SYSERR;
}
for (unsigned int i = 0; i<rrh->md.num_fresh_coins; i++)
{
- struct TALER_EXCHANGE_RevealedCoinInfo *rci =
- &rcis[i];
+ struct TALER_EXCHANGE_RevealedCoinInfo *rci = &rcis[i];
const struct FreshCoinData *fcd = &rrh->md.fcds[i];
const struct TALER_DenominationPublicKey *pk;
json_t *jsonai;
@@ -152,26 +138,22 @@ refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,
GNUNET_JSON_spec_end ()
};
struct TALER_FreshCoin coin;
- union TALER_DenominationBlindingKeyP bks;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ const struct TALER_AgeCommitmentHash *pah = NULL;
rci->ps = fcd->ps[rrh->noreveal_index];
rci->bks = fcd->bks[rrh->noreveal_index];
- rci->age_commitment_proof = fcd->age_commitment_proof[rrh->noreveal_index];
- rci->h_age_commitment = NULL;
+ rci->age_commitment_proof = NULL;
pk = &fcd->fresh_pk;
jsonai = json_array_get (jsona, i);
-
GNUNET_assert (NULL != jsonai);
- GNUNET_assert (
- (NULL != rrh->md.melted_coin.age_commitment_proof) ==
- (NULL != rci->age_commitment_proof));
-
- if (NULL != rci->age_commitment_proof)
+ if (NULL != rrh->md.melted_coin.age_commitment_proof)
{
- rci->h_age_commitment = GNUNET_new (struct TALER_AgeCommitmentHash);
- TALER_age_commitment_hash (
- &rci->age_commitment_proof->commitment,
- rci->h_age_commitment);
+ rci->age_commitment_proof
+ = fcd->age_commitment_proofs[rrh->noreveal_index];
+ TALER_age_commitment_hash (&rci->age_commitment_proof->commitment,
+ &rci->h_age_commitment);
+ pah = &rci->h_age_commitment;
}
if (GNUNET_OK !=
@@ -180,7 +162,6 @@ refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,
NULL, NULL))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (outer_spec);
return GNUNET_SYSERR;
}
@@ -194,28 +175,28 @@ refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,
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,
- rci->h_age_commitment,
- &coin_hash);
+ TALER_coin_pub_hash (
+ &coin_pub,
+ pah,
+ &coin_hash);
if (GNUNET_OK !=
- TALER_planchet_to_coin (pk,
- &blind_sig,
- &bks,
- &rci->coin_priv,
- rci->h_age_commitment,
- &coin_hash,
- &rrh->alg_values[i],
- &coin))
+ TALER_planchet_to_coin (
+ pk,
+ &blind_sig,
+ &bks,
+ &rci->coin_priv,
+ pah,
+ &coin_hash,
+ &rrh->alg_values[i],
+ &coin))
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
- GNUNET_JSON_parse_free (outer_spec);
return GNUNET_SYSERR;
}
GNUNET_JSON_parse_free (spec);
rci->sig = coin.sig;
}
- GNUNET_JSON_parse_free (outer_spec);
return GNUNET_OK;
}
@@ -266,14 +247,17 @@ handle_refresh_reveal_finished (void *cls,
else
{
GNUNET_assert (rrh->noreveal_index < TALER_CNC_KAPPA);
- rr.details.success.num_coins = rrh->md.num_fresh_coins;
- rr.details.success.coins = rcis;
+ rr.details.ok.num_coins = rrh->md.num_fresh_coins;
+ rr.details.ok.coins = rcis;
rrh->reveal_cb (rrh->reveal_cb_cls,
&rr);
rrh->reveal_cb = NULL;
}
for (unsigned int i = 0; i<rrh->md.num_fresh_coins; i++)
+ {
TALER_denom_sig_free (&rcis[i].sig);
+ TALER_age_commitment_proof_free (rcis[i].age_commitment_proof);
+ }
TALER_EXCHANGE_refreshes_reveal_cancel (rrh);
return;
}
@@ -321,11 +305,12 @@ handle_refresh_reveal_finished (void *cls,
struct TALER_EXCHANGE_RefreshesRevealHandle *
TALER_EXCHANGE_refreshes_reveal (
- struct TALER_EXCHANGE_Handle *exchange,
+ struct GNUNET_CURL_Context *ctx,
+ const char *url,
const struct TALER_RefreshMasterSecretP *rms,
const struct TALER_EXCHANGE_RefreshData *rd,
unsigned int num_coins,
- const struct TALER_ExchangeWithdrawValues *alg_values,
+ const struct TALER_ExchangeWithdrawValues alg_values[static num_coins],
uint32_t noreveal_index,
TALER_EXCHANGE_RefreshesRevealCallback reveal_cb,
void *reveal_cb_cls)
@@ -338,7 +323,6 @@ TALER_EXCHANGE_refreshes_reveal (
json_t *link_sigs;
json_t *old_age_commitment = NULL;
CURL *eh;
- struct GNUNET_CURL_Context *ctx;
struct MeltData md;
char arg_str[sizeof (struct TALER_RefreshCommitmentP) * 2 + 32];
bool send_rms = false;
@@ -353,12 +337,6 @@ TALER_EXCHANGE_refreshes_reveal (
GNUNET_break (0);
return NULL;
}
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
if (GNUNET_OK !=
TALER_EXCHANGE_get_melt_data_ (rms,
rd,
@@ -378,7 +356,8 @@ TALER_EXCHANGE_refreshes_reveal (
const struct TALER_RefreshCoinData *rcd = &md.rcd[noreveal_index][i];
struct TALER_DenominationHashP denom_hash;
- if (TALER_DENOMINATION_CS == md.fcds[i].fresh_pk.cipher)
+ if (GNUNET_CRYPTO_BSA_CS ==
+ md.fcds[i].fresh_pk.bsign_pub_key->cipher)
send_rms = true;
TALER_denom_pub_hash (&md.fcds[i].fresh_pk,
&denom_hash);
@@ -429,20 +408,20 @@ TALER_EXCHANGE_refreshes_reveal (
}
/* build array of old age commitment, if applicable */
- GNUNET_assert ((NULL == rd->melt_age_commitment_proof) ==
- (NULL == rd->melt_h_age_commitment));
if (NULL != rd->melt_age_commitment_proof)
{
+ GNUNET_assert (NULL != rd->melt_h_age_commitment);
GNUNET_assert (NULL != (old_age_commitment = json_array ()));
for (size_t i = 0; i < rd->melt_age_commitment_proof->commitment.num; i++)
{
- GNUNET_assert (0 ==
- json_array_append_new (
- old_age_commitment,
- GNUNET_JSON_from_data_auto (
- &rd->melt_age_commitment_proof->
- commitment.keys[i])));
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = json_array_append_new (
+ old_age_commitment,
+ GNUNET_JSON_from_data_auto (
+ &rd->melt_age_commitment_proof->commitment.keys[i]));
+ GNUNET_assert (0 == ret);
}
}
@@ -478,22 +457,24 @@ TALER_EXCHANGE_refreshes_reveal (
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/refreshes/%s/reveal",
+ "refreshes/%s/reveal",
pub_str);
}
/* finally, we can actually issue the request */
rrh = GNUNET_new (struct TALER_EXCHANGE_RefreshesRevealHandle);
- rrh->exchange = exchange;
rrh->noreveal_index = noreveal_index;
rrh->reveal_cb = reveal_cb;
rrh->reveal_cb_cls = reveal_cb_cls;
rrh->md = md;
- rrh->alg_values = GNUNET_memdup (alg_values,
- md.num_fresh_coins
- * sizeof (struct
- TALER_ExchangeWithdrawValues));
- rrh->url = TEAH_path_to_url (rrh->exchange,
- arg_str);
+ rrh->alg_values
+ = GNUNET_new_array (md.num_fresh_coins,
+ struct TALER_ExchangeWithdrawValues);
+ for (unsigned int i = 0; i<md.num_fresh_coins; i++)
+ TALER_denom_ewv_copy (&rrh->alg_values[i],
+ &alg_values[i]);
+ rrh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == rrh->url)
{
json_decref (reveal_obj);
@@ -521,7 +502,6 @@ TALER_EXCHANGE_refreshes_reveal (
return NULL;
}
json_decref (reveal_obj);
- ctx = TEAH_handle_to_context (rrh->exchange);
rrh->job = GNUNET_CURL_job_add2 (ctx,
eh,
rrh->ctx.headers,
@@ -540,6 +520,8 @@ TALER_EXCHANGE_refreshes_reveal_cancel (
GNUNET_CURL_job_cancel (rrh->job);
rrh->job = NULL;
}
+ for (unsigned int i = 0; i<rrh->md.num_fresh_coins; i++)
+ TALER_denom_ewv_free (&rrh->alg_values[i]);
GNUNET_free (rrh->alg_values);
GNUNET_free (rrh->url);
TALER_curl_easy_post_finished (&rrh->ctx);
diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c
index a937c18af..9159b55f2 100644
--- a/src/lib/exchange_api_refund.c
+++ b/src/lib/exchange_api_refund.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ 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
@@ -39,9 +39,9 @@ struct TALER_EXCHANGE_RefundHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the exchange this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -117,7 +117,6 @@ verify_refund_signature_ok (struct TALER_EXCHANGE_RefundHandle *rh,
struct TALER_ExchangePublicKeyP *exchange_pub,
struct TALER_ExchangeSignatureP *exchange_sig)
{
- const struct TALER_EXCHANGE_Keys *key_state;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("exchange_sig",
exchange_sig),
@@ -134,9 +133,8 @@ verify_refund_signature_ok (struct TALER_EXCHANGE_RefundHandle *rh,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- key_state = TALER_EXCHANGE_get_keys (rh->exchange);
if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
+ TALER_EXCHANGE_test_signing_key (rh->keys,
exchange_pub))
{
GNUNET_break_op (0);
@@ -160,313 +158,6 @@ verify_refund_signature_ok (struct TALER_EXCHANGE_RefundHandle *rh,
/**
- * Verify that the information in the "409 Conflict" response
- * from the exchange is valid and indeed shows that the refund
- * amount requested is too high.
- *
- * @param[in,out] rh refund handle (refund fee added)
- * @param json json reply with the coin transaction history
- * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
- */
-static enum GNUNET_GenericReturnValue
-verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh,
- const json_t *json)
-{
- json_t *history;
- struct TALER_DenominationHashP h_denom_pub;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("history",
- &history),
- GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
- &h_denom_pub),
- GNUNET_JSON_spec_end ()
- };
- size_t len;
- struct TALER_Amount dtotal;
- bool have_deposit;
- struct TALER_Amount rtotal;
- bool have_refund;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- len = json_array_size (history);
- if (0 == len)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- have_deposit = false;
- have_refund = false;
- for (size_t off = 0; off<len; off++)
- {
- json_t *transaction;
- struct TALER_Amount amount;
- const char *type;
- struct GNUNET_JSON_Specification spec_glob[] = {
- TALER_JSON_spec_amount_any ("amount",
- &amount),
- GNUNET_JSON_spec_string ("type",
- &type),
- GNUNET_JSON_spec_end ()
- };
-
- transaction = json_array_get (history,
- off);
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- spec_glob,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if (0 == strcasecmp (type,
- "DEPOSIT"))
- {
- struct TALER_Amount deposit_fee;
- struct TALER_MerchantWireHashP h_wire;
- struct TALER_PrivateContractHashP h_contract_terms;
- struct TALER_AgeCommitmentHash h_age_commitment;
- bool no_hac;
- // struct TALER_ExtensionContractHashP h_extensions; // FIXME #7270!
- struct GNUNET_TIME_Timestamp wallet_timestamp;
- struct TALER_MerchantPublicKeyP merchant_pub;
- struct GNUNET_TIME_Timestamp refund_deadline;
- struct TALER_CoinSpendSignatureP sig;
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &sig),
- GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
- &h_contract_terms),
- GNUNET_JSON_spec_fixed_auto ("h_wire",
- &h_wire),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
- &h_age_commitment),
- &no_hac),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &wallet_timestamp),
- GNUNET_JSON_spec_timestamp ("refund_deadline",
- &refund_deadline),
- TALER_JSON_spec_amount_any ("deposit_fee",
- &deposit_fee),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &merchant_pub),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_wallet_deposit_verify (&amount,
- &deposit_fee,
- &h_wire,
- &h_contract_terms,
- no_hac
- ? NULL
- : &h_age_commitment,
- NULL /* FIXME #7270-OEC: h_extensions! */,
- &h_denom_pub,
- wallet_timestamp,
- &merchant_pub,
- refund_deadline,
- &rh->coin_pub,
- &sig))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if ( (0 != GNUNET_memcmp (&rh->h_contract_terms,
- &h_contract_terms)) ||
- (0 != GNUNET_memcmp (&rh->merchant,
- &merchant_pub)) )
- {
- /* deposit information is about a different merchant/contract */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if (have_deposit)
- {
- /* this cannot really happen, but we conservatively support it anyway */
- if (GNUNET_YES !=
- TALER_amount_cmp_currency (&amount,
- &dtotal))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- GNUNET_break (0 <=
- TALER_amount_add (&dtotal,
- &dtotal,
- &amount));
- }
- else
- {
- dtotal = amount;
- have_deposit = true;
- }
- }
- else if (0 == strcasecmp (type,
- "REFUND"))
- {
- struct TALER_MerchantSignatureP sig;
- struct TALER_Amount refund_fee;
- struct TALER_Amount sig_amount;
- 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 ("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 (transaction,
- ispec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if (0 >
- TALER_amount_add (&sig_amount,
- &refund_fee,
- &amount))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_merchant_refund_verify (&rh->coin_pub,
- &h_contract_terms,
- rtransaction_id,
- &sig_amount,
- &merchant_pub,
- &sig))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if ( (0 != GNUNET_memcmp (&rh->h_contract_terms,
- &h_contract_terms)) ||
- (0 != GNUNET_memcmp (&rh->merchant,
- &merchant_pub)) )
- {
- /* refund is about a different merchant/contract */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if (rtransaction_id == rh->rtransaction_id)
- {
- /* Eh, this shows either a dependency failure or idempotency,
- but must not happen in a conflict reply. Fail! */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
-
- if (have_refund)
- {
- if (GNUNET_YES !=
- TALER_amount_cmp_currency (&amount,
- &rtotal))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- GNUNET_break (0 <=
- TALER_amount_add (&rtotal,
- &rtotal,
- &amount));
- }
- else
- {
- rtotal = amount;
- have_refund = true;
- }
- }
- else
- {
- /* unexpected type, new version on server? */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected type `%s' in response for exchange refund\n",
- type);
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- }
-
- if (have_refund)
- {
- if (0 >
- TALER_amount_add (&rtotal,
- &rtotal,
- &rh->refund_amount))
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- }
- else
- {
- rtotal = rh->refund_amount;
- have_refund = true;
- }
- if (! have_deposit)
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if (-1 != TALER_amount_cmp (&dtotal,
- &rtotal))
- {
- /* rtotal <= dtotal is fine, no conflict! */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- /* dtotal < rtotal: that's a conflict! */
- GNUNET_JSON_parse_free (spec);
- 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.
@@ -479,11 +170,11 @@ static enum GNUNET_GenericReturnValue
verify_failed_dependency_ok (struct TALER_EXCHANGE_RefundHandle *rh,
const json_t *json)
{
- json_t *h;
+ const json_t *h;
json_t *e;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("history",
- &h),
+ GNUNET_JSON_spec_array_const ("history",
+ &h),
GNUNET_JSON_spec_end ()
};
@@ -495,11 +186,9 @@ verify_failed_dependency_ok (struct TALER_EXCHANGE_RefundHandle *rh,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- if ( (! json_is_array (h)) ||
- (1 != json_array_size (h) ) )
+ if (1 != json_array_size (h))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
e = json_array_get (h, 0);
@@ -535,7 +224,6 @@ verify_failed_dependency_ok (struct TALER_EXCHANGE_RefundHandle *rh,
NULL, NULL))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
@@ -547,7 +235,6 @@ verify_failed_dependency_ok (struct TALER_EXCHANGE_RefundHandle *rh,
&sig))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
if ( (rtransaction_id != rh->rtransaction_id) ||
@@ -559,11 +246,9 @@ verify_failed_dependency_ok (struct TALER_EXCHANGE_RefundHandle *rh,
&amount)) )
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
}
- GNUNET_JSON_parse_free (spec);
return GNUNET_OK;
}
@@ -582,37 +267,28 @@ handle_refund_finished (void *cls,
const void *response)
{
struct TALER_EXCHANGE_RefundHandle *rh = cls;
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct TALER_ExchangeSignatureP exchange_sig;
- struct TALER_ExchangePublicKeyP *ep = NULL;
- struct TALER_ExchangeSignatureP *es = NULL;
const json_t *j = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
+ struct TALER_EXCHANGE_RefundResponse rr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
};
rh->job = NULL;
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
verify_refund_signature_ok (rh,
j,
- &exchange_pub,
- &exchange_sig))
+ &rr.details.ok.exchange_pub,
+ &rr.details.ok.exchange_sig))
{
GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_SIGNATURE_BY_EXCHANGE;
- }
- else
- {
- ep = &exchange_pub;
- es = &exchange_sig;
+ rr.hr.http_status = 0;
+ rr.hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_SIGNATURE_BY_EXCHANGE;
}
break;
case MHD_HTTP_BAD_REQUEST:
@@ -620,45 +296,36 @@ handle_refund_finished (void *cls,
(or API version conflict); also can happen if the currency
differs (which we should obviously never support).
Just pass JSON reply to the application */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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 */
- if (GNUNET_OK !=
- verify_conflict_history_ok (rh,
- j))
- {
- GNUNET_break (0);
- json_dumpf (j,
- stderr,
- JSON_INDENT (2));
- hr.http_status = 0;
- hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE;
- hr.hint = "conflict information provided by exchange is invalid";
- break;
- }
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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). */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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 !=
@@ -666,44 +333,44 @@ handle_refund_finished (void *cls,
j))
{
GNUNET_break (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_EXCHANGE_REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE;
- hr.hint = "failed precondition proof returned by exchange is invalid";
+ 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. */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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);
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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,
- hr.ec);
+ rr.hr.ec);
break;
}
rh->cb (rh->cb_cls,
- &hr,
- ep,
- es);
+ &rr);
TALER_EXCHANGE_refund_cancel (rh);
}
struct TALER_EXCHANGE_RefundHandle *
TALER_EXCHANGE_refund (
- struct TALER_EXCHANGE_Handle *exchange,
+ 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,
@@ -715,13 +382,10 @@ TALER_EXCHANGE_refund (
struct TALER_MerchantPublicKeyP merchant_pub;
struct TALER_MerchantSignatureP merchant_sig;
struct TALER_EXCHANGE_RefundHandle *rh;
- struct GNUNET_CURL_Context *ctx;
json_t *refund_obj;
CURL *eh;
char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
- GNUNET_assert (GNUNET_YES ==
- TEAH_handle_is_ready (exchange));
GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
&merchant_pub.eddsa_pub);
TALER_merchant_refund_sign (coin_pub,
@@ -742,7 +406,7 @@ TALER_EXCHANGE_refund (
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/coins/%s/refund",
+ "coins/%s/refund",
pub_str);
}
refund_obj = GNUNET_JSON_PACK (
@@ -757,11 +421,11 @@ TALER_EXCHANGE_refund (
GNUNET_JSON_pack_data_auto ("merchant_sig",
&merchant_sig));
rh = GNUNET_new (struct TALER_EXCHANGE_RefundHandle);
- rh->exchange = exchange;
rh->cb = cb;
rh->cb_cls = cb_cls;
- rh->url = TEAH_path_to_url (exchange,
- arg_str);
+ rh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == rh->url)
{
json_decref (refund_obj);
@@ -792,7 +456,7 @@ TALER_EXCHANGE_refund (
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"URL for refund: `%s'\n",
rh->url);
- ctx = TEAH_handle_to_context (exchange);
+ rh->keys = TALER_EXCHANGE_keys_incref (keys);
rh->job = GNUNET_CURL_job_add2 (ctx,
eh,
rh->ctx.headers,
@@ -812,6 +476,7 @@ TALER_EXCHANGE_refund_cancel (struct TALER_EXCHANGE_RefundHandle *refund)
}
GNUNET_free (refund->url);
TALER_curl_easy_post_finished (&refund->ctx);
+ TALER_EXCHANGE_keys_decref (refund->keys);
GNUNET_free (refund);
}
diff --git a/src/lib/exchange_api_reserves_status.c b/src/lib/exchange_api_reserves_attest.c
index 35f5846f3..d5a867114 100644
--- a/src/lib/exchange_api_reserves_status.c
+++ b/src/lib/exchange_api_reserves_attest.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ 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
@@ -15,13 +15,13 @@
<http://www.gnu.org/licenses/>
*/
/**
- * @file lib/exchange_api_reserves_status.c
- * @brief Implementation of the POST /reserves/$RESERVE_PUB/status requests
+ * @file lib/exchange_api_reserves_attest.c
+ * @brief Implementation of the POST /reserves-attest/$RESERVE_PUB requests
* @author Christian Grothoff
*/
#include "platform.h"
#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
+#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>
@@ -33,15 +33,15 @@
/**
- * @brief A /reserves/$RID/status Handle
+ * @brief A /reserves-attest/$RID Handle
*/
-struct TALER_EXCHANGE_ReservesStatusHandle
+struct TALER_EXCHANGE_ReservesAttestHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -62,7 +62,7 @@ struct TALER_EXCHANGE_ReservesStatusHandle
/**
* Function to call with the result.
*/
- TALER_EXCHANGE_ReservesStatusCallback cb;
+ TALER_EXCHANGE_ReservesPostAttestCallback cb;
/**
* Public key of the reserve we are querying.
@@ -78,7 +78,7 @@ struct TALER_EXCHANGE_ReservesStatusHandle
/**
- * We received an #MHD_HTTP_OK status code. Handle the JSON
+ * We received an #MHD_HTTP_OK attest code. Handle the JSON
* response.
*
* @param rsh handle of the request
@@ -86,22 +86,25 @@ struct TALER_EXCHANGE_ReservesStatusHandle
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
-handle_reserves_status_ok (struct TALER_EXCHANGE_ReservesStatusHandle *rsh,
+handle_reserves_attest_ok (struct TALER_EXCHANGE_ReservesAttestHandle *rsh,
const json_t *j)
{
- json_t *history;
- unsigned int len;
- struct TALER_EXCHANGE_ReserveStatus rs = {
+ struct TALER_EXCHANGE_ReservePostAttestResult rs = {
.hr.reply = j,
.hr.http_status = MHD_HTTP_OK
};
+ const json_t *attributes;
struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("balance",
- &rs.details.ok.balance),
- GNUNET_JSON_spec_bool ("kyc_passed",
- &rs.details.ok.kyc_ok),
- GNUNET_JSON_spec_json ("history",
- &history),
+ 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 ()
};
@@ -114,39 +117,36 @@ handle_reserves_status_ok (struct TALER_EXCHANGE_ReservesStatusHandle *rsh,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- len = json_array_size (history);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (rsh->keys,
+ &rs.details.ok.exchange_pub))
{
- struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
-
- rhistory = GNUNET_new_array (len,
- struct TALER_EXCHANGE_ReserveHistoryEntry);
- if (GNUNET_OK !=
- TALER_EXCHANGE_parse_reserve_history (rsh->exchange,
- 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);
- TALER_EXCHANGE_free_reserve_history (rhistory,
- len);
- 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;
- }
- TALER_EXCHANGE_free_reserve_history (rhistory,
- len);
+ 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;
}
@@ -154,20 +154,20 @@ handle_reserves_status_ok (struct TALER_EXCHANGE_ReservesStatusHandle *rsh,
/**
* Function called when we're done processing the
- * HTTP /reserves/$RID/status request.
+ * HTTP /reserves-attest/$RID request.
*
- * @param cls the `struct TALER_EXCHANGE_ReservesStatusHandle`
+ * @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_status_finished (void *cls,
+handle_reserves_attest_finished (void *cls,
long response_code,
const void *response)
{
- struct TALER_EXCHANGE_ReservesStatusHandle *rsh = cls;
+ struct TALER_EXCHANGE_ReservesAttestHandle *rsh = cls;
const json_t *j = response;
- struct TALER_EXCHANGE_ReserveStatus rs = {
+ struct TALER_EXCHANGE_ReservePostAttestResult rs = {
.hr.reply = j,
.hr.http_status = (unsigned int) response_code
};
@@ -180,7 +180,7 @@ handle_reserves_status_finished (void *cls,
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
- handle_reserves_status_ok (rsh,
+ handle_reserves_attest_ok (rsh,
j))
{
rs.hr.http_status = 0;
@@ -207,6 +207,11 @@ handle_reserves_status_finished (void *cls,
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 */
@@ -219,7 +224,7 @@ handle_reserves_status_finished (void *cls,
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 status\n",
+ "Unexpected response code %u/%d for reserves attest\n",
(unsigned int) response_code,
(int) rs.hr.ec);
break;
@@ -230,33 +235,42 @@ handle_reserves_status_finished (void *cls,
&rs);
rsh->cb = NULL;
}
- TALER_EXCHANGE_reserves_status_cancel (rsh);
+ TALER_EXCHANGE_reserves_attest_cancel (rsh);
}
-struct TALER_EXCHANGE_ReservesStatusHandle *
-TALER_EXCHANGE_reserves_status (
- struct TALER_EXCHANGE_Handle *exchange,
+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,
- TALER_EXCHANGE_ReservesStatusCallback cb,
+ unsigned int attributes_length,
+ const char *attributes[const static attributes_length],
+ TALER_EXCHANGE_ReservesPostAttestCallback cb,
void *cb_cls)
{
- struct TALER_EXCHANGE_ReservesStatusHandle *rsh;
- struct GNUNET_CURL_Context *ctx;
+ struct TALER_EXCHANGE_ReservesAttestHandle *rsh;
CURL *eh;
char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
struct TALER_ReserveSignatureP reserve_sig;
- struct GNUNET_TIME_Timestamp ts
- = GNUNET_TIME_timestamp_get ();
+ json_t *details;
+ struct GNUNET_TIME_Timestamp ts;
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
+ if (0 == attributes_length)
{
GNUNET_break (0);
return NULL;
}
- rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesStatusHandle);
- rsh->exchange = exchange;
+ 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,
@@ -273,13 +287,15 @@ TALER_EXCHANGE_reserves_status (
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/reserves/%s/status",
+ "reserves-attest/%s",
pub_str);
}
- rsh->url = TEAH_path_to_url (exchange,
- arg_str);
+ rsh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == rsh->url)
{
+ json_decref (details);
GNUNET_free (rsh);
return NULL;
}
@@ -287,47 +303,52 @@ TALER_EXCHANGE_reserves_status (
if (NULL == eh)
{
GNUNET_break (0);
+ json_decref (details);
GNUNET_free (rsh->url);
GNUNET_free (rsh);
return NULL;
}
- TALER_wallet_reserve_status_sign (ts,
- reserve_priv,
- &reserve_sig);
+ ts = GNUNET_TIME_timestamp_get ();
+ TALER_wallet_reserve_attest_request_sign (ts,
+ details,
+ reserve_priv,
+ &reserve_sig);
{
- json_t *status_obj = GNUNET_JSON_PACK (
+ 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_data_auto ("reserve_sig",
- &reserve_sig));
+ GNUNET_JSON_pack_array_steal ("details",
+ details));
if (GNUNET_OK !=
TALER_curl_easy_post (&rsh->post_ctx,
eh,
- status_obj))
+ attest_obj))
{
GNUNET_break (0);
curl_easy_cleanup (eh);
- json_decref (status_obj);
+ json_decref (attest_obj);
GNUNET_free (rsh->url);
GNUNET_free (rsh);
return NULL;
}
- json_decref (status_obj);
+ json_decref (attest_obj);
}
- ctx = TEAH_handle_to_context (exchange);
rsh->job = GNUNET_CURL_job_add2 (ctx,
eh,
rsh->post_ctx.headers,
- &handle_reserves_status_finished,
+ &handle_reserves_attest_finished,
rsh);
+ rsh->keys = TALER_EXCHANGE_keys_incref (keys);
return rsh;
}
void
-TALER_EXCHANGE_reserves_status_cancel (
- struct TALER_EXCHANGE_ReservesStatusHandle *rsh)
+TALER_EXCHANGE_reserves_attest_cancel (
+ struct TALER_EXCHANGE_ReservesAttestHandle *rsh)
{
if (NULL != rsh->job)
{
@@ -335,9 +356,10 @@ TALER_EXCHANGE_reserves_status_cancel (
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_status.c */
+/* 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
new file mode 100644
index 000000000..a3769a22f
--- /dev/null
+++ b/src/lib/exchange_api_reserves_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_reserves_close.c
+ * @brief Implementation of the POST /reserves/$RESERVE_PUB/close requests
+ * @author Christian Grothoff
+ */
+#include "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_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /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 char *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_PaytoHashP 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)
+ TALER_payto_hash (target_payto_uri,
+ &h_payto);
+ TALER_wallet_reserve_close_sign (rch->ts,
+ (NULL != target_payto_uri)
+ ? &h_payto
+ : NULL,
+ reserve_priv,
+ &rch->reserve_sig);
+ {
+ json_t *close_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("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
index d829b4593..b6980dd1d 100644
--- a/src/lib/exchange_api_reserves_get.c
+++ b/src/lib/exchange_api_reserves_get.c
@@ -39,11 +39,6 @@ struct TALER_EXCHANGE_ReservesGetHandle
{
/**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
* The url for this request.
*/
char *url;
@@ -168,9 +163,10 @@ handle_reserves_get_finished (void *cls,
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\n",
+ "Unexpected response code %u/%d for GET %s\n",
(unsigned int) response_code,
- (int) rs.hr.ec);
+ (int) rs.hr.ec,
+ rgh->url);
break;
}
if (NULL != rgh->cb)
@@ -185,23 +181,20 @@ handle_reserves_get_finished (void *cls,
struct TALER_EXCHANGE_ReservesGetHandle *
TALER_EXCHANGE_reserves_get (
- struct TALER_EXCHANGE_Handle *exchange,
+ 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;
- struct GNUNET_CURL_Context *ctx;
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;
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
{
char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
char *end;
@@ -215,29 +208,27 @@ TALER_EXCHANGE_reserves_get (
*end = '\0';
GNUNET_snprintf (timeout_str,
sizeof (timeout_str),
- "%llu",
- (unsigned long long)
- (timeout.rel_value_us
- / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us));
- if (GNUNET_TIME_relative_is_zero (timeout))
+ "%u",
+ tms);
+ if (0 == tms)
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/reserves/%s",
+ "reserves/%s",
pub_str);
else
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/reserves/%s?timeout_ms=%s",
+ "reserves/%s?timeout_ms=%s",
pub_str,
timeout_str);
}
rgh = GNUNET_new (struct TALER_EXCHANGE_ReservesGetHandle);
- rgh->exchange = exchange;
rgh->cb = cb;
rgh->cb_cls = cb_cls;
rgh->reserve_pub = *reserve_pub;
- rgh->url = TEAH_path_to_url (exchange,
- arg_str);
+ rgh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == rgh->url)
{
GNUNET_free (rgh);
@@ -251,7 +242,13 @@ TALER_EXCHANGE_reserves_get (
GNUNET_free (rgh);
return NULL;
}
- ctx = TEAH_handle_to_context (exchange);
+ 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,
diff --git a/src/lib/exchange_api_reserves_get_attestable.c b/src/lib/exchange_api_reserves_get_attestable.c
new file mode 100644
index 000000000..f58e0592e
--- /dev/null
+++ b/src/lib/exchange_api_reserves_get_attestable.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_reserves_get_attestable.c
+ * @brief Implementation of the GET_ATTESTABLE /reserves/$RESERVE_PUB requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /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
index 2a0dd5651..0654ad837 100644
--- a/src/lib/exchange_api_reserves_history.c
+++ b/src/lib/exchange_api_reserves_history.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ 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
@@ -39,9 +39,9 @@ struct TALER_EXCHANGE_ReservesHistoryHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the exchange this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -65,29 +65,808 @@ struct TALER_EXCHANGE_ReservesHistoryHandle
TALER_EXCHANGE_ReservesHistoryCallback cb;
/**
+ * Public key of the reserve we are querying.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
* Closure for @a cb.
*/
void *cb_cls;
/**
- * Public key of the reserve we are querying.
+ * Where to store the etag (if any).
*/
- struct TALER_ReservePublicKeyP reserve_pub;
+ 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;
/**
- * Our signature.
+ * Array of UUIDs.
*/
- struct TALER_ReserveSignatureP reserve_sig;
+ struct GNUNET_HashCode *uuids;
/**
- * When did we make the request.
+ * Where to sum up total inbound amounts.
*/
- struct GNUNET_TIME_Timestamp ts;
+ 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)
+{
+ const char *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",
+ &timestamp),
+ TALER_JSON_spec_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 = GNUNET_strdup (wire_uri);
+ rh->details.in_details.wire_reference = wire_reference;
+ rh->details.in_details.timestamp = timestamp;
+ return GNUNET_OK;
+}
+
+
+/**
+ * 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_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+ struct HistoryParseContext *uc,
+ const json_t *transaction)
+{
+ struct TALER_ReserveSignatureP sig;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_BlindedCoinHashP bch;
+ struct TALER_Amount withdraw_fee;
+ struct GNUNET_JSON_Specification withdraw_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &sig),
+ TALER_JSON_spec_amount_any ("withdraw_fee",
+ &withdraw_fee),
+ GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+ &h_denom_pub),
+ GNUNET_JSON_spec_fixed_auto ("h_coin_envelope",
+ &bch),
+ 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;
+ }
+
+ /* Check that the signature is a valid withdraw request */
+ if (GNUNET_OK !=
+ TALER_wallet_withdraw_verify (&h_denom_pub,
+ &rh->amount,
+ &bch,
+ uc->reserve_pub,
+ &sig))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ /* check that withdraw fee matches expectations! */
+ {
+ const struct TALER_EXCHANGE_Keys *key_state;
+ const struct TALER_EXCHANGE_DenomPublicKey *dki;
+
+ key_state = uc->keys;
+ dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
+ &h_denom_pub);
+ if ( (GNUNET_YES !=
+ TALER_amount_cmp_currency (&withdraw_fee,
+ &dki->fees.withdraw)) ||
+ (0 !=
+ TALER_amount_cmp (&withdraw_fee,
+ &dki->fees.withdraw)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ rh->details.withdraw.fee = withdraw_fee;
+ }
+ 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 (&sig,
+ sizeof (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;
+ }
+ 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 GNUNET_JSON_Specification closing_spec[] = {
+ TALER_JSON_spec_payto_uri (
+ "receiver_account_details",
+ &rh->details.close_details.receiver_account_details),
+ 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,
+ rh->details.close_details.receiver_account_details,
+ &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;
+ }
+ 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++)
+ {
+ switch (rhistory[i].type)
+ {
+ case TALER_EXCHANGE_RTT_CREDIT:
+ GNUNET_free (rhistory[i].details.in_details.sender_url);
+ break;
+ case TALER_EXCHANGE_RTT_WITHDRAWAL:
+ break;
+ case TALER_EXCHANGE_RTT_AGEWITHDRAWAL:
+ 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:
+ 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.
*
@@ -99,19 +878,18 @@ static enum GNUNET_GenericReturnValue
handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rsh,
const json_t *j)
{
- json_t *history;
+ const json_t *history;
unsigned int len;
struct TALER_EXCHANGE_ReserveHistory rs = {
.hr.reply = j,
.hr.http_status = MHD_HTTP_OK,
- .ts = rsh->ts,
- .reserve_sig = &rsh->reserve_sig
+ .details.ok.etag = rsh->etag
};
struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount_any ("balance",
&rs.details.ok.balance),
- GNUNET_JSON_spec_json ("history",
- &history),
+ GNUNET_JSON_spec_array_const ("history",
+ &history),
GNUNET_JSON_spec_end ()
};
@@ -131,18 +909,18 @@ handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rsh,
rhistory = GNUNET_new_array (len,
struct TALER_EXCHANGE_ReserveHistoryEntry);
if (GNUNET_OK !=
- TALER_EXCHANGE_parse_reserve_history (rsh->exchange,
- history,
- &rsh->reserve_pub,
- rs.details.ok.balance.currency,
- &rs.details.ok.total_in,
- &rs.details.ok.total_out,
- len,
- rhistory))
+ 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);
- TALER_EXCHANGE_free_reserve_history (rhistory,
- len);
+ free_reserve_history (len,
+ rhistory);
GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
@@ -154,10 +932,9 @@ handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rsh,
&rs);
rsh->cb = NULL;
}
- TALER_EXCHANGE_free_reserve_history (rhistory,
- len);
+ free_reserve_history (len,
+ rhistory);
}
- GNUNET_JSON_parse_free (spec);
return GNUNET_OK;
}
@@ -193,7 +970,6 @@ handle_reserves_history_finished (void *cls,
handle_reserves_history_ok (rsh,
j))
{
- GNUNET_break_op (0);
rs.hr.http_status = 0;
rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
}
@@ -218,11 +994,6 @@ handle_reserves_history_finished (void *cls,
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 history */
- 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 */
@@ -252,29 +1023,22 @@ handle_reserves_history_finished (void *cls,
struct TALER_EXCHANGE_ReservesHistoryHandle *
TALER_EXCHANGE_reserves_history (
- struct TALER_EXCHANGE_Handle *exchange,
+ 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;
- struct GNUNET_CURL_Context *ctx;
CURL *eh;
- char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
- const struct TALER_EXCHANGE_Keys *keys;
- const struct TALER_EXCHANGE_GlobalFee *gf;
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 64];
+ struct curl_slist *job_headers;
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle);
- rsh->exchange = exchange;
rsh->cb = cb;
rsh->cb_cls = cb_cls;
- rsh->ts = GNUNET_TIME_timestamp_get ();
GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
&rsh->reserve_pub.eddsa_pub);
{
@@ -287,13 +1051,21 @@ TALER_EXCHANGE_reserves_history (
pub_str,
sizeof (pub_str));
*end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "/reserves/%s/history",
- pub_str);
+ 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 = TEAH_path_to_url (exchange,
- arg_str);
+ rsh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == rsh->url)
{
GNUNET_free (rsh);
@@ -307,56 +1079,49 @@ TALER_EXCHANGE_reserves_history (
GNUNET_free (rsh);
return NULL;
}
- keys = TALER_EXCHANGE_get_keys (exchange);
- if (NULL == keys)
- {
- GNUNET_break (0);
- curl_easy_cleanup (eh);
- GNUNET_free (rsh->url);
- GNUNET_free (rsh);
- return NULL;
- }
- gf = TALER_EXCHANGE_get_global_fee (keys,
- rsh->ts);
- if (NULL == gf)
- {
- GNUNET_break_op (0);
- curl_easy_cleanup (eh);
- GNUNET_free (rsh->url);
- GNUNET_free (rsh);
- return NULL;
- }
- TALER_wallet_reserve_history_sign (rsh->ts,
- &gf->fees.history,
- reserve_priv,
- &rsh->reserve_sig);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERFUNCTION,
+ &handle_header));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HEADERDATA,
+ rsh));
{
- json_t *history_obj = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_timestamp ("request_timestamp",
- rsh->ts),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &rsh->reserve_sig));
+ struct TALER_ReserveSignatureP reserve_sig;
+ char *sig_hdr;
+ char *hdr;
- if (GNUNET_OK !=
- TALER_curl_easy_post (&rsh->post_ctx,
- eh,
- history_obj))
+ 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);
- json_decref (history_obj);
- GNUNET_free (rsh->url);
- GNUNET_free (rsh);
return NULL;
}
- json_decref (history_obj);
}
- ctx = TEAH_handle_to_context (exchange);
+
+ rsh->keys = TALER_EXCHANGE_keys_incref (keys);
rsh->job = GNUNET_CURL_job_add2 (ctx,
eh,
- rsh->post_ctx.headers,
+ job_headers,
&handle_reserves_history_finished,
rsh);
+ curl_slist_free_all (job_headers);
return rsh;
}
@@ -372,6 +1137,7 @@ TALER_EXCHANGE_reserves_history_cancel (
}
TALER_curl_easy_post_finished (&rsh->post_ctx);
GNUNET_free (rsh->url);
+ TALER_EXCHANGE_keys_decref (rsh->keys);
GNUNET_free (rsh);
}
diff --git a/src/lib/exchange_api_reserves_open.c b/src/lib/exchange_api_reserves_open.c
new file mode 100644
index 000000000..36e435685
--- /dev/null
+++ b/src/lib/exchange_api_reserves_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_reserves_open.c
+ * @brief Implementation of the POST /reserves/$RESERVE_PUB/open requests
+ * @author Christian Grothoff
+ */
+#include "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_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "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_AgeCommitmentHash ahac;
+ struct TALER_AgeCommitmentHash *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_stefan.c b/src/lib/exchange_api_stefan.c
new file mode 100644
index 000000000..226bca82f
--- /dev/null
+++ b/src/lib/exchange_api_stefan.c
@@ -0,0 +1,328 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_stefan.c
+ * @brief calculations on the STEFAN curve
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+#include <math.h>
+
+
+/**
+ * Determine smallest denomination in @a keys.
+ *
+ * @param keys exchange response to evaluate
+ * @return NULL on error (no denominations)
+ */
+static const struct TALER_Amount *
+get_unit (const struct TALER_EXCHANGE_Keys *keys)
+{
+ const struct TALER_Amount *min = NULL;
+
+ for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+ {
+ const struct TALER_EXCHANGE_DenomPublicKey *dk
+ = &keys->denom_keys[i];
+
+ if ( (NULL == min) ||
+ (1 == TALER_amount_cmp (min,
+ /* > */
+ &dk->value)) )
+ min = &dk->value;
+ }
+ GNUNET_break (NULL != min);
+ return min;
+}
+
+
+/**
+ * Convert amount to double for STEFAN curve evaluation.
+ *
+ * @param a input amount
+ * @return (rounded) amount as a double
+ */
+static double
+amount_to_double (const struct TALER_Amount *a)
+{
+ double d = (double) a->value;
+
+ d += a->fraction / ((double) TALER_AMOUNT_FRAC_BASE);
+ return d;
+}
+
+
+/**
+ * Convert double to amount for STEFAN curve evaluation.
+ *
+ * @param dv input amount
+ * @param currency deisred currency
+ * @param[out] rval (rounded) amount as a double
+ */
+static void
+double_to_amount (double dv,
+ const char *currency,
+ struct TALER_Amount *rval)
+{
+ double rem;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (currency,
+ rval));
+ rval->value = floorl (dv);
+ rem = dv - ((double) rval->value);
+ if (rem < 0.0)
+ rem = 0.0;
+ rem *= TALER_AMOUNT_FRAC_BASE;
+ rval->fraction = floorl (rem);
+ if (rval->fraction >= TALER_AMOUNT_FRAC_BASE)
+ {
+ /* Strange, multiplication overflowed our range,
+ round up value instead */
+ rval->fraction = 0;
+ rval->value += 1;
+ }
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_keys_stefan_b2n (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_Amount *brut,
+ struct TALER_Amount *net)
+{
+ const struct TALER_Amount *min;
+ double log_d = amount_to_double (&keys->stefan_log);
+ double lin_d = keys->stefan_lin;
+ double abs_d = amount_to_double (&keys->stefan_abs);
+ double bru_d = amount_to_double (brut);
+ double min_d;
+ double fee_d;
+ double net_d;
+
+ if (TALER_amount_is_zero (brut))
+ {
+ *net = *brut;
+ return GNUNET_NO;
+ }
+ min = get_unit (keys);
+ if (NULL == min)
+ return GNUNET_SYSERR;
+ if (1.0f <= keys->stefan_lin)
+ {
+ /* This cannot work, linear STEFAN fee estimate always
+ exceed any gross amount. */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ min_d = amount_to_double (min);
+ fee_d = abs_d
+ + log_d * log2 (bru_d / min_d)
+ + lin_d * bru_d;
+ if (fee_d > bru_d)
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (brut->currency,
+ net));
+ return GNUNET_NO;
+ }
+ net_d = bru_d - fee_d;
+ double_to_amount (net_d,
+ brut->currency,
+ net);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Our function
+ * f(x) := ne + ab + lo * log2(x/mi) + li * x - x
+ * for #newton().
+ */
+static double
+eval_f (double mi,
+ double ab,
+ double lo,
+ double li,
+ double ne,
+ double x)
+{
+ return ne + ab + lo * log2 (x / mi) + li * x - x;
+}
+
+
+/**
+ * Our function
+ * f'(x) := lo / log(2) / x + li - 1
+ * for #newton().
+ */
+static double
+eval_fp (double mi,
+ double lo,
+ double li,
+ double ne,
+ double x)
+{
+ return lo / log (2) / x + li - 1;
+}
+
+
+/**
+ * Use Newton's method to find x where f(x)=0.
+ *
+ * @return x where "eval_f(x)==0".
+ */
+static double
+newton (double mi,
+ double ab,
+ double lo,
+ double li,
+ double ne)
+{
+ const double eps = 0.00000001; /* max error allowed */
+ double min_ab = ne + ab; /* result cannot be smaller than this! */
+ /* compute lower bounds by various heuristics */
+ double min_ab_li = min_ab + min_ab * li;
+ double min_ab_li_lo = min_ab_li + log2 (min_ab_li / mi) * lo;
+ double min_ab_lo = min_ab + log2 (min_ab / mi) * lo;
+ double min_ab_lo_li = min_ab_lo + min_ab_lo * li;
+ /* take global lower bound */
+ double x_min = GNUNET_MAX (min_ab_lo_li,
+ min_ab_li_lo);
+ double x = x_min; /* use lower bound as starting point */
+
+ /* Objective: invert
+ ne := br - ab - lo * log2 (br/mi) - li * br
+ to find 'br'.
+ Method: use Newton's method to find root of:
+ f(x) := ne + ab + lo * log2 (x/mi) + li * x - x
+ using also
+ f'(x) := lo / log(2) / x + li - 1
+ */
+ /* Loop to abort in case of divergence;
+ 100 is already very high, 2-4 is normal! */
+ for (unsigned int i = 0; i<100; i++)
+ {
+ double fx = eval_f (mi, ab, lo, li, ne, x);
+ double fxp = eval_fp (mi, lo, li, ne, x);
+ double x_new = x - fx / fxp;
+
+ if (fabs (x - x_new) <= eps)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Needed %u rounds from %f to result BRUT %f => NET: %f\n",
+ i,
+ x_min,
+ x_new,
+ x_new - ab - li * x_new - lo * log2 (x / mi));
+ return x_new;
+ }
+ if (x_new < x_min)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Divergence, obtained very bad estimate %f after %u rounds!\n",
+ x_new,
+ i);
+ return x_min;
+ }
+ x = x_new;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Slow convergence, returning bad estimate %f!\n",
+ x);
+ return x;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_keys_stefan_n2b (
+ const struct TALER_EXCHANGE_Keys *keys,
+ const struct TALER_Amount *net,
+ struct TALER_Amount *brut)
+{
+ const struct TALER_Amount *min;
+ double lin_d = keys->stefan_lin;
+ double log_d = amount_to_double (&keys->stefan_log);
+ double abs_d = amount_to_double (&keys->stefan_abs);
+ double net_d = amount_to_double (net);
+ double min_d;
+ double brut_d;
+
+ if (TALER_amount_is_zero (net))
+ {
+ *brut = *net;
+ return GNUNET_NO;
+ }
+ min = get_unit (keys);
+ if (NULL == min)
+ return GNUNET_SYSERR;
+ if (1.0f <= keys->stefan_lin)
+ {
+ /* This cannot work, linear STEFAN fee estimate always
+ exceed any gross amount. */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ min_d = amount_to_double (min);
+ brut_d = newton (min_d,
+ abs_d,
+ log_d,
+ lin_d,
+ net_d);
+ double_to_amount (brut_d,
+ net->currency,
+ brut);
+ return GNUNET_OK;
+}
+
+
+void
+TALER_EXCHANGE_keys_stefan_round (
+ const struct TALER_EXCHANGE_Keys *keys,
+ struct TALER_Amount *val)
+{
+ const struct TALER_Amount *min;
+ uint32_t mod;
+ uint32_t frac;
+ uint32_t lim;
+
+ if (0 == val->fraction)
+ {
+ /* rounding of non-fractions not supported */
+ return;
+ }
+ min = get_unit (keys);
+ if (NULL == min)
+ return;
+ if (0 == min->fraction)
+ {
+ frac = TALER_AMOUNT_FRAC_BASE;
+ }
+ else
+ {
+ frac = min->fraction;
+ }
+ lim = frac / 2;
+ mod = val->fraction % frac;
+ if (mod < lim)
+ val->fraction -= mod; /* round down */
+ else
+ val->fraction += frac - mod; /* round up */
+}
diff --git a/src/lib/exchange_api_transfers_get.c b/src/lib/exchange_api_transfers_get.c
index 06465618c..c558fb42e 100644
--- a/src/lib/exchange_api_transfers_get.c
+++ b/src/lib/exchange_api_transfers_get.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
+ 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
@@ -38,9 +38,9 @@ struct TALER_EXCHANGE_TransfersGetHandle
{
/**
- * The connection to exchange this request handle will use
+ * The keys of the exchange this request handle will use
*/
- struct TALER_EXCHANGE_Handle *exchange;
+ struct TALER_EXCHANGE_Keys *keys;
/**
* The url for this request.
@@ -84,25 +84,34 @@ check_transfers_get_response_ok (
struct TALER_EXCHANGE_TransfersGetHandle *wdh,
const json_t *json)
{
- json_t *details_j;
- struct TALER_EXCHANGE_TransferData td;
+ 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_json ("deposits", &details_j),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig", &td.exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub", &td.exchange_pub),
+ 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 ()
};
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = json,
- .http_status = MHD_HTTP_OK
- };
if (GNUNET_OK !=
GNUNET_JSON_parse (json,
@@ -113,32 +122,30 @@ check_transfers_get_response_ok (
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
- TALER_amount_set_zero (td.total_amount.currency,
+ TALER_amount_set_zero (td->total_amount.currency,
&total_expected))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
TALER_EXCHANGE_test_signing_key (
- TALER_EXCHANGE_get_keys (wdh->exchange),
- &td.exchange_pub))
+ wdh->keys,
+ &td->exchange_pub))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
return GNUNET_SYSERR;
}
- td.details_length = json_array_size (details_j);
+ td->details_length = json_array_size (details_j);
{
struct GNUNET_HashContext *hash_context;
struct TALER_TrackTransferDetails *details;
- details = GNUNET_new_array (td.details_length,
+ details = GNUNET_new_array (td->details_length,
struct TALER_TrackTransferDetails);
- td.details = details;
+ td->details = details;
hash_context = GNUNET_CRYPTO_hash_context_start ();
- for (unsigned int i = 0; i<td.details_length; i++)
+ 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);
@@ -146,21 +153,27 @@ check_transfers_get_response_ok (
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_any ("deposit_value", &detail->coin_value),
- TALER_JSON_spec_amount_any ("deposit_fee", &detail->coin_fee),
+ 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)) ||
- (GNUNET_OK !=
- TALER_amount_cmp_currency (&total_expected,
- &detail->coin_value)) ||
- (GNUNET_OK !=
- TALER_amount_cmp_currency (&total_expected,
- &detail->coin_fee)) ||
(0 >
TALER_amount_add (&total_expected,
&total_expected,
@@ -172,7 +185,6 @@ check_transfers_get_response_ok (
{
GNUNET_break_op (0);
GNUNET_CRYPTO_hash_context_abort (hash_context);
- GNUNET_JSON_parse_free (spec);
GNUNET_free (details);
return GNUNET_SYSERR;
}
@@ -180,7 +192,7 @@ check_transfers_get_response_ok (
TALER_exchange_online_wire_deposit_append (
hash_context,
&detail->h_contract_terms,
- td.execution_time,
+ td->execution_time,
&detail->coin_pub,
&detail->coin_value,
&detail->coin_fee);
@@ -193,16 +205,15 @@ check_transfers_get_response_ok (
&h_details);
if (GNUNET_OK !=
TALER_exchange_online_wire_deposit_verify (
- &td.total_amount,
- &td.wire_fee,
+ &td->total_amount,
+ &td->wire_fee,
&merchant_pub,
- &td.h_payto,
+ &td->h_payto,
&h_details,
- &td.exchange_pub,
- &td.exchange_sig))
+ &td->exchange_pub,
+ &td->exchange_sig))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
GNUNET_free (details);
return GNUNET_SYSERR;
}
@@ -211,29 +222,24 @@ check_transfers_get_response_ok (
if (0 >
TALER_amount_subtract (&total_expected,
&total_expected,
- &td.wire_fee))
+ &td->wire_fee))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
GNUNET_free (details);
return GNUNET_SYSERR;
}
if (0 !=
TALER_amount_cmp (&total_expected,
- &td.total_amount))
+ &td->total_amount))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
GNUNET_free (details);
return GNUNET_SYSERR;
}
wdh->cb (wdh->cb_cls,
- &hr,
- &td);
+ &tgr);
GNUNET_free (details);
}
- GNUNET_JSON_parse_free (spec);
- TALER_EXCHANGE_transfers_get_cancel (wdh);
return GNUNET_OK;
}
@@ -253,90 +259,85 @@ handle_transfers_get_finished (void *cls,
{
struct TALER_EXCHANGE_TransfersGetHandle *wdh = cls;
const json_t *j = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
+ struct TALER_EXCHANGE_TransfersGetResponse tgr = {
+ .hr.reply = j,
+ .hr.http_status = (unsigned int) response_code
};
wdh->job = NULL;
switch (response_code)
{
case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ 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);
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- hr.http_status = 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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);
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
+ 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) hr.ec);
+ (int) tgr.hr.ec);
break;
}
wdh->cb (wdh->cb_cls,
- &hr,
- NULL);
+ &tgr);
TALER_EXCHANGE_transfers_get_cancel (wdh);
}
struct TALER_EXCHANGE_TransfersGetHandle *
TALER_EXCHANGE_transfers_get (
- struct TALER_EXCHANGE_Handle *exchange,
+ 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;
- struct GNUNET_CURL_Context *ctx;
CURL *eh;
char arg_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2 + 32];
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
-
wdh = GNUNET_new (struct TALER_EXCHANGE_TransfersGetHandle);
- wdh->exchange = exchange;
wdh->cb = cb;
wdh->cb_cls = cb_cls;
@@ -352,11 +353,12 @@ TALER_EXCHANGE_transfers_get (
*end = '\0';
GNUNET_snprintf (arg_str,
sizeof (arg_str),
- "/transfers/%s",
+ "transfers/%s",
wtid_str);
}
- wdh->url = TEAH_path_to_url (wdh->exchange,
- arg_str);
+ wdh->url = TALER_url_join (url,
+ arg_str,
+ NULL);
if (NULL == wdh->url)
{
GNUNET_free (wdh);
@@ -370,7 +372,7 @@ TALER_EXCHANGE_transfers_get (
GNUNET_free (wdh);
return NULL;
}
- ctx = TEAH_handle_to_context (exchange);
+ wdh->keys = TALER_EXCHANGE_keys_incref (keys);
wdh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
eh,
&handle_transfers_get_finished,
@@ -395,6 +397,7 @@ TALER_EXCHANGE_transfers_get_cancel (
wdh->job = NULL;
}
GNUNET_free (wdh->url);
+ TALER_EXCHANGE_keys_decref (wdh->keys);
GNUNET_free (wdh);
}
diff --git a/src/lib/exchange_api_wire.c b/src/lib/exchange_api_wire.c
deleted file mode 100644
index 3079064c8..000000000
--- a/src/lib/exchange_api_wire.c
+++ /dev/null
@@ -1,488 +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_wire.c
- * @brief Implementation of the /wire request of the exchange's HTTP API
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_exchange_service.h"
-#include "taler_json_lib.h"
-#include "taler_signatures.h"
-#include "exchange_api_handle.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A Wire Handle
- */
-struct TALER_EXCHANGE_WireHandle
-{
-
- /**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_WireCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
-};
-
-
-/**
- * List of wire fees by method.
- */
-struct FeeMap
-{
- /**
- * Next entry in list.
- */
- struct FeeMap *next;
-
- /**
- * Wire method this fee structure is for.
- */
- char *method;
-
- /**
- * Array of wire fees, also linked list, but allocated
- * only once.
- */
- struct TALER_EXCHANGE_WireAggregateFees *fee_list;
-};
-
-
-/**
- * Frees @a fm.
- *
- * @param fm memory to release
- */
-static void
-free_fees (struct FeeMap *fm)
-{
- while (NULL != fm)
- {
- struct FeeMap *fe = fm->next;
-
- GNUNET_free (fm->fee_list);
- GNUNET_free (fm->method);
- GNUNET_free (fm);
- fm = fe;
- }
-}
-
-
-/**
- * Parse wire @a fees and return map.
- *
- * @param fees json AggregateTransferFee to parse
- * @return NULL on error
- */
-static struct FeeMap *
-parse_fees (json_t *fees)
-{
- struct FeeMap *fm = NULL;
- const char *key;
- json_t *fee_array;
-
- json_object_foreach (fees, key, fee_array) {
- struct FeeMap *fe = GNUNET_new (struct FeeMap);
- unsigned int len;
- unsigned int idx;
- json_t *fee;
-
- if (0 == (len = json_array_size (fee_array)))
- {
- GNUNET_free (fe);
- continue; /* skip */
- }
- fe->method = GNUNET_strdup (key);
- fe->next = fm;
- fe->fee_list = GNUNET_new_array (len,
- struct TALER_EXCHANGE_WireAggregateFees);
- fm = fe;
- json_array_foreach (fee_array, idx, fee)
- {
- struct TALER_EXCHANGE_WireAggregateFees *wa = &fe->fee_list[idx];
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("sig",
- &wa->master_sig),
- TALER_JSON_spec_amount_any ("wire_fee",
- &wa->fees.wire),
- TALER_JSON_spec_amount_any ("wad_fee",
- &wa->fees.wad),
- TALER_JSON_spec_amount_any ("closing_fee",
- &wa->fees.closing),
- GNUNET_JSON_spec_timestamp ("start_date",
- &wa->start_date),
- GNUNET_JSON_spec_timestamp ("end_date",
- &wa->end_date),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (fee,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- free_fees (fm);
- return NULL;
- }
- if (idx + 1 < len)
- wa->next = &fe->fee_list[idx + 1];
- else
- wa->next = NULL;
- }
- }
- return fm;
-}
-
-
-/**
- * Find fee by @a method.
- *
- * @param fm map to look in
- * @param method key to look for
- * @return NULL if fee is not specified in @a fm
- */
-static const struct TALER_EXCHANGE_WireAggregateFees *
-lookup_fee (const struct FeeMap *fm,
- const char *method)
-{
- for (; NULL != fm; fm = fm->next)
- if (0 == strcasecmp (fm->method,
- method))
- return fm->fee_list;
- return NULL;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /wire request.
- *
- * @param cls the `struct TALER_EXCHANGE_WireHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_wire_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_WireHandle *wh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
- };
-
- TALER_LOG_DEBUG ("Checking raw /wire response\n");
- wh->job = NULL;
- switch (response_code)
- {
- case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- wh->exchange->wire_error_count++;
- break;
- case MHD_HTTP_OK:
- {
- json_t *accounts;
- json_t *fees;
- unsigned int num_accounts;
- struct FeeMap *fm;
- const struct TALER_EXCHANGE_Keys *key_state;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("accounts",
- &accounts),
- GNUNET_JSON_spec_json ("fees",
- &fees),
- GNUNET_JSON_spec_end ()
- };
-
- wh->exchange->wire_error_count = 0;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (0 == (num_accounts = json_array_size (accounts)))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (NULL == (fm = parse_fees (fees)))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
-
- key_state = TALER_EXCHANGE_get_keys (wh->exchange);
- /* parse accounts */
- {
- struct TALER_EXCHANGE_WireAccount was[num_accounts];
-
- for (unsigned int i = 0; i<num_accounts; i++)
- {
- struct TALER_EXCHANGE_WireAccount *wa = &was[i];
- json_t *account;
- struct GNUNET_JSON_Specification spec_account[] = {
- GNUNET_JSON_spec_string ("payto_uri",
- &wa->payto_uri),
- GNUNET_JSON_spec_fixed_auto ("master_sig",
- &wa->master_sig),
- GNUNET_JSON_spec_end ()
- };
- char *method;
-
- account = json_array_get (accounts,
- i);
- if (GNUNET_OK !=
- TALER_JSON_exchange_wire_signature_check (account,
- &key_state->master_pub))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_EXCHANGE_WIRE_SIGNATURE_INVALID;
- break;
- }
- if (GNUNET_OK !=
- GNUNET_JSON_parse (account,
- spec_account,
- NULL, NULL))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (NULL == (method = TALER_payto_get_method (wa->payto_uri)))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- if (NULL == (wa->fees = lookup_fee (fm,
- method)))
- {
- /* bogus reply */
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- GNUNET_free (method);
- break;
- }
- GNUNET_free (method);
- } /* end 'for all accounts */
- if ( (0 != response_code) &&
- (NULL != wh->cb) )
- {
- wh->cb (wh->cb_cls,
- &hr,
- num_accounts,
- was);
- wh->cb = NULL;
- }
- } /* end of 'parse accounts */
- free_fees (fm);
- GNUNET_JSON_parse_free (spec);
- } /* end of MHD_HTTP_OK */
- 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
- break;
- default:
- /* unexpected response code */
- if (MHD_HTTP_GATEWAY_TIMEOUT == response_code)
- wh->exchange->wire_error_count++;
- GNUNET_break_op (0);
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange wire\n",
- (unsigned int) response_code,
- (int) hr.ec);
- break;
- }
- if (NULL != wh->cb)
- wh->cb (wh->cb_cls,
- &hr,
- 0,
- NULL);
- TALER_EXCHANGE_wire_cancel (wh);
-}
-
-
-/**
- * Compute the network timeout for the next request to /wire.
- *
- * @param exchange the exchange handle
- * @returns the timeout in seconds (for use by CURL)
- */
-static long
-get_wire_timeout_seconds (struct TALER_EXCHANGE_Handle *exchange)
-{
- return GNUNET_MIN (60,
- 5 + (1L << exchange->wire_error_count));
-}
-
-
-/**
- * Obtain information about a exchange's wire instructions.
- * A exchange may provide wire instructions for creating
- * a reserve. The wire instructions also indicate
- * which wire formats merchants may use with the exchange.
- * This API is typically used by a wallet for wiring
- * funds, and possibly by a merchant to determine
- * supported wire formats.
- *
- * Note that while we return the (main) response verbatim to the
- * caller for further processing, we do already verify that the
- * response is well-formed (i.e. that signatures included in the
- * response are all valid). If the exchange's reply is not well-formed,
- * we return an HTTP status code of zero to @a cb.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param wire_cb the callback to call when a reply for this request is available
- * @param wire_cb_cls closure for the above callback
- * @return a handle for this request
- */
-struct TALER_EXCHANGE_WireHandle *
-TALER_EXCHANGE_wire (struct TALER_EXCHANGE_Handle *exchange,
- TALER_EXCHANGE_WireCallback wire_cb,
- void *wire_cb_cls)
-{
- struct TALER_EXCHANGE_WireHandle *wh;
- struct GNUNET_CURL_Context *ctx;
- CURL *eh;
-
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
- wh = GNUNET_new (struct TALER_EXCHANGE_WireHandle);
- wh->exchange = exchange;
- wh->cb = wire_cb;
- wh->cb_cls = wire_cb_cls;
- wh->url = TEAH_path_to_url (exchange,
- "/wire");
- if (NULL == wh->url)
- {
- GNUNET_free (wh);
- return NULL;
- }
- eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
- if (NULL == eh)
- {
- GNUNET_break (0);
- GNUNET_free (wh->url);
- GNUNET_free (wh);
- return NULL;
- }
- GNUNET_break (CURLE_OK ==
- curl_easy_setopt (eh,
- CURLOPT_TIMEOUT,
- get_wire_timeout_seconds (wh->exchange)));
- ctx = TEAH_handle_to_context (exchange);
- wh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
- eh,
- &handle_wire_finished,
- wh);
- return wh;
-}
-
-
-/**
- * Cancel a wire information request. This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param wh the wire information request handle
- */
-void
-TALER_EXCHANGE_wire_cancel (struct TALER_EXCHANGE_WireHandle *wh)
-{
- if (NULL != wh->job)
- {
- GNUNET_CURL_job_cancel (wh->job);
- wh->job = NULL;
- }
- GNUNET_free (wh->url);
- GNUNET_free (wh);
-}
-
-
-/* end of exchange_api_wire.c */
diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c
deleted file mode 100644
index b8ab1df44..000000000
--- a/src/lib/exchange_api_withdraw.c
+++ /dev/null
@@ -1,342 +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_withdraw.c
- * @brief Implementation of /reserves/$RESERVE_PUB/withdraw requests with blinding/unblinding
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_exchange_service.h"
-#include "taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A Withdraw Handle
- */
-struct TALER_EXCHANGE_WithdrawHandle
-{
-
- /**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
- * Handle for the actual (internal) withdraw operation.
- */
- struct TALER_EXCHANGE_Withdraw2Handle *wh2;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_WithdrawCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Reserve private key.
- */
- const struct TALER_ReservePrivateKeyP *reserve_priv;
-
- /**
- * Seed of the planchet.
- */
- struct TALER_PlanchetMasterSecretP ps;
-
- /**
- * blinding secret
- */
- union TALER_DenominationBlindingKeyP bks;
-
- /**
- * Private key of the coin we are withdrawing.
- */
- struct TALER_CoinSpendPrivateKeyP priv;
-
- /**
- * Details of the planchet.
- */
- struct TALER_PlanchetDetail pd;
-
- /**
- * Values of the @cipher selected
- */
- struct TALER_ExchangeWithdrawValues alg_values;
-
- /**
- * Hash of the age commitment for this coin, if applicable. Maybe NULL
- */
- const struct TALER_AgeCommitmentHash *ach;
-
- /**
- * Denomination key we are withdrawing.
- */
- struct TALER_EXCHANGE_DenomPublicKey pk;
-
- /**
- * Hash of the public key of the coin we are signing.
- */
- struct TALER_CoinPubHashP c_hash;
-
- /**
- * Handler for the CS R request (only used for TALER_DENOMINATION_CS denominations)
- */
- struct TALER_EXCHANGE_CsRWithdrawHandle *csrh;
-
-};
-
-
-/**
- * Function called when we're done processing the
- * HTTP /reserves/$RESERVE_PUB/withdraw request.
- *
- * @param cls the `struct TALER_EXCHANGE_WithdrawHandle`
- * @param hr HTTP response data
- * @param blind_sig blind signature over the coin, NULL on error
- */
-static void
-handle_reserve_withdraw_finished (
- void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct TALER_BlindedDenominationSignature *blind_sig)
-{
- struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
- struct TALER_EXCHANGE_WithdrawResponse wr = {
- .hr = *hr
- };
-
- wh->wh2 = NULL;
- switch (hr->http_status)
- {
- case MHD_HTTP_OK:
- {
- struct TALER_FreshCoin fc;
-
- if (GNUNET_OK !=
- TALER_planchet_to_coin (&wh->pk.key,
- blind_sig,
- &wh->bks,
- &wh->priv,
- wh->ach,
- &wh->c_hash,
- &wh->alg_values,
- &fc))
- {
- wr.hr.http_status = 0;
- wr.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE;
- break;
- }
- wr.details.success.coin_priv = wh->priv;
- wr.details.success.bks = wh->bks;
- wr.details.success.sig = fc.sig;
- wr.details.success.exchange_vals = wh->alg_values;
- break;
- }
- case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint64 ("payment_target_uuid",
- &wr.details.accepted.payment_target_uuid),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (hr->reply,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- wr.hr.http_status = 0;
- wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- }
- break;
- default:
- break;
- }
- wh->cb (wh->cb_cls,
- &wr);
- if (MHD_HTTP_OK == hr->http_status)
- TALER_denom_sig_free (&wr.details.success.sig);
- TALER_EXCHANGE_withdraw_cancel (wh);
-}
-
-
-/**
- * Function called when stage 1 of CS withdraw is finished (request r_pub's)
- *
- * @param cls the `struct TALER_EXCHANGE_WithdrawHandle`
- * @param csrr replies from the /csr-withdraw request
- */
-static void
-withdraw_cs_stage_two_callback (
- void *cls,
- const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr)
-{
- struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
- struct TALER_EXCHANGE_WithdrawResponse wr = {
- .hr = csrr->hr
- };
-
- wh->csrh = NULL;
- GNUNET_assert (TALER_DENOMINATION_CS == wh->pk.key.cipher);
- switch (csrr->hr.http_status)
- {
- case MHD_HTTP_OK:
- wh->alg_values = csrr->details.success.alg_values;
- TALER_planchet_setup_coin_priv (&wh->ps,
- &wh->alg_values,
- &wh->priv);
- TALER_planchet_blinding_secret_create (&wh->ps,
- &wh->alg_values,
- &wh->bks);
- /* This initializes the 2nd half of the
- wh->pd.blinded_planchet! */
- if (GNUNET_OK !=
- TALER_planchet_prepare (&wh->pk.key,
- &wh->alg_values,
- &wh->bks,
- &wh->priv,
- wh->ach,
- &wh->c_hash,
- &wh->pd))
- {
- GNUNET_break (0);
- GNUNET_free (wh);
- }
- wh->wh2 = TALER_EXCHANGE_withdraw2 (wh->exchange,
- &wh->pd,
- wh->reserve_priv,
- &handle_reserve_withdraw_finished,
- wh);
- return;
- default:
- break;
- }
- wh->cb (wh->cb_cls,
- &wr);
- TALER_EXCHANGE_withdraw_cancel (wh);
-}
-
-
-struct TALER_EXCHANGE_WithdrawHandle *
-TALER_EXCHANGE_withdraw (
- struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- const struct TALER_EXCHANGE_WithdrawCoinInput *wci,
- TALER_EXCHANGE_WithdrawCallback res_cb,
- void *res_cb_cls)
-{
- struct TALER_EXCHANGE_WithdrawHandle *wh;
-
- wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle);
- wh->exchange = exchange;
- wh->cb = res_cb;
- wh->cb_cls = res_cb_cls;
- wh->reserve_priv = reserve_priv;
- wh->ps = *wci->ps;
- wh->ach = wci->ach;
- wh->pk = *wci->pk;
- TALER_denom_pub_deep_copy (&wh->pk.key,
- &wci->pk->key);
-
- switch (wci->pk->key.cipher)
- {
- case TALER_DENOMINATION_RSA:
- {
- wh->alg_values.cipher = TALER_DENOMINATION_RSA;
- TALER_planchet_setup_coin_priv (&wh->ps,
- &wh->alg_values,
- &wh->priv);
- TALER_planchet_blinding_secret_create (&wh->ps,
- &wh->alg_values,
- &wh->bks);
- if (GNUNET_OK !=
- TALER_planchet_prepare (&wh->pk.key,
- &wh->alg_values,
- &wh->bks,
- &wh->priv,
- wh->ach,
- &wh->c_hash,
- &wh->pd))
- {
- GNUNET_break (0);
- GNUNET_free (wh);
- return NULL;
- }
- wh->wh2 = TALER_EXCHANGE_withdraw2 (exchange,
- &wh->pd,
- wh->reserve_priv,
- &handle_reserve_withdraw_finished,
- wh);
- break;
- }
- case TALER_DENOMINATION_CS:
- {
- TALER_cs_withdraw_nonce_derive (
- &wh->ps,
- &wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce);
- /* Note that we only initialize the first half
- of the blinded_planchet here; the other part
- will be done after the /csr-withdraw request! */
- wh->pd.blinded_planchet.cipher = TALER_DENOMINATION_CS;
- wh->csrh = TALER_EXCHANGE_csr_withdraw (
- exchange,
- &wh->pk,
- &wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce,
- &withdraw_cs_stage_two_callback,
- wh);
- break;
- }
- default:
- GNUNET_break (0);
- GNUNET_free (wh);
- return NULL;
- }
- return wh;
-}
-
-
-void
-TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh)
-{
- TALER_blinded_planchet_free (&wh->pd.blinded_planchet);
- if (NULL != wh->csrh)
- {
- TALER_EXCHANGE_csr_withdraw_cancel (wh->csrh);
- wh->csrh = NULL;
- }
- if (NULL != wh->wh2)
- {
- TALER_EXCHANGE_withdraw2_cancel (wh->wh2);
- wh->wh2 = NULL;
- }
- TALER_denom_pub_free (&wh->pk.key);
- GNUNET_free (wh);
-}
diff --git a/src/lib/exchange_api_withdraw2.c b/src/lib/exchange_api_withdraw2.c
deleted file mode 100644
index a5371442f..000000000
--- a/src/lib/exchange_api_withdraw2.c
+++ /dev/null
@@ -1,504 +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_withdraw2.c
- * @brief Implementation of /reserves/$RESERVE_PUB/withdraw requests without blinding/unblinding
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_exchange_service.h"
-#include "taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/**
- * @brief A Withdraw Handle
- */
-struct TALER_EXCHANGE_Withdraw2Handle
-{
-
- /**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_Withdraw2Callback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Context for #TEH_curl_easy_post(). Keeps the data that must
- * persist for Curl to make the upload.
- */
- struct TALER_CURL_PostContext post_ctx;
-
- /**
- * Total amount requested (value plus withdraw fee).
- */
- struct TALER_Amount requested_amount;
-
- /**
- * Public key of the reserve we are withdrawing from.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
-};
-
-
-/**
- * We got a 200 OK response for the /reserves/$RESERVE_PUB/withdraw operation.
- * Extract the coin's signature and return it to the caller. The signature we
- * get from the exchange is for the blinded value. Thus, we first must
- * unblind it and then should verify its validity against our coin's hash.
- *
- * If everything checks out, we return the unblinded signature
- * to the application via the callback.
- *
- * @param wh operation handle
- * @param json reply from the exchange
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static enum GNUNET_GenericReturnValue
-reserve_withdraw_ok (struct TALER_EXCHANGE_Withdraw2Handle *wh,
- const json_t *json)
-{
- struct TALER_BlindedDenominationSignature blind_sig;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_blinded_denom_sig ("ev_sig",
- &blind_sig),
- GNUNET_JSON_spec_end ()
- };
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = json,
- .http_status = MHD_HTTP_OK
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- /* signature is valid, return it to the application */
- wh->cb (wh->cb_cls,
- &hr,
- &blind_sig);
- /* make sure callback isn't called again after return */
- wh->cb = NULL;
- GNUNET_JSON_parse_free (spec);
- return GNUNET_OK;
-}
-
-
-/**
- * We got a 409 CONFLICT response for the /reserves/$RESERVE_PUB/withdraw operation.
- * Check the signatures on the withdraw transactions in the provided
- * history and that the balances add up. We don't do anything directly
- * with the information, as the JSON will be returned to the application.
- * However, our job is ensuring that the exchange followed the protocol, and
- * this in particular means checking all of the signatures in the history.
- *
- * @param wh operation handle
- * @param json reply from the exchange
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static enum GNUNET_GenericReturnValue
-reserve_withdraw_payment_required (
- struct TALER_EXCHANGE_Withdraw2Handle *wh,
- const json_t *json)
-{
- struct TALER_Amount balance;
- struct TALER_Amount total_in_from_history;
- struct TALER_Amount total_out_from_history;
- json_t *history;
- size_t len;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount_any ("balance",
- &balance),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- history = json_object_get (json,
- "history");
- if (NULL == history)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- /* go over transaction history and compute
- total incoming and outgoing amounts */
- len = json_array_size (history);
- {
- struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
-
- /* Use heap allocation as "len" may be very big and thus this may
- not fit on the stack. Use "GNUNET_malloc_large" as a malicious
- exchange may theoretically try to crash us by giving a history
- that does not fit into our memory. */
- rhistory = GNUNET_malloc_large (
- sizeof (struct TALER_EXCHANGE_ReserveHistoryEntry)
- * len);
- if (NULL == rhistory)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
-
- if (GNUNET_OK !=
- TALER_EXCHANGE_parse_reserve_history (wh->exchange,
- history,
- &wh->reserve_pub,
- balance.currency,
- &total_in_from_history,
- &total_out_from_history,
- len,
- rhistory))
- {
- GNUNET_break_op (0);
- TALER_EXCHANGE_free_reserve_history (rhistory,
- len);
- return GNUNET_SYSERR;
- }
- TALER_EXCHANGE_free_reserve_history (rhistory,
- len);
- }
-
- /* Check that funds were really insufficient */
- if (0 >= TALER_amount_cmp (&wh->requested_amount,
- &balance))
- {
- /* Requested amount is smaller or equal to reported balance,
- so this should not have failed. */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /reserves/$RESERVE_PUB/withdraw request.
- *
- * @param cls the `struct TALER_EXCHANGE_WithdrawHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_reserve_withdraw_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_Withdraw2Handle *wh = cls;
- const json_t *j = response;
- struct TALER_EXCHANGE_HttpResponse hr = {
- .reply = j,
- .http_status = (unsigned int) response_code
- };
-
- wh->job = NULL;
- switch (response_code)
- {
- case 0:
- hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- case MHD_HTTP_OK:
- if (GNUNET_OK !=
- reserve_withdraw_ok (wh,
- j))
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- break;
- }
- GNUNET_assert (NULL == wh->cb);
- TALER_EXCHANGE_withdraw2_cancel (wh);
- return;
- case MHD_HTTP_ACCEPTED:
- /* only validate reply is well-formed */
- {
- uint64_t ptu;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_uint64 ("payment_target_uuid",
- &ptu),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
- 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_NOT_FOUND:
- /* Nothing really to verify, the exchange basically just says
- that it doesn't know this reserve. Can happen if we
- query before the wire transfer went through.
- We should simply pass the JSON reply to the application. */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
- break;
- case MHD_HTTP_CONFLICT:
- /* The exchange says that the reserve has insufficient funds;
- check the signatures in the history... */
- if (GNUNET_OK !=
- reserve_withdraw_payment_required (wh,
- j))
- {
- GNUNET_break_op (0);
- hr.http_status = 0;
- hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- }
- else
- {
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
- }
- break;
- case MHD_HTTP_GONE:
- /* could happen if denomination was revoked */
- /* Note: one might want to check /keys for revocation
- signature here, alas tricky in case our /keys
- is outdated => left to clients */
- hr.ec = TALER_JSON_get_error_code (j);
- 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 */
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
- break;
- default:
- /* unexpected response code */
- GNUNET_break_op (0);
- hr.ec = TALER_JSON_get_error_code (j);
- hr.hint = TALER_JSON_get_error_hint (j);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange withdraw\n",
- (unsigned int) response_code,
- (int) hr.ec);
- break;
- }
- if (NULL != wh->cb)
- {
- wh->cb (wh->cb_cls,
- &hr,
- NULL);
- wh->cb = NULL;
- }
- TALER_EXCHANGE_withdraw2_cancel (wh);
-}
-
-
-struct TALER_EXCHANGE_Withdraw2Handle *
-TALER_EXCHANGE_withdraw2 (
- struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_PlanchetDetail *pd,
- const struct TALER_ReservePrivateKeyP *reserve_priv,
- TALER_EXCHANGE_Withdraw2Callback res_cb,
- void *res_cb_cls)
-{
- struct TALER_EXCHANGE_Withdraw2Handle *wh;
- const struct TALER_EXCHANGE_Keys *keys;
- const struct TALER_EXCHANGE_DenomPublicKey *dk;
- struct TALER_ReserveSignatureP reserve_sig;
- char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
- struct TALER_BlindedCoinHashP bch;
-
- keys = TALER_EXCHANGE_get_keys (exchange);
- if (NULL == keys)
- {
- GNUNET_break (0);
- return NULL;
- }
- dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
- &pd->denom_pub_hash);
- if (NULL == dk)
- {
- GNUNET_break (0);
- return NULL;
- }
- wh = GNUNET_new (struct TALER_EXCHANGE_Withdraw2Handle);
- wh->exchange = exchange;
- wh->cb = res_cb;
- wh->cb_cls = res_cb_cls;
- /* Compute how much we expected to charge to the reserve */
- if (0 >
- TALER_amount_add (&wh->requested_amount,
- &dk->value,
- &dk->fees.withdraw))
- {
- /* Overflow here? Very strange, our CPU must be fried... */
- GNUNET_break (0);
- GNUNET_free (wh);
- return NULL;
- }
-
- GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
- &wh->reserve_pub.eddsa_pub);
-
- {
- char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- &wh->reserve_pub,
- sizeof (struct TALER_ReservePublicKeyP),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "/reserves/%s/withdraw",
- pub_str);
- }
-
- if (GNUNET_OK !=
- TALER_coin_ev_hash (&pd->blinded_planchet,
- &pd->denom_pub_hash,
- &bch))
- {
- GNUNET_break (0);
- GNUNET_free (wh);
- return NULL;
- }
-
- TALER_wallet_withdraw_sign (&pd->denom_pub_hash,
- &wh->requested_amount,
- &bch,
- reserve_priv,
- &reserve_sig);
- {
- json_t *withdraw_obj = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("denom_pub_hash",
- &pd->denom_pub_hash),
- TALER_JSON_pack_blinded_planchet ("coin_ev",
- &pd->blinded_planchet),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &reserve_sig));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Attempting to withdraw from reserve %s\n",
- TALER_B2S (&wh->reserve_pub));
- wh->url = TEAH_path_to_url (exchange,
- arg_str);
- if (NULL == wh->url)
- {
- json_decref (withdraw_obj);
- GNUNET_free (wh);
- return NULL;
- }
- {
- CURL *eh;
- struct GNUNET_CURL_Context *ctx;
-
- ctx = TEAH_handle_to_context (exchange);
- eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
- if ( (NULL == eh) ||
- (GNUNET_OK !=
- TALER_curl_easy_post (&wh->post_ctx,
- eh,
- withdraw_obj)) )
- {
- GNUNET_break (0);
- if (NULL != eh)
- curl_easy_cleanup (eh);
- json_decref (withdraw_obj);
- GNUNET_free (wh->url);
- GNUNET_free (wh);
- return NULL;
- }
- json_decref (withdraw_obj);
- wh->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- wh->post_ctx.headers,
- &handle_reserve_withdraw_finished,
- wh);
- }
- }
- return wh;
-}
-
-
-void
-TALER_EXCHANGE_withdraw2_cancel (struct TALER_EXCHANGE_Withdraw2Handle *wh)
-{
- if (NULL != wh->job)
- {
- GNUNET_CURL_job_cancel (wh->job);
- wh->job = NULL;
- }
- GNUNET_free (wh->url);
- TALER_curl_easy_post_finished (&wh->post_ctx);
- GNUNET_free (wh);
-}
diff --git a/src/lib/test_stefan.c b/src/lib/test_stefan.c
new file mode 100644
index 000000000..4f7add593
--- /dev/null
+++ b/src/lib/test_stefan.c
@@ -0,0 +1,206 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/test_stefan.c
+ * @brief test calculations on the STEFAN curve
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_handle.h"
+
+
+/**
+ * Check if @a a and @a b are numerically close.
+ *
+ * @param a an amount
+ * @param b an amount
+ * @return true if both values are quite close
+ */
+static bool
+amount_close (const struct TALER_Amount *a,
+ const struct TALER_Amount *b)
+{
+ struct TALER_Amount delta;
+
+ switch (TALER_amount_cmp (a,
+ b))
+ {
+ case -1: /* a < b */
+ GNUNET_assert (0 <
+ TALER_amount_subtract (&delta,
+ b,
+ a));
+ break;
+ case 0:
+ /* perfect */
+ return true;
+ case 1: /* a > b */
+ GNUNET_assert (0 <
+ TALER_amount_subtract (&delta,
+ a,
+ b));
+ break;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Rounding error is %s\n",
+ TALER_amount2s (&delta));
+ if (delta.value > 0)
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ if (delta.fraction > 5000)
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ return true; /* let's consider this a rounding error */
+}
+
+
+int
+main (int argc,
+ char **argv)
+{
+ struct TALER_EXCHANGE_DenomPublicKey dk;
+ struct TALER_EXCHANGE_Keys keys = {
+ .denom_keys = &dk,
+ .num_denom_keys = 1
+ };
+ struct TALER_Amount brut;
+ struct TALER_Amount net;
+
+ (void) argc;
+ (void) argv;
+ GNUNET_log_setup ("test-stefan",
+ "INFO",
+ NULL);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:0.00001",
+ &dk.value));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:1",
+ &keys.stefan_abs));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:0.13",
+ &keys.stefan_log));
+ keys.stefan_lin = 1.15;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:4",
+ &brut));
+ GNUNET_log_skip (1,
+ GNUNET_NO);
+ GNUNET_assert (GNUNET_SYSERR ==
+ TALER_EXCHANGE_keys_stefan_b2n (&keys,
+ &brut,
+ &net));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:4",
+ &net));
+ GNUNET_log_skip (1,
+ GNUNET_NO);
+ GNUNET_assert (GNUNET_SYSERR ==
+ TALER_EXCHANGE_keys_stefan_n2b (&keys,
+ &net,
+ &brut));
+ keys.stefan_lin = 1.0;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:4",
+ &brut));
+ GNUNET_log_skip (1,
+ GNUNET_NO);
+ GNUNET_assert (GNUNET_SYSERR ==
+ TALER_EXCHANGE_keys_stefan_b2n (&keys,
+ &brut,
+ &net));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount ("MAGIC:4",
+ &net));
+ GNUNET_log_skip (1,
+ GNUNET_NO);
+ GNUNET_assert (GNUNET_SYSERR ==
+ TALER_EXCHANGE_keys_stefan_n2b (&keys,
+ &net,
+ &brut));
+ GNUNET_assert (0 == GNUNET_get_log_skip ());
+ keys.stefan_lin = 0.1;
+
+ /* try various values for lin and log STEFAN values */
+ for (unsigned int li = 1; li < 13; li += 1)
+ {
+ keys.stefan_lin = 1.0 * li / 100.0;
+
+ for (unsigned int lx = 1; lx < 100; lx += 1)
+ {
+ keys.stefan_log.fraction = lx * TALER_AMOUNT_FRAC_BASE / 100;
+
+ /* Check brutto-to-netto is stable */
+ for (unsigned int i = 0; i<10; i++)
+ {
+ struct TALER_Amount rval;
+
+ brut.value = i;
+ brut.fraction = i * TALER_AMOUNT_FRAC_BASE / 10;
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_EXCHANGE_keys_stefan_b2n (&keys,
+ &brut,
+ &net));
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_EXCHANGE_keys_stefan_n2b (&keys,
+ &net,
+ &rval));
+ if (TALER_amount_is_zero (&net))
+ GNUNET_assert (TALER_amount_is_zero (&rval));
+ else
+ {
+ GNUNET_assert (amount_close (&brut,
+ &rval));
+ TALER_EXCHANGE_keys_stefan_round (&keys,
+ &rval);
+ GNUNET_assert (amount_close (&brut,
+ &rval));
+ }
+ }
+
+ /* Check netto-to-brutto is stable */
+ for (unsigned int i = 0; i<10; i++)
+ {
+ struct TALER_Amount rval;
+
+ net.value = i;
+ net.fraction = i * TALER_AMOUNT_FRAC_BASE / 10;
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_EXCHANGE_keys_stefan_n2b (&keys,
+ &net,
+ &brut));
+ GNUNET_assert (GNUNET_SYSERR !=
+ TALER_EXCHANGE_keys_stefan_b2n (&keys,
+ &brut,
+ &rval));
+ GNUNET_assert (amount_close (&net,
+ &rval));
+ TALER_EXCHANGE_keys_stefan_round (&keys,
+ &rval);
+ GNUNET_assert (amount_close (&net,
+ &rval));
+ }
+ }
+ }
+ return 0;
+}