commit 12f9e8c08bc8987585e009bcee068a51351d30da
parent abf2f890754bcf1d6c11fc4b2b41adf4c18a70d7
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 11 Oct 2026 14:30:36 +0200
refuse inconsistent age masks
Diffstat:
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",