zexus 1.8.0 → 1.8.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 (51) hide show
  1. package/README.md +34 -6
  2. package/bin/zexus +12 -2
  3. package/bin/zpics +12 -2
  4. package/bin/zpm +12 -2
  5. package/bin/zx +12 -2
  6. package/bin/zx-deploy +12 -2
  7. package/bin/zx-dev +12 -2
  8. package/bin/zx-run +12 -2
  9. package/package.json +2 -1
  10. package/rust_core/Cargo.lock +603 -0
  11. package/rust_core/Cargo.toml +26 -0
  12. package/rust_core/README.md +15 -0
  13. package/rust_core/pyproject.toml +25 -0
  14. package/rust_core/src/binary_bytecode.rs +543 -0
  15. package/rust_core/src/contract_vm.rs +643 -0
  16. package/rust_core/src/executor.rs +847 -0
  17. package/rust_core/src/hasher.rs +90 -0
  18. package/rust_core/src/lib.rs +71 -0
  19. package/rust_core/src/merkle.rs +128 -0
  20. package/rust_core/src/rust_vm.rs +2313 -0
  21. package/rust_core/src/signature.rs +79 -0
  22. package/rust_core/src/state_adapter.rs +281 -0
  23. package/rust_core/src/validator.rs +116 -0
  24. package/scripts/postinstall.js +204 -21
  25. package/src/zexus/__init__.py +1 -1
  26. package/src/zexus/cli/main.py +1 -1
  27. package/src/zexus/cli/zpm.py +1 -1
  28. package/src/zexus/evaluator/bytecode_compiler.py +150 -52
  29. package/src/zexus/evaluator/core.py +151 -809
  30. package/src/zexus/evaluator/expressions.py +27 -22
  31. package/src/zexus/evaluator/functions.py +171 -126
  32. package/src/zexus/evaluator/statements.py +55 -112
  33. package/src/zexus/module_cache.py +20 -9
  34. package/src/zexus/object.py +330 -38
  35. package/src/zexus/parser/parser.py +103 -23
  36. package/src/zexus/parser/strategy_context.py +318 -6
  37. package/src/zexus/parser/strategy_structural.py +2 -2
  38. package/src/zexus/persistence.py +46 -17
  39. package/src/zexus/security.py +140 -234
  40. package/src/zexus/type_checker.py +44 -5
  41. package/src/zexus/vm/binary_bytecode.py +7 -3
  42. package/src/zexus/vm/bytecode.py +6 -0
  43. package/src/zexus/vm/cache.py +24 -46
  44. package/src/zexus/vm/compiler.py +549 -68
  45. package/src/zexus/vm/memory_pool.py +21 -9
  46. package/src/zexus/vm/vm.py +609 -95
  47. package/src/zexus/zpm/package_manager.py +1 -1
  48. package/src/zexus.egg-info/PKG-INFO +56 -12
  49. package/src/zexus.egg-info/SOURCES.txt +14 -0
  50. package/src/zexus.egg-info/entry_points.txt +5 -1
  51. package/src/zexus.egg-info/requires.txt +26 -0
