// Copyright 2025 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <openssl/xwing.h>

#include <openssl/bytestring.h>
#include <openssl/curve25519.h>
#include <openssl/mlkem.h>
#include <openssl/rand.h>

#include "../fipsmodule/bcm_interface.h"
#include "../fipsmodule/keccak/internal.h"


using namespace bssl;

struct private_key {
  MLKEM768_private_key mlkem_private_key;
  uint8_t x25519_private_key[32];
  uint8_t seed[XWING_PRIVATE_KEY_BYTES];
};

static_assert(sizeof(XWING_private_key) == sizeof(private_key));
static_assert(alignof(XWING_private_key) == alignof(private_key));

static const private_key *private_key_from_external(
    const XWING_private_key *external) {
  return reinterpret_cast<const private_key *>(external);
}
static private_key *private_key_from_external(XWING_private_key *external) {
  return reinterpret_cast<private_key *>(external);
}

static void xwing_expand_private_key(private_key *inout_private_key) {
  BORINGSSL_keccak_st context;
  BORINGSSL_keccak_init(&context, boringssl_shake256);
  BORINGSSL_keccak_absorb(&context, inout_private_key->seed,
                          sizeof(inout_private_key->seed));

  // ML-KEM-768
  uint8_t mlkem_seed[64];
  BORINGSSL_keccak_squeeze(&context, mlkem_seed, sizeof(mlkem_seed));
  MLKEM768_private_key_from_seed(&inout_private_key->mlkem_private_key,
                                 mlkem_seed, sizeof(mlkem_seed));

  // X25519
  BORINGSSL_keccak_squeeze(&context, inout_private_key->x25519_private_key,
                           sizeof(inout_private_key->x25519_private_key));
}

static int xwing_parse_private_key(private_key *out_private_key, CBS *in) {
  if (!CBS_copy_bytes(in, out_private_key->seed,
                      sizeof(out_private_key->seed))) {
    return 0;
  }

  xwing_expand_private_key(out_private_key);
  return 1;
}

static int xwing_marshal_private_key(CBB *out, const private_key *private_key) {
  return CBB_add_bytes(out, private_key->seed, sizeof(private_key->seed));
}

static int xwing_public_from_private(
    uint8_t out_encoded_public_key[XWING_PUBLIC_KEY_BYTES],
    const private_key *private_key) {
  CBB cbb;
  if (!CBB_init_fixed(&cbb, out_encoded_public_key, XWING_PUBLIC_KEY_BYTES)) {
    return 0;
  }

  // ML-KEM-768
  MLKEM768_public_key mlkem_public_key;
  MLKEM768_public_from_private(&mlkem_public_key,
                               &private_key->mlkem_private_key);

  if (!MLKEM768_marshal_public_key(&cbb, &mlkem_public_key)) {
    return 0;
  }

  // X25519
  uint8_t *buf;
  if (!CBB_add_space(&cbb, &buf, 32)) {
    return 0;
  }
  X25519_public_from_private(buf, private_key->x25519_private_key);

  if (CBB_len(&cbb) != XWING_PUBLIC_KEY_BYTES) {
    return 0;
  }
  return 1;
}

static void xwing_combiner(
    uint8_t out_shared_secret[XWING_SHARED_SECRET_BYTES],
    const uint8_t mlkem_shared_secret[MLKEM_SHARED_SECRET_BYTES],
    const uint8_t x25519_shared_secret[32], const uint8_t x25519_ciphertext[32],
    const uint8_t x25519_public_key[32]) {
  BORINGSSL_keccak_st context;
  BORINGSSL_keccak_init(&context, boringssl_sha3_256);

  BORINGSSL_keccak_absorb(&context, mlkem_shared_secret,
                          MLKEM_SHARED_SECRET_BYTES);
  BORINGSSL_keccak_absorb(&context, x25519_shared_secret, 32);
  BORINGSSL_keccak_absorb(&context, x25519_ciphertext, 32);
  BORINGSSL_keccak_absorb(&context, x25519_public_key, 32);

  uint8_t xwing_label[6] = {0x5c, 0x2e, 0x2f, 0x2f, 0x5e, 0x5c};
  BORINGSSL_keccak_absorb(&context, xwing_label, sizeof(xwing_label));

  BORINGSSL_keccak_squeeze(&context, out_shared_secret,
                           XWING_SHARED_SECRET_BYTES);
}

// Public API.

int XWING_parse_private_key(struct XWING_private_key *out_private_key,
                            CBS *in) {
  if (!xwing_parse_private_key(private_key_from_external(out_private_key),
                               in) ||
      CBS_len(in) != 0) {
    return 0;
  }
  return 1;
}

int XWING_marshal_private_key(CBB *out,
                              const struct XWING_private_key *private_key) {
  return xwing_marshal_private_key(out, private_key_from_external(private_key));
}

int XWING_generate_key(uint8_t out_encoded_public_key[XWING_PUBLIC_KEY_BYTES],
                       struct XWING_private_key *out_private_key) {
  private_key *private_key = private_key_from_external(out_private_key);
  RAND_bytes(private_key->seed, sizeof(private_key->seed));

  xwing_expand_private_key(private_key);

  return XWING_public_from_private(out_encoded_public_key, out_private_key);
}

