split httpd_refresh.c into refresh_melt, refresh_link and refresh_reveal

This commit is contained in:
Christian Grothoff 2017-06-19 21:04:49 +02:00
parent 703c54a279
commit f8e62141f2
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
12 changed files with 1688 additions and 1603 deletions

View File

@ -50,7 +50,9 @@ taler_exchange_httpd_SOURCES = \
taler-exchange-httpd_mhd.c taler-exchange-httpd_mhd.h \
taler-exchange-httpd_parsing.c taler-exchange-httpd_parsing.h \
taler-exchange-httpd_payback.c taler-exchange-httpd_payback.h \
taler-exchange-httpd_refresh.c taler-exchange-httpd_refresh.h \
taler-exchange-httpd_refresh_link.c taler-exchange-httpd_refresh_link.h \
taler-exchange-httpd_refresh_melt.c taler-exchange-httpd_refresh_melt.h \
taler-exchange-httpd_refresh_reveal.c taler-exchange-httpd_refresh_reveal.h \
taler-exchange-httpd_refund.c taler-exchange-httpd_refund.h \
taler-exchange-httpd_reserve_status.c taler-exchange-httpd_reserve_status.h \
taler-exchange-httpd_reserve_withdraw.c taler-exchange-httpd_reserve_withdraw.h \

View File

@ -34,11 +34,13 @@
#include "taler-exchange-httpd_reserve_status.h"
#include "taler-exchange-httpd_reserve_withdraw.h"
#include "taler-exchange-httpd_payback.h"
#include "taler-exchange-httpd_wire.h"
#include "taler-exchange-httpd_refresh.h"
#include "taler-exchange-httpd_refresh_link.h"
#include "taler-exchange-httpd_refresh_melt.h"
#include "taler-exchange-httpd_refresh_reveal.h"
#include "taler-exchange-httpd_track_transfer.h"
#include "taler-exchange-httpd_track_transaction.h"
#include "taler-exchange-httpd_keystate.h"
#include "taler-exchange-httpd_wire.h"
#if HAVE_DEVELOPER
#include "taler-exchange-httpd_test.h"
#endif

View File

