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.
- package/README.md +34 -6
- package/bin/zexus +12 -2
- package/bin/zpics +12 -2
- package/bin/zpm +12 -2
- package/bin/zx +12 -2
- package/bin/zx-deploy +12 -2
- package/bin/zx-dev +12 -2
- package/bin/zx-run +12 -2
- package/package.json +2 -1
- package/rust_core/Cargo.lock +603 -0
- package/rust_core/Cargo.toml +26 -0
- package/rust_core/README.md +15 -0
- package/rust_core/pyproject.toml +25 -0
- package/rust_core/src/binary_bytecode.rs +543 -0
- package/rust_core/src/contract_vm.rs +643 -0
- package/rust_core/src/executor.rs +847 -0
- package/rust_core/src/hasher.rs +90 -0
- package/rust_core/src/lib.rs +71 -0
- package/rust_core/src/merkle.rs +128 -0
- package/rust_core/src/rust_vm.rs +2313 -0
- package/rust_core/src/signature.rs +79 -0
- package/rust_core/src/state_adapter.rs +281 -0
- package/rust_core/src/validator.rs +116 -0
- package/scripts/postinstall.js +204 -21
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/cli/main.py +1 -1
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/evaluator/bytecode_compiler.py +150 -52
- package/src/zexus/evaluator/core.py +151 -809
- package/src/zexus/evaluator/expressions.py +27 -22
- package/src/zexus/evaluator/functions.py +171 -126
- package/src/zexus/evaluator/statements.py +55 -112
- package/src/zexus/module_cache.py +20 -9
- package/src/zexus/object.py +330 -38
- package/src/zexus/parser/parser.py +103 -23
- package/src/zexus/parser/strategy_context.py +318 -6
- package/src/zexus/parser/strategy_structural.py +2 -2
- package/src/zexus/persistence.py +46 -17
- package/src/zexus/security.py +140 -234
- package/src/zexus/type_checker.py +44 -5
- package/src/zexus/vm/binary_bytecode.py +7 -3
- package/src/zexus/vm/bytecode.py +6 -0
- package/src/zexus/vm/cache.py +24 -46
- package/src/zexus/vm/compiler.py +549 -68
- package/src/zexus/vm/memory_pool.py +21 -9
- package/src/zexus/vm/vm.py +609 -95
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus.egg-info/PKG-INFO +56 -12
- package/src/zexus.egg-info/SOURCES.txt +14 -0
- package/src/zexus.egg-info/entry_points.txt +5 -1
- 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
|
+
}
|