namespace AcDream.Core.Net.Cryptography; /// /// Asheron's Call variant of Bob Jenkins' ISAAC keystream generator. AC's /// client and server each seed an ISAAC instance with a 4-byte value shared /// during the connect handshake, then XOR the keystream into the CRC field of /// each outbound packet so the peer can both verify the packet is authentic /// and recover the CRC for the real checksum comparison. /// /// /// Algorithm overview (public ISAAC from Bob Jenkins, 1996): /// /// Internal state is 256 uint32 mm[] words plus a, /// b, c registers and a 256-word output buffer /// randRsl[]. /// Initialization mixes mm[] via 4 rounds of Jenkins' golden- /// ratio shuffle on 8-word state, then folds in the seed-derived /// randRsl[] and mm[] words via two more passes. /// Scramble() produces 256 output words per call, consumed in /// reverse order (offset 255 → 0) by Next(). When the buffer /// is drained the scramble re-runs. /// AC variant: seed is exactly 4 bytes (the UDP-handshake ISAAC seed /// from the server). Those 4 bytes are interpreted as a /// little-endian uint32 and assigned to a = b = c before the /// first scramble. The rest of mm[] and randRsl[] /// start at zero. /// /// /// /// /// This is a clean-room reimplementation based on reading the public /// algorithm description and ACE's AGPL reference implementation at /// references/ACE/Source/ACE.Common/Cryptography/ISAAC.cs. See /// NOTICE.md for attribution. No code is copied. /// /// public sealed class IsaacRandom { private const int StateSize = 256; private const uint GoldenRatio = 0x9E3779B9u; private readonly uint[] _mm = new uint[StateSize]; private readonly uint[] _rsl = new uint[StateSize]; private uint _a; private uint _b; private uint _c; /// Current position in . /// consumes values from index _offset down to 0 then re-scrambles. private int _offset; /// /// Construct a new keystream seeded with the 4 bytes starting at /// [0]. The bytes are interpreted as a /// little-endian uint32 and fed into the ISAAC initialization such that /// two instances with the same seed produce identical output sequences. /// public IsaacRandom(ReadOnlySpan seedBytes) { if (seedBytes.Length < 4) throw new ArgumentException("seed must be at least 4 bytes", nameof(seedBytes)); Initialize(); uint seed = (uint)seedBytes[0] | ((uint)seedBytes[1] << 8) | ((uint)seedBytes[2] << 16) | ((uint)seedBytes[3] << 24); _a = _b = _c = seed; Scramble(); _offset = StateSize - 1; } /// /// Return the next uint32 of the keystream. Cheap — amortized one array /// read per call, with a scramble round every 256 calls. /// public uint Next() { uint value = _rsl[_offset]; if (_offset > 0) { _offset--; } else { Scramble(); _offset = StateSize - 1; } return value; } /// /// Run the Jenkins golden-ratio shuffle on the supplied 8-word state. /// Used during initialization to stir mm[] and randRsl[]. /// Each line is a mix step from the reference; the constants are the /// Jenkins-published avalanche offsets. /// private static void Mix(Span s) { s[0] ^= s[1] << 11; s[3] += s[0]; s[1] += s[2]; s[1] ^= s[2] >> 2; s[4] += s[1]; s[2] += s[3]; s[2] ^= s[3] << 8; s[5] += s[2]; s[3] += s[4]; s[3] ^= s[4] >> 16; s[6] += s[3]; s[4] += s[5]; s[4] ^= s[5] << 10; s[7] += s[4]; s[5] += s[6]; s[5] ^= s[6] >> 4; s[0] += s[5]; s[6] += s[7]; s[6] ^= s[7] << 8; s[1] += s[6]; s[7] += s[0]; s[7] ^= s[0] >> 9; s[2] += s[7]; s[0] += s[1]; } private void Initialize() { // mm[] and rsl[] start as all zeroes. Span s = stackalloc uint[8]; for (int i = 0; i < 8; i++) s[i] = GoldenRatio; // 4 warmup rounds so the initial state diverges from the golden-ratio // pattern before we start folding in real values. for (int i = 0; i < 4; i++) Mix(s); // First pass folds _rsl (zeroes on a fresh instance) into mm[]. for (int j = 0; j < StateSize; j += 8) { for (int k = 0; k < 8; k++) s[k] += _rsl[j + k]; Mix(s); for (int k = 0; k < 8; k++) _mm[j + k] = s[k]; } // Second pass folds mm[] (now populated) back into itself. for (int j = 0; j < StateSize; j += 8) { for (int k = 0; k < 8; k++) s[k] += _mm[j + k]; Mix(s); for (int k = 0; k < 8; k++) _mm[j + k] = s[k]; } } /// /// Produce the next 256 output words into . Consumed /// in reverse by . Each iteration rotates a /// through one of four avalanche shifts depending on i & 3, /// pulls an indirection from the far half of the state, and produces the /// next output + next state word. /// private void Scramble() { _c++; _b += _c; for (int i = 0; i < StateSize; i++) { uint x = _mm[i]; switch (i & 3) { case 0: _a ^= _a << 13; break; case 1: _a ^= _a >> 6; break; case 2: _a ^= _a << 2; break; case 3: _a ^= _a >> 16; break; } _a += _mm[(i + 128) & 0xFF]; uint y = _mm[(int)((x >> 2) & 0xFF)] + _a + _b; _mm[i] = y; uint nextB = _mm[(int)((y >> 10) & 0xFF)] + x; _rsl[i] = nextB; _b = nextB; } } }