gnunet-svn
[Top][All Lists]
Advanced

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

[taler-anastasis] branch master updated: -first rough cut towards implem


From: gnunet
Subject: [taler-anastasis] branch master updated: -first rough cut towards implementing new /truths/ endpoint design (#7064)
Date: Sun, 27 Feb 2022 22:36:01 +0100

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

grothoff pushed a commit to branch master
in repository anastasis.

The following commit(s) were added to refs/heads/master by this push:
     new fb3c4bb  -first rough cut towards implementing new /truths/ endpoint 
design (#7064)
fb3c4bb is described below

commit fb3c4bb1885f40a84bd534cd38f631b93bfa4a87
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Sun Feb 27 22:35:51 2022 +0100

    -first rough cut towards implementing new /truths/ endpoint design (#7064)
---
 INSTALL                                            |    6 +-
 contrib/gana                                       |    2 +-
 doc/texinfo.tex                                    |  418 ++++--
 .../anastasis_authorization_plugin_totp.c          |    1 -
 src/backend/Makefile.am                            |    6 +-
 src/backend/anastasis-httpd.c                      |   48 +-
 ...cy_upload.c => anastasis-httpd_policy-upload.c} |    0
 src/backend/anastasis-httpd_truth-challenge.c      | 1389 +++++++++++++++++++
 src/backend/anastasis-httpd_truth-solve.c          | 1430 ++++++++++++++++++++
 ...uth_upload.c => anastasis-httpd_truth-upload.c} |    0
 src/backend/anastasis-httpd_truth.h                |   60 +-
 11 files changed, 3214 insertions(+), 146 deletions(-)

diff --git a/INSTALL b/INSTALL
index e82fd21..8865734 100644
--- a/INSTALL
+++ b/INSTALL
@@ -1,8 +1,8 @@
 Installation Instructions
 *************************
 
-   Copyright (C) 1994-1996, 1999-2002, 2004-2017, 2020-2021 Free
-Software Foundation, Inc.
+   Copyright (C) 1994-1996, 1999-2002, 2004-2016 Free Software
+Foundation, Inc.
 
    Copying and distribution of this file, with or without modification,
 are permitted in any medium without royalty provided the copyright
@@ -225,7 +225,7 @@ order to use an ANSI C compiler:
 
 and if that doesn't work, install pre-built binaries of GCC for HP-UX.
 
-   HP-UX 'make' updates targets which have the same timestamps as their
+   HP-UX 'make' updates targets which have the same time stamps as their
 prerequisites, which makes it generally unusable when shipped generated
 files such as 'configure' are involved.  Use GNU 'make' instead.
 
diff --git a/contrib/gana b/contrib/gana
index ecb597d..71a75a1 160000
--- a/contrib/gana
+++ b/contrib/gana
@@ -1 +1 @@
-Subproject commit ecb597d6fb23e18a282059791c49716aa8ffd8cb
+Subproject commit 71a75a14496199ba1e1fd245ceef96cc0d0c0ab0
diff --git a/doc/texinfo.tex b/doc/texinfo.tex
index e48383d..3c7051d 100644
--- a/doc/texinfo.tex
+++ b/doc/texinfo.tex
@@ -3,9 +3,9 @@
 % Load plain if necessary, i.e., if running under initex.
 \expandafter\ifx\csname fmtname\endcsname\relax\input plain\fi
 %
-\def\texinfoversion{2021-04-25.21}
+\def\texinfoversion{2020-10-24.12}
 %
-% Copyright 1985, 1986, 1988, 1990-2021 Free Software Foundation, Inc.
+% Copyright 1985, 1986, 1988, 1990-2020 Free Software Foundation, Inc.
 %
 % This texinfo.tex file is free software: you can redistribute it and/or
 % modify it under the terms of the GNU General Public License as
@@ -572,8 +572,9 @@
   \fi
 }
 
-
-% @end foo calls \checkenv and executes the definition of \Efoo.
+% @end foo executes the definition of \Efoo.
+% But first, it executes a specialized version of \checkenv
+%
 \parseargdef\end{%
   \if 1\csname iscond.#1\endcsname
   \else
@@ -1002,14 +1003,6 @@ where each line of input produces a line of output.}
   \global\everypar = {}%
 }
 
-% leave vertical mode without cancelling any first paragraph indent
-\gdef\imageindent{%
-  \toks0=\everypar
-  \everypar={}%
-  \ptexnoindent
-  \global\everypar=\toks0
-}
-
 
 % @refill is a no-op.
 \let\refill=\relax
@@ -1870,23 +1863,19 @@ output) for that.)}
       \closein 1
     \endgroup
     %
-    % Putting an \hbox around the image can prevent an over-long line
-    % after the image.
-    \hbox\bgroup
-      \def\xetexpdfext{pdf}%
+    \def\xetexpdfext{pdf}%
+    \ifx\xeteximgext\xetexpdfext
+      \XeTeXpdffile "#1".\xeteximgext ""
+    \else
+      \def\xetexpdfext{PDF}%
       \ifx\xeteximgext\xetexpdfext
         \XeTeXpdffile "#1".\xeteximgext ""
       \else
-        \def\xetexpdfext{PDF}%
-        \ifx\xeteximgext\xetexpdfext
-          \XeTeXpdffile "#1".\xeteximgext ""
-        \else
-          \XeTeXpicfile "#1".\xeteximgext ""
-        \fi
+        \XeTeXpicfile "#1".\xeteximgext ""
       \fi
-      \ifdim \wd0 >0pt width \xeteximagewidth \fi
-      \ifdim \wd2 >0pt height \xeteximageheight \fi \relax
-    \egroup
+    \fi
+    \ifdim \wd0 >0pt width \xeteximagewidth \fi
+    \ifdim \wd2 >0pt height \xeteximageheight \fi \relax
   }
 \fi
 
@@ -2684,6 +2673,8 @@ end
 \definetextfontsizexi
 
 
+\message{markup,}
+
 % Check if we are currently using a typewriter font.  Since all the
 % Computer Modern typewriter fonts have zero interword stretch (and
 % shrink), and it is reasonable to expect all typewriter fonts to have
@@ -2691,14 +2682,68 @@ end
 %
 \def\ifmonospace{\ifdim\fontdimen3\font=0pt }
 
