/*
     This file is part of GNUnet.
     Copyright (C) 2012, 2013, 2015 GNUnet e.V.

     GNUnet 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 of the License,
     or (at your option) any later version.

     GNUnet 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 this program.  If not, see <http://www.gnu.org/licenses/>.

     SPDX-License-Identifier: AGPL3.0-or-later
 */

/**
 * @file util/crypto_ecc.c
 * @brief public key cryptography (ECC) with libgcrypt
 * @author Christian Grothoff
 * @author Florian Dold
 */
#include "platform.h"
#include "gnunet_common.h"
#include <gcrypt.h>
#include <sodium.h>
#include <sodium/crypto_sign_ed25519.h>
#include "gnunet_util_lib.h"
#include "benchmark.h"
#include "sodium/crypto_scalarmult.h"
#include "sodium/crypto_scalarmult_curve25519.h"
#include "sodium/utils.h"

#define EXTRA_CHECKS 0

/**
 * IMPLEMENTATION NOTICE:
 *
 * ECDSA: We use a non-standard curve for ECDSA: Ed25519.
 * For performance reasons, we use cryptographic operations from
 * libsodium wherever we can get away with it, even though libsodium
 * itself does not support ECDSA.
 * This is why the sign and verify functionality from libgcrypt is
 * required and used.
 *
 * EdDSA: We use a standard EdDSA construction.
 * (We still use libgcrypt for hashing and RNG, but not EC)
 *
 * ECDHE: For both EdDSA and ECDSA keys, we use libsodium for
 * ECDHE due to performance benefits over libgcrypt.
 */

/**
 * Name of the curve we are using.  Note that we have hard-coded
 * structs that use 256 bits, so using a bigger curve will require
 * changes that break stuff badly.  The name of the curve given here
 * must be agreed by all peers and be supported by libgcrypt.
 */
#define CURVE "Ed25519"

#define LOG(kind, ...) GNUNET_log_from (kind, "util-crypto-ecc", __VA_ARGS__)

#define LOG_STRERROR(kind, syscall) \
        GNUNET_log_from_strerror (kind, "util-crypto-ecc", syscall)

#define LOG_STRERROR_FILE(kind, syscall, filename) \
        GNUNET_log_from_strerror_file (kind, "util-crypto-ecc", syscall, \
                                       filename)

/**
 * Log an error message at log-level 'level' that indicates
 * a failure of the command 'cmd' with the message given
 * by gcry_strerror(rc).
 */
#define LOG_GCRY(level, cmd, rc)                      \
        do                                                  \
        {                                                   \
          LOG (level,                                       \
               _ ("`%s' failed at %s:%d with error: %s\n"), \
               cmd,                                         \
               __FILE__,                                    \
               __LINE__,                                    \
               gcry_strerror (rc));                         \
        } while (0)


/**
 * Extract values from an S-expression.
 *
 * @param array where to store the result(s)
 * @param sexp S-expression to parse
 * @param topname top-level name in the S-expression that is of interest
 * @param elems names of the elements to extract
 * @return 0 on success
 */
static int
key_from_sexp (gcry_mpi_t *array,
               gcry_sexp_t sexp,
               const char *topname,
               const char *elems)
{
  gcry_sexp_t list;
  gcry_sexp_t l2;
  unsigned int idx;

  list = gcry_sexp_find_token (sexp, topname, 0);
  if (! list)
    return 1;
  l2 = gcry_sexp_cadr (list);
  gcry_sexp_release (list);
  list = l2;
  if (! list)
    return 2;

  idx = 0;
  for (const char *s = elems; *s; s++, idx++)
  {
    l2 = gcry_sexp_find_token (list, s, 1);
    if (! l2)
    {
      for (unsigned int i = 0; i < idx; i++)
      {
        gcry_free (array[i]);
        array[i] = NULL;
      }
      gcry_sexp_release (list);
      return 3;     /* required parameter not found */
    }
    array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
    gcry_sexp_release (l2);
    if (! array[idx])
    {
      for (unsigned int i = 0; i < idx; i++)
      {
        gcry_free (array[i]);
        array[i] = NULL;
      }
      gcry_sexp_release (list);
      return 4;     /* required parameter is invalid */
    }
  }
  gcry_sexp_release (list);
  return 0;
}


