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
|
@@ -1,19 +1,255 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Zexus Post-Quantum Cryptography Bridge
|
|
3
|
+
|
|
4
|
+
Provides post-quantum digital signature support. When the ``pqcrypto``
|
|
5
|
+
or ``oqs`` library is available, real SPHINCS+-SHA2-128f is used.
|
|
6
|
+
Otherwise, a pure-Python **WOTS+ (Winternitz One-Time Signature Plus)**
|
|
7
|
+
hash-based scheme is provided as a cryptographically real fallback.
|
|
8
|
+
|
|
9
|
+
WOTS+ is provably secure under the assumption that the underlying hash
|
|
10
|
+
function (SHA-256) is a pseudorandom function. It is one of the building
|
|
11
|
+
blocks of XMSS (RFC 8391) and SPHINCS+.
|
|
12
|
+
|
|
13
|
+
Limitations of the WOTS+ fallback:
|
|
14
|
+
- Each private key should only sign ONE message safely. Re-using a
|
|
15
|
+
WOTS+ key for a second message leaks partial private key information.
|
|
16
|
+
- Signatures are ~2 KB (67 × 32 bytes).
|
|
17
|
+
|
|
18
|
+
For production deployments involving many signatures per key, install a
|
|
19
|
+
real post-quantum library:
|
|
20
|
+
pip install pqcrypto # or: pip install oqs
|
|
21
|
+
"""
|
|
22
|
+
|
|
1
23
|
import hashlib
|
|
24
|
+
import secrets
|
|
25
|
+
from typing import Dict, Tuple
|
|
26
|
+
|
|
27
|
+
# ── Try real post-quantum libraries first ────────────────────────────
|
|
28
|
+
|
|
29
|
+
_PQ_BACKEND = "wots" # default fallback
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
import oqs # liboqs-python
|
|
33
|
+
_PQ_BACKEND = "oqs"
|
|
34
|
+
except ImportError:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
if _PQ_BACKEND == "wots":
|
|
38
|
+
try:
|
|
39
|
+
from pqcrypto.sign.sphincs_sha2_128f import (
|
|
40
|
+
generate_keypair as _sphincs_keygen,
|
|
41
|
+
sign as _sphincs_sign_raw,
|
|
42
|
+
verify as _sphincs_verify_raw,
|
|
43
|
+
)
|
|
44
|
+
_PQ_BACKEND = "pqcrypto"
|
|
45
|
+
except ImportError:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
50
|
+
# WOTS+ (Winternitz One-Time Signature Plus) — Pure Python Fallback
|
|
51
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
52
|
+
|
|
53
|
+
_WOTS_N = 32 # security parameter (hash output bytes = 256 bits)
|
|
54
|
+
_WOTS_W = 16 # Winternitz parameter
|
|
55
|
+
_WOTS_LEN1 = 64 # ceil(8*N / log2(W)) = ceil(256/4) = 64
|
|
56
|
+
_WOTS_LEN2 = 3 # floor(log2(LEN1 * (W-1)) / log2(W)) + 1 = 3
|
|
57
|
+
_WOTS_LEN = _WOTS_LEN1 + _WOTS_LEN2 # 67 chains total
|
|
58
|
+
|
|
59
|
+
# Fixed public seed for domain separation (safe — only provides randomization)
|
|
60
|
+
_WOTS_PUB_SEED = hashlib.sha256(b"zexus-wots-public-seed-v1").digest()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _hash(data: bytes) -> bytes:
|
|
64
|
+
return hashlib.sha256(data).digest()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _wots_chain(value: bytes, start: int, steps: int, addr: int) -> bytes:
|
|
68
|
+
"""Iterate the hash chain ``steps`` times starting at index ``start``.
|
|
69
|
+
|
|
70
|
+
Uses bitmask indices start, start+1, ..., start+steps-1 so that
|
|
71
|
+
chain(x, 0, a, addr) followed by chain(result, a, b, addr) equals
|
|
72
|
+
chain(x, 0, a+b, addr). This composability is essential for WOTS+.
|
|
73
|
+
"""
|
|
74
|
+
result = value
|
|
75
|
+
for i in range(start, start + steps):
|
|
76
|
+
bitmask = _hash(_WOTS_PUB_SEED + addr.to_bytes(4, "big") + i.to_bytes(4, "big"))
|
|
77
|
+
result = _hash(bytes(a ^ b for a, b in zip(result, bitmask)))
|
|
78
|
+
return result
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _base_w(msg_hash: bytes, w: int = _WOTS_W) -> list:
|
|
82
|
+
"""Convert hash to base-w representation (4-bit nibbles for w=16)."""
|
|
83
|
+
out = []
|
|
84
|
+
for byte in msg_hash:
|
|
85
|
+
out.append(byte >> 4)
|
|
86
|
+
out.append(byte & 0x0F)
|
|
87
|
+
return out
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _checksum(msg_base_w: list) -> list:
|
|
91
|
+
"""Compute WOTS+ checksum digits."""
|
|
92
|
+
total = sum(_WOTS_W - 1 - v for v in msg_base_w)
|
|
93
|
+
cs = []
|
|
94
|
+
for _ in range(_WOTS_LEN2):
|
|
95
|
+
cs.append(total % _WOTS_W)
|
|
96
|
+
total //= _WOTS_W
|
|
97
|
+
cs.reverse()
|
|
98
|
+
return cs
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def wots_keygen(seed: bytes = b"") -> Tuple[bytes, bytes]:
|
|
102
|
+
"""Generate a WOTS+ keypair.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
(private_key, public_key)
|
|
106
|
+
private_key = 32-byte seed
|
|
107
|
+
public_key = 67 × 32 = 2144 bytes
|
|
108
|
+
"""
|
|
109
|
+
if not seed:
|
|
110
|
+
seed = secrets.token_bytes(_WOTS_N)
|
|
111
|
+
|
|
112
|
+
pk_parts = []
|
|
113
|
+
for i in range(_WOTS_LEN):
|
|
114
|
+
sk_i = _hash(seed + i.to_bytes(4, "big"))
|
|
115
|
+
pk_i = _wots_chain(sk_i, 0, _WOTS_W - 1, i)
|
|
116
|
+
pk_parts.append(pk_i)
|
|
117
|
+
|
|
118
|
+
return seed, b"".join(pk_parts)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def wots_sign(message: bytes, private_key: bytes) -> bytes:
|
|
122
|
+
"""Sign a message using WOTS+.
|
|
123
|
+
|
|
124
|
+
Returns signature bytes (67 × 32 = 2144 bytes).
|
|
125
|
+
"""
|
|
126
|
+
seed = private_key[:_WOTS_N]
|
|
127
|
+
msg_hash = _hash(message)
|
|
128
|
+
msg_bw = _base_w(msg_hash)
|
|
129
|
+
msg_bw += _checksum(msg_bw)
|
|
130
|
+
|
|
131
|
+
sig_parts = []
|
|
132
|
+
for i in range(_WOTS_LEN):
|
|
133
|
+
sk_i = _hash(seed + i.to_bytes(4, "big"))
|
|
134
|
+
sig_i = _wots_chain(sk_i, 0, msg_bw[i], i)
|
|
135
|
+
sig_parts.append(sig_i)
|
|
136
|
+
|
|
137
|
+
return b"".join(sig_parts)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def wots_verify(message: bytes, signature: bytes, public_key: bytes) -> bool:
|
|
141
|
+
"""Verify a WOTS+ signature.
|
|
142
|
+
|
|
143
|
+
Returns True only if the signature is cryptographically valid.
|
|
144
|
+
"""
|
|
145
|
+
if len(signature) != _WOTS_LEN * _WOTS_N:
|
|
146
|
+
return False
|
|
147
|
+
if len(public_key) != _WOTS_LEN * _WOTS_N:
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
msg_hash = _hash(message)
|
|
151
|
+
msg_bw = _base_w(msg_hash)
|
|
152
|
+
msg_bw += _checksum(msg_bw)
|
|
153
|
+
|
|
154
|
+
for i in range(_WOTS_LEN):
|
|
155
|
+
sig_i = signature[i * _WOTS_N: (i + 1) * _WOTS_N]
|
|
156
|
+
pk_i = public_key[i * _WOTS_N: (i + 1) * _WOTS_N]
|
|
157
|
+
remaining = _WOTS_W - 1 - msg_bw[i]
|
|
158
|
+
computed = _wots_chain(sig_i, msg_bw[i], remaining, i)
|
|
159
|
+
if computed != pk_i:
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
return True
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
166
|
+
# Public API — auto-selects the best available backend
|
|
167
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
2
168
|
|
|
3
169
|
def sha256_hash(data):
|
|
170
|
+
"""SHA-256 hash (hex)."""
|
|
4
171
|
return hashlib.sha256(data.encode()).hexdigest()
|
|
5
172
|
|
|
6
|
-
|
|
7
|
-
|
|
173
|
+
|
|
174
|
+
def generate_sphincs_keypair() -> Dict[str, str]:
|
|
175
|
+
"""Generate a post-quantum keypair.
|
|
176
|
+
|
|
177
|
+
Returns dict with ``public_key`` and ``private_key`` (hex-encoded),
|
|
178
|
+
plus ``algorithm`` indicating which backend was used.
|
|
179
|
+
"""
|
|
180
|
+
if _PQ_BACKEND == "oqs":
|
|
181
|
+
signer = oqs.Signature("SPHINCS+-SHA2-128f-simple")
|
|
182
|
+
pk = signer.generate_keypair()
|
|
183
|
+
sk = signer.export_secret_key()
|
|
184
|
+
return {
|
|
185
|
+
"public_key": pk.hex(),
|
|
186
|
+
"private_key": sk.hex(),
|
|
187
|
+
"algorithm": "SPHINCS+-SHA2-128f (liboqs)",
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if _PQ_BACKEND == "pqcrypto":
|
|
191
|
+
pk, sk = _sphincs_keygen()
|
|
192
|
+
return {
|
|
193
|
+
"public_key": pk.hex(),
|
|
194
|
+
"private_key": sk.hex(),
|
|
195
|
+
"algorithm": "SPHINCS+-SHA2-128f (pqcrypto)",
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# WOTS+ fallback
|
|
199
|
+
sk, pk = wots_keygen()
|
|
8
200
|
return {
|
|
9
|
-
"public_key":
|
|
10
|
-
"private_key":
|
|
201
|
+
"public_key": pk.hex(),
|
|
202
|
+
"private_key": sk.hex(),
|
|
203
|
+
"algorithm": "WOTS+ (hash-based OTS, pure Python)",
|
|
11
204
|
}
|
|
12
205
|
|
|
206
|
+
|
|
13
207
|
def sphincs_sign(message, private_key):
|
|
14
|
-
|
|
15
|
-
|
|
208
|
+
"""Sign a message with the post-quantum private key.
|
|
209
|
+
|
|
210
|
+
Returns hex-encoded signature.
|
|
211
|
+
"""
|
|
212
|
+
msg_bytes = message.encode("utf-8") if isinstance(message, str) else message
|
|
213
|
+
sk_bytes = bytes.fromhex(private_key) if isinstance(private_key, str) else private_key
|
|
214
|
+
|
|
215
|
+
if _PQ_BACKEND == "oqs":
|
|
216
|
+
signer = oqs.Signature("SPHINCS+-SHA2-128f-simple", sk_bytes)
|
|
217
|
+
sig = signer.sign(msg_bytes)
|
|
218
|
+
return sig.hex()
|
|
219
|
+
|
|
220
|
+
if _PQ_BACKEND == "pqcrypto":
|
|
221
|
+
sig = _sphincs_sign_raw(sk_bytes, msg_bytes)
|
|
222
|
+
return sig.hex()
|
|
223
|
+
|
|
224
|
+
# WOTS+ fallback
|
|
225
|
+
sig = wots_sign(msg_bytes, sk_bytes)
|
|
226
|
+
return sig.hex()
|
|
227
|
+
|
|
16
228
|
|
|
17
229
|
def sphincs_verify(message, signature, public_key):
|
|
18
|
-
|
|
19
|
-
|
|
230
|
+
"""Verify a post-quantum signature.
|
|
231
|
+
|
|
232
|
+
Returns True ONLY if the signature is cryptographically valid.
|
|
233
|
+
No longer returns True unconditionally.
|
|
234
|
+
"""
|
|
235
|
+
msg_bytes = message.encode("utf-8") if isinstance(message, str) else message
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
sig_bytes = bytes.fromhex(signature) if isinstance(signature, str) else signature
|
|
239
|
+
pk_bytes = bytes.fromhex(public_key) if isinstance(public_key, str) else public_key
|
|
240
|
+
except (ValueError, AttributeError):
|
|
241
|
+
return False
|
|
242
|
+
|
|
243
|
+
if _PQ_BACKEND == "oqs":
|
|
244
|
+
verifier = oqs.Signature("SPHINCS+-SHA2-128f-simple")
|
|
245
|
+
return verifier.verify(msg_bytes, sig_bytes, pk_bytes)
|
|
246
|
+
|
|
247
|
+
if _PQ_BACKEND == "pqcrypto":
|
|
248
|
+
try:
|
|
249
|
+
_sphincs_verify_raw(pk_bytes, msg_bytes, sig_bytes)
|
|
250
|
+
return True
|
|
251
|
+
except Exception:
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
# WOTS+ fallback
|
|
255
|
+
return wots_verify(msg_bytes, sig_bytes, pk_bytes)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Debug Adapter Protocol (DAP) support for Zexus.
|
|
3
|
+
|
|
4
|
+
Provides step-through debugging with breakpoints, variable inspection,
|
|
5
|
+
call stack display, and expression evaluation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .debug_engine import DebugEngine, StopReason
|
|
9
|
+
|
|
10
|
+
__all__ = ["DebugEngine", "StopReason"]
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DAP (Debug Adapter Protocol) Server for Zexus.
|
|
3
|
+
|
|
4
|
+
Implements the DAP JSON wire protocol over stdin/stdout so that VS Code
|
|
5
|
+
(or any DAP client) can drive step-through debugging of Zexus programs.
|
|
6
|
+
|
|
7
|
+
The server spawns the Zexus evaluator on a worker thread and controls it
|
|
8
|
+
via the ``DebugEngine``.
|
|
9
|
+
|
|
10
|
+
Reference: https://microsoft.github.io/debug-adapter-protocol/specification
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
import threading
|
|
19
|
+
import traceback
|
|
20
|
+
from typing import Any, Dict, List, Optional
|
|
21
|
+
|
|
22
|
+
from .debug_engine import DebugEngine, StopReason
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Helpers
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
def _read_message(stream) -> Optional[Dict]:
|
|
30
|
+
"""Read one DAP message (Content-Length header + JSON body)."""
|
|
31
|
+
headers: Dict[str, str] = {}
|
|
32
|
+
while True:
|
|
33
|
+
line = stream.readline()
|
|
34
|
+
if not line:
|
|
35
|
+
return None # EOF
|
|
36
|
+
if isinstance(line, bytes):
|
|
37
|
+
line = line.decode("utf-8")
|
|
38
|
+
line = line.strip()
|
|
39
|
+
if line == "":
|
|
40
|
+
break
|
|
41
|
+
if ":" in line:
|
|
42
|
+
key, val = line.split(":", 1)
|
|
43
|
+
headers[key.strip()] = val.strip()
|
|
44
|
+
length = int(headers.get("Content-Length", 0))
|
|
45
|
+
if length == 0:
|
|
46
|
+
return None
|
|
47
|
+
body = stream.read(length)
|
|
48
|
+
if isinstance(body, bytes):
|
|
49
|
+
body = body.decode("utf-8")
|
|
50
|
+
return json.loads(body)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _send_message(stream, msg: Dict):
|
|
54
|
+
"""Write one DAP message."""
|
|
55
|
+
body = json.dumps(msg)
|
|
56
|
+
header = f"Content-Length: {len(body)}\r\n\r\n"
|
|
57
|
+
data = header + body
|
|
58
|
+
if hasattr(stream, "buffer"):
|
|
59
|
+
stream.buffer.write(data.encode("utf-8"))
|
|
60
|
+
stream.buffer.flush()
|
|
61
|
+
else:
|
|
62
|
+
stream.write(data.encode("utf-8") if isinstance(data, str) else data)
|
|
63
|
+
stream.flush()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
# DAP Server
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
_SEQ = 0
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _next_seq() -> int:
|
|
74
|
+
global _SEQ
|
|
75
|
+
_SEQ += 1
|
|
76
|
+
return _SEQ
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class DAPServer:
|
|
80
|
+
"""Minimal DAP server that wraps the Zexus evaluator."""
|
|
81
|
+
|
|
82
|
+
def __init__(self, input_stream=None, output_stream=None):
|
|
83
|
+
self._in = input_stream or sys.stdin
|
|
84
|
+
self._out = output_stream or sys.stdout
|
|
85
|
+
self.engine = DebugEngine()
|
|
86
|
+
self.engine.on_stopped = self._on_stopped
|
|
87
|
+
self.engine.on_terminated = self._on_terminated
|
|
88
|
+
self._program_path: Optional[str] = None
|
|
89
|
+
self._worker: Optional[threading.Thread] = None
|
|
90
|
+
self._launched = False
|
|
91
|
+
self._variable_refs: Dict[int, Dict[str, Any]] = {}
|
|
92
|
+
self._var_ref_counter = 0
|
|
93
|
+
|
|
94
|
+
# -- Main loop ---------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
def run(self):
|
|
97
|
+
"""Read DAP messages in a loop and dispatch them."""
|
|
98
|
+
while True:
|
|
99
|
+
try:
|
|
100
|
+
msg = _read_message(self._in)
|
|
101
|
+
except Exception:
|
|
102
|
+
break
|
|
103
|
+
if msg is None:
|
|
104
|
+
break
|
|
105
|
+
msg_type = msg.get("type")
|
|
106
|
+
if msg_type == "request":
|
|
107
|
+
self._handle_request(msg)
|
|
108
|
+
# (events and responses from client are ignored)
|
|
109
|
+
|
|
110
|
+
# -- Response / event helpers ------------------------------------------
|
|
111
|
+
|
|
112
|
+
def _respond(self, request: Dict, body: Optional[Dict] = None,
|
|
113
|
+
success: bool = True, message: str = ""):
|
|
114
|
+
resp: Dict[str, Any] = {
|
|
115
|
+
"seq": _next_seq(),
|
|
116
|
+
"type": "response",
|
|
117
|
+
"request_seq": request["seq"],
|
|
118
|
+
"command": request["command"],
|
|
119
|
+
"success": success,
|
|
120
|
+
}
|
|
121
|
+
if body:
|
|
122
|
+
resp["body"] = body
|
|
123
|
+
if message:
|
|
124
|
+
resp["message"] = message
|
|
125
|
+
_send_message(self._out, resp)
|
|
126
|
+
|
|
127
|
+
def _event(self, event: str, body: Optional[Dict] = None):
|
|
128
|
+
msg: Dict[str, Any] = {
|
|
129
|
+
"seq": _next_seq(),
|
|
130
|
+
"type": "event",
|
|
131
|
+
"event": event,
|
|
132
|
+
}
|
|
133
|
+
if body:
|
|
134
|
+
msg["body"] = body
|
|
135
|
+
_send_message(self._out, msg)
|
|
136
|
+
|
|
137
|
+
# -- Request dispatch --------------------------------------------------
|
|
138
|
+
|
|
139
|
+
def _handle_request(self, req: Dict):
|
|
140
|
+
cmd = req.get("command", "")
|
|
141
|
+
handler = getattr(self, f"_cmd_{cmd}", None)
|
|
142
|
+
if handler:
|
|
143
|
+
try:
|
|
144
|
+
handler(req)
|
|
145
|
+
except Exception as exc:
|
|
146
|
+
self._respond(req, success=False, message=str(exc))
|
|
147
|
+
else:
|
|
148
|
+
# Unknown command — respond with success (DAP spec recommends this)
|
|
149
|
+
self._respond(req)
|
|
150
|
+
|
|
151
|
+
# -- DAP commands ------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
def _cmd_initialize(self, req: Dict):
|
|
154
|
+
capabilities = {
|
|
155
|
+
"supportsConfigurationDoneRequest": True,
|
|
156
|
+
"supportsFunctionBreakpoints": False,
|
|
157
|
+
"supportsConditionalBreakpoints": False,
|
|
158
|
+
"supportsEvaluateForHovers": True,
|
|
159
|
+
"supportsStepBack": False,
|
|
160
|
+
"supportsSetVariable": False,
|
|
161
|
+
"supportsTerminateRequest": True,
|
|
162
|
+
"supportsSingleThreadExecutionRequests": False,
|
|
163
|
+
}
|
|
164
|
+
self._respond(req, body=capabilities)
|
|
165
|
+
self._event("initialized")
|
|
166
|
+
|
|
167
|
+
def _cmd_configurationDone(self, req: Dict):
|
|
168
|
+
self._respond(req)
|
|
169
|
+
|
|
170
|
+
def _cmd_launch(self, req: Dict):
|
|
171
|
+
args = req.get("arguments", {})
|
|
172
|
+
self._program_path = args.get("program", "")
|
|
173
|
+
stop_on_entry = args.get("stopOnEntry", False)
|
|
174
|
+
self.engine.set_stop_on_entry(stop_on_entry)
|
|
175
|
+
self._respond(req)
|
|
176
|
+
# Start execution on worker thread
|
|
177
|
+
self._worker = threading.Thread(
|
|
178
|
+
target=self._run_program, daemon=True
|
|
179
|
+
)
|
|
180
|
+
self._launched = True
|
|
181
|
+
self._worker.start()
|
|
182
|
+
|
|
183
|
+
def _cmd_disconnect(self, req: Dict):
|
|
184
|
+
self.engine.terminate()
|
|
185
|
+
self._respond(req)
|
|
186
|
+
|
|
187
|
+
def _cmd_terminate(self, req: Dict):
|
|
188
|
+
self.engine.terminate()
|
|
189
|
+
self._respond(req)
|
|
190
|
+
|
|
191
|
+
def _cmd_setBreakpoints(self, req: Dict):
|
|
192
|
+
args = req.get("arguments", {})
|
|
193
|
+
source = args.get("source", {})
|
|
194
|
+
path = source.get("path", "")
|
|
195
|
+
bp_args = args.get("breakpoints", [])
|
|
196
|
+
lines = [b["line"] for b in bp_args]
|
|
197
|
+
bps = self.engine.set_breakpoints(path, lines)
|
|
198
|
+
body = {
|
|
199
|
+
"breakpoints": [
|
|
200
|
+
{"id": bp.id, "verified": True, "line": bp.line}
|
|
201
|
+
for bp in bps
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
self._respond(req, body=body)
|
|
205
|
+
|
|
206
|
+
def _cmd_threads(self, req: Dict):
|
|
207
|
+
self._respond(req, body={
|
|
208
|
+
"threads": [{"id": 1, "name": "Main Thread"}]
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
def _cmd_stackTrace(self, req: Dict):
|
|
212
|
+
frames = self.engine.get_stack_trace()
|
|
213
|
+
dap_frames = []
|
|
214
|
+
for f in frames:
|
|
215
|
+
self._var_ref_counter += 1
|
|
216
|
+
ref = self._var_ref_counter
|
|
217
|
+
self._variable_refs[ref] = f.variables
|
|
218
|
+
dap_frames.append({
|
|
219
|
+
"id": f.id,
|
|
220
|
+
"name": f.name,
|
|
221
|
+
"source": {"name": os.path.basename(f.file), "path": f.file},
|
|
222
|
+
"line": f.line,
|
|
223
|
+
"column": f.column,
|
|
224
|
+
"scopes": ref, # we'll use this in scopes request
|
|
225
|
+
})
|
|
226
|
+
self._respond(req, body={
|
|
227
|
+
"stackFrames": dap_frames,
|
|
228
|
+
"totalFrames": len(dap_frames),
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
def _cmd_scopes(self, req: Dict):
|
|
232
|
+
args = req.get("arguments", {})
|
|
233
|
+
frame_id = args.get("frameId", 0)
|
|
234
|
+
variables = self.engine.get_variables(frame_id)
|
|
235
|
+
self._var_ref_counter += 1
|
|
236
|
+
ref = self._var_ref_counter
|
|
237
|
+
self._variable_refs[ref] = variables
|
|
238
|
+
self._respond(req, body={
|
|
239
|
+
"scopes": [{
|
|
240
|
+
"name": "Locals",
|
|
241
|
+
"variablesReference": ref,
|
|
242
|
+
"expensive": False,
|
|
243
|
+
}]
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
def _cmd_variables(self, req: Dict):
|
|
247
|
+
args = req.get("arguments", {})
|
|
248
|
+
ref = args.get("variablesReference", 0)
|
|
249
|
+
variables = self._variable_refs.get(ref, {})
|
|
250
|
+
dap_vars = []
|
|
251
|
+
for name, val in variables.items():
|
|
252
|
+
if name.startswith("__"):
|
|
253
|
+
continue # hide internal vars
|
|
254
|
+
dap_vars.append({
|
|
255
|
+
"name": name,
|
|
256
|
+
"value": str(val),
|
|
257
|
+
"variablesReference": 0,
|
|
258
|
+
})
|
|
259
|
+
self._respond(req, body={"variables": dap_vars})
|
|
260
|
+
|
|
261
|
+
def _cmd_continue(self, req: Dict):
|
|
262
|
+
self.engine.continue_execution()
|
|
263
|
+
self._respond(req, body={"allThreadsContinued": True})
|
|
264
|
+
|
|
265
|
+
def _cmd_next(self, req: Dict):
|
|
266
|
+
self.engine.step_over()
|
|
267
|
+
self._respond(req)
|
|
268
|
+
|
|
269
|
+
def _cmd_stepIn(self, req: Dict):
|
|
270
|
+
self.engine.step_into()
|
|
271
|
+
self._respond(req)
|
|
272
|
+
|
|
273
|
+
def _cmd_stepOut(self, req: Dict):
|
|
274
|
+
self.engine.step_out()
|
|
275
|
+
self._respond(req)
|
|
276
|
+
|
|
277
|
+
def _cmd_pause(self, req: Dict):
|
|
278
|
+
self.engine.pause()
|
|
279
|
+
self._respond(req)
|
|
280
|
+
|
|
281
|
+
def _cmd_evaluate(self, req: Dict):
|
|
282
|
+
args = req.get("arguments", {})
|
|
283
|
+
expression = args.get("expression", "")
|
|
284
|
+
# Simple variable lookup from current frame
|
|
285
|
+
frames = self.engine.get_stack_trace()
|
|
286
|
+
result = "undefined"
|
|
287
|
+
if frames:
|
|
288
|
+
vs = frames[0].variables
|
|
289
|
+
if expression in vs:
|
|
290
|
+
result = str(vs[expression])
|
|
291
|
+
self._respond(req, body={"result": result, "variablesReference": 0})
|
|
292
|
+
|
|
293
|
+
# -- Execution ---------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
def _run_program(self):
|
|
296
|
+
"""Execute the Zexus program on this thread."""
|
|
297
|
+
try:
|
|
298
|
+
if not self._program_path:
|
|
299
|
+
self._event("output", {
|
|
300
|
+
"category": "stderr",
|
|
301
|
+
"output": "No program specified\n",
|
|
302
|
+
})
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
# Import Zexus machinery
|
|
306
|
+
from ..lexer import Lexer
|
|
307
|
+
from ..parser.parser import UltimateParser as Parser
|
|
308
|
+
from ..evaluator import evaluate, Evaluator
|
|
309
|
+
from ..environment import Environment
|
|
310
|
+
from ..object import String
|
|
311
|
+
|
|
312
|
+
with open(self._program_path, "r") as f:
|
|
313
|
+
source = f.read()
|
|
314
|
+
|
|
315
|
+
# Parse
|
|
316
|
+
lexer = Lexer(source, filename=self._program_path)
|
|
317
|
+
parser = Parser(lexer, "auto", enable_advanced_strategies=True)
|
|
318
|
+
program = parser.parse_program()
|
|
319
|
+
|
|
320
|
+
if parser.errors:
|
|
321
|
+
for err in parser.errors:
|
|
322
|
+
self._event("output", {
|
|
323
|
+
"category": "stderr",
|
|
324
|
+
"output": f"Parse error: {err}\n",
|
|
325
|
+
})
|
|
326
|
+
return
|
|
327
|
+
|
|
328
|
+
# Set up environment
|
|
329
|
+
env = Environment()
|
|
330
|
+
abs_path = os.path.abspath(self._program_path)
|
|
331
|
+
env.set("__file__", String(abs_path))
|
|
332
|
+
env.set("__FILE__", String(abs_path))
|
|
333
|
+
env.set("__MODULE__", String("__main__"))
|
|
334
|
+
env.set("__DIR__", String(os.path.dirname(abs_path)))
|
|
335
|
+
|
|
336
|
+
# Push initial frame
|
|
337
|
+
self.engine.notify_call(
|
|
338
|
+
"<module>", abs_path, 1, env
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
# Evaluate with debug engine attached
|
|
342
|
+
evaluator = Evaluator(use_vm=False)
|
|
343
|
+
evaluator._debug_engine = self.engine
|
|
344
|
+
evaluator._debug_file = abs_path
|
|
345
|
+
|
|
346
|
+
# Merge builtins
|
|
347
|
+
for name, val in evaluator.builtins.items():
|
|
348
|
+
env.set(name, val)
|
|
349
|
+
|
|
350
|
+
evaluator.eval_node(program, env)
|
|
351
|
+
|
|
352
|
+
except Exception as exc:
|
|
353
|
+
self._event("output", {
|
|
354
|
+
"category": "stderr",
|
|
355
|
+
"output": f"Runtime error: {exc}\n{traceback.format_exc()}",
|
|
356
|
+
})
|
|
357
|
+
finally:
|
|
358
|
+
self.engine.notify_terminated()
|
|
359
|
+
|
|
360
|
+
# -- Engine callbacks --------------------------------------------------
|
|
361
|
+
|
|
362
|
+
def _on_stopped(self, reason: StopReason, description: str):
|
|
363
|
+
reason_map = {
|
|
364
|
+
StopReason.BREAKPOINT: "breakpoint",
|
|
365
|
+
StopReason.STEP: "step",
|
|
366
|
+
StopReason.PAUSE: "pause",
|
|
367
|
+
StopReason.ENTRY: "entry",
|
|
368
|
+
StopReason.EXCEPTION: "exception",
|
|
369
|
+
}
|
|
370
|
+
self._event("stopped", {
|
|
371
|
+
"reason": reason_map.get(reason, "unknown"),
|
|
372
|
+
"threadId": 1,
|
|
373
|
+
"description": description,
|
|
374
|
+
"allThreadsStopped": True,
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
def _on_terminated(self):
|
|
378
|
+
self._event("terminated")
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
# ---------------------------------------------------------------------------
|
|
382
|
+
# Entry point (python -m zexus.dap)
|
|
383
|
+
# ---------------------------------------------------------------------------
|
|
384
|
+
|
|
385
|
+
def main():
|
|
386
|
+
server = DAPServer()
|
|
387
|
+
server.run()
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
if __name__ == "__main__":
|
|
391
|
+
main()
|