From 0a2b049864c8dae0c53c203d46fca89e0e66849d Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sat, 29 Feb 2020 16:42:10 +0100 Subject: big rename fest related to #6067 API renaming --- .../taler-exchange-httpd_refreshes_reveal.c | 979 +++++++++++++++++++++ 1 file changed, 979 insertions(+) create mode 100644 src/exchange/taler-exchange-httpd_refreshes_reveal.c (limited to 'src/exchange/taler-exchange-httpd_refreshes_reveal.c') diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.c b/src/exchange/taler-exchange-httpd_refreshes_reveal.c new file mode 100644 index 000000000..e7e5b97da --- /dev/null +++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.c @@ -0,0 +1,979 @@ +/* + This file is part of TALER + Copyright (C) 2014-2019 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_refreshes_reveal.c + * @brief Handle /refreshes/$RCH/reveal requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include "taler_mhd_lib.h" +#include "taler-exchange-httpd_mhd.h" +#include "taler-exchange-httpd_refreshes_reveal.h" +#include "taler-exchange-httpd_responses.h" +#include "taler-exchange-httpd_keystate.h" + + +/** + * Maximum number of fresh coins we allow per refresh operation. + */ +#define MAX_FRESH_COINS 256 + +/** + * How often do we at most retry the reveal transaction sequence? + * Twice should really suffice in all cases (as the possible conflict + * cannot happen more than once). + */ +#define MAX_REVEAL_RETRIES 2 + + +/** + * Send a response for "/refresh/reveal". + * + * @param connection the connection to send the response to + * @param num_newcoins number of new coins for which we reveal data + * @param sigs array of @a num_newcoins signatures revealed + * @return a MHD result code + */ +static int +reply_refresh_reveal_success (struct MHD_Connection *connection, + unsigned int num_newcoins, + const struct TALER_DenominationSignature *sigs) +{ + json_t *list; + int ret; + + list = json_array (); + for (unsigned int newcoin_index = 0; + newcoin_index < num_newcoins; + newcoin_index++) + { + json_t *obj; + + obj = json_object (); + json_object_set_new (obj, + "ev_sig", + GNUNET_JSON_from_rsa_signature ( + sigs[newcoin_index].rsa_signature)); + GNUNET_assert (0 == + json_array_append_new (list, + obj)); + } + + { + json_t *root; + + root = json_object (); + json_object_set_new (root, + "ev_sigs", + list); + ret = TALER_MHD_reply_json (connection, + root, + MHD_HTTP_OK); + json_decref (root); + } + return ret; +} + + +/** + * Send a response for a failed "/refresh/reveal", where the + * revealed value(s) do not match the original commitment. + * + * @param connection the connection to send the response to + * @param rc commitment computed by the exchange + * @return a MHD result code + */ +static int +reply_refresh_reveal_mismatch (struct MHD_Connection *connection, + const struct TALER_RefreshCommitmentP *rc) +{ + return TALER_MHD_reply_json_pack (connection, + MHD_HTTP_CONFLICT, + "{s:s, s:I, s:o}", + "hint", "commitment violation", + "code", + (json_int_t) + TALER_EC_REFRESH_REVEAL_COMMITMENT_VIOLATION, + "rc_expected", + GNUNET_JSON_from_data_auto (rc)); +} + + +/** + * State for a /refresh/reveal operation. + */ +struct RevealContext +{ + + /** + * Commitment of the refresh operaton. + */ + struct TALER_RefreshCommitmentP rc; + + /** + * Transfer public key at gamma. + */ + struct TALER_TransferPublicKeyP gamma_tp; + + /** + * Transfer private keys revealed to us. + */ + struct TALER_TransferPrivateKeyP transfer_privs[TALER_CNC_KAPPA - 1]; + + /** + * Denominations being requested. + */ + const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation **dkis; + + /** + * Envelopes to be signed. + */ + const struct TALER_RefreshCoinData *rcds; + + /** + * Signatures over the link data (of type + * #TALER_SIGNATURE_WALLET_COIN_LINK) + */ + const struct TALER_CoinSpendSignatureP *link_sigs; + + /** + * Envelopes with the signatures to be returned. Initially NULL. + */ + struct TALER_DenominationSignature *ev_sigs; + + /** + * Size of the @e dkis, @e rcds and @e ev_sigs arrays (if non-NULL). + */ + unsigned int num_fresh_coins; + + /** + * Result from preflight checks. #GNUNET_NO for no result, + * #GNUNET_YES if preflight found previous successful operation, + * #GNUNET_SYSERR if prefight check failed hard (and generated + * an MHD response already). + */ + int preflight_ok; + +}; + + +/** + * Function called with information about a refresh order we already + * persisted. Stores the result in @a cls so we don't do the calculation + * again. + * + * @param cls closure with a `struct RevealContext` + * @param num_newcoins size of the @a rrcs array + * @param rrcs array of @a num_newcoins information about coins to be created + * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1 + * @param tprivs array of @e num_tprivs transfer private keys + * @param tp transfer public key information + */ +static void +check_exists_cb (void *cls, + uint32_t num_newcoins, + const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs, + unsigned int num_tprivs, + const struct TALER_TransferPrivateKeyP *tprivs, + const struct TALER_TransferPublicKeyP *tp) +{ + struct RevealContext *rctx = cls; + + if (0 == num_newcoins) + { + GNUNET_break (0); + return; + } + GNUNET_break (TALER_CNC_KAPPA - 1 == num_tprivs); + GNUNET_break_op (0 == + GNUNET_memcmp (tp, + &rctx->gamma_tp)); + GNUNET_break_op (0 == + memcmp (tprivs, + &rctx->transfer_privs, + sizeof (struct TALER_TransferPrivateKeyP) + * num_tprivs)); + /* We usually sign early (optimistic!), but in case we change that *and* + we do find the operation in the database, we could use this: */ + if (NULL == rctx->ev_sigs) + { + rctx->ev_sigs = GNUNET_new_array (num_newcoins, + struct TALER_DenominationSignature); + for (unsigned int i = 0; iev_sigs[i].rsa_signature + = GNUNET_CRYPTO_rsa_signature_dup (rrcs[i].coin_sig.rsa_signature); + } +} + + +/** + * Check if the "/refresh/reveal" was already successful before. + * If so, just return the old result. + * + * @param cls closure of type `struct RevealContext` + * @param connection MHD request which triggered the transaction + * @param session database session to use + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!) + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +refresh_reveal_preflight (void *cls, + struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + int *mhd_ret) +{ + struct RevealContext *rctx = cls; + enum GNUNET_DB_QueryStatus qs; + + /* Try to see if we already have given an answer before. */ + qs = TEH_plugin->get_refresh_reveal (TEH_plugin->cls, + session, + &rctx->rc, + &check_exists_cb, + rctx); + switch (qs) + { + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + return qs; /* continue normal execution */ + case GNUNET_DB_STATUS_SOFT_ERROR: + return qs; + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (qs); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_REVEAL_DB_FETCH_REVEAL_ERROR, + "failed to fetch reveal data"); + rctx->preflight_ok = GNUNET_SYSERR; + return GNUNET_DB_STATUS_HARD_ERROR; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + default: + /* Hossa, already found our reply! */ + GNUNET_assert (NULL != rctx->ev_sigs); + rctx->preflight_ok = GNUNET_YES; + return qs; + } +} + + +/** + * Execute a "/refresh/reveal". The client is revealing to us the + * transfer keys for @a #TALER_CNC_KAPPA-1 sets of coins. Verify that the + * revealed transfer keys would allow linkage to the blinded coins. + * + * IF it returns a non-error code, the transaction logic MUST + * NOT queue a MHD response. IF it returns an hard error, the + * transaction logic MUST queue a MHD response and set @a mhd_ret. IF + * it returns the soft error code, the function MAY be called again to + * retry and MUST not queue a MHD response. + * + * @param cls closure of type `struct RevealContext` + * @param connection MHD request which triggered the transaction + * @param session database session to use + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!) + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +refresh_reveal_transaction (void *cls, + struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + int *mhd_ret) +{ + struct RevealContext *rctx = cls; + struct TALER_EXCHANGEDB_RefreshMelt melt; + enum GNUNET_DB_QueryStatus qs; + + /* Obtain basic information about the refresh operation and what + gamma we committed to. */ + qs = TEH_plugin->get_melt (TEH_plugin->cls, + session, + &rctx->rc, + &melt); + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_REFRESH_REVEAL_SESSION_UNKNOWN, + "rc"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + return qs; + if ( (GNUNET_DB_STATUS_HARD_ERROR == qs) || + (melt.session.noreveal_index >= TALER_CNC_KAPPA) ) + { + GNUNET_break (0); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_REVEAL_DB_FETCH_SESSION_ERROR, + "failed to fetch valid challenge from database"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + + /* Verify commitment */ + { + /* Note that the contents of rcs[melt.session.noreveal_index] + will be aliased and are *not* allocated (or deallocated) in + this function -- in contrast to the other offsets! */ + struct TALER_RefreshCommitmentEntry rcs[TALER_CNC_KAPPA]; + struct TALER_RefreshCommitmentP rc_expected; + unsigned int off; + + off = 0; /* did we pass session.noreveal_index yet? */ + for (unsigned int i = 0; itransfer_pub = rctx->gamma_tp; + rce->new_coins = (struct TALER_RefreshCoinData *) rctx->rcds; + off = 1; + } + else + { + /* Reconstruct coin envelopes from transfer private key */ + struct TALER_TransferPrivateKeyP *tpriv = &rctx->transfer_privs[i + - off]; + struct TALER_TransferSecretP ts; + + GNUNET_CRYPTO_ecdhe_key_get_public (&tpriv->ecdhe_priv, + &rce->transfer_pub.ecdhe_pub); + TALER_link_reveal_transfer_secret (tpriv, + &melt.session.coin.coin_pub, + &ts); + rce->new_coins = GNUNET_new_array (rctx->num_fresh_coins, + struct TALER_RefreshCoinData); + for (unsigned int j = 0; jnum_fresh_coins; j++) + { + struct TALER_RefreshCoinData *rcd = &rce->new_coins[j]; + struct TALER_PlanchetSecretsP ps; + struct TALER_PlanchetDetail pd; + + rcd->dk = &rctx->dkis[j]->denom_pub; + TALER_planchet_setup_refresh (&ts, + j, + &ps); + GNUNET_assert (GNUNET_OK == + TALER_planchet_prepare (rcd->dk, + &ps, + &pd)); + rcd->coin_ev = pd.coin_ev; + rcd->coin_ev_size = pd.coin_ev_size; + } + } + } + TALER_refresh_get_commitment (&rc_expected, + TALER_CNC_KAPPA, + rctx->num_fresh_coins, + rcs, + &melt.session.coin.coin_pub, + &melt.session.amount_with_fee); + + /* Free resources allocated above */ + for (unsigned int i = 0; inum_fresh_coins; j++) + { + struct TALER_RefreshCoinData *rcd = &rce->new_coins[j]; + + GNUNET_free (rcd->coin_ev); + } + GNUNET_free (rce->new_coins); + } + + /* Verify rc_expected matches rc */ + if (0 != GNUNET_memcmp (&rctx->rc, + &rc_expected)) + { + GNUNET_break_op (0); + *mhd_ret = reply_refresh_reveal_mismatch (connection, + &rc_expected); + return GNUNET_DB_STATUS_HARD_ERROR; + } + } /* end of checking "rc_expected" */ + + /* check amounts add up! */ + { + struct TALER_Amount refresh_cost; + + refresh_cost = melt.melt_fee; + for (unsigned int i = 0; inum_fresh_coins; i++) + { + struct TALER_Amount fee_withdraw; + struct TALER_Amount value; + struct TALER_Amount total; + + TALER_amount_ntoh (&fee_withdraw, + &rctx->dkis[i]->issue.properties.fee_withdraw); + TALER_amount_ntoh (&value, + &rctx->dkis[i]->issue.properties.value); + if ( (GNUNET_OK != + TALER_amount_add (&total, + &fee_withdraw, + &value)) || + (GNUNET_OK != + TALER_amount_add (&refresh_cost, + &refresh_cost, + &total)) ) + { + GNUNET_break_op (0); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_REVEAL_COST_CALCULATION_OVERFLOW, + "failed to add up refresh costs"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + } + if (0 < TALER_amount_cmp (&refresh_cost, + &melt.session.amount_with_fee)) + { + GNUNET_break_op (0); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_REVEAL_AMOUNT_INSUFFICIENT, + "melted coin value is insufficient to cover cost of operation"); + return GNUNET_DB_STATUS_HARD_ERROR; + } + } + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; +} + + +/** + * Persist result of a "/refresh/reveal". + * + * @param cls closure of type `struct RevealContext` + * @param connection MHD request which triggered the transaction + * @param session database session to use + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!) + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +refresh_reveal_persist (void *cls, + struct MHD_Connection *connection, + struct TALER_EXCHANGEDB_Session *session, + int *mhd_ret) +{ + struct RevealContext *rctx = cls; + enum GNUNET_DB_QueryStatus qs; + + /* Persist operation result in DB */ + { + struct TALER_EXCHANGEDB_RefreshRevealedCoin rrcs[rctx->num_fresh_coins]; + + for (unsigned int i = 0; inum_fresh_coins; i++) + { + struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i]; + + rrc->denom_pub = rctx->dkis[i]->denom_pub; + rrc->orig_coin_link_sig = rctx->link_sigs[i]; + rrc->coin_ev = rctx->rcds[i].coin_ev; + rrc->coin_ev_size = rctx->rcds[i].coin_ev_size; + rrc->coin_sig = rctx->ev_sigs[i]; + } + qs = TEH_plugin->insert_refresh_reveal (TEH_plugin->cls, + session, + &rctx->rc, + rctx->num_fresh_coins, + rrcs, + TALER_CNC_KAPPA - 1, + rctx->transfer_privs, + &rctx->gamma_tp); + } + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_REVEAL_DB_COMMIT_ERROR, + "failed to persist reveal data"); + } + return qs; +} + + +/** + * Resolve denomination hashes using the @a key_state + * + * @param key_state the key state + * @param connection the MHD connection to handle + * @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 coin_evs envelopes of gamma-selected coins to be signed + * @return MHD result code + */ +static int +resolve_refresh_reveal_denominations (struct TEH_KS_StateHandle *key_state, + struct MHD_Connection *connection, + struct RevealContext *rctx, + const json_t *link_sigs_json, + const json_t *new_denoms_h_json, + const json_t *coin_evs) +{ + unsigned int num_fresh_coins = json_array_size (new_denoms_h_json); + const struct + TALER_EXCHANGEDB_DenominationKeyIssueInformation *dkis[num_fresh_coins]; + struct GNUNET_HashCode dki_h[num_fresh_coins]; + struct TALER_RefreshCoinData rcds[num_fresh_coins]; + struct TALER_CoinSpendSignatureP link_sigs[num_fresh_coins]; + struct TALER_EXCHANGEDB_RefreshMelt melt; + int res; + + /* Parse denomination key hashes */ + for (unsigned int i = 0; idenom_priv.rsa_private_key); + } + + /* Parse coin envelopes */ + for (unsigned int i = 0; icoin_ev, + &rcd->coin_ev_size), + GNUNET_JSON_spec_end () + }; + + res = TALER_MHD_parse_json_array (connection, + coin_evs, + spec, + i, + -1); + if (GNUNET_OK != res) + { + for (unsigned int j = 0; jdk = &dkis[i]->denom_pub; + } + + /* lookup old_coin_pub in database */ + { + enum GNUNET_DB_QueryStatus qs; + + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + (qs = TEH_plugin->get_melt (TEH_plugin->cls, + NULL, + &rctx->rc, + &melt))) + { + switch (qs) + { + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + res = TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_REFRESH_REVEAL_SESSION_UNKNOWN, + "rc"); + break; + case GNUNET_DB_STATUS_HARD_ERROR: + res = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_REVEAL_DB_FETCH_SESSION_ERROR, + "failed to fetch session data"); + break; + case GNUNET_DB_STATUS_SOFT_ERROR: + default: + GNUNET_break (0); /* should be impossible */ + res = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_INTERNAL_INVARIANT_FAILURE, + "assertion failed"); + break; + } + goto cleanup; + } + } + /* Parse link signatures array */ + for (unsigned int i = 0; igamma_tp; + GNUNET_CRYPTO_hash (rcds[i].coin_ev, + rcds[i].coin_ev_size, + &ldp.coin_envelope_hash); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK, + &ldp.purpose, + &link_sigs[i].eddsa_signature, + &melt.session.coin.coin_pub. + eddsa_pub)) + { + GNUNET_break_op (0); + res = TALER_MHD_reply_with_error (connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_REFRESH_REVEAL_LINK_SIGNATURE_INVALID, + "link_sig"); + goto cleanup; + } + } + } + + rctx->num_fresh_coins = num_fresh_coins; + rctx->rcds = rcds; + rctx->dkis = dkis; + rctx->link_sigs = link_sigs; + + /* sign _early_ (optimistic!) to keep out of transaction scope! */ + rctx->ev_sigs = GNUNET_new_array (rctx->num_fresh_coins, + struct TALER_DenominationSignature); + for (unsigned int i = 0; inum_fresh_coins; i++) + { + rctx->ev_sigs[i].rsa_signature + = GNUNET_CRYPTO_rsa_sign_blinded ( + rctx->dkis[i]->denom_priv.rsa_private_key, + rctx->rcds[i].coin_ev, + rctx->rcds[i].coin_ev_size); + if (NULL == rctx->ev_sigs[i].rsa_signature) + { + GNUNET_break (0); + res = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_REVEAL_SIGNING_ERROR, + "internal signing error"); + goto cleanup; + } + } + + /* We try the three transactions a few times, as theoretically + the pre-check might be satisfied by a concurrent transaction + voiding our final commit due to uniqueness violation; naturally, + on hard errors we exit immediately */ + for (unsigned int retries = 0; retries < MAX_REVEAL_RETRIES; retries++) + { + /* do transactional work */ + rctx->preflight_ok = GNUNET_NO; + if ( (GNUNET_OK == + TEH_DB_run_transaction (connection, + "reveal pre-check", + &res, + &refresh_reveal_preflight, + rctx)) && + (GNUNET_YES == rctx->preflight_ok) ) + { + /* Generate final (positive) response */ + GNUNET_assert (NULL != rctx->ev_sigs); + res = reply_refresh_reveal_success (connection, + num_fresh_coins, + rctx->ev_sigs); + GNUNET_break (MHD_NO != res); + goto cleanup; /* aka 'break' */ + } + if (GNUNET_SYSERR == rctx->preflight_ok) + { + GNUNET_break (0); + goto cleanup; /* aka 'break' */ + } + if (GNUNET_OK != + TEH_DB_run_transaction (connection, + "run reveal", + &res, + &refresh_reveal_transaction, + rctx)) + { + /* reveal failed, too bad */ + GNUNET_break_op (0); + goto cleanup; /* aka 'break' */ + } + if (GNUNET_OK == + TEH_DB_run_transaction (connection, + "persist reveal", + &res, + &refresh_reveal_persist, + rctx)) + { + /* Generate final (positive) response */ + GNUNET_assert (NULL != rctx->ev_sigs); + res = reply_refresh_reveal_success (connection, + num_fresh_coins, + rctx->ev_sigs); + break; + } + } /* end for (retries...) */ + +cleanup: + GNUNET_break (MHD_NO != res); + /* free resources */ + if (NULL != rctx->ev_sigs) + { + for (unsigned int i = 0; iev_sigs[i].rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (rctx->ev_sigs[i].rsa_signature); + GNUNET_free (rctx->ev_sigs); + rctx->ev_sigs = NULL; /* just to be safe... */ + } + for (unsigned int i = 0; i= MAX_FRESH_COINS) || + (0 == num_fresh_coins) ) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE, + "new_denoms_h"); + + } + if (json_array_size (new_denoms_h_json) != + json_array_size (coin_evs)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISSMATCH, + "new_denoms/coin_evs"); + } + if (json_array_size (new_denoms_h_json) != + json_array_size (link_sigs_json)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISSMATCH, + "new_denoms/link_sigs"); + } + + /* Parse transfer private keys array */ + for (unsigned int i = 0; itransfer_privs[i]), + GNUNET_JSON_spec_end () + }; + int res; + + res = TALER_MHD_parse_json_array (connection, + tp_json, + trans_spec, + i, + -1); + if (GNUNET_OK != res) + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + } + + { + struct TEH_KS_StateHandle *key_state; + int ret; + + key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ()); + if (NULL == key_state) + { + TALER_LOG_ERROR ("Lacking keys to operate\n"); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_REFRESH_REVEAL_KEYS_MISSING, + "exchange lacks keys"); + } + ret = resolve_refresh_reveal_denominations (key_state, + connection, + rctx, + link_sigs_json, + new_denoms_h_json, + coin_evs); + TEH_KS_release (key_state); + return ret; + } +} + + +/** + * Handle a "/refreshes/$RCH/reveal" request. This time, the client reveals the + * private transfer keys except for the cut-and-choose value returned from + * "/coins/$COIN_PUB/melt". This function parses the revealed keys and secrets and + * ultimately passes everything to #resolve_refresh_reveal_denominations() + * which will verify that the revealed information is valid then runs the + * transaction in #refresh_reveal_transaction() and finally returns the signed + * refreshed coins. + * + * @param rh context of the handler + * @param coin_pub public key of the coin + * @param root uploaded JSON data + * @param args array of additional options (length: 2, session hash and the string "reveal") + * @return MHD result code + */ +int +TEH_REFRESH_handler_reveal (const struct TEH_RequestHandler *rh, + struct MHD_Connection *connection, + const json_t *root, + const char *const args[2]) +{ + int res; + json_t *coin_evs; + json_t *transfer_privs; + json_t *link_sigs; + json_t *new_denoms_h; + struct RevealContext rctx; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("transfer_pub", &rctx.gamma_tp), + GNUNET_JSON_spec_json ("transfer_privs", &transfer_privs), + GNUNET_JSON_spec_json ("link_sigs", &link_sigs), + GNUNET_JSON_spec_json ("coin_evs", &coin_evs), + GNUNET_JSON_spec_json ("new_denoms_h", &new_denoms_h), + GNUNET_JSON_spec_end () + }; + + (void) rh; + memset (&rctx, + 0, + sizeof (rctx)); + + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &rctx.rc, + sizeof (rctx.rc))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_REFRESHES_INVALID_RCH, + "refresh commitment hash malformed"); + } + if (0 != strcmp (args[1], + "reveal")) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_OPERATION_INVALID, + "expected 'reveal' operation"); + } + res = TALER_MHD_parse_json_data (connection, + root, + spec); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } + + /* Check we got enough transfer private keys */ + /* Note we do +1 as 1 row (cut-and-choose!) is missing! */ + if (TALER_CNC_KAPPA != json_array_size (transfer_privs) + 1) + { + GNUNET_JSON_parse_free (spec); + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_REFRESH_REVEAL_CNC_TRANSFER_ARRAY_SIZE_INVALID, + "transfer_privs"); + } + res = handle_refresh_reveal_json (connection, + &rctx, + transfer_privs, + link_sigs, + new_denoms_h, + coin_evs); + GNUNET_JSON_parse_free (spec); + return res; +} + + +/* end of taler-exchange-httpd_refresh_reveal.c */ -- cgit v1.2.3