/**
 * Convert the given private key from the network format to the
 * S-expression that can be used by libgcrypt.
 *
 * @param priv private key to decode
 * @return NULL on error
 */
static gcry_sexp_t
decode_private_ecdsa_key (const struct GNUNET_CRYPTO_EcdsaPrivateKey *priv)
{
  gcry_sexp_t result;
  int rc;
  uint8_t d[32];

  for (size_t i = 0; i<32; i++)
    d[i] = priv->d[31 - i];

  rc = gcry_sexp_build (&result,
                        NULL,
                        "(private-key(ecc(curve \"" CURVE "\")"
                        "(d %b)))",
                        32,
                        d);
  if (0 != rc)
  {
    LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_sexp_build", rc);
    GNUNET_assert (0);
  }
#if EXTRA_CHECKS
  if (0 != (rc = gcry_pk_testkey (result)))
  {
    LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_pk_testkey", rc);
    GNUNET_assert (0);
  }
#endif
  return result;
}


void
GNUNET_CRYPTO_ecdsa_key_get_public (
  const struct GNUNET_CRYPTO_EcdsaPrivateKey *priv,
  struct GNUNET_CRYPTO_EcdsaPublicKey *pub)
{
  BENCHMARK_START (ecdsa_key_get_public);
  crypto_scalarmult_ed25519_base_noclamp (pub->q_y, priv->d);
  BENCHMARK_END (ecdsa_key_get_public);
}


void
GNUNET_CRYPTO_eddsa_key_get_public (
  const struct GNUNET_CRYPTO_EddsaPrivateKey *priv,
  struct GNUNET_CRYPTO_EddsaPublicKey *pub)
{
  unsigned char pk[crypto_sign_PUBLICKEYBYTES];
  unsigned char sk[crypto_sign_SECRETKEYBYTES];

  BENCHMARK_START (eddsa_key_get_public);
  GNUNET_assert (0 == crypto_sign_seed_keypair (pk, sk, priv->d));
  GNUNET_memcpy (pub->q_y, pk, crypto_sign_PUBLICKEYBYTES);
  sodium_memzero (sk, crypto_sign_SECRETKEYBYTES);
  BENCHMARK_END (eddsa_key_get_public);
}


void
GNUNET_CRYPTO_ecdhe_key_get_public (
  const struct GNUNET_CRYPTO_EcdhePrivateKey *priv,
  struct GNUNET_CRYPTO_EcdhePublicKey *pub)
{
  BENCHMARK_START (ecdhe_key_get_public);
  GNUNET_assert (0 == crypto_scalarmult_base (pub->q_y, priv->d));
  BENCHMARK_END (ecdhe_key_get_public);
}


char *
GNUNET_CRYPTO_ecdsa_public_key_to_string (
  const struct GNUNET_CRYPTO_EcdsaPublicKey *pub)
{
  char *pubkeybuf;
  size_t keylen = (sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey)) * 8;
  char *end;

  if (keylen % 5 > 0)
    keylen += 5 - keylen % 5;
  keylen /= 5;
  pubkeybuf = GNUNET_malloc (keylen + 1);
  end =
    GNUNET_STRINGS_data_to_string ((unsigned char *) pub,
                                   sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey),
                                   pubkeybuf,
                                   keylen);
  if (NULL == end)
  {
    GNUNET_free (pubkeybuf);
    return NULL;
  }
  *end = '\0';
  return pubkeybuf;
}


char *
GNUNET_CRYPTO_eddsa_public_key_to_string (
  const struct GNUNET_CRYPTO_EddsaPublicKey *pub)
{
  char *pubkeybuf;
  size_t keylen = (sizeof(struct GNUNET_CRYPTO_EddsaPublicKey)) * 8;
  char *end;

  if (keylen % 5 > 0)
    keylen += 5 - keylen % 5;
  keylen /= 5;
  pubkeybuf = GNUNET_malloc (keylen + 1);
  end =
    GNUNET_STRINGS_data_to_string ((unsigned char *) pub,
                                   sizeof(struct GNUNET_CRYPTO_EddsaPublicKey),
                                   pubkeybuf,
                                   keylen);
  if (NULL == end)
  {
    GNUNET_free (pubkeybuf);
    return NULL;
  }
  *end = '\0';
  return pubkeybuf;
}


