[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-merchant] branch master updated (2e8cfbe8 -> 7d63433c)
From: |
gnunet |
Subject: |
[taler-merchant] branch master updated (2e8cfbe8 -> 7d63433c) |
Date: |
Thu, 13 Jun 2024 17:03:09 +0200 |
This is an automated email from the git hooks/post-receive script.
christian-blaettler pushed a change to branch master
in repository merchant.
from 2e8cfbe8 bump merchant SPA lockfile to 0.11.4
new b15e3ce1 add choice_index to private get orders id
new a41b9cce fix compiler warning
new b41d4002 add sql migration to add choice_index in db
new 8a169633 use better error code
new 4ff7bb25 fix hash length constraint
new 0d296088 check hash before inserting
new 8415d33b remove todo
new 4fdbc050 add new migration to makefile
new 4f00d126 adapt to changed spec and fix key handling
new 8e8634c9 fix choice index type
new 105974b5 proper date rounding & fix db key extraction
new fe9a1da3 rename keys
new e25dc0e9 parse tokens in pay handler
new 602693be fix timestamp in order creation test
new be9027b1 fix serialized json format of choices
new e3965464 pay: fetch and parse choices from db
new fa4322c4 work on pay handler
new 10c796e8 rename signature
new cb751157 parse and hash wallet data in pay handler
new 4f097dd6 fix set_token_family memory leaks
new 1f4f26ad add todo
new ae672cad fix choices length check
new e83caaa1 fix memory management in case of error
new 169ece9d extend merchant lib to allow tokens as inputs
new e491838f extend testing lib to support paying for orders with choices
new 81bb90b4 add pay command to tokens test case (which will currently
fail)
new ceecb31a build tokens array in test command
new 1739fb42 set valid_after timestamp to rounded value of matching key
new 6365debb tokens, not coins 🙃
new 0df606b9 fix token family key parsing
new d50651cd work on testing orders with tokens
new 27ab6516 pass wallet data hash to deposit signature validation
new 5d9db001 validate and sign token envelopes
new 4a3145fc work on tokens
new d1461b24 work on tokens
new 44ef77be misc
new 1c42a826 add test for double-spending a token
new 84df99d0 store used tokens in database and prevent double using a token
new 3fa00294 only allow creation of token families with validity period in
the future
new 6f4486ff check validity period of token families and keys
new 6466a990 check validity period of token issue key
new 11776f2c fix token family validity duration check
new b1a3c1cb allow for orders with no coins (zero price)
new 30313105 store issued tokens in database
new 99c500b4 fix tests
new 9a1cd913 check tokens on idempotent pay request
new 25ae6b68 comment out idempotent pay test for now
new b9315cf4 rename spent_tokens —> used_tokens
new 599db2c1 Merge branch 'master' into tokens-payment
new fdcf3f09 add rounding to token_families in database
new 7d63433c add rounding and remove h_issue
The 51 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
src/backend/taler-merchant-httpd.c | 4 +
src/backend/taler-merchant-httpd_contract.c | 415 ++++++++-
src/backend/taler-merchant-httpd_contract.h | 184 ++--
.../taler-merchant-httpd_post-orders-ID-pay.c | 941 ++++++++++++++++++++-
...taler-merchant-httpd_private-delete-orders-ID.c | 5 +-
.../taler-merchant-httpd_private-get-orders-ID.c | 26 +-
...erchant-httpd_private-get-token-families-SLUG.c | 7 +-
.../taler-merchant-httpd_private-post-orders.c | 649 +++++++++-----
...er-merchant-httpd_private-post-token-families.c | 53 +-
src/backenddb/Makefile.am | 5 +
src/backenddb/merchant-0006.sql | 10 +
.../{merchant-0004.sql => merchant-0007.sql} | 18 +-
src/backenddb/merchant-0008.sql | 52 ++
..._delete_category.c => pg_insert_issued_token.c} | 46 +-
...ert_token_family.h => pg_insert_issued_token.h} | 25 +-
src/backenddb/pg_insert_spent_token.c | 72 ++
...g_select_category.h => pg_insert_spent_token.h} | 31 +-
src/backenddb/pg_insert_token_family.c | 4 +-
src/backenddb/pg_insert_token_family_key.c | 23 +-
src/backenddb/pg_insert_token_family_key.h | 6 +-
src/backenddb/pg_lookup_contract_terms3.c | 15 +-
src/backenddb/pg_lookup_contract_terms3.h | 6 +-
src/backenddb/pg_lookup_spent_tokens_by_order.c | 162 ++++
...y_order.h => pg_lookup_spent_tokens_by_order.h} | 26 +-
src/backenddb/pg_lookup_token_family_key.c | 194 ++---
src/backenddb/pg_lookup_token_family_key.h | 5 +-
src/backenddb/plugin_merchantdb_postgres.c | 11 +
src/backenddb/test_merchantdb.c | 4 +-
src/include/taler_merchant_service.h | 166 +++-
src/include/taler_merchant_testing_lib.h | 43 +-
src/include/taler_merchantdb_plugin.h | 97 ++-
src/lib/merchant_api_post_order_pay.c | 206 ++++-
src/lib/merchant_api_post_tokenfamilies.c | 3 +
src/testing/test_merchant_api.c | 101 ++-
src/testing/test_merchant_order_creation.sh | 2 +-
src/testing/testing_api_cmd_claim_order.c | 1 +
src/testing/testing_api_cmd_pay_order.c | 510 ++++++++++-
src/testing/testing_api_cmd_post_orders.c | 23 +-
src/testing/testing_api_cmd_post_tokenfamilies.c | 8 +
39 files changed, 3614 insertions(+), 545 deletions(-)
copy src/backenddb/{merchant-0004.sql => merchant-0007.sql} (65%)
create mode 100644 src/backenddb/merchant-0008.sql
copy src/backenddb/{pg_delete_category.c => pg_insert_issued_token.c} (50%)
copy src/backenddb/{pg_insert_token_family.h => pg_insert_issued_token.h} (55%)
create mode 100644 src/backenddb/pg_insert_spent_token.c
copy src/backenddb/{pg_select_category.h => pg_insert_spent_token.h} (50%)
create mode 100644 src/backenddb/pg_lookup_spent_tokens_by_order.c
copy src/backenddb/{pg_lookup_deposits_by_order.h =>
pg_lookup_spent_tokens_by_order.h} (60%)
diff --git a/src/backend/taler-merchant-httpd.c
b/src/backend/taler-merchant-httpd.c
index e9ee1cbf..a7c1cbb4 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -2264,6 +2264,10 @@ run (void *cls,
return;
}
+ /* TODO: Load config variables for merchant token family
+ cipher type "rsa" or "cs" and key size.
+ Defaults to "rsa" and 2048 bits. */
+
if (GNUNET_OK !=
TALER_CONFIG_parse_currencies (cfg,
&TMH_num_cspecs,
diff --git a/src/backend/taler-merchant-httpd_contract.c
b/src/backend/taler-merchant-httpd_contract.c
index 38c82e70..e7c7ac49 100644
--- a/src/backend/taler-merchant-httpd_contract.c
+++ b/src/backend/taler-merchant-httpd_contract.c
@@ -19,11 +19,13 @@
* @author Christian Blättler
*/
#include "platform.h"
+#include <gnunet/gnunet_common.h>
#include <jansson.h>
+#include <stdint.h>
#include "taler-merchant-httpd_contract.h"
enum TALER_MerchantContractInputType
-TMH_string_to_contract_input_type (const char *str)
+TMH_contract_input_type_from_string (const char *str)
{
/* For now, only 'token' is the only supported option. */
if (0 == strcmp("token", str))
@@ -35,7 +37,7 @@ TMH_string_to_contract_input_type (const char *str)
}
enum TALER_MerchantContractOutputType
-TMH_string_to_contract_output_type (const char *str)
+TMH_contract_output_type_from_string (const char *str)
{
/* For now, only 'token' is the only supported option. */
if (0 == strcmp("token", str))
@@ -45,3 +47,412 @@ TMH_string_to_contract_output_type (const char *str)
return TALER_MCOT_INVALID;
}
+
+const char *
+TMH_string_from_contract_input_type (enum TALER_MerchantContractInputType t)
+{
+ switch (t) {
+ case TALER_MCIT_TOKEN:
+ return "token";
+ case TALER_MCIT_COIN:
+ return "coin";
+ default:
+ return "invalid";
+ }
+}
+
+const char *
+TMH_string_from_contract_output_type (enum TALER_MerchantContractOutputType t)
+{
+ switch (t) {
+ case TALER_MCOT_TOKEN:
+ return "token";
+ case TALER_MCOT_COIN:
+ return "coin";
+ case TALER_MCOT_TAX_RECEIPT:
+ return "tax_receipt";
+ default:
+ return "invalid";
+ }
+}
+
+/**
+ * Parse given JSON object to choices array.
+ *
+ * @param cls closure, pointer to array length
+ * @param root the json array representing the choices
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_choices (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_MerchantContractChoice **choices = spec->ptr;
+ unsigned int *choices_len = cls;
+ if (!json_is_array (root))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_array_grow (*choices,
+ *choices_len,
+ json_array_size (root));
+
+ for (unsigned int i = 0; i<*choices_len; i++)
+ {
+ const json_t *jinputs;
+ const json_t *joutputs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("inputs",
+ &jinputs),
+ GNUNET_JSON_spec_array_const ("outputs",
+ &joutputs),
+ GNUNET_JSON_spec_end ()
+ };
+ const char *error_name;
+ unsigned int error_line;
+ struct TALER_MerchantContractChoice *choice = &(*choices)[i];
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (root, i),
+ spec,
+ &error_name,
+ &error_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s at %u: %s\n",
+ spec[error_line].field,
+ error_line,
+ error_name);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ const json_t *jinput;
+ size_t idx;
+ json_array_foreach ((json_t *) jinputs, idx, jinput)
+ {
+ struct TALER_MerchantContractInput input = {.details.token.count = 1};
+ const char *kind;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("kind",
+ &kind),
+ GNUNET_JSON_spec_string ("token_family_slug",
+ &input.details.token.token_family_slug),
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &input.details.token.valid_after),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("count",
+ &input.details.token.count),
+ NULL),
+ GNUNET_JSON_spec_end()
+ };
+ const char *ierror_name;
+ unsigned int ierror_line;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jinput,
+ ispec,
+ &ierror_name,
+ &ierror_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s at %u: %s\n",
+ spec[ierror_line].field,
+ ierror_line,
+ ierror_name);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ input.type = TMH_contract_input_type_from_string (kind);
+
+ if (TALER_MCIT_INVALID == input.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Field 'kind' invalid in input #%u\n",
+ (unsigned int) idx);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 == input.details.token.count)
+ {
+ /* Ignore inputs with 'number' field set to 0 */
+ continue;
+ }
+
+ GNUNET_array_append (choice->inputs,
+ choice->inputs_len,
+ input);
+ }
+ }
+
+ {
+ const json_t *joutput;
+ size_t idx;
+ json_array_foreach ((json_t *) joutputs, idx, joutput)
+ {
+ struct TALER_MerchantContractOutput output = {.details.token.count =
1};
+ const char *kind;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("kind",
+ &kind),
+ GNUNET_JSON_spec_string ("token_family_slug",
+ &output.details.token.token_family_slug),
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &output.details.token.valid_after),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("count",
+ &output.details.token.count),
+ NULL),
+ GNUNET_JSON_spec_end()
+ };
+ const char *ierror_name;
+ unsigned int ierror_line;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (joutput,
+ ispec,
+ &ierror_name,
+ &ierror_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s at %u: %s\n",
+ spec[ierror_line].field,
+ ierror_line,
+ ierror_name);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ output.type = TMH_contract_output_type_from_string (kind);
+
+ if (TALER_MCOT_INVALID == output.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Field 'kind' invalid in output #%u\n",
+ (unsigned int) idx);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 == output.details.token.count)
+ {
+ /* Ignore outputs with 'number' field set to 0 */
+ continue;
+ }
+
+ GNUNET_array_append (choice->outputs,
+ choice->outputs_len,
+ output);
+ }
+ }
+ }
+
+ return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_choices (const char *name,
+ struct TALER_MerchantContractChoice **choices,
+ unsigned int *choices_len)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .cls = (void *) choices_len,
+ .parser = &parse_choices,
+ .field = name,
+ .ptr = choices,
+ };
+
+ return ret;
+}
+
+
+/**
+ * Parse given JSON object to token families array.
+ *
+ * @param cls closure, pointer to array length
+ * @param root the json object representing the token families. The keys are
+ * the token family slugs.
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_token_families (void *cls,
+ json_t *root,
+ struct GNUNET_JSON_Specification *spec)
+{
+ struct TALER_MerchantContractTokenFamily **families = spec->ptr;
+ unsigned int *families_len = cls;
+ json_t *jfamily;
+ const char *slug;
+ if (!json_is_object (root))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ json_object_foreach(root, slug, jfamily)
+ {
+ const json_t *keys;
+ struct TALER_MerchantContractTokenFamily family = {
+ .slug = slug
+ };
+
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("keys",
+ &keys),
+ 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),
+ // GNUNET_JSON_spec_object_const ("description_i18n",
+ // &family.description_i18n),
+ };
+ const char *error_name;
+ unsigned int error_line;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jfamily,
+ spec,
+ &error_name,
+ &error_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s at %u: %s\n",
+ spec[error_line].field,
+ error_line,
+ error_name);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_array_grow (family.keys,
+ family.keys_len,
+ json_array_size (keys));
+
+ for (unsigned int i = 0; i<family.keys_len; i++)
+ {
+ /* TODO: Move this to TALER_JSON_spec_token_issue_key */
+ int64_t cipher;
+ struct TALER_MerchantContractTokenFamilyKey *key;
+ key = &family.keys[i];
+ /* TODO: Free when not used anymore */
+ key->pub.public_key = GNUNET_new (struct
GNUNET_CRYPTO_BlindSignPublicKey);
+ struct GNUNET_JSON_Specification key_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_pub",
+ &key->pub.public_key->pub_key_hash),
+ GNUNET_JSON_spec_rsa_public_key ("rsa_pub",
+
&key->pub.public_key->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",
+ &key->valid_after),
+ GNUNET_JSON_spec_timestamp ("valid_before",
+ &key->valid_before),
+ GNUNET_JSON_spec_end()
+ };
+ const char *ierror_name;
+ unsigned int ierror_line;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json_array_get (keys, i),
+ key_spec,
+ &ierror_name,
+ &ierror_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to parse %s at %u: %s\n",
+ key_spec[ierror_line].field,
+ ierror_line,
+ ierror_name);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ switch (cipher) {
+ case GNUNET_CRYPTO_BSA_RSA:
+ key->pub.public_key->cipher = GNUNET_CRYPTO_BSA_RSA;
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ key->pub.public_key->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);
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ GNUNET_array_append (*families, *families_len, family);
+ }
+
+ return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_token_families (const char *name,
+ struct TALER_MerchantContractTokenFamily
**families,
+ unsigned int *families_len)
+{
+ struct GNUNET_JSON_Specification ret = {
+ .cls = (void *) families_len,
+ .parser = &parse_token_families,
+ .field = name,
+ .ptr = families,
+ };
+
+ 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 b1e3938c..6389cc70 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>
@@ -251,102 +252,92 @@ struct TALER_MerchantContractChoice
unsigned int outputs_len;
};
-struct TALER_MerchantContractLimits
+/**
+ * Public key and corresponding metadata for a token family.
+ */
+struct TALER_MerchantContractTokenFamilyKey
{
/**
- * Currency these limits are for.
+ * Public key.
*/
- char currency[TALER_CURRENCY_LEN];
+ struct TALER_TokenIssuePublicKeyP pub;
/**
- * The hash of the merchant instance's wire details.
+ * Tokens signed by this key will be valid after this time.
*/
- struct TALER_MerchantWireHashP h_wire;
+ struct GNUNET_TIME_Timestamp valid_after;
/**
- * Wire transfer method identifier for the wire method associated with
``h_wire``.
- * The wallet may only select exchanges via a matching auditor if the
- * exchange also supports this wire method.
- * The wire transfer fees must be added based on this wire transfer method.
+ * Tokens signed by this key will be valid before this time.
*/
- char *wire_method;
+ struct GNUNET_TIME_Timestamp valid_before;
+};
+struct TALER_MerchantContractTokenFamily
+{
/**
- * Maximum total deposit fee accepted by the merchant for this contract.
+ * Slug of the token family.
*/
- struct TALER_Amount max_fee;
-};
+ const char *slug;
-struct TALER_MerchantContractTokenAuthority
-{
/**
- * Label of the token authority.
- */
- const char *label;
+ * Human-readable name of the token family.
+ */
+ char *name;
/**
- * Human-readable description of the semantics of the tokens issued by
- * this authority.
- */
+ * Human-readable description of the semantics of the tokens issued by
+ * this token family.
+ */
char *description;
/**
- * Map from IETF BCP 47 language tags to localized description.
- */
+ * Map from IETF BCP 47 language tags to localized description.
+ */
json_t *description_i18n;
/**
- * Public key used to validate tokens signed by this authority.
- */
- struct TALER_TokenFamilyPublicKey *pub;
+ * Relevant public keys of this token family for the given contract.
+ */
+ struct TALER_MerchantContractTokenFamilyKey *keys;
/**
- * When will tokens signed by this key expire?
- */
- struct GNUNET_TIME_Timestamp token_expiration;
+ * Length of the @e keys array.
+ */
+ unsigned int keys_len;
/**
- * Must a wallet understand this token type to process contracts that
- * consume or yield it?
- */
+ * Must a wallet understand this token type to process contracts that
+ * consume or yield it?
+ */
bool critical;
/**
- * Kind of the token.
- */
+ * Kind of the token family.
+ */
enum TALER_MerchantContractTokenKind kind;
/**
- * Kind-specific information about the token.
- */
+ * Kind-specific information about the token.
+ */
union
{
/**
- * Subscription token.
- */
+ * Subscription token.
+ */
struct
{
/**
- * When does the subscription period start?
- */
- struct GNUNET_TIME_Timestamp start_date;
-
- /**
- * When does the subscription period end?
- */
- struct GNUNET_TIME_Timestamp end_date;
-
- /**
- * Array of domain names where this subscription can be safely used
- * (e.g. the issuer warrants that these sites will re-issue tokens of
- * this type if the respective contract says so). May contain "*" for
- * any domain or subdomain.
- */
+ * Array of domain names where this subscription can be safely used
+ * (e.g. the issuer warrants that these sites will re-issue tokens of
+ * this type if the respective contract says so). May contain "*" for
+ * any domain or subdomain.
+ */
const char **trusted_domains;
/**
- * Length of the @e trusted_domains array.
- */
+ * Length of the @e trusted_domains array.
+ */
unsigned int trusted_domains_len;
} subscription;
@@ -356,19 +347,19 @@ struct TALER_MerchantContractTokenAuthority
struct
{
/**
- * Array of domain names where this discount token is intended to be
- * used. May contain "*" for any domain or subdomain. Users should be
- * warned about sites proposing to consume discount tokens of this
- * type that are not in this list that the merchant is accepting a
- * coupon from a competitor and thus may be attaching different
- * semantics (like get 20% discount for my competitors 30% discount
- * token).
- */
+ * Array of domain names where this discount token is intended to be
+ * used. May contain "*" for any domain or subdomain. Users should be
+ * warned about sites proposing to consume discount tokens of this
+ * type that are not in this list that the merchant is accepting a
+ * coupon from a competitor and thus may be attaching different
+ * semantics (like get 20% discount for my competitors 30% discount
+ * token).
+ */
const char **expected_domains;
/**
- * Length of the @e expected_domains array.
- */
+ * Length of the @e expected_domains array.
+ */
unsigned int expected_domains_len;
} discount;
@@ -540,7 +531,7 @@ struct TALER_MerchantContract
/**
* Array of token authorities.
*/
- struct TALER_MerchantContractTokenAuthority *token_authorities;
+ struct TALER_MerchantContractTokenFamily *token_authorities;
/**
* Length of the @e token_authorities array.
@@ -556,10 +547,16 @@ struct TALER_MerchantContract
};
enum TALER_MerchantContractInputType
-TMH_string_to_contract_input_type (const char *str);
+TMH_contract_input_type_from_string (const char *str);
enum TALER_MerchantContractOutputType
-TMH_string_to_contract_output_type (const char *str);
+TMH_contract_output_type_from_string (const char *str);
+
+const char *
+TMH_string_from_contract_input_type (enum TALER_MerchantContractInputType t);
+
+const char *
+TMH_string_from_contract_output_type (enum TALER_MerchantContractOutputType t);
/**
* Serialize @a contract to a JSON object, ready to be stored in the database.
@@ -589,4 +586,53 @@ enum GNUNET_GenericReturnValue
TMH_serialize_contract_v1 (const struct TALER_MerchantContract *contract,
const struct TMH_MerchantInstance *instance,
json_t *exchanges,
- json_t **out);
\ No newline at end of file
+ json_t **out);
+
+/**
+ * Provide specification to parse given JSON array to an array
+ * of contract choices.
+ *
+ * @param name name of the choices field in the JSON
+ * @param[out] choices pointer to the first element of the array
+ * @param[out] choices_len pointer to the length of the array
+ * @return spec for parsing a choices array
+ */
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_choices (const char *name,
+ struct TALER_MerchantContractChoice **choices,
+ unsigned int *choices_len);
+
+/**
+ * Provide specification to parse given JSON object to an array
+ * of token families.
+ *
+ * @param name name of the token families field in the JSON
+ * @param[out] families pointer to the first element of the array
+ * @param[out] families_len pointer to the length of the array
+ * @return spec for parsing a token families object
+ */
+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 14edfd55..08e4b6d8 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -25,14 +25,27 @@
* @author Florian Dold
*/
#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_db_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_time_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
#include <taler/taler_dbevents.h>
+#include <taler/taler_error_codes.h>
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_exchange_service.h>
+#include "taler-merchant-httpd.h"
#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_contract.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_post-orders-ID-pay.h"
#include "taler-merchant-httpd_private-get-orders.h"
+#include "taler_merchantdb_plugin.h"
/**
@@ -45,6 +58,16 @@
*/
#define MAX_COIN_ALLOWED_COINS 1024
+/**
+ * Maximum number of tokens that we allow as inputs per transaction
+ */
+#define MAX_TOKEN_ALLOWED_INPUTs 128
+
+/**
+ * Maximum number of tokens that we allow as outputs per transaction
+ */
+#define MAX_TOKEN_ALLOWED_OUTPUTs 128
+
/**
* How often do we ask the exchange again about our
* KYC status? Very rarely, as if the user actively
@@ -68,11 +91,21 @@ enum PayPhase
*/
PP_INIT = 0,
+ /**
+ * Parse wallet data object from the pay request.
+ */
+ PP_PARSE_WALLET_DATA,
+
/**
* Check database state for the given order.
*/
PP_CHECK_CONTRACT,
+ /**
+ * Validate provided tokens and token evelopes.
+ */
+ PP_VALIDATE_TOKENS,
+
/**
* Contract has been paid.
*/
@@ -202,6 +235,71 @@ struct DepositConfirmation
};
+struct TokenUseConfirmation
+{
+
+ /**
+ * Signature on the deposit request made using the token use private key.
+ */
+ struct TALER_TokenUseSignatureP sig;
+
+ /**
+ * Token use public key. This key was blindly signed by the merchant during
+ * the token issuance process.
+ */
+ struct TALER_TokenUsePublicKeyP pub;
+
+ /**
+ * Unblinded signature on the token use public key done by the merchant.
+ */
+ struct TALER_TokenIssueSignatureP unblinded_sig;
+
+ /**
+ * Hash of the token issue public key associated with this token.
+ * Note this is set in the validate_tokens phase.
+ */
+ struct TALER_TokenIssuePublicKeyHashP h_issue;
+
+ /**
+ * true if we found this token in the database.
+ */
+ bool found_in_db;
+
+};
+
+
+/**
+ * Information about a token envelope.
+ */
+struct TokenEnvelope
+{
+
+ /**
+ * Blinded token use public keys waiting to be signed.
+ */
+ struct TALER_TokenEnvelopeP blinded_token;
+
+};
+
+
+/**
+ * (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.
@@ -273,6 +371,32 @@ struct PayContext
*/
struct DepositConfirmation *dc;
+ /**
+ * Array with @e tokens_cnt input tokens passed to this request.
+ */
+ struct TokenUseConfirmation *tokens;
+
+ /**
+ * Array with @e output_tokens_cnt signed tokens returned in
+ * the response to the wallet.
+ */
+ struct SignedOutputToken *output_tokens;
+
+ /**
+ * Array with @e token_envelopes_cnt (blinded) token envelopes.
+ */
+ struct TokenEnvelope *token_envelopes;
+
+ /**
+ * Array with @e choices_len choices from the contract terms.
+ */
+ struct TALER_MerchantContractChoice *choices;
+
+ /**
+ * Array with @e token_families_len token families from the contract terms.
+ */
+ struct TALER_MerchantContractTokenFamily *token_families;
+
/**
* MHD connection to return to
*/
@@ -301,11 +425,32 @@ struct PayContext
*/
struct MHD_Response *response;
+ /**
+ * Index of selected choice in the @e contract_terms choices array.
+ */
+ int64_t choice_index;
+
/**
* Our contract (or NULL if not available).
*/
json_t *contract_terms;
+ /**
+ * Wallet data json object from the request. Containing additional
+ * wallet data such as the selected choice_index.
+ */
+ const json_t *wallet_data;
+
+ /**
+ * Hash of the canonicalized wallet data json object.
+ */
+ struct GNUNET_HashCode h_wallet_data;
+
+ /**
+ * Output commitment hash calculated from the 'tokens_evs' field of the
request.
+ */
+ struct GNUNET_HashCode h_outputs;
+
/**
* Placeholder for #TALER_MHD_parse_post_json() to keep its internal state.
*/
@@ -420,6 +565,34 @@ struct PayContext
*/
size_t coins_cnt;
+ /**
+ * Number of input tokens passed to this request. Length
+ * of the @e tokens array.
+ */
+ size_t tokens_cnt;
+
+ /**
+ * Number of token envelopes passed to this request.
+ * Length of the @e token_envelopes array.
+ */
+ size_t token_envelopes_cnt;
+
+ /**
+ * Number of output tokens to return in the response.
+ * Length of the @e output_tokens array.
+ */
+ unsigned int output_tokens_len;
+
+ /**
+ * Length of the @e choices array.
+ */
+ unsigned int choices_len;
+
+ /**
+ * Length of the @e token_families array.
+ */
+ unsigned int token_families_len;
+
/**
* Number of exchanges involved in the payment. Length
* of the @e eg array.
@@ -1137,6 +1310,7 @@ AGE_FAIL:
.merchant_payto_uri = pc->wm->payto_uri,
.wire_salt = pc->wm->wire_salt,
.h_contract_terms = pc->h_contract_terms,
+ .wallet_data_hash = pc->h_wallet_data,
.wallet_timestamp = pc->timestamp,
.merchant_pub = hc->instance->merchant_pub,
.refund_deadline = pc->refund_deadline
@@ -1324,6 +1498,27 @@ 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)
+ ));
+ }
+
+ return token_sigs;
+}
+
+
/**
* Generate response (payment successful)
*
@@ -1334,6 +1529,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) */
@@ -1347,6 +1543,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,
@@ -1354,6 +1553,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);
@@ -1851,6 +2053,47 @@ phase_execute_pay_transaction (struct PayContext *pc)
return;
}
+ for (size_t i = 0; i<pc->tokens_cnt; i++)
+ {
+ struct TokenUseConfirmation *tuc = &pc->tokens[i];
+
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Insert used token into database, the unique contraint will
+ case an error if this token was used before. */
+ qs = TMH_db->insert_spent_token (TMH_db->cls,
+ &pc->h_contract_terms,
+ &tuc->h_issue,
+ &tuc->pub,
+ &tuc->sig,
+ &tuc->unblinded_sig);
+
+ if (0 > qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return; /* do it again */
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert used token"));
+ return;
+ }
+ else if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ /* UNIQUE constraint violation, meaning this token was already used. */
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_CONFLICT,
+
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_INVALID,
+ NULL));
+ return;
+ }
+ }
+
{
enum GNUNET_DB_QueryStatus qs;
@@ -1968,6 +2211,34 @@ phase_execute_pay_transaction (struct PayContext *pc)
}
}
+ /* Store signed output tokens in database. */
+ for (size_t i = 0; i<pc->output_tokens_len; i++)
+ {
+ struct SignedOutputToken *output = &pc->output_tokens[i];
+
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->insert_issued_token (TMH_db->cls,
+ &pc->h_contract_terms,
+ &output->h_issue,
+ &output->sig);
+
+ if (0 >= qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return; /* do it again */
+ /* Always report on hard error as well to enable diagnostics */
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert output token"));
+ return;
+ }
+ }
+
TMH_notify_order_change (hc->instance,
TMH_OSF_CLAIMED | TMH_OSF_PAID,
pc->timestamp,
@@ -2025,6 +2296,346 @@ phase_execute_pay_transaction (struct PayContext *pc)
}
+/**
+ * Ensures that the expected number of tokens for a @e key
+ * are provided as inputs and have valid signatures.
+ */
+static enum GNUNET_GenericReturnValue
+find_valid_input_tokens (struct PayContext *pc,
+ struct TALER_MerchantContractTokenFamilyKey *key,
+ unsigned int index,
+ unsigned int expected_num)
+{
+ int num_validated = 0;
+ struct TokenUseConfirmation *tuc = NULL;
+
+ for (unsigned int j = 0; j < expected_num; j++)
+ {
+ tuc = &pc->tokens[index + j];
+
+ if (NULL == tuc)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Input token for public key with "
+ "valid_after `%s' not found\n",
+ GNUNET_TIME_timestamp2s (key->valid_after));
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "'tokens' array is missing "
+ "required input token"));
+ return GNUNET_NO;
+ }
+
+ tuc->h_issue.hash = key->pub.public_key->pub_key_hash;
+
+ 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' has invalid issue signature\n",
+ GNUNET_TIME_timestamp2s (key->valid_after));
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_ISSUE_SIG_INVALID,
+ NULL));
+ return GNUNET_NO;
+ }
+
+ if (GNUNET_OK != TALER_wallet_token_use_verify (&pc->h_contract_terms,
+ &pc->h_wallet_data,
+ &tuc->pub,
+ &tuc->sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Input token for public key with valid_after "
+ "`%s' has invalid use signature\n",
+ GNUNET_TIME_timestamp2s (key->valid_after));
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_USE_SIG_INVALID,
+ NULL));
+ return GNUNET_NO;
+ }
+
+ num_validated++;
+ }
+
+ if (num_validated != expected_num)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Expected %d tokens for public key with valid_after "
+ "`%s', but found %d\n",
+ expected_num,
+ GNUNET_TIME_timestamp2s (key->valid_after),
+ num_validated);
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_COUNT_MISMATCH,
+ NULL));
+ return GNUNET_NO;
+ }
+
+ return GNUNET_YES;
+}
+
+
+static enum GNUNET_GenericReturnValue
+sign_token_envelopes (struct PayContext *pc,
+ struct TALER_MerchantContractTokenFamilyKey *key,
+ struct TALER_TokenIssuePrivateKeyP *priv,
+ unsigned int index,
+ unsigned int expected_num)
+{
+ int num_signed = 0;
+
+ for (unsigned int j = 0; j<expected_num; j++)
+ {
+ unsigned int pos = index + j;
+
+ /* TODO: Handle missing envelopes for non-critical output tokens. */
+ if (pos > pc->token_envelopes_cnt || pos > pc->output_tokens_len) {
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "Token envelope array is missing "
+ "required token envelope"));
+ return GNUNET_NO;
+ }
+
+ struct TokenEnvelope *env = &pc->token_envelopes[index + j];
+ struct SignedOutputToken *output = &pc->output_tokens[index + j];
+
+ TALER_token_issue_sign (priv,
+ &env->blinded_token,
+ &output->sig);
+
+ output->h_issue.hash = key->pub.public_key->pub_key_hash;
+
+ num_signed++;
+ }
+
+ if (num_signed != expected_num)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Expected %d token envelopes for public key with valid_after "
+ "'%s', but found %d\n",
+ expected_num,
+ GNUNET_TIME_timestamp2s (key->valid_after),
+ num_signed);
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_ENVELOPE_COUNT_MISMATCH,
+ NULL));
+ return GNUNET_NO;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * 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.
+ *
+ * @param[in,out] pc context we use to handle the payment
+ */
+static void
+phase_validate_tokens (struct PayContext *pc)
+{
+ if (NULL == pc->choices || 0 >= pc->choices_len)
+ {
+ /* No tokens to validate */
+ pc->phase = PP_PAY_TRANSACTION;
+ return;
+ }
+
+ if (pc->choice_index < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order `%s' has non-empty choices array but"
+ "request is missing 'choice_index' field\n",
+ pc->order_id);
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_CHOICE_INDEX_MISSING,
+ NULL));
+ return;
+ }
+
+ if (pc->choice_index >= pc->choices_len)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order `%s' has choices array with %u elements but "
+ "request has 'choice_index' field with value %ld\n",
+ pc->order_id,
+ pc->choices_len,
+ pc->choice_index);
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_CHOICE_INDEX_OUT_OF_BOUNDS,
+ NULL));
+ return;
+ }
+
+ {
+ struct TALER_MerchantContractChoice selected;
+
+ selected = pc->choices[pc->choice_index];
+
+ for (unsigned int i = 0; i<selected.inputs_len; i++)
+ {
+ struct TALER_MerchantContractInput input = selected.inputs[i];
+ struct TALER_MerchantContractTokenFamily family;
+ struct TALER_MerchantContractTokenFamilyKey key;
+
+ if (input.type != TALER_MCIT_TOKEN)
+ {
+ /* only validate inputs of type token (for now) */
+ continue;
+ }
+
+ /* TODO: Replace this with ordering convention. */
+ 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. */
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL));
+ return;
+ }
+
+ struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
+
+ /* Ensure tokens signed by this key are valid at the current time. */
+ if (GNUNET_TIME_timestamp_cmp (key.valid_after, >, now) ||
+ GNUNET_TIME_timestamp_cmp (key.valid_before, <=, now))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Token family key validity period from %s to %s "
+ "is not valid at the current time\n",
+ GNUNET_TIME_timestamp2s (key.valid_after),
+ GNUNET_TIME_timestamp2s (key.valid_before));
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_CONFLICT,
+
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_INVALID,
+ NULL));
+ return;
+ }
+
+ if (GNUNET_NO == find_valid_input_tokens (pc,
+ &key,
+ i,
+ input.details.token.count))
+ {
+ /* Error is already scheduled from find_valid_input_token. */
+ return;
+ }
+ }
+
+ GNUNET_array_grow (pc->output_tokens,
+ pc->output_tokens_len,
+ selected.outputs_len);
+
+ for (unsigned int i = 0; i<selected.outputs_len; i++)
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_MERCHANTDB_TokenFamilyKeyDetails details;
+ struct TALER_MerchantContractOutput output = selected.outputs[i];
+ struct TALER_MerchantContractTokenFamily family;
+ struct TALER_MerchantContractTokenFamilyKey key;
+
+ if (output.type != TALER_MCOT_TOKEN)
+ {
+ /* only validate outputs of type tokens (for now) */
+ continue;
+ }
+
+ 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. */
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL));
+ return;
+ }
+
+ qs = TMH_db->lookup_token_family_key (TMH_db->cls,
+ pc->hc->instance->settings.id,
+ 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));
+ return;
+ }
+
+ GNUNET_assert (NULL != details.priv.private_key);
+
+ if (GNUNET_OK != sign_token_envelopes (pc,
+ &key,
+ &details.priv,
+ i,
+ output.details.token.count))
+ {
+ /* Error is already scheduled from sign_token_envelopes. */
+ return;
+ }
+
+ }
+ }
+
+ pc->phase = PP_PAY_TRANSACTION;
+}
+
+
/**
* Function called with information about a coin that was deposited.
* Checks if this coin is in our list of deposits as well.
@@ -2075,6 +2686,35 @@ deposit_paid_check (
}
+static void
+input_tokens_paid_check (
+ void *cls,
+ uint64_t spent_token_serial,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_TokenIssuePublicKeyHashP *h_issue_pub,
+ const struct TALER_TokenUsePublicKeyP *use_pub,
+ const struct TALER_TokenUseSignatureP *use_sig,
+ const struct TALER_TokenIssueSignatureP *issue_sig)
+{
+ struct PayContext *pc = cls;
+
+ for (size_t i = 0; i<pc->tokens_cnt; i++)
+ {
+ struct TokenUseConfirmation *tuc = &pc->tokens[i];
+
+ if ( (0 ==
+ GNUNET_memcmp (&tuc->pub, use_pub)) &&
+ (0 ==
+ GNUNET_memcmp (&tuc->sig, use_sig)) &&
+ (0 ==
+ GNUNET_memcmp (&tuc->unblinded_sig, issue_sig)) )
+ {
+ tuc->found_in_db = true;
+ break;
+ }
+ }
+}
+
/**
* Handle case where contract was already paid. Either decides
* the payment is idempotent, or refunds the excess payment.
@@ -2084,31 +2724,64 @@ deposit_paid_check (
static void
phase_contract_paid (struct PayContext *pc)
{
- enum GNUNET_DB_QueryStatus qs;
- bool unmatched = false;
json_t *refunds;
+ bool unmatched = false;
- qs = TMH_db->lookup_deposits_by_order (TMH_db->cls,
- pc->order_serial,
- &deposit_paid_check,
- pc);
- 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,
- "lookup_deposits_by_order"));
- return;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->lookup_deposits_by_order (TMH_db->cls,
+ pc->order_serial,
+ &deposit_paid_check,
+ pc);
+ /* Since orders with choices can have a price of zero,
+ 0 is also a valid query state */
+ 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,
+ "lookup_deposits_by_order"));
+ return;
+ }
}
- for (size_t i = 0; i<pc->coins_cnt; i++)
+ for (size_t i = 0; i<pc->coins_cnt && !unmatched; i++)
{
struct DepositConfirmation *dci = &pc->dc[i];
if (! dci->matched_in_db)
unmatched = true;
}
+ /* Check if provided input tokens match token in the database */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* TODO: Use h_contract instead of order_serial here? */
+ qs = TMH_db->lookup_spent_tokens_by_order (TMH_db->cls,
+ pc->order_serial,
+ &input_tokens_paid_check,
+ pc);
+
+ 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,
+ "lookup_spent_tokens_by_order"));
+ return;
+ }
+ }
+ for (size_t i = 0; i<pc->tokens_cnt && !unmatched; i++)
+ {
+ struct TokenUseConfirmation *tuc = &pc->tokens[i];
+
+ if (! tuc->found_in_db)
+ unmatched = true;
+ }
if (! unmatched)
{
/* Everything fine, idempotent request */
@@ -2120,6 +2793,7 @@ phase_contract_paid (struct PayContext *pc)
TALER_merchant_pay_sign (&pc->h_contract_terms,
&pc->hc->instance->merchant_priv,
&sig);
+ /* TODO: Add token_sigs to response body. */
pay_end (pc,
TALER_MHD_REPLY_JSON_PACK (
pc->connection,
@@ -2129,6 +2803,8 @@ phase_contract_paid (struct PayContext *pc)
return;
}
/* Conflict, double-payment detected! */
+ /* TODO: What should we do with input tokens?
+ Currently there is no refund for tokens. */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Client attempted to pay extra for already paid order `%s'\n",
pc->order_id);
@@ -2288,6 +2964,16 @@ phase_check_contract (struct PayContext *pc)
&pc->pay_deadline),
GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
&pc->wire_transfer_deadline),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_choices ("choices",
+ &pc->choices,
+ &pc->choices_len),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_token_families ("token_families",
+ &pc->token_families,
+ &pc->token_families_len),
+ NULL),
GNUNET_JSON_spec_fixed_auto ("h_wire",
&pc->h_wire),
GNUNET_JSON_spec_mark_optional (
@@ -2390,7 +3076,72 @@ phase_check_contract (struct PayContext *pc)
}
pc->wm = wm;
}
- pc->phase = PP_PAY_TRANSACTION;
+ pc->phase = PP_VALIDATE_TOKENS;
+}
+
+
+/**
+ * Try to parse the wallet_data object of the pay request into
+ * the given context. Schedules an error response in the connection
+ * on failure.
+ *
+ * @param[in,out] pc context we use to handle the payment
+ */
+static void
+phase_parse_wallet_data (struct PayContext *pc)
+{
+ struct GNUNET_HashCode h_outputs_req;
+ pc->choice_index = -1;
+
+ // TODO: Ensure that wallet_data must be set for contracts with choices.
+ if (NULL == pc->wallet_data)
+ {
+ pc->phase = PP_CHECK_CONTRACT;
+ return;
+ }
+
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional(
+ GNUNET_JSON_spec_int64 ("choice_index",
+ &pc->choice_index),
+ NULL),
+ GNUNET_JSON_spec_mark_optional(
+ GNUNET_JSON_spec_fixed_auto ("h_outputs",
+ &h_outputs_req),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (pc->connection,
+ pc->wallet_data,
+ spec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO);
+ return;
+ }
+
+ if (0 != GNUNET_CRYPTO_hash_cmp(&h_outputs_req, &pc->h_outputs))
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "'wallet_data.h_outputs' does not
match hash of tokens_evs"));
+ return;
+ }
+
+ TALER_json_hash (pc->wallet_data,
+ &pc->h_wallet_data);
+
+ pc->phase = PP_CHECK_CONTRACT;
}
@@ -2405,6 +3156,8 @@ phase_parse_pay (struct PayContext *pc)
{
const char *session_id = NULL;
const json_t *coins;
+ const json_t *tokens;
+ const json_t *tokens_evs;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_array_const ("coins",
&coins),
@@ -2412,6 +3165,18 @@ phase_parse_pay (struct PayContext *pc)
GNUNET_JSON_spec_string ("session_id",
&session_id),
NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("wallet_data",
+ &pc->wallet_data),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("tokens",
+ &tokens),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("tokens_evs",
+ &tokens_evs),
+ NULL),
GNUNET_JSON_spec_end ()
};
@@ -2443,6 +3208,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)
{
@@ -2455,7 +3221,6 @@ phase_parse_pay (struct PayContext *pc)
"'coins' array too long"));
return;
}
-
/* note: 1 coin = 1 deposit confirmation expected */
pc->dc = GNUNET_new_array (pc->coins_cnt,
struct DepositConfirmation);
@@ -2574,7 +3339,141 @@ phase_parse_pay (struct PayContext *pc)
}
}
}
- pc->phase = PP_CHECK_CONTRACT;
+
+ 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;
+ json_t *token;
+
+ json_array_foreach (tokens, tokens_index, token)
+ {
+ struct TokenUseConfirmation *tuc = &pc->tokens[tokens_index];
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto ("token_sig",
+ &tuc->sig),
+ GNUNET_JSON_spec_fixed_auto ("token_pub",
+ &tuc->pub),
+ TALER_JSON_spec_token_issue_sig ("ub_sig",
+ &tuc->unblinded_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (pc->connection,
+ token,
+ ispec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO);
+ return;
+ }
+
+ for (unsigned int j = 0; j<tokens_index; j++)
+ {
+ if (0 ==
+ GNUNET_memcmp (&tuc->pub,
+ &pc->tokens[j].pub))
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+
TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "duplicate token in list"));
+ return;
+ }
+ }
+ }
+ }
+
+ 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,
+ "'tokens_evs' array too long"));
+ return;
+ }
+ if (0 < pc->token_envelopes_cnt)
+ {
+ /* Calculate output commitment to be verified later. */
+ TALER_json_hash (tokens_evs,
+ &pc->h_outputs);
+
+ }
+
+ pc->token_envelopes = GNUNET_new_array (pc->token_envelopes_cnt,
+ struct TokenEnvelope);
+
+ {
+ unsigned int tokens_ev_index;
+ json_t *token_ev;
+
+ json_array_foreach (tokens_evs, tokens_ev_index, token_ev)
+ {
+ struct TokenEnvelope *ev = &pc->token_envelopes[tokens_ev_index];
+ struct GNUNET_JSON_Specification ispec[] = {
+ TALER_JSON_spec_token_envelope ("token_ev",
+ &ev->blinded_token),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (pc->connection,
+ token_ev,
+ ispec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO);
+ return;
+ }
+
+ for (unsigned int j = 0; j<tokens_ev_index; j++)
+ {
+ if (0 ==
+ GNUNET_memcmp (ev->blinded_token.blinded_pub,
+ pc->token_envelopes[j].blinded_token.blinded_pub))
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+
TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "duplicate token envelope in
list"));
+ return;
+ }
+ }
+ }
+ }
+ pc->phase = PP_PARSE_WALLET_DATA;
}
@@ -2660,12 +3559,18 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler
*rh,
case PP_INIT:
phase_parse_pay (pc);
break;
+ case PP_PARSE_WALLET_DATA:
+ phase_parse_wallet_data (pc);
+ break;
case PP_CHECK_CONTRACT:
phase_check_contract (pc);
break;
case PP_CONTRACT_PAID:
phase_contract_paid (pc);
break;
+ case PP_VALIDATE_TOKENS:
+ phase_validate_tokens (pc);
+ break;
case PP_PAY_TRANSACTION:
phase_execute_pay_transaction (pc);
break;
diff --git a/src/backend/taler-merchant-httpd_private-delete-orders-ID.c
b/src/backend/taler-merchant-httpd_private-delete-orders-ID.c
index cd8aa10c..f288ee05 100644
--- a/src/backend/taler-merchant-httpd_private-delete-orders-ID.c
+++ b/src/backend/taler-merchant-httpd_private-delete-orders-ID.c
@@ -20,6 +20,7 @@
*/
#include "platform.h"
#include "taler-merchant-httpd_private-delete-orders-ID.h"
+#include <stdint.h>
#include <taler/taler_json_lib.h>
@@ -80,6 +81,7 @@ TMH_private_delete_orders_ID (const struct TMH_RequestHandler
*rh,
bool paid = false;
bool wired = false;
bool matches = false;
+ int16_t choice_index;
qs = TMH_db->lookup_order (TMH_db->cls,
mi->settings.id,
@@ -98,7 +100,8 @@ TMH_private_delete_orders_ID (const struct
TMH_RequestHandler *rh,
&paid,
&wired,
&matches,
- NULL);
+ NULL,
+ &choice_index);
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
return TALER_MHD_reply_with_error (connection,
diff --git a/src/backend/taler-merchant-httpd_private-get-orders-ID.c
b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
index 98653997..50626876 100644
--- a/src/backend/taler-merchant-httpd_private-get-orders-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
@@ -22,6 +22,9 @@
#include "platform.h"
#include "taler-merchant-httpd_private-get-orders-ID.h"
#include "taler-merchant-httpd_get-orders-ID.h"
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <stdint.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_dbevents.h>
#include "taler-merchant-httpd_mhd.h"
@@ -315,6 +318,12 @@ struct GetOrderRequestContext
*/
uint64_t order_serial;
+ /**
+ * Index of selected choice from ``choices`` array in the contract_terms.
+ * Is -1 for orders without choices.
+ */
+ int16_t choice_index;
+
/**
* Total refunds granted for this payment. Only initialized
* if @e refunded is set to true.
@@ -610,6 +619,7 @@ phase_fetch_contract (struct GetOrderRequestContext *gorc)
gorc->order_only = false;
}
TMH_db->preflight (TMH_db->cls);
+ /* TODO: Check if choice_index is actually set to NULL if not in db. */
qs = TMH_db->lookup_contract_terms3 (TMH_db->cls,
hc->instance->settings.id,
hc->infix,
@@ -619,7 +629,8 @@ phase_fetch_contract (struct GetOrderRequestContext *gorc)
&gorc->paid,
&gorc->wired,
&gorc->paid_session_matches,
- &gorc->claim_token);
+ &gorc->claim_token,
+ &gorc->choice_index);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"lookup_contract_terms (%s) returned %d\n",
hc->infix,
@@ -1376,6 +1387,7 @@ phase_reply_result (struct GetOrderRequestContext *gorc)
struct TMH_HandlerContext *hc = gorc->hc;
MHD_RESULT ret;
char *order_status_url;
+ json_t *choice_index = json_null();
{
struct TALER_PrivateContractHashP *h_contract = NULL;
@@ -1403,6 +1415,12 @@ phase_reply_result (struct GetOrderRequestContext *gorc)
TALER_amount_is_zero (&gorc->contract_amount));
gorc->last_payment = gorc->timestamp;
}
+ if (-1 != gorc->choice_index) {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Choice index is %d\n",
+ gorc->choice_index);
+ choice_index = json_integer ((json_int_t) gorc->choice_index);
+ }
ret = TALER_MHD_REPLY_JSON_PACK (
gorc->sc.con,
MHD_HTTP_OK,
@@ -1440,7 +1458,11 @@ phase_reply_result (struct GetOrderRequestContext *gorc)
GNUNET_JSON_pack_array_steal ("refund_details",
gorc->refund_details),
GNUNET_JSON_pack_string ("order_status_url",
- order_status_url));
+ order_status_url),
+ {
+ .field_name = "choice_index",
+ .object = choice_index,
+ });
GNUNET_free (order_status_url);
gorc->wire_details = NULL;
gorc->refund_details = NULL;
diff --git a/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c
b/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c
index 06dbbdf9..12e57a99 100644
--- a/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c
+++ b/src/backend/taler-merchant-httpd_private-get-token-families-SLUG.c
@@ -20,6 +20,7 @@
*/
#include "platform.h"
#include "taler-merchant-httpd_private-get-token-families-SLUG.h"
+#include <gnunet/gnunet_json_lib.h>
#include <taler/taler_json_lib.h>
@@ -84,6 +85,7 @@ TMH_private_get_tokenfamilies_SLUG (const struct
TMH_RequestHandler *rh,
result = TALER_MHD_REPLY_JSON_PACK (
connection,
MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("slug", details.slug),
GNUNET_JSON_pack_string ("name", details.name),
GNUNET_JSON_pack_string ("description", details.description),
GNUNET_JSON_pack_object_steal ("description_i18n",
@@ -91,7 +93,10 @@ TMH_private_get_tokenfamilies_SLUG (const struct
TMH_RequestHandler *rh,
GNUNET_JSON_pack_timestamp ("valid_after", details.valid_after),
GNUNET_JSON_pack_timestamp ("valid_before", details.valid_before),
GNUNET_JSON_pack_time_rel ("duration", details.duration),
- GNUNET_JSON_pack_string ("kind", kind)
+ GNUNET_JSON_pack_string ("kind", kind),
+ // TODO: Implement
+ GNUNET_JSON_pack_int64 ("issued", 0),
+ GNUNET_JSON_pack_int64 ("redeemed", 0)
);
GNUNET_free (details.name);
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c
b/src/backend/taler-merchant-httpd_private-post-orders.c
index 8bdae3f1..a2e3b493 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -26,6 +26,7 @@
*/
#include "platform.h"
#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_db_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <gnunet/gnunet_time_lib.h>
#include <jansson.h>
@@ -34,6 +35,7 @@
#include <taler/taler_error_codes.h>
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.h>
+#include <time.h>
#include "taler-merchant-httpd.h"
#include "taler-merchant-httpd_private-post-orders.h"
#include "taler-merchant-httpd_exchanges.h"
@@ -376,14 +378,14 @@ struct OrderContext
unsigned int choices_len;
/**
- * Array of token types referenced in the contract.
+ * Array of token families referenced in the contract.
*/
- struct TALER_MerchantContractTokenAuthority *authorities;
+ struct TALER_MerchantContractTokenFamily *token_families;
/**
- * Length of the @e authorities array.
+ * Length of the @e token_families array.
*/
- unsigned int authorities_len;
+ unsigned int token_families_len;
} parse_choices;
/**
@@ -679,7 +681,6 @@ clean_order (void *cls)
json_decref (oc->merge_inventory.products);
oc->merge_inventory.products = NULL;
}
- // TODO: Check if this is even correct
for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
{
GNUNET_array_grow (oc->parse_choices.choices[i].inputs,
@@ -689,6 +690,25 @@ clean_order (void *cls)
oc->parse_choices.choices[i].outputs_len,
0);
}
+ GNUNET_array_grow (oc->parse_choices.choices,
+ oc->parse_choices.choices_len,
+ 0);
+ for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++)
+ {
+ GNUNET_free (oc->parse_choices.token_families[i].name);
+ GNUNET_free (oc->parse_choices.token_families[i].description);
+ json_decref (oc->parse_choices.token_families[i].description_i18n);
+ for (unsigned int j = 0; j<oc->parse_choices.token_families[i].keys_len;
j++)
+ {
+
GNUNET_CRYPTO_blind_sign_pub_decref(oc->parse_choices.token_families[i].keys[j].pub.public_key);
+ }
+ GNUNET_array_grow (oc->parse_choices.token_families[i].keys,
+ oc->parse_choices.token_families[i].keys_len,
+ 0);
+ }
+ GNUNET_array_grow (oc->parse_choices.token_families,
+ oc->parse_choices.token_families_len,
+ 0);
GNUNET_array_grow (oc->parse_request.inventory_products,
oc->parse_request.inventory_products_length,
0);
@@ -1324,36 +1344,168 @@ get_exchange_keys (void *cls,
/**
- * Fetch details about the token family with the given @a slug
- * and add them to the list of token authorities. Check if the
- * token family already has a valid key configured and if not,
- * create a new one.
+ * Get rounded time interval. @a start is calculated by rounding
+ * @a ts down to the nearest multiple of @a precision. @a end is
+ * the next higher multiple of @a precision.
+ *
+ * @param precision rounding precision.
+ * year, month, day, hour, minute are supported.
+ * @param ts timestamp to round
+ * @param[out] start start of the interval
+ * @param[out] end end of the interval
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+static enum GNUNET_GenericReturnValue
+get_rounded_time_interval (struct GNUNET_TIME_Relative precision,
+ struct GNUNET_TIME_Timestamp ts,
+ struct GNUNET_TIME_Timestamp *start,
+ struct GNUNET_TIME_Timestamp *end)
+{
+ struct tm* timeinfo;
+ time_t seconds;
+
+ seconds = GNUNET_TIME_timestamp_to_s (ts);
+ timeinfo = localtime (&seconds);
+
+ if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_YEARS, ==, precision))
+ {
+ timeinfo->tm_mon = 0;
+ timeinfo->tm_mday = 1;
+ timeinfo->tm_hour = 0;
+ timeinfo->tm_min = 0;
+ timeinfo->tm_sec = 0;
+ }
+ else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MONTHS, ==, precision))
+ {
+ timeinfo->tm_mday = 1;
+ timeinfo->tm_hour = 0;
+ timeinfo->tm_min = 0;
+ timeinfo->tm_sec = 0;
+ }
+ else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_DAYS, ==, precision))
+ {
+ timeinfo->tm_hour = 0;
+ timeinfo->tm_min = 0;
+ timeinfo->tm_sec = 0;
+ }
+ else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS, ==, precision))
+ {
+ timeinfo->tm_min = 0;
+ timeinfo->tm_sec = 0;
+ }
+ else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES, ==, precision))
+ {
+ timeinfo->tm_sec = 0;
+ }
+ else
+ {
+ return GNUNET_SYSERR;
+ }
+
+ *start = GNUNET_TIME_timestamp_from_s (mktime (timeinfo));
+
+ if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_YEARS, ==, precision))
+ {
+ timeinfo->tm_year++;
+ }
+ else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MONTHS, ==, precision))
+ {
+ timeinfo->tm_mon = (timeinfo->tm_mon + 1) % 12;
+ }
+ else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_DAYS, ==, precision))
+ {
+ timeinfo->tm_mday++;
+ }
+ else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS, ==, precision))
+ {
+ timeinfo->tm_hour++;
+ }
+ else if (GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES, ==, precision))
+ {
+ timeinfo->tm_min++;
+ }
+ else
+ {
+ return GNUNET_SYSERR;
+ }
+
+ *end = GNUNET_TIME_timestamp_from_s (mktime (timeinfo));
+ return GNUNET_OK;
+}
+
+/**
+ * Check if the token family with the given @a slug is already present in
+ * the list of token families for this order. If not, fetch its details and
+ * add it to the list. Then check if there is a public key with a matching
+ * @a valid_after field. If not, generate a new key pair and store it in the
+ * database.
*
* @param[in,out] oc order context
* @param slug slug of the token family
- * @param start_date validity start date of the token
+ * @param[in,out] valid_after validity start date of the token,
+ subject to rounding. Set to the rounded validity
+ start date of the matching key.
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
*/
-static MHD_RESULT
-set_token_authority (struct OrderContext *oc,
- const char *slug,
- struct GNUNET_TIME_Timestamp start_date)
+static enum GNUNET_GenericReturnValue
+set_token_family (struct OrderContext *oc,
+ const char *slug,
+ struct GNUNET_TIME_Timestamp *valid_after)
{
struct TALER_MERCHANTDB_TokenFamilyKeyDetails key_details;
- struct TALER_MerchantContractTokenAuthority authority;
+ struct TALER_MerchantContractTokenFamily *family = NULL;
enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Absolute min_start_date = GNUNET_TIME_absolute_subtract (
- start_date.abs_time,
- // TODO: make this configurable. This is the granularity of token
- // expiration dates.
- GNUNET_TIME_UNIT_DAYS
- );
+ /* TODO: Implement rounding duration of token family and use this here. */
+ struct GNUNET_TIME_Relative precision = GNUNET_TIME_UNIT_MONTHS;
+ struct GNUNET_TIME_Timestamp min_valid_after;
+ struct GNUNET_TIME_Timestamp max_valid_after;
+
+ if ( GNUNET_OK != get_rounded_time_interval (precision,
+ *valid_after,
+ &min_valid_after,
+ &max_valid_after))
+ {
+ GNUNET_break (0);
+ reply_with_error (oc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "valid_after");
+ return GNUNET_SYSERR;
+ }
+
+ for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++)
+ {
+ if (0 == strcmp (oc->parse_choices.token_families[i].slug,
+ slug))
+ {
+ family = &oc->parse_choices.token_families[i];
+ break;
+ }
+ }
+
+ if (NULL != family)
+ {
+ for (unsigned int i = 0; i<family->keys_len; i++)
+ {
+ if (GNUNET_TIME_timestamp_cmp (family->keys[i].valid_after,
+ >=,
+ min_valid_after)
+ && GNUNET_TIME_timestamp_cmp (family->keys[i].valid_after,
+ <,
+ max_valid_after))
+ {
+ /* The token family and a matching key is already added. */
+ *valid_after = family->keys[i].valid_after;
+ return GNUNET_OK;
+ }
+ }
+ }
qs = TMH_db->lookup_token_family_key (TMH_db->cls,
oc->hc->instance->settings.id,
slug,
- GNUNET_TIME_absolute_to_timestamp (
- min_start_date),
- start_date,
+ min_valid_after,
+ max_valid_after,
&key_details);
if (qs <= 0)
@@ -1386,85 +1538,164 @@ set_token_authority (struct OrderContext *oc,
http_status,
ec,
"token_family_slug");
- return MHD_NO;
+ return GNUNET_SYSERR;
}
- if (NULL == key_details.pub)
+ struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
+
+ /* Verify that the token family is valid right now. */
+ if (GNUNET_TIME_timestamp_cmp (key_details.token_family.valid_after, >, now)
||
+ GNUNET_TIME_timestamp_cmp (key_details.token_family.valid_before, <=,
now))
{
- /* If public key is NULL, private key must also be NULL */
- GNUNET_assert (NULL == key_details.priv);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Token family expired or not yet valid\n");
+ reply_with_error (oc,
+ /* TODO: HTTP Status Code GONE would be more elegant,
+ but that is already used to indicate that a
product is out of stock. */
+ MHD_HTTP_CONFLICT,
+
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_NOT_VALID,
+ key_details.token_family.slug);
+ return GNUNET_SYSERR;
+ }
+
+ /* slug is not needed */
+ GNUNET_free (key_details.token_family.slug);
- struct GNUNET_CRYPTO_BlindSignPrivateKey *priv;
- struct GNUNET_CRYPTO_BlindSignPublicKey *pub;
- struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
- struct GNUNET_TIME_Timestamp valid_before =
- GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (now,
- key_details.token_family.duration));
+ {
+ struct TALER_MerchantContractTokenFamilyKey key;
- GNUNET_CRYPTO_blind_sign_keys_create (&priv,
- &pub,
- // TODO: Make cipher and key length
configurable
- GNUNET_CRYPTO_BSA_RSA,
- 4096);
+ if (NULL == family)
+ {
+ struct TALER_MerchantContractTokenFamily new_family = {
+ .slug = slug,
+ .name = key_details.token_family.name,
+ .description = key_details.token_family.description,
+ .description_i18n = key_details.token_family.description_i18n,
+ .keys = GNUNET_new (struct TALER_MerchantContractTokenFamilyKey),
+ .keys_len = 0,
+ };
- struct TALER_TokenFamilyPublicKey token_pub = {
- .public_key = *pub,
- };
- struct TALER_TokenFamilyPrivateKey token_priv = {
- .private_key = *priv,
- };
+ switch (key_details.token_family.kind) {
+ case TALER_MERCHANTDB_TFK_Subscription:
+ new_family.kind = TALER_MCTK_SUBSCRIPTION;
+ new_family.critical = true;
+ // TODO: Set trusted domains
+ break;
+ case TALER_MERCHANTDB_TFK_Discount:
+ new_family.kind = TALER_MCTK_DISCOUNT;
+ new_family.critical = false;
+ // TODO: Set expected domains
+ break;
+ }
- qs = TMH_db->insert_token_family_key (TMH_db->cls,
- slug,
- &token_pub,
- &token_priv,
- GNUNET_TIME_absolute_to_timestamp
(now
- ),
- valid_before);
+ GNUNET_array_append (oc->parse_choices.token_families,
+ oc->parse_choices.token_families_len,
+ new_family);
- authority.token_expiration = valid_before;
- authority.pub = &token_pub;
- // GNUNET_CRYPTO_blind_sign_priv_decref (&token_priv.private_key);
- }
- else
- {
- authority.token_expiration = key_details.valid_before;
- authority.pub = key_details.pub;
- }
+ family =
&oc->parse_choices.token_families[oc->parse_choices.token_families_len - 1];
+ }
- authority.label = slug;
- authority.description = key_details.token_family.description;
- authority.description_i18n = key_details.token_family.description_i18n;
+ if (NULL == key_details.pub.public_key)
+ {
+ /* There is no matching key for this token family yet. */
+ /* We have to generate a fresh key pair. */
+ /* If public key is NULL, private key must also be NULL */
+ GNUNET_assert (NULL == key_details.priv.private_key);
+
+ enum GNUNET_DB_QueryStatus iqs;
+ struct GNUNET_CRYPTO_BlindSignPrivateKey *priv;
+ struct GNUNET_CRYPTO_BlindSignPublicKey *pub;
+ struct GNUNET_TIME_Timestamp valid_before =
+ GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (min_valid_after.abs_time,
+ key_details.token_family.duration));
+
+ if (GNUNET_TIME_timestamp_cmp (min_valid_after,
+ <,
+ key_details.token_family.valid_after))
+ {
+ /* If key would start before validity of token family,
+ set valid_after of key to the value of the token family. */
+ min_valid_after = key_details.token_family.valid_after;
+ }
- GNUNET_free (key_details.token_family.slug);
- GNUNET_free (key_details.token_family.name);
- if (NULL != key_details.priv)
- {
- GNUNET_CRYPTO_blind_sign_priv_decref (&key_details.priv->private_key);
- }
+ if (GNUNET_TIME_timestamp_cmp (valid_before,
+ >,
+ key_details.token_family.valid_before))
+ {
+ /* If key would end after validity of token family,
+ set valid_before of key to the value of the token family. */
+ valid_before = key_details.token_family.valid_before;
+ }
- switch (key_details.token_family.kind)
- {
- case TALER_MERCHANTDB_TFK_Subscription:
- authority.kind = TALER_MCTK_SUBSCRIPTION;
- authority.details.subscription.start_date = key_details.valid_after;
- authority.details.subscription.end_date = key_details.valid_before;
- authority.critical = true;
- // TODO: Set trusted domains
- break;
- case TALER_MERCHANTDB_TFK_Discount:
- authority.kind = TALER_MCTK_DISCOUNT;
- authority.critical = false;
- // TODO: Set expected domains
- break;
- }
+ GNUNET_CRYPTO_blind_sign_keys_create (&priv,
+ &pub,
+ /* TODO: Make cipher and key
length configurable */
+ GNUNET_CRYPTO_BSA_RSA,
+ 4096);
+
+ struct TALER_TokenIssuePublicKeyP token_pub = {
+ .public_key = pub,
+ };
+ struct TALER_TokenIssuePrivateKeyP token_priv = {
+ .private_key = priv,
+ };
- GNUNET_array_append (oc->parse_choices.authorities,
- oc->parse_choices.authorities_len,
- authority);
+ iqs = TMH_db->insert_token_family_key (TMH_db->cls,
+ slug,
+ &token_pub,
+ &token_priv,
+ min_valid_after,
+ valid_before);
+
+ GNUNET_CRYPTO_blind_sign_priv_decref (priv);
+
+ if (iqs <= 0)
+ {
+ enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ unsigned int http_status = 0;
- return MHD_YES;
+ switch (iqs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ ec = TALER_EC_GENERIC_DB_STORE_FAILED;
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* case listed to make compilers happy */
+ GNUNET_assert (0);
+ }
+
+ GNUNET_break (0);
+ reply_with_error (oc,
+ http_status,
+ ec,
+ "token_family_slug");
+ return GNUNET_SYSERR;
+ }
+
+ key.pub = token_pub;
+ key.valid_after = min_valid_after;
+ key.valid_before = valid_before;
+ } else {
+ key.pub = key_details.pub;
+ key.valid_after = key_details.valid_after;
+ key.valid_before = key_details.valid_before;
+ }
+
+ GNUNET_array_append (family->keys,
+ family->keys_len,
+ key);
+
+ *valid_after = key.valid_after;
+ }
+
+ return GNUNET_OK;
}
@@ -1481,7 +1712,7 @@ serialize_order (struct OrderContext *oc)
const struct TALER_MERCHANTDB_InstanceSettings *settings =
&oc->hc->instance->settings;
json_t *merchant;
- json_t *token_types = json_object ();
+ json_t *token_families = json_object ();
json_t *choices = json_array ();
merchant = GNUNET_JSON_PACK (
@@ -1528,38 +1759,54 @@ serialize_order (struct OrderContext *oc)
}
}
- for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
+ for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++)
{
- struct TALER_MerchantContractTokenAuthority *authority =
&oc->parse_choices.
- authorities[i];
+ json_t *keys = json_array();
+ struct TALER_MerchantContractTokenFamily *family =
&oc->parse_choices.token_families[i];
+
+ for (unsigned int j = 0; j<family->keys_len; j++)
+ {
+ struct TALER_MerchantContractTokenFamilyKey key = family->keys[j];
+
+ json_t *jkey = GNUNET_JSON_PACK (
+ /* TODO: Remove h_pub. */
+ GNUNET_JSON_pack_data_auto ("h_pub",
+ &key.pub.public_key->pub_key_hash),
+ GNUNET_JSON_pack_allow_null(
+ GNUNET_JSON_pack_rsa_public_key ("rsa_pub",
+
key.pub.public_key->details.rsa_public_key)),
+ // GNUNET_JSON_pack_allow_null(
+ // GNUNET_JSON_pack_data_auto ("cs_pub",
+ //
&key.pub.public_key->details.cs_public_key)),
+ GNUNET_JSON_pack_int64 ("cipher",
+ key.pub.public_key->cipher),
+ GNUNET_JSON_pack_timestamp ("valid_after",
+ key.valid_after),
+ GNUNET_JSON_pack_timestamp ("valid_before",
+ key.valid_before)
+ );
- // TODO: Finish spec to clearly define how token families are stored in
- // ContractTerms.
- // Here are some thoughts:
- // - Multiple keys of the same token family can be referenced in
- // one contract. E.g. exchanging old subscription for new.
- // - TokenAuthority should be renamed to TokenFamily for consistency.
- // - TokenFamilySlug can be used instead of TokenAuthorityLabel, but
- // every token-based in- or output needs to have a valid_after
date,
- // so it's clear with key is referenced.
- json_t *jauthority = GNUNET_JSON_PACK (
+ GNUNET_assert (0 == json_array_append_new (keys, jkey));
+ }
+
+ /* TODO: Add 'details' field. */
+ json_t *jfamily = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("name",
+ family->name),
GNUNET_JSON_pack_string ("description",
- authority->description),
+ family->description),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("description_i18n",
- authority->description_i18n)),
- GNUNET_JSON_pack_data_auto ("h_pub",
- &authority->pub->public_key.pub_key_hash),
- GNUNET_JSON_pack_data_auto ("pub",
- &authority->pub->public_key.pub_key_hash),
- GNUNET_JSON_pack_timestamp ("token_expiration",
- authority->token_expiration)
- );
+ family->description_i18n)),
+ GNUNET_JSON_pack_array_steal ("keys",
+ keys),
+ GNUNET_JSON_pack_bool ("critical",
+ family->critical)
+ );
- GNUNET_assert (0 ==
- json_object_set_new (token_types,
- authority->label,
- jauthority));
+ GNUNET_assert (0 == json_object_set_new (token_families,
+ family->slug,
+ jfamily));
}
for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
@@ -1573,25 +1820,19 @@ serialize_order (struct OrderContext *oc)
{
struct TALER_MerchantContractInput *input = &choice->inputs[j];
- json_t *jinput = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_int64 ("type",
- input->type)
- );
+ /* For now, only tokens are supported */
+ GNUNET_assert (TALER_MCIT_TOKEN == input->type);
- if (TALER_MCIT_TOKEN == input->type)
- {
- GNUNET_assert (0 ==
- json_object_set_new (jinput,
- "number",
- json_integer (
- input->details.token.count)));
- GNUNET_assert (0 ==
- json_object_set_new (jinput,
- "token_family_slug",
- json_string (
- input->details.token.
- token_family_slug)));
- }
+ json_t *jinput = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("kind",
+ TMH_string_from_contract_input_type
(input->type)),
+ GNUNET_JSON_pack_string ("token_family_slug",
+ input->details.token.token_family_slug),
+ GNUNET_JSON_pack_int64 ("number",
+ input->details.token.count),
+ GNUNET_JSON_pack_timestamp ("valid_after",
+ input->details.token.valid_after)
+ );
GNUNET_assert (0 == json_array_append_new (inputs, jinput));
}
@@ -1600,26 +1841,19 @@ serialize_order (struct OrderContext *oc)
{
struct TALER_MerchantContractOutput *output = &choice->outputs[j];
- json_t *joutput = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_int64 ("type",
- output->type)
- );
-
- if (TALER_MCOT_TOKEN == output->type)
- {
- GNUNET_assert (0 ==
- json_object_set_new (joutput,
- "number",
- json_integer (
- output->details.token.count)));
+ /* For now, only tokens are supported */
+ GNUNET_assert (TALER_MCOT_TOKEN == output->type);
- GNUNET_assert (0 ==
- json_object_set_new (joutput,
- "token_family_slug",
- json_string (
- output->details.token.
- token_family_slug)));
- }
+ json_t *joutput = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("kind",
+ TMH_string_from_contract_output_type
(output->type)),
+ GNUNET_JSON_pack_string ("token_family_slug",
+ output->details.token.token_family_slug),
+ GNUNET_JSON_pack_int64 ("number",
+ output->details.token.count),
+ GNUNET_JSON_pack_timestamp ("valid_after",
+ output->details.token.valid_after)
+ );
GNUNET_assert (0 == json_array_append (outputs, joutput));
}
@@ -1691,13 +1925,13 @@ serialize_order (struct OrderContext *oc)
TALER_JSON_pack_amount ("amount",
&oc->parse_order.brutto),
GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_array_incref ("choices",
- choices)
- ),
+ GNUNET_JSON_pack_array_steal ("choices",
+ choices)
+ ),
GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref ("token_types",
- token_types)
- ),
+ GNUNET_JSON_pack_object_steal ("token_families",
+ token_families)
+ ),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("extra",
(json_t *) oc->parse_order.extra))
@@ -1780,8 +2014,8 @@ set_max_fee (struct OrderContext *oc)
static bool
set_exchanges (struct OrderContext *oc)
{
- /* Note: re-building 'oc->exchanges' every time here might be a tad
- expensive; could likely consider caching the result if it starts to
+ /* Note: re-building 'oc->set_exchanges.exchanges' every time here might be a
+ tad expensive; could likely consider caching the result if it starts to
matter. */
if (NULL == oc->set_exchanges.exchanges)
{
@@ -1852,6 +2086,8 @@ parse_order (struct OrderContext *oc)
* mostly because in GNUnet relative times can't
* be negative. */
bool no_fee;
+ /* TODO: Move "amount" field to choices. This entails moving the
+ stefan-base fee calculation to the parse_choices phase. */
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("version",
@@ -2314,10 +2550,14 @@ parse_choices (struct OrderContext *oc)
const json_t *jinputs;
const json_t *joutputs;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_array_const ("inputs",
- &jinputs),
- GNUNET_JSON_spec_array_const ("outputs",
- &joutputs),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("inputs",
+ &jinputs),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("outputs",
+ &joutputs),
+ NULL),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue ret;
@@ -2339,14 +2579,15 @@ parse_choices (struct OrderContext *oc)
return;
}
- if (! json_is_array (jinputs) ||
- ! json_is_array (joutputs))
+ if (0 == json_array_size(jinputs) && 0 == json_array_size(joutputs))
{
- GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Choice #%u must have at least one input or output\n",
+ i);
reply_with_error (oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "inputs or outputs");
+ "choice");
return;
}
@@ -2356,10 +2597,6 @@ parse_choices (struct OrderContext *oc)
size_t idx;
json_array_foreach ((json_t *) jinputs, idx, jinput)
{
- // TODO: Assuming this struct would have more fields, would i use
GNUNET_new then?
- // Or should i use GNUNET_grow first and then get the element
using the index?
- // Assuming you add a field in the future, it's easier to that
way, so you don't
- // free it.
struct TALER_MerchantContractInput input = {.details.token.count = 1};
const char *kind;
const char *ierror_name;
@@ -2370,6 +2607,8 @@ parse_choices (struct OrderContext *oc)
&kind),
GNUNET_JSON_spec_string ("token_family_slug",
&input.details.token.token_family_slug),
+ /* TODO: Remove valid_after field for inputs,
+ use current system time instead. */
GNUNET_JSON_spec_timestamp ("valid_after",
&input.details.token.valid_after),
GNUNET_JSON_spec_mark_optional (
@@ -2396,7 +2635,7 @@ parse_choices (struct OrderContext *oc)
return;
}
- input.type = TMH_string_to_contract_input_type (kind);
+ input.type = TMH_contract_input_type_from_string (kind);
if (TALER_MCIT_INVALID == input.type)
{
@@ -2416,29 +2655,12 @@ parse_choices (struct OrderContext *oc)
continue;
}
- bool found = false;
-
- for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
+ if (GNUNET_OK != set_token_family (oc,
+ input.details.token.token_family_slug,
+ &input.details.token.valid_after))
{
- if (0 == strcmp (oc->parse_choices.authorities[i].label,
- input.details.token.token_family_slug))
- {
- found = true;
- break;
- }
- }
-
- if (! found)
- {
- MHD_RESULT res;
- res = set_token_authority (oc,
- input.details.token.token_family_slug,
- input.details.token.valid_after);
-
- if (MHD_NO == res)
- {
- return;
- }
+ /* error is already scheduled, return. */
+ return;
}
GNUNET_array_append (oc->parse_choices.choices[i].inputs,
@@ -2463,6 +2685,7 @@ parse_choices (struct OrderContext *oc)
&kind),
GNUNET_JSON_spec_string ("token_family_slug",
&output.details.token.token_family_slug),
+ /* TODO: Make valid_after optional, default to current system time.
*/
GNUNET_JSON_spec_timestamp ("valid_after",
&output.details.token.valid_after),
GNUNET_JSON_spec_mark_optional (
@@ -2491,7 +2714,7 @@ parse_choices (struct OrderContext *oc)
return;
}
- output.type = TMH_string_to_contract_output_type (kind);
+ output.type = TMH_contract_output_type_from_string (kind);
if (TALER_MCOT_INVALID == output.type)
{
@@ -2509,33 +2732,16 @@ parse_choices (struct OrderContext *oc)
if (0 == output.details.token.count)
{
- /* Ignore outputs with 'number' field set to 0 */
+ /* Ignore outputs with 'number' field set to 0. */
continue;
}
- bool found = false;
-
- for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
+ if (GNUNET_OK != set_token_family (oc,
+ output.details.token.token_family_slug,
+ &output.details.token.valid_after))
{
- if (0 == strcmp (oc->parse_choices.authorities[i].label,
- output.details.token.token_family_slug))
- {
- found = true;
- break;
- }
- }
-
- if (! found)
- {
- MHD_RESULT res;
- res = set_token_authority (oc,
- output.details.token.token_family_slug,
- output.details.token.valid_after);
-
- if (MHD_NO == res)
- {
- return;
- }
+ /* Error is already scheduled, return. */
+ return;
}
GNUNET_array_append (oc->parse_choices.choices[i].outputs,
@@ -2651,7 +2857,6 @@ merge_inventory (struct OrderContext *oc)
/* case listed to make compilers happy */
GNUNET_assert (0);
}
- json_decref (oc->merge_inventory.products);
reply_with_error (oc,
http_status,
ec,
diff --git a/src/backend/taler-merchant-httpd_private-post-token-families.c
b/src/backend/taler-merchant-httpd_private-post-token-families.c
index f4472c39..069f6b29 100644
--- a/src/backend/taler-merchant-httpd_private-post-token-families.c
+++ b/src/backend/taler-merchant-httpd_private-post-token-families.c
@@ -25,6 +25,7 @@
#include "platform.h"
#include "taler-merchant-httpd_private-post-token-families.h"
#include "taler-merchant-httpd_helper.h"
+#include <gnunet/gnunet_time_lib.h>
#include <taler/taler_json_lib.h>
@@ -74,6 +75,7 @@ TMH_private_post_token_families (const struct
TMH_RequestHandler *rh,
struct TMH_MerchantInstance *mi = hc->instance;
struct TALER_MERCHANTDB_TokenFamilyDetails details = { 0 };
const char *kind = NULL;
+ bool no_valid_after = false;
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("slug",
@@ -87,12 +89,16 @@ TMH_private_post_token_families (const struct
TMH_RequestHandler *rh,
&details.description_i18n),
NULL),
GNUNET_JSON_spec_string ("kind", &kind),
- GNUNET_JSON_spec_timestamp ("valid_after",
- &details.valid_after),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &details.valid_after),
+ &no_valid_after),
GNUNET_JSON_spec_timestamp ("valid_before",
&details.valid_before),
GNUNET_JSON_spec_relative_time ("duration",
&details.duration),
+ GNUNET_JSON_spec_relative_time ("rounding",
+ &details.rounding),
GNUNET_JSON_spec_end ()
};
@@ -112,6 +118,33 @@ TMH_private_post_token_families (const struct
TMH_RequestHandler *rh,
}
}
+ struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
+
+ if (no_valid_after) {
+ details.valid_after = now;
+ }
+
+ /* Ensure that valid_after is before valid_before */
+ if (GNUNET_TIME_timestamp_cmp (details.valid_after, >=,
details.valid_before))
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "valid_before");
+ }
+
+ /* Ensure that valid_after is not in the past */
+ if (GNUNET_TIME_timestamp_cmp (details.valid_after, <, now))
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "valid_after");
+ }
if (strcmp (kind, "discount") == 0)
details.kind = TALER_MERCHANTDB_TFK_Discount;
@@ -140,6 +173,22 @@ TMH_private_post_token_families (const struct
TMH_RequestHandler *rh,
"description_i18n");
}
+ if ( GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_YEARS, !=, details.rounding)
&&
+ GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MONTHS, !=,
details.rounding) &&
+ GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_DAYS, !=, details.rounding)
&&
+ GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_HOURS, !=, details.rounding)
&&
+ GNUNET_TIME_relative_cmp (GNUNET_TIME_UNIT_MINUTES, !=,
details.rounding) )
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Received invalid rounding value: %s\n",
+ GNUNET_STRINGS_relative_time_to_string (details.rounding,
GNUNET_YES));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "rounding");
+ }
/* finally, interact with DB until no serialization error */
for (unsigned int i = 0; i<MAX_RETRIES; i++)
diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am
index 4cacd3b4..e6511fef 100644
--- a/src/backenddb/Makefile.am
+++ b/src/backenddb/Makefile.am
@@ -23,6 +23,8 @@ sql_DATA = \
merchant-0004.sql \
merchant-0005.sql \
merchant-0006.sql \
+ merchant-0007.sql \
+ merchant-0008.sql \
drop.sql
BUILT_SOURCES = \
@@ -183,6 +185,9 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \
pg_update_token_family.h pg_update_token_family.c \
pg_insert_token_family_key.h pg_insert_token_family_key.c \
pg_lookup_token_family_key.h pg_lookup_token_family_key.c \
+ pg_insert_spent_token.h pg_insert_spent_token.c \
+ pg_insert_issued_token.h pg_insert_issued_token.c \
+ pg_lookup_spent_tokens_by_order.h pg_lookup_spent_tokens_by_order.c \
plugin_merchantdb_postgres.c \
pg_helper.h pg_helper.c
libtaler_plugin_merchantdb_postgres_la_LIBADD = \
diff --git a/src/backenddb/merchant-0006.sql b/src/backenddb/merchant-0006.sql
index 00b9afff..e216b4ad 100644
--- a/src/backenddb/merchant-0006.sql
+++ b/src/backenddb/merchant-0006.sql
@@ -14,6 +14,10 @@
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
+-- @file merchant-0006.sql
+-- @brief add selected choice index to contract terms
+-- @author Christian Blättler
+
-- Everything in one big transaction
BEGIN;
@@ -22,6 +26,12 @@ SELECT _v.register_patch('merchant-0006', NULL, NULL);
SET search_path TO merchant;
+ALTER TABLE merchant_contract_terms
+ ADD COLUMN choice_index INT2 DEFAULT NULL;
+
+COMMENT ON COLUMN merchant_contract_terms.choice_index
+ IS 'Index of selected choice. Refers to the `choices` array in the contract
terms. NULL for contracts without choices.';
+
ALTER TABLE merchant_inventory
ALTER COLUMN image SET DATA TYPE TEXT;
diff --git a/src/backenddb/merchant-0004.sql b/src/backenddb/merchant-0007.sql
similarity index 65%
copy from src/backenddb/merchant-0004.sql
copy to src/backenddb/merchant-0007.sql
index 711026a2..36cd1550 100644
--- a/src/backenddb/merchant-0004.sql
+++ b/src/backenddb/merchant-0007.sql
@@ -14,17 +14,21 @@
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
+-- @file merchant-0007.sql
+-- @brief alter length check of public key hash in merchant_token_family_keys
+-- @author Christian Blättler
+
+-- Everything in one big transaction
BEGIN;
+
-- Check patch versioning is in place.
-SELECT _v.register_patch('merchant-0004', NULL, NULL);
+SELECT _v.register_patch('merchant-0007', NULL, NULL);
SET search_path TO merchant;
-DROP TABLE merchant_reward_pickup_signatures;
-DROP TABLE merchant_reward_pickups;
-DROP TABLE merchant_rewards;
-DROP TABLE merchant_reward_reserve_keys;
-DROP TABLE merchant_reward_reserves;
-
+ALTER TABLE merchant_token_family_keys
+ DROP CONSTRAINT merchant_token_family_keys_h_pub_check,
+ ADD CONSTRAINT h_pub_length_check CHECK (LENGTH(h_pub) = 64);
+-- Complete transaction
COMMIT;
diff --git a/src/backenddb/merchant-0008.sql b/src/backenddb/merchant-0008.sql
new file mode 100644
index 00000000..bb8beb73
--- /dev/null
+++ b/src/backenddb/merchant-0008.sql
@@ -0,0 +1,52 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+-- @file merchant-0008.sql
+-- @brief add merchant_issued_tokens table
+-- @author Christian Blättler
+
+-- Everything in one big transaction
+BEGIN;
+
+-- Check patch versioning is in place.
+SELECT _v.register_patch('merchant-0008', NULL, NULL);
+
+SET search_path TO merchant;
+
+
+CREATE TABLE IF NOT EXISTS merchant_issued_tokens
+ (issued_token_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+ ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)
+ ,token_family_key_serial BIGINT REFERENCES
merchant_token_family_keys(token_family_key_serial) ON DELETE CASCADE
+ ,blind_sig BYTEA NOT NULL
+ );
+COMMENT ON TABLE merchant_issued_tokens
+ IS 'Tokens that have been (blindly) issued to customers.';
+COMMENT ON COLUMN merchant_issued_tokens.h_contract_terms
+ IS 'This is no foreign key by design.';
+COMMENT ON COLUMN merchant_issued_tokens.token_family_key_serial
+ IS 'Token family key to which the spent token belongs.';
+COMMENT ON COLUMN merchant_issued_tokens.blind_sig
+ IS 'Blind signature made with token issue key to prove validity of token.';
+
+ALTER TABLE merchant_spent_tokens RENAME TO merchant_used_tokens;
+
+ALTER TABLE merchant_token_families ADD COLUMN rounding BIGINT NOT NULL;
+COMMENT ON COLUMN merchant_token_families.rounding
+ IS 'Token start date rounding granularity.';
+
+-- Complete transaction
+COMMIT;
diff --git a/src/backenddb/pg_delete_category.c
b/src/backenddb/pg_insert_issued_token.c
similarity index 50%
copy from src/backenddb/pg_delete_category.c
copy to src/backenddb/pg_insert_issued_token.c
index 4a43aa37..2c07450f 100644
--- a/src/backenddb/pg_delete_category.c
+++ b/src/backenddb/pg_insert_issued_token.c
@@ -14,41 +14,49 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file backenddb/pg_delete_category.c
- * @brief Implementation of the delete_category function for Postgres
+ * @file backenddb/pg_insert_issued_token.c
+ * @brief Implementation of the insert_issued_token function for Postgres
* @author Christian Grothoff
*/
#include "platform.h"
#include <taler/taler_error_codes.h>
#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
-#include "pg_delete_category.h"
+#include "pg_insert_issued_token.h"
#include "pg_helper.h"
-
enum GNUNET_DB_QueryStatus
-TMH_PG_delete_category (void *cls,
- const char *instance_id,
- uint64_t category_id)
+TMH_PG_insert_issued_token (void *cls,
+ const struct TALER_PrivateContractHashP
*h_contract_terms,
+ const struct TALER_TokenIssuePublicKeyHashP
*h_issue_pub,
+ const struct TALER_TokenIssueBlindSignatureP
*blind_sig)
{
struct PostgresClosure *pg = cls;
+
struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (instance_id),
- GNUNET_PQ_query_param_uint64 (&category_id),
+ GNUNET_PQ_query_param_auto_from_type (h_issue_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_blinded_sig (blind_sig->signature),
GNUNET_PQ_query_param_end
};
check_connection (pg);
PREPARE (pg,
- "delete_category",
- "DELETE"
- " FROM merchant_categories"
- " WHERE merchant_serial="
- " (SELECT merchant_serial "
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND category_serial=$2");
+ "issued_token_insert",
+ "INSERT INTO merchant_issued_tokens"
+ "(token_family_key_serial"
+ ",h_contract_terms"
+ ",blind_sig)"
+ " SELECT token_family_key_serial, $2, $3"
+ " FROM merchant_token_families"
+ " JOIN merchant_token_family_keys"
+ " USING (token_family_serial)"
+ " WHERE h_pub = $1");
+
+ /* TODO: Increase issued counter on merchant_token_family table. */
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_category",
+ "issued_token_insert",
params);
-}
+
+
+}
\ No newline at end of file
diff --git a/src/backenddb/pg_insert_token_family.h
b/src/backenddb/pg_insert_issued_token.h
similarity index 55%
copy from src/backenddb/pg_insert_token_family.h
copy to src/backenddb/pg_insert_issued_token.h
index d584f5e7..a65fc16d 100644
--- a/src/backenddb/pg_insert_token_family.h
+++ b/src/backenddb/pg_insert_issued_token.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2023 Taler Systems SA
+ Copyright (C) 2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -14,12 +14,12 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file backenddb/pg_insert_token_family.h
- * @brief implementation of the insert_token_family function for Postgres
+ * @file backenddb/pg_insert_issued_token.h
+ * @brief implementation of the insert_issued_token function for Postgres
* @author Christian Blättler
*/
-#ifndef PG_INSERT_TOKEN_FAMILY_H
-#define PG_INSERT_TOKEN_FAMILY_H
+#ifndef PG_INSERT_ISSUED_TOKEN_H
+#define PG_INSERT_ISSUED_TOKEN_H
#include <taler/taler_util.h>
#include <taler/taler_json_lib.h>
@@ -28,16 +28,15 @@
/**
* @param cls closure
- * @param instance_id instance to insert token family for
- * @param token_family_slug slug of the token family to insert
- * @param details the token family details to insert
+ * @param h_contract_terms hash of the contract the token was issued for
+ * @param h_issue_pub hash of the token issue public key used to sign the
issued token
+ * @param blind_sig resulting blind token issue signature
* @return database result code
*/
enum GNUNET_DB_QueryStatus
-TMH_PG_insert_token_family (void *cls,
- const char *instance_id,
- const char *token_family_slug,
- const struct TALER_MERCHANTDB_TokenFamilyDetails
*details);
+TMH_PG_insert_issued_token (void *cls,
+ const struct TALER_PrivateContractHashP
*h_contract_terms,
+ const struct TALER_TokenIssuePublicKeyHashP
*h_issue_pub,
+ const struct TALER_TokenIssueBlindSignatureP
*blind_sig);
#endif
-
diff --git a/src/backenddb/pg_insert_spent_token.c
b/src/backenddb/pg_insert_spent_token.c
new file mode 100644
index 00000000..1f11c5a3
--- /dev/null
+++ b/src/backenddb/pg_insert_spent_token.c
@@ -0,0 +1,72 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_spent_token.c
+ * @brief Implementation of the insert_spent_token function for Postgres
+ * @author Christian Blättler
+ */
+#ifndef PG_INSERT_SPENT_TOKEN_H
+#define PG_INSERT_SPENT_TOKEN_H
+
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_spent_token.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_spent_token (void *cls,
+ const struct TALER_PrivateContractHashP
*h_contract_terms,
+ const struct TALER_TokenIssuePublicKeyHashP
*h_issue_pub,
+ const struct TALER_TokenUsePublicKeyP *use_pub,
+ const struct TALER_TokenUseSignatureP *use_sig,
+ const struct TALER_TokenIssueSignatureP *issue_sig)
+{
+ struct PostgresClosure *pg = cls;
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_issue_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_auto_from_type (use_pub),
+ GNUNET_PQ_query_param_auto_from_type (use_sig),
+ GNUNET_PQ_query_param_unblinded_sig (issue_sig->signature),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "spent_token_insert",
+ "INSERT INTO merchant_used_tokens"
+ "(merchant_serial" /* TODO: Remove merchant_serial field from the
db, it's already given by token_family.merchant_serial. */
+ ",token_family_key_serial"
+ ",h_contract_terms"
+ ",token_pub"
+ ",token_sig"
+ ",blind_sig)"
+ " SELECT merchant_serial, token_family_key_serial, $2, $3, $4, $5"
+ " FROM merchant_token_families"
+ " JOIN merchant_token_family_keys"
+ " USING (token_family_serial)"
+ " WHERE h_pub = $1");
+
+ /* TODO: Increase used counter on merchant_token_family table. */
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "spent_token_insert",
+ params);
+}
+
+#endif
\ No newline at end of file
diff --git a/src/backenddb/pg_select_category.h
b/src/backenddb/pg_insert_spent_token.h
similarity index 50%
copy from src/backenddb/pg_select_category.h
copy to src/backenddb/pg_insert_spent_token.h
index 9eeb14aa..c2857c45 100644
--- a/src/backenddb/pg_select_category.h
+++ b/src/backenddb/pg_insert_spent_token.h
@@ -14,12 +14,12 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file backenddb/pg_select_category.h
- * @brief implementation of the select_category function for Postgres
- * @author Christian Grothoff
+ * @file backenddb/pg_insert_spent_token.h
+ * @brief implementation of the insert_spent_token function for Postgres
+ * @author Christian Blättler
*/
-#ifndef PG_SELECT_CATEGORY_H
-#define PG_SELECT_CATEGORY_H
+#ifndef PG_INSERT_SPENT_TOKEN_H
+#define PG_INSERT_SPENT_TOKEN_H
#include <taler/taler_util.h>
#include <taler/taler_json_lib.h>
@@ -27,19 +27,20 @@
/**
- * Lookup details about product category.
- *
* @param cls closure
- * @param instance_id instance to lookup template for
- * @param category_id category to update
- * @param[out] cd set to the category details on success, can be NULL
- * (in that case we only want to check if the category exists)
+ * @param h_contract_terms hash of the contract the token was used for
+ * @param h_issue_pub hash of the token issue public key
+ * @param use_pub token use public key
+ * @param use_sig token use signature
+ * @param issue_sig token issue signature
* @return database result code
*/
enum GNUNET_DB_QueryStatus
-TMH_PG_select_category (void *cls,
- const char *instance_id,
- uint64_t category_id,
- struct TALER_MERCHANTDB_CategoryDetails *cd);
+TMH_PG_insert_spent_token (void *cls,
+ const struct TALER_PrivateContractHashP
*h_contract_terms,
+ const struct TALER_TokenIssuePublicKeyHashP
*h_issue_pub,
+ const struct TALER_TokenUsePublicKeyP *use_pub,
+ const struct TALER_TokenUseSignatureP *use_sig,
+ const struct TALER_TokenIssueSignatureP *issue_sig);
#endif
diff --git a/src/backenddb/pg_insert_token_family.c
b/src/backenddb/pg_insert_token_family.c
index bf7159b8..f533a2fb 100644
--- a/src/backenddb/pg_insert_token_family.c
+++ b/src/backenddb/pg_insert_token_family.c
@@ -56,6 +56,7 @@ TMH_PG_insert_token_family (void *cls,
GNUNET_PQ_query_param_timestamp (&details->valid_after),
GNUNET_PQ_query_param_timestamp (&details->valid_before),
GNUNET_PQ_query_param_relative_time (&details->duration),
+ GNUNET_PQ_query_param_relative_time (&details->rounding),
GNUNET_PQ_query_param_string (kind),
GNUNET_PQ_query_param_end
};
@@ -72,8 +73,9 @@ TMH_PG_insert_token_family (void *cls,
",valid_after"
",valid_before"
",duration"
+ ",rounding"
",kind)"
- " SELECT merchant_serial, $2, $3, $4, $5, $6, $7, $8, $9"
+ " SELECT merchant_serial, $2, $3, $4, $5, $6, $7, $8, $9, $10"
" FROM merchant_instances"
" WHERE merchant_id=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
diff --git a/src/backenddb/pg_insert_token_family_key.c
b/src/backenddb/pg_insert_token_family_key.c
index b13c8079..b6602931 100644
--- a/src/backenddb/pg_insert_token_family_key.c
+++ b/src/backenddb/pg_insert_token_family_key.c
@@ -30,8 +30,8 @@
enum GNUNET_DB_QueryStatus
TMH_PG_insert_token_family_key (void *cls,
const char *token_family_slug,
- const struct TALER_TokenFamilyPublicKey *pub,
- const struct TALER_TokenFamilyPrivateKey *priv,
+ const struct TALER_TokenIssuePublicKeyP *pub,
+ const struct TALER_TokenIssuePrivateKeyP *priv,
const struct GNUNET_TIME_Timestamp valid_after,
const struct GNUNET_TIME_Timestamp
valid_before)
{
@@ -39,40 +39,39 @@ TMH_PG_insert_token_family_key (void *cls,
const char *cipher = NULL;
struct GNUNET_HashCode pub_hash;
- switch (pub->public_key.cipher)
+ switch (pub->public_key->cipher)
{
case GNUNET_CRYPTO_BSA_RSA:
cipher = "rsa";
- GNUNET_CRYPTO_rsa_public_key_hash (pub->public_key.details.rsa_public_key,
+ GNUNET_CRYPTO_rsa_public_key_hash (pub->public_key->details.rsa_public_key,
&pub_hash);
break;
case GNUNET_CRYPTO_BSA_CS:
cipher = "cs";
- GNUNET_CRYPTO_hash (&pub->public_key.details.cs_public_key,
- sizeof (pub->public_key.details.cs_public_key),
+ GNUNET_CRYPTO_hash (&pub->public_key->details.cs_public_key,
+ sizeof (pub->public_key->details.cs_public_key),
&pub_hash);
break;
case GNUNET_CRYPTO_BSA_INVALID:
- /* case listed to make compilers happy */
return GNUNET_DB_STATUS_HARD_ERROR;
}
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (token_family_slug),
- GNUNET_PQ_query_param_blind_sign_pub (&pub->public_key),
- GNUNET_PQ_query_param_auto_from_type (&pub->public_key.pub_key_hash),
- GNUNET_PQ_query_param_blind_sign_priv (&priv->private_key),
+ GNUNET_PQ_query_param_blind_sign_pub (pub->public_key),
+ GNUNET_PQ_query_param_auto_from_type (&pub->public_key->pub_key_hash),
+ GNUNET_PQ_query_param_blind_sign_priv (priv->private_key),
GNUNET_PQ_query_param_timestamp (&valid_after),
GNUNET_PQ_query_param_timestamp (&valid_before),
GNUNET_PQ_query_param_string (cipher),
GNUNET_PQ_query_param_end
};
- GNUNET_assert (pub->public_key.cipher == priv->private_key.cipher);
+ GNUNET_assert (pub->public_key->cipher == priv->private_key->cipher);
GNUNET_assert (0 ==
GNUNET_memcmp (&pub_hash,
- &pub->public_key.pub_key_hash));
+ &pub->public_key->pub_key_hash));
GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
valid_after.abs_time));
GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
diff --git a/src/backenddb/pg_insert_token_family_key.h
b/src/backenddb/pg_insert_token_family_key.h
index c4fc8d85..38e35e0b 100644
--- a/src/backenddb/pg_insert_token_family_key.h
+++ b/src/backenddb/pg_insert_token_family_key.h
@@ -16,7 +16,7 @@
/**
* @file backenddb/pg_insert_token_family_key.h
* @brief implementation of the insert_token_family_key function for Postgres
- * @author Christian Grothoff
+ * @author Christian Blättler
*/
#ifndef PG_INSERT_TOKEN_FAMILY_KEY_H
#define PG_INSERT_TOKEN_FAMILY_KEY_H
@@ -38,8 +38,8 @@
enum GNUNET_DB_QueryStatus
TMH_PG_insert_token_family_key (void *cls,
const char *token_family_slug,
- const struct TALER_TokenFamilyPublicKey *pub,
- const struct TALER_TokenFamilyPrivateKey *priv,
+ const struct TALER_TokenIssuePublicKeyP *pub,
+ const struct TALER_TokenIssuePrivateKeyP *priv,
const struct GNUNET_TIME_Timestamp valid_after,
const struct GNUNET_TIME_Timestamp
valid_before);
diff --git a/src/backenddb/pg_lookup_contract_terms3.c
b/src/backenddb/pg_lookup_contract_terms3.c
index ef955a51..4c6fd477 100644
--- a/src/backenddb/pg_lookup_contract_terms3.c
+++ b/src/backenddb/pg_lookup_contract_terms3.c
@@ -20,6 +20,7 @@
* @author Christian Grothoff
*/
#include "platform.h"
+#include <sys/types.h>
#include <taler/taler_error_codes.h>
#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
@@ -38,11 +39,14 @@ TMH_PG_lookup_contract_terms3 (
bool *paid,
bool *wired,
bool *session_matches,
- struct TALER_ClaimTokenP *claim_token)
+ struct TALER_ClaimTokenP *claim_token,
+ int16_t *choice_index)
{
struct PostgresClosure *pg = cls;
enum GNUNET_DB_QueryStatus qs;
struct TALER_ClaimTokenP ct;
+ uint16_t ci;
+ bool choice_index_null = false;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (instance_id),
GNUNET_PQ_query_param_string (order_id),
@@ -67,6 +71,10 @@ TMH_PG_lookup_contract_terms3 (
NULL),
GNUNET_PQ_result_spec_auto_from_type ("claim_token",
&ct),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint16 ("choice_index",
+ &ci),
+ &choice_index_null),
GNUNET_PQ_result_spec_end
};
@@ -81,6 +89,7 @@ TMH_PG_lookup_contract_terms3 (
",paid"
",wired"
",(session_id=$3) AS session_matches"
+ ",choice_index"
" FROM merchant_contract_terms"
" WHERE order_id=$2"
" AND merchant_serial="
@@ -95,5 +104,9 @@ TMH_PG_lookup_contract_terms3 (
: &rs[1]);
if (NULL != claim_token)
*claim_token = ct;
+ if (! choice_index_null)
+ *choice_index = ci;
+ else
+ *choice_index = -1;
return qs;
}
diff --git a/src/backenddb/pg_lookup_contract_terms3.h
b/src/backenddb/pg_lookup_contract_terms3.h
index d1cc78a2..14463835 100644
--- a/src/backenddb/pg_lookup_contract_terms3.h
+++ b/src/backenddb/pg_lookup_contract_terms3.h
@@ -37,7 +37,8 @@
* @param[out] paid set to true if the order is fully paid
* @param[out] wired set to true if the exchange wired the funds
* @param[out] session_matches set to true if @a session_id matches session
stored for this contract
- * @param[out] claim_token set to token to use for access control
+ * @param[out] claim_token set to token to use for access control
+ * @param[out] choice_index set to the choice index, -1 if not set
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
@@ -51,6 +52,7 @@ TMH_PG_lookup_contract_terms3 (
bool *paid,
bool *wired,
bool *session_matches,
- struct TALER_ClaimTokenP *claim_token);
+ struct TALER_ClaimTokenP *claim_token,
+ int16_t *choice_index);
#endif
diff --git a/src/backenddb/pg_lookup_spent_tokens_by_order.c
b/src/backenddb/pg_lookup_spent_tokens_by_order.c
new file mode 100644
index 00000000..5c27e072
--- /dev/null
+++ b/src/backenddb/pg_lookup_spent_tokens_by_order.c
@@ -0,0 +1,162 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_lookup_spent_tokens_by_order.c
+ * @brief Implementation of the lookup_spent_tokens_by_order function for
Postgres
+ * @author Christian Blättler
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_spent_tokens_by_order.h"
+#include "pg_helper.h"
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Closure for lookup_spent_tokens_by_order_cb().
+ */
+struct LookupSpentTokensByOrderContext
+{
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Function to call with all results.
+ */
+ TALER_MERCHANTDB_UsedTokensCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Set to the query result.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls of type `struct LookupSpentTokensByOrderContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_spent_tokens_by_order_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupSpentTokensByOrderContext *ctx = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t spent_token_serial;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ struct TALER_TokenIssuePublicKeyHashP h_issue_pub;
+ struct TALER_TokenUsePublicKeyP use_pub;
+ struct TALER_TokenUseSignatureP use_sig;
+ struct TALER_TokenIssueSignatureP issue_sig;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("spent_token_serial",
+ &spent_token_serial),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &h_contract_terms),
+ GNUNET_PQ_result_spec_auto_from_type ("h_pub",
+ &h_issue_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("token_pub",
+ &use_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("token_sig",
+ &use_sig),
+ // GNUNET_PQ_result_spec_unblinded_sig ("blind_sig",
+ // &issue_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ spent_token_serial,
+ &h_contract_terms,
+ &h_issue_pub,
+ &use_pub,
+ &use_sig,
+ &issue_sig);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+ ctx->qs = num_results;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_spent_tokens_by_order (void *cls,
+ uint64_t order_serial,
+ TALER_MERCHANTDB_UsedTokensCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupSpentTokensByOrderContext ctx = {
+ .pg = pg,
+ .cb = cb,
+ .cb_cls = cb_cls
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&order_serial),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_spent_tokens_by_order",
+ "SELECT"
+ " spent_token_serial"
+ ",h_contract_terms"
+ ",h_pub"
+ ",token_pub"
+ ",token_sig"
+ ",blind_sig"
+ " FROM merchant_used_tokens"
+ " JOIN merchant_contract_terms"
+ " USING (h_contract_terms)"
+ " JOIN merchant_token_family_keys"
+ " USING (token_family_key_serial)"
+ " WHERE order_serial=$1");
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_spent_tokens_by_order",
+ params,
+ &lookup_spent_tokens_by_order_cb,
+ &ctx);
+
+ if (qs < 0)
+ return qs;
+ return ctx.qs;
+}
diff --git a/src/backenddb/pg_lookup_deposits_by_order.h
b/src/backenddb/pg_lookup_spent_tokens_by_order.h
similarity index 60%
copy from src/backenddb/pg_lookup_deposits_by_order.h
copy to src/backenddb/pg_lookup_spent_tokens_by_order.h
index ecf2d367..d3c55676 100644
--- a/src/backenddb/pg_lookup_deposits_by_order.h
+++ b/src/backenddb/pg_lookup_spent_tokens_by_order.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2023 Taler Systems SA
+ Copyright (C) 2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -14,30 +14,32 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file backenddb/pg_lookup_deposits_by_order.h
- * @brief implementation of the lookup_deposits_by_order function for Postgres
- * @author Iván Ávalos
+ * @file backenddb/pg_lookup_spent_tokens_by_order.h
+ * @brief implementation of the lookup_spent_tokens_by_order function for
Postgres
+ * @author Christian Grothoff
*/
-#ifndef PG_LOOKUP_DEPOSITS_BY_ORDER_H
-#define PG_LOOKUP_DEPOSITS_BY_ORDER_H
+#ifndef PG_LOOKUP_SPENT_TOKENS_BY_ORDER_H
+#define PG_LOOKUP_SPENT_TOKENS_BY_ORDER_H
#include <taler/taler_util.h>
#include <taler/taler_json_lib.h>
#include "taler_merchantdb_plugin.h"
/**
- * Retrieve details about coins that were deposited for an order.
+ * Retrieve details about tokens that were used for an order.
*
* @param cls closure
* @param order_serial identifies the order
- * @param cb function to call for each deposited coin
+ * @param cb function to call for each used token
* @param cb_cls closure for @a cb
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
-TMH_PG_lookup_deposits_by_order (void *cls,
- uint64_t order_serial,
- TALER_MERCHANTDB_DepositedCoinsCallback cb,
- void *cb_cls);
+TMH_PG_lookup_spent_tokens_by_order (void *cls,
+ uint64_t order_serial,
+ TALER_MERCHANTDB_UsedTokensCallback cb,
+ void *cb_cls);
+
+
#endif
diff --git a/src/backenddb/pg_lookup_token_family_key.c
b/src/backenddb/pg_lookup_token_family_key.c
index f1fa75f3..a0a5fab1 100644
--- a/src/backenddb/pg_lookup_token_family_key.c
+++ b/src/backenddb/pg_lookup_token_family_key.c
@@ -57,105 +57,109 @@ TMH_PG_lookup_token_family_key (void *cls,
params,
rs_null);
}
- else
- {
- char *kind;
- details->pub = NULL;
- details->priv = NULL;
- details->valid_after = GNUNET_TIME_UNIT_ZERO_TS;
- details->valid_before = GNUNET_TIME_UNIT_ZERO_TS;
- struct GNUNET_PQ_ResultSpec rs[] = {
- // GNUNET_PQ_result_spec_allow_null (
- // GNUNET_PQ_result_spec_blind_sign_pub ("pub",
- // &details->pub->public_key),
- // NULL),
- // GNUNET_PQ_result_spec_allow_null (
- // GNUNET_PQ_result_spec_blind_sign_priv ("priv",
- // &details->priv->private_key),
- // NULL),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_timestamp ("key_valid_after",
- &details->valid_after),
- NULL),
- GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_timestamp ("key_valid_before",
- &details->valid_before),
- NULL),
- GNUNET_PQ_result_spec_string ("slug",
- &details->token_family.slug),
- GNUNET_PQ_result_spec_string ("name",
- &details->token_family.name),
- GNUNET_PQ_result_spec_string ("description",
- &details->token_family.description),
- TALER_PQ_result_spec_json ("description_i18n",
- &details->token_family.description_i18n),
- GNUNET_PQ_result_spec_timestamp ("valid_after",
- &details->token_family.valid_after),
- GNUNET_PQ_result_spec_timestamp ("valid_before",
- &details->token_family.valid_before),
- GNUNET_PQ_result_spec_relative_time ("duration",
- &details->token_family.duration),
- GNUNET_PQ_result_spec_string ("kind",
- &kind),
- GNUNET_PQ_result_spec_uint64 ("issued",
- &details->token_family.issued),
- GNUNET_PQ_result_spec_uint64 ("redeemed",
- &details->token_family.redeemed),
- GNUNET_PQ_result_spec_end
- };
+ char *kind;
- check_connection (pg);
- PREPARE (pg,
- "lookup_token_family_key",
- "SELECT"
- " h_pub"
- ",pub"
- ",priv"
- ",cipher"
- ",merchant_token_family_keys.valid_after as key_valid_after"
- ",merchant_token_family_keys.valid_before as key_valid_before"
- ",slug"
- ",name"
- ",description"
- ",description_i18n"
- ",merchant_token_families.valid_after"
- ",merchant_token_families.valid_before"
- ",duration"
- ",kind"
- ",issued"
- ",redeemed"
- " FROM merchant_token_families"
- " 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"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND slug=$2"
- " LIMIT 1");
- enum GNUNET_DB_QueryStatus qs;
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_token_family_key",
- params,
- rs);
+ details->valid_after = GNUNET_TIME_UNIT_ZERO_TS;
+ details->valid_before = GNUNET_TIME_UNIT_ZERO_TS;
+
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_blind_sign_pub ("pub",
+ &details->pub.public_key),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_blind_sign_priv ("priv",
+ &details->priv.private_key),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("key_valid_after",
+ &details->valid_after),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("key_valid_before",
+ &details->valid_before),
+ NULL),
+ GNUNET_PQ_result_spec_string ("slug",
+ &details->token_family.slug),
+ GNUNET_PQ_result_spec_string ("name",
+ &details->token_family.name),
+ GNUNET_PQ_result_spec_string ("description",
+ &details->token_family.description),
+ TALER_PQ_result_spec_json ("description_i18n",
+ &details->token_family.description_i18n),
+ GNUNET_PQ_result_spec_timestamp ("valid_after",
+ &details->token_family.valid_after),
+ GNUNET_PQ_result_spec_timestamp ("valid_before",
+ &details->token_family.valid_before),
+ GNUNET_PQ_result_spec_relative_time ("duration",
+ &details->token_family.duration),
+ GNUNET_PQ_result_spec_string ("kind",
+ &kind),
+ GNUNET_PQ_result_spec_uint64 ("issued",
+ &details->token_family.issued),
+ GNUNET_PQ_result_spec_uint64 ("redeemed",
+ &details->token_family.redeemed),
+ GNUNET_PQ_result_spec_end
+ };
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ memset (details,
+ 0,
+ sizeof (*details));
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_token_family_key",
+ "SELECT"
+ " h_pub"
+ ",pub"
+ ",priv"
+ ",cipher"
+ ",merchant_token_family_keys.valid_after as key_valid_after"
+ ",merchant_token_family_keys.valid_before as key_valid_before"
+ ",slug"
+ ",name"
+ ",description"
+ ",description_i18n"
+ ",merchant_token_families.valid_after"
+ ",merchant_token_families.valid_before"
+ ",duration"
+ ",kind"
+ ",issued"
+ ",redeemed"
+ " FROM merchant_token_families"
+ " 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" /* 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,
+ "lookup_token_family_key",
+ params,
+ rs);
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ if (0 == strcmp(kind, "discount"))
+ details->token_family.kind = TALER_MERCHANTDB_TFK_Discount;
+ else if (0 == strcmp(kind, "subscription"))
+ details->token_family.kind = TALER_MERCHANTDB_TFK_Subscription;
+ else
{
- if (0 == strcmp(kind, "discount"))
- details->token_family.kind = TALER_MERCHANTDB_TFK_Discount;
- else if (0 == strcmp(kind, "subscription"))
- details->token_family.kind = TALER_MERCHANTDB_TFK_Subscription;
- else
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
+ GNUNET_free (kind);
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
}
- /* TODO: How to handle multiple results? */
-
- return qs;
+ if (NULL != kind)
+ GNUNET_free (kind);
}
-}
\ No newline at end of file
+
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_token_family_key.h
b/src/backenddb/pg_lookup_token_family_key.h
index aa7335cf..fd88887c 100644
--- a/src/backenddb/pg_lookup_token_family_key.h
+++ b/src/backenddb/pg_lookup_token_family_key.h
@@ -27,13 +27,14 @@
#include "taler_merchantdb_plugin.h"
/**
- * Lookup details about a particular token family key.
+ * Lookup details about a particular token family key. The valid_after field
+ * of the key has to be >= @e min_valid_after and < @e max_valid_after.
*
* @param cls closure
* @param instance_id instance to lookup token family key for
* @param token_family_slug slug of token family to lookup
* @param min_valid_after lower bound of the start of the key validation period
- * @param max_valid_after upper bound of the start of the key validation period
+ * @param max_valid_after strict upper bound of the start of the key
validation period
* @param[out] details set to the token family key details on success, can be
NULL
* (in that case we only want to check if the token family key
exists)
* @return database result code
diff --git a/src/backenddb/plugin_merchantdb_postgres.c
b/src/backenddb/plugin_merchantdb_postgres.c
index 6c5c7a5b..2f499994 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -147,6 +147,9 @@
#include "pg_update_token_family.h"
#include "pg_insert_token_family_key.h"
#include "pg_lookup_token_family_key.h"
+#include "pg_insert_spent_token.h"
+#include "pg_insert_issued_token.h"
+#include "pg_lookup_spent_tokens_by_order.h"
/**
@@ -604,6 +607,14 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
= &TMH_PG_lookup_token_family_key;
plugin->update_deposit_confirmation_status
= &TMH_PG_update_deposit_confirmation_status;
+ plugin->insert_spent_token
+ = &TMH_PG_insert_spent_token;
+ plugin->insert_issued_token
+ = &TMH_PG_insert_issued_token;
+ plugin->lookup_spent_tokens_by_order
+ = &TMH_PG_lookup_spent_tokens_by_order;
+
+
return plugin;
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
index 83fd8dcd..3591a133 100644
--- a/src/backenddb/test_merchantdb.c
+++ b/src/backenddb/test_merchantdb.c
@@ -2012,6 +2012,7 @@ test_lookup_payment_status (const char *instance_id,
bool wired;
bool matches;
uint64_t os;
+ int16_t choice_index;
TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
plugin->lookup_contract_terms3 (plugin->cls,
@@ -2023,7 +2024,8 @@ test_lookup_payment_status (const char *instance_id,
&paid,
&wired,
&matches,
- NULL),
+ NULL,
+ &choice_index),
"Lookup payment status failed\n");
if ( (NULL != session_id) && (! matches) )
{
diff --git a/src/include/taler_merchant_service.h
b/src/include/taler_merchant_service.h
index c36328a7..52034f63 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -2069,6 +2069,7 @@ typedef void
* @param valid_after when the token family becomes valid
* @param valid_before when the token family expires
* @param duration how long tokens issued by this token family are valid for
+ * @param rounding rounding duration of token family
* @param kind kind of token family, "subscription" or "discount"
* @param cb function to call with the backend's result
* @param cb_cls closure for @a cb
@@ -2085,6 +2086,7 @@ TALER_MERCHANT_token_families_post (
struct GNUNET_TIME_Timestamp valid_after,
struct GNUNET_TIME_Timestamp valid_before,
struct GNUNET_TIME_Relative duration,
+ struct GNUNET_TIME_Relative rounding,
const char *kind,
TALER_MERCHANT_TokenFamiliesPostCallback cb,
void *cb_cls);
@@ -3122,6 +3124,65 @@ void
TALER_MERCHANT_order_claim_cancel (struct TALER_MERCHANT_OrderClaimHandle
*och);
+/**
+ * All the details about a token that are generated during issuance and
+ * that may be needed for future operations on the coin.
+ */
+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;
+
+ /**
+ * Inputs needed from the merchant for blind signing.
+ */
+ struct TALER_TokenUseMerchantValues blinding_inputs;
+
+ /**
+ * Token issue public key.
+ */
+ struct TALER_TokenIssuePublicKeyP issue_pub;
+
+ /**
+ * Unblinded token issue signature made by the merchant.
+ */
+ struct TALER_TokenIssueSignatureP issue_sig;
+
+ /**
+ * Blinded token issue signature made by the merchant.
+ */
+ struct TALER_TokenIssueBlindSignatureP blinded_sig;
+
+};
+
/**
* @brief Handle to a POST /orders/$ID/pay operation at a merchant. Note that
* we use the same handle for interactions with frontends (API for wallets) or
@@ -3165,6 +3226,16 @@ struct TALER_MERCHANT_PayResponse
*/
const char *pos_confirmation;
+ /**
+ * Array of tokens that were issued for the payment.
+ */
+ struct TALER_MERCHANT_OutputToken *tokens;
+
+ /**
+ * Length of the @e tokens array.
+ */
+ unsigned int num_tokens;
+
} ok;
// TODO: might want to return further details on errors,
@@ -3237,6 +3308,59 @@ struct TALER_MERCHANT_PaidCoin
};
+/**
+ * Information the frontend forwards to the backend to use a token for
+ * an order. Note that this does not include the token use private key,
+ * but only public keys and signatures.
+ */
+struct TALER_MERCHANT_UsedToken
+{
+
+ /**
+ * Signature on TALER_TokenUseRequestPS made with the token use private key.
+ */
+ struct TALER_TokenUseSignatureP token_sig;
+
+ /**
+ * Public key of the token. This was blindly signed by the merchant
+ * during the issuance and is now being revealed to the merchant.
+ */
+ struct TALER_TokenUsePublicKeyP token_pub;
+
+ /**
+ * Unblinded signature made by the token issue public key of the merchant.
+ */
+ struct TALER_TokenIssueSignatureP ub_sig;
+
+ /**
+ * Token issue public key associated with this token.
+ */
+ struct TALER_TokenIssuePublicKeyP issue_pub;
+
+};
+
+
+/**
+ * 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;
+
+ /**
+ * 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
@@ -3252,6 +3376,9 @@ struct TALER_MERCHANT_PaidCoin
* @param wallet_data inputs from the wallet for the contract, NULL for none
* @param num_coins length of the @a coins array
* @param coins array of coins to pay with
+ * @param num_tokens length of the @a tokens array
+ * @param tokens array of tokens used
+ * @param j_output_tokens json array of token envelopes, NULL for none
* @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
@@ -3265,6 +3392,9 @@ TALER_MERCHANT_order_pay_frontend (
const json_t *wallet_data,
unsigned int num_coins,
const struct TALER_MERCHANT_PaidCoin coins[static num_coins],
+ unsigned int num_tokens,
+ const struct TALER_MERCHANT_UsedToken tokens[static num_tokens],
+ json_t *j_output_tokens,
TALER_MERCHANT_OrderPayCallback pay_cb,
void *pay_cb_cls);
@@ -3318,6 +3448,30 @@ struct TALER_MERCHANT_PayCoin
};
+/**
+ * Information we need from the wallet to use a token for an order.
+ */
+struct TALER_MERCHANT_UseToken
+{
+
+ /**
+ * Token use private key. We will derive the public key from this.
+ */
+ struct TALER_TokenUsePrivateKeyP token_priv;
+
+ /**
+ * Unblinded signature made by the token issue public key of the merchant.
+ */
+ struct TALER_TokenIssueSignatureP ub_sig;
+
+ /**
+ * Token issue public key associated with this token.
+ */
+ struct TALER_TokenIssuePublicKeyP issue_pub;
+
+};
+
+
/**
* Pay a merchant. API for wallets that have the coin's private keys.
*
@@ -3327,7 +3481,7 @@ struct TALER_MERCHANT_PayCoin
* @param merchant_url base URL of the merchant
* @param session_id session to pay for, or NULL for none
* @param h_contract hash of the contact of the merchant with the customer
- * @param wallet_data inputs from the wallet for the contract, NULL for none
+ * @param choice_index index of the selected contract coice, -1 for none
* @param amount total value of the contract to be paid to the merchant
* @param max_fee maximum fee covered by the merchant (according to the
contract)
* @param merchant_pub the public key of the merchant (used to identify the
merchant for refund requests)
@@ -3339,6 +3493,10 @@ struct TALER_MERCHANT_PayCoin
* @param order_id order id
* @param num_coins number of coins used to pay
* @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
@@ -3349,7 +3507,7 @@ TALER_MERCHANT_order_pay (
const char *merchant_url,
const char *session_id,
const struct TALER_PrivateContractHashP *h_contract,
- const json_t *wallet_data,
+ int choice_index,
const struct TALER_Amount *amount,
const struct TALER_Amount *max_fee,
const struct TALER_MerchantPublicKeyP *merchant_pub,
@@ -3361,6 +3519,10 @@ TALER_MERCHANT_order_pay (
const char *order_id,
unsigned int num_coins,
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/include/taler_merchant_testing_lib.h
b/src/include/taler_merchant_testing_lib.h
index 47d081fc..d4b5bd27 100644
--- a/src/include/taler_merchant_testing_lib.h
+++ b/src/include/taler_merchant_testing_lib.h
@@ -614,6 +614,8 @@ TALER_TESTING_cmd_merchant_post_orders3 (
* the proposal request.
* @param http_status expected HTTP status.
* @param token_family_reference label of the POST /tokenfamilies cmd.
+ * @param num_inputs number of input tokens.
+ * @param num_outputs number of output tokens.
* @param order_id the name of the order to add.
* @param refund_deadline the deadline for refunds on this order.
* @param pay_deadline the deadline for payment on this order.
@@ -628,6 +630,8 @@ TALER_TESTING_cmd_merchant_post_orders_choices (
const char *merchant_url,
unsigned int http_status,
const char *token_family_reference,
+ unsigned int num_inputs,
+ unsigned int num_outputs,
const char *order_id,
struct GNUNET_TIME_Timestamp refund_deadline,
struct GNUNET_TIME_Timestamp pay_deadline,
@@ -983,6 +987,39 @@ TALER_TESTING_cmd_merchant_pay_order (
const char *session_id);
+/**
+ * Make a "pay" test command for an order with choices.
+ *
+ * @param label command label.
+ * @param merchant_url merchant base url
+ * @param http_status expected HTTP response code.
+ * @param proposal_reference the proposal whose payment status
+ * is going to be checked.
+ * @param coin_reference reference to any command which is able
+ * to provide coins to use for paying.
+ * @param amount_with_fee amount to pay, including the deposit
+ * fee
+ * @param amount_without_fee amount to pay, no fees included.
+ * @param session_id the session id to use for the payment (can be NULL).
+ * @param choice_index index of the selected choice for the payment.
+ * @param input_reference reference to a previous pay command that issued some
+ outputs to be used as inputs to this pay request.
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_pay_order_choices (
+ const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *proposal_reference,
+ const char *coin_reference,
+ const char *amount_with_fee,
+ const char *amount_without_fee,
+ const char *session_id,
+ int choice_index,
+ const char *input_reference);
+
+
/**
* Make an "order paid" test command.
*
@@ -1535,6 +1572,7 @@ TALER_TESTING_cmd_merchant_post_tokenfamilies (
struct GNUNET_TIME_Timestamp valid_after,
struct GNUNET_TIME_Timestamp valid_before,
struct GNUNET_TIME_Relative duration,
+ struct GNUNET_TIME_Relative rounding,
const char *kind);
/* ****** Webhooks ******* */
@@ -1823,7 +1861,10 @@ TALER_TESTING_cmd_checkserver2 (const char *label,
op (http_header, const char) \
op (http_body, const void) \
op (http_body_size, const size_t) \
- op (planchet_secrets, const struct TALER_PlanchetMasterSecretP)
+ op (planchet_secrets, const struct TALER_PlanchetMasterSecretP) \
+ op (token_priv, const struct TALER_TokenUsePrivateKeyP) \
+ op (token_issue_sig, const struct TALER_TokenIssueSignatureP) \
+ op (token_issue_pub, const struct TALER_TokenIssuePublicKeyP)
TALER_MERCHANT_TESTING_SIMPLE_TRAITS (TALER_TESTING_MAKE_DECL_SIMPLE_TRAIT)
diff --git a/src/include/taler_merchantdb_plugin.h
b/src/include/taler_merchantdb_plugin.h
index 476f5785..e7f3090a 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -1147,6 +1147,11 @@ struct TALER_MERCHANTDB_TokenFamilyDetails
*/
struct GNUNET_TIME_Relative duration;
+ /**
+ * Rounding duration of the token family.
+ */
+ struct GNUNET_TIME_Relative rounding;
+
/**
* Token family kind.
*/
@@ -1182,12 +1187,12 @@ struct TALER_MERCHANTDB_TokenFamilyKeyDetails
/**
* Token family public key.
*/
- struct TALER_TokenFamilyPublicKey *pub;
+ struct TALER_TokenIssuePublicKeyP pub;
/**
* Token family private key.
*/
- struct TALER_TokenFamilyPrivateKey *priv;
+ struct TALER_TokenIssuePrivateKeyP priv;
/**
* Details about the token family this key belongs to.
@@ -1203,20 +1208,41 @@ struct TALER_MERCHANTDB_SpentTokenDetails
/**
* Public key of the spent token.
*/
- struct TALER_TokenPublicKey pub;
+ struct TALER_TokenUsePublicKeyP pub;
/**
* Signature that this token was spent on the specified order.
*/
- struct TALER_TokenSignature sig;
+ struct TALER_TokenUseSignatureP sig;
/**
* Blind signature for the spent token to prove validity of it.
*/
- struct TALER_TokenBlindSignature blind_sig;
+ struct TALER_TokenIssueBlindSignatureP blind_sig;
};
+/**
+ * Function called with information about a token that was used.
+ *
+ * @param cls closure
+ * @param spent_token_serial which used token is this about
+ * @param h_contract_terms hash of the contract terms this token was used on
+ * @param h_issue_pub hash of the token issue public key
+ * @param use_pub token use public key
+ * @param use_sig token use signature
+ * @param issue_sig unblinded token issue signature
+ */
+typedef void
+(*TALER_MERCHANTDB_UsedTokensCallback)(
+ void *cls,
+ uint64_t spent_token_serial,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_TokenIssuePublicKeyHashP *h_issue_pub,
+ const struct TALER_TokenUsePublicKeyP *use_pub,
+ const struct TALER_TokenUseSignatureP *use_sig,
+ const struct TALER_TokenIssueSignatureP *issue_sig);
+
/**
* Handle to interact with the database.
*
@@ -2031,6 +2057,7 @@ struct TALER_MERCHANTDB_Plugin
* @param[out] wired set to true if the exchange wired the funds
* @param[out] session_matches set to true if @a session_id matches session
stored for this contract
* @param[out] claim_token set to the claim token, NULL to only check for
existence
+ * @param[out] choice_index set to the choice index, -1 if not set
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
@@ -2044,7 +2071,8 @@ struct TALER_MERCHANTDB_Plugin
bool *paid,
bool *wired,
bool *session_matches,
- struct TALER_ClaimTokenP *claim_token);
+ struct TALER_ClaimTokenP *claim_token,
+ int16_t *choice_index);
/**
@@ -2253,6 +2281,23 @@ struct TALER_MERCHANTDB_Plugin
void *rc_cls);
+
+ /**
+ * Retrieve details about tokens that were used for an order.
+ *
+ * @param cls closure
+ * @param order_serial identifies the order
+ * @param cb function to call for each used token
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_spent_tokens_by_order) (void *cls,
+ uint64_t order_serial,
+ TALER_MERCHANTDB_UsedTokensCallback cb,
+ void *cb_cls);
+
+
/**
* Mark contract as paid and store the current @a session_id
* for which the contract was paid. Deletes the underlying order
@@ -2496,6 +2541,42 @@ struct TALER_MERCHANTDB_Plugin
const struct TALER_ExchangePublicKeyP *exchange_pub);
+ /**
+ * Insert used token into the database.
+ *
+ * @param cls closure
+ * @param h_contract_terms hash of the contract the token was used for
+ * @param h_issue_pub hash of the token issue public key
+ * @param use_pub token use public key
+ * @param use_sig token use signature
+ * @param issue_sig token issue signature
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_spent_token)(void *cls,
+ const struct TALER_PrivateContractHashP
*h_contract_terms,
+ const struct TALER_TokenIssuePublicKeyHashP
*h_issue_pub,
+ const struct TALER_TokenUsePublicKeyP *use_pub,
+ const struct TALER_TokenUseSignatureP *use_sig,
+ const struct TALER_TokenIssueSignatureP *issue_sig);
+
+
+ /**
+ * Insert issued token into the database.
+ *
+ * @param cls closure
+ * @param h_contract_terms hash of the contract the token was issued for
+ * @param h_issue_pub hash of the token issue public key used to sign the
issued token
+ * @param blind_sig resulting blind token issue signature
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_issued_token) (void *cls,
+ const struct TALER_PrivateContractHashP
*h_contract_terms,
+ const struct TALER_TokenIssuePublicKeyHashP
*h_issue_pub,
+ const struct TALER_TokenIssueBlindSignatureP
*blind_sig);
+
+
/**
* Lookup refund proof data.
*
@@ -3521,8 +3602,8 @@ struct TALER_MERCHANTDB_Plugin
(*insert_token_family_key)(
void *cls,
const char *token_family_slug,
- const struct TALER_TokenFamilyPublicKey *pub,
- const struct TALER_TokenFamilyPrivateKey *priv,
+ const struct TALER_TokenIssuePublicKeyP *pub,
+ const struct TALER_TokenIssuePrivateKeyP *priv,
struct GNUNET_TIME_Timestamp valid_after,
struct GNUNET_TIME_Timestamp valid_before);
diff --git a/src/lib/merchant_api_post_order_pay.c
b/src/lib/merchant_api_post_order_pay.c
index 57c85565..b34240b6 100644
--- a/src/lib/merchant_api_post_order_pay.c
+++ b/src/lib/merchant_api_post_order_pay.c
@@ -25,6 +25,8 @@
*/
#include "platform.h"
#include <curl/curl.h>
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_json_lib.h>
#include <jansson.h>
#include <microhttpd.h> /* just for HTTP status codes */
#include <gnunet/gnunet_util_lib.h>
@@ -32,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>
@@ -122,6 +125,53 @@ 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_OutputToken **tokens,
+ unsigned int *num_tokens)
+{
+ GNUNET_array_grow (*tokens,
+ *num_tokens,
+ json_array_size (token_sigs));
+
+ for (unsigned int i = 0; i<(*num_tokens); i++)
+ {
+ struct TALER_MERCHANT_OutputToken *token = &(*tokens)[i];
+ const json_t *jtoken = json_array_get (token_sigs,
+ i);
+
+ if (NULL == jtoken)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_blinded_token_issue_sig ("blind_sig",
+ &token->blinded_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jtoken,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ return GNUNET_YES;
+}
+
+
/**
* Function called when we're done processing the
* HTTP /pay request.
@@ -142,6 +192,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",
@@ -154,15 +212,17 @@ handle_pay_finished (void *cls,
case MHD_HTTP_OK:
if (oph->am_wallet)
{
- /* Here we can (and should) verify the merchant's signature */
+ const json_t *token_sigs = NULL;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto (
- "sig",
- &pr.details.ok.merchant_sig),
+ GNUNET_JSON_spec_fixed_auto ("sig",
+ &pr.details.ok.merchant_sig),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string (
- "pos_confirmation",
- &pr.details.ok.pos_confirmation),
+ GNUNET_JSON_spec_string ("pos_confirmation",
+ &pr.details.ok.pos_confirmation),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("token_sigs",
+ &token_sigs),
NULL),
GNUNET_JSON_spec_end ()
};
@@ -179,6 +239,18 @@ handle_pay_finished (void *cls,
break;
}
+ 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;
+ }
+
if (GNUNET_OK !=
TALER_merchant_pay_verify (&oph->h_contract_terms,
&oph->merchant_pub,
@@ -308,21 +380,20 @@ TALER_MERCHANT_order_pay_frontend (
const json_t *wallet_data,
unsigned int num_coins,
const struct TALER_MERCHANT_PaidCoin coins[static num_coins],
+ unsigned int num_tokens,
+ const struct TALER_MERCHANT_UsedToken tokens[static num_tokens],
+ json_t *j_output_tokens,
TALER_MERCHANT_OrderPayCallback pay_cb,
void *pay_cb_cls)
{
struct TALER_MERCHANT_OrderPayHandle *oph;
json_t *pay_obj;
json_t *j_coins;
+ json_t *j_tokens = NULL;
CURL *eh;
struct TALER_Amount total_fee;
struct TALER_Amount total_amount;
- if (0 == num_coins)
- {
- GNUNET_break (0);
- return NULL;
- }
j_coins = json_array ();
GNUNET_assert (NULL != j_coins);
for (unsigned int i = 0; i<num_coins; i++)
@@ -392,9 +463,42 @@ TALER_MERCHANT_order_pay_frontend (
}
}
+ if (0 < num_tokens)
+ {
+ j_tokens = json_array ();
+ GNUNET_assert (NULL != j_tokens);
+ for (unsigned int i = 0; i<num_tokens; i++)
+ {
+ json_t *j_token;
+ const struct TALER_MERCHANT_UsedToken *ut = &tokens[i];
+
+ j_token = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("token_sig",
+ &ut->token_sig),
+ GNUNET_JSON_pack_data_auto ("token_pub",
+ &ut->token_pub),
+ TALER_JSON_pack_token_issue_sig ("ub_sig",
+ &ut->ub_sig));
+ if (0 !=
+ json_array_append_new (j_tokens,
+ j_token))
+ {
+ GNUNET_break (0);
+ json_decref (j_tokens);
+ return NULL;
+ }
+ }
+ }
+
pay_obj = GNUNET_JSON_PACK (
GNUNET_JSON_pack_array_steal ("coins",
j_coins),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("tokens",
+ j_tokens)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_incref ("tokens_evs",
+ j_output_tokens)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("wallet_data",
(json_t *) wallet_data)),
@@ -402,6 +506,10 @@ TALER_MERCHANT_order_pay_frontend (
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;
@@ -461,7 +569,7 @@ TALER_MERCHANT_order_pay (
const char *merchant_url,
const char *session_id,
const struct TALER_PrivateContractHashP *h_contract_terms,
- const json_t *wallet_data,
+ int choice_index,
const struct TALER_Amount *amount,
const struct TALER_Amount *max_fee,
const struct TALER_MerchantPublicKeyP *merchant_pub,
@@ -473,11 +581,18 @@ TALER_MERCHANT_order_pay (
const char *order_id,
unsigned int num_coins,
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)
{
+ json_t *j_output_tokens = NULL;
+ const json_t *wallet_data = NULL;
+ struct GNUNET_HashCode h_outputs;
struct GNUNET_HashCode wallet_data_hash;
-
+
if (GNUNET_YES !=
TALER_amount_cmp_currency (amount,
max_fee))
@@ -485,11 +600,53 @@ TALER_MERCHANT_order_pay (
GNUNET_break (0);
return NULL;
}
- if (NULL != wallet_data)
+ if ((0 < num_tokens || 0 < num_output_tokens) && 0 > choice_index)
+ {
+ /* Tokens (input or output) require a valid choice_index to be set.
+ Only contracts with coices can use or issue tokens. */
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (0 < num_output_tokens)
+ {
+ /* Build token envelopes json array. */
+ 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));
+
+ if (0 !=
+ json_array_append_new (j_output_tokens,
+ j_token_ev))
+ {
+ GNUNET_break (0);
+ json_decref (j_output_tokens);
+ return NULL;
+ }
+ }
+
+ TALER_json_hash (j_output_tokens, &h_outputs);
+ }
+ if (0 <= choice_index)
+ {
+ wallet_data = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_int64 ("choice_index",
+ choice_index),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("h_outputs",
+ &h_outputs)));
TALER_json_hash (wallet_data,
- &wallet_data_hash);
+ &wallet_data_hash);
+ }
{
struct TALER_MERCHANT_PaidCoin pc[num_coins];
+ struct TALER_MERCHANT_UsedToken ut[num_tokens];
for (unsigned int i = 0; i<num_coins; i++)
{
@@ -534,6 +691,20 @@ TALER_MERCHANT_order_pay (
p->amount_without_fee = coin->amount_without_fee;
p->exchange_url = coin->exchange_url;
}
+ for (unsigned int i = 0; i<num_tokens; i++)
+ {
+ const struct TALER_MERCHANT_UseToken *token = &tokens[i];
+ struct TALER_MERCHANT_UsedToken *t = &ut[i];
+
+ TALER_wallet_token_use_sign (h_contract_terms,
+ &wallet_data_hash, // checked for != NULL
above
+ &token->token_priv,
+ &t->token_sig);
+ t->ub_sig = token->ub_sig;
+ t->issue_pub = token->issue_pub;
+ GNUNET_CRYPTO_eddsa_key_get_public (&token->token_priv.private_key,
+ &t->token_pub.public_key);
+ }
{
struct TALER_MERCHANT_OrderPayHandle *oph;
@@ -544,6 +715,9 @@ TALER_MERCHANT_order_pay (
wallet_data,
num_coins,
pc,
+ num_tokens,
+ ut,
+ j_output_tokens,
pay_cb,
pay_cb_cls);
if (NULL == oph)
diff --git a/src/lib/merchant_api_post_tokenfamilies.c
b/src/lib/merchant_api_post_tokenfamilies.c
index 0c5e18c2..95cbd13b 100644
--- a/src/lib/merchant_api_post_tokenfamilies.c
+++ b/src/lib/merchant_api_post_tokenfamilies.c
@@ -169,6 +169,7 @@ TALER_MERCHANT_token_families_post (
struct GNUNET_TIME_Timestamp valid_after,
struct GNUNET_TIME_Timestamp valid_before,
struct GNUNET_TIME_Relative duration,
+ struct GNUNET_TIME_Relative rounding,
const char *kind,
TALER_MERCHANT_TokenFamiliesPostCallback cb,
void *cb_cls)
@@ -193,6 +194,8 @@ TALER_MERCHANT_token_families_post (
valid_before),
GNUNET_JSON_pack_time_rel ("duration",
duration),
+ GNUNET_JSON_pack_time_rel ("rounding",
+ rounding),
GNUNET_JSON_pack_string ("kind",
kind));
handle = GNUNET_new (struct TALER_MERCHANT_TokenFamiliesPostHandle);
diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c
index 3f9136bc..28c32c2f 100644
--- a/src/testing/test_merchant_api.c
+++ b/src/testing/test_merchant_api.c
@@ -1682,6 +1682,35 @@ run (void *cls,
"EUR:5",
0,
MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_tokenfamilies
("create-upcoming-tokenfamily",
+ merchant_url,
+ MHD_HTTP_NO_CONTENT,
+ "subscription-upcoming",
+ "Upcoming Subscription",
+ "An upcoming subscription
that is not valid yet.",
+ NULL,
+ /* In one day */
+
GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+
GNUNET_TIME_timestamp_get ().abs_time, GNUNET_TIME_UNIT_DAYS)),
+ /* In a year */
+
GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+
GNUNET_TIME_timestamp_get ().abs_time, GNUNET_TIME_UNIT_YEARS)),
+ GNUNET_TIME_UNIT_MONTHS,
+ GNUNET_TIME_UNIT_MONTHS,
+ "subscription"),
+ TALER_TESTING_cmd_merchant_post_orders_choices
("create-order-with-upcoming-output",
+ cred.cfg,
+ merchant_url,
+ MHD_HTTP_CONFLICT,
+
"create-upcoming-tokenfamily",
+ 0,
+ 1,
+ "5-upcoming-output",
+ GNUNET_TIME_UNIT_ZERO_TS,
+
GNUNET_TIME_UNIT_FOREVER_TS,
+ "EUR:5.0"),
TALER_TESTING_cmd_merchant_post_tokenfamilies ("create-tokenfamily",
merchant_url,
MHD_HTTP_NO_CONTENT,
@@ -1689,20 +1718,84 @@ run (void *cls,
"Subscription",
"A subscription.",
NULL,
- GNUNET_TIME_timestamp_get
(),
+ GNUNET_TIME_UNIT_ZERO_TS,
GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_YEARS),
GNUNET_TIME_UNIT_MONTHS,
+ GNUNET_TIME_UNIT_MONTHS,
"subscription"),
- TALER_TESTING_cmd_merchant_post_orders_choices
("create-order-with-choices",
+ TALER_TESTING_cmd_merchant_post_orders_choices ("create-order-with-output",
cred.cfg,
merchant_url,
MHD_HTTP_OK,
"create-tokenfamily",
- "5-choices",
+ 0,
+ 1,
+ "5-output",
GNUNET_TIME_UNIT_ZERO_TS,
GNUNET_TIME_UNIT_FOREVER_TS,
"EUR:5.0"),
-
+ TALER_TESTING_cmd_merchant_pay_order_choices ("pay-order-with-output",
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-order-with-output",
+ "withdraw-coin-1",
+ "EUR:5",
+ "EUR:4.99",
+ NULL,
+ 0,
+ NULL),
+ TALER_TESTING_cmd_merchant_post_orders_choices
("create-order-with-input-and-output",
+ cred.cfg,
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-tokenfamily",
+ 1,
+ 1,
+ "5-input-output",
+ GNUNET_TIME_UNIT_ZERO_TS,
+
GNUNET_TIME_UNIT_FOREVER_TS,
+ "EUR:0.0"),
+ TALER_TESTING_cmd_merchant_pay_order_choices
("pay-order-with-input-and-output",
+ merchant_url,
+ MHD_HTTP_OK,
+
"create-order-with-input-and-output",
+ "",
+ "EUR:0",
+ "EUR:0",
+ NULL,
+ 0,
+ "pay-order-with-output"),
+ // TALER_TESTING_cmd_merchant_pay_order_choices
("idempotent-pay-order-with-input-and-output",
+ // merchant_url,
+ // MHD_HTTP_OK,
+ //
"create-order-with-input-and-output",
+ // "",
+ // "EUR:0",
+ // "EUR:0",
+ // NULL,
+ // 0,
+ // "pay-order-with-output"),
+ TALER_TESTING_cmd_merchant_post_orders_choices
("create-another-order-with-input-and-output",
+ cred.cfg,
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-tokenfamily",
+ 1,
+ 1,
+ "5-input-output-2",
+ GNUNET_TIME_UNIT_ZERO_TS,
+
GNUNET_TIME_UNIT_FOREVER_TS,
+ "EUR:0.0"),
+ TALER_TESTING_cmd_merchant_pay_order_choices ("double-spend-token",
+ merchant_url,
+ MHD_HTTP_CONFLICT,
+
"create-another-order-with-input-and-output",
+ "",
+ "EUR:0",
+ "EUR:0",
+ NULL,
+ 0,
+ "pay-order-with-output"),
TALER_TESTING_cmd_end ()
};
diff --git a/src/testing/test_merchant_order_creation.sh
b/src/testing/test_merchant_order_creation.sh
index 2336ad4e..1b52b4af 100755
--- a/src/testing/test_merchant_order_creation.sh
+++ b/src/testing/test_merchant_order_creation.sh
@@ -250,7 +250,7 @@ echo -n "Creating token family ..."
NOW=$(date +%s)
IN_A_YEAR=$((NOW + 31536000))
STATUS=$(curl 'http://localhost:9966/private/tokenfamilies' \
- -d '{"slug":"test-sub","kind":"subscription","description":"Test token
family","name":"Test
Subscription","valid_after":{"t_s":'$NOW'},"valid_before":{"t_s":'$IN_A_YEAR'},"duration":
{"d_us": 2592000000}}' \
+ -d '{"slug":"test-sub","kind":"subscription","description":"Test token
family","name":"Test
Subscription","valid_after":{"t_s":'$NOW'},"valid_before":{"t_s":'$IN_A_YEAR'},"duration":
{"d_us": 2592000000000}}' \
-w "%{http_code}" -s -o "$LAST_RESPONSE")
if [ "$STATUS" != "204" ]
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 0b84c8a6..97d0b66a 100644
--- a/src/testing/testing_api_cmd_pay_order.c
+++ b/src/testing/testing_api_cmd_pay_order.c
@@ -23,6 +23,12 @@
* @author Christian Grothoff
*/
#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>
@@ -62,6 +68,13 @@ struct PayState
*/
const char *coin_reference;
+ /**
+ * Reference to a command that can provide one or
+ * multiple tokens used as inputs for the payment.
+ * In the form "LABEL0[/INDEX];LABEL1[/INDEX];..."
+ */
+ const char *token_reference;
+
/**
* The merchant base URL.
*/
@@ -92,6 +105,16 @@ struct PayState
*/
struct TALER_MerchantSignatureP merchant_sig;
+ /**
+ * Array of issued tokens, set on success.
+ */
+ struct TALER_MERCHANT_PrivateTokenDetails *issued_tokens;
+
+ /**
+ * Number of tokens in @e issued_tokens.
+ */
+ unsigned int num_issued_tokens;
+
/**
* The session for which the payment is made.
*/
@@ -107,9 +130,131 @@ struct PayState
*/
enum TALER_MerchantConfirmationAlgorithm pos_alg;
+ /**
+ * 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.
@@ -155,6 +300,7 @@ build_coins (struct TALER_MERCHANT_PayCoin **pc,
/* Token syntax is "LABEL[/NUMBER]" */
ctok = strchr (token, '/');
+ // TODO: Check why ci variable is parsed but not used?
ci = 0;
if (NULL != ctok)
{
@@ -236,6 +382,93 @@ build_coins (struct TALER_MERCHANT_PayCoin **pc,
}
+/**
+ * Parse the @a pay_references specification and grow the @a tokens
+ * array with the tokens found, updating @a tokens_num.
+ *
+ * @param[in,out] tokens array of tokens found
+ * @param[in,out] tokens_num length of @a tokens array
+ * @param[in] pay_references string of ; separated references to pay commands
+ that issued the tokens.
+ * @param is interpreter state
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+build_tokens (struct TALER_MERCHANT_UseToken **tokens,
+ unsigned int *tokens_num,
+ char *pay_references,
+ struct TALER_TESTING_Interpreter *is)
+{
+ char *ref;
+
+ for (ref = strtok (pay_references, ";");
+ NULL != ref;
+ ref = strtok (NULL, ";"))
+ {
+ const struct TALER_TESTING_Command *pay_cmd;
+ char *slash;
+ unsigned int index;
+ struct TALER_MERCHANT_UseToken *token;
+
+ /* Reference syntax is "LABEL[/NUMBER]" */
+ slash = strchr (ref, '/');
+ index = 0;
+ if (NULL != slash)
+ {
+ *slash = '\0';
+ slash++;
+ if (1 != sscanf (slash,
+ "%u",
+ &index))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ pay_cmd = TALER_TESTING_interpreter_lookup_command (is, ref);
+
+ if (NULL == pay_cmd)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_array_grow (*tokens,
+ *tokens_num,
+ (*tokens_num) + 1);
+
+ token = &((*tokens)[(*tokens_num) - 1]);
+
+ {
+ const struct TALER_TokenUsePrivateKeyP *token_priv;
+ const struct TALER_TokenIssueSignatureP *issue_sig;
+ const struct TALER_TokenIssuePublicKeyP *issue_pub;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_token_priv (pay_cmd,
+ index,
+ &token_priv));
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_token_issue_sig (pay_cmd,
+ index,
+ &issue_sig));
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_token_issue_pub (pay_cmd,
+ index,
+ &issue_pub));
+
+ token->token_priv = *token_priv;
+ token->ub_sig = *issue_sig;
+ token->issue_pub = *issue_pub;
+ }
+ }
+
+ return GNUNET_OK;
+}
+
/**
* Function called with the result of a /pay operation.
* Checks whether the merchant signature is valid and the
@@ -254,15 +487,51 @@ pay_cb (void *cls,
if (ps->http_status != pr->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u (%d) to command %s\n",
+ "Unexpected response code %u (%d) to command (%s) %s\n",
pr->hr.http_status,
(int) pr->hr.ec,
+ pr->hr.hint,
TALER_TESTING_interpreter_get_current_label (ps->is));
TALER_TESTING_FAIL (ps->is);
}
if (MHD_HTTP_OK == pr->hr.http_status)
{
ps->merchant_sig = pr->details.ok.merchant_sig;
+ 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. */
+ 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;
@@ -327,10 +596,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;
@@ -377,6 +652,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 ()
};
@@ -423,6 +706,169 @@ pay_run (void *cls,
}
GNUNET_free (cr);
}
+ if (NULL != ps->token_reference)
+ {
+ char *tr;
+
+ tr = GNUNET_strdup (ps->token_reference);
+ if (GNUNET_OK !=
+ build_tokens (&use_tokens,
+ &len_use_tokens,
+ tr,
+ is))
+ {
+ GNUNET_array_grow (use_tokens,
+ len_use_tokens,
+ 0);
+ GNUNET_free (tr);
+ TALER_TESTING_FAIL (is);
+ }
+ 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_blind_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].envelope.blinded_pub =
ps->issued_tokens[i].envelope.blinded_pub;
+ }
+
if (GNUNET_OK !=
TALER_TESTING_get_trait_merchant_sig (proposal_cmd,
&merchant_sig))
@@ -438,7 +884,7 @@ pay_run (void *cls,
ps->merchant_url,
ps->session_id,
h_proposal,
- NULL,
+ ps->choice_index,
&ps->total_amount,
&max_fee,
&merchant_pub,
@@ -450,6 +896,10 @@ pay_run (void *cls,
order_id,
npay_coins,
pay_coins,
+ len_use_tokens,
+ use_tokens,
+ len_output_tokens,
+ output_tokens,
&pay_cb,
ps);
GNUNET_array_grow (pay_coins,
@@ -506,6 +956,13 @@ pay_traits (void *cls,
const struct TALER_TESTING_Command *proposal_cmd;
const struct TALER_MerchantPublicKeyP *merchant_pub;
+ if (NULL != ps->token_reference &&
+ index >= ps->num_issued_tokens)
+ {
+ GNUNET_break (0);
+ return GNUNET_NO;
+ }
+
if (NULL ==
(proposal_cmd =
TALER_TESTING_interpreter_lookup_command (ps->is,
@@ -547,6 +1004,12 @@ pay_traits (void *cls,
TALER_TESTING_make_trait_amount (&amount_with_fee),
TALER_TESTING_make_trait_otp_key (ps->pos_key),
TALER_TESTING_make_trait_otp_alg (&ps->pos_alg),
+ TALER_TESTING_make_trait_token_priv (index,
+
&ps->issued_tokens[index].token_priv),
+ TALER_TESTING_make_trait_token_issue_pub (index,
+
&ps->issued_tokens[index].issue_pub),
+ TALER_TESTING_make_trait_token_issue_sig (index,
+
&ps->issued_tokens[index].issue_sig),
TALER_TESTING_trait_end ()
};
@@ -560,14 +1023,16 @@ pay_traits (void *cls,
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_pay_order (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- const char *proposal_reference,
- const char *coin_reference,
- const char *amount_with_fee,
- const char *amount_without_fee,
- const char *session_id)
+TALER_TESTING_cmd_merchant_pay_order_choices (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *proposal_reference,
+ const char *coin_reference,
+ const char *amount_with_fee,
+ const char *amount_without_fee,
+ const char *session_id,
+ int choice_index,
+ const char *token_reference)
{
struct PayState *ps;
@@ -579,6 +1044,8 @@ TALER_TESTING_cmd_merchant_pay_order (const char *label,
ps->amount_with_fee = amount_with_fee;
ps->amount_without_fee = amount_without_fee;
ps->session_id = session_id;
+ ps->token_reference = token_reference;
+ ps->choice_index = choice_index;
{
struct TALER_TESTING_Command cmd = {
.cls = ps,
@@ -593,4 +1060,27 @@ TALER_TESTING_cmd_merchant_pay_order (const char *label,
}
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_pay_order (const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *proposal_reference,
+ const char *coin_reference,
+ const char *amount_with_fee,
+ const char *amount_without_fee,
+ const char *session_id)
+{
+ return TALER_TESTING_cmd_merchant_pay_order_choices (label,
+ merchant_url,
+ http_status,
+ proposal_reference,
+ coin_reference,
+ amount_with_fee,
+ amount_without_fee,
+ session_id,
+ -1,
+ NULL);
+}
+
+
/* end of testing_api_cmd_pay_order.c */
diff --git a/src/testing/testing_api_cmd_post_orders.c
b/src/testing/testing_api_cmd_post_orders.c
index 8f7bd46d..a0f2941c 100644
--- a/src/testing/testing_api_cmd_post_orders.c
+++ b/src/testing/testing_api_cmd_post_orders.c
@@ -64,6 +64,18 @@ struct OrdersState
*/
const char *token_family_reference;
+ /**
+ * How many tokens of the token family created in
+ * @a token_family_reference are required as inputs.
+ */
+ unsigned int num_inputs;
+
+ /**
+ * How many tokens of the token family created in
+ * @a token_family_reference should be issued as outputs.
+ */
+ unsigned int num_outputs;
+
/**
* Contract terms obtained from the backend.
*/
@@ -657,9 +669,10 @@ orders_run3 (void *cls,
TALER_TESTING_FAIL (is);
}
make_choices_json (slug, slug,
- 1, 1,
- GNUNET_TIME_absolute_to_timestamp(now),
- GNUNET_TIME_absolute_to_timestamp(now),
+ ps->num_inputs,
+ ps->num_outputs,
+ GNUNET_TIME_absolute_to_timestamp (now),
+ GNUNET_TIME_absolute_to_timestamp (now),
&ps->choices);
GNUNET_assert (0 ==
@@ -962,6 +975,8 @@ TALER_TESTING_cmd_merchant_post_orders_choices (
const char *merchant_url,
unsigned int http_status,
const char *token_family_reference,
+ unsigned int num_inputs,
+ unsigned int num_outputs,
const char *order_id,
struct GNUNET_TIME_Timestamp refund_deadline,
struct GNUNET_TIME_Timestamp pay_deadline,
@@ -978,6 +993,8 @@ TALER_TESTING_cmd_merchant_post_orders_choices (
&ps->order_terms);
ps->http_status = http_status;
ps->token_family_reference = token_family_reference;
+ ps->num_inputs = num_inputs;
+ ps->num_outputs = num_outputs;
ps->expected_order_id = order_id;
ps->merchant_url = merchant_url;
ps->with_claim = true;
diff --git a/src/testing/testing_api_cmd_post_tokenfamilies.c
b/src/testing/testing_api_cmd_post_tokenfamilies.c
index aafff9ef..c24b98db 100644
--- a/src/testing/testing_api_cmd_post_tokenfamilies.c
+++ b/src/testing/testing_api_cmd_post_tokenfamilies.c
@@ -91,6 +91,11 @@ struct PostTokenFamiliesState
*/
struct GNUNET_TIME_Relative duration;
+ /**
+ * Rounding duation of token family.
+ */
+ struct GNUNET_TIME_Relative rounding;
+
/**
* Kind of the token family. "subscription" or "discount".
*/
@@ -166,6 +171,7 @@ post_tokenfamilies_run (void *cls,
state->valid_after,
state->valid_before,
state->duration,
+ state->rounding,
state->kind,
&post_tokenfamilies_cb,
state);
@@ -241,6 +247,7 @@ TALER_TESTING_cmd_merchant_post_tokenfamilies (
struct GNUNET_TIME_Timestamp valid_after,
struct GNUNET_TIME_Timestamp valid_before,
struct GNUNET_TIME_Relative duration,
+ struct GNUNET_TIME_Relative rounding,
const char *kind) /* "subscription" or "discount" */
{
struct PostTokenFamiliesState *state;
@@ -257,6 +264,7 @@ TALER_TESTING_cmd_merchant_post_tokenfamilies (
state->valid_after = valid_after;
state->valid_before = valid_before;
state->duration = duration;
+ state->rounding = rounding;
state->kind = kind;
{
struct TALER_TESTING_Command cmd = {
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
- [taler-merchant] branch master updated (2e8cfbe8 -> 7d63433c),
gnunet <=
- [taler-merchant] 03/51: add sql migration to add choice_index in db, gnunet, 2024/06/13
- [taler-merchant] 16/51: pay: fetch and parse choices from db, gnunet, 2024/06/13
- [taler-merchant] 02/51: fix compiler warning, gnunet, 2024/06/13
- [taler-merchant] 01/51: add choice_index to private get orders id, gnunet, 2024/06/13
- [taler-merchant] 05/51: fix hash length constraint, gnunet, 2024/06/13
- [taler-merchant] 06/51: check hash before inserting, gnunet, 2024/06/13
- [taler-merchant] 11/51: proper date rounding & fix db key extraction, gnunet, 2024/06/13
- [taler-merchant] 04/51: use better error code, gnunet, 2024/06/13
- [taler-merchant] 24/51: extend merchant lib to allow tokens as inputs, gnunet, 2024/06/13
- [taler-merchant] 13/51: parse tokens in pay handler, gnunet, 2024/06/13