multirandom 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ import original_random
2
+ import sha_rand as sha_random
@@ -0,0 +1 @@
1
+ from random import *
@@ -0,0 +1,86 @@
1
+ _sha1 = None
2
+ _sha224 = None
3
+ _sha256 = None
4
+ _sha384 = None
5
+ _sha512 = None
6
+
7
+ def _get_sha1():
8
+ global _sha1
9
+ if _sha1 is None:
10
+ try:
11
+ from _sha1 import sha1 as _sha1
12
+ except ImportError:
13
+ from hashlib import sha1 as _sha1
14
+ return _sha1
15
+
16
+ def _get_sha224():
17
+ global _sha224
18
+ if _sha224 is None:
19
+ try:
20
+ from _sha2 import sha224 as _sha224
21
+ except ImportError:
22
+ from hashlib import sha224 as _sha224
23
+ return _sha224
24
+
25
+ def _get_sha256():
26
+ global _sha256
27
+ if _sha256 is None:
28
+ try:
29
+ from _sha2 import sha256 as _sha256
30
+ except ImportError:
31
+ from hashlib import sha256 as _sha256
32
+ return _sha256
33
+
34
+ def _get_sha384():
35
+ global _sha384
36
+ if _sha384 is None:
37
+ try:
38
+ from _sha2 import sha384 as _sha384
39
+ except ImportError:
40
+ from hashlib import sha384 as _sha384
41
+ return _sha384
42
+
43
+ def _get_sha512():
44
+ global _sha512
45
+ if _sha512 is None:
46
+ try:
47
+ # hashlib is pretty heavy to load, try lean internal module first
48
+ from _sha2 import sha512 as _sha512
49
+ except ImportError:
50
+ # fallback to official implementation
51
+ from hashlib import sha512 as _sha512
52
+ return _sha512
53
+ class _SHARandom:
54
+ def __init__(self, seed=None):
55
+ self.seed = seed
56
+ self.counter = 0
57
+
58
+ def _get_hash(self):
59
+ raise NotImplementedError
60
+
61
+ def getrandbytes(self, n):
62
+ res = b""
63
+ hf = self._get_hash()
64
+ seed_bytes = str(self.seed).encode()
65
+ while len(res) < n:
66
+ h = hf()
67
+ h.update(seed_bytes + self.counter.to_bytes(8, 'big'))
68
+ res += h.digest()
69
+ self.counter += 1
70
+ return res[:n]
71
+
72
+ class SHA1Random(_SHARandom):
73
+ def _get_hash(self):
74
+ return _get_sha1()
75
+
76
+ class SHA256Random(_SHARandom):
77
+ def _get_hash(self):
78
+ return _get_sha256()
79
+
80
+ class SHA384Random(_SHARandom):
81
+ def _get_hash(self):
82
+ return _get_sha384()
83
+
84
+ class SHA512Random(_SHARandom):
85
+ def _get_hash(self):
86
+ return _get_sha512()
@@ -0,0 +1,3 @@
1
+ import rand_via_clicks
2
+ import rand_using_online_api
3
+ # import weather_rand
@@ -0,0 +1,74 @@
1
+ import aiohttp
2
+ import asyncio
3
+
4
+ async def fetch_random_org_integers(num: int = 1, min_val: int = 1, max_val: int = 100):
5
+ """Fetches true random integers from random.org using their plain text API."""
6
+ url = "https://www.random.org/integers/"
7
+ params = {
8
+ "num": num,
9
+ "min": min_val,
10
+ "max": max_val,
11
+ "col": 1,
12
+ "base": 10,
13
+ "format": "plain",
14
+ "rnd": "new"
15
+ }
16
+ async with aiohttp.ClientSession() as session:
17
+ async with session.get(url, params=params) as response:
18
+ if response.status == 200:
19
+ text = await response.text()
20
+ return [int(x) for x in text.splitlines() if x.strip()]
21
+ return []
22
+
23
+ async def fetch_qrng_anu_integers(num: int = 1):
24
+ """Fetches quantum random numbers from the Australian National University QRNG API."""
25
+ url = "https://qrng.anu.edu.au/API/jsonI.php"
26
+ params = {"length": num, "type": "uint8"}
27
+ async with aiohttp.ClientSession() as session:
28
+ async with session.get(url, params=params) as response:
29
+ if response.status == 200:
30
+ data = await response.json()
31
+ return data.get("data", [])
32
+ return []
33
+
34
+ async def fetch_roll_api_result():
35
+ """Fetches a random dice roll from TheLastGimbus Roll-API (Physical Dice)."""
36
+ # Base URL found in research
37
+ base_url = "https://roll.lastgimbus.com/api"
38
+
39
+ async with aiohttp.ClientSession() as session:
40
+ try:
41
+ # Attempt to get a simple result first
42
+ # Research mentioned 'getRandomNumber' logic which suggests a GET might trigger it
43
+ # or we need to check a status.
44
+ # Trying the most likely 'roll' endpoint or root.
45
+ async with session.get(f"{base_url}/roll") as response:
46
+ if response.status == 200:
47
+ data = await response.json()
48
+ # Expecting 'result' or 'number'
49
+ return data.get("result", data.get("number", None))
50
+
51
+ # If 202 (Accepted/Queued), we might need to wait.
52
+ # Capturing status for debugging
53
+ if response.status != 404:
54
+ print(f"Roll-API Status: {response.status}")
55
+
56
+ # Fallback check on root if /roll fails
57
+ async with session.get(base_url) as response:
58
+ if response.status == 200:
59
+ text = await response.text()
60
+ # If it's just a number string
61
+ if text.isdigit():
62
+ return int(text)
63
+ # If json
64
+ try:
65
+ data = await response.json()
66
+ return data.get("val", data.get("result"))
67
+ except:
68
+ pass
69
+
70
+ return None
71
+
72
+ except Exception as e:
73
+ print(f"Roll-API Exception: {e}")
74
+ return None
@@ -0,0 +1,93 @@
1
+ import pyautogui
2
+ import time
3
+ import ctypes
4
+ import random
5
+
6
+ # Windows constants for mouse buttons
7
+ VK_LBUTTON = 0x01
8
+ VK_RBUTTON = 0x02
9
+ VK_MBUTTON = 0x04
10
+
11
+ def is_left_click_pressed():
12
+ """Checks if the left mouse button is currently down using Windows API."""
13
+ return ctypes.windll.user32.GetAsyncKeyState(VK_LBUTTON) & 0x8000
14
+
15
+ def is_right_click_pressed():
16
+ """Checks if the right mouse button is currently down using Windows API."""
17
+ return ctypes.windll.user32.GetAsyncKeyState(VK_RBUTTON) & 0x8000
18
+
19
+ def is_middle_click_pressed():
20
+ """Checks if the middle mouse button is currently down using Windows API."""
21
+ return ctypes.windll.user32.GetAsyncKeyState(VK_MBUTTON) & 0x8000
22
+
23
+ def get_click_data(button_check_func=is_left_click_pressed):
24
+ """Waits for a specific click and returns (timestamp, cursor_position)."""
25
+ # Wait for the button to be pressed
26
+ while not button_check_func():
27
+ time.sleep(0.005) # Tiny sleep to prevent high CPU usage
28
+
29
+ t = time.perf_counter()
30
+ pos = pyautogui.position()
31
+
32
+ # Wait for the button to be released to avoid duplicate captures
33
+ while button_check_func():
34
+ time.sleep(0.005)
35
+
36
+ return t, pos
37
+
38
+ class RealRandomUsingBetweenLeftClicks(random.Random):
39
+ def seed(self, a=None, version=2):
40
+ if a is None:
41
+ t1, p1 = get_click_data(is_left_click_pressed)
42
+ t2, p2 = get_click_data(is_left_click_pressed)
43
+ # Use the time interval and positions to create a seed without hashing
44
+ a = int((t2 - t1) * 10**9) + p1.x + p1.y + p2.x + p2.y
45
+ super().seed(a, version=version)
46
+
47
+ class RealRandomUsingBetweenRightClicks(random.Random):
48
+ def seed(self, a=None, version=2):
49
+ if a is None:
50
+ t1, p1 = get_click_data(is_right_click_pressed)
51
+ t2, p2 = get_click_data(is_right_click_pressed)
52
+ a = int((t2 - t1) * 10**9) + p1.x + p1.y + p2.x + p2.y
53
+ super().seed(a, version=version)
54
+
55
+ class RealRandomUsingBetweenMiddleClicks(random.Random):
56
+ def seed(self, a=None, version=2):
57
+ if a is None:
58
+ t1, p1 = get_click_data(is_middle_click_pressed)
59
+ t2, p2 = get_click_data(is_middle_click_pressed)
60
+ a = int((t2 - t1) * 10**9) + p1.x + p1.y + p2.x + p2.y
61
+ super().seed(a, version=version)
62
+
63
+ class RealRandomUsingBetweenMixedClicks(random.Random):
64
+ def seed(self, a=None, version=2):
65
+ if a is None:
66
+ # Mixed logic: Left -> Right -> Middle
67
+ t1, p1 = get_click_data(is_left_click_pressed)
68
+ t2, p2 = get_click_data(is_right_click_pressed)
69
+ t3, p3 = get_click_data(is_middle_click_pressed)
70
+ a = int((t3 - t1) * 10**9) + p1.x + p1.y + p2.x + p2.y + p3.x + p3.y
71
+ super().seed(a, version=version)
72
+
73
+ def main():
74
+ print("--- Click-based Randomness Test ---")
75
+ print("Each generator requires specific mouse clicks to seed.")
76
+
77
+ print("\n1. Seeding Left-Click Random (requires 2 left clicks)...")
78
+ rl = RealRandomUsingBetweenLeftClicks()
79
+ print(f"Result: {rl.random()}")
80
+
81
+ print("\n2. Seeding Right-Click Random (requires 2 right clicks)...")
82
+ rr = RealRandomUsingBetweenRightClicks()
83
+ print(f"Result: {rr.random()}")
84
+
85
+ print("\n3. Seeding Mixed-Click Random (requires Left -> Right -> Middle)...")
86
+ rm = RealRandomUsingBetweenMixedClicks()
87
+ print(f"Result: {rm.random()}")
88
+
89
+ if __name__ == "__main__":
90
+ try:
91
+ main()
92
+ except KeyboardInterrupt:
93
+ print("\nStopped by user.")
@@ -0,0 +1,2 @@
1
+ # rand via openweather api and openmeteo api
2
+ print("coming soon")
@@ -0,0 +1,2 @@
1
+ import rand_using_virt_lcg as LCG
2
+ import xor_shift
@@ -0,0 +1 @@
1
+ print("coming soon :/")
@@ -0,0 +1,93 @@
1
+ import random
2
+ import warnings
3
+
4
+ # Safety Warnings
5
+ warnings.warn("WARNING: LCGs are NOT cryptographically secure. They use linear algebra and can be reversed.")
6
+ warnings.warn("This is only safe for OFFLINE GAMES with NO LEADERBOARDS.")
7
+ warnings.warn("See: https://youtu.be/XDsYPXRCXAs?si=oDaFsqZyNJWVXwEi")
8
+
9
+ class LCGs:
10
+ """
11
+ Linear Congruential Generator (LCG) Implementation.
12
+
13
+ Summary:
14
+ This class implements a Linear Congruential Generator, a simple algorithm that yields a
15
+ sequence of pseudo-randomized numbers calculated with a linear equation.
16
+
17
+ WARNING:
18
+ LCGs are NOT safe for cryptography or high-stakes randomness (like gambling or leaderboards).
19
+ Because they are based on simple math (modular arithmetic), their state can be recovered
20
+ (reversed) if a few outputs are known, e.g., using Modular Multiplicative Inverse (MMI).
21
+
22
+ Required args:
23
+ rules: str - The forward formula, e.g., "(s * a + c) % d"
24
+
25
+ Optional args:
26
+ reverse_rules: str or None - The formula to reverse the state, proving insecurity.
27
+ e.g., "((s - c) * pow(a, -1, d)) % d"
28
+ s: int - Seed/State
29
+ a: int - Multiplier
30
+ c: int - Increment
31
+ d: int - Modulus
32
+ """
33
+ def __init__(
34
+ self,
35
+ rules: str,
36
+ reverse_rules: str or None = None,
37
+ s: int = random.randint(1, 9999),
38
+ a: int = 10,
39
+ b: int = 3, # kept for backward compatibility if needed
40
+ c: int = 5,
41
+ d: int = 25565
42
+ ):
43
+ if rules is None:
44
+ raise ValueError("rules must be provided (e.g., '(s * a + c) % d')")
45
+
46
+ self.rules = rules
47
+ self.reverse_rules = reverse_rules
48
+ self.s = s
49
+ self.a = a
50
+ self.b = b
51
+ self.c = c
52
+ self.d = d
53
+
54
+ def roll(self):
55
+ """Advance the generator state forward."""
56
+ # Standard eval with restricted scope
57
+ self.s = eval(self.rules, {"__builtins__": None}, {
58
+ 's': self.s, 'a': self.a, 'b': self.b,
59
+ 'c': self.c, 'd': self.d
60
+ })
61
+ return self.s
62
+
63
+ def reverse_roll(self):
64
+ """
65
+ Reverse the generator state backward.
66
+
67
+ This method demonstrates the insecurity of LCGs. If you know the parameters (a, c, d)
68
+ and the current state (s), you can calculate the PREVIOUS state using:
69
+ s_prev = (s - c) * a^(-1) mod d
70
+ """
71
+ if self.reverse_rules is None:
72
+ raise ValueError("reverse_rules is not defined! Cannot reverse state.")
73
+
74
+ # Using pow(a, -1, d) for modular multiplicative inverse in python 3.8+
75
+ self.s = eval(self.reverse_rules, {"__builtins__": None, "pow": pow}, {
76
+ 's': self.s, 'a': self.a, 'b': self.b,
77
+ 'c': self.c, 'd': self.d
78
+ })
79
+ return self.s
80
+
81
+ def random(self, s: int or None = None):
82
+ """Predict next bit/value based on roll (0 or 1)."""
83
+ if s is not None:
84
+ self.s = s
85
+ old_s = self.s
86
+ self.roll()
87
+ return 1 if old_s <= self.s else 0
88
+
89
+ def randint(self):
90
+ """Returns the current raw state integer."""
91
+ self.roll()
92
+ return self.s
93
+
@@ -0,0 +1,235 @@
1
+ import random
2
+ import warnings
3
+
4
+ # Safety Warnings
5
+ warnings.warn("WARNING: XorShift/Xoshiro generators are SAFER than LCGs but still INSECURE for cryptography. They are pseudo-random and not true randomness.")
6
+ warnings.warn("It uses a linear feedback shift register (LFSR) to generate pseudo-random numbers.")
7
+ warnings.warn("See: https://youtu.be/XDsYPXRCXAs?si=oDaFsqZyNJWVXwEi")
8
+
9
+ def reverse_xor_lshift(x, s, bits=32):
10
+ """Reverses x ^= x << s."""
11
+ res = x
12
+ mask = (1 << bits) - 1
13
+ for _ in range(bits // s):
14
+ res = x ^ ((res << s) & mask)
15
+ return res & mask
16
+
17
+ def reverse_xor_rshift(x, s, bits=32):
18
+ """Reverses x ^= x >> s."""
19
+ res = x
20
+ mask = (1 << bits) - 1
21
+ for _ in range(bits // s):
22
+ res = x ^ (res >> s)
23
+ return res & mask
24
+
25
+ class Xorshift32:
26
+ """
27
+ Marsaglia's original 32-bit Xorshift.
28
+ Passes some tests but too linear for modern standards.
29
+ """
30
+ def __init__(self, seed: int = None, a: int = 13, b: int = 17, c: int = 5):
31
+ if seed is None:
32
+ seed = random.randint(1, 0xFFFFFFFF)
33
+ self.state = seed & 0xFFFFFFFF
34
+ if self.state == 0: self.state = 1
35
+ self.a = a
36
+ self.b = b
37
+ self.c = c
38
+
39
+ def roll(self):
40
+ x = self.state
41
+ x ^= (x << self.a) & 0xFFFFFFFF
42
+ x ^= (x >> self.b) & 0xFFFFFFFF
43
+ x ^= (x << self.c) & 0xFFFFFFFF
44
+ self.state = x
45
+ return self.state
46
+
47
+ def reverse_roll(self):
48
+ """Demonstrates the reversibility of pure Xorshift."""
49
+ x = self.state
50
+ # Reverse x ^= x << c
51
+ x = reverse_xor_lshift(x, self.c, 32)
52
+ # Reverse x ^= x >> b
53
+ x = reverse_xor_rshift(x, self.b, 32)
54
+ # Reverse x ^= x << a
55
+ x = reverse_xor_lshift(x, self.a, 32)
56
+ self.state = x
57
+ return self.state
58
+
59
+ def random(self):
60
+ # Normalize to [0, 1)
61
+ return self.roll() / 0x100000000
62
+
63
+ class Xorshift128:
64
+ """
65
+ Marsaglia's original 128-bit Xorshift.
66
+ Uses a 4-word state.
67
+ """
68
+ def __init__(self, seeds: list = None, a: int = 11, b: int = 8, c: int = 19):
69
+ if seeds is None or len(seeds) < 4:
70
+ seeds = [random.randint(1, 0xFFFFFFFF) for _ in range(4)]
71
+ self.state = [s & 0xFFFFFFFF for s in seeds]
72
+ if all(s == 0 for s in self.state): self.state[0] = 1
73
+ self.a = a
74
+ self.b = b
75
+ self.c = c
76
+
77
+ def roll(self):
78
+ t = self.state[3]
79
+ s = self.state[0]
80
+ self.state[3] = self.state[2]
81
+ self.state[2] = self.state[1]
82
+ self.state[1] = s
83
+
84
+ t ^= (t << self.a) & 0xFFFFFFFF
85
+ t ^= (t >> self.b) & 0xFFFFFFFF
86
+ self.state[0] = (t ^ s ^ (s >> self.c)) & 0xFFFFFFFF
87
+ return self.state[0]
88
+
89
+ def reverse_roll(self):
90
+ """Demonstrates reversibility of 128-bit version."""
91
+ s_prev = self.state[1]
92
+ res_xor_s = (self.state[0] ^ s_prev ^ (s_prev >> self.c)) & 0xFFFFFFFF
93
+ t_original = reverse_xor_rshift(reverse_xor_lshift(res_xor_s, self.a, 32), self.b, 32)
94
+
95
+ # Shift back
96
+ self.state[0] = self.state[1]
97
+ self.state[1] = self.state[2]
98
+ self.state[2] = self.state[3]
99
+ self.state[3] = t_original
100
+ return self.state[0]
101
+
102
+ class Xorwow:
103
+ """Xorwow adds a Weyl sequence to fix linearity issues."""
104
+ def __init__(self, seeds: list = None):
105
+ if seeds is None or len(seeds) < 5:
106
+ seeds = [random.randint(1, 0xFFFFFFFF) for _ in range(5)]
107
+ self.state = [s & 0xFFFFFFFF for s in seeds]
108
+ self.counter = 0
109
+
110
+ def roll(self):
111
+ t = self.state[4]
112
+ s = self.state[0]
113
+ self.state[4] = self.state[3]
114
+ self.state[3] = self.state[2]
115
+ self.state[2] = self.state[1]
116
+ self.state[1] = s
117
+
118
+ t ^= (t >> 2) & 0xFFFFFFFF
119
+ t ^= (t << 1) & 0xFFFFFFFF
120
+ t ^= (s ^ (s << 4)) & 0xFFFFFFFF
121
+ self.state[0] = t
122
+ self.counter = (self.counter + 362437) & 0xFFFFFFFF
123
+ return (t + self.counter) & 0xFFFFFFFF
124
+
125
+ class XorshiftStar:
126
+ """Xorshift* multiplies outputs by a constant."""
127
+ def __init__(self, seed: int = None, a: int = 12, b: int = 25, c: int = 27):
128
+ self.state = seed or random.randint(1, 0xFFFFFFFFFFFFFFFF)
129
+ self.a = a
130
+ self.b = b
131
+ self.c = c
132
+
133
+ def roll(self):
134
+ x = self.state
135
+ x ^= (x >> self.a) & 0xFFFFFFFFFFFFFFFF
136
+ x ^= (x << self.b) & 0xFFFFFFFFFFFFFFFF
137
+ x ^= (x >> self.c) & 0xFFFFFFFFFFFFFFFF
138
+ self.state = x
139
+ return (x * 0x2545F4914F6CDD1D) & 0xFFFFFFFFFFFFFFFF
140
+
141
+ class XorshiftPlus:
142
+ """Xorshift128+ used in V8 and Safari."""
143
+ def __init__(self, s0: int = None, s1: int = None, a: int = 23, b: int = 17, c: int = 26):
144
+ self.s0 = s0 or random.randint(1, 0xFFFFFFFFFFFFFFFF)
145
+ self.s1 = s1 or random.randint(1, 0xFFFFFFFFFFFFFFFF)
146
+ self.a = a
147
+ self.b = b
148
+ self.c = c
149
+
150
+ def roll(self):
151
+ x = self.s0
152
+ y = self.s1
153
+ self.s0 = y
154
+ x ^= (x << self.a) & 0xFFFFFFFFFFFFFFFF
155
+ self.s1 = (x ^ y ^ (x >> self.b) ^ (y >> self.c)) & 0xFFFFFFFFFFFFFFFF
156
+ return (self.s1 + y) & 0xFFFFFFFFFFFFFFFF
157
+
158
+ def rotl(x, k, bits=64):
159
+ mask = (1 << bits) - 1
160
+ return ((x << k) & mask) | (x >> (bits - k))
161
+
162
+ class Xoshiro256StarStar:
163
+ """Modern robust 64-bit generator."""
164
+ def __init__(self, seeds: list = None):
165
+ if seeds is None or len(seeds) < 4:
166
+ seeds = [random.randint(1, 0xFFFFFFFFFFFFFFFF) for _ in range(4)]
167
+ self.s = [s & 0xFFFFFFFFFFFFFFFF for s in seeds]
168
+
169
+ def roll(self):
170
+ res = (rotl((self.s[1] * 5) & 0xFFFFFFFFFFFFFFFF, 7) * 9) & 0xFFFFFFFFFFFFFFFF
171
+ t = (self.s[1] << 17) & 0xFFFFFFFFFFFFFFFF
172
+
173
+ self.s[2] ^= self.s[0]
174
+ self.s[3] ^= self.s[1]
175
+ self.s[1] ^= self.s[2]
176
+ self.s[0] ^= self.s[3]
177
+ self.s[2] ^= t
178
+ self.s[3] = rotl(self.s[3], 45)
179
+
180
+ return res
181
+
182
+ class Xoshiro128Plus:
183
+ """32-bit optimized modern generator."""
184
+ def __init__(self, seeds: list = None):
185
+ if seeds is None or len(seeds) < 4:
186
+ seeds = [random.randint(1, 0xFFFFFFFF) for _ in range(4)]
187
+ self.s = [s & 0xFFFFFFFF for s in seeds]
188
+
189
+ def roll(self):
190
+ res = (self.s[0] + self.s[3]) & 0xFFFFFFFF
191
+ t = (self.s[1] << 9) & 0xFFFFFFFF
192
+
193
+ self.s[2] ^= self.s[0]
194
+ self.s[3] ^= self.s[1]
195
+ self.s[1] ^= self.s[2]
196
+ self.s[0] ^= self.s[3]
197
+ self.s[2] ^= t
198
+ self.s[3] = rotl(self.s[3], 11, bits=32)
199
+
200
+ return res
201
+
202
+ class Xoroshiro128PlusPlus:
203
+ """
204
+ Xoroshiro128++ generator.
205
+ Used in modern Minecraft (Java 1.18+) for world generation and other random tasks.
206
+ """
207
+ def __init__(self, seeds: list = None):
208
+ if seeds is None or len(seeds) < 2:
209
+ seeds = [random.randint(1, 0xFFFFFFFFFFFFFFFF) for _ in range(2)]
210
+ self.s = [s & 0xFFFFFFFFFFFFFFFF for s in seeds]
211
+ if all(s == 0 for s in self.s):
212
+ self.s[0] = 1
213
+
214
+ def roll(self):
215
+ s0 = self.s[0]
216
+ s1 = self.s[1]
217
+ res = (rotl((s0 + s1) & 0xFFFFFFFFFFFFFFFF, 17) + s0) & 0xFFFFFFFFFFFFFFFF
218
+
219
+ s1 ^= s0
220
+ self.s[0] = (rotl(s0, 49) ^ s1 ^ ((s1 << 21) & 0xFFFFFFFFFFFFFFFF)) & 0xFFFFFFFFFFFFFFFF
221
+ self.s[1] = rotl(s1, 28)
222
+
223
+ return res
224
+
225
+
226
+ if __name__ == "__main__":
227
+ print("Testing Xorshift32 Reverse...")
228
+ gen = Xorshift32(42)
229
+ original = gen.state
230
+ gen.roll()
231
+ print(f"Post Roll: {gen.state}")
232
+ gen.reverse_roll()
233
+ print(f"Post Reverse: {gen.state}")
234
+ assert gen.state == original
235
+ print("Success!")