Skip to content

Commit 8a328d5

Browse files
authored
Merge pull request #1 from medeirosdev/feature/hybrid-mode
Feature/hybrid mode
2 parents a194673 + 4b08cb7 commit 8a328d5

13 files changed

Lines changed: 1135 additions & 594 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,6 @@ Thumbs.db
9696
.env.local
9797
.env.*.local
9898
secrets.json
99+
100+
# Demos
101+
demo_*.py

ARCHITECTURE.md

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,80 @@ TrueEntropy harvests entropy from real-world sources and converts it into crypto
3333

3434
---
3535

36+
## Operation Modes
37+
38+
TrueEntropy supports two operation modes, selectable via `configure(mode=...)`:
39+
40+
### Mode Comparison
41+
42+
| Aspect | DIRECT Mode | HYBRID Mode |
43+
|--------|-------------|-------------|
44+
| **Speed** | ~60K ops/sec | ~5M ops/sec |
45+
| **Source** | Direct pool extraction | PRNG seeded by pool |
46+
| **Security** | Maximum (true random) | High (periodic reseed) |
47+
| **Use Case** | Crypto keys, wallets | Simulations, games |
48+
49+
### DIRECT Mode (Default)
50+
51+
Every call extracts fresh entropy directly from the pool:
52+
53+
```
54+
trueentropy.random() ──► EntropyTap.random() ──► pool.extract(8) ──► float
55+
```
56+
57+
```
58+
┌────────────────────────────────────────────────────────────────────┐
59+
│ DIRECT MODE │
60+
├────────────────────────────────────────────────────────────────────┤
61+
│ │
62+
│ random() ───► EntropyTap ───► Pool ───► 8 bytes ───► float │
63+
│ │ │
64+
│ ▲ │
65+
│ Harvesters │
66+
│ │
67+
└────────────────────────────────────────────────────────────────────┘
68+
```
69+
70+
### HYBRID Mode
71+
72+
Uses a fast PRNG (Mersenne Twister) that re-seeds from the pool periodically:
73+
74+
```
75+
trueentropy.random() ──► HybridTap.random() ──► PRNG.random() ──► float
76+
77+
└──► (every N seconds) ──► pool.extract(32) ──► PRNG.seed()
78+
```
79+
80+
```
81+
┌────────────────────────────────────────────────────────────────────┐
82+
│ HYBRID MODE │
83+
├────────────────────────────────────────────────────────────────────┤
84+
│ │
85+
│ random() ───► HybridTap ───► PRNG (Mersenne Twister) ───► float │
86+
│ │ │
87+
│ ▼ (periodic reseed) │
88+
│ Pool ◄────── Harvesters │
89+
│ │ │
90+
│ └──► 32 bytes ──► PRNG.seed() │
91+
│ │
92+
└────────────────────────────────────────────────────────────────────┘
93+
```
94+
95+
### Configuration
96+
97+
```python
98+
import trueentropy
99+
100+
# DIRECT mode (default) - maximum security
101+
trueentropy.configure(mode="DIRECT")
102+
103+
# HYBRID mode - maximum performance
104+
trueentropy.configure(mode="HYBRID", hybrid_reseed_interval=60.0)
105+
106+
# Combined: Hybrid + Offline (fast, no network)
107+
trueentropy.configure(mode="HYBRID", offline_mode=True)
108+
```
109+
36110
## Entropy Sources
37111

38112
### 1. Timing Jitter (timing.py)
@@ -300,7 +374,9 @@ Guarantees all N! permutations are equally probable.
300374
| Module | Purpose |
301375
|--------|---------|
302376
| `pool.py` | Accumulates and mixes entropy with SHA-256 |
303-
| `tap.py` | Extracts entropy and converts to types |
377+
| `tap.py` | `BaseTap` abstract class + `EntropyTap` (DIRECT mode) |
378+
| `hybrid.py` | `HybridTap` for HYBRID mode (PRNG seeded by pool) |
379+
| `config.py` | `TrueEntropyConfig` dataclass + `configure()` function |
304380
| `collector.py` | Background thread for automatic collection |
305381
| `health.py` | Monitors pool health (score 0-100) |
306382
| `harvesters/` | Collectors for different entropy sources |

README.md

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,43 @@ print_health_report(get_pool())
149149
> **Note**: Offline mode provides reduced entropy diversity. For security-critical applications, consider using all available sources when network access is available.
150150
151151