@ -32,85 +32,6 @@
*/
#define MAX_TRANSACTION_COMMIT_RETRIES 3
/**
* Code to begin a transaction, must be inline as we define a block
* that ends with #COMMIT_TRANSACTION() within which we perform a number
* of retries. Note that this code may call "return" internally, so
* it must be called within a function where any cleanup will be done
* by the caller. Furthermore, the function's return value must
* match that of a #TEH_RESPONSE_reply_internal_db_error() status code.
*
* @param session session handle
* @param connection connection handle
*/
#define START_TRANSACTION(session,connection) \
{ /* start new scope, will be ended by COMMIT_TRANSACTION() */\
unsigned int transaction_retries = 0; \
enum GNUNET_DB_QueryStatus transaction_commit_result; \
transaction_start_label: /* we will use goto for retries */ \
if (GNUNET_OK != \
TEH_plugin->start (TEH_plugin->cls, \
session)) \
{ \
GNUNET_break (0); \
return TEH_RESPONSE_reply_internal_db_error (connection, \
TALER_EC_DB_START_FAILED); \
}
/**
* Code to conclude a transaction, dual to #START_TRANSACTION(). Note
* that this code may call "return" internally, so it must be called
* within a function where any cleanup will be done by the caller.
* Furthermore, the function's return value must match that of a
* #TEH_RESPONSE_reply_internal_db_error() status code.
*
* @param session session handle
* @param connection connection handle
*/
#define COMMIT_TRANSACTION(session,connection) \
transaction_commit_result = \
TEH_plugin->commit (TEH_plugin->cls, \
session); \
if (GNUNET_DB_STATUS_HARD_ERROR == transaction_commit_result) \
{ \
TALER_LOG_WARNING ("Transaction commit failed in %s\n", __FUNCTION__); \
return TEH_RESPONSE_reply_commit_error (connection, \
TALER_EC_DB_COMMIT_FAILED_HARD); \
} \
if (GNUNET_DB_STATUS_SOFT_ERROR == transaction_commit_result) \
{ \
TALER_LOG_WARNING ("Transaction commit failed in %s\n", __FUNCTION__); \
if (transaction_retries++ <= MAX_TRANSACTION_COMMIT_RETRIES) \
goto transaction_start_label; \
TALER_LOG_WARNING ("Transaction commit failed %u times in %s\n", \
transaction_retries, \
__FUNCTION__); \
return TEH_RESPONSE_reply_commit_error (connection, \
TALER_EC_DB_COMMIT_FAILED_ON_RETRY); \
} \
} /* end of scope opened by BEGIN_TRANSACTION */
/**
* Code to include to retry a transaction, must only be used in between
* #START_TRANSACTION and #COMMIT_TRANSACTION.
*
* @param session session handle
* @param connection connection handle
*/
#define RETRY_TRANSACTION(session,connection) \
do { \
TEH_plugin->rollback (TEH_plugin->cls, \
session); \
if (transaction_retries++ <= MAX_TRANSACTION_COMMIT_RETRIES) \
goto transaction_start_label; \
TALER_LOG_WARNING ("Transaction commit failed %u times in %s\n", \
transaction_retries, \
__FUNCTION__); \
return TEH_RESPONSE_reply_commit_error (connection, \
TALER_EC_DB_COMMIT_FAILED_ON_RETRY); \
} while (0)
/**
* Run a database transaction for @a connection.
@ -279,870 +200,4 @@ TEH_DB_calculate_transaction_list_totals (struct TALER_EXCHANGEDB_TransactionLis
}
/**
* Parse coin melt requests from a JSON object and write them to
* the database.
*
* @param connection the connection to send errors to
* @param session the database connection
* @param key_state the exchange's key state
* @param session_hash hash identifying the refresh session
* @param coin_details details about the coin being melted
* @param[out] meltp on success, set to melt details
* @return #GNUNET_OK on success,
* #GNUNET_NO if an error message was generated,
* #GNUNET_SYSERR on internal errors (no response generated)
*/
static int
refresh_check_melt (struct MHD_Connection *connection,
struct TALER_EXCHANGEDB_Session *session,
const struct TEH_KS_StateHandle *key_state,
const struct GNUNET_HashCode *session_hash,
const struct TEH_DB_MeltDetails *coin_details,
struct TALER_EXCHANGEDB_RefreshMelt *meltp)
{
struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dk;
struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
struct TALER_EXCHANGEDB_TransactionList *tl;
struct TALER_Amount coin_value;
struct TALER_Amount coin_residual;
struct TALER_Amount spent;
int res;
enum GNUNET_DB_QueryStatus qs;
dk = TEH_KS_denomination_key_lookup (key_state,
&coin_details->coin_info.denom_pub,
TEH_KS_DKU_DEPOSIT);
if (NULL == dk)
return (MHD_YES ==
TEH_RESPONSE_reply_internal_error (connection,
TALER_EC_REFRESH_MELT_DB_DENOMINATION_KEY_NOT_FOUND,
"denomination key no longer available while executing transaction"))
? GNUNET_NO : GNUNET_SYSERR;
dki = &dk->issue;
TALER_amount_ntoh (&coin_value,
&dki->properties.value);
/* fee for THIS transaction; the melt amount includes the fee! */
spent = coin_details->melt_amount_with_fee;
/* add historic transaction costs of this coin */
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
session,
&coin_details->coin_info.coin_pub,
&tl);
(void) qs; /* FIXME #5010 */
if (GNUNET_OK !=
TEH_DB_calculate_transaction_list_totals (tl,
&spent,
&spent))
{
GNUNET_break (0);
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
tl);
return (MHD_YES ==
TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_MELT_COIN_HISTORY_COMPUTATION_FAILED))
? GNUNET_NO : GNUNET_SYSERR;
}
/* Refuse to refresh when the coin's value is insufficient
for the cost of all transactions. */
if (TALER_amount_cmp (&coin_value,
&spent) < 0)
{
GNUNET_assert (GNUNET_SYSERR !=
TALER_amount_subtract (&coin_residual,
&spent,
&coin_details->melt_amount_with_fee));
res = (MHD_YES ==
TEH_RESPONSE_reply_refresh_melt_insufficient_funds (connection,
&coin_details->coin_info.coin_pub,
coin_value,
tl,
coin_details->melt_amount_with_fee,
coin_residual))
? GNUNET_NO : GNUNET_SYSERR;
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
tl);
return res;
}
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
tl);
meltp->coin = coin_details->coin_info;
meltp->coin_sig = coin_details->melt_sig;
meltp->session_hash = *session_hash;
meltp->amount_with_fee = coin_details->melt_amount_with_fee;
meltp->melt_fee = coin_details->melt_fee;
return GNUNET_OK;
}
/**
* Execute a "/refresh/melt". We have been given a list of valid
* coins and a request to melt them into the given
* @a refresh_session_pub. Check that the coins all have the
* required value left and if so, store that they have been
* melted and confirm the melting operation to the client.
*
* @param connection the MHD connection to handle
* @param session_hash hash code of the session the coins are melted into
* @param num_new_denoms number of entries in @a denom_pubs, size of y-dimension of @a commit_coin array
* @param denom_pubs public keys of the coins we want to withdraw in the end
* @param coin_melt_detail signature and (residual) value of the respective coin should be melted
* @param commit_coin 2d array of coin commitments (what the exchange is to sign
* once the "/refres/reveal" of cut and choose is done),
* x-dimension must be #TALER_CNC_KAPPA
* @param transfer_pubs array of transfer public keys (what the exchange is
* to return via "/refresh/link" to enable linkage in the
* future) of length #TALER_CNC_KAPPA
* @return MHD result code
*/
int
TEH_DB_execute_refresh_melt (struct MHD_Connection *connection,
const struct GNUNET_HashCode *session_hash,
unsigned int num_new_denoms,
const struct TALER_DenominationPublicKey *denom_pubs,
const struct TEH_DB_MeltDetails *coin_melt_detail,
struct TALER_EXCHANGEDB_RefreshCommitCoin *const* commit_coin,
const struct TALER_TransferPublicKeyP *transfer_pubs)
{
struct TEH_KS_StateHandle *key_state;
struct TALER_EXCHANGEDB_RefreshSession refresh_session;
struct TALER_EXCHANGEDB_Session *session;
int res;
if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls)))
{
GNUNET_break (0);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_DB_SETUP_FAILED);
}
START_TRANSACTION (session, connection);
res = TEH_plugin->get_refresh_session (TEH_plugin->cls,
session,
session_hash,
&refresh_session);
if (GNUNET_YES == res)
{
TEH_plugin->rollback (TEH_plugin->cls,
session);
res = TEH_RESPONSE_reply_refresh_melt_success (connection,
session_hash,
refresh_session.noreveal_index);
return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
}
if (GNUNET_SYSERR == res)
{
TEH_plugin->rollback (TEH_plugin->cls,
session);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_MELT_DB_FETCH_ERROR);
}
/* store 'global' session data */
refresh_session.num_newcoins = num_new_denoms;
refresh_session.noreveal_index
= GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG,
TALER_CNC_KAPPA);
key_state = TEH_KS_acquire ();
if (GNUNET_OK !=
(res = refresh_check_melt (connection,
session,
key_state,
session_hash,
coin_melt_detail,
&refresh_session.melt)))
{
TEH_KS_release (key_state);
TEH_plugin->rollback (TEH_plugin->cls,
session);
return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
}
TEH_KS_release (key_state);
if (GNUNET_OK !=
(res = TEH_plugin->create_refresh_session (TEH_plugin->cls,
session,
session_hash,
&refresh_session)))
{
TEH_plugin->rollback (TEH_plugin->cls,
session);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_MELT_DB_STORE_SESSION_ERROR);
}
/* store requested new denominations */
if (GNUNET_OK !=
TEH_plugin->insert_refresh_order (TEH_plugin->cls,
session,
session_hash,
num_new_denoms,
denom_pubs))
{
TEH_plugin->rollback (TEH_plugin->cls,
session);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_MELT_DB_STORE_ORDER_ERROR);
}
if (GNUNET_OK !=
TEH_plugin->insert_refresh_commit_coins (TEH_plugin->cls,
session,
session_hash,
num_new_denoms,
commit_coin[refresh_session.noreveal_index]))
{
TEH_plugin->rollback (TEH_plugin->cls,
session);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_MELT_DB_STORE_ORDER_ERROR);
}
if (GNUNET_OK !=
TEH_plugin->insert_refresh_transfer_public_key (TEH_plugin->cls,
session,
session_hash,
&transfer_pubs[refresh_session.noreveal_index]))
{
TEH_plugin->rollback (TEH_plugin->cls,
session);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_MELT_DB_STORE_TRANSFER_ERROR);
}
COMMIT_TRANSACTION (session, connection);
return TEH_RESPONSE_reply_refresh_melt_success (connection,
session_hash,
refresh_session.noreveal_index);
}
/**
* Check if the given @a transfer_privs correspond to an honest
* commitment for the given session.
* Checks that the transfer private keys match their commitments.
* Then derives the shared secret for each #TALER_CNC_KAPPA, and check that they match.
*
* @param connection the MHD connection to handle
* @param session database connection to use
* @param session_hash hash of session to query
* @param off commitment offset to check
* @param transfer_priv private transfer key
* @param melt information about the melted coin
* @param num_newcoins number of newcoins being generated
* @param denom_pubs array of @a num_newcoins keys for the new coins
* @param hash_context hash context to update by hashing in the data
* from this offset
* @return #GNUNET_OK if the committment was honest,
* #GNUNET_NO if there was a problem and we generated an error message
* #GNUNET_SYSERR if we could not even generate an error message
*/
static int
check_commitment (struct MHD_Connection *connection,
struct TALER_EXCHANGEDB_Session *session,
const struct GNUNET_HashCode *session_hash,
unsigned int off,
const struct TALER_TransferPrivateKeyP *transfer_priv,
const struct TALER_EXCHANGEDB_RefreshMelt *melt,
unsigned int num_newcoins,
const struct TALER_DenominationPublicKey *denom_pubs,
struct GNUNET_HashContext *hash_context)
{
struct TALER_TransferSecretP transfer_secret;
unsigned int j;
TALER_link_reveal_transfer_secret (transfer_priv,
&melt->coin.coin_pub,
&transfer_secret);
/* Check that the commitments for all new coins were correct */
for (j = 0; j < num_newcoins; j++)
{
struct TALER_FreshCoinP fc;
struct TALER_CoinSpendPublicKeyP coin_pub;
struct GNUNET_HashCode h_msg;
char *buf;
size_t buf_len;
TALER_setup_fresh_coin (&transfer_secret,
j,
&fc);
GNUNET_CRYPTO_eddsa_key_get_public (&fc.coin_priv.eddsa_priv,
&coin_pub.eddsa_pub);
GNUNET_CRYPTO_hash (&coin_pub,
sizeof (struct TALER_CoinSpendPublicKeyP),
&h_msg);
if (GNUNET_YES !=
GNUNET_CRYPTO_rsa_blind (&h_msg,
&fc.blinding_key.bks,
denom_pubs[j].rsa_public_key,
&buf,
&buf_len))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Blind failed (bad denomination key!?)\n");
return (MHD_YES ==
TEH_RESPONSE_reply_internal_error (connection,
TALER_EC_REFRESH_REVEAL_BLINDING_ERROR,
"Blinding error"))
? GNUNET_NO : GNUNET_SYSERR;
}
GNUNET_CRYPTO_hash_context_read (hash_context,
buf,
buf_len);
GNUNET_free (buf);
}
return GNUNET_OK;
}
/**
* Exchange a coin as part of a refresh operation. Obtains the
* envelope from the database and performs the signing operation.
*
* @param connection the MHD connection to handle
* @param session database connection to use
* @param session_hash hash of session to query
* @param key_state key state to lookup denomination pubs
* @param denom_pub denomination key for the coin to create
* @param commit_coin the coin that was committed
* @param coin_off number of the coin
* @return NULL on error, otherwise signature over the coin
*/
static struct TALER_DenominationSignature
refresh_exchange_coin (struct MHD_Connection *connection,
struct TALER_EXCHANGEDB_Session *session,
const struct GNUNET_HashCode *session_hash,
struct TEH_KS_StateHandle *key_state,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coin,
unsigned int coin_off)
{
struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
struct TALER_DenominationSignature ev_sig;
dki = TEH_KS_denomination_key_lookup (key_state,
denom_pub,
TEH_KS_DKU_WITHDRAW);
if (NULL == dki)
{
GNUNET_break (0);
ev_sig.rsa_signature = NULL;
return ev_sig;
}
if (GNUNET_OK ==
TEH_plugin->get_refresh_out (TEH_plugin->cls,
session,
session_hash,
coin_off,
&ev_sig))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Returning cached reply for /refresh/reveal signature\n");
return ev_sig;
}
ev_sig.rsa_signature
= GNUNET_CRYPTO_rsa_sign_blinded (dki->denom_priv.rsa_private_key,
commit_coin->coin_ev,
commit_coin->coin_ev_size);
if (NULL == ev_sig.rsa_signature)
{
GNUNET_break (0);
return ev_sig;
}
if (GNUNET_SYSERR ==
TEH_plugin->insert_refresh_out (TEH_plugin->cls,
session,
session_hash,
coin_off,
&ev_sig))
{
GNUNET_break (0);
GNUNET_CRYPTO_rsa_signature_free (ev_sig.rsa_signature);
ev_sig.rsa_signature = NULL;
}
return ev_sig;
}
/**
* The client request was well-formed, now execute the DB transaction
* of a "/refresh/reveal" operation. We use the @a ev_sigs and
* @a commit_coins to clean up resources after this function returns
* as we might experience retries of the database transaction.
*
* @param connection the MHD connection to handle
* @param session database session
* @param session_hash hash identifying the refresh session
* @param refresh_session information about the refresh operation we are doing
* @param denom_pubs array of "num_newcoins" denomination keys for the new coins
* @param[out] ev_sigs where to store generated signatures for the new coins,
* array of length "num_newcoins", memory released by the
* caller
* @param[out] commit_coins array of length "num_newcoins" to be used for
* information about the new coins from the commitment.
* @return MHD result code
*/
static int
execute_refresh_reveal_transaction (struct MHD_Connection *connection,
struct TALER_EXCHANGEDB_Session *session,
const struct GNUNET_HashCode *session_hash,
const struct TALER_EXCHANGEDB_RefreshSession *refresh_session,
const struct TALER_DenominationPublicKey *denom_pubs,
struct TALER_DenominationSignature *ev_sigs,
struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins)
{
unsigned int j;
struct TEH_KS_StateHandle *key_state;
int ret;
START_TRANSACTION (session, connection);
key_state = TEH_KS_acquire ();
for (j=0;j<refresh_session->num_newcoins;j++)
{
if (NULL == ev_sigs[j].rsa_signature) /* could be non-NULL during retries */
ev_sigs[j] = refresh_exchange_coin (connection,
session,
session_hash,
key_state,
&denom_pubs[j],
&commit_coins[j],
j);
if (NULL == ev_sigs[j].rsa_signature)
{
TEH_plugin->rollback (TEH_plugin->cls,
session);
ret = TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_REVEAL_SIGNING_ERROR);
goto cleanup;
}
}
COMMIT_TRANSACTION (session, connection);
ret = TEH_RESPONSE_reply_refresh_reveal_success (connection,
refresh_session->num_newcoins,
ev_sigs);
cleanup:
TEH_KS_release (key_state);
return ret;
}
/**
* 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,
* and if so, return the signed coins for corresponding to the set of
* coins that was not chosen.
*
* @param connection the MHD connection to handle
* @param session_hash hash identifying the refresh session
* @param transfer_privs array with the revealed transfer keys,
* length must be #TALER_CNC_KAPPA - 1
* @return MHD result code
*/
int
TEH_DB_execute_refresh_reveal (struct MHD_Connection *connection,
const struct GNUNET_HashCode *session_hash,
struct TALER_TransferPrivateKeyP *transfer_privs)
{
int res;
struct TALER_EXCHANGEDB_Session *session;
struct TALER_EXCHANGEDB_RefreshSession refresh_session;
struct TALER_DenominationPublicKey *denom_pubs;
struct TALER_DenominationSignature *ev_sigs;
struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins;
unsigned int i;
unsigned int j;
unsigned int off;
struct GNUNET_HashContext *hash_context;
struct GNUNET_HashCode sh_check;
int ret;
struct TALER_TransferPublicKeyP gamma_tp;
if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls)))
{
GNUNET_break (0);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_DB_SETUP_FAILED);
}
res = TEH_plugin->get_refresh_session (TEH_plugin->cls,
session,
session_hash,
&refresh_session);
if (GNUNET_NO == res)
return TEH_RESPONSE_reply_arg_invalid (connection,
TALER_EC_REFRESH_REVEAL_SESSION_UNKNOWN,
"session_hash");
if ( (GNUNET_SYSERR == res) ||
(refresh_session.noreveal_index >= TALER_CNC_KAPPA) )
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_REVEAL_DB_FETCH_SESSION_ERROR);
denom_pubs = GNUNET_new_array (refresh_session.num_newcoins,
struct TALER_DenominationPublicKey);
if (GNUNET_OK !=
TEH_plugin->get_refresh_order (TEH_plugin->cls,
session,
session_hash,
refresh_session.num_newcoins,
denom_pubs))
{
GNUNET_break (0);
GNUNET_free (denom_pubs);
GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature);
GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key);
return (MHD_YES == TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_REVEAL_DB_FETCH_ORDER_ERROR))
? GNUNET_NO : GNUNET_SYSERR;
}
hash_context = GNUNET_CRYPTO_hash_context_start ();
/* first, iterate over transfer public keys for hash_context */
off = 0;
for (i=0;i<TALER_CNC_KAPPA;i++)
{
if (i == refresh_session.noreveal_index)
{
off = 1;
/* obtain gamma_tp from db */
if (GNUNET_OK !=
TEH_plugin->get_refresh_transfer_public_key (TEH_plugin->cls,
session,
session_hash,
&gamma_tp))
{
GNUNET_break (0);
GNUNET_free (denom_pubs);
GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature);
GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key);
GNUNET_CRYPTO_hash_context_abort (hash_context);
return (MHD_YES == TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_REVEAL_DB_FETCH_TRANSFER_ERROR))
? GNUNET_NO : GNUNET_SYSERR;
}
GNUNET_CRYPTO_hash_context_read (hash_context,
&gamma_tp,
sizeof (struct TALER_TransferPublicKeyP));
}
else
{
/* compute tp from private key */
struct TALER_TransferPublicKeyP tp;
GNUNET_CRYPTO_ecdhe_key_get_public (&transfer_privs[i - off].ecdhe_priv,
&tp.ecdhe_pub);
GNUNET_CRYPTO_hash_context_read (hash_context,
&tp,
sizeof (struct TALER_TransferPublicKeyP));
}
}
/* next, add all of the hashes from the denomination keys to the
hash_context */
{
struct TALER_DenominationPublicKey denom_pubs[refresh_session.num_newcoins];
if (GNUNET_OK !=
TEH_plugin->get_refresh_order (TEH_plugin->cls,
session,
session_hash,
refresh_session.num_newcoins,
denom_pubs))
{
GNUNET_break (0);
GNUNET_free (denom_pubs);
GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature);
GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key);
GNUNET_CRYPTO_hash_context_abort (hash_context);
return (MHD_YES == TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_REVEAL_DB_FETCH_ORDER_ERROR))
? GNUNET_NO : GNUNET_SYSERR;
}
for (i=0;i<refresh_session.num_newcoins;i++)
{
char *buf;
size_t buf_size;
buf_size = GNUNET_CRYPTO_rsa_public_key_encode (denom_pubs[i].rsa_public_key,
&buf);
GNUNET_CRYPTO_hash_context_read (hash_context,
buf,
buf_size);
GNUNET_free (buf);
GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[i].rsa_public_key);
}
}
/* next, add public key of coin and amount being refreshed */
{
struct TALER_AmountNBO melt_amountn;
GNUNET_CRYPTO_hash_context_read (hash_context,
&refresh_session.melt.coin.coin_pub,
sizeof (struct TALER_CoinSpendPublicKeyP));
TALER_amount_hton (&melt_amountn,
&refresh_session.melt.amount_with_fee);
GNUNET_CRYPTO_hash_context_read (hash_context,
&melt_amountn,
sizeof (struct TALER_AmountNBO));
}
commit_coins = GNUNET_new_array (refresh_session.num_newcoins,
struct TALER_EXCHANGEDB_RefreshCommitCoin);
off = 0;
for (i=0;i<TALER_CNC_KAPPA;i++)
{
if (i == refresh_session.noreveal_index)
{
off = 1;
/* obtain commit_coins for the selected gamma value from DB */
if (GNUNET_OK !=
TEH_plugin->get_refresh_commit_coins (TEH_plugin->cls,
session,
session_hash,
refresh_session.num_newcoins,
commit_coins))
{
GNUNET_break (0);
GNUNET_free (denom_pubs);
GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature);
GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key);
GNUNET_CRYPTO_hash_context_abort (hash_context);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_REVEAL_DB_FETCH_COMMIT_ERROR);
}
/* add envelopes to hash_context */
for (j=0;j<refresh_session.num_newcoins;j++)
{
GNUNET_CRYPTO_hash_context_read (hash_context,
commit_coins[j].coin_ev,
commit_coins[j].coin_ev_size);
}
continue;
}
if (GNUNET_OK !=
(res = check_commitment (connection,
session,
session_hash,
i,
&transfer_privs[i - off],
&refresh_session.melt,
refresh_session.num_newcoins,
denom_pubs,
hash_context)))
{
GNUNET_break_op (0);
for (j=0;j<refresh_session.num_newcoins;j++)
{
GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key);
GNUNET_free (commit_coins[j].coin_ev);
}
GNUNET_free (commit_coins);
GNUNET_free (denom_pubs);
GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature);
GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key);
GNUNET_CRYPTO_hash_context_abort (hash_context);
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
}
/* Check session hash matches */
GNUNET_CRYPTO_hash_context_finish (hash_context,
&sh_check);
if (0 != memcmp (&sh_check,
session_hash,
sizeof (struct GNUNET_HashCode)))
{
GNUNET_break_op (0);
ret = TEH_RESPONSE_reply_refresh_reveal_missmatch (connection,
&refresh_session,
commit_coins,
denom_pubs,
&gamma_tp);
for (j=0;j<refresh_session.num_newcoins;j++)
{
GNUNET_free (commit_coins[j].coin_ev);
GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key);
}
GNUNET_free (commit_coins);
GNUNET_free (denom_pubs);
GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature);
GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key);
return ret;
}
/* Client request OK, start transaction */
ev_sigs = GNUNET_new_array (refresh_session.num_newcoins,
struct TALER_DenominationSignature);
/* FIXME: might need to store revealed transfer private keys for
the auditor for later; should pass them as arguments here! #4792*/
res = execute_refresh_reveal_transaction (connection,
session,
session_hash,
&refresh_session,
denom_pubs,
ev_sigs,
commit_coins);
for (i=0;i<refresh_session.num_newcoins;i++)
{
if (NULL != ev_sigs[i].rsa_signature)
GNUNET_CRYPTO_rsa_signature_free (ev_sigs[i].rsa_signature);
GNUNET_free (commit_coins[i].coin_ev);
}
for (j=0;j<refresh_session.num_newcoins;j++)
if (NULL != denom_pubs[j].rsa_public_key)
GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key);
GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature);
GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key);
GNUNET_free (ev_sigs);
GNUNET_free (denom_pubs);
GNUNET_free (commit_coins);
return res;
}
/**
* Closure for #handle_transfer_data().
*/
struct HTD_Context
{
/**
* Session link data we collect.
*/
struct TEH_RESPONSE_LinkSessionInfo *sessions;
/**
* Database session. Nothing to do with @a sessions.
*/
struct TALER_EXCHANGEDB_Session *session;
/**
* MHD connection, for queueing replies.
*/
struct MHD_Connection *connection;
/**
* Number of sessions the coin was melted into.
*/
unsigned int num_sessions;
/**
* How are we expected to proceed. #GNUNET_SYSERR if we
* failed to return an error (should return #MHD_NO).
* #GNUNET_NO if we succeeded in queueing an MHD error
* (should return #MHD_YES from #TEH_execute_refresh_link),
* #GNUNET_OK if we should call #TEH_RESPONSE_reply_refresh_link_success().
*/
int status;
};
/**
* Function called with the session hashes and transfer secret
* information for a given coin. Gets the linkage data and
* builds the reply for the client.
*
*
* @param cls closure, a `struct HTD_Context`
* @param session_hash a session the coin was melted in
* @param transfer_pub public transfer key for the session
*/
static void
handle_transfer_data (void *cls,
const struct GNUNET_HashCode *session_hash,
const struct TALER_TransferPublicKeyP *transfer_pub)
{
struct HTD_Context *ctx = cls;
struct TALER_EXCHANGEDB_LinkDataList *ldl;
struct TEH_RESPONSE_LinkSessionInfo *lsi;
if (GNUNET_OK != ctx->status)
return;
ldl = TEH_plugin->get_link_data_list (TEH_plugin->cls,
ctx->session,
session_hash);
if (NULL == ldl)
{
ctx->status = GNUNET_NO;
if (MHD_NO ==
TEH_RESPONSE_reply_json_pack (ctx->connection,
MHD_HTTP_NOT_FOUND,
"{s:s}",
"error",
"link data not found (link)"))
ctx->status = GNUNET_SYSERR;
return;
}
GNUNET_array_grow (ctx->sessions,
ctx->num_sessions,
ctx->num_sessions + 1);
lsi = &ctx->sessions[ctx->num_sessions - 1];
lsi->transfer_pub = *transfer_pub;
lsi->ldl = ldl;
}
/**
* Execute a "/refresh/link". Returns the linkage information that
* will allow the owner of a coin to follow the refresh trail to
* the refreshed coin.
*
* @param connection the MHD connection to handle
* @param coin_pub public key of the coin to link
* @return MHD result code
*/
int
TEH_DB_execute_refresh_link (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
struct HTD_Context ctx;
int res;
unsigned int i;
if (NULL == (ctx.session = TEH_plugin->get_session (TEH_plugin->cls)))
{
GNUNET_break (0);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_DB_SETUP_FAILED);
}
ctx.connection = connection;
ctx.num_sessions = 0;
ctx.sessions = NULL;
ctx.status = GNUNET_OK;
res = TEH_plugin->get_transfer (TEH_plugin->cls,
ctx.session,
coin_pub,
&handle_transfer_data,
&ctx);
if (GNUNET_SYSERR == ctx.status)
{
res = MHD_NO;
goto cleanup;
}
if (GNUNET_NO == ctx.status)
{
res = MHD_YES;
goto cleanup;
}
GNUNET_assert (GNUNET_OK == ctx.status);
if (0 == ctx.num_sessions)
return TEH_RESPONSE_reply_arg_unknown (connection,
TALER_EC_REFRESH_LINK_COIN_UNKNOWN,
"coin_pub");
res = TEH_RESPONSE_reply_refresh_link_success (connection,
ctx.num_sessions,
ctx.sessions);
cleanup:
for (i=0;i<ctx.num_sessions;i++)
TEH_plugin->free_link_data_list (TEH_plugin->cls,
ctx.sessions[i].ldl);
GNUNET_free_non_null (ctx.sessions);
return res;
}
/* end of taler-exchange-httpd_db.c */

