exchange

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

commit 12f9e8c08bc8987585e009bcee068a51351d30da
parent abf2f890754bcf1d6c11fc4b2b41adf4c18a70d7
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun, 11 Oct 2026 14:30:36 +0200

refuse inconsistent age masks

Diffstat:
Msrc/exchange/taler-exchange-httpd_post-reveal-withdraw.c | 13++++++++++++-
Msrc/exchange/taler-exchange-httpd_post-withdraw.c | 36++++++++++++++++++++++++++++++++++++
2 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/src/exchange/taler-exchange-httpd_post-reveal-withdraw.c b/src/exchange/taler-exchange-httpd_post-reveal-withdraw.c @@ -391,7 +391,18 @@ verify_commitment_and_max_age ( if (i == 0) mask = denom_keys[i]->meta.age_mask; - GNUNET_assert (mask.bits == denom_keys[i]->meta.age_mask.bits); + if (mask.bits != denom_keys[i]->meta.age_mask.bits) + { + /* /withdraw refuses age-restricted batches with mixed age masks, + so such a commitment cannot have been stored by this version. */ + GNUNET_break (0); + *result = TALER_MHD_reply_with_error ( + con, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "denominations of the withdraw commitment have different age masks"); + return GNUNET_SYSERR; + } if (GNUNET_CRYPTO_BSA_CS == denom_keys[i]->denom_pub.bsign_pub_key->cipher) diff --git a/src/exchange/taler-exchange-httpd_post-withdraw.c b/src/exchange/taler-exchange-httpd_post-withdraw.c @@ -49,6 +49,7 @@ enum WithdrawError { WITHDRAW_ERROR_NONE, + WITHDRAW_ERROR_AGE_MASK_MISMATCH, WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION, WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED, WITHDRAW_ERROR_AMOUNT_OVERFLOW, @@ -257,6 +258,7 @@ struct WithdrawContext * WITHDRAW_ERROR_DENOMINATION_EXPIRED, * WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE, * WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION, + * WITHDRAW_ERROR_AGE_MASK_MISMATCH, * we use this one field. */ const struct TALER_DenominationHashP *denom_h; @@ -952,6 +954,7 @@ phase_check_keys ( { struct TEH_KeyStateHandle *ksh; bool is_cs_denom[wc->request.withdraw.num_coins]; + struct TALER_AgeMask age_mask = { 0 }; memset (is_cs_denom, 0, @@ -986,6 +989,23 @@ phase_check_keys ( &wc->request.denoms_h[i], &dk)) return; + /* All coins of an age-restricted withdraw must use the same age + groups, as /reveal-withdraw derives their age commitments with + one mask. */ + if (wc->request.withdraw.age_proof_required) + { + if (0 == i) + age_mask = dk->meta.age_mask; + if (age_mask.bits != dk->meta.age_mask.bits) + { + GNUNET_break_op (0); + SET_ERROR_WITH_DETAIL (wc, + WITHDRAW_ERROR_AGE_MASK_MISMATCH, + denom_h, + &wc->request.denoms_h[i]); + return; + } + } switch (dk->denom_pub.bsign_pub_key->cipher) { case GNUNET_CRYPTO_BSA_INVALID: @@ -1460,6 +1480,22 @@ phase_generate_reply_error ( msg)); return; } + case WITHDRAW_ERROR_AGE_MASK_MISMATCH: + { + char msg[256]; + + GNUNET_snprintf (msg, + sizeof(msg), + "age mask of denomination %s differs from that of the first denomination", + GNUNET_h2s (&wc->error.details.denom_h->hash)); + finish_loop (wc, + TALER_MHD_reply_with_error ( + wc->rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_WITHDRAW_AGE_MASK_MISMATCH, + msg)); + return; + } case WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE: GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Generating JSON response with code %d\n",