diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_age-withdraw_reveal.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_age-withdraw_reveal.c | 826 |
1 files changed, 310 insertions, 516 deletions
diff --git a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c index 50d524a2f..c9aca8e99 100644 --- a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c +++ b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c @@ -19,9 +19,13 @@ * @author Özgür Kesim */ #include "platform.h" +#include <gnunet/gnunet_common.h> #include <gnunet/gnunet_util_lib.h> #include <jansson.h> #include <microhttpd.h> +#include "taler-exchange-httpd_metrics.h" +#include "taler_error_codes.h" +#include "taler_exchangedb_plugin.h" #include "taler_mhd_lib.h" #include "taler-exchange-httpd_mhd.h" #include "taler-exchange-httpd_age-withdraw_reveal.h" @@ -48,60 +52,28 @@ struct AgeRevealContext struct TALER_ReservePublicKeyP reserve_pub; /** - * Number of coins/denonations in the reveal + * Number of coins to reveal. MUST be equal to + * @e num_secrets/(kappa -1). */ uint32_t num_coins; /** - * #num_coins hashes of the denominations from which the coins are withdrawn. - * Those must support age restriction. + * Number of secrets in the reveal. MUST be a multiple of (kappa-1). */ - struct TALER_DenominationHashP *denoms_h; + uint32_t num_secrets; /** - * #num_coins denomination keys, found in the system, according to denoms_h; - */ - struct TEH_DenominationKey *denom_keys; - - /** - * Total sum of all denominations' values - **/ - struct TALER_Amount total_amount; - - /** - * Total sum of all denominations' fees - */ - struct TALER_Amount total_fee; - - /** - * #num_coins hashes of blinded coins. - */ - struct TALER_BlindedCoinHashP *coin_evs; - - /** - * secrets for #num_coins*(kappa - 1) disclosed coins. + * @e num_secrets secrets for disclosed coins. */ struct TALER_PlanchetMasterSecretP *disclosed_coin_secrets; /** * The data from the original age-withdraw. Will be retrieved from - * the DB via @a ach. + * the DB via @a ach and @a reserve_pub. */ - struct TALER_EXCHANGEDB_AgeWithdrawCommitment commitment; + struct TALER_EXCHANGEDB_AgeWithdraw commitment; }; -/** - * Helper function to free resources in the context - */ -void -age_reveal_context_free (struct AgeRevealContext *actx) -{ - GNUNET_free (actx->denoms_h); - GNUNET_free (actx->denom_keys); - GNUNET_free (actx->coin_evs); - GNUNET_free (actx->disclosed_coin_secrets); -} - /** * Parse the json body of an '/age-withdraw/$ACH/reveal' request. It extracts @@ -109,8 +81,6 @@ age_reveal_context_free (struct AgeRevealContext *actx) * memory for those. * * @param connection The MHD connection to handle - * @param j_denoms_h Array of hashes of the denominations for the withdrawal, in JSON format - * @param j_coin_evs The blinded envelopes in JSON format for the coins that are not revealed and will be signed on success * @param j_disclosed_coin_secrets The n*(kappa-1) disclosed coins' private keys in JSON format, from which all other attributes (age restriction, blinding, nonce) will be derived from * @param[out] actx The context of the operation, only partially built at call time * @param[out] mhd_ret The result if a reply is queued for MHD @@ -119,140 +89,95 @@ age_reveal_context_free (struct AgeRevealContext *actx) static enum GNUNET_GenericReturnValue parse_age_withdraw_reveal_json ( struct MHD_Connection *connection, - const json_t *j_denoms_h, - const json_t *j_coin_evs, const json_t *j_disclosed_coin_secrets, struct AgeRevealContext *actx, MHD_RESULT *mhd_ret) { enum GNUNET_GenericReturnValue result = GNUNET_SYSERR; + size_t num_entries; /* Verify JSON-structure consistency */ { const char *error = NULL; - actx->num_coins = json_array_size (j_denoms_h); /* 0, if j_denoms_h is not an array */ + num_entries = json_array_size (j_disclosed_coin_secrets); /* 0, if not an array */ - if (! json_is_array (j_denoms_h)) - error = "denoms_h must be an array"; - else if (! json_is_array (j_coin_evs)) - error = "coin_evs must be an array"; - else if (! json_is_array (j_disclosed_coin_secrets)) + if (! json_is_array (j_disclosed_coin_secrets)) error = "disclosed_coin_secrets must be an array"; - else if (actx->num_coins == 0) - error = "denoms_h must not be empty"; - else if (actx->num_coins != json_array_size (j_coin_evs)) - error = "denoms_h and coins_evs must be arrays of the same size"; - else if (actx->num_coins > TALER_MAX_FRESH_COINS) - /** - * The wallet had committed to more than the maximum coins allowed, the - * reserve has been charged, but now the user can not withdraw any money - * from it. Note that the user can't get their money back in this case! - **/ + else if (num_entries == 0) + error = "disclosed_coin_secrets must not be empty"; + else if (num_entries > TALER_MAX_FRESH_COINS) error = "maximum number of coins that can be withdrawn has been exceeded"; - else if (actx->num_coins * (TALER_CNC_KAPPA - 1) - != json_array_size (j_disclosed_coin_secrets)) - error = "the size of array disclosed_coin_secrets must be " - TALER_CNC_KAPPA_MINUS_ONE_STR " times the size of denoms_h"; if (NULL != error) { - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - error); + *mhd_ret = TALER_MHD_reply_with_ec (connection, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + error); return GNUNET_SYSERR; } + + actx->num_secrets = num_entries * (TALER_CNC_KAPPA - 1); + actx->num_coins = num_entries; + } /* Continue parsing the parts */ { unsigned int idx = 0; + unsigned int k = 0; + json_t *array = NULL; json_t *value = NULL; - /* Parse denomination keys */ - actx->denoms_h = GNUNET_new_array (actx->num_coins, - struct TALER_DenominationHashP); - - json_array_foreach (j_denoms_h, idx, value) { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto (NULL, &actx->denoms_h[idx]), - GNUNET_JSON_spec_end () - }; + /* Parse diclosed keys */ + actx->disclosed_coin_secrets = + GNUNET_new_array (actx->num_secrets, + struct TALER_PlanchetMasterSecretP); - if (GNUNET_OK != - GNUNET_JSON_parse (value, spec, NULL, NULL)) + json_array_foreach (j_disclosed_coin_secrets, idx, array) { + if (! json_is_array (array) || + (TALER_CNC_KAPPA - 1 != json_array_size (array))) { char msg[256] = {0}; GNUNET_snprintf (msg, sizeof(msg), - "couldn't parse entry no. %d in array denoms_h", + "couldn't parse entry no. %d in array disclosed_coin_secrets", idx + 1); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - msg); + *mhd_ret = TALER_MHD_reply_with_ec (connection, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + msg); goto EXIT; - } - }; - - /* Parse blinded envelopes */ - actx->coin_evs = GNUNET_new_array (actx->num_coins, - struct TALER_BlindedCoinHashP); - - json_array_foreach (j_coin_evs, idx, value) { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto (NULL, &actx->coin_evs[idx]), - GNUNET_JSON_spec_end () - }; - if (GNUNET_OK != - GNUNET_JSON_parse (value, spec, NULL, NULL)) - { - char msg[256] = {0}; - GNUNET_snprintf (msg, - sizeof(msg), - "couldn't parse entry no. %d in array coin_evs", - idx + 1); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - msg); - goto EXIT; } - }; - - /* Parse diclosed keys */ - actx->disclosed_coin_secrets = GNUNET_new_array ( - actx->num_coins * (TALER_CNC_KAPPA - 1), - struct TALER_PlanchetMasterSecretP); - - json_array_foreach (j_disclosed_coin_secrets, idx, value) { - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto (NULL, &actx->disclosed_coin_secrets[idx]), - GNUNET_JSON_spec_end () - }; - if (GNUNET_OK != - GNUNET_JSON_parse (value, spec, NULL, NULL)) + json_array_foreach (array, k, value) { - char msg[256] = {0}; - GNUNET_snprintf (msg, - sizeof(msg), - "couldn't parse entry no. %d in array disclosed_coin_secrets", - idx + 1); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - msg); - goto EXIT; + struct TALER_PlanchetMasterSecretP *secret = + &actx->disclosed_coin_secrets[2 * idx + k]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto (NULL, secret), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (value, spec, NULL, NULL)) + { + char msg[256] = {0}; + GNUNET_snprintf (msg, + sizeof(msg), + "couldn't parse entry no. %d in array disclosed_coin_secrets[%d]", + k + 1, + idx + 1); + *mhd_ret = TALER_MHD_reply_with_ec (connection, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + msg); + goto EXIT; + } } }; } result = GNUNET_OK; - *mhd_ret = MHD_YES; - EXIT: return result; @@ -269,254 +194,181 @@ EXIT: * @param reserve_pub Reserve public key used in the original age-withdraw request * @param[out] commitment Data from the original age-withdraw request * @param[out] result In the error cases, a response will be queued with MHD and this will be the result. - * @return GNUNET_OK if the withdraw request has been found, - * GNUNET_SYSERROR if we did not find the request in the DB + * @return #GNUNET_OK if the withdraw request has been found, + * #GNUNET_SYSERR if we did not find the request in the DB */ static enum GNUNET_GenericReturnValue find_original_commitment ( struct MHD_Connection *connection, const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, const struct TALER_ReservePublicKeyP *reserve_pub, - struct TALER_EXCHANGEDB_AgeWithdrawCommitment *commitment, + struct TALER_EXCHANGEDB_AgeWithdraw *commitment, MHD_RESULT *result) { enum GNUNET_DB_QueryStatus qs; - qs = TEH_plugin->get_age_withdraw_info (TEH_plugin->cls, - reserve_pub, - h_commitment, - commitment); - switch (qs) + for (unsigned int try = 0; try < 3; try++) { - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - return GNUNET_OK; /* Only happy case */ - - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - *result = TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_AGE_WITHDRAW_COMMITMENT_UNKNOWN, - NULL); - break; - - case GNUNET_DB_STATUS_HARD_ERROR: - *result = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_age_withdraw_info"); - break; - - case GNUNET_DB_STATUS_SOFT_ERROR: - /* FIXME:oec: Do we queue a result in this case or retry? */ - default: - GNUNET_break (0); /* should be impossible */ - *result = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - NULL); + qs = TEH_plugin->get_age_withdraw (TEH_plugin->cls, + reserve_pub, + h_commitment, + commitment); + switch (qs) + { + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + return GNUNET_OK; /* Only happy case */ + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + *result = TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_AGE_WITHDRAW_COMMITMENT_UNKNOWN, + NULL); + return GNUNET_SYSERR; + case GNUNET_DB_STATUS_HARD_ERROR: + *result = TALER_MHD_reply_with_ec (connection, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_age_withdraw_info"); + return GNUNET_SYSERR; + case GNUNET_DB_STATUS_SOFT_ERROR: + break; /* try again */ + default: + GNUNET_break (0); + *result = TALER_MHD_reply_with_ec (connection, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + NULL); + return GNUNET_SYSERR; + } } - + /* after unsuccessful retries*/ + *result = TALER_MHD_reply_with_ec (connection, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_age_withdraw_info"); return GNUNET_SYSERR; } /** - * Check if the given denomination is still or already valid, has not been - * revoked and supports age restriction. + * @brief Derives a age-restricted planchet from a given secret and calculates the hash * - * @param connection HTTP-connection to the client - * @param ksh The handle to the current state of (denomination) keys in the exchange - * @param denom_h Hash of the denomination key to check - * @param[out] dks On success, will contain the denomination key details - * @param[out] result On failure, an MHD-response will be qeued and result will be set to accordingly - * @return true on success (denomination valid), false otherwise + * @param connection Connection to the client + * @param keys The denomination keys in memory + * @param secret The secret to a planchet + * @param denom_pub_h The hash of the denomination for the planchet + * @param max_age The maximum age allowed + * @param[out] bch Hashcode to write + * @param[out] result On error, a HTTP-response will be queued and result set accordingly + * @return GNUNET_OK on success, GNUNET_SYSERR otherwise, with an error message + * written to the client and @e result set. */ -static bool -denomination_is_valid ( +static enum GNUNET_GenericReturnValue +calculate_blinded_hash ( struct MHD_Connection *connection, - struct TEH_KeyStateHandle *ksh, - const struct TALER_DenominationHashP *denom_h, - struct TEH_DenominationKey *dks, + const struct TEH_KeyStateHandle *keys, + const struct TALER_PlanchetMasterSecretP *secret, + const struct TALER_DenominationHashP *denom_pub_h, + uint8_t max_age, + struct TALER_BlindedCoinHashP *bch, MHD_RESULT *result) { - dks = TEH_keys_denomination_by_hash2 ( - ksh, - denom_h, - connection, - result); - - if (NULL == dks) - { - /* The denomination doesn't exist */ - GNUNET_assert (result != NULL); - /* Note: a HTTP-response has been queued and result has been set by - * TEH_keys_denominations_by_hash2 */ - return false; - } - - if (GNUNET_TIME_absolute_is_past (dks->meta.expire_withdraw.abs_time)) - { - /* This denomination is past the expiration time for withdraws */ - *result = TEH_RESPONSE_reply_expired_denom_pub_hash ( - connection, - denom_h, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED, - "age-withdraw_reveal"); - return false; - } - - if (GNUNET_TIME_absolute_is_future (dks->meta.start.abs_time)) + enum GNUNET_GenericReturnValue ret; + struct TEH_DenominationKey *denom_key; + struct TALER_AgeCommitmentHash ach; + + /* First, retrieve denomination details */ + denom_key = TEH_keys_denomination_by_hash_from_state (keys, + denom_pub_h, + connection, + result); + if (NULL == denom_key) { - /* This denomination is not yet valid */ - *result = TEH_RESPONSE_reply_expired_denom_pub_hash ( - connection, - denom_h, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE, - "age-withdraw_reveal"); - return false; + GNUNET_break_op (0); + *result = TALER_MHD_reply_with_ec (connection, + TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, + NULL); + return GNUNET_SYSERR; } - if (dks->recoup_possible) + /* calculate age commitment hash */ { - /* This denomination has been revoked */ - *result = TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_GONE, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, - NULL); - return false; + struct TALER_AgeCommitmentProof acp; + + TALER_age_restriction_from_secret (secret, + &denom_key->denom_pub.age_mask, + max_age, + &acp); + TALER_age_commitment_hash (&acp.commitment, + &ach); + TALER_age_commitment_proof_free (&acp); } - if (0 == dks->denom_pub.age_mask.bits) + /* Next: calculate planchet */ { - /* This denomation does not support age restriction */ - char msg[256] = {0}; - GNUNET_snprintf (msg, - sizeof(msg), - "denomination %s does not support age restriction", - GNUNET_h2s (&denom_h->hash)); - - *result = TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN, - msg); - return false; - } - - return true; -} - - -/** - * Check if the given array of hashes of denomination_keys a) belong - * to valid denominations and b) those are marked as age restricted. - * - * @param connection The HTTP connection to the client - * @param len The lengths of the array @a denoms_h - * @param denoms_h array of hashes of denomination public keys - * @param[out] dks On success, will be filled with the denomination keys. Caller must deallocate. - * @param amount_with_fee The committed amount including fees - * @param[out] total_amount On success, will contain the total sum of all denominations - * @param[out] total_fee On success, will contain the total sum of all fees - * @param[out] result In the error cases, a response will be queued with MHD and this will be the result. - * @return GNUNET_OK if the denominations are valid and support age-restriction - * GNUNET_SYSERR otherwise - */ -static enum GNUNET_GenericReturnValue -are_denominations_valid ( - struct MHD_Connection *connection, - uint32_t len, - const struct TALER_DenominationHashP *denoms_h, - struct TEH_DenominationKey **dks, - const struct TALER_Amount *amount_with_fee, - struct TALER_Amount *total_amount, - struct TALER_Amount *total_fee, - MHD_RESULT *result) -{ - struct TEH_KeyStateHandle *ksh; - - GNUNET_assert (*dks == NULL); - - ksh = TEH_keys_get_state (); - if (NULL == ksh) - { - *result = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, - NULL); - return GNUNET_SYSERR; - } - - *dks = GNUNET_new_array (len, struct TEH_DenominationKey); - TALER_amount_set_zero (TEH_currency, total_amount); - TALER_amount_set_zero (TEH_currency, total_fee); + struct TALER_CoinPubHashP c_hash; + struct TALER_PlanchetDetail detail = {0}; + struct TALER_CoinSpendPrivateKeyP coin_priv; + union GNUNET_CRYPTO_BlindingSecretP bks; + struct GNUNET_CRYPTO_BlindingInputValues bi = { + .cipher = denom_key->denom_pub.bsign_pub_key->cipher + }; + struct TALER_ExchangeWithdrawValues alg_values = { + .blinding_inputs = &bi + }; + union GNUNET_CRYPTO_BlindSessionNonce nonce; + union GNUNET_CRYPTO_BlindSessionNonce *noncep = NULL; - for (uint32_t i = 0; i < len; i++) - { - if (! denomination_is_valid (connection, - ksh, - &denoms_h[i], - dks[i], - result)) + // FIXME: add logic to denom.c to do this! + if (GNUNET_CRYPTO_BSA_CS == bi.cipher) { - return GNUNET_SYSERR; - } + struct TEH_CsDeriveData cdd = { + .h_denom_pub = &denom_key->h_denom_pub, + .nonce = &nonce.cs_nonce, + }; - /* Accumulate the values */ - if (0 > TALER_amount_add ( - total_amount, - total_amount, - &dks[i]->meta.value)) - { - GNUNET_break (0); - *result = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_OVERFLOW, - "amount"); - return GNUNET_SYSERR; + TALER_cs_withdraw_nonce_derive (secret, + &nonce.cs_nonce); + noncep = &nonce; + GNUNET_assert (TALER_EC_NONE == + TEH_keys_denomination_cs_r_pub ( + &cdd, + false, + &bi.details.cs_values)); } - - /* Accumulate the withdraw fees */ - if (0 > TALER_amount_add ( - total_fee, - total_fee, - &dks[i]->meta.fees.withdraw)) + TALER_planchet_blinding_secret_create (secret, + &alg_values, + &bks); + TALER_planchet_setup_coin_priv (secret, + &alg_values, + &coin_priv); + ret = TALER_planchet_prepare (&denom_key->denom_pub, + &alg_values, + &bks, + noncep, + &coin_priv, + &ach, + &c_hash, + &detail); + if (GNUNET_OK != ret) { GNUNET_break (0); - *result = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_OVERFLOW, - "fee"); - return GNUNET_SYSERR; + *result = TALER_MHD_reply_json_pack (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + "{ss}", + "details", + "failed to prepare planchet from base key"); + return ret; } - } - - /* Compare the committed amount against the totals */ - { - struct TALER_Amount sum; - TALER_amount_set_zero (TEH_currency, &sum); - - GNUNET_assert (0 < TALER_amount_add ( - &sum, - total_amount, - total_fee)); - if (0 != TALER_amount_cmp (&sum, amount_with_fee)) - { - GNUNET_break_op (0); - *result = TALER_MHD_reply_with_ec (connection, - TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_INCORRECT, - NULL); - return GNUNET_SYSERR; - } + TALER_coin_ev_hash (&detail.blinded_planchet, + &denom_key->h_denom_pub, + bch); + TALER_blinded_planchet_free (&detail.blinded_planchet); } - return GNUNET_OK; + return ret; } /** - * Checks the validity of the disclosed coins as follows: + * @brief Checks the validity of the disclosed coins as follows: * - Derives and calculates the disclosed coins' * - public keys, * - nonces (if applicable), @@ -533,169 +385,90 @@ are_denominations_valid ( * https://docs.taler.net/design-documents/024-age-restriction.html#withdraw * * @param connection HTTP-connection to the client - * @param h_commitment_orig Original commitment - * @param max_age Maximum age allowed for the age restriction - * @param noreveal_idx Index that was given to the client in response to the age-withdraw request - * @param num_coins Number of coins - * @param coin_evs The Hashes of the undisclosed, blinded coins, @a num_coins many - * @param denom_keys The array of denomination keys, @a num_coins. Needed to detect Clause-Schnorr-based denominations + * @param commitment Original commitment * @param disclosed_coin_secrets The secrets of the disclosed coins, (TALER_CNC_KAPPA - 1)*num_coins many + * @param num_coins number of coins to reveal via @a disclosed_coin_secrets * @param[out] result On error, a HTTP-response will be queued and result set accordingly * @return GNUNET_OK on success, GNUNET_SYSERR otherwise */ static enum GNUNET_GenericReturnValue verify_commitment_and_max_age ( struct MHD_Connection *connection, - const struct TALER_AgeWithdrawCommitmentHashP *h_commitment_orig, - const uint32_t max_age, - const uint32_t noreveal_idx, - const uint32_t num_coins, - const struct TALER_BlindedCoinHashP *coin_evs, - const struct TEH_DenominationKey *denom_keys, + const struct TALER_EXCHANGEDB_AgeWithdraw *commitment, const struct TALER_PlanchetMasterSecretP *disclosed_coin_secrets, + uint32_t num_coins, MHD_RESULT *result) { enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; struct GNUNET_HashContext *hash_context; + struct TEH_KeyStateHandle *keys; + + if (num_coins != commitment->num_coins) + { + GNUNET_break_op (0); + *result = TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "#coins"); + return GNUNET_SYSERR; + } + + /* We need the current keys in memory for the meta-data of the denominations */ + keys = TEH_keys_get_state (); + if (NULL == keys) + { + *result = TALER_MHD_reply_with_ec (connection, + TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, + NULL); + return GNUNET_SYSERR; + } hash_context = GNUNET_CRYPTO_hash_context_start (); - for (size_t c = 0; c < num_coins; c++) + for (size_t coin_idx = 0; coin_idx < num_coins; coin_idx++) { - size_t k = 0; /* either 0 or 1, to index into coin_evs */ + size_t i = 0; /* either 0 or 1, to index into coin_evs */ - for (size_t idx = 0; idx<3; idx++) + for (size_t k = 0; k<TALER_CNC_KAPPA; k++) { - if (idx == (size_t) noreveal_idx) + if (k == (size_t) commitment->noreveal_index) { GNUNET_CRYPTO_hash_context_read (hash_context, - &coin_evs[c], - sizeof(coin_evs[c])); + &commitment->h_coin_evs[coin_idx], + sizeof(commitment->h_coin_evs[coin_idx])); } else { - /* FIXME:oec: Refactor this block out into its own function */ - - size_t j = 2 * c + k; /* Index into disclosed_coin_secrets[] */ + /* j is the index into disclosed_coin_secrets[] */ + size_t j = (TALER_CNC_KAPPA - 1) * coin_idx + i; const struct TALER_PlanchetMasterSecretP *secret; - struct TALER_AgeCommitmentHash ach; + struct TALER_BlindedCoinHashP bch; - GNUNET_assert (k<2); - GNUNET_assert (num_coins * (TALER_CNC_KAPPA - 1) > j); + GNUNET_assert (2>i); + GNUNET_assert ((TALER_CNC_KAPPA - 1) * num_coins > j); secret = &disclosed_coin_secrets[j]; - k++; + i++; - /* First: calculate age commitment hash */ - { - struct TALER_AgeCommitmentProof acp; - ret = TALER_age_restriction_from_secret ( - secret, - &denom_keys[c].denom_pub.age_mask, - max_age, - &acp); - - if (GNUNET_OK != ret) - { - GNUNET_break (0); - *result = TALER_MHD_reply_json_pack (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - "{sssi}", - "failed to derive age restriction from base key", - "index", - j); - return ret; - } - - TALER_age_commitment_hash (&acp.commitment, &ach); - } + ret = calculate_blinded_hash (connection, + keys, + secret, + &commitment->denom_pub_hashes[coin_idx], + commitment->max_age, + &bch, + result); - /* Next: calculate planchet */ + if (GNUNET_OK != ret) { - struct TALER_CoinPubHashP c_hash; - struct TALER_PlanchetDetail detail; - struct TALER_BlindedCoinHashP bch; - struct TALER_CoinSpendPrivateKeyP coin_priv; - union TALER_DenominationBlindingKeyP bks; - struct TALER_ExchangeWithdrawValues alg_values = { - .cipher = denom_keys[c].denom_pub.cipher, - }; - - if (TALER_DENOMINATION_CS == alg_values.cipher) - { - struct TALER_CsNonce nonce; - - TALER_cs_withdraw_nonce_derive ( - secret, - &nonce); - - { - enum TALER_ErrorCode ec; - struct TEH_CsDeriveData cdd = { - .h_denom_pub = &denom_keys[c].h_denom_pub, - .nonce = &nonce, - }; - - ec = TEH_keys_denomination_cs_r_pub (&cdd, - false, - &alg_values.details. - cs_values); - -#pragma message ("FIXME:oec: return value of needs handling!") - /* FIXME:oec: Handle error */ - GNUNET_assert (TALER_EC_NONE == ec); - } - } - - TALER_planchet_blinding_secret_create (secret, - &alg_values, - &bks); - - TALER_planchet_setup_coin_priv (secret, - &alg_values, - &coin_priv); - - ret = TALER_planchet_prepare (&denom_keys[c].denom_pub, - &alg_values, - &bks, - &coin_priv, - &ach, - &c_hash, - &detail); - - if (GNUNET_OK != ret) - { - GNUNET_break (0); - *result = TALER_MHD_reply_json_pack (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - "{sssi}", - "details", - "failed to prepare planchet from base key", - "index", - j); - return ret; - } - - ret = TALER_coin_ev_hash (&detail.blinded_planchet, - &denom_keys[c].h_denom_pub, - &bch); - if (GNUNET_OK != ret) - { - GNUNET_break (0); - *result = TALER_MHD_reply_json_pack (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - "{sssi}", - "details", - "failed to hash planchet from base key", - "index", - j); - return ret; - } - - GNUNET_CRYPTO_hash_context_read (hash_context, - &detail.blinded_planchet, - sizeof(detail.blinded_planchet)); + GNUNET_CRYPTO_hash_context_abort (hash_context); + return GNUNET_SYSERR; } + + /* Continue the running hash of all coin hashes with the calculated + * hash-value of the current, disclosed coin */ + GNUNET_CRYPTO_hash_context_read (hash_context, + &bch, + sizeof(bch)); } } } @@ -706,7 +479,7 @@ verify_commitment_and_max_age ( GNUNET_CRYPTO_hash_context_finish (hash_context, &calc_hash); - if (0 != GNUNET_CRYPTO_hash_cmp (&h_commitment_orig->hash, + if (0 != GNUNET_CRYPTO_hash_cmp (&commitment->h_commitment.hash, &calc_hash)) { GNUNET_break_op (0); @@ -717,8 +490,40 @@ verify_commitment_and_max_age ( } } + return GNUNET_OK; +} - return ret; + +/** + * @brief Send a response for "/age-withdraw/$RCH/reveal" + * + * @param connection The http connection to the client to send the response to + * @param commitment The data from the commitment with signatures + * @return a MHD result code + */ +static MHD_RESULT +reply_age_withdraw_reveal_success ( + struct MHD_Connection *connection, + const struct TALER_EXCHANGEDB_AgeWithdraw *commitment) +{ + json_t *list = json_array (); + GNUNET_assert (NULL != list); + + for (unsigned int i = 0; i < commitment->num_coins; i++) + { + json_t *obj = GNUNET_JSON_PACK ( + TALER_JSON_pack_blinded_denom_sig (NULL, + &commitment->denom_sigs[i])); + GNUNET_assert (0 == + json_array_append_new (list, + obj)); + } + + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_array_steal ("ev_sigs", + list)); } @@ -731,45 +536,41 @@ TEH_handler_age_withdraw_reveal ( MHD_RESULT result = MHD_NO; enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; struct AgeRevealContext actx = {0}; - json_t *j_denoms_h; - json_t *j_coin_evs; - json_t *j_disclosed_coin_secrets; + const json_t *j_disclosed_coin_secrets; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("reserve_pub", &actx.reserve_pub), - GNUNET_JSON_spec_json ("denoms_h", &j_denoms_h), - GNUNET_JSON_spec_json ("coin_evs", &j_coin_evs), - GNUNET_JSON_spec_json ("disclosed_coin_secrets", &j_disclosed_coin_secrets), + GNUNET_JSON_spec_fixed_auto ("reserve_pub", + &actx.reserve_pub), + GNUNET_JSON_spec_array_const ("disclosed_coin_secrets", + &j_disclosed_coin_secrets), GNUNET_JSON_spec_end () }; actx.ach = *ach; /* Parse JSON body*/ + ret = TALER_MHD_parse_json_data (rc->connection, + root, + spec); + if (GNUNET_OK != ret) { - ret = TALER_MHD_parse_json_data (rc->connection, - root, - spec); - if (GNUNET_OK != ret) - { - GNUNET_break_op (0); - return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES; - } + GNUNET_break_op (0); + return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES; } do { /* Extract denominations, blinded and disclosed coins */ - if (GNUNET_OK != parse_age_withdraw_reveal_json ( + if (GNUNET_OK != + parse_age_withdraw_reveal_json ( rc->connection, - j_denoms_h, - j_coin_evs, j_disclosed_coin_secrets, &actx, &result)) break; /* Find original commitment */ - if (GNUNET_OK != find_original_commitment ( + if (GNUNET_OK != + find_original_commitment ( rc->connection, &actx.ach, &actx.reserve_pub, @@ -777,38 +578,31 @@ TEH_handler_age_withdraw_reveal ( &result)) break; - /* Ensure validity of denoms and the sum of amounts and fees */ - if (GNUNET_OK != are_denominations_valid ( - rc->connection, - actx.num_coins, - actx.denoms_h, - &actx.denom_keys, - &actx.commitment.amount_with_fee, - &actx.total_amount, - &actx.total_fee, - &result)) - break; - - /* Verify the computed h_commitment equals the committed one and that - * coins have a maximum age group corresponding max_age (age-mask dependent) */ - if (GNUNET_OK != verify_commitment_and_max_age ( + /* Verify the computed h_commitment equals the committed one and that coins + * have a maximum age group corresponding max_age (age-mask dependent) */ + if (GNUNET_OK != + verify_commitment_and_max_age ( rc->connection, - &actx.commitment.h_commitment, - actx.commitment.max_age, - actx.commitment.noreveal_index, - actx.num_coins, - actx.coin_evs, - actx.denom_keys, + &actx.commitment, actx.disclosed_coin_secrets, + actx.num_coins, &result)) break; - /* TODO:oec: sign the coins */ + /* Finally, return the signatures */ + result = reply_age_withdraw_reveal_success (rc->connection, + &actx.commitment); - } while(0); + } while (0); - age_reveal_context_free (&actx); GNUNET_JSON_parse_free (spec); + if (NULL != actx.commitment.denom_sigs) + for (unsigned int i = 0; i<actx.num_coins; i++) + TALER_blinded_denom_sig_free (&actx.commitment.denom_sigs[i]); + GNUNET_free (actx.commitment.denom_sigs); + GNUNET_free (actx.commitment.denom_pub_hashes); + GNUNET_free (actx.commitment.denom_serials); + GNUNET_free (actx.disclosed_coin_secrets); return result; } |