gnunet-svn
[Top][All Lists]
Advanced

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

[taler-donau] branch master updated (71fb5c1 -> a9e7605)


From: gnunet
Subject: [taler-donau] branch master updated (71fb5c1 -> a9e7605)
Date: Mon, 23 Oct 2023 14:18:21 +0200

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

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

    from 71fb5c1  [db] working on headers
     new daf0a95  [build] first cleanup/adaptation
     new bc1f2c3  [build] ignore build files
     new a9e7605  [build] copy src/{curl,json,mhd,pq,sq} from exchange to build 
succesfully

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .gitignore                |    7 +
 aclocal.m4                |    4 +-
 configure.ac              |   35 +-
 doc/Makefile.am           |   84 +--
 doc/doxygen/Makefile.in   |    2 +-
 src/Makefile.am           |   17 +-
 src/curl/Makefile.am      |   24 +
 src/curl/curl.c           |  107 ++++
 src/donau/Makefile.am     |   35 +-
 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 +++++++
 33 files changed, 9679 insertions(+), 79 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 src/curl/Makefile.am
 create mode 100644 src/curl/curl.c
 create mode 100644 src/json/.gitignore
 create mode 100644 src/json/Makefile.am
 create mode 100644 src/json/i18n.c
 create mode 100644 src/json/json.c
 create mode 100644 src/json/json_helper.c
 create mode 100644 src/json/json_pack.c
 create mode 100644 src/json/json_wire.c
 create mode 100644 src/json/test_json.c
 create mode 100644 src/mhd/Makefile.am
 create mode 100644 src/mhd/mhd_config.c
 create mode 100644 src/mhd/mhd_legal.c
 create mode 100644 src/mhd/mhd_parsing.c
 create mode 100644 src/mhd/mhd_responses.c
 create mode 100644 src/mhd/mhd_run.c
 create mode 100644 src/pq/Makefile.am
 create mode 100644 src/pq/pq_common.c
 create mode 100644 src/pq/pq_common.h
 create mode 100644 src/pq/pq_query_helper.c
 create mode 100644 src/pq/pq_result_helper.c
 create mode 100644 src/pq/test_pq.c
 create mode 100644 src/sq/Makefile.am
 create mode 100644 src/sq/sq_query_helper.c
 create mode 100644 src/sq/sq_result_helper.c
 create mode 100644 src/sq/test_sq.c

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c428c54
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+Makefile.in
+autom4te.cache/
+build-aux/
+config.log
+configure
+configure~
+taler_config.h.in~
diff --git a/aclocal.m4 b/aclocal.m4
index 981f0e3..44ab1c8 100644
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -8,7 +8,7 @@
 
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY, to the extent permitted by law; without
-# even the implied warranty of CHARITYABILITY or FITNESS FOR A
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 # PARTICULAR PURPOSE.
 
 m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], 
[])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])])
@@ -33,7 +33,7 @@ dnl (at your option) any later version.
 dnl
 dnl This program is distributed in the hope that it will be useful, but
 dnl WITHOUT ANY WARRANTY; without even the implied warranty of
-dnl CHARITYABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 dnl General Public License for more details.
 dnl
 dnl You should have received a copy of the GNU General Public License
diff --git a/configure.ac b/configure.ac
index dbbf57c..afe25ea 100644
--- a/configure.ac
+++ b/configure.ac
@@ -511,31 +511,40 @@ AM_CONDITIONAL([HAVE_TWISTER], [false])
 # end of 'doc_only'
 ])
 
-AC_CONFIG_FILES([Makefile
+# AC_CONFIG_FILES([Makefile
                  contrib/Makefile
                  doc/Makefile
                  doc/doxygen/Makefile
                  po/Makefile.in
                  src/Makefile
-                 src/auditor/Makefile
-                 src/auditordb/Makefile
-                 src/bank-lib/Makefile
-                 src/curl/Makefile
+                 # src/curl/Makefile
                  src/donau/Makefile
                  src/donaudb/Makefile
                  src/donau-tools/Makefile
-                 src/extensions/Makefile
-                 src/extensions/age_restriction/Makefile
+                 # src/extensions/Makefile
                  src/lib/Makefile
-                 src/kyclogic/Makefile
                  src/testing/Makefile
-                 src/benchmark/Makefile
+                 # src/benchmark/Makefile
                  src/include/Makefile
-                 src/json/Makefile
-                 src/mhd/Makefile
-                 src/pq/Makefile
-                 src/sq/Makefile
+                 # src/json/Makefile
+                 # src/mhd/Makefile
+                 # src/pq/Makefile
+                 # src/sq/Makefile
                  src/templating/Makefile
                  src/util/Makefile
                  ])
