coinrandom 0.1.0__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.
- coinrandom/__init__.py +5 -0
- coinrandom/core.py +39 -0
- coinrandom/functions.py +64 -0
- coinrandom/heavy/__init__.py +18 -0
- coinrandom/heavy/engine.py +233 -0
- coinrandom/light/__init__.py +12 -0
- coinrandom/light/engine.py +124 -0
- coinrandom/proof.py +27 -0
- coinrandom/superheavy/__init__.py +18 -0
- coinrandom/superheavy/engine.py +90 -0
- coinrandom/superheavy/optimizer.py +96 -0
- coinrandom-0.1.0.dist-info/METADATA +188 -0
- coinrandom-0.1.0.dist-info/RECORD +15 -0
- coinrandom-0.1.0.dist-info/WHEEL +5 -0
- coinrandom-0.1.0.dist-info/top_level.txt +1 -0
coinrandom/__init__.py
ADDED
coinrandom/core.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import os
|
|
3
|
+
import struct
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
BINANCE_TRADES = "https://api.binance.com/api/v3/trades"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def fetch_binance_entropy(symbols: list[str], limit: int = 5) -> bytes:
|
|
12
|
+
raw = bytearray()
|
|
13
|
+
for symbol in symbols:
|
|
14
|
+
try:
|
|
15
|
+
resp = requests.get(
|
|
16
|
+
BINANCE_TRADES,
|
|
17
|
+
params={"symbol": symbol, "limit": limit},
|
|
18
|
+
timeout=3,
|
|
19
|
+
)
|
|
20
|
+
for t in resp.json():
|
|
21
|
+
raw += str(t["price"]).encode()
|
|
22
|
+
raw += str(t["qty"]).encode()
|
|
23
|
+
raw += str(t["time"]).encode()
|
|
24
|
+
raw += str(t["isBuyerMaker"]).encode()
|
|
25
|
+
except Exception:
|
|
26
|
+
pass
|
|
27
|
+
return bytes(raw)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def mix_entropy(*sources: bytes) -> bytes:
|
|
31
|
+
ts = struct.pack(">d", time.time())
|
|
32
|
+
sys_e = os.urandom(32)
|
|
33
|
+
combined = b"".join(sources) + ts + sys_e
|
|
34
|
+
return hashlib.sha512(combined).digest()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def bytes_to_float(b: bytes) -> float:
|
|
38
|
+
val = int.from_bytes(b[:8], "big")
|
|
39
|
+
return val / (2**64)
|
coinrandom/functions.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import secrets
|
|
3
|
+
from typing import Any, MutableSequence, Sequence
|
|
4
|
+
|
|
5
|
+
from .core import _build_seed
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _get_random_bytes(n: int) -> bytes:
|
|
9
|
+
seed = _build_seed()
|
|
10
|
+
# seed를 secrets.token_bytes의 추가 entropy로 혼합
|
|
11
|
+
mixed = bytes(a ^ b for a, b in zip(seed[:n], secrets.token_bytes(n)))
|
|
12
|
+
return mixed
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _random_float() -> float:
|
|
16
|
+
raw = _get_random_bytes(8)
|
|
17
|
+
val = int.from_bytes(raw, "big")
|
|
18
|
+
return val / (2**64)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def random() -> float:
|
|
22
|
+
return _random_float()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def uniform(a: float, b: float) -> float:
|
|
26
|
+
return a + (b - a) * _random_float()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def randint(a: int, b: int) -> int:
|
|
30
|
+
span = b - a + 1
|
|
31
|
+
raw = _get_random_bytes(8)
|
|
32
|
+
val = int.from_bytes(raw, "big")
|
|
33
|
+
return a + (val % span)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def choice(seq: Sequence[Any]) -> Any:
|
|
37
|
+
return seq[randint(0, len(seq) - 1)]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def choices(seq: Sequence[Any], k: int = 1) -> list[Any]:
|
|
41
|
+
return [choice(seq) for _ in range(k)]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def sample(seq: Sequence[Any], k: int) -> list[Any]:
|
|
45
|
+
pool = list(seq)
|
|
46
|
+
result = []
|
|
47
|
+
for _ in range(k):
|
|
48
|
+
idx = randint(0, len(pool) - 1)
|
|
49
|
+
result.append(pool.pop(idx))
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def shuffle(seq: MutableSequence[Any]) -> None:
|
|
54
|
+
for i in range(len(seq) - 1, 0, -1):
|
|
55
|
+
j = randint(0, i)
|
|
56
|
+
seq[i], seq[j] = seq[j], seq[i]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def gauss(mu: float = 0.0, sigma: float = 1.0) -> float:
|
|
60
|
+
# Box-Muller transform
|
|
61
|
+
u1 = _random_float()
|
|
62
|
+
u2 = _random_float()
|
|
63
|
+
z = math.sqrt(-2 * math.log(u1)) * math.cos(2 * math.pi * u2)
|
|
64
|
+
return mu + sigma * z
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .engine import HeavyEngine
|
|
2
|
+
|
|
3
|
+
_engine = HeavyEngine()
|
|
4
|
+
|
|
5
|
+
random = _engine.random
|
|
6
|
+
uniform = _engine.uniform
|
|
7
|
+
randint = _engine.randint
|
|
8
|
+
choice = _engine.choice
|
|
9
|
+
choices = _engine.choices
|
|
10
|
+
sample = _engine.sample
|
|
11
|
+
shuffle = _engine.shuffle
|
|
12
|
+
gauss = _engine.gauss
|
|
13
|
+
random_with_proof = _engine.random_with_proof
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"random", "uniform", "randint", "choice", "choices",
|
|
17
|
+
"sample", "shuffle", "gauss", "random_with_proof",
|
|
18
|
+
]
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import math
|
|
3
|
+
import os
|
|
4
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from typing import Any, MutableSequence, Sequence
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
from ..core import mix_entropy, bytes_to_float
|
|
11
|
+
from ..proof import RandomProof
|
|
12
|
+
|
|
13
|
+
HEAVY_SYMBOLS = [
|
|
14
|
+
"BTCUSDT", "ETHUSDT", "SOLUSDT", "XMRUSDT", "LINKUSDT",
|
|
15
|
+
"DOGEUSDT", "ATOMUSDT", "MATICUSDT", "AVAXUSDT", "DOTUSDT",
|
|
16
|
+
"LTCUSDT", "UNIUSDT", "AAVEUSDT", "MKRUSDT", "CRVUSDT",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
ARGON2_TIME_COST = 4
|
|
20
|
+
ARGON2_MEMORY_COST = 65536 # 64MB
|
|
21
|
+
ARGON2_PARALLELISM = 2
|
|
22
|
+
ARGON2_HASH_LEN = 64
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _fetch_binance(symbols: list[str]) -> tuple[bytes, list[dict]]:
|
|
26
|
+
raw = bytearray()
|
|
27
|
+
records = []
|
|
28
|
+
for symbol in symbols:
|
|
29
|
+
try:
|
|
30
|
+
resp = requests.get(
|
|
31
|
+
"https://api.binance.com/api/v3/trades",
|
|
32
|
+
params={"symbol": symbol, "limit": 5},
|
|
33
|
+
timeout=4,
|
|
34
|
+
)
|
|
35
|
+
trades = resp.json()
|
|
36
|
+
for t in trades:
|
|
37
|
+
raw += str(t["price"]).encode()
|
|
38
|
+
raw += str(t["qty"]).encode()
|
|
39
|
+
raw += str(t["time"]).encode()
|
|
40
|
+
raw += str(t["isBuyerMaker"]).encode()
|
|
41
|
+
records.append({"exchange": "binance", "symbol": symbol, "count": len(trades)})
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
44
|
+
return bytes(raw), records
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _fetch_upbit(symbols: list[str]) -> tuple[bytes, list[dict]]:
|
|
48
|
+
raw = bytearray()
|
|
49
|
+
records = []
|
|
50
|
+
upbit_map = {
|
|
51
|
+
"BTCUSDT": "KRW-BTC", "ETHUSDT": "KRW-ETH", "SOLUSDT": "KRW-SOL",
|
|
52
|
+
"DOGEUSDT": "KRW-DOGE", "LINKUSDT": "KRW-LINK", "DOTUSDT": "KRW-DOT",
|
|
53
|
+
"AVAXUSDT": "KRW-AVAX", "ATOMUSDT": "KRW-ATOM",
|
|
54
|
+
}
|
|
55
|
+
for symbol in symbols:
|
|
56
|
+
market = upbit_map.get(symbol)
|
|
57
|
+
if not market:
|
|
58
|
+
continue
|
|
59
|
+
try:
|
|
60
|
+
resp = requests.get(
|
|
61
|
+
"https://api.upbit.com/v1/trades/ticks",
|
|
62
|
+
params={"market": market, "count": 5},
|
|
63
|
+
timeout=4,
|
|
64
|
+
)
|
|
65
|
+
trades = resp.json()
|
|
66
|
+
for t in trades:
|
|
67
|
+
raw += str(t["trade_price"]).encode()
|
|
68
|
+
raw += str(t["trade_volume"]).encode()
|
|
69
|
+
raw += str(t["timestamp"]).encode()
|
|
70
|
+
records.append({"exchange": "upbit", "symbol": market, "count": len(trades)})
|
|
71
|
+
except Exception:
|
|
72
|
+
pass
|
|
73
|
+
return bytes(raw), records
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _fetch_coinbase(symbols: list[str]) -> tuple[bytes, list[dict]]:
|
|
77
|
+
raw = bytearray()
|
|
78
|
+
records = []
|
|
79
|
+
cb_map = {
|
|
80
|
+
"BTCUSDT": "BTC-USD", "ETHUSDT": "ETH-USD", "SOLUSDT": "SOL-USD",
|
|
81
|
+
"LINKUSDT": "LINK-USD", "DOGEUSDT": "DOGE-USD", "LTCUSDT": "LTC-USD",
|
|
82
|
+
"DOTUSDT": "DOT-USD", "AVAXUSDT": "AVAX-USD", "UNIUSDT": "UNI-USD",
|
|
83
|
+
}
|
|
84
|
+
for symbol in symbols:
|
|
85
|
+
product = cb_map.get(symbol)
|
|
86
|
+
if not product:
|
|
87
|
+
continue
|
|
88
|
+
try:
|
|
89
|
+
resp = requests.get(
|
|
90
|
+
f"https://api.exchange.coinbase.com/products/{product}/trades",
|
|
91
|
+
params={"limit": 5},
|
|
92
|
+
timeout=4,
|
|
93
|
+
)
|
|
94
|
+
trades = resp.json()
|
|
95
|
+
for t in trades:
|
|
96
|
+
raw += str(t["price"]).encode()
|
|
97
|
+
raw += str(t["size"]).encode()
|
|
98
|
+
raw += str(t["time"]).encode()
|
|
99
|
+
records.append({"exchange": "coinbase", "symbol": product, "count": len(trades)})
|
|
100
|
+
except Exception:
|
|
101
|
+
pass
|
|
102
|
+
return bytes(raw), records
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
_ETH_RPC_ENDPOINTS = [
|
|
106
|
+
"https://eth.llamarpc.com",
|
|
107
|
+
"https://rpc.ankr.com/eth",
|
|
108
|
+
"https://ethereum.publicnode.com",
|
|
109
|
+
"https://cloudflare-eth.com",
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
def _fetch_eth_block_hash() -> str:
|
|
113
|
+
payload = {"jsonrpc": "2.0", "method": "eth_getBlockByNumber", "params": ["latest", False], "id": 1}
|
|
114
|
+
for endpoint in _ETH_RPC_ENDPOINTS:
|
|
115
|
+
try:
|
|
116
|
+
resp = requests.post(endpoint, json=payload, timeout=5)
|
|
117
|
+
h = resp.json()["result"]["hash"]
|
|
118
|
+
if h:
|
|
119
|
+
return h
|
|
120
|
+
except Exception:
|
|
121
|
+
continue
|
|
122
|
+
return ""
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _argon2_stretch(data: bytes, salt: bytes) -> bytes:
|
|
126
|
+
from argon2.low_level import hash_secret_raw, Type
|
|
127
|
+
return hash_secret_raw(
|
|
128
|
+
secret=data,
|
|
129
|
+
salt=salt,
|
|
130
|
+
time_cost=ARGON2_TIME_COST,
|
|
131
|
+
memory_cost=ARGON2_MEMORY_COST,
|
|
132
|
+
parallelism=ARGON2_PARALLELISM,
|
|
133
|
+
hash_len=ARGON2_HASH_LEN,
|
|
134
|
+
type=Type.ID,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _collect_entropy(symbols: list[str]) -> tuple[bytes, list[dict], str]:
|
|
139
|
+
results = {}
|
|
140
|
+
with ThreadPoolExecutor(max_workers=3) as ex:
|
|
141
|
+
futures = {
|
|
142
|
+
ex.submit(_fetch_binance, symbols): "binance",
|
|
143
|
+
ex.submit(_fetch_upbit, symbols): "upbit",
|
|
144
|
+
ex.submit(_fetch_coinbase, symbols): "coinbase",
|
|
145
|
+
}
|
|
146
|
+
for f in as_completed(futures):
|
|
147
|
+
results[futures[f]] = f.result()
|
|
148
|
+
|
|
149
|
+
all_raw = b""
|
|
150
|
+
all_records = []
|
|
151
|
+
for key in ("binance", "upbit", "coinbase"):
|
|
152
|
+
raw, records = results.get(key, (b"", []))
|
|
153
|
+
all_raw += raw
|
|
154
|
+
all_records.extend(records)
|
|
155
|
+
|
|
156
|
+
block_hash = _fetch_eth_block_hash()
|
|
157
|
+
all_raw += block_hash.encode()
|
|
158
|
+
return all_raw, all_records, block_hash
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _build_heavy_seed(symbols: list[str]) -> tuple[bytes, list[dict], str, str]:
|
|
162
|
+
raw, records, block_hash = _collect_entropy(symbols)
|
|
163
|
+
mixed = mix_entropy(raw)
|
|
164
|
+
salt = os.urandom(16)
|
|
165
|
+
stretched = _argon2_stretch(mixed, salt)
|
|
166
|
+
final_hash = hashlib.sha256(stretched).hexdigest()
|
|
167
|
+
return stretched, records, block_hash, final_hash
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class HeavyEngine:
|
|
171
|
+
def __init__(self, symbols: list[str] = HEAVY_SYMBOLS):
|
|
172
|
+
self.symbols = symbols
|
|
173
|
+
self._argon2_params = {
|
|
174
|
+
"time_cost": ARGON2_TIME_COST,
|
|
175
|
+
"memory_cost_kb": ARGON2_MEMORY_COST,
|
|
176
|
+
"parallelism": ARGON2_PARALLELISM,
|
|
177
|
+
"hash_len": ARGON2_HASH_LEN,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
def _generate(self) -> tuple[bytes, list[dict], str, str]:
|
|
181
|
+
return _build_heavy_seed(self.symbols)
|
|
182
|
+
|
|
183
|
+
def random(self) -> float:
|
|
184
|
+
seed, _, _, _ = self._generate()
|
|
185
|
+
return bytes_to_float(seed)
|
|
186
|
+
|
|
187
|
+
def uniform(self, a: float, b: float) -> float:
|
|
188
|
+
return a + (b - a) * self.random()
|
|
189
|
+
|
|
190
|
+
def randint(self, a: int, b: int) -> int:
|
|
191
|
+
span = b - a + 1
|
|
192
|
+
threshold = (2**64) - (2**64 % span)
|
|
193
|
+
while True:
|
|
194
|
+
seed, _, _, _ = self._generate()
|
|
195
|
+
val = int.from_bytes(seed[:8], "big")
|
|
196
|
+
if val < threshold:
|
|
197
|
+
return a + (val % span)
|
|
198
|
+
|
|
199
|
+
def choice(self, seq: Sequence[Any]) -> Any:
|
|
200
|
+
return seq[self.randint(0, len(seq) - 1)]
|
|
201
|
+
|
|
202
|
+
def choices(self, seq: Sequence[Any], k: int = 1) -> list[Any]:
|
|
203
|
+
return [self.choice(seq) for _ in range(k)]
|
|
204
|
+
|
|
205
|
+
def sample(self, seq: Sequence[Any], k: int) -> list[Any]:
|
|
206
|
+
pool = list(seq)
|
|
207
|
+
result = []
|
|
208
|
+
for _ in range(k):
|
|
209
|
+
idx = self.randint(0, len(pool) - 1)
|
|
210
|
+
result.append(pool.pop(idx))
|
|
211
|
+
return result
|
|
212
|
+
|
|
213
|
+
def shuffle(self, seq: MutableSequence[Any]) -> None:
|
|
214
|
+
for i in range(len(seq) - 1, 0, -1):
|
|
215
|
+
j = self.randint(0, i)
|
|
216
|
+
seq[i], seq[j] = seq[j], seq[i]
|
|
217
|
+
|
|
218
|
+
def gauss(self, mu: float = 0.0, sigma: float = 1.0) -> float:
|
|
219
|
+
u1, u2 = self.random(), self.random()
|
|
220
|
+
z = math.sqrt(-2 * math.log(u1)) * math.cos(2 * math.pi * u2)
|
|
221
|
+
return mu + sigma * z
|
|
222
|
+
|
|
223
|
+
def random_with_proof(self) -> RandomProof:
|
|
224
|
+
seed, records, block_hash, final_hash = self._generate()
|
|
225
|
+
return RandomProof(
|
|
226
|
+
value=bytes_to_float(seed),
|
|
227
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
228
|
+
exchanges=records,
|
|
229
|
+
symbols=self.symbols,
|
|
230
|
+
block_hash=block_hash,
|
|
231
|
+
argon2_params=self._argon2_params,
|
|
232
|
+
final_hash=final_hash,
|
|
233
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .engine import _engine
|
|
2
|
+
|
|
3
|
+
random = _engine.random
|
|
4
|
+
uniform = _engine.uniform
|
|
5
|
+
randint = _engine.randint
|
|
6
|
+
choice = _engine.choice
|
|
7
|
+
choices = _engine.choices
|
|
8
|
+
sample = _engine.sample
|
|
9
|
+
shuffle = _engine.shuffle
|
|
10
|
+
gauss = _engine.gauss
|
|
11
|
+
|
|
12
|
+
__all__ = ["random", "uniform", "randint", "choice", "choices", "sample", "shuffle", "gauss"]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import math
|
|
3
|
+
import os
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any, MutableSequence, Sequence
|
|
7
|
+
|
|
8
|
+
from argon2.low_level import hash_secret_raw, Type
|
|
9
|
+
|
|
10
|
+
from ..core import fetch_binance_entropy, mix_entropy
|
|
11
|
+
|
|
12
|
+
LIGHT_SYMBOLS = ["BTCUSDT", "ETHUSDT", "SOLUSDT"]
|
|
13
|
+
RESEED_CALLS = 1000
|
|
14
|
+
RESEED_SECONDS = 60
|
|
15
|
+
|
|
16
|
+
ARGON2_TIME_COST = 1
|
|
17
|
+
ARGON2_MEMORY_COST = 8192 # 8MB
|
|
18
|
+
ARGON2_PARALLELISM = 1
|
|
19
|
+
ARGON2_HASH_LEN = 64
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class HashDRBG:
|
|
23
|
+
"""SHA-512 카운터 기반 결정론적 난수 생성기. stdlib random 미사용."""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self._state = b""
|
|
27
|
+
self._counter = 0
|
|
28
|
+
|
|
29
|
+
def seed(self, data: bytes) -> None:
|
|
30
|
+
self._state = hashlib.sha512(data).digest()
|
|
31
|
+
self._counter = 0
|
|
32
|
+
|
|
33
|
+
def _next_block(self) -> bytes:
|
|
34
|
+
self._counter += 1
|
|
35
|
+
return hashlib.sha512(
|
|
36
|
+
self._state + self._counter.to_bytes(8, "big")
|
|
37
|
+
).digest()
|
|
38
|
+
|
|
39
|
+
def random(self) -> float:
|
|
40
|
+
val = int.from_bytes(self._next_block()[:8], "big")
|
|
41
|
+
return val / (2**64)
|
|
42
|
+
|
|
43
|
+
def randint(self, a: int, b: int) -> int:
|
|
44
|
+
span = b - a + 1
|
|
45
|
+
threshold = (2**64) - (2**64 % span)
|
|
46
|
+
while True:
|
|
47
|
+
val = int.from_bytes(self._next_block()[:8], "big")
|
|
48
|
+
if val < threshold:
|
|
49
|
+
return a + (val % span)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class LightEngine:
|
|
53
|
+
def __init__(self):
|
|
54
|
+
self._rng = HashDRBG()
|
|
55
|
+
self._lock = threading.Lock()
|
|
56
|
+
self._call_count = 0
|
|
57
|
+
self._last_seed_time = 0.0
|
|
58
|
+
self._seeded = False
|
|
59
|
+
|
|
60
|
+
def _maybe_reseed(self) -> None:
|
|
61
|
+
now = time.time()
|
|
62
|
+
if (
|
|
63
|
+
not self._seeded
|
|
64
|
+
or self._call_count >= RESEED_CALLS
|
|
65
|
+
or now - self._last_seed_time >= RESEED_SECONDS
|
|
66
|
+
):
|
|
67
|
+
coin = fetch_binance_entropy(LIGHT_SYMBOLS)
|
|
68
|
+
mixed = mix_entropy(coin)
|
|
69
|
+
salt = os.urandom(16)
|
|
70
|
+
stretched = hash_secret_raw(
|
|
71
|
+
secret=mixed,
|
|
72
|
+
salt=salt,
|
|
73
|
+
time_cost=ARGON2_TIME_COST,
|
|
74
|
+
memory_cost=ARGON2_MEMORY_COST,
|
|
75
|
+
parallelism=ARGON2_PARALLELISM,
|
|
76
|
+
hash_len=ARGON2_HASH_LEN,
|
|
77
|
+
type=Type.ID,
|
|
78
|
+
)
|
|
79
|
+
self._rng.seed(stretched)
|
|
80
|
+
self._call_count = 0
|
|
81
|
+
self._last_seed_time = now
|
|
82
|
+
self._seeded = True
|
|
83
|
+
|
|
84
|
+
def random(self) -> float:
|
|
85
|
+
with self._lock:
|
|
86
|
+
self._maybe_reseed()
|
|
87
|
+
self._call_count += 1
|
|
88
|
+
return self._rng.random()
|
|
89
|
+
|
|
90
|
+
def uniform(self, a: float, b: float) -> float:
|
|
91
|
+
return a + (b - a) * self.random()
|
|
92
|
+
|
|
93
|
+
def randint(self, a: int, b: int) -> int:
|
|
94
|
+
with self._lock:
|
|
95
|
+
self._maybe_reseed()
|
|
96
|
+
self._call_count += 1
|
|
97
|
+
return self._rng.randint(a, b)
|
|
98
|
+
|
|
99
|
+
def choice(self, seq: Sequence[Any]) -> Any:
|
|
100
|
+
return seq[self.randint(0, len(seq) - 1)]
|
|
101
|
+
|
|
102
|
+
def choices(self, seq: Sequence[Any], k: int = 1) -> list[Any]:
|
|
103
|
+
return [self.choice(seq) for _ in range(k)]
|
|
104
|
+
|
|
105
|
+
def sample(self, seq: Sequence[Any], k: int) -> list[Any]:
|
|
106
|
+
pool = list(seq)
|
|
107
|
+
result = []
|
|
108
|
+
for _ in range(k):
|
|
109
|
+
idx = self.randint(0, len(pool) - 1)
|
|
110
|
+
result.append(pool.pop(idx))
|
|
111
|
+
return result
|
|
112
|
+
|
|
113
|
+
def shuffle(self, seq: MutableSequence[Any]) -> None:
|
|
114
|
+
for i in range(len(seq) - 1, 0, -1):
|
|
115
|
+
j = self.randint(0, i)
|
|
116
|
+
seq[i], seq[j] = seq[j], seq[i]
|
|
117
|
+
|
|
118
|
+
def gauss(self, mu: float = 0.0, sigma: float = 1.0) -> float:
|
|
119
|
+
u1, u2 = self.random(), self.random()
|
|
120
|
+
z = math.sqrt(-2 * math.log(u1)) * math.cos(2 * math.pi * u2)
|
|
121
|
+
return mu + sigma * z
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
_engine = LightEngine()
|
coinrandom/proof.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class RandomProof:
|
|
7
|
+
value: float
|
|
8
|
+
timestamp: str
|
|
9
|
+
exchanges: list[dict]
|
|
10
|
+
symbols: list[str]
|
|
11
|
+
block_hash: str
|
|
12
|
+
argon2_params: dict
|
|
13
|
+
final_hash: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class SuperProof:
|
|
18
|
+
value: float
|
|
19
|
+
timestamp: str
|
|
20
|
+
exchanges: list[dict]
|
|
21
|
+
block_hash: str
|
|
22
|
+
argon2_params: dict
|
|
23
|
+
candidate_count: int
|
|
24
|
+
selected_symbols: list[str]
|
|
25
|
+
correlation_matrix: dict[str, dict[str, float]]
|
|
26
|
+
optimization_result: dict[str, Any]
|
|
27
|
+
final_hash: str
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .engine import SuperHeavyEngine
|
|
2
|
+
|
|
3
|
+
_engine = SuperHeavyEngine()
|
|
4
|
+
|
|
5
|
+
random = _engine.random
|
|
6
|
+
uniform = _engine.uniform
|
|
7
|
+
randint = _engine.randint
|
|
8
|
+
choice = _engine.choice
|
|
9
|
+
choices = _engine.choices
|
|
10
|
+
sample = _engine.sample
|
|
11
|
+
shuffle = _engine.shuffle
|
|
12
|
+
gauss = _engine.gauss
|
|
13
|
+
random_with_proof = _engine.random_with_proof
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"random", "uniform", "randint", "choice", "choices",
|
|
17
|
+
"sample", "shuffle", "gauss", "random_with_proof",
|
|
18
|
+
]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import math
|
|
3
|
+
import os
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any, MutableSequence, Sequence
|
|
6
|
+
|
|
7
|
+
from ..core import mix_entropy, bytes_to_float
|
|
8
|
+
from ..heavy.engine import _collect_entropy, _argon2_stretch, ARGON2_TIME_COST, ARGON2_MEMORY_COST, ARGON2_PARALLELISM, ARGON2_HASH_LEN
|
|
9
|
+
from ..proof import SuperProof
|
|
10
|
+
from .optimizer import select_min_correlation_symbols
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SuperHeavyEngine:
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self._argon2_params = {
|
|
16
|
+
"time_cost": ARGON2_TIME_COST,
|
|
17
|
+
"memory_cost_kb": ARGON2_MEMORY_COST,
|
|
18
|
+
"parallelism": ARGON2_PARALLELISM,
|
|
19
|
+
"hash_len": ARGON2_HASH_LEN,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def _generate(self) -> tuple[bytes, list[str], dict, dict, list[dict], str, str]:
|
|
23
|
+
# 1단계: 역 포트폴리오 최적화로 최소 상관 코인 선정
|
|
24
|
+
selected, corr_matrix, opt_result = select_min_correlation_symbols()
|
|
25
|
+
|
|
26
|
+
# 2단계: 선정된 코인으로 3거래소 병렬 수집 + ETH 블록 해시
|
|
27
|
+
raw, records, block_hash = _collect_entropy(selected)
|
|
28
|
+
|
|
29
|
+
# 3단계: Argon2 스트레칭
|
|
30
|
+
mixed = mix_entropy(raw)
|
|
31
|
+
salt = os.urandom(16)
|
|
32
|
+
stretched = _argon2_stretch(mixed, salt)
|
|
33
|
+
final_hash = hashlib.sha256(stretched).hexdigest()
|
|
34
|
+
|
|
35
|
+
return stretched, selected, corr_matrix, opt_result, records, block_hash, final_hash
|
|
36
|
+
|
|
37
|
+
def random(self) -> float:
|
|
38
|
+
seed, *_ = self._generate()
|
|
39
|
+
return bytes_to_float(seed)
|
|
40
|
+
|
|
41
|
+
def uniform(self, a: float, b: float) -> float:
|
|
42
|
+
return a + (b - a) * self.random()
|
|
43
|
+
|
|
44
|
+
def randint(self, a: int, b: int) -> int:
|
|
45
|
+
span = b - a + 1
|
|
46
|
+
threshold = (2**64) - (2**64 % span)
|
|
47
|
+
while True:
|
|
48
|
+
seed, *_ = self._generate()
|
|
49
|
+
val = int.from_bytes(seed[:8], "big")
|
|
50
|
+
if val < threshold:
|
|
51
|
+
return a + (val % span)
|
|
52
|
+
|
|
53
|
+
def choice(self, seq: Sequence[Any]) -> Any:
|
|
54
|
+
return seq[self.randint(0, len(seq) - 1)]
|
|
55
|
+
|
|
56
|
+
def choices(self, seq: Sequence[Any], k: int = 1) -> list[Any]:
|
|
57
|
+
return [self.choice(seq) for _ in range(k)]
|
|
58
|
+
|
|
59
|
+
def sample(self, seq: Sequence[Any], k: int) -> list[Any]:
|
|
60
|
+
pool = list(seq)
|
|
61
|
+
result = []
|
|
62
|
+
for _ in range(k):
|
|
63
|
+
idx = self.randint(0, len(pool) - 1)
|
|
64
|
+
result.append(pool.pop(idx))
|
|
65
|
+
return result
|
|
66
|
+
|
|
67
|
+
def shuffle(self, seq: MutableSequence[Any]) -> None:
|
|
68
|
+
for i in range(len(seq) - 1, 0, -1):
|
|
69
|
+
j = self.randint(0, i)
|
|
70
|
+
seq[i], seq[j] = seq[j], seq[i]
|
|
71
|
+
|
|
72
|
+
def gauss(self, mu: float = 0.0, sigma: float = 1.0) -> float:
|
|
73
|
+
u1, u2 = self.random(), self.random()
|
|
74
|
+
z = math.sqrt(-2 * math.log(u1)) * math.cos(2 * math.pi * u2)
|
|
75
|
+
return mu + sigma * z
|
|
76
|
+
|
|
77
|
+
def random_with_proof(self) -> SuperProof:
|
|
78
|
+
seed, selected, corr_matrix, opt_result, records, block_hash, final_hash = self._generate()
|
|
79
|
+
return SuperProof(
|
|
80
|
+
value=bytes_to_float(seed),
|
|
81
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
82
|
+
exchanges=records,
|
|
83
|
+
block_hash=block_hash,
|
|
84
|
+
argon2_params=self._argon2_params,
|
|
85
|
+
candidate_count=len(corr_matrix),
|
|
86
|
+
selected_symbols=selected,
|
|
87
|
+
correlation_matrix=corr_matrix,
|
|
88
|
+
optimization_result=opt_result,
|
|
89
|
+
final_hash=final_hash,
|
|
90
|
+
)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
역 포트폴리오 최적화로 최소 상관관계 코인 조합을 선정한다.
|
|
3
|
+
Markowitz 최대 분산화 포트폴리오와 동일한 수식:
|
|
4
|
+
maximize Σᵢ Σⱼ wᵢwⱼ(1 - ρᵢⱼ)
|
|
5
|
+
s.t. Σwᵢ = 1, wᵢ >= 0
|
|
6
|
+
"""
|
|
7
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import requests
|
|
11
|
+
from scipy.optimize import minimize
|
|
12
|
+
|
|
13
|
+
CANDIDATE_SYMBOLS = [
|
|
14
|
+
"BTCUSDT", "ETHUSDT", "SOLUSDT", "XMRUSDT", "LINKUSDT",
|
|
15
|
+
"DOGEUSDT", "ATOMUSDT", "AVAXUSDT", "DOTUSDT", "LTCUSDT",
|
|
16
|
+
"UNIUSDT", "AAVEUSDT", "MKRUSDT", "CRVUSDT", "MATICUSDT",
|
|
17
|
+
"INJUSDT", "SUIUSDT", "SEIUSDT", "TIAUSDT", "RENDERUSDT",
|
|
18
|
+
]
|
|
19
|
+
KLINE_URL = "https://api.binance.com/api/v3/klines"
|
|
20
|
+
TOP_N = 8
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _fetch_returns(symbol: str, limit: int = 60) -> tuple[str, list[float]]:
|
|
24
|
+
try:
|
|
25
|
+
resp = requests.get(
|
|
26
|
+
KLINE_URL,
|
|
27
|
+
params={"symbol": symbol, "interval": "1m", "limit": limit},
|
|
28
|
+
timeout=5,
|
|
29
|
+
)
|
|
30
|
+
closes = [float(k[4]) for k in resp.json()]
|
|
31
|
+
returns = [closes[i] / closes[i - 1] - 1 for i in range(1, len(closes))]
|
|
32
|
+
return symbol, returns
|
|
33
|
+
except Exception:
|
|
34
|
+
return symbol, []
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _build_correlation_matrix(symbols: list[str]) -> tuple[np.ndarray, list[str]]:
|
|
38
|
+
returns_map: dict[str, list[float]] = {}
|
|
39
|
+
|
|
40
|
+
with ThreadPoolExecutor(max_workers=10) as ex:
|
|
41
|
+
futures = {ex.submit(_fetch_returns, s): s for s in symbols}
|
|
42
|
+
for f in as_completed(futures):
|
|
43
|
+
sym, rets = f.result()
|
|
44
|
+
if len(rets) >= 30:
|
|
45
|
+
returns_map[sym] = rets
|
|
46
|
+
|
|
47
|
+
valid = list(returns_map.keys())
|
|
48
|
+
if len(valid) < 2:
|
|
49
|
+
return np.eye(len(symbols)), symbols
|
|
50
|
+
|
|
51
|
+
min_len = min(len(returns_map[s]) for s in valid)
|
|
52
|
+
matrix = np.array([returns_map[s][:min_len] for s in valid])
|
|
53
|
+
corr = np.corrcoef(matrix)
|
|
54
|
+
return corr, valid
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def select_min_correlation_symbols(n: int = TOP_N) -> tuple[list[str], dict, dict]:
|
|
58
|
+
print(f" [SuperHeavy] 후보 {len(CANDIDATE_SYMBOLS)}개 코인 수익률 데이터 수집 중...", flush=True)
|
|
59
|
+
corr, valid = _build_correlation_matrix(CANDIDATE_SYMBOLS)
|
|
60
|
+
print(f" [SuperHeavy] {len(valid)}개 유효, 상관관계 행렬 완성 → 최적화 시작", flush=True)
|
|
61
|
+
k = len(valid)
|
|
62
|
+
|
|
63
|
+
if k <= n:
|
|
64
|
+
corr_dict = {valid[i]: {valid[j]: float(corr[i, j]) for j in range(k)} for i in range(k)}
|
|
65
|
+
return valid, corr_dict, {"method": "all_selected", "n_candidates": k}
|
|
66
|
+
|
|
67
|
+
# 목적함수: 음수 → minimize = maximize 분산화
|
|
68
|
+
def objective(w):
|
|
69
|
+
return -float(w @ (1 - corr) @ w)
|
|
70
|
+
|
|
71
|
+
def jac(w):
|
|
72
|
+
return -2 * (1 - corr) @ w
|
|
73
|
+
|
|
74
|
+
constraints = [{"type": "eq", "fun": lambda w: np.sum(w) - 1}]
|
|
75
|
+
bounds = [(0, 1)] * k
|
|
76
|
+
w0 = np.ones(k) / k
|
|
77
|
+
|
|
78
|
+
result = minimize(objective, w0, jac=jac, method="SLSQP",
|
|
79
|
+
bounds=bounds, constraints=constraints,
|
|
80
|
+
options={"ftol": 1e-9, "maxiter": 1000})
|
|
81
|
+
|
|
82
|
+
top_idx = np.argsort(result.x)[-n:]
|
|
83
|
+
selected = [valid[i] for i in top_idx]
|
|
84
|
+
|
|
85
|
+
corr_dict = {
|
|
86
|
+
valid[i]: {valid[j]: round(float(corr[i, j]), 4) for j in range(k)}
|
|
87
|
+
for i in range(k)
|
|
88
|
+
}
|
|
89
|
+
opt_info = {
|
|
90
|
+
"method": "SLSQP",
|
|
91
|
+
"n_candidates": k,
|
|
92
|
+
"success": bool(result.success),
|
|
93
|
+
"objective_value": float(-result.fun),
|
|
94
|
+
"weights": {valid[i]: round(float(result.x[i]), 4) for i in range(k)},
|
|
95
|
+
}
|
|
96
|
+
return selected, corr_dict, opt_info
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: coinrandom
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: True random numbers sourced from live cryptocurrency market data
|
|
5
|
+
Author-email: dglst <woo9910203626@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/LETUED/coinrandom
|
|
8
|
+
Project-URL: Repository, https://github.com/LETUED/coinrandom
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/LETUED/coinrandom/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/LETUED/coinrandom/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: random,entropy,cryptocurrency,bitcoin,randomness,cryptography,DRBG,verifiable-random,proof
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Security :: Cryptography
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: requests>=2.28
|
|
25
|
+
Requires-Dist: argon2-cffi>=21.0
|
|
26
|
+
Provides-Extra: superheavy
|
|
27
|
+
Requires-Dist: numpy>=1.24; extra == "superheavy"
|
|
28
|
+
Requires-Dist: scipy>=1.10; extra == "superheavy"
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: build; extra == "dev"
|
|
31
|
+
Requires-Dist: twine; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest; extra == "dev"
|
|
33
|
+
|
|
34
|
+
# coinrandom
|
|
35
|
+
|
|
36
|
+
> True random numbers sourced from live cryptocurrency market data.
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import coinrandom
|
|
40
|
+
|
|
41
|
+
coinrandom.random() # 0.7182818...
|
|
42
|
+
coinrandom.randint(1, 100) # 42
|
|
43
|
+
coinrandom.choice(["a", "b", "c"])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Why coinrandom?
|
|
49
|
+
|
|
50
|
+
Cryptocurrency markets trade 24/7 globally. At the tick level — individual trade prices, quantities, timestamps, and buyer/seller direction — the data is highly unpredictable. The Efficient Market Hypothesis (EMH) says no one can consistently predict short-term market movements. **coinrandom uses this unpredictability as an entropy source.**
|
|
51
|
+
|
|
52
|
+
### Trust Model
|
|
53
|
+
|
|
54
|
+
> Even with the full source code published, no one can predict the output in advance — because no one can predict the coin market.
|
|
55
|
+
|
|
56
|
+
This is an application of **Kerckhoffs's principle**: security depends on the unpredictability of the market, not on keeping the algorithm secret. Each value generated by Heavy/SuperHeavy comes with a `RandomProof` — a verifiable audit trail showing exactly which market data produced the result.
|
|
57
|
+
|
|
58
|
+
**Honest limits:** coinrandom provides *computational security* (like AES/RSA), not *information-theoretic security* (like Chainlink VRF). The trust model is economic, not mathematical. For cryptographic key generation, use `secrets`. For smart contract RNG, use Chainlink VRF.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Installation
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install coinrandom # Light + Heavy
|
|
66
|
+
pip install "coinrandom[superheavy]" # + SuperHeavy (numpy, scipy)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
No API keys. No configuration.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Three Tiers
|
|
74
|
+
|
|
75
|
+
| Tier | Speed | Entropy source | Proof | Use case |
|
|
76
|
+
|------|-------|---------------|-------|----------|
|
|
77
|
+
| **Light** | ~1ms | Binance tick + Argon2 | No | High-volume generation |
|
|
78
|
+
| **Heavy** | ~2s | 3 exchanges + ETH block hash + Argon2 | Yes | Raffles, NFT mints, DAO votes |
|
|
79
|
+
| **SuperHeavy** | ~30s | Portfolio-optimized coins + Heavy pipeline | Yes | Maximum entropy, auditable |
|
|
80
|
+
|
|
81
|
+
All tiers return the same API — drop-in replacement for Python's `random` module.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Usage
|
|
86
|
+
|
|
87
|
+
### Light (default)
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
import coinrandom
|
|
91
|
+
|
|
92
|
+
coinrandom.random() # float in [0.0, 1.0)
|
|
93
|
+
coinrandom.uniform(1.5, 9.5)
|
|
94
|
+
coinrandom.randint(1, 100)
|
|
95
|
+
coinrandom.choice(["a", "b", "c"])
|
|
96
|
+
coinrandom.choices(["a", "b", "c"], k=5)
|
|
97
|
+
coinrandom.sample(range(100), k=10)
|
|
98
|
+
|
|
99
|
+
lst = [1, 2, 3, 4, 5]
|
|
100
|
+
coinrandom.shuffle(lst)
|
|
101
|
+
|
|
102
|
+
coinrandom.gauss(mu=0.0, sigma=1.0)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Heavy — with proof
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from coinrandom.heavy import HeavyEngine
|
|
109
|
+
|
|
110
|
+
engine = HeavyEngine()
|
|
111
|
+
value, proof = engine.random_with_proof()
|
|
112
|
+
|
|
113
|
+
print(value) # 0.3571428...
|
|
114
|
+
print(proof.exchanges) # [{"name": "binance", "symbols": [...], ...}, ...]
|
|
115
|
+
print(proof.block_hash) # "0xabc123..."
|
|
116
|
+
print(proof.final_hash) # SHA-512 of the combined entropy
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### SuperHeavy — portfolio-optimized entropy
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from coinrandom.superheavy import SuperHeavyEngine
|
|
123
|
+
|
|
124
|
+
engine = SuperHeavyEngine()
|
|
125
|
+
value, proof = engine.random_with_proof()
|
|
126
|
+
|
|
127
|
+
print(proof.selected_symbols) # coins selected by inverse portfolio optimization
|
|
128
|
+
print(proof.correlation_matrix) # correlation matrix of candidates
|
|
129
|
+
print(proof.optimization_result) # scipy SLSQP result
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
SuperHeavy runs inverse Markowitz portfolio optimization to select the **least-correlated coins** as entropy sources — maximizing entropy diversity.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Design Principles
|
|
137
|
+
|
|
138
|
+
1. **No API keys** — works out of the box with `pip install`
|
|
139
|
+
2. **Uniform API** — every tier exposes the same functions as `random`
|
|
140
|
+
3. **No stdlib random** — custom HashDRBG (SHA-512 counter-based), Mersenne Twister not used anywhere
|
|
141
|
+
4. **Open-source safe** — Kerckhoffs's principle: publishing the algorithm doesn't compromise security
|
|
142
|
+
5. **Intentionally heavy** — Heavy/SuperHeavy: each call runs the full entropy pipeline. "Slow = costly to manipulate."
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Internals
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
coinrandom/
|
|
150
|
+
├── __init__.py # Light tier as default API
|
|
151
|
+
├── core.py # fetch_binance_entropy, mix_entropy
|
|
152
|
+
├── proof.py # RandomProof, SuperProof dataclasses
|
|
153
|
+
├── light/ # HashDRBG + Argon2 (t=1, m=8MB) reseed cache
|
|
154
|
+
├── heavy/ # 3 exchanges parallel + ETH block hash + Argon2 (t=4, m=64MB)
|
|
155
|
+
└── superheavy/ # Inverse portfolio optimization → Heavy pipeline
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### HashDRBG
|
|
159
|
+
|
|
160
|
+
Custom SHA-512 counter-based DRBG. No `import random` anywhere in the codebase.
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
# Simplified
|
|
164
|
+
state = argon2(mix_entropy(coin_data, os.urandom(32)))
|
|
165
|
+
output = sha512(state + counter) # per call
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Manipulation resistance
|
|
169
|
+
|
|
170
|
+
Heavy mode requires simultaneously moving 32+ coins across Binance, Upbit, and Coinbase in the exact direction needed — estimated cost: billions of dollars. SuperHeavy additionally hides the target coins until optimization runs.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Versioning
|
|
175
|
+
|
|
176
|
+
This project follows [Semantic Versioning](https://semver.org/):
|
|
177
|
+
|
|
178
|
+
- `PATCH` — bug fixes, internal improvements
|
|
179
|
+
- `MINOR` — new features, backward-compatible
|
|
180
|
+
- `MAJOR` — stable API declaration or breaking changes
|
|
181
|
+
|
|
182
|
+
Current status: `0.x` (API stabilizing, not yet guaranteed stable)
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
coinrandom/__init__.py,sha256=LUgoF5iMVbVk3XDA_Nc94qnwWabEcD3soj9hLTTilVQ,188
|
|
2
|
+
coinrandom/core.py,sha256=e3zAvT40AhqzG0PT6e7dtZOzg7i5IpIKYUJW1u1AlwA,1030
|
|
3
|
+
coinrandom/functions.py,sha256=u7WQCBuBFxvvcU4iFRUGVfktZpgSt-wK0e9aB_C7G1o,1526
|
|
4
|
+
coinrandom/proof.py,sha256=i_vY4OmVEyfjR7aIR0c7Z-A0bTI1VoZ9MgQacB80Ge4,544
|
|
5
|
+
coinrandom/heavy/__init__.py,sha256=b740x6RKfupmtd2XlmXUsn5sn_U7AumHB5nMHlIGR_U,429
|
|
6
|
+
coinrandom/heavy/engine.py,sha256=zdVYwKMckRM7srlqg5qJyfhSLIlnO0BB4PF0scQI04g,7728
|
|
7
|
+
coinrandom/light/__init__.py,sha256=zDwvMWhZr5ex2cilcRimwgtrj40y55R4txfUfTrq9k0,322
|
|
8
|
+
coinrandom/light/engine.py,sha256=PVg5NAaGlkz3MrzFL6iCxTrhIIYndISqgpfSvG2lfmc,3644
|
|
9
|
+
coinrandom/superheavy/__init__.py,sha256=5AYS52NS4PNqjOZmtt5ylZTngeBA5qnXxqvRkBT-tF8,439
|
|
10
|
+
coinrandom/superheavy/engine.py,sha256=PUHwhKKTDYZsNeeBNwKd_rKzezG0_ZScxhYFd3loXyU,3341
|
|
11
|
+
coinrandom/superheavy/optimizer.py,sha256=InZXN2Tnck-8AIbhcpRlatJaA8M3P8Y1eJluJf47N7s,3418
|
|
12
|
+
coinrandom-0.1.0.dist-info/METADATA,sha256=bv2BhUySZ5PVcDgWRB6Tc4ioDEq-rGIPvuuBBVxG1xY,6582
|
|
13
|
+
coinrandom-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
14
|
+
coinrandom-0.1.0.dist-info/top_level.txt,sha256=twziXgHaTo-ZVJ_fXl3s1Hfk9u3z-TqBPWsiZEIpOnk,11
|
|
15
|
+
coinrandom-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
coinrandom
|