commit 44b048c200843a3206216f461752f87225cac06c
parent 24a97957451a6580ddf3d533834c4088414adcfc
Author: Florian Dold <florian@dold.me>
Date: Tue, 7 Oct 2025 18:22:09 +0200
fix output token handling
There was a major confusion between output token and signed output
tokens. One output token generates zero or more signed output tokens
depending on the wallet data.
Diffstat:
1 file changed, 66 insertions(+), 43 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -316,6 +316,12 @@ struct SignedOutputToken
{
/**
+ * Index of the output token that produced
+ * this blindly signed token.
+ */
+ unsigned int output_index;
+
+ /**
* Blinded token use public keys waiting to be signed.
*/
struct TALER_BlindedTokenIssueSignature sig;
@@ -672,6 +678,12 @@ struct PayContext
*/
struct TALER_Amount brutto;
+ /**
+ * Index of the donau output in the list of tokens.
+ * Set to -1 if no donau output exists.
+ */
+ int donau_output_index;
+
} validate_tokens;
/**
@@ -2017,52 +2029,21 @@ add_donation_receipt_outputs (
size_t num_blinded_sigs,
struct DONAU_BlindedDonationUnitSignature *blinded_sigs)
{
- const struct TALER_MERCHANT_ContractChoice *choice
- = &pc->check_contract.contract_terms->details.v1.choices[
- pc->parse_wallet_data.choice_index];
- unsigned int off = 0;
+ int donau_output_index = pc->validate_tokens.donau_output_index;
GNUNET_assert (pc->parse_wallet_data.num_bkps ==
num_blinded_sigs);
- for (unsigned int i = 0; i < choice->outputs_len; i++)
- {
- const struct TALER_MERCHANT_ContractOutput *output
- = &choice->outputs[i];
+ GNUNET_assert (donau_output_index >= 0);
- switch (output->type)
- {
- case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
- GNUNET_assert (0);
- break;
- case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
- GNUNET_assert (off + output->details.token.count >= off);
- off += output->details.token.count;
- continue;
- case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
- break;
-#if FUTURE
- case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_COIN:
- GNUNET_break (0);
- pay_end (pc,
- TALER_MHD_reply_with_error (
- pc->connection,
- MHD_HTTP_NOT_IMPLEMENTED,
- TALER_EC_MERCHANT_GENERIC_FEATURE_NOT_AVAILABLE,
- "token type not yet supported"));
- return GNUNET_SYSERR;
-#endif
- }
- /* must have been the donau case we care about */
- break;
- }
-
- GNUNET_assert (off + num_blinded_sigs >= off);
- GNUNET_assert (off + num_blinded_sigs <= pc->output_tokens_len);
for (unsigned int i = 0; i<pc->output_tokens_len; i++)
{
struct SignedOutputToken *sot
- = &pc->output_tokens[off + i];
+ = &pc->output_tokens[i];
+
+ /* Only look at actual donau tokens. */
+ if (sot->output_index != donau_output_index)
+ continue;
// FIXME: in the future, use incref here once
// we change libdonau to do decref!
@@ -2831,7 +2812,14 @@ phase_execute_pay_transaction (struct PayContext *pc)
for (size_t i = 0; i<pc->output_tokens_len; i++)
{
- switch (choice->outputs[i].type)
+ unsigned int output_index;
+ enum TALER_MERCHANT_ContractOutputType type;
+
+ output_index = pc->output_tokens[i].output_index;
+ GNUNET_assert (output_index < choice->outputs_len);
+ type = choice->outputs[output_index].type;
+
+ switch (type)
{
case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
/* Well, good luck getting here */
@@ -2843,7 +2831,7 @@ phase_execute_pay_transaction (struct PayContext *pc)
"invalid output type"));
break;
case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
- /* We skip outputs of donation receipts here, as they are handled in the
+ /* We skip output tokens of donation receipts here, as they are handled in the
* phase_final_output_token_processing() callback from donau */
break;
case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
@@ -3486,6 +3474,9 @@ handle_output_donation_receipt (
static void
phase_validate_tokens (struct PayContext *pc)
{
+ /* We haven't seen a donau output yet. */
+ pc->validate_tokens.donau_output_index = -1;
+
switch (pc->check_contract.contract_terms->version)
{
case TALER_MERCHANT_CONTRACT_VERSION_0:
@@ -3503,7 +3494,6 @@ phase_validate_tokens (struct PayContext *pc)
pc->parse_wallet_data.choice_index];
unsigned int output_off;
unsigned int cnt;
- bool donau_seen = false;
pc->validate_tokens.max_fee = selected->max_fee;
pc->validate_tokens.brutto = selected->amount;
@@ -3593,7 +3583,7 @@ phase_validate_tokens (struct PayContext *pc)
break;
case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
/* check that this output type appears at most once */
- if (donau_seen)
+ if (pc->validate_tokens.donau_output_index >= 0)
{
/* This should have been prevented when the
contract was initially created */
@@ -3606,7 +3596,7 @@ phase_validate_tokens (struct PayContext *pc)
"two donau output sets in same contract"));
return;
}
- donau_seen = true;
+ pc->validate_tokens.donau_output_index = i;
#ifdef HAVE_DONAU_DONAU_SERVICE_H
if (output_off + pc->parse_wallet_data.num_bkps < output_off)
{
@@ -3625,11 +3615,40 @@ phase_validate_tokens (struct PayContext *pc)
}
}
+
pc->output_tokens_len = output_off;
pc->output_tokens
= GNUNET_new_array (pc->output_tokens_len,
struct SignedOutputToken);
+ /* calculate pc->output_tokens[].output_index */
+ output_off = 0;
+ for (unsigned int i = 0; i<selected->outputs_len; i++)
+ {
+ const struct TALER_MERCHANT_ContractOutput *output
+ = &selected->outputs[i];
+
+ cnt = 0;
+ switch (output->type)
+ {
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
+ GNUNET_assert (0);
+ break;
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
+ cnt = output->details.token.count;
+ output_off += cnt;
+ break;
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+ cnt = pc->parse_wallet_data.num_bkps;
+ output_off += pc->parse_wallet_data.num_bkps;
+#endif
+ break;
+ }
+ for (unsigned int j = 0; j<cnt; j++)
+ pc->output_tokens[output_off + j].output_index = i;
+ }
+
/* compute non-donau outputs */
output_off = 0;
for (unsigned int i = 0; i<selected->outputs_len; i++)
@@ -4910,6 +4929,10 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh,
return MHD_YES;
case PP_END_NO:
return MHD_NO;
+ default:
+ /* should not be reachable */
+ GNUNET_assert (0);
+ return MHD_NO;
}
switch (pc->suspended)
{