gnunet-svn
[Top][All Lists]
Advanced

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

[taler-anastasis] branch master updated: push new /truth/ API through th


From: gnunet
Subject: [taler-anastasis] branch master updated: push new /truth/ API through the entire implementation
Date: Wed, 02 Mar 2022 23:39:29 +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 cf4b6eb  push new /truth/ API through the entire implementation
cf4b6eb is described below

commit cf4b6ebd6de3370da4b16d2f1ef19a2a3d3d0b12
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Wed Mar 2 23:39:26 2022 +0100

    push new /truth/ API through the entire implementation
---
 doc/sphinx/rest.rst                                |   19 +-
 .../anastasis_authorization_plugin_email.c         |   11 +-
 .../anastasis_authorization_plugin_file.c          |    4 +-
 .../anastasis_authorization_plugin_iban.c          |   25 +-
 .../anastasis_authorization_plugin_post.c          |   11 +-
 .../anastasis_authorization_plugin_sms.c           |   11 +-
 src/backend/Makefile.am                            |    2 +-
 src/backend/anastasis-httpd.c                      |   51 +-
 src/backend/anastasis-httpd_truth-challenge.c      |   36 +-
 src/backend/anastasis-httpd_truth-solve.c          |   36 +-
 src/backend/anastasis-httpd_truth.c                | 1702 --------------------
 src/backend/anastasis-httpd_truth.h                |   25 +-
 src/include/anastasis.h                            |  309 ++--
 src/include/anastasis_service.h                    |  323 +---
 src/include/anastasis_testing_lib.h                |   33 +-
 src/lib/anastasis_recovery.c                       |  375 +++--
 src/reducer/anastasis_api_recovery_redux.c         |  474 +++---
 src/restclient/Makefile.am                         |    1 -
 src/restclient/anastasis_api_curl_defaults.c       |   12 +
 src/restclient/anastasis_api_truth_challenge.c     |   50 +-
 src/restclient/anastasis_api_truth_solve.c         |    2 +-
 src/testing/Makefile.am                            |    3 +-
 src/testing/test_anastasis.c                       |   10 +-
 src/testing/test_anastasis_api.c                   |   30 +-
 ..._lookup.c => testing_api_cmd_truth_challenge.c} |  275 ++--
 ...hare_lookup.c => testing_api_cmd_truth_solve.c} |  194 +--
 src/testing/testing_cmd_challenge_answer.c         |  230 +--
 27 files changed, 1184 insertions(+), 3070 deletions(-)

diff --git a/doc/sphinx/rest.rst b/doc/sphinx/rest.rst
index 206eda7..4a5aad5 100644
--- a/doc/sphinx/rest.rst
+++ b/doc/sphinx/rest.rst
@@ -1,6 +1,6 @@
 ..
   This file is part of Anastasis
-  Copyright (C) 2019-2021 Anastasis SARL
+  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
@@ -789,13 +789,14 @@ charge per truth operation using GNU Taler.
   .. ts:def:: ChallengeInstructionMessage
 
     type ChallengeInstructionMessage =
+      | FileChallengeInstructionMessage
       | IbanChallengeInstructionMessage
       | PinChallengeInstructionMessage;
 
     interface IbanChallengeInstructionMessage {
 
       // What kind of challenge is this?
-      method: "iban";
+      method: "IBAN_WIRE";
 
       // How much should be wired?
       amount: Amount;
@@ -817,11 +818,21 @@ charge per truth operation using GNU Taler.
     interface PinChallengeInstructionMessage {
 
       // What kind of challenge is this?
-      method: "pin";
+      method: "TAN_SENT";
 
       // Where was the PIN code sent? Note that this
       // address will most likely have been obscured
       // to improve privacy.
-      address_hint: string;
+      tan_address_hint: string;
+
+    }
+
+    interface FileChallengeInstructionMessage {
+
+      // What kind of challenge is this?
+      method: "FILE_WRITTEN";
+
+      // Name of the file where the PIN code was written.
+      filename: string;
 
     }
diff --git a/src/authorization/anastasis_authorization_plugin_email.c 
b/src/authorization/anastasis_authorization_plugin_email.c
index 2284988..78be9b6 100644
--- a/src/authorization/anastasis_authorization_plugin_email.c
+++ b/src/authorization/anastasis_authorization_plugin_email.c
@@ -463,12 +463,9 @@ email_process (struct ANASTASIS_AUTHORIZATION_State *as,
       user = GNUNET_strndup (as->email,
                              len);
       resp = TALER_MHD_MAKE_JSON_PACK (
-        GNUNET_JSON_pack_uint64 ("code",
-                                 
TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED),
-        GNUNET_JSON_pack_string ("hint",
-                                 TALER_ErrorCode_get_hint (
-                                   
TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED)),
-        GNUNET_JSON_pack_string ("detail",
+        GNUNET_JSON_pack_string ("challenge_type",
+                                 "TAN_SENT"),
+        GNUNET_JSON_pack_string ("tan_address_hint",
                                  user));
       GNUNET_free (user);
     }
@@ -494,7 +491,7 @@ email_process (struct ANASTASIS_AUTHORIZATION_State *as,
                                              "text/plain"));
     }
     mres = MHD_queue_response (connection,
-                               MHD_HTTP_FORBIDDEN,
+                               MHD_HTTP_OK,
                                resp);
     MHD_destroy_response (resp);
     if (MHD_YES != mres)
diff --git a/src/authorization/anastasis_authorization_plugin_file.c 
b/src/authorization/anastasis_authorization_plugin_file.c
index 863db11..5186ae8 100644
--- a/src/authorization/anastasis_authorization_plugin_file.c
+++ b/src/authorization/anastasis_authorization_plugin_file.c
@@ -235,6 +235,8 @@ file_process (struct ANASTASIS_AUTHORIZATION_State *as,
                                  "application/json"))
     {
       resp = TALER_MHD_MAKE_JSON_PACK (
+        GNUNET_JSON_pack_string ("challenge_type",
+                                 "FILE_WRITTEN"),
         GNUNET_JSON_pack_string ("filename",
                                  as->filename));
     }
@@ -260,7 +262,7 @@ file_process (struct ANASTASIS_AUTHORIZATION_State *as,
       MHD_RESULT mres;
 
       mres = MHD_queue_response (connection,
-                                 MHD_HTTP_FORBIDDEN,
+                                 MHD_HTTP_OK,
                                  resp);
       MHD_destroy_response (resp);
       if (MHD_YES != mres)
diff --git a/src/authorization/anastasis_authorization_plugin_iban.c 
b/src/authorization/anastasis_authorization_plugin_iban.c
index fec0d6d..bd58c51 100644
--- a/src/authorization/anastasis_authorization_plugin_iban.c
+++ b/src/authorization/anastasis_authorization_plugin_iban.c
@@ -340,23 +340,18 @@ respond_with_challenge (struct 
ANASTASIS_AUTHORIZATION_State *as,
                        "Anastasis %llu",
                        (unsigned long long) as->code);
       resp = TALER_MHD_MAKE_JSON_PACK (
-        GNUNET_JSON_pack_string ("method",
-                                 "iban"),
-        GNUNET_JSON_pack_bool ("async",
-                               true),
+        GNUNET_JSON_pack_string ("challenge_type",
+                                 "IBAN_WIRE"),
         GNUNET_JSON_pack_uint64 ("answer_code",
                                  as->code),
-        GNUNET_JSON_pack_object_steal (
-          "details",
-          GNUNET_JSON_PACK (
-            TALER_JSON_pack_amount ("challenge_amount",
-                                    &ctx->expected_amount),
-            GNUNET_JSON_pack_string ("credit_iban",
-                                     ctx->business_iban),
-            GNUNET_JSON_pack_string ("business_name",
-                                     ctx->business_name),
-            GNUNET_JSON_pack_string ("wire_transfer_subject",
-                                     subject))));
+        TALER_JSON_pack_amount ("challenge_amount",
+                                &ctx->expected_amount),
+        GNUNET_JSON_pack_string ("credit_iban",
+                                 ctx->business_iban),
+        GNUNET_JSON_pack_string ("business_name",
+                                 ctx->business_name),
+        GNUNET_JSON_pack_string ("wire_transfer_subject",
+                                 subject));
     }
     else
     {
diff --git a/src/authorization/anastasis_authorization_plugin_post.c 
b/src/authorization/anastasis_authorization_plugin_post.c
index 4f901d2..ad0ed15 100644
--- a/src/authorization/anastasis_authorization_plugin_post.c
+++ b/src/authorization/anastasis_authorization_plugin_post.c
@@ -524,12 +524,9 @@ post_process (struct ANASTASIS_AUTHORIZATION_State *as,
                                  "application/json"))
     {
       resp = TALER_MHD_MAKE_JSON_PACK (
-        GNUNET_JSON_pack_uint64 ("code",
-                                 
TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED),
-        GNUNET_JSON_pack_string ("hint",
-                                 TALER_ErrorCode_get_hint (
-                                   
TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED)),
-        GNUNET_JSON_pack_string ("detail",
+        GNUNET_JSON_pack_string ("challenge_type",
+                                 "TAN_SENT"),
+        GNUNET_JSON_pack_string ("tan_address_hint",
                                  zip));
     }
     else
@@ -549,7 +546,7 @@ post_process (struct ANASTASIS_AUTHORIZATION_State *as,
       TALER_MHD_add_global_headers (resp);
     }
     mres = MHD_queue_response (connection,
-                               MHD_HTTP_FORBIDDEN,
+                               MHD_HTTP_OK,
                                resp);
     MHD_destroy_response (resp);
     if (MHD_YES != mres)
diff --git a/src/authorization/anastasis_authorization_plugin_sms.c 
b/src/authorization/anastasis_authorization_plugin_sms.c
index 98152ef..6598d29 100644
--- a/src/authorization/anastasis_authorization_plugin_sms.c
+++ b/src/authorization/anastasis_authorization_plugin_sms.c
@@ -455,12 +455,9 @@ sms_process (struct ANASTASIS_AUTHORIZATION_State *as,
                                  "application/json"))
     {
       resp = TALER_MHD_MAKE_JSON_PACK (
-        GNUNET_JSON_pack_uint64 ("code",
-                                 
TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED),
-        GNUNET_JSON_pack_string ("hint",
-                                 TALER_ErrorCode_get_hint (
-                                   
TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED)),
-        GNUNET_JSON_pack_string ("detail",
+        GNUNET_JSON_pack_string ("challenge_type",
+                                 "TAN_SENT"),
+        GNUNET_JSON_pack_string ("tan_address_hint",
                                  end));
     }
     else
@@ -484,7 +481,7 @@ sms_process (struct ANASTASIS_AUTHORIZATION_State *as,
                                              "text/plain"));
     }
     mres = MHD_queue_response (connection,
-                               MHD_HTTP_FORBIDDEN,
+                               MHD_HTTP_OK,
                                resp);
     MHD_destroy_response (resp);
     if (MHD_YES != mres)
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
index 83877bc..db37478 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -20,7 +20,7 @@ anastasis_httpd_SOURCES = \
   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_truth.c anastasis-httpd_truth.h \
+  anastasis-httpd_truth.h \
   anastasis-httpd_terms.c anastasis-httpd_terms.h \
   anastasis-httpd_config.c anastasis-httpd_config.h \
   anastasis-httpd_truth-challenge.c \
diff --git a/src/backend/anastasis-httpd.c b/src/backend/anastasis-httpd.c
index 0c9d957..4428851 100644
--- a/src/backend/anastasis-httpd.c
+++ b/src/backend/anastasis-httpd.c
@@ -439,17 +439,19 @@ url_handler (void *cls,
                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                          "truth UUID");
     }
-    if ( (NULL == end) &&
-         (0 == strcmp (method,
-                       MHD_HTTP_METHOD_GET)) )
-    {
-      return AH_handler_truth_get (connection,
-                                   &tu,
-                                   hc);
-    }
-    if ( (NULL == end) &&
-         (0 == strcmp (method,
-                       MHD_HTTP_METHOD_POST)) )
+    if ( (NULL != end) &&
+         (0 != strcmp (end, "/solve")) &&
+         (0 != strcmp (end, "/challenge")) )
+      return TMH_MHD_handler_static_response (&h404,
+                                              connection);
+    if (0 == strcmp (method,
+                     MHD_HTTP_METHOD_OPTIONS))
+      return TALER_MHD_reply_cors_preflight (connection);
+    if (0 != strcmp (method,
+                     MHD_HTTP_METHOD_POST))
+      return TMH_MHD_handler_static_response (&h405,
+                                              connection);
+    if (NULL == end)
     {
       return AH_handler_truth_post (connection,
                                     hc,
@@ -457,11 +459,8 @@ url_handler (void *cls,
                                     upload_data,
                                     upload_data_size);
     }
-    if ( (NULL != end) &&
-         (0 == strcmp (end,
-                       "/solve")) &&
-         (0 == strcmp (method,
-                       MHD_HTTP_METHOD_POST)) )
+    if (0 == strcmp (end,
+                     "/solve"))
     {
       return AH_handler_truth_solve (connection,
                                      hc,
@@ -469,11 +468,8 @@ url_handler (void *cls,
                                      upload_data,
                                      upload_data_size);
     }
-    if ( (NULL != end) &&
-         (0 == strcmp (end,
-                       "/challenge")) &&
-         (0 == strcmp (method,
-                       MHD_HTTP_METHOD_POST)) )
+    if (0 == strcmp (end,
+                     "/challenge"))
     {
       return AH_handler_truth_challenge (connection,
                                          hc,
@@ -481,14 +477,9 @@ url_handler (void *cls,
                                          upload_data,
                                          upload_data_size);
     }
-    if (0 == strcmp (method,
-                     MHD_HTTP_METHOD_OPTIONS))
-    {
-      return TALER_MHD_reply_cors_preflight (connection);
-    }
-    return TMH_MHD_handler_static_response (&h405,
-                                            connection);
-  }
+    /* should be impossible to get here */
+    GNUNET_assert (0);
+  } /* end of "/truth/" prefix */
   path_matched = false;
   for (unsigned int i = 0; NULL != handlers[i].url; i++)
   {
@@ -531,7 +522,6 @@ 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 ();
@@ -1030,7 +1020,6 @@ main (int argc,
                                  "CERTTYPE",
                                  "type of the TLS client certificate, defaults 
to PEM if not specified",
                                  &certtype),
-
     GNUNET_GETOPT_OPTION_END
   };
 
diff --git a/src/backend/anastasis-httpd_truth-challenge.c 
b/src/backend/anastasis-httpd_truth-challenge.c
index c583403..65dc244 100644
--- a/src/backend/anastasis-httpd_truth-challenge.c
+++ b/src/backend/anastasis-httpd_truth-challenge.c
@@ -981,33 +981,6 @@ AH_handler_truth_challenge (
     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;
@@ -1104,6 +1077,9 @@ AH_handler_truth_challenge (
       struct GNUNET_JSON_Specification spec[] = {
         GNUNET_JSON_spec_fixed_auto ("truth_decryption_key",
                                      &gc->truth_key),
+        GNUNET_JSON_spec_mark_optional (
+          GNUNET_JSON_spec_fixed_auto ("payment_secret",
+                                       &gc->payment_identifier)),
         GNUNET_JSON_spec_end ()
       };
       enum GNUNET_GenericReturnValue res;
@@ -1121,6 +1097,12 @@ AH_handler_truth_challenge (
         GNUNET_break_op (0);
         return MHD_YES; /* failure */
       }
+      gc->payment_identifier_provided
+        = ! GNUNET_is_zero (&gc->payment_identifier);
+      if (gc->payment_identifier_provided)
+        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                    "Client provided payment identifier `%s'\n",
+                    TALER_B2S (&gc->payment_identifier));
     }
   }
 
diff --git a/src/backend/anastasis-httpd_truth-solve.c 
b/src/backend/anastasis-httpd_truth-solve.c
index 577ec50..a452d8e 100644
--- a/src/backend/anastasis-httpd_truth-solve.c
+++ b/src/backend/anastasis-httpd_truth-solve.c
@@ -1049,33 +1049,6 @@ AH_handler_truth_solve (
     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;
@@ -1174,6 +1147,9 @@ AH_handler_truth_solve (
                                      &gc->truth_key),
         GNUNET_JSON_spec_fixed_auto ("h_response",
                                      &gc->challenge_response),
+        GNUNET_JSON_spec_mark_optional (
+          GNUNET_JSON_spec_fixed_auto ("payment_secret",
+                                       &gc->payment_identifier)),
         GNUNET_JSON_spec_end ()
       };
       enum GNUNET_GenericReturnValue res;
