aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Blättler <blatc2@bfh.ch>2024-04-30 22:28:08 +0200
committerChristian Blättler <blatc2@bfh.ch>2024-04-30 22:28:08 +0200
commit4a3145fca389fca1bbfc7ba61b7cafd1156ef656 (patch)
treef95de17dcb3e20036dc3b6c0046cf5a2b18cc935
parent5d9db0012144cbad9ba90b654fdfff17b55b6af9 (diff)
work on tokens
-rw-r--r--src/backend/taler-merchant-httpd_contract.c44
-rw-r--r--src/backend/taler-merchant-httpd_contract.h22
-rw-r--r--src/backend/taler-merchant-httpd_post-orders-ID-pay.c235
-rw-r--r--src/backenddb/pg_lookup_token_family_key.c5
-rw-r--r--src/include/taler_merchant_service.h70
-rw-r--r--src/lib/merchant_api_post_order_pay.c104
-rw-r--r--src/testing/test_merchant_api.c6
-rw-r--r--src/testing/testing_api_cmd_claim_order.c1
-rw-r--r--src/testing/testing_api_cmd_pay_order.c337
9 files changed, 676 insertions, 148 deletions
diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c
index f05c97d5..e7c7ac49 100644
--- a/src/backend/taler-merchant-httpd_contract.c
+++ b/src/backend/taler-merchant-httpd_contract.c
@@ -308,8 +308,9 @@ parse_token_families (void *cls,
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_array_const ("keys",
&keys),
- GNUNET_JSON_spec_bool ("critical",
- &family.critical),
+ GNUNET_JSON_spec_bool ("critical",
+ &family.critical),
+ GNUNET_JSON_spec_end ()
/* TODO: Figure out if these fields should be 'const' */
// GNUNET_JSON_spec_string ("description",
// &family.description),
@@ -415,4 +416,43 @@ TALER_JSON_spec_token_families (const char *name,
};
return ret;
+}
+
+enum GNUNET_GenericReturnValue
+TMH_find_token_family_key (const char *slug,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct TALER_MerchantContractTokenFamily *families,
+ unsigned int families_len,
+ struct TALER_MerchantContractTokenFamily *family,
+ struct TALER_MerchantContractTokenFamilyKey *key)
+{
+ for (unsigned int i = 0; i < families_len; i++)
+ {
+ if (0 != strcmp (families[i].slug, slug))
+ {
+ continue;
+ }
+ if (NULL != family)
+ {
+ *family = families[i];
+ }
+ for (unsigned int k = 0; k < family->keys_len; k++)
+ {
+ if (GNUNET_TIME_timestamp_cmp(family->keys[k].valid_after,
+ ==,
+ valid_after))
+ {
+ if (NULL != key)
+ {
+ *key = family->keys[k];
+ }
+ return GNUNET_OK;
+ }
+ }
+ /* matching family found, but no key. */
+ return GNUNET_NO;
+ }
+
+ /* no matching family found */
+ return GNUNET_NO;
} \ No newline at end of file
diff --git a/src/backend/taler-merchant-httpd_contract.h b/src/backend/taler-merchant-httpd_contract.h
index 6c1841e9..006d0205 100644
--- a/src/backend/taler-merchant-httpd_contract.h
+++ b/src/backend/taler-merchant-httpd_contract.h
@@ -19,6 +19,7 @@
* @author Christian Blättler
*/
#include "taler-merchant-httpd.h"
+#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_time_lib.h>
#include <jansson.h>
@@ -614,3 +615,24 @@ struct GNUNET_JSON_Specification
TALER_JSON_spec_token_families (const char *name,
struct TALER_MerchantContractTokenFamily **families,
unsigned int *families_len);
+
+
+/**
+ * Find matching token family in @a families based on @a slug. Then use
+ * @a valid_after to find the matching public key within it.
+ *
+ * @param slug slug of the token family
+ * @param valid_after start time of the validity period of the key
+ * @param families array of token families to search in
+ * @param families_len length of the @a families array
+ * @param[out] family found family, set to NULL to only check for existence
+ * @param[out] key found key, set to NULL to only check for existence
+ * @return #GNUNET_OK on success #GNUNET_NO if no key was found
+ */
+enum GNUNET_GenericReturnValue
+TMH_find_token_family_key (const char *slug,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct TALER_MerchantContractTokenFamily *families,
+ unsigned int families_len,
+ struct TALER_MerchantContractTokenFamily *family,
+ struct TALER_MerchantContractTokenFamilyKey *key); \ No newline at end of file
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
index 1808989a..0dad53db 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -60,13 +60,13 @@
* TODO: What is a good value for this?
* Maximum number of tokens that we allow as inputs per transaction
*/
-#define MAX_TOKEN_ALLOWED_INPUT_TOKENS 128
+#define MAX_TOKEN_ALLOWED_INPUTs 128
/**
* TODO: What is a good value for this?
* Maximum number of tokens that we allow as outputs per transaction
*/
-#define MAX_TOKEN_ALLOWED_OUTPUT_TOKENS 128
+#define MAX_TOKEN_ALLOWED_OUTPUTs 128
/**
* How often do we ask the exchange again about our
@@ -287,6 +287,25 @@ struct TokenEnvelope
/**
+ * (Blindly) signed token to be returned to the wallet.
+ */
+struct SignedOutputToken
+{
+
+ /**
+ * Blinded token use public keys waiting to be signed.
+ */
+ struct TALER_TokenIssueBlindSignatureP sig;
+
+ /**
+ * Hash of token issue public key.
+ */
+ struct TALER_TokenIssuePublicKeyHashP h_issue;
+
+};
+
+
+/**
* Information kept during a pay request for each exchange.
*/
struct ExchangeGroup
@@ -365,7 +384,7 @@ struct PayContext
* Array with @e output_tokens_cnt signed tokens returned in
* the response to the wallet.
*/
- struct TALER_TokenIssueBlindSignatureP *output_tokens;
+ struct SignedOutputToken *output_tokens;
/**
* Array with @e token_envelopes_cnt (blinded) token envelopes.
@@ -1484,6 +1503,28 @@ phase_batch_deposits (struct PayContext *pc)
/**
+ * Build JSON array of blindly signed token envelopes,
+ * to be used in the response to the wallet.
+ *
+ * @param[in,out] pc payment context to use
+ */
+static json_t *
+build_token_sigs (struct PayContext *pc)
+{
+ json_t *token_sigs = json_array ();
+ for (unsigned int i = 0; i < pc->output_tokens_len; i++)
+ {
+ json_array_append_new (token_sigs, GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_blinded_sig ("blind_sig", pc->output_tokens[i].sig.signature),
+ GNUNET_JSON_pack_data_auto ("h_issue", &pc->output_tokens[i].h_issue)
+ ));
+ }
+
+ return token_sigs;
+}
+
+
+/**
* Generate response (payment successful)
*
* @param[in,out] pc payment context where the payment was successful
@@ -1493,6 +1534,7 @@ phase_success_response (struct PayContext *pc)
{
struct TALER_MerchantSignatureP sig;
char *pos_confirmation;
+ json_t *token_sigs;
/* Sign on our end (as the payment did go through, even if it may
have been refunded already) */
@@ -1506,6 +1548,9 @@ phase_success_response (struct PayContext *pc)
pc->pos_alg,
&pc->amount,
pc->timestamp);
+ token_sigs = (0 >= pc->output_tokens_len)
+ ? NULL
+ : build_token_sigs (pc);
pay_end (pc,
TALER_MHD_REPLY_JSON_PACK (
pc->connection,
@@ -1513,6 +1558,9 @@ phase_success_response (struct PayContext *pc)
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("pos_confirmation",
pos_confirmation)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("token_sigs",
+ token_sigs)),
GNUNET_JSON_pack_data_auto ("sig",
&sig)));
GNUNET_free (pos_confirmation);
@@ -2223,13 +2271,13 @@ find_valid_input_tokens (struct PayContext *pc,
return GNUNET_NO;
}
- if (GNUNET_OK != TALER_merchant_token_issue_verify (&tuc->pub,
- &key->pub,
- &tuc->unblinded_sig))
+ if (GNUNET_OK != TALER_token_issue_verify (&tuc->pub,
+ &key->pub,
+ &tuc->unblinded_sig))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Input token for public key with valid_after "
- "`%s' is invalid\n",
+ "`%s' has invalid issue signature\n",
GNUNET_TIME_timestamp2s (key->valid_after));
GNUNET_break (0);
pay_end (pc,
@@ -2299,9 +2347,11 @@ sign_token_envelopes (struct PayContext *pc,
continue;
}
- TALER_merchant_token_issue_sign (&pc->token_envelopes[i].blinded_token,
- priv,
- &pc->output_tokens[i]);
+ TALER_token_issue_sign (priv,
+ &pc->token_envelopes[i].blinded_token,
+ &pc->output_tokens[i].sig);
+
+ pc->output_tokens[i].h_issue.hash = pc->token_envelopes[i].h_issue.hash;
num_signed++;
}
@@ -2310,7 +2360,7 @@ sign_token_envelopes (struct PayContext *pc,
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Expected %d token envelopes for public key with valid_after "
- "`%s', but found %d\n",
+ "'%s', but found %d\n",
expected_num,
GNUNET_TIME_timestamp2s (key->valid_after),
num_signed);
@@ -2328,45 +2378,6 @@ sign_token_envelopes (struct PayContext *pc,
/**
- * Find metching token family based on @a slug. Then use @a valid_after to
- * find the matching public key.
- *
- * @param pc payment context
- * @param slug token family slug
- * @param valid_after valid_after timestamp
- * @param[out] family matching token family
- * @param[out] key matching public key
- */
-static void
-lookup_token_family_key (struct PayContext *pc,
- const char *slug,
- struct GNUNET_TIME_Timestamp valid_after,
- struct TALER_MerchantContractTokenFamily *family,
- struct TALER_MerchantContractTokenFamilyKey *key)
-{
- for (unsigned int i = 0; i < pc->token_families_len; i++)
- {
- if (0 != strcmp (pc->token_families[i].slug, slug))
- {
- continue;
- }
- *family = pc->token_families[i];
- for (unsigned int k = 0; k < family->keys_len; k++)
- {
- if (GNUNET_TIME_timestamp_cmp(family->keys[k].valid_after,
- ==,
- valid_after))
- {
- *key = family->keys[k];
- break;
- }
- }
- break;
- }
-}
-
-
-/**
* Validate tokens and token envelopes. First, we check if all tokens listed in
* the 'inputs' array of the selected choice are present in the 'tokens' array
* of the request. Then, we validate the signatures of each provided token.
@@ -2428,8 +2439,8 @@ phase_validate_tokens (struct PayContext *pc)
for (unsigned int i = 0; i<selected.inputs_len; i++)
{
struct TALER_MerchantContractInput input = selected.inputs[i];
- struct TALER_MerchantContractTokenFamily *family = NULL;
- struct TALER_MerchantContractTokenFamilyKey *key = NULL;
+ struct TALER_MerchantContractTokenFamily family;
+ struct TALER_MerchantContractTokenFamilyKey key;
if (input.type != TALER_MCIT_TOKEN)
{
@@ -2437,13 +2448,12 @@ phase_validate_tokens (struct PayContext *pc)
continue;
}
- lookup_token_family_key (pc,
- input.details.token.token_family_slug,
- input.details.token.valid_after,
- family,
- key);
-
- if (NULL == family || NULL == key)
+ if (GNUNET_OK != TMH_find_token_family_key (input.details.token.token_family_slug,
+ input.details.token.valid_after,
+ pc->token_families,
+ pc->token_families_len,
+ &family,
+ &key))
{
/* this should never happen, since the choices and
token families are validated on insert. */
@@ -2457,8 +2467,8 @@ phase_validate_tokens (struct PayContext *pc)
}
if (GNUNET_NO == find_valid_input_tokens (pc,
- key,
- input.details.token.count))
+ &key,
+ input.details.token.count))
{
/* Error is already scheduled from find_valid_input_token. */
return;
@@ -2474,8 +2484,8 @@ phase_validate_tokens (struct PayContext *pc)
enum GNUNET_DB_QueryStatus qs;
struct TALER_MERCHANTDB_TokenFamilyKeyDetails details;
struct TALER_MerchantContractOutput output = selected.outputs[i];
- struct TALER_MerchantContractTokenFamily *family = NULL;
- struct TALER_MerchantContractTokenFamilyKey *key = NULL;
+ struct TALER_MerchantContractTokenFamily family;
+ struct TALER_MerchantContractTokenFamilyKey key;
if (output.type != TALER_MCOT_TOKEN)
{
@@ -2483,13 +2493,12 @@ phase_validate_tokens (struct PayContext *pc)
continue;
}
- lookup_token_family_key (pc,
- output.details.token.token_family_slug,
- output.details.token.valid_after,
- family,
- key);
-
- if (NULL == family || NULL == key)
+ if (GNUNET_OK != TMH_find_token_family_key (output.details.token.token_family_slug,
+ output.details.token.valid_after,
+ pc->token_families,
+ pc->token_families_len,
+ &family,
+ &key))
{
/* this should never happen, since the choices and
token families are validated on insert. */
@@ -2504,25 +2513,27 @@ phase_validate_tokens (struct PayContext *pc)
qs = TMH_db->lookup_token_family_key (TMH_db->cls,
pc->hc->instance->settings.id,
- family->slug,
- key->valid_after,
- key->valid_after,
+ family.slug,
+ key.valid_after,
+ key.valid_after,
&details);
if (qs <= 0)
{
GNUNET_break (0);
pay_end (pc,
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- NULL));
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL));
return;
}
+ GNUNET_assert (NULL != details.priv.private_key);
+
if (GNUNET_OK != sign_token_envelopes (pc,
- family->slug,
- key,
+ family.slug,
+ &key,
&details.priv,
output.details.token.count))
{
@@ -2940,10 +2951,11 @@ phase_parse_wallet_data (struct PayContext *pc)
GNUNET_JSON_spec_int64 ("choice_index",
&pc->choice_index),
NULL),
- GNUNET_JSON_spec_mark_optional(
- GNUNET_JSON_spec_fixed_auto ("h_outputs",
- &pc->choice_index),
- NULL),
+ /* TODO: Add h_outputs to wallet_data */
+ // GNUNET_JSON_spec_mark_optional(
+ // GNUNET_JSON_spec_fixed_auto ("h_outputs",
+ // &pc->h_outputs),
+ // NULL),
GNUNET_JSON_spec_end ()
};
@@ -3032,6 +3044,7 @@ phase_parse_pay (struct PayContext *pc)
/* use empty string as default if client didn't specify it */
pc->session_id = GNUNET_strdup ("");
}
+
pc->coins_cnt = json_array_size (coins);
if (pc->coins_cnt > MAX_COIN_ALLOWED_COINS)
{
@@ -3044,30 +3057,10 @@ phase_parse_pay (struct PayContext *pc)
"'coins' array too long"));
return;
}
-
- pc->tokens_cnt = json_array_size (tokens);
- if (pc->tokens_cnt > MAX_TOKEN_ALLOWED_INPUT_TOKENS)
- {
- GNUNET_break_op (0);
- pay_end (pc,
- TALER_MHD_reply_with_error (
- pc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "'tokens' array too long"));
- return;
- }
-
/* note: 1 coin = 1 deposit confirmation expected */
pc->dc = GNUNET_new_array (pc->coins_cnt,
struct DepositConfirmation);
- pc->tokens = GNUNET_new_array (pc->tokens_cnt,
- struct TokenUseConfirmation);
-
- pc->token_envelopes = GNUNET_new_array (pc->token_envelopes_cnt,
- struct TokenEnvelope);
-
/* This loop populates the array 'dc' in 'pc' */
{
unsigned int coins_index;
@@ -3183,6 +3176,22 @@ phase_parse_pay (struct PayContext *pc)
}
}
+ pc->tokens_cnt = json_array_size (tokens);
+ if (pc->tokens_cnt > MAX_TOKEN_ALLOWED_INPUTs)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "'tokens' array too long"));
+ return;
+ }
+
+ pc->tokens = GNUNET_new_array (pc->tokens_cnt,
+ struct TokenUseConfirmation);
+
/* This look populates the array 'tokens' in 'pc' */
{
unsigned int tokens_index;
@@ -3235,6 +3244,22 @@ phase_parse_pay (struct PayContext *pc)
}
}
+ pc->token_envelopes_cnt = json_array_size (tokens_evs);
+ if (pc->token_envelopes_cnt > MAX_TOKEN_ALLOWED_OUTPUTs)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "'token_evs' array too long"));
+ return;
+ }
+
+ pc->token_envelopes = GNUNET_new_array (pc->token_envelopes_cnt,
+ struct TokenEnvelope);
+
{
unsigned int tokens_ev_index;
json_t *token_ev;
@@ -3246,7 +3271,9 @@ phase_parse_pay (struct PayContext *pc)
TALER_JSON_spec_token_envelope ("token_ev",
&ev->blinded_token),
GNUNET_JSON_spec_fixed_auto ("h_issue",
- &ev->h_issue.hash)};
+ &ev->h_issue.hash),
+ GNUNET_JSON_spec_end ()
+ };
enum GNUNET_GenericReturnValue res;
res = TALER_MHD_parse_json_data (pc->connection,
diff --git a/src/backenddb/pg_lookup_token_family_key.c b/src/backenddb/pg_lookup_token_family_key.c
index 33fc0b27..a0a5fab1 100644
--- a/src/backenddb/pg_lookup_token_family_key.c
+++ b/src/backenddb/pg_lookup_token_family_key.c
@@ -131,11 +131,12 @@ TMH_PG_lookup_token_family_key (void *cls,
" LEFT JOIN merchant_token_family_keys"
" ON merchant_token_families.token_family_serial = merchant_token_family_keys.token_family_serial"
" AND merchant_token_family_keys.valid_after >= $3"
- " AND merchant_token_family_keys.valid_after < $4"
+ " AND merchant_token_family_keys.valid_after <= $4" /* TODO: Should this be < instead of <=? */
" JOIN merchant_instances"
" USING (merchant_serial)"
" WHERE merchant_instances.merchant_id=$1"
" AND slug=$2"
+ " ORDER BY merchant_token_family_keys.valid_after ASC"
" LIMIT 1");
enum GNUNET_DB_QueryStatus qs;
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
@@ -161,4 +162,4 @@ TMH_PG_lookup_token_family_key (void *cls,
}
return qs;
-} \ No newline at end of file
+}
diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h
index e3d65188..1907f88f 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -3091,20 +3091,41 @@ TALER_MERCHANT_order_claim_cancel (struct TALER_MERCHANT_OrderClaimHandle *och);
*/
struct TALER_MERCHANT_PrivateTokenDetails
{
+
+ /**
+ * Master secret used to derive the private key from.
+ */
+ struct TALER_TokenUseMasterSecretP master;
+
/**
* Private key of the token.
*/
struct TALER_TokenUsePrivateKeyP token_priv;
/**
+ * Public key of the token.
+ */
+ struct TALER_TokenUsePublicKeyP token_pub;
+
+ /**
+ * Public key of the token.
+ */
+ struct TALER_TokenUsePublicKeyHashP h_token_pub;
+
+ /**
+ * Blinded public key of the token.
+ */
+ struct TALER_TokenEnvelopeP envelope;
+
+ /**
* Value used to blind the key for the signature.
*/
union GNUNET_CRYPTO_BlindingSecretP blinding_secret;
/**
- * Unblinded token issue signature made by the merchant.
+ * Inputs needed from the merchant for blind signing.
*/
- struct TALER_TokenIssueSignatureP issue_sig;
+ struct TALER_TokenUseMerchantValues blinding_inputs;
/**
* Token issue public key.
@@ -3112,9 +3133,14 @@ struct TALER_MERCHANT_PrivateTokenDetails
struct TALER_TokenIssuePublicKeyP issue_pub;
/**
- * Inputs needed from the merchant for blind signing.
+ * Unblinded token issue signature made by the merchant.
*/
- struct GNUNET_CRYPTO_BlindingInputValues *blinding_inputs;
+ struct TALER_TokenIssueSignatureP issue_sig;
+
+ /**
+ * Blinded token issue signature made by the merchant.
+ */
+ struct TALER_TokenIssueBlindSignatureP blinded_sig;
};
@@ -3164,7 +3190,7 @@ struct TALER_MERCHANT_PayResponse
/**
* Array of tokens that were issued for the payment.
*/
- struct TALER_MERCHANT_PrivateTokenDetails *tokens;
+ struct TALER_MERCHANT_OutputToken *tokens;
/**
* Length of the @e tokens array.
@@ -3276,6 +3302,32 @@ struct TALER_MERCHANT_UsedToken
/**
+ * Information the frontend forwards to the backend for an output token. The
+ * blinded issue signature is set once the request return with an HTTP_OK.
+ * This does not inlcude the blinding secret or any other private information.
+ */
+struct TALER_MERCHANT_OutputToken
+{
+
+ /**
+ * Token envelope.
+ */
+ struct TALER_TokenEnvelopeP envelope;
+
+ /**
+ * Hash of the corresponding token issue public key.
+ */
+ struct TALER_TokenIssuePublicKeyHashP h_issue;
+
+ /**
+ * Blinded issue signature made by the merchant.
+ */
+ struct TALER_TokenIssueBlindSignatureP blinded_sig;
+
+};
+
+
+/**
* Pay a merchant. API for frontends talking to backends. Here,
* the frontend does not have the coin's private keys, but just
* the public keys and signatures.
@@ -3292,6 +3344,8 @@ struct TALER_MERCHANT_UsedToken
* @param coins array of coins to pay with
* @param num_tokens length of the @a tokens array
* @param tokens array of tokens used
+ * @param num_output_tokens length of the @a output_tokens array
+ * @param output_tokens array of output token to be issued by the merchant
* @param pay_cb the callback to call when a reply for this request is available
* @param pay_cb_cls closure for @a pay_cb
* @return a handle for this request
@@ -3307,6 +3361,8 @@ TALER_MERCHANT_order_pay_frontend (
const struct TALER_MERCHANT_PaidCoin coins[static num_coins],
unsigned int num_tokens,
const struct TALER_MERCHANT_UsedToken tokens[static num_tokens],
+ unsigned int num_output_tokens,
+ const struct TALER_MERCHANT_OutputToken output_tokens[static num_output_tokens],
TALER_MERCHANT_OrderPayCallback pay_cb,
void *pay_cb_cls);
@@ -3407,6 +3463,8 @@ struct TALER_MERCHANT_UseToken
* @param coins array of coins we use to pay
* @param num_tokens number of tokens to used in this payment request
* @param tokens array of tokens we use in this payment request
+ * @param num_output_tokens length of the @a output_tokens array
+ * @param output_tokens array of output tokens to be issued by the merchant
* @param pay_cb the callback to call when a reply for this request is available
* @param pay_cb_cls closure for @a pay_cb
* @return a handle for this request
@@ -3431,6 +3489,8 @@ TALER_MERCHANT_order_pay (
const struct TALER_MERCHANT_PayCoin coins[static num_coins],
unsigned int num_tokens,
const struct TALER_MERCHANT_UseToken tokens[static num_tokens],
+ unsigned int num_output_tokens,
+ const struct TALER_MERCHANT_OutputToken output_tokens[static num_output_tokens],
TALER_MERCHANT_OrderPayCallback pay_cb,
void *pay_cb_cls);
diff --git a/src/lib/merchant_api_post_order_pay.c b/src/lib/merchant_api_post_order_pay.c
index c74113b6..39e4e35b 100644
--- a/src/lib/merchant_api_post_order_pay.c
+++ b/src/lib/merchant_api_post_order_pay.c
@@ -34,6 +34,7 @@
#include "taler_merchant_service.h"
#include "merchant_api_common.h"
#include "merchant_api_curl_defaults.h"
+#include <stdio.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_signatures.h>
#include <taler/taler_exchange_service.h>
@@ -82,11 +83,6 @@ struct TALER_MERCHANT_OrderPayHandle
struct TALER_MERCHANT_PaidCoin *coins;
/**
- * The tokens we are using as inputs.
- */
- struct TALER_MERCHANT_UsedToken *tokens;
-
- /**
* Hash of the contract we are paying, set
* if @e am_wallet is true.
*/
@@ -121,11 +117,6 @@ struct TALER_MERCHANT_OrderPayHandle
unsigned int num_coins;
/**
- * Number of @e tokens we are using.
- */
- unsigned int num_tokens;
-
- /**
* Set to true if this is the wallet API and we have
* initialized @e h_contract_terms and @e merchant_pub.
*/
@@ -136,10 +127,14 @@ struct TALER_MERCHANT_OrderPayHandle
/**
* Parse blindly signed output tokens from response.
+ *
+ * @param token_sigs the JSON array with the token signatures. Can be NULL.
+ * @param tokens where to store the parsed tokens.
+ * @param num_tokens where to store the length of the @a tokens array.
*/
static enum GNUNET_GenericReturnValue
parse_tokens (const json_t *token_sigs,
- struct TALER_MERCHANT_PrivateTokenDetails **tokens,
+ struct TALER_MERCHANT_OutputToken **tokens,
unsigned int *num_tokens)
{
GNUNET_array_grow (*tokens,
@@ -148,22 +143,25 @@ parse_tokens (const json_t *token_sigs,
for (unsigned int i = 0; i<(*num_tokens); i++)
{
- struct TALER_MERCHANT_PrivateTokenDetails *token = &(*tokens)[i];
- const json_t *js = json_array_get (token_sigs,
+ struct TALER_MERCHANT_OutputToken *token = &(*tokens)[i];
+ const json_t *jtoken = json_array_get (token_sigs,
i);
- if (NULL == js)
+ if (NULL == jtoken)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_token_issue_sig ("blind_sig",
- &token->issue_sig),
+ TALER_JSON_spec_blinded_token_issue_sig ("blind_sig",
+ &token->blinded_sig),
+ GNUNET_JSON_spec_fixed_auto ("h_issue",
+ &token->h_issue),
+ GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
- GNUNET_JSON_parse (js,
+ GNUNET_JSON_parse (jtoken,
spec,
NULL, NULL))
{
@@ -172,7 +170,7 @@ parse_tokens (const json_t *token_sigs,
}
}
- return GNUNET_NO;
+ return GNUNET_YES;
}
@@ -196,6 +194,14 @@ handle_pay_finished (void *cls,
.hr.reply = json
};
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received /pay response with status code %u\n",
+ (unsigned int) response_code);
+
+ json_dumpf (json,
+ stderr,
+ JSON_INDENT (2));
+
oph->job = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"/pay completed with response code %u\n",
@@ -235,19 +241,16 @@ handle_pay_finished (void *cls,
break;
}
- if (NULL != token_sigs)
+ if (GNUNET_OK !=
+ parse_tokens (token_sigs,
+ &pr.details.ok.tokens,
+ &pr.details.ok.num_tokens))
{
- if (GNUNET_OK !=
- parse_tokens (token_sigs,
- &pr.details.ok.tokens,
- &pr.details.ok.num_tokens))
- {
- GNUNET_break_op (0);
- pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- pr.hr.http_status = 0;
- pr.hr.hint = "failed to parse token_sigs field in response";
- break;
- }
+ GNUNET_break_op (0);
+ pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ pr.hr.http_status = 0;
+ pr.hr.hint = "failed to parse token_sigs field in response";
+ break;
}
if (GNUNET_OK !=
@@ -381,6 +384,8 @@ TALER_MERCHANT_order_pay_frontend (
const struct TALER_MERCHANT_PaidCoin coins[static num_coins],
unsigned int num_tokens,
const struct TALER_MERCHANT_UsedToken tokens[static num_tokens],
+ unsigned int num_output_tokens,
+ const struct TALER_MERCHANT_OutputToken output_tokens[static num_output_tokens],
TALER_MERCHANT_OrderPayCallback pay_cb,
void *pay_cb_cls)
{
@@ -388,6 +393,7 @@ TALER_MERCHANT_order_pay_frontend (
json_t *pay_obj;
json_t *j_coins;
json_t *j_tokens = NULL;
+ json_t *j_output_tokens = NULL;
CURL *eh;
struct TALER_Amount total_fee;
struct TALER_Amount total_amount;
@@ -496,6 +502,31 @@ TALER_MERCHANT_order_pay_frontend (
}
}
+ if (0 < num_output_tokens)
+ {
+ j_output_tokens = json_array ();
+ GNUNET_assert (NULL != j_output_tokens);
+ for (unsigned int i = 0; i<num_output_tokens; i++)
+ {
+ json_t *j_token_ev;
+ const struct TALER_MERCHANT_OutputToken *ev = &output_tokens[i];
+
+ j_token_ev = GNUNET_JSON_PACK (
+ TALER_JSON_pack_token_envelope ("token_ev",
+ &ev->envelope),
+ GNUNET_JSON_pack_data_auto ("h_issue",
+ &ev->h_issue.hash));
+ if (0 !=
+ json_array_append_new (j_output_tokens,
+ j_token_ev))
+ {
+ GNUNET_break (0);
+ json_decref (j_output_tokens);
+ return NULL;
+ }
+ }
+ }
+
pay_obj = GNUNET_JSON_PACK (
GNUNET_JSON_pack_array_steal ("coins",
j_coins),
@@ -503,12 +534,19 @@ TALER_MERCHANT_order_pay_frontend (
GNUNET_JSON_pack_array_steal ("tokens",
j_tokens)),
GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("tokens_evs",
+ j_output_tokens)),
+ GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("wallet_data",
(json_t *) wallet_data)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("session_id",
session_id)));
+ json_dumpf (pay_obj,
+ stderr,
+ JSON_INDENT (2));
+
oph = GNUNET_new (struct TALER_MERCHANT_OrderPayHandle);
oph->ctx = ctx;
oph->pay_cb = pay_cb;
@@ -538,6 +576,8 @@ TALER_MERCHANT_order_pay_frontend (
GNUNET_memcpy (oph->coins,
coins,
num_coins * sizeof (struct TALER_MERCHANT_PaidCoin));
+ /* TODO: Copy token_evs to pay handle so they
+ can be unblinded in the callback. */
eh = TALER_MERCHANT_curl_easy_get_ (oph->url);
if (GNUNET_OK !=
@@ -582,6 +622,8 @@ TALER_MERCHANT_order_pay (
const struct TALER_MERCHANT_PayCoin coins[static num_coins],
unsigned int num_tokens,
const struct TALER_MERCHANT_UseToken tokens[static num_tokens],
+ unsigned int num_output_tokens,
+ const struct TALER_MERCHANT_OutputToken output_tokens[static num_output_tokens],
TALER_MERCHANT_OrderPayCallback pay_cb,
void *pay_cb_cls)
{
@@ -678,6 +720,8 @@ TALER_MERCHANT_order_pay (
pc,
num_tokens,
ut,
+ num_output_tokens,
+ output_tokens,
pay_cb,
pay_cb_cls);
if (NULL == oph)
diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c
index 9f687588..9e9025ed 100644
--- a/src/testing/test_merchant_api.c
+++ b/src/testing/test_merchant_api.c
@@ -1725,11 +1725,11 @@ run (void *cls,
GNUNET_TIME_UNIT_ZERO_TS,
GNUNET_TIME_UNIT_FOREVER_TS,
"EUR:0.0"),
- TALER_TESTING_cmd_merchant_pay_order_choices ("pay-order-with-output",
+ TALER_TESTING_cmd_merchant_pay_order_choices ("pay-order-with-input-and-output",
merchant_url,
MHD_HTTP_OK,
- "create-order-with-output",
- "withdraw-coin-1",
+ "create-order-with-input-and-output",
+ "withdraw-coin-2",
"EUR:5",
"EUR:4.99",
NULL,
diff --git a/src/testing/testing_api_cmd_claim_order.c b/src/testing/testing_api_cmd_claim_order.c
index aec03876..353cd914 100644
--- a/src/testing/testing_api_cmd_claim_order.c
+++ b/src/testing/testing_api_cmd_claim_order.c
@@ -246,6 +246,7 @@ order_claim_traits (void *cls,
unsigned int index)
{
struct OrderClaimState *pls = cls;
+
struct TALER_TESTING_Trait traits[] = {
TALER_TESTING_make_trait_contract_terms (pls->contract_terms),
TALER_TESTING_make_trait_h_contract_terms (&pls->contract_terms_hash),
diff --git a/src/testing/testing_api_cmd_pay_order.c b/src/testing/testing_api_cmd_pay_order.c
index 12653ebc..143540e9 100644
--- a/src/testing/testing_api_cmd_pay_order.c
+++ b/src/testing/testing_api_cmd_pay_order.c
@@ -24,7 +24,11 @@
*/
#include "platform.h"
#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_time_lib.h>
#include <jansson.h>
+#include <stddef.h>
+#include <stdint.h>
#include <taler/taler_exchange_service.h>
#include <taler/taler_testing_lib.h>
#include <taler/taler_signatures.h>
@@ -131,10 +135,132 @@ struct PayState
*/
const json_t *wallet_data;
+ /**
+ * Index of the choice to be used in the payment. -1 for orders without choices.
+ */
+ int choice_index;
+
};
/**
+ * Find the token issue public key for a given token family @a slug and
+ * @a valid_after timestamp.
+ *
+ * @param token_families json object of token families where the key is the slug
+ * @param slug the slug of the token family
+ * @param valid_after the timestamp of the token family
+ * @param[out] pub the token issue public key of the token family
+ * @return #GNUNET_OK on success and #GNUNET_SYSERR if not found
+ */
+static enum GNUNET_GenericReturnValue
+find_token_public_key (const json_t *token_families,
+ const char *slug,
+ struct GNUNET_TIME_Timestamp valid_after,
+ struct TALER_TokenIssuePublicKeyP *pub)
+{
+ const json_t *tf = json_object_get (token_families, slug);
+ const json_t *keys;
+
+ if (NULL == tf)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Token family `%s' not found\n",
+ slug);
+ return GNUNET_SYSERR;
+ }
+
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("keys",
+ &keys),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (tf,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse token family `%s'\n",
+ slug);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ unsigned int i;
+ const json_t *key;
+
+ json_array_foreach (keys, i, key)
+ {
+ int64_t cipher;
+ struct GNUNET_TIME_Timestamp ivalid_after;
+ struct GNUNET_CRYPTO_BlindSignPublicKey *issue_pub = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey);
+ const char *error_name;
+ unsigned int error_line;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_pub",
+ &issue_pub->pub_key_hash),
+ GNUNET_JSON_spec_rsa_public_key ("rsa_pub",
+ &issue_pub->details.rsa_public_key),
+ // GNUNET_JSON_spec_fixed_auto ("cs_pub",
+ // &key.pub.public_key->details.cs_public_key)),
+ GNUNET_JSON_spec_int64 ("cipher",
+ &cipher),
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &ivalid_after),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (key,
+ ispec,
+ &error_name,
+ &error_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s at %u: %s\n",
+ ispec[error_line].field,
+ error_line,
+ error_name);
+ return GNUNET_SYSERR;
+ }
+
+ switch (cipher) {
+ case GNUNET_CRYPTO_BSA_RSA:
+ issue_pub->cipher = GNUNET_CRYPTO_BSA_RSA;
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ issue_pub->cipher = GNUNET_CRYPTO_BSA_CS;
+ break;
+ case GNUNET_CRYPTO_BSA_INVALID:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Field 'cipher' invalid in key #%u\n",
+ i);
+ return GNUNET_SYSERR;
+ }
+
+ /* Compare valid_after to make sure it matches. */
+ if (GNUNET_TIME_timestamp_cmp(valid_after, !=, ivalid_after))
+ {
+ continue;
+ }
+
+ pub->public_key = issue_pub;
+ return GNUNET_OK;
+ }
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Key with valid_after '%s' for token family '%s' not found\n",
+ GNUNET_TIME_timestamp2s(valid_after),
+ slug);
+ return GNUNET_SYSERR;
+}
+
+
+/**
* Parse the @a coins specification and grow the @a pc
* array with the coins found, updating @a npc.
*
@@ -376,8 +502,55 @@ pay_cb (void *cls,
if (MHD_HTTP_OK == pr->hr.http_status)
{
ps->merchant_sig = pr->details.ok.merchant_sig;
- ps->issued_tokens = pr->details.ok.tokens;
- ps->num_issued_tokens = pr->details.ok.num_tokens;
+ if (ps->num_issued_tokens != pr->details.ok.num_tokens)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected number of tokens issued. "
+ "Sent %d envelopes but got %d tokens issued.\n",
+ ps->num_issued_tokens,
+ pr->details.ok.num_tokens);
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ps->is);
+ return;
+ }
+ for (unsigned int i = 0; i < ps->num_issued_tokens; i++)
+ {
+ struct TALER_MERCHANT_PrivateTokenDetails *details =
+ &ps->issued_tokens[i];
+ /* The issued tokens should be in the
+ same order as the provided envelopes. */
+ if (1 == GNUNET_CRYPTO_hash_cmp(
+ &details->issue_pub.public_key->pub_key_hash,
+ &pr->details.ok.tokens[i].h_issue.hash))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected token issued. "
+ "Expected token with hash %s but got token with hash %s.\n",
+ GNUNET_h2s (&details->issue_pub.public_key->pub_key_hash),
+ GNUNET_h2s (&pr->details.ok.tokens[i].h_issue.hash));
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ps->is);
+ return;
+ }
+
+ ps->issued_tokens[i].blinded_sig = pr->details.ok.tokens[i].blinded_sig;
+
+
+ if (GNUNET_OK !=
+ TALER_token_issue_sig_unblind (&details->issue_sig,
+ &details->blinded_sig,
+ &details->blinding_secret,
+ &details->h_token_pub,
+ &details->blinding_inputs,
+ &details->issue_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to unblind token signature\n");
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (ps->is);
+ return;
+ }
+ }
if (NULL != ps->pos_key)
{
char *pc;
@@ -442,12 +615,16 @@ pay_run (void *cls,
struct TALER_MerchantWireHashP h_wire;
const struct TALER_PrivateContractHashP *h_proposal;
struct TALER_Amount max_fee;
+ const json_t *choices = NULL;
+ const json_t *token_families = NULL;
const char *error_name = NULL;
unsigned int error_line = 0;
struct TALER_MERCHANT_PayCoin *pay_coins;
unsigned int npay_coins;
struct TALER_MERCHANT_UseToken *use_tokens = NULL;
unsigned int len_use_tokens = 0;
+ struct TALER_MERCHANT_OutputToken *output_tokens = NULL;
+ unsigned int len_output_tokens = 0;
const struct TALER_MerchantSignatureP *merchant_sig;
const enum TALER_MerchantConfirmationAlgorithm *alg_ptr;
@@ -494,6 +671,14 @@ pay_run (void *cls,
&ps->total_amount),
TALER_JSON_spec_amount_any ("max_fee",
&max_fee),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("token_families",
+ &token_families),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("choices",
+ &choices),
+ NULL),
/* FIXME oec: parse minimum age, use data later? */
GNUNET_JSON_spec_end ()
};
@@ -559,6 +744,151 @@ pay_run (void *cls,
}
GNUNET_free (tr);
}
+ if (0 <= ps->choice_index)
+ {
+ const json_t *outputs;
+ json_t *output;
+ unsigned int output_index;
+ const json_t *choice = json_array_get (choices, ps->choice_index);
+
+ if (NULL == choice)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No choice found at index %d\n",
+ ps->choice_index);
+ TALER_TESTING_FAIL (is);
+ }
+
+ {
+ const char *ierror_name = NULL;
+ unsigned int ierror_line = 0;
+
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_array_const ("outputs",
+ &outputs),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (choice,
+ ispec,
+ &ierror_name,
+ &ierror_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Parser failed on %s:%u for input `%s'\n",
+ ierror_name,
+ ierror_line,
+ json_dumps (choice,
+ JSON_INDENT (2)));
+ TALER_TESTING_FAIL (is);
+ }
+ }
+
+ json_array_foreach (outputs, output_index, output)
+ {
+ const char *slug;
+ const char *kind;
+ struct GNUNET_TIME_Timestamp valid_after;
+ uint32_t count = 1;
+ const char *ierror_name = NULL;
+ unsigned int ierror_line = 0;
+
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("kind",
+ &kind),
+ GNUNET_JSON_spec_string ("token_family_slug",
+ &slug),
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &valid_after),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("count",
+ &count),
+ NULL),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (output,
+ ispec,
+ &ierror_name,
+ &ierror_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Parser failed on %s:%u for input `%s'\n",
+ ierror_name,
+ ierror_line,
+ json_dumps (output,
+ JSON_INDENT (2)));
+ TALER_TESTING_FAIL (is);
+ }
+
+ if (0 != strcmp("token", kind))
+ {
+ continue;
+ }
+
+ GNUNET_array_grow (ps->issued_tokens,
+ ps->num_issued_tokens,
+ ps->num_issued_tokens + count);
+
+ for (unsigned int k = 0; k < count; k++)
+ {
+ struct TALER_MERCHANT_PrivateTokenDetails *details =
+ &ps->issued_tokens[ps->num_issued_tokens - count + k];
+
+ if (GNUNET_OK != find_token_public_key (token_families,
+ slug,
+ valid_after,
+ &details->issue_pub))
+ {
+ TALER_TESTING_FAIL (is);
+ }
+
+ /* Only RSA is supported for now. */
+ GNUNET_assert (GNUNET_CRYPTO_BSA_RSA == details->issue_pub.public_key->cipher);
+
+ TALER_token_blind_input_copy (&details->blinding_inputs,
+ TALER_token_bling_input_rsa_singleton ());
+ /* TODO: Where to get details->blinding_inputs from? */
+ TALER_token_use_setup_random (&details->master);
+ TALER_token_use_setup_priv (&details->master,
+ &details->blinding_inputs,
+ &details->token_priv);
+ TALER_token_use_blinding_secret_create (&details->master,
+ &details->blinding_inputs,
+ &details->blinding_secret);
+ GNUNET_CRYPTO_eddsa_key_get_public (&details->token_priv.private_key,
+ &details->token_pub.public_key);
+ GNUNET_CRYPTO_hash (&details->token_pub.public_key,
+ sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
+ &details->h_token_pub.hash);
+ details->envelope.blinded_pub = GNUNET_CRYPTO_message_blind_to_sign (
+ details->issue_pub.public_key,
+ &details->blinding_secret,
+ NULL, /* TODO: Add session nonce to support CS tokens */
+ &details->h_token_pub.hash,
+ sizeof (details->h_token_pub.hash),
+ details->blinding_inputs.blinding_inputs);
+
+ if (NULL == details->envelope.blinded_pub)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_FAIL (is);
+ }
+ }
+ }
+ }
+
+ GNUNET_array_grow (output_tokens,
+ len_output_tokens,
+ ps->num_issued_tokens);
+ for (unsigned int i = 0; i<len_output_tokens; i++)
+ {
+ output_tokens[i].h_issue.hash = ps->issued_tokens[i].issue_pub.public_key->pub_key_hash;
+ output_tokens[i].envelope.blinded_pub = ps->issued_tokens[i].envelope.blinded_pub;
+ }
+
if (GNUNET_OK !=
TALER_TESTING_get_trait_merchant_sig (proposal_cmd,
&merchant_sig))
@@ -588,6 +918,8 @@ pay_run (void *cls,
pay_coins,
len_use_tokens,
use_tokens,
+ len_output_tokens,
+ output_tokens,
&pay_cb,
ps);
GNUNET_array_grow (pay_coins,
@@ -733,6 +1065,7 @@ TALER_TESTING_cmd_merchant_pay_order_choices (const char *label,
ps->amount_without_fee = amount_without_fee;
ps->session_id = session_id;
ps->token_reference = token_reference;
+ ps->choice_index = choice_index;
if (0 <= choice_index)
{
ps->wallet_data = json_pack ("{s:i}",