/*
  This file is part of TALER
  Copyright (C) 2022 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_contract.c
 * @brief functions for encrypting and decrypting contracts for P2P payments
 * @author Christian Grothoff <christian@grothoff.org>
 */
#include "platform.h"
#include "taler_util.h"
#include <zlib.h>
#include "taler_exchange_service.h"


/**
 * Different types of contracts supported.
 */
enum ContractFormats
{
  /**
   * The encrypted contract represents a payment offer. The receiver
   * can merge it into a reserve/account to accept the contract and
   * obtain the payment.
   */
  TALER_EXCHANGE_CONTRACT_PAYMENT_OFFER = 0,

  /**
   * The encrypted contract represents a payment request.
   */
  TALER_EXCHANGE_CONTRACT_PAYMENT_REQUEST = 1
};


/**
 * Nonce used for encryption, 24 bytes.
 */
struct NonceP
{
  uint8_t nonce[crypto_secretbox_NONCEBYTES];
};

/**
 * Specifies a key used for symmetric encryption, 32 bytes.
 */
struct SymKeyP
{
  uint32_t key[8];
};


/**
 * Compute @a key.
 *
 * @param key_material key for calculation
 * @param key_m_len length of key
 * @param nonce nonce for calculation
 * @param salt salt value for calculation
 * @param[out] key where to write the en-/description key
 */
static void
derive_key (const void *key_material,
            size_t key_m_len,
            const struct NonceP *nonce,
            const char *salt,
            struct SymKeyP *key)
{
  GNUNET_assert (GNUNET_YES ==
                 GNUNET_CRYPTO_kdf (key,
                                    sizeof (*key),
                                    /* salt / XTS */
                                    nonce,
                                    sizeof (*nonce),
                                    /* ikm */
                                    key_material,
                                    key_m_len,
                                    /* info chunks */
                                    /* The "salt" passed here is actually not something random,
                                       but a protocol-specific identifier string.  Thus
                                       we pass it as a context info to the HKDF */
                                    salt,
                                    strlen (salt),
                                    NULL,
                                    0));
}


/**
 * Encryption of data.
 *
 * @param nonce value to use for the nonce
 * @param key key which is used to derive a key/iv pair from
 * @param key_len length of key
 * @param data data to encrypt
 * @param data_size size of the data
 * @param salt salt value which is used for key derivation
 * @param[out] res ciphertext output
 * @param[out] res_size size of the ciphertext
 */
static void
blob_encrypt (const struct NonceP *nonce,
              const void *key,
              size_t key_len,
              const void *data,
              size_t data_size,
              const char *salt,
              void **res,
              size_t *res_size)
{
  size_t ciphertext_size;
  struct SymKeyP skey;

  derive_key (key,
              key_len,
              nonce,
              salt,
              &skey);
  ciphertext_size = crypto_secretbox_NONCEBYTES
                    + crypto_secretbox_MACBYTES
                    + data_size;
  *res_size = ciphertext_size;
  *res = GNUNET_malloc (ciphertext_size);
  GNUNET_memcpy (*res,
                 nonce,
                 crypto_secretbox_NONCEBYTES);
  GNUNET_assert (0 ==
                 crypto_secretbox_easy (*res + crypto_secretbox_NONCEBYTES,
                                        data,
                                        data_size,
                                        (void *) nonce,
                                        (void *) &skey));
}


/**
 * Decryption of data like encrypted recovery document etc.
 *
 * @param key key which is used to derive a key/iv pair from
 * @param key_len length of key
 * @param data data to decrypt
 * @param data_size size of the data
 * @param salt salt value which is used for key derivation
 * @param[out] res plaintext output
 * @param[out] res_size size of the plaintext
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
blob_decrypt (const void *key,
              size_t key_len,
              const void *data,
              size_t data_size,
              const char *salt,
              void **res,
              size_t *res_size)
{
  const struct NonceP *nonce;
  struct SymKeyP skey;
  size_t plaintext_size;

  if (data_size < crypto_secretbox_NONCEBYTES + crypto_secretbox_MACBYTES)
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  nonce = data;
  derive_key (key,
              key_len,
              nonce,
              salt,
              &skey);
  plaintext_size = data_size - (crypto_secretbox_NONCEBYTES
                                + crypto_secretbox_MACBYTES);
  *res = GNUNET_malloc (plaintext_size);
  *res_size = plaintext_size;
  if (0 != crypto_secretbox_open_easy (*res,
                                       data + crypto_secretbox_NONCEBYTES,
                                       data_size - crypto_secretbox_NONCEBYTES,
                                       (void *) nonce,
                                       (void *) &skey))
  {
    GNUNET_break (0);
    GNUNET_free (*res);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Header for encrypted contracts.
 */
