/*
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-mint-httpd_refresh.c
* @brief Handle /refresh/ requests
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
*/
#include "platform.h"
#include
#include
#include
#include "taler_mintdb_plugin.h"
#include "taler_signatures.h"
#include "taler_util.h"
#include "taler-mint-httpd_parsing.h"
#include "taler-mint-httpd_mhd.h"
#include "taler-mint-httpd_refresh.h"
#include "taler-mint-httpd_responses.h"
#include "taler-mint-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 @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 @commit_coin array
* @param coin_public_infos array with @a coin_count entries about the coins
* @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_client_sig signature of the client over this commitment
* @param commit_coin 2d array of coin commitments (what the mint is to sign
* once the "/refres/reveal" of cut and choose is done)
* @param commit_link 2d array of coin link commitments (what the mint 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,
struct TALER_CoinPublicInfo *coin_public_infos,
const struct TMH_DB_MeltDetails *coin_melt_details,
const struct GNUNET_HashCode *session_hash,
struct TALER_MINTDB_RefreshCommitCoin *const* commit_coin,
struct TALER_MINTDB_RefreshCommitLinkP *const* commit_link)
{
unsigned int i;
struct TMH_KS_StateHandle *key_state;
struct TALER_DenominationKeyValidityPS *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_MINT_CURRENCY,
&total_cost));
key_state = TMH_KS_acquire ();
for (i=0;iissue;
TALER_amount_ntoh (&value,
&dki->value);
TALER_amount_ntoh (&fee_withdraw,
&dki->fee_withdraw);
if ( (GNUNET_OK !=
TALER_amount_add (&cost,
&value,
&fee_withdraw)) ||
(GNUNET_OK !=
TALER_amount_add (&total_cost,
&cost,
&total_cost)) )
{
TMH_KS_release (key_state);
return TMH_RESPONSE_reply_internal_error (connection,
"cost calculation failure");
}
}
GNUNET_assert (GNUNET_OK ==
TALER_amount_get_zero (TMH_MINT_CURRENCY,
&total_melt));
for (i=0;iissue;
TALER_amount_ntoh (&fee_melt,
&dki->fee_refresh);
if (GNUNET_OK !=
TALER_amount_subtract (&melt,
&coin_melt_details->melt_amount_with_fee,
&fee_melt))
{
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))
{
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))
{
/* 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_public_infos,
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 r_public_info[OUT] set to the coin's public information
* @param r_melt_detail[OUT] 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 TALER_CoinPublicInfo *r_public_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_public_info->coin_pub),
TMH_PARSE_MEMBER_RSA_SIGNATURE ("denom_sig", &sig.rsa_signature),
TMH_PARSE_MEMBER_RSA_PUBLIC_KEY ("denom_pub", &pk.rsa_public_key),
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)
return ret;
/* check mint signature on the coin */
r_public_info->denom_sig = sig;
r_public_info->denom_pub = pk;
if (GNUNET_OK !=
TALER_test_coin_valid (r_public_info))
{
TMH_PARSE_release_data (spec);
r_public_info->denom_sig.rsa_signature = NULL;
r_public_info->denom_pub.rsa_public_key = NULL;
return (MHD_YES ==
TMH_RESPONSE_reply_json_pack (connection,
MHD_HTTP_NOT_FOUND,
"{s:s}",
"error", "coin invalid"))
? GNUNET_NO : GNUNET_SYSERR;
}
r_melt_detail->melt_sig = melt_sig;
r_melt_detail->melt_amount_with_fee = amount;
TMH_PARSE_release_data (spec);
return GNUNET_OK;
}
/**
* Verify that the signature shows that this coin is to be melted into
* the given @a session_pub 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 r_public_info the coin's public information
* @param r_melt_detail 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
verify_coin_public_info (struct MHD_Connection *connection,
const struct GNUNET_HashCode *session_hash,
const struct TALER_CoinPublicInfo *r_public_info,
const struct TMH_DB_MeltDetails *r_melt_detail)
{
struct TALER_RefreshMeltCoinAffirmationPS body;
struct TMH_KS_StateHandle *key_state;
struct TALER_MINTDB_DenominationKeyIssueInformation *dki;
struct TALER_Amount 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,
&r_melt_detail->melt_amount_with_fee);
body.coin_pub = r_public_info->coin_pub;
if (GNUNET_OK !=
GNUNET_CRYPTO_ecdsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT,
&body.purpose,
&r_melt_detail->melt_sig.ecdsa_signature,
&r_public_info->coin_pub.ecdsa_pub))
{
if (MHD_YES !=
TMH_RESPONSE_reply_json_pack (connection,
MHD_HTTP_UNAUTHORIZED,
"{s:s}",
"error", "signature invalid"))
return GNUNET_SYSERR;
return GNUNET_NO;
}
key_state = TMH_KS_acquire ();
dki = TMH_KS_denomination_key_lookup (key_state,
&r_public_info->denom_pub);
if (NULL == dki)
{
TMH_KS_release (key_state);
TALER_LOG_WARNING ("Unknown denomination key in /refresh/melt request\n");
return TMH_RESPONSE_reply_arg_invalid (connection,
"denom_pub");
}
/* FIXME: need to check if denomination key is still
valid for issuing! (#3634) */
TALER_amount_ntoh (&fee_refresh,
&dki->issue.fee_refresh);
if (TALER_amount_cmp (&fee_refresh,
&r_melt_detail->melt_amount_with_fee) < 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);
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_MINTDB_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_CRYPTO_hash_context_abort (hash_context);
free_commit_coins (commit_coin,
TALER_CNC_KAPPA,
num_newcoins);
return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
}
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_CRYPTO_hash_context_abort (hash_context);
free_commit_coins (commit_coin,
TALER_CNC_KAPPA,
num_newcoins);
return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
}
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);
}
}
for (i = 0; i < TALER_CNC_KAPPA; i++)
{
commit_link[i] = GNUNET_malloc (num_oldcoins *
sizeof (struct TALER_MINTDB_RefreshCommitLinkP));
for (j = 0; j < num_oldcoins; j++)
{
struct TALER_MINTDB_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 (GNUNET_SYSERR != res);
GNUNET_CRYPTO_hash_context_abort (hash_context);
free_commit_coins (commit_coin,
TALER_CNC_KAPPA,
num_newcoins);
free_commit_links (commit_link,
TALER_CNC_KAPPA,
num_oldcoins);
return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
}
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 GNUNET_HashCode));
if (GNUNET_OK != res)
{
GNUNET_break (GNUNET_SYSERR != res);
GNUNET_CRYPTO_hash_context_abort (hash_context);
free_commit_coins (commit_coin,
TALER_CNC_KAPPA,
num_newcoins);
free_commit_links (commit_link,
TALER_CNC_KAPPA,
num_oldcoins);
return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
}
GNUNET_CRYPTO_hash_context_read (hash_context,
rcl,
sizeof (struct TALER_MINTDB_RefreshCommitLinkP));
}
}
GNUNET_CRYPTO_hash_context_finish (hash_context,
&session_hash);
for (i=0;i