aboutsummaryrefslogtreecommitdiff
path: root/src/mint
diff options
context:
space:
mode:
Diffstat (limited to 'src/mint')
-rw-r--r--src/mint/.gitignore6
-rw-r--r--src/mint/Makefile.am131
-rw-r--r--src/mint/mint.h198
-rw-r--r--src/mint/mint_api.c1121
-rw-r--r--src/mint/mint_common.c283
-rw-r--r--src/mint/mint_db.c1838
-rw-r--r--src/mint/mint_db.h344
-rw-r--r--src/mint/taler-mint-dbinit.c285
-rw-r--r--src/mint/taler-mint-httpd.c376
-rw-r--r--src/mint/taler-mint-httpd.h106
-rw-r--r--src/mint/taler-mint-httpd_deposit.c270
-rw-r--r--src/mint/taler-mint-httpd_deposit.h48
-rw-r--r--src/mint/taler-mint-httpd_keys.c512
-rw-r--r--src/mint/taler-mint-httpd_keys.h155
-rw-r--r--src/mint/taler-mint-httpd_mhd.c300
-rw-r--r--src/mint/taler-mint-httpd_mhd.h132
-rw-r--r--src/mint/taler-mint-httpd_refresh.c1497
-rw-r--r--src/mint/taler-mint-httpd_refresh.h103
-rw-r--r--src/mint/taler-mint-httpd_withdraw.c400
-rw-r--r--src/mint/taler-mint-httpd_withdraw.h65
-rw-r--r--src/mint/taler-mint-keycheck.c169
-rw-r--r--src/mint/taler-mint-keyup.c657
-rw-r--r--src/mint/taler-mint-reservemod.c215
-rw-r--r--src/mint/test_mint_api.c211
-rw-r--r--src/mint/test_mint_common.c83
-rw-r--r--src/mint/test_mint_deposits.c149
-rw-r--r--src/mint/test_mint_nayapaisa.eccbin0 -> 32 bytes
-rw-r--r--src/mint/test_mint_nayapaisa/README1
-rw-r--r--src/mint/test_mint_nayapaisa/config/mint-common.conf6
-rw-r--r--src/mint/test_mint_nayapaisa/config/mint-keyup.conf79
-rw-r--r--src/mint/test_mint_nyadirahim.ecc1
-rw-r--r--src/mint/test_mint_nyadirahim/README1
-rw-r--r--src/mint/test_mint_nyadirahim/config/mint-common.conf6
-rw-r--r--src/mint/test_mint_nyadirahim/config/mint-keyup.conf79
34 files changed, 9827 insertions, 0 deletions
diff --git a/src/mint/.gitignore b/src/mint/.gitignore
new file mode 100644
index 000000000..a2e71d5da
--- /dev/null
+++ b/src/mint/.gitignore
@@ -0,0 +1,6 @@
+taler-mint-dbinit
+taler-mint-keycheck
+taler-mint-keyup
+taler-mint-pursemod
+taler-mint-reservemod
+taler-mint-httpd \ No newline at end of file
diff --git a/src/mint/Makefile.am b/src/mint/Makefile.am
new file mode 100644
index 000000000..2ae153485
--- /dev/null
+++ b/src/mint/Makefile.am
@@ -0,0 +1,131 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/include $(POSTGRESQL_CPPFLAGS)
+
+lib_LTLIBRARIES = \
+ libtalermint.la \
+ libtalermintapi.la
+
+libtalermint_la_SOURCES = \
+ mint_common.c \
+ mint_db.c
+
+libtalermint_la_LIBADD = \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetutil \
+ -lpq
+
+libtalermint_la_LDFLAGS = \
+ $(POSTGRESQL_LDFLAGS) \
+ -version-info 0:0:0 \
+ -no-undefined
+
+libtalermintapi_la_SOURCES = \
+ mint_api.c
+
+libtalermintapi_la_LIBADD = \
+ -lgnunetutil \
+ -ljansson \
+ -lcurl
+
+libtalermintapi_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
+
+
+bin_PROGRAMS = \
+ taler-mint-keyup \
+ taler-mint-keycheck \
+ taler-mint-reservemod \
+ taler-mint-httpd \
+ taler-mint-dbinit
+
+taler_mint_keyup_SOURCES = \
+ taler-mint-keyup.c
+
+taler_mint_keyup_LDADD = \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/mint/libtalermint.la \
+ -lpq \
+ -lgnunetutil
+taler_mint_keyup_LDFLAGS = $(POSTGRESQL_LDFLAGS)
+
+
+taler_mint_keycheck_SOURCES = \
+ taler-mint-keycheck.c
+
+taler_mint_keycheck_LDADD = \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/mint/libtalermint.la \
+ -lgnunetutil \
+ -lpq
+taler_mint_keycheck_LDFLAGS = $(POSTGRESQL_LDFLAGS)
+
+taler_mint_reservemod_SOURCES = \
+ taler-mint-reservemod.c
+taler_mint_reservemod_LDADD = \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/mint/libtalermint.la \
+ -lpq \
+ -lgnunetutil
+taler_mint_reservemod_LDFLAGS = \
+ $(POSTGRESQL_LDFLAGS)
+
+taler_mint_httpd_SOURCES = \
+ taler-mint-httpd.c \
+ taler-mint-httpd_mhd.c \
+ taler-mint-httpd_keys.c \
+ taler-mint-httpd_deposit.c \
+ taler-mint-httpd_withdraw.c \
+ taler-mint-httpd_refresh.c
+taler_mint_httpd_LDADD = \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/mint/libtalermint.la \
+ -lpq \
+ -lmicrohttpd \
+ -ljansson \
+ -lgnunetutil \
+ -lpthread
+taler_mint_httpd_LDFLAGS = \
+ $(POSTGRESQL_LDFLAGS)
+
+
+taler_mint_dbinit_SOURCES = \
+ taler-mint-dbinit.c
+taler_mint_dbinit_LDADD = \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/mint/libtalermint.la \
+ -lpq \
+ -lgnunetutil
+taler_mint_dbinit_LDFLAGS = $(POSTGRESQL_LDFLAGS)
+
+check_PROGRAMS = \
+ test-mint-api \
+ test-mint-deposits \
+ test-mint-common
+
+test_mint_api_SOURCES = test_mint_api.c
+test_mint_api_LDADD = \
+ libtalermintapi.la \
+ $(LIBGCRYPT_LIBS) \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetutil \
+ -ljansson
+
+test_mint_deposits_SOURCES = \
+ test_mint_deposits.c
+test_mint_deposits_LDADD = \
+ libtalermint.la \
+ $(top_srcdir)/src/util/libtalerutil.la \
+ -lgnunetutil \
+ -lpq
+
+test_mint_common_SOURCES = \
+ test_mint_common.c
+test_mint_common_LDADD = \
+ libtalermint.la \
+ $(top_srcdir)/src/util/libtalerutil.la \
+ -lgnunetutil
diff --git a/src/mint/mint.h b/src/mint/mint.h
new file mode 100644
index 000000000..5adce03c6
--- /dev/null
+++ b/src/mint/mint.h
@@ -0,0 +1,198 @@
+/*
+ This file is part of TALER
+ (C) 2014 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 taler_mint.h
+ * @brief Common functionality for the mint
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ */
+
+#ifndef _MINT_H
+#define _MINT_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_common.h>
+#include <libpq-fe.h>
+#include "taler_util.h"
+#include "taler_rsa.h"
+
+#define DIR_SIGNKEYS "signkeys"
+#define DIR_DENOMKEYS "denomkeys"
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+
+/**
+ * FIXME
+ */
+struct TALER_MINT_SignKeyIssue
+{
+ struct GNUNET_CRYPTO_EddsaPrivateKey signkey_priv;
+ struct GNUNET_CRYPTO_EddsaSignature signature;
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+ struct GNUNET_CRYPTO_EddsaPublicKey master_pub;
+ struct GNUNET_TIME_AbsoluteNBO start;
+ struct GNUNET_TIME_AbsoluteNBO expire;
+ struct GNUNET_CRYPTO_EddsaPublicKey signkey_pub;
+};
+
+struct TALER_MINT_DenomKeyIssue
+{
+ /**
+ * The private key of the denomination. Will be NULL if the private key is
+ * not available.
+ */
+ struct TALER_RSA_PrivateKey *denom_priv;
+ struct GNUNET_CRYPTO_EddsaSignature signature;
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+ struct GNUNET_CRYPTO_EddsaPublicKey master;
+ struct GNUNET_TIME_AbsoluteNBO start;
+ struct GNUNET_TIME_AbsoluteNBO expire_withdraw;
+ struct GNUNET_TIME_AbsoluteNBO expire_spend;
+ struct TALER_RSA_PublicKeyBinaryEncoded denom_pub;
+ struct TALER_AmountNBO value;
+ struct TALER_AmountNBO fee_withdraw;
+ struct TALER_AmountNBO fee_deposit;
+ struct TALER_AmountNBO fee_refresh;
+};
+
+struct RefreshMeltSignatureBody
+{
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+ struct GNUNET_HashCode melt_hash;
+};
+
+struct RefreshCommitSignatureBody
+{
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+ struct GNUNET_HashCode commit_hash;
+};
+
+struct RefreshCommitResponseSignatureBody
+{
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+ uint16_t noreveal_index;
+};
+
+struct RefreshMeltResponseSignatureBody
+{
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+ struct GNUNET_HashCode melt_response_hash;
+};
+
+
+struct RefreshMeltConfirmSignRequestBody
+{
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+ struct GNUNET_CRYPTO_EddsaPublicKey session_pub;
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+
+
+/**
+ * Iterator for sign keys.
+ *
+ * @param cls closure
+ * @param ski the sign key issue
+ * @return #GNUNET_OK to continue to iterate,
+ * #GNUNET_NO to stop iteration with no error,
+ * #GNUNET_SYSERR to abort iteration with error!
+ */
+typedef int (*TALER_MINT_SignkeyIterator)(void *cls,
+ const struct TALER_MINT_SignKeyIssue *ski);
+
+/**
+ * Iterator for denomination keys.
+ *
+ * @param cls closure
+ * @param dki the denomination key issue
+ * @param alias coin alias
+ * @return #GNUNET_OK to continue to iterate,
+ * #GNUNET_NO to stop iteration with no error,
+ * #GNUNET_SYSERR to abort iteration with error!
+ */
+typedef int (*TALER_MINT_DenomkeyIterator)(void *cls,
+ const char *alias,
+ const struct TALER_MINT_DenomKeyIssue *dki);
+
+
+
+/**
+ * FIXME
+ */
+int
+TALER_MINT_signkeys_iterate (const char *mint_base_dir,
+ TALER_MINT_SignkeyIterator it, void *cls);
+
+
+/**
+ * FIXME
+ */
+int
+TALER_MINT_denomkeys_iterate (const char *mint_base_dir,
+ TALER_MINT_DenomkeyIterator it, void *cls);
+
+
+/**
+ * Exports a denomination key to the given file
+ *
+ * @param filename the file where to write the denomination key
+ * @param dki the denomination key
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure.
+ */
+int
+TALER_MINT_write_denom_key (const char *filename,
+ const struct TALER_MINT_DenomKeyIssue *dki);
+
+
+/**
+ * Import a denomination key from the given file
+ *
+ * @param filename the file to import the key from
+ * @param dki pointer to return the imported denomination key
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+ */
+int
+TALER_MINT_read_denom_key (const char *filename,
+ struct TALER_MINT_DenomKeyIssue *dki);
+
+
+/**
+ * Load the configuration for the mint in the given
+ * directory.
+ *
+ * @param mint_base_dir the mint's base directory
+ * @return the mint configuratin, or NULL on error
+ */
+struct GNUNET_CONFIGURATION_Handle *
+TALER_MINT_config_load (const char *mint_base_dir);
+
+
+int
+TALER_TALER_DB_extract_amount (PGresult *result, unsigned int row,
+ int indices[3], struct TALER_Amount *denom);
+
+int
+TALER_TALER_DB_extract_amount_nbo (PGresult *result, unsigned int row,
+ int indices[3], struct TALER_AmountNBO *denom_nbo);
+
+#endif /* _MINT_H */
+
diff --git a/src/mint/mint_api.c b/src/mint/mint_api.c
new file mode 100644
index 000000000..b8d42b274
--- /dev/null
+++ b/src/mint/mint_api.c
@@ -0,0 +1,1121 @@
+/*
+ This file is part of TALER
+ (C) 2014 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/mint_api.c
+ * @brief Implementation of the client interface to mint's HTTP API
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ */
+
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_mint_service.h"
+#include "taler_signatures.h"
+#include "mint.h"
+
+#define CURL_STRERROR(TYPE, FUNCTION, CODE) \
+ GNUNET_log (TYPE, "cURL function `%s' has failed at `%s:%d' with error: %s", \
+ FUNCTION, __FILE__, __LINE__, curl_easy_strerror (CODE));
+
+
+
+/**
+ * Print JSON parsing related error information
+ */
+#define JSON_WARN(error) \
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \
+ "JSON parsing failed at %s:%u: %s (%s)", \
+ __FILE__, __LINE__, error.text, error.source)
+
+/**
+ * Failsafe flag
+ */
+static int fail;
+
+/**
+ * Context
+ */
+struct TALER_MINT_Context
+{
+ /**
+ * CURL multi handle
+ */
+ CURLM *multi;
+
+ /**
+ * CURL share handle
+ */
+ CURLSH *share;
+
+ /**
+ * Perform task handle
+ */
+ struct GNUNET_SCHEDULER_Task *perform_task;
+};
+
+/**
+ * Type of requests we currently have
+ */
+enum RequestType
+{
+ /**
+ * No request
+ */
+ REQUEST_TYPE_NONE,
+
+ /**
+ * Current request is to receive mint's keys
+ */
+ REQUEST_TYPE_KEYSGET,
+
+ /**
+ * Current request is to submit a deposit permission and get its status
+ */
+ REQUEST_TYPE_DEPOSIT
+};
+
+
+/**
+ * Handle to the mint
+ */
+struct TALER_MINT_Handle
+{
+ /**
+ * The context of this handle
+ */
+ struct TALER_MINT_Context *ctx;
+
+ /**
+ * The hostname of the mint
+ */
+ char *hostname;
+
+ /**
+ * The CURL handle
+ */
+ CURL *curl;
+
+ /**
+ * Error buffer for CURL
+ */
+ char emsg[CURL_ERROR_SIZE];
+
+ /**
+ * Download buffer
+ */
+ void *buf;
+
+ /**
+ * The currently active request
+ */
+ union {
+ /**
+ * Used to denote no request if set to NULL
+ */
+ void *none;
+
+ /**
+ * Denom keys get request if REQUEST_TYPE_KEYSGET
+ */
+ struct TALER_MINT_KeysGetHandle *keys_get;
+
+ /**
+ * Deposit request if REQUEST_TYPE_DEPOSIT
+ */
+ struct TALER_MINT_DepositHandle *deposit;
+ } req;
+
+ /**
+ * The size of the download buffer
+ */
+ size_t buf_size;
+
+ /**
+ * Active request type
+ */
+ enum RequestType req_type;
+
+ /**
+ * The service port of the mint
+ */
+ uint16_t port;
+
+ /**
+ * Are we connected to the mint?
+ */
+ uint8_t connected;
+
+};
+
+
+/**
+ * A handle to get the keys of a mint
+ */
+struct TALER_MINT_KeysGetHandle
+{
+ /**
+ * The connection to mint this request handle will use
+ */
+ struct TALER_MINT_Handle *mint;
+
+ /**
+ * The url for this handle
+ */
+ char *url;
+
+ TALER_MINT_KeysGetCallback cb;
+ void *cls;
+
+ TALER_MINT_ContinuationCallback cont_cb;
+ void *cont_cls;
+};
+
+
+/**
+ * A handle to submit a deposit permission and get its status
+ */
+struct TALER_MINT_DepositHandle
+{
+ /**
+ *The connection to mint this request handle will use
+ */
+ struct TALER_MINT_Handle *mint;
+
+ /**
+ * The url for this handle
+ */
+ char *url;
+
+ TALER_MINT_DepositResultCallback cb;
+ void *cls;
+
+ char *json_enc;
+
+ struct curl_slist *headers;
+
+};
+
+
+/**
+ * Parses the timestamp encoded as ASCII string as UNIX timstamp.
+ *
+ * @param abs successfully parsed timestamp will be returned thru this parameter
+ * @param tstamp_enc the ASCII encoding of the timestamp
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+ */
+static int
+parse_timestamp (struct GNUNET_TIME_Absolute *abs, const char *tstamp_enc)
+{
+ unsigned long tstamp;
+
+ if (1 != sscanf (tstamp_enc, "%lu", &tstamp))
+ return GNUNET_SYSERR;
+ *abs = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get_zero_ (),
+ GNUNET_TIME_relative_multiply
+ (GNUNET_TIME_UNIT_SECONDS, tstamp));
+ return GNUNET_OK;
+}
+
+
+#define EXITIF(cond) \
+ do { \
+ if (cond) { GNUNET_break (0); goto EXITIF_exit; } \
+ } while (0)
+
+
+
+static int
+parse_json_signkey (struct TALER_MINT_SigningPublicKey **_sign_key,
+ json_t *sign_key_obj,
+ struct GNUNET_CRYPTO_EddsaPublicKey *master_key)
+{
+ json_t *valid_from_obj;
+ json_t *valid_until_obj;
+ json_t *key_obj;
+ json_t *sig_obj;
+ const char *valid_from_enc;
+ const char *valid_until_enc;
+ const char *key_enc;
+ const char *sig_enc;
+ struct TALER_MINT_SigningPublicKey *sign_key;
+ struct TALER_MINT_SignKeyIssue sign_key_issue;
+ struct GNUNET_CRYPTO_EddsaSignature sig;
+ struct GNUNET_TIME_Absolute valid_from;
+ struct GNUNET_TIME_Absolute valid_until;
+
+ EXITIF (JSON_OBJECT != json_typeof (sign_key_obj));
+ EXITIF (NULL == (valid_from_obj = json_object_get (sign_key_obj,
+ "stamp_start")));
+ EXITIF (NULL == (valid_until_obj = json_object_get (sign_key_obj,
+ "stamp_expire")));
+ EXITIF (NULL == (key_obj = json_object_get (sign_key_obj, "key")));
+ EXITIF (NULL == (sig_obj = json_object_get (sign_key_obj, "master_sig")));
+ EXITIF (NULL == (valid_from_enc = json_string_value (valid_from_obj)));
+ EXITIF (NULL == (valid_until_enc = json_string_value (valid_until_obj)));
+ EXITIF (NULL == (key_enc = json_string_value (key_obj)));
+ EXITIF (NULL == (sig_enc = json_string_value (sig_obj)));
+ EXITIF (GNUNET_SYSERR == parse_timestamp (&valid_from,
+ valid_from_enc));
+ EXITIF (GNUNET_SYSERR == parse_timestamp (&valid_until,
+ valid_until_enc));
+ EXITIF (52 != strlen (key_enc)); /* strlen(base32(char[32])) = 52 */
+ EXITIF (103 != strlen (sig_enc)); /* strlen(base32(char[64])) = 103 */
+ EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data (sig_enc, 103,
+ &sig, sizeof (sig)));
+ (void) memset (&sign_key_issue, 0, sizeof (sign_key_issue));
+ EXITIF (GNUNET_SYSERR ==
+ GNUNET_CRYPTO_eddsa_public_key_from_string (key_enc,
+ 52,
+ &sign_key_issue.signkey_pub));
+ sign_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNKEY);
+ sign_key_issue.purpose.size =
+ htonl (sizeof (sign_key_issue)
+ - offsetof (struct TALER_MINT_SignKeyIssue, purpose));
+ sign_key_issue.master_pub = *master_key;
+ sign_key_issue.start = GNUNET_TIME_absolute_hton (valid_from);
+ sign_key_issue.expire = GNUNET_TIME_absolute_hton (valid_until);
+ EXITIF (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNKEY,
+ &sign_key_issue.purpose,
+ &sig,
+ master_key));
+ sign_key = GNUNET_new (struct TALER_MINT_SigningPublicKey);
+ sign_key->valid_from = valid_from;
+ sign_key->valid_until = valid_until;
+ sign_key->key = sign_key_issue.signkey_pub;
+ *_sign_key = sign_key;
+ return GNUNET_OK;
+
+ EXITIF_exit:
+ return GNUNET_SYSERR;
+}
+
+
+static int
+parse_json_amount (json_t *amount_obj, struct TALER_Amount *amt)
+{
+ json_t *obj;
+ const char *currency_str;
+ int value;
+ int fraction;
+
+ EXITIF (NULL == (obj = json_object_get (amount_obj, "currency")));
+ EXITIF (NULL == (currency_str = json_string_value (obj)));
+ EXITIF (NULL == (obj = json_object_get (amount_obj, "value")));
+ EXITIF (JSON_INTEGER != json_typeof (obj));
+ EXITIF (0 > (value = json_integer_value (obj)));
+ EXITIF (NULL == (obj = json_object_get (amount_obj, "fraction")));
+ EXITIF (JSON_INTEGER != json_typeof (obj));
+ EXITIF (0 > (fraction = json_integer_value (obj)));
+ (void) memset (amt->currency, 0, sizeof (amt->currency));
+ (void) strncpy (amt->currency, currency_str, sizeof (amt->currency) - 1);
+ amt->value = (uint32_t) value;
+ amt->fraction = (uint32_t) fraction;
+ return GNUNET_OK;
+
+ EXITIF_exit:
+ return GNUNET_SYSERR;
+}
+
+static int
+parse_json_denomkey (struct TALER_MINT_DenomPublicKey **_denom_key,
+ json_t *denom_key_obj,
+ struct GNUNET_CRYPTO_EddsaPublicKey *master_key)
+{
+ json_t *obj;
+ const char *sig_enc;
+ const char *deposit_valid_until_enc;
+ const char *withdraw_valid_until_enc;
+ const char *valid_from_enc;
+ const char *key_enc;
+ struct TALER_MINT_DenomPublicKey *denom_key;
+ struct GNUNET_TIME_Absolute valid_from;
+ struct GNUNET_TIME_Absolute withdraw_valid_until;
+ struct GNUNET_TIME_Absolute deposit_valid_until;
+ struct TALER_Amount value;
+ struct TALER_Amount fee_withdraw;
+ struct TALER_Amount fee_deposit;
+ struct TALER_Amount fee_refresh;
+ struct TALER_MINT_DenomKeyIssue denom_key_issue;
+ struct GNUNET_CRYPTO_EddsaSignature sig;
+
+ EXITIF (JSON_OBJECT != json_typeof (denom_key_obj));
+ EXITIF (NULL == (obj = json_object_get (denom_key_obj, "master_sig")));
+ EXITIF (NULL == (sig_enc = json_string_value (obj)));
+ EXITIF (103 != strlen (sig_enc));
+ EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data (sig_enc, 103,
+ &sig, sizeof (sig)));
+ EXITIF (NULL == (obj = json_object_get (denom_key_obj, "stamp_expire_deposit")));
+ EXITIF (NULL == (deposit_valid_until_enc = json_string_value (obj)));
+ EXITIF (NULL == (obj = json_object_get (denom_key_obj, "stamp_expire_withdraw")));
+ EXITIF (NULL == (withdraw_valid_until_enc = json_string_value (obj)));
+ EXITIF (NULL == (obj = json_object_get (denom_key_obj, "stamp_start")));
+ EXITIF (NULL == (valid_from_enc = json_string_value (obj)));
+ EXITIF (NULL == (obj = json_object_get (denom_key_obj, "denom_pub")));
+ EXITIF (NULL == (key_enc = json_string_value (obj)));
+ EXITIF (52 != strlen (key_enc)); /* strlen(base32(char[32])) = 52 */
+ EXITIF (GNUNET_SYSERR == parse_timestamp (&valid_from, valid_from_enc));
+ EXITIF (GNUNET_SYSERR == parse_timestamp (&withdraw_valid_until,
+ withdraw_valid_until_enc));
+ EXITIF (GNUNET_SYSERR == parse_timestamp (&deposit_valid_until,
+ deposit_valid_until_enc));
+
+ (void) memset (&denom_key_issue, 0, sizeof (denom_key_issue));
+ EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data (key_enc, 52,
+ &denom_key_issue.denom_pub,
+ sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)));
+ EXITIF (NULL == (obj = json_object_get (denom_key_obj, "value")));
+ EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &value));
+ EXITIF (NULL == (obj = json_object_get (denom_key_obj, "fee_withdraw")));
+ EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &fee_withdraw));
+ EXITIF (NULL == (obj = json_object_get (denom_key_obj, "fee_deposit")));
+ EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &fee_deposit));
+ EXITIF (NULL == (obj = json_object_get (denom_key_obj, "fee_refresh")));
+ EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &fee_refresh));
+ denom_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DENOM);
+ denom_key_issue.purpose.size = htonl
+ (sizeof (struct TALER_MINT_DenomKeyIssue) -
+ offsetof (struct TALER_MINT_DenomKeyIssue, purpose));
+ denom_key_issue.master = *master_key;
+ denom_key_issue.start = GNUNET_TIME_absolute_hton (valid_from);
+ denom_key_issue.expire_withdraw = GNUNET_TIME_absolute_hton (withdraw_valid_until);
+ denom_key_issue.expire_spend = GNUNET_TIME_absolute_hton (deposit_valid_until);
+ denom_key_issue.value = TALER_amount_hton (value);
+ denom_key_issue.fee_withdraw = TALER_amount_hton (fee_withdraw);
+ denom_key_issue.fee_deposit = TALER_amount_hton (fee_deposit);
+ denom_key_issue.fee_refresh = TALER_amount_hton (fee_refresh);
+ EXITIF (GNUNET_SYSERR ==
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOM,
+ &denom_key_issue.purpose,
+ &sig,
+ master_key));
+ denom_key = GNUNET_new (struct TALER_MINT_DenomPublicKey);
+ denom_key->key = denom_key_issue.denom_pub;
+ denom_key->valid_from = valid_from;
+ denom_key->withdraw_valid_until = withdraw_valid_until;
+ denom_key->deposit_valid_until = deposit_valid_until;
+ denom_key->value = value;
+ denom_key->fee_withdraw = fee_withdraw;
+ denom_key->fee_deposit = fee_deposit;
+ denom_key->fee_refresh = fee_refresh;
+ *_denom_key = denom_key;
+ return GNUNET_OK;
+
+ EXITIF_exit:
+ return GNUNET_SYSERR;
+}
+
+static int
+parse_response_keys_get (const char *in, size_t size,
+ struct TALER_MINT_SigningPublicKey ***_sign_keys,
+ unsigned int *_n_sign_keys,
+ struct TALER_MINT_DenomPublicKey ***_denom_keys,
+ unsigned int *_n_denom_keys)
+{
+ json_t *resp_obj;
+ struct TALER_MINT_DenomPublicKey **denom_keys;
+ struct GNUNET_CRYPTO_EddsaPublicKey master_key;
+ struct GNUNET_TIME_Absolute list_issue_date;
+ struct TALER_MINT_SigningPublicKey **sign_keys;
+ unsigned int n_denom_keys;
+ unsigned int n_sign_keys;
+ json_error_t error;
+ unsigned int index;
+ int OK;
+
+ denom_keys = NULL;
+ n_denom_keys = 0;
+ sign_keys = NULL;
+ n_sign_keys = 0;
+ OK = 0;
+ resp_obj = json_loadb (in, size,
+ JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK,
+ &error);
+ if (NULL == resp_obj)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unable to parse received data as JSON object\n");
+ return GNUNET_SYSERR;
+ }
+
+ EXITIF (JSON_OBJECT != json_typeof (resp_obj));
+ {
+ /* parse the master public key */
+ json_t *master_key_obj;
+ const char *master_key_enc;
+
+ EXITIF (NULL == (master_key_obj = json_object_get (resp_obj, "master_pub")));
+ EXITIF (NULL == (master_key_enc = json_string_value (master_key_obj)));
+ EXITIF (52 != strlen (master_key_enc)); /* strlen(base32(char[32])) = 52 */
+ EXITIF (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_public_key_from_string (master_key_enc,
+ 52,
+ &master_key));
+ }
+ {
+ /* parse the issue date of the response */
+ json_t *list_issue_date_obj;
+ const char *tstamp_enc;
+
+ EXITIF (NULL == (list_issue_date_obj =
+ json_object_get(resp_obj, "list_issue_date")));
+ EXITIF (NULL == (tstamp_enc = json_string_value (list_issue_date_obj)));
+ EXITIF (GNUNET_SYSERR == parse_timestamp (&list_issue_date, tstamp_enc));
+ }
+ {
+ /* parse the signing keys */
+ json_t *sign_keys_array;
+ json_t *sign_key_obj;
+
+ EXITIF (NULL == (sign_keys_array =
+ json_object_get (resp_obj, "signkeys")));
+ EXITIF (JSON_ARRAY != json_typeof (sign_keys_array));
+ EXITIF (0 == (n_sign_keys = json_array_size (sign_keys_array)));
+ sign_keys = GNUNET_malloc (sizeof (struct TALER_MINT_SigningPublicKey *)
+ * (n_sign_keys + 1));
+ index = 0;
+ json_array_foreach (sign_keys_array, index, sign_key_obj) {
+ EXITIF (GNUNET_SYSERR == parse_json_signkey (&sign_keys[index],
+ sign_key_obj,
+ &master_key));
+ }
+ }
+ {
+ /* parse the denomination keys */
+ json_t *denom_keys_array;
+ json_t *denom_key_obj;
+
+ EXITIF (NULL == (denom_keys_array = json_object_get (resp_obj, "denoms")));
+ EXITIF (JSON_ARRAY != json_typeof (denom_keys_array));
+ EXITIF (0 == (n_denom_keys = json_array_size (denom_keys_array)));
+ denom_keys = GNUNET_malloc (sizeof (struct TALER_MINT_DenomPublicKey *)
+ * (n_denom_keys + 1));
+ index = 0;
+ json_array_foreach (denom_keys_array, index, denom_key_obj) {
+ EXITIF (GNUNET_SYSERR == parse_json_denomkey (&denom_keys[index],
+ denom_key_obj,
+ &master_key));
+ }
+ }
+ OK = 1;
+
+ EXITIF_exit:
+ json_decref (resp_obj);
+ if (!OK)
+ {
+ if (NULL != sign_keys)
+ {
+ for (index=0; NULL != sign_keys[index]; index++)
+ GNUNET_free_non_null (sign_keys[index]);
+ GNUNET_free (sign_keys);
+ }
+ if (NULL != denom_keys)
+ {
+ for (index=0; NULL != denom_keys[index]; index++)
+ GNUNET_free_non_null (denom_keys[index]);
+ GNUNET_free (denom_keys);
+ }
+ return GNUNET_SYSERR;
+ }
+
+ *_sign_keys = sign_keys;
+ *_n_sign_keys = n_sign_keys;
+ *_denom_keys = denom_keys;
+ *_n_denom_keys = n_denom_keys;
+ return GNUNET_OK;
+}
+
+
+int
+parse_deposit_response (void *buf, size_t size, int *r_status, json_t **r_obj)
+{
+ json_t *obj;
+ const char *status_str;
+ json_error_t error;
+
+ status_str = NULL;
+ obj = NULL;
+ obj = json_loadb (buf, size,
+ JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error);
+ if (NULL == obj)
+ {
+ JSON_WARN (error);
+ return GNUNET_SYSERR;
+ }
+ EXITIF (-1 == json_unpack (obj, "{s:s}", "status", &status_str));
+ LOG_DEBUG ("Received deposit response: %s from mint\n", status_str);
+ if (0 == strcmp ("DEPOSIT_OK", status_str))
+ *r_status = 1;
+ else if (0 == strcmp ("DEPOSIT_QUEUED", status_str))
+ *r_status = 2;
+ else
+ *r_status = 0;
+ *r_obj = obj;
+
+ return GNUNET_OK;
+ EXITIF_exit:
+ json_decref (obj);
+ return GNUNET_SYSERR;
+}
+
+#undef EXITIF
+
+static void
+mint_connect (struct TALER_MINT_Handle *mint)
+{
+ struct TALER_MINT_Context *ctx = mint->ctx;
+
+ GNUNET_assert (0 == mint->connected);
+ GNUNET_assert (CURLM_OK == curl_multi_add_handle (ctx->multi, mint->curl));
+ mint->connected = GNUNET_YES;
+}
+
+static void
+mint_disconnect (struct TALER_MINT_Handle *mint)
+{
+ struct TALER_MINT_Context *ctx = mint->ctx;
+
+ GNUNET_assert (GNUNET_YES == mint->connected);
+ GNUNET_break (CURLM_OK == curl_multi_remove_handle (ctx->multi,
+ mint->curl));
+ mint->connected = GNUNET_NO;
+ GNUNET_free_non_null (mint->buf);
+ mint->buf = NULL;
+ mint->buf_size = 0;
+ mint->req_type = REQUEST_TYPE_NONE;
+ mint->req.none = NULL;
+}
+
+static void
+cleanup_keys_get (struct TALER_MINT_KeysGetHandle *gh)
+{
+ GNUNET_free (gh->url);
+ GNUNET_free (gh);
+}
+
+static void
+cleanup_deposit (struct TALER_MINT_DepositHandle *dh)
+{
+ curl_slist_free_all (dh->headers);
+ GNUNET_free_non_null (dh->json_enc);
+ GNUNET_free (dh->url);
+ GNUNET_free (dh);
+}
+
+static void
+request_failed (struct TALER_MINT_Handle *mint, long resp_code)
+{
+ switch (mint->req_type)
+ {
+ case REQUEST_TYPE_NONE:
+ GNUNET_assert (0);
+ break;
+ case REQUEST_TYPE_KEYSGET:
+ {
+ struct TALER_MINT_KeysGetHandle *gh = mint->req.keys_get;
+ TALER_MINT_ContinuationCallback cont_cb;
+ void *cont_cls;
+ GNUNET_assert (NULL != gh);
+ cont_cb = gh->cont_cb;
+ cont_cls = gh->cont_cls;
+ cleanup_keys_get (gh);
+ mint_disconnect (mint);
+ cont_cb (cont_cls, mint->emsg);
+ }
+ break;
+ case REQUEST_TYPE_DEPOSIT:
+ {
+ struct TALER_MINT_DepositHandle *dh = mint->req.deposit;
+ TALER_MINT_DepositResultCallback cb = dh->cb;
+ void *cls = dh->cls;
+ GNUNET_assert (NULL != dh);
+ cleanup_deposit (dh);
+ mint_disconnect (mint);
+ cb (cls, 0, NULL, mint->emsg);
+ }
+ break;
+ }
+}
+
+static void
+request_succeeded (struct TALER_MINT_Handle *mint, long resp_code)
+{
+ char *emsg;
+
+ emsg = NULL;
+ switch (mint->req_type)
+ {
+ case REQUEST_TYPE_NONE:
+ GNUNET_assert (0);
+ break;
+ case REQUEST_TYPE_KEYSGET:
+ {
+ struct TALER_MINT_KeysGetHandle *gh = mint->req.keys_get;
+ TALER_MINT_ContinuationCallback cont_cb;
+ void *cont_cls;
+ struct TALER_MINT_SigningPublicKey **sign_keys;
+ struct TALER_MINT_DenomPublicKey **denom_keys;
+ unsigned int n_sign_keys;
+ unsigned int n_denom_keys;
+
+ GNUNET_assert (NULL != gh);
+ cont_cb = gh->cont_cb;
+ cont_cls = gh->cont_cls;
+ if (200 == resp_code)
+ {
+ /* parse JSON object from the mint->buf which is of size mint->buf_size */
+ if (GNUNET_OK ==
+ parse_response_keys_get (mint->buf, mint->buf_size,
+ &sign_keys, &n_sign_keys,
+ &denom_keys, &n_denom_keys))
+ gh->cb (gh->cls, sign_keys, denom_keys);
+ else
+ emsg = GNUNET_strdup ("Error parsing response");
+ }
+ else
+ GNUNET_asprintf (&emsg, "Failed with response code: %ld", resp_code);
+ cleanup_keys_get (gh);
+ mint_disconnect (mint);
+ cont_cb (cont_cls, emsg);
+ }
+ break;
+ case REQUEST_TYPE_DEPOSIT:
+ {
+ struct TALER_MINT_DepositHandle *dh = mint->req.deposit;
+ TALER_MINT_DepositResultCallback cb;
+ void *cls;
+ int status;
+ json_t *obj;
+
+ GNUNET_assert (NULL != dh);
+ obj = NULL;
+ cb = dh->cb;
+ cls = dh->cls;
+ status = 0;
+ if (200 == resp_code)
+ {
+ /* parse JSON object from the mint->buf which is of size mint->buf_size */
+ if (GNUNET_OK !=
+ parse_deposit_response (mint->buf, mint->buf_size,
+ &status, &obj))
+ emsg = GNUNET_strdup ("Error parsing response");
+ }
+ else
+ GNUNET_asprintf (&emsg, "Failed with response code: %ld", resp_code);
+ cleanup_deposit (dh);
+ mint_disconnect (mint);
+ cb (cls, status, obj, emsg);
+ }
+ break;
+ }
+ GNUNET_free_non_null (emsg);
+}
+
+
+static void
+do_perform (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
+
+static void
+perform (struct TALER_MINT_Context *ctx)
+{
+ fd_set fd_rs;
+ fd_set fd_ws;
+ struct GNUNET_NETWORK_FDSet rs;
+ struct GNUNET_NETWORK_FDSet ws;
+ CURLMsg *cmsg;
+ struct TALER_MINT_Handle *mint;
+ long timeout;
+ long resp_code;
+ static unsigned int n_old;
+ int n_running;
+ int n_completed;
+ int max_fd;
+
+ n_completed = 0;
+ curl_multi_perform (ctx->multi, &n_running);
+ GNUNET_assert (0 <= n_running);
+ if ((0 == n_running) || (n_running < n_old))
+ {
+ /* some requests were completed -- handle them */
+ while (NULL != (cmsg = curl_multi_info_read (ctx->multi, &n_completed)))
+ {
+ GNUNET_break (CURLMSG_DONE == cmsg->msg); /* curl only has CURLMSG_DONE */
+ GNUNET_assert (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle,
+ CURLINFO_PRIVATE,
+ (char *) &mint));
+ GNUNET_assert (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle,
+ CURLINFO_RESPONSE_CODE,
+ &resp_code));
+ GNUNET_assert (ctx == mint->ctx); /* did we get the correct one? */
+ if (CURLE_OK == cmsg->data.result)
+ request_succeeded (mint, resp_code);
+ else
+ request_failed (mint, resp_code);
+ }
+ }
+ n_old = n_running;
+ /* reschedule perform() */
+ if (0 != n_old)
+ {
+ FD_ZERO (&fd_rs);
+ FD_ZERO (&fd_ws);
+ GNUNET_assert (CURLM_OK == curl_multi_fdset (ctx->multi,
+ &fd_rs,
+ &fd_ws,
+ NULL,
+ &max_fd));
+ if (-1 == max_fd)
+ {
+ ctx->perform_task = GNUNET_SCHEDULER_add_delayed
+ (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100),
+ &do_perform, ctx);
+ return;
+ }
+ GNUNET_assert (CURLM_OK == curl_multi_timeout (ctx->multi, &timeout));
+ if (-1 == timeout)
+ {
+ timeout = 1000 * 60 * 5;
+ }
+ GNUNET_NETWORK_fdset_zero (&rs);
+ GNUNET_NETWORK_fdset_zero (&ws);
+ GNUNET_NETWORK_fdset_copy_native (&rs, &fd_rs, max_fd + 1);
+ GNUNET_NETWORK_fdset_copy_native (&ws, &fd_ws, max_fd + 1);
+ ctx->perform_task = GNUNET_SCHEDULER_add_select
+ (GNUNET_SCHEDULER_PRIORITY_KEEP,
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, timeout),
+ &rs, &ws,
+ &do_perform, ctx);
+ }
+}
+
+
+static void
+do_perform (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+ struct TALER_MINT_Context *ctx = cls;
+
+ GNUNET_assert (NULL != ctx->perform_task);
+ ctx->perform_task = NULL;
+ perform (ctx);
+}
+
+static void
+perform_now (struct TALER_MINT_Context *ctx)
+{
+ if (NULL != ctx->perform_task)
+ {
+ GNUNET_SCHEDULER_cancel (ctx->perform_task);
+ ctx->perform_task = NULL;
+ }
+ ctx->perform_task = GNUNET_SCHEDULER_add_now (&do_perform, ctx);
+}
+
+
+/* This function gets called by libcurl as soon as there is data received that */
+/* needs to be saved. The size of the data pointed to by ptr is size */
+/* multiplied with nmemb, it will not be zero terminated. Return the number */
+/* of bytes actually taken care of. If that amount differs from the amount passed */
+/* to your function, it'll signal an error to the library. This will abort the */
+/* transfer and return CURLE_WRITE_ERROR. */
+
+/* From 7.18.0, the function can return CURL_WRITEFUNC_PAUSE which then will */
+/* cause writing to this connection to become paused. See */
+/* curl_easy_pause(3) for further details. */
+
+/* This function may be called with zero bytes data if the transferred file is */
+/* empty. */
+
+/* Set this option to NULL to get the internal default function. The internal */
+/* default function will write the data to the FILE * given with */
+/* CURLOPT_WRITEDATA. */
+
+/* Set the userdata argument with the CURLOPT_WRITEDATA option. */
+
+/* The callback function will be passed as much data as possible in all invokes, */
+/* but you cannot possibly make any assumptions. It may be one byte, it may be */
+/* thousands. The maximum amount of body data that can be passed to the write */
+/* callback is defined in the curl.h header file: CURL_MAX_WRITE_SIZE (the usual */
+/* default is 16K). If you however have CURLOPT_HEADER set, which sends */
+/* header data to the write callback, you can get up to */
+/* CURL_MAX_HTTP_HEADER bytes of header data passed into it. This usually */
+/* means 100K. */
+static size_t
+download (char *bufptr, size_t size, size_t nitems, void *cls)
+{
+ struct TALER_MINT_Handle *mint = cls;
+ size_t msize;
+ void *buf;
+
+ if (0 == size * nitems)
+ {
+ /* file is empty */
+ return 0;
+ }
+ msize = size * nitems;
+ mint->buf = GNUNET_realloc (mint->buf, mint->buf_size + msize);
+ buf = mint->buf + mint->buf_size;
+ memcpy (buf, bufptr, msize);
+ mint->buf_size += msize;
+ return msize;
+}
+
+
+/**
+ * Initialise a connection to the mint.
+ *
+ * @param ctx the context
+ * @param hostname the hostname of the mint
+ * @param port the point where the mint's HTTP service is running.
+ * @param mint_key the public key of the mint. This is used to verify the
+ * responses of the mint.
+ * @return the mint handle; NULL upon error
+ */
+struct TALER_MINT_Handle *
+TALER_MINT_connect (struct TALER_MINT_Context *ctx,
+ const char *hostname,
+ uint16_t port,
+ struct GNUNET_CRYPTO_EddsaPublicKey *mint_key)
+{
+ struct TALER_MINT_Handle *mint;
+
+ mint = GNUNET_new (struct TALER_MINT_Handle);
+ mint->ctx = ctx;
+ mint->hostname = GNUNET_strdup (hostname);
+ mint->port = (0 != port) ? port : 80;
+ mint->curl = curl_easy_init ();
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (mint->curl, CURLOPT_SHARE, ctx->share));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (mint->curl, CURLOPT_ERRORBUFFER, mint->emsg));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (mint->curl, CURLOPT_WRITEFUNCTION, &download));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (mint->curl, CURLOPT_WRITEDATA, mint));
+ GNUNET_assert (CURLE_OK == curl_easy_setopt (mint->curl, CURLOPT_PRIVATE, mint));
+ return mint;
+}
+
+/**
+ * Disconnect from the mint
+ *
+ * @param mint the mint handle
+ */
+void
+TALER_MINT_disconnect (struct TALER_MINT_Handle *mint)
+{
+ if (GNUNET_YES == mint->connected)
+ mint_disconnect (mint);
+ curl_easy_cleanup (mint->curl);
+ GNUNET_free (mint->hostname);
+ GNUNET_free (mint);
+}
+
+/**
+ * Get the signing and denomination key of the mint.
+ *
+ * @param mint handle to the mint
+ * @param cb the callback to call with each retrieved denomination key
+ * @param cls closure for the above callback
+ * @param cont_cb the callback to call after completing this asynchronous call
+ * @param cont_cls the closure for the continuation callback
+ * @return a handle to this asynchronous call; NULL upon eror
+ */
+struct TALER_MINT_KeysGetHandle *
+TALER_MINT_keys_get (struct TALER_MINT_Handle *mint,
+ TALER_MINT_KeysGetCallback cb, void *cls,
+ TALER_MINT_ContinuationCallback cont_cb, void *cont_cls)
+{
+ struct TALER_MINT_KeysGetHandle *gh;
+
+ GNUNET_assert (REQUEST_TYPE_NONE == mint->req_type);
+ gh = GNUNET_new (struct TALER_MINT_KeysGetHandle);
+ gh->mint = mint;
+ mint->req_type = REQUEST_TYPE_KEYSGET;
+ mint->req.keys_get = gh;
+ gh->cb = cb;
+ gh->cls = cls;
+ gh->cont_cb = cont_cb;
+ gh->cont_cls = cont_cls;
+ GNUNET_asprintf (&gh->url, "http://%s:%hu/keys", mint->hostname, mint->port);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (mint->curl, CURLOPT_URL, gh->url));
+ if (GNUNET_NO == mint->connected)
+ mint_connect (mint);
+ perform_now (mint->ctx);
+ return gh;
+}
+
+
+/**
+ * Cancel the asynchronous call initiated by TALER_MINT_keys_get(). This
+ * should not be called if either of the @a TALER_MINT_KeysGetCallback or
+ * @a TALER_MINT_ContinuationCallback passed to TALER_MINT_keys_get() have
+ * been called.
+ *
+ * @param get the handle for retrieving the keys
+ */
+void
+TALER_MINT_keys_get_cancel (struct TALER_MINT_KeysGetHandle *get)
+{
+ struct TALER_MINT_Handle *mint = get->mint;
+
+ mint_disconnect (mint);
+ cleanup_keys_get (get);
+}
+
+/**
+ * Submit a deposit permission to the mint and get the mint's response
+ *
+ * @param mint the mint handle
+ * @param cb the callback to call when a reply for this request is available
+ * @param cls closure for the above callback
+ * @param deposit_obj the deposit permission received from the customer along
+ * with the wireformat JSON object
+ * @return a handle for this request; NULL if the JSON object could not be
+ * parsed or is of incorrect format or any other error. In this case,
+ * the callback is not called.
+ */
+struct TALER_MINT_DepositHandle *
+TALER_MINT_deposit_submit_json (struct TALER_MINT_Handle *mint,
+ TALER_MINT_DepositResultCallback cb,
+ void *cls,
+ json_t *deposit_obj)
+{
+ struct TALER_MINT_DepositHandle *dh;
+
+ GNUNET_assert (REQUEST_TYPE_NONE == mint->req_type);
+ dh = GNUNET_new (struct TALER_MINT_DepositHandle);
+ dh->mint = mint;
+ mint->req_type = REQUEST_TYPE_DEPOSIT;
+ mint->req.deposit = dh;
+ dh->cb = cb;
+ dh->cls = cls;
+ GNUNET_asprintf (&dh->url, "http://%s:%hu/deposit", mint->hostname, mint->port);
+ GNUNET_assert (NULL != (dh->json_enc = json_dumps (deposit_obj, JSON_COMPACT)));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (mint->curl, CURLOPT_URL, dh->url));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (mint->curl, CURLOPT_POSTFIELDS,
+ dh->json_enc));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (mint->curl, CURLOPT_POSTFIELDSIZE,
+ strlen (dh->json_enc)));
+ GNUNET_assert (NULL != (dh->headers =
+ curl_slist_append (dh->headers, "Content-Type: application/json")));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (mint->curl, CURLOPT_HTTPHEADER, dh->headers));
+ if (GNUNET_NO == mint->connected)
+ mint_connect (mint);
+ perform_now (mint->ctx);
+ return dh;
+}
+
+
+/**
+ * Cancel a deposit permission request. This function cannot be used on a
+ * request handle if a response is already served for it.
+ *
+ * @param the deposit permission request handle
+ */
+void
+TALER_MINT_deposit_submit_cancel (struct TALER_MINT_DepositHandle *deposit)
+{
+ struct TALER_MINT_Handle *mint = deposit->mint;
+
+ mint_disconnect (mint);
+ cleanup_deposit (deposit);
+}
+
+
+/**
+ * Initialise this library. This function should be called before using any of
+ * the following functions.
+ *
+ * @return library context
+ */
+struct TALER_MINT_Context *
+TALER_MINT_init ()
+{
+ struct TALER_MINT_Context *ctx;
+ CURLM *multi;
+ CURLSH *share;
+
+ if (fail)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "cURL was not initialised properly\n");
+ return NULL;
+ }
+ if (NULL == (multi = curl_multi_init ()))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Cannot create a cURL multi handle\n");
+ return NULL;
+ }
+ if (NULL == (share = curl_share_init ()))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Cannot create a cURL share handle\n");
+ return NULL;
+ }
+ ctx = GNUNET_new (struct TALER_MINT_Context);
+ ctx->multi = multi;
+ ctx->share = share;
+ return ctx;
+}
+
+
+/**
+ * Cleanup library initialisation resources. This function should be called
+ * after using this library to cleanup the resources occupied during library's
+ * initialisation.
+ *
+ * @param ctx the library context
+ */
+void
+TALER_MINT_cleanup (struct TALER_MINT_Context *ctx)
+{
+ curl_share_cleanup (ctx->share);
+ curl_multi_cleanup (ctx->multi);
+ if (NULL != ctx->perform_task)
+ {
+ GNUNET_break (0); /* investigate why this happens */
+ GNUNET_SCHEDULER_cancel (ctx->perform_task);
+ }
+ GNUNET_free (ctx);
+}
+
+
+__attribute__ ((constructor))
+void
+TALER_MINT_constructor__ (void)
+{
+ CURLcode ret;
+ if (CURLE_OK != (ret = curl_global_init (CURL_GLOBAL_DEFAULT)))
+ {
+ CURL_STRERROR (GNUNET_ERROR_TYPE_ERROR, "curl_global_init", ret);
+ fail = 1;
+ }
+}
+
+__attribute__ ((destructor))
+void
+TALER_MINT_destructor__ (void)
+{
+ if (fail)
+ return;
+ curl_global_cleanup ();
+}
diff --git a/src/mint/mint_common.c b/src/mint/mint_common.c
new file mode 100644
index 000000000..4afbf072b
--- /dev/null
+++ b/src/mint/mint_common.c
@@ -0,0 +1,283 @@
+/*
+ This file is part of TALER
+ (C) 2014 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_common.c
+ * @brief Common functionality for the mint
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Sree Harsha Totakura
+ */
+
+#include "platform.h"
+#include "mint.h"
+
+struct SignkeysIterateContext
+{
+ TALER_MINT_SignkeyIterator it;
+ void *it_cls;
+};
+
+
+struct DenomkeysIterateContext
+{
+ const char *alias;
+ TALER_MINT_DenomkeyIterator it;
+ void *it_cls;
+};
+
+
+static int
+signkeys_iterate_dir_iter (void *cls,
+ const char *filename)
+{
+
+ struct SignkeysIterateContext *skc = cls;
+ ssize_t nread;
+ struct TALER_MINT_SignKeyIssue issue;
+ nread = GNUNET_DISK_fn_read (filename,
+ &issue,
+ sizeof (struct TALER_MINT_SignKeyIssue));
+ if (nread != sizeof (struct TALER_MINT_SignKeyIssue))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Invalid signkey file: '%s'\n", filename);
+ return GNUNET_OK;
+ }
+ return skc->it (skc->it_cls, &issue);
+}
+
+
+int
+TALER_MINT_signkeys_iterate (const char *mint_base_dir,
+ TALER_MINT_SignkeyIterator it, void *cls)
+{
+ char *signkey_dir;
+ size_t len;
+ struct SignkeysIterateContext skc;
+
+ len = GNUNET_asprintf (&signkey_dir, ("%s" DIR_SEPARATOR_STR DIR_SIGNKEYS), mint_base_dir);
+ GNUNET_assert (len > 0);
+
+ skc.it = it;
+ skc.it_cls = cls;
+
+ return GNUNET_DISK_directory_scan (signkey_dir, &signkeys_iterate_dir_iter, &skc);
+}
+
+
+/**
+ * Import a denomination key from the given file
+ *
+ * @param filename the file to import the key from
+ * @param dki pointer to return the imported denomination key
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+ */
+int
+TALER_MINT_read_denom_key (const char *filename,
+ struct TALER_MINT_DenomKeyIssue *dki)
+{
+ uint64_t size;
+ size_t offset;
+ void *data;
+ struct TALER_RSA_PrivateKey *priv;
+ int ret;
+
+ ret = GNUNET_SYSERR;
+ data = NULL;
+ offset = sizeof (struct TALER_MINT_DenomKeyIssue)
+ - offsetof (struct TALER_MINT_DenomKeyIssue, signature);
+ if (GNUNET_OK != GNUNET_DISK_file_size (filename,
+ &size,
+ GNUNET_YES,
+ GNUNET_YES))
+ goto cleanup;
+ if (size <= offset)
+ {
+ GNUNET_break (0);
+ goto cleanup;
+ }
+ data = GNUNET_malloc (size);
+ if (size != GNUNET_DISK_fn_read (filename,
+ data,
+ size))
+ goto cleanup;
+ if (NULL == (priv = TALER_RSA_decode_key (data + offset, size - offset)))
+ goto cleanup;
+ dki->denom_priv = priv;
+ (void) memcpy (&dki->signature, data, offset);
+ ret = GNUNET_OK;
+
+ cleanup:
+ GNUNET_free_non_null (data);
+ return ret;
+}
+
+
+/**
+ * Exports a denomination key to the given file
+ *
+ * @param filename the file where to write the denomination key
+ * @param dki the denomination key
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure.
+ */
+int
+TALER_MINT_write_denom_key (const char *filename,
+ const struct TALER_MINT_DenomKeyIssue *dki)
+{
+ struct TALER_RSA_PrivateKeyBinaryEncoded *priv_enc;
+ struct GNUNET_DISK_FileHandle *fh;
+ ssize_t wrote;
+ size_t wsize;
+ int ret;
+
+ fh = NULL;
+ priv_enc = NULL;
+ ret = GNUNET_SYSERR;
+ if (NULL == (fh = GNUNET_DISK_file_open
+ (filename,
+ GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_TRUNCATE,
+ GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE)))
+ goto cleanup;
+ if (NULL == (priv_enc = TALER_RSA_encode_key (dki->denom_priv)))
+ goto cleanup;
+ wsize = sizeof (struct TALER_MINT_DenomKeyIssue)
+ - offsetof (struct TALER_MINT_DenomKeyIssue, signature);
+ if (GNUNET_SYSERR == (wrote = GNUNET_DISK_file_write (fh,
+ &dki->signature,
+ wsize)))
+ goto cleanup;
+ if (wrote != wsize)
+ goto cleanup;
+ wsize = ntohs (priv_enc->len);
+ if (GNUNET_SYSERR == (wrote = GNUNET_DISK_file_write (fh,
+ priv_enc,
+ wsize)))
+ goto cleanup;
+ if (wrote != wsize)
+ goto cleanup;
+ ret = GNUNET_OK;
+ cleanup:
+ GNUNET_free_non_null (priv_enc);
+ if (NULL != fh)
+ (void) GNUNET_DISK_file_close (fh);
+ return ret;
+}
+
+
+static int
+denomkeys_iterate_keydir_iter (void *cls,
+ const char *filename)
+{
+
+ struct DenomkeysIterateContext *dic = cls;
+ struct TALER_MINT_DenomKeyIssue issue;
+
+ if (GNUNET_OK != TALER_MINT_read_denom_key (filename, &issue))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Invalid denomkey file: '%s'\n", filename);
+ return GNUNET_OK;
+ }
+ return dic->it (dic->it_cls, dic->alias, &issue);
+}
+
+
+static int
+denomkeys_iterate_topdir_iter (void *cls,
+ const char *filename)
+{
+
+ struct DenomkeysIterateContext *dic = cls;
+ dic->alias = GNUNET_STRINGS_get_short_name (filename);
+
+ // FIXME: differentiate between error case and normal iteration abortion
+ if (0 > GNUNET_DISK_directory_scan (filename, &denomkeys_iterate_keydir_iter, dic))
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+}
+
+
+int
+TALER_MINT_denomkeys_iterate (const char *mint_base_dir,
+ TALER_MINT_DenomkeyIterator it, void *cls)
+{
+ char *dir;
+ size_t len;
+ struct DenomkeysIterateContext dic;
+ len = GNUNET_asprintf (&dir, ("%s" DIR_SEPARATOR_STR DIR_DENOMKEYS),
+ mint_base_dir);
+ GNUNET_assert (len > 0);
+
+ dic.it = it;
+ dic.it_cls = cls;
+
+ // scan over alias dirs
+ return GNUNET_DISK_directory_scan (dir, &denomkeys_iterate_topdir_iter, &dic);
+}
+
+
+struct GNUNET_CONFIGURATION_Handle *
+TALER_MINT_config_load (const char *mint_base_dir)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+ char *cfg_dir;
+ int res;
+
+ res = GNUNET_asprintf (&cfg_dir, "%s" DIR_SEPARATOR_STR "config", mint_base_dir);
+ GNUNET_assert (res > 0);
+
+ cfg = GNUNET_CONFIGURATION_create ();
+ res = GNUNET_CONFIGURATION_load_from (cfg, cfg_dir);
+ GNUNET_free (cfg_dir);
+ if (GNUNET_OK != res)
+ return NULL;
+ return cfg;
+}
+
+int
+TALER_TALER_DB_extract_amount_nbo (PGresult *result, unsigned int row,
+ int indices[3], struct TALER_AmountNBO *denom_nbo)
+{
+ if ((indices[0] < 0) || (indices[1] < 0) || (indices[2] < 0))
+ return GNUNET_NO;
+ if (sizeof (uint32_t) != PQgetlength (result, row, indices[0]))
+ return GNUNET_SYSERR;
+ if (sizeof (uint32_t) != PQgetlength (result, row, indices[1]))
+ return GNUNET_SYSERR;
+ if (PQgetlength (result, row, indices[2]) > TALER_CURRENCY_LEN)
+ return GNUNET_SYSERR;
+ denom_nbo->value = *(uint32_t *) PQgetvalue (result, row, indices[0]);
+ denom_nbo->fraction = *(uint32_t *) PQgetvalue (result, row, indices[1]);
+ memset (denom_nbo->currency, 0, TALER_CURRENCY_LEN);
+ memcpy (denom_nbo->currency, PQgetvalue (result, row, indices[2]), PQgetlength (result, row, indices[2]));
+ return GNUNET_OK;
+}
+
+
+int
+TALER_TALER_DB_extract_amount (PGresult *result, unsigned int row,
+ int indices[3], struct TALER_Amount *denom)
+{
+ struct TALER_AmountNBO denom_nbo;
+ int res;
+
+ res = TALER_TALER_DB_extract_amount_nbo (result, row, indices, &denom_nbo);
+ if (GNUNET_OK != res)
+ return res;
+ *denom = TALER_amount_ntoh (denom_nbo);
+ return GNUNET_OK;
+}
+
+/* end of mint_common.c */
diff --git a/src/mint/mint_db.c b/src/mint/mint_db.c
new file mode 100644
index 000000000..6dc025877
--- /dev/null
+++ b/src/mint/mint_db.c
@@ -0,0 +1,1838 @@
+/*
+ This file is part of TALER
+ (C) 2014 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_db.c
+ * @brief Database access for the mint
+ * @author Florian Dold
+ */
+#include "platform.h"
+#include "taler_db_lib.h"
+#include "taler_signatures.h"
+#include "mint_db.h"
+#include "mint.h"
+#include <pthread.h>
+
+/**
+ * Thread-local database connection.
+ * Contains a pointer to PGconn or NULL.
+ */
+static pthread_key_t db_conn_threadlocal;
+
+
+/**
+ * Database connection string, as read from
+ * the configuration.
+ */
+static char *TALER_MINT_db_connection_cfg_str;
+
+
+#define break_db_err(result) do { \
+ GNUNET_break(0); \
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s\n", PQresultErrorMessage (result)); \
+ } while (0)
+
+/**
+ * Shorthand for exit jumps.
+ */
+#define EXITIF(cond) \
+ do { \
+ if (cond) { GNUNET_break (0); goto EXITIF_exit; } \
+ } while (0)
+
+int
+TALER_MINT_DB_get_collectable_blindcoin (PGconn *db_conn,
+ struct TALER_RSA_BlindedSignaturePurpose *blind_ev,
+ struct CollectableBlindcoin *collectable)
+{
+ PGresult *result;
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR (blind_ev),
+ TALER_DB_QUERY_PARAM_END
+ };
+ result = TALER_DB_exec_prepared (db_conn, "get_collectable_blindcoins", params);
+
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result));
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 == PQntuples (result))
+ {
+ PQclear (result);
+ return GNUNET_NO;
+ }
+
+ struct TALER_DB_ResultSpec rs[] = {
+ TALER_DB_RESULT_SPEC("blind_ev_sig", &collectable->ev_sig),
+ TALER_DB_RESULT_SPEC("denom_pub", &collectable->denom_pub),
+ TALER_DB_RESULT_SPEC("reserve_sig", &collectable->reserve_sig),
+ TALER_DB_RESULT_SPEC("reserve_pub", &collectable->reserve_pub),
+ TALER_DB_RESULT_SPEC_END
+ };
+
+ if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ (void) memcpy (&collectable->ev, blind_ev, sizeof (struct TALER_RSA_BlindedSignaturePurpose));
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+int
+TALER_MINT_DB_insert_collectable_blindcoin (PGconn *db_conn,
+ const struct CollectableBlindcoin *collectable)
+{
+ PGresult *result;
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR (&collectable->ev),
+ TALER_DB_QUERY_PARAM_PTR (&collectable->ev_sig),
+ TALER_DB_QUERY_PARAM_PTR (&collectable->denom_pub),
+ TALER_DB_QUERY_PARAM_PTR (&collectable->reserve_pub),
+ TALER_DB_QUERY_PARAM_PTR (&collectable->reserve_sig),
+ TALER_DB_QUERY_PARAM_END
+ };
+ result = TALER_DB_exec_prepared (db_conn, "insert_collectable_blindcoins", params);
+
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result));
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 != strcmp ("1", PQcmdTuples (result)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Insert failed (updated '%s' tupes instead of '1')\n",
+ PQcmdTuples (result));
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+int
+TALER_MINT_DB_get_reserve (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub,
+ struct Reserve *reserve)
+{
+ PGresult *result;
+ int res;
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR (reserve_pub),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ result = TALER_DB_exec_prepared (db_conn, "get_reserve", params);
+
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result));
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 == PQntuples (result))
+ {
+ PQclear (result);
+ return GNUNET_NO;
+ }
+
+ reserve->reserve_pub = *reserve_pub;
+
+ struct TALER_DB_ResultSpec rs[] = {
+ TALER_DB_RESULT_SPEC("status_sig", &reserve->status_sig),
+ TALER_DB_RESULT_SPEC("status_sign_pub", &reserve->status_sign_pub),
+ TALER_DB_RESULT_SPEC_END
+ };
+
+ res = TALER_DB_extract_result (result, rs, 0);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ {
+ int fnums[] = {
+ PQfnumber (result, "balance_value"),
+ PQfnumber (result, "balance_fraction"),
+ PQfnumber (result, "balance_currency"),
+ };
+ if (GNUNET_OK != TALER_TALER_DB_extract_amount_nbo (result, 0, fnums, &reserve->balance))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ /* FIXME: Add expiration?? */
+
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+/* If fresh is GNUNET_YES, set some fields to NULL as they are not actually valid */
+int
+TALER_MINT_DB_update_reserve (PGconn *db_conn,
+ const struct Reserve *reserve,
+ int fresh)
+{
+ PGresult *result;
+ uint64_t stamp_sec;
+
+ stamp_sec = GNUNET_ntohll (GNUNET_TIME_absolute_ntoh (reserve->expiration).abs_value_us / 1000000);
+
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR (&reserve->reserve_pub),
+ TALER_DB_QUERY_PARAM_PTR (&reserve->balance.value),
+ TALER_DB_QUERY_PARAM_PTR (&reserve->balance.fraction),
+ TALER_DB_QUERY_PARAM_PTR_SIZED (&reserve->balance.currency,
+ strlen (reserve->balance.currency)),
+ TALER_DB_QUERY_PARAM_PTR (&reserve->status_sig),
+ TALER_DB_QUERY_PARAM_PTR (&reserve->status_sign_pub),
+ TALER_DB_QUERY_PARAM_PTR (&stamp_sec),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ /* set some fields to NULL if they are not actually valid */
+
+ if (GNUNET_YES == fresh)
+ {
+ unsigned i;
+ for (i = 4; i <= 7; i += 1)
+ {
+ params[i].data = NULL;
+ params[i].size = 0;
+ }
+ }
+
+ result = TALER_DB_exec_prepared (db_conn, "update_reserve", params);
+
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result));
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 != strcmp ("1", PQcmdTuples (result)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Update failed (updated '%s' tupes instead of '1')\n",
+ PQcmdTuples (result));
+ return GNUNET_SYSERR;
+ }
+
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+
+int
+TALER_MINT_DB_prepare (PGconn *db_conn)
+{
+ PGresult *result;
+
+ result = PQprepare (db_conn, "get_reserve",
+ "SELECT "
+ " balance_value, balance_fraction, balance_currency "
+ ",expiration_date, blind_session_pub, blind_session_priv"
+ ",status_sig, status_sign_pub "
+ "FROM reserves "
+ "WHERE reserve_pub=$1 "
+ "LIMIT 1; ",
+ 1, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "update_reserve",
+ "UPDATE reserves "
+ "SET"
+ " balance_value=$2 "
+ ",balance_fraction=$3 "
+ ",balance_currency=$4 "
+ ",status_sig=$5 "
+ ",status_sign_pub=$6 "
+ ",expiration_date=$7 "
+ "WHERE reserve_pub=$1 ",
+ 9, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+ result = PQprepare (db_conn, "insert_collectable_blindcoins",
+ "INSERT INTO collectable_blindcoins ( "
+ " blind_ev, blind_ev_sig "
+ ",denom_pub, reserve_pub, reserve_sig) "
+ "VALUES ($1, $2, $3, $4, $5)",
+ 6, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "get_collectable_blindcoins",
+ "SELECT "
+ "blind_ev_sig, denom_pub, reserve_sig, reserve_pub "
+ "FROM collectable_blindcoins "
+ "WHERE blind_ev = $1",
+ 1, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "insert_reserve_order",
+ "SELECT "
+ " blind_ev, blind_ev_sig, denom_pub, reserve_sig, reserve_pub "
+ "FROM collectable_blindcoins "
+ "WHERE blind_session_pub = $1",
+ 1, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ /* FIXME: does it make sense to store these computed values in the DB? */
+ result = PQprepare (db_conn, "get_refresh_session",
+ "SELECT "
+ " (SELECT count(*) FROM refresh_melt WHERE session_pub = $1)::INT2 as num_oldcoins "
+ ",(SELECT count(*) FROM refresh_blind_session_keys "
+ " WHERE session_pub = $1 and cnc_index = 0)::INT2 as num_newcoins "
+ ",(SELECT count(*) FROM refresh_blind_session_keys "
+ " WHERE session_pub = $1 and newcoin_index = 0)::INT2 as kappa "
+ ",noreveal_index"
+ ",session_commit_sig "
+ ",reveal_ok "
+ "FROM refresh_sessions "
+ "WHERE session_pub = $1",
+ 1, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "get_known_coin",
+ "SELECT "
+ " coin_pub, denom_pub, denom_sig "
+ ",expended_value, expended_fraction, expended_currency "
+ ",refresh_session_pub "
+ "FROM known_coins "
+ "WHERE coin_pub = $1",
+ 1, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "update_known_coin",
+ "UPDATE known_coins "
+ "SET "
+ " denom_pub = $2 "
+ ",denom_sig = $3 "
+ ",expended_value = $4 "
+ ",expended_fraction = $5 "
+ ",expended_currency = $6 "
+ ",refresh_session_pub = $7 "
+ "WHERE "
+ " coin_pub = $1 ",
+ 7, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "insert_known_coin",
+ "INSERT INTO known_coins ("
+ " coin_pub"
+ ",denom_pub"
+ ",denom_sig"
+ ",expended_value"
+ ",expended_fraction"
+ ",expended_currency"
+ ",refresh_session_pub"
+ ")"
+ "VALUES ($1,$2,$3,$4,$5,$6,$7)",
+ 7, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "get_refresh_commit_link",
+ "SELECT "
+ " transfer_pub "
+ ",link_secret_enc "
+ "FROM refresh_commit_link "
+ "WHERE session_pub = $1 AND cnc_index = $2 AND oldcoin_index = $3",
+ 3, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "get_refresh_commit_coin",
+ "SELECT "
+ " link_vector_enc "
+ ",coin_ev "
+ "FROM refresh_commit_coin "
+ "WHERE session_pub = $1 AND cnc_index = $2 AND newcoin_index = $3",
+ 3, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "insert_refresh_order",
+ "INSERT INTO refresh_order ( "
+ " newcoin_index "
+ ",session_pub "
+ ",denom_pub "
+ ") "
+ "VALUES ($1, $2, $3) ",
+ 3, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "insert_refresh_melt",
+ "INSERT INTO refresh_melt ( "
+ " session_pub "
+ ",oldcoin_index "
+ ",coin_pub "
+ ",denom_pub "
+ ") "
+ "VALUES ($1, $2, $3, $4) ",
+ 3, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "get_refresh_order",
+ "SELECT denom_pub "
+ "FROM refresh_order "
+ "WHERE session_pub = $1 AND newcoin_index = $2",
+ 2, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "get_refresh_collectable",
+ "SELECT ev_sig "
+ "FROM refresh_collectable "
+ "WHERE session_pub = $1 AND newcoin_index = $2",
+ 2, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "get_refresh_melt",
+ "SELECT coin_pub "
+ "FROM refresh_melt "
+ "WHERE session_pub = $1 AND oldcoin_index = $2",
+ 2, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "insert_refresh_session",
+ "INSERT INTO refresh_sessions ( "
+ " session_pub "
+ ",noreveal_index "
+ ") "
+ "VALUES ($1, $2) ",
+ 2, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "insert_refresh_commit_link",
+ "INSERT INTO refresh_commit_link ( "
+ " session_pub "
+ ",transfer_pub "
+ ",cnc_index "
+ ",oldcoin_index "
+ ",link_secret_enc "
+ ") "
+ "VALUES ($1, $2, $3, $4, $5) ",
+ 5, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "insert_refresh_commit_coin",
+ "INSERT INTO refresh_commit_coin ( "
+ " session_pub "
+ ",coin_ev "
+ ",cnc_index "
+ ",newcoin_index "
+ ",link_vector_enc "
+ ") "
+ "VALUES ($1, $2, $3, $4, $5) ",
+ 5, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "insert_refresh_collectable",
+ "INSERT INTO refresh_collectable ( "
+ " session_pub "
+ ",newcoin_index "
+ ",ev_sig "
+ ") "
+ "VALUES ($1, $2, $3) ",
+ 3, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "set_reveal_ok",
+ "UPDATE refresh_sessions "
+ "SET reveal_ok = TRUE "
+ "WHERE session_pub = $1 ",
+ 1, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "get_link",
+ "SELECT link_vector_enc, ro.denom_pub, ev_sig "
+ "FROM refresh_melt rm "
+ " JOIN refresh_order ro USING (session_pub) "
+ " JOIN refresh_commit_coin rcc USING (session_pub) "
+ " JOIN refresh_sessions rs USING (session_pub) "
+ " JOIN refresh_collectable rc USING (session_pub) "
+ "WHERE rm.coin_pub = $1 "
+ "AND ro.newcoin_index = rcc.newcoin_index "
+ "AND ro.newcoin_index = rc.newcoin_index "
+ "AND rcc.cnc_index = rs.noreveal_index % ( "
+ " SELECT count(*) FROM refresh_commit_coin rcc2 "
+ " WHERE rcc2.newcoin_index = 0 AND rcc2.session_pub = rs.session_pub "
+ " ) ",
+ 1, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQprepare (db_conn, "get_transfer",
+ "SELECT transfer_pub, link_secret_enc "
+ "FROM refresh_melt rm "
+ " JOIN refresh_commit_link rcl USING (session_pub) "
+ " JOIN refresh_sessions rs USING (session_pub) "
+ "WHERE rm.coin_pub = $1 "
+ "AND rm.oldcoin_index = rcl.oldcoin_index "
+ "AND rcl.cnc_index = rs.noreveal_index % ( "
+ " SELECT count(*) FROM refresh_commit_coin rcc2 "
+ " WHERE newcoin_index = 0 AND rcc2.session_pub = rm.session_pub "
+ " ) ",
+ 1, NULL);
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ if (GNUNET_OK != TALER_MINT_DB_prepare_deposits (db_conn))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Roll back the current transaction of a database connection.
+ *
+ * @param db_conn the database connection
+ * @return GNUNET_OK on success
+ */
+int
+TALER_MINT_DB_rollback (PGconn *db_conn)
+{
+ PGresult *result;
+
+ result = PQexec(db_conn, "ROLLBACK");
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ PQclear (result);
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Roll back the current transaction of a database connection.
+ *
+ * @param db_conn the database connection
+ * @return GNUNET_OK on success
+ */
+int
+TALER_MINT_DB_commit (PGconn *db_conn)
+{
+ PGresult *result;
+
+ result = PQexec(db_conn, "COMMIT");
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ PQclear (result);
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Start a transaction.
+ *
+ * @param db_conn the database connection
+ * @return GNUNET_OK on success
+ */
+int
+TALER_MINT_DB_transaction (PGconn *db_conn)
+{
+ PGresult *result;
+
+ result = PQexec(db_conn, "BEGIN");
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Can't start transaction: %s\n", PQresultErrorMessage (result));
+ PQclear (result);
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Insert a refresh order into the database.
+ */
+int
+TALER_MINT_DB_insert_refresh_order (PGconn *db_conn,
+ uint16_t newcoin_index,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
+ const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub)
+{
+ uint16_t newcoin_index_nbo = htons (newcoin_index);
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo),
+ TALER_DB_QUERY_PARAM_PTR(session_pub),
+ TALER_DB_QUERY_PARAM_PTR(denom_pub),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_order", params);
+
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 != strcmp ("1", PQcmdTuples (result)))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+int
+TALER_MINT_DB_get_refresh_session (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub,
+ struct RefreshSession *session)
+{
+ int res;
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(refresh_session_pub),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_session", params);
+
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result));
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 == PQntuples (result))
+ return GNUNET_NO;
+
+ GNUNET_assert (1 == PQntuples (result));
+
+ /* We're done if the caller is only interested in
+ * whether the session exists or not */
+
+ if (NULL == session)
+ return GNUNET_YES;
+
+ memset (session, 0, sizeof (struct RefreshSession));
+
+ session->session_pub = *refresh_session_pub;
+
+ struct TALER_DB_ResultSpec rs[] = {
+ TALER_DB_RESULT_SPEC("num_oldcoins", &session->num_oldcoins),
+ TALER_DB_RESULT_SPEC("num_newcoins", &session->num_newcoins),
+ TALER_DB_RESULT_SPEC("kappa", &session->kappa),
+ TALER_DB_RESULT_SPEC("noreveal_index", &session->noreveal_index),
+ TALER_DB_RESULT_SPEC("reveal_ok", &session->reveal_ok),
+ TALER_DB_RESULT_SPEC_END
+ };
+
+ res = TALER_DB_extract_result (result, rs, 0);
+
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (TALER_DB_field_isnull (result, 0, "session_commit_sig"))
+ session->has_commit_sig = GNUNET_NO;
+ else
+ session->has_commit_sig = GNUNET_YES;
+
+ session->num_oldcoins = ntohs (session->num_oldcoins);
+ session->num_newcoins = ntohs (session->num_newcoins);
+ session->kappa = ntohs (session->kappa);
+ session->noreveal_index = ntohs (session->noreveal_index);
+
+ PQclear (result);
+ return GNUNET_YES;
+}
+
+
+int
+TALER_MINT_DB_get_known_coin (PGconn *db_conn, struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub,
+ struct KnownCoin *known_coin)
+{
+ int res;
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(coin_pub),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "get_known_coin", params);
+
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed: %s\n", PQresultErrorMessage (result));
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 == PQntuples (result))
+ return GNUNET_NO;
+
+ GNUNET_assert (1 == PQntuples (result));
+
+ /* extract basic information about the known coin */
+
+ {
+ struct TALER_DB_ResultSpec rs[] = {
+ TALER_DB_RESULT_SPEC("coin_pub", &known_coin->public_info.coin_pub),
+ TALER_DB_RESULT_SPEC("denom_pub", &known_coin->public_info.denom_pub),
+ TALER_DB_RESULT_SPEC("denom_sig", &known_coin->public_info.denom_sig),
+ TALER_DB_RESULT_SPEC_END
+ };
+
+ if (GNUNET_OK != (res = TALER_DB_extract_result (result, rs, 0)))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ /* extract the expended amount of the coin */
+
+ if (GNUNET_OK != TALER_DB_extract_amount (result, 0,
+ "expended_value",
+ "expended_fraction",
+ "expended_currency",
+ &known_coin->expended_balance))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ /* extract the refresh session of the coin or mark it as missing */
+
+ {
+ struct TALER_DB_ResultSpec rs[] = {
+ TALER_DB_RESULT_SPEC("refresh_session_pub", &known_coin->refresh_session_pub),
+ TALER_DB_RESULT_SPEC_END
+ };
+
+ if (GNUNET_SYSERR == (res = TALER_DB_extract_result (result, rs, 0)))
+ {
+ GNUNET_break (0);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_NO == res)
+ {
+ known_coin->is_refreshed = GNUNET_NO;
+ memset (&known_coin->refresh_session_pub, 0, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
+ }
+ else
+ {
+ known_coin->is_refreshed = GNUNET_YES;
+ }
+ }
+
+ PQclear (result);
+ return GNUNET_YES;
+}
+
+
+int
+TALER_MINT_DB_create_refresh_session (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub)
+{
+ uint16_t noreveal_index;
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(session_pub),
+ TALER_DB_QUERY_PARAM_PTR(&noreveal_index),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ noreveal_index = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 1<<15);
+ noreveal_index = htonl (noreveal_index);
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_session", params);
+
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+int
+TALER_MINT_DB_set_commit_signature (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
+ const struct GNUNET_CRYPTO_EddsaSignature *commit_sig)
+{
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+}
+
+
+int
+TALER_MINT_DB_set_reveal_ok (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub)
+{
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(session_pub),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "set_reveal_ok", params);
+
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 != strcmp ("1", PQcmdTuples (result)))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+int
+TALER_MINT_DB_update_known_coin (PGconn *db_conn,
+ const struct KnownCoin *known_coin)
+{
+ struct TALER_AmountNBO expended_nbo = TALER_amount_hton (known_coin->expended_balance);
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.coin_pub),
+ TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.denom_pub),
+ TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.denom_sig),
+ TALER_DB_QUERY_PARAM_PTR(&expended_nbo.value),
+ TALER_DB_QUERY_PARAM_PTR(&expended_nbo.fraction),
+ TALER_DB_QUERY_PARAM_PTR_SIZED(expended_nbo.currency, strlen (expended_nbo.currency)),
+ TALER_DB_QUERY_PARAM_PTR(&known_coin->refresh_session_pub),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ if (GNUNET_NO == known_coin->is_refreshed)
+ {
+ // Mind the magic index!
+ params[6].data = NULL;
+ params[6].size = 0;
+ }
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "update_known_coin", params);
+
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 != strcmp ("1", PQcmdTuples (result)))
+ {
+ PQclear (result);
+ // return 'no' here (don't fail) so that we can
+ // insert if update fails (=> "upsert")
+ return GNUNET_NO;
+ }
+
+ PQclear (result);
+ return GNUNET_YES;
+}
+
+int
+TALER_MINT_DB_insert_known_coin (PGconn *db_conn,
+ const struct KnownCoin *known_coin)
+{
+ struct TALER_AmountNBO expended_nbo = TALER_amount_hton (known_coin->expended_balance);
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.coin_pub),
+ TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.denom_pub),
+ TALER_DB_QUERY_PARAM_PTR(&known_coin->public_info.denom_sig),
+ TALER_DB_QUERY_PARAM_PTR(&expended_nbo.value),
+ TALER_DB_QUERY_PARAM_PTR(&expended_nbo.fraction),
+ TALER_DB_QUERY_PARAM_PTR_SIZED(&expended_nbo.currency, strlen (expended_nbo.currency)),
+ TALER_DB_QUERY_PARAM_PTR(&known_coin->refresh_session_pub),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ if (GNUNET_NO == known_coin->is_refreshed)
+ {
+ // Mind the magic index!
+ params[6].data = NULL;
+ params[6].size = 0;
+ }
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_known_coin", params);
+
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 != strcmp ("1", PQcmdTuples (result)))
+ {
+ PQclear (result);
+ // return 'no' here (don't fail) so that we can
+ // update if insert fails (=> "upsert")
+ return GNUNET_NO;
+ }
+
+ PQclear (result);
+ return GNUNET_YES;
+}
+
+
+int
+TALER_MINT_DB_upsert_known_coin (PGconn *db_conn, struct KnownCoin *known_coin)
+{
+ int ret;
+ ret = TALER_MINT_DB_update_known_coin (db_conn, known_coin);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_YES == ret)
+ return GNUNET_YES;
+ return TALER_MINT_DB_insert_known_coin (db_conn, known_coin);
+}
+
+
+int
+TALER_MINT_DB_insert_refresh_commit_link (PGconn *db_conn, struct RefreshCommitLink *commit_link)
+{
+ uint16_t cnc_index_nbo = htons (commit_link->cnc_index);
+ uint16_t oldcoin_index_nbo = htons (commit_link->oldcoin_index);
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(&commit_link->session_pub),
+ TALER_DB_QUERY_PARAM_PTR(&commit_link->transfer_pub),
+ TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo),
+ TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo),
+ TALER_DB_QUERY_PARAM_PTR_SIZED(&commit_link->shared_secret_enc, sizeof (struct GNUNET_HashCode)),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_commit_link", params);
+
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 != strcmp ("1", PQcmdTuples (result)))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+int
+TALER_MINT_DB_insert_refresh_commit_coin (PGconn *db_conn, struct RefreshCommitCoin *commit_coin)
+{
+ uint16_t cnc_index_nbo = htons (commit_coin->cnc_index);
+ uint16_t newcoin_index_nbo = htons (commit_coin->newcoin_index);
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(&commit_coin->session_pub),
+ TALER_DB_QUERY_PARAM_PTR(&commit_coin->coin_ev),
+ TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo),
+ TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo),
+ TALER_DB_QUERY_PARAM_PTR_SIZED(&commit_coin->link_enc, sizeof (struct LinkData)),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_commit_coin", params);
+
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 != strcmp ("1", PQcmdTuples (result)))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+int
+TALER_MINT_DB_get_refresh_commit_link (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub,
+ int cnc_index, int oldcoin_index,
+ struct RefreshCommitLink *cc)
+{
+ uint16_t cnc_index_nbo = htons (cnc_index);
+ uint16_t oldcoin_index_nbo = htons (oldcoin_index);
+
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(refresh_session_pub),
+ TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo),
+ TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ cc->cnc_index = cnc_index;
+ cc->oldcoin_index = oldcoin_index;
+ cc->session_pub = *refresh_session_pub;
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_commit_link", params);
+
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 == PQntuples (result))
+ {
+ PQclear (result);
+ return GNUNET_NO;
+ }
+
+ struct TALER_DB_ResultSpec rs[] = {
+ TALER_DB_RESULT_SPEC("transfer_pub", &cc->transfer_pub),
+ TALER_DB_RESULT_SPEC_SIZED("link_secret_enc", &cc->shared_secret_enc,
+ TALER_REFRESH_SHARED_SECRET_LENGTH),
+ TALER_DB_RESULT_SPEC_END
+ };
+
+ if (GNUNET_YES != TALER_DB_extract_result (result, rs, 0))
+ {
+ PQclear (result);
+ GNUNET_free (cc);
+ return GNUNET_SYSERR;
+ }
+
+ PQclear (result);
+ return GNUNET_YES;
+}
+
+
+int
+TALER_MINT_DB_get_refresh_commit_coin (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub,
+ int cnc_index, int newcoin_index,
+ struct RefreshCommitCoin *cc)
+{
+ uint16_t cnc_index_nbo = htons (cnc_index);
+ uint16_t newcoin_index_nbo = htons (newcoin_index);
+
+ cc->cnc_index = cnc_index;
+ cc->newcoin_index = newcoin_index;
+ cc->session_pub = *refresh_session_pub;
+
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(refresh_session_pub),
+ TALER_DB_QUERY_PARAM_PTR(&cnc_index_nbo),
+ TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_commit_coin", params);
+
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 == PQntuples (result))
+ {
+ PQclear (result);
+ return GNUNET_NO;
+ }
+
+ struct TALER_DB_ResultSpec rs[] = {
+ TALER_DB_RESULT_SPEC("coin_ev", &cc->coin_ev),
+ TALER_DB_RESULT_SPEC_SIZED("link_vector_enc", &cc->link_enc,
+ TALER_REFRESH_LINK_LENGTH),
+ TALER_DB_RESULT_SPEC_END
+ };
+
+ if (GNUNET_YES != TALER_DB_extract_result (result, rs, 0))
+ {
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ PQclear (result);
+ return GNUNET_YES;
+}
+
+
+int
+TALER_MINT_DB_get_refresh_order (PGconn *db_conn,
+ uint16_t newcoin_index,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
+ struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub)
+{
+ uint16_t newcoin_index_nbo = htons (newcoin_index);
+
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(session_pub),
+ TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_order", params);
+
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 == PQntuples (result))
+ {
+ PQclear (result);
+ return GNUNET_NO;
+ }
+
+ GNUNET_assert (1 == PQntuples (result));
+
+ struct TALER_DB_ResultSpec rs[] = {
+ TALER_DB_RESULT_SPEC("denom_pub", denom_pub),
+ TALER_DB_RESULT_SPEC_END
+ };
+
+ if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0))
+ {
+ PQclear (result);
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+int
+TALER_MINT_DB_insert_refresh_collectable (PGconn *db_conn,
+ uint16_t newcoin_index,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
+ const struct TALER_RSA_Signature *ev_sig)
+{
+ uint16_t newcoin_index_nbo = htons (newcoin_index);
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(session_pub),
+ TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo),
+ TALER_DB_QUERY_PARAM_PTR(ev_sig),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_collectable", params);
+
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+int
+TALER_MINT_DB_get_refresh_collectable (PGconn *db_conn,
+ uint16_t newcoin_index,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
+ struct TALER_RSA_Signature *ev_sig)
+{
+
+ uint16_t newcoin_index_nbo = htons (newcoin_index);
+
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(session_pub),
+ TALER_DB_QUERY_PARAM_PTR(&newcoin_index_nbo),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_collectable", params);
+
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 == PQntuples (result))
+ {
+ PQclear (result);
+ return GNUNET_NO;
+ }
+
+ GNUNET_assert (1 == PQntuples (result));
+
+ struct TALER_DB_ResultSpec rs[] = {
+ TALER_DB_RESULT_SPEC("ev_sig", ev_sig),
+ TALER_DB_RESULT_SPEC_END
+ };
+
+ if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0))
+ {
+ PQclear (result);
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+
+int
+TALER_MINT_DB_insert_refresh_melt (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
+ uint16_t oldcoin_index,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub,
+ const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub)
+{
+ uint16_t oldcoin_index_nbo = htons (oldcoin_index);
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(session_pub),
+ TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo),
+ TALER_DB_QUERY_PARAM_PTR(coin_pub),
+ TALER_DB_QUERY_PARAM_PTR(denom_pub),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "insert_refresh_melt", params);
+
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+
+int
+TALER_MINT_DB_get_refresh_melt (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
+ uint16_t oldcoin_index,
+ struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub)
+{
+ uint16_t oldcoin_index_nbo = htons (oldcoin_index);
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(session_pub),
+ TALER_DB_QUERY_PARAM_PTR(&oldcoin_index_nbo),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "get_refresh_melt", params);
+
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 == PQntuples (result))
+ {
+ PQclear (result);
+ return GNUNET_NO;
+ }
+
+ GNUNET_assert (1 == PQntuples (result));
+
+ struct TALER_DB_ResultSpec rs[] = {
+ TALER_DB_RESULT_SPEC("coin_pub", coin_pub),
+ TALER_DB_RESULT_SPEC_END
+ };
+
+ if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0))
+ {
+ PQclear (result);
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+int
+TALER_db_get_link (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub,
+ LinkIterator link_iter,
+ void *cls)
+{
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(coin_pub),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "get_link", params);
+
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 == PQntuples (result))
+ {
+ PQclear (result);
+ return GNUNET_NO;
+ }
+
+
+ int i = 0;
+ int res;
+
+ for (i = 0; i < PQntuples (result); i++)
+ {
+ struct LinkDataEnc link_data_enc;
+ struct TALER_RSA_PublicKeyBinaryEncoded denom_pub;
+ struct TALER_RSA_Signature ev_sig;
+ struct TALER_DB_ResultSpec rs[] = {
+ TALER_DB_RESULT_SPEC("link_vector_enc", &link_data_enc),
+ TALER_DB_RESULT_SPEC("denom_pub", &denom_pub),
+ TALER_DB_RESULT_SPEC("ev_sig", &ev_sig),
+ TALER_DB_RESULT_SPEC_END
+ };
+
+ if (GNUNET_OK != TALER_DB_extract_result (result, rs, i))
+ {
+ PQclear (result);
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK != (res = link_iter (cls, &link_data_enc, &denom_pub, &ev_sig)))
+ {
+ GNUNET_assert (GNUNET_SYSERR != res);
+ PQclear (result);
+ return res;
+ }
+ }
+
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+int
+TALER_db_get_transfer (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub,
+ struct GNUNET_CRYPTO_EcdsaPublicKey *transfer_pub,
+ struct SharedSecretEnc *shared_secret_enc)
+{
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR(coin_pub),
+ TALER_DB_QUERY_PARAM_END
+ };
+
+ PGresult *result = TALER_DB_exec_prepared (db_conn, "get_transfer", params);
+
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 == PQntuples (result))
+ {
+ PQclear (result);
+ return GNUNET_NO;
+ }
+
+ if (1 != PQntuples (result))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "got %d tuples for get_transfer\n", PQntuples (result));
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ struct TALER_DB_ResultSpec rs[] = {
+ TALER_DB_RESULT_SPEC("transfer_pub", transfer_pub),
+ TALER_DB_RESULT_SPEC("link_secret_enc", shared_secret_enc),
+ TALER_DB_RESULT_SPEC_END
+ };
+
+ if (GNUNET_OK != TALER_DB_extract_result (result, rs, 0))
+ {
+ PQclear (result);
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+
+int
+TALER_MINT_DB_init_deposits (PGconn *conn, int tmp)
+{
+ const char *tmp_str = (1 == tmp) ? "TEMPORARY" : "";
+ char *sql;
+ PGresult *res;
+ int ret;
+
+ res = NULL;
+ (void) GNUNET_asprintf (&sql,
+ "CREATE %1$s TABLE IF NOT EXISTS deposits ("
+ " coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (length(coin_pub)=32)"
+ ",denom_pub BYTEA NOT NULL CHECK (length(denom_pub)=32)"
+ ",transaction_id INT8 NOT NULL"
+ ",amount_value INT4 NOT NULL"
+ ",amount_fraction INT4 NOT NULL"
+ ",amount_currency VARCHAR(4) NOT NULL"
+ ",merchant_pub BYTEA NOT NULL"
+ ",h_contract BYTEA NOT NULL CHECK (length(h_contract)=64)"
+ ",h_wire BYTEA NOT NULL CHECK (length(h_wire)=64)"
+ ",coin_sig BYTEA NOT NULL CHECK (length(coin_sig)=64)"
+ ",wire TEXT NOT NULL"
+ ")",
+ tmp_str);
+ res = PQexec (conn, sql);
+ GNUNET_free (sql);
+ if (PGRES_COMMAND_OK != PQresultStatus (res))
+ {
+ break_db_err (res);
+ ret = GNUNET_SYSERR;
+ }
+ else
+ ret = GNUNET_OK;
+ PQclear (res);
+ return ret;
+}
+
+int
+TALER_MINT_DB_prepare_deposits (PGconn *db_conn)
+{
+ PGresult *result;
+
+ result = PQprepare (db_conn, "insert_deposit",
+ "INSERT INTO deposits ("
+ "coin_pub,"
+ "denom_pub,"
+ "transaction_id,"
+ "amount_value,"
+ "amount_fraction,"
+ "amount_currency,"
+ "merchant_pub,"
+ "h_contract,"
+ "h_wire,"
+ "coin_sig,"
+ "wire"
+ ") VALUES ("
+ "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11"
+ ")",
+ 11, NULL);
+ EXITIF (PGRES_COMMAND_OK != PQresultStatus(result));
+ PQclear (result);
+
+ result = PQprepare (db_conn, "get_deposit",
+ "SELECT "
+ "coin_pub,"
+ "denom_pub,"
+ "transaction_id,"
+ "amount_value,"
+ "amount_fraction,"
+ "amount_currency,"
+ "merchant_pub,"
+ "h_contract,"
+ "h_wire,"
+ "coin_sig"
+ " FROM deposits WHERE ("
+ "coin_pub = $1"
+ ")",
+ 1, NULL);
+ EXITIF (PGRES_COMMAND_OK != PQresultStatus(result));
+ PQclear (result);
+
+ return GNUNET_OK;
+
+ EXITIF_exit:
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+}
+
+
+int
+TALER_MINT_DB_insert_deposit (PGconn *db_conn,
+ const struct Deposit *deposit)
+{
+ struct TALER_DB_QueryParam params[]= {
+ TALER_DB_QUERY_PARAM_PTR (&deposit->coin_pub),
+ TALER_DB_QUERY_PARAM_PTR (&deposit->denom_pub),
+ TALER_DB_QUERY_PARAM_PTR (&deposit->transaction_id),
+ TALER_DB_QUERY_PARAM_PTR (&deposit->amount.value),
+ TALER_DB_QUERY_PARAM_PTR (&deposit->amount.fraction),
+ TALER_DB_QUERY_PARAM_PTR_SIZED (deposit->amount.currency, strlen (deposit->amount.currency)),
+ TALER_DB_QUERY_PARAM_PTR (&deposit->merchant_pub),
+ TALER_DB_QUERY_PARAM_PTR (&deposit->h_contract),
+ TALER_DB_QUERY_PARAM_PTR (&deposit->h_wire),
+ TALER_DB_QUERY_PARAM_PTR (&deposit->coin_sig),
+ TALER_DB_QUERY_PARAM_PTR_SIZED (deposit->wire, strlen(deposit->wire)),
+ TALER_DB_QUERY_PARAM_END
+ };
+ PGresult *result;
+
+ result = TALER_DB_exec_prepared (db_conn, "insert_deposit", params);
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ PQclear (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+ return GNUNET_OK;
+}
+
+int
+TALER_MINT_DB_get_deposit (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *coin_pub,
+ struct Deposit **r_deposit)
+{
+ struct TALER_DB_QueryParam params[] = {
+ TALER_DB_QUERY_PARAM_PTR (coin_pub),
+ TALER_DB_QUERY_PARAM_END
+ };
+ PGresult *result;
+ struct Deposit *deposit;
+
+ deposit = NULL;
+ result = TALER_DB_exec_prepared (db_conn, "get_deposit", params);
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ break_db_err (result);
+ goto EXITIF_exit;
+ }
+
+ if (0 == PQntuples (result))
+ {
+ PQclear (result);
+ return GNUNET_NO;
+ }
+
+ if (1 != PQntuples (result))
+ {
+ GNUNET_break (0);
+ goto EXITIF_exit;
+ }
+
+ {
+ deposit = GNUNET_malloc (sizeof (struct Deposit)); /* Without wire data */
+ struct TALER_DB_ResultSpec rs[] = {
+ TALER_DB_RESULT_SPEC ("coin_pub", &deposit->coin_pub),
+ TALER_DB_RESULT_SPEC ("denom_pub", &deposit->denom_pub),
+ TALER_DB_RESULT_SPEC ("coin_sig", &deposit->coin_sig),
+ TALER_DB_RESULT_SPEC ("transaction_id", &deposit->transaction_id),
+ TALER_DB_RESULT_SPEC ("merchant_pub", &deposit->merchant_pub),
+ TALER_DB_RESULT_SPEC ("h_contract", &deposit->h_contract),
+ TALER_DB_RESULT_SPEC ("h_wire", &deposit->h_wire),
+ TALER_DB_RESULT_SPEC_END
+ };
+ EXITIF (GNUNET_OK != TALER_DB_extract_result (result, rs, 0));
+ EXITIF (GNUNET_OK != TALER_DB_extract_amount_nbo (result, 0,
+ "amount_value",
+ "amount_fraction",
+ "amount_currency",
+ &deposit->amount));
+ deposit->purpose.purpose = htonl (TALER_SIGNATURE_DEPOSIT);
+ deposit->purpose.size = htonl (sizeof (struct Deposit)
+ - offsetof (struct Deposit, purpose));
+ }
+
+ PQclear (result);
+ *r_deposit = deposit;
+ return GNUNET_OK;
+
+EXITIF_exit:
+ PQclear (result);
+ GNUNET_free_non_null (deposit);
+ deposit = NULL;
+ return GNUNET_SYSERR;
+}
+
+
+
+/**
+ * Get the thread-local database-handle.
+ * Connect to the db if the connection does not exist yet.
+ *
+ * @param the database connection, or NULL on error
+ */
+PGconn *
+TALER_MINT_DB_get_connection (void)
+{
+ PGconn *db_conn;
+
+ if (NULL != (db_conn = pthread_getspecific (db_conn_threadlocal)))
+ return db_conn;
+
+ db_conn = PQconnectdb (TALER_MINT_db_connection_cfg_str);
+
+ if (CONNECTION_OK != PQstatus (db_conn))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "db connection failed: %s\n",
+ PQerrorMessage (db_conn));
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ if (GNUNET_OK != TALER_MINT_DB_prepare (db_conn))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ if (0 != pthread_setspecific (db_conn_threadlocal, db_conn))
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ return db_conn;
+}
+
+
+/**
+ * Close thread-local database connection when a thread is destroyed.
+ *
+ * @param closure we get from pthreads (the db handle)
+ */
+static void
+db_conn_destroy (void *cls)
+{
+ PGconn *db_conn = cls;
+ if (NULL != db_conn)
+ PQfinish (db_conn);
+}
+
+
+/**
+ * Initialize database subsystem.
+ *
+ * @param connection_cfg configuration to use to talk to DB
+ * @return GNUNET_OK on success
+ */
+int
+TALER_MINT_DB_init (const char *connection_cfg)
+{
+
+ if (0 != pthread_key_create (&db_conn_threadlocal, &db_conn_destroy))
+ {
+ fprintf (stderr,
+ "Can't create pthread key.\n");
+ return GNUNET_SYSERR;
+ }
+ TALER_MINT_db_connection_cfg_str = GNUNET_strdup (connection_cfg);
+ return GNUNET_OK;
+}
diff --git a/src/mint/mint_db.h b/src/mint/mint_db.h
new file mode 100644
index 000000000..4f47aac1c
--- /dev/null
+++ b/src/mint/mint_db.h
@@ -0,0 +1,344 @@
+/*
+ This file is part of TALER
+ (C) 2014 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/mint_db.h
+ * @brief Mint-specific database access
+ * @author Florian Dold
+ */
+
+#ifndef _NEURO_MINT_DB_H
+#define _NEURO_MINT_DB_H
+
+#include <libpq-fe.h>
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_util.h"
+#include "taler_types.h"
+#include "taler_rsa.h"
+
+
+/**
+ * Reserve row. Corresponds to table 'reserves' in
+ * the mint's database.
+ */
+struct Reserve
+{
+ /**
+ * Signature over the purse.
+ * Only valid if (blind_session_missing==GNUNET_YES).
+ */
+ struct GNUNET_CRYPTO_EddsaSignature status_sig;
+ /**
+ * Signature with purpose TALER_SIGNATURE_PURSE.
+ * Only valid if (blind_session_missing==GNUNET_YES).
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose status_sig_purpose;
+ /**
+ * Signing key used to sign the purse.
+ * Only valid if (blind_session_missing==GNUNET_YES).
+ */
+ struct GNUNET_CRYPTO_EddsaPublicKey status_sign_pub;
+ /**
+ * Withdraw public key, identifies the purse.
+ * Only the customer knows the corresponding private key.
+ */
+ struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub;
+ /**
+ * Remaining balance in the purse.
+ */
+ struct TALER_AmountNBO balance;
+
+ /**
+ * Expiration date for the purse.
+ */
+ struct GNUNET_TIME_AbsoluteNBO expiration;
+};
+
+
+struct CollectableBlindcoin
+{
+ struct TALER_RSA_BlindedSignaturePurpose ev;
+ struct TALER_RSA_Signature ev_sig;
+ struct TALER_RSA_PublicKeyBinaryEncoded denom_pub;
+ struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub;
+ struct GNUNET_CRYPTO_EddsaSignature reserve_sig;
+};
+
+
+struct RefreshSession
+{
+ int has_commit_sig;
+ struct GNUNET_CRYPTO_EddsaSignature commit_sig;
+ struct GNUNET_CRYPTO_EddsaPublicKey session_pub;
+ uint16_t num_oldcoins;
+ uint16_t num_newcoins;
+ uint16_t kappa;
+ uint16_t noreveal_index;
+ uint8_t reveal_ok;
+};
+
+
+#define TALER_REFRESH_SHARED_SECRET_LENGTH (sizeof (struct GNUNET_HashCode))
+#define TALER_REFRESH_LINK_LENGTH (sizeof (struct LinkData))
+
+struct RefreshCommitLink
+{
+ struct GNUNET_CRYPTO_EddsaPublicKey session_pub;
+ struct GNUNET_CRYPTO_EcdsaPublicKey transfer_pub;
+ uint16_t cnc_index;
+ uint16_t oldcoin_index;
+ char shared_secret_enc[sizeof (struct GNUNET_HashCode)];
+};
+
+struct LinkData
+{
+ struct GNUNET_CRYPTO_EcdsaPrivateKey coin_priv;
+ struct TALER_RSA_BlindingKeyBinaryEncoded bkey_enc;
+};
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+struct SharedSecretEnc
+{
+ char data[TALER_REFRESH_SHARED_SECRET_LENGTH];
+};
+
+
+struct LinkDataEnc
+{
+ char data[sizeof (struct LinkData)];
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+struct RefreshCommitCoin
+{
+ struct GNUNET_CRYPTO_EddsaPublicKey session_pub;
+ struct TALER_RSA_BlindedSignaturePurpose coin_ev;
+ uint16_t cnc_index;
+ uint16_t newcoin_index;
+ char link_enc[sizeof (struct LinkData)];
+};
+
+
+struct KnownCoin
+{
+ struct TALER_CoinPublicInfo public_info;
+ struct TALER_Amount expended_balance;
+ int is_refreshed;
+ /**
+ * Refreshing session, only valid if
+ * is_refreshed==1.
+ */
+ struct GNUNET_CRYPTO_EddsaPublicKey refresh_session_pub;
+};
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+struct Deposit
+{
+ /* FIXME: should be TALER_CoinPublicInfo */
+ struct GNUNET_CRYPTO_EddsaPublicKey coin_pub;
+ struct TALER_RSA_PublicKeyBinaryEncoded denom_pub;
+ struct TALER_RSA_Signature coin_sig;
+ struct TALER_RSA_SignaturePurpose purpose;
+ uint64_t transaction_id;
+ struct TALER_AmountNBO amount;
+ struct GNUNET_CRYPTO_EddsaPublicKey merchant_pub;
+ struct GNUNET_HashCode h_contract;
+ struct GNUNET_HashCode h_wire;
+ /* TODO: uint16_t wire_size */
+ char wire[]; /* string encoded wire JSON object */
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+int
+TALER_MINT_DB_prepare (PGconn *db_conn);
+
+int
+TALER_MINT_DB_get_collectable_blindcoin (PGconn *db_conn,
+ struct TALER_RSA_BlindedSignaturePurpose *blind_ev,
+ struct CollectableBlindcoin *collectable);
+
+int
+TALER_MINT_DB_insert_collectable_blindcoin (PGconn *db_conn,
+ const struct CollectableBlindcoin *collectable);
+
+
+int
+TALER_MINT_DB_rollback (PGconn *db_conn);
+
+
+int
+TALER_MINT_DB_transaction (PGconn *db_conn);
+
+
+int
+TALER_MINT_DB_commit (PGconn *db_conn);
+
+
+int
+TALER_MINT_DB_get_reserve (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub,
+ struct Reserve *reserve_res);
+
+int
+TALER_MINT_DB_update_reserve (PGconn *db_conn,
+ const struct Reserve *reserve,
+ int fresh);
+
+
+int
+TALER_MINT_DB_insert_refresh_order (PGconn *db_conn,
+ uint16_t newcoin_index,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
+ const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub);
+
+int
+TALER_MINT_DB_get_refresh_session (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub,
+ struct RefreshSession *r_session);
+
+
+int
+TALER_MINT_DB_get_known_coin (PGconn *db_conn, struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub,
+ struct KnownCoin *known_coin);
+
+
+int
+TALER_MINT_DB_upsert_known_coin (PGconn *db_conn, struct KnownCoin *known_coin);
+
+
+int
+TALER_MINT_DB_insert_refresh_commit_link (PGconn *db_conn, struct RefreshCommitLink *commit_link);
+
+int
+TALER_MINT_DB_insert_refresh_commit_coin (PGconn *db_conn, struct RefreshCommitCoin *commit_coin);
+
+
+int
+TALER_MINT_DB_get_refresh_commit_link (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub,
+ int i, int j,
+ struct RefreshCommitLink *commit_link);
+
+
+int
+TALER_MINT_DB_get_refresh_commit_coin (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub,
+ int i, int j,
+ struct RefreshCommitCoin *commit_coin);
+
+
+int
+TALER_MINT_DB_create_refresh_session (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey
+ *session_pub);
+
+
+int
+TALER_MINT_DB_get_refresh_order (PGconn *db_conn,
+ uint16_t newcoin_index,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
+ struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub);
+
+
+int
+TALER_MINT_DB_insert_refresh_collectable (PGconn *db_conn,
+ uint16_t newcoin_index,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
+ const struct TALER_RSA_Signature *ev_sig);
+int
+TALER_MINT_DB_get_refresh_collectable (PGconn *db_conn,
+ uint16_t newcoin_index,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
+ struct TALER_RSA_Signature *ev_sig);
+int
+TALER_MINT_DB_set_reveal_ok (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub);
+
+int
+TALER_MINT_DB_insert_refresh_melt (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
+ uint16_t oldcoin_index,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub,
+ const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub);
+
+
+int
+TALER_MINT_DB_get_refresh_melt (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
+ uint16_t oldcoin_index,
+ struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub);
+
+
+typedef
+int (*LinkIterator) (void *cls,
+ const struct LinkDataEnc *link_data_enc,
+ const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub,
+ const struct TALER_RSA_Signature *ev_sig);
+
+int
+TALER_db_get_link (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub,
+ LinkIterator link_iter,
+ void *cls);
+
+
+int
+TALER_db_get_transfer (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub,
+ struct GNUNET_CRYPTO_EcdsaPublicKey *transfer_pub,
+ struct SharedSecretEnc *shared_secret_enc);
+
+int
+TALER_MINT_DB_init_deposits (PGconn *db_conn, int temporary);
+
+int
+TALER_MINT_DB_prepare_deposits (PGconn *db_conn);
+
+int
+TALER_MINT_DB_insert_deposit (PGconn *db_conn,
+ const struct Deposit *deposit);
+
+int
+TALER_MINT_DB_get_deposit (PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *coin_pub,
+ struct Deposit **r_deposit);
+int
+TALER_MINT_DB_insert_known_coin (PGconn *db_conn,
+ const struct KnownCoin *known_coin);
+
+
+
+/**
+ * Get the thread-local database-handle.
+ * Connect to the db if the connection does not exist yet.
+ *
+ * @param the database connection, or NULL on error
+ */
+PGconn *
+TALER_MINT_DB_get_connection (void);
+
+
+int
+TALER_MINT_DB_init (const char *connection_cfg);
+
+
+
+#endif /* _NEURO_MINT_DB_H */
diff --git a/src/mint/taler-mint-dbinit.c b/src/mint/taler-mint-dbinit.c
new file mode 100644
index 000000000..d877f62c6
--- /dev/null
+++ b/src/mint/taler-mint-dbinit.c
@@ -0,0 +1,285 @@
+/*
+ This file is part of TALER
+ (C) 2014 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 taler-mint-dbinit.c
+ * @brief Create tables for the mint database.
+ * @author Florian Dold
+ */
+
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <libpq-fe.h>
+#include "mint.h"
+
+
+#define break_db_err(result) do { \
+ GNUNET_break(0); \
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s\n", PQresultErrorMessage (result)); \
+ PQclear (result); \
+ } while (0)
+
+
+static char *mint_base_dir;
+static struct GNUNET_CONFIGURATION_Handle *cfg;
+static PGconn *db_conn;
+static char *TALER_MINT_db_connection_cfg_str;
+
+
+int
+TALER_MINT_init_withdraw_tables (PGconn *conn)
+{
+ PGresult *result;
+ result = PQexec (conn,
+ "CREATE TABLE IF NOT EXISTS reserves"
+ "("
+ " reserve_pub BYTEA PRIMARY KEY"
+ ",balance_value INT4 NOT NULL"
+ ",balance_fraction INT4 NOT NULL"
+ ",balance_currency VARCHAR(4) NOT NULL"
+ ",status_sig BYTEA"
+ ",status_sign_pub BYTEA"
+ ",expiration_date INT8 NOT NULL"
+ ")");
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQexec (conn,
+ "CREATE TABLE IF NOT EXISTS collectable_blindcoins"
+ "("
+ "blind_ev BYTEA PRIMARY KEY"
+ ",blind_ev_sig BYTEA NOT NULL"
+ ",denom_pub BYTEA NOT NULL"
+ ",reserve_sig BYTEA NOT NULL"
+ ",reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub)"
+ ")");
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQexec (conn,
+ "CREATE TABLE IF NOT EXISTS known_coins "
+ "("
+ " coin_pub BYTEA NOT NULL PRIMARY KEY"
+ ",denom_pub BYTEA NOT NULL"
+ ",denom_sig BYTEA NOT NULL"
+ ",expended_value INT4 NOT NULL"
+ ",expended_fraction INT4 NOT NULL"
+ ",expended_currency VARCHAR(4) NOT NULL"
+ ",refresh_session_pub BYTEA"
+ ")");
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQexec (conn,
+ "CREATE TABLE IF NOT EXISTS refresh_sessions "
+ "("
+ " session_pub BYTEA PRIMARY KEY CHECK (length(session_pub) = 32)"
+ ",session_melt_sig BYTEA"
+ ",session_commit_sig BYTEA"
+ ",noreveal_index INT2 NOT NULL"
+ // non-zero if all reveals were ok
+ // and the new coin signatures are ready
+ ",reveal_ok BOOLEAN NOT NULL DEFAULT false"
+ ") ");
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQexec (conn,
+ "CREATE TABLE IF NOT EXISTS refresh_order "
+ "( "
+ " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub)"
+ ",newcoin_index INT2 NOT NULL "
+ ",denom_pub BYTEA NOT NULL "
+ ",PRIMARY KEY (session_pub, newcoin_index)"
+ ") ");
+
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+
+ result = PQexec (conn,
+ "CREATE TABLE IF NOT EXISTS refresh_commit_link"
+ "("
+ " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub)"
+ ",transfer_pub BYTEA NOT NULL"
+ ",link_secret_enc BYTEA NOT NULL"
+ // index of the old coin in the customer's request
+ ",oldcoin_index INT2 NOT NULL"
+ // index for cut and choose,
+ // ranges from 0 to kappa-1
+ ",cnc_index INT2 NOT NULL"
+ ")");
+
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQexec (conn,
+ "CREATE TABLE IF NOT EXISTS refresh_commit_coin"
+ "("
+ " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) "
+ ",link_vector_enc BYTEA NOT NULL"
+ // index of the new coin in the customer's request
+ ",newcoin_index INT2 NOT NULL"
+ // index for cut and choose,
+ ",cnc_index INT2 NOT NULL"
+ ",coin_ev BYTEA NOT NULL"
+ ")");
+
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQexec (conn,
+ "CREATE TABLE IF NOT EXISTS refresh_melt"
+ "("
+ " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) "
+ ",coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) "
+ ",denom_pub BYTEA NOT NULL "
+ ",oldcoin_index INT2 NOT NULL"
+ ")");
+
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQexec (conn,
+ "CREATE TABLE IF NOT EXISTS refresh_collectable"
+ "("
+ " session_pub BYTEA NOT NULL REFERENCES refresh_sessions (session_pub) "
+ ",ev_sig BYTEA NOT NULL"
+ ",newcoin_index INT2 NOT NULL"
+ ")");
+
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ result = PQexec (conn,
+ "CREATE TABLE IF NOT EXISTS deposits "
+ "( "
+ " coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (length(coin_pub)=32)"
+ ",denom_pub BYTEA NOT NULL CHECK (length(denom_pub)=32)"
+ ",transaction_id INT8 NOT NULL"
+ ",amount_currency VARCHAR(4) NOT NULL"
+ ",amount_value INT4 NOT NULL"
+ ",amount_fraction INT4 NOT NULL"
+ ",merchant_pub BYTEA NOT NULL"
+ ",h_contract BYTEA NOT NULL CHECK (length(h_contract)=64)"
+ ",h_wire BYTEA NOT NULL CHECK (length(h_wire)=64)"
+ ",coin_sig BYTEA NOT NULL CHECK (length(coin_sig)=64)"
+ ",wire TEXT NOT NULL"
+ ")");
+
+ if (PGRES_COMMAND_OK != PQresultStatus(result))
+ {
+ break_db_err (result);
+ return GNUNET_SYSERR;
+ }
+ PQclear (result);
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * The main function of the serve tool
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc, char *const *argv)
+{
+ static const struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"),
+ {'d', "mint-dir", "DIR",
+ "mint directory", 1,
+ &GNUNET_GETOPT_set_filename, &mint_base_dir},
+ GNUNET_GETOPT_OPTION_END
+ };
+
+ if (GNUNET_GETOPT_run ("taler-mint-serve", options, argc, argv) < 0)
+ return 1;
+
+ GNUNET_assert (GNUNET_OK == GNUNET_log_setup ("taler-mint-dbinit", "INFO", NULL));
+
+ if (NULL == mint_base_dir)
+ {
+ fprintf (stderr, "Mint base directory not given.\n");
+ return 1;
+ }
+
+ cfg = TALER_MINT_config_load (mint_base_dir);
+ if (NULL == cfg)
+ {
+ fprintf (stderr, "Can't load mint configuration.\n");
+ return 1;
+ }
+ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "mint", "db", &TALER_MINT_db_connection_cfg_str))
+ {
+ fprintf (stderr, "Configuration 'mint.db' not found.\n");
+ return 42;
+ }
+ db_conn = PQconnectdb (TALER_MINT_db_connection_cfg_str);
+ if (CONNECTION_OK != PQstatus (db_conn))
+ {
+ fprintf (stderr, "Database connection failed: %s\n", PQerrorMessage (db_conn));
+ return 1;
+ }
+
+ if (GNUNET_OK != TALER_MINT_init_withdraw_tables (db_conn))
+ {
+ fprintf (stderr, "Failed to initialize database.\n");
+ return 1;
+ }
+
+ return 0;
+}
+
diff --git a/src/mint/taler-mint-httpd.c b/src/mint/taler-mint-httpd.c
new file mode 100644
index 000000000..6d69813c0
--- /dev/null
+++ b/src/mint/taler-mint-httpd.c
@@ -0,0 +1,376 @@
+/*
+ This file is part of TALER
+ (C) 2014 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-mint-httpd.c
+ * @brief Serve the HTTP interface of the mint
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <libpq-fe.h>
+#include <pthread.h>
+#include "mint.h"
+#include "mint_db.h"
+#include "taler_types.h"
+#include "taler_signatures.h"
+#include "taler_rsa.h"
+#include "taler_json_lib.h"
+#include "taler_microhttpd_lib.h"
+#include "taler-mint-httpd_mhd.h"
+#include "taler-mint-httpd_keys.h"
+#include "taler-mint-httpd_deposit.h"
+#include "taler-mint-httpd_withdraw.h"
+#include "taler-mint-httpd_refresh.h"
+
+
+/**
+ * Base directory of the mint (global)
+ */
+char *mintdir;
+
+/**
+ * The mint's configuration (global)
+ */
+struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Master public key (according to the
+ * configuration in the mint directory).
+ */
+struct GNUNET_CRYPTO_EddsaPublicKey master_pub;
+
+/**
+ * The HTTP Daemon.
+ */
+static struct MHD_Daemon *mydaemon;
+
+/**
+ * The kappa value for refreshing.
+ */
+static unsigned int refresh_security_parameter;
+
+/**
+ * Port to run the daemon on.
+ */
+static uint16_t serve_port;
+
+
+/**
+ * Convert a string representing an EdDSA signature to an EdDSA
+ * signature.
+ *
+ * FIXME: this should be in GNUnet.
+ * FIXME: why? this code is dead, even here!
+ *
+ * @param enc encoded EdDSA signature
+ * @param enclen number of bytes in @a enc (without 0-terminator)
+ * @param pub where to store the EdDSA signature
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_eddsa_signature_from_string (const char *enc,
+ size_t enclen,
+ struct GNUNET_CRYPTO_EddsaSignature *sig)
+{
+ size_t keylen = (sizeof (struct GNUNET_CRYPTO_EddsaSignature)) * 8;
+
+ if (keylen % 5 > 0)
+ keylen += 5 - keylen % 5;
+ keylen /= 5;
+ if (enclen != keylen)
+ return GNUNET_SYSERR;
+
+ if (GNUNET_OK != GNUNET_STRINGS_string_to_data (enc, enclen,
+ sig,
+ sizeof (struct GNUNET_CRYPTO_EddsaSignature)))
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle a request coming from libmicrohttpd.
+ *
+ * @param cls closure for MHD daemon (unused)
+ * @param connection the connection
+ * @param url the requested url
+ * @param method the method (POST, GET, ...)
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request (a `struct Buffer *`)
+ * @return MHD result code
+ */
+static int
+handle_mhd_request (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ static struct RequestHandler handlers[] =
+ {
+ { "/", MHD_HTTP_METHOD_GET, "text/plain",
+ "Hello, I'm the mint\n", 0,
+ &TALER_MINT_handler_static_response, MHD_HTTP_OK },
+ { "/agpl", MHD_HTTP_METHOD_GET, "text/plain",
+ NULL, 0,
+ &TALER_MINT_handler_agpl_redirect, MHD_HTTP_FOUND },
+ { "/keys", MHD_HTTP_METHOD_GET, "application/json",
+ NULL, 0,
+ &TALER_MINT_handler_keys, MHD_HTTP_OK },
+ { "/keys", NULL, "text/plain",
+ "Only GET is allowed", 0,
+ &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
+ { "/withdraw/status", MHD_HTTP_METHOD_GET, "application/json",
+ NULL, 0,
+ &TALER_MINT_handler_withdraw_status, MHD_HTTP_OK },
+ { "/withdraw/status", NULL, "text/plain",
+ "Only GET is allowed", 0,
+ &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
+ { "/withdraw/sign", MHD_HTTP_METHOD_GET, "application/json",
+ NULL, 0,
+ &TALER_MINT_handler_withdraw_sign, MHD_HTTP_OK },
+ { "/withdraw/sign", NULL, "text/plain",
+ "Only GET is allowed", 0,
+ &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
+ { "/refresh/melt", MHD_HTTP_METHOD_POST, "application/json",
+ NULL, 0,
+ &TALER_MINT_handler_refresh_melt, MHD_HTTP_OK },
+ { "/refresh/melt", NULL, "text/plain",
+ "Only POST is allowed", 0,
+ &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
+ { "/refresh/commit", MHD_HTTP_METHOD_POST, "application/json",
+ NULL, 0,
+ &TALER_MINT_handler_refresh_commit, MHD_HTTP_OK },
+ { "/refresh/commit", NULL, "text/plain",
+ "Only POST is allowed", 0,
+ &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
+ { "/refresh/reveal", MHD_HTTP_METHOD_POST, "application/json",
+ NULL, 0,
+ &TALER_MINT_handler_refresh_melt, MHD_HTTP_OK },
+ { "/refresh/reveal", NULL, "text/plain",
+ "Only POST is allowed", 0,
+ &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
+ { "/refresh/link", MHD_HTTP_METHOD_GET, "application/json",
+ NULL, 0,
+ &TALER_MINT_handler_refresh_link, MHD_HTTP_OK },
+ { "/refresh/link", NULL, "text/plain",
+ "Only GET is allowed", 0,
+ &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
+ { "/refresh/reveal", MHD_HTTP_METHOD_GET, "application/json",
+ NULL, 0,
+ &TALER_MINT_handler_refresh_reveal, MHD_HTTP_OK },
+ { "/refresh/reveal", NULL, "text/plain",
+ "Only GET is allowed", 0,
+ &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
+ { "/deposit", MHD_HTTP_METHOD_POST, "application/json",
+ NULL, 0,
+ &TALER_MINT_handler_deposit, MHD_HTTP_OK },
+ { "/deposit", NULL, "text/plain",
+ "Only POST is allowed", 0,
+ &TALER_MINT_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
+ { NULL, NULL, NULL, NULL, 0, 0 }
+ };
+ static struct RequestHandler h404 =
+ {
+ "", NULL, "text/html",
+ "<html><title>404: not found</title></html>", 0,
+ &TALER_MINT_handler_static_response, MHD_HTTP_NOT_FOUND
+ };
+ struct RequestHandler *rh;
+ unsigned int i;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling request for URL '%s'\n",
+ url);
+ for (i=0;NULL != handlers[i].url;i++)
+ {
+ rh = &handlers[i];
+ if ( (0 == strcasecmp (url,
+ rh->url)) &&
+ ( (NULL == rh->method) ||
+ (0 == strcasecmp (method,
+ rh->method)) ) )
+ return rh->handler (rh,
+ connection,
+ con_cls,
+ upload_data,
+ upload_data_size);
+ }
+ return TALER_MINT_handler_static_response (&h404,
+ connection,
+ con_cls,
+ upload_data,
+ upload_data_size);
+}
+
+
+
+/**
+ * Load configuration parameters for the mint
+ * server into the corresponding global variables.
+ *
+ * @param param mint_directory the mint's directory
+ * @return GNUNET_OK on success
+ */
+static int
+mint_serve_process_config (const char *mint_directory)
+{
+ unsigned long long port;
+ unsigned long long kappa;
+ char *master_pub_str;
+ char *db_cfg;
+
+ cfg = TALER_MINT_config_load (mint_directory);
+ if (NULL == cfg)
+ {
+ fprintf (stderr,
+ "can't load mint configuration\n");
+ return 1;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "mint", "master_pub",
+ &master_pub_str))
+ {
+ fprintf (stderr,
+ "No master public key given in mint configuration.");
+ return GNUNET_NO;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_public_key_from_string (master_pub_str,
+ strlen (master_pub_str),
+ &master_pub))
+ {
+ fprintf (stderr,
+ "Invalid master public key given in mint configuration.");
+ return GNUNET_NO;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "mint", "db",
+ &db_cfg))
+ {
+ fprintf (stderr,
+ "invalid configuration: mint.db\n");
+ return GNUNET_NO;
+ }
+ if (GNUNET_OK !=
+ TALER_MINT_DB_init (db_cfg))
+ {
+ fprintf (stderr,
+ "failed to initialize DB subsystem\n");
+ return GNUNET_NO;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cfg,
+ "mint", "port",
+ &port))
+ {
+ fprintf (stderr,
+ "invalid configuration: mint.port\n");
+ return GNUNET_NO;
+ }
+
+ if ((port == 0) || (port > UINT16_MAX))
+ {
+ fprintf (stderr,
+ "invalid configuration (value out of range): mint.port\n");
+ return GNUNET_NO;
+ }
+ serve_port = port;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cfg,
+ "mint", "refresh_security_parameter",
+ &kappa))
+ {
+ fprintf (stderr,
+ "invalid configuration: mint.refresh_security_parameter\n");
+ return GNUNET_NO;
+ }
+ refresh_security_parameter = kappa;
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * The main function of the serve tool
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc, char *const *argv)
+{
+ static const struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"),
+ {'d', "mint-dir", "DIR",
+ "mint directory", 1,
+ &GNUNET_GETOPT_set_filename, &mintdir},
+ GNUNET_GETOPT_OPTION_END
+ };
+ int ret;
+
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_log_setup ("taler-mint-serve",
+ "INFO",
+ NULL));
+ if (GNUNET_GETOPT_run ("taler-mint-serve",
+ options,
+ argc, argv) < 0)
+ return 1;
+ if (NULL == mintdir)
+ {
+ fprintf (stderr,
+ "no mint dir given\n");
+ return 1;
+ }
+
+ if (GNUNET_OK != mint_serve_process_config (mintdir))
+ return 1;
+
+
+ mydaemon = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+ serve_port,
+ NULL, NULL,
+ &handle_mhd_request, NULL,
+ MHD_OPTION_END);
+
+ if (NULL == mydaemon)
+ {
+ fprintf (stderr,
+ "Failed to start MHD.\n");
+ return 1;
+ }
+
+ ret = TALER_MINT_key_reload_loop ();
+ MHD_stop_daemon (mydaemon);
+ return (GNUNET_OK == ret) ? 0 : 1;
+}
+
diff --git a/src/mint/taler-mint-httpd.h b/src/mint/taler-mint-httpd.h
new file mode 100644
index 000000000..59f38aadb
--- /dev/null
+++ b/src/mint/taler-mint-httpd.h
@@ -0,0 +1,106 @@
+/*
+ This file is part of TALER
+ (C) 2014 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-mint-httpd.h
+ * @brief Global declarations for the mint
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MINT_HTTPD_H
+#define TALER_MINT_HTTPD_H
+
+
+/**
+ * Cut-and-choose size for refreshing.
+ * FIXME: maybe make it a config option?
+ */
+#define KAPPA 3
+
+
+/**
+ * The mint's configuration.
+ */
+extern struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Main directory with mint data.
+ */
+extern char *mintdir;
+
+/**
+ * Master public key (according to the
+ * configuration in the mint directory).
+ */
+extern struct GNUNET_CRYPTO_EddsaPublicKey master_pub;
+
+
+/**
+ * Struct describing an URL and the handler for it.
+ */
+struct RequestHandler
+{
+
+ /**
+ * URL the handler is for.
+ */
+ const char *url;
+
+ /**
+ * Method the handler is for, NULL for "all".
+ */
+ const char *method;
+
+ /**
+ * Mime type to use in reply (hint, can be NULL).
+ */
+ const char *mime_type;
+
+ /**
+ * Raw data for the @e handler
+ */
+ const void *data;
+
+ /**
+ * Number of bytes in @e data, 0 for 0-terminated.
+ */
+ size_t data_size;
+
+ /**
+ * Function to call to handle the request.
+ *
+ * @param rh this struct
+ * @param mime_type the @e mime_type for the reply (hint, can be NULL)
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+ int (*handler)(struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+ /**
+ * Default response code.
+ */
+ int response_code;
+};
+
+
+#endif
diff --git a/src/mint/taler-mint-httpd_deposit.c b/src/mint/taler-mint-httpd_deposit.c
new file mode 100644
index 000000000..ecbc5c13b
--- /dev/null
+++ b/src/mint/taler-mint-httpd_deposit.c
@@ -0,0 +1,270 @@
+/*
+ This file is part of TALER
+ (C) 2014 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-mint-httpd_deposit.c
+ * @brief Handle /deposit requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <libpq-fe.h>
+#include <pthread.h>
+#include "mint.h"
+#include "mint_db.h"
+#include "taler_types.h"
+#include "taler_signatures.h"
+#include "taler_rsa.h"
+#include "taler_json_lib.h"
+#include "taler_microhttpd_lib.h"
+#include "taler-mint-httpd_keys.h"
+#include "taler-mint-httpd_deposit.h"
+
+
+/**
+ * Send confirmation of deposit success to client.
+ *
+ * @param connection connection to the client
+ * @param deposit deposit request to confirm
+ * @return MHD result code
+ */
+static int
+helper_deposit_send_response_success (struct MHD_Connection *connection,
+ struct Deposit *deposit)
+{
+ // FIXME: return more information here
+ return request_send_json_pack (connection, MHD_HTTP_OK,
+ "{s:s}", "status", "DEPOSIT_OK");
+}
+
+
+/**
+ * Handle a "/deposit" request
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_deposit (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+ json_t *json;
+ struct Deposit *deposit;
+ json_t *wire;
+ json_t *resp;
+ char *wire_enc = NULL;
+ const char *deposit_type;
+ struct MintKeyState *key_state;
+ struct TALER_CoinPublicInfo coin_info;
+ struct TALER_RSA_Signature ubsig;
+ size_t len;
+ int resp_code;
+ PGconn *db_conn;
+ int res;
+
+ res = process_post_json (connection,
+ connection_cls,
+ upload_data, upload_data_size,
+ &json);
+ if (GNUNET_SYSERR == res)
+ {
+ // FIXME: return 'internal error'
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (GNUNET_NO == res)
+ return MHD_YES;
+ if (NULL == (db_conn = TALER_MINT_DB_get_connection ()))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ deposit = NULL;
+ wire = NULL;
+ resp = NULL;
+ if (-1 == json_unpack (json,
+ "{s:s s:o}",
+ "type", &deposit_type,
+ "wire", &wire))
+ {
+ GNUNET_break_op (0);
+ resp = json_pack ("{s:s}", "error", "Bad format");
+ resp_code = MHD_HTTP_BAD_REQUEST;
+ goto EXITIF_exit;
+ }
+ if (NULL == (wire_enc = json_dumps (wire, JSON_COMPACT|JSON_SORT_KEYS)))
+ {
+ GNUNET_break_op (0);
+ resp = json_pack ("{s:s}", "error", "Bad format");
+ resp_code = MHD_HTTP_BAD_REQUEST;
+ goto EXITIF_exit;
+ }
+ len = strlen (wire_enc) + 1;
+ deposit = GNUNET_malloc (sizeof (struct Deposit) + len);
+#define EXITIF(cond) \
+ do { \
+ if (cond) { GNUNET_break (0); goto EXITIF_exit; } \
+ } while (0)
+#define PARSE_DATA(field, addr) \
+ EXITIF (GNUNET_OK != request_json_require_nav \
+ (connection, json, \
+ JNAV_FIELD, field, JNAV_RET_DATA, addr, sizeof (*addr)))
+ PARSE_DATA ("coin_pub", &deposit->coin_pub);
+ PARSE_DATA ("denom_pub", &deposit->denom_pub);
+ PARSE_DATA ("ubsig", &ubsig);
+ PARSE_DATA ("merchant_pub", &deposit->merchant_pub);
+ PARSE_DATA ("H_a", &deposit->h_contract);
+ PARSE_DATA ("H_wire", &deposit->h_wire);
+ PARSE_DATA ("csig", &deposit->coin_sig);
+ PARSE_DATA ("transaction_id", &deposit->transaction_id);
+#undef PARSE_DATA
+ if (0 == strcmp ("DIRECT_DEPOSIT", deposit_type))
+ deposit->purpose.purpose = htonl (TALER_SIGNATURE_DEPOSIT);
+ else if (0 == strcmp ("INCREMENTAL_DEPOSIT", deposit_type))
+ deposit->purpose.purpose = htonl (TALER_SIGNATURE_INCREMENTAL_DEPOSIT);
+ else
+ {
+ GNUNET_break_op (0);
+ resp = json_pack ("{s:s}", "error", "Bad format");
+ resp_code = MHD_HTTP_BAD_REQUEST;
+ goto EXITIF_exit;
+ }
+ deposit->purpose.size = htonl (sizeof (struct Deposit)
+ - offsetof (struct Deposit, purpose));
+ memcpy (&coin_info.coin_pub,
+ &deposit->coin_pub,
+ sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey));
+ coin_info.denom_pub = deposit->denom_pub;
+ coin_info.denom_sig = ubsig;
+ key_state = TALER_MINT_key_state_acquire ();
+ if (GNUNET_YES != TALER_MINT_test_coin_valid (key_state,
+ &coin_info))
+ {
+ TALER_MINT_key_state_release (key_state);
+ resp = json_pack ("{s:s}", "error", "Coin is not valid");
+ resp_code = MHD_HTTP_NOT_FOUND;
+ goto EXITIF_exit;
+ }
+ TALER_MINT_key_state_release (key_state);
+ /*
+ if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_DEPOSIT,
+ &deposit->purpose,
+ &deposit->coin_sig,
+ &deposit->coin_pub))
+ {
+ resp = json_pack ("{s:s}", "error", "Signature verfication failed");
+ resp_code = MHD_HTTP_NOT_FOUND;
+ goto EXITIF_exit;
+ }
+ */
+
+ /* Check if we already received the same deposit permission,
+ * or the coin was already deposited */
+
+ {
+ struct Deposit *existing_deposit;
+ int res;
+
+ res = TALER_MINT_DB_get_deposit (db_conn,
+ &deposit->coin_pub,
+ &existing_deposit);
+ if (GNUNET_YES == res)
+ {
+ // FIXME: memory leak
+ if (0 == memcmp (existing_deposit, deposit, sizeof (struct Deposit)))
+ return helper_deposit_send_response_success (connection, deposit);
+ // FIXME: in the future, check if there's enough credits
+ // left on the coin. For now: refuse
+ // FIXME: return more information here
+ return request_send_json_pack (connection, MHD_HTTP_FORBIDDEN,
+ "{s:s}",
+ "error", "double spending");
+ }
+
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ {
+ struct KnownCoin known_coin;
+ int res;
+
+ res = TALER_MINT_DB_get_known_coin (db_conn, &coin_info.coin_pub, &known_coin);
+ if (GNUNET_YES == res)
+ {
+ // coin must have been refreshed
+ // FIXME: check
+ // FIXME: return more information here
+ return request_send_json_pack (connection, MHD_HTTP_FORBIDDEN,
+ "{s:s}",
+ "error", "coin was refreshed");
+ }
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* coin valid but not known => insert into DB */
+ known_coin.is_refreshed = GNUNET_NO;
+ known_coin.expended_balance = TALER_amount_ntoh (deposit->amount);
+ known_coin.public_info = coin_info;
+
+ if (GNUNET_OK != TALER_MINT_DB_insert_known_coin (db_conn, &known_coin))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ if (GNUNET_OK != TALER_MINT_DB_insert_deposit (db_conn, deposit))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return helper_deposit_send_response_success (connection, deposit);
+
+ EXITIF_exit:
+ if (NULL != resp)
+ res = send_response_json (connection, resp, resp_code);
+ else
+ res = MHD_NO;
+ if (NULL != wire)
+ json_decref (wire);
+ if (NULL != deposit)
+ GNUNET_free (deposit);
+ if (NULL != wire_enc)
+ GNUNET_free (wire_enc);
+ return res;
+#undef EXITIF
+#undef PARSE_DATA
+}
+
+/* end of taler-mint-httpd_deposit.c */
diff --git a/src/mint/taler-mint-httpd_deposit.h b/src/mint/taler-mint-httpd_deposit.h
new file mode 100644
index 000000000..dd7b8c133
--- /dev/null
+++ b/src/mint/taler-mint-httpd_deposit.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ (C) 2014 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-mint-httpd_deposit.h
+ * @brief Handle /deposit requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MINT_HTTPD_DEPOSIT_H
+#define TALER_MINT_HTTPD_DEPOSIT_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-mint-httpd.h"
+
+
+/**
+ * Handle a "/deposit" request
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_deposit (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+#endif
diff --git a/src/mint/taler-mint-httpd_keys.c b/src/mint/taler-mint-httpd_keys.c
new file mode 100644
index 000000000..ba023fe69
--- /dev/null
+++ b/src/mint/taler-mint-httpd_keys.c
@@ -0,0 +1,512 @@
+/*
+ This file is part of TALER
+ (C) 2014 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-mint-httpd_keys.c
+ * @brief Handle /keys requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <libpq-fe.h>
+#include <pthread.h>
+#include "mint.h"
+#include "mint_db.h"
+#include "taler_types.h"
+#include "taler_signatures.h"
+#include "taler_rsa.h"
+#include "taler_json_lib.h"
+#include "taler_microhttpd_lib.h"
+#include "taler-mint-httpd_keys.h"
+
+
+/**
+ * Mint key state. Never use directly, instead access via
+ * #TALER_MINT_key_state_acquire and #TALER_MINT_key_state_release.
+ */
+static struct MintKeyState *internal_key_state;
+
+/**
+ * Mutex protecting access to #internal_key_state.
+ */
+static pthread_mutex_t internal_key_state_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/**
+ * Pipe used for signaling reloading of our key state.
+ */
+static int reload_pipe[2];
+
+
+/**
+ * Convert the public part of a denomination key
+ * issue to a JSON object.
+ *
+ * @param dki the denomination key issue
+ * @return a JSON object describing the denomination key isue (public part)
+ */
+static json_t *
+denom_key_issue_to_json (const struct TALER_MINT_DenomKeyIssue *dki)
+{
+ json_t *dk_json = json_object ();
+ json_object_set_new (dk_json, "master_sig",
+ TALER_JSON_from_data (&dki->signature, sizeof (struct GNUNET_CRYPTO_EddsaSignature)));
+ json_object_set_new (dk_json, "stamp_start", TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (dki->start)));
+ json_object_set_new (dk_json, "stamp_expire_withdraw", TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (dki->expire_withdraw)));
+ json_object_set_new (dk_json, "stamp_expire_deposit", TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (dki->expire_spend)));
+ json_object_set_new (dk_json, "denom_pub",
+ TALER_JSON_from_data (&dki->denom_pub, sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)));
+ json_object_set_new (dk_json, "value",
+ TALER_JSON_from_amount (TALER_amount_ntoh (dki->value)));
+ json_object_set_new (dk_json,
+ "fee_withdraw",
+ TALER_JSON_from_amount(TALER_amount_ntoh (dki->fee_withdraw)));
+ json_object_set_new (dk_json,
+ "fee_deposit",
+ TALER_JSON_from_amount(TALER_amount_ntoh (dki->fee_deposit)));
+ json_object_set_new (dk_json,
+ "fee_refresh",
+ TALER_JSON_from_amount(TALER_amount_ntoh (dki->fee_refresh)));
+ return dk_json;
+}
+
+
+/**
+ * Convert the public part of a sign key
+ * issue to a JSON object.
+ *
+ * @param ski the sign key issue
+ * @return a JSON object describing the sign key isue (public part)
+ */
+static json_t *
+sign_key_issue_to_json (const struct TALER_MINT_SignKeyIssue *ski)
+{
+ json_t *sk_json = json_object ();
+ json_object_set_new (sk_json, "stamp_start", TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (ski->start)));
+ json_object_set_new (sk_json, "stamp_expire", TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (ski->expire)));
+ json_object_set_new (sk_json, "master_sig",
+ TALER_JSON_from_data (&ski->signature, sizeof (struct GNUNET_CRYPTO_EddsaSignature)));
+ json_object_set_new (sk_json, "key",
+ TALER_JSON_from_data (&ski->signkey_pub, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)));
+ return sk_json;
+}
+
+
+/**
+ * Get the relative time value that describes how
+ * far in the future do we want to provide coin keys.
+ *
+ * @return the provide duration
+ */
+static struct GNUNET_TIME_Relative
+TALER_MINT_conf_duration_provide ()
+{
+ struct GNUNET_TIME_Relative rel;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ "mint_keys",
+ "lookahead_provide",
+ &rel))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "mint_keys.lookahead_provide not valid or not given\n");
+ GNUNET_abort ();
+ }
+ return rel;
+}
+
+
+/**
+ * Iterator for denomination keys.
+ *
+ * @param cls closure
+ * @param dki the denomination key issue
+ * @param alias coin alias
+ * @return #GNUNET_OK to continue to iterate,
+ * #GNUNET_NO to stop iteration with no error,
+ * #GNUNET_SYSERR to abort iteration with error!
+ */
+static int
+reload_keys_denom_iter (void *cls,
+ const char *alias,
+ const struct TALER_MINT_DenomKeyIssue *dki)
+{
+ struct MintKeyState *ctx = cls;
+ struct GNUNET_TIME_Absolute stamp_provide;
+ struct GNUNET_HashCode denom_key_hash;
+ int res;
+
+ stamp_provide = GNUNET_TIME_absolute_add (ctx->reload_time,
+ TALER_MINT_conf_duration_provide ());
+
+ if (GNUNET_TIME_absolute_ntoh (dki->expire_spend).abs_value_us < ctx->reload_time.abs_value_us)
+ {
+ // this key is expired
+ return GNUNET_OK;
+ }
+ if (GNUNET_TIME_absolute_ntoh (dki->start).abs_value_us > stamp_provide.abs_value_us)
+ {
+ // we are to early for this key
+ return GNUNET_OK;
+ }
+
+ GNUNET_CRYPTO_hash (&dki->denom_pub, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey), &denom_key_hash);
+
+ res = GNUNET_CONTAINER_multihashmap_put (ctx->denomkey_map,
+ &denom_key_hash,
+ GNUNET_memdup (dki, sizeof (struct TALER_MINT_DenomKeyIssue)),
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
+ if (GNUNET_OK != res)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Duplicate denomination key\n");
+
+ json_array_append_new (ctx->denom_keys_array,
+ denom_key_issue_to_json (dki));
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Iterator for sign keys.
+ *
+ * @param cls closure
+ * @param ski the sign key issue
+ * @return #GNUNET_OK to continue to iterate,
+ * #GNUNET_NO to stop iteration with no error,
+ * #GNUNET_SYSERR to abort iteration with error!
+ */
+static int
+reload_keys_sign_iter (void *cls,
+ const struct TALER_MINT_SignKeyIssue *ski)
+{
+ struct MintKeyState *ctx = cls;
+ struct GNUNET_TIME_Absolute stamp_provide;
+
+ stamp_provide = GNUNET_TIME_absolute_add (ctx->reload_time, TALER_MINT_conf_duration_provide (cfg));
+
+ if (GNUNET_TIME_absolute_ntoh (ski->expire).abs_value_us < ctx->reload_time.abs_value_us)
+ {
+ // this key is expired
+ return GNUNET_OK;
+ }
+
+ if (GNUNET_TIME_absolute_ntoh (ski->start).abs_value_us > stamp_provide.abs_value_us)
+ {
+ // we are to early for this key
+ return GNUNET_OK;
+ }
+
+ // the signkey is valid for now, check
+ // if it's more recent than the current one!
+ if (GNUNET_TIME_absolute_ntoh (ctx->current_sign_key_issue.start).abs_value_us >
+ GNUNET_TIME_absolute_ntoh (ski->start).abs_value_us)
+ ctx->current_sign_key_issue = *ski;
+
+
+ ctx->next_reload = GNUNET_TIME_absolute_min (ctx->next_reload,
+ GNUNET_TIME_absolute_ntoh (ski->expire));
+
+ json_array_append_new (ctx->sign_keys_array,
+ sign_key_issue_to_json (ski));
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Load the mint's key state from disk.
+ *
+ * @return fresh key state (with reference count 1)
+ */
+static struct MintKeyState *
+reload_keys ()
+{
+ struct MintKeyState *key_state;
+ json_t *keys;
+
+ key_state = GNUNET_new (struct MintKeyState);
+ key_state->refcnt = 1;
+
+ key_state->next_reload = GNUNET_TIME_UNIT_FOREVER_ABS;
+
+ key_state->denom_keys_array = json_array ();
+ GNUNET_assert (NULL != key_state->denom_keys_array);
+
+ key_state->sign_keys_array = json_array ();
+ GNUNET_assert (NULL != key_state->sign_keys_array);
+
+ key_state->denomkey_map = GNUNET_CONTAINER_multihashmap_create (32, GNUNET_NO);
+ GNUNET_assert (NULL != key_state->denomkey_map);
+
+ key_state->reload_time = GNUNET_TIME_absolute_get ();
+
+ TALER_MINT_denomkeys_iterate (mintdir, &reload_keys_denom_iter, key_state);
+ TALER_MINT_signkeys_iterate (mintdir, &reload_keys_sign_iter, key_state);
+
+ keys = json_pack ("{s:o, s:o, s:o, s:o}",
+ "master_pub", TALER_JSON_from_data (&master_pub, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)),
+ "signkeys", key_state->sign_keys_array,
+ "denoms", key_state->denom_keys_array,
+ "list_issue_date", TALER_JSON_from_abs (key_state->reload_time));
+
+ key_state->keys_json = json_dumps (keys, JSON_INDENT(2));
+
+ return key_state;
+}
+
+
+/**
+ * Release key state, free if necessary (if reference count gets to zero).
+ *
+ * @param key_state the key state to release
+ */
+void
+TALER_MINT_key_state_release (struct MintKeyState *key_state)
+{
+ GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex));
+ GNUNET_assert (0 != key_state->refcnt);
+ key_state->refcnt += 1;
+ if (key_state->refcnt == 0) {
+ GNUNET_free (key_state);
+ }
+ GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex));
+}
+
+
+/**
+ * Acquire the key state of the mint. Updates keys if necessary.
+ * For every call to #TALER_MINT_key_state_acquire, a matching call
+ * to #TALER_MINT_key_state_release must be made.
+ *
+ * @return the key state
+ */
+struct MintKeyState *
+TALER_MINT_key_state_acquire (void)
+{
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct MintKeyState *key_state;
+
+ // FIXME: the locking we have is very coarse-grained,
+ // using multiple locks might be nicer ...
+
+ GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex));
+ if (NULL == internal_key_state)
+ {
+ internal_key_state = reload_keys ();
+ }
+ else if (internal_key_state->next_reload.abs_value_us <= now.abs_value_us)
+ {
+ GNUNET_assert (0 != internal_key_state->refcnt);
+ internal_key_state->refcnt--;
+ if (0 == internal_key_state->refcnt)
+ GNUNET_free (internal_key_state);
+ internal_key_state = reload_keys ();
+ }
+ key_state = internal_key_state;
+ key_state->refcnt += 1;
+ GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex));
+
+ return key_state;
+}
+
+
+/**
+ * Look up the issue for a denom public key.
+ *
+ * @param key state to look in
+ * @param denom_pub denomination public key
+ * @return the denomination key issue,
+ * or NULL if denom_pub could not be found
+ */
+struct TALER_MINT_DenomKeyIssue *
+TALER_MINT_get_denom_key (const struct MintKeyState *key_state,
+ const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub)
+{
+ struct TALER_MINT_DenomKeyIssue *issue;
+ struct GNUNET_HashCode hash;
+
+ GNUNET_CRYPTO_hash (denom_pub, sizeof (struct TALER_RSA_PublicKeyBinaryEncoded), &hash);
+ issue = GNUNET_CONTAINER_multihashmap_get (key_state->denomkey_map, &hash);
+ return issue;
+}
+
+
+/**
+ * Check if a coin is valid; that is, whether the denomination key exists,
+ * is not expired, and the signature is correct.
+ *
+ * @param key_state the key state to use for checking the coin's validity
+ * @param coin_public_info the coin public info to check for validity
+ * @return GNUNET_YES if the coin is valid,
+ * GNUNET_NO if it is invalid
+ * GNUNET_SYSERROR if an internal error occured
+ */
+int
+TALER_MINT_test_coin_valid (const struct MintKeyState *key_state,
+ struct TALER_CoinPublicInfo *coin_public_info)
+{
+ struct TALER_MINT_DenomKeyIssue *dki;
+
+ dki = TALER_MINT_get_denom_key (key_state, &coin_public_info->denom_pub);
+ if (NULL == dki)
+ return GNUNET_NO;
+ if (GNUNET_OK != TALER_RSA_verify (&coin_public_info->coin_pub,
+ sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
+ &coin_public_info->denom_sig,
+ &dki->denom_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "coin signature is invalid\n");
+ return GNUNET_NO;
+ }
+ return GNUNET_YES;
+}
+
+
+/**
+ * Function to call to handle the request by sending
+ * back static data from the @a rh.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_keys (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+ struct MintKeyState *key_state;
+ struct MHD_Response *response;
+ int ret;
+
+ key_state = TALER_MINT_key_state_acquire ();
+ response = MHD_create_response_from_buffer (strlen (key_state->keys_json),
+ key_state->keys_json,
+ MHD_RESPMEM_MUST_COPY);
+ TALER_MINT_key_state_release (key_state);
+ if (NULL == response)
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ (void) MHD_add_response_header (response,
+ "Content-Type",
+ rh->mime_type);
+ ret = MHD_queue_response (connection,
+ rh->response_code,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+/**
+ * Handle a signal, writing relevant signal numbers
+ * (currently just SIGUSR1) to a pipe.
+ *
+ * @param signal_number the signal number
+ */
+static void
+handle_signal (int signal_number)
+{
+ size_t res;
+ char c = signal_number;
+
+ if (SIGUSR1 == signal_number)
+ {
+ errno = 0;
+ res = write (reload_pipe[1], &c, 1);
+ if ((res < 0) && (EINTR != errno))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ if (0 == res)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ }
+}
+
+
+/**
+ * Read signals from a pipe in a loop, and reload keys from disk if
+ * SIGUSR1 is read from the pipe.
+ */
+int
+TALER_MINT_key_reload_loop (void)
+{
+ struct sigaction act;
+
+ if (0 != pipe (reload_pipe))
+ {
+ fprintf (stderr,
+ "Failed to create pipe.\n");
+ return GNUNET_SYSERR;
+ }
+ memset (&act, 0, sizeof (struct sigaction));
+ act.sa_handler = &handle_signal;
+
+ if (0 != sigaction (SIGUSR1, &act, NULL))
+ {
+ fprintf (stderr,
+ "Failed to set signal handler.\n");
+ return GNUNET_SYSERR;
+ }
+
+ while (1)
+ {
+ char c;
+ ssize_t res;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "(re-)loading keys\n");
+ GNUNET_assert (0 == pthread_mutex_lock (&internal_key_state_mutex));
+ if (NULL != internal_key_state)
+ {
+ GNUNET_assert (0 != internal_key_state->refcnt);
+ internal_key_state->refcnt -= 1;
+ if (0 == internal_key_state->refcnt)
+ GNUNET_free (internal_key_state);
+ }
+ internal_key_state = reload_keys ();
+ GNUNET_assert (0 == pthread_mutex_unlock (&internal_key_state_mutex));
+read_again:
+ errno = 0;
+ res = read (reload_pipe[0], &c, 1);
+ if ((res < 0) && (EINTR != errno))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (EINTR == errno)
+ goto read_again;
+ }
+ return GNUNET_OK;
+}
+
+
+/* end of taler-mint-httpd_keys.c */
diff --git a/src/mint/taler-mint-httpd_keys.h b/src/mint/taler-mint-httpd_keys.h
new file mode 100644
index 000000000..640a9c916
--- /dev/null
+++ b/src/mint/taler-mint-httpd_keys.h
@@ -0,0 +1,155 @@
+/*
+ This file is part of TALER
+ (C) 2014 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-mint-httpd_keys.h
+ * @brief Handle /keys requests and manage key state
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MINT_HTTPD_KEYS_H
+#define TALER_MINT_HTTPD_KEYS_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-mint-httpd.h"
+
+
+/**
+ * Snapshot of the (coin and signing)
+ * keys (including private keys) of the mint.
+ */
+struct MintKeyState
+{
+ /**
+ * When did we initiate the key reloading?
+ */
+ struct GNUNET_TIME_Absolute reload_time;
+
+ /**
+ * JSON array with denomination keys.
+ */
+ json_t *denom_keys_array;
+
+ /**
+ * JSON array with signing keys.
+ */
+ json_t *sign_keys_array;
+
+ /**
+ * Mapping from denomination keys to denomination key issue struct.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *denomkey_map;
+
+ /**
+ * When is the next key invalid and we have to reload?
+ */
+ struct GNUNET_TIME_Absolute next_reload;
+
+ /**
+ * Mint signing key that should be used currently.
+ */
+ struct TALER_MINT_SignKeyIssue current_sign_key_issue;
+
+ /**
+ * Cached JSON text that the mint will send for
+ * a /keys request.
+ */
+ char *keys_json;
+
+ /**
+ * Reference count.
+ */
+ unsigned int refcnt;
+};
+
+
+/**
+ * Release key state, free if necessary (if reference count gets to zero).
+ *
+ * @param key_state the key state to release
+ */
+void
+TALER_MINT_key_state_release (struct MintKeyState *key_state);
+
+
+/**
+ * Acquire the key state of the mint. Updates keys if necessary.
+ * For every call to #TALER_MINT_key_state_acquire, a matching call
+ * to #TALER_MINT_key_state_release must be made.
+ *
+ * @return the key state
+ */
+struct MintKeyState *
+TALER_MINT_key_state_acquire (void);
+
+
+/**
+ * Look up the issue for a denom public key.
+ *
+ * @param key state to look in
+ * @param denom_pub denomination public key
+ * @return the denomination key issue,
+ * or NULL if denom_pub could not be found
+ */
+struct TALER_MINT_DenomKeyIssue *
+TALER_MINT_get_denom_key (const struct MintKeyState *key_state,
+ const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub);
+
+
+/**
+ * Check if a coin is valid; that is, whether the denomination key exists,
+ * is not expired, and the signature is correct.
+ *
+ * @param key_state the key state to use for checking the coin's validity
+ * @param coin_public_info the coin public info to check for validity
+ * @return GNUNET_YES if the coin is valid,
+ * GNUNET_NO if it is invalid
+ * GNUNET_SYSERROR if an internal error occured
+ */
+int
+TALER_MINT_test_coin_valid (const struct MintKeyState *key_state,
+ struct TALER_CoinPublicInfo *coin_public_info);
+
+
+/**
+ * Read signals from a pipe in a loop, and reload keys from disk if
+ * SIGUSR1 is read from the pipe.
+ *
+ * @return GNUNET_OK if we terminated normally, GNUNET_SYSERR on error
+ */
+int
+TALER_MINT_key_reload_loop (void);
+
+
+/**
+ * Handle a "/keys" request
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_keys (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+#endif
diff --git a/src/mint/taler-mint-httpd_mhd.c b/src/mint/taler-mint-httpd_mhd.c
new file mode 100644
index 000000000..09f3025b8
--- /dev/null
+++ b/src/mint/taler-mint-httpd_mhd.c
@@ -0,0 +1,300 @@
+/*
+ This file is part of TALER
+ (C) 2014 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-mint-httpd_mhd.c
+ * @brief helpers for MHD interaction
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <libpq-fe.h>
+#include <pthread.h>
+#include "taler_microhttpd_lib.h"
+#include "taler-mint-httpd.h"
+#include "taler-mint-httpd_mhd.h"
+
+
+/**
+ * Function to call to handle the request by sending
+ * back static data from the @a rh.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_static_response (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+ struct MHD_Response *response;
+ int ret;
+
+ if (0 == rh->data_size)
+ rh->data_size = strlen ((const char *) rh->data);
+ response = MHD_create_response_from_buffer (rh->data_size,
+ (void *) rh->data,
+ MHD_RESPMEM_PERSISTENT);
+ if (NULL == response)
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (NULL != rh->mime_type)
+ (void) MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ rh->mime_type);
+ ret = MHD_queue_response (connection,
+ rh->response_code,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+/**
+ * Function to call to handle the request by sending
+ * back a redirect to the AGPL source code.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_agpl_redirect (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+ const char *agpl =
+ "This server is licensed under the Affero GPL. You will now be redirected to the source code.";
+ struct MHD_Response *response;
+ int ret;
+
+ response = MHD_create_response_from_buffer (strlen (agpl),
+ (void *) agpl,
+ MHD_RESPMEM_PERSISTENT);
+ if (NULL == response)
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (NULL != rh->mime_type)
+ (void) MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ rh->mime_type);
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_LOCATION,
+ "http://www.git.taler.net/?p=mint.git");
+ ret = MHD_queue_response (connection,
+ rh->response_code,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+/**
+ * Function to call to handle the request by building a JSON
+ * reply from varargs.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param response_code HTTP response code to use
+ * @param do_cache can the response be cached? (0: no, 1: yes)
+ * @param fmt format string for pack
+ * @param ... varargs
+ * @return MHD result code
+ */
+int
+TALER_MINT_helper_send_json_pack (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void *connection_cls,
+ int response_code,
+ int do_cache,
+ const char *fmt,
+ ...)
+{
+ int ret;
+ json_t *json;
+ va_list argp;
+ char *json_str;
+ struct MHD_Response *response;
+
+ va_start (argp, fmt);
+ json = json_vpack_ex (NULL, 0, fmt, argp);
+ va_end (argp);
+ if (NULL == json)
+ return MHD_NO;
+ json_str = json_dumps (json, JSON_INDENT(2));
+ json_decref (json);
+ if (NULL == json_str)
+ return MHD_NO;
+ response = MHD_create_response_from_buffer (strlen (json_str),
+ json_str,
+ MHD_RESPMEM_MUST_FREE);
+ if (NULL == response)
+ {
+ free (json_str);
+ return MHD_NO;
+ }
+ if (NULL != rh->mime_type)
+ (void) MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ rh->mime_type);
+ ret = MHD_queue_response (connection,
+ response_code,
+ response);
+ MHD_destroy_response (response);
+ return ret;
+}
+
+
+/**
+ * Function to call to handle the request by building a JSON
+ * reply with an error message from @a rh.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_send_json_pack_error (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+ return TALER_MINT_helper_send_json_pack (rh,
+ connection,
+ connection_cls,
+ 1, /* caching enabled */
+ rh->response_code,
+ "{s:s}",
+ "error",
+ rh->data);
+}
+
+
+/**
+ * Send a response for an invalid argument.
+ *
+ * @param connection the MHD connection to use
+ * @param param_name the parameter that is missing
+ * @return a GNUnet result code
+ */
+static int
+request_arg_invalid (struct MHD_Connection *connection,
+ const char *param_name)
+{
+ json_t *json;
+ json = json_pack ("{ s:s, s:s }",
+ "error", "invalid parameter",
+ "parameter", param_name);
+ if (MHD_YES != send_response_json (connection, json, MHD_HTTP_BAD_REQUEST))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_NO;
+}
+
+
+/**
+ * Get a GET paramater that is a string,
+ * or send an error response if the parameter is missing.
+ *
+ * @param connection the connection to get the parameter from /
+ * send the error response to
+ * @param param_name the parameter name
+ * @param str pointer to store the parameter string,
+ * must be freed by the caller
+ * @return GNUNET_YES if the parameter is present and valid,
+ * GNUNET_NO if the parameter is missing
+ * GNUNET_SYSERR on internal error
+ */
+static int
+request_arg_require_string (struct MHD_Connection *connection,
+ const char *param_name,
+ const char **str)
+{
+ *str = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, param_name);
+ if (NULL == *str)
+ {
+ if (MHD_NO ==
+ request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST,
+ "{ s:s, s:s }",
+ "error", "missing parameter",
+ "parameter", param_name))
+ return GNUNET_SYSERR;
+ return GNUNET_NO;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Extraxt base32crockford encoded data from request.
+ *
+ * Queues an error response to the connection if the parameter is missing or
+ * invalid.
+ *
+ * @param connection the MHD connection
+ * @param param_name the name of the parameter with the key
+ * @param[out] out_data pointer to store the result
+ * @param out_size expected size of data
+ * @return
+ * GNUNET_YES if the the argument is present
+ * GNUNET_NO if the argument is absent or malformed
+ * GNUNET_SYSERR on internal error (error response could not be sent)
+ */
+int
+TALER_MINT_mhd_request_arg_data (struct MHD_Connection *connection,
+ const char *param_name,
+ void *out_data,
+ size_t out_size)
+{
+ const char *str;
+ int ret;
+
+ if (GNUNET_OK != (ret = request_arg_require_string (connection, param_name, &str)))
+ return ret;
+ if (GNUNET_OK != GNUNET_STRINGS_string_to_data (str, strlen (str), out_data, out_size))
+ return request_arg_invalid (connection, param_name);
+ return GNUNET_OK;
+}
+
+
+
+/* end of taler-mint-httpd_mhd.c */
diff --git a/src/mint/taler-mint-httpd_mhd.h b/src/mint/taler-mint-httpd_mhd.h
new file mode 100644
index 000000000..29ab7f64b
--- /dev/null
+++ b/src/mint/taler-mint-httpd_mhd.h
@@ -0,0 +1,132 @@
+/*
+ This file is part of TALER
+ (C) 2014 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-mint-httpd_mhd.h
+ * @brief helpers for MHD interaction
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MINT_HTTPD_MHD_H
+#define TALER_MINT_HTTPD_MHD_H
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-mint-httpd.h"
+
+
+/**
+ * Function to call to handle the request by sending
+ * back static data from the @a rh.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_static_response (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+
+/**
+ * Function to call to handle the request by sending
+ * back a redirect to the AGPL source code.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_agpl_redirect (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+
+/**
+ * Function to call to handle the request by building a JSON
+ * reply from varargs.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param response_code HTTP response code to use
+ * @param do_cache can the response be cached? (0: no, 1: yes)
+ * @param fmt format string for pack
+ * @param ... varargs
+ * @return MHD result code
+ */
+int
+TALER_MINT_helper_send_json_pack (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void *connection_cls,
+ int response_code,
+ int do_cache,
+ const char *fmt,
+ ...);
+
+
+/**
+ * Function to call to handle the request by building a JSON
+ * reply with an error message from @a rh.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_send_json_pack_error (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+
+/**
+ * Extraxt base32crockford encoded data from request.
+ *
+ * Queues an error response to the connection if the parameter is missing or
+ * invalid.
+ *
+ * @param connection the MHD connection
+ * @param param_name the name of the parameter with the key
+ * @param[out] out_data pointer to store the result
+ * @param out_size expected size of data
+ * @return
+ * GNUNET_YES if the the argument is present
+ * GNUNET_NO if the argument is absent or malformed
+ * GNUNET_SYSERR on internal error (error response could not be sent)
+ */
+int
+TALER_MINT_mhd_request_arg_data (struct MHD_Connection *connection,
+ const char *param_name,
+ void *out_data,
+ size_t out_size);
+
+#endif
diff --git a/src/mint/taler-mint-httpd_refresh.c b/src/mint/taler-mint-httpd_refresh.c
new file mode 100644
index 000000000..8121bb234
--- /dev/null
+++ b/src/mint/taler-mint-httpd_refresh.c
@@ -0,0 +1,1497 @@
+/*
+ This file is part of TALER
+ (C) 2014 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-mint-httpd_refresh.c
+ * @brief Handle /refresh/ requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <libpq-fe.h>
+#include <pthread.h>
+#include "mint.h"
+#include "mint_db.h"
+#include "taler_types.h"
+#include "taler_signatures.h"
+#include "taler_rsa.h"
+#include "taler_json_lib.h"
+#include "taler_microhttpd_lib.h"
+#include "taler-mint-httpd_keys.h"
+#include "taler-mint-httpd_mhd.h"
+#include "taler-mint-httpd_refresh.h"
+
+
+/**
+ * Sign the message in @a purpose with the mint's signing
+ * key and encode the signature as a JSON object.
+ *
+ * @param purpose the message to sign
+ * @return signature as JSON object
+ */
+static json_t *
+sign_as_json (struct GNUNET_CRYPTO_EccSignaturePurpose *purpose)
+{
+ json_t *sig_json;
+ struct GNUNET_CRYPTO_EddsaSignature sig;
+ struct MintKeyState *key_state;
+
+ key_state = TALER_MINT_key_state_acquire ();
+
+ sig_json = json_object ();
+
+ GNUNET_assert (GNUNET_OK == GNUNET_CRYPTO_eddsa_sign (&key_state->current_sign_key_issue.signkey_priv,
+ purpose,
+ &sig));
+
+ TALER_MINT_key_state_release (key_state);
+
+ json_object_set (sig_json, "sig", TALER_JSON_from_data (&sig, sizeof (struct GNUNET_CRYPTO_EddsaSignature)));
+ json_object_set (sig_json, "purpose", json_integer (ntohl (purpose->purpose)));
+ json_object_set (sig_json, "size", json_integer (ntohl (purpose->size)));
+
+ return sig_json;
+}
+
+
+static int
+link_iter (void *cls,
+ const struct LinkDataEnc *link_data_enc,
+ const struct TALER_RSA_PublicKeyBinaryEncoded *denom_pub,
+ const struct TALER_RSA_Signature *ev_sig)
+{
+ json_t *list = cls;
+ json_t *obj = json_object ();
+
+ json_array_append_new (list, obj);
+
+ json_object_set_new (obj, "link_enc",
+ TALER_JSON_from_data (link_data_enc,
+ sizeof (struct LinkDataEnc)));
+
+ json_object_set_new (obj, "denom_pub",
+ TALER_JSON_from_data (denom_pub,
+ sizeof (struct TALER_RSA_PublicKeyBinaryEncoded)));
+
+ json_object_set_new (obj, "ev_sig",
+ TALER_JSON_from_data (ev_sig,
+ sizeof (struct TALER_RSA_Signature)));
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Insert all requested denominations into the db, and compute the
+ * required cost of the denominations, including fees.
+ *
+ * @param connection the connection to send an error response to
+ * @param db_conn the database connection
+ * @param key_state the mint's key state to use
+ * @param session_pub the refresh session public key
+ * @param root the request JSON object
+ * @param hash_context the hash context where accepted
+ * denominations will be hased into
+ * @param r_amount the sum of the cost (value+fee) for
+ * all requested coins
+ * @return FIXME!
+ */
+static int
+refresh_accept_denoms (struct MHD_Connection *connection,
+ PGconn *db_conn,
+ const struct MintKeyState *key_state,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
+ const json_t *root,
+ struct TALER_HashContext *hash_context,
+ struct TALER_Amount *r_amount)
+{
+ unsigned i;
+ int res;
+ json_t *new_denoms;
+
+ res = request_json_require_nav (connection, root,
+ JNAV_FIELD, "new_denoms",
+ JNAV_RET_TYPED_JSON,
+ JSON_ARRAY,
+ &new_denoms);
+ if (GNUNET_OK != res)
+ return res;
+
+ memset (r_amount, 0, sizeof (struct TALER_Amount));
+
+ for (i = 0; i < json_array_size (new_denoms); i++)
+ {
+ struct TALER_RSA_PublicKeyBinaryEncoded denom_pub;
+ int res;
+ struct TALER_MINT_DenomKeyIssue *dki;
+ struct TALER_Amount cost;
+
+ res = request_json_require_nav (connection, root,
+ JNAV_FIELD, "new_denoms",
+ JNAV_INDEX, (int) i,
+ JNAV_RET_DATA,
+ &denom_pub,
+ sizeof (struct TALER_RSA_PublicKeyBinaryEncoded));
+
+ if (GNUNET_OK != res)
+ return res;
+
+ dki = TALER_MINT_get_denom_key (key_state, &denom_pub);
+
+ TALER_hash_context_read (hash_context,
+ &denom_pub, sizeof (struct TALER_RSA_PublicKeyBinaryEncoded));
+
+ cost = TALER_amount_add (TALER_amount_ntoh (dki->value),
+ TALER_amount_ntoh (dki->fee_withdraw));
+
+ *r_amount = TALER_amount_add (cost, *r_amount);
+
+ /* Insert the requested coin into the DB, so we'll know later
+ * what denomination the request had */
+
+ if (GNUNET_OK !=
+ TALER_MINT_DB_insert_refresh_order (db_conn,
+ i,
+ session_pub,
+ &denom_pub))
+ return res; // ???
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Get an amount in the mint's currency
+ * that is zero.
+ *
+ * @return zero amount in the mint's currency
+ */
+static struct TALER_Amount
+mint_amount_native_zero ()
+{
+ struct TALER_Amount amount;
+
+ memset (&amount, 0, sizeof (amount));
+ // FIXME: load from config
+ memcpy (amount.currency, "EUR", 3);
+
+ return amount;
+}
+
+
+static int
+check_confirm_signature (struct MHD_Connection *connection,
+ json_t *coin_info,
+ const struct GNUNET_CRYPTO_EcdsaPublicKey *coin_pub,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub)
+{
+ struct RefreshMeltConfirmSignRequestBody body;
+ struct GNUNET_CRYPTO_EcdsaSignature sig;
+ int res;
+
+ body.purpose.size = htonl (sizeof (struct RefreshMeltConfirmSignRequestBody));
+ body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_MELT_CONFIRM);
+ body.session_pub = *session_pub;
+
+ res = request_json_require_nav (connection, coin_info,
+ JNAV_FIELD, "confirm_sig",
+ JNAV_RET_DATA,
+ &sig,
+ sizeof (struct GNUNET_CRYPTO_EcdsaSignature));
+
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (GNUNET_SYSERR != res);
+ return res;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_ecdsa_verify (TALER_SIGNATURE_REFRESH_MELT_CONFIRM,
+ &body.purpose,
+ &sig,
+ coin_pub))
+ {
+ if (MHD_YES !=
+ request_send_json_pack (connection,
+ MHD_HTTP_UNAUTHORIZED,
+ "{s:s}",
+ "error", "signature invalid"))
+ return GNUNET_SYSERR;
+ return GNUNET_NO;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Extract public coin information from a JSON object.
+ *
+ * @param connection the connection to send error responses to
+ * @param root the JSON object to extract the coin info from
+ * @return GNUNET_YES if coin public info in JSON was valid
+ * GNUNET_NO otherwise
+ * GNUNET_SYSERR on internal error
+ */
+static int
+request_json_require_coin_public_info (struct MHD_Connection *connection,
+ json_t *root,
+ struct TALER_CoinPublicInfo *r_public_info)
+{
+ int ret;
+
+ GNUNET_assert (NULL != root);
+
+ ret = request_json_require_nav (connection, root,
+ JNAV_FIELD, "coin_pub",
+ JNAV_RET_DATA,
+ &r_public_info->coin_pub,
+ sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey));
+ if (GNUNET_OK != ret)
+ return ret;
+
+ ret = request_json_require_nav (connection, root,
+ JNAV_FIELD, "denom_sig",
+ JNAV_RET_DATA,
+ &r_public_info->denom_sig,
+ sizeof (struct TALER_RSA_Signature));
+ if (GNUNET_OK != ret)
+ return ret;
+
+ ret = request_json_require_nav (connection, root,
+ JNAV_FIELD, "denom_pub",
+ JNAV_RET_DATA,
+ &r_public_info->denom_pub,
+ sizeof (struct TALER_RSA_PublicKeyBinaryEncoded));
+ if (GNUNET_OK != ret)
+ return ret;
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse coin melt requests from a JSON object and write them to
+ * the database.
+ *
+ * @param connection the connection to send errors to
+ * @param db_conn the database connection
+ * @param key_state the mint's key state
+ * @param session_pub the refresh session's public key
+ * @param root the JSON object
+ * @param hash_context the hash context that will receive
+ * the coin public keys of the melted coin
+ * @return a GNUnet result code, GNUNET_OK on success,
+ * GNUNET_NO if an error message was generated,
+ * GNUNET_SYSERR on internal errors (no response generated)
+ */
+static int
+refresh_accept_melts (struct MHD_Connection *connection,
+ PGconn *db_conn,
+ const struct MintKeyState *key_state,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub,
+ json_t *root,
+ struct TALER_HashContext *hash_context,
+ struct TALER_Amount *r_melt_balance)
+{
+ size_t i;
+ int res;
+ json_t *melt_coins;
+
+ res = request_json_require_nav (connection, root,
+ JNAV_FIELD, "melt_coins",
+ JNAV_RET_TYPED_JSON,
+ JSON_ARRAY,
+ &melt_coins);
+ if (GNUNET_OK != res)
+ return res;
+
+ memset (r_melt_balance, 0, sizeof (struct TALER_Amount));
+
+ for (i = 0; i < json_array_size (melt_coins); i++)
+ {
+ struct TALER_CoinPublicInfo coin_public_info;
+ struct TALER_MINT_DenomKeyIssue *dki;
+ struct KnownCoin known_coin;
+ // money the customer gets by melting the current coin
+ struct TALER_Amount coin_gain;
+
+ res = request_json_require_coin_public_info (connection,
+ json_array_get (melt_coins, i),
+ &coin_public_info);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (GNUNET_SYSERR != res);
+ return res;
+ }
+
+ if (GNUNET_OK != (res = check_confirm_signature (connection,
+ json_array_get (melt_coins, i),
+ &coin_public_info.coin_pub,
+ session_pub)))
+ {
+ GNUNET_break (GNUNET_SYSERR != res);
+ return res;
+ }
+
+ TALER_hash_context_read (hash_context,
+ &coin_public_info.coin_pub, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
+
+ dki = TALER_MINT_get_denom_key (key_state, &coin_public_info.denom_pub);
+
+ if (NULL == dki)
+ return (MHD_YES == request_send_json_pack (connection, MHD_HTTP_NOT_FOUND,
+ "{s:s}",
+ "error", "denom not found"))
+ ? GNUNET_NO : GNUNET_SYSERR;
+
+ if (GNUNET_OK != TALER_MINT_test_coin_valid (key_state, &coin_public_info))
+ return (MHD_YES == request_send_json_pack (connection, MHD_HTTP_NOT_FOUND,
+ "{s:s}",
+ "error", "coin invalid"))
+ ? GNUNET_NO : GNUNET_SYSERR;
+
+ res = TALER_MINT_DB_get_known_coin (db_conn, &coin_public_info.coin_pub,
+ &known_coin);
+
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_YES == res)
+ {
+ if (GNUNET_YES == known_coin.is_refreshed)
+ return (MHD_YES == request_send_json_pack (connection, MHD_HTTP_NOT_FOUND,
+ "{s:s}",
+ "error", "coin already refreshed")) ? GNUNET_NO : GNUNET_SYSERR;
+ }
+ else
+ {
+ known_coin.expended_balance = mint_amount_native_zero ();
+ known_coin.public_info = coin_public_info;
+ }
+
+ known_coin.is_refreshed = GNUNET_YES;
+ known_coin.refresh_session_pub = *session_pub;
+
+ if (GNUNET_OK != TALER_MINT_DB_upsert_known_coin (db_conn, &known_coin))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK != TALER_MINT_DB_insert_refresh_melt (db_conn, session_pub, i,
+ &coin_public_info.coin_pub,
+ &coin_public_info.denom_pub))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ coin_gain = TALER_amount_ntoh (dki->value);
+ coin_gain = TALER_amount_subtract (coin_gain, known_coin.expended_balance);
+
+ /* Refuse to refresh when the coin does not have enough money left to
+ * pay the refreshing fees of the coin. */
+
+ if (TALER_amount_cmp (coin_gain, TALER_amount_ntoh (dki->fee_refresh)) < 0)
+ return (MHD_YES == request_send_json_pack (connection, MHD_HTTP_NOT_FOUND,
+ "{s:s}",
+ "error", "depleted")) ? GNUNET_NO : GNUNET_SYSERR;
+
+ coin_gain = TALER_amount_subtract (coin_gain, TALER_amount_ntoh (dki->fee_refresh));
+
+ *r_melt_balance = TALER_amount_add (*r_melt_balance, coin_gain);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Send a response for "/refresh/melt".
+ *
+ * @param connection the connection to send the response to
+ * @param db_conn the database connection to fetch values from
+ * @param session_pub the refresh session public key.
+ * @return a MHD result code
+ */
+static int
+helper_refresh_send_melt_response (struct MHD_Connection *connection,
+ PGconn *db_conn,
+ const struct GNUNET_CRYPTO_EddsaPublicKey *session_pub)
+{
+ struct RefreshSession session;
+ int res;
+ json_t *root;
+ json_t *list;
+ struct TALER_HashContext hash_context;
+
+ if (GNUNET_OK !=
+ (res = TALER_MINT_DB_get_refresh_session (db_conn,
+ session_pub,
+ &session)))
+ {
+ // FIXME: send internal error
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ root = json_object ();
+ list = json_array ();
+ json_object_set_new (root, "blind_session_pubs", list);
+
+ TALER_hash_context_start (&hash_context);
+
+ {
+ struct RefreshMeltResponseSignatureBody body;
+ json_t *sig_json;
+
+ body.purpose.size = htonl (sizeof (struct RefreshMeltResponseSignatureBody));
+ body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_MELT_RESPONSE);
+ TALER_hash_context_finish (&hash_context, &body.melt_response_hash);
+ sig_json = sign_as_json (&body.purpose);
+ GNUNET_assert (NULL != sig_json);
+ json_object_set (root, "signature", sig_json);
+ }
+
+ return send_response_json (connection,
+ root,
+ MHD_HTTP_OK);
+}
+
+
+/**
+ * Verify a signature that is encoded in a JSON object
+ *
+ * @param connection the connection to send errors to
+ * @param root the JSON object with the signature
+ * @param the public key that the signature was created with
+ * @param purpose the signed message
+ * @return GNUNET_YES if the signature was valid
+ * GNUNET_NO if the signature was invalid
+ * GNUNET_SYSERR on internal error
+ */
+static int
+request_json_check_signature (struct MHD_Connection *connection,
+ json_t *root,
+ struct GNUNET_CRYPTO_EddsaPublicKey *pub,
+ struct GNUNET_CRYPTO_EccSignaturePurpose *purpose)
+{
+ struct GNUNET_CRYPTO_EddsaSignature signature;
+ int size;
+ uint32_t purpose_num;
+ int res;
+ json_t *el;
+
+ res = request_json_require_nav (connection, root,
+ JNAV_FIELD, "sig",
+ JNAV_RET_DATA,
+ &signature,
+ sizeof (struct GNUNET_CRYPTO_EddsaSignature));
+
+ if (GNUNET_OK != res)
+ return res;
+
+ res = request_json_require_nav (connection, root,
+ JNAV_FIELD, "purpose",
+ JNAV_RET_TYPED_JSON,
+ JSON_INTEGER,
+ &el);
+
+ if (GNUNET_OK != res)
+ return res;
+
+ purpose_num = json_integer_value (el);
+
+ if (purpose_num != ntohl (purpose->purpose))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "signature invalid (purpose wrong)\n");
+ return request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST,
+ "{s:s}",
+ "error", "signature invalid (purpose)");
+ }
+
+ res = request_json_require_nav (connection, root,
+ JNAV_FIELD, "size",
+ JNAV_RET_TYPED_JSON,
+ JSON_INTEGER,
+ &el);
+
+ if (GNUNET_OK != res)
+ return res;
+
+ size = json_integer_value (el);
+
+ if (size != ntohl (purpose->size))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "signature invalid (size wrong)\n");
+ return request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST,
+ GNUNET_NO, GNUNET_SYSERR,
+ "{s:s}",
+ "error", "signature invalid (size)");
+ }
+
+ if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (purpose_num,
+ purpose,
+ &signature,
+ pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "signature invalid (did not verify)\n");
+ return request_send_json_pack (connection, MHD_HTTP_UNAUTHORIZED,
+ "{s:s}",
+ "error", "invalid signature (verification)");
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle a "/refresh/melt" request
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_refresh_melt (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+ json_t *root;
+ PGconn *db_conn;
+ struct GNUNET_CRYPTO_EddsaPublicKey refresh_session_pub;
+ int res;
+ struct MintKeyState *key_state;
+ struct TALER_Amount requested_cost;
+ struct TALER_Amount melt_balance;
+ struct TALER_HashContext hash_context;
+ struct GNUNET_HashCode melt_hash;
+
+ res = process_post_json (connection,
+ connection_cls,
+ upload_data,
+ upload_data_size, &root);
+ if (GNUNET_SYSERR == res)
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (GNUNET_NO == res)
+ return MHD_YES;
+
+ if (NULL == (db_conn = TALER_MINT_DB_get_connection ()))
+ return GNUNET_SYSERR;
+
+ /* session_pub field must always be present */
+ res = request_json_require_nav (connection, root,
+ JNAV_FIELD, "session_pub",
+ JNAV_RET_DATA,
+ &refresh_session_pub,
+ sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
+ if (GNUNET_OK != res)
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (GNUNET_NO == res)
+ return MHD_YES;
+
+ /* Send response immediately if we already know the session.
+ * Do _not_ care about fields other than session_pub in this case. */
+
+ res = TALER_MINT_DB_get_refresh_session (db_conn,
+ &refresh_session_pub,
+ NULL);
+ if (GNUNET_YES == res)
+ return helper_refresh_send_melt_response (connection,
+ db_conn,
+ &refresh_session_pub);
+ if (GNUNET_SYSERR == res)
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ /* We incrementally update the db with other parameters in a transaction.
+ * The transaction is aborted if some parameter does not validate. */
+
+ if (GNUNET_OK != TALER_MINT_DB_transaction (db_conn))
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ if (GNUNET_OK != TALER_MINT_DB_create_refresh_session (db_conn,
+ &refresh_session_pub))
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ TALER_MINT_DB_rollback (db_conn);
+ return MHD_NO;
+ }
+
+ /* The next two operations must see the same key state,
+ * thus we acquire it here. */
+
+ key_state = TALER_MINT_key_state_acquire ();
+
+ /* Write requested denominations to the DB,
+ * and sum the costs (value plus fees) */
+
+ TALER_hash_context_start (&hash_context);
+
+ if (GNUNET_OK != (res = refresh_accept_denoms (connection, db_conn, key_state,
+ &refresh_session_pub, root,
+ &hash_context,
+ &requested_cost)))
+ {
+ TALER_MINT_key_state_release (key_state);
+ TALER_MINT_DB_rollback (db_conn);
+ // FIXME: hash_context_end?
+ return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
+ }
+
+ /* Write old coins to db and sum their value */
+
+ if (GNUNET_OK != (res = refresh_accept_melts (connection, db_conn, key_state,
+ &refresh_session_pub, root,
+ &hash_context,
+ &melt_balance)))
+ {
+ TALER_MINT_key_state_release (key_state);
+ GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn));
+ return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
+ }
+
+ TALER_hash_context_finish (&hash_context, &melt_hash);
+
+ TALER_MINT_key_state_release (key_state);
+
+ /* check that signature from the session public key is ok */
+ {
+ struct RefreshMeltSignatureBody body;
+ json_t *melt_sig_json;
+
+ melt_sig_json = json_object_get (root, "melt_signature");
+ if (NULL == melt_sig_json)
+ {
+ GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn));
+ return request_send_json_pack (connection,
+ MHD_HTTP_BAD_REQUEST,
+ "{s:s}",
+ "error", "melt_signature missing");
+ }
+
+ body.melt_hash = melt_hash;
+ body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_MELT);
+ body.purpose.size = htonl (sizeof (struct RefreshMeltSignatureBody));
+
+ if (GNUNET_OK != (res = request_json_check_signature (connection,
+ melt_sig_json,
+ &refresh_session_pub,
+ &body.purpose)))
+ {
+ GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn));
+ return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
+ }
+ }
+
+
+ /* Request is only ok if cost of requested coins
+ * does not exceed value of melted coins. */
+
+ // FIXME: also, consider fees?
+ if (TALER_amount_cmp (melt_balance, requested_cost) < 0)
+ {
+ GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn));
+
+ return request_send_json_pack (connection, MHD_HTTP_FORBIDDEN,
+ "{s:s}",
+ "error", "not enough coins melted");
+ }
+
+ if (GNUNET_OK != TALER_MINT_DB_commit (db_conn))
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ return helper_refresh_send_melt_response (connection,
+ db_conn,
+ &refresh_session_pub);
+}
+
+
+/**
+ * Send a response to a "/refresh/commit" request.
+ *
+ * @param connection the connection to send the response to
+ * @param db_conn the mint database
+ * @param refresh_session the refresh session
+ * @return a MHD status code
+ */
+static int
+refresh_send_commit_response (struct MHD_Connection *connection,
+ PGconn *db_conn,
+ struct RefreshSession *refresh_session)
+{
+ struct RefreshCommitResponseSignatureBody body;
+ json_t *sig_json;
+
+ body.purpose.size = htonl (sizeof (struct RefreshCommitResponseSignatureBody));
+ body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_COMMIT_RESPONSE);
+ body.noreveal_index = htons (refresh_session->noreveal_index);
+ sig_json = sign_as_json (&body.purpose);
+ GNUNET_assert (NULL != sig_json);
+ return request_send_json_pack (connection, MHD_HTTP_OK,
+ "{s:i, s:o}",
+ "noreveal_index", (int) refresh_session->noreveal_index,
+ "signature", sig_json);
+}
+
+
+/**
+ * Handle a "/refresh/commit" request
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_refresh_commit (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+ struct GNUNET_CRYPTO_EddsaPublicKey refresh_session_pub;
+ int res;
+ PGconn *db_conn;
+ struct RefreshSession refresh_session;
+ int i;
+ struct GNUNET_HashCode commit_hash;
+ struct TALER_HashContext hash_context;
+ json_t *root;
+
+ res = process_post_json (connection,
+ connection_cls,
+ upload_data,
+ upload_data_size, &root);
+ if (GNUNET_SYSERR == res)
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (GNUNET_NO == res)
+ return MHD_YES;
+
+
+ res = request_json_require_nav (connection, root,
+ JNAV_FIELD, "session_pub",
+ JNAV_RET_DATA,
+ &refresh_session_pub,
+ sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (GNUNET_SYSERR != res);
+ return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
+ }
+
+ if (NULL == (db_conn = TALER_MINT_DB_get_connection ()))
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ /* Send response immediately if we already know the session.
+ * Do _not_ care about fields other than session_pub in this case. */
+
+ res = TALER_MINT_DB_get_refresh_session (db_conn,
+ &refresh_session_pub,
+ &refresh_session);
+ if ( (GNUNET_YES == res) &&
+ (GNUNET_YES == refresh_session.has_commit_sig) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "sending cached commit response\n");
+ res = refresh_send_commit_response (connection,
+ db_conn,
+ &refresh_session);
+ GNUNET_break (res != GNUNET_SYSERR);
+ return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
+ }
+ if (GNUNET_SYSERR == res)
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ if (GNUNET_OK != TALER_MINT_DB_transaction (db_conn))
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ /* Re-fetch the session information from the database,
+ * in case a concurrent transaction modified it. */
+
+ res = TALER_MINT_DB_get_refresh_session (db_conn,
+ &refresh_session_pub,
+ &refresh_session);
+ if (GNUNET_OK != res)
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (GNUNET_SYSERR != res);
+ GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn));
+ return MHD_NO;
+ }
+
+ TALER_hash_context_start (&hash_context);
+
+ for (i = 0; i < refresh_session.kappa; i++)
+ {
+ unsigned int j;
+
+ for (j = 0; j < refresh_session.num_newcoins; j++)
+ {
+ struct RefreshCommitCoin commit_coin;
+
+ res = request_json_require_nav (connection, root,
+ JNAV_FIELD, "coin_evs",
+ JNAV_INDEX, (int) i,
+ JNAV_INDEX, (int) j,
+ JNAV_RET_DATA,
+ &commit_coin.coin_ev,
+ sizeof (struct TALER_RSA_BlindedSignaturePurpose));
+
+ if (GNUNET_OK != res)
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn));
+ return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
+ }
+
+ TALER_hash_context_read (&hash_context,
+ &commit_coin.coin_ev,
+ sizeof (struct TALER_RSA_BlindedSignaturePurpose));
+
+ res = request_json_require_nav (connection, root,
+ JNAV_FIELD, "link_encs",
+ JNAV_INDEX, (int) i,
+ JNAV_INDEX, (int) j,
+ JNAV_RET_DATA,
+ commit_coin.link_enc,
+ TALER_REFRESH_LINK_LENGTH);
+ if (GNUNET_OK != res)
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn));
+ return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
+ }
+
+ TALER_hash_context_read (&hash_context,
+ commit_coin.link_enc,
+ TALER_REFRESH_LINK_LENGTH);
+
+ commit_coin.cnc_index = i;
+ commit_coin.newcoin_index = j;
+ commit_coin.session_pub = refresh_session_pub;
+
+ if (GNUNET_OK !=
+ TALER_MINT_DB_insert_refresh_commit_coin (db_conn,
+ &commit_coin))
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn));
+ return MHD_NO;
+ }
+ }
+ }
+
+ for (i = 0; i < refresh_session.kappa; i++)
+ {
+ unsigned int j;
+ for (j = 0; j < refresh_session.num_oldcoins; j++)
+ {
+ struct RefreshCommitLink commit_link;
+
+ res = request_json_require_nav (connection, root,
+ JNAV_FIELD, "transfer_pubs",
+ JNAV_INDEX, (int) i,
+ JNAV_INDEX, (int) j,
+ JNAV_RET_DATA,
+ &commit_link.transfer_pub,
+ sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey));
+
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (GNUNET_SYSERR != res);
+ GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn));
+ return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
+ }
+
+ TALER_hash_context_read (&hash_context,
+ &commit_link.transfer_pub,
+ sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey));
+
+ res = request_json_require_nav (connection, root,
+ JNAV_FIELD, "secret_encs",
+ JNAV_INDEX, (int) i,
+ JNAV_INDEX, (int) j,
+ JNAV_RET_DATA,
+ commit_link.shared_secret_enc,
+ TALER_REFRESH_SHARED_SECRET_LENGTH);
+
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (GNUNET_SYSERR != res);
+ GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn));
+ return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
+ }
+
+ TALER_hash_context_read (&hash_context,
+ commit_link.shared_secret_enc,
+ TALER_REFRESH_SHARED_SECRET_LENGTH);
+
+ commit_link.cnc_index = i;
+ commit_link.oldcoin_index = j;
+ commit_link.session_pub = refresh_session_pub;
+
+ if (GNUNET_OK != TALER_MINT_DB_insert_refresh_commit_link (db_conn, &commit_link))
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn));
+ return MHD_NO;
+ }
+ }
+ }
+
+ TALER_hash_context_finish (&hash_context, &commit_hash);
+
+ {
+ struct RefreshCommitSignatureBody body;
+ json_t *commit_sig_json;
+
+ commit_sig_json = json_object_get (root, "commit_signature");
+ if (NULL == commit_sig_json)
+ {
+ GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn));
+ return request_send_json_pack (connection, MHD_HTTP_BAD_REQUEST,
+ "{s:s}",
+ "error", "commit_signature missing");
+ }
+
+ body.commit_hash = commit_hash;
+
+ body.purpose.purpose = htonl (TALER_SIGNATURE_REFRESH_COMMIT);
+ body.purpose.size = htonl (sizeof (struct RefreshCommitSignatureBody));
+
+ if (GNUNET_OK != (res = request_json_check_signature (connection,
+ commit_sig_json,
+ &refresh_session_pub,
+ &body.purpose)))
+ {
+ GNUNET_break (GNUNET_OK == TALER_MINT_DB_rollback (db_conn));
+ return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
+ }
+ }
+
+ if (GNUNET_OK != TALER_MINT_DB_commit (db_conn))
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ return refresh_send_commit_response (connection, db_conn, &refresh_session);
+}
+
+
+/**
+ * Send response for "/refresh/reveal".
+ *
+ * @param connection the MHD connection
+ * @param db_conn the connection to the mint's db
+ * @param refresh_session_pub the refresh session's public key
+ * @return a MHD result code
+ */
+static int
+helper_refresh_reveal_send_response (struct MHD_Connection *connection,
+ PGconn *db_conn,
+ struct GNUNET_CRYPTO_EddsaPublicKey *refresh_session_pub)
+{
+ int res;
+ int newcoin_index;
+ struct RefreshSession refresh_session;
+ json_t *root;
+ json_t *list;
+
+ res = TALER_MINT_DB_get_refresh_session (db_conn,
+ refresh_session_pub,
+ &refresh_session);
+ if (GNUNET_OK != res)
+ {
+ // FIXME: return 'internal error'
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ GNUNET_assert (0 != refresh_session.reveal_ok);
+
+ root = json_object ();
+ list = json_array ();
+ json_object_set_new (root, "ev_sigs", list);
+
+ for (newcoin_index = 0; newcoin_index < refresh_session.num_newcoins; newcoin_index++)
+ {
+ struct TALER_RSA_Signature ev_sig;
+
+ res = TALER_MINT_DB_get_refresh_collectable (db_conn,
+ newcoin_index,
+ refresh_session_pub,
+ &ev_sig);
+ if (GNUNET_OK != res)
+ {
+ // FIXME: return 'internal error'
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ json_array_append_new (list,
+ TALER_JSON_from_data (&ev_sig,
+ sizeof (struct TALER_RSA_Signature)));
+ }
+ return send_response_json (connection,
+ root,
+ MHD_HTTP_OK);
+}
+
+
+/**
+ * Handle a "/refresh/reveal" request
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_refresh_reveal (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+ struct GNUNET_CRYPTO_EddsaPublicKey refresh_session_pub;
+ int res;
+ PGconn *db_conn;
+ struct RefreshSession refresh_session;
+ struct MintKeyState *key_state;
+ int i;
+ int j;
+ json_t *root;
+
+ res = process_post_json (connection,
+ connection_cls,
+ upload_data, upload_data_size,
+ &root);
+ if (GNUNET_SYSERR == res)
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (GNUNET_NO == res)
+ return MHD_YES;
+
+ res = request_json_require_nav (connection, root,
+ JNAV_FIELD, "session_pub",
+ JNAV_RET_DATA,
+ &refresh_session_pub,
+ sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (GNUNET_SYSERR != res);
+ return res;
+ }
+
+ if (NULL == (db_conn = TALER_MINT_DB_get_connection ()))
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ /* Send response immediately if we already know the session,
+ * and the session commited already.
+ * Do _not_ care about fields other than session_pub in this case. */
+
+ res = TALER_MINT_DB_get_refresh_session (db_conn, &refresh_session_pub, &refresh_session);
+ if (GNUNET_YES == res && 0 != refresh_session.reveal_ok)
+ return helper_refresh_reveal_send_response (connection, db_conn, &refresh_session_pub);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ /* Check that the transfer private keys match their commitments.
+ * Then derive the shared secret for each kappa, and check that they match. */
+
+ for (i = 0; i < refresh_session.kappa; i++)
+ {
+ struct GNUNET_HashCode last_shared_secret;
+ int secret_initialized = GNUNET_NO;
+
+ if (i == (refresh_session.noreveal_index % refresh_session.kappa))
+ continue;
+
+ for (j = 0; j < refresh_session.num_oldcoins; j++)
+ {
+ struct GNUNET_CRYPTO_EcdsaPrivateKey transfer_priv;
+ struct RefreshCommitLink commit_link;
+ struct GNUNET_CRYPTO_EcdsaPublicKey coin_pub;
+ struct GNUNET_HashCode transfer_secret;
+ struct GNUNET_HashCode shared_secret;
+
+ res = request_json_require_nav (connection, root,
+ JNAV_FIELD, "transfer_privs",
+ JNAV_INDEX, (int) i,
+ JNAV_INDEX, (int) j,
+ JNAV_RET_DATA,
+ &transfer_priv,
+ sizeof (struct GNUNET_CRYPTO_EddsaPrivateKey));
+
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (GNUNET_SYSERR != res);
+ return res;
+ }
+
+ res = TALER_MINT_DB_get_refresh_commit_link (db_conn,
+ &refresh_session_pub,
+ i, j,
+ &commit_link);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ res = TALER_MINT_DB_get_refresh_melt (db_conn, &refresh_session_pub, j, &coin_pub);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* We're converting key types here, which is not very nice
+ * but necessary and harmless (keys will be thrown away later). */
+ if (GNUNET_OK != GNUNET_CRYPTO_ecc_ecdh ((struct GNUNET_CRYPTO_EcdhePrivateKey *) &transfer_priv,
+ (struct GNUNET_CRYPTO_EcdhePublicKey *) &coin_pub,
+ &transfer_secret))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 >= TALER_refresh_decrypt (commit_link.shared_secret_enc, TALER_REFRESH_SHARED_SECRET_LENGTH,
+ &transfer_secret, &shared_secret))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "decryption failed\n");
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_NO == secret_initialized)
+ {
+ secret_initialized = GNUNET_YES;
+ last_shared_secret = shared_secret;
+ }
+ else if (0 != memcmp (&shared_secret, &last_shared_secret, sizeof (struct GNUNET_HashCode)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "shared secrets do not match\n");
+ return GNUNET_SYSERR;
+ }
+
+ {
+ struct GNUNET_CRYPTO_EcdsaPublicKey transfer_pub_check;
+ GNUNET_CRYPTO_ecdsa_key_get_public (&transfer_priv, &transfer_pub_check);
+ if (0 != memcmp (&transfer_pub_check, &commit_link.transfer_pub, sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "transfer keys do not match\n");
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+
+ /* Check that the commitments for all new coins were correct */
+
+ for (j = 0; j < refresh_session.num_newcoins; j++)
+ {
+ struct RefreshCommitCoin commit_coin;
+ struct LinkData link_data;
+ struct TALER_RSA_BlindedSignaturePurpose *coin_ev_check;
+ struct GNUNET_CRYPTO_EcdsaPublicKey coin_pub;
+ struct TALER_RSA_BlindingKey *bkey;
+ struct TALER_RSA_PublicKeyBinaryEncoded denom_pub;
+
+ bkey = NULL;
+ res = TALER_MINT_DB_get_refresh_commit_coin (db_conn,
+ &refresh_session_pub,
+ i, j,
+ &commit_coin);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+
+ if (0 >= TALER_refresh_decrypt (commit_coin.link_enc, sizeof (struct LinkData),
+ &last_shared_secret, &link_data))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "decryption failed\n");
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_CRYPTO_ecdsa_key_get_public (&link_data.coin_priv, &coin_pub);
+ if (NULL == (bkey = TALER_RSA_blinding_key_decode (&link_data.bkey_enc)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Invalid blinding key\n");
+ return GNUNET_SYSERR;
+ }
+ res = TALER_MINT_DB_get_refresh_order (db_conn, j, &refresh_session_pub, &denom_pub);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (NULL == (coin_ev_check =
+ TALER_RSA_message_blind (&coin_pub,
+ sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
+ bkey,
+ &denom_pub)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "blind failed\n");
+ return GNUNET_SYSERR;
+ }
+
+ if (0 != memcmp (&coin_ev_check,
+ &commit_coin.coin_ev,
+ sizeof (struct TALER_RSA_BlindedSignaturePurpose)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "blind envelope does not match for kappa=%d, old=%d\n",
+ (int) i, (int) j);
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+
+
+ if (GNUNET_OK != TALER_MINT_DB_transaction (db_conn))
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ for (j = 0; j < refresh_session.num_newcoins; j++)
+ {
+ struct RefreshCommitCoin commit_coin;
+ struct TALER_RSA_PublicKeyBinaryEncoded denom_pub;
+ struct TALER_MINT_DenomKeyIssue *dki;
+ struct TALER_RSA_Signature ev_sig;
+
+ res = TALER_MINT_DB_get_refresh_commit_coin (db_conn,
+ &refresh_session_pub,
+ refresh_session.noreveal_index % refresh_session.kappa,
+ j,
+ &commit_coin);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ res = TALER_MINT_DB_get_refresh_order (db_conn, j, &refresh_session_pub, &denom_pub);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+
+ key_state = TALER_MINT_key_state_acquire ();
+ dki = TALER_MINT_get_denom_key (key_state, &denom_pub);
+ TALER_MINT_key_state_release (key_state);
+ if (NULL == dki)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_RSA_sign (dki->denom_priv,
+ &commit_coin.coin_ev,
+ sizeof (struct TALER_RSA_BlindedSignaturePurpose),
+ &ev_sig))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ res = TALER_MINT_DB_insert_refresh_collectable (db_conn,
+ j,
+ &refresh_session_pub,
+ &ev_sig);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ /* mark that reveal was successful */
+
+ res = TALER_MINT_DB_set_reveal_ok (db_conn, &refresh_session_pub);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK != TALER_MINT_DB_commit (db_conn))
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ return helper_refresh_reveal_send_response (connection, db_conn, &refresh_session_pub);
+}
+
+
+/**
+ * Handle a "/refresh/link" request
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_refresh_link (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+ struct GNUNET_CRYPTO_EcdsaPublicKey coin_pub;
+ int res;
+ json_t *root;
+ json_t *list;
+ PGconn *db_conn;
+ struct GNUNET_CRYPTO_EcdsaPublicKey transfer_pub;
+ struct SharedSecretEnc shared_secret_enc;
+
+ res = TALER_MINT_mhd_request_arg_data (connection,
+ "coin_pub",
+ &coin_pub,
+ sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey));
+ if (GNUNET_SYSERR == res)
+ {
+ // FIXME: return 'internal error'
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (GNUNET_OK != res)
+ return MHD_YES;
+
+ if (NULL == (db_conn = TALER_MINT_DB_get_connection ()))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ list = json_array ();
+ root = json_object ();
+ json_object_set_new (root, "new_coins", list);
+
+ res = TALER_db_get_transfer (db_conn,
+ &coin_pub,
+ &transfer_pub,
+ &shared_secret_enc);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (GNUNET_NO == res)
+ {
+ return request_send_json_pack (connection,
+ MHD_HTTP_NOT_FOUND,
+ "{s:s}",
+ "error", "link data not found (transfer)");
+ }
+ GNUNET_assert (GNUNET_OK == res);
+
+ res = TALER_db_get_link (db_conn, &coin_pub, link_iter, list);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (GNUNET_NO == res)
+ {
+ return request_send_json_pack (connection,
+ MHD_HTTP_NOT_FOUND,
+ "{s:s}",
+ "error", "link data not found (link)");
+ }
+ GNUNET_assert (GNUNET_OK == res);
+ json_object_set_new (root, "transfer_pub",
+ TALER_JSON_from_data (&transfer_pub,
+ sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)));
+ json_object_set_new (root, "secret_enc",
+ TALER_JSON_from_data (&shared_secret_enc,
+ sizeof (struct SharedSecretEnc)));
+ return send_response_json (connection, root, MHD_HTTP_OK);
+}
+
+
+/* end of taler-mint-httpd_refresh.c */
diff --git a/src/mint/taler-mint-httpd_refresh.h b/src/mint/taler-mint-httpd_refresh.h
new file mode 100644
index 000000000..20e7d97c2
--- /dev/null
+++ b/src/mint/taler-mint-httpd_refresh.h
@@ -0,0 +1,103 @@
+/*
+ This file is part of TALER
+ (C) 2014 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-mint-httpd_refresh.h
+ * @brief Handle /refresh/ requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MINT_HTTPD_REFRESH_H
+#define TALER_MINT_HTTPD_REFRESH_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-mint-httpd.h"
+
+
+/**
+ * Handle a "/refresh/melt" request
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_refresh_melt (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+
+/**
+ * Handle a "/refresh/commit" request
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_refresh_commit (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+
+/**
+ * Handle a "/refresh/link" request
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_refresh_link (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+
+/**
+ * Handle a "/refresh/reveal" request
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_refresh_reveal (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+
+#endif
diff --git a/src/mint/taler-mint-httpd_withdraw.c b/src/mint/taler-mint-httpd_withdraw.c
new file mode 100644
index 000000000..7ffa45706
--- /dev/null
+++ b/src/mint/taler-mint-httpd_withdraw.c
@@ -0,0 +1,400 @@
+/*
+ This file is part of TALER
+ (C) 2014 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-mint-httpd_withdraw.c
+ * @brief Handle /withdraw/ requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <libpq-fe.h>
+#include <pthread.h>
+#include "mint.h"
+#include "mint_db.h"
+#include "taler_types.h"
+#include "taler_signatures.h"
+#include "taler_rsa.h"
+#include "taler_json_lib.h"
+#include "taler_microhttpd_lib.h"
+#include "taler-mint-httpd_keys.h"
+#include "taler-mint-httpd_mhd.h"
+#include "taler-mint-httpd_withdraw.h"
+
+
+/**
+ * Convert a signature (with purpose) to
+ * a JSON object representation.
+ *
+ * @param purpose purpose of the signature
+ * @param signature the signature
+ * @return the JSON reporesentation of the signature with purpose
+ */
+static json_t *
+sig_to_json (const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+ const struct GNUNET_CRYPTO_EddsaSignature *signature)
+{
+ json_t *root;
+ json_t *el;
+
+ root = json_object ();
+
+ el = json_integer ((json_int_t) ntohl (purpose->size));
+ json_object_set_new (root, "size", el);
+
+ el = json_integer ((json_int_t) ntohl (purpose->purpose));
+ json_object_set_new (root, "purpose", el);
+
+ el = TALER_JSON_from_data (signature, sizeof (struct GNUNET_CRYPTO_EddsaSignature));
+ json_object_set_new (root, "sig", el);
+
+ return root;
+}
+
+
+/**
+ * Sign a reserve's status with the current signing key.
+ *
+ * @param reserve the reserve to sign
+ * @param key_state the key state containing the current
+ * signing private key
+ */
+static void
+sign_reserve (struct Reserve *reserve,
+ struct MintKeyState *key_state)
+{
+ reserve->status_sign_pub = key_state->current_sign_key_issue.signkey_pub;
+ reserve->status_sig_purpose.purpose = htonl (TALER_SIGNATURE_RESERVE_STATUS);
+ reserve->status_sig_purpose.size = htonl (sizeof (struct Reserve) -
+ offsetof (struct Reserve, status_sig_purpose));
+ GNUNET_CRYPTO_eddsa_sign (&key_state->current_sign_key_issue.signkey_priv,
+ &reserve->status_sig_purpose,
+ &reserve->status_sig);
+}
+
+
+/**
+ * Handle a "/withdraw/status" request
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_withdraw_status (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+ struct GNUNET_CRYPTO_EddsaPublicKey reserve_pub;
+ PGconn *db_conn;
+ int res;
+ struct Reserve reserve;
+ struct MintKeyState *key_state;
+ int must_update = GNUNET_NO;
+ json_t *json;
+
+ res = TALER_MINT_mhd_request_arg_data (connection,
+ "reserve_pub",
+ &reserve_pub,
+ sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
+ if (GNUNET_SYSERR == res)
+ {
+ // FIXME: return 'internal error'
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (GNUNET_OK != res)
+ return MHD_YES;
+ if (NULL == (db_conn = TALER_MINT_DB_get_connection ()))
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ res = TALER_MINT_DB_get_reserve (db_conn,
+ &reserve_pub,
+ &reserve);
+ if (GNUNET_SYSERR == res)
+ return TALER_MINT_helper_send_json_pack (rh,
+ connection,
+ connection_cls,
+ 0 /* no caching */,
+ MHD_HTTP_NOT_FOUND,
+ "{s:s}",
+ "error",
+ "Reserve not found");
+ if (GNUNET_OK != res)
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ key_state = TALER_MINT_key_state_acquire ();
+ if (0 != memcmp (&key_state->current_sign_key_issue.signkey_pub,
+ &reserve.status_sign_pub,
+ sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)))
+ {
+ sign_reserve (&reserve, key_state);
+ must_update = GNUNET_YES;
+ }
+ if ((GNUNET_YES == must_update) &&
+ (GNUNET_OK != TALER_MINT_DB_update_reserve (db_conn, &reserve, !must_update)))
+ {
+ GNUNET_break (0);
+ return MHD_YES;
+ }
+
+ /* Convert the public information of a reserve (i.e.
+ excluding private key) to a JSON object. */
+ json = json_object ();
+ json_object_set_new (json,
+ "balance",
+ TALER_JSON_from_amount (TALER_amount_ntoh (reserve.balance)));
+ json_object_set_new (json,
+ "expiration",
+ TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (reserve.expiration)));
+ json_object_set_new (json,
+ "signature",
+ sig_to_json (&reserve.status_sig_purpose,
+ &reserve.status_sig));
+
+ return send_response_json (connection,
+ json,
+ MHD_HTTP_OK);
+}
+
+
+/**
+ * Send positive, normal response for "/withdraw/sign".
+ *
+ * @param connection the connection to send the response to
+ * @param collectable the collectable blindcoin (i.e. the blindly signed coin)
+ * @return a MHD result code
+ */
+static int
+helper_withdraw_sign_send_reply (struct MHD_Connection *connection,
+ const struct CollectableBlindcoin *collectable)
+{
+ json_t *root = json_object ();
+
+ json_object_set_new (root, "ev_sig",
+ TALER_JSON_from_data (&collectable->ev_sig,
+ sizeof (struct TALER_RSA_Signature)));
+ return send_response_json (connection,
+ root,
+ MHD_HTTP_OK);
+}
+
+
+/**
+ * Handle a "/withdraw/sign" request
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_withdraw_sign (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size)
+{
+ struct TALER_WithdrawRequest wsrd;
+ int res;
+ PGconn *db_conn;
+ struct Reserve reserve;
+ struct MintKeyState *key_state;
+ struct CollectableBlindcoin collectable;
+ struct TALER_MINT_DenomKeyIssue *dki;
+ struct TALER_RSA_Signature ev_sig;
+ struct TALER_Amount amount_required;
+
+ memset (&wsrd,
+ 0,
+ sizeof (struct TALER_WithdrawRequest));
+ res = TALER_MINT_mhd_request_arg_data (connection,
+ "reserve_pub",
+ &wsrd.reserve_pub,
+ sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
+ if (GNUNET_SYSERR == res)
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (GNUNET_OK != res)
+ return MHD_YES;
+ res = TALER_MINT_mhd_request_arg_data (connection,
+ "denom_pub",
+ &wsrd.denomination_pub,
+ sizeof (struct TALER_RSA_PublicKeyBinaryEncoded));
+ if (GNUNET_SYSERR == res)
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (GNUNET_OK != res)
+ return MHD_YES;
+ res = TALER_MINT_mhd_request_arg_data (connection,
+ "coin_ev",
+ &wsrd.coin_envelope,
+ sizeof (struct TALER_RSA_Signature));
+ if (GNUNET_SYSERR == res)
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (GNUNET_OK != res)
+ return MHD_YES;
+ res = TALER_MINT_mhd_request_arg_data (connection,
+ "reserve_sig",
+ &wsrd.sig,
+ sizeof (struct GNUNET_CRYPTO_EddsaSignature));
+ if (GNUNET_SYSERR == res)
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (GNUNET_OK != res)
+ return MHD_YES;
+
+ if (NULL == (db_conn = TALER_MINT_DB_get_connection ()))
+ {
+ // FIXME: return 'internal error'?
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ res = TALER_MINT_DB_get_collectable_blindcoin (db_conn,
+ &wsrd.coin_envelope,
+ &collectable);
+ if (GNUNET_SYSERR == res)
+ {
+ // FIXME: return 'internal error'
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ /* Don't sign again if we have already signed the coin */
+ if (GNUNET_YES == res)
+ return helper_withdraw_sign_send_reply (connection,
+ &collectable);
+ GNUNET_assert (GNUNET_NO == res);
+ res = TALER_MINT_DB_get_reserve (db_conn,
+ &wsrd.reserve_pub,
+ &reserve);
+ if (GNUNET_SYSERR == res)
+ {
+ // FIXME: return 'internal error'
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if (GNUNET_NO == res)
+ return request_send_json_pack (connection,
+ MHD_HTTP_NOT_FOUND,
+ "{s:s}",
+ "error", "Reserve not found");
+
+ // fill out all the missing info in the request before
+ // we can check the signature on the request
+
+ wsrd.purpose.purpose = htonl (TALER_SIGNATURE_WITHDRAW);
+ wsrd.purpose.size = htonl (sizeof (struct TALER_WithdrawRequest) -
+ offsetof (struct TALER_WithdrawRequest, purpose));
+
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WITHDRAW,
+ &wsrd.purpose,
+ &wsrd.sig,
+ &wsrd.reserve_pub))
+ return request_send_json_pack (connection,
+ MHD_HTTP_UNAUTHORIZED,
+ "{s:s}",
+ "error", "Invalid Signature");
+
+ key_state = TALER_MINT_key_state_acquire ();
+ dki = TALER_MINT_get_denom_key (key_state,
+ &wsrd.denomination_pub);
+ TALER_MINT_key_state_release (key_state);
+ if (NULL == dki)
+ return request_send_json_pack (connection, MHD_HTTP_NOT_FOUND,
+ "{s:s}",
+ "error", "Denomination not found");
+
+ amount_required = TALER_amount_ntoh (dki->value);
+ amount_required = TALER_amount_add (amount_required,
+ TALER_amount_ntoh (dki->fee_withdraw));
+
+ if (0 < TALER_amount_cmp (amount_required,
+ TALER_amount_ntoh (reserve.balance)))
+ return request_send_json_pack (connection,
+ MHD_HTTP_PAYMENT_REQUIRED,
+ "{s:s}",
+ "error", "Insufficient funds");
+ if (GNUNET_OK != TALER_RSA_sign (dki->denom_priv,
+ &wsrd.coin_envelope,
+ sizeof (struct TALER_RSA_BlindedSignaturePurpose),
+ &ev_sig))
+ {
+ // FIXME: return 'internal error'
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ reserve.balance = TALER_amount_hton (TALER_amount_subtract (TALER_amount_ntoh (reserve.balance),
+ amount_required));
+ if (GNUNET_OK !=
+ TALER_MINT_DB_update_reserve (db_conn,
+ &reserve,
+ GNUNET_YES))
+ {
+ // FIXME: return 'internal error'
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ collectable.ev = wsrd.coin_envelope;
+ collectable.ev_sig = ev_sig;
+ collectable.reserve_pub = wsrd.reserve_pub;
+ collectable.reserve_sig = wsrd.sig;
+ if (GNUNET_OK !=
+ TALER_MINT_DB_insert_collectable_blindcoin (db_conn,
+ &collectable))
+ {
+ // FIXME: return 'internal error'
+ GNUNET_break (0);
+ return GNUNET_NO;;
+ }
+ return helper_withdraw_sign_send_reply (connection,
+ &collectable);
+}
+
+/* end of taler-mint-httpd_withdraw.c */
diff --git a/src/mint/taler-mint-httpd_withdraw.h b/src/mint/taler-mint-httpd_withdraw.h
new file mode 100644
index 000000000..1d292ebd9
--- /dev/null
+++ b/src/mint/taler-mint-httpd_withdraw.h
@@ -0,0 +1,65 @@
+/*
+ This file is part of TALER
+ (C) 2014 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-mint-httpd_withdraw.h
+ * @brief Handle /withdraw/ requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MINT_HTTPD_WITHDRAW_H
+#define TALER_MINT_HTTPD_WITHDRAW_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-mint-httpd.h"
+
+/**
+ * Handle a "/withdraw/status" request
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_withdraw_status (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+
+/**
+ * Handle a "/withdraw/sign" request
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[IN|OUT] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[IN|OUT] upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+int
+TALER_MINT_handler_withdraw_sign (struct RequestHandler *rh,
+ struct MHD_Connection *connection,
+ void **connection_cls,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+#endif
diff --git a/src/mint/taler-mint-keycheck.c b/src/mint/taler-mint-keycheck.c
new file mode 100644
index 000000000..c6186859c
--- /dev/null
+++ b/src/mint/taler-mint-keycheck.c
@@ -0,0 +1,169 @@
+/*
+ This file is part of TALER
+ (C) 2014 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 taler-mint-keycheck.c
+ * @brief Check mint keys for validity.
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ */
+
+#include <platform.h>
+#include <gnunet/gnunet_util_lib.h>
+#include "mint.h"
+#include "taler_signatures.h"
+
+
+static char *mintdir;
+static struct GNUNET_CONFIGURATION_Handle *kcfg;
+
+
+static int
+signkeys_iter (void *cls, const struct TALER_MINT_SignKeyIssue *ski)
+{
+ struct GNUNET_TIME_Absolute start;
+
+ printf ("iterating over key for start time %s\n",
+ GNUNET_STRINGS_absolute_time_to_string (GNUNET_TIME_absolute_ntoh (ski->start)));
+
+ start = GNUNET_TIME_absolute_ntoh (ski->start);
+
+ if (ntohl (ski->purpose.size) !=
+ (sizeof (struct TALER_MINT_SignKeyIssue) - offsetof (struct TALER_MINT_SignKeyIssue, purpose)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Signkey with start %s has invalid purpose field (timestamp: %llu)\n",
+ GNUNET_STRINGS_absolute_time_to_string (start),
+ (long long) start.abs_value_us);
+ return GNUNET_SYSERR;
+ }
+
+
+ if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNKEY,
+ &ski->purpose,
+ &ski->signature,
+ &ski->master_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Signkey with start %s has invalid signature (timestamp: %llu)\n",
+ GNUNET_STRINGS_absolute_time_to_string (start),
+ (long long) start.abs_value_us);
+ return GNUNET_SYSERR;
+ }
+ printf ("key valid\n");
+ return GNUNET_OK;
+}
+
+
+static int
+mint_signkeys_check ()
+{
+ if (0 > TALER_MINT_signkeys_iterate (mintdir, signkeys_iter, NULL))
+ return GNUNET_NO;
+ return GNUNET_OK;
+}
+
+
+static int denomkeys_iter (void *cls,
+ const char *alias,
+ const struct TALER_MINT_DenomKeyIssue *dki)
+{
+ struct GNUNET_TIME_Absolute start;
+
+ start = GNUNET_TIME_absolute_ntoh (dki->start);
+
+ if (ntohl (dki->purpose.size) !=
+ (sizeof (struct TALER_MINT_DenomKeyIssue) - offsetof (struct TALER_MINT_DenomKeyIssue, purpose)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Denomkey for '%s' with start %s has invalid purpose field (timestamp: %llu)\n",
+ alias,
+ GNUNET_STRINGS_absolute_time_to_string (start),
+ (long long) start.abs_value_us);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOM,
+ &dki->purpose,
+ &dki->signature,
+ &dki->master))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Denomkey for '%s'with start %s has invalid signature (timestamp: %llu)\n",
+ alias,
+ GNUNET_STRINGS_absolute_time_to_string (start),
+ (long long) start.abs_value_us);
+ return GNUNET_SYSERR;
+ }
+ printf ("denom key valid\n");
+
+ return GNUNET_OK;
+}
+
+
+static int
+mint_denomkeys_check ()
+{
+ if (0 > TALER_MINT_denomkeys_iterate (mintdir, denomkeys_iter, NULL))
+ return GNUNET_NO;
+ return GNUNET_OK;
+}
+
+
+static int
+mint_keys_check (void)
+{
+ if (GNUNET_OK != mint_signkeys_check ())
+ return GNUNET_NO;
+ return mint_denomkeys_check ();
+}
+
+
+/**
+ * The main function of the keyup tool
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc, char *const *argv)
+{
+ static const struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"),
+ {'d', "mint-dir", "DIR",
+ "mint directory with keys to update", 1,
+ &GNUNET_GETOPT_set_filename, &mintdir},
+ GNUNET_GETOPT_OPTION_END
+ };
+
+ GNUNET_assert (GNUNET_OK == GNUNET_log_setup ("taler-mint-keycheck", "WARNING", NULL));
+
+ if (GNUNET_GETOPT_run ("taler-mint-keyup", options, argc, argv) < 0)
+ return 1;
+ if (NULL == mintdir)
+ {
+ fprintf (stderr, "mint directory not given\n");
+ return 1;
+ }
+
+ kcfg = TALER_MINT_config_load (mintdir);
+ if (NULL == kcfg)
+ {
+ fprintf (stderr, "can't load mint configuration\n");
+ return 1;
+ }
+ if (GNUNET_OK != mint_keys_check ())
+ return 1;
+ return 0;
+}
+
diff --git a/src/mint/taler-mint-keyup.c b/src/mint/taler-mint-keyup.c
new file mode 100644
index 000000000..8a1a77882
--- /dev/null
+++ b/src/mint/taler-mint-keyup.c
@@ -0,0 +1,657 @@
+/*
+ This file is part of TALER
+ (C) 2014 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 taler-mint-keyup.c
+ * @brief Update the mint's keys for coins and signatures,
+ * using the mint's offline master key.
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ */
+
+#include <platform.h>
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "mint.h"
+
+#define HASH_CUTOFF 20
+
+/**
+ * Macro to round microseconds to seconds in GNUNET_TIME_* structs.
+ */
+#define ROUND_TO_SECS(name,us_field) name.us_field -= name.us_field % (1000 * 1000);
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+struct CoinTypeNBO
+{
+ struct GNUNET_TIME_RelativeNBO duration_spend;
+ struct GNUNET_TIME_RelativeNBO duration_withdraw;
+ struct TALER_AmountNBO value;
+ struct TALER_AmountNBO fee_withdraw;
+ struct TALER_AmountNBO fee_deposit;
+ struct TALER_AmountNBO fee_refresh;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+struct CoinTypeParams
+{
+ struct GNUNET_TIME_Relative duration_spend;
+ struct GNUNET_TIME_Relative duration_withdraw;
+ struct GNUNET_TIME_Relative duration_overlap;
+ struct TALER_Amount value;
+ struct TALER_Amount fee_withdraw;
+ struct TALER_Amount fee_deposit;
+ struct TALER_Amount fee_refresh;
+ struct GNUNET_TIME_Absolute anchor;
+};
+
+
+/**
+ * Filename of the master private key.
+ */
+static char *masterkeyfile;
+
+/**
+ * Director of the mint, containing the keys.
+ */
+static char *mintdir;
+
+/**
+ * Time to pretend when the key update is executed.
+ */
+static char *pretend_time_str;
+
+/**
+ * Handle to the mint's configuration
+ */
+static struct GNUNET_CONFIGURATION_Handle *kcfg;
+
+/**
+ * Time when the key update is executed. Either the actual current time, or a
+ * pretended time.
+ */
+static struct GNUNET_TIME_Absolute now;
+
+/**
+ * Master private key of the mint.
+ */
+static struct GNUNET_CRYPTO_EddsaPrivateKey *master_priv;
+
+/**
+ * Master public key of the mint.
+ */
+static struct GNUNET_CRYPTO_EddsaPublicKey *master_pub;
+
+/**
+ * Until what time do we provide keys?
+ */
+static struct GNUNET_TIME_Absolute lookahead_sign_stamp;
+
+
+int
+config_get_denom (const char *section, const char *option, struct TALER_Amount *denom)
+{
+ char *str;
+ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (kcfg, section, option, &str))
+ return GNUNET_NO;
+ if (GNUNET_OK != TALER_string_to_amount (str, denom))
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+}
+
+
+char *
+get_signkey_dir ()
+{
+ char *dir;
+ size_t len;
+ len = GNUNET_asprintf (&dir, ("%s" DIR_SEPARATOR_STR DIR_SIGNKEYS), mintdir);
+ GNUNET_assert (len > 0);
+ return dir;
+}
+
+
+char *
+get_signkey_file (struct GNUNET_TIME_Absolute start)
+{
+ char *dir;
+ size_t len;
+ len = GNUNET_asprintf (&dir, ("%s" DIR_SEPARATOR_STR DIR_SIGNKEYS DIR_SEPARATOR_STR "%llu"),
+ mintdir, (long long) start.abs_value_us);
+ GNUNET_assert (len > 0);
+ return dir;
+}
+
+
+
+/**
+ * Hash the data defining the coin type.
+ * Exclude information that may not be the same for all
+ * instances of the coin type (i.e. the anchor, overlap).
+ */
+void
+hash_coin_type (const struct CoinTypeParams *p, struct GNUNET_HashCode *hash)
+{
+ struct CoinTypeNBO p_nbo;
+
+ memset (&p_nbo, 0, sizeof (struct CoinTypeNBO));
+
+ p_nbo.duration_spend = GNUNET_TIME_relative_hton (p->duration_spend);
+ p_nbo.duration_withdraw = GNUNET_TIME_relative_hton (p->duration_withdraw);
+ p_nbo.value = TALER_amount_hton (p->value);
+ p_nbo.fee_withdraw = TALER_amount_hton (p->fee_withdraw);
+ p_nbo.fee_deposit = TALER_amount_hton (p->fee_deposit);
+ p_nbo.fee_refresh = TALER_amount_hton (p->fee_refresh);
+
+ GNUNET_CRYPTO_hash (&p_nbo, sizeof (struct CoinTypeNBO), hash);
+}
+
+
+static const char *
+get_cointype_dir (const struct CoinTypeParams *p)
+{
+ static char dir[4096];
+ size_t len;
+ struct GNUNET_HashCode hash;
+ char *hash_str;
+ char *val_str;
+ unsigned int i;
+
+ hash_coin_type (p, &hash);
+ hash_str = TALER_data_to_string_alloc (&hash, sizeof (struct GNUNET_HashCode));
+ GNUNET_assert (HASH_CUTOFF <= strlen (hash_str) + 1);
+ GNUNET_assert (NULL != hash_str);
+ hash_str[HASH_CUTOFF] = 0;
+
+ val_str = TALER_amount_to_string (p->value);
+ for (i = 0; i < strlen (val_str); i++)
+ if (':' == val_str[i] || '.' == val_str[i])
+ val_str[i] = '_';
+
+ len = GNUNET_snprintf (dir, sizeof (dir),
+ ("%s" DIR_SEPARATOR_STR DIR_DENOMKEYS DIR_SEPARATOR_STR "%s-%s"),
+ mintdir, val_str, hash_str);
+ GNUNET_assert (len > 0);
+ GNUNET_free (hash_str);
+ return dir;
+}
+
+
+static const char *
+get_cointype_file (struct CoinTypeParams *p,
+ struct GNUNET_TIME_Absolute start)
+{
+ const char *dir;
+ static char filename[4096];
+ size_t len;
+ dir = get_cointype_dir (p);
+ len = GNUNET_snprintf (filename, sizeof (filename), ("%s" DIR_SEPARATOR_STR "%llu"),
+ dir, (unsigned long long) start.abs_value_us);
+ GNUNET_assert (len > 0);
+ return filename;
+}
+
+
+/**
+ * Get the latest key file from the past.
+ *
+ * @param cls closure
+ * @param filename complete filename (absolute path)
+ * @return #GNUNET_OK to continue to iterate,
+ * #GNUNET_NO to stop iteration with no error,
+ * #GNUNET_SYSERR to abort iteration with error!
+ */
+static int
+get_anchor_iter (void *cls,
+ const char *filename)
+{
+ struct GNUNET_TIME_Absolute stamp;
+ struct GNUNET_TIME_Absolute *anchor = cls;
+ const char *base;
+ char *end = NULL;
+
+ base = GNUNET_STRINGS_get_short_name (filename);
+ stamp.abs_value_us = strtol (base, &end, 10);
+
+ if ((NULL == end) || (0 != *end))
+ {
+ fprintf(stderr, "Ignoring unexpected file '%s'.\n", filename);
+ return GNUNET_OK;
+ }
+
+ // TODO: check if it's actually a valid key file
+
+ if ((stamp.abs_value_us <= now.abs_value_us) && (stamp.abs_value_us > anchor->abs_value_us))
+ *anchor = stamp;
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Get the timestamp where the first new key should be generated.
+ * Relies on correctly named key files.
+ *
+ * @param dir directory with the signed stuff
+ * @param duration how long is one key valid?
+ * @param overlap what's the overlap between the keys validity period?
+ * @param[out] anchor the timestamp where the first new key should be generated
+ */
+void
+get_anchor (const char *dir,
+ struct GNUNET_TIME_Relative duration,
+ struct GNUNET_TIME_Relative overlap,
+ struct GNUNET_TIME_Absolute *anchor)
+{
+ GNUNET_assert (0 == duration.rel_value_us % 1000000);
+ GNUNET_assert (0 == overlap.rel_value_us % 1000000);
+ if (GNUNET_YES != GNUNET_DISK_directory_test (dir, GNUNET_YES))
+ {
+ *anchor = now;
+ printf ("Can't look for anchor (%s)\n", dir);
+ return;
+ }
+
+ *anchor = GNUNET_TIME_UNIT_ZERO_ABS;
+ if (-1 == GNUNET_DISK_directory_scan (dir, &get_anchor_iter, anchor))
+ {
+ *anchor = now;
+ return;
+ }
+
+ if ((GNUNET_TIME_absolute_add (*anchor, duration)).abs_value_us < now.abs_value_us)
+ {
+ // there's no good anchor, start from now
+ // (existing keys are too old)
+ *anchor = now;
+ }
+ else if (anchor->abs_value_us != now.abs_value_us)
+ {
+ // we have a good anchor
+ *anchor = GNUNET_TIME_absolute_add (*anchor, duration);
+ *anchor = GNUNET_TIME_absolute_subtract (*anchor, overlap);
+ }
+ // anchor is now the stamp where we need to create a new key
+}
+
+static void
+create_signkey_issue (struct GNUNET_TIME_Absolute start,
+ struct GNUNET_TIME_Relative duration,
+ struct TALER_MINT_SignKeyIssue *issue)
+{
+ struct GNUNET_CRYPTO_EddsaPrivateKey *priv;
+
+ priv = GNUNET_CRYPTO_eddsa_key_create ();
+ GNUNET_assert (NULL != priv);
+ issue->signkey_priv = *priv;
+ GNUNET_free (priv);
+ issue->master_pub = *master_pub;
+ issue->start = GNUNET_TIME_absolute_hton (start);
+ issue->expire = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (start, duration));
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&issue->signkey_priv, &issue->signkey_pub);
+
+ issue->purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNKEY);
+ issue->purpose.size = htonl (sizeof (struct TALER_MINT_SignKeyIssue) - offsetof (struct TALER_MINT_SignKeyIssue, purpose));
+
+ if (GNUNET_OK != GNUNET_CRYPTO_eddsa_sign (master_priv, &issue->purpose, &issue->signature))
+ {
+ GNUNET_abort ();
+ }
+}
+
+
+static int
+check_signkey_valid (const char *signkey_filename)
+{
+ // FIXME: do real checks
+ return GNUNET_OK;
+}
+
+
+int
+mint_keys_update_signkeys ()
+{
+ struct GNUNET_TIME_Relative signkey_duration;
+ struct GNUNET_TIME_Absolute anchor;
+ char *signkey_dir;
+
+ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_keys", "signkey_duration", &signkey_duration))
+ {
+ fprintf (stderr, "Can't read config value mint_keys.signkey_duration\n");
+ return GNUNET_SYSERR;
+ }
+ ROUND_TO_SECS (signkey_duration, rel_value_us);
+ signkey_dir = get_signkey_dir ();
+ // make sure the directory exists
+ if (GNUNET_OK != GNUNET_DISK_directory_create (signkey_dir))
+ {
+ fprintf (stderr, "Cant create signkey dir\n");
+ return GNUNET_SYSERR;
+ }
+
+ get_anchor (signkey_dir, signkey_duration, GNUNET_TIME_UNIT_ZERO, &anchor);
+
+ while (anchor.abs_value_us < lookahead_sign_stamp.abs_value_us) {
+ char *skf;
+ skf = get_signkey_file (anchor);
+ if (GNUNET_YES != GNUNET_DISK_file_test (skf))
+ {
+ struct TALER_MINT_SignKeyIssue signkey_issue;
+ ssize_t nwrite;
+ printf ("Generating signing key for %s.\n", GNUNET_STRINGS_absolute_time_to_string (anchor));
+ create_signkey_issue (anchor, signkey_duration, &signkey_issue);
+ nwrite = GNUNET_DISK_fn_write (skf, &signkey_issue, sizeof (struct TALER_MINT_SignKeyIssue),
+ (GNUNET_DISK_PERM_USER_WRITE | GNUNET_DISK_PERM_USER_READ));
+ if (nwrite != sizeof (struct TALER_MINT_SignKeyIssue))
+ {
+ fprintf (stderr, "Can't write to file '%s'\n", skf);
+ return GNUNET_SYSERR;
+ }
+ }
+ else if (GNUNET_OK != check_signkey_valid (skf))
+ {
+ return GNUNET_SYSERR;
+ }
+ anchor = GNUNET_TIME_absolute_add (anchor, signkey_duration);
+ }
+ return GNUNET_OK;
+}
+
+
+int
+get_cointype_params (const char *ct, struct CoinTypeParams *params)
+{
+ const char *dir;
+ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_denom_duration_withdraw", ct, &params->duration_withdraw))
+ {
+ fprintf (stderr, "Withdraw duration not given for coin type '%s'\n", ct);
+ return GNUNET_SYSERR;
+ }
+ ROUND_TO_SECS (params->duration_withdraw, rel_value_us);
+ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_denom_duration_spend", ct, &params->duration_spend))
+ {
+ fprintf (stderr, "Spend duration not given for coin type '%s'\n", ct);
+ return GNUNET_SYSERR;
+ }
+ ROUND_TO_SECS (params->duration_spend, rel_value_us);
+ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_denom_duration_overlap", ct, &params->duration_overlap))
+ {
+ fprintf (stderr, "Overlap duration not given for coin type '%s'\n", ct);
+ return GNUNET_SYSERR;
+ }
+ ROUND_TO_SECS (params->duration_overlap, rel_value_us);
+
+ if (GNUNET_OK != config_get_denom ("mint_denom_value", ct, &params->value))
+ {
+ fprintf (stderr, "Value not given for coin type '%s'\n", ct);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK != config_get_denom ("mint_denom_fee_withdraw", ct, &params->fee_withdraw))
+ {
+ fprintf (stderr, "Withdraw fee not given for coin type '%s'\n", ct);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK != config_get_denom ("mint_denom_fee_deposit", ct, &params->fee_deposit))
+ {
+ fprintf (stderr, "Deposit fee not given for coin type '%s'\n", ct);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK != config_get_denom ("mint_denom_fee_refresh", ct, &params->fee_refresh))
+ {
+ fprintf (stderr, "Deposit fee not given for coin type '%s'\n", ct);
+ return GNUNET_SYSERR;
+ }
+
+ dir = get_cointype_dir (params);
+ get_anchor (dir, params->duration_spend, params->duration_overlap, &params->anchor);
+ return GNUNET_OK;
+}
+
+
+static void
+create_denomkey_issue (struct CoinTypeParams *params, struct TALER_MINT_DenomKeyIssue *dki)
+{
+ GNUNET_assert (NULL != (dki->denom_priv = TALER_RSA_key_create ()));
+ TALER_RSA_key_get_public (dki->denom_priv, &dki->denom_pub);
+ dki->master = *master_pub;
+ dki->start = GNUNET_TIME_absolute_hton (params->anchor);
+ dki->expire_withdraw =
+ GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor,
+ params->duration_withdraw));
+ dki->expire_spend =
+ GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor,
+ params->duration_spend));
+ dki->value = TALER_amount_hton (params->value);
+ dki->fee_withdraw = TALER_amount_hton (params->fee_withdraw);
+ dki->fee_deposit = TALER_amount_hton (params->fee_deposit);
+ dki->fee_refresh = TALER_amount_hton (params->fee_refresh);
+
+ dki->purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DENOM);
+ dki->purpose.size = htonl (sizeof (struct TALER_MINT_DenomKeyIssue) - offsetof (struct TALER_MINT_DenomKeyIssue, purpose));
+
+ if (GNUNET_OK != GNUNET_CRYPTO_eddsa_sign (master_priv, &dki->purpose, &dki->signature))
+ {
+ GNUNET_abort ();
+ }
+}
+
+
+static int
+check_cointype_valid (const char *filename, struct CoinTypeParams *params)
+{
+ // FIXME: add real checks
+ return GNUNET_OK;
+}
+
+
+int
+mint_keys_update_cointype (const char *coin_alias)
+{
+ struct CoinTypeParams p;
+ const char *cointype_dir;
+
+ if (GNUNET_OK != get_cointype_params (coin_alias, &p))
+ return GNUNET_SYSERR;
+
+ cointype_dir = get_cointype_dir (&p);
+ if (GNUNET_OK != GNUNET_DISK_directory_create (cointype_dir))
+ return GNUNET_SYSERR;
+
+ while (p.anchor.abs_value_us < lookahead_sign_stamp.abs_value_us) {
+ const char *dkf;
+ dkf = get_cointype_file (&p, p.anchor);
+
+ if (GNUNET_YES != GNUNET_DISK_file_test (dkf))
+ {
+ struct TALER_MINT_DenomKeyIssue denomkey_issue;
+ int ret;
+ printf ("Generating denomination key for type '%s', start %s.\n",
+ coin_alias, GNUNET_STRINGS_absolute_time_to_string (p.anchor));
+ printf ("Target path: %s\n", dkf);
+ create_denomkey_issue (&p, &denomkey_issue);
+ ret = TALER_MINT_write_denom_key (dkf, &denomkey_issue);
+ TALER_RSA_key_free (denomkey_issue.denom_priv);
+ if (GNUNET_OK != ret)
+ {
+ fprintf (stderr, "Can't write to file '%s'\n", dkf);
+ return GNUNET_SYSERR;
+ }
+ }
+ else if (GNUNET_OK != check_cointype_valid (dkf, &p))
+ {
+ return GNUNET_SYSERR;
+ }
+ p.anchor = GNUNET_TIME_absolute_add (p.anchor, p.duration_spend);
+ p.anchor = GNUNET_TIME_absolute_subtract (p.anchor, p.duration_overlap);
+ }
+ return GNUNET_OK;
+}
+
+
+int
+mint_keys_update_denomkeys ()
+{
+ char *coin_types;
+ char *ct;
+ char *tok_ctx;
+
+ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (kcfg, "mint_keys", "coin_types", &coin_types))
+ {
+ fprintf (stderr, "mint_keys.coin_types not in configuration\n");
+ return GNUNET_SYSERR;
+ }
+
+ for (ct = strtok_r (coin_types, " ", &tok_ctx);
+ ct != NULL;
+ ct = strtok_r (NULL, " ", &tok_ctx))
+ {
+ if (GNUNET_OK != mint_keys_update_cointype (ct))
+ {
+ GNUNET_free (coin_types);
+ return GNUNET_SYSERR;
+ }
+ }
+ GNUNET_free (coin_types);
+ return GNUNET_OK;
+}
+
+
+static int
+mint_keys_update ()
+{
+ int ret;
+ struct GNUNET_TIME_Relative lookahead_sign;
+ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (kcfg, "mint_keys", "lookahead_sign", &lookahead_sign))
+ {
+ fprintf (stderr, "mint_keys.lookahead_sign not found\n");
+ return GNUNET_SYSERR;
+ }
+ if (lookahead_sign.rel_value_us == 0)
+ {
+ fprintf (stderr, "mint_keys.lookahead_sign must not be zero\n");
+ return GNUNET_SYSERR;
+ }
+ ROUND_TO_SECS (lookahead_sign, rel_value_us);
+ lookahead_sign_stamp = GNUNET_TIME_absolute_add (now, lookahead_sign);
+
+ ret = mint_keys_update_signkeys ();
+ if (GNUNET_OK != ret)
+ return GNUNET_SYSERR;
+
+ return mint_keys_update_denomkeys ();
+}
+
+
+/**
+ * The main function of the keyup tool
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc, char *const *argv)
+{
+ static const struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"),
+ {'m', "master-key", "FILE",
+ "master key file (private key)", 1,
+ &GNUNET_GETOPT_set_filename, &masterkeyfile},
+ {'d', "mint-dir", "DIR",
+ "mint directory with keys to update", 1,
+ &GNUNET_GETOPT_set_filename, &mintdir},
+ {'t', "time", "TIMESTAMP",
+ "pretend it is a different time for the update", 0,
+ &GNUNET_GETOPT_set_string, &pretend_time_str},
+ GNUNET_GETOPT_OPTION_END
+ };
+
+ GNUNET_assert (GNUNET_OK == GNUNET_log_setup ("taler-mint-keyup", "WARNING", NULL));
+
+ if (GNUNET_GETOPT_run ("taler-mint-keyup", options, argc, argv) < 0)
+ return 1;
+ if (NULL == mintdir)
+ {
+ fprintf (stderr, "mint directory not given\n");
+ return 1;
+ }
+
+ if (NULL != pretend_time_str)
+ {
+ if (GNUNET_OK != GNUNET_STRINGS_fancy_time_to_absolute (pretend_time_str, &now))
+ {
+ fprintf (stderr, "timestamp invalid\n");
+ return 1;
+ }
+ }
+ else
+ {
+ now = GNUNET_TIME_absolute_get ();
+ }
+ ROUND_TO_SECS (now, abs_value_us);
+
+ kcfg = TALER_MINT_config_load (mintdir);
+ if (NULL == kcfg)
+ {
+ fprintf (stderr, "can't load mint configuration\n");
+ return 1;
+ }
+
+ if (NULL == masterkeyfile)
+ {
+ fprintf (stderr, "master key file not given\n");
+ return 1;
+ }
+ master_priv = GNUNET_CRYPTO_eddsa_key_create_from_file (masterkeyfile);
+ if (NULL == master_priv)
+ {
+ fprintf (stderr, "master key invalid\n");
+ return 1;
+ }
+
+ master_pub = GNUNET_new (struct GNUNET_CRYPTO_EddsaPublicKey);
+ GNUNET_CRYPTO_eddsa_key_get_public (master_priv, master_pub);
+
+ // check if key from file matches the one from the configuration
+ {
+ struct GNUNET_CRYPTO_EddsaPublicKey master_pub_from_cfg;
+ if (GNUNET_OK != TALER_configuration_get_data (kcfg, "mint", "master_pub",
+ &master_pub_from_cfg,
+ sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)))
+ {
+ fprintf (stderr, "master key missing in configuration (mint.master_pub)\n");
+ return 1;
+ }
+ if (0 != memcmp (master_pub, &master_pub_from_cfg, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)))
+ {
+ fprintf (stderr, "Mismatch between key from mint configuration and master private key file from command line.\n");
+ return 1;
+ }
+ }
+
+ if (GNUNET_OK != mint_keys_update ())
+ return 1;
+ return 0;
+}
+
diff --git a/src/mint/taler-mint-reservemod.c b/src/mint/taler-mint-reservemod.c
new file mode 100644
index 000000000..3dd94f84b
--- /dev/null
+++ b/src/mint/taler-mint-reservemod.c
@@ -0,0 +1,215 @@
+/*
+ This file is part of TALER
+ (C) 2014 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 taler-mint-reservemod.c
+ * @brief Modify reserves.
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ */
+
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <libpq-fe.h>
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "mint.h"
+
+static char *mintdir;
+static struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub;
+static struct GNUNET_CONFIGURATION_Handle *kcfg;
+static PGconn *db_conn;
+
+
+
+/**
+ * Create a new or add to existing reserve.
+ * Fails if currencies do not match.
+ *
+ * @param denom denomination to add
+ *
+ * @return ...
+ */
+int
+reservemod_add (struct TALER_Amount denom)
+{
+ PGresult *result;
+ {
+ const void *param_values[] = { reserve_pub };
+ int param_lengths[] = {sizeof(struct GNUNET_CRYPTO_EddsaPublicKey)};
+ int param_formats[] = {1};
+ result = PQexecParams (db_conn,
+ "select balance_value, balance_fraction, balance_currency from reserves where reserve_pub=$1 limit 1;",
+ 1, NULL, (const char * const *) param_values, param_lengths, param_formats, 1);
+ }
+
+ if (PGRES_TUPLES_OK != PQresultStatus (result))
+ {
+ fprintf (stderr, "Select failed: %s\n", PQresultErrorMessage (result));
+ return GNUNET_SYSERR;
+ }
+ if (0 == PQntuples (result))
+ {
+ struct GNUNET_TIME_AbsoluteNBO exnbo;
+ exnbo = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add ( GNUNET_TIME_absolute_get (), GNUNET_TIME_UNIT_YEARS));
+
+ uint32_t value = htonl (denom.value);
+ uint32_t fraction = htonl (denom.fraction);
+ const void *param_values[] = {
+ reserve_pub,
+ &value,
+ &fraction,
+ denom.currency,
+ &exnbo};
+ int param_lengths[] = {32, 4, 4, strlen(denom.currency), 8};
+ int param_formats[] = {1, 1, 1, 1, 1};
+ result = PQexecParams (db_conn,
+ "insert into reserves (reserve_pub, balance_value, balance_fraction, balance_currency, "
+ " expiration_date )"
+ "values ($1,$2,$3,$4,$5);",
+ 5, NULL, (const char **) param_values, param_lengths, param_formats, 1);
+
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ fprintf (stderr, "Insert failed: %s\n", PQresultErrorMessage (result));
+ return GNUNET_SYSERR;
+ }
+ }
+ else
+ {
+ struct TALER_Amount old_denom;
+ struct TALER_Amount new_denom;
+ struct TALER_AmountNBO new_denom_nbo;
+ int denom_indices[] = {0, 1, 2};
+ int param_lengths[] = {4, 4, 32};
+ int param_formats[] = {1, 1, 1};
+ const void *param_values[] = {
+ &new_denom_nbo.value,
+ &new_denom_nbo.fraction,
+ reserve_pub
+ };
+
+ GNUNET_assert (GNUNET_OK == TALER_TALER_DB_extract_amount (result, 0, denom_indices, &old_denom));
+ new_denom = TALER_amount_add (old_denom, denom);
+ new_denom_nbo = TALER_amount_hton (new_denom);
+ result = PQexecParams (db_conn,
+ "UPDATE reserves "
+ "SET balance_value = $1, balance_fraction = $2, "
+ " status_sig = NULL, status_sign_pub = NULL "
+ "WHERE reserve_pub = $3 ",
+ 3, NULL, (const char **) param_values, param_lengths, param_formats, 1);
+
+ if (PGRES_COMMAND_OK != PQresultStatus (result))
+ {
+ fprintf (stderr, "Update failed: %s\n", PQresultErrorMessage (result));
+ return GNUNET_SYSERR;
+ }
+
+ if (0 != strcmp ("1", PQcmdTuples (result)))
+ {
+ fprintf (stderr, "Update failed (updated '%s' tupes instead of '1')\n",
+ PQcmdTuples (result));
+ return GNUNET_SYSERR;
+ }
+
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * The main function of the reservemod tool
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc, char *const *argv)
+{
+ static char *reserve_pub_str;
+ static char *add_str;
+ static const struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"),
+ {'d', "mint-dir", "DIR",
+ "mint directory with keys to update", 1,
+ &GNUNET_GETOPT_set_filename, &mintdir},
+ {'R', "reserve", "KEY",
+ "reserve (public key) to modify", 1,
+ &GNUNET_GETOPT_set_string, &reserve_pub_str},
+ {'a', "add", "DENOM",
+ "value to add", 1,
+ &GNUNET_GETOPT_set_string, &add_str},
+ GNUNET_GETOPT_OPTION_END
+ };
+ char *TALER_MINT_db_connection_cfg_str;
+
+ GNUNET_assert (GNUNET_OK == GNUNET_log_setup ("taler-mint-keycheck", "WARNING", NULL));
+
+ if (GNUNET_GETOPT_run ("taler-mint-keyup", options, argc, argv) < 0)
+ return 1;
+ if (NULL == mintdir)
+ {
+ fprintf (stderr, "mint directory not given\n");
+ return 1;
+ }
+
+ reserve_pub = GNUNET_new (struct GNUNET_CRYPTO_EddsaPublicKey);
+ if ((NULL == reserve_pub_str) ||
+ (GNUNET_OK != GNUNET_STRINGS_string_to_data (reserve_pub_str,
+ strlen (reserve_pub_str),
+ reserve_pub,
+ sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))))
+ {
+ fprintf (stderr, "reserve key invalid\n");
+ return 1;
+ }
+
+ kcfg = TALER_MINT_config_load (mintdir);
+ if (NULL == kcfg)
+ {
+ fprintf (stderr, "can't load mint configuration\n");
+ return 1;
+ }
+ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (kcfg, "mint", "db", &TALER_MINT_db_connection_cfg_str))
+ {
+ fprintf (stderr, "db configuration string not found\n");
+ return 42;
+ }
+ db_conn = PQconnectdb (TALER_MINT_db_connection_cfg_str);
+ if (CONNECTION_OK != PQstatus (db_conn))
+ {
+ fprintf (stderr, "db connection failed: %s\n", PQerrorMessage (db_conn));
+ return 1;
+ }
+
+ if (NULL != add_str)
+ {
+ struct TALER_Amount add_value;
+ if (GNUNET_OK != TALER_string_to_amount (add_str, &add_value))
+ {
+ fprintf (stderr, "could not read value\n");
+ return 1;
+ }
+ if (GNUNET_OK != reservemod_add (add_value))
+ {
+ fprintf (stderr, "adding value failed\n");
+ return 1;
+ }
+ }
+ return 0;
+}
+
diff --git a/src/mint/test_mint_api.c b/src/mint/test_mint_api.c
new file mode 100644
index 000000000..965d607f5
--- /dev/null
+++ b/src/mint/test_mint_api.c
@@ -0,0 +1,211 @@
+/*
+ This file is part of TALER
+ (C) 2014 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/test_mint_api.c
+ * @brief testcase to test mint's HTTP API interface
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ */
+
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_mint_service.h"
+
+struct TALER_MINT_Context *ctx;
+
+struct TALER_MINT_Handle *mint;
+
+struct TALER_MINT_KeysGetHandle *dkey_get;
+
+struct TALER_MINT_DepositHandle *dh;
+
+static GNUNET_SCHEDULER_TaskIdentifier shutdown_task;
+
+static int result;
+
+
+static void
+do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+ shutdown_task = GNUNET_SCHEDULER_NO_TASK;
+ if (NULL != dkey_get)
+ TALER_MINT_keys_get_cancel (dkey_get);
+ dkey_get = NULL;
+ if (NULL != dh)
+ TALER_MINT_deposit_submit_cancel (dh);
+ dh = NULL;
+ TALER_MINT_disconnect (mint);
+ mint = NULL;
+ TALER_MINT_cleanup (ctx);
+ ctx = NULL;
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a deposit
+ * permission object to a mint
+ *
+ * @param cls closure
+ * @param status 1 for successful deposit, 2 for retry, 0 for failure
+ * @param obj the received JSON object; can be NULL if it cannot be constructed
+ * from the reply
+ * @param emsg in case of unsuccessful deposit, this contains a human readable
+ * explanation.
+ */
+static void
+deposit_status (void *cls,
+ int status,
+ json_t *obj,
+ char *emsg)
+{
+ char *json_enc;
+
+ dh = NULL;
+ json_enc = NULL;
+ if (NULL != obj)
+ {
+ json_enc = json_dumps (obj, JSON_INDENT(2));
+ fprintf (stderr, "%s", json_enc);
+ }
+ if (1 == status)
+ result = GNUNET_OK;
+ else
+ GNUNET_break (0);
+ if (NULL != emsg)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Deposit failed: %s\n", emsg);
+ GNUNET_SCHEDULER_shutdown ();
+}
+/**
+ * Functions of this type are called to signal completion of an asynchronous call.
+ *
+ * @param cls closure
+ * @param emsg if the asynchronous call could not be completed due to an error,
+ * this parameter contains a human readable error message
+ */
+static void
+cont (void *cls, const char *emsg)
+{
+ json_t *dp;
+ char rnd_32[32];
+ char rnd_64[64];
+ char *enc_32;
+ char *enc_64;
+
+ GNUNET_assert (NULL == cls);
+ dkey_get = NULL;
+ if (NULL != emsg)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "%s\n", emsg);
+
+ enc_32 = TALER_data_to_string_alloc (rnd_32, sizeof (rnd_32));
+ enc_64 = TALER_data_to_string_alloc (rnd_64, sizeof (rnd_64));
+ dp = json_pack ("{s:s s:o s:s s:s s:s s:s s:s s:s s:s s:s}",
+ "type", "DIRECT_DEPOSIT",
+ "wire", json_pack ("{s:s}", "type", "SEPA"),
+ "C", enc_32,
+ "K", enc_32,
+ "ubsig", enc_64,
+ "M", enc_32,
+ "H_a", enc_64,
+ "H_wire", enc_64,
+ "csig", enc_64,
+ "m", "B1C5GP2RB1C5G");
+ GNUNET_free (enc_32);
+ GNUNET_free (enc_64);
+ dh = TALER_MINT_deposit_submit_json (mint,
+ deposit_status,
+ NULL,
+ dp);
+ json_decref (dp);
+}
+
+
+/**
+ * Functions of this type are called to provide the retrieved signing and
+ * denomination keys of the mint. No TALER_MINT_*() functions should be called
+ * in this callback.
+ *
+ * @param cls closure passed to TALER_MINT_keys_get()
+ * @param sign_keys NULL-terminated array of pointers to the mint's signing
+ * keys. NULL if no signing keys are retrieved.
+ * @param denom_keys NULL-terminated array of pointers to the mint's
+ * denomination keys; will be NULL if no signing keys are retrieved.
+ */
+static void
+read_denom_key (void *cls,
+ struct TALER_MINT_SigningPublicKey **sign_keys,
+ struct TALER_MINT_DenomPublicKey **denom_keys)
+{
+ unsigned int cnt;
+ GNUNET_assert (NULL == cls);
+#define ERR(cond) do { if(!(cond)) break; GNUNET_break (0); return; } while (0)
+ ERR (NULL == sign_keys);
+ ERR (NULL == denom_keys);
+ for (cnt = 0; NULL != sign_keys[cnt]; cnt++)
+ GNUNET_free (sign_keys[cnt]);
+ ERR (0 == cnt);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Read %u signing keys\n", cnt);
+ GNUNET_free (sign_keys);
+ for (cnt = 0; NULL != denom_keys[cnt]; cnt++)
+ GNUNET_free (denom_keys[cnt]);
+ ERR (0 == cnt);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Read %u denomination keys\n", cnt);
+ GNUNET_free (denom_keys);
+#undef ERR
+ return;
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param config configuration
+ */
+static void
+run (void *cls, char *const *args, const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *config)
+{
+ ctx = TALER_MINT_init ();
+ mint = TALER_MINT_connect (ctx, "localhost", 4241, NULL);
+ GNUNET_assert (NULL != mint);
+ dkey_get = TALER_MINT_keys_get (mint,
+ &read_denom_key, NULL,
+ &cont, NULL);
+ GNUNET_assert (NULL != dkey_get);
+ shutdown_task =
+ GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply
+ (GNUNET_TIME_UNIT_SECONDS, 5),
+ &do_shutdown, NULL);
+}
+
+int
+main (int argc, char * const *argv)
+{
+ static struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_OPTION_END
+ };
+
+ result = GNUNET_SYSERR;
+ if (GNUNET_OK !=
+ GNUNET_PROGRAM_run (argc, argv, "test-mint-api",
+ gettext_noop
+ ("Testcase to test mint's HTTP API interface"),
+ options, &run, NULL))
+ return 3;
+ return (GNUNET_OK == result) ? 0 : 1;
+}
diff --git a/src/mint/test_mint_common.c b/src/mint/test_mint_common.c
new file mode 100644
index 000000000..b7cad3ea4
--- /dev/null
+++ b/src/mint/test_mint_common.c
@@ -0,0 +1,83 @@
+/*
+ This file is part of TALER
+ (C) 2014 GNUnet e. V. (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/test_mint_common.c
+ * @brief test cases for some functions in mint/mint_common.c
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ */
+
+#include "platform.h"
+#include "gnunet/gnunet_util_lib.h"
+#include "taler_rsa.h"
+#include "mint.h"
+
+#define EXITIF(cond) \
+ do { \
+ if (cond) { GNUNET_break (0); goto EXITIF_exit; } \
+ } while (0)
+
+int
+main (int argc, const char *const argv[])
+{
+ struct TALER_MINT_DenomKeyIssue dki;
+ struct TALER_RSA_PrivateKeyBinaryEncoded *enc;
+ struct TALER_MINT_DenomKeyIssue dki_read;
+ struct TALER_RSA_PrivateKeyBinaryEncoded *enc_read;
+ char *tmpfile;
+
+ int ret;
+
+ ret = 1;
+ enc = NULL;
+ enc_read = NULL;
+ tmpfile = NULL;
+ dki.denom_priv = NULL;
+ dki_read.denom_priv = NULL;
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ &dki.signature,
+ sizeof (dki) - offsetof (struct TALER_MINT_DenomKeyIssue,
+ signature));
+ dki.denom_priv = TALER_RSA_key_create ();
+ EXITIF (NULL == (enc = TALER_RSA_encode_key (dki.denom_priv)));
+ EXITIF (NULL == (tmpfile = GNUNET_DISK_mktemp ("test_mint_common")));
+ EXITIF (GNUNET_OK != TALER_MINT_write_denom_key (tmpfile, &dki));
+ EXITIF (GNUNET_OK != TALER_MINT_read_denom_key (tmpfile, &dki_read));
+ EXITIF (NULL == (enc_read = TALER_RSA_encode_key (dki_read.denom_priv)));
+ EXITIF (enc->len != enc_read->len);
+ EXITIF (0 != memcmp (enc,
+ enc_read,
+ ntohs(enc->len)));
+ EXITIF (0 != memcmp (&dki.signature,
+ &dki_read.signature,
+ sizeof (dki) - offsetof (struct TALER_MINT_DenomKeyIssue,
+ signature)));
+ ret = 0;
+
+ EXITIF_exit:
+ GNUNET_free_non_null (enc);
+ if (NULL != tmpfile)
+ {
+ (void) unlink (tmpfile);
+ GNUNET_free (tmpfile);
+ }
+ GNUNET_free_non_null (enc_read);
+ if (NULL != dki.denom_priv)
+ TALER_RSA_key_free (dki.denom_priv);
+ if (NULL != dki_read.denom_priv)
+ TALER_RSA_key_free (dki_read.denom_priv);
+ return ret;
+}
diff --git a/src/mint/test_mint_deposits.c b/src/mint/test_mint_deposits.c
new file mode 100644
index 000000000..776bc15d2
--- /dev/null
+++ b/src/mint/test_mint_deposits.c
@@ -0,0 +1,149 @@
+/*
+ This file is part of TALER
+ (C) 2014 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/test_mint_deposits.c
+ * @brief testcase for mint deposits
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ */
+
+#include "platform.h"
+#include <libpq-fe.h>
+#include <gnunet/gnunet_util_lib.h>
+#include "mint_db.h"
+
+#define DB_URI "postgres:///taler"
+
+/**
+ * Shorthand for exit jumps.
+ */
+#define EXITIF(cond) \
+ do { \
+ if (cond) { GNUNET_break (0); goto EXITIF_exit; } \
+ } while (0)
+
+
+/**
+ * DB connection handle
+ */
+static PGconn *conn;
+
+/**
+ * Should we not interact with a temporary table?
+ */
+static int persistent;
+
+/**
+ * Testcase result
+ */
+static int result;
+
+
+static void
+do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+ if (NULL != conn)
+ PQfinish (conn);
+ conn = NULL;
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param config configuration
+ */
+static void
+run (void *cls, char *const *args, const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *config)
+{
+ static const char wire[] = "{"
+ "\"type\":\"SEPA\","
+ "\"IBAN\":\"DE67830654080004822650\","
+ "\"NAME\":\"GNUNET E.V\","
+ "\"BIC\":\"GENODEF1SRL\""
+ "}";
+ struct Deposit *deposit;
+ struct Deposit *q_deposit;
+ uint64_t transaction_id;
+
+ deposit = NULL;
+ q_deposit = NULL;
+ GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
+ &do_shutdown, NULL);
+ EXITIF (NULL == (conn = PQconnectdb(DB_URI)));
+ EXITIF (GNUNET_OK != TALER_MINT_DB_init_deposits (conn, !persistent));
+ EXITIF (GNUNET_OK != TALER_MINT_DB_prepare_deposits (conn));
+ deposit = GNUNET_malloc (sizeof (struct Deposit) + sizeof (wire));
+ /* Makeup a random coin public key */
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ deposit,
+ sizeof (struct Deposit));
+ /* Makeup a random 64bit transaction ID */
+ transaction_id = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
+ UINT64_MAX);
+ deposit->transaction_id = GNUNET_htonll (transaction_id);
+ /* Random amount */
+ deposit->amount.value =
+ htonl (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX));
+ deposit->amount.fraction =
+ htonl (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, UINT32_MAX));
+ strcpy (deposit->amount.currency, "EUR");
+ /* Copy wireformat */
+ (void) memcpy (deposit->wire, wire, sizeof (wire));
+ EXITIF (GNUNET_OK != TALER_MINT_DB_insert_deposit (conn,
+ deposit));
+ EXITIF (GNUNET_OK != TALER_MINT_DB_get_deposit (conn,
+ &deposit->coin_pub,
+ &q_deposit));
+ EXITIF (0 != memcmp (deposit,
+ q_deposit,
+ sizeof (struct Deposit) - offsetof(struct Deposit,
+ wire)));
+ EXITIF (transaction_id != GNUNET_ntohll (q_deposit->transaction_id));
+ result = GNUNET_OK;
+
+ EXITIF_exit:
+ GNUNET_free_non_null (deposit);
+ GNUNET_free_non_null (q_deposit);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+}
+
+
+int main(int argc, char *const argv[])
+{
+ static const struct GNUNET_GETOPT_CommandLineOption options[] = {
+ {'T', "persist", NULL,
+ gettext_noop ("Use a persistent database table instead of a temporary one"),
+ GNUNET_NO, &GNUNET_GETOPT_set_one, &persistent},
+ GNUNET_GETOPT_OPTION_END
+ };
+
+
+ persistent = GNUNET_NO;
+ result = GNUNET_SYSERR;
+ if (GNUNET_OK !=
+ GNUNET_PROGRAM_run (argc, argv,
+ "test-mint-deposits",
+ "testcase for mint deposits",
+ options, &run, NULL))
+ return 3;
+ return (GNUNET_OK == result) ? 0 : 1;
+}
diff --git a/src/mint/test_mint_nayapaisa.ecc b/src/mint/test_mint_nayapaisa.ecc
new file mode 100644
index 000000000..942110b5c
--- /dev/null
+++ b/src/mint/test_mint_nayapaisa.ecc
Binary files differ
diff --git a/src/mint/test_mint_nayapaisa/README b/src/mint/test_mint_nayapaisa/README
new file mode 100644
index 000000000..fce5e0180
--- /dev/null
+++ b/src/mint/test_mint_nayapaisa/README
@@ -0,0 +1 @@
+This directory is a template for the mint directory.
diff --git a/src/mint/test_mint_nayapaisa/config/mint-common.conf b/src/mint/test_mint_nayapaisa/config/mint-common.conf
new file mode 100644
index 000000000..c1fede7a2
--- /dev/null
+++ b/src/mint/test_mint_nayapaisa/config/mint-common.conf
@@ -0,0 +1,6 @@
+[mint]
+db = postgres:///taler
+port = 4241
+master_pub = 6ZE0HEY2M0FWP61M0470HYBF4K6RRD5DP54372PD2TN9N9VX2VJG
+refresh_security_parameter = 3
+
diff --git a/src/mint/test_mint_nayapaisa/config/mint-keyup.conf b/src/mint/test_mint_nayapaisa/config/mint-keyup.conf
new file mode 100644
index 000000000..1542d1a63
--- /dev/null
+++ b/src/mint/test_mint_nayapaisa/config/mint-keyup.conf
@@ -0,0 +1,79 @@
+[mint_keys]
+
+# how long is one signkey valid?
+signkey_duration = 4 weeks
+
+# how long do we generate denomination and signing keys
+# ahead of time?
+lookahead_sign = 32 weeks 1 day
+
+# how long do we provide to clients denomination and signing keys
+# ahead of time?
+lookahead_provide = 4 weeks 1 day
+
+# what coin types do we have available?
+coin_types = default_eur_ct_10 default_eur_5 default_eur_10 default_eur_1000
+
+
+
+[mint_denom_duration_overlap]
+default_eur_ct_10 = 5 minutes
+default_eur_5 = 5 minutes
+default_eur_10 = 5 minutes
+default_eur_1000 = 5 minutes
+
+
+
+[mint_denom_value]
+default_eur_ct_10 = EUR:0.10
+default_eur_5 = EUR:5
+default_eur_10 = EUR:10
+default_eur_1000 = EUR:1000
+
+
+
+[mint_denom_duration_withdraw]
+default_eur_ct_10 = 7 days
+default_eur_5 = 7 days
+default_eur_10 = 7 days
+default_eur_1000 = 1 day
+
+
+
+[mint_denom_duration_spend]
+default_eur_ct_10 = 30 days
+default_eur_5 = 30 days
+default_eur_10 = 30 days
+default_eur_1000 = 30 day
+
+
+
+[mint_denom_fee_withdraw]
+default_eur_ct_10 = EUR:0.01
+default_eur_5 = EUR:0.01
+default_eur_10 = EUR:0.01
+default_eur_1000 = EUR:0.01
+
+
+[mint_denom_fee_deposit]
+default_eur_ct_10 = EUR:0.01
+default_eur_5 = EUR:0.01
+default_eur_10 = EUR:0.01
+default_eur_1000 = EUR:0.01
+
+
+
+[mint_denom_fee_refresh]
+default_eur_ct_10 = EUR:0.01
+default_eur_5 = EUR:0.01
+default_eur_10 = EUR:0.01
+default_eur_1000 = EUR:0.01
+
+
+
+[mint_denom_kappa]
+default_eur_ct_10 = 3
+default_eur_5 = 3
+default_eur_10 = 3
+default_eur_1000 = 5
+
diff --git a/src/mint/test_mint_nyadirahim.ecc b/src/mint/test_mint_nyadirahim.ecc
new file mode 100644
index 000000000..9db920894
--- /dev/null
+++ b/src/mint/test_mint_nyadirahim.ecc
@@ -0,0 +1 @@
+ÃWÜBøÐfØ ŽµrØñ·•ŠÊÐŒ„Ú:ß½V»j \ No newline at end of file
diff --git a/src/mint/test_mint_nyadirahim/README b/src/mint/test_mint_nyadirahim/README
new file mode 100644
index 000000000..fce5e0180
--- /dev/null
+++ b/src/mint/test_mint_nyadirahim/README
@@ -0,0 +1 @@
+This directory is a template for the mint directory.
diff --git a/src/mint/test_mint_nyadirahim/config/mint-common.conf b/src/mint/test_mint_nyadirahim/config/mint-common.conf
new file mode 100644
index 000000000..c4d528634
--- /dev/null
+++ b/src/mint/test_mint_nyadirahim/config/mint-common.conf
@@ -0,0 +1,6 @@
+[mint]
+db = postgres:///taler
+port = 4241
+master_pub = 7995WKK71KPKTBBMA5BHNBSZFGNRZPYNXDJMQ8EK86V9598H03TG
+refresh_security_parameter = 3
+
diff --git a/src/mint/test_mint_nyadirahim/config/mint-keyup.conf b/src/mint/test_mint_nyadirahim/config/mint-keyup.conf
new file mode 100644
index 000000000..1542d1a63
--- /dev/null
+++ b/src/mint/test_mint_nyadirahim/config/mint-keyup.conf
@@ -0,0 +1,79 @@
+[mint_keys]
+
+# how long is one signkey valid?
+signkey_duration = 4 weeks
+
+# how long do we generate denomination and signing keys
+# ahead of time?
+lookahead_sign = 32 weeks 1 day
+
+# how long do we provide to clients denomination and signing keys
+# ahead of time?
+lookahead_provide = 4 weeks 1 day
+
+# what coin types do we have available?
+coin_types = default_eur_ct_10 default_eur_5 default_eur_10 default_eur_1000
+
+
+
+[mint_denom_duration_overlap]
+default_eur_ct_10 = 5 minutes
+default_eur_5 = 5 minutes
+default_eur_10 = 5 minutes
+default_eur_1000 = 5 minutes
+
+
+
+[mint_denom_value]
+default_eur_ct_10 = EUR:0.10
+default_eur_5 = EUR:5
+default_eur_10 = EUR:10
+default_eur_1000 = EUR:1000
+
+
+
+[mint_denom_duration_withdraw]
+default_eur_ct_10 = 7 days
+default_eur_5 = 7 days
+default_eur_10 = 7 days
+default_eur_1000 = 1 day
+
+
+
+[mint_denom_duration_spend]
+default_eur_ct_10 = 30 days
+default_eur_5 = 30 days
+default_eur_10 = 30 days
+default_eur_1000 = 30 day
+
+
+
+[mint_denom_fee_withdraw]
+default_eur_ct_10 = EUR:0.01
+default_eur_5 = EUR:0.01
+default_eur_10 = EUR:0.01
+default_eur_1000 = EUR:0.01
+
+
+[mint_denom_fee_deposit]
+default_eur_ct_10 = EUR:0.01
+default_eur_5 = EUR:0.01
+default_eur_10 = EUR:0.01
+default_eur_1000 = EUR:0.01
+
+
+
+[mint_denom_fee_refresh]
+default_eur_ct_10 = EUR:0.01
+default_eur_5 = EUR:0.01
+default_eur_10 = EUR:0.01
+default_eur_1000 = EUR:0.01
+
+
+
+[mint_denom_kappa]
+default_eur_ct_10 = 3
+default_eur_5 = 3
+default_eur_10 = 3
+default_eur_1000 = 5
+