View File

@ -1,6 +1,6 @@
/*
This file is part of TALER
Copyright (C) 2014, 2015 GNUnet e.V.
Copyright (C) 2014-2017 GNUnet e.V.
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@ -83,97 +83,5 @@ TEH_DB_calculate_transaction_list_totals (struct TALER_EXCHANGEDB_TransactionLis
struct TALER_Amount *ret);
/**
* @brief Details about a melt operation of an individual coin.
*/
struct TEH_DB_MeltDetails
{
/**
* Information about the coin being melted.
*/
struct TALER_CoinPublicInfo coin_info;
/**
* Signature allowing the melt (using
* a `struct TALER_EXCHANGEDB_RefreshMeltConfirmSignRequestBody`) to sign over.
*/
struct TALER_CoinSpendSignatureP melt_sig;
/**
* How much of the coin's value did the client allow to be melted?
* This amount includes the fees, so the final amount contributed
* to the melt is this value minus the fee for melting the coin.
*/
struct TALER_Amount melt_amount_with_fee;
/**
* What fee is earned by the exchange? Set delayed during
* #verify_coin_public_info().
*/
struct TALER_Amount melt_fee;
};
/**
* Execute a "/refresh/melt". We have been given a list of valid
* coins and a request to melt them into the given
* @a refresh_session_pub. Check that the coins all have the
* required value left and if so, store that they have been
* melted and confirm the melting operation to the client.
*
* @param connection the MHD connection to handle
* @param session_hash hash code of the session the coins are melted into
* @param num_new_denoms number of entries in @a denom_pubs, size of y-dimension of @a commit_coin array
* @param denom_pubs array of public denomination keys for the refresh (?)
* @param coin_melt_detail signatures and (residual) value of and information about the respective coin to be melted
* @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 transfer_pubs array of transfer public keys (what the exchange is
* to return via "/refresh/link" to enable linkage in the
* future) of length #TALER_CNC_KAPPA
* @return MHD result code
*/
int
TEH_DB_execute_refresh_melt (struct MHD_Connection *connection,
const struct GNUNET_HashCode *session_hash,
unsigned int num_new_denoms,
const struct TALER_DenominationPublicKey *denom_pubs,
const struct TEH_DB_MeltDetails *coin_melt_detail,
struct TALER_EXCHANGEDB_RefreshCommitCoin *const* commit_coin,
const struct TALER_TransferPublicKeyP *transfer_pubs);
/**
* Execute a "/refresh/reveal". The client is revealing to us the
* transfer keys for #TALER_CNC_KAPPA-1 sets of coins. Verify that the
* revealed transfer keys would allow linkage to the blinded coins,
* and if so, return the signed coins for corresponding to the set of
* coins that was not chosen.
*
* @param connection the MHD connection to handle
* @param session_hash hash over the refresh session
* @param transfer_privs array of length #TALER_CNC_KAPPA-1 with the revealed transfer keys
* @return MHD result code
*/
int
TEH_DB_execute_refresh_reveal (struct MHD_Connection *connection,
const struct GNUNET_HashCode *session_hash,
struct TALER_TransferPrivateKeyP *transfer_privs);
/**
* Execute a "/refresh/link". Returns the linkage information that
* will allow the owner of a coin to follow the refresh trail to the
* refreshed coin.
*
* @param connection the MHD connection to handle
* @param coin_pub public key of the coin to link
* @return MHD result code
*/
int
TEH_DB_execute_refresh_link (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub);
#endif
/* TALER_EXCHANGE_HTTPD_DB_H */

View File