+% Markup style infrastructure.  \defmarkupstylesetup\INITMACRO will
+% define and register \INITMACRO to be called on markup style changes.
+% \INITMACRO can check \currentmarkupstyle for the innermost
+% style.
+
+\let\currentmarkupstyle\empty
+
+\def\setupmarkupstyle#1{%
+  \def\currentmarkupstyle{#1}%
+  \markupstylesetup
+}
+
+\let\markupstylesetup\empty
+
+\def\defmarkupstylesetup#1{%
+  \expandafter\def\expandafter\markupstylesetup
+    \expandafter{\markupstylesetup #1}%
+  \def#1%
+}
+
+% Markup style setup for left and right quotes.
+\defmarkupstylesetup\markupsetuplq{%
+  \expandafter\let\expandafter \temp
+    \csname markupsetuplq\currentmarkupstyle\endcsname
+  \ifx\temp\relax \markupsetuplqdefault \else \temp \fi
+}
+
+\defmarkupstylesetup\markupsetuprq{%
+  \expandafter\let\expandafter \temp
+    \csname markupsetuprq\currentmarkupstyle\endcsname
+  \ifx\temp\relax \markupsetuprqdefault \else \temp \fi
+}
+
 {
 \catcode`\'=\active
 \catcode`\`=\active
 
-\gdef\setcodequotes{\let`\codequoteleft \let'\codequoteright}
-\gdef\setregularquotes{\let`\lq \let'\rq}
+\gdef\markupsetuplqdefault{\let`\lq}
+\gdef\markupsetuprqdefault{\let'\rq}
+
+\gdef\markupsetcodequoteleft{\let`\codequoteleft}
+\gdef\markupsetcodequoteright{\let'\codequoteright}
 }
 
+\let\markupsetuplqcode \markupsetcodequoteleft
+\let\markupsetuprqcode \markupsetcodequoteright
+%
+\let\markupsetuplqexample \markupsetcodequoteleft
+\let\markupsetuprqexample \markupsetcodequoteright
+%
+\let\markupsetuplqkbd     \markupsetcodequoteleft
+\let\markupsetuprqkbd     \markupsetcodequoteright
+%
+\let\markupsetuplqsamp \markupsetcodequoteleft
+\let\markupsetuprqsamp \markupsetcodequoteright
+%
+\let\markupsetuplqverb \markupsetcodequoteleft
+\let\markupsetuprqverb \markupsetcodequoteright
+%
+\let\markupsetuplqverbatim \markupsetcodequoteleft
+\let\markupsetuprqverbatim \markupsetcodequoteright
+
 % Allow an option to not use regular directed right quote/apostrophe
 % (char 0x27), but instead the undirected quote from cmtt (char 0x0d).
 % The undirected quote is ugly, so don't make it the default, but it
@@ -2861,7 +2906,7 @@ end
 }
 
 % @samp.
-\def\samp#1{{\setcodequotes\lq\tclose{#1}\rq\null}}
+\def\samp#1{{\setupmarkupstyle{samp}\lq\tclose{#1}\rq\null}}
 
 % @indicateurl is \samp, that is, with quotes.
 \let\indicateurl=\samp
@@ -2904,7 +2949,8 @@ end
   \global\let'=\rq \global\let`=\lq  % default definitions
   %
   \global\def\code{\begingroup
-    \setcodequotes
+    \setupmarkupstyle{code}%
+    % The following should really be moved into \setupmarkupstyle handlers.
     \catcode\dashChar=\active  \catcode\underChar=\active
     \ifallowcodebreaks
      \let-\codedash
@@ -3058,7 +3104,7 @@ end
   \urefcatcodes
   %
   \global\def\urefcode{\begingroup
-    \setcodequotes
+    \setupmarkupstyle{code}%
     \urefcatcodes
     \let&\urefcodeamp
     \let.\urefcodedot
@@ -3179,8 +3225,8 @@ end
 \def\kbdsub#1#2#3\par{%
   \def\one{#1}\def\three{#3}\def\threex{??}%
   \ifx\one\xkey\ifx\threex\three \key{#2}%
-  \else{\tclose{\kbdfont\setcodequotes\look}}\fi
-  \else{\tclose{\kbdfont\setcodequotes\look}}\fi
+  \else{\tclose{\kbdfont\setupmarkupstyle{kbd}\look}}\fi
+  \else{\tclose{\kbdfont\setupmarkupstyle{kbd}\look}}\fi
 }
 
 % definition of @key that produces a lozenge.  Doesn't adjust to text size.
@@ -3197,7 +3243,7 @@ end
 % monospace, don't change it; that way, we respect @kbdinputstyle.  But
 % if it isn't monospace, then use \tt.
 %
-\def\key#1{{\setregularquotes
+\def\key#1{{\setupmarkupstyle{key}%
   \nohyphenation
   \ifmonospace\else\tt\fi
   #1}\null}
@@ -3327,20 +3373,16 @@ end
 {\obeylines
 \globaldefs=1
 \envdef\displaymath{%
-\tex%
+\tex
 \def\thisenv{\displaymath}%
-\begingroup\let\end\displaymathend%
 $$%
 }
 
-\def\displaymathend{$$\endgroup\end}%
-
-\def\Edisplaymath{%
+\def\Edisplaymath{$$
 \def\thisenv{\tex}%
 \end tex
 }}
 
-
 % @inlinefmt{FMTNAME,PROCESSED-TEXT} and @inlineraw{FMTNAME,RAW-TEXT}.
 % Ignore unless FMTNAME == tex; then it is like @iftex and @tex,
 % except specified as a normal braced arg, so no newlines to worry about.
@@ -4301,8 +4343,82 @@ $$%
   \doitemize{#1.}\flushcr
 }
 
+% @alphaenumerate and @capsenumerate are abbreviations for giving an arg
+% to @enumerate.
+%
+\def\alphaenumerate{\enumerate{a}}
+\def\capsenumerate{\enumerate{A}}
+\def\Ealphaenumerate{\Eenumerate}
+\def\Ecapsenumerate{\Eenumerate}
+
 
 % @multitable macros
+% Amy Hendrickson, 8/18/94, 3/6/96
+%
+% @multitable ... @end multitable will make as many columns as desired.
+% Contents of each column will wrap at width given in preamble.  Width
+% can be specified either with sample text given in a template line,
+% or in percent of \hsize, the current width of text on page.
+
+% Table can continue over pages but will only break between lines.
+
+% To make preamble:
+%
+% Either define widths of columns in terms of percent of \hsize:
+%   @multitable @columnfractions .25 .3 .45
+%   @item ...
+%
+%   Numbers following @columnfractions are the percent of the total
+%   current hsize to be used for each column. You may use as many
+%   columns as desired.
+
+
+% Or use a template:
+%   @multitable {Column 1 template} {Column 2 template} {Column 3 template}
+%   @item ...
+%   using the widest term desired in each column.
+
+% Each new table line starts with @item, each subsequent new column
+% starts with @tab. Empty columns may be produced by supplying @tab's
+% with nothing between them for as many times as empty columns are needed,
+% ie, @tab@tab@tab will produce two empty columns.
+
+% @item, @tab do not need to be on their own lines, but it will not hurt
+% if they are.
+
+% Sample multitable:
+
+%   @multitable {Column 1 template} {Column 2 template} {Column 3 template}
+%   @item first col stuff @tab second col stuff @tab third col
+%   @item
+%   first col stuff
+%   @tab
+%   second col stuff
+%   @tab
+%   third col
+%   @item first col stuff @tab second col stuff
+%   @tab Many paragraphs of text may be used in any column.
+%
+%         They will wrap at the width determined by the template.
+%   @item@tab@tab This will be in third column.
+%   @end multitable
+
+% Default dimensions may be reset by user.
+% @multitableparskip is vertical space between paragraphs in table.
+% @multitableparindent is paragraph indent in table.
+% @multitablecolmargin is horizontal space to be left between columns.
+% @multitablelinespace is space to leave between table items, baseline
+%                                                            to baseline.
+%   0pt means it depends on current normal line spacing.
+%
+\newskip\multitableparskip
+\newskip\multitableparindent
+\newdimen\multitablecolspace
+\newskip\multitablelinespace
+\multitableparskip=0pt
+\multitableparindent=6pt
+\multitablecolspace=12pt
+\multitablelinespace=0pt
 
 % Macros used to set up halign preamble:
 %
@@ -4350,6 +4466,8 @@ $$%
   \go
 }
 
+% multitable-only commands.
+%
 % @headitem starts a heading row, which we typeset in bold.  Assignments
 % have to be global since we are inside the implicit group of an
 % alignment entry.  \everycr below resets \everytab so we don't have to
@@ -4366,8 +4484,14 @@ $$%
 % default for tables with no headings.
 \let\headitemcrhook=\relax
 %
+% A \tab used to include \hskip1sp.  But then the space in a template
+% line is not enough.  That is bad.  So let's go back to just `&' until
+% we again encounter the problem the 1sp was intended to solve.
+%                                      --karl, nathan@acm.org, 20apr99.
 \def\tab{\checkenv\multitable &\the\everytab}%
 
+% @multitable ... @end multitable definitions:
+%
 \newtoks\everytab  % insert after every tab.
 %
 \envdef\multitable{%
@@ -4382,8 +4506,9 @@ $$%
   %
   \tolerance=9500
   \hbadness=9500
-  \parskip=0pt
-  \parindent=6pt
+  \setmultitablespacing
+  \parskip=\multitableparskip
+  \parindent=\multitableparindent
   \overfullrule=0pt
   \global\colcount=0
   %
@@ -4413,24 +4538,47 @@ $$%
   % continue for many paragraphs if desired.
   \halign\bgroup &%
     \global\advance\colcount by 1
-    \strut
+    \multistrut
     \vtop{%
-      \advance\hsize by -1\leftskip
-      % Find the correct column width
+      % Use the current \colcount to find the correct column width:
       \hsize=\expandafter\csname col\the\colcount\endcsname
       %
+      % In order to keep entries from bumping into each other
+      % we will add a \leftskip of \multitablecolspace to all columns after
+      % the first one.
+      %
+      % If a template has been used, we will add \multitablecolspace
+      % to the width of each template entry.
+      %
+      % If the user has set preamble in terms of percent of \hsize we will
+      % use that dimension as the width of the column, and the \leftskip
+      % will keep entries from bumping into each other.  Table will start at
+      % left margin and final column will justify at right margin.
+      %
+      % Make sure we don't inherit \rightskip from the outer environment.
       \rightskip=0pt
       \ifnum\colcount=1
-        \advance\hsize by\leftskip % Add indent of surrounding text
+       % The first column will be indented with the surrounding text.
+       \advance\hsize by\leftskip
       \else
-        % In order to keep entries from bumping into each other.
-        \leftskip=12pt
-        \ifsetpercent \else
-          % If a template has been used
-          \advance\hsize by \leftskip
-        \fi
+       \ifsetpercent \else
+         % If user has not set preamble in terms of percent of \hsize
+         % we will advance \hsize by \multitablecolspace.
+         \advance\hsize by \multitablecolspace
+       \fi
+       % In either case we will make \leftskip=\multitablecolspace:
+      \leftskip=\multitablecolspace
       \fi
-      \noindent\ignorespaces##\unskip\strut
+      % Ignoring space at the beginning and end avoids an occasional spurious
+      % blank line, when TeX decides to break the line at the space before the
+      % box from the multistrut, so the strut ends up on a line by itself.
+      % For example:
+      % @multitable @columnfractions .11 .89
+      % @item @code{#}
+      % @tab Legal holiday which is valid in major parts of the whole country.
+      % Is automatically provided with highlighting sequences respectively
+      % marking characters.
+      \noindent\ignorespaces##\unskip\multistrut
     }\cr
 }
 \def\Emultitable{%
@@ -4439,6 +4587,31 @@ $$%
   \global\setpercentfalse
 }
 
+\def\setmultitablespacing{%
+  \def\multistrut{\strut}% just use the standard line spacing
+  %
+  % Compute \multitablelinespace (if not defined by user) for use in
+  % \multitableparskip calculation.  We used define \multistrut based on
+  % this, but (ironically) that caused the spacing to be off.
+  % See bug-texinfo report from Werner Lemberg, 31 Oct 2004 12:52:20 +0100.
+\ifdim\multitablelinespace=0pt
+\setbox0=\vbox{X}\global\multitablelinespace=\the\baselineskip
+\global\advance\multitablelinespace by-\ht0
+\fi
+% Test to see if parskip is larger than space between lines of
+% table. If not, do nothing.
+%        If so, set to same dimension as multitablelinespace.
+\ifdim\multitableparskip>\multitablelinespace
+\global\multitableparskip=\multitablelinespace
+\global\advance\multitableparskip-7pt % to keep parskip somewhat smaller
+                                      % than skip between lines in the table.
+\fi%
+\ifdim\multitableparskip=0pt
+\global\multitableparskip=\multitablelinespace
+\global\advance\multitableparskip-7pt % to keep parskip somewhat smaller
+                                      % than skip between lines in the table.
+\fi}
+
 
 \message{conditionals,}
 
@@ -5052,29 +5225,30 @@ $$%
   \let\lbracechar\{%
   \let\rbracechar\}%
   %
-  % Non-English letters.
-  \def\AA{AA}%
-  \def\AE{AE}%
-  \def\DH{DZZ}%
-  \def\L{L}%
-  \def\OE{OE}%
-  \def\O{O}%
-  \def\TH{TH}%
-  \def\aa{aa}%
-  \def\ae{ae}%
-  \def\dh{dzz}%
-  \def\exclamdown{!}%
-  \def\l{l}%
-  \def\oe{oe}%
-  \def\ordf{a}%
-  \def\ordm{o}%
-  \def\o{o}%
-  \def\questiondown{?}%
-  \def\ss{ss}%
-  \def\th{th}%
   %
   \let\do\indexnofontsdef
   %
+  % Non-English letters.
+  \do\AA{AA}%
+  \do\AE{AE}%
+  \do\DH{DZZ}%
+  \do\L{L}%
+  \do\OE{OE}%
+  \do\O{O}%
+  \do\TH{TH}%
+  \do\aa{aa}%
+  \do\ae{ae}%
+  \do\dh{dzz}%
+  \do\exclamdown{!}%
+  \do\l{l}%
+  \do\oe{oe}%
+  \do\ordf{a}%
+  \do\ordm{o}%
+  \do\o{o}%
+  \do\questiondown{?}%
+  \do\ss{ss}%
+  \do\th{th}%
+  %
   \do\LaTeX{LaTeX}%
   \do\TeX{TeX}%
   %
@@ -6970,7 +7144,7 @@ might help (with 'rm \jobname.?? \jobname.??s')%
 % But \@ or @@ will get a plain @ character.
 
 \envdef\tex{%
-  \setregularquotes
+  \setupmarkupstyle{tex}%
   \catcode `\\=0 \catcode `\{=1 \catcode `\}=2
   \catcode `\$=3 \catcode `\&=4 \catcode `\#=6
   \catcode `\^=7 \catcode `\_=8 \catcode `\~=\active \let~=\tie
@@ -7196,7 +7370,7 @@ might help (with 'rm \jobname.?? \jobname.??s')%
 % If you want all examples etc. small: @set dispenvsize small.
 % If you want even small examples the full size: @set dispenvsize nosmall.
 % This affects the following displayed environments:
-%    @example, @display, @format, @lisp, @verbatim
+%    @example, @display, @format, @lisp
 %
 \def\smallword{small}
 \def\nosmallword{nosmall}
@@ -7242,9 +7416,9 @@ might help (with 'rm \jobname.?? \jobname.??s')%
 %
 \maketwodispenvdef{lisp}{example}{%
   \nonfillstart
-  \tt\setcodequotes
+  \tt\setupmarkupstyle{example}%
   \let\kbdfont = \kbdexamplefont % Allow @kbd to do something special.
-  \parsearg\gobble
+  \gobble % eat return
 }
 % @display/@smalldisplay: same as @lisp except keep current font.
 %
@@ -7402,7 +7576,7 @@ might help (with 'rm \jobname.?? \jobname.??s')%
 \def\setupverb{%
   \tt  % easiest (and conventionally used) font for verbatim
   \def\par{\leavevmode\endgraf}%
-  \setcodequotes
+  \setupmarkupstyle{verb}%
   \tabeightspaces
   % Respect line breaks,
   % print special symbols as themselves, and
@@ -7443,7 +7617,7 @@ might help (with 'rm \jobname.?? \jobname.??s')%
   \tt % easiest (and conventionally used) font for verbatim
   \def\par{\egroup\leavevmode\box\verbbox\endgraf\starttabbox}%
   \tabexpand
-  \setcodequotes
+  \setupmarkupstyle{verbatim}%
   % Respect line breaks,
   % print special symbols as themselves, and
   % make each space count.
@@ -7862,7 +8036,7 @@ might help (with 'rm \jobname.?? \jobname.??s')%
   % leave the code in, but it's strange for @var to lead to typewriter.
   % Nowadays we recommend @code, since the difference between a ttsl hyphen
   % and a tt hyphen is pretty tiny.  @code also disables ?` !`.
-  \def\var##1{{\setregularquotes\ttslanted{##1}}}%
+  \def\var##1{{\setupmarkupstyle{var}\ttslanted{##1}}}%
   #1%
   \sl\hyphenchar\font=45
 }
@@ -7971,18 +8145,11 @@ might help (with 'rm \jobname.?? \jobname.??s')%
   }
 \fi
 
-\let\E=\expandafter
-
 % Used at the time of macro expansion.
 % Argument is macro body with arguments substituted
 \def\scanmacro#1{%
   \newlinechar`\^^M
-  % expand the expansion of \eatleadingcr twice to maybe remove a leading
-  % newline (and \else and \fi tokens), then call \eatspaces on the result.
-  \def\xeatspaces##1{%
-    \E\E\E\E\E\E\E\eatspaces\E\E\E\E\E\E\E{\eatleadingcr##1%
-  }}%
-  \def\xempty##1{}%
+  \def\xeatspaces{\eatspaces}%
   %
   % Process the macro body under the current catcode regime.
   \scantokens{#1@comment}%
@@ -8035,11 +8202,6 @@ might help (with 'rm \jobname.?? \jobname.??s')%
 \unbrace{\gdef\trim@@@ #1 } #2@{#1}
 }
 
-{\catcode`\^^M=\other%
-\gdef\eatleadingcr#1{\if\noexpand#1\noexpand^^M\else\E#1\fi}}%
-% Warning: this won't work for a delimited argument
-% or for an empty argument
-
 % Trim a single trailing ^^M off a string.
 {\catcode`\^^M=\other \catcode`\Q=3%
 \gdef\eatcr #1{\eatcra #1Q^^MQ}%
@@ -8206,7 +8368,6 @@ might help (with 'rm \jobname.?? \jobname.??s')%
   \let\hash\relax
   % \hash is redefined to `#' later to get it into definitions
   \let\xeatspaces\relax
-  \let\xempty\relax
   \parsemargdefxxx#1,;,%
   \ifnum\paramno<10\relax\else
     \paramno0\relax
@@ -8218,11 +8379,9 @@ might help (with 'rm \jobname.?? \jobname.??s')%
   \else \let\next=\parsemargdefxxx
     \advance\paramno by 1
     \expandafter\edef\csname macarg.\eatspaces{#1}\endcsname
-        {\xeatspaces{\hash\the\paramno\noexpand\xempty{}}}%
+        {\xeatspaces{\hash\the\paramno}}%
     \edef\paramlist{\paramlist\hash\the\paramno,}%
   \fi\next}
-% the \xempty{} is to give \eatleadingcr an argument in the case of an
-% empty macro argument.
 
 % \parsemacbody, \parsermacbody
 %
@@ -8811,7 +8970,7 @@ might help (with 'rm \jobname.?? \jobname.??s')%
       \else
         \ifhavexrefs
           % We (should) know the real title if we have the xref values.
-          \def\printedrefname{\refx{#1-title}}%
+          \def\printedrefname{\refx{#1-title}{}}%
         \else
           % Otherwise just copy the Info node name.
           \def\printedrefname{\ignorespaces #1}%
@@ -8905,7 +9064,7 @@ might help (with 'rm \jobname.?? \jobname.??s')%
     % If the user specified the print name (third arg) to the ref,
     % print it instead of our usual "Figure 1.2".
     \ifdim\wd\printedrefnamebox = 0pt
-      \refx{#1-snt}%
+      \refx{#1-snt}{}%
     \else
       \printedrefname
     \fi
@@ -8940,30 +9099,28 @@ might help (with 'rm \jobname.?? \jobname.??s')%
     \else
       % Reference within this manual.
       %
-      % Only output a following space if the -snt ref is nonempty, as the ref
-      % will be empty for @unnumbered and @anchor.
-      \setbox2 = \hbox{\ignorespaces \refx{#1-snt}}%
+      % Only output a following space if the -snt ref is nonempty; for
+      % @unnumbered and @anchor, it won't be.
+      \setbox2 = \hbox{\ignorespaces \refx{#1-snt}{}}%
       \ifdim \wd2 > 0pt \refx{#1-snt}\space\fi
       %
       % output the `[mynode]' via the macro below so it can be overridden.
       \xrefprintnodename\printedrefname
       %
-      \expandafter\ifx\csname SETtxiomitxrefpg\endcsname\relax
-        % But we always want a comma and a space:
-        ,\space
-        %
-        % output the `page 3'.
-        \turnoffactive \putwordpage\tie\refx{#1-pg}%
-        % Add a , if xref followed by a space
-        \if\space\noexpand\tokenafterxref ,%
-        \else\ifx\     \tokenafterxref ,% @TAB
-        \else\ifx\*\tokenafterxref ,%   @*
-        \else\ifx\ \tokenafterxref ,%   @SPACE
-        \else\ifx\
-                  \tokenafterxref ,%    @NL
-        \else\ifx\tie\tokenafterxref ,% @tie
-        \fi\fi\fi\fi\fi\fi
-      \fi
+      % But we always want a comma and a space:
+      ,\space
+      %
+      % output the `page 3'.
+      \turnoffactive \putwordpage\tie\refx{#1-pg}{}%
+      % Add a , if xref followed by a space
+      \if\space\noexpand\tokenafterxref ,%
+      \else\ifx\       \tokenafterxref ,% @TAB
+      \else\ifx\*\tokenafterxref ,%   @*
+      \else\ifx\ \tokenafterxref ,%   @SPACE
+      \else\ifx\
+                \tokenafterxref ,%    @NL
+      \else\ifx\tie\tokenafterxref ,% @tie
+      \fi\fi\fi\fi\fi\fi
     \fi\fi
   \fi
   \endlink
@@ -9030,8 +9187,9 @@ might help (with 'rm \jobname.?? \jobname.??s')%
   \fi\fi\fi
 }
 
-% \refx{NAME} - reference a cross-reference string named NAME.
-\def\refx#1{%
+% \refx{NAME}{SUFFIX} - reference a cross-reference string named NAME.  SUFFIX
+% is output afterwards if non-empty.
+\def\refx#1#2{%
   \requireauxfile
   {%
     \indexnofonts
@@ -9058,6 +9216,7 @@ might help (with 'rm \jobname.?? \jobname.??s')%
     % It's defined, so just use it.
     \thisrefX
   \fi
+  #2% Output the suffix in any case.
 }
 
 % This is the macro invoked by entries in the aux file.  Define a control
@@ -9167,10 +9326,10 @@ might help (with 'rm \jobname.?? \jobname.??s')%
   \catcode`\[=\other
   \catcode`\]=\other
   \catcode`\"=\other
-  \catcode`\_=\active
-  \catcode`\|=\active
-  \catcode`\<=\active
-  \catcode`\>=\active
+  \catcode`\_=\other
+  \catcode`\|=\other
+  \catcode`\<=\other
+  \catcode`\>=\other
   \catcode`\$=\other
   \catcode`\#=\other
   \catcode`\&=\other
@@ -9391,7 +9550,7 @@ might help (with 'rm \jobname.?? \jobname.??s')%
 \def\imagexxx#1,#2,#3,#4,#5,#6\finish{\begingroup
   \catcode`\^^M = 5     % in case we're inside an example
   \normalturnoffactive  % allow _ et al. in names
-  \makevalueexpandable
+  \def\xprocessmacroarg{\eatspaces}% in case we are being used via a macro
   % If the image is by itself, center it.
   \ifvmode
     \imagevmodetrue
@@ -9417,7 +9576,7 @@ might help (with 'rm \jobname.?? \jobname.??s')%
   % On the other hand, if we are in the case of @center @image, we don't
   %  want to start a paragraph, which will create a hsize-width box and
   %  eradicate the centering.
-  \ifx\centersub\centerV \else \imageindent \fi
+  \ifx\centersub\centerV\else \noindent \fi
   %
   % Output the image.
   \ifpdf
@@ -11444,7 +11603,7 @@ directory should work if nowhere else does.}
   \let> = \activegtr
   \let~ = \activetilde
   \let^ = \activehat
-  \setregularquotes
+  \markupsetuplqdefault \markupsetuprqdefault
   \let\b = \strong
   \let\i = \smartitalic
   % in principle, all other definitions in \tex have to be undone too.
@@ -11503,7 +11662,8 @@ directory should work if nowhere else does.}
    @let|=@normalverticalbar
    @let~=@normaltilde
    @let\=@ttbackslash
-   @setregularquotes
+   @markupsetuplqdefault
+   @markupsetuprqdefault
    @unsepspaces
  }
 }
@@ -11596,7 +11756,8 @@ directory should work if nowhere else does.}
 @c Do this last of all since we use ` in the previous @catcode assignments.
 @catcode`@'=@active
 @catcode`@`=@active
-@setregularquotes
+@markupsetuplqdefault
+@markupsetuprqdefault
 
 @c Local variables:
 @c eval: (add-hook 'before-save-hook 'time-stamp)
@@ -11609,4 +11770,3 @@ directory should work if nowhere else does.}
 @c vim:sw=2:
 
 @enablebackslashhack
-
diff --git a/src/authorization/anastasis_authorization_plugin_totp.c 
b/src/authorization/anastasis_authorization_plugin_totp.c
index 74d7b7c..1f01652 100644
--- a/src/authorization/anastasis_authorization_plugin_totp.c
+++ b/src/authorization/anastasis_authorization_plugin_totp.c
@@ -278,7 +278,6 @@ totp_process (struct ANASTASIS_AUTHORIZATION_State *as,
     if (MHD_YES != mres)
       return ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED;
     return ANASTASIS_AUTHORIZATION_RES_FAILED;
-
   }
   for (unsigned int i = 0; i<=TIME_INTERVAL_RANGE * 2; i++)
     if (0 ==
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
index 0f3a969..83877bc 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -19,11 +19,13 @@ anastasis_httpd_SOURCES = \
   anastasis-httpd_mhd.c anastasis-httpd_mhd.h \
   anastasis-httpd_policy.c anastasis-httpd_policy.h \
   anastasis-httpd_policy-meta.c anastasis-httpd_policy-meta.h \
-  anastasis-httpd_policy_upload.c \
+  anastasis-httpd_policy-upload.c \
   anastasis-httpd_truth.c anastasis-httpd_truth.h \
   anastasis-httpd_terms.c anastasis-httpd_terms.h \
   anastasis-httpd_config.c anastasis-httpd_config.h \
-  anastasis-httpd_truth_upload.c
+  anastasis-httpd_truth-challenge.c \
+  anastasis-httpd_truth-solve.c \
+  anastasis-httpd_truth-upload.c
 
 anastasis_httpd_LDADD = \
   $(top_builddir)/src/util/libanastasisutil.la \
diff --git a/src/backend/anastasis-httpd.c b/src/backend/anastasis-httpd.c
index 4ef6087..0c9d957 100644
--- a/src/backend/anastasis-httpd.c
+++ b/src/backend/anastasis-httpd.c
@@ -1,6 +1,6 @@
 /*
   This file is part of Anastasis
-  (C) 2020 Anastasis SARL
+  (C) 2020-2022 Anastasis SARL
 
   Anastasis 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
@@ -416,12 +416,20 @@ url_handler (void *cls,
   {
     struct ANASTASIS_CRYPTO_TruthUUIDP tu;
     const char *pub_key_str;
+    const char *end;
+    size_t len;
 
     pub_key_str = &url[strlen ("/truth/")];
+    end = strchr (pub_key_str,
+                  '/');
+    if (NULL == end)
+      len = strlen (pub_key_str);
+    else
+      len = end - pub_key_str;
     if (GNUNET_OK !=
         GNUNET_STRINGS_string_to_data (
           pub_key_str,
-          strlen (pub_key_str),
+          len,
           &tu,
           sizeof(tu)))
     {
@@ -431,15 +439,17 @@ url_handler (void *cls,
                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                          "truth UUID");
     }
-    if (0 == strcmp (method,
-                     MHD_HTTP_METHOD_GET))
+    if ( (NULL == end) &&
+         (0 == strcmp (method,
+                       MHD_HTTP_METHOD_GET)) )
     {
       return AH_handler_truth_get (connection,
                                    &tu,
                                    hc);
     }
-    if (0 == strcmp (method,
-                     MHD_HTTP_METHOD_POST))
+    if ( (NULL == end) &&
+         (0 == strcmp (method,
+                       MHD_HTTP_METHOD_POST)) )
     {
       return AH_handler_truth_post (connection,
                                     hc,
@@ -447,6 +457,30 @@ url_handler (void *cls,
                                     upload_data,
                                     upload_data_size);
     }
+    if ( (NULL != end) &&
+         (0 == strcmp (end,
+                       "/solve")) &&
+         (0 == strcmp (method,
+                       MHD_HTTP_METHOD_POST)) )
+    {
+      return AH_handler_truth_solve (connection,
+                                     hc,
+                                     &tu,
+                                     upload_data,
+                                     upload_data_size);
+    }
+    if ( (NULL != end) &&
+         (0 == strcmp (end,
+                       "/challenge")) &&
+         (0 == strcmp (method,
+                       MHD_HTTP_METHOD_POST)) )
+    {
+      return AH_handler_truth_challenge (connection,
+                                         hc,
+                                         &tu,
+                                         upload_data,
+                                         upload_data_size);
+    }
     if (0 == strcmp (method,
                      MHD_HTTP_METHOD_OPTIONS))
     {
@@ -498,6 +532,8 @@ do_shutdown (void *cls)
   (void) cls;
   AH_resume_all_bc ();
   AH_truth_shutdown ();
+  AH_truth_challenge_shutdown ();
+  AH_truth_solve_shutdown ();
   AH_truth_upload_shutdown ();
   if (NULL != mhd_task)
   {
diff --git a/src/backend/anastasis-httpd_policy_upload.c 
b/src/backend/anastasis-httpd_policy-upload.c
similarity index 100%
rename from src/backend/anastasis-httpd_policy_upload.c
rename to src/backend/anastasis-httpd_policy-upload.c
diff --git a/src/backend/anastasis-httpd_truth-challenge.c 
b/src/backend/anastasis-httpd_truth-challenge.c
new file mode 100644
index 0000000..c583403
--- /dev/null
+++ b/src/backend/anastasis-httpd_truth-challenge.c
@@ -0,0 +1,1389 @@
+/*
+  This file is part of Anastasis
+  Copyright (C) 2019-2022 Anastasis SARL
+
+  Anastasis 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.
+
+  Anastasis 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
+  Anastasis; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis-httpd_truth-challenge.c
+ * @brief functions to handle incoming requests on /truth/$TID/challenge
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "anastasis-httpd.h"
+#include "anastasis_service.h"
+#include "anastasis-httpd_truth.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_rest_lib.h>
+#include "anastasis_authorization_lib.h"
+#include <taler/taler_merchant_service.h>
+#include <taler/taler_json_lib.h>
+#include <taler/taler_mhd_lib.h>
+
+/**
+ * What is the maximum frequency at which we allow
+ * clients to attempt to answer security questions?
+ */
+#define MAX_QUESTION_FREQ GNUNET_TIME_relative_multiply ( \
+    GNUNET_TIME_UNIT_SECONDS, 30)
+
+/**
+ * How long should the wallet check for auto-refunds before giving up?
+ */
+#define AUTO_REFUND_TIMEOUT GNUNET_TIME_relative_multiply ( \
+    GNUNET_TIME_UNIT_MINUTES, 2)
+
+
+/**
+ * How many retries do we allow per code?
+ */
+#define INITIAL_RETRY_COUNTER 3
+
+
+struct ChallengeContext
+{
+
+  /**
+   * Payment Identifier
+   */
+  struct ANASTASIS_PaymentSecretP payment_identifier;
+
+  /**
+   * Public key of the challenge which is solved.
+   */
+  struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
+
+  /**
+   * Key to decrypt the truth.
+   */
+  struct ANASTASIS_CRYPTO_TruthKeyP truth_key;
+
+  /**
+   * Cost for paying the challenge.
+   */
+  struct TALER_Amount challenge_cost;
+
+  /**
+   * Our handler context.
+   */
+  struct TM_HandlerContext *hc;
+
+  /**
+   * Opaque parsing context.
+   */
+  void *opaque_post_parsing_context;
+
+  /**
+   * Uploaded JSON data, NULL if upload is not yet complete.
+   */
+  json_t *root;
+
+  /**
+   * Kept in DLL for shutdown handling while suspended.
+   */
+  struct ChallengeContext *next;
+
+  /**
+   * Kept in DLL for shutdown handling while suspended.
+   */
+  struct ChallengeContext *prev;
+
+  /**
+   * Connection handle for closing or resuming
+   */
+  struct MHD_Connection *connection;
+
+  /**
+   * Reference to the authorization plugin which was loaded
+   */
+  struct ANASTASIS_AuthorizationPlugin *authorization;
+
+  /**
+   * Status of the authorization
+   */
+  struct ANASTASIS_AUTHORIZATION_State *as;
+
+  /**
+   * Used while we are awaiting proposal creation.
+   */
+  struct TALER_MERCHANT_PostOrdersHandle *po;
+
+  /**
+   * Used while we are waiting payment.
+   */
+  struct TALER_MERCHANT_OrderMerchantGetHandle *cpo;
+
+  /**
+   * HTTP response code to use on resume, if non-NULL.
+   */
+  struct MHD_Response *resp;
+
+  /**
+   * Our entry in the #to_heap, or NULL.
+   */
+  struct GNUNET_CONTAINER_HeapNode *hn;
+
+  /**
+   * How long do we wait at most for payment or
+   * authorization?
+   */
+  struct GNUNET_TIME_Absolute timeout;
+
+  /**
+   * Random authorization code we are using.
+   */
+  uint64_t code;
+
+  /**
+   * HTTP response code to use on resume, if resp is set.
+   */
+  unsigned int response_code;
+
+  /**
+   * true if client provided a payment secret / order ID?
+   */
+  bool payment_identifier_provided;
+
+  /**
+   * True if this entry is in the #gc_head DLL.
+   */
+  bool in_list;
+
+  /**
+   * True if this entry is currently suspended.
+   */
+  bool suspended;
+
+};
+
+
+/**
+ * Information we track for refunds.
+ */
+struct RefundEntry
+{
+  /**
+   * Kept in a DLL.
+   */
+  struct RefundEntry *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct RefundEntry *prev;
+
+  /**
+   * Operation handle.
+   */
+  struct TALER_MERCHANT_OrderRefundHandle *ro;
+
+  /**
+   * Which order is being refunded.
+   */
+  char *order_id;
+
+  /**
+   * Payment Identifier
+   */
+  struct ANASTASIS_PaymentSecretP payment_identifier;
+
+  /**
+   * Public key of the challenge which is solved.
+   */
+  struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
+};
+
+
+/**
+ * Head of linked list of active refund operations.
+ */
+static struct RefundEntry *re_head;
+
+/**
+ * Tail of linked list of active refund operations.
+ */
+static struct RefundEntry *re_tail;
+
+/**
+ * Head of linked list over all authorization processes
+ */
+static struct ChallengeContext *gc_head;
+
+/**
+ * Tail of linked list over all authorization processes
+ */
+static struct ChallengeContext *gc_tail;
+
+/**
+ * Task running #do_timeout().
+ */
+static struct GNUNET_SCHEDULER_Task *to_task;
+
+
+/**
+ * Generate a response telling the client that answering this
+ * challenge failed because the rate limit has been exceeded.
+ *
+ * @param gc request to answer for
+ * @return MHD status code
+ */
+static MHD_RESULT
+reply_rate_limited (const struct ChallengeContext *gc)
+{
+  return TALER_MHD_REPLY_JSON_PACK (
+    gc->connection,
+    MHD_HTTP_TOO_MANY_REQUESTS,
+    TALER_MHD_PACK_EC (TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED),
+    GNUNET_JSON_pack_uint64 ("request_limit",
+                             gc->authorization->retry_counter),
+    GNUNET_JSON_pack_time_rel ("request_frequency",
+                               gc->authorization->code_rotation_period));
+}
+
+
+/**
+ * Timeout requests that are past their due date.
+ *
+ * @param cls NULL
+ */
+static void
+do_timeout (void *cls)
+{
+  struct ChallengeContext *gc;
+
+  (void) cls;
+  to_task = NULL;
+  while (NULL !=
+         (gc = GNUNET_CONTAINER_heap_peek (AH_to_heap)))
+  {
+    if (GNUNET_TIME_absolute_is_future (gc->timeout))
+      break;
+    if (gc->suspended)
+    {
+      /* Test needed as we may have a "concurrent"
+         wakeup from another task that did not clear
+         this entry from the heap before the
+         response process concluded. */
+      gc->suspended = false;
+      MHD_resume_connection (gc->connection);
+    }
+    GNUNET_assert (NULL != gc->hn);
+    gc->hn = NULL;
+    GNUNET_assert (gc ==
+                   GNUNET_CONTAINER_heap_remove_root (AH_to_heap));
+  }
+  if (NULL == gc)
+    return;
+  to_task = GNUNET_SCHEDULER_add_at (gc->timeout,
+                                     &do_timeout,
+                                     NULL);
+}
+
+
+void
+AH_truth_challenge_shutdown (void)
+{
+  struct ChallengeContext *gc;
+  struct RefundEntry *re;
+
+  while (NULL != (re = re_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (re_head,
+                                 re_tail,
+                                 re);
+    if (NULL != re->ro)
+    {
+      TALER_MERCHANT_post_order_refund_cancel (re->ro);
+      re->ro = NULL;
+    }
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Refund `%s' failed due to shutdown\n",
+                re->order_id);
+    GNUNET_free (re->order_id);
+    GNUNET_free (re);
+  }
+
+  while (NULL != (gc = gc_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (gc_head,
+                                 gc_tail,
+                                 gc);
+    gc->in_list = false;
+    if (NULL != gc->cpo)
+    {
+      TALER_MERCHANT_merchant_order_get_cancel (gc->cpo);
+      gc->cpo = NULL;
+    }
+    if (NULL != gc->po)
+    {
+      TALER_MERCHANT_orders_post_cancel (gc->po);
+      gc->po = NULL;
+    }
+    if (gc->suspended)
+    {
+      gc->suspended = false;
+      MHD_resume_connection (gc->connection);
+    }
+    if (NULL != gc->as)
+    {
+      gc->authorization->cleanup (gc->as);
+      gc->as = NULL;
+      gc->authorization = NULL;
+    }
+  }
+  ANASTASIS_authorization_plugin_shutdown ();
+  if (NULL != to_task)
+  {
+    GNUNET_SCHEDULER_cancel (to_task);
+    to_task = NULL;
+  }
+}
+
+
+/**
+ * Callback to process a POST /orders/ID/refund request
+ *
+ * @param cls closure with a `struct RefundEntry *`
+ * @param hr HTTP response details
+ * @param taler_refund_uri the refund uri offered to the wallet
+ * @param h_contract hash of the contract a Browser may need to authorize
+ *        obtaining the HTTP response.
+ */
+static void
+refund_cb (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  const char *taler_refund_uri,
+  const struct TALER_PrivateContractHashP *h_contract)
+{
+  struct RefundEntry *re = cls;
+
+  re->ro = NULL;
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    {
+      enum GNUNET_DB_QueryStatus qs;
+
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Refund `%s' succeeded\n",
+                  re->order_id);
+      qs = db->record_challenge_refund (db->cls,
+                                        &re->truth_uuid,
+                                        &re->payment_identifier);
+      switch (qs)
+      {
+      case GNUNET_DB_STATUS_HARD_ERROR:
+        GNUNET_break (0);
+        break;
+      case GNUNET_DB_STATUS_SOFT_ERROR:
+        GNUNET_break (0);
+        break;
+      case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+        GNUNET_break (0);
+        break;
+      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+        break;
+      }
+    }
+    break;
+  default:
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Refund `%s' failed with HTTP status %u: %s (#%u)\n",
+                re->order_id,
+                hr->http_status,
+                hr->hint,
+                (unsigned int) hr->ec);
+    break;
+  }
+  GNUNET_CONTAINER_DLL_remove (re_head,
+                               re_tail,
+                               re);
+  GNUNET_free (re->order_id);
+  GNUNET_free (re);
+}
+
+
+/**
+ * Start to give a refund for the challenge created by @a gc.
+ *
+ * @param gc request where we failed and should now grant a refund for
+ */
+static void
+begin_refund (const struct ChallengeContext *gc)
+{
+  struct RefundEntry *re;
+
+  re = GNUNET_new (struct RefundEntry);
+  re->order_id = GNUNET_STRINGS_data_to_string_alloc (
+    &gc->payment_identifier,
+    sizeof (gc->payment_identifier));
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Challenge execution failed, triggering refund for order `%s'\n",
+              re->order_id);
+  re->payment_identifier = gc->payment_identifier;
+  re->truth_uuid = gc->truth_uuid;
+  re->ro = TALER_MERCHANT_post_order_refund (AH_ctx,
+                                             AH_backend_url,
+                                             re->order_id,
+                                             &gc->challenge_cost,
+                                             "failed to issue challenge",
+                                             &refund_cb,
+                                             re);
+  if (NULL == re->ro)
+  {
+    GNUNET_break (0);
+    GNUNET_free (re->order_id);
+    GNUNET_free (re);
+    return;
+  }
+  GNUNET_CONTAINER_DLL_insert (re_head,
+                               re_tail,
+                               re);
+}
+
+
+/**
+ * Callback used to notify the application about completed requests.
+ * Cleans up the requests data structures.
+ *
+ * @param hc
+ */
+static void
+request_done (struct TM_HandlerContext *hc)
+{
+  struct ChallengeContext *gc = hc->ctx;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Request completed\n");
+  if (NULL == gc)
+    return;
+  hc->cc = NULL;
+  GNUNET_assert (! gc->suspended);
+  if (gc->in_list)
+  {
+    GNUNET_CONTAINER_DLL_remove (gc_head,
+                                 gc_tail,
+                                 gc);
+    gc->in_list = false;
+  }
+  if (NULL != gc->hn)
+  {
+    GNUNET_assert (gc ==
+                   GNUNET_CONTAINER_heap_remove_node (gc->hn));
+    gc->hn = NULL;
+  }
+  if (NULL != gc->as)
+  {
+    gc->authorization->cleanup (gc->as);
+    gc->authorization = NULL;
+    gc->as = NULL;
+  }
+  if (NULL != gc->cpo)
+  {
+    TALER_MERCHANT_merchant_order_get_cancel (gc->cpo);
+    gc->cpo = NULL;
+  }
+  if (NULL != gc->po)
+  {
+    TALER_MERCHANT_orders_post_cancel (gc->po);
+    gc->po = NULL;
+  }
+  if (NULL != gc->root)
+  {
+    json_decref (gc->root);
+    gc->root = NULL;
+  }
+  TALER_MHD_parse_post_cleanup_callback (gc->opaque_post_parsing_context);
+  GNUNET_free (gc);
+  hc->ctx = NULL;
+}
+
+
+/**
+ * Transmit a payment request for @a order_id on @a connection
+ *
+ * @param gc context to make payment request for
+ */
+static void
+make_payment_request (struct ChallengeContext *gc)
+{
+  struct MHD_Response *resp;
+
+  resp = MHD_create_response_from_buffer (0,
+                                          NULL,
+                                          MHD_RESPMEM_PERSISTENT);
+  GNUNET_assert (NULL != resp);
+  TALER_MHD_add_global_headers (resp);
+  {
+    char *hdr;
+    char *order_id;
+    const char *pfx;
+    const char *hn;
+
+    if (0 == strncasecmp ("https://";,
+                          AH_backend_url,
+                          strlen ("https://";)))
+    {
+      pfx = "taler://";
+      hn = &AH_backend_url[strlen ("https://";)];
+    }
+    else if (0 == strncasecmp ("http://";,
+                               AH_backend_url,
+                               strlen ("http://";)))
+    {
+      pfx = "taler+http://";;
+      hn = &AH_backend_url[strlen ("http://";)];
+    }
+    else
+    {
+      /* This invariant holds as per check in anastasis-httpd.c */
+      GNUNET_assert (0);
+    }
+    /* This invariant holds as per check in anastasis-httpd.c */
+    GNUNET_assert (0 != strlen (hn));
+
+    order_id = GNUNET_STRINGS_data_to_string_alloc (
+      &gc->payment_identifier,
+      sizeof (gc->payment_identifier));
+    GNUNET_asprintf (&hdr,
+                     "%spay/%s%s/",
+                     pfx,
+                     hn,
+                     order_id);
+    GNUNET_free (order_id);
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Sending payment request `%s'\n",
+                hdr);
+    GNUNET_break (MHD_YES ==
+                  MHD_add_response_header (resp,
+                                           ANASTASIS_HTTP_HEADER_TALER,
+                                           hdr));
+    GNUNET_free (hdr);
+  }
+  gc->resp = resp;
+  gc->response_code = MHD_HTTP_PAYMENT_REQUIRED;
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * /contract request to a merchant.
+ *
+ * @param cls our `struct ChallengeContext`
+ * @param por response details
+ */
+static void
+proposal_cb (void *cls,
+             const struct TALER_MERCHANT_PostOrdersReply *por)
+{
+  struct ChallengeContext *gc = cls;
+  enum GNUNET_DB_QueryStatus qs;
+
+  gc->po = NULL;
+  GNUNET_assert (gc->in_list);
+  GNUNET_CONTAINER_DLL_remove (gc_head,
+                               gc_tail,
+                               gc);
+  gc->in_list = false;
+  GNUNET_assert (gc->suspended);
+  gc->suspended = false;
+  MHD_resume_connection (gc->connection);
+  AH_trigger_daemon (NULL);
+  if (MHD_HTTP_OK != por->hr.http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Backend returned status %u/%d\n",
+                por->hr.http_status,
+                (int) por->hr.ec);
+    GNUNET_break (0);
+    gc->resp = TALER_MHD_MAKE_JSON_PACK (
+      GNUNET_JSON_pack_uint64 ("code",
+                               
TALER_EC_ANASTASIS_TRUTH_PAYMENT_CREATE_BACKEND_ERROR),
+      GNUNET_JSON_pack_string ("hint",
+                               "Failed to setup order with merchant backend"),
+      GNUNET_JSON_pack_uint64 ("backend-ec",
+                               por->hr.ec),
+      GNUNET_JSON_pack_uint64 ("backend-http-status",
+                               por->hr.http_status),
+      GNUNET_JSON_pack_allow_null (
+        GNUNET_JSON_pack_object_steal ("backend-reply",
+                                       (json_t *) por->hr.reply)));
+    gc->response_code = MHD_HTTP_BAD_GATEWAY;
+    return;
+  }
+  qs = db->record_challenge_payment (db->cls,
+                                     &gc->truth_uuid,
+                                     &gc->payment_identifier,
+                                     &gc->challenge_cost);
+  if (0 >= qs)
+  {
+    GNUNET_break (0);
+    gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
+                                     "record challenge payment");
+    gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+    return;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Setup fresh order, creating payment request\n");
+  make_payment_request (gc);
+}
+
+
+/**
+ * Callback to process a GET /check-payment request
+ *
+ * @param cls our `struct ChallengeContext`
+ * @param hr HTTP response details
+ * @param osr order status
+ */
+static void
+check_payment_cb (void *cls,
+                  const struct TALER_MERCHANT_HttpResponse *hr,
+                  const struct TALER_MERCHANT_OrderStatusResponse *osr)
+
+{
+  struct ChallengeContext *gc = cls;
+
+  gc->cpo = NULL;
+  GNUNET_assert (gc->in_list);
+  GNUNET_CONTAINER_DLL_remove (gc_head,
+                               gc_tail,
+                               gc);
+  gc->in_list = false;
+  GNUNET_assert (gc->suspended);
+  gc->suspended = false;
+  MHD_resume_connection (gc->connection);
+  AH_trigger_daemon (NULL);
+
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    GNUNET_assert (NULL != osr);
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* We created this order before, how can it be not found now? */
+    GNUNET_break (0);
+    gc->resp = TALER_MHD_make_error 
(TALER_EC_ANASTASIS_TRUTH_ORDER_DISAPPEARED,
+                                     NULL);
+    gc->response_code = MHD_HTTP_BAD_GATEWAY;
+    return;
+  case MHD_HTTP_BAD_GATEWAY:
+    gc->resp = TALER_MHD_make_error (
+      TALER_EC_ANASTASIS_TRUTH_BACKEND_EXCHANGE_BAD,
+      NULL);
+    gc->response_code = MHD_HTTP_BAD_GATEWAY;
+    return;
+  case MHD_HTTP_GATEWAY_TIMEOUT:
+    gc->resp = TALER_MHD_make_error 
(TALER_EC_ANASTASIS_GENERIC_BACKEND_TIMEOUT,
+                                     "Timeout check payment status");
+    GNUNET_assert (NULL != gc->resp);
+    gc->response_code = MHD_HTTP_GATEWAY_TIMEOUT;
+    return;
+  default:
+    {
+      char status[14];
+
+      GNUNET_snprintf (status,
+                       sizeof (status),
+                       "%u",
+                       hr->http_status);
+      gc->resp = TALER_MHD_make_error (
+        TALER_EC_ANASTASIS_TRUTH_UNEXPECTED_PAYMENT_STATUS,
+        status);
+      GNUNET_assert (NULL != gc->resp);
+      gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+      return;
+    }
+  }
+
+  switch (osr->status)
+  {
+  case TALER_MERCHANT_OSC_PAID:
+    {
+      enum GNUNET_DB_QueryStatus qs;
+
+      qs = db->update_challenge_payment (db->cls,
+                                         &gc->truth_uuid,
+                                         &gc->payment_identifier);
+      if (0 <= qs)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                    "Order has been paid, continuing with request 
processing\n");
+        return; /* continue as planned */
+      }
+      GNUNET_break (0);
+      gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
+                                       "update challenge payment");
+      gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+      return; /* continue as planned */
+    }
+  case TALER_MERCHANT_OSC_CLAIMED:
+  case TALER_MERCHANT_OSC_UNPAID:
+    /* repeat payment request */
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Order remains unpaid, sending payment request again\n");
+    make_payment_request (gc);
+    return;
+  }
+  /* should never get here */
+  GNUNET_break (0);
+}
+
+
+/**
+ * Helper function used to ask our backend to begin processing a
+ * payment for the user's account.  May perform asynchronous
+ * operations by suspending the connection if required.
+ *
+ * @param gc context to begin payment for.
+ * @return MHD status code
+ */
+static MHD_RESULT
+begin_payment (struct ChallengeContext *gc)
+{
+  enum GNUNET_DB_QueryStatus qs;
+  char *order_id;
+
+  qs = db->lookup_challenge_payment (db->cls,
+                                     &gc->truth_uuid,
+                                     &gc->payment_identifier);
+  if (qs < 0)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (gc->connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                       "lookup challenge payment");
+  }
+  GNUNET_assert (! gc->in_list);
+  gc->in_list = true;
+  GNUNET_CONTAINER_DLL_insert (gc_tail,
+                               gc_head,
+                               gc);
+  GNUNET_assert (! gc->suspended);
+  gc->suspended = true;
+  MHD_suspend_connection (gc->connection);
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+  {
+    /* We already created the order, check if it was paid */
+    struct GNUNET_TIME_Relative timeout;
+
+    order_id = GNUNET_STRINGS_data_to_string_alloc (
+      &gc->payment_identifier,
+      sizeof (gc->payment_identifier));
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Order exists, checking payment status for order `%s'\n",
+                order_id);
+    timeout = GNUNET_TIME_absolute_get_remaining (gc->timeout);
+    gc->cpo = TALER_MERCHANT_merchant_order_get (AH_ctx,
+                                                 AH_backend_url,
+                                                 order_id,
+                                                 NULL /* NOT session-bound */,
+                                                 false,
+                                                 timeout,
+                                                 &check_payment_cb,
+                                                 gc);
+  }
+  else
+  {
+    /* Create a fresh order */
+    json_t *order;
+    struct GNUNET_TIME_Timestamp pay_deadline;
+
+    GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+                                &gc->payment_identifier,
+                                sizeof (struct ANASTASIS_PaymentSecretP));
+    order_id = GNUNET_STRINGS_data_to_string_alloc (
+      &gc->payment_identifier,
+      sizeof (gc->payment_identifier));
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Creating fresh order `%s'\n",
+                order_id);
+    pay_deadline = GNUNET_TIME_relative_to_timestamp (
+      ANASTASIS_CHALLENGE_OFFER_LIFETIME);
+    order = GNUNET_JSON_PACK (
+      TALER_JSON_pack_amount ("amount",
+                              &gc->challenge_cost),
+      GNUNET_JSON_pack_string ("summary",
+                               "challenge fee for anastasis service"),
+      GNUNET_JSON_pack_string ("order_id",
+                               order_id),
+      GNUNET_JSON_pack_time_rel ("auto_refund",
+                                 AUTO_REFUND_TIMEOUT),
+      GNUNET_JSON_pack_timestamp ("pay_deadline",
+                                  pay_deadline));
+    gc->po = TALER_MERCHANT_orders_post2 (AH_ctx,
+                                          AH_backend_url,
+                                          order,
+                                          AUTO_REFUND_TIMEOUT,
+                                          NULL, /* no payment target */
+                                          0,
+                                          NULL, /* no inventory products */
+                                          0,
+                                          NULL, /* no uuids */
+                                          false, /* do NOT require claim token 
*/
+                                          &proposal_cb,
+                                          gc);
+    json_decref (order);
+  }
+  GNUNET_free (order_id);
+  AH_trigger_curl ();
+  return MHD_YES;
+}
+
+
+/**
+ * Mark @a gc as suspended and update the respective
+ * data structures and jobs.
+ *
+ * @param[in,out] gc context of the suspended operation
+ */
+static void
+gc_suspended (struct ChallengeContext *gc)
+{
+  gc->suspended = true;
+  if (NULL == AH_to_heap)
+    AH_to_heap = GNUNET_CONTAINER_heap_create (
+      GNUNET_CONTAINER_HEAP_ORDER_MIN);
+  gc->hn = GNUNET_CONTAINER_heap_insert (AH_to_heap,
+                                         gc,
+                                         gc->timeout.abs_value_us);
+  if (NULL != to_task)
+  {
+    GNUNET_SCHEDULER_cancel (to_task);
+    to_task = NULL;
+  }
+  {
+    struct ChallengeContext *rn;
+
+    rn = GNUNET_CONTAINER_heap_peek (AH_to_heap);
+    to_task = GNUNET_SCHEDULER_add_at (rn->timeout,
+                                       &do_timeout,
+                                       NULL);
+  }
+}
+
+
+/**
+ * Run the authorization method-specific 'process' function and continue
+ * based on its result with generating an HTTP response.
+ *
+ * @param connection the connection we are handling
+ * @param gc our overall handler context
+ */
+static MHD_RESULT
+run_authorization_process (struct MHD_Connection *connection,
+                           struct ChallengeContext *gc)
+{
+  enum ANASTASIS_AUTHORIZATION_Result ret;
+  enum GNUNET_DB_QueryStatus qs;
+
+  GNUNET_assert (! gc->suspended);
+  ret = gc->authorization->process (gc->as,
+                                    gc->timeout,
+                                    connection);
+  switch (ret)
+  {
+  case ANASTASIS_AUTHORIZATION_RES_SUCCESS:
+    /* Challenge sent successfully */
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Authorization request sent successfully\n");
+    qs = db->mark_challenge_sent (db->cls,
+                                  &gc->payment_identifier,
+                                  &gc->truth_uuid,
+                                  gc->code);
+    GNUNET_break (0 < qs);
+    gc->authorization->cleanup (gc->as);
+    gc->as = NULL;
+    return MHD_YES;
+  case ANASTASIS_AUTHORIZATION_RES_FAILED:
+    if (gc->payment_identifier_provided)
+    {
+      begin_refund (gc);
+    }
+    gc->authorization->cleanup (gc->as);
+    gc->as = NULL;
+    return MHD_YES;
+  case ANASTASIS_AUTHORIZATION_RES_SUSPENDED:
+    /* connection was suspended */
+    gc_suspended (gc);
+    return MHD_YES;
+  case ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED:
+    /* Challenge sent successfully */
+    qs = db->mark_challenge_sent (db->cls,
+                                  &gc->payment_identifier,
+                                  &gc->truth_uuid,
+                                  gc->code);
+    GNUNET_break (0 < qs);
+    gc->authorization->cleanup (gc->as);
+    gc->as = NULL;
+    return MHD_NO;
+  case ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED:
+    gc->authorization->cleanup (gc->as);
+    gc->as = NULL;
+    return MHD_NO;
+  case ANASTASIS_AUTHORIZATION_RES_FINISHED:
+    /* Neither case should EVER happen here! */
+    GNUNET_break (0);
+    GNUNET_assert (! gc->suspended);
+    gc->authorization->cleanup (gc->as);
+    gc->as = NULL;
+    if (gc->in_list)
+    {
+      GNUNET_CONTAINER_DLL_remove (gc_head,
+                                   gc_tail,
+                                   gc);
+      gc->in_list = false;
+    }
+    return TALER_MHD_reply_with_error (gc->connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+                                       "authorization successful when we were 
only supposed to be challenging");
+  }
+  GNUNET_break (0);
+  return MHD_NO;
+}
+
+
+MHD_RESULT
+AH_handler_truth_challenge (
+  struct MHD_Connection *connection,
+  struct TM_HandlerContext *hc,
+  const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+  const char *upload_data,
+  size_t *upload_data_size)
+{
+  struct ChallengeContext *gc = hc->ctx;
+  void *encrypted_truth;
+  size_t encrypted_truth_size;
+  void *decrypted_truth;
+  size_t decrypted_truth_size;
+  char *truth_mime = NULL;
+
+  if (NULL == gc)
+  {
+    /* Fresh request, do initial setup */
+    gc = GNUNET_new (struct ChallengeContext);
+    gc->hc = hc;
+    hc->ctx = gc;
+    gc->connection = connection;
+    gc->truth_uuid = *truth_uuid;
+    gc->hc->cc = &request_done;
+    {
+      const char *pay_id;
+
+      pay_id = MHD_lookup_connection_value (connection,
+                                            MHD_HEADER_KIND,
+                                            
ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER);
+      if (NULL != pay_id)
+      {
+        if (GNUNET_OK !=
+            GNUNET_STRINGS_string_to_data (
+              pay_id,
+              strlen (pay_id),
+              &gc->payment_identifier,
+              sizeof (struct ANASTASIS_PaymentSecretP)))
+        {
+          GNUNET_break_op (0);
+          return TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_BAD_REQUEST,
+                                             
TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                             
ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER);
+        }
+        gc->payment_identifier_provided = true;
+        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                    "Client provided payment identifier `%s'\n",
+                    pay_id);
+      }
+    }
+
+    {
+      const char *long_poll_timeout_ms;
+
+      long_poll_timeout_ms = MHD_lookup_connection_value (connection,
+                                                          
MHD_GET_ARGUMENT_KIND,
+                                                          "timeout_ms");
+      if (NULL != long_poll_timeout_ms)
+      {
+        unsigned int timeout;
+        char dummy;
+
+        if (1 != sscanf (long_poll_timeout_ms,
+                         "%u%c",
+                         &timeout,
+                         &dummy))
+        {
+          GNUNET_break_op (0);
+          return TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_BAD_REQUEST,
+                                             
TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                             "timeout_ms (must be non-negative 
number)");
+        }
+        gc->timeout
+          = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
+                                                GNUNET_TIME_UNIT_MILLISECONDS,
+                                                timeout));
+      }
+      else
+      {
+        gc->timeout = GNUNET_TIME_relative_to_absolute (
+          GNUNET_TIME_UNIT_SECONDS);
+      }
+    }
+  } /* end of first-time initialization (if NULL == gc) */
+  else
+  {
+    /* might have been woken up by authorization plugin,
+       so clear the flag. MDH called us, so we are
+       clearly no longer suspended */
+    gc->suspended = false;
+    if (NULL != gc->resp)
+    {
+      MHD_RESULT ret;
+
+      /* We generated a response asynchronously, queue that */
+      ret = MHD_queue_response (connection,
+                                gc->response_code,
+                                gc->resp);
+      GNUNET_break (MHD_YES == ret);
+      MHD_destroy_response (gc->resp);
+      gc->resp = NULL;
+      return ret;
+    }
+    if (NULL != gc->as)
+    {
+      /* Authorization process is "running", check what is going on */
+      GNUNET_assert (NULL != gc->authorization);
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Continuing with running the authorization process\n");
+      GNUNET_assert (! gc->suspended);
+      return run_authorization_process (connection,
+                                        gc);
+
+    }
+    /* We get here if the async check for payment said this request
+       was indeed paid! */
+  }
+
+  /* parse byte stream upload into JSON */
+  if (NULL == gc->root)
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_post_json (connection,
+                                     &gc->opaque_post_parsing_context,
+                                     upload_data,
+                                     upload_data_size,
+                                     &gc->root);
+    if (GNUNET_SYSERR == res)
+    {
+      GNUNET_assert (NULL == gc->root);
+      return MHD_NO; /* bad upload, could not even generate error */
+    }
+    if ( (GNUNET_NO == res) ||
+         (NULL == gc->root) )
+    {
+      GNUNET_assert (NULL == gc->root);
+      return MHD_YES; /* so far incomplete upload or parser error */
+    }
+
+    /* 'root' is now initialized */
+    {
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_fixed_auto ("truth_decryption_key",
+                                     &gc->truth_key),
+        GNUNET_JSON_spec_end ()
+      };
+      enum GNUNET_GenericReturnValue res;
+
+      res = TALER_MHD_parse_json_data (connection,
+                                       gc->root,
+                                       spec);
+      if (GNUNET_SYSERR == res)
+      {
+        GNUNET_break (0);
+        return MHD_NO; /* hard failure */
+      }
+      if (GNUNET_NO == res)
+      {
+        GNUNET_break_op (0);
+        return MHD_YES; /* failure */
+      }
+    }
+  }
+
+  {
+    /* load encrypted truth from DB */
+    enum GNUNET_DB_QueryStatus qs;
+    char *method;
+
+    qs = db->get_escrow_challenge (db->cls,
+                                   &gc->truth_uuid,
+                                   &encrypted_truth,
+                                   &encrypted_truth_size,
+                                   &truth_mime,
+                                   &method);
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_HARD_ERROR:
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (gc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                         "get escrow challenge");
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_ANASTASIS_TRUTH_UNKNOWN,
+                                         NULL);
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      break;
+    }
+    if (0 == strcmp ("question",
+                     method))
+    {
+      GNUNET_break_op (0);
+      GNUNET_free (encrypted_truth);
+      GNUNET_free (truth_mime);
+      GNUNET_free (method);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         
TALER_EC_ANASTASIS_TRUTH_CHALLENGE_WRONG_METHOD,
+                                         "question");
+    }
+
+    gc->authorization
+      = ANASTASIS_authorization_plugin_load (method,
+                                             db,
+                                             AH_cfg);
+    if (NULL == gc->authorization)
+    {
+      MHD_RESULT ret;
+
+      ret = TALER_MHD_reply_with_error (
+        connection,
+        MHD_HTTP_INTERNAL_SERVER_ERROR,
+        TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_METHOD_NO_LONGER_SUPPORTED,
+        method);
+      GNUNET_free (encrypted_truth);
+      GNUNET_free (truth_mime);
+      GNUNET_free (method);
+      return ret;
+    }
+
+    if (gc->authorization->user_provided_code)
+    {
+      MHD_RESULT ret;
+
+      GNUNET_break_op (0);
+      ret = TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_BAD_REQUEST,
+                                        
TALER_EC_ANASTASIS_TRUTH_CHALLENGE_WRONG_METHOD,
+                                        method);
+      GNUNET_free (encrypted_truth);
+      GNUNET_free (truth_mime);
+      GNUNET_free (method);
+      return ret;
+    }
+
+    gc->challenge_cost = gc->authorization->cost;
+    GNUNET_free (method);
+  }
+
+  if (! gc->authorization->payment_plugin_managed)
+  {
+    if (! TALER_amount_is_zero (&gc->challenge_cost))
+    {
+      /* Check database to see if the transaction is paid for */
+      enum GNUNET_DB_QueryStatus qs;
+      bool paid;
+
+      if (! gc->payment_identifier_provided)
+      {
+        GNUNET_free (truth_mime);
+        GNUNET_free (encrypted_truth);
+        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                    "Beginning payment, client did not provide payment 
identifier\n");
+        return begin_payment (gc);
+      }
+      qs = db->check_challenge_payment (db->cls,
+                                        &gc->payment_identifier,
+                                        &gc->truth_uuid,
+                                        &paid);
+      switch (qs)
+      {
+      case GNUNET_DB_STATUS_HARD_ERROR:
+      case GNUNET_DB_STATUS_SOFT_ERROR:
+        GNUNET_break (0);
+        GNUNET_free (truth_mime);
+        GNUNET_free (encrypted_truth);
+        return TALER_MHD_reply_with_error (gc->connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                           "check challenge payment");
+      case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+        /* Create fresh payment identifier (cannot trust client) */
+        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                    "Client-provided payment identifier is unknown.\n");
+        GNUNET_free (truth_mime);
+        GNUNET_free (encrypted_truth);
+        return begin_payment (gc);
+      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+        if (! paid)
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                      "Payment identifier known. Checking payment with 
client's payment identifier\n");
+          GNUNET_free (truth_mime);
+          GNUNET_free (encrypted_truth);
+          return begin_payment (gc);
+        }
+        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                    "Payment confirmed\n");
+        break;
+      }
+    }
+    else
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Request is free of charge\n");
+    }
+  }
+
+  /* We've been paid, now validate response */
+  {
+    /* decrypt encrypted_truth */
+    ANASTASIS_CRYPTO_truth_decrypt (&gc->truth_key,
+                                    encrypted_truth,
+                                    encrypted_truth_size,
+                                    &decrypted_truth,
+                                    &decrypted_truth_size);
+    GNUNET_free (encrypted_truth);
+  }
+  if (NULL == decrypted_truth)
+  {
+    GNUNET_free (truth_mime);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_EXPECTATION_FAILED,
+                                       
TALER_EC_ANASTASIS_TRUTH_DECRYPTION_FAILED,
+                                       NULL);
+  }
+
+  /* Not security question and no answer: use plugin to check if
+     decrypted truth is a valid challenge! */
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "No challenge provided, creating fresh challenge\n");
+  {
+    enum GNUNET_GenericReturnValue ret;
+
+    ret = gc->authorization->validate (gc->authorization->cls,
+                                       connection,
+                                       truth_mime,
+                                       decrypted_truth,
+                                       decrypted_truth_size);
+    GNUNET_free (truth_mime);
+    switch (ret)
+    {
+    case GNUNET_OK:
+      /* data valid, continued below */
+      break;
+    case GNUNET_NO:
+      /* data invalid, reply was queued */
+      GNUNET_free (decrypted_truth);
+      return MHD_YES;
+    case GNUNET_SYSERR:
+      /* data invalid, reply was NOT queued */
+      GNUNET_free (decrypted_truth);
+      return MHD_NO;
+    }
+  }
+
+  /* Setup challenge and begin authorization process */
+  {
+    struct GNUNET_TIME_Timestamp transmission_date;
+    enum GNUNET_DB_QueryStatus qs;
+
+    qs = db->create_challenge_code (db->cls,
+                                    &gc->truth_uuid,
+                                    gc->authorization->code_rotation_period,
+                                    gc->authorization->code_validity_period,
+                                    gc->authorization->retry_counter,
+                                    &transmission_date,
+                                    &gc->code);
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_HARD_ERROR:
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      GNUNET_break (0);
+      GNUNET_free (decrypted_truth);
+      return TALER_MHD_reply_with_error (gc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                         "create_challenge_code");
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      /* 0 == retry_counter of existing challenge => rate limit exceeded */
+      GNUNET_free (decrypted_truth);
+      return reply_rate_limited (gc);
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      /* challenge code was stored successfully*/
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Created fresh challenge\n");
+      break;
+    }
+
+    if (GNUNET_TIME_relative_cmp (
+          GNUNET_TIME_absolute_get_duration (
+            transmission_date.abs_time),
+          <,
+          gc->authorization->code_retransmission_frequency) )
+    {
+      /* Too early for a retransmission! */
+      GNUNET_free (decrypted_truth);
+      return TALER_MHD_reply_with_error (gc->connection,
+                                         MHD_HTTP_ALREADY_REPORTED,
+                                         
TALER_EC_ANASTASIS_TRUTH_CHALLENGE_ACTIVE,
+                                         NULL);
+    }
+  }
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Beginning authorization process\n");
+  gc->as = gc->authorization->start (gc->authorization->cls,
+                                     &AH_trigger_daemon,
+                                     NULL,
+                                     &gc->truth_uuid,
+                                     gc->code,
+                                     decrypted_truth,
+                                     decrypted_truth_size);
+  GNUNET_free (decrypted_truth);
+  if (NULL == gc->as)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (gc->connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED,
+                                       NULL);
+  }
+  if (! gc->in_list)
+  {
+    gc->in_list = true;
+    GNUNET_CONTAINER_DLL_insert (gc_head,
+                                 gc_tail,
+                                 gc);
+  }
+  GNUNET_assert (! gc->suspended);
+  return run_authorization_process (connection,
+                                    gc);
+}
diff --git a/src/backend/anastasis-httpd_truth-solve.c 
b/src/backend/anastasis-httpd_truth-solve.c
new file mode 100644
index 0000000..577ec50
--- /dev/null
+++ b/src/backend/anastasis-httpd_truth-solve.c
@@ -0,0 +1,1430 @@
+/*
+  This file is part of Anastasis
+  Copyright (C) 2019-2022 Anastasis SARL
+
+  Anastasis 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.
+
+  Anastasis 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
+  Anastasis; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis-httpd_truth-solve.c
+ * @brief functions to handle incoming requests on /truth/$TID/solve
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "anastasis-httpd.h"
+#include "anastasis_service.h"
+#include "anastasis-httpd_truth.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_rest_lib.h>
+#include "anastasis_authorization_lib.h"
+#include <taler/taler_merchant_service.h>
+#include <taler/taler_json_lib.h>
+#include <taler/taler_mhd_lib.h>
+
+/**
+ * What is the maximum frequency at which we allow
+ * clients to attempt to answer security questions?
+ */
+#define MAX_QUESTION_FREQ GNUNET_TIME_relative_multiply ( \
+    GNUNET_TIME_UNIT_SECONDS, 30)
+
+/**
+ * How long should the wallet check for auto-refunds before giving up?
+ */
+#define AUTO_REFUND_TIMEOUT GNUNET_TIME_relative_multiply ( \
+    GNUNET_TIME_UNIT_MINUTES, 2)
+
+
+/**
+ * How many retries do we allow per code?
+ */
+#define INITIAL_RETRY_COUNTER 3
+
+
+struct SolveContext
+{
+
+  /**
+   * Payment Identifier
+   */
+  struct ANASTASIS_PaymentSecretP payment_identifier;
+
+  /**
+   * Public key of the challenge which is solved.
+   */
+  struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
+
+  /**
+   * Key to decrypt the truth.
+   */
+  struct ANASTASIS_CRYPTO_TruthKeyP truth_key;
+
+  /**
+   * Cost for paying the challenge.
+   */
+  struct TALER_Amount challenge_cost;
+
+  /**
+   * Our handler context.
+   */
+  struct TM_HandlerContext *hc;
+
+  /**
+   * Opaque parsing context.
+   */
+  void *opaque_post_parsing_context;
+
+  /**
+   * Uploaded JSON data, NULL if upload is not yet complete.
+   */
+  json_t *root;
+
+  /**
+   * Kept in DLL for shutdown handling while suspended.
+   */
+  struct SolveContext *next;
+
+  /**
+   * Kept in DLL for shutdown handling while suspended.
+   */
+  struct SolveContext *prev;
+
+  /**
+   * Connection handle for closing or resuming
+   */
+  struct MHD_Connection *connection;
+
+  /**
+   * Reference to the authorization plugin which was loaded
+   */
+  struct ANASTASIS_AuthorizationPlugin *authorization;
+
+  /**
+   * Status of the authorization
+   */
+  struct ANASTASIS_AUTHORIZATION_State *as;
+
+  /**
+   * Used while we are awaiting proposal creation.
+   */
+  struct TALER_MERCHANT_PostOrdersHandle *po;
+
+  /**
+   * Used while we are waiting payment.
+   */
+  struct TALER_MERCHANT_OrderMerchantGetHandle *cpo;
+
+  /**
+   * HTTP response code to use on resume, if non-NULL.
+   */
+  struct MHD_Response *resp;
+
+  /**
+   * Our entry in the #to_heap, or NULL.
+   */
+  struct GNUNET_CONTAINER_HeapNode *hn;
+
+  /**
+   * Challenge response we got from the request.
+   */
+  struct GNUNET_HashCode challenge_response;
+
+  /**
+   * How long do we wait at most for payment or
+   * authorization?
+   */
+  struct GNUNET_TIME_Absolute timeout;
+
+  /**
+   * Random authorization code we are using.
+   */
+  uint64_t code;
+
+  /**
+   * HTTP response code to use on resume, if resp is set.
+   */
+  unsigned int response_code;
+
+  /**
+   * true if client provided a payment secret / order ID?
+   */
+  bool payment_identifier_provided;
+
+  /**
+   * True if this entry is in the #gc_head DLL.
+   */
+  bool in_list;
+
+  /**
+   * True if this entry is currently suspended.
+   */
+  bool suspended;
+
+};
+
+
+/**
+ * Head of linked list over all authorization processes
+ */
+static struct SolveContext *gc_head;
+
+/**
+ * Tail of linked list over all authorization processes
+ */
+static struct SolveContext *gc_tail;
+
+/**
+ * Task running #do_timeout().
+ */
+static struct GNUNET_SCHEDULER_Task *to_task;
+
+
+/**
+ * Generate a response telling the client that answering this
+ * challenge failed because the rate limit has been exceeded.
+ *
+ * @param gc request to answer for
+ * @return MHD status code
+ */
+static MHD_RESULT
+reply_rate_limited (const struct SolveContext *gc)
+{
+  return TALER_MHD_REPLY_JSON_PACK (
+    gc->connection,
+    MHD_HTTP_TOO_MANY_REQUESTS,
+    TALER_MHD_PACK_EC (TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED),
+    GNUNET_JSON_pack_uint64 ("request_limit",
+                             gc->authorization->retry_counter),
+    GNUNET_JSON_pack_time_rel ("request_frequency",
+                               gc->authorization->code_rotation_period));
+}
+
+
+/**
+ * Timeout requests that are past their due date.
+ *
+ * @param cls NULL
+ */
+static void
+do_timeout (void *cls)
+{
+  struct SolveContext *gc;
+
+  (void) cls;
+  to_task = NULL;
+  while (NULL !=
+         (gc = GNUNET_CONTAINER_heap_peek (AH_to_heap)))
+  {
+    if (GNUNET_TIME_absolute_is_future (gc->timeout))
+      break;
+    if (gc->suspended)
+    {
+      /* Test needed as we may have a "concurrent"
+         wakeup from another task that did not clear
+         this entry from the heap before the
+         response process concluded. */
+      gc->suspended = false;
+      MHD_resume_connection (gc->connection);
+    }
+    GNUNET_assert (NULL != gc->hn);
+    gc->hn = NULL;
+    GNUNET_assert (gc ==
+                   GNUNET_CONTAINER_heap_remove_root (AH_to_heap));
+  }
+  if (NULL == gc)
+    return;
+  to_task = GNUNET_SCHEDULER_add_at (gc->timeout,
+                                     &do_timeout,
+                                     NULL);
+}
+
+
+void
+AH_truth_solve_shutdown (void)
+{
+  struct SolveContext *gc;
+
+  while (NULL != (gc = gc_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (gc_head,
+                                 gc_tail,
+                                 gc);
+    gc->in_list = false;
+    if (NULL != gc->cpo)
+    {
+      TALER_MERCHANT_merchant_order_get_cancel (gc->cpo);
+      gc->cpo = NULL;
+    }
+    if (NULL != gc->po)
+    {
+      TALER_MERCHANT_orders_post_cancel (gc->po);
+      gc->po = NULL;
+    }
+    if (gc->suspended)
+    {
+      gc->suspended = false;
+      MHD_resume_connection (gc->connection);
+    }
+    if (NULL != gc->as)
+    {
+      gc->authorization->cleanup (gc->as);
+      gc->as = NULL;
+      gc->authorization = NULL;
+    }
+  }
+  ANASTASIS_authorization_plugin_shutdown ();
+  if (NULL != to_task)
+  {
+    GNUNET_SCHEDULER_cancel (to_task);
+    to_task = NULL;
+  }
+}
+
+
+/**
+ * Callback used to notify the application about completed requests.
+ * Cleans up the requests data structures.
+ *
+ * @param[in,out] hc
+ */
+static void
+request_done (struct TM_HandlerContext *hc)
+{
+  struct SolveContext *gc = hc->ctx;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Request completed\n");
+  if (NULL == gc)
+    return;
+  hc->cc = NULL;
+  GNUNET_assert (! gc->suspended);
+  if (gc->in_list)
+  {
+    GNUNET_CONTAINER_DLL_remove (gc_head,
+                                 gc_tail,
+                                 gc);
+    gc->in_list = false;
+  }
+  if (NULL != gc->hn)
+  {
+    GNUNET_assert (gc ==
+                   GNUNET_CONTAINER_heap_remove_node (gc->hn));
+    gc->hn = NULL;
+  }
+  if (NULL != gc->as)
+  {
+    gc->authorization->cleanup (gc->as);
+    gc->authorization = NULL;
+    gc->as = NULL;
+  }
+  if (NULL != gc->cpo)
+  {
+    TALER_MERCHANT_merchant_order_get_cancel (gc->cpo);
+    gc->cpo = NULL;
+  }
+  if (NULL != gc->po)
+  {
+    TALER_MERCHANT_orders_post_cancel (gc->po);
+    gc->po = NULL;
+  }
+  if (NULL != gc->root)
+  {
+    json_decref (gc->root);
+    gc->root = NULL;
+  }
+  TALER_MHD_parse_post_cleanup_callback (gc->opaque_post_parsing_context);
+  GNUNET_free (gc);
+  hc->ctx = NULL;
+}
+
+
+/**
+ * Transmit a payment request for @a order_id on @a connection
+ *
+ * @param gc context to make payment request for
+ */
+static void
+make_payment_request (struct SolveContext *gc)
+{
+  struct MHD_Response *resp;
+
+  resp = MHD_create_response_from_buffer (0,
+                                          NULL,
+                                          MHD_RESPMEM_PERSISTENT);
+  GNUNET_assert (NULL != resp);
+  TALER_MHD_add_global_headers (resp);
+  {
+    char *hdr;
+    char *order_id;
+    const char *pfx;
+    const char *hn;
+
+    if (0 == strncasecmp ("https://";,
+                          AH_backend_url,
+                          strlen ("https://";)))
+    {
+      pfx = "taler://";
+      hn = &AH_backend_url[strlen ("https://";)];
+    }
+    else if (0 == strncasecmp ("http://";,
+                               AH_backend_url,
+                               strlen ("http://";)))
+    {
+      pfx = "taler+http://";;
+      hn = &AH_backend_url[strlen ("http://";)];
+    }
+    else
+    {
+      /* This invariant holds as per check in anastasis-httpd.c */
+      GNUNET_assert (0);
+    }
+    /* This invariant holds as per check in anastasis-httpd.c */
+    GNUNET_assert (0 != strlen (hn));
+
+    order_id = GNUNET_STRINGS_data_to_string_alloc (
+      &gc->payment_identifier,
+      sizeof (gc->payment_identifier));
+    GNUNET_asprintf (&hdr,
+                     "%spay/%s%s/",
+                     pfx,
+                     hn,
+                     order_id);
+    GNUNET_free (order_id);
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Sending payment request `%s'\n",
+                hdr);
+    GNUNET_break (MHD_YES ==
+                  MHD_add_response_header (resp,
+                                           ANASTASIS_HTTP_HEADER_TALER,
+                                           hdr));
+    GNUNET_free (hdr);
+  }
+  gc->resp = resp;
+  gc->response_code = MHD_HTTP_PAYMENT_REQUIRED;
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * /contract request to a merchant.
+ *
+ * @param cls our `struct SolveContext`
+ * @param por response details
+ */
+static void
+proposal_cb (void *cls,
+             const struct TALER_MERCHANT_PostOrdersReply *por)
+{
+  struct SolveContext *gc = cls;
+  enum GNUNET_DB_QueryStatus qs;
+
+  gc->po = NULL;
+  GNUNET_assert (gc->in_list);
+  GNUNET_CONTAINER_DLL_remove (gc_head,
+                               gc_tail,
+                               gc);
+  gc->in_list = false;
+  GNUNET_assert (gc->suspended);
+  gc->suspended = false;
+  MHD_resume_connection (gc->connection);
+  AH_trigger_daemon (NULL);
+  if (MHD_HTTP_OK != por->hr.http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Backend returned status %u/%d\n",
+                por->hr.http_status,
+                (int) por->hr.ec);
+    GNUNET_break (0);
+    gc->resp = TALER_MHD_MAKE_JSON_PACK (
+      GNUNET_JSON_pack_uint64 ("code",
+                               
TALER_EC_ANASTASIS_TRUTH_PAYMENT_CREATE_BACKEND_ERROR),
+      GNUNET_JSON_pack_string ("hint",
+                               "Failed to setup order with merchant backend"),
+      GNUNET_JSON_pack_uint64 ("backend-ec",
+                               por->hr.ec),
+      GNUNET_JSON_pack_uint64 ("backend-http-status",
+                               por->hr.http_status),
+      GNUNET_JSON_pack_allow_null (
+        GNUNET_JSON_pack_object_steal ("backend-reply",
+                                       (json_t *) por->hr.reply)));
+    gc->response_code = MHD_HTTP_BAD_GATEWAY;
+    return;
+  }
+  qs = db->record_challenge_payment (db->cls,
+                                     &gc->truth_uuid,
+                                     &gc->payment_identifier,
+                                     &gc->challenge_cost);
+  if (0 >= qs)
+  {
+    GNUNET_break (0);
+    gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
+                                     "record challenge payment");
+    gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+    return;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Setup fresh order, creating payment request\n");
+  make_payment_request (gc);
+}
+
+
+/**
+ * Callback to process a GET /check-payment request
+ *
+ * @param cls our `struct SolveContext`
+ * @param hr HTTP response details
+ * @param osr order status
+ */
+static void
+check_payment_cb (void *cls,
+                  const struct TALER_MERCHANT_HttpResponse *hr,
+                  const struct TALER_MERCHANT_OrderStatusResponse *osr)
+
+{
+  struct SolveContext *gc = cls;
+
+  gc->cpo = NULL;
+  GNUNET_assert (gc->in_list);
+  GNUNET_CONTAINER_DLL_remove (gc_head,
+                               gc_tail,
+                               gc);
+  gc->in_list = false;
+  GNUNET_assert (gc->suspended);
+  gc->suspended = false;
+  MHD_resume_connection (gc->connection);
+  AH_trigger_daemon (NULL);
+
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    GNUNET_assert (NULL != osr);
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* We created this order before, how can it be not found now? */
+    GNUNET_break (0);
+    gc->resp = TALER_MHD_make_error 
(TALER_EC_ANASTASIS_TRUTH_ORDER_DISAPPEARED,
+                                     NULL);
+    gc->response_code = MHD_HTTP_BAD_GATEWAY;
+    return;
+  case MHD_HTTP_BAD_GATEWAY:
+    gc->resp = TALER_MHD_make_error (
+      TALER_EC_ANASTASIS_TRUTH_BACKEND_EXCHANGE_BAD,
+      NULL);
+    gc->response_code = MHD_HTTP_BAD_GATEWAY;
+    return;
+  case MHD_HTTP_GATEWAY_TIMEOUT:
+    gc->resp = TALER_MHD_make_error 
(TALER_EC_ANASTASIS_GENERIC_BACKEND_TIMEOUT,
+                                     "Timeout check payment status");
+    GNUNET_assert (NULL != gc->resp);
+    gc->response_code = MHD_HTTP_GATEWAY_TIMEOUT;
+    return;
+  default:
+    {
+      char status[14];
+
+      GNUNET_snprintf (status,
+                       sizeof (status),
+                       "%u",
+                       hr->http_status);
+      gc->resp = TALER_MHD_make_error (
+        TALER_EC_ANASTASIS_TRUTH_UNEXPECTED_PAYMENT_STATUS,
+        status);
+      GNUNET_assert (NULL != gc->resp);
+      gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+      return;
+    }
+  }
+
+  switch (osr->status)
+  {
+  case TALER_MERCHANT_OSC_PAID:
+    {
+      enum GNUNET_DB_QueryStatus qs;
+
+      qs = db->update_challenge_payment (db->cls,
+                                         &gc->truth_uuid,
+                                         &gc->payment_identifier);
+      if (0 <= qs)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                    "Order has been paid, continuing with request 
processing\n");
+        return; /* continue as planned */
+      }
+      GNUNET_break (0);
+      gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
+                                       "update challenge payment");
+      gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+      return; /* continue as planned */
+    }
+  case TALER_MERCHANT_OSC_CLAIMED:
+  case TALER_MERCHANT_OSC_UNPAID:
+    /* repeat payment request */
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Order remains unpaid, sending payment request again\n");
+    make_payment_request (gc);
+    return;
+  }
+  /* should never get here */
+  GNUNET_break (0);
+}
+
+
+/**
+ * Helper function used to ask our backend to begin processing a
+ * payment for the user's account.  May perform asynchronous
+ * operations by suspending the connection if required.
+ *
+ * @param gc context to begin payment for.
+ * @return MHD status code
+ */
+static MHD_RESULT
+begin_payment (struct SolveContext *gc)
+{
+  enum GNUNET_DB_QueryStatus qs;
+  char *order_id;
+
+  qs = db->lookup_challenge_payment (db->cls,
+                                     &gc->truth_uuid,
+                                     &gc->payment_identifier);
+  if (qs < 0)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (gc->connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                       "lookup challenge payment");
+  }
+  GNUNET_assert (! gc->in_list);
+  gc->in_list = true;
+  GNUNET_CONTAINER_DLL_insert (gc_tail,
+                               gc_head,
+                               gc);
+  GNUNET_assert (! gc->suspended);
+  gc->suspended = true;
+  MHD_suspend_connection (gc->connection);
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+  {
+    /* We already created the order, check if it was paid */
+    struct GNUNET_TIME_Relative timeout;
+
+    order_id = GNUNET_STRINGS_data_to_string_alloc (
+      &gc->payment_identifier,
+      sizeof (gc->payment_identifier));
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Order exists, checking payment status for order `%s'\n",
+                order_id);
+    timeout = GNUNET_TIME_absolute_get_remaining (gc->timeout);
+    gc->cpo = TALER_MERCHANT_merchant_order_get (AH_ctx,
+                                                 AH_backend_url,
+                                                 order_id,
+                                                 NULL /* NOT session-bound */,
+                                                 false,
+                                                 timeout,
+                                                 &check_payment_cb,
+                                                 gc);
+  }
+  else
+  {
+    /* Create a fresh order */
+    json_t *order;
+    struct GNUNET_TIME_Timestamp pay_deadline;
+
+    GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+                                &gc->payment_identifier,
+                                sizeof (struct ANASTASIS_PaymentSecretP));
+    order_id = GNUNET_STRINGS_data_to_string_alloc (
+      &gc->payment_identifier,
+      sizeof (gc->payment_identifier));
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Creating fresh order `%s'\n",
+                order_id);
+    pay_deadline = GNUNET_TIME_relative_to_timestamp (
+      ANASTASIS_CHALLENGE_OFFER_LIFETIME);
+    order = GNUNET_JSON_PACK (
+      TALER_JSON_pack_amount ("amount",
+                              &gc->challenge_cost),
+      GNUNET_JSON_pack_string ("summary",
+                               "challenge fee for anastasis service"),
+      GNUNET_JSON_pack_string ("order_id",
+                               order_id),
+      GNUNET_JSON_pack_time_rel ("auto_refund",
+                                 AUTO_REFUND_TIMEOUT),
+      GNUNET_JSON_pack_timestamp ("pay_deadline",
+                                  pay_deadline));
+    gc->po = TALER_MERCHANT_orders_post2 (AH_ctx,
+                                          AH_backend_url,
+                                          order,
+                                          AUTO_REFUND_TIMEOUT,
+                                          NULL, /* no payment target */
+                                          0,
+                                          NULL, /* no inventory products */
+                                          0,
+                                          NULL, /* no uuids */
+                                          false, /* do NOT require claim token 
*/
+                                          &proposal_cb,
+                                          gc);
+    json_decref (order);
+  }
+  GNUNET_free (order_id);
+  AH_trigger_curl ();
+  return MHD_YES;
+}
+
+
+/**
+ * Load encrypted keyshare from db and return it to the client.
+ *
+ * @param truth_uuid UUID to the truth for the looup
+ * @param connection the connection to respond upon
+ * @return MHD status code
+ */
+static MHD_RESULT
+return_key_share (
+  const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+  struct MHD_Connection *connection)
+{
+  struct ANASTASIS_CRYPTO_EncryptedKeyShareP encrypted_keyshare;
+
+  {
+    enum GNUNET_DB_QueryStatus qs;
+
+    qs = db->get_key_share (db->cls,
+                            truth_uuid,
+                            &encrypted_keyshare);
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_HARD_ERROR:
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                         "get key share");
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         
TALER_EC_ANASTASIS_TRUTH_KEY_SHARE_GONE,
+                                         NULL);
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      break;
+    }
+  }
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Returning key share\n");
+  {
+    struct MHD_Response *resp;
+    MHD_RESULT ret;
+
+    resp = MHD_create_response_from_buffer (sizeof (encrypted_keyshare),
+                                            &encrypted_keyshare,
+                                            MHD_RESPMEM_MUST_COPY);
+    TALER_MHD_add_global_headers (resp);
+    ret = MHD_queue_response (connection,
+                              MHD_HTTP_OK,
+                              resp);
+    MHD_destroy_response (resp);
+    return ret;
+  }
+}
+
+
+/**
+ * Mark @a gc as suspended and update the respective
+ * data structures and jobs.
+ *
+ * @param[in,out] gc context of the suspended operation
+ */
+static void
+gc_suspended (struct SolveContext *gc)
+{
+  gc->suspended = true;
+  if (NULL == AH_to_heap)
+    AH_to_heap = GNUNET_CONTAINER_heap_create (
+      GNUNET_CONTAINER_HEAP_ORDER_MIN);
+  gc->hn = GNUNET_CONTAINER_heap_insert (AH_to_heap,
+                                         gc,
+                                         gc->timeout.abs_value_us);
+  if (NULL != to_task)
+  {
+    GNUNET_SCHEDULER_cancel (to_task);
+    to_task = NULL;
+  }
+  {
+    struct SolveContext *rn;
+
+    rn = GNUNET_CONTAINER_heap_peek (AH_to_heap);
+    to_task = GNUNET_SCHEDULER_add_at (rn->timeout,
+                                       &do_timeout,
+                                       NULL);
+  }
+}
+
+
+/**
+ * Run the authorization method-specific 'process' function and continue
+ * based on its result with generating an HTTP response.
+ *
+ * @param connection the connection we are handling
+ * @param gc our overall handler context
+ */
+static MHD_RESULT
+run_authorization_process (struct MHD_Connection *connection,
+                           struct SolveContext *gc)
+{
+  enum ANASTASIS_AUTHORIZATION_Result ret;
+
+  GNUNET_assert (! gc->suspended);
+  ret = gc->authorization->process (gc->as,
+                                    gc->timeout,
+                                    connection);
+  switch (ret)
+  {
+  case ANASTASIS_AUTHORIZATION_RES_SUCCESS:
+  case ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED:
+    /* Neither case should EVER happen here! */
+    GNUNET_break (0);
+    gc->authorization->cleanup (gc->as);
+    gc->as = NULL;
+    return TALER_MHD_reply_with_error (gc->connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+                                       "challenge sent when we were only 
supposed to be checking");
+  case ANASTASIS_AUTHORIZATION_RES_SUSPENDED:
+    /* connection was suspended */
+    gc_suspended (gc);
+    return MHD_YES;
+  case ANASTASIS_AUTHORIZATION_RES_FAILED:
+    gc->authorization->cleanup (gc->as);
+    gc->as = NULL;
+    return MHD_YES;
+  case ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED:
+    gc->authorization->cleanup (gc->as);
+    gc->as = NULL;
+    return MHD_NO;
+  case ANASTASIS_AUTHORIZATION_RES_FINISHED:
+    GNUNET_assert (! gc->suspended);
+    gc->authorization->cleanup (gc->as);
+    gc->as = NULL;
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Resuming with authorization successful!\n");
+    if (gc->in_list)
+    {
+      GNUNET_CONTAINER_DLL_remove (gc_head,
+                                   gc_tail,
+                                   gc);
+      gc->in_list = false;
+    }
+    return MHD_YES;
+  }
+  GNUNET_break (0);
+  return MHD_NO;
+}
+
+
+/**
+ * Use the database to rate-limit queries to the authentication
+ * procedure, but without actually storing 'real' challenge codes.
+ *
+ * @param[in,out] gc context to rate limit requests for
+ * @return #GNUNET_OK if rate-limiting passes,
+ *         #GNUNET_NO if a reply was sent (rate limited)
+ *         #GNUNET_SYSERR if we failed and no reply
+ *                        was queued
+ */
+static enum GNUNET_GenericReturnValue
+rate_limit (struct SolveContext *gc)
+{
+  enum GNUNET_DB_QueryStatus qs;
+  struct GNUNET_TIME_Timestamp rt;
+  uint64_t code;
+  enum ANASTASIS_DB_CodeStatus cs;
+  struct GNUNET_HashCode hc;
+  bool satisfied;
+  uint64_t dummy;
+
+  rt = GNUNET_TIME_UNIT_FOREVER_TS;
+  qs = db->create_challenge_code (db->cls,
+                                  &gc->truth_uuid,
+                                  MAX_QUESTION_FREQ,
+                                  GNUNET_TIME_UNIT_HOURS,
+                                  INITIAL_RETRY_COUNTER,
+                                  &rt,
+                                  &code);
+  if (0 > qs)
+  {
+    GNUNET_break (0 < qs);
+    return (MHD_YES ==
+            TALER_MHD_reply_with_error (gc->connection,
+                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                        TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                        "create_challenge_code (for rate 
limiting)"))
+      ? GNUNET_NO
+      : GNUNET_SYSERR;
+  }
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+  {
+    return (MHD_YES ==
+            reply_rate_limited (gc))
+      ? GNUNET_NO
+      : GNUNET_SYSERR;
+  }
+  /* decrement trial counter */
+  ANASTASIS_hash_answer (code + 1,      /* always use wrong answer */
+                         &hc);
+  cs = db->verify_challenge_code (db->cls,
+                                  &gc->truth_uuid,
+                                  &hc,
+                                  &dummy,
+                                  &satisfied);
+  switch (cs)
+  {
+  case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH:
+    /* good, what we wanted */
+    return GNUNET_OK;
+  case ANASTASIS_DB_CODE_STATUS_HARD_ERROR:
+  case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR:
+    GNUNET_break (0);
+    return (MHD_YES ==
+            TALER_MHD_reply_with_error (gc->connection,
+                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                        TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                        "verify_challenge_code"))
+      ? GNUNET_NO
+      : GNUNET_SYSERR;
+  case ANASTASIS_DB_CODE_STATUS_NO_RESULTS:
+    return (MHD_YES ==
+            reply_rate_limited (gc))
+      ? GNUNET_NO
+      : GNUNET_SYSERR;
+  case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED:
+    /* this should be impossible, we used code+1 */
+    GNUNET_assert (0);
+  }
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Handle special case of a security question where we do not
+ * generate a code. Rate limits answers against brute forcing.
+ *
+ * @param[in,out] gc request to handle
+ * @param decrypted_truth hash to check against
+ * @param decrypted_truth_size number of bytes in @a decrypted_truth
+ * @return MHD status code
+ */
+static MHD_RESULT
+handle_security_question (struct SolveContext *gc,
+                          const void *decrypted_truth,
+                          size_t decrypted_truth_size)
+{
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Handling security question challenge\n");
+  /* rate limit */
+  {
+    enum GNUNET_GenericReturnValue ret;
+
+    ret = rate_limit (gc);
+    if (GNUNET_OK != ret)
+      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+  }
+  /* check reply matches truth */
+  if ( (decrypted_truth_size != sizeof (struct GNUNET_HashCode)) ||
+       (0 != memcmp (&gc->challenge_response,
+                     decrypted_truth,
+                     decrypted_truth_size)) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Wrong answer provided to secure question had %u bytes, wanted 
%u\n",
+                (unsigned int) decrypted_truth_size,
+                (unsigned int) sizeof (struct GNUNET_HashCode));
+    return TALER_MHD_reply_with_error (gc->connection,
+                                       MHD_HTTP_FORBIDDEN,
+                                       
TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED,
+                                       NULL);
+  }
+  /* good, return the key share */
+  return return_key_share (&gc->truth_uuid,
+                           gc->connection);
+}
+
+
+/**
+ * Handle special case of an answer being directly checked by the
+ * plugin and not by our database. Rate limits answers against brute
+ * forcing.
+ *
+ * @param[in,out] gc request to handle
+ * @param decrypted_truth hash to check against
+ * @param decrypted_truth_size number of bytes in @a decrypted_truth
+ * @return MHD status code
+ */
+static MHD_RESULT
+direct_validation (struct SolveContext *gc,
+                   const void *decrypted_truth,
+                   size_t decrypted_truth_size)
+{
+  /* Non-random code, call plugin directly! */
+  enum ANASTASIS_AUTHORIZATION_Result aar;
+  enum GNUNET_GenericReturnValue res;
+
+  res = rate_limit (gc);
+  if (GNUNET_OK != res)
+    return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+  gc->as = gc->authorization->start (gc->authorization->cls,
+                                     &AH_trigger_daemon,
+                                     NULL,
+                                     &gc->truth_uuid,
+                                     0LLU,
+                                     decrypted_truth,
+                                     decrypted_truth_size);
+  if (NULL == gc->as)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (gc->connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED,
+                                       NULL);
+  }
+  aar = gc->authorization->process (gc->as,
+                                    GNUNET_TIME_UNIT_ZERO_ABS,
+                                    gc->connection);
+  switch (aar)
+  {
+  case ANASTASIS_AUTHORIZATION_RES_SUCCESS:
+    GNUNET_break (0);
+    return MHD_YES;
+  case ANASTASIS_AUTHORIZATION_RES_FAILED:
+    return MHD_YES;
+  case ANASTASIS_AUTHORIZATION_RES_SUSPENDED:
+    gc_suspended (gc);
+    return MHD_YES;
+  case ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED:
+    GNUNET_break (0);
+    return MHD_NO;
+  case ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED:
+    return MHD_NO;
+  case ANASTASIS_AUTHORIZATION_RES_FINISHED:
+    return return_key_share (&gc->truth_uuid,
+                             gc->connection);
+  }
+  GNUNET_break (0);
+  return MHD_NO;
+}
+
+
+MHD_RESULT
+AH_handler_truth_solve (
+  struct MHD_Connection *connection,
+  struct TM_HandlerContext *hc,
+  const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+  const char *upload_data,
+  size_t *upload_data_size)
+{
+  struct SolveContext *gc = hc->ctx;
+  void *encrypted_truth;
+  size_t encrypted_truth_size;
+  void *decrypted_truth;
+  size_t decrypted_truth_size;
+  char *truth_mime = NULL;
+  bool is_question;
+
+  if (NULL == gc)
+  {
+    /* Fresh request, do initial setup */
+    gc = GNUNET_new (struct SolveContext);
+    gc->hc = hc;
+    hc->ctx = gc;
+    gc->connection = connection;
+    gc->truth_uuid = *truth_uuid;
+    gc->hc->cc = &request_done;
+    {
+      const char *pay_id;
+
+      pay_id = MHD_lookup_connection_value (connection,
+                                            MHD_HEADER_KIND,
+                                            
ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER);
+      if (NULL != pay_id)
+      {
+        if (GNUNET_OK !=
+            GNUNET_STRINGS_string_to_data (
+              pay_id,
+              strlen (pay_id),
+              &gc->payment_identifier,
+              sizeof (struct ANASTASIS_PaymentSecretP)))
+        {
+          GNUNET_break_op (0);
+          return TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_BAD_REQUEST,
+                                             
TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                             
ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER);
+        }
+        gc->payment_identifier_provided = true;
+        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                    "Client provided payment identifier `%s'\n",
+                    pay_id);
+      }
+    }
+
+    {
+      const char *long_poll_timeout_ms;
+
+      long_poll_timeout_ms = MHD_lookup_connection_value (connection,
+                                                          
MHD_GET_ARGUMENT_KIND,
+                                                          "timeout_ms");
+      if (NULL != long_poll_timeout_ms)
+      {
+        unsigned int timeout;
+        char dummy;
+
+        if (1 != sscanf (long_poll_timeout_ms,
+                         "%u%c",
+                         &timeout,
+                         &dummy))
+        {
+          GNUNET_break_op (0);
+          return TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_BAD_REQUEST,
+                                             
TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                             "timeout_ms (must be non-negative 
number)");
+        }
+        gc->timeout
+          = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
+                                                GNUNET_TIME_UNIT_MILLISECONDS,
+                                                timeout));
+      }
+      else
+      {
+        gc->timeout = GNUNET_TIME_relative_to_absolute (
+          GNUNET_TIME_UNIT_SECONDS);
+      }
+    }
+  } /* end of first-time initialization (if NULL == gc) */
+  else
+  {
+    /* might have been woken up by authorization plugin,
+       so clear the flag. MDH called us, so we are
+       clearly no longer suspended */
+    gc->suspended = false;
+    if (NULL != gc->resp)
+    {
+      MHD_RESULT ret;
+
+      /* We generated a response asynchronously, queue that */
+      ret = MHD_queue_response (connection,
+                                gc->response_code,
+                                gc->resp);
+      GNUNET_break (MHD_YES == ret);
+      MHD_destroy_response (gc->resp);
+      gc->resp = NULL;
+      return ret;
+    }
+    if (NULL != gc->as)
+    {
+      /* Authorization process is "running", check what is going on */
+      GNUNET_assert (NULL != gc->authorization);
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Continuing with running the authorization process\n");
+      GNUNET_assert (! gc->suspended);
+      return run_authorization_process (connection,
+                                        gc);
+
+    }
+    /* We get here if the async check for payment said this request
+       was indeed paid! */
+  }
+
+  if (NULL == gc->root)
+  {
+    /* parse byte stream upload into JSON */
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_post_json (connection,
+                                     &gc->opaque_post_parsing_context,
+                                     upload_data,
+                                     upload_data_size,
+                                     &gc->root);
+    if (GNUNET_SYSERR == res)
+    {
+      GNUNET_assert (NULL == gc->root);
+      return MHD_NO; /* bad upload, could not even generate error */
+    }
+    if ( (GNUNET_NO == res) ||
+         (NULL == gc->root) )
+    {
+      GNUNET_assert (NULL == gc->root);
+      return MHD_YES; /* so far incomplete upload or parser error */
+    }
+
+    /* 'root' is now initialized, parse JSON body */
+    {
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_fixed_auto ("truth_decryption_key",
+                                     &gc->truth_key),
+        GNUNET_JSON_spec_fixed_auto ("h_response",
+                                     &gc->challenge_response),
+        GNUNET_JSON_spec_end ()
+      };
+      enum GNUNET_GenericReturnValue res;
+
+      res = TALER_MHD_parse_json_data (connection,
+                                       gc->root,
+                                       spec);
+      if (GNUNET_SYSERR == res)
+      {
+        GNUNET_break (0);
+        return MHD_NO; /* hard failure */
+      }
+      if (GNUNET_NO == res)
+      {
+        GNUNET_break_op (0);
+        return MHD_YES; /* failure */
+      }
+    }
+  }
+
+  {
+    /* load encrypted truth from DB; we may do this repeatedly
+       while handling the same request, if payment was checked
+       asynchronously! */
+    enum GNUNET_DB_QueryStatus qs;
+    char *method;
+
+    qs = db->get_escrow_challenge (db->cls,
+                                   &gc->truth_uuid,
+                                   &encrypted_truth,
+                                   &encrypted_truth_size,
+                                   &truth_mime,
+                                   &method);
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_HARD_ERROR:
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (gc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                         "get escrow challenge");
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_ANASTASIS_TRUTH_UNKNOWN,
+                                         NULL);
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      break;
+    }
+    is_question = (0 == strcmp ("question",
+                                method));
+    if (! is_question)
+    {
+      gc->authorization
+        = ANASTASIS_authorization_plugin_load (method,
+                                               db,
+                                               AH_cfg);
+      if (NULL == gc->authorization)
+      {
+        MHD_RESULT ret;
+
+        ret = TALER_MHD_reply_with_error (
+          connection,
+          MHD_HTTP_INTERNAL_SERVER_ERROR,
+          TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_METHOD_NO_LONGER_SUPPORTED,
+          method);
+        GNUNET_free (encrypted_truth);
+        GNUNET_free (truth_mime);
+        GNUNET_free (method);
+        return ret;
+      }
+      gc->challenge_cost = gc->authorization->cost;
+    }
+    else
+    {
+      gc->challenge_cost = AH_question_cost;
+    }
+    GNUNET_free (method);
+  }
+
+  /* check for payment */
+  if ( (is_question) ||
+       (! gc->authorization->payment_plugin_managed) )
+  {
+    if (! TALER_amount_is_zero (&gc->challenge_cost))
+    {
+      /* Check database to see if the transaction is paid for */
+      enum GNUNET_DB_QueryStatus qs;
+      bool paid;
+
+      if (! gc->payment_identifier_provided)
+      {
+        GNUNET_free (truth_mime);
+        GNUNET_free (encrypted_truth);
+        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                    "Beginning payment, client did not provide payment 
identifier\n");
+        return begin_payment (gc);
+      }
+      qs = db->check_challenge_payment (db->cls,
+                                        &gc->payment_identifier,
+                                        &gc->truth_uuid,
+                                        &paid);
+      switch (qs)
+      {
+      case GNUNET_DB_STATUS_HARD_ERROR:
+      case GNUNET_DB_STATUS_SOFT_ERROR:
+        GNUNET_break (0);
+        GNUNET_free (truth_mime);
+        GNUNET_free (encrypted_truth);
+        return TALER_MHD_reply_with_error (gc->connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                           "check challenge payment");
+      case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+        /* Create fresh payment identifier (cannot trust client) */
+        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                    "Client-provided payment identifier is unknown.\n");
+        GNUNET_free (truth_mime);
+        GNUNET_free (encrypted_truth);
+        return begin_payment (gc);
+      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+        if (! paid)
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                      "Payment identifier known. Checking payment with 
client's payment identifier\n");
+          GNUNET_free (truth_mime);
+          GNUNET_free (encrypted_truth);
+          return begin_payment (gc);
+        }
+        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                    "Payment confirmed\n");
+        break;
+      }
+    }
+    else
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Request is free of charge\n");
+    }
+  }
+
+  /* We've been paid, now validate the response */
+  /* decrypt encrypted_truth */
+  ANASTASIS_CRYPTO_truth_decrypt (&gc->truth_key,
+                                  encrypted_truth,
+                                  encrypted_truth_size,
+                                  &decrypted_truth,
+                                  &decrypted_truth_size);
+  GNUNET_free (encrypted_truth);
+  if (NULL == decrypted_truth)
+  {
+    /* most likely, the decryption key is simply wrong */
+    GNUNET_break_op (0);
+    GNUNET_free (truth_mime);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       
TALER_EC_ANASTASIS_TRUTH_DECRYPTION_FAILED,
+                                       NULL);
+  }
+
+  /* Special case for secure question: we do not generate a numeric challenge,
+     but check that the hash matches */
+  if (is_question)
+  {
+    MHD_RESULT ret;
+
+    ret = handle_security_question (gc,
+                                    decrypted_truth,
+                                    decrypted_truth_size);
+    GNUNET_free (truth_mime);
+    GNUNET_free (decrypted_truth);
+    return ret;
+  }
+
+  /* Not security question, check for answer in DB */
+  {
+    enum ANASTASIS_DB_CodeStatus cs;
+    bool satisfied = false;
+    uint64_t code;
+
+    GNUNET_free (truth_mime);
+    if (gc->authorization->user_provided_code)
+    {
+      MHD_RESULT res;
+
+      if (GNUNET_TIME_absolute_is_past (gc->timeout))
+      {
+        GNUNET_free (decrypted_truth);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_FORBIDDEN,
+                                           
TALER_EC_ANASTASIS_TRUTH_AUTH_TIMEOUT,
+                                           "timeout awaiting validation");
+      }
+      res = direct_validation (gc,
+                               decrypted_truth,
+                               decrypted_truth_size);
+      GNUNET_free (decrypted_truth);
+      return res;
+    }
+
+    /* random code, check against database */
+    // FIXME: check that this statement NEVER puts
+    // a new code INTO the DB (old style!)
+    cs = db->verify_challenge_code (db->cls,
+                                    &gc->truth_uuid,
+                                    &gc->challenge_response,
+                                    &code,
+                                    &satisfied);
+    switch (cs)
+    {
+    case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH:
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Provided response does not match our stored challenge\n");
+      GNUNET_free (decrypted_truth);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_FORBIDDEN,
+                                         
TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED,
+                                         NULL);
+    case ANASTASIS_DB_CODE_STATUS_HARD_ERROR:
+    case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR:
+      GNUNET_break (0);
+      GNUNET_free (decrypted_truth);
+      return TALER_MHD_reply_with_error (gc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                         "verify_challenge_code");
+    case ANASTASIS_DB_CODE_STATUS_NO_RESULTS:
+      GNUNET_free (decrypted_truth);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         
TALER_EC_ANASTASIS_TRUTH_CHALLENGE_UNKNOWN,
+                                         NULL);
+    case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED:
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Response code valid (%s)\n",
+                  satisfied ? "satisfied" : "unsatisfied");
+      if (! satisfied)
+      {
+        GNUNET_free (decrypted_truth);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_NOT_FOUND,
+                                           
TALER_EC_ANASTASIS_TRUTH_CHALLENGE_UNKNOWN,
+                                           NULL);
+      }
+      GNUNET_free (decrypted_truth);
+      return return_key_share (&gc->truth_uuid,
+                               connection);
+    default:
+      GNUNET_break (0);
+      return MHD_NO;
+    }
+  }
+}
diff --git a/src/backend/anastasis-httpd_truth_upload.c 
b/src/backend/anastasis-httpd_truth-upload.c
similarity index 100%
rename from src/backend/anastasis-httpd_truth_upload.c
rename to src/backend/anastasis-httpd_truth-upload.c
diff --git a/src/backend/anastasis-httpd_truth.h 
b/src/backend/anastasis-httpd_truth.h
index 87e570b..d0851ba 100644
--- a/src/backend/anastasis-httpd_truth.h
+++ b/src/backend/anastasis-httpd_truth.h
@@ -32,6 +32,19 @@ void
 AH_truth_shutdown (void);
 
 
