exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit 0e02c014318916993ceb0436f985c1d4891bb06d
parent 08682d8b99c1df217d07480e3e4e3c92096807be
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun,  3 May 2026 22:55:36 +0200

fix #11318

Diffstat:
MMakefile.in | 6+++---
Msrc/exchange/taler-exchange-httpd_get-coins-COIN_PUB-history.c | 6++++++
Msrc/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-merge.c | 3+++
Msrc/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-open.c | 28++++++++++++++++++++++++----
Msrc/exchangedb/get_coin_transactions.c | 21++++++++++++++-------
Msrc/include/exchange-database/free_coin_transaction_list.h | 11+++++++++++
Msrc/include/taler/exchange/get-coins-COIN_PUB-history.h | 2++
Msrc/include/taler/taler_crypto_lib.h | 10++++++++++
Msrc/include/taler/taler_error_codes.h | 8++++++++
Msrc/lib/exchange_api_get-coins-COIN_PUB-history.c | 25++++++++++++++++++-------
Msrc/lib/exchange_api_post-reserves-RESERVE_PUB-open.c | 6++++--
Msrc/templating/mustach-jansson.c | 464++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/testing/meson.build | 33+++++++++++++++++----------------
Msrc/util/taler_error_codes.c | 10+++++++++-
Msrc/util/wallet_signatures.c | 26+++++++++++++++++++++++---
15 files changed, 411 insertions(+), 248 deletions(-)