struct ContractHeaderP
{
  /**
   * Type of the contract, in NBO.
   */
  uint32_t ctype;

  /**
   * Length of the encrypted contract, in NBO.
   */
  uint32_t clen;
};


/**
 * Header for encrypted contracts.
 */
struct ContractHeaderMergeP
{
  /**
   * Generic header.
   */
  struct ContractHeaderP header;

  /**
   * Private key with the merge capability.
   */
  struct TALER_PurseMergePrivateKeyP merge_priv;
};


/**
 * Salt we use when encrypting contracts for merge.
 */
#define MERGE_SALT "p2p-merge-contract"


void
TALER_CRYPTO_contract_encrypt_for_merge (
  const struct TALER_PurseContractPublicKeyP *purse_pub,
  const struct TALER_ContractDiffiePrivateP *contract_priv,
  const struct TALER_PurseMergePrivateKeyP *merge_priv,
  const json_t *contract_terms,
  void **econtract,
  size_t *econtract_size)
{
  struct GNUNET_HashCode key;
  char *cstr;
  size_t clen;
  void *xbuf;
  struct ContractHeaderMergeP *hdr;
  struct NonceP nonce;
  uLongf cbuf_size;
  int ret;

  GNUNET_assert (GNUNET_OK ==
                 GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv,
                                           &purse_pub->eddsa_pub,
                                           &key));
  cstr = json_dumps (contract_terms,
                     JSON_COMPACT | JSON_SORT_KEYS);
  clen = strlen (cstr);
  cbuf_size = compressBound (clen);
  xbuf = GNUNET_malloc (cbuf_size);
  ret = compress (xbuf,
                  &cbuf_size,
                  (const Bytef *) cstr,
                  clen);
  GNUNET_assert (Z_OK == ret);
  free (cstr);
  hdr = GNUNET_malloc (sizeof (*hdr) + cbuf_size);
  hdr->header.ctype = htonl (TALER_EXCHANGE_CONTRACT_PAYMENT_OFFER);
  hdr->header.clen = htonl ((uint32_t) clen);
  hdr->merge_priv = *merge_priv;
  GNUNET_memcpy (&hdr[1],
                 xbuf,
                 cbuf_size);
  GNUNET_free (xbuf);
  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
                              &nonce,
                              sizeof (nonce));
  blob_encrypt (&nonce,
                &key,
                sizeof (key),
                hdr,
                sizeof (*hdr) + cbuf_size,
                MERGE_SALT,
                econtract,
                econtract_size);
  GNUNET_free (hdr);
}


json_t *
TALER_CRYPTO_contract_decrypt_for_merge (
  const struct TALER_ContractDiffiePrivateP *contract_priv,
  const struct TALER_PurseContractPublicKeyP *purse_pub,
  const void *econtract,
  size_t econtract_size,
  struct TALER_PurseMergePrivateKeyP *merge_priv)
{
  struct GNUNET_HashCode key;
  void *xhdr;
  size_t hdr_size;
  const struct ContractHeaderMergeP *hdr;
  char *cstr;
  uLongf clen;
  json_error_t json_error;
  json_t *ret;

  if (GNUNET_OK !=
      GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv,
                                &purse_pub->eddsa_pub,
                                &key))
  {
    GNUNET_break (0);
    return NULL;
  }
  if (GNUNET_OK !=
      blob_decrypt (&key,
                    sizeof (key),
                    econtract,
                    econtract_size,
                    MERGE_SALT,
                    &xhdr,
                    &hdr_size))
  {
    GNUNET_break_op (0);
    return NULL;
  }
  if (hdr_size < sizeof (*hdr))
  {
    GNUNET_break_op (0);
    GNUNET_free (xhdr);
    return NULL;
  }
  hdr = xhdr;
  if (TALER_EXCHANGE_CONTRACT_PAYMENT_OFFER != ntohl (hdr->header.ctype))
  {
    GNUNET_break_op (0);
    GNUNET_free (xhdr);
    return NULL;
  }
  clen = ntohl (hdr->header.clen);
  if (clen >= GNUNET_MAX_MALLOC_CHECKED)
  {
    GNUNET_break_op (0);
    GNUNET_free (xhdr);
    return NULL;
  }
  cstr = GNUNET_malloc (clen + 1);
  if (Z_OK !=
      uncompress ((Bytef *) cstr,
                  &clen,
                  (const Bytef *) &hdr[1],
                  hdr_size - sizeof (*hdr)))
  {
    GNUNET_break_op (0);
    GNUNET_free (cstr);
    GNUNET_free (xhdr);
    return NULL;
  }
  *merge_priv = hdr->merge_priv;
  GNUNET_free (xhdr);
  ret = json_loadb ((char *) cstr,
                    clen,
                    JSON_DECODE_ANY,
                    &json_error);
  if (NULL == ret)
  {
    GNUNET_break_op (0);
    GNUNET_free (cstr);
    return NULL;
  }
  GNUNET_free (cstr);
  return ret;
}


