diff options
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/Makefile.am | 13 | ||||
-rw-r--r-- | src/lib/exchange_api_common.c | 416 | ||||
-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.c | 505 | ||||
-rw-r--r-- | src/lib/exchange_api_refresh.c | 1770 | ||||
-rw-r--r-- | src/lib/exchange_api_refresh_common.c | 631 | ||||
-rw-r--r-- | src/lib/exchange_api_refresh_common.h | 240 | ||||
-rw-r--r-- | src/lib/exchange_api_refreshes_reveal.c | 512 | ||||
-rw-r--r-- | src/lib/exchange_api_reserve.c | 1306 | ||||
-rw-r--r-- | src/lib/exchange_api_reserves_get.c | 308 | ||||
-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.c | 611 |
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", + ×tamp), + 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", - ×tamp), - 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); +} |