Ascon Hash Digest

using System;
using System.Runtime.CompilerServices;
using Algorithms.Crypto.Utils;

namespace Algorithms.Crypto.Digests;

/// <summary>
/// Implements the Ascon cryptographic hash algorithm, providing both the standard Ascon-Hash and the Ascon-HashA variants.
/// </summary>
/// <remarks>
/// The <see cref="AsconDigest"/> class implements the Ascon hash function, a lightweight cryptographic algorithm designed for
/// resource-constrained environments such as IoT devices. It provides two variants:
/// <list type="bullet">
/// <item>
/// <description>
/// <see cref="AsconParameters.AsconHash"/>: The standard Ascon-Hash variant with 12 rounds of the permutation function for enhanced security.
/// </description>
/// </item>
/// <item>
/// <description>
/// <see cref="AsconParameters.AsconHashA"/>: A performance-optimized variant with 8 rounds of the permutation function, offering a trade-off between security and performance.
/// </description>
/// </item>
/// </list>
/// <br />
/// The AsconDigest processes data in 8-byte blocks, accumulating input until a block is complete, at which point it applies
/// the permutation function to update the internal state. After all data has been processed, the hash value can be finalized
/// and retrieved.
/// <br />
/// Ascon was designed to meet the requirements of lightweight cryptography, making it ideal for devices with limited computational power.
/// </remarks>
public class AsconDigest : IDigest
{
    public enum AsconParameters
    {
        /// <summary>
        /// Represents the Ascon Hash variant, the standard cryptographic hashing function of the Ascon family.
        /// </summary>
        /// <remarks>
        /// AsconHash is the primary hashing algorithm in the Ascon family. It is designed for efficiency and security
        /// in resource-constrained environments, such as IoT devices, and provides high resistance to cryptanalytic attacks.
        /// This variant uses 12 rounds of the permutation function for increased security.
        /// </remarks>
        AsconHash,

        /// <summary>
        /// Represents the Ascon HashA variant, an alternative variant of the Ascon hashing function with fewer permutation rounds.
        /// </summary>
        /// <remarks>
        /// AsconHashA is a variant of the Ascon hashing function that uses fewer rounds (8 rounds) of the permutation function,
        /// trading off some security for improved performance in specific scenarios. It is still designed to be secure for many
        /// applications, but it operates faster in environments where computational resources are limited.
        /// </remarks>
        AsconHashA,
    }

    /// <summary>
    /// Specifies the Ascon variant being used (either Ascon-Hash or Ascon-HashA). This defines the cryptographic algorithm's behavior.
    /// </summary>
    private readonly AsconParameters asconParameters;

    /// <summary>
    /// The number of permutation rounds applied in the Ascon cryptographic process. This is determined by the selected Ascon variant.
    /// </summary>
    private readonly int asconPbRounds;

    /// <summary>
    /// Internal buffer that temporarily stores input data before it is processed in 8-byte blocks. The buffer is cleared after each block is processed.
    /// </summary>
    private readonly byte[] buffer = new byte[8];

    /// <summary>
    /// Internal state variable <c>x0</c> used in the cryptographic permutation function. This is updated continuously as input data is processed.
    /// </summary>
    private ulong x0;

    /// <summary>
    /// Internal state variable <c>x1</c> used in the cryptographic permutation function. This, along with other state variables, is updated during each round.
    /// </summary>
    private ulong x1;

    /// <summary>
    /// Internal state variable <c>x2</c> used in the cryptographic permutation function. It helps track the evolving state of the digest.
    /// </summary>
    private ulong x2;

    /// <summary>
    /// Internal state variable <c>x3</c> used in the cryptographic permutation function, contributing to the mixing and non-linearity of the state.
    /// </summary>
    private ulong x3;

    /// <summary>
    /// Internal state variable <c>x4</c> used in the cryptographic permutation function. This, along with <c>x0</c> to <c>x3</c>, ensures cryptographic security.
    /// </summary>
    private ulong x4;

    /// <summary>
    /// Tracks the current position within the <c>buffer</c> array. When <c>bufferPosition</c> reaches 8, the buffer is processed and reset.
    /// </summary>
    private int bufferPosition;