/**
 * Salt we use when encrypting contracts for merge.
 */
#define DEPOSIT_SALT "p2p-deposit-contract"


void
TALER_CRYPTO_contract_encrypt_for_deposit (
  const struct TALER_PurseContractPublicKeyP *purse_pub,
  const struct TALER_ContractDiffiePrivateP *contract_priv,
  const json_t *contract_terms,
  void **econtract,
  size_t *econtract_size)
{
  struct GNUNET_HashCode key;
  char *cstr;
  size_t clen;
  void *xbuf;
  struct ContractHeaderP *hdr;
  struct NonceP nonce;
  uLongf cbuf_size;
  int ret;
  void *xecontract;
  size_t xecontract_size;

  GNUNET_assert (GNUNET_OK ==
                 GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv,
                                           &purse_pub->eddsa_pub,
                                           &key));
  cstr = json_dumps (contract_terms,
                     JSON_COMPACT | JSON_SORT_KEYS);
  GNUNET_assert (NULL != cstr);
  clen = strlen (cstr);
  cbuf_size = compressBound (clen);
  xbuf = GNUNET_malloc (cbuf_size);
  ret = compress (xbuf,
                  &cbuf_size,
                  (const Bytef *) cstr,
                  clen);
  GNUNET_assert (Z_OK == ret);
  free (cstr);
  hdr = GNUNET_malloc (sizeof (*hdr) + cbuf_size);
  hdr->ctype = htonl (TALER_EXCHANGE_CONTRACT_PAYMENT_REQUEST);
  hdr->clen = htonl ((uint32_t) clen);
  GNUNET_memcpy (&hdr[1],
                 xbuf,
                 cbuf_size);
  GNUNET_free (xbuf);
  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
                              &nonce,
                              sizeof (nonce));
  blob_encrypt (&nonce,
                &key,
                sizeof (key),
                hdr,
                sizeof (*hdr) + cbuf_size,
                DEPOSIT_SALT,
                &xecontract,
                &xecontract_size);
  GNUNET_free (hdr);
  /* prepend purse_pub */
  *econtract = GNUNET_malloc (xecontract_size + sizeof (*purse_pub));
  GNUNET_memcpy (*econtract,
                 purse_pub,
                 sizeof (*purse_pub));
  GNUNET_memcpy (sizeof (*purse_pub) + *econtract,
                 xecontract,
                 xecontract_size);
  *econtract_size = xecontract_size + sizeof (*purse_pub);
  GNUNET_free (xecontract);
}


json_t *
TALER_CRYPTO_contract_decrypt_for_deposit (
  const struct TALER_ContractDiffiePrivateP *contract_priv,
  const void *econtract,
  size_t econtract_size)
{
  const struct TALER_PurseContractPublicKeyP *purse_pub = econtract;
  struct GNUNET_HashCode key;
  void *xhdr;
  size_t hdr_size;
  const struct ContractHeaderP *hdr;
  char *cstr;
  uLongf clen;
  json_error_t json_error;
  json_t *ret;

  if (econtract_size < sizeof (*purse_pub))
  {
    GNUNET_break_op (0);
    return NULL;
  }
  if (GNUNET_OK !=
      GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv,
                                &purse_pub->eddsa_pub,
                                &key))
  {
    GNUNET_break (0);
    return NULL;
  }
  econtract += sizeof (*purse_pub);
  econtract_size -= sizeof (*purse_pub);
  if (GNUNET_OK !=
      blob_decrypt (&key,
                    sizeof (key),
                    econtract,
                    econtract_size,
                    DEPOSIT_SALT,
                    &xhdr,
                    &hdr_size))
  {
    GNUNET_break_op (0);
    return NULL;
  }
  if (hdr_size < sizeof (*hdr))
  {
    GNUNET_break_op (0);
    GNUNET_free (xhdr);
    return NULL;
  }
  hdr = xhdr;
  if (TALER_EXCHANGE_CONTRACT_PAYMENT_REQUEST != ntohl (hdr->ctype))
  {
    GNUNET_break_op (0);
    GNUNET_free (xhdr);
    return NULL;
  }
  clen = ntohl (hdr->clen);
  if (clen >= GNUNET_MAX_MALLOC_CHECKED)
  {
    GNUNET_break_op (0);
    GNUNET_free (xhdr);
    return NULL;
  }
  cstr = GNUNET_malloc (clen + 1);
  if (Z_OK !=
      uncompress ((Bytef *) cstr,
                  &clen,
                  (const Bytef *) &hdr[1],
                  hdr_size - sizeof (*hdr)))
  {
    GNUNET_break_op (0);
    GNUNET_free (cstr);
    GNUNET_free (xhdr);
    return NULL;
  }
  GNUNET_free (xhdr);
  ret = json_loadb ((char *) cstr,
                    clen,
                    JSON_DECODE_ANY,
                    &json_error);
  if (NULL == ret)
  {
    GNUNET_break_op (0);
    GNUNET_free (cstr);
    return NULL;
  }
  GNUNET_free (cstr);
  return ret;
}