char *
GNUNET_CRYPTO_eddsa_private_key_to_string (
  const struct GNUNET_CRYPTO_EddsaPrivateKey *priv)
{
  char *privkeybuf;
  size_t keylen = (sizeof(struct GNUNET_CRYPTO_EddsaPrivateKey)) * 8;
  char *end;

  if (keylen % 5 > 0)
    keylen += 5 - keylen % 5;
  keylen /= 5;
  privkeybuf = GNUNET_malloc (keylen + 1);
  end = GNUNET_STRINGS_data_to_string ((unsigned char *) priv,
                                       sizeof(
                                         struct GNUNET_CRYPTO_EddsaPrivateKey),
                                       privkeybuf,
                                       keylen);
  if (NULL == end)
  {
    GNUNET_free (privkeybuf);
    return NULL;
  }
  *end = '\0';
  return privkeybuf;
}


char *
GNUNET_CRYPTO_ecdsa_private_key_to_string (
  const struct GNUNET_CRYPTO_EcdsaPrivateKey *priv)
{
  char *privkeybuf;
  size_t keylen = (sizeof(struct GNUNET_CRYPTO_EcdsaPrivateKey)) * 8;
  char *end;

  if (keylen % 5 > 0)
    keylen += 5 - keylen % 5;
  keylen /= 5;
  privkeybuf = GNUNET_malloc (keylen + 1);
  end = GNUNET_STRINGS_data_to_string ((unsigned char *) priv,
                                       sizeof(
                                         struct GNUNET_CRYPTO_EcdsaPrivateKey),
                                       privkeybuf,
                                       keylen);
  if (NULL == end)
  {
    GNUNET_free (privkeybuf);
    return NULL;
  }
  *end = '\0';
  return privkeybuf;
}


enum GNUNET_GenericReturnValue
GNUNET_CRYPTO_ecdsa_public_key_from_string (
  const char *enc,
  size_t enclen,
  struct GNUNET_CRYPTO_EcdsaPublicKey *pub)
{
  size_t keylen = (sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey)) * 8;

  if (keylen % 5 > 0)
    keylen += 5 - keylen % 5;
  keylen /= 5;
  if (enclen != keylen)
    return GNUNET_SYSERR;

  if (GNUNET_OK !=
      GNUNET_STRINGS_string_to_data (enc,
                                     enclen,
                                     pub,
                                     sizeof(
                                       struct GNUNET_CRYPTO_EcdsaPublicKey)))
    return GNUNET_SYSERR;
  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
GNUNET_CRYPTO_eddsa_public_key_from_string (
  const char *enc,
  size_t enclen,
  struct GNUNET_CRYPTO_EddsaPublicKey *pub)
{
  size_t keylen = (sizeof(struct GNUNET_CRYPTO_EddsaPublicKey)) * 8;

  if (keylen % 5 > 0)
    keylen += 5 - keylen % 5;
  keylen /= 5;
  if (enclen != keylen)
    return GNUNET_SYSERR;

  if (GNUNET_OK !=
      GNUNET_STRINGS_string_to_data (enc,
                                     enclen,
                                     pub,
                                     sizeof(
                                       struct GNUNET_CRYPTO_EddsaPublicKey)))
    return GNUNET_SYSERR;
  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
GNUNET_CRYPTO_eddsa_private_key_from_string (
  const char *enc,
  size_t enclen,
  struct GNUNET_CRYPTO_EddsaPrivateKey *priv)
{
  size_t keylen = (sizeof(struct GNUNET_CRYPTO_EddsaPrivateKey)) * 8;

  if (keylen % 5 > 0)
    keylen += 5 - keylen % 5;
  keylen /= 5;
  if (enclen != keylen)
    return GNUNET_SYSERR;

