gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-donau] 03/03: [build] copy src/{curl,json,mhd,pq,sq} from exchang


From: gnunet
Subject: [taler-donau] 03/03: [build] copy src/{curl,json,mhd,pq,sq} from exchange to build succesfully
Date: Mon, 23 Oct 2023 14:18:24 +0200

This is an automated email from the git hooks/post-receive script.

pius-loosli pushed a commit to branch master
in repository donau.

commit a9e76052a241f6e666ec02c64ab2d2456453ac55
Author: Pius Loosli <loosp2@bfh.ch>
AuthorDate: Mon Oct 23 14:16:26 2023 +0200

    [build] copy src/{curl,json,mhd,pq,sq} from exchange to build succesfully
---
 src/curl/Makefile.am      |   24 +
 src/curl/curl.c           |  107 ++++
 src/json/.gitignore       |    1 +
 src/json/Makefile.am      |   43 ++
 src/json/i18n.c           |  134 +++++
 src/json/json.c           |  886 ++++++++++++++++++++++++++++
 src/json/json_helper.c    | 1366 +++++++++++++++++++++++++++++++++++++++++++
 src/json/json_pack.c      |  308 ++++++++++
 src/json/json_wire.c      |  122 ++++
 src/json/test_json.c      |  439 ++++++++++++++
 src/mhd/Makefile.am       |   29 +
 src/mhd/mhd_config.c      |  493 ++++++++++++++++
 src/mhd/mhd_legal.c       |  694 ++++++++++++++++++++++
 src/mhd/mhd_parsing.c     |  444 ++++++++++++++
 src/mhd/mhd_responses.c   |  550 +++++++++++++++++
 src/mhd/mhd_run.c         |  175 ++++++
 src/pq/Makefile.am        |   42 ++
 src/pq/pq_common.c        |   68 +++
 src/pq/pq_common.h        |  125 ++++
 src/pq/pq_query_helper.c  | 1183 +++++++++++++++++++++++++++++++++++++
 src/pq/pq_result_helper.c | 1424 +++++++++++++++++++++++++++++++++++++++++++++
 src/pq/test_pq.c          |  250 ++++++++
 src/sq/Makefile.am        |   40 ++
 src/sq/sq_query_helper.c  |  175 ++++++
 src/sq/sq_result_helper.c |  237 ++++++++
 src/sq/test_sq.c          |  215 +++++++
 26 files changed, 9574 insertions(+)