/**
 * Salt we use when encrypting KYC attributes.
 */
#define ATTRIBUTE_SALT "kyc-attributes"


void
TALER_CRYPTO_kyc_attributes_encrypt (
  const struct TALER_AttributeEncryptionKeyP *key,
  const json_t *attr,
  void **enc_attr,
  size_t *enc_attr_size)
{
  uLongf cbuf_size;
  char *cstr;
  uLongf clen;
  void *xbuf;
  int ret;
  uint32_t belen;
  struct NonceP nonce;

  cstr = json_dumps (attr,
                     JSON_COMPACT | JSON_SORT_KEYS);
  GNUNET_assert (NULL != cstr);
  clen = strlen (cstr);
  GNUNET_assert (clen <= UINT32_MAX);
  cbuf_size = compressBound (clen);
  xbuf = GNUNET_malloc (cbuf_size + sizeof (uint32_t));
  belen = htonl ((uint32_t) clen);
  GNUNET_memcpy (xbuf,
                 &belen,
                 sizeof (belen));
  ret = compress (xbuf + 4,
                  &cbuf_size,
                  (const Bytef *) cstr,
                  clen);
  GNUNET_assert (Z_OK == ret);
  free (cstr);
  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
                              &nonce,
                              sizeof (nonce));
  blob_encrypt (&nonce,
                key,
                sizeof (*key),
                xbuf,
                cbuf_size + sizeof (uint32_t),
                ATTRIBUTE_SALT,
                enc_attr,
                enc_attr_size);
  GNUNET_free (xbuf);
}


json_t *
TALER_CRYPTO_kyc_attributes_decrypt (
  const struct TALER_AttributeEncryptionKeyP *key,
  const void *enc_attr,
  size_t enc_attr_size)
{
  void *xhdr;
  size_t hdr_size;
  char *cstr;
  uLongf clen;
  json_error_t json_error;
  json_t *ret;
  uint32_t belen;

  if (GNUNET_OK !=
      blob_decrypt (key,
                    sizeof (*key),
                    enc_attr,
                    enc_attr_size,
                    ATTRIBUTE_SALT,
                    &xhdr,
                    &hdr_size))
  {
    GNUNET_break_op (0);
    return NULL;
  }
  GNUNET_memcpy (&belen,
                 xhdr,
                 sizeof (belen));
  clen = ntohl (belen);
  if (clen >= GNUNET_MAX_MALLOC_CHECKED)
  {
    GNUNET_break_op (0);
    GNUNET_free (xhdr);
    return NULL;
  }
  cstr = GNUNET_malloc (clen + 1);
  if (Z_OK !=
      uncompress ((Bytef *) cstr,
                  &clen,
                  (const Bytef *) (xhdr + sizeof (uint32_t)),
                  hdr_size - sizeof (uint32_t)))
  {
    GNUNET_break_op (0);
    GNUNET_free (cstr);
    GNUNET_free (xhdr);
    return NULL;
  }
  GNUNET_free (xhdr);
  ret = json_loadb ((char *) cstr,
                    clen,
                    JSON_DECODE_ANY,
                    &json_error);
  if (NULL == ret)
  {
    GNUNET_break_op (0);
    GNUNET_free (cstr);
    return NULL;
  }
  GNUNET_free (cstr);
  return ret;
}