@ -0,0 +1,286 @@
/*
This file is part of TALER
Copyright (C) 2014-2017 Inria & 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, see <http://www.gnu.org/licenses/>
*/
/**
* @file taler-exchange-httpd_refresh_link.c
* @brief Handle /refresh/link requests
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include "taler-exchange-httpd_parsing.h"
#include "taler-exchange-httpd_mhd.h"
#include "taler-exchange-httpd_refresh_link.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_keystate.h"
/**
* @brief Information for each session a coin was melted into.
*/
struct TEH_RESPONSE_LinkSessionInfo
{
/**
* Transfer public key of the coin.
*/
struct TALER_TransferPublicKeyP transfer_pub;
/**
* Linked data of coins being created in the session.
*/
struct TALER_EXCHANGEDB_LinkDataList *ldl;
};
/**
* Closure for #handle_transfer_data().
*/
struct HTD_Context
{
/**
* Session link data we collect.
*/
struct TEH_RESPONSE_LinkSessionInfo *sessions;
/**
* Database session. Nothing to do with @a sessions.
*/
struct TALER_EXCHANGEDB_Session *session;
/**
* MHD connection, for queueing replies.
*/
struct MHD_Connection *connection;
/**
* Number of sessions the coin was melted into.
*/
unsigned int num_sessions;
/**
* How are we expected to proceed. #GNUNET_SYSERR if we
* failed to return an error (should return #MHD_NO).
* #GNUNET_NO if we succeeded in queueing an MHD error
* (should return #MHD_YES from #TEH_execute_refresh_link),
* #GNUNET_OK if we should call #reply_refresh_link_success().
*/
int status;
};
/**
* Send a response for "/refresh/link".
*
* @param connection the connection to send the response to
* @param num_sessions number of sessions the coin was used in
* @param sessions array of @a num_session entries with
* information for each session
* @return a MHD result code
*/
static int
reply_refresh_link_success (struct MHD_Connection *connection,
unsigned int num_sessions,
const struct TEH_RESPONSE_LinkSessionInfo *sessions)
{
json_t *root;
json_t *mlist;
int res;
unsigned int i;
mlist = json_array ();
for (i=0;i<num_sessions;i++)
{
const struct TALER_EXCHANGEDB_LinkDataList *pos;
json_t *list = json_array ();
for (pos = sessions[i].ldl; NULL != pos; pos = pos->next)
{
json_t *obj;
obj = json_object ();
json_object_set_new (obj,
"denom_pub",
GNUNET_JSON_from_rsa_public_key (pos->denom_pub.rsa_public_key));
json_object_set_new (obj,
"ev_sig",
GNUNET_JSON_from_rsa_signature (pos->ev_sig.rsa_signature));
GNUNET_assert (0 ==
json_array_append_new (list,
obj));
}
root = json_object ();
json_object_set_new (root,
"new_coins",
list);
json_object_set_new (root,
"transfer_pub",
GNUNET_JSON_from_data_auto (&sessions[i].transfer_pub));
GNUNET_assert (0 ==
json_array_append_new (mlist,
root));
}
res = TEH_RESPONSE_reply_json (connection,
mlist,
MHD_HTTP_OK);
json_decref (mlist);
return res;
}
/**
* Function called with the session hashes and transfer secret
* information for a given coin. Gets the linkage data and
* builds the reply for the client.
*
*
* @param cls closure, a `struct HTD_Context`
* @param session_hash a session the coin was melted in
* @param transfer_pub public transfer key for the session
*/
static void
handle_transfer_data (void *cls,
const struct GNUNET_HashCode *session_hash,
const struct TALER_TransferPublicKeyP *transfer_pub)
{
struct HTD_Context *ctx = cls;
struct TALER_EXCHANGEDB_LinkDataList *ldl;
struct TEH_RESPONSE_LinkSessionInfo *lsi;
if (GNUNET_OK != ctx->status)
return;
ldl = TEH_plugin->get_link_data_list (TEH_plugin->cls,
ctx->session,
session_hash);
if (NULL == ldl)
{
ctx->status = GNUNET_NO;
if (MHD_NO ==
TEH_RESPONSE_reply_json_pack (ctx->connection,
MHD_HTTP_NOT_FOUND,
"{s:s}",
"error",
"link data not found (link)"))
ctx->status = GNUNET_SYSERR;
return;
}
GNUNET_array_grow (ctx->sessions,
ctx->num_sessions,
ctx->num_sessions + 1);
lsi = &ctx->sessions[ctx->num_sessions - 1];
lsi->transfer_pub = *transfer_pub;
lsi->ldl = ldl;
}
/**
* Execute a "/refresh/link". Returns the linkage information that
* will allow the owner of a coin to follow the refresh trail to
* the refreshed coin.
*
* @param connection the MHD connection to handle
* @param coin_pub public key of the coin to link
* @return MHD result code
*/
static int
execute_refresh_link (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
struct HTD_Context ctx;
int res;
unsigned int i;
if (NULL == (ctx.session = TEH_plugin->get_session (TEH_plugin->cls)))
{
GNUNET_break (0);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_DB_SETUP_FAILED);
}
ctx.connection = connection;
ctx.num_sessions = 0;
ctx.sessions = NULL;
ctx.status = GNUNET_OK;
res = TEH_plugin->get_transfer (TEH_plugin->cls,
ctx.session,
coin_pub,
&handle_transfer_data,
&ctx);
if (GNUNET_SYSERR == ctx.status)
{
res = MHD_NO;
goto cleanup;
}
if (GNUNET_NO == ctx.status)
{
res = MHD_YES;
goto cleanup;
}
GNUNET_assert (GNUNET_OK == ctx.status);
if (0 == ctx.num_sessions)
return TEH_RESPONSE_reply_arg_unknown (connection,
TALER_EC_REFRESH_LINK_COIN_UNKNOWN,
"coin_pub");
res = reply_refresh_link_success (connection,
ctx.num_sessions,
ctx.sessions);
cleanup:
for (i=0;i<ctx.num_sessions;i++)
TEH_plugin->free_link_data_list (TEH_plugin->cls,
ctx.sessions[i].ldl);
GNUNET_free_non_null (ctx.sessions);
return res;
}
/**
* Handle a "/refresh/link" request. Note that for "/refresh/link"
* we do use a simple HTTP GET, and a HTTP POST!
*
* @param rh context of the handler
* @param connection the MHD connection to handle
* @param[in,out] connection_cls the connection's closure (can be updated)
* @param upload_data upload data
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
int
TEH_REFRESH_handler_refresh_link (struct TEH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size)
{
struct TALER_CoinSpendPublicKeyP coin_pub;
int res;
res = TEH_PARSE_mhd_request_arg_data (connection,
"coin_pub",
&coin_pub,
sizeof (struct TALER_CoinSpendPublicKeyP));
if (GNUNET_SYSERR == res)
return MHD_NO;
if (GNUNET_OK != res)
return MHD_YES;
return execute_refresh_link (connection,
&coin_pub);
}
/* end of taler-exchange-httpd_refresh_link.c */

View File

