zexus 1.6.8 → 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.
- package/README.md +12 -5
- package/package.json +1 -1
- package/src/__init__.py +7 -0
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
- package/src/zexus/advanced_types.py +17 -2
- package/src/zexus/blockchain/__init__.py +411 -0
- package/src/zexus/blockchain/accelerator.py +1160 -0
- package/src/zexus/blockchain/chain.py +660 -0
- package/src/zexus/blockchain/consensus.py +821 -0
- package/src/zexus/blockchain/contract_vm.py +1019 -0
- package/src/zexus/blockchain/crypto.py +79 -14
- package/src/zexus/blockchain/events.py +526 -0
- package/src/zexus/blockchain/loadtest.py +721 -0
- package/src/zexus/blockchain/monitoring.py +350 -0
- package/src/zexus/blockchain/mpt.py +716 -0
- package/src/zexus/blockchain/multichain.py +951 -0
- package/src/zexus/blockchain/multiprocess_executor.py +338 -0
- package/src/zexus/blockchain/network.py +886 -0
- package/src/zexus/blockchain/node.py +666 -0
- package/src/zexus/blockchain/rpc.py +1203 -0
- package/src/zexus/blockchain/rust_bridge.py +421 -0
- package/src/zexus/blockchain/storage.py +423 -0
- package/src/zexus/blockchain/tokens.py +750 -0
- package/src/zexus/blockchain/upgradeable.py +1004 -0
- package/src/zexus/blockchain/verification.py +1602 -0
- package/src/zexus/blockchain/wallet.py +621 -0
- package/src/zexus/capability_system.py +184 -9
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +383 -34
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/compiler/bytecode.py +124 -7
- package/src/zexus/compiler/compat_runtime.py +6 -2
- package/src/zexus/compiler/lexer.py +16 -5
- package/src/zexus/compiler/parser.py +108 -7
- package/src/zexus/compiler/semantic.py +18 -19
- package/src/zexus/compiler/zexus_ast.py +26 -1
- package/src/zexus/concurrency_system.py +79 -0
- package/src/zexus/config.py +54 -0
- package/src/zexus/crypto_bridge.py +244 -8
- package/src/zexus/dap/__init__.py +10 -0
- package/src/zexus/dap/__main__.py +4 -0
- package/src/zexus/dap/dap_server.py +391 -0
- package/src/zexus/dap/debug_engine.py +298 -0
- package/src/zexus/environment.py +112 -9
- package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/bytecode_compiler.py +457 -37
- package/src/zexus/evaluator/core.py +644 -50
- package/src/zexus/evaluator/expressions.py +358 -62
- package/src/zexus/evaluator/functions.py +458 -20
- package/src/zexus/evaluator/resource_limiter.py +4 -4
- package/src/zexus/evaluator/statements.py +774 -122
- package/src/zexus/evaluator/unified_execution.py +573 -72
- package/src/zexus/evaluator/utils.py +14 -2
- package/src/zexus/evaluator_original.py +1 -1
- package/src/zexus/event_loop.py +186 -0
- package/src/zexus/lexer.py +742 -458
- package/src/zexus/lsp/__init__.py +1 -1
- package/src/zexus/lsp/definition_provider.py +163 -9
- package/src/zexus/lsp/server.py +22 -8
- package/src/zexus/lsp/symbol_provider.py +182 -9
- package/src/zexus/module_cache.py +239 -9
- package/src/zexus/module_manager.py +129 -1
- package/src/zexus/object.py +76 -6
- package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
- package/src/zexus/parser/parser.py +1349 -408
- package/src/zexus/parser/strategy_context.py +755 -58
- package/src/zexus/parser/strategy_structural.py +121 -21
- package/src/zexus/persistence.py +15 -1
- package/src/zexus/renderer/__init__.py +61 -0
- package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
- package/src/zexus/renderer/backend.py +261 -0
- package/src/zexus/renderer/canvas.py +78 -0
- package/src/zexus/renderer/color_system.py +201 -0
- package/src/zexus/renderer/graphics.py +31 -0
- package/src/zexus/renderer/layout.py +222 -0
- package/src/zexus/renderer/main_renderer.py +66 -0
- package/src/zexus/renderer/painter.py +30 -0
- package/src/zexus/renderer/tk_backend.py +208 -0
- package/src/zexus/renderer/web_backend.py +260 -0
- package/src/zexus/runtime/__init__.py +10 -2
- package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
- package/src/zexus/runtime/file_flags.py +137 -0
- package/src/zexus/runtime/load_manager.py +368 -0
- package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
- package/src/zexus/security.py +424 -34
- package/src/zexus/stdlib/fs.py +23 -18
- package/src/zexus/stdlib/http.py +289 -186
- package/src/zexus/stdlib/sockets.py +207 -163
- package/src/zexus/stdlib/websockets.py +282 -0
- package/src/zexus/stdlib_integration.py +369 -2
- package/src/zexus/strategy_recovery.py +6 -3
- package/src/zexus/type_checker.py +423 -0
- package/src/zexus/virtual_filesystem.py +189 -2
- package/src/zexus/vm/__init__.py +113 -3
- package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/async_optimizer.py +80 -6
- package/src/zexus/vm/binary_bytecode.py +659 -0
- package/src/zexus/vm/bytecode.py +59 -11
- package/src/zexus/vm/bytecode_converter.py +26 -12
- package/src/zexus/vm/cabi.c +1985 -0
- package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/cabi.h +127 -0
- package/src/zexus/vm/cache.py +561 -17
- package/src/zexus/vm/compiler.py +818 -51
- package/src/zexus/vm/fastops.c +15743 -0
- package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/fastops.pyx +288 -0
- package/src/zexus/vm/gas_metering.py +50 -9
- package/src/zexus/vm/jit.py +364 -20
- package/src/zexus/vm/native_jit_backend.py +1816 -0
- package/src/zexus/vm/native_runtime.cpp +1388 -0
- package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/optimizer.py +161 -11
- package/src/zexus/vm/parallel_vm.py +140 -45
- package/src/zexus/vm/peephole_optimizer.py +82 -4
- package/src/zexus/vm/profiler.py +38 -18
- package/src/zexus/vm/register_allocator.py +16 -5
- package/src/zexus/vm/register_vm.py +8 -5
- package/src/zexus/vm/vm.py +3581 -531
- package/src/zexus/vm/wasm_compiler.py +658 -0
- package/src/zexus/zexus_ast.py +137 -11
- package/src/zexus/zexus_token.py +16 -5
- package/src/zexus/zpm/installer.py +55 -15
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus/zpm/registry.py +257 -28
- package/src/zexus.egg-info/PKG-INFO +16 -6
- package/src/zexus.egg-info/SOURCES.txt +129 -17
- package/src/zexus.egg-info/entry_points.txt +1 -0
- 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)
|