[age restriction] progress 14/n - withdraw and deposit

Age restriction support for
  - withdraw is done and tested
  - deposit is done and tested
  - melt is done, untested
  - reveal started
  - link started

Added functions
 - TALER_age_commitment_hash
 - TALER_age_restriction_commit
 - TALER_age_commitment_derive
 - TALER_age_restriction_commitment_free_inside
 - Hash of age commitment passed around API boundaries

Exchangedb adjustments for denominations
  - all prepared statements re: denominations now handle age_mask
  - signature parameters adjusted

Hash and signature verification of /keys adjusted
  - Hashes of (normal) denominations and age-restricted denominations
    are calculated seperately
  - The hash of the age-restricted ones will then be added to the other
    hash
  - The total hash is signed/verified

Tests for withdraw with age restriction added
  - TALER_EXCHANGE_DenomPublickey now carries age_mask
  - TALER_TESTING_cmd_withdraw_amount* takes age parameter
  - WithdrawState carries age_commitment and its hash
  - withdraw_run derives new age commitment, if applicable
  - Added age parameter to testing (13 as example)
  - TALER_TESTING_find_pk takes boolean age_restricted
  - struct RefreshMeltState carries age commitment of melted coin
  - melt_run calls TALER_age_commitment_derive, if necessary

Various Fixes and changes
  - Fixes of post handler for /management/extensions
  - Fixes for offline tool extensions signing
  - Slight refactoring of extensions
  - Age restriction extension simplified
    - config is now global to extension
    - added global TEH_age_restriction_enabled and TEH_age_mask in
      taler-exchange-httpd
    - helper functions and macros introduced
This commit is contained in:
Özgür Kesim 2022-01-23 18:27:31 +01:00
parent 8684a9bfea
commit 327361adda
Signed by: oec
GPG Key ID: 3D76A56D79EDD9D7
59 changed files with 1882 additions and 365 deletions

@ -1 +1 @@
Subproject commit b0dd85e8187f33a1f92dd5eb31082050d333e168
Subproject commit 94b3d826de2aa442a985d03a52eaf526ef950d83

View File

@ -1227,6 +1227,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,
@ -1289,6 +1290,7 @@ refresh_session_cb (void *cls,
&fee_refresh,
rc,
&h_denom_pub,
h_age_commitment,
coin_pub,
coin_sig))
{
@ -1615,6 +1617,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);
@ -1631,6 +1634,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,

View File

@ -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;

View File

@ -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

View File

@ -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,
@ -3036,6 +3041,7 @@ do_show (char *const *args)
keys = parse_keys_input ("show");
if (NULL == keys)
return;
if (GNUNET_OK !=
load_offline_key (GNUNET_NO))
return;
@ -3196,6 +3202,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.
*
@ -3284,7 +3327,10 @@ sign_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
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)
@ -3518,14 +3564,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)
@ -3779,6 +3817,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);

View File

@ -126,6 +126,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.
*/
@ -736,6 +742,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/*");

View File

@ -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.
*/

View File

@ -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,

View File

@ -239,6 +239,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",
@ -387,6 +390,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,

View File

@ -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;

View File

@ -743,43 +743,30 @@ 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;
enum GNUNET_GenericReturnValue ret;
struct TALER_AgeMask age_mask = TALER_extensions_age_restriction_ageMask ();
if (age_mask.mask == 0)
{
/* Age restriction support is not enabled. Ignore the AGE_RESTRICTED field
* for the particular denomination and simply return the null_mask
*/
return null_mask;
}
if (GNUNET_OK == (GNUNET_CONFIGURATION_have_value (
if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value (
TEH_cfg,
section_name,
"AGE_RESTRICTED")))
{
enum GNUNET_GenericReturnValue ret;
if (GNUNET_SYSERR == (ret = GNUNET_CONFIGURATION_get_value_yesno (TEH_cfg,
return null_mask;
ret = GNUNET_CONFIGURATION_get_value_yesno (TEH_cfg,
section_name,
"AGE_RESTRICTED")))
{
"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;
}
}
return age_mask;
}
@ -1187,6 +1174,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,
@ -1601,7 +1590,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
@ -1719,7 +1708,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 */
@ -1755,7 +1745,7 @@ create_krd (struct TEH_KeyStateHandle *ksh,
json_t *sig;
int r;
r = json_object_set_new (
r = json_object_set (
keys,
"extensions",
extensions);
@ -1771,14 +1761,14 @@ create_krd (struct TEH_KeyStateHandle *ksh,
GNUNET_assert (0 == r);
}
// 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));
@ -1857,7 +1847,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);
@ -1882,19 +1874,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;
@ -1908,6 +1904,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);
@ -1936,14 +1937,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 (
@ -1970,18 +1971,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 (
@ -1990,13 +1999,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,
@ -2010,7 +2033,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);
@ -2667,7 +2690,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;
}
@ -2794,7 +2819,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);

