/*
   This file is part of TALER
   Copyright (C) 2020-2026 Taler Systems SA

   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free Software
   Foundation; either version 3, or (at your option) any later version.

   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License along with
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
/**
 * @file taler-exchange-httpd_secmod-helpers.c
 * @brief management of state with secmod helpers
 * @author Christian Grothoff
 * @author Özgür Kesim
 */
#include "taler/taler_json_lib.h"
#include "taler/taler_mhd_lib.h"
#include "taler-exchange-httpd.h"
#include "taler-exchange-httpd_get-keys.h"
#include "taler-exchange-httpd_secmod-helpers.h"
#include "taler-exchange-httpd_configuration.h"


/**
 * For how long should a signing key be legally retained?
 * Configuration value.
 */
static struct GNUNET_TIME_Relative signkey_legal_duration;

/**
 * Handle for the esign/EdDSA helper.
 */
static struct TALER_CRYPTO_ExchangeSignHelper *esh;

/**
 * Handle for the denom/RSA helper.
 */
static struct TALER_CRYPTO_RsaDenominationHelper *rsadh;

/**
 * Handle for the denom/CS helper.
 */
static struct TALER_CRYPTO_CsDenominationHelper *csdh;

/**
 * Map from H(denom_pub) to `struct HelperDenomination` entries.
 */
static struct GNUNET_CONTAINER_MultiHashMap *denom_keys;

/**
 * Map from H(rsa_pub) to `struct HelperDenomination` entries.
 */
static struct GNUNET_CONTAINER_MultiHashMap *rsa_keys;

/**
 * Map from H(cs_pub) to `struct HelperDenomination` entries.
 */
static struct GNUNET_CONTAINER_MultiHashMap *cs_keys;

/**
 * Map from `struct TALER_ExchangePublicKey` to `struct HelperSignkey`
 * entries.  Based on the fact that a `struct GNUNET_PeerIdentity` is also
 * an EdDSA public key.
 */
static struct GNUNET_CONTAINER_MultiPeerMap *esign_keys;

/**
 * RSA security module public key, all zero if not known.
 */
static struct TALER_SecurityModulePublicKeyP denom_rsa_sm_pub;

/**
 * CS security module public key, all zero if not known.
 */
static struct TALER_SecurityModulePublicKeyP denom_cs_sm_pub;

/**
 * EdDSA security module public key, all zero if not known.
 */
static struct TALER_SecurityModulePublicKeyP esign_sm_pub;


bool
TEH_SECMOD_have_denom_sm_pub ()
{
  return
    (! GNUNET_is_zero (&denom_rsa_sm_pub)) ||
    (! GNUNET_is_zero (&denom_cs_sm_pub));
}


bool
TEH_SECMOD_have_esign_sm_pub ()
{
  return ! GNUNET_is_zero (&esign_sm_pub);
}


json_t *
TEH_SECMOD_get_sm_pubs_as_json ()
{
  return GNUNET_JSON_PACK (
    GNUNET_JSON_pack_data_auto ("denom_secmod_public_key",
                                &denom_rsa_sm_pub),
    GNUNET_JSON_pack_data_auto ("denom_secmod_cs_public_key",
                                &denom_cs_sm_pub),
    GNUNET_JSON_pack_data_auto ("signkey_secmod_public_key",
                                &esign_sm_pub));
}


/**
 * Check that the given RSA security module's public key is the one
 * we have pinned.  If it does not match, we die hard.
 *
 * @param sm_pub RSA security module public key to check
 */
static void
check_denom_rsa_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
{
  if (0 !=
      GNUNET_memcmp (sm_pub,
                     &denom_rsa_sm_pub))
  {
    if (! GNUNET_is_zero (&denom_rsa_sm_pub))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Our RSA security module changed its key. This must not happen.\n");
      GNUNET_assert (0);
    }
    denom_rsa_sm_pub = *sm_pub; /* TOFU ;-) */
  }
}


/**
 * Function called with information about available keys for signing.  Usually
 * only called once per key upon connect. Also called again in case a key is
 * being revoked, in that case with an @a end_time of zero.
 *
 * @param cls NULL
 * @param section_name name of the denomination type in the configuration;
 *                 NULL if the key has been revoked or purged
 * @param start_time when does the key become available for signing;
 *                 zero if the key has been revoked or purged
 * @param validity_duration how long does the key remain available for signing;
 *                 zero if the key has been revoked or purged
 * @param h_rsa hash of the @a denom_pub that is available (or was purged)
 * @param bs_pub the public key itself, NULL if the key was revoked or purged
 * @param sm_pub public key of the security module, NULL if the key was revoked or purged
 * @param sm_sig signature from the security module
 */
