<?php

namespace app\models;

/**
 * Perform cryptographic operations using the ED25519 cryptographic method.
 * 
 * @author  Gareth Palmer <gareth@one-space.co.za>
 */

final class ED25519 {

    /**
     * The secret key for use in cryptographic operations.
     * 
     * @var string  $secretKey
     * 
     * @readonly
     * @access  private
     */

    private readonly string $secretKey;

    /**
     * The public key for use in cryptographic operations.
     * 
     * @var string $publicKey
     * 
     * @readonly
     * @access  private
     */

    private readonly string $publicKey;


    /**
     * Generate a public and secret key for use in cryptographic operations.
     * 
     * @access  public
     */

    public function generateKeyPair(): void {
        $keyPair = sodium_crypto_sign_keypair();
        $this->secretKey = sodium_crypto_sign_secretkey($keyPair);
        $this->publicKey = sodium_crypto_sign_publickey($keyPair);
    }


    /**
     * Create a signature to attach to a string to prove it's authenticity.
     * 
     * @param   string  $data       The raw message to be signed.
     * @param   string  $secretKey  The private or secret key used to sign a message.
     * 
     * @return  string  The generated signature.
     * 
     * @access  public
     */

    public function sign(string $data, string $secretKey): string {
        $signature = sodium_crypto_sign_detached($data, $secretKey);
        return base64_encode($signature);
    }


    /**
     * Verify if a signature is valid as sent with the signature.
     * 
     * @param   string  $data       The raw string which is to be checked.
     * @param   string  $signature  The signature which should prove the 
     *                              message is valid.
     * @param   string  $publicKey  The public key which is attached to the 
     *                              secret key used to sign the message.
     * 
     * @return  bool
     * 
     * @access  public
     */

    public function verify(string $data, string $signature, string $publicKey): bool {
        $signature = base64_decode($signature);
        return sodium_crypto_sign_verify_detached($signature, $data, $publicKey);
    }


    /**
     * Encrypt a raw message into an encypted string.
     * 
     * @param   string  $data               The raw string to be encypted.
     * @param   string  $sharedSecretKey    The public key which can be used to decrypt the message.
     * 
     * @return  string
     * 
     * @access  public
     */

    public function encrypt(string $data, string $sharedSecretKey): string {
        $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
        $ciphertext = sodium_crypto_secretbox($data, $nonce, $sharedSecretKey);
        return base64_encode($nonce . $ciphertext);
    }


    /**
     * Decrypt an encypted string into it's origonal form.
     * 
     * @param   string  $encryptedData      The encypted message.
     * @param   string  $sharedSecretKey    The public key which can be used to decrypt the message.
     * 
     * @return  string|false    False if unable to decrypt the message.
     * 
     * @access  public
     */

    public function decrypt(string $encryptedData, string $sharedSecretKey): string|false {
        $decoded = base64_decode($encryptedData);
        $nonce = substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
        $ciphertext = substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
        $decrypted = sodium_crypto_secretbox_open($ciphertext, $nonce, $sharedSecretKey);
        return $decrypted;
    }


    /**
     * Returns the public (shared secret) key generated by the object.
     * 
     * @return  string
     * 
     * @access  public
     */

    public function getPublicKey(): string {
        return $this->publicKey;
    }


    /**
     * Returns the secret key generated by the object.
     * 
     * @return  string
     * 
     * @access  public
     */

    public function getSecretKey(): string {
        return $this->secretKey;
    }
}
