/* This file is part of TALER Copyright (C) 2023 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with TALER; see the file COPYING. If not, see */ /** * @file taler-exchange-httpd_age-withdraw_reveal.c * @brief Handle /age-withdraw/$ACH/reveal requests * @author Özgür Kesim */ #include "platform.h" #include #include #include #include "taler-exchange-httpd_metrics.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" #include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_keys.h" /** * State for an /age-withdraw/$ACH/reveal operation. */ struct AgeRevealContext { /** * Commitment for the age-withdraw operation, previously called by the * client. */ struct TALER_AgeWithdrawCommitmentHashP ach; /** * Public key of the reserve for with the age-withdraw commitment was * originally made. This parameter is provided by the client again * during the call to reveal in order to save a database-lookup. */ struct TALER_ReservePublicKeyP reserve_pub; /** * Number of coins/denonations in the reveal */ uint32_t num_coins; /** * #num_coins hashes of the denominations from which the coins are withdrawn. * Those must support age restriction. */ struct TALER_DenominationHashP *denoms_h; /** * #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 coin planchets. */ struct TALER_BlindedPlanchet *coin_evs; /** * secrets for #num_coins*(kappa - 1) disclosed coins. */ struct TALER_PlanchetMasterSecretP *disclosed_coin_secrets; /** * The data from the original age-withdraw. Will be retrieved from * the DB via @a ach. */ struct TALER_EXCHANGEDB_AgeWithdrawCommitment commitment; }; /** * Information per planchet in the batch. */ struct PlanchetContext { /** * Hash of the (blinded) message to be signed by the Exchange. */ struct TALER_BlindedCoinHashP h_coin_envelope; /** * Value of the coin being exchanged (matching the denomination key) * plus the transaction fee. We include this in what is being * signed so that we can verify a reserve's remaining total balance * without needing to access the respective denomination key * information each time. */ struct TALER_Amount amount_with_fee; /** * Blinded planchet. */ struct TALER_BlindedPlanchet blinded_planchet; /** * Set to the resulting signed coin data to be returned to the client. */ struct TALER_EXCHANGEDB_CollectableBlindcoin collectable; }; /** * 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 * the denomination hashes, blinded coins and disclosed coins and allocates * 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 * @return true on success, false on failure, with a reply already queued for MHD. */ 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; /* 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 */ 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)) 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! **/ 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); return GNUNET_SYSERR; } } /* Continue parsing the parts */ { unsigned int idx = 0; 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 () }; 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 denoms_h", idx + 1); *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, msg); goto EXIT; } }; /* Parse blinded envelopes */ actx->coin_evs = GNUNET_new_array (actx->num_coins, struct TALER_BlindedPlanchet); json_array_foreach (j_coin_evs, idx, value) { struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_blinded_planchet (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; } /* Check for duplicate planchets */ for (unsigned int i = 0; i < idx; i++) { if (0 == TALER_blinded_planchet_cmp (&actx->coin_evs[idx], &actx->coin_evs[i])) { GNUNET_break_op (0); *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, "duplicate planchet"); 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)) { 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; } }; } result = GNUNET_OK; *mhd_ret = MHD_YES; EXIT: return result; } /** * Check if the request belongs to an existing age-withdraw request. * If so, sets the commitment object with the request data. * Otherwise, it queues an appropriate MHD response. * * @param connection The HTTP connection to the client * @param h_commitment Original commitment value sent with the age-withdraw request * @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 */ 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, 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) { 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); *result = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, NULL); } return GNUNET_SYSERR; } /** * Check if the given denomination is still or already valid, has not been * revoked and supports age restriction. * * @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 */ static bool denomination_is_valid ( struct MHD_Connection *connection, struct TEH_KeyStateHandle *ksh, const struct TALER_DenominationHashP *denom_h, struct TEH_DenominationKey *dks, 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)) { /* 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; } if (dks->recoup_possible) { /* This denomination has been revoked */ *result = TALER_MHD_reply_with_error ( connection, MHD_HTTP_GONE, TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED, NULL); return false; } if (0 == dks->denom_pub.age_mask.bits) { /* 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 coin_evs array of blinded coin planchets * @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, const struct TALER_BlindedPlanchet *coin_evs, 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); for (uint32_t i = 0; i < len; i++) { if (! denomination_is_valid (connection, ksh, &denoms_h[i], dks[i], result)) return GNUNET_SYSERR; /* Ensure the ciphers from the planchets match the denominations' */ if (dks[i]->denom_pub.cipher != coin_evs[i].cipher) { GNUNET_break_op (0); *result = TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, NULL); return GNUNET_SYSERR; } /* Accumulate the values */ if (0 > TALER_amount_add ( total_amount, total_amount, &dks[i]->meta.value)) { GNUNET_break_op (0); *result = TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_OVERFLOW, "amount"); return GNUNET_SYSERR; } /* Accumulate the withdraw fees */ if (0 > TALER_amount_add ( total_fee, total_fee, &dks[i]->meta.fees.withdraw)) { GNUNET_break_op (0); *result = TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_OVERFLOW, "fee"); return GNUNET_SYSERR; } } /* 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_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_INCORRECT, NULL); return GNUNET_SYSERR; } } return GNUNET_OK; } /** * Checks the validity of the disclosed coins as follows: * - Derives and calculates the disclosed coins' * - public keys, * - nonces (if applicable), * - age commitments, * - blindings * - blinded hashes * - Computes h_commitment with those calculated and the undisclosed hashes * - Compares h_commitment with the value from the original commitment * - Verifies that all public keys in indices larger than the age group * corresponding to max_age are derived from the constant public key. * * The derivation of the blindings, (potential) nonces and age-commitment from * a coin's private keys is defined in * 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 blindet planchets of the undisclosed coins, @a num_coins many * @param denom_keys The array of denomination keys, @a num_coins. Needed to detect Clause-Schnorr-based denominations * @param disclosed_coin_secrets The secrets of the disclosed coins, (TALER_CNC_KAPPA - 1)*num_coins many * @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_BlindedPlanchet *coin_evs, const struct TEH_DenominationKey *denom_keys, const struct TALER_PlanchetMasterSecretP *disclosed_coin_secrets, MHD_RESULT *result) { enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; struct GNUNET_HashContext *hash_context; hash_context = GNUNET_CRYPTO_hash_context_start (); for (size_t c = 0; c < num_coins; c++) { size_t k = 0; /* either 0 or 1, to index into coin_evs */ for (size_t idx = 0; idx j); secret = &disclosed_coin_secrets[j]; k++; /* 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); } /* Next: calculate planchet */ { struct TALER_CoinPubHashP c_hash; struct TALER_PlanchetDetail detail; 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); /* FIXME 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; } } /* 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)); } } } /* Finally, compare the calculated hash with the original commitment */ { struct GNUNET_HashCode calc_hash; GNUNET_CRYPTO_hash_context_finish (hash_context, &calc_hash); if (0 != GNUNET_CRYPTO_hash_cmp (&h_commitment_orig->hash, &calc_hash)) { GNUNET_break_op (0); *result = TALER_MHD_reply_with_ec (connection, TALER_EC_EXCHANGE_AGE_WITHDRAW_REVEAL_INVALID_HASH, NULL); return GNUNET_SYSERR; } } 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 num_coins Number of new coins with age restriction for which we reveal data * @param awrcs array of @a num_coins signatures revealed * @return a MHD result code */ static MHD_RESULT reply_age_withdraw_reveal_success ( struct MHD_Connection *connection, unsigned int num_coins, const struct TALER_EXCHANGEDB_AgeWithdrawRevealedCoin *awrcs) { json_t *list = json_array (); GNUNET_assert (NULL != list); for (unsigned int index = 0; index < num_coins; index++) { json_t *obj = GNUNET_JSON_PACK ( TALER_JSON_pack_blinded_denom_sig ("ev_sig", &awrcs[index].coin_sig)); 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)); } /** * @brief Signs and persists the undisclosed coins * * @param connection HTTP-connection to the client * @param h_commitment Original commitment * @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[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 sign_and_finalize_age_withdraw ( struct MHD_Connection *connection, const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, const uint32_t num_coins, const struct TALER_BlindedPlanchet *coin_evs, const struct TEH_DenominationKey *denom_keys, MHD_RESULT *result) { enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; struct TEH_CoinSignData csds[num_coins]; struct TALER_BlindedDenominationSignature bds[num_coins]; struct TALER_EXCHANGEDB_AgeWithdrawRevealedCoin awrcs[num_coins]; enum GNUNET_DB_QueryStatus qs; for (uint32_t i = 0; istart (TEH_plugin->cls, "insert_age_withdraw_reveal batch")) { GNUNET_break (0); ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_START_FAILED, NULL); goto cleanup; } qs = TEH_plugin->insert_age_withdraw_reveal (TEH_plugin->cls, h_commitment, num_coins, awrcs); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) { TEH_plugin->rollback (TEH_plugin->cls); continue; } else if (GNUNET_DB_STATUS_HARD_ERROR == qs) { GNUNET_break (0); TEH_plugin->rollback (TEH_plugin->cls); ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_STORE_FAILED, "insert_age_withdraw_reveal"); goto cleanup; } changed = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); /* Commit the transaction */ qs = TEH_plugin->commit (TEH_plugin->cls); if (qs >= 0) { if (changed) TEH_METRICS_num_success[TEH_MT_SUCCESS_AGE_WITHDRAW_REVEAL]++; break; /* success */ } else if (GNUNET_DB_STATUS_HARD_ERROR == qs) { GNUNET_break (0); TEH_plugin->rollback (TEH_plugin->cls); ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_COMMIT_FAILED, NULL); goto cleanup; } else { TEH_plugin->rollback (TEH_plugin->cls); } } /* end of retry */ if (GNUNET_DB_STATUS_SOFT_ERROR == qs) { GNUNET_break (0); TEH_plugin->rollback (TEH_plugin->cls); ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_SOFT_FAILURE, NULL); goto cleanup; } /* Generate final (positive) response */ ret = reply_age_withdraw_reveal_success (connection, num_coins, awrcs); cleanup: GNUNET_break (GNUNET_OK != ret); /* Free resources */ for (unsigned int i = 0; iconnection, root, spec); if (GNUNET_OK != ret) { 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 ( rc->connection, j_denoms_h, j_coin_evs, j_disclosed_coin_secrets, &actx, &result)) break; /* Find original commitment */ if (GNUNET_OK != find_original_commitment ( rc->connection, &actx.ach, &actx.reserve_pub, &actx.commitment, &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.coin_evs, &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 ( rc->connection, &actx.commitment.h_commitment, actx.commitment.max_age, actx.commitment.noreveal_index, actx.num_coins, actx.coin_evs, actx.denom_keys, actx.disclosed_coin_secrets, &result)) break; /* Finally, sign and persist the coins */ if (GNUNET_OK != sign_and_finalize_age_withdraw ( rc->connection, &actx.commitment.h_commitment, actx.num_coins, actx.coin_evs, actx.denom_keys, &result)) break; } while(0); age_reveal_context_free (&actx); return result; } /* end of taler-exchange-httpd_age-withdraw_reveal.c */