@@ -1191,6 +1167,12 @@ AH_handler_truth_solve (
         GNUNET_break_op (0);
         return MHD_YES; /* failure */
       }
+      gc->payment_identifier_provided
+        = ! GNUNET_is_zero (&gc->payment_identifier);
+      if (gc->payment_identifier_provided)
+        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                    "Client provided payment identifier `%s'\n",
+                    TALER_B2S (&gc->payment_identifier));
     }
   }
 
diff --git a/src/backend/anastasis-httpd_truth.c 
b/src/backend/anastasis-httpd_truth.c
deleted file mode 100644
index ba6837a..0000000
--- a/src/backend/anastasis-httpd_truth.c
+++ /dev/null
@@ -1,1702 +0,0 @@
-/*
-  This file is part of Anastasis
-  Copyright (C) 2019, 2021 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.c
- * @brief functions to handle incoming requests on /truth
- * @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>
-
-/**
- * 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 GetContext
-{
-
-  /**
-   * 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;
-
-  /**
-   * Kept in DLL for shutdown handling while suspended.
-   */
-  struct GetContext *next;
-
-  /**
-   * Kept in DLL for shutdown handling while suspended.
-   */
-  struct GetContext *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;
-
-  /**
-   * Did the request include a response?
-   */
-  bool have_response;
-
-};
-
-/**
- * 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 GetContext *gc_head;
-
-/**
- * Tail of linked list over all authorization processes
- */
-static struct GetContext *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 GetContext *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 GetContext *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_shutdown (void)
-{
-  struct GetContext *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 GetContext *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 GetContext *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;
-  }
-  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 GetContext *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 GetContext`
- * @param por response details
- */
-static void
-proposal_cb (void *cls,
-             const struct TALER_MERCHANT_PostOrdersReply *por)
-{
-  struct GetContext *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 GetContext`
- * @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 GetContext *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 GetContext *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 GetContext *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 GetContext *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 GetContext *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:
-    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 GetContext *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 GetContext *gc,
-                          const void *decrypted_truth,
-                          size_t decrypted_truth_size)
-{
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Handling security question challenge\n");
-  if (! gc->have_response)
-  {
-    return TALER_MHD_reply_with_error (gc->connection,
-                                       MHD_HTTP_FORBIDDEN,
-                                       
TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED,
-                                       NULL);
-  }
-  /* 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 GetContext *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_get (
-  struct MHD_Connection *connection,
-  const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
-  struct TM_HandlerContext *hc)
-{
-  struct GetContext *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 GetContext);
-    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);
-      }
-    }
-
-    {
-      /* check if header contains Truth-Decryption-Key */
-      const char *tdk;
-
-      tdk = MHD_lookup_connection_value (connection,
-                                         MHD_HEADER_KIND,
-                                         
ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY);
-      if (NULL == tdk)
-      {
-        GNUNET_break_op (0);
-        return TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_BAD_REQUEST,
-                                           TALER_EC_GENERIC_PARAMETER_MISSING,
-                                           
ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY);
-      }
-
-      if (GNUNET_OK !=
-          GNUNET_STRINGS_string_to_data (
-            tdk,
-            strlen (tdk),
-            &gc->truth_key,
-            sizeof (struct ANASTASIS_CRYPTO_TruthKeyP)))
-      {
-        GNUNET_break_op (0);
-        return TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_BAD_REQUEST,
-                                           
TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                                           
ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY);
-      }
-    }
-
-    {
-      const char *challenge_response_s;
-
-      challenge_response_s = MHD_lookup_connection_value (connection,
-                                                          
MHD_GET_ARGUMENT_KIND,
-                                                          "response");
-      if ( (NULL != challenge_response_s) &&
-           (GNUNET_OK !=
-            GNUNET_CRYPTO_hash_from_string (challenge_response_s,
-                                            &gc->challenge_response)) )
-      {
-        GNUNET_break_op (0);
-        return TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_BAD_REQUEST,
-                                           
TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                                           "response");
-      }
-      gc->have_response = (NULL != challenge_response_s);
-    }
-
-    {
-      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! */
-  }
-
-  {
-    /* 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;
-    }
-    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);
-  }
-
-  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 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);
-  }
-
-  /* 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 */
-  if (gc->have_response)
-  {
-    enum ANASTASIS_DB_CodeStatus cs;
-    bool satisfied;
-    uint64_t code;
-
-    GNUNET_free (truth_mime);
-    if (gc->authorization->user_provided_code)
-    {
-      MHD_RESULT res;
-
-      res = direct_validation (gc,
-                               decrypted_truth,
-                               decrypted_truth_size);
-      GNUNET_free (decrypted_truth);
-      return res;
-    }
-
-    /* random code, check against database */
-    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_log (GNUNET_ERROR_TYPE_INFO,
-                  "Response code unknown (possibly expired). Testing if we may 
provide a new one.\n");
-      gc->have_response = false;
-      break;
-    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 return_key_share (&gc->truth_uuid,
-                                 connection);
-      }
-      /* continue with authorization plugin below */
-      gc->code = code;
-      break;
-    default:
-      GNUNET_break (0);
-      return MHD_NO;
-    }
-  }
-  if (! gc->have_response)
-  {
-    /* 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.h 
b/src/backend/anastasis-httpd_truth.h
index d0851ba..a436394 100644
--- a/src/backend/anastasis-httpd_truth.h
+++ b/src/backend/anastasis-httpd_truth.h
@@ -1,6 +1,6 @@
 /*
   This file is part of Anastasis
-  Copyright (C) 2014, 2015, 2016, 2021 Anastasis SARL
+  Copyright (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
@@ -24,14 +24,6 @@
 #define ANASTASIS_HTTPD_TRUTH_H
 #include <microhttpd.h>
 
-
-/**
- * Prepare all active GET truth requests for system shutdown.
- */
-void
-AH_truth_shutdown (void);
-
-
 /**
  * Prepare all active POST truth solve requests for system shutdown.
  */
@@ -52,21 +44,6 @@ void
 AH_truth_upload_shutdown (void);
 
 
-/**
- * Handle a GET to /truth/$UUID
- *
- * @param[in,out] connection the MHD connection to handle
- * @param truth_uuid the truth UUID
- * @param[in,out] hc connection context
- * @return MHD result code
- */
-MHD_RESULT
-AH_handler_truth_get (
-  struct MHD_Connection *connection,
-  const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
-  struct TM_HandlerContext *hc);
-
-
 /**
  * Handle a POST to /truth/$UUID.
  *
diff --git a/src/include/anastasis.h b/src/include/anastasis.h
index 92c0745..90f3f5d 100644
--- a/src/include/anastasis.h
+++ b/src/include/anastasis.h
@@ -97,61 +97,38 @@ ANASTASIS_challenge_get_details (struct ANASTASIS_Challenge 
*challenge);
 /**
  * Possible outcomes of trying to start a challenge operation.
  */
-enum ANASTASIS_ChallengeStatus
+enum ANASTASIS_ChallengeStartStatus
 {
 
   /**
-   * The challenge has been solved.
-   */
-  ANASTASIS_CHALLENGE_STATUS_SOLVED,
-
-  /**
-   * Instructions for how to solve the challenge are provided.  Also
-   * used if the answer we provided was wrong (or if no answer was
-   * provided, but one is needed).
-   */
-  ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS,
-
-  /**
-   * A redirection URL needed to solve the challenge is provided.  Also
-   * used if the answer we provided was wrong (or if no answer was
-   * provided, but one is needed).
+   * We encountered an error talking to the Anastasis service.
    */
-  ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION,
+  ANASTASIS_CHALLENGE_START_STATUS_SERVER_FAILURE,
 
   /**
    * Payment is required before the challenge can be answered.
    */
-  ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED,
-
-  /**
-   * We encountered an error talking to the Anastasis service.
-   */
-  ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE,
+  ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED,
 
   /**
    * The server does not know this truth.
    */
-  ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN,
+  ANASTASIS_CHALLENGE_START_STATUS_TRUTH_UNKNOWN,
 
   /**
-   * The rate limit for solving the challenge was exceeded.
+   * A filename with the TAN has been provided.
    */
-  ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED,
+  ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED,
 
   /**
-   * The user did not satisfy the (external) authentication
-   * challenge in time. The request should be repeated
-   * later and may then succeed.
+   * A TAN has been send, address hint is provided.
    */
-  ANASTASIS_CHALLENGE_STATUS_AUTH_TIMEOUT,
+  ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED,
 
   /**
-   * Plugin-specific ("external") instructions for how to solve the
-   * challenge are provided.
+   * Wire transfer required, banking details provided.
    */
-  ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS
-
+  ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED
 
 };
 
@@ -161,10 +138,21 @@ enum ANASTASIS_ChallengeStatus
  */
 struct ANASTASIS_ChallengeStartResponse
 {
+
+  /**
+   * HTTP status returned by the server.
+   */
+  unsigned int http_status;
+
+  /**
+   * Taler-specific error code.
+   */
+  enum TALER_ErrorCode ec;
+
   /**
    * What is our status on satisfying this challenge. Determines @e details.
    */
-  enum ANASTASIS_ChallengeStatus cs;
+  enum ANASTASIS_ChallengeStartStatus cs;
 
   /**
    * Which challenge is this about?
@@ -179,36 +167,159 @@ struct ANASTASIS_ChallengeStartResponse
 
     /**
      * Challenge details provided if
-     * @e cs is #ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS
+     * @e cs is #ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED.
+     */
+    const char *tan_filename;
+
+    /**
+     * Challenge details provided if
+     * @e cs is #ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED.
+     */
+    const char *tan_address_hint;
+
+    /**
+     * Challenge details provided if
+     * @e cs is #ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED.
+     */
+    struct ANASTASIS_WireFundsDetails bank_transfer_required;
+
+    /**
+     * Response with instructions for how to pay, if
+     * @e cs is #ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED.
      */
     struct
     {
 
       /**
-       * Response with server-side instructions for the user.
+       * "taler://pay" URI with details how to pay for the challenge.
        */
-      const void *body;
+      const char *taler_pay_uri;
 
       /**
-       * Mime type of the data in @e body.
+       * Payment secret from @e taler_pay_uri.
        */
-      const char *content_type;
+      struct ANASTASIS_PaymentSecretP payment_secret;
 
-      /**
-       * Number of bytes in @e body
-       */
-      size_t body_size;
+    } payment_required;
+
+  } details;
+};
 
-      /**
-       * HTTP status returned by the server.  #MHD_HTTP_ALREADY_REPORTED
-       * if the server did already send the challenge to the user,
-       * #MHD_HTTP_FORBIDDEN if the answer was wrong (or missing).
-       */
-      unsigned int http_status;
-    } open_challenge;
+
+/**
+ * Defines a callback for the response status for a challenge start
+ * operation.
+ *
+ * @param cls closure
+ * @param csr response details
+ */
+typedef void
+(*ANASTASIS_ChallengeStartFeedback)(
+  void *cls,
+  const struct ANASTASIS_ChallengeStartResponse *csr);
+
+
+/**
+ * User starts a challenge which reponds out of bounds (E-Mail, SMS,
+ * Postal..)  If the challenge is zero cost, the challenge
+ * instructions will be sent to the client. If the challenge needs
+ * payment a payment link is sent to the client. After payment the
+ * challenge start method has to be called again.
+ *
+ * @param c reference to the escrow challenge which is started
+ * @param psp payment secret, NULL if no payment was yet made
+ * @param af reference to the answerfeedback which is passed back to the user
+ * @param af_cls closure for @a af
+ * @return #GNUNET_OK if the challenge was successfully started
+ */
+enum GNUNET_GenericReturnValue
+ANASTASIS_challenge_start (struct ANASTASIS_Challenge *c,
+                           const struct ANASTASIS_PaymentSecretP *psp,
+                           ANASTASIS_ChallengeStartFeedback af,
+                           void *af_cls);
+
+
+/**
+ * Possible outcomes of trying to start a challenge operation.
+ */
+enum ANASTASIS_ChallengeAnswerStatus
+{
+
+  /**
+   * The challenge has been solved.
+   */
+  ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED,
+
+  /**
+   * Payment is required before the challenge can be answered.
+   */
+  ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED,
+
+  /**
+   * We encountered an error talking to the Anastasis service.
+   */
+  ANASTASIS_CHALLENGE_ANSWER_STATUS_SERVER_FAILURE,
+
+  /**
+   * The server does not know this truth.
+   */
+  ANASTASIS_CHALLENGE_ANSWER_STATUS_TRUTH_UNKNOWN,
+
+  /**
+   * The answer was wrong.
+   */
+  ANASTASIS_CHALLENGE_ANSWER_STATUS_INVALID_ANSWER,
+
+  /**
+   * The rate limit for solving the challenge was exceeded.
+   */
+  ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED,
+
+  /**
+   * The user did not satisfy the (external) authentication
+   * challenge in time. The request should be repeated
+   * later and may then succeed.
+   */
+  ANASTASIS_CHALLENGE_ANSWER_STATUS_AUTH_TIMEOUT
+
+
+};
+
+
+/**
+ * Response from an #ANASTASIS_challenge_start() operation.
+ */
+struct ANASTASIS_ChallengeAnswerResponse
+{
+
+  /**
+   * HTTP status returned by the server.
+   */
+  unsigned int http_status;
+
+  /**
+   * Taler-specific error code.
+   */
+  enum TALER_ErrorCode ec;
+
+  /**
+   * What is our status on satisfying this challenge. Determines @e details.
+   */
+  enum ANASTASIS_ChallengeAnswerStatus cs;
+
+  /**
+   * Which challenge is this about?
+   */
+  struct ANASTASIS_Challenge *challenge;
+
+  /**
+   * Details depending on @e cs
+   */
+  union
+  {
 
     /**
-     * Details for #ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED.
+     * Details for #ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED.
      */
     struct
     {
@@ -226,21 +337,9 @@ struct ANASTASIS_ChallengeStartResponse
 
     } rate_limit_exceeded;
 
-    /**
-     * Response with details if
-     * @e cs is #ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS.
-     */
-    const json_t *external_challenge;
-
-    /**
-     * Response with URL to redirect the user to, if
-     * @e cs is #ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION.
-     */
-    const char *redirect_url;
-
     /**
      * Response with instructions for how to pay, if
-     * @e cs is #ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED.
+     * @e cs is #ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED.
      */
     struct
     {
@@ -257,26 +356,6 @@ struct ANASTASIS_ChallengeStartResponse
 
     } payment_required;
 
-
-    /**
-     * Response with details about a server-side failure, if
-     * @e cs is #ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE.
-     */
-    struct
-    {
-
-      /**
-       * HTTP status returned by the server.
-       */
-      unsigned int http_status;
-
-      /**
-       * Taler-specific error code.
-       */
-      enum TALER_ErrorCode ec;
-
-    } server_failure;
-
   } details;
 };
 
@@ -286,36 +365,12 @@ struct ANASTASIS_ChallengeStartResponse
  * operation.
  *
  * @param cls closure
- * @param csr response details
+ * @param car response details
  */
 typedef void
 (*ANASTASIS_AnswerFeedback)(
   void *cls,
-  const struct ANASTASIS_ChallengeStartResponse *csr);
-
-
-/**
- * User starts a challenge which reponds out of bounds (E-Mail, SMS,
- * Postal..)  If the challenge is zero cost, the challenge
- * instructions will be sent to the client. If the challenge needs
- * payment a payment link is sent to the client. After payment the
- * challenge start method has to be called again.
- *
- * @param c reference to the escrow challenge which is started
- * @param psp payment secret, NULL if no payment was yet made
- * @param timeout how long to wait for payment
- * @param hashed_answer answer to the challenge, NULL if we have none yet
- * @param af reference to the answerfeedback which is passed back to the user
- * @param af_cls closure for @a af
- * @return #GNUNET_OK if the challenge was successfully started
- */
-enum GNUNET_GenericReturnValue
-ANASTASIS_challenge_start (struct ANASTASIS_Challenge *c,
-                           const struct ANASTASIS_PaymentSecretP *psp,
-                           struct GNUNET_TIME_Relative timeout,
-                           const struct GNUNET_HashCode *hashed_answer,
-                           ANASTASIS_AnswerFeedback af,
-                           void *af_cls);
+  const struct ANASTASIS_ChallengeAnswerResponse *car);
 
 
 /**
@@ -328,8 +383,8 @@ ANASTASIS_challenge_start (struct ANASTASIS_Challenge *c,
  * @param psp information about payment made for the recovery
  * @param timeout how long to wait for payment
  * @param answer user input instruction defines which input is needed
- * @param af reference to the answerfeedback which is passed back to the user
- * @param af_cls closure for @a af
+ * @param csf function to call with the result
+ * @param csf_cls closure for @a csf
  * @return #GNUNET_OK on success
  */
 enum GNUNET_GenericReturnValue