@ -0,0 +1,49 @@
/*
This file is part of TALER
Copyright (C) 2014-2017 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, see <http://www.gnu.org/licenses/>
*/
/**
* @file taler-exchange-httpd_refresh_link.h
* @brief Handle /refresh/link requests
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
*/
#ifndef TALER_EXCHANGE_HTTPD_REFRESH_LINK_H
#define TALER_EXCHANGE_HTTPD_REFRESH_LINK_H
#include <gnunet/gnunet_util_lib.h>
#include <microhttpd.h>
#include "taler-exchange-httpd.h"
/**
* Handle a "/refresh/link" request
*
* @param rh context of the handler
* @param connection the MHD connection to handle
* @param[in,out] connection_cls the connection's closure (can be updated)
* @param upload_data upload data
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
int
TEH_REFRESH_handler_refresh_link (struct TEH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size);
#endif

View File

@ -1,6 +1,6 @@
/*
This file is part of TALER
Copyright (C) 2014, 2015, 2016 Inria & GNUnet e.V.
Copyright (C) 2014-2017 Inria & 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
@ -14,8 +14,8 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file taler-exchange-httpd_refresh.c
* @brief Handle /refresh/ requests
* @file taler-exchange-httpd_refresh_melt.c
* @brief Handle /refresh/melt requests
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
@ -26,11 +26,454 @@
#include <microhttpd.h>
#include "taler-exchange-httpd_parsing.h"
#include "taler-exchange-httpd_mhd.h"
#include "taler-exchange-httpd_refresh.h"
#include "taler-exchange-httpd_refresh_melt.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_keystate.h"
/**
* How often should we retry a transaction before giving up
* (for transactions resulting in serialization/dead locks only).
*/
#define MAX_TRANSACTION_COMMIT_RETRIES 3
/**
* Code to begin a transaction, must be inline as we define a block
* that ends with #COMMIT_TRANSACTION() within which we perform a number
* of retries. Note that this code may call "return" internally, so
* it must be called within a function where any cleanup will be done
* by the caller. Furthermore, the function's return value must
* match that of a #TEH_RESPONSE_reply_internal_db_error() status code.
*
* @param session session handle
* @param connection connection handle
*/
#define START_TRANSACTION(session,connection) \
{ /* start new scope, will be ended by COMMIT_TRANSACTION() */\
unsigned int transaction_retries = 0; \
enum GNUNET_DB_QueryStatus transaction_commit_result; \
transaction_start_label: /* we will use goto for retries */ \
if (GNUNET_OK != \
TEH_plugin->start (TEH_plugin->cls, \
session)) \
{ \
GNUNET_break (0); \
return TEH_RESPONSE_reply_internal_db_error (connection, \
TALER_EC_DB_START_FAILED); \
}
/**
* Code to conclude a transaction, dual to #START_TRANSACTION(). Note
* that this code may call "return" internally, so it must be called
* within a function where any cleanup will be done by the caller.
* Furthermore, the function's return value must match that of a
* #TEH_RESPONSE_reply_internal_db_error() status code.
*
* @param session session handle
* @param connection connection handle
*/
#define COMMIT_TRANSACTION(session,connection) \
transaction_commit_result = \
TEH_plugin->commit (TEH_plugin->cls, \
session); \
if (GNUNET_DB_STATUS_HARD_ERROR == transaction_commit_result) \
{ \
TALER_LOG_WARNING ("Transaction commit failed in %s\n", __FUNCTION__); \
return TEH_RESPONSE_reply_commit_error (connection, \
TALER_EC_DB_COMMIT_FAILED_HARD); \
} \
if (GNUNET_DB_STATUS_SOFT_ERROR == transaction_commit_result) \
{ \
TALER_LOG_WARNING ("Transaction commit failed in %s\n", __FUNCTION__); \
if (transaction_retries++ <= MAX_TRANSACTION_COMMIT_RETRIES) \
goto transaction_start_label; \
TALER_LOG_WARNING ("Transaction commit failed %u times in %s\n", \
transaction_retries, \
__FUNCTION__); \
return TEH_RESPONSE_reply_commit_error (connection, \
TALER_EC_DB_COMMIT_FAILED_ON_RETRY); \
} \
} /* end of scope opened by BEGIN_TRANSACTION */
/**
* Code to include to retry a transaction, must only be used in between
* #START_TRANSACTION and #COMMIT_TRANSACTION.
*
* @param session session handle
* @param connection connection handle
*/
#define RETRY_TRANSACTION(session,connection) \
do { \
TEH_plugin->rollback (TEH_plugin->cls, \
session); \
if (transaction_retries++ <= MAX_TRANSACTION_COMMIT_RETRIES) \
goto transaction_start_label; \
TALER_LOG_WARNING ("Transaction commit failed %u times in %s\n", \
transaction_retries, \
__FUNCTION__); \
return TEH_RESPONSE_reply_commit_error (connection, \
TALER_EC_DB_COMMIT_FAILED_ON_RETRY); \
} while (0)
/**
* @brief Details about a melt operation of an individual coin.
*/
struct TEH_DB_MeltDetails
{
/**
* Information about the coin being melted.
*/
struct TALER_CoinPublicInfo coin_info;
/**
* Signature allowing the melt (using
* a `struct TALER_EXCHANGEDB_RefreshMeltConfirmSignRequestBody`) to sign over.
*/
struct TALER_CoinSpendSignatureP melt_sig;
/**
* How much of the coin's value did the client allow to be melted?
* This amount includes the fees, so the final amount contributed
* to the melt is this value minus the fee for melting the coin.
*/
struct TALER_Amount melt_amount_with_fee;
/**
* What fee is earned by the exchange? Set delayed during
* #verify_coin_public_info().
*/
struct TALER_Amount melt_fee;
};
/**
* Send a response for a failed "/refresh/melt" request. The
* transaction history of the given coin demonstrates that the
* @a residual value of the coin is below the @a requested
* contribution of the coin for the melt. Thus, the exchange
* refuses the melt operation.
*
* @param connection the connection to send the response to
* @param coin_pub public key of the coin
* @param coin_value original value of the coin
* @param tl transaction history for the coin
* @param requested how much this coin was supposed to contribute, including fee
* @param residual remaining value of the coin (after subtracting @a tl)
* @return a MHD result code
*/
static int
reply_refresh_melt_insufficient_funds (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
struct TALER_Amount coin_value,
struct TALER_EXCHANGEDB_TransactionList *tl,
struct TALER_Amount requested,
struct TALER_Amount residual)
{
json_t *history;
history = TEH_RESPONSE_compile_transaction_history (tl);
if (NULL == history)
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS);
return TEH_RESPONSE_reply_json_pack (connection,
MHD_HTTP_FORBIDDEN,
"{s:s, s:I, s:o, s:o, s:o, s:o, s:o}",
"error",
"insufficient funds",
"code",
(json_int_t) TALER_EC_REFRESH_MELT_INSUFFICIENT_FUNDS,
"coin_pub",
GNUNET_JSON_from_data_auto (coin_pub),
"original_value",
TALER_JSON_from_amount (&coin_value),
"residual_value",
TALER_JSON_from_amount (&residual),
"requested_value",
TALER_JSON_from_amount (&requested),
"history",
history);
}
/**
* Send a response to a "/refresh/melt" request.
*
* @param connection the connection to send the response to
* @param session_hash hash of the refresh session
* @param noreveal_index which index will the client not have to reveal
* @return a MHD status code
*/
static int
reply_refresh_melt_success (struct MHD_Connection *connection,
const struct GNUNET_HashCode *session_hash,
uint16_t noreveal_index)
{
struct TALER_RefreshMeltConfirmationPS body;
struct TALER_ExchangePublicKeyP pub;
struct TALER_ExchangeSignatureP sig;
json_t *sig_json;
body.purpose.size = htonl (sizeof (struct TALER_RefreshMeltConfirmationPS));
body.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT);
body.session_hash = *session_hash;
body.noreveal_index = htons (noreveal_index);
body.reserved = htons (0);
TEH_KS_sign (&body.purpose,
&pub,
&sig);
sig_json = GNUNET_JSON_from_data_auto (&sig);
GNUNET_assert (NULL != sig_json);
return TEH_RESPONSE_reply_json_pack (connection,
MHD_HTTP_OK,
"{s:i, s:o, s:o}",
"noreveal_index", (int) noreveal_index,
"exchange_sig", sig_json,
"exchange_pub", GNUNET_JSON_from_data_auto (&pub));
}
/**
* Parse coin melt requests from a JSON object and write them to
* the database.
*
* @param connection the connection to send errors to
* @param session the database connection
* @param key_state the exchange's key state
* @param session_hash hash identifying the refresh session
* @param coin_details details about the coin being melted
* @param[out] meltp on success, set to melt details
* @return #GNUNET_OK on success,
* #GNUNET_NO if an error message was generated,
* #GNUNET_SYSERR on internal errors (no response generated)
*/
static int
refresh_check_melt (struct MHD_Connection *connection,
struct TALER_EXCHANGEDB_Session *session,
const struct TEH_KS_StateHandle *key_state,
const struct GNUNET_HashCode *session_hash,
const struct TEH_DB_MeltDetails *coin_details,
struct TALER_EXCHANGEDB_RefreshMelt *meltp)
{
struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dk;
struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki;
struct TALER_EXCHANGEDB_TransactionList *tl;
struct TALER_Amount coin_value;
struct TALER_Amount coin_residual;
struct TALER_Amount spent;
int res;
enum GNUNET_DB_QueryStatus qs;
dk = TEH_KS_denomination_key_lookup (key_state,
&coin_details->coin_info.denom_pub,
TEH_KS_DKU_DEPOSIT);
if (NULL == dk)
return (MHD_YES ==
TEH_RESPONSE_reply_internal_error (connection,
TALER_EC_REFRESH_MELT_DB_DENOMINATION_KEY_NOT_FOUND,
"denomination key no longer available while executing transaction"))
? GNUNET_NO : GNUNET_SYSERR;
dki = &dk->issue;
TALER_amount_ntoh (&coin_value,
&dki->properties.value);
/* fee for THIS transaction; the melt amount includes the fee! */
spent = coin_details->melt_amount_with_fee;
/* add historic transaction costs of this coin */
qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
session,
&coin_details->coin_info.coin_pub,
&tl);
(void) qs; /* FIXME #5010 */
if (GNUNET_OK !=
TEH_DB_calculate_transaction_list_totals (tl,
&spent,
&spent))
{
GNUNET_break (0);
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
tl);
return (MHD_YES ==
TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_MELT_COIN_HISTORY_COMPUTATION_FAILED))
? GNUNET_NO : GNUNET_SYSERR;
}
/* Refuse to refresh when the coin's value is insufficient
for the cost of all transactions. */
if (TALER_amount_cmp (&coin_value,
&spent) < 0)
{
GNUNET_assert (GNUNET_SYSERR !=
TALER_amount_subtract (&coin_residual,
&spent,
&coin_details->melt_amount_with_fee));
res = (MHD_YES ==
reply_refresh_melt_insufficient_funds (connection,
&coin_details->coin_info.coin_pub,
coin_value,
tl,
coin_details->melt_amount_with_fee,
coin_residual))
? GNUNET_NO : GNUNET_SYSERR;
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
tl);
return res;
}
TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
tl);
meltp->coin = coin_details->coin_info;
meltp->coin_sig = coin_details->melt_sig;
meltp->session_hash = *session_hash;
meltp->amount_with_fee = coin_details->melt_amount_with_fee;
meltp->melt_fee = coin_details->melt_fee;
return GNUNET_OK;
}
/**
* Execute a "/refresh/melt". We have been given a list of valid
* coins and a request to melt them into the given
* @a refresh_session_pub. Check that the coins all have the
* required value left and if so, store that they have been
* melted and confirm the melting operation to the client.
*
* @param connection the MHD connection to handle
* @param session_hash hash code of the session the coins are melted into
* @param num_new_denoms number of entries in @a denom_pubs, size of y-dimension of @a commit_coin array
* @param denom_pubs public keys of the coins we want to withdraw in the end
* @param coin_melt_detail signature and (residual) value of the respective coin should be melted
* @param commit_coin 2d array of coin commitments (what the exchange is to sign
* once the "/refres/reveal" of cut and choose is done),
* x-dimension must be #TALER_CNC_KAPPA
* @param transfer_pubs array of transfer public keys (what the exchange is
* to return via "/refresh/link" to enable linkage in the
* future) of length #TALER_CNC_KAPPA
* @return MHD result code
*/
static int
execute_refresh_melt (struct MHD_Connection *connection,
const struct GNUNET_HashCode *session_hash,
unsigned int num_new_denoms,
const struct TALER_DenominationPublicKey *denom_pubs,
const struct TEH_DB_MeltDetails *coin_melt_detail,
struct TALER_EXCHANGEDB_RefreshCommitCoin *const* commit_coin,
const struct TALER_TransferPublicKeyP *transfer_pubs)
{
struct TEH_KS_StateHandle *key_state;
struct TALER_EXCHANGEDB_RefreshSession refresh_session;
struct TALER_EXCHANGEDB_Session *session;
int res;
if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls)))
{
GNUNET_break (0);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_DB_SETUP_FAILED);
}
START_TRANSACTION (session, connection);
res = TEH_plugin->get_refresh_session (TEH_plugin->cls,
session,
session_hash,
&refresh_session);
if (GNUNET_YES == res)
{
TEH_plugin->rollback (TEH_plugin->cls,
session);
res = reply_refresh_melt_success (connection,
session_hash,
refresh_session.noreveal_index);
return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
}
if (GNUNET_SYSERR == res)
{
TEH_plugin->rollback (TEH_plugin->cls,
session);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_MELT_DB_FETCH_ERROR);
}
/* store 'global' session data */
refresh_session.num_newcoins = num_new_denoms;
refresh_session.noreveal_index
= GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG,
TALER_CNC_KAPPA);
key_state = TEH_KS_acquire ();
if (GNUNET_OK !=
(res = refresh_check_melt (connection,
session,
key_state,
session_hash,
coin_melt_detail,
&refresh_session.melt)))
{
TEH_KS_release (key_state);
TEH_plugin->rollback (TEH_plugin->cls,
session);
return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
}
TEH_KS_release (key_state);
if (GNUNET_OK !=
(res = TEH_plugin->create_refresh_session (TEH_plugin->cls,
session,
session_hash,
&refresh_session)))
{
TEH_plugin->rollback (TEH_plugin->cls,
session);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_MELT_DB_STORE_SESSION_ERROR);
}
/* store requested new denominations */
if (GNUNET_OK !=
TEH_plugin->insert_refresh_order (TEH_plugin->cls,
session,
session_hash,
num_new_denoms,
denom_pubs))
{
TEH_plugin->rollback (TEH_plugin->cls,
session);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_MELT_DB_STORE_ORDER_ERROR);
}
if (GNUNET_OK !=
TEH_plugin->insert_refresh_commit_coins (TEH_plugin->cls,
session,
session_hash,
num_new_denoms,
commit_coin[refresh_session.noreveal_index]))
{
TEH_plugin->rollback (TEH_plugin->cls,
session);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_MELT_DB_STORE_ORDER_ERROR);
}
if (GNUNET_OK !=
TEH_plugin->insert_refresh_transfer_public_key (TEH_plugin->cls,
session,
session_hash,
&transfer_pubs[refresh_session.noreveal_index]))
{
TEH_plugin->rollback (TEH_plugin->cls,
session);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_MELT_DB_STORE_TRANSFER_ERROR);
}
COMMIT_TRANSACTION (session, connection);
return reply_refresh_melt_success (connection,
session_hash,
refresh_session.noreveal_index);
}
/**
* 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
@ -149,7 +592,7 @@ handle_refresh_melt_binary (struct MHD_Connection *connection,
"error", "value mismatch",
"code", (json_int_t) TALER_EC_REFRESH_MELT_FEES_MISSMATCH);
}
return TEH_DB_execute_refresh_melt (connection,
return execute_refresh_melt (connection,
session_hash,
num_new_denoms,
denom_pubs,
@ -597,159 +1040,4 @@ TEH_REFRESH_handler_refresh_melt (struct TEH_RequestHandler *rh,
}
/**
* Handle a "/refresh/reveal" request. Parses the given JSON
* transfer private keys and if successful, passes everything to
* #TEH_DB_execute_refresh_reveal() which will verify that the
* revealed information is valid then returns the signed refreshed
* coins.
*
* @param connection the MHD connection to handle
* @param session_hash hash identifying the melting session
* @param tp_json private transfer keys in JSON format
* @return MHD result code
*/
static int
handle_refresh_reveal_json (struct MHD_Connection *connection,
const struct GNUNET_HashCode *session_hash,
const json_t *tp_json)
{
struct TALER_TransferPrivateKeyP transfer_privs[TALER_CNC_KAPPA - 1];
unsigned int i;
int res;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"reveal request for session %s\n",
GNUNET_h2s (session_hash));
res = GNUNET_OK;
for (i = 0; i < TALER_CNC_KAPPA - 1; i++)
{
struct GNUNET_JSON_Specification tp_spec[] = {
GNUNET_JSON_spec_fixed_auto (NULL, &transfer_privs[i]),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK != res)
break;
res = TEH_PARSE_json_array (connection,
tp_json,
tp_spec,
i, -1);
GNUNET_break_op (GNUNET_OK == res);
}
if (GNUNET_OK != res)
res = (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
else
res = TEH_DB_execute_refresh_reveal (connection,
session_hash,
transfer_privs);
return res;
}
/**
* Handle a "/refresh/reveal" request. This time, the client reveals
* the private transfer keys except for the cut-and-choose value
* returned from "/refresh/melt". This function parses the revealed
* keys and secrets and ultimately passes everything to
* #TEH_DB_execute_refresh_reveal() which will verify that the
* revealed information is valid then returns the signed refreshed
* coins.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
* @param[in,out] connection_cls the connection's closure (can be updated)
* @param upload_data upload data
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
int
TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size)
{
struct GNUNET_HashCode session_hash;
int res;
json_t *root;
json_t *transfer_privs;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("session_hash", &session_hash),
GNUNET_JSON_spec_json ("transfer_privs", &transfer_privs),
GNUNET_JSON_spec_end ()
};
res = TEH_PARSE_post_json (connection,
connection_cls,
upload_data,
upload_data_size,
&root);
if (GNUNET_SYSERR == res)
return MHD_NO;
if ( (GNUNET_NO == res) || (NULL == root) )
return MHD_YES;
res = TEH_PARSE_json_data (connection,
root,
spec);
json_decref (root);
if (GNUNET_OK != res)
{
GNUNET_break_op (0);
return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
}
/* Determine dimensionality of the request (kappa and #old coins) */
/* 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 TEH_RESPONSE_reply_arg_invalid (connection,
TALER_EC_REFRESH_REVEAL_CNC_TRANSFER_ARRAY_SIZE_INVALID,
"transfer_privs");
}
res = handle_refresh_reveal_json (connection,
&session_hash,
transfer_privs);
GNUNET_JSON_parse_free (spec);
return res;
}
/**
* Handle a "/refresh/link" request. Note that for "/refresh/link"
* we do use a simple HTTP GET, and a HTTP POST!
*
* @param rh context of the handler
* @param connection the MHD connection to handle
* @param[in,out] connection_cls the connection's closure (can be updated)
* @param upload_data upload data
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
int
TEH_REFRESH_handler_refresh_link (struct TEH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size)
{
struct TALER_CoinSpendPublicKeyP coin_pub;
int res;
res = TEH_PARSE_mhd_request_arg_data (connection,
"coin_pub",
&coin_pub,
sizeof (struct TALER_CoinSpendPublicKeyP));
if (GNUNET_SYSERR == res)
return MHD_NO;
if (GNUNET_OK != res)
return MHD_YES;
return TEH_DB_execute_refresh_link (connection,
&coin_pub);
}
/* end of taler-exchange-httpd_refresh.c */
/* end of taler-exchange-httpd_refresh_melt.c */

View File