+AC_CONFIG_FILES([Makefile
+                 contrib/Makefile
+                 doc/Makefile
+                 doc/doxygen/Makefile
+                 po/Makefile.in
+                 src/Makefile
+                 src/donau/Makefile
+                 src/donaudb/Makefile
+                 src/donau-tools/Makefile
+                 src/lib/Makefile
+                 src/testing/Makefile
+                 src/include/Makefile
+                 src/util/Makefile
+                 ])
 AC_OUTPUT
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 6ff7cbc..24ee82d 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -10,65 +10,65 @@ man_MANS = \
   prebuilt/man/taler.conf.5            \
   prebuilt/man/taler-config.1            \
   prebuilt/man/taler-aggregator-benchmark.1   \
-  prebuilt/man/taler-auditor.1             \
-  prebuilt/man/taler-auditor-dbinit.1      \
-  prebuilt/man/taler-auditor-donau.1     \
-  prebuilt/man/taler-auditor-httpd.1       \
-  prebuilt/man/taler-auditor-offline.1    \
-  prebuilt/man/taler-auditor-sign.1       \
-  prebuilt/man/taler-auditor-sync.1       \
-  prebuilt/man/taler-bank-benchmark.1   \
-  prebuilt/man/taler-bank-transfer.1   \
+  # prebuilt/man/taler-auditor.1             \
+  # prebuilt/man/taler-auditor-dbinit.1      \
+  # prebuilt/man/taler-auditor-donau.1     \
+  # prebuilt/man/taler-auditor-httpd.1       \
+  # prebuilt/man/taler-auditor-offline.1    \
+  #prebuilt/man/taler-auditor-sign.1       \
+  #prebuilt/man/taler-auditor-sync.1       \
+  #prebuilt/man/taler-bank-benchmark.1   \
+  #prebuilt/man/taler-bank-transfer.1   \
   prebuilt/man/taler-donau-aggregator.1 \
   prebuilt/man/taler-donau-benchmark.1  \
   prebuilt/man/taler-donau-closer.1     \
   prebuilt/man/taler-donau-dbconfig.1  \
   prebuilt/man/taler-donau-dbinit.1  \
-  prebuilt/man/taler-donau-drain.1  \
-  prebuilt/man/taler-donau-expire.1  \
+  #prebuilt/man/taler-donau-drain.1  \
+  #prebuilt/man/taler-donau-expire.1  \
   prebuilt/man/taler-donau-httpd.1       \
-  prebuilt/man/taler-donau-kyc-aml-pep-trigger.1 \
-  prebuilt/man/taler-donau-kyc-tester.1       \
+  #prebuilt/man/taler-donau-kyc-aml-pep-trigger.1 \
+  #prebuilt/man/taler-donau-kyc-tester.1       \
   prebuilt/man/taler-donau-offline.1    \
   prebuilt/man/taler-donau-router.1\
   prebuilt/man/taler-donau-secmod-cs.1\
   prebuilt/man/taler-donau-secmod-eddsa.1\
   prebuilt/man/taler-donau-secmod-rsa.1 \
-  prebuilt/man/taler-donau-transfer.1\
-  prebuilt/man/taler-donau-wire-gateway-client.1\
-  prebuilt/man/taler-donau-wirewatch.1   \
-  prebuilt/man/taler-helper-auditor-aggregation.1 \
-  prebuilt/man/taler-helper-auditor-coins.1\
-  prebuilt/man/taler-helper-auditor-deposits.1\
-  prebuilt/man/taler-helper-auditor-purses.1\
-  prebuilt/man/taler-helper-auditor-reserves.1\
-  prebuilt/man/taler-helper-auditor-wire.1 \
+  #prebuilt/man/taler-donau-transfer.1\
+  #prebuilt/man/taler-donau-wire-gateway-client.1\
+  #prebuilt/man/taler-donau-wirewatch.1   \
+  #prebuilt/man/taler-helper-auditor-aggregation.1 \
+  #prebuilt/man/taler-helper-auditor-coins.1\
+  #prebuilt/man/taler-helper-auditor-deposits.1\
+  #prebuilt/man/taler-helper-auditor-purses.1\
+  #prebuilt/man/taler-helper-auditor-reserves.1\
+  #prebuilt/man/taler-helper-auditor-wire.1 \
   prebuilt/man/taler-terms-generator.1 \
   prebuilt/man/taler-unified-setup.1
 
-info_TEXINFOS = \
-  prebuilt/texinfo/taler-auditor.texi \
-  prebuilt/texinfo/taler-bank.texi \
-  prebuilt/texinfo/taler-developer-manual.texi \
-  prebuilt/texinfo/taler-donau.texi
+# info_TEXINFOS = \
+  #prebuilt/texinfo/taler-auditor.texi \
+  #prebuilt/texinfo/taler-bank.texi \
+  #prebuilt/texinfo/taler-developer-manual.texi \
+  #prebuilt/texinfo/taler-donau.texi
 
 
 EXTRA_DIST = \
   $(man_MANS) \
   doxygen/taler.doxy \
   $(info_TEXINFOS) \