diff --git a/Makefile.in b/Makefile.in @@ -31,15 +31,15 @@ doxygen: # Run tests .PHONY: check check: - $(MESON) test -C $(mesonbuilddir) --maxfail 1 --timeout-multiplier 4 + $(MESON) test -C $(mesonbuilddir) --maxfail 999 --timeout-multiplier 4 .PHONY: installcheck installcheck: - $(MESON) test -C $(mesonbuilddir) --suite=installcheck --maxfail 1 --timeout-multiplier 4 + $(MESON) test -C $(mesonbuilddir) --suite=installcheck --maxfail 999 --timeout-multiplier 4 .PHONY: integrationtests integrationtests: - $(MESON) test -C $(mesonbuilddir) --suite=integrationtests --maxfail 1 --timeout-multiplier 4 + $(MESON) test -C $(mesonbuilddir) --suite=integrationtests --maxfail 999 --timeout-multiplier 4 format: $(MESON) fmt -i -r . diff --git a/src/exchange/taler-exchange-httpd_get-coins-COIN_PUB-history.c b/src/exchange/taler-exchange-httpd_get-coins-COIN_PUB-history.c @@ -557,7 +557,10 @@ compile_transaction_history ( { struct TALER_EXCHANGEDB_ReserveOpenListEntry *role = pos->details.reserve_open; + const struct TALER_AgeCommitmentHashP *phac = NULL; + if (! role->no_age_commitment) + phac = &role->h_age_commitment; if (0 != json_array_append_new ( history, @@ -568,6 +571,9 @@ compile_transaction_history ( pos->coin_history_id), TALER_JSON_pack_amount ("coin_contribution", &role->coin_contribution), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("h_age_commitment", + phac)), GNUNET_JSON_pack_data_auto ("reserve_sig", &role->reserve_sig), GNUNET_JSON_pack_data_auto ("coin_sig", diff --git a/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-merge.c b/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-merge.c @@ -255,6 +255,8 @@ reply_merge_success (const struct PurseMergeContext *pmc) TALER_JSON_pack_amount ("target_amount", &pmc->target_amount)); } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wduplicated-branches" if ( (NULL == pmc->provider_url) || (0 == strcmp (pmc->provider_url, TEH_base_url)) ) @@ -279,6 +281,7 @@ reply_merge_success (const struct PurseMergeContext *pmc) merge_amount = pmc->target_amount; #endif } +#pragma GCC diagnostic pop if (TALER_EC_NONE != (ec = TALER_exchange_online_purse_merged_sign ( &TEH_keys_exchange_sign_, diff --git a/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-open.c b/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-open.c @@ -387,6 +387,25 @@ TEH_handler_reserves_open (struct TEH_RequestContext *rc, cleanup_rsc (&rsc); return MHD_YES; /* failure */ } + if (GNUNET_OK != + TALER_wallet_reserve_open_deposit_verify ( + &coin->amount, + &coin->cpi.denom_pub_hash, + coin->cpi.no_age_commitment + ? NULL + : &coin->cpi.h_age_commitment, + &rsc.reserve_sig, + &coin->cpi.coin_pub, + &coin->coin_sig)) + { + GNUNET_break_op (0); + cleanup_rsc (&rsc); + return TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_EXCHANGE_RESERVES_OPEN_COIN_SIGNATURE_INVALID, + "TALER_wallet_reserve_open_deposit_verify failed"); + } if (0 > TALER_amount_add (&rsc.total, &rsc.total, @@ -394,10 +413,11 @@ TEH_handler_reserves_open (struct TEH_RequestContext *rc, { GNUNET_break (0); cleanup_rsc (&rsc); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, - NULL); + return TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, + NULL); } } diff --git a/src/exchangedb/get_coin_transactions.c b/src/exchangedb/get_coin_transactions.c @@ -658,6 +658,10 @@ add_coin_reserve_open (void *cls, &role->coin_sig), TALER_PQ_RESULT_SPEC_AMOUNT ("contribution", &role->coin_contribution), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash", + &role->h_age_commitment), + &role->no_age_commitment), GNUNET_PQ_result_spec_uint64 ("reserve_open_deposit_uuid", &serial_id), GNUNET_PQ_result_spec_end @@ -1065,13 +1069,16 @@ TALER_EXCHANGEDB_get_coin_transactions ( PREPARE (pg, "reserve_open_by_coin", "SELECT" - " reserve_open_deposit_uuid" - ",coin_sig" - ",reserve_sig" - ",contribution" - " FROM reserves_open_deposits" - " WHERE coin_pub=$1" - " AND reserve_open_deposit_uuid=$2;"); + " rod.reserve_open_deposit_uuid" + ",rod.coin_sig" + ",rod.reserve_sig" + ",rod.contribution" + ",kc.age_commitment_hash" + " FROM reserves_open_deposits rod" + " JOIN known_coins kc" + " ON (rod.coin_pub = kc.coin_pub)" + " WHERE rod.coin_pub=$1" + " AND rod.reserve_open_deposit_uuid=$2;"); GNUNET_log (GNUNET_ERROR_TYPE_INFO, " --- landed here 1\n"); for (unsigned int i = 0; i<RETRIES; i++) diff --git a/src/include/exchange-database/free_coin_transaction_list.h b/src/include/exchange-database/free_coin_transaction_list.h @@ -506,6 +506,17 @@ struct TALER_EXCHANGEDB_ReserveOpenListEntry */ struct TALER_CoinSpendSignatureP coin_sig; + /** + * Hash of the age commitment used to sign the coin, if age restriction was + * applicable to the denomination. + */ + struct TALER_AgeCommitmentHashP h_age_commitment; + + /** + * Set to true if there was no age commitment. + */ + bool no_age_commitment; + }; diff --git a/src/include/taler/exchange/get-coins-COIN_PUB-history.h b/src/include/taler/exchange/get-coins-COIN_PUB-history.h @@ -211,6 +211,8 @@ struct TALER_EXCHANGE_CoinHistoryEntry struct TALER_ReserveSignatureP reserve_sig; struct TALER_CoinSpendSignatureP coin_sig; struct TALER_Amount coin_contribution; + struct TALER_AgeCommitmentHashP h_age_commitment; + bool no_hac; } reserve_open_deposit; } details; diff --git a/src/include/taler/taler_crypto_lib.h b/src/include/taler/taler_crypto_lib.h @@ -4345,6 +4345,9 @@ TALER_wallet_reserve_open_verify ( * Sign to deposit coin to pay for keeping a reserve open. * * @param coin_contribution how much the coin should contribute + * @param h_denom_pub hash over the denomination public key of the coin + * @param h_age_commitment hash over the age commitment, NULL if coin + * is not age restricted * @param reserve_sig signature over the reserve open operation * @param coin_priv private key of the coin * @param[out] coin_sig signature by the coin @@ -4352,6 +4355,8 @@ TALER_wallet_reserve_open_verify ( void TALER_wallet_reserve_open_deposit_sign ( const struct TALER_Amount *coin_contribution, + const struct TALER_DenominationHashP *h_denom_pub, + const struct TALER_AgeCommitmentHashP *h_age_commitment, const struct TALER_ReserveSignatureP *reserve_sig, const struct TALER_CoinSpendPrivateKeyP *coin_priv, struct TALER_CoinSpendSignatureP *coin_sig); @@ -4361,6 +4366,9 @@ TALER_wallet_reserve_open_deposit_sign ( * Verify signature that deposits coin to pay for keeping a reserve open. * * @param coin_contribution how much the coin should contribute + * @param h_denom_pub hash over the denomination public key of the coin + * @param h_age_commitment hash over the age commitment, NULL if coin + * is not age restricted * @param reserve_sig signature over the reserve open operation * @param coin_pub public key of the coin * @param coin_sig signature by the coin @@ -4369,6 +4377,8 @@ TALER_wallet_reserve_open_deposit_sign ( enum GNUNET_GenericReturnValue TALER_wallet_reserve_open_deposit_verify ( const struct TALER_Amount *coin_contribution, + const struct TALER_DenominationHashP *h_denom_pub, + const struct TALER_AgeCommitmentHashP *h_age_commitment, const struct TALER_ReserveSignatureP *reserve_sig, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_CoinSpendSignatureP *coin_sig); diff --git a/src/include/taler/taler_error_codes.h b/src/include/taler/taler_error_codes.h @@ -1720,6 +1720,14 @@ enum TALER_ErrorCode /** + * A coin signature for a deposit to pay to open the reserve is invalid. + * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403). + * (A value of 0 indicates that the error is generated client-side). + */ + TALER_EC_EXCHANGE_RESERVES_OPEN_COIN_SIGNATURE_INVALID = 1790, + + + /** * The auditor that was supposed to be disabled is unknown to this exchange. * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). * (A value of 0 indicates that the error is generated client-side). diff --git a/src/lib/exchange_api_get-coins-COIN_PUB-history.c b/src/lib/exchange_api_get-coins-COIN_PUB-history.c @@ -716,13 +716,20 @@ help_reserve_open_deposit (struct CoinHistoryParseContext *pc, 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), - TALER_JSON_spec_amount_any ("coin_contribution", - &rh->details.reserve_open_deposit. - coin_contribution), + 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), + TALER_JSON_spec_amount_any ( + "coin_contribution", + &rh->details.reserve_open_deposit.coin_contribution), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ( + "h_age_commitment", + &rh->details.reserve_open_deposit.h_age_commitment), + &rh->details.reserve_open_deposit.no_hac), GNUNET_JSON_spec_end () }; @@ -737,6 +744,10 @@ help_reserve_open_deposit (struct CoinHistoryParseContext *pc, if (GNUNET_OK != TALER_wallet_reserve_open_deposit_verify ( amount, + &pc->dk->h_key, + rh->details.reserve_open_deposit.no_hac + ? NULL + : &rh->details.reserve_open_deposit.h_age_commitment, &rh->details.reserve_open_deposit.reserve_sig, pc->coin_pub, &rh->details.reserve_open_deposit.coin_sig)) diff --git a/src/lib/exchange_api_post-reserves-RESERVE_PUB-open.c b/src/lib/exchange_api_post-reserves-RESERVE_PUB-open.c @@ -388,7 +388,7 @@ TALER_EXCHANGE_post_reserves_open_create ( { const struct TALER_EXCHANGE_PurseDeposit *pd = &coin_payments[i]; const struct TALER_AgeCommitmentProof *acp = pd->age_commitment_proof; - struct TALER_AgeCommitmentHashP ahac; + struct TALER_AgeCommitmentHashP ahac = { 0 }; struct TALER_AgeCommitmentHashP *achp = NULL; struct CoinData *cd = &proh->coins[i]; json_t *cp; @@ -402,6 +402,8 @@ TALER_EXCHANGE_post_reserves_open_create ( achp = &ahac; } TALER_wallet_reserve_open_deposit_sign (&pd->amount, + &pd->h_denom_pub, + achp, &reserve_sig, &pd->coin_priv, &cd->coin_sig); @@ -411,7 +413,7 @@ TALER_EXCHANGE_post_reserves_open_create ( cp = GNUNET_JSON_PACK ( GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_data_auto ("h_age_commitment", - achp)), + &ahac)), TALER_JSON_pack_amount ("amount", &pd->amount), GNUNET_JSON_pack_data_auto ("denom_pub_hash", diff --git a/src/templating/mustach-jansson.c b/src/templating/mustach-jansson.c @@ -17,255 +17,309 @@ #include "mustach-wrap.h" #include "mustach-jansson.h" -struct expl { - json_t *root; - json_t *selection; - int depth; - struct { - json_t *cont; - json_t *obj; - void *iter; - int is_objiter; - size_t index, count; - } stack[MUSTACH_MAX_DEPTH]; +struct expl +{ + json_t *root; + json_t *selection; + int depth; + struct + { + json_t *cont; + json_t *obj; + void *iter; + int is_objiter; + size_t index, count; + } stack[MUSTACH_MAX_DEPTH]; }; -static int start(void *closure) +static int +start (void *closure) { - struct expl *e = closure; - e->depth = 0; - e->selection = json_null(); - e->stack[0].cont = NULL; - e->stack[0].obj = e->root; - e->stack[0].index = 0; - e->stack[0].count = 1; - return MUSTACH_OK; + struct expl *e = closure; + e->depth = 0; + e->selection = json_null (); + e->stack[0].cont = NULL; + e->stack[0].obj = e->root; + e->stack[0].index = 0; + e->stack[0].count = 1; + return MUSTACH_OK; } -static int compare(void *closure, const char *value) + +static int +compare (void *closure, const char *value) { - struct expl *e = closure; - json_t *o = e->selection; - double d; - json_int_t i; - - switch (json_typeof(o)) { - case JSON_REAL: - d = json_number_value(o) - atof(value); - return d < 0 ? -1 : d > 0 ? 1 : 0; - case JSON_INTEGER: - i = (json_int_t)json_integer_value(o) - (json_int_t)atoll(value); - return i < 0 ? -1 : i > 0 ? 1 : 0; - case JSON_STRING: - return strcmp(json_string_value(o), value); - case JSON_TRUE: - return strcmp("true", value); - case JSON_FALSE: - return strcmp("false", value); - case JSON_NULL: - return strcmp("null", value); - default: - return 1; - } + struct expl *e = closure; + json_t *o = e->selection; + double d; + json_int_t i; + + switch (json_typeof (o)) + { + case JSON_REAL: + d = json_number_value (o) - atof (value); + return d < 0 ? -1 : d > 0 ? 1 : 0; + case JSON_INTEGER: + i = (json_int_t) json_integer_value (o) - (json_int_t) atoll (value); + return i < 0 ? -1 : i > 0 ? 1 : 0; + case JSON_STRING: + return strcmp (json_string_value (o), value); + case JSON_TRUE: + return strcmp ("true", value); + case JSON_FALSE: + return strcmp ("false", value); + case JSON_NULL: + return strcmp ("null", value); + default: + return 1; + } } -static int sel(void *closure, const char *name) + +static int +sel (void *closure, const char *name) { - struct expl *e = closure; - json_t *o; - int i, r; - - if (name == NULL) { - o = e->stack[e->depth].obj; - r = 1; - } else { - i = e->depth; - while (i >= 0 && !(o = json_object_get(e->stack[i].obj, name))) - i--; - if (i >= 0) - r = 1; - else { - o = json_null(); - r = 0; - } - } - e->selection = o; - return r; + struct expl *e = closure; + json_t *o; + int i, r; + + if (name == NULL) + { + o = e->stack[e->depth].obj; + r = 1; + } + else + { + i = e->depth; + while (i >= 0 && ! (o = json_object_get (e->stack[i].obj, name))) + i--; + if (i >= 0) + r = 1; + else + { + o = json_null (); + r = 0; + } + } + e->selection = o; + return r; } -static int subsel(void *closure, const char *name) + +static int +subsel (void *closure, const char *name) { - struct expl *e = closure; - json_t *o = NULL; - int r = 0; - - if (json_is_object(e->selection)) { - o = json_object_get(e->selection, name); - r = o != NULL; - } - else if (json_is_array(e->selection)) { - char *end; - size_t idx = (size_t)strtol(name, &end, 10); - if (!*end && idx < json_array_size(e->selection)) { - o = json_array_get(e->selection, idx); - r = 1; - } - } - if (r) - e->selection = o; - return r; + struct expl *e = closure; + json_t *o = NULL; + int r = 0; + + if (json_is_object (e->selection)) + { + o = json_object_get (e->selection, name); + r = o != NULL; + } + else if (json_is_array (e->selection)) + { + char *end; + size_t idx = (size_t) strtol (name, &end, 10); + if (! *end && idx < json_array_size (e->selection)) + { + o = json_array_get (e->selection, idx); + r = 1; + } + } + if (r) + e->selection = o; + return r; } -static int enter(void *closure, int objiter) + +static int +enter (void *closure, int objiter) { - struct expl *e = closure; - json_t *o; - - if (++e->depth >= MUSTACH_MAX_DEPTH) - return MUSTACH_ERROR_TOO_DEEP; - - o = e->selection; - e->stack[e->depth].is_objiter = 0; - if (objiter) { - if (!json_is_object(o)) - goto not_entering; - e->stack[e->depth].iter = json_object_iter(o); - if (e->stack[e->depth].iter == NULL) - goto not_entering; - e->stack[e->depth].obj = json_object_iter_value(e->stack[e->depth].iter); - e->stack[e->depth].cont = o; - e->stack[e->depth].is_objiter = 1; - } else if (json_is_array(o)) { - e->stack[e->depth].count = json_array_size(o); - if (e->stack[e->depth].count == 0) - goto not_entering; - e->stack[e->depth].cont = o; - e->stack[e->depth].obj = json_array_get(o, 0); - e->stack[e->depth].index = 0; - } else if ((json_is_object(o) && json_object_size(o)) - || json_is_true(o) - || (json_is_string(o) && json_string_length(o) > 0) - || (json_is_integer(o) && json_integer_value(o) != 0) - || (json_is_real(o) && json_real_value(o) != 0)) { - e->stack[e->depth].count = 1; - e->stack[e->depth].cont = NULL; - e->stack[e->depth].obj = o; - e->stack[e->depth].index = 0; - } else - goto not_entering; - return 1; + struct expl *e = closure; + json_t *o; + + if (++e->depth >= MUSTACH_MAX_DEPTH) + return MUSTACH_ERROR_TOO_DEEP; + + o = e->selection; + e->stack[e->depth].is_objiter = 0; + /* Squash warning for comparing float to 0.0 */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" + if (objiter) + { + if (! json_is_object (o)) + goto not_entering; + e->stack[e->depth].iter = json_object_iter (o); + if (e->stack[e->depth].iter == NULL) + goto not_entering; + e->stack[e->depth].obj = json_object_iter_value (e->stack[e->depth].iter); + e->stack[e->depth].cont = o; + e->stack[e->depth].is_objiter = 1; + } + else if (json_is_array (o)) + { + e->stack[e->depth].count = json_array_size (o); + if (e->stack[e->depth].count == 0) + goto not_entering; + e->stack[e->depth].cont = o; + e->stack[e->depth].obj = json_array_get (o, 0); + e->stack[e->depth].index = 0; + } + else if ((json_is_object (o) && json_object_size (o)) + || json_is_true (o) + || (json_is_string (o) && json_string_length (o) > 0) + || (json_is_integer (o) && json_integer_value (o) != 0) + || (json_is_real (o) && json_real_value (o) != 0)) + { + e->stack[e->depth].count = 1; + e->stack[e->depth].cont = NULL; + e->stack[e->depth].obj = o; + e->stack[e->depth].index = 0; + } + else + goto not_entering; +#pragma GCC diagnostic pop + return 1; not_entering: - e->depth--; - return 0; + e->depth--; + return 0; } -static int next(void *closure) -{ - struct expl *e = closure; - - if (e->depth <= 0) - return MUSTACH_ERROR_CLOSING; - - if (e->stack[e->depth].is_objiter) { - e->stack[e->depth].iter = json_object_iter_next(e->stack[e->depth].cont, e->stack[e->depth].iter); - if (e->stack[e->depth].iter == NULL) - return 0; - e->stack[e->depth].obj = json_object_iter_value(e->stack[e->depth].iter); - return 1; - } - - e->stack[e->depth].index++; - if (e->stack[e->depth].index >= e->stack[e->depth].count) - return 0; - e->stack[e->depth].obj = json_array_get(e->stack[e->depth].cont, e->stack[e->depth].index); - return 1; +static int +next (void *closure) +{ + struct expl *e = closure; + + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; + + if (e->stack[e->depth].is_objiter) + { + e->stack[e->depth].iter = json_object_iter_next (e->stack[e->depth].cont, e->stack[e->depth]. + iter); + if (e->stack[e->depth].iter == NULL) + return 0; + e->stack[e->depth].obj = json_object_iter_value (e->stack[e->depth].iter); + return 1; + } + + e->stack[e->depth].index++; + if (e->stack[e->depth].index >= e->stack[e->depth].count) + return 0; + + e->stack[e->depth].obj = json_array_get (e->stack[e->depth].cont, e->stack[e->depth].index); + return 1; } -static int leave(void *closure) + +static int +leave (void *closure) { - struct expl *e = closure; + struct expl *e = closure; - if (e->depth <= 0) - return MUSTACH_ERROR_CLOSING; + if (e->depth <= 0) + return MUSTACH_ERROR_CLOSING; - e->depth--; - return 0; + e->depth--; + return 0; } -static int get(void *closure, struct mustach_sbuf *sbuf, int key) + +static int +get (void *closure, struct mustach_sbuf *sbuf, int key) { - struct expl *e = closure; - const char *s; - int d; - - if (key) { - s = ""; - for (d = e->depth ; d >= 0 ; d--) - if (e->stack[d].is_objiter) { - s = json_object_iter_key(e->stack[d].iter); - break; - } - } - else if (json_is_string(e->selection)) - s = json_string_value(e->selection); - else if (json_is_null(e->selection)) - s = ""; - else { - s = json_dumps(e->selection, JSON_ENCODE_ANY | JSON_COMPACT); - if (s == NULL) - return MUSTACH_ERROR_SYSTEM; - sbuf->freecb = free; - } - sbuf->value = s; - return 1; + struct expl *e = closure; + const char *s; + int d; + + if (key) + { + s = ""; + for (d = e->depth ; d >= 0 ; d--) + if (e->stack[d].is_objiter) + { + s = json_object_iter_key (e->stack[d].iter); + break; + } + } + else if (json_is_string (e->selection)) + s = json_string_value (e->selection); + else if (json_is_null (e->selection)) + s = ""; + else + { + s = json_dumps (e->selection, JSON_ENCODE_ANY | JSON_COMPACT); + if (s == NULL) + return MUSTACH_ERROR_SYSTEM; + sbuf->freecb = free; + } + sbuf->value = s; + return 1; } + const struct mustach_wrap_itf mustach_jansson_wrap_itf = { - .start = start, - .stop = NULL, - .compare = compare, - .sel = sel, - .subsel = subsel, - .enter = enter, - .next = next, - .leave = leave, - .get = get + .start = start, + .stop = NULL, + .compare = compare, + .sel = sel, + .subsel = subsel, + .enter = enter, + .next = next, + .leave = leave, + .get = get }; -int mustach_jansson_file(const char *template, size_t length, json_t *root, int flags, FILE *file) +int +mustach_jansson_file (const char *template, size_t length, json_t *root, int flags, FILE *file) { - struct expl e; - e.root = root; - return mustach_wrap_file(template, length, &mustach_jansson_wrap_itf, &e, flags, file); + struct expl e; + e.root = root; + return mustach_wrap_file (template, length, &mustach_jansson_wrap_itf, &e, flags, file); } -int mustach_jansson_fd(const char *template, size_t length, json_t *root, int flags, int fd) + +int +mustach_jansson_fd (const char *template, size_t length, json_t *root, int flags, int fd) { - struct expl e; - e.root = root; - return mustach_wrap_fd(template, length, &mustach_jansson_wrap_itf, &e, flags, fd); + struct expl e; + e.root = root; + return mustach_wrap_fd (template, length, &mustach_jansson_wrap_itf, &e, flags, fd); } -int mustach_jansson_mem(const char *template, size_t length, json_t *root, int flags, char **result, size_t *size) + +int +mustach_jansson_mem (const char *template, size_t length, json_t *root, int flags, char **result, + size_t *size) { - struct expl e; - e.root = root; - return mustach_wrap_mem(template, length, &mustach_jansson_wrap_itf, &e, flags, result, size); + struct expl e; + e.root = root; + return mustach_wrap_mem (template, length, &mustach_jansson_wrap_itf, &e, flags, result, size); } -int mustach_jansson_write(const char *template, size_t length, json_t *root, int flags, mustach_write_cb_t *writecb, void *closure) + +int +mustach_jansson_write (const char *template, size_t length, json_t *root, int flags, + mustach_write_cb_t *writecb, void *closure) { - struct expl e; - e.root = root; - return mustach_wrap_write(template, length, &mustach_jansson_wrap_itf, &e, flags, writecb, closure); + struct expl e; + e.root = root; + return mustach_wrap_write (template, length, &mustach_jansson_wrap_itf, &e, flags, writecb, + closure); } -int mustach_jansson_emit(const char *template, size_t length, json_t *root, int flags, mustach_emit_cb_t *emitcb, void *closure) + +int +mustach_jansson_emit (const char *template, size_t length, json_t *root, int flags, + mustach_emit_cb_t *emitcb, void *closure) { - struct expl e; - e.root = root; - return mustach_wrap_emit(template, length, &mustach_jansson_wrap_itf, &e, flags, emitcb, closure); + struct expl e; + e.root = root; + return mustach_wrap_emit (template, length, &mustach_jansson_wrap_itf, &e, flags, emitcb, closure) + ; } - diff --git a/src/testing/meson.build b/src/testing/meson.build @@ -4,22 +4,6 @@ # rm -rf report* -check_SCRIPTS = ['test-exchange-taler-harness', 'test-sanctions'] - -foreach f : check_SCRIPTS - f_sh = '@0@.sh'.format(f) - s = configure_file(input: f_sh, output: f_sh, copy: true) - test( - f, - s, - workdir: meson.current_build_dir(), - suite: ['testing', 'integrationtests'], - is_parallel: false, - timeout: 300, - ) - -endforeach - configure_file( input: 'taler-unified-setup.sh', output: 'taler-unified-setup.sh', @@ -951,3 +935,20 @@ subdir( subdir( 'test_exchange_api_home' / '.local' / 'share' / 'taler-exchange' / 'offline', ) + + +check_SCRIPTS = ['test-exchange-taler-harness', 'test-sanctions'] + +foreach f : check_SCRIPTS + f_sh = '@0@.sh'.format(f) + s = configure_file(input: f_sh, output: f_sh, copy: true) + test( + f, + s, + workdir: meson.current_build_dir(), + suite: ['testing', 'integrationtests'], + is_parallel: false, + timeout: 300, + ) + +endforeach diff --git a/src/util/taler_error_codes.c b/src/util/taler_error_codes.c @@ -1675,6 +1675,14 @@ static const struct ErrorCodeAndHint code_hint_pairs[] = { }, { + /* 1790 */ + .ec = TALER_EC_EXCHANGE_RESERVES_OPEN_COIN_SIGNATURE_INVALID, + .hint = + "A coin signature for a deposit to pay to open the reserve is invalid.", + .http_code = MHD_HTTP_FORBIDDEN + }, + + { /* 1800 */ .ec = TALER_EC_EXCHANGE_MANAGEMENT_AUDITOR_NOT_FOUND, .hint = @@ -5666,7 +5674,7 @@ static const struct ErrorCodeAndHint code_hint_pairs[] = { /** * The length of @e code_hint_pairs. */ -static const unsigned int code_hint_pairs_length = 732; +static const unsigned int code_hint_pairs_length = 733; const char * diff --git a/src/util/wallet_signatures.c b/src/util/wallet_signatures.c @@ -1753,15 +1753,27 @@ struct TALER_ReserveOpenDepositPS * reserve. */ struct TALER_AmountNBO coin_contribution; + + /** + * Hash over the coin's age commitment, all zero if the coin had + * no age restriction. + */ + struct TALER_AgeCommitmentHashP h_age_commitment; + + /** + * Hash over the coin's denomination public key. + */ + struct TALER_DenominationHashP h_denom_pub; }; GNUNET_NETWORK_STRUCT_END -// FIXME-#11318: add h_age_commitment, h_denom_pub to have proof! void TALER_wallet_reserve_open_deposit_sign ( const struct TALER_Amount *coin_contribution, + const struct TALER_DenominationHashP *h_denom_pub, + const struct TALER_AgeCommitmentHashP *h_age_commitment, const struct TALER_ReserveSignatureP *reserve_sig, const struct TALER_CoinSpendPrivateKeyP *coin_priv, struct TALER_CoinSpendSignatureP *coin_sig) @@ -1769,9 +1781,12 @@ TALER_wallet_reserve_open_deposit_sign ( struct TALER_ReserveOpenDepositPS rod = { .purpose.size = htonl (sizeof (rod)), .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT), - .reserve_sig = *reserve_sig + .reserve_sig = *reserve_sig, + .h_denom_pub = *h_denom_pub }; + if (NULL != h_age_commitment) + rod.h_age_commitment = *h_age_commitment; TALER_amount_hton (&rod.coin_contribution, coin_contribution); GNUNET_assert (GNUNET_OK == @@ -1784,6 +1799,8 @@ TALER_wallet_reserve_open_deposit_sign ( enum GNUNET_GenericReturnValue TALER_wallet_reserve_open_deposit_verify ( const struct TALER_Amount *coin_contribution, + const struct TALER_DenominationHashP *h_denom_pub, + const struct TALER_AgeCommitmentHashP *h_age_commitment, const struct TALER_ReserveSignatureP *reserve_sig, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_CoinSpendSignatureP *coin_sig) @@ -1791,9 +1808,12 @@ TALER_wallet_reserve_open_deposit_verify ( struct TALER_ReserveOpenDepositPS rod = { .purpose.size = htonl (sizeof (rod)), .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT), - .reserve_sig = *reserve_sig + .reserve_sig = *reserve_sig, + .h_denom_pub = *h_denom_pub }; + if (NULL != h_age_commitment) + rod.h_age_commitment = *h_age_commitment; TALER_amount_hton (&rod.coin_contribution, coin_contribution); return GNUNET_CRYPTO_eddsa_verify_ (