+/**
+ * Prepare all active POST truth solve requests for system shutdown.
+ */
+void
+AH_truth_solve_shutdown (void);
+
+
+/**
+ * Prepare all active POST truth challenge requests for system shutdown.
+ */
+void
+AH_truth_challenge_shutdown (void);
+
 /**
  * Prepare all active POST truth requests for system shutdown.
  */
@@ -42,9 +55,9 @@ AH_truth_upload_shutdown (void);
 /**
  * Handle a GET to /truth/$UUID
  *
- * @param connection the MHD connection to handle
+ * @param[in,out] connection the MHD connection to handle
  * @param truth_uuid the truth UUID
- * @param hc connection context
+ * @param[in,out] hc connection context
  * @return MHD result code
  */
 MHD_RESULT
@@ -57,8 +70,8 @@ AH_handler_truth_get (
 /**
  * Handle a POST to /truth/$UUID.
  *
- * @param connection the MHD connection to handle
- * @param hc connection context
+ * @param[in,out] connection the MHD connection to handle
+ * @param[in,out] hc connection context
  * @param truth_uuid the truth UUID
  * @param truth_data truth data
  * @param truth_data_size number of bytes (left) in @a truth_data
@@ -72,4 +85,43 @@ AH_handler_truth_post (
   const char *truth_data,
   size_t *truth_data_size);
 
+
+/**
+ * Handle a POST to /truth/$UUID/solve.
+ *
+ * @param[in,out] connection the MHD connection to handle
+ * @param[in,out] hc connection context
+ * @param truth_uuid the truth UUID
+ * @param truth_data truth data
+ * @param truth_data_size number of bytes (left) in @a truth_data
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_handler_truth_solve (
+  struct MHD_Connection *connection,
+  struct TM_HandlerContext *hc,
+  const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+  const char *upload_data,
+  size_t *upload_data_size);
+
+
+/**
+ * Handle a POST to /truth/$UUID/challenge.
+ *
+ * @param[in,out] connection the MHD connection to handle
+ * @param[in,out] hc connection context
+ * @param truth_uuid the truth UUID
+ * @param truth_data truth data
+ * @param truth_data_size number of bytes (left) in @a truth_data
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_handler_truth_challenge (
+  struct MHD_Connection *connection,
+  struct TM_HandlerContext *hc,
+  const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+  const char *upload_data,
+  size_t *upload_data_size);
+
+
 #endif

-- 
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]