int XWING_public_from_private(
    uint8_t out_encoded_public_key[XWING_PUBLIC_KEY_BYTES],
    const struct XWING_private_key *private_key) {
  return xwing_public_from_private(out_encoded_public_key,
                                   private_key_from_external(private_key));
}

int XWING_encap(uint8_t out_ciphertext[XWING_CIPHERTEXT_BYTES],
                uint8_t out_shared_secret[XWING_SHARED_SECRET_BYTES],
                const uint8_t encoded_public_key[XWING_PUBLIC_KEY_BYTES]) {
  uint8_t eseed[64];
  RAND_bytes(eseed, sizeof(eseed));

  return XWING_encap_external_entropy(out_ciphertext, out_shared_secret,
                                      encoded_public_key, eseed);
}

int XWING_encap_external_entropy(
    uint8_t out_ciphertext[XWING_CIPHERTEXT_BYTES],
    uint8_t out_shared_secret[XWING_SHARED_SECRET_BYTES],
    const uint8_t encoded_public_key[XWING_PUBLIC_KEY_BYTES],
    const uint8_t eseed[64]) {
  // X25519
  static_assert(XWING_PUBLIC_KEY_BYTES >= MLKEM768_PUBLIC_KEY_BYTES + 32);
  const uint8_t *x25519_public_key =
      encoded_public_key + MLKEM768_PUBLIC_KEY_BYTES;
  const uint8_t *x25519_ephemeral_private_key = eseed + 32;
  uint8_t *x25519_ciphertext = out_ciphertext + MLKEM768_CIPHERTEXT_BYTES;
  X25519_public_from_private(x25519_ciphertext, x25519_ephemeral_private_key);

  uint8_t x25519_shared_secret[32];
  if (!X25519(x25519_shared_secret, x25519_ephemeral_private_key,
              x25519_public_key)) {
    return 0;
  }

  // ML-KEM-768
  CBS mlkem_cbs;
  static_assert(MLKEM768_PUBLIC_KEY_BYTES <= XWING_PUBLIC_KEY_BYTES);
  CBS_init(&mlkem_cbs, encoded_public_key, MLKEM768_PUBLIC_KEY_BYTES);

  MLKEM768_public_key mlkem_public_key;
  if (!MLKEM768_parse_public_key(&mlkem_public_key, &mlkem_cbs)) {
    return 0;
  }

  uint8_t *mlkem_ciphertext = out_ciphertext;
  uint8_t mlkem_shared_secret[MLKEM_SHARED_SECRET_BYTES];
  BCM_mlkem768_encap_external_entropy(mlkem_ciphertext, mlkem_shared_secret,
                                      &mlkem_public_key, eseed);

  // Combine the shared secrets
  xwing_combiner(out_shared_secret, mlkem_shared_secret, x25519_shared_secret,
                 x25519_ciphertext, x25519_public_key);
  return 1;
}

static int xwing_decap(uint8_t out_shared_secret[XWING_SHARED_SECRET_BYTES],
                       const uint8_t ciphertext[XWING_CIPHERTEXT_BYTES],
                       const private_key *private_key) {
  static_assert(XWING_CIPHERTEXT_BYTES >= MLKEM768_CIPHERTEXT_BYTES + 32);
  const uint8_t *mlkem_ciphertext = ciphertext;
  const uint8_t *x25519_ciphertext = ciphertext + MLKEM768_CIPHERTEXT_BYTES;

  // ML-KEM-768
  uint8_t mlkem_shared_secret[MLKEM_SHARED_SECRET_BYTES];
  if (!MLKEM768_decap(mlkem_shared_secret, mlkem_ciphertext,
                      MLKEM768_CIPHERTEXT_BYTES,
                      &private_key->mlkem_private_key)) {
    goto err;
  }

  // X25519
  uint8_t x25519_public_key[32];
  X25519_public_from_private(x25519_public_key,
                             private_key->x25519_private_key);

  uint8_t x25519_shared_secret[32];
  if (!X25519(x25519_shared_secret, private_key->x25519_private_key,
              x25519_ciphertext)) {
    goto err;
  }

  // Combine the shared secrets
  xwing_combiner(out_shared_secret, mlkem_shared_secret, x25519_shared_secret,
                 x25519_ciphertext, x25519_public_key);
  return 1;

err:
  // In case of error, fill the shared secret with random bytes so that if the
  // caller forgets to check the return code:
  // - no intermediate information leaks,
  // - the shared secret is unpredictable, so for example any data encrypted
  //   with it wouldn't be trivially decryptable by an attacker.
  RAND_bytes(out_shared_secret, XWING_SHARED_SECRET_BYTES);
  return 0;
}

int XWING_decap(uint8_t out_shared_secret[XWING_SHARED_SECRET_BYTES],
                const uint8_t ciphertext[XWING_CIPHERTEXT_BYTES],
                const struct XWING_private_key *private_key) {
  return xwing_decap(out_shared_secret, ciphertext,
                     private_key_from_external(private_key));
}