    /// <summary>
    /// Initializes a new instance of the <see cref="AsconDigest"/> class with the specified Ascon parameters.
    /// </summary>
    /// <param name="parameters">The Ascon variant to use, either <see cref="AsconParameters.AsconHash"/> or <see cref="AsconParameters.AsconHashA"/>.</param>
    /// <remarks>
    /// This constructor sets up the digest by selecting the appropriate number of permutation rounds based on the Ascon variant.
    /// <list type="bullet">
    /// <item><description>For <see cref="AsconParameters.AsconHash"/>, 12 permutation rounds are used.</description></item>
    /// <item><description>For <see cref="AsconParameters.AsconHashA"/>, 8 permutation rounds are used.</description></item>
    /// </list>
    /// If an unsupported parameter is provided, the constructor throws an <see cref="ArgumentException"/> to indicate that the parameter is invalid.
    /// The internal state of the digest is then reset to prepare for processing input data.
    /// </remarks>
    /// <exception cref="ArgumentException">Thrown when an invalid parameter setting is provided for Ascon Hash.</exception>
    public AsconDigest(AsconParameters parameters)
    {
        // Set the Ascon parameter (AsconHash or AsconHashA) for this instance.
        asconParameters = parameters;

        // Determine the number of permutation rounds based on the Ascon variant.
        asconPbRounds = parameters switch
        {
            AsconParameters.AsconHash => 12,  // 12 rounds for Ascon-Hash variant.
            AsconParameters.AsconHashA => 8,  // 8 rounds for Ascon-HashA variant.
            _ => throw new ArgumentException("Invalid parameter settings for Ascon Hash"), // Throw exception for invalid parameter.
        };

        // Reset the internal state to prepare for new input.
        Reset();
    }

    /// <summary>
    /// Gets the name of the cryptographic algorithm based on the selected Ascon parameter.
    /// </summary>
    /// <value>
    /// A string representing the name of the algorithm variant, either "Ascon-Hash" or "Ascon-HashA".
    /// </value>
    /// <remarks>
    /// This property determines the algorithm name based on the selected Ascon variant when the instance was initialized.
    /// It supports two variants:
    /// <list type="bullet">
    /// <item><description>"Ascon-Hash" for the <see cref="AsconParameters.AsconHash"/> variant.</description></item>
    /// <item><description>"Ascon-HashA" for the <see cref="AsconParameters.AsconHashA"/> variant.</description></item>
    /// </list>
    /// If an unsupported or unknown parameter is used, the property throws an <see cref="InvalidOperationException"/>.
    /// </remarks>
    /// <exception cref="InvalidOperationException">Thrown if an unknown Ascon parameter is encountered.</exception>
    public string AlgorithmName
    {
        get
        {
            return asconParameters switch
            {
                AsconParameters.AsconHash => "Ascon-Hash",  // Return "Ascon-Hash" for AsconHash variant.
                AsconParameters.AsconHashA => "Ascon-HashA", // Return "Ascon-HashA" for AsconHashA variant.
                _ => throw new InvalidOperationException(), // Throw an exception for unknown Ascon parameters.
            };
        }
    }

    /// <summary>
    /// Gets the size of the resulting hash produced by the digest, in bytes.
    /// </summary>
    /// <returns>The size of the hash, which is 32 bytes (256 bits) for this digest implementation.</returns>
    /// <remarks>
    /// This method returns the fixed size of the hash output produced by the digest algorithm. In this implementation,
    /// the digest produces a 256-bit hash, which corresponds to 32 bytes. This is typical for cryptographic hash functions
    /// that aim to provide a high level of security by generating a large output size.
    /// </remarks>
    public int GetDigestSize() => 32;

    /// <summary>
    /// Gets the internal block size of the digest in bytes.
    /// </summary>
    /// <returns>The internal block size of the digest, which is 8 bytes (64 bits).</returns>
    /// <remarks>
    /// This method returns the block size that the digest algorithm uses when processing input data. The input is processed
    /// in chunks (blocks) of 8 bytes at a time. This block size determines how the input data is split and processed in multiple
    /// steps before producing the final hash.
    /// </remarks>
    public int GetByteLength() => 8;

