/*
  This file is part of TALER
  Copyright (C) 2024 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/donau_crypto.c
 * @brief Cryptographic utility functions
 * @author Lukas Matyja
 */
#include "donau_config.h"
#include <taler/taler_util.h>
#include "donau_util.h"
#include <gcrypt.h>

GNUNET_NETWORK_STRUCT_BEGIN
/**
 * Structure we hash to compute the group key for
 * a donation unit group.
 */
struct DonationUnitGroupP
{
  /**
   * Value of coins in this donation unit group.
   */
  struct TALER_AmountNBO value;

  /**
   * Cipher used for the donation unit, in NBO.
   */
  uint32_t cipher GNUNET_PACKED;
};
GNUNET_NETWORK_STRUCT_END


void
DONAU_donation_unit_group_get_key (
  const struct DONAU_DonationUnitGroup *dg,
  struct GNUNET_HashCode *key)
{
  struct DonationUnitGroupP dgp = {
    .cipher = htonl (dg->cipher)
  };

  TALER_amount_hton (&dgp.value,
                     &dg->value);
  GNUNET_CRYPTO_hash (&dgp,
                      sizeof (dgp),
                      key);
}


int
DONAU_donation_unit_pub_cmp (
  const struct DONAU_DonationUnitPublicKey *donation_unit1,
  const struct DONAU_DonationUnitPublicKey *donation_unit2)
{
  if (donation_unit1->bsign_pub_key->cipher !=
      donation_unit2->bsign_pub_key->cipher)
    return (donation_unit1->bsign_pub_key->cipher >
            donation_unit2->bsign_pub_key->cipher) ? 1 : -1;
  return GNUNET_CRYPTO_bsign_pub_cmp (donation_unit1->bsign_pub_key,
                                      donation_unit2->bsign_pub_key);
}


void
DONAU_donation_unit_pub_deep_copy (
  struct DONAU_DonationUnitPublicKey *donation_unit_dst,
  const struct DONAU_DonationUnitPublicKey *donation_unit_src)
{
  donation_unit_dst->bsign_pub_key
    = GNUNET_CRYPTO_bsign_pub_incref (donation_unit_src->bsign_pub_key);
}


void
DONAU_donation_unit_pub_free (
  struct DONAU_DonationUnitPublicKey *donation_unit_pub)
{
  if (NULL != donation_unit_pub->bsign_pub_key)
  {
    GNUNET_CRYPTO_blind_sign_pub_decref (donation_unit_pub->bsign_pub_key);
    donation_unit_pub->bsign_pub_key = NULL;
  }
}


void
DONAU_blinded_donation_unit_sig_free (
  struct DONAU_BlindedDonationUnitSignature *du_sig)
{
  if (NULL != du_sig->blinded_sig)
  {
    GNUNET_CRYPTO_blinded_sig_decref (du_sig->blinded_sig);
    du_sig->blinded_sig = NULL;
  }
}


void
DONAU_donation_unit_pub_hash (
  const struct DONAU_DonationUnitPublicKey *donation_unit_pub,
  struct DONAU_DonationUnitHashP *donation_unit_hash
  )
{
  struct GNUNET_CRYPTO_BlindSignPublicKey *bsp
    = donation_unit_pub->bsign_pub_key;

  switch (bsp->cipher)
  {
  case GNUNET_CRYPTO_BSA_RSA:
    /* Important: this MUST match the way the RSA-secmod does the
       hashing of the public keys (see donau-httpd_keys.c) */
    GNUNET_CRYPTO_rsa_public_key_hash (bsp->details.rsa_public_key,
                                       &donation_unit_hash->hash);
    break;
  case GNUNET_CRYPTO_BSA_CS:
    /* Important: this MUST match the way the CS-secmod does the
       hashing of the public keys (see donau-httpd_keys.c) */
    GNUNET_CRYPTO_hash (&bsp->details.cs_public_key,
                        sizeof(bsp->details.cs_public_key),
                        &donation_unit_hash->hash);
    break;
  default:
    GNUNET_assert (0);
  }
}


void
DONAU_unique_donor_id_hash (
  const struct DONAU_HashDonorTaxId *h_donor_tax_id,
  const struct DONAU_UniqueDonorIdentifierNonce *nonce,
  struct DONAU_UniqueDonorIdentifierHashP *h_udi)
{
  struct GNUNET_HashContext *hash_context;

  hash_context = GNUNET_CRYPTO_hash_context_start ();
  GNUNET_CRYPTO_hash_context_read (
    hash_context,
    h_donor_tax_id,
    sizeof(struct DONAU_HashDonorTaxId));
  GNUNET_CRYPTO_hash_context_read (
    hash_context,
    nonce,
    sizeof(struct DONAU_UniqueDonorIdentifierNonce));
  GNUNET_CRYPTO_hash_context_finish (
    hash_context,
    &h_udi->hash);
}