  if (GNUNET_OK !=
      GNUNET_STRINGS_string_to_data (enc,
                                     enclen,
                                     priv,
                                     sizeof(
                                       struct GNUNET_CRYPTO_EddsaPrivateKey)))
    return GNUNET_SYSERR;
#if CRYPTO_BUG
  if (GNUNET_OK != check_eddsa_key (priv))
  {
    GNUNET_break (0);
    return GNUNET_OK;
  }
#endif
  return GNUNET_OK;
}


static void
buffer_clear (void *buf, size_t len)
{
#if HAVE_MEMSET_S
  memset_s (buf, len, 0, len);
#elif HAVE_EXPLICIT_BZERO
  explicit_bzero (buf, len);
#else
  volatile unsigned char *p = buf;
  while (len--)
    *p++ = 0;
#endif
}


void
GNUNET_CRYPTO_ecdhe_key_clear (struct GNUNET_CRYPTO_EcdhePrivateKey *pk)
{
  buffer_clear (pk, sizeof(struct GNUNET_CRYPTO_EcdhePrivateKey));
}


void
GNUNET_CRYPTO_ecdsa_key_clear (struct GNUNET_CRYPTO_EcdsaPrivateKey *pk)
{
  buffer_clear (pk, sizeof(struct GNUNET_CRYPTO_EcdsaPrivateKey));
}


void
GNUNET_CRYPTO_eddsa_key_clear (struct GNUNET_CRYPTO_EddsaPrivateKey *pk)
{
  buffer_clear (pk, sizeof(struct GNUNET_CRYPTO_EddsaPrivateKey));
}


void
GNUNET_CRYPTO_ecdhe_key_create (struct GNUNET_CRYPTO_EcdhePrivateKey *pk)
{
  BENCHMARK_START (ecdhe_key_create);
  GNUNET_CRYPTO_random_block (pk,
                              sizeof (struct GNUNET_CRYPTO_EcdhePrivateKey));
  BENCHMARK_END (ecdhe_key_create);
}


void
GNUNET_CRYPTO_ecdsa_key_create (struct GNUNET_CRYPTO_EcdsaPrivateKey *pk)
{
  BENCHMARK_START (ecdsa_key_create);
  GNUNET_CRYPTO_random_block (pk,
                              sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
  pk->d[0] &= 248;
  pk->d[31] &= 127;
  pk->d[31] |= 64;

  BENCHMARK_END (ecdsa_key_create);
}


void
GNUNET_CRYPTO_eddsa_key_create (struct GNUNET_CRYPTO_EddsaPrivateKey *pk)
{
  BENCHMARK_START (eddsa_key_create);
  /*
   * We do not clamp for EdDSA, since all functions that use the private key do
   * their own clamping (just like in libsodium).  What we call "private key"
   * here, actually corresponds to the seed in libsodium.
   *
   * (Contrast this to ECDSA, where functions using the private key can't clamp
   * due to properties needed for GNS.  That is a worse/unsafter API, but
   * required for the GNS constructions to work.)
   */
  GNUNET_CRYPTO_random_block (pk,
                              sizeof (struct GNUNET_CRYPTO_EddsaPrivateKey));
  BENCHMARK_END (eddsa_key_create);
}


const struct GNUNET_CRYPTO_EcdsaPrivateKey *
GNUNET_CRYPTO_ecdsa_key_get_anonymous ()
{
  /**
   * 'anonymous' pseudonym (global static, d=1, public key = G
   * (generator).
   */
  static struct GNUNET_CRYPTO_EcdsaPrivateKey anonymous;
  static int once;

  if (once)
    return &anonymous;
  GNUNET_CRYPTO_mpi_print_unsigned (anonymous.d,
                                    sizeof(anonymous.d),
                                    GCRYMPI_CONST_ONE);
  anonymous.d[0] &= 248;
  anonymous.d[31] &= 127;
  anonymous.d[31] |= 64;

  once = 1;
  return &anonymous;
}


const struct GNUNET_CRYPTO_EddsaPrivateKey *
GNUNET_CRYPTO_eddsa_key_get_anonymous ()
{
  /**
   * 'anonymous' pseudonym (global static, d=1, public key = G
   * (generator).
   */
  static struct GNUNET_CRYPTO_EddsaPrivateKey anonymous;
  static int once;

  if (once)
    return &anonymous;

  memset (anonymous.d, 0, sizeof(anonymous.d));

  // Set the first byte to 1 (Little-Endian representation of 1)
  anonymous.d[0] = 1;

  once = 1;
  return &anonymous;
}


/**
 * Convert the data specified in the given purpose argument to an
 * S-expression suitable for signature operations.
 *
 * @param purpose data to convert
 * @return converted s-expression
 */
static gcry_sexp_t
data_to_ecdsa_value (const struct GNUNET_CRYPTO_SignaturePurpose *purpose)
{
  gcry_sexp_t data;
  int rc;
  /* Unlike EdDSA, libgcrypt expects a hash for ECDSA. */
  struct GNUNET_HashCode hc;

  GNUNET_CRYPTO_hash (purpose, ntohl (purpose->size), &hc);
  if (0 != (rc = gcry_sexp_build (&data,
                                  NULL,
                                  "(data(flags rfc6979)(hash %s %b))",
                                  "sha512",
                                  (int) sizeof(hc),
                                  &hc)))
  {
    LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_sexp_build", rc);
    return NULL;
  }
  return data;
}


enum GNUNET_GenericReturnValue
GNUNET_CRYPTO_ecdsa_sign_ (
  const struct GNUNET_CRYPTO_EcdsaPrivateKey *priv,
  const struct GNUNET_CRYPTO_SignaturePurpose *purpose,
  struct GNUNET_CRYPTO_EcdsaSignature *sig)
{
  gcry_sexp_t priv_sexp;
  gcry_sexp_t sig_sexp;
  gcry_sexp_t data;
  int rc;
  gcry_mpi_t rs[2];

  BENCHMARK_START (ecdsa_sign);

  priv_sexp = decode_private_ecdsa_key (priv);
  data = data_to_ecdsa_value (purpose);
  if (0 != (rc = gcry_pk_sign (&sig_sexp, data, priv_sexp)))
  {
    LOG (GNUNET_ERROR_TYPE_WARNING,
         _ ("ECC signing failed at %s:%d: %s\n"),
         __FILE__,
         __LINE__,
         gcry_strerror (rc));
    gcry_sexp_release (data);
    gcry_sexp_release (priv_sexp);
    return GNUNET_SYSERR;
  }
  gcry_sexp_release (priv_sexp);
  gcry_sexp_release (data);

  /* extract 'r' and 's' values from sexpression 'sig_sexp' and store in
     'signature' */
  if (0 != (rc = key_from_sexp (rs, sig_sexp, "sig-val", "rs")))
  {
    GNUNET_break (0);
    gcry_sexp_release (sig_sexp);
    return GNUNET_SYSERR;
  }
  gcry_sexp_release (sig_sexp);
  GNUNET_CRYPTO_mpi_print_unsigned (sig->r, sizeof(sig->r), rs[0]);
  GNUNET_CRYPTO_mpi_print_unsigned (sig->s, sizeof(sig->s), rs[1]);
  gcry_mpi_release (rs[0]);
  gcry_mpi_release (rs[1]);

  BENCHMARK_END (ecdsa_sign);

  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
GNUNET_CRYPTO_eddsa_sign_raw (
  const struct GNUNET_CRYPTO_EddsaPrivateKey *priv,
  void *data,
  size_t size,
  struct GNUNET_CRYPTO_EddsaSignature *sig)
{
  unsigned char sk[crypto_sign_SECRETKEYBYTES];
  unsigned char pk[crypto_sign_PUBLICKEYBYTES];
  int res;

  GNUNET_assert (0 == crypto_sign_seed_keypair (pk, sk, priv->d));
  res = crypto_sign_detached ((uint8_t *) sig,
                              NULL,
                              (uint8_t *) data,
                              size,
                              sk);
  return (res == 0) ? GNUNET_OK : GNUNET_SYSERR;
}


enum GNUNET_GenericReturnValue
GNUNET_CRYPTO_eddsa_sign_ (
  const struct GNUNET_CRYPTO_EddsaPrivateKey *priv,
  const struct GNUNET_CRYPTO_SignaturePurpose *purpose,
  struct GNUNET_CRYPTO_EddsaSignature *sig)
{

  size_t mlen = ntohl (purpose->size);
  unsigned char sk[crypto_sign_SECRETKEYBYTES];
  unsigned char pk[crypto_sign_PUBLICKEYBYTES];
  int res;

  BENCHMARK_START (eddsa_sign);
  GNUNET_assert (0 == crypto_sign_seed_keypair (pk, sk, priv->d));
  res = crypto_sign_detached ((uint8_t *) sig,
                              NULL,
                              (uint8_t *) purpose,
                              mlen,
                              sk);
  BENCHMARK_END (eddsa_sign);
  return (res == 0) ? GNUNET_OK : GNUNET_SYSERR;
}


enum GNUNET_GenericReturnValue
GNUNET_CRYPTO_ecdsa_verify_ (
  uint32_t purpose,
  const struct GNUNET_CRYPTO_SignaturePurpose *validate,
  const struct GNUNET_CRYPTO_EcdsaSignature *sig,
  const struct GNUNET_CRYPTO_EcdsaPublicKey *pub)
{
  gcry_sexp_t data;
  gcry_sexp_t sig_sexpr;
  gcry_sexp_t pub_sexpr;
  int rc;

  BENCHMARK_START (ecdsa_verify);

  if (purpose != ntohl (validate->purpose))
    return GNUNET_SYSERR; /* purpose mismatch */

  /* build s-expression for signature */
  if (0 != (rc = gcry_sexp_build (&sig_sexpr,
                                  NULL,
                                  "(sig-val(ecdsa(r %b)(s %b)))",
                                  (int) sizeof(sig->r),
                                  sig->r,
                                  (int) sizeof(sig->s),
                                  sig->s)))
  {
    LOG_GCRY (GNUNET_ERROR_TYPE_ERROR, "gcry_sexp_build", rc);
    return GNUNET_SYSERR;
  }
  data = data_to_ecdsa_value (validate);
  if (0 != (rc = gcry_sexp_build (&pub_sexpr,
                                  NULL,
                                  "(public-key(ecc(curve " CURVE ")(q %b)))",
                                  (int) sizeof(pub->q_y),
                                  pub->q_y)))
  {
    gcry_sexp_release (data);
    gcry_sexp_release (sig_sexpr);
    return GNUNET_SYSERR;
  }
  rc = gcry_pk_verify (sig_sexpr, data, pub_sexpr);
  gcry_sexp_release (pub_sexpr);
  gcry_sexp_release (data);
  gcry_sexp_release (sig_sexpr);
  if (0 != rc)
  {
    LOG (GNUNET_ERROR_TYPE_INFO,
         _ ("ECDSA signature verification failed at %s:%d: %s\n"),
         __FILE__,
         __LINE__,
         gcry_strerror (rc));
    BENCHMARK_END (ecdsa_verify);
    return GNUNET_SYSERR;
  }
  BENCHMARK_END (ecdsa_verify);
  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
GNUNET_CRYPTO_eddsa_verify_ (
  uint32_t purpose,
  const struct GNUNET_CRYPTO_SignaturePurpose *validate,
  const struct GNUNET_CRYPTO_EddsaSignature *sig,
  const struct GNUNET_CRYPTO_EddsaPublicKey *pub)
{
  const unsigned char *m = (const void *) validate;
  size_t mlen = ntohl (validate->size);
  const unsigned char *s = (const void *) sig;

  int res;

  if (purpose != ntohl (validate->purpose))
    return GNUNET_SYSERR; /* purpose mismatch */

  BENCHMARK_START (eddsa_verify);

  res = crypto_sign_verify_detached (s, m, mlen, pub->q_y);
  BENCHMARK_END (eddsa_verify);
  return (res == 0) ? GNUNET_OK : GNUNET_SYSERR;
}


enum GNUNET_GenericReturnValue
GNUNET_CRYPTO_ecc_ecdh (const struct GNUNET_CRYPTO_EcdhePrivateKey *priv,
                        const struct GNUNET_CRYPTO_EcdhePublicKey *pub,
                        struct GNUNET_HashCode *key_material)
{
  uint8_t p[crypto_scalarmult_BYTES];
  if (0 != crypto_scalarmult (p, priv->d, pub->q_y))
    return GNUNET_SYSERR;
  GNUNET_CRYPTO_hash (p, crypto_scalarmult_BYTES, key_material);
  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
GNUNET_CRYPTO_eddsa_ecdh (const struct GNUNET_CRYPTO_EddsaPrivateKey *priv,
                          const struct GNUNET_CRYPTO_EcdhePublicKey *pub,
                          struct GNUNET_HashCode *key_material)
{
  struct GNUNET_HashCode hc;
  uint8_t a[crypto_scalarmult_SCALARBYTES];
  uint8_t p[crypto_scalarmult_BYTES];

  GNUNET_CRYPTO_hash (priv,
                      sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey),
                      &hc);
  memcpy (a, &hc, sizeof (struct GNUNET_CRYPTO_EcdhePrivateKey));
  if (0 != crypto_scalarmult (p, a, pub->q_y))
    return GNUNET_SYSERR;
  GNUNET_CRYPTO_hash (p,
                      crypto_scalarmult_BYTES,
                      key_material);
  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
GNUNET_CRYPTO_x25519_ecdh (const struct GNUNET_CRYPTO_EcdhePrivateKey *sk,
                           const struct GNUNET_CRYPTO_EcdhePublicKey *pub,
                           struct GNUNET_CRYPTO_EcdhePublicKey *dh)
{
  uint64_t checkbyte = 0;
  size_t num_words = sizeof *dh / sizeof (uint64_t);
  if (0 != crypto_scalarmult_curve25519 (dh->q_y, sk->d, pub->q_y))
    return GNUNET_SYSERR;
  // We need to check if this is the all-zero value
  for (int i = 0; i < num_words; i++)
    checkbyte |= ((uint64_t*) dh)[i];
  return (0 == checkbyte) ? GNUNET_SYSERR : GNUNET_OK;
}


enum GNUNET_GenericReturnValue
GNUNET_CRYPTO_ecdh_x25519 (const struct GNUNET_CRYPTO_EcdhePrivateKey *sk,
                           const struct GNUNET_CRYPTO_EcdhePublicKey *pk,
                           struct GNUNET_CRYPTO_EcdhePublicKey *dh)
{
  uint64_t checkbyte = 0;
  size_t num_words = sizeof *dh / sizeof (uint64_t);
  if (0 != crypto_scalarmult_curve25519 (dh->q_y, sk->d, pk->q_y))
    return GNUNET_SYSERR;
  // We need to check if this is the all-zero value
  for (int i = 0; i < num_words; i++)
    checkbyte |= ((uint64_t*) dh)[i];
  if (0 == checkbyte)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "HPKE ECDH: X25519 all zero value!\n");
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
GNUNET_CRYPTO_ecdsa_ecdh (const struct GNUNET_CRYPTO_EcdsaPrivateKey *priv,
                          const struct GNUNET_CRYPTO_EcdhePublicKey *pub,
                          struct GNUNET_HashCode *key_material)
{
  uint8_t p[crypto_scalarmult_BYTES];

  BENCHMARK_START (ecdsa_ecdh);
  if (0 != crypto_scalarmult (p, priv->d, pub->q_y))
    return GNUNET_SYSERR;
  GNUNET_CRYPTO_hash (p,
                      crypto_scalarmult_BYTES,
                      key_material);
  BENCHMARK_END (ecdsa_ecdh);
  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
GNUNET_CRYPTO_ecdh_eddsa (const struct GNUNET_CRYPTO_EcdhePrivateKey *priv,
                          const struct GNUNET_CRYPTO_EddsaPublicKey *pub,
                          struct GNUNET_HashCode *key_material)
{
  uint8_t p[crypto_scalarmult_BYTES];
  uint8_t curve25510_pk[crypto_scalarmult_BYTES];

  if (0 != crypto_sign_ed25519_pk_to_curve25519 (curve25510_pk, pub->q_y))
    return GNUNET_SYSERR;
  if (0 != crypto_scalarmult (p, priv->d, curve25510_pk))
    return GNUNET_SYSERR;
  GNUNET_CRYPTO_hash (p, crypto_scalarmult_BYTES, key_material);
  return GNUNET_OK;
}


enum GNUNET_GenericReturnValue
GNUNET_CRYPTO_ecdh_ecdsa (const struct GNUNET_CRYPTO_EcdhePrivateKey *priv,
                          const struct GNUNET_CRYPTO_EcdsaPublicKey *pub,
                          struct GNUNET_HashCode *key_material)
{
  uint8_t p[crypto_scalarmult_BYTES];
  uint8_t curve25510_pk[crypto_scalarmult_BYTES];

  if (0 != crypto_sign_ed25519_pk_to_curve25519 (curve25510_pk, pub->q_y))
    return GNUNET_SYSERR;
  if (0 != crypto_scalarmult (p, priv->d, curve25510_pk))
    return GNUNET_SYSERR;
  GNUNET_CRYPTO_hash (p, crypto_scalarmult_BYTES, key_material);
  return GNUNET_OK;
}


/* end of crypto_ecc.c */