@@ -0,0 +1,79 @@
1
+ // ─────────────────────────────────────────────────────────────────────
2
+ // ECDSA-secp256k1 Signature Verification (batch-parallel)
3
+ // ─────────────────────────────────────────────────────────────────────
4
+
5
+ use k256::ecdsa::{
6
+ signature::Verifier, Signature, VerifyingKey,
7
+ };
8
+ use pyo3::prelude::*;
9
+ use rayon::prelude::*;
10
+ use sha2::{Digest, Sha256};
11
+
12
+ #[pyclass]
13
+ pub struct RustSignature;
14
+
15
+ #[pymethods]
16
+ impl RustSignature {
17
+ #[new]
18
+ fn new() -> Self {
19
+ RustSignature
20
+ }
21
+
22
+ /// Verify a single ECDSA-secp256k1 signature.
23
+ ///
24
+ /// * `message` — the raw message bytes that were signed
25
+ /// * `signature` — 64-byte DER or compact signature
26
+ /// * `public_key`— 33-byte (compressed) or 65-byte (uncompressed) public key
27
+ ///
28
+ /// Returns `true` if the signature is valid.
29
+ #[staticmethod]
30
+ fn verify(message: Vec<u8>, signature: Vec<u8>, public_key: Vec<u8>) -> bool {
31
+ Self::_verify_inner(&message, &signature, &public_key)
32
+ }
33
+
34
+ /// Batch-verify multiple signatures in parallel.
35
+ ///
36
+ /// Each item is a tuple `(message_bytes, signature_bytes, pubkey_bytes)`.
37
+ /// Returns a list of booleans (one per signature).
38
+ #[staticmethod]
39
+ fn verify_batch(
40
+ py: Python<'_>,
41
+ items: Vec<(Vec<u8>, Vec<u8>, Vec<u8>)>,
42
+ ) -> Vec<bool> {
43
+ py.allow_threads(|| {
44
+ items
45
+ .par_iter()
46
+ .map(|(msg, sig, pk)| Self::_verify_inner(msg, sig, pk))
47
+ .collect()
48
+ })
49
+ }
50
+
51
+ /// Hash a message with SHA-256 then verify the signature against the
52
+ /// hash. This matches the common blockchain pattern of sign(sha256(msg)).
53
+ #[staticmethod]
54
+ fn verify_hashed(message: Vec<u8>, signature: Vec<u8>, public_key: Vec<u8>) -> bool {
55
+ let hash = Sha256::digest(&message);
56
+ Self::_verify_inner(&hash, &signature, &public_key)
57
+ }
58
+ }
59
+
60
+ impl RustSignature {
61
+ fn _verify_inner(message: &[u8], signature: &[u8], public_key: &[u8]) -> bool {
62
+ verify_single(message, signature, public_key)
63
+ }
64
+ }
65
+
66
+ /// Public helper so other modules (validator) can call it directly.
67
+ pub fn verify_single(message: &[u8], signature: &[u8], public_key: &[u8]) -> bool {
68
+ let vk = match VerifyingKey::from_sec1_bytes(public_key) {
69
+ Ok(vk) => vk,
70
+ Err(_) => return false,
71
+ };
72
+
73
+ let sig = match Signature::from_slice(signature) {
74
+ Ok(s) => s,
75
+ Err(_) => return false,
76
+ };
77
+
78
+ vk.verify(message, &sig).is_ok()
79
+ }
@@ -0,0 +1,281 @@
1
+ // ─────────────────────────────────────────────────────────────────────
2
+ // Zexus Blockchain — Rust State Adapter (Phase 3)
3
+ // ─────────────────────────────────────────────────────────────────────
4
+ //
5
+ // In-memory state cache that batches reads/writes in Rust to minimise
6
+ // GIL crossings when the Rust VM operates on blockchain state.
7
+ //
8
+ // Architecture:
9
+ //
10
+ // 1. Pre-load state from Python dict → Rust HashMap ("warm cache")
11
+ // 2. STATE_READ/STATE_WRITE hit the Rust cache — zero Python calls
12
+ // 3. After execution, flush dirty keys back to Python in one batch
13
+ // 4. TX_BEGIN/TX_COMMIT/TX_REVERT use Rust-side snapshots
14
+ //
15
+ // This avoids per-opcode GIL acquisition for state operations,
16
+ // delivering ~50x speedup on state-heavy contracts.
17
+
18
+ use pyo3::prelude::*;
19
+ use pyo3::types::{PyDict, PyList};
20
+ use pyo3::ToPyObject;
21
+ use std::collections::{HashMap, HashSet};
22
+
23
+ /// A single value in the state cache.
24
+ #[derive(Debug, Clone)]
25
+ pub enum StateValue {
26
+ Null,
27
+ Bool(bool),
28
+ Int(i64),
29
+ Float(f64),
30
+ Str(String),
31
+ Bytes(Vec<u8>),
32
+ List(Vec<StateValue>),
33
+ Map(Vec<(String, StateValue)>),
34
+ }
35
+
36
+ impl StateValue {
37
+ /// Convert a Python object to a StateValue.
38
+ pub fn from_py(py: Python<'_>, obj: &PyObject) -> Self {
39
+ let bound = obj.bind(py);
40
+ if bound.is_none() {
41
+ return StateValue::Null;
42
+ }
43
+ if let Ok(b) = bound.extract::<bool>() {
44
+ return StateValue::Bool(b);
45
+ }
46
+ if let Ok(i) = bound.extract::<i64>() {
47
+ return StateValue::Int(i);
48
+ }
49
+ if let Ok(f) = bound.extract::<f64>() {
50
+ return StateValue::Float(f);
51
+ }
52
+ if let Ok(s) = bound.extract::<String>() {
53
+ return StateValue::Str(s);
54
+ }
55
+ if let Ok(list) = bound.downcast::<PyList>() {
56
+ let items: Vec<StateValue> = list
57
+ .iter()
58
+ .map(|item| StateValue::from_py(py, &item.to_object(py)))
59
+ .collect();
60
+ return StateValue::List(items);
61
+ }
62
+ if let Ok(dict) = bound.downcast::<PyDict>() {
63
+ let items: Vec<(String, StateValue)> = dict
64
+ .iter()
65
+ .filter_map(|(k, v)| {
66
+ k.extract::<String>()
67
+ .ok()
68
+ .map(|key| (key, StateValue::from_py(py, &v.to_object(py))))
69
+ })
70
+ .collect();
71
+ return StateValue::Map(items);
72
+ }
73
+ // Fallback: try string representation
74
+ if let Ok(s) = bound.str().and_then(|s| s.extract::<String>()) {
75
+ return StateValue::Str(s);
76
+ }
77
+ StateValue::Null
78
+ }
79
+
80
+ /// Convert to a Python object.
81
+ pub fn to_py(&self, py: Python<'_>) -> PyObject {
82
+ match self {
83
+ StateValue::Null => py.None(),
84
+ StateValue::Bool(b) => b.to_object(py),
85
+ StateValue::Int(i) => i.to_object(py),
86
+ StateValue::Float(f) => f.to_object(py),
87
+ StateValue::Str(s) => s.to_object(py),
88
+ StateValue::Bytes(b) => {
89
+ pyo3::types::PyBytes::new_bound(py, b).to_object(py)
90
+ }
91
+ StateValue::List(items) => {
92
+ let py_list = PyList::empty_bound(py);
93
+ for item in items {
94
+ let _ = py_list.append(item.to_py(py));
95
+ }
96
+ py_list.to_object(py)
97
+ }
98
+ StateValue::Map(items) => {
99
+ let py_dict = PyDict::new_bound(py);
100
+ for (k, v) in items {
101
+ let _ = py_dict.set_item(k, v.to_py(py));
102
+ }
103
+ py_dict.to_object(py)
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ /// Transaction snapshot for nested transaction support.
110
+ struct TxSnapshot {
111
+ cache: HashMap<String, StateValue>,
112
+ dirty: HashSet<String>,
113
+ }
114
+
115
+ /// Rust-side state cache with batch flush capability.
116
+ ///
117
+ /// Usage from Python:
118
+ /// ```python
119
+ /// from zexus_core import RustStateAdapter
120
+ /// adapter = RustStateAdapter()
121
+ /// adapter.load_from_dict({"key": "value", "counter": 42})
122
+ /// adapter.set("counter", 43)
123
+ /// val = adapter.get("counter")
124
+ /// dirty = adapter.flush_dirty() # returns dict of changed keys
125
+ /// ```
126
+ #[pyclass]
127
+ pub struct RustStateAdapter {
128
+ /// In-memory state cache (key → value).
129
+ cache: HashMap<String, StateValue>,
130
+
131
+ /// Set of keys that have been written (dirty) since last flush.
132
+ dirty: HashSet<String>,
133
+
134
+ /// Transaction snapshot stack for TX_BEGIN/TX_COMMIT/TX_REVERT.
135
+ tx_stack: Vec<TxSnapshot>,
136
+
137
+ /// Total reads served from cache (no Python call).
138
+ cache_hits: u64,
139
+
140
+ /// Total writes batched (no Python call until flush).
141
+ cache_writes: u64,
142
+ }
143
+
144
+ #[pymethods]
145
+ impl RustStateAdapter {
146
+ #[new]
147
+ fn new() -> Self {
148
+ RustStateAdapter {
149
+ cache: HashMap::new(),
150
+ dirty: HashSet::new(),
151
+ tx_stack: Vec::new(),
152
+ cache_hits: 0,
153
+ cache_writes: 0,
154
+ }
155
+ }
156
+
157
+ /// Bulk-load state from a Python dict into the Rust cache.
158
+ /// This is the "warm-up" step — called once before Rust VM execution.
159
+ fn load_from_dict(&mut self, py: Python<'_>, data: &Bound<'_, PyDict>) -> PyResult<u64> {
160
+ let mut count: u64 = 0;
161
+ for (k, v) in data.iter() {
162
+ let key = k.extract::<String>()?;
163
+ let val = StateValue::from_py(py, &v.to_object(py));
164
+ self.cache.insert(key, val);
165
+ count += 1;
166
+ }
167
+ Ok(count)
168
+ }
169
+
170
+ /// Read a value from the cache.
171
+ fn get(&mut self, py: Python<'_>, key: &str) -> PyObject {
172
+ self.cache_hits += 1;
173
+ match self.cache.get(key) {
174
+ Some(val) => val.to_py(py),
175
+ None => py.None(),
176
+ }
177
+ }
178
+
179
+ /// Write a value to the cache and mark it dirty.
180
+ fn set(&mut self, py: Python<'_>, key: String, value: PyObject) {
181
+ let sv = StateValue::from_py(py, &value);
182
+ self.cache.insert(key.clone(), sv);
183
+ self.dirty.insert(key);
184
+ self.cache_writes += 1;
185
+ }
186
+
187
+ /// Check if a key exists in the cache.
188
+ fn contains(&self, key: &str) -> bool {
189
+ self.cache.contains_key(key)
190
+ }
191
+
192
+ /// Delete a key from the cache and mark it dirty (sets to Null).
193
+ fn delete(&mut self, key: &str) {
194
+ self.cache.insert(key.to_string(), StateValue::Null);
195
+ self.dirty.insert(key.to_string());
196
+ self.cache_writes += 1;
197
+ }
198
+
199
+ /// Return all dirty (modified) keys as a Python dict.
200
+ /// Clears the dirty set after flushing.
201
+ fn flush_dirty(&mut self, py: Python<'_>) -> PyResult<PyObject> {
202
+ let result = PyDict::new_bound(py);
203
+ for key in self.dirty.drain() {
204
+ if let Some(val) = self.cache.get(&key) {
205
+ result.set_item(&key, val.to_py(py))?;
206
+ }
207
+ }
208
+ Ok(result.to_object(py))
209
+ }
210
+
211
+ /// Return the full cache as a Python dict.
212
+ fn to_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
213
+ let result = PyDict::new_bound(py);
214
+ for (k, v) in &self.cache {
215
+ result.set_item(k, v.to_py(py))?;
216
+ }
217
+ Ok(result.to_object(py))
218
+ }
219
+
220
+ /// Get the number of keys in the cache.
221
+ fn len(&self) -> usize {
222
+ self.cache.len()
223
+ }
224
+
225
+ /// Get the number of dirty keys pending flush.
226
+ fn dirty_count(&self) -> usize {
227
+ self.dirty.len()
228
+ }
229
+
230
+ /// Begin a transaction — snapshot current state.
231
+ fn tx_begin(&mut self) {
232
+ let snapshot = TxSnapshot {
233
+ cache: self.cache.clone(),
234
+ dirty: self.dirty.clone(),
235
+ };
236
+ self.tx_stack.push(snapshot);
237
+ }
238
+
239
+ /// Commit a transaction — discard the snapshot, keep changes.
240
+ fn tx_commit(&mut self) -> bool {
241
+ if self.tx_stack.is_empty() {
242
+ return false;
243
+ }
244
+ // Pop snapshot but keep current state — changes are committed
245
+ let _snapshot = self.tx_stack.pop().unwrap();
246
+ // Merge dirty keys from this transaction into the parent
247
+ // (dirty set already contains all writes — nothing extra needed)
248
+ true
249
+ }
250
+
251
+ /// Revert a transaction — restore the snapshot.
252
+ fn tx_revert(&mut self) -> bool {
253
+ if let Some(snapshot) = self.tx_stack.pop() {
254
+ self.cache = snapshot.cache;
255
+ self.dirty = snapshot.dirty;
256
+ true
257
+ } else {
258
+ false
259
+ }
260
+ }
261
+
262
+ /// Return cache statistics.
263
+ fn stats(&self, py: Python<'_>) -> PyResult<PyObject> {
264
+ let result = PyDict::new_bound(py);
265
+ result.set_item("cache_size", self.cache.len())?;
266
+ result.set_item("dirty_count", self.dirty.len())?;
267
+ result.set_item("cache_hits", self.cache_hits)?;
268
+ result.set_item("cache_writes", self.cache_writes)?;
269
+ result.set_item("tx_depth", self.tx_stack.len())?;
270
+ Ok(result.to_object(py))
271
+ }
272
+
273
+ /// Reset the adapter.
274
+ fn clear(&mut self) {
275
+ self.cache.clear();
276
+ self.dirty.clear();
277
+ self.tx_stack.clear();
278
+ self.cache_hits = 0;
279
+ self.cache_writes = 0;
280
+ }
281
+ }
@@ -0,0 +1,116 @@
1
+ // ─────────────────────────────────────────────────────────────────────
2
+ // Block Validation — fast parallel checks
3
+ // ─────────────────────────────────────────────────────────────────────
4
+
5
+ use pyo3::prelude::*;
6
+ use rayon::prelude::*;
7
+ use sha2::{Digest, Sha256};
8
+ use std::collections::HashMap;
9
+
10
+ /// Validates block header integrity at native speed.
11
+ #[pyclass]
12
+ pub struct RustBlockValidator;
13
+
14
+ #[pymethods]
15
+ impl RustBlockValidator {
16
+ #[new]
17
+ fn new() -> Self {
18
+ RustBlockValidator
19
+ }
20
+
21
+ /// Verify a block hash matches its header contents.
22
+ ///
23
+ /// `header_json` — JSON-encoded block header
24
+ /// `expected_hash` — the hash claimed by the block
25
+ ///
26
+ /// Returns `true` if SHA-256(header_json) == expected_hash.
27
+ #[staticmethod]
28
+ fn verify_block_hash(header_json: &str, expected_hash: &str) -> bool {
29
+ let hash = Sha256::digest(header_json.as_bytes());
30
+ hex::encode(hash) == expected_hash
31
+ }
32
+
33
+ /// Validate a chain of block hashes in parallel.
34
+ ///
35
+ /// `blocks` — list of (header_json, claimed_hash, prev_hash) tuples.
36
+ /// Returns a list of booleans — one per block.
37
+ ///
38
+ /// Checks:
39
+ /// 1. SHA-256(header) == claimed_hash
40
+ /// 2. prev_hash links correctly (sequential check)
41
+ #[staticmethod]
42
+ fn validate_chain(
43
+ py: Python<'_>,
44
+ blocks: Vec<(String, String, String)>,
45
+ ) -> Vec<bool> {
46
+ if blocks.is_empty() {
47
+ return vec![];
48
+ }
49
+
50
+ // Step 1: verify hashes in parallel
51
+ let hash_checks: Vec<bool> = py.allow_threads(|| {
52
+ blocks
53
+ .par_iter()
54
+ .map(|(header, claimed, _)| {
55
+ let hash = Sha256::digest(header.as_bytes());
56
+ hex::encode(hash) == *claimed
57
+ })
58
+ .collect()
59
+ });
60
+
61
+ // Step 2: verify chain linkage (sequential — inherently ordered)
62
+ let mut results = hash_checks;
63
+ for i in 1..blocks.len() {
64
+ let expected_prev = &blocks[i - 1].1; // previous block's hash
65
+ let actual_prev = &blocks[i].2; // this block's prev_hash
66
+ if expected_prev != actual_prev {
67
+ results[i] = false;
68
+ }
69
+ }
70
+
71
+ results
72
+ }
73
+
74
+ /// Check Proof-of-Work difficulty: does the block hash have the
75
+ /// required number of leading zero bits?
76
+ #[staticmethod]
77
+ fn check_pow_difficulty(block_hash: &str, difficulty: u32) -> bool {
78
+ let bytes = match hex::decode(block_hash) {
79
+ Ok(b) => b,
80
+ Err(_) => return false,
81
+ };
82
+
83
+ let mut leading_zeros = 0u32;
84
+ for byte in &bytes {
85
+ if *byte == 0 {
86
+ leading_zeros += 8;
87
+ } else {
88
+ leading_zeros += byte.leading_zeros();
89
+ break;
90
+ }
91
+ }
92
+
93
+ leading_zeros >= difficulty
94
+ }
95
+
96
+ /// Batch-validate transaction signatures within a block.
97
+ ///
98
+ /// `tx_data` — list of (message_bytes, signature_bytes, pubkey_bytes).
99
+ /// Returns the number of valid signatures.
100
+ #[staticmethod]
101
+ fn validate_tx_signatures(
102
+ py: Python<'_>,
103
+ tx_data: Vec<(Vec<u8>, Vec<u8>, Vec<u8>)>,
104
+ ) -> usize {
105
+ let results: Vec<bool> = py.allow_threads(|| {
106
+ use rayon::prelude::*;
107
+ tx_data
108
+ .par_iter()
109
+ .map(|(msg, sig, pk)| {
110
+ crate::signature::verify_single(msg, sig, pk)
111
+ })
112
+ .collect()
113
+ });
114
+ results.iter().filter(|&&v| v).count()
115
+ }
116
+ }