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

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

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

  You should have received a copy of the GNU General Public License along with
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file util/crypto_signatures.c
 * @brief POS confirmations that sign a challenge (DD 97)
 * @author Bohdan Potuzhnyi
 * @author Volodymyr Potuzhnyi
 *
 * The counterpart to the time-based confirmations in
 * crypto_confirmation.c. An offline verifier picks an unpredictable
 * challenge, the merchant backend signs it once the order was paid,
 * and the verifier checks that signature against a public key it was
 * configured with. Only the public key ever leaves the backend.
 *
 * This file owns the message that gets signed, the encoding of keys
 * and signatures, and which curve each algorithm uses. The signature
 * operations themselves come from TALER_ecdsa_p256_*() and from
 * GNUnet's EdDSA.
 */
#include "platform.h"
#include "taler/taler_util.h"
#include <gcrypt.h>


/**
 * Size of the message that gets signed.
 */
#define CHALLENGE_MSG_SIZE \
  (sizeof (TALER_POS_CHALLENGE_SALT) - 1 + TALER_POS_CHALLENGE_LENGTH)


/**
 * Assemble the message a confirmation signs: the domain separation
 * salt directly followed by the raw challenge bytes. An offline
 * verifier reproduces exactly this.
 *
 * @param challenge challenge to bind the signature to
 * @param[out] msg where to write #CHALLENGE_MSG_SIZE bytes
 */
static void
build_message (const struct TALER_PosChallengeP *challenge,
               unsigned char *msg)
{
  memcpy (msg,
          TALER_POS_CHALLENGE_SALT,
          sizeof (TALER_POS_CHALLENGE_SALT) - 1);
  memcpy (&msg[sizeof (TALER_POS_CHALLENGE_SALT) - 1],
          challenge->challenge,
          TALER_POS_CHALLENGE_LENGTH);
}


/**
 * Hash the challenge message, as the ECDSA algorithm signs a digest.
 *
 * @param challenge challenge to bind the signature to
 * @param[out] hash set to the SHA-256 of the challenge message
 */
static void
hash_message (const struct TALER_PosChallengeP *challenge,
              struct GNUNET_ShortHashCode *hash)
{
  unsigned char msg[CHALLENGE_MSG_SIZE];

  build_message (challenge,
                 msg);
  gcry_md_hash_buffer (GCRY_MD_SHA256,
                       hash,
                       msg,
                       sizeof (msg));
}