@@ -337,8 +392,8 @@ ANASTASIS_challenge_answer (struct ANASTASIS_Challenge *c,
                             const struct ANASTASIS_PaymentSecretP *psp,
                             struct GNUNET_TIME_Relative timeout,
                             const char *answer,
-                            ANASTASIS_AnswerFeedback af,
-                            void *af_cls);
+                            ANASTASIS_AnswerFeedback csf,
+                            void *csf_cls);
 
 
 /**
@@ -364,6 +419,30 @@ ANASTASIS_challenge_answer2 (struct ANASTASIS_Challenge *c,
                              void *af_cls);
 
 
+/**
+ * User starts a challenge which reponds out of bounds (E-Mail, SMS,
+ * Postal..)  If the challenge is zero cost, the challenge
+ * instructions will be sent to the client. If the challenge needs
+ * payment a payment link is sent to the client. After payment the
+ * challenge start method has to be called again.
+ *
+ * @param c reference to the escrow challenge which is started
+ * @param psp payment secret, NULL if no payment was yet made
+ * @param timeout how long to wait for payment
+ * @param hashed_answer answer to the challenge
+ * @param af reference to the answerfeedback which is passed back to the user
+ * @param af_cls closure for @a af
+ * @return #GNUNET_OK if the challenge was successfully started
+ */
+enum GNUNET_GenericReturnValue
+ANASTASIS_challenge_answer3 (struct ANASTASIS_Challenge *c,
+                             const struct ANASTASIS_PaymentSecretP *psp,
+                             struct GNUNET_TIME_Relative timeout,
+                             const struct GNUNET_HashCode *hashed_answer,
+                             ANASTASIS_AnswerFeedback af,
+                             void *af_cls);
+
+
 /**
  * Abort answering challenge.
  *
diff --git a/src/include/anastasis_service.h b/src/include/anastasis_service.h
index c21cde5..e88b7e0 100644
--- a/src/include/anastasis_service.h
+++ b/src/include/anastasis_service.h
@@ -516,258 +516,6 @@ ANASTASIS_policy_store_cancel (
 /****** TRUTH API ******/
 
 
-/**
- * Operational status.
- */
-enum ANASTASIS_KeyShareDownloadStatus
-{
-  /**
-   * We got the encrypted key share.
-   */
-  ANASTASIS_KSD_SUCCESS = 0,
-
-  /**
-   * Payment is needed to proceed with the recovery.
-   */
-  ANASTASIS_KSD_PAYMENT_REQUIRED,
-
-  /**
-   * The provided answer was wrong or missing. Instructions for
-   * getting a good answer may be provided.
-   */
-  ANASTASIS_KSD_INVALID_ANSWER,
-
-  /**
-   * To answer the challenge, the client should be redirected to
-   * the given URL.
-   */
-  ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION,
-
-  /**
-   * The provider had an error.
-   */
-  ANASTASIS_KSD_SERVER_ERROR,
-
-  /**
-   * The provider claims we made an error.
-   */
-  ANASTASIS_KSD_CLIENT_FAILURE,
-
-  /**
-   * The provider does not know this truth.
-   */
-  ANASTASIS_KSD_TRUTH_UNKNOWN,
-
-  /**
-   * Too many attempts to solve the challenge were made in a short
-   * time. Try again later.
-   */
-  ANASTASIS_KSD_RATE_LIMIT_EXCEEDED,
-
-  /**
-   * The user did not satisfy the (external)
-   * authentication check until the request timeout
-   * was reached. The client should try again later.
-   */
-  ANASTASIS_KSD_AUTHENTICATION_TIMEOUT,
-
-  /**
-   * The plugin provided external challenge instructions
-   * that should be followed. They are method-specific.
-   */
-  ANASTASIS_KSD_EXTERNAL_CHALLENGE_INSTRUCTIONS
-
-};
-
-
-/**
- * Detailed results from the successful download.
- */
-struct ANASTASIS_KeyShareDownloadDetails
-{
-
-  /**
-   * Operational status.
-   */
-  enum ANASTASIS_KeyShareDownloadStatus status;
-
-  /**
-   * Anastasis URL that returned the @e status.
-   */
-  const char *server_url;
-
-  /**
-   * Details depending on @e status.
-   */
-  union
-  {
-
-    /**
-     * The encrypted key share (if @e status is #ANASTASIS_KSD_SUCCESS).
-     */
-    struct ANASTASIS_CRYPTO_EncryptedKeyShareP eks;
-
-    /**
-     * Response if the challenge still needs to be answered, and the
-     * instructions are provided inline (no redirection).
-     */
-    struct
-    {
-
-      /**
-       * HTTP status returned by the server.  #MHD_HTTP_ALREADY_REPORTED
-       * if the server did already send the challenge to the user,
-       * #MHD_HTTP_FORBIDDEN if the answer was wrong (or missing).
-       */
-      unsigned int http_status;
-
-      /**
-       * Response with server-side reply containing instructions for the user
-       */
-      const char *body;
-
-      /**
-       * Content-type: mime type of @e body, NULL if server did not provide 
any.
-       */
-      const char *content_type;
-
-      /**
-       * Number of bytes in @e body.
-       */
-      size_t body_size;
-
-    } open_challenge;
-
-    /**
-     * URL with instructions for the user to satisfy the challenge, if
-     * @e status is #ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION.
-     */
-    const char *redirect_url;
-
-    /**
-     * Response with instructions for how to pay, if
-     * @e status is #ANASTASIS_KSD_PAYMENT_REQUIRED.
-     */
-    struct
-    {
-
-      /**
-       * "taler://pay" URL with details how to pay for the challenge.
-       */
-      const char *taler_pay_uri;
-
-      /**
-       * The order ID from @e taler_pay_uri.
-       */
-      struct ANASTASIS_PaymentSecretP payment_secret;
-
-    } payment_required;
-
-
-    struct
-    {
-
-      /**
-       * How many requests are allowed at most per @e request_frequency?
-       */
-      uint32_t request_limit;
-
-      /**
-       * Frequency at which requests are allowed / new challenges are
-       * created.
-       */
-      struct GNUNET_TIME_Relative request_frequency;
-
-    } rate_limit_exceeded;
-
-
-    /**
-     * Response with details about a server-side failure, if
-     * @e status is #ANASTASIS_KSD_SERVER_ERROR,
-     * #ANASTASIS_KSD_CLIENT_FAILURE or #ANASTASIS_KSD_TRUTH_UNKNOWN.
-     */
-    struct
-    {
-
-      /**
-       * HTTP status returned by the server.
-       */
-      unsigned int http_status;
-
-      /**
-       * Taler-specific error code.
-       */
-      enum TALER_ErrorCode ec;
-
-    } server_failure;
-
-    /**
-     * External challenge instructions, if @e status is
-     * #ANASTASIS_KSD_EXTERNAL_CHALLENGE_INSTRUCTIONS.
-     */
-    const json_t *external_challenge;
-
-  } details;
-};
-
-
-/**
- * Handle for a GET /truth operation.
- */
-struct ANASTASIS_KeyShareLookupOperation;
-
-
-/**
- * Callback to process a GET /truth request
- *
- * @param cls closure
- * @param http_status HTTP status code for this request
- * @param kdd details about the key share
- */
-typedef void
-(*ANASTASIS_KeyShareLookupCallback) (
-  void *cls,
-  const struct ANASTASIS_KeyShareDownloadDetails *kdd);
-
-
-/**
- * Does a GET /truth.
- *
- * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param truth_uuid identification of the Truth
- * @param truth_key Key used to Decrypt the Truth on the Server
- * @param payment_secret secret from the previously done payment NULL to 
trigger payment
- * @param timeout how long to wait for the payment, use
- *           #GNUNET_TIME_UNIT_ZERO to let the server pick
- * @param hashed_answer hashed answer to the challenge
- * @param cb callback which will work the response gotten from the backend
- * @param cb_cls closure to pass to the callback
- * @return handle for this operation, NULL upon errors
- */
-struct ANASTASIS_KeyShareLookupOperation *
-ANASTASIS_keyshare_lookup (
-  struct GNUNET_CURL_Context *ctx,
-  const char *backend_url,
-  const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
-  const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key,
-  const struct ANASTASIS_PaymentSecretP *payment_secret,
-  struct GNUNET_TIME_Relative timeout,
-  const struct GNUNET_HashCode *hashed_answer,
-  ANASTASIS_KeyShareLookupCallback cb,
-  void *cb_cls);
-
-
-/**
- * Cancel a GET /truth request.
- *
- * @param kslo cancel the key share lookup operation
- */
-void
-ANASTASIS_keyshare_lookup_cancel (
-  struct ANASTASIS_KeyShareLookupOperation *kslo);
-
-
 /**
  * Handle for a POST /truth operation.
  */