    /// <summary>
    /// Updates the cryptographic state by processing a single byte of input and adding it to the internal buffer.
    /// </summary>
    /// <param name="input">The byte to be added to the internal buffer and processed.</param>
    /// <remarks>
    /// This method collects input bytes in an internal buffer. Once the buffer is filled (reaching 8 bytes), the buffer is processed
    /// by converting it into a 64-bit unsigned integer in big-endian format and XORing it with the internal state variable <c>x0</c>.
    /// After processing the buffer, the permutation function <see cref="P(int)"/> is applied to mix the internal state, and the buffer position is reset to zero.
    /// <br/><br/>
    /// If the buffer has not yet reached 8 bytes, the method simply adds the input byte to the buffer and waits for further input.
    /// </remarks>
    public void Update(byte input)
    {
        // Add the input byte to the buffer.
        buffer[bufferPosition] = input;

        // If the buffer is not full (less than 8 bytes), increment the buffer position and return early.
        if (++bufferPosition != 8)
        {
            return; // Wait for more input to fill the buffer before processing.
        }

        // Once the buffer is full (8 bytes), convert the buffer to a 64-bit integer (big-endian) and XOR it with the state.
        x0 ^= ByteEncodingUtils.BigEndianToUint64(buffer, 0);

        // Apply the permutation function to mix the state.
        P(asconPbRounds);

        // Reset the buffer position for the next block of input.
        bufferPosition = 0;
    }

    /// <summary>
    /// Updates the cryptographic state by processing a segment of input data from a byte array, starting at a specified offset and length.
    /// </summary>
    /// <param name="input">The byte array containing the input data to be processed.</param>
    /// <param name="inOff">The offset in the input array where processing should begin.</param>
    /// <param name="inLen">The number of bytes from the input array to process.</param>
    /// <remarks>
    /// This method ensures that the input data is valid by checking the array length, starting from the provided offset,
    /// and making sure it is long enough to accommodate the specified length. It then processes the data by converting
    /// the relevant section of the byte array to a <see cref="ReadOnlySpan{T}"/> and delegating the actual block update to
    /// the <see cref="BlockUpdate(ReadOnlySpan{byte})"/> method for further processing.
    /// </remarks>
    /// <exception cref="ArgumentException">
    /// Thrown if the input data is too short, starting from <paramref name="inOff"/> and for the length <paramref name="inLen"/>.
    /// </exception>
    public void BlockUpdate(byte[] input, int inOff, int inLen)
    {
        // Validate the input data to ensure there is enough data to process from the specified offset and length.
        ValidationUtils.CheckDataLength(input, inOff, inLen, "input buffer too short");

        // Convert the input byte array into a ReadOnlySpan<byte> and delegate the processing to the span-based method.
        BlockUpdate(input.AsSpan(inOff, inLen));
    }

    /// <summary>
    /// Processes the input data by updating the internal cryptographic state, handling both partial and full blocks.
    /// </summary>
    /// <param name="input">A read-only span of bytes representing the input data to be processed.</param>
    /// <remarks>
    /// This method processes the input data in chunks of 8 bytes. It manages the internal buffer to accumulate data
    /// until there are enough bytes to process a full 8-byte block. When the buffer is full or enough input is provided,
    /// it XORs the buffered data with the internal state variable <c>x0</c> and applies the permutation function
    /// <see cref="P"/> to update the cryptographic state.
    /// <br/><br/>
    /// If the input contains more than 8 bytes, the method continues to process full 8-byte blocks in a loop until
    /// the input is exhausted. Any remaining bytes (less than 8) are stored in the internal buffer for future processing.
    /// </remarks>
    public void BlockUpdate(ReadOnlySpan<byte> input)
    {
        // Calculate the number of available bytes left in the buffer before it reaches 8 bytes.
        var available = 8 - bufferPosition;

        // If the input length is smaller than the remaining space in the buffer, copy the input into the buffer.
        if (input.Length < available)
        {
            input.CopyTo(buffer.AsSpan(bufferPosition)); // Copy the small input into the buffer.
            bufferPosition += input.Length; // Update the buffer position.
            return; // Return early since we don't have enough data to process a full block.
        }

        // If there is data in the buffer, but it isn't full, fill it and process the full 8-byte block.
        if (bufferPosition > 0)
        {
            // Copy enough bytes from the input to complete the buffer.
            input[..available].CopyTo(buffer.AsSpan(bufferPosition));

            // XOR the full buffer with the internal state (x0) and apply the permutation.
            x0 ^= ByteEncodingUtils.BigEndianToUint64(buffer);
            P(asconPbRounds); // Apply the permutation rounds.

            // Update the input to exclude the bytes we've already processed from the buffer.
            input = input[available..];
        }

        // Process full 8-byte blocks directly from the input.
        while (input.Length >= 8)
        {
            // XOR the next 8-byte block from the input with the internal state and apply the permutation.
            x0 ^= ByteEncodingUtils.BigEndianToUint64(input);
            P(asconPbRounds);

            // Move to the next 8-byte chunk in the input.
            input = input[8..];
        }

        // Copy any remaining bytes (less than 8) into the buffer to store for future processing.
        input.CopyTo(buffer);
        bufferPosition = input.Length; // Update the buffer position to reflect the remaining unprocessed data.
    }