-  prebuilt/texinfo/taler-auditor-figures/auditor-db.png \
-  prebuilt/texinfo/taler-auditor-figures/replication.png  \
-  prebuilt/texinfo/taler-bank-figures/arch-api.png  \
-  prebuilt/texinfo/taler-bank-figures/auditor-db.png       \
-  prebuilt/texinfo/taler-bank-figures/donau-db.png \
-  prebuilt/texinfo/taler-bank-figures/charity-db.png \
-  prebuilt/texinfo/taler-bank-figures/replication.png \
-  prebuilt/texinfo/taler-developer-manual-figures/arch-api.png \
-  prebuilt/texinfo/taler-developer-manual-figures/auditor-db.png \
-  prebuilt/texinfo/taler-developer-manual-figures/donau-db.png \
-  prebuilt/texinfo/taler-developer-manual-figures/charity-db.png \
-  prebuilt/texinfo/taler-developer-manual-figures/replication.png \
-  prebuilt/texinfo/taler-donau-figures/auditor-db.png \
-  prebuilt/texinfo/taler-donau-figures/donau-db.png\
-  prebuilt/texinfo/taler-donau-figures/replication.png
+  #prebuilt/texinfo/taler-auditor-figures/auditor-db.png \
+  #prebuilt/texinfo/taler-auditor-figures/replication.png  \
+  #prebuilt/texinfo/taler-bank-figures/arch-api.png  \
+  #prebuilt/texinfo/taler-bank-figures/auditor-db.png       \
+  #prebuilt/texinfo/taler-bank-figures/donau-db.png \
+  #prebuilt/texinfo/taler-bank-figures/charity-db.png \
+  #prebuilt/texinfo/taler-bank-figures/replication.png \
+  #prebuilt/texinfo/taler-developer-manual-figures/arch-api.png \
+  #prebuilt/texinfo/taler-developer-manual-figures/auditor-db.png \
+  #prebuilt/texinfo/taler-developer-manual-figures/donau-db.png \
+  #prebuilt/texinfo/taler-developer-manual-figures/charity-db.png \
+  #prebuilt/texinfo/taler-developer-manual-figures/replication.png \
+  #prebuilt/texinfo/taler-donau-figures/auditor-db.png \
+  #prebuilt/texinfo/taler-donau-figures/donau-db.png\
+  #prebuilt/texinfo/taler-donau-figures/replication.png
diff --git a/doc/doxygen/Makefile.in b/doc/doxygen/Makefile.in
index 973790b..582dba3 100644
--- a/doc/doxygen/Makefile.in
+++ b/doc/doxygen/Makefile.in
@@ -9,7 +9,7 @@
 
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY, to the extent permitted by law; without
-# even the implied warranty of CHARITYABILITY or FITNESS FOR A
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 # PARTICULAR PURPOSE.
 
 @SET_MAKE@
diff --git a/src/Makefile.am b/src/Makefile.am
index 107b378..9c9def4 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -14,7 +14,7 @@ pkgcfg_DATA = \
 EXTRA_DIST = \
   taler.conf
 
-SUBDIRS = \
+# SUBDIRS = \
   include \
   util \
   json \
@@ -35,3 +35,18 @@ SUBDIRS = \
   extensions/age_restriction \
   testing \
   benchmark
+
+
+SUBDIRS = \
+  include \
+  util \
+  json \
+  curl \
+  $(PQ_DIR) \
+  $(SQ_DIR) \
+  mhd \
+  donaudb \
+  donau \
+  lib \
+  donau-tools \
+  testing
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/donau/Makefile.am b/src/donau/Makefile.am
index 4ed7b43..0771f1a 100644
--- a/src/donau/Makefile.am
+++ b/src/donau/Makefile.am
@@ -19,11 +19,11 @@ bin_SCRIPTS = \
   taler-donau-kyc-aml-pep-trigger.sh
 
 bin_PROGRAMS = \
-  taler-donau-httpd \
+  taler-donau-httpd
 
-taler_donau_aggregator_SOURCES = \
+# taler_donau_aggregator_SOURCES = \
   taler-donau-aggregator.c
-taler_donau_aggregator_LDADD = \
+# taler_donau_aggregator_LDADD = \
   $(LIBGCRYPT_LIBS) \
   $(top_builddir)/src/kyclogic/libtalerkyclogic.la \
   $(top_builddir)/src/json/libtalerjson.la \
@@ -36,9 +36,9 @@ taler_donau_aggregator_LDADD = \
   $(XLIB)
 
 
