astreum 0.2.6__tar.gz → 0.2.8__tar.gz
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.
Potentially problematic release.
This version of astreum might be problematic. Click here for more details.
- {astreum-0.2.6/src/astreum.egg-info → astreum-0.2.8}/PKG-INFO +1 -1
- {astreum-0.2.6 → astreum-0.2.8}/pyproject.toml +1 -1
- astreum-0.2.8/src/astreum/crypto/quadratic_form.py +123 -0
- astreum-0.2.8/src/astreum/crypto/wesolowski.py +154 -0
- {astreum-0.2.6 → astreum-0.2.8}/src/astreum/node.py +115 -124
- {astreum-0.2.6 → astreum-0.2.8/src/astreum.egg-info}/PKG-INFO +1 -1
- {astreum-0.2.6 → astreum-0.2.8}/src/astreum.egg-info/SOURCES.txt +2 -0
- {astreum-0.2.6 → astreum-0.2.8}/LICENSE +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/README.md +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/setup.cfg +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/src/astreum/__init__.py +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/src/astreum/_node/__init__.py +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/src/astreum/_node/storage/__init__.py +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/src/astreum/_node/storage/merkle.py +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/src/astreum/_node/storage/patricia.py +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/src/astreum/crypto/__init__.py +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/src/astreum/crypto/ed25519.py +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/src/astreum/crypto/x25519.py +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/src/astreum/format.py +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/src/astreum/lispeum/__init__.py +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/src/astreum/lispeum/parser.py +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/src/astreum/lispeum/tokenizer.py +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/src/astreum.egg-info/dependency_links.txt +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/src/astreum.egg-info/requires.txt +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/src/astreum.egg-info/top_level.txt +0 -0
- {astreum-0.2.6 → astreum-0.2.8}/tests/test_node_machine.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
|
|
5
5
|
Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
|
|
6
6
|
Project-URL: Homepage, https://github.com/astreum/lib
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def extended_gcd(a: int, b: int) -> tuple[int, int, int]:
|
|
5
|
+
"""
|
|
6
|
+
Return (g, x, y) such that a*x + b*y = g = gcd(a, b).
|
|
7
|
+
"""
|
|
8
|
+
if b == 0:
|
|
9
|
+
return (a, 1, 0)
|
|
10
|
+
g, x1, y1 = extended_gcd(b, a % b)
|
|
11
|
+
return (g, y1, x1 - (a // b) * y1)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def modinv(a: int, m: int) -> int:
|
|
15
|
+
"""
|
|
16
|
+
Return modular inverse of a mod m.
|
|
17
|
+
"""
|
|
18
|
+
g, x, _ = extended_gcd(a, m)
|
|
19
|
+
if g != 1:
|
|
20
|
+
raise ValueError(f"No modular inverse for {a} modulo {m}")
|
|
21
|
+
return x % m
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def is_reduced(q: 'QuadraticForm') -> bool:
|
|
25
|
+
"""
|
|
26
|
+
Check if the form q is in reduced (Gauss) form:
|
|
27
|
+
|b| <= a <= c, and if a == c then b >= 0.
|
|
28
|
+
"""
|
|
29
|
+
return abs(q.b) <= q.a <= q.c and not (q.a == q.c and q.b < 0)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def is_primitive(a: int, b: int, c: int) -> bool:
|
|
33
|
+
"""
|
|
34
|
+
Check if the form coefficients are coprime: gcd(a, b, c) == 1.
|
|
35
|
+
"""
|
|
36
|
+
return math.gcd(math.gcd(a, b), c) == 1
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class QuadraticForm:
|
|
40
|
+
"""
|
|
41
|
+
Represents the binary quadratic form ax^2 + bxy + cy^2 with discriminant D,
|
|
42
|
+
stored in reduced, primitive form.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, a: int, b: int, c: int, D: int):
|
|
46
|
+
if b*b - 4*a*c != D:
|
|
47
|
+
raise ValueError(f"Discriminant mismatch: b^2 - 4ac = {b*b - 4*a*c}, expected {D}")
|
|
48
|
+
if not is_primitive(a, b, c):
|
|
49
|
+
raise ValueError("Form coefficients are not coprime (not primitive)")
|
|
50
|
+
self.a = a
|
|
51
|
+
self.b = b
|
|
52
|
+
self.c = c
|
|
53
|
+
self.D = D
|
|
54
|
+
|
|
55
|
+
def reduce(self) -> 'QuadraticForm':
|
|
56
|
+
"""
|
|
57
|
+
Perform Gauss reduction until the form is reduced.
|
|
58
|
+
"""
|
|
59
|
+
while not is_reduced(self):
|
|
60
|
+
self._gauss_step()
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
def _gauss_step(self) -> None:
|
|
64
|
+
"""
|
|
65
|
+
One iteration of Gauss reduction on the current form.
|
|
66
|
+
"""
|
|
67
|
+
a, b, c = self.a, self.b, self.c
|
|
68
|
+
# Compute m = round(b / (2a)) using integer arithmetic
|
|
69
|
+
sign = 1 if b >= 0 else -1
|
|
70
|
+
m = (b + sign * a) // (2 * a)
|
|
71
|
+
# Update b and c
|
|
72
|
+
b_new = b - 2 * m * a
|
|
73
|
+
c_new = m * m * a - m * b + c
|
|
74
|
+
# Assign back
|
|
75
|
+
self.b = b_new
|
|
76
|
+
self.c = c_new
|
|
77
|
+
# Swap if needed to ensure a <= c and proper sign
|
|
78
|
+
if a > self.c or (a == self.c and self.b < 0):
|
|
79
|
+
self.a, self.b, self.c = self.c, -self.b, a
|
|
80
|
+
|
|
81
|
+
def __mul__(self, other: 'QuadraticForm') -> 'QuadraticForm':
|
|
82
|
+
"""
|
|
83
|
+
Dirichlet (NUCOMP) composition of two forms of the same discriminant.
|
|
84
|
+
"""
|
|
85
|
+
if self.D != other.D:
|
|
86
|
+
raise ValueError("Cannot compose forms with different discriminants")
|
|
87
|
+
a1, b1, c1 = self.a, self.b, self.c
|
|
88
|
+
a2, b2, c2 = other.a, other.b, other.c
|
|
89
|
+
D = self.D
|
|
90
|
+
# Compute g = gcd(a1, a2, (b1 + b2)//2)
|
|
91
|
+
k = (b1 + b2) // 2
|
|
92
|
+
g = math.gcd(math.gcd(a1, a2), k)
|
|
93
|
+
a1p = a1 // g
|
|
94
|
+
a2p = a2 // g
|
|
95
|
+
# Solve m * a1p ≡ (b2 - b1)//2 mod a2p
|
|
96
|
+
diff = (b2 - b1) // 2
|
|
97
|
+
inv = modinv(a1p, a2p)
|
|
98
|
+
m = (diff * inv) % a2p
|
|
99
|
+
# Compute composed coefficients
|
|
100
|
+
b3 = b1 + 2 * m * a1
|
|
101
|
+
a3 = a1 * a2p
|
|
102
|
+
c3 = (b3 * b3 - D) // (4 * a3)
|
|
103
|
+
return QuadraticForm(a3, b3, c3, D).reduce()
|
|
104
|
+
|
|
105
|
+
def to_bytes(self) -> bytes:
|
|
106
|
+
"""
|
|
107
|
+
Serialize this form to bytes (a and b, big-endian, fixed width).
|
|
108
|
+
"""
|
|
109
|
+
# Width: enough to hold |D| bitlength / 8 rounded up
|
|
110
|
+
width = (self.D.bit_length() + 15) // 8
|
|
111
|
+
return self.a.to_bytes(width, 'big', signed=True) + \
|
|
112
|
+
self.b.to_bytes(width, 'big', signed=True)
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def from_bytes(cls, data: bytes, D: int) -> 'QuadraticForm':
|
|
116
|
+
"""
|
|
117
|
+
Deserialize bytes back into a QuadraticForm for discriminant D.
|
|
118
|
+
"""
|
|
119
|
+
width = len(data) // 2
|
|
120
|
+
a = int.from_bytes(data[:width], 'big', signed=True)
|
|
121
|
+
b = int.from_bytes(data[width:], 'big', signed=True)
|
|
122
|
+
c = (b*b - D) // (4 * a)
|
|
123
|
+
return cls(a, b, c, D).reduce()
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
from typing import Tuple
|
|
3
|
+
from quadratic_form import QuadraticForm
|
|
4
|
+
|
|
5
|
+
# --- Helper functions ---------------------------------------------------
|
|
6
|
+
|
|
7
|
+
def hash_to_int(*args: bytes) -> int:
|
|
8
|
+
"""
|
|
9
|
+
Hash the concatenation of args (bytes) to a large integer using SHA-256.
|
|
10
|
+
"""
|
|
11
|
+
h = hashlib.sha256()
|
|
12
|
+
for b in args:
|
|
13
|
+
h.update(b)
|
|
14
|
+
return int.from_bytes(h.digest(), 'big')
|
|
15
|
+
|
|
16
|
+
# --- Class-group VDF functions using QuadraticForm ----------------------
|
|
17
|
+
|
|
18
|
+
def group_mul(x: QuadraticForm, y: QuadraticForm) -> QuadraticForm:
|
|
19
|
+
"""
|
|
20
|
+
Compose two class-group elements via QuadraticForm multiplication.
|
|
21
|
+
"""
|
|
22
|
+
return (x * y)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def identity(D: int) -> QuadraticForm:
|
|
26
|
+
"""
|
|
27
|
+
Return the identity element of the class group for discriminant D.
|
|
28
|
+
"""
|
|
29
|
+
# For D ≡ 1 mod 4, identity form is (1, 1, (1-D)//4)
|
|
30
|
+
b0 = 1
|
|
31
|
+
c0 = (b0*b0 - D) // 4
|
|
32
|
+
return QuadraticForm(1, b0, c0, D)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def class_group_square(x: QuadraticForm) -> QuadraticForm:
|
|
36
|
+
"""
|
|
37
|
+
One sequential squaring step in the class group.
|
|
38
|
+
"""
|
|
39
|
+
return group_mul(x, x)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def group_exp(x: QuadraticForm, exponent: int) -> QuadraticForm:
|
|
43
|
+
"""
|
|
44
|
+
Fast exponentiation in the class group by repeated squaring.
|
|
45
|
+
"""
|
|
46
|
+
result = identity(x.D)
|
|
47
|
+
base = x
|
|
48
|
+
e = exponent
|
|
49
|
+
while e > 0:
|
|
50
|
+
if e & 1:
|
|
51
|
+
result = group_mul(result, base)
|
|
52
|
+
base = group_mul(base, base)
|
|
53
|
+
e >>= 1
|
|
54
|
+
return result
|
|
55
|
+
|
|
56
|
+
# --- Wesolowski proof and verify ----------------------------------------
|
|
57
|
+
|
|
58
|
+
def compute_wesolowski_proof(
|
|
59
|
+
x0: QuadraticForm,
|
|
60
|
+
y: QuadraticForm,
|
|
61
|
+
T: int
|
|
62
|
+
) -> QuadraticForm:
|
|
63
|
+
"""
|
|
64
|
+
Compute the Wesolowski proof π for VDF evaluation:
|
|
65
|
+
Solve 2^T = c * q + r, where
|
|
66
|
+
c = hash(x0 || y || T)
|
|
67
|
+
Return π = x0^q in the class group.
|
|
68
|
+
"""
|
|
69
|
+
# Derive challenge c
|
|
70
|
+
h_bytes = serialize(x0) + serialize(y) + T.to_bytes((T.bit_length()+7)//8, 'big')
|
|
71
|
+
c = hash_to_int(h_bytes)
|
|
72
|
+
# Divide exponent
|
|
73
|
+
two_T = 1 << T
|
|
74
|
+
q, r = divmod(two_T, c)
|
|
75
|
+
# π = x0^q
|
|
76
|
+
return group_exp(x0, q)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def verify_wesolowski_proof(
|
|
80
|
+
x0: QuadraticForm,
|
|
81
|
+
y: QuadraticForm,
|
|
82
|
+
pi: QuadraticForm,
|
|
83
|
+
T: int
|
|
84
|
+
) -> bool:
|
|
85
|
+
"""
|
|
86
|
+
Verify π satisfies: π^c * x0^r == y.
|
|
87
|
+
"""
|
|
88
|
+
h_bytes = serialize(x0) + serialize(y) + T.to_bytes((T.bit_length()+7)//8, 'big')
|
|
89
|
+
c = hash_to_int(h_bytes)
|
|
90
|
+
two_T = 1 << T
|
|
91
|
+
q, r = divmod(two_T, c)
|
|
92
|
+
lhs = group_mul(group_exp(pi, c), group_exp(x0, r))
|
|
93
|
+
return lhs == y
|
|
94
|
+
|
|
95
|
+
# --- Serialization helpers ----------------------------------------------
|
|
96
|
+
|
|
97
|
+
def serialize(x: QuadraticForm) -> bytes:
|
|
98
|
+
"""
|
|
99
|
+
Serialize a QuadraticForm to bytes.
|
|
100
|
+
"""
|
|
101
|
+
return x.to_bytes()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def deserialize(data: bytes, D: int) -> QuadraticForm:
|
|
105
|
+
"""
|
|
106
|
+
Deserialize bytes into a QuadraticForm of discriminant D.
|
|
107
|
+
"""
|
|
108
|
+
return QuadraticForm.from_bytes(data, D)
|
|
109
|
+
|
|
110
|
+
# --- Public VDF API -----------------------------------------------------
|
|
111
|
+
|
|
112
|
+
def generate(
|
|
113
|
+
old_output: bytes,
|
|
114
|
+
T: int,
|
|
115
|
+
D: int
|
|
116
|
+
) -> Tuple[bytes, bytes]:
|
|
117
|
+
"""
|
|
118
|
+
Evaluate the VDF by sequentially squaring the previous output 'T' times,
|
|
119
|
+
then produce a Wesolowski proof.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
new_output : serialized new VDF output (y)
|
|
123
|
+
proof : serialized proof (π)
|
|
124
|
+
"""
|
|
125
|
+
# Decode previous output
|
|
126
|
+
x0 = deserialize(old_output, D)
|
|
127
|
+
# Sequential squarings
|
|
128
|
+
x = x0
|
|
129
|
+
for _ in range(T):
|
|
130
|
+
x = class_group_square(x)
|
|
131
|
+
# Serialize output
|
|
132
|
+
y_bytes = serialize(x)
|
|
133
|
+
# Compute proof
|
|
134
|
+
pi = compute_wesolowski_proof(x0, x, T)
|
|
135
|
+
proof_bytes = serialize(pi)
|
|
136
|
+
return y_bytes, proof_bytes
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def verify(
|
|
140
|
+
old_output: bytes,
|
|
141
|
+
new_output: bytes,
|
|
142
|
+
proof: bytes,
|
|
143
|
+
T: int,
|
|
144
|
+
D: int
|
|
145
|
+
) -> bool:
|
|
146
|
+
"""
|
|
147
|
+
Verify the Wesolowski VDF proof.
|
|
148
|
+
|
|
149
|
+
Returns True if valid, False otherwise.
|
|
150
|
+
"""
|
|
151
|
+
x0 = deserialize(old_output, D)
|
|
152
|
+
y = deserialize(new_output, D)
|
|
153
|
+
pi = deserialize(proof, D)
|
|
154
|
+
return verify_wesolowski_proof(x0, y, pi, T)
|
|
@@ -289,23 +289,39 @@ class Expr:
|
|
|
289
289
|
return f'(error "{self.message}" in {self.origin})'
|
|
290
290
|
|
|
291
291
|
class Env:
|
|
292
|
-
def __init__(
|
|
293
|
-
self
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
292
|
+
def __init__(
|
|
293
|
+
self,
|
|
294
|
+
data: Optional[Dict[str, Expr]] = None,
|
|
295
|
+
parent_id: Optional[uuid.UUID] = None,
|
|
296
|
+
max_exprs: Optional[int] = 8,
|
|
297
|
+
):
|
|
298
|
+
self.data: Dict[str, Expr] = data if data is not None else {}
|
|
299
|
+
self.parent_id: Optional[uuid.UUID] = parent_id
|
|
300
|
+
self.max_exprs: Optional[int] = max_exprs
|
|
301
|
+
|
|
302
|
+
def put(self, name: str, value: Expr) -> None:
|
|
303
|
+
if (
|
|
304
|
+
self.max_exprs is not None
|
|
305
|
+
and name not in self.data
|
|
306
|
+
and len(self.data) >= self.max_exprs
|
|
307
|
+
):
|
|
308
|
+
raise RuntimeError(
|
|
309
|
+
f"environment full: {len(self.data)} ≥ max_exprs={self.max_exprs}"
|
|
310
|
+
)
|
|
297
311
|
self.data[name] = value
|
|
298
312
|
|
|
299
313
|
def get(self, name: str) -> Optional[Expr]:
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
else:
|
|
305
|
-
return None
|
|
314
|
+
return self.data.get(name)
|
|
315
|
+
|
|
316
|
+
def pop(self, name: str) -> Optional[Expr]:
|
|
317
|
+
return self.data.pop(name, None)
|
|
306
318
|
|
|
307
|
-
def __repr__(self):
|
|
308
|
-
return
|
|
319
|
+
def __repr__(self) -> str:
|
|
320
|
+
return (
|
|
321
|
+
f"Env(size={len(self.data)}, "
|
|
322
|
+
f"max_exprs={self.max_exprs}, "
|
|
323
|
+
f"parent_id={self.parent_id})"
|
|
324
|
+
)
|
|
309
325
|
|
|
310
326
|
|
|
311
327
|
class Node:
|
|
@@ -706,14 +722,19 @@ class Node:
|
|
|
706
722
|
self.environments[env_id] = Env(parent_id=parent_id)
|
|
707
723
|
return env_id
|
|
708
724
|
|
|
709
|
-
def machine_get_or_create_environment(
|
|
725
|
+
def machine_get_or_create_environment(
|
|
726
|
+
self,
|
|
727
|
+
env_id: Optional[uuid.UUID] = None,
|
|
728
|
+
parent_id: Optional[uuid.UUID] = None,
|
|
729
|
+
max_exprs: Optional[int] = None
|
|
730
|
+
) -> uuid.UUID:
|
|
710
731
|
with self.machine_environments_lock:
|
|
711
732
|
if env_id is not None and env_id in self.environments:
|
|
712
733
|
return env_id
|
|
713
734
|
new_id = env_id if env_id is not None else uuid.uuid4()
|
|
714
735
|
while new_id in self.environments:
|
|
715
736
|
new_id = uuid.uuid4()
|
|
716
|
-
self.environments[new_id] = Env(parent_id=parent_id)
|
|
737
|
+
self.environments[new_id] = Env(parent_id=parent_id, max_exprs=max_exprs)
|
|
717
738
|
return new_id
|
|
718
739
|
|
|
719
740
|
def machine_delete_environment(self, env_id: uuid.UUID) -> bool:
|
|
@@ -911,120 +932,90 @@ class Node:
|
|
|
911
932
|
# env=env,
|
|
912
933
|
# )
|
|
913
934
|
|
|
914
|
-
# Integer
|
|
935
|
+
# Integer arithmetic primitives
|
|
915
936
|
elif first.value == "+":
|
|
916
937
|
args = expr.elements[1:]
|
|
917
|
-
if
|
|
918
|
-
return Expr.Error(
|
|
919
|
-
|
|
920
|
-
for
|
|
921
|
-
|
|
922
|
-
if isinstance(
|
|
923
|
-
return
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
938
|
+
if not args:
|
|
939
|
+
return Expr.Error("'+' expects at least 1 argument", origin=expr)
|
|
940
|
+
vals = [self.machine_expr_eval(env_id=env_id, expr=a) for a in args]
|
|
941
|
+
for v in vals:
|
|
942
|
+
if isinstance(v, Expr.Error): return v
|
|
943
|
+
if not isinstance(v, Expr.Integer):
|
|
944
|
+
return Expr.Error("'+' only accepts integer operands", origin=v)
|
|
945
|
+
return Expr.Integer(abs(vals[0].value) if len(vals) == 1
|
|
946
|
+
else sum(v.value for v in vals))
|
|
947
|
+
|
|
948
|
+
elif first.value == "-":
|
|
949
|
+
args = expr.elements[1:]
|
|
950
|
+
if not args:
|
|
951
|
+
return Expr.Error("'-' expects at least 1 argument", origin=expr)
|
|
952
|
+
vals = [self.machine_expr_eval(env_id=env_id, expr=a) for a in args]
|
|
953
|
+
for v in vals:
|
|
954
|
+
if isinstance(v, Expr.Error): return v
|
|
955
|
+
if not isinstance(v, Expr.Integer):
|
|
956
|
+
return Expr.Error("'-' only accepts integer operands", origin=v)
|
|
957
|
+
if len(vals) == 1:
|
|
958
|
+
return Expr.Integer(-vals[0].value)
|
|
959
|
+
result = vals[0].value
|
|
960
|
+
for v in vals[1:]:
|
|
961
|
+
result -= v.value
|
|
929
962
|
return Expr.Integer(result)
|
|
930
|
-
|
|
931
|
-
# # Subtraction
|
|
932
|
-
# elif first.value == "-":
|
|
933
|
-
# evaluated_args = [self.evaluate_expression(arg, env) for arg in expr.elements[1:]]
|
|
934
|
-
|
|
935
|
-
# # Check for non-integer arguments
|
|
936
|
-
# if not all(isinstance(arg, Expr.Integer) for arg in evaluated_args):
|
|
937
|
-
# return Expr.Error(
|
|
938
|
-
# category="TypeError",
|
|
939
|
-
# message="All arguments to - must be integers"
|
|
940
|
-
# )
|
|
941
|
-
|
|
942
|
-
# # With only one argument, negate it
|
|
943
|
-
# if len(evaluated_args) == 1:
|
|
944
|
-
# return Expr.Integer(-evaluated_args[0].value)
|
|
945
|
-
|
|
946
|
-
# # With multiple arguments, subtract all from the first
|
|
947
|
-
# result = evaluated_args[0].value
|
|
948
|
-
# for arg in evaluated_args[1:]:
|
|
949
|
-
# result -= arg.value
|
|
950
|
-
|
|
951
|
-
# return Expr.Integer(result)
|
|
952
|
-
|
|
953
|
-
# # Multiplication
|
|
954
|
-
# elif first.value == "*":
|
|
955
|
-
# evaluated_args = [self.evaluate_expression(arg, env) for arg in expr.elements[1:]]
|
|
956
963
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
# # Check for non-integer arguments
|
|
976
|
-
# if not all(isinstance(arg, Expr.Integer) for arg in evaluated_args):
|
|
977
|
-
# return Expr.Error(
|
|
978
|
-
# category="TypeError",
|
|
979
|
-
# message="All arguments to / must be integers"
|
|
980
|
-
# )
|
|
981
|
-
|
|
982
|
-
# # Need exactly two arguments
|
|
983
|
-
# if len(evaluated_args) != 2:
|
|
984
|
-
# return Expr.Error(
|
|
985
|
-
# category="ArgumentError",
|
|
986
|
-
# message="The / operation requires exactly two arguments"
|
|
987
|
-
# )
|
|
988
|
-
|
|
989
|
-
# dividend = evaluated_args[0].value
|
|
990
|
-
# divisor = evaluated_args[1].value
|
|
991
|
-
|
|
992
|
-
# if divisor == 0:
|
|
993
|
-
# return Expr.Error(
|
|
994
|
-
# category="DivisionError",
|
|
995
|
-
# message="Division by zero"
|
|
996
|
-
# )
|
|
997
|
-
|
|
998
|
-
# return Expr.Integer(dividend // divisor) # Integer division
|
|
999
|
-
|
|
1000
|
-
# # Remainder (modulo)
|
|
1001
|
-
# elif first.value == "%":
|
|
1002
|
-
# evaluated_args = [self.evaluate_expression(arg, env) for arg in expr.elements[1:]]
|
|
964
|
+
elif first.value == "/":
|
|
965
|
+
args = expr.elements[1:]
|
|
966
|
+
if len(args) < 2:
|
|
967
|
+
return Expr.Error("'/' expects at least 2 arguments", origin=expr)
|
|
968
|
+
vals = [self.machine_expr_eval(env_id=env_id, expr=a) for a in args]
|
|
969
|
+
for v in vals:
|
|
970
|
+
if isinstance(v, Expr.Error): return v
|
|
971
|
+
if not isinstance(v, Expr.Integer):
|
|
972
|
+
return Expr.Error("'/' only accepts integer operands", origin=v)
|
|
973
|
+
result = vals[0].value
|
|
974
|
+
for v in vals[1:]:
|
|
975
|
+
if v.value == 0:
|
|
976
|
+
return Expr.Error("division by zero", origin=v)
|
|
977
|
+
if result % v.value:
|
|
978
|
+
return Expr.Error("non-exact division", origin=expr)
|
|
979
|
+
result //= v.value
|
|
980
|
+
return Expr.Integer(result)
|
|
1003
981
|
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
982
|
+
elif first.value == "%":
|
|
983
|
+
if len(expr.elements) != 3:
|
|
984
|
+
return Expr.Error("'%' expects exactly 2 arguments", origin=expr)
|
|
985
|
+
a = self.machine_expr_eval(env_id=env_id, expr=expr.elements[1])
|
|
986
|
+
b = self.machine_expr_eval(env_id=env_id, expr=expr.elements[2])
|
|
987
|
+
for v in (a, b):
|
|
988
|
+
if isinstance(v, Expr.Error): return v
|
|
989
|
+
if not isinstance(v, Expr.Integer):
|
|
990
|
+
return Expr.Error("'%' only accepts integer operands", origin=v)
|
|
991
|
+
if b.value == 0:
|
|
992
|
+
return Expr.Error("division by zero", origin=expr.elements[2])
|
|
993
|
+
return Expr.Integer(a.value % b.value)
|
|
994
|
+
|
|
995
|
+
elif first.value in ("=", "!=", ">", "<", ">=", "<="):
|
|
996
|
+
args = expr.elements[1:]
|
|
997
|
+
if len(args) != 2:
|
|
998
|
+
return Expr.Error(f"'{first.value}' expects exactly 2 arguments", origin=expr)
|
|
999
|
+
|
|
1000
|
+
left = self.machine_expr_eval(env_id=env_id, expr=args[0])
|
|
1001
|
+
right = self.machine_expr_eval(env_id=env_id, expr=args[1])
|
|
1002
|
+
|
|
1003
|
+
for v in (left, right):
|
|
1004
|
+
if isinstance(v, Expr.Error):
|
|
1005
|
+
return v
|
|
1006
|
+
if not isinstance(v, Expr.Integer):
|
|
1007
|
+
return Expr.Error(f"'{first.value}' only accepts integer operands", origin=v)
|
|
1008
|
+
|
|
1009
|
+
a, b = left.value, right.value
|
|
1010
|
+
match first.value:
|
|
1011
|
+
case "=": res = a == b
|
|
1012
|
+
case "!=": res = a != b
|
|
1013
|
+
case ">": res = a > b
|
|
1014
|
+
case "<": res = a < b
|
|
1015
|
+
case ">=": res = a >= b
|
|
1016
|
+
case "<=": res = a <= b
|
|
1017
|
+
|
|
1018
|
+
return Expr.Boolean(res)
|
|
1028
1019
|
|
|
1029
1020
|
else:
|
|
1030
1021
|
evaluated_elements = [self.machine_expr_eval(env_id=env_id, expr=e) for e in expr.elements]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: Python library to interact with the Astreum blockchain and its Lispeum virtual machine.
|
|
5
5
|
Author-email: "Roy R. O. Okello" <roy@stelar.xyz>
|
|
6
6
|
Project-URL: Homepage, https://github.com/astreum/lib
|
|
@@ -15,6 +15,8 @@ src/astreum/_node/storage/merkle.py
|
|
|
15
15
|
src/astreum/_node/storage/patricia.py
|
|
16
16
|
src/astreum/crypto/__init__.py
|
|
17
17
|
src/astreum/crypto/ed25519.py
|
|
18
|
+
src/astreum/crypto/quadratic_form.py
|
|
19
|
+
src/astreum/crypto/wesolowski.py
|
|
18
20
|
src/astreum/crypto/x25519.py
|
|
19
21
|
src/astreum/lispeum/__init__.py
|
|
20
22
|
src/astreum/lispeum/parser.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|