/* * Copyright (c) 2015, 2018 Scott Bennett * (c) 2018-2023 Kaarlo Räihä * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ using System; using System.IO; using System.Threading.Tasks; using System.Runtime.Intrinsics; using System.Runtime.CompilerServices; namespace BeyondTools.VFS.Crypto; /// /// Chosen SIMD mode /// public enum SimdMode { /// /// Autodetect /// AutoDetect = 0, /// /// 128 bit SIMD /// V128, /// /// 256 bit SIMD /// V256, /// /// 512 bit SIMD /// V512, /// /// No SIMD /// None } /// /// Class for ChaCha20 encryption / decryption /// public sealed class CSChaCha20 : IDisposable { /// /// Only allowed key lenght in bytes /// public const int allowedKeyLength = 32; /// /// Only allowed nonce lenght in bytes /// public const int allowedNonceLength = 12; /// /// How many bytes are processed per loop /// public const int processBytesAtTime = 64; private const int stateLength = 16; /// /// The ChaCha20 state (aka "context") /// private readonly uint[] state = new uint[stateLength]; /// /// Determines if the objects in this class have been disposed of. Set to true by the Dispose() method. /// private bool isDisposed = false; /// /// Set up a new ChaCha20 state. The lengths of the given parameters are checked before encryption happens. /// /// /// See ChaCha20 Spec Section 2.4 for a detailed description of the inputs. /// /// /// A 32-byte (256-bit) key, treated as a concatenation of eight 32-bit little-endian integers /// /// /// A 12-byte (96-bit) nonce, treated as a concatenation of three 32-bit little-endian integers /// /// /// A 4-byte (32-bit) block counter, treated as a 32-bit little-endian integer /// public CSChaCha20(byte[] key, byte[] nonce, uint counter) { KeySetup(key); IvSetup(nonce, counter); } /// /// Set up a new ChaCha20 state. The lengths of the given parameters are checked before encryption happens. /// /// /// See ChaCha20 Spec Section 2.4 for a detailed description of the inputs. /// /// A 32-byte (256-bit) key, treated as a concatenation of eight 32-bit little-endian integers /// A 12-byte (96-bit) nonce, treated as a concatenation of three 32-bit little-endian integers /// A 4-byte (32-bit) block counter, treated as a 32-bit little-endian unsigned integer public CSChaCha20(ReadOnlySpan key, ReadOnlySpan nonce, uint counter) { KeySetup(key.ToArray()); IvSetup(nonce.ToArray(), counter); } /// /// The ChaCha20 state (aka "context"). Read-Only. /// public uint[] State { get { return state; } } // These are the same constants defined in the reference implementation. // http://cr.yp.to/streamciphers/timings/estreambench/submissions/salsa20/chacha8/ref/chacha.c private static readonly byte[] sigma = "expand 32-byte k"u8.ToArray(); private static readonly byte[] tau = "expand 16-byte k"u8.ToArray(); /// /// Set up the ChaCha state with the given key. A 32-byte key is required and enforced. /// /// /// A 32-byte (256-bit) key, treated as a concatenation of eight 32-bit little-endian integers /// private void KeySetup(byte[] key) { if (key == null) { throw new ArgumentNullException("Key is null"); } if (key.Length != allowedKeyLength) { throw new ArgumentException($"Key length must be {allowedKeyLength}. Actual: {key.Length}"); } state[4] = Util.U8To32Little(key, 0); state[5] = Util.U8To32Little(key, 4); state[6] = Util.U8To32Little(key, 8); state[7] = Util.U8To32Little(key, 12); byte[] constants = key.Length == allowedKeyLength ? sigma : tau; int keyIndex = key.Length - 16; state[8] = Util.U8To32Little(key, keyIndex + 0); state[9] = Util.U8To32Little(key, keyIndex + 4); state[10] = Util.U8To32Little(key, keyIndex + 8); state[11] = Util.U8To32Little(key, keyIndex + 12); state[0] = Util.U8To32Little(constants, 0); state[1] = Util.U8To32Little(constants, 4); state[2] = Util.U8To32Little(constants, 8); state[3] = Util.U8To32Little(constants, 12); } /// /// Set up the ChaCha state with the given nonce (aka Initialization Vector or IV) and block counter. A 12-byte nonce and a 4-byte counter are required. /// /// /// A 12-byte (96-bit) nonce, treated as a concatenation of three 32-bit little-endian integers /// /// /// A 4-byte (32-bit) block counter, treated as a 32-bit little-endian integer /// private void IvSetup(byte[] nonce, uint counter) { if (nonce == null) { // There has already been some state set up. Clear it before exiting. Dispose(); throw new ArgumentNullException("Nonce is null"); } if (nonce.Length != allowedNonceLength) { // There has already been some state set up. Clear it before exiting. Dispose(); throw new ArgumentException($"Nonce length must be {allowedNonceLength}. Actual: {nonce.Length}"); } state[12] = counter; state[13] = Util.U8To32Little(nonce, 0); state[14] = Util.U8To32Little(nonce, 4); state[15] = Util.U8To32Little(nonce, 8); } private static SimdMode DetectSimdMode() { if (Vector512.IsHardwareAccelerated) { return SimdMode.V512; } else if (Vector256.IsHardwareAccelerated) { return SimdMode.V256; } else if (Vector128.IsHardwareAccelerated) { return SimdMode.V128; } return SimdMode.None; } #region Encryption methods /// /// Encrypt arbitrary-length byte array (input), writing the resulting byte array to preallocated output buffer. /// /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method /// Output byte array, must have enough bytes /// Input byte array /// Number of bytes to encrypt /// Chosen SIMD mode (default is auto-detect) public void EncryptBytes(byte[] output, byte[] input, int numBytes, SimdMode simdMode = SimdMode.AutoDetect) { if (output == null) { throw new ArgumentNullException("output", "Output cannot be null"); } if (input == null) { throw new ArgumentNullException("input", "Input cannot be null"); } if (numBytes < 0 || numBytes > input.Length) { throw new ArgumentOutOfRangeException("numBytes", "The number of bytes to read must be between [0..input.Length]"); } if (output.Length < numBytes) { throw new ArgumentOutOfRangeException("output", $"Output byte array should be able to take at least {numBytes}"); } if (simdMode == SimdMode.AutoDetect) { simdMode = DetectSimdMode(); } WorkBytes(output, input, numBytes, simdMode); } /// /// Encrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output) /// /// Output stream /// Input stream /// How many bytes to read and write at time, default is 1024 /// Chosen SIMD mode (default is auto-detect) public void EncryptStream(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024, SimdMode simdMode = SimdMode.AutoDetect) { if (simdMode == SimdMode.AutoDetect) { simdMode = DetectSimdMode(); } WorkStreams(output, input, simdMode, howManyBytesToProcessAtTime); } /// /// Async encrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output) /// /// Output stream /// Input stream /// How many bytes to read and write at time, default is 1024 /// Chosen SIMD mode (default is auto-detect) public async Task EncryptStreamAsync(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024, SimdMode simdMode = SimdMode.AutoDetect) { if (simdMode == SimdMode.AutoDetect) { simdMode = DetectSimdMode(); } await WorkStreamsAsync(output, input, simdMode, howManyBytesToProcessAtTime); } /// /// Encrypt arbitrary-length byte array (input), writing the resulting byte array to preallocated output buffer. /// /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method /// Output byte array, must have enough bytes /// Input byte array /// Chosen SIMD mode (default is auto-detect) public void EncryptBytes(byte[] output, byte[] input, SimdMode simdMode = SimdMode.AutoDetect) { if (output == null) { throw new ArgumentNullException("output", "Output cannot be null"); } if (input == null) { throw new ArgumentNullException("input", "Input cannot be null"); } if (simdMode == SimdMode.AutoDetect) { simdMode = DetectSimdMode(); } WorkBytes(output, input, input.Length, simdMode); } /// /// Encrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method. /// /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method /// Input byte array /// Number of bytes to encrypt /// Chosen SIMD mode (default is auto-detect) /// Byte array that contains encrypted bytes public byte[] EncryptBytes(byte[] input, int numBytes, SimdMode simdMode = SimdMode.AutoDetect) { if (input == null) { throw new ArgumentNullException("input", "Input cannot be null"); } if (numBytes < 0 || numBytes > input.Length) { throw new ArgumentOutOfRangeException("numBytes", "The number of bytes to read must be between [0..input.Length]"); } if (simdMode == SimdMode.AutoDetect) { simdMode = DetectSimdMode(); } byte[] returnArray = new byte[numBytes]; WorkBytes(returnArray, input, numBytes, simdMode); return returnArray; } /// /// Encrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method. /// /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method /// Input byte array /// Chosen SIMD mode (default is auto-detect) /// Byte array that contains encrypted bytes public byte[] EncryptBytes(byte[] input, SimdMode simdMode = SimdMode.AutoDetect) { if (input == null) { throw new ArgumentNullException("input", "Input cannot be null"); } if (simdMode == SimdMode.AutoDetect) { simdMode = DetectSimdMode(); } byte[] returnArray = new byte[input.Length]; WorkBytes(returnArray, input, input.Length, simdMode); return returnArray; } /// /// Encrypt string as UTF8 byte array, returns byte array that is allocated by method. /// /// Here you can NOT swap encrypt and decrypt methods, because of bytes-string transform /// Input string /// Chosen SIMD mode (default is auto-detect) /// Byte array that contains encrypted bytes public byte[] EncryptString(string input, SimdMode simdMode = SimdMode.AutoDetect) { if (input == null) { throw new ArgumentNullException("input", "Input cannot be null"); } if (simdMode == SimdMode.AutoDetect) { simdMode = DetectSimdMode(); } byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(input); byte[] returnArray = new byte[utf8Bytes.Length]; WorkBytes(returnArray, utf8Bytes, utf8Bytes.Length, simdMode); return returnArray; } #endregion // Encryption methods #region // Decryption methods /// /// Decrypt arbitrary-length byte array (input), writing the resulting byte array to the output buffer. /// /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method /// Output byte array /// Input byte array /// Number of bytes to decrypt /// Chosen SIMD mode (default is auto-detect) public void DecryptBytes(byte[] output, byte[] input, int numBytes, SimdMode simdMode = SimdMode.AutoDetect) { if (output == null) { throw new ArgumentNullException("output", "Output cannot be null"); } if (input == null) { throw new ArgumentNullException("input", "Input cannot be null"); } if (numBytes < 0 || numBytes > input.Length) { throw new ArgumentOutOfRangeException("numBytes", "The number of bytes to read must be between [0..input.Length]"); } if (output.Length < numBytes) { throw new ArgumentOutOfRangeException("output", $"Output byte array should be able to take at least {numBytes}"); } if (simdMode == SimdMode.AutoDetect) { simdMode = DetectSimdMode(); } WorkBytes(output, input, numBytes, simdMode); } /// /// Decrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output) /// /// Output stream /// Input stream /// How many bytes to read and write at time, default is 1024 /// Chosen SIMD mode (default is auto-detect) public void DecryptStream(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024, SimdMode simdMode = SimdMode.AutoDetect) { if (simdMode == SimdMode.AutoDetect) { simdMode = DetectSimdMode(); } WorkStreams(output, input, simdMode, howManyBytesToProcessAtTime); } /// /// Async decrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output) /// /// Output stream /// Input stream /// How many bytes to read and write at time, default is 1024 /// Chosen SIMD mode (default is auto-detect) public async Task DecryptStreamAsync(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024, SimdMode simdMode = SimdMode.AutoDetect) { if (simdMode == SimdMode.AutoDetect) { simdMode = DetectSimdMode(); } await WorkStreamsAsync(output, input, simdMode, howManyBytesToProcessAtTime); } /// /// Decrypt arbitrary-length byte array (input), writing the resulting byte array to preallocated output buffer. /// /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method /// Output byte array, must have enough bytes /// Input byte array /// Chosen SIMD mode (default is auto-detect) public void DecryptBytes(byte[] output, byte[] input, SimdMode simdMode = SimdMode.AutoDetect) { if (output == null) { throw new ArgumentNullException("output", "Output cannot be null"); } if (input == null) { throw new ArgumentNullException("input", "Input cannot be null"); } if (simdMode == SimdMode.AutoDetect) { simdMode = DetectSimdMode(); } WorkBytes(output, input, input.Length, simdMode); } /// /// Decrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method. /// /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method /// Input byte array /// Number of bytes to encrypt /// Chosen SIMD mode (default is auto-detect) /// Byte array that contains decrypted bytes public byte[] DecryptBytes(byte[] input, int numBytes, SimdMode simdMode = SimdMode.AutoDetect) { if (input == null) { throw new ArgumentNullException("input", "Input cannot be null"); } if (numBytes < 0 || numBytes > input.Length) { throw new ArgumentOutOfRangeException("numBytes", "The number of bytes to read must be between [0..input.Length]"); } if (simdMode == SimdMode.AutoDetect) { simdMode = DetectSimdMode(); } byte[] returnArray = new byte[numBytes]; WorkBytes(returnArray, input, numBytes, simdMode); return returnArray; } /// /// Decrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method. /// /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method /// Input byte array /// Chosen SIMD mode (default is auto-detect) /// Byte array that contains decrypted bytes public byte[] DecryptBytes(byte[] input, SimdMode simdMode = SimdMode.AutoDetect) { if (input == null) { throw new ArgumentNullException("input", "Input cannot be null"); } if (simdMode == SimdMode.AutoDetect) { simdMode = DetectSimdMode(); } byte[] returnArray = new byte[input.Length]; WorkBytes(returnArray, input, input.Length, simdMode); return returnArray; } /// /// Decrypt UTF8 byte array to string. /// /// Here you can NOT swap encrypt and decrypt methods, because of bytes-string transform /// Byte array /// Chosen SIMD mode (default is auto-detect) /// Byte array that contains encrypted bytes public string DecryptUTF8ByteArray(byte[] input, SimdMode simdMode = SimdMode.AutoDetect) { if (input == null) { throw new ArgumentNullException("input", "Input cannot be null"); } if (simdMode == SimdMode.AutoDetect) { simdMode = DetectSimdMode(); } byte[] tempArray = new byte[input.Length]; WorkBytes(tempArray, input, input.Length, simdMode); return System.Text.Encoding.UTF8.GetString(tempArray); } #endregion // Decryption methods private void WorkStreams(Stream output, Stream input, SimdMode simdMode, int howManyBytesToProcessAtTime = 1024) { int readBytes; byte[] inputBuffer = new byte[howManyBytesToProcessAtTime]; byte[] outputBuffer = new byte[howManyBytesToProcessAtTime]; while ((readBytes = input.Read(inputBuffer, 0, howManyBytesToProcessAtTime)) > 0) { // Encrypt or decrypt WorkBytes(output: outputBuffer, input: inputBuffer, numBytes: readBytes, simdMode); // Write buffer output.Write(outputBuffer, 0, readBytes); } } private async Task WorkStreamsAsync(Stream output, Stream input, SimdMode simdMode, int howManyBytesToProcessAtTime = 1024) { byte[] readBytesBuffer = new byte[howManyBytesToProcessAtTime]; byte[] writeBytesBuffer = new byte[howManyBytesToProcessAtTime]; int howManyBytesWereRead = await input.ReadAsync(readBytesBuffer, 0, howManyBytesToProcessAtTime); while (howManyBytesWereRead > 0) { // Encrypt or decrypt WorkBytes(output: writeBytesBuffer, input: readBytesBuffer, numBytes: howManyBytesWereRead, simdMode); // Write await output.WriteAsync(writeBytesBuffer, 0, howManyBytesWereRead); // Read more howManyBytesWereRead = await input.ReadAsync(readBytesBuffer, 0, howManyBytesToProcessAtTime); } } /// /// Encrypt or decrypt an arbitrary-length byte array (input), writing the resulting byte array to the output buffer. The number of bytes to read from the input buffer is determined by numBytes. /// /// Output byte array /// Input byte array /// How many bytes to process /// Chosen SIMD mode (default is auto-detect) private void WorkBytes(byte[] output, byte[] input, int numBytes, SimdMode simdMode) { if (isDisposed) { throw new ObjectDisposedException("state", "The ChaCha state has been disposed"); } uint[] x = new uint[stateLength]; // Working buffer byte[] tmp = new byte[processBytesAtTime]; // Temporary buffer int offset = 0; int howManyFullLoops = numBytes / processBytesAtTime; int tailByteCount = numBytes - howManyFullLoops * processBytesAtTime; for (int loop = 0; loop < howManyFullLoops; loop++) { UpdateStateAndGenerateTemporaryBuffer(state, x, tmp); if (simdMode == SimdMode.V512) { // 1 x 64 bytes Vector512 inputV = Vector512.Create(input, offset); Vector512 tmpV = Vector512.Create(tmp, 0); Vector512 outputV = inputV ^ tmpV; outputV.CopyTo(output, offset); } else if (simdMode == SimdMode.V256) { // 2 x 32 bytes Vector256 inputV = Vector256.Create(input, offset); Vector256 tmpV = Vector256.Create(tmp, 0); Vector256 outputV = inputV ^ tmpV; outputV.CopyTo(output, offset); inputV = Vector256.Create(input, offset + 32); tmpV = Vector256.Create(tmp, 32); outputV = inputV ^ tmpV; outputV.CopyTo(output, offset + 32); } else if (simdMode == SimdMode.V128) { // 4 x 16 bytes Vector128 inputV = Vector128.Create(input, offset); Vector128 tmpV = Vector128.Create(tmp, 0); Vector128 outputV = inputV ^ tmpV; outputV.CopyTo(output, offset); inputV = Vector128.Create(input, offset + 16); tmpV = Vector128.Create(tmp, 16); outputV = inputV ^ tmpV; outputV.CopyTo(output, offset + 16); inputV = Vector128.Create(input, offset + 32); tmpV = Vector128.Create(tmp, 32); outputV = inputV ^ tmpV; outputV.CopyTo(output, offset + 32); inputV = Vector128.Create(input, offset + 48); tmpV = Vector128.Create(tmp, 48); outputV = inputV ^ tmpV; outputV.CopyTo(output, offset + 48); } else { for (int i = 0; i < processBytesAtTime; i += 4) { // Small unroll int start = i + offset; output[start] = (byte)(input[start] ^ tmp[i]); output[start + 1] = (byte)(input[start + 1] ^ tmp[i + 1]); output[start + 2] = (byte)(input[start + 2] ^ tmp[i + 2]); output[start + 3] = (byte)(input[start + 3] ^ tmp[i + 3]); } } offset += processBytesAtTime; } // In case there are some bytes left if (tailByteCount > 0) { UpdateStateAndGenerateTemporaryBuffer(state, x, tmp); for (int i = 0; i < tailByteCount; i++) { output[i + offset] = (byte)(input[i + offset] ^ tmp[i]); } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void UpdateStateAndGenerateTemporaryBuffer(uint[] stateToModify, uint[] workingBuffer, byte[] temporaryBuffer) { // Copy state to working buffer Buffer.BlockCopy(stateToModify, 0, workingBuffer, 0, stateLength * sizeof(uint)); for (int i = 0; i < 10; i++) { QuarterRound(workingBuffer, 0, 4, 8, 12); QuarterRound(workingBuffer, 1, 5, 9, 13); QuarterRound(workingBuffer, 2, 6, 10, 14); QuarterRound(workingBuffer, 3, 7, 11, 15); QuarterRound(workingBuffer, 0, 5, 10, 15); QuarterRound(workingBuffer, 1, 6, 11, 12); QuarterRound(workingBuffer, 2, 7, 8, 13); QuarterRound(workingBuffer, 3, 4, 9, 14); } for (int i = 0; i < stateLength; i++) { Util.ToBytes(temporaryBuffer, Util.Add(workingBuffer[i], stateToModify[i]), 4 * i); } stateToModify[12] = Util.AddOne(stateToModify[12]); if (stateToModify[12] <= 0) { /* Stopping at 2^70 bytes per nonce is the user's responsibility */ stateToModify[13] = Util.AddOne(stateToModify[13]); } } /// /// The ChaCha Quarter Round operation. It operates on four 32-bit unsigned integers within the given buffer at indices a, b, c, and d. /// /// /// The ChaCha state does not have four integer numbers: it has 16. So the quarter-round operation works on only four of them -- hence the name. Each quarter round operates on four predetermined numbers in the ChaCha state. /// See ChaCha20 Spec Sections 2.1 - 2.2. /// /// A ChaCha state (vector). Must contain 16 elements. /// Index of the first number /// Index of the second number /// Index of the third number /// Index of the fourth number [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void QuarterRound(uint[] x, uint a, uint b, uint c, uint d) { x[a] = Util.Add(x[a], x[b]); x[d] = Util.Rotate(Util.XOr(x[d], x[a]), 16); x[c] = Util.Add(x[c], x[d]); x[b] = Util.Rotate(Util.XOr(x[b], x[c]), 12); x[a] = Util.Add(x[a], x[b]); x[d] = Util.Rotate(Util.XOr(x[d], x[a]), 8); x[c] = Util.Add(x[c], x[d]); x[b] = Util.Rotate(Util.XOr(x[b], x[c]), 7); } #region Destructor and Disposer /// /// Clear and dispose of the internal state. The finalizer is only called if Dispose() was never called on this cipher. /// ~CSChaCha20() { Dispose(false); } /// /// Clear and dispose of the internal state. Also request the GC not to call the finalizer, because all cleanup has been taken care of. /// public void Dispose() { Dispose(true); /* * The Garbage Collector does not need to invoke the finalizer because Dispose(bool) has already done all the cleanup needed. */ GC.SuppressFinalize(this); } /// /// This method should only be invoked from Dispose() or the finalizer. This handles the actual cleanup of the resources. /// /// /// Should be true if called by Dispose(); false if called by the finalizer /// private void Dispose(bool disposing) { if (!isDisposed) { if (disposing) { /* Cleanup managed objects by calling their Dispose() methods */ } /* Cleanup any unmanaged objects here */ Array.Clear(state, 0, stateLength); } isDisposed = true; } #endregion // Destructor and Disposer } /// /// Utilities that are used during compression /// public static class Util { /// /// n-bit left rotation operation (towards the high bits) for 32-bit integers. /// /// /// /// The result of (v LEFTSHIFT c) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Rotate(uint v, int c) { unchecked { return v << c | v >> 32 - c; } } /// /// Unchecked integer exclusive or (XOR) operation. /// /// /// /// The result of (v XOR w) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint XOr(uint v, uint w) { return unchecked(v ^ w); } /// /// Unchecked integer addition. The ChaCha spec defines certain operations to use 32-bit unsigned integer addition modulo 2^32. /// /// /// See ChaCha20 Spec Section 2.1. /// /// /// /// The result of (v + w) modulo 2^32 [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Add(uint v, uint w) { return unchecked(v + w); } /// /// Add 1 to the input parameter using unchecked integer addition. The ChaCha spec defines certain operations to use 32-bit unsigned integer addition modulo 2^32. /// /// /// See ChaCha20 Spec Section 2.1. /// /// /// The result of (v + 1) modulo 2^32 [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint AddOne(uint v) { return unchecked(v + 1); } /// /// Convert four bytes of the input buffer into an unsigned 32-bit integer, beginning at the inputOffset. /// /// /// /// An unsigned 32-bit integer [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint U8To32Little(byte[] p, int inputOffset) { unchecked { return p[inputOffset] | (uint)p[inputOffset + 1] << 8 | (uint)p[inputOffset + 2] << 16 | (uint)p[inputOffset + 3] << 24; } } /// /// Serialize the input integer into the output buffer. The input integer will be split into 4 bytes and put into four sequential places in the output buffer, starting at the outputOffset. /// /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ToBytes(byte[] output, uint input, int outputOffset) { unchecked { output[outputOffset] = (byte)input; output[outputOffset + 1] = (byte)(input >> 8); output[outputOffset + 2] = (byte)(input >> 16); output[outputOffset + 3] = (byte)(input >> 24); } } }