@@ -835,6 +583,12 @@ ANASTASIS_truth_store_cancel (
 enum ANASTASIS_ChallengeDetailType
 {
 
+  /**
+   * A challenge TAN was written to a file.
+   * The name of the file is provided.
+   */
+  ANASTASIS_CS_FILE_WRITTEN,
+
   /**
    * A challenge TAN was sent to the customer.
    * A hint may be provided as to the address used.
@@ -850,6 +604,42 @@ enum ANASTASIS_ChallengeDetailType
 };
 
 
+/**
+ * This structure contains information about where to wire the funds
+ * to authenticate as well as a hint as to which bank account to send
+ * the funds from.
+ */
+struct ANASTASIS_WireFundsDetails
+{
+
+  /**
+   * Answer code expected.
+   */
+  uint64_t answer_code;
+
+  /**
+   * How much should be sent.
+   */
+  struct TALER_Amount amount;
+
+  /**
+   * IBAN where to send the funds.
+   */
+  const char *target_iban;
+
+  /**
+   * Name of the business receiving the funds.
+   */
+  const char *target_business_name;
+
+  /**
+   * Wire transfer subject to use.
+   */
+  const char *wire_transfer_subject;
+
+};
+
+
 /**
  * Information returned for a POST /truth/$TID/challenge request.
  */
@@ -893,6 +683,12 @@ struct ANASTASIS_TruthChallengeDetails
       union
       {
 
+        /**
+         * If @e cs is #ANASTASIS_CS_FILE_WRITTEN, this
+         * is the filename with the challenge code.
+         */
+        const char *challenge_filename;
+
         /**
          * If @e cs is #ANASTASIS_CS_TAN_SENT, this
          * is human-readable information as to where
@@ -907,28 +703,7 @@ struct ANASTASIS_TruthChallengeDetails
          * as a hint as to which bank account to send
          * the funds from.
          */
-        struct
-        {
-
-          /**
-           * How much should be sent.
-           */
-          struct TALER_Amount amount;
-
-          /**
-           * payto:// URI with the target account number.
-           */
-          const char *target_payto;
-
-          /**
-           * Human-readable hint about which sender bank
-           * account must be used.
-           */
-          const char *sender_hint;
-
-          // FIXME: more? Wire transfer subject?
-
-        } wire_funds;
+        struct ANASTASIS_WireFundsDetails wire_funds;
 
       } details;
 
diff --git a/src/include/anastasis_testing_lib.h 
b/src/include/anastasis_testing_lib.h
index ba1b8a3..0066939 100644
--- a/src/include/anastasis_testing_lib.h
+++ b/src/include/anastasis_testing_lib.h
@@ -374,7 +374,28 @@ ANASTASIS_TESTING_cmd_truth_question (
 
 
 /**
- * Make the "keyshare lookup" command.
+ * Make a "truth challenge" command.
+ *
+ * @param label command label
+ * @param anastasis_url base URL of the ANASTASIS serving
+ *        the keyshare lookup request.
+ * @param answer (response to challenge)
+ * @param payment_ref reference to the payment request
+ * @param upload_ref reference to upload command
+ * @param http_status expected HTTP status
+ * @return the command
+ */
+struct TALER_TESTING_Command
+ANASTASIS_TESTING_cmd_truth_challenge (
+  const char *label,
+  const char *anastasis_url,
+  const char *payment_ref,
+  const char *upload_ref,
+  unsigned int http_status);
+
+
+/**
+ * Make a "truth solve" command.
  *
  * @param label command label
  * @param anastasis_url base URL of the ANASTASIS serving
@@ -384,18 +405,18 @@ ANASTASIS_TESTING_cmd_truth_question (
  * @param upload_ref reference to upload command
  * @param lookup_mode 0 for security question, 1 for
  *          code-based
- * @param ksdd expected status
+ * @param http_status expected HTTP status
  * @return the command
  */
 struct TALER_TESTING_Command
-ANASTASIS_TESTING_cmd_keyshare_lookup (
+ANASTASIS_TESTING_cmd_truth_solve (
   const char *label,
   const char *anastasis_url,
   const char *answer,
   const char *payment_ref,
   const char *upload_ref,
   int lookup_mode,
-  enum ANASTASIS_KeyShareDownloadStatus ksdd);
+  unsigned int http_status);
 
 
 /**
@@ -633,7 +654,7 @@ ANASTASIS_TESTING_cmd_challenge_start (
   const char *payment_ref,
   const char *challenge_ref,
   unsigned int challenge_index,
-  enum ANASTASIS_ChallengeStatus expected_cs);
+  enum ANASTASIS_ChallengeStartStatus expected_cs);
 
 
 /**
@@ -657,7 +678,7 @@ ANASTASIS_TESTING_cmd_challenge_answer (
   unsigned int challenge_index,
   const char *answer,
   unsigned int mode,
-  enum ANASTASIS_ChallengeStatus expected_cs);
+  enum ANASTASIS_ChallengeAnswerStatus expected_cs);
 
 
 #endif
diff --git a/src/lib/anastasis_recovery.c b/src/lib/anastasis_recovery.c
index 9e5d1ca..a450392 100644
--- a/src/lib/anastasis_recovery.c
+++ b/src/lib/anastasis_recovery.c
@@ -63,15 +63,26 @@ struct ANASTASIS_Challenge
 
   /**
    * Callback which gives back the instructions and a status code of
-   * the request to the user when answering a challenge was initiated.
+   * the request to the user when answering a challenge.
    */
   ANASTASIS_AnswerFeedback af;
 
   /**
-   * Closure for the challenge callback
+   * Closure for @e af.
    */
   void *af_cls;
 
+  /**
+   * Callback which gives back the instructions and a status code of
+   * the request to the user when initiating a challenge.
+   */
+  ANASTASIS_ChallengeStartFeedback csf;
+
+  /**
+   * Closure for @e csf.
+   */
+  void *csf_cls;
+
   /**
    * Defines the base URL of the Anastasis provider used for the challenge.
    */
@@ -99,9 +110,14 @@ struct ANASTASIS_Challenge
   struct ANASTASIS_Recovery *recovery;
 
   /**
-   * keyshare lookup operation
+   * Handle for the /truth/$TID/challenge request.
+   */
+  struct ANASTASIS_TruthChallengeOperation *tco;
+
+  /**
+   * Handle for the /truth/$TID/solve request.
    */
-  struct ANASTASIS_KeyShareLookupOperation *kslo;
+  struct ANASTASIS_TruthSolveOperation *tso;
 
 };
 
@@ -238,165 +254,137 @@ struct ANASTASIS_Recovery
 
 
 /**
- * Function called with the results of a #ANASTASIS_keyshare_lookup().
+ * Function called with the results of a #ANASTASIS_challenge_start().
  *
  * @param cls closure
  * @param dd details about the lookup operation
  */
 static void
-keyshare_lookup_cb (void *cls,
-                    const struct ANASTASIS_KeyShareDownloadDetails *dd)
+truth_challenge_cb (void *cls,
+                    const struct ANASTASIS_TruthChallengeDetails *tcd)
 {
   struct ANASTASIS_Challenge *c = cls;
-  struct ANASTASIS_Recovery *recovery = c->recovery;
-  struct ANASTASIS_CRYPTO_UserIdentifierP id;
-  struct DecryptionPolicy *rdps;
-
-  c->kslo = NULL;
-  switch (dd->status)
+  struct ANASTASIS_ChallengeStartResponse csr = {
+    .challenge = c,
+    .ec = tcd->ec,
+    .http_status = tcd->http_status
+  };
+
+  c->tco = NULL;
+  switch (tcd->http_status)
   {
-  case ANASTASIS_KSD_SUCCESS:
-    break;
-  case ANASTASIS_KSD_PAYMENT_REQUIRED:
-    {
-      struct ANASTASIS_ChallengeStartResponse csr = {
-        .cs = ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED,
-        .challenge = c,
-        .details.payment_required.taler_pay_uri
-          = dd->details.payment_required.taler_pay_uri,
-        .details.payment_required.payment_secret
-          = dd->details.payment_required.payment_secret
-      };
-
-      c->af (c->af_cls,
-             &csr);
-      return;
-    }
-  case ANASTASIS_KSD_INVALID_ANSWER:
-    {
-      struct ANASTASIS_ChallengeStartResponse csr = {
-        .cs = ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS,
-        .challenge = c,
-        .details.open_challenge.body
-          = dd->details.open_challenge.body,
-        .details.open_challenge.content_type
-          = dd->details.open_challenge.content_type,
-        .details.open_challenge.body_size
-          = dd->details.open_challenge.body_size,
-        .details.open_challenge.http_status
-          = dd->details.open_challenge.http_status
-      };
-
-      c->af (c->af_cls,
-             &csr);
-      return;
-    }
-  case ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION:
-    {
-      struct ANASTASIS_ChallengeStartResponse csr = {
-        .cs = ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION,
-        .challenge = c,
-        .details.redirect_url
-          = dd->details.redirect_url
-      };
-
-      c->af (c->af_cls,
-             &csr);
-      return;
-    }
-  case ANASTASIS_KSD_TRUTH_UNKNOWN:
+  case MHD_HTTP_OK:
+    switch (tcd->details.success.cs)
     {
-      struct ANASTASIS_ChallengeStartResponse csr = {
-        .cs = ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN,
-        .challenge = c
-      };
-
-      c->af (c->af_cls,
-             &csr);
-      return;
+    case ANASTASIS_CS_FILE_WRITTEN:
+      csr.cs = ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED;
+      csr.details.tan_filename
+        = tcd->details.success.details.challenge_filename;
+      break;
+    case ANASTASIS_CS_TAN_SENT:
+      csr.cs = ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED;
+      csr.details.tan_address_hint
+        = tcd->details.success.details.tan_address_hint;
+      break;
+    case ANASTASIS_CS_WIRE_FUNDS:
+      csr.cs = ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED;
+      csr.details.bank_transfer_required
+        = tcd->details.success.details.wire_funds;
+      break;
     }
-  case ANASTASIS_KSD_RATE_LIMIT_EXCEEDED:
-    {
-      struct ANASTASIS_ChallengeStartResponse csr = {
-        .cs = ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED,
-        .challenge = c,
-        .details.rate_limit_exceeded.request_limit
-          = dd->details.rate_limit_exceeded.request_limit,
-        .details.rate_limit_exceeded.request_frequency
-          = dd->details.rate_limit_exceeded.request_frequency
-      };
+    break;
+  case MHD_HTTP_PAYMENT_REQUIRED:
+    csr.cs = ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED;
+    csr.details.payment_required.taler_pay_uri
+      = tcd->details.payment_required.payment_request;
+    csr.details.payment_required.payment_secret
+      = tcd->details.payment_required.ps;
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    csr.cs = ANASTASIS_CHALLENGE_START_STATUS_TRUTH_UNKNOWN;
+    break;
+  default:
+    csr.cs = ANASTASIS_CHALLENGE_START_STATUS_SERVER_FAILURE;
+    break;
+  }
+  c->csf (c->csf_cls,
+          &csr);
+}
 
-      c->af (c->af_cls,
-             &csr);
-      return;
-    }
-  case ANASTASIS_KSD_SERVER_ERROR:
-  case ANASTASIS_KSD_CLIENT_FAILURE:
-    {
-      struct ANASTASIS_ChallengeStartResponse csr = {
-        .cs = ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE,
-        .challenge = c,
-        .details.server_failure.ec
-          = dd->details.server_failure.ec,
-        .details.server_failure.http_status
-          = dd->details.server_failure.http_status
-      };
 
-      c->af (c->af_cls,
-             &csr);
-      return;
-    }
-  case ANASTASIS_KSD_AUTHENTICATION_TIMEOUT:
-    {
-      struct ANASTASIS_ChallengeStartResponse csr = {
-        .cs = ANASTASIS_CHALLENGE_STATUS_AUTH_TIMEOUT,
-        .challenge = c,
-        .details.server_failure.ec
-          = dd->details.server_failure.ec,
-        .details.server_failure.http_status
-          = dd->details.server_failure.http_status
-      };
+/**
+ * Function called with the results of a #ANASTASIS_truth_solve().
+ *
+ * @param cls closure
+ * @param tsr details about the solution response
+ */
+static void
+truth_solve_cb (void *cls,
+                const struct ANASTASIS_TruthSolveReply *tsr)
+{
+  struct ANASTASIS_Challenge *c = cls;
+  struct ANASTASIS_Recovery *recovery = c->recovery;
+  struct ANASTASIS_CRYPTO_UserIdentifierP id;
+  struct DecryptionPolicy *rdps;
+  struct ANASTASIS_ChallengeAnswerResponse csr = {
+    .challenge = c,
+    .ec = tsr->ec,
+    .http_status = tsr->http_status
+  };
 
-      c->ci.async = true;
-      c->af (c->af_cls,
-             &csr);
-      return;
-    }
-  case ANASTASIS_KSD_EXTERNAL_CHALLENGE_INSTRUCTIONS:
-    {
-      struct ANASTASIS_ChallengeStartResponse csr = {
-        .cs = ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS,
-        .challenge = c,
-        .details.external_challenge = dd->details.external_challenge
-      };
 
-      c->af (c->af_cls,
-             &csr);
-      return;
-    }
+  c->tso = NULL;
+  switch (tsr->http_status)
+  {
+  case MHD_HTTP_OK:
+    break;
+  case MHD_HTTP_PAYMENT_REQUIRED:
+    csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED;
+    csr.details.payment_required.taler_pay_uri
+      = tsr->details.payment_required.payment_request;
+    csr.details.payment_required.payment_secret
+      = tsr->details.payment_required.ps;
+    c->af (c->af_cls,
+           &csr);
+    return;
+  case MHD_HTTP_FORBIDDEN:
+    csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_INVALID_ANSWER;
+    c->af (c->af_cls,
+           &csr);
+    return;
+  case MHD_HTTP_NOT_FOUND:
+    csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_TRUTH_UNKNOWN;
+    c->af (c->af_cls,
+           &csr);
+    return;
+  case MHD_HTTP_TOO_MANY_REQUESTS:
+    csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED;
+    csr.details.rate_limit_exceeded.request_limit
+      = tsr->details.too_many_requests.request_limit;
+    csr.details.rate_limit_exceeded.request_frequency
+      = tsr->details.too_many_requests.request_frequency;
+    c->af (c->af_cls,
+           &csr);
+    return;
+  default:
+    csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_SERVER_FAILURE;
+    c->af (c->af_cls,
+           &csr);
+    return;
   }
 
-  GNUNET_assert (NULL != dd);
   ANASTASIS_CRYPTO_user_identifier_derive (recovery->id_data,
                                            &c->provider_salt,
                                            &id);
-  ANASTASIS_CRYPTO_keyshare_decrypt (&dd->details.eks,
+  ANASTASIS_CRYPTO_keyshare_decrypt (&tsr->details.success.eks,
                                      &id,
                                      c->answer,
                                      &c->key_share);
   recovery->solved_challenges[recovery->solved_challenge_pos++] = c;
-
-  {
-    struct ANASTASIS_ChallengeStartResponse csr = {
-      .cs = ANASTASIS_CHALLENGE_STATUS_SOLVED,
-      .challenge = c
-    };
-
-    c->ci.solved = true;
-    c->af (c->af_cls,
-           &csr);
-  }
-
+  c->ci.solved = true;
+  csr.cs = ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED;
+  c->af (c->af_cls,
+         &csr);
 
   /* Check if there is a policy for which all challenges have
      been satisfied, if so, store it in 'rdps'. */
@@ -477,33 +465,67 @@ ANASTASIS_challenge_get_details (struct 
ANASTASIS_Challenge *challenge)
 enum GNUNET_GenericReturnValue
 ANASTASIS_challenge_start (struct ANASTASIS_Challenge *c,
                            const struct ANASTASIS_PaymentSecretP *psp,
-                           struct GNUNET_TIME_Relative timeout,
-                           const struct GNUNET_HashCode *hashed_answer,
-                           ANASTASIS_AnswerFeedback af,
-                           void *af_cls)
+                           ANASTASIS_ChallengeStartFeedback csf,
+                           void *csf_cls)
 {
   if (c->ci.solved)
   {
     GNUNET_break (0);
     return GNUNET_NO; /* already solved */
   }
-  if (NULL != c->kslo)
+  if (NULL != c->tco)
+  {
+    GNUNET_break (0);
+    return GNUNET_NO; /* already solving */
+  }
+  c->csf = csf;
+  c->csf_cls = csf_cls;
+  c->tco = ANASTASIS_truth_challenge (c->recovery->ctx,
+                                      c->url,
+                                      &c->ci.uuid,
+                                      &c->truth_key,
+                                      psp,
+                                      &truth_challenge_cb,
+                                      c);
+  if (NULL == c->tco)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+ANASTASIS_challenge_answer3 (struct ANASTASIS_Challenge *c,
+                             const struct ANASTASIS_PaymentSecretP *psp,
+                             struct GNUNET_TIME_Relative timeout,
+                             const struct GNUNET_HashCode *hashed_answer,
+                             ANASTASIS_AnswerFeedback af,
+                             void *af_cls)
+{
+  if (c->ci.solved)
+  {
+    GNUNET_break (0);
+    return GNUNET_NO; /* already solved */
+  }
+  if (NULL != c->tso)
   {
     GNUNET_break (0);
     return GNUNET_NO; /* already solving */
   }
   c->af = af;
   c->af_cls = af_cls;
-  c->kslo = ANASTASIS_keyshare_lookup (c->recovery->ctx,
-                                       c->url,
-                                       &c->ci.uuid,
-                                       &c->truth_key,
-                                       psp,
-                                       timeout,
-                                       hashed_answer,
-                                       &keyshare_lookup_cb,
-                                       c);
-  if (NULL == c->kslo)
+  c->tso = ANASTASIS_truth_solve (c->recovery->ctx,
+                                  c->url,
+                                  &c->ci.uuid,
+                                  &c->truth_key,
+                                  psp,
+                                  timeout,
+                                  hashed_answer,
+                                  &truth_solve_cb,
+                                  c);
+  if (NULL == c->tso)
   {
     GNUNET_break (0);
     return GNUNET_SYSERR;
@@ -529,12 +551,12 @@ ANASTASIS_challenge_answer (
                                        &c->ci.uuid,
                                        &c->salt,
                                        &hashed_answer);
-  return ANASTASIS_challenge_start (c,
-                                    psp,
-                                    timeout,
-                                    &hashed_answer,
-                                    af,
-                                    af_cls);
+  return ANASTASIS_challenge_answer3 (c,
+                                      psp,
+                                      timeout,
+                                      &hashed_answer,
+                                      af,
+                                      af_cls);
 }
 
 
@@ -550,25 +572,28 @@ ANASTASIS_challenge_answer2 (struct ANASTASIS_Challenge 
*c,
 
   ANASTASIS_hash_answer (answer,
                          &answer_s);
-  return ANASTASIS_challenge_start (c,
-                                    psp,
-                                    timeout,
-                                    &answer_s,
-                                    af,
-                                    af_cls);
+  return ANASTASIS_challenge_answer3 (c,
+                                      psp,
+                                      timeout,
+                                      &answer_s,
+                                      af,
+                                      af_cls);
 }
 
 
 void
 ANASTASIS_challenge_abort (struct ANASTASIS_Challenge *c)
 {
-  if (NULL == c->kslo)
+  if (NULL != c->tso)
   {
-    GNUNET_break (0);
-    return;
+    ANASTASIS_truth_solve_cancel (c->tso);
+    c->tso = NULL;
+  }
+  if (NULL != c->tco)
+  {
+    ANASTASIS_truth_challenge_cancel (c->tco);
+    c->tco = NULL;
   }
-  ANASTASIS_keyshare_lookup_cancel (c->kslo);
-  c->kslo = NULL;
   c->af = NULL;
   c->af_cls = NULL;
 }
@@ -1461,10 +1486,10 @@ ANASTASIS_recovery_abort (struct ANASTASIS_Recovery *r)
   {
     struct ANASTASIS_Challenge *cs = &r->cs[i];
 
-    if (NULL != cs->kslo)
+    if (NULL != cs->tso)
     {
-      ANASTASIS_keyshare_lookup_cancel (cs->kslo);
-      cs->kslo = NULL;
+      ANASTASIS_truth_solve_cancel (cs->tso);
+      cs->tso = NULL;
     }
     GNUNET_free (cs->url);
     GNUNET_free (cs->type);
diff --git a/src/reducer/anastasis_api_recovery_redux.c 
b/src/reducer/anastasis_api_recovery_redux.c
index 2be963f..5de278c 100644
--- a/src/reducer/anastasis_api_recovery_redux.c
+++ b/src/reducer/anastasis_api_recovery_redux.c
@@ -504,7 +504,7 @@ find_challenge_in_cs (json_t *state,
  * @param csr response details
  */
 static void
-answer_feedback_cb (
+start_feedback_cb (
   void *cls,
   const struct ANASTASIS_ChallengeStartResponse *csr)
 {
@@ -533,7 +533,246 @@ answer_feedback_cb (
   }
   switch (csr->cs)
   {
-  case ANASTASIS_CHALLENGE_STATUS_SOLVED:
+  case ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED:
+    {
+      json_t *instructions;
+
+      instructions = GNUNET_JSON_PACK (
+        GNUNET_JSON_pack_string ("state",
+                                 "hint"),
+        GNUNET_JSON_pack_string ("hint",
+                                 csr->details.tan_filename),
+        GNUNET_JSON_pack_uint64 ("http_status",
+                                 (json_int_t) csr->http_status));
+      GNUNET_assert (0 ==
+                     json_object_set_new (feedback,
+                                          uuid,
+                                          instructions));
+    }
+    set_state (sctx->state,
+               ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
+    sctx->cb (sctx->cb_cls,
+              TALER_EC_NONE,
+              sctx->state);
+    sctx_free (sctx);
+    return;
+  case ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED:
+    {
+      json_t *instructions;
+
+      instructions = GNUNET_JSON_PACK (
+        GNUNET_JSON_pack_string ("state",
+                                 "hint"),
+        GNUNET_JSON_pack_string ("hint",
+                                 csr->details.tan_address_hint),
+        GNUNET_JSON_pack_uint64 ("http_status",
+                                 (json_int_t) csr->http_status));
+      GNUNET_assert (0 ==
+                     json_object_set_new (feedback,
+                                          uuid,
+                                          instructions));
+    }
+    set_state (sctx->state,
+               ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
+    sctx->cb (sctx->cb_cls,
+              TALER_EC_NONE,
+              sctx->state);
+    sctx_free (sctx);
+    return;
+  case ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED:
+    {
+      json_t *pay;
+
+      pay = GNUNET_JSON_PACK (
+        GNUNET_JSON_pack_string ("state",
+                                 "payment"),
+        GNUNET_JSON_pack_string (
+          "taler_pay_uri",
+          csr->details.payment_required.taler_pay_uri),
+        GNUNET_JSON_pack_string ("provider",
+                                 cd->provider_url),
+        GNUNET_JSON_pack_data_auto (
+          "payment_secret",
+          &csr->details.payment_required.payment_secret));
+      GNUNET_assert (0 ==
+                     json_object_set_new (feedback,
+                                          uuid,
+                                          pay));
+    }
+    /* Remember payment secret for later (once application claims it paid) */
+    {
+      json_t *challenge = find_challenge_in_ri (sctx->state,
+                                                &cd->uuid);
+
+      GNUNET_assert (NULL != challenge);
+      GNUNET_assert (0 ==
+                     json_object_set_new (
+                       challenge,
+                       "payment_secret",
+                       GNUNET_JSON_from_data_auto (
+                         &csr->details.payment_required.payment_secret)));
+    }
+    set_state (sctx->state,
+               ANASTASIS_RECOVERY_STATE_CHALLENGE_PAYING);
+    sctx->cb (sctx->cb_cls,
+              TALER_EC_NONE,
+              sctx->state);
+    sctx_free (sctx);
+    return;
+  case ANASTASIS_CHALLENGE_START_STATUS_SERVER_FAILURE:
+    {
+      json_t *err;
+
+      err = GNUNET_JSON_PACK (
+        GNUNET_JSON_pack_string ("state",
+                                 "server-failure"),
+        GNUNET_JSON_pack_uint64 ("http_status",
+                                 csr->http_status),
+        GNUNET_JSON_pack_uint64 ("error_code",
+                                 csr->ec));
+      GNUNET_assert (0 ==
+                     json_object_set_new (feedback,
+                                          uuid,
+                                          err));
+    }
+    set_state (sctx->state,
+               ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
+    sctx->cb (sctx->cb_cls,
+              csr->ec,
+              sctx->state);
+    sctx_free (sctx);
+    return;
+  case ANASTASIS_CHALLENGE_START_STATUS_TRUTH_UNKNOWN:
+    {
+      json_t *err;
+
+      err = GNUNET_JSON_PACK (
+        GNUNET_JSON_pack_string ("state",
+                                 "truth-unknown"),
+        GNUNET_JSON_pack_uint64 ("http_status",
+                                 csr->http_status),
+        GNUNET_JSON_pack_uint64 ("error_code",
+                                 TALER_EC_ANASTASIS_TRUTH_UNKNOWN));
+      GNUNET_assert (0 ==
+                     json_object_set_new (feedback,
+                                          uuid,
+                                          err));
+    }
+    set_state (sctx->state,
+               ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
+    sctx->cb (sctx->cb_cls,
+              TALER_EC_ANASTASIS_TRUTH_UNKNOWN,
+              sctx->state);
+    sctx_free (sctx);
+    return;
+  case ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED:
+    {
+      json_t *reply;
+      json_t *c;
+
+      c = find_challenge_in_cs (sctx->state,
+                                &cd->uuid);
+      if (NULL == c)
+      {
+        GNUNET_break (0);
+        ANASTASIS_redux_fail_ (sctx->cb,
+                               sctx->cb_cls,
+                               TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+                               NULL);
+        sctx_free (sctx);
+        return;
+      }
+      GNUNET_assert (0 ==
+                     json_object_set_new (c,
+                                          "async",
+                                          json_true ()));
+      GNUNET_assert (
+        0 ==
+        json_object_set_new (
+          c,
+          "answer-pin",
+          json_integer (
+            csr->details.bank_transfer_required.answer_code)));
+      reply = GNUNET_JSON_PACK (
+        GNUNET_JSON_pack_string ("state",
+                                 "iban-instructions"),
+        GNUNET_JSON_pack_string (
+          "target_iban",
+          csr->details.bank_transfer_required.target_iban),
+        GNUNET_JSON_pack_string (
+          "target_business_name",
+          csr->details.bank_transfer_required.target_business_name),
+        GNUNET_JSON_pack_string (
+          "wire_transfer_subject",
+          csr->details.bank_transfer_required.wire_transfer_subject),
+        TALER_JSON_pack_amount (
+          "challenge_amount",
+          &csr->details.bank_transfer_required.amount));
+      GNUNET_assert (0 ==
+                     json_object_set_new (feedback,
+                                          uuid,
+                                          reply));
+    }
+    GNUNET_assert (0 ==
+                   json_object_set_new (sctx->state,
+                                        "selected_challenge_uuid",
+                                        GNUNET_JSON_from_data_auto (
+                                          &cd->uuid)));
+    set_state (sctx->state,
+               ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
+    sctx->cb (sctx->cb_cls,
+              TALER_EC_NONE,
+              sctx->state);
+    sctx_free (sctx);
+    return;
+  }
+  GNUNET_break (0);
+  ANASTASIS_redux_fail_ (sctx->cb,
+                         sctx->cb_cls,
+                         TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+                         NULL);
+  sctx_free (sctx);
+}
+
+
+/**
+ * Defines a callback for the response status for a challenge answer
+ * operation.
+ *
+ * @param cls a `struct SelectChallengeContext *`
+ * @param csr response details
+ */
+static void
+answer_feedback_cb (
+  void *cls,
+  const struct ANASTASIS_ChallengeAnswerResponse *csr)
+{
+  struct SelectChallengeContext *sctx = cls;
+  const struct ANASTASIS_ChallengeDetails *cd;
+  char uuid[sizeof (cd->uuid) * 2];
+  char *end;
+  json_t *feedback;
+
+  cd = ANASTASIS_challenge_get_details (csr->challenge);
+  end = GNUNET_STRINGS_data_to_string (&cd->uuid,
+                                       sizeof (cd->uuid),
+                                       uuid,
+                                       sizeof (uuid));
+  GNUNET_assert (NULL != end);
+  *end = '\0';
+  feedback = json_object_get (sctx->state,
+                              "challenge_feedback");
+  if (NULL == feedback)
+  {
+    feedback = json_object ();
+    GNUNET_assert (0 ==
+                   json_object_set_new (sctx->state,
+                                        "challenge_feedback",
+                                        feedback));
+  }
+  switch (csr->cs)
+  {
+  case ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED:
     {
       json_t *rd;
 
@@ -570,77 +809,17 @@ answer_feedback_cb (
     sctx->delayed_report = GNUNET_SCHEDULER_add_now (&report_solved,
                                                      sctx);
     return;
-  case ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS:
+  case ANASTASIS_CHALLENGE_ANSWER_STATUS_INVALID_ANSWER:
     {
       json_t *instructions;
-      const char *mime;
-
-      mime = csr->details.open_challenge.content_type;
-      if (NULL != mime)
-      {
-        if ( (0 == strcasecmp (mime,
-                               "text/plain")) ||
-             (0 == strcasecmp (mime,
-                               "text/utf8")) )
-        {
-          char *s = GNUNET_strndup (csr->details.open_challenge.body,
-                                    csr->details.open_challenge.body_size);
-
-          instructions = GNUNET_JSON_PACK (
-            GNUNET_JSON_pack_string ("state",
-                                     "hint"),
-            GNUNET_JSON_pack_string ("hint",
-                                     s),
-            GNUNET_JSON_pack_uint64 ("http_status",
-                                     (json_int_t) csr->details.open_challenge.
-                                     http_status));
-          GNUNET_free (s);
-        }
-        else if (0 == strcasecmp (mime,
-                                  "application/json"))
-        {
-          json_t *body;
 
-          body = json_loadb (csr->details.open_challenge.body,
-                             csr->details.open_challenge.body_size,
-                             JSON_REJECT_DUPLICATES,
-                             NULL);
-          if (NULL == body)
-          {
-            GNUNET_break_op (0);
-            mime = NULL;
-          }
-          else
-          {
-            instructions = GNUNET_JSON_PACK (
-              GNUNET_JSON_pack_string ("state",
-                                       "details"),
-              GNUNET_JSON_pack_object_steal ("details",
-                                             body),
-              GNUNET_JSON_pack_uint64 ("http_status",
-                                       
csr->details.open_challenge.http_status));
-          }
-        }
-        else
-        {
-          /* unexpected / unsupported mime type */
-          mime = NULL;
-        }
-      }
-      if (NULL == mime)
-      {
-        instructions = GNUNET_JSON_PACK (
-          GNUNET_JSON_pack_string ("state",
-                                   "body"),
-          GNUNET_JSON_pack_data_varsize ("body",
-                                         csr->details.open_challenge.body,
-                                         
csr->details.open_challenge.body_size),
-          GNUNET_JSON_pack_uint64 ("http_status",
-                                   csr->details.open_challenge.http_status),
-          GNUNET_JSON_pack_allow_null (
-            GNUNET_JSON_pack_string ("mime_type",
-                                     mime)));
-      }
+      instructions = GNUNET_JSON_PACK (
+        GNUNET_JSON_pack_string ("status",
+                                 "incorrect-answer"),
+        GNUNET_JSON_pack_uint64 ("error_code",
+                                 csr->ec),
+        GNUNET_JSON_pack_uint64 ("http_status",
+                                 csr->http_status));
       GNUNET_assert (0 ==
                      json_object_set_new (feedback,
                                           uuid,
@@ -653,35 +832,13 @@ answer_feedback_cb (
               sctx->state);
     sctx_free (sctx);
     return;
-  case ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION:
-    {
-      json_t *redir;
-
-      redir = GNUNET_JSON_PACK (
-        GNUNET_JSON_pack_string ("state",
-                                 "redirect"),
-        GNUNET_JSON_pack_string ("redirect_url",
-                                 csr->details.redirect_url));
-      GNUNET_assert (NULL != redir);
-      GNUNET_assert (0 ==
-                     json_object_set_new (feedback,
-                                          uuid,
-                                          redir));
-    }
-    set_state (sctx->state,
-               ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
-    sctx->cb (sctx->cb_cls,
-              TALER_EC_NONE,
-              sctx->state);
-    sctx_free (sctx);
-    return;
-  case ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED:
+  case ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED:
     {
       json_t *pay;
 
       pay = GNUNET_JSON_PACK (
         GNUNET_JSON_pack_string ("state",
-                                 "payment"),
+                                 "payment-required"),
         GNUNET_JSON_pack_string ("taler_pay_uri",
                                  csr->details.payment_required.
                                  taler_pay_uri),
@@ -715,7 +872,7 @@ answer_feedback_cb (
               sctx->state);
     sctx_free (sctx);
     return;
-  case ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE:
+  case ANASTASIS_CHALLENGE_ANSWER_STATUS_SERVER_FAILURE:
     {
       json_t *err;
 
@@ -723,10 +880,9 @@ answer_feedback_cb (
         GNUNET_JSON_pack_string ("state",
                                  "server-failure"),
         GNUNET_JSON_pack_uint64 ("http_status",
-                                 csr->details.server_failure.
-                                 http_status),
+                                 csr->http_status),
         GNUNET_JSON_pack_uint64 ("error_code",
-                                 csr->details.server_failure.ec));
+                                 csr->ec));
       GNUNET_assert (0 ==
                      json_object_set_new (feedback,
                                           uuid,
@@ -735,11 +891,11 @@ answer_feedback_cb (
     set_state (sctx->state,
                ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING);
     sctx->cb (sctx->cb_cls,
-              csr->details.server_failure.ec,
+              csr->ec,
               sctx->state);
     sctx_free (sctx);
     return;
-  case ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN:
+  case ANASTASIS_CHALLENGE_ANSWER_STATUS_TRUTH_UNKNOWN:
     {
       json_t *err;
 
@@ -760,7 +916,7 @@ answer_feedback_cb (
               sctx->state);
     sctx_free (sctx);
     return;
-  case ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED:
+  case ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED:
     {
       json_t *err;
 
@@ -789,7 +945,8 @@ answer_feedback_cb (
               sctx->state);
     sctx_free (sctx);
     return;
-  case ANASTASIS_CHALLENGE_STATUS_AUTH_TIMEOUT:
+  case ANASTASIS_CHALLENGE_ANSWER_STATUS_AUTH_TIMEOUT:
+    // FIXME: check if this status code is even properly generated!
     {
       json_t *err;
 
@@ -811,95 +968,6 @@ answer_feedback_cb (
               sctx->state);
     sctx_free (sctx);
     return;
-
-  case ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS:
-    {
-      const json_t *body = csr->details.external_challenge;
-      const char *method;
-      json_t *details;
-      bool is_async = false;
-      uint64_t code = 0;
-      struct GNUNET_JSON_Specification spec[] = {
-        GNUNET_JSON_spec_string ("method",
-                                 &method),
-        GNUNET_JSON_spec_mark_optional (
-          GNUNET_JSON_spec_bool ("async",
-                                 &is_async)),
-        GNUNET_JSON_spec_mark_optional (
-          GNUNET_JSON_spec_uint64 ("answer_code",
-                                   &code)),
-        GNUNET_JSON_spec_json ("details",
-                               &details),
-        GNUNET_JSON_spec_end ()
-      };
-      json_t *reply;
-
-      if (GNUNET_OK !=
-          GNUNET_JSON_parse (body,
-                             spec,
-                             NULL, NULL))
-      {
-        json_t *err;
-
-        GNUNET_break_op (0);
-        err = GNUNET_JSON_PACK (
-          GNUNET_JSON_pack_string ("state",
-                                   "server-failure"),
-          GNUNET_JSON_pack_uint64 ("error_code",
-                                   TALER_EC_GENERIC_REPLY_MALFORMED));
-        GNUNET_assert (0 ==
-                       json_object_set_new (feedback,
-                                            uuid,
-                                            err));
-        return;
-      }
-      if (is_async)
-      {
-        json_t *c = find_challenge_in_cs (sctx->state,
-                                          &cd->uuid);
-
-        if (NULL == c)
-        {
-          GNUNET_break (0);
-          ANASTASIS_redux_fail_ (sctx->cb,
-                                 sctx->cb_cls,
-                                 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
-                                 NULL);
-          sctx_free (sctx);
-          return;
-        }
-        GNUNET_assert (0 ==
-                       json_object_set_new (c,
-                                            "async",
-                                            json_true ()));
-        GNUNET_assert (0 ==
-                       json_object_set_new (c,
-                                            "answer-pin",
-                                            json_integer (code)));
-      }
-      reply = GNUNET_JSON_PACK (
-        GNUNET_JSON_pack_string ("state",
-                                 "external-instructions"),
-        GNUNET_JSON_pack_string ("method",
-                                 method),
-        GNUNET_JSON_pack_object_incref ("details",
-                                        details));
-      GNUNET_JSON_parse_free (spec);
-      GNUNET_assert (0 ==
-                     json_object_set_new (feedback,
-                                          uuid,
-                                          reply));
-    }
-    json_object_set_new (sctx->state,
-                         "selected_challenge_uuid",
-                         GNUNET_JSON_from_data_auto (&cd->uuid));
-    set_state (sctx->state,
-               ANASTASIS_RECOVERY_STATE_CHALLENGE_SOLVING);
-    sctx->cb (sctx->cb_cls,
-              TALER_EC_NONE,
-              sctx->state);
-    sctx_free (sctx);
-    return;
   }
   GNUNET_break (0);
   ANASTASIS_redux_fail_ (sctx->cb,
@@ -1197,21 +1265,19 @@ solve_challenge_cb (void *cls,
           sctx_free (sctx);
           return;
         }
-        ret = ANASTASIS_challenge_start (ci,
-                                         psp,
-                                         timeout,
-                                         &hashed_answer,
-                                         &answer_feedback_cb,
-                                         sctx);
+        ret = ANASTASIS_challenge_answer3 (ci,
+                                           psp,
+                                           timeout,
+                                           &hashed_answer,
+                                           &answer_feedback_cb,
+                                           sctx);
       }
       else
       {
         /* no answer provided */
         ret = ANASTASIS_challenge_start (ci,
                                          psp,
-                                         timeout,
-                                         NULL,   /* no answer */
-                                         &answer_feedback_cb,
+                                         &start_feedback_cb,
                                          sctx);
       }
     }
@@ -1331,9 +1397,7 @@ pay_challenge_cb (void *cls,
     {
       ret = ANASTASIS_challenge_start (ci,
                                        &sctx->ps,
-                                       sctx->timeout,
-                                       NULL,   /* no answer yet */
-                                       &answer_feedback_cb,
+                                       &start_feedback_cb,
                                        sctx);
     }
     if (GNUNET_OK != ret)
@@ -1780,9 +1844,7 @@ select_challenge_cb (void *cls,
       {
         ret = ANASTASIS_challenge_start (ci,
                                          psp,
-                                         timeout,
-                                         NULL, /* no answer */
-                                         &answer_feedback_cb,
+                                         &start_feedback_cb,
                                          sctx);
       }
     }
diff --git a/src/restclient/Makefile.am b/src/restclient/Makefile.am
index b2e9d0b..9bee2dd 100644
--- a/src/restclient/Makefile.am
+++ b/src/restclient/Makefile.am
@@ -22,7 +22,6 @@ libanastasisrest_la_SOURCES = \
   anastasis_api_truth_challenge.c \
   anastasis_api_truth_solve.c \
   anastasis_api_truth_store.c \
-  anastasis_api_keyshare_lookup.c \
   anastasis_api_curl_defaults.c anastasis_api_curl_defaults.h
 libanastasisrest_la_LIBADD = \
   -lgnunetcurl \
diff --git a/src/restclient/anastasis_api_curl_defaults.c 
b/src/restclient/anastasis_api_curl_defaults.c
index e052517..33665e0 100644
--- a/src/restclient/anastasis_api_curl_defaults.c
+++ b/src/restclient/anastasis_api_curl_defaults.c
@@ -42,5 +42,17 @@ ANASTASIS_curl_easy_get_ (const char *url)
                  curl_easy_setopt (eh,
                                    CURLOPT_TCP_FASTOPEN,
                                    1L));
+  /* limit MAXREDIRS to 5 as a simple security measure against
+      a potential infinite loop caused by a malicious target */
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_MAXREDIRS,
+                                   5L));
+  /* Enable compression (using whatever curl likes), see
+     https://curl.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html  */
+  GNUNET_break (CURLE_OK ==
+                curl_easy_setopt (eh,
+                                  CURLOPT_ACCEPT_ENCODING,
+                                  ""));
   return eh;
 }
diff --git a/src/restclient/anastasis_api_truth_challenge.c 
b/src/restclient/anastasis_api_truth_challenge.c
index 92916d6..911eba6 100644
--- a/src/restclient/anastasis_api_truth_challenge.c
+++ b/src/restclient/anastasis_api_truth_challenge.c
@@ -123,6 +123,7 @@ handle_truth_challenge_finished (void *cls,
     {
       const char *ct;
       const char *tan_hint = NULL;
+      const char *filename = NULL;
       json_t *wire_details = NULL;
       struct GNUNET_JSON_Specification spec[] = {
         GNUNET_JSON_spec_string (
@@ -131,6 +132,9 @@ handle_truth_challenge_finished (void *cls,
         GNUNET_JSON_spec_mark_optional (
           GNUNET_JSON_spec_string ("tan_address_hint",
                                    &tan_hint)),
+        GNUNET_JSON_spec_mark_optional (
+          GNUNET_JSON_spec_string ("filename",
+                                   &filename)),
         GNUNET_JSON_spec_mark_optional (
           GNUNET_JSON_spec_json ("wire_details",
                                  &wire_details)),
@@ -152,19 +156,34 @@ handle_truth_challenge_finished (void *cls,
       {
         tcd.details.success.cs = ANASTASIS_CS_TAN_SENT;
         tcd.details.success.details.tan_address_hint = tan_hint;
+        break;
+      }
+      if ( (0 == strcmp (ct,
+                         "FILE_WRITTEN")) &&
+           (NULL != filename) )
+      {
+        tcd.details.success.cs = ANASTASIS_CS_FILE_WRITTEN;
+        tcd.details.success.details.challenge_filename = filename;
+        break;
       }
-      else if ( (0 == strcmp (ct,
-                              "WIRE_FUNDS")) &&
-                (NULL != wire_details) )
+      if ( (0 == strcmp (ct,
+                         "IBAN_WIRE")) &&
+           (NULL != wire_details) )
       {
         struct GNUNET_JSON_Specification ispec[] = {
           GNUNET_JSON_spec_string (
-            "target_account",
-            &tcd.details.success.details.wire_funds.target_payto),
+            "credit_iban",
+            &tcd.details.success.details.wire_funds.target_iban),
+          GNUNET_JSON_spec_uint64 (
+            "answer_code",
+            &tcd.details.success.details.wire_funds.answer_code),
+          GNUNET_JSON_spec_string (
+            "business_name",
+            &tcd.details.success.details.wire_funds.target_business_name),
           GNUNET_JSON_spec_string (
-            "sender_hint",
-            &tcd.details.success.details.wire_funds.sender_hint),
-          TALER_JSON_spec_amount_any ("amount",
+            "wire_transfer_subject",
+            &tcd.details.success.details.wire_funds.wire_transfer_subject),
+          TALER_JSON_spec_amount_any ("challenge_amount",
                                       &tcd.details.success.details.wire_funds.
                                       amount),
           GNUNET_JSON_spec_end ()
@@ -188,6 +207,12 @@ handle_truth_challenge_finished (void *cls,
         ANASTASIS_truth_challenge_cancel (tco);
         return;
       }
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Unexpected challenge type `%s'\n",
+                  ct);
+      tcd.http_status = 0;
+      tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+      break;
     }
   case MHD_HTTP_BAD_REQUEST:
     /* This should never happen, either us or the anastasis server is buggy
@@ -409,10 +434,11 @@ ANASTASIS_truth_challenge (
                  curl_easy_setopt (eh,
                                    CURLOPT_HEADERDATA,
                                    tco));
-  tco->job = GNUNET_CURL_job_add (ctx,
-                                  eh,
-                                  &handle_truth_challenge_finished,
-                                  tco);
+  tco->job = GNUNET_CURL_job_add2 (ctx,
+                                   eh,
+                                   tco->ctx.headers,
+                                   &handle_truth_challenge_finished,
+                                   tco);
   return tco;
 }
 
diff --git a/src/restclient/anastasis_api_truth_solve.c 
b/src/restclient/anastasis_api_truth_solve.c
index 376fc74..971e917 100644
--- a/src/restclient/anastasis_api_truth_solve.c
+++ b/src/restclient/anastasis_api_truth_solve.c
@@ -427,7 +427,7 @@ ANASTASIS_truth_solve (
                                    tso));
   tso->job = GNUNET_CURL_job_add_raw (ctx,
                                       eh,
-                                      NULL,
+                                      tso->ctx.headers,
                                       &handle_truth_solve_finished,
                                       tso);
   return tso;
diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am
index 9a98530..fec971a 100644
--- a/src/testing/Makefile.am
+++ b/src/testing/Makefile.am
@@ -14,9 +14,10 @@ libanastasistesting_la_LDFLAGS = \
   -no-undefined
 libanastasistesting_la_SOURCES = \
   testing_api_cmd_policy_store.c \
+  testing_api_cmd_truth_challenge.c \
+  testing_api_cmd_truth_solve.c \
   testing_api_cmd_truth_store.c \
   testing_api_cmd_policy_lookup.c \
-  testing_api_cmd_keyshare_lookup.c \
   testing_api_cmd_config.c \
   testing_api_helpers.c \
   testing_api_traits.c \
diff --git a/src/testing/test_anastasis.c b/src/testing/test_anastasis.c
index 6ce6771..7b127ac 100644
--- a/src/testing/test_anastasis.c
+++ b/src/testing/test_anastasis.c
@@ -280,7 +280,7 @@ run (void *cls,
                                             0, /* challenge index */
                                             "SomeTruth1",
                                             0,  /* mode */
-                                            ANASTASIS_CHALLENGE_STATUS_SOLVED),
+                                            
ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED),
 #if 0
     ANASTASIS_TESTING_cmd_challenge_answer ("challenge-answer-2",
                                             NULL, /* payment ref */
@@ -288,13 +288,13 @@ run (void *cls,
                                             1, /* challenge index */
                                             "SomeTruth2",
                                             0, /* mode */
-                                            ANASTASIS_CHALLENGE_STATUS_SOLVED),
+                                            
ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED),
 #endif
     ANASTASIS_TESTING_cmd_challenge_start ("challenge-start-3-pay",
                                            NULL,  /* payment ref */
                                            "recover-secret-1",
                                            2,  /* challenge index */
-                                           
ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED),
+                                           
ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED),
     TALER_TESTING_cmd_merchant_claim_order ("fetch-challenge-pay-proposal",
                                             merchant_url,
                                             MHD_HTTP_OK,
@@ -312,14 +312,14 @@ run (void *cls,
                                            "challenge-start-3-pay",  /* 
payment ref */
                                            "recover-secret-1",
                                            2,  /* challenge index */
-                                           
ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS),
+                                           
ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED),
     ANASTASIS_TESTING_cmd_challenge_answer ("challenge-answer-3",
                                             "challenge-start-3-pay", /* 
payment ref */
                                             "recover-secret-1",
                                             2, /* challenge index */
                                             "challenge-start-3-paid", /* 
answer */
                                             1, /* mode */
-                                            ANASTASIS_CHALLENGE_STATUS_SOLVED),
+                                            
ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED),
     ANASTASIS_TESTING_cmd_recover_secret_finish ("recover-finish-1",
                                                  "recover-secret-1",
                                                  GNUNET_TIME_UNIT_SECONDS),
diff --git a/src/testing/test_anastasis_api.c b/src/testing/test_anastasis_api.c
index b8d9608..caa59c8 100644
--- a/src/testing/test_anastasis_api.c
+++ b/src/testing/test_anastasis_api.c
@@ -209,14 +209,14 @@ run (void *cls,
       "The-Answer",
       ANASTASIS_TESTING_TSO_NONE,
       MHD_HTTP_NO_CONTENT),
-    ANASTASIS_TESTING_cmd_keyshare_lookup (
+    ANASTASIS_TESTING_cmd_truth_solve (
       "keyshare-lookup-1",
       anastasis_url,
       "The-Answer",
       NULL, /* payment ref */
       "truth-store-1",
       0,
-      ANASTASIS_KSD_SUCCESS),
+      MHD_HTTP_OK),
     ANASTASIS_TESTING_cmd_truth_store (
       "truth-store-2",
       anastasis_url,
@@ -227,22 +227,20 @@ run (void *cls,
       file_secret,
       ANASTASIS_TESTING_TSO_NONE,
       MHD_HTTP_NO_CONTENT),
-    ANASTASIS_TESTING_cmd_keyshare_lookup (
+    ANASTASIS_TESTING_cmd_truth_solve (
       "challenge-fail-1",
       anastasis_url,
       "Wrong-Answer",
-      NULL,
-      "truth-store-1",
-      0,
-      ANASTASIS_KSD_INVALID_ANSWER),
-    ANASTASIS_TESTING_cmd_keyshare_lookup (
+      NULL, /* payment ref */
+      "truth-store-1", /* upload ref */
+      0, /* security question mode */
+      MHD_HTTP_FORBIDDEN),
+    ANASTASIS_TESTING_cmd_truth_challenge (
       "file-challenge-run-1",
       anastasis_url,
-      NULL, /* no answer */
       NULL, /* payment ref */
       "truth-store-2", /* upload ref */
-      0,
-      ANASTASIS_KSD_PAYMENT_REQUIRED),
+      MHD_HTTP_PAYMENT_REQUIRED),
     /* what would we have to pay? */
     TALER_TESTING_cmd_merchant_claim_order ("fetch-proposal-2",
                                             merchant_url,
@@ -259,22 +257,20 @@ run (void *cls,
                                           "EUR:1",
                                           NULL),
 
-    ANASTASIS_TESTING_cmd_keyshare_lookup (
+    ANASTASIS_TESTING_cmd_truth_challenge (
       "file-challenge-run-2",
       anastasis_url,
-      NULL, /* no answer */
       "file-challenge-run-1", /* payment ref */
       "truth-store-2",
-      0,
-      ANASTASIS_KSD_INVALID_ANSWER),
-    ANASTASIS_TESTING_cmd_keyshare_lookup (
+      MHD_HTTP_OK),
+    ANASTASIS_TESTING_cmd_truth_solve (
       "file-challenge-run-3",
       anastasis_url,
       "file-challenge-run-2", /* answer */
       "file-challenge-run-1", /* payment ref */
       "truth-store-2",
       1,
-      ANASTASIS_KSD_SUCCESS),
+      MHD_HTTP_OK),
     TALER_TESTING_cmd_end ()
   };
 
diff --git a/src/testing/testing_api_cmd_keyshare_lookup.c 
b/src/testing/testing_api_cmd_truth_challenge.c
similarity index 54%
copy from src/testing/testing_api_cmd_keyshare_lookup.c
copy to src/testing/testing_api_cmd_truth_challenge.c
index 8b06a67..c584d5f 100644
--- a/src/testing/testing_api_cmd_keyshare_lookup.c
+++ b/src/testing/testing_api_cmd_truth_challenge.c
@@ -1,6 +1,6 @@
 /*
   This file is part of Anastasis
-  Copyright (C) 2020 Anastasis SARL
+  Copyright (C) 2020, 2022 Anastasis SARL
 
   Anastasis is free software; you can redistribute it and/or modify it under 
the
   terms of the GNU General Public License as published by the Free Software
@@ -14,7 +14,7 @@
   Anastasis; see the file COPYING.GPL.  If not, see 
<http://www.gnu.org/licenses/>
 */
 /**
- * @file testing/testing_api_cmd_keyshare_lookup.c
+ * @file testing/testing_api_cmd_truth_challenge.c
  * @brief Testing of Implementation of the /truth GET
  * @author Christian Grothoff
  * @author Dennis Neufeld
@@ -31,7 +31,7 @@
 /**
  * State for a "keyshare lookup" CMD.
  */
-struct KeyShareLookupState
+struct TruthChallengeState
 {
   /**
    * The interpreter state.
@@ -44,19 +44,14 @@ struct KeyShareLookupState
   const char *anastasis_url;
 
   /**
-   * Expected status code.
+   * Expected HTTP status code.
    */
-  enum ANASTASIS_KeyShareDownloadStatus expected_ksdd;
+  unsigned int expected_http_status;
 
   /**
    * The /truth GET operation handle.
    */
-  struct ANASTASIS_KeyShareLookupOperation *kslo;
-
-  /**
-   * answer to a challenge
-   */
-  const char *answer;
+  struct ANASTASIS_TruthChallengeOperation *tco;
 
   /**
    * Reference to upload command we expect to lookup.
@@ -83,11 +78,6 @@ struct KeyShareLookupState
    */
   char *order_id;
 
-  /**
-   * Redirect-URI for challenge, if any.
-   */
-  char *redirect_uri;
-
   /**
    * "code" returned by service, if any.
    */
@@ -98,45 +88,81 @@ struct KeyShareLookupState
    */
   char *instructions;
 
-  /**
-   * Name of the file where the service will write the challenge, if method is 
"file".
-   * Otherwise NULL.
-   */
-  char *filename;
-
-  /**
-   * Mode for the lookup(0 = question, 1 = code based)
-   */
-  int lookup_mode;
-
 };
 
 
 static void
-keyshare_lookup_cb (void *cls,
-                    const struct ANASTASIS_KeyShareDownloadDetails *dd)
+truth_challenge_cb (void *cls,
+                    const struct ANASTASIS_TruthChallengeDetails *tcd)
 {
-  struct KeyShareLookupState *ksls = cls;
+  struct TruthChallengeState *ksls = cls;
 
-  ksls->kslo = NULL;
-  if (dd->status != ksls->expected_ksdd)
+  ksls->tco = NULL;
+  if (tcd->http_status != ksls->expected_http_status)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                 "Unexpected response code %u to command %s in %s:%u\n",
-                dd->status,
+                tcd->http_status,
                 ksls->is->commands[ksls->is->ip].label,
                 __FILE__,
                 __LINE__);
     TALER_TESTING_interpreter_fail (ksls->is);
     return;
   }
-  switch (dd->status)
+  switch (tcd->http_status)
   {
-  case ANASTASIS_KSD_SUCCESS:
+  case MHD_HTTP_OK:
+    switch (tcd->details.success.cs)
+    {
+    case ANASTASIS_CS_FILE_WRITTEN:
+      {
+        FILE *file;
+        char code[22];
+
+        file = fopen (tcd->details.success.details.challenge_filename,
+                      "r");
+        if (NULL == file)
+        {
+          GNUNET_log_strerror_file (
+            GNUNET_ERROR_TYPE_ERROR,
+            "open",
+            tcd->details.success.details.challenge_filename);
+          TALER_TESTING_interpreter_fail (ksls->is);
+          return;
+        }
+        if (0 == fscanf (file,
+                         "%21s",
+                         code))
+        {
+          GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+                                    "fscanf",
+                                    tcd->details.success.details.
+                                    challenge_filename);
+          GNUNET_break (0 == fclose (file));
+          TALER_TESTING_interpreter_fail (ksls->is);
+          return;
+        }
+        GNUNET_break (0 == fclose (file));
+        ksls->code = GNUNET_strdup (code);
+        GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                    "Read code `%s'\n",
+                    code);
+      }
+      break;
+    case ANASTASIS_CS_TAN_SENT:
+      ksls->instructions = GNUNET_strdup (
+        tcd->details.success.details.tan_address_hint);
+      break;
+    case ANASTASIS_CS_WIRE_FUNDS:
+      /* FIXME: not implemented */
+      GNUNET_break (0);
+      return;
+    }
     break;
-  case ANASTASIS_KSD_PAYMENT_REQUIRED:
-    ksls->pay_uri = GNUNET_strdup (dd->details.payment_required.taler_pay_uri);
-    ksls->payment_secret_response = 
dd->details.payment_required.payment_secret;
+  case MHD_HTTP_PAYMENT_REQUIRED:
+    ksls->pay_uri = GNUNET_strdup (
+      tcd->details.payment_required.payment_request);
+    ksls->payment_secret_response = tcd->details.payment_required.ps;
     {
       struct TALER_MERCHANT_PayUriData pd;
 
@@ -153,60 +179,7 @@ keyshare_lookup_cb (void *cls,
     }
 
     break;
-  case ANASTASIS_KSD_INVALID_ANSWER:
-    if (ksls->filename)
-    {
-      FILE *file;
-      char code[22];
-
-      file = fopen (ksls->filename,
-                    "r");
-      if (NULL == file)
-      {
-        GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
-                                  "open",
-                                  ksls->filename);
-        TALER_TESTING_interpreter_fail (ksls->is);
-        return;
-      }
-      if (0 == fscanf (file,
-                       "%21s",
-                       code))
-      {
-        GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
-                                  "fscanf",
-                                  ksls->filename);
-        GNUNET_break (0 == fclose (file));
-        TALER_TESTING_interpreter_fail (ksls->is);
-        return;
-      }
-      GNUNET_break (0 == fclose (file));
-      ksls->code = GNUNET_strdup (code);
-      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                  "Read code `%s'\n",
-                  code);
-    }
-    else
-    {
-      ksls->instructions = GNUNET_strndup (
-        dd->details.open_challenge.body,
-        dd->details.open_challenge.body_size);
-    }
-    break;
-  case ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION:
-    ksls->redirect_uri = GNUNET_strdup (dd->details.redirect_url);
-    break;
-  case ANASTASIS_KSD_SERVER_ERROR:
-    break;
-  case ANASTASIS_KSD_CLIENT_FAILURE:
-    break;
-  case ANASTASIS_KSD_TRUTH_UNKNOWN:
-    break;
-  case ANASTASIS_KSD_RATE_LIMIT_EXCEEDED:
-    break;
-  case ANASTASIS_KSD_AUTHENTICATION_TIMEOUT:
-    break;
-  case ANASTASIS_KSD_EXTERNAL_CHALLENGE_INSTRUCTIONS:
+  default:
     break;
   }
   TALER_TESTING_interpreter_next (ksls->is);
@@ -214,15 +187,14 @@ keyshare_lookup_cb (void *cls,
 
 
 static void
-keyshare_lookup_run (void *cls,
+truth_challenge_run (void *cls,
                      const struct TALER_TESTING_Command *cmd,
                      struct TALER_TESTING_Interpreter *is)
 {
-  struct KeyShareLookupState *ksls = cls;
+  struct TruthChallengeState *ksls = cls;
   const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key;
   const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid;
   const struct ANASTASIS_PaymentSecretP *payment_secret;
-  const char **answerp;
 
   ksls->is = is;
   if (NULL == ksls->upload_reference)
@@ -243,20 +215,6 @@ keyshare_lookup_run (void *cls,
       TALER_TESTING_interpreter_fail (ksls->is);
       return;
     }
-    {
-      const char **fn;
-
-      if (GNUNET_OK !=
-          ANASTASIS_TESTING_get_trait_filename (upload_cmd,
-                                                &fn))
-      {
-        GNUNET_break (0);
-        TALER_TESTING_interpreter_fail (ksls->is);
-        return;
-      }
-      if (NULL != *fn)
-        ksls->filename = GNUNET_strdup (*fn);
-    }
     if (GNUNET_OK !=
         ANASTASIS_TESTING_get_trait_truth_uuid (upload_cmd,
                                                 &truth_uuid))
@@ -287,39 +245,6 @@ keyshare_lookup_run (void *cls,
     }
   }
 
-  if (ksls->lookup_mode == 1)
-  {
-    const struct TALER_TESTING_Command *download_cmd;
-
-    download_cmd = TALER_TESTING_interpreter_lookup_command (is,
-                                                             ksls->answer);
-    if (NULL == download_cmd)
-    {
-      GNUNET_break (0);
-      TALER_TESTING_interpreter_fail (ksls->is);
-      return;
-    }
-    if (GNUNET_OK !=
-        ANASTASIS_TESTING_get_trait_code (download_cmd,
-                                          &answerp))
-    {
-      GNUNET_break (0);
-      TALER_TESTING_interpreter_fail (ksls->is);
-      return;
-    }
-    if (NULL == *answerp)
-    {
-      GNUNET_break (0);
-      TALER_TESTING_interpreter_fail (ksls->is);
-      return;
-    }
-  }
-  else
-  {
-    /* answer is the answer */
-    answerp = &ksls->answer;
-  }
-
   if (NULL != ksls->payment_reference)
   {
     const struct TALER_TESTING_Command *payment_cmd;
@@ -341,26 +266,14 @@ keyshare_lookup_run (void *cls,
     payment_secret = NULL;
   }
 
-  {
-    struct GNUNET_HashCode h_answer;
-
-    if (NULL != *answerp)
-      GNUNET_CRYPTO_hash (*answerp,
-                          strlen (*answerp),
-                          &h_answer);
-    ksls->kslo = ANASTASIS_keyshare_lookup (is->ctx,
-                                            ksls->anastasis_url,
-                                            truth_uuid,
-                                            truth_key,
-                                            payment_secret,
-                                            GNUNET_TIME_UNIT_ZERO,
-                                            (NULL != *answerp)
-                                            ? &h_answer
-                                            : NULL,
-                                            &keyshare_lookup_cb,
-                                            ksls);
-  }
-  if (NULL == ksls->kslo)
+  ksls->tco = ANASTASIS_truth_challenge (is->ctx,
+                                         ksls->anastasis_url,
+                                         truth_uuid,
+                                         truth_key,
+                                         payment_secret,
+                                         &truth_challenge_cb,
+                                         ksls);
+  if (NULL == ksls->tco)
   {
     GNUNET_break (0);
     TALER_TESTING_interpreter_fail (ksls->is);
@@ -370,25 +283,23 @@ keyshare_lookup_run (void *cls,
 
 
 static void
-keyshare_lookup_cleanup (void *cls,
+truth_challenge_cleanup (void *cls,
                          const struct TALER_TESTING_Command *cmd)
 {
-  struct KeyShareLookupState *ksls = cls;
+  struct TruthChallengeState *ksls = cls;
 
-  if (NULL != ksls->kslo)
+  if (NULL != ksls->tco)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                 "Command '%s' did not complete (keyshare lookup)\n",
                 cmd->label);
-    ANASTASIS_keyshare_lookup_cancel (ksls->kslo);
-    ksls->kslo = NULL;
+    ANASTASIS_truth_challenge_cancel (ksls->tco);
+    ksls->tco = NULL;
   }
   GNUNET_free (ksls->pay_uri);
   GNUNET_free (ksls->order_id);
   GNUNET_free (ksls->code);
-  GNUNET_free (ksls->filename);
   GNUNET_free (ksls->instructions);
-  GNUNET_free (ksls->redirect_uri);
   GNUNET_free (ksls);
 }
 
@@ -403,12 +314,12 @@ keyshare_lookup_cleanup (void *cls,
  * @return #GNUNET_OK on success
  */
 static enum GNUNET_GenericReturnValue
-keyshare_lookup_traits (void *cls,
+truth_challenge_traits (void *cls,
                         const void **ret,
                         const char *trait,
                         unsigned int index)
 {
-  struct KeyShareLookupState *ksls = cls;
+  struct TruthChallengeState *ksls = cls;
   struct TALER_TESTING_Trait traits[] = {
     ANASTASIS_TESTING_make_trait_payment_secret (
       &ksls->payment_secret_response),
@@ -429,32 +340,28 @@ keyshare_lookup_traits (void *cls,
 
 
 struct TALER_TESTING_Command
-ANASTASIS_TESTING_cmd_keyshare_lookup (
+ANASTASIS_TESTING_cmd_truth_challenge (
   const char *label,
   const char *anastasis_url,
-  const char *answer,
   const char *payment_ref,
   const char *upload_ref,
-  int lookup_mode,
-  enum ANASTASIS_KeyShareDownloadStatus ksdd)
+  unsigned int http_status)
 {
-  struct KeyShareLookupState *ksls;
+  struct TruthChallengeState *ksls;
 
   GNUNET_assert (NULL != upload_ref);
-  ksls = GNUNET_new (struct KeyShareLookupState);
-  ksls->expected_ksdd = ksdd;
+  ksls = GNUNET_new (struct TruthChallengeState);
+  ksls->expected_http_status = http_status;
   ksls->anastasis_url = anastasis_url;
   ksls->upload_reference = upload_ref;
   ksls->payment_reference = payment_ref;
-  ksls->answer = answer;
-  ksls->lookup_mode = lookup_mode;
   {
     struct TALER_TESTING_Command cmd = {
       .cls = ksls,
       .label = label,
-      .run = &keyshare_lookup_run,
-      .cleanup = &keyshare_lookup_cleanup,
-      .traits = &keyshare_lookup_traits
+      .run = &truth_challenge_run,
+      .cleanup = &truth_challenge_cleanup,
+      .traits = &truth_challenge_traits
     };
 
     return cmd;
@@ -462,4 +369,4 @@ ANASTASIS_TESTING_cmd_keyshare_lookup (
 }
 
 
-/* end of testing_api_cmd_keyshare_lookup.c */
+/* end of testing_api_cmd_truth_challenge.c */
diff --git a/src/testing/testing_api_cmd_keyshare_lookup.c 
b/src/testing/testing_api_cmd_truth_solve.c
similarity index 62%
rename from src/testing/testing_api_cmd_keyshare_lookup.c
rename to src/testing/testing_api_cmd_truth_solve.c
index 8b06a67..5c12b3f 100644
--- a/src/testing/testing_api_cmd_keyshare_lookup.c
+++ b/src/testing/testing_api_cmd_truth_solve.c
@@ -1,6 +1,6 @@
 /*
   This file is part of Anastasis
-  Copyright (C) 2020 Anastasis SARL
+  Copyright (C) 2020, 2022 Anastasis SARL
 
   Anastasis is free software; you can redistribute it and/or modify it under 
the
   terms of the GNU General Public License as published by the Free Software
@@ -14,7 +14,7 @@
   Anastasis; see the file COPYING.GPL.  If not, see 
<http://www.gnu.org/licenses/>
 */
 /**
- * @file testing/testing_api_cmd_keyshare_lookup.c
+ * @file testing/testing_api_cmd_truth_solve.c
  * @brief Testing of Implementation of the /truth GET
  * @author Christian Grothoff
  * @author Dennis Neufeld
@@ -31,7 +31,7 @@
 /**
  * State for a "keyshare lookup" CMD.
  */
-struct KeyShareLookupState
+struct TruthSolveState
 {
   /**
    * The interpreter state.
@@ -46,12 +46,18 @@ struct KeyShareLookupState
   /**
    * Expected status code.
    */
-  enum ANASTASIS_KeyShareDownloadStatus expected_ksdd;
+  unsigned int expected_http_status;
+
+  /**
+   * Resulting encrypted key share.
+   * Note: currently not used.
+   */
+  struct ANASTASIS_CRYPTO_EncryptedKeyShareP eks;
 
   /**
    * The /truth GET operation handle.
    */
-  struct ANASTASIS_KeyShareLookupOperation *kslo;
+  struct ANASTASIS_TruthSolveOperation *tso;
 
   /**
    * answer to a challenge
@@ -113,100 +119,35 @@ struct KeyShareLookupState
 
 
 static void
-keyshare_lookup_cb (void *cls,
-                    const struct ANASTASIS_KeyShareDownloadDetails *dd)
+truth_solve_cb (void *cls,
+                const struct ANASTASIS_TruthSolveReply *tsr)
 {
-  struct KeyShareLookupState *ksls = cls;
+  struct TruthSolveState *ksls = cls;
 
-  ksls->kslo = NULL;
-  if (dd->status != ksls->expected_ksdd)
+  ksls->tso = NULL;
+  if (tsr->http_status != ksls->expected_http_status)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                 "Unexpected response code %u to command %s in %s:%u\n",
-                dd->status,
+                tsr->http_status,
                 ksls->is->commands[ksls->is->ip].label,
                 __FILE__,
                 __LINE__);
     TALER_TESTING_interpreter_fail (ksls->is);
     return;
   }
-  switch (dd->status)
+  switch (tsr->http_status)
   {
-  case ANASTASIS_KSD_SUCCESS:
-    break;
-  case ANASTASIS_KSD_PAYMENT_REQUIRED:
-    ksls->pay_uri = GNUNET_strdup (dd->details.payment_required.taler_pay_uri);
-    ksls->payment_secret_response = 
dd->details.payment_required.payment_secret;
-    {
-      struct TALER_MERCHANT_PayUriData pd;
-
-      if (GNUNET_OK !=
-          TALER_MERCHANT_parse_pay_uri (ksls->pay_uri,
-                                        &pd))
-      {
-        GNUNET_break (0);
-        TALER_TESTING_interpreter_fail (ksls->is);
-        return;
-      }
-      ksls->order_id = GNUNET_strdup (pd.order_id);
-      TALER_MERCHANT_parse_pay_uri_free (&pd);
-    }
-
-    break;
-  case ANASTASIS_KSD_INVALID_ANSWER:
-    if (ksls->filename)
-    {
-      FILE *file;
-      char code[22];
-
-      file = fopen (ksls->filename,
-                    "r");
-      if (NULL == file)
-      {
-        GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
-                                  "open",
-                                  ksls->filename);
-        TALER_TESTING_interpreter_fail (ksls->is);
-        return;
-      }
-      if (0 == fscanf (file,
-                       "%21s",
-                       code))
-      {
-        GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
-                                  "fscanf",
-                                  ksls->filename);
-        GNUNET_break (0 == fclose (file));
-        TALER_TESTING_interpreter_fail (ksls->is);
-        return;
-      }
-      GNUNET_break (0 == fclose (file));
-      ksls->code = GNUNET_strdup (code);
-      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                  "Read code `%s'\n",
-                  code);
-    }
-    else
-    {
-      ksls->instructions = GNUNET_strndup (
-        dd->details.open_challenge.body,
-        dd->details.open_challenge.body_size);
-    }
-    break;
-  case ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION:
-    ksls->redirect_uri = GNUNET_strdup (dd->details.redirect_url);
+  case MHD_HTTP_OK:
+    ksls->eks = tsr->details.success.eks;
     break;
-  case ANASTASIS_KSD_SERVER_ERROR:
+  case MHD_HTTP_PAYMENT_REQUIRED:
+    ksls->pay_uri = GNUNET_strdup (
+      tsr->details.payment_required.payment_request);
+    ksls->payment_secret_response = tsr->details.payment_required.ps;
+    ksls->order_id = GNUNET_strdup 
(tsr->details.payment_required.pd->order_id);
     break;
-  case ANASTASIS_KSD_CLIENT_FAILURE:
-    break;
-  case ANASTASIS_KSD_TRUTH_UNKNOWN:
-    break;
-  case ANASTASIS_KSD_RATE_LIMIT_EXCEEDED:
-    break;
-  case ANASTASIS_KSD_AUTHENTICATION_TIMEOUT:
-    break;
-  case ANASTASIS_KSD_EXTERNAL_CHALLENGE_INSTRUCTIONS:
+  default:
     break;
   }
   TALER_TESTING_interpreter_next (ksls->is);
@@ -214,11 +155,11 @@ keyshare_lookup_cb (void *cls,
 
 
 static void
-keyshare_lookup_run (void *cls,
-                     const struct TALER_TESTING_Command *cmd,
-                     struct TALER_TESTING_Interpreter *is)
+truth_solve_run (void *cls,
+                 const struct TALER_TESTING_Command *cmd,
+                 struct TALER_TESTING_Interpreter *is)
 {
-  struct KeyShareLookupState *ksls = cls;
+  struct TruthSolveState *ksls = cls;
   const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key;
   const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid;
   const struct ANASTASIS_PaymentSecretP *payment_secret;
@@ -319,6 +260,12 @@ keyshare_lookup_run (void *cls,
     /* answer is the answer */
     answerp = &ksls->answer;
   }
+  if (NULL == answerp)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (ksls->is);
+    return;
+  }
 
   if (NULL != ksls->payment_reference)
   {
@@ -344,23 +291,20 @@ keyshare_lookup_run (void *cls,
   {
     struct GNUNET_HashCode h_answer;
 
-    if (NULL != *answerp)
-      GNUNET_CRYPTO_hash (*answerp,
-                          strlen (*answerp),
-                          &h_answer);
-    ksls->kslo = ANASTASIS_keyshare_lookup (is->ctx,
-                                            ksls->anastasis_url,
-                                            truth_uuid,
-                                            truth_key,
-                                            payment_secret,
-                                            GNUNET_TIME_UNIT_ZERO,
-                                            (NULL != *answerp)
-                                            ? &h_answer
-                                            : NULL,
-                                            &keyshare_lookup_cb,
-                                            ksls);
+    GNUNET_CRYPTO_hash (*answerp,
+                        strlen (*answerp),
+                        &h_answer);
+    ksls->tso = ANASTASIS_truth_solve (is->ctx,
+                                       ksls->anastasis_url,
+                                       truth_uuid,
+                                       truth_key,
+                                       payment_secret,
+                                       GNUNET_TIME_UNIT_ZERO,
+                                       &h_answer,
+                                       &truth_solve_cb,
+                                       ksls);
   }
-  if (NULL == ksls->kslo)
+  if (NULL == ksls->tso)
   {
     GNUNET_break (0);
     TALER_TESTING_interpreter_fail (ksls->is);
@@ -370,18 +314,18 @@ keyshare_lookup_run (void *cls,
 
 
 static void
-keyshare_lookup_cleanup (void *cls,
-                         const struct TALER_TESTING_Command *cmd)
+truth_solve_cleanup (void *cls,
+                     const struct TALER_TESTING_Command *cmd)
 {
-  struct KeyShareLookupState *ksls = cls;
+  struct TruthSolveState *ksls = cls;
 
-  if (NULL != ksls->kslo)
+  if (NULL != ksls->tso)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                 "Command '%s' did not complete (keyshare lookup)\n",
                 cmd->label);
-    ANASTASIS_keyshare_lookup_cancel (ksls->kslo);
-    ksls->kslo = NULL;
+    ANASTASIS_truth_solve_cancel (ksls->tso);
+    ksls->tso = NULL;
   }
   GNUNET_free (ksls->pay_uri);
   GNUNET_free (ksls->order_id);
@@ -403,12 +347,12 @@ keyshare_lookup_cleanup (void *cls,
  * @return #GNUNET_OK on success
  */
 static enum GNUNET_GenericReturnValue
-keyshare_lookup_traits (void *cls,
-                        const void **ret,
-                        const char *trait,
-                        unsigned int index)
+truth_solve_traits (void *cls,
+                    const void **ret,
+                    const char *trait,
+                    unsigned int index)
 {
-  struct KeyShareLookupState *ksls = cls;
+  struct TruthSolveState *ksls = cls;
   struct TALER_TESTING_Trait traits[] = {
     ANASTASIS_TESTING_make_trait_payment_secret (
       &ksls->payment_secret_response),
@@ -429,20 +373,20 @@ keyshare_lookup_traits (void *cls,
 
 
 struct TALER_TESTING_Command
-ANASTASIS_TESTING_cmd_keyshare_lookup (
+ANASTASIS_TESTING_cmd_truth_solve (
   const char *label,
   const char *anastasis_url,
   const char *answer,
   const char *payment_ref,
   const char *upload_ref,
   int lookup_mode,
-  enum ANASTASIS_KeyShareDownloadStatus ksdd)
+  unsigned int http_status)
 {
-  struct KeyShareLookupState *ksls;
+  struct TruthSolveState *ksls;
 
   GNUNET_assert (NULL != upload_ref);
-  ksls = GNUNET_new (struct KeyShareLookupState);
-  ksls->expected_ksdd = ksdd;
+  ksls = GNUNET_new (struct TruthSolveState);
+  ksls->expected_http_status = http_status;
   ksls->anastasis_url = anastasis_url;
   ksls->upload_reference = upload_ref;
   ksls->payment_reference = payment_ref;
@@ -452,9 +396,9 @@ ANASTASIS_TESTING_cmd_keyshare_lookup (
     struct TALER_TESTING_Command cmd = {
       .cls = ksls,
       .label = label,
-      .run = &keyshare_lookup_run,
-      .cleanup = &keyshare_lookup_cleanup,
-      .traits = &keyshare_lookup_traits
+      .run = &truth_solve_run,
+      .cleanup = &truth_solve_cleanup,
+      .traits = &truth_solve_traits
     };
 
     return cmd;
@@ -462,4 +406,4 @@ ANASTASIS_TESTING_cmd_keyshare_lookup (
 }
 
 
-/* end of testing_api_cmd_keyshare_lookup.c */
+/* end of testing_api_cmd_truth_solve.c */
diff --git a/src/testing/testing_cmd_challenge_answer.c 
b/src/testing/testing_cmd_challenge_answer.c
index 78f7404..88c4c2f 100644
--- a/src/testing/testing_cmd_challenge_answer.c
+++ b/src/testing/testing_cmd_challenge_answer.c
@@ -1,6 +1,6 @@
 /*
   This file is part of Anastasis
-  Copyright (C) 2020, 2021 Anastasis SARL
+  Copyright (C) 2020, 2021, 2022 Anastasis SARL
 
   Anastasis is free software; you can redistribute it and/or modify it under 
the
   terms of the GNU General Public License as published by the Free Software
@@ -20,7 +20,6 @@
  * @author Dennis Neufeld
  * @author Dominik Meister
  */
-
 #include "platform.h"
 #include "anastasis_testing_lib.h"
 #include <taler/taler_util.h>
@@ -28,6 +27,8 @@
 #include <taler/taler_merchant_service.h>
 
 
+// FIXME: break up into two files, one for start, one for answer!
+
 /**
  * State for a "challenge answer" CMD.
  */
@@ -74,9 +75,14 @@ struct ChallengeState
   struct ANASTASIS_PaymentSecretP payment_order_req;
 
   /**
-   * Expected status code.
+   * Expected answer status code.
+   */
+  enum ANASTASIS_ChallengeAnswerStatus expected_acs;
+
+  /**
+   * Expected start status code.
    */
-  enum ANASTASIS_ChallengeStatus expected_cs;
+  enum ANASTASIS_ChallengeStartStatus expected_scs;
 
   /**
    * Index of the challenge we are solving
@@ -98,97 +104,28 @@ struct ChallengeState
 
 static void
 challenge_answer_cb (void *af_cls,
-                     const struct ANASTASIS_ChallengeStartResponse *csr)
+                     const struct ANASTASIS_ChallengeAnswerResponse *csr)
 {
   struct ChallengeState *cs = af_cls;
 
   cs->c = NULL;
-  if (csr->cs != cs->expected_cs)
+  if (csr->cs != cs->expected_acs)
   {
     GNUNET_break (0);
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                 "Expected status %u, got %u\n",
-                cs->expected_cs,
+                cs->expected_acs,
                 csr->cs);
     TALER_TESTING_interpreter_fail (cs->is);
     return;
   }
   switch (csr->cs)
   {
-  case ANASTASIS_CHALLENGE_STATUS_SOLVED:
+  case ANASTASIS_CHALLENGE_ANSWER_STATUS_SOLVED:
     break;
-  case ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS:
-    {
-      FILE *file;
-      char *fn;
-
-      if (0 == strcasecmp (csr->details.open_challenge.content_type,
-                           "application/json"))
-      {
-        const char *filename;
-        json_t *in;
-
-        in = json_loadb (csr->details.open_challenge.body,
-                         csr->details.open_challenge.body_size,
-                         JSON_REJECT_DUPLICATES,
-                         NULL);
-        if (NULL == in)
-        {
-          GNUNET_break (0);
-          TALER_TESTING_interpreter_fail (cs->is);
-          return;
-        }
-        filename = json_string_value (json_object_get (in,
-                                                       "filename"));
-        if (NULL == filename)
-        {
-          GNUNET_break (0);
-          json_decref (in);
-          TALER_TESTING_interpreter_fail (cs->is);
-          return;
-        }
-        fn = GNUNET_strdup (filename);
-        json_decref (in);
-      }
-      else
-      {
-        fn = GNUNET_strndup (csr->details.open_challenge.body,
-                             csr->details.open_challenge.body_size);
-      }
-      file = fopen (fn,
-                    "r");
-      if (NULL == file)
-      {
-        GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
-                                  "open",
-                                  fn);
-        GNUNET_free (fn);
-        TALER_TESTING_interpreter_fail (cs->is);
-        return;
-      }
-      cs->code = GNUNET_malloc (22);
-      if (0 == fscanf (file,
-                       "%21s",
-                       cs->code))
-      {
-        GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
-                                  "fscanf",
-                                  fn);
-        TALER_TESTING_interpreter_fail (cs->is);
-        fclose (file);
-        GNUNET_free (fn);
-        return;
-      }
-      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                  "Read challenge answer `%s' from file `%s'\n",
-                  cs->code,
-                  fn);
-      TALER_TESTING_interpreter_next (cs->is);
-      GNUNET_break (0 == fclose (file));
-      GNUNET_free (fn);
-      return;
-    }
-  case ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED:
+  case ANASTASIS_CHALLENGE_ANSWER_STATUS_INVALID_ANSWER:
+    break;
+  case ANASTASIS_CHALLENGE_ANSWER_STATUS_PAYMENT_REQUIRED:
     if (0 != strncmp (csr->details.payment_required.taler_pay_uri,
                       "taler+http://pay/";,
                       strlen ("taler+http://pay/";)))
@@ -228,19 +165,15 @@ challenge_answer_cb (void *af_cls,
     }
     TALER_TESTING_interpreter_next (cs->is);
     return;
-  case ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN:
-    break;
-  case ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION:
+  case ANASTASIS_CHALLENGE_ANSWER_STATUS_TRUTH_UNKNOWN:
     break;
-  case ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE:
+  case ANASTASIS_CHALLENGE_ANSWER_STATUS_SERVER_FAILURE:
     GNUNET_break (0);
     TALER_TESTING_interpreter_fail (cs->is);
     return;
-  case ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED:
+  case ANASTASIS_CHALLENGE_ANSWER_STATUS_RATE_LIMIT_EXCEEDED:
     break;
-  case ANASTASIS_CHALLENGE_STATUS_AUTH_TIMEOUT:
-    break;
-  case ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS:
+  case ANASTASIS_CHALLENGE_ANSWER_STATUS_AUTH_TIMEOUT:
     break;
   }
   TALER_TESTING_interpreter_next (cs->is);
@@ -382,6 +315,115 @@ challenge_answer_run (void *cls,
 }
 
 
+static void
+challenge_start_cb (void *af_cls,
+                    const struct ANASTASIS_ChallengeStartResponse *csr)
+{
+  struct ChallengeState *cs = af_cls;
+
+  cs->c = NULL;
+  if (csr->cs != cs->expected_scs)
+  {
+    GNUNET_break (0);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Expected status %u, got %u\n",
+                cs->expected_scs,
+                csr->cs);
+    TALER_TESTING_interpreter_fail (cs->is);
+    return;
+  }
+  switch (csr->cs)
+  {
+  case ANASTASIS_CHALLENGE_START_STATUS_FILENAME_PROVIDED:
+    {
+      FILE *file;
+      char code[22];
+
+      file = fopen (csr->details.tan_filename,
+                    "r");
+      if (NULL == file)
+      {
+        GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+                                  "open",
+                                  csr->details.tan_filename);
+        TALER_TESTING_interpreter_fail (cs->is);
+        return;
+      }
+      if (0 == fscanf (file,
+                       "%21s",
+                       code))
+      {
+        GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+                                  "fscanf",
+                                  csr->details.tan_filename);
+        GNUNET_break (0 == fclose (file));
+        TALER_TESTING_interpreter_fail (cs->is);
+        return;
+      }
+      GNUNET_break (0 == fclose (file));
+      cs->code = GNUNET_strdup (code);
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                  "Read code `%s'\n",
+                  code);
+    }
+    break;
+  case ANASTASIS_CHALLENGE_START_STATUS_TAN_SENT_HINT_PROVIDED:
+    GNUNET_break (0); /* FIXME: not implemented */
+    break;
+  case ANASTASIS_CHALLENGE_START_STATUS_BANK_TRANSFER_REQUIRED:
+    GNUNET_break (0); /* FIXME: not implemented */
+    break;
+  case ANASTASIS_CHALLENGE_START_STATUS_PAYMENT_REQUIRED:
+    if (0 != strncmp (csr->details.payment_required.taler_pay_uri,
+                      "taler+http://pay/";,
+                      strlen ("taler+http://pay/";)))
+    {
+      GNUNET_break (0);
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Invalid payment URI `%s'\n",
+                  csr->details.payment_required.taler_pay_uri);
+      TALER_TESTING_interpreter_fail (cs->is);
+      return;
+    }
+    cs->payment_uri = GNUNET_strdup (
+      csr->details.payment_required.taler_pay_uri);
+    {
+      struct TALER_MERCHANT_PayUriData pud;
+
+      if (GNUNET_OK !=
+          TALER_MERCHANT_parse_pay_uri (cs->payment_uri,
+                                        &pud))
+      {
+        GNUNET_break (0);
+        TALER_TESTING_interpreter_fail (cs->is);
+        return;
+      }
+      cs->order_id = GNUNET_strdup (pud.order_id);
+      if (GNUNET_OK !=
+          GNUNET_STRINGS_string_to_data (cs->order_id,
+                                         strlen (cs->order_id),
+                                         &cs->payment_order_req,
+                                         sizeof (cs->payment_order_req)))
+      {
+        GNUNET_break (0);
+        TALER_TESTING_interpreter_fail (cs->is);
+        return;
+      }
+      TALER_MERCHANT_parse_pay_uri_free (&pud);
+    }
+    TALER_TESTING_interpreter_next (cs->is);
+    return;
+  case ANASTASIS_CHALLENGE_START_STATUS_TRUTH_UNKNOWN:
+    break;
+  case ANASTASIS_CHALLENGE_START_STATUS_SERVER_FAILURE:
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (cs->is);
+    return;
+  }
+  TALER_TESTING_interpreter_next (cs->is);
+}
+
+
 /**
  * Run a "recover secret" CMD.
  *
@@ -446,9 +488,7 @@ challenge_start_run (void *cls,
   if (GNUNET_OK !=
       ANASTASIS_challenge_start ((struct ANASTASIS_Challenge *) *c,
                                  ps,
-                                 GNUNET_TIME_UNIT_ZERO,
-                                 NULL,
-                                 &challenge_answer_cb,
+                                 &challenge_start_cb,
                                  cs))
   {
     GNUNET_break (0);
@@ -527,12 +567,12 @@ ANASTASIS_TESTING_cmd_challenge_start (
   const char *payment_ref,
   const char *challenge_ref,
   unsigned int challenge_index,
-  enum ANASTASIS_ChallengeStatus expected_cs)
+  enum ANASTASIS_ChallengeStartStatus expected_cs)
 {
   struct ChallengeState *cs;
 
   cs = GNUNET_new (struct ChallengeState);
-  cs->expected_cs = expected_cs;
+  cs->expected_scs = expected_cs;
   cs->challenge_ref = challenge_ref;
   cs->payment_ref = payment_ref;
   cs->challenge_index = challenge_index;
@@ -558,12 +598,12 @@ ANASTASIS_TESTING_cmd_challenge_answer (
   unsigned int challenge_index,
   const char *answer,
   unsigned int mode,
-  enum ANASTASIS_ChallengeStatus expected_cs)
+  enum ANASTASIS_ChallengeAnswerStatus expected_cs)
 {
   struct ChallengeState *cs;
 
   cs = GNUNET_new (struct ChallengeState);
-  cs->expected_cs = expected_cs;
+  cs->expected_acs = expected_cs;
   cs->challenge_ref = challenge_ref;
   cs->payment_ref = payment_ref;
   cs->answer = answer;

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