diff options
author | Christian Grothoff <christian@grothoff.org> | 2017-11-27 23:42:17 +0100 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2017-11-29 20:23:08 +0100 |
commit | 499247a4805583dc67b9d6fef850ae86b4be1e32 (patch) | |
tree | abf9bf358bc00149a78d8128101bfc43540af8f2 /src/exchange | |
parent | 9041840d6e1caa5a0a4f8222b312b547ccd2ab1b (diff) | |
download | exchange-499247a4805583dc67b9d6fef850ae86b4be1e32.tar.gz exchange-499247a4805583dc67b9d6fef850ae86b4be1e32.tar.bz2 exchange-499247a4805583dc67b9d6fef850ae86b4be1e32.zip |
fixing #5178
Diffstat (limited to 'src/exchange')
-rw-r--r-- | src/exchange/taler-exchange-httpd_db.c | 2 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_keystate.c | 40 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_keystate.h | 16 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_refresh_link.c | 240 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_refresh_melt.c | 706 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_refresh_reveal.c | 860 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_responses.c | 20 |
7 files changed, 656 insertions, 1228 deletions
diff --git a/src/exchange/taler-exchange-httpd_db.c b/src/exchange/taler-exchange-httpd_db.c index 30bc33e17..bd7777ce3 100644 --- a/src/exchange/taler-exchange-httpd_db.c +++ b/src/exchange/taler-exchange-httpd_db.c @@ -155,7 +155,7 @@ TEH_DB_calculate_transaction_list_totals (struct TALER_EXCHANGEDB_TransactionLis if (GNUNET_OK != TALER_amount_add (&spent, &spent, - &pos->details.melt->amount_with_fee)) + &pos->details.melt->session.amount_with_fee)) { GNUNET_break (0); return GNUNET_SYSERR; diff --git a/src/exchange/taler-exchange-httpd_keystate.c b/src/exchange/taler-exchange-httpd_keystate.c index be87f6d49..801d6fee6 100644 --- a/src/exchange/taler-exchange-httpd_keystate.c +++ b/src/exchange/taler-exchange-httpd_keystate.c @@ -39,7 +39,7 @@ * release version, and the format is NOT the same that semantic * versioning uses either. */ -#define TALER_PROTOCOL_VERSION "1:0:1" +#define TALER_PROTOCOL_VERSION "2:0:0" /** @@ -1499,7 +1499,7 @@ make_fresh_key_state () GNUNET_h2s (&dke->denom_key_hash), dke); } - + /* Determine size of `krd_array` by counting number of discrete denomination key starting times. */ last = GNUNET_TIME_UNIT_ZERO_ABS; @@ -1662,15 +1662,37 @@ TEH_KS_denomination_key_lookup (const struct TEH_KS_StateHandle *key_state, enum TEH_KS_DenominationKeyUse use) { struct GNUNET_HashCode hc; + + GNUNET_CRYPTO_rsa_public_key_hash (denom_pub->rsa_public_key, + &hc); + return TEH_KS_denomination_key_lookup_by_hash (key_state, + &hc, + use); +} + + +/** + * Look up the issue for a denom public key. Note that the result + * is only valid while the @a key_state is not released! + * + * @param key_state state to look in + * @param denom_pub_hash hash of denomination public key + * @param use purpose for which the key is being located + * @return the denomination key issue, + * or NULL if denom_pub could not be found (or is not valid at this time for the given @a use) + */ +struct TALER_EXCHANGEDB_DenominationKeyIssueInformation * +TEH_KS_denomination_key_lookup_by_hash (const struct TEH_KS_StateHandle *key_state, + const struct GNUNET_HashCode *denom_pub_hash, + enum TEH_KS_DenominationKeyUse use) +{ struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; struct GNUNET_TIME_Absolute now; const struct GNUNET_CONTAINER_MultiHashMap *map; - GNUNET_CRYPTO_rsa_public_key_hash (denom_pub->rsa_public_key, - &hc); map = (TEH_KS_DKU_PAYBACK == use) ? key_state->revoked_map : key_state->denomkey_map; dki = GNUNET_CONTAINER_multihashmap_get (map, - &hc); + denom_pub_hash); if (NULL == dki) return NULL; now = GNUNET_TIME_absolute_get (); @@ -1679,7 +1701,7 @@ TEH_KS_denomination_key_lookup (const struct TEH_KS_StateHandle *key_state, { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Not returning DKI for %s, as start time is in the future\n", - GNUNET_h2s (&hc)); + GNUNET_h2s (denom_pub_hash)); return NULL; } now = GNUNET_TIME_absolute_get (); @@ -1691,7 +1713,7 @@ TEH_KS_denomination_key_lookup (const struct TEH_KS_StateHandle *key_state, { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Not returning DKI for %s, as time to create coins has passed\n", - GNUNET_h2s (&hc)); + GNUNET_h2s (denom_pub_hash)); return NULL; } break; @@ -1701,7 +1723,7 @@ TEH_KS_denomination_key_lookup (const struct TEH_KS_StateHandle *key_state, { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Not returning DKI for %s, as time to spend coin has passed\n", - GNUNET_h2s (&hc)); + GNUNET_h2s (denom_pub_hash)); return NULL; } break; @@ -1711,7 +1733,7 @@ TEH_KS_denomination_key_lookup (const struct TEH_KS_StateHandle *key_state, { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Not returning DKI for %s, as time to payback coin has passed\n", - GNUNET_h2s (&hc)); + GNUNET_h2s (denom_pub_hash)); return NULL; } break; diff --git a/src/exchange/taler-exchange-httpd_keystate.h b/src/exchange/taler-exchange-httpd_keystate.h index b956c6308..c332182e0 100644 --- a/src/exchange/taler-exchange-httpd_keystate.h +++ b/src/exchange/taler-exchange-httpd_keystate.h @@ -121,6 +121,22 @@ TEH_KS_denomination_key_lookup (const struct TEH_KS_StateHandle *key_state, /** + * Look up the issue for a denom public key. Note that the result + * is only valid while the @a key_state is not released! + * + * @param key_state state to look in + * @param denom_pub_hash hash of denomination public key + * @param use purpose for which the key is being located + * @return the denomination key issue, + * or NULL if denom_pub could not be found (or is not valid at this time for the given @a use) + */ +struct TALER_EXCHANGEDB_DenominationKeyIssueInformation * +TEH_KS_denomination_key_lookup_by_hash (const struct TEH_KS_StateHandle *key_state, + const struct GNUNET_HashCode *denom_pub_hash, + enum TEH_KS_DenominationKeyUse use); + + +/** * Read signals from a pipe in a loop, and reload keys from disk if * SIGUSR1 is received, terminate if SIGTERM/SIGINT is received, and * restart if SIGHUP is received. diff --git a/src/exchange/taler-exchange-httpd_refresh_link.c b/src/exchange/taler-exchange-httpd_refresh_link.c index 0be699840..aee23369c 100644 --- a/src/exchange/taler-exchange-httpd_refresh_link.c +++ b/src/exchange/taler-exchange-httpd_refresh_link.c @@ -32,183 +32,88 @@ /** - * @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 { /** - * Public key of the coin that we are tracing. + * Public key of the coin for which we are running /refresh/link. */ struct TALER_CoinSpendPublicKeyP coin_pub; /** - * Session link data we collect. - */ - struct TEH_RESPONSE_LinkSessionInfo *sessions; - - /** - * Database session. Nothing to do with @a sessions. + * Json array with transfer data we collect. */ - 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; + json_t *mlist; /** - * 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(). + * Taler error code. */ - int status; + enum TALER_ErrorCode ec; }; /** - * 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 *mlist; - int res; - - mlist = json_array (); - for (unsigned int i=0;i<num_sessions;i++) - { - json_t *list = json_array (); - json_t *root; - - for (const struct TALER_EXCHANGEDB_LinkDataList *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 + * @param ldl link data related to @a transfer_pub */ static void -handle_transfer_data (void *cls, - const struct GNUNET_HashCode *session_hash, - const struct TALER_TransferPublicKeyP *transfer_pub) +handle_link_data (void *cls, + const struct TALER_TransferPublicKeyP *transfer_pub, + const struct TALER_EXCHANGEDB_LinkDataList *ldl) { struct HTD_Context *ctx = cls; - struct TALER_EXCHANGEDB_LinkDataList *ldl; - struct TEH_RESPONSE_LinkSessionInfo *lsi; - enum GNUNET_DB_QueryStatus qs; + json_t *list; + json_t *root; - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != ctx->status) + if (NULL == ctx->mlist) return; - ldl = NULL; - qs = TEH_plugin->get_link_data_list (TEH_plugin->cls, - ctx->session, - session_hash, - &ldl); - if (qs <= 0) + if (NULL == (list = json_array ())) + goto fail; + + for (const struct TALER_EXCHANGEDB_LinkDataList *pos = ldl; + NULL != pos; + pos = pos->next) { - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - ctx->status = GNUNET_DB_STATUS_HARD_ERROR; - else - ctx->status = qs; - return; + json_t *obj; + + if (NULL == (obj = json_object ())) + goto fail; + 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)); + if (0 != + json_array_append_new (list, + obj)) + goto fail; } - GNUNET_assert (NULL != ldl); - 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; -} - - -/** - * Free session data kept in @a ctx - * - * @param ctx context to clean up - */ -static void -purge_context (struct HTD_Context *ctx) -{ - for (unsigned int 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); - ctx->sessions = NULL; - ctx->num_sessions = 0; + if (NULL == (root = json_object ())) + goto fail; + json_object_set_new (root, + "new_coins", + list); + json_object_set_new (root, + "transfer_pub", + GNUNET_JSON_from_data_auto (transfer_pub)); + if (0 != + json_array_append_new (ctx->mlist, + root)) + goto fail; + return; + fail: + ctx->ec = TALER_EC_JSON_ALLOCATION_FAILURE; + json_decref (ctx->mlist); + ctx->mlist = NULL; } @@ -239,14 +144,18 @@ refresh_link_transaction (void *cls, struct HTD_Context *ctx = cls; enum GNUNET_DB_QueryStatus qs; - ctx->session = session; - ctx->status = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; - qs = TEH_plugin->get_transfer (TEH_plugin->cls, - session, - &ctx->coin_pub, - &handle_transfer_data, - ctx); - ctx->session = NULL; + qs = TEH_plugin->get_link_data (TEH_plugin->cls, + session, + &ctx->coin_pub, + &handle_link_data, + ctx); + if (NULL == ctx->mlist) + { + *mhd_ret = TEH_RESPONSE_reply_internal_error (connection, + ctx->ec, + "coin_pub"); + return GNUNET_DB_STATUS_HARD_ERROR; + } if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { *mhd_ret = TEH_RESPONSE_reply_arg_unknown (connection, @@ -254,21 +163,6 @@ refresh_link_transaction (void *cls, "coin_pub"); return GNUNET_DB_STATUS_HARD_ERROR; } - if (0 < qs) - { - qs = ctx->status; - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - *mhd_ret = TEH_RESPONSE_reply_json_pack (ctx->connection, - MHD_HTTP_NOT_FOUND, - "{s:s}", - "error", - "link data not found (link)"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - return qs; - } - purge_context (ctx); return qs; } @@ -306,19 +200,21 @@ TEH_REFRESH_handler_refresh_link (struct TEH_RequestHandler *rh, return MHD_NO; if (GNUNET_OK != res) return MHD_YES; + ctx.mlist = json_array (); if (GNUNET_OK != TEH_DB_run_transaction (connection, &mhd_ret, &refresh_link_transaction, &ctx)) { - purge_context (&ctx); + if (NULL != ctx.mlist) + json_decref (ctx.mlist); return mhd_ret; } - mhd_ret = reply_refresh_link_success (connection, - ctx.num_sessions, - ctx.sessions); - purge_context (&ctx); + mhd_ret = TEH_RESPONSE_reply_json (connection, + ctx.mlist, + MHD_HTTP_OK); + json_decref (ctx.mlist); return mhd_ret; } diff --git a/src/exchange/taler-exchange-httpd_refresh_melt.c b/src/exchange/taler-exchange-httpd_refresh_melt.c index 320ec9d8b..400d2bb8c 100644 --- a/src/exchange/taler-exchange-httpd_refresh_melt.c +++ b/src/exchange/taler-exchange-httpd_refresh_melt.c @@ -32,38 +32,6 @@ /** - * @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 @@ -116,14 +84,14 @@ reply_refresh_melt_insufficient_funds (struct MHD_Connection *connection, * 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 rc value the client commited to * @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) + const struct TALER_RefreshCommitmentP *rc, + uint32_t noreveal_index) { struct TALER_RefreshMeltConfirmationPS body; struct TALER_ExchangePublicKeyP pub; @@ -132,9 +100,8 @@ reply_refresh_melt_success (struct MHD_Connection *connection, 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); + body.rc = *rc; + body.noreveal_index = htonl (noreveal_index); if (GNUNET_OK != TEH_KS_sign (&body.purpose, &pub, @@ -162,63 +129,22 @@ struct RefreshMeltContext { /** - * Key state that can be used to lookup keys. + * noreveal_index is only initialized during + * #refresh_melt_transaction(). */ - struct TEH_KS_StateHandle *key_state; + struct TALER_EXCHANGEDB_RefreshSession refresh_session; /** - * Information about the denomination key of the coin being - * melted. + * Information about the @e coin's denomination. */ struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; - /** - * Array of denominations of the fresh coins. - */ - struct TALER_DenominationPublicKey *denom_pubs; - - /** - * Number of new coins to be generated in the melt. - * Size of the @e denom_pubs array. - */ - unsigned int num_newcoins; - - /** - * Details about the coin to be melted. - */ - struct TEH_DB_MeltDetails coin_melt_details; - - /** - * Set to the session hash once the @e hash_context has finished. - */ - struct GNUNET_HashCode session_hash; - - /** - * Hash operation used to calculate the session hash. - */ - struct GNUNET_HashContext *hash_context; - - /** - * Committments to the blinded envelopes for the fresh coins. - */ - struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coin[TALER_CNC_KAPPA]; - - /** - * Commmittments to the transfer public keys. - */ - struct TALER_TransferPublicKeyP transfer_pub[TALER_CNC_KAPPA]; - - /** - * Initialized during #refresh_melt_transaction(). - */ - struct TALER_EXCHANGEDB_RefreshSession refresh_session; - }; /** - * Parse coin melt requests from a JSON object and write them to - * the database. + * Check that the coin has sufficient funds left for the selected + * melt operation. * * @param connection the connection to send errors to * @param session the database connection @@ -233,20 +159,19 @@ refresh_check_melt (struct MHD_Connection *connection, int *mhd_ret) { struct TALER_EXCHANGEDB_TransactionList *tl; - struct TALER_EXCHANGEDB_RefreshMelt *meltp = &rmc->refresh_session.melt; struct TALER_Amount coin_value; - struct TALER_Amount coin_residual; struct TALER_Amount spent; enum GNUNET_DB_QueryStatus qs; TALER_amount_ntoh (&coin_value, &rmc->dki->issue.properties.value); - /* fee for THIS transaction; the melt amount includes the fee! */ - spent = rmc->coin_melt_details.melt_amount_with_fee; + /* Start with cost of this melt transaction */ + spent = rmc->refresh_session.amount_with_fee; + /* add historic transaction costs of this coin */ qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, session, - &rmc->coin_melt_details.coin_info.coin_pub, + &rmc->refresh_session.coin.coin_pub, &tl); if (0 > qs) { @@ -267,33 +192,32 @@ refresh_check_melt (struct MHD_Connection *connection, TALER_EC_REFRESH_MELT_COIN_HISTORY_COMPUTATION_FAILED); return GNUNET_DB_STATUS_HARD_ERROR; } + /* Refuse to refresh when the coin's value is insufficient for the cost of all transactions. */ if (TALER_amount_cmp (&coin_value, &spent) < 0) { + struct TALER_Amount coin_residual; + GNUNET_assert (GNUNET_SYSERR != TALER_amount_subtract (&coin_residual, &spent, - &rmc->coin_melt_details.melt_amount_with_fee)); + &rmc->refresh_session.amount_with_fee)); *mhd_ret = reply_refresh_melt_insufficient_funds (connection, - &rmc->coin_melt_details.coin_info.coin_pub, + &rmc->refresh_session.coin.coin_pub, coin_value, tl, - &rmc->coin_melt_details.melt_amount_with_fee, + &rmc->refresh_session.amount_with_fee, &coin_residual); TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, tl); return GNUNET_DB_STATUS_HARD_ERROR; } + + /* we're good, coin has sufficient funds to be melted */ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, tl); - - meltp->coin = rmc->coin_melt_details.coin_info; - meltp->coin_sig = rmc->coin_melt_details.melt_sig; - meltp->session_hash = rmc->session_hash; - meltp->amount_with_fee = rmc->coin_melt_details.melt_amount_with_fee; - meltp->melt_fee = rmc->coin_melt_details.melt_fee; return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; } @@ -325,17 +249,19 @@ refresh_melt_transaction (void *cls, int *mhd_ret) { struct RefreshMeltContext *rmc = cls; + struct TALER_EXCHANGEDB_RefreshMelt rm; enum GNUNET_DB_QueryStatus qs; - qs = TEH_plugin->get_refresh_session (TEH_plugin->cls, - session, - &rmc->session_hash, - &rmc->refresh_session); + /* Check if we already created such a session */ + qs = TEH_plugin->get_melt (TEH_plugin->cls, + session, + &rmc->refresh_session.rc, + &rm); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) { *mhd_ret = reply_refresh_melt_success (connection, - &rmc->session_hash, - rmc->refresh_session.noreveal_index); + &rmc->refresh_session.rc, + rm.session.noreveal_index); return GNUNET_DB_STATUS_HARD_ERROR; } if (0 > qs) @@ -346,12 +272,7 @@ refresh_melt_transaction (void *cls, return qs; } - /* store 'global' session data */ - rmc->refresh_session.num_newcoins = rmc->num_newcoins; - rmc->refresh_session.noreveal_index - = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, - TALER_CNC_KAPPA); - + /* check coin has enough funds remaining on it to cover melt cost */ qs = refresh_check_melt (connection, session, rmc, @@ -359,28 +280,15 @@ refresh_melt_transaction (void *cls, if (0 > qs) return qs; - if ( (0 >= - (qs = TEH_plugin->create_refresh_session (TEH_plugin->cls, - session, - &rmc->session_hash, - &rmc->refresh_session))) || - (0 >= - (qs = TEH_plugin->insert_refresh_order (TEH_plugin->cls, - session, - &rmc->session_hash, - rmc->num_newcoins, - rmc->denom_pubs))) || - (0 >= - (qs = TEH_plugin->insert_refresh_commit_coins (TEH_plugin->cls, - session, - &rmc->session_hash, - rmc->num_newcoins, - rmc->commit_coin[rmc->refresh_session.noreveal_index]))) || - (0 >= - (qs = TEH_plugin->insert_refresh_transfer_public_key (TEH_plugin->cls, - session, - &rmc->session_hash, - &rmc->transfer_pub[rmc->refresh_session.noreveal_index]))) ) + /* pick challenge and persist it */ + rmc->refresh_session.noreveal_index + = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, + TALER_CNC_KAPPA); + + if (0 >= + (qs = TEH_plugin->insert_melt (TEH_plugin->cls, + session, + &rmc->refresh_session))) { if (GNUNET_DB_STATUS_SOFT_ERROR != qs) { @@ -395,233 +303,6 @@ refresh_melt_transaction (void *cls, /** - * Handle a "/refresh/melt" request after the main JSON parsing has - * happened. We now need to validate the coins being melted and the - * session signature and then hand things of to execute the melt - * operation. - * - * @param connection the MHD connection to handle - * @param[out] mhd_ret set on failure to return value for MHD - * @param rmc information about the melt to process - * @return MHD result code - */ -static int -refresh_melt_prepare (struct MHD_Connection *connection, - int *mhd_ret, - struct RefreshMeltContext *rmc) -{ - struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dk; - struct TALER_EXCHANGEDB_DenominationKeyInformationP *dki; - struct TALER_Amount cost; - struct TALER_Amount total_cost; - struct TALER_Amount value; - struct TALER_Amount fee_withdraw; - struct TALER_Amount fee_melt; - struct TALER_Amount total_melt; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "/refresh/melt request for session %s\n", - GNUNET_h2s (&rmc->session_hash)); - - GNUNET_assert (GNUNET_OK == - TALER_amount_get_zero (TEH_exchange_currency_string, - &total_cost)); - for (unsigned int i=0;i<rmc->num_newcoins;i++) - { - dk = TEH_KS_denomination_key_lookup (rmc->key_state, - &rmc->denom_pubs[i], - TEH_KS_DKU_WITHDRAW); - if (NULL == dk) - { - GNUNET_break_op (0); - *mhd_ret = TEH_RESPONSE_reply_arg_invalid (connection, - TALER_EC_REFRESH_MELT_FRESH_DENOMINATION_KEY_NOT_FOUND, - "new_denoms"); - return GNUNET_SYSERR; - } - dki = &dk->issue; - TALER_amount_ntoh (&value, - &dki->properties.value); - TALER_amount_ntoh (&fee_withdraw, - &dki->properties.fee_withdraw); - if ( (GNUNET_OK != - TALER_amount_add (&cost, - &value, - &fee_withdraw)) || - (GNUNET_OK != - TALER_amount_add (&total_cost, - &cost, - &total_cost)) ) - { - GNUNET_break_op (0); - *mhd_ret = TEH_RESPONSE_reply_internal_error (connection, - TALER_EC_REFRESH_MELT_COST_CALCULATION_OVERFLOW, - "cost calculation failure"); - return GNUNET_SYSERR; - } - } - - dki = &rmc->dki->issue; - TALER_amount_ntoh (&fee_melt, - &dki->properties.fee_refresh); - if (GNUNET_OK != - TALER_amount_subtract (&total_melt, - &rmc->coin_melt_details.melt_amount_with_fee, - &fee_melt)) - { - GNUNET_break_op (0); - *mhd_ret = TEH_RESPONSE_reply_external_error (connection, - TALER_EC_REFRESH_MELT_FEES_EXCEED_CONTRIBUTION, - "Melt contribution below melting fee"); - return GNUNET_SYSERR; - } - if (0 != - TALER_amount_cmp (&total_cost, - &total_melt)) - { - GNUNET_break_op (0); - /* We require total value of coins being melted and - total value of coins being generated to match! */ - *mhd_ret = TEH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_BAD_REQUEST, - "{s:s, s:I}", - "error", "value mismatch", - "code", (json_int_t) TALER_EC_REFRESH_MELT_FEES_MISSMATCH); - return GNUNET_SYSERR; - } - return TEH_DB_run_transaction (connection, - mhd_ret, - &refresh_melt_transaction, - rmc); -} - - -/** - * Extract public coin information from a JSON object. - * - * @param connection the connection to send error responses to - * @param coin_info the JSON object to extract the coin info from - * @param[out] r_melt_detail set to details about the coin's melting permission (if valid) - * @return #GNUNET_YES if coin public info in JSON was valid - * #GNUNET_NO JSON was invalid, response was generated - * #GNUNET_SYSERR on internal error - */ -static int -get_coin_public_info (struct MHD_Connection *connection, - const json_t *coin_info, - struct TEH_DB_MeltDetails *r_melt_detail) -{ - int ret; - struct TALER_CoinSpendSignatureP melt_sig; - struct TALER_DenominationSignature sig; - struct TALER_DenominationPublicKey pk; - struct TALER_Amount amount; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("coin_pub", &r_melt_detail->coin_info.coin_pub), - TALER_JSON_spec_denomination_signature ("denom_sig", &sig), - TALER_JSON_spec_denomination_public_key ("denom_pub", &pk), - GNUNET_JSON_spec_fixed_auto ("confirm_sig", &melt_sig), - TALER_JSON_spec_amount ("value_with_fee", &amount), - GNUNET_JSON_spec_end () - }; - - ret = TEH_PARSE_json_data (connection, - coin_info, - spec); - if (GNUNET_OK != ret) - { - GNUNET_break_op (0); - return ret; - } - /* check exchange signature on the coin */ - r_melt_detail->coin_info.denom_sig = sig; - r_melt_detail->coin_info.denom_pub = pk; - if (GNUNET_OK != - TALER_test_coin_valid (&r_melt_detail->coin_info)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - r_melt_detail->coin_info.denom_sig.rsa_signature = NULL; - r_melt_detail->coin_info.denom_pub.rsa_public_key = NULL; - return (MHD_YES == - TEH_RESPONSE_reply_signature_invalid (connection, - TALER_EC_REFRESH_MELT_DENOMINATION_SIGNATURE_INVALID, - "denom_sig")) - ? GNUNET_NO : GNUNET_SYSERR; - } - r_melt_detail->melt_sig = melt_sig; - r_melt_detail->melt_amount_with_fee = amount; - return GNUNET_OK; -} - - -/** - * Release memory from the @a commit_coin array. - * - * @param commit_coin array to release - * @param kappa size of 1st dimension - * @param num_new_coins size of 2nd dimension - */ -static void -free_commit_coins (struct TALER_EXCHANGEDB_RefreshCommitCoin **commit_coin, - unsigned int kappa, - unsigned int num_new_coins) -{ - for (unsigned int i=0;i<kappa;i++) - { - if (NULL == commit_coin[i]) - break; - for (unsigned int j=0;j<num_new_coins;j++) - GNUNET_free_non_null (commit_coin[i][j].coin_ev); - GNUNET_free (commit_coin[i]); - commit_coin[i] = NULL; - } -} - - -/** - * Cleanup state kept in the @a rmc. - * - * @param rmc state to clean up; does not free @a rmc itself - */ -static void -cleanup_rmc (struct RefreshMeltContext *rmc) -{ - free_commit_coins (rmc->commit_coin, - TALER_CNC_KAPPA, - rmc->num_newcoins); - if (NULL != rmc->coin_melt_details.coin_info.denom_pub.rsa_public_key) - { - GNUNET_CRYPTO_rsa_public_key_free (rmc->coin_melt_details.coin_info.denom_pub.rsa_public_key); - rmc->coin_melt_details.coin_info.denom_pub.rsa_public_key = NULL; - } - if (NULL != rmc->coin_melt_details.coin_info.denom_sig.rsa_signature) - { - GNUNET_CRYPTO_rsa_signature_free (rmc->coin_melt_details.coin_info.denom_sig.rsa_signature); - rmc->coin_melt_details.coin_info.denom_sig.rsa_signature = NULL; - } - if (NULL != rmc->denom_pubs) - { - for (unsigned int j=0;j<rmc->num_newcoins;j++) - if (NULL != rmc->denom_pubs[j].rsa_public_key) - GNUNET_CRYPTO_rsa_public_key_free (rmc->denom_pubs[j].rsa_public_key); - GNUNET_free (rmc->denom_pubs); - rmc->denom_pubs = NULL; - } - if (NULL != rmc->hash_context) - { - GNUNET_CRYPTO_hash_context_abort (rmc->hash_context); - rmc->hash_context = NULL; - } - if (NULL != rmc->key_state) - { - TEH_KS_release (rmc->key_state); - rmc->key_state = NULL; - } -} - - -/** * Handle a "/refresh/melt" request after the first parsing has * happened. We now need to validate the coins being melted and the * session signature and then hand things of to execute the melt @@ -629,223 +310,70 @@ cleanup_rmc (struct RefreshMeltContext *rmc) * processing on to #handle_refresh_melt_binary(). * * @param connection the MHD connection to handle - * @param new_denoms array of denomination keys - * @param melt_coin coin to melt - * @param transfer_pubs #TALER_CNC_KAPPA-dimensional array of transfer keys - * @param coin_evs #TALER_CNC_KAPPA-dimensional array of envelopes to sign + * @param[in,out] rmc details about the melt request * @return MHD result code */ static int -handle_refresh_melt_json (struct MHD_Connection *connection, - const json_t *new_denoms, - const json_t *melt_coin, - const json_t *transfer_pubs, - const json_t *coin_evs) +handle_refresh_melt (struct MHD_Connection *connection, + struct RefreshMeltContext *rmc) { - int res; - int mhd_ret; - struct RefreshMeltContext rmc; - - memset (&rmc, - 0, - sizeof (rmc)); - /* For the signature check, we hash most of the inputs together - (except for the signatures on the coins). */ - rmc.hash_context = GNUNET_CRYPTO_hash_context_start (); - for (unsigned int i = 0; i < TALER_CNC_KAPPA; i++) - { - struct GNUNET_JSON_Specification trans_spec[] = { - GNUNET_JSON_spec_fixed_auto (NULL, &rmc.transfer_pub[i]), - GNUNET_JSON_spec_end () - }; - - res = TEH_PARSE_json_array (connection, - transfer_pubs, - trans_spec, - i, -1); - if (GNUNET_OK != res) - { - GNUNET_break_op (0); - mhd_ret = (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; - cleanup_rmc (&rmc); - return mhd_ret; - } - GNUNET_CRYPTO_hash_context_read (rmc.hash_context, - &rmc.transfer_pub[i], - sizeof (struct TALER_TransferPublicKeyP)); - } - - rmc.num_newcoins = json_array_size (new_denoms); - rmc.denom_pubs = GNUNET_new_array (rmc.num_newcoins, - struct TALER_DenominationPublicKey); - for (unsigned int i=0;i<rmc.num_newcoins;i++) - { - char *buf; - size_t buf_size; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_denomination_public_key (NULL, - &rmc.denom_pubs[i]), - GNUNET_JSON_spec_end () - }; - - res = TEH_PARSE_json_array (connection, - new_denoms, - spec, - i, - -1); - if (GNUNET_OK != res) - { - mhd_ret = (GNUNET_NO == res) ? MHD_YES : MHD_NO; - cleanup_rmc (&rmc); - return mhd_ret; - } - buf_size = GNUNET_CRYPTO_rsa_public_key_encode (rmc.denom_pubs[i].rsa_public_key, - &buf); - GNUNET_CRYPTO_hash_context_read (rmc.hash_context, - buf, - buf_size); - GNUNET_free (buf); - } - - /* decode JSON data on coin to melt and check that this is a - valid coin */ + /* sanity-check that "total melt amount > melt fee" */ { - struct TALER_AmountNBO melt_amount; + struct TALER_Amount fee_refresh; - res = get_coin_public_info (connection, - melt_coin, - &rmc.coin_melt_details); - if (GNUNET_OK != res) + TALER_amount_ntoh (&fee_refresh, + &rmc->dki->issue.properties.fee_refresh); + if (TALER_amount_cmp (&fee_refresh, + &rmc->refresh_session.amount_with_fee) > 0) { GNUNET_break_op (0); - mhd_ret = (GNUNET_NO == res) ? MHD_YES : MHD_NO; - cleanup_rmc (&rmc); - return mhd_ret; - } - TALER_amount_hton (&melt_amount, - &rmc.coin_melt_details.melt_amount_with_fee); - GNUNET_CRYPTO_hash_context_read (rmc.hash_context, - &rmc.coin_melt_details.coin_info.coin_pub, - sizeof (struct TALER_CoinSpendPublicKeyP)); - GNUNET_CRYPTO_hash_context_read (rmc.hash_context, - &melt_amount, - sizeof (struct TALER_AmountNBO)); - } - - /* parse JSON arrays into binary arrays and hash everything - together for the signature check */ - for (unsigned int i = 0; i < TALER_CNC_KAPPA; i++) - { - rmc.commit_coin[i] = GNUNET_new_array (rmc.num_newcoins, - struct TALER_EXCHANGEDB_RefreshCommitCoin); - for (unsigned int j = 0; j < rmc.num_newcoins; j++) - { - struct TALER_EXCHANGEDB_RefreshCommitCoin *rcc = &rmc.commit_coin[i][j]; - struct GNUNET_JSON_Specification coin_spec[] = { - GNUNET_JSON_spec_varsize (NULL, - (void **) &rcc->coin_ev, - &rcc->coin_ev_size), - GNUNET_JSON_spec_end () - }; - - res = TEH_PARSE_json_array (connection, - coin_evs, - coin_spec, - i, - j, - -1); - if (GNUNET_OK != res) - { - GNUNET_break_op (0); - mhd_ret = (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; - cleanup_rmc (&rmc); - return mhd_ret; - } - - GNUNET_CRYPTO_hash_context_read (rmc.hash_context, - rcc->coin_ev, - rcc->coin_ev_size); + return TEH_RESPONSE_reply_external_error (connection, + TALER_EC_REFRESH_MELT_FEES_EXCEED_CONTRIBUTION, + "melt amount smaller than melting fee"); } } - GNUNET_CRYPTO_hash_context_finish (rmc.hash_context, - &rmc.session_hash); - rmc.hash_context = NULL; - - rmc.key_state = TEH_KS_acquire (); - if (NULL == rmc.key_state) - { - TALER_LOG_ERROR ("Lacking keys to operate\n"); - return TEH_RESPONSE_reply_internal_error (connection, - TALER_EC_EXCHANGE_BAD_CONFIGURATION, - "no keys"); - } - rmc.dki = TEH_KS_denomination_key_lookup (rmc.key_state, - &rmc.coin_melt_details.coin_info.denom_pub, - TEH_KS_DKU_DEPOSIT); - if (NULL == rmc.dki) - { - TEH_KS_release (rmc.key_state); - TALER_LOG_WARNING ("Unknown denomination key in /refresh/melt request\n"); - return TEH_RESPONSE_reply_arg_unknown (connection, - TALER_EC_REFRESH_MELT_DENOMINATION_KEY_NOT_FOUND, - "denom_pub"); - } - /* verify signature of coin for melt operation */ { struct TALER_RefreshMeltCoinAffirmationPS body; - struct TALER_Amount fee_refresh; - TALER_amount_ntoh (&fee_refresh, - &rmc.dki->issue.properties.fee_refresh); - rmc.coin_melt_details.melt_fee = fee_refresh; body.purpose.size = htonl (sizeof (struct TALER_RefreshMeltCoinAffirmationPS)); body.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); - body.session_hash = rmc.session_hash; + body.rc = rmc->refresh_session.rc; TALER_amount_hton (&body.amount_with_fee, - &rmc.coin_melt_details.melt_amount_with_fee); - TALER_amount_hton (&body.melt_fee, - &fee_refresh); - body.coin_pub = rmc.coin_melt_details.coin_info.coin_pub; - if (TALER_amount_cmp (&fee_refresh, - &rmc.coin_melt_details.melt_amount_with_fee) > 0) - { - GNUNET_break_op (0); - cleanup_rmc (&rmc); - return TEH_RESPONSE_reply_external_error (connection, - TALER_EC_REFRESH_MELT_AMOUNT_INSUFFICIENT, - "melt amount smaller than melting fee"); - } + &rmc->refresh_session.amount_with_fee); + body.melt_fee = rmc->dki->issue.properties.fee_refresh; + body.coin_pub = rmc->refresh_session.coin.coin_pub; if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT, &body.purpose, - &rmc.coin_melt_details.melt_sig.eddsa_signature, - &rmc.coin_melt_details.coin_info.coin_pub.eddsa_pub)) + &rmc->refresh_session.coin_sig.eddsa_signature, + &rmc->refresh_session.coin.coin_pub.eddsa_pub)) { GNUNET_break_op (0); - cleanup_rmc (&rmc); return TEH_RESPONSE_reply_signature_invalid (connection, TALER_EC_REFRESH_MELT_COIN_SIGNATURE_INVALID, "confirm_sig"); } } - /* prepare commit */ - if (GNUNET_OK != - refresh_melt_prepare (connection, - &mhd_ret, - &rmc)) + /* run transaction */ { - cleanup_rmc (&rmc); - return mhd_ret; + int mhd_ret; + + if (GNUNET_OK != + TEH_DB_run_transaction (connection, + &mhd_ret, + &refresh_melt_transaction, + rmc)) + return mhd_ret; } - mhd_ret = reply_refresh_melt_success (connection, - &rmc.session_hash, - rmc.refresh_session.noreveal_index); - cleanup_rmc (&rmc); - return mhd_ret; + + /* generate ordinary response */ + return reply_refresh_melt_success (connection, + &rmc->refresh_session.rc, + rmc->refresh_session.noreveal_index); } @@ -870,16 +398,22 @@ TEH_REFRESH_handler_refresh_melt (struct TEH_RequestHandler *rh, size_t *upload_data_size) { json_t *root; - json_t *new_denoms; - json_t *melt_coin; - json_t *coin_evs; - json_t *transfer_pubs; + struct RefreshMeltContext rmc; int res; + struct TEH_KS_StateHandle *key_state; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_json ("new_denoms", &new_denoms), - GNUNET_JSON_spec_json ("melt_coin", &melt_coin), - GNUNET_JSON_spec_json ("coin_evs", &coin_evs), - GNUNET_JSON_spec_json ("transfer_pubs", &transfer_pubs), + GNUNET_JSON_spec_fixed_auto ("coin_pub", + &rmc.refresh_session.coin.coin_pub), + TALER_JSON_spec_denomination_signature ("denom_sig", + &rmc.refresh_session.coin.denom_sig), + TALER_JSON_spec_denomination_public_key ("denom_pub", + &rmc.refresh_session.coin.denom_pub), + GNUNET_JSON_spec_fixed_auto ("confirm_sig", + &rmc.refresh_session.coin_sig), + TALER_JSON_spec_amount ("value_with_fee", + &rmc.refresh_session.amount_with_fee), + GNUNET_JSON_spec_fixed_auto ("rc", + &rmc.refresh_session.rc), GNUNET_JSON_spec_end () }; @@ -894,6 +428,9 @@ TEH_REFRESH_handler_refresh_melt (struct TEH_RequestHandler *rh, (NULL == root) ) return MHD_YES; + memset (&rmc, + 0, + sizeof (rmc)); res = TEH_PARSE_json_data (connection, root, spec); @@ -901,29 +438,60 @@ TEH_REFRESH_handler_refresh_melt (struct TEH_RequestHandler *rh, if (GNUNET_OK != res) return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; - /* Determine dimensionality of the request (kappa, #old and #new coins) */ - if (TALER_CNC_KAPPA != json_array_size (coin_evs)) + if (GNUNET_OK != + TALER_test_coin_valid (&rmc.refresh_session.coin)) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); - return TEH_RESPONSE_reply_arg_invalid (connection, - TALER_EC_REFRESH_MELT_CNC_COIN_ARRAY_SIZE_INVALID, - "coin_evs"); + return TEH_RESPONSE_reply_signature_invalid (connection, + TALER_EC_REFRESH_MELT_DENOMINATION_SIGNATURE_INVALID, + "denom_sig"); } - if (TALER_CNC_KAPPA != json_array_size (transfer_pubs)) + + /* run actual logic, now that the request was parsed */ + key_state = TEH_KS_acquire (); + if (NULL == key_state) { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TEH_RESPONSE_reply_arg_invalid (connection, - TALER_EC_REFRESH_MELT_CNC_TRANSFER_ARRAY_SIZE_INVALID, - "transfer_pubs"); + TALER_LOG_ERROR ("Lacking keys to operate\n"); + res = TEH_RESPONSE_reply_internal_error (connection, + TALER_EC_EXCHANGE_BAD_CONFIGURATION, + "no keys"); + goto cleanup; + } + rmc.dki = TEH_KS_denomination_key_lookup (key_state, + &rmc.refresh_session.coin.denom_pub, + TEH_KS_DKU_DEPOSIT); + if (NULL == rmc.dki) + { + TALER_LOG_WARNING ("Unknown denomination key in /refresh/melt request\n"); + res = TEH_RESPONSE_reply_arg_unknown (connection, + TALER_EC_REFRESH_MELT_DENOMINATION_KEY_NOT_FOUND, + "denom_pub"); + goto cleanup; + } + + res = handle_refresh_melt (connection, + &rmc); + + + cleanup: + if (NULL != key_state) + { + TEH_KS_release (key_state); + key_state = NULL; + } + if (NULL != rmc.refresh_session.coin.denom_pub.rsa_public_key) + { + GNUNET_CRYPTO_rsa_public_key_free (rmc.refresh_session.coin.denom_pub.rsa_public_key); + rmc.refresh_session.coin.denom_pub.rsa_public_key = NULL; + } + if (NULL != rmc.refresh_session.coin.denom_sig.rsa_signature) + { + GNUNET_CRYPTO_rsa_signature_free (rmc.refresh_session.coin.denom_sig.rsa_signature); + rmc.refresh_session.coin.denom_sig.rsa_signature = NULL; } - res = handle_refresh_melt_json (connection, - new_denoms, - melt_coin, - transfer_pubs, - coin_evs); GNUNET_JSON_parse_free (spec); + return res; } diff --git a/src/exchange/taler-exchange-httpd_refresh_reveal.c b/src/exchange/taler-exchange-httpd_refresh_reveal.c index a64ec1cee..4a7cd33db 100644 --- a/src/exchange/taler-exchange-httpd_refresh_reveal.c +++ b/src/exchange/taler-exchange-httpd_refresh_reveal.c @@ -32,6 +32,12 @@ /** + * Maximum number of fresh coins we allow per refresh operation. + */ +#define MAX_FRESH_COINS 256 + + +/** * Send a response for "/refresh/reveal". * * @param connection the connection to send the response to @@ -79,133 +85,19 @@ reply_refresh_reveal_success (struct MHD_Connection *connection, * 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 + * @param rc commitment computed by the exchange * @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) + const struct TALER_RefreshCommitmentP *rc) { - json_t *info_new; - json_t *info_commit_k; - - info_new = json_array (); - info_commit_k = json_array (); - for (unsigned int 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}", + "{s:s, s:I, s:o}", "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; - - TALER_link_reveal_transfer_secret (transfer_priv, - &melt->coin.coin_pub, - &transfer_secret); - - /* Check that the commitments for all new coins were correct */ - for (unsigned int j = 0; j < num_newcoins; j++) - { - struct TALER_PlanchetSecretsP fc; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct GNUNET_HashCode h_msg; - char *buf; - size_t buf_len; - - TALER_planchet_setup_refresh (&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; + "rc_expected", GNUNET_JSON_from_data_auto (rc)); } @@ -216,169 +108,83 @@ struct RevealContext { /** - * Hash of the refresh session. + * Commitment of the refresh operaton. */ - const struct GNUNET_HashCode *session_hash; + struct TALER_RefreshCommitmentP rc; /** - * Database session used to execute the transaction. + * Transfer public key at gamma. */ - struct TALER_EXCHANGEDB_Session *session; - - /** - * Session state from the database. - */ - struct TALER_EXCHANGEDB_RefreshSession refresh_session; + struct TALER_TransferPublicKeyP gamma_tp; /** - * Array of denomination public keys used for the refresh. + * Transfer private keys revealed to us. */ - struct TALER_DenominationPublicKey *denom_pubs; + struct TALER_TransferPrivateKeyP transfer_privs[TALER_CNC_KAPPA - 1]; /** - * Envelopes with the signatures to be returned. + * Denominations being requested. */ - struct TALER_DenominationSignature *ev_sigs; + const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation **dkis; /** - * Commitment data from the DB giving data about original - * commitments, in particular the blinded envelopes (for - * index gamma). + * Envelopes to be signed. */ - struct TALER_EXCHANGEDB_RefreshCommitCoin *commit_coins; + const struct TALER_RefreshCoinData *rcds; /** - * Transfer public key associated with the gamma value - * selected by the exchange. + * Envelopes with the signatures to be returned. Initially NULL. */ - struct TALER_TransferPublicKeyP gamma_tp; + struct TALER_DenominationSignature *ev_sigs; /** - * Transfer private keys revealed to us. + * Size of the @e dkis, @e rcds and @e ev_sigs arrays (if non-NULL). */ - struct TALER_TransferPrivateKeyP transfer_privs[TALER_CNC_KAPPA - 1]; + unsigned int num_fresh_coins; }; /** - * Exchange a coin as part of a refresh operation. Obtains the - * envelope from the database and performs the signing operation. + * Function called with information about a refresh order we already + * persisted. Stores the result in @a cls so we don't do the calculation + * again. * - * @param 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 - * @param[out] ev_sig set to signature over the coin upon success - * @return database transaction status + * @param cls closure with a `struct RevealContext` + * @param rowid unique serial ID for the row in our database + * @param num_newcoins size of the @a rrcs array + * @param rrcs array of @a num_newcoins information about coins to be created + * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1 + * @param tprivs array of @e num_tprivs transfer private keys + * @param tp transfer public key information */ -static enum GNUNET_DB_QueryStatus -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_DenominationSignature *ev_sig) +static void +check_exists_cb (void *cls, + uint32_t num_newcoins, + const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs, + unsigned int num_tprivs, + const struct TALER_TransferPrivateKeyP *tprivs, + const struct TALER_TransferPublicKeyP *tp) { - struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki; - enum GNUNET_DB_QueryStatus qs; + struct RevealContext *rctx = cls; - dki = TEH_KS_denomination_key_lookup (key_state, - denom_pub, - TEH_KS_DKU_WITHDRAW); - if (NULL == dki) + if (0 == num_newcoins) { GNUNET_break (0); - ev_sig->rsa_signature = NULL; - return GNUNET_DB_STATUS_HARD_ERROR; - } - qs = TEH_plugin->get_refresh_out (TEH_plugin->cls, - session, - session_hash, - coin_off, - ev_sig); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Returning cached reply for /refresh/reveal signature\n"); - return qs; - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs) - return qs; - - 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 GNUNET_DB_STATUS_HARD_ERROR; - } - qs = TEH_plugin->insert_refresh_out (TEH_plugin->cls, - session, - session_hash, - coin_off, - ev_sig); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - if (NULL != ev_sig->rsa_signature) - { - GNUNET_CRYPTO_rsa_signature_free (ev_sig->rsa_signature); - ev_sig->rsa_signature = NULL; - } - } - return qs; -} - - -/** - * Cleanup state of the transaction stored in @a rc. - * - * @param rc context to clean up - */ -static void -cleanup_rc (struct RevealContext *rc) -{ - if (NULL != rc->denom_pubs) - { - for (unsigned int i=0;i<rc->refresh_session.num_newcoins;i++) - if (NULL != rc->denom_pubs[i].rsa_public_key) - GNUNET_CRYPTO_rsa_public_key_free (rc->denom_pubs[i].rsa_public_key); - GNUNET_free (rc->denom_pubs); - rc->denom_pubs = NULL; - } - if (NULL != rc->commit_coins) - { - for (unsigned int j=0;j<rc->refresh_session.num_newcoins;j++) - GNUNET_free_non_null (rc->commit_coins[j].coin_ev); - GNUNET_free (rc->commit_coins); - rc->commit_coins = NULL; - } - if (NULL != rc->ev_sigs) - { - for (unsigned int j=0;j<rc->refresh_session.num_newcoins;j++) - if (NULL != rc->ev_sigs[j].rsa_signature) - GNUNET_CRYPTO_rsa_signature_free (rc->ev_sigs[j].rsa_signature); - GNUNET_free (rc->ev_sigs); - rc->ev_sigs = NULL; - } - if (NULL != rc->refresh_session.melt.coin.denom_sig.rsa_signature) - { - GNUNET_CRYPTO_rsa_signature_free (rc->refresh_session.melt.coin.denom_sig.rsa_signature); - rc->refresh_session.melt.coin.denom_sig.rsa_signature = NULL; - } - if (NULL != rc->refresh_session.melt.coin.denom_pub.rsa_public_key) - { - GNUNET_CRYPTO_rsa_public_key_free (rc->refresh_session.melt.coin.denom_pub.rsa_public_key); - rc->refresh_session.melt.coin.denom_pub.rsa_public_key = NULL; + return; } + GNUNET_break (TALER_CNC_KAPPA - 1 == num_tprivs); + GNUNET_break_op (0 == memcmp (tp, + &rctx->gamma_tp, + sizeof (struct TALER_TransferPublicKeyP))); + GNUNET_break_op (0 == memcmp (tprivs, + &rctx->transfer_privs, + sizeof (struct TALER_TransferPrivateKeyP) * num_tprivs)); + rctx->ev_sigs = GNUNET_new_array (num_newcoins, + struct TALER_DenominationSignature); + for (unsigned int i=0;i<num_newcoins;i++) + rctx->ev_sigs[i].rsa_signature + = GNUNET_CRYPTO_rsa_signature_dup (rrcs[i].coin_sig.rsa_signature); } @@ -408,233 +214,230 @@ refresh_reveal_transaction (void *cls, struct TALER_EXCHANGEDB_Session *session, int *mhd_ret) { - struct RevealContext *rc = cls; - unsigned int off; - struct GNUNET_HashContext *hash_context; - struct GNUNET_HashCode sh_check; + struct RevealContext *rctx = cls; + struct TALER_EXCHANGEDB_RefreshMelt refresh_melt; enum GNUNET_DB_QueryStatus qs; - rc->session = session; - qs = TEH_plugin->get_refresh_session (TEH_plugin->cls, - session, - rc->session_hash, - &rc->refresh_session); + /* Try to see if we already have given an answer before. */ + qs = TEH_plugin->get_refresh_reveal (TEH_plugin->cls, + session, + &rctx->rc, + &check_exists_cb, + rctx); + switch (qs) { + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + /* continue normal execution */ + break; + case GNUNET_DB_STATUS_SOFT_ERROR: + return qs; + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (qs); + *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_REVEAL_DB_FETCH_REVEAL_ERROR); + return GNUNET_DB_STATUS_HARD_ERROR; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + default: + /* Hossa, already found our reply! */ + GNUNET_assert (NULL != rctx->ev_sigs); + return qs; + } + + /* Obtain basic information about the refresh operation and what + gamma we committed to. */ + qs = TEH_plugin->get_melt (TEH_plugin->cls, + session, + &rctx->rc, + &refresh_melt); if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { *mhd_ret = TEH_RESPONSE_reply_arg_invalid (connection, TALER_EC_REFRESH_REVEAL_SESSION_UNKNOWN, - "session_hash"); + "rc"); return GNUNET_DB_STATUS_HARD_ERROR; } if (GNUNET_DB_STATUS_SOFT_ERROR == qs) return qs; if ( (GNUNET_DB_STATUS_HARD_ERROR == qs) || - (rc->refresh_session.noreveal_index >= TALER_CNC_KAPPA) ) + (refresh_melt.session.noreveal_index >= TALER_CNC_KAPPA) ) { GNUNET_break (0); - cleanup_rc (rc); *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection, TALER_EC_REFRESH_REVEAL_DB_FETCH_SESSION_ERROR); return GNUNET_DB_STATUS_HARD_ERROR; } - rc->denom_pubs = GNUNET_new_array (rc->refresh_session.num_newcoins, - struct TALER_DenominationPublicKey); - qs = TEH_plugin->get_refresh_order (TEH_plugin->cls, - session, - rc->session_hash, - rc->refresh_session.num_newcoins, - rc->denom_pubs); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) - { - cleanup_rc (rc); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - return qs; - GNUNET_break (0); - *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFRESH_REVEAL_DB_FETCH_ORDER_ERROR); - return GNUNET_DB_STATUS_HARD_ERROR; - } - hash_context = GNUNET_CRYPTO_hash_context_start (); - /* first, iterate over transfer public keys for hash_context */ - off = 0; - for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) + /* Verify commitment */ { - if (i == rc->refresh_session.noreveal_index) + /* Note that the contents of rcs[refresh_melt.session.noreveal_index] + will be aliased and are *not* allocated (or deallocated) in + this function -- in contrast to the other offsets! */ + struct TALER_RefreshCommitmentEntry rcs[TALER_CNC_KAPPA]; + struct TALER_RefreshCommitmentP rc_expected; + unsigned int off; + + off = 0; /* did we pass session.noreveal_index yet? */ + for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) { - off = 1; - /* obtain gamma_tp from db */ - qs = TEH_plugin->get_refresh_transfer_public_key (TEH_plugin->cls, - session, - rc->session_hash, - &rc->gamma_tp); - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) + struct TALER_RefreshCommitmentEntry *rce = &rcs[i]; + + if (i == refresh_melt.session.noreveal_index) + { + /* Take these coin envelopes from the client */ + rce->transfer_pub = rctx->gamma_tp; + rce->new_coins = (struct TALER_RefreshCoinData *) rctx->rcds; + off = 1; + } + else { - GNUNET_CRYPTO_hash_context_abort (hash_context); - cleanup_rc (rc); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - return qs; - GNUNET_break (0); - *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFRESH_REVEAL_DB_FETCH_TRANSFER_ERROR); - return GNUNET_DB_STATUS_HARD_ERROR; + /* Reconstruct coin envelopes from transfer private key */ + struct TALER_TransferPrivateKeyP *tpriv = &rctx->transfer_privs[i - off]; + struct TALER_TransferSecretP ts; + + GNUNET_CRYPTO_ecdhe_key_get_public (&tpriv->ecdhe_priv, + &rce->transfer_pub.ecdhe_pub); + TALER_link_reveal_transfer_secret (tpriv, + &refresh_melt.session.coin.coin_pub, + &ts); + rce->new_coins = GNUNET_new_array (rctx->num_fresh_coins, + struct TALER_RefreshCoinData); + for (unsigned int j=0;j<rctx->num_fresh_coins;j++) + { + struct TALER_RefreshCoinData *rcd = &rce->new_coins[j]; + struct TALER_PlanchetSecretsP ps; + struct TALER_PlanchetDetail pd; + + rcd->dk = &rctx->dkis[j]->denom_pub; + TALER_planchet_setup_refresh (&ts, + j, + &ps); + TALER_planchet_prepare (rcd->dk, + &ps, + &pd); + rcd->coin_ev = pd.coin_ev; + rcd->coin_ev_size = pd.coin_ev_size; + } } - GNUNET_CRYPTO_hash_context_read (hash_context, - &rc->gamma_tp, - sizeof (struct TALER_TransferPublicKeyP)); } - else + TALER_refresh_get_commitment (&rc_expected, + TALER_CNC_KAPPA, + rctx->num_fresh_coins, + rcs, + &refresh_melt.session.coin.coin_pub, + &refresh_melt.session.amount_with_fee); + + /* Free resources allocated above */ + for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) { - /* compute tp from private key */ - struct TALER_TransferPublicKeyP tp; - - GNUNET_CRYPTO_ecdhe_key_get_public (&rc->transfer_privs[i - off].ecdhe_priv, - &tp.ecdhe_pub); - GNUNET_CRYPTO_hash_context_read (hash_context, - &tp, - sizeof (struct TALER_TransferPublicKeyP)); - } - } + struct TALER_RefreshCommitmentEntry *rce = &rcs[i]; - /* next, add all of the hashes from the denomination keys to the - hash_context */ - for (unsigned int i=0;i<rc->refresh_session.num_newcoins;i++) - { - char *buf; - size_t buf_size; - - buf_size = GNUNET_CRYPTO_rsa_public_key_encode (rc->denom_pubs[i].rsa_public_key, - &buf); - GNUNET_CRYPTO_hash_context_read (hash_context, - buf, - buf_size); - GNUNET_free (buf); - } + if (i == refresh_melt.session.noreveal_index) + continue; /* This offset is special... */ + for (unsigned int j=0;j<rctx->num_fresh_coins;j++) + { + struct TALER_RefreshCoinData *rcd = &rce->new_coins[j]; - /* next, add public key of coin and amount being refreshed */ - { - struct TALER_AmountNBO melt_amountn; - - GNUNET_CRYPTO_hash_context_read (hash_context, - &rc->refresh_session.melt.coin.coin_pub, - sizeof (struct TALER_CoinSpendPublicKeyP)); - TALER_amount_hton (&melt_amountn, - &rc->refresh_session.melt.amount_with_fee); - GNUNET_CRYPTO_hash_context_read (hash_context, - &melt_amountn, - sizeof (struct TALER_AmountNBO)); - } + GNUNET_free (rcd->coin_ev); + } + GNUNET_free (rce->new_coins); + } + + /* Verify rc_expected matches rc */ + if (0 != memcmp (&rctx->rc, + &rc_expected, + sizeof (struct TALER_RefreshCommitmentP))) + { + GNUNET_break_op (0); + *mhd_ret = reply_refresh_reveal_missmatch (connection, + &rc_expected); + return GNUNET_DB_STATUS_HARD_ERROR; + } + } /* end of checking "rc_expected" */ - rc->commit_coins = GNUNET_new_array (rc->refresh_session.num_newcoins, - struct TALER_EXCHANGEDB_RefreshCommitCoin); - off = 0; - for (unsigned int i=0;i<TALER_CNC_KAPPA;i++) + /* check amounts add up! */ { - int res; + struct TALER_Amount refresh_cost; - if (i == rc->refresh_session.noreveal_index) + refresh_cost = refresh_melt.melt_fee; + for (unsigned int i=0;i<rctx->num_fresh_coins;i++) { - off = 1; - /* obtain commit_coins for the selected gamma value from DB */ - qs = TEH_plugin->get_refresh_commit_coins (TEH_plugin->cls, - session, - rc->session_hash, - rc->refresh_session.num_newcoins, - rc->commit_coins); - if (0 >= qs) - { - cleanup_rc (rc); - GNUNET_CRYPTO_hash_context_abort (hash_context); - if (GNUNET_DB_STATUS_SOFT_ERROR == qs) - return qs; - GNUNET_break (0); - *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFRESH_REVEAL_DB_FETCH_COMMIT_ERROR); - return GNUNET_DB_STATUS_HARD_ERROR; - } - /* add envelopes to hash_context */ - for (unsigned int j=0;j<rc->refresh_session.num_newcoins;j++) + struct TALER_Amount fee_withdraw; + struct TALER_Amount value; + struct TALER_Amount total; + + TALER_amount_ntoh (&fee_withdraw, + &rctx->dkis[i]->issue.properties.fee_withdraw); + TALER_amount_ntoh (&value, + &rctx->dkis[i]->issue.properties.value); + if ( (GNUNET_OK != + TALER_amount_add (&total, + &fee_withdraw, + &value)) || + (GNUNET_OK != + TALER_amount_add (&refresh_cost, + &refresh_cost, + &total)) ) { - GNUNET_CRYPTO_hash_context_read (hash_context, - rc->commit_coins[j].coin_ev, - rc->commit_coins[j].coin_ev_size); + GNUNET_break_op (0); + *mhd_ret = TEH_RESPONSE_reply_internal_error (connection, + TALER_EC_REFRESH_REVEAL_COST_CALCULATION_OVERFLOW, + "failed to add up refresh costs"); + return GNUNET_DB_STATUS_HARD_ERROR; } - continue; } - if (GNUNET_OK != - (res = check_commitment (connection, - session, - rc->session_hash, - i, - &rc->transfer_privs[i - off], - &rc->refresh_session.melt, - rc->refresh_session.num_newcoins, - rc->denom_pubs, - hash_context))) + if (0 < TALER_amount_cmp (&refresh_cost, + &refresh_melt.session.amount_with_fee)) { GNUNET_break_op (0); - cleanup_rc (rc); - GNUNET_CRYPTO_hash_context_abort (hash_context); - *mhd_ret = (GNUNET_NO == res) ? MHD_YES : MHD_NO; + *mhd_ret = TEH_RESPONSE_reply_external_error (connection, + TALER_EC_REFRESH_REVEAL_AMOUNT_INSUFFICIENT, + "melted coin value is insufficient to cover cost of operation"); return GNUNET_DB_STATUS_HARD_ERROR; } } - /* Check session hash matches */ - GNUNET_CRYPTO_hash_context_finish (hash_context, - &sh_check); - if (0 != memcmp (&sh_check, - rc->session_hash, - sizeof (struct GNUNET_HashCode))) - { - GNUNET_break_op (0); - *mhd_ret = reply_refresh_reveal_missmatch (connection, - &rc->refresh_session, - rc->commit_coins, - rc->denom_pubs, - &rc->gamma_tp); - cleanup_rc (rc); - return GNUNET_DB_STATUS_HARD_ERROR; - } - /* Client request OK, sign coins */ - rc->ev_sigs = GNUNET_new_array (rc->refresh_session.num_newcoins, - struct TALER_DenominationSignature); + rctx->ev_sigs = GNUNET_new_array (rctx->num_fresh_coins, + struct TALER_DenominationSignature); + for (unsigned int i=0;i<rctx->num_fresh_coins;i++) { - struct TEH_KS_StateHandle *key_state; - - key_state = TEH_KS_acquire (); - if (NULL == key_state) + rctx->ev_sigs[i].rsa_signature + = GNUNET_CRYPTO_rsa_sign_blinded (rctx->dkis[i]->denom_priv.rsa_private_key, + rctx->rcds[i].coin_ev, + rctx->rcds[i].coin_ev_size); + if (NULL == rctx->ev_sigs[i].rsa_signature) { - TALER_LOG_ERROR ("Lacking keys to operate\n"); - cleanup_rc (rc); + *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_REVEAL_SIGNING_ERROR); return GNUNET_DB_STATUS_HARD_ERROR; } - for (unsigned int j=0;j<rc->refresh_session.num_newcoins;j++) + } + + /* Persist operation result in DB */ + { + struct TALER_EXCHANGEDB_RefreshRevealedCoin rrcs[rctx->num_fresh_coins]; + + for (unsigned int i=0;i<rctx->num_fresh_coins;i++) { - qs = refresh_exchange_coin (connection, - session, - rc->session_hash, - key_state, - &rc->denom_pubs[j], - &rc->commit_coins[j], - j, - &rc->ev_sigs[j]); - if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) || - (NULL == rc->ev_sigs[j].rsa_signature) ) - { - *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection, - TALER_EC_REFRESH_REVEAL_SIGNING_ERROR); - qs = GNUNET_DB_STATUS_HARD_ERROR; - break; - } + struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i]; + + rrc->denom_pub = rctx->dkis[i]->denom_pub; + rrc->coin_ev = rctx->rcds[i].coin_ev; + rrc->coin_ev_size = rctx->rcds[i].coin_ev_size; + rrc->coin_sig = rctx->ev_sigs[i]; } - TEH_KS_release (key_state); + qs = TEH_plugin->insert_refresh_reveal (TEH_plugin->cls, + session, + &rctx->rc, + rctx->num_fresh_coins, + rrcs, + TALER_CNC_KAPPA - 1, + rctx->transfer_privs, + &rctx->gamma_tp); } - if (0 >= qs) + if (GNUNET_DB_STATUS_HARD_ERROR == qs) { - cleanup_rc (rc); - return qs; + *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection, + TALER_EC_REFRESH_REVEAL_DB_COMMIT_ERROR); } return qs; } @@ -648,59 +451,171 @@ refresh_reveal_transaction (void *cls, * coins. * * @param connection the MHD connection to handle - * @param session_hash hash identifying the melting session + * @param rctx context for the operation, partially built at this time + * @param transfer_pub transfer public key * @param tp_json private transfer keys in JSON format + * @param new_denoms_h_json requests for fresh coins to be created + * @param coin_evs envelopes of gamma-selected coins to be signed * @return MHD result code - */ + */ static int handle_refresh_reveal_json (struct MHD_Connection *connection, - const struct GNUNET_HashCode *session_hash, - const json_t *tp_json) + struct RevealContext *rctx, + const json_t *tp_json, + const json_t *new_denoms_h_json, + const json_t *coin_evs) { - struct RevealContext rc; - int mhd_ret; - - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "reveal request for session %s\n", - GNUNET_h2s (session_hash)); - memset (&rc, - 0, - sizeof (rc)); - rc.session_hash = session_hash; - for (unsigned int i = 0; i < TALER_CNC_KAPPA - 1; i++) + unsigned int num_fresh_coins = json_array_size (new_denoms_h_json); + unsigned int num_tprivs = json_array_size (tp_json); + struct TEH_KS_StateHandle *key_state; + + GNUNET_assert (num_tprivs == TALER_CNC_KAPPA - 1); + if ( (num_fresh_coins >= MAX_FRESH_COINS) || + (0 == num_fresh_coins) ) + { + GNUNET_break_op (0); + return TEH_RESPONSE_reply_arg_invalid (connection, + TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE, + "new_denoms"); + + } + if (json_array_size (new_denoms_h_json) != + json_array_size (coin_evs)) + { + GNUNET_break_op (0); + return TEH_RESPONSE_reply_arg_invalid (connection, + TALER_EC_REFRESH_REVEAL_NEW_DENOMS_ARRAY_SIZE_MISSMATCH, + "new_denoms/coin_evs"); + } + + /* Parse transfer private keys array */ + for (unsigned int i=0;i<num_tprivs;i++) { - struct GNUNET_JSON_Specification tp_spec[] = { - GNUNET_JSON_spec_fixed_auto (NULL, &rc.transfer_privs[i]), + struct GNUNET_JSON_Specification trans_spec[] = { + GNUNET_JSON_spec_fixed_auto (NULL, &rctx->transfer_privs[i]), GNUNET_JSON_spec_end () }; int res; res = TEH_PARSE_json_array (connection, tp_json, - tp_spec, + trans_spec, i, - -1); - GNUNET_break_op (GNUNET_OK == res); + -1); if (GNUNET_OK != res) - return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; } - if (GNUNET_OK != - TEH_DB_run_transaction (connection, - &mhd_ret, - &refresh_reveal_transaction, - &rc)) + + /* Resolve denomination hashes */ { - cleanup_rc (&rc); - return mhd_ret; + const struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dkis[num_fresh_coins]; + struct TALER_RefreshCoinData rcds[num_fresh_coins]; + int res; + + /* Resolve denomination hashes */ + key_state = TEH_KS_acquire (); + if (NULL == key_state) + { + TALER_LOG_ERROR ("Lacking keys to operate\n"); + /* FIXME: use correct EC code! */ + return TEH_RESPONSE_reply_internal_error (connection, + TALER_EC_REFRESH_REVEAL_SIGNING_ERROR, + "exchange lacks keys"); + } + + /* Parse denomination key hashes */ + for (unsigned int i=0;i<num_fresh_coins;i++) + { + struct GNUNET_HashCode dpk_h; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto (NULL, + &dpk_h), + GNUNET_JSON_spec_end () + }; + + res = TEH_PARSE_json_array (connection, + new_denoms_h_json, + spec, + i, + -1); + if (GNUNET_OK != res) + { + TEH_KS_release (key_state); + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + } + dkis[i] = TEH_KS_denomination_key_lookup_by_hash (key_state, + &dpk_h, + TEH_KS_DKU_WITHDRAW); + if (NULL == dkis[i]) + { + TEH_KS_release (key_state); + return TEH_RESPONSE_reply_arg_invalid (connection, + TALER_EC_REFRESH_REVEAL_FRESH_DENOMINATION_KEY_NOT_FOUND, + "new_denoms"); + } + } + + /* Parse coin envelopes */ + for (unsigned int i=0;i<num_fresh_coins;i++) + { + struct TALER_RefreshCoinData *rcd = &rcds[i]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_varsize (NULL, + (void **) &rcd->coin_ev, + &rcd->coin_ev_size), + GNUNET_JSON_spec_end () + }; + + res = TEH_PARSE_json_array (connection, + coin_evs, + spec, + i, + -1); + if (GNUNET_OK != res) + { + for (unsigned int j=0;j<i;j++) + GNUNET_free_non_null (rcds[j].coin_ev); + TEH_KS_release (key_state); + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + } + rcd->dk = &dkis[i]->denom_pub; + } + + rctx->num_fresh_coins = num_fresh_coins; + rctx->rcds = rcds; + rctx->dkis = dkis; + /* do transactional work */ + if (GNUNET_OK == + TEH_DB_run_transaction (connection, + &res, + &refresh_reveal_transaction, + rctx)) + { + /* Generate final (positive) response */ + GNUNET_assert (NULL != rctx->ev_sigs); + res = reply_refresh_reveal_success (connection, + num_fresh_coins, + rctx->ev_sigs); + + } + + /* free resources */ + if (NULL != rctx->ev_sigs) + { + for (unsigned int i=0;i<num_fresh_coins;i++) + if (NULL != rctx->ev_sigs[i].rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (rctx->ev_sigs[i].rsa_signature); + GNUNET_free (rctx->ev_sigs); + } + for (unsigned int i=0;i<num_fresh_coins;i++) + GNUNET_free_non_null (rcds[i].coin_ev); + TEH_KS_release (key_state); + return res; } - mhd_ret = reply_refresh_reveal_success (connection, - rc.refresh_session.num_newcoins, - rc.ev_sigs); - cleanup_rc (&rc); - return mhd_ret; } + /** * Handle a "/refresh/reveal" request. This time, the client reveals * the private transfer keys except for the cut-and-choose value @@ -724,13 +639,18 @@ TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh, const char *upload_data, size_t *upload_data_size) { - struct GNUNET_HashCode session_hash; int res; json_t *root; + json_t *coin_evs; json_t *transfer_privs; + json_t *new_denoms_h; + struct RevealContext rctx; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("session_hash", &session_hash), + GNUNET_JSON_spec_fixed_auto ("rc", &rctx.rc), + GNUNET_JSON_spec_fixed_auto ("transfer_pub", &rctx.gamma_tp), GNUNET_JSON_spec_json ("transfer_privs", &transfer_privs), + GNUNET_JSON_spec_json ("coin_evs", &coin_evs), + GNUNET_JSON_spec_json ("new_denoms_h", &new_denoms_h), GNUNET_JSON_spec_end () }; @@ -745,6 +665,9 @@ TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh, (NULL == root) ) return MHD_YES; + memset (&rctx, + 0, + sizeof (rctx)); res = TEH_PARSE_json_data (connection, root, spec); @@ -754,7 +677,8 @@ TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh, GNUNET_break_op (0); return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; } - /* Determine dimensionality of the request (kappa and #old coins) */ + + /* Check we got enough transfer private keys */ /* Note we do +1 as 1 row (cut-and-choose!) is missing! */ if (TALER_CNC_KAPPA != json_array_size (transfer_privs) + 1) { @@ -765,8 +689,10 @@ TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh, "transfer_privs"); } res = handle_refresh_reveal_json (connection, - &session_hash, - transfer_privs); + &rctx, + transfer_privs, + new_denoms_h, + coin_evs); GNUNET_JSON_parse_free (spec); return res; } diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c index ac86416f3..64e7a9367 100644 --- a/src/exchange/taler-exchange-httpd_responses.c +++ b/src/exchange/taler-exchange-httpd_responses.c @@ -541,18 +541,18 @@ TEH_RESPONSE_compile_transaction_history (const struct TALER_EXCHANGEDB_Transact ms.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); ms.purpose.size = htonl (sizeof (struct TALER_RefreshMeltCoinAffirmationPS)); - ms.session_hash = melt->session_hash; + ms.rc = melt->session.rc; TALER_amount_hton (&ms.amount_with_fee, - &melt->amount_with_fee); + &melt->session.amount_with_fee); TALER_amount_hton (&ms.melt_fee, &melt->melt_fee); - ms.coin_pub = melt->coin.coin_pub; + ms.coin_pub = melt->session.coin.coin_pub; /* internal sanity check before we hand out a bogus sig... */ if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_MELT, &ms.purpose, - &melt->coin_sig.eddsa_signature, - &melt->coin.coin_pub.eddsa_pub)) + &melt->session.coin_sig.eddsa_signature, + &melt->session.coin.coin_pub.eddsa_pub)) { GNUNET_break (0); json_decref (history); @@ -563,10 +563,10 @@ TEH_RESPONSE_compile_transaction_history (const struct TALER_EXCHANGEDB_Transact json_array_append_new (history, json_pack ("{s:s, s:o, s:o, s:o, s:o}", "type", "MELT", - "amount", TALER_JSON_from_amount (&melt->amount_with_fee), + "amount", TALER_JSON_from_amount (&melt->session.amount_with_fee), "melt_fee", TALER_JSON_from_amount (&melt->melt_fee), - "session_hash", GNUNET_JSON_from_data_auto (&melt->session_hash), - "coin_sig", GNUNET_JSON_from_data_auto (&melt->coin_sig)))); + "rc", GNUNET_JSON_from_data_auto (&melt->session.rc), + "coin_sig", GNUNET_JSON_from_data_auto (&melt->session.coin_sig)))); } break; case TALER_EXCHANGEDB_TT_REFUND: @@ -812,7 +812,7 @@ TEH_RESPONSE_compile_reserve_history (const struct TALER_EXCHANGEDB_ReserveHisto return NULL; } - GNUNET_assert (0 == + GNUNET_assert (0 == json_array_append_new (json_history, json_pack ("{s:s, s:o, s:o, s:o, s:o, s:o}", "type", "PAYBACK", @@ -847,7 +847,7 @@ TEH_RESPONSE_compile_reserve_history (const struct TALER_EXCHANGEDB_ReserveHisto } } ret |= 2; - rcc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED); + rcc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED); rcc.purpose.size = htonl (sizeof (struct TALER_ReserveCloseConfirmationPS)); rcc.timestamp = GNUNET_TIME_absolute_hton (pos->details.closing->execution_date); TALER_amount_hton (&rcc.closing_amount, |