diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 6787b00d5..02dbcae5e 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -5764,6 +5764,8 @@ postgres_ensure_coin_known (void *cls, 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), @@ -5771,20 +5773,19 @@ 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 }; @@ -5808,7 +5809,16 @@ postgres_ensure_coin_known (void *cls, return TALER_EXCHANGEDB_CKS_ADDED; break; /* continued below */ } - if ( (! is_null) && + + 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)) ) { @@ -5816,13 +5826,7 @@ postgres_ensure_coin_known (void *cls, GNUNET_break_op (0); return TALER_EXCHANGEDB_CKS_AGE_CONFLICT; } - if ( (! is_null) && - (0 != GNUNET_memcmp (denom_hash, - &coin->denom_pub_hash)) ) - { - GNUNET_break_op (0); - return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT; - } + return TALER_EXCHANGEDB_CKS_PRESENT; } diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 661918ce1..ce4cd0214 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -780,6 +780,7 @@ TALER_EXCHANGE_wire_cancel (struct TALER_EXCHANGE_WireHandle *wh); * @param h_extensions hash over the extensions * @param h_denom_pub hash of the coin denomination's public key * @param coin_priv coin’s private key + * @param age_commitment age commitment that went into the making of the coin, might be NULL * @param wallet_timestamp timestamp when the contract was finalized, must not be too far in the future * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) * @param refund_deadline date until which the merchant can issue a refund to the customer via the exchange (can be zero if refunds are not allowed); must not be after the @a wire_deadline @@ -794,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, @@ -899,6 +901,7 @@ typedef void * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) * @param extension_details extension-specific details about the deposit relevant to the exchange * @param coin_pub coin’s public key + * @param age_commitment age commitment that went into the making of the coin, might be NULL * @param denom_pub denomination key with which the coin is signed * @param denom_sig exchange’s unblinded signature of the coin * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the exchange @@ -921,6 +924,7 @@ TALER_EXCHANGE_deposit ( const struct TALER_PrivateContractHash *h_contract_terms, const json_t *extension_details, const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_AgeCommitment *age_commitment, const struct TALER_DenominationSignature *denom_sig, const struct TALER_DenominationPublicKey *denom_pub, struct GNUNET_TIME_Timestamp timestamp, diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h index 4d8b8a407..f40232e2f 100644 --- a/src/include/taler_testing_lib.h +++ b/src/include/taler_testing_lib.h @@ -2461,8 +2461,8 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits, */ #define TALER_TESTING_SIMPLE_TRAITS(op) \ op (bank_row, const uint64_t) \ - op (reserve_priv, const struct TALER_ReservePrivateKeyP) \ - op (reserve_pub, const struct TALER_ReservePublicKeyP) \ + op (reserve_priv, const struct TALER_ReservePrivateKeyP) \ + op (reserve_pub, const struct TALER_ReservePublicKeyP) \ op (merchant_priv, const struct TALER_MerchantPrivateKeyP) \ op (merchant_pub, const struct TALER_MerchantPublicKeyP) \ op (merchant_sig, const struct TALER_MerchantSignatureP) \ @@ -2475,8 +2475,8 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits, op (exchange_bank_account_url, const char *) \ op (taler_uri, const char *) \ op (payto_uri, const char *) \ - op (kyc_url, const char *) \ - op (web_url, const char *) \ + op (kyc_url, const char *) \ + op (web_url, const char *) \ op (row, const uint64_t) \ op (payment_target_uuid, const uint64_t) \ op (array_length, const unsigned int) \ @@ -2501,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) \ diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c index de67bc5f2..f56531917 100644 --- a/src/lib/exchange_api_deposit.c +++ b/src/lib/exchange_api_deposit.c @@ -462,6 +462,7 @@ handle_deposit_finished (void *cls, * @param h_wire hash of the merchant’s account details * @param h_contract_terms hash of the contact of the merchant with the customer (further details are never disclosed to the exchange) * @param coin_pub coin’s public key + * @param h_age_commitment coin’s hash of age commitment, might be NULL * @param denom_sig exchange’s unblinded signature of the coin * @param denom_pub denomination key with which the coin is signed * @param denom_pub_hash hash of @a denom_pub @@ -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 *ach, const struct TALER_DenominationSignature *denom_sig, const struct TALER_DenominationPublicKey *denom_pub, const struct TALER_DenominationHash *denom_pub_hash, @@ -514,8 +516,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 != ach) + { + coin_info.age_commitment_hash = *ach; + } if (GNUNET_YES != TALER_test_coin_valid (&coin_info, @@ -549,6 +555,7 @@ TALER_EXCHANGE_deposit ( const struct TALER_PrivateContractHash *h_contract_terms, const json_t *extension_details, const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_AgeCommitment *age_commitment, const struct TALER_DenominationSignature *denom_sig, const struct TALER_DenominationPublicKey *denom_pub, struct GNUNET_TIME_Timestamp timestamp, @@ -569,6 +576,7 @@ TALER_EXCHANGE_deposit ( struct TALER_DenominationHash denom_pub_hash; struct TALER_Amount amount_without_fee; struct TALER_ExtensionContractHash ech; + struct TALER_AgeCommitmentHash ach; char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32]; if (NULL != extension_details) @@ -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,8 +633,13 @@ TALER_EXCHANGE_deposit ( GNUNET_break_op (0); return NULL; } + TALER_denom_pub_hash (denom_pub, &denom_pub_hash); + + if (NULL != age_commitment) + TALER_age_commitment_hash (age_commitment, &ach); + if (GNUNET_OK != verify_signatures (dki, amount, @@ -632,6 +649,7 @@ TALER_EXCHANGE_deposit ( ? &ech : NULL, coin_pub, + (NULL != age_commitment) ? &ach : NULL, denom_sig, denom_pub, &denom_pub_hash, diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c index e5ff061f6..a42afb9ff 100644 --- a/src/testing/test_exchange_api.c +++ b/src/testing/test_exchange_api.c @@ -169,42 +169,6 @@ 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:6.02"), - TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-age", - "EUR:6.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", - "create-reserve-age", - "EUR:5", - 13, - MHD_HTTP_OK), - TALER_TESTING_cmd_end () - }; - struct TALER_TESTING_Command spend[] = { /** * Spend the coin. @@ -371,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:6.02"), + TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-age", + "EUR:6.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 @@ -1002,12 +1021,14 @@ run (void *cls, wire), TALER_TESTING_cmd_batch ("withdraw", withdraw), - TALER_TESTING_cmd_batch ("withdraw-age", - withdraw_age), TALER_TESTING_cmd_batch ("spend", 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", diff --git a/src/testing/testing_api_cmd_deposit.c b/src/testing/testing_api_cmd_deposit.c index a0eb35f19..6a609100d 100644 --- a/src/testing/testing_api_cmd_deposit.c +++ b/src/testing/testing_api_cmd_deposit.c @@ -287,6 +287,7 @@ 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 *pac = NULL; const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; const struct TALER_DenominationSignature *denom_pub_sig; struct TALER_CoinSpendSignatureP coin_sig; @@ -382,6 +383,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, + &pac)) || (GNUNET_OK != TALER_TESTING_get_trait_denom_pub (coin_cmd, ds->coin_index, @@ -447,6 +452,7 @@ deposit_run (void *cls, &h_contract_terms, NULL, /* FIXME: extension object */ &coin_pub, + pac, denom_pub_sig, &denom_pub->key, ds->wallet_timestamp, @@ -520,6 +526,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 +547,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 +566,8 @@ deposit_traits (void *cls, /* These traits are always available */ TALER_TESTING_make_trait_coin_priv (0, coin_spent_priv), + TALER_TESTING_make_trait_age_commitment (0, + age_commitment), TALER_TESTING_make_trait_wire_details (ds->wire_details), TALER_TESTING_make_trait_contract_terms (ds->contract_terms), TALER_TESTING_make_trait_merchant_priv (&ds->merchant_priv), diff --git a/src/testing/testing_api_cmd_refresh.c b/src/testing/testing_api_cmd_refresh.c index 76294f8e8..db2537a30 100644 --- a/src/testing/testing_api_cmd_refresh.c +++ b/src/testing/testing_api_cmd_refresh.c @@ -70,6 +70,11 @@ struct TALER_TESTING_FreshCoinData */ struct TALER_CoinSpendPrivateKeyP coin_priv; + /* + * Age commitment for the coin, NULL if not applicable. + */ + struct TALER_AgeCommitment *age_commitment; + /** * The blinding key (needed for recoup operations). */ @@ -124,7 +129,7 @@ struct RefreshMeltState /* * Age commitment for the coin, NULL if not applicable. */ - const struct TALER_AgeCommitment *age_commitment; + struct TALER_AgeCommitment *age_commitment; /** * Task scheduled to try later. @@ -1018,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, @@ -1198,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 () }; @@ -1356,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), diff --git a/src/testing/testing_api_cmd_withdraw.c b/src/testing/testing_api_cmd_withdraw.c index bd65f4f27..7389e0eac 100644 --- a/src/testing/testing_api_cmd_withdraw.c +++ b/src/testing/testing_api_cmd_withdraw.c @@ -27,6 +27,7 @@ #include #include #include "taler_signatures.h" +#include "taler_extensions.h" #include "taler_testing_lib.h" #include "backoff.h" @@ -452,31 +453,6 @@ withdraw_run (void *cls, ws->amount = ws->pk->value; } - if (ws->age > 0) - { - uint32_t seed; - struct TALER_AgeCommitment *ac; - - ac = GNUNET_malloc (sizeof(struct TALER_AgeCommitment)); - seed = GNUNET_CRYPTO_random_u32 ( - GNUNET_CRYPTO_QUALITY_WEAK, - UINT32_MAX); - - GNUNET_assert (GNUNET_OK == - TALER_age_restriction_commit ( - &ws->pk->key.age_mask, - ws->age, - seed, - ac)); - - ws->age_commitment = ac; - ws->h_age_commitment = GNUNET_malloc (sizeof(struct - TALER_AgeCommitmentHash)); - TALER_age_commitment_hash ( - ac, - ws->h_age_commitment); - } - ws->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL; GNUNET_assert (0 <= TALER_amount_add (&ws->reserve_history.amount, @@ -535,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); @@ -561,13 +547,15 @@ 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_age_commitment (index, ws->age_commitment), + TALER_TESTING_make_trait_h_age_commitment (index, ws->h_age_commitment), + 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), @@ -611,7 +599,39 @@ TALER_TESTING_cmd_withdraw_amount (const char *label, struct WithdrawState *ws; ws = GNUNET_new (struct WithdrawState); + ws->age = age; + if (age > 0) + { + 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.mask = TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK; + + 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,