static void
helper_rsa_cb (
  void *cls,
  const char *section_name,
  struct GNUNET_TIME_Timestamp start_time,
  struct GNUNET_TIME_Relative validity_duration,
  const struct TALER_RsaPubHashP *h_rsa,
  struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub,
  const struct TALER_SecurityModulePublicKeyP *sm_pub,
  const struct TALER_SecurityModuleSignatureP *sm_sig)
{
  struct HelperDenomination *hd;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "RSA helper announces key %s for denomination type %s with validity %s\n",
              GNUNET_h2s (&h_rsa->hash),
              section_name,
              GNUNET_STRINGS_relative_time_to_string (validity_duration,
                                                      GNUNET_NO));
  TEH_keys_bump_generation ();
  TEH_resume_keys_requests (false);
  hd = GNUNET_CONTAINER_multihashmap_get (rsa_keys,
                                          &h_rsa->hash);
  if (NULL != hd)
  {
    /* should be just an update (revocation!), so update existing entry */
    hd->validity_duration = validity_duration;
    return;
  }
  GNUNET_assert (NULL != sm_pub);
  check_denom_rsa_sm_pub (sm_pub);
  hd = GNUNET_new (struct HelperDenomination);
  hd->start_time = start_time;
  hd->validity_duration = validity_duration;
  hd->h_details.h_rsa = *h_rsa;
  hd->sm_sig = *sm_sig;
  GNUNET_assert (GNUNET_CRYPTO_BSA_RSA == bs_pub->cipher);
  hd->denom_pub.bsign_pub_key =
    GNUNET_CRYPTO_bsign_pub_incref (bs_pub);
  /* load the age mask for the denomination, if applicable */
  hd->denom_pub.age_mask = TEH_CONFIG_load_age_mask (section_name);
  TALER_denom_pub_hash (&hd->denom_pub,
                        &hd->h_denom_pub);
  hd->section_name = GNUNET_strdup (section_name);
  GNUNET_assert (
    GNUNET_OK ==
    GNUNET_CONTAINER_multihashmap_put (
      denom_keys,
      &hd->h_denom_pub.hash,
      hd,
      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
  GNUNET_assert (
    GNUNET_OK ==
    GNUNET_CONTAINER_multihashmap_put (
      rsa_keys,
      &hd->h_details.h_rsa.hash,
      hd,
      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
}


/**
 * Check that the given CS security module's public key is the one
 * we have pinned.  If it does not match, we die hard.
 *
 * @param sm_pub RSA security module public key to check
 */
static void
check_denom_cs_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
{
  if (0 !=
      GNUNET_memcmp (sm_pub,
                     &denom_cs_sm_pub))
  {
    if (! GNUNET_is_zero (&denom_cs_sm_pub))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Our CS security module changed its key. This must not happen.\n");
      GNUNET_assert (0);
    }
    denom_cs_sm_pub = *sm_pub; /* TOFU ;-) */
  }
}


/**
 * Function called with information about available CS keys for signing. Usually
 * only called once per key upon connect. Also called again in case a key is
 * being revoked, in that case with an @a end_time of zero.
 *
 * @param cls NULL
 * @param section_name name of the denomination type in the configuration;
 *                 NULL if the key has been revoked or purged
 * @param start_time when does the key become available for signing;
 *                 zero if the key has been revoked or purged
 * @param validity_duration how long does the key remain available for signing;
 *                 zero if the key has been revoked or purged
 * @param h_cs hash of the @a denom_pub that is available (or was purged)
 * @param bs_pub the public key itself, NULL if the key was revoked or purged
 * @param sm_pub public key of the security module, NULL if the key was revoked or purged
 * @param sm_sig signature from the security module
 */
static void
helper_cs_cb (
  void *cls,
  const char *section_name,
  struct GNUNET_TIME_Timestamp start_time,
  struct GNUNET_TIME_Relative validity_duration,
  const struct TALER_CsPubHashP *h_cs,
  struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub,
  const struct TALER_SecurityModulePublicKeyP *sm_pub,
  const struct TALER_SecurityModuleSignatureP *sm_sig)
{
  struct HelperDenomination *hd;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "CS helper announces key %s for denomination type %s with validity %s\n",
              GNUNET_h2s (&h_cs->hash),
              section_name,
              GNUNET_STRINGS_relative_time_to_string (validity_duration,
                                                      GNUNET_NO));
  TEH_keys_bump_generation ();
  TEH_resume_keys_requests (false);
  hd = GNUNET_CONTAINER_multihashmap_get (cs_keys,
                                          &h_cs->hash);
  if (NULL != hd)
  {
    /* should be just an update (revocation!), so update existing entry */
    hd->validity_duration = validity_duration;
    return;
  }
  GNUNET_assert (NULL != sm_pub);
  check_denom_cs_sm_pub (sm_pub);
  hd = GNUNET_new (struct HelperDenomination);
  hd->start_time = start_time;
  hd->validity_duration = validity_duration;
  hd->h_details.h_cs = *h_cs;
  hd->sm_sig = *sm_sig;
  GNUNET_assert (GNUNET_CRYPTO_BSA_CS == bs_pub->cipher);
  hd->denom_pub.bsign_pub_key
    = GNUNET_CRYPTO_bsign_pub_incref (bs_pub);
  /* load the age mask for the denomination, if applicable */
  hd->denom_pub.age_mask = TEH_CONFIG_load_age_mask (section_name);
  TALER_denom_pub_hash (&hd->denom_pub,
                        &hd->h_denom_pub);
  hd->section_name = GNUNET_strdup (section_name);
  GNUNET_assert (
    GNUNET_OK ==
    GNUNET_CONTAINER_multihashmap_put (
      denom_keys,
      &hd->h_denom_pub.hash,
      hd,
      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
  GNUNET_assert (
    GNUNET_OK ==
    GNUNET_CONTAINER_multihashmap_put (
      cs_keys,
      &hd->h_details.h_cs.hash,
      hd,
      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
}


/**
 * Check that the given EdDSA security module's public key is the one
 * we have pinned.  If it does not match, we die hard.
 *
 * @param sm_pub EdDSA security module public key to check
 */
static void
check_esign_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
{
  if (0 !=
      GNUNET_memcmp (sm_pub,
                     &esign_sm_pub))
  {
    if (! GNUNET_is_zero (&esign_sm_pub))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Our EdDSA security module changed its key. This must not happen.\n");
      GNUNET_assert (0);
    }
    esign_sm_pub = *sm_pub; /* TOFU ;-) */
  }
}


/**
 * Function called with information about available keys for signing.  Usually
 * only called once per key upon connect. Also called again in case a key is
 * being revoked, in that case with an @a end_time of zero.
 *
 * @param cls NULL
 * @param start_time when does the key become available for signing;
 *                 zero if the key has been revoked or purged
 * @param validity_duration how long does the key remain available for signing;
 *                 zero if the key has been revoked or purged
 * @param exchange_pub the public key itself, NULL if the key was revoked or purged
 * @param sm_pub public key of the security module, NULL if the key was revoked or purged
 * @param sm_sig signature from the security module
 */
static void
helper_esign_cb (
  void *cls,
  struct GNUNET_TIME_Timestamp start_time,
  struct GNUNET_TIME_Relative validity_duration,
  const struct TALER_ExchangePublicKeyP *exchange_pub,
  const struct TALER_SecurityModulePublicKeyP *sm_pub,
  const struct TALER_SecurityModuleSignatureP *sm_sig)
{
  struct HelperSignkey *hsk;
  struct GNUNET_PeerIdentity pid;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "EdDSA helper announces signing key %s with validity %s\n",
              TALER_B2S (exchange_pub),
              GNUNET_STRINGS_relative_time_to_string (validity_duration,
                                                      GNUNET_NO));
  TEH_keys_bump_generation ();
  TEH_resume_keys_requests (false);
  pid.public_key = exchange_pub->eddsa_pub;
  hsk = GNUNET_CONTAINER_multipeermap_get (esign_keys,
                                           &pid);
  if (NULL != hsk)
  {
    /* should be just an update (revocation!), so update existing entry */
    hsk->validity_duration = validity_duration;
    return;
  }
  GNUNET_assert (NULL != sm_pub);
  check_esign_sm_pub (sm_pub);
  hsk = GNUNET_new (struct HelperSignkey);
  hsk->start_time = start_time;
  hsk->validity_duration = validity_duration;
  hsk->exchange_pub = *exchange_pub;
  hsk->sm_sig = *sm_sig;
  GNUNET_assert (
    GNUNET_OK ==
    GNUNET_CONTAINER_multipeermap_put (
      esign_keys,
      &pid,
      hsk,
      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
}


enum GNUNET_GenericReturnValue
TEH_SECMOD_setup_key_helpers ()
{
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
                                           "exchange",
                                           "SIGNKEY_LEGAL_DURATION",
                                           &signkey_legal_duration))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "exchange",
                               "SIGNKEY_LEGAL_DURATION");
    return GNUNET_SYSERR;
  }
  denom_keys
    = GNUNET_CONTAINER_multihashmap_create (1024,
                                            GNUNET_YES);
  rsa_keys
    = GNUNET_CONTAINER_multihashmap_create (1024,
                                            GNUNET_YES);
  cs_keys
    = GNUNET_CONTAINER_multihashmap_create (1024,
                                            GNUNET_YES);
  esign_keys
    = GNUNET_CONTAINER_multipeermap_create (32,
                                            GNUNET_NO /* MUST BE NO! */);
  rsadh = TALER_CRYPTO_helper_rsa_connect (TEH_cfg,
                                           "taler-exchange",
                                           &helper_rsa_cb,
                                           NULL);
  if (NULL == rsadh)
    return GNUNET_SYSERR;
  csdh = TALER_CRYPTO_helper_cs_connect (TEH_cfg,
                                         "taler-exchange",
                                         &helper_cs_cb,
                                         NULL);
  if (NULL == csdh)
    return GNUNET_SYSERR;
  esh = TALER_CRYPTO_helper_esign_connect (TEH_cfg,
                                           "taler-exchange",
                                           &helper_esign_cb,
                                           NULL);
  if (NULL == esh)
    return GNUNET_SYSERR;
  return GNUNET_OK;
}


void
TEH_SECMOD_sync_key_helpers (void)
{
  TALER_CRYPTO_helper_rsa_poll (rsadh);
  TALER_CRYPTO_helper_cs_poll (csdh);
  TALER_CRYPTO_helper_esign_poll (esh);
}


/**
 * Helper function for #TEH_SECMOD_destroy_key_helpers to free all entries
 * in the `denom_keys` map.
 *
 * @param cls NULL
 * @param h_denom_pub hash of the denomination public key
 * @param value the `struct HelperDenomination` to release
 * @return #GNUNET_OK (continue to iterate)
 */
static enum GNUNET_GenericReturnValue
free_denom_cb (void *cls,
               const struct GNUNET_HashCode *h_denom_pub,
               void *value)
{
  struct HelperDenomination *hd = value;

  (void) cls;
  (void) h_denom_pub;
  TALER_denom_pub_free (&hd->denom_pub);
  GNUNET_free (hd->section_name);
  GNUNET_free (hd);
  return GNUNET_OK;
}


/**
 * Helper function for #TEH_SECMOD_destroy_key_helpers to free all entries
 * in the `esign_keys` map.
 *
 * @param cls NULL
 * @param pid unused, matches the exchange public key
 * @param value the `struct HelperSignkey` to release
 * @return #GNUNET_OK (continue to iterate)
 */
static enum GNUNET_GenericReturnValue
free_esign_cb (void *cls,
               const struct GNUNET_PeerIdentity *pid,
               void *value)
{
  struct HelperSignkey *hsk = value;

  (void) cls;
  (void) pid;
  GNUNET_free (hsk);
  return GNUNET_OK;
}


void
TEH_SECMOD_destroy_key_helpers ()
{
  GNUNET_CONTAINER_multihashmap_iterate (denom_keys,
                                         &free_denom_cb,
                                         NULL);
  GNUNET_CONTAINER_multihashmap_destroy (rsa_keys);
  rsa_keys = NULL;
  GNUNET_CONTAINER_multihashmap_destroy (cs_keys);
  cs_keys = NULL;
  GNUNET_CONTAINER_multihashmap_destroy (denom_keys);
  denom_keys = NULL;
  GNUNET_CONTAINER_multipeermap_iterate (esign_keys,
                                         &free_esign_cb,
                                         NULL);
  GNUNET_CONTAINER_multipeermap_destroy (esign_keys);
  esign_keys = NULL;
  if (NULL != rsadh)
  {
    TALER_CRYPTO_helper_rsa_disconnect (rsadh);
    rsadh = NULL;
  }
  if (NULL != csdh)
  {
    TALER_CRYPTO_helper_cs_disconnect (csdh);
    csdh = NULL;
  }
  if (NULL != esh)
  {
    TALER_CRYPTO_helper_esign_disconnect (esh);
    esh = NULL;
  }
}


void
TEH_SECMOD_iterate_denom_keys (
  GNUNET_CONTAINER_MultiHashMapIteratorCallback cb,
  void *cb_cls)
{
  GNUNET_CONTAINER_multihashmap_iterate (denom_keys,
                                         cb,
                                         cb_cls);
}


void
TEH_SECMOD_iterate_esign_keys (
  GNUNET_CONTAINER_PeerMapIterator cb,
  void *cb_cls)
{
  GNUNET_CONTAINER_multipeermap_iterate (esign_keys,
                                         cb,
                                         cb_cls);

}


enum TALER_ErrorCode
TEH_SECMOD_denom_cs_batch_r_pub_simple (
  unsigned int cdds_length,
  const struct TEH_SECMOD_CsDeriveData cdds[static cdds_length],
  bool for_melt,
  struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[static cdds_length])
{
  struct HelperDenomination *hd;
  struct TALER_CRYPTO_CsDeriveRequest cdrs[cdds_length];

  for (unsigned int i = 0; i<cdds_length; i++)
  {
    const struct TALER_DenominationHashP *h_denom_pub = cdds[i].h_denom_pub;
    const struct GNUNET_CRYPTO_CsSessionNonce *nonce = cdds[i].nonce;

    hd = GNUNET_CONTAINER_multihashmap_get (denom_keys,
                                            &h_denom_pub->hash);
    if (NULL == hd)
    {
      return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
    }
    if (GNUNET_CRYPTO_BSA_CS !=
        hd->denom_pub.bsign_pub_key->cipher)
    {
      return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    }
    cdrs[i].h_cs = &hd->h_details.h_cs;
    cdrs[i].nonce = nonce;
  }

  return TALER_CRYPTO_helper_cs_r_batch_derive (csdh,
                                                cdds_length,
                                                cdrs,
                                                for_melt,
                                                r_pubs);
}


enum TALER_ErrorCode
TEH_SECMOD_denom_cs_batch_r_pub (
  size_t num,
  const struct TALER_DenominationHashP h_denom_pubs[static num],
  const struct GNUNET_CRYPTO_CsSessionNonce nonces[static num],
  bool for_melt,
  struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[static num],
  size_t *err_idx)
{
  struct TALER_CRYPTO_CsDeriveRequest cdrs[num];

  for (unsigned int i = 0; i<num; i++)
  {
    const struct TEH_DenominationKey *dk;
    const struct HelperDenomination *hd;

    *err_idx = i;

    /* FIXME: right now we need both,
     * TEH_DenominationKey and HelperDenomination,
     * because only TEH_DenominationKey has .recoup_possible
     */
    hd = GNUNET_CONTAINER_multihashmap_get (denom_keys,
                                            &h_denom_pubs[i].hash);
    dk = TEH_keys_denomination_by_hash (&h_denom_pubs[i],
                                        NULL,
                                        NULL);
    if (NULL == hd)
      return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;

    GNUNET_assert (NULL != dk);

    if (GNUNET_CRYPTO_BSA_CS !=
        hd->denom_pub.bsign_pub_key->cipher)
      return TALER_EC_EXCHANGE_GENERIC_INVALID_DENOMINATION_CIPHER_FOR_OPERATION
      ;

    if (GNUNET_TIME_absolute_is_future (hd->start_time.abs_time))
      return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE;
    if (dk->recoup_possible)
      return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED;
    if (GNUNET_TIME_relative_is_zero (hd->validity_duration))
      return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED;

    cdrs[i].h_cs = &hd->h_details.h_cs;
    cdrs[i].nonce = &nonces[i];
  }

  return TALER_CRYPTO_helper_cs_r_batch_derive (csdh,
                                                num,
                                                cdrs,
                                                for_melt,
                                                r_pubs);
}


enum TALER_ErrorCode
TEH_SECMOD_denom_batch_sign (
  unsigned int csds_length,
  const struct TEH_SECMOD_CoinSignData csds[static csds_length],
  bool for_melt,
  struct TALER_BlindedDenominationSignature bss[static csds_length])
{
  struct HelperDenomination *hd;
  struct TALER_CRYPTO_RsaSignRequest rsrs[csds_length];
  struct TALER_CRYPTO_CsSignRequest csrs[csds_length];
  struct TALER_BlindedDenominationSignature rs[csds_length];
  struct TALER_BlindedDenominationSignature cs[csds_length];
  unsigned int rsrs_pos = 0;
  unsigned int csrs_pos = 0;
  enum TALER_ErrorCode ec;

  for (unsigned int i = 0; i<csds_length; i++)
  {
    const struct TALER_DenominationHashP *h_denom_pub = csds[i].h_denom_pub;
    const struct TALER_BlindedPlanchet *bp = csds[i].bp;

    hd = GNUNET_CONTAINER_multihashmap_get (denom_keys,
                                            &h_denom_pub->hash);
    if (NULL == hd)
      return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
    if (bp->blinded_message->cipher !=
        hd->denom_pub.bsign_pub_key->cipher)
      return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    switch (hd->denom_pub.bsign_pub_key->cipher)
    {
    case GNUNET_CRYPTO_BSA_RSA:
      rsrs[rsrs_pos].h_rsa = &hd->h_details.h_rsa;
      rsrs[rsrs_pos].msg
        = bp->blinded_message->details.rsa_blinded_message.blinded_msg;
      rsrs[rsrs_pos].msg_size
        = bp->blinded_message->details.rsa_blinded_message.blinded_msg_size;
      rsrs_pos++;
      break;
    case GNUNET_CRYPTO_BSA_CS:
      csrs[csrs_pos].h_cs = &hd->h_details.h_cs;
      csrs[csrs_pos].blinded_planchet
        = &bp->blinded_message->details.cs_blinded_message;
      csrs_pos++;
      break;
    default:
      return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    }
  }

  if (0 != rsrs_pos)
  {
    memset (rs,
            0,
            sizeof (rs));
  }
  if (0 != csrs_pos)
  {
    memset (cs,
            0,
            sizeof (cs));
  }
  ec = TALER_EC_NONE;
  if (0 != csrs_pos)
  {
    ec = TALER_CRYPTO_helper_cs_batch_sign (
      csdh,
      csrs_pos,
      csrs,
      for_melt,
      (0 == rsrs_pos) ? bss : cs);
    if (TALER_EC_NONE != ec)
    {
      for (unsigned int i = 0; i<csrs_pos; i++)
        TALER_blinded_denom_sig_free (&cs[i]);
      return ec;
    }
    TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_CS] += csrs_pos;
  }
  if (0 != rsrs_pos)
  {
    ec = TALER_CRYPTO_helper_rsa_batch_sign (
      rsadh,
      rsrs_pos,
      rsrs,
      (0 == csrs_pos) ? bss : rs);
    if (TALER_EC_NONE != ec)
    {
      for (unsigned int i = 0; i<csrs_pos; i++)
        TALER_blinded_denom_sig_free (&cs[i]);
      for (unsigned int i = 0; i<rsrs_pos; i++)
        TALER_blinded_denom_sig_free (&rs[i]);
      return ec;
    }
    TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_RSA] += rsrs_pos;
  }

  if ( (0 != csrs_pos) &&
       (0 != rsrs_pos) )
  {
    rsrs_pos = 0;
    csrs_pos = 0;
    for (unsigned int i = 0; i<csds_length; i++)
    {
      const struct TALER_BlindedPlanchet *bp = csds[i].bp;

      switch (bp->blinded_message->cipher)
      {
      case GNUNET_CRYPTO_BSA_RSA:
        bss[i] = rs[rsrs_pos++];
        break;
      case GNUNET_CRYPTO_BSA_CS:
        bss[i] = cs[csrs_pos++];
        break;
      default:
        GNUNET_assert (0);
      }
    }
  }
  return TALER_EC_NONE;
}