152+
## Direct vs Hybrid Mode
153+
154+
TrueEntropy offers two modes of operation to balance security and performance:
155+
156+
| Mode | Entropy Source | Performance | Use Case |
157+
|------|---------------|-------------|----------|
158+
| **DIRECT** (Default) | Directly from entropy pool | Slower, blocking | Cryptographic keys, wallets, severe security |
159+
| **HYBRID** | PRNG seeded by pool | Extremely fast | Simulations, games, UI, general purpose |
160+
161+
### Using Hybrid Mode
162+
163+
Hybrid mode uses the TrueEntropy pool to periodically re-seed a fast pseudo-random number generator (PRNG). This provides the best of both worlds: the speed of standard Python random numbers with the entropy quality of our harvesters.
164+
165+
```python
166+
import trueentropy
167+
168+
# Configure Hybrid Mode (re-seed every 60 seconds)
169+
trueentropy.configure(mode="HYBRID", hybrid_reseed_interval=60.0)
170+
171+
# Generate numbers at max speed
172+
# The internal PRNG is automatically re-seeded from the entropy pool
173+
for _ in range(1000000):
174+
val = trueentropy.random()
175+
```
176+
177+
### Tuning Hybrid Mode
178+
179+
The `hybrid_reseed_interval` should be chosen based on your `offline_mode` setting:
180+
181+
* **Online (Default)**: Network harvesters take time to collect entropy (latency). Set the interval to **10.0s or higher** to allow the pool to refill between reseeds.
182+
* **Offline (`offline_mode=True`)**: Local sources are near-instant. You can use lower intervals (e.g., **1.0s - 2.0s**) for frequent reseeding.
183+
184+
> **Tip**: If `health()` reports degraded status with low entropy bits, increase your reseed interval.
185+
186+
187+
188+
152189
## Advanced Features
153190

154191
### Async Support
@@ -278,10 +315,14 @@ True random numbers from quantum phenomena:
278315

279316
| Function | Description |
280317
|----------|-------------|
318+
| `configure(...)` | Set mode (DIRECT/HYBRID), offline_mode, enable sources |
319+
| `reset_config()` | Reset configuration to defaults |
281320
| `health()` | Returns entropy pool health status |
282321
| `start_collector(interval)` | Starts background entropy collection |
283322
| `stop_collector()` | Stops background collection |
284323
| `feed(data)` | Manually feed entropy into the pool |
324+
| `get_tap()` | Get current tap instance (EntropyTap or HybridTap) |
325+
| `get_pool()` | Get the global entropy pool instance |
285326

286327
## How It Works
287328

@@ -307,7 +348,7 @@ True random numbers from quantum phenomena:
307348
| v |
308349
| +-----------+ |
309350
| | EXTRACTOR | Secure extraction |
310-
| | (Tap) | Depletion protection |
351+
| | (Tap) | DIRECT or HYBRID mode |
311352
| +-----+-----+ |
312353
| v |
313354
| +---------------+--------------+ |

src/trueentropy/__init__.py

Lines changed: 98 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,24 @@
4848
# Type Imports (for type hints)
4949
# -----------------------------------------------------------------------------
5050
from collections.abc import MutableSequence, Sequence
51-
from typing import TYPE_CHECKING, Any, TypeVar
51+
from typing import TYPE_CHECKING, Any, Literal, TypeVar
5252

5353
if TYPE_CHECKING:
5454
pass
5555

5656
# -----------------------------------------------------------------------------
5757
# Internal Module Imports
5858
# -----------------------------------------------------------------------------
59+
import trueentropy.config as _config_module
5960
from trueentropy.config import (
6061
TrueEntropyConfig,
61-
configure,
6262
get_config,
6363
reset_config,
6464
)
6565
from trueentropy.health import HealthStatus, entropy_health
66+
from trueentropy.hybrid import HybridTap
6667
from trueentropy.pool import EntropyPool
67-
from trueentropy.tap import EntropyTap
68+
from trueentropy.tap import BaseTap, EntropyTap
6869

6970
# -----------------------------------------------------------------------------
7071
# Type Variables for Generic Functions
@@ -78,12 +79,96 @@
7879
# Users can also create their own instances if needed.
7980

8081
_pool: EntropyPool = EntropyPool()
81-
_tap: EntropyTap = EntropyTap(_pool)
82+
# _tap is initialized with EntropyTap (DIRECT mode) by default
83+
_tap: BaseTap = EntropyTap(_pool)
8284

8385
# Flag to track if background collector is running
8486
_collector_running: bool = False
8587

8688

