astreum 0.2.17__tar.gz → 0.2.18__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.18}/PKG-INFO +1 -1
- {astreum-0.2.17 → astreum-0.2.18}/pyproject.toml +1 -1
- astreum-0.2.18/src/astreum/models/account.py +91 -0
- astreum-0.2.18/src/astreum/models/transaction.py +106 -0
- {astreum-0.2.17 → astreum-0.2.18/src/astreum.egg-info}/PKG-INFO +1 -1
- {astreum-0.2.17 → astreum-0.2.18}/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.18}/LICENSE +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/README.md +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/setup.cfg +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum/__init__.py +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum/crypto/__init__.py +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum/crypto/ed25519.py +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum/crypto/quadratic_form.py +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum/crypto/wesolowski.py +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum/crypto/x25519.py +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum/format.py +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum/lispeum/__init__.py +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum/lispeum/parser.py +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum/lispeum/tokenizer.py +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum/models/__init__.py +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum/models/block.py +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum/models/merkle.py +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum/models/patricia.py +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum/node.py +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum.egg-info/dependency_links.txt +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum.egg-info/requires.txt +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/src/astreum.egg-info/top_level.txt +0 -0
- {astreum-0.2.17 → astreum-0.2.18}/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.18
|
|
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
|
|
@@ -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.18
|
|
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
|
|
File without changes
|