enum TALER_ErrorCode
TEH_SECMOD_exchange_sign (
  const struct GNUNET_CRYPTO_SignaturePurpose *purpose,
  struct TALER_ExchangePublicKeyP *pub,
  struct TALER_ExchangeSignatureP *sig)
{
  return TALER_CRYPTO_helper_esign_sign_ (esh,
                                          purpose,
                                          pub,
                                          sig);
}


enum GNUNET_GenericReturnValue
TEH_SECMOD_denom_load_meta (
  const struct TALER_DenominationHashP *h_denom_pub,
  struct TALER_DenominationPublicKey *denom_pub,
  struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta)
{
  struct HelperDenomination *hd;
  enum GNUNET_GenericReturnValue ok;

  hd = GNUNET_CONTAINER_multihashmap_get (denom_keys,
                                          &h_denom_pub->hash);
  if (NULL == hd)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Denomination %s not known\n",
                GNUNET_h2s (&h_denom_pub->hash));
    return GNUNET_NO;
  }
  meta->start = hd->start_time;
  meta->expire_withdraw = GNUNET_TIME_absolute_to_timestamp (
    GNUNET_TIME_absolute_add (meta->start.abs_time,
                              hd->validity_duration));
  ok = TEH_CONFIG_load_denom_data (hd->section_name,
                                   meta);
  if (GNUNET_OK == ok)
  {
    GNUNET_assert (GNUNET_CRYPTO_BSA_INVALID !=
                   hd->denom_pub.bsign_pub_key->cipher);
    TALER_denom_pub_copy (denom_pub,
                          &hd->denom_pub);
  }
  else
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "No fees for `%s', voiding key\n",
                hd->section_name);
    memset (denom_pub,
            0,
            sizeof (*denom_pub));
  }
  return ok;
}