View File

@ -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. */
{
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))
verify_extensions_from_json (extensions, &sec))
{
ret = TALER_MHD_reply_with_error (
GNUNET_JSON_parse_free (top_spec);
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"invalid configuration for extension");
goto CLEANUP;
"invalid object");
}
/* We have a validly signed JSON object for the extension. Increment its
* refcount.
*/
json_incref (sec.extensions[idx].config);
idx++;
} /* json_object_foreach */
}
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++)
{

View File

@ -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,

View File

@ -278,6 +278,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 */
@ -287,6 +288,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 */
@ -299,6 +301,7 @@ check_melt_valid (struct MHD_Connection *connection,
rmc->coin_refresh_fee = dk->meta.fee_refresh;
rmc->coin_value = dk->meta.value;
/* sanity-check that "total melt amount > melt fee" */
if (0 <
TALER_amount_cmp (&rmc->coin_refresh_fee,
@ -328,6 +331,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))
{
@ -403,6 +407,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",

View File

@ -248,7 +248,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,
&c_hash,
&coin_ev,

View File

@ -250,7 +250,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,
&c_hash,
&coin_ev,

View File

@ -191,6 +191,7 @@ check_commitment (struct RevealContext *rctx,
GNUNET_assert (GNUNET_OK ==
TALER_planchet_prepare (rcd->dk,
&ps,
NULL, /* FIXME-Oec, struct TALER_AgeCommitmentHash * */
&c_hash,
&pd));
rcd->coin_ev = pd.coin_ev;
@ -285,6 +286,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
*/
@ -293,6 +295,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);
@ -317,6 +320,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++)
{
@ -443,6 +447,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++)
{
@ -460,6 +465,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 (
@ -467,6 +473,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);
@ -489,6 +496,7 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
rcd->coin_ev_size = rrc->coin_ev_size;
rcd->dk = &dks[i]->denom_pub;
}
rctx->dks = dks;
rctx->rcds = rcds;
if (GNUNET_OK !=
@ -500,6 +508,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++)
{
@ -520,8 +529,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;
@ -577,11 +588,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
*/
@ -591,6 +609,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);
@ -626,6 +645,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++)
{
@ -649,6 +681,7 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection,
rctx,
link_sigs_json,
new_denoms_h_json,
old_age_commitment_json,
coin_evs);
}
@ -662,6 +695,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",
@ -674,6 +708,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 ()
};
@ -735,6 +772,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;

View File

@ -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))))
{

View File

@ -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';
@ -342,6 +342,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
@ -356,6 +357,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);

View File

@ -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),
@ -830,6 +834,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"
@ -848,6 +853,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;",
@ -2388,10 +2395,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"
@ -3094,9 +3102,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));
@ -3170,6 +3181,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
};
@ -3256,12 +3269,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,
@ -3270,6 +3286,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
@ -3355,10 +3378,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",
@ -3385,6 +3408,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
};
@ -3396,6 +3421,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,
@ -5730,11 +5759,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),
@ -5742,24 +5773,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,
@ -5779,21 +5808,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)) )
{
GNUNET_break (GNUNET_is_zero (age_hash)); // FIXME-OEC
GNUNET_break_op (0);
return TALER_EXCHANGEDB_CKS_AGE_CONFLICT;
}
if ( (! is_null) &&
(0 != GNUNET_memcmp (denom_hash,
&coin->denom_pub_hash)) )
if ( (! is_denom_pub_hash_null) &&
(0 != GNUNET_memcmp (&denom_hash->hash,
&coin->denom_pub_hash.hash)) )
{
GNUNET_break_op (0);
return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT;
}
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_AGE_CONFLICT;
}
return TALER_EXCHANGEDB_CKS_PRESENT;
}
@ -6019,6 +6051,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
@ -6035,6 +6068,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",
@ -6050,6 +6087,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;
}
@ -8202,6 +8244,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;
@ -8209,6 +8253,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",
@ -8234,9 +8282,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,
@ -10173,6 +10223,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
};
@ -10216,6 +10268,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
};

View File