diff --git a/src/curl/Makefile.am b/src/curl/Makefile.am
new file mode 100644
index 0000000..f60a380
--- /dev/null
+++ b/src/curl/Makefile.am
@@ -0,0 +1,24 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+if USE_COVERAGE
+  AM_CFLAGS = --coverage -O0
+  XLIB = -lgcov
+endif
+
+lib_LTLIBRARIES = \
+  libtalercurl.la
+
+libtalercurl_la_LDFLAGS = \
+  -version-info 0:0:0 \
+  -no-undefined
+libtalercurl_la_SOURCES = \
+  curl.c
+libtalercurl_la_LIBADD = \
+  -lgnunetcurl \
+  -lgnunetutil \
+  $(LIBGNURLCURL_LIBS) \
+  -ljansson \
+  -lz \
+  -lm \
+  $(XLIB)
diff --git a/src/curl/curl.c b/src/curl/curl.c
new file mode 100644
index 0000000..caa0052
--- /dev/null
+++ b/src/curl/curl.c
@@ -0,0 +1,107 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2019-2021 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published
+  by the Free Software Foundation; either version 3, or (at your
+  option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public
+  License along with TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file curl/curl.c
+ * @brief Helper routines for interactions with libcurl
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_curl_lib.h"
+
+
+#if TALER_CURL_COMPRESS_BODIES
+#include <zlib.h>
+#endif
+
+
+enum GNUNET_GenericReturnValue
+TALER_curl_easy_post (struct TALER_CURL_PostContext *ctx,
+                      CURL *eh,
+                      const json_t *body)
+{
+  char *str;
+  size_t slen;
+
+  str = json_dumps (body,
+                    JSON_COMPACT);
+  if (NULL == str)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  slen = strlen (str);
+  if (TALER_CURL_COMPRESS_BODIES &&
+      (! ctx->disable_compression) )
+  {
+    Bytef *cbuf;
+    uLongf cbuf_size;
+    int ret;
+
+    cbuf_size = compressBound (slen);
+    cbuf = GNUNET_malloc (cbuf_size);
+    ret = compress (cbuf,
+                    &cbuf_size,
+                    (const Bytef *) str,
+                    slen);
+    if (Z_OK != ret)
+    {
+      /* compression failed!? */
+      GNUNET_break (0);
+      GNUNET_free (cbuf);
+      return GNUNET_SYSERR;
+    }
+    free (str);
+    slen = (size_t) cbuf_size;
+    ctx->json_enc = (char *) cbuf;
+    GNUNET_assert (
+      NULL !=
+      (ctx->headers = curl_slist_append (
+         ctx->headers,
+         "Content-Encoding: deflate")));
+  }
+  else
+  {
+    ctx->json_enc = str;
+  }
+  GNUNET_assert (
+    NULL !=
+    (ctx->headers = curl_slist_append (
+       ctx->headers,
+       "Content-Type: application/json")));
+
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_POSTFIELDS,
+                                   ctx->json_enc));
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_POSTFIELDSIZE,
+                                   slen));
+  return GNUNET_OK;
+}
+
+
+void
+TALER_curl_easy_post_finished (struct TALER_CURL_PostContext *ctx)
+{
+  curl_slist_free_all (ctx->headers);
+  ctx->headers = NULL;
+  GNUNET_free (ctx->json_enc);
+  ctx->json_enc = NULL;
+}
diff --git a/src/json/.gitignore b/src/json/.gitignore
new file mode 100644
index 0000000..d288434
--- /dev/null
+++ b/src/json/.gitignore
@@ -0,0 +1 @@
+test_json_wire
diff --git a/src/json/Makefile.am b/src/json/Makefile.am
new file mode 100644
index 0000000..6bd5b46
--- /dev/null
+++ b/src/json/Makefile.am
@@ -0,0 +1,43 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+if USE_COVERAGE
+  AM_CFLAGS = --coverage -O0
+  XLIB = -lgcov
+endif
+
+lib_LTLIBRARIES = \
+  libtalerjson.la
+
+libtalerjson_la_SOURCES = \
+  i18n.c \
+  json.c \
+  json_helper.c \
+  json_pack.c \
+  json_wire.c
+libtalerjson_la_LDFLAGS = \
+  -version-info 1:0:1 \
+  -no-undefined
+libtalerjson_la_LIBADD = \
+  $(top_builddir)/src/util/libtalerutil.la \
+  -lgnunetjson \
+  -lgnunetutil \
+  -lunistring \
+  -ljansson \
+  -lm \
+  $(XLIB)
+
+TESTS = \
+  test_json
+
+check_PROGRAMS= \
+  test_json
+
+test_json_SOURCES = \
+  test_json.c
+test_json_LDADD = \
+  $(top_builddir)/src/json/libtalerjson.la \
+  -lgnunetjson \
+  $(top_builddir)/src/util/libtalerutil.la \
+  -lgnunetutil \
+  -ljansson
diff --git a/src/json/i18n.c b/src/json/i18n.c
new file mode 100644
index 0000000..f927a71
--- /dev/null
+++ b/src/json/i18n.c
@@ -0,0 +1,134 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020, 2021 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file json/i18n.c
+ * @brief helper functions for i18n in JSON processing
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_util.h"
+#include "taler_json_lib.h"
+
+
+const json_t *
+TALER_JSON_extract_i18n (const json_t *object,
+                         const char *language_pattern,
+                         const char *field)
+{
+  const json_t *ret;
+  json_t *i18n;
+  double quality = -1;
+
+  ret = json_object_get (object,
+                         field);
+  if (NULL == ret)
+    return NULL; /* field MUST exist in object */
+  {
+    char *name;
+
+    GNUNET_asprintf (&name,
+                     "%s_i18n",
+                     field);
+    i18n = json_object_get (object,
+                            name);
+    GNUNET_free (name);
+  }
+  if (NULL == i18n)
+    return ret;
+  {
+    const char *key;
+    json_t *value;
+
+    json_object_foreach (i18n, key, value) {
+      double q = TALER_language_matches (language_pattern,
+                                         key);
+      if (q > quality)
+      {
+        quality = q;
+        ret = value;
+      }
+    }
+  }
+  return ret;
+}
+
+
+bool
+TALER_JSON_check_i18n (const json_t *i18n)
+{
+  const char *field;
+  json_t *member;
+
+  if (! json_is_object (i18n))
+    return false;
+  json_object_foreach ((json_t *) i18n, field, member)
+  {
+    if (! json_is_string (member))
+      return false;
+    /* Field name must be either of format "en_UK"
+       or just "en"; we do not care about capitalization;
+       for syntax, see GNU Gettext manual, including
+       appendix A for rare language codes. */
+    switch (strlen (field))
+    {
+    case 0:
+    case 1:
+      return false;
+    case 2:
+      if (! isalpha (field[0]))
+        return false;
+      if (! isalpha (field[1]))
+        return false;
+      break;
+    case 3:
+    case 4:
+      return false;
+    case 5:
+      if (! isalpha (field[0]))
+        return false;
+      if (! isalpha (field[1]))
+        return false;
+      if ('_' != field[2])
+        return false;
+      if (! isalpha (field[3]))
+        return false;
+      if (! isalpha (field[4]))
+        return false;
+      break;
+    case 6:
+      if (! isalpha (field[0]))
+        return false;
+      if (! isalpha (field[1]))
+        return false;
+      if ('_' != field[2])
+        return false;
+      if (! isalpha (field[3]))
+        return false;
+      if (! isalpha (field[4]))
+        return false;
+      if (! isalpha (field[5]))
+        return false;
+      break;
+    default:
+      return false;
+    }
+  }
+  return true;
+}
+
+
+/* end of i18n.c */
diff --git a/src/json/json.c b/src/json/json.c
new file mode 100644
index 0000000..fb00fb5
--- /dev/null
+++ b/src/json/json.c
@@ -0,0 +1,886 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014, 2015, 2016, 2020, 2021 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file json/json.c
+ * @brief helper functions for JSON processing using libjansson
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include <unistr.h>
+
+
+/**
+ * Check if @a json contains a 'real' value anywhere.
+ *
+ * @param json json to check
+ * @return true if a real is in it somewhere
+ */
+static bool
+contains_real (const json_t *json)
+{
+  if (json_is_real (json))
+    return true;
+  if (json_is_object (json))
+  {
+    json_t *member;
+    const char *name;
+
+    json_object_foreach ((json_t *) json, name, member)
+    if (contains_real (member))
+      return true;
+    return false;
+  }
+  if (json_is_array (json))
+  {
+    json_t *member;
+    size_t index;
+
+    json_array_foreach ((json_t *) json, index, member)
+    if (contains_real (member))
+      return true;
+    return false;
+  }
+  return false;
+}
+
+
+/**
+ * Dump the @a json to a string and hash it.
+ *
+ * @param json value to hash
+ * @param salt salt value to include when using HKDF,
+ *        NULL to not use any salt and to use SHA512
+ * @param[out] hc where to store the hash
+ * @return #GNUNET_OK on success,
+ *         #GNUNET_NO if @a json was not hash-able
+ *         #GNUNET_SYSERR on failure
+ */
+static enum GNUNET_GenericReturnValue
+dump_and_hash (const json_t *json,
+               const char *salt,
+               struct GNUNET_HashCode *hc)
+{
+  char *wire_enc;
+  size_t len;
+
+  if (NULL == json)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_NO;
+  }
+  if (contains_real (json))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_NO;
+  }
+  if (NULL == (wire_enc = json_dumps (json,
+                                      JSON_ENCODE_ANY
+                                      | JSON_COMPACT
+                                      | JSON_SORT_KEYS)))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  len = TALER_rfc8785encode (&wire_enc);
+  if (NULL == salt)
+  {
+    GNUNET_CRYPTO_hash (wire_enc,
+                        len,
+                        hc);
+  }
+  else
+  {
+    if (GNUNET_YES !=
+        GNUNET_CRYPTO_kdf (hc,
+                           sizeof (*hc),
+                           salt,
+                           strlen (salt) + 1,
+                           wire_enc,
+                           len,
+                           NULL,
+                           0))
+    {
+      GNUNET_break (0);
+      free (wire_enc);
+      return GNUNET_SYSERR;
+    }
+  }
+  free (wire_enc);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Replace "forgettable" parts of a JSON object with their salted hash.
+ *
+ * @param[in] in some JSON value
+ * @param[out] out resulting JSON value
+ * @return #GNUNET_OK on success,
+ *         #GNUNET_NO if @a json was not hash-able
+ *         #GNUNET_SYSERR on failure
+ */
+static enum GNUNET_GenericReturnValue
+forget (const json_t *in,
+        json_t **out)
+{
+  if (json_is_real (in))
+  {
+    /* floating point is not allowed! */
+    GNUNET_break_op (0);
+    return GNUNET_NO;
+  }
+  if (json_is_array (in))
+  {
+    /* array is a JSON array */
+    size_t index;
+    json_t *value;
+    json_t *ret;
+
+    ret = json_array ();
+    if (NULL == ret)
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+    json_array_foreach (in, index, value) {
+      enum GNUNET_GenericReturnValue iret;
+      json_t *t;
+
+      iret = forget (value,
+                     &t);
+      if (GNUNET_OK != iret)
+      {
+        json_decref (ret);
+        return iret;
+      }
+      if (0 != json_array_append_new (ret,
+                                      t))
+      {
+        GNUNET_break (0);
+        json_decref (ret);
+        return GNUNET_SYSERR;
+      }
+    }
+    *out = ret;
+    return GNUNET_OK;
+  }
+  if (json_is_object (in))
+  {
+    json_t *ret;
+    const char *key;
+    json_t *value;
+    json_t *fg;
+    json_t *rx;
+
+    fg = json_object_get (in,
+                          "$forgettable");
+    rx = json_object_get (in,
+                          "$forgotten");
+    if (NULL != rx)
+    {
+      rx = json_deep_copy (rx); /* should be shallow
+                                   by structure, but
+                                   deep copy is safer */
+      if (NULL == rx)
+      {
+        GNUNET_break (0);
+        return GNUNET_SYSERR;
+      }
+    }
+    ret = json_object ();
+    if (NULL == ret)
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+    json_object_foreach ((json_t*) in, key, value) {
+      json_t *t;
+      json_t *salt;
+      enum GNUNET_GenericReturnValue iret;
+
+      if (fg == value)
+        continue; /* skip! */
+      if (rx == value)
+        continue; /* skip! */
+      if ( (NULL != rx) &&
+           (NULL !=
+            json_object_get (rx,
+                             key)) )
+      {
+        (void) json_object_del (ret,
+                                key);
+        continue; /* already forgotten earlier */
+      }
+      iret = forget (value,
+                     &t);
+      if (GNUNET_OK != iret)
+      {
+        json_decref (ret);
+        json_decref (rx);
+        return iret;
+      }
+      if ( (NULL != fg) &&
+           (NULL != (salt = json_object_get (fg,
+                                             key))) )
+      {
+        /* 't' is to be forgotten! */
+        struct GNUNET_HashCode hc;
+
+        if (! json_is_string (salt))
+        {
+          GNUNET_break_op (0);
+          json_decref (ret);
+          json_decref (rx);
+          json_decref (t);
+          return GNUNET_NO;
+        }
+        iret = dump_and_hash (t,
+                              json_string_value (salt),
+                              &hc);
+        if (GNUNET_OK != iret)
+        {
+          json_decref (ret);
+          json_decref (rx);
+          json_decref (t);
+          return iret;
+        }
+        json_decref (t);
+        /* scrub salt */
+        if (0 !=
+            json_object_del (fg,
+                             key))
+        {
+          GNUNET_break_op (0);
+          json_decref (ret);
+          json_decref (rx);
+          return GNUNET_NO;
+        }
+        if (NULL == rx)
+          rx = json_object ();
+        if (NULL == rx)
+        {
+          GNUNET_break (0);
+          json_decref (ret);
+          return GNUNET_SYSERR;
+        }
+        if (0 !=
+            json_object_set_new (rx,
+                                 key,
+                                 GNUNET_JSON_from_data_auto (&hc)))
+        {
+          GNUNET_break (0);
+          json_decref (ret);
+          json_decref (rx);
+          return GNUNET_SYSERR;
+        }
+      }
+      else
+      {
+        /* 't' to be used without 'forgetting' */
+        if (0 !=
+            json_object_set_new (ret,
+                                 key,
+                                 t))
+        {
+          GNUNET_break (0);
+          json_decref (ret);
+          json_decref (rx);
+          return GNUNET_SYSERR;
+        }
+      }
+    } /* json_object_foreach */
+    if ( (NULL != rx) &&
+         (0 !=
+          json_object_set_new (ret,
+                               "$forgotten",
+                               rx)) )
+    {
+      GNUNET_break (0);
+      json_decref (ret);
+      return GNUNET_SYSERR;
+    }
+    *out = ret;
+    return GNUNET_OK;
+  }
+  *out = json_incref ((json_t *) in);
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_contract_hash (const json_t *json,
+                          struct TALER_PrivateContractHashP *hc)
+{
+  enum GNUNET_GenericReturnValue ret;
+  json_t *cjson;
+  json_t *dc;
+
+  dc = json_deep_copy (json);
+  ret = forget (dc,
+                &cjson);
+  json_decref (dc);
+  if (GNUNET_OK != ret)
+    return ret;
+  ret = dump_and_hash (cjson,
+                       NULL,
+                       &hc->hash);
+  json_decref (cjson);
+  return ret;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_contract_mark_forgettable (json_t *json,
+                                      const char *field)
+{
+  json_t *fg;
+  struct GNUNET_ShortHashCode salt;
+
+  if (! json_is_object (json))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  /* check field name is legal for forgettable field */
+  for (const char *f = field; '\0' != *f; f++)
+  {
+    char c = *f;
+
+    if ( (c >= 'a') && (c <= 'z') )
+      continue;
+    if ( (c >= 'A') && (c <= 'Z') )
+      continue;
+    if ( (c >= '0') && (c <= '9') )
+      continue;
+    if ('_' == c)
+      continue;
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (NULL == json_object_get (json,
+                               field))
+  {
+    /* field must exist */
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  fg = json_object_get (json,
+                        "$forgettable");
+  if (NULL == fg)
+  {
+    fg = json_object ();
+    if (0 !=
+        json_object_set_new (json,
+                             "$forgettable",
+                             fg))
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+  }
+
+  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+                              &salt,
+                              sizeof (salt));
+  if (0 !=
+      json_object_set_new (fg,
+                           field,
+                           GNUNET_JSON_from_data_auto (&salt)))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_contract_part_forget (json_t *json,
+                                 const char *field)
+{
+  json_t *fg;
+  const json_t *part;
+  json_t *fp;
+  json_t *rx;
+  struct GNUNET_HashCode hc;
+  const char *salt;
+  enum GNUNET_GenericReturnValue ret;
+
+  if (! json_is_object (json))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (NULL == (part = json_object_get (json,
+                                       field)))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Did not find field `%s' we were asked to forget\n",
+                field);
+    return GNUNET_SYSERR;
+  }
+  fg = json_object_get (json,
+                        "$forgettable");
+  if (NULL == fg)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Did not find '$forgettable' attribute trying to forget field 
`%s'\n",
+                field);
+    return GNUNET_SYSERR;
+  }
+  rx = json_object_get (json,
+                        "$forgotten");
+  if (NULL == rx)
+  {
+    rx = json_object ();
+    if (0 !=
+        json_object_set_new (json,
+                             "$forgotten",
+                             rx))
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+  }
+  if (NULL !=
+      json_object_get (rx,
+                       field))
+  {
+    if (! json_is_null (json_object_get (json,
+                                         field)))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Field `%s' market as forgotten, but still exists!\n",
+                  field);
+      return GNUNET_SYSERR;
+    }
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Already forgot field `%s'\n",
+                field);
+    return GNUNET_NO;
+  }
+  salt = json_string_value (json_object_get (fg,
+                                             field));
+  if (NULL == salt)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Did not find required salt to forget field `%s'\n",
+                field);
+    return GNUNET_SYSERR;
+  }
+
+  /* need to recursively forget to compute 'hc' */
+  ret = forget (part,
+                &fp);
+  if (GNUNET_OK != ret)
+    return ret;
+  if (GNUNET_OK !=
+      dump_and_hash (fp,
+                     salt,
+                     &hc))
+  {
+    json_decref (fp);
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  json_decref (fp);
+  /* drop salt */
+  if (0 !=
+      json_object_del (fg,
+                       field))
+  {
+    json_decref (fp);
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* remember field as 'forgotten' */
+  if (0 !=
+      json_object_set_new (rx,
+                           field,
+                           GNUNET_JSON_from_data_auto (&hc)))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  /* finally, set 'forgotten' field to null */
+  if (0 !=
+      json_object_del (json,
+                       field))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Look over all of the values of a '$forgettable' object.  Replace 'True'
+ * values with proper random salts.  Fails if any forgettable values are
+ * neither 'True' nor valid salts (strings).
+ *
+ * @param[in,out] f JSON to transform
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+seed_forgettable (json_t *f)
+{
+  const char *key;
+  json_t *val;
+
+  json_object_foreach (f,
+                       key,
+                       val)
+  {
+    if (json_is_string (val))
+      continue;
+    if (json_is_true (val))
+    {
+      struct GNUNET_ShortHashCode sh;
+
+      GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+                                  &sh,
+                                  sizeof (sh));
+      if (0 !=
+          json_object_set_new (f,
+                               key,
+                               GNUNET_JSON_from_data_auto (&sh)))
+      {
+        GNUNET_break (0);
+        return GNUNET_SYSERR;
+      }
+      continue;
+    }
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Forgettable field `%s' has invalid value\n",
+                key);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Take a given contract with "forgettable" fields marked
+ * but with 'True' instead of a real salt. Replaces all
+ * 'True' values with proper random salts.  Fails if any
+ * forgettable markers are neither 'True' nor valid salts.
+ *
+ * @param[in,out] json JSON to transform
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_JSON_contract_seed_forgettable (json_t *json)
+{
+  if (json_is_object (json))
+  {
+    const char *key;
+    json_t *val;
+
+    json_object_foreach (json,
+                         key,
+                         val)
+    {
+      if (0 == strcmp ("$forgettable",
+                       key))
+      {
+        if (GNUNET_OK !=
+            seed_forgettable (val))
+          return GNUNET_SYSERR;
+        continue;
+      }
+      if (GNUNET_OK !=
+          TALER_JSON_contract_seed_forgettable (val))
+        return GNUNET_SYSERR;
+    }
+  }
+  if (json_is_array (json))
+  {
+    size_t index;
+    json_t *val;
+
+    json_array_foreach (json,
+                        index,
+                        val)
+    {
+      if (GNUNET_OK !=
+          TALER_JSON_contract_seed_forgettable (val))
+        return GNUNET_SYSERR;
+    }
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse a json path.
+ *
+ * @param obj the object that the path is relative to.
+ * @param prev the parent of @e obj.
+ * @param path the path to parse.
+ * @param cb the callback to call, if we get to the end of @e path.
+ * @param cb_cls the closure for the callback.
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR if @e path is malformed.
+ */
+static enum GNUNET_GenericReturnValue
+parse_path (json_t *obj,
+            json_t *prev,
+            const char *path,
+            TALER_JSON_ExpandPathCallback cb,
+            void *cb_cls)
+{
+  char *id = GNUNET_strdup (path);
+  char *next_id = strchr (id,
+                          '.');
+  char *next_path;
+  char *bracket;
+  json_t *next_obj = NULL;
+  char *next_dot;
+
+  GNUNET_assert (NULL != id); /* make stupid compiler happy */
+  if (NULL == next_id)
+  {
+    cb (cb_cls,
+        id,
+        prev);
+    GNUNET_free (id);
+    return GNUNET_OK;
+  }
+  bracket = strchr (next_id,
+                    '[');
+  *next_id = '\0';
+  next_id++;
+  next_path = GNUNET_strdup (next_id);
+  next_dot = strchr (next_id,
+                     '.');
+  if (NULL != next_dot)
+    *next_dot = '\0';
+  /* If this is the first time this is called, make sure id is "$" */
+  if ( (NULL == prev) &&
+       (0 != strcmp (id,
+                     "$")))
+  {
+    GNUNET_free (id);
+    GNUNET_free (next_path);
+    return GNUNET_SYSERR;
+  }
+
+  /* Check for bracketed indices */
+  if (NULL != bracket)
+  {
+    char *end_bracket = strchr (bracket,
+                                ']');
+    if (NULL == end_bracket)
+    {
+      GNUNET_free (id);
+      GNUNET_free (next_path);
+      return GNUNET_SYSERR;
+    }
+    *end_bracket = '\0';
+
+    *bracket = '\0';
+    bracket++;
+
+    json_t *array = json_object_get (obj,
+                                     next_id);
+    if (0 == strcmp (bracket,
+                     "*"))
+    {
+      size_t index;
+      json_t *value;
+      int ret = GNUNET_OK;
+
+      json_array_foreach (array, index, value) {
+        ret = parse_path (value,
+                          obj,
+                          next_path,
+                          cb,
+                          cb_cls);
+        if (GNUNET_OK != ret)
+        {
+          GNUNET_free (id);
+          GNUNET_free (next_path);
+          return ret;
+        }
+      }
+    }
+    else
+    {
+      unsigned int index;
+      char dummy;
+
+      if (1 != sscanf (bracket,
+                       "%u%c",
+                       &index,
+                       &dummy))
+      {
+        GNUNET_free (id);
+        GNUNET_free (next_path);
+        return GNUNET_SYSERR;
+      }
+      next_obj = json_array_get (array,
+                                 index);
+    }
+  }
+  else
+  {
+    /* No brackets, so just fetch the object by name */
+    next_obj = json_object_get (obj,
+                                next_id);
+  }
+
+  if (NULL != next_obj)
+  {
+    int ret = parse_path (next_obj,
+                          obj,
+                          next_path,
+                          cb,
+                          cb_cls);
+    GNUNET_free (id);
+    GNUNET_free (next_path);
+    return ret;
+  }
+  GNUNET_free (id);
+  GNUNET_free (next_path);
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_expand_path (json_t *json,
+                        const char *path,
+                        TALER_JSON_ExpandPathCallback cb,
+                        void *cb_cls)
+{
+  return parse_path (json,
+                     NULL,
+                     path,
+                     cb,
+                     cb_cls);
+}
+
+
+enum TALER_ErrorCode
+TALER_JSON_get_error_code (const json_t *json)
+{
+  const json_t *jc;
+
+  if (NULL == json)
+    return TALER_EC_GENERIC_INVALID_RESPONSE;
+  jc = json_object_get (json, "code");
+  /* The caller already knows that the JSON represents an error,
+     so we are dealing with a missing error code here.  */
+  if (NULL == jc)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Expected Taler error code `code' in JSON, but field does not 
exist!\n");
+    return TALER_EC_INVALID;
+  }
+  if (json_is_integer (jc))
+    return (enum TALER_ErrorCode) json_integer_value (jc);
+  GNUNET_break_op (0);
+  return TALER_EC_INVALID;
+}
+
+
+const char *
+TALER_JSON_get_error_hint (const json_t *json)
+{
+  const json_t *jc;
+
+  if (NULL == json)
+    return NULL;
+  jc = json_object_get (json,
+                        "hint");
+  if (NULL == jc)
+    return NULL; /* no hint, is allowed */
+  if (! json_is_string (jc))
+  {
+    /* Hints must be strings */
+    GNUNET_break_op (0);
+    return NULL;
+  }
+  return json_string_value (jc);
+}
+
+
+enum TALER_ErrorCode
+TALER_JSON_get_error_code2 (const void *data,
+                            size_t data_size)
+{
+  json_t *json;
+  enum TALER_ErrorCode ec;
+  json_error_t err;
+
+  json = json_loadb (data,
+                     data_size,
+                     JSON_REJECT_DUPLICATES,
+                     &err);
+  if (NULL == json)
+    return TALER_EC_INVALID;
+  ec = TALER_JSON_get_error_code (json);
+  json_decref (json);
+  if (ec == TALER_EC_NONE)
+    return TALER_EC_INVALID;
+  return ec;
+}
+
+
+void
+TALER_deposit_policy_hash (const json_t *policy,
+                           struct TALER_ExtensionPolicyHashP *ech)
+{
+  GNUNET_assert (GNUNET_OK ==
+                 dump_and_hash (policy,
+                                "taler-extensions-policy",
+                                &ech->hash));
+}
+
+
+char *
+TALER_JSON_canonicalize (const json_t *input)
+{
+  char *wire_enc;
+
+  if (NULL == (wire_enc = json_dumps (input,
+                                      JSON_ENCODE_ANY
+                                      | JSON_COMPACT
+                                      | JSON_SORT_KEYS)))
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  TALER_rfc8785encode (&wire_enc);
+  return wire_enc;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_extensions_manifests_hash (const json_t *manifests,
+                                      struct TALER_ExtensionManifestsHashP 
*ech)
+{
+  return dump_and_hash (manifests,
+                        "taler-extensions-manifests",
+                        &ech->hash);
+}
+
+
+/* End of json/json.c */
diff --git a/src/json/json_helper.c b/src/json/json_helper.c
new file mode 100644
index 0000000..6c96035
--- /dev/null
+++ b/src/json/json_helper.c
@@ -0,0 +1,1366 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2021 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file json/json_helper.c
+ * @brief helper functions to generate specifications to parse
+ *        Taler-specific JSON objects with libgnunetjson
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_util.h"
+#include "taler_json_lib.h"
+
+
+/**
+ * Convert string value to numeric cipher value.
+ *
+ * @param cipher_s input string
+ * @return numeric cipher value
+ */
+static enum TALER_DenominationCipher
+string_to_cipher (const char *cipher_s)
+{
+  if ((0 == strcasecmp (cipher_s,
+                        "RSA")) ||
+      (0 == strcasecmp (cipher_s,
+                        "RSA+age_restricted")))
+    return TALER_DENOMINATION_RSA;
+  if ((0 == strcasecmp (cipher_s,
+                        "CS")) ||
+      (0 == strcasecmp (cipher_s,
+                        "CS+age_restricted")))
+    return TALER_DENOMINATION_CS;
+  return TALER_DENOMINATION_INVALID;
+}
+
+
+json_t *
+TALER_JSON_from_amount (const struct TALER_Amount *amount)
+{
+  char *amount_str = TALER_amount_to_string (amount);
+
+  GNUNET_assert (NULL != amount_str);
+  {
+    json_t *j = json_string (amount_str);
+
+    GNUNET_free (amount_str);
+    return j;
+  }
+}
+
+
+/**
+ * Parse given JSON object to Amount
+ *
+ * @param cls closure, expected currency, or NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_amount (void *cls,
+              json_t *root,
+              struct GNUNET_JSON_Specification *spec)
+{
+  const char *currency = cls;
+  struct TALER_Amount *r_amount = spec->ptr;
+
+  (void) cls;
+  if (! json_is_string (root))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_string_to_amount (json_string_value (root),
+                              r_amount))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if ( (NULL != currency) &&
+       (0 !=
+        strcasecmp (currency,
+                    r_amount->currency)) )
+  {
+    GNUNET_break_op (0);
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Expected currency `%s', but amount used currency `%s' in 
field `%s'\n",
+                currency,
+                r_amount->currency,
+                spec->field);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_amount (const char *name,
+                        const char *currency,
+                        struct TALER_Amount *r_amount)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_amount,
+    .cleaner = NULL,
+    .cls = (void *) currency,
+    .field = name,
+    .ptr = r_amount,
+    .ptr_size = 0,
+    .size_ptr = NULL
+  };
+
+  GNUNET_assert (NULL != currency);
+  return ret;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_amount_any (const char *name,
+                            struct TALER_Amount *r_amount)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_amount,
+    .cleaner = NULL,
+    .cls = NULL,
+    .field = name,
+    .ptr = r_amount,
+    .ptr_size = 0,
+    .size_ptr = NULL
+  };
+
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to currency spec.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_cspec (void *cls,
+             json_t *root,
+             struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_CurrencySpecification *r_cspec = spec->ptr;
+  const char *name;
+  const char *currency;
+  const char *decimal_separator;
+  uint32_t fid;
+  uint32_t fnd;
+  uint32_t ftzd;
+  const json_t *map;
+  struct GNUNET_JSON_Specification gspec[] = {
+    GNUNET_JSON_spec_string ("currency",
+                             &currency),
+    GNUNET_JSON_spec_string ("name",
+                             &name),
+    GNUNET_JSON_spec_string ("decimal_separator",
+                             &decimal_separator),
+    GNUNET_JSON_spec_uint32 ("num_fractional_input_digits",
+                             &fid),
+    GNUNET_JSON_spec_uint32 ("num_fractional_normal_digits",
+                             &fnd),
+    GNUNET_JSON_spec_uint32 ("num_fractional_trailing_zero_digits",
+                             &ftzd),
+    GNUNET_JSON_spec_bool ("is_currency_name_leading",
+                           &r_cspec->is_currency_name_leading),
+    GNUNET_JSON_spec_object_const ("alt_unit_names",
+                                   &map),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *emsg;
+  unsigned int eline;
+
+  (void) cls;
+  memset (r_cspec->currency,
+          0,
+          sizeof (r_cspec->currency));
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (root,
+                         gspec,
+                         &emsg,
+                         &eline))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Failed to parse %s at %u: %s\n",
+                spec[eline].field,
+                eline,
+                emsg);
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (strlen (currency) >= TALER_CURRENCY_LEN)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if ( (fid > TALER_AMOUNT_FRAC_LEN) ||
+       (fnd > TALER_AMOUNT_FRAC_LEN) ||
+       (ftzd > TALER_AMOUNT_FRAC_LEN) )
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_check_currency (currency))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  strcpy (r_cspec->currency,
+          currency);
+  if (GNUNET_OK !=
+      TALER_check_currency_scale_map (map))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  r_cspec->name = GNUNET_strdup (name);
+  r_cspec->decimal_separator = GNUNET_strdup (decimal_separator);
+  r_cspec->map_alt_unit_names = json_incref ((json_t *) map);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Cleanup data left from parsing encrypted contract.
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_cspec (void *cls,
+             struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_CurrencySpecification *cspec = spec->ptr;
+
+  (void) cls;
+  GNUNET_free (cspec->name);
+  GNUNET_free (cspec->decimal_separator);
+  json_decref (cspec->map_alt_unit_names);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_currency_specification (
+  const char *name,
+  struct TALER_CurrencySpecification *r_cspec)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_cspec,
+    .cleaner = &clean_cspec,
+    .cls = NULL,
+    .field = name,
+    .ptr = r_cspec,
+    .ptr_size = 0,
+    .size_ptr = NULL
+  };
+
+  return ret;
+}
+
+
+static enum GNUNET_GenericReturnValue
+parse_denomination_group (void *cls,
+                          json_t *root,
+                          struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_DenominationGroup *group = spec->ptr;
+  const char *cipher;
+  const char *currency = cls;
+  bool age_mask_missing = false;
+  bool has_age_restricted_suffix = false;
+  struct GNUNET_JSON_Specification gspec[] = {
+    GNUNET_JSON_spec_string ("cipher",
+                             &cipher),
+    TALER_JSON_spec_amount ("value",
+                            currency,
+                            &group->value),
+    TALER_JSON_SPEC_DENOM_FEES ("fee",
+                                currency,
+                                &group->fees),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_uint32 ("age_mask",
+                               &group->age_mask.bits),
+      &age_mask_missing),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *emsg;
+  unsigned int eline;
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (root,
+                         gspec,
+                         &emsg,
+                         &eline))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Failed to parse %s at %u: %s\n",
+                spec[eline].field,
+                eline,
+                emsg);
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  group->cipher = string_to_cipher (cipher);
+  if (TALER_DENOMINATION_INVALID == group->cipher)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* age_mask and suffix must be consistent */
+  has_age_restricted_suffix =
+    (NULL != strstr (cipher, "+age_restricted"));
+  if (has_age_restricted_suffix && age_mask_missing)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  if (age_mask_missing)
+    group->age_mask.bits = 0;
+
+  return GNUNET_OK;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_denomination_group (const char *name,
+                                    const char *currency,
+                                    struct TALER_DenominationGroup *group)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .cls = (void *) currency,
+    .parser = &parse_denomination_group,
+    .cleaner = NULL,
+    .field = name,
+    .ptr = group,
+    .ptr_size = sizeof(*group),
+    .size_ptr = NULL,
+  };
+
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to an encrypted contract.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_econtract (void *cls,
+                 json_t *root,
+                 struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_EncryptedContract *econtract = spec->ptr;
+  struct GNUNET_JSON_Specification ispec[] = {
+    GNUNET_JSON_spec_varsize ("econtract",
+                              &econtract->econtract,
+                              &econtract->econtract_size),
+    GNUNET_JSON_spec_fixed_auto ("econtract_sig",
+                                 &econtract->econtract_sig),
+    GNUNET_JSON_spec_fixed_auto ("contract_pub",
+                                 &econtract->contract_pub),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *emsg;
+  unsigned int eline;
+
+  (void) cls;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (root,
+                         ispec,
+                         &emsg,
+                         &eline))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Cleanup data left from parsing encrypted contract.
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_econtract (void *cls,
+                 struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_EncryptedContract *econtract = spec->ptr;
+
+  (void) cls;
+  GNUNET_free (econtract->econtract);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_econtract (const char *name,
+                           struct TALER_EncryptedContract *econtract)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_econtract,
+    .cleaner = &clean_econtract,
+    .cls = NULL,
+    .field = name,
+    .ptr = econtract,
+    .ptr_size = 0,
+    .size_ptr = NULL
+  };
+
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to an age commitmnet
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_age_commitment (void *cls,
+                      json_t *root,
+                      struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_AgeCommitment *age_commitment = spec->ptr;
+  json_t *pk;
+  unsigned int idx;
+  size_t num;
+
+  (void) cls;
+  if ( (NULL == root) ||
+       (! json_is_array (root)))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  num = json_array_size (root);
+  if (32 <= num || 0 == num)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  age_commitment->num = num;
+  age_commitment->keys =
+    GNUNET_new_array (num,
+                      struct TALER_AgeCommitmentPublicKeyP);
+
+  json_array_foreach (root, idx, pk) {
+    const char *emsg;
+    unsigned int eline;
+    struct GNUNET_JSON_Specification pkspec[] = {
+      GNUNET_JSON_spec_fixed_auto (
+        NULL,
+        &age_commitment->keys[idx].pub),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (pk,
+                           pkspec,
+                           &emsg,
+                           &eline))
+    {
+      GNUNET_break_op (0);
+      GNUNET_JSON_parse_free (spec);
+      return GNUNET_SYSERR;
+    }
+  };
+
+  return GNUNET_OK;
+}
+
+
+/**
+ * Cleanup data left fom parsing age commitment
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_age_commitment (void *cls,
+                      struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_AgeCommitment *age_commitment = spec->ptr;
+
+  (void) cls;
+
+  if (NULL == age_commitment ||
+      NULL == age_commitment->keys)
+    return;
+
+  age_commitment->num = 0;
+  GNUNET_free (age_commitment->keys);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_age_commitment (const char *name,
+                                struct TALER_AgeCommitment *age_commitment)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_age_commitment,
+    .cleaner = &clean_age_commitment,
+    .cls = NULL,
+    .field = name,
+    .ptr = age_commitment,
+    .ptr_size = 0,
+    .size_ptr = NULL
+  };
+
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to denomination public key.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_denom_pub (void *cls,
+                 json_t *root,
+                 struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_DenominationPublicKey *denom_pub = spec->ptr;
+  const char *cipher;
+  bool age_mask_missing = false;
+  struct GNUNET_JSON_Specification dspec[] = {
+    GNUNET_JSON_spec_string ("cipher",
+                             &cipher),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_uint32 ("age_mask",
+                               &denom_pub->age_mask.bits),
+      &age_mask_missing),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *emsg;
+  unsigned int eline;
+
+  (void) cls;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (root,
+                         dspec,
+                         &emsg,
+                         &eline))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  if (age_mask_missing)
+    denom_pub->age_mask.bits = 0;
+
+  denom_pub->cipher = string_to_cipher (cipher);
+  switch (denom_pub->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_rsa_public_key (
+          "rsa_public_key",
+          &denom_pub->details.rsa_public_key),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      return GNUNET_OK;
+    }
+  case TALER_DENOMINATION_CS:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_fixed ("cs_public_key",
+                                &denom_pub->details.cs_public_key,
+                                sizeof (denom_pub->details.cs_public_key)),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      return GNUNET_OK;
+    }
+  default:
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+}
+
+
+/**
+ * Cleanup data left from parsing denomination public key.
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_denom_pub (void *cls,
+                 struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_DenominationPublicKey *denom_pub = spec->ptr;
+
+  (void) cls;
+  TALER_denom_pub_free (denom_pub);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_denom_pub (const char *field,
+                           struct TALER_DenominationPublicKey *pk)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_denom_pub,
+    .cleaner = &clean_denom_pub,
+    .field = field,
+    .ptr = pk
+  };
+
+  pk->cipher = TALER_DENOMINATION_INVALID;
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object partially into a denomination public key.
+ *
+ * Depending on the cipher in cls, it parses the corresponding public key type.
+ *
+ * @param cls closure, enum TALER_DenominationCipher
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_denom_pub_cipher (void *cls,
+                        json_t *root,
+                        struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_DenominationPublicKey *denom_pub = spec->ptr;
+  enum TALER_DenominationCipher cipher =
+    (enum TALER_DenominationCipher) (long) cls;
+  const char *emsg;
+  unsigned int eline;
+
+  switch (cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_rsa_public_key (
+          "rsa_pub",
+          &denom_pub->details.rsa_public_key),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      denom_pub->cipher = cipher;
+      return GNUNET_OK;
+    }
+  case TALER_DENOMINATION_CS:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_fixed ("cs_pub",
+                                &denom_pub->details.cs_public_key,
+                                sizeof (denom_pub->details.cs_public_key)),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      denom_pub->cipher = cipher;
+      return GNUNET_OK;
+    }
+  default:
+    GNUNET_break_op (0);
+    denom_pub->cipher = 0;
+    return GNUNET_SYSERR;
+  }
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_denom_pub_cipher (const char *field,
+                                  enum TALER_DenominationCipher cipher,
+                                  struct TALER_DenominationPublicKey *pk)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_denom_pub_cipher,
+    .cleaner = &clean_denom_pub,
+    .field = field,
+    .cls = (void *) cipher,
+    .ptr = pk
+  };
+
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to denomination signature.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_denom_sig (void *cls,
+                 json_t *root,
+                 struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_DenominationSignature *denom_sig = spec->ptr;
+  const char *cipher;
+  struct GNUNET_JSON_Specification dspec[] = {
+    GNUNET_JSON_spec_string ("cipher",
+                             &cipher),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *emsg;
+  unsigned int eline;
+
+  (void) cls;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (root,
+                         dspec,
+                         &emsg,
+                         &eline))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  denom_sig->cipher = string_to_cipher (cipher);
+  switch (denom_sig->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_rsa_signature (
+          "rsa_signature",
+          &denom_sig->details.rsa_signature),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      return GNUNET_OK;
+    }
+  case TALER_DENOMINATION_CS:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_fixed_auto ("cs_signature_r",
+                                     &denom_sig->details.cs_signature.r_point),
+        GNUNET_JSON_spec_fixed_auto ("cs_signature_s",
+                                     
&denom_sig->details.cs_signature.s_scalar),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      return GNUNET_OK;
+    }
+  default:
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+}
+
+
+/**
+ * Cleanup data left from parsing denomination public key.
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_denom_sig (void *cls,
+                 struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_DenominationSignature *denom_sig = spec->ptr;
+
+  (void) cls;
+  TALER_denom_sig_free (denom_sig);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_denom_sig (const char *field,
+                           struct TALER_DenominationSignature *sig)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_denom_sig,
+    .cleaner = &clean_denom_sig,
+    .field = field,
+    .ptr = sig
+  };
+
+  sig->cipher = TALER_DENOMINATION_INVALID;
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to blinded denomination signature.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_blinded_denom_sig (void *cls,
+                         json_t *root,
+                         struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_BlindedDenominationSignature *denom_sig = spec->ptr;
+  const char *cipher;
+  struct GNUNET_JSON_Specification dspec[] = {
+    GNUNET_JSON_spec_string ("cipher",
+                             &cipher),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *emsg;
+  unsigned int eline;
+
+  (void) cls;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (root,
+                         dspec,
+                         &emsg,
+                         &eline))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  denom_sig->cipher = string_to_cipher (cipher);
+  switch (denom_sig->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_rsa_signature (
+          "blinded_rsa_signature",
+          &denom_sig->details.blinded_rsa_signature),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      return GNUNET_OK;
+    }
+  case TALER_DENOMINATION_CS:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_uint32 ("b",
+                                 &denom_sig->details.blinded_cs_answer.b),
+        GNUNET_JSON_spec_fixed_auto ("s",
+                                     &denom_sig->details.blinded_cs_answer.
+                                     s_scalar),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      return GNUNET_OK;
+    }
+    break;
+  default:
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+}
+
+
+/**
+ * Cleanup data left from parsing denomination public key.
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_blinded_denom_sig (void *cls,
+                         struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_BlindedDenominationSignature *denom_sig = spec->ptr;
+
+  (void) cls;
+  TALER_blinded_denom_sig_free (denom_sig);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_blinded_denom_sig (
+  const char *field,
+  struct TALER_BlindedDenominationSignature *sig)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_blinded_denom_sig,
+    .cleaner = &clean_blinded_denom_sig,
+    .field = field,
+    .ptr = sig
+  };
+
+  sig->cipher = TALER_DENOMINATION_INVALID;
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to blinded planchet.
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_blinded_planchet (void *cls,
+                        json_t *root,
+                        struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_BlindedPlanchet *blinded_planchet = spec->ptr;
+  const char *cipher;
+  struct GNUNET_JSON_Specification dspec[] = {
+    GNUNET_JSON_spec_string ("cipher",
+                             &cipher),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *emsg;
+  unsigned int eline;
+
+  (void) cls;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (root,
+                         dspec,
+                         &emsg,
+                         &eline))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  blinded_planchet->cipher = string_to_cipher (cipher);
+  switch (blinded_planchet->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_varsize (
+          "rsa_blinded_planchet",
+          &blinded_planchet->details.rsa_blinded_planchet.blinded_msg,
+          &blinded_planchet->details.rsa_blinded_planchet.blinded_msg_size),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      return GNUNET_OK;
+    }
+  case TALER_DENOMINATION_CS:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_fixed_auto (
+          "cs_nonce",
+          &blinded_planchet->details.cs_blinded_planchet.nonce),
+        GNUNET_JSON_spec_fixed_auto (
+          "cs_blinded_c0",
+          &blinded_planchet->details.cs_blinded_planchet.c[0]),
+        GNUNET_JSON_spec_fixed_auto (
+          "cs_blinded_c1",
+          &blinded_planchet->details.cs_blinded_planchet.c[1]),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      return GNUNET_OK;
+    }
+    break;
+  default:
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+}
+
+
+/**
+ * Cleanup data left from parsing blinded planchet.
+ *
+ * @param cls closure, NULL
+ * @param[out] spec where to free the data
+ */
+static void
+clean_blinded_planchet (void *cls,
+                        struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_BlindedPlanchet *blinded_planchet = spec->ptr;
+
+  (void) cls;
+  TALER_blinded_planchet_free (blinded_planchet);
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_blinded_planchet (const char *field,
+                                  struct TALER_BlindedPlanchet 
*blinded_planchet)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_blinded_planchet,
+    .cleaner = &clean_blinded_planchet,
+    .field = field,
+    .ptr = blinded_planchet
+  };
+
+  blinded_planchet->cipher = TALER_DENOMINATION_INVALID;
+  return ret;
+}
+
+
+/**
+ * Parse given JSON object to exchange withdraw values (/csr).
+ *
+ * @param cls closure, NULL
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_exchange_withdraw_values (void *cls,
+                                json_t *root,
+                                struct GNUNET_JSON_Specification *spec)
+{
+  struct TALER_ExchangeWithdrawValues *ewv = spec->ptr;
+  const char *cipher;
+  struct GNUNET_JSON_Specification dspec[] = {
+    GNUNET_JSON_spec_string ("cipher",
+                             &cipher),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *emsg;
+  unsigned int eline;
+
+  (void) cls;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (root,
+                         dspec,
+                         &emsg,
+                         &eline))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  ewv->cipher = string_to_cipher (cipher);
+  switch (ewv->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    return GNUNET_OK;
+  case TALER_DENOMINATION_CS:
+    {
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_fixed (
+          "r_pub_0",
+          &ewv->details.cs_values.r_pub[0],
+          sizeof (struct GNUNET_CRYPTO_CsRPublic)),
+        GNUNET_JSON_spec_fixed (
+          "r_pub_1",
+          &ewv->details.cs_values.r_pub[1],
+          sizeof (struct GNUNET_CRYPTO_CsRPublic)),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (root,
+                             ispec,
+                             &emsg,
+                             &eline))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      return GNUNET_OK;
+    }
+  default:
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_exchange_withdraw_values (
+  const char *field,
+  struct TALER_ExchangeWithdrawValues *ewv)
+{
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_exchange_withdraw_values,
+    .field = field,
+    .ptr = ewv
+  };
+
+  ewv->cipher = TALER_DENOMINATION_INVALID;
+  return ret;
+}
+
+
+/**
+ * Closure for #parse_i18n_string.
+ */
+struct I18nContext
+{
+  /**
+   * Language pattern to match.
+   */
+  char *lp;
+
+  /**
+   * Name of the field to match.
+   */
+  const char *field;
+};
+
+
+/**
+ * Parse given JSON object to internationalized string.
+ *
+ * @param cls closure, our `struct I18nContext *`
+ * @param root the json object representing data
+ * @param[out] spec where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static enum GNUNET_GenericReturnValue
+parse_i18n_string (void *cls,
+                   json_t *root,
+                   struct GNUNET_JSON_Specification *spec)
+{
+  struct I18nContext *ctx = cls;
+  json_t *i18n;
+  json_t *val;
+
+  {
+    char *i18nf;
+
+    GNUNET_asprintf (&i18nf,
+                     "%s_i18n",
+                     ctx->field);
+    i18n = json_object_get (root,
+                            i18nf);
+    GNUNET_free (i18nf);
+  }
+
+  val = json_object_get (root,
+                         ctx->field);
+  if ( (NULL != i18n) &&
+       (NULL != ctx->lp) )
+  {
+    double best = 0.0;
+    json_t *pos;
+    const char *lang;
+
+    json_object_foreach (i18n, lang, pos)
+    {
+      double score;
+
+      score = TALER_language_matches (ctx->lp,
+                                      lang);
+      if (score > best)
+      {
+        best = score;
+        val = pos;
+      }
+    }
+  }
+
+  {
+    const char *str;
+
+    str = json_string_value (val);
+    *(const char **) spec->ptr = str;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called to clean up data from earlier parsing.
+ *
+ * @param cls closure
+ * @param spec our specification entry with data to clean.
+ */
+static void
+i18n_cleaner (void *cls,
+              struct GNUNET_JSON_Specification *spec)
+{
+  struct I18nContext *ctx = cls;
+
+  (void) spec;
+  if (NULL != ctx)
+  {
+    GNUNET_free (ctx->lp);
+    GNUNET_free (ctx);
+  }
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_i18n_string (const char *name,
+                             const char *language_pattern,
+                             const char **strptr)
+{
+  struct I18nContext *ctx = GNUNET_new (struct I18nContext);
+  struct GNUNET_JSON_Specification ret = {
+    .parser = &parse_i18n_string,
+    .cleaner = &i18n_cleaner,
+    .cls = ctx,
+    .field = NULL, /* we want the main object */
+    .ptr = strptr,
+    .ptr_size = 0,
+    .size_ptr = NULL
+  };
+
+  ctx->lp = (NULL != language_pattern)
+    ? GNUNET_strdup (language_pattern)
+    : NULL;
+  ctx->field = name;
+  *strptr = NULL;
+  return ret;
+}
+
+
+struct GNUNET_JSON_Specification
+TALER_JSON_spec_i18n_str (const char *name,
+                          const char **strptr)
+{
+  const char *lang = getenv ("LANG");
+  char *dot;
+  char *l;
+  struct GNUNET_JSON_Specification ret;
+
+  if (NULL != lang)
+  {
+    dot = strchr (lang,
+                  '.');
+    if (NULL == dot)
+      l = GNUNET_strdup (lang);
+    else
+      l = GNUNET_strndup (lang,
+                          dot - lang);
+  }
+  else
+  {
+    l = NULL;
+  }
+  ret = TALER_JSON_spec_i18n_string (name,
+                                     l,
+                                     strptr);
+  GNUNET_free (l);
+  return ret;
+}
+
+
+/* end of json/json_helper.c */
diff --git a/src/json/json_pack.c b/src/json/json_pack.c
new file mode 100644
index 0000000..c6844c1
--- /dev/null
+++ b/src/json/json_pack.c
@@ -0,0 +1,308 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2021, 2022 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file json/json_pack.c
+ * @brief helper functions for JSON object packing
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_util.h"
+#include "taler_json_lib.h"
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_time_abs_human (const char *name,
+                                struct GNUNET_TIME_Absolute at)
+{
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+    .object = json_string (
+      GNUNET_STRINGS_absolute_time_to_string (at))
+  };
+
+  return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_econtract (
+  const char *name,
+  const struct TALER_EncryptedContract *econtract)
+{
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+  };
+
+  if (NULL == econtract)
+    return ps;
+  ps.object
+    = GNUNET_JSON_PACK (
+        GNUNET_JSON_pack_data_varsize ("econtract",
+                                       econtract->econtract,
+                                       econtract->econtract_size),
+        GNUNET_JSON_pack_data_auto ("econtract_sig",
+                                    &econtract->econtract_sig),
+        GNUNET_JSON_pack_data_auto ("contract_pub",
+                                    &econtract->contract_pub));
+  return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_age_commitment (
+  const char *name,
+  const struct TALER_AgeCommitment *age_commitment)
+{
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+  };
+  json_t *keys;
+
+  if (NULL == age_commitment ||
+      0 == age_commitment->num)
+    return ps;
+
+  GNUNET_assert (NULL !=
+                 (keys = json_array ()));
+
+  for (size_t i = 0;
+       i < age_commitment->num;
+       i++)
+  {
+    json_t *val;
+    val = GNUNET_JSON_from_data (&age_commitment->keys[i],
+                                 sizeof(age_commitment->keys[i]));
+    GNUNET_assert (NULL != val);
+    GNUNET_assert (0 ==
+                   json_array_append_new (keys, val));
+  }
+
+  ps.object = keys;
+  return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_denom_pub (
+  const char *name,
+  const struct TALER_DenominationPublicKey *pk)
+{
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+  };
+
+  if (NULL == pk)
+    return ps;
+  switch (pk->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    ps.object
+      = GNUNET_JSON_PACK (
+          GNUNET_JSON_pack_string ("cipher", "RSA"),
+          GNUNET_JSON_pack_uint64 ("age_mask",
+                                   pk->age_mask.bits),
+          GNUNET_JSON_pack_rsa_public_key ("rsa_public_key",
+                                           pk->details.rsa_public_key));
+    break;
+  case TALER_DENOMINATION_CS:
+    ps.object
+      = GNUNET_JSON_PACK (
+          GNUNET_JSON_pack_string ("cipher", "CS"),
+          GNUNET_JSON_pack_uint64 ("age_mask",
+                                   pk->age_mask.bits),
+          GNUNET_JSON_pack_data_varsize ("cs_public_key",
+                                         &pk->details.cs_public_key,
+                                         sizeof (pk->details.cs_public_key)));
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+
+  return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_denom_sig (
+  const char *name,
+  const struct TALER_DenominationSignature *sig)
+{
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+  };
+
+  if (NULL == sig)
+    return ps;
+  switch (sig->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    ps.object = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("cipher",
+                               "RSA"),
+      GNUNET_JSON_pack_rsa_signature ("rsa_signature",
+                                      sig->details.rsa_signature));
+    break;
+  case TALER_DENOMINATION_CS:
+    ps.object = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("cipher",
+                               "CS"),
+      GNUNET_JSON_pack_data_auto ("cs_signature_r",
+                                  &sig->details.cs_signature.r_point),
+      GNUNET_JSON_pack_data_auto ("cs_signature_s",
+                                  &sig->details.cs_signature.s_scalar));
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+  return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_exchange_withdraw_values (
+  const char *name,
+  const struct TALER_ExchangeWithdrawValues *ewv)
+{
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+  };
+
+  if (NULL == ewv)
+    return ps;
+  switch (ewv->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    ps.object = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("cipher",
+                               "RSA"));
+    break;
+  case TALER_DENOMINATION_CS:
+    ps.object = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("cipher",
+                               "CS"),
+      GNUNET_JSON_pack_data_varsize (
+        "r_pub_0",
+        &ewv->details.cs_values.r_pub[0],
+        sizeof(struct GNUNET_CRYPTO_CsRPublic)),
+      GNUNET_JSON_pack_data_varsize (
+        "r_pub_1",
+        &ewv->details.cs_values.r_pub[1],
+        sizeof(struct GNUNET_CRYPTO_CsRPublic))
+      );
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+  return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_blinded_denom_sig (
+  const char *name,
+  const struct TALER_BlindedDenominationSignature *sig)
+{
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+  };
+
+  if (NULL == sig)
+    return ps;
+  switch (sig->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    ps.object = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("cipher",
+                               "RSA"),
+      GNUNET_JSON_pack_rsa_signature ("blinded_rsa_signature",
+                                      sig->details.blinded_rsa_signature));
+    break;
+  case TALER_DENOMINATION_CS:
+    ps.object = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("cipher",
+                               "CS"),
+      GNUNET_JSON_pack_uint64 ("b",
+                               sig->details.blinded_cs_answer.b),
+      GNUNET_JSON_pack_data_auto ("s",
+                                  &sig->details.blinded_cs_answer.s_scalar));
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+  return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_blinded_planchet (
+  const char *name,
+  const struct TALER_BlindedPlanchet *blinded_planchet)
+{
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+  };
+
+  if (NULL == blinded_planchet)
+    return ps;
+  switch (blinded_planchet->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    ps.object = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("cipher",
+                               "RSA"),
+      GNUNET_JSON_pack_data_varsize (
+        "rsa_blinded_planchet",
+        blinded_planchet->details.rsa_blinded_planchet.blinded_msg,
+        blinded_planchet->details.rsa_blinded_planchet.blinded_msg_size));
+    break;
+  case TALER_DENOMINATION_CS:
+    ps.object = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("cipher",
+                               "CS"),
+      GNUNET_JSON_pack_data_auto (
+        "cs_nonce",
+        &blinded_planchet->details.cs_blinded_planchet.nonce),
+      GNUNET_JSON_pack_data_auto (
+        "cs_blinded_c0",
+        &blinded_planchet->details.cs_blinded_planchet.c[0]),
+      GNUNET_JSON_pack_data_auto (
+        "cs_blinded_c1",
+        &blinded_planchet->details.cs_blinded_planchet.c[1]));
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+  return ps;
+}
+
+
+struct GNUNET_JSON_PackSpec
+TALER_JSON_pack_amount (const char *name,
+                        const struct TALER_Amount *amount)
+{
+  struct GNUNET_JSON_PackSpec ps = {
+    .field_name = name,
+    .object = (NULL != amount)
+              ? TALER_JSON_from_amount (amount)
+              : NULL
+  };
+
+  return ps;
+}
+
+
+/* End of json/json_pack.c */
diff --git a/src/json/json_wire.c b/src/json/json_wire.c
new file mode 100644
index 0000000..9d22d28
--- /dev/null
+++ b/src/json/json_wire.c
@@ -0,0 +1,122 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2018, 2021 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file json/json_wire.c
+ * @brief helper functions to generate or check /wire replies
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_util.h"
+#include "taler_json_lib.h"
+
+
+enum GNUNET_GenericReturnValue
+TALER_JSON_merchant_wire_signature_hash (const json_t *wire_s,
+                                         struct TALER_MerchantWireHashP *hc)
+{
+  const char *payto_uri;
+  struct TALER_WireSaltP salt;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_string ("payto_uri",
+                             &payto_uri),
+    GNUNET_JSON_spec_fixed_auto ("salt",
+                                 &salt),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (wire_s,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Validating `%s'\n",
+              payto_uri);
+  {
+    char *err;
+
+    err = TALER_payto_validate (payto_uri);
+    if (NULL != err)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "URI `%s' ill-formed: %s\n",
+                  payto_uri,
+                  err);
+      GNUNET_free (err);
+      return GNUNET_SYSERR;
+    }
+  }
+  TALER_merchant_wire_signature_hash (payto_uri,
+                                      &salt,
+                                      hc);
+  return GNUNET_OK;
+}
+
+
+char *
+TALER_JSON_wire_to_payto (const json_t *wire_s)
+{
+  json_t *payto_o;
+  const char *payto_str;
+  char *err;
+
+  payto_o = json_object_get (wire_s,
+                             "payto_uri");
+  if ( (NULL == payto_o) ||
+       (NULL == (payto_str = json_string_value (payto_o))) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Malformed wire record encountered: lacks payto://-url\n");
+    return NULL;
+  }
+  if (NULL !=
+      (err = TALER_payto_validate (payto_str)))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Malformed wire record encountered: payto URI `%s' invalid: 
%s\n",
+                payto_str,
+                err);
+    GNUNET_free (err);
+    return NULL;
+  }
+  return GNUNET_strdup (payto_str);
+}
+
+
+char *
+TALER_JSON_wire_to_method (const json_t *wire_s)
+{
+  json_t *payto_o;
+  const char *payto_str;
+
+  payto_o = json_object_get (wire_s,
+                             "payto_uri");
+  if ( (NULL == payto_o) ||
+       (NULL == (payto_str = json_string_value (payto_o))) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Fatally malformed wire record encountered: lacks 
payto://-url\n");
+    return NULL;
+  }
+  return TALER_payto_get_method (payto_str);
+}
+
+
+/* end of json_wire.c */
diff --git a/src/json/test_json.c b/src/json/test_json.c
new file mode 100644
index 0000000..d37f66e
--- /dev/null
+++ b/src/json/test_json.c
@@ -0,0 +1,439 @@
+/*
+  This file is part of TALER
+  (C) 2015, 2016, 2020 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file json/test_json.c
+ * @brief Tests for Taler-specific crypto logic
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_json_lib.h"
+
+
+/**
+ * Test amount conversion from/to JSON.
+ *
+ * @return 0 on success
+ */
+static int
+test_amount (void)
+{
+  json_t *j;
+  struct TALER_Amount a1;
+  struct TALER_Amount a2;
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount ("amount",
+                            "EUR",
+                            &a2),
+    GNUNET_JSON_spec_end ()
+  };
+
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:4.3",
+                                         &a1));
+  j = json_pack ("{s:o}", "amount", TALER_JSON_from_amount (&a1));
+  GNUNET_assert (NULL != j);
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_JSON_parse (j, spec,
+                                    NULL, NULL));
+  GNUNET_assert (0 ==
+                 TALER_amount_cmp (&a1,
+                                   &a2));
+  json_decref (j);
+  return 0;
+}
+
+
+struct TestPath_Closure
+{
+  const char **object_ids;
+
+  const json_t **parents;
+
+  unsigned int results_length;
+
+  int cmp_result;
+};
+
+
+static void
+path_cb (void *cls,
+         const char *object_id,
+         json_t *parent)
+{
+  struct TestPath_Closure *cmp = cls;
+  if (NULL == cmp)
+    return;
+  unsigned int i = cmp->results_length;
+  if ((0 != strcmp (cmp->object_ids[i],
+                    object_id)) ||
+      (1 != json_equal (cmp->parents[i],
+                        parent)))
+    cmp->cmp_result = 1;
+  cmp->results_length += 1;
+}
+
+
+static int
+test_contract (void)
+{
+  struct TALER_PrivateContractHashP h1;
+  struct TALER_PrivateContractHashP h2;
+  json_t *c1;
+  json_t *c2;
+  json_t *c3;
+  json_t *c4;
+
+  c1 = json_pack ("{s:s, s:{s:s, s:{s:b}}}",
+                  "k1", "v1",
+                  "k2", "n1", "n2",
+                  /***/ "$forgettable", "n1", true);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_seed_forgettable (c1));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_hash (c1,
+                                           &h1));
+  json_decref (c1);
+
+  c1 = json_pack ("{s:s, s:{s:s, s:{s:s}}}",
+                  "k1", "v1",
+                  "k2", "n1", "n2",
+                  /***/ "$forgettable", "n1", "salt");
+  GNUNET_assert (NULL != c1);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_mark_forgettable (c1,
+                                                       "k1"));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_mark_forgettable (c1,
+                                                       "k2"));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_hash (c1,
+                                           &h1));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_part_forget (c1,
+                                                  "k1"));
+  /* check salt was forgotten */
+  GNUNET_assert (NULL ==
+                 json_object_get (json_object_get (c1,
+                                                   "$forgettable"),
+                                  "k1"));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_hash (c1,
+                                           &h2));
+  if (0 !=
+      GNUNET_memcmp (&h1,
+                     &h2))
+  {
+    GNUNET_break (0);
+    json_decref (c1);
+    return 1;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_part_forget (json_object_get (c1,
+                                                                   "k2"),
+                                                  "n1"));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_hash (c1,
+                                           &h2));
+  if (0 !=
+      GNUNET_memcmp (&h1,
+                     &h2))
+  {
+    GNUNET_break (0);
+    json_decref (c1);
+    return 1;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_part_forget (c1,
+                                                  "k2"));
+  // json_dumpf (c1, stderr, JSON_INDENT (2));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_hash (c1,
+                                           &h2));
+  json_decref (c1);
+  if (0 !=
+      GNUNET_memcmp (&h1,
+                     &h2))
+  {
+    GNUNET_break (0);
+    return 1;
+  }
+
+  c1 = json_pack ("{s:I, s:{s:s}, s:{s:b, s:{s:s}}, s:{s:s}}",
+                  "k1", 1,
+                  "$forgettable", "k1", "SALT",
+                  "k2", "n1", true,
+                  /***/ "$forgettable", "n1", "salt",
+                  "k3", "n1", "string");
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_hash (c1,
+                                           &h1));
+  // json_dumpf (c1, stderr, JSON_INDENT (2));
+  json_decref (c1);
+  {
+    char *s;
+
+    s = GNUNET_STRINGS_data_to_string_alloc (&h1,
+                                             sizeof (h1));
+    if (0 !=
+        strcmp (s,
+                
"VDE8JPX0AEEE3EX1K8E11RYEWSZQKGGZCV6BWTE4ST1C8711P7H850Z7F2Q2HSSYETX87ERC2JNHWB7GTDWTDWMM716VKPSRBXD7SRR"))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Invalid reference hash: %s\n",
+                  s);
+      GNUNET_free (s);
+      return 1;
+    }
+    GNUNET_free (s);
+  }
+
+
+  c2 = json_pack ("{s:s}",
+                  "n1", "n2");
+  GNUNET_assert (NULL != c2);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_mark_forgettable (c2,
+                                                       "n1"));
+  c3 = json_pack ("{s:s, s:o}",
+                  "k1", "v1",
+                  "k2", c2);
+  GNUNET_assert (NULL != c3);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_mark_forgettable (c3,
+                                                       "k1"));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_hash (c3,
+                                           &h1));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_part_forget (c2,
+                                                  "n1"));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_hash (c3,
+                                           &h2));
+  json_decref (c3);
+  c4 = json_pack ("{s:{s:s}, s:[{s:s}, {s:s}, {s:s}]}",
+                  "abc1",
+                  "xyz", "value",
+                  "fruit",
+                  "name", "banana",
+                  "name", "apple",
+                  "name", "orange");
+  GNUNET_assert (NULL != c4);
+  GNUNET_assert (GNUNET_SYSERR ==
+                 TALER_JSON_expand_path (c4,
+                                         "%.xyz",
+                                         &path_cb,
+                                         NULL));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_expand_path (c4,
+                                         "$.nonexistent_id",
+                                         &path_cb,
+                                         NULL));
+  GNUNET_assert (GNUNET_SYSERR ==
+                 TALER_JSON_expand_path (c4,
+                                         "$.fruit[n]",
+                                         &path_cb,
+                                         NULL));
+
+  {
+    const char *object_ids[] = { "xyz" };
+    const json_t *parents[] = {
+      json_object_get (c4,
+                       "abc1")
+    };
+    struct TestPath_Closure tp = {
+      .object_ids = object_ids,
+      .parents = parents,
+      .results_length = 0,
+      .cmp_result = 0
+    };
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_JSON_expand_path (c4,
+                                           "$.abc1.xyz",
+                                           &path_cb,
+                                           &tp));
+    GNUNET_assert (1 == tp.results_length);
+    GNUNET_assert (0 == tp.cmp_result);
+  }
+  {
+    const char *object_ids[] = { "name" };
+    const json_t *parents[] = {
+      json_array_get (json_object_get (c4,
+                                       "fruit"),
+                      0)
+    };
+    struct TestPath_Closure tp = {
+      .object_ids = object_ids,
+      .parents = parents,
+      .results_length = 0,
+      .cmp_result = 0
+    };
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_JSON_expand_path (c4,
+                                           "$.fruit[0].name",
+                                           &path_cb,
+                                           &tp));
+    GNUNET_assert (1 == tp.results_length);
+    GNUNET_assert (0 == tp.cmp_result);
+  }
+  {
+    const char *object_ids[] = { "name", "name", "name" };
+    const json_t *parents[] = {
+      json_array_get (json_object_get (c4,
+                                       "fruit"),
+                      0),
+      json_array_get (json_object_get (c4,
+                                       "fruit"),
+                      1),
+      json_array_get (json_object_get (c4,
+                                       "fruit"),
+                      2)
+    };
+    struct TestPath_Closure tp = {
+      .object_ids = object_ids,
+      .parents = parents,
+      .results_length = 0,
+      .cmp_result = 0
+    };
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_JSON_expand_path (c4,
+                                           "$.fruit[*].name",
+                                           &path_cb,
+                                           &tp));
+    GNUNET_assert (3 == tp.results_length);
+    GNUNET_assert (0 == tp.cmp_result);
+  }
+  json_decref (c4);
+  if (0 !=
+      GNUNET_memcmp (&h1,
+                     &h2))
+  {
+    GNUNET_break (0);
+    return 1;
+  }
+  return 0;
+}
+
+
+static int
+test_json_canon (void)
+{
+  {
+    json_t *c1;
+    char *canon;
+    c1 = json_pack ("{s:s}",
+                    "k1", "Hello\nWorld");
+
+    canon = TALER_JSON_canonicalize (c1);
+    GNUNET_assert (NULL != canon);
+
+    printf ("canon: '%s'\n", canon);
+
+    GNUNET_assert (0 == strcmp (canon,
+                                "{\"k1\":\"Hello\\nWorld\"}"));
+  }
+  {
+    json_t *c1;
+    char *canon;
+    c1 = json_pack ("{s:s}",
+                    "k1", "Testing “unicode” characters");
+
+    canon = TALER_JSON_canonicalize (c1);
+    GNUNET_assert (NULL != canon);
+
+    printf ("canon: '%s'\n", canon);
+
+    GNUNET_assert (0 == strcmp (canon,
+                                "{\"k1\":\"Testing “unicode” characters\"}"));
+  }
+  {
+    json_t *c1;
+    char *canon;
+    c1 = json_pack ("{s:s}",
+                    "k1", "low range \x05 chars");
+
+    canon = TALER_JSON_canonicalize (c1);
+    GNUNET_assert (NULL != canon);
+
+    printf ("canon: '%s'\n", canon);
+
+    GNUNET_assert (0 == strcmp (canon,
+                                "{\"k1\":\"low range \\u0005 chars\"}"));
+  }
+
+
+  return 0;
+}
+
+
+static int
+test_rfc8785 (void)
+{
+  struct TALER_PrivateContractHashP h1;
+  json_t *c1;
+
+  c1 = json_pack ("{s:s}",
+                  "k1", "\x08\x0B\t\1\\\x0d");
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_contract_hash (c1,
+                                           &h1));
+  {
+    char *s;
+
+    s = GNUNET_STRINGS_data_to_string_alloc (&h1,
+                                             sizeof (h1));
+    if (0 !=
+        strcmp (s,
+                
"531S33T8ZRGW6548G7T67PMDNGS4Z1D8A2GMB87G3PNKYTW6KGF7Q99XVCGXBKVA2HX6PR5ENJ1PQ5ZTYMMXQB6RM7S82VP7ZG2X5G8"))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Invalid reference hash: %s\n",
+                  s);
+      GNUNET_free (s);
+      json_decref (c1);
+      return 1;
+    }
+    GNUNET_free (s);
+  }
+  json_decref (c1);
+  return 0;
+}
+
+
+int
+main (int argc,
+      const char *const argv[])
+{
+  (void) argc;
+  (void) argv;
+  GNUNET_log_setup ("test-json",
+                    "WARNING",
+                    NULL);
+  if (0 != test_amount ())
+    return 1;
+  if (0 != test_contract ())
+    return 2;
+  if (0 != test_json_canon ())
+    return 2;
+  if (0 != test_rfc8785 ())
+    return 2;
+  return 0;
+}
+
+
+/* end of test_json.c */
diff --git a/src/mhd/Makefile.am b/src/mhd/Makefile.am
new file mode 100644
index 0000000..f7f052d
--- /dev/null
+++ b/src/mhd/Makefile.am
@@ -0,0 +1,29 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+if USE_COVERAGE
+  AM_CFLAGS = --coverage -O0
+  XLIB = -lgcov
+endif
+
+lib_LTLIBRARIES = \
+  libtalermhd.la
+
+libtalermhd_la_SOURCES = \
+  mhd_config.c \
+  mhd_legal.c \
+  mhd_parsing.c \
+  mhd_responses.c \
+  mhd_run.c
+libtalermhd_la_LDFLAGS = \
+  -version-info 0:0:0 \
+  -no-undefined
+libtalermhd_la_LIBADD = \
+  -lgnunetjson \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  -lgnunetutil \
+  -lmicrohttpd \
+  -ljansson \
+  -lz \
+  $(XLIB)
diff --git a/src/mhd/mhd_config.c b/src/mhd/mhd_config.c
new file mode 100644
index 0000000..31ec3e4
--- /dev/null
+++ b/src/mhd/mhd_config.c
@@ -0,0 +1,493 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014--2020 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU 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, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file mhd_config.c
+ * @brief functions to configure and setup MHD
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_mhd_lib.h"
+
+
+/**
+ * Backlog for listen operation on UNIX domain sockets.
+ */
+#define UNIX_BACKLOG 500
+
+
+/**
+ * Parse the configuration to determine on which port
+ * or UNIX domain path we should run an HTTP service.
+ *
+ * @param cfg configuration to parse
+ * @param section section of the configuration to parse (usually "exchange")
+ * @param[out] rport set to the port number, or 0 for none
+ * @param[out] unix_path set to the UNIX path, or NULL for none
+ * @param[out] unix_mode set to the mode to be used for @a unix_path
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_config (const struct GNUNET_CONFIGURATION_Handle *cfg,
+                        const char *section,
+                        uint16_t *rport,
+                        char **unix_path,
+                        mode_t *unix_mode)
+{
+  const char *choices[] = {
+    "tcp",
+    "unix",
+    NULL
+  };
+  const char *serve_type;
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_choice (cfg,
+                                             section,
+                                             "SERVE",
+                                             choices,
+                                             &serve_type))
+  {
+    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                               section,
+                               "SERVE",
+                               "serve type (tcp or unix) required");
+    return GNUNET_SYSERR;
+  }
+
+  if (0 == strcasecmp (serve_type,
+                       "tcp"))
+  {
+    unsigned long long port;
+
+    if (GNUNET_OK !=
+        GNUNET_CONFIGURATION_get_value_number (cfg,
+                                               section,
+                                               "PORT",
+                                               &port))
+    {
+      GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                                 section,
+                                 "PORT",
+                                 "port number required");
+      return GNUNET_SYSERR;
+    }
+
+    if ( (0 == port) ||
+         (port > UINT16_MAX) )
+    {
+      GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                                 section,
+                                 "PORT",
+                                 "port number not in [1,65535]");
+      return GNUNET_SYSERR;
+    }
+    *rport = (uint16_t) port;
+    *unix_path = NULL;
+    return GNUNET_OK;
+  }
+  if (0 == strcmp (serve_type,
+                   "unix"))
+  {
+    struct sockaddr_un s_un;
+    char *modestring;
+
+    if (GNUNET_OK !=
+        GNUNET_CONFIGURATION_get_value_filename (cfg,
+                                                 section,
+                                                 "UNIXPATH",
+                                                 unix_path))
+    {
+      GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                                 section,
+                                 "UNIXPATH",
+                                 "UNIXPATH value required");
+      return GNUNET_SYSERR;
+    }
+    if (strlen (*unix_path) >= sizeof (s_un.sun_path))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "unixpath `%s' is too long\n",
+                  *unix_path);
+      GNUNET_free (*unix_path);
+      return GNUNET_SYSERR;
+    }
+
+    if (GNUNET_OK !=
+        GNUNET_CONFIGURATION_get_value_string (cfg,
+                                               section,
+                                               "UNIXPATH_MODE",
+                                               &modestring))
+    {
+      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                                 section,
+                                 "UNIXPATH_MODE");
+      GNUNET_free (*unix_path);
+      return GNUNET_SYSERR;
+    }
+    errno = 0;
+    *unix_mode = (mode_t) strtoul (modestring, NULL, 8);
+    if (0 != errno)
+    {
+      GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                                 section,
+                                 "UNIXPATH_MODE",
+                                 "must be octal number");
+      GNUNET_free (modestring);
+      GNUNET_free (*unix_path);
+      return GNUNET_SYSERR;
+    }
+    GNUNET_free (modestring);
+    return GNUNET_OK;
+  }
+  /* not reached */
+  GNUNET_assert (0);
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Function called for logging by MHD.
+ *
+ * @param cls closure, NULL
+ * @param fm format string (`printf()`-style)
+ * @param ap arguments to @a fm
+ */
+void
+TALER_MHD_handle_logs (void *cls,
+                       const char *fm,
+                       va_list ap)
+{
+  static int cache;
+  char buf[2048];
+
+  (void) cls;
+  if (-1 == cache)
+    return;
+  if (0 == cache)
+  {
+    if (0 ==
+        GNUNET_get_log_call_status (GNUNET_ERROR_TYPE_INFO,
+                                    "libmicrohttpd",
+                                    __FILE__,
+                                    __FUNCTION__,
+                                    __LINE__))
+    {
+      cache = -1;
+      return;
+    }
+  }
+  cache = 1;
+  vsnprintf (buf,
+             sizeof (buf),
+             fm,
+             ap);
+  GNUNET_log_from_nocheck (GNUNET_ERROR_TYPE_INFO,
+                           "libmicrohttpd",
+                           "%s",
+                           buf);
+}
+
+
+/**
+ * Open UNIX domain socket for listining at @a unix_path with
+ * permissions @a unix_mode.
+ *
+ * @param unix_path where to listen
+ * @param unix_mode access permissions to set
+ * @return -1 on error, otherwise the listen socket
+ */
+int
+TALER_MHD_open_unix_path (const char *unix_path,
+                          mode_t unix_mode)
+{
+  struct GNUNET_NETWORK_Handle *nh;
+  struct sockaddr_un *un;
+
+  if (sizeof (un->sun_path) <= strlen (unix_path))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "unixpath `%s' is too long\n",
+                unix_path);
+    return -1;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Creating listen socket '%s' with mode %o\n",
+              unix_path,
+              unix_mode);
+
+  if (GNUNET_OK !=
+      GNUNET_DISK_directory_create_for_file (unix_path))
+  {
+    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+                              "mkdir",
+                              unix_path);
+  }
+
+  un = GNUNET_new (struct sockaddr_un);
+  un->sun_family = AF_UNIX;
+  strncpy (un->sun_path,
+           unix_path,
+           sizeof (un->sun_path) - 1);
+  GNUNET_NETWORK_unix_precheck (un);
+
+  if (NULL == (nh = GNUNET_NETWORK_socket_create (AF_UNIX,
+                                                  SOCK_STREAM,
+                                                  0)))
+  {
+    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+                         "socket");
+    GNUNET_free (un);
+    return -1;
+  }
+
+  if (GNUNET_OK !=
+      GNUNET_NETWORK_socket_bind (nh,
+                                  (void *) un,
+                                  sizeof (struct sockaddr_un)))
+  {
+    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+                              "bind",
+                              unix_path);
+    GNUNET_free (un);
+    GNUNET_NETWORK_socket_close (nh);
+    return -1;
+  }
+  GNUNET_free (un);
+  if (GNUNET_OK !=
+      GNUNET_NETWORK_socket_listen (nh,
+                                    UNIX_BACKLOG))
+  {
+    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+                         "listen");
+    GNUNET_NETWORK_socket_close (nh);
+    return -1;
+  }
+
+  if (0 != chmod (unix_path,
+                  unix_mode))
+  {
+    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+                         "chmod");
+    GNUNET_NETWORK_socket_close (nh);
+    return -1;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "set socket '%s' to mode %o\n",
+              unix_path,
+              unix_mode);
+
+  /* extract and return actual socket handle from 'nh' */
+  {
+    int fd;
+
+    fd = GNUNET_NETWORK_get_fd (nh);
+    GNUNET_NETWORK_socket_free_memory_only_ (nh);
+    return fd;
+  }
+}
+
+
+/**
+ * Bind a listen socket to the UNIX domain path or the TCP port and IP address
+ * as specified in @a cfg in section @a section.  IF only a port was
+ * specified, set @a port and return -1.  Otherwise, return the bound file
+ * descriptor.
+ *
+ * @param cfg configuration to parse
+ * @param section configuration section to use
+ * @param[out] port port to set, if TCP without BINDTO
+ * @return -1 and a port of zero on error, otherwise
+ *    either -1 and a port, or a bound stream socket
+ */
+int
+TALER_MHD_bind (const struct GNUNET_CONFIGURATION_Handle *cfg,
+                const char *section,
+                uint16_t *port)
+{
+  char *bind_to;
+  struct GNUNET_NETWORK_Handle *nh;
+
+  /* try systemd passing first */
+  {
+    const char *listen_pid;
+    const char *listen_fds;
+
+    /* check for systemd-style FD passing */
+    listen_pid = getenv ("LISTEN_PID");
+    listen_fds = getenv ("LISTEN_FDS");
+    if ( (NULL != listen_pid) &&
+         (NULL != listen_fds) &&
+         (getpid () == strtol (listen_pid,
+                               NULL,
+                               10)) &&
+         (1 == strtoul (listen_fds,
+                        NULL,
+                        10)) )
+    {
+      int fh;
+      int flags;
+
+      fh = 3;
+      flags = fcntl (fh,
+                     F_GETFD);
+      if ( (-1 == flags) &&
+           (EBADF == errno) )
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Bad listen socket passed, ignored\n");
+        fh = -1;
+      }
+      flags |= FD_CLOEXEC;
+      if ( (-1 != fh) &&
+           (0 != fcntl (fh,
+                        F_SETFD,
+                        flags)) )
+        GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+                             "fcntl");
+      if (-1 != fh)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                    "Successfully obtained listen socket from hypervisor\n");
+        return fh;
+      }
+    }
+  }
+
+  /* now try configuration file */
+  *port = 0;
+  {
+    char *serve_unixpath;
+    mode_t unixpath_mode;
+
+    if (GNUNET_OK !=
+        TALER_MHD_parse_config (cfg,
+                                section,
+                                port,
+                                &serve_unixpath,
+                                &unixpath_mode))
+      return -1;
+    if (NULL != serve_unixpath)
+    {
+      int ret;
+
+      ret = TALER_MHD_open_unix_path (serve_unixpath,
+                                      unixpath_mode);
+      GNUNET_free (serve_unixpath);
+      return ret;
+    }
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (cfg,
+                                             section,
+                                             "BIND_TO",
+                                             &bind_to))
+    return -1; /* only set port */
+
+  /* let's have fun binding... */
+  {
+    char port_str[6];
+    struct addrinfo hints;
+    struct addrinfo *res;
+    int ec;
+
+    GNUNET_snprintf (port_str,
+                     sizeof (port_str),
+                     "%u",
+                     (unsigned int) *port);
+    *port = 0; /* do NOT return port in case of errors */
+    memset (&hints,
+            0,
+            sizeof (hints));
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_protocol = IPPROTO_TCP;
+    hints.ai_flags = AI_PASSIVE
+#ifdef AI_IDN
+                     | AI_IDN
+#endif
+    ;
+
+    if (0 !=
+        (ec = getaddrinfo (bind_to,
+                           port_str,
+                           &hints,
+                           &res)))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Failed to resolve BIND_TO address `%s': %s\n",
+                  bind_to,
+                  gai_strerror (ec));
+      GNUNET_free (bind_to);
+      return -1;
+    }
+    GNUNET_free (bind_to);
+
+    if (NULL == (nh = GNUNET_NETWORK_socket_create (res->ai_family,
+                                                    res->ai_socktype,
+                                                    res->ai_protocol)))
+    {
+      GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+                           "socket");
+      freeaddrinfo (res);
+      return -1;
+    }
+    {
+      const int on = 1;
+
+      if (GNUNET_OK !=
+          GNUNET_NETWORK_socket_setsockopt (nh,
+                                            SOL_SOCKET,
+                                            SO_REUSEPORT,
+                                            &on,
+                                            sizeof(on)))
+        GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+                             "setsockopt");
+    }
+    if (GNUNET_OK !=
+        GNUNET_NETWORK_socket_bind (nh,
+                                    res->ai_addr,
+                                    res->ai_addrlen))
+    {
+      GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+                           "bind");
+      freeaddrinfo (res);
+      return -1;
+    }
+    freeaddrinfo (res);
+  }
+
+  if (GNUNET_OK !=
+      GNUNET_NETWORK_socket_listen (nh,
+                                    UNIX_BACKLOG))
+  {
+    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+                         "listen");
+    GNUNET_SCHEDULER_shutdown ();
+    return -1;
+  }
+
+  /* extract and return actual socket handle from 'nh' */
+  {
+    int fh;
+
+    fh = GNUNET_NETWORK_get_fd (nh);
+    GNUNET_NETWORK_socket_free_memory_only_ (nh);
+    return fh;
+  }
+}
diff --git a/src/mhd/mhd_legal.c b/src/mhd/mhd_legal.c
new file mode 100644
index 0000000..137cc43
--- /dev/null
+++ b/src/mhd/mhd_legal.c
@@ -0,0 +1,694 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2019, 2020, 2022 Taler Systems SA
+
+  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, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file mhd_legal.c
+ * @brief API for returning legal documents based on client language
+ *        and content type preferences
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler_util.h"
+#include "taler_mhd_lib.h"
+
+/**
+ * How long should browsers/proxies cache the "legal" replies?
+ */
+#define MAX_TERMS_CACHING GNUNET_TIME_UNIT_DAYS
+
+
+/**
+ * Entry in the terms-of-service array.
+ */
+struct Terms
+{
+  /**
+   * Kept in a DLL.
+   */
+  struct Terms *prev;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct Terms *next;
+
+  /**
+   * Mime type of the terms.
+   */
+  const char *mime_type;
+
+  /**
+   * The terms (NOT 0-terminated!), mmap()'ed. Do not free,
+   * use munmap() instead.
+   */
+  void *terms;
+
+  /**
+   * The desired language.
+   */
+  char *language;
+
+  /**
+   * deflated @e terms, to return if client supports deflate compression.
+   * malloc()'ed.  NULL if @e terms does not compress.
+   */
+  void *compressed_terms;
+
+  /**
+   * Number of bytes in @e terms.
+   */
+  size_t terms_size;
+
+  /**
+   * Number of bytes in @e compressed_terms.
+   */
+  size_t compressed_terms_size;
+
+  /**
+   * Sorting key by format preference in case
+   * everything else is equal. Higher is preferred.
+   */
+  unsigned int priority;
+
+};
+
+
+/**
+ * Prepared responses for legal documents
+ * (terms of service, privacy policy).
+ */
+struct TALER_MHD_Legal
+{
+  /**
+   * DLL of terms of service.
+   */
+  struct Terms *terms_head;
+
+  /**
+   * DLL of terms of service.
+   */
+  struct Terms *terms_tail;
+
+  /**
+   * Etag to use for the terms of service (= version).
+   */
+  char *terms_etag;
+};
+
+
+/**
+ * Check if @a mime matches the @a accept_pattern.
+ *
+ * @param accept_pattern a mime pattern like "text/plain"
+ *        or "image/STAR"
+ * @param mime the mime type to match
+ * @return true if @a mime matches the @a accept_pattern
+ */
+static bool
+mime_matches (const char *accept_pattern,
+              const char *mime)
+{
+  const char *da = strchr (accept_pattern, '/');
+  const char *dm = strchr (mime, '/');
+  const char *end;
+
+  if ( (NULL == da) ||
+       (NULL == dm) )
+    return (0 == strcmp ("*", accept_pattern));
+  /* FIXME: eventually, we might want to parse the "q=$FLOAT"
+     part after the ';' and figure out which one is the
+     best/preferred match instead of returning a boolean... */
+  end = strchr (da, ';');
+  if (NULL == end)
+    end = &da[strlen (da)];
+  return
+    ( ( (1 == da - accept_pattern) &&
+        ('*' == *accept_pattern) ) ||
+      ( (da - accept_pattern == dm - mime) &&
+        (0 == strncasecmp (accept_pattern,
+                           mime,
+                           da - accept_pattern)) ) ) &&
+    ( (0 == strcmp (da, "/*")) ||
+      (0 == strncasecmp (da,
+                         dm,
+                         end - da)) );
+}
+
+
+bool
+TALER_MHD_xmime_matches (const char *accept_pattern,
+                         const char *mime)
+{
+  char *ap = GNUNET_strdup (accept_pattern);
+  char *sptr;
+
+  for (const char *tok = strtok_r (ap, ",", &sptr);
+       NULL != tok;
+       tok = strtok_r (NULL, ",", &sptr))
+  {
+    if (mime_matches (tok,
+                      mime))
+    {
+      GNUNET_free (ap);
+      return true;
+    }
+  }
+  GNUNET_free (ap);
+  return false;
+}
+
+
+MHD_RESULT
+TALER_MHD_reply_legal (struct MHD_Connection *conn,
+                       struct TALER_MHD_Legal *legal)
+{
+  struct MHD_Response *resp;
+  struct Terms *t;
+  struct GNUNET_TIME_Absolute a;
+  struct GNUNET_TIME_Timestamp m;
+  char dat[128];
+  char *langs;
+
+  a = GNUNET_TIME_relative_to_absolute (MAX_TERMS_CACHING);
+  m = GNUNET_TIME_absolute_to_timestamp (a);
+  TALER_MHD_get_date_string (m.abs_time,
+                             dat);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Setting '%s' header to '%s'\n",
+              MHD_HTTP_HEADER_EXPIRES,
+              dat);
+  if (NULL != legal)
+  {
+    const char *etag;
+
+    etag = MHD_lookup_connection_value (conn,
+                                        MHD_HEADER_KIND,
+                                        MHD_HTTP_HEADER_IF_NONE_MATCH);
+    if ( (NULL != etag) &&
+         (NULL != legal->terms_etag) &&
+         (0 == strcasecmp (etag,
+                           legal->terms_etag)) )
+    {
+      MHD_RESULT ret;
+
+      resp = MHD_create_response_from_buffer (0,
+                                              NULL,
+                                              MHD_RESPMEM_PERSISTENT);
+      TALER_MHD_add_global_headers (resp);
+      GNUNET_break (MHD_YES ==
+                    MHD_add_response_header (resp,
+                                             MHD_HTTP_HEADER_EXPIRES,
+                                             dat));
+
+      GNUNET_break (MHD_YES ==
+                    MHD_add_response_header (resp,
+                                             MHD_HTTP_HEADER_ETAG,
+                                             legal->terms_etag));
+      ret = MHD_queue_response (conn,
+                                MHD_HTTP_NOT_MODIFIED,
+                                resp);
+      GNUNET_break (MHD_YES == ret);
+      MHD_destroy_response (resp);
+      return ret;
+    }
+  }
+
+  t = NULL;
+  langs = NULL;
+  if (NULL != legal)
+  {
+    const char *mime;
+    const char *lang;
+
+    mime = MHD_lookup_connection_value (conn,
+                                        MHD_HEADER_KIND,
+                                        MHD_HTTP_HEADER_ACCEPT);
+    if (NULL == mime)
+      mime = "text/plain";
+    lang = MHD_lookup_connection_value (conn,
+                                        MHD_HEADER_KIND,
+                                        MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
+    if (NULL == lang)
+      lang = "en";
+    /* Find best match: must match mime type (if possible), and if
+       mime type matches, ideally also language */
+    for (struct Terms *p = legal->terms_head;
+         NULL != p;
+         p = p->next)
+    {
+      if ( (NULL == t) ||
+           (TALER_MHD_xmime_matches (mime,
+                                     p->mime_type)) )
+      {
+        if (NULL == langs)
+        {
+          langs = GNUNET_strdup (p->language);
+        }
+        else
+        {
+          char *tmp = langs;
+
+          GNUNET_asprintf (&langs,
+                           "%s,%s",
+                           tmp,
+                           p->language);
+          GNUNET_free (tmp);
+        }
+        if ( (NULL == t) ||
+             (! TALER_MHD_xmime_matches (mime,
+                                         t->mime_type)) ||
+             (TALER_language_matches (lang,
+                                      p->language) >
+              TALER_language_matches (lang,
+                                      t->language) ) )
+          t = p;
+      }
+    }
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Best match for %s/%s: %s / %s\n",
+                lang,
+                mime,
+                (NULL != t) ? t->mime_type : "<none>",
+                (NULL != t) ? t->language : "<none>");
+  }
+
+  if (NULL == t)
+  {
+    /* Default terms of service if none are configured */
+    static struct Terms none = {
+      .mime_type = "text/plain",
+      .terms = "not configured",
+      .language = "en",
+      .terms_size = strlen ("not configured")
+    };
+
+    t = &none;
+  }
+
+  /* try to compress the response */
+  resp = NULL;
+  if (MHD_YES ==
+      TALER_MHD_can_compress (conn))
+  {
+    resp = MHD_create_response_from_buffer (t->compressed_terms_size,
+                                            t->compressed_terms,
+                                            MHD_RESPMEM_PERSISTENT);
+    if (MHD_NO ==
+        MHD_add_response_header (resp,
+                                 MHD_HTTP_HEADER_CONTENT_ENCODING,
+                                 "deflate"))
+    {
+      GNUNET_break (0);
+      MHD_destroy_response (resp);
+      resp = NULL;
+    }
+  }
+  if (NULL == resp)
+  {
+    /* could not generate compressed response, return uncompressed */
+    resp = MHD_create_response_from_buffer (t->terms_size,
+                                            (void *) t->terms,
+                                            MHD_RESPMEM_PERSISTENT);
+  }
+  TALER_MHD_add_global_headers (resp);
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (resp,
+                                         MHD_HTTP_HEADER_EXPIRES,
+                                         dat));
+  if (NULL != langs)
+  {
+    GNUNET_break (MHD_YES ==
+                  MHD_add_response_header (resp,
+                                           "Avail-Languages",
+                                           langs));
+    GNUNET_free (langs);
+  }
+  /* Set cache control headers: our response varies depending on these headers 
*/
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (resp,
+                                         MHD_HTTP_HEADER_VARY,
+                                         MHD_HTTP_HEADER_ACCEPT_LANGUAGE ","
+                                         MHD_HTTP_HEADER_ACCEPT ","
+                                         MHD_HTTP_HEADER_ACCEPT_ENCODING));
+  /* Information is always public, revalidate after 10 days */
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (resp,
+                                         MHD_HTTP_HEADER_CACHE_CONTROL,
+                                         "public max-age=864000"));
+  if (NULL != legal)
+    GNUNET_break (MHD_YES ==
+                  MHD_add_response_header (resp,
+                                           MHD_HTTP_HEADER_ETAG,
+                                           legal->terms_etag));
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (resp,
+                                         MHD_HTTP_HEADER_CONTENT_TYPE,
+                                         t->mime_type));
+  {
+    MHD_RESULT ret;
+
+    ret = MHD_queue_response (conn,
+                              MHD_HTTP_OK,
+                              resp);
+    MHD_destroy_response (resp);
+    return ret;
+  }
+}
+
+
+/**
+ * Load all the terms of service from @a path under language @a lang
+ * from file @a name
+ *
+ * @param[in,out] legal where to write the result
+ * @param path where the terms are found
+ * @param lang which language directory to crawl
+ * @param name specific file to access
+ */
+static void
+load_terms (struct TALER_MHD_Legal *legal,
+            const char *path,
+            const char *lang,
+            const char *name)
+{
+  static struct MimeMap
+  {
+    const char *ext;
+    const char *mime;
+    unsigned int priority;
+  } mm[] = {
+    { .ext = ".txt", .mime = "text/plain", .priority = 150 },
+    { .ext = ".html", .mime = "text/html", .priority = 100 },
+    { .ext = ".htm", .mime = "text/html", .priority = 99 },
+    { .ext = ".md", .mime = "text/markdown", .priority = 50 },
+    { .ext = ".pdf", .mime = "application/pdf", .priority = 25 },
+    { .ext = ".jpg", .mime = "image/jpeg" },
+    { .ext = ".jpeg", .mime = "image/jpeg" },
+    { .ext = ".png", .mime = "image/png" },
+    { .ext = ".gif", .mime = "image/gif" },
+    { .ext = ".epub", .mime = "application/epub+zip", .priority = 10 },
+    { .ext = ".xml", .mime = "text/xml", .priority = 10 },
+    { .ext = NULL, .mime = NULL }
+  };
+  const char *ext = strrchr (name, '.');
+  const char *mime;
+  unsigned int priority;
+
+  if (NULL == ext)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unsupported file `%s' in directory `%s/%s': lacks 
extension\n",
+                name,
+                path,
+                lang);
+    return;
+  }
+  if ( (NULL == legal->terms_etag) ||
+       (0 != strncmp (legal->terms_etag,
+                      name,
+                      ext - name - 1)) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Filename `%s' does not match Etag `%s' in directory `%s/%s'. 
Ignoring it.\n",
+                name,
+                legal->terms_etag,
+                path,
+                lang);
+    return;
+  }
+  mime = NULL;
+  for (unsigned int i = 0; NULL != mm[i].ext; i++)
+    if (0 == strcasecmp (mm[i].ext,
+                         ext))
+    {
+      mime = mm[i].mime;
+      priority = mm[i].priority;
+      break;
+    }
+  if (NULL == mime)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Unsupported file extension `%s' of file `%s' in directory 
`%s/%s'\n",
+                ext,
+                name,
+                path,
+                lang);
+    return;
+  }
+  /* try to read the file with the terms of service */
+  {
+    struct stat st;
+    char *fn;
+    int fd;
+
+    GNUNET_asprintf (&fn,
+                     "%s/%s/%s",
+                     path,
+                     lang,
+                     name);
+    fd = open (fn, O_RDONLY);
+    if (-1 == fd)
+    {
+      GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+                                "open",
+                                fn);
+      GNUNET_free (fn);
+      return;
+    }
+    if (0 != fstat (fd, &st))
+    {
+      GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+                                "fstat",
+                                fn);
+      GNUNET_break (0 == close (fd));
+      GNUNET_free (fn);
+      return;
+    }
+    if (SIZE_MAX < ((unsigned long long) st.st_size))
+    {
+      GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+                                "fstat-size",
+                                fn);
+      GNUNET_break (0 == close (fd));
+      GNUNET_free (fn);
+      return;
+    }
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Loading legal information from file `%s'\n",
+                fn);
+    {
+      void *buf;
+      size_t bsize;
+
+      bsize = (size_t) st.st_size;
+      buf = mmap (NULL,
+                  bsize,
+                  PROT_READ,
+                  MAP_SHARED,
+                  fd,
+                  0);
+      if (MAP_FAILED == buf)
+      {
+        GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
+                                  "mmap",
+                                  fn);
+        GNUNET_break (0 == close (fd));
+        GNUNET_free (fn);
+        return;
+      }
+      GNUNET_break (0 == close (fd));
+      GNUNET_free (fn);
+
+      /* insert into global list of terms of service */
+      {
+        struct Terms *t;
+
+        t = GNUNET_new (struct Terms);
+        t->mime_type = mime;
+        t->terms = buf;
+        t->language = GNUNET_strdup (lang);
+        t->terms_size = bsize;
+        t->priority = priority;
+        buf = GNUNET_memdup (t->terms,
+                             t->terms_size);
+        if (TALER_MHD_body_compress (&buf,
+                                     &bsize))
+        {
+          t->compressed_terms = buf;
+          t->compressed_terms_size = bsize;
+        }
+        else
+        {
+          GNUNET_free (buf);
+        }
+        {
+          struct Terms *prev = NULL;
+
+          for (struct Terms *pos = legal->terms_head;
+               NULL != pos;
+               pos = pos->next)
+          {
+            if (pos->priority < priority)
+              break;
+            prev = pos;
+          }
+          GNUNET_CONTAINER_DLL_insert_after (legal->terms_head,
+                                             legal->terms_tail,
+                                             prev,
+                                             t);
+        }
+      }
+    }
+  }
+}
+
+
+/**
+ * Load all the terms of service from @a path under language @a lang.
+ *
+ * @param[in,out] legal where to write the result
+ * @param path where the terms are found
+ * @param lang which language directory to crawl
+ */
+static void
+load_language (struct TALER_MHD_Legal *legal,
+               const char *path,
+               const char *lang)
+{
+  char *dname;
+  DIR *d;
+
+  GNUNET_asprintf (&dname,
+                   "%s/%s",
+                   path,
+                   lang);
+  d = opendir (dname);
+  if (NULL == d)
+  {
+    GNUNET_free (dname);
+    return;
+  }
+  for (struct dirent *de = readdir (d);
+       NULL != de;
+       de = readdir (d))
+  {
+    const char *fn = de->d_name;
+
+    if (fn[0] == '.')
+      continue;
+    load_terms (legal,
+                path,
+                lang,
+                fn);
+  }
+  GNUNET_break (0 == closedir (d));
+  GNUNET_free (dname);
+}
+
+
+struct TALER_MHD_Legal *
+TALER_MHD_legal_load (const struct GNUNET_CONFIGURATION_Handle *cfg,
+                      const char *section,
+                      const char *diroption,
+                      const char *tagoption)
+{
+  struct TALER_MHD_Legal *legal;
+  char *path;
+  DIR *d;
+
+  legal = GNUNET_new (struct TALER_MHD_Legal);
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (cfg,
+                                             section,
+                                             tagoption,
+                                             &legal->terms_etag))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+                               section,
+                               tagoption);
+    GNUNET_free (legal);
+    return NULL;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (cfg,
+                                               section,
+                                               diroption,
+                                               &path))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+                               section,
+                               diroption);
+    GNUNET_free (legal->terms_etag);
+    GNUNET_free (legal);
+    return NULL;
+  }
+  d = opendir (path);
+  if (NULL == d)
+  {
+    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING,
+                               section,
+                               diroption,
+                               "Could not open directory");
+    GNUNET_free (legal->terms_etag);
+    GNUNET_free (legal);
+    GNUNET_free (path);
+    return NULL;
+  }
+  for (struct dirent *de = readdir (d);
+       NULL != de;
+       de = readdir (d))
+  {
+    const char *lang = de->d_name;
+
+    if (lang[0] == '.')
+      continue;
+    if (0 == strcmp (lang,
+                     "locale"))
+      continue;
+    load_language (legal,
+                   path,
+                   lang);
+  }
+  GNUNET_break (0 == closedir (d));
+  GNUNET_free (path);
+  return legal;
+}
+
+
+void
+TALER_MHD_legal_free (struct TALER_MHD_Legal *legal)
+{
+  struct Terms *t;
+  if (NULL == legal)
+    return;
+  while (NULL != (t = legal->terms_head))
+  {
+    GNUNET_free (t->language);
+    GNUNET_free (t->compressed_terms);
+    if (0 != munmap (t->terms, t->terms_size))
+      GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+                           "munmap");
+    GNUNET_CONTAINER_DLL_remove (legal->terms_head,
+                                 legal->terms_tail,
+                                 t);
+    GNUNET_free (t);
+  }
+  GNUNET_free (legal->terms_etag);
+  GNUNET_free (legal);
+}
diff --git a/src/mhd/mhd_parsing.c b/src/mhd/mhd_parsing.c
new file mode 100644
index 0000000..381b064
--- /dev/null
+++ b/src/mhd/mhd_parsing.c
@@ -0,0 +1,444 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014--2020 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU 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, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file mhd_parsing.c
+ * @brief functions to parse incoming requests (MHD arguments and JSON 
snippets)
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+
+
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_post_json (struct MHD_Connection *connection,
+                           void **con_cls,
+                           const char *upload_data,
+                           size_t *upload_data_size,
+                           json_t **json)
+{
+  enum GNUNET_JSON_PostResult pr;
+
+  pr = GNUNET_JSON_post_parser (TALER_MHD_REQUEST_BUFFER_MAX,
+                                connection,
+                                con_cls,
+                                upload_data,
+                                upload_data_size,
+                                json);
+  switch (pr)
+  {
+  case GNUNET_JSON_PR_OUT_OF_MEMORY:
+    GNUNET_break (NULL == *json);
+    return (MHD_NO ==
+            TALER_MHD_reply_with_error (
+              connection,
+              MHD_HTTP_INTERNAL_SERVER_ERROR,
+              TALER_EC_GENERIC_PARSER_OUT_OF_MEMORY,
+              NULL)) ? GNUNET_SYSERR : GNUNET_NO;
+
+  case GNUNET_JSON_PR_CONTINUE:
+    GNUNET_break (NULL == *json);
+    return GNUNET_YES;
+  case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
+    GNUNET_break (NULL == *json);
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Closing connection, upload too large\n");
+    return GNUNET_SYSERR;
+  case GNUNET_JSON_PR_JSON_INVALID:
+    GNUNET_break (NULL == *json);
+    return (MHD_YES ==
+            TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_BAD_REQUEST,
+                                        TALER_EC_GENERIC_JSON_INVALID,
+                                        NULL))
+           ? GNUNET_NO : GNUNET_SYSERR;
+  case GNUNET_JSON_PR_SUCCESS:
+    GNUNET_break (NULL != *json);
+    return GNUNET_YES;
+  }
+  /* this should never happen */
+  GNUNET_break (0);
+  return GNUNET_SYSERR;
+}
+
+
+void
+TALER_MHD_parse_post_cleanup_callback (void *con_cls)
+{
+  GNUNET_JSON_post_parser_cleanup (con_cls);
+}
+
+
+/**
+ * Extract fixed-size 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 HTTP key with the value
+ * @param kind whether to extract from header, argument or footer
+ * @param[out] out_data pointer to store the result
+ * @param out_size expected size of @a out_data
+ * @param[out] present set to true if argument was found
+ * @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)
+ */
+static enum GNUNET_GenericReturnValue
+parse_request_data (struct MHD_Connection *connection,
+                    const char *param_name,
+                    enum MHD_ValueKind kind,
+                    void *out_data,
+                    size_t out_size,
+                    bool *present)
+{
+  const char *str;
+
+  str = MHD_lookup_connection_value (connection,
+                                     kind,
+                                     param_name);
+  if (NULL == str)
+  {
+    *present = false;
+    return GNUNET_OK;
+  }
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (str,
+                                     strlen (str),
+                                     out_data,
+                                     out_size))
+    return (MHD_NO ==
+            TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_BAD_REQUEST,
+                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                        param_name))
+           ? GNUNET_SYSERR : GNUNET_NO;
+  *present = true;
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_request_arg_data (struct MHD_Connection *connection,
+                                  const char *param_name,
+                                  void *out_data,
+                                  size_t out_size,
+                                  bool *present)
+{
+  return parse_request_data (connection,
+                             param_name,
+                             MHD_GET_ARGUMENT_KIND,
+                             out_data,
+                             out_size,
+                             present);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_request_header_data (struct MHD_Connection *connection,
+                                     const char *header_name,
+                                     void *out_data,
+                                     size_t out_size,
+                                     bool *present)
+{
+  return parse_request_data (connection,
+                             header_name,
+                             MHD_HEADER_KIND,
+                             out_data,
+                             out_size,
+                             present);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_request_arg_timeout (struct MHD_Connection *connection,
+                                     struct GNUNET_TIME_Absolute *expiration)
+{
+  const char *ts;
+  char dummy;
+  unsigned long long tms;
+
+  ts = MHD_lookup_connection_value (connection,
+                                    MHD_GET_ARGUMENT_KIND,
+                                    "timeout_ms");
+  if (NULL == ts)
+  {
+    *expiration = GNUNET_TIME_UNIT_ZERO_ABS;
+    return GNUNET_OK;
+  }
+  if (1 !=
+      sscanf (ts,
+              "%llu%c",
+              &tms,
+              &dummy))
+  {
+    MHD_RESULT mret;
+
+    GNUNET_break_op (0);
+    mret = TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                       "timeout_ms");
+    return (MHD_YES == mret)
+      ? GNUNET_NO
+      : GNUNET_SYSERR;
+  }
+  *expiration = GNUNET_TIME_relative_to_absolute (
+    GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
+                                   tms));
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_request_arg_number (struct MHD_Connection *connection,
+                                    const char *name,
+                                    uint64_t *off)
+{
+  const char *ts;
+  char dummy;
+  unsigned long long num;
+
+  ts = MHD_lookup_connection_value (connection,
+                                    MHD_GET_ARGUMENT_KIND,
+                                    name);
+  if (NULL == ts)
+    return GNUNET_OK;
+  if (1 !=
+      sscanf (ts,
+              "%llu%c",
+              &num,
+              &dummy))
+  {
+    MHD_RESULT mret;
+
+    GNUNET_break_op (0);
+    mret = TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                       name);
+    return (MHD_YES == mret)
+      ? GNUNET_NO
+      : GNUNET_SYSERR;
+  }
+  *off = (uint64_t) num;
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_json_data (struct MHD_Connection *connection,
+                           const json_t *root,
+                           struct GNUNET_JSON_Specification *spec)
+{
+  enum GNUNET_GenericReturnValue ret;
+  const char *error_json_name;
+  unsigned int error_line;
+
+  ret = GNUNET_JSON_parse (root,
+                           spec,
+                           &error_json_name,
+                           &error_line);
+  if (GNUNET_SYSERR == ret)
+  {
+    if (NULL == error_json_name)
+      error_json_name = "<no field>";
+    ret = (MHD_YES ==
+           TALER_MHD_REPLY_JSON_PACK (
+             connection,
+             MHD_HTTP_BAD_REQUEST,
+             GNUNET_JSON_pack_string ("hint",
+                                      TALER_ErrorCode_get_hint (
+                                        TALER_EC_GENERIC_JSON_INVALID)),
+             GNUNET_JSON_pack_uint64 ("code",
+                                      TALER_EC_GENERIC_JSON_INVALID),
+             GNUNET_JSON_pack_string ("field",
+                                      error_json_name),
+             GNUNET_JSON_pack_uint64 ("line",
+                                      error_line)))
+          ? GNUNET_NO : GNUNET_SYSERR;
+    return ret;
+  }
+  return GNUNET_YES;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_internal_json_data (struct MHD_Connection *connection,
+                                    const json_t *root,
+                                    struct GNUNET_JSON_Specification *spec)
+{
+  enum GNUNET_GenericReturnValue ret;
+  const char *error_json_name;
+  unsigned int error_line;
+
+  ret = GNUNET_JSON_parse (root,
+                           spec,
+                           &error_json_name,
+                           &error_line);
+  if (GNUNET_SYSERR == ret)
+  {
+    if (NULL == error_json_name)
+      error_json_name = "<no field>";
+    ret = (MHD_YES ==
+           TALER_MHD_REPLY_JSON_PACK (
+             connection,
+             MHD_HTTP_INTERNAL_SERVER_ERROR,
+             GNUNET_JSON_pack_string ("hint",
+                                      TALER_ErrorCode_get_hint (
+                                        
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE)),
+             GNUNET_JSON_pack_uint64 ("code",
+                                      
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE),
+             GNUNET_JSON_pack_string ("field",
+                                      error_json_name),
+             GNUNET_JSON_pack_uint64 ("line",
+                                      error_line)))
+          ? GNUNET_NO : GNUNET_SYSERR;
+    return ret;
+  }
+  return GNUNET_YES;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_json_array (struct MHD_Connection *connection,
+                            const json_t *root,
+                            struct GNUNET_JSON_Specification *spec,
+                            ...)
+{
+  enum GNUNET_GenericReturnValue ret;
+  const char *error_json_name;
+  unsigned int error_line;
+  va_list ap;
+  json_int_t dim;
+
+  va_start (ap, spec);
+  dim = 0;
+  while ( (-1 != (ret = va_arg (ap, int))) &&
+          (NULL != root) )
+  {
+    dim++;
+    root = json_array_get (root, ret);
+  }
+  va_end (ap);
+  if (NULL == root)
+  {
+    ret = (MHD_YES ==
+           TALER_MHD_REPLY_JSON_PACK (
+             connection,
+             MHD_HTTP_BAD_REQUEST,
+             GNUNET_JSON_pack_string ("hint",
+                                      TALER_ErrorCode_get_hint (
+                                        TALER_EC_GENERIC_JSON_INVALID)),
+             GNUNET_JSON_pack_uint64 ("code",
+                                      TALER_EC_GENERIC_JSON_INVALID),
+             GNUNET_JSON_pack_string ("detail",
+                                      "expected array"),
+             GNUNET_JSON_pack_uint64 ("dimension",
+                                      dim)))
+          ? GNUNET_NO : GNUNET_SYSERR;
+    return ret;
+  }
+  ret = GNUNET_JSON_parse (root,
+                           spec,
+                           &error_json_name,
+                           &error_line);
+  if (GNUNET_SYSERR == ret)
+  {
+    if (NULL == error_json_name)
+      error_json_name = "<no field>";
+    ret = (MHD_YES ==
+           TALER_MHD_REPLY_JSON_PACK (
+             connection,
+             MHD_HTTP_BAD_REQUEST,
+             GNUNET_JSON_pack_string ("detail",
+                                      error_json_name),
+             GNUNET_JSON_pack_string ("hint",
+                                      TALER_ErrorCode_get_hint (
+                                        TALER_EC_GENERIC_JSON_INVALID)),
+             GNUNET_JSON_pack_uint64 ("code",
+                                      TALER_EC_GENERIC_JSON_INVALID),
+             GNUNET_JSON_pack_uint64 ("line",
+                                      error_line)))
+          ? GNUNET_NO : GNUNET_SYSERR;
+    return ret;
+  }
+  return GNUNET_YES;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_MHD_check_content_length_ (struct MHD_Connection *connection,
+                                 unsigned long long max_len)
+{
+  const char *cl;
+  unsigned long long cv;
+  char dummy;
+
+  /* Maybe check for maximum upload size
+       and refuse requests if they are just too big. */
+  cl = MHD_lookup_connection_value (connection,
+                                    MHD_HEADER_KIND,
+                                    MHD_HTTP_HEADER_CONTENT_LENGTH);
+  if (NULL == cl)
+  {
+    return GNUNET_OK;
+#if 0
+    /* wallet currently doesn't always send content-length! */
+    GNUNET_break_op (0);
+    return (MHD_YES ==
+            TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_BAD_REQUEST,
+                                        TALER_EC_GENERIC_PARAMETER_MISSING,
+                                        MHD_HTTP_HEADER_CONTENT_LENGTH))
+      ? GNUNET_NO
+      : GNUNET_SYSERR;
+#endif
+  }
+  if (1 != sscanf (cl,
+                   "%llu%c",
+                   &cv,
+                   &dummy))
+  {
+    /* Not valid HTTP request, just close connection. */
+    GNUNET_break_op (0);
+    return (MHD_YES ==
+            TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_BAD_REQUEST,
+                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                        MHD_HTTP_HEADER_CONTENT_LENGTH))
+      ? GNUNET_NO
+      : GNUNET_SYSERR;
+  }
+  if (cv > TALER_MHD_REQUEST_BUFFER_MAX)
+  {
+    GNUNET_break_op (0);
+    return (MHD_YES ==
+            TALER_MHD_reply_request_too_large (connection))
+    ? GNUNET_NO
+    : GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/* end of mhd_parsing.c */
diff --git a/src/mhd/mhd_responses.c b/src/mhd/mhd_responses.c
new file mode 100644
index 0000000..7dd6824
--- /dev/null
+++ b/src/mhd/mhd_responses.c
@@ -0,0 +1,550 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2021 Taler Systems SA
+
+  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, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file mhd_responses.c
+ * @brief API for generating HTTP replies
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <zlib.h>
+#include "taler_util.h"
+#include "taler_mhd_lib.h"
+
+
+/**
+ * Global options for response generation.
+ */
+static enum TALER_MHD_GlobalOptions TM_go;
+
+
+void
+TALER_MHD_setup (enum TALER_MHD_GlobalOptions go)
+{
+  TM_go = go;
+}
+
+
+void
+TALER_MHD_add_global_headers (struct MHD_Response *response)
+{
+  if (0 != (TM_go & TALER_MHD_GO_FORCE_CONNECTION_CLOSE))
+    GNUNET_break (MHD_YES ==
+                  MHD_add_response_header (response,
+                                           MHD_HTTP_HEADER_CONNECTION,
+                                           "close"));
+  /* The wallet, operating from a background page, needs CORS to
+     be disabled otherwise browsers block access. */
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (response,
+                                         
MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
+                                         "*"));
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (response,
+                                         /* Not available as MHD constant yet 
*/
+                                         "Access-Control-Expose-Headers",
+                                         "*"));
+}
+
+
+MHD_RESULT
+TALER_MHD_can_compress (struct MHD_Connection *connection)
+{
+  const char *ae;
+  const char *de;
+
+  if (0 != (TM_go & TALER_MHD_GO_DISABLE_COMPRESSION))
+    return MHD_NO;
+  ae = MHD_lookup_connection_value (connection,
+                                    MHD_HEADER_KIND,
+                                    MHD_HTTP_HEADER_ACCEPT_ENCODING);
+  if (NULL == ae)
+    return MHD_NO;
+  if (0 == strcmp (ae,
+                   "*"))
+    return MHD_YES;
+  de = strstr (ae,
+               "deflate");
+  if (NULL == de)
+    return MHD_NO;
+  if ( ( (de == ae) ||
+         (de[-1] == ',') ||
+         (de[-1] == ' ') ) &&
+       ( (de[strlen ("deflate")] == '\0') ||
+         (de[strlen ("deflate")] == ',') ||
+         (de[strlen ("deflate")] == ';') ) )
+    return MHD_YES;
+  return MHD_NO;
+}
+
+
+MHD_RESULT
+TALER_MHD_body_compress (void **buf,
+                         size_t *buf_size)
+{
+  Bytef *cbuf;
+  uLongf cbuf_size;
+  MHD_RESULT ret;
+
+  cbuf_size = compressBound (*buf_size);
+  cbuf = malloc (cbuf_size);
+  if (NULL == cbuf)
+    return MHD_NO;
+  ret = compress (cbuf,
+                  &cbuf_size,
+                  (const Bytef *) *buf,
+                  *buf_size);
+  if ( (Z_OK != ret) ||
+       (cbuf_size >= *buf_size) )
+  {
+    /* compression failed */
+    free (cbuf);
+    return MHD_NO;
+  }
+  free (*buf);
+  *buf = (void *) cbuf;
+  *buf_size = (size_t) cbuf_size;
+  return MHD_YES;
+}
+
+
+struct MHD_Response *
+TALER_MHD_make_json (const json_t *json)
+{
+  struct MHD_Response *response;
+  char *json_str;
+
+  json_str = json_dumps (json,
+                         JSON_INDENT (2));
+  if (NULL == json_str)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  response = MHD_create_response_from_buffer (strlen (json_str),
+                                              json_str,
+                                              MHD_RESPMEM_MUST_FREE);
+  if (NULL == response)
+  {
+    free (json_str);
+    GNUNET_break (0);
+    return NULL;
+  }
+  TALER_MHD_add_global_headers (response);
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (response,
+                                         MHD_HTTP_HEADER_CONTENT_TYPE,
+                                         "application/json"));
+  return response;
+}
+
+
+struct MHD_Response *
+TALER_MHD_make_json_steal (json_t *json)
+{
+  struct MHD_Response *res;
+
+  res = TALER_MHD_make_json (json);
+  json_decref (json);
+  return res;
+}
+
+
+MHD_RESULT
+TALER_MHD_reply_json (struct MHD_Connection *connection,
+                      const json_t *json,
+                      unsigned int response_code)
+{
+  struct MHD_Response *response;
+  void *json_str;
+  size_t json_len;
+  MHD_RESULT is_compressed;
+
+  json_str = json_dumps (json,
+                         JSON_INDENT (2));
+  if (NULL == json_str)
+  {
+    /**
+     * This log helps to figure out which
+     * function called this one and assert-failed.
+     */
+    TALER_LOG_ERROR ("Aborting json-packing for HTTP code: %u\n",
+                     response_code);
+
+    GNUNET_assert (0);
+    return MHD_NO;
+  }
+  json_len = strlen (json_str);
+  /* try to compress the body */
+  is_compressed = MHD_NO;
+  if (MHD_YES ==
+      TALER_MHD_can_compress (connection))
+    is_compressed = TALER_MHD_body_compress (&json_str,
+                                             &json_len);
+  response = MHD_create_response_from_buffer (json_len,
+                                              json_str,
+                                              MHD_RESPMEM_MUST_FREE);
+  if (NULL == response)
+  {
+    free (json_str);
+    GNUNET_break (0);
+    return MHD_NO;
+  }
+  TALER_MHD_add_global_headers (response);
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (response,
+                                         MHD_HTTP_HEADER_CONTENT_TYPE,
+                                         "application/json"));
+  if (MHD_YES == is_compressed)
+  {
+    /* Need to indicate to client that body is compressed */
+    if (MHD_NO ==
+        MHD_add_response_header (response,
+                                 MHD_HTTP_HEADER_CONTENT_ENCODING,
+                                 "deflate"))
+    {
+      GNUNET_break (0);
+      MHD_destroy_response (response);
+      return MHD_NO;
+    }
+  }
+
+  {
+    MHD_RESULT ret;
+
+    ret = MHD_queue_response (connection,
+                              response_code,
+                              response);
+    MHD_destroy_response (response);
+    return ret;
+  }
+}
+
+
+MHD_RESULT
+TALER_MHD_reply_json_steal (struct MHD_Connection *connection,
+                            json_t *json,
+                            unsigned int response_code)
+{
+  MHD_RESULT ret;
+
+  ret = TALER_MHD_reply_json (connection,
+                              json,
+                              response_code);
+  json_decref (json);
+  return ret;
+}
+
+
+MHD_RESULT
+TALER_MHD_reply_cors_preflight (struct MHD_Connection *connection)
+{
+  struct MHD_Response *response;
+
+  response = MHD_create_response_from_buffer (0,
+                                              NULL,
+                                              MHD_RESPMEM_PERSISTENT);
+  if (NULL == response)
+    return MHD_NO;
+  /* This adds the Access-Control-Allow-Origin header.
+   * All endpoints of the exchange allow CORS. */
+  TALER_MHD_add_global_headers (response);
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (response,
+                                         /* Not available as MHD constant yet 
*/
+                                         "Access-Control-Allow-Headers",
+                                         "*"));
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (response,
+                                         /* Not available as MHD constant yet 
*/
+                                         "Access-Control-Allow-Methods",
+                                         "*"));
+  {
+    MHD_RESULT ret;
+
+    ret = MHD_queue_response (connection,
+                              MHD_HTTP_NO_CONTENT,
+                              response);
+    MHD_destroy_response (response);
+    return ret;
+  }
+}
+
+
+MHD_RESULT
+TALER_MHD_reply_json_pack (struct MHD_Connection *connection,
+                           unsigned int response_code,
+                           const char *fmt,
+                           ...)
+{
+  json_t *json;
+  json_error_t jerror;
+
+  {
+    va_list argp;
+
+    va_start (argp,
+              fmt);
+    json = json_vpack_ex (&jerror,
+                          0,
+                          fmt,
+                          argp);
+    va_end (argp);
+  }
+
+  if (NULL == json)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to pack JSON with format `%s': %s\n",
+                fmt,
+                jerror.text);
+    GNUNET_break (0);
+    return MHD_NO;
+  }
+
+  {
+    MHD_RESULT ret;
+
+    ret = TALER_MHD_reply_json (connection,
+                                json,
+                                response_code);
+    json_decref (json);
+    return ret;
+  }
+}
+
+
+struct MHD_Response *
+TALER_MHD_make_json_pack (const char *fmt,
+                          ...)
+{
+  json_t *json;
+  json_error_t jerror;
+
+  {
+    va_list argp;
+
+    va_start (argp, fmt);
+    json = json_vpack_ex (&jerror,
+                          0,
+                          fmt,
+                          argp);
+    va_end (argp);
+  }
+
+  if (NULL == json)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to pack JSON with format `%s': %s\n",
+                fmt,
+                jerror.text);
+    GNUNET_break (0);
+    return NULL;
+  }
+
+  {
+    struct MHD_Response *response;
+
+    response = TALER_MHD_make_json (json);
+    json_decref (json);
+    return response;
+  }
+}
+
+
+struct MHD_Response *
+TALER_MHD_make_error (enum TALER_ErrorCode ec,
+                      const char *detail)
+{
+  return TALER_MHD_MAKE_JSON_PACK (
+    TALER_MHD_PACK_EC (ec),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_string ("detail", detail)));
+}
+
+
+MHD_RESULT
+TALER_MHD_reply_with_error (struct MHD_Connection *connection,
+                            unsigned int http_status,
+                            enum TALER_ErrorCode ec,
+                            const char *detail)
+{
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    http_status,
+    TALER_MHD_PACK_EC (ec),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_string ("detail", detail)));
+}
+
+
+MHD_RESULT
+TALER_MHD_reply_with_ec (struct MHD_Connection *connection,
+                         enum TALER_ErrorCode ec,
+                         const char *detail)
+{
+  unsigned int hc = TALER_ErrorCode_get_http_status (ec);
+
+  if ( (0 == hc) ||
+       (UINT_MAX == hc) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid Taler error code %d provided for response!\n",
+                (int) ec);
+    hc = MHD_HTTP_INTERNAL_SERVER_ERROR;
+  }
+  return TALER_MHD_reply_with_error (connection,
+                                     hc,
+                                     ec,
+                                     detail);
+}
+
+
+MHD_RESULT
+TALER_MHD_reply_request_too_large (struct MHD_Connection *connection)
+{
+  return TALER_MHD_reply_with_error (connection,
+                                     MHD_HTTP_REQUEST_ENTITY_TOO_LARGE,
+                                     TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT,
+                                     NULL);
+}
+
+
+MHD_RESULT
+TALER_MHD_reply_agpl (struct MHD_Connection *connection,
+                      const char *url)
+{
+  const char *agpl =
+    "This server is licensed under the Affero GPL. You will now be redirected 
to the source code.";
+  struct MHD_Response *response;
+
+  response = MHD_create_response_from_buffer (strlen (agpl),
+                                              (void *) agpl,
+                                              MHD_RESPMEM_PERSISTENT);
+  if (NULL == response)
+  {
+    GNUNET_break (0);
+    return MHD_NO;
+  }
+  TALER_MHD_add_global_headers (response);
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (response,
+                                         MHD_HTTP_HEADER_CONTENT_TYPE,
+                                         "text/plain"));
+  if (MHD_NO ==
+      MHD_add_response_header (response,
+                               MHD_HTTP_HEADER_LOCATION,
+                               url))
+  {
+    GNUNET_break (0);
+    MHD_destroy_response (response);
+    return MHD_NO;
+  }
+
+  {
+    MHD_RESULT ret;
+
+    ret = MHD_queue_response (connection,
+                              MHD_HTTP_FOUND,
+                              response);
+    MHD_destroy_response (response);
+    return ret;
+  }
+}
+
+
+MHD_RESULT
+TALER_MHD_reply_static (struct MHD_Connection *connection,
+                        unsigned int http_status,
+                        const char *mime_type,
+                        const char *body,
+                        size_t body_size)
+{
+  struct MHD_Response *response;
+
+  response = MHD_create_response_from_buffer (body_size,
+                                              (void *) body,
+                                              MHD_RESPMEM_PERSISTENT);
+  if (NULL == response)
+  {
+    GNUNET_break (0);
+    return MHD_NO;
+  }
+  TALER_MHD_add_global_headers (response);
+  if (NULL != mime_type)
+    GNUNET_break (MHD_YES ==
+                  MHD_add_response_header (response,
+                                           MHD_HTTP_HEADER_CONTENT_TYPE,
+                                           mime_type));
+  {
+    MHD_RESULT ret;
+
+    ret = MHD_queue_response (connection,
+                              http_status,
+                              response);
+    MHD_destroy_response (response);
+    return ret;
+  }
+}
+
+
+void
+TALER_MHD_get_date_string (struct GNUNET_TIME_Absolute at,
+                           char date[128])
+{
+  static const char *const days[] =
+  { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+  static const char *const mons[] =
+  { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
+    "Nov", "Dec"};
+  struct tm now;
+  time_t t;
+#if ! defined(HAVE_C11_GMTIME_S) && ! defined(HAVE_W32_GMTIME_S) && \
+  ! defined(HAVE_GMTIME_R)
+  struct tm*pNow;
+#endif
+
+  date[0] = 0;
+  t = (time_t) (at.abs_value_us / 1000LL / 1000LL);
+#if defined(HAVE_C11_GMTIME_S)
+  if (NULL == gmtime_s (&t, &now))
+    return;
+#elif defined(HAVE_W32_GMTIME_S)
+  if (0 != gmtime_s (&now, &t))
+    return;
+#elif defined(HAVE_GMTIME_R)
+  if (NULL == gmtime_r (&t, &now))
+    return;
+#else
+  pNow = gmtime (&t);
+  if (NULL == pNow)
+    return;
+  now = *pNow;
+#endif
+  sprintf (date,
+           "%3s, %02u %3s %04u %02u:%02u:%02u GMT",
+           days[now.tm_wday % 7],
+           (unsigned int) now.tm_mday,
+           mons[now.tm_mon % 12],
+           (unsigned int) (1900 + now.tm_year),
+           (unsigned int) now.tm_hour,
+           (unsigned int) now.tm_min,
+           (unsigned int) now.tm_sec);
+}
+
+
+/* end of mhd_responses.c */
diff --git a/src/mhd/mhd_run.c b/src/mhd/mhd_run.c
new file mode 100644
index 0000000..8388fbf
--- /dev/null
+++ b/src/mhd/mhd_run.c
@@ -0,0 +1,175 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2019-2021 Taler Systems SA
+
+  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, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file mhd_run.c
+ * @brief API for running an MHD daemon with the
+ *        GNUnet scheduler
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler_util.h"
+#include "taler_mhd_lib.h"
+
+
+/**
+ * Set to true if we should immediately MHD_run() again.
+ */
+static bool triggered;
+
+/**
+ * Task running the HTTP server.
+ */
+static struct GNUNET_SCHEDULER_Task *mhd_task;
+
+/**
+ * The MHD daemon we are running.
+ */
+static struct MHD_Daemon *mhd;
+
+
+/**
+ * Function that queries MHD's select sets and
+ * starts the task waiting for them.
+ */
+static struct GNUNET_SCHEDULER_Task *
+prepare_daemon (void);
+
+
+/**
+ * Call MHD to process pending requests and then go back
+ * and schedule the next run.
+ *
+ * @param cls NULL
+ */
+static void
+run_daemon (void *cls)
+{
+  (void) cls;
+  mhd_task = NULL;
+  do {
+    triggered = false;
+    GNUNET_assert (MHD_YES ==
+                   MHD_run (mhd));
+  } while (triggered);
+  mhd_task = prepare_daemon ();
+}
+
+
+/**
+ * Function that queries MHD's select sets and starts the task waiting for
+ * them.
+ *
+ * @return task handle for the MHD task.
+ */
+static struct GNUNET_SCHEDULER_Task *
+prepare_daemon (void)
+{
+  struct GNUNET_SCHEDULER_Task *ret;
+  fd_set rs;
+  fd_set ws;
+  fd_set es;
+  struct GNUNET_NETWORK_FDSet *wrs;
+  struct GNUNET_NETWORK_FDSet *wws;
+  int max;
+  MHD_UNSIGNED_LONG_LONG timeout;
+  int haveto;
+  struct GNUNET_TIME_Relative tv;
+
+  FD_ZERO (&rs);
+  FD_ZERO (&ws);
+  FD_ZERO (&es);
+  wrs = GNUNET_NETWORK_fdset_create ();
+  wws = GNUNET_NETWORK_fdset_create ();
+  max = -1;
+  GNUNET_assert (MHD_YES ==
+                 MHD_get_fdset (mhd,
+                                &rs,
+                                &ws,
+                                &es,
+                                &max));
+  haveto = MHD_get_timeout (mhd,
+                            &timeout);
+  if (haveto == MHD_YES)
+    tv = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
+                                        timeout);
+  else
+    tv = GNUNET_TIME_UNIT_FOREVER_REL;
+  GNUNET_NETWORK_fdset_copy_native (wrs,
+                                    &rs,
+                                    max + 1);
+  GNUNET_NETWORK_fdset_copy_native (wws,
+                                    &ws,
+                                    max + 1);
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Adding run_daemon select task\n");
+  ret = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH,
+                                     tv,
+                                     wrs,
+                                     wws,
+                                     &run_daemon,
+                                     NULL);
+  GNUNET_NETWORK_fdset_destroy (wrs);
+  GNUNET_NETWORK_fdset_destroy (wws);
+  return ret;
+}
+
+
+void
+TALER_MHD_daemon_start (struct MHD_Daemon *daemon)
+{
+  GNUNET_assert (NULL == mhd);
+  mhd = daemon;
+  mhd_task = prepare_daemon ();
+}
+
+
+struct MHD_Daemon *
+TALER_MHD_daemon_stop (void)
+{
+  struct MHD_Daemon *ret;
+
+  if (NULL != mhd_task)
+  {
+    GNUNET_SCHEDULER_cancel (mhd_task);
+    mhd_task = NULL;
+  }
+  ret = mhd;
+  mhd = NULL;
+  return ret;
+}
+
+
+void
+TALER_MHD_daemon_trigger (void)
+{
+  if (NULL != mhd_task)
+  {
+    GNUNET_SCHEDULER_cancel (mhd_task);
+    mhd_task = GNUNET_SCHEDULER_add_now (&run_daemon,
+                                         NULL);
+  }
+  else
+  {
+    triggered = true;
+  }
+}
+
+
+/* end of mhd_run.c */
diff --git a/src/pq/Makefile.am b/src/pq/Makefile.am
new file mode 100644
index 0000000..4b192d7
--- /dev/null
+++ b/src/pq/Makefile.am
@@ -0,0 +1,42 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include $(LIBGCRYPT_CFLAGS) 
$(POSTGRESQL_CPPFLAGS)
+
+if USE_COVERAGE
+  AM_CFLAGS = --coverage -O0
+  XLIB = -lgcov
+endif
+
+lib_LTLIBRARIES = \
+  libtalerpq.la
+
+libtalerpq_la_SOURCES = \
+  pq_common.h pq_common.c \
+  pq_query_helper.c \
+  pq_result_helper.c
+libtalerpq_la_LIBADD = \
+  $(top_builddir)/src/util/libtalerutil.la  \
+  -lgnunetutil -ljansson \
+  -lgnunetpq \
+  -lpq \
+  $(XLIB)
+libtalerpq_la_LDFLAGS = \
+  $(POSTGRESQL_LDFLAGS) \
+  -version-info 0:0:0 \
+  -no-undefined
+
+check_PROGRAMS= \
+ test_pq
+
+TESTS = \
+ $(check_PROGRAMS)
+
+test_pq_SOURCES = \
+  test_pq.c
+test_pq_LDADD = \
+  libtalerpq.la \
+  $(top_builddir)/src/util/libtalerutil.la  \
+  -lgnunetpq \
+  -lgnunetutil \
+  -ljansson \
+  -lpq \
+  $(XLIB)
diff --git a/src/pq/pq_common.c b/src/pq/pq_common.c
new file mode 100644
index 0000000..8b6f8f2
--- /dev/null
+++ b/src/pq/pq_common.c
@@ -0,0 +1,68 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file pq/pq_common.c
+ * @brief common defines for the pq functions
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "pq_common.h"
+
+struct TALER_PQ_AmountP
+TALER_PQ_make_taler_pq_amount_ (
+  const struct TALER_Amount *amount,
+  uint32_t oid_v,
+  uint32_t oid_f)
+{
+  struct TALER_PQ_AmountP rval = {
+    .cnt = htonl (2),
+    .oid_v = htonl (oid_v),
+    .oid_f = htonl (oid_f),
+    .sz_v = htonl (sizeof((amount)->value)),
+    .sz_f = htonl (sizeof((amount)->fraction)),
+    .v = GNUNET_htonll ((amount)->value),
+    .f = htonl ((amount)->fraction)
+  };
+
+  return rval;
+}
+
+
+size_t
+TALER_PQ_make_taler_pq_amount_currency_ (
+  const struct TALER_Amount *amount,
+  uint32_t oid_v,
+  uint32_t oid_f,
+  uint32_t oid_c,
+  struct TALER_PQ_AmountCurrencyP *rval)
+{
+  size_t clen = strlen (amount->currency);
+
+  GNUNET_assert (clen < TALER_CURRENCY_LEN);
+  rval->cnt = htonl (3);
+  rval->oid_v = htonl (oid_v);
+  rval->oid_f = htonl (oid_f);
+  rval->oid_c = htonl (oid_c);
+  rval->sz_v = htonl (sizeof(amount->value));
+  rval->sz_f = htonl (sizeof(amount->fraction));
+  rval->sz_c = htonl (clen);
+  rval->v = GNUNET_htonll (amount->value);
+  rval->f = htonl (amount->fraction);
+  memcpy (rval->c,
+          amount->currency,
+          clen);
+  return sizeof (*rval) - TALER_CURRENCY_LEN + clen;
+}
diff --git a/src/pq/pq_common.h b/src/pq/pq_common.h
new file mode 100644
index 0000000..6172c0b
--- /dev/null
+++ b/src/pq/pq_common.h
@@ -0,0 +1,125 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file pq/pq_common.h
+ * @brief common defines for the pq functions
+ * @author Özgür Kesim
+ */
+#ifndef TALER_PQ_COMMON_H_
+#define TALER_PQ_COMMON_H_
+
+#include "taler_util.h"
+
+/**
+ * Internal types that are supported as TALER-exchange-specific array types.
+ *
+ * To support a new type,
+ *   1. add a new entry into this list,
+ *   2. for query-support, implement the size calculation and memory copying in
+ *      qconv_array() accordingly, in pq_query_helper.c
+ *   3. provide a query-API for arrays of the type, by calling
+ *      query_param_array_generic with the appropriate parameters,
+ *      in pq_query_helper.c
+ *   4. for result-support, implement memory copying by adding another case
+ *      to extract_array_generic, in pq_result_helper.c
+ *   5. provide a result-spec-API for arrays of the type,
+ *      in pq_result_helper.c
+ *   6. expose the API's in taler_pq_lib.h
+ */
+enum TALER_PQ_ArrayType
+{
+  TALER_PQ_array_of_blinded_denom_sig,
+  TALER_PQ_array_of_blinded_coin_hash,
+  TALER_PQ_array_of_denom_hash,
+  /**
+   * Amounts *without* currency.
+   */
+  TALER_PQ_array_of_amount,
+  TALER_PQ_array_of_MAX,       /* must be last */
+};
+
+/**
+ * Memory representation of an taler amount record for Postgres.
+ *
+ * All values need to be in network-byte-order.
+ */
+struct TALER_PQ_AmountP
+{
+  uint32_t cnt;   /* # elements in the tuple (== 2) */
+  uint32_t oid_v; /* oid of .v  */
+  uint32_t sz_v;  /* size of .v */
+  uint64_t v;     /* value      */
+  uint32_t oid_f; /* oid of .f  */
+  uint32_t sz_f;  /* size of .f */
+  uint32_t f;     /* fraction   */
+} __attribute__((packed));
+
+
+/**
+ * Memory representation of an taler amount record with currency for Postgres.
+ *
+ * All values need to be in network-byte-order.
+ */
+struct TALER_PQ_AmountCurrencyP
+{
+  uint32_t cnt;   /* # elements in the tuple (== 3) */
+  uint32_t oid_v; /* oid of .v  */
+  uint32_t sz_v;  /* size of .v */
+  uint64_t v;     /* value      */
+  uint32_t oid_f; /* oid of .f  */
+  uint32_t sz_f;  /* size of .f */
+  uint32_t f;     /* fraction   */
+  uint32_t oid_c; /* oid of .c  */
+  uint32_t sz_c;  /* size of .c */
+  uint8_t c[TALER_CURRENCY_LEN];  /* currency */
+} __attribute__((packed));
+
+
+/**
+ * Create a `struct TALER_PQ_AmountP` for initialization
+ *
+ * @param amount amount of type `struct TALER_Amount *`
+ * @param oid_v OID of the INT8 type in postgres
+ * @param oid_f OID of the INT4 type in postgres
+ */
+struct TALER_PQ_AmountP
+TALER_PQ_make_taler_pq_amount_ (
+  const struct TALER_Amount *amount,
+  uint32_t oid_v,
+  uint32_t oid_f);
+
+
+/**
+ * Create a `struct TALER_PQ_AmountCurrencyP` for initialization
+ *
+ * @param amount amount of type `struct TALER_Amount *`
+ * @param oid_v OID of the INT8 type in postgres
+ * @param oid_f OID of the INT4 type in postgres
+ * @param oid_c OID of the TEXT type in postgres
+ * @param[out] rval set to encoded @a amount
+ * @return actual (useful) size of @a rval for Postgres
+ */
+size_t
+TALER_PQ_make_taler_pq_amount_currency_ (
+  const struct TALER_Amount *amount,
+  uint32_t oid_v,
+  uint32_t oid_f,
+  uint32_t oid_c,
+  struct TALER_PQ_AmountCurrencyP *rval);
+
+
+#endif  /* TALER_PQ_COMMON_H_ */
+/* end of pg/pq_common.h */
diff --git a/src/pq/pq_query_helper.c b/src/pq/pq_query_helper.c
new file mode 100644
index 0000000..dd6cf67
--- /dev/null
+++ b/src/pq/pq_query_helper.c
@@ -0,0 +1,1183 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file pq/pq_query_helper.c
+ * @brief helper functions for Taler-specific libpq (PostGres) interactions
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Florian Dold
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_pq_lib.h>
+#include "taler_pq_lib.h"
+#include "pq_common.h"
+
+
+/**
+ * Function called to convert input amount into SQL parameter as tuple.
+ *
+ * @param cls closure
+ * @param data pointer to input argument, here a `struct TALER_Amount`
+ * @param data_len number of bytes in @a data (if applicable)
+ * @param[out] param_values SQL data to set
+ * @param[out] param_lengths SQL length data to set
+ * @param[out] param_formats SQL format data to set
+ * @param param_length number of entries available in the @a param_values, @a 
param_lengths and @a param_formats arrays
+ * @param[out] scratch buffer for dynamic allocations (to be done via 
GNUNET_malloc()
+ * @param scratch_length number of entries left in @a scratch
+ * @return -1 on error, number of offsets used in @a scratch otherwise
+ */
+static int
+qconv_amount_currency_tuple (void *cls,
+                             const void *data,
+                             size_t data_len,
+                             void *param_values[],
+                             int param_lengths[],
+                             int param_formats[],
+                             unsigned int param_length,
+                             void *scratch[],
+                             unsigned int scratch_length)
+{
+  struct GNUNET_PQ_Context *db = cls;
+  const struct TALER_Amount *amount = data;
+  size_t sz;
+
+  GNUNET_assert (NULL != db);
+  GNUNET_assert (NULL != amount);
+  GNUNET_assert (1 == param_length);
+  GNUNET_assert (1 <= scratch_length);
+  GNUNET_assert (sizeof (struct TALER_Amount) == data_len);
+  GNUNET_static_assert (sizeof(uint32_t) == sizeof(Oid));
+  {
+    char *out;
+    Oid oid_v;
+    Oid oid_f;
+    Oid oid_c;
+    struct TALER_PQ_AmountCurrencyP d;
+
+    GNUNET_assert (GNUNET_OK ==
+                   GNUNET_PQ_get_oid_by_name (db,
+                                              "int8",
+                                              &oid_v));
+    GNUNET_assert (GNUNET_OK ==
+                   GNUNET_PQ_get_oid_by_name (db,
+                                              "int4",
+                                              &oid_f));
+    GNUNET_assert (GNUNET_OK ==
+                   GNUNET_PQ_get_oid_by_name (db,
+                                              "varchar",
+                                              &oid_c));
+    sz = TALER_PQ_make_taler_pq_amount_currency_ (amount,
+                                                  oid_v,
+                                                  oid_f,
+                                                  oid_c,
+                                                  &d);
+    out = GNUNET_malloc (sz);
+    memcpy (out,
+            &d,
+            sz);
+    scratch[0] = out;
+  }
+
+  param_values[0] = scratch[0];
+  param_lengths[0] = sz;
+  param_formats[0] = 1;
+
+  return 1;
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_amount_with_currency (
+  const struct GNUNET_PQ_Context *db,
+  const struct TALER_Amount *amount)
+{
+  struct GNUNET_PQ_QueryParam res = {
+    .conv_cls = (void *) db,
+    .conv = &qconv_amount_currency_tuple,
+    .data = amount,
+    .size = sizeof (*amount),
+    .num_params = 1,
+  };
+
+  return res;
+}
+
+
+/**
+ * Function called to convert input amount into SQL parameter as tuple.
+ *
+ * @param cls closure
+ * @param data pointer to input argument, here a `struct TALER_Amount`
+ * @param data_len number of bytes in @a data (if applicable)
+ * @param[out] param_values SQL data to set
+ * @param[out] param_lengths SQL length data to set
+ * @param[out] param_formats SQL format data to set
+ * @param param_length number of entries available in the @a param_values, @a 
param_lengths and @a param_formats arrays
+ * @param[out] scratch buffer for dynamic allocations (to be done via 
GNUNET_malloc()
+ * @param scratch_length number of entries left in @a scratch
+ * @return -1 on error, number of offsets used in @a scratch otherwise
+ */
+static int
+qconv_amount_tuple (void *cls,
+                    const void *data,
+                    size_t data_len,
+                    void *param_values[],
+                    int param_lengths[],
+                    int param_formats[],
+                    unsigned int param_length,
+                    void *scratch[],
+                    unsigned int scratch_length)
+{
+  struct GNUNET_PQ_Context *db = cls;
+  const struct TALER_Amount *amount = data;
+  size_t sz;
+
+  GNUNET_assert (NULL != db);
+  GNUNET_assert (NULL != amount);
+  GNUNET_assert (1 == param_length);
+  GNUNET_assert (1 <= scratch_length);
+  GNUNET_assert (sizeof (struct TALER_Amount) == data_len);
+  GNUNET_static_assert (sizeof(uint32_t) == sizeof(Oid));
+  {
+    char *out;
+    Oid oid_v;
+    Oid oid_f;
+
+    GNUNET_assert (GNUNET_OK ==
+                   GNUNET_PQ_get_oid_by_name (db,
+                                              "int8",
+                                              &oid_v));
+    GNUNET_assert (GNUNET_OK ==
+                   GNUNET_PQ_get_oid_by_name (db,
+                                              "int4",
+                                              &oid_f));
+
+    {
+      struct TALER_PQ_AmountP d
+        = TALER_PQ_make_taler_pq_amount_ (amount,
+                                          oid_v,
+                                          oid_f);
+
+      sz = sizeof(d);
+      out = GNUNET_malloc (sz);
+      scratch[0] = out;
+      GNUNET_memcpy (out,
+                     &d,
+                     sizeof(d));
+    }
+  }
+
+  param_values[0] = scratch[0];
+  param_lengths[0] = sz;
+  param_formats[0] = 1;
+
+  return 1;
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_amount (
+  const struct GNUNET_PQ_Context *db,
+  const struct TALER_Amount *amount)
+{
+  struct GNUNET_PQ_QueryParam res = {
+    .conv_cls = (void *) db,
+    .conv = &qconv_amount_tuple,
+    .data = amount,
+    .size = sizeof (*amount),
+    .num_params = 1,
+  };
+
+  return res;
+}
+
+
+/**
+ * Function called to convert input argument into SQL parameters.
+ *
+ * @param cls closure
+ * @param data pointer to input argument
+ * @param data_len number of bytes in @a data (if applicable)
+ * @param[out] param_values SQL data to set
+ * @param[out] param_lengths SQL length data to set
+ * @param[out] param_formats SQL format data to set
+ * @param param_length number of entries available in the @a param_values, @a 
param_lengths and @a param_formats arrays
+ * @param[out] scratch buffer for dynamic allocations (to be done via 
#GNUNET_malloc()
+ * @param scratch_length number of entries left in @a scratch
+ * @return -1 on error, number of offsets used in @a scratch otherwise
+ */
+static int
+qconv_denom_pub (void *cls,
+                 const void *data,
+                 size_t data_len,
+                 void *param_values[],
+                 int param_lengths[],
+                 int param_formats[],
+                 unsigned int param_length,
+                 void *scratch[],
+                 unsigned int scratch_length)
+{
+  const struct TALER_DenominationPublicKey *denom_pub = data;
+  size_t tlen;
+  size_t len;
+  uint32_t be[2];
+  char *buf;
+  void *tbuf;
+
+  (void) cls;
+  (void) data_len;
+  GNUNET_assert (1 == param_length);
+  GNUNET_assert (scratch_length > 0);
+  GNUNET_break (NULL == cls);
+  be[0] = htonl ((uint32_t) denom_pub->cipher);
+  be[1] = htonl (denom_pub->age_mask.bits);
+  switch (denom_pub->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    tlen = GNUNET_CRYPTO_rsa_public_key_encode (
+      denom_pub->details.rsa_public_key,
+      &tbuf);
+    break;
+  case TALER_DENOMINATION_CS:
+    tlen = sizeof (denom_pub->details.cs_public_key);
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+  len = tlen + sizeof (be);
+  buf = GNUNET_malloc (len);
+  GNUNET_memcpy (buf,
+                 be,
+                 sizeof (be));
+  switch (denom_pub->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    GNUNET_memcpy (&buf[sizeof (be)],
+                   tbuf,
+                   tlen);
+    GNUNET_free (tbuf);
+    break;
+  case TALER_DENOMINATION_CS:
+    GNUNET_memcpy (&buf[sizeof (be)],
+                   &denom_pub->details.cs_public_key,
+                   tlen);
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+
+  scratch[0] = buf;
+  param_values[0] = (void *) buf;
+  param_lengths[0] = len;
+  param_formats[0] = 1;
+  return 1;
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_denom_pub (
+  const struct TALER_DenominationPublicKey *denom_pub)
+{
+  struct GNUNET_PQ_QueryParam res = {
+    .conv = &qconv_denom_pub,
+    .data = denom_pub,
+    .num_params = 1
+  };
+
+  return res;
+}
+
+
+/**
+ * Function called to convert input argument into SQL parameters.
+ *
+ * @param cls closure
+ * @param data pointer to input argument
+ * @param data_len number of bytes in @a data (if applicable)
+ * @param[out] param_values SQL data to set
+ * @param[out] param_lengths SQL length data to set
+ * @param[out] param_formats SQL format data to set
+ * @param param_length number of entries available in the @a param_values, @a 
param_lengths and @a param_formats arrays
+ * @param[out] scratch buffer for dynamic allocations (to be done via 
#GNUNET_malloc()
+ * @param scratch_length number of entries left in @a scratch
+ * @return -1 on error, number of offsets used in @a scratch otherwise
+ */
+static int
+qconv_denom_sig (void *cls,
+                 const void *data,
+                 size_t data_len,
+                 void *param_values[],
+                 int param_lengths[],
+                 int param_formats[],
+                 unsigned int param_length,
+                 void *scratch[],
+                 unsigned int scratch_length)
+{
+  const struct TALER_DenominationSignature *denom_sig = data;
+  size_t tlen;
+  size_t len;
+  uint32_t be[2];
+  char *buf;
+  void *tbuf;
+
+  (void) cls;
+  (void) data_len;
+  GNUNET_assert (1 == param_length);
+  GNUNET_assert (scratch_length > 0);
+  GNUNET_break (NULL == cls);
+  be[0] = htonl ((uint32_t) denom_sig->cipher);
+  be[1] = htonl (0x00); /* magic marker: unblinded */
+  switch (denom_sig->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    tlen = GNUNET_CRYPTO_rsa_signature_encode (
+      denom_sig->details.rsa_signature,
+      &tbuf);
+    break;
+  case TALER_DENOMINATION_CS:
+    tlen = sizeof (denom_sig->details.cs_signature);
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+  len = tlen + sizeof (be);
+  buf = GNUNET_malloc (len);
+  GNUNET_memcpy (buf,
+                 &be,
+                 sizeof (be));
+  switch (denom_sig->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    GNUNET_memcpy (&buf[sizeof (be)],
+                   tbuf,
+                   tlen);
+    GNUNET_free (tbuf);
+    break;
+  case TALER_DENOMINATION_CS:
+    GNUNET_memcpy (&buf[sizeof (be)],
+                   &denom_sig->details.cs_signature,
+                   tlen);
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+
+  scratch[0] = buf;
+  param_values[0] = (void *) buf;
+  param_lengths[0] = len;
+  param_formats[0] = 1;
+  return 1;
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_denom_sig (
+  const struct TALER_DenominationSignature *denom_sig)
+{
+  struct GNUNET_PQ_QueryParam res = {
+    .conv = &qconv_denom_sig,
+    .data = denom_sig,
+    .num_params = 1
+  };
+
+  return res;
+}
+
+
+/**
+ * Function called to convert input argument into SQL parameters.
+ *
+ * @param cls closure
+ * @param data pointer to input argument
+ * @param data_len number of bytes in @a data (if applicable)
+ * @param[out] param_values SQL data to set
+ * @param[out] param_lengths SQL length data to set
+ * @param[out] param_formats SQL format data to set
+ * @param param_length number of entries available in the @a param_values, @a 
param_lengths and @a param_formats arrays
+ * @param[out] scratch buffer for dynamic allocations (to be done via 
#GNUNET_malloc()
+ * @param scratch_length number of entries left in @a scratch
+ * @return -1 on error, number of offsets used in @a scratch otherwise
+ */
+static int
+qconv_blinded_denom_sig (void *cls,
+                         const void *data,
+                         size_t data_len,
+                         void *param_values[],
+                         int param_lengths[],
+                         int param_formats[],
+                         unsigned int param_length,
+                         void *scratch[],
+                         unsigned int scratch_length)
+{
+  const struct TALER_BlindedDenominationSignature *denom_sig = data;
+  size_t tlen;
+  size_t len;
+  uint32_t be[2];
+  char *buf;
+  void *tbuf;
+
+  (void) cls;
+  (void) data_len;
+  GNUNET_assert (1 == param_length);
+  GNUNET_assert (scratch_length > 0);
+  GNUNET_break (NULL == cls);
+  be[0] = htonl ((uint32_t) denom_sig->cipher);
+  be[1] = htonl (0x01); /* magic marker: blinded */
+  switch (denom_sig->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    tlen = GNUNET_CRYPTO_rsa_signature_encode (
+      denom_sig->details.blinded_rsa_signature,
+      &tbuf);
+    break;
+  case TALER_DENOMINATION_CS:
+    tlen = sizeof (denom_sig->details.blinded_cs_answer);
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+  len = tlen + sizeof (be);
+  buf = GNUNET_malloc (len);
+  GNUNET_memcpy (buf,
+                 &be,
+                 sizeof (be));
+  switch (denom_sig->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    GNUNET_memcpy (&buf[sizeof (be)],
+                   tbuf,
+                   tlen);
+    GNUNET_free (tbuf);
+    break;
+  case TALER_DENOMINATION_CS:
+    GNUNET_memcpy (&buf[sizeof (be)],
+                   &denom_sig->details.blinded_cs_answer,
+                   tlen);
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+
+  scratch[0] = buf;
+  param_values[0] = (void *) buf;
+  param_lengths[0] = len;
+  param_formats[0] = 1;
+  return 1;
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_blinded_denom_sig (
+  const struct TALER_BlindedDenominationSignature *denom_sig)
+{
+  struct GNUNET_PQ_QueryParam res = {
+    .conv = &qconv_blinded_denom_sig,
+    .data = denom_sig,
+    .num_params = 1
+  };
+
+  return res;
+}
+
+
+/**
+ * Function called to convert input argument into SQL parameters.
+ *
+ * @param cls closure
+ * @param data pointer to input argument
+ * @param data_len number of bytes in @a data (if applicable)
+ * @param[out] param_values SQL data to set
+ * @param[out] param_lengths SQL length data to set
+ * @param[out] param_formats SQL format data to set
+ * @param param_length number of entries available in the @a param_values, @a 
param_lengths and @a param_formats arrays
+ * @param[out] scratch buffer for dynamic allocations (to be done via 
#GNUNET_malloc()
+ * @param scratch_length number of entries left in @a scratch
+ * @return -1 on error, number of offsets used in @a scratch otherwise
+ */
+static int
+qconv_blinded_planchet (void *cls,
+                        const void *data,
+                        size_t data_len,
+                        void *param_values[],
+                        int param_lengths[],
+                        int param_formats[],
+                        unsigned int param_length,
+                        void *scratch[],
+                        unsigned int scratch_length)
+{
+  const struct TALER_BlindedPlanchet *bp = data;
+  size_t tlen;
+  size_t len;
+  uint32_t be[2];
+  char *buf;
+
+  (void) cls;
+  (void) data_len;
+  GNUNET_assert (1 == param_length);
+  GNUNET_assert (scratch_length > 0);
+  GNUNET_break (NULL == cls);
+  be[0] = htonl ((uint32_t) bp->cipher);
+  be[1] = htonl (0x0100); /* magic marker: blinded */
+  switch (bp->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    tlen = bp->details.rsa_blinded_planchet.blinded_msg_size;
+    break;
+  case TALER_DENOMINATION_CS:
+    tlen = sizeof (bp->details.cs_blinded_planchet);
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+  len = tlen + sizeof (be);
+  buf = GNUNET_malloc (len);
+  GNUNET_memcpy (buf,
+                 &be,
+                 sizeof (be));
+  switch (bp->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    GNUNET_memcpy (&buf[sizeof (be)],
+                   bp->details.rsa_blinded_planchet.blinded_msg,
+                   tlen);
+    break;
+  case TALER_DENOMINATION_CS:
+    GNUNET_memcpy (&buf[sizeof (be)],
+                   &bp->details.cs_blinded_planchet,
+                   tlen);
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+  scratch[0] = buf;
+  param_values[0] = (void *) buf;
+  param_lengths[0] = len;
+  param_formats[0] = 1;
+  return 1;
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_blinded_planchet (
+  const struct TALER_BlindedPlanchet *bp)
+{
+  struct GNUNET_PQ_QueryParam res = {
+    .conv = &qconv_blinded_planchet,
+    .data = bp,
+    .num_params = 1
+  };
+
+  return res;
+}
+
+
+/**
+ * Function called to convert input argument into SQL parameters.
+ *
+ * @param cls closure
+ * @param data pointer to input argument
+ * @param data_len number of bytes in @a data (if applicable)
+ * @param[out] param_values SQL data to set
+ * @param[out] param_lengths SQL length data to set
+ * @param[out] param_formats SQL format data to set
+ * @param param_length number of entries available in the @a param_values, @a 
param_lengths and @a param_formats arrays
+ * @param[out] scratch buffer for dynamic allocations (to be done via 
#GNUNET_malloc()
+ * @param scratch_length number of entries left in @a scratch
+ * @return -1 on error, number of offsets used in @a scratch otherwise
+ */
+static int
+qconv_exchange_withdraw_values (void *cls,
+                                const void *data,
+                                size_t data_len,
+                                void *param_values[],
+                                int param_lengths[],
+                                int param_formats[],
+                                unsigned int param_length,
+                                void *scratch[],
+                                unsigned int scratch_length)
+{
+  const struct TALER_ExchangeWithdrawValues *alg_values = data;
+  size_t tlen;
+  size_t len;
+  uint32_t be[2];
+  char *buf;
+
+  (void) cls;
+  (void) data_len;
+  GNUNET_assert (1 == param_length);
+  GNUNET_assert (scratch_length > 0);
+  GNUNET_break (NULL == cls);
+  be[0] = htonl ((uint32_t) alg_values->cipher);
+  be[1] = htonl (0x010000); /* magic marker: EWV */
+  switch (alg_values->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    tlen = 0;
+    break;
+  case TALER_DENOMINATION_CS:
+    tlen = sizeof (struct TALER_DenominationCSPublicRPairP);
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+  len = tlen + sizeof (be);
+  buf = GNUNET_malloc (len);
+  GNUNET_memcpy (buf,
+                 &be,
+                 sizeof (be));
+  switch (alg_values->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    break;
+  case TALER_DENOMINATION_CS:
+    GNUNET_memcpy (&buf[sizeof (be)],
+                   &alg_values->details.cs_values,
+                   tlen);
+    break;
+  default:
+    GNUNET_assert (0);
+  }
+  scratch[0] = buf;
+  param_values[0] = (void *) buf;
+  param_lengths[0] = len;
+  param_formats[0] = 1;
+  return 1;
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_exchange_withdraw_values (
+  const struct TALER_ExchangeWithdrawValues *alg_values)
+{
+  struct GNUNET_PQ_QueryParam res = {
+    .conv = &qconv_exchange_withdraw_values,
+    .data = alg_values,
+    .num_params = 1
+  };
+
+  return res;
+}
+
+
+/**
+ * Function called to convert input argument into SQL parameters.
+ *
+ * @param cls closure
+ * @param data pointer to input argument, here a `json_t *`
+ * @param data_len number of bytes in @a data (if applicable)
+ * @param[out] param_values SQL data to set
+ * @param[out] param_lengths SQL length data to set
+ * @param[out] param_formats SQL format data to set
+ * @param param_length number of entries available in the @a param_values, @a 
param_lengths and @a param_formats arrays
+ * @param[out] scratch buffer for dynamic allocations (to be done via 
GNUNET_malloc()
+ * @param scratch_length number of entries left in @a scratch
+ * @return -1 on error, number of offsets used in @a scratch otherwise
+ */
+static int
+qconv_json (void *cls,
+            const void *data,
+            size_t data_len,
+            void *param_values[],
+            int param_lengths[],
+            int param_formats[],
+            unsigned int param_length,
+            void *scratch[],
+            unsigned int scratch_length)
+{
+  const json_t *json = data;
+  char *str;
+
+  (void) cls;
+  (void) data_len;
+  GNUNET_assert (1 == param_length);
+  GNUNET_assert (scratch_length > 0);
+  str = json_dumps (json, JSON_COMPACT);
+  if (NULL == str)
+    return -1;
+  scratch[0] = str;
+  param_values[0] = (void *) str;
+  param_lengths[0] = strlen (str);
+  param_formats[0] = 1;
+  return 1;
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_json (const json_t *x)
+{
+  struct GNUNET_PQ_QueryParam res = {
+    .conv = &qconv_json,
+    .data = x,
+    .num_params = 1
+  };
+
+  return res;
+}
+
+
+/** ------------------- Array support  -----------------------------------**/
+
+/**
+ * Closure for the array type handlers.
+ *
+ * May contain sizes information for the data, given (and handled) by the
+ * caller.
+ */
+struct qconv_array_cls
+{
+  /**
+   * If not null, contains the array of sizes (the size of the array is the
+   * .size field in the ambient GNUNET_PQ_QueryParam struct). We do not free
+   * this memory.
+   *
+   * If not null, this value has precedence over @a sizes, which MUST be NULL 
*/
+  const size_t *sizes;
+
+  /**
+   * If @a size and @a c_sizes are NULL, this field defines the same size
+   * for each element in the array.
+   */
+  size_t same_size;
+
+  /**
+   * If true, the array parameter to the data pointer to the qconv_array is a
+   * continuous byte array of data, either with @a same_size each or sizes
+   * provided bytes by @a sizes;
+   */
+  bool continuous;
+
+  /**
+   * Type of the array elements
+   */
+  enum TALER_PQ_ArrayType typ;
+
+  /**
+   * Oid of the array elements
+   */
+  Oid oid;
+
+  /**
+   * db context, needed for OID-lookup of basis-types
+   */
+  struct GNUNET_PQ_Context *db;
+};
+
+/**
+ * Callback to cleanup a qconv_array_cls to be used during
+ * GNUNET_PQ_cleanup_query_params_closures
+ */
+static void
+qconv_array_cls_cleanup (void *cls)
+{
+  GNUNET_free (cls);
+}
+
+
+/**
+ * Function called to convert input argument into SQL parameters for arrays
+ *
+ * Note: the format for the encoding of arrays for libpq is not very well
+ * documented.  We peeked into various sources (postgresql and libpqtypes) for
+ * guidance.
+ *
+ * @param cls Closure of type struct qconv_array_cls*
+ * @param data Pointer to first element in the array
+ * @param data_len Number of _elements_ in array @a data (if applicable)
+ * @param[out] param_values SQL data to set
+ * @param[out] param_lengths SQL length data to set
+ * @param[out] param_formats SQL format data to set
+ * @param param_length number of entries available in the @a param_values, @a 
param_lengths and @a param_formats arrays
+ * @param[out] scratch buffer for dynamic allocations (to be done via 
#GNUNET_malloc()
+ * @param scratch_length number of entries left in @a scratch
+ * @return -1 on error, number of offsets used in @a scratch otherwise
+ */
+static int
+qconv_array (
+  void *cls,
+  const void *data,
+  size_t data_len,
+  void *param_values[],
+  int param_lengths[],
+  int param_formats[],
+  unsigned int param_length,
+  void *scratch[],
+  unsigned int scratch_length)
+{
+  struct qconv_array_cls *meta = cls;
+  size_t num = data_len;
+  size_t total_size;
+  const size_t *sizes;
+  bool same_sized;
+  void *elements = NULL;
+  bool noerror = true;
+  /* needed to capture the encoded rsa signatures */
+  void **buffers = NULL;
+  size_t *buffer_lengths = NULL;
+
+  (void) (param_length);
+  (void) (scratch_length);
+
+  GNUNET_assert (NULL != meta);
+  GNUNET_assert (num < INT_MAX);
+
+  sizes = meta->sizes;
+  same_sized = (0 != meta->same_size);
+
+#define RETURN_UNLESS(cond) \
+        do { \
+          if (! (cond)) \
+          { \
+            GNUNET_break ((cond)); \
+            noerror = false; \
+            goto DONE; \
+          } \
+        } while (0)
+
+  /* Calculate sizes and check bounds */
+  {
+    /* num * length-field */
+    size_t x = sizeof(uint32_t);
+    size_t y = x * num;
+    RETURN_UNLESS ((0 == num) || (y / num == x));
+
+    /* size of header */
+    total_size  = x = sizeof(struct GNUNET_PQ_ArrayHeader_P);
+    total_size += y;
+    RETURN_UNLESS (total_size >= x);
+
+    /* sizes of elements */
+    if (same_sized)
+    {
+      x = num * meta->same_size;
+      RETURN_UNLESS ((0 == num) || (x / num == meta->same_size));
+
+      y = total_size;
+      total_size += x;
+      RETURN_UNLESS (total_size >= y);
+    }
+    else  /* sizes are different per element */
+    {
+      switch (meta->typ)
+      {
+      case TALER_PQ_array_of_blinded_denom_sig:
+        {
+          const struct TALER_BlindedDenominationSignature *denom_sigs = data;
+          size_t len;
+
+          buffers  = GNUNET_new_array (num, void *);
+          buffer_lengths  = GNUNET_new_array (num, size_t);
+
+          for (size_t i = 0; i<num; i++)
+          {
+            switch (denom_sigs[i].cipher)
+            {
+            case TALER_DENOMINATION_RSA:
+              len = GNUNET_CRYPTO_rsa_signature_encode (
+                denom_sigs[i].details.blinded_rsa_signature,
+                &buffers[i]);
+              RETURN_UNLESS (len != 0);
+              break;
+            case TALER_DENOMINATION_CS:
+              len = sizeof (denom_sigs[i].details.blinded_cs_answer);
+              break;
+            default:
+              GNUNET_assert (0);
+            }
+
+            /* for the cipher and marker */
+            len += 2 * sizeof(uint32_t);
+            buffer_lengths[i] = len;
+
+            y = total_size;
+            total_size += len;
+            RETURN_UNLESS (total_size >= y);
+          }
+          sizes = buffer_lengths;
+          break;
+        }
+      default:
+        GNUNET_assert (0);
+      }
+    }
+
+    RETURN_UNLESS (INT_MAX > total_size);
+    RETURN_UNLESS (0 != total_size);
+
+    elements = GNUNET_malloc (total_size);
+  }
+
+  /* Write data */
+  {
+    char *out = elements;
+    struct GNUNET_PQ_ArrayHeader_P h = {
+      .ndim = htonl (1),        /* We only support one-dimensional arrays */
+      .has_null = htonl (0),    /* We do not support NULL entries in arrays */
+      .lbound = htonl (1),      /* Default start index value */
+      .dim = htonl (num),
+      .oid = htonl (meta->oid),
+    };
+
+    /* Write header */
+    GNUNET_memcpy (out,
+                   &h,
+                   sizeof(h));
+    out += sizeof(h);
+
+    /* Write elements */
+    for (size_t i = 0; i < num; i++)
+    {
+      size_t sz = same_sized ? meta->same_size : sizes[i];
+
+      *(uint32_t *) out = htonl (sz);
+      out += sizeof(uint32_t);
+      switch (meta->typ)
+      {
+      case TALER_PQ_array_of_amount:
+        {
+          const struct TALER_Amount *amounts = data;
+          Oid oid_v;
+          Oid oid_f;
+
+          GNUNET_assert (GNUNET_OK ==
+                         GNUNET_PQ_get_oid_by_name (meta->db,
+                                                    "int8",
+                                                    &oid_v));
+          GNUNET_assert (GNUNET_OK ==
+                         GNUNET_PQ_get_oid_by_name (meta->db,
+                                                    "int4",
+                                                    &oid_f));
+          {
+            struct TALER_PQ_AmountP am
+              = TALER_PQ_make_taler_pq_amount_ (
+                  &amounts[i],
+                  oid_v,
+                  oid_f);
+
+            GNUNET_memcpy (out,
+                           &am,
+                           sizeof(am));
+          }
+          break;
+        }
+      case TALER_PQ_array_of_blinded_denom_sig:
+        {
+          const struct TALER_BlindedDenominationSignature *denom_sigs = data;
+          uint32_t be[2];
+
+          be[0] = htonl ((uint32_t) denom_sigs[i].cipher);
+          be[1] = htonl (0x01);     /* magic margker: blinded */
+          GNUNET_memcpy (out,
+                         &be,
+                         sizeof(be));
+          out += sizeof(be);
+          sz -= sizeof(be);
+
+          switch (denom_sigs[i].cipher)
+          {
+          case TALER_DENOMINATION_RSA:
+            /* For RSA, 'same_sized' must have been false */
+            GNUNET_assert (NULL != buffers);
+            GNUNET_memcpy (out,
+                           buffers[i],
+                           sz);
+            break;
+          case TALER_DENOMINATION_CS:
+            GNUNET_memcpy (out,
+                           &denom_sigs[i].details.blinded_cs_answer,
+                           sz);
+            break;
+          default:
+            GNUNET_assert (0);
+          }
+          break;
+        }
+      case TALER_PQ_array_of_blinded_coin_hash:
+        {
+          const struct TALER_BlindedCoinHashP *coin_hs = data;
+
+          GNUNET_memcpy (out,
+                         &coin_hs[i],
+                         sizeof(struct TALER_BlindedCoinHashP));
+
+          break;
+        }
+      case TALER_PQ_array_of_denom_hash:
+        {
+          const struct TALER_DenominationHashP *denom_hs = data;
+
+          GNUNET_memcpy (out,
+                         &denom_hs[i],
+                         sizeof(struct TALER_DenominationHashP));
+          break;
+        }
+      default:
+        {
+          GNUNET_assert (0);
+          break;
+        }
+      }
+      out += sz;
+    }
+  }
+  param_values[0] = elements;
+  param_lengths[0] = total_size;
+  param_formats[0] = 1;
+  scratch[0] = elements;
+
+DONE:
+  if (NULL != buffers)
+  {
+    for (size_t i = 0; i<num; i++)
+      GNUNET_free (buffers[i]);
+    GNUNET_free (buffers);
+  }
+  GNUNET_free (buffer_lengths);
+  if (noerror)
+    return 1;
+  return -1;
+}
+
+
+/**
+ * Function to generate a typ specific query parameter and corresponding 
closure
+ *
+ * @param num Number of elements in @a elements
+ * @param continuous If true, @a elements is an continuous array of data
+ * @param elements Array of @a num elements, either continuous or pointers
+ * @param sizes Array of @a num sizes, one per element, may be NULL
+ * @param same_size If not 0, all elements in @a elements have this size
+ * @param typ Supported internal type of each element in @a elements
+ * @param oid Oid of the type to be used in Postgres
+ * @param[in,out] db our database handle for looking up OIDs
+ * @return Query parameter
+ */
+static struct GNUNET_PQ_QueryParam
+query_param_array_generic (
+  unsigned int num,
+  bool continuous,
+  const void *elements,
+  const size_t *sizes,
+  size_t same_size,
+  enum TALER_PQ_ArrayType typ,
+  Oid oid,
+  struct GNUNET_PQ_Context *db)
+{
+  struct qconv_array_cls *meta = GNUNET_new (struct qconv_array_cls);
+  meta->typ = typ;
+  meta->oid = oid;
+  meta->sizes = sizes;
+  meta->same_size = same_size;
+  meta->continuous = continuous;
+  meta->db = db;
+
+  struct GNUNET_PQ_QueryParam res = {
+    .conv = qconv_array,
+    .conv_cls = meta,
+    .conv_cls_cleanup = qconv_array_cls_cleanup,
+    .data = elements,
+    .size = num,
+    .num_params = 1,
+  };
+
+  return res;
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_array_blinded_denom_sig (
+  size_t num,
+  const struct TALER_BlindedDenominationSignature *denom_sigs,
+  struct GNUNET_PQ_Context *db)
+{
+  Oid oid;
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_PQ_get_oid_by_name (db, "bytea", &oid));
+  return query_param_array_generic (num,
+                                    true,
+                                    denom_sigs,
+                                    NULL,
+                                    0,
+                                    TALER_PQ_array_of_blinded_denom_sig,
+                                    oid,
+                                    NULL);
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_array_blinded_coin_hash (
+  size_t num,
+  const struct TALER_BlindedCoinHashP *coin_hs,
+  struct GNUNET_PQ_Context *db)
+{
+  Oid oid;
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_PQ_get_oid_by_name (db, "bytea", &oid));
+  return query_param_array_generic (num,
+                                    true,
+                                    coin_hs,
+                                    NULL,
+                                    sizeof(struct TALER_BlindedCoinHashP),
+                                    TALER_PQ_array_of_blinded_coin_hash,
+                                    oid,
+                                    NULL);
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_array_denom_hash (
+  size_t num,
+  const struct TALER_DenominationHashP *denom_hs,
+  struct GNUNET_PQ_Context *db)
+{
+  Oid oid;
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_PQ_get_oid_by_name (db, "bytea", &oid));
+  return query_param_array_generic (num,
+                                    true,
+                                    denom_hs,
+                                    NULL,
+                                    sizeof(struct TALER_DenominationHashP),
+                                    TALER_PQ_array_of_denom_hash,
+                                    oid,
+                                    NULL);
+}
+
+
+struct GNUNET_PQ_QueryParam
+TALER_PQ_query_param_array_amount (
+  size_t num,
+  const struct TALER_Amount *amounts,
+  struct GNUNET_PQ_Context *db)
+{
+  Oid oid;
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_PQ_get_oid_by_name (db, "taler_amount", &oid));
+  return query_param_array_generic (
+    num,
+    true,
+    amounts,
+    NULL,
+    sizeof(struct TALER_PQ_AmountP),
+    TALER_PQ_array_of_amount,
+    oid,
+    db);
+}
+
+
+/* end of pq/pq_query_helper.c */
diff --git a/src/pq/pq_result_helper.c b/src/pq/pq_result_helper.c
new file mode 100644
index 0000000..95850bc
--- /dev/null
+++ b/src/pq/pq_result_helper.c
@@ -0,0 +1,1424 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2022 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file pq/pq_result_helper.c
+ * @brief functions to initialize parameter arrays
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "pq_common.h"
+#include "taler_pq_lib.h"
+
+
+/**
+ * Extract an amount from a tuple including the currency from a Postgres
+ * database @a result at row @a row.
+ *
+ * @param cls closure; not used
+ * @param result where to extract data from
+ * @param row row to extract data from
+ * @param fname name (or prefix) of the fields to extract from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ *   #GNUNET_YES if all results could be extracted
+ *   #GNUNET_NO if at least one result was NULL
+ *   #GNUNET_SYSERR if a result was invalid (non-existing field)
+ */
+static enum GNUNET_GenericReturnValue
+extract_amount_currency_tuple (void *cls,
+                               PGresult *result,
+                               int row,
+                               const char *fname,
+                               size_t *dst_size,
+                               void *dst)
+{
+  struct TALER_Amount *r_amount = dst;
+  int col;
+
+  (void) cls;
+  if (sizeof (struct TALER_Amount) != *dst_size)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* Set return value to invalid in case we don't finish */
+  memset (r_amount,
+          0,
+          sizeof (struct TALER_Amount));
+  col = PQfnumber (result,
+                   fname);
+  if (col < 0)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Field `%s' does not exist in result\n",
+                fname);
+    return GNUNET_SYSERR;
+  }
+  if (PQgetisnull (result,
+                   row,
+                   col))
+  {
+    return GNUNET_NO;
+  }
+
+  /* Parse the tuple */
+  {
+    struct TALER_PQ_AmountCurrencyP ap;
+    const char *in;
+    size_t size;
+
+    size = PQgetlength (result,
+                        row,
+                        col);
+    if ( (size >= sizeof (ap)) ||
+         (size <= sizeof (ap) - TALER_CURRENCY_LEN) )
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Incorrect size of binary field `%s' (got %zu, expected 
(%zu-%zu))\n",
+                  fname,
+                  size,
+                  sizeof (ap) - TALER_CURRENCY_LEN,
+                  sizeof (ap));
+      return GNUNET_SYSERR;
+    }
+
+    in = PQgetvalue (result,
+                     row,
+                     col);
+    memset (&ap.c,
+            0,
+            TALER_CURRENCY_LEN);
+    memcpy (&ap,
+            in,
+            size);
+    if (3 != ntohl (ap.cnt))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Incorrect number of elements in tuple-field `%s'\n",
+                  fname);
+      return GNUNET_SYSERR;
+    }
+    /* TODO[oec]: OID-checks? */
+
+    r_amount->value = GNUNET_ntohll (ap.v);
+    r_amount->fraction = ntohl (ap.f);
+    memcpy (r_amount->currency,
+            ap.c,
+            TALER_CURRENCY_LEN);
+    if ('\0' != r_amount->currency[TALER_CURRENCY_LEN - 1])
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Invalid currency (not 0-terminated) in tuple field `%s'\n",
+                  fname);
+      /* be sure nobody uses this by accident */
+      memset (r_amount,
+              0,
+              sizeof (struct TALER_Amount));
+      return GNUNET_SYSERR;
+    }
+  }
+
+  if (r_amount->value >= TALER_AMOUNT_MAX_VALUE)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Value in field `%s' exceeds legal range\n",
+                fname);
+    return GNUNET_SYSERR;
+  }
+  if (r_amount->fraction >= TALER_AMOUNT_FRAC_BASE)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Fraction in field `%s' exceeds legal range\n",
+                fname);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_amount_with_currency (const char *name,
+                                           struct TALER_Amount *amount)
+{
+  struct GNUNET_PQ_ResultSpec res = {
+    .conv = &extract_amount_currency_tuple,
+    .dst = (void *) amount,
+    .dst_size = sizeof (*amount),
+    .fname = name
+  };
+
+  return res;
+}
+
+
+/**
+ * Extract an amount from a tuple from a Postgres database @a result at row @a 
row.
+ *
+ * @param cls closure, a `const char *` giving the currency
+ * @param result where to extract data from
+ * @param row row to extract data from
+ * @param fname name (or prefix) of the fields to extract from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ *   #GNUNET_YES if all results could be extracted
+ *   #GNUNET_NO if at least one result was NULL
+ *   #GNUNET_SYSERR if a result was invalid (non-existing field)
+ */
+static enum GNUNET_GenericReturnValue
+extract_amount_tuple (void *cls,
+                      PGresult *result,
+                      int row,
+                      const char *fname,
+                      size_t *dst_size,
+                      void *dst)
+{
+  struct TALER_Amount *r_amount = dst;
+  const char *currency = cls;
+  int col;
+  size_t len;
+
+  if (sizeof (struct TALER_Amount) != *dst_size)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* Set return value to invalid in case we don't finish */
+  memset (r_amount,
+          0,
+          sizeof (struct TALER_Amount));
+  col = PQfnumber (result,
+                   fname);
+  if (col < 0)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Field `%s' does not exist in result\n",
+                fname);
+    return GNUNET_SYSERR;
+  }
+  if (PQgetisnull (result,
+                   row,
+                   col))
+  {
+    return GNUNET_NO;
+  }
+
+  /* Parse the tuple */
+  {
+    struct TALER_PQ_AmountP ap;
+    const char *in;
+    size_t size;
+
+    size = PQgetlength (result,
+                        row,
+                        col);
+    if (sizeof(ap) != size)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Incorrect size of binary field `%s' (got %zu, expected 
%zu)\n",
+                  fname,
+                  size,
+                  sizeof(ap));
+      return GNUNET_SYSERR;
+    }
+
+    in = PQgetvalue (result,
+                     row,
+                     col);
+    memcpy (&ap,
+            in,
+            size);
+    if (2 != ntohl (ap.cnt))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Incorrect number of elements in tuple-field `%s'\n",
+                  fname);
+      return GNUNET_SYSERR;
+    }
+    /* TODO[oec]: OID-checks? */
+
+    r_amount->value = GNUNET_ntohll (ap.v);
+    r_amount->fraction = ntohl (ap.f);
+  }
+
+  if (r_amount->value >= TALER_AMOUNT_MAX_VALUE)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Value in field `%s' exceeds legal range\n",
+                fname);
+    return GNUNET_SYSERR;
+  }
+  if (r_amount->fraction >= TALER_AMOUNT_FRAC_BASE)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Fraction in field `%s' exceeds legal range\n",
+                fname);
+    return GNUNET_SYSERR;
+  }
+
+  len = GNUNET_MIN (TALER_CURRENCY_LEN - 1,
+                    strlen (currency));
+
+  GNUNET_memcpy (r_amount->currency,
+                 currency,
+                 len);
+  return GNUNET_OK;
+}
+
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_amount (const char *name,
+                             const char *currency,
+                             struct TALER_Amount *amount)
+{
+  struct GNUNET_PQ_ResultSpec res = {
+    .conv = &extract_amount_tuple,
+    .cls = (void *) currency,
+    .dst = (void *) amount,
+    .dst_size = sizeof (*amount),
+    .fname = name
+  };
+
+  return res;
+}
+
+
+/**
+ * Extract data from a Postgres database @a result at row @a row.
+ *
+ * @param cls closure
+ * @param result where to extract data from
+ * @param row row to extract data from
+ * @param fname name (or prefix) of the fields to extract from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ *   #GNUNET_YES if all results could be extracted
+ *   #GNUNET_NO if at least one result was NULL
+ *   #GNUNET_SYSERR if a result was invalid (non-existing field)
+ */
+static enum GNUNET_GenericReturnValue
+extract_json (void *cls,
+              PGresult *result,
+              int row,
+              const char *fname,
+              size_t *dst_size,
+              void *dst)
+{
+  json_t **j_dst = dst;
+  const char *res;
+  int fnum;
+  json_error_t json_error;
+  size_t slen;
+
+  (void) cls;
+  (void) dst_size;
+  fnum = PQfnumber (result,
+                    fname);
+  if (fnum < 0)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Field `%s' does not exist in result\n",
+                fname);
+    return GNUNET_SYSERR;
+  }
+  if (PQgetisnull (result,
+                   row,
+                   fnum))
+    return GNUNET_NO;
+  slen = PQgetlength (result,
+                      row,
+                      fnum);
+  res = (const char *) PQgetvalue (result,
+                                   row,
+                                   fnum);
+  *j_dst = json_loadb (res,
+                       slen,
+                       JSON_REJECT_DUPLICATES,
+                       &json_error);
+  if (NULL == *j_dst)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to parse JSON result for field `%s': %s (%s)\n",
+                fname,
+                json_error.text,
+                json_error.source);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called to clean up memory allocated
+ * by a #GNUNET_PQ_ResultConverter.
+ *
+ * @param cls closure
+ * @param rd result data to clean up
+ */
+static void
+clean_json (void *cls,
+            void *rd)
+{
+  json_t **dst = rd;
+
+  (void) cls;
+  if (NULL != *dst)
+  {
+    json_decref (*dst);
+    *dst = NULL;
+  }
+}
+
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_json (const char *name,
+                           json_t **jp)
+{
+  struct GNUNET_PQ_ResultSpec res = {
+    .conv = &extract_json,
+    .cleaner = &clean_json,
+    .dst = (void *) jp,
+    .fname  = name
+  };
+
+  return res;
+}
+
+
+/**
+ * Extract data from a Postgres database @a result at row @a row.
+ *
+ * @param cls closure
+ * @param result where to extract data from
+ * @param row the row to extract data from
+ * @param fname name (or prefix) of the fields to extract from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ *   #GNUNET_YES if all results could be extracted
+ *   #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
+ */
+static enum GNUNET_GenericReturnValue
+extract_denom_pub (void *cls,
+                   PGresult *result,
+                   int row,
+                   const char *fname,
+                   size_t *dst_size,
+                   void *dst)
+{
+  struct TALER_DenominationPublicKey *pk = dst;
+  size_t len;
+  const char *res;
+  int fnum;
+  uint32_t be[2];
+
+  (void) cls;
+  (void) dst_size;
+  fnum = PQfnumber (result,
+                    fname);
+  if (fnum < 0)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (PQgetisnull (result,
+                   row,
+                   fnum))
+    return GNUNET_NO;
+
+  /* if a field is null, continue but
+   * remember that we now return a different result */
+  len = PQgetlength (result,
+                     row,
+                     fnum);
+  res = PQgetvalue (result,
+                    row,
+                    fnum);
+  if (len < sizeof (be))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_memcpy (be,
+                 res,
+                 sizeof (be));
+  res += sizeof (be);
+  len -= sizeof (be);
+  pk->cipher = ntohl (be[0]);
+  pk->age_mask.bits = ntohl (be[1]);
+  switch (pk->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    pk->details.rsa_public_key
+      = GNUNET_CRYPTO_rsa_public_key_decode (res,
+                                             len);
+    if (NULL == pk->details.rsa_public_key)
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+    return GNUNET_OK;
+  case TALER_DENOMINATION_CS:
+    if (sizeof (pk->details.cs_public_key) != len)
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+    GNUNET_memcpy (&pk->details.cs_public_key,
+                   res,
+                   len);
+    return GNUNET_OK;
+  default:
+    GNUNET_break (0);
+  }
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Function called to clean up memory allocated
+ * by a #GNUNET_PQ_ResultConverter.
+ *
+ * @param cls closure
+ * @param rd result data to clean up
+ */
+static void
+clean_denom_pub (void *cls,
+                 void *rd)
+{
+  struct TALER_DenominationPublicKey *denom_pub = rd;
+
+  (void) cls;
+  TALER_denom_pub_free (denom_pub);
+}
+
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_denom_pub (const char *name,
+                                struct TALER_DenominationPublicKey *denom_pub)
+{
+  struct GNUNET_PQ_ResultSpec res = {
+    .conv = &extract_denom_pub,
+    .cleaner = &clean_denom_pub,
+    .dst = (void *) denom_pub,
+    .fname = name
+  };
+
+  return res;
+}
+
+
+/**
+ * Extract data from a Postgres database @a result at row @a row.
+ *
+ * @param cls closure
+ * @param result where to extract data from
+ * @param row the row to extract data from
+ * @param fname name (or prefix) of the fields to extract from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ *   #GNUNET_YES if all results could be extracted
+ *   #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
+ */
+static enum GNUNET_GenericReturnValue
+extract_denom_sig (void *cls,
+                   PGresult *result,
+                   int row,
+                   const char *fname,
+                   size_t *dst_size,
+                   void *dst)
+{
+  struct TALER_DenominationSignature *sig = dst;
+  size_t len;
+  const char *res;
+  int fnum;
+  uint32_t be[2];
+
+  (void) cls;
+  (void) dst_size;
+  fnum = PQfnumber (result,
+                    fname);
+  if (fnum < 0)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (PQgetisnull (result,
+                   row,
+                   fnum))
+    return GNUNET_NO;
+
+  /* if a field is null, continue but
+   * remember that we now return a different result */
+  len = PQgetlength (result,
+                     row,
+                     fnum);
+  res = PQgetvalue (result,
+                    row,
+                    fnum);
+  if (len < sizeof (be))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_memcpy (&be,
+                 res,
+                 sizeof (be));
+  if (0x00 != ntohl (be[1]))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  res += sizeof (be);
+  len -= sizeof (be);
+  sig->cipher = ntohl (be[0]);
+  switch (sig->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    sig->details.rsa_signature
+      = GNUNET_CRYPTO_rsa_signature_decode (res,
+                                            len);
+    if (NULL == sig->details.rsa_signature)
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+    return GNUNET_OK;
+  case TALER_DENOMINATION_CS:
+    if (sizeof (sig->details.cs_signature) != len)
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+    GNUNET_memcpy (&sig->details.cs_signature,
+                   res,
+                   len);
+    return GNUNET_OK;
+  default:
+    GNUNET_break (0);
+  }
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Function called to clean up memory allocated
+ * by a #GNUNET_PQ_ResultConverter.
+ *
+ * @param cls closure
+ * @param rd result data to clean up
+ */
+static void
+clean_denom_sig (void *cls,
+                 void *rd)
+{
+  struct TALER_DenominationSignature *denom_sig = rd;
+
+  (void) cls;
+  TALER_denom_sig_free (denom_sig);
+}
+
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_denom_sig (const char *name,
+                                struct TALER_DenominationSignature *denom_sig)
+{
+  struct GNUNET_PQ_ResultSpec res = {
+    .conv = &extract_denom_sig,
+    .cleaner = &clean_denom_sig,
+    .dst = (void *) denom_sig,
+    .fname = name
+  };
+
+  return res;
+}
+
+
+/**
+ * Extract data from a Postgres database @a result at row @a row.
+ *
+ * @param cls closure
+ * @param result where to extract data from
+ * @param row the row to extract data from
+ * @param fname name (or prefix) of the fields to extract from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ *   #GNUNET_YES if all results could be extracted
+ *   #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
+ */
+static enum GNUNET_GenericReturnValue
+extract_blinded_denom_sig (void *cls,
+                           PGresult *result,
+                           int row,
+                           const char *fname,
+                           size_t *dst_size,
+                           void *dst)
+{
+  struct TALER_BlindedDenominationSignature *sig = dst;
+  size_t len;
+  const char *res;
+  int fnum;
+  uint32_t be[2];
+
+  (void) cls;
+  (void) dst_size;
+  fnum = PQfnumber (result,
+                    fname);
+  if (fnum < 0)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (PQgetisnull (result,
+                   row,
+                   fnum))
+    return GNUNET_NO;
+
+  /* if a field is null, continue but
+   * remember that we now return a different result */
+  len = PQgetlength (result,
+                     row,
+                     fnum);
+  res = PQgetvalue (result,
+                    row,
+                    fnum);
+  if (len < sizeof (be))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_memcpy (&be,
+                 res,
+                 sizeof (be));
+  if (0x01 != ntohl (be[1])) /* magic marker: blinded */
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  res += sizeof (be);
+  len -= sizeof (be);
+  sig->cipher = ntohl (be[0]);
+  switch (sig->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    sig->details.blinded_rsa_signature
+      = GNUNET_CRYPTO_rsa_signature_decode (res,
+                                            len);
+    if (NULL == sig->details.blinded_rsa_signature)
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+    return GNUNET_OK;
+  case TALER_DENOMINATION_CS:
+    if (sizeof (sig->details.blinded_cs_answer) != len)
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+    GNUNET_memcpy (&sig->details.blinded_cs_answer,
+                   res,
+                   len);
+    return GNUNET_OK;
+  default:
+    GNUNET_break (0);
+  }
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Function called to clean up memory allocated
+ * by a #GNUNET_PQ_ResultConverter.
+ *
+ * @param cls closure
+ * @param rd result data to clean up
+ */
+static void
+clean_blinded_denom_sig (void *cls,
+                         void *rd)
+{
+  struct TALER_BlindedDenominationSignature *denom_sig = rd;
+
+  (void) cls;
+  TALER_blinded_denom_sig_free (denom_sig);
+}
+
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_blinded_denom_sig (
+  const char *name,
+  struct TALER_BlindedDenominationSignature *denom_sig)
+{
+  struct GNUNET_PQ_ResultSpec res = {
+    .conv = &extract_blinded_denom_sig,
+    .cleaner = &clean_blinded_denom_sig,
+    .dst = (void *) denom_sig,
+    .fname = name
+  };
+
+  return res;
+}
+
+
+/**
+ * Extract data from a Postgres database @a result at row @a row.
+ *
+ * @param cls closure
+ * @param result where to extract data from
+ * @param row the row to extract data from
+ * @param fname name (or prefix) of the fields to extract from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ *   #GNUNET_YES if all results could be extracted
+ *   #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
+ */
+static enum GNUNET_GenericReturnValue
+extract_blinded_planchet (void *cls,
+                          PGresult *result,
+                          int row,
+                          const char *fname,
+                          size_t *dst_size,
+                          void *dst)
+{
+  struct TALER_BlindedPlanchet *bp = dst;
+  size_t len;
+  const char *res;
+  int fnum;
+  uint32_t be[2];
+
+  (void) cls;
+  (void) dst_size;
+  fnum = PQfnumber (result,
+                    fname);
+  if (fnum < 0)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (PQgetisnull (result,
+                   row,
+                   fnum))
+    return GNUNET_NO;
+
+  /* if a field is null, continue but
+   * remember that we now return a different result */
+  len = PQgetlength (result,
+                     row,
+                     fnum);
+  res = PQgetvalue (result,
+                    row,
+                    fnum);
+  if (len < sizeof (be))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_memcpy (&be,
+                 res,
+                 sizeof (be));
+  if (0x0100 != ntohl (be[1])) /* magic marker: blinded */
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  res += sizeof (be);
+  len -= sizeof (be);
+  bp->cipher = ntohl (be[0]);
+  switch (bp->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    bp->details.rsa_blinded_planchet.blinded_msg_size
+      = len;
+    bp->details.rsa_blinded_planchet.blinded_msg
+      = GNUNET_memdup (res,
+                       len);
+    return GNUNET_OK;
+  case TALER_DENOMINATION_CS:
+    if (sizeof (bp->details.cs_blinded_planchet) != len)
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+    GNUNET_memcpy (&bp->details.cs_blinded_planchet,
+                   res,
+                   len);
+    return GNUNET_OK;
+  default:
+    GNUNET_break (0);
+  }
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Function called to clean up memory allocated
+ * by a #GNUNET_PQ_ResultConverter.
+ *
+ * @param cls closure
+ * @param rd result data to clean up
+ */
+static void
+clean_blinded_planchet (void *cls,
+                        void *rd)
+{
+  struct TALER_BlindedPlanchet *bp = rd;
+
+  (void) cls;
+  TALER_blinded_planchet_free (bp);
+}
+
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_blinded_planchet (
+  const char *name,
+  struct TALER_BlindedPlanchet *bp)
+{
+  struct GNUNET_PQ_ResultSpec res = {
+    .conv = &extract_blinded_planchet,
+    .cleaner = &clean_blinded_planchet,
+    .dst = (void *) bp,
+    .fname = name
+  };
+
+  return res;
+}
+
+
+/**
+ * Extract data from a Postgres database @a result at row @a row.
+ *
+ * @param cls closure
+ * @param result where to extract data from
+ * @param row row to extract data from
+ * @param fname name (or prefix) of the fields to extract from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ *   #GNUNET_YES if all results could be extracted
+ *   #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
+ */
+static enum GNUNET_GenericReturnValue
+extract_exchange_withdraw_values (void *cls,
+                                  PGresult *result,
+                                  int row,
+                                  const char *fname,
+                                  size_t *dst_size,
+                                  void *dst)
+{
+  struct TALER_ExchangeWithdrawValues *alg_values = dst;
+  size_t len;
+  const char *res;
+  int fnum;
+  uint32_t be[2];
+
+  (void) cls;
+  (void) dst_size;
+  fnum = PQfnumber (result,
+                    fname);
+  if (fnum < 0)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (PQgetisnull (result,
+                   row,
+                   fnum))
+    return GNUNET_NO;
+
+  /* if a field is null, continue but
+   * remember that we now return a different result */
+  len = PQgetlength (result,
+                     row,
+                     fnum);
+  res = PQgetvalue (result,
+                    row,
+                    fnum);
+  if (len < sizeof (be))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_memcpy (&be,
+                 res,
+                 sizeof (be));
+  if (0x010000 != ntohl (be[1])) /* magic marker: EWV */
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  res += sizeof (be);
+  len -= sizeof (be);
+  alg_values->cipher = ntohl (be[0]);
+  switch (alg_values->cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    if (0 != len)
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+    return GNUNET_OK;
+  case TALER_DENOMINATION_CS:
+    if (sizeof (struct TALER_DenominationCSPublicRPairP) != len)
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+    GNUNET_memcpy (&alg_values->details.cs_values,
+                   res,
+                   len);
+    return GNUNET_OK;
+  default:
+    GNUNET_break (0);
+  }
+  return GNUNET_SYSERR;
+}
+
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_exchange_withdraw_values (
+  const char *name,
+  struct TALER_ExchangeWithdrawValues *ewv)
+{
+  struct GNUNET_PQ_ResultSpec res = {
+    .conv = &extract_exchange_withdraw_values,
+    .dst = (void *) ewv,
+    .fname = name
+  };
+
+  return res;
+}
+
+
+/**
+ * Closure for the array result specifications.  Contains type information
+ * for the generic parser extract_array_generic and out-pointers for the 
results.
+ */
+struct ArrayResultCls
+{
+  /* Oid of the expected type, must match the oid in the header of the 
PQResult struct */
+  Oid oid;
+
+  /* Target type */
+  enum TALER_PQ_ArrayType typ;
+
+  /* If not 0, defines the expected size of each entry */
+  size_t same_size;
+
+  /* Out-pointer to write the number of elements in the array */
+  size_t *num;
+
+  /* Out-pointer. If @a typ is TALER_PQ_array_of_byte and @a same_size is 0,
+   * allocate and put the array of @a num sizes here. NULL otherwise */
+  size_t **sizes;
+
+  /* DB_connection, needed for OID-lookup for composite types */
+  const struct GNUNET_PQ_Context *db;
+
+  /* Currency information for amount composites */
+  char currency[TALER_CURRENCY_LEN];
+};
+
+/**
+ * Extract data from a Postgres database @a result as array of a specific type
+ * from row @a row.  The type information and optionally additional
+ * out-parameters are given in @a cls which is of type array_result_cls.
+ *
+ * @param cls closure of type array_result_cls
+ * @param result where to extract data from
+ * @param row row to extract data from
+ * @param fname name (or prefix) of the fields to extract from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ *   #GNUNET_YES if all results could be extracted
+ *   #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
+ */
+static enum GNUNET_GenericReturnValue
+extract_array_generic (
+  void *cls,
+  PGresult *result,
+  int row,
+  const char *fname,
+  size_t *dst_size,
+  void *dst)
+{
+  const struct ArrayResultCls *info = cls;
+  int data_sz;
+  char *data;
+  void *out = NULL;
+  struct GNUNET_PQ_ArrayHeader_P header;
+  int col_num;
+
+  GNUNET_assert (NULL != dst);
+  *((void **) dst) = NULL;
+
+  #define FAIL_IF(cond) \
+          do { \
+            if ((cond)) \
+            { \
+              GNUNET_break (! (cond)); \
+              goto FAIL; \
+            } \
+          } while (0)
+
+  col_num = PQfnumber (result, fname);
+  FAIL_IF (0 > col_num);
+
+  data_sz = PQgetlength (result, row, col_num);
+  FAIL_IF (0 > data_sz);
+  FAIL_IF (sizeof(header) > (size_t) data_sz);
+
+  data = PQgetvalue (result, row, col_num);
+  FAIL_IF (NULL == data);
+
+  {
+    struct GNUNET_PQ_ArrayHeader_P *h =
+      (struct GNUNET_PQ_ArrayHeader_P *) data;
+
+    header.ndim = ntohl (h->ndim);
+    header.has_null = ntohl (h->has_null);
+    header.oid = ntohl (h->oid);
+    header.dim = ntohl (h->dim);
+    header.lbound = ntohl (h->lbound);
+
+    FAIL_IF (1 != header.ndim);
+    FAIL_IF (INT_MAX <= header.dim);
+    FAIL_IF (0 != header.has_null);
+    FAIL_IF (1 != header.lbound);
+    FAIL_IF (info->oid != header.oid);
+  }
+
+  if (NULL != info->num)
+    *info->num = header.dim;
+
+  {
+    char *in = data + sizeof(header);
+
+    switch (info->typ)
+    {
+    case TALER_PQ_array_of_amount:
+      {
+        struct TALER_Amount *amounts;
+        if (NULL != dst_size)
+          *dst_size = sizeof(struct TALER_Amount) * (header.dim);
+
+        amounts = GNUNET_new_array (header.dim, struct TALER_Amount);
+        *((void **) dst) = amounts;
+
+        for (uint32_t i = 0; i < header.dim; i++)
+        {
+          struct TALER_PQ_AmountP ap;
+          struct TALER_Amount *amount = &amounts[i];
+          uint32_t val;
+          size_t sz;
+
+          GNUNET_memcpy (&val,
+                         in,
+                         sizeof(val));
+          sz =  ntohl (val);
+          in += sizeof(val);
+
+          /* total size for this array-entry */
+          FAIL_IF (sizeof(ap) > sz);
+
+          GNUNET_memcpy (&ap,
+                         in,
+                         sz);
+          FAIL_IF (2 != ntohl (ap.cnt));
+
+          amount->value = GNUNET_ntohll (ap.v);
+          amount->fraction = ntohl (ap.f);
+          GNUNET_memcpy (amount->currency,
+                         info->currency,
+                         TALER_CURRENCY_LEN);
+
+          in += sizeof(struct TALER_PQ_AmountP);
+        }
+        return GNUNET_OK;
+      }
+    case TALER_PQ_array_of_denom_hash:
+      if (NULL != dst_size)
+        *dst_size = sizeof(struct TALER_DenominationHashP) * (header.dim);
+      out = GNUNET_new_array (header.dim, struct TALER_DenominationHashP);
+      *((void **) dst) = out;
+      for (uint32_t i = 0; i < header.dim; i++)
+      {
+        uint32_t val;
+        size_t sz;
+
+        GNUNET_memcpy (&val,
+                       in,
+                       sizeof(val));
+        sz =  ntohl (val);
+        FAIL_IF (sz != sizeof(struct TALER_DenominationHashP));
+        in += sizeof(uint32_t);
+        *(struct TALER_DenominationHashP *) out =
+          *(struct TALER_DenominationHashP *) in;
+        in += sz;
+        out += sz;
+      }
+      return GNUNET_OK;
+
+    case TALER_PQ_array_of_blinded_coin_hash:
+      if (NULL != dst_size)
+        *dst_size = sizeof(struct TALER_BlindedCoinHashP) * (header.dim);
+      out = GNUNET_new_array (header.dim, struct TALER_BlindedCoinHashP);
+      *((void **) dst) = out;
+      for (uint32_t i = 0; i < header.dim; i++)
+      {
+        uint32_t val;
+        size_t sz;
+
+        GNUNET_memcpy (&val,
+                       in,
+                       sizeof(val));
+        sz =  ntohl (val);
+        FAIL_IF (sz != sizeof(struct TALER_BlindedCoinHashP));
+        in += sizeof(uint32_t);
+        *(struct TALER_BlindedCoinHashP *) out =
+          *(struct TALER_BlindedCoinHashP *) in;
+        in += sz;
+        out += sz;
+      }
+      return GNUNET_OK;
+
+    case TALER_PQ_array_of_blinded_denom_sig:
+      {
+        struct TALER_BlindedDenominationSignature *denom_sigs;
+        if (0 == header.dim)
+        {
+          if (NULL != dst_size)
+            *dst_size = 0;
+          break;
+        }
+
+        denom_sigs = GNUNET_new_array (header.dim,
+                                       struct 
TALER_BlindedDenominationSignature);
+        *((void **) dst) = denom_sigs;
+
+        /* copy data */
+        for (uint32_t i = 0; i < header.dim; i++)
+        {
+          struct TALER_BlindedDenominationSignature *denom_sig = 
&denom_sigs[i];
+          uint32_t be[2];
+          uint32_t val;
+          size_t sz;
+
+          GNUNET_memcpy (&val,
+                         in,
+                         sizeof(val));
+          sz = ntohl (val);
+          FAIL_IF (sizeof(be) > sz);
+
+          in += sizeof(val);
+          GNUNET_memcpy (&be,
+                         in,
+                         sizeof(be));
+          FAIL_IF (0x01 != ntohl (be[1]));  /* magic marker: blinded */
+
+          in += sizeof(be);
+          sz -= sizeof(be);
+
+          denom_sig->cipher = ntohl (be[0]);
+          switch (denom_sig->cipher)
+          {
+          case TALER_DENOMINATION_RSA:
+            denom_sig->details.blinded_rsa_signature =
+              GNUNET_CRYPTO_rsa_signature_decode (in,
+                                                  sz);
+            FAIL_IF (NULL == denom_sig->details.blinded_rsa_signature);
+            break;
+
+          case TALER_DENOMINATION_CS:
+            FAIL_IF (sizeof(denom_sig->details.blinded_cs_answer) != sz);
+            GNUNET_memcpy (&denom_sig->details.blinded_cs_answer,
+                           in,
+                           sz);
+            break;
+
+          default:
+            FAIL_IF (true);
+          }
+
+          in += sz;
+        }
+        return GNUNET_OK;
+      }
+    default:
+      FAIL_IF (true);
+    }
+  }
+
+FAIL:
+  GNUNET_free (*(void **) dst);
+  return GNUNET_SYSERR;
+  #undef FAIL_IF
+
+}
+
+
+/**
+ * Cleanup of the data and closure of an array spec.
+ */
+static void
+array_cleanup (void *cls,
+               void *rd)
+{
+
+  struct ArrayResultCls *info = cls;
+  void **dst = rd;
+
+  if ((0 == info->same_size) &&
+      (NULL != info->sizes))
+    GNUNET_free (*(info->sizes));
+
+  GNUNET_free (cls);
+  GNUNET_free (*dst);
+  *dst = NULL;
+}
+
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_array_blinded_denom_sig (
+  struct GNUNET_PQ_Context *db,
+  const char *name,
+  size_t *num,
+  struct TALER_BlindedDenominationSignature **denom_sigs)
+{
+  struct ArrayResultCls *info = GNUNET_new (struct ArrayResultCls);
+
+  info->num = num;
+  info->typ = TALER_PQ_array_of_blinded_denom_sig;
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_PQ_get_oid_by_name (db,
+                                            "bytea",
+                                            &info->oid));
+
+  struct GNUNET_PQ_ResultSpec res = {
+    .conv = extract_array_generic,
+    .cleaner = array_cleanup,
+    .dst = (void *) denom_sigs,
+    .fname = name,
+    .cls = info
+  };
+  return res;
+
+};
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_array_blinded_coin_hash (
+  struct GNUNET_PQ_Context *db,
+  const char *name,
+  size_t *num,
+  struct TALER_BlindedCoinHashP **h_coin_evs)
+{
+  struct ArrayResultCls *info = GNUNET_new (struct ArrayResultCls);
+
+  info->num = num;
+  info->typ = TALER_PQ_array_of_blinded_coin_hash;
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_PQ_get_oid_by_name (db,
+                                            "bytea",
+                                            &info->oid));
+
+  struct GNUNET_PQ_ResultSpec res = {
+    .conv = extract_array_generic,
+    .cleaner = array_cleanup,
+    .dst = (void *) h_coin_evs,
+    .fname = name,
+    .cls = info
+  };
+  return res;
+
+};
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_array_denom_hash (
+  struct GNUNET_PQ_Context *db,
+  const char *name,
+  size_t *num,
+  struct TALER_DenominationHashP **denom_hs)
+{
+  struct ArrayResultCls *info = GNUNET_new (struct ArrayResultCls);
+
+  info->num = num;
+  info->typ = TALER_PQ_array_of_denom_hash;
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_PQ_get_oid_by_name (db,
+                                            "bytea",
+                                            &info->oid));
+
+  struct GNUNET_PQ_ResultSpec res = {
+    .conv = extract_array_generic,
+    .cleaner = array_cleanup,
+    .dst = (void *) denom_hs,
+    .fname = name,
+    .cls = info
+  };
+  return res;
+
+};
+
+struct GNUNET_PQ_ResultSpec
+TALER_PQ_result_spec_array_amount (
+  struct GNUNET_PQ_Context *db,
+  const char *name,
+  const char *currency,
+  size_t *num,
+  struct TALER_Amount **amounts)
+{
+  struct ArrayResultCls *info = GNUNET_new (struct ArrayResultCls);
+
+  info->num = num;
+  info->typ = TALER_PQ_array_of_amount;
+  info->db = db;
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_PQ_get_oid_by_name (db,
+                                            "taler_amount",
+                                            &info->oid));
+
+  {
+    size_t clen = GNUNET_MIN (TALER_CURRENCY_LEN - 1,
+                              strlen (currency));
+    GNUNET_memcpy (&info->currency,
+                   currency,
+                   clen);
+  }
+
+  struct GNUNET_PQ_ResultSpec res = {
+    .conv = extract_array_generic,
+    .cleaner = array_cleanup,
+    .dst = (void *) amounts,
+    .fname = name,
+    .cls = info,
+  };
+  return res;
+
+
+}
+
+
+/* end of pq_result_helper.c */
diff --git a/src/pq/test_pq.c b/src/pq/test_pq.c
new file mode 100644
index 0000000..237c8a9
--- /dev/null
+++ b/src/pq/test_pq.c
@@ -0,0 +1,250 @@
+/*
+  This file is part of TALER
+  (C) 2015, 2016, 2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file pq/test_pq.c
+ * @brief Tests for Postgres convenience API
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_pq_lib.h"
+
+
+/**
+ * Setup prepared statements.
+ *
+ * @param db database handle to initialize
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
+ */
+static enum GNUNET_GenericReturnValue
+postgres_prepare (struct GNUNET_PQ_Context *db)
+{
+  struct GNUNET_PQ_PreparedStatement ps[] = {
+    GNUNET_PQ_make_prepare ("test_insert",
+                            "INSERT INTO test_pq ("
+                            " tamount"
+                            ",json"
+                            ",aamount"
+                            ",tamountc"
+                            ") VALUES "
+                            "($1, $2, $3, $4);"),
+    GNUNET_PQ_make_prepare ("test_select",
+                            "SELECT"
+                            " tamount"
+                            ",json"
+                            ",aamount"
+                            ",tamountc"
+                            " FROM test_pq;"),
+    GNUNET_PQ_PREPARED_STATEMENT_END
+  };
+
+  return GNUNET_PQ_prepare_statements (db,
+                                       ps);
+}
+
+
+/**
+ * Run actual test queries.
+ *
+ * @return 0 on success
+ */
+static int
+run_queries (struct GNUNET_PQ_Context *conn)
+{
+  struct TALER_Amount tamount;
+  struct TALER_Amount aamount[3];
+  struct TALER_Amount tamountc;
+  json_t *json;
+
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:5.3",
+                                         &aamount[0]));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:6.4",
+                                         &aamount[1]));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:7.5",
+                                         &aamount[2]));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:7.7",
+                                         &tamount));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("FOO:8.7",
+                                         &tamountc));
+  json = json_object ();
+  GNUNET_assert (NULL != json);
+  GNUNET_assert (0 ==
+                 json_object_set_new (json,
+                                      "foo",
+                                      json_integer (42)));
+  {
+    struct GNUNET_PQ_QueryParam params_insert[] = {
+      TALER_PQ_query_param_amount (conn,
+                                   &tamount),
+      TALER_PQ_query_param_json (json),
+      TALER_PQ_query_param_array_amount (3,
+                                         aamount,
+                                         conn),
+      TALER_PQ_query_param_amount_with_currency (conn,
+                                                 &tamountc),
+      GNUNET_PQ_query_param_end
+    };
+    PGresult *result;
+
+    result = GNUNET_PQ_exec_prepared (conn,
+                                      "test_insert",
+                                      params_insert);
+    if (PGRES_COMMAND_OK != PQresultStatus (result))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Database failure: %s\n",
+                  PQresultErrorMessage (result));
+      PQclear (result);
+      return 1;
+    }
+    PQclear (result);
+    json_decref (json);
+  }
+  {
+    struct TALER_Amount tamount2;
+    struct TALER_Amount tamountc2;
+    struct TALER_Amount *pamount;
+    size_t npamount;
+    json_t *json2;
+    struct GNUNET_PQ_QueryParam params_select[] = {
+      GNUNET_PQ_query_param_end
+    };
+    struct GNUNET_PQ_ResultSpec results_select[] = {
+      TALER_PQ_result_spec_amount ("tamount",
+                                   "EUR",
+                                   &tamount2),
+      TALER_PQ_result_spec_json ("json",
+                                 &json2),
+      TALER_PQ_result_spec_array_amount (conn,
+                                         "aamount",
+                                         "EUR",
+                                         &npamount,
+                                         &pamount),
+      TALER_PQ_result_spec_amount_with_currency ("tamountc",
+                                                 &tamountc2),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (1 !=
+        GNUNET_PQ_eval_prepared_singleton_select (conn,
+                                                  "test_select",
+                                                  params_select,
+                                                  results_select))
+    {
+      GNUNET_break (0);
+      return 1;
+    }
+    GNUNET_break (0 ==
+                  TALER_amount_cmp (&tamount,
+                                    &tamount2));
+    GNUNET_break (42 ==
+                  json_integer_value (json_object_get (json2,
+                                                       "foo")));
+    GNUNET_break (3 == npamount);
+    for (size_t i = 0; i < 3; i++)
+    {
+      GNUNET_break (0 ==
+                    TALER_amount_cmp (&aamount[i],
+                                      &pamount[i]));
+    }
+    GNUNET_break (0 ==
+                  TALER_amount_cmp (&tamountc,
+                                    &tamountc2));
+    GNUNET_PQ_cleanup_result (results_select);
+  }
+  return 0;
+}
+
+
+int
+main (int argc,
+      const char *const argv[])
+{
+  struct GNUNET_PQ_ExecuteStatement es[] = {
+    GNUNET_PQ_make_execute ("DO $$ "
+                            " BEGIN"
+                            " CREATE TYPE taler_amount AS"
+                            "   (val INT8, frac INT4);"
+                            " EXCEPTION"
+                            "   WHEN duplicate_object THEN null;"
+                            " END "
+                            "$$;"),
+    GNUNET_PQ_make_execute ("DO $$ "
+                            " BEGIN"
+                            " CREATE TYPE taler_amount_currency AS"
+                            "   (val INT8, frac INT4, curr VARCHAR(12));"
+                            " EXCEPTION"
+                            "   WHEN duplicate_object THEN null;"
+                            " END "
+                            "$$;"),
+    GNUNET_PQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS test_pq ("
+                            " tamount taler_amount NOT NULL"
+                            ",json VARCHAR NOT NULL"
+                            ",aamount taler_amount[]"
+                            ",tamountc taler_amount_currency"
+                            ")"),
+    GNUNET_PQ_EXECUTE_STATEMENT_END
+  };
+  struct GNUNET_PQ_Context *conn;
+  int ret;
+
+  (void) argc;
+  (void) argv;
+  GNUNET_log_setup ("test-pq",
+                    "WARNING",
+                    NULL);
+  conn = GNUNET_PQ_connect ("postgres:///talercheck",
+                            NULL,
+                            es,
+                            NULL);
+  if (NULL == conn)
+    return 77;
+  if (GNUNET_OK !=
+      postgres_prepare (conn))
+  {
+    GNUNET_break (0);
+    GNUNET_PQ_disconnect (conn);
+    return 1;
+  }
+
+  ret = run_queries (conn);
+  {
+    struct GNUNET_PQ_ExecuteStatement ds[] = {
+      GNUNET_PQ_make_execute ("DROP TABLE test_pq"),
+      GNUNET_PQ_EXECUTE_STATEMENT_END
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_exec_statements (conn,
+                                   ds))
+    {
+      fprintf (stderr,
+               "Failed to drop table\n");
+      GNUNET_PQ_disconnect (conn);
+      return 1;
+    }
+  }
+  GNUNET_PQ_disconnect (conn);
+  return ret;
+}
+
+
+/* end of test_pq.c */
diff --git a/src/sq/Makefile.am b/src/sq/Makefile.am
new file mode 100644
index 0000000..f27dec3
--- /dev/null
+++ b/src/sq/Makefile.am
@@ -0,0 +1,40 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include $(LIBGCRYPT_CFLAGS) 
$(SQLITE_CPPFLAGS)
+
+if USE_COVERAGE
+  AM_CFLAGS = --coverage -O0
+  XLIB = -lgcov
+endif
+
+lib_LTLIBRARIES = \
+  libtalersq.la
+
+libtalersq_la_SOURCES = \
+  sq_query_helper.c \
+  sq_result_helper.c
+libtalersq_la_LIBADD = \
+  $(top_builddir)/src/util/libtalerutil.la  \
+  -lgnunetutil -ljansson \
+  -lsqlite3 \
+  $(XLIB)
+libtalersq_la_LDFLAGS = \
+  $(SQLITE_LDFLAGS) \
+  -version-info 0:0:0 \
+  -no-undefined
+
+check_PROGRAMS= \
+ test_sq
+
+TESTS = \
+ $(check_PROGRAMS)
+
+test_sq_SOURCES = \
+  test_sq.c
+test_sq_LDADD = \
+  libtalersq.la \
+  $(top_builddir)/src/util/libtalerutil.la  \
+  -lgnunetsq \
+  -lgnunetutil \
+  -ljansson \
+  -lsqlite3 \
+  $(XLIB)
diff --git a/src/sq/sq_query_helper.c b/src/sq/sq_query_helper.c
new file mode 100644
index 0000000..711e638
--- /dev/null
+++ b/src/sq/sq_query_helper.c
@@ -0,0 +1,175 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file sq/sq_query_helper.c
+ * @brief helper functions for Taler-specific SQLite3 interactions
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include <sqlite3.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_sq_lib.h>
+#include "taler_sq_lib.h"
+
+
+/**
+ * Function called to convert input argument into SQL parameters.
+ *
+ * @param cls closure
+ * @param data pointer to input argument, here a `struct TALER_Amount`
+ * @param data_len number of bytes in @a data (if applicable)
+ * @param stmt sqlite statement to parameters for
+ * @param off offset of the argument to bind in @a stmt, numbered from 1,
+ *            so immediately suitable for passing to `sqlite3_bind`-functions.
+ * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+qconv_amount (void *cls,
+              const void *data,
+              size_t data_len,
+              sqlite3_stmt *stmt,
+              unsigned int off)
+{
+  const struct TALER_Amount *amount = data;
+
+  (void) cls;
+  GNUNET_assert (sizeof (struct TALER_Amount) == data_len);
+  if (SQLITE_OK != sqlite3_bind_int64 (stmt,
+                                       (int) off,
+                                       (sqlite3_int64) amount->value))
+    return GNUNET_SYSERR;
+  if (SQLITE_OK != sqlite3_bind_int64 (stmt,
+                                       (int) off + 1,
+                                       (sqlite3_int64) amount->fraction))
+    return GNUNET_SYSERR;
+  return GNUNET_OK;
+}
+
+
+struct GNUNET_SQ_QueryParam
+TALER_SQ_query_param_amount (const struct TALER_Amount *x)
+{
+  struct GNUNET_SQ_QueryParam res = {
+    .conv = &qconv_amount,
+    .data = x,
+    .size = sizeof (*x),
+    .num_params = 2
+  };
+
+  return res;
+}
+
+
+/**
+ * Function called to convert input argument into SQL parameters.
+ *
+ * @param cls closure
+ * @param data pointer to input argument, here a `struct TALER_AmountNBO`
+ * @param data_len number of bytes in @a data (if applicable)
+ * @param stmt sqlite statement to parameters for
+ * @param off offset of the argument to bind in @a stmt, numbered from 1,
+ *            so immediately suitable for passing to `sqlite3_bind`-functions.
+ * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+qconv_amount_nbo (void *cls,
+                  const void *data,
+                  size_t data_len,
+                  sqlite3_stmt *stmt,
+                  unsigned int off)
+{
+  const struct TALER_AmountNBO *amount = data;
+  struct TALER_Amount amount_hbo;
+
+  (void) cls;
+  (void) data_len;
+  TALER_amount_ntoh (&amount_hbo,
+                     amount);
+  return qconv_amount (cls,
+                       &amount_hbo,
+                       sizeof (struct TALER_Amount),
+                       stmt,
+                       off);
+}
+
+
+struct GNUNET_SQ_QueryParam
+TALER_SQ_query_param_amount_nbo (const struct TALER_AmountNBO *x)
+{
+  struct GNUNET_SQ_QueryParam res = {
+    .conv = &qconv_amount_nbo,
+    .data = x,
+    .size = sizeof (*x),
+    .num_params = 2
+  };
+
+  return res;
+}
+
+
+/**
+ * Function called to convert input argument into SQL parameters.
+ *
+ * @param cls closure
+ * @param data pointer to input argument, here a `struct TALER_Amount`
+ * @param data_len number of bytes in @a data (if applicable)
+ * @param stmt sqlite statement to parameters for
+ * @param off offset of the argument to bind in @a stmt, numbered from 1,
+ *            so immediately suitable for passing to `sqlite3_bind`-functions.
+ * @return #GNUNET_SYSERR on error, #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+qconv_json (void *cls,
+            const void *data,
+            size_t data_len,
+            sqlite3_stmt *stmt,
+            unsigned int off)
+{
+  const json_t *json = data;
+  char *str;
+
+  (void) cls;
+  (void) data_len;
+  str = json_dumps (json, JSON_COMPACT);
+  if (NULL == str)
+    return GNUNET_SYSERR;
+
+  if (SQLITE_OK != sqlite3_bind_text (stmt,
+                                      (int) off,
+                                      str,
+                                      strlen (str),
+                                      SQLITE_TRANSIENT))
+    return GNUNET_SYSERR;
+  GNUNET_free (str);
+  return GNUNET_OK;
+}
+
+
+struct GNUNET_SQ_QueryParam
+TALER_SQ_query_param_json (const json_t *x)
+{
+  struct GNUNET_SQ_QueryParam res = {
+    .conv = &qconv_json,
+    .data = x,
+    .size = sizeof (*x),
+    .num_params = 1
+  };
+
+  return res;
+}
+
+
+/* end of sq/sq_query_helper.c */
diff --git a/src/sq/sq_result_helper.c b/src/sq/sq_result_helper.c
new file mode 100644
index 0000000..9d80837
--- /dev/null
+++ b/src/sq/sq_result_helper.c
@@ -0,0 +1,237 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file sq/sq_result_helper.c
+ * @brief functions to initialize parameter arrays
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include <sqlite3.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_sq_lib.h>
+#include "taler_sq_lib.h"
+#include "taler_util.h"
+
+
+/**
+ * Extract amount data from a SQLite database
+ *
+ * @param cls closure, a `const char *` giving the currency
+ * @param result where to extract data from
+ * @param column column to extract data from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ *   #GNUNET_YES if all results could be extracted
+ *   #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
+ */
+static enum GNUNET_GenericReturnValue
+extract_amount (void *cls,
+                sqlite3_stmt *result,
+                unsigned int column,
+                size_t *dst_size,
+                void *dst)
+{
+  struct TALER_Amount *amount = dst;
+  const char *currency = cls;
+  if ((sizeof (struct TALER_Amount) != *dst_size) ||
+      (SQLITE_INTEGER != sqlite3_column_type (result,
+                                              (int) column)) ||
+      (SQLITE_INTEGER != sqlite3_column_type (result,
+                                              (int) column + 1)))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_strlcpy (amount->currency,
+                  currency,
+                  TALER_CURRENCY_LEN);
+  amount->value = (uint64_t) sqlite3_column_int64 (result,
+                                                   (int) column);
+  uint64_t frac = (uint64_t) sqlite3_column_int64 (result,
+                                                   (int) column + 1);
+  amount->fraction = (uint32_t) frac;
+  return GNUNET_YES;
+}
+
+
+struct GNUNET_SQ_ResultSpec
+TALER_SQ_result_spec_amount (const char *currency,
+                             struct TALER_Amount *amount)
+{
+  struct GNUNET_SQ_ResultSpec res = {
+    .conv = &extract_amount,
+    .cls = (void *) currency,
+    .dst = (void *) amount,
+    .dst_size = sizeof (struct TALER_Amount),
+    .num_params = 2
+  };
+
+  return res;
+}
+
+
+/**
+ * Extract amount data from a SQLite database
+ *
+ * @param cls closure, a `const char *` giving the currency
+ * @param result where to extract data from
+ * @param column column to extract data from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ *   #GNUNET_YES if all results could be extracted
+ *   #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
+ */
+static enum GNUNET_GenericReturnValue
+extract_amount_nbo (void *cls,
+                    sqlite3_stmt *result,
+                    unsigned int column,
+                    size_t *dst_size,
+                    void *dst)
+{
+  struct TALER_AmountNBO *amount = dst;
+  struct TALER_Amount amount_hbo;
+  size_t amount_hbo_size = sizeof (struct TALER_Amount);
+
+  (void) cls;
+  (void) dst_size;
+  if (GNUNET_YES !=
+      extract_amount (cls,
+                      result,
+                      column,
+                      &amount_hbo_size,
+                      &amount_hbo))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  TALER_amount_hton (amount,
+                     &amount_hbo);
+  return GNUNET_YES;
+}
+
+
+struct GNUNET_SQ_ResultSpec
+TALER_SQ_result_spec_amount_nbo (const char *currency,
+                                 struct TALER_AmountNBO *amount)
+{
+  struct GNUNET_SQ_ResultSpec res = {
+    .conv = &extract_amount_nbo,
+    .cls = (void *) currency,
+    .dst = (void *) amount,
+    .dst_size = sizeof (struct TALER_AmountNBO),
+    .num_params = 2
+  };
+
+  return res;
+}
+
+
+/**
+ * Extract amount data from a SQLite database
+ *
+ * @param cls closure
+ * @param result where to extract data from
+ * @param column column to extract data from
+ * @param[in,out] dst_size where to store size of result, may be NULL
+ * @param[out] dst where to store the result
+ * @return
+ *   #GNUNET_YES if all results could be extracted
+ *   #GNUNET_SYSERR if a result was invalid (non-existing field or NULL)
+ */
+static enum GNUNET_GenericReturnValue
+extract_json (void *cls,
+              sqlite3_stmt *result,
+              unsigned int column,
+              size_t *dst_size,
+              void *dst)
+{
+  json_t **j_dst = dst;
+  const char *res;
+  json_error_t json_error;
+  size_t slen;
+
+  (void) cls;
+  (void) dst_size;
+  if (SQLITE_TEXT != sqlite3_column_type (result,
+                                          column))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  res = (const char *) sqlite3_column_text (result,
+                                            column);
+  slen = strlen (res);
+  *j_dst = json_loadb (res,
+                       slen,
+                       JSON_REJECT_DUPLICATES,
+                       &json_error);
+  if (NULL == *j_dst)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to parse JSON result for column %d: %s (%s)\n",
+                column,
+                json_error.text,
+                json_error.source);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called to clean up memory allocated
+ * by a #GNUNET_SQ_ResultConverter.
+ *
+ * @param cls closure
+ */
+static void
+clean_json (void *cls)
+{
+  json_t **dst = cls;
+
+  (void) cls;
+  if (NULL != *dst)
+  {
+    json_decref (*dst);
+    *dst = NULL;
+  }
+}
+
+
+/**
+ * json_t expected.
+ *
+ * @param[out] jp where to store the result
+ * @return array entry for the result specification to use
+ */
+struct GNUNET_SQ_ResultSpec
+TALER_SQ_result_spec_json (json_t **jp)
+{
+  struct GNUNET_SQ_ResultSpec res = {
+    .conv = &extract_json,
+    .cleaner = &clean_json,
+    .dst = (void *) jp,
+    .cls = (void *) jp,
+    .num_params = 1
+  };
+
+  return res;
+}
+
+
+/* end of sq/sq_result_helper.c */
diff --git a/src/sq/test_sq.c b/src/sq/test_sq.c
new file mode 100644
index 0000000..8f464fa
--- /dev/null
+++ b/src/sq/test_sq.c
@@ -0,0 +1,215 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file sq/test_sq.c
+ * @brief Tests for SQLite3 convenience API
+ * @author Jonathan Buchanan
+ */
+#include "platform.h"
+#include "taler_sq_lib.h"
+
+
+/**
+ * Run actual test queries.
+ *
+ * @return 0 on success
+ */
+static int
+run_queries (sqlite3 *db)
+{
+  struct TALER_Amount hamount;
+  struct TALER_AmountNBO namount;
+  json_t *json;
+  sqlite3_stmt *test_insert;
+  sqlite3_stmt *test_select;
+  struct GNUNET_SQ_PrepareStatement ps[] = {
+    GNUNET_SQ_make_prepare ("INSERT INTO test_sq ("
+                            " hamount_val"
+                            ",hamount_frac"
+                            ",namount_val"
+                            ",namount_frac"
+                            ",json"
+                            ") VALUES "
+                            "($1, $2, $3, $4, $5)",
+                            &test_insert),
+    GNUNET_SQ_make_prepare ("SELECT"
+                            " hamount_val"
+                            ",hamount_frac"
+                            ",namount_val"
+                            ",namount_frac"
+                            ",json"
+                            " FROM test_sq",
+                            &test_select),
+    GNUNET_SQ_PREPARE_END
+  };
+  int ret = 0;
+
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:1.23",
+                                         &hamount));
+  TALER_amount_hton (&namount,
+                     &hamount);
+  json = json_object ();
+  GNUNET_assert (NULL != json);
+  GNUNET_assert (0 ==
+                 json_object_set_new (json,
+                                      "foo",
+                                      json_integer (42)));
+  GNUNET_assert (NULL != json);
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_SQ_prepare (db,
+                                    ps));
+
+  {
+    struct GNUNET_SQ_QueryParam params_insert[] = {
+      TALER_SQ_query_param_amount (&hamount),
+      TALER_SQ_query_param_amount_nbo (&namount),
+      TALER_SQ_query_param_json (json),
+      GNUNET_SQ_query_param_end
+    };
+    GNUNET_SQ_reset (db,
+                     test_insert);
+    GNUNET_assert (GNUNET_OK == GNUNET_SQ_bind (test_insert,
+                                                params_insert));
+    GNUNET_assert (SQLITE_DONE == sqlite3_step (test_insert));
+    sqlite3_finalize (test_insert);
+  }
+
+  {
+    struct TALER_Amount result_amount;
+    struct TALER_AmountNBO nresult_amount;
+    struct TALER_Amount nresult_amount_converted;
+    json_t *result_json;
+    struct GNUNET_SQ_QueryParam params_select[] = {
+      GNUNET_SQ_query_param_end
+    };
+    struct GNUNET_SQ_ResultSpec results_select[] = {
+      TALER_SQ_result_spec_amount ("EUR",
+                                   &result_amount),
+      TALER_SQ_result_spec_amount_nbo ("EUR",
+                                       &nresult_amount),
+      TALER_SQ_result_spec_json (&result_json),
+      GNUNET_SQ_result_spec_end
+    };
+
+    GNUNET_SQ_reset (db,
+                     test_select);
+    GNUNET_assert (GNUNET_OK == GNUNET_SQ_bind (test_select,
+                                                params_select));
+    GNUNET_assert (SQLITE_ROW == sqlite3_step (test_select));
+
+    GNUNET_assert (GNUNET_OK == GNUNET_SQ_extract_result (test_select,
+                                                          results_select));
+    TALER_amount_ntoh (&nresult_amount_converted,
+                       &nresult_amount);
+    if ((GNUNET_OK != TALER_amount_cmp_currency (&hamount,
+                                                 &result_amount)) ||
+        (0 != TALER_amount_cmp (&hamount,
+                                &result_amount)) ||
+        (GNUNET_OK != TALER_amount_cmp_currency (&hamount,
+                                                 &nresult_amount_converted)) ||
+        (0 != TALER_amount_cmp (&hamount,
+                                &nresult_amount_converted)) ||
+        (1 != json_equal (json,
+                          result_json)) )
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Result from database doesn't match input\n");
+      ret = 1;
+    }
+    GNUNET_SQ_cleanup_result (results_select);
+    sqlite3_finalize (test_select);
+  }
+  json_decref (json);
+
+  return ret;
+}
+
+
+int
+main (int argc,
+      const char *const argv[])
+{
+  struct GNUNET_SQ_ExecuteStatement es[] = {
+    GNUNET_SQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS test_sq ("
+                            " hamount_val INT8 NOT NULL"
+                            ",hamount_frac INT8 NOT NULL"
+                            ",namount_val INT8 NOT NULL"
+                            ",namount_frac INT8 NOT NULL"
+                            ",json VARCHAR NOT NULL"
+                            ")"),
+    GNUNET_SQ_EXECUTE_STATEMENT_END
+  };
+  sqlite3 *db;
+  int ret;
+
+  (void) argc;
+  (void) argv;
+  GNUNET_log_setup ("test-pq",
+                    "WARNING",
+                    NULL);
+
+  if (SQLITE_OK != sqlite3_open ("talercheck.db",
+                                 &db))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to open SQLite3 database\n");
+    return 77;
+  }
+
+  if (GNUNET_OK != GNUNET_SQ_exec_statements (db,
+                                              es))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to create new table\n");
+    if ((SQLITE_OK != sqlite3_close (db)) ||
+        (0 != unlink ("talercheck.db")))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Failed to close db or unlink\n");
+    }
+    return 1;
+  }
+
+  ret = run_queries (db);
+
+  if (SQLITE_OK !=
+      sqlite3_exec (db,
+                    "DROP TABLE test_sq",
+                    NULL, NULL, NULL))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to drop table\n");
+    ret = 1;
+  }
+
+  if (SQLITE_OK != sqlite3_close (db))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to close database\n");
+    ret = 1;
+  }
+  if (0 != unlink ("talercheck.db"))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to unlink test database file\n");
+    ret = 1;
+  }
+  return ret;
+}
+
+
+/* end of sq/test_sq.c */

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]