89+
# -----------------------------------------------------------------------------
90+
# Configuration Helper
91+
# -----------------------------------------------------------------------------
92+
93+
94+
def _update_tap() -> None:
95+
"""
96+
Update the global _tap instance based on current configuration.
97+
98+
Switches between EntropyTap (DIRECT) and HybridTap (HYBRID).
99+
"""
100+
global _tap
101+
config = get_config()
102+
103+
if config.mode == "HYBRID":
104+
# Create new HybridTap if not already one
105+
if not isinstance(_tap, HybridTap):
106+
_tap = HybridTap(_pool, reseed_interval=config.hybrid_reseed_interval)
107+
else:
108+
# Update existing HybridTap interval
109+
_tap._reseed_interval = config.hybrid_reseed_interval
110+
else:
111+
# Default to DIRECT mode (EntropyTap)
112+
if not isinstance(_tap, EntropyTap):
113+
_tap = EntropyTap(_pool)
114+
115+
116+
def configure(
117+
*,
118+
mode: Literal["DIRECT", "HYBRID"] | None = None,
119+
hybrid_reseed_interval: float | None = None,
120+
offline_mode: bool | None = None,
121+
enable_timing: bool | None = None,
122+
enable_system: bool | None = None,
123+
enable_network: bool | None = None,
124+
enable_external: bool | None = None,
125+
enable_weather: bool | None = None,
126+
enable_radioactive: bool | None = None,
127+
) -> TrueEntropyConfig:
128+
"""
129+
Configure TrueEntropy globally.
130+
131+
This function updates the global configuration and switches operation mode
132+
(DIRECT vs HYBRID) if requested.
133+
134+
Args:
135+
mode: Operation mode ("DIRECT" or "HYBRID")
136+
hybrid_reseed_interval: Seconds between re-seeds in HYBRID mode
137+
offline_mode: If True, disables all network-dependent sources.
138+
enable_timing: Enable/disable CPU timing harvester
139+
enable_system: Enable/disable system state harvester
140+
enable_network: Enable/disable network latency harvester
141+
enable_external: Enable/disable external API harvester
142+
enable_weather: Enable/disable weather data harvester
143+
enable_radioactive: Enable/disable quantum randomness harvester
144+
145+
Returns:
146+
The updated global configuration
147+
148+
Example:
149+
>>> import trueentropy
150+
>>> # Switch to Hybrid Mode (faster)
151+
>>> trueentropy.configure(mode="HYBRID")
152+
"""
153+
# Call the underlying config update
154+
cfg = _config_module.configure(
155+
mode=mode,
156+
hybrid_reseed_interval=hybrid_reseed_interval,
157+
offline_mode=offline_mode,
158+
enable_timing=enable_timing,
159+
enable_system=enable_system,
160+
enable_network=enable_network,
161+
enable_external=enable_external,
162+
enable_weather=enable_weather,
163+
enable_radioactive=enable_radioactive,
164+
)
165+
166+
# Update the active tap based on new config
167+
_update_tap()
168+
169+
return cfg
170+
171+
87172
# =============================================================================
88173
# PUBLIC API - Random Value Generation
89174
# =============================================================================
@@ -93,9 +178,7 @@ def random() -> float:
93178
"""
94179
Generate a random floating-point number in the range [0.0, 1.0).
95180
96-
This function extracts entropy from the pool and converts it to a
97-
uniformly distributed float. The distribution is uniform, meaning
98-
all values in the range are equally likely.
181+
This function uses the currently configured tap (DIRECT or HYBRID).
99182
100183
Returns:
101184
A float value where 0.0 <= value < 1.0
@@ -182,9 +265,8 @@ def randbytes(n: int) -> bytes:
182265
"""
183266
Generate n random bytes.
184267
185-
This function extracts raw entropy from the pool and returns it
186-
as a bytes object. Useful for generating cryptographic keys,
187-
tokens, or other binary data.
268+
This function extracts raw entropy (or PRNG bytes in Hybrid mode).
269+
Useful for generating cryptographic keys, tokens, or other binary data.
188270
189271
Args:
190272
n: The number of bytes to generate (must be positive)
@@ -208,8 +290,7 @@ def shuffle(seq: MutableSequence[Any]) -> None:
208290
"""
209291
Shuffle a mutable sequence in-place.
210292
211-
Uses the Fisher-Yates shuffle algorithm with true random numbers
212-
to ensure a uniform distribution of permutations.
293+
Uses the Fisher-Yates shuffle algorithm.
213294
214295
Args:
215296
seq: A mutable sequence (list) to shuffle in-place
@@ -503,15 +584,15 @@ def get_pool() -> EntropyPool:
503584
return _pool
504585

505586

506-
def get_tap() -> EntropyTap:
587+
def get_tap() -> BaseTap:
507588
"""
508589
Get the global entropy tap instance.
509590
510591
This is useful for advanced users who want to use the tap
511-
directly or create their own tap with custom settings.
592+
directly or inspect which implementation (BaseTap/HybridTap) is active.
512593
513594
Returns:
514-
The global EntropyTap instance
595+
The global BaseTap instance (EntropyTap or HybridTap)
515596
"""
516597
return _tap
517598

@@ -557,6 +638,8 @@ def get_tap() -> EntropyTap:
557638
# Classes (for type hints and advanced usage)
558639
"EntropyPool",
559640
"EntropyTap",
641+
"HybridTap",
642+
"BaseTap",
560643
"HealthStatus",
561644
"TrueEntropyConfig",
562645
]

0 commit comments

Comments
 (0)