@ -459,6 +459,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
@ -471,6 +472,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,
@ -1467,8 +1470,8 @@ run (void *cls)
{
struct TALER_PlanchetDetail pd;
struct TALER_CoinSpendPublicKeyP coin_pub;
struct TALER_AgeHash age_hash;
struct TALER_AgeHash *p_ah[2] = {NULL, &age_hash};
struct TALER_AgeCommitmentHash age_hash;
struct TALER_AgeCommitmentHash *p_ah[2] = {NULL, &age_hash};
/* Call TALER_denom_blind()/TALER_denom_sign_blinded() twice, once without
* age_hash, once with age_hash */
@ -1576,7 +1579,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,
@ -1819,7 +1822,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;
@ -2171,7 +2174,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,

View File

@ -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;
this->config = GNUNET_malloc (sizeof(struct TALER_AgeMask));
GNUNET_memcpy (this->config, &mask, sizeof(struct TALER_AgeMask));
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 = &_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 */

View File

@ -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 */

View File

@ -280,37 +280,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
@ -338,6 +307,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.
@ -635,6 +627,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.
@ -712,9 +744,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,
@ -824,7 +857,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,
struct TALER_CoinPubHash *c_hash,
void **coin_ev,
@ -1024,7 +1057,7 @@ TALER_coin_ev_hash (const void *coin_ev,
*/
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);
@ -1110,8 +1143,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;
};
@ -1232,6 +1266,7 @@ TALER_planchet_setup_random (struct TALER_PlanchetSecretsP *ps);
*
* @param dk denomination key for the coin to be created
* @param ps secret planchet internals (for #TALER_planchet_to_coin)
* @param ach (optional) 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
@ -1240,6 +1275,7 @@ TALER_planchet_setup_random (struct TALER_PlanchetSecretsP *ps);
enum GNUNET_GenericReturnValue
TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk,
const struct TALER_PlanchetSecretsP *ps,
const struct TALER_AgeCommitmentHash *ach,
struct TALER_CoinPubHash *c_hash,
struct TALER_PlanchetDetail *pd);
@ -1251,6 +1287,7 @@ TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk,
* @param dk denomination key, must match what was given to #TALER_planchet_prepare()
* @param blind_sig blind signature from the exchange
* @param ps secrets from #TALER_planchet_prepare()
* @param ach (optional) 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[out] coin set to the details of the fresh coin
* @return #GNUNET_OK on success
@ -1260,6 +1297,7 @@ TALER_planchet_to_coin (
const struct TALER_DenominationPublicKey *dk,
const struct TALER_BlindedDenominationSignature *blind_sig,
const struct TALER_PlanchetSecretsP *ps,
const struct TALER_AgeCommitmentHash *ach,
const struct TALER_CoinPubHash *c_hash,
struct TALER_FreshCoin *coin);
@ -1682,6 +1720,7 @@ TALER_exchange_deposit_confirm_verify (
* @param deposit_fee the deposit fee we expect to pay
* @param h_wire hash of the merchants 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 coins private key
@ -1696,6 +1735,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,
@ -1712,6 +1752,7 @@ TALER_wallet_deposit_sign (
* @param deposit_fee the deposit fee we expect to pay
* @param h_wire hash of the merchants 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
@ -1727,6 +1768,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,
@ -1763,6 +1805,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 coins public key
* @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_MELT
* @return #GNUNET_OK if the signature is valid
@ -1773,6 +1816,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);
@ -1803,6 +1847,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
*/
@ -1812,6 +1857,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);
@ -2557,5 +2603,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

View File

@ -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 coins 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_WireSalt *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,
@ -1359,6 +1357,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 (optional) 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
@ -1371,6 +1370,7 @@ TALER_EXCHANGE_withdraw (
const struct TALER_EXCHANGE_DenomPublicKey *pk,
const struct TALER_ReservePrivateKeyP *reserve_priv,
const struct TALER_PlanchetSecretsP *ps,
const struct TALER_AgeCommitmentHash *ach,
TALER_EXCHANGE_WithdrawCallback res_cb,
void *res_cb_cls);
@ -1479,6 +1479,8 @@ TALER_EXCHANGE_withdraw2_cancel (struct TALER_EXCHANGE_Withdraw2Handle *wh);
* @param melt_pk denomination key information
* record corresponding to the @a melt_sig
* validity of the keys
* @param age_commitment (optional) age commitment that went into the original
* coin. Maybe NULL, if no age commitment was provided.
* @param fresh_pks_len length of the @a pks array
* @param fresh_pks array of @a pks_len denominations of fresh coins to create
* @return NULL
@ -1493,6 +1495,7 @@ TALER_EXCHANGE_refresh_prepare (
const struct TALER_Amount *melt_amount,
const struct TALER_DenominationSignature *melt_sig,
const struct TALER_EXCHANGE_DenomPublicKey *melt_pk,
const struct TALER_AgeCommitment *age_commitment,
unsigned int fresh_pks_len,
const struct TALER_EXCHANGE_DenomPublicKey *fresh_pks);

View File

@ -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;
@ -643,7 +649,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;
};
@ -1260,6 +1266,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.
*/
@ -1305,6 +1318,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
@ -1585,6 +1605,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
@ -1597,6 +1618,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,
@ -2735,7 +2757,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);
/**

View File

@ -85,6 +85,31 @@ enum GNUNET_GenericReturnValue
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
*/
@ -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

View File

@ -552,17 +552,6 @@ enum GNUNET_GenericReturnValue
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);
#endif /* TALER_JSON_LIB_H_ */
/* End of taler_json_lib.h */

