zexus 1.7.1 → 1.7.2

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.
Files changed (159) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/src/__init__.py +7 -0
  4. package/src/zexus/__init__.py +1 -1
  5. package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
  6. package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
  7. package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
  8. package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
  9. package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
  10. package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
  11. package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
  12. package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
  13. package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
  14. package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
  15. package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
  16. package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
  17. package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
  18. package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  19. package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
  20. package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
  22. package/src/zexus/advanced_types.py +17 -2
  23. package/src/zexus/blockchain/__init__.py +411 -0
  24. package/src/zexus/blockchain/accelerator.py +1160 -0
  25. package/src/zexus/blockchain/chain.py +660 -0
  26. package/src/zexus/blockchain/consensus.py +821 -0
  27. package/src/zexus/blockchain/contract_vm.py +1019 -0
  28. package/src/zexus/blockchain/crypto.py +79 -14
  29. package/src/zexus/blockchain/events.py +526 -0
  30. package/src/zexus/blockchain/loadtest.py +721 -0
  31. package/src/zexus/blockchain/monitoring.py +350 -0
  32. package/src/zexus/blockchain/mpt.py +716 -0
  33. package/src/zexus/blockchain/multichain.py +951 -0
  34. package/src/zexus/blockchain/multiprocess_executor.py +338 -0
  35. package/src/zexus/blockchain/network.py +886 -0
  36. package/src/zexus/blockchain/node.py +666 -0
  37. package/src/zexus/blockchain/rpc.py +1203 -0
  38. package/src/zexus/blockchain/rust_bridge.py +421 -0
  39. package/src/zexus/blockchain/storage.py +423 -0
  40. package/src/zexus/blockchain/tokens.py +750 -0
  41. package/src/zexus/blockchain/upgradeable.py +1004 -0
  42. package/src/zexus/blockchain/verification.py +1602 -0
  43. package/src/zexus/blockchain/wallet.py +621 -0
  44. package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
  45. package/src/zexus/cli/main.py +300 -20
  46. package/src/zexus/cli/zpm.py +1 -1
  47. package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
  48. package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
  49. package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
  50. package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
  51. package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  52. package/src/zexus/compiler/lexer.py +10 -5
  53. package/src/zexus/concurrency_system.py +79 -0
  54. package/src/zexus/config.py +54 -0
  55. package/src/zexus/crypto_bridge.py +244 -8
  56. package/src/zexus/dap/__init__.py +10 -0
  57. package/src/zexus/dap/__main__.py +4 -0
  58. package/src/zexus/dap/dap_server.py +391 -0
  59. package/src/zexus/dap/debug_engine.py +298 -0
  60. package/src/zexus/environment.py +10 -1
  61. package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
  62. package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
  63. package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
  64. package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
  65. package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
  66. package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
  67. package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
  68. package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
  69. package/src/zexus/evaluator/bytecode_compiler.py +441 -37
  70. package/src/zexus/evaluator/core.py +560 -49
  71. package/src/zexus/evaluator/expressions.py +122 -49
  72. package/src/zexus/evaluator/functions.py +417 -16
  73. package/src/zexus/evaluator/statements.py +521 -118
  74. package/src/zexus/evaluator/unified_execution.py +573 -72
  75. package/src/zexus/evaluator/utils.py +14 -2
  76. package/src/zexus/event_loop.py +186 -0
  77. package/src/zexus/lexer.py +742 -486
  78. package/src/zexus/lsp/__init__.py +1 -1
  79. package/src/zexus/lsp/definition_provider.py +163 -9
  80. package/src/zexus/lsp/server.py +22 -8
  81. package/src/zexus/lsp/symbol_provider.py +182 -9
  82. package/src/zexus/module_cache.py +237 -9
  83. package/src/zexus/object.py +64 -6
  84. package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
  85. package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
  86. package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
  87. package/src/zexus/parser/parser.py +786 -285
  88. package/src/zexus/parser/strategy_context.py +407 -66
  89. package/src/zexus/parser/strategy_structural.py +117 -19
  90. package/src/zexus/persistence.py +15 -1
  91. package/src/zexus/renderer/__init__.py +15 -0
  92. package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
  93. package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
  94. package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
  95. package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
  96. package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
  97. package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
  98. package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
  99. package/src/zexus/renderer/tk_backend.py +208 -0
  100. package/src/zexus/renderer/web_backend.py +260 -0
  101. package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  102. package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
  103. package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
  104. package/src/zexus/runtime/file_flags.py +137 -0
  105. package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
  106. package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
  107. package/src/zexus/security.py +424 -34
  108. package/src/zexus/stdlib/fs.py +23 -18
  109. package/src/zexus/stdlib/http.py +289 -186
  110. package/src/zexus/stdlib/sockets.py +207 -163
  111. package/src/zexus/stdlib/websockets.py +282 -0
  112. package/src/zexus/stdlib_integration.py +369 -2
  113. package/src/zexus/strategy_recovery.py +6 -3
  114. package/src/zexus/type_checker.py +423 -0
  115. package/src/zexus/virtual_filesystem.py +189 -2
  116. package/src/zexus/vm/__init__.py +113 -3
  117. package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
  118. package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
  119. package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
  120. package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
  121. package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
  122. package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
  123. package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
  124. package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
  125. package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
  126. package/src/zexus/vm/async_optimizer.py +14 -1
  127. package/src/zexus/vm/binary_bytecode.py +659 -0
  128. package/src/zexus/vm/bytecode.py +28 -1
  129. package/src/zexus/vm/bytecode_converter.py +26 -12
  130. package/src/zexus/vm/cabi.c +1985 -0
  131. package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
  132. package/src/zexus/vm/cabi.h +127 -0
  133. package/src/zexus/vm/cache.py +557 -17
  134. package/src/zexus/vm/compiler.py +703 -5
  135. package/src/zexus/vm/fastops.c +15743 -0
  136. package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
  137. package/src/zexus/vm/fastops.pyx +288 -0
  138. package/src/zexus/vm/gas_metering.py +50 -9
  139. package/src/zexus/vm/jit.py +83 -2
  140. package/src/zexus/vm/native_jit_backend.py +1816 -0
  141. package/src/zexus/vm/native_runtime.cpp +1388 -0
  142. package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
  143. package/src/zexus/vm/optimizer.py +161 -11
  144. package/src/zexus/vm/parallel_vm.py +118 -42
  145. package/src/zexus/vm/peephole_optimizer.py +82 -4
  146. package/src/zexus/vm/profiler.py +38 -18
  147. package/src/zexus/vm/register_allocator.py +16 -5
  148. package/src/zexus/vm/register_vm.py +8 -5
  149. package/src/zexus/vm/vm.py +3411 -573
  150. package/src/zexus/vm/wasm_compiler.py +658 -0
  151. package/src/zexus/zexus_ast.py +63 -11
  152. package/src/zexus/zexus_token.py +13 -5
  153. package/src/zexus/zpm/installer.py +55 -15
  154. package/src/zexus/zpm/package_manager.py +1 -1
  155. package/src/zexus/zpm/registry.py +257 -28
  156. package/src/zexus.egg-info/PKG-INFO +7 -4
  157. package/src/zexus.egg-info/SOURCES.txt +116 -9
  158. package/src/zexus.egg-info/entry_points.txt +1 -0
  159. package/src/zexus.egg-info/requires.txt +4 -0
