commit 9dc093b153e95fc6050081afbbcb9ec57f61d04b
parent 44740603715c03d7b5af1bec3fe3f404525b7525
Author: Özgür Kesim <oec@codeblau.de>
Date: Mon, 14 Sep 2026 21:03:22 +0200
lib: batch recoup client API and test commands
Rewrite TALER_EXCHANGE_post_recoup_withdraw_create()
and TALER_EXCHANGE_post_recoup_refresh_create()
for the vRECOUP protocol (#9828):
The caller passes the operation (reserve_pub/planchets_h or
old_coin_pub/rc), its blinding seed and one TALER_EXCHANGE_RecoupCoin
per coin the exchange signed; coins with a denomination signature are
disclosed and recouped, the others contribute the hash of their blinded
envelope, which the library recomputes from the planchet secrets. The
response parser verifies the batch confirmation signature and that the
recouped coins and amounts match the request.
The coin and reserve history parsers accept the new fields
(coin_blinding_secret, planchets_h, rc, coin_index) and the
recoup-refresh entry is now verified with the recoup-refresh purpose.
The recoup test commands drive the new API from a withdraw command,
or a melt and reveal command pair; batch variants recoup several coins
of one operation in one request. The melt command exposes its refresh
commitment and blinding seed as traits.
Diffstat:
15 files changed, 1687 insertions(+), 869 deletions(-)
diff --git a/src/include/taler/exchange/get-coins-COIN_PUB-history.h b/src/include/taler/exchange/get-coins-COIN_PUB-history.h
@@ -169,6 +169,16 @@ struct TALER_EXCHANGE_CoinHistoryEntry
struct TALER_ExchangePublicKeyP exchange_pub;
struct TALER_ExchangeSignatureP exchange_sig;
struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
+ * Commitment of the withdraw operation the coin originated from.
+ */
+ struct TALER_HashBlindedPlanchetsP planchets_h;
+
+ /**
+ * Index of the coin in the batch signed in that operation.
+ */
+ uint32_t coin_index;
} recoup;
struct
@@ -179,6 +189,16 @@ struct TALER_EXCHANGE_CoinHistoryEntry
struct TALER_ExchangePublicKeyP exchange_pub;
struct TALER_ExchangeSignatureP exchange_sig;
struct TALER_CoinSpendSignatureP coin_sig;
+
+ /**
+ * Commitment of the refresh operation the coin originated from.
+ */
+ struct TALER_RefreshCommitmentP rc;
+
+ /**
+ * Index of the coin in the batch signed in that operation.
+ */
+ uint32_t coin_index;
} recoup_refresh;
struct
@@ -187,6 +207,12 @@ struct TALER_EXCHANGE_CoinHistoryEntry
struct TALER_ExchangeSignatureP exchange_sig;
struct TALER_CoinSpendPublicKeyP new_coin_pub;
struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Commitment of the refresh operation of this coin that the
+ * recouped coin originated from.
+ */
+ struct TALER_RefreshCommitmentP rc;
} old_coin_recoup;
struct
diff --git a/src/include/taler/exchange/get-reserves-RESERVE_PUB-history.h b/src/include/taler/exchange/get-reserves-RESERVE_PUB-history.h
@@ -205,6 +205,11 @@ struct TALER_EXCHANGE_ReserveHistoryEntry
*/
struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Commitment of the withdraw operation the coin originated from.
+ */
+ struct TALER_HashBlindedPlanchetsP planchets_h;
} recoup_details;
/**
diff --git a/src/include/taler/exchange/post-recoup-refresh.h b/src/include/taler/exchange/post-recoup-refresh.h
@@ -12,42 +12,42 @@
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
+*/
/**
* @file include/taler/exchange/post-recoup-refresh.h
- * @brief C interface for POST /coins/$COIN_PUB/recoup-refresh
+ * @brief C interface for the /recoup-refresh request
* @author Christian Grothoff
+ * @author Özgür Kesim
*/
#ifndef _TALER_EXCHANGE__POST_RECOUP_REFRESH_H
#define _TALER_EXCHANGE__POST_RECOUP_REFRESH_H
#include <taler/exchange/common.h>
+#include <taler/exchange/post-recoup-withdraw.h>
+
/**
- * Handle for an operation to POST /coins/$COIN_PUB/recoup-refresh.
+ * @brief Handle for a /recoup-refresh request.
*/
struct TALER_EXCHANGE_PostRecoupRefreshHandle;
/**
- * Set up POST /coins/$COIN_PUB/recoup-refresh operation.
- * Note that you must explicitly start the operation after setup.
- *
- * Ask the exchange to pay back a coin due to the exchange triggering
- * the emergency recoup protocol for a given denomination. The value
- * of the coin will be refunded to the original coin that the
- * revoked coin was refreshed from. The original coin is then
- * considered a zombie.
+ * Prepare a request to recoup coins of revoked denominations that
+ * originated from one refresh operation to the old coin they were
+ * refreshed from. The request must then be started with
+ * #TALER_EXCHANGE_post_recoup_refresh_start().
*
* @param ctx curl context
* @param url exchange base URL
* @param keys exchange keys
- * @param pk kind of coin to pay back
- * @param denom_sig signature over the coin by the exchange using @a pk
- * @param exchange_vals contribution from the exchange on the withdraw
- * @param rms the public seed of the refreshing operation
- * @param ps coin-specific secrets derived for this coin during the refreshing operation
- * @param idx index of the fresh coin in the refresh operation that is now being recouped
+ * @param old_coin_pub old coin that was melted
+ * @param rc commitment of the refresh operation
+ * @param blinding_seed blinding seed used in the melt operation,
+ * NULL if no coin has a CS denomination
+ * @param num_coins number of coins the exchange signed in the operation
+ * @param coins the coins, in the order of the operation; at least
+ * one must have a denomination signature
* @return handle to operation, NULL if the inputs are invalid
*/
struct TALER_EXCHANGE_PostRecoupRefreshHandle *
@@ -55,16 +55,15 @@ TALER_EXCHANGE_post_recoup_refresh_create (
struct GNUNET_CURL_Context *ctx,
const char *url,
struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_EXCHANGE_DenomPublicKey *pk,
- const struct TALER_DenominationSignature *denom_sig,
- const struct TALER_ExchangeBlindingValues *exchange_vals,
- const struct TALER_PublicRefreshMasterSeedP *rms,
- const struct TALER_PlanchetMasterSecretP *ps,
- unsigned int idx);
+ const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ const struct TALER_RefreshCommitmentP *rc,
+ const struct TALER_BlindingMasterSeedP *blinding_seed,
+ size_t num_coins,
+ const struct TALER_EXCHANGE_RecoupCoin coins[static num_coins]);
/**
- * Response from a POST /coins/$COIN_PUB/recoup-refresh request.
+ * Response from a /recoup-refresh request.
*/
struct TALER_EXCHANGE_PostRecoupRefreshResponse
{
@@ -74,40 +73,74 @@ struct TALER_EXCHANGE_PostRecoupRefreshResponse
struct TALER_EXCHANGE_HttpResponse hr;
/**
- * Response details depending on the HTTP status.
+ * Details depending on @e hr.http_status.
*/
union
{
/**
- * Details if HTTP status is #MHD_HTTP_OK.
+ * Details on #MHD_HTTP_OK. The batch confirmation
+ * signature was verified.
*/
struct
{
/**
- * public key of the dirty coin that was credited
+ * Old coin that was credited.
*/
struct TALER_CoinSpendPublicKeyP old_coin_pub;
+ /**
+ * Commitment of the refresh operation.
+ */
+ struct TALER_RefreshCommitmentP rc;
+
+ /**
+ * When the exchange accepted the recoup.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Total amount credited to the old coin.
+ */
+ struct TALER_Amount total_amount;
+
+ /**
+ * Number of entries in @e recoups, equal to the number of
+ * disclosed coins in the request.
+ */
+ size_t num_recoups;
+
+ /**
+ * The recouped coins and their amounts, in request order.
+ */
+ const struct TALER_RecoupedCoin *recoups;
+
+ /**
+ * Signature over `TALER_RecoupRefreshBatchConfirmationPS`.
+ */
+ struct TALER_ExchangeSignatureP exchange_sig;
+
+ /**
+ * Public key used to create @e exchange_sig.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
} ok;
} details;
-
};
#ifndef TALER_EXCHANGE_POST_RECOUP_REFRESH_RESULT_CLOSURE
/**
- * Type of the closure used by
- * the #TALER_EXCHANGE_PostRecoupRefreshCallback.
+ * Type of the closure for #TALER_EXCHANGE_PostRecoupRefreshCallback.
*/
#define TALER_EXCHANGE_POST_RECOUP_REFRESH_RESULT_CLOSURE void
#endif /* TALER_EXCHANGE_POST_RECOUP_REFRESH_RESULT_CLOSURE */
+
/**
- * Type of the function that receives the result of a
- * POST /coins/$COIN_PUB/recoup-refresh request.
+ * Callback for a /recoup-refresh request.
*
* @param cls closure
- * @param result result returned by the HTTP server
+ * @param result the response
*/
typedef void
(*TALER_EXCHANGE_PostRecoupRefreshCallback)(
@@ -116,12 +149,12 @@ typedef void
/**
- * Start POST /coins/$COIN_PUB/recoup-refresh operation.
+ * Start the /recoup-refresh request.
*
- * @param[in,out] prrh operation to start
- * @param cb function to call with the exchange's result
+ * @param prrh the prepared request
+ * @param cb function to call with the result
* @param cb_cls closure for @a cb
- * @return status code, #TALER_EC_NONE on success
+ * @return #TALER_EC_NONE on success
*/
enum TALER_ErrorCode
TALER_EXCHANGE_post_recoup_refresh_start (
@@ -131,16 +164,13 @@ TALER_EXCHANGE_post_recoup_refresh_start (
/**
- * Cancel POST /coins/$COIN_PUB/recoup-refresh operation. This function must
- * not be called by clients after the
- * TALER_EXCHANGE_PostRecoupRefreshCallback has been invoked (as in those
- * cases it'll be called internally by the implementation already).
+ * Cancel the /recoup-refresh request; must not be called
+ * after the callback was invoked.
*
- * @param[in] prrh operation to cancel
+ * @param prrh the request to cancel
*/
void
TALER_EXCHANGE_post_recoup_refresh_cancel (
struct TALER_EXCHANGE_PostRecoupRefreshHandle *prrh);
-
#endif /* _TALER_EXCHANGE__POST_RECOUP_REFRESH_H */
diff --git a/src/include/taler/exchange/post-recoup-withdraw.h b/src/include/taler/exchange/post-recoup-withdraw.h
@@ -12,41 +12,80 @@
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
+*/
/**
* @file include/taler/exchange/post-recoup-withdraw.h
- * @brief C interface for POST /coins/$COIN_PUB/recoup
+ * @brief C interface for the /recoup-withdraw request
* @author Christian Grothoff
+ * @author Özgür Kesim
*/
#ifndef _TALER_EXCHANGE__POST_RECOUP_WITHDRAW_H
#define _TALER_EXCHANGE__POST_RECOUP_WITHDRAW_H
#include <taler/exchange/common.h>
+
/**
- * Handle for an operation to POST /coins/$COIN_PUB/recoup.
+ * One coin of the batch the exchange signed in the withdraw or refresh
+ * operation a recoup request is about. Coins with a @e denom_sig are
+ * disclosed and recouped; for the others only the hash of the blinded
+ * envelope is sent to the exchange.
+ */
+struct TALER_EXCHANGE_RecoupCoin
+{
+ /**
+ * Denomination of the coin.
+ */
+ const struct TALER_EXCHANGE_DenomPublicKey *pk;
+
+ /**
+ * Values the exchange contributed to the blinding of the coin
+ * (from /blinding-prepare), NULL for RSA denominations.
+ */
+ const struct TALER_ExchangeBlindingValues *exchange_vals;
+
+ /**
+ * Planchet secret of the coin, from which its private key and
+ * its blinding secret are derived.
+ */
+ const struct TALER_PlanchetMasterSecretP *ps;
+
+ /**
+ * Hash of the age commitment of the coin, NULL if the
+ * denomination has no age restriction.
+ */
+ const struct TALER_AgeCommitmentHashP *h_age_commitment;
+
+ /**
+ * Unblinded signature of the denomination over the coin,
+ * NULL if the coin is not to be recouped.
+ */
+ const struct TALER_DenominationSignature *denom_sig;
+};
+
+
+/**
+ * @brief Handle for a /recoup-withdraw request.
*/
struct TALER_EXCHANGE_PostRecoupWithdrawHandle;
/**
- * Set up POST /coins/$COIN_PUB/recoup operation.
- * Note that you must explicitly start the operation after setup.
- *
- * Ask the exchange to pay back a coin due to the exchange triggering
- * the emergency recoup protocol for a given denomination. The value
- * of the coin will be refunded to the original customer (without fees).
+ * Prepare a request to recoup coins of revoked denominations that
+ * originated from one withdraw operation to the reserve they were
+ * withdrawn from. The request must then be started with
+ * #TALER_EXCHANGE_post_recoup_withdraw_start().
*
* @param ctx curl context
* @param url exchange base URL
* @param keys exchange keys
- * @param pk kind of coin to pay back
- * @param denom_sig signature over the coin by the exchange using @a pk
- * @param exchange_vals contribution from the exchange on the withdraw
- * @param blinding_seed seed used to derive the CS nonce; NULL for RSA
- * @param coin_offset offset of the coin in the original withdraw operation
- * @param ps secret internals of the original planchet
- * @param h_planchets hash of the commitment of the corresponding original withdraw request
+ * @param reserve_pub reserve the coins were withdrawn from
+ * @param planchets_h commitment of the withdraw operation
+ * @param blinding_seed blinding seed used in the withdraw operation,
+ * NULL if no coin has a CS denomination
+ * @param num_coins number of coins the exchange signed in the operation
+ * @param coins the coins, in the order of the operation; at least
+ * one must have a denomination signature
* @return handle to operation, NULL if the inputs are invalid
*/
struct TALER_EXCHANGE_PostRecoupWithdrawHandle *
@@ -54,17 +93,15 @@ TALER_EXCHANGE_post_recoup_withdraw_create (
struct GNUNET_CURL_Context *ctx,
const char *url,
struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_EXCHANGE_DenomPublicKey *pk,
- const struct TALER_DenominationSignature *denom_sig,
- const struct TALER_ExchangeBlindingValues *exchange_vals,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_HashBlindedPlanchetsP *planchets_h,
const struct TALER_BlindingMasterSeedP *blinding_seed,
- uint32_t coin_offset,
- const struct TALER_PlanchetMasterSecretP *ps,
- const struct TALER_HashBlindedPlanchetsP *h_planchets);
+ size_t num_coins,
+ const struct TALER_EXCHANGE_RecoupCoin coins[static num_coins]);
/**
- * Response from a POST /coins/$COIN_PUB/recoup request.
+ * Response from a /recoup-withdraw request.
*/
struct TALER_EXCHANGE_PostRecoupWithdrawResponse
{
@@ -74,40 +111,74 @@ struct TALER_EXCHANGE_PostRecoupWithdrawResponse
struct TALER_EXCHANGE_HttpResponse hr;
/**
- * Response details depending on the HTTP status.
+ * Details depending on @e hr.http_status.
*/
union
{
/**
- * Details if HTTP status is #MHD_HTTP_OK.
+ * Details on #MHD_HTTP_OK. The batch confirmation
+ * signature was verified.
*/
struct
{
/**
- * public key of the reserve receiving the recoup
+ * Reserve that was credited.
*/
struct TALER_ReservePublicKeyP reserve_pub;
+ /**
+ * Commitment of the withdraw operation.
+ */
+ struct TALER_HashBlindedPlanchetsP planchets_h;
+
+ /**
+ * When the exchange accepted the recoup.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Total amount credited to the reserve.
+ */
+ struct TALER_Amount total_amount;
+
+ /**
+ * Number of entries in @e recoups, equal to the number of
+ * disclosed coins in the request.
+ */
+ size_t num_recoups;
+
+ /**
+ * The recouped coins and their amounts, in request order.
+ */
+ const struct TALER_RecoupedCoin *recoups;
+
+ /**
+ * Signature over `TALER_RecoupWithdrawBatchConfirmationPS`.
+ */
+ struct TALER_ExchangeSignatureP exchange_sig;
+
+ /**
+ * Public key used to create @e exchange_sig.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
} ok;
} details;
-
};
#ifndef TALER_EXCHANGE_POST_RECOUP_WITHDRAW_RESULT_CLOSURE
/**
- * Type of the closure used by
- * the #TALER_EXCHANGE_PostRecoupWithdrawCallback.
+ * Type of the closure for #TALER_EXCHANGE_PostRecoupWithdrawCallback.
*/
#define TALER_EXCHANGE_POST_RECOUP_WITHDRAW_RESULT_CLOSURE void
#endif /* TALER_EXCHANGE_POST_RECOUP_WITHDRAW_RESULT_CLOSURE */
+
/**
- * Type of the function that receives the result of a
- * POST /coins/$COIN_PUB/recoup request.
+ * Callback for a /recoup-withdraw request.
*
* @param cls closure
- * @param result result returned by the HTTP server
+ * @param result the response
*/
typedef void
(*TALER_EXCHANGE_PostRecoupWithdrawCallback)(
@@ -116,12 +187,12 @@ typedef void
/**
- * Start POST /coins/$COIN_PUB/recoup operation.
+ * Start the /recoup-withdraw request.
*
- * @param[in,out] prwh operation to start
- * @param cb function to call with the exchange's result
+ * @param prwh the prepared request
+ * @param cb function to call with the result
* @param cb_cls closure for @a cb
- * @return status code, #TALER_EC_NONE on success
+ * @return #TALER_EC_NONE on success
*/
enum TALER_ErrorCode
TALER_EXCHANGE_post_recoup_withdraw_start (
@@ -131,16 +202,13 @@ TALER_EXCHANGE_post_recoup_withdraw_start (
/**
- * Cancel POST /coins/$COIN_PUB/recoup operation. This function must not be
- * called by clients after the TALER_EXCHANGE_PostRecoupWithdrawCallback has
- * been invoked (as in those cases it'll be called internally by the
- * implementation already).
+ * Cancel the /recoup-withdraw request; must not be called
+ * after the callback was invoked.
*
- * @param[in] prwh operation to cancel
+ * @param prwh the request to cancel
*/
void
TALER_EXCHANGE_post_recoup_withdraw_cancel (
struct TALER_EXCHANGE_PostRecoupWithdrawHandle *prwh);
-
#endif /* _TALER_EXCHANGE__POST_RECOUP_WITHDRAW_H */
diff --git a/src/include/taler/taler_testing_lib.h b/src/include/taler/taler_testing_lib.h
@@ -620,6 +620,19 @@ TALER_TESTING_parse_coin_reference (
unsigned int *idx);
+/**
+ * Parse a comma-separated list of indices, e.g. "0,2".
+ *
+ * @param indices the list
+ * @param[out] num_indices set to the number of indices found
+ * @return array of @a num_indices indices to be freed by the caller,
+ * NULL on parse error
+ */
+unsigned int *
+TALER_TESTING_parse_index_list (const char *indices,
+ size_t *num_indices);
+
+
/* ************** Specific interpreter commands ************ */
@@ -1791,15 +1804,16 @@ TALER_TESTING_cmd_refund (const char *label,
/**
- * Make a "recoup" command.
+ * Make a "recoup-withdraw" command that recoups one coin of a
+ * withdraw operation to its reserve. The other coins of the
+ * operation are only named by their envelope hashes.
*
* @param label the command label
* @param expected_response_code expected HTTP status code
- * @param coin_reference reference to any command which
- * offers a coin and reserve private key. May specify
- * the index of the coin using "$LABEL#$INDEX" syntax.
- * Here, $INDEX must be a non-negative number.
- * @param amount how much do we expect to recoup, NULL for nothing
+ * @param coin_reference reference to a withdraw command, with the
+ * index of the coin to recoup using "$LABEL#$INDEX" syntax
+ * (index 0 if omitted).
+ * @param amount how much we expect to recoup for the coin, NULL for nothing
* @return the command.
*/
struct TALER_TESTING_Command
@@ -1810,16 +1824,37 @@ TALER_TESTING_cmd_recoup (const char *label,
/**
- * Make a "recoup-refresh" command.
+ * Make a "recoup-withdraw" command that recoups several coins of
+ * one withdraw operation to its reserve in one request.
+ *
+ * @param label the command label
+ * @param expected_response_code expected HTTP status code
+ * @param withdraw_reference label of the withdraw command
+ * @param indices comma-separated indices of the coins to recoup,
+ * e.g. "0,2"
+ * @param amount how much we expect to recoup per coin, NULL for nothing
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_recoup_batch (const char *label,
+ unsigned int expected_response_code,
+ const char *withdraw_reference,
+ const char *indices,
+ const char *amount);
+
+
+/**
+ * Make a "recoup-refresh" command that recoups one fresh coin of a
+ * refresh operation to the old coin that was melted. The other coins
+ * of the operation are only named by their envelope hashes.
*
* @param label the command label
* @param expected_response_code expected HTTP status code
- * @param coin_reference reference to any command which
- * offers a coin and reserve private key. May specify
- * the index of the coin using "$LABEL#$INDEX" syntax.
- * Here, $INDEX must be a non-negative number.
- * @param melt_reference label of the melt operation
- * @param amount how much do we expect to recoup, NULL for nothing
+ * @param coin_reference reference to the reveal-melt command, with the
+ * index of the fresh coin to recoup using "$LABEL#$INDEX" syntax
+ * (index 0 if omitted).
+ * @param melt_reference label of the melt command
+ * @param amount how much we expect to recoup for the coin, NULL for nothing
* @return the command.
*/
struct TALER_TESTING_Command
@@ -1831,6 +1866,28 @@ TALER_TESTING_cmd_recoup_refresh (const char *label,
/**
+ * Make a "recoup-refresh" command that recoups several fresh coins of
+ * one refresh operation to the old coin in one request.
+ *
+ * @param label the command label
+ * @param expected_response_code expected HTTP status code
+ * @param reveal_reference label of the reveal-melt command
+ * @param indices comma-separated indices of the fresh coins to recoup,
+ * e.g. "0,2"
+ * @param melt_reference label of the melt command
+ * @param amount how much we expect to recoup per coin, NULL for nothing
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_recoup_refresh_batch (const char *label,
+ unsigned int expected_response_code,
+ const char *reveal_reference,
+ const char *indices,
+ const char *melt_reference,
+ const char *amount);
+
+
+/**
* Make a "revoke" command.
*
* @param label the command label.
@@ -2820,6 +2877,7 @@ TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
op (withdraw_commitment, const struct TALER_HashBlindedPlanchetsP) \
op (kappa_seed, const struct TALER_KappaWithdrawMasterSeedP) \
op (refresh_seed, const struct TALER_PublicRefreshMasterSeedP) \
+ op (refresh_commitment, const struct TALER_RefreshCommitmentP) \
op (reserve_pub, const struct TALER_ReservePublicKeyP) \
op (merchant_priv, const struct TALER_MerchantPrivateKeyP) \
op (merchant_pub, const struct TALER_MerchantPublicKeyP) \
diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c
@@ -762,4 +762,236 @@ TALER_EXCHANGE_parse_451 (struct TALER_EXCHANGE_KycNeededRedirect *uflr,
}
+json_t *
+TALER_EXCHANGE_recoup_coin_data_ (
+ size_t num_coins,
+ const struct TALER_EXCHANGE_RecoupCoin coins[static num_coins],
+ const struct TALER_BlindingMasterSeedP *blinding_seed,
+ bool for_melt,
+ struct TALER_CoinSpendPublicKeyP recouped_pubs[static num_coins],
+ size_t *num_recouped)
+{
+ json_t *arr = json_array ();
+
+ GNUNET_assert (NULL != arr);
+ *num_recouped = 0;
+ for (size_t i = 0; i < num_coins; i++)
+ {
+ const struct TALER_EXCHANGE_RecoupCoin *c = &coins[i];
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+ const union GNUNET_CRYPTO_BlindSessionNonce *np = NULL;
+ json_t *entry;
+
+ const struct TALER_ExchangeBlindingValues *alg_values
+ = c->exchange_vals;
+
+ if ( (NULL == c->pk) ||
+ (NULL == c->ps) )
+ {
+ GNUNET_break (0);
+ json_decref (arr);
+ return NULL;
+ }
+ if (NULL == alg_values)
+ {
+ /* documented for RSA denominations, which have no
+ exchange-contributed blinding values */
+ if (GNUNET_CRYPTO_BSA_RSA != c->pk->key.bsign_pub_key->cipher)
+ {
+ GNUNET_break (0);
+ json_decref (arr);
+ return NULL;
+ }
+ alg_values = TALER_denom_ewv_rsa_singleton ();
+ }
+ TALER_planchet_setup_coin_priv (c->ps,
+ alg_values,
+ &coin_priv);
+ TALER_planchet_blinding_secret_create (c->ps,
+ alg_values,
+ &bks);
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
+ &coin_pub.eddsa_pub);
+ if (GNUNET_CRYPTO_BSA_CS == c->pk->key.bsign_pub_key->cipher)
+ {
+ if (NULL == blinding_seed)
+ {
+ GNUNET_break (0);
+ json_decref (arr);
+ return NULL;
+ }
+ TALER_cs_nonce_derive_indexed (blinding_seed,
+ for_melt,
+ (uint32_t) i,
+ &nonce.cs_nonce);
+ np = &nonce;
+ }
+ if (NULL == c->denom_sig)
+ {
+ struct TALER_PlanchetDetail pd;
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_BlindedCoinHashP bch;
+
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&c->pk->key,
+ alg_values,
+ &bks,
+ np,
+ &coin_priv,
+ c->h_age_commitment,
+ &c_hash,
+ &pd))
+ {
+ GNUNET_break (0);
+ json_decref (arr);
+ return NULL;
+ }
+ TALER_coin_ev_hash (&pd.blinded_planchet,
+ &c->pk->h_key,
+ &bch);
+ TALER_blinded_planchet_free (&pd.blinded_planchet);
+ entry = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "hash"),
+ GNUNET_JSON_pack_data_auto ("h_coin_ev",
+ &bch));
+ }
+ else
+ {
+ struct TALER_CoinSpendSignatureP coin_sig;
+
+ if (for_melt)
+ TALER_wallet_recoup_refresh_sign (&c->pk->h_key,
+ &bks,
+ &coin_priv,
+ &coin_sig);
+ else
+ TALER_wallet_recoup_sign (&c->pk->h_key,
+ &bks,
+ &coin_priv,
+ &coin_sig);
+ entry = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "recoup"),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &coin_pub),
+ GNUNET_JSON_pack_data_auto ("denom_pub_h",
+ &c->pk->h_key),
+ TALER_JSON_pack_denom_sig ("denom_sig",
+ c->denom_sig),
+ GNUNET_JSON_pack_data_auto ("coin_blinding_secret",
+ &bks),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_varsize ("h_age_commitment",
+ c->h_age_commitment,
+ (NULL == c->h_age_commitment)
+ ? 0
+ : sizeof (*c->h_age_commitment))),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &coin_sig));
+ recouped_pubs[*num_recouped] = coin_pub;
+ (*num_recouped)++;
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (arr,
+ entry));
+ }
+ if (0 == *num_recouped)
+ {
+ GNUNET_break (0);
+ json_decref (arr);
+ return NULL;
+ }
+ return arr;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_parse_recoups_ (
+ const json_t *j_recoups,
+ size_t num_expected,
+ const struct TALER_CoinSpendPublicKeyP expected_pubs[static num_expected],
+ const struct TALER_Amount *total_amount,
+ struct TALER_RecoupedCoin **recoups,
+ struct GNUNET_HashCode *h_recoups)
+{
+ struct TALER_RecoupedCoin *rcs;
+ struct TALER_Amount sum;
+ size_t idx;
+ json_t *entry;
+
+ *recoups = NULL;
+ if ( (! json_is_array (j_recoups)) ||
+ (num_expected != json_array_size (j_recoups)) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rcs = GNUNET_new_array (num_expected,
+ struct TALER_RecoupedCoin);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (total_amount->currency,
+ &sum));
+ json_array_foreach (j_recoups, idx, entry)
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &rcs[idx].coin_pub),
+ TALER_JSON_spec_amount_any ("amount",
+ &rcs[idx].amount),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (entry,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (rcs);
+ return GNUNET_SYSERR;
+ }
+ if (0 !=
+ GNUNET_memcmp (&rcs[idx].coin_pub,
+ &expected_pubs[idx]))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (rcs);
+ return GNUNET_SYSERR;
+ }
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (&sum,
+ &rcs[idx].amount)) ||
+ (0 >
+ TALER_amount_add (&sum,
+ &sum,
+ &rcs[idx].amount)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (rcs);
+ return GNUNET_SYSERR;
+ }
+ }
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (&sum,
+ total_amount)) ||
+ (0 !=
+ TALER_amount_cmp (&sum,
+ total_amount)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (rcs);
+ return GNUNET_SYSERR;
+ }
+ TALER_recoup_batch_hash (num_expected,
+ rcs,
+ h_recoups);
+ *recoups = rcs;
+ return GNUNET_OK;
+}
+
+
/* end of exchange_api_common.c */
diff --git a/src/lib/exchange_api_common.h b/src/lib/exchange_api_common.h
@@ -180,4 +180,55 @@ TALER_EXCHANGE_verify_deposit_signature_ (
const struct TALER_EXCHANGE_DenomPublicKey *dki);
+/**
+ * Build the ``coin_data`` array of a recoup request: the hash of the
+ * blinded envelope for coins that are not to be recouped, the disclosed
+ * coin with its recoup signature for the others.
+ *
+ * @param num_coins number of coins the exchange signed in the operation
+ * @param coins the coins, in the order of the operation
+ * @param blinding_seed blinding seed of the operation, NULL if there
+ * are no CS coins
+ * @param for_melt true if the operation was a melt (affects the
+ * nonce derivation and the signature purpose)
+ * @param[out] recouped_pubs array of @a num_coins entries, set to the
+ * public keys of the recouped coins, in order
+ * @param[out] num_recouped set to the number of recouped coins
+ * @return the JSON array, NULL on failure (no disclosed coin, or a CS
+ * coin without @a blinding_seed)
+ */
+json_t *
+TALER_EXCHANGE_recoup_coin_data_ (
+ size_t num_coins,
+ const struct TALER_EXCHANGE_RecoupCoin coins[static num_coins],
+ const struct TALER_BlindingMasterSeedP *blinding_seed,
+ bool for_melt,
+ struct TALER_CoinSpendPublicKeyP recouped_pubs[static num_coins],
+ size_t *num_recouped);
+
+
+/**
+ * Parse the ``recoups`` array of a recoup response and check it
+ * against the request: the entries must name the recouped coins in
+ * request order and their amounts must add up to @a total_amount.
+ *
+ * @param j_recoups the JSON array
+ * @param num_expected number of recouped coins in the request
+ * @param expected_pubs their public keys, in order
+ * @param total_amount total amount claimed by the response
+ * @param[out] recoups set to an array of @a num_expected entries, to
+ * be freed by the caller
+ * @param[out] h_recoups set to the hash over the entries as covered
+ * by the batch confirmation signature
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_parse_recoups_ (
+ const json_t *j_recoups,
+ size_t num_expected,
+ const struct TALER_CoinSpendPublicKeyP expected_pubs[static num_expected],
+ const struct TALER_Amount *total_amount,
+ struct TALER_RecoupedCoin **recoups,
+ struct GNUNET_HashCode *h_recoups);
+
#endif
diff --git a/src/lib/exchange_api_get-coins-COIN_PUB-history.c b/src/lib/exchange_api_get-coins-COIN_PUB-history.c
@@ -406,8 +406,12 @@ help_recoup (struct CoinHistoryParseContext *pc,
&rh->details.recoup.reserve_pub),
GNUNET_JSON_spec_fixed_auto ("coin_sig",
&rh->details.recoup.coin_sig),
- GNUNET_JSON_spec_fixed_auto ("coin_blind",
+ GNUNET_JSON_spec_fixed_auto ("coin_blinding_secret",
&rh->details.recoup.coin_bks),
+ GNUNET_JSON_spec_fixed_auto ("planchets_h",
+ &rh->details.recoup.planchets_h),
+ GNUNET_JSON_spec_uint32 ("coin_index",
+ &rh->details.recoup.coin_index),
GNUNET_JSON_spec_timestamp ("timestamp",
&rh->details.recoup.timestamp),
GNUNET_JSON_spec_end ()
@@ -480,8 +484,12 @@ help_recoup_refresh (struct CoinHistoryParseContext *pc,
&rh->details.recoup_refresh.coin_sig),
GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
&rh->details.recoup_refresh.old_coin_pub),
- GNUNET_JSON_spec_fixed_auto ("coin_blind",
+ GNUNET_JSON_spec_fixed_auto ("coin_blinding_secret",
&rh->details.recoup_refresh.coin_bks),
+ GNUNET_JSON_spec_fixed_auto ("rc",
+ &rh->details.recoup_refresh.rc),
+ GNUNET_JSON_spec_uint32 ("coin_index",
+ &rh->details.recoup_refresh.coin_index),
GNUNET_JSON_spec_timestamp ("timestamp",
&rh->details.recoup_refresh.timestamp),
GNUNET_JSON_spec_end ()
@@ -516,10 +524,10 @@ help_recoup_refresh (struct CoinHistoryParseContext *pc,
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
- TALER_wallet_recoup_verify (&pc->dk->h_key,
- &rh->details.recoup_refresh.coin_bks,
- pc->coin_pub,
- &rh->details.recoup_refresh.coin_sig))
+ TALER_wallet_recoup_refresh_verify (&pc->dk->h_key,
+ &rh->details.recoup_refresh.coin_bks,
+ pc->coin_pub,
+ &rh->details.recoup_refresh.coin_sig))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
@@ -553,6 +561,8 @@ help_old_coin_recoup (struct CoinHistoryParseContext *pc,
&rh->details.old_coin_recoup.exchange_pub),
GNUNET_JSON_spec_fixed_auto ("coin_pub",
&rh->details.old_coin_recoup.new_coin_pub),
+ GNUNET_JSON_spec_fixed_auto ("rc",
+ &rh->details.old_coin_recoup.rc),
GNUNET_JSON_spec_timestamp ("timestamp",
&rh->details.old_coin_recoup.timestamp),
GNUNET_JSON_spec_end ()
diff --git a/src/lib/exchange_api_get-reserves-RESERVE_PUB-history.c b/src/lib/exchange_api_get-reserves-RESERVE_PUB-history.c
@@ -447,6 +447,8 @@ parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
&rh->details.recoup_details.exchange_pub),
GNUNET_JSON_spec_timestamp ("timestamp",
&rh->details.recoup_details.timestamp),
+ GNUNET_JSON_spec_fixed_auto ("planchets_h",
+ &rh->details.recoup_details.planchets_h),
GNUNET_JSON_spec_end ()
};
diff --git a/src/lib/exchange_api_post-recoup-refresh.c b/src/lib/exchange_api_post-recoup-refresh.c
@@ -3,21 +3,21 @@
Copyright (C) 2017-2026 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
+ terms of the GNU Affero General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ A PARTICULAR PURPOSE. See the GNU Affero 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/>
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file lib/exchange_api_post-recoup-refresh.c
* @brief Implementation of the /recoup-refresh request of the exchange's HTTP API
* @author Christian Grothoff
+ * @author Özgür Kesim
*/
#include <jansson.h>
#include <microhttpd.h> /* just for HTTP status codes */
@@ -32,7 +32,7 @@
/**
- * @brief A Recoup-Refresh Handle
+ * @brief A /recoup-refresh handle
*/
struct TALER_EXCHANGE_PostRecoupRefreshHandle
{
@@ -78,9 +78,24 @@ struct TALER_EXCHANGE_PostRecoupRefreshHandle
struct TALER_EXCHANGE_Keys *keys;
/**
- * Public key of the coin we are trying to get paid back.
+ * Old coin the coins were refreshed from.
*/
- struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinSpendPublicKeyP old_coin_pub;
+
+ /**
+ * Commitment of the refresh operation.
+ */
+ struct TALER_RefreshCommitmentP rc;
+
+ /**
+ * Public keys of the recouped coins, in request order.
+ */
+ struct TALER_CoinSpendPublicKeyP *recouped_pubs;
+
+ /**
+ * Number of entries in @e recouped_pubs.
+ */
+ size_t num_recouped;
/**
* Pre-built request body.
@@ -91,38 +106,99 @@ struct TALER_EXCHANGE_PostRecoupRefreshHandle
/**
- * Parse a recoup-refresh response. If it is valid, call the callback.
+ * Parse and verify a successful response. If it is valid,
+ * call the callback.
*
- * @param ph recoup-refresh handle
+ * @param prrh request handle
* @param json json reply with the signature
- * @return #GNUNET_OK if the signature is valid and we called the callback;
+ * @return #GNUNET_OK if the response is valid and we called the callback;
* #GNUNET_SYSERR if not (callback must still be called)
*/
static enum GNUNET_GenericReturnValue
-process_recoup_response (
- const struct TALER_EXCHANGE_PostRecoupRefreshHandle *ph,
+process_ok_response (
+ const struct TALER_EXCHANGE_PostRecoupRefreshHandle *prrh,
const json_t *json)
{
- struct TALER_EXCHANGE_PostRecoupRefreshResponse rrr = {
+ struct TALER_EXCHANGE_PostRecoupRefreshResponse rr = {
.hr.reply = json,
.hr.http_status = MHD_HTTP_OK
};
- struct GNUNET_JSON_Specification spec_refresh[] = {
+ const json_t *j_recoups;
+ struct TALER_RecoupedCoin *recoups;
+ struct GNUNET_HashCode h_recoups;
+ struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
- &rrr.details.ok.old_coin_pub),
+ &rr.details.ok.old_coin_pub),
+ GNUNET_JSON_spec_fixed_auto ("rc",
+ &rr.details.ok.rc),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &rr.details.ok.timestamp),
+ TALER_JSON_spec_amount_any ("total_amount",
+ &rr.details.ok.total_amount),
+ GNUNET_JSON_spec_array_const ("recoups",
+ &j_recoups),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rr.details.ok.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rr.details.ok.exchange_pub),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (json,
- spec_refresh,
+ spec,
NULL, NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- ph->cb (ph->cb_cls,
- &rrr);
+ if ( (0 !=
+ GNUNET_memcmp (&rr.details.ok.old_coin_pub,
+ &prrh->old_coin_pub)) ||
+ (0 !=
+ GNUNET_memcmp (&rr.details.ok.rc,
+ &prrh->rc)) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (prrh->keys,
+ &rr.details.ok.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_parse_recoups_ (j_recoups,
+ prrh->num_recouped,
+ prrh->recouped_pubs,
+ &rr.details.ok.total_amount,
+ &recoups,
+ &h_recoups))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_confirm_recoup_refresh_batch_verify (
+ rr.details.ok.timestamp,
+ &rr.details.ok.old_coin_pub,
+ &rr.details.ok.rc,
+ &rr.details.ok.total_amount,
+ &h_recoups,
+ &rr.details.ok.exchange_pub,
+ &rr.details.ok.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (recoups);
+ return GNUNET_SYSERR;
+ }
+ rr.details.ok.num_recoups = prrh->num_recouped;
+ rr.details.ok.recoups = recoups;
+ prrh->cb (prrh->cb_cls,
+ &rr);
+ GNUNET_free (recoups);
return GNUNET_OK;
}
@@ -140,80 +216,92 @@ handle_recoup_refresh_finished (void *cls,
long response_code,
const void *response)
{
- struct TALER_EXCHANGE_PostRecoupRefreshHandle *ph = cls;
+ struct TALER_EXCHANGE_PostRecoupRefreshHandle *prrh = cls;
const json_t *j = response;
- struct TALER_EXCHANGE_PostRecoupRefreshResponse rrr = {
+ struct TALER_EXCHANGE_PostRecoupRefreshResponse rr = {
.hr.reply = j,
.hr.http_status = (unsigned int) response_code
};
- ph->job = NULL;
+ prrh->job = NULL;
switch (response_code)
{
case 0:
- rrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ rr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
- process_recoup_response (ph,
- j))
+ process_ok_response (prrh,
+ j))
{
GNUNET_break_op (0);
- rrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- rrr.hr.http_status = 0;
+ rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+ rr.hr.http_status = 0;
break;
}
- TALER_EXCHANGE_post_recoup_refresh_cancel (ph);
+ TALER_EXCHANGE_post_recoup_refresh_cancel (prrh);
return;
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 */
- rrr.hr.ec = TALER_JSON_get_error_code (j);
- rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_FORBIDDEN:
/* 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 */
- rrr.hr.ec = TALER_JSON_get_error_code (j);
- rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_NOT_FOUND:
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- rrr.hr.ec = TALER_JSON_get_error_code (j);
- rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Exchange does not know the denomination or the refresh
+ operation; pass the JSON reply to the application */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_CONFLICT:
- rrr.hr.ec = TALER_JSON_get_error_code (j);
- rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Commitment mismatch, denomination mismatch or a coin without
+ residual value; pass the JSON reply to the application */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_GONE:
- /* Kind of normal: the money was already sent to the merchant
- (it was too late for the refund). */
- rrr.hr.ec = TALER_JSON_get_error_code (j);
- rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ /* Denomination not eligible for recoup (not revoked, or expired) */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_PRECONDITION_FAILED:
+ /* Denomination not yet valid */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.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 */
- rrr.hr.ec = TALER_JSON_get_error_code (j);
- rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_BAD_GATEWAY:
+ case MHD_HTTP_SERVICE_UNAVAILABLE:
+ /* Exchange is (temporarily) unable to sign; retry later */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
default:
/* unexpected response code */
- rrr.hr.ec = TALER_JSON_get_error_code (j);
- rrr.hr.hint = TALER_JSON_get_error_hint (j);
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d for exchange recoup-refresh\n",
(unsigned int) response_code,
- (int) rrr.hr.ec);
+ (int) rr.hr.ec);
GNUNET_break (0);
break;
}
- ph->cb (ph->cb_cls,
- &rrr);
- TALER_EXCHANGE_post_recoup_refresh_cancel (ph);
+ prrh->cb (prrh->cb_cls,
+ &rr);
+ TALER_EXCHANGE_post_recoup_refresh_cancel (prrh);
}
@@ -222,100 +310,61 @@ TALER_EXCHANGE_post_recoup_refresh_create (
struct GNUNET_CURL_Context *ctx,
const char *url,
struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_EXCHANGE_DenomPublicKey *pk,
- const struct TALER_DenominationSignature *denom_sig,
- const struct TALER_ExchangeBlindingValues *exchange_vals,
- const struct TALER_PublicRefreshMasterSeedP *rms,
- const struct TALER_PlanchetMasterSecretP *ps,
- unsigned int idx)
+ const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ const struct TALER_RefreshCommitmentP *rc,
+ const struct TALER_BlindingMasterSeedP *blinding_seed,
+ size_t num_coins,
+ const struct TALER_EXCHANGE_RecoupCoin coins[static num_coins])
{
- struct TALER_EXCHANGE_PostRecoupRefreshHandle *ph;
- struct TALER_DenominationHashP h_denom_pub;
- struct TALER_CoinSpendPrivateKeyP coin_priv;
- union GNUNET_CRYPTO_BlindingSecretP bks;
- struct TALER_CoinSpendSignatureP coin_sig;
-
- (void) rms; // FIXME: why did we pass this again?
- (void) idx; // FIXME: why did we pass this again?
- ph = GNUNET_new (struct TALER_EXCHANGE_PostRecoupRefreshHandle);
- ph->ctx = ctx;
- ph->base_url = GNUNET_strdup (url);
- ph->keys = TALER_EXCHANGE_keys_incref (keys);
- TALER_planchet_setup_coin_priv (ps,
- exchange_vals,
- &coin_priv);
- TALER_planchet_blinding_secret_create (ps,
- exchange_vals,
- &bks);
- GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
- &ph->coin_pub.eddsa_pub);
- TALER_denom_pub_hash (&pk->key,
- &h_denom_pub);
- TALER_wallet_recoup_refresh_sign (&h_denom_pub,
- &bks,
- &coin_priv,
- &coin_sig);
- ph->body = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("denom_pub_hash",
- &h_denom_pub),
- TALER_JSON_pack_denom_sig ("denom_sig",
- denom_sig),
- TALER_JSON_pack_exchange_blinding_values ("ewv",
- exchange_vals),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &coin_sig),
- GNUNET_JSON_pack_data_auto ("coin_blind_key_secret",
- &bks));
- // FIXME: nonce and h_age_commitment never sent here, but required
- // by spec and server (if used with age commentment or CS cipher!)
- switch (denom_sig->unblinded_sig->cipher)
+ struct TALER_EXCHANGE_PostRecoupRefreshHandle *prrh;
+ json_t *coin_data;
+
+ prrh = GNUNET_new (struct TALER_EXCHANGE_PostRecoupRefreshHandle);
+ prrh->recouped_pubs = GNUNET_new_array (num_coins,
+ struct TALER_CoinSpendPublicKeyP);
+ coin_data = TALER_EXCHANGE_recoup_coin_data_ (num_coins,
+ coins,
+ blinding_seed,
+ true, /* for melt */
+ prrh->recouped_pubs,
+ &prrh->num_recouped);
+ if (NULL == coin_data)
{
- case GNUNET_CRYPTO_BSA_INVALID:
- json_decref (ph->body);
- GNUNET_free (ph->base_url);
- TALER_EXCHANGE_keys_decref (ph->keys);
- GNUNET_free (ph);
GNUNET_break (0);
+ GNUNET_free (prrh->recouped_pubs);
+ GNUNET_free (prrh);
return NULL;
- case GNUNET_CRYPTO_BSA_RSA:
- break;
- case GNUNET_CRYPTO_BSA_CS:
- break;
}
- return ph;
+ prrh->ctx = ctx;
+ prrh->base_url = GNUNET_strdup (url);
+ prrh->keys = TALER_EXCHANGE_keys_incref (keys);
+ prrh->old_coin_pub = *old_coin_pub;
+ prrh->rc = *rc;
+ prrh->body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("old_coin_pub",
+ old_coin_pub),
+ GNUNET_JSON_pack_data_auto ("rc",
+ rc),
+ GNUNET_JSON_pack_array_steal ("coin_data",
+ coin_data));
+ return prrh;
}
enum TALER_ErrorCode
TALER_EXCHANGE_post_recoup_refresh_start (
- struct TALER_EXCHANGE_PostRecoupRefreshHandle *ph,
+ struct TALER_EXCHANGE_PostRecoupRefreshHandle *prrh,
TALER_EXCHANGE_PostRecoupRefreshCallback cb,
TALER_EXCHANGE_POST_RECOUP_REFRESH_RESULT_CLOSURE *cb_cls)
{
CURL *eh;
- char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
- ph->cb = cb;
- ph->cb_cls = cb_cls;
- {
- char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- &ph->coin_pub,
- sizeof (struct TALER_CoinSpendPublicKeyP),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "coins/%s/recoup-refresh",
- pub_str);
- }
- ph->url = TALER_url_join (ph->base_url,
- arg_str,
- NULL);
- if (NULL == ph->url)
+ prrh->cb = cb;
+ prrh->cb_cls = cb_cls;
+ prrh->url = TALER_url_join (prrh->base_url,
+ "recoup-refresh",
+ NULL);
+ if (NULL == prrh->url)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Could not construct request URL.\n");
@@ -323,25 +372,25 @@ TALER_EXCHANGE_post_recoup_refresh_start (
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"URL for recoup-refresh: `%s'\n",
- ph->url);
- eh = TALER_EXCHANGE_curl_easy_get_ (ph->url);
+ prrh->url);
+ eh = TALER_EXCHANGE_curl_easy_get_ (prrh->url);
if ( (NULL == eh) ||
(GNUNET_OK !=
- TALER_curl_easy_post (&ph->post_ctx,
+ TALER_curl_easy_post (&prrh->post_ctx,
eh,
- ph->body)) )
+ prrh->body)) )
{
GNUNET_break (0);
if (NULL != eh)
curl_easy_cleanup (eh);
return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
}
- ph->job = GNUNET_CURL_job_add2 (ph->ctx,
- eh,
- ph->post_ctx.headers,
- &handle_recoup_refresh_finished,
- ph);
- if (NULL == ph->job)
+ prrh->job = GNUNET_CURL_job_add2 (prrh->ctx,
+ eh,
+ prrh->post_ctx.headers,
+ &handle_recoup_refresh_finished,
+ prrh);
+ if (NULL == prrh->job)
return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
return TALER_EC_NONE;
}
@@ -349,19 +398,20 @@ TALER_EXCHANGE_post_recoup_refresh_start (
void
TALER_EXCHANGE_post_recoup_refresh_cancel (
- struct TALER_EXCHANGE_PostRecoupRefreshHandle *ph)
+ struct TALER_EXCHANGE_PostRecoupRefreshHandle *prrh)
{
- if (NULL != ph->job)
+ if (NULL != prrh->job)
{
- GNUNET_CURL_job_cancel (ph->job);
- ph->job = NULL;
+ GNUNET_CURL_job_cancel (prrh->job);
+ prrh->job = NULL;
}
- TALER_curl_easy_post_finished (&ph->post_ctx);
- GNUNET_free (ph->url);
- GNUNET_free (ph->base_url);
- json_decref (ph->body);
- TALER_EXCHANGE_keys_decref (ph->keys);
- GNUNET_free (ph);
+ TALER_curl_easy_post_finished (&prrh->post_ctx);
+ GNUNET_free (prrh->url);
+ GNUNET_free (prrh->base_url);
+ GNUNET_free (prrh->recouped_pubs);
+ json_decref (prrh->body);
+ TALER_EXCHANGE_keys_decref (prrh->keys);
+ GNUNET_free (prrh);
}
diff --git a/src/lib/exchange_api_post-recoup-withdraw.c b/src/lib/exchange_api_post-recoup-withdraw.c
@@ -3,21 +3,21 @@
Copyright (C) 2017-2026 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
+ terms of the GNU Affero General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ A PARTICULAR PURPOSE. See the GNU Affero 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/>
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file lib/exchange_api_post-recoup-withdraw.c
- * @brief Implementation of the /recoup request of the exchange's HTTP API
+ * @brief Implementation of the /recoup-withdraw request of the exchange's HTTP API
* @author Christian Grothoff
+ * @author Özgür Kesim
*/
#include <jansson.h>
#include <microhttpd.h> /* just for HTTP status codes */
@@ -32,7 +32,7 @@
/**
- * @brief A Recoup Handle
+ * @brief A /recoup-withdraw handle
*/
struct TALER_EXCHANGE_PostRecoupWithdrawHandle
{
@@ -78,9 +78,24 @@ struct TALER_EXCHANGE_PostRecoupWithdrawHandle
struct TALER_EXCHANGE_Keys *keys;
/**
- * Public key of the coin we are trying to get paid back.
+ * Reserve the coins were withdrawn from.
*/
- struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Commitment of the withdraw operation.
+ */
+ struct TALER_HashBlindedPlanchetsP planchets_h;
+
+ /**
+ * Public keys of the recouped coins, in request order.
+ */
+ struct TALER_CoinSpendPublicKeyP *recouped_pubs;
+
+ /**
+ * Number of entries in @e recouped_pubs.
+ */
+ size_t num_recouped;
/**
* Pre-built request body.
@@ -91,63 +106,124 @@ struct TALER_EXCHANGE_PostRecoupWithdrawHandle
/**
- * Parse a recoup response. If it is valid, call the callback.
+ * Parse and verify a successful response. If it is valid,
+ * call the callback.
*
- * @param ph recoup handle
+ * @param prwh request handle
* @param json json reply with the signature
- * @return #GNUNET_OK if the signature is valid and we called the callback;
+ * @return #GNUNET_OK if the response is valid and we called the callback;
* #GNUNET_SYSERR if not (callback must still be called)
*/
static enum GNUNET_GenericReturnValue
-process_recoup_response (
- const struct TALER_EXCHANGE_PostRecoupWithdrawHandle *ph,
+process_ok_response (
+ const struct TALER_EXCHANGE_PostRecoupWithdrawHandle *prwh,
const json_t *json)
{
struct TALER_EXCHANGE_PostRecoupWithdrawResponse rr = {
.hr.reply = json,
.hr.http_status = MHD_HTTP_OK
};
- struct GNUNET_JSON_Specification spec_withdraw[] = {
+ const json_t *j_recoups;
+ struct TALER_RecoupedCoin *recoups;
+ struct GNUNET_HashCode h_recoups;
+ struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("reserve_pub",
&rr.details.ok.reserve_pub),
+ GNUNET_JSON_spec_fixed_auto ("planchets_h",
+ &rr.details.ok.planchets_h),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &rr.details.ok.timestamp),
+ TALER_JSON_spec_amount_any ("total_amount",
+ &rr.details.ok.total_amount),
+ GNUNET_JSON_spec_array_const ("recoups",
+ &j_recoups),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rr.details.ok.exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rr.details.ok.exchange_pub),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (json,
- spec_withdraw,
+ spec,
NULL, NULL))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- ph->cb (ph->cb_cls,
- &rr);
+ if ( (0 !=
+ GNUNET_memcmp (&rr.details.ok.reserve_pub,
+ &prwh->reserve_pub)) ||
+ (0 !=
+ GNUNET_memcmp (&rr.details.ok.planchets_h,
+ &prwh->planchets_h)) )
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (prwh->keys,
+ &rr.details.ok.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_parse_recoups_ (j_recoups,
+ prwh->num_recouped,
+ prwh->recouped_pubs,
+ &rr.details.ok.total_amount,
+ &recoups,
+ &h_recoups))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_online_confirm_recoup_withdraw_batch_verify (
+ rr.details.ok.timestamp,
+ &rr.details.ok.reserve_pub,
+ &rr.details.ok.planchets_h,
+ &rr.details.ok.total_amount,
+ &h_recoups,
+ &rr.details.ok.exchange_pub,
+ &rr.details.ok.exchange_sig))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (recoups);
+ return GNUNET_SYSERR;
+ }
+ rr.details.ok.num_recoups = prwh->num_recouped;
+ rr.details.ok.recoups = recoups;
+ prwh->cb (prwh->cb_cls,
+ &rr);
+ GNUNET_free (recoups);
return GNUNET_OK;
}
/**
* Function called when we're done processing the
- * HTTP /recoup request.
+ * HTTP /recoup-withdraw request.
*
* @param cls the `struct TALER_EXCHANGE_PostRecoupWithdrawHandle`
* @param response_code HTTP response code, 0 on error
* @param response parsed JSON result, NULL on error
*/
static void
-handle_recoup_finished (void *cls,
- long response_code,
- const void *response)
+handle_recoup_withdraw_finished (void *cls,
+ long response_code,
+ const void *response)
{
- struct TALER_EXCHANGE_PostRecoupWithdrawHandle *ph = cls;
+ struct TALER_EXCHANGE_PostRecoupWithdrawHandle *prwh = cls;
const json_t *j = response;
struct TALER_EXCHANGE_PostRecoupWithdrawResponse rr = {
.hr.reply = j,
.hr.http_status = (unsigned int) response_code
};
- ph->job = NULL;
+ prwh->job = NULL;
switch (response_code)
{
case 0:
@@ -155,15 +231,15 @@ handle_recoup_finished (void *cls,
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
- process_recoup_response (ph,
- j))
+ process_ok_response (prwh,
+ j))
{
GNUNET_break_op (0);
rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
rr.hr.http_status = 0;
break;
}
- TALER_EXCHANGE_post_recoup_withdraw_cancel (ph);
+ TALER_EXCHANGE_post_recoup_withdraw_cancel (prwh);
return;
case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the exchange is buggy
@@ -171,23 +247,6 @@ handle_recoup_finished (void *cls,
rr.hr.ec = TALER_JSON_get_error_code (j);
rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
- case MHD_HTTP_CONFLICT:
- {
- struct TALER_Amount min_key;
-
- rr.hr.ec = TALER_JSON_get_error_code (j);
- rr.hr.hint = TALER_JSON_get_error_hint (j);
- if (GNUNET_OK !=
- TALER_EXCHANGE_get_min_denomination_ (ph->keys,
- &min_key))
- {
- GNUNET_break (0);
- rr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
- rr.hr.http_status = 0;
- break;
- }
- break;
- }
case MHD_HTTP_FORBIDDEN:
/* Nothing really to verify, exchange says one of the signatures is
invalid; as we checked them, this should never happen, we
@@ -196,14 +255,24 @@ handle_recoup_finished (void *cls,
rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_NOT_FOUND:
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
+ /* Exchange does not know the denomination or the withdraw
+ operation; pass the JSON reply to the application */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* Commitment mismatch, denomination mismatch or a coin without
+ residual value; pass the JSON reply to the application */
rr.hr.ec = TALER_JSON_get_error_code (j);
rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
case MHD_HTTP_GONE:
- /* Kind of normal: the money was already sent to the merchant
- (it was too late for the refund). */
+ /* Denomination not eligible for recoup (not revoked, or expired) */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
+ case MHD_HTTP_PRECONDITION_FAILED:
+ /* Denomination not yet valid */
rr.hr.ec = TALER_JSON_get_error_code (j);
rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
@@ -213,20 +282,26 @@ handle_recoup_finished (void *cls,
rr.hr.ec = TALER_JSON_get_error_code (j);
rr.hr.hint = TALER_JSON_get_error_hint (j);
break;
+ case MHD_HTTP_BAD_GATEWAY:
+ case MHD_HTTP_SERVICE_UNAVAILABLE:
+ /* Exchange is (temporarily) unable to sign; retry later */
+ rr.hr.ec = TALER_JSON_get_error_code (j);
+ rr.hr.hint = TALER_JSON_get_error_hint (j);
+ break;
default:
/* unexpected response code */
rr.hr.ec = TALER_JSON_get_error_code (j);
rr.hr.hint = TALER_JSON_get_error_hint (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u/%d for exchange recoup\n",
+ "Unexpected response code %u/%d for exchange recoup-withdraw\n",
(unsigned int) response_code,
(int) rr.hr.ec);
GNUNET_break (0);
break;
}
- ph->cb (ph->cb_cls,
- &rr);
- TALER_EXCHANGE_post_recoup_withdraw_cancel (ph);
+ prwh->cb (prwh->cb_cls,
+ &rr);
+ TALER_EXCHANGE_post_recoup_withdraw_cancel (prwh);
}
@@ -235,151 +310,87 @@ TALER_EXCHANGE_post_recoup_withdraw_create (
struct GNUNET_CURL_Context *ctx,
const char *url,
struct TALER_EXCHANGE_Keys *keys,
- const struct TALER_EXCHANGE_DenomPublicKey *pk,
- const struct TALER_DenominationSignature *denom_sig,
- const struct TALER_ExchangeBlindingValues *exchange_vals,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_HashBlindedPlanchetsP *planchets_h,
const struct TALER_BlindingMasterSeedP *blinding_seed,
- uint32_t coin_offset,
- const struct TALER_PlanchetMasterSecretP *ps,
- const struct TALER_HashBlindedPlanchetsP *h_planchets)
+ size_t num_coins,
+ const struct TALER_EXCHANGE_RecoupCoin coins[static num_coins])
{
- struct TALER_EXCHANGE_PostRecoupWithdrawHandle *ph;
- struct TALER_DenominationHashP h_denom_pub;
- struct TALER_CoinSpendPrivateKeyP coin_priv;
- union GNUNET_CRYPTO_BlindingSecretP bks;
- struct TALER_CoinSpendSignatureP coin_sig;
-
- ph = GNUNET_new (struct TALER_EXCHANGE_PostRecoupWithdrawHandle);
- ph->ctx = ctx;
- ph->base_url = GNUNET_strdup (url);
- ph->keys = TALER_EXCHANGE_keys_incref (keys);
- TALER_planchet_setup_coin_priv (ps,
- exchange_vals,
- &coin_priv);
- TALER_planchet_blinding_secret_create (ps,
- exchange_vals,
- &bks);
- GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
- &ph->coin_pub.eddsa_pub);
- TALER_denom_pub_hash (&pk->key,
- &h_denom_pub);
- TALER_wallet_recoup_sign (&h_denom_pub,
- &bks,
- &coin_priv,
- &coin_sig);
- ph->body = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("denom_pub_hash",
- &h_denom_pub),
- TALER_JSON_pack_denom_sig ("denom_sig",
- denom_sig),
- TALER_JSON_pack_exchange_blinding_values ("ewv",
- exchange_vals),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &coin_sig),
- GNUNET_JSON_pack_data_auto ("h_planchets",
- h_planchets),
- GNUNET_JSON_pack_data_auto ("coin_blind_key_secret",
- &bks));
- switch (denom_sig->unblinded_sig->cipher)
+ struct TALER_EXCHANGE_PostRecoupWithdrawHandle *prwh;
+ json_t *coin_data;
+
+ prwh = GNUNET_new (struct TALER_EXCHANGE_PostRecoupWithdrawHandle);
+ prwh->recouped_pubs = GNUNET_new_array (num_coins,
+ struct TALER_CoinSpendPublicKeyP);
+ coin_data = TALER_EXCHANGE_recoup_coin_data_ (num_coins,
+ coins,
+ blinding_seed,
+ false, /* not for melt */
+ prwh->recouped_pubs,
+ &prwh->num_recouped);
+ if (NULL == coin_data)
{
- case GNUNET_CRYPTO_BSA_INVALID:
- json_decref (ph->body);
- GNUNET_free (ph->base_url);
- TALER_EXCHANGE_keys_decref (ph->keys);
- GNUNET_free (ph);
GNUNET_break (0);
+ GNUNET_free (prwh->recouped_pubs);
+ GNUNET_free (prwh);
return NULL;
- case GNUNET_CRYPTO_BSA_RSA:
- break;
- case GNUNET_CRYPTO_BSA_CS:
- {
- union GNUNET_CRYPTO_BlindSessionNonce nonce;
- const uint32_t indices[] = { coin_offset };
-
- if (NULL == blinding_seed)
- {
- json_decref (ph->body);
- GNUNET_free (ph->base_url);
- TALER_EXCHANGE_keys_decref (ph->keys);
- GNUNET_free (ph);
- GNUNET_break (0);
- return NULL;
- }
- TALER_cs_derive_only_cs_blind_nonces_from_seed (
- blinding_seed,
- false,
- 1,
- indices,
- &nonce);
- GNUNET_assert (
- 0 ==
- json_object_set_new (ph->body,
- "nonce",
- GNUNET_JSON_from_data_auto (
- &nonce)));
- }
- break;
}
- return ph;
+ prwh->ctx = ctx;
+ prwh->base_url = GNUNET_strdup (url);
+ prwh->keys = TALER_EXCHANGE_keys_incref (keys);
+ prwh->reserve_pub = *reserve_pub;
+ prwh->planchets_h = *planchets_h;
+ prwh->body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ reserve_pub),
+ GNUNET_JSON_pack_data_auto ("planchets_h",
+ planchets_h),
+ GNUNET_JSON_pack_array_steal ("coin_data",
+ coin_data));
+ return prwh;
}
enum TALER_ErrorCode
TALER_EXCHANGE_post_recoup_withdraw_start (
- struct TALER_EXCHANGE_PostRecoupWithdrawHandle *ph,
+ struct TALER_EXCHANGE_PostRecoupWithdrawHandle *prwh,
TALER_EXCHANGE_PostRecoupWithdrawCallback cb,
TALER_EXCHANGE_POST_RECOUP_WITHDRAW_RESULT_CLOSURE *cb_cls)
{
CURL *eh;
- char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
- ph->cb = cb;
- ph->cb_cls = cb_cls;
- {
- char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (
- &ph->coin_pub,
- sizeof (struct TALER_CoinSpendPublicKeyP),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "coins/%s/recoup",
- pub_str);
- }
- ph->url = TALER_url_join (ph->base_url,
- arg_str,
- NULL);
- if (NULL == ph->url)
+ prwh->cb = cb;
+ prwh->cb_cls = cb_cls;
+ prwh->url = TALER_url_join (prwh->base_url,
+ "recoup-withdraw",
+ NULL);
+ if (NULL == prwh->url)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Could not construct request URL.\n");
return TALER_EC_GENERIC_CONFIGURATION_INVALID;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "URL for recoup: `%s'\n",
- ph->url);
- eh = TALER_EXCHANGE_curl_easy_get_ (ph->url);
+ "URL for recoup-withdraw: `%s'\n",
+ prwh->url);
+ eh = TALER_EXCHANGE_curl_easy_get_ (prwh->url);
if ( (NULL == eh) ||
(GNUNET_OK !=
- TALER_curl_easy_post (&ph->post_ctx,
+ TALER_curl_easy_post (&prwh->post_ctx,
eh,
- ph->body)) )
+ prwh->body)) )
{
GNUNET_break (0);
if (NULL != eh)
curl_easy_cleanup (eh);
return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
}
- ph->job = GNUNET_CURL_job_add2 (ph->ctx,
- eh,
- ph->post_ctx.headers,
- &handle_recoup_finished,
- ph);
- if (NULL == ph->job)
+ prwh->job = GNUNET_CURL_job_add2 (prwh->ctx,
+ eh,
+ prwh->post_ctx.headers,
+ &handle_recoup_withdraw_finished,
+ prwh);
+ if (NULL == prwh->job)
return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
return TALER_EC_NONE;
}
@@ -387,19 +398,20 @@ TALER_EXCHANGE_post_recoup_withdraw_start (
void
TALER_EXCHANGE_post_recoup_withdraw_cancel (
- struct TALER_EXCHANGE_PostRecoupWithdrawHandle *ph)
+ struct TALER_EXCHANGE_PostRecoupWithdrawHandle *prwh)
{
- if (NULL != ph->job)
+ if (NULL != prwh->job)
{
- GNUNET_CURL_job_cancel (ph->job);
- ph->job = NULL;
+ GNUNET_CURL_job_cancel (prwh->job);
+ prwh->job = NULL;
}
- TALER_curl_easy_post_finished (&ph->post_ctx);
- GNUNET_free (ph->url);
- GNUNET_free (ph->base_url);
- json_decref (ph->body);
- TALER_EXCHANGE_keys_decref (ph->keys);
- GNUNET_free (ph);
+ TALER_curl_easy_post_finished (&prwh->post_ctx);
+ GNUNET_free (prwh->url);
+ GNUNET_free (prwh->base_url);
+ GNUNET_free (prwh->recouped_pubs);
+ json_decref (prwh->body);
+ TALER_EXCHANGE_keys_decref (prwh->keys);
+ GNUNET_free (prwh);
}
diff --git a/src/testing/testing_api_cmd_recoup.c b/src/testing/testing_api_cmd_recoup.c
@@ -1,36 +1,33 @@
/*
This file is part of TALER
- Copyright (C) 2014-2023 Taler Systems SA
+ Copyright (C) 2018-2026 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 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.
+ 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/>
+ 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 testing/testing_api_cmd_recoup.c
- * @brief Implement the /recoup test command.
+ * @brief Implement the /recoup-withdraw test command.
* @author Marcello Stanisci
+ * @author Christian Grothoff
+ * @author Özgür Kesim
*/
+#include "platform.h"
#include "taler/taler_json_lib.h"
#include <gnunet/gnunet_curl_lib.h>
-struct RecoupState;
-#define TALER_EXCHANGE_POST_RECOUP_WITHDRAW_RESULT_CLOSURE struct RecoupState
-#include "taler/exchange/post-recoup-withdraw.h"
#include "taler/taler_testing_lib.h"
/**
- * State for a "pay back" CMD.
+ * State for a "recoup-withdraw" CMD.
*/
struct RecoupState
{
@@ -40,62 +37,71 @@ struct RecoupState
unsigned int expected_response_code;
/**
- * Command that offers a reserve private key,
- * plus a coin to be paid back.
+ * Label of the withdraw command the coins came from.
*/
- const char *coin_reference;
+ char *withdraw_reference;
/**
- * The interpreter state.
+ * Indices (in the withdraw command) of the coins to recoup.
+ */
+ unsigned int *indices;
+
+ /**
+ * Number of entries in @e indices, @e ches, @e rhes and @e coin_pubs.
+ */
+ size_t num_indices;
+
+ /**
+ * Amount we expect to be recouped per coin, invalid if
+ * not specified.
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Interpreter state.
*/
struct TALER_TESTING_Interpreter *is;
/**
- * Handle to the ongoing operation.
+ * Handle to the request.
*/
struct TALER_EXCHANGE_PostRecoupWithdrawHandle *ph;
/**
- * If the recoup filled a reserve, this is set to the reserve's public key.
+ * Reserve that is credited.
*/
struct TALER_ReservePublicKeyP reserve_pub;
/**
- * Entry in the coin's history generated by this operation.
+ * Coin history entries we expect, one per recouped coin.
*/
- struct TALER_EXCHANGE_CoinHistoryEntry che;
+ struct TALER_EXCHANGE_CoinHistoryEntry *ches;
/**
- * Public key of the refunded coin.
+ * Reserve history entries we expect, one per recouped coin.
*/
- struct TALER_CoinSpendPublicKeyP coin;
+ struct TALER_EXCHANGE_ReserveHistoryEntry *rhes;
/**
- * Reserve history entry, set if this recoup actually filled up a reserve.
- * Otherwise `reserve_history.type` will be zero.
+ * Public keys of the recouped coins.
*/
- struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
-
+ struct TALER_CoinSpendPublicKeyP *coin_pubs;
};
/**
- * Check the result of the recoup request: checks whether
- * the HTTP response code is good, and that the coin that
- * was paid back belonged to the right reserve.
+ * Check the response.
*
- * @param ps closure
+ * @param cls closure, our `struct RecoupState`
* @param rr response details
*/
static void
-recoup_cb (struct RecoupState *ps,
+recoup_cb (void *cls,
const struct TALER_EXCHANGE_PostRecoupWithdrawResponse *rr)
{
+ struct RecoupState *ps = cls;
const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
struct TALER_TESTING_Interpreter *is = ps->is;
- const struct TALER_TESTING_Command *reserve_cmd;
- char *cref;
- unsigned int idx;
ps->ph = NULL;
if (ps->expected_response_code != hr->http_status)
@@ -105,67 +111,55 @@ recoup_cb (struct RecoupState *ps,
ps->expected_response_code);
return;
}
-
- if (GNUNET_OK !=
- TALER_TESTING_parse_coin_reference (
- ps->coin_reference,
- &cref,
- &idx))
- {
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- (void) idx; /* do NOT use! We ignore 'idx', must be 0 for melt! */
-
- reserve_cmd = TALER_TESTING_interpreter_lookup_command (is,
- cref);
- GNUNET_free (cref);
-
- if (NULL == reserve_cmd)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
-
switch (hr->http_status)
{
case MHD_HTTP_OK:
- /* check old_coin_pub or reserve_pub, respectively */
+ if (0 != GNUNET_memcmp (&rr->details.ok.reserve_pub,
+ &ps->reserve_pub))
{
- const struct TALER_ReservePrivateKeyP *reserve_priv;
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (rr->details.ok.num_recoups != ps->num_indices)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ for (size_t i = 0; i < ps->num_indices; i++)
+ {
+ const struct TALER_RecoupedCoin *rc = &rr->details.ok.recoups[i];
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_reserve_priv (reserve_cmd,
- &reserve_priv))
+ if (0 != GNUNET_memcmp (&rc->coin_pub,
+ &ps->coin_pubs[i]))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
- GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
- &ps->reserve_pub.eddsa_pub);
- if (0 != GNUNET_memcmp (&rr->details.ok.reserve_pub,
- &ps->reserve_pub))
+ if ( (GNUNET_OK ==
+ TALER_amount_is_valid (&ps->amount)) &&
+ (0 != TALER_amount_cmp (&rc->amount,
+ &ps->amount)) )
{
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Recouped %s for coin %u, expected %s\n",
+ TALER_amount2s (&rc->amount),
+ ps->indices[i],
+ TALER_amount2s (&ps->amount));
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
- if (GNUNET_OK ==
- TALER_amount_is_valid (&ps->reserve_history.amount))
- ps->reserve_history.type = TALER_EXCHANGE_RTT_RECOUP;
- /* ps->reserve_history.details.recoup_details.coin_pub; // initialized earlier */
- ps->che.details.recoup.reserve_pub = ps->reserve_pub;
+ ps->ches[i].amount = rc->amount;
+ ps->rhes[i].amount = rc->amount;
+ ps->rhes[i].type = TALER_EXCHANGE_RTT_RECOUP;
}
break;
- case MHD_HTTP_NOT_FOUND:
- break;
- case MHD_HTTP_CONFLICT:
- break;
default:
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Unmanaged HTTP status code %u/%d.\n",
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Recoup-withdraw failed as expected with HTTP status %u/%d\n",
hr->http_status,
(int) hr->ec);
break;
@@ -188,134 +182,178 @@ recoup_run (void *cls,
{
struct RecoupState *ps = cls;
const struct TALER_TESTING_Command *coin_cmd;
- const struct TALER_CoinSpendPrivateKeyP *coin_priv;
- const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
- const struct TALER_DenominationSignature *coin_sig;
+ const struct TALER_ReservePublicKeyP *reserve_pub;
const struct TALER_WithdrawMasterSeedP *seed;
- const struct TALER_BlindingMasterSeedP *blinding_seed;
const struct TALER_HashBlindedPlanchetsP *h_planchets;
- struct TALER_PlanchetMasterSecretP secret;
- char *cref;
- unsigned int idx;
- const struct TALER_ExchangeBlindingValues *ewv;
- struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_BlindingMasterSeedP blinding_seed;
+ bool have_cs = false;
+ size_t num_coins = 0;
+ (void) cmd;
ps->is = is;
- if (GNUNET_OK !=
- TALER_TESTING_parse_coin_reference (
- ps->coin_reference,
- &cref,
- &idx))
- {
- TALER_TESTING_interpreter_fail (is);
- return;
- }
-
coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
- cref);
- GNUNET_free (cref);
-
+ ps->withdraw_reference);
if (NULL == coin_cmd)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_coin_priv (coin_cmd,
- idx,
- &coin_priv))
+ if ( (GNUNET_OK !=
+ TALER_TESTING_get_trait_reserve_pub (coin_cmd,
+ &reserve_pub)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_withdraw_seed (coin_cmd,
+ &seed)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_withdraw_commitment (coin_cmd,
+ &h_planchets)) )
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
- GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
- &ps->coin.eddsa_pub);
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_exchange_blinding_values (coin_cmd,
- idx,
- &ewv))
+ ps->reserve_pub = *reserve_pub;
+ /* count the coins of the withdraw operation */
+ while (num_coins < TALER_MAX_COINS)
{
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
+ const struct TALER_EXCHANGE_DenomPublicKey *pk;
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_pub (coin_cmd,
+ (unsigned int) num_coins,
+ &pk))
+ break;
+ num_coins++;
}
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_withdraw_seed (coin_cmd,
- &seed))
+ if (0 == num_coins)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_blinding_seed (coin_cmd,
- &blinding_seed))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- GNUNET_CRYPTO_eddsa_key_get_public (
- &coin_priv->eddsa_priv,
- &ps->reserve_history.details.recoup_details.coin_pub.eddsa_pub);
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_denom_pub (coin_cmd,
- idx,
- &denom_pub))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_denom_sig (coin_cmd,
- idx,
- &coin_sig))
+ for (size_t i = 0; i < ps->num_indices; i++)
+ if (ps->indices[i] >= num_coins)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Coin index %u out of range for command %s (%u coins)\n",
+ ps->indices[i],
+ ps->withdraw_reference,
+ (unsigned int) num_coins);
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ /* the CS blinding seed is derived from the withdraw seed */
+ TALER_cs_withdraw_seed_to_blinding_seed (seed,
+ &blinding_seed);
{
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
+ struct TALER_PlanchetMasterSecretP secrets[num_coins];
+ struct TALER_EXCHANGE_RecoupCoin coins[num_coins];
+ size_t k = 0;
+
+ TALER_withdraw_expand_secrets (num_coins,
+ seed,
+ secrets);
+ memset (coins,
+ 0,
+ sizeof (coins));
+ for (size_t i = 0; i < num_coins; i++)
+ {
+ struct TALER_EXCHANGE_RecoupCoin *c = &coins[i];
+ const struct TALER_AgeCommitmentHashP *hac = NULL;
+ bool recoup = false;
+
+ if ( (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_pub (coin_cmd,
+ (unsigned int) i,
+ &c->pk)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_exchange_blinding_values (
+ coin_cmd,
+ (unsigned int) i,
+ &c->exchange_vals)) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK ==
+ TALER_TESTING_get_trait_h_age_commitment (coin_cmd,
+ (unsigned int) i,
+ &hac))
+ c->h_age_commitment = hac; /* may be NULL */
+ c->ps = &secrets[i];
+ if (GNUNET_CRYPTO_BSA_CS == c->pk->key.bsign_pub_key->cipher)
+ have_cs = true;
+ for (size_t j = 0; j < ps->num_indices; j++)
+ if (ps->indices[j] == i)
+ recoup = true;
+ if (! recoup)
+ continue;
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_sig (coin_cmd,
+ (unsigned int) i,
+ &c->denom_sig))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ /* what the histories should show for this coin */
+ {
+ struct TALER_EXCHANGE_CoinHistoryEntry *che = &ps->ches[k];
+ struct TALER_EXCHANGE_ReserveHistoryEntry *rhe = &ps->rhes[k];
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ const struct TALER_ExchangeBlindingValues *vals = c->exchange_vals;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Trying to recoup coin %u of denomination '%s'\n",
+ (unsigned int) i,
+ TALER_B2S (&c->pk->h_key));
+ TALER_planchet_setup_coin_priv (c->ps,
+ vals,
+ &coin_priv);
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
+ &ps->coin_pubs[k].eddsa_pub);
+ che->type = TALER_EXCHANGE_CTT_RECOUP;
+ che->amount = ps->amount;
+ che->details.recoup.reserve_pub = ps->reserve_pub;
+ che->details.recoup.planchets_h = *h_planchets;
+ che->details.recoup.coin_index = (uint32_t) i;
+ TALER_planchet_blinding_secret_create (c->ps,
+ vals,
+ &che->details.recoup.coin_bks);
+ /* exercise the documented contract: the request needs no
+ exchange values for RSA denominations */
+ if (GNUNET_CRYPTO_BSA_RSA == c->pk->key.bsign_pub_key->cipher)
+ c->exchange_vals = NULL;
+ TALER_wallet_recoup_sign (&c->pk->h_key,
+ &che->details.recoup.coin_bks,
+ &coin_priv,
+ &che->details.recoup.coin_sig);
+ rhe->amount = ps->amount;
+ rhe->details.recoup_details.coin_pub = ps->coin_pubs[k];
+ rhe->details.recoup_details.planchets_h = *h_planchets;
+ k++;
+ }
+ }
+ ps->ph = TALER_EXCHANGE_post_recoup_withdraw_create (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ &ps->reserve_pub,
+ h_planchets,
+ have_cs ? &blinding_seed : NULL,
+ num_coins,
+ coins);
}
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_withdraw_commitment (coin_cmd,
- &h_planchets))
+ if (NULL == ps->ph)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Trying to recoup denomination '%s'\n",
- TALER_B2S (&denom_pub->h_key));
- ps->che.type = TALER_EXCHANGE_CTT_RECOUP;
- ps->che.amount = ps->reserve_history.amount;
- TALER_withdraw_expand_secrets (1,
- seed,
- &secret);
- TALER_planchet_blinding_secret_create (&secret,
- ewv,
- &ps->che.details.recoup.coin_bks);
- TALER_denom_pub_hash (&denom_pub->key,
- &h_denom_pub);
- TALER_wallet_recoup_sign (&h_denom_pub,
- &ps->che.details.recoup.coin_bks,
- coin_priv,
- &ps->che.details.recoup.coin_sig);
- ps->ph = TALER_EXCHANGE_post_recoup_withdraw_create (
- TALER_TESTING_interpreter_get_context (is),
- TALER_TESTING_get_exchange_url (is),
- TALER_TESTING_get_keys (is),
- denom_pub,
- coin_sig,
- ewv,
- blinding_seed,
- idx,
- &secret,
- h_planchets);
- GNUNET_assert (NULL != ps->ph);
GNUNET_assert (TALER_EC_NONE ==
TALER_EXCHANGE_post_recoup_withdraw_start (ps->ph,
&recoup_cb,
@@ -324,10 +362,9 @@ recoup_run (void *cls,
/**
- * Cleanup the "recoup" CMD state, and possibly cancel
- * a pending operation thereof.
+ * Cleanup the state.
*
- * @param cls closure.
+ * @param cls closure, must be a `struct RecoupState`.
* @param cmd the command which is being cleaned up.
*/
static void
@@ -335,23 +372,30 @@ recoup_cleanup (void *cls,
const struct TALER_TESTING_Command *cmd)
{
struct RecoupState *ps = cls;
+
+ (void) cmd;
if (NULL != ps->ph)
{
TALER_EXCHANGE_post_recoup_withdraw_cancel (ps->ph);
ps->ph = NULL;
}
+ GNUNET_free (ps->withdraw_reference);
+ GNUNET_free (ps->indices);
+ GNUNET_free (ps->ches);
+ GNUNET_free (ps->rhes);
+ GNUNET_free (ps->coin_pubs);
GNUNET_free (ps);
}
/**
- * Offer internal data from a "recoup" CMD state to other
- * commands.
+ * Offer internal data to other commands: one coin history entry
+ * and one reserve history entry per recouped coin.
*
* @param cls closure
* @param[out] ret result (could be anything)
* @param trait name of the trait
- * @param index index number of the object to offer.
+ * @param index index number of the object to extract.
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
@@ -362,17 +406,19 @@ recoup_traits (void *cls,
{
struct RecoupState *ps = cls;
- if (ps->reserve_history.type != TALER_EXCHANGE_RTT_RECOUP)
- return GNUNET_SYSERR; /* no traits */
+ if (index >= ps->num_indices)
+ return GNUNET_NO;
+ if (TALER_EXCHANGE_RTT_RECOUP != ps->rhes[index].type)
+ return GNUNET_SYSERR; /* request did not succeed: no traits */
{
struct TALER_TESTING_Trait traits[] = {
TALER_TESTING_make_trait_reserve_pub (&ps->reserve_pub),
- TALER_TESTING_make_trait_reserve_history (0,
- &ps->reserve_history),
- TALER_TESTING_make_trait_coin_history (0,
- &ps->che),
- TALER_TESTING_make_trait_coin_pub (0,
- &ps->coin),
+ TALER_TESTING_make_trait_reserve_history (index,
+ &ps->rhes[index]),
+ TALER_TESTING_make_trait_coin_history (index,
+ &ps->ches[index]),
+ TALER_TESTING_make_trait_coin_pub (index,
+ &ps->coin_pubs[index]),
TALER_TESTING_trait_end ()
};
@@ -384,20 +430,42 @@ recoup_traits (void *cls,
}
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_recoup (const char *label,
- unsigned int expected_response_code,
- const char *coin_reference,
- const char *amount)
+/**
+ * Create the command state shared by both constructors.
+ *
+ * @param label the command label
+ * @param expected_response_code expected HTTP status code
+ * @param withdraw_reference label of the withdraw command
+ * @param indices coins to recoup, becomes owned by the state
+ * @param num_indices length of @a indices
+ * @param amount expected amount per coin, NULL for none
+ * @return the command
+ */
+static struct TALER_TESTING_Command
+make_recoup_cmd (const char *label,
+ unsigned int expected_response_code,
+ const char *withdraw_reference,
+ unsigned int *indices,
+ size_t num_indices,
+ const char *amount)
{
struct RecoupState *ps;
ps = GNUNET_new (struct RecoupState);
ps->expected_response_code = expected_response_code;
- ps->coin_reference = coin_reference;
- if (GNUNET_OK !=
- TALER_string_to_amount (amount,
- &ps->reserve_history.amount))
+ ps->withdraw_reference = GNUNET_strdup (withdraw_reference);
+ ps->indices = indices;
+ ps->num_indices = num_indices;
+ ps->ches = GNUNET_new_array (num_indices,
+ struct TALER_EXCHANGE_CoinHistoryEntry);
+ ps->rhes = GNUNET_new_array (num_indices,
+ struct TALER_EXCHANGE_ReserveHistoryEntry);
+ ps->coin_pubs = GNUNET_new_array (num_indices,
+ struct TALER_CoinSpendPublicKeyP);
+ if ( (NULL != amount) &&
+ (GNUNET_OK !=
+ TALER_string_to_amount (amount,
+ &ps->amount)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to parse amount `%s' at %s\n",
@@ -417,3 +485,57 @@ TALER_TESTING_cmd_recoup (const char *label,
return cmd;
}
}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_recoup (const char *label,
+ unsigned int expected_response_code,
+ const char *coin_reference,
+ const char *amount)
+{
+ char *cref;
+ unsigned int idx;
+ unsigned int *indices;
+ struct TALER_TESTING_Command cmd;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_parse_coin_reference (coin_reference,
+ &cref,
+ &idx));
+ indices = GNUNET_new_array (1,
+ unsigned int);
+ indices[0] = idx;
+ cmd = make_recoup_cmd (label,
+ expected_response_code,
+ cref,
+ indices,
+ 1,
+ amount);
+ GNUNET_free (cref);
+ return cmd;
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_recoup_batch (const char *label,
+ unsigned int expected_response_code,
+ const char *withdraw_reference,
+ const char *indices,
+ const char *amount)
+{
+ unsigned int *idx;
+ size_t num;
+
+ idx = TALER_TESTING_parse_index_list (indices,
+ &num);
+ GNUNET_assert (NULL != idx);
+ return make_recoup_cmd (label,
+ expected_response_code,
+ withdraw_reference,
+ idx,
+ num,
+ amount);
+}
+
+
+/* end of testing_api_cmd_recoup.c */
diff --git a/src/testing/testing_api_cmd_recoup_refresh.c b/src/testing/testing_api_cmd_recoup_refresh.c
@@ -1,37 +1,33 @@
/*
This file is part of TALER
- Copyright (C) 2014-2022 Taler Systems SA
+ Copyright (C) 2018-2026 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 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.
+ 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/>
+ 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 testing/testing_api_cmd_recoup_refresh.c
* @brief Implement the /recoup-refresh test command.
* @author Marcello Stanisci
+ * @author Christian Grothoff
+ * @author Özgür Kesim
*/
+#include "platform.h"
#include "taler/taler_json_lib.h"
#include <gnunet/gnunet_curl_lib.h>
-struct RecoupRefreshState;
-#define TALER_EXCHANGE_POST_RECOUP_REFRESH_RESULT_CLOSURE struct \
- RecoupRefreshState
-#include "taler/exchange/post-recoup-refresh.h"
#include "taler/taler_testing_lib.h"
/**
- * State for a "pay back" CMD.
+ * State for a "recoup-refresh" CMD.
*/
struct RecoupRefreshState
{
@@ -41,71 +37,78 @@ struct RecoupRefreshState
unsigned int expected_response_code;
/**
- * Command that offers a reserve private key,
- * plus a coin to be paid back.
+ * Label of the reveal-melt command the fresh coins came from.
*/
- const char *coin_reference;
+ char *reveal_reference;
/**
- * Entry in the old coin's history generated by this operation.
+ * Label of the melt command.
*/
- struct TALER_EXCHANGE_CoinHistoryEntry che_old;
-
- /**
- * Entry in the recouped coin's history generated by this operation.
- */
- struct TALER_EXCHANGE_CoinHistoryEntry che_new;
+ const char *melt_reference;
/**
- * Public key of the refunded coin.
+ * Indices (in the reveal command) of the coins to recoup.
*/
- struct TALER_CoinSpendPublicKeyP coin_pub_old;
+ unsigned int *indices;
/**
- * Public key of the refunded coin.
+ * Number of entries in @e indices, @e ches_new, @e ches_old
+ * and @e coin_pubs.
*/
- struct TALER_CoinSpendPublicKeyP coin_pub_new;
+ size_t num_indices;
/**
- * Amount to be recouped.
+ * Amount we expect to be recouped per coin, invalid if
+ * not specified.
*/
struct TALER_Amount amount;
/**
- * The interpreter state.
+ * Interpreter state.
*/
struct TALER_TESTING_Interpreter *is;
/**
- * Handle to the ongoing operation.
+ * Handle to the request.
*/
struct TALER_EXCHANGE_PostRecoupRefreshHandle *ph;
/**
- * NULL if coin was not refreshed, otherwise reference
- * to the melt operation underlying @a coin_reference.
+ * Old coin that is credited.
*/
- const char *melt_reference;
+ struct TALER_CoinSpendPublicKeyP old_coin_pub;
+ /**
+ * Coin history entries we expect for the recouped coins.
+ */
+ struct TALER_EXCHANGE_CoinHistoryEntry *ches_new;
+
+ /**
+ * Coin history entries we expect for the old coin, one per
+ * recouped coin.
+ */
+ struct TALER_EXCHANGE_CoinHistoryEntry *ches_old;
+
+ /**
+ * Public keys of the recouped coins.
+ */
+ struct TALER_CoinSpendPublicKeyP *coin_pubs;
};
/**
- * Check the result of the recoup_refresh request: checks whether
- * the HTTP response code is good, and that the coin that
- * was paid back belonged to the right old coin.
+ * Check the response.
*
- * @param rrs closure
+ * @param cls closure, our `struct RecoupRefreshState`
* @param rrr response details
*/
static void
-recoup_refresh_cb (struct RecoupRefreshState *rrs,
+recoup_refresh_cb (void *cls,
const struct TALER_EXCHANGE_PostRecoupRefreshResponse *rrr)
{
+ struct RecoupRefreshState *rrs = cls;
const struct TALER_EXCHANGE_HttpResponse *hr = &rrr->hr;
struct TALER_TESTING_Interpreter *is = rrs->is;
- char *cref;
- unsigned int idx;
rrs->ph = NULL;
if (rrs->expected_response_code != hr->http_status)
@@ -115,67 +118,54 @@ recoup_refresh_cb (struct RecoupRefreshState *rrs,
rrs->expected_response_code);
return;
}
-
- if (GNUNET_OK !=
- TALER_TESTING_parse_coin_reference (
- rrs->coin_reference,
- &cref,
- &idx))
- {
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- (void) idx; /* do NOT use! We ignore 'idx', must be 0 for melt! */
-
- GNUNET_free (cref);
switch (hr->http_status)
{
case MHD_HTTP_OK:
- /* check old_coin_pub */
+ if (0 != GNUNET_memcmp (&rrr->details.ok.old_coin_pub,
+ &rrs->old_coin_pub))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (rrr->details.ok.num_recoups != rrs->num_indices)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ for (size_t i = 0; i < rrs->num_indices; i++)
{
- const struct TALER_TESTING_Command *melt_cmd;
- const struct TALER_CoinSpendPrivateKeyP *dirty_priv;
- struct TALER_CoinSpendPublicKeyP oc;
+ const struct TALER_RecoupedCoin *rc = &rrr->details.ok.recoups[i];
- melt_cmd = TALER_TESTING_interpreter_lookup_command (is,
- rrs->melt_reference);
- if (NULL == melt_cmd)
+ if (0 != GNUNET_memcmp (&rc->coin_pub,
+ &rrs->coin_pubs[i]))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_coin_priv (melt_cmd,
- 0,
- &dirty_priv))
+ if ( (GNUNET_OK ==
+ TALER_amount_is_valid (&rrs->amount)) &&
+ (0 != TALER_amount_cmp (&rc->amount,
+ &rrs->amount)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Coin %u not found in command %s\n",
- 0,
- rrs->melt_reference);
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- GNUNET_CRYPTO_eddsa_key_get_public (&dirty_priv->eddsa_priv,
- &oc.eddsa_pub);
- if (0 != GNUNET_memcmp (&oc,
- &rrr->details.ok.old_coin_pub))
- {
+ "Recouped %s for coin %u, expected %s\n",
+ TALER_amount2s (&rc->amount),
+ rrs->indices[i],
+ TALER_amount2s (&rrs->amount));
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
+ rrs->ches_new[i].amount = rc->amount;
+ rrs->ches_old[i].amount = rc->amount;
}
break;
- case MHD_HTTP_NOT_FOUND:
- break;
- case MHD_HTTP_CONFLICT:
- break;
default:
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Unmanaged HTTP status code %u/%d.\n",
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Recoup-refresh failed as expected with HTTP status %u/%d\n",
hr->http_status,
(int) hr->ec);
break;
@@ -197,153 +187,174 @@ recoup_refresh_run (void *cls,
struct TALER_TESTING_Interpreter *is)
{
struct RecoupRefreshState *rrs = cls;
- const struct TALER_TESTING_Command *coin_cmd;
+ const struct TALER_TESTING_Command *reveal_cmd;
const struct TALER_TESTING_Command *melt_cmd;
- const struct TALER_CoinSpendPrivateKeyP *coin_priv;
- const struct TALER_CoinSpendPrivateKeyP *coin_priv_old;
- const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
- const struct TALER_DenominationSignature *coin_sig;
- const struct TALER_PublicRefreshMasterSeedP *rms;
- const struct TALER_PlanchetMasterSecretP *planchet;
- const struct TALER_ExchangeBlindingValues *ewv;
- char *cref;
- unsigned int idx;
- struct TALER_DenominationHashP h_denom_pub;
+ const struct TALER_CoinSpendPublicKeyP *old_coin_pub;
+ const struct TALER_RefreshCommitmentP *rc;
+ const struct TALER_BlindingMasterSeedP *blinding_seed = NULL;
+ const unsigned int *num_coins_p;
+ size_t num_coins;
+ (void) cmd;
rrs->is = is;
- if (GNUNET_OK !=
- TALER_TESTING_parse_coin_reference (
- rrs->coin_reference,
- &cref,
- &idx))
- {
- TALER_TESTING_interpreter_fail (is);
- return;
- }
-
- coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
- cref);
- GNUNET_free (cref);
- if (NULL == coin_cmd)
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
+ reveal_cmd = TALER_TESTING_interpreter_lookup_command (is,
+ rrs->reveal_reference);
melt_cmd = TALER_TESTING_interpreter_lookup_command (is,
rrs->melt_reference);
- if (NULL == melt_cmd)
+ if ( (NULL == reveal_cmd) ||
+ (NULL == melt_cmd) )
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_coin_priv (coin_cmd,
- idx,
- &coin_priv))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_coin_priv (melt_cmd,
- 0,
- &coin_priv_old))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- GNUNET_CRYPTO_eddsa_key_get_public (
- &coin_priv->eddsa_priv,
- &rrs->coin_pub_new.eddsa_pub);
- GNUNET_CRYPTO_eddsa_key_get_public (
- &coin_priv_old->eddsa_priv,
- &rrs->coin_pub_old.eddsa_pub);
-
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_exchange_blinding_values (melt_cmd,
- idx,
- &ewv))
+ if ( (GNUNET_OK !=
+ TALER_TESTING_get_trait_coin_pub (melt_cmd,
+ 0,
+ &old_coin_pub)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_refresh_commitment (melt_cmd,
+ &rc)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_array_length (reveal_cmd,
+ &num_coins_p)) )
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
+ /* NULL if the melt used no CS denomination */
if (GNUNET_OK !=
- TALER_TESTING_get_trait_planchet_secrets (coin_cmd,
- idx,
- &planchet))
+ TALER_TESTING_get_trait_blinding_seed (melt_cmd,
+ &blinding_seed))
+ blinding_seed = NULL;
+ rrs->old_coin_pub = *old_coin_pub;
+ num_coins = *num_coins_p;
+ if (0 == num_coins)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_refresh_seed (melt_cmd,
- &rms))
- {
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
- }
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_denom_pub (coin_cmd,
- idx,
- &denom_pub))
+ for (size_t i = 0; i < rrs->num_indices; i++)
+ if (rrs->indices[i] >= num_coins)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Coin index %u out of range for command %s (%u coins)\n",
+ rrs->indices[i],
+ rrs->reveal_reference,
+ (unsigned int) num_coins);
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
{
- GNUNET_break (0);
- TALER_TESTING_interpreter_fail (is);
- return;
+ struct TALER_EXCHANGE_RecoupCoin coins[num_coins];
+ size_t k = 0;
+
+ memset (coins,
+ 0,
+ sizeof (coins));
+ for (size_t i = 0; i < num_coins; i++)
+ {
+ struct TALER_EXCHANGE_RecoupCoin *c = &coins[i];
+ const struct TALER_AgeCommitmentHashP *hac = NULL;
+ bool recoup = false;
+
+ if ( (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_pub (reveal_cmd,
+ (unsigned int) i,
+ &c->pk)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_planchet_secrets (reveal_cmd,
+ (unsigned int) i,
+ &c->ps)) ||
+ (GNUNET_OK !=
+ TALER_TESTING_get_trait_exchange_blinding_values (
+ melt_cmd,
+ (unsigned int) i,
+ &c->exchange_vals)) )
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ if (GNUNET_OK ==
+ TALER_TESTING_get_trait_h_age_commitment (reveal_cmd,
+ (unsigned int) i,
+ &hac))
+ c->h_age_commitment = hac; /* may be NULL */
+ for (size_t j = 0; j < rrs->num_indices; j++)
+ if (rrs->indices[j] == i)
+ recoup = true;
+ if (! recoup)
+ continue;
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_denom_sig (reveal_cmd,
+ (unsigned int) i,
+ &c->denom_sig))
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+ /* what the histories should show for this coin and the old coin */
+ {
+ struct TALER_EXCHANGE_CoinHistoryEntry *che_new = &rrs->ches_new[k];
+ struct TALER_EXCHANGE_CoinHistoryEntry *che_old = &rrs->ches_old[k];
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ const struct TALER_ExchangeBlindingValues *vals = c->exchange_vals;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Trying to recoup-refresh coin %u of denomination '%s'\n",
+ (unsigned int) i,
+ TALER_B2S (&c->pk->h_key));
+ TALER_planchet_setup_coin_priv (c->ps,
+ vals,
+ &coin_priv);
+ GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
+ &rrs->coin_pubs[k].eddsa_pub);
+ che_new->type = TALER_EXCHANGE_CTT_RECOUP_REFRESH;
+ che_new->amount = rrs->amount;
+ che_new->details.recoup_refresh.old_coin_pub = rrs->old_coin_pub;
+ che_new->details.recoup_refresh.rc = *rc;
+ che_new->details.recoup_refresh.coin_index = (uint32_t) i;
+ TALER_planchet_blinding_secret_create (
+ c->ps,
+ vals,
+ &che_new->details.recoup_refresh.coin_bks);
+ /* exercise the documented contract: the request needs no
+ exchange values for RSA denominations */
+ if (GNUNET_CRYPTO_BSA_RSA == c->pk->key.bsign_pub_key->cipher)
+ c->exchange_vals = NULL;
+ TALER_wallet_recoup_refresh_sign (
+ &c->pk->h_key,
+ &che_new->details.recoup_refresh.coin_bks,
+ &coin_priv,
+ &che_new->details.recoup_refresh.coin_sig);
+ che_old->type = TALER_EXCHANGE_CTT_OLD_COIN_RECOUP;
+ che_old->amount = rrs->amount;
+ che_old->details.old_coin_recoup.new_coin_pub = rrs->coin_pubs[k];
+ che_old->details.old_coin_recoup.rc = *rc;
+ k++;
+ }
+ }
+ rrs->ph = TALER_EXCHANGE_post_recoup_refresh_create (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_exchange_url (is),
+ TALER_TESTING_get_keys (is),
+ &rrs->old_coin_pub,
+ rc,
+ blinding_seed,
+ num_coins,
+ coins);
}
- if (GNUNET_OK !=
- TALER_TESTING_get_trait_denom_sig (coin_cmd,
- idx,
- &coin_sig))
+ if (NULL == rrs->ph)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (is);
return;
}
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Trying to recoup_refresh denomination '%s'\n",
- TALER_B2S (&denom_pub->h_key));
- rrs->che_old.type
- = TALER_EXCHANGE_CTT_OLD_COIN_RECOUP;
- rrs->che_old.amount
- = rrs->amount;
- rrs->che_old.details.old_coin_recoup.new_coin_pub
- = rrs->coin_pub_new;
- rrs->che_new.type
- = TALER_EXCHANGE_CTT_RECOUP_REFRESH;
- rrs->che_new.amount
- = rrs->amount;
- rrs->che_new.details.recoup_refresh.old_coin_pub
- = rrs->coin_pub_old;
- TALER_planchet_blinding_secret_create (
- planchet,
- ewv,
- &rrs->che_new.details.recoup_refresh.coin_bks);
- TALER_denom_pub_hash (&denom_pub->key,
- &h_denom_pub);
- TALER_wallet_recoup_refresh_sign (
- &h_denom_pub,
- &rrs->che_new.details.recoup_refresh.coin_bks,
- coin_priv,
- &rrs->che_new.details.recoup_refresh.coin_sig);
- rrs->ph = TALER_EXCHANGE_post_recoup_refresh_create (
- TALER_TESTING_interpreter_get_context (is),
- TALER_TESTING_get_exchange_url (is),
- TALER_TESTING_get_keys (is),
- denom_pub,
- coin_sig,
- ewv,
- rms,
- planchet,
- idx);
- GNUNET_assert (NULL != rrs->ph);
GNUNET_assert (TALER_EC_NONE ==
TALER_EXCHANGE_post_recoup_refresh_start (rrs->ph,
&recoup_refresh_cb,
@@ -352,10 +363,9 @@ recoup_refresh_run (void *cls,
/**
- * Cleanup the "recoup_refresh" CMD state, and possibly cancel
- * a pending operation thereof.
+ * Cleanup the state.
*
- * @param cls closure.
+ * @param cls closure, must be a `struct RecoupRefreshState`.
* @param cmd the command which is being cleaned up.
*/
static void
@@ -363,23 +373,32 @@ recoup_refresh_cleanup (void *cls,
const struct TALER_TESTING_Command *cmd)
{
struct RecoupRefreshState *rrs = cls;
+
+ (void) cmd;
if (NULL != rrs->ph)
{
TALER_EXCHANGE_post_recoup_refresh_cancel (rrs->ph);
rrs->ph = NULL;
}
+ GNUNET_free (rrs->reveal_reference);
+ GNUNET_free (rrs->indices);
+ GNUNET_free (rrs->ches_new);
+ GNUNET_free (rrs->ches_old);
+ GNUNET_free (rrs->coin_pubs);
GNUNET_free (rrs);
}
/**
- * Offer internal data from a "recoup-refresh" CMD state to other
- * commands.
+ * Offer internal data to other commands: for index k below the
+ * number of recouped coins, the recouped coin and its history
+ * entry; for the following indices, the old coin and its history
+ * entry for the respective recoup.
*
* @param cls closure
* @param[out] ret result (could be anything)
* @param trait name of the trait
- * @param index index number of the object to offer.
+ * @param index index number of the object to extract.
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
@@ -389,41 +408,76 @@ recoup_refresh_traits (void *cls,
unsigned int index)
{
struct RecoupRefreshState *rrs = cls;
- struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_coin_history (0,
- &rrs->che_old),
- TALER_TESTING_make_trait_coin_pub (0,
- &rrs->coin_pub_old),
- TALER_TESTING_make_trait_coin_history (1,
- &rrs->che_new),
- TALER_TESTING_make_trait_coin_pub (1,
- &rrs->coin_pub_new),
- TALER_TESTING_trait_end ()
- };
-
- return TALER_TESTING_get_trait (traits,
- ret,
- trait,
- index);
+ size_t k;
+ bool old;
+
+ if (index >= 2 * rrs->num_indices)
+ return GNUNET_NO;
+ old = (index >= rrs->num_indices);
+ k = old ? index - rrs->num_indices : index;
+ if (GNUNET_OK !=
+ TALER_amount_is_valid (&rrs->ches_new[k].amount))
+ return GNUNET_SYSERR; /* request did not succeed: no traits */
+ {
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_coin_history (index,
+ old
+ ? &rrs->ches_old[k]
+ : &rrs->ches_new[k]),
+ TALER_TESTING_make_trait_coin_pub (index,
+ old
+ ? &rrs->old_coin_pub
+ : &rrs->coin_pubs[k]),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+ }
}
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_recoup_refresh (const char *label,
- unsigned int expected_response_code,
- const char *coin_reference,
- const char *melt_reference,
- const char *amount)
+/**
+ * Create the command state shared by both constructors.
+ *
+ * @param label the command label
+ * @param expected_response_code expected HTTP status code
+ * @param reveal_reference label of the reveal-melt command
+ * @param indices coins to recoup, becomes owned by the state
+ * @param num_indices length of @a indices
+ * @param melt_reference label of the melt command
+ * @param amount expected amount per coin, NULL for none
+ * @return the command
+ */
+static struct TALER_TESTING_Command
+make_recoup_refresh_cmd (const char *label,
+ unsigned int expected_response_code,
+ const char *reveal_reference,
+ unsigned int *indices,
+ size_t num_indices,
+ const char *melt_reference,
+ const char *amount)
{
struct RecoupRefreshState *rrs;
rrs = GNUNET_new (struct RecoupRefreshState);
rrs->expected_response_code = expected_response_code;
- rrs->coin_reference = coin_reference;
+ rrs->reveal_reference = GNUNET_strdup (reveal_reference);
rrs->melt_reference = melt_reference;
- if (GNUNET_OK !=
- TALER_string_to_amount (amount,
- &rrs->amount))
+ rrs->indices = indices;
+ rrs->num_indices = num_indices;
+ rrs->ches_new = GNUNET_new_array (num_indices,
+ struct TALER_EXCHANGE_CoinHistoryEntry);
+ rrs->ches_old = GNUNET_new_array (num_indices,
+ struct TALER_EXCHANGE_CoinHistoryEntry);
+ rrs->coin_pubs = GNUNET_new_array (num_indices,
+ struct TALER_CoinSpendPublicKeyP);
+ if ( (NULL != amount) &&
+ (GNUNET_OK !=
+ TALER_string_to_amount (amount,
+ &rrs->amount)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to parse amount `%s' at %s\n",
@@ -443,3 +497,61 @@ TALER_TESTING_cmd_recoup_refresh (const char *label,
return cmd;
}
}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_recoup_refresh (const char *label,
+ unsigned int expected_response_code,
+ const char *coin_reference,
+ const char *melt_reference,
+ const char *amount)
+{
+ char *cref;
+ unsigned int idx;
+ unsigned int *indices;
+ struct TALER_TESTING_Command cmd;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_parse_coin_reference (coin_reference,
+ &cref,
+ &idx));
+ indices = GNUNET_new_array (1,
+ unsigned int);
+ indices[0] = idx;
+ cmd = make_recoup_refresh_cmd (label,
+ expected_response_code,
+ cref,
+ indices,
+ 1,
+ melt_reference,
+ amount);
+ GNUNET_free (cref);
+ return cmd;
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_recoup_refresh_batch (const char *label,
+ unsigned int expected_response_code,
+ const char *reveal_reference,
+ const char *indices,
+ const char *melt_reference,
+ const char *amount)
+{
+ unsigned int *idx;
+ size_t num;
+
+ idx = TALER_TESTING_parse_index_list (indices,
+ &num);
+ GNUNET_assert (NULL != idx);
+ return make_recoup_refresh_cmd (label,
+ expected_response_code,
+ reveal_reference,
+ idx,
+ num,
+ melt_reference,
+ amount);
+}
+
+
+/* end of testing_api_cmd_recoup_refresh.c */
diff --git a/src/testing/testing_api_cmd_refresh.c b/src/testing/testing_api_cmd_refresh.c
@@ -1049,6 +1049,10 @@ melt_traits (void *cls,
index,
rms->melt_input.melt_h_age_commitment),
TALER_TESTING_make_trait_refresh_seed (&rms->rms),
+ TALER_TESTING_make_trait_refresh_commitment (&rms->rc),
+ TALER_TESTING_make_trait_blinding_seed (rms->no_blinding_seed
+ ? NULL
+ : &rms->blinding_seed),
(NULL != rms->reveal_melt_input.blinding_values)
? TALER_TESTING_make_trait_exchange_blinding_values (
index,
diff --git a/src/testing/testing_api_misc.c b/src/testing/testing_api_misc.c
@@ -398,3 +398,39 @@ TALER_TESTING_url_port_free (const char *url)
}
return GNUNET_OK;
}
+
+
+unsigned int *
+TALER_TESTING_parse_index_list (const char *indices,
+ size_t *num_indices)
+{
+ unsigned int *ret;
+ size_t num = 1;
+
+ for (const char *p = indices; '\0' != *p; p++)
+ if (',' == *p)
+ num++;
+ ret = GNUNET_new_array (num,
+ unsigned int);
+ *num_indices = 0;
+ for (const char *p = indices; *num_indices < num; )
+ {
+ char *end;
+ unsigned long v;
+
+ v = strtoul (p,
+ &end,
+ 10);
+ if ( (end == p) ||
+ ( (',' != *end) && ('\0' != *end) ) )
+ {
+ GNUNET_free (ret);
+ return NULL;
+ }
+ ret[(*num_indices)++] = (unsigned int) v;
+ if ('\0' == *end)
+ break;
+ p = end + 1;
+ }
+ return ret;
+}