enum GNUNET_GenericReturnValue
TEH_SECMOD_esign_load_meta (const struct TALER_ExchangePublicKeyP *exchange_pub,
                            struct TALER_EXCHANGEDB_SignkeyMetaData *meta)
{
  struct HelperSignkey *hsk;
  struct GNUNET_PeerIdentity pid;

  pid.public_key = exchange_pub->eddsa_pub;
  hsk = GNUNET_CONTAINER_multipeermap_get (esign_keys,
                                           &pid);
  if (NULL == hsk)
  {
    GNUNET_break (0);
    return GNUNET_NO;
  }
  meta->start = hsk->start_time;

  meta->expire_sign = GNUNET_TIME_absolute_to_timestamp (
    GNUNET_TIME_absolute_add (meta->start.abs_time,
                              hsk->validity_duration));
  meta->expire_legal = GNUNET_TIME_absolute_to_timestamp (
    GNUNET_TIME_absolute_add (meta->expire_sign.abs_time,
                              signkey_legal_duration));
  return GNUNET_OK;
}


bool
TEH_SECMOD_denom_priv_check_lost (
  const struct TALER_DenominationHashP *h_denom_pub)
{
  struct HelperDenomination *hd;

  hd = GNUNET_CONTAINER_multihashmap_get (denom_keys,
                                          &h_denom_pub->hash);
  return (NULL == hd) ||
         GNUNET_TIME_absolute_is_past (
    GNUNET_TIME_absolute_add (
      hd->start_time.abs_time,
      hd->validity_duration));
}


void
TEH_SECMOD_esign_revoke (
  const struct TALER_ExchangePublicKeyP *exchange_pub)
{
  TALER_CRYPTO_helper_esign_revoke (esh,
                                    exchange_pub);
  TEH_keys_update_states ();
}


void
TEH_SECMOD_denom_revoke (
  const struct TALER_DenominationHashP *h_denom_pub)
{
  struct HelperDenomination *hd;

  hd = GNUNET_CONTAINER_multihashmap_get (denom_keys,
                                          &h_denom_pub->hash);
  if (NULL == hd)
  {
    GNUNET_break (0);
    return;
  }
  switch (hd->denom_pub.bsign_pub_key->cipher)
  {
  case GNUNET_CRYPTO_BSA_INVALID:
    break;
  case GNUNET_CRYPTO_BSA_RSA:
    TALER_CRYPTO_helper_rsa_revoke (rsadh,
                                    &hd->h_details.h_rsa);
    TEH_keys_update_states ();
    return;
  case GNUNET_CRYPTO_BSA_CS:
    TALER_CRYPTO_helper_cs_revoke (csdh,
                                   &hd->h_details.h_cs);
    TEH_keys_update_states ();
    return;
  }
  GNUNET_break (0);
  return;
}