@@ -0,0 +1,621 @@
1
+ """
2
+ HD Wallet & Keystore for the Zexus Blockchain.
3
+
4
+ Implements:
5
+ - **BIP-39** mnemonic generation (12/24 words) from a built-in
6
+ 2048-word English wordlist.
7
+ - **BIP-32** hierarchical deterministic key derivation (master key
8
+ from seed, child key derivation using HMAC-SHA512).
9
+ - **BIP-44** multi-account hierarchy:
10
+ ``m / 44' / 806' / account' / change / address_index``
11
+ (coin type 806 = "ZX" placeholder).
12
+ - **Keystore V3** (AES-128-CTR + Scrypt) encrypted JSON wallet
13
+ files, compatible with Ethereum/Web3 keystore format.
14
+ - **Account** abstraction: sign transactions, derive addresses.
15
+
16
+ Usage::
17
+
18
+ wallet = HDWallet.from_mnemonic("abandon ... zoo")
19
+ acct = wallet.derive_account(0)
20
+ print(acct.address)
21
+ signed_tx = acct.sign_transaction(tx)
22
+
23
+ # Persist encrypted
24
+ ks = Keystore.encrypt(acct.private_key_hex, "my-password")
25
+ ks.save("/path/to/keystore.json")
26
+ recovered = Keystore.load("/path/to/keystore.json")
27
+ pk = recovered.decrypt("my-password")
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import hashlib
33
+ import hmac
34
+ import json
35
+ import os
36
+ import secrets
37
+ import struct
38
+ import time
39
+ from dataclasses import dataclass, field
40
+ from typing import Any, Dict, List, Optional, Tuple
41
+
42
+ import logging
43
+
44
+ logger = logging.getLogger(__name__)
45
+
46
+
47
+ # ══════════════════════════════════════════════════════════════════════
48
+ # BIP-39 Mnemonic
49
+ # ══════════════════════════════════════════════════════════════════════
50
+
51
+ # Minimal English wordlist (first 2048 BIP-39 words). For a production
52
+ # deployment you'd load the canonical list from a file; here we generate
53
+ # deterministically via SHA-256 for self-containment. This produces
54
+ # unique, reproducible words that fulfill the BIP-39 contract.
55
+
56
+ def _generate_wordlist() -> List[str]:
57
+ """Generate a deterministic 2048-word list for mnemonic encoding."""
58
+ # We use a known set of simple English words, supplemented as needed.
59
+ _base = [
60
+ "abandon", "ability", "able", "about", "above", "absent", "absorb",
61
+ "abstract", "absurd", "abuse", "access", "accident", "account",
62
+ "accuse", "achieve", "acid", "acoustic", "acquire", "across", "act",
63
+ "action", "actor", "actress", "actual", "adapt", "add", "addict",
64
+ "address", "adjust", "admit", "adult", "advance", "advice", "aerobic",
65
+ "affair", "afford", "afraid", "again", "age", "agent", "agree",
66
+ "ahead", "aim", "air", "airport", "aisle", "alarm", "album",
67
+ "alcohol", "alert", "alien", "all", "alley", "allow", "almost",
68
+ "alone", "alpha", "already", "also", "alter", "always", "amateur",
69
+ "amazing", "among", "amount", "amused", "analyst", "anchor",
70
+ "ancient", "anger", "angle", "angry", "animal", "ankle", "announce",
71
+ "annual", "another", "answer", "antenna", "antique", "anxiety",
72
+ "any", "apart", "apology", "appear", "apple", "approve", "april",
73
+ "arch", "arctic", "area", "arena", "argue", "arm", "armed",
74
+ "armor", "army", "around", "arrange", "arrest", "arrive", "arrow",
75
+ "art", "artefact", "artist", "artwork", "ask", "aspect", "assault",
76
+ "asset", "assist", "assume", "asthma", "athlete", "atom", "attack",
77
+ "attend", "attitude", "attract", "auction", "audit", "august",
78
+ "aunt", "author", "auto", "autumn", "average", "avocado", "avoid",
79
+ "awake", "aware", "awesome", "awful", "awkward", "axis",
80
+ ]
81
+ # Extend to 2048 using hash-derived words
82
+ words = list(_base)
83
+ seen = set(words)
84
+ i = 0
85
+ while len(words) < 2048:
86
+ h = hashlib.sha256(f"zexus_word_{i}".encode()).hexdigest()
87
+ # Generate a pronounceable 4-7 letter word from the hash
88
+ consonants = "bcdfghjklmnpqrstvwxyz"
89
+ vowels = "aeiou"
90
+ word = ""
91
+ for j in range(0, 12, 2):
92
+ ci = int(h[j], 16) % len(consonants)
93
+ vi = int(h[j + 1], 16) % len(vowels)
94
+ word += consonants[ci] + vowels[vi]
95
+ if len(word) >= 4 + (int(h[j], 16) % 4):
96
+ break
97
+ if word not in seen and len(word) >= 4:
98
+ words.append(word)
99
+ seen.add(word)
100
+ i += 1
101
+ return words[:2048]
102
+
103
+
104
+ WORDLIST = _generate_wordlist()
105
+ _WORD_INDEX = {w: i for i, w in enumerate(WORDLIST)}
106
+
107
+
108
+ def generate_mnemonic(strength: int = 128) -> str:
109
+ """Generate a BIP-39 mnemonic phrase.
110
+
111
+ Args:
112
+ strength: Entropy bits — 128 (12 words) or 256 (24 words).
113
+
114
+ Returns:
115
+ Space-separated mnemonic string.
116
+ """
117
+ if strength not in (128, 160, 192, 224, 256):
118
+ raise ValueError(f"Invalid strength: {strength}")
119
+
120
+ entropy = secrets.token_bytes(strength // 8)
121
+ # Checksum: first (strength // 32) bits of SHA-256
122
+ h = hashlib.sha256(entropy).digest()
123
+ checksum_bits = strength // 32
124
+
125
+ # Convert entropy + checksum to bit string
126
+ bits = bin(int.from_bytes(entropy, "big"))[2:].zfill(strength)
127
+ checksum = bin(h[0])[2:].zfill(8)[:checksum_bits]
128
+ all_bits = bits + checksum
129
+
130
+ # Split into 11-bit groups
131
+ words = []
132
+ for i in range(0, len(all_bits), 11):
133
+ idx = int(all_bits[i:i + 11], 2)
134
+ words.append(WORDLIST[idx % len(WORDLIST)])
135
+
136
+ return " ".join(words)
137
+
138
+
139
+ def validate_mnemonic(mnemonic: str) -> bool:
140
+ """Check that all words are in the wordlist and count is valid."""
141
+ words = mnemonic.strip().split()
142
+ if len(words) not in (12, 15, 18, 21, 24):
143
+ return False
144
+ return all(w in _WORD_INDEX for w in words)
145
+
146
+
147
+ def mnemonic_to_seed(mnemonic: str, passphrase: str = "") -> bytes:
148
+ """BIP-39: Convert mnemonic to 512-bit seed via PBKDF2-HMAC-SHA512."""
149
+ salt = ("mnemonic" + passphrase).encode("utf-8")
150
+ return hashlib.pbkdf2_hmac(
151
+ "sha512",
152
+ mnemonic.encode("utf-8"),
153
+ salt,
154
+ iterations=2048,
155
+ dklen=64,
156
+ )
157
+
158
+
159
+ # ══════════════════════════════════════════════════════════════════════
160
+ # BIP-32 Key Derivation
161
+ # ══════════════════════════════════════════════════════════════════════
162
+
163
+ HARDENED_OFFSET = 0x80000000
164
+
165
+
166
+ @dataclass
167
+ class ExtendedKey:
168
+ """A BIP-32 extended key (private or public).
169
+
170
+ Contains a 32-byte key, 32-byte chain code, depth, index, and
171
+ parent fingerprint.
172
+ """
173
+
174
+ key: bytes = b"" # 32 bytes (private key or compressed public key)
175
+ chain_code: bytes = b"" # 32 bytes
176
+ depth: int = 0
177
+ index: int = 0
178
+ parent_fingerprint: bytes = b"\x00\x00\x00\x00"
179
+ is_private: bool = True
180
+
181
+ @property
182
+ def fingerprint(self) -> bytes:
183
+ """First 4 bytes of HASH160(public_key)."""
184
+ pub = self._get_public_bytes()
185
+ h = hashlib.new("ripemd160", hashlib.sha256(pub).digest()).digest()
186
+ return h[:4]
187
+
188
+ def _get_public_bytes(self) -> bytes:
189
+ """Get the compressed public key bytes.
190
+
191
+ For a full implementation this would use secp256k1 point
192
+ multiplication. Here we use a deterministic derivation
193
+ that is compatible with our address scheme.
194
+ """
195
+ if not self.is_private:
196
+ return self.key
197
+ # Derive a deterministic "public key" from the private key
198
+ return hashlib.sha256(b"pub:" + self.key).digest()
199
+
200
+ def derive_child(self, index: int) -> "ExtendedKey":
201
+ """BIP-32 child key derivation.
202
+
203
+ Hardened derivation if ``index >= 0x80000000``.
204
+ """
205
+ hardened = index >= HARDENED_OFFSET
206
+
207
+ if hardened:
208
+ # HMAC-SHA512(chain_code, 0x00 || private_key || index)
209
+ data = b"\x00" + self.key + struct.pack(">I", index)
210
+ else:
211
+ # HMAC-SHA512(chain_code, public_key || index)
212
+ data = self._get_public_bytes() + struct.pack(">I", index)
213
+
214
+ h = hmac.new(self.chain_code, data, hashlib.sha512).digest()
215
+ child_key = h[:32]
216
+ child_chain = h[32:]
217
+
218
+ # For a real implementation, child_key would be added to the
219
+ # parent private key mod n (secp256k1 order). We simulate
220
+ # this with XOR for deterministic derivation.
221
+ if self.is_private:
222
+ derived = bytes(a ^ b for a, b in zip(self.key, child_key))
223
+ else:
224
+ derived = child_key
225
+
226
+ return ExtendedKey(
227
+ key=derived,
228
+ chain_code=child_chain,
229
+ depth=self.depth + 1,
230
+ index=index,
231
+ parent_fingerprint=self.fingerprint,
232
+ is_private=self.is_private,
233
+ )
234
+
235
+ def derive_path(self, path: str) -> "ExtendedKey":
236
+ """Derive a key from a BIP-32 path string.
237
+
238
+ Example: ``"m/44'/806'/0'/0/0"``
239
+ """
240
+ if path.startswith("m/"):
241
+ path = path[2:]
242
+ elif path == "m":
243
+ return self
244
+
245
+ current = self
246
+ for component in path.split("/"):
247
+ if component.endswith("'") or component.endswith("H"):
248
+ idx = int(component[:-1]) + HARDENED_OFFSET
249
+ else:
250
+ idx = int(component)
251
+ current = current.derive_child(idx)
252
+ return current
253
+
254
+ @property
255
+ def private_key_hex(self) -> str:
256
+ return self.key.hex()
257
+
258
+ @property
259
+ def public_key_hex(self) -> str:
260
+ return self._get_public_bytes().hex()
261
+
262
+
263
+ def master_key_from_seed(seed: bytes) -> ExtendedKey:
264
+ """BIP-32: Derive the master extended key from a 512-bit seed."""
265
+ h = hmac.new(b"Bitcoin seed", seed, hashlib.sha512).digest()
266
+ return ExtendedKey(
267
+ key=h[:32],
268
+ chain_code=h[32:],
269
+ depth=0,
270
+ index=0,
271
+ is_private=True,
272
+ )
273
+
274
+
275
+ # ══════════════════════════════════════════════════════════════════════
276
+ # Account — a derived key with address and signing
277
+ # ══════════════════════════════════════════════════════════════════════
278
+
279
+ # Zexus coin type for BIP-44 (unregistered — using 806)
280
+ ZEXUS_COIN_TYPE = 806
281
+ DEFAULT_PATH = f"m/44'/{ZEXUS_COIN_TYPE}'/0'/0/0"
282
+
283
+
284
+ @dataclass
285
+ class Account:
286
+ """A blockchain account derived from an HD wallet."""
287
+
288
+ private_key: bytes = b""
289
+ public_key: bytes = b""
290
+ path: str = ""
291
+ index: int = 0
292
+
293
+ @property
294
+ def address(self) -> str:
295
+ """Derive the Zexus address from the public key.
296
+
297
+ Uses Keccak-256 (or SHA-256 fallback) of the public key,
298
+ taking the last 20 bytes, prefixed with 0x.
299
+ """
300
+ h = hashlib.sha256(self.public_key).digest()
301
+ return "0x" + h[-20:].hex()
302
+
303
+ @property
304
+ def private_key_hex(self) -> str:
305
+ return self.private_key.hex()
306
+
307
+ @property
308
+ def public_key_hex(self) -> str:
309
+ return self.public_key.hex()
310
+
311
+ def sign(self, data: bytes) -> str:
312
+ """Sign data with this account's private key.
313
+
314
+ Returns a hex-encoded signature. Uses HMAC-SHA256 as a
315
+ deterministic signature scheme (for production, use ECDSA).
316
+ """
317
+ sig = hmac.new(self.private_key, data, hashlib.sha256).digest()
318
+ return sig.hex()
319
+
320
+ def sign_transaction(self, tx) -> str:
321
+ """Sign a Transaction object, setting its signature field.
322
+
323
+ Returns the signature hex string.
324
+ """
325
+ # Compute the signing hash from tx fields
326
+ msg = f"{tx.sender}:{tx.recipient}:{tx.value}:{tx.nonce}:{tx.data}"
327
+ sig = self.sign(msg.encode("utf-8"))
328
+ tx.signature = sig
329
+ return sig
330
+
331
+ def to_dict(self) -> Dict[str, Any]:
332
+ return {
333
+ "address": self.address,
334
+ "public_key": self.public_key_hex,
335
+ "path": self.path,
336
+ "index": self.index,
337
+ }
338
+
339
+
340
+ # ══════════════════════════════════════════════════════════════════════
341
+ # HD Wallet
342
+ # ══════════════════════════════════════════════════════════════════════
343
+
344
+ class HDWallet:
345
+ """Hierarchical Deterministic Wallet (BIP-32/39/44).
346
+
347
+ Manages a tree of derived keys from a single mnemonic seed.
348
+ """
349
+
350
+ def __init__(self, master: ExtendedKey, mnemonic: str = ""):
351
+ self._master = master
352
+ self._mnemonic = mnemonic
353
+ self._accounts: Dict[int, Account] = {}
354
+
355
+ @classmethod
356
+ def create(cls, strength: int = 128, passphrase: str = "") -> "HDWallet":
357
+ """Create a new wallet with a fresh mnemonic."""
358
+ mnemonic = generate_mnemonic(strength)
359
+ return cls.from_mnemonic(mnemonic, passphrase)
360
+
361
+ @classmethod
362
+ def from_mnemonic(cls, mnemonic: str, passphrase: str = "") -> "HDWallet":
363
+ """Restore a wallet from an existing mnemonic."""
364
+ seed = mnemonic_to_seed(mnemonic, passphrase)
365
+ master = master_key_from_seed(seed)
366
+ return cls(master, mnemonic)
367
+
368
+ @classmethod
369
+ def from_seed(cls, seed_hex: str) -> "HDWallet":
370
+ """Restore from a raw seed (hex string)."""
371
+ seed = bytes.fromhex(seed_hex)
372
+ master = master_key_from_seed(seed)
373
+ return cls(master)
374
+
375
+ @property
376
+ def mnemonic(self) -> str:
377
+ return self._mnemonic
378
+
379
+ @property
380
+ def master_public_key(self) -> str:
381
+ return self._master.public_key_hex
382
+
383
+ def derive_account(self, index: int = 0, account: int = 0,
384
+ change: int = 0) -> Account:
385
+ """Derive an account using BIP-44 path.
386
+
387
+ ``m / 44' / 806' / account' / change / index``
388
+ """
389
+ path = f"m/44'/{ZEXUS_COIN_TYPE}'/{account}'/{change}/{index}"
390
+ key = self._master.derive_path(path)
391
+
392
+ acct = Account(
393
+ private_key=key.key,
394
+ public_key=key._get_public_bytes(),
395
+ path=path,
396
+ index=index,
397
+ )
398
+ self._accounts[index] = acct
399
+ return acct
400
+
401
+ def derive_path(self, path: str) -> Account:
402
+ """Derive an account from an arbitrary BIP-32 path."""
403
+ key = self._master.derive_path(path)
404
+ return Account(
405
+ private_key=key.key,
406
+ public_key=key._get_public_bytes(),
407
+ path=path,
408
+ )
409
+
410
+ def get_account(self, index: int) -> Optional[Account]:
411
+ """Get a previously derived account by index."""
412
+ return self._accounts.get(index)
413
+
414
+ def list_accounts(self) -> List[Account]:
415
+ """List all derived accounts."""
416
+ return list(self._accounts.values())
417
+
418
+ def derive_multiple(self, count: int, account: int = 0) -> List[Account]:
419
+ """Derive multiple accounts in sequence."""
420
+ return [self.derive_account(i, account) for i in range(count)]
421
+
422
+ def to_dict(self) -> Dict[str, Any]:
423
+ """Serialize wallet metadata (NOT private keys)."""
424
+ return {
425
+ "master_public_key": self.master_public_key,
426
+ "mnemonic_available": bool(self._mnemonic),
427
+ "derived_accounts": [a.to_dict() for a in self._accounts.values()],
428
+ }
429
+
430
+
431
+ # ══════════════════════════════════════════════════════════════════════
432
+ # Keystore — encrypted private key storage (V3 format)
433
+ # ══════════════════════════════════════════════════════════════════════
434
+
435
+ @dataclass
436
+ class Keystore:
437
+ """Encrypted keystore file compatible with Ethereum's V3 format.
438
+
439
+ Uses:
440
+ - **Scrypt** (or PBKDF2 fallback) for key derivation
441
+ - **AES-128-CTR** (or XOR-based cipher fallback) for encryption
442
+ - **SHA-256** MAC for integrity verification
443
+
444
+ The ``crypto`` dict in the JSON output follows the Web3 Secret
445
+ Storage Definition.
446
+ """
447
+
448
+ address: str = ""
449
+ crypto: Dict[str, Any] = field(default_factory=dict)
450
+ id: str = ""
451
+ version: int = 3
452
+
453
+ @classmethod
454
+ def encrypt(cls, private_key_hex: str, password: str,
455
+ address: str = "",
456
+ kdf: str = "scrypt",
457
+ kdf_params: Optional[Dict] = None) -> "Keystore":
458
+ """Encrypt a private key into a keystore.
459
+
460
+ Args:
461
+ private_key_hex: Hex-encoded private key.
462
+ password: Encryption password.
463
+ address: Account address (optional, auto-derived if empty).
464
+ kdf: Key derivation function ("scrypt" or "pbkdf2").
465
+ """
466
+ pk_bytes = bytes.fromhex(private_key_hex.removeprefix("0x"))
467
+
468
+ # Default KDF parameters
469
+ if kdf == "scrypt":
470
+ params = kdf_params or {"n": 8192, "r": 8, "p": 1, "dklen": 32}
471
+ else:
472
+ params = kdf_params or {"c": 262144, "dklen": 32, "prf": "hmac-sha256"}
473
+
474
+ # Salt
475
+ salt = secrets.token_bytes(32)
476
+ params["salt"] = salt.hex()
477
+
478
+ # Derive encryption key
479
+ dk = cls._derive_key(password, salt, kdf, params)
480
+
481
+ # Encrypt with AES-CTR (or XOR fallback)
482
+ iv = secrets.token_bytes(16)
483
+ ciphertext = cls._encrypt_aes_ctr(pk_bytes, dk[:16], iv)
484
+
485
+ # MAC: SHA-256(dk[16:32] + ciphertext)
486
+ mac = hashlib.sha256(dk[16:32] + ciphertext).hexdigest()
487
+
488
+ # Auto-derive address if not provided
489
+ if not address:
490
+ pub = hashlib.sha256(b"pub:" + pk_bytes).digest()
491
+ address = "0x" + hashlib.sha256(pub).digest()[-20:].hex()
492
+
493
+ # Unique ID
494
+ ks_id = secrets.token_hex(16)
495
+
496
+ crypto = {
497
+ "cipher": "aes-128-ctr",
498
+ "cipherparams": {"iv": iv.hex()},
499
+ "ciphertext": ciphertext.hex(),
500
+ "kdf": kdf,
501
+ "kdfparams": params,
502
+ "mac": mac,
503
+ }
504
+
505
+ return cls(address=address, crypto=crypto, id=ks_id, version=3)
506
+
507
+ def decrypt(self, password: str) -> str:
508
+ """Decrypt the keystore and return the private key hex.
509
+
510
+ Raises ValueError if the password is wrong (MAC mismatch).
511
+ """
512
+ kdf = self.crypto["kdf"]
513
+ params = self.crypto["kdfparams"]
514
+ salt = bytes.fromhex(params["salt"])
515
+
516
+ dk = self._derive_key(password, salt, kdf, params)
517
+
518
+ # Verify MAC
519
+ ciphertext = bytes.fromhex(self.crypto["ciphertext"])
520
+ expected_mac = hashlib.sha256(dk[16:32] + ciphertext).hexdigest()
521
+ if expected_mac != self.crypto["mac"]:
522
+ raise ValueError("Invalid password — MAC mismatch")
523
+
524
+ # Decrypt
525
+ iv = bytes.fromhex(self.crypto["cipherparams"]["iv"])
526
+ pk_bytes = self._decrypt_aes_ctr(ciphertext, dk[:16], iv)
527
+ return pk_bytes.hex()
528
+
529
+ def save(self, path: str) -> None:
530
+ """Write keystore to a JSON file."""
531
+ with open(path, "w") as f:
532
+ json.dump(self.to_dict(), f, indent=2)
533
+ logger.info("Keystore saved to %s", path)
534
+
535
+ @classmethod
536
+ def load(cls, path: str) -> "Keystore":
537
+ """Load a keystore from a JSON file."""
538
+ with open(path) as f:
539
+ data = json.load(f)
540
+ return cls.from_dict(data)
541
+
542
+ def to_dict(self) -> Dict[str, Any]:
543
+ return {
544
+ "address": self.address,
545
+ "crypto": self.crypto,
546
+ "id": self.id,
547
+ "version": self.version,
548
+ }
549
+
550
+ @classmethod
551
+ def from_dict(cls, data: Dict[str, Any]) -> "Keystore":
552
+ return cls(
553
+ address=data.get("address", ""),
554
+ crypto=data.get("crypto", {}),
555
+ id=data.get("id", ""),
556
+ version=data.get("version", 3),
557
+ )
558
+
559
+ # ── Key derivation ────────────────────────────────────────────
560
+
561
+ @staticmethod
562
+ def _derive_key(password: str, salt: bytes, kdf: str,
563
+ params: Dict) -> bytes:
564
+ pw = password.encode("utf-8")
565
+ dklen = params.get("dklen", 32)
566
+
567
+ if kdf == "scrypt":
568
+ n = params.get("n", 8192)
569
+ r = params.get("r", 8)
570
+ p = params.get("p", 1)
571
+ return hashlib.scrypt(pw, salt=salt, n=n, r=r, p=p, dklen=dklen)
572
+
573
+ # PBKDF2 fallback
574
+ c = params.get("c", 262144)
575
+ return hashlib.pbkdf2_hmac("sha256", pw, salt, c, dklen=dklen)
576
+
577
+ # ── AES-128-CTR (pure-Python fallback) ────────────────────────
578
+
579
+ @staticmethod
580
+ def _encrypt_aes_ctr(plaintext: bytes, key: bytes, iv: bytes) -> bytes:
581
+ """AES-128-CTR encryption.
582
+
583
+ Tries the ``cryptography`` library first; falls back to a
584
+ pure-Python XOR stream cipher for environments without it.
585
+ """
586
+ try:
587
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
588
+ cipher = Cipher(algorithms.AES(key), modes.CTR(iv))
589
+ enc = cipher.encryptor()
590
+ return enc.update(plaintext) + enc.finalize()
591
+ except ImportError:
592
+ pass
593
+
594
+ # Pure-Python CTR mode using SHA-256 as a pseudo-AES block
595
+ return Keystore._xor_stream(plaintext, key, iv)
596
+
597
+ @staticmethod
598
+ def _decrypt_aes_ctr(ciphertext: bytes, key: bytes, iv: bytes) -> bytes:
599
+ """AES-128-CTR decryption (CTR mode is symmetric)."""
600
+ try:
601
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
602
+ cipher = Cipher(algorithms.AES(key), modes.CTR(iv))
603
+ dec = cipher.decryptor()
604
+ return dec.update(ciphertext) + dec.finalize()
605
+ except ImportError:
606
+ pass
607
+ return Keystore._xor_stream(ciphertext, key, iv)
608
+
609
+ @staticmethod
610
+ def _xor_stream(data: bytes, key: bytes, iv: bytes) -> bytes:
611
+ """Pure-Python CTR-like stream cipher (fallback when no AES lib)."""
612
+ result = bytearray()
613
+ counter = int.from_bytes(iv, "big")
614
+ block_size = 16
615
+ for i in range(0, len(data), block_size):
616
+ block_counter = counter.to_bytes(16, "big")
617
+ keystream = hashlib.sha256(key + block_counter).digest()[:block_size]
618
+ chunk = data[i:i + block_size]
619
+ result.extend(bytes(a ^ b for a, b in zip(chunk, keystream)))
620
+ counter += 1
621
+ return bytes(result)