enum GNUNET_GenericReturnValue
DONAU_donation_receipt_verify (
  const struct DONAU_DonationUnitPublicKey *donation_unit_pub,
  const struct DONAU_UniqueDonorIdentifierHashP *h_udi,
  const struct DONAU_DonationUnitSignature *donation_unit_sig)
{
  return GNUNET_CRYPTO_blind_sig_verify (donation_unit_pub->bsign_pub_key,
                                         donation_unit_sig->unblinded_sig,
                                         h_udi,
                                         sizeof (*h_udi));
}


enum GNUNET_GenericReturnValue
DONAU_donation_unit_blind (
  const struct DONAU_DonationUnitPublicKey *du_pub,
  const union GNUNET_CRYPTO_BlindingSecretP *budi_secret,
  const union GNUNET_CRYPTO_BlindSessionNonce *cs_nonce,
  const struct DONAU_UniqueDonorIdentifierNonce *udi_nonce,// message
  const struct DONAU_HashDonorTaxId *h_tax_id, // message
  const struct DONAU_BatchIssueValues *alg_values,
  struct DONAU_UniqueDonorIdentifierHashP *udi_hash,
  struct DONAU_BlindedUniqueDonorIdentifier *budi)
{
  DONAU_unique_donor_id_hash (
    h_tax_id,
    udi_nonce,
    udi_hash);

  budi->blinded_message
    = GNUNET_CRYPTO_message_blind_to_sign (du_pub->bsign_pub_key,
                                           budi_secret,
                                           cs_nonce,
                                           udi_hash,
                                           sizeof (*udi_hash),
                                           alg_values->blinding_inputs);
  if (NULL == budi->blinded_message)
    return GNUNET_SYSERR;
  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
DONAU_donation_unit_sig_unblind (
  struct DONAU_DonationUnitSignature *du_sig,
  const struct DONAU_BlindedDonationUnitSignature *blind_du_sig,
  const union GNUNET_CRYPTO_BlindingSecretP *budi_secret,
  const struct DONAU_UniqueDonorIdentifierHashP *udi_hash,
  const struct DONAU_BatchIssueValues *alg_values,
  const struct DONAU_DonationUnitPublicKey *du_pub)
{
  du_sig->unblinded_sig
    = GNUNET_CRYPTO_blind_sig_unblind (blind_du_sig->blinded_sig,
                                       budi_secret,
                                       udi_hash,
                                       sizeof (*udi_hash),
                                       alg_values->blinding_inputs,
                                       du_pub->bsign_pub_key);
  if (NULL == du_sig->unblinded_sig)
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


void
DONAU_budi_secret_create (
  const struct DONAU_BudiMasterSecretP *ps,
  const struct DONAU_BatchIssueValues *alg_values,
  union GNUNET_CRYPTO_BlindingSecretP *bks)
{
  const struct GNUNET_CRYPTO_BlindingInputValues *bi =
    alg_values->blinding_inputs;

  switch (bi->cipher)
  {
  case GNUNET_CRYPTO_BSA_INVALID:
    GNUNET_break (0);
    return;
  case GNUNET_CRYPTO_BSA_RSA:
    GNUNET_assert (GNUNET_YES ==
                   GNUNET_CRYPTO_hkdf_gnunet (&bks->rsa_bks,
                                              sizeof (bks->rsa_bks),
                                              "bks",
                                              strlen ("bks"),
                                              ps,
                                              sizeof(*ps)));
    return;
  case GNUNET_CRYPTO_BSA_CS:
    GNUNET_assert (
      GNUNET_YES ==
      GNUNET_CRYPTO_hkdf_gnunet (
        &bks->nonce,
        sizeof (bks->nonce),
        "bseed",
        strlen ("bseed"),
        ps,
        sizeof(*ps),
        GNUNET_CRYPTO_kdf_arg_auto (&bi->details.cs_values)));
    return;
  }
  GNUNET_assert (0);
}


const struct DONAU_BatchIssueValues *
DONAU_donation_unit_ewv_rsa_singleton ()
{
  static struct GNUNET_CRYPTO_BlindingInputValues bi = {
    .cipher = GNUNET_CRYPTO_BSA_RSA
  };
  static struct DONAU_BatchIssueValues alg_values = {
    .blinding_inputs = &bi
  };
  return &alg_values;
}


void
DONAU_donation_unit_ewv_copy (struct DONAU_BatchIssueValues *bi_dst,
                              const struct DONAU_BatchIssueValues *bi_src)
{
  if (bi_src == DONAU_donation_unit_ewv_rsa_singleton ())
  {
    *bi_dst = *bi_src;
    return;
  }
  bi_dst->blinding_inputs
    = GNUNET_CRYPTO_blinding_input_values_incref (bi_src->blinding_inputs);
}
