summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/auditor/taler-helper-auditor-coins.c4
-rw-r--r--src/benchmark/taler-aggregator-benchmark.c2
-rw-r--r--src/benchmark/taler-exchange-benchmark.c1
-rw-r--r--src/exchange-tools/taler-exchange-offline.c67
-rw-r--r--src/exchange/taler-exchange-httpd.c12
-rw-r--r--src/exchange/taler-exchange-httpd.h6
-rw-r--r--src/exchange/taler-exchange-httpd_db.c2
-rw-r--r--src/exchange/taler-exchange-httpd_deposit.c4
-rw-r--r--src/exchange/taler-exchange-httpd_extensions.c16
-rw-r--r--src/exchange/taler-exchange-httpd_keys.c100
-rw-r--r--src/exchange/taler-exchange-httpd_management_extensions.c110
-rw-r--r--src/exchange/taler-exchange-httpd_management_post_keys.c2
-rw-r--r--src/exchange/taler-exchange-httpd_melt.c8
-rw-r--r--src/exchange/taler-exchange-httpd_recoup-refresh.c2
-rw-r--r--src/exchange/taler-exchange-httpd_recoup.c2
-rw-r--r--src/exchange/taler-exchange-httpd_refreshes_reveal.c38
-rw-r--r--src/exchange/taler-exchange-httpd_responses.c12
-rw-r--r--src/exchangedb/exchange-0001.sql7
-rw-r--r--src/exchangedb/plugin_exchangedb_postgres.c105
-rw-r--r--src/exchangedb/test_exchangedb.c13
-rw-r--r--src/extensions/extension_age_restriction.c109
-rw-r--r--src/extensions/extensions.c24
-rw-r--r--src/include/taler_crypto_lib.h213
-rw-r--r--src/include/taler_exchange_service.h10
-rw-r--r--src/include/taler_exchangedb_plugin.h28
-rw-r--r--src/include/taler_extensions.h78
-rw-r--r--src/include/taler_json_lib.h11
-rw-r--r--src/include/taler_signatures.h18
-rw-r--r--src/include/taler_testing_lib.h37
-rw-r--r--src/json/json_helper.c31
-rw-r--r--src/lib/exchange_api_common.c34
-rw-r--r--src/lib/exchange_api_deposit.c24
-rw-r--r--src/lib/exchange_api_handle.c58
-rw-r--r--src/lib/exchange_api_link.c12
-rw-r--r--src/lib/exchange_api_management_get_keys.c2
-rw-r--r--src/lib/exchange_api_management_post_extensions.c4
-rw-r--r--src/lib/exchange_api_refresh_common.c1
-rw-r--r--src/lib/exchange_api_refresh_common.h13
-rw-r--r--src/lib/exchange_api_refreshes_reveal.c11
-rw-r--r--src/lib/exchange_api_refund.c5
-rw-r--r--src/lib/exchange_api_withdraw.c10
-rw-r--r--src/testing/Makefile.am3
-rw-r--r--src/testing/test_auditor_api.c24
-rw-r--r--src/testing/test_exchange_api-cs.conf10
-rw-r--r--src/testing/test_exchange_api-rsa.conf10
-rw-r--r--src/testing/test_exchange_api.c93
-rw-r--r--src/testing/test_exchange_api_revocation.c2
-rw-r--r--src/testing/test_kyc_api.c4
-rw-r--r--src/testing/testing_api_cmd_deposit.c27
-rw-r--r--src/testing/testing_api_cmd_insert_deposit.c2
-rw-r--r--src/testing/testing_api_cmd_offline_sign_extensions.c164
-rw-r--r--src/testing/testing_api_cmd_refresh.c56
-rw-r--r--src/testing/testing_api_cmd_withdraw.c91
-rw-r--r--src/testing/testing_api_helpers_exchange.c13
-rw-r--r--src/util/crypto.c302
-rw-r--r--src/util/denom.c4
-rw-r--r--src/util/test_crypto.c10
-rw-r--r--src/util/test_helper_rsa.c11
-rw-r--r--src/util/wallet_signatures.c28
59 files changed, 1754 insertions, 346 deletions
diff --git a/src/auditor/taler-helper-auditor-coins.c b/src/auditor/taler-helper-auditor-coins.c
index d425b9ea..2ed8e5a1 100644
--- a/src/auditor/taler-helper-auditor-coins.c
+++ b/src/auditor/taler-helper-auditor-coins.c
@@ -1224,6 +1224,7 @@ static int
refresh_session_cb (void *cls,
uint64_t rowid,
const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
const struct TALER_Amount *amount_with_fee,
@@ -1286,6 +1287,7 @@ refresh_session_cb (void *cls,
&fee_refresh,
rc,
&h_denom_pub,
+ h_age_commitment,
coin_pub,
coin_sig))
{
@@ -1612,6 +1614,7 @@ deposit_cb (void *cls,
struct TALER_MerchantWireHash h_wire;
struct TALER_DenominationHash h_denom_pub;
struct TALER_Amount deposit_fee;
+ struct TALER_AgeCommitmentHash *h_age_commitment = NULL; /* FIXME-oec */
TALER_denom_pub_hash (denom_pub,
&h_denom_pub);
@@ -1628,6 +1631,7 @@ deposit_cb (void *cls,
&deposit_fee,
&h_wire,
&deposit->h_contract_terms,
+ h_age_commitment, /* FIXME-oec */
NULL /* h_extensions! */,
&h_denom_pub,
deposit->timestamp,
diff --git a/src/benchmark/taler-aggregator-benchmark.c b/src/benchmark/taler-aggregator-benchmark.c
index 005acfef..6452d6fc 100644
--- a/src/benchmark/taler-aggregator-benchmark.c
+++ b/src/benchmark/taler-aggregator-benchmark.c
@@ -300,7 +300,7 @@ add_deposit (const struct Merchant *m)
struct TALER_EXCHANGEDB_Deposit deposit;
uint64_t known_coin_id;
struct TALER_DenominationHash dph;
- struct TALER_AgeHash agh;
+ struct TALER_AgeCommitmentHash agh;
RANDOMIZE (&d.coin.coin_pub);
d.coin.denom_pub_hash = h_denom_pub;
diff --git a/src/benchmark/taler-exchange-benchmark.c b/src/benchmark/taler-exchange-benchmark.c
index 25c3b045..77ef94eb 100644
--- a/src/benchmark/taler-exchange-benchmark.c
+++ b/src/benchmark/taler-exchange-benchmark.c
@@ -366,6 +366,7 @@ run (void *cls,
(TALER_TESTING_cmd_withdraw_amount (wl,
create_reserve_label,
amount_5,
+ 0, /* age restriction off */
MHD_HTTP_OK));
unit[1] =
TALER_TESTING_cmd_deposit_with_retry
diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c
index c5c9584d..55720a1b 100644
--- a/src/exchange-tools/taler-exchange-offline.c
+++ b/src/exchange-tools/taler-exchange-offline.c
@@ -152,6 +152,10 @@ static char *currency;
*/
static char *CFG_exchange_url;
+/**
+ * If age restriction is enabled, the age mask to be used
+ */
+static struct TALER_AgeMask age_mask = {0};
/**
* A subcommand supported by this program.
@@ -1924,6 +1928,7 @@ trigger_upload (const char *exchange_url)
if (0 == strcasecmp (key,
uhs[i].key))
{
+
found = true;
uhs[i].cb (exchange_url,
index,
@@ -3092,6 +3097,7 @@ do_show (char *const *args)
keys = parse_keys_input ("show");
if (NULL == keys)
return;
+
if (GNUNET_OK !=
load_offline_key (GNUNET_NO))
return;
@@ -3254,6 +3260,43 @@ sign_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
/**
+ * Looks up the AGE_RESTRICTED setting for a denomination in the config and
+ * returns the age restriction (mask) accordingly.
+ *
+ * @param section_name Section in the configuration for the particular
+ * denomination.
+ */
+static struct TALER_AgeMask
+load_age_mask (const char*section_name)
+{
+ static const struct TALER_AgeMask null_mask = {0};
+ enum GNUNET_GenericReturnValue ret;
+
+ if (age_mask.mask == 0)
+ return null_mask;
+
+ if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value (
+ kcfg,
+ section_name,
+ "AGE_RESTRICTED")))
+ return null_mask;
+
+ ret = GNUNET_CONFIGURATION_get_value_yesno (kcfg,
+ section_name,
+ "AGE_RESTRICTED");
+ if (GNUNET_YES == ret)
+ return age_mask;
+
+ if (GNUNET_SYSERR == ret)
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section_name,
+ "AGE_RESTRICTED",
+ "Value must be YES or NO\n");
+ return null_mask;
+}
+
+
+/**
* Sign @a denomkeys with offline key.
*
* @param secm_pub_rsa security module public key used to sign the RSA denominations
@@ -3343,7 +3386,10 @@ sign_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub_rsa,
duration = GNUNET_TIME_absolute_get_difference (
stamp_start.abs_time,
stamp_expire_withdraw.abs_time);
- // FIXME-Oec: setup age mask here?
+
+ /* Load the age mask, if applicable to this denomination */
+ denom_pub.age_mask = load_age_mask (section_name);
+
TALER_denom_pub_hash (&denom_pub,
&h_denom_pub);
switch (denom_pub.cipher)
@@ -3604,14 +3650,6 @@ do_extensions_show (char *const *args)
json_t *exts = json_object ();
const struct TALER_Extension *it;
- TALER_extensions_init ();
- if (GNUNET_OK != TALER_extensions_load_taler_config (kcfg))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "error while loading taler config for extensions\n");
- return;
- }
-
for (it = TALER_extensions_get_head ();
NULL != it;
it = it->next)
@@ -3865,6 +3903,17 @@ run (void *cls,
global_ret = EXIT_NOTCONFIGURED;
return;
}
+
+ /* load age mask, if age restriction is enabled */
+ TALER_extensions_init ();
+ if (GNUNET_OK != TALER_extensions_load_taler_config (kcfg))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "error while loading taler config for extensions\n");
+ return;
+ }
+ age_mask = TALER_extensions_age_restriction_ageMask ();
+
ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
&rc);
rc = GNUNET_CURL_gnunet_rc_create (ctx);
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c
index 06ad7ca9..a0d0aa3b 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -128,6 +128,12 @@ char *TEH_currency;
char *TEH_base_url;
/**
+ * Age restriction flags and mask
+ */
+bool TEH_age_restriction_enabled = false;
+struct TALER_AgeMask TEH_age_mask = {0};
+
+/**
* Default timeout in seconds for HTTP requests.
*/
static unsigned int connection_timeout = 30;
@@ -737,6 +743,12 @@ handle_post_management (struct TEH_RequestContext *rc,
return TEH_handler_management_post_wire_fees (rc->connection,
root);
}
+ if (0 == strcmp (args[0],
+ "extensions"))
+ {
+ return TEH_handler_management_post_extensions (rc->connection,
+ root);
+ }
GNUNET_break_op (0);
return r404 (rc->connection,
"/management/*");
diff --git a/src/exchange/taler-exchange-httpd.h b/src/exchange/taler-exchange-httpd.h
index d3b1ba84..ffbce0e9 100644
--- a/src/exchange/taler-exchange-httpd.h
+++ b/src/exchange/taler-exchange-httpd.h
@@ -186,6 +186,12 @@ extern struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
*/
extern char *TEH_currency;
+/*
+ * Age restriction extension state
+ */
+extern bool TEH_age_restriction_enabled;
+extern struct TALER_AgeMask TEH_age_mask;
+
/**
* Our (externally visible) base URL.
*/
diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c
index 3600d793..f331e17d 100644
--- a/src/exchange/taler-exchange-httpd_db.c
+++ b/src/exchange/taler-exchange-httpd_db.c
@@ -50,7 +50,7 @@ TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
{
enum TALER_EXCHANGEDB_CoinKnownStatus cks;
struct TALER_DenominationHash h_denom_pub;
- struct TALER_AgeHash age_hash;
+ struct TALER_AgeCommitmentHash age_hash;
/* make sure coin is 'known' in database */
cks = TEH_plugin->ensure_coin_known (TEH_plugin->cls,
diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c
index 053552f2..d750ec70 100644
--- a/src/exchange/taler-exchange-httpd_deposit.c
+++ b/src/exchange/taler-exchange-httpd_deposit.c
@@ -240,6 +240,9 @@ TEH_handler_deposit (struct MHD_Connection *connection,
&deposit.merchant_pub),
GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
&deposit.h_contract_terms),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &deposit.coin.age_commitment_hash)),
GNUNET_JSON_spec_fixed_auto ("coin_sig",
&deposit.csig),
GNUNET_JSON_spec_timestamp ("timestamp",
@@ -397,6 +400,7 @@ TEH_handler_deposit (struct MHD_Connection *connection,
&deposit.deposit_fee,
&h_wire,
&deposit.h_contract_terms,
+ &deposit.coin.age_commitment_hash,
NULL /* h_extensions! */,
&deposit.coin.denom_pub_hash,
deposit.timestamp,
diff --git a/src/exchange/taler-exchange-httpd_extensions.c b/src/exchange/taler-exchange-httpd_extensions.c
index 8edb24d4..6894a076 100644
--- a/src/exchange/taler-exchange-httpd_extensions.c
+++ b/src/exchange/taler-exchange-httpd_extensions.c
@@ -127,6 +127,16 @@ extension_update_event_cb (void *cls,
GNUNET_break (0);
}
}
+
+ /* Special case age restriction: Update global flag and mask */
+ if (TALER_Extension_AgeRestriction == type)
+ {
+ TEH_age_mask.mask = 0;
+ TEH_age_restriction_enabled =
+ TALER_extensions_age_restriction_is_enabled ();
+ if (TEH_age_restriction_enabled)
+ TEH_age_mask = TALER_extensions_age_restriction_ageMask ();
+ }
}
@@ -151,6 +161,12 @@ TEH_extensions_init ()
return GNUNET_SYSERR;
}
+ /* FIXME: shall we load the extensions from the config right away?
+ * We do have to for now, as otherwise denominations with age restriction
+ * will not have the age mask set right upon initial generation.
+ */
+ TALER_extensions_load_taler_config (TEH_cfg);
+
/* Trigger the initial load of configuration from the db */
for (const struct TALER_Extension *it = TALER_extensions_get_head ();
NULL != it->next;
diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c
index 4b1a6213..d1dfb28b 100644
--- a/src/exchange/taler-exchange-httpd_keys.c
+++ b/src/exchange/taler-exchange-httpd_keys.c
@@ -795,27 +795,17 @@ static struct TALER_AgeMask
load_age_mask (const char*section_name)
{
static const struct TALER_AgeMask null_mask = {0};
- struct TALER_AgeMask age_mask = {0};
- /* TODO: optimize by putting this into global? */
- const struct TALER_Extension *age_ext =
- TALER_extensions_get_by_type (TALER_Extension_AgeRestriction);
-
- // Get the age mask from the extension, if configured
- /* TODO: optimize by putting this into global? */
- if (TALER_extensions_is_enabled (age_ext))
- age_mask = *(struct TALER_AgeMask *) age_ext->config;
- if (0 == age_mask.mask)
- {
- /* Age restriction support is not enabled. Ignore the AGE_RESTRICTED field
- * for the particular denomination and simply return the null_mask
- */
+ struct TALER_AgeMask age_mask = TALER_extensions_age_restriction_ageMask ();
+
+ if (age_mask.mask == 0)
return null_mask;
- }
- if (GNUNET_OK == (GNUNET_CONFIGURATION_have_value (
+ if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value (
TEH_cfg,
section_name,
"AGE_RESTRICTED")))
+ return null_mask;
+
{
enum GNUNET_GenericReturnValue ret;
@@ -1331,6 +1321,8 @@ denomination_info_cb (
dk->meta = *meta;
dk->master_sig = *master_sig;
dk->recoup_possible = recoup_possible;
+ dk->denom_pub.age_mask = meta->age_mask;
+
GNUNET_assert (
GNUNET_OK ==
GNUNET_CONTAINER_multihashmap_put (ksh->denomkey_map,
@@ -1745,7 +1737,7 @@ setup_general_response_headers (struct TEH_KeyStateHandle *ksh,
* @a recoup and @a denoms.
*
* @param[in,out] ksh key state handle we build @a krd for
- * @param[in] denom_keys_hash hash over all the denominatoin keys in @a denoms
+ * @param[in] denom_keys_hash hash over all the denominatoin keys in @a denoms and age_restricted_denoms
* @param last_cpd timestamp to use
* @param signkeys list of sign keys to return
* @param recoup list of revoked keys to return
@@ -1863,7 +1855,8 @@ create_krd (struct TEH_KeyStateHandle *ksh,
int r;
/* skip if not configured == disabled */
- if (NULL == extension->config)
+ if (NULL == extension->config ||
+ NULL == extension->config_json)
continue;
/* flag our findings so far */
@@ -1899,7 +1892,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
json_t *sig;
int r;
- r = json_object_set_new (
+ r = json_object_set (
keys,
"extensions",
extensions);
@@ -1919,14 +1912,14 @@ create_krd (struct TEH_KeyStateHandle *ksh,
json_decref (extensions);
}
- // Special case for age restrictions: if enabled, provide the lits of
+ // Special case for age restrictions: if enabled, provide the list of
// age-restricted denominations.
if (age_restriction_enabled &&
NULL != age_restricted_denoms)
{
GNUNET_assert (
0 ==
- json_object_set_new (
+ json_object_set (
keys,
"age_restricted_denoms",
age_restricted_denoms));
@@ -2005,7 +1998,9 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
json_t *age_restricted_denoms = NULL;
struct GNUNET_TIME_Timestamp last_cpd;
struct GNUNET_CONTAINER_Heap *heap;
- struct GNUNET_HashContext *hash_context;
+ struct GNUNET_HashContext *hash_context = NULL;
+ struct GNUNET_HashContext *hash_context_restricted = NULL;
+ bool have_age_restricted_denoms = false;
sctx.signkeys = json_array ();
GNUNET_assert (NULL != sctx.signkeys);
@@ -2030,19 +2025,23 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
= GNUNET_TIME_relative_min (dkc.min_dk_frequency,
sctx.min_sk_frequency);
}
+
denoms = json_array ();
GNUNET_assert (NULL != denoms);
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
- // If age restriction is enabled, initialize the array of age restricted denoms.
- /* TODO: optimize by putting this into global? */
- if (TALER_extensions_is_enabled_type (TALER_Extension_AgeRestriction))
+ /* If age restriction is enabled, initialize the array of age restricted
+ denoms and prepare a hash for them, separate from the others. We will join
+ those hashes afterwards.*/
+ if (TEH_age_restriction_enabled)
{
age_restricted_denoms = json_array ();
GNUNET_assert (NULL != age_restricted_denoms);
+ hash_context_restricted = GNUNET_CRYPTO_hash_context_start ();
}
last_cpd = GNUNET_TIME_UNIT_ZERO_TS;
- hash_context = GNUNET_CRYPTO_hash_context_start ();
+
{
struct TEH_DenominationKey *dk;
@@ -2056,6 +2055,11 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
{
struct GNUNET_HashCode hc;
+ /* FIXME-oec: Do we need to take hash_context_restricted into account
+ * in this if-branch!? Current tests suggests: no, (they don't fail).
+ * But something seems to be odd about only finishing hash_context.
+ */
+
GNUNET_CRYPTO_hash_context_finish (
GNUNET_CRYPTO_hash_context_copy (hash_context),
&hc);
@@ -2084,14 +2088,14 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
return GNUNET_SYSERR;
}
}
+
last_cpd = dk->meta.start;
- GNUNET_CRYPTO_hash_context_read (hash_context,
- &dk->h_denom_pub,
- sizeof (struct GNUNET_HashCode));
{
json_t *denom;
json_t *array;
+ struct GNUNET_HashContext *hc;
+
denom =
GNUNET_JSON_PACK (
@@ -2118,18 +2122,26 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
TALER_JSON_pack_amount ("fee_refund",
&dk->meta.fee_refund));
- /* Put the denom into the correct array - denoms or age_restricted_denoms -
- * depending on the settings and the properties of the denomination */
- if (NULL != age_restricted_denoms &&
- 0 != dk->meta.age_restrictions.mask)
+ /* Put the denom into the correct array depending on the settings and
+ * the properties of the denomination. Also, we build up the right
+ * hash for the corresponding array. */
+ if (TEH_age_restriction_enabled &&
+ (0 != dk->denom_pub.age_mask.mask))
{
+ have_age_restricted_denoms = true;
array = age_restricted_denoms;
+ hc = hash_context_restricted;
}
else
{
array = denoms;
+ hc = hash_context;
}
+ GNUNET_CRYPTO_hash_context_read (hc,
+ &dk->h_denom_pub,
+ sizeof (struct GNUNET_HashCode));
+
GNUNET_assert (
0 ==
json_array_append_new (
@@ -2138,13 +2150,27 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
}
}
}
+
GNUNET_CONTAINER_heap_destroy (heap);
if (! GNUNET_TIME_absolute_is_zero (last_cpd.abs_time))
{
struct GNUNET_HashCode hc;
+ /* If age restriction is active and we had at least one denomination of
+ * that sort, we simply add the hash of all age restricted denominations at
+ * the end of the others. */
+ if (TEH_age_restriction_enabled && have_age_restricted_denoms)
+ {
+ struct GNUNET_HashCode hcr;
+ GNUNET_CRYPTO_hash_context_finish (hash_context_restricted, &hcr);
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &hcr,
+ sizeof (struct GNUNET_HashCode));
+ }
+
GNUNET_CRYPTO_hash_context_finish (hash_context,
&hc);
+
if (GNUNET_OK !=
create_krd (ksh,
&hc,
@@ -2158,7 +2184,7 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh)
"Failed to generate key response data for %s\n",
GNUNET_TIME_timestamp2s (last_cpd));
json_decref (denoms);
- if (NULL != age_restricted_denoms)
+ if (TEH_age_restriction_enabled && NULL != age_restricted_denoms)
json_decref (age_restricted_denoms);
json_decref (sctx.signkeys);
json_decref (recoup);
@@ -2849,7 +2875,9 @@ load_extension_data (const char *section_name,
TEH_currency);
return GNUNET_SYSERR;
}
- meta->age_restrictions = load_age_mask (section_name);
+
+ meta->age_mask = load_age_mask (section_name);
+
return GNUNET_OK;
}
@@ -2976,7 +3004,7 @@ add_future_denomkey_cb (void *cls,
struct FutureBuilderContext *fbc = cls;
struct HelperDenomination *hd = value;
struct TEH_DenominationKey *dk;
- struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData meta = {0};
dk = GNUNET_CONTAINER_multihashmap_get (fbc->ksh->denomkey_map,
h_denom_pub);
diff --git a/src/exchange/taler-exchange-httpd_management_extensions.c b/src/exchange/taler-exchange-httpd_management_extensions.c
index 17b00006..ab0287e3 100644
--- a/src/exchange/taler-exchange-httpd_management_extensions.c
+++ b/src/exchange/taler-exchange-httpd_management_extensions.c
@@ -31,7 +31,6 @@
#include "taler_extensions.h"
#include "taler_dbevents.h"
-
/**
* Extension carries the necessary data for a particular extension.
*
@@ -91,6 +90,8 @@ set_extensions (void *cls,
return GNUNET_DB_STATUS_HARD_ERROR;
}
+ GNUNET_assert (NULL != ext->config);
+
config = json_dumps (ext->config, JSON_COMPACT | JSON_SORT_KEYS);
if (NULL == config)
{
@@ -140,6 +141,57 @@ set_extensions (void *cls,
}
+static enum GNUNET_GenericReturnValue
+verify_extensions_from_json (
+ json_t *extensions,
+ struct SetExtensionsContext *sec)
+{
+ const char*name;
+ const struct TALER_Extension *extension;
+ size_t i = 0;
+ json_t *blob;
+
+ GNUNET_assert (NULL != extensions);
+ GNUNET_assert (json_is_object (extensions));
+
+ sec->num_extensions = json_object_size (extensions);
+ sec->extensions = GNUNET_new_array (sec->num_extensions,
+ struct Extension);
+
+ json_object_foreach (extensions, name, blob)
+ {
+ int critical = 0;
+ json_t *config;
+ const char *version = NULL;
+
+ /* load and verify criticality, version, etc. */
+ extension = TALER_extensions_get_by_name (name);
+ if (NULL == extension)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "no such extension: %s\n", name);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ TALER_extensions_is_json_config (
+ blob, &critical, &version, &config))
+ return GNUNET_SYSERR;
+
+ if (critical != extension->critical
+ || 0 != strcmp (version, extension->version) // TODO: libtool compare?
+ || NULL == config
+ || GNUNET_OK != extension->test_json_config (config))
+ return GNUNET_SYSERR;
+
+ sec->extensions[i].type = extension->type;
+ sec->extensions[i].config = config;
+ }
+
+ return GNUNET_OK;
+}
+
+
MHD_RESULT
TEH_handler_management_post_extensions (
struct MHD_Connection *connection,
@@ -204,57 +256,18 @@ TEH_handler_management_post_extensions (
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Received /management/extensions\n");
- sec.num_extensions = json_object_size (extensions);
- sec.extensions = GNUNET_new_array (sec.num_extensions,
- struct Extension);
-
/* Now parse individual extensions and signatures from those objects. */
+ if (GNUNET_OK !=
+ verify_extensions_from_json (extensions, &sec))
{
- const struct TALER_Extension *extension = NULL;
- const char *name;
- json_t *config;
- int idx = 0;
-
- json_object_foreach (extensions, name, config){
-
- /* 1. Make sure name refers to a supported extension */
- extension = TALER_extensions_get_by_name (name);
- if (NULL == extension)
- {
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "invalid extension type");
- goto CLEANUP;
- }
-
- sec.extensions[idx].config = config;
- sec.extensions[idx].type = extension->type;
-
- /* 2. Make sure the config is sound */
- if (GNUNET_OK !=
- extension->test_json_config (
- sec.extensions[idx].config))
- {
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "invalid configuration for extension");
- goto CLEANUP;
- }
-
- /* We have a validly signed JSON object for the extension. Increment its
- * refcount.
- */
- json_incref (sec.extensions[idx].config);
- idx++;
-
- } /* json_object_foreach */
+ GNUNET_JSON_parse_free (top_spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "invalid object");
}
-
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Received %u extensions\n",
sec.num_extensions);
@@ -281,6 +294,7 @@ TEH_handler_management_post_extensions (
NULL,
0);
+
CLEANUP:
for (unsigned int i = 0; i < sec.num_extensions; i++)
{
diff --git a/src/exchange/taler-exchange-httpd_management_post_keys.c b/src/exchange/taler-exchange-httpd_management_post_keys.c
index f0c3f1f3..c353a995 100644
--- a/src/exchange/taler-exchange-httpd_management_post_keys.c
+++ b/src/exchange/taler-exchange-httpd_management_post_keys.c
@@ -204,6 +204,7 @@ add_keys (void *cls,
TALER_denom_pub_free (&denom_pub);
return GNUNET_DB_STATUS_HARD_ERROR;
}
+
if (is_active)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -211,6 +212,7 @@ add_keys (void *cls,
GNUNET_h2s (&d->h_denom_pub.hash));
continue; /* skip, already known */
}
+
qs = TEH_plugin->add_denomination_key (
TEH_plugin->cls,
&d->h_denom_pub,
diff --git a/src/exchange/taler-exchange-httpd_melt.c b/src/exchange/taler-exchange-httpd_melt.c
index 021b629b..8bfdf8ce 100644
--- a/src/exchange/taler-exchange-httpd_melt.c
+++ b/src/exchange/taler-exchange-httpd_melt.c
@@ -279,6 +279,7 @@ check_melt_valid (struct MHD_Connection *connection,
&mret);
if (NULL == dk)
return mret;
+
if (GNUNET_TIME_absolute_is_past (dk->meta.expire_legal.abs_time))
{
/* Way too late now, even zombies have expired */
@@ -288,6 +289,7 @@ check_melt_valid (struct MHD_Connection *connection,
TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
"MELT");
}
+
if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
{
/* This denomination is not yet valid */
@@ -300,9 +302,11 @@ check_melt_valid (struct MHD_Connection *connection,
rmc->coin_refresh_fee = dk->meta.fee_refresh;
rmc->coin_value = dk->meta.value;
+
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Melted coin's denomination is worth %s\n",
TALER_amount2s (&dk->meta.value));
+
/* sanity-check that "total melt amount > melt fee" */
if (0 <
TALER_amount_cmp (&rmc->coin_refresh_fee,
@@ -332,6 +336,7 @@ check_melt_valid (struct MHD_Connection *connection,
&rmc->coin_refresh_fee,
&rmc->refresh_session.rc,
&rmc->refresh_session.coin.denom_pub_hash,
+ &rmc->refresh_session.coin.age_commitment_hash,
&rmc->refresh_session.coin.coin_pub,
&rmc->refresh_session.coin_sig))
{
@@ -407,6 +412,9 @@ TEH_handler_melt (struct MHD_Connection *connection,
&rmc.refresh_session.coin.denom_sig),
GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
&rmc.refresh_session.coin.denom_pub_hash),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("age_commitment_hash",
+ &rmc.refresh_session.coin.age_commitment_hash)),
GNUNET_JSON_spec_fixed_auto ("confirm_sig",
&rmc.refresh_session.coin_sig),
TALER_JSON_spec_amount ("value_with_fee",
diff --git a/src/exchange/taler-exchange-httpd_recoup-refresh.c b/src/exchange/taler-exchange-httpd_recoup-refresh.c
index 829e2cbd..bbf6defe 100644
--- a/src/exchange/taler-exchange-httpd_recoup-refresh.c
+++ b/src/exchange/taler-exchange-httpd_recoup-refresh.c
@@ -251,7 +251,7 @@ verify_and_execute_recoup_refresh (
if (GNUNET_OK !=
TALER_denom_blind (&dk->denom_pub,
coin_bks,
- NULL, /* FIXME-Oec: TALER_AgeHash * */
+ NULL, /* FIXME-Oec: TALER_AgeCommitmentHash * */
&coin->coin_pub,
exchange_vals,
&c_hash,
diff --git a/src/exchange/taler-exchange-httpd_recoup.c b/src/exchange/taler-exchange-httpd_recoup.c
index ea319d11..4ac997e9 100644
--- a/src/exchange/taler-exchange-httpd_recoup.c
+++ b/src/exchange/taler-exchange-httpd_recoup.c
@@ -256,7 +256,7 @@ verify_and_execute_recoup (
if (GNUNET_OK !=
TALER_denom_blind (&dk->denom_pub,
coin_bks,
- NULL, /* FIXME-Oec: TALER_AgeHash * */
+ NULL, /* FIXME-Oec: TALER_AgeCommitmentHash * */
&coin->coin_pub,
exchange_vals,
&c_hash,
diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.c b/src/exchange/taler-exchange-httpd_refreshes_reveal.c
index 0d8f7bf9..1f0782aa 100644
--- a/src/exchange/taler-exchange-httpd_refreshes_reveal.c
+++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.c
@@ -281,6 +281,7 @@ check_commitment (struct RevealContext *rctx,
alg_value,
&bks,
&coin_priv,
+ NULL, /* FIXME-Oec, struct TALER_AgeCommitmentHash * */
&c_hash,
&pd));
if (TALER_DENOMINATION_CS == dk->cipher)
@@ -380,6 +381,7 @@ check_commitment (struct RevealContext *rctx,
* @param rctx context for the operation, partially built at this time
* @param link_sigs_json link signatures in JSON format
* @param new_denoms_h_json requests for fresh coins to be created
+ * @param old_age_commitment_json age commitment that went into the withdrawal, maybe NULL
* @param coin_evs envelopes of gamma-selected coins to be signed
* @return MHD result code
*/
@@ -388,6 +390,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
struct RevealContext *rctx,
const json_t *link_sigs_json,
const json_t *new_denoms_h_json,
+ const json_t *old_age_commitment_json,
const json_t *coin_evs)
{
unsigned int num_fresh_coins = json_array_size (new_denoms_h_json);
@@ -412,6 +415,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
NULL);
}
+
/* Parse denomination key hashes */
for (unsigned int i = 0; i<num_fresh_coins; i++)
{
@@ -537,6 +541,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
goto cleanup;
}
}
+
/* Parse link signatures array */
for (unsigned int i = 0; i<num_fresh_coins; i++)
{
@@ -554,6 +559,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
-1);
if (GNUNET_OK != res)
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+
/* Check signature */
if (GNUNET_OK !=
TALER_wallet_link_verify (
@@ -561,6 +567,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
&rctx->gamma_tp,
&rrcs[i].coin_envelope_hash,
&rctx->melt.session.coin.coin_pub,
+ NULL, // TODO-oec: calculate the correct h_age_commitment
&rrcs[i].orig_coin_link_sig))
{
GNUNET_break_op (0);
@@ -592,6 +599,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
goto cleanup;
}
}
+
rctx->dks = dks;
rctx->rcds = rcds;
rctx->rrcs = rrcs;
@@ -604,6 +612,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Creating %u signatures\n",
(unsigned int) rctx->num_fresh_coins);
+
/* create fresh coin signatures */
for (unsigned int i = 0; i<rctx->num_fresh_coins; i++)
{
@@ -622,8 +631,10 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
goto cleanup;
}
}
+
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Signatures ready, starting DB interaction\n");
+
/* Persist operation result in DB */
{
enum GNUNET_DB_QueryStatus qs;
@@ -678,11 +689,18 @@ cleanup:
* revealed information is valid then returns the signed refreshed
* coins.
*
+ * If the denomination has age restriction support, the array of EDDSA public
+ * keys, one for each age group that was activated during the withdrawal
+ * by the parent/ward, must be provided in old_age_commitment. The hash of
+ * this array must be the same as the h_age_commitment of the persisted reveal
+ * request.
+ *
* @param connection the MHD connection to handle
* @param rctx context for the operation, partially built at this time
* @param tp_json private transfer keys in JSON format
* @param link_sigs_json link signatures in JSON format
* @param new_denoms_h_json requests for fresh coins to be created
+ * @param old_age_commitment_json array of EDDSA public keys in JSON, used for age restriction, maybe NULL
* @param coin_evs envelopes of gamma-selected coins to be signed
* @return MHD result code
*/
@@ -692,6 +710,7 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection,
const json_t *tp_json,
const json_t *link_sigs_json,
const json_t *new_denoms_h_json,
+ const json_t *old_age_commitment_json,
const json_t *coin_evs)
{
unsigned int num_fresh_coins = json_array_size (new_denoms_h_json);
@@ -727,6 +746,19 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection,
"new_denoms/link_sigs");
}
+ /* Sanity check of age commitment: If it was provided, it _must_ be an array
+ * of the size the # of age groups */
+ if (NULL != old_age_commitment_json
+ && TALER_extensions_age_restriction_num_groups () !=
+ json_array_size (old_age_commitment_json))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
+ "old_age_commitment");
+ }
+
/* Parse transfer private keys array */
for (unsigned int i = 0; i<num_tprivs; i++)
{
@@ -750,6 +782,7 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection,
rctx,
link_sigs_json,
new_denoms_h_json,
+ old_age_commitment_json,
coin_evs);
}
@@ -763,6 +796,7 @@ TEH_handler_reveal (struct TEH_RequestContext *rc,
json_t *transfer_privs;
json_t *link_sigs;
json_t *new_denoms_h;
+ json_t *old_age_commitment = NULL;
struct RevealContext rctx;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("transfer_pub",
@@ -775,6 +809,9 @@ TEH_handler_reveal (struct TEH_RequestContext *rc,
&coin_evs),
GNUNET_JSON_spec_json ("new_denoms_h",
&new_denoms_h),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_json ("old_age_commitment",
+ &old_age_commitment)),
GNUNET_JSON_spec_end ()
};
@@ -836,6 +873,7 @@ TEH_handler_reveal (struct TEH_RequestContext *rc,
transfer_privs,
link_sigs,
new_denoms_h,
+ old_age_commitment,
coin_evs);
GNUNET_JSON_parse_free (spec);
return res;
diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c
index 55b23044..00f04717 100644
--- a/src/exchange/taler-exchange-httpd_responses.c
+++ b/src/exchange/taler-exchange-httpd_responses.c
@@ -73,6 +73,7 @@ TEH_RESPONSE_compile_transaction_history (
&deposit->deposit_fee,
&h_wire,
&deposit->h_contract_terms,
+ NULL, /* h_age_commitment, FIXME-oec */
NULL /* h_extensions! */,
&deposit->h_denom_pub,
deposit->timestamp,
@@ -122,6 +123,7 @@ TEH_RESPONSE_compile_transaction_history (
{
const struct TALER_EXCHANGEDB_MeltListEntry *melt =
pos->details.melt;
+ const struct TALER_AgeCommitmentHash *phac = NULL;
#if ENABLE_SANITY_CHECKS
if (GNUNET_OK !=
@@ -129,6 +131,7 @@ TEH_RESPONSE_compile_transaction_history (
&melt->melt_fee,
&melt->rc,
&melt->h_denom_pub,
+ &melt->h_age_commitment,
coin_pub,
&melt->coin_sig))
{
@@ -137,6 +140,12 @@ TEH_RESPONSE_compile_transaction_history (
return NULL;
}
#endif
+
+ /* Age restriction is optional. We communicate a NULL value to
+ * JSON_PACK below */
+ if (! TALER_AgeCommitmentHash_isNullOrZero (&melt->h_age_commitment))
+ phac = &melt->h_age_commitment;
+
if (0 !=
json_array_append_new (
history,
@@ -151,6 +160,9 @@ TEH_RESPONSE_compile_transaction_history (
&melt->rc),
GNUNET_JSON_pack_data_auto ("h_denom_pub",
&melt->h_denom_pub),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ phac)),
GNUNET_JSON_pack_data_auto ("coin_sig",
&melt->coin_sig))))
{
diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql
index 1111f381..df07e025 100644
--- a/src/exchangedb/exchange-0001.sql
+++ b/src/exchangedb/exchange-0001.sql
@@ -25,7 +25,7 @@ CREATE TABLE IF NOT EXISTS denominations
(denominations_serial BIGSERIAL UNIQUE
,denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64)
,denom_type INT4 NOT NULL DEFAULT (1) -- 1 == RSA (for now, remove default later!)
- ,age_restrictions INT4 NOT NULL DEFAULT (0)
+ ,age_mask INT4 NOT NULL DEFAULT (0)
,denom_pub BYTEA NOT NULL
,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
,valid_from INT8 NOT NULL
@@ -47,7 +47,7 @@ COMMENT ON TABLE denominations
IS 'Main denominations table. All the valid denominations the exchange knows about.';
COMMENT ON COLUMN denominations.denom_type
IS 'determines cipher type for blind signatures used with this denomination; 0 is for RSA';
-COMMENT ON COLUMN denominations.age_restrictions
+COMMENT ON COLUMN denominations.age_mask
IS 'bitmask with the age restrictions that are being used for this denomination; 0 if denomination does not support the use of age restrictions';
COMMENT ON COLUMN denominations.denominations_serial
IS 'needed for exchange-auditor replication logic';
@@ -345,6 +345,7 @@ CREATE TABLE IF NOT EXISTS refresh_commitments
(melt_serial_id BIGSERIAL -- UNIQUE
,rc BYTEA PRIMARY KEY CHECK (LENGTH(rc)=64)
,old_coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE
+ ,h_age_commitment BYTEA CHECK(LENGTH(h_age_commitment)=32)
,old_coin_sig BYTEA NOT NULL CHECK(LENGTH(old_coin_sig)=64)
,amount_with_fee_val INT8 NOT NULL
,amount_with_fee_frac INT4 NOT NULL
@@ -359,6 +360,8 @@ COMMENT ON COLUMN refresh_commitments.rc
IS 'Commitment made by the client, hash over the various client inputs in the cut-and-choose protocol';
COMMENT ON COLUMN refresh_commitments.old_coin_pub
IS 'Coin being melted in the refresh process.';
+COMMENT ON COLUMN refresh_commitments.h_age_commitment
+ IS '(optional) age commitment that was involved in the minting process of the coin, may be NULL.';
CREATE TABLE IF NOT EXISTS refresh_commitments_default
PARTITION OF refresh_commitments
FOR VALUES WITH (MODULUS 1, REMAINDER 0);
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c
index 98724fa0..878c36f9 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -231,10 +231,11 @@ prepare_statements (struct PostgresClosure *pg)
",fee_refresh_frac"
",fee_refund_val"
",fee_refund_frac"
+ ",age_mask"
") VALUES "
"($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
- " $11, $12, $13, $14, $15, $16, $17);",
- 17),
+ " $11, $12, $13, $14, $15, $16, $17, $18);",
+ 18),
/* Used in #postgres_iterate_denomination_info() */
GNUNET_PQ_make_prepare (
"denomination_iterate",
@@ -255,6 +256,7 @@ prepare_statements (struct PostgresClosure *pg)
",fee_refund_val"
",fee_refund_frac"
",denom_pub"
+ ",age_mask"
" FROM denominations;",
0),
/* Used in #postgres_iterate_denominations() */
@@ -278,6 +280,7 @@ prepare_statements (struct PostgresClosure *pg)
",fee_refund_val"
",fee_refund_frac"
",denom_pub"
+ ",age_mask"
" FROM denominations"
" LEFT JOIN "
" denomination_revocations USING (denominations_serial);",
@@ -341,6 +344,7 @@ prepare_statements (struct PostgresClosure *pg)
",fee_refresh_frac"
",fee_refund_val"
",fee_refund_frac"
+ ",age_mask"
" FROM denominations"
" WHERE denom_pub_hash=$1;",
1),
@@ -825,6 +829,7 @@ prepare_statements (struct PostgresClosure *pg)
",denoms.fee_refresh_frac"
",old_coin_pub"
",old_coin_sig"
+ ",h_age_commitment"
",amount_with_fee_val"
",amount_with_fee_frac"
",noreveal_index"
@@ -843,6 +848,7 @@ prepare_statements (struct PostgresClosure *pg)
"SELECT"
" denom.denom_pub"
",kc.coin_pub AS old_coin_pub"
+ ",h_age_commitment"
",old_coin_sig"
",amount_with_fee_val"
",amount_with_fee_frac"
@@ -1842,6 +1848,7 @@ prepare_statements (struct PostgresClosure *pg)
",fee_refresh_frac"
",fee_refund_val"
",fee_refund_frac"
+ ",age_mask"
" FROM denominations"
" WHERE denom_pub_hash=$1;",
1),
@@ -2069,7 +2076,6 @@ prepare_statements (struct PostgresClosure *pg)
"SELECT"
" denominations_serial AS serial"
",denom_type"
- ",age_restrictions"
",denom_pub"
",master_sig"
",valid_from"
@@ -2086,6 +2092,7 @@ prepare_statements (struct PostgresClosure *pg)
",fee_refresh_frac"
",fee_refund_val"
",fee_refund_frac"
+ ",age_mask"
" FROM denominations"
" WHERE denominations_serial > $1"
" ORDER BY denominations_serial ASC;",
@@ -2389,10 +2396,11 @@ prepare_statements (struct PostgresClosure *pg)
",fee_refresh_frac"
",fee_refund_val"
",fee_refund_frac"
+ ",age_mask"
") VALUES "
"($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
- " $11, $12, $13, $14, $15, $16, $17, $18);",
- 18),
+ " $11, $12, $13, $14, $15, $16, $17, $18, $19);",
+ 19),
GNUNET_PQ_make_prepare (
"insert_into_table_denomination_revocations",
"INSERT INTO denomination_revocations"
@@ -3096,9 +3104,12 @@ postgres_insert_denomination_info (
TALER_PQ_query_param_amount_nbo (&issue->properties.fee_deposit),
TALER_PQ_query_param_amount_nbo (&issue->properties.fee_refresh),
TALER_PQ_query_param_amount_nbo (&issue->properties.fee_refund),
+ GNUNET_PQ_query_param_uint32 (&denom_pub->age_mask.mask),
GNUNET_PQ_query_param_end
};
+ GNUNET_assert (denom_pub->age_mask.mask == issue->age_mask.mask);
+
GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
GNUNET_TIME_timestamp_ntoh (
issue->properties.start).abs_time));
@@ -3172,6 +3183,8 @@ postgres_get_denomination_info (
&issue->properties.fee_refresh),
TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refund",
&issue->properties.fee_refund),
+ GNUNET_PQ_result_spec_uint32 ("age_mask",
+ &issue->age_mask.mask),
GNUNET_PQ_result_spec_end
};
@@ -3258,12 +3271,15 @@ domination_cb_helper (void *cls,
&issue.properties.fee_refund),
TALER_PQ_result_spec_denom_pub ("denom_pub",
&denom_pub),
+ GNUNET_PQ_result_spec_uint32 ("age_mask",
+ &issue.age_mask.mask),
GNUNET_PQ_result_spec_end
};
memset (&issue.properties.master,
0,
sizeof (issue.properties.master));
+
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
@@ -3272,6 +3288,13 @@ domination_cb_helper (void *cls,
GNUNET_break (0);
return;
}
+
+ /* Unfortunately we have to carry the age mask in both, the
+ * TALER_DenominationPublicKey and
+ * TALER_EXCHANGEDB_DenominationKeyInformationP at different times.
+ * Here we use _both_ so let's make sure the values are the same. */
+ denom_pub.age_mask = issue.age_mask;
+
issue.properties.purpose.size
= htonl (sizeof (struct TALER_DenominationKeyValidityPS));
issue.properties.purpose.purpose
@@ -3357,10 +3380,10 @@ dominations_cb_helper (void *cls,
for (unsigned int i = 0; i<num_results; i++)
{
- struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
- struct TALER_DenominationPublicKey denom_pub;
- struct TALER_MasterSignatureP master_sig;
- struct TALER_DenominationHash h_denom_pub;
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData meta = {0};
+ struct TALER_DenominationPublicKey denom_pub = {0};
+ struct TALER_MasterSignatureP master_sig = {0};
+ struct TALER_DenominationHash h_denom_pub = {0};
bool revoked;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("master_sig",
@@ -3387,6 +3410,8 @@ dominations_cb_helper (void *cls,
&meta.fee_refund),
TALER_PQ_result_spec_denom_pub ("denom_pub",
&denom_pub),
+ GNUNET_PQ_result_spec_uint32 ("age_mask",
+ &meta.age_mask.mask),
GNUNET_PQ_result_spec_end
};
@@ -3398,6 +3423,10 @@ dominations_cb_helper (void *cls,
GNUNET_break (0);
return;
}
+
+ /* make sure the mask information is the same */
+ denom_pub.age_mask = meta.age_mask;
+
TALER_denom_pub_hash (&denom_pub,
&h_denom_pub);
dic->cb (dic->cb_cls,
@@ -5741,11 +5770,13 @@ postgres_ensure_coin_known (void *cls,
const struct TALER_CoinPublicInfo *coin,
uint64_t *known_coin_id,
struct TALER_DenominationHash *denom_hash,
- struct TALER_AgeHash *age_hash)
+ struct TALER_AgeCommitmentHash *age_hash)
{
struct PostgresClosure *pg = cls;
enum GNUNET_DB_QueryStatus qs;
bool existed;
+ bool is_denom_pub_hash_null = false;
+ bool is_age_hash_null = false;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (&coin->coin_pub),
GNUNET_PQ_query_param_auto_from_type (&coin->denom_pub_hash),
@@ -5753,24 +5784,22 @@ postgres_ensure_coin_known (void *cls,
TALER_PQ_query_param_denom_sig (&coin->denom_sig),
GNUNET_PQ_query_param_end
};
- bool is_null = false;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_bool ("existed",
&existed),
GNUNET_PQ_result_spec_uint64 ("known_coin_id",
known_coin_id),
GNUNET_PQ_result_spec_allow_null (
- GNUNET_PQ_result_spec_auto_from_type ("age_hash",
- age_hash),
- &is_null),
- GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
denom_hash),
- &is_null),
+ &is_denom_pub_hash_null),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_hash",
+ age_hash),
+ &is_age_hash_null),
GNUNET_PQ_result_spec_end
};
- GNUNET_break (GNUNET_is_zero (&coin->age_commitment_hash)); // FIXME-OEC
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"insert_known_coin",
params,
@@ -5790,21 +5819,24 @@ postgres_ensure_coin_known (void *cls,
return TALER_EXCHANGEDB_CKS_ADDED;
break; /* continued below */
}
- if ( (! is_null) &&
- (0 != GNUNET_memcmp (age_hash,
- &coin->age_commitment_hash)) )
+
+ if ( (! is_denom_pub_hash_null) &&
+ (0 != GNUNET_memcmp (&denom_hash->hash,
+ &coin->denom_pub_hash.hash)) )
{
- GNUNET_break (GNUNET_is_zero (age_hash)); // FIXME-OEC
GNUNET_break_op (0);
- return TALER_EXCHANGEDB_CKS_AGE_CONFLICT;
+ return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT;
}
- if ( (! is_null) &&
- (0 != GNUNET_memcmp (denom_hash,
- &coin->denom_pub_hash)) )
+
+ if ( (! is_age_hash_null) &&
+ (0 != GNUNET_memcmp (age_hash,
+ &coin->age_commitment_hash)) )
{
+ GNUNET_break (GNUNET_is_zero (age_hash));
GNUNET_break_op (0);
- return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT;
+ return TALER_EXCHANGEDB_CKS_AGE_CONFLICT;
}
+
return TALER_EXCHANGEDB_CKS_PRESENT;
}
@@ -6030,6 +6062,7 @@ postgres_get_melt (void *cls,
uint64_t *melt_serial_id)
{
struct PostgresClosure *pg = cls;
+ bool h_age_commitment_is_null;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (rc),
GNUNET_PQ_query_param_end
@@ -6046,6 +6079,10 @@ postgres_get_melt (void *cls,
&melt->session.coin.coin_pub),
GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
&melt->session.coin_sig),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("h_age_commitment",
+ &melt->session.h_age_commitment),
+ &h_age_commitment_is_null),
TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
&melt->session.amount_with_fee),
GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
@@ -6061,6 +6098,11 @@ postgres_get_melt (void *cls,
"get_melt",
params,
rs);
+ if (h_age_commitment_is_null)
+ memset (&melt->session.h_age_commitment,
+ 0,
+ sizeof(melt->session.h_age_commitment));
+
melt->session.rc = *rc;
return qs;
}
@@ -8225,6 +8267,8 @@ refreshs_serial_helper_cb (void *cls,
struct TALER_DenominationPublicKey denom_pub;
struct TALER_CoinSpendPublicKeyP coin_pub;
struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_AgeCommitmentHash h_age_commitment;
+ bool ac_isnull;
struct TALER_Amount amount_with_fee;
uint32_t noreveal_index;
uint64_t rowid;
@@ -8232,6 +8276,10 @@ refreshs_serial_helper_cb (void *cls,
struct GNUNET_PQ_ResultSpec rs[] = {
TALER_PQ_result_spec_denom_pub ("denom_pub",
&denom_pub),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("h_age_commitment",
+ &h_age_commitment),
+ &ac_isnull),
GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
&coin_pub),
GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
@@ -8257,9 +8305,11 @@ refreshs_serial_helper_cb (void *cls,
rsc->status = GNUNET_SYSERR;
return;
}
+
ret = rsc->cb (rsc->cb_cls,
rowid,
&denom_pub,
+ ac_isnull ? NULL : &h_age_commitment,
&coin_pub,
&coin_sig,
&amount_with_fee,
@@ -10198,6 +10248,8 @@ postgres_lookup_denomination_key (
&meta->fee_refresh),
TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
&meta->fee_refund),
+ GNUNET_PQ_result_spec_uint32 ("age_mask",
+ &meta->age_mask.mask),
GNUNET_PQ_result_spec_end
};
@@ -10241,6 +10293,7 @@ postgres_add_denomination_key (
TALER_PQ_query_param_amount (&meta->fee_deposit),
TALER_PQ_query_param_amount (&meta->fee_refresh),
TALER_PQ_query_param_amount (&meta->fee_refund),
+ GNUNET_PQ_query_param_uint32 (&meta->age_mask.mask),
GNUNET_PQ_query_param_end
};
diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c
index 0622e069..f9e64fdc 100644
--- a/src/exchangedb/test_exchangedb.c
+++ b/src/exchangedb/test_exchangedb.c
@@ -458,6 +458,7 @@ static unsigned int auditor_row_cnt;
* @param cls closure
* @param rowid unique serial ID for the refresh session in our DB
* @param denom_pub denomination of the @a coin_pub
+ * @param h_age_commitment hash of age commitment that went into the minting, may be NULL
* @param coin_pub public key of the coin
* @param coin_sig signature from the coin
* @param amount_with_fee amount that was deposited including fee
@@ -470,6 +471,8 @@ static enum GNUNET_GenericReturnValue
audit_refresh_session_cb (void *cls,
uint64_t rowid,
const struct TALER_DenominationPublicKey *denom_pub,
+ const struct
+ TALER_AgeCommitmentHash *h_age_commitment,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
const struct TALER_Amount *amount_with_fee,
@@ -1475,8 +1478,8 @@ run (void *cls)
{
struct TALER_PlanchetDetail pd;
struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_AgeHash age_hash;
- struct TALER_AgeHash *p_ah[2] = {
+ struct TALER_AgeCommitmentHash age_hash;
+ struct TALER_AgeCommitmentHash *p_ah[2] = {
NULL,
&age_hash
};
@@ -1597,7 +1600,7 @@ run (void *cls)
deadline = GNUNET_TIME_timestamp_get ();
{
struct TALER_DenominationHash dph;
- struct TALER_AgeHash agh;
+ struct TALER_AgeCommitmentHash agh;
FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
plugin->ensure_coin_known (plugin->cls,
@@ -1847,7 +1850,7 @@ run (void *cls)
uint64_t new_known_coin_id;
struct TALER_CoinPublicInfo new_coin;
struct TALER_DenominationHash dph;
- struct TALER_AgeHash agh;
+ struct TALER_AgeCommitmentHash agh;
bool recoup_ok;
bool internal_failure;
@@ -2201,7 +2204,7 @@ run (void *cls)
{
uint64_t known_coin_id;
struct TALER_DenominationHash dph;
- struct TALER_AgeHash agh;
+ struct TALER_AgeCommitmentHash agh;
FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
plugin->ensure_coin_known (plugin->cls,
diff --git a/src/extensions/extension_age_restriction.c b/src/extensions/extension_age_restriction.c
index a9ffb7f1..28b2dbb1 100644
--- a/src/extensions/extension_age_restriction.c
+++ b/src/extensions/extension_age_restriction.c
@@ -23,6 +23,19 @@
#include "taler_extensions.h"
#include "stdint.h"
+/**
+ * Carries all the information we need for age restriction
+ */
+struct age_restriction_config
+{
+ struct TALER_AgeMask mask;
+ size_t num_groups;
+};
+
+/**
+ * Global config for this extension
+ */
+static struct age_restriction_config _config = {0};
/**
* @param groups String representation of the age groups. Must be of the form
@@ -146,6 +159,9 @@ age_restriction_disable (
json_decref (this->config_json);
this->config_json = NULL;
}
+
+ _config.mask.mask = 0;
+ _config.num_groups = 0;
}
@@ -197,7 +213,6 @@ age_restriction_load_taler_config (
mask.mask = TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK;
-
ret = GNUNET_OK;
if (groups != NULL)
@@ -208,7 +223,19 @@ age_restriction_load_taler_config (
}
if (GNUNET_OK == ret)
- this->config = (void *) (size_t) mask.mask;
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "setting age mask to %x with #groups: %d\n", mask.mask,
+ __builtin_popcount (mask.mask) - 1);
+ _config.mask.mask = mask.mask;
+ _config.num_groups = __builtin_popcount (mask.mask) - 1; /* no underflow, first bit always set */
+ this->config = &_config;
+
+ /* Note: we do now have _config set, however this->config_json is NOT set,
+ * i.e. the extension is not yet active! For age restriction to become
+ * active, load_json_config must have been called. */
+ }
+
GNUNET_free (groups);
return ret;
@@ -223,12 +250,12 @@ age_restriction_load_taler_config (
static enum GNUNET_GenericReturnValue
age_restriction_load_json_config (
struct TALER_Extension *this,
- json_t *config)
+ json_t *jconfig)
{
struct TALER_AgeMask mask = {0};
enum GNUNET_GenericReturnValue ret;
- ret = TALER_JSON_parse_agemask (config, &mask);
+ ret = TALER_JSON_parse_age_groups (jconfig, &mask);
if (GNUNET_OK != ret)
return ret;
@@ -239,16 +266,28 @@ age_restriction_load_json_config (
if (TALER_Extension_AgeRestriction != this->type)
return GNUNET_SYSERR;
- if (NULL != this->config)
- GNUNET_free (this->config);
+ _config.mask.mask = mask.mask;
+ _config.num_groups = 0;
+
+ if (mask.mask > 0)
+ {
+ /* if the mask is not zero, the first bit MUST be set */
+ if (0 == (mask.mask & 1))
+ return GNUNET_SYSERR;
+
+ _config.num_groups = __builtin_popcount (mask.mask) - 1;
+ }
- this->config = GNUNET_malloc (sizeof(struct TALER_AgeMask));
- GNUNET_memcpy (this->config, &mask, sizeof(struct TALER_AgeMask));
+ this->config = &_config;
if (NULL != this->config_json)
json_decref (this->config_json);
- this->config_json = config;
+ this->config_json = jconfig;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "loaded new age restriction config with age groups: %s\n",
+ TALER_age_mask_to_string (&mask));
return GNUNET_OK;
}
@@ -263,7 +302,6 @@ json_t *
age_restriction_config_to_json (
const struct TALER_Extension *this)
{
- struct TALER_AgeMask mask;
char *mask_str;
json_t *conf;
@@ -275,8 +313,7 @@ age_restriction_config_to_json (
return json_copy (this->config_json);
}
- mask.mask = (uint32_t) (size_t) this->config;
- mask_str = TALER_age_mask_to_string (&mask);
+ mask_str = TALER_age_mask_to_string (&_config.mask);
conf = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("age_groups", mask_str)
);
@@ -298,7 +335,7 @@ age_restriction_test_json_config (
{
struct TALER_AgeMask mask = {0};
- return TALER_JSON_parse_agemask (config, &mask);
+ return TALER_JSON_parse_age_groups (config, &mask);
}
@@ -318,4 +355,50 @@ struct TALER_Extension _extension_age_restriction = {
.load_taler_config = &age_restriction_load_taler_config,
};
+bool
+TALER_extensions_age_restriction_is_configured ()
+{
+ return (0 != _config.mask.mask);
+}
+
+
+struct TALER_AgeMask
+TALER_extensions_age_restriction_ageMask ()
+{
+ return _config.mask;
+}
+
+
+size_t
+TALER_extensions_age_restriction_num_groups ()
+{
+ return _config.num_groups;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_parse_age_groups (const json_t *root,
+ struct TALER_AgeMask *mask)
+{
+ enum GNUNET_GenericReturnValue ret;
+ const char *str;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("age_groups",
+ &str),
+ GNUNET_JSON_spec_end ()
+ };
+
+ ret = GNUNET_JSON_parse (root,
+ spec,
+ NULL,
+ NULL);
+ if (GNUNET_OK == ret)
+ TALER_parse_age_group_string (str, mask);
+
+ GNUNET_JSON_parse_free (spec);
+
+ return ret;
+}
+
+
/* end of extension_age_restriction.c */
diff --git a/src/extensions/extensions.c b/src/extensions/extensions.c
index 55d970c5..516c56a4 100644
--- a/src/extensions/extensions.c
+++ b/src/extensions/extensions.c
@@ -247,27 +247,31 @@ TALER_extensions_load_taler_config (
}
-static enum GNUNET_GenericReturnValue
-is_json_extension_config (
+enum GNUNET_GenericReturnValue
+TALER_extensions_is_json_config (
json_t *obj,
int *critical,
const char **version,
json_t **config)
{
enum GNUNET_GenericReturnValue ret;
+ json_t *cfg;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_boolean ("critical",
critical),
GNUNET_JSON_spec_string ("version",
version),
GNUNET_JSON_spec_json ("config",
- config),
+ &cfg),
GNUNET_JSON_spec_end ()
};
ret = GNUNET_JSON_parse (obj, spec, NULL, NULL);
if (GNUNET_OK == ret)
+ {
+ *config = json_copy (cfg);
GNUNET_JSON_parse_free (spec);
+ }
return ret;
}
@@ -300,7 +304,7 @@ TALER_extensions_load_json_config (
/* load and verify criticality, version, etc. */
if (GNUNET_OK !=
- is_json_extension_config (
+ TALER_extensions_is_json_config (
blob, &critical, &version, &config))
return GNUNET_SYSERR;
@@ -330,4 +334,16 @@ TALER_extensions_load_json_config (
}
+bool
+TALER_extensions_age_restriction_is_enabled ()
+{
+ const struct TALER_Extension *age =
+ TALER_extensions_get_by_type (TALER_Extension_AgeRestriction);
+
+ return (NULL != age &&
+ NULL != age->config_json &&
+ TALER_extensions_age_restriction_is_configured ());
+}
+
+
/* end of extensions.c */
diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h
index ab5202ba..9bbf29de 100644
--- a/src/include/taler_crypto_lib.h
+++ b/src/include/taler_crypto_lib.h
@@ -306,37 +306,6 @@ struct TALER_MasterSignatureP
struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
};
-/*
- * @brief Type of a list of age groups, represented as bit mask.
- *
- * The bits set in the mask mark the edges at the beginning of a next age
- * group. F.e. for the age groups
- * 0-7, 8-9, 10-11, 12-14, 14-15, 16-17, 18-21, 21-*
- * the following bits are set:
- *
- * 31 24 16 8 0
- * | | | | |
- * oooooooo oo1oo1o1 o1o1o1o1 ooooooo1
- *
- * A value of 0 means that the exchange does not support the extension for
- * age-restriction.
- */
-struct TALER_AgeMask
-{
- uint32_t mask;
-};
-
-/**
- * @brief Age restriction commitment of a coin.
- */
-struct TALER_AgeHash
-{
- /**
- * The commitment is a SHA-256 hash code.
- */
- struct GNUNET_ShortHashCode shash;
-};
-
/**
* @brief Type of public keys for Taler coins. The same key material is used
@@ -364,6 +333,29 @@ struct TALER_CoinSpendPrivateKeyP
struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
};
+/**
+ * @brief Type of private keys for age commitment in coins.
+ */
+struct TALER_AgeCommitmentPrivateKeyP
+{
+ /**
+ * Taler uses EdDSA for coins when signing age verification attestation.
+ */
+ struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+
+/**
+ * @brief Type of public keys for age commitment in coins.
+ */
+struct TALER_AgeCommitmentPublicKeyP
+{
+ /**
+ * Taler uses EdDSA for coins when signing age verification attestation.
+ */
+ struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+};
+
/**
* @brief Type of signatures made with Taler coins.
@@ -765,6 +757,46 @@ struct TALER_BlindedDenominationSignature
};
+/* *************** Age Restriction *********************************** */
+
+/*
+ * @brief Type of a list of age groups, represented as bit mask.
+ *
+ * The bits set in the mask mark the edges at the beginning of a next age
+ * group. F.e. for the age groups
+ * 0-7, 8-9, 10-11, 12-14, 14-15, 16-17, 18-21, 21-*
+ * the following bits are set:
+ *
+ * 31 24 16 8 0
+ * | | | | |
+ * oooooooo oo1oo1o1 o1o1o1o1 ooooooo1
+ *
+ * A value of 0 means that the exchange does not support the extension for
+ * age-restriction.
+ */
+struct TALER_AgeMask
+{
+ uint32_t mask;
+};
+
+/**
+ * @brief Age commitment of a coin.
+ */
+struct TALER_AgeCommitmentHash
+{
+ /**
+ * The commitment is a SHA-256 hash code.
+ */
+ struct GNUNET_ShortHashCode shash;
+};
+
+extern const struct TALER_AgeCommitmentHash TALER_ZeroAgeCommitmentHash;
+#define TALER_AgeCommitmentHash_isNullOrZero(ph) ((NULL == ph) || \
+ (0 == memcmp (ph, \
+ & \
+ TALER_ZeroAgeCommitmentHash, \
+ sizeof(struct \
+ TALER_AgeCommitmentHash))))
/**
* @brief Type of public signing keys for verifying blindly signed coins.
@@ -944,9 +976,10 @@ struct TALER_CoinPublicInfo
struct TALER_DenominationHash denom_pub_hash;
/**
- * Hash of the age commitment.
+ * Hash of the age commitment. If no age commitment was provided, it must be
+ * set to all zeroes.
*/
- struct TALER_AgeHash age_commitment_hash;
+ struct TALER_AgeCommitmentHash age_commitment_hash;
/**
* (Unblinded) signature over @e coin_pub with @e denom_pub,
@@ -1117,7 +1150,7 @@ TALER_denom_sig_free (struct TALER_DenominationSignature *denom_sig);
enum GNUNET_GenericReturnValue
TALER_denom_blind (const struct TALER_DenominationPublicKey *dk,
const union TALER_DenominationBlindingKeyP *coin_bks,
- const struct TALER_AgeHash *age_commitment_hash,
+ const struct TALER_AgeCommitmentHash *age_commitment_hash,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_ExchangeWithdrawValues *alg_values,
struct TALER_CoinPubHash *c_hash,
@@ -1349,7 +1382,7 @@ TALER_withdraw_request_hash (
*/
void
TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_AgeHash *age_commitment_hash,
+ const struct TALER_AgeCommitmentHash *age_commitment_hash,
struct TALER_CoinPubHash *coin_h);
@@ -1402,8 +1435,9 @@ struct TALER_FreshCoin
struct TALER_CoinSpendPrivateKeyP coin_priv;
/**
- * FIXME-Oec: Age-verification vector, as pointer: Dyn alloc!
+ * Optional hash of an age commitment bound to this coin, maybe NULL.
*/
+ const struct TALER_AgeCommitmentHash *h_age_commitment;
};
@@ -1571,6 +1605,7 @@ TALER_planchet_blinding_secret_create (
* @param alg_values algorithm specific values
* @param bks blinding secrets
* @param coin_priv coin private key
+ * @param ach hash of age commitment to bind to this coin, maybe NULL
* @param[out] c_hash set to the hash of the public key of the coin (needed later)
* @param[out] pd set to the planchet detail for TALER_MERCHANT_tip_pickup() and
* other withdraw operations, `pd->blinded_planchet.cipher` will be set
@@ -1582,6 +1617,7 @@ TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk,
const struct TALER_ExchangeWithdrawValues *alg_values,
const union TALER_DenominationBlindingKeyP *bks,
const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ const struct TALER_AgeCommitmentHash *ach,
struct TALER_CoinPubHash *c_hash,
struct TALER_PlanchetDetail *pd);
@@ -1613,6 +1649,7 @@ TALER_planchet_detail_free (struct TALER_PlanchetDetail *pd);
* @param blind_sig blind signature from the exchange
* @param bks blinding key secret
* @param coin_priv private key of the coin
+ * @param ach hash of age commitment that is bound to this coin, maybe NULL
* @param c_hash hash of the coin's public key for verification of the signature
* @param alg_values values obtained from the exchange for the withdrawal
* @param[out] coin set to the details of the fresh coin
@@ -1624,6 +1661,7 @@ TALER_planchet_to_coin (
const struct TALER_BlindedDenominationSignature *blind_sig,
const union TALER_DenominationBlindingKeyP *bks,
const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ const struct TALER_AgeCommitmentHash *ach,
const struct TALER_CoinPubHash *c_hash,
const struct TALER_ExchangeWithdrawValues *alg_values,
struct TALER_FreshCoin *coin);
@@ -2202,6 +2240,7 @@ TALER_wallet_account_setup_sign (
* @param deposit_fee the deposit fee we expect to pay
* @param h_wire hash of the merchant’s account details
* @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange)
+ * @param h_age_commitment hash over the age commitment, if applicable to the denomination (maybe NULL)
* @param h_extensions hash over the extensions
* @param h_denom_pub hash of the coin denomination's public key
* @param coin_priv coin’s private key
@@ -2216,6 +2255,7 @@ TALER_wallet_deposit_sign (
const struct TALER_Amount *deposit_fee,
const struct TALER_MerchantWireHash *h_wire,
const struct TALER_PrivateContractHash *h_contract_terms,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
const struct TALER_ExtensionContractHash *h_extensions,
const struct TALER_DenominationHash *h_denom_pub,
struct GNUNET_TIME_Timestamp wallet_timestamp,
@@ -2232,6 +2272,7 @@ TALER_wallet_deposit_sign (
* @param deposit_fee the deposit fee we expect to pay
* @param h_wire hash of the merchant’s account details
* @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange)
+ * @param h_age_commitment hash over the age commitment (maybe all zeroes, if not applicable to the denomination)
* @param h_extensions hash over the extensions
* @param h_denom_pub hash of the coin denomination's public key
* @param wallet_timestamp timestamp when the contract was finalized, must not be too far in the future
@@ -2247,6 +2288,7 @@ TALER_wallet_deposit_verify (
const struct TALER_Amount *deposit_fee,
const struct TALER_MerchantWireHash *h_wire,
const struct TALER_PrivateContractHash *h_contract_terms,
+ const struct TALER_AgeCommitmentHash *h_commitment_hash,
const struct TALER_ExtensionContractHash *h_extensions,
const struct TALER_DenominationHash *h_denom_pub,
struct GNUNET_TIME_Timestamp wallet_timestamp,
@@ -2283,6 +2325,7 @@ TALER_wallet_melt_sign (
* @param melt_fee the melt fee we expect to pay
* @param rc refresh session we are committed to
* @param h_denom_pub hash of the coin denomination's public key
+ * @param h_age_commitment hash of the age commitment (may be NULL)
* @param coin_pub coin’s public key
* @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_MELT
* @return #GNUNET_OK if the signature is valid
@@ -2293,6 +2336,7 @@ TALER_wallet_melt_verify (
const struct TALER_Amount *melt_fee,
const struct TALER_RefreshCommitmentP *rc,
const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_CoinSpendSignatureP *coin_sig);
@@ -2321,6 +2365,7 @@ TALER_wallet_link_sign (const struct TALER_DenominationHash *h_denom_pub,
* @param transfer_pub transfer public key
* @param h_coin_ev hash of the coin envelope
* @param old_coin_pub old coin key that the link signature is for
+ * @param h_age_commitment hash of age commitment. Maybe NULL, if not applicable.
* @param coin_sig resulting signature
* @return #GNUNET_OK if the signature is valid
*/
@@ -2330,6 +2375,7 @@ TALER_wallet_link_verify (
const struct TALER_TransferPublicKeyP *transfer_pub,
const struct TALER_BlindedCoinHash *h_coin_ev,
const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
const struct TALER_CoinSpendSignatureP *coin_sig);
@@ -3149,5 +3195,100 @@ TALER_exchange_offline_extension_config_hash_verify (
const struct TALER_MasterSignatureP *master_sig
);
+/*
+ * @brief Representation of an age commitment: one public key per age group.
+ *
+ * The number of keys must be be the same as the number of bits set in the
+ * corresponding age mask.
+ */
+struct TALER_AgeCommitment
+{
+
+ /* The age mask defines the age groups that were a parameter during the
+ * generation of this age commitment */
+ struct TALER_AgeMask mask;
+
+ /* The number of public keys, which must be the same as the number of
+ * groups in the mask.
+ */
+ size_t num_pub;
+
+ /* The list of #num_pub public keys. In must have same size as the number of
+ * age groups defined in the mask.
+ *
+ * A hash of this list is the hashed commitment that goes into FDC
+ * calculation during the withdraw and refresh operations for new coins. That
+ * way, the particular age commitment becomes mandatory and bound to a coin.
+ *
+ * The list has been allocated via GNUNET_malloc.
+ */
+ struct TALER_AgeCommitmentPublicKeyP *pub;
+
+ /* The number of private keys, which must be at most num_pub_keys. One minus
+ * this number corresponds to the largest age group that is supported with
+ * this age commitment.
+ */
+ size_t num_priv;
+
+ /* List of #num_priv private keys.
+ *
+ * Note that the list can be _smaller_ than the corresponding list of public
+ * keys. In that case, the wallet can sign off only for a subset of the age
+ * groups.
+ *
+ * The list has been allocated via GNUNET_malloc.
+ */
+ struct TALER_AgeCommitmentPrivateKeyP *priv;
+};
+
+/*
+ * @brief Generates a hash of the public keys in the age commitment.
+ *
+ * @param commitment the age commitment - one public key per age group
+ * @param[out] hash resulting hash
+ */
+void
+TALER_age_commitment_hash (
+ const struct TALER_AgeCommitment *commitment,
+ struct TALER_AgeCommitmentHash *hash);
+
+/*
+ * @brief Generates an age commitent for the given age.
+ *
+ * @param mask The age mask the defines the age groups
+ * @param age The actual age for which an age commitment is generated
+ * @param seed The seed that goes into the key generation. MUST be choosen uniformly random.
+ * @param commitment[out] The generated age commitment, ->priv and ->pub allocated via GNUNET_malloc on success
+ * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
+ */
+enum GNUNET_GenericReturnValue
+TALER_age_restriction_commit (
+ const struct TALER_AgeMask *mask,
+ const uint8_t age,
+ const uint32_t seed,
+ struct TALER_AgeCommitment *commitment);
+
+/*
+ * @brief Derives another, equivalent age commitment for a given one.
+ *
+ * @param orig Original age commitment
+ * @param seed Used to move the points on the elliptic curve in order to generate another, equivalent commitment.
+ * @param derived[out] The resulting age commitment, ->priv and ->pub allocated via GNUNET_malloc on success.
+ * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
+ */
+enum GNUNET_GenericReturnValue
+TALER_age_commitment_derive (
+ const struct TALER_AgeCommitment *orig,
+ const uint32_t seed,
+ struct TALER_AgeCommitment *derived);
+
+/*
+ * @brief helper function to free memory inside a struct TALER_AgeCommitment
+ * @param cmt the commitment from which internal memory should be freed. Note
+ * that cmt itself is NOT freed!
+ */
+void
+TALER_age_restriction_commitment_free_inside (
+ struct TALER_AgeCommitment *cmt);
#endif
diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h
index 8c1b4bde..fef09f72 100644
--- a/src/include/taler_exchange_service.h
+++ b/src/include/taler_exchange_service.h
@@ -159,11 +159,6 @@ struct TALER_EXCHANGE_DenomPublicKey
* revoked by the exchange.
*/
bool revoked;
-
- /**
- * Is the denomination age-restricted?
- */
- bool age_restricted;
};
@@ -785,6 +780,7 @@ TALER_EXCHANGE_wire_cancel (struct TALER_EXCHANGE_WireHandle *wh);
* @param h_extensions hash over the extensions
* @param h_denom_pub hash of the coin denomination's public key
* @param coin_priv coin’s private key
+ * @param age_commitment age commitment that went into the making of the coin, might be NULL
* @param wallet_timestamp timestamp when the contract was finalized, must not be too far in the future
* @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests)
* @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline
@@ -799,6 +795,7 @@ TALER_EXCHANGE_deposit_permission_sign (
const struct TALER_ExtensionContractHash *h_extensions,
const struct TALER_DenominationHash *h_denom_pub,
const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ const struct TALER_AgeCommitment *age_commitment,
struct GNUNET_TIME_Timestamp wallet_timestamp,
const struct TALER_MerchantPublicKeyP *merchant_pub,
struct GNUNET_TIME_Timestamp refund_deadline,
@@ -924,6 +921,7 @@ TALER_EXCHANGE_deposit (
const char *merchant_payto_uri,
const struct TALER_WireSaltP *wire_salt,
const struct TALER_PrivateContractHash *h_contract_terms,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
const json_t *extension_details,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_DenominationSignature *denom_sig,
@@ -1496,6 +1494,7 @@ typedef void
* @param reserve_priv private key of the reserve to withdraw from
* @param ps secrets of the planchet
* caller must have committed this value to disk before the call (with @a pk)
+ * @param ach hash of the age commitment that should be bound to this coin. Maybe NULL.
* @param res_cb the callback to call when the final result for this request is available
* @param res_cb_cls closure for @a res_cb
* @return NULL
@@ -1508,6 +1507,7 @@ TALER_EXCHANGE_withdraw (
const struct TALER_EXCHANGE_DenomPublicKey *pk,
const struct TALER_ReservePrivateKeyP *reserve_priv,
const struct TALER_PlanchetMasterSecretP *ps,
+ const struct TALER_AgeCommitmentHash *ach,
TALER_EXCHANGE_WithdrawCallback res_cb,
void *res_cb_cls);
diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h
index ec647e9c..f0a6f8bd 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -70,6 +70,12 @@ struct TALER_EXCHANGEDB_DenominationKeyInformationP
* Signed properties of the denomination key.
*/
struct TALER_DenominationKeyValidityPS properties;
+
+ /**
+ * If denomination was setup for age restriction, non-zero age mask.
+ * Note that the mask is not part of the signature.
+ */
+ struct TALER_AgeMask age_mask;
};
@@ -295,7 +301,7 @@ struct TALER_EXCHANGEDB_TableData
struct
{
struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_AgeHash age_hash;
+ struct TALER_AgeCommitmentHash age_hash;
uint64_t denominations_serial;
struct TALER_DenominationSignature denom_sig;
} known_coins;
@@ -644,7 +650,7 @@ struct TALER_EXCHANGEDB_DenominationKeyMetaData
* A value of 0 means that the denomination does not support the extension for
* age-restriction.
*/
- struct TALER_AgeMask age_restrictions;
+ struct TALER_AgeMask age_mask;
};
@@ -1262,6 +1268,13 @@ struct TALER_EXCHANGEDB_Refresh
struct TALER_CoinSpendSignatureP coin_sig;
/**
+ * Hash of the age commitment used to sign the coin, if age restriction was
+ * applicable to the denomination. May be all zeroes if no age restriction
+ * applies.
+ */
+ struct TALER_AgeCommitmentHash h_age_commitment;
+
+ /**
* Refresh commitment this coin is melted into.
*/
struct TALER_RefreshCommitmentP rc;
@@ -1307,6 +1320,13 @@ struct TALER_EXCHANGEDB_MeltListEntry
struct TALER_DenominationHash h_denom_pub;
/**
+ * Hash of the age commitment used to sign the coin, if age restriction was
+ * applicable to the denomination. May be all zeroes if no age restriction
+ * applies.
+ */
+ struct TALER_AgeCommitmentHash h_age_commitment;
+
+ /**
* How much value is being melted? This amount includes the fees,
* so the final amount contributed to the melt is this value minus
* the fee for melting the coin. We include the fee in what is
@@ -1606,6 +1626,7 @@ typedef enum GNUNET_GenericReturnValue
* @param cls closure
* @param rowid unique serial ID for the refresh session in our DB
* @param denom_pub denomination public key of @a coin_pub
+ * @param h_age_commitment age commitment that went into the signing of the coin, may be NULL
* @param coin_pub public key of the coin
* @param coin_sig signature from the coin
* @param amount_with_fee amount that was deposited including fee
@@ -1618,6 +1639,7 @@ typedef enum GNUNET_GenericReturnValue
void *cls,
uint64_t rowid,
const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
const struct TALER_Amount *amount_with_fee,
@@ -2758,7 +2780,7 @@ struct TALER_EXCHANGEDB_Plugin
const struct TALER_CoinPublicInfo *coin,
uint64_t *known_coin_id,
struct TALER_DenominationHash *denom_pub_hash,
- struct TALER_AgeHash *age_hash);
+ struct TALER_AgeCommitmentHash *age_hash);
/**
diff --git a/src/include/taler_extensions.h b/src/include/taler_extensions.h
index f00f3ed5..b7b93e17 100644
--- a/src/include/taler_extensions.h
+++ b/src/include/taler_extensions.h
@@ -86,6 +86,31 @@ TALER_extensions_load_taler_config (
const struct GNUNET_CONFIGURATION_Handle *cfg);
/*
+ * Check the given obj to be a valid extension object and fill the fields
+ * accordingly.
+ */
+enum GNUNET_GenericReturnValue
+TALER_extensions_is_json_config (
+ json_t *obj,
+ int *critical,
+ const char **version,
+ json_t **config);
+
+/*
+ * Sets the configuration of the extensions from a given JSON object.
+ *
+ * he JSON object must be of type ExchangeKeysResponse as described in
+ * https://docs.taler.net/design-documents/006-extensions.html#exchange
+ *
+ * @param cfg JSON object containting the configuration for all extensions
+ * @return GNUNET_OK on success, GNUNET_SYSERR if unknown extensions were found
+ * or any particular configuration couldn't be parsed.
+ */
+enum GNUNET_GenericReturnValue
+TALER_extensions_load_json_config (
+ json_t *cfg);
+
+/*
* Returns the head of the linked list of extensions
*/
const struct TALER_Extension *
@@ -156,20 +181,6 @@ TALER_extensions_verify_json_config_signature (
struct TALER_MasterSignatureP *extensions_sig,
struct TALER_MasterPublicKeyP *master_pub);
-/*
- * Sets the configuration of the extensions from a given JSON object.
- *
- * The JSON object must be of type ExchangeKeysResponse as described in
- * https://docs.taler.net/design-documents/006-extensions.html#exchange
- *
- * @param cfg Handle to the TALER configuration
- * @return GNUNET_OK on success, GNUNET_SYSERR if unknown extensions were found
- * or any particular configuration couldn't be parsed.
- */
-enum GNUNET_GenericReturnValue
-TALER_extensions_load_json_config (
- json_t *extensions);
-
/*
* TALER Age Restriction Extension
@@ -221,6 +232,45 @@ char *
TALER_age_mask_to_string (
const struct TALER_AgeMask *mask);
+/**
+ * Returns true when age restriction is configured and enabled.
+ */
+bool
+TALER_extensions_age_restriction_is_enabled ();
+
+/**
+ * Returns true when age restriction is configured (might not be _enabled_,
+ * though).
+ */
+bool
+TALER_extensions_age_restriction_is_configured ();
+
+/**
+ * Returns the currently set age mask. Note that even if age restriction is
+ * not enabled, the age mask might be have a non-zero value.
+ */
+struct TALER_AgeMask
+TALER_extensions_age_restriction_ageMask ();
+
+
+/**
+ * Returns the amount of age groups defined. 0 means no age restriction
+ * enabled.
+ */
+size_t
+TALER_extensions_age_restriction_num_groups ();
+
+/**
+ * Parses a JSON object { "age_groups": "a:b:...y:z" }.
+ *
+ * @param root is the json object
+ * @param[out] mask on succes, will contain the age mask
+ * @return #GNUNET_OK on success and #GNUNET_SYSERR on failure.
+ */
+enum GNUNET_GenericReturnValue
+TALER_JSON_parse_age_groups (const json_t *root,
+ struct TALER_AgeMask *mask);
+
/*
* TODO: Add Peer2Peer Extension
diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h
index b7bcd845..e3e47222 100644
--- a/src/include/taler_json_lib.h
+++ b/src/include/taler_json_lib.h
@@ -608,17 +608,6 @@ TALER_JSON_extensions_config_hash (const json_t *config,
struct TALER_ExtensionConfigHash *eh);
/**
- * Parses a JSON object `{ "extension": "age_restriction", "mask": uint32 }`.
- *
- * @param root is the json object
- * @param[out] mask on succes, will contain the age mask
- * @return #GNUNET_OK on success and #GNUNET_SYSERR on failure.
- */
-enum GNUNET_GenericReturnValue
-TALER_JSON_parse_agemask (const json_t *root,
- struct TALER_AgeMask *mask);
-
-/**
* Canonicalize a JSON input to a string according to RFC 8785.
*/
char *
diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h
index 17ed4b57..e3d9a893 100644
--- a/src/include/taler_signatures.h
+++ b/src/include/taler_signatures.h
@@ -419,6 +419,11 @@ struct TALER_LinkDataPS
struct TALER_TransferPublicKeyP transfer_pub;
/**
+ * Hash of the age commitment, if applicable. Can be all zero
+ */
+ struct TALER_AgeCommitmentHash h_age_commitment;
+
+ /**
* Hash of the blinded new coin.
*/
struct TALER_BlindedCoinHash coin_envelope_hash;
@@ -477,6 +482,12 @@ struct TALER_DepositRequestPS
struct TALER_PrivateContractHash h_contract_terms GNUNET_PACKED;
/**
+ * Hash over the age commitment that went into the coin. Maybe all zero, if
+ * age commitment isn't applicable to the denomination.
+ */
+ struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED;
+
+ /**
* Hash over extension attributes shared with the exchange.
*/
struct TALER_ExtensionContractHash h_extensions GNUNET_PACKED;
@@ -710,6 +721,13 @@ struct TALER_RefreshMeltCoinAffirmationPS
struct TALER_DenominationHash h_denom_pub GNUNET_PACKED;
/**
+ * If age commitment was provided during the withdrawal of the coin, this is
+ * the hash of the age commitment vector. It must be all zeroes if no age
+ * commitment was provided.
+ */
+ struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED;
+
+ /**
* How much of the value of the coin should be melted? This amount
* includes the fees, so the final amount contributed to the melt is
* this value minus the fee for melting the coin. We include the
diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h
index 69cb9f68..ab8b64fc 100644
--- a/src/include/taler_testing_lib.h
+++ b/src/include/taler_testing_lib.h
@@ -66,11 +66,13 @@ TALER_TESTING_make_wire_details (const char *payto);
*
* @param keys array of keys to search
* @param amount coin value to look for
+ * @param age_restricted must the denomination be age restricted?
* @return NULL if no matching key was found
*/
const struct TALER_EXCHANGE_DenomPublicKey *
TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_Amount *amount);
+ const struct TALER_Amount *amount,
+ bool age_restricted);
/**
@@ -1278,6 +1280,7 @@ TALER_TESTING_cmd_exec_transfer (const char *label,
* @param label command label.
* @param reserve_reference command providing us with a reserve to withdraw from
* @param amount how much we withdraw.
+ * @param age if > 0, age restriction applies
* @param expected_response_code which HTTP response code
* we expect from the exchange.
* @return the withdraw command to be executed by the interpreter.
@@ -1286,6 +1289,7 @@ struct TALER_TESTING_Command
TALER_TESTING_cmd_withdraw_amount (const char *label,
const char *reserve_reference,
const char *amount,
+ uint8_t age,
unsigned int expected_response_code);
@@ -1298,6 +1302,7 @@ TALER_TESTING_cmd_withdraw_amount (const char *label,
* @param label command label.
* @param reserve_reference command providing us with a reserve to withdraw from
* @param amount how much we withdraw.
+ * @param age if > 0, age restriction applies.
* @param coin_ref reference to (withdraw/reveal) command of a coin
* from which we should re-use the private key
* @param expected_response_code which HTTP response code
@@ -1309,6 +1314,7 @@ TALER_TESTING_cmd_withdraw_amount_reuse_key (
const char *label,
const char *reserve_reference,
const char *amount,
+ uint8_t age,
const char *coin_ref,
unsigned int expected_response_code);
@@ -2138,6 +2144,19 @@ TALER_TESTING_cmd_wire_del (const char *label,
unsigned int expected_http_status,
bool bad_sig);
+/**
+ * Sign all extensions that the exchange has to offer, f. e. the extension for
+ * age restriction. This has to be run before any withdrawal of age restricted
+ * can be performed.
+ *
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_extensions (const char *label,
+ const char *config_filename);
+
/**
* Sign all exchange denomination and online signing keys
@@ -2422,10 +2441,10 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
*/
#define TALER_TESTING_SIMPLE_TRAITS(op) \
op (bank_row, const uint64_t) \
- op (reserve_priv, const struct TALER_ReservePrivateKeyP) \
- op (planchet_secret, const struct TALER_PlanchetMasterSecretP) \
- op (refresh_secret, const struct TALER_RefreshMasterSecretP) \
- op (reserve_pub, const struct TALER_ReservePublicKeyP) \
+ op (reserve_priv, const struct TALER_ReservePrivateKeyP) \
+ op (planchet_secret, const struct TALER_PlanchetMasterSecretP) \
+ op (refresh_secret, const struct TALER_RefreshMasterSecretP) \
+ op (reserve_pub, const struct TALER_ReservePublicKeyP) \
op (merchant_priv, const struct TALER_MerchantPrivateKeyP) \
op (merchant_pub, const struct TALER_MerchantPublicKeyP) \
op (merchant_sig, const struct TALER_MerchantSignatureP) \
@@ -2438,8 +2457,8 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
op (exchange_bank_account_url, const char *) \
op (taler_uri, const char *) \
op (payto_uri, const char *) \
- op (kyc_url, const char *) \
- op (web_url, const char *) \
+ op (kyc_url, const char *) \
+ op (web_url, const char *) \
op (row, const uint64_t) \
op (payment_target_uuid, const uint64_t) \
op (array_length, const unsigned int) \
@@ -2464,7 +2483,9 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
#define TALER_TESTING_INDEXED_TRAITS(op) \
op (denom_pub, const struct TALER_EXCHANGE_DenomPublicKey) \
op (denom_sig, const struct TALER_DenominationSignature) \
- op (planchet_secrets, const struct TALER_PlanchetMasterSecretP) \
+ op (age_commitment, struct TALER_AgeCommitment) \
+ op (h_age_commitment, struct TALER_AgeCommitmentHash) \
+ op (planchet_secrets, const struct TALER_PlanchetMasterSecretP) \
op (exchange_wd_value, const struct TALER_ExchangeWithdrawValues) \
op (coin_priv, const struct TALER_CoinSpendPrivateKeyP) \
op (coin_pub, const struct TALER_CoinSpendPublicKeyP) \
diff --git a/src/json/json_helper.c b/src/json/json_helper.c
index 96e41b5e..4ec9a698 100644
--- a/src/json/json_helper.c
+++ b/src/json/json_helper.c
@@ -950,35 +950,4 @@ TALER_JSON_spec_i18n_str (const char *name,
}
-enum GNUNET_GenericReturnValue
-TALER_JSON_parse_agemask (const json_t *root,
- struct TALER_AgeMask *mask)
-{
- const char *name;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("extension",
- &name),
- GNUNET_JSON_spec_uint32 ("mask",
- &mask->mask),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK != GNUNET_JSON_parse (root,
- spec,
- NULL,
- NULL))
- {
- return GNUNET_SYSERR;
- }
-
- if (! strncmp (name,
- "age_restriction",
- sizeof("age_restriction")))
- {
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
/* end of json/json_helper.c */
diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c
index 53a75a93..d0340924 100644
--- a/src/lib/exchange_api_common.c
+++ b/src/lib/exchange_api_common.c
@@ -477,6 +477,7 @@ TALER_EXCHANGE_verify_coin_history (
struct TALER_MerchantPublicKeyP merchant_pub;
struct GNUNET_TIME_Timestamp refund_deadline = {0};
struct TALER_CoinSpendSignatureP sig;
+ struct TALER_AgeCommitmentHash *hac = NULL;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("coin_sig",
&sig),
@@ -511,6 +512,7 @@ TALER_EXCHANGE_verify_coin_history (
&fee,
&h_wire,
&h_contract_terms,
+ hac,
NULL /* h_extensions! */,
h_denom_pub,
wallet_timestamp,
@@ -543,6 +545,7 @@ TALER_EXCHANGE_verify_coin_history (
{
struct TALER_CoinSpendSignatureP sig;
struct TALER_RefreshCommitmentP rc;
+ struct TALER_AgeCommitmentHash h_age_commitment = {0};
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("coin_sig",
&sig),
@@ -550,6 +553,9 @@ TALER_EXCHANGE_verify_coin_history (
&rc),
GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
h_denom_pub),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &h_age_commitment)),
TALER_JSON_spec_amount_any ("melt_fee",
&fee),
GNUNET_JSON_spec_end ()
@@ -563,6 +569,7 @@ TALER_EXCHANGE_verify_coin_history (
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
+
if (NULL != dk)
{
/* check that melt fee matches our expectations from /keys! */
@@ -577,16 +584,25 @@ TALER_EXCHANGE_verify_coin_history (
return GNUNET_SYSERR;
}
}
- if (GNUNET_OK !=
- TALER_wallet_melt_verify (&amount,
- &fee,
- &rc,
- h_denom_pub,
- coin_pub,
- &sig))
+
{
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
+ const struct TALER_AgeCommitmentHash *ahc = &h_age_commitment;
+
+ if (TALER_AgeCommitmentHash_isNullOrZero (ahc))
+ ahc = NULL;
+
+ if (GNUNET_OK !=
+ TALER_wallet_melt_verify (&amount,
+ &fee,
+ &rc,
+ h_denom_pub,
+ ahc,
+ coin_pub,
+ &sig))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
}
add = GNUNET_YES;
}
diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c
index 7ff59651..2bfaaf6c 100644
--- a/src/lib/exchange_api_deposit.c
+++ b/src/lib/exchange_api_deposit.c
@@ -463,6 +463,7 @@ handle_deposit_finished (void *cls,
* @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange)
* @param ech hash over contract extensions
* @param coin_pub coin’s public key
+ * @param h_age_commitment coin’s hash of age commitment, might be NULL
* @param denom_sig exchange’s unblinded signature of the coin
* @param denom_pub denomination key with which the coin is signed
* @param denom_pub_hash hash of @a denom_pub
@@ -479,6 +480,7 @@ verify_signatures (const struct TALER_EXCHANGE_DenomPublicKey *dki,
const struct TALER_PrivateContractHash *h_contract_terms,
const struct TALER_ExtensionContractHash *ech,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
const struct TALER_DenominationSignature *denom_sig,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_DenominationHash *denom_pub_hash,
@@ -492,6 +494,7 @@ verify_signatures (const struct TALER_EXCHANGE_DenomPublicKey *dki,
&dki->fee_deposit,
h_wire,
h_contract_terms,
+ h_age_commitment,
ech,
denom_pub_hash,
timestamp,
@@ -515,8 +518,12 @@ verify_signatures (const struct TALER_EXCHANGE_DenomPublicKey *dki,
.coin_pub = *coin_pub,
.denom_pub_hash = *denom_pub_hash,
.denom_sig = *denom_sig,
- .age_commitment_hash = {{{0}}} /* FIXME-Oec */
+ .age_commitment_hash = {{{0}}}
};
+ if (NULL != h_age_commitment)
+ {
+ coin_info.age_commitment_hash = *h_age_commitment;
+ }
if (GNUNET_YES !=
TALER_test_coin_valid (&coin_info,
@@ -548,6 +555,7 @@ TALER_EXCHANGE_deposit (
const char *merchant_payto_uri,
const struct TALER_WireSaltP *wire_salt,
const struct TALER_PrivateContractHash *h_contract_terms,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
const json_t *extension_details,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_DenominationSignature *denom_sig,
@@ -600,11 +608,14 @@ TALER_EXCHANGE_deposit (
}
GNUNET_assert (GNUNET_YES ==
TEAH_handle_is_ready (exchange));
+
/* initialize h_wire */
TALER_merchant_wire_signature_hash (merchant_payto_uri,
wire_salt,
&h_wire);
+
key_state = TALER_EXCHANGE_get_keys (exchange);
+
dki = TALER_EXCHANGE_get_denomination_key (key_state,
denom_pub);
if (NULL == dki)
@@ -613,6 +624,7 @@ TALER_EXCHANGE_deposit (
GNUNET_break_op (0);
return NULL;
}
+
if (0 >
TALER_amount_subtract (&amount_without_fee,
amount,
@@ -622,17 +634,18 @@ TALER_EXCHANGE_deposit (
GNUNET_break_op (0);
return NULL;
}
+
TALER_denom_pub_hash (denom_pub,
&denom_pub_hash);
+
if (GNUNET_OK !=
verify_signatures (dki,
amount,
&h_wire,
h_contract_terms,
- (NULL != extension_details)
- ? &ech
- : NULL,
+ (NULL != extension_details) ? &ech : NULL,
coin_pub,
+ h_age_commitment,
denom_sig,
denom_pub,
&denom_pub_hash,
@@ -655,6 +668,9 @@ TALER_EXCHANGE_deposit (
wire_salt),
GNUNET_JSON_pack_data_auto ("h_contract_terms",
h_contract_terms),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ h_age_commitment)),
GNUNET_JSON_pack_data_auto ("denom_pub_hash",
&denom_pub_hash),
TALER_JSON_pack_denom_sig ("ub_sig",
diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c
index cf3d69d6..3243f5e9 100644
--- a/src/lib/exchange_api_handle.c
+++ b/src/lib/exchange_api_handle.c
@@ -667,7 +667,9 @@ decode_keys_json (const json_t *resp_obj,
enum TALER_EXCHANGE_VersionCompatibility *vc)
{
struct TALER_ExchangeSignatureP sig;
- struct GNUNET_HashContext *hash_context;
+ struct GNUNET_HashContext *hash_context = NULL;
+ struct GNUNET_HashContext *hash_context_restricted = NULL;
+ bool have_age_restricted_denom = false;
struct TALER_ExchangePublicKeyP pub;
const char *currency;
struct GNUNET_JSON_Specification mspec[] = {
@@ -746,7 +748,6 @@ decode_keys_json (const json_t *resp_obj,
key_data->version = GNUNET_strdup (ver);
}
- hash_context = NULL;
EXITIF (GNUNET_OK !=
GNUNET_JSON_parse (resp_obj,
(check_sig) ? mspec : &mspec[2],
@@ -766,7 +767,10 @@ decode_keys_json (const json_t *resp_obj,
/* parse the master public key and issue date of the response */
if (check_sig)
+ {
hash_context = GNUNET_CRYPTO_hash_context_start ();
+ hash_context_restricted = GNUNET_CRYPTO_hash_context_start ();
+ }
/* parse the signing keys */
{
@@ -829,6 +833,9 @@ decode_keys_json (const json_t *resp_obj,
EXITIF (GNUNET_OK !=
TALER_extensions_load_json_config (extensions));
}
+
+ /* 4. assuming we might have now a new value for age_mask, set it in key_data */
+ key_data->age_mask = TALER_extensions_age_restriction_ageMask ();
}
/* parse the denomination keys, merging with the
@@ -839,9 +846,15 @@ decode_keys_json (const json_t *resp_obj,
*/
struct
{ char *name;
- bool is_optional_age_restriction;} hive[2] = {
- { "denoms", false },
- { "age_restricted_denoms", true },
+ struct GNUNET_HashContext *hc;
+ bool is_optional_age_restriction;}
+ hive[2] = {
+ { "denoms",
+ hash_context,
+ false },
+ { "age_restricted_denoms",
+ hash_context_restricted,
+ true }
};
for (size_t s = 0; s < sizeof(hive) / sizeof(hive[0]); s++)
@@ -853,25 +866,19 @@ decode_keys_json (const json_t *resp_obj,
denom_keys_array = json_object_get (resp_obj,
hive[s].name);
- EXITIF (NULL == denom_keys_array &&
- ! hive[s].is_optional_age_restriction);
-
- if (NULL == denom_keys_array &&
- hive[s].is_optional_age_restriction)
+ if (NULL == denom_keys_array)
continue;
- /* if "age_restricted_denoms" exists, age-restriction better be enabled
- * (that is: mask non-zero) */
- EXITIF (NULL != denom_keys_array &&
- hive[s].is_optional_age_restriction &&
- 0 == key_data->age_mask.mask);
-
EXITIF (JSON_ARRAY != json_typeof (denom_keys_array));
json_array_foreach (denom_keys_array, index, denom_key_obj) {
struct TALER_EXCHANGE_DenomPublicKey dk;
bool found = false;
+ /* mark that we have at least one age restricted denomination, needed
+ * for the hash calculation and signature verification below. */
+ have_age_restricted_denom |= hive[s].is_optional_age_restriction;
+
memset (&dk,
0,
sizeof (dk));
@@ -880,12 +887,7 @@ decode_keys_json (const json_t *resp_obj,
check_sig,
denom_key_obj,
&key_data->master_pub,
- hash_context));
-
- /* Mark age restriction according where we got this denomination from,
- * "denoms" or "age_restricted_denoms" */
- if (hive[s].is_optional_age_restriction)
- dk.age_restricted = true;
+ hive[s].hc));
for (unsigned int j = 0;
j<key_data->num_denom_keys;
@@ -1044,6 +1046,18 @@ decode_keys_json (const json_t *resp_obj,
.list_issue_date = GNUNET_TIME_timestamp_hton (key_data->list_issue_date)
};
+ /* If we had any age restricted denominations, add their hash to the end of
+ * the normal denominations. */
+ if (have_age_restricted_denom)
+ {
+ struct GNUNET_HashCode hcr;
+ GNUNET_CRYPTO_hash_context_finish (hash_context_restricted,
+ &hcr);
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &hcr,
+ sizeof(struct GNUNET_HashCode));
+ }
+
GNUNET_CRYPTO_hash_context_finish (hash_context,
&ks.hc);
hash_context = NULL;
diff --git a/src/lib/exchange_api_link.c b/src/lib/exchange_api_link.c
index a44ccdce..10ddd471 100644
--- a/src/lib/exchange_api_link.c
+++ b/src/lib/exchange_api_link.c
@@ -113,6 +113,7 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
struct TALER_TransferSecretP secret;
struct TALER_PlanchetDetail pd;
struct TALER_CoinPubHash c_hash;
+ struct TALER_AgeCommitmentHash h_age_commitment = {0}; // TODO, see below.
/* parse reply */
memset (&nonce,
@@ -143,6 +144,7 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
&alg_values,
&bks,
&lci->coin_priv,
+ NULL, /* FIXME-oec. struct TALER_AgeCommitmentHash */
&c_hash,
&pd))
{
@@ -179,6 +181,15 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
GNUNET_CRYPTO_eddsa_key_get_public (&lh->coin_priv.eddsa_priv,
&old_coin_pub.eddsa_pub);
+ /*
+ * TODO-oec: Derive the age commitment vector and hash it into
+ * h_age_commitment.
+ * Questions:
+ * - Where do we get the information about the support for age
+ * restriction of the denomination?
+ * - Where do we get the information bout the previous coin's age groups?
+ */
+
TALER_coin_ev_hash (&pd.blinded_planchet,
&pd.denom_pub_hash,
&coin_envelope_hash);
@@ -187,6 +198,7 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
trans_pub,
&coin_envelope_hash,
&old_coin_pub,
+ &h_age_commitment,
&link_sig))
{
GNUNET_break_op (0);
diff --git a/src/lib/exchange_api_management_get_keys.c b/src/lib/exchange_api_management_get_keys.c
index 4d686633..ac419388 100644
--- a/src/lib/exchange_api_management_get_keys.c
+++ b/src/lib/exchange_api_management_get_keys.c
@@ -32,7 +32,7 @@
/**
* Set to 1 for extra debug logging.
*/
-#define DEBUG 0
+#define DEBUG 1 /* FIXME-oec */
/**
diff --git a/src/lib/exchange_api_management_post_extensions.c b/src/lib/exchange_api_management_post_extensions.c
index c0ab143f..87b0e0be 100644
--- a/src/lib/exchange_api_management_post_extensions.c
+++ b/src/lib/exchange_api_management_post_extensions.c
@@ -151,7 +151,7 @@ TALER_EXCHANGE_management_post_extensions (
body = GNUNET_JSON_PACK (
GNUNET_JSON_pack_object_steal ("extensions",
ped->extensions),
- GNUNET_JSON_pack_data_auto ("extensions_sigs",
+ GNUNET_JSON_pack_data_auto ("extensions_sig",
&ped->extensions_sig));
eh = curl_easy_init ();
@@ -168,7 +168,7 @@ TALER_EXCHANGE_management_post_extensions (
return NULL;
}
json_decref (body);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Requesting URL '%s'\n",
ph->url);
GNUNET_assert (CURLE_OK == curl_easy_setopt (eh,
diff --git a/src/lib/exchange_api_refresh_common.c b/src/lib/exchange_api_refresh_common.c
index e944b79a..89ee1e17 100644
--- a/src/lib/exchange_api_refresh_common.c
+++ b/src/lib/exchange_api_refresh_common.c
@@ -175,6 +175,7 @@ TALER_EXCHANGE_get_melt_data_ (
&alg_values[j],
bks,
coin_priv,
+ NULL, /* FIXME-oec: This needs to be setup !*/
&c_hash,
&pd))
{
diff --git a/src/lib/exchange_api_refresh_common.h b/src/lib/exchange_api_refresh_common.h
index ab19ad7d..b6926b51 100644
--- a/src/lib/exchange_api_refresh_common.h
+++ b/src/lib/exchange_api_refresh_common.h
@@ -53,6 +53,12 @@ struct MeltedCoin
struct TALER_Amount original_value;
/**
+ * The original age commitment hash. MUST be all zeroes, if no age
+ * commitment was set.
+ */
+ struct TALER_AgeCommitmentHash h_age_commitment;
+
+ /**
* Timestamp indicating when coins of this denomination become invalid.
*/
struct GNUNET_TIME_Timestamp expire_deposit;
@@ -93,6 +99,13 @@ struct FreshCoinData
struct TALER_CoinSpendPrivateKeyP coin_priv;
/**
+ * Arrays age commitments to be created, one for each cut-and-choose
+ * dimension. The entries in each list might be NULL and indicate no age
+ * commitment/restriction on the particular coin.
+ */
+ struct TALER_AgeCommitment *age_commitment[TALER_CNC_KAPPA];
+
+ /**
* Blinding key secrets for the coins, depending on the
* cut-and-choose.
*/
diff --git a/src/lib/exchange_api_refreshes_reveal.c b/src/lib/exchange_api_refreshes_reveal.c
index 08357c14..8d04c279 100644
--- a/src/lib/exchange_api_refreshes_reveal.c
+++ b/src/lib/exchange_api_refreshes_reveal.c
@@ -142,6 +142,7 @@ refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,
&rcis[i];
const struct FreshCoinData *fcd = &rrh->md.fcds[i];
const struct TALER_DenominationPublicKey *pk;
+ struct TALER_AgeCommitmentHash *ach = NULL;
json_t *jsonai;
struct TALER_BlindedDenominationSignature blind_sig;
struct TALER_CoinSpendPublicKeyP coin_pub;
@@ -160,6 +161,12 @@ refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,
jsonai = json_array_get (jsona, i);
GNUNET_assert (NULL != jsonai);
+ if (! TALER_AgeCommitmentHash_isNullOrZero (
+ &rrh->md.melted_coin.h_age_commitment))
+ {
+ /* FIXME-oec: need to pull fresh_ach from somewhere */
+ }
+
if (GNUNET_OK !=
GNUNET_JSON_parse (jsonai,
spec,
@@ -180,15 +187,15 @@ refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,
hence recomputing it here... */
GNUNET_CRYPTO_eddsa_key_get_public (&rci->coin_priv.eddsa_priv,
&coin_pub.eddsa_pub);
- /* FIXME-Oec: Age commitment hash. */
TALER_coin_pub_hash (&coin_pub,
- NULL, /* FIXME-Oec */
+ ach,
&coin_hash);
if (GNUNET_OK !=
TALER_planchet_to_coin (pk,
&blind_sig,
&bks,
&rci->coin_priv,
+ ach,
&coin_hash,
&rrh->alg_values[i],
&coin))
diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c
index 94909470..a9510715 100644
--- a/src/lib/exchange_api_refund.c
+++ b/src/lib/exchange_api_refund.c
@@ -203,6 +203,7 @@ verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh,
struct TALER_Amount deposit_fee;
struct TALER_MerchantWireHash h_wire;
struct TALER_PrivateContractHash h_contract_terms;
+ struct TALER_AgeCommitmentHash h_age_commitment = {{{0}}};
// struct TALER_ExtensionContractHash h_extensions; // FIXME!
struct TALER_DenominationHash h_denom_pub;
struct GNUNET_TIME_Timestamp wallet_timestamp;
@@ -218,6 +219,9 @@ verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh,
&h_wire),
GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
&h_denom_pub),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &h_age_commitment)),
GNUNET_JSON_spec_timestamp ("timestamp",
&wallet_timestamp),
GNUNET_JSON_spec_timestamp ("refund_deadline",
@@ -243,6 +247,7 @@ verify_conflict_history_ok (struct TALER_EXCHANGE_RefundHandle *rh,
&deposit_fee,
&h_wire,
&h_contract_terms,
+ &h_age_commitment,
NULL /* h_extensions! */,
&h_denom_pub,
wallet_timestamp,
diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c
index c832699a..efc8a99c 100644
--- a/src/lib/exchange_api_withdraw.c
+++ b/src/lib/exchange_api_withdraw.c
@@ -89,6 +89,11 @@ struct TALER_EXCHANGE_WithdrawHandle
struct TALER_ExchangeWithdrawValues alg_values;
/**
+ * Hash of the age commitment for this coin, if applicable. Maybe NULL
+ */
+ const struct TALER_AgeCommitmentHash *ach;
+
+ /**
* Denomination key we are withdrawing.
*/
struct TALER_EXCHANGE_DenomPublicKey pk;
@@ -137,6 +142,7 @@ handle_reserve_withdraw_finished (
blind_sig,
&wh->bks,
&wh->priv,
+ wh->ach,
&wh->c_hash,
&wh->alg_values,
&fc))
@@ -222,6 +228,7 @@ withdraw_cs_stage_two_callback (void *cls,
&wh->alg_values,
&wh->bks,
&wh->priv,
+ wh->ach,
&wh->c_hash,
&wh->pd))
{
@@ -249,6 +256,7 @@ TALER_EXCHANGE_withdraw (
const struct TALER_EXCHANGE_DenomPublicKey *pk,
const struct TALER_ReservePrivateKeyP *reserve_priv,
const struct TALER_PlanchetMasterSecretP *ps,
+ const struct TALER_AgeCommitmentHash *ach,
TALER_EXCHANGE_WithdrawCallback res_cb,
void *res_cb_cls)
{
@@ -260,6 +268,7 @@ TALER_EXCHANGE_withdraw (
wh->cb_cls = res_cb_cls;
wh->reserve_priv = reserve_priv;
wh->ps = *ps;
+ wh->ach = ach;
wh->pk = *pk;
TALER_denom_pub_deep_copy (&wh->pk.key,
&pk->key);
@@ -280,6 +289,7 @@ TALER_EXCHANGE_withdraw (
&wh->alg_values,
&wh->bks,
&wh->priv,
+ wh->ach,
&wh->c_hash,
&wh->pd))
{
diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am
index a6b58270..39cc6cbe 100644
--- a/src/testing/Makefile.am
+++ b/src/testing/Makefile.am
@@ -68,6 +68,7 @@ libtalertesting_la_SOURCES = \
testing_api_cmd_oauth.c \
testing_api_cmd_offline_sign_fees.c \
testing_api_cmd_offline_sign_keys.c \
+ testing_api_cmd_offline_sign_extensions.c \
testing_api_cmd_set_wire_fee.c \
testing_api_cmd_recoup.c \
testing_api_cmd_recoup_refresh.c \
@@ -249,6 +250,7 @@ test_exchange_api_cs_LDADD = \
-lgnunetcurl \
-lgnunetutil \
-ljansson \
+ -ltalerextensions \
$(XLIB)
test_exchange_api_rsa_SOURCES = \
@@ -265,6 +267,7 @@ test_exchange_api_rsa_LDADD = \
-lgnunetcurl \
-lgnunetutil \
-ljansson \
+ -ltalerextensions \
$(XLIB)
test_exchange_api_keys_cherry_picking_cs_SOURCES = \
diff --git a/src/testing/test_auditor_api.c b/src/testing/test_auditor_api.c
index 38b1b1ab..9ab78664 100644
--- a/src/testing/test_auditor_api.c
+++ b/src/testing/test_auditor_api.c
@@ -128,6 +128,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
"create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_end ()
};
@@ -168,6 +169,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-1",
"refresh-create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/**
* Try to partially spend (deposit) 1 EUR of the 5 EUR coin (in
@@ -315,6 +317,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregated",
"create-reserve-unaggregated",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_deposit ("deposit-unaggregated",
"withdraw-coin-unaggregated",
@@ -347,6 +350,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1",
"create-reserve-r1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/**
* Spend 5 EUR of the 5 EUR coin (in full). Merchant would
@@ -402,6 +406,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1",
"recoup-create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_revoke ("revoke-1",
MHD_HTTP_OK,
@@ -417,6 +422,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2",
"recoup-create-reserve-1",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/**
* These commands should close the reserve because the aggregator
@@ -447,6 +453,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2a",
"recoup-create-reserve-2",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/**
* Withdraw a 1 EUR coin, at fee of 1 ct
@@ -454,6 +461,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2b",
"recoup-create-reserve-2",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_deposit ("recoup-deposit-partial",
"recoup-withdraw-coin-2a",
@@ -491,42 +499,52 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-1",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-2",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-3",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-4",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-5",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-6",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-7",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-8",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-9",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_withdraw_amount ("massive-withdraw-10",
"massive-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_deposit (
"massive-deposit-1",
@@ -719,7 +737,7 @@ main (int argc,
GNUNET_break (0);
return 1;
case GNUNET_NO:
- return 77;
+ return 78;
case GNUNET_OK:
if (GNUNET_OK !=
/* Set up event loop and reschedule context, plus
@@ -729,11 +747,11 @@ main (int argc,
TALER_TESTING_auditor_setup (&run,
NULL,
config_file))
- return 1;
+ return 2;
break;
default:
GNUNET_break (0);
- return 1;
+ return 3;
}
return 0;
}
diff --git a/src/testing/test_exchange_api-cs.conf b/src/testing/test_exchange_api-cs.conf
index 3fbf4c3c..79332d64 100644
--- a/src/testing/test_exchange_api-cs.conf
+++ b/src/testing/test_exchange_api-cs.conf
@@ -149,7 +149,7 @@ fee_withdraw = EUR:0.00
fee_deposit = EUR:0.00
fee_refresh = EUR:0.01
fee_refund = EUR:0.01
-age_restricted = true
+age_restricted = YES
CIPHER = CS
[coin_eur_ct_10_age_restricted]
@@ -161,7 +161,7 @@ fee_withdraw = EUR:0.01
fee_deposit = EUR:0.01
fee_refresh = EUR:0.03
fee_refund = EUR:0.01
-age_restricted = true
+age_restricted = YES
CIPHER = CS
[coin_eur_1_age_restricted]
@@ -173,7 +173,7 @@ fee_withdraw = EUR:0.01
fee_deposit = EUR:0.01
fee_refresh = EUR:0.03
fee_refund = EUR:0.01
-age_restricted = true
+age_restricted = YES
CIPHER = CS
[coin_eur_5_age_restricted]
@@ -185,7 +185,7 @@ fee_withdraw = EUR:0.01
fee_deposit = EUR:0.01
fee_refresh = EUR:0.03
fee_refund = EUR:0.01
-age_restricted = true
+age_restricted = YES
CIPHER = CS
[coin_eur_10_age_restricted]
@@ -197,5 +197,5 @@ fee_withdraw = EUR:0.01
fee_deposit = EUR:0.01
fee_refresh = EUR:0.03
fee_refund = EUR:0.01
-age_restricted = true
+age_restricted = YES
CIPHER = CS
diff --git a/src/testing/test_exchange_api-rsa.conf b/src/testing/test_exchange_api-rsa.conf
index cffe3b87..1d445662 100644
--- a/src/testing/test_exchange_api-rsa.conf
+++ b/src/testing/test_exchange_api-rsa.conf
@@ -155,7 +155,7 @@ fee_deposit = EUR:0.00
fee_refresh = EUR:0.01
fee_refund = EUR:0.01
rsa_keysize = 1024
-age_restricted = true
+age_restricted = YES
CIPHER = RSA
[coin_eur_ct_10_age_restricted]
@@ -168,7 +168,7 @@ fee_deposit = EUR:0.01
fee_refresh = EUR:0.03
fee_refund = EUR:0.01
rsa_keysize = 1024
-age_restricted = true
+age_restricted = YES
CIPHER = RSA
[coin_eur_1_age_restricted]
@@ -181,7 +181,7 @@ fee_deposit = EUR:0.01
fee_refresh = EUR:0.03
fee_refund = EUR:0.01
rsa_keysize = 1024
-age_restricted = true
+age_restricted = YES
CIPHER = RSA
[coin_eur_5_age_restricted]
@@ -194,7 +194,7 @@ fee_deposit = EUR:0.01
fee_refresh = EUR:0.03
fee_refund = EUR:0.01
rsa_keysize = 1024
-age_restricted = true
+age_restricted = YES
CIPHER = RSA
[coin_eur_10_age_restricted]
@@ -207,5 +207,5 @@ fee_deposit = EUR:0.01
fee_refresh = EUR:0.03
fee_refund = EUR:0.01
rsa_keysize = 1024
-age_restricted = true
+age_restricted = YES
CIPHER = RSA
diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c
index b1779a7d..957e42e8 100644
--- a/src/testing/test_exchange_api.c
+++ b/src/testing/test_exchange_api.c
@@ -34,6 +34,7 @@
#include "taler_bank_service.h"
#include "taler_fakebank_lib.h"
#include "taler_testing_lib.h"
+#include "taler_extensions.h"
/**
* Configuration file we use. One (big) configuration is used
@@ -149,6 +150,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
"create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/**
* Withdraw EUR:1 using the SAME private coin key as for the previous coin
@@ -162,6 +164,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount_reuse_key ("withdraw-coin-1x",
"create-reserve-1",
"EUR:1",
+ 0, /* age restriction off */
"withdraw-coin-1",
MHD_HTTP_OK),
/**
@@ -177,6 +180,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
"create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_end ()
};
@@ -282,6 +286,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-1",
"refresh-create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/* Try to partially spend (deposit) 1 EUR of the 5 EUR coin
* (in full) (merchant would receive EUR:0.99 due to 1 ct
@@ -358,6 +363,61 @@ run (void *cls,
TALER_TESTING_cmd_end ()
};
+ /**
+ * Test withdrawal with age restriction. Success is expected, so it MUST be
+ * called _after_ TALER_TESTING_cmd_exec_offline_sign_extensions is called,
+ * i. e. age restriction is activated in the exchange!
+ *
+ * TODO: create a test that tries to withdraw coins with age restriction but
+ * (expectedly) fails because the exchange doesn't support age restriction
+ * yet.
+ */
+ struct TALER_TESTING_Command withdraw_age[] = {
+ /**
+ * Move money to the exchange's bank account.
+ */
+ CMD_TRANSFER_TO_EXCHANGE ("create-reserve-age",
+ "EUR:5.01"),
+ TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-age",
+ "EUR:5.01",
+ bc.user42_payto,
+ bc.exchange_payto,
+ "create-reserve-age"),
+ /**
+ * Make a reserve exist, according to the previous
+ * transfer.
+ */
+ CMD_EXEC_WIREWATCH ("wirewatch-age"),
+ /**
+ * Withdraw EUR:5.
+ */
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-age-1",
+ "create-reserve-age",
+ "EUR:5",
+ 13,
+ MHD_HTTP_OK),
+
+ TALER_TESTING_cmd_end ()
+ };
+
+ struct TALER_TESTING_Command spend_age[] = {
+ /**
+ * Spend the coin.
+ */
+ TALER_TESTING_cmd_deposit ("deposit-simple-age",
+ "withdraw-coin-age-1",
+ 0,
+ bc.user42_payto,
+ "{\"items\":[{\"name\":\"ice cream\",\"value\":1}]}",
+ GNUNET_TIME_UNIT_ZERO,
+ "EUR:4.99",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_deposit_replay ("deposit-simple-replay-age",
+ "deposit-simple-age",
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end ()
+ };
+
struct TALER_TESTING_Command track[] = {
/* Try resolving a deposit's WTID, as we never triggered
* execution of transactions, the answer should be that
@@ -400,6 +460,11 @@ run (void *cls,
"EUR:4.98",
bc.exchange_payto,
bc.user42_payto),
+ TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-499c2",
+ ec.exchange_url,
+ "EUR:4.97",
+ bc.exchange_payto,
+ bc.user42_payto),
TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c1",
ec.exchange_url,
"EUR:0.98",
@@ -463,6 +528,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregated",
"create-reserve-unaggregated",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_deposit ("deposit-unaggregated",
"withdraw-coin-unaggregated",
@@ -501,6 +567,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-aggtest",
"create-reserve-aggtest",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_deposit ("deposit-aggtest-1",
"withdraw-coin-aggtest",
@@ -549,6 +616,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1",
"create-reserve-r1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/**
* Spend 5 EUR of the 5 EUR coin (in full) (merchant would
@@ -649,6 +717,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-rb",
"create-reserve-rb",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_deposit ("deposit-refund-1b",
"withdraw-coin-rb",
@@ -698,11 +767,13 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1",
"recoup-create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/* Withdraw a 10 EUR coin, at fee of 1 ct */
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1b",
"recoup-create-reserve-1",
"EUR:10",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/* melt 10 EUR coin to get 5 EUR refreshed coin */
TALER_TESTING_cmd_melt ("recoup-melt-coin-1b",
@@ -793,6 +864,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2",
"recoup-create-reserve-1",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/**
* This withdrawal will test the logic to create a "recoup"
@@ -801,6 +873,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2-over",
"recoup-create-reserve-1",
"EUR:10",
+ 0, /* age restriction off */
MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_status ("recoup-reserve-status-2",
"recoup-create-reserve-1",
@@ -833,6 +906,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("expired-withdraw",
"short-lived-reserve",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_CONFLICT),
TALER_TESTING_cmd_check_bank_transfer ("check_bank_short-lived_reimburse",
ec.exchange_url,
@@ -857,11 +931,13 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2a",
"recoup-create-reserve-2",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/* Withdraw a 1 EUR coin, at fee of 1 ct */
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2b",
"recoup-create-reserve-2",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_deposit ("recoup-deposit-partial",
"recoup-withdraw-coin-2a",
@@ -924,6 +1000,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-3-revoked",
"recoup-create-reserve-3",
"EUR:1",
+ 0, /* age restriction off */
MHD_HTTP_GONE),
/* check that we are empty before the rejection test */
TALER_TESTING_cmd_check_bank_empty ("check-empty-again"),
@@ -970,6 +1047,8 @@ run (void *cls,
TALER_TESTING_cmd_auditor_add ("add-auditor-OK",
MHD_HTTP_NO_CONTENT,
false),
+ TALER_TESTING_cmd_exec_offline_sign_extensions ("offline-sign-extensions",
+ config_file),
TALER_TESTING_cmd_wire_add ("add-wire-account",
"payto://x-taler-bank/localhost/2",
MHD_HTTP_NO_CONTENT,
@@ -990,6 +1069,10 @@ run (void *cls,
spend),
TALER_TESTING_cmd_batch ("refresh",
refresh),
+ TALER_TESTING_cmd_batch ("withdraw-age",
+ withdraw_age),
+ TALER_TESTING_cmd_batch ("spend-age",
+ spend_age),
TALER_TESTING_cmd_batch ("track",
track),
TALER_TESTING_cmd_batch ("unaggregation",
@@ -1026,6 +1109,9 @@ main (int argc,
GNUNET_log_setup (argv[0],
"INFO",
NULL);
+
+ TALER_extensions_init ();
+
cipher = GNUNET_TESTING_get_testname_from_underscore (argv[0]);
GNUNET_assert (NULL != cipher);
uses_cs = (0 == strcmp (cipher, "cs"));
@@ -1036,6 +1122,7 @@ main (int argc,
"test_exchange_api_expire_reserve_now-%s.conf",
cipher);
GNUNET_free (cipher);
+
/* Check fakebank port is available and get config */
if (GNUNET_OK !=
TALER_TESTING_prepare_fakebank (config_file,
@@ -1054,7 +1141,7 @@ main (int argc,
GNUNET_break (0);
return 1;
case GNUNET_NO:
- return 77;
+ return 78;
case GNUNET_OK:
if (GNUNET_OK !=
/* Set up event loop and reschedule context, plus
@@ -1064,11 +1151,11 @@ main (int argc,
TALER_TESTING_setup_with_exchange (&run,
NULL,
config_file))
- return 1;
+ return 2;
break;
default:
GNUNET_break (0);
- return 1;
+ return 3;
}
return 0;
}
diff --git a/src/testing/test_exchange_api_revocation.c b/src/testing/test_exchange_api_revocation.c
index beb94dba..bb3dcc06 100644
--- a/src/testing/test_exchange_api_revocation.c
+++ b/src/testing/test_exchange_api_revocation.c
@@ -96,11 +96,13 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-revocation-coin-1",
"create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/* Withdraw another 5 EUR coin, at fee of 1 ct */
TALER_TESTING_cmd_withdraw_amount ("withdraw-revocation-coin-2",
"create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
/* Try to partially spend (deposit) 1 EUR of the 5 EUR coin (in full)
* (merchant would receive EUR:0.99 due to 1 ct deposit fee) *///
diff --git a/src/testing/test_kyc_api.c b/src/testing/test_kyc_api.c
index b9e9a9f8..ca87edd8 100644
--- a/src/testing/test_kyc_api.c
+++ b/src/testing/test_kyc_api.c
@@ -105,10 +105,12 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-no-kyc",
"create-reserve-1",
"EUR:10",
+ 0, /* age restriction off */
MHD_HTTP_ACCEPTED),
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
"create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_end ()
};
@@ -120,6 +122,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-lacking-kyc",
"create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_ACCEPTED),
TALER_TESTING_cmd_proof_kyc ("proof-kyc",
"withdraw-coin-1-lacking-kyc",
@@ -129,6 +132,7 @@ run (void *cls,
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-with-kyc",
"create-reserve-1",
"EUR:5",
+ 0, /* age restriction off */
MHD_HTTP_OK),
TALER_TESTING_cmd_end ()
};
diff --git a/src/testing/testing_api_cmd_deposit.c b/src/testing/testing_api_cmd_deposit.c
index b2fd7ddf..d3fafc63 100644
--- a/src/testing/testing_api_cmd_deposit.c
+++ b/src/testing/testing_api_cmd_deposit.c
@@ -287,6 +287,8 @@ deposit_run (void *cls,
const struct TALER_TESTING_Command *coin_cmd;
const struct TALER_CoinSpendPrivateKeyP *coin_priv;
struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_AgeCommitment *age_commitment = NULL;
+ struct TALER_AgeCommitmentHash h_age_commitment = {0};
const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
const struct TALER_DenominationSignature *denom_pub_sig;
struct TALER_CoinSpendSignatureP coin_sig;
@@ -383,6 +385,10 @@ deposit_run (void *cls,
ds->coin_index,
&coin_priv)) ||
(GNUNET_OK !=
+ TALER_TESTING_get_trait_age_commitment (coin_cmd,
+ ds->coin_index,
+ &age_commitment)) ||
+ (GNUNET_OK !=
TALER_TESTING_get_trait_denom_pub (coin_cmd,
ds->coin_index,
&denom_pub)) ||
@@ -398,6 +404,12 @@ deposit_run (void *cls,
TALER_TESTING_interpreter_fail (is);
return;
}
+
+ if (NULL != age_commitment)
+ {
+ TALER_age_commitment_hash (age_commitment, &h_age_commitment);
+ }
+
ds->deposit_fee = denom_pub->fee_deposit;
GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
&coin_pub.eddsa_pub);
@@ -431,7 +443,8 @@ deposit_run (void *cls,
&denom_pub->fee_deposit,
&h_wire,
&h_contract_terms,
- NULL, /* FIXME: extension hash! */
+ &h_age_commitment,
+ NULL, /* FIXME: add hash of extensions */
&denom_pub->h_key,
ds->wallet_timestamp,
&merchant_pub,
@@ -445,7 +458,8 @@ deposit_run (void *cls,
payto_uri,
&wire_salt,
&h_contract_terms,
- NULL, /* FIXME: extension object */
+ &h_age_commitment,
+ NULL, /* FIXME: add hash of extensions */
&coin_pub,
denom_pub_sig,
&denom_pub->key,
@@ -520,6 +534,7 @@ deposit_traits (void *cls,
const struct TALER_TESTING_Command *coin_cmd;
/* Will point to coin cmd internals. */
const struct TALER_CoinSpendPrivateKeyP *coin_spent_priv;
+ struct TALER_AgeCommitment *age_commitment;
if (GNUNET_YES != ds->command_initialized)
{
@@ -540,7 +555,11 @@ deposit_traits (void *cls,
if (GNUNET_OK !=
TALER_TESTING_get_trait_coin_priv (coin_cmd,
ds->coin_index,
- &coin_spent_priv))
+ &coin_spent_priv) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_age_commitment (coin_cmd,
+ ds->coin_index,
+ &age_commitment)))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (ds->is);
@@ -555,6 +574,8 @@ deposit_traits (void *cls,
/* These traits are always available */
TALER_TESTING_make_trait_coin_priv (0,
coin_spent_priv),
+ TALER_TESTING_make_trait_age_commitment (0,
+ age_commitment),
TALER_TESTING_make_trait_wire_details (ds->wire_details),
TALER_TESTING_make_trait_contract_terms (ds->contract_terms),
TALER_TESTING_make_trait_merchant_priv (&ds->merchant_priv),
diff --git a/src/testing/testing_api_cmd_insert_deposit.c b/src/testing/testing_api_cmd_insert_deposit.c
index be49df94..dcda7cf3 100644
--- a/src/testing/testing_api_cmd_insert_deposit.c
+++ b/src/testing/testing_api_cmd_insert_deposit.c
@@ -244,7 +244,7 @@ insert_deposit_run (void *cls,
{
uint64_t known_coin_id;
struct TALER_DenominationHash dph;
- struct TALER_AgeHash agh;
+ struct TALER_AgeCommitmentHash agh;
if ( (GNUNET_OK !=
ids->dbc->plugin->start (ids->dbc->plugin->cls,
diff --git a/src/testing/testing_api_cmd_offline_sign_extensions.c b/src/testing/testing_api_cmd_offline_sign_extensions.c
new file mode 100644
index 00000000..f39679f9
--- /dev/null
+++ b/src/testing/testing_api_cmd_offline_sign_extensions.c
@@ -0,0 +1,164 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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 testing/testing_api_cmd_offline_sign_extensions.c
+ * @brief run the taler-exchange-offline command to sign extensions (and therefore activate them)
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "extensionssign" CMD.
+ */
+struct ExtensionsSignState
+{
+
+ /**
+ * Process for the "extensionssign" command.
+ */
+ struct GNUNET_OS_Process *extensionssign_proc;
+
+ /**
+ * Configuration file used by the command.
+ */
+ const char *config_filename;
+
+};
+
+
+/**
+ * Run the command; calls the `taler-exchange-offline' program.
+ *
+ * @param cls closure.
+ * @param cmd the commaind being run.
+ * @param is interpreter state.
+ */
+static void
+extensionssign_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct ExtensionsSignState *ks = cls;
+
+ ks->extensionssign_proc
+ = GNUNET_OS_start_process (
+ GNUNET_OS_INHERIT_STD_ALL,
+ NULL, NULL, NULL,
+ "taler-exchange-offline",
+ "taler-exchange-offline",
+ "-c", ks->config_filename,
+ "-L", "INFO",
+ "extensions",
+ "sign",
+ "upload",
+ NULL);
+ if (NULL == ks->extensionssign_proc)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "extensionssign" CMD, and possibly kills its
+ * process if it did not terminate correctly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+extensionssign_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct ExtensionsSignState *ks = cls;
+
+ (void) cmd;
+ if (NULL != ks->extensionssign_proc)
+ {
+ GNUNET_break (0 ==
+ GNUNET_OS_process_kill (ks->extensionssign_proc,
+ SIGKILL));
+ GNUNET_OS_process_wait (ks->extensionssign_proc);
+ GNUNET_OS_process_destroy (ks->extensionssign_proc);
+ ks->extensionssign_proc = NULL;
+ }
+ GNUNET_free (ks);
+}
+
+
+/**
+ * Offer "extensionssign" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+extensionssign_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct ExtensionsSignState *ks = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_process (&ks->extensionssign_proc),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_extensions (const char *label,
+ const char *config_filename)
+{
+ struct ExtensionsSignState *ks;
+
+ ks = GNUNET_new (struct ExtensionsSignState);
+ ks->config_filename = config_filename;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ks,
+ .label = label,
+ .run = &extensionssign_run,
+ .cleanup = &extensionssign_cleanup,
+ .traits = &extensionssign_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_exec_offline_sign_extensions.c */
diff --git a/src/testing/testing_api_cmd_refresh.c b/src/testing/testing_api_cmd_refresh.c
index de3efd13..11c88c19 100644
--- a/src/testing/testing_api_cmd_refresh.c
+++ b/src/testing/testing_api_cmd_refresh.c
@@ -70,6 +70,11 @@ struct TALER_TESTING_FreshCoinData
*/
struct TALER_CoinSpendPrivateKeyP coin_priv;
+ /*
+ * Age commitment for the coin, NULL if not applicable.
+ */
+ struct TALER_AgeCommitment *age_commitment;
+
/**
* The blinding key (needed for recoup operations).
*/
@@ -132,6 +137,11 @@ struct RefreshMeltState
*/
const struct TALER_CoinSpendPrivateKeyP *melt_priv;
+ /*
+ * Age commitment for the coin, NULL if not applicable.
+ */
+ struct TALER_AgeCommitment *age_commitment;
+
/**
* Task scheduled to try later.
*/
@@ -1038,6 +1048,7 @@ melt_run (void *cls,
const struct TALER_DenominationSignature *melt_sig;
const struct TALER_EXCHANGE_DenomPublicKey *melt_denom_pub;
const struct TALER_TESTING_Command *coin_command;
+ bool age_restricted;
if (NULL == (coin_command
= TALER_TESTING_interpreter_lookup_command (
@@ -1059,6 +1070,16 @@ melt_run (void *cls,
return;
}
if (GNUNET_OK !=
+ TALER_TESTING_get_trait_age_commitment (coin_command,
+ 0,
+ &rms->age_commitment))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (rms->is);
+ return;
+ }
+
+ if (GNUNET_OK !=
TALER_TESTING_get_trait_denom_sig (coin_command,
0,
&melt_sig))
@@ -1067,6 +1088,7 @@ melt_run (void *cls,
TALER_TESTING_interpreter_fail (rms->is);
return;
}
+
if (GNUNET_OK !=
TALER_TESTING_get_trait_denom_pub (coin_command,
0,
@@ -1076,9 +1098,11 @@ melt_run (void *cls,
TALER_TESTING_interpreter_fail (rms->is);
return;
}
+
/* Melt amount starts with the melt fee of the old coin; we'll add the
values and withdraw fees of the fresh coins next */
melt_amount = melt_denom_pub->fee_refresh;
+ age_restricted = melt_denom_pub->key.age_mask.mask != 0;
for (unsigned int i = 0; i<num_fresh_coins; i++)
{
const struct TALER_EXCHANGE_DenomPublicKey *fresh_pk;
@@ -1096,7 +1120,8 @@ melt_run (void *cls,
return;
}
fresh_pk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange),
- &fresh_amount);
+ &fresh_amount,
+ age_restricted);
if (NULL == fresh_pk)
{
GNUNET_break (0);
@@ -1117,12 +1142,36 @@ melt_run (void *cls,
TALER_denom_pub_deep_copy (&rms->fresh_pks[i].key,
&fresh_pk->key);
} /* end for */
+
rms->refresh_data.melt_priv = *rms->melt_priv;
rms->refresh_data.melt_amount = melt_amount;
rms->refresh_data.melt_sig = *melt_sig;
rms->refresh_data.melt_pk = *melt_denom_pub;
rms->refresh_data.fresh_pks = rms->fresh_pks;
rms->refresh_data.fresh_pks_len = num_fresh_coins;
+/* FIXME-oec: is this needed _here_?
+ {
+ struct TALER_AgeCommitment *ac = NULL;
+
+ GNUNET_assert (age_restricted == (NULL != rms->age_commitment));
+
+ if (NULL != rms->age_commitment)
+ {
+ uint32_t seed = GNUNET_CRYPTO_random_u32 (
+ GNUNET_CRYPTO_QUALITY_WEAK,
+ UINT32_MAX);
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_age_commitment_derive (
+ rms->age_commitment,
+ seed,
+ ac));
+ }
+
+ rms->refresh_data.age_commitment = ac
+ }
+*/
+
rms->rmh = TALER_EXCHANGE_melt (is->exchange,
&rms->rms,
&rms->refresh_data,
@@ -1207,6 +1256,8 @@ melt_traits (void *cls,
&rms->fresh_pks[index]),
TALER_TESTING_make_trait_coin_priv (0,
rms->melt_priv),
+ TALER_TESTING_make_trait_age_commitment (index,
+ rms->age_commitment),
TALER_TESTING_make_trait_exchange_wd_value (index,
&rms->mbds[index].alg_value),
TALER_TESTING_make_trait_refresh_secret (&rms->rms),
@@ -1370,6 +1421,9 @@ refresh_reveal_traits (void *cls,
TALER_TESTING_make_trait_coin_priv (
index,
&rrs->fresh_coins[index].coin_priv),
+ TALER_TESTING_make_trait_age_commitment (
+ index,
+ rrs->fresh_coins[index].age_commitment),
TALER_TESTING_make_trait_denom_pub (
index,
rrs->fresh_coins[index].pk),
diff --git a/src/testing/testing_api_cmd_withdraw.c b/src/testing/testing_api_cmd_withdraw.c
index c7265c6c..e5e8adfd 100644
--- a/src/testing/testing_api_cmd_withdraw.c
+++ b/src/testing/testing_api_cmd_withdraw.c
@@ -27,6 +27,7 @@
#include <microhttpd.h>
#include <gnunet/gnunet_curl_lib.h>
#include "taler_signatures.h"
+#include "taler_extensions.h"
#include "taler_testing_lib.h"
#include "backoff.h"
@@ -132,6 +133,18 @@ struct WithdrawState
struct TALER_PlanchetMasterSecretP ps;
/**
+ * An age > 0 signifies age restriction is required
+ */
+ uint8_t age;
+
+ /**
+ * If age > 0, put here the corresponding age commitment and its hash,
+ * respectivelly, NULL otherwise.
+ */
+ struct TALER_AgeCommitment *age_commitment;
+ struct TALER_AgeCommitmentHash *h_age_commitment;
+
+ /**
* Reserve history entry that corresponds to this operation.
* Will be of type #TALER_EXCHANGE_RTT_WITHDRAWAL.
*/
@@ -382,12 +395,14 @@ withdraw_run (void *cls,
= TALER_TESTING_interpreter_lookup_command (
is,
ws->reserve_reference);
+
if (NULL == create_reserve)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
+
if (GNUNET_OK !=
TALER_TESTING_get_trait_reserve_priv (create_reserve,
&rp))
@@ -396,6 +411,7 @@ withdraw_run (void *cls,
TALER_TESTING_interpreter_fail (is);
return;
}
+
if (NULL == ws->exchange_url)
ws->exchange_url
= GNUNET_strdup (TALER_EXCHANGE_get_base_url (is->exchange));
@@ -405,6 +421,7 @@ withdraw_run (void *cls,
ws->reserve_payto_uri
= TALER_payto_from_reserve (ws->exchange_url,
&ws->reserve_pub);
+
if (NULL == ws->reuse_coin_key_ref)
{
TALER_planchet_master_setup_random (&ws->ps);
@@ -429,10 +446,12 @@ withdraw_run (void *cls,
&ps));
ws->ps = *ps;
}
+
if (NULL == ws->pk)
{
dpk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange),
- &ws->amount);
+ &ws->amount,
+ ws->age > 0);
if (NULL == dpk)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -450,18 +469,24 @@ withdraw_run (void *cls,
{
ws->amount = ws->pk->value;
}
+
ws->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL;
GNUNET_assert (0 <=
TALER_amount_add (&ws->reserve_history.amount,
&ws->amount,
&ws->pk->fee_withdraw));
ws->reserve_history.details.withdraw.fee = ws->pk->fee_withdraw;
- ws->wsh = TALER_EXCHANGE_withdraw (is->exchange,
- ws->pk,
- rp,
- &ws->ps,
- &reserve_withdraw_cb,
- ws);
+
+ {
+
+ ws->wsh = TALER_EXCHANGE_withdraw (is->exchange,
+ ws->pk,
+ rp,
+ &ws->ps,
+ ws->h_age_commitment,
+ &reserve_withdraw_cb,
+ ws);
+ }
if (NULL == ws->wsh)
{
GNUNET_break (0);
@@ -503,6 +528,16 @@ withdraw_cleanup (void *cls,
TALER_EXCHANGE_destroy_denomination_key (ws->pk);
ws->pk = NULL;
}
+ if (NULL != ws->age_commitment)
+ {
+ GNUNET_free (ws->age_commitment);
+ ws->age_commitment = NULL;
+ }
+ if (NULL != ws->h_age_commitment)
+ {
+ GNUNET_free (ws->h_age_commitment);
+ ws->h_age_commitment = NULL;
+ }
GNUNET_free (ws->exchange_url);
GNUNET_free (ws->reserve_payto_uri);
GNUNET_free (ws);
@@ -538,7 +573,7 @@ withdraw_traits (void *cls,
&ws->exchange_vals),
TALER_TESTING_make_trait_denom_pub (0 /* only one coin */,
ws->pk),
- TALER_TESTING_make_trait_denom_sig (0 /* only one coin */,
+ TALER_TESTING_make_trait_denom_sig (index /* only one coin */,
&ws->sig),
TALER_TESTING_make_trait_reserve_priv (&ws->reserve_priv),
TALER_TESTING_make_trait_reserve_pub (&ws->reserve_pub),
@@ -548,6 +583,8 @@ withdraw_traits (void *cls,
(const char **) &ws->reserve_payto_uri),
TALER_TESTING_make_trait_exchange_url (
(const char **) &ws->exchange_url),
+ TALER_TESTING_make_trait_age_commitment (index, ws->age_commitment),
+ TALER_TESTING_make_trait_h_age_commitment (index, ws->h_age_commitment),
TALER_TESTING_trait_end ()
};
@@ -567,6 +604,7 @@ withdraw_traits (void *cls,
* @param label command label.
* @param reserve_reference command providing us with a reserve to withdraw from
* @param amount how much we withdraw.
+ * @param age if > 0, age restriction is activated
* @param expected_response_code which HTTP response code
* we expect from the exchange.
* @return the withdraw command to be executed by the interpreter.
@@ -575,11 +613,45 @@ struct TALER_TESTING_Command
TALER_TESTING_cmd_withdraw_amount (const char *label,
const char *reserve_reference,
const char *amount,
+ const uint8_t age,
unsigned int expected_response_code)
{
struct WithdrawState *ws;
ws = GNUNET_new (struct WithdrawState);
+
+ ws->age = age;
+ if (0 < age)
+ {
+ struct TALER_AgeCommitment *ac;
+ struct TALER_AgeCommitmentHash *hac;
+ uint32_t seed;
+ struct TALER_AgeMask mask;
+
+ ac = GNUNET_new (struct TALER_AgeCommitment);
+ hac = GNUNET_new (struct TALER_AgeCommitmentHash);
+ seed = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX);
+ mask = TALER_extensions_age_restriction_ageMask ();
+
+ if (GNUNET_OK !=
+ TALER_age_restriction_commit (
+ &mask,
+ age,
+ seed,
+ ac))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to generate age commitment for age %d at %s\n",
+ age,
+ label);
+ GNUNET_assert (0);
+ }
+
+ TALER_age_commitment_hash (ac,hac);
+ ws->age_commitment = ac;
+ ws->h_age_commitment = hac;
+ }
+
ws->reserve_reference = reserve_reference;
if (GNUNET_OK !=
TALER_string_to_amount (amount,
@@ -615,6 +687,7 @@ TALER_TESTING_cmd_withdraw_amount (const char *label,
* @param label command label.
* @param reserve_reference command providing us with a reserve to withdraw from
* @param amount how much we withdraw.
+ * @param age if > 0, age restriction is activated
* @param coin_ref reference to (withdraw/reveal) command of a coin
* from which we should re-use the private key
* @param expected_response_code which HTTP response code
@@ -626,6 +699,7 @@ TALER_TESTING_cmd_withdraw_amount_reuse_key (
const char *label,
const char *reserve_reference,
const char *amount,
+ uint8_t age,
const char *coin_ref,
unsigned int expected_response_code)
{
@@ -634,6 +708,7 @@ TALER_TESTING_cmd_withdraw_amount_reuse_key (
cmd = TALER_TESTING_cmd_withdraw_amount (label,
reserve_reference,
amount,
+ age,
expected_response_code);
{
struct WithdrawState *ws = cmd.cls;
diff --git a/src/testing/testing_api_helpers_exchange.c b/src/testing/testing_api_helpers_exchange.c
index 8e0e0298..1eecbfeb 100644
--- a/src/testing/testing_api_helpers_exchange.c
+++ b/src/testing/testing_api_helpers_exchange.c
@@ -27,6 +27,7 @@
#include "taler_json_lib.h"
#include <gnunet/gnunet_curl_lib.h>
#include "taler_signatures.h"
+#include "taler_extensions.h"
#include "taler_testing_lib.h"
/**
@@ -312,6 +313,9 @@ sign_keys_for_exchange (void *cls,
char *exchange_master_pub;
int ret;
+ /* Load the age restriction mask from the configuration */
+ TALER_extensions_load_taler_config (cfg);
+
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (cfg,
"exchange",
@@ -402,7 +406,8 @@ TALER_TESTING_prepare_exchange (const char *config_filename,
const struct TALER_EXCHANGE_DenomPublicKey *
TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_Amount *amount)
+ const struct TALER_Amount *amount,
+ bool age_restricted)
{
struct GNUNET_TIME_Timestamp now;
struct TALER_EXCHANGE_DenomPublicKey *pk;
@@ -419,7 +424,8 @@ TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys,
pk->valid_from)) &&
(GNUNET_TIME_timestamp_cmp (now,
<,
- pk->withdraw_valid_until)) )
+ pk->withdraw_valid_until)) &&
+ (age_restricted == (0 != pk->key.age_mask.mask)) )
return pk;
}
/* do 2nd pass to check if expiration times are to blame for
@@ -435,7 +441,8 @@ TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys,
pk->valid_from) ||
GNUNET_TIME_timestamp_cmp (now,
>,
- pk->withdraw_valid_until) ) )
+ pk->withdraw_valid_until) ) &&
+ (age_restricted == (0 != pk->key.age_mask.mask)) )
{
GNUNET_log
(GNUNET_ERROR_TYPE_WARNING,
diff --git a/src/util/crypto.c b/src/util/crypto.c
index 13b9188c..6bea984f 100644
--- a/src/util/crypto.c
+++ b/src/util/crypto.c
@@ -20,11 +20,16 @@
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
+ * @author Özgür Kesim
*/
#include "platform.h"
#include "taler_util.h"
#include <gcrypt.h>
+/**
+ * Used in TALER_AgeCommitmentHash_isNullOrZero for comparison
+ */
+const struct TALER_AgeCommitmentHash TALER_ZeroAgeCommitmentHash = {0};
/**
* Function called by libgcrypt on serious errors.
@@ -83,12 +88,11 @@ TALER_test_coin_valid (const struct TALER_CoinPublicInfo *coin_public_info,
GNUNET_memcmp (&d_hash,
&coin_public_info->denom_pub_hash));
#endif
- // FIXME-Oec: replace with function that
- // also hashes the age vector if we have
- // one!
- GNUNET_CRYPTO_hash (&coin_public_info->coin_pub,
- sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
- &c_hash.hash);
+
+ TALER_coin_pub_hash (&coin_public_info->coin_pub,
+ &coin_public_info->age_commitment_hash,
+ &c_hash);
+
if (GNUNET_OK !=
TALER_denom_pub_verify (denom_pub,
&coin_public_info->denom_sig,
@@ -251,6 +255,7 @@ TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk,
const struct TALER_ExchangeWithdrawValues *alg_values,
const union TALER_DenominationBlindingKeyP *bks,
const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ const struct TALER_AgeCommitmentHash *ach,
struct TALER_CoinPubHash *c_hash,
struct TALER_PlanchetDetail *pd
)
@@ -263,7 +268,7 @@ TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk,
if (GNUNET_OK !=
TALER_denom_blind (dk,
bks,
- NULL, /* FIXME-Oec */
+ ach,
&coin_pub,
alg_values,
c_hash,
@@ -291,6 +296,7 @@ TALER_planchet_to_coin (
const struct TALER_BlindedDenominationSignature *blind_sig,
const union TALER_DenominationBlindingKeyP *bks,
const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ const struct TALER_AgeCommitmentHash *ach,
const struct TALER_CoinPubHash *c_hash,
const struct TALER_ExchangeWithdrawValues *alg_values,
struct TALER_FreshCoin *coin)
@@ -321,7 +327,9 @@ TALER_planchet_to_coin (
TALER_denom_sig_free (&coin->sig);
return GNUNET_SYSERR;
}
+
coin->coin_priv = *coin_priv;
+ coin->h_age_commitment = ach;
return GNUNET_OK;
}
@@ -396,10 +404,10 @@ TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc,
void
TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_AgeHash *age_commitment_hash,
+ const struct TALER_AgeCommitmentHash *ach,
struct TALER_CoinPubHash *coin_h)
{
- if (NULL == age_commitment_hash)
+ if (TALER_AgeCommitmentHash_isNullOrZero (ach))
{
/* No age commitment was set */
GNUNET_CRYPTO_hash (&coin_pub->eddsa_pub,
@@ -411,14 +419,14 @@ TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub,
/* Coin comes with age commitment. Take the hash of the age commitment
* into account */
const size_t key_s = sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey);
- const size_t age_s = sizeof(struct TALER_AgeHash);
+ const size_t age_s = sizeof(struct TALER_AgeCommitmentHash);
char data[key_s + age_s];
GNUNET_memcpy (&data[0],
&coin_pub->eddsa_pub,
key_s);
GNUNET_memcpy (&data[key_s],
- age_commitment_hash,
+ ach,
age_s);
GNUNET_CRYPTO_hash (&data,
key_s + age_s,
@@ -427,4 +435,276 @@ TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub,
}
+void
+TALER_age_commitment_hash (
+ const struct TALER_AgeCommitment *commitment,
+ struct TALER_AgeCommitmentHash *ahash)
+{
+ struct GNUNET_HashContext *hash_context;
+ struct GNUNET_HashCode hash;
+
+ GNUNET_assert (NULL != ahash);
+ if (NULL == commitment)
+ {
+ memset (ahash, 0, sizeof(struct TALER_AgeCommitmentHash));
+ return;
+ }
+
+ GNUNET_assert (__builtin_popcount (commitment->mask.mask) - 1 ==
+ commitment->num_pub);
+
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+
+ for (size_t i = 0; i < commitment->num_pub; i++)
+ {
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &commitment->pub[i],
+ sizeof(struct
+ GNUNET_CRYPTO_EddsaPublicKey));
+ }
+
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ &hash);
+ GNUNET_memcpy (&ahash->shash.bits,
+ &hash.bits,
+ sizeof(ahash->shash.bits));
+}
+
+
+/* To a given age value between 0 and 31, returns the index of the age group
+ * defined by the given mask.
+ */
+static uint8_t
+get_age_group (
+ const struct TALER_AgeMask *mask,
+ uint8_t age)
+{
+ uint32_t m = mask->mask;
+ uint8_t i = 0;
+
+ while (m > 0)
+ {
+ if (0 >= age)
+ break;
+ m = m >> 1;
+ i += m & 1;
+ age--;
+ }
+ return i;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_age_restriction_commit (
+ const struct TALER_AgeMask *mask,
+ const uint8_t age,
+ const uint32_t seed,
+ struct TALER_AgeCommitment *new)
+{
+ uint8_t num_pub = __builtin_popcount (mask->mask) - 1;
+ uint8_t num_priv = get_age_group (mask, age) - 1;
+ size_t i;
+
+ GNUNET_assert (NULL != new);
+ GNUNET_assert (mask->mask & 1); /* fist bit must have been set */
+ GNUNET_assert (0 <= num_priv);
+ GNUNET_assert (31 > num_priv);
+
+ new->mask.mask = mask->mask;
+ new->num_pub = num_pub;
+ new->num_priv = num_priv;
+
+ new->pub = GNUNET_new_array (
+ num_pub,
+ struct TALER_AgeCommitmentPublicKeyP);
+ new->priv = GNUNET_new_array (
+ num_priv,
+ struct TALER_AgeCommitmentPrivateKeyP);
+
+ /* Create as many private keys as we need */
+ for (i = 0; i < num_priv; i++)
+ {
+ uint32_t seedBE = htonl (seed + i);
+
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_kdf (&new->priv[i],
+ sizeof (new->priv[i]),
+ &seedBE,
+ sizeof (seedBE),
+ "taler-age-commitment-derivation",
+ strlen (
+ "taler-age-commitment-derivation"),
+ NULL, 0))
+ goto FAIL;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&new->priv[i].eddsa_priv,
+ &new->pub[i].eddsa_pub);
+ }
+
+ /* Fill the rest of the public keys with random values */
+ for (; i<num_pub; i++)
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &new->pub[i],
+ sizeof(new->pub[i]));
+
+ return GNUNET_OK;
+
+FAIL:
+ GNUNET_free (new->pub);
+ GNUNET_free (new->priv);
+ return GNUNET_SYSERR;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_age_commitment_derive (
+ const struct TALER_AgeCommitment *orig,
+ const uint32_t seed,
+ struct TALER_AgeCommitment *new)
+{
+ struct GNUNET_CRYPTO_EccScalar val;
+
+ /*
+ * age commitment consists of GNUNET_CRYPTO_Eddsa{Private,Public}Key
+ *
+ * GNUNET_CRYPTO_EddsaPrivateKey is a
+ * unsigned char d[256 / 8];
+ *
+ * GNUNET_CRYPTO_EddsaPublicKey is a
+ * unsigned char q_y[256 / 8];
+ *
+ * We want to multiply, both, the Private Key by an integer factor and the
+ * public key (point on curve) with the equivalent scalar.
+ *
+ * From the seed we will derive
+ * 1. a scalar to multiply the public keys with
+ * 2. a factor to multiply the private key with
+ *
+ * Invariants:
+ * point*scalar == public(private*factor)
+ *
+ * A point on a curve is GNUNET_CRYPTO_EccPoint which is
+ * unsigned char v[256 / 8];
+ *
+ * A ECC scaler for use in point multiplications is a
+ * GNUNET_CRYPTO_EccScalar which is a
+ * unsigned car v[256 / 8];
+ * */
+
+ GNUNET_assert (NULL != new);
+ GNUNET_assert (orig->num_pub == __builtin_popcount (orig->mask.mask) - 1);
+ GNUNET_assert (orig->num_priv <= orig->num_pub);
+
+ new->mask = orig->mask;
+ new->num_pub = orig->num_pub;
+ new->num_priv = orig->num_priv;
+ new->pub = GNUNET_new_array (
+ new->num_pub,
+ struct TALER_AgeCommitmentPublicKeyP);
+ new->priv = GNUNET_new_array (
+ new->num_priv,
+ struct TALER_AgeCommitmentPrivateKeyP);
+
+
+ GNUNET_CRYPTO_ecc_scalar_from_int (seed, &val);
+
+ /* scalar multiply the public keys on the curve */
+ for (size_t i = 0; i < orig->num_pub; i++)
+ {
+ /* We shift all keys by the same scalar */
+ struct GNUNET_CRYPTO_EccPoint *p = (struct
+ GNUNET_CRYPTO_EccPoint *) &orig->pub[i];
+ struct GNUNET_CRYPTO_EccPoint *np = (struct
+ GNUNET_CRYPTO_EccPoint *) &new->pub[i];
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_ecc_pmul_mpi (
+ p,
+ &val,
+ np))
+ goto FAIL;
+
+ }
+
+ /* multiply the private keys */
+ /* we borough ideas from GNUNET_CRYPTO_ecdsa_private_key_derive */
+ {
+ uint32_t seedBE;
+ uint8_t dc[32];
+ gcry_mpi_t f, x, d, n;
+ gcry_ctx_t ctx;
+
+ GNUNET_assert (0==gcry_mpi_ec_new (&ctx,NULL, "Ed25519"));
+ n = gcry_mpi_ec_get_mpi ("n", ctx, 1);
+
+ /* make the seed big endian */
+ seedBE = GNUNET_htonll (seed);
+
+ GNUNET_CRYPTO_mpi_scan_unsigned (&f, &seedBE, sizeof(seedBE));
+
+ for (size_t i = 0; i < orig->num_priv; i++)
+ {
+
+ /* convert to big endian for libgrypt */
+ for (size_t j = 0; j < 32; j++)
+ dc[i] = orig->priv[i].eddsa_priv.d[31 - j];
+ GNUNET_CRYPTO_mpi_scan_unsigned (&x, dc, sizeof(dc));
+
+ d = gcry_mpi_new (256);
+ gcry_mpi_mulm (d, f, x, n);
+ gcry_mpi_release (x);
+ gcry_mpi_release (d);
+ gcry_mpi_release (n);
+ gcry_mpi_release (d);
+ GNUNET_CRYPTO_mpi_print_unsigned (dc, sizeof(dc), d);
+
+ for (size_t j = 0; j <32; j++)
+ new->priv[i].eddsa_priv.d[j] = dc[31 - 1];
+
+ sodium_memzero (dc, sizeof(dc));
+
+ /* TODO:
+ * make sure that the calculated private key generate the same public
+ * keys */
+ }
+
+ gcry_mpi_release (f);
+ gcry_ctx_release (ctx);
+ }
+
+ return GNUNET_OK;
+
+FAIL:
+ GNUNET_free (new->pub);
+ GNUNET_free (new->priv);
+ return GNUNET_SYSERR;
+}
+
+
+void
+TALER_age_restriction_commmitment_free_inside (
+ struct TALER_AgeCommitment *commitment)
+{
+ if (NULL == commitment)
+ return;
+
+ if (NULL != commitment->priv)
+ {
+ GNUNET_CRYPTO_zero_keys (
+ commitment->priv,
+ sizeof(*commitment->priv) * commitment->num_priv);
+
+ GNUNET_free (commitment->priv);
+ commitment->priv = NULL;
+ }
+
+ if (NULL != commitment->pub)
+ {
+ GNUNET_free (commitment->pub);
+ commitment->priv = NULL;
+ }
+
+ /* Caller is responsible for commitment itself */
+}
+
+
/* end of crypto.c */
diff --git a/src/util/denom.c b/src/util/denom.c
index 783e9a36..7c2c42c9 100644
--- a/src/util/denom.c
+++ b/src/util/denom.c
@@ -297,14 +297,14 @@ enum GNUNET_GenericReturnValue
TALER_denom_blind (
const struct TALER_DenominationPublicKey *dk,
const union TALER_DenominationBlindingKeyP *coin_bks,
- const struct TALER_AgeHash *age_commitment_hash,
+ const struct TALER_AgeCommitmentHash *ach,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_ExchangeWithdrawValues *alg_values,
struct TALER_CoinPubHash *c_hash,
struct TALER_BlindedPlanchet *blinded_planchet)
{
TALER_coin_pub_hash (coin_pub,
- age_commitment_hash,
+ ach,
c_hash);
switch (dk->cipher)
{
diff --git a/src/util/test_crypto.c b/src/util/test_crypto.c
index fbf30e3a..cda17d9b 100644
--- a/src/util/test_crypto.c
+++ b/src/util/test_crypto.c
@@ -152,6 +152,7 @@ test_planchets_rsa (void)
&alg_values,
&bks,
&coin_priv,
+ NULL, /* no age commitment */
&c_hash,
&pd));
GNUNET_assert (GNUNET_OK ==
@@ -164,6 +165,7 @@ test_planchets_rsa (void)
&blind_sig,
&bks,
&coin_priv,
+ NULL, /* no age commitment */
&c_hash,
&alg_values,
&coin));
@@ -175,6 +177,8 @@ test_planchets_rsa (void)
}
+/** FIXME-oec: Add test for planchets with age commitment hash */
+
/**
* @brief Function for CS signatures to derive public R_0 and R_1
*
@@ -392,10 +396,12 @@ main (int argc,
return 1;
if (0 != test_planchets ())
return 2;
- if (0 != test_exchange_sigs ())
+ if (0 != test_planchets_with_age_commitment ())
return 3;
- if (0 != test_merchant_sigs ())
+ if (0 != test_exchange_sigs ())
return 4;
+ if (0 != test_merchant_sigs ())
+ return 5;
return 0;
}
diff --git a/src/util/test_helper_rsa.c b/src/util/test_helper_rsa.c
index 679f5d7f..2ead8a6e 100644
--- a/src/util/test_helper_rsa.c
+++ b/src/util/test_helper_rsa.c
@@ -269,6 +269,7 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh)
bool success = false;
struct TALER_PlanchetMasterSecretP ps;
struct TALER_ExchangeWithdrawValues alg_values;
+ struct TALER_AgeCommitmentHash ach;
struct TALER_CoinPubHash c_hash;
struct TALER_CoinSpendPrivateKeyP coin_priv;
union TALER_DenominationBlindingKeyP bks;
@@ -280,6 +281,9 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh)
alg_values.cipher = TALER_DENOMINATION_RSA;
TALER_planchet_setup_coin_priv (&ps, &alg_values, &coin_priv);
TALER_planchet_blinding_secret_create (&ps, &alg_values, &bks);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &ach,
+ sizeof(ach));
for (unsigned int i = 0; i<MAX_KEYS; i++)
{
@@ -296,6 +300,7 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh)
&alg_values,
&bks,
&coin_priv,
+ &ach,
&c_hash,
&pd));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -440,6 +445,7 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,
struct GNUNET_TIME_Relative duration;
struct TALER_PlanchetMasterSecretP ps;
struct TALER_CoinSpendPrivateKeyP coin_priv;
+ struct TALER_AgeCommitmentHash ach;
union TALER_DenominationBlindingKeyP bks;
struct TALER_ExchangeWithdrawValues alg_values;
@@ -447,7 +453,9 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,
alg_values.cipher = TALER_DENOMINATION_RSA;
TALER_planchet_setup_coin_priv (&ps, &alg_values, &coin_priv);
TALER_planchet_blinding_secret_create (&ps, &alg_values, &bks);
-
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &ach,
+ sizeof(ach));
duration = GNUNET_TIME_UNIT_ZERO;
TALER_CRYPTO_helper_rsa_poll (dh);
for (unsigned int j = 0; j<NUM_SIGN_PERFS;)
@@ -477,6 +485,7 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,
&alg_values,
&bks,
&coin_priv,
+ &ach,
&c_hash,
&pd));
/* use this key as long as it works */
diff --git a/src/util/wallet_signatures.c b/src/util/wallet_signatures.c
index 01f33ae8..88cd8de0 100644
--- a/src/util/wallet_signatures.c
+++ b/src/util/wallet_signatures.c
@@ -29,6 +29,7 @@ TALER_wallet_deposit_sign (
const struct TALER_Amount *deposit_fee,
const struct TALER_MerchantWireHash *h_wire,
const struct TALER_PrivateContractHash *h_contract_terms,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
const struct TALER_ExtensionContractHash *h_extensions,
const struct TALER_DenominationHash *h_denom_pub,
struct GNUNET_TIME_Timestamp wallet_timestamp,
@@ -48,8 +49,12 @@ TALER_wallet_deposit_sign (
.merchant = *merchant_pub
};
+ if (NULL != h_age_commitment)
+ dr.h_age_commitment = *h_age_commitment;
+
if (NULL != h_extensions)
dr.h_extensions = *h_extensions;
+
TALER_amount_hton (&dr.amount_with_fee,
amount);
TALER_amount_hton (&dr.deposit_fee,
@@ -66,6 +71,7 @@ TALER_wallet_deposit_verify (
const struct TALER_Amount *deposit_fee,
const struct TALER_MerchantWireHash *h_wire,
const struct TALER_PrivateContractHash *h_contract_terms,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
const struct TALER_ExtensionContractHash *h_extensions,
const struct TALER_DenominationHash *h_denom_pub,
struct GNUNET_TIME_Timestamp wallet_timestamp,
@@ -82,11 +88,17 @@ TALER_wallet_deposit_verify (
.h_denom_pub = *h_denom_pub,
.wallet_timestamp = GNUNET_TIME_timestamp_hton (wallet_timestamp),
.refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline),
- .merchant = *merchant_pub
+ .merchant = *merchant_pub,
+ .h_age_commitment = {{{0}}},
+ .h_extensions = {{{0}}}
};
+ if (NULL != h_age_commitment)
+ dr.h_age_commitment = *h_age_commitment;
+
if (NULL != h_extensions)
dr.h_extensions = *h_extensions;
+
TALER_amount_hton (&dr.amount_with_fee,
amount);
TALER_amount_hton (&dr.deposit_fee,
@@ -131,6 +143,7 @@ TALER_wallet_link_verify (
const struct TALER_TransferPublicKeyP *transfer_pub,
const struct TALER_BlindedCoinHash *h_coin_ev,
const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
const struct TALER_CoinSpendSignatureP *coin_sig)
{
struct TALER_LinkDataPS ldp = {
@@ -138,9 +151,13 @@ TALER_wallet_link_verify (
.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK),
.h_denom_pub = *h_denom_pub,
.transfer_pub = *transfer_pub,
- .coin_envelope_hash = *h_coin_ev
+ .coin_envelope_hash = *h_coin_ev,
+ .h_age_commitment = {{{0}}}
};
+ if (NULL != h_age_commitment)
+ ldp.h_age_commitment = *h_age_commitment;
+
return
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK,
&ldp,
@@ -263,6 +280,7 @@ TALER_wallet_melt_verify (
const struct TALER_Amount *melt_fee,
const struct TALER_RefreshCommitmentP *rc,
const struct TALER_DenominationHash *h_denom_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_CoinSpendSignatureP *coin_sig)
{
@@ -270,9 +288,13 @@ TALER_wallet_melt_verify (
.purpose.size = htonl (sizeof (melt)),
.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT),
.rc = *rc,
- .h_denom_pub = *h_denom_pub
+ .h_denom_pub = *h_denom_pub,
+ .h_age_commitment = {{{0}}},
};
+ if (NULL != h_age_commitment)
+ melt.h_age_commitment = *h_age_commitment;
+
TALER_amount_hton (&melt.amount_with_fee,
amount_with_fee);
TALER_amount_hton (&melt.melt_fee,