Skip to content

Commit 0f862ec

Browse files
committed
feat(hybrid): implement HybridTap with PRNG seeded by entropy pool
1 parent 72bf4d0 commit 0f862ec

1 file changed

Lines changed: 147 additions & 0 deletions

File tree

src/trueentropy/hybrid.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# =============================================================================
2+
# TrueEntropy - Hybrid Entopy Tap
3+
# =============================================================================
4+
#
5+
# The Hybrid Tap provides a high-performance Pseudo-Random Number Generator (PRNG)
6+
# that is regularly re-seeded with true entropy from the pool.
7+
#
8+
# This architecture combines the best of both worlds:
9+
# 1. Performance: Uses Python's optimized Mersenne Twister (via random.Random)
10+
# 2. Resiliency: Even if sources fail, the PRNG continues to function
11+
# 3. Security: Regular re-seeding prevents long-term predictability
12+
#
13+
# =============================================================================
14+
15+
"""
16+
Hybrid Tap - PRNG seeded by TrueEntropy pool.
17+
"""
18+
19+
from __future__ import annotations
20+
21+
import random
22+
import time
23+
from typing import Any
24+
25+
from trueentropy.pool import EntropyPool
26+
from trueentropy.tap import BaseTap
27+
28+
29+
class HybridTap(BaseTap):
30+
"""
31+
High-performance PRNG seeded by true entropy.
32+
33+
This tap uses Python's standard `random.Random` for generation,
34+
but re-seeds it periodically using entropy extracted from the
35+
TrueEntropy pool.
36+
37+
Ideally used for:
38+
- High-volume simulations
39+
- Games and UI effects
40+
- Scenarios where performance < 1ms is critical
41+
- Resilience against temporary entropy source failures
42+
"""
43+
44+
def __init__(
45+
self,
46+
pool: EntropyPool,
47+
reseed_interval: float = 60.0,
48+
reseed_on_init: bool = True,
49+
) -> None:
50+
"""
51+
Initialize the hybrid tap.
52+
53+
Args:
54+
pool: The EntropyPool to use for seeding
55+
reseed_interval: Seconds between automatic re-seeds (default: 60)
56+
reseed_on_init: Whether to pull seed immediately (default: True)
57+
"""
58+
self._pool = pool
59+
self._reseed_interval = reseed_interval
60+
self._last_reseed_time = 0.0
61+
62+
# Dedicated PRNG instance to avoid sharing state with global random
63+
self._prng = random.Random()
64+
65+
if reseed_on_init:
66+
self.reseed()
67+
68+
def reseed(self) -> None:
69+
"""
70+
Force a re-seed of the internal PRNG from the entropy pool.
71+
72+
Extracts 32 bytes (256 bits) from the pool to seed the PRNG.
73+
This operation blocks briefly while extracting from the pool.
74+
"""
75+
# Extract 32 bytes of high-quality entropy
76+
seed_data = self._pool.extract(32)
77+
78+
# Seed the PRNG
79+
self._prng.seed(seed_data)
80+
81+
# Update timestamp
82+
self._last_reseed_time = time.time()
83+
84+
def _check_reseed(self) -> None:
85+
"""Check if re-seed is needed and perform it if so."""
86+
if time.time() - self._last_reseed_time > self._reseed_interval:
87+
self.reseed()
88+
89+
# -------------------------------------------------------------------------
90+
# BaseTap Implementation
91+
# -------------------------------------------------------------------------
92+
93+
def random(self) -> float:
94+
"""Generate a random float in [0.0, 1.0)."""
95+
self._check_reseed()
96+
return self._prng.random()
97+
98+
def randint(self, a: int, b: int) -> int:
99+
"""Generate a random integer N such that a <= N <= b."""
100+
self._check_reseed()
101+
return self._prng.randint(a, b)
102+
103+
def randbytes(self, n: int) -> bytes:
104+
"""Generate n random bytes."""
105+
self._check_reseed()
106+
# random.randbytes was added in Python 3.9
107+
# For compatibility, we use getrandbits if randbytes is missing (older python 3)
108+
if hasattr(self._prng, "randbytes"):
109+
return self._prng.randbytes(n)
110+
else:
111+
# Fallback for older python versions
112+
return self._prng.getrandbits(n * 8).to_bytes(n, "little")
113+
114+
# -------------------------------------------------------------------------
115+
# Optimized Overrides
116+
# -------------------------------------------------------------------------
117+
# We override these methods because random.Random usually implements them
118+
# in C for better performance than our generic BaseTap implementation.
119+
120+
def choice(self, seq: Any) -> Any:
121+
self._check_reseed()
122+
return self._prng.choice(seq)
123+
124+
def shuffle(self, x: Any) -> None:
125+
self._check_reseed()
126+
return self._prng.shuffle(x)
127+
128+
def sample(self, population: Any, k: int, **kwargs) -> list[Any]:
129+
self._check_reseed()
130+
# Support 'counts' keyword arg in newer python versions
131+
return self._prng.sample(population, k, **kwargs)
132+
133+
def uniform(self, a: float, b: float) -> float:
134+
self._check_reseed()
135+
return self._prng.uniform(a, b)
136+
137+
def gauss(self, mu: float = 0.0, sigma: float = 1.0) -> float:
138+
self._check_reseed()
139+
return self._prng.gauss(mu, sigma)
140+
141+
def triangular(self, low: float = 0.0, high: float = 1.0, mode: float | None = None) -> float:
142+
self._check_reseed()
143+
return self._prng.triangular(low, high, mode)
144+
145+
def exponential(self, lambd: float = 1.0) -> float:
146+
self._check_reseed()
147+
return self._prng.expovariate(lambd)

0 commit comments

Comments
 (0)