-implement batch withdraw client-side logic
This commit is contained in:
parent
656b521a83
commit
054f2ab51c
@ -1882,6 +1882,29 @@ TALER_EXCHANGE_reserves_history_cancel (
|
|||||||
struct TALER_EXCHANGE_WithdrawHandle;
|
struct TALER_EXCHANGE_WithdrawHandle;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information input into the withdraw process per coin.
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGE_WithdrawCoinInput
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Denomination of the coin.
|
||||||
|
*/
|
||||||
|
const struct TALER_EXCHANGE_DenomPublicKey *pk;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Master key material for the coin.
|
||||||
|
*/
|
||||||
|
const struct TALER_PlanchetMasterSecretP *ps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Age commitment for the coin.
|
||||||
|
*/
|
||||||
|
const struct TALER_AgeCommitmentHash *ach;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All the details about a coin that are generated during withdrawal and that
|
* All the details about a coin that are generated during withdrawal and that
|
||||||
* may be needed for future operations on the coin.
|
* may be needed for future operations on the coin.
|
||||||
@ -1988,11 +2011,8 @@ typedef void
|
|||||||
* same arguments in case of failures.
|
* same arguments in case of failures.
|
||||||
*
|
*
|
||||||
* @param exchange the exchange handle; the exchange must be ready to operate
|
* @param exchange the exchange handle; the exchange must be ready to operate
|
||||||
* @param pk kind of coin to create
|
|
||||||
* @param reserve_priv private key of the reserve to withdraw from
|
* @param reserve_priv private key of the reserve to withdraw from
|
||||||
* @param ps secrets of the planchet
|
* @param wci inputs that determine the planchet
|
||||||
* caller must have committed this value to disk before the call (with @a pk)
|
|
||||||
* @param ach hash of the age commitment that should be bound to this coin. Maybe NULL.
|
|
||||||
* @param res_cb the callback to call when the final result for this request is available
|
* @param res_cb the callback to call when the final result for this request is available
|
||||||
* @param res_cb_cls closure for @a res_cb
|
* @param res_cb_cls closure for @a res_cb
|
||||||
* @return NULL
|
* @return NULL
|
||||||
@ -2002,10 +2022,8 @@ typedef void
|
|||||||
struct TALER_EXCHANGE_WithdrawHandle *
|
struct TALER_EXCHANGE_WithdrawHandle *
|
||||||
TALER_EXCHANGE_withdraw (
|
TALER_EXCHANGE_withdraw (
|
||||||
struct TALER_EXCHANGE_Handle *exchange,
|
struct TALER_EXCHANGE_Handle *exchange,
|
||||||
const struct TALER_EXCHANGE_DenomPublicKey *pk,
|
|
||||||
const struct TALER_ReservePrivateKeyP *reserve_priv,
|
const struct TALER_ReservePrivateKeyP *reserve_priv,
|
||||||
const struct TALER_PlanchetMasterSecretP *ps,
|
const struct TALER_EXCHANGE_WithdrawCoinInput *wci,
|
||||||
const struct TALER_AgeCommitmentHash *ach,
|
|
||||||
TALER_EXCHANGE_WithdrawCallback res_cb,
|
TALER_EXCHANGE_WithdrawCallback res_cb,
|
||||||
void *res_cb_cls);
|
void *res_cb_cls);
|
||||||
|
|
||||||
@ -2020,6 +2038,130 @@ void
|
|||||||
TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh);
|
TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A /reserves/$RESERVE_PUB/batch-withdraw Handle
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGE_BatchWithdrawHandle;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Details about a response for a batch withdraw request.
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGE_BatchWithdrawResponse
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* HTTP response data.
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGE_HttpResponse hr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Details about the response.
|
||||||
|
*/
|
||||||
|
union
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Details if the status is #MHD_HTTP_OK.
|
||||||
|
*/
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of coins returned by the batch withdraw operation.
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGE_PrivateCoinDetails *coins;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Length of the @e coins array.
|
||||||
|
*/
|
||||||
|
unsigned int num_coins;
|
||||||
|
} success;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Details if the status is #MHD_HTTP_ACCEPTED.
|
||||||
|
*/
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Payment target that the merchant should use
|
||||||
|
* to check for its KYC status.
|
||||||
|
*/
|
||||||
|
uint64_t payment_target_uuid;
|
||||||
|
} accepted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Details if the status is #MHD_HTTP_CONFLICT.
|
||||||
|
*/
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
/* TODO: returning full details is not implemented */
|
||||||
|
} conflict;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Details if the status is #MHD_HTTP_GONE.
|
||||||
|
*/
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
/* TODO: returning full details is not implemented */
|
||||||
|
} gone;
|
||||||
|
|
||||||
|
} details;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callbacks of this type are used to serve the result of submitting a
|
||||||
|
* batch withdraw request to a exchange.
|
||||||
|
*
|
||||||
|
* @param cls closure
|
||||||
|
* @param wr response details
|
||||||
|
*/
|
||||||
|
typedef void
|
||||||
|
(*TALER_EXCHANGE_BatchWithdrawCallback) (
|
||||||
|
void *cls,
|
||||||
|
const struct TALER_EXCHANGE_BatchWithdrawResponse *wr);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Withdraw multiple coins from the exchange using a /reserves/$RESERVE_PUB/batch-withdraw
|
||||||
|
* request. This API is typically used by a wallet to withdraw many coins from a
|
||||||
|
* reserve.
|
||||||
|
*
|
||||||
|
* Note that to ensure that no money is lost in case of hardware
|
||||||
|
* failures, the caller must have committed (most of) the arguments to
|
||||||
|
* disk before calling, and be ready to repeat the request with the
|
||||||
|
* same arguments in case of failures.
|
||||||
|
*
|
||||||
|
* @param exchange the exchange handle; the exchange must be ready to operate
|
||||||
|
* @param reserve_priv private key of the reserve to withdraw from
|
||||||
|
* @param wcis inputs that determine the planchets
|
||||||
|
* @param wci_length number of entries in @a wcis
|
||||||
|
* @param res_cb the callback to call when the final result for this request is available
|
||||||
|
* @param res_cb_cls closure for @a res_cb
|
||||||
|
* @return NULL
|
||||||
|
* if the inputs are invalid (i.e. denomination key not with this exchange).
|
||||||
|
* In this case, the callback is not called.
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGE_BatchWithdrawHandle *
|
||||||
|
TALER_EXCHANGE_batch_withdraw (
|
||||||
|
struct TALER_EXCHANGE_Handle *exchange,
|
||||||
|
const struct TALER_ReservePrivateKeyP *reserve_priv,
|
||||||
|
const struct TALER_EXCHANGE_WithdrawCoinInput *wcis,
|
||||||
|
unsigned int wci_length,
|
||||||
|
TALER_EXCHANGE_BatchWithdrawCallback res_cb,
|
||||||
|
void *res_cb_cls);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel a batch withdraw status request. This function cannot be used on a
|
||||||
|
* request handle if a response is already served for it.
|
||||||
|
*
|
||||||
|
* @param wh the batch withdraw handle
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
TALER_EXCHANGE_batch_withdraw_cancel (
|
||||||
|
struct TALER_EXCHANGE_BatchWithdrawHandle *wh);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callbacks of this type are used to serve the result of submitting a
|
* Callbacks of this type are used to serve the result of submitting a
|
||||||
* withdraw request to a exchange without the (un)blinding factor.
|
* withdraw request to a exchange without the (un)blinding factor.
|
||||||
@ -2082,6 +2224,74 @@ void
|
|||||||
TALER_EXCHANGE_withdraw2_cancel (struct TALER_EXCHANGE_Withdraw2Handle *wh);
|
TALER_EXCHANGE_withdraw2_cancel (struct TALER_EXCHANGE_Withdraw2Handle *wh);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callbacks of this type are used to serve the result of submitting a batch
|
||||||
|
* withdraw request to a exchange without the (un)blinding factor.
|
||||||
|
*
|
||||||
|
* @param cls closure
|
||||||
|
* @param hr HTTP response data
|
||||||
|
* @param blind_sigs array of blind signatures over the coins, NULL on error
|
||||||
|
* @param blind_sigs_length length of @a blind_sigs
|
||||||
|
*/
|
||||||
|
typedef void
|
||||||
|
(*TALER_EXCHANGE_BatchWithdraw2Callback) (
|
||||||
|
void *cls,
|
||||||
|
const struct TALER_EXCHANGE_HttpResponse *hr,
|
||||||
|
const struct TALER_BlindedDenominationSignature *blind_sigs,
|
||||||
|
unsigned int blind_sigs_length);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A /reserves/$RESERVE_PUB/batch-withdraw Handle, 2nd variant.
|
||||||
|
* This variant does not do the blinding/unblinding and only
|
||||||
|
* fetches the blind signatures on the already blinded planchets.
|
||||||
|
* Used internally by the `struct TALER_EXCHANGE_BatchWithdrawHandle`
|
||||||
|
* implementation as well as for the tipping logic of merchants.
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGE_BatchWithdraw2Handle;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Withdraw a coin from the exchange using a /reserves/$RESERVE_PUB/batch-withdraw
|
||||||
|
* request. This API is typically used by a merchant to withdraw a tip
|
||||||
|
* where the blinding factor is unknown to the merchant.
|
||||||
|
*
|
||||||
|
* Note that to ensure that no money is lost in case of hardware
|
||||||
|
* failures, the caller must have committed (most of) the arguments to
|
||||||
|
* disk before calling, and be ready to repeat the request with the
|
||||||
|
* same arguments in case of failures.
|
||||||
|
*
|
||||||
|
* @param exchange the exchange handle; the exchange must be ready to operate
|
||||||
|
* @param pds array of planchet details of the planchet to withdraw
|
||||||
|
* @param pds_length number of entries in the @a pds array
|
||||||
|
* @param reserve_priv private key of the reserve to withdraw from
|
||||||
|
* @param res_cb the callback to call when the final result for this request is available
|
||||||
|
* @param res_cb_cls closure for @a res_cb
|
||||||
|
* @return NULL
|
||||||
|
* if the inputs are invalid (i.e. denomination key not with this exchange).
|
||||||
|
* In this case, the callback is not called.
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGE_BatchWithdraw2Handle *
|
||||||
|
TALER_EXCHANGE_batch_withdraw2 (
|
||||||
|
struct TALER_EXCHANGE_Handle *exchange,
|
||||||
|
const struct TALER_ReservePrivateKeyP *reserve_priv,
|
||||||
|
const struct TALER_PlanchetDetail *pds,
|
||||||
|
unsigned int pds_length,
|
||||||
|
TALER_EXCHANGE_BatchWithdraw2Callback res_cb,
|
||||||
|
void *res_cb_cls);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel a batch withdraw request. This function cannot be used
|
||||||
|
* on a request handle if a response is already served for it.
|
||||||
|
*
|
||||||
|
* @param wh the withdraw handle
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
TALER_EXCHANGE_batch_withdraw2_cancel (
|
||||||
|
struct TALER_EXCHANGE_BatchWithdraw2Handle *wh);
|
||||||
|
|
||||||
|
|
||||||
/* ********************* /refresh/melt+reveal ***************************** */
|
/* ********************* /refresh/melt+reveal ***************************** */
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@ libtalerexchange_la_LDFLAGS = \
|
|||||||
-no-undefined
|
-no-undefined
|
||||||
libtalerexchange_la_SOURCES = \
|
libtalerexchange_la_SOURCES = \
|
||||||
exchange_api_auditor_add_denomination.c \
|
exchange_api_auditor_add_denomination.c \
|
||||||
|
exchange_api_batch_withdraw.c \
|
||||||
|
exchange_api_batch_withdraw2.c \
|
||||||
exchange_api_curl_defaults.c exchange_api_curl_defaults.h \
|
exchange_api_curl_defaults.c exchange_api_curl_defaults.h \
|
||||||
exchange_api_common.c \
|
exchange_api_common.c \
|
||||||
exchange_api_contracts_get.c \
|
exchange_api_contracts_get.c \
|
||||||
|
433
src/lib/exchange_api_batch_withdraw.c
Normal file
433
src/lib/exchange_api_batch_withdraw.c
Normal file
@ -0,0 +1,433 @@
|
|||||||
|
/*
|
||||||
|
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 General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
TALER; see the file COPYING. If not, see
|
||||||
|
<http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @file lib/exchange_api_batch_withdraw.c
|
||||||
|
* @brief Implementation of /reserves/$RESERVE_PUB/batch-withdraw requests with blinding/unblinding
|
||||||
|
* @author Christian Grothoff
|
||||||
|
*/
|
||||||
|
#include "platform.h"
|
||||||
|
#include <jansson.h>
|
||||||
|
#include <microhttpd.h> /* just for HTTP status codes */
|
||||||
|
#include <gnunet/gnunet_util_lib.h>
|
||||||
|
#include <gnunet/gnunet_json_lib.h>
|
||||||
|
#include <gnunet/gnunet_curl_lib.h>
|
||||||
|
#include "taler_exchange_service.h"
|
||||||
|
#include "taler_json_lib.h"
|
||||||
|
#include "exchange_api_handle.h"
|
||||||
|
#include "taler_signatures.h"
|
||||||
|
#include "exchange_api_curl_defaults.h"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data we keep per coin in the batch.
|
||||||
|
*/
|
||||||
|
struct CoinData
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Denomination key we are withdrawing.
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGE_DenomPublicKey pk;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Master key material for the coin.
|
||||||
|
*/
|
||||||
|
struct TALER_PlanchetMasterSecretP ps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Age commitment for the coin.
|
||||||
|
*/
|
||||||
|
const struct TALER_AgeCommitmentHash *ach;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* blinding secret
|
||||||
|
*/
|
||||||
|
union TALER_DenominationBlindingKeyP bks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private key of the coin we are withdrawing.
|
||||||
|
*/
|
||||||
|
struct TALER_CoinSpendPrivateKeyP priv;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Details of the planchet.
|
||||||
|
*/
|
||||||
|
struct TALER_PlanchetDetail pd;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Values of the @cipher selected
|
||||||
|
*/
|
||||||
|
struct TALER_ExchangeWithdrawValues alg_values;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash of the public key of the coin we are signing.
|
||||||
|
*/
|
||||||
|
struct TALER_CoinPubHashP c_hash;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for the CS R request (only used for TALER_DENOMINATION_CS denominations)
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGE_CsRWithdrawHandle *csrh;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch withdraw this coin is part of.
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGE_BatchWithdrawHandle *wh;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A batch withdraw handle
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGE_BatchWithdrawHandle
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The connection to exchange this request handle will use
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGE_Handle *exchange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle for the actual (internal) batch withdraw operation.
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGE_BatchWithdraw2Handle *wh2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to call with the result.
|
||||||
|
*/
|
||||||
|
TALER_EXCHANGE_BatchWithdrawCallback cb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closure for @a cb.
|
||||||
|
*/
|
||||||
|
void *cb_cls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reserve private key.
|
||||||
|
*/
|
||||||
|
const struct TALER_ReservePrivateKeyP *reserve_priv;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of per-coin data.
|
||||||
|
*/
|
||||||
|
struct CoinData *coins;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Length of the @e coins array.
|
||||||
|
*/
|
||||||
|
unsigned int num_coins;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of CS requests still pending.
|
||||||
|
*/
|
||||||
|
unsigned int cs_pending;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function called when we're done processing the
|
||||||
|
* HTTP /reserves/$RESERVE_PUB/batch-withdraw request.
|
||||||
|
*
|
||||||
|
* @param cls the `struct TALER_EXCHANGE_BatchWithdrawHandle`
|
||||||
|
* @param hr HTTP response data
|
||||||
|
* @param blind_sig blind signature over the coin, NULL on error
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
handle_reserve_batch_withdraw_finished (
|
||||||
|
void *cls,
|
||||||
|
const struct TALER_EXCHANGE_HttpResponse *hr,
|
||||||
|
const struct TALER_BlindedDenominationSignature *blind_sigs,
|
||||||
|
unsigned int blind_sigs_length)
|
||||||
|
{
|
||||||
|
struct TALER_EXCHANGE_BatchWithdrawHandle *wh = cls;
|
||||||
|
struct TALER_EXCHANGE_BatchWithdrawResponse wr = {
|
||||||
|
.hr = *hr
|
||||||
|
};
|
||||||
|
struct TALER_EXCHANGE_PrivateCoinDetails coins[wh->num_coins];
|
||||||
|
|
||||||
|
wh->wh2 = NULL;
|
||||||
|
if (blind_sigs_length != wh->num_coins)
|
||||||
|
{
|
||||||
|
GNUNET_break_op (0);
|
||||||
|
wr.hr.http_status = 0;
|
||||||
|
wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
|
||||||
|
}
|
||||||
|
switch (hr->http_status)
|
||||||
|
{
|
||||||
|
case MHD_HTTP_OK:
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i<wh->num_coins; i++)
|
||||||
|
{
|
||||||
|
struct CoinData *cd = &wh->coins[i];
|
||||||
|
struct TALER_EXCHANGE_PrivateCoinDetails *coin = &coins[i];
|
||||||
|
struct TALER_FreshCoin fc;
|
||||||
|
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
TALER_planchet_to_coin (&cd->pk.key,
|
||||||
|
&blind_sigs[i],
|
||||||
|
&cd->bks,
|
||||||
|
&cd->priv,
|
||||||
|
cd->ach,
|
||||||
|
&cd->c_hash,
|
||||||
|
&cd->alg_values,
|
||||||
|
&fc))
|
||||||
|
{
|
||||||
|
wr.hr.http_status = 0;
|
||||||
|
wr.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
coin->coin_priv = cd->priv;
|
||||||
|
coin->bks = cd->bks;
|
||||||
|
coin->sig = fc.sig;
|
||||||
|
coin->exchange_vals = cd->alg_values;
|
||||||
|
}
|
||||||
|
wr.details.success.coins = coins;
|
||||||
|
wr.details.success.num_coins = wh->num_coins;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MHD_HTTP_ACCEPTED:
|
||||||
|
{
|
||||||
|
struct GNUNET_JSON_Specification spec[] = {
|
||||||
|
GNUNET_JSON_spec_uint64 ("payment_target_uuid",
|
||||||
|
&wr.details.accepted.payment_target_uuid),
|
||||||
|
GNUNET_JSON_spec_end ()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
GNUNET_JSON_parse (hr->reply,
|
||||||
|
spec,
|
||||||
|
NULL, NULL))
|
||||||
|
{
|
||||||
|
GNUNET_break_op (0);
|
||||||
|
wr.hr.http_status = 0;
|
||||||
|
wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
wh->cb (wh->cb_cls,
|
||||||
|
&wr);
|
||||||
|
for (unsigned int i = 0; i<wh->num_coins; i++)
|
||||||
|
TALER_denom_sig_free (&coins[i].sig);
|
||||||
|
TALER_EXCHANGE_batch_withdraw_cancel (wh);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs phase two, the actual withdraw operation.
|
||||||
|
* Started once the preparation for CS-denominations is
|
||||||
|
* done.
|
||||||
|
*
|
||||||
|
* @param[in,out] wh batch withdraw to start phase 2 for
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
phase_two (struct TALER_EXCHANGE_BatchWithdrawHandle *wh)
|
||||||
|
{
|
||||||
|
struct TALER_PlanchetDetail pds[wh->num_coins];
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i<wh->num_coins; i++)
|
||||||
|
{
|
||||||
|
struct CoinData *cd = &wh->coins[i];
|
||||||
|
|
||||||
|
pds[i] = cd->pd;
|
||||||
|
}
|
||||||
|
wh->wh2 = TALER_EXCHANGE_batch_withdraw2 (
|
||||||
|
wh->exchange,
|
||||||
|
wh->reserve_priv,
|
||||||
|
pds,
|
||||||
|
wh->num_coins,
|
||||||
|
&handle_reserve_batch_withdraw_finished,
|
||||||
|
wh);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function called when stage 1 of CS withdraw is finished (request r_pub's)
|
||||||
|
*
|
||||||
|
* @param cls the `struct CoinData *`
|
||||||
|
* @param csrr replies from the /csr-withdraw request
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
withdraw_cs_stage_two_callback (
|
||||||
|
void *cls,
|
||||||
|
const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr)
|
||||||
|
{
|
||||||
|
struct CoinData *cd = cls;
|
||||||
|
struct TALER_EXCHANGE_BatchWithdrawHandle *wh = cd->wh;
|
||||||
|
struct TALER_EXCHANGE_BatchWithdrawResponse wr = {
|
||||||
|
.hr = csrr->hr
|
||||||
|
};
|
||||||
|
|
||||||
|
cd->csrh = NULL;
|
||||||
|
GNUNET_assert (TALER_DENOMINATION_CS == cd->pk.key.cipher);
|
||||||
|
switch (csrr->hr.http_status)
|
||||||
|
{
|
||||||
|
case MHD_HTTP_OK:
|
||||||
|
cd->alg_values = csrr->details.success.alg_values;
|
||||||
|
TALER_planchet_setup_coin_priv (&cd->ps,
|
||||||
|
&cd->alg_values,
|
||||||
|
&cd->priv);
|
||||||
|
TALER_planchet_blinding_secret_create (&cd->ps,
|
||||||
|
&cd->alg_values,
|
||||||
|
&cd->bks);
|
||||||
|
/* This initializes the 2nd half of the
|
||||||
|
wh->pd.blinded_planchet! */
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
TALER_planchet_prepare (&cd->pk.key,
|
||||||
|
&cd->alg_values,
|
||||||
|
&cd->bks,
|
||||||
|
&cd->priv,
|
||||||
|
cd->ach,
|
||||||
|
&cd->c_hash,
|
||||||
|
&cd->pd))
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
TALER_EXCHANGE_batch_withdraw_cancel (wh);
|
||||||
|
}
|
||||||
|
wh->cs_pending--;
|
||||||
|
if (0 == wh->cs_pending)
|
||||||
|
phase_two (wh);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
wh->cb (wh->cb_cls,
|
||||||
|
&wr);
|
||||||
|
TALER_EXCHANGE_batch_withdraw_cancel (wh);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct TALER_EXCHANGE_BatchWithdrawHandle *
|
||||||
|
TALER_EXCHANGE_batch_withdraw (
|
||||||
|
struct TALER_EXCHANGE_Handle *exchange,
|
||||||
|
const struct TALER_ReservePrivateKeyP *reserve_priv,
|
||||||
|
const struct TALER_EXCHANGE_WithdrawCoinInput *wcis,
|
||||||
|
unsigned int wci_length,
|
||||||
|
TALER_EXCHANGE_BatchWithdrawCallback res_cb,
|
||||||
|
void *res_cb_cls)
|
||||||
|
{
|
||||||
|
struct TALER_EXCHANGE_BatchWithdrawHandle *wh;
|
||||||
|
|
||||||
|
wh = GNUNET_new (struct TALER_EXCHANGE_BatchWithdrawHandle);
|
||||||
|
wh->exchange = exchange;
|
||||||
|
wh->cb = res_cb;
|
||||||
|
wh->cb_cls = res_cb_cls;
|
||||||
|
wh->reserve_priv = reserve_priv;
|
||||||
|
wh->num_coins = wci_length;
|
||||||
|
wh->coins = GNUNET_new_array (wh->num_coins,
|
||||||
|
struct CoinData);
|
||||||
|
for (unsigned int i = 0; i<wci_length; i++)
|
||||||
|
{
|
||||||
|
struct CoinData *cd = &wh->coins[i];
|
||||||
|
const struct TALER_EXCHANGE_WithdrawCoinInput *wci = &wcis[i];
|
||||||
|
|
||||||
|
cd->wh = wh;
|
||||||
|
cd->ps = *wci->ps;
|
||||||
|
cd->ach = wci->ach;
|
||||||
|
cd->pk = *wci->pk;
|
||||||
|
TALER_denom_pub_deep_copy (&cd->pk.key,
|
||||||
|
&wci->pk->key);
|
||||||
|
switch (wci->pk->key.cipher)
|
||||||
|
{
|
||||||
|
case TALER_DENOMINATION_RSA:
|
||||||
|
{
|
||||||
|
cd->alg_values.cipher = TALER_DENOMINATION_RSA;
|
||||||
|
TALER_planchet_setup_coin_priv (&cd->ps,
|
||||||
|
&cd->alg_values,
|
||||||
|
&cd->priv);
|
||||||
|
TALER_planchet_blinding_secret_create (&cd->ps,
|
||||||
|
&cd->alg_values,
|
||||||
|
&cd->bks);
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
TALER_planchet_prepare (&cd->pk.key,
|
||||||
|
&cd->alg_values,
|
||||||
|
&cd->bks,
|
||||||
|
&cd->priv,
|
||||||
|
cd->ach,
|
||||||
|
&cd->c_hash,
|
||||||
|
&cd->pd))
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
TALER_EXCHANGE_batch_withdraw_cancel (wh);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TALER_DENOMINATION_CS:
|
||||||
|
{
|
||||||
|
TALER_cs_withdraw_nonce_derive (
|
||||||
|
&cd->ps,
|
||||||
|
&cd->pd.blinded_planchet.details.cs_blinded_planchet.nonce);
|
||||||
|
/* Note that we only initialize the first half
|
||||||
|
of the blinded_planchet here; the other part
|
||||||
|
will be done after the /csr-withdraw request! */
|
||||||
|
cd->pd.blinded_planchet.cipher = TALER_DENOMINATION_CS;
|
||||||
|
cd->csrh = TALER_EXCHANGE_csr_withdraw (
|
||||||
|
exchange,
|
||||||
|
&cd->pk,
|
||||||
|
&cd->pd.blinded_planchet.details.cs_blinded_planchet.nonce,
|
||||||
|
&withdraw_cs_stage_two_callback,
|
||||||
|
cd);
|
||||||
|
if (NULL == cd->csrh)
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
TALER_EXCHANGE_batch_withdraw_cancel (wh);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
wh->cs_pending++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
GNUNET_break (0);
|
||||||
|
TALER_EXCHANGE_batch_withdraw_cancel (wh);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (0 == wh->cs_pending)
|
||||||
|
phase_two (wh);
|
||||||
|
return wh;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
TALER_EXCHANGE_batch_withdraw_cancel (
|
||||||
|
struct TALER_EXCHANGE_BatchWithdrawHandle *wh)
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i<wh->num_coins; i++)
|
||||||
|
{
|
||||||
|
struct CoinData *cd = &wh->coins[i];
|
||||||
|
|
||||||
|
if (NULL != cd->csrh)
|
||||||
|
{
|
||||||
|
TALER_EXCHANGE_csr_withdraw_cancel (cd->csrh);
|
||||||
|
cd->csrh = NULL;
|
||||||
|
}
|
||||||
|
TALER_blinded_planchet_free (&cd->pd.blinded_planchet);
|
||||||
|
TALER_denom_pub_free (&cd->pk.key);
|
||||||
|
}
|
||||||
|
GNUNET_free (wh->coins);
|
||||||
|
if (NULL != wh->wh2)
|
||||||
|
{
|
||||||
|
TALER_EXCHANGE_batch_withdraw2_cancel (wh->wh2);
|
||||||
|
wh->wh2 = NULL;
|
||||||
|
}
|
||||||
|
GNUNET_free (wh);
|
||||||
|
}
|
560
src/lib/exchange_api_batch_withdraw2.c
Normal file
560
src/lib/exchange_api_batch_withdraw2.c
Normal file
@ -0,0 +1,560 @@
|
|||||||
|
/*
|
||||||
|
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 General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
TALER; see the file COPYING. If not, see
|
||||||
|
<http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @file lib/exchange_api_batch_withdraw2.c
|
||||||
|
* @brief Implementation of /reserves/$RESERVE_PUB/batch-withdraw requests without blinding/unblinding
|
||||||
|
* @author Christian Grothoff
|
||||||
|
*/
|
||||||
|
#include "platform.h"
|
||||||
|
#include <jansson.h>
|
||||||
|
#include <microhttpd.h> /* just for HTTP status codes */
|
||||||
|
#include <gnunet/gnunet_util_lib.h>
|
||||||
|
#include <gnunet/gnunet_json_lib.h>
|
||||||
|
#include <gnunet/gnunet_curl_lib.h>
|
||||||
|
#include "taler_exchange_service.h"
|
||||||
|
#include "taler_json_lib.h"
|
||||||
|
#include "exchange_api_handle.h"
|
||||||
|
#include "taler_signatures.h"
|
||||||
|
#include "exchange_api_curl_defaults.h"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A batch withdraw handle
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGE_BatchWithdraw2Handle
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The connection to exchange this request handle will use
|
||||||
|
*/
|
||||||
|
struct TALER_EXCHANGE_Handle *exchange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The url for this request.
|
||||||
|
*/
|
||||||
|
char *url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle for the request.
|
||||||
|
*/
|
||||||
|
struct GNUNET_CURL_Job *job;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to call with the result.
|
||||||
|
*/
|
||||||
|
TALER_EXCHANGE_BatchWithdraw2Callback cb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closure for @a cb.
|
||||||
|
*/
|
||||||
|
void *cb_cls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context for #TEH_curl_easy_post(). Keeps the data that must
|
||||||
|
* persist for Curl to make the upload.
|
||||||
|
*/
|
||||||
|
struct TALER_CURL_PostContext post_ctx;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Total amount requested (value plus withdraw fee).
|
||||||
|
*/
|
||||||
|
struct TALER_Amount requested_amount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public key of the reserve we are withdrawing from.
|
||||||
|
*/
|
||||||
|
struct TALER_ReservePublicKeyP reserve_pub;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of coins expected.
|
||||||
|
*/
|
||||||
|
unsigned int num_coins;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We got a 200 OK response for the /reserves/$RESERVE_PUB/batch-withdraw operation.
|
||||||
|
* Extract the coin's signature and return it to the caller. The signature we
|
||||||
|
* get from the exchange is for the blinded value. Thus, we first must
|
||||||
|
* unblind it and then should verify its validity against our coin's hash.
|
||||||
|
*
|
||||||
|
* If everything checks out, we return the unblinded signature
|
||||||
|
* to the application via the callback.
|
||||||
|
*
|
||||||
|
* @param wh operation handle
|
||||||
|
* @param json reply from the exchange
|
||||||
|
* @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
|
||||||
|
*/
|
||||||
|
static enum GNUNET_GenericReturnValue
|
||||||
|
reserve_batch_withdraw_ok (struct TALER_EXCHANGE_BatchWithdraw2Handle *wh,
|
||||||
|
const json_t *json)
|
||||||
|
{
|
||||||
|
struct TALER_BlindedDenominationSignature blind_sigs[wh->num_coins];
|
||||||
|
const json_t *ja = json_object_get (json,
|
||||||
|
"ev_sigs");
|
||||||
|
const json_t *j;
|
||||||
|
unsigned int index;
|
||||||
|
struct TALER_EXCHANGE_HttpResponse hr = {
|
||||||
|
.reply = json,
|
||||||
|
.http_status = MHD_HTTP_OK
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( (NULL == ja) ||
|
||||||
|
(! json_is_array (ja)) ||
|
||||||
|
(wh->num_coins != json_array_size (ja)) )
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
return GNUNET_SYSERR;
|
||||||
|
}
|
||||||
|
json_array_foreach (ja, index, j)
|
||||||
|
{
|
||||||
|
struct GNUNET_JSON_Specification spec[] = {
|
||||||
|
TALER_JSON_spec_blinded_denom_sig ("ev_sig",
|
||||||
|
&blind_sigs[index]),
|
||||||
|
GNUNET_JSON_spec_end ()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
GNUNET_JSON_parse (json,
|
||||||
|
spec,
|
||||||
|
NULL, NULL))
|
||||||
|
{
|
||||||
|
GNUNET_break_op (0);
|
||||||
|
for (unsigned int i = 0; i<index; i++)
|
||||||
|
TALER_blinded_denom_sig_free (&blind_sigs[i]);
|
||||||
|
return GNUNET_SYSERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* signature is valid, return it to the application */
|
||||||
|
wh->cb (wh->cb_cls,
|
||||||
|
&hr,
|
||||||
|
blind_sigs,
|
||||||
|
wh->num_coins);
|
||||||
|
/* make sure callback isn't called again after return */
|
||||||
|
wh->cb = NULL;
|
||||||
|
for (unsigned int i = 0; i<wh->num_coins; i++)
|
||||||
|
TALER_blinded_denom_sig_free (&blind_sigs[i]);
|
||||||
|
|
||||||
|
return GNUNET_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We got a 409 CONFLICT response for the /reserves/$RESERVE_PUB/batch-withdraw operation.
|
||||||
|
* Check the signatures on the batch withdraw transactions in the provided
|
||||||
|
* history and that the balances add up. We don't do anything directly
|
||||||
|
* with the information, as the JSON will be returned to the application.
|
||||||
|
* However, our job is ensuring that the exchange followed the protocol, and
|
||||||
|
* this in particular means checking all of the signatures in the history.
|
||||||
|
*
|
||||||
|
* @param wh operation handle
|
||||||
|
* @param json reply from the exchange
|
||||||
|
* @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
|
||||||
|
*/
|
||||||
|
static enum GNUNET_GenericReturnValue
|
||||||
|
reserve_batch_withdraw_payment_required (
|
||||||
|
struct TALER_EXCHANGE_BatchWithdraw2Handle *wh,
|
||||||
|
const json_t *json)
|
||||||
|
{
|
||||||
|
struct TALER_Amount balance;
|
||||||
|
struct TALER_Amount total_in_from_history;
|
||||||
|
struct TALER_Amount total_out_from_history;
|
||||||
|
json_t *history;
|
||||||
|
size_t len;
|
||||||
|
struct GNUNET_JSON_Specification spec[] = {
|
||||||
|
TALER_JSON_spec_amount_any ("balance",
|
||||||
|
&balance),
|
||||||
|
GNUNET_JSON_spec_end ()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
GNUNET_JSON_parse (json,
|
||||||
|
spec,
|
||||||
|
NULL, NULL))
|
||||||
|
{
|
||||||
|
GNUNET_break_op (0);
|
||||||
|
return GNUNET_SYSERR;
|
||||||
|
}
|
||||||
|
history = json_object_get (json,
|
||||||
|
"history");
|
||||||
|
if (NULL == history)
|
||||||
|
{
|
||||||
|
GNUNET_break_op (0);
|
||||||
|
return GNUNET_SYSERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* go over transaction history and compute
|
||||||
|
total incoming and outgoing amounts */
|
||||||
|
len = json_array_size (history);
|
||||||
|
{
|
||||||
|
struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
|
||||||
|
|
||||||
|
/* Use heap allocation as "len" may be very big and thus this may
|
||||||
|
not fit on the stack. Use "GNUNET_malloc_large" as a malicious
|
||||||
|
exchange may theoretically try to crash us by giving a history
|
||||||
|
that does not fit into our memory. */
|
||||||
|
rhistory = GNUNET_malloc_large (
|
||||||
|
sizeof (struct TALER_EXCHANGE_ReserveHistoryEntry)
|
||||||
|
* len);
|
||||||
|
if (NULL == rhistory)
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
return GNUNET_SYSERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
TALER_EXCHANGE_parse_reserve_history (wh->exchange,
|
||||||
|
history,
|
||||||
|
&wh->reserve_pub,
|
||||||
|
balance.currency,
|
||||||
|
&total_in_from_history,
|
||||||
|
&total_out_from_history,
|
||||||
|
len,
|
||||||
|
rhistory))
|
||||||
|
{
|
||||||
|
GNUNET_break_op (0);
|
||||||
|
TALER_EXCHANGE_free_reserve_history (rhistory,
|
||||||
|
len);
|
||||||
|
return GNUNET_SYSERR;
|
||||||
|
}
|
||||||
|
TALER_EXCHANGE_free_reserve_history (rhistory,
|
||||||
|
len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check that funds were really insufficient */
|
||||||
|
if (0 >= TALER_amount_cmp (&wh->requested_amount,
|
||||||
|
&balance))
|
||||||
|
{
|
||||||
|
/* Requested amount is smaller or equal to reported balance,
|
||||||
|
so this should not have failed. */
|
||||||
|
GNUNET_break_op (0);
|
||||||
|
return GNUNET_SYSERR;
|
||||||
|
}
|
||||||
|
return GNUNET_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function called when we're done processing the
|
||||||
|
* HTTP /reserves/$RESERVE_PUB/batch-withdraw request.
|
||||||
|
*
|
||||||
|
* @param cls the `struct TALER_EXCHANGE_BatchWithdraw2Handle`
|
||||||
|
* @param response_code HTTP response code, 0 on error
|
||||||
|
* @param response parsed JSON result, NULL on error
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
handle_reserve_batch_withdraw_finished (void *cls,
|
||||||
|
long response_code,
|
||||||
|
const void *response)
|
||||||
|
{
|
||||||
|
struct TALER_EXCHANGE_BatchWithdraw2Handle *wh = cls;
|
||||||
|
const json_t *j = response;
|
||||||
|
struct TALER_EXCHANGE_HttpResponse hr = {
|
||||||
|
.reply = j,
|
||||||
|
.http_status = (unsigned int) response_code
|
||||||
|
};
|
||||||
|
|
||||||
|
wh->job = NULL;
|
||||||
|
switch (response_code)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
|
||||||
|
break;
|
||||||
|
case MHD_HTTP_OK:
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
reserve_batch_withdraw_ok (wh,
|
||||||
|
j))
|
||||||
|
{
|
||||||
|
GNUNET_break_op (0);
|
||||||
|
hr.http_status = 0;
|
||||||
|
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
GNUNET_assert (NULL == wh->cb);
|
||||||
|
TALER_EXCHANGE_batch_withdraw2_cancel (wh);
|
||||||
|
return;
|
||||||
|
case MHD_HTTP_ACCEPTED:
|
||||||
|
/* only validate reply is well-formed */
|
||||||
|
{
|
||||||
|
uint64_t ptu;
|
||||||
|
struct GNUNET_JSON_Specification spec[] = {
|
||||||
|
GNUNET_JSON_spec_uint64 ("payment_target_uuid",
|
||||||
|
&ptu),
|
||||||
|
GNUNET_JSON_spec_end ()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
GNUNET_JSON_parse (j,
|
||||||
|
spec,
|
||||||
|
NULL, NULL))
|
||||||
|
{
|
||||||
|
GNUNET_break_op (0);
|
||||||
|
hr.http_status = 0;
|
||||||
|
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MHD_HTTP_BAD_REQUEST:
|
||||||
|
/* This should never happen, either us or the exchange is buggy
|
||||||
|
(or API version conflict); just pass JSON reply to the application */
|
||||||
|
hr.ec = TALER_JSON_get_error_code (j);
|
||||||
|
hr.hint = TALER_JSON_get_error_hint (j);
|
||||||
|
break;
|
||||||
|
case MHD_HTTP_FORBIDDEN:
|
||||||
|
GNUNET_break_op (0);
|
||||||
|
/* Nothing really to verify, exchange says one of the signatures is
|
||||||
|
invalid; as we checked them, this should never happen, we
|
||||||
|
should pass the JSON reply to the application */
|
||||||
|
hr.ec = TALER_JSON_get_error_code (j);
|
||||||
|
hr.hint = TALER_JSON_get_error_hint (j);
|
||||||
|
break;
|
||||||
|
case MHD_HTTP_NOT_FOUND:
|
||||||
|
/* Nothing really to verify, the exchange basically just says
|
||||||
|
that it doesn't know this reserve. Can happen if we
|
||||||
|
query before the wire transfer went through.
|
||||||
|
We should simply pass the JSON reply to the application. */
|
||||||
|
hr.ec = TALER_JSON_get_error_code (j);
|
||||||
|
hr.hint = TALER_JSON_get_error_hint (j);
|
||||||
|
break;
|
||||||
|
case MHD_HTTP_CONFLICT:
|
||||||
|
/* The exchange says that the reserve has insufficient funds;
|
||||||
|
check the signatures in the history... */
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
reserve_batch_withdraw_payment_required (wh,
|
||||||
|
j))
|
||||||
|
{
|
||||||
|
GNUNET_break_op (0);
|
||||||
|
hr.http_status = 0;
|
||||||
|
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hr.ec = TALER_JSON_get_error_code (j);
|
||||||
|
hr.hint = TALER_JSON_get_error_hint (j);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MHD_HTTP_GONE:
|
||||||
|
/* could happen if denomination was revoked */
|
||||||
|
/* Note: one might want to check /keys for revocation
|
||||||
|
signature here, alas tricky in case our /keys
|
||||||
|
is outdated => left to clients */
|
||||||
|
hr.ec = TALER_JSON_get_error_code (j);
|
||||||
|
hr.hint = TALER_JSON_get_error_hint (j);
|
||||||
|
break;
|
||||||
|
case MHD_HTTP_INTERNAL_SERVER_ERROR:
|
||||||
|
/* Server had an internal issue; we should retry, but this API
|
||||||
|
leaves this to the application */
|
||||||
|
hr.ec = TALER_JSON_get_error_code (j);
|
||||||
|
hr.hint = TALER_JSON_get_error_hint (j);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* unexpected response code */
|
||||||
|
GNUNET_break_op (0);
|
||||||
|
hr.ec = TALER_JSON_get_error_code (j);
|
||||||
|
hr.hint = TALER_JSON_get_error_hint (j);
|
||||||
|
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
|
||||||
|
"Unexpected response code %u/%d for exchange batch withdraw\n",
|
||||||
|
(unsigned int) response_code,
|
||||||
|
(int) hr.ec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (NULL != wh->cb)
|
||||||
|
{
|
||||||
|
wh->cb (wh->cb_cls,
|
||||||
|
&hr,
|
||||||
|
NULL,
|
||||||
|
0);
|
||||||
|
wh->cb = NULL;
|
||||||
|
}
|
||||||
|
TALER_EXCHANGE_batch_withdraw2_cancel (wh);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct TALER_EXCHANGE_BatchWithdraw2Handle *
|
||||||
|
TALER_EXCHANGE_batch_withdraw2 (
|
||||||
|
struct TALER_EXCHANGE_Handle *exchange,
|
||||||
|
const struct TALER_ReservePrivateKeyP *reserve_priv,
|
||||||
|
const struct TALER_PlanchetDetail *pds,
|
||||||
|
unsigned int pds_length,
|
||||||
|
TALER_EXCHANGE_BatchWithdraw2Callback res_cb,
|
||||||
|
void *res_cb_cls)
|
||||||
|
{
|
||||||
|
struct TALER_EXCHANGE_BatchWithdraw2Handle *wh;
|
||||||
|
const struct TALER_EXCHANGE_Keys *keys;
|
||||||
|
const struct TALER_EXCHANGE_DenomPublicKey *dk;
|
||||||
|
struct TALER_ReserveSignatureP reserve_sig;
|
||||||
|
char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
|
||||||
|
struct TALER_BlindedCoinHashP bch;
|
||||||
|
json_t *jc;
|
||||||
|
|
||||||
|
keys = TALER_EXCHANGE_get_keys (exchange);
|
||||||
|
if (NULL == keys)
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
wh = GNUNET_new (struct TALER_EXCHANGE_BatchWithdraw2Handle);
|
||||||
|
wh->exchange = exchange;
|
||||||
|
wh->cb = res_cb;
|
||||||
|
wh->cb_cls = res_cb_cls;
|
||||||
|
wh->num_coins = pds_length;
|
||||||
|
TALER_amount_set_zero (keys->currency,
|
||||||
|
&wh->requested_amount);
|
||||||
|
GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
|
||||||
|
&wh->reserve_pub.eddsa_pub);
|
||||||
|
{
|
||||||
|
char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
|
||||||
|
char *end;
|
||||||
|
|
||||||
|
end = GNUNET_STRINGS_data_to_string (
|
||||||
|
&wh->reserve_pub,
|
||||||
|
sizeof (struct TALER_ReservePublicKeyP),
|
||||||
|
pub_str,
|
||||||
|
sizeof (pub_str));
|
||||||
|
*end = '\0';
|
||||||
|
GNUNET_snprintf (arg_str,
|
||||||
|
sizeof (arg_str),
|
||||||
|
"/reserves/%s/batch-withdraw",
|
||||||
|
pub_str);
|
||||||
|
}
|
||||||
|
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
|
||||||
|
"Attempting to batch-withdraw from reserve %s\n",
|
||||||
|
TALER_B2S (&wh->reserve_pub));
|
||||||
|
wh->url = TEAH_path_to_url (exchange,
|
||||||
|
arg_str);
|
||||||
|
if (NULL == wh->url)
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
TALER_EXCHANGE_batch_withdraw2_cancel (wh);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
jc = json_array ();
|
||||||
|
GNUNET_assert (NULL != jc);
|
||||||
|
for (unsigned int i = 0; i<pds_length; i++)
|
||||||
|
{
|
||||||
|
const struct TALER_PlanchetDetail *pd = &pds[i];
|
||||||
|
struct TALER_Amount coin_total;
|
||||||
|
json_t *withdraw_obj;
|
||||||
|
|
||||||
|
dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
|
||||||
|
&pd->denom_pub_hash);
|
||||||
|
if (NULL == dk)
|
||||||
|
{
|
||||||
|
TALER_EXCHANGE_batch_withdraw2_cancel (wh);
|
||||||
|
json_decref (jc);
|
||||||
|
GNUNET_break (0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
/* Compute how much we expected to charge to the reserve */
|
||||||
|
if (0 >
|
||||||
|
TALER_amount_add (&coin_total,
|
||||||
|
&dk->fees.withdraw,
|
||||||
|
&dk->value))
|
||||||
|
{
|
||||||
|
/* Overflow here? Very strange, our CPU must be fried... */
|
||||||
|
GNUNET_break (0);
|
||||||
|
TALER_EXCHANGE_batch_withdraw2_cancel (wh);
|
||||||
|
json_decref (jc);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (0 >
|
||||||
|
TALER_amount_add (&wh->requested_amount,
|
||||||
|
&wh->requested_amount,
|
||||||
|
&coin_total))
|
||||||
|
{
|
||||||
|
/* Overflow here? Very strange, our CPU must be fried... */
|
||||||
|
GNUNET_break (0);
|
||||||
|
TALER_EXCHANGE_batch_withdraw2_cancel (wh);
|
||||||
|
json_decref (jc);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (GNUNET_OK !=
|
||||||
|
TALER_coin_ev_hash (&pd->blinded_planchet,
|
||||||
|
&pd->denom_pub_hash,
|
||||||
|
&bch))
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
TALER_EXCHANGE_batch_withdraw2_cancel (wh);
|
||||||
|
json_decref (jc);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
TALER_wallet_withdraw_sign (&pd->denom_pub_hash,
|
||||||
|
&coin_total,
|
||||||
|
&bch,
|
||||||
|
reserve_priv,
|
||||||
|
&reserve_sig);
|
||||||
|
withdraw_obj = GNUNET_JSON_PACK (
|
||||||
|
GNUNET_JSON_pack_data_auto ("denom_pub_hash",
|
||||||
|
&pd->denom_pub_hash),
|
||||||
|
TALER_JSON_pack_blinded_planchet ("coin_ev",
|
||||||
|
&pd->blinded_planchet),
|
||||||
|
GNUNET_JSON_pack_data_auto ("reserve_sig",
|
||||||
|
&reserve_sig));
|
||||||
|
GNUNET_assert (NULL != withdraw_obj);
|
||||||
|
GNUNET_assert (0 ==
|
||||||
|
json_array_append_new (jc,
|
||||||
|
withdraw_obj));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CURL *eh;
|
||||||
|
struct GNUNET_CURL_Context *ctx;
|
||||||
|
json_t *req;
|
||||||
|
|
||||||
|
req = GNUNET_JSON_PACK (
|
||||||
|
GNUNET_JSON_pack_array_steal ("planchets",
|
||||||
|
jc));
|
||||||
|
ctx = TEAH_handle_to_context (exchange);
|
||||||
|
eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
|
||||||
|
if ( (NULL == eh) ||
|
||||||
|
(GNUNET_OK !=
|
||||||
|
TALER_curl_easy_post (&wh->post_ctx,
|
||||||
|
eh,
|
||||||
|
req)) )
|
||||||
|
{
|
||||||
|
GNUNET_break (0);
|
||||||
|
if (NULL != eh)
|
||||||
|
curl_easy_cleanup (eh);
|
||||||
|
json_decref (req);
|
||||||
|
TALER_EXCHANGE_batch_withdraw2_cancel (wh);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
json_decref (req);
|
||||||
|
wh->job = GNUNET_CURL_job_add2 (ctx,
|
||||||
|
eh,
|
||||||
|
wh->post_ctx.headers,
|
||||||
|
&handle_reserve_batch_withdraw_finished,
|
||||||
|
wh);
|
||||||
|
}
|
||||||
|
return wh;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
TALER_EXCHANGE_batch_withdraw2_cancel (
|
||||||
|
struct TALER_EXCHANGE_BatchWithdraw2Handle *wh)
|
||||||
|
{
|
||||||
|
if (NULL != wh->job)
|
||||||
|
{
|
||||||
|
GNUNET_CURL_job_cancel (wh->job);
|
||||||
|
wh->job = NULL;
|
||||||
|
}
|
||||||
|
GNUNET_free (wh->url);
|
||||||
|
TALER_curl_easy_post_finished (&wh->post_ctx);
|
||||||
|
GNUNET_free (wh);
|
||||||
|
}
|
@ -248,10 +248,8 @@ withdraw_cs_stage_two_callback (
|
|||||||
struct TALER_EXCHANGE_WithdrawHandle *
|
struct TALER_EXCHANGE_WithdrawHandle *
|
||||||
TALER_EXCHANGE_withdraw (
|
TALER_EXCHANGE_withdraw (
|
||||||
struct TALER_EXCHANGE_Handle *exchange,
|
struct TALER_EXCHANGE_Handle *exchange,
|
||||||
const struct TALER_EXCHANGE_DenomPublicKey *pk,
|
|
||||||
const struct TALER_ReservePrivateKeyP *reserve_priv,
|
const struct TALER_ReservePrivateKeyP *reserve_priv,
|
||||||
const struct TALER_PlanchetMasterSecretP *ps,
|
const struct TALER_EXCHANGE_WithdrawCoinInput *wci,
|
||||||
const struct TALER_AgeCommitmentHash *ach,
|
|
||||||
TALER_EXCHANGE_WithdrawCallback res_cb,
|
TALER_EXCHANGE_WithdrawCallback res_cb,
|
||||||
void *res_cb_cls)
|
void *res_cb_cls)
|
||||||
{
|
{
|
||||||
@ -262,25 +260,25 @@ TALER_EXCHANGE_withdraw (
|
|||||||
wh->cb = res_cb;
|
wh->cb = res_cb;
|
||||||
wh->cb_cls = res_cb_cls;
|
wh->cb_cls = res_cb_cls;
|
||||||
wh->reserve_priv = reserve_priv;
|
wh->reserve_priv = reserve_priv;
|
||||||
wh->ps = *ps;
|
wh->ps = *wci->ps;
|
||||||
wh->ach = ach;
|
wh->ach = wci->ach;
|
||||||
wh->pk = *pk;
|
wh->pk = *wci->pk;
|
||||||
TALER_denom_pub_deep_copy (&wh->pk.key,
|
TALER_denom_pub_deep_copy (&wh->pk.key,
|
||||||
&pk->key);
|
&wci->pk->key);
|
||||||
|
|
||||||
switch (pk->key.cipher)
|
switch (wci->pk->key.cipher)
|
||||||
{
|
{
|
||||||
case TALER_DENOMINATION_RSA:
|
case TALER_DENOMINATION_RSA:
|
||||||
{
|
{
|
||||||
wh->alg_values.cipher = TALER_DENOMINATION_RSA;
|
wh->alg_values.cipher = TALER_DENOMINATION_RSA;
|
||||||
TALER_planchet_setup_coin_priv (ps,
|
TALER_planchet_setup_coin_priv (&wh->ps,
|
||||||
&wh->alg_values,
|
&wh->alg_values,
|
||||||
&wh->priv);
|
&wh->priv);
|
||||||
TALER_planchet_blinding_secret_create (ps,
|
TALER_planchet_blinding_secret_create (&wh->ps,
|
||||||
&wh->alg_values,
|
&wh->alg_values,
|
||||||
&wh->bks);
|
&wh->bks);
|
||||||
if (GNUNET_OK !=
|
if (GNUNET_OK !=
|
||||||
TALER_planchet_prepare (&pk->key,
|
TALER_planchet_prepare (&wh->pk.key,
|
||||||
&wh->alg_values,
|
&wh->alg_values,
|
||||||
&wh->bks,
|
&wh->bks,
|
||||||
&wh->priv,
|
&wh->priv,
|
||||||
@ -302,7 +300,7 @@ TALER_EXCHANGE_withdraw (
|
|||||||
case TALER_DENOMINATION_CS:
|
case TALER_DENOMINATION_CS:
|
||||||
{
|
{
|
||||||
TALER_cs_withdraw_nonce_derive (
|
TALER_cs_withdraw_nonce_derive (
|
||||||
ps,
|
&wh->ps,
|
||||||
&wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce);
|
&wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce);
|
||||||
/* Note that we only initialize the first half
|
/* Note that we only initialize the first half
|
||||||
of the blinded_planchet here; the other part
|
of the blinded_planchet here; the other part
|
||||||
@ -310,7 +308,7 @@ TALER_EXCHANGE_withdraw (
|
|||||||
wh->pd.blinded_planchet.cipher = TALER_DENOMINATION_CS;
|
wh->pd.blinded_planchet.cipher = TALER_DENOMINATION_CS;
|
||||||
wh->csrh = TALER_EXCHANGE_csr_withdraw (
|
wh->csrh = TALER_EXCHANGE_csr_withdraw (
|
||||||
exchange,
|
exchange,
|
||||||
pk,
|
&wh->pk,
|
||||||
&wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce,
|
&wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce,
|
||||||
&withdraw_cs_stage_two_callback,
|
&withdraw_cs_stage_two_callback,
|
||||||
wh);
|
wh);
|
||||||
|
@ -433,13 +433,18 @@ withdraw_run (void *cls,
|
|||||||
&ws->amount,
|
&ws->amount,
|
||||||
&ws->pk->fees.withdraw));
|
&ws->pk->fees.withdraw));
|
||||||
ws->reserve_history.details.withdraw.fee = ws->pk->fees.withdraw;
|
ws->reserve_history.details.withdraw.fee = ws->pk->fees.withdraw;
|
||||||
|
{
|
||||||
|
struct TALER_EXCHANGE_WithdrawCoinInput wci = {
|
||||||
|
.pk = ws->pk,
|
||||||
|
.ps = &ws->ps,
|
||||||
|
.ach = ws->h_age_commitment
|
||||||
|
};
|
||||||
ws->wsh = TALER_EXCHANGE_withdraw (is->exchange,
|
ws->wsh = TALER_EXCHANGE_withdraw (is->exchange,
|
||||||
ws->pk,
|
|
||||||
rp,
|
rp,
|
||||||
&ws->ps,
|
&wci,
|
||||||
ws->h_age_commitment,
|
|
||||||
&reserve_withdraw_cb,
|
&reserve_withdraw_cb,
|
||||||
ws);
|
ws);
|
||||||
|
}
|
||||||
if (NULL == ws->wsh)
|
if (NULL == ws->wsh)
|
||||||
{
|
{
|
||||||
GNUNET_break (0);
|
GNUNET_break (0);
|
||||||
|
Loading…
Reference in New Issue
Block a user