diff --git a/Algorithms.Tests/Crypto/Digests/AsconDigestTests.cs b/Algorithms.Tests/Crypto/Digests/AsconDigestTests.cs new file mode 100644 index 00000000..bd83f2dd --- /dev/null +++ b/Algorithms.Tests/Crypto/Digests/AsconDigestTests.cs @@ -0,0 +1,266 @@ +using System; +using System.Text; +using Algorithms.Crypto.Digests; +using Algorithms.Crypto.Exceptions; +using FluentAssertions; +using NUnit.Framework; + +namespace Algorithms.Tests.Crypto.Digests; + +[NonParallelizable] +public class AsconDigestTests +{ + private readonly AsconDigest asconHash = new AsconDigest(AsconDigest.AsconParameters.AsconHash); + private readonly AsconDigest asconHashA = new AsconDigest(AsconDigest.AsconParameters.AsconHashA); + + [TestCase("a", "02a9d471afab12914197af7090f00d16c41b6e30be0a63bbfd00bc13064de548")] + [TestCase("abc", "d37fe9f1d10dbcfad8408a6804dbe91124a8912693322bb23ec1701e19e3fd51")] + [TestCase("Hello", "d80f38d94ad72bd18718879f753a44870e8446925ff64bd7441db5fe020b6c0c")] + [TestCase("message digest", "e8848979c5adfd21bfcf29e54be1dd085ee523d251e8e6876f2654d6368da0ca")] + [TestCase("abcdefghijklmnopqrstuvwxyz", "c62368674e1b2301f19f46c50bb7f87a988a3e41205d68ab9d7882d2a15e917b")] + [TestCase("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "4ff71928d740524735b5ab12bb1598463054f88089f3c5f9760b6bdcd23f897b")] + [TestCase("12345678901234567890123456789012345678901234567890123456789012345678901234567890", "2dae8b553b93841120e88ee77b9ccb8b512a32318db6012025f3f1c482b1def8")] + public void AsconHash_ReturnsCorrectValue(string input, string expected) + { + var inputBytes = Encoding.ASCII.GetBytes(input); + var result = asconHash.Digest(inputBytes); + + result.Should().Be(expected); + } + + [TestCase("a", "062bb0346671da00da4f460308b4d2c4d9877c3e2827d6229ff5361332d36527")] + [TestCase("abc", "836a5ddba0142b011ce3425ea9789fd6a21628d619195a48c1540f847667a84e")] + [TestCase("Hello", "15f245df8af697dc540e86083822809ab7299575d8ad6c2e17ecc603a7ab79dd")] + [TestCase("message digest", "3f18a1f398a40a77e0e9477aa6cb50e9e1abecff651c1874f9717c02c8a165ba")] + [TestCase("abcdefghijklmnopqrstuvwxyz", "406b809260f361e12dcf0bf924bfe1ffd2f987fc18d90b94fc544ff80dc2946b")] + [TestCase("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "5c6c69ff3ee83361391b7236c8eb6718f52df43de5a61a4f4d2819d40430dc19")] + [TestCase("12345678901234567890123456789012345678901234567890123456789012345678901234567890", "d8e38fc50d682550cd176decda61adb7fd1c793cdafa825f17f3a002d65847be")] + public void AsconHashA_ReturnsCorrectValue(string input, string expected) + { + var inputBytes = Encoding.ASCII.GetBytes(input); + var result = asconHashA.Digest(inputBytes); + + result.Should().Be(expected); + } + + [Test] + public void BlockUpdate_WithValidOffsetAndLength_ShouldProcessCorrectly() + { + // Arrange + var input = new byte[] { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99 }; + var offset = 2; + var length = 6; // Picking 6 bytes starting from offset 2 + + // Act + var act = () => asconHash.BlockUpdate(input, offset, length); + + // Assert + act.Should().NotThrow(); // Ensure no exceptions are thrown during processing + + // Finalize the hash and check the output size + var output = new byte[asconHash.GetDigestSize()]; + asconHash.DoFinal(output, 0); + output.Should().HaveCount(32); // Ascon hash size is 32 bytes + } + + [Test] + public void BlockUpdate_WithInvalidOffset_ShouldThrowDataLengthException() + { + // Arrange + var input = new byte[] { 0x00, 0x11, 0x22, 0x33 }; + var offset = 3; // Offset goes too close to the end + var length = 3; // Length would exceed buffer size + + // Act + var act = () => asconHash.BlockUpdate(input, offset, length); + + // Assert + act.Should().Throw() + .WithMessage("input buffer too short"); + } + + [Test] + public void BlockUpdate_WithInvalidLength_ShouldThrowDataLengthException() + { + // Arrange + var input = new byte[] { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 }; + var offset = 1; // Valid offset + var length = 10; // Invalid length (exceeds buffer) + + // Act + var act = () => asconHash.BlockUpdate(input, offset, length); + + // Assert + act.Should().Throw() + .WithMessage("input buffer too short"); + } + + [Test] + public void BlockUpdate_WithPartialBlock_ShouldProcessCorrectly() + { + // Arrange + var input = new byte[] { 0x00, 0x11, 0x22, 0x33, 0x44 }; + var offset = 0; + var length = 5; // Less than 8 bytes, partial block + + // Act + asconHash.BlockUpdate(input, offset, length); + + // Assert + var output = new byte[asconHash.GetDigestSize()]; + asconHash.DoFinal(output, 0); + output.Should().HaveCount(32); // Ensure valid hash output + } + + [Test] + public void BlockUpdate_WithFullBlock_ShouldProcessCorrectly() + { + // Arrange + var input = new byte[] { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 }; + var offset = 0; + var length = 8; // Full block + + // Act + asconHash.BlockUpdate(input, offset, length); + + // Assert + var output = new byte[asconHash.GetDigestSize()]; + asconHash.DoFinal(output, 0); + output.Should().HaveCount(32); // Ensure valid hash output + } + + [Test] + public void BlockUpdate_MultipleCalls_ShouldProcessCorrectly() + { + // Arrange + var input1 = new byte[] { 0x00, 0x11, 0x22 }; + var input2 = new byte[] { 0x33, 0x44, 0x55, 0x66, 0x77 }; + + // Act + asconHash.BlockUpdate(input1, 0, input1.Length); + asconHash.BlockUpdate(input2, 0, input2.Length); + + // Assert + var output = new byte[asconHash.GetDigestSize()]; + asconHash.DoFinal(output, 0); + output.Should().HaveCount(32); // Ensure valid hash output + } + + [Test] + public void AsconHash_WhenGetNameIsCalled_ReturnsCorrectValue() + { + asconHash.AlgorithmName.Should().Be("Ascon-Hash"); + asconHashA.AlgorithmName.Should().Be("Ascon-HashA"); + } + + [Test] + public void AsconHash_WhenGetByteLengthIsCalled_ReturnsCorrectValue() + { + asconHash.GetByteLength().Should().Be(8); + } + + [Test] + public void Update_ShouldProcessByte_WhenBufferIsFull() + { + // Arrange + byte[] inputBytes = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 }; // 8 bytes to fill the buffer + + // Act + foreach (var input in inputBytes) + { + asconHashA.Update(input); + } + + // Assert + // Since the buffer is full after 8 updates, we expect the state to have been processed. + var output = new byte[asconHashA.GetDigestSize()]; + asconHashA.DoFinal(output, 0); + output.Should().HaveCount(32); // Ascon hash size is 32 bytes + } + + [Test] + public void Update_ShouldNotProcess_WhenBufferIsNotFull() + { + // Arrange + byte[] inputBytes = { 0x00, 0x11, 0x22, 0x33 }; // Only 4 bytes (buffer is not full) + + // Act + foreach (var input in inputBytes) + { + asconHashA.Update(input); + } + + // Assert + // Even though the buffer has received input, it should not process until it is full (8 bytes). + // We can check that DoFinal still completes, but the buffer has not been processed yet. + var output = new byte[asconHashA.GetDigestSize()]; + asconHashA.DoFinal(output, 0); + output.Should().HaveCount(32); // Ensure valid hash output + } + + [Test] + public void Update_ShouldProcessMultipleBlocks() + { + // Arrange + var inputBytes = new byte[16]; // Enough to fill two full blocks (16 bytes) + + // Act + foreach (var input in inputBytes) + { + asconHashA.Update(input); + } + + // Assert + // Ensure that the state is processed twice since 16 bytes were passed (2 blocks of 8 bytes). + var output = new byte[asconHashA.GetDigestSize()]; + asconHashA.DoFinal(output, 0); + output.Should().HaveCount(32); // Ensure valid hash output + } + + [Test] + public void Update_ShouldHandleSingleByteCorrectly() + { + // Arrange + byte input = 0xFF; // Single byte input + + // Act + asconHashA.Update(input); + + // Assert + // Even though one byte is provided, it should not process the state (waiting for 8 bytes). + var output = new byte[asconHashA.GetDigestSize()]; + asconHashA.DoFinal(output, 0); + output.Should().HaveCount(32); // Ensure valid hash output + } + + [Test] + public void Update_ShouldAccumulateStateWithMultipleUpdates() + { + // Arrange + byte[] inputBytes = { 0x00, 0x11, 0x22 }; // Partial input + + // Act + foreach (var input in inputBytes) + { + asconHashA.Update(input); + } + + // Add more data to fill the buffer. + byte[] additionalBytes = { 0x33, 0x44, 0x55, 0x66, 0x77 }; + foreach (var input in additionalBytes) + { + asconHashA.Update(input); + } + + // Assert + // Ensure that the state is correctly updated after multiple partial updates. + var output = new byte[asconHashA.GetDigestSize()]; + asconHashA.DoFinal(output, 0); + output.Should().HaveCount(32); // Ensure valid hash output + } + + private static string ToHexString(byte[] bytes) + { + return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant(); + } +} diff --git a/Algorithms.Tests/Crypto/Exceptions/CryptoExceptionTests.cs b/Algorithms.Tests/Crypto/Exceptions/CryptoExceptionTests.cs new file mode 100644 index 00000000..dc43c741 --- /dev/null +++ b/Algorithms.Tests/Crypto/Exceptions/CryptoExceptionTests.cs @@ -0,0 +1,69 @@ +using Algorithms.Crypto.Exceptions; +using NUnit.Framework; +using FluentAssertions; +using System; + + +namespace Algorithms.Tests.Crypto.Exceptions +{ + [TestFixture] + public class CryptoExceptionTests + { + [Test] + public void CryptoException_ShouldBeCreatedWithoutMessageOrInnerException() + { + // Act + var exception = new CryptoException(); + + // Assert + exception.Should().BeOfType() + .And.Subject.As() + .Message.Should().NotBeNullOrEmpty(); + exception.InnerException.Should().BeNull(); + } + + [Test] + public void CryptoException_ShouldSetMessage() + { + // Arrange + var expectedMessage = "This is a custom cryptographic error."; + + // Act + var exception = new CryptoException(expectedMessage); + + // Assert + exception.Should().BeOfType() + .And.Subject.As() + .Message.Should().Be(expectedMessage); + exception.InnerException.Should().BeNull(); + } + + [Test] + public void CryptoException_ShouldSetMessageAndInnerException() + { + // Arrange + var expectedMessage = "An error occurred during encryption."; + var innerException = new InvalidOperationException("Invalid operation"); + + // Act + var exception = new CryptoException(expectedMessage, innerException); + + // Assert + exception.Should().BeOfType() + .And.Subject.As() + .Message.Should().Be(expectedMessage); + exception.InnerException.Should().Be(innerException); + } + + [Test] + public void CryptoException_MessageShouldNotBeNullWhenUsingDefaultConstructor() + { + // Act + var exception = new CryptoException(); + + // Assert + exception.Message.Should().NotBeNullOrEmpty(); // Even the default Exception message is not null or empty. + } + } +} + diff --git a/Algorithms.Tests/Crypto/Exceptions/DataLengthExceptionTests.cs b/Algorithms.Tests/Crypto/Exceptions/DataLengthExceptionTests.cs new file mode 100644 index 00000000..e792d4cb --- /dev/null +++ b/Algorithms.Tests/Crypto/Exceptions/DataLengthExceptionTests.cs @@ -0,0 +1,67 @@ +using NUnit.Framework; +using FluentAssertions; +using System; +using Algorithms.Crypto.Exceptions; + +namespace Algorithms.Tests.Crypto.Exceptions +{ + [TestFixture] + public class DataLengthExceptionTests + { + [Test] + public void DataLengthException_ShouldBeCreatedWithoutMessageOrInnerException() + { + // Act + var exception = new DataLengthException(); + + // Assert + exception.Should().BeOfType() + .And.Subject.As() + .Message.Should().NotBeNullOrEmpty(); + exception.InnerException.Should().BeNull(); + } + + [Test] + public void DataLengthException_ShouldSetMessage() + { + // Arrange + var expectedMessage = "Data length is invalid."; + + // Act + var exception = new DataLengthException(expectedMessage); + + // Assert + exception.Should().BeOfType() + .And.Subject.As() + .Message.Should().Be(expectedMessage); + exception.InnerException.Should().BeNull(); + } + + [Test] + public void DataLengthException_ShouldSetMessageAndInnerException() + { + // Arrange + var expectedMessage = "An error occurred due to incorrect data length."; + var innerException = new ArgumentException("Invalid argument"); + + // Act + var exception = new DataLengthException(expectedMessage, innerException); + + // Assert + exception.Should().BeOfType() + .And.Subject.As() + .Message.Should().Be(expectedMessage); + exception.InnerException.Should().Be(innerException); + } + + [Test] + public void DataLengthException_MessageShouldNotBeNullWhenUsingDefaultConstructor() + { + // Act + var exception = new DataLengthException(); + + // Assert + exception.Message.Should().NotBeNullOrEmpty(); // Even the default Exception message is not null or empty. + } + } +} diff --git a/Algorithms.Tests/Crypto/Exceptions/OutputLengthExceptionTests.cs b/Algorithms.Tests/Crypto/Exceptions/OutputLengthExceptionTests.cs new file mode 100644 index 00000000..313b95ad --- /dev/null +++ b/Algorithms.Tests/Crypto/Exceptions/OutputLengthExceptionTests.cs @@ -0,0 +1,67 @@ +using NUnit.Framework; +using FluentAssertions; +using System; +using Algorithms.Crypto.Exceptions; + +namespace Algorithms.Tests.Crypto.Exceptions +{ + [TestFixture] + public class OutputLengthExceptionTests + { + [Test] + public void OutputLengthException_ShouldBeCreatedWithoutMessageOrInnerException() + { + // Act + var exception = new OutputLengthException(); + + // Assert + exception.Should().BeOfType() + .And.Subject.As() + .Message.Should().NotBeNullOrEmpty(); + exception.InnerException.Should().BeNull(); + } + + [Test] + public void OutputLengthException_ShouldSetMessage() + { + // Arrange + var expectedMessage = "Output buffer is too short."; + + // Act + var exception = new OutputLengthException(expectedMessage); + + // Assert + exception.Should().BeOfType() + .And.Subject.As() + .Message.Should().Be(expectedMessage); + exception.InnerException.Should().BeNull(); + } + + [Test] + public void OutputLengthException_ShouldSetMessageAndInnerException() + { + // Arrange + var expectedMessage = "Output length error."; + var innerException = new ArgumentException("Invalid argument"); + + // Act + var exception = new OutputLengthException(expectedMessage, innerException); + + // Assert + exception.Should().BeOfType() + .And.Subject.As() + .Message.Should().Be(expectedMessage); + exception.InnerException.Should().Be(innerException); + } + + [Test] + public void OutputLengthException_MessageShouldNotBeNullWhenUsingDefaultConstructor() + { + // Act + var exception = new OutputLengthException(); + + // Assert + exception.Message.Should().NotBeNullOrEmpty(); // Even the default Exception message is not null or empty. + } + } +} diff --git a/Algorithms.Tests/Crypto/Utils/ByteEncodingUtils.cs b/Algorithms.Tests/Crypto/Utils/ByteEncodingUtils.cs new file mode 100644 index 00000000..ef04168d --- /dev/null +++ b/Algorithms.Tests/Crypto/Utils/ByteEncodingUtils.cs @@ -0,0 +1,81 @@ +using NUnit.Framework; +using FluentAssertions; +using System; +using Algorithms.Crypto.Utils; + +namespace Algorithms.Tests.Crypto.Utils +{ + [TestFixture] + public class ByteEncodingUtilsTests + { + [Test] + public void BigEndianToUint64_ByteArray_ShouldConvertCorrectly() + { + // Arrange + byte[] input = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF }; + var expected = 0x0123456789ABCDEFUL; + + // Act + var result = ByteEncodingUtils.BigEndianToUint64(input, 0); + + // Assert + result.Should().Be(expected); + } + + [Test] + public void BigEndianToUint64_ByteArray_WithOffset_ShouldConvertCorrectly() + { + // Arrange + byte[] input = { 0x00, 0x00, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF }; + var expected = 0x0123456789ABCDEFUL; + + // Act + var result = ByteEncodingUtils.BigEndianToUint64(input, 2); + + // Assert + result.Should().Be(expected); + } + + [Test] + public void BigEndianToUint64_Span_ShouldConvertCorrectly() + { + // Arrange + Span input = stackalloc byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF }; + var expected = 0x0123456789ABCDEFUL; + + // Act + var result = ByteEncodingUtils.BigEndianToUint64(input); + + // Assert + result.Should().Be(expected); + } + + [Test] + public void UInt64ToBigEndian_ShouldWriteCorrectly() + { + // Arrange + var value = 0x0123456789ABCDEFUL; + Span output = stackalloc byte[8]; + byte[] expected = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF }; + + // Act + ByteEncodingUtils.UInt64ToBigEndian(value, output); + + // Assert + output.ToArray().Should().Equal(expected); + } + + [Test] + public void BigEndianToUint64_InvalidOffset_ShouldThrowException() + { + // Arrange + byte[] input = { 0x01, 0x23 }; + + // Act + Action act = () => ByteEncodingUtils.BigEndianToUint64(input, 1); + + // Assert + act.Should().Throw(); + } + } +} diff --git a/Algorithms.Tests/Crypto/Utils/LongUtilsTests.cs b/Algorithms.Tests/Crypto/Utils/LongUtilsTests.cs new file mode 100644 index 00000000..cc2029bc --- /dev/null +++ b/Algorithms.Tests/Crypto/Utils/LongUtilsTests.cs @@ -0,0 +1,98 @@ +using NUnit.Framework; +using FluentAssertions; +using Algorithms.Crypto.Utils; + +namespace Algorithms.Tests.Crypto.Utils +{ + [TestFixture] + public class LongUtilsTests + { + [Test] + public void RotateLeft_Long_ShouldRotateCorrectly() + { + // Arrange + var input = 0x0123456789ABCDEF; + var distance = 8; + var expected = 0x23456789ABCDEF01L; // The expected result is a signed long value. + + // Act + var result = LongUtils.RotateLeft(input, distance); + + // Assert + result.Should().Be(expected); + } + + [Test] + public void RotateLeft_Ulong_ShouldRotateCorrectly() + { + // Arrange + var input = 0x0123456789ABCDEFUL; + var distance = 8; + var expected = 0x23456789ABCDEF01UL; // The expected result is an unsigned ulong value. + + // Act + var result = LongUtils.RotateLeft(input, distance); + + // Assert + result.Should().Be(expected); + } + + [Test] + public void RotateRight_Long_ShouldRotateCorrectly() + { + // Arrange + var input = 0x0123456789ABCDEF; + var distance = 8; + var expected = unchecked((long)0xEF0123456789ABCD); // Using unchecked to correctly represent signed long. + + // Act + var result = LongUtils.RotateRight(input, distance); + + // Assert + result.Should().Be(expected); + } + + [Test] + public void RotateRight_Ulong_ShouldRotateCorrectly() + { + // Arrange + var input = 0x0123456789ABCDEFUL; + var distance = 8; + var expected = 0xEF0123456789ABCDUL; // The expected result is an unsigned ulong value. + + // Act + var result = LongUtils.RotateRight(input, distance); + + // Assert + result.Should().Be(expected); + } + + [Test] + public void RotateLeft_Long_ShouldHandleZeroRotation() + { + // Arrange + var input = 0x0123456789ABCDEF; + var distance = 0; + + // Act + var result = LongUtils.RotateLeft(input, distance); + + // Assert + result.Should().Be(input); // No rotation, result should be the same as input. + } + + [Test] + public void RotateRight_Ulong_ShouldHandleFullRotation() + { + // Arrange + var input = 0x0123456789ABCDEFUL; + var distance = 64; + + // Act + var result = LongUtils.RotateRight(input, distance); + + // Assert + result.Should().Be(input); // Full 64-bit rotation should result in the same value. + } + } +} diff --git a/Algorithms.Tests/Crypto/Utils/ValidationUtilsTests.cs b/Algorithms.Tests/Crypto/Utils/ValidationUtilsTests.cs new file mode 100644 index 00000000..f109fcda --- /dev/null +++ b/Algorithms.Tests/Crypto/Utils/ValidationUtilsTests.cs @@ -0,0 +1,124 @@ +using NUnit.Framework; +using FluentAssertions; +using System; +using Algorithms.Crypto.Utils; +using Algorithms.Crypto.Exceptions; + +namespace Algorithms.Tests.Crypto.Utils +{ + [TestFixture] + public class ValidationUtilsTests + { + [Test] + public void CheckDataLength_WithBufferOutOfBounds_ShouldThrowDataLengthException() + { + // Arrange + var buffer = new byte[5]; // A byte array of length 5 + var offset = 3; // Starting at index 3 + var length = 4; // Expecting to read 4 bytes (which will exceed the buffer size) + var errorMessage = "Buffer is too short"; + + // Act + var act = () => ValidationUtils.CheckDataLength(buffer, offset, length, errorMessage); + + // Assert + act.Should().Throw() + .WithMessage(errorMessage); + } + + [Test] + public void CheckOutputLength_WithCondition_ShouldThrowOutputLengthException() + { + // Arrange + var condition = true; + var errorMessage = "Output length is invalid"; + + // Act + var act = () => ValidationUtils.CheckOutputLength(condition, errorMessage); + + // Assert + act.Should().Throw() + .WithMessage(errorMessage); + } + + [Test] + public void CheckOutputLength_WithCondition_ShouldNotThrowOutputLengthException() + { + // Arrange + var condition = false; + var errorMessage = "Output length is invalid"; + + // Act + var act = () => ValidationUtils.CheckOutputLength(condition, errorMessage); + + // Assert + act.Should().NotThrow(); + } + + [Test] + public void CheckOutputLength_WithBufferOutOfBounds_ShouldThrowOutputLengthException() + { + // Arrange + var buffer = new byte[5]; + var offset = 3; + var length = 4; + var errorMessage = "Output buffer is too short"; + + // Act + var act = () => ValidationUtils.CheckOutputLength(buffer, offset, length, errorMessage); + + // Assert + act.Should().Throw() + .WithMessage(errorMessage); + } + + [Test] + public void CheckOutputLength_WithBProperBufferSize_ShouldThrowOutputLengthException() + { + // Arrange + var buffer = new byte[5]; + var offset = 0; + var length = 4; + var errorMessage = "Output buffer is too short"; + + // Act + var act = () => ValidationUtils.CheckOutputLength(buffer, offset, length, errorMessage); + + // Assert + act.Should().NotThrow(); + } + + [Test] + public void CheckOutputLength_SpanExceedsLimit_ShouldThrowOutputLengthException() + { + // Arrange + Span output = new byte[10]; + var outputLength = output.Length; + var maxLength = 5; + var errorMessage = "Output exceeds maximum length"; + + // Act + var act = () => ValidationUtils.CheckOutputLength(outputLength > maxLength, errorMessage); // Capture the length + + // Assert + act.Should().Throw() + .WithMessage(errorMessage); + } + + [Test] + public void CheckOutputLength_SpanDoesNotExceedLimit_ShouldThrowOutputLengthException() + { + // Arrange + Span output = new byte[10]; + var outputLength = output.Length; + var maxLength = 15; + var errorMessage = "Output exceeds maximum length"; + + // Act + var act = () => ValidationUtils.CheckOutputLength(outputLength > maxLength, errorMessage); // Capture the length + + // Assert + act.Should().NotThrow(); + } + } +} diff --git a/Algorithms/Crypto/Digests/AsconDigest.cs b/Algorithms/Crypto/Digests/AsconDigest.cs new file mode 100644 index 00000000..d6ec3c97 --- /dev/null +++ b/Algorithms/Crypto/Digests/AsconDigest.cs @@ -0,0 +1,538 @@ +using System; +using System.Runtime.CompilerServices; +using Algorithms.Crypto.Utils; + +namespace Algorithms.Crypto.Digests; + +/// +/// Implements the Ascon cryptographic hash algorithm, providing both the standard Ascon-Hash and the Ascon-HashA variants. +/// +/// +/// The class implements the Ascon hash function, a lightweight cryptographic algorithm designed for +/// resource-constrained environments such as IoT devices. It provides two variants: +/// +/// +/// +/// : The standard Ascon-Hash variant with 12 rounds of the permutation function for enhanced security. +/// +/// +/// +/// +/// : A performance-optimized variant with 8 rounds of the permutation function, offering a trade-off between security and performance. +/// +/// +/// +///
+/// 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. +///
+/// Ascon was designed to meet the requirements of lightweight cryptography, making it ideal for devices with limited computational power. +///
+public class AsconDigest : IDigest +{ + public enum AsconParameters + { + /// + /// Represents the Ascon Hash variant, the standard cryptographic hashing function of the Ascon family. + /// + /// + /// 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. + /// + AsconHash, + + /// + /// Represents the Ascon HashA variant, an alternative variant of the Ascon hashing function with fewer permutation rounds. + /// + /// + /// 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. + /// + AsconHashA, + } + + /// + /// Specifies the Ascon variant being used (either Ascon-Hash or Ascon-HashA). This defines the cryptographic algorithm's behavior. + /// + private readonly AsconParameters asconParameters; + + /// + /// The number of permutation rounds applied in the Ascon cryptographic process. This is determined by the selected Ascon variant. + /// + private readonly int asconPbRounds; + + /// + /// Internal buffer that temporarily stores input data before it is processed in 8-byte blocks. The buffer is cleared after each block is processed. + /// + private readonly byte[] buffer = new byte[8]; + + /// + /// Internal state variable x0 used in the cryptographic permutation function. This is updated continuously as input data is processed. + /// + private ulong x0; + + /// + /// Internal state variable x1 used in the cryptographic permutation function. This, along with other state variables, is updated during each round. + /// + private ulong x1; + + /// + /// Internal state variable x2 used in the cryptographic permutation function. It helps track the evolving state of the digest. + /// + private ulong x2; + + /// + /// Internal state variable x3 used in the cryptographic permutation function, contributing to the mixing and non-linearity of the state. + /// + private ulong x3; + + /// + /// Internal state variable x4 used in the cryptographic permutation function. This, along with x0 to x3, ensures cryptographic security. + /// + private ulong x4; + + /// + /// Tracks the current position within the buffer array. When bufferPosition reaches 8, the buffer is processed and reset. + /// + private int bufferPosition; + + /// + /// Initializes a new instance of the class with the specified Ascon parameters. + /// + /// The Ascon variant to use, either or . + /// + /// This constructor sets up the digest by selecting the appropriate number of permutation rounds based on the Ascon variant. + /// + /// For , 12 permutation rounds are used. + /// For , 8 permutation rounds are used. + /// + /// If an unsupported parameter is provided, the constructor throws an to indicate that the parameter is invalid. + /// The internal state of the digest is then reset to prepare for processing input data. + /// + /// Thrown when an invalid parameter setting is provided for Ascon Hash. + 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(); + } + + /// + /// Gets the name of the cryptographic algorithm based on the selected Ascon parameter. + /// + /// + /// A string representing the name of the algorithm variant, either "Ascon-Hash" or "Ascon-HashA". + /// + /// + /// This property determines the algorithm name based on the selected Ascon variant when the instance was initialized. + /// It supports two variants: + /// + /// "Ascon-Hash" for the variant. + /// "Ascon-HashA" for the variant. + /// + /// If an unsupported or unknown parameter is used, the property throws an . + /// + /// Thrown if an unknown Ascon parameter is encountered. + 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. + }; + } + } + + /// + /// Gets the size of the resulting hash produced by the digest, in bytes. + /// + /// The size of the hash, which is 32 bytes (256 bits) for this digest implementation. + /// + /// 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. + /// + public int GetDigestSize() => 32; + + /// + /// Gets the internal block size of the digest in bytes. + /// + /// The internal block size of the digest, which is 8 bytes (64 bits). + /// + /// 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. + /// + public int GetByteLength() => 8; + + /// + /// Updates the cryptographic state by processing a single byte of input and adding it to the internal buffer. + /// + /// The byte to be added to the internal buffer and processed. + /// + /// 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 x0. + /// After processing the buffer, the permutation function is applied to mix the internal state, and the buffer position is reset to zero. + ///

+ /// If the buffer has not yet reached 8 bytes, the method simply adds the input byte to the buffer and waits for further input. + ///
+ 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; + } + + /// + /// Updates the cryptographic state by processing a segment of input data from a byte array, starting at a specified offset and length. + /// + /// The byte array containing the input data to be processed. + /// The offset in the input array where processing should begin. + /// The number of bytes from the input array to process. + /// + /// 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 and delegating the actual block update to + /// the method for further processing. + /// + /// + /// Thrown if the input data is too short, starting from and for the length . + /// + 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 and delegate the processing to the span-based method. + BlockUpdate(input.AsSpan(inOff, inLen)); + } + + /// + /// Processes the input data by updating the internal cryptographic state, handling both partial and full blocks. + /// + /// A read-only span of bytes representing the input data to be processed. + /// + /// 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 x0 and applies the permutation function + /// to update the cryptographic state. + ///

+ /// 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. + ///
+ public void BlockUpdate(ReadOnlySpan 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. + } + + /// + /// 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. + /// + /// The byte array where the final 32-byte hash will be written. + /// The offset in the output array at which to start writing the hash. + /// The size of the hash (32 bytes). + /// + /// This method finalizes the hash computation by converting the output array to a and + /// calling the method. It provides flexibility in placing the result in an + /// existing byte array with a specified offset. + /// + /// Thrown if the output buffer is too small to hold the resulting hash. + 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)); + } + + /// + /// Finalizes the cryptographic hash computation, absorbing any remaining data, applying the final permutation, and + /// writing the resulting hash to the provided output buffer. + /// + /// A span of bytes where the final 32-byte hash will be written. + /// The size of the hash (32 bytes). + /// + /// 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. + /// + /// Thrown if the output buffer is too small to hold the resulting hash. + public int DoFinal(Span 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; + } + + /// + /// Computes the cryptographic hash of the input byte array and returns the result as a lowercase hexadecimal string. + /// + /// The input byte array to be hashed. + /// A string containing the computed hash in lowercase hexadecimal format. + /// + /// 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 and delegates the actual hashing to the + /// method. + /// + public string Digest(byte[] input) + { + return Digest(input.AsSpan()); + } + + /// + /// Computes the cryptographic hash of the input span of bytes and returns the result as a lowercase hexadecimal string. + /// + /// A span of bytes representing the input data to be hashed. + /// A string containing the computed hash in lowercase hexadecimal format. + /// + /// 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 . + /// + public string Digest(Span 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(); + } + + /// + /// Resets the internal state of the Ascon cryptographic hash algorithm to its initial state based on the selected variant. + /// + /// + /// This method clears the internal buffer and resets the buffer position to zero. Depending on the specified + /// Ascon variant ( or ), it also reinitializes + /// the internal state variables (x0, x1, x2, x3, x4) to their starting values. + ///

+ /// 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. + ///
+ 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(); + } + } + + /// + /// Finalizes the absorption phase of the cryptographic hash by padding the buffer and applying the final permutation round. + /// + /// + /// 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 x0. After padding, the final permutation round is applied using 12 rounds of + /// the permutation function . This ensures the internal state is fully mixed and the cryptographic hash + /// is securely finalized. + /// + 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); + } + + /// + /// Executes the cryptographic permutation function by applying a sequence of rounds that transform the internal state variables. + /// + /// + /// The number of rounds to execute. If set to 12, additional rounds are performed with specific constants to enhance the security of the transformation. + /// + /// + /// In the Ascon cryptographic algorithm, the permutation function P transforms the internal state over multiple rounds. + /// This method applies a set of round constants, each of which alters the state variables (x0, x1, x2, x3, x4) differently, + /// ensuring that the transformation introduces non-linearity and diffusion, which are essential for cryptographic security. + ///

+ /// When 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. + ///
+ 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); + } + + /// + /// Executes a single round of the cryptographic permutation function, transforming the internal state + /// variables x0, x1, x2, x3, and x4 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. + /// + /// + /// 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. + /// + /// + /// The Round 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. + ///

