diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_refreshes_reveal.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_refreshes_reveal.c | 585 |
1 files changed, 457 insertions, 128 deletions
diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.c b/src/exchange/taler-exchange-httpd_refreshes_reveal.c index 30a7294c1..5630051cf 100644 --- a/src/exchange/taler-exchange-httpd_refreshes_reveal.c +++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2019, 2021 Taler Systems SA + Copyright (C) 2014-2022 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -32,17 +32,11 @@ /** - * Maximum number of fresh coins we allow per refresh operation. - */ -#define MAX_FRESH_COINS 256 - - -/** * Send a response for "/refreshes/$RCH/reveal". * * @param connection the connection to send the response to * @param num_freshcoins number of new coins for which we reveal data - * @param sigs array of @a num_freshcoins signatures revealed + * @param rrcs array of @a num_freshcoins signatures revealed * @return a MHD result code */ static MHD_RESULT @@ -109,15 +103,35 @@ struct RevealContext const struct TEH_DenominationKey **dks; /** + * Age commitment that was used for the original coin. If not NULL, its hash + * should be the same as melt.session.h_age_commitment. + */ + struct TALER_AgeCommitment *old_age_commitment; + + /** + * Array of information about fresh coins being revealed. + */ + struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs; + + /** * Envelopes to be signed. */ struct TALER_RefreshCoinData *rcds; /** + * Refresh master secret. + */ + struct TALER_RefreshMasterSecretP rms; + + /** * Size of the @e dks, @e rcds and @e ev_sigs arrays (if non-NULL). */ unsigned int num_fresh_coins; + /** + * True if @e rms was not provided. + */ + bool no_rms; }; @@ -142,6 +156,89 @@ check_commitment (struct RevealContext *rctx, struct MHD_Connection *connection, MHD_RESULT *mhd_ret) { + const union GNUNET_CRYPTO_BlindSessionNonce *nonces[rctx->num_fresh_coins]; + + memset (nonces, + 0, + sizeof (nonces)); + for (unsigned int j = 0; j<rctx->num_fresh_coins; j++) + { + const struct TALER_DenominationPublicKey *dk = &rctx->dks[j]->denom_pub; + + if (dk->bsign_pub_key->cipher != + rctx->rcds[j].blinded_planchet.blinded_message->cipher) + { + GNUNET_break (0); + *mhd_ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH, + NULL); + return GNUNET_SYSERR; + } + switch (dk->bsign_pub_key->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + GNUNET_break (0); + *mhd_ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + NULL); + return GNUNET_SYSERR; + case GNUNET_CRYPTO_BSA_RSA: + continue; + case GNUNET_CRYPTO_BSA_CS: + nonces[j] + = (const union GNUNET_CRYPTO_BlindSessionNonce *) + &rctx->rcds[j].blinded_planchet.blinded_message->details. + cs_blinded_message.nonce; + break; + } + } + + // OPTIMIZE: do this in batch later! + for (unsigned int j = 0; j<rctx->num_fresh_coins; j++) + { + const struct TALER_DenominationPublicKey *dk = &rctx->dks[j]->denom_pub; + struct TALER_ExchangeWithdrawValues *alg_values + = &rctx->rrcs[j].exchange_vals; + struct GNUNET_CRYPTO_BlindingInputValues *bi; + + bi = GNUNET_new (struct GNUNET_CRYPTO_BlindingInputValues); + alg_values->blinding_inputs = bi; + bi->rc = 1; + bi->cipher = dk->bsign_pub_key->cipher; + switch (dk->bsign_pub_key->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + GNUNET_assert (0); + return GNUNET_SYSERR; + case GNUNET_CRYPTO_BSA_RSA: + continue; + case GNUNET_CRYPTO_BSA_CS: + { + enum TALER_ErrorCode ec; + const struct TEH_CsDeriveData cdd = { + .h_denom_pub = &rctx->rrcs[j].h_denom_pub, + .nonce = &nonces[j]->cs_nonce + }; + + ec = TEH_keys_denomination_cs_r_pub ( + &cdd, + true, + &bi->details.cs_values); + if (TALER_EC_NONE != ec) + { + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + ec, + NULL); + return GNUNET_SYSERR; + } + } + } + } /* Verify commitment */ { /* Note that the contents of rcs[melt.session.noreveal_index] @@ -169,9 +266,12 @@ check_commitment (struct RevealContext *rctx, const struct TALER_TransferPrivateKeyP *tpriv = &rctx->transfer_privs[i - off]; struct TALER_TransferSecretP ts; + struct TALER_AgeCommitmentHash h = {0}; + struct TALER_AgeCommitmentHash *hac = NULL; GNUNET_CRYPTO_ecdhe_key_get_public (&tpriv->ecdhe_priv, &rce->transfer_pub.ecdhe_pub); + TEH_METRICS_num_keyexchanges[TEH_MT_KEYX_ECDH]++; TALER_link_reveal_transfer_secret (tpriv, &rctx->melt.session.coin.coin_pub, &ts); @@ -180,26 +280,64 @@ check_commitment (struct RevealContext *rctx, 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; - struct TALER_CoinPubHash c_hash; + struct TALER_CoinSpendPrivateKeyP coin_priv; + union GNUNET_CRYPTO_BlindingSecretP bks; + const struct TALER_ExchangeWithdrawValues *alg_value + = &rctx->rrcs[j].exchange_vals; + struct TALER_PlanchetDetail pd = {0}; + struct TALER_CoinPubHashP c_hash; + struct TALER_PlanchetMasterSecretP ps; rcd->dk = &rctx->dks[j]->denom_pub; - TALER_planchet_setup_refresh (&ts, - j, - &ps); + TALER_transfer_secret_to_planchet_secret (&ts, + j, + &ps); + TALER_planchet_setup_coin_priv (&ps, + alg_value, + &coin_priv); + TALER_planchet_blinding_secret_create (&ps, + alg_value, + &bks); + /* Calculate, if applicable, the age commitment and its hash, from + * the transfer_secret and the old age commitment. */ + if (NULL != rctx->old_age_commitment) + { + struct TALER_AgeCommitmentProof acp = { + /* we only need the commitment, not the proof, for the call to + * TALER_age_commitment_derive. */ + .commitment = *(rctx->old_age_commitment) + }; + struct TALER_AgeCommitmentProof nacp = {0}; + + GNUNET_assert (GNUNET_OK == + TALER_age_commitment_derive ( + &acp, + &ts.key, + &nacp)); + TALER_age_commitment_hash (&nacp.commitment, + &h); + TALER_age_commitment_proof_free (&nacp); + hac = &h; + } + GNUNET_assert (GNUNET_OK == TALER_planchet_prepare (rcd->dk, - &ps, + alg_value, + &bks, + nonces[j], + &coin_priv, + hac, &c_hash, &pd)); - rcd->coin_ev = pd.coin_ev; - rcd->coin_ev_size = pd.coin_ev_size; + rcd->blinded_planchet = pd.blinded_planchet; } } } TALER_refresh_get_commitment (&rc_expected, TALER_CNC_KAPPA, + rctx->no_rms + ? NULL + : &rctx->rms, rctx->num_fresh_coins, rcs, &rctx->melt.session.coin.coin_pub, @@ -216,7 +354,7 @@ check_commitment (struct RevealContext *rctx, { struct TALER_RefreshCoinData *rcd = &rce->new_coins[j]; - GNUNET_free (rcd->coin_ev); + TALER_blinded_planchet_free (&rcd->blinded_planchet); } GNUNET_free (rce->new_coins); } @@ -248,7 +386,7 @@ check_commitment (struct RevealContext *rctx, if ( (0 > TALER_amount_add (&total, - &rctx->dks[i]->meta.fee_withdraw, + &rctx->dks[i]->meta.fees.withdraw, &rctx->dks[i]->meta.value)) || (0 > TALER_amount_add (&refresh_cost, @@ -285,24 +423,29 @@ check_commitment (struct RevealContext *rctx, * @param rctx context for the operation, partially built at this time * @param link_sigs_json link signatures in JSON format * @param new_denoms_h_json requests for fresh coins to be created + * @param old_age_commitment_json age commitment that went into the withdrawal, maybe NULL * @param coin_evs envelopes of gamma-selected coins to be signed * @return MHD result code */ static MHD_RESULT -resolve_refreshes_reveal_denominations (struct MHD_Connection *connection, - struct RevealContext *rctx, - const json_t *link_sigs_json, - const json_t *new_denoms_h_json, - const json_t *coin_evs) +resolve_refreshes_reveal_denominations ( + struct MHD_Connection *connection, + struct RevealContext *rctx, + const json_t *link_sigs_json, + const json_t *new_denoms_h_json, + const json_t *old_age_commitment_json, + const json_t *coin_evs) { unsigned int num_fresh_coins = json_array_size (new_denoms_h_json); - /* We know num_fresh_coins is bounded by #MAX_FRESH_COINS, so this is safe */ + /* We know num_fresh_coins is bounded by #TALER_MAX_FRESH_COINS, so this is safe */ const struct TEH_DenominationKey *dks[num_fresh_coins]; + const struct TEH_DenominationKey *old_dk; struct TALER_RefreshCoinData rcds[num_fresh_coins]; struct TALER_EXCHANGEDB_RefreshRevealedCoin rrcs[num_fresh_coins]; MHD_RESULT ret; struct TEH_KeyStateHandle *ksh; uint64_t melt_serial_id; + enum GNUNET_DB_QueryStatus qs; memset (dks, 0, sizeof (dks)); memset (rrcs, 0, sizeof (rrcs)); @@ -317,6 +460,61 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection, TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING, NULL); } + + /* lookup old_coin_pub in database */ + { + enum GNUNET_DB_QueryStatus qs; + + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + (qs = TEH_plugin->get_melt (TEH_plugin->cls, + &rctx->rc, + &rctx->melt, + &melt_serial_id))) + { + switch (qs) + { + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN, + NULL); + break; + case GNUNET_DB_STATUS_HARD_ERROR: + ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "melt"); + break; + case GNUNET_DB_STATUS_SOFT_ERROR: + default: + GNUNET_break (0); /* should be impossible */ + ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + NULL); + break; + } + goto cleanup; + } + if (rctx->melt.session.noreveal_index >= TALER_CNC_KAPPA) + { + GNUNET_break (0); + ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "melt"); + goto cleanup; + } + } + + old_dk = TEH_keys_denomination_by_hash_from_state ( + ksh, + &rctx->melt.session.coin.denom_pub_hash, + connection, + &ret); + if (NULL == old_dk) + return ret; + /* Parse denomination key hashes */ for (unsigned int i = 0; i<num_fresh_coins; i++) { @@ -334,13 +532,22 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection, -1); if (GNUNET_OK != res) return (GNUNET_NO == res) ? MHD_YES : MHD_NO; - dks[i] = TEH_keys_denomination_by_hash2 (ksh, - &rrcs[i].h_denom_pub, - connection, - &ret); + dks[i] = TEH_keys_denomination_by_hash_from_state (ksh, + &rrcs[i].h_denom_pub, + connection, + &ret); if (NULL == dks[i]) return ret; - + if ( (GNUNET_CRYPTO_BSA_CS == + dks[i]->denom_pub.bsign_pub_key->cipher) && + (rctx->no_rms) ) + { + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MISSING, + "rms"); + } if (GNUNET_TIME_absolute_is_past (dks[i]->meta.expire_withdraw.abs_time)) { /* This denomination is past the expiration time for withdraws */ @@ -375,9 +582,8 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection, { struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i]; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_varsize (NULL, - &rrc->coin_ev, - &rrc->coin_ev_size), + TALER_JSON_spec_blinded_planchet (NULL, + &rrc->blinded_planchet), GNUNET_JSON_spec_end () }; enum GNUNET_GenericReturnValue res; @@ -390,59 +596,85 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection, if (GNUNET_OK != res) { for (unsigned int j = 0; j<i; j++) - GNUNET_free (rrcs[j].coin_ev); + TALER_blinded_planchet_free (&rrcs[j].blinded_planchet); return (GNUNET_NO == res) ? MHD_YES : MHD_NO; } - GNUNET_CRYPTO_hash (rrc->coin_ev, - rrc->coin_ev_size, - &rrc->coin_envelope_hash.hash); + TALER_coin_ev_hash (&rrc->blinded_planchet, + &rrcs[i].h_denom_pub, + &rrc->coin_envelope_hash); } - /* lookup old_coin_pub in database */ + if (TEH_age_restriction_enabled && + ((NULL == old_age_commitment_json) != + TALER_AgeCommitmentHash_isNullOrZero ( + &rctx->melt.session.coin.h_age_commitment))) { - enum GNUNET_DB_QueryStatus qs; + GNUNET_break (0); + return MHD_NO; + } - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != - (qs = TEH_plugin->get_melt (TEH_plugin->cls, - &rctx->rc, - &rctx->melt, - &melt_serial_id))) + /* Reconstruct the old age commitment and verify its hash matches the one + * from the melt request */ + if (TEH_age_restriction_enabled && + (NULL != old_age_commitment_json)) + { + enum GNUNET_GenericReturnValue res; + struct TALER_AgeCommitment *oac; + size_t ng = json_array_size (old_age_commitment_json); + bool failed = true; + + /* Has been checked in handle_refreshes_reveal_json() */ + GNUNET_assert (ng == TEH_age_restriction_config.num_groups); + + rctx->old_age_commitment = GNUNET_new (struct TALER_AgeCommitment); + oac = rctx->old_age_commitment; + oac->mask = old_dk->meta.age_mask; + oac->num = ng; + oac->keys = GNUNET_new_array (ng, struct TALER_AgeCommitmentPublicKeyP); + + /* Extract old age commitment */ + for (unsigned int i = 0; i< ng; i++) { - switch (qs) - { - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN, - NULL); - break; - case GNUNET_DB_STATUS_HARD_ERROR: - ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "melt"); - break; - case GNUNET_DB_STATUS_SOFT_ERROR: - default: - GNUNET_break (0); /* should be impossible */ - ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - NULL); - break; - } - goto cleanup; + struct GNUNET_JSON_Specification ac_spec[] = { + GNUNET_JSON_spec_fixed_auto (NULL, + &oac->keys[i]), + GNUNET_JSON_spec_end () + }; + + res = TALER_MHD_parse_json_array (connection, + old_age_commitment_json, + ac_spec, + i, + -1); + + GNUNET_break_op (GNUNET_OK == res); + if (GNUNET_OK != res) + goto clean_age; } - if (rctx->melt.session.noreveal_index >= TALER_CNC_KAPPA) + + /* Sanity check: Compare hash from melting with hash of this age commitment */ { - GNUNET_break (0); - ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "melt"); - goto cleanup; + struct TALER_AgeCommitmentHash hac = {0}; + TALER_age_commitment_hash (oac, &hac); + if (0 != memcmp (&hac, + &rctx->melt.session.coin.h_age_commitment, + sizeof(struct TALER_AgeCommitmentHash))) + goto clean_age; + } + + failed = false; + +clean_age: + if (failed) + { + TALER_age_commitment_free (oac); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID, + "old_age_commitment"); } } + /* Parse link signatures array */ for (unsigned int i = 0; i<num_fresh_coins; i++) { @@ -460,7 +692,9 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection, -1); if (GNUNET_OK != res) return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + /* Check signature */ + TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; if (GNUNET_OK != TALER_wallet_link_verify ( &rrcs[i].h_denom_pub, @@ -485,12 +719,24 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection, const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i]; struct TALER_RefreshCoinData *rcd = &rcds[i]; - rcd->coin_ev = rrc->coin_ev; - rcd->coin_ev_size = rrc->coin_ev_size; + rcd->blinded_planchet = rrc->blinded_planchet; rcd->dk = &dks[i]->denom_pub; + if (rcd->blinded_planchet.blinded_message->cipher != + rcd->dk->bsign_pub_key->cipher) + { + GNUNET_break_op (0); + ret = TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_BAD_REQUEST, + TALER_JSON_pack_ec ( + TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH)); + goto cleanup; + } } + rctx->dks = dks; rctx->rcds = rcds; + rctx->rrcs = rrcs; if (GNUNET_OK != check_commitment (rctx, connection, @@ -500,17 +746,23 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection, GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Creating %u signatures\n", (unsigned int) rctx->num_fresh_coins); + /* create fresh coin signatures */ - for (unsigned int i = 0; i<rctx->num_fresh_coins; i++) { - enum TALER_ErrorCode ec = TALER_EC_NONE; + struct TEH_CoinSignData csds[rctx->num_fresh_coins]; + struct TALER_BlindedDenominationSignature bss[rctx->num_fresh_coins]; + enum TALER_ErrorCode ec; - rrcs[i].coin_sig - = TEH_keys_denomination_sign ( - &rrcs[i].h_denom_pub, - rcds[i].coin_ev, - rcds[i].coin_ev_size, - &ec); + for (unsigned int i = 0; i<rctx->num_fresh_coins; i++) + { + csds[i].h_denom_pub = &rrcs[i].h_denom_pub; + csds[i].bp = &rcds[i].blinded_planchet; + } + ec = TEH_keys_denomination_batch_sign ( + rctx->num_fresh_coins, + csds, + true, + bss); if (TALER_EC_NONE != ec) { GNUNET_break (0); @@ -519,39 +771,88 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection, NULL); goto cleanup; } + + for (unsigned int i = 0; i<rctx->num_fresh_coins; i++) + { + rrcs[i].coin_sig = bss[i]; + rrcs[i].blinded_planchet = rcds[i].blinded_planchet; + } } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Signatures ready, starting DB interaction\n"); - /* Persist operation result in DB */ + + + for (unsigned int r = 0; r<MAX_TRANSACTION_COMMIT_RETRIES; r++) { - enum GNUNET_DB_QueryStatus qs; + bool changed; - for (unsigned int i = 0; i<rctx->num_fresh_coins; i++) + /* Persist operation result in DB */ + if (GNUNET_OK != + TEH_plugin->start (TEH_plugin->cls, + "insert_refresh_reveal batch")) { - struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i]; + GNUNET_break (0); + ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_START_FAILED, + NULL); + goto cleanup; + } - rrc->coin_ev = rcds[i].coin_ev; - rrc->coin_ev_size = rcds[i].coin_ev_size; + qs = TEH_plugin->insert_refresh_reveal ( + TEH_plugin->cls, + melt_serial_id, + num_fresh_coins, + rrcs, + TALER_CNC_KAPPA - 1, + rctx->transfer_privs, + &rctx->gamma_tp); + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + TEH_plugin->rollback (TEH_plugin->cls); + continue; } - qs = TEH_plugin->insert_refresh_reveal (TEH_plugin->cls, - melt_serial_id, - num_fresh_coins, - rrcs, - TALER_CNC_KAPPA - 1, - rctx->transfer_privs, - &rctx->gamma_tp); /* 0 == qs is ok, as we did not check for repeated requests */ - if (0 > qs) + if (GNUNET_DB_STATUS_HARD_ERROR == qs) { GNUNET_break (0); + TEH_plugin->rollback (TEH_plugin->cls); ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_STORE_FAILED, "insert_refresh_reveal"); goto cleanup; } + changed = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); + qs = TEH_plugin->commit (TEH_plugin->cls); + if (qs >= 0) + { + if (changed) + TEH_METRICS_num_success[TEH_MT_SUCCESS_REFRESH_REVEAL]++; + break; /* success */ + } + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + GNUNET_break (0); + TEH_plugin->rollback (TEH_plugin->cls); + ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_COMMIT_FAILED, + NULL); + goto cleanup; + } + TEH_plugin->rollback (TEH_plugin->cls); + } + if (GNUNET_DB_STATUS_SOFT_ERROR == qs) + { + GNUNET_break (0); + TEH_plugin->rollback (TEH_plugin->cls); + ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_SOFT_FAILURE, + NULL); + goto cleanup; } - /* Generate final (positive) response */ ret = reply_refreshes_reveal_success (connection, num_fresh_coins, @@ -562,9 +863,12 @@ cleanup: for (unsigned int i = 0; i<num_fresh_coins; i++) { struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i]; + struct TALER_ExchangeWithdrawValues *alg_values + = &rrcs[i].exchange_vals; + GNUNET_free (alg_values->blinding_inputs); TALER_blinded_denom_sig_free (&rrc->coin_sig); - GNUNET_free (rrc->coin_ev); + TALER_blinded_planchet_free (&rrc->blinded_planchet); } return ret; } @@ -577,11 +881,18 @@ cleanup: * revealed information is valid then returns the signed refreshed * coins. * + * If the denomination has age restriction support, the array of EDDSA public + * keys, one for each age group that was activated during the withdrawal + * by the parent/ward, must be provided in old_age_commitment. The hash of + * this array must be the same as the h_age_commitment of the persisted reveal + * request. + * * @param connection the MHD connection to handle * @param rctx context for the operation, partially built at this time * @param tp_json private transfer keys in JSON format * @param link_sigs_json link signatures in JSON format * @param new_denoms_h_json requests for fresh coins to be created + * @param old_age_commitment_json array of EDDSA public keys in JSON, used for age restriction, maybe NULL * @param coin_evs envelopes of gamma-selected coins to be signed * @return MHD result code */ @@ -591,19 +902,20 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection, const json_t *tp_json, const json_t *link_sigs_json, const json_t *new_denoms_h_json, + const json_t *old_age_commitment_json, const json_t *coin_evs) { unsigned int num_fresh_coins = json_array_size (new_denoms_h_json); unsigned int num_tprivs = json_array_size (tp_json); GNUNET_assert (num_tprivs == TALER_CNC_KAPPA - 1); /* checked just earlier */ - if ( (num_fresh_coins >= MAX_FRESH_COINS) || + if ( (num_fresh_coins >= TALER_MAX_FRESH_COINS) || (0 == num_fresh_coins) ) { GNUNET_break_op (0); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, - TALER_EC_EXCHANGE_REFRESHES_REVEAL_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE, + TALER_EC_EXCHANGE_GENERIC_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE, NULL); } @@ -626,6 +938,19 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection, "new_denoms/link_sigs"); } + /* Sanity check of age commitment: If it was provided, it _must_ be an array + * of the size the # of age groups */ + if (NULL != old_age_commitment_json + && TEH_age_restriction_config.num_groups != + json_array_size (old_age_commitment_json)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID, + "old_age_commitment"); + } + /* Parse transfer private keys array */ for (unsigned int i = 0; i<num_tprivs; i++) { @@ -649,6 +974,7 @@ handle_refreshes_reveal_json (struct MHD_Connection *connection, rctx, link_sigs_json, new_denoms_h_json, + old_age_commitment_json, coin_evs); } @@ -658,22 +984,31 @@ TEH_handler_reveal (struct TEH_RequestContext *rc, const json_t *root, const char *const args[2]) { - json_t *coin_evs; - json_t *transfer_privs; - json_t *link_sigs; - json_t *new_denoms_h; + const json_t *coin_evs; + const json_t *transfer_privs; + const json_t *link_sigs; + const json_t *new_denoms_h; + const json_t *old_age_commitment; struct RevealContext rctx; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("transfer_pub", &rctx.gamma_tp), - GNUNET_JSON_spec_json ("transfer_privs", - &transfer_privs), - GNUNET_JSON_spec_json ("link_sigs", - &link_sigs), - GNUNET_JSON_spec_json ("coin_evs", - &coin_evs), - GNUNET_JSON_spec_json ("new_denoms_h", - &new_denoms_h), + GNUNET_JSON_spec_array_const ("transfer_privs", + &transfer_privs), + GNUNET_JSON_spec_array_const ("link_sigs", + &link_sigs), + GNUNET_JSON_spec_array_const ("coin_evs", + &coin_evs), + GNUNET_JSON_spec_array_const ("new_denoms_h", + &new_denoms_h), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("old_age_commitment", + &old_age_commitment), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ("rms", + &rctx.rms), + &rctx.no_rms), GNUNET_JSON_spec_end () }; @@ -719,7 +1054,6 @@ TEH_handler_reveal (struct TEH_RequestContext *rc, /* Note we do +1 as 1 row (cut-and-choose!) is missing! */ if (TALER_CNC_KAPPA != json_array_size (transfer_privs) + 1) { - GNUNET_JSON_parse_free (spec); GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_BAD_REQUEST, @@ -727,18 +1061,13 @@ TEH_handler_reveal (struct TEH_RequestContext *rc, NULL); } - { - MHD_RESULT res; - - res = handle_refreshes_reveal_json (rc->connection, - &rctx, - transfer_privs, - link_sigs, - new_denoms_h, - coin_evs); - GNUNET_JSON_parse_free (spec); - return res; - } + return handle_refreshes_reveal_json (rc->connection, + &rctx, + transfer_privs, + link_sigs, + new_denoms_h, + old_age_commitment, + coin_evs); } |