@ -0,0 +1,52 @@
/*
This file is part of TALER
Copyright (C) 2014-2017 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, see <http://www.gnu.org/licenses/>
*/
/**
* @file taler-exchange-httpd_refresh_melt.h
* @brief Handle /refresh/melt requests
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
*/
#ifndef TALER_EXCHANGE_HTTPD_REFRESH_MELT_H
#define TALER_EXCHANGE_HTTPD_REFRESH_MELT_H
#include <gnunet/gnunet_util_lib.h>
#include <microhttpd.h>
#include "taler-exchange-httpd.h"
/**
* Handle a "/refresh/melt" request. Parses the request into the JSON
* components and then hands things of to #handle_refresh_melt_json()
* to validate the melted coins, the signature and execute the melt
* using TEH_DB_execute_refresh_melt().
*
* @param rh context of the handler
* @param connection the MHD connection to handle
* @param[in,out] connection_cls the connection's closure (can be updated)
* @param upload_data upload data
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
int
TEH_REFRESH_handler_refresh_melt (struct TEH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size);
#endif

View File

@ -0,0 +1,833 @@
/*
This file is part of TALER
Copyright (C) 2014-2017 Inria & 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, see <http://www.gnu.org/licenses/>
*/
/**
* @file taler-exchange-httpd_refresh_reveal.c
* @brief Handle /refresh/reveal requests
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
*/
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include "taler-exchange-httpd_parsing.h"
#include "taler-exchange-httpd_mhd.h"
#include "taler-exchange-httpd_refresh_reveal.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_keystate.h"
/**
* How often should we retry a transaction before giving up
* (for transactions resulting in serialization/dead locks only).
*/
#define MAX_TRANSACTION_COMMIT_RETRIES 3
/**
* Code to begin a transaction, must be inline as we define a block
* that ends with #COMMIT_TRANSACTION() within which we perform a number
* of retries. Note that this code may call "return" internally, so
* it must be called within a function where any cleanup will be done
* by the caller. Furthermore, the function's return value must
* match that of a #TEH_RESPONSE_reply_internal_db_error() status code.
*
* @param session session handle
* @param connection connection handle
*/
#define START_TRANSACTION(session,connection) \
{ /* start new scope, will be ended by COMMIT_TRANSACTION() */\
unsigned int transaction_retries = 0; \
enum GNUNET_DB_QueryStatus transaction_commit_result; \
transaction_start_label: /* we will use goto for retries */ \
if (GNUNET_OK != \
TEH_plugin->start (TEH_plugin->cls, \
session)) \
{ \
GNUNET_break (0); \
return TEH_RESPONSE_reply_internal_db_error (connection, \
TALER_EC_DB_START_FAILED); \
}
/**
* Code to conclude a transaction, dual to #START_TRANSACTION(). Note
* that this code may call "return" internally, so it must be called
* within a function where any cleanup will be done by the caller.
* Furthermore, the function's return value must match that of a
* #TEH_RESPONSE_reply_internal_db_error() status code.
*
* @param session session handle
* @param connection connection handle
*/
#define COMMIT_TRANSACTION(session,connection) \
transaction_commit_result = \
TEH_plugin->commit (TEH_plugin->cls, \
session); \
if (GNUNET_DB_STATUS_HARD_ERROR == transaction_commit_result) \
{ \
TALER_LOG_WARNING ("Transaction commit failed in %s\n", __FUNCTION__); \
return TEH_RESPONSE_reply_commit_error (connection, \
TALER_EC_DB_COMMIT_FAILED_HARD); \
} \
if (GNUNET_DB_STATUS_SOFT_ERROR == transaction_commit_result) \
{ \
TALER_LOG_WARNING ("Transaction commit failed in %s\n", __FUNCTION__); \
if (transaction_retries++ <= MAX_TRANSACTION_COMMIT_RETRIES) \
goto transaction_start_label; \
TALER_LOG_WARNING ("Transaction commit failed %u times in %s\n", \
transaction_retries, \
__FUNCTION__); \
return TEH_RESPONSE_reply_commit_error (connection, \
TALER_EC_DB_COMMIT_FAILED_ON_RETRY); \
} \
} /* end of scope opened by BEGIN_TRANSACTION */
/**
* Code to include to retry a transaction, must only be used in between
* #START_TRANSACTION and #COMMIT_TRANSACTION.
*
* @param session session handle
* @param connection connection handle
*/
#define RETRY_TRANSACTION(session,connection) \
do { \
TEH_plugin->rollback (TEH_plugin->cls, \
session); \
if (transaction_retries++ <= MAX_TRANSACTION_COMMIT_RETRIES) \
goto transaction_start_label; \
TALER_LOG_WARNING ("Transaction commit failed %u times in %s\n", \
transaction_retries, \
__FUNCTION__); \
return TEH_RESPONSE_reply_commit_error (connection, \
TALER_EC_DB_COMMIT_FAILED_ON_RETRY); \
} while (0)
/**
* 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)
{
int newcoin_index;
json_t *root;
json_t *obj;
json_t *list;
int ret;
list = json_array ();
for (newcoin_index = 0; newcoin_index < num_newcoins; newcoin_index++)
{
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));
}
root = json_object ();
json_object_set_new (root,
"ev_sigs",
list);
ret = TEH_RESPONSE_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 session info about session
* @param commit_coins array of @a num_newcoins committed envelopes at offset @a gamma
* @param denom_pubs array of @a num_newcoins denomination keys for the new coins
* @param gamma_tp transfer public key at offset @a gamma
* @return a MHD result code
*/
static int
reply_refresh_reveal_missmatch (struct MHD_Connection *connection,
const struct TALER_EXCHANGEDB_RefreshSession *session,
const struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins,
const struct TALER_DenominationPublicKey *denom_pubs,
const struct TALER_TransferPublicKeyP *gamma_tp)
{
json_t *info_new;
json_t *info_commit_k;
unsigned int i;
info_new = json_array ();
info_commit_k = json_array ();
for (i=0;i<session->num_newcoins;i++)
{
const struct TALER_EXCHANGEDB_RefreshCommitCoin *cc;
json_t *cc_json;
GNUNET_assert (0 ==
json_array_append_new (info_new,
GNUNET_JSON_from_rsa_public_key (denom_pubs[i].rsa_public_key)));
cc = &commit_coins[i];
cc_json = json_pack ("{s:o}",
"coin_ev",
GNUNET_JSON_from_data (cc->coin_ev,
cc->coin_ev_size));
GNUNET_assert (0 ==
json_array_append_new (info_commit_k,
cc_json));
}
return TEH_RESPONSE_reply_json_pack (connection,
MHD_HTTP_CONFLICT,
"{s:s, s:I, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:i}",
"error", "commitment violation",
"code", (json_int_t) TALER_EC_REFRESH_REVEAL_COMMITMENT_VIOLATION,
"coin_sig", GNUNET_JSON_from_data_auto (&session->melt.coin_sig),
"coin_pub", GNUNET_JSON_from_data_auto (&session->melt.coin.coin_pub),
"melt_amount_with_fee", TALER_JSON_from_amount (&session->melt.amount_with_fee),
"melt_fee", TALER_JSON_from_amount (&session->melt.melt_fee),
"newcoin_infos", info_new,
"commit_infos", info_commit_k,
"gamma_tp", GNUNET_JSON_from_data_auto (gamma_tp),
"gamma", (int) session->noreveal_index);
}
/**
* Check if the given @a transfer_privs correspond to an honest
* commitment for the given session.
* Checks that the transfer private keys match their commitments.
* Then derives the shared secret for each #TALER_CNC_KAPPA, and check that they match.
*
* @param connection the MHD connection to handle
* @param session database connection to use
* @param session_hash hash of session to query
* @param off commitment offset to check
* @param transfer_priv private transfer key
* @param melt information about the melted coin
* @param num_newcoins number of newcoins being generated
* @param denom_pubs array of @a num_newcoins keys for the new coins
* @param hash_context hash context to update by hashing in the data
* from this offset
* @return #GNUNET_OK if the committment was honest,
* #GNUNET_NO if there was a problem and we generated an error message
* #GNUNET_SYSERR if we could not even generate an error message
*/
static int
check_commitment (struct MHD_Connection *connection,
struct TALER_EXCHANGEDB_Session *session,
const struct GNUNET_HashCode *session_hash,
unsigned int off,
const struct TALER_TransferPrivateKeyP *transfer_priv,
const struct TALER_EXCHANGEDB_RefreshMelt *melt,
unsigned int num_newcoins,
const struct TALER_DenominationPublicKey *denom_pubs,
struct GNUNET_HashContext *hash_context)
{
struct TALER_TransferSecretP transfer_secret;
unsigned int j;
TALER_link_reveal_transfer_secret (transfer_priv,
&melt->coin.coin_pub,
&transfer_secret);
/* Check that the commitments for all new coins were correct */
for (j = 0; j < num_newcoins; j++)
{
struct TALER_FreshCoinP fc;
struct TALER_CoinSpendPublicKeyP coin_pub;
struct GNUNET_HashCode h_msg;
char *buf;
size_t buf_len;
TALER_setup_fresh_coin (&transfer_secret,
j,
&fc);
GNUNET_CRYPTO_eddsa_key_get_public (&fc.coin_priv.eddsa_priv,
&coin_pub.eddsa_pub);
GNUNET_CRYPTO_hash (&coin_pub,
sizeof (struct TALER_CoinSpendPublicKeyP),
&h_msg);
if (GNUNET_YES !=
GNUNET_CRYPTO_rsa_blind (&h_msg,
&fc.blinding_key.bks,
denom_pubs[j].rsa_public_key,
&buf,
&buf_len))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Blind failed (bad denomination key!?)\n");
return (MHD_YES ==
TEH_RESPONSE_reply_internal_error (connection,
TALER_EC_REFRESH_REVEAL_BLINDING_ERROR,
"Blinding error"))
? GNUNET_NO : GNUNET_SYSERR;
}
GNUNET_CRYPTO_hash_context_read (hash_context,
buf,
buf_len);
GNUNET_free (buf);
}
return GNUNET_OK;
}
/**
* Exchange a coin as part of a refresh operation. Obtains the
* envelope from the database and performs the signing operation.
*
* @param connection the MHD connection to handle
* @param session database connection to use
* @param session_hash hash of session to query
* @param key_state key state to lookup denomination pubs
* @param denom_pub denomination key for the coin to create
* @param commit_coin the coin that was committed
* @param coin_off number of the coin
* @return NULL on error, otherwise signature over the coin
*/
static struct TALER_DenominationSignature
refresh_exchange_coin (struct MHD_Connection *connection,
struct TALER_EXCHANGEDB_Session *session,
const struct GNUNET_HashCode *session_hash,
struct TEH_KS_StateHandle *key_state,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coin,
unsigned int coin_off)
{
struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
struct TALER_DenominationSignature ev_sig;
dki = TEH_KS_denomination_key_lookup (key_state,
denom_pub,
TEH_KS_DKU_WITHDRAW);
if (NULL == dki)
{
GNUNET_break (0);
ev_sig.rsa_signature = NULL;
return ev_sig;
}
if (GNUNET_OK ==
TEH_plugin->get_refresh_out (TEH_plugin->cls,
session,
session_hash,
coin_off,
&ev_sig))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Returning cached reply for /refresh/reveal signature\n");
return ev_sig;
}
ev_sig.rsa_signature
= GNUNET_CRYPTO_rsa_sign_blinded (dki->denom_priv.rsa_private_key,
commit_coin->coin_ev,
commit_coin->coin_ev_size);
if (NULL == ev_sig.rsa_signature)
{
GNUNET_break (0);
return ev_sig;
}
if (GNUNET_SYSERR ==
TEH_plugin->insert_refresh_out (TEH_plugin->cls,
session,
session_hash,
coin_off,
&ev_sig))
{
GNUNET_break (0);
GNUNET_CRYPTO_rsa_signature_free (ev_sig.rsa_signature);
ev_sig.rsa_signature = NULL;
}
return ev_sig;
}
/**
* The client request was well-formed, now execute the DB transaction
* of a "/refresh/reveal" operation. We use the @a ev_sigs and
* @a commit_coins to clean up resources after this function returns
* as we might experience retries of the database transaction.
*
* @param connection the MHD connection to handle
* @param session database session
* @param session_hash hash identifying the refresh session
* @param refresh_session information about the refresh operation we are doing
* @param denom_pubs array of "num_newcoins" denomination keys for the new coins
* @param[out] ev_sigs where to store generated signatures for the new coins,
* array of length "num_newcoins", memory released by the
* caller
* @param[out] commit_coins array of length "num_newcoins" to be used for
* information about the new coins from the commitment.
* @return MHD result code
*/
static int
execute_refresh_reveal_transaction (struct MHD_Connection *connection,
struct TALER_EXCHANGEDB_Session *session,
const struct GNUNET_HashCode *session_hash,
const struct TALER_EXCHANGEDB_RefreshSession *refresh_session,
const struct TALER_DenominationPublicKey *denom_pubs,
struct TALER_DenominationSignature *ev_sigs,
struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins)
{
unsigned int j;
struct TEH_KS_StateHandle *key_state;
int ret;
START_TRANSACTION (session, connection);
key_state = TEH_KS_acquire ();
for (j=0;j<refresh_session->num_newcoins;j++)
{
if (NULL == ev_sigs[j].rsa_signature) /* could be non-NULL during retries */
ev_sigs[j] = refresh_exchange_coin (connection,
session,
session_hash,
key_state,
&denom_pubs[j],
&commit_coins[j],
j);
if (NULL == ev_sigs[j].rsa_signature)
{
TEH_plugin->rollback (TEH_plugin->cls,
session);
ret = TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_REVEAL_SIGNING_ERROR);
goto cleanup;
}
}
COMMIT_TRANSACTION (session, connection);
ret = reply_refresh_reveal_success (connection,
refresh_session->num_newcoins,
ev_sigs);
cleanup:
TEH_KS_release (key_state);
return ret;
}
/**
* 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,
* and if so, return the signed coins for corresponding to the set of
* coins that was not chosen.
*
* @param connection the MHD connection to handle
* @param session_hash hash identifying the refresh session
* @param transfer_privs array with the revealed transfer keys,
* length must be #TALER_CNC_KAPPA - 1
* @return MHD result code
*/
static int
execute_refresh_reveal (struct MHD_Connection *connection,
const struct GNUNET_HashCode *session_hash,
struct TALER_TransferPrivateKeyP *transfer_privs)
{
int res;
struct TALER_EXCHANGEDB_Session *session;
struct TALER_EXCHANGEDB_RefreshSession refresh_session;
struct TALER_DenominationPublicKey *denom_pubs;
struct TALER_DenominationSignature *ev_sigs;
struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins;
unsigned int i;
unsigned int j;
unsigned int off;
struct GNUNET_HashContext *hash_context;
struct GNUNET_HashCode sh_check;
int ret;
struct TALER_TransferPublicKeyP gamma_tp;
if (NULL == (session = TEH_plugin->get_session (TEH_plugin->cls)))
{
GNUNET_break (0);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_DB_SETUP_FAILED);
}
res = TEH_plugin->get_refresh_session (TEH_plugin->cls,
session,
session_hash,
&refresh_session);
if (GNUNET_NO == res)
return TEH_RESPONSE_reply_arg_invalid (connection,
TALER_EC_REFRESH_REVEAL_SESSION_UNKNOWN,
"session_hash");
if ( (GNUNET_SYSERR == res) ||
(refresh_session.noreveal_index >= TALER_CNC_KAPPA) )
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_REVEAL_DB_FETCH_SESSION_ERROR);
denom_pubs = GNUNET_new_array (refresh_session.num_newcoins,
struct TALER_DenominationPublicKey);
if (GNUNET_OK !=
TEH_plugin->get_refresh_order (TEH_plugin->cls,
session,
session_hash,
refresh_session.num_newcoins,
denom_pubs))
{
GNUNET_break (0);
GNUNET_free (denom_pubs);
GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature);
GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key);
return (MHD_YES == TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_REVEAL_DB_FETCH_ORDER_ERROR))
? GNUNET_NO : GNUNET_SYSERR;
}
hash_context = GNUNET_CRYPTO_hash_context_start ();
/* first, iterate over transfer public keys for hash_context */
off = 0;
for (i=0;i<TALER_CNC_KAPPA;i++)
{
if (i == refresh_session.noreveal_index)
{
off = 1;
/* obtain gamma_tp from db */
if (GNUNET_OK !=
TEH_plugin->get_refresh_transfer_public_key (TEH_plugin->cls,
session,
session_hash,
&gamma_tp))
{
GNUNET_break (0);
GNUNET_free (denom_pubs);
GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature);
GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key);
GNUNET_CRYPTO_hash_context_abort (hash_context);
return (MHD_YES == TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_REVEAL_DB_FETCH_TRANSFER_ERROR))
? GNUNET_NO : GNUNET_SYSERR;
}
GNUNET_CRYPTO_hash_context_read (hash_context,
&gamma_tp,
sizeof (struct TALER_TransferPublicKeyP));
}
else
{
/* compute tp from private key */
struct TALER_TransferPublicKeyP tp;
GNUNET_CRYPTO_ecdhe_key_get_public (&transfer_privs[i - off].ecdhe_priv,
&tp.ecdhe_pub);
GNUNET_CRYPTO_hash_context_read (hash_context,
&tp,
sizeof (struct TALER_TransferPublicKeyP));
}
}
/* next, add all of the hashes from the denomination keys to the
hash_context */
{
struct TALER_DenominationPublicKey denom_pubs[refresh_session.num_newcoins];
if (GNUNET_OK !=
TEH_plugin->get_refresh_order (TEH_plugin->cls,
session,
session_hash,
refresh_session.num_newcoins,
denom_pubs))
{
GNUNET_break (0);
GNUNET_free (denom_pubs);
GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature);
GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key);
GNUNET_CRYPTO_hash_context_abort (hash_context);
return (MHD_YES == TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_REVEAL_DB_FETCH_ORDER_ERROR))
? GNUNET_NO : GNUNET_SYSERR;
}
for (i=0;i<refresh_session.num_newcoins;i++)
{
char *buf;
size_t buf_size;
buf_size = GNUNET_CRYPTO_rsa_public_key_encode (denom_pubs[i].rsa_public_key,
&buf);
GNUNET_CRYPTO_hash_context_read (hash_context,
buf,
buf_size);
GNUNET_free (buf);
GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[i].rsa_public_key);
}
}
/* next, add public key of coin and amount being refreshed */
{
struct TALER_AmountNBO melt_amountn;
GNUNET_CRYPTO_hash_context_read (hash_context,
&refresh_session.melt.coin.coin_pub,
sizeof (struct TALER_CoinSpendPublicKeyP));
TALER_amount_hton (&melt_amountn,
&refresh_session.melt.amount_with_fee);
GNUNET_CRYPTO_hash_context_read (hash_context,
&melt_amountn,
sizeof (struct TALER_AmountNBO));
}
commit_coins = GNUNET_new_array (refresh_session.num_newcoins,
struct TALER_EXCHANGEDB_RefreshCommitCoin);
off = 0;
for (i=0;i<TALER_CNC_KAPPA;i++)
{
if (i == refresh_session.noreveal_index)
{
off = 1;
/* obtain commit_coins for the selected gamma value from DB */
if (GNUNET_OK !=
TEH_plugin->get_refresh_commit_coins (TEH_plugin->cls,
session,
session_hash,
refresh_session.num_newcoins,
commit_coins))
{
GNUNET_break (0);
GNUNET_free (denom_pubs);
GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature);
GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key);
GNUNET_CRYPTO_hash_context_abort (hash_context);
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_REVEAL_DB_FETCH_COMMIT_ERROR);
}
/* add envelopes to hash_context */
for (j=0;j<refresh_session.num_newcoins;j++)
{
GNUNET_CRYPTO_hash_context_read (hash_context,
commit_coins[j].coin_ev,
commit_coins[j].coin_ev_size);
}
continue;
}
if (GNUNET_OK !=
(res = check_commitment (connection,
session,
session_hash,
i,
&transfer_privs[i - off],
&refresh_session.melt,
refresh_session.num_newcoins,
denom_pubs,
hash_context)))
{
GNUNET_break_op (0);
for (j=0;j<refresh_session.num_newcoins;j++)
{
GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key);
GNUNET_free (commit_coins[j].coin_ev);
}
GNUNET_free (commit_coins);
GNUNET_free (denom_pubs);
GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature);
GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key);
GNUNET_CRYPTO_hash_context_abort (hash_context);
return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
}
}
/* Check session hash matches */
GNUNET_CRYPTO_hash_context_finish (hash_context,
&sh_check);
if (0 != memcmp (&sh_check,
session_hash,
sizeof (struct GNUNET_HashCode)))
{
GNUNET_break_op (0);
ret = reply_refresh_reveal_missmatch (connection,
&refresh_session,
commit_coins,
denom_pubs,
&gamma_tp);
for (j=0;j<refresh_session.num_newcoins;j++)
{
GNUNET_free (commit_coins[j].coin_ev);
GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key);
}
GNUNET_free (commit_coins);
GNUNET_free (denom_pubs);
GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature);
GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key);
return ret;
}
/* Client request OK, start transaction */
ev_sigs = GNUNET_new_array (refresh_session.num_newcoins,
struct TALER_DenominationSignature);
/* FIXME: might need to store revealed transfer private keys for
the auditor for later; should pass them as arguments here! #4792*/
res = execute_refresh_reveal_transaction (connection,
session,
session_hash,
&refresh_session,
denom_pubs,
ev_sigs,
commit_coins);
for (i=0;i<refresh_session.num_newcoins;i++)
{
if (NULL != ev_sigs[i].rsa_signature)
GNUNET_CRYPTO_rsa_signature_free (ev_sigs[i].rsa_signature);
GNUNET_free (commit_coins[i].coin_ev);
}
for (j=0;j<refresh_session.num_newcoins;j++)
if (NULL != denom_pubs[j].rsa_public_key)
GNUNET_CRYPTO_rsa_public_key_free (denom_pubs[j].rsa_public_key);
GNUNET_CRYPTO_rsa_signature_free (refresh_session.melt.coin.denom_sig.rsa_signature);
GNUNET_CRYPTO_rsa_public_key_free (refresh_session.melt.coin.denom_pub.rsa_public_key);
GNUNET_free (ev_sigs);
GNUNET_free (denom_pubs);
GNUNET_free (commit_coins);
return res;
}
/**
* Handle a "/refresh/reveal" request. Parses the given JSON
* transfer private keys and if successful, passes everything to
* #TEH_DB_execute_refresh_reveal() which will verify that the
* revealed information is valid then returns the signed refreshed
* coins.
*
* @param connection the MHD connection to handle
* @param session_hash hash identifying the melting session
* @param tp_json private transfer keys in JSON format
* @return MHD result code
*/
static int
handle_refresh_reveal_json (struct MHD_Connection *connection,
const struct GNUNET_HashCode *session_hash,
const json_t *tp_json)
{
struct TALER_TransferPrivateKeyP transfer_privs[TALER_CNC_KAPPA - 1];
unsigned int i;
int res;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"reveal request for session %s\n",
GNUNET_h2s (session_hash));
res = GNUNET_OK;
for (i = 0; i < TALER_CNC_KAPPA - 1; i++)
{
struct GNUNET_JSON_Specification tp_spec[] = {
GNUNET_JSON_spec_fixed_auto (NULL, &transfer_privs[i]),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK != res)
break;
res = TEH_PARSE_json_array (connection,
tp_json,
tp_spec,
i, -1);
GNUNET_break_op (GNUNET_OK == res);
}
if (GNUNET_OK != res)
res = (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
else
res = execute_refresh_reveal (connection,
session_hash,
transfer_privs);
return res;
}
/**
* Handle a "/refresh/reveal" request. This time, the client reveals
* the private transfer keys except for the cut-and-choose value
* returned from "/refresh/melt". This function parses the revealed
* keys and secrets and ultimately passes everything to
* #TEH_DB_execute_refresh_reveal() which will verify that the
* revealed information is valid then returns the signed refreshed
* coins.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
* @param[in,out] connection_cls the connection's closure (can be updated)
* @param upload_data upload data
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
int
TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size)
{
struct GNUNET_HashCode session_hash;
int res;
json_t *root;
json_t *transfer_privs;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("session_hash", &session_hash),
GNUNET_JSON_spec_json ("transfer_privs", &transfer_privs),
GNUNET_JSON_spec_end ()
};
res = TEH_PARSE_post_json (connection,
connection_cls,
upload_data,
upload_data_size,
&root);
if (GNUNET_SYSERR == res)
return MHD_NO;
if ( (GNUNET_NO == res) || (NULL == root) )
return MHD_YES;
res = TEH_PARSE_json_data (connection,
root,
spec);
json_decref (root);
if (GNUNET_OK != res)
{
GNUNET_break_op (0);
return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
}
/* Determine dimensionality of the request (kappa and #old coins) */
/* 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 TEH_RESPONSE_reply_arg_invalid (connection,
TALER_EC_REFRESH_REVEAL_CNC_TRANSFER_ARRAY_SIZE_INVALID,
"transfer_privs");
}
res = handle_refresh_reveal_json (connection,
&session_hash,
transfer_privs);
GNUNET_JSON_parse_free (spec);
return res;
}
/* end of taler-exchange-httpd_refresh_reveal.c */