-taler_donau_closer_SOURCES = \
+# taler_donau_closer_SOURCES = \
   taler-donau-closer.c
-taler_donau_closer_LDADD = \
+# taler_donau_closer_LDADD = \
   $(LIBGCRYPT_LIBS) \
   $(top_builddir)/src/json/libtalerjson.la \
   $(top_builddir)/src/util/libtalerutil.la \
@@ -49,9 +49,9 @@ taler_donau_closer_LDADD = \
   -lgnunetutil \
   $(XLIB)
 
-taler_donau_drain_SOURCES = \
+# taler_donau_drain_SOURCES = \
   taler-donau-drain.c
-taler_donau_drain_LDADD = \
+# taler_donau_drain_LDADD = \
   $(LIBGCRYPT_LIBS) \
   $(top_builddir)/src/json/libtalerjson.la \
   $(top_builddir)/src/util/libtalerutil.la \
@@ -62,9 +62,9 @@ taler_donau_drain_LDADD = \
   -lgnunetutil \
   $(XLIB)
 
-taler_donau_expire_SOURCES = \
+# taler_donau_expire_SOURCES = \
   taler-donau-expire.c
-taler_donau_expire_LDADD = \
+# taler_donau_expire_LDADD = \
   $(LIBGCRYPT_LIBS) \
   $(top_builddir)/src/json/libtalerjson.la \
   $(top_builddir)/src/util/libtalerutil.la \
@@ -75,9 +75,9 @@ taler_donau_expire_LDADD = \
   -lgnunetutil \
   $(XLIB)
 
-taler_donau_router_SOURCES = \
+# taler_donau_router_SOURCES = \
   taler-donau-router.c
-taler_donau_router_LDADD = \
+# taler_donau_router_LDADD = \
   $(LIBGCRYPT_LIBS) \
   $(top_builddir)/src/json/libtalerjson.la \
   $(top_builddir)/src/util/libtalerutil.la \
@@ -88,9 +88,9 @@ taler_donau_router_LDADD = \
   -lgnunetutil \
   $(XLIB)
 
-taler_donau_transfer_SOURCES = \
+# taler_donau_transfer_SOURCES = \
   taler-donau-transfer.c
-taler_donau_transfer_LDADD = \
+# taler_donau_transfer_LDADD = \
   $(LIBGCRYPT_LIBS) \
   $(top_builddir)/src/json/libtalerjson.la \
   $(top_builddir)/src/util/libtalerutil.la \
@@ -101,9 +101,9 @@ taler_donau_transfer_LDADD = \
   -lgnunetutil \
   $(XLIB)
 
-taler_donau_wirewatch_SOURCES = \
+# taler_donau_wirewatch_SOURCES = \
   taler-donau-wirewatch.c
-taler_donau_wirewatch_LDADD = \
+# taler_donau_wirewatch_LDADD = \
   $(LIBGCRYPT_LIBS) \
   $(top_builddir)/src/json/libtalerjson.la \
   $(top_builddir)/src/util/libtalerutil.la \
@@ -117,7 +117,6 @@ taler_donau_wirewatch_LDADD = \
 
 taler_donau_httpd_SOURCES = \
   taler-donau-httpd.c taler-donau-httpd.h \
-  taler-donau-httpd_auditors.c taler-donau-httpd_auditors.h \
   taler-donau-httpd_aml-decision.c taler-donau-httpd_aml-decision.h \
   taler-donau-httpd_aml-decision-get.c \
   taler-donau-httpd_aml-decisions-get.c \
@@ -141,8 +140,6 @@ taler_donau_httpd_SOURCES = \
   taler-donau-httpd_link.c taler-donau-httpd_link.h \
   taler-donau-httpd_management.h \
   taler-donau-httpd_management_aml-officers.c \
-  taler-donau-httpd_management_auditors.c \
-  taler-donau-httpd_management_auditors_AP_disable.c \
   taler-donau-httpd_management_denominations_HDP_revoke.c \
   taler-donau-httpd_management_drain.c \
   taler-donau-httpd_management_extensions.c \
@@ -180,12 +177,10 @@ taler_donau_httpd_SOURCES = \
 
 taler_donau_httpd_LDADD = \
   $(LIBGCRYPT_LIBS) \
-  $(top_builddir)/src/bank-lib/libtalerbank.la \
   $(top_builddir)/src/mhd/libtalermhd.la \
   $(top_builddir)/src/json/libtalerjson.la \
   $(top_builddir)/src/donaudb/libtalerdonaudb.la \
   $(top_builddir)/src/templating/libtalertemplating.la \
-  $(top_builddir)/src/kyclogic/libtalerkyclogic.la \
   $(top_builddir)/src/util/libtalerutil.la \
   $(top_builddir)/src/extensions/libtalerextensions.la \
   -lmicrohttpd \
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]