astreum 0.2.17__tar.gz → 0.2.19__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.17/src/astreum.egg-info → astreum-0.2.19}/PKG-INFO +1 -1
- {astreum-0.2.17 → astreum-0.2.19}/pyproject.toml +1 -1
- astreum-0.2.19/src/astreum/models/account.py +91 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum/models/merkle.py +21 -50
- astreum-0.2.19/src/astreum/models/transaction.py +106 -0
- {astreum-0.2.17 → astreum-0.2.19/src/astreum.egg-info}/PKG-INFO +1 -1
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum.egg-info/SOURCES.txt +1 -0
- astreum-0.2.17/src/astreum/models/transaction.py +0 -78
- {astreum-0.2.17 → astreum-0.2.19}/LICENSE +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/README.md +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/setup.cfg +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum/__init__.py +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum/crypto/__init__.py +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum/crypto/ed25519.py +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum/crypto/quadratic_form.py +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum/crypto/wesolowski.py +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum/crypto/x25519.py +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum/format.py +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum/lispeum/__init__.py +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum/lispeum/parser.py +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum/lispeum/tokenizer.py +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum/models/__init__.py +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum/models/block.py +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum/models/patricia.py +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum/node.py +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum.egg-info/dependency_links.txt +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum.egg-info/requires.txt +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/src/astreum.egg-info/top_level.txt +0 -0
- {astreum-0.2.17 → astreum-0.2.19}/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.19
|
|
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,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Callable
|
|
4
|
+
|
|
5
|
+
from .merkle import MerkleTree
|
|
6
|
+
|
|
7
|
+
_FIELD_ORDER = ["balance", "data", "nonce"]
|
|
8
|
+
_INT_FIELDS = {"balance", "nonce"}
|
|
9
|
+
|
|
10
|
+
def _int_to_min_bytes(i: int) -> bytes:
|
|
11
|
+
length = (i.bit_length() + 7) // 8 or 1
|
|
12
|
+
return i.to_bytes(length, "big")
|
|
13
|
+
|
|
14
|
+
class Account:
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
body_hash: bytes,
|
|
18
|
+
*,
|
|
19
|
+
body_tree: Optional[MerkleTree] = None,
|
|
20
|
+
get_node_fn: Optional[Callable[[bytes], Optional[bytes]]] = None,
|
|
21
|
+
) -> None:
|
|
22
|
+
self._body_hash = body_hash
|
|
23
|
+
self._body_tree = body_tree
|
|
24
|
+
self._balance: Optional[int] = None
|
|
25
|
+
self._data: Optional[bytes] = None
|
|
26
|
+
self._nonce: Optional[int] = None
|
|
27
|
+
|
|
28
|
+
if self._body_tree and get_node_fn:
|
|
29
|
+
self._body_tree._node_get = get_node_fn
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def create(
|
|
33
|
+
cls,
|
|
34
|
+
balance: int,
|
|
35
|
+
data: bytes,
|
|
36
|
+
nonce: int,
|
|
37
|
+
) -> Account:
|
|
38
|
+
"""Build an Account body from explicit fields in alphabetical order."""
|
|
39
|
+
# prepare values dict
|
|
40
|
+
values = {"balance": balance, "data": data, "nonce": nonce}
|
|
41
|
+
|
|
42
|
+
# build leaves in alphabetical order
|
|
43
|
+
leaves: list[bytes] = []
|
|
44
|
+
for name in _FIELD_ORDER:
|
|
45
|
+
v = values[name]
|
|
46
|
+
if name in _INT_FIELDS:
|
|
47
|
+
leaves.append(_int_to_min_bytes(v)) # type: ignore[arg-type]
|
|
48
|
+
else:
|
|
49
|
+
leaves.append(v)
|
|
50
|
+
|
|
51
|
+
tree = MerkleTree.from_leaves(leaves)
|
|
52
|
+
return cls(tree.root_hash, body_tree=tree)
|
|
53
|
+
|
|
54
|
+
def body_hash(self) -> bytes:
|
|
55
|
+
"""Return the Merkle root of the account body."""
|
|
56
|
+
return self._body_hash
|
|
57
|
+
|
|
58
|
+
def _require_tree(self) -> MerkleTree:
|
|
59
|
+
if not self._body_tree:
|
|
60
|
+
raise ValueError("Body tree unavailable for this Account")
|
|
61
|
+
return self._body_tree
|
|
62
|
+
|
|
63
|
+
def balance(self) -> int:
|
|
64
|
+
"""Fetch & cache the `balance` field (leaf 0)."""
|
|
65
|
+
if self._balance is not None:
|
|
66
|
+
return self._balance
|
|
67
|
+
raw = self._require_tree().get(0)
|
|
68
|
+
if raw is None:
|
|
69
|
+
raise ValueError("Merkle leaf 0 (balance) missing")
|
|
70
|
+
self._balance = int.from_bytes(raw, "big")
|
|
71
|
+
return self._balance
|
|
72
|
+
|
|
73
|
+
def data(self) -> bytes:
|
|
74
|
+
"""Fetch & cache the `data` field (leaf 1)."""
|
|
75
|
+
if self._data is not None:
|
|
76
|
+
return self._data
|
|
77
|
+
raw = self._require_tree().get(1)
|
|
78
|
+
if raw is None:
|
|
79
|
+
raise ValueError("Merkle leaf 1 (data) missing")
|
|
80
|
+
self._data = raw
|
|
81
|
+
return self._data
|
|
82
|
+
|
|
83
|
+
def nonce(self) -> int:
|
|
84
|
+
"""Fetch & cache the `nonce` field (leaf 2)."""
|
|
85
|
+
if self._nonce is not None:
|
|
86
|
+
return self._nonce
|
|
87
|
+
raw = self._require_tree().get(2)
|
|
88
|
+
if raw is None:
|
|
89
|
+
raise ValueError("Merkle leaf 2 (nonce) missing")
|
|
90
|
+
self._nonce = int.from_bytes(raw, "big")
|
|
91
|
+
return self._nonce
|
|
@@ -2,18 +2,10 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import Callable, Dict, List, Optional, Tuple
|
|
4
4
|
|
|
5
|
-
import blake3
|
|
5
|
+
import blake3
|
|
6
6
|
from ..format import encode, decode
|
|
7
7
|
|
|
8
8
|
class MerkleNode:
|
|
9
|
-
"""A node in a binary Merkle tree.
|
|
10
|
-
|
|
11
|
-
*Leaf* : ``value`` is **not** ``None``.
|
|
12
|
-
*Interior*: ``value`` is ``None``.
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
__slots__ = ("left", "right", "value", "_hash")
|
|
16
|
-
|
|
17
9
|
def __init__(
|
|
18
10
|
self,
|
|
19
11
|
left: Optional[bytes],
|
|
@@ -25,9 +17,6 @@ class MerkleNode:
|
|
|
25
17
|
self.value = value
|
|
26
18
|
self._hash: Optional[bytes] = None
|
|
27
19
|
|
|
28
|
-
# ------------------------------------------------------------------
|
|
29
|
-
# serialisation helpers
|
|
30
|
-
# ------------------------------------------------------------------
|
|
31
20
|
def to_bytes(self) -> bytes:
|
|
32
21
|
return encode([self.left, self.right, self.value])
|
|
33
22
|
|
|
@@ -36,11 +25,8 @@ class MerkleNode:
|
|
|
36
25
|
left, right, value = decode(blob)
|
|
37
26
|
return cls(left, right, value)
|
|
38
27
|
|
|
39
|
-
# ------------------------------------------------------------------
|
|
40
|
-
# content hash (blake3)
|
|
41
|
-
# ------------------------------------------------------------------
|
|
42
28
|
def _compute_hash(self) -> bytes:
|
|
43
|
-
if self.value is not None:
|
|
29
|
+
if self.value is not None:
|
|
44
30
|
return blake3.blake3(self.value).digest()
|
|
45
31
|
left = self.left or b""
|
|
46
32
|
right = self.right or b""
|
|
@@ -52,21 +38,7 @@ class MerkleNode:
|
|
|
52
38
|
return self._hash
|
|
53
39
|
|
|
54
40
|
|
|
55
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
56
|
-
# Merkle tree – fixed‑height, no dynamic growth
|
|
57
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
58
|
-
|
|
59
|
-
|
|
60
41
|
class MerkleTree:
|
|
61
|
-
"""Binary Merkle tree addressed by *leaf position* (0‑based).
|
|
62
|
-
|
|
63
|
-
* The number of levels is fixed once the tree is built (``_height``).
|
|
64
|
-
* ``put`` can **only update** existing leaves; it never adds capacity.
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
# ------------------------------------------------------------------
|
|
68
|
-
# construction helpers
|
|
69
|
-
# ------------------------------------------------------------------
|
|
70
42
|
def __init__(
|
|
71
43
|
self,
|
|
72
44
|
node_get: Callable[[bytes], Optional[bytes]],
|
|
@@ -84,11 +56,6 @@ class MerkleTree:
|
|
|
84
56
|
leaves: List[bytes],
|
|
85
57
|
node_get: Callable[[bytes], Optional[bytes]] | None = None,
|
|
86
58
|
) -> "MerkleTree":
|
|
87
|
-
"""Build a complete tree from *leaves* (left‑to‑right order).
|
|
88
|
-
|
|
89
|
-
Missing right siblings in the top levels are allowed; they are encoded
|
|
90
|
-
as interior nodes with a single child.
|
|
91
|
-
"""
|
|
92
59
|
if not leaves:
|
|
93
60
|
raise ValueError("must supply at least one leaf")
|
|
94
61
|
|
|
@@ -125,9 +92,6 @@ class MerkleTree:
|
|
|
125
92
|
tree._height = height
|
|
126
93
|
return tree
|
|
127
94
|
|
|
128
|
-
# ------------------------------------------------------------------
|
|
129
|
-
# internal helpers
|
|
130
|
-
# ------------------------------------------------------------------
|
|
131
95
|
def _fetch(self, h: bytes | None) -> Optional[MerkleNode]:
|
|
132
96
|
if h is None:
|
|
133
97
|
return None
|
|
@@ -141,11 +105,10 @@ class MerkleTree:
|
|
|
141
105
|
return node
|
|
142
106
|
|
|
143
107
|
def _invalidate(self, node: MerkleNode) -> None:
|
|
144
|
-
node._hash = None
|
|
108
|
+
node._hash = None
|
|
145
109
|
|
|
146
110
|
def _ensure_height(self) -> None:
|
|
147
111
|
if self._height is None:
|
|
148
|
-
# Recompute by traversing leftmost branch
|
|
149
112
|
h = 0
|
|
150
113
|
nh = self.root_hash
|
|
151
114
|
while nh is not None:
|
|
@@ -186,11 +149,7 @@ class MerkleTree:
|
|
|
186
149
|
return leaf.value if leaf else None
|
|
187
150
|
|
|
188
151
|
def put(self, index: int, value: bytes) -> None:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
Raises ``IndexError`` if *index* is outside current capacity **or** if
|
|
192
|
-
the path to that leaf is missing in the stored structure.
|
|
193
|
-
"""
|
|
152
|
+
# 1 . input validation
|
|
194
153
|
if index < 0:
|
|
195
154
|
raise IndexError("negative index")
|
|
196
155
|
if self.root_hash is None:
|
|
@@ -198,6 +157,7 @@ class MerkleTree:
|
|
|
198
157
|
if index >= self._capacity():
|
|
199
158
|
raise IndexError("index beyond tree capacity")
|
|
200
159
|
|
|
160
|
+
# 2 . walk down to the target leaf
|
|
201
161
|
node_hash = self.root_hash
|
|
202
162
|
stack: List[Tuple[MerkleNode, bytes, bool]] = []
|
|
203
163
|
for bit in self._path_bits(index):
|
|
@@ -207,28 +167,39 @@ class MerkleTree:
|
|
|
207
167
|
went_right = bool(bit)
|
|
208
168
|
child_hash = node.right if went_right else node.left
|
|
209
169
|
if child_hash is None:
|
|
210
|
-
raise IndexError("path leads into non
|
|
170
|
+
raise IndexError("path leads into non-existent branch")
|
|
211
171
|
stack.append((node, node.hash(), went_right))
|
|
212
172
|
node_hash = child_hash
|
|
213
173
|
|
|
174
|
+
# 3 . update the leaf
|
|
214
175
|
leaf = self._fetch(node_hash)
|
|
215
176
|
if leaf is None or leaf.value is None:
|
|
216
177
|
raise IndexError("target leaf missing")
|
|
178
|
+
|
|
179
|
+
old_hash = leaf.hash()
|
|
217
180
|
leaf.value = value
|
|
218
181
|
self._invalidate(leaf)
|
|
219
182
|
new_hash = leaf.hash()
|
|
183
|
+
|
|
184
|
+
if new_hash != old_hash:
|
|
185
|
+
self.nodes.pop(old_hash, None)
|
|
220
186
|
self.nodes[new_hash] = leaf
|
|
221
|
-
|
|
222
|
-
# bubble
|
|
187
|
+
|
|
188
|
+
# 4 . bubble the change up
|
|
223
189
|
for parent, old_hash, went_right in reversed(stack):
|
|
224
190
|
if went_right:
|
|
225
191
|
parent.right = new_hash
|
|
226
192
|
else:
|
|
227
193
|
parent.left = new_hash
|
|
194
|
+
|
|
228
195
|
self._invalidate(parent)
|
|
229
196
|
new_hash = parent.hash()
|
|
197
|
+
|
|
230
198
|
if new_hash != old_hash:
|
|
231
|
-
|
|
232
|
-
|
|
199
|
+
self.nodes.pop(old_hash, None)
|
|
200
|
+
self.nodes[new_hash] = parent
|
|
201
|
+
|
|
202
|
+
# 5 . finalise the new root
|
|
233
203
|
self.root_hash = new_hash
|
|
234
204
|
|
|
205
|
+
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Optional, Union, Any, Callable
|
|
4
|
+
|
|
5
|
+
from .merkle import MerkleTree
|
|
6
|
+
from ..crypto import ed25519
|
|
7
|
+
|
|
8
|
+
_FIELD_ORDER: List[str] = [
|
|
9
|
+
"amount",
|
|
10
|
+
"balance",
|
|
11
|
+
"fee",
|
|
12
|
+
"nonce",
|
|
13
|
+
"recipient_pk",
|
|
14
|
+
"sender_pk",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
_INT_FIELDS = {"amount", "balance", "fee", "nonce"}
|
|
18
|
+
|
|
19
|
+
def _int_to_min_bytes(i: int) -> bytes:
|
|
20
|
+
length = (i.bit_length() + 7) // 8 or 1
|
|
21
|
+
return i.to_bytes(length, "big")
|
|
22
|
+
|
|
23
|
+
class Transaction:
|
|
24
|
+
# init
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
tx_hash: bytes,
|
|
28
|
+
*,
|
|
29
|
+
tree: Optional[MerkleTree] = None,
|
|
30
|
+
get_node_fn: Optional[Callable[[bytes], Optional[bytes]]] = None,
|
|
31
|
+
) -> None:
|
|
32
|
+
self._hash = tx_hash
|
|
33
|
+
self._tree = tree
|
|
34
|
+
self._field_cache: Dict[str, Union[int, bytes]] = {}
|
|
35
|
+
|
|
36
|
+
if self._tree and get_node_fn:
|
|
37
|
+
self._tree.set_external_node_fetcher(get_node_fn)
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def create(
|
|
41
|
+
cls,
|
|
42
|
+
*,
|
|
43
|
+
amount: int,
|
|
44
|
+
balance: int,
|
|
45
|
+
fee: int,
|
|
46
|
+
nonce: int,
|
|
47
|
+
recipient_pk: bytes,
|
|
48
|
+
sender_pk: bytes,
|
|
49
|
+
) -> "Transaction":
|
|
50
|
+
vals: Dict[str, Any] = locals().copy()
|
|
51
|
+
leaves = [
|
|
52
|
+
vals[name] if isinstance(vals[name], bytes) else _int_to_min_bytes(vals[name])
|
|
53
|
+
for name in _FIELD_ORDER
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
tree = MerkleTree.from_leaves(leaves)
|
|
57
|
+
return cls(tx_hash=tree.root_hash, tree=tree)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def hash(self) -> bytes:
|
|
61
|
+
return self._hash
|
|
62
|
+
|
|
63
|
+
def _require_tree(self) -> MerkleTree:
|
|
64
|
+
if not self._tree:
|
|
65
|
+
raise ValueError("Merkle tree unavailable for this Transaction")
|
|
66
|
+
return self._tree
|
|
67
|
+
|
|
68
|
+
def _field(self, idx: int, name: str) -> Union[int, bytes]:
|
|
69
|
+
if name in self._field_cache:
|
|
70
|
+
return self._field_cache[name]
|
|
71
|
+
|
|
72
|
+
raw = self._require_tree().get(idx)
|
|
73
|
+
if raw is None:
|
|
74
|
+
raise ValueError(f"Leaf {idx} (‘{name}’) missing from Merkle tree")
|
|
75
|
+
|
|
76
|
+
value = int.from_bytes(raw, "big") if name in _INT_FIELDS else raw
|
|
77
|
+
self._field_cache[name] = value
|
|
78
|
+
return value
|
|
79
|
+
|
|
80
|
+
def get_amount(self) -> int:
|
|
81
|
+
return self._field(0, "amount")
|
|
82
|
+
|
|
83
|
+
def get_balance(self) -> int:
|
|
84
|
+
return self._field(1, "balance")
|
|
85
|
+
|
|
86
|
+
def get_fee(self) -> int:
|
|
87
|
+
return self._field(2, "fee")
|
|
88
|
+
|
|
89
|
+
def get_nonce(self) -> int:
|
|
90
|
+
return self._field(3, "nonce")
|
|
91
|
+
|
|
92
|
+
def get_recipient_pk(self) -> bytes:
|
|
93
|
+
return self._field(4, "recipient_pk")
|
|
94
|
+
|
|
95
|
+
def get_sender_pk(self) -> bytes:
|
|
96
|
+
return self._field(5, "sender_pk")
|
|
97
|
+
|
|
98
|
+
def sign(self, priv: ed25519.Ed25519PrivateKey) -> bytes:
|
|
99
|
+
return priv.sign(self.hash)
|
|
100
|
+
|
|
101
|
+
def verify_signature(self, sig: bytes, sender_pk: bytes) -> bool:
|
|
102
|
+
try:
|
|
103
|
+
ed25519.Ed25519PublicKey.from_public_bytes(sender_pk).verify(sig, self.hash)
|
|
104
|
+
return True
|
|
105
|
+
except Exception:
|
|
106
|
+
return False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: astreum
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.19
|
|
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
|
|
@@ -18,6 +18,7 @@ src/astreum/lispeum/__init__.py
|
|
|
18
18
|
src/astreum/lispeum/parser.py
|
|
19
19
|
src/astreum/lispeum/tokenizer.py
|
|
20
20
|
src/astreum/models/__init__.py
|
|
21
|
+
src/astreum/models/account.py
|
|
21
22
|
src/astreum/models/block.py
|
|
22
23
|
src/astreum/models/merkle.py
|
|
23
24
|
src/astreum/models/patricia.py
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
from ..format import encode, decode
|
|
2
|
-
from ..crypto import ed25519
|
|
3
|
-
import blake3
|
|
4
|
-
|
|
5
|
-
class Transaction:
|
|
6
|
-
def __init__(
|
|
7
|
-
self,
|
|
8
|
-
sender_pk: bytes,
|
|
9
|
-
recipient_pk: bytes,
|
|
10
|
-
amount: int,
|
|
11
|
-
fee: int,
|
|
12
|
-
nonce: int,
|
|
13
|
-
signature: bytes | None = None,
|
|
14
|
-
) -> None:
|
|
15
|
-
self.sender_pk = sender_pk
|
|
16
|
-
self.recipient_pk = recipient_pk
|
|
17
|
-
self.amount = amount
|
|
18
|
-
self.fee = fee
|
|
19
|
-
self.nonce = nonce
|
|
20
|
-
self.signature = signature
|
|
21
|
-
|
|
22
|
-
if self.amount < 0 or self.fee < 0:
|
|
23
|
-
raise ValueError("amount and fee must be non-negative")
|
|
24
|
-
|
|
25
|
-
if self.fee % 2 != 0:
|
|
26
|
-
raise ValueError("fee must be divisible by two")
|
|
27
|
-
|
|
28
|
-
self.tx_body_hash: bytes = blake3.blake3(self._body_bytes()).digest()
|
|
29
|
-
|
|
30
|
-
if self.signature is not None:
|
|
31
|
-
self.tx_hash = blake3.blake3(self.tx_body_hash + self.signature).digest()
|
|
32
|
-
else:
|
|
33
|
-
self.tx_hash = None
|
|
34
|
-
|
|
35
|
-
def sign(self, priv_key: ed25519.Ed25519PrivateKey) -> None:
|
|
36
|
-
if self.signature is not None:
|
|
37
|
-
raise ValueError("transaction already signed")
|
|
38
|
-
sig = priv_key.sign(self.tx_body_hash)
|
|
39
|
-
self.signature = sig
|
|
40
|
-
self.tx_hash = blake3.blake3(self.tx_body_hash + sig).digest()
|
|
41
|
-
|
|
42
|
-
def verify_signature(self) -> bool:
|
|
43
|
-
if self.signature is None:
|
|
44
|
-
return False
|
|
45
|
-
try:
|
|
46
|
-
pub = ed25519.Ed25519PublicKey.from_public_bytes(self.sender_pk)
|
|
47
|
-
pub.verify(self.signature, self.tx_body_hash)
|
|
48
|
-
return True
|
|
49
|
-
except Exception:
|
|
50
|
-
return False
|
|
51
|
-
|
|
52
|
-
def to_bytes(self) -> bytes:
|
|
53
|
-
sig = self.signature or b""
|
|
54
|
-
return encode([
|
|
55
|
-
self.sender_pk,
|
|
56
|
-
self.recipient_pk,
|
|
57
|
-
self.amount,
|
|
58
|
-
self.fee,
|
|
59
|
-
self.nonce,
|
|
60
|
-
sig,
|
|
61
|
-
])
|
|
62
|
-
|
|
63
|
-
@classmethod
|
|
64
|
-
def from_bytes(cls, blob: bytes) -> 'Transaction':
|
|
65
|
-
sender, recipient, amount, fee, nonce, sig = decode(blob)
|
|
66
|
-
return cls(sender, recipient, int(amount), int(fee), int(nonce), sig)
|
|
67
|
-
|
|
68
|
-
def _body_bytes(self) -> bytes:
|
|
69
|
-
return encode([
|
|
70
|
-
self.sender_pk,
|
|
71
|
-
self.recipient_pk,
|
|
72
|
-
self.amount,
|
|
73
|
-
self.fee,
|
|
74
|
-
self.nonce,
|
|
75
|
-
])
|
|
76
|
-
|
|
77
|
-
def __hash__(self) -> int:
|
|
78
|
-
return int.from_bytes(self.tx_hash, 'big')
|
|
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
|
|
File without changes
|
|
File without changes
|