    /// <summary>
    /// Finalizes the cryptographic hash computation, absorbing any remaining data, applying the final permutation,
    /// and writing the resulting hash to the specified position in the provided output byte array.
    /// </summary>
    /// <param name="output">The byte array where the final 32-byte hash will be written.</param>
    /// <param name="outOff">The offset in the output array at which to start writing the hash.</param>
    /// <returns>The size of the hash (32 bytes).</returns>
    /// <remarks>
    /// This method finalizes the hash computation by converting the output array to a <see cref="Span{T}"/> and
    /// calling the <see cref="DoFinal(Span{byte})"/> method. It provides flexibility in placing the result in an
    /// existing byte array with a specified offset.
    /// </remarks>
    /// <exception cref="ArgumentException">Thrown if the output buffer is too small to hold the resulting hash.</exception>
    public int DoFinal(byte[] output, int outOff)
    {
        // Call the Span-based DoFinal method with the output byte array and offset.
        return DoFinal(output.AsSpan(outOff));
    }

    /// <summary>
    /// Finalizes the cryptographic hash computation, absorbing any remaining data, applying the final permutation, and
    /// writing the resulting hash to the provided output buffer.
    /// </summary>
    /// <param name="output">A span of bytes where the final 32-byte hash will be written.</param>
    /// <returns>The size of the hash (32 bytes).</returns>
    /// <remarks>
    /// This method completes the hash computation by absorbing any remaining input data, applying the final permutation,
    /// and extracting the state variables to produce the final hash. The method processes the state in 8-byte chunks,
    /// writing the result into the output buffer in big-endian format. After the final permutation is applied, the internal
    /// state is reset to prepare for a new hashing session.
    /// </remarks>
    /// <exception cref="ArgumentException">Thrown if the output buffer is too small to hold the resulting hash.</exception>
    public int DoFinal(Span<byte> output)
    {
        // Validate that the output buffer is at least 32 bytes in length.
        ValidationUtils.CheckOutputLength(output, 32, "output buffer too short");

        // Absorb any remaining input and apply the final permutation.
        AbsorbAndFinish();

        // Convert the first part of the state (x0) to big-endian format and write it to the output.
        ByteEncodingUtils.UInt64ToBigEndian(x0, output);

        // Loop to process the remaining parts of the internal state (x1, x2, etc.).
        for (var i = 0; i < 3; ++i)
        {
            // Move to the next 8-byte segment in the output buffer.
            output = output[8..];

            // Apply the permutation rounds to mix the state.
            P(asconPbRounds);

            // Convert the updated state variable (x0) to big-endian format and write it to the output.
            ByteEncodingUtils.UInt64ToBigEndian(x0, output);
        }

        // Reset the internal state for the next hash computation.
        Reset();

        // Return the size of the hash (32 bytes).
        return 32;
    }

    /// <summary>
    /// Computes the cryptographic hash of the input byte array and returns the result as a lowercase hexadecimal string.
    /// </summary>
    /// <param name="input">The input byte array to be hashed.</param>
    /// <returns>A string containing the computed hash in lowercase hexadecimal format.</returns>
    /// <remarks>
    /// This method takes a byte array as input, processes it to compute the Ascon hash, and returns the result as a hexadecimal string.
    /// It internally converts the byte array to a <see cref="Span{T}"/> and delegates the actual hashing to the
    /// <see cref="Digest(Span{byte})"/> method.
    /// </remarks>
    public string Digest(byte[] input)
    {
        return Digest(input.AsSpan());
    }

