/* This file is part of TALER Copyright (C) 2014, 2015 GNUnet e.V. 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, If not, see */ /** * @file taler-exchange-httpd_refresh.c * @brief Handle /refresh/ requests * @author Florian Dold * @author Benedikt Mueller * @author Christian Grothoff */ #include "platform.h" #include #include #include #include "taler-exchange-httpd_parsing.h" #include "taler-exchange-httpd_mhd.h" #include "taler-exchange-httpd_refresh.h" #include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_keystate.h" /** * Handle a "/refresh/melt" request after the main JSON parsing has happened. * We now need to validate the coins being melted and the session signature * and then hand things of to execute the melt operation. * * @param connection the MHD connection to handle * @param num_new_denoms number of coins to be created, size of y-dimension of @a commit_link array * @param denom_pubs array of @a num_new_denoms keys * @param coin_count number of coins to be melted, size of y-dimension of @a commit_coin array * @param coin_melt_details array with @a coin_count entries with melting details * @param session_hash hash over the data that the client commits to * @param commit_coin 2d array of coin commitments (what the exchange is to sign * once the "/refres/reveal" of cut and choose is done) * @param commit_link 2d array of coin link commitments (what the exchange is * to return via "/refresh/link" to enable linkage in the * future) * @return MHD result code */ static int handle_refresh_melt_binary (struct MHD_Connection *connection, unsigned int num_new_denoms, const struct TALER_DenominationPublicKey *denom_pubs, unsigned int coin_count, const struct TMH_DB_MeltDetails *coin_melt_details, const struct GNUNET_HashCode *session_hash, struct TALER_EXCHANGEDB_RefreshCommitCoin *const* commit_coin, struct TALER_RefreshCommitLinkP *const* commit_link) { unsigned int i; struct TMH_KS_StateHandle *key_state; struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dk; struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki; struct TALER_Amount cost; struct TALER_Amount total_cost; struct TALER_Amount melt; struct TALER_Amount value; struct TALER_Amount fee_withdraw; struct TALER_Amount fee_melt; struct TALER_Amount total_melt; GNUNET_assert (GNUNET_OK == TALER_amount_get_zero (TMH_exchange_currency_string, &total_cost)); key_state = TMH_KS_acquire (); for (i=0;iissue; TALER_amount_ntoh (&value, &dki->properties.value); TALER_amount_ntoh (&fee_withdraw, &dki->properties.fee_withdraw); if ( (GNUNET_OK != TALER_amount_add (&cost, &value, &fee_withdraw)) || (GNUNET_OK != TALER_amount_add (&total_cost, &cost, &total_cost)) ) { GNUNET_break_op (0); TMH_KS_release (key_state); return TMH_RESPONSE_reply_internal_error (connection, "cost calculation failure"); } } GNUNET_assert (GNUNET_OK == TALER_amount_get_zero (TMH_exchange_currency_string, &total_melt)); for (i=0;iissue; TALER_amount_ntoh (&fee_melt, &dki->properties.fee_refresh); if (GNUNET_OK != TALER_amount_subtract (&melt, &coin_melt_details->melt_amount_with_fee, &fee_melt)) { GNUNET_break_op (0); TMH_KS_release (key_state); return TMH_RESPONSE_reply_external_error (connection, "Melt contribution below melting fee"); } if (GNUNET_OK != TALER_amount_add (&total_melt, &melt, &total_melt)) { GNUNET_break_op (0); TMH_KS_release (key_state); return TMH_RESPONSE_reply_internal_error (connection, "balance calculation failure"); } } TMH_KS_release (key_state); if (0 != TALER_amount_cmp (&total_cost, &total_melt)) { GNUNET_break_op (0); /* We require total value of coins being melted and total value of coins being generated to match! */ return TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_BAD_REQUEST, "{s:s}", "error", "value mismatch"); } return TMH_DB_execute_refresh_melt (connection, session_hash, num_new_denoms, denom_pubs, coin_count, coin_melt_details, commit_coin, commit_link); } /** * Extract public coin information from a JSON object. * * @param connection the connection to send error responses to * @param coin_info the JSON object to extract the coin info from * @param[out] r_melt_detail set to details about the coin's melting permission (if valid) * @return #GNUNET_YES if coin public info in JSON was valid * #GNUNET_NO JSON was invalid, response was generated * #GNUNET_SYSERR on internal error */ static int get_coin_public_info (struct MHD_Connection *connection, json_t *coin_info, struct TMH_DB_MeltDetails *r_melt_detail) { int ret; struct TALER_CoinSpendSignatureP melt_sig; struct TALER_DenominationSignature sig; struct TALER_DenominationPublicKey pk; struct TALER_Amount amount; struct TMH_PARSE_FieldSpecification spec[] = { TMH_PARSE_member_fixed ("coin_pub", &r_melt_detail->coin_info.coin_pub), TMH_PARSE_member_denomination_signature ("denom_sig", &sig), TMH_PARSE_member_denomination_public_key ("denom_pub", &pk), TMH_PARSE_member_fixed ("confirm_sig", &melt_sig), TMH_PARSE_member_amount ("value_with_fee", &amount), TMH_PARSE_MEMBER_END }; ret = TMH_PARSE_json_data (connection, coin_info, spec); if (GNUNET_OK != ret) { GNUNET_break_op (0); return ret; } /* check exchange signature on the coin */ r_melt_detail->coin_info.denom_sig = sig; r_melt_detail->coin_info.denom_pub = pk; if (GNUNET_OK != TALER_test_coin_valid (&r_melt_detail->coin_info)) { GNUNET_break_op (0); TMH_PARSE_release_data (spec); r_melt_detail->coin_info.denom_sig.rsa_signature = NULL; r_melt_detail->coin_info.denom_pub.rsa_public_key = NULL; return (MHD_YES == TMH_RESPONSE_reply_signature_invalid (connection, "denom_sig")) ? GNUNET_NO : GNUNET_SYSERR; } r_melt_detail->melt_sig = melt_sig; r_melt_detail->melt_amount_with_fee = amount; return GNUNET_OK; } /** * Verify that the signature shows that this coin is to be melted into * the given @a session_hash melting session, and that this is a valid * coin (we know the denomination key and the signature on it is * valid). Essentially, this does all of the per-coin checks that can * be done before the transaction starts. * * @param connection the connection to send error responses to * @param session_hash hash over refresh session the coin is melted into * @param[in,out] melt_detail details about the coin's melting permission, * the `melt_fee` is updated * @return #GNUNET_YES if coin public info in JSON was valid * #GNUNET_NO JSON was invalid, response was generated * #GNUNET_SYSERR on internal error */ static int verify_coin_public_info (struct MHD_Connection *connection, const struct GNUNET_HashCode *session_hash, struct TMH_DB_MeltDetails *melt_detail) { struct TALER_RefreshMeltCoinAffirmationPS body; struct TMH_KS_StateHandle *key_state; struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; struct TALER_Amount fee_refresh; key_state = TMH_KS_acquire (); dki = TMH_KS_denomination_key_lookup (key_state, &melt_detail->coin_info.denom_pub, TMH_KS_DKU_DEPOSIT); if (NULL == dki) { TMH_KS_release (key_state); TALER_LOG_WARNING ("Unknown denomination key in /refresh/melt request\n"); return TMH_RESPONSE_reply_arg_unknown (connection, "denom_pub"); } TALER_amount_ntoh (&fee_refresh, &dki->issue.properties.fee_refresh); melt_detail->melt_fee = fee_refresh; body.purpose.size = htonl (sizeof (struct TALER_RefreshMeltCoinAffirmationPS)); body.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); body.session_hash = *session_hash; TALER_amount_hton (&body.amount_with_fee, &melt_detail->melt_amount_with_fee); TALER_amount_hton (&body.melt_fee, &fee_refresh); body.coin_pub = melt_detail->coin_info.coin_pub; if (TALER_amount_cmp (&fee_refresh, &melt_detail->melt_amount_with_fee) > 0) { GNUNET_break_op (0); TMH_KS_release (key_state); return (MHD_YES == TMH_RESPONSE_reply_external_error (connection, "melt amount smaller than melting fee")) ? GNUNET_NO : GNUNET_SYSERR; } TMH_KS_release (key_state); if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT, &body.purpose, &melt_detail->melt_sig.eddsa_signature, &melt_detail->coin_info.coin_pub.eddsa_pub)) { GNUNET_break_op (0); if (MHD_YES != TMH_RESPONSE_reply_signature_invalid (connection, "confirm_sig")) return GNUNET_SYSERR; return GNUNET_NO; } return GNUNET_OK; } /** * Release memory from the @a commit_coin array. * * @param commit_coin array to release * @param kappa size of 1st dimension * @param num_new_coins size of 2nd dimension */ static void free_commit_coins (struct TALER_EXCHANGEDB_RefreshCommitCoin **commit_coin, unsigned int kappa, unsigned int num_new_coins) { unsigned int i; unsigned int j; for (i=0;icoin_ev, &rcc->coin_ev_size); if (GNUNET_OK != res) { GNUNET_break_op (0); res = (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; goto cleanup; } GNUNET_CRYPTO_hash_context_read (hash_context, rcc->coin_ev, rcc->coin_ev_size); res = TMH_PARSE_navigate_json (connection, link_encs, TMH_PARSE_JNC_INDEX, (int) i, TMH_PARSE_JNC_INDEX, (int) j, TMH_PARSE_JNC_RET_DATA_VAR, &link_enc, &link_enc_size); if (GNUNET_OK != res) { GNUNET_break_op (0); res = (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; goto cleanup; } rcc->refresh_link = TALER_refresh_link_encrypted_decode (link_enc, link_enc_size); GNUNET_CRYPTO_hash_context_read (hash_context, link_enc, link_enc_size); GNUNET_free (link_enc); } } for (i = 0; i < TALER_CNC_KAPPA; i++) { commit_link[i] = GNUNET_malloc (num_oldcoins * sizeof (struct TALER_RefreshCommitLinkP)); for (j = 0; j < num_oldcoins; j++) { struct TALER_RefreshCommitLinkP *rcl = &commit_link[i][j]; res = TMH_PARSE_navigate_json (connection, transfer_pubs, TMH_PARSE_JNC_INDEX, (int) i, TMH_PARSE_JNC_INDEX, (int) j, TMH_PARSE_JNC_RET_DATA, &rcl->transfer_pub, sizeof (struct TALER_TransferPublicKeyP)); if (GNUNET_OK != res) { GNUNET_break_op (0); res = (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; goto cleanup; } res = TMH_PARSE_navigate_json (connection, secret_encs, TMH_PARSE_JNC_INDEX, (int) i, TMH_PARSE_JNC_INDEX, (int) j, TMH_PARSE_JNC_RET_DATA, &rcl->shared_secret_enc, sizeof (struct TALER_EncryptedLinkSecretP)); if (GNUNET_OK != res) { GNUNET_break_op (0); res = (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; goto cleanup; } GNUNET_CRYPTO_hash_context_read (hash_context, rcl, sizeof (struct TALER_RefreshCommitLinkP)); } } GNUNET_CRYPTO_hash_context_finish (hash_context, &session_hash); hash_context = NULL; for (i=0;i