enum GNUNET_GenericReturnValue
TALER_otp_device_key_create (
  enum TALER_MerchantConfirmationAlgorithm pos_alg,
  char **pos_key,
  char **pos_pub)
{
  *pos_key = NULL;
  *pos_pub = NULL;
  switch (pos_alg)
  {
  case TALER_MCA_ECDSA_CHALLENGE:
    {
      struct TALER_EcdsaP256PrivateKeyP priv;
      struct TALER_EcdsaP256PublicKeyP pub;

      if (GNUNET_OK !=
          TALER_ecdsa_p256_key_create (&priv,
                                       &pub))
        return GNUNET_SYSERR;
      *pos_key = GNUNET_STRINGS_data_to_string_alloc (&priv,
                                                      sizeof (priv));
      *pos_pub = GNUNET_STRINGS_data_to_string_alloc (&pub,
                                                      sizeof (pub));
      GNUNET_CRYPTO_zero_keys (&priv,
                               sizeof (priv));
      break;
    }
  case TALER_MCA_EDDSA_CHALLENGE:
    {
      struct GNUNET_CRYPTO_EddsaPrivateKey priv;
      struct GNUNET_CRYPTO_EddsaPublicKey pub;

      GNUNET_CRYPTO_eddsa_key_create (&priv);
      GNUNET_CRYPTO_eddsa_key_get_public (&priv,
                                          &pub);
      *pos_key = GNUNET_STRINGS_data_to_string_alloc (&priv,
                                                      sizeof (priv));
      *pos_pub = GNUNET_STRINGS_data_to_string_alloc (&pub,
                                                      sizeof (pub));
      GNUNET_CRYPTO_eddsa_key_clear (&priv);
      break;
    }
  case TALER_MCA_NONE:
  case TALER_MCA_WITHOUT_PRICE:
  case TALER_MCA_WITH_PRICE:
    /* TOTP keys are supplied by the merchant, not generated here */
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  if ( (NULL == *pos_key) ||
       (NULL == *pos_pub) )
  {
    GNUNET_break (0);
    GNUNET_free (*pos_key);
    GNUNET_free (*pos_pub);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Sign @a challenge with the NIST P-256 private key @a pos_key.
 *
 * @param pos_key Crockford base32-encoded P-256 private scalar
 * @param challenge challenge to bind the signature to
 * @return Crockford base32-encoded r|s signature, or NULL on error
 */
static char *
sign_ecdsa (const char *pos_key,
            const struct TALER_PosChallengeP *challenge)
{
  struct TALER_EcdsaP256PrivateKeyP priv;
  struct TALER_EcdsaP256SignatureP sig;
  struct GNUNET_ShortHashCode hash;
  char *ret;

  if ( (NULL == pos_key) ||
       (NULL == challenge) )
  {
    GNUNET_break (0);
    return NULL;
  }
  if (GNUNET_OK !=
      GNUNET_STRINGS_string_to_data (pos_key,
                                     strlen (pos_key),
                                     &priv,
                                     sizeof (priv)))
  {
    GNUNET_break (0);
    return NULL;
  }
  hash_message (challenge,
                &hash);
  if (GNUNET_OK !=
      TALER_ecdsa_p256_sign (&priv,
                             &hash,
                             &sig))
  {
    GNUNET_break (0);
    GNUNET_CRYPTO_zero_keys (&priv,
                             sizeof (priv));
    return NULL;
  }
  GNUNET_CRYPTO_zero_keys (&priv,
                           sizeof (priv));
  ret = GNUNET_STRINGS_data_to_string_alloc (&sig,
                                             sizeof (sig));
  return ret;
}


/**
 * Sign @a challenge with the Ed25519 private key @a pos_key.
 *
 * @param pos_key Crockford base32-encoded Ed25519 private key
 * @param challenge challenge to bind the signature to
 * @return Crockford base32-encoded signature, or NULL on error
 */
static char *
sign_eddsa (const char *pos_key,
            const struct TALER_PosChallengeP *challenge)
{
  struct GNUNET_CRYPTO_EddsaPrivateKey priv;
  struct GNUNET_CRYPTO_EddsaSignature sig;
  unsigned char msg[CHALLENGE_MSG_SIZE];
  char *ret;

  if ( (NULL == pos_key) ||
       (NULL == challenge) )
  {
    GNUNET_break (0);
    return NULL;
  }
  if (GNUNET_OK !=
      GNUNET_STRINGS_string_to_data (pos_key,
                                     strlen (pos_key),
                                     &priv,
                                     sizeof (priv)))
  {
    GNUNET_break (0);
    return NULL;
  }
  build_message (challenge,
                 msg);
  if (GNUNET_OK !=
      GNUNET_CRYPTO_eddsa_sign_raw (&priv,
                                    msg,
                                    sizeof (msg),
                                    &sig))
  {
    GNUNET_break (0);
    GNUNET_CRYPTO_eddsa_key_clear (&priv);
    return NULL;
  }
  GNUNET_CRYPTO_eddsa_key_clear (&priv);
  ret = GNUNET_STRINGS_data_to_string_alloc (&sig,
                                             sizeof (sig));
  return ret;
}


char *
TALER_build_pos_confirmation_sig (
  const char *pos_key,
  enum TALER_MerchantConfirmationAlgorithm pos_alg,
  const struct TALER_PosChallengeP *challenge)
{
  if ( (NULL == pos_key) ||
       (NULL == challenge) )
  {
    GNUNET_break (0);
    return NULL;
  }
  switch (pos_alg)
  {
  case TALER_MCA_ECDSA_CHALLENGE:
    return sign_ecdsa (pos_key,
                       challenge);
  case TALER_MCA_EDDSA_CHALLENGE:
    return sign_eddsa (pos_key,
                       challenge);
  case TALER_MCA_NONE:
  case TALER_MCA_WITHOUT_PRICE:
  case TALER_MCA_WITH_PRICE:
    /* time-based algorithms are TALER_build_pos_confirmation()'s job */
    GNUNET_break (0);
    return NULL;
  }
  GNUNET_break (0);
  return NULL;
}


enum GNUNET_GenericReturnValue
TALER_check_pos_confirmation_sig (
  const char *pos_pub,
  enum TALER_MerchantConfirmationAlgorithm pos_alg,
  const struct TALER_PosChallengeP *challenge,
  const char *pos_confirmation)
{
  if ( (NULL == pos_pub) ||
       (NULL == challenge) ||
       (NULL == pos_confirmation) )
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  switch (pos_alg)
  {
  case TALER_MCA_ECDSA_CHALLENGE:
    {
      struct TALER_EcdsaP256PublicKeyP pub;
      struct TALER_EcdsaP256SignatureP sig;
      struct GNUNET_ShortHashCode hash;

      if ( (GNUNET_OK !=
            GNUNET_STRINGS_string_to_data (pos_pub,
                                           strlen (pos_pub),
                                           &pub,
                                           sizeof (pub))) ||
           (GNUNET_OK !=
            GNUNET_STRINGS_string_to_data (pos_confirmation,
                                           strlen (pos_confirmation),
                                           &sig,
                                           sizeof (sig))) )
      {
        GNUNET_break_op (0);
        return GNUNET_SYSERR;
      }
      hash_message (challenge,
                    &hash);
      return TALER_ecdsa_p256_verify (&pub,
                                      &hash,
                                      &sig);
    }
  case TALER_MCA_EDDSA_CHALLENGE:
    {
      struct GNUNET_CRYPTO_EddsaPublicKey pub;
      struct GNUNET_CRYPTO_EddsaSignature sig;
      unsigned char msg[CHALLENGE_MSG_SIZE];

      if ( (GNUNET_OK !=
            GNUNET_STRINGS_string_to_data (pos_pub,
                                           strlen (pos_pub),
                                           &pub,
                                           sizeof (pub))) ||
           (GNUNET_OK !=
            GNUNET_STRINGS_string_to_data (pos_confirmation,
                                           strlen (pos_confirmation),
                                           &sig,
                                           sizeof (sig))) )
      {
        GNUNET_break_op (0);
        return GNUNET_SYSERR;
      }
      build_message (challenge,
                     msg);
      return GNUNET_CRYPTO_eddsa_verify_raw (msg,
                                             sizeof (msg),
                                             &sig,
                                             &pub);
    }
  case TALER_MCA_NONE:
  case TALER_MCA_WITHOUT_PRICE:
  case TALER_MCA_WITH_PRICE:
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  GNUNET_break (0);
  return GNUNET_SYSERR;
}


/* end of crypto_signatures.c */
