forked from TheAlgorithms/C-Sharp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
AsconDigest.cs
538 lines (478 loc) · 27.5 KB
/
AsconDigest.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
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);
}
}