    /// <summary>
    /// Computes the cryptographic hash of the input span of bytes and returns the result as a lowercase hexadecimal string.
    /// </summary>
    /// <param name="input">A span of bytes representing the input data to be hashed.</param>
    /// <returns>A string containing the computed hash in lowercase hexadecimal format.</returns>
    /// <remarks>
    /// This method processes the input span using the Ascon cryptographic algorithm to compute the hash. It accumulates
    /// the input, applies the necessary permutations and internal state updates, and finally produces a hash in the form
    /// of a 32-byte array. The result is then converted into a lowercase hexadecimal string using <see cref="BitConverter"/>.
    /// </remarks>
    public string Digest(Span<byte> input)
    {
        // Update the internal state with the input data.
        BlockUpdate(input);

        // Create an array to hold the final hash output (32 bytes).
        var output = new byte[GetDigestSize()];

        // Finalize the hash computation and store the result in the output array.
        DoFinal(output, 0);

        // Convert the hash (byte array) to a lowercase hexadecimal string.
        return BitConverter.ToString(output).Replace("-", string.Empty).ToLowerInvariant();
    }

    /// <summary>
    /// Resets the internal state of the Ascon cryptographic hash algorithm to its initial state based on the selected variant.
    /// </summary>
    /// <remarks>
    /// This method clears the internal buffer and resets the buffer position to zero. Depending on the specified
    /// Ascon variant (<see cref="AsconParameters.AsconHash"/> or <see cref="AsconParameters.AsconHashA"/>), it also reinitializes
    /// the internal state variables (<c>x0</c>, <c>x1</c>, <c>x2</c>, <c>x3</c>, <c>x4</c>) to their starting values.
    /// <br/><br/>
    /// The reset is necessary to prepare the hash function for a new message. It ensures that previous messages do not
    /// affect the new one and that the internal state is consistent with the algorithm’s specification for the selected variant.
    /// </remarks>
    public void Reset()
    {
        // Clear the buffer to remove any leftover data from previous operations.
        Array.Clear(buffer, 0, buffer.Length);

        // Reset the buffer position to zero to start processing fresh input.
        bufferPosition = 0;

        // Initialize the internal state variables (x0, x1, x2, x3, x4) based on the selected Ascon variant.
        switch (asconParameters)
        {
            // If using the AsconHashA variant, set the specific initial state values for x0 through x4.
            case AsconParameters.AsconHashA:
                x0 = 92044056785660070UL;
                x1 = 8326807761760157607UL;
                x2 = 3371194088139667532UL;
                x3 = 15489749720654559101UL;
                x4 = 11618234402860862855UL;
                break;

            // If using the AsconHash variant, set the specific initial state values for x0 through x4.
            case AsconParameters.AsconHash:
                x0 = 17191252062196199485UL;
                x1 = 10066134719181819906UL;
                x2 = 13009371945472744034UL;
                x3 = 4834782570098516968UL;
                x4 = 3787428097924915520UL;
                break;

            // If an unknown Ascon variant is encountered, throw an exception.
            default:
                throw new InvalidOperationException();
        }
    }

    /// <summary>
    /// Finalizes the absorption phase of the cryptographic hash by padding the buffer and applying the final permutation round.
    /// </summary>
    /// <remarks>
    /// This method is called when the input data has been fully absorbed into the internal state, and it needs to be finalized.
    /// The buffer is padded with a specific value (0x80) to signify the end of the data, and the remaining portion of the buffer is
    /// XORed with the internal state variable <c>x0</c>. After padding, the final permutation round is applied using 12 rounds of
    /// the permutation function <see cref="P(int)"/>. This ensures the internal state is fully mixed and the cryptographic hash
    /// is securely finalized.
    /// </remarks>
    private void AbsorbAndFinish()
    {
        // Pad the buffer with 0x80 to indicate the end of the data.
        buffer[bufferPosition] = 0x80;

        // XOR the buffer (after padding) with the internal state x0, but only the relevant portion of the buffer is considered.
        // The (56 - (bufferPosition << 3)) shifts ensure that only the unprocessed part of the buffer is XORed into x0.
        x0 ^= ByteEncodingUtils.BigEndianToUint64(buffer, 0) & (ulong.MaxValue << (56 - (bufferPosition << 3)));

        // Apply 12 rounds of the permutation function to fully mix and finalize the internal state.
        P(12);
    }