View File

@ -1,6 +1,6 @@
/*
This file is part of TALER
Copyright (C) 2014, 2015 GNUnet e.V.
Copyright (C) 2014-2017 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
@ -14,41 +14,20 @@
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file taler-exchange-httpd_refresh.h
* @brief Handle /refresh/ requests
* @file taler-exchange-httpd_refresh_reveal.h
* @brief Handle /refresh/reveal requests
* @author Florian Dold
* @author Benedikt Mueller
* @author Christian Grothoff
*/
#ifndef TALER_EXCHANGE_HTTPD_REFRESH_H
#define TALER_EXCHANGE_HTTPD_REFRESH_H
#ifndef TALER_EXCHANGE_HTTPD_REFRESH_REVEAL_H
#define TALER_EXCHANGE_HTTPD_REFRESH_REVEAL_H
#include <gnunet/gnunet_util_lib.h>
#include <microhttpd.h>
#include "taler-exchange-httpd.h"
/**
* Handle a "/refresh/melt" request. Parses the request into the JSON
* components and then hands things of to #handle_refresh_melt_json()
* to validate the melted coins, the signature and execute the melt
* using TEH_DB_execute_refresh_melt().
*
* @param rh context of the handler
* @param connection the MHD connection to handle
* @param[in,out] connection_cls the connection's closure (can be updated)
* @param upload_data upload data
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
int
TEH_REFRESH_handler_refresh_melt (struct TEH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size);
/**
* Handle a "/refresh/reveal" request. This time, the client reveals
* the private transfer keys except for the cut-and-choose value
@ -72,23 +51,4 @@ TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh,
const char *upload_data,
size_t *upload_data_size);
/**
* Handle a "/refresh/link" request
*
* @param rh context of the handler
* @param connection the MHD connection to handle
* @param[in,out] connection_cls the connection's closure (can be updated)
* @param upload_data upload data
* @param[in,out] upload_data_size number of bytes (left) in @a upload_data
* @return MHD result code
*/
int
TEH_REFRESH_handler_refresh_link (struct TEH_RequestHandler *rh,
struct MHD_Connection *connection,
void **connection_cls,
const char *upload_data,
size_t *upload_data_size);
#endif