View File

@ -414,6 +414,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.
*/
@ -478,6 +483,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.
*/
@ -711,6 +722,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

View File

@ -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);
/**
@ -1277,6 +1279,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.
@ -1285,6 +1288,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);
@ -1297,6 +1301,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
@ -1308,6 +1313,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
@ -2482,6 +2501,8 @@ 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 (age_commitment, struct TALER_AgeCommitment) \
op (h_age_commitment, struct TALER_AgeCommitmentHash) \
op (coin_priv, const struct TALER_CoinSpendPrivateKeyP) \
op (coin_pub, const struct TALER_CoinSpendPublicKeyP) \
op (absolute_time, const struct GNUNET_TIME_Absolute) \

View File

@ -659,36 +659,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 */

View File

@ -482,6 +482,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),
@ -516,6 +517,7 @@ TALER_EXCHANGE_verify_coin_history (
&fee,
&h_wire,
&h_contract_terms,
hac,
NULL /* h_extensions! */,
h_denom_pub,
wallet_timestamp,
@ -548,6 +550,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),
@ -555,6 +558,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 ()
@ -568,6 +574,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! */
@ -582,17 +589,26 @@ TALER_EXCHANGE_verify_coin_history (
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;
}
else if (0 == strcasecmp (type,

View File

@ -462,6 +462,7 @@ handle_deposit_finished (void *cls,
* @param h_wire hash of the merchants 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 coin_pub coins public key
* @param h_age_commitment coins hash of age commitment, might be NULL
* @param denom_sig exchanges 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
@ -478,6 +479,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,
@ -491,6 +493,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,
@ -514,8 +517,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,
@ -547,6 +554,7 @@ TALER_EXCHANGE_deposit (
const char *merchant_payto_uri,
const struct TALER_WireSalt *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,
@ -599,11 +607,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)
@ -612,6 +623,7 @@ TALER_EXCHANGE_deposit (
GNUNET_break_op (0);
return NULL;
}
if (0 >
TALER_amount_subtract (&amount_without_fee,
amount,
@ -621,17 +633,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,
@ -654,6 +667,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",

View File

@ -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;

View File

@ -105,6 +105,7 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
};
struct TALER_TransferSecretP secret;
struct TALER_PlanchetSecretsP fc;
struct TALER_AgeCommitmentHash h_age_commitment = {0}; // TODO, see below.
/* parse reply */
if (GNUNET_OK !=
@ -145,6 +146,7 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
if (GNUNET_OK !=
TALER_planchet_prepare (&rpub,
&fc,
NULL, /* FIXME-oec. struct TALER_AgeCommitmentHash * */
&c_hash,
&pd))
{
@ -156,11 +158,21 @@ parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
pd.coin_ev_size,
&coin_envelope_hash.hash);
/*
* 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?
*/
if (GNUNET_OK !=
TALER_wallet_link_verify (&pd.denom_pub_hash,
trans_pub,
&coin_envelope_hash,
&old_coin_pub,
&h_age_commitment,
&link_sig))
{
GNUNET_break_op (0);

View File

@ -32,7 +32,7 @@
/**
* Set to 1 for extra debug logging.
*/
#define DEBUG 0
#define DEBUG 1 /* FIXME-oec */
/**

View File

@ -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,

View File

@ -79,6 +79,8 @@ serialize_melted_coin (const struct MeltedCoin *mc)
return GNUNET_JSON_PACK (
GNUNET_JSON_pack_data_auto ("coin_priv",
&mc->coin_priv),
GNUNET_JSON_pack_data_auto ("h_age_commitment",
&mc->h_age_commitment),
TALER_JSON_pack_denom_sig ("denom_sig",
&mc->sig),
TALER_JSON_pack_denom_pub ("denom_pub",
@ -113,6 +115,9 @@ deserialize_melted_coin (struct MeltedCoin *mc,
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("coin_priv",
&mc->coin_priv),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
&mc->h_age_commitment)),
TALER_JSON_spec_denom_sig ("denom_sig",
&mc->sig),
TALER_JSON_spec_denom_pub ("denom_pub",
@ -343,12 +348,14 @@ TALER_EXCHANGE_refresh_prepare (
const struct TALER_Amount *melt_amount,
const struct TALER_DenominationSignature *melt_sig,
const struct TALER_EXCHANGE_DenomPublicKey *melt_pk,
const struct TALER_AgeCommitment *age_commitment,
unsigned int fresh_pks_len,
const struct TALER_EXCHANGE_DenomPublicKey *fresh_pks)
{
struct MeltData md;
json_t *ret;
struct TALER_Amount total;
struct TALER_AgeCommitmentHash ach = {0};
struct TALER_CoinSpendPublicKeyP coin_pub;
struct TALER_TransferSecretP trans_sec[TALER_CNC_KAPPA];
struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA];
@ -366,6 +373,10 @@ TALER_EXCHANGE_refresh_prepare (
md.melted_coin.original_value = melt_pk->value;
md.melted_coin.expire_deposit
= melt_pk->expire_deposit;
md.melted_coin.h_age_commitment = ach;
TALER_age_commitment_hash (age_commitment,
&md.melted_coin.h_age_commitment);
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (melt_amount->currency,
&total));
@ -375,6 +386,7 @@ TALER_EXCHANGE_refresh_prepare (
melt_sig);
md.fresh_pks = GNUNET_new_array (fresh_pks_len,
struct TALER_DenominationPublicKey);
for (unsigned int i = 0; i<fresh_pks_len; i++)
{
TALER_denom_pub_deep_copy (&md.fresh_pks[i],
@ -393,6 +405,7 @@ TALER_EXCHANGE_refresh_prepare (
return NULL;
}
}
/* verify that melt_amount is above total cost */
if (1 ==
TALER_amount_cmp (&total,
@ -416,23 +429,62 @@ TALER_EXCHANGE_refresh_prepare (
TALER_link_derive_transfer_secret (melt_priv,
&md.melted_coin.transfer_priv[i],
&trans_sec[i]);
md.fresh_coins[i] = GNUNET_new_array (fresh_pks_len,
struct TALER_PlanchetSecretsP);
rce[i].new_coins = GNUNET_new_array (fresh_pks_len,
struct TALER_RefreshCoinData);
md.fresh_ach[i] = GNUNET_new_array (fresh_pks_len,
struct TALER_AgeCommitmentHash);
for (unsigned int j = 0; j<fresh_pks_len; j++)
{
struct TALER_PlanchetSecretsP *fc = &md.fresh_coins[i][j];
struct TALER_RefreshCoinData *rcd = &rce[i].new_coins[j];
struct TALER_AgeCommitmentHash *ach = &md.fresh_ach[i][j];
struct TALER_PlanchetDetail pd;
struct TALER_CoinPubHash c_hash;
/* Handle age commitment, if present */
if (NULL == age_commitment)
{
memset (ach, 0, sizeof(struct TALER_AgeCommitmentHash));
ach = NULL;
}
else
{
struct TALER_AgeCommitment new_ac;
uint32_t seed;
/* we use the first 4 bytes of the trans_sec to generate a new age
* commitment */
seed = *(uint32_t *) trans_sec[i].key.bits;
if (GNUNET_OK != TALER_age_commitment_derive (
age_commitment,
seed,
&new_ac))
{
GNUNET_break_op (0);
TALER_EXCHANGE_free_melt_data_ (&md);
return NULL;
}
TALER_age_commitment_hash (
&new_ac,
&md.fresh_ach[i][j]);
}
TALER_planchet_setup_refresh (&trans_sec[i],
j,
fc);
if (GNUNET_OK !=
TALER_planchet_prepare (&md.fresh_pks[j],
fc,
ach,
&c_hash,
&pd))
{
@ -453,6 +505,7 @@ TALER_EXCHANGE_refresh_prepare (
rce,
&coin_pub,
melt_amount);
/* finally, serialize everything */
ret = serialize_melt_data (&md);
for (unsigned int i = 0; i < TALER_CNC_KAPPA; i++)
@ -461,6 +514,7 @@ TALER_EXCHANGE_refresh_prepare (
GNUNET_free (rce[i].new_coins[j].coin_ev);
GNUNET_free (rce[i].new_coins);
}
TALER_EXCHANGE_free_melt_data_ (&md);
return ret;
}

View File

@ -52,6 +52,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;
/**
* Transfer private keys for each cut-and-choose dimension.
*/
@ -107,6 +113,14 @@ struct MeltData
* coins to be created, for each cut-and-choose dimension.
*/
struct TALER_PlanchetSecretsP *fresh_coins[TALER_CNC_KAPPA];
/**
* Arrays of @e num_fresh_coins with information about the hashes of age
* commitments coins to be created, 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_AgeCommitmentHash *fresh_ach[TALER_CNC_KAPPA];
};

View File

@ -136,6 +136,7 @@ refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,
{
const struct TALER_PlanchetSecretsP *fc;
struct TALER_DenominationPublicKey *pk;
struct TALER_AgeCommitmentHash *ach = NULL;
json_t *jsonai;
struct TALER_BlindedDenominationSignature blind_sig;
struct TALER_CoinSpendPublicKeyP coin_pub;
@ -152,6 +153,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))
{
ach = &rrh->md->fresh_ach[rrh->noreveal_index][i];
}
if (GNUNET_OK !=
GNUNET_JSON_parse (jsonai,
spec,
@ -166,14 +173,14 @@ refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,
hence recomputing it here... */
GNUNET_CRYPTO_eddsa_key_get_public (&fc->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,
fc,
ach,
&coin_hash,
&coin))
{
@ -359,6 +366,7 @@ TALER_EXCHANGE_refreshes_reveal (
if (GNUNET_OK !=
TALER_planchet_prepare (&md->fresh_pks[i],
&md->fresh_coins[noreveal_index][i],
NULL, /* FIXME-oec */
&c_hash,
&pd))
{

View File

@ -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,

View File

@ -63,6 +63,11 @@ struct TALER_EXCHANGE_WithdrawHandle
*/
struct TALER_PlanchetSecretsP ps;
/**
* Hash of the age commitment for this coin, if applicable. Maybe NULL
*/
const struct TALER_AgeCommitmentHash *ach;
/**
* Denomination key we are withdrawing.
*/
@ -106,6 +111,7 @@ handle_reserve_withdraw_finished (
TALER_planchet_to_coin (&wh->pk.key,
blind_sig,
&wh->ps,
wh->ach,
&wh->c_hash,
&fc))
{
@ -159,6 +165,7 @@ handle_reserve_withdraw_finished (
* @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 (optional) 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 the above callback
* @return handle for the operation on success, NULL on error, i.e.
@ -171,11 +178,13 @@ TALER_EXCHANGE_withdraw (
const struct TALER_EXCHANGE_DenomPublicKey *pk,
const struct TALER_ReservePrivateKeyP *reserve_priv,
const struct TALER_PlanchetSecretsP *ps,
const struct TALER_AgeCommitmentHash *ach,
TALER_EXCHANGE_WithdrawCallback res_cb,
void *res_cb_cls)
{
struct TALER_PlanchetDetail pd;
struct TALER_EXCHANGE_WithdrawHandle *wh;
bool age_restricted = (0 != pk->key.age_mask.mask);
wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle);
wh->exchange = exchange;
@ -183,9 +192,14 @@ TALER_EXCHANGE_withdraw (
wh->cb_cls = res_cb_cls;
wh->pk = *pk;
wh->ps = *ps;
wh->ach = ach;
GNUNET_assert (age_restricted == (NULL != ach));
if (GNUNET_OK !=
TALER_planchet_prepare (&pk->key,
ps,
ach,
&wh->c_hash,
&pd))
{

View File

@ -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 \
@ -208,6 +209,7 @@ test_exchange_api_LDADD = \
-lgnunetcurl \
-lgnunetutil \
-ljansson \
-ltalerextensions \
$(XLIB)
test_exchange_management_api_SOURCES = \

View File

@ -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",
@ -708,7 +726,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
@ -718,11 +736,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;
}

View File

@ -33,6 +33,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
@ -138,6 +139,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
@ -146,6 +148,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),
/**
@ -161,6 +164,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 ()
};
@ -254,6 +258,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
@ -330,6 +335,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.02"),
TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-age",
"EUR:5.02",
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:5",
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
@ -372,6 +432,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.98",
bc.exchange_payto,
bc.user42_payto),
TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c1",
ec.exchange_url,
"EUR:0.98",
@ -426,6 +491,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",
@ -464,6 +530,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",
@ -512,6 +579,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
@ -612,6 +680,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",
@ -661,11 +730,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",
@ -756,6 +827,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"
@ -764,6 +836,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",
@ -796,6 +869,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,
@ -820,11 +894,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",
@ -885,6 +961,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"),
@ -931,6 +1008,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,
@ -951,6 +1030,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",
@ -986,6 +1069,9 @@ main (int argc,
GNUNET_log_setup ("test-exchange-api",
"INFO",
NULL);
TALER_extensions_init ();
/* Check fakebank port is available and get config */
if (GNUNET_OK !=
TALER_TESTING_prepare_fakebank (CONFIG_FILE,
@ -1004,7 +1090,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
@ -1014,11 +1100,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;
}

View File

@ -150,7 +150,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
[coin_eur_ct_10_age_restricted]
value = EUR:0.10
@ -162,7 +162,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
[coin_eur_1_age_restricted]
value = EUR:1
@ -174,7 +174,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
[coin_eur_5_age_restricted]
value = EUR:5
@ -186,7 +186,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
[coin_eur_10_age_restricted]
value = EUR:10
@ -198,4 +198,4 @@ fee_deposit = EUR:0.01
fee_refresh = EUR:0.03
fee_refund = EUR:0.01
rsa_keysize = 1024
age_restricted = true
age_restricted = YES

View File

@ -95,11 +95,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) *///

View File

@ -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 ()
};

View File

@ -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;
@ -382,6 +384,10 @@ deposit_run (void *cls,
TALER_TESTING_get_trait_coin_priv (coin_cmd,
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,
@ -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),

View File

@ -238,7 +238,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,

View File

@ -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 */

View File

@ -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).
*/
@ -121,6 +126,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.
*/
@ -992,6 +1002,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
@ -1012,6 +1023,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,
@ -1021,6 +1042,7 @@ melt_run (void *cls,
TALER_TESTING_interpreter_fail (rms->is);
return;
}
if (GNUNET_OK !=
TALER_TESTING_get_trait_denom_pub (coin_command,
0,
@ -1030,9 +1052,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;
@ -1048,8 +1072,9 @@ melt_run (void *cls,
TALER_TESTING_interpreter_fail (rms->is);
return;
}
fresh_pk = TALER_TESTING_find_pk
(TALER_EXCHANGE_get_keys (is->exchange), &fresh_amount);
fresh_pk = TALER_TESTING_find_pk (TALER_EXCHANGE_get_keys (is->exchange),
&fresh_amount,
age_restricted);
if (NULL == fresh_pk)
{
GNUNET_break (0);
@ -1071,13 +1096,33 @@ melt_run (void *cls,
&fresh_pk->key);
} /* end for */
GNUNET_assert (NULL == rms->refresh_data);
{
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
= TALER_EXCHANGE_refresh_prepare (rms->melt_priv,
&melt_amount,
melt_sig,
melt_denom_pub,
ac,
num_fresh_coins,
rms->fresh_pks);
}
if (NULL == rms->refresh_data)
{
GNUNET_break (0);
@ -1168,6 +1213,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_trait_end ()
};
@ -1326,6 +1373,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),

View File

@ -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"
@ -115,6 +116,18 @@ struct WithdrawState
*/
struct TALER_PlanchetSecretsP 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.
@ -363,12 +376,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))
@ -377,6 +392,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));
@ -386,6 +402,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_setup_random (&ws->ps);
@ -412,10 +429,12 @@ withdraw_run (void *cls,
TALER_planchet_setup_random (&ws->ps);
ws->ps.coin_priv = *coin_priv;
}
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,
@ -433,18 +452,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,
ws->h_age_commitment,
&reserve_withdraw_cb,
ws);
}
if (NULL == ws->wsh)
{
GNUNET_break (0);
@ -486,6 +511,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);
@ -512,13 +547,13 @@ withdraw_traits (void *cls,
struct TALER_TESTING_Trait traits[] = {
/* history entry MUST be first due to response code logic below! */
TALER_TESTING_make_trait_reserve_history (&ws->reserve_history),
TALER_TESTING_make_trait_coin_priv (0 /* only one coin */,
TALER_TESTING_make_trait_coin_priv (index /* only one coin */,
&ws->ps.coin_priv),
TALER_TESTING_make_trait_blinding_key (0 /* only one coin */,
TALER_TESTING_make_trait_blinding_key (index /* only one coin */,
&ws->ps.blinding_key),
TALER_TESTING_make_trait_denom_pub (0 /* only one coin */,
TALER_TESTING_make_trait_denom_pub (index /* 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),
@ -528,6 +563,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 ()
};
@ -547,6 +584,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.
@ -555,11 +593,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,
@ -595,6 +667,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
@ -606,6 +679,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)
{
@ -614,6 +688,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;

View File

@ -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",
@ -416,11 +420,13 @@ TALER_TESTING_prepare_exchange (const char *config_filename,
*
* @param keys array of keys to search
* @param amount coin value to look for
* @param age_restricted must denomination support age restriction?
* @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)
{
struct GNUNET_TIME_Timestamp now;
struct TALER_EXCHANGE_DenomPublicKey *pk;
@ -437,7 +443,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
@ -453,7 +460,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,

View File

@ -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,
@ -178,6 +182,7 @@ TALER_planchet_setup_random (struct TALER_PlanchetSecretsP *ps)
enum GNUNET_GenericReturnValue
TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk,
const struct TALER_PlanchetSecretsP *ps,
const struct TALER_AgeCommitmentHash *ach,
struct TALER_CoinPubHash *c_hash,
struct TALER_PlanchetDetail *pd)
{
@ -188,7 +193,7 @@ TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk,
if (GNUNET_OK !=
TALER_denom_blind (dk,
&ps->blinding_key,
NULL, /* FIXME-Oec */
ach,
&coin_pub,
c_hash,
&pd->coin_ev,
@ -208,6 +213,7 @@ TALER_planchet_to_coin (
const struct TALER_DenominationPublicKey *dk,
const struct TALER_BlindedDenominationSignature *blind_sig,
const struct TALER_PlanchetSecretsP *ps,
const struct TALER_AgeCommitmentHash *ach,
const struct TALER_CoinPubHash *c_hash,
struct TALER_FreshCoin *coin)
{
@ -233,6 +239,7 @@ TALER_planchet_to_coin (
}
coin->sig = sig;
coin->coin_priv = ps->coin_priv;
coin->h_age_commitment = ach;
return GNUNET_OK;
}
@ -319,10 +326,10 @@ TALER_coin_ev_hash (const void *coin_ev,
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,
@ -334,7 +341,7 @@ 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],
@ -342,7 +349,7 @@ TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub,
key_s);
GNUNET_memcpy (&data[key_s],
age_commitment_hash,
ach,
age_s);
GNUNET_CRYPTO_hash (&data,
@ -352,4 +359,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 */

View File

@ -235,7 +235,7 @@ TALER_denom_priv_to_pub (const struct TALER_DenominationPrivateKey *denom_priv,
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,
struct TALER_CoinPubHash *c_hash,
void **coin_ev,
@ -245,7 +245,7 @@ TALER_denom_blind (const struct TALER_DenominationPublicKey *dk,
{
case TALER_DENOMINATION_RSA:
TALER_coin_pub_hash (coin_pub,
age_commitment_hash,
ach,
c_hash);
if (GNUNET_YES !=
GNUNET_CRYPTO_rsa_blind (&c_hash->hash,

View File

@ -101,6 +101,7 @@ test_planchets (void)
GNUNET_assert (GNUNET_OK ==
TALER_planchet_prepare (&dk_pub,
&ps,
NULL, /* no age commitment */
&c_hash,
&pd));
GNUNET_assert (GNUNET_OK ==
@ -112,6 +113,62 @@ test_planchets (void)
TALER_planchet_to_coin (&dk_pub,
&blind_sig,
&ps,
NULL, /* no age commitment */
&c_hash,
&coin));
TALER_blinded_denom_sig_free (&blind_sig);
TALER_denom_sig_free (&coin.sig);
TALER_denom_priv_free (&dk_priv);
TALER_denom_pub_free (&dk_pub);
return 0;
}
/**
* Test the basic planchet functionality of creating a fresh planchet and
* extracting the respective signature, this time _with_ age commitment.
*
* @return 0 on success
*/
static int
test_planchets_with_age_commitment (void)
{
struct TALER_PlanchetSecretsP ps;
struct TALER_AgeCommitmentHash ach;
struct TALER_DenominationPrivateKey dk_priv;
struct TALER_DenominationPublicKey dk_pub;
struct TALER_PlanchetDetail pd;
struct TALER_BlindedDenominationSignature blind_sig;
struct TALER_FreshCoin coin;
struct TALER_CoinPubHash c_hash;
GNUNET_assert (GNUNET_OK ==
TALER_denom_priv_create (&dk_priv,
&dk_pub,
TALER_DENOMINATION_RSA,
1024));
TALER_planchet_setup_random (&ps);
GNUNET_CRYPTO_random_block (
GNUNET_CRYPTO_QUALITY_WEAK,
&ach,
sizeof(struct TALER_AgeCommitmentHash));
GNUNET_assert (GNUNET_OK ==
TALER_planchet_prepare (&dk_pub,
&ps,
&ach,
&c_hash,
&pd));
GNUNET_assert (GNUNET_OK ==
TALER_denom_sign_blinded (&blind_sig,
&dk_priv,
pd.coin_ev,
pd.coin_ev_size));
GNUNET_assert (GNUNET_OK ==
TALER_planchet_to_coin (&dk_pub,
&blind_sig,
&ps,
&ach,
&c_hash,
&coin));
TALER_blinded_denom_sig_free (&blind_sig);
@ -221,10 +278,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;
}

View File

@ -268,9 +268,13 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh)
enum TALER_ErrorCode ec;
bool success = false;
struct TALER_PlanchetSecretsP ps;
struct TALER_AgeCommitmentHash ach;
struct TALER_CoinPubHash c_hash;
TALER_planchet_setup_random (&ps);
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
&ach,
sizeof(ach));
for (unsigned int i = 0; i<MAX_KEYS; i++)
{
if (! keys[i].valid)
@ -281,6 +285,7 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh)
GNUNET_assert (GNUNET_YES ==
TALER_planchet_prepare (&keys[i].denom_pub,
&ps,
&ach,
&c_hash,
&pd));
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@ -418,8 +423,12 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,
enum TALER_ErrorCode ec;
struct GNUNET_TIME_Relative duration;
struct TALER_PlanchetSecretsP ps;
struct TALER_AgeCommitmentHash ach;
TALER_planchet_setup_random (&ps);
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;)
@ -445,6 +454,7 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh,
GNUNET_assert (GNUNET_YES ==
TALER_planchet_prepare (&keys[i].denom_pub,
&ps,
&ach,
&c_hash,
&pd));
/* use this key as long as it works */

View File

@ -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,21 @@ 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;
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"VFVFVFVF got NON-NULL h_age_commitment\n");
}
if (NULL != h_extensions)
dr.h_extensions = *h_extensions;
TALER_amount_hton (&dr.amount_with_fee,
amount);
TALER_amount_hton (&dr.deposit_fee,
@ -135,6 +151,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 = {
@ -142,9 +159,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,
@ -267,6 +288,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)
{
@ -274,9 +296,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,