/* This file is part of TALER 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 Lesser General Public License as published by the Free Software Foundation; either version 2.1, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with TALER; see the file COPYING.LGPL. If not, see */ /** * @file merchant_api_tip_pickup.c * @brief Implementation of the /tip-pickup request of the merchant's HTTP API * @author Marcello Stanisci * @author Christian Grothoff */ #include "platform.h" #include #include #include /* just for HTTP status codes */ #include #include #include "taler_merchant_service.h" #include #include #include /** * Data we keep per planchet. */ struct PlanchetData { /** * Secrets of the planchet. */ struct TALER_PlanchetMasterSecretP ps; /** * Denomination key we are withdrawing. */ struct TALER_EXCHANGE_DenomPublicKey pk; /** * Hash of the public key of the coin we are signing. */ struct TALER_CoinPubHashP c_hash; /** * Nonce used for @e csr request, if any. */ struct TALER_CsNonce nonce; /** * Handle for a /csr request we may optionally need * to trigger. */ struct TALER_EXCHANGE_CsRWithdrawHandle *csr; /** * Handle for the /tip-pickup operation we are part of. */ struct TALER_MERCHANT_TipPickupHandle *tp; /** * Offset of this entry in the array. */ unsigned int off; }; /** * Handle for a /tip-pickup operation. */ struct TALER_MERCHANT_TipPickupHandle { /** * Function to call with the result. */ TALER_MERCHANT_TipPickupCallback cb; /** * Closure for @a cb. */ void *cb_cls; /** * Handle for the actual (internal) withdraw operation. */ struct TALER_MERCHANT_TipPickup2Handle *tpo2; /** * Array of length @e num_planchets. */ struct PlanchetData *planchets; /** * Array of length @e num_planchets. */ struct TALER_EXCHANGE_PrivateCoinDetails *pcds; /** * Context for making HTTP requests. */ struct GNUNET_CURL_Context *ctx; /** * URL of the merchant backend. */ char *backend_url; /** * ID of the tip we are picking up. */ struct TALER_TipIdentifierP tip_id; /** * Number of planchets/coins used for this operation. */ unsigned int num_planchets; /** * Number of remaining active /csr-withdraw requests. */ unsigned int csr_active; }; /** * Fail the pickup operation @a tp, returning @a ec. * Also cancels @a tp. * * @param[in] tp operation to fail * @param ec reason for the failure */ static void fail_pickup (struct TALER_MERCHANT_TipPickupHandle *tp, enum TALER_ErrorCode ec) { struct TALER_MERCHANT_PickupDetails pd = { .hr.ec = ec }; tp->cb (tp->cb_cls, &pd); TALER_MERCHANT_tip_pickup_cancel (tp); } /** * Callback for a /tip-pickup request. Returns the result of the operation. * Note that the client MUST still do the unblinding of the @a blind_sigs. * * @param cls closure, a `struct TALER_MERCHANT_TipPickupHandle *` * @param hr HTTP response details * @param num_blind_sigs length of the @a reserve_sigs array, 0 on error * @param blind_sigs array of blind signatures over the planchets, NULL on error */ static void pickup_done_cb (void *cls, const struct TALER_MERCHANT_HttpResponse *hr, unsigned int num_blind_sigs, const struct TALER_BlindedDenominationSignature *blind_sigs) { struct TALER_MERCHANT_TipPickupHandle *tp = cls; struct TALER_MERCHANT_PickupDetails pd = { .hr = *hr }; tp->tpo2 = NULL; if (NULL == blind_sigs) { tp->cb (tp->cb_cls, &pd); TALER_MERCHANT_tip_pickup_cancel (tp); return; } { enum GNUNET_GenericReturnValue ok = GNUNET_OK; for (unsigned int i = 0; ipcds[i]; struct TALER_FreshCoin fc; if (GNUNET_OK != TALER_planchet_to_coin (&tp->planchets[i].pk.key, &blind_sigs[i], &pcd->bks, &pcd->coin_priv, NULL, &tp->planchets[i].c_hash, &pcd->exchange_vals, &fc)) { ok = GNUNET_SYSERR; break; } pcd->sig = fc.sig; } if (GNUNET_OK != ok) { struct TALER_MERCHANT_HttpResponse hrx = { .reply = hr->reply, .ec = TALER_EC_MERCHANT_TIP_PICKUP_UNBLIND_FAILURE }; pd.hr = hrx; tp->cb (tp->cb_cls, &pd); } else { pd.details.success.num_sigs = num_blind_sigs; pd.details.success.pcds = tp->pcds; tp->cb (tp->cb_cls, &pd); } } TALER_MERCHANT_tip_pickup_cancel (tp); } /** * We have obtained all of the exchange inputs. Continue the pickup. * * @param[in,out] tp operation to continue */ static void pickup_post_csr (struct TALER_MERCHANT_TipPickupHandle *tp) { struct TALER_PlanchetDetail details[tp->num_planchets]; for (unsigned int i = 0; inum_planchets; i++) { const struct PlanchetData *pd = &tp->planchets[i]; struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[i]; TALER_planchet_setup_coin_priv (&pd->ps, &pcd->exchange_vals, &pcd->coin_priv); TALER_planchet_blinding_secret_create (&pd->ps, &pcd->exchange_vals, &pcd->bks); if (TALER_DENOMINATION_CS == pcd->exchange_vals.cipher) { details[i].blinded_planchet.details.cs_blinded_planchet.nonce = pd->nonce; } if (GNUNET_OK != TALER_planchet_prepare (&pd->pk.key, &pcd->exchange_vals, &pcd->bks, &pcd->coin_priv, NULL, &tp->planchets[i].c_hash, &details[i])) { GNUNET_break (0); for (unsigned int j = 0; jtpo2 = TALER_MERCHANT_tip_pickup2 (tp->ctx, tp->backend_url, &tp->tip_id, tp->num_planchets, details, &pickup_done_cb, tp); for (unsigned int j = 0; jnum_planchets; j++) TALER_planchet_detail_free (&details[j]); if (NULL == tp->tpo2) { GNUNET_break (0); fail_pickup (tp, TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE); return; } } /** * Callbacks of this type are used to serve the result of submitting a * CS R request to a exchange. * * @param cls a `struct TALER_MERCHANT_TipPickupHandle` * @param csrr response details */ static void csr_cb (void *cls, const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr) { struct PlanchetData *pd = cls; struct TALER_MERCHANT_TipPickupHandle *tp = pd->tp; pd->csr = NULL; tp->csr_active--; switch (csrr->hr.http_status) { case MHD_HTTP_OK: { struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[pd->off]; pcd->exchange_vals = csrr->details.success.alg_values; } if (0 != tp->csr_active) return; pickup_post_csr (tp); return; default: { struct TALER_MERCHANT_PickupDetails pd = { .hr.hint = "/csr-withdraw failed", .hr.exchange_http_status = csrr->hr.http_status }; tp->cb (tp->cb_cls, &pd); TALER_MERCHANT_tip_pickup_cancel (tp); return; } } } struct TALER_MERCHANT_TipPickupHandle * TALER_MERCHANT_tip_pickup (struct GNUNET_CURL_Context *ctx, struct TALER_EXCHANGE_Handle *exchange, const char *backend_url, const struct TALER_TipIdentifierP *tip_id, unsigned int num_planchets, const struct TALER_MERCHANT_PlanchetData *pds, TALER_MERCHANT_TipPickupCallback pickup_cb, void *pickup_cb_cls) { struct TALER_MERCHANT_TipPickupHandle *tp; if (0 == num_planchets) { GNUNET_break (0); return NULL; } tp = GNUNET_new (struct TALER_MERCHANT_TipPickupHandle); tp->cb = pickup_cb; tp->cb_cls = pickup_cb_cls; tp->ctx = ctx; tp->backend_url = GNUNET_strdup (backend_url); tp->tip_id = *tip_id; tp->num_planchets = num_planchets; tp->planchets = GNUNET_new_array (num_planchets, struct PlanchetData); tp->pcds = GNUNET_new_array (num_planchets, struct TALER_EXCHANGE_PrivateCoinDetails); for (unsigned int i = 0; ipk; struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[i]; struct PlanchetData *pd = &tp->planchets[i]; pd->off = i; pd->tp = tp; tp->planchets[i].ps = mpd->ps; tp->planchets[i].pk = *pds[i].pk; TALER_denom_pub_deep_copy (&tp->planchets[i].pk.key, &pds[i].pk->key); switch (pk->key.cipher) { case TALER_DENOMINATION_RSA: pcd->exchange_vals.cipher = TALER_DENOMINATION_RSA; break; case TALER_DENOMINATION_CS: { TALER_cs_withdraw_nonce_derive (&pd->ps, &pd->nonce); pd->csr = TALER_EXCHANGE_csr_withdraw (exchange, &pd->pk, &pd->nonce, &csr_cb, pd); if (NULL == pd->csr) { GNUNET_break (0); TALER_MERCHANT_tip_pickup_cancel (tp); return NULL; } tp->csr_active++; break; } default: GNUNET_break (0); TALER_MERCHANT_tip_pickup_cancel (tp); return NULL; } } if (0 == tp->csr_active) { pickup_post_csr (tp); return tp; } return tp; } void TALER_MERCHANT_tip_pickup_cancel (struct TALER_MERCHANT_TipPickupHandle *tp) { for (unsigned int i = 0; inum_planchets; i++) { struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[i]; struct PlanchetData *pd = &tp->planchets[i]; TALER_denom_sig_free (&pcd->sig); TALER_denom_pub_free (&tp->planchets[i].pk.key); if (NULL != pd->csr) { TALER_EXCHANGE_csr_withdraw_cancel (pd->csr); pd->csr = NULL; } } GNUNET_array_grow (tp->planchets, tp->num_planchets, 0); if (NULL != tp->tpo2) { TALER_MERCHANT_tip_pickup2_cancel (tp->tpo2); tp->tpo2 = NULL; } GNUNET_free (tp->backend_url); GNUNET_free (tp->pcds); GNUNET_free (tp); } /* end of merchant_api_tip_pickup.c */