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 ADDED
@@ -0,0 +1,5 @@
1
+ from .light import (
2
+ random, uniform, randint, choice, choices, sample, shuffle, gauss
3
+ )
4
+
5
+ __all__ = ["random", "uniform", "randint", "choice", "choices", "sample", "shuffle", "gauss"]
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)
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ coinrandom