diff options
24 files changed, 2715 insertions, 299 deletions
diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index 4126894a1..0f25ea3c4 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -469,6 +469,23 @@ TALER_link_decrypt_secret2 (const struct TALER_EncryptedLinkSecretP *secret_enc, /** + * Given the coin and the transfer private keys, compute the + * transfer secret. (Technically, we only need one of the two + * private keys, but the caller currently trivially only has + * the two private keys, so we derive one of the public keys + * internally to this function.) + * + * @param coin_priv coin key + * @param trans_priv transfer private key + * @param[out] ts computed transfer secret + */ +void +TALER_link_derive_transfer_secret (const struct TALER_CoinSpendPrivateKeyP *coin_priv, + const struct TALER_TransferPrivateKeyP *trans_priv, + struct TALER_TransferSecretP *ts); + + +/** * Encrypt the shared @a secret to generate the encrypted link secret. * Also creates the transfer key. * diff --git a/src/include/taler_mint_service.h b/src/include/taler_mint_service.h index b228acc5e..02407a3a9 100644 --- a/src/include/taler_mint_service.h +++ b/src/include/taler_mint_service.h @@ -181,14 +181,14 @@ struct TALER_MINT_DenomPublicKey struct TALER_Amount fee_deposit; /** - *The applicable fee to refresh a coin of this denomination + *The applicable fee to melt/refresh a coin of this denomination */ struct TALER_Amount fee_refresh; }; /** - * Information we get from the mint about auditors. + * @brief Information we get from the mint about auditors. */ struct TALER_MINT_AuditorInformation { @@ -222,9 +222,8 @@ struct TALER_MINT_AuditorInformation }; - /** - * Information about keys from the mint. + * @brief Information about keys from the mint. */ struct TALER_MINT_Keys { @@ -396,11 +395,11 @@ typedef void * * @param mint the mint handle; the mint must be ready to operate * @param amount the amount to be deposited - * @param wire the merchant’s account details, in a format supported by the mint + * @param wire_details the merchant’s account details, in a format supported by the mint * @param h_contract hash of the contact of the merchant with the customer (further details are never disclosed to the mint) * @param coin_pub coin’s public key * @param denom_pub denomination key with which the coin is signed - * @param ub_sig mint’s unblinded signature of the coin + * @param denom_sig mint’s unblinded signature of the coin * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the mint * @param transaction_id transaction id for the transaction between merchant and customer * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) @@ -466,7 +465,7 @@ enum TALER_MINT_ReserveTransactionType { /** - * Entry in the reserve's transaction history. + * @brief Entry in the reserve's transaction history. */ struct TALER_MINT_ReserveHistory { @@ -635,7 +634,7 @@ TALER_MINT_withdraw_sign_cancel (struct TALER_MINT_WithdrawSignHandle *sign); * no money is lost in case of hardware failures, is 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_MINT_refresh_execute() that will generate the request. + * to #TALER_MINT_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 only verified if @a @@ -660,11 +659,11 @@ TALER_MINT_withdraw_sign_cancel (struct TALER_MINT_WithdrawSignHandle *sign); * @param check_sigs verify the validity of the signatures of @a melt_sigs * @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 + * @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 mint). * Otherwise, pointer to a buffer of @a res_size to store persistently - * before proceeding to #TALER_MINT_refresh_execute(). + * before proceeding to #TALER_MINT_refresh_melt(). * Non-null results should be freed using #GNUNET_free(). */ char * @@ -727,11 +726,11 @@ typedef void * In this case, neither callback will be called. */ struct TALER_MINT_RefreshMeltHandle * -TALER_MINT_refresh_melt_execute (struct TALER_MINT_Handle *mint, - size_t refresh_data_length, - const char *refresh_data, - TALER_MINT_RefreshMeltCallback melt_cb, - void *melt_cb_cls); +TALER_MINT_refresh_melt (struct TALER_MINT_Handle *mint, + size_t refresh_data_length, + const char *refresh_data, + TALER_MINT_RefreshMeltCallback melt_cb, + void *melt_cb_cls); /** @@ -841,6 +840,7 @@ struct TALER_MINT_RefreshLinkHandle; * @param num_coins number of fresh coins created, length of the @a sigs and @a coin_privs arrays, 0 if the operation failed * @param coin_privs array of @a num_coins private keys for the coins that were created, NULL on error * @param sigs array of signature over @a num_coins coins, NULL on error + * @param pubs array of public keys for the @a sigs, NULL on error * @param full_response full response from the mint (for logging, in case of errors) */ typedef void @@ -849,6 +849,7 @@ typedef void unsigned int num_coins, const struct TALER_CoinSpendPrivateKeyP *coin_privs, const struct TALER_DenominationSignature *sigs, + const struct TALER_DenominationPublicKey *pubs, json_t *full_response); diff --git a/src/include/taler_mintdb_plugin.h b/src/include/taler_mintdb_plugin.h index 21d83d9d8..c5b9828d7 100644 --- a/src/include/taler_mintdb_plugin.h +++ b/src/include/taler_mintdb_plugin.h @@ -388,33 +388,6 @@ struct TALER_MINTDB_RefreshCommitCoin }; -GNUNET_NETWORK_STRUCT_BEGIN - -/** - * @brief For each (old) coin being melted, we have a `struct - * RefreshCommitLinkP` that allows the user to find the shared secret - * to decrypt the respective refresh links for the new coins in the - * `struct TALER_MINTDB_RefreshCommitCoin`. - */ -struct TALER_MINTDB_RefreshCommitLinkP -{ - /** - * Transfer public key, used to decrypt the @e shared_secret_enc - * in combintation with the corresponding private key of the - * coin. - */ - struct TALER_TransferPublicKeyP transfer_pub; - - /** - * Encrypted shared secret to decrypt the link. - */ - struct TALER_EncryptedLinkSecretP shared_secret_enc; -}; - -GNUNET_NETWORK_STRUCT_END - - - /** * @brief Linked list of refresh information linked to a coin. */ @@ -566,7 +539,7 @@ struct TALER_MINTDB_MeltCommitment /** * 2D-Array of #TALER_CNC_KAPPA and @e new_oldcoins links. */ - struct TALER_MINTDB_RefreshCommitLinkP *commit_links[TALER_CNC_KAPPA]; + struct TALER_RefreshCommitLinkP *commit_links[TALER_CNC_KAPPA]; }; @@ -830,11 +803,8 @@ struct TALER_MINTDB_Plugin * @param sesssion database connection * @param deposit deposit to search for * @return #GNUNET_YES if we know this operation, - * #GNUNET_NO if this deposit is unknown to us, - * #GNUNET_SYSERR on DB error or if same coin(pub), merchant(pub) and - * transaction ID are already in DB, but for different - * other transaction details (contract, wiring details, - * amount, etc.) + * #GNUNET_NO if this exact deposit is unknown to us, + * #GNUNET_SYSERR on DB error */ int (*have_deposit) (void *cls, @@ -1032,7 +1002,7 @@ struct TALER_MINTDB_Plugin const struct GNUNET_HashCode *session_hash, uint16_t cnc_index, uint16_t num_links, - const struct TALER_MINTDB_RefreshCommitLinkP *commit_links); + const struct TALER_RefreshCommitLinkP *commit_links); /** * Obtain the commited (encrypted) refresh link data @@ -1054,7 +1024,7 @@ struct TALER_MINTDB_Plugin const struct GNUNET_HashCode *session_hash, uint16_t cnc_index, uint16_t num_links, - struct TALER_MINTDB_RefreshCommitLinkP *links); + struct TALER_RefreshCommitLinkP *links); /** diff --git a/src/include/taler_signatures.h b/src/include/taler_signatures.h index 402e67fe2..c5348eb5d 100644 --- a/src/include/taler_signatures.h +++ b/src/include/taler_signatures.h @@ -648,6 +648,31 @@ struct TALER_MintKeyValidityPS }; +/** + * @brief For each (old) coin being melted, we have a `struct + * RefreshCommitLinkP` that allows the user to find the shared secret + * to decrypt the respective refresh links for the new coins in the + * `struct TALER_MINTDB_RefreshCommitCoin`. + * + * Part of the construction of the refresh session's hash and + * thus of what is signed there. + */ +struct TALER_RefreshCommitLinkP +{ + /** + * Transfer public key, used to decrypt the @e shared_secret_enc + * in combintation with the corresponding private key of the + * coin. + */ + struct TALER_TransferPublicKeyP transfer_pub; + + /** + * Encrypted shared secret to decrypt the link. + */ + struct TALER_EncryptedLinkSecretP shared_secret_enc; +}; + + GNUNET_NETWORK_STRUCT_END #endif diff --git a/src/mint-lib/Makefile.am b/src/mint-lib/Makefile.am index 400fc77e0..b7b39ded7 100644 --- a/src/mint-lib/Makefile.am +++ b/src/mint-lib/Makefile.am @@ -14,6 +14,7 @@ libtalermint_la_LDFLAGS = \ -no-undefined libtalermint_la_SOURCES = \ + mint_api_common.c mint_api_common.h \ mint_api_context.c mint_api_context.h \ mint_api_json.c mint_api_json.h \ mint_api_handle.c mint_api_handle.h \ diff --git a/src/mint-lib/mint_api_common.c b/src/mint-lib/mint_api_common.c new file mode 100644 index 000000000..d8e83c785 --- /dev/null +++ b/src/mint-lib/mint_api_common.c @@ -0,0 +1,154 @@ +/* + This file is part of TALER + Copyright (C) 2015 Christian Grothoff (and other contributing authors) + + 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, If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file mint-lib/mint_api_common.c + * @brief common functions for the mint API + * @author Christian Grothoff + */ +#include "platform.h" +#include "mint_api_common.h" +#include "mint_api_json.h" +#include "mint_api_context.h" +#include "mint_api_handle.h" +#include "taler_signatures.h" + + +/** + * Verify a coins transaction history as returned by the mint. + * + * @param currency expected currency for the coin + * @param coin_pub public key of the coin + * @param history history of the coin in json encoding + * @param[out] total how much of the coin has been spent according to @a history + * @return #GNUNET_OK if @a history is valid, #GNUNET_SYSERR if not + */ +int +TALER_MINT_verify_coin_history_ (const char *currency, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + json_t *history, + struct TALER_Amount *total) +{ + size_t len; + size_t off; + + if (NULL == history) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + len = json_array_size (history); + if (0 == len) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + TALER_amount_get_zero (currency, + total); + for (off=0;off<len;off++) + { + json_t *transaction; + struct TALER_Amount amount; + struct GNUNET_CRYPTO_EccSignaturePurpose *purpose; + struct MAJ_Specification spec[] = { + MAJ_spec_amount ("amount", + &amount), + MAJ_spec_eddsa_signed_purpose ("signature", + &purpose, + &coin_pub->eddsa_pub), + MAJ_spec_end + }; + + transaction = json_array_get (history, + off); + if (GNUNET_OK != + MAJ_parse_json (transaction, + spec)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + switch (ntohl (purpose->purpose)) + { + case TALER_SIGNATURE_WALLET_COIN_DEPOSIT: + { + const struct TALER_DepositRequestPS *dr; + struct TALER_Amount dr_amount; + + if (ntohl (purpose->size) != sizeof (struct TALER_DepositRequestPS)) + { + GNUNET_break (0); + MAJ_parse_free (spec); + return GNUNET_SYSERR; + } + dr = (const struct TALER_DepositRequestPS *) purpose; + TALER_amount_ntoh (&dr_amount, + &dr->amount_with_fee); + if (0 != TALER_amount_cmp (&dr_amount, + &amount)) + { + GNUNET_break (0); + MAJ_parse_free (spec); + return GNUNET_SYSERR; + } + } + break; + case TALER_SIGNATURE_WALLET_COIN_MELT: + { + const struct TALER_RefreshMeltCoinAffirmationPS *rm; + struct TALER_Amount rm_amount; + + if (ntohl (purpose->size) != sizeof (struct TALER_RefreshMeltCoinAffirmationPS)) + { + GNUNET_break (0); + MAJ_parse_free (spec); + return GNUNET_SYSERR; + } + rm = (const struct TALER_RefreshMeltCoinAffirmationPS *) purpose; + TALER_amount_ntoh (&rm_amount, + &rm->amount_with_fee); + if (0 != TALER_amount_cmp (&rm_amount, + &amount)) + { + GNUNET_break (0); + MAJ_parse_free (spec); + return GNUNET_SYSERR; + } + } + break; + default: + /* signature not supported, new version on server? */ + GNUNET_break (0); + MAJ_parse_free (spec); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_add (total, + total, + &amount)) + { + /* overflow in history already!? inconceivable! Bad mint! */ + GNUNET_break_op (0); + MAJ_parse_free (spec); + return GNUNET_SYSERR; + } + MAJ_parse_free (spec); + } + return GNUNET_OK; +} + + +/* end of mint_api_common.c */ diff --git a/src/mint-lib/mint_api_common.h b/src/mint-lib/mint_api_common.h new file mode 100644 index 000000000..d256fa428 --- /dev/null +++ b/src/mint-lib/mint_api_common.h @@ -0,0 +1,41 @@ +/* + This file is part of TALER + Copyright (C) 2015 Christian Grothoff (and other contributing authors) + + 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, If not, see + <http://www.gnu.org/licenses/> +*/ +/** + * @file mint-lib/mint_api_common.h + * @brief common functions for the mint API + * @author Christian Grothoff + */ +#include <jansson.h> +#include <gnunet/gnunet_util_lib.h> +#include "taler_mint_service.h" + +/** + * Verify a coins transaction history as returned by the mint. + * + * @param currency expected currency for the coin + * @param coin_pub public key of the coin + * @param history history of the coin in json encoding + * @param[out] total how much of the coin has been spent according to @a history + * @return #GNUNET_OK if @a history is valid, #GNUNET_SYSERR if not + */ +int +TALER_MINT_verify_coin_history_ (const char *currency, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + json_t *history, + struct TALER_Amount *total); + +/* end of mint_api_common.h */ diff --git a/src/mint-lib/mint_api_context.h b/src/mint-lib/mint_api_context.h index c545a3fe7..79613cc8b 100644 --- a/src/mint-lib/mint_api_context.h +++ b/src/mint-lib/mint_api_context.h @@ -90,7 +90,7 @@ MAC_job_cancel (struct MAC_Job *job); /** - * Buffer data structure we use to buffer the HTTP download + * @brief Buffer data structure we use to buffer the HTTP download * before giving it to the JSON parser. */ struct MAC_DownloadBuffer diff --git a/src/mint-lib/mint_api_deposit.c b/src/mint-lib/mint_api_deposit.c index 7be88a887..3da9d0ae4 100644 --- a/src/mint-lib/mint_api_deposit.c +++ b/src/mint-lib/mint_api_deposit.c @@ -26,6 +26,7 @@ #include <microhttpd.h> /* just for HTTP status codes */ #include <gnunet/gnunet_util_lib.h> #include "taler_mint_service.h" +#include "mint_api_common.h" #include "mint_api_json.h" #include "mint_api_context.h" #include "mint_api_handle.h" @@ -153,114 +154,19 @@ verify_deposit_signature_forbidden (const struct TALER_MINT_DepositHandle *dh, json_t *json) { json_t *history; - size_t len; - size_t off; struct TALER_Amount total; history = json_object_get (json, "history"); - if (NULL == history) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - len = json_array_size (history); - if (0 == len) + if (GNUNET_OK != + TALER_MINT_verify_coin_history_ (dh->coin_value.currency, + &dh->depconf.coin_pub, + history, + &total)) { GNUNET_break_op (0); return GNUNET_SYSERR; } - TALER_amount_get_zero (dh->coin_value.currency, - &total); - for (off=0;off<len;off++) - { - json_t *transaction; - struct TALER_Amount amount; - struct GNUNET_CRYPTO_EccSignaturePurpose *purpose; - struct MAJ_Specification spec[] = { - MAJ_spec_amount ("amount", - &amount), - MAJ_spec_eddsa_signed_purpose ("signature", - &purpose, - &dh->depconf.coin_pub.eddsa_pub), - MAJ_spec_end - }; - - transaction = json_array_get (history, - off); - if (GNUNET_OK != - MAJ_parse_json (transaction, - spec)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - switch (ntohl (purpose->purpose)) - { - case TALER_SIGNATURE_WALLET_COIN_DEPOSIT: - { - const struct TALER_DepositRequestPS *dr; - struct TALER_Amount dr_amount; - - if (ntohl (purpose->size) != sizeof (struct TALER_DepositRequestPS)) - { - GNUNET_break (0); - MAJ_parse_free (spec); - return GNUNET_SYSERR; - } - dr = (const struct TALER_DepositRequestPS *) purpose; - TALER_amount_ntoh (&dr_amount, - &dr->amount_with_fee); - if (0 != TALER_amount_cmp (&dr_amount, - &amount)) - { - GNUNET_break (0); - MAJ_parse_free (spec); - return GNUNET_SYSERR; - } - } - break; - case TALER_SIGNATURE_WALLET_COIN_MELT: - { - const struct TALER_RefreshMeltCoinAffirmationPS *rm; - struct TALER_Amount rm_amount; - - if (ntohl (purpose->size) != sizeof (struct TALER_RefreshMeltCoinAffirmationPS)) - { - GNUNET_break (0); - MAJ_parse_free (spec); - return GNUNET_SYSERR; - } - rm = (const struct TALER_RefreshMeltCoinAffirmationPS *) purpose; - TALER_amount_ntoh (&rm_amount, - &rm->amount_with_fee); - if (0 != TALER_amount_cmp (&rm_amount, - &amount)) - { - GNUNET_break (0); - MAJ_parse_free (spec); - return GNUNET_SYSERR; - } - } - break; - default: - /* signature not supported, new version on server? */ - GNUNET_break (0); - MAJ_parse_free (spec); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_add (&total, - &total, - &amount)) - { - /* overflow in history already!? inconceivable! Bad mint! */ - GNUNET_break_op (0); - MAJ_parse_free (spec); - return GNUNET_SYSERR; - } - MAJ_parse_free (spec); - } if (GNUNET_OK != TALER_amount_add (&total, &total, @@ -452,7 +358,7 @@ verify_signatures (const struct TALER_MINT_DenomPublicKey *dki, * * @param mint the mint handle; the mint must be ready to operate * @param amount the amount to be deposited - * @param wire the merchant’s account details, in a format supported by the mint + * @param wire_details the merchant’s account details, in a format supported by the mint * @param h_contract hash of the contact of the merchant with the customer (further details are never disclosed to the mint) * @param coin_pub coin’s public key * @param denom_pub denomination key with which the coin is signed diff --git a/src/mint-lib/mint_api_json.c b/src/mint-lib/mint_api_json.c index 8b0b54375..b15173940 100644 --- a/src/mint-lib/mint_api_json.c +++ b/src/mint-lib/mint_api_json.c @@ -253,6 +253,37 @@ parse_json (json_t *root, } break; + case MAJ_CMD_UINT16: + { + json_int_t val; + + if (! json_is_integer (pos)) + { + GNUNET_break_op (0); + return i; + } + val = json_integer_value (pos); + if ( (0 > val) || (val > UINT16_MAX) ) + { + GNUNET_break_op (0); + return i; + } + *spec[i].details.u16 = (uint16_t) val; + } + break; + + case MAJ_CMD_JSON_OBJECT: + { + if (! (json_is_object (pos) || json_is_array (pos)) ) + { + GNUNET_break_op (0); + return i; + } + json_incref (pos); + *spec[i].details.obj = pos; + } + break; + default: GNUNET_break (0); return i; @@ -307,6 +338,10 @@ parse_free (struct MAJ_Specification *spec, GNUNET_free (*spec[i].details.eddsa_signature.purpose_p); *spec[i].details.eddsa_signature.purpose_p = NULL; break; + case MAJ_CMD_JSON_OBJECT: + json_decref (*spec[i].details.obj); + *spec[i].details.obj = NULL; + break; default: GNUNET_break (0); break; @@ -418,6 +453,46 @@ MAJ_spec_amount (const char *name, /** + * 16-bit integer. + * + * @param name name of the JSON field + * @param[out] u16 where to store the integer found under @a name + */ +struct MAJ_Specification +MAJ_spec_uint16 (const char *name, + uint16_t *u16) +{ + struct MAJ_Specification ret = + { + .cmd = MAJ_CMD_UINT16, + .field = name, + .details.u16 = u16 + }; + return ret; +} + + +/** + * JSON object. + * + * @param name name of the JSON field + * @param[out] jsonp where to store the JSON found under @a name + */ +struct MAJ_Specification +MAJ_spec_json (const char *name, + json_t **jsonp) +{ + struct MAJ_Specification ret = + { + .cmd = MAJ_CMD_JSON_OBJECT, + .field = name, + .details.obj = jsonp + }; + return ret; +} + + +/** * Specification for parsing an RSA public key. * * @param name name of the JSON field diff --git a/src/mint-lib/mint_api_json.h b/src/mint-lib/mint_api_json.h index 46ccef3ab..2af5588e5 100644 --- a/src/mint-lib/mint_api_json.h +++ b/src/mint-lib/mint_api_json.h @@ -79,7 +79,17 @@ enum MAJ_Command MAJ_CMD_STRING, /** - * Parse at current position. + * Parse `uint16_t` integer at the current position. + */ + MAJ_CMD_UINT16, + + /** + * Parse JSON object at the current position. + */ + MAJ_CMD_JSON_OBJECT, + + /** + * Parse ??? at current position. */ MAJ_CMD_C @@ -87,7 +97,7 @@ enum MAJ_Command /** - * Entry in parser specification for #MAJ_parse_json. + * @brief Entry in parser specification for #MAJ_parse_json. */ struct MAJ_Specification { @@ -181,6 +191,16 @@ struct MAJ_Specification */ const char **strptr; + /** + * Where to store 16-bit integer. + */ + uint16_t *u16; + + /** + * Where to store a JSON object. + */ + json_t **obj; + } details; }; @@ -249,7 +269,7 @@ MAJ_spec_string (const char *name, * Absolute time. * * @param name name of the JSON field - * @param at where to store the absolute time found under @a name + * @param[out] at where to store the absolute time found under @a name */ struct MAJ_Specification MAJ_spec_absolute_time (const char *name, @@ -257,6 +277,28 @@ MAJ_spec_absolute_time (const char *name, /** + * 16-bit integer. + * + * @param name name of the JSON field + * @param[out] u16 where to store the integer found under @a name + */ +struct MAJ_Specification +MAJ_spec_uint16 (const char *name, + uint16_t *u16); + + +/** + * JSON object. + * + * @param name name of the JSON field + * @param[out] jsonp where to store the JSON found under @a name + */ +struct MAJ_Specification +MAJ_spec_json (const char *name, + json_t **jsonp); + + +/** * Specification for parsing an amount value. * * @param name name of the JSON field diff --git a/src/mint-lib/mint_api_refresh.c b/src/mint-lib/mint_api_refresh.c index 30d24e024..66e8ea83b 100644 --- a/src/mint-lib/mint_api_refresh.c +++ b/src/mint-lib/mint_api_refresh.c @@ -25,6 +25,7 @@ #include <microhttpd.h> /* just for HTTP status codes */ #include <gnunet/gnunet_util_lib.h> #include "taler_mint_service.h" +#include "mint_api_common.h" #include "mint_api_json.h" #include "mint_api_context.h" #include "mint_api_handle.h" @@ -56,7 +57,12 @@ struct MeltedCoinP /** * The applicable fee for withdrawing a coin of this denomination */ - struct TALER_AmountNBO fee_withdraw; + 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. @@ -68,6 +74,16 @@ struct MeltedCoinP */ struct GNUNET_TIME_AbsoluteNBO deposit_valid_until; + /** + * 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; @@ -88,10 +104,9 @@ struct FreshCoinP struct TALER_CoinSpendPrivateKeyP coin_priv; /** - * Link secret used to encrypt the @a coin_priv and the blinding - * key in the linkage data. + * Size of the encoded blinding key that follows. */ - struct TALER_LinkSecretP link_secret; + uint32_t bbuf_size; /* Followed by serialization of: - struct TALER_DenominationBlindingKey blinding_key; @@ -113,9 +128,10 @@ struct MeltDataP struct GNUNET_HashCode melt_session_hash; /** - * Transfer secrets for each cut-and-choose dimension. + * Link secret used to encrypt the @a coin_priv and the blinding + * key in the linkage data for the respective cut-and-choose dimension. */ - struct TALER_TransferSecretP transfer_secrets[TALER_CNC_KAPPA]; + struct TALER_LinkSecretP link_secrets[TALER_CNC_KAPPA]; /** * Number of coins we are melting, in NBO @@ -130,7 +146,8 @@ struct MeltDataP /* Followed by serializations of: 1) struct MeltedCoinP melted_coins[num_melted_coins]; 2) struct TALER_MINT_DenomPublicKey fresh_pks[num_fresh_coins]; - 3) struct FreshCoinP fresh_coins[num_fresh_coins][k]; + 3) TALER_CNC_KAPPA times: + 3a) struct FreshCoinP fresh_coins[num_fresh_coins]; */ }; @@ -154,9 +171,14 @@ struct MeltedCoin struct TALER_Amount melt_amount_with_fee; /** - * The applicable fee for withdrawing a coin of this denomination + * The applicable fee for melting a coin of this denomination */ - struct TALER_Amount fee_withdraw; + 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. @@ -166,7 +188,7 @@ struct MeltedCoin /** * Timestamp indicating when coins of this denomination become invalid. */ - struct GNUNET_TIME_AbsoluteNBO deposit_valid_until; + struct GNUNET_TIME_Absolute deposit_valid_until; /** * Denomination key of the original coin. @@ -194,12 +216,6 @@ struct FreshCoin struct TALER_CoinSpendPrivateKeyP coin_priv; /** - * Link secret used to encrypt the @a coin_priv and the blinding - * key in the linkage data. - */ - struct TALER_LinkSecretP link_secret; - - /** * Blinding key used for blinding during blind signing. */ struct TALER_DenominationBlindingKey blinding_key; @@ -219,9 +235,9 @@ struct MeltData struct GNUNET_HashCode melt_session_hash; /** - * Transfer secrets for each cut-and-choose dimension. + * Link secrets for each cut-and-choose dimension. */ - struct TALER_TransferSecretP transfer_secrets[TALER_CNC_KAPPA]; + struct TALER_LinkSecretP link_secrets[TALER_CNC_KAPPA]; /** * Number of coins we are melting @@ -262,8 +278,12 @@ struct MeltData static void free_melted_coin (struct MeltedCoin *mc) { - GNUNET_CRYPTO_rsa_public_key_free (mc->pub_key.rsa_public_key); - GNUNET_CRYPTO_rsa_signature_free (mc->sig.rsa_signature); + if (NULL == mc) + return; + 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); } @@ -276,12 +296,18 @@ free_melted_coin (struct MeltedCoin *mc) static void free_fresh_coin (struct FreshCoin *fc) { - GNUNET_CRYPTO_rsa_blinding_key_free (fc->blinding_key.rsa_blinding_key); + if (NULL == fc) + return; + if (NULL != fc->blinding_key.rsa_blinding_key) + GNUNET_CRYPTO_rsa_blinding_key_free (fc->blinding_key.rsa_blinding_key); } /** - * Free all information associated with a melting session. + * 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) @@ -292,19 +318,28 @@ free_melt_data (struct MeltData *md) unsigned int i; unsigned int j; - for (i=0;i<md->num_melted_coins;i++) - free_melted_coin (&md->melted_coins[i]); - GNUNET_free (md->melted_coins); - - for (i=0;i<md->num_fresh_coins;i++) - GNUNET_CRYPTO_rsa_public_key_free (md->fresh_pks[i].rsa_public_key); - GNUNET_free (md->fresh_pks); + if (NULL != md->melted_coins) + { + for (i=0;i<md->num_melted_coins;i++) + free_melted_coin (&md->melted_coins[i]); + GNUNET_free (md->melted_coins); + } + if (NULL != md->fresh_pks) + { + for (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 (i=0;i<TALER_CNC_KAPPA;i++) { - for (j=0;j<md->num_fresh_coins;j++) - free_fresh_coin (&md->fresh_coins[i][j]); - GNUNET_free (md->fresh_coins[i]); + if (NULL != md->fresh_coins) + { + for (j=0;j<md->num_fresh_coins;j++) + free_fresh_coin (&md->fresh_coins[i][j]); + GNUNET_free (md->fresh_coins[i]); + } } /* Finally, clean up a bit... (NOTE: compilers might optimize this away, so this is @@ -317,6 +352,313 @@ free_melt_data (struct MeltData *md) /** + * 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.deposit_valid_until = GNUNET_TIME_absolute_hton (mc->deposit_valid_until); + 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->deposit_valid_until = GNUNET_TIME_absolute_ntoh (mcp.deposit_valid_until); + 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 FreshCoin *fc, + char *buf, + size_t off) +{ + struct FreshCoinP fcp; + char *bbuf; + size_t bbuf_size; + + bbuf_size = GNUNET_CRYPTO_rsa_blinding_key_encode (fc->blinding_key.rsa_blinding_key, + &bbuf); + if (NULL == buf) + { + GNUNET_free (bbuf); + return sizeof (struct FreshCoinP) + bbuf_size; + } + fcp.coin_priv = fc->coin_priv; + fcp.bbuf_size = htonl ((uint32_t) bbuf_size); + memcpy (&buf[off], + &fcp, + sizeof (struct FreshCoinP)); + memcpy (&buf[off + sizeof (struct FreshCoinP)], + bbuf, + bbuf_size); + GNUNET_free (bbuf); + return sizeof (struct FreshCoinP) + bbuf_size; +} + + +/** + * 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 FreshCoin *fc, + const char *buf, + size_t size, + int *ok) +{ + struct FreshCoinP fcp; + size_t bbuf_size; + + if (size < sizeof (struct FreshCoinP)) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + memcpy (&fcp, + buf, + sizeof (struct FreshCoinP)); + bbuf_size = ntohl (fcp.bbuf_size); + if (size < sizeof (struct FreshCoinP) + bbuf_size) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + fc->blinding_key.rsa_blinding_key + = GNUNET_CRYPTO_rsa_blinding_key_decode (&buf[sizeof (struct FreshCoinP)], + bbuf_size); + if (NULL == fc->blinding_key.rsa_blinding_key) + { + GNUNET_break (0); + *ok = GNUNET_NO; + return 0; + } + fc->coin_priv = fcp.coin_priv; + return sizeof (struct FreshCoinP) + bbuf_size; +} + + +/** * Serialize melt data. * * @param md data to serialize @@ -327,9 +669,52 @@ static char * serialize_melt_data (const struct MeltData *md, size_t *res_size) { - GNUNET_break (0); // FIXME: not implemented - *res_size = 0; - return NULL; + size_t size; + size_t asize; + char *buf; + unsigned int i; + unsigned int j; + + size = 0; + 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->melt_session_hash = md->melt_session_hash; + for (i=0;i<TALER_CNC_KAPPA;i++) + mdp->link_secrets[i] = md->link_secrets[i]; + mdp->num_melted_coins = htons (md->num_melted_coins); + mdp->num_fresh_coins = htons (md->num_fresh_coins); + } + for (i=0;i<md->num_melted_coins;i++) + size += serialize_melted_coin (&md->melted_coins[i], + buf, + size); + for (i=0;i<md->num_fresh_coins;i++) + size += serialize_denomination_key (&md->fresh_pks[i], + buf, + size); + for (i=0;i<TALER_CNC_KAPPA;i++) + for(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; } @@ -344,8 +729,84 @@ static struct MeltData * deserialize_melt_data (const char *buf, size_t buf_size) { - GNUNET_break (0); // FIXME: not implemented - return NULL; + struct MeltData *md; + struct MeltDataP mdp; + unsigned int i; + unsigned int j; + 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->melt_session_hash = mdp.melt_session_hash; + for (i=0;i<TALER_CNC_KAPPA;i++) + md->link_secrets[i] = mdp.link_secrets[i]; + md->num_melted_coins = ntohs (mdp.num_melted_coins); + md->num_fresh_coins = ntohs (mdp.num_fresh_coins); + md->melted_coins = GNUNET_new_array (md->num_melted_coins, + struct MeltedCoin); + md->fresh_pks = GNUNET_new_array (md->num_fresh_coins, + struct TALER_DenominationPublicKey); + for (i=0;i<TALER_CNC_KAPPA;i++) + md->fresh_coins[i] = GNUNET_new_array (md->num_fresh_coins, + struct FreshCoin); + off = sizeof (struct MeltDataP); + ok = GNUNET_YES; + for (i=0;(i<md->num_melted_coins)&&(GNUNET_YES == ok);i++) + off += deserialize_melted_coin (&md->melted_coins[i], + &buf[off], + buf_size - off, + &ok); + for (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 (i=0;i<TALER_CNC_KAPPA;i++) + for(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; +} + + +/** + * Setup information for a fresh coin. + * + * @param[out] fc value to initialize + * @param pk denomination information for the fresh coin + */ +static void +setup_fresh_coin (struct FreshCoin *fc, + const struct TALER_MINT_DenomPublicKey *pk) +{ + struct GNUNET_CRYPTO_EddsaPrivateKey *epk; + unsigned int len; + + epk = GNUNET_CRYPTO_eddsa_key_create (); + fc->coin_priv.eddsa_priv = *epk; + GNUNET_free (epk); + len = GNUNET_CRYPTO_rsa_public_key_len (pk->key.rsa_public_key); + fc->blinding_key.rsa_blinding_key + = GNUNET_CRYPTO_rsa_blinding_key_create (len); } @@ -359,11 +820,11 @@ deserialize_melt_data (const char *buf, * no money is lost in case of hardware failures, is 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_MINT_refresh_execute() that will generate the request. + * to #TALER_MINT_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 only verified if @a - * check_sigs is set to #GNUNET_YES, as this may be relatively + * consistent. However, the @a melts_sigs are only verified if + * @a check_sigs is set to #GNUNET_YES, as this may be relatively * expensive and should be redundant. * * Aside from some non-trivial cryptographic operations that might @@ -384,11 +845,11 @@ deserialize_melt_data (const char *buf, * @param check_sigs verify the validity of the signatures of @a melt_sigs * @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 + * @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 mint). * Otherwise, pointer to a buffer of @a res_size to store persistently - * before proceeding to #TALER_MINT_refresh_execute(). + * before proceeding to #TALER_MINT_refresh_melt(). * Non-null results should be freed using #GNUNET_free(). */ char * @@ -404,10 +865,152 @@ TALER_MINT_refresh_prepare (unsigned int num_melts, { struct MeltData md; char *buf; + unsigned int i; + unsigned int j; + struct GNUNET_HashContext *hash_context; - GNUNET_break (0); // FIXME: not implemented - // FIXME: init 'md' here! + /* build up melt data structure */ + for (i=0;i<TALER_CNC_KAPPA;i++) + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, + &md.link_secrets[i], + sizeof (struct TALER_LinkSecretP)); + md.num_melted_coins = num_melts; + md.num_fresh_coins = fresh_pks_len; + md.melted_coins = GNUNET_new_array (num_melts, + struct MeltedCoin); + for (i=0;i<num_melts;i++) + { + md.melted_coins[i].coin_priv = melt_privs[i]; + md.melted_coins[i].melt_amount_with_fee = melt_amounts[i]; + md.melted_coins[i].fee_melt = melt_pks[i].fee_refresh; + md.melted_coins[i].original_value = melt_pks[i].value; + for (j=0;j<TALER_CNC_KAPPA;j++) + { + struct GNUNET_CRYPTO_EcdhePrivateKey *tpk; + + tpk = GNUNET_CRYPTO_ecdhe_key_create (); + md.melted_coins[i].transfer_priv[j].ecdhe_priv = *tpk; + GNUNET_free (tpk); + } + md.melted_coins[i].deposit_valid_until + = melt_pks[i].deposit_valid_until; + md.melted_coins[i].pub_key.rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_dup (melt_pks[i].key.rsa_public_key); + md.melted_coins[i].sig.rsa_signature + = GNUNET_CRYPTO_rsa_signature_dup (melt_sigs[i].rsa_signature); + } + md.fresh_pks = GNUNET_new_array (fresh_pks_len, + struct TALER_DenominationPublicKey); + for (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); + for (i=0;i<TALER_CNC_KAPPA;i++) + { + md.fresh_coins[i] = GNUNET_new_array (fresh_pks_len, + struct FreshCoin); + for (j=0;j<fresh_pks_len;j++) + setup_fresh_coin (&md.fresh_coins[i][j], + &fresh_pks[j]); + } + /* now compute melt session hash */ + hash_context = GNUNET_CRYPTO_hash_context_start (); + for (i=0;i<fresh_pks_len;i++) + { + char *buf; + size_t buf_size; + + buf_size = GNUNET_CRYPTO_rsa_public_key_encode (fresh_pks[i].key.rsa_public_key, + &buf); + GNUNET_CRYPTO_hash_context_read (hash_context, + buf, + buf_size); + GNUNET_free (buf); + } + for (i=0;i<num_melts;i++) + { + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_AmountNBO melt_amount; + + GNUNET_CRYPTO_eddsa_key_get_public (&melt_privs[i].eddsa_priv, + &coin_pub.eddsa_pub); + GNUNET_CRYPTO_hash_context_read (hash_context, + &coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP)); + TALER_amount_hton (&melt_amount, + &melt_amounts[i]); + GNUNET_CRYPTO_hash_context_read (hash_context, + &melt_amount, + sizeof (struct TALER_AmountNBO)); + + } + for (i = 0; i < TALER_CNC_KAPPA; i++) + { + for (j = 0; j < fresh_pks_len; j++) + { + const struct FreshCoin *fc; /* coin this is about */ + struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_HashCode coin_hash; + char *coin_ev; /* blinded message to be signed (in envelope) for each coin */ + size_t coin_ev_size; + struct TALER_RefreshLinkDecrypted rld; + struct TALER_RefreshLinkEncrypted *rle; + char *link_enc; /* encrypted link data */ + size_t link_enc_size; + + fc = &md.fresh_coins[i][j]; + 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); + coin_ev_size = GNUNET_CRYPTO_rsa_blind (&coin_hash, + fc->blinding_key.rsa_blinding_key, + md.fresh_pks[j].rsa_public_key, + &coin_ev); + GNUNET_CRYPTO_hash_context_read (hash_context, + coin_ev, + coin_ev_size); + GNUNET_free (coin_ev); + + rld.coin_priv = fc->coin_priv; + rld.blinding_key = fc->blinding_key; + rle = TALER_refresh_encrypt (&rld, + &md.link_secrets[i]); + link_enc = TALER_refresh_link_encrypted_encode (rle, + &link_enc_size); + + GNUNET_CRYPTO_hash_context_read (hash_context, + link_enc, + link_enc_size); + GNUNET_free (link_enc); + } + } + for (i = 0; i < TALER_CNC_KAPPA; i++) + { + for (j = 0; j < num_melts; j++) + { + struct TALER_RefreshCommitLinkP rcl; + struct TALER_TransferSecretP trans_sec; + + GNUNET_CRYPTO_ecdhe_key_get_public (&md.melted_coins[j].transfer_priv[i].ecdhe_priv, + &rcl.transfer_pub.ecdhe_pub); + TALER_link_derive_transfer_secret (&melt_privs[j], + &md.melted_coins[j].transfer_priv[i], + &trans_sec); + TALER_transfer_encrypt (&md.link_secrets[i], + &trans_sec, + &rcl.shared_secret_enc); + GNUNET_CRYPTO_hash_context_read (hash_context, + &rcl, + sizeof (struct TALER_RefreshCommitLinkP)); + } + } + + GNUNET_CRYPTO_hash_context_finish (hash_context, + &md.melt_session_hash); + + /* finally, serialize everything */ buf = serialize_melt_data (&md, res_size); free_melt_data (&md); @@ -467,6 +1070,195 @@ struct TALER_MINT_RefreshMeltHandle /** + * Verify that the signature on the "200 OK" response + * from the mint is valid. + * + * @param rmh melt handle + * @param json json reply with the signature + * @param[out] noreveal_index set to the noreveal index selected by the mint + * @return #GNUNET_OK if the signature is valid, #GNUNET_SYSERR if not + */ +static int +verify_refresh_melt_signature_ok (struct TALER_MINT_RefreshMeltHandle *rmh, + json_t *json, + uint16_t *noreveal_index) +{ + struct TALER_MintSignatureP mint_sig; + struct TALER_MintPublicKeyP mint_pub; + const struct TALER_MINT_Keys *key_state; + struct MAJ_Specification spec[] = { + MAJ_spec_fixed_auto ("mint_sig", &mint_sig), + MAJ_spec_fixed_auto ("mint_pub", &mint_sig), + MAJ_spec_uint16 ("noreveal_index", noreveal_index), + MAJ_spec_end + }; + struct TALER_RefreshMeltConfirmationPS confirm; + + if (GNUNET_OK != + MAJ_parse_json (json, + spec)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* check that mint signing key is permitted */ + key_state = TALER_MINT_get_keys (rmh->mint); + if (GNUNET_OK != + TALER_MINT_test_signing_key (key_state, + &mint_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 mint */ + confirm.purpose.purpose = htonl (TALER_SIGNATURE_MINT_CONFIRM_MELT); + confirm.purpose.size = htonl (sizeof (confirm)); + confirm.session_hash = rmh->md->melt_session_hash; + confirm.noreveal_index = htons (*noreveal_index); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MINT_CONFIRM_MELT, + &confirm.purpose, + &mint_sig.eddsa_signature, + &mint_pub.eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Verify that the signatures on the "403 FORBIDDEN" response from the + * mint 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_forbidden (struct TALER_MINT_RefreshMeltHandle *rmh, + 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; + unsigned int i; + struct MAJ_Specification spec[] = { + MAJ_spec_json ("history", &history), + MAJ_spec_fixed_auto ("coin_pub", &coin_pub), + MAJ_spec_amount ("original_value", &original_value), + MAJ_spec_amount ("requested_value", &melt_value_with_fee), + MAJ_spec_end + }; + const struct MeltedCoin *mc; + + /* parse JSON reply */ + if (GNUNET_OK != + MAJ_parse_json (json, + spec)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* Find out which coin was deemed problematic by the mint */ + mc = NULL; + for (i=0;i<rmh->md->num_melted_coins;i++) + { + if (0 == TALER_amount_cmp (&melt_value_with_fee, + &rmh->md->melted_coins[i].melt_amount_with_fee)) + { + struct TALER_CoinSpendPublicKeyP mc_pub; + + GNUNET_CRYPTO_eddsa_key_get_public (&rmh->md->melted_coins[i].coin_priv.eddsa_priv, + &mc_pub.eddsa_pub); + if (0 == memcmp (&mc_pub, + &coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP))) + { + mc = &rmh->md->melted_coins[i]; + break; + } + } + } + if (NULL == mc) + { + /* coin not found in our original request */ + GNUNET_break_op (0); + json_decref (history); + return GNUNET_SYSERR; + } + + /* 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_MINT_verify_coin_history_ (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. * @@ -480,6 +1272,7 @@ handle_refresh_melt_finished (void *cls, struct TALER_MINT_RefreshMeltHandle *rmh = cls; long response_code; json_t *json; + uint16_t noreveal_index = TALER_CNC_KAPPA; /* invalid value */ rmh->job = NULL; json = MAC_download_get_result (&rmh->db, @@ -490,8 +1283,22 @@ handle_refresh_melt_finished (void *cls, case 0: break; case MHD_HTTP_OK: - GNUNET_break (0); // FIXME: NOT implemented! (parse, check sig!) - + if (GNUNET_OK != + verify_refresh_melt_signature_ok (rmh, + json, + &noreveal_index)) + { + GNUNET_break_op (0); + response_code = 0; + } + if (NULL != rmh->melt_cb) + { + rmh->melt_cb (rmh->melt_cb_cls, + response_code, + noreveal_index, + json); + rmh->melt_cb = NULL; + } break; case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the mint is buggy @@ -499,7 +1306,13 @@ handle_refresh_melt_finished (void *cls, break; case MHD_HTTP_FORBIDDEN: /* Double spending; check signatures on transaction history */ - GNUNET_break (0); // FIXME: NOT implemented! + if (GNUNET_OK != + verify_refresh_melt_signature_forbidden (rmh, + json)) + { + GNUNET_break_op (0); + response_code = 0; + } break; case MHD_HTTP_UNAUTHORIZED: /* Nothing really to verify, mint says one of the signatures is @@ -534,6 +1347,48 @@ handle_refresh_melt_finished (void *cls, /** + * Convert a coin to be melted to the respective JSON encoding. + * + * @param melt_session_hash session hash to use + * @param mc coin to be melted + * @return JSON encoding of the melting request + */ +static json_t * +melted_coin_to_json (const struct GNUNET_HashCode *melt_session_hash, + const struct MeltedCoin *mc) +{ + struct TALER_CoinSpendSignatureP confirm_sig; + struct TALER_RefreshMeltCoinAffirmationPS melt; + + melt.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); + melt.purpose.size = htonl (sizeof (struct TALER_RefreshMeltCoinAffirmationPS)); + melt.session_hash = *melt_session_hash; + TALER_amount_hton (&melt.amount_with_fee, + &mc->melt_amount_with_fee); + TALER_amount_hton (&melt.melt_fee, + &mc->fee_melt); + GNUNET_CRYPTO_eddsa_key_get_public (&mc->coin_priv.eddsa_priv, + &melt.coin_pub.eddsa_pub); + GNUNET_CRYPTO_eddsa_sign (&mc->coin_priv.eddsa_priv, + &melt.purpose, + &confirm_sig.eddsa_signature); + return json_pack ("{s:o, s:o, s:o, s:o, s:o}", + "coin_pub", + TALER_json_from_data (&melt.coin_pub, + sizeof (melt.coin_pub)), + "denom_pub", + TALER_json_from_rsa_public_key (mc->pub_key.rsa_public_key), + "denom_sig", + TALER_json_from_rsa_signature (mc->sig.rsa_signature), + "confirm_sig", + TALER_json_from_data (&confirm_sig, + sizeof (confirm_sig)), + "value_with_fee", + TALER_json_from_amount (&mc->melt_amount_with_fee)); +} + + +/** * Submit a melt request to the mint and get the mint's * response. * @@ -561,10 +1416,19 @@ TALER_MINT_refresh_melt (struct TALER_MINT_Handle *mint, void *melt_cb_cls) { json_t *melt_obj; + json_t *new_denoms; + json_t *melt_coins; + json_t *coin_evs; + json_t *transfer_pubs; + json_t *secret_encs; + json_t *link_encs; + json_t *tmp; struct TALER_MINT_RefreshMeltHandle *rmh; CURL *eh; struct TALER_MINT_Context *ctx; struct MeltData *md; + unsigned int i; + unsigned int j; if (GNUNET_YES != MAH_handle_is_ready (mint)) @@ -580,12 +1444,145 @@ TALER_MINT_refresh_melt (struct TALER_MINT_Handle *mint, return NULL; } - /* FIXME: totally bogus request building here: */ - melt_obj = json_pack ("{s:o, s:O}", /* f/wire */ - "4", 42, - "6", 62); + /* build JSON request, each of the 6 arrays first */ + new_denoms = json_array (); + melt_coins = json_array (); + coin_evs = json_array (); + transfer_pubs = json_array (); + secret_encs = json_array (); + link_encs = json_array (); + for (i=0;i<md->num_melted_coins;i++) + { + const struct MeltedCoin *mc = &md->melted_coins[i]; + + /* now melt_coins */ + json_array_append (melt_coins, + melted_coin_to_json (&md->melt_session_hash, + mc)); + } + + /* now transfer_pubs */ + for (j=0;j<TALER_CNC_KAPPA;j++) + { + tmp = json_array (); + for (i=0;i<md->num_melted_coins;i++) + { + const struct MeltedCoin *mc = &md->melted_coins[i]; + struct TALER_TransferPublicKeyP transfer_pub; + + GNUNET_CRYPTO_ecdhe_key_get_public (&mc->transfer_priv[j].ecdhe_priv, + &transfer_pub.ecdhe_pub); + json_array_append (tmp, + TALER_json_from_data (&transfer_pub, + sizeof (transfer_pub))); + } + json_array_append (transfer_pubs, + tmp); + } + + /* now secret_encs */ + for (j=0;j<TALER_CNC_KAPPA;j++) + { + tmp = json_array (); + for (i=0;i<md->num_melted_coins;i++) + { + const struct MeltedCoin *mc = &md->melted_coins[i]; + struct TALER_EncryptedLinkSecretP els; + struct TALER_TransferSecretP trans_sec; + + TALER_link_derive_transfer_secret (&mc->coin_priv, + &mc->transfer_priv[j], + &trans_sec); + GNUNET_assert (GNUNET_OK == + TALER_transfer_encrypt (&md->link_secrets[j], + &trans_sec, + &els)); + json_array_append (tmp, + TALER_json_from_data (&els, + sizeof (els))); + } + json_array_append (secret_encs, + tmp); + } + + /* now new_denoms */ + for (i=0;i<md->num_fresh_coins;i++) + { + json_array_append (new_denoms, + TALER_json_from_rsa_public_key + (md->fresh_pks[i].rsa_public_key)); + } + + /* now link_encs */ + for (j=0;j<TALER_CNC_KAPPA;j++) + { + tmp = json_array (); + for (i=0;i<md->num_fresh_coins;i++) + { + const struct FreshCoin *fc = &md->fresh_coins[j][i]; + struct TALER_RefreshLinkDecrypted rld; + struct TALER_RefreshLinkEncrypted *rle; + char *buf; + size_t buf_len; + + rld.coin_priv = fc->coin_priv; + rld.blinding_key = fc->blinding_key; + rle = TALER_refresh_encrypt (&rld, + &md->link_secrets[j]); + GNUNET_assert (NULL != rle); + buf = TALER_refresh_link_encrypted_encode (rle, + &buf_len); + GNUNET_assert (NULL != buf); + json_array_append (tmp, + TALER_json_from_data (buf, + buf_len)); + GNUNET_free (buf); + GNUNET_free (rle); + } + json_array_append (link_encs, + tmp); + } + + /* now coin_evs */ + for (j=0;j<TALER_CNC_KAPPA;j++) + { + tmp = json_array (); + for (i=0;i<md->num_fresh_coins;i++) + { + const struct FreshCoin *fc = &md->fresh_coins[j][i]; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_HashCode coin_hash; + char *coin_ev; /* blinded message to be signed (in envelope) for each coin */ + size_t coin_ev_size; + + 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); + coin_ev_size = GNUNET_CRYPTO_rsa_blind (&coin_hash, + fc->blinding_key.rsa_blinding_key, + md->fresh_pks[i].rsa_public_key, + &coin_ev); + json_array_append (tmp, + TALER_json_from_data (coin_ev, + coin_ev_size)); + GNUNET_free (coin_ev); + } + json_array_append (coin_evs, + tmp); + } + /* finally, assemble main JSON request from constitutent arrays */ + melt_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}", + "new_denoms", new_denoms, + "melt_coins", melt_coins, + "coin_evs", coin_evs, + "transfer_pubs", transfer_pubs, + "secret_encs", secret_encs, + "link_encs", link_encs); + /* and now we can at last begin the actual request handling */ rmh = GNUNET_new (struct TALER_MINT_RefreshMeltHandle); rmh->mint = mint; rmh->melt_cb = melt_cb; @@ -701,10 +1698,108 @@ struct TALER_MINT_RefreshRevealHandle */ struct MeltData *md; + /** + * The index selected by the mint in cut-and-choose to not be revealed. + */ + uint16_t noreveal_index; + }; /** + * We got a 200 OK response for the /refresh/reveal operation. + * Extract the coin signatures and return them to the caller. + * The signatures we get from the mint 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 jsona reply from the mint + * @param[out] coin_privs array of length `num_fresh_coins`, initialized to contain private keys + * @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_MINT_RefreshRevealHandle *rrh, + json_t *jsona, + struct TALER_CoinSpendPrivateKeyP *coin_privs, + struct TALER_DenominationSignature *sigs) +{ + unsigned int i; + + if (! json_is_array (jsona)) + { + /* We expected an array of coins */ + GNUNET_break_op (0); + 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); + return GNUNET_SYSERR; + } + for (i=0;i<rrh->md->num_fresh_coins;i++) + { + const struct FreshCoin *fc; + struct TALER_DenominationPublicKey *pk; + json_t *json; + struct GNUNET_CRYPTO_rsa_Signature *blind_sig; + struct GNUNET_CRYPTO_rsa_Signature *sig; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct GNUNET_HashCode coin_hash; + + struct MAJ_Specification spec[] = { + MAJ_spec_rsa_signature ("ev_sig", &blind_sig), + MAJ_spec_end + }; + + fc = &rrh->md->fresh_coins[rrh->noreveal_index][i]; + pk = &rrh->md->fresh_pks[i]; + json = json_array_get (jsona, i); + GNUNET_assert (NULL != json); + + if (GNUNET_OK != + MAJ_parse_json (json, + spec)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* unblind the signature */ + sig = GNUNET_CRYPTO_rsa_unblind (blind_sig, + fc->blinding_key.rsa_blinding_key, + pk->rsa_public_key); + GNUNET_CRYPTO_rsa_signature_free (blind_sig); + + /* verify the signature */ + 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 != + GNUNET_CRYPTO_rsa_verify (&coin_hash, + sig, + pk->rsa_public_key)) + { + GNUNET_break_op (0); + GNUNET_CRYPTO_rsa_signature_free (sig); + return GNUNET_SYSERR; + } + coin_privs[i] = fc->coin_priv; + sigs[i].rsa_signature = sig; + } + return GNUNET_OK; +} + + +/** * Function called when we're done processing the * HTTP /refresh/reveal request. * @@ -728,8 +1823,35 @@ handle_refresh_reveal_finished (void *cls, case 0: break; case MHD_HTTP_OK: - GNUNET_break (0); // FIXME: NOT implemented! - // rrh->reveal_cb = NULL; (call with real result, do not call again below) + { + struct TALER_CoinSpendPrivateKeyP coin_privs[rrh->md->num_fresh_coins]; + struct TALER_DenominationSignature sigs[rrh->md->num_fresh_coins]; + unsigned int i; + int ret; + + memset (sigs, 0, sizeof (sigs)); + ret = refresh_reveal_ok (rrh, + json, + coin_privs, + sigs); + if (GNUNET_OK != ret) + { + response_code = 0; + } + else + { + rrh->reveal_cb (rrh->reveal_cb_cls, + MHD_HTTP_OK, + rrh->md->num_fresh_coins, + coin_privs, + sigs, + json); + rrh->reveal_cb = NULL; + } + for (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 mint is buggy @@ -763,7 +1885,6 @@ handle_refresh_reveal_finished (void *cls, } - /** * Submit a /refresh/reval request to the mint and get the mint's * response. @@ -795,10 +1916,14 @@ TALER_MINT_refresh_reveal (struct TALER_MINT_Handle *mint, void *reveal_cb_cls) { struct TALER_MINT_RefreshRevealHandle *rrh; + json_t *transfer_privs; json_t *reveal_obj; + json_t *tmp; CURL *eh; struct TALER_MINT_Context *ctx; struct MeltData *md; + unsigned int i; + unsigned int j; if (GNUNET_YES != MAH_handle_is_ready (mint)) @@ -813,14 +1938,51 @@ TALER_MINT_refresh_reveal (struct TALER_MINT_Handle *mint, GNUNET_break (0); return NULL; } + 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 mint replied, but maybe + we had some internal corruption that changed the value... */ + GNUNET_break (0); + return NULL; + } + + /* build array of transfer private keys */ + transfer_privs = json_array (); + for (i=0;i<md->num_melted_coins;i++) + { + const struct MeltedCoin *mc = &md->melted_coins[i]; + + tmp = json_array (); + for (j=0;j<TALER_CNC_KAPPA;j++) + { + if (j == noreveal_index) + { + /* This is crucial: exclude the transfer key for the + noreval index! */ + continue; + } + json_array_append (tmp, + TALER_json_from_data (&mc->transfer_priv[j], + sizeof (struct TALER_TransferPrivateKeyP))); + } + json_array_append (transfer_privs, + tmp); + } - /* FIXME: totally bogus request building here: */ - reveal_obj = json_pack ("{s:o, s:O}", /* f/wire */ - "4", 42, - "6", 62); + /* build main JSON request */ + reveal_obj = json_pack ("{s:o, s:o}", + "session_hash", + TALER_json_from_data (&md->melt_session_hash, + sizeof (struct GNUNET_HashCode)), + "transfer_privs", + transfer_privs); + /* finally, we can actually issue the request */ rrh = GNUNET_new (struct TALER_MINT_RefreshRevealHandle); rrh->mint = mint; + rrh->noreveal_index = noreveal_index; rrh->reveal_cb = reveal_cb; rrh->reveal_cb_cls = reveal_cb_cls; rrh->md = md; diff --git a/src/mint-lib/mint_api_refresh_link.c b/src/mint-lib/mint_api_refresh_link.c index 3ea6b23e4..f17949af0 100644 --- a/src/mint-lib/mint_api_refresh_link.c +++ b/src/mint-lib/mint_api_refresh_link.c @@ -48,11 +48,6 @@ struct TALER_MINT_RefreshLinkHandle char *url; /** - * JSON encoding of the request to POST. - */ - char *json_enc; - - /** * Handle for the request. */ struct MAC_Job *job; @@ -72,10 +67,191 @@ struct TALER_MINT_RefreshLinkHandle */ struct MAC_DownloadBuffer db; + /** + * Private key of the coin, required to decode link information. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + }; /** + * Parse the provided linkage data from the "200 OK" response + * for one of the coins. + * + * @param rlh refresh link handle + * @param json json reply with the data for one coin + * @param trans_pub our transfer public key + * @param secret_enc encrypted key to decrypt link data + * @param[out] coin_priv where to return private coin key + * @param[out] sig where to return private coin signature + * @param[out] pub where to return the public key for the coin + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +parse_refresh_link_coin (const struct TALER_MINT_RefreshLinkHandle *rlh, + json_t *json, + const struct TALER_TransferPublicKeyP *trans_pub, + const struct TALER_EncryptedLinkSecretP *secret_enc, + struct TALER_CoinSpendPrivateKeyP *coin_priv, + struct TALER_DenominationSignature *sig, + struct TALER_DenominationPublicKey *pub) +{ + void *link_enc; + size_t link_enc_size; + struct GNUNET_CRYPTO_rsa_Signature *bsig; + struct MAJ_Specification spec[] = { + MAJ_spec_varsize ("link_enc", &link_enc, &link_enc_size), + MAJ_spec_rsa_public_key ("denom_pub", &pub->rsa_public_key), + MAJ_spec_rsa_signature ("ev_sig", &bsig), + MAJ_spec_end + }; + struct TALER_RefreshLinkEncrypted *rle; + struct TALER_RefreshLinkDecrypted *rld; + struct TALER_LinkSecretP secret; + + /* parse reply */ + if (GNUNET_OK != + MAJ_parse_json (json, + spec)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + /* decode and decrypt link data */ + rle = TALER_refresh_link_encrypted_decode (link_enc, + link_enc_size); + if (NULL == rle) + { + GNUNET_break_op (0); + MAJ_parse_free (spec); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_link_decrypt_secret2 (secret_enc, + trans_pub, + &rlh->coin_priv, + &secret)) + { + GNUNET_break_op (0); + MAJ_parse_free (spec); + return GNUNET_SYSERR; + } + rld = TALER_refresh_decrypt (rle, + &secret); + if (NULL == rld) + { + GNUNET_break_op (0); + MAJ_parse_free (spec); + return GNUNET_SYSERR; + } + + /* extract coin and signature */ + *coin_priv = rld->coin_priv; + sig->rsa_signature + = GNUNET_CRYPTO_rsa_unblind (bsig, + rld->blinding_key.rsa_blinding_key, + pub->rsa_public_key); + + /* clean up */ + GNUNET_free (rld); + MAJ_parse_free (spec); + return GNUNET_OK; +} + + +/** + * 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 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_MINT_RefreshLinkHandle *rlh, + json_t *json) +{ + json_t *jsona; + struct TALER_TransferPublicKeyP trans_pub; + struct TALER_EncryptedLinkSecretP secret_enc; + struct MAJ_Specification spec[] = { + MAJ_spec_json ("new_coins", &jsona), + MAJ_spec_fixed_auto ("trans_pub", &trans_pub), + MAJ_spec_fixed_auto ("secret_enc", &secret_enc), + MAJ_spec_end + }; + unsigned int num_coins; + int ret; + + if (GNUNET_OK != + MAJ_parse_json (json, + spec)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (! json_is_array (jsona)) + { + GNUNET_break_op (0); + MAJ_parse_free (spec); + return GNUNET_SYSERR; + } + + /* decode all coins */ + num_coins = json_array_size (json); + { + unsigned int i; + struct TALER_CoinSpendPrivateKeyP coin_privs[num_coins]; + struct TALER_DenominationSignature sigs[num_coins]; + struct TALER_DenominationPublicKey pubs[num_coins]; + + for (i=0;i<num_coins;i++) + { + if (GNUNET_OK != + parse_refresh_link_coin (rlh, + json_array_get (json, i), + &trans_pub, + &secret_enc, + &coin_privs[i], + &sigs[i], + &pubs[i])) + { + GNUNET_break_op (0); + break; + } + } + + /* check if we really got all, then invoke callback */ + if (i != num_coins) + { + GNUNET_break_op (0); + ret = GNUNET_SYSERR; + } + else + { + rlh->link_cb (rlh->link_cb_cls, + MHD_HTTP_OK, + num_coins, + coin_privs, + sigs, + pubs, + json); + rlh->link_cb = NULL; + ret = GNUNET_OK; + } + + /* clean up */ + for (i=0;i<num_coins;i++) + if (NULL != sigs[i].rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (sigs[i].rsa_signature); + } + return ret; +} + + +/** * Function called when we're done processing the * HTTP /refresh/link request. * @@ -99,8 +275,13 @@ handle_refresh_link_finished (void *cls, case 0: break; case MHD_HTTP_OK: - GNUNET_break (0); // FIXME: NOT implemented! - // rh->link_cb = NULL; (call with real result, do not call again below) + if (GNUNET_OK != + parse_refresh_link_ok (rlh, + json)) + { + GNUNET_break_op (0); + response_code = 0; + } break; case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the mint is buggy @@ -126,7 +307,7 @@ handle_refresh_link_finished (void *cls, if (NULL != rlh->link_cb) rlh->link_cb (rlh->link_cb_cls, response_code, - 0, NULL, NULL, + 0, NULL, NULL, NULL, json); json_decref (json); TALER_MINT_refresh_link_cancel (rlh); @@ -153,10 +334,12 @@ TALER_MINT_refresh_link (struct TALER_MINT_Handle *mint, TALER_MINT_RefreshLinkCallback link_cb, void *link_cb_cls) { - json_t *link_obj; struct TALER_MINT_RefreshLinkHandle *rlh; CURL *eh; struct TALER_MINT_Context *ctx; + struct TALER_CoinSpendPublicKeyP coin_pub; + char *pub_str; + char *arg_str; if (GNUNET_YES != MAH_handle_is_ready (mint)) @@ -164,38 +347,31 @@ TALER_MINT_refresh_link (struct TALER_MINT_Handle *mint, GNUNET_break (0); return NULL; } - /* FIXME: totally bogus request building here: */ - link_obj = json_pack ("{s:o, s:O}", /* f/wire */ - "4", 42, - "6", 62); + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, + &coin_pub.eddsa_pub); + pub_str = GNUNET_STRINGS_data_to_string_alloc (&coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP)); + GNUNET_asprintf (&arg_str, + "/refresh/link?coin_pub=%s", + pub_str); + GNUNET_free (pub_str); rlh = GNUNET_new (struct TALER_MINT_RefreshLinkHandle); rlh->mint = mint; rlh->link_cb = link_cb; rlh->link_cb_cls = link_cb_cls; - - rlh->url = MAH_path_to_url (mint, "/refresh/link"); + rlh->coin_priv = *coin_priv; + rlh->url = MAH_path_to_url (mint, arg_str); + GNUNET_free (arg_str); eh = curl_easy_init (); - GNUNET_assert (NULL != (rlh->json_enc = - json_dumps (link_obj, - JSON_COMPACT))); - json_decref (link_obj); GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_URL, rlh->url)); GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, - CURLOPT_POSTFIELDS, - rlh->json_enc)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_POSTFIELDSIZE, - strlen (rlh->json_enc))); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, CURLOPT_WRITEFUNCTION, &MAC_download_cb)); GNUNET_assert (CURLE_OK == @@ -228,7 +404,6 @@ TALER_MINT_refresh_link_cancel (struct TALER_MINT_RefreshLinkHandle *rlh) } GNUNET_free_non_null (rlh->db.buf); GNUNET_free (rlh->url); - GNUNET_free (rlh->json_enc); GNUNET_free (rlh); } diff --git a/src/mint-lib/mint_api_withdraw.c b/src/mint-lib/mint_api_withdraw.c index e7a1a61d4..ddabb8116 100644 --- a/src/mint-lib/mint_api_withdraw.c +++ b/src/mint-lib/mint_api_withdraw.c @@ -287,6 +287,7 @@ handle_withdraw_status_finished (void *cls, break; case MHD_HTTP_OK: { + /* TODO: move into separate function... */ json_t *history; unsigned int len; struct TALER_Amount balance; diff --git a/src/mint-lib/test-mint-home/config/mint-keyup.conf b/src/mint-lib/test-mint-home/config/mint-keyup.conf index d8bbc9d20..8ad1f3bb2 100644 --- a/src/mint-lib/test-mint-home/config/mint-keyup.conf +++ b/src/mint-lib/test-mint-home/config/mint-keyup.conf @@ -19,6 +19,17 @@ lookahead_provide = 4 weeks 1 day # name begins with "coin_". The rest of the # name is free, but of course following the convention # of "coin_$CURRENCY[_$SUBUNIT]_$VALUE" make sense. +[coin_eur_ct_1] +value = EUR:0.01 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.00 +fee_deposit = EUR:0.00 +fee_refresh = EUR:0.01 +rsa_keysize = 1024 + [coin_eur_ct_10] value = EUR:0.10 duration_overlap = 5 minutes @@ -27,7 +38,18 @@ duration_spend = 2 years duration_legal = 3 years fee_withdraw = EUR:0.01 fee_deposit = EUR:0.01 -fee_refresh = EUR:0.01 +fee_refresh = EUR:0.03 +rsa_keysize = 1024 + +[coin_eur_1] +value = EUR:1 +duration_overlap = 5 minutes +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 rsa_keysize = 1024 [coin_eur_5] @@ -38,7 +60,7 @@ duration_spend = 2 years duration_legal = 3 years fee_withdraw = EUR:0.01 fee_deposit = EUR:0.01 -fee_refresh = EUR:0.01 +fee_refresh = EUR:0.03 rsa_keysize = 1024 [coin_eur_10] @@ -49,7 +71,7 @@ duration_spend = 2 years duration_legal = 3 years fee_withdraw = EUR:0.01 fee_deposit = EUR:0.01 -fee_refresh = EUR:0.01 +fee_refresh = EUR:0.03 rsa_keysize = 1024 [coin_eur_1000] @@ -60,5 +82,5 @@ duration_spend = 2 years duration_legal = 3 years fee_withdraw = EUR:0.01 fee_deposit = EUR:0.01 -fee_refresh = EUR:0.01 +fee_refresh = EUR:0.03 rsa_keysize = 2048 diff --git a/src/mint-lib/test_mint_api.c b/src/mint-lib/test_mint_api.c index 29ccd1e5d..4b1b0f228 100644 --- a/src/mint-lib/test_mint_api.c +++ b/src/mint-lib/test_mint_api.c @@ -81,7 +81,72 @@ enum OpCode /** * Deposit a coin (pay with it). */ - OC_DEPOSIT + OC_DEPOSIT, + + /** + * Melt a (set of) coins. + */ + OC_REFRESH_MELT, + + /** + * Complete melting session by withdrawing melted coins. + */ + OC_REFRESH_REVEAL, + + /** + * Verify mint's /refresh/link by linking original private key to + * results from #OC_REFRESH_REVEAL step. + */ + OC_REFRESH_LINK + +}; + + +/** + * Structure specifying details about a coin to be melted. + * Used in a NULL-terminated array as part of command + * specification. + */ +struct MeltDetails +{ + + /** + * Amount to melt (including fee). + */ + const char *amount; + + /** + * Reference to withdraw_sign operations for coin to + * be used for the /refresh/melt operation. + */ + const char *coin_ref; + +}; + + +/** + * Information about a fresh coin generated by the refresh operation. + */ +struct FreshCoin +{ + + /** + * If @e amount is NULL, this specifies the denomination key to + * use. Otherwise, this will be set (by the interpreter) to the + * denomination PK matching @e amount. + */ + const struct TALER_MINT_DenomPublicKey *pk; + + /** + * Set (by the interpreter) to the mint's signature over the + * coin's public key. + */ + struct TALER_DenominationSignature sig; + + /** + * Set (by the interpreter) to the coin's private key. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; }; @@ -112,6 +177,9 @@ struct Command union { + /** + * Information for a #OC_ADMIN_ADD_INCOMING command. + */ struct { @@ -145,6 +213,9 @@ struct Command } admin_add_incoming; + /** + * Information for a #OC_WITHDRAW_STATUS command. + */ struct { @@ -166,8 +237,12 @@ struct Command } withdraw_status; + /** + * Information for a #OC_WITHDRAW_SIGN command. + */ struct { + /** * Which reserve should we withdraw from? */ @@ -210,6 +285,9 @@ struct Command } withdraw_sign; + /** + * Information for a #OC_DEPOSIT command. + */ struct { @@ -225,6 +303,12 @@ struct Command const char *coin_ref; /** + * If this @e coin_ref refers to an operation that generated + * an array of coins, this value determines which coin to use. + */ + unsigned int coin_idx; + + /** * JSON string describing the merchant's "wire details". */ const char *wire_details; @@ -258,6 +342,103 @@ struct Command } deposit; + /** + * Information for a #OC_REFRESH_MELT command. + */ + struct + { + + /** + * Information about coins to be melted. + */ + struct MeltDetails *melted_coins; + + /** + * Denominations of the fresh coins to withdraw. + */ + const char **fresh_amounts; + + /** + * Array of the public keys corresponding to + * the @e fresh_amounts, set by the interpreter. + */ + const struct TALER_MINT_DenomPublicKey **fresh_pks; + + /** + * Melt handle while operation is running. + */ + struct TALER_MINT_RefreshMeltHandle *rmh; + + /** + * Data used in the refresh operation, set by the interpreter. + */ + char *refresh_data; + + /** + * Number of bytes in @e refresh_data, set by the interpreter. + */ + size_t refresh_data_length; + + /** + * Set by the interpreter (upon completion) to the noreveal + * index selected by the mint. + */ + uint16_t noreveal_index; + + } refresh_melt; + + /** + * Information for a #OC_REFRESH_REVEAL command. + */ + struct + { + + /** + * Melt operation this is the matching reveal for. + */ + const char *melt_ref; + + /** + * Reveal handle while operation is running. + */ + struct TALER_MINT_RefreshRevealHandle *rrh; + + /** + * Number of fresh coins withdrawn, set by the interpreter. + * Length of the @e fresh_coins array. + */ + unsigned int num_fresh_coins; + + /** + * Information about coins withdrawn, set by the interpreter. + */ + struct FreshCoin *fresh_coins; + + } refresh_reveal; + + /** + * Information for a #OC_REFRESH_LINK command. + */ + struct + { + + /** + * Reveal operation this is the matching link for. + */ + const char *reveal_ref; + + /** + * Link handle while operation is running. + */ + struct TALER_MINT_RefreshLinkHandle *rlh; + + /** + * Which of the melted coins should be used for the linkage? + */ + unsigned int coin_idx; + + } refresh_link; + } details; }; @@ -671,7 +852,182 @@ deposit_cb (void *cls, is->ip++; is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, is); +} + + +/** + * Function called with the result of the /refresh/melt operation. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, never #MHD_HTTP_OK (200) as for successful intermediate response this callback is skipped. + * 0 if the mint's reply is bogus (fails to follow the protocol) + * @param noreveal_index choice by the mint in the cut-and-choose protocol, + * UINT16_MAX on error + * @param full_response full response from the mint (for logging, in case of errors) + */ +static void +melt_cb (void *cls, + unsigned int http_status, + uint16_t noreveal_index, + json_t *full_response) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + + cmd->details.refresh_melt.rmh = NULL; + if (cmd->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s\n", + http_status, + cmd->label); + fail (is); + return; + } + cmd->details.refresh_melt.noreveal_index = noreveal_index; + is->ip++; + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + +/** + * Function called with the result of the /refresh/reveal operation. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + * 0 if the mint's reply is bogus (fails to follow the protocol) + * @param num_coins number of fresh coins created, length of the @a sigs and @a coin_privs arrays, 0 if the operation failed + * @param coin_privs array of @a num_coins private keys for the coins that were created, NULL on error + * @param sigs array of signature over @a num_coins coins, NULL on error + * @param full_response full response from the mint (for logging, in case of errors) + */ +static void +reveal_cb (void *cls, + unsigned int http_status, + unsigned int num_coins, + const struct TALER_CoinSpendPrivateKeyP *coin_privs, + const struct TALER_DenominationSignature *sigs, + json_t *full_response) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + const struct Command *ref; + unsigned int i; + + cmd->details.refresh_reveal.rrh = NULL; + if (cmd->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s\n", + http_status, + cmd->label); + fail (is); + return; + } + ref = find_command (is, + cmd->details.refresh_reveal.melt_ref); + cmd->details.refresh_reveal.num_fresh_coins = num_coins; + switch (http_status) + { + case MHD_HTTP_OK: + cmd->details.refresh_reveal.fresh_coins + = GNUNET_new_array (num_coins, + struct FreshCoin); + for (i=0;i<num_coins;i++) + { + struct FreshCoin *fc = &cmd->details.refresh_reveal.fresh_coins[i]; + + fc->pk = ref->details.refresh_melt.fresh_pks[i]; + fc->coin_priv = coin_privs[i]; + fc->sig.rsa_signature + = GNUNET_CRYPTO_rsa_signature_dup (sigs[i].rsa_signature); + } + break; + default: + break; + } + + is->ip++; + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + + +/** + * Function called with the result of a /refresh/link operation. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + * 0 if the mint's reply is bogus (fails to follow the protocol) + * @param num_coins number of fresh coins created, length of the @a sigs and @a coin_privs arrays, 0 if the operation failed + * @param coin_privs array of @a num_coins private keys for the coins that were created, NULL on error + * @param sigs array of signature over @a num_coins coins, NULL on error + * @param pubs array of public keys for the @a sigs, NULL on error + * @param full_response full response from the mint (for logging, in case of errors) + */ +static void +link_cb (void *cls, + unsigned int http_status, + unsigned int num_coins, + const struct TALER_CoinSpendPrivateKeyP *coin_privs, + const struct TALER_DenominationSignature *sigs, + const struct TALER_DenominationPublicKey *pubs, + json_t *full_response) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + const struct Command *ref; + unsigned int i; + + cmd->details.refresh_link.rlh = NULL; + if (cmd->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s\n", + http_status, + cmd->label); + fail (is); + return; + } + ref = find_command (is, + cmd->details.refresh_link.reveal_ref); + switch (http_status) + { + case MHD_HTTP_OK: + /* check that number of coins returned matches */ + if (num_coins != ref->details.refresh_reveal.num_fresh_coins) + { + GNUNET_break (0); + fail (is); + return; + } + /* check that the coins match */ + for (i=0;i<num_coins;i++) + { + const struct FreshCoin *fc; + + fc = &ref->details.refresh_reveal.fresh_coins[i]; + if ( (0 != memcmp (&coin_privs[i], + &fc->coin_priv, + sizeof (struct TALER_CoinSpendPrivateKeyP))) || + (0 != GNUNET_CRYPTO_rsa_signature_cmp (fc->sig.rsa_signature, + sigs[i].rsa_signature)) || + (0 != GNUNET_CRYPTO_rsa_public_key_cmp (fc->pk->key.rsa_public_key, + pubs[i].rsa_public_key)) ) + { + GNUNET_break (0); + fail (is); + return; + } + } + break; + default: + break; + } + is->ip++; + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); } @@ -904,6 +1260,9 @@ interpreter_run (void *cls, case OC_DEPOSIT: { struct GNUNET_HashCode h_contract; + const struct TALER_CoinSpendPrivateKeyP *coin_priv; + const struct TALER_MINT_DenomPublicKey *coin_pk; + const struct TALER_DenominationSignature *coin_pk_sig; struct TALER_CoinSpendPublicKeyP coin_pub; struct TALER_CoinSpendSignatureP coin_sig; struct GNUNET_TIME_Absolute refund_deadline; @@ -916,7 +1275,30 @@ interpreter_run (void *cls, ref = find_command (is, cmd->details.deposit.coin_ref); GNUNET_assert (NULL != ref); - GNUNET_assert (OC_WITHDRAW_SIGN == ref->oc); + switch (ref->oc) + { + case OC_WITHDRAW_SIGN: + coin_priv = &ref->details.withdraw_sign.coin_priv; + coin_pk = ref->details.withdraw_sign.pk; + coin_pk_sig = &ref->details.withdraw_sign.sig; + break; + case OC_REFRESH_REVEAL: + { + const struct FreshCoin *fc; + unsigned int idx; + + idx = cmd->details.deposit.coin_idx; + GNUNET_assert (idx < ref->details.refresh_reveal.num_fresh_coins); + fc = &ref->details.refresh_reveal.fresh_coins[idx]; + + coin_priv = &fc->coin_priv; + coin_pk = fc->pk; + coin_pk_sig = &fc->sig; + } + break; + default: + GNUNET_assert (0); + } if (GNUNET_OK != TALER_string_to_amount (cmd->details.deposit.amount, &amount)) @@ -943,7 +1325,8 @@ interpreter_run (void *cls, fail (is); return; } - GNUNET_CRYPTO_eddsa_key_get_public (&ref->details.withdraw_sign.coin_priv.eddsa_priv, + + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, &coin_pub.eddsa_pub); if (0 != cmd->details.deposit.refund_deadline.rel_value_us) @@ -975,11 +1358,11 @@ interpreter_run (void *cls, TALER_amount_hton (&dr.amount_with_fee, &amount); TALER_amount_hton (&dr.deposit_fee, - &ref->details.withdraw_sign.pk->fee_deposit); + &coin_pk->fee_deposit); dr.merchant = merchant_pub; dr.coin_pub = coin_pub; GNUNET_assert (GNUNET_OK == - GNUNET_CRYPTO_eddsa_sign (&ref->details.withdraw_sign.coin_priv.eddsa_priv, + GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv, &dr.purpose, &coin_sig.eddsa_signature)); @@ -990,8 +1373,8 @@ interpreter_run (void *cls, wire, &h_contract, &coin_pub, - &ref->details.withdraw_sign.sig, - &ref->details.withdraw_sign.pk->key, + coin_pk_sig, + &coin_pk->key, timestamp, cmd->details.deposit.transaction_id, &merchant_pub, @@ -1009,6 +1392,157 @@ interpreter_run (void *cls, trigger_context_task (); return; } + case OC_REFRESH_MELT: + { + unsigned int num_melted_coins; + unsigned int num_fresh_coins; + + cmd->details.refresh_melt.noreveal_index = UINT16_MAX; + for (num_melted_coins=0; + NULL != cmd->details.refresh_melt.melted_coins[num_melted_coins].amount; + num_melted_coins++) ; + for (num_fresh_coins=0; + NULL != cmd->details.refresh_melt.fresh_amounts[num_fresh_coins]; + num_fresh_coins++) ; + + cmd->details.refresh_melt.fresh_pks + = GNUNET_new_array (num_fresh_coins, + const struct TALER_MINT_DenomPublicKey *); + { + struct TALER_CoinSpendPrivateKeyP melt_privs[num_melted_coins]; + struct TALER_Amount melt_amounts[num_melted_coins]; + struct TALER_DenominationSignature melt_sigs[num_melted_coins]; + struct TALER_MINT_DenomPublicKey melt_pks[num_melted_coins]; + struct TALER_MINT_DenomPublicKey fresh_pks[num_fresh_coins]; + unsigned int i; + + for (i=0;i<num_melted_coins;i++) + { + const struct MeltDetails *md = &cmd->details.refresh_melt.melted_coins[i]; + ref = find_command (is, + md->coin_ref); + GNUNET_assert (NULL != ref); + GNUNET_assert (OC_WITHDRAW_SIGN == ref->oc); + + melt_privs[i] = ref->details.withdraw_sign.coin_priv; + if (GNUNET_OK != + TALER_string_to_amount (md->amount, + &melt_amounts[i])) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %u\n", + md->amount, + is->ip); + fail (is); + return; + } + melt_sigs[i] = ref->details.withdraw_sign.sig; + melt_pks[i] = *ref->details.withdraw_sign.pk; + } + for (i=0;i<num_fresh_coins;i++) + { + if (GNUNET_OK != + TALER_string_to_amount (cmd->details.refresh_melt.fresh_amounts[i], + &amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %u\n", + cmd->details.withdraw_sign.amount, + is->ip); + fail (is); + return; + } + cmd->details.refresh_melt.fresh_pks[i] + = find_pk (is->keys, + &amount); + fresh_pks[i] = *cmd->details.refresh_melt.fresh_pks[i]; + } + cmd->details.refresh_melt.refresh_data + = TALER_MINT_refresh_prepare (num_melted_coins, + melt_privs, + melt_amounts, + melt_sigs, + melt_pks, + GNUNET_YES, + num_fresh_coins, + fresh_pks, + &cmd->details.refresh_melt.refresh_data_length); + if (NULL == cmd->details.refresh_melt.refresh_data) + { + GNUNET_break (0); + fail (is); + return; + } + cmd->details.refresh_melt.rmh + = TALER_MINT_refresh_melt (mint, + cmd->details.refresh_melt.refresh_data_length, + cmd->details.refresh_melt.refresh_data, + &melt_cb, + is); + if (NULL == cmd->details.refresh_melt.rmh) + { + GNUNET_break (0); + fail (is); + return; + } + } + } + trigger_context_task (); + return; + case OC_REFRESH_REVEAL: + ref = find_command (is, + cmd->details.refresh_reveal.melt_ref); + cmd->details.refresh_reveal.rrh + = TALER_MINT_refresh_reveal (mint, + ref->details.refresh_melt.refresh_data_length, + ref->details.refresh_melt.refresh_data, + ref->details.refresh_melt.noreveal_index, + &reveal_cb, + is); + if (NULL == cmd->details.refresh_reveal.rrh) + { + GNUNET_break (0); + fail (is); + return; + } + trigger_context_task (); + return; + case OC_REFRESH_LINK: + /* find reveal command */ + ref = find_command (is, + cmd->details.refresh_link.reveal_ref); + /* find melt command */ + ref = find_command (is, + ref->details.refresh_reveal.melt_ref); + /* find withdraw_sign command */ + { + unsigned int idx; + const struct MeltDetails *md; + unsigned int num_melted_coins; + + for (num_melted_coins=0; + NULL != ref->details.refresh_melt.melted_coins[num_melted_coins].amount; + num_melted_coins++) ; + idx = cmd->details.refresh_link.coin_idx; + GNUNET_assert (idx < num_melted_coins); + md = &ref->details.refresh_melt.melted_coins[idx]; + ref = find_command (is, + md->coin_ref); + } + /* finally, use private key from withdraw sign command */ + cmd->details.refresh_link.rlh + = TALER_MINT_refresh_link (mint, + &ref->details.withdraw_sign.coin_priv, + &link_cb, + is); + if (NULL == cmd->details.refresh_link.rlh) + { + GNUNET_break (0); + fail (is); + return; + } + trigger_context_task (); + return; default: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unknown instruction %d at %u (%s)\n", @@ -1100,6 +1634,55 @@ do_shutdown (void *cls, cmd->details.deposit.dh = NULL; } break; + case OC_REFRESH_MELT: + if (NULL != cmd->details.refresh_melt.rmh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + i, + cmd->label); + TALER_MINT_refresh_melt_cancel (cmd->details.refresh_melt.rmh); + cmd->details.refresh_melt.rmh = NULL; + } + GNUNET_free_non_null (cmd->details.refresh_melt.fresh_pks); + cmd->details.refresh_melt.fresh_pks = NULL; + GNUNET_free_non_null (cmd->details.refresh_melt.refresh_data); + cmd->details.refresh_melt.refresh_data = NULL; + cmd->details.refresh_melt.refresh_data_length = 0; + break; + case OC_REFRESH_REVEAL: + if (NULL != cmd->details.refresh_reveal.rrh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + i, + cmd->label); + TALER_MINT_refresh_reveal_cancel (cmd->details.refresh_reveal.rrh); + cmd->details.refresh_reveal.rrh = NULL; + } + { + unsigned int j; + struct FreshCoin *fresh_coins; + + fresh_coins = cmd->details.refresh_reveal.fresh_coins; + for (j=0;j<cmd->details.refresh_reveal.num_fresh_coins;j++) + GNUNET_CRYPTO_rsa_signature_free (fresh_coins[j].sig.rsa_signature); + } + GNUNET_free_non_null (cmd->details.refresh_reveal.fresh_coins); + cmd->details.refresh_reveal.fresh_coins = NULL; + cmd->details.refresh_reveal.num_fresh_coins = 0; + break; + case OC_REFRESH_LINK: + if (NULL != cmd->details.refresh_link.rlh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + i, + cmd->label); + TALER_MINT_refresh_link_cancel (cmd->details.refresh_link.rlh); + cmd->details.refresh_link.rlh = NULL; + } + break; default: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unknown instruction %d at %u (%s)\n", @@ -1236,6 +1819,34 @@ run (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct InterpreterState *is; + static struct MeltDetails melt_coins_1[] = { + { .amount = "EUR:4", + .coin_ref = "refresh-withdraw-coin-1" }, + { NULL, NULL } + }; + static const char *melt_fresh_amounts_1[] = { + "EUR:1", + "EUR:1", + "EUR:1", + "EUR:0.1", + "EUR:0.1", + "EUR:0.1", + "EUR:0.1", + "EUR:0.1", + "EUR:0.1", + "EUR:0.1", + "EUR:0.1", + "EUR:0.01", + "EUR:0.01", + "EUR:0.01", + "EUR:0.01", + "EUR:0.01", + "EUR:0.01", + /* with 0.01 withdraw fees (except for 1ct coins), + this totals up to exactly EUR:3.97, and with + the 0.03 refresh fee, to EUR:4.0*/ + NULL + }; static struct Command commands[] = { /* Fill reserve with EUR:5.01, as withdraw fee is 1 ct per config */ @@ -1273,6 +1884,7 @@ run (void *cls, .expected_response_code = MHD_HTTP_PAYMENT_REQUIRED, .details.withdraw_sign.reserve_reference = "create-reserve-1", .details.withdraw_sign.amount = "EUR:5" }, + /* Try to double-spend the 5 EUR coin with different wire details */ { .oc = OC_DEPOSIT, .label = "deposit-double-1", @@ -1303,6 +1915,87 @@ run (void *cls, .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":2 } }", .details.deposit.transaction_id = 1 }, + /* ***************** /refresh testing ******************** */ + + /* Fill reserve with EUR:5.01, as withdraw fee is 1 ct */ + { .oc = OC_ADMIN_ADD_INCOMING, + .label = "refresh-create-reserve-1", + .expected_response_code = MHD_HTTP_OK, + .details.admin_add_incoming.wire = "{ \"type\":\"TEST\", \"bank\":\"source bank\", \"account\":424 }", + .details.admin_add_incoming.amount = "EUR:5.01" }, + /* Withdraw a 5 EUR coin, at fee of 1 ct */ + { .oc = OC_WITHDRAW_SIGN, + .label = "refresh-withdraw-coin-1", + .expected_response_code = MHD_HTTP_OK, + .details.withdraw_sign.reserve_reference = "refresh-create-reserve-1", + .details.withdraw_sign.amount = "EUR:5" }, + /* Try to partially spend (deposit) 1 EUR of the 5 EUR coin (in full) + (merchant would receive EUR:0.99 due to 1 ct deposit fee) */ + { .oc = OC_DEPOSIT, + .label = "refresh-deposit-partial", + .expected_response_code = MHD_HTTP_OK, + .details.deposit.amount = "EUR:1", + .details.deposit.coin_ref = "refresh-withdraw-coin-1", + .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", + .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\"EUR:1 } }", + .details.deposit.transaction_id = 42421 }, + + /* Melt the rest of the coin's value (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */ + + { .oc = OC_REFRESH_MELT, + .label = "refresh-melt-1", + .expected_response_code = MHD_HTTP_OK, + .details.refresh_melt.melted_coins = melt_coins_1, + .details.refresh_melt.fresh_amounts = melt_fresh_amounts_1 }, + +#if TEST_REFRESH + + /* Complete (successful) melt operation, and withdraw the coins */ + { .oc = OC_REFRESH_REVEAL, + .label = "refresh-reveal-1", + .expected_response_code = MHD_HTTP_OK, + .details.refresh_reveal.melt_ref = "refresh-melt-1" }, + + + /* Test that /refresh/link works */ + { .oc = OC_REFRESH_LINK, + .label = "refresh-link-1", + .expected_response_code = MHD_HTTP_OK, + .details.refresh_link.reveal_ref = "refresh-reveal-1" }, + + /* Test successfully spending coins from the refresh operation: + first EUR:1 */ + { .oc = OC_DEPOSIT, + .label = "refresh-deposit-refreshed-1", + .expected_response_code = MHD_HTTP_OK, + .details.deposit.amount = "EUR:1", + .details.deposit.coin_ref = "refresh-reveal-1a", + .details.deposit.coin_idx = 0, + .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", + .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":3 } }", + .details.deposit.transaction_id = 2 }, + /* Test successfully spending coins from the refresh operation: + finally EUR:0.1 */ + { .oc = OC_DEPOSIT, + .label = "refresh-deposit-refreshed-1b", + .expected_response_code = MHD_HTTP_OK, + .details.deposit.amount = "EUR:0.1", + .details.deposit.coin_ref = "refresh-reveal-1b", + .details.deposit.coin_idx = 4, + .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", + .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":3 } }", + .details.deposit.transaction_id = 2 }, + + /* Test running a failing melt operation (same operation again must fail) */ + { .oc = OC_REFRESH_MELT, + .label = "refresh-melt-failing", + .expected_response_code = MHD_HTTP_FORBIDDEN, + .details.refresh_melt.melted_coins = melt_coins_1, + .details.refresh_melt.fresh_amounts = melt_fresh_amounts_1 }, + + /* *************** end of /refresh testing ************** */ +#endif + { .oc = OC_END } }; @@ -1359,7 +2052,14 @@ main (int argc, "-d", "test-mint-home", NULL); /* give child time to start and bind against the socket */ - sleep (2); + fprintf (stderr, "Waiting for taler-mint-httpd to be ready"); + do + { + fprintf (stderr, "."); + sleep (1); + } + while (0 != system ("wget -q -t 1 http://localhost:8081/agpl -o /dev/null -O /dev/null")); + fprintf (stderr, "\n"); result = GNUNET_SYSERR; GNUNET_SCHEDULER_run (&run, NULL); GNUNET_OS_process_kill (mintd, diff --git a/src/mint/taler-mint-httpd_db.c b/src/mint/taler-mint-httpd_db.c index 4e91e7e76..2a4c16748 100644 --- a/src/mint/taler-mint-httpd_db.c +++ b/src/mint/taler-mint-httpd_db.c @@ -550,7 +550,9 @@ refresh_accept_melts (struct MHD_Connection *connection, GNUNET_break (0); TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, tl); - return TMH_RESPONSE_reply_internal_db_error (connection); + return (MHD_YES == + TMH_RESPONSE_reply_internal_db_error (connection)) + ? GNUNET_NO : GNUNET_SYSERR; } /* Refuse to refresh when the coin's value is insufficient for the cost of all transactions. */ @@ -580,6 +582,7 @@ refresh_accept_melts (struct MHD_Connection *connection, melt.coin_sig = coin_details->melt_sig; melt.session_hash = *session_hash; melt.amount_with_fee = coin_details->melt_amount_with_fee; + melt.melt_fee = coin_details->melt_fee; if (GNUNET_OK != TMH_plugin->insert_refresh_melt (TMH_plugin->cls, session, @@ -587,7 +590,9 @@ refresh_accept_melts (struct MHD_Connection *connection, &melt)) { GNUNET_break (0); - return GNUNET_SYSERR; + return (MHD_YES == + TMH_RESPONSE_reply_internal_db_error (connection)) + ? GNUNET_NO : GNUNET_SYSERR; } return GNUNET_OK; } @@ -623,7 +628,7 @@ TMH_DB_execute_refresh_melt (struct MHD_Connection *connection, unsigned int coin_count, const struct TMH_DB_MeltDetails *coin_melt_details, struct TALER_MINTDB_RefreshCommitCoin *const* commit_coin, - struct TALER_MINTDB_RefreshCommitLinkP *const* commit_link) + struct TALER_RefreshCommitLinkP *const* commit_link) { struct TMH_KS_StateHandle *key_state; struct TALER_MINTDB_RefreshSession refresh_session; @@ -839,11 +844,11 @@ check_commitment (struct MHD_Connection *connection, unsigned int j; struct TALER_LinkSecretP last_shared_secret; int secret_initialized = GNUNET_NO; - struct TALER_MINTDB_RefreshCommitLinkP *commit_links; + struct TALER_RefreshCommitLinkP *commit_links; struct TALER_MINTDB_RefreshCommitCoin *commit_coins; commit_links = GNUNET_malloc (num_oldcoins * - sizeof (struct TALER_MINTDB_RefreshCommitLinkP)); + sizeof (struct TALER_RefreshCommitLinkP)); if (GNUNET_OK != TMH_plugin->get_refresh_commit_links (TMH_plugin->cls, session, diff --git a/src/mint/taler-mint-httpd_db.h b/src/mint/taler-mint-httpd_db.h index 8a171153a..599762179 100644 --- a/src/mint/taler-mint-httpd_db.h +++ b/src/mint/taler-mint-httpd_db.h @@ -99,6 +99,12 @@ struct TMH_DB_MeltDetails * to the melt is this value minus the fee for melting the coin. */ struct TALER_Amount melt_amount_with_fee; + + /** + * What fee is earned by the mint? Set delayed during + * #verify_coin_public_info(). + */ + struct TALER_Amount melt_fee; }; @@ -130,7 +136,7 @@ TMH_DB_execute_refresh_melt (struct MHD_Connection *connection, unsigned int coin_count, const struct TMH_DB_MeltDetails *coin_melt_details, struct TALER_MINTDB_RefreshCommitCoin *const* commit_coin, - struct TALER_MINTDB_RefreshCommitLinkP *const* commit_link); + struct TALER_RefreshCommitLinkP *const* commit_link); /** diff --git a/src/mint/taler-mint-httpd_parsing.c b/src/mint/taler-mint-httpd_parsing.c index 1844fa881..4e7020bfe 100644 --- a/src/mint/taler-mint-httpd_parsing.c +++ b/src/mint/taler-mint-httpd_parsing.c @@ -490,6 +490,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, fname); if (NULL == root) { + GNUNET_break_op (0); ret = (MHD_YES == TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_BAD_REQUEST, @@ -513,6 +514,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, fnum); if (NULL == root) { + GNUNET_break_op (0); ret = (MHD_YES == TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_BAD_REQUEST, @@ -535,6 +537,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, str = json_string_value (root); if (NULL == str) { + GNUNET_break_op (0); ret = (MHD_YES == TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_BAD_REQUEST, @@ -548,6 +551,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, where, len); if (GNUNET_OK != res) { + GNUNET_break_op (0); ret = (MHD_YES == TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_BAD_REQUEST, @@ -571,6 +575,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, str = json_string_value (root); if (NULL == str) { + GNUNET_break_op (0); ret = (MHD_YES == TMH_RESPONSE_reply_internal_error (connection, "json_string_value() failed")) @@ -587,6 +592,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, *len); if (GNUNET_OK != res) { + GNUNET_break_op (0); GNUNET_free (*where); *where = NULL; *len = 0; @@ -613,6 +619,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, ( (-1 != typ) && (json_typeof (root) != typ)) ) { + GNUNET_break_op (0); *r_json = NULL; ret = (MHD_YES == TMH_RESPONSE_reply_json_pack (connection, @@ -637,6 +644,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, if (json_typeof (root) != JSON_INTEGER) { + GNUNET_break_op (0); ret = (MHD_YES == TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_BAD_REQUEST, @@ -666,6 +674,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, str = json_string_value (root); if (NULL == str) { + GNUNET_break_op (0); ret = (MHD_YES == TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_BAD_REQUEST, @@ -683,6 +692,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, len); if (GNUNET_OK != res) { + GNUNET_break_op (0); GNUNET_free (buf); ret = (MHD_YES == TMH_RESPONSE_reply_json_pack (connection, @@ -698,6 +708,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, GNUNET_free (buf); if (NULL == where->rsa_public_key) { + GNUNET_break_op (0); ret = (MHD_YES == TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_BAD_REQUEST, @@ -724,6 +735,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, str = json_string_value (root); if (NULL == str) { + GNUNET_break_op (0); ret = (MHD_YES == TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_BAD_REQUEST, @@ -741,6 +753,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, len); if (GNUNET_OK != res) { + GNUNET_break_op (0); GNUNET_free (buf); ret = (MHD_YES == TMH_RESPONSE_reply_json_pack (connection, @@ -756,6 +769,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, GNUNET_free (buf); if (NULL == where->rsa_signature) { + GNUNET_break_op (0); ret = (MHD_YES == TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_BAD_REQUEST, @@ -777,6 +791,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, TALER_json_to_amount ((json_t *) root, where)) { + GNUNET_break_op (0); if (MHD_YES != TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_BAD_REQUEST, @@ -789,6 +804,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, if (0 != strcmp (where->currency, TMH_mint_currency_string)) { + GNUNET_break_op (0); if (MHD_YES != TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_BAD_REQUEST, @@ -815,6 +831,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, TALER_json_to_abs ((json_t *) root, where)) { + GNUNET_break_op (0); if (MHD_YES != TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_BAD_REQUEST, diff --git a/src/mint/taler-mint-httpd_refresh.c b/src/mint/taler-mint-httpd_refresh.c index 687fb998d..fcd843083 100644 --- a/src/mint/taler-mint-httpd_refresh.c +++ b/src/mint/taler-mint-httpd_refresh.c @@ -57,7 +57,7 @@ handle_refresh_melt_binary (struct MHD_Connection *connection, const struct TMH_DB_MeltDetails *coin_melt_details, const struct GNUNET_HashCode *session_hash, struct TALER_MINTDB_RefreshCommitCoin *const* commit_coin, - struct TALER_MINTDB_RefreshCommitLinkP *const* commit_link) + struct TALER_RefreshCommitLinkP *const* commit_link) { unsigned int i; struct TMH_KS_StateHandle *key_state; @@ -92,6 +92,7 @@ handle_refresh_melt_binary (struct MHD_Connection *connection, &cost, &total_cost)) ) { + GNUNET_break_op (0); TMH_KS_release (key_state); return TMH_RESPONSE_reply_internal_error (connection, "cost calculation failure"); @@ -115,6 +116,7 @@ handle_refresh_melt_binary (struct MHD_Connection *connection, &coin_melt_details->melt_amount_with_fee, &fee_melt)) { + GNUNET_break_op (0); TMH_KS_release (key_state); return TMH_RESPONSE_reply_external_error (connection, "Melt contribution below melting fee"); @@ -124,6 +126,7 @@ handle_refresh_melt_binary (struct MHD_Connection *connection, &melt, &total_melt)) { + GNUNET_break_op (0); TMH_KS_release (key_state); return TMH_RESPONSE_reply_internal_error (connection, "balance calculation failure"); @@ -134,6 +137,7 @@ handle_refresh_melt_binary (struct MHD_Connection *connection, TALER_amount_cmp (&total_cost, &total_melt)) { + GNUNET_break_op (0); /* We require total value of coins being melted and total value of coins being generated to match! */ return TMH_RESPONSE_reply_json_pack (connection, @@ -185,13 +189,17 @@ get_coin_public_info (struct MHD_Connection *connection, coin_info, spec); if (GNUNET_OK != ret) + { + GNUNET_break_op (0); return ret; + } /* check mint signature on the coin */ r_melt_detail->coin_info.denom_sig = sig; r_melt_detail->coin_info.denom_pub = pk; if (GNUNET_OK != TALER_test_coin_valid (&r_melt_detail->coin_info)) { + GNUNET_break_op (0); TMH_PARSE_release_data (spec); r_melt_detail->coin_info.denom_sig.rsa_signature = NULL; r_melt_detail->coin_info.denom_pub.rsa_public_key = NULL; @@ -202,21 +210,21 @@ get_coin_public_info (struct MHD_Connection *connection, } r_melt_detail->melt_sig = melt_sig; r_melt_detail->melt_amount_with_fee = amount; - TMH_PARSE_release_data (spec); return GNUNET_OK; } /** * Verify that the signature shows that this coin is to be melted into - * the given @a session_pub melting session, and that this is a valid + * the given @a session_hash melting session, and that this is a valid * coin (we know the denomination key and the signature on it is * valid). Essentially, this does all of the per-coin checks that can * be done before the transaction starts. * * @param connection the connection to send error responses to * @param session_hash hash over refresh session the coin is melted into - * @param melt_detail details about the coin's melting permission (if valid) + * @param[in,out] melt_detail details about the coin's melting permission, + * the `melt_fee` is updated * @return #GNUNET_YES if coin public info in JSON was valid * #GNUNET_NO JSON was invalid, response was generated * #GNUNET_SYSERR on internal error @@ -224,7 +232,7 @@ get_coin_public_info (struct MHD_Connection *connection, static int verify_coin_public_info (struct MHD_Connection *connection, const struct GNUNET_HashCode *session_hash, - const struct TMH_DB_MeltDetails *melt_detail) + struct TMH_DB_MeltDetails *melt_detail) { struct TALER_RefreshMeltCoinAffirmationPS body; struct TMH_KS_StateHandle *key_state; @@ -246,17 +254,20 @@ verify_coin_public_info (struct MHD_Connection *connection, valid for issuing! (#3634) */ TALER_amount_ntoh (&fee_refresh, &dki->issue.properties.fee_refresh); + melt_detail->melt_fee = fee_refresh; body.purpose.size = htonl (sizeof (struct TALER_RefreshMeltCoinAffirmationPS)); body.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT); body.session_hash = *session_hash; + TALER_amount_hton (&body.amount_with_fee, &melt_detail->melt_amount_with_fee); TALER_amount_hton (&body.melt_fee, &fee_refresh); body.coin_pub = melt_detail->coin_info.coin_pub; if (TALER_amount_cmp (&fee_refresh, - &melt_detail->melt_amount_with_fee) < 0) + &melt_detail->melt_amount_with_fee) > 0) { + GNUNET_break_op (0); TMH_KS_release (key_state); return (MHD_YES == TMH_RESPONSE_reply_external_error (connection, @@ -271,6 +282,7 @@ verify_coin_public_info (struct MHD_Connection *connection, &melt_detail->melt_sig.eddsa_signature, &melt_detail->coin_info.coin_pub.eddsa_pub)) { + GNUNET_break_op (0); if (MHD_YES != TMH_RESPONSE_reply_signature_invalid (connection, "confirm_sig")) @@ -318,7 +330,7 @@ free_commit_coins (struct TALER_MINTDB_RefreshCommitCoin **commit_coin, * @param num_old_coins size of 2nd dimension */ static void -free_commit_links (struct TALER_MINTDB_RefreshCommitLinkP **commit_link, +free_commit_links (struct TALER_RefreshCommitLinkP **commit_link, unsigned int kappa, unsigned int num_old_coins) { @@ -361,7 +373,6 @@ handle_refresh_melt_json (struct MHD_Connection *connection, unsigned int num_newcoins, const json_t *coin_evs, const json_t *link_encs) - { int res; unsigned int i; @@ -373,7 +384,7 @@ handle_refresh_melt_json (struct MHD_Connection *connection, struct GNUNET_HashCode session_hash; struct GNUNET_HashContext *hash_context; struct TALER_MINTDB_RefreshCommitCoin *commit_coin[TALER_CNC_KAPPA]; - struct TALER_MINTDB_RefreshCommitLinkP *commit_link[TALER_CNC_KAPPA]; + struct TALER_RefreshCommitLinkP *commit_link[TALER_CNC_KAPPA]; /* For the signature check, we hash most of the inputs together (except for the signatures on the coins). */ @@ -407,8 +418,8 @@ handle_refresh_melt_json (struct MHD_Connection *connection, } coin_count = json_array_size (melt_coins); - coin_melt_details = GNUNET_malloc (coin_count * - sizeof (struct TMH_DB_MeltDetails)); + coin_melt_details = GNUNET_new_array (coin_count, + struct TMH_DB_MeltDetails); for (i=0;i<coin_count;i++) { /* decode JSON data on coin to melt */ @@ -419,6 +430,7 @@ handle_refresh_melt_json (struct MHD_Connection *connection, &coin_melt_details[i]); if (GNUNET_OK != res) { + GNUNET_break_op (0); for (j=0;j<i;j++) { GNUNET_CRYPTO_rsa_public_key_free (coin_melt_details[j].coin_info.denom_pub.rsa_public_key); @@ -438,6 +450,7 @@ handle_refresh_melt_json (struct MHD_Connection *connection, &coin_melt_details[j].coin_info.coin_pub, sizeof (struct TALER_CoinSpendPublicKeyP))) { + GNUNET_break_op (0); for (j=0;j<i;j++) { GNUNET_CRYPTO_rsa_public_key_free (coin_melt_details[j].coin_info.denom_pub.rsa_public_key); @@ -459,7 +472,6 @@ handle_refresh_melt_json (struct MHD_Connection *connection, GNUNET_CRYPTO_hash_context_read (hash_context, &melt_amount, sizeof (struct TALER_AmountNBO)); - } /* parse JSON arrays into 2d binary arrays and hash everything @@ -486,6 +498,7 @@ handle_refresh_melt_json (struct MHD_Connection *connection, if (GNUNET_OK != res) { + GNUNET_break_op (0); GNUNET_CRYPTO_hash_context_abort (hash_context); free_commit_coins (commit_coin, TALER_CNC_KAPPA, @@ -504,6 +517,7 @@ handle_refresh_melt_json (struct MHD_Connection *connection, &link_enc_size); if (GNUNET_OK != res) { + GNUNET_break_op (0); GNUNET_CRYPTO_hash_context_abort (hash_context); free_commit_coins (commit_coin, TALER_CNC_KAPPA, @@ -524,10 +538,10 @@ handle_refresh_melt_json (struct MHD_Connection *connection, for (i = 0; i < TALER_CNC_KAPPA; i++) { commit_link[i] = GNUNET_malloc (num_oldcoins * - sizeof (struct TALER_MINTDB_RefreshCommitLinkP)); + sizeof (struct TALER_RefreshCommitLinkP)); for (j = 0; j < num_oldcoins; j++) { - struct TALER_MINTDB_RefreshCommitLinkP *rcl = &commit_link[i][j]; + struct TALER_RefreshCommitLinkP *rcl = &commit_link[i][j]; res = TMH_PARSE_navigate_json (connection, transfer_pubs, @@ -539,6 +553,7 @@ handle_refresh_melt_json (struct MHD_Connection *connection, if (GNUNET_OK != res) { + GNUNET_break_op (0); GNUNET_break (GNUNET_SYSERR != res); GNUNET_CRYPTO_hash_context_abort (hash_context); free_commit_coins (commit_coin, @@ -559,6 +574,7 @@ handle_refresh_melt_json (struct MHD_Connection *connection, if (GNUNET_OK != res) { + GNUNET_break_op (0); GNUNET_break (GNUNET_SYSERR != res); GNUNET_CRYPTO_hash_context_abort (hash_context); free_commit_coins (commit_coin, @@ -572,7 +588,7 @@ handle_refresh_melt_json (struct MHD_Connection *connection, GNUNET_CRYPTO_hash_context_read (hash_context, rcl, - sizeof (struct TALER_MINTDB_RefreshCommitLinkP)); + sizeof (struct TALER_RefreshCommitLinkP)); } } @@ -587,6 +603,7 @@ handle_refresh_melt_json (struct MHD_Connection *connection, &coin_melt_details[i]); if (GNUNET_OK != res) { + GNUNET_break_op (0); res = (GNUNET_NO == res) ? MHD_YES : MHD_NO; goto cleanup; } @@ -694,12 +711,14 @@ TMH_REFRESH_handler_refresh_melt (struct TMH_RequestHandler *rh, return TMH_RESPONSE_reply_arg_invalid (connection, "transfer_pubs"); } - res = TMH_PARSE_navigate_json (connection, coin_evs, + res = TMH_PARSE_navigate_json (connection, + coin_evs, TMH_PARSE_JNC_INDEX, (int) 0, - TMH_PARSE_JNC_RET_DATA, + TMH_PARSE_JNC_RET_TYPED_JSON, JSON_ARRAY, &coin_detail); if (GNUNET_OK != res) { + GNUNET_break_op (0); TMH_PARSE_release_data (spec); return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; } @@ -707,10 +726,11 @@ TMH_REFRESH_handler_refresh_melt (struct TMH_RequestHandler *rh, res = TMH_PARSE_navigate_json (connection, transfer_pubs, TMH_PARSE_JNC_INDEX, (int) 0, - TMH_PARSE_JNC_RET_DATA, + TMH_PARSE_JNC_RET_TYPED_JSON, JSON_ARRAY, &coin_detail); if (GNUNET_OK != res) { + GNUNET_break_op (0); TMH_PARSE_release_data (spec); return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; } diff --git a/src/mint/taler-mint-httpd_responses.c b/src/mint/taler-mint-httpd_responses.c index 57b233e73..9a6813f1d 100644 --- a/src/mint/taler-mint-httpd_responses.c +++ b/src/mint/taler-mint-httpd_responses.c @@ -695,7 +695,7 @@ TMH_RESPONSE_reply_withdraw_sign_success (struct MHD_Connection *connection, * @param coin_pub public key of the coin * @param coin_value original value of the coin * @param tl transaction history for the coin - * @param requested how much this coin was supposed to contribute + * @param requested how much this coin was supposed to contribute, including fee * @param residual remaining value of the coin (after subtracting @a tl) * @return a MHD result code */ @@ -713,13 +713,19 @@ TMH_RESPONSE_reply_refresh_melt_insufficient_funds (struct MHD_Connection *conne return TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_NOT_FOUND, "{s:s, s:o, s:o, s:o, s:o, s:o}", - "error", "insufficient funds", - "coin-pub", TALER_json_from_data (coin_pub, - sizeof (struct TALER_CoinSpendPublicKeyP)), - "original-value", TALER_json_from_amount (&coin_value), - "residual-value", TALER_json_from_amount (&residual), - "requested-value", TALER_json_from_amount (&requested), - "history", history); + "error", + "insufficient funds", + "coin_pub", + TALER_json_from_data (coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP)), + "original_value", + TALER_json_from_amount (&coin_value), + "residual_value", + TALER_json_from_amount (&residual), + "requested_value", + TALER_json_from_amount (&requested), + "history", + history); } @@ -894,7 +900,7 @@ TMH_RESPONSE_reply_refresh_reveal_missmatch (struct MHD_Connection *connection, info_link_k = json_array (); for (i=0;i<mc->num_newcoins;i++) { - const struct TALER_MINTDB_RefreshCommitLinkP *cl; + const struct TALER_RefreshCommitLinkP *cl; json_t *cl_json; cl = &mc->commit_links[k][i]; diff --git a/src/mint/taler-mint-httpd_responses.h b/src/mint/taler-mint-httpd_responses.h index 7afd01884..a35356389 100644 --- a/src/mint/taler-mint-httpd_responses.h +++ b/src/mint/taler-mint-httpd_responses.h @@ -350,7 +350,7 @@ TMH_RESPONSE_reply_refresh_reveal_missmatch (struct MHD_Connection *connection, /** - * Information for each session a coin was melted into. + * @brief Information for each session a coin was melted into. */ struct TMH_RESPONSE_LinkSessionInfo { diff --git a/src/mintdb/plugin_mintdb_postgres.c b/src/mintdb/plugin_mintdb_postgres.c index 16330e04a..658d8dd90 100644 --- a/src/mintdb/plugin_mintdb_postgres.c +++ b/src/mintdb/plugin_mintdb_postgres.c @@ -166,7 +166,7 @@ static int postgres_drop_temporary (void *cls, struct TALER_MINTDB_Session *session) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Dropping temporary tables\n"); SQLEXEC_ (session->conn, "DROP SCHEMA " TALER_TEMP_SCHEMA_NAME " CASCADE;"); @@ -177,6 +177,40 @@ postgres_drop_temporary (void *cls, /** + * Function called by libpq whenever it wants to log something. + * We already log whenever we care, so this function does nothing + * and merely exists to silence the libpq logging. + * + * @param arg NULL + * @param res information about some libpq event + */ +static void +pq_notice_receiver_cb (void *arg, + const PGresult *res) +{ + /* do nothing, intentionally */ +} + + +/** + * Function called by libpq whenever it wants to log something. + * We log those using the Taler logger. + * + * @param arg NULL + * @param message information about some libpq event + */ +static void +pq_notice_processor_cb (void *arg, + const char *message) +{ + GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, + "pq", + "%s", + message); +} + + +/** * Create the necessary tables if they are not present * * @param cls the `struct PostgresClosure` with the plugin-specific state @@ -198,6 +232,12 @@ postgres_create_tables (void *cls, PQfinish (conn); return GNUNET_SYSERR; } + PQsetNoticeReceiver (conn, + &pq_notice_receiver_cb, + NULL); + PQsetNoticeProcessor (conn, + &pq_notice_processor_cb, + NULL); if ( (GNUNET_YES == temporary) && (GNUNET_SYSERR == set_temporary_schema (conn))) { @@ -939,6 +979,12 @@ postgres_get_session (void *cls, GNUNET_break (0); return NULL; } + PQsetNoticeReceiver (db_conn, + &pq_notice_receiver_cb, + NULL); + PQsetNoticeProcessor (db_conn, + &pq_notice_processor_cb, + NULL); if ( (GNUNET_YES == temporary) && (GNUNET_SYSERR == set_temporary_schema(db_conn)) ) { @@ -1751,11 +1797,8 @@ postgres_get_reserve_history (void *cls, * @param session database connection * @param deposit deposit to search for * @return #GNUNET_YES if we know this operation, - * #GNUNET_NO if this deposit is unknown to us - * #GNUNET_SYSERR on DB error or if same coin(pub), merchant(pub) and - * transaction ID are already in DB, but for different - * other transaction details (contract, wiring details, - * amount, etc.) + * #GNUNET_NO if this exact deposit is unknown to us + * #GNUNET_SYSERR on DB error */ static int postgres_have_deposit (void *cls, @@ -1823,13 +1866,12 @@ postgres_have_deposit (void *cls, &deposit2.h_wire, sizeof (struct GNUNET_HashCode))) ) { - /* Inconsistencies detected! Bug in merchant! (We might want to + /* Inconsistencies detected! Does not match! (We might want to expand the API with a 'get_deposit' function to return the original transaction details to be used for an error message in the future!) #3838 */ - GNUNET_break_op (0); PQclear (result); - return GNUNET_SYSERR; + return GNUNET_NO; } } PQclear (result); @@ -2598,7 +2640,7 @@ postgres_insert_refresh_commit_links (void *cls, const struct GNUNET_HashCode *session_hash, uint16_t cnc_index, uint16_t num_links, - const struct TALER_MINTDB_RefreshCommitLinkP *links) + const struct TALER_RefreshCommitLinkP *links) { // FIXME: check logic! links is array! struct TALER_PQ_QueryParam params[] = { @@ -2651,7 +2693,7 @@ postgres_get_refresh_commit_links (void *cls, const struct GNUNET_HashCode *session_hash, uint16_t cnc_index, uint16_t num_links, - struct TALER_MINTDB_RefreshCommitLinkP *links) + struct TALER_RefreshCommitLinkP *links) { // FIXME: check logic: was written for a single link! struct TALER_PQ_QueryParam params[] = { @@ -2759,7 +2801,7 @@ postgres_get_melt_commitment (void *cls, goto cleanup; mc->commit_links[cnc_index] = GNUNET_malloc (mc->num_oldcoins * - sizeof (struct TALER_MINTDB_RefreshCommitLinkP)); + sizeof (struct TALER_RefreshCommitLinkP)); if (GNUNET_OK != postgres_get_refresh_commit_links (cls, session, diff --git a/src/util/crypto.c b/src/util/crypto.c index b803b960a..ebf6413db 100644 --- a/src/util/crypto.c +++ b/src/util/crypto.c @@ -163,6 +163,34 @@ TALER_transfer_decrypt (const struct TALER_EncryptedLinkSecretP *secret_enc, /** + * Given the coin and the transfer private keys, compute the + * transfer secret. (Technically, we only need one of the two + * private keys, but the caller currently trivially only has + * the two private keys, so we derive one of the public keys + * internally to this function.) + * + * @param coin_priv coin key + * @param trans_priv transfer private key + * @param[out] ts computed transfer secret + */ +void +TALER_link_derive_transfer_secret (const struct TALER_CoinSpendPrivateKeyP *coin_priv, + const struct TALER_TransferPrivateKeyP *trans_priv, + struct TALER_TransferSecretP *ts) +{ + struct TALER_CoinSpendPublicKeyP coin_pub; + + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, + &coin_pub.eddsa_pub); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecdh_eddsa (&trans_priv->ecdhe_priv, + &coin_pub.eddsa_pub, + &ts->key)); + +} + + +/** * Use the @a trans_sec (from ECDHE) to encrypt the @a secret * to obtain the @a secret_enc. * |