+ /// The round constant () 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 + /// to spread bits throughout the 64-bit word. + ///
+ [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); + } +} diff --git a/Algorithms/Crypto/Digests/IDigest.cs b/Algorithms/Crypto/Digests/IDigest.cs new file mode 100644 index 00000000..0800f176 --- /dev/null +++ b/Algorithms/Crypto/Digests/IDigest.cs @@ -0,0 +1,70 @@ +using System; + +namespace Algorithms.Crypto.Digests; + +/// +/// Interface for message digest algorithms, providing methods to update, finalize, and reset the digest state. +/// +public interface IDigest +{ + /// + /// Gets the name of the digest algorithm (e.g., "SHA-256"). + /// + string AlgorithmName { get; } + + /// + /// Gets the size of the digest in bytes (e.g., 32 bytes for SHA-256). + /// + /// The size of the digest in bytes. + int GetDigestSize(); + + /// + /// Gets the byte length of the internal buffer used by the digest. + /// + /// The byte length of the internal buffer. + int GetByteLength(); + + /// + /// Updates the digest with a single byte of input data. + /// + /// The byte to add to the digest. + void Update(byte input); + + /// + /// Updates the digest with a portion of a byte array. + /// + /// The byte array containing the input data. + /// The offset within the array to start reading from. + /// The length of data to read from the array. + void BlockUpdate(byte[] input, int inOff, int inLen); + + /// + /// Updates the digest with a portion of input data from a of bytes. + /// + /// The containing the input data. + void BlockUpdate(ReadOnlySpan input); + + /// + /// Completes the digest calculation and stores the result in the specified byte array. + /// + /// The byte array to store the final digest. + /// The offset within the array to start writing the digest. + /// The number of bytes written to the output array. + int DoFinal(byte[] output, int outOff); + + /// + /// Completes the digest calculation and stores the result in the specified of bytes. + /// + /// The to store the final digest. + /// The number of bytes written to the output span. + int DoFinal(Span output); + + string Digest(byte[] input); + + string Digest(Span input); + + /// + /// Resets the digest to its initial state, clearing all data accumulated so far. + /// + void Reset(); +} diff --git a/Algorithms/Crypto/Exceptions/CryptoException.cs b/Algorithms/Crypto/Exceptions/CryptoException.cs new file mode 100644 index 00000000..44b3bbd7 --- /dev/null +++ b/Algorithms/Crypto/Exceptions/CryptoException.cs @@ -0,0 +1,36 @@ +using System; + +namespace Algorithms.Crypto.Exceptions; + +/// +/// Represents errors that occur during cryptographic operations. +/// +public class CryptoException : Exception +{ + /// + /// Initializes a new instance of the class. + /// + public CryptoException() + { + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public CryptoException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception that is the cause of this exception. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception. + public CryptoException(string message, Exception inner) + : base(message, inner) + { + } +} diff --git a/Algorithms/Crypto/Exceptions/DataLengthException.cs b/Algorithms/Crypto/Exceptions/DataLengthException.cs new file mode 100644 index 00000000..f96e2a7f --- /dev/null +++ b/Algorithms/Crypto/Exceptions/DataLengthException.cs @@ -0,0 +1,36 @@ +using System; + +namespace Algorithms.Crypto.Exceptions; + +/// +/// Represents errors that occur when the length of data in a cryptographic operation is invalid or incorrect. +/// +public class DataLengthException : CryptoException +{ + /// + /// Initializes a new instance of the class. + /// + public DataLengthException() + { + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public DataLengthException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception that is the cause of this exception. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception. + public DataLengthException(string message, Exception inner) + : base(message, inner) + { + } +} diff --git a/Algorithms/Crypto/Exceptions/OutputLengthException.cs b/Algorithms/Crypto/Exceptions/OutputLengthException.cs new file mode 100644 index 00000000..1e295fc4 --- /dev/null +++ b/Algorithms/Crypto/Exceptions/OutputLengthException.cs @@ -0,0 +1,57 @@ +using System; + +namespace Algorithms.Crypto.Exceptions; + +/// +/// Represents an exception that is thrown when the output buffer length is insufficient for a cryptographic operation. +/// +/// +/// The is a specific subclass of . It is used in cryptographic +/// operations to signal that the provided output buffer does not have enough space to store the required output. This exception is +/// typically thrown when encryption, hashing, or other cryptographic operations require more space than what has been allocated in +/// the output buffer. +///
+/// This exception provides constructors for creating the exception with a custom message, an inner exception, or both. By inheriting +/// from , it can be handled similarly in cases where both input and output length issues may arise. +///
+public class OutputLengthException : DataLengthException +{ + /// + /// Initializes a new instance of the class. + /// + /// + /// This constructor initializes a new instance of the class without any additional message or inner exception. + /// It is commonly used when a generic output length issue needs to be raised without specific details. + /// + public OutputLengthException() + { + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + /// + /// This constructor allows for a custom error message to be provided, giving more detail about the specific issue with the output length. + /// + public OutputLengthException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception that is the cause of this exception. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception. + /// + /// This constructor allows for both a custom message and an inner exception, which can be useful for propagating + /// the underlying cause of the error. For example, if the output buffer length is too short due to incorrect calculations, + /// the root cause (e.g., an ) can be passed in as the inner exception. + /// + public OutputLengthException(string message, Exception inner) + : base(message, inner) + { + } +} diff --git a/Algorithms/Crypto/Utils/ByteEncodingUtils.cs b/Algorithms/Crypto/Utils/ByteEncodingUtils.cs new file mode 100644 index 00000000..479d2508 --- /dev/null +++ b/Algorithms/Crypto/Utils/ByteEncodingUtils.cs @@ -0,0 +1,63 @@ +using System; +using System.Buffers.Binary; +using System.Runtime.CompilerServices; + +namespace Algorithms.Crypto.Utils; + +/// +/// Provides utility methods for converting between byte arrays and 64-bit unsigned integers using big-endian byte order. +/// +/// +/// The class contains static methods that assist in reading and writing 64-bit unsigned integers +/// from and to byte arrays or spans in big-endian format. These methods are optimized for cryptographic operations where byte +/// encoding is critical for consistency and security. +/// +public static class ByteEncodingUtils +{ + /// + /// Converts an 8-byte segment from a byte array (starting at the specified offset) into a 64-bit unsigned integer using big-endian format. + /// + /// The byte array containing the input data. + /// The offset within the byte array to start reading from. + /// A 64-bit unsigned integer representing the big-endian interpretation of the byte array segment. + /// Thrown if the specified offset is out of range of the byte array. + /// + /// This method reads 8 bytes from the specified offset within the byte array and converts them to a 64-bit unsigned integer + /// in big-endian format. Big-endian format stores the most significant byte first, followed by the less significant bytes. + /// + public static ulong BigEndianToUint64(byte[] byteStream, int offset) + { + return BinaryPrimitives.ReadUInt64BigEndian(byteStream.AsSpan(offset)); + } + + /// + /// Converts a read-only span of bytes into a 64-bit unsigned integer using big-endian format. + /// + /// A read-only span containing the input data. + /// A 64-bit unsigned integer representing the big-endian interpretation of the span of bytes. + /// + /// This method is optimized for performance using the attribute to encourage + /// inlining by the compiler. It reads exactly 8 bytes from the input span and converts them into a 64-bit unsigned integer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong BigEndianToUint64(ReadOnlySpan byteStream) + { + return BinaryPrimitives.ReadUInt64BigEndian(byteStream); + } + + /// + /// Writes a 64-bit unsigned integer to a span of bytes using big-endian format. + /// + /// The 64-bit unsigned integer to write. + /// The span of bytes where the value will be written. + /// + /// This method writes the 64-bit unsigned integer into the span in big-endian format, where the most significant byte is written first. + /// The method is optimized using the attribute to improve performance in scenarios + /// where frequent byte-to-integer conversions are required, such as cryptographic algorithms. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UInt64ToBigEndian(ulong value, Span byteStream) + { + BinaryPrimitives.WriteUInt64BigEndian(byteStream, value); + } +} diff --git a/Algorithms/Crypto/Utils/LongUtils.cs b/Algorithms/Crypto/Utils/LongUtils.cs new file mode 100644 index 00000000..60f266f8 --- /dev/null +++ b/Algorithms/Crypto/Utils/LongUtils.cs @@ -0,0 +1,78 @@ +using System.Numerics; + +namespace Algorithms.Crypto.Utils; + +/// +/// Provides utility methods for performing bitwise rotation operations (left and right) on 64-bit integers. +/// +/// +/// The class contains methods to rotate 64-bit signed and unsigned integers to the left or right. +/// These rotations are crucial in various cryptographic algorithms, where circular shifts are used to mix data and +/// introduce non-linearity. The methods use the underlying for efficient, +/// hardware-supported bitwise rotations. +/// +public static class LongUtils +{ + /// + /// Rotates the bits of a 64-bit signed integer to the left by a specified number of bits. + /// + /// The 64-bit signed integer to rotate. + /// The number of bits to rotate the integer to the left. + /// The result of rotating the integer to the left by the specified distance. + /// + /// This method uses the underlying method, converting the signed integer to an unsigned integer + /// for the rotation, then casting it back to a signed integer. The rotation is performed in a circular manner, where bits shifted + /// out of the most significant bit are reintroduced into the least significant bit. + /// + public static long RotateLeft(long i, int distance) + { + return (long)BitOperations.RotateLeft((ulong)i, distance); + } + + /// + /// Rotates the bits of a 64-bit unsigned integer to the left by a specified number of bits. + /// + /// The 64-bit unsigned integer to rotate. + /// The number of bits to rotate the integer to the left. + /// The result of rotating the integer to the left by the specified distance. + /// + /// The rotation is performed circularly, meaning bits shifted out of the most significant bit are reintroduced into + /// the least significant bit. This method is optimized for performance using hardware-supported operations through + /// . + /// + public static ulong RotateLeft(ulong i, int distance) + { + return BitOperations.RotateLeft(i, distance); + } + + /// + /// Rotates the bits of a 64-bit signed integer to the right by a specified number of bits. + /// + /// The 64-bit signed integer to rotate. + /// The number of bits to rotate the integer to the right. + /// The result of rotating the integer to the right by the specified distance. + /// + /// Similar to the left rotation, this method uses to perform the rotation. + /// The signed integer is cast to an unsigned integer for the operation and cast back to a signed integer afterward. + /// The rotation wraps bits shifted out of the least significant bit into the most significant bit. + /// + public static long RotateRight(long i, int distance) + { + return (long)BitOperations.RotateRight((ulong)i, distance); + } + + /// + /// Rotates the bits of a 64-bit unsigned integer to the right by a specified number of bits. + /// + /// The 64-bit unsigned integer to rotate. + /// The number of bits to rotate the integer to the right. + /// The result of rotating the integer to the right by the specified distance. + /// + /// This method performs the rotation circularly, where bits shifted out of the least significant bit are reintroduced + /// into the most significant bit. The operation uses hardware-supported instructions via . + /// + public static ulong RotateRight(ulong i, int distance) + { + return BitOperations.RotateRight(i, distance); + } +} diff --git a/Algorithms/Crypto/Utils/ValidationUtils.cs b/Algorithms/Crypto/Utils/ValidationUtils.cs new file mode 100644 index 00000000..88931dfa --- /dev/null +++ b/Algorithms/Crypto/Utils/ValidationUtils.cs @@ -0,0 +1,95 @@ +using System; +using System.Diagnostics; +using Algorithms.Crypto.Exceptions; + +namespace Algorithms.Crypto.Utils; + +/// +/// Provides utility methods for validating the lengths of input and output data in cryptographic operations. +/// +/// +/// The class contains static methods to validate the length and position of data buffers used in +/// cryptographic operations. These methods throw appropriate exceptions such as or +/// when the validation fails. These are critical for ensuring that cryptographic computations +/// do not run into buffer overflows, underflows, or incorrect input/output buffer lengths. +/// +public static class ValidationUtils +{ + /// + /// Validates that the specified offset and length fit within the bounds of the given buffer. + /// + /// The byte array to validate. + /// The offset into the byte array where validation should start. + /// The number of bytes to validate from the specified offset. + /// The message that describes the error if the exception is thrown. + /// Thrown if the offset and length exceed the bounds of the buffer. + /// + /// This method ensures that the specified offset and length fit within the bounds of the buffer. If the offset and length + /// go out of bounds, a is thrown with the provided error message. + /// + public static void CheckDataLength(byte[] buffer, int offset, int length, string message) + { + if (offset > (buffer.Length - length)) + { + throw new DataLengthException(message); + } + } + + /// + /// Throws an if the specified condition is true. + /// + /// A boolean condition indicating whether the exception should be thrown. + /// The message that describes the error if the exception is thrown. + /// Thrown if the condition is true. + /// + /// This method performs a simple conditional check for output length validation. If the condition is true, an + /// is thrown with the provided message. + /// + public static void CheckOutputLength(bool condition, string message) + { + if (condition) + { + throw new OutputLengthException(message); + } + } + + /// + /// Validates that the specified offset and length fit within the bounds of the output buffer. + /// + /// The byte array to validate. + /// The offset into the byte array where validation should start. + /// The number of bytes to validate from the specified offset. + /// The message that describes the error if the exception is thrown. + /// Thrown if the offset and length exceed the bounds of the buffer. + /// + /// This method ensures that the specified offset and length do not exceed the bounds of the output buffer. If the + /// validation fails, an is thrown with the provided message. + /// + public static void CheckOutputLength(byte[] buffer, int offset, int length, string message) + { + if (offset > (buffer.Length - length)) + { + throw new OutputLengthException(message); + } + } + + /// + /// Validates that the length of the output span does not exceed the specified length. + /// + /// The type of elements in the span. + /// The span to validate. + /// The maximum allowed length for the output span. + /// The message that describes the error if the exception is thrown. + /// Thrown if the length of the output span exceeds the specified length. + /// + /// This method checks that the span does not exceed the specified length. If the span length exceeds the allowed length, + /// an is thrown with the provided error message. + /// + public static void CheckOutputLength(Span output, int length, string message) + { + if (output.Length > length) + { + throw new OutputLengthException(message); + } + } +} diff --git a/README.md b/README.md index f13516de..93e048d2 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ find more than one implementation for the same objective but using different alg * [TBC Padding](./Algorithms/Crypto/Paddings/TbcPadding.cs) * [PKCS7 Padding](./Algorithms/Crypto/Paddings/Pkcs7Padding.cs) * [Digests](./Algorithms/Crypto/Digests/) + * [Ascon Hash Digest](./Algorithms/Crypto/Digests/AsconDigest.cs) * [MD2 Digest](./Algorithms/Crypto/Digests/Md2Digest.cs) * [Data Compression](./Algorithms/DataCompression) * [Burrows-Wheeler transform](./Algorithms/DataCompression/BurrowsWheelerTransform.cs) @@ -66,7 +67,7 @@ find more than one implementation for the same objective but using different alg * [Extended Euclidean Algorithm](./Algorithms/ModularArithmetic/ExtendedEuclideanAlgorithm.cs) * [Modular Multiplicative Inverse](./Algorithms/ModularArithmetic/ModularMultiplicativeInverse.cs) * [Numeric](./Algorithms/Numeric) - * [Absolute](./Algorithms/Numeric/Abs.cs) + * [Absolute](./Algorithms/Numeric/Abs.cs) * [Aliquot Sum Calculator](./Algorithms/Numeric/AliquotSumCalculator.cs) * [Amicable Numbers Checker](./Algorithms/Numeric/AmicableNumbersChecker.cs) * [Decomposition](./Algorithms/Numeric/Decomposition)