aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/Makefile.am13
-rw-r--r--src/lib/exchange_api_common.c416
-rw-r--r--src/lib/exchange_api_deposits_get.c (renamed from src/lib/exchange_api_track_transaction.c)12
-rw-r--r--src/lib/exchange_api_link.c (renamed from src/lib/exchange_api_refresh_link.c)172
-rw-r--r--src/lib/exchange_api_melt.c505
-rw-r--r--src/lib/exchange_api_refresh.c1770
-rw-r--r--src/lib/exchange_api_refresh_common.c631
-rw-r--r--src/lib/exchange_api_refresh_common.h240
-rw-r--r--src/lib/exchange_api_refreshes_reveal.c512
-rw-r--r--src/lib/exchange_api_reserve.c1306
-rw-r--r--src/lib/exchange_api_reserves_get.c308
-rw-r--r--src/lib/exchange_api_transfers_get.c (renamed from src/lib/exchange_api_track_transfer.c)32
-rw-r--r--src/lib/exchange_api_withdraw.c611
13 files changed, 3337 insertions, 3191 deletions
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index f03522a69..87f343094 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -25,13 +25,16 @@ libtalerexchange_la_SOURCES = \
exchange_api_common.c \
exchange_api_handle.c exchange_api_handle.h \
exchange_api_deposit.c \
+ exchange_api_deposits_get.c \
+ exchange_api_link.c \
+ exchange_api_melt.c \
exchange_api_recoup.c \
- exchange_api_refresh.c \
- exchange_api_refresh_link.c \
+ exchange_api_refresh_common.c exchange_api_refresh_common.h \
+ exchange_api_refreshes_reveal.c \
exchange_api_refund.c \
- exchange_api_reserve.c \
- exchange_api_track_transaction.c \
- exchange_api_track_transfer.c \
+ exchange_api_reserves_get.c \
+ exchange_api_transfers_get.c \
+ exchange_api_withdraw.c \
exchange_api_wire.c
libtalerexchange_la_LIBADD = \
libtalerauditor.la \
diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c
index d5f31b064..6b0d638e8 100644
--- a/src/lib/exchange_api_common.c
+++ b/src/lib/exchange_api_common.c
@@ -27,6 +27,422 @@
/**
+ * Parse history given in JSON format and return it in binary
+ * format.
+ *
+ * @param exchange connection to the exchange we can use
+ * @param history JSON array with the history
+ * @param reserve_pub public key of the reserve to inspect
+ * @param currency currency we expect the balance to be in
+ * @param[out] balance final balance
+ * @param history_length number of entries in @a history
+ * @param[out] rhistory array of length @a history_length, set to the
+ * parsed history entries
+ * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
+ * were set,
+ * #GNUNET_SYSERR if there was a protocol violation in @a history
+ */
+int
+TALER_EXCHANGE_parse_reserve_history (struct TALER_EXCHANGE_Handle *exchange,
+ const json_t *history,
+ const struct
+ TALER_ReservePublicKeyP *reserve_pub,
+ const char *currency,
+ struct TALER_Amount *balance,
+ unsigned int history_length,
+ struct TALER_EXCHANGE_ReserveHistory *
+ rhistory)
+{
+ struct GNUNET_HashCode uuid[history_length];
+ unsigned int uuid_off;
+ struct TALER_Amount total_in;
+ struct TALER_Amount total_out;
+ size_t off;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &total_in));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (currency,
+ &total_out));
+ uuid_off = 0;
+ for (off = 0; off<history_length; off++)
+ {
+ json_t *transaction;
+ struct TALER_Amount amount;
+ const char *type;
+ struct GNUNET_JSON_Specification hist_spec[] = {
+ GNUNET_JSON_spec_string ("type", &type),
+ TALER_JSON_spec_amount ("amount",
+ &amount),
+ /* 'wire' and 'signature' are optional depending on 'type'! */
+ GNUNET_JSON_spec_end ()
+ };
+
+ transaction = json_array_get (history,
+ off);
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ hist_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rhistory[off].amount = amount;
+
+ if (0 == strcasecmp (type,
+ "DEPOSIT"))
+ {
+ const char *wire_url;
+ void *wire_reference;
+ size_t wire_reference_size;
+ struct GNUNET_TIME_Absolute timestamp;
+
+ struct GNUNET_JSON_Specification withdraw_spec[] = {
+ GNUNET_JSON_spec_varsize ("wire_reference",
+ &wire_reference,
+ &wire_reference_size),
+ GNUNET_JSON_spec_absolute_time ("timestamp",
+ &timestamp),
+ GNUNET_JSON_spec_string ("sender_account_url",
+ &wire_url),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rhistory[off].type = TALER_EXCHANGE_RTT_DEPOSIT;
+ if (GNUNET_OK !=
+ TALER_amount_add (&total_in,
+ &total_in,
+ &amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ withdraw_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rhistory[off].details.in_details.sender_url = GNUNET_strdup (wire_url);
+ rhistory[off].details.in_details.wire_reference = wire_reference;
+ rhistory[off].details.in_details.wire_reference_size =
+ wire_reference_size;
+ rhistory[off].details.in_details.timestamp = timestamp;
+ /* end type==DEPOSIT */
+ }
+ else if (0 == strcasecmp (type,
+ "WITHDRAW"))
+ {
+ struct TALER_ReserveSignatureP sig;
+ struct TALER_WithdrawRequestPS withdraw_purpose;
+ struct GNUNET_JSON_Specification withdraw_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &sig),
+ TALER_JSON_spec_amount_nbo ("withdraw_fee",
+ &withdraw_purpose.withdraw_fee),
+ GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+ &withdraw_purpose.h_denomination_pub),
+ GNUNET_JSON_spec_fixed_auto ("h_coin_envelope",
+ &withdraw_purpose.h_coin_envelope),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rhistory[off].type = TALER_EXCHANGE_RTT_WITHDRAWAL;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ withdraw_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ withdraw_purpose.purpose.size
+ = htonl (sizeof (withdraw_purpose));
+ withdraw_purpose.purpose.purpose
+ = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
+ withdraw_purpose.reserve_pub = *reserve_pub;
+ TALER_amount_hton (&withdraw_purpose.amount_with_fee,
+ &amount);
+ /* Check that the signature is a valid withdraw request */
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
+ &withdraw_purpose.purpose,
+ &sig.eddsa_signature,
+ &reserve_pub->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ /* check that withdraw fee matches expectations! */
+ {
+ const struct TALER_EXCHANGE_Keys *key_state;
+ const struct TALER_EXCHANGE_DenomPublicKey *dki;
+ struct TALER_Amount fee;
+
+ key_state = TALER_EXCHANGE_get_keys (exchange);
+ dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
+ &withdraw_purpose.
+ h_denomination_pub);
+ TALER_amount_ntoh (&fee,
+ &withdraw_purpose.withdraw_fee);
+ if ( (GNUNET_YES !=
+ TALER_amount_cmp_currency (&fee,
+ &dki->fee_withdraw)) ||
+ (0 !=
+ TALER_amount_cmp (&fee,
+ &dki->fee_withdraw)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ }
+ rhistory[off].details.out_authorization_sig
+ = json_object_get (transaction,
+ "signature");
+ /* Check check that the same withdraw transaction
+ isn't listed twice by the exchange. We use the
+ "uuid" array to remember the hashes of all
+ purposes, and compare the hashes to find
+ duplicates. *///
+ GNUNET_CRYPTO_hash (&withdraw_purpose,
+ ntohl (withdraw_purpose.purpose.size),
+ &uuid[uuid_off]);
+ for (unsigned int i = 0; i<uuid_off; i++)
+ {
+ if (0 == GNUNET_memcmp (&uuid[uuid_off],
+ &uuid[i]))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ }
+ uuid_off++;
+
+ if (GNUNET_OK !=
+ TALER_amount_add (&total_out,
+ &total_out,
+ &amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (withdraw_spec);
+ return GNUNET_SYSERR;
+ }
+ /* end type==WITHDRAW */
+ }
+ else if (0 == strcasecmp (type,
+ "RECOUP"))
+ {
+ struct TALER_RecoupConfirmationPS pc;
+ struct GNUNET_TIME_Absolute timestamp;
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct GNUNET_JSON_Specification recoup_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &pc.coin_pub),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rhistory[off].details.recoup_details.
+ exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rhistory[off].details.recoup_details.
+ exchange_pub),
+ GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
+ &pc.timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rhistory[off].type = TALER_EXCHANGE_RTT_RECOUP;
+ rhistory[off].amount = amount;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ recoup_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ rhistory[off].details.recoup_details.coin_pub = pc.coin_pub;
+ TALER_amount_hton (&pc.recoup_amount,
+ &amount);
+ pc.purpose.size = htonl (sizeof (pc));
+ pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP);
+ pc.reserve_pub = *reserve_pub;
+ timestamp = GNUNET_TIME_absolute_ntoh (pc.timestamp);
+ rhistory[off].details.recoup_details.timestamp = timestamp;
+
+ key_state = TALER_EXCHANGE_get_keys (exchange);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (key_state,
+ &rhistory[off].details.
+ recoup_details.exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP,
+ &pc.purpose,
+ &rhistory[off].details.recoup_details.
+ exchange_sig.eddsa_signature,
+ &rhistory[off].details.recoup_details.
+ exchange_pub.eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_amount_add (&total_in,
+ &total_in,
+ &rhistory[off].amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* end type==RECOUP */
+ }
+ else if (0 == strcasecmp (type,
+ "CLOSING"))
+ {
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct TALER_ReserveCloseConfirmationPS rcc;
+ struct GNUNET_TIME_Absolute timestamp;
+ struct GNUNET_JSON_Specification closing_spec[] = {
+ GNUNET_JSON_spec_string ("receiver_account_details",
+ &rhistory[off].details.close_details.
+ receiver_account_details),
+ GNUNET_JSON_spec_fixed_auto ("wtid",
+ &rhistory[off].details.close_details.wtid),
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+ &rhistory[off].details.close_details.
+ exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &rhistory[off].details.close_details.
+ exchange_pub),
+ TALER_JSON_spec_amount_nbo ("closing_fee",
+ &rcc.closing_fee),
+ GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
+ &rcc.timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rhistory[off].type = TALER_EXCHANGE_RTT_CLOSE;
+ rhistory[off].amount = amount;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (transaction,
+ closing_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ TALER_amount_hton (&rcc.closing_amount,
+ &amount);
+ GNUNET_CRYPTO_hash (
+ rhistory[off].details.close_details.receiver_account_details,
+ strlen (
+ rhistory[off].details.close_details.receiver_account_details) + 1,
+ &rcc.h_wire);
+ rcc.wtid = rhistory[off].details.close_details.wtid;
+ rcc.purpose.size = htonl (sizeof (rcc));
+ rcc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED);
+ rcc.reserve_pub = *reserve_pub;
+ timestamp = GNUNET_TIME_absolute_ntoh (rcc.timestamp);
+ rhistory[off].details.close_details.timestamp = timestamp;
+
+ key_state = TALER_EXCHANGE_get_keys (exchange);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (key_state,
+ &rhistory[off].details.close_details.
+ exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED,
+ &rcc.purpose,
+ &rhistory[off].details.close_details.
+ exchange_sig.eddsa_signature,
+ &rhistory[off].details.close_details.
+ exchange_pub.eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_amount_add (&total_out,
+ &total_out,
+ &rhistory[off].amount))
+ {
+ /* overflow in history already!? inconceivable! Bad exchange! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* end type==CLOSING */
+ }
+ else
+ {
+ /* unexpected 'type', protocol incompatibility, complain! */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ /* check balance = total_in - total_out < withdraw-amount */
+ if (GNUNET_SYSERR ==
+ TALER_amount_subtract (balance,
+ &total_in,
+ &total_out))
+ {
+ /* total_in < total_out, why did the exchange ever allow this!? */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Free memory (potentially) allocated by #TALER_EXCHANGE_parse_reserve_history().
+ *
+ * @param rhistory result to free
+ * @param len number of entries in @a rhistory
+ */
+void
+TALER_EXCHANGE_free_reserve_history (struct
+ TALER_EXCHANGE_ReserveHistory *rhistory,
+ unsigned int len)
+{
+ for (unsigned int i = 0; i<len; i++)
+ {
+ switch (rhistory[i].type)
+ {
+ case TALER_EXCHANGE_RTT_DEPOSIT:
+ GNUNET_free_non_null (rhistory[i].details.in_details.wire_reference);
+ GNUNET_free_non_null (rhistory[i].details.in_details.sender_url);
+ break;
+ case TALER_EXCHANGE_RTT_WITHDRAWAL:
+ break;
+ case TALER_EXCHANGE_RTT_RECOUP:
+ break;
+ case TALER_EXCHANGE_RTT_CLOSE:
+ /* FIXME: should we free "receiver_account_details" ? */
+ break;
+ }
+ }
+ GNUNET_free (rhistory);
+}
+
+
+/**
* Verify a coins transaction history as returned by the exchange.
*
* @param dk fee structure for the coin, NULL to skip verifying fees
diff --git a/src/lib/exchange_api_track_transaction.c b/src/lib/exchange_api_deposits_get.c
index 636b986f9..1ffd738c3 100644
--- a/src/lib/exchange_api_track_transaction.c
+++ b/src/lib/exchange_api_deposits_get.c
@@ -15,8 +15,8 @@
<http://www.gnu.org/licenses/>
*/
/**
- * @file lib/exchange_api_track_transaction.c
- * @brief Implementation of the /track/transaction request of the exchange's HTTP API
+ * @file lib/exchange_api_deposits_get.c
+ * @brief Implementation of the /deposits/ GET request
* @author Christian Grothoff
*/
#include "platform.h"
@@ -378,15 +378,13 @@ TALER_EXCHANGE_deposits_get (struct TALER_EXCHANGE_Handle *exchange,
/**
- * Cancel deposit wtid request. This function cannot be used on a request
+ * Cancel /deposits/$WTID request. This function cannot be used on a request
* handle if a response is already served for it.
*
* @param dwh the wire deposits request handle
*/
void
-TALER_EXCHANGE_deposits_get_cancel (struct
- TALER_EXCHANGE_DepositGetHandle *
- dwh)
+TALER_EXCHANGE_deposits_get_cancel (struct TALER_EXCHANGE_DepositGetHandle *dwh)
{
if (NULL != dwh->job)
{
@@ -399,4 +397,4 @@ TALER_EXCHANGE_deposits_get_cancel (struct
}
-/* end of exchange_api_track_transaction.c */
+/* end of exchange_api_deposits_get.c */
diff --git a/src/lib/exchange_api_refresh_link.c b/src/lib/exchange_api_link.c
index 4b4b38ba1..3204ca842 100644
--- a/src/lib/exchange_api_refresh_link.c
+++ b/src/lib/exchange_api_link.c
@@ -15,8 +15,8 @@
<http://www.gnu.org/licenses/>
*/
/**
- * @file lib/exchange_api_refresh_link.c
- * @brief Implementation of the /refresh/link request of the exchange's HTTP API
+ * @file lib/exchange_api_link.c
+ * @brief Implementation of the /coins/$COIN_PUB/link request
* @author Christian Grothoff
*/
#include "platform.h"
@@ -31,9 +31,9 @@
/**
- * @brief A /refresh/link Handle
+ * @brief A /coins/$COIN_PUB/link Handle
*/
-struct TALER_EXCHANGE_RefreshLinkHandle
+struct TALER_EXCHANGE_LinkHandle
{
/**
@@ -54,7 +54,7 @@ struct TALER_EXCHANGE_RefreshLinkHandle
/**
* Function to call with the result.
*/
- TALER_EXCHANGE_RefreshLinkCallback link_cb;
+ TALER_EXCHANGE_LinkCallback link_cb;
/**
* Closure for @e cb.
@@ -73,7 +73,7 @@ struct TALER_EXCHANGE_RefreshLinkHandle
* Parse the provided linkage data from the "200 OK" response
* for one of the coins.
*
- * @param rlh refresh link handle
+ * @param lh link handle
* @param json json reply with the data for one coin
* @param coin_num number of the coin to decode
* @param trans_pub our transfer public key
@@ -83,13 +83,13 @@ struct TALER_EXCHANGE_RefreshLinkHandle
* @return #GNUNET_OK on success, #GNUNET_SYSERR on error
*/
static int
-parse_refresh_link_coin (const struct TALER_EXCHANGE_RefreshLinkHandle *rlh,
- const json_t *json,
- unsigned int coin_num,
- const struct TALER_TransferPublicKeyP *trans_pub,
- struct TALER_CoinSpendPrivateKeyP *coin_priv,
- struct TALER_DenominationSignature *sig,
- struct TALER_DenominationPublicKey *pub)
+parse_link_coin (const struct TALER_EXCHANGE_LinkHandle *lh,
+ const json_t *json,
+ unsigned int coin_num,
+ const struct TALER_TransferPublicKeyP *trans_pub,
+ struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ struct TALER_DenominationSignature *sig,
+ struct TALER_DenominationPublicKey *pub)
{
struct GNUNET_CRYPTO_RsaSignature *bsig;
struct GNUNET_CRYPTO_RsaPublicKey *rpub;
@@ -114,7 +114,7 @@ parse_refresh_link_coin (const struct TALER_EXCHANGE_RefreshLinkHandle *rlh,
}
TALER_link_recover_transfer_secret (trans_pub,
- &rlh->coin_priv,
+ &lh->coin_priv,
&secret);
TALER_planchet_setup_refresh (&secret,
coin_num,
@@ -133,7 +133,7 @@ parse_refresh_link_coin (const struct TALER_EXCHANGE_RefreshLinkHandle *rlh,
ldp.purpose.size = htonl (sizeof (ldp));
ldp.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK);
- GNUNET_CRYPTO_eddsa_key_get_public (&rlh->coin_priv.eddsa_priv,
+ GNUNET_CRYPTO_eddsa_key_get_public (&lh->coin_priv.eddsa_priv,
&ldp.old_coin_pub.eddsa_pub);
ldp.transfer_pub = *trans_pub;
pub->rsa_public_key = rpub;
@@ -175,13 +175,13 @@ parse_refresh_link_coin (const struct TALER_EXCHANGE_RefreshLinkHandle *rlh,
* Parse the provided linkage data from the "200 OK" response
* for one of the coins.
*
- * @param[in,out] rlh refresh link handle (callback may be zero'ed out)
+ * @param[in,out] lh link handle (callback may be zero'ed out)
* @param json json reply with the data for one coin
* @return #GNUNET_OK on success, #GNUNET_SYSERR on error
*/
static int
-parse_refresh_link_ok (struct TALER_EXCHANGE_RefreshLinkHandle *rlh,
- const json_t *json)
+parse_link_ok (struct TALER_EXCHANGE_LinkHandle *lh,
+ const json_t *json)
{
unsigned int session;
unsigned int num_coins;
@@ -277,14 +277,14 @@ parse_refresh_link_ok (struct TALER_EXCHANGE_RefreshLinkHandle *rlh,
{
GNUNET_assert (i + off_coin < num_coins);
if (GNUNET_OK !=
- parse_refresh_link_coin (rlh,
- json_array_get (jsona,
- i),
- i,
- &trans_pub,
- &coin_privs[i + off_coin],
- &sigs[i + off_coin],
- &pubs[i + off_coin]))
+ parse_link_coin (lh,
+ json_array_get (jsona,
+ i),
+ i,
+ &trans_pub,
+ &coin_privs[i + off_coin],
+ &sigs[i + off_coin],
+ &pubs[i + off_coin]))
{
GNUNET_break_op (0);
break;
@@ -304,15 +304,15 @@ parse_refresh_link_ok (struct TALER_EXCHANGE_RefreshLinkHandle *rlh,
if (off_coin == num_coins)
{
- rlh->link_cb (rlh->link_cb_cls,
- MHD_HTTP_OK,
- TALER_EC_NONE,
- num_coins,
- coin_privs,
- sigs,
- pubs,
- json);
- rlh->link_cb = NULL;
+ lh->link_cb (lh->link_cb_cls,
+ MHD_HTTP_OK,
+ TALER_EC_NONE,
+ num_coins,
+ coin_privs,
+ sigs,
+ pubs,
+ json);
+ lh->link_cb = NULL;
ret = GNUNET_OK;
}
else
@@ -337,29 +337,29 @@ parse_refresh_link_ok (struct TALER_EXCHANGE_RefreshLinkHandle *rlh,
/**
* Function called when we're done processing the
- * HTTP /refresh/link request.
+ * HTTP /coins/$COIN_PUB/link request.
*
- * @param cls the `struct TALER_EXCHANGE_RefreshLinkHandle`
+ * @param cls the `struct TALER_EXCHANGE_LinkHandle`
* @param response_code HTTP response code, 0 on error
* @param response parsed JSON result, NULL on error
*/
static void
-handle_refresh_link_finished (void *cls,
- long response_code,
- const void *response)
+handle_link_finished (void *cls,
+ long response_code,
+ const void *response)
{
- struct TALER_EXCHANGE_RefreshLinkHandle *rlh = cls;
+ struct TALER_EXCHANGE_LinkHandle *lh = cls;
const json_t *j = response;
- rlh->job = NULL;
+ lh->job = NULL;
switch (response_code)
{
case 0:
break;
case MHD_HTTP_OK:
if (GNUNET_OK !=
- parse_refresh_link_ok (rlh,
- j))
+ parse_link_ok (lh,
+ j))
{
GNUNET_break_op (0);
response_code = 0;
@@ -386,25 +386,24 @@ handle_refresh_link_finished (void *cls,
response_code = 0;
break;
}
- if (NULL != rlh->link_cb)
- rlh->link_cb (rlh->link_cb_cls,
- response_code,
- TALER_JSON_get_error_code (j),
- 0,
- NULL,
- NULL,
- NULL,
- j);
- TALER_EXCHANGE_refresh_link_cancel (rlh);
+ if (NULL != lh->link_cb)
+ lh->link_cb (lh->link_cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ j);
+ TALER_EXCHANGE_link_cancel (lh);
}
/**
* Submit a link request to the exchange and get the exchange's response.
*
- * This API is typically not used by anyone, it is more a threat
- * against those trying to receive a funds transfer by abusing the
- * /refresh protocol.
+ * This API is typically not used by anyone, it is more a threat against those
+ * trying to receive a funds transfer by abusing the refresh protocol.
*
* @param exchange the exchange handle; the exchange must be ready to operate
* @param coin_priv private key to request link data for
@@ -413,13 +412,13 @@ handle_refresh_link_finished (void *cls,
* @param link_cb_cls closure for @a link_cb
* @return a handle for this request
*/
-struct TALER_EXCHANGE_RefreshLinkHandle *
-TALER_EXCHANGE_refresh_link (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_CoinSpendPrivateKeyP *coin_priv,
- TALER_EXCHANGE_RefreshLinkCallback link_cb,
- void *link_cb_cls)
+struct TALER_EXCHANGE_LinkHandle *
+TALER_EXCHANGE_link (struct TALER_EXCHANGE_Handle *exchange,
+ const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+ TALER_EXCHANGE_LinkCallback link_cb,
+ void *link_cb_cls)
{
- struct TALER_EXCHANGE_RefreshLinkHandle *rlh;
+ struct TALER_EXCHANGE_LinkHandle *lh;
CURL *eh;
struct GNUNET_CURL_Context *ctx;
struct TALER_CoinSpendPublicKeyP coin_pub;
@@ -449,42 +448,41 @@ TALER_EXCHANGE_refresh_link (struct TALER_EXCHANGE_Handle *exchange,
"/coins/%s/link",
pub_str);
}
- rlh = GNUNET_new (struct TALER_EXCHANGE_RefreshLinkHandle);
- rlh->exchange = exchange;
- rlh->link_cb = link_cb;
- rlh->link_cb_cls = link_cb_cls;
- rlh->coin_priv = *coin_priv;
- rlh->url = TEAH_path_to_url (exchange,
- arg_str);
- eh = TEL_curl_easy_get (rlh->url);
+ lh = GNUNET_new (struct TALER_EXCHANGE_LinkHandle);
+ lh->exchange = exchange;
+ lh->link_cb = link_cb;
+ lh->link_cb_cls = link_cb_cls;
+ lh->coin_priv = *coin_priv;
+ lh->url = TEAH_path_to_url (exchange,
+ arg_str);
+ eh = TEL_curl_easy_get (lh->url);
ctx = TEAH_handle_to_context (exchange);
- rlh->job = GNUNET_CURL_job_add (ctx,
- eh,
- GNUNET_YES,
- &handle_refresh_link_finished,
- rlh);
- return rlh;
+ lh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ GNUNET_YES,
+ &handle_link_finished,
+ lh);
+ return lh;
}
/**
- * Cancel a refresh link request. This function cannot be used
+ * Cancel a link request. This function cannot be used
* on a request handle if the callback was already invoked.
*
- * @param rlh the refresh link handle
+ * @param lh the link handle
*/
void
-TALER_EXCHANGE_refresh_link_cancel (struct
- TALER_EXCHANGE_RefreshLinkHandle *rlh)
+TALER_EXCHANGE_link_cancel (struct TALER_EXCHANGE_LinkHandle *lh)
{
- if (NULL != rlh->job)
+ if (NULL != lh->job)
{
- GNUNET_CURL_job_cancel (rlh->job);
- rlh->job = NULL;
+ GNUNET_CURL_job_cancel (lh->job);
+ lh->job = NULL;
}
- GNUNET_free (rlh->url);
- GNUNET_free (rlh);
+ GNUNET_free (lh->url);
+ GNUNET_free (lh);
}
-/* end of exchange_api_refresh_link.c */
+/* end of exchange_api_link.c */
diff --git a/src/lib/exchange_api_melt.c b/src/lib/exchange_api_melt.c
new file mode 100644
index 000000000..9c85fa188
--- /dev/null
+++ b/src/lib/exchange_api_melt.c
@@ -0,0 +1,505 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_melt.c
+ * @brief Implementation of the /coins/$COIN_PUB/melt request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+#include "exchange_api_refresh_common.h"
+
+
+/**
+ * @brief A /coins/$COIN_PUB/melt Handle
+ */
+struct TALER_EXCHANGE_MeltHandle
+{
+
+ /**
+ * The connection to exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Handle *exchange;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with refresh melt failure results.
+ */
+ TALER_EXCHANGE_MeltCallback melt_cb;
+
+ /**
+ * Closure for @e result_cb and @e melt_failure_cb.
+ */
+ void *melt_cb_cls;
+
+ /**
+ * Actual information about the melt operation.
+ */
+ struct MeltData *md;
+
+ /**
+ * @brief Public information about the coin's denomination key
+ */
+ struct TALER_EXCHANGE_DenomPublicKey dki;
+};
+
+
+/**
+ * Verify that the signature on the "200 OK" response
+ * from the exchange is valid.
+ *
+ * @param mh melt handle
+ * @param json json reply with the signature
+ * @param[out] exchange_pub public key of the exchange used for the signature
+ * @param[out] noreveal_index set to the noreveal index selected by the exchange
+ * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
+ */
+static int
+verify_melt_signature_ok (struct TALER_EXCHANGE_MeltHandle *mh,
+ const json_t *json,
+ struct TALER_ExchangePublicKeyP *exchange_pub,
+ uint32_t *noreveal_index)
+{
+ struct TALER_ExchangeSignatureP exchange_sig;
+ const struct TALER_EXCHANGE_Keys *key_state;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub", exchange_pub),
+ GNUNET_JSON_spec_uint32 ("noreveal_index", noreveal_index),
+ GNUNET_JSON_spec_end ()
+ };
+ struct TALER_RefreshMeltConfirmationPS confirm;
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* check that exchange signing key is permitted */
+ key_state = TALER_EXCHANGE_get_keys (mh->exchange);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_test_signing_key (key_state,
+ exchange_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* check that noreveal index is in permitted range */
+ if (TALER_CNC_KAPPA <= *noreveal_index)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* verify signature by exchange */
+ confirm.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT);
+ confirm.purpose.size = htonl (sizeof (struct
+ TALER_RefreshMeltConfirmationPS));
+ confirm.rc = mh->md->rc;
+ confirm.noreveal_index = htonl (*noreveal_index);
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT,
+ &confirm.purpose,
+ &exchange_sig.eddsa_signature,
+ &exchange_pub->eddsa_pub))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Verify that the signatures on the "409 CONFLICT" response from the
+ * exchange demonstrating customer double-spending are valid.
+ *
+ * @param mh melt handle
+ * @param json json reply with the signature(s) and transaction history
+ * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not
+ */
+static int
+verify_melt_signature_conflict (struct TALER_EXCHANGE_MeltHandle *mh,
+ const json_t *json)
+{
+ json_t *history;
+ struct TALER_Amount original_value;
+ struct TALER_Amount melt_value_with_fee;
+ struct TALER_Amount total;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("history", &history),
+ GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub),
+ TALER_JSON_spec_amount ("original_value", &original_value),
+ TALER_JSON_spec_amount ("requested_value", &melt_value_with_fee),
+ GNUNET_JSON_spec_end ()
+ };
+ const struct MeltedCoin *mc;
+
+ /* parse JSON reply */
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* Find out which coin was deemed problematic by the exchange */
+ mc = &mh->md->melted_coin;
+
+ /* check basic coin properties */
+ if (0 != TALER_amount_cmp (&original_value,
+ &mc->original_value))
+ {
+ /* We disagree on the value of the coin */
+ GNUNET_break_op (0);
+ json_decref (history);
+ return GNUNET_SYSERR;
+ }
+ if (0 != TALER_amount_cmp (&melt_value_with_fee,
+ &mc->melt_amount_with_fee))
+ {
+ /* We disagree on the value of the coin */
+ GNUNET_break_op (0);
+ json_decref (history);
+ return GNUNET_SYSERR;
+ }
+
+ /* verify coin history */
+ history = json_object_get (json,
+ "history");
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_verify_coin_history (&mh->dki,
+ original_value.currency,
+ &coin_pub,
+ history,
+ &total))
+ {
+ GNUNET_break_op (0);
+ json_decref (history);
+ return GNUNET_SYSERR;
+ }
+ json_decref (history);
+
+ /* check if melt operation was really too expensive given history */
+ if (GNUNET_OK !=
+ TALER_amount_add (&total,
+ &total,
+ &melt_value_with_fee))
+ {
+ /* clearly not OK if our transaction would have caused
+ the overflow... */
+ return GNUNET_OK;
+ }
+
+ if (0 >= TALER_amount_cmp (&total,
+ &original_value))
+ {
+ /* transaction should have still fit */
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* everything OK, valid proof of double-spending was provided */
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /coins/$COIN_PUB/melt request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_MeltHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_melt_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_MeltHandle *mh = cls;
+ uint32_t noreveal_index = TALER_CNC_KAPPA; /* invalid value */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ const json_t *j = response;
+
+ mh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ verify_melt_signature_ok (mh,
+ j,
+ &exchange_pub,
+ &noreveal_index))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ }
+ if (NULL != mh->melt_cb)
+ {
+ mh->melt_cb (mh->melt_cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ noreveal_index,
+ (0 == response_code) ? NULL : &exchange_pub,
+ j);
+ mh->melt_cb = NULL;
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* Double spending; check signatures on transaction history */
+ if (GNUNET_OK !=
+ verify_melt_signature_conflict (mh,
+ j))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ }
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; assuming we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ if (NULL != mh->melt_cb)
+ mh->melt_cb (mh->melt_cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ UINT32_MAX,
+ NULL,
+ j);
+ TALER_EXCHANGE_melt_cancel (mh);
+}
+
+
+/**
+ * Submit a melt request to the exchange and get the exchange's
+ * response.
+ *
+ * This API is typically used by a wallet. Note that to ensure that
+ * no money is lost in case of hardware failures, the provided
+ * argument should have been constructed using
+ * #TALER_EXCHANGE_refresh_prepare and committed to persistent storage
+ * prior to calling this function.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param refresh_data_length size of the @a refresh_data (returned
+ * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare())
+ * @param refresh_data the refresh data as returned from
+ #TALER_EXCHANGE_refresh_prepare())
+ * @param melt_cb the callback to call with the result
+ * @param melt_cb_cls closure for @a melt_cb
+ * @return a handle for this request; NULL if the argument was invalid.
+ * In this case, neither callback will be called.
+ */
+struct TALER_EXCHANGE_MeltHandle *
+TALER_EXCHANGE_melt (struct TALER_EXCHANGE_Handle *exchange,
+ size_t refresh_data_length,
+ const char *refresh_data,
+ TALER_EXCHANGE_MeltCallback melt_cb,
+ void *melt_cb_cls)
+{
+ const struct TALER_EXCHANGE_Keys *key_state;
+ const struct TALER_EXCHANGE_DenomPublicKey *dki;
+ json_t *melt_obj;
+ struct TALER_EXCHANGE_MeltHandle *mh;
+ CURL *eh;
+ struct GNUNET_CURL_Context *ctx;
+ struct MeltData *md;
+ struct TALER_CoinSpendSignatureP confirm_sig;
+ struct TALER_RefreshMeltCoinAffirmationPS melt;
+ struct GNUNET_HashCode h_denom_pub;
+ char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
+
+ GNUNET_assert (GNUNET_YES ==
+ TEAH_handle_is_ready (exchange));
+ md = TALER_EXCHANGE_deserialize_melt_data_ (refresh_data,
+ refresh_data_length);
+ if (NULL == md)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ melt.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT);
+ melt.purpose.size = htonl (sizeof (struct
+ TALER_RefreshMeltCoinAffirmationPS));
+ melt.rc = md->rc;
+ TALER_amount_hton (&melt.amount_with_fee,
+ &md->melted_coin.melt_amount_with_fee);
+ TALER_amount_hton (&melt.melt_fee,
+ &md->melted_coin.fee_melt);
+ GNUNET_CRYPTO_eddsa_key_get_public (&md->melted_coin.coin_priv.eddsa_priv,
+ &melt.coin_pub.eddsa_pub);
+ GNUNET_CRYPTO_eddsa_sign (&md->melted_coin.coin_priv.eddsa_priv,
+ &melt.purpose,
+ &confirm_sig.eddsa_signature);
+ GNUNET_CRYPTO_rsa_public_key_hash (md->melted_coin.pub_key.rsa_public_key,
+ &h_denom_pub);
+ melt_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}",
+ "coin_pub",
+ GNUNET_JSON_from_data_auto (&melt.coin_pub),
+ "denom_pub_hash",
+ GNUNET_JSON_from_data_auto (&h_denom_pub),
+ "denom_sig",
+ GNUNET_JSON_from_rsa_signature (
+ md->melted_coin.sig.rsa_signature),
+ "confirm_sig",
+ GNUNET_JSON_from_data_auto (&confirm_sig),
+ "value_with_fee",
+ TALER_JSON_from_amount (
+ &md->melted_coin.melt_amount_with_fee),
+ "rc",
+ GNUNET_JSON_from_data_auto (&melt.rc));
+ if (NULL == melt_obj)
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_free_melt_data_ (md);
+ return NULL;
+ }
+ {
+ char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (&melt.coin_pub,
+ sizeof (struct
+ TALER_CoinSpendPublicKeyP),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "/coins/%s/melt",
+ pub_str);
+ }
+
+ key_state = TALER_EXCHANGE_get_keys (exchange);
+ dki = TALER_EXCHANGE_get_denomination_key (key_state,
+ &md->melted_coin.pub_key);
+
+ /* and now we can at last begin the actual request handling */
+ mh = GNUNET_new (struct TALER_EXCHANGE_MeltHandle);
+ mh->exchange = exchange;
+ mh->dki = *dki;
+ mh->dki.key.rsa_public_key = NULL; /* lifetime not warranted, so better
+ not copy the pointer */
+ mh->melt_cb = melt_cb;
+ mh->melt_cb_cls = melt_cb_cls;
+ mh->md = md;
+ mh->url = TEAH_path_to_url (exchange,
+ arg_str);
+ eh = TEL_curl_easy_get (mh->url);
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&mh->ctx,
+ eh,
+ melt_obj))
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ json_decref (melt_obj);
+ GNUNET_free (mh->url);
+ GNUNET_free (mh);
+ return NULL;
+ }
+ json_decref (melt_obj);
+ ctx = TEAH_handle_to_context (exchange);
+ mh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ mh->ctx.headers,
+ &handle_melt_finished,
+ mh);
+ return mh;
+}
+
+
+/**
+ * Cancel a melt request. This function cannot be used
+ * on a request handle if either callback was already invoked.
+ *
+ * @param mh the refresh melt handle
+ */
+void
+TALER_EXCHANGE_melt_cancel (struct TALER_EXCHANGE_MeltHandle *mh)
+{
+ if (NULL != mh->job)
+ {
+ GNUNET_CURL_job_cancel (mh->job);
+ mh->job = NULL;
+ }
+ TALER_EXCHANGE_free_melt_data_ (mh->md); /* does not free 'md' itself */
+ GNUNET_free (mh->md);
+ GNUNET_free (mh->url);
+ TALER_curl_easy_post_finished (&mh->ctx);
+ GNUNET_free (mh);
+}
+
+
+/* end of exchange_api_melt.c */
diff --git a/src/lib/exchange_api_refresh.c b/src/lib/exchange_api_refresh.c
deleted file mode 100644
index eb26b7c15..000000000
--- a/src/lib/exchange_api_refresh.c
+++ /dev/null
@@ -1,1770 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2015-2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file lib/exchange_api_refresh.c
- * @brief Implementation of the /refresh/melt+reveal requests of the exchange's HTTP API
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_json_lib.h"
-#include "taler_exchange_service.h"
-#include "exchange_api_handle.h"
-#include "taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/* ********************* /refresh/ common ***************************** */
-
-/* structures for committing refresh data to disk before doing the
- network interaction(s) */
-
-GNUNET_NETWORK_STRUCT_BEGIN
-
-/**
- * Header of serialized information about a coin we are melting.
- */
-struct MeltedCoinP
-{
- /**
- * Private key of the coin.
- */
- struct TALER_CoinSpendPrivateKeyP coin_priv;
-
- /**
- * Amount this coin contributes to the melt, including fee.
- */
- struct TALER_AmountNBO melt_amount_with_fee;
-
- /**
- * The applicable fee for withdrawing a coin of this denomination
- */
- struct TALER_AmountNBO fee_melt;
-
- /**
- * The original value of the coin.
- */
- struct TALER_AmountNBO original_value;
-
- /**
- * Transfer private keys for each cut-and-choose dimension.
- */
- struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA];
-
- /**
- * Timestamp indicating when coins of this denomination become invalid.
- */
- struct GNUNET_TIME_AbsoluteNBO expire_deposit;
-
- /**
- * Size of the encoded public key that follows.
- */
- uint16_t pbuf_size;
-
- /**
- * Size of the encoded signature that follows.
- */
- uint16_t sbuf_size;
-
- /* Followed by serializations of:
- 1) struct TALER_DenominationPublicKey pub_key;
- 2) struct TALER_DenominationSignature sig;
- */
-};
-
-
-/**
- * Header of serialized data about a melt operation, suitable for
- * persisting it on disk.
- */
-struct MeltDataP
-{
-
- /**
- * Hash over the melting session.
- */
- struct TALER_RefreshCommitmentP rc;
-
- /**
- * Number of coins we are melting, in NBO
- */
- uint16_t num_melted_coins GNUNET_PACKED;
-
- /**
- * Number of coins we are creating, in NBO
- */
- uint16_t num_fresh_coins GNUNET_PACKED;
-
- /* Followed by serializations of:
- 1) struct MeltedCoinP melted_coins[num_melted_coins];
- 2) struct TALER_EXCHANGE_DenomPublicKey fresh_pks[num_fresh_coins];
- 3) TALER_CNC_KAPPA times:
- 3a) struct TALER_PlanchetSecretsP fresh_coins[num_fresh_coins];
- */
-};
-
-
-GNUNET_NETWORK_STRUCT_END
-
-
-/**
- * Information about a coin we are melting.
- */
-struct MeltedCoin
-{
- /**
- * Private key of the coin.
- */
- struct TALER_CoinSpendPrivateKeyP coin_priv;
-
- /**
- * Amount this coin contributes to the melt, including fee.
- */
- struct TALER_Amount melt_amount_with_fee;
-
- /**
- * The applicable fee for melting a coin of this denomination
- */
- struct TALER_Amount fee_melt;
-
- /**
- * The original value of the coin.
- */
- struct TALER_Amount original_value;
-
- /**
- * Transfer private keys for each cut-and-choose dimension.
- */
- struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA];
-
- /**
- * Timestamp indicating when coins of this denomination become invalid.
- */
- struct GNUNET_TIME_Absolute expire_deposit;
-
- /**
- * Denomination key of the original coin.
- */
- struct TALER_DenominationPublicKey pub_key;
-
- /**
- * Exchange's signature over the coin.
- */
- struct TALER_DenominationSignature sig;
-
-};
-
-
-/**
- * Melt data in non-serialized format for convenient processing.
- */
-struct MeltData
-{
-
- /**
- * Hash over the committed data during refresh operation.
- */
- struct TALER_RefreshCommitmentP rc;
-
- /**
- * Number of coins we are creating
- */
- uint16_t num_fresh_coins;
-
- /**
- * Information about the melted coin.
- */
- struct MeltedCoin melted_coin;
-
- /**
- * Array of @e num_fresh_coins denomination keys for the coins to be
- * freshly exchangeed.
- */
- struct TALER_DenominationPublicKey *fresh_pks;
-
- /**
- * Arrays of @e num_fresh_coins with information about the fresh
- * coins to be created, for each cut-and-choose dimension.
- */
- struct TALER_PlanchetSecretsP *fresh_coins[TALER_CNC_KAPPA];
-};
-
-
-/**
- * Free all information associated with a melted coin session.
- *
- * @param mc melted coin to release, the pointer itself is NOT
- * freed (as it is typically not allocated by itself)
- */
-static void
-free_melted_coin (struct MeltedCoin *mc)
-{
- if (NULL != mc->pub_key.rsa_public_key)
- GNUNET_CRYPTO_rsa_public_key_free (mc->pub_key.rsa_public_key);
- if (NULL != mc->sig.rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (mc->sig.rsa_signature);
-}
-
-
-/**
- * Free all information associated with a melting session. Note
- * that we allow the melting session to be only partially initialized,
- * as we use this function also when freeing melt data that was not
- * fully initialized (i.e. due to failures in #deserialize_melt_data()).
- *
- * @param md melting data to release, the pointer itself is NOT
- * freed (as it is typically not allocated by itself)
- */
-static void
-free_melt_data (struct MeltData *md)
-{
- free_melted_coin (&md->melted_coin);
- if (NULL != md->fresh_pks)
- {
- for (unsigned int i = 0; i<md->num_fresh_coins; i++)
- if (NULL != md->fresh_pks[i].rsa_public_key)
- GNUNET_CRYPTO_rsa_public_key_free (md->fresh_pks[i].rsa_public_key);
- GNUNET_free (md->fresh_pks);
- }
-
- for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
- GNUNET_free_non_null (md->fresh_coins[i]);
- /* Finally, clean up a bit...
- (NOTE: compilers might optimize this away, so this is
- not providing any strong assurances that the key material
- is purged.) */
- memset (md,
- 0,
- sizeof (struct MeltData));
-}
-
-
-/**
- * Serialize information about a coin we are melting.
- *
- * @param mc information to serialize
- * @param buf buffer to write data in, NULL to just compute
- * required size
- * @param off offeset at @a buf to use
- * @return number of bytes written to @a buf at @a off, or if
- * @a buf is NULL, number of bytes required; 0 on error
- */
-static size_t
-serialize_melted_coin (const struct MeltedCoin *mc,
- char *buf,
- size_t off)
-{
- struct MeltedCoinP mcp;
- unsigned int i;
- char *pbuf;
- size_t pbuf_size;
- char *sbuf;
- size_t sbuf_size;
-
- sbuf_size = GNUNET_CRYPTO_rsa_signature_encode (mc->sig.rsa_signature,
- &sbuf);
- pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (mc->pub_key.rsa_public_key,
- &pbuf);
- if (NULL == buf)
- {
- GNUNET_free (sbuf);
- GNUNET_free (pbuf);
- return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size;
- }
- if ( (sbuf_size > UINT16_MAX) ||
- (pbuf_size > UINT16_MAX) )
- {
- GNUNET_break (0);
- return 0;
- }
- mcp.coin_priv = mc->coin_priv;
- TALER_amount_hton (&mcp.melt_amount_with_fee,
- &mc->melt_amount_with_fee);
- TALER_amount_hton (&mcp.fee_melt,
- &mc->fee_melt);
- TALER_amount_hton (&mcp.original_value,
- &mc->original_value);
- for (i = 0; i<TALER_CNC_KAPPA; i++)
- mcp.transfer_priv[i] = mc->transfer_priv[i];
- mcp.expire_deposit = GNUNET_TIME_absolute_hton (mc->expire_deposit);
- mcp.pbuf_size = htons ((uint16_t) pbuf_size);
- mcp.sbuf_size = htons ((uint16_t) sbuf_size);
- memcpy (&buf[off],
- &mcp,
- sizeof (struct MeltedCoinP));
- memcpy (&buf[off + sizeof (struct MeltedCoinP)],
- pbuf,
- pbuf_size);
- memcpy (&buf[off + sizeof (struct MeltedCoinP) + pbuf_size],
- sbuf,
- sbuf_size);
- GNUNET_free (sbuf);
- GNUNET_free (pbuf);
- return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size;
-}
-
-
-/**
- * Deserialize information about a coin we are melting.
- *
- * @param[out] mc information to deserialize
- * @param buf buffer to read data from
- * @param size number of bytes available at @a buf to use
- * @param[out] ok set to #GNUNET_NO to report errors
- * @return number of bytes read from @a buf, 0 on error
- */
-static size_t
-deserialize_melted_coin (struct MeltedCoin *mc,
- const char *buf,
- size_t size,
- int *ok)
-{
- struct MeltedCoinP mcp;
- unsigned int i;
- size_t pbuf_size;
- size_t sbuf_size;
- size_t off;
-
- if (size < sizeof (struct MeltedCoinP))
- {
- GNUNET_break (0);
- *ok = GNUNET_NO;
- return 0;
- }
- memcpy (&mcp,
- buf,
- sizeof (struct MeltedCoinP));
- pbuf_size = ntohs (mcp.pbuf_size);
- sbuf_size = ntohs (mcp.sbuf_size);
- if (size < sizeof (struct MeltedCoinP) + pbuf_size + sbuf_size)
- {
- GNUNET_break (0);
- *ok = GNUNET_NO;
- return 0;
- }
- off = sizeof (struct MeltedCoinP);
- mc->pub_key.rsa_public_key
- = GNUNET_CRYPTO_rsa_public_key_decode (&buf[off],
- pbuf_size);
- off += pbuf_size;
- mc->sig.rsa_signature
- = GNUNET_CRYPTO_rsa_signature_decode (&buf[off],
- sbuf_size);
- off += sbuf_size;
- if ( (NULL == mc->pub_key.rsa_public_key) ||
- (NULL == mc->sig.rsa_signature) )
- {
- GNUNET_break (0);
- *ok = GNUNET_NO;
- return 0;
- }
-
- mc->coin_priv = mcp.coin_priv;
- TALER_amount_ntoh (&mc->melt_amount_with_fee,
- &mcp.melt_amount_with_fee);
- TALER_amount_ntoh (&mc->fee_melt,
- &mcp.fee_melt);
- TALER_amount_ntoh (&mc->original_value,
- &mcp.original_value);
- for (i = 0; i<TALER_CNC_KAPPA; i++)
- mc->transfer_priv[i] = mcp.transfer_priv[i];
- mc->expire_deposit = GNUNET_TIME_absolute_ntoh (mcp.expire_deposit);
- return off;
-}
-
-
-/**
- * Serialize information about a denomination key.
- *
- * @param dk information to serialize
- * @param buf buffer to write data in, NULL to just compute
- * required size
- * @param off offeset at @a buf to use
- * @return number of bytes written to @a buf at @a off, or if
- * @a buf is NULL, number of bytes required
- */
-static size_t
-serialize_denomination_key (const struct TALER_DenominationPublicKey *dk,
- char *buf,
- size_t off)
-{
- char *pbuf;
- size_t pbuf_size;
- uint32_t be;
-
- pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (dk->rsa_public_key,
- &pbuf);
- if (NULL == buf)
- {
- GNUNET_free (pbuf);
- return pbuf_size + sizeof (uint32_t);
- }
- be = htonl ((uint32_t) pbuf_size);
- memcpy (&buf[off],
- &be,
- sizeof (uint32_t));
- memcpy (&buf[off + sizeof (uint32_t)],
- pbuf,
- pbuf_size);
- GNUNET_free (pbuf);
- return pbuf_size + sizeof (uint32_t);
-}
-
-
-/**
- * Deserialize information about a denomination key.
- *
- * @param[out] dk information to deserialize
- * @param buf buffer to read data from
- * @param size number of bytes available at @a buf to use
- * @param[out] ok set to #GNUNET_NO to report errors
- * @return number of bytes read from @a buf, 0 on error
- */
-static size_t
-deserialize_denomination_key (struct TALER_DenominationPublicKey *dk,
- const char *buf,
- size_t size,
- int *ok)
-{
- size_t pbuf_size;
- uint32_t be;
-
- if (size < sizeof (uint32_t))
- {
- GNUNET_break (0);
- *ok = GNUNET_NO;
- return 0;
- }
- memcpy (&be,
- buf,
- sizeof (uint32_t));
- pbuf_size = ntohl (be);
- if (size < sizeof (uint32_t) + pbuf_size)
- {
- GNUNET_break (0);
- *ok = GNUNET_NO;
- return 0;
- }
- dk->rsa_public_key
- = GNUNET_CRYPTO_rsa_public_key_decode (&buf[sizeof (uint32_t)],
- pbuf_size);
- if (NULL == dk->rsa_public_key)
- {
- GNUNET_break (0);
- *ok = GNUNET_NO;
- return 0;
- }
- return sizeof (uint32_t) + pbuf_size;
-}
-
-
-/**
- * Serialize information about a fresh coin we are generating.
- *
- * @param fc information to serialize
- * @param buf buffer to write data in, NULL to just compute
- * required size
- * @param off offeset at @a buf to use
- * @return number of bytes written to @a buf at @a off, or if
- * @a buf is NULL, number of bytes required
- */
-static size_t
-serialize_fresh_coin (const struct TALER_PlanchetSecretsP *fc,
- char *buf,
- size_t off)
-{
- if (NULL != buf)
- memcpy (&buf[off],
- fc,
- sizeof (struct TALER_PlanchetSecretsP));
- return sizeof (struct TALER_PlanchetSecretsP);
-}
-
-
-/**
- * Deserialize information about a fresh coin we are generating.
- *
- * @param[out] fc information to deserialize
- * @param buf buffer to read data from
- * @param size number of bytes available at @a buf to use
- * @param[out] ok set to #GNUNET_NO to report errors
- * @return number of bytes read from @a buf, 0 on error
- */
-static size_t
-deserialize_fresh_coin (struct TALER_PlanchetSecretsP *fc,
- const char *buf,
- size_t size,
- int *ok)
-{
- if (size < sizeof (struct TALER_PlanchetSecretsP))
- {
- GNUNET_break (0);
- *ok = GNUNET_NO;
- return 0;
- }
- memcpy (fc,
- buf,
- sizeof (struct TALER_PlanchetSecretsP));
- return sizeof (struct TALER_PlanchetSecretsP);
-}
-
-
-/**
- * Serialize melt data.
- *
- * @param md data to serialize
- * @param[out] res_size size of buffer returned
- * @return serialized melt data
- */
-static char *
-serialize_melt_data (const struct MeltData *md,
- size_t *res_size)
-{
- size_t size;
- size_t asize;
- char *buf;
-
- size = 0;
- asize = (size_t) -1; /* make the compiler happy */
- buf = NULL;
- /* we do 2 iterations, #1 to determine total size, #2 to
- actually construct the buffer */
- do {
- if (0 == size)
- {
- size = sizeof (struct MeltDataP);
- }
- else
- {
- struct MeltDataP *mdp;
-
- buf = GNUNET_malloc (size);
- asize = size; /* just for invariant check later */
- size = sizeof (struct MeltDataP);
- mdp = (struct MeltDataP *) buf;
- mdp->rc = md->rc;
- mdp->num_fresh_coins = htons (md->num_fresh_coins);
- }
- size += serialize_melted_coin (&md->melted_coin,
- buf,
- size);
- for (unsigned int i = 0; i<md->num_fresh_coins; i++)
- size += serialize_denomination_key (&md->fresh_pks[i],
- buf,
- size);
- for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
- for (unsigned int j = 0; j<md->num_fresh_coins; j++)
- size += serialize_fresh_coin (&md->fresh_coins[i][j],
- buf,
- size);
- } while (NULL == buf);
- GNUNET_assert (size == asize);
- *res_size = size;
- return buf;
-}
-
-
-/**
- * Deserialize melt data.
- *
- * @param buf serialized data
- * @param buf_size size of @a buf
- * @return deserialized melt data, NULL on error
- */
-static struct MeltData *
-deserialize_melt_data (const char *buf,
- size_t buf_size)
-{
- struct MeltData *md;
- struct MeltDataP mdp;
- size_t off;
- int ok;
-
- if (buf_size < sizeof (struct MeltDataP))
- return NULL;
- memcpy (&mdp,
- buf,
- sizeof (struct MeltDataP));
- md = GNUNET_new (struct MeltData);
- md->rc = mdp.rc;
- md->num_fresh_coins = ntohs (mdp.num_fresh_coins);
- md->fresh_pks = GNUNET_new_array (md->num_fresh_coins,
- struct TALER_DenominationPublicKey);
- for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
- md->fresh_coins[i] = GNUNET_new_array (md->num_fresh_coins,
- struct TALER_PlanchetSecretsP);
- off = sizeof (struct MeltDataP);
- ok = GNUNET_YES;
- off += deserialize_melted_coin (&md->melted_coin,
- &buf[off],
- buf_size - off,
- &ok);
- for (unsigned int i = 0; (i<md->num_fresh_coins) && (GNUNET_YES == ok); i++)
- off += deserialize_denomination_key (&md->fresh_pks[i],
- &buf[off],
- buf_size - off,
- &ok);
-
- for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
- for (unsigned int j = 0; (j<md->num_fresh_coins) && (GNUNET_YES == ok); j++)
- off += deserialize_fresh_coin (&md->fresh_coins[i][j],
- &buf[off],
- buf_size - off,
- &ok);
- if (off != buf_size)
- {
- GNUNET_break (0);
- ok = GNUNET_NO;
- }
- if (GNUNET_YES != ok)
- {
- free_melt_data (md);
- GNUNET_free (md);
- return NULL;
- }
- return md;
-}
-
-
-/**
- * Melt (partially spent) coins to obtain fresh coins that are
- * unlinkable to the original coin(s). Note that melting more
- * than one coin in a single request will make those coins linkable,
- * so the safest operation only melts one coin at a time.
- *
- * This API is typically used by a wallet. Note that to ensure that
- * no money is lost in case of hardware failures, this operation does
- * not actually initiate the request. Instead, it generates a buffer
- * which the caller must store before proceeding with the actual call
- * to #TALER_EXCHANGE_refresh_melt() that will generate the request.
- *
- * This function does verify that the given request data is internally
- * consistent. However, the @a melts_sigs are NOT verified.
- *
- * Aside from some non-trivial cryptographic operations that might
- * take a bit of CPU time to complete, this function returns
- * its result immediately and does not start any asynchronous
- * processing. This function is also thread-safe.
- *
- * @param melt_priv private key of the coin to melt
- * @param melt_amount amount specifying how much
- * the coin will contribute to the melt (including fee)
- * @param melt_sig signature affirming the
- * validity of the public keys corresponding to the
- * @a melt_priv private key
- * @param melt_pk denomination key information
- * record corresponding to the @a melt_sig
- * validity of the keys
- * @param fresh_pks_len length of the @a pks array
- * @param fresh_pks array of @a pks_len denominations of fresh coins to create
- * @param[out] res_size set to the size of the return value, or 0 on error
- * @return NULL
- * if the inputs are invalid (i.e. denomination key not with this exchange).
- * Otherwise, pointer to a buffer of @a res_size to store persistently
- * before proceeding to #TALER_EXCHANGE_refresh_melt().
- * Non-null results should be freed using GNUNET_free().
- */
-char *
-TALER_EXCHANGE_refresh_prepare (const struct
- TALER_CoinSpendPrivateKeyP *melt_priv,
- const struct TALER_Amount *melt_amount,
- const struct
- TALER_DenominationSignature *melt_sig,
- const struct
- TALER_EXCHANGE_DenomPublicKey *melt_pk,
- unsigned int fresh_pks_len,
- const struct
- TALER_EXCHANGE_DenomPublicKey *fresh_pks,
- size_t *res_size)
-{
- struct MeltData md;
- char *buf;
- struct TALER_Amount total;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_TransferSecretP trans_sec[TALER_CNC_KAPPA];
- struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA];
-
- GNUNET_CRYPTO_eddsa_key_get_public (&melt_priv->eddsa_priv,
- &coin_pub.eddsa_pub);
- /* build up melt data structure */
- memset (&md, 0, sizeof (md));
- md.num_fresh_coins = fresh_pks_len;
- md.melted_coin.coin_priv = *melt_priv;
- md.melted_coin.melt_amount_with_fee = *melt_amount;
- md.melted_coin.fee_melt = melt_pk->fee_refresh;
- md.melted_coin.original_value = melt_pk->value;
- md.melted_coin.expire_deposit
- = melt_pk->expire_deposit;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (melt_amount->currency,
- &total));
- md.melted_coin.pub_key.rsa_public_key
- = GNUNET_CRYPTO_rsa_public_key_dup (melt_pk->key.rsa_public_key);
- md.melted_coin.sig.rsa_signature
- = GNUNET_CRYPTO_rsa_signature_dup (melt_sig->rsa_signature);
- md.fresh_pks = GNUNET_new_array (fresh_pks_len,
- struct TALER_DenominationPublicKey);
- for (unsigned int i = 0; i<fresh_pks_len; i++)
- {
- md.fresh_pks[i].rsa_public_key
- = GNUNET_CRYPTO_rsa_public_key_dup (fresh_pks[i].key.rsa_public_key);
- if ( (GNUNET_OK !=
- TALER_amount_add (&total,
- &total,
- &fresh_pks[i].value)) ||
- (GNUNET_OK !=
- TALER_amount_add (&total,
- &total,
- &fresh_pks[i].fee_withdraw)) )
- {
- GNUNET_break (0);
- free_melt_data (&md);
- return NULL;
- }
- }
- /* verify that melt_amount is above total cost */
- if (1 ==
- TALER_amount_cmp (&total,
- melt_amount) )
- {
- /* Eh, this operation is more expensive than the
- @a melt_amount. This is not OK. */
- GNUNET_break (0);
- free_melt_data (&md);
- return NULL;
- }
-
- /* build up coins */
- for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
- {
- struct GNUNET_CRYPTO_EcdhePrivateKey *tpk;
-
- tpk = GNUNET_CRYPTO_ecdhe_key_create ();
- md.melted_coin.transfer_priv[i].ecdhe_priv = *tpk;
- GNUNET_free (tpk);
-
- GNUNET_CRYPTO_ecdhe_key_get_public (
- &md.melted_coin.transfer_priv[i].ecdhe_priv,
- &rce[i].transfer_pub.ecdhe_pub);
- TALER_link_derive_transfer_secret (melt_priv,
- &md.melted_coin.transfer_priv[i],
- &trans_sec[i]);
- md.fresh_coins[i] = GNUNET_new_array (fresh_pks_len,
- struct TALER_PlanchetSecretsP);
- rce[i].new_coins = GNUNET_new_array (fresh_pks_len,
- struct TALER_RefreshCoinData);
- for (unsigned int j = 0; j<fresh_pks_len; j++)
- {
- struct TALER_PlanchetSecretsP *fc = &md.fresh_coins[i][j];
- struct TALER_RefreshCoinData *rcd = &rce[i].new_coins[j];
- struct TALER_PlanchetDetail pd;
-
- TALER_planchet_setup_refresh (&trans_sec[i],
- j,
- fc);
- if (GNUNET_OK !=
- TALER_planchet_prepare (&md.fresh_pks[j],
- fc,
- &pd))
- {
- GNUNET_break_op (0);
- free_melt_data (&md);
- return NULL;
- }
- rcd->dk = &md.fresh_pks[j];
- rcd->coin_ev = pd.coin_ev;
- rcd->coin_ev_size = pd.coin_ev_size;
- }
- }
-
- /* Compute refresh commitment */
- TALER_refresh_get_commitment (&md.rc,
- TALER_CNC_KAPPA,
- fresh_pks_len,
- rce,
- &coin_pub,
- melt_amount);
- /* finally, serialize everything */
- buf = serialize_melt_data (&md,
- res_size);
- for (unsigned int i = 0; i < TALER_CNC_KAPPA; i++)
- {
- for (unsigned int j = 0; j < fresh_pks_len; j++)
- GNUNET_free_non_null (rce[i].new_coins[j].coin_ev);
- GNUNET_free_non_null (rce[i].new_coins);
- }
- free_melt_data (&md);
- return buf;
-}
-
-
-/* ********************* /refresh/melt ***************************** */
-
-
-/**
- * @brief A /refresh/melt Handle
- */
-struct TALER_EXCHANGE_RefreshMeltHandle
-{
-
- /**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Context for #TEH_curl_easy_post(). Keeps the data that must
- * persist for Curl to make the upload.
- */
- struct TALER_CURL_PostContext ctx;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with refresh melt failure results.
- */
- TALER_EXCHANGE_RefreshMeltCallback melt_cb;
-
- /**
- * Closure for @e result_cb and @e melt_failure_cb.
- */
- void *melt_cb_cls;
-
- /**
- * Actual information about the melt operation.
- */
- struct MeltData *md;
-
- /**
- * @brief Public information about the coin's denomination key
- */
- struct TALER_EXCHANGE_DenomPublicKey dki;
-};
-
-
-/**
- * Verify that the signature on the "200 OK" response
- * from the exchange is valid.
- *
- * @param rmh melt handle
- * @param json json reply with the signature
- * @param[out] exchange_pub public key of the exchange used for the signature
- * @param[out] noreveal_index set to the noreveal index selected by the exchange
- * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not
- */
-static int
-verify_refresh_melt_signature_ok (struct TALER_EXCHANGE_RefreshMeltHandle *rmh,
- const json_t *json,
- struct TALER_ExchangePublicKeyP *exchange_pub,
- uint32_t *noreveal_index)
-{
- struct TALER_ExchangeSignatureP exchange_sig;
- const struct TALER_EXCHANGE_Keys *key_state;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("exchange_sig", &exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub", exchange_pub),
- GNUNET_JSON_spec_uint32 ("noreveal_index", noreveal_index),
- GNUNET_JSON_spec_end ()
- };
- struct TALER_RefreshMeltConfirmationPS confirm;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- /* check that exchange signing key is permitted */
- key_state = TALER_EXCHANGE_get_keys (rmh->exchange);
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
- exchange_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- /* check that noreveal index is in permitted range */
- if (TALER_CNC_KAPPA <= *noreveal_index)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- /* verify signature by exchange */
- confirm.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT);
- confirm.purpose.size = htonl (sizeof (struct
- TALER_RefreshMeltConfirmationPS));
- confirm.rc = rmh->md->rc;
- confirm.noreveal_index = htonl (*noreveal_index);
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT,
- &confirm.purpose,
- &exchange_sig.eddsa_signature,
- &exchange_pub->eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Verify that the signatures on the "409 CONFLICT" response from the
- * exchange demonstrating customer double-spending are valid.
- *
- * @param rmh melt handle
- * @param json json reply with the signature(s) and transaction history
- * @return #GNUNET_OK if the signature(s) is valid, #GNUNET_SYSERR if not
- */
-static int
-verify_refresh_melt_signature_conflict (struct
- TALER_EXCHANGE_RefreshMeltHandle *rmh,
- const json_t *json)
-{
- json_t *history;
- struct TALER_Amount original_value;
- struct TALER_Amount melt_value_with_fee;
- struct TALER_Amount total;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_json ("history", &history),
- GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub),
- TALER_JSON_spec_amount ("original_value", &original_value),
- TALER_JSON_spec_amount ("requested_value", &melt_value_with_fee),
- GNUNET_JSON_spec_end ()
- };
- const struct MeltedCoin *mc;
-
- /* parse JSON reply */
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- /* Find out which coin was deemed problematic by the exchange */
- mc = &rmh->md->melted_coin;
-
- /* check basic coin properties */
- if (0 != TALER_amount_cmp (&original_value,
- &mc->original_value))
- {
- /* We disagree on the value of the coin */
- GNUNET_break_op (0);
- json_decref (history);
- return GNUNET_SYSERR;
- }
- if (0 != TALER_amount_cmp (&melt_value_with_fee,
- &mc->melt_amount_with_fee))
- {
- /* We disagree on the value of the coin */
- GNUNET_break_op (0);
- json_decref (history);
- return GNUNET_SYSERR;
- }
-
- /* verify coin history */
- history = json_object_get (json,
- "history");
- if (GNUNET_OK !=
- TALER_EXCHANGE_verify_coin_history (&rmh->dki,
- original_value.currency,
- &coin_pub,
- history,
- &total))
- {
- GNUNET_break_op (0);
- json_decref (history);
- return GNUNET_SYSERR;
- }
- json_decref (history);
-
- /* check if melt operation was really too expensive given history */
- if (GNUNET_OK !=
- TALER_amount_add (&total,
- &total,
- &melt_value_with_fee))
- {
- /* clearly not OK if our transaction would have caused
- the overflow... */
- return GNUNET_OK;
- }
-
- if (0 >= TALER_amount_cmp (&total,
- &original_value))
- {
- /* transaction should have still fit */
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
-
- /* everything OK, valid proof of double-spending was provided */
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /refresh/melt request.
- *
- * @param cls the `struct TALER_EXCHANGE_RefreshMeltHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_refresh_melt_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_RefreshMeltHandle *rmh = cls;
- uint32_t noreveal_index = TALER_CNC_KAPPA; /* invalid value */
- struct TALER_ExchangePublicKeyP exchange_pub;
- const json_t *j = response;
-
- rmh->job = NULL;
- switch (response_code)
- {
- case 0:
- break;
- case MHD_HTTP_OK:
- if (GNUNET_OK !=
- verify_refresh_melt_signature_ok (rmh,
- j,
- &exchange_pub,
- &noreveal_index))
- {
- GNUNET_break_op (0);
- response_code = 0;
- }
- if (NULL != rmh->melt_cb)
- {
- rmh->melt_cb (rmh->melt_cb_cls,
- response_code,
- TALER_JSON_get_error_code (j),
- noreveal_index,
- (0 == response_code) ? NULL : &exchange_pub,
- j);
- rmh->melt_cb = NULL;
- }
- break;
- case MHD_HTTP_BAD_REQUEST:
- /* This should never happen, either us or the exchange is buggy
- (or API version conflict); just pass JSON reply to the application */
- break;
- case MHD_HTTP_CONFLICT:
- /* Double spending; check signatures on transaction history */
- if (GNUNET_OK !=
- verify_refresh_melt_signature_conflict (rmh,
- j))
- {
- GNUNET_break_op (0);
- response_code = 0;
- }
- break;
- case MHD_HTTP_FORBIDDEN:
- /* Nothing really to verify, exchange says one of the signatures is
- invalid; assuming we checked them, this should never happen, we
- should pass the JSON reply to the application */
- break;
- case MHD_HTTP_NOT_FOUND:
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- break;
- default:
- /* unexpected response code */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u\n",
- (unsigned int) response_code);
- GNUNET_break (0);
- response_code = 0;
- break;
- }
- if (NULL != rmh->melt_cb)
- rmh->melt_cb (rmh->melt_cb_cls,
- response_code,
- TALER_JSON_get_error_code (j),
- UINT32_MAX,
- NULL,
- j);
- TALER_EXCHANGE_refresh_melt_cancel (rmh);
-}
-
-
-/**
- * Submit a melt request to the exchange and get the exchange's
- * response.
- *
- * This API is typically used by a wallet. Note that to ensure that
- * no money is lost in case of hardware failures, the provided
- * argument should have been constructed using
- * #TALER_EXCHANGE_refresh_prepare and committed to persistent storage
- * prior to calling this function.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param refresh_data_length size of the @a refresh_data (returned
- * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare())
- * @param refresh_data the refresh data as returned from
- #TALER_EXCHANGE_refresh_prepare())
- * @param melt_cb the callback to call with the result
- * @param melt_cb_cls closure for @a melt_cb
- * @return a handle for this request; NULL if the argument was invalid.
- * In this case, neither callback will be called.
- */
-struct TALER_EXCHANGE_RefreshMeltHandle *
-TALER_EXCHANGE_refresh_melt (struct TALER_EXCHANGE_Handle *exchange,
- size_t refresh_data_length,
- const char *refresh_data,
- TALER_EXCHANGE_RefreshMeltCallback melt_cb,
- void *melt_cb_cls)
-{
- const struct TALER_EXCHANGE_Keys *key_state;
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
- json_t *melt_obj;
- struct TALER_EXCHANGE_RefreshMeltHandle *rmh;
- CURL *eh;
- struct GNUNET_CURL_Context *ctx;
- struct MeltData *md;
- struct TALER_CoinSpendSignatureP confirm_sig;
- struct TALER_RefreshMeltCoinAffirmationPS melt;
- struct GNUNET_HashCode h_denom_pub;
- char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
-
- GNUNET_assert (GNUNET_YES ==
- TEAH_handle_is_ready (exchange));
- md = deserialize_melt_data (refresh_data,
- refresh_data_length);
- if (NULL == md)
- {
- GNUNET_break (0);
- return NULL;
- }
- melt.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT);
- melt.purpose.size = htonl (sizeof (struct
- TALER_RefreshMeltCoinAffirmationPS));
- melt.rc = md->rc;
- TALER_amount_hton (&melt.amount_with_fee,
- &md->melted_coin.melt_amount_with_fee);
- TALER_amount_hton (&melt.melt_fee,
- &md->melted_coin.fee_melt);
- GNUNET_CRYPTO_eddsa_key_get_public (&md->melted_coin.coin_priv.eddsa_priv,
- &melt.coin_pub.eddsa_pub);
- GNUNET_CRYPTO_eddsa_sign (&md->melted_coin.coin_priv.eddsa_priv,
- &melt.purpose,
- &confirm_sig.eddsa_signature);
- GNUNET_CRYPTO_rsa_public_key_hash (md->melted_coin.pub_key.rsa_public_key,
- &h_denom_pub);
- melt_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}",
- "coin_pub",
- GNUNET_JSON_from_data_auto (&melt.coin_pub),
- "denom_pub_hash",
- GNUNET_JSON_from_data_auto (&h_denom_pub),
- "denom_sig",
- GNUNET_JSON_from_rsa_signature (
- md->melted_coin.sig.rsa_signature),
- "confirm_sig",
- GNUNET_JSON_from_data_auto (&confirm_sig),
- "value_with_fee",
- TALER_JSON_from_amount (
- &md->melted_coin.melt_amount_with_fee),
- "rc",
- GNUNET_JSON_from_data_auto (&melt.rc));
- if (NULL == melt_obj)
- {
- GNUNET_break (0);
- free_melt_data (md);
- return NULL;
- }
- {
- char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (&melt.coin_pub,
- sizeof (struct
- TALER_CoinSpendPublicKeyP),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "/coins/%s/melt",
- pub_str);
- }
-
- key_state = TALER_EXCHANGE_get_keys (exchange);
- dki = TALER_EXCHANGE_get_denomination_key (key_state,
- &md->melted_coin.pub_key);
-
- /* and now we can at last begin the actual request handling */
- rmh = GNUNET_new (struct TALER_EXCHANGE_RefreshMeltHandle);
- rmh->exchange = exchange;
- rmh->dki = *dki;
- rmh->dki.key.rsa_public_key = NULL; /* lifetime not warranted, so better
- not copy the pointer */
- rmh->melt_cb = melt_cb;
- rmh->melt_cb_cls = melt_cb_cls;
- rmh->md = md;
- rmh->url = TEAH_path_to_url (exchange,
- arg_str);
- eh = TEL_curl_easy_get (rmh->url);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&rmh->ctx,
- eh,
- melt_obj))
- {
- GNUNET_break (0);
- curl_easy_cleanup (eh);
- json_decref (melt_obj);
- GNUNET_free (rmh->url);
- GNUNET_free (rmh);
- return NULL;
- }
- json_decref (melt_obj);
- ctx = TEAH_handle_to_context (exchange);
- rmh->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- rmh->ctx.headers,
- &handle_refresh_melt_finished,
- rmh);
- return rmh;
-}
-
-
-/**
- * Cancel a refresh execute request. This function cannot be used
- * on a request handle if either callback was already invoked.
- *
- * @param rmh the refresh melt handle
- */
-void
-TALER_EXCHANGE_refresh_melt_cancel (struct
- TALER_EXCHANGE_RefreshMeltHandle *rmh)
-{
- if (NULL != rmh->job)
- {
- GNUNET_CURL_job_cancel (rmh->job);
- rmh->job = NULL;
- }
- free_melt_data (rmh->md); /* does not free 'md' itself */
- GNUNET_free (rmh->md);
- GNUNET_free (rmh->url);
- TALER_curl_easy_post_finished (&rmh->ctx);
- GNUNET_free (rmh);
-}
-
-
-/* ********************* /refreshes/$RCH/reveal ***************************** */
-
-
-/**
- * @brief A /refreshes/$RCH/reveal Handle
- */
-struct TALER_EXCHANGE_RefreshRevealHandle
-{
-
- /**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Context for #TEH_curl_easy_post(). Keeps the data that must
- * persist for Curl to make the upload.
- */
- struct TALER_CURL_PostContext ctx;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_RefreshRevealCallback reveal_cb;
-
- /**
- * Closure for @e reveal_cb.
- */
- void *reveal_cb_cls;
-
- /**
- * Actual information about the melt operation.
- */
- struct MeltData *md;
-
- /**
- * The index selected by the exchange in cut-and-choose to not be revealed.
- */
- uint16_t noreveal_index;
-
-};
-
-
-/**
- * We got a 200 OK response for the /refreshes/$RCH/reveal operation.
- * Extract the coin signatures and return them to the caller.
- * The signatures we get from the exchange is for the blinded value.
- * Thus, we first must unblind them and then should verify their
- * validity.
- *
- * If everything checks out, we return the unblinded signatures
- * to the application via the callback.
- *
- * @param rrh operation handle
- * @param json reply from the exchange
- * @param[out] sigs array of length `num_fresh_coins`, initialized to cointain RSA signatures
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static int
-refresh_reveal_ok (struct TALER_EXCHANGE_RefreshRevealHandle *rrh,
- const json_t *json,
- struct TALER_DenominationSignature *sigs)
-{
- json_t *jsona;
- struct GNUNET_JSON_Specification outer_spec[] = {
- GNUNET_JSON_spec_json ("ev_sigs", &jsona),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- outer_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (! json_is_array (jsona))
- {
- /* We expected an array of coins */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (outer_spec);
- return GNUNET_SYSERR;
- }
- if (rrh->md->num_fresh_coins != json_array_size (jsona))
- {
- /* Number of coins generated does not match our expectation */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (outer_spec);
- return GNUNET_SYSERR;
- }
- for (unsigned int i = 0; i<rrh->md->num_fresh_coins; i++)
- {
- const struct TALER_PlanchetSecretsP *fc;
- struct TALER_DenominationPublicKey *pk;
- json_t *jsonai;
- struct GNUNET_CRYPTO_RsaSignature *blind_sig;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct GNUNET_HashCode coin_hash;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_rsa_signature ("ev_sig", &blind_sig),
- GNUNET_JSON_spec_end ()
- };
- struct TALER_FreshCoin coin;
-
- fc = &rrh->md->fresh_coins[rrh->noreveal_index][i];
- pk = &rrh->md->fresh_pks[i];
- jsonai = json_array_get (jsona, i);
- GNUNET_assert (NULL != jsonai);
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (jsonai,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (outer_spec);
- return GNUNET_SYSERR;
- }
-
- /* needed to verify the signature, and we didn't store it earlier,
- hence recomputing it here... */
- GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv,
- &coin_pub.eddsa_pub);
- GNUNET_CRYPTO_hash (&coin_pub.eddsa_pub,
- sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
- &coin_hash);
- if (GNUNET_OK !=
- TALER_planchet_to_coin (pk,
- blind_sig,
- fc,
- &coin_hash,
- &coin))
- {
- GNUNET_break_op (0);
- GNUNET_CRYPTO_rsa_signature_free (blind_sig);
- GNUNET_JSON_parse_free (outer_spec);
- return GNUNET_SYSERR;
- }
- GNUNET_CRYPTO_rsa_signature_free (blind_sig);
- sigs[i] = coin.sig;
- }
- GNUNET_JSON_parse_free (outer_spec);
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /refreshes/$RCH/reveal request.
- *
- * @param cls the `struct TALER_EXCHANGE_RefreshHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_refresh_reveal_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_RefreshRevealHandle *rrh = cls;
- const json_t *j = response;
-
- rrh->job = NULL;
- switch (response_code)
- {
- case 0:
- break;
- case MHD_HTTP_OK:
- {
- struct TALER_DenominationSignature sigs[rrh->md->num_fresh_coins];
- int ret;
-
- memset (sigs, 0, sizeof (sigs));
- ret = refresh_reveal_ok (rrh,
- j,
- sigs);
- if (GNUNET_OK != ret)
- {
- response_code = 0;
- }
- else
- {
- rrh->reveal_cb (rrh->reveal_cb_cls,
- MHD_HTTP_OK,
- TALER_EC_NONE,
- rrh->md->num_fresh_coins,
- rrh->md->fresh_coins[rrh->noreveal_index],
- sigs,
- j);
- rrh->reveal_cb = NULL;
- }
- for (unsigned int i = 0; i<rrh->md->num_fresh_coins; i++)
- if (NULL != sigs[i].rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature);
- }
- break;
- case MHD_HTTP_BAD_REQUEST:
- /* This should never happen, either us or the exchange is buggy
- (or API version conflict); just pass JSON reply to the application */
- break;
- case MHD_HTTP_CONFLICT:
- /* Nothing really to verify, exchange says our reveal is inconsitent
- with our commitment, so either side is buggy; we
- should pass the JSON reply to the application */
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- break;
- default:
- /* unexpected response code */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u\n",
- (unsigned int) response_code);
- GNUNET_break (0);
- response_code = 0;
- break;
- }
- if (NULL != rrh->reveal_cb)
- rrh->reveal_cb (rrh->reveal_cb_cls,
- response_code,
- TALER_JSON_get_error_code (j),
- 0,
- NULL,
- NULL,
- j);
- TALER_EXCHANGE_refresh_reveal_cancel (rrh);
-}
-
-
-/**
- * Submit a /refresh/reval request to the exchange and get the exchange's
- * response.
- *
- * This API is typically used by a wallet. Note that to ensure that
- * no money is lost in case of hardware failures, the provided
- * arguments should have been committed to persistent storage
- * prior to calling this function.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param refresh_data_length size of the @a refresh_data (returned
- * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare())
- * @param refresh_data the refresh data as returned from
- #TALER_EXCHANGE_refresh_prepare())
- * @param noreveal_index response from the exchange to the
- * #TALER_EXCHANGE_refresh_melt() invocation
- * @param reveal_cb the callback to call with the final result of the
- * refresh operation
- * @param reveal_cb_cls closure for the above callback
- * @return a handle for this request; NULL if the argument was invalid.
- * In this case, neither callback will be called.
- */
-struct TALER_EXCHANGE_RefreshRevealHandle *
-TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange,
- size_t refresh_data_length,
- const char *refresh_data,
- uint32_t noreveal_index,
- TALER_EXCHANGE_RefreshRevealCallback reveal_cb,
- void *reveal_cb_cls)
-{
- struct TALER_EXCHANGE_RefreshRevealHandle *rrh;
- json_t *transfer_privs;
- json_t *new_denoms_h;
- json_t *coin_evs;
- json_t *reveal_obj;
- json_t *link_sigs;
- CURL *eh;
- struct GNUNET_CURL_Context *ctx;
- struct MeltData *md;
- struct TALER_TransferPublicKeyP transfer_pub;
- char arg_str[sizeof (struct TALER_RefreshCommitmentP) * 2 + 32];
-
- if (noreveal_index >= TALER_CNC_KAPPA)
- {
- /* We check this here, as it would be really bad to below just
- disclose all the transfer keys. Note that this error should
- have been caught way earlier when the exchange replied, but maybe
- we had some internal corruption that changed the value... */
- GNUNET_break (0);
- return NULL;
- }
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
- md = deserialize_melt_data (refresh_data,
- refresh_data_length);
- if (NULL == md)
- {
- GNUNET_break (0);
- return NULL;
- }
-
- /* now transfer_pub */
- GNUNET_CRYPTO_ecdhe_key_get_public (
- &md->melted_coin.transfer_priv[noreveal_index].ecdhe_priv,
- &transfer_pub.ecdhe_pub);
-
- /* now new_denoms */
- GNUNET_assert (NULL != (new_denoms_h = json_array ()));
- GNUNET_assert (NULL != (coin_evs = json_array ()));
- GNUNET_assert (NULL != (link_sigs = json_array ()));
- for (unsigned int i = 0; i<md->num_fresh_coins; i++)
- {
- struct GNUNET_HashCode denom_hash;
- struct TALER_PlanchetDetail pd;
-
- GNUNET_CRYPTO_rsa_public_key_hash (md->fresh_pks[i].rsa_public_key,
- &denom_hash);
- GNUNET_assert (0 ==
- json_array_append_new (new_denoms_h,
- GNUNET_JSON_from_data_auto (
- &denom_hash)));
-
- if (GNUNET_OK !=
- TALER_planchet_prepare (&md->fresh_pks[i],
- &md->fresh_coins[noreveal_index][i],
- &pd))
- {
- /* This should have been noticed during the preparation stage. */
- GNUNET_break (0);
- json_decref (new_denoms_h);
- json_decref (coin_evs);
- return NULL;
- }
- GNUNET_assert (0 ==
- json_array_append_new (coin_evs,
- GNUNET_JSON_from_data (pd.coin_ev,
- pd.coin_ev_size)));
-
- /* compute link signature */
- {
- struct TALER_CoinSpendSignatureP link_sig;
- struct TALER_LinkDataPS ldp;
-
- ldp.purpose.size = htonl (sizeof (ldp));
- ldp.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK);
- ldp.h_denom_pub = denom_hash;
- GNUNET_CRYPTO_eddsa_key_get_public (&md->melted_coin.coin_priv.eddsa_priv,
- &ldp.old_coin_pub.eddsa_pub);
- ldp.transfer_pub = transfer_pub;
- GNUNET_CRYPTO_hash (pd.coin_ev,
- pd.coin_ev_size,
- &ldp.coin_envelope_hash);
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (
- &md->melted_coin.coin_priv.eddsa_priv,
- &ldp.purpose,
- &link_sig.eddsa_signature));
- GNUNET_assert (0 ==
- json_array_append_new (link_sigs,
- GNUNET_JSON_from_data_auto (
- &link_sig)));
- }
-
- GNUNET_free (pd.coin_ev);
- }
-
- /* build array of transfer private keys */
- GNUNET_assert (NULL != (transfer_privs = json_array ()));
- for (unsigned int j = 0; j<TALER_CNC_KAPPA; j++)
- {
- if (j == noreveal_index)
- {
- /* This is crucial: exclude the transfer key for the
- noreval index! */
- continue;
- }
- GNUNET_assert (0 ==
- json_array_append_new (transfer_privs,
- GNUNET_JSON_from_data_auto (
- &md->melted_coin.transfer_priv[j])));
- }
-
- /* build main JSON request */
- reveal_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o}",
- "transfer_pub",
- GNUNET_JSON_from_data_auto (&transfer_pub),
- "transfer_privs",
- transfer_privs,
- "link_sigs",
- link_sigs,
- "new_denoms_h",
- new_denoms_h,
- "coin_evs",
- coin_evs);
- if (NULL == reveal_obj)
- {
- GNUNET_break (0);
- return NULL;
- }
-
- {
- char pub_str[sizeof (struct TALER_RefreshCommitmentP) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (&md->rc,
- sizeof (struct
- TALER_RefreshCommitmentP),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "/refreshes/%s/reveal",
- pub_str);
- }
- /* finally, we can actually issue the request */
- rrh = GNUNET_new (struct TALER_EXCHANGE_RefreshRevealHandle);
- rrh->exchange = exchange;
- rrh->noreveal_index = noreveal_index;
- rrh->reveal_cb = reveal_cb;
- rrh->reveal_cb_cls = reveal_cb_cls;
- rrh->md = md;
- rrh->url = TEAH_path_to_url (rrh->exchange,
- arg_str);
-
- eh = TEL_curl_easy_get (rrh->url);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&rrh->ctx,
- eh,
- reveal_obj))
- {
- GNUNET_break (0);
- curl_easy_cleanup (eh);
- json_decref (reveal_obj);
- GNUNET_free (rrh->url);
- GNUNET_free (rrh);
- return NULL;
- }
- json_decref (reveal_obj);
- ctx = TEAH_handle_to_context (rrh->exchange);
- rrh->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- rrh->ctx.headers,
- &handle_refresh_reveal_finished,
- rrh);
- return rrh;
-}
-
-
-/**
- * Cancel a refresh reveal request. This function cannot be used
- * on a request handle if the callback was already invoked.
- *
- * @param rrh the refresh reval handle
- */
-void
-TALER_EXCHANGE_refresh_reveal_cancel (struct
- TALER_EXCHANGE_RefreshRevealHandle *rrh)
-{
- if (NULL != rrh->job)
- {
- GNUNET_CURL_job_cancel (rrh->job);
- rrh->job = NULL;
- }
- GNUNET_free (rrh->url);
- TALER_curl_easy_post_finished (&rrh->ctx);
- free_melt_data (rrh->md); /* does not free 'md' itself */
- GNUNET_free (rrh->md);
- GNUNET_free (rrh);
-}
-
-
-/* end of exchange_api_refresh.c */
diff --git a/src/lib/exchange_api_refresh_common.c b/src/lib/exchange_api_refresh_common.c
new file mode 100644
index 000000000..b2eec2914
--- /dev/null
+++ b/src/lib/exchange_api_refresh_common.c
@@ -0,0 +1,631 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_refresh_common.c
+ * @brief Serialization logic shared between melt and reveal steps during refreshing
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "exchange_api_refresh_common.h"
+
+
+/**
+ * Free all information associated with a melted coin session.
+ *
+ * @param mc melted coin to release, the pointer itself is NOT
+ * freed (as it is typically not allocated by itself)
+ */
+static void
+free_melted_coin (struct MeltedCoin *mc)
+{
+ if (NULL != mc->pub_key.rsa_public_key)
+ GNUNET_CRYPTO_rsa_public_key_free (mc->pub_key.rsa_public_key);
+ if (NULL != mc->sig.rsa_signature)
+ GNUNET_CRYPTO_rsa_signature_free (mc->sig.rsa_signature);
+}
+
+
+/**
+ * Free all information associated with a melting session. Note
+ * that we allow the melting session to be only partially initialized,
+ * as we use this function also when freeing melt data that was not
+ * fully initialized (i.e. due to failures in #TALER_EXCHANGE_deserialize_melt_data_()).
+ *
+ * @param md melting data to release, the pointer itself is NOT
+ * freed (as it is typically not allocated by itself)
+ */
+void
+TALER_EXCHANGE_free_melt_data_ (struct MeltData *md)
+{
+ free_melted_coin (&md->melted_coin);
+ if (NULL != md->fresh_pks)
+ {
+ for (unsigned int i = 0; i<md->num_fresh_coins; i++)
+ if (NULL != md->fresh_pks[i].rsa_public_key)
+ GNUNET_CRYPTO_rsa_public_key_free (md->fresh_pks[i].rsa_public_key);
+ GNUNET_free (md->fresh_pks);
+ }
+
+ for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
+ GNUNET_free_non_null (md->fresh_coins[i]);
+ /* Finally, clean up a bit...
+ (NOTE: compilers might optimize this away, so this is
+ not providing any strong assurances that the key material
+ is purged.) */
+ memset (md,
+ 0,
+ sizeof (struct MeltData));
+}
+
+
+/**
+ * Serialize information about a coin we are melting.
+ *
+ * @param mc information to serialize
+ * @param buf buffer to write data in, NULL to just compute
+ * required size
+ * @param off offeset at @a buf to use
+ * @return number of bytes written to @a buf at @a off, or if
+ * @a buf is NULL, number of bytes required; 0 on error
+ */
+static size_t
+serialize_melted_coin (const struct MeltedCoin *mc,
+ char *buf,
+ size_t off)
+{
+ struct MeltedCoinP mcp;
+ unsigned int i;
+ char *pbuf;
+ size_t pbuf_size;
+ char *sbuf;
+ size_t sbuf_size;
+
+ sbuf_size = GNUNET_CRYPTO_rsa_signature_encode (mc->sig.rsa_signature,
+ &sbuf);
+ pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (mc->pub_key.rsa_public_key,
+ &pbuf);
+ if (NULL == buf)
+ {
+ GNUNET_free (sbuf);
+ GNUNET_free (pbuf);
+ return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size;
+ }
+ if ( (sbuf_size > UINT16_MAX) ||
+ (pbuf_size > UINT16_MAX) )
+ {
+ GNUNET_break (0);
+ return 0;
+ }
+ mcp.coin_priv = mc->coin_priv;
+ TALER_amount_hton (&mcp.melt_amount_with_fee,
+ &mc->melt_amount_with_fee);
+ TALER_amount_hton (&mcp.fee_melt,
+ &mc->fee_melt);
+ TALER_amount_hton (&mcp.original_value,
+ &mc->original_value);
+ for (i = 0; i<TALER_CNC_KAPPA; i++)
+ mcp.transfer_priv[i] = mc->transfer_priv[i];
+ mcp.expire_deposit = GNUNET_TIME_absolute_hton (mc->expire_deposit);
+ mcp.pbuf_size = htons ((uint16_t) pbuf_size);
+ mcp.sbuf_size = htons ((uint16_t) sbuf_size);
+ memcpy (&buf[off],
+ &mcp,
+ sizeof (struct MeltedCoinP));
+ memcpy (&buf[off + sizeof (struct MeltedCoinP)],
+ pbuf,
+ pbuf_size);
+ memcpy (&buf[off + sizeof (struct MeltedCoinP) + pbuf_size],
+ sbuf,
+ sbuf_size);
+ GNUNET_free (sbuf);
+ GNUNET_free (pbuf);
+ return sizeof (struct MeltedCoinP) + sbuf_size + pbuf_size;
+}
+
+
+/**
+ * Deserialize information about a coin we are melting.
+ *
+ * @param[out] mc information to deserialize
+ * @param buf buffer to read data from
+ * @param size number of bytes available at @a buf to use
+ * @param[out] ok set to #GNUNET_NO to report errors
+ * @return number of bytes read from @a buf, 0 on error
+ */
+static size_t
+deserialize_melted_coin (struct MeltedCoin *mc,
+ const char *buf,
+ size_t size,
+ int *ok)
+{
+ struct MeltedCoinP mcp;
+ unsigned int i;
+ size_t pbuf_size;
+ size_t sbuf_size;
+ size_t off;
+
+ if (size < sizeof (struct MeltedCoinP))
+ {
+ GNUNET_break (0);
+ *ok = GNUNET_NO;
+ return 0;
+ }
+ memcpy (&mcp,
+ buf,
+ sizeof (struct MeltedCoinP));
+ pbuf_size = ntohs (mcp.pbuf_size);
+ sbuf_size = ntohs (mcp.sbuf_size);
+ if (size < sizeof (struct MeltedCoinP) + pbuf_size + sbuf_size)
+ {
+ GNUNET_break (0);
+ *ok = GNUNET_NO;
+ return 0;
+ }
+ off = sizeof (struct MeltedCoinP);
+ mc->pub_key.rsa_public_key
+ = GNUNET_CRYPTO_rsa_public_key_decode (&buf[off],
+ pbuf_size);
+ off += pbuf_size;
+ mc->sig.rsa_signature
+ = GNUNET_CRYPTO_rsa_signature_decode (&buf[off],
+ sbuf_size);
+ off += sbuf_size;
+ if ( (NULL == mc->pub_key.rsa_public_key) ||
+ (NULL == mc->sig.rsa_signature) )
+ {
+ GNUNET_break (0);
+ *ok = GNUNET_NO;
+ return 0;
+ }
+
+ mc->coin_priv = mcp.coin_priv;
+ TALER_amount_ntoh (&mc->melt_amount_with_fee,
+ &mcp.melt_amount_with_fee);
+ TALER_amount_ntoh (&mc->fee_melt,
+ &mcp.fee_melt);
+ TALER_amount_ntoh (&mc->original_value,
+ &mcp.original_value);
+ for (i = 0; i<TALER_CNC_KAPPA; i++)
+ mc->transfer_priv[i] = mcp.transfer_priv[i];
+ mc->expire_deposit = GNUNET_TIME_absolute_ntoh (mcp.expire_deposit);
+ return off;
+}
+
+
+/**
+ * Serialize information about a denomination key.
+ *
+ * @param dk information to serialize
+ * @param buf buffer to write data in, NULL to just compute
+ * required size
+ * @param off offeset at @a buf to use
+ * @return number of bytes written to @a buf at @a off, or if
+ * @a buf is NULL, number of bytes required
+ */
+static size_t
+serialize_denomination_key (const struct TALER_DenominationPublicKey *dk,
+ char *buf,
+ size_t off)
+{
+ char *pbuf;
+ size_t pbuf_size;
+ uint32_t be;
+
+ pbuf_size = GNUNET_CRYPTO_rsa_public_key_encode (dk->rsa_public_key,
+ &pbuf);
+ if (NULL == buf)
+ {
+ GNUNET_free (pbuf);
+ return pbuf_size + sizeof (uint32_t);
+ }
+ be = htonl ((uint32_t) pbuf_size);
+ memcpy (&buf[off],
+ &be,
+ sizeof (uint32_t));
+ memcpy (&buf[off + sizeof (uint32_t)],
+ pbuf,
+ pbuf_size);
+ GNUNET_free (pbuf);
+ return pbuf_size + sizeof (uint32_t);
+}
+
+
+/**
+ * Deserialize information about a denomination key.
+ *
+ * @param[out] dk information to deserialize
+ * @param buf buffer to read data from
+ * @param size number of bytes available at @a buf to use
+ * @param[out] ok set to #GNUNET_NO to report errors
+ * @return number of bytes read from @a buf, 0 on error
+ */
+static size_t
+deserialize_denomination_key (struct TALER_DenominationPublicKey *dk,
+ const char *buf,
+ size_t size,
+ int *ok)
+{
+ size_t pbuf_size;
+ uint32_t be;
+
+ if (size < sizeof (uint32_t))
+ {
+ GNUNET_break (0);
+ *ok = GNUNET_NO;
+ return 0;
+ }
+ memcpy (&be,
+ buf,
+ sizeof (uint32_t));
+ pbuf_size = ntohl (be);
+ if (size < sizeof (uint32_t) + pbuf_size)
+ {
+ GNUNET_break (0);
+ *ok = GNUNET_NO;
+ return 0;
+ }
+ dk->rsa_public_key
+ = GNUNET_CRYPTO_rsa_public_key_decode (&buf[sizeof (uint32_t)],
+ pbuf_size);
+ if (NULL == dk->rsa_public_key)
+ {
+ GNUNET_break (0);
+ *ok = GNUNET_NO;
+ return 0;
+ }
+ return sizeof (uint32_t) + pbuf_size;
+}
+
+
+/**
+ * Serialize information about a fresh coin we are generating.
+ *
+ * @param fc information to serialize
+ * @param buf buffer to write data in, NULL to just compute
+ * required size
+ * @param off offeset at @a buf to use
+ * @return number of bytes written to @a buf at @a off, or if
+ * @a buf is NULL, number of bytes required
+ */
+static size_t
+serialize_fresh_coin (const struct TALER_PlanchetSecretsP *fc,
+ char *buf,
+ size_t off)
+{
+ if (NULL != buf)
+ memcpy (&buf[off],
+ fc,
+ sizeof (struct TALER_PlanchetSecretsP));
+ return sizeof (struct TALER_PlanchetSecretsP);
+}
+
+
+/**
+ * Deserialize information about a fresh coin we are generating.
+ *
+ * @param[out] fc information to deserialize
+ * @param buf buffer to read data from
+ * @param size number of bytes available at @a buf to use
+ * @param[out] ok set to #GNUNET_NO to report errors
+ * @return number of bytes read from @a buf, 0 on error
+ */
+static size_t
+deserialize_fresh_coin (struct TALER_PlanchetSecretsP *fc,
+ const char *buf,
+ size_t size,
+ int *ok)
+{
+ if (size < sizeof (struct TALER_PlanchetSecretsP))
+ {
+ GNUNET_break (0);
+ *ok = GNUNET_NO;
+ return 0;
+ }
+ memcpy (fc,
+ buf,
+ sizeof (struct TALER_PlanchetSecretsP));
+ return sizeof (struct TALER_PlanchetSecretsP);
+}
+
+
+/**
+ * Serialize melt data.
+ *
+ * @param md data to serialize
+ * @param[out] res_size size of buffer returned
+ * @return serialized melt data
+ */
+static char *
+serialize_melt_data (const struct MeltData *md,
+ size_t *res_size)
+{
+ size_t size;
+ size_t asize;
+ char *buf;
+
+ size = 0;
+ asize = (size_t) -1; /* make the compiler happy */
+ buf = NULL;
+ /* we do 2 iterations, #1 to determine total size, #2 to
+ actually construct the buffer */
+ do {
+ if (0 == size)
+ {
+ size = sizeof (struct MeltDataP);
+ }
+ else
+ {
+ struct MeltDataP *mdp;
+
+ buf = GNUNET_malloc (size);
+ asize = size; /* just for invariant check later */
+ size = sizeof (struct MeltDataP);
+ mdp = (struct MeltDataP *) buf;
+ mdp->rc = md->rc;
+ mdp->num_fresh_coins = htons (md->num_fresh_coins);
+ }
+ size += serialize_melted_coin (&md->melted_coin,
+ buf,
+ size);
+ for (unsigned int i = 0; i<md->num_fresh_coins; i++)
+ size += serialize_denomination_key (&md->fresh_pks[i],
+ buf,
+ size);
+ for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
+ for (unsigned int j = 0; j<md->num_fresh_coins; j++)
+ size += serialize_fresh_coin (&md->fresh_coins[i][j],
+ buf,
+ size);
+ } while (NULL == buf);
+ GNUNET_assert (size == asize);
+ *res_size = size;
+ return buf;
+}
+
+
+/**
+ * Deserialize melt data.
+ *
+ * @param buf serialized data
+ * @param buf_size size of @a buf
+ * @return deserialized melt data, NULL on error
+ */
+struct MeltData *
+TALER_EXCHANGE_deserialize_melt_data_ (const char *buf,
+ size_t buf_size)
+{
+ struct MeltData *md;
+ struct MeltDataP mdp;
+ size_t off;
+ int ok;
+
+ if (buf_size < sizeof (struct MeltDataP))
+ return NULL;
+ memcpy (&mdp,
+ buf,
+ sizeof (struct MeltDataP));
+ md = GNUNET_new (struct MeltData);
+ md->rc = mdp.rc;
+ md->num_fresh_coins = ntohs (mdp.num_fresh_coins);
+ md->fresh_pks = GNUNET_new_array (md->num_fresh_coins,
+ struct TALER_DenominationPublicKey);
+ for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
+ md->fresh_coins[i] = GNUNET_new_array (md->num_fresh_coins,
+ struct TALER_PlanchetSecretsP);
+ off = sizeof (struct MeltDataP);
+ ok = GNUNET_YES;
+ off += deserialize_melted_coin (&md->melted_coin,
+ &buf[off],
+ buf_size - off,
+ &ok);
+ for (unsigned int i = 0; (i<md->num_fresh_coins) && (GNUNET_YES == ok); i++)
+ off += deserialize_denomination_key (&md->fresh_pks[i],
+ &buf[off],
+ buf_size - off,
+ &ok);
+
+ for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
+ for (unsigned int j = 0; (j<md->num_fresh_coins) && (GNUNET_YES == ok); j++)
+ off += deserialize_fresh_coin (&md->fresh_coins[i][j],
+ &buf[off],
+ buf_size - off,
+ &ok);
+ if (off != buf_size)
+ {
+ GNUNET_break (0);
+ ok = GNUNET_NO;
+ }
+ if (GNUNET_YES != ok)
+ {
+ TALER_EXCHANGE_free_melt_data_ (md);
+ GNUNET_free (md);
+ return NULL;
+ }
+ return md;
+}
+
+
+/**
+ * Melt (partially spent) coins to obtain fresh coins that are
+ * unlinkable to the original coin(s). Note that melting more
+ * than one coin in a single request will make those coins linkable,
+ * so the safest operation only melts one coin at a time.
+ *
+ * This API is typically used by a wallet. Note that to ensure that
+ * no money is lost in case of hardware failures, this operation does
+ * not actually initiate the request. Instead, it generates a buffer
+ * which the caller must store before proceeding with the actual call
+ * to #TALER_EXCHANGE_melt() that will generate the request.
+ *
+ * This function does verify that the given request data is internally
+ * consistent. However, the @a melts_sigs are NOT verified.
+ *
+ * Aside from some non-trivial cryptographic operations that might
+ * take a bit of CPU time to complete, this function returns
+ * its result immediately and does not start any asynchronous
+ * processing. This function is also thread-safe.
+ *
+ * @param melt_priv private key of the coin to melt
+ * @param melt_amount amount specifying how much
+ * the coin will contribute to the melt (including fee)
+ * @param melt_sig signature affirming the
+ * validity of the public keys corresponding to the
+ * @a melt_priv private key
+ * @param melt_pk denomination key information
+ * record corresponding to the @a melt_sig
+ * validity of the keys
+ * @param fresh_pks_len length of the @a pks array
+ * @param fresh_pks array of @a pks_len denominations of fresh coins to create
+ * @param[out] res_size set to the size of the return value, or 0 on error
+ * @return NULL
+ * if the inputs are invalid (i.e. denomination key not with this exchange).
+ * Otherwise, pointer to a buffer of @a res_size to store persistently
+ * before proceeding to #TALER_EXCHANGE_melt().
+ * Non-null results should be freed using GNUNET_free().
+ */
+char *
+TALER_EXCHANGE_refresh_prepare (const struct
+ TALER_CoinSpendPrivateKeyP *melt_priv,
+ const struct TALER_Amount *melt_amount,
+ const struct
+ TALER_DenominationSignature *melt_sig,
+ const struct
+ TALER_EXCHANGE_DenomPublicKey *melt_pk,
+ unsigned int fresh_pks_len,
+ const struct
+ TALER_EXCHANGE_DenomPublicKey *fresh_pks,
+ size_t *res_size)
+{
+ struct MeltData md;
+ char *buf;
+ struct TALER_Amount total;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_TransferSecretP trans_sec[TALER_CNC_KAPPA];
+ struct TALER_RefreshCommitmentEntry rce[TALER_CNC_KAPPA];
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&melt_priv->eddsa_priv,
+ &coin_pub.eddsa_pub);
+ /* build up melt data structure */
+ memset (&md, 0, sizeof (md));
+ md.num_fresh_coins = fresh_pks_len;
+ md.melted_coin.coin_priv = *melt_priv;
+ md.melted_coin.melt_amount_with_fee = *melt_amount;
+ md.melted_coin.fee_melt = melt_pk->fee_refresh;
+ md.melted_coin.original_value = melt_pk->value;
+ md.melted_coin.expire_deposit
+ = melt_pk->expire_deposit;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_get_zero (melt_amount->currency,
+ &total));
+ md.melted_coin.pub_key.rsa_public_key
+ = GNUNET_CRYPTO_rsa_public_key_dup (melt_pk->key.rsa_public_key);
+ md.melted_coin.sig.rsa_signature
+ = GNUNET_CRYPTO_rsa_signature_dup (melt_sig->rsa_signature);
+ md.fresh_pks = GNUNET_new_array (fresh_pks_len,
+ struct TALER_DenominationPublicKey);
+ for (unsigned int i = 0; i<fresh_pks_len; i++)
+ {
+ md.fresh_pks[i].rsa_public_key
+ = GNUNET_CRYPTO_rsa_public_key_dup (fresh_pks[i].key.rsa_public_key);
+ if ( (GNUNET_OK !=
+ TALER_amount_add (&total,
+ &total,
+ &fresh_pks[i].value)) ||
+ (GNUNET_OK !=
+ TALER_amount_add (&total,
+ &total,
+ &fresh_pks[i].fee_withdraw)) )
+ {
+ GNUNET_break (0);
+ TALER_EXCHANGE_free_melt_data_ (&md);
+ return NULL;
+ }
+ }
+ /* verify that melt_amount is above total cost */
+ if (1 ==
+ TALER_amount_cmp (&total,
+ melt_amount) )
+ {
+ /* Eh, this operation is more expensive than the
+ @a melt_amount. This is not OK. */
+ GNUNET_break (0);
+ TALER_EXCHANGE_free_melt_data_ (&md);
+ return NULL;
+ }
+
+ /* build up coins */
+ for (unsigned int i = 0; i<TALER_CNC_KAPPA; i++)
+ {
+ struct GNUNET_CRYPTO_EcdhePrivateKey *tpk;
+
+ tpk = GNUNET_CRYPTO_ecdhe_key_create ();
+ md.melted_coin.transfer_priv[i].ecdhe_priv = *tpk;
+ GNUNET_free (tpk);
+
+ GNUNET_CRYPTO_ecdhe_key_get_public (
+ &md.melted_coin.transfer_priv[i].ecdhe_priv,
+ &rce[i].transfer_pub.ecdhe_pub);
+ TALER_link_derive_transfer_secret (melt_priv,
+ &md.melted_coin.transfer_priv[i],
+ &trans_sec[i]);
+ md.fresh_coins[i] = GNUNET_new_array (fresh_pks_len,
+ struct TALER_PlanchetSecretsP);
+ rce[i].new_coins = GNUNET_new_array (fresh_pks_len,
+ struct TALER_RefreshCoinData);
+ for (unsigned int j = 0; j<fresh_pks_len; j++)
+ {
+ struct TALER_PlanchetSecretsP *fc = &md.fresh_coins[i][j];
+ struct TALER_RefreshCoinData *rcd = &rce[i].new_coins[j];
+ struct TALER_PlanchetDetail pd;
+
+ TALER_planchet_setup_refresh (&trans_sec[i],
+ j,
+ fc);
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&md.fresh_pks[j],
+ fc,
+ &pd))
+ {
+ GNUNET_break_op (0);
+ TALER_EXCHANGE_free_melt_data_ (&md);
+ return NULL;
+ }
+ rcd->dk = &md.fresh_pks[j];
+ rcd->coin_ev = pd.coin_ev;
+ rcd->coin_ev_size = pd.coin_ev_size;
+ }
+ }
+
+ /* Compute refresh commitment */
+ TALER_refresh_get_commitment (&md.rc,
+ TALER_CNC_KAPPA,
+ fresh_pks_len,
+ rce,
+ &coin_pub,
+ melt_amount);
+ /* finally, serialize everything */
+ buf = serialize_melt_data (&md,
+ res_size);
+ for (unsigned int i = 0; i < TALER_CNC_KAPPA; i++)
+ {
+ for (unsigned int j = 0; j < fresh_pks_len; j++)
+ GNUNET_free_non_null (rce[i].new_coins[j].coin_ev);
+ GNUNET_free_non_null (rce[i].new_coins);
+ }
+ TALER_EXCHANGE_free_melt_data_ (&md);
+ return buf;
+}
diff --git a/src/lib/exchange_api_refresh_common.h b/src/lib/exchange_api_refresh_common.h
new file mode 100644
index 000000000..05988e65e
--- /dev/null
+++ b/src/lib/exchange_api_refresh_common.h
@@ -0,0 +1,240 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_refresh.c
+ * @brief Implementation of the /refresh/melt+reveal requests of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#ifndef REFRESH_COMMON_H
+#define REFRESH_COMMON_H
+#include <jansson.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "taler_signatures.h"
+
+
+/* structures for committing refresh data to disk before doing the
+ network interaction(s) */
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Header of serialized information about a coin we are melting.
+ */
+struct MeltedCoinP
+{
+ /**
+ * Private key of the coin.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * Amount this coin contributes to the melt, including fee.
+ */
+ struct TALER_AmountNBO melt_amount_with_fee;
+
+ /**
+ * The applicable fee for withdrawing a coin of this denomination
+ */
+ struct TALER_AmountNBO fee_melt;
+
+ /**
+ * The original value of the coin.
+ */
+ struct TALER_AmountNBO original_value;
+
+ /**
+ * Transfer private keys for each cut-and-choose dimension.
+ */
+ struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA];
+
+ /**
+ * Timestamp indicating when coins of this denomination become invalid.
+ */
+ struct GNUNET_TIME_AbsoluteNBO expire_deposit;
+
+ /**
+ * Size of the encoded public key that follows.
+ */
+ uint16_t pbuf_size;
+
+ /**
+ * Size of the encoded signature that follows.
+ */
+ uint16_t sbuf_size;
+
+ /* Followed by serializations of:
+ 1) struct TALER_DenominationPublicKey pub_key;
+ 2) struct TALER_DenominationSignature sig;
+ */
+};
+
+
+/**
+ * Header of serialized data about a melt operation, suitable for
+ * persisting it on disk.
+ */
+struct MeltDataP
+{
+
+ /**
+ * Hash over the melting session.
+ */
+ struct TALER_RefreshCommitmentP rc;
+
+ /**
+ * Number of coins we are melting, in NBO
+ */
+ uint16_t num_melted_coins GNUNET_PACKED;
+
+ /**
+ * Number of coins we are creating, in NBO
+ */
+ uint16_t num_fresh_coins GNUNET_PACKED;
+
+ /* Followed by serializations of:
+ 1) struct MeltedCoinP melted_coins[num_melted_coins];
+ 2) struct TALER_EXCHANGE_DenomPublicKey fresh_pks[num_fresh_coins];
+ 3) TALER_CNC_KAPPA times:
+ 3a) struct TALER_PlanchetSecretsP fresh_coins[num_fresh_coins];
+ */
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+
+/**
+ * Information about a coin we are melting.
+ */
+struct MeltedCoin
+{
+ /**
+ * Private key of the coin.
+ */
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+ /**
+ * Amount this coin contributes to the melt, including fee.
+ */
+ struct TALER_Amount melt_amount_with_fee;
+
+ /**
+ * The applicable fee for melting a coin of this denomination
+ */
+ struct TALER_Amount fee_melt;
+
+ /**
+ * The original value of the coin.
+ */
+ struct TALER_Amount original_value;
+
+ /**
+ * Transfer private keys for each cut-and-choose dimension.
+ */
+ struct TALER_TransferPrivateKeyP transfer_priv[TALER_CNC_KAPPA];
+
+ /**
+ * Timestamp indicating when coins of this denomination become invalid.
+ */
+ struct GNUNET_TIME_Absolute expire_deposit;
+
+ /**
+ * Denomination key of the original coin.
+ */
+ struct TALER_DenominationPublicKey pub_key;
+
+ /**
+ * Exchange's signature over the coin.
+ */
+ struct TALER_DenominationSignature sig;
+
+};
+
+
+/**
+ * Melt data in non-serialized format for convenient processing.
+ */
+struct MeltData
+{
+
+ /**
+ * Hash over the committed data during refresh operation.
+ */
+ struct TALER_RefreshCommitmentP rc;
+
+ /**
+ * Number of coins we are creating
+ */
+ uint16_t num_fresh_coins;
+
+ /**
+ * Information about the melted coin.
+ */
+ struct MeltedCoin melted_coin;
+
+ /**
+ * Array of @e num_fresh_coins denomination keys for the coins to be
+ * freshly exchangeed.
+ */
+ struct TALER_DenominationPublicKey *fresh_pks;
+
+ /**
+ * Arrays of @e num_fresh_coins with information about the fresh
+ * coins to be created, for each cut-and-choose dimension.
+ */
+ struct TALER_PlanchetSecretsP *fresh_coins[TALER_CNC_KAPPA];
+};
+
+
+/**
+ * Serialize melt data.
+ *
+ * @param md data to serialize
+ * @param[out] res_size size of buffer returned
+ * @return serialized melt data
+ */
+char *
+TALER_EXCHANGE_serialize_melt_data_ (const struct MeltData *md,
+ size_t *res_size);
+
+
+/**
+ * Deserialize melt data.
+ *
+ * @param buf serialized data
+ * @param buf_size size of @a buf
+ * @return deserialized melt data, NULL on error
+ */
+struct MeltData *
+TALER_EXCHANGE_deserialize_melt_data_ (const char *buf,
+ size_t buf_size);
+
+
+/**
+ * Free all information associated with a melting session. Note
+ * that we allow the melting session to be only partially initialized,
+ * as we use this function also when freeing melt data that was not
+ * fully initialized (i.e. due to failures in #deserialize_melt_data()).
+ *
+ * @param md melting data to release, the pointer itself is NOT
+ * freed (as it is typically not allocated by itself)
+ */
+void
+TALER_EXCHANGE_free_melt_data_ (struct MeltData *md);
+
+#endif
diff --git a/src/lib/exchange_api_refreshes_reveal.c b/src/lib/exchange_api_refreshes_reveal.c
new file mode 100644
index 000000000..96aafbda8
--- /dev/null
+++ b/src/lib/exchange_api_refreshes_reveal.c
@@ -0,0 +1,512 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_refreshes_reveal.c
+ * @brief Implementation of the /refreshes/$RCH/reveal requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+#include "exchange_api_refresh_common.h"
+
+
+/**
+ * @brief A /refreshes/$RCH/reveal Handle
+ */
+struct TALER_EXCHANGE_RefreshesRevealHandle
+{
+
+ /**
+ * The connection to exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Handle *exchange;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_RefreshesRevealCallback reveal_cb;
+
+ /**
+ * Closure for @e reveal_cb.
+ */
+ void *reveal_cb_cls;
+
+ /**
+ * Actual information about the melt operation.
+ */
+ struct MeltData *md;
+
+ /**
+ * The index selected by the exchange in cut-and-choose to not be revealed.
+ */
+ uint16_t noreveal_index;
+
+};
+
+
+/**
+ * We got a 200 OK response for the /refreshes/$RCH/reveal operation.
+ * Extract the coin signatures and return them to the caller.
+ * The signatures we get from the exchange is for the blinded value.
+ * Thus, we first must unblind them and then should verify their
+ * validity.
+ *
+ * If everything checks out, we return the unblinded signatures
+ * to the application via the callback.
+ *
+ * @param rrh operation handle
+ * @param json reply from the exchange
+ * @param[out] sigs array of length `num_fresh_coins`, initialized to cointain RSA signatures
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static int
+refresh_reveal_ok (struct TALER_EXCHANGE_RefreshesRevealHandle *rrh,
+ const json_t *json,
+ struct TALER_DenominationSignature *sigs)
+{
+ json_t *jsona;
+ struct GNUNET_JSON_Specification outer_spec[] = {
+ GNUNET_JSON_spec_json ("ev_sigs", &jsona),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ outer_spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (! json_is_array (jsona))
+ {
+ /* We expected an array of coins */
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (outer_spec);
+ return GNUNET_SYSERR;
+ }
+ if (rrh->md->num_fresh_coins != json_array_size (jsona))
+ {
+ /* Number of coins generated does not match our expectation */
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (outer_spec);
+ return GNUNET_SYSERR;
+ }
+ for (unsigned int i = 0; i<rrh->md->num_fresh_coins; i++)
+ {
+ const struct TALER_PlanchetSecretsP *fc;
+ struct TALER_DenominationPublicKey *pk;
+ json_t *jsonai;
+ struct GNUNET_CRYPTO_RsaSignature *blind_sig;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct GNUNET_HashCode coin_hash;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_rsa_signature ("ev_sig", &blind_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ struct TALER_FreshCoin coin;
+
+ fc = &rrh->md->fresh_coins[rrh->noreveal_index][i];
+ pk = &rrh->md->fresh_pks[i];
+ jsonai = json_array_get (jsona, i);
+ GNUNET_assert (NULL != jsonai);
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jsonai,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (outer_spec);
+ return GNUNET_SYSERR;
+ }
+
+ /* needed to verify the signature, and we didn't store it earlier,
+ hence recomputing it here... */
+ GNUNET_CRYPTO_eddsa_key_get_public (&fc->coin_priv.eddsa_priv,
+ &coin_pub.eddsa_pub);
+ GNUNET_CRYPTO_hash (&coin_pub.eddsa_pub,
+ sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
+ &coin_hash);
+ if (GNUNET_OK !=
+ TALER_planchet_to_coin (pk,
+ blind_sig,
+ fc,
+ &coin_hash,
+ &coin))
+ {
+ GNUNET_break_op (0);
+ GNUNET_CRYPTO_rsa_signature_free (blind_sig);
+ GNUNET_JSON_parse_free (outer_spec);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_CRYPTO_rsa_signature_free (blind_sig);
+ sigs[i] = coin.sig;
+ }
+ GNUNET_JSON_parse_free (outer_spec);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /refreshes/$RCH/reveal request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_RefreshHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_refresh_reveal_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_RefreshesRevealHandle *rrh = cls;
+ const json_t *j = response;
+
+ rrh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ {
+ struct TALER_DenominationSignature sigs[rrh->md->num_fresh_coins];
+ int ret;
+
+ memset (sigs, 0, sizeof (sigs));
+ ret = refresh_reveal_ok (rrh,
+ j,
+ sigs);
+ if (GNUNET_OK != ret)
+ {
+ response_code = 0;
+ }
+ else
+ {
+ rrh->reveal_cb (rrh->reveal_cb_cls,
+ MHD_HTTP_OK,
+ TALER_EC_NONE,
+ rrh->md->num_fresh_coins,
+ rrh->md->fresh_coins[rrh->noreveal_index],
+ sigs,
+ j);
+ rrh->reveal_cb = NULL;
+ }
+ for (unsigned int i = 0; i<rrh->md->num_fresh_coins; i++)
+ if (NULL != sigs[i].rsa_signature)
+ GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature);
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* Nothing really to verify, exchange says our reveal is inconsitent
+ with our commitment, so either side is buggy; we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ if (NULL != rrh->reveal_cb)
+ rrh->reveal_cb (rrh->reveal_cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ 0,
+ NULL,
+ NULL,
+ j);
+ TALER_EXCHANGE_refreshes_reveal_cancel (rrh);
+}
+
+
+/**
+ * Submit a /refresh/reval request to the exchange and get the exchange's
+ * response.
+ *
+ * This API is typically used by a wallet. Note that to ensure that
+ * no money is lost in case of hardware failures, the provided
+ * arguments should have been committed to persistent storage
+ * prior to calling this function.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param refresh_data_length size of the @a refresh_data (returned
+ * in the `res_size` argument from #TALER_EXCHANGE_refresh_prepare())
+ * @param refresh_data the refresh data as returned from
+ #TALER_EXCHANGE_refresh_prepare())
+ * @param noreveal_index response from the exchange to the
+ * #TALER_EXCHANGE_melt() invocation
+ * @param reveal_cb the callback to call with the final result of the
+ * refresh operation
+ * @param reveal_cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the argument was invalid.
+ * In this case, neither callback will be called.
+ */
+struct TALER_EXCHANGE_RefreshesRevealHandle *
+TALER_EXCHANGE_refreshes_reveal (struct TALER_EXCHANGE_Handle *exchange,
+ size_t refresh_data_length,
+ const char *refresh_data,
+ uint32_t noreveal_index,
+ TALER_EXCHANGE_RefreshesRevealCallback
+ reveal_cb,
+ void *reveal_cb_cls)
+{
+ struct TALER_EXCHANGE_RefreshesRevealHandle *rrh;
+ json_t *transfer_privs;
+ json_t *new_denoms_h;
+ json_t *coin_evs;
+ json_t *reveal_obj;
+ json_t *link_sigs;
+ CURL *eh;
+ struct GNUNET_CURL_Context *ctx;
+ struct MeltData *md;
+ struct TALER_TransferPublicKeyP transfer_pub;
+ char arg_str[sizeof (struct TALER_RefreshCommitmentP) * 2 + 32];
+
+ if (noreveal_index >= TALER_CNC_KAPPA)
+ {
+ /* We check this here, as it would be really bad to below just
+ disclose all the transfer keys. Note that this error should
+ have been caught way earlier when the exchange replied, but maybe
+ we had some internal corruption that changed the value... */
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (GNUNET_YES !=
+ TEAH_handle_is_ready (exchange))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ md = TALER_EXCHANGE_deserialize_melt_data_ (refresh_data,
+ refresh_data_length);
+ if (NULL == md)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ /* now transfer_pub */
+ GNUNET_CRYPTO_ecdhe_key_get_public (
+ &md->melted_coin.transfer_priv[noreveal_index].ecdhe_priv,
+ &transfer_pub.ecdhe_pub);
+
+ /* now new_denoms */
+ GNUNET_assert (NULL != (new_denoms_h = json_array ()));
+ GNUNET_assert (NULL != (coin_evs = json_array ()));
+ GNUNET_assert (NULL != (link_sigs = json_array ()));
+ for (unsigned int i = 0; i<md->num_fresh_coins; i++)
+ {
+ struct GNUNET_HashCode denom_hash;
+ struct TALER_PlanchetDetail pd;
+
+ GNUNET_CRYPTO_rsa_public_key_hash (md->fresh_pks[i].rsa_public_key,
+ &denom_hash);
+ GNUNET_assert (0 ==
+ json_array_append_new (new_denoms_h,
+ GNUNET_JSON_from_data_auto (
+ &denom_hash)));
+
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&md->fresh_pks[i],
+ &md->fresh_coins[noreveal_index][i],
+ &pd))
+ {
+ /* This should have been noticed during the preparation stage. */
+ GNUNET_break (0);
+ json_decref (new_denoms_h);
+ json_decref (coin_evs);
+ return NULL;
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (coin_evs,
+ GNUNET_JSON_from_data (pd.coin_ev,
+ pd.coin_ev_size)));
+
+ /* compute link signature */
+ {
+ struct TALER_CoinSpendSignatureP link_sig;
+ struct TALER_LinkDataPS ldp;
+
+ ldp.purpose.size = htonl (sizeof (ldp));
+ ldp.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK);
+ ldp.h_denom_pub = denom_hash;
+ GNUNET_CRYPTO_eddsa_key_get_public (&md->melted_coin.coin_priv.eddsa_priv,
+ &ldp.old_coin_pub.eddsa_pub);
+ ldp.transfer_pub = transfer_pub;
+ GNUNET_CRYPTO_hash (pd.coin_ev,
+ pd.coin_ev_size,
+ &ldp.coin_envelope_hash);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CRYPTO_eddsa_sign (
+ &md->melted_coin.coin_priv.eddsa_priv,
+ &ldp.purpose,
+ &link_sig.eddsa_signature));
+ GNUNET_assert (0 ==
+ json_array_append_new (link_sigs,
+ GNUNET_JSON_from_data_auto (
+ &link_sig)));
+ }
+
+ GNUNET_free (pd.coin_ev);
+ }
+
+ /* build array of transfer private keys */
+ GNUNET_assert (NULL != (transfer_privs = json_array ()));
+ for (unsigned int j = 0; j<TALER_CNC_KAPPA; j++)
+ {
+ if (j == noreveal_index)
+ {
+ /* This is crucial: exclude the transfer key for the
+ noreval index! */
+ continue;
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (transfer_privs,
+ GNUNET_JSON_from_data_auto (
+ &md->melted_coin.transfer_priv[j])));
+ }
+
+ /* build main JSON request */
+ reveal_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o}",
+ "transfer_pub",
+ GNUNET_JSON_from_data_auto (&transfer_pub),
+ "transfer_privs",
+ transfer_privs,
+ "link_sigs",
+ link_sigs,
+ "new_denoms_h",
+ new_denoms_h,
+ "coin_evs",
+ coin_evs);
+ if (NULL == reveal_obj)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ {
+ char pub_str[sizeof (struct TALER_RefreshCommitmentP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (&md->rc,
+ sizeof (struct
+ TALER_RefreshCommitmentP),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "/refreshes/%s/reveal",
+ pub_str);
+ }
+ /* finally, we can actually issue the request */
+ rrh = GNUNET_new (struct TALER_EXCHANGE_RefreshesRevealHandle);
+ rrh->exchange = exchange;
+ rrh->noreveal_index = noreveal_index;
+ rrh->reveal_cb = reveal_cb;
+ rrh->reveal_cb_cls = reveal_cb_cls;
+ rrh->md = md;
+ rrh->url = TEAH_path_to_url (rrh->exchange,
+ arg_str);
+
+ eh = TEL_curl_easy_get (rrh->url);
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&rrh->ctx,
+ eh,
+ reveal_obj))
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ json_decref (reveal_obj);
+ GNUNET_free (rrh->url);
+ GNUNET_free (rrh);
+ return NULL;
+ }
+ json_decref (reveal_obj);
+ ctx = TEAH_handle_to_context (rrh->exchange);
+ rrh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ rrh->ctx.headers,
+ &handle_refresh_reveal_finished,
+ rrh);
+ return rrh;
+}
+
+
+/**
+ * Cancel a refresh reveal request. This function cannot be used
+ * on a request handle if the callback was already invoked.
+ *
+ * @param rrh the refresh reval handle
+ */
+void
+TALER_EXCHANGE_refreshes_reveal_cancel (struct
+ TALER_EXCHANGE_RefreshesRevealHandle *
+ rrh)
+{
+ if (NULL != rrh->job)
+ {
+ GNUNET_CURL_job_cancel (rrh->job);
+ rrh->job = NULL;
+ }
+ GNUNET_free (rrh->url);
+ TALER_curl_easy_post_finished (&rrh->ctx);
+ TALER_EXCHANGE_free_melt_data_ (rrh->md); /* does not free 'md' itself */
+ GNUNET_free (rrh->md);
+ GNUNET_free (rrh);
+}
+
+
+/* exchange_api_refreshes_reveal.c */
diff --git a/src/lib/exchange_api_reserve.c b/src/lib/exchange_api_reserve.c
deleted file mode 100644
index 95a4a90eb..000000000
--- a/src/lib/exchange_api_reserve.c
+++ /dev/null
@@ -1,1306 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file lib/exchange_api_reserve.c
- * @brief Implementation of the /reserve requests of the exchange's HTTP API
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <jansson.h>
-#include <microhttpd.h> /* just for HTTP status codes */
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <gnunet/gnunet_curl_lib.h>
-#include "taler_exchange_service.h"
-#include "taler_json_lib.h"
-#include "exchange_api_handle.h"
-#include "taler_signatures.h"
-#include "exchange_api_curl_defaults.h"
-
-
-/* ********************** /reserve/status ********************** */
-
-/**
- * @brief A Withdraw Status Handle
- */
-struct TALER_EXCHANGE_ReservesGetHandle
-{
-
- /**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_ReservesGetCallback cb;
-
- /**
- * Public key of the reserve we are querying.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
-};
-
-
-/**
- * Parse history given in JSON format and return it in binary
- * format.
- *
- * @param exchange connection to the exchange we can use
- * @param history JSON array with the history
- * @param reserve_pub public key of the reserve to inspect
- * @param currency currency we expect the balance to be in
- * @param[out] balance final balance
- * @param history_length number of entries in @a history
- * @param[out] rhistory array of length @a history_length, set to the
- * parsed history entries
- * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
- * were set,
- * #GNUNET_SYSERR if there was a protocol violation in @a history
- */
-static int
-parse_reserve_history (struct TALER_EXCHANGE_Handle *exchange,
- const json_t *history,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const char *currency,
- struct TALER_Amount *balance,
- unsigned int history_length,
- struct TALER_EXCHANGE_ReserveHistory *rhistory)
-{
- struct GNUNET_HashCode uuid[history_length];
- unsigned int uuid_off;
- struct TALER_Amount total_in;
- struct TALER_Amount total_out;
- size_t off;
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (currency,
- &total_in));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_get_zero (currency,
- &total_out));
- uuid_off = 0;
- for (off = 0; off<history_length; off++)
- {
- json_t *transaction;
- struct TALER_Amount amount;
- const char *type;
- struct GNUNET_JSON_Specification hist_spec[] = {
- GNUNET_JSON_spec_string ("type", &type),
- TALER_JSON_spec_amount ("amount",
- &amount),
- /* 'wire' and 'signature' are optional depending on 'type'! */
- GNUNET_JSON_spec_end ()
- };
-
- transaction = json_array_get (history,
- off);
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- hist_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- rhistory[off].amount = amount;
-
- if (0 == strcasecmp (type,
- "DEPOSIT"))
- {
- const char *wire_url;
- void *wire_reference;
- size_t wire_reference_size;
- struct GNUNET_TIME_Absolute timestamp;
-
- struct GNUNET_JSON_Specification withdraw_spec[] = {
- GNUNET_JSON_spec_varsize ("wire_reference",
- &wire_reference,
- &wire_reference_size),
- GNUNET_JSON_spec_absolute_time ("timestamp",
- &timestamp),
- GNUNET_JSON_spec_string ("sender_account_url",
- &wire_url),
- GNUNET_JSON_spec_end ()
- };
-
- rhistory[off].type = TALER_EXCHANGE_RTT_DEPOSIT;
- if (GNUNET_OK !=
- TALER_amount_add (&total_in,
- &total_in,
- &amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- withdraw_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- rhistory[off].details.in_details.sender_url = GNUNET_strdup (wire_url);
- rhistory[off].details.in_details.wire_reference = wire_reference;
- rhistory[off].details.in_details.wire_reference_size =
- wire_reference_size;
- rhistory[off].details.in_details.timestamp = timestamp;
- /* end type==DEPOSIT */
- }
- else if (0 == strcasecmp (type,
- "WITHDRAW"))
- {
- struct TALER_ReserveSignatureP sig;
- struct TALER_WithdrawRequestPS withdraw_purpose;
- struct GNUNET_JSON_Specification withdraw_spec[] = {
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &sig),
- TALER_JSON_spec_amount_nbo ("withdraw_fee",
- &withdraw_purpose.withdraw_fee),
- GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
- &withdraw_purpose.h_denomination_pub),
- GNUNET_JSON_spec_fixed_auto ("h_coin_envelope",
- &withdraw_purpose.h_coin_envelope),
- GNUNET_JSON_spec_end ()
- };
-
- rhistory[off].type = TALER_EXCHANGE_RTT_WITHDRAWAL;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- withdraw_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- withdraw_purpose.purpose.size
- = htonl (sizeof (withdraw_purpose));
- withdraw_purpose.purpose.purpose
- = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
- withdraw_purpose.reserve_pub = *reserve_pub;
- TALER_amount_hton (&withdraw_purpose.amount_with_fee,
- &amount);
- /* Check that the signature is a valid withdraw request */
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
- &withdraw_purpose.purpose,
- &sig.eddsa_signature,
- &reserve_pub->eddsa_pub))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
- /* check that withdraw fee matches expectations! */
- {
- const struct TALER_EXCHANGE_Keys *key_state;
- const struct TALER_EXCHANGE_DenomPublicKey *dki;
- struct TALER_Amount fee;
-
- key_state = TALER_EXCHANGE_get_keys (exchange);
- dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
- &withdraw_purpose.
- h_denomination_pub);
- TALER_amount_ntoh (&fee,
- &withdraw_purpose.withdraw_fee);
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&fee,
- &dki->fee_withdraw)) ||
- (0 !=
- TALER_amount_cmp (&fee,
- &dki->fee_withdraw)) )
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
- }
- rhistory[off].details.out_authorization_sig
- = json_object_get (transaction,
- "signature");
- /* Check check that the same withdraw transaction
- isn't listed twice by the exchange. We use the
- "uuid" array to remember the hashes of all
- purposes, and compare the hashes to find
- duplicates. *///
- GNUNET_CRYPTO_hash (&withdraw_purpose,
- ntohl (withdraw_purpose.purpose.size),
- &uuid[uuid_off]);
- for (unsigned int i = 0; i<uuid_off; i++)
- {
- if (0 == GNUNET_memcmp (&uuid[uuid_off],
- &uuid[i]))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
- }
- uuid_off++;
-
- if (GNUNET_OK !=
- TALER_amount_add (&total_out,
- &total_out,
- &amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (withdraw_spec);
- return GNUNET_SYSERR;
- }
- /* end type==WITHDRAW */
- }
- else if (0 == strcasecmp (type,
- "RECOUP"))
- {
- struct TALER_RecoupConfirmationPS pc;
- struct GNUNET_TIME_Absolute timestamp;
- const struct TALER_EXCHANGE_Keys *key_state;
- struct GNUNET_JSON_Specification recoup_spec[] = {
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &pc.coin_pub),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &rhistory[off].details.recoup_details.
- exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &rhistory[off].details.recoup_details.
- exchange_pub),
- GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
- &pc.timestamp),
- GNUNET_JSON_spec_end ()
- };
-
- rhistory[off].type = TALER_EXCHANGE_RTT_RECOUP;
- rhistory[off].amount = amount;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- recoup_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- rhistory[off].details.recoup_details.coin_pub = pc.coin_pub;
- TALER_amount_hton (&pc.recoup_amount,
- &amount);
- pc.purpose.size = htonl (sizeof (pc));
- pc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP);
- pc.reserve_pub = *reserve_pub;
- timestamp = GNUNET_TIME_absolute_ntoh (pc.timestamp);
- rhistory[off].details.recoup_details.timestamp = timestamp;
-
- key_state = TALER_EXCHANGE_get_keys (exchange);
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
- &rhistory[off].details.
- recoup_details.exchange_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP,
- &pc.purpose,
- &rhistory[off].details.recoup_details.
- exchange_sig.eddsa_signature,
- &rhistory[off].details.recoup_details.
- exchange_pub.eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_amount_add (&total_in,
- &total_in,
- &rhistory[off].amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- /* end type==RECOUP */
- }
- else if (0 == strcasecmp (type,
- "CLOSING"))
- {
- const struct TALER_EXCHANGE_Keys *key_state;
- struct TALER_ReserveCloseConfirmationPS rcc;
- struct GNUNET_TIME_Absolute timestamp;
- struct GNUNET_JSON_Specification closing_spec[] = {
- GNUNET_JSON_spec_string ("receiver_account_details",
- &rhistory[off].details.close_details.
- receiver_account_details),
- GNUNET_JSON_spec_fixed_auto ("wtid",
- &rhistory[off].details.close_details.wtid),
- GNUNET_JSON_spec_fixed_auto ("exchange_sig",
- &rhistory[off].details.close_details.
- exchange_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &rhistory[off].details.close_details.
- exchange_pub),
- TALER_JSON_spec_amount_nbo ("closing_fee",
- &rcc.closing_fee),
- GNUNET_JSON_spec_absolute_time_nbo ("timestamp",
- &rcc.timestamp),
- GNUNET_JSON_spec_end ()
- };
-
- rhistory[off].type = TALER_EXCHANGE_RTT_CLOSE;
- rhistory[off].amount = amount;
- if (GNUNET_OK !=
- GNUNET_JSON_parse (transaction,
- closing_spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- TALER_amount_hton (&rcc.closing_amount,
- &amount);
- GNUNET_CRYPTO_hash (
- rhistory[off].details.close_details.receiver_account_details,
- strlen (
- rhistory[off].details.close_details.receiver_account_details) + 1,
- &rcc.h_wire);
- rcc.wtid = rhistory[off].details.close_details.wtid;
- rcc.purpose.size = htonl (sizeof (rcc));
- rcc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED);
- rcc.reserve_pub = *reserve_pub;
- timestamp = GNUNET_TIME_absolute_ntoh (rcc.timestamp);
- rhistory[off].details.close_details.timestamp = timestamp;
-
- key_state = TALER_EXCHANGE_get_keys (exchange);
- if (GNUNET_OK !=
- TALER_EXCHANGE_test_signing_key (key_state,
- &rhistory[off].details.close_details.
- exchange_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED,
- &rcc.purpose,
- &rhistory[off].details.close_details.
- exchange_sig.eddsa_signature,
- &rhistory[off].details.close_details.
- exchange_pub.eddsa_pub))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_amount_add (&total_out,
- &total_out,
- &rhistory[off].amount))
- {
- /* overflow in history already!? inconceivable! Bad exchange! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- /* end type==CLOSING */
- }
- else
- {
- /* unexpected 'type', protocol incompatibility, complain! */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- }
-
- /* check balance = total_in - total_out < withdraw-amount */
- if (GNUNET_SYSERR ==
- TALER_amount_subtract (balance,
- &total_in,
- &total_out))
- {
- /* total_in < total_out, why did the exchange ever allow this!? */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Free memory (potentially) allocated by #parse_reserve_history().
- *
- * @param rhistory result to free
- * @param len number of entries in @a rhistory
- */
-static void
-free_rhistory (struct TALER_EXCHANGE_ReserveHistory *rhistory,
- unsigned int len)
-{
- for (unsigned int i = 0; i<len; i++)
- {
- switch (rhistory[i].type)
- {
- case TALER_EXCHANGE_RTT_DEPOSIT:
- GNUNET_free_non_null (rhistory[i].details.in_details.wire_reference);
- GNUNET_free_non_null (rhistory[i].details.in_details.sender_url);
- break;
- case TALER_EXCHANGE_RTT_WITHDRAWAL:
- break;
- case TALER_EXCHANGE_RTT_RECOUP:
- break;
- case TALER_EXCHANGE_RTT_CLOSE:
- // should we free "receiver_account_details" ?
- break;
- }
- }
- GNUNET_free (rhistory);
-}
-
-
-/**
- * We received an #MHD_HTTP_OK status code. Handle the JSON
- * response.
- *
- * @param rsh handle of the request
- * @param j JSON response
- * @return #GNUNET_OK on success
- */
-static int
-handle_reserve_status_ok (struct TALER_EXCHANGE_ReservesGetHandle *rsh,
- const json_t *j)
-{
- json_t *history;
- unsigned int len;
- struct TALER_Amount balance;
- struct TALER_Amount balance_from_history;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("balance", &balance),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL,
- NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- history = json_object_get (j,
- "history");
- if (NULL == history)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- len = json_array_size (history);
- {
- struct TALER_EXCHANGE_ReserveHistory *rhistory;
-
- rhistory = GNUNET_new_array (len,
- struct TALER_EXCHANGE_ReserveHistory);
- if (GNUNET_OK !=
- parse_reserve_history (rsh->exchange,
- history,
- &rsh->reserve_pub,
- balance.currency,
- &balance_from_history,
- len,
- rhistory))
- {
- GNUNET_break_op (0);
- free_rhistory (rhistory,
- len);
- return GNUNET_SYSERR;
- }
- if (0 !=
- TALER_amount_cmp (&balance_from_history,
- &balance))
- {
- /* exchange cannot add up balances!? */
- GNUNET_break_op (0);
- free_rhistory (rhistory,
- len);
- return GNUNET_SYSERR;
- }
- if (NULL != rsh->cb)
- {
- rsh->cb (rsh->cb_cls,
- MHD_HTTP_OK,
- TALER_EC_NONE,
- j,
- &balance,
- len,
- rhistory);
- rsh->cb = NULL;
- }
- free_rhistory (rhistory,
- len);
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /reserve/status request.
- *
- * @param cls the `struct TALER_EXCHANGE_ReservesGetHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_reserve_status_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_ReservesGetHandle *rsh = cls;
- const json_t *j = response;
-
- rsh->job = NULL;
- switch (response_code)
- {
- case 0:
- break;
- case MHD_HTTP_OK:
- if (GNUNET_OK !=
- handle_reserve_status_ok (rsh,
- j))
- response_code = 0;
- break;
- case MHD_HTTP_BAD_REQUEST:
- /* This should never happen, either us or the exchange is buggy
- (or API version conflict); just pass JSON reply to the application */
- break;
- case MHD_HTTP_NOT_FOUND:
- /* Nothing really to verify, this should never
- happen, we should pass the JSON reply to the application */
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- break;
- default:
- /* unexpected response code */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u\n",
- (unsigned int) response_code);
- GNUNET_break (0);
- response_code = 0;
- break;
- }
- if (NULL != rsh->cb)
- {
- rsh->cb (rsh->cb_cls,
- response_code,
- TALER_JSON_get_error_code (j),
- j,
- NULL,
- 0, NULL);
- rsh->cb = NULL;
- }
- TALER_EXCHANGE_reserves_get_cancel (rsh);
-}
-
-
-/**
- * Submit a request to obtain the transaction history of a reserve
- * from the exchange. Note that while we return the full response to the
- * caller for further processing, we do already verify that the
- * response is well-formed (i.e. that signatures included in the
- * response are all valid and add up to the balance). If the exchange's
- * reply is not well-formed, we return an HTTP status code of zero to
- * @a cb.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param reserve_pub public key of the reserve to inspect
- * @param cb the callback to call when a reply for this request is available
- * @param cb_cls closure for the above callback
- * @return a handle for this request; NULL if the inputs are invalid (i.e.
- * signatures fail to verify). In this case, the callback is not called.
- */
-struct TALER_EXCHANGE_ReservesGetHandle *
-TALER_EXCHANGE_reserves_get (struct TALER_EXCHANGE_Handle *exchange,
- const struct
- TALER_ReservePublicKeyP *reserve_pub,
- TALER_EXCHANGE_ReservesGetCallback cb,
- void *cb_cls)
-{
- struct TALER_EXCHANGE_ReservesGetHandle *rsh;
- struct GNUNET_CURL_Context *ctx;
- CURL *eh;
- char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 16];
-
- if (GNUNET_YES !=
- TEAH_handle_is_ready (exchange))
- {
- GNUNET_break (0);
- return NULL;
- }
- {
- char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (reserve_pub,
- sizeof (struct
- TALER_ReservePublicKeyP),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "/reserves/%s",
- pub_str);
- }
- rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesGetHandle);
- rsh->exchange = exchange;
- rsh->cb = cb;
- rsh->cb_cls = cb_cls;
- rsh->reserve_pub = *reserve_pub;
- rsh->url = TEAH_path_to_url (exchange,
- arg_str);
- eh = TEL_curl_easy_get (rsh->url);
- ctx = TEAH_handle_to_context (exchange);
- rsh->job = GNUNET_CURL_job_add (ctx,
- eh,
- GNUNET_NO,
- &handle_reserve_status_finished,
- rsh);
- return rsh;
-}
-
-
-/**
- * Cancel a reserve status request. This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param rsh the reserve status request handle
- */
-void
-TALER_EXCHANGE_reserves_get_cancel (struct
- TALER_EXCHANGE_ReservesGetHandle *rsh)
-{
- if (NULL != rsh->job)
- {
- GNUNET_CURL_job_cancel (rsh->job);
- rsh->job = NULL;
- }
- GNUNET_free (rsh->url);
- GNUNET_free (rsh);
-}
-
-
-/* ********************** /reserve/withdraw ********************** */
-
-/**
- * @brief A Withdraw Sign Handle
- */
-struct TALER_EXCHANGE_WithdrawHandle
-{
-
- /**
- * The connection to exchange this request handle will use
- */
- struct TALER_EXCHANGE_Handle *exchange;
-
- /**
- * The url for this request.
- */
- char *url;
-
- /**
- * Context for #TEH_curl_easy_post(). Keeps the data that must
- * persist for Curl to make the upload.
- */
- struct TALER_CURL_PostContext ctx;
-
- /**
- * Handle for the request.
- */
- struct GNUNET_CURL_Job *job;
-
- /**
- * Function to call with the result.
- */
- TALER_EXCHANGE_WithdrawCallback cb;
-
- /**
- * Secrets of the planchet.
- */
- struct TALER_PlanchetSecretsP ps;
-
- /**
- * Denomination key we are withdrawing.
- */
- struct TALER_EXCHANGE_DenomPublicKey pk;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Hash of the public key of the coin we are signing.
- */
- struct GNUNET_HashCode c_hash;
-
- /**
- * Public key of the reserve we are withdrawing from.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
-};
-
-
-/**
- * We got a 200 OK response for the /reserves/$RESERVE_PUB/withdraw operation.
- * Extract the coin's signature and return it to the caller. The signature we
- * get from the exchange is for the blinded value. Thus, we first must
- * unblind it and then should verify its validity against our coin's hash.
- *
- * If everything checks out, we return the unblinded signature
- * to the application via the callback.
- *
- * @param wsh operation handle
- * @param json reply from the exchange
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static int
-reserve_withdraw_ok (struct TALER_EXCHANGE_WithdrawHandle *wsh,
- const json_t *json)
-{
- struct GNUNET_CRYPTO_RsaSignature *blind_sig;
- struct TALER_FreshCoin fc;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_rsa_signature ("ev_sig",
- &blind_sig),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_planchet_to_coin (&wsh->pk.key,
- blind_sig,
- &wsh->ps,
- &wsh->c_hash,
- &fc))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- GNUNET_JSON_parse_free (spec);
-
- /* signature is valid, return it to the application */
- wsh->cb (wsh->cb_cls,
- MHD_HTTP_OK,
- TALER_EC_NONE,
- &fc.sig,
- json);
- /* make sure callback isn't called again after return */
- wsh->cb = NULL;
- GNUNET_CRYPTO_rsa_signature_free (fc.sig.rsa_signature);
- return GNUNET_OK;
-}
-
-
-/**
- * We got a 409 CONFLICT response for the /reserves/$RESERVE_PUB/withdraw operation.
- * Check the signatures on the withdraw transactions in the provided
- * history and that the balances add up. We don't do anything directly
- * with the information, as the JSON will be returned to the application.
- * However, our job is ensuring that the exchange followed the protocol, and
- * this in particular means checking all of the signatures in the history.
- *
- * @param wsh operation handle
- * @param json reply from the exchange
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static int
-reserve_withdraw_payment_required (struct
- TALER_EXCHANGE_WithdrawHandle *wsh,
- const json_t *json)
-{
- struct TALER_Amount balance;
- struct TALER_Amount balance_from_history;
- struct TALER_Amount requested_amount;
- json_t *history;
- size_t len;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("balance", &balance),
- GNUNET_JSON_spec_end ()
- };
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (json,
- spec,
- NULL, NULL))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- history = json_object_get (json,
- "history");
- if (NULL == history)
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
-
- /* go over transaction history and compute
- total incoming and outgoing amounts */
- len = json_array_size (history);
- {
- struct TALER_EXCHANGE_ReserveHistory *rhistory;
-
- /* Use heap allocation as "len" may be very big and thus this may
- not fit on the stack. Use "GNUNET_malloc_large" as a malicious
- exchange may theoretically try to crash us by giving a history
- that does not fit into our memory. */
- rhistory = GNUNET_malloc_large (sizeof (struct
- TALER_EXCHANGE_ReserveHistory)
- * len);
- if (NULL == rhistory)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
-
- if (GNUNET_OK !=
- parse_reserve_history (wsh->exchange,
- history,
- &wsh->reserve_pub,
- balance.currency,
- &balance_from_history,
- len,
- rhistory))
- {
- GNUNET_break_op (0);
- free_rhistory (rhistory,
- len);
- return GNUNET_SYSERR;
- }
- free_rhistory (rhistory,
- len);
- }
-
- if (0 !=
- TALER_amount_cmp (&balance_from_history,
- &balance))
- {
- /* exchange cannot add up balances!? */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- /* Compute how much we expected to charge to the reserve */
- if (GNUNET_OK !=
- TALER_amount_add (&requested_amount,
- &wsh->pk.value,
- &wsh->pk.fee_withdraw))
- {
- /* Overflow here? Very strange, our CPU must be fried... */
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- /* Check that funds were really insufficient */
- if (0 >= TALER_amount_cmp (&requested_amount,
- &balance))
- {
- /* Requested amount is smaller or equal to reported balance,
- so this should not have failed. */
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Function called when we're done processing the
- * HTTP /reserves/$RESERVE_PUB/withdraw request.
- *
- * @param cls the `struct TALER_EXCHANGE_WithdrawHandle`
- * @param response_code HTTP response code, 0 on error
- * @param response parsed JSON result, NULL on error
- */
-static void
-handle_reserve_withdraw_finished (void *cls,
- long response_code,
- const void *response)
-{
- struct TALER_EXCHANGE_WithdrawHandle *wsh = cls;
- const json_t *j = response;
-
- wsh->job = NULL;
- switch (response_code)
- {
- case 0:
- break;
- case MHD_HTTP_OK:
- if (GNUNET_OK !=
- reserve_withdraw_ok (wsh,
- j))
- {
- GNUNET_break_op (0);
- response_code = 0;
- }
- break;
- case MHD_HTTP_BAD_REQUEST:
- /* This should never happen, either us or the exchange is buggy
- (or API version conflict); just pass JSON reply to the application */
- break;
- case MHD_HTTP_CONFLICT:
- /* The exchange says that the reserve has insufficient funds;
- check the signatures in the history... */
- if (GNUNET_OK !=
- reserve_withdraw_payment_required (wsh,
- j))
- {
- GNUNET_break_op (0);
- response_code = 0;
- }
- break;
- case MHD_HTTP_FORBIDDEN:
- GNUNET_break (0);
- /* Nothing really to verify, exchange says one of the signatures is
- invalid; as we checked them, this should never happen, we
- should pass the JSON reply to the application */
- break;
- case MHD_HTTP_NOT_FOUND:
- /* Nothing really to verify, the exchange basically just says
- that it doesn't know this reserve. Can happen if we
- query before the wire transfer went through.
- We should simply pass the JSON reply to the application. */
- break;
- case MHD_HTTP_INTERNAL_SERVER_ERROR:
- /* Server had an internal issue; we should retry, but this API
- leaves this to the application */
- break;
- default:
- /* unexpected response code */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u\n",
- (unsigned int) response_code);
- GNUNET_break (0);
- response_code = 0;
- break;
- }
- if (NULL != wsh->cb)
- {
- wsh->cb (wsh->cb_cls,
- response_code,
- TALER_JSON_get_error_code (j),
- NULL,
- j);
- wsh->cb = NULL;
- }
- TALER_EXCHANGE_withdraw_cancel (wsh);
-}
-
-
-/**
- * Helper function for #TALER_EXCHANGE_withdraw2() and
- * #TALER_EXCHANGE_withdraw().
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param pk kind of coin to create
- * @param reserve_sig signature from the reserve authorizing the withdrawal
- * @param reserve_pub public key of the reserve to withdraw from
- * @param ps secrets of the planchet
- * caller must have committed this value to disk before the call (with @a pk)
- * @param pd planchet details matching @a ps
- * @param res_cb the callback to call when the final result for this request is available
- * @param res_cb_cls closure for @a res_cb
- * @return NULL
- * if the inputs are invalid (i.e. denomination key not with this exchange).
- * In this case, the callback is not called.
- */
-struct TALER_EXCHANGE_WithdrawHandle *
-reserve_withdraw_internal (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_EXCHANGE_DenomPublicKey *pk,
- const struct TALER_ReserveSignatureP *reserve_sig,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_PlanchetSecretsP *ps,
- const struct TALER_PlanchetDetail *pd,
- TALER_EXCHANGE_WithdrawCallback res_cb,
- void *res_cb_cls)
-{
- struct TALER_EXCHANGE_WithdrawHandle *wsh;
- struct GNUNET_CURL_Context *ctx;
- json_t *withdraw_obj;
- CURL *eh;
- struct GNUNET_HashCode h_denom_pub;
- char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
-
- {
- char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
- char *end;
-
- end = GNUNET_STRINGS_data_to_string (reserve_pub,
- sizeof (struct
- TALER_ReservePublicKeyP),
- pub_str,
- sizeof (pub_str));
- *end = '\0';
- GNUNET_snprintf (arg_str,
- sizeof (arg_str),
- "/reserves/%s/withdraw",
- pub_str);
- }
- wsh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle);
- wsh->exchange = exchange;
- wsh->cb = res_cb;
- wsh->cb_cls = res_cb_cls;
- wsh->pk = *pk;
- wsh->pk.key.rsa_public_key
- = GNUNET_CRYPTO_rsa_public_key_dup (pk->key.rsa_public_key);
- wsh->reserve_pub = *reserve_pub;
- wsh->c_hash = pd->c_hash;
- GNUNET_CRYPTO_rsa_public_key_hash (pk->key.rsa_public_key,
- &h_denom_pub);
- withdraw_obj = json_pack ("{s:o, s:o," /* denom_pub_hash and coin_ev */
- " s:o}",/* reserve_pub and reserve_sig */
- "denom_pub_hash", GNUNET_JSON_from_data_auto (
- &h_denom_pub),
- "coin_ev", GNUNET_JSON_from_data (pd->coin_ev,
- pd->coin_ev_size),
- "reserve_sig", GNUNET_JSON_from_data_auto (
- reserve_sig));
- if (NULL == withdraw_obj)
- {
- GNUNET_break (0);
- GNUNET_CRYPTO_rsa_public_key_free (wsh->pk.key.rsa_public_key);
- GNUNET_free (wsh);
- return NULL;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Attempting to withdraw from reserve %s\n",
- TALER_B2S (reserve_pub));
- wsh->ps = *ps;
- wsh->url = TEAH_path_to_url (exchange,
- arg_str);
- eh = TEL_curl_easy_get (wsh->url);
- if (GNUNET_OK !=
- TALER_curl_easy_post (&wsh->ctx,
- eh,
- withdraw_obj))
- {
- GNUNET_break (0);
- curl_easy_cleanup (eh);
- json_decref (withdraw_obj);
- GNUNET_free (wsh->url);
- GNUNET_CRYPTO_rsa_public_key_free (wsh->pk.key.rsa_public_key);
- GNUNET_free (wsh);
- return NULL;
- }
- json_decref (withdraw_obj);
- ctx = TEAH_handle_to_context (exchange);
- wsh->job = GNUNET_CURL_job_add2 (ctx,
- eh,
- wsh->ctx.headers,
- &handle_reserve_withdraw_finished,
- wsh);
- return wsh;
-}
-
-
-/**
- * Withdraw a coin from the exchange using a /reserve/withdraw request. Note
- * that to ensure that no money is lost in case of hardware failures,
- * the caller must have committed (most of) the arguments to disk
- * before calling, and be ready to repeat the request with the same
- * arguments in case of failures.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param pk kind of coin to create
- * @param reserve_priv private key of the reserve to withdraw from
- * @param ps secrets of the planchet
- * caller must have committed this value to disk before the call (with @a pk)
- * @param res_cb the callback to call when the final result for this request is available
- * @param res_cb_cls closure for the above callback
- * @return handle for the operation on success, NULL on error, i.e.
- * if the inputs are invalid (i.e. denomination key not with this exchange).
- * In this case, the callback is not called.
- */
-struct TALER_EXCHANGE_WithdrawHandle *
-TALER_EXCHANGE_withdraw (struct TALER_EXCHANGE_Handle *exchange,
- const struct TALER_EXCHANGE_DenomPublicKey *pk,
- const struct
- TALER_ReservePrivateKeyP *reserve_priv,
- const struct TALER_PlanchetSecretsP *ps,
- TALER_EXCHANGE_WithdrawCallback
- res_cb,
- void *res_cb_cls)
-{
- struct TALER_Amount amount_with_fee;
- struct TALER_ReserveSignatureP reserve_sig;
- struct TALER_WithdrawRequestPS req;
- struct TALER_PlanchetDetail pd;
- struct TALER_EXCHANGE_WithdrawHandle *wsh;
-
- GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
- &req.reserve_pub.eddsa_pub);
- req.purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS));
- req.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
- if (GNUNET_OK !=
- TALER_amount_add (&amount_with_fee,
- &pk->fee_withdraw,
- &pk->value))
- {
- /* exchange gave us denomination keys that overflow like this!? */
- GNUNET_break_op (0);
- return NULL;
- }
- TALER_amount_hton (&req.amount_with_fee,
- &amount_with_fee);
- TALER_amount_hton (&req.withdraw_fee,
- &pk->fee_withdraw);
- if (GNUNET_OK !=
- TALER_planchet_prepare (&pk->key,
- ps,
- &pd))
- {
- GNUNET_break_op (0);
- return NULL;
- }
- req.h_denomination_pub = pd.denom_pub_hash;
- GNUNET_CRYPTO_hash (pd.coin_ev,
- pd.coin_ev_size,
- &req.h_coin_envelope);
- GNUNET_assert (GNUNET_OK ==
- GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
- &req.purpose,
- &reserve_sig.eddsa_signature));
- wsh = reserve_withdraw_internal (exchange,
- pk,
- &reserve_sig,
- &req.reserve_pub,
- ps,
- &pd,
- res_cb,
- res_cb_cls);
- GNUNET_free (pd.coin_ev);
- return wsh;
-}
-
-
-/**
- * Withdraw a coin from the exchange using a /reserve/withdraw
- * request. This API is typically used by a wallet to withdraw a tip
- * where the reserve's signature was created by the merchant already.
- *
- * Note that to ensure that no money is lost in case of hardware
- * failures, the caller must have committed (most of) the arguments to
- * disk before calling, and be ready to repeat the request with the
- * same arguments in case of failures.
- *
- * @param exchange the exchange handle; the exchange must be ready to operate
- * @param pk kind of coin to create
- * @param reserve_sig signature from the reserve authorizing the withdrawal
- * @param reserve_pub public key of the reserve to withdraw from
- * @param ps secrets of the planchet
- * caller must have committed this value to disk before the call (with @a pk)
- * @param res_cb the callback to call when the final result for this request is available
- * @param res_cb_cls closure for @a res_cb
- * @return NULL
- * if the inputs are invalid (i.e. denomination key not with this exchange).
- * In this case, the callback is not called.
- */
-struct TALER_EXCHANGE_WithdrawHandle *
-TALER_EXCHANGE_withdraw2 (struct TALER_EXCHANGE_Handle *exchange,
- const struct
- TALER_EXCHANGE_DenomPublicKey *pk,
- const struct
- TALER_ReserveSignatureP *reserve_sig,
- const struct
- TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_PlanchetSecretsP *ps,
- TALER_EXCHANGE_WithdrawCallback
- res_cb,
- void *res_cb_cls)
-{
- struct TALER_EXCHANGE_WithdrawHandle *wsh;
- struct TALER_PlanchetDetail pd;
-
- if (GNUNET_OK !=
- TALER_planchet_prepare (&pk->key,
- ps,
- &pd))
- {
- GNUNET_break_op (0);
- return NULL;
- }
- wsh = reserve_withdraw_internal (exchange,
- pk,
- reserve_sig,
- reserve_pub,
- ps,
- &pd,
- res_cb,
- res_cb_cls);
- GNUNET_free (pd.coin_ev);
- return wsh;
-}
-
-
-/**
- * Cancel a withdraw status request. This function cannot be used
- * on a request handle if a response is already served for it.
- *
- * @param sign the withdraw sign request handle
- */
-void
-TALER_EXCHANGE_withdraw_cancel (struct
- TALER_EXCHANGE_WithdrawHandle *
- sign)
-{
- if (NULL != sign->job)
- {
- GNUNET_CURL_job_cancel (sign->job);
- sign->job = NULL;
- }
- GNUNET_free (sign->url);
- TALER_curl_easy_post_finished (&sign->ctx);
- GNUNET_CRYPTO_rsa_public_key_free (sign->pk.key.rsa_public_key);
- GNUNET_free (sign);
-}
-
-
-/* end of exchange_api_reserve.c */
diff --git a/src/lib/exchange_api_reserves_get.c b/src/lib/exchange_api_reserves_get.c
new file mode 100644
index 000000000..62e28f05e
--- /dev/null
+++ b/src/lib/exchange_api_reserves_get.c
@@ -0,0 +1,308 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_reserves_get.c
+ * @brief Implementation of the GET /reserves/$RESERVE_PUB requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /reserves/ GET Handle
+ */
+struct TALER_EXCHANGE_ReservesGetHandle
+{
+
+ /**
+ * The connection to exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Handle *exchange;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_ReservesGetCallback cb;
+
+ /**
+ * Public key of the reserve we are querying.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * We received an #MHD_HTTP_OK status code. Handle the JSON
+ * response.
+ *
+ * @param rgh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static int
+handle_reserves_get_ok (struct TALER_EXCHANGE_ReservesGetHandle *rgh,
+ const json_t *j)
+{
+ json_t *history;
+ unsigned int len;
+ struct TALER_Amount balance;
+ struct TALER_Amount balance_from_history;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount ("balance", &balance),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (j,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ history = json_object_get (j,
+ "history");
+ if (NULL == history)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ len = json_array_size (history);
+ {
+ struct TALER_EXCHANGE_ReserveHistory *rhistory;
+
+ rhistory = GNUNET_new_array (len,
+ struct TALER_EXCHANGE_ReserveHistory);
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_parse_reserve_history (rgh->exchange,
+ history,
+ &rgh->reserve_pub,
+ balance.currency,
+ &balance_from_history,
+ len,
+ rhistory))
+ {
+ GNUNET_break_op (0);
+ TALER_EXCHANGE_free_reserve_history (rhistory,
+ len);
+ return GNUNET_SYSERR;
+ }
+ if (0 !=
+ TALER_amount_cmp (&balance_from_history,
+ &balance))
+ {
+ /* exchange cannot add up balances!? */
+ GNUNET_break_op (0);
+ TALER_EXCHANGE_free_reserve_history (rhistory,
+ len);
+ return GNUNET_SYSERR;
+ }
+ if (NULL != rgh->cb)
+ {
+ rgh->cb (rgh->cb_cls,
+ MHD_HTTP_OK,
+ TALER_EC_NONE,
+ j,
+ &balance,
+ len,
+ rhistory);
+ rgh->cb = NULL;
+ }
+ TALER_EXCHANGE_free_reserve_history (rhistory,
+ len);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/ GET request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReservesGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserves_get_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_ReservesGetHandle *rgh = cls;
+ const json_t *j = response;
+
+ rgh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ handle_reserves_get_ok (rgh,
+ j))
+ response_code = 0;
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ if (NULL != rgh->cb)
+ {
+ rgh->cb (rgh->cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ j,
+ NULL,
+ 0, NULL);
+ rgh->cb = NULL;
+ }
+ TALER_EXCHANGE_reserves_get_cancel (rgh);
+}
+
+
+/**
+ * Submit a request to obtain the transaction history of a reserve
+ * from the exchange. Note that while we return the full response to the
+ * caller for further processing, we do already verify that the
+ * response is well-formed (i.e. that signatures included in the
+ * response are all valid and add up to the balance). If the exchange's
+ * reply is not well-formed, we return an HTTP status code of zero to
+ * @a cb.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param reserve_pub public key of the reserve to inspect
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ * signatures fail to verify). In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_ReservesGetHandle *
+TALER_EXCHANGE_reserves_get (struct TALER_EXCHANGE_Handle *exchange,
+ const struct
+ TALER_ReservePublicKeyP *reserve_pub,
+ TALER_EXCHANGE_ReservesGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_EXCHANGE_ReservesGetHandle *rgh;
+ struct GNUNET_CURL_Context *ctx;
+ CURL *eh;
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 16];
+
+ if (GNUNET_YES !=
+ TEAH_handle_is_ready (exchange))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ {
+ char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (reserve_pub,
+ sizeof (struct
+ TALER_ReservePublicKeyP),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "/reserves/%s",
+ pub_str);
+ }
+ rgh = GNUNET_new (struct TALER_EXCHANGE_ReservesGetHandle);
+ rgh->exchange = exchange;
+ rgh->cb = cb;
+ rgh->cb_cls = cb_cls;
+ rgh->reserve_pub = *reserve_pub;
+ rgh->url = TEAH_path_to_url (exchange,
+ arg_str);
+ eh = TEL_curl_easy_get (rgh->url);
+ ctx = TEAH_handle_to_context (exchange);
+ rgh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ GNUNET_NO,
+ &handle_reserves_get_finished,
+ rgh);
+ return rgh;
+}
+
+
+/**
+ * Cancel a reserve status request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param rgh the reserve status request handle
+ */
+void
+TALER_EXCHANGE_reserves_get_cancel (struct
+ TALER_EXCHANGE_ReservesGetHandle *rgh)
+{
+ if (NULL != rgh->job)
+ {
+ GNUNET_CURL_job_cancel (rgh->job);
+ rgh->job = NULL;
+ }
+ GNUNET_free (rgh->url);
+ GNUNET_free (rgh);
+}
+
+
+/* end of exchange_api_reserve.c */
diff --git a/src/lib/exchange_api_track_transfer.c b/src/lib/exchange_api_transfers_get.c
index c5d484d23..8ea8918ca 100644
--- a/src/lib/exchange_api_track_transfer.c
+++ b/src/lib/exchange_api_transfers_get.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014, 2015, 2016 Taler Systems SA
+ Copyright (C) 2014-2020 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
@@ -15,8 +15,8 @@
<http://www.gnu.org/licenses/>
*/
/**
- * @file lib/exchange_api_track_transfer.c
- * @brief Implementation of the /track/transfer request of the exchange's HTTP API
+ * @file lib/exchange_api_transfers_get.c
+ * @brief Implementation of the GET /transfers/ request
* @author Christian Grothoff
*/
#include "platform.h"
@@ -32,7 +32,7 @@
/**
- * @brief A /track/transfer Handle
+ * @brief A /transfers/ GET Handle
*/
struct TALER_EXCHANGE_TransfersGetHandle
{
@@ -66,12 +66,12 @@ struct TALER_EXCHANGE_TransfersGetHandle
/**
- * We got a #MHD_HTTP_OK response for the /track/transfer request.
+ * We got a #MHD_HTTP_OK response for the /transfers/ request.
* Check that the response is well-formed and if it is, call the
* callback. If not, return an error code.
*
* This code is very similar to
- * merchant_api_track_transfer.c::check_track_transfer_response_ok.
+ * merchant_api_track_transfer.c::check_transfers_get_response_ok.
* Any changes should likely be reflected there as well.
*
* @param wdh handle to the operation
@@ -80,9 +80,9 @@ struct TALER_EXCHANGE_TransfersGetHandle
* #GNUNET_SYSERR if the response was bogus
*/
static int
-check_track_transfer_response_ok (struct
- TALER_EXCHANGE_TransfersGetHandle *wdh,
- const json_t *json)
+check_transfers_get_response_ok (struct
+ TALER_EXCHANGE_TransfersGetHandle *wdh,
+ const json_t *json)
{
json_t *details_j;
struct GNUNET_HashCode h_wire;
@@ -248,16 +248,16 @@ check_track_transfer_response_ok (struct
/**
* Function called when we're done processing the
- * HTTP /track/transfer request.
+ * HTTP /transfers/ request.
*
* @param cls the `struct TALER_EXCHANGE_TransfersGetHandle`
* @param response_code HTTP response code, 0 on error
* @param response parsed JSON result, NULL on error
*/
static void
-handle_track_transfer_finished (void *cls,
- long response_code,
- const void *response)
+handle_transfers_get_finished (void *cls,
+ long response_code,
+ const void *response)
{
struct TALER_EXCHANGE_TransfersGetHandle *wdh = cls;
const json_t *j = response;
@@ -269,8 +269,8 @@ handle_track_transfer_finished (void *cls,
break;
case MHD_HTTP_OK:
if (GNUNET_OK ==
- check_track_transfer_response_ok (wdh,
- j))
+ check_transfers_get_response_ok (wdh,
+ j))
return;
GNUNET_break_op (0);
response_code = 0;
@@ -371,7 +371,7 @@ TALER_EXCHANGE_transfers_get (struct TALER_EXCHANGE_Handle *exchange,
wdh->job = GNUNET_CURL_job_add (ctx,
eh,
GNUNET_YES,
- &handle_track_transfer_finished,
+ &handle_transfers_get_finished,
wdh);
return wdh;
}
diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c
new file mode 100644
index 000000000..c63235377
--- /dev/null
+++ b/src/lib/exchange_api_withdraw.c
@@ -0,0 +1,611 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_withdraw.c
+ * @brief Implementation of the /reserves/$RESERVE_PUB/withdraw requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Withdraw Handle
+ */
+struct TALER_EXCHANGE_WithdrawHandle
+{
+
+ /**
+ * The connection to exchange this request handle will use
+ */
+ struct TALER_EXCHANGE_Handle *exchange;
+
+ /**
+ * The url for this request.
+ */
+ char *url;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_EXCHANGE_WithdrawCallback cb;
+
+ /**
+ * Secrets of the planchet.
+ */
+ struct TALER_PlanchetSecretsP ps;
+
+ /**
+ * Denomination key we are withdrawing.
+ */
+ struct TALER_EXCHANGE_DenomPublicKey pk;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Hash of the public key of the coin we are signing.
+ */
+ struct GNUNET_HashCode c_hash;
+
+ /**
+ * Public key of the reserve we are withdrawing from.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+};
+
+
+/**
+ * We got a 200 OK response for the /reserves/$RESERVE_PUB/withdraw operation.
+ * Extract the coin's signature and return it to the caller. The signature we
+ * get from the exchange is for the blinded value. Thus, we first must
+ * unblind it and then should verify its validity against our coin's hash.
+ *
+ * If everything checks out, we return the unblinded signature
+ * to the application via the callback.
+ *
+ * @param wh operation handle
+ * @param json reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static int
+reserve_withdraw_ok (struct TALER_EXCHANGE_WithdrawHandle *wh,
+ const json_t *json)
+{
+ struct GNUNET_CRYPTO_RsaSignature *blind_sig;
+ struct TALER_FreshCoin fc;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_rsa_signature ("ev_sig",
+ &blind_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_planchet_to_coin (&wh->pk.key,
+ blind_sig,
+ &wh->ps,
+ &wh->c_hash,
+ &fc))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_JSON_parse_free (spec);
+
+ /* signature is valid, return it to the application */
+ wh->cb (wh->cb_cls,
+ MHD_HTTP_OK,
+ TALER_EC_NONE,
+ &fc.sig,
+ json);
+ /* make sure callback isn't called again after return */
+ wh->cb = NULL;
+ GNUNET_CRYPTO_rsa_signature_free (fc.sig.rsa_signature);
+ return GNUNET_OK;
+}
+
+
+/**
+ * We got a 409 CONFLICT response for the /reserves/$RESERVE_PUB/withdraw operation.
+ * Check the signatures on the withdraw transactions in the provided
+ * history and that the balances add up. We don't do anything directly
+ * with the information, as the JSON will be returned to the application.
+ * However, our job is ensuring that the exchange followed the protocol, and
+ * this in particular means checking all of the signatures in the history.
+ *
+ * @param wh operation handle
+ * @param json reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static int
+reserve_withdraw_payment_required (struct
+ TALER_EXCHANGE_WithdrawHandle *wh,
+ const json_t *json)
+{
+ struct TALER_Amount balance;
+ struct TALER_Amount balance_from_history;
+ struct TALER_Amount requested_amount;
+ json_t *history;
+ size_t len;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount ("balance", &balance),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ history = json_object_get (json,
+ "history");
+ if (NULL == history)
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* go over transaction history and compute
+ total incoming and outgoing amounts */
+ len = json_array_size (history);
+ {
+ struct TALER_EXCHANGE_ReserveHistory *rhistory;
+
+ /* Use heap allocation as "len" may be very big and thus this may
+ not fit on the stack. Use "GNUNET_malloc_large" as a malicious
+ exchange may theoretically try to crash us by giving a history
+ that does not fit into our memory. */
+ rhistory = GNUNET_malloc_large (sizeof (struct
+ TALER_EXCHANGE_ReserveHistory)
+ * len);
+ if (NULL == rhistory)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ TALER_EXCHANGE_parse_reserve_history (wh->exchange,
+ history,
+ &wh->reserve_pub,
+ balance.currency,
+ &balance_from_history,
+ len,
+ rhistory))
+ {
+ GNUNET_break_op (0);
+ TALER_EXCHANGE_free_reserve_history (rhistory,
+ len);
+ return GNUNET_SYSERR;
+ }
+ TALER_EXCHANGE_free_reserve_history (rhistory,
+ len);
+ }
+
+ if (0 !=
+ TALER_amount_cmp (&balance_from_history,
+ &balance))
+ {
+ /* exchange cannot add up balances!? */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ /* Compute how much we expected to charge to the reserve */
+ if (GNUNET_OK !=
+ TALER_amount_add (&requested_amount,
+ &wh->pk.value,
+ &wh->pk.fee_withdraw))
+ {
+ /* Overflow here? Very strange, our CPU must be fried... */
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ /* Check that funds were really insufficient */
+ if (0 >= TALER_amount_cmp (&requested_amount,
+ &balance))
+ {
+ /* Requested amount is smaller or equal to reported balance,
+ so this should not have failed. */
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RESERVE_PUB/withdraw request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_WithdrawHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserve_withdraw_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
+ const json_t *j = response;
+
+ wh->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ break;
+ case MHD_HTTP_OK:
+ if (GNUNET_OK !=
+ reserve_withdraw_ok (wh,
+ j))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ }
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ /* This should never happen, either us or the exchange is buggy
+ (or API version conflict); just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_CONFLICT:
+ /* The exchange says that the reserve has insufficient funds;
+ check the signatures in the history... */
+ if (GNUNET_OK !=
+ reserve_withdraw_payment_required (wh,
+ j))
+ {
+ GNUNET_break_op (0);
+ response_code = 0;
+ }
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ GNUNET_break (0);
+ /* Nothing really to verify, exchange says one of the signatures is
+ invalid; as we checked them, this should never happen, we
+ should pass the JSON reply to the application */
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Nothing really to verify, the exchange basically just says
+ that it doesn't know this reserve. Can happen if we
+ query before the wire transfer went through.
+ We should simply pass the JSON reply to the application. */
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* Server had an internal issue; we should retry, but this API
+ leaves this to the application */
+ break;
+ default:
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u\n",
+ (unsigned int) response_code);
+ GNUNET_break (0);
+ response_code = 0;
+ break;
+ }
+ if (NULL != wh->cb)
+ {
+ wh->cb (wh->cb_cls,
+ response_code,
+ TALER_JSON_get_error_code (j),
+ NULL,
+ j);
+ wh->cb = NULL;
+ }
+ TALER_EXCHANGE_withdraw_cancel (wh);
+}
+
+
+/**
+ * Helper function for #TALER_EXCHANGE_withdraw2() and
+ * #TALER_EXCHANGE_withdraw().
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param pk kind of coin to create
+ * @param reserve_sig signature from the reserve authorizing the withdrawal
+ * @param reserve_pub public key of the reserve to withdraw from
+ * @param ps secrets of the planchet
+ * caller must have committed this value to disk before the call (with @a pk)
+ * @param pd planchet details matching @a ps
+ * @param res_cb the callback to call when the final result for this request is available
+ * @param res_cb_cls closure for @a res_cb
+ * @return NULL
+ * if the inputs are invalid (i.e. denomination key not with this exchange).
+ * In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_WithdrawHandle *
+reserve_withdraw_internal (struct TALER_EXCHANGE_Handle *exchange,
+ const struct TALER_EXCHANGE_DenomPublicKey *pk,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_PlanchetSecretsP *ps,
+ const struct TALER_PlanchetDetail *pd,
+ TALER_EXCHANGE_WithdrawCallback res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_WithdrawHandle *wh;
+ struct GNUNET_CURL_Context *ctx;
+ json_t *withdraw_obj;
+ CURL *eh;
+ struct GNUNET_HashCode h_denom_pub;
+ char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+
+ {
+ char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+ char *end;
+
+ end = GNUNET_STRINGS_data_to_string (reserve_pub,
+ sizeof (struct
+ TALER_ReservePublicKeyP),
+ pub_str,
+ sizeof (pub_str));
+ *end = '\0';
+ GNUNET_snprintf (arg_str,
+ sizeof (arg_str),
+ "/reserves/%s/withdraw",
+ pub_str);
+ }
+ wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle);
+ wh->exchange = exchange;
+ wh->cb = res_cb;
+ wh->cb_cls = res_cb_cls;
+ wh->pk = *pk;
+ wh->pk.key.rsa_public_key
+ = GNUNET_CRYPTO_rsa_public_key_dup (pk->key.rsa_public_key);
+ wh->reserve_pub = *reserve_pub;
+ wh->c_hash = pd->c_hash;
+ GNUNET_CRYPTO_rsa_public_key_hash (pk->key.rsa_public_key,
+ &h_denom_pub);
+ withdraw_obj = json_pack ("{s:o, s:o," /* denom_pub_hash and coin_ev */
+ " s:o}",/* reserve_pub and reserve_sig */
+ "denom_pub_hash", GNUNET_JSON_from_data_auto (
+ &h_denom_pub),
+ "coin_ev", GNUNET_JSON_from_data (pd->coin_ev,
+ pd->coin_ev_size),
+ "reserve_sig", GNUNET_JSON_from_data_auto (
+ reserve_sig));
+ if (NULL == withdraw_obj)
+ {
+ GNUNET_break (0);
+ GNUNET_CRYPTO_rsa_public_key_free (wh->pk.key.rsa_public_key);
+ GNUNET_free (wh);
+ return NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Attempting to withdraw from reserve %s\n",
+ TALER_B2S (reserve_pub));
+ wh->ps = *ps;
+ wh->url = TEAH_path_to_url (exchange,
+ arg_str);
+ eh = TEL_curl_easy_get (wh->url);
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&wh->ctx,
+ eh,
+ withdraw_obj))
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ json_decref (withdraw_obj);
+ GNUNET_free (wh->url);
+ GNUNET_CRYPTO_rsa_public_key_free (wh->pk.key.rsa_public_key);
+ GNUNET_free (wh);
+ return NULL;
+ }
+ json_decref (withdraw_obj);
+ ctx = TEAH_handle_to_context (exchange);
+ wh->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ wh->ctx.headers,
+ &handle_reserve_withdraw_finished,
+ wh);
+ return wh;
+}
+
+
+/**
+ * Withdraw a coin from the exchange using a /reserve/withdraw request. Note
+ * that to ensure that no money is lost in case of hardware failures,
+ * the caller must have committed (most of) the arguments to disk
+ * before calling, and be ready to repeat the request with the same
+ * arguments in case of failures.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param pk kind of coin to create
+ * @param reserve_priv private key of the reserve to withdraw from
+ * @param ps secrets of the planchet
+ * caller must have committed this value to disk before the call (with @a pk)
+ * @param res_cb the callback to call when the final result for this request is available
+ * @param res_cb_cls closure for the above callback
+ * @return handle for the operation on success, NULL on error, i.e.
+ * if the inputs are invalid (i.e. denomination key not with this exchange).
+ * In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_WithdrawHandle *
+TALER_EXCHANGE_withdraw (struct TALER_EXCHANGE_Handle *exchange,
+ const struct TALER_EXCHANGE_DenomPublicKey *pk,
+ const struct
+ TALER_ReservePrivateKeyP *reserve_priv,
+ const struct TALER_PlanchetSecretsP *ps,
+ TALER_EXCHANGE_WithdrawCallback
+ res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_Amount amount_with_fee;
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct TALER_WithdrawRequestPS req;
+ struct TALER_PlanchetDetail pd;
+ struct TALER_EXCHANGE_WithdrawHandle *wh;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+ &req.reserve_pub.eddsa_pub);
+ req.purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS));
+ req.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
+ if (GNUNET_OK !=
+ TALER_amount_add (&amount_with_fee,
+ &pk->fee_withdraw,
+ &pk->value))
+ {
+ /* exchange gave us denomination keys that overflow like this!? */
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ TALER_amount_hton (&req.amount_with_fee,
+ &amount_with_fee);
+ TALER_amount_hton (&req.withdraw_fee,
+ &pk->fee_withdraw);
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&pk->key,
+ ps,
+ &pd))
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ req.h_denomination_pub = pd.denom_pub_hash;
+ GNUNET_CRYPTO_hash (pd.coin_ev,
+ pd.coin_ev_size,
+ &req.h_coin_envelope);
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
+ &req.purpose,
+ &reserve_sig.eddsa_signature));
+ wh = reserve_withdraw_internal (exchange,
+ pk,
+ &reserve_sig,
+ &req.reserve_pub,
+ ps,
+ &pd,
+ res_cb,
+ res_cb_cls);
+ GNUNET_free (pd.coin_ev);
+ return wh;
+}
+
+
+/**
+ * Withdraw a coin from the exchange using a /reserve/withdraw
+ * request. This API is typically used by a wallet to withdraw a tip
+ * where the reserve's signature was created by the merchant already.
+ *
+ * Note that to ensure that no money is lost in case of hardware
+ * failures, the caller must have committed (most of) the arguments to
+ * disk before calling, and be ready to repeat the request with the
+ * same arguments in case of failures.
+ *
+ * @param exchange the exchange handle; the exchange must be ready to operate
+ * @param pk kind of coin to create
+ * @param reserve_sig signature from the reserve authorizing the withdrawal
+ * @param reserve_pub public key of the reserve to withdraw from
+ * @param ps secrets of the planchet
+ * caller must have committed this value to disk before the call (with @a pk)
+ * @param res_cb the callback to call when the final result for this request is available
+ * @param res_cb_cls closure for @a res_cb
+ * @return NULL
+ * if the inputs are invalid (i.e. denomination key not with this exchange).
+ * In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_WithdrawHandle *
+TALER_EXCHANGE_withdraw2 (struct TALER_EXCHANGE_Handle *exchange,
+ const struct
+ TALER_EXCHANGE_DenomPublicKey *pk,
+ const struct
+ TALER_ReserveSignatureP *reserve_sig,
+ const struct
+ TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_PlanchetSecretsP *ps,
+ TALER_EXCHANGE_WithdrawCallback
+ res_cb,
+ void *res_cb_cls)
+{
+ struct TALER_EXCHANGE_WithdrawHandle *wh;
+ struct TALER_PlanchetDetail pd;
+
+ if (GNUNET_OK !=
+ TALER_planchet_prepare (&pk->key,
+ ps,
+ &pd))
+ {
+ GNUNET_break_op (0);
+ return NULL;
+ }
+ wh = reserve_withdraw_internal (exchange,
+ pk,
+ reserve_sig,
+ reserve_pub,
+ ps,
+ &pd,
+ res_cb,
+ res_cb_cls);
+ GNUNET_free (pd.coin_ev);
+ return wh;
+}
+
+
+/**
+ * Cancel a withdraw status request. This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param sign the withdraw sign request handle
+ */
+void
+TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh)
+{
+ if (NULL != wh->job)
+ {
+ GNUNET_CURL_job_cancel (wh->job);
+ wh->job = NULL;
+ }
+ GNUNET_free (wh->url);
+ TALER_curl_easy_post_finished (&wh->ctx);
+ GNUNET_CRYPTO_rsa_public_key_free (wh->pk.key.rsa_public_key);
+ GNUNET_free (wh);
+}