    /// <summary>
    /// Executes the cryptographic permutation function by applying a sequence of rounds that transform the internal state variables.
    /// </summary>
    /// <param name="numberOfRounds">
    /// The number of rounds to execute. If set to 12, additional rounds are performed with specific constants to enhance the security of the transformation.
    /// </param>
    /// <remarks>
    /// In the Ascon cryptographic algorithm, the permutation function <c>P</c> transforms the internal state over multiple rounds.
    /// This method applies a set of round constants, each of which alters the state variables (<c>x0</c>, <c>x1</c>, <c>x2</c>, <c>x3</c>, <c>x4</c>) differently,
    /// ensuring that the transformation introduces non-linearity and diffusion, which are essential for cryptographic security.
    /// <br/><br/>
    /// When <paramref name="numberOfRounds"/> is set to 12, the method first applies four unique round constants.
    /// Afterward, it applies a fixed set of six additional constants regardless of the number of rounds.
    /// </remarks>
    private void P(int numberOfRounds)
    {
        if (numberOfRounds == 12)
        {
            Round(0xf0UL);
            Round(0xe1UL);
            Round(0xd2UL);
            Round(0xc3UL);
        }

        Round(0xb4UL);
        Round(0xa5UL);

        Round(0x96UL);
        Round(0x87UL);
        Round(0x78UL);
        Round(0x69UL);
        Round(0x5aUL);
        Round(0x4bUL);
    }

    /// <summary>
    /// Executes a single round of the cryptographic permutation function, transforming the internal state
    /// variables <c>x0</c>, <c>x1</c>, <c>x2</c>, <c>x3</c>, and <c>x4</c> using XOR, AND, and NOT operations, along with circular bit rotations.
    /// This function is designed to introduce diffusion and non-linearity into the state for cryptographic security.
    /// </summary>
    /// <param name="circles">
    /// A 64-bit unsigned integer constant that influences the round's transformation. Each round uses a unique value of this constant
    /// to ensure that the transformation applied to the state differs for each round.
    /// </param>
    /// <remarks>
    /// The <c>Round</c> function uses a series of bitwise operations (XOR, AND, NOT) and circular bit rotations to mix
    /// the internal state. Each transformation step introduces non-linearity and ensures that small changes in the input or state
    /// variables propagate widely across the internal state, enhancing the security of the cryptographic process.
    /// <br/><br/>
    /// The round constant (<paramref name="circles"/>) plays a crucial role in altering the state at each round, ensuring
    /// that each round contributes uniquely to the overall cryptographic transformation. Circular rotations are applied using
    /// <see cref="LongUtils.RotateRight(ulong, int)"/> to spread bits throughout the 64-bit word.
    /// </remarks>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void Round(ulong circles)
    {
        // Step 1: Perform XOR and AND operations to mix inputs and state variables
        var t0 = x0 ^ x1 ^ x2 ^ x3 ^ circles ^ (x1 & (x0 ^ x2 ^ x4 ^ circles));
        var t1 = x0 ^ x2 ^ x3 ^ x4 ^ circles ^ ((x1 ^ x2 ^ circles) & (x1 ^ x3));
        var t2 = x1 ^ x2 ^ x4 ^ circles ^ (x3 & x4);
        var t3 = x0 ^ x1 ^ x2 ^ circles ^ (~x0 & (x3 ^ x4));
        var t4 = x1 ^ x3 ^ x4 ^ ((x0 ^ x4) & x1);

        // Step 2: Apply circular right shifts and update the internal state variables
        x0 = t0 ^ LongUtils.RotateRight(t0, 19) ^ LongUtils.RotateRight(t0, 28);
        x1 = t1 ^ LongUtils.RotateRight(t1, 39) ^ LongUtils.RotateRight(t1, 61);
        x2 = ~(t2 ^ LongUtils.RotateRight(t2, 1) ^ LongUtils.RotateRight(t2, 6));
        x3 = t3 ^ LongUtils.RotateRight(t3, 10) ^ LongUtils.RotateRight(t3, 17);
        x4 = t4 ^ LongUtils.RotateRight(t4, 7) ^ LongUtils.RotateRight(t4, 41);
    }
}