View File

@ -878,251 +878,6 @@ TEH_RESPONSE_compile_reserve_history (const struct TALER_EXCHANGEDB_ReserveHisto
}
/**
* Send a response for a failed "/refresh/melt" request. The
* transaction history of the given coin demonstrates that the
* @a residual value of the coin is below the @a requested
* contribution of the coin for the melt. Thus, the exchange
* refuses the melt operation.
*
* @param connection the connection to send the response to
* @param coin_pub public key of the coin
* @param coin_value original value of the coin
* @param tl transaction history for the coin
* @param requested how much this coin was supposed to contribute, including fee
* @param residual remaining value of the coin (after subtracting @a tl)
* @return a MHD result code
*/
int
TEH_RESPONSE_reply_refresh_melt_insufficient_funds (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
struct TALER_Amount coin_value,
struct TALER_EXCHANGEDB_TransactionList *tl,
struct TALER_Amount requested,
struct TALER_Amount residual)
{
json_t *history;
history = TEH_RESPONSE_compile_transaction_history (tl);
if (NULL == history)
return TEH_RESPONSE_reply_internal_db_error (connection,
TALER_EC_REFRESH_MELT_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS);
return TEH_RESPONSE_reply_json_pack (connection,
MHD_HTTP_FORBIDDEN,
"{s:s, s:I, s:o, s:o, s:o, s:o, s:o}",
"error",
"insufficient funds",
"code",
(json_int_t) TALER_EC_REFRESH_MELT_INSUFFICIENT_FUNDS,
"coin_pub",
GNUNET_JSON_from_data_auto (coin_pub),
"original_value",
TALER_JSON_from_amount (&coin_value),
"residual_value",
TALER_JSON_from_amount (&residual),
"requested_value",
TALER_JSON_from_amount (&requested),
"history",
history);
}
/**
* Send a response to a "/refresh/melt" request.
*
* @param connection the connection to send the response to
* @param session_hash hash of the refresh session
* @param noreveal_index which index will the client not have to reveal
* @return a MHD status code
*/
int
TEH_RESPONSE_reply_refresh_melt_success (struct MHD_Connection *connection,
const struct GNUNET_HashCode *session_hash,
uint16_t noreveal_index)
{
struct TALER_RefreshMeltConfirmationPS body;
struct TALER_ExchangePublicKeyP pub;
struct TALER_ExchangeSignatureP sig;
json_t *sig_json;
body.purpose.size = htonl (sizeof (struct TALER_RefreshMeltConfirmationPS));
body.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT);
body.session_hash = *session_hash;
body.noreveal_index = htons (noreveal_index);
body.reserved = htons (0);
TEH_KS_sign (&body.purpose,
&pub,
&sig);
sig_json = GNUNET_JSON_from_data_auto (&sig);
GNUNET_assert (NULL != sig_json);
return TEH_RESPONSE_reply_json_pack (connection,
MHD_HTTP_OK,
"{s:i, s:o, s:o}",
"noreveal_index", (int) noreveal_index,
"exchange_sig", sig_json,
"exchange_pub", GNUNET_JSON_from_data_auto (&pub));
}
/**
* 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
*/
int
TEH_RESPONSE_reply_refresh_reveal_success (struct MHD_Connection *connection,
unsigned int num_newcoins,
const struct TALER_DenominationSignature *sigs)
{
int newcoin_index;
json_t *root;
json_t *obj;
json_t *list;
int ret;
list = json_array ();
for (newcoin_index = 0; newcoin_index < num_newcoins; newcoin_index++)
{
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));
}
root = json_object ();
json_object_set_new (root,
"ev_sigs",
list);
ret = TEH_RESPONSE_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 session info about session
* @param commit_coins array of @a num_newcoins committed envelopes at offset @a gamma
* @param denom_pubs array of @a num_newcoins denomination keys for the new coins
* @param gamma_tp transfer public key at offset @a gamma
* @return a MHD result code
*/
int
TEH_RESPONSE_reply_refresh_reveal_missmatch (struct MHD_Connection *connection,
const struct TALER_EXCHANGEDB_RefreshSession *session,
const struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins,
const struct TALER_DenominationPublicKey *denom_pubs,
const struct TALER_TransferPublicKeyP *gamma_tp)
{
json_t *info_new;
json_t *info_commit_k;
unsigned int i;
info_new = json_array ();
info_commit_k = json_array ();
for (i=0;i<session->num_newcoins;i++)
{
const struct TALER_EXCHANGEDB_RefreshCommitCoin *cc;
json_t *cc_json;
GNUNET_assert (0 ==
json_array_append_new (info_new,
GNUNET_JSON_from_rsa_public_key (denom_pubs[i].rsa_public_key)));
cc = &commit_coins[i];
cc_json = json_pack ("{s:o}",
"coin_ev",
GNUNET_JSON_from_data (cc->coin_ev,
cc->coin_ev_size));
GNUNET_assert (0 ==
json_array_append_new (info_commit_k,
cc_json));
}
return TEH_RESPONSE_reply_json_pack (connection,
MHD_HTTP_CONFLICT,
"{s:s, s:I, s:o, s:o, s:o, s:o, s:o, s:o, s:o, s:i}",
"error", "commitment violation",
"code", (json_int_t) TALER_EC_REFRESH_REVEAL_COMMITMENT_VIOLATION,
"coin_sig", GNUNET_JSON_from_data_auto (&session->melt.coin_sig),
"coin_pub", GNUNET_JSON_from_data_auto (&session->melt.coin.coin_pub),
"melt_amount_with_fee", TALER_JSON_from_amount (&session->melt.amount_with_fee),
"melt_fee", TALER_JSON_from_amount (&session->melt.melt_fee),
"newcoin_infos", info_new,
"commit_infos", info_commit_k,
"gamma_tp", GNUNET_JSON_from_data_auto (gamma_tp),
"gamma", (int) session->noreveal_index);
}
/**
* Send a response for "/refresh/link".
*
* @param connection the connection to send the response to
* @param num_sessions number of sessions the coin was used in
* @param sessions array of @a num_session entries with
* information for each session
* @return a MHD result code
*/
int
TEH_RESPONSE_reply_refresh_link_success (struct MHD_Connection *connection,
unsigned int num_sessions,
const struct TEH_RESPONSE_LinkSessionInfo *sessions)
{
json_t *root;
json_t *mlist;
int res;
unsigned int i;
mlist = json_array ();
for (i=0;i<num_sessions;i++)
{
const struct TALER_EXCHANGEDB_LinkDataList *pos;
json_t *list = json_array ();
for (pos = sessions[i].ldl; NULL != pos; pos = pos->next)
{
json_t *obj;
obj = json_object ();
json_object_set_new (obj,
"denom_pub",
GNUNET_JSON_from_rsa_public_key (pos->denom_pub.rsa_public_key));
json_object_set_new (obj,
"ev_sig",
GNUNET_JSON_from_rsa_signature (pos->ev_sig.rsa_signature));
GNUNET_assert (0 ==
json_array_append_new (list,
obj));
}
root = json_object ();
json_object_set_new (root,
"new_coins",
list);
json_object_set_new (root,
"transfer_pub",
GNUNET_JSON_from_data_auto (&sessions[i].transfer_pub));
GNUNET_assert (0 ==
json_array_append_new (mlist,
root));
}
res = TEH_RESPONSE_reply_json (connection,
mlist,
MHD_HTTP_OK);
json_decref (mlist);
return res;
}
/**
* A merchant asked for details about a deposit, but
* we do not know anything about the deposit. Generate the

View File

@ -300,111 +300,6 @@ TEH_RESPONSE_reply_transfer_pending (struct MHD_Connection *connection,
struct GNUNET_TIME_Absolute planned_exec_time);
/**
* Send a confirmation response to a "/refresh/melt" request.
*
* @param connection the connection to send the response to
* @param session_hash hash of the refresh session
* @param noreveal_index which index will the client not have to reveal
* @return a MHD status code
*/
int
TEH_RESPONSE_reply_refresh_melt_success (struct MHD_Connection *connection,
const struct GNUNET_HashCode *session_hash,
uint16_t noreveal_index);
/**
* Send a response for a failed "/refresh/melt" request. The
* transaction history of the given coin demonstrates that the
* @a residual value of the coin is below the @a requested
* contribution of the coin for the melt. Thus, the exchange
* refuses the melt operation.
*
* @param connection the connection to send the response to
* @param coin_pub public key of the coin
* @param coin_value original value of the coin
* @param tl transaction history for the coin
* @param requested how much this coin was supposed to contribute
* @param residual remaining value of the coin (after subtracting @a tl)
* @return a MHD result code
*/
int
TEH_RESPONSE_reply_refresh_melt_insufficient_funds (struct MHD_Connection *connection,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
struct TALER_Amount coin_value,
struct TALER_EXCHANGEDB_TransactionList *tl,
struct TALER_Amount requested,
struct TALER_Amount residual);
/**
* 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
*/
int
TEH_RESPONSE_reply_refresh_reveal_success (struct MHD_Connection *connection,
unsigned int num_newcoins,
const struct TALER_DenominationSignature *sigs);
/**
* 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 session info about session
* @param commit_coins array of @a num_newcoins committed envelopes at offset @a gamma
* @param denom_pubs array of @a num_newcoins denomination keys for the new coins
* @param gamma_tp transfer public key at offset @a gamma
* @return a MHD result code
*/
int
TEH_RESPONSE_reply_refresh_reveal_missmatch (struct MHD_Connection *connection,
const struct TALER_EXCHANGEDB_RefreshSession *session,
const struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins,
const struct TALER_DenominationPublicKey *denom_pubs,
const struct TALER_TransferPublicKeyP *gamma_tp);
/**
* @brief Information for each session a coin was melted into.
*/
struct TEH_RESPONSE_LinkSessionInfo
{
/**
* Transfer public key of the coin.
*/
struct TALER_TransferPublicKeyP transfer_pub;
/**
* Linked data of coins being created in the session.
*/
struct TALER_EXCHANGEDB_LinkDataList *ldl;
};
/**
* Send a response for "/refresh/link".
*
* @param connection the connection to send the response to
* @param num_sessions number of sessions the coin was used in
* @param sessions array of @a num_session entries with
* information for each session
* @return a MHD result code
*/
int
TEH_RESPONSE_reply_refresh_link_success (struct MHD_Connection *connection,
unsigned int num_sessions,
const struct TEH_RESPONSE_LinkSessionInfo *sessions);
/**
* Compile the transaction history of a coin into a JSON object.
*