// Copyright 2018-2026 the Deno authors. MIT license.

use base64::prelude::BASE64_URL_SAFE_NO_PAD;
use curve25519_dalek::montgomery::MontgomeryPoint;
use elliptic_curve::subtle::ConstantTimeEq;
use rand::RngCore;
use rand::rngs::OsRng;
use spki::der::Encode;
use spki::der::asn1::BitString;

#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum X25519Error {
  #[class("DOMExceptionOperationError")]
  #[error("Failed to export key")]
  FailedExport,
  #[class("DOMExceptionDataError")]
  #[error("Invalid key data")]
  InvalidKeyLength,
  #[class(generic)]
  #[error(transparent)]
  Der(#[from] spki::der::Error),
}
// u-coordinate of the base point.
const X25519_BASEPOINT_BYTES: [u8; 32] = [
  9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0,
];
/// Rust-callable wrapper for [`op_crypto_generate_x25519_keypair`].
pub fn generate_x25519_keypair(pkey: &mut [u8], pubkey: &mut [u8]) {
  let mut rng = OsRng;
  rng.fill_bytes(pkey);
  let pkey: [u8; 32] = pkey.try_into().expect("Expected byteLength 32");
  pubkey.copy_from_slice(&x25519_dalek::x25519(pkey, X25519_BASEPOINT_BYTES));
}

pub(crate) fn x25519_public_key(private_key: &[u8]) -> String {
  use base64::Engine;
  let private_key: [u8; 32] =
    private_key.try_into().expect("Expected byteLength 32");
  BASE64_URL_SAFE_NO_PAD
    .encode(x25519_dalek::x25519(private_key, X25519_BASEPOINT_BYTES))
}

const MONTGOMERY_IDENTITY: MontgomeryPoint = MontgomeryPoint([0; 32]);

/// Compute the X25519 shared secret from a raw 32-byte private key
/// `k` and 32-byte peer public key `u`, writing into `secret`. Returns
/// `Ok(true)` if the result is the Montgomery identity (low-order
/// point), in which case the caller must reject. Called from
/// [`crate::subtle_derive_bits::run`].
pub(crate) fn x25519_derive_bits(
  k: &[u8],
  u: &[u8],
  secret: &mut [u8],
) -> Result<bool, X25519Error> {
  let k: [u8; 32] = k.try_into().map_err(|_| X25519Error::InvalidKeyLength)?;
  let u: [u8; 32] = u.try_into().map_err(|_| X25519Error::InvalidKeyLength)?;
  let sh_sec = x25519_dalek::x25519(k, u);
  let point = MontgomeryPoint(sh_sec);
  if point.ct_eq(&MONTGOMERY_IDENTITY).unwrap_u8() == 1 {
    return Ok(true);
  }
  secret.copy_from_slice(&sh_sec);
  Ok(false)
}

// id-X25519 OBJECT IDENTIFIER ::= { 1 3 101 110 }
#[allow(dead_code, reason = "used by node_interop and subtle_import_key")]
pub const X25519_OID: const_oid::ObjectIdentifier =
  const_oid::ObjectIdentifier::new_unwrap("1.3.101.110");

pub(crate) fn export_spki_x25519(
  pubkey: &[u8],
) -> Result<Vec<u8>, X25519Error> {
  let key_info = spki::SubjectPublicKeyInfo {
    algorithm: spki::AlgorithmIdentifierRef {
      oid: X25519_OID,
      parameters: None,
    },
    subject_public_key: BitString::from_bytes(pubkey)?,
  };
  key_info.to_der().map_err(|_| X25519Error::FailedExport)
}

pub(crate) fn export_pkcs8_x25519(pkey: &[u8]) -> Result<Vec<u8>, X25519Error> {
  use rsa::pkcs1::der::Encode;
  let pk_info = rsa::pkcs8::PrivateKeyInfo {
    public_key: None,
    algorithm: rsa::pkcs8::AlgorithmIdentifierRef {
      oid: X25519_OID,
      parameters: None,
    },
    private_key: pkey,
  };
  let mut buf = Vec::new();
  pk_info.encode_to_vec(&mut buf)?;
  Ok(buf)
}
