astreum 0.2.16__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.

Files changed (30) hide show
  1. {astreum-0.2.16/src/astreum.egg-info → astreum-0.2.18}/PKG-INFO +1 -1
  2. {astreum-0.2.16 → astreum-0.2.18}/pyproject.toml +1 -1
  3. astreum-0.2.18/src/astreum/models/account.py +91 -0
  4. astreum-0.2.18/src/astreum/models/block.py +130 -0
  5. astreum-0.2.18/src/astreum/models/transaction.py +106 -0
  6. {astreum-0.2.16 → astreum-0.2.18/src/astreum.egg-info}/PKG-INFO +1 -1
  7. {astreum-0.2.16 → astreum-0.2.18}/src/astreum.egg-info/SOURCES.txt +1 -0
  8. astreum-0.2.16/src/astreum/models/block.py +0 -98
  9. astreum-0.2.16/src/astreum/models/transaction.py +0 -83
  10. {astreum-0.2.16 → astreum-0.2.18}/LICENSE +0 -0
  11. {astreum-0.2.16 → astreum-0.2.18}/README.md +0 -0
  12. {astreum-0.2.16 → astreum-0.2.18}/setup.cfg +0 -0
  13. {astreum-0.2.16 → astreum-0.2.18}/src/astreum/__init__.py +0 -0
  14. {astreum-0.2.16 → astreum-0.2.18}/src/astreum/crypto/__init__.py +0 -0
  15. {astreum-0.2.16 → astreum-0.2.18}/src/astreum/crypto/ed25519.py +0 -0
  16. {astreum-0.2.16 → astreum-0.2.18}/src/astreum/crypto/quadratic_form.py +0 -0
  17. {astreum-0.2.16 → astreum-0.2.18}/src/astreum/crypto/wesolowski.py +0 -0
  18. {astreum-0.2.16 → astreum-0.2.18}/src/astreum/crypto/x25519.py +0 -0
  19. {astreum-0.2.16 → astreum-0.2.18}/src/astreum/format.py +0 -0
  20. {astreum-0.2.16 → astreum-0.2.18}/src/astreum/lispeum/__init__.py +0 -0
  21. {astreum-0.2.16 → astreum-0.2.18}/src/astreum/lispeum/parser.py +0 -0
  22. {astreum-0.2.16 → astreum-0.2.18}/src/astreum/lispeum/tokenizer.py +0 -0
  23. {astreum-0.2.16 → astreum-0.2.18}/src/astreum/models/__init__.py +0 -0
  24. {astreum-0.2.16 → astreum-0.2.18}/src/astreum/models/merkle.py +0 -0
  25. {astreum-0.2.16 → astreum-0.2.18}/src/astreum/models/patricia.py +0 -0
  26. {astreum-0.2.16 → astreum-0.2.18}/src/astreum/node.py +0 -0
  27. {astreum-0.2.16 → astreum-0.2.18}/src/astreum.egg-info/dependency_links.txt +0 -0
  28. {astreum-0.2.16 → astreum-0.2.18}/src/astreum.egg-info/requires.txt +0 -0
  29. {astreum-0.2.16 → astreum-0.2.18}/src/astreum.egg-info/top_level.txt +0 -0
  30. {astreum-0.2.16 → 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.16
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "astreum"
3
- version = "0.2.16"
3
+ version = "0.2.18"
4
4
  authors = [
5
5
  { name="Roy R. O. Okello", email="roy@stelar.xyz" },
6
6
  ]
@@ -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,130 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import List, Dict, Any, Optional, Union
4
+ from ..crypto import ed25519
5
+ from .merkle import MerkleTree
6
+
7
+ # Constants for integer field names
8
+ _INT_FIELDS = {
9
+ "delay_difficulty",
10
+ "number",
11
+ "timestamp",
12
+ "transaction_limit",
13
+ "transactions_total_fees",
14
+ }
15
+
16
+ class Block:
17
+ def __init__(
18
+ self,
19
+ block_hash: bytes,
20
+ body_tree: Optional[MerkleTree] = None,
21
+ signature: Optional[bytes] = None,
22
+ ) -> None:
23
+ self._block_hash = block_hash
24
+ self._body_tree = body_tree
25
+ self._signature = signature
26
+ # store field names in alphabetical order for consistent indexing
27
+ self._field_names = [
28
+ "accounts_hash",
29
+ "delay_difficulty",
30
+ "delay_output",
31
+ "delay_proof",
32
+ "number",
33
+ "prev_block_hash",
34
+ "timestamp",
35
+ "transaction_limit",
36
+ "transactions_root_hash",
37
+ "transactions_total_fees",
38
+ "validator_pk",
39
+ ]
40
+
41
+ @property
42
+ def hash(self) -> bytes:
43
+ """Return the block hash (Merkle root of body_root || signature)."""
44
+ return self._block_hash
45
+
46
+ @classmethod
47
+ def create(
48
+ cls,
49
+ number: int,
50
+ prev_block_hash: bytes,
51
+ timestamp: int,
52
+ accounts_hash: bytes,
53
+ transactions_total_fees: int,
54
+ transaction_limit: int,
55
+ transactions_root_hash: bytes,
56
+ delay_difficulty: int,
57
+ delay_output: bytes,
58
+ delay_proof: bytes,
59
+ validator_pk: bytes,
60
+ signature: bytes,
61
+ ) -> Block:
62
+ """Build a new block by hashing the provided fields into Merkle trees."""
63
+ # map fields by name
64
+ field_map: Dict[str, Any] = {
65
+ "accounts_hash": accounts_hash,
66
+ "delay_difficulty": delay_difficulty,
67
+ "delay_output": delay_output,
68
+ "delay_proof": delay_proof,
69
+ "number": number,
70
+ "prev_block_hash": prev_block_hash,
71
+ "timestamp": timestamp,
72
+ "transaction_limit": transaction_limit,
73
+ "transactions_root_hash": transactions_root_hash,
74
+ "transactions_total_fees": transactions_total_fees,
75
+ "validator_pk": validator_pk,
76
+ }
77
+
78
+ leaves: List[bytes] = []
79
+ for name in sorted(field_map):
80
+ v = field_map[name]
81
+ if isinstance(v, bytes):
82
+ leaf_bytes = v
83
+ elif isinstance(v, int):
84
+ length = (v.bit_length() + 7) // 8 or 1
85
+ leaf_bytes = v.to_bytes(length, "big")
86
+ else:
87
+ raise TypeError(f"Unsupported field type for '{name}': {type(v)}")
88
+ leaves.append(leaf_bytes)
89
+
90
+ body_tree = MerkleTree.from_leaves(leaves)
91
+ body_root = body_tree.root_hash
92
+ top_tree = MerkleTree.from_leaves([body_root, signature])
93
+ block_hash = top_tree.root_hash
94
+
95
+ return cls(block_hash, body_tree, signature)
96
+
97
+ def get_body_hash(self) -> bytes:
98
+ """Return the Merkle root of the body fields."""
99
+ if not self._body_tree:
100
+ raise ValueError("Body tree not available for this block instance.")
101
+ return self._body_tree.root_hash
102
+
103
+ def get_signature(self) -> bytes:
104
+ """Return the block's signature leaf."""
105
+ if self._signature is None:
106
+ raise ValueError("Signature not available for this block instance.")
107
+ return self._signature
108
+
109
+ def get_field(self, name: str) -> Union[int, bytes]:
110
+ """Query a single body field by name, returning an int or bytes."""
111
+ if name not in self._field_names:
112
+ raise KeyError(f"Unknown field: {name}")
113
+ if not self._body_tree:
114
+ raise ValueError("Body tree not available for field queries.")
115
+ idx = self._field_names.index(name)
116
+ leaf_bytes = self._body_tree.leaves[idx]
117
+ if name in _INT_FIELDS:
118
+ return int.from_bytes(leaf_bytes, "big")
119
+ return leaf_bytes
120
+
121
+ def verify_block_signature(self) -> bool:
122
+ """Verify the block's Ed25519 signature against its body root."""
123
+ pub = ed25519.Ed25519PublicKey.from_public_bytes(
124
+ self.get_field("validator_pk")
125
+ )
126
+ try:
127
+ pub.verify(self.get_signature(), self.get_body_hash())
128
+ return True
129
+ except Exception:
130
+ return False
@@ -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.16
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,98 +0,0 @@
1
- from ..format import encode, decode
2
- from ..crypto import ed25519
3
- import blake3
4
-
5
- class Block:
6
- def __init__(
7
- self,
8
- number: int,
9
- prev_block_hash: bytes,
10
- timestamp: int,
11
- accounts_hash: bytes,
12
- transactions_total_fees: int,
13
- transaction_limit: int,
14
- transactions_root_hash: bytes,
15
- vdf_difficulty: int,
16
- vdf_output: bytes,
17
- vdf_proof: bytes,
18
- validator_pk: bytes,
19
- signature: bytes,
20
- ) -> None:
21
- self.accounts_hash = accounts_hash
22
- self.number = int(number)
23
- self.prev_block_hash = prev_block_hash
24
- self.timestamp = int(timestamp)
25
- self.transactions_total_fees = int(transactions_total_fees)
26
- self.transaction_limit = int(transaction_limit)
27
- self.transactions_root_hash = transactions_root_hash
28
- self.validator_pk = validator_pk
29
- self.vdf_difficulty = int(vdf_difficulty)
30
- self.vdf_output = vdf_output
31
- self.vdf_proof = vdf_proof
32
- self.signature = signature
33
- self.body_hash = self._compute_body_hash()
34
-
35
- def _body_fields_without_sig(self) -> list:
36
- return [
37
- self.accounts_hash,
38
- self.number,
39
- self.prev_block_hash,
40
- self.timestamp,
41
- self.transactions_total_fees,
42
- self.transaction_limit,
43
- self.transactions_root_hash,
44
- self.validator_pk,
45
- self.vdf_difficulty,
46
- self.vdf_output,
47
- self.vdf_proof,
48
- ]
49
-
50
- def _compute_body_hash(self) -> bytes:
51
- return blake3.blake3(encode(self._body_fields_without_sig())).digest()
52
-
53
- def to_bytes(self) -> bytes:
54
- return encode(self._body_fields_without_sig() + [self.signature])
55
-
56
- @classmethod
57
- def from_bytes(cls, blob: bytes) -> "Block":
58
- (
59
- accounts_hash,
60
- number,
61
- prev_block_hash,
62
- timestamp,
63
- transactions_total_fees,
64
- transaction_limit,
65
- transactions_root_hash,
66
- validator_pk,
67
- vdf_difficulty,
68
- vdf_output,
69
- vdf_proof,
70
- signature
71
- ) = decode(blob)
72
- return cls(
73
- number=int(number),
74
- prev_block_hash=prev_block_hash,
75
- timestamp=int(timestamp),
76
- accounts_hash=accounts_hash,
77
- transactions_total_fees=int(transactions_total_fees),
78
- transaction_limit=int(transaction_limit),
79
- transactions_root_hash=transactions_root_hash,
80
- vdf_difficulty=int(vdf_difficulty),
81
- vdf_output=vdf_output,
82
- vdf_proof=vdf_proof,
83
- validator_pk=validator_pk,
84
- signature=signature,
85
- )
86
-
87
- @property
88
- def hash(self) -> bytes:
89
- return blake3.blake3(self.body_hash + self.signature).digest()
90
-
91
- def verify_block_signature(self) -> bool:
92
- try:
93
- pub = ed25519.Ed25519PublicKey.from_public_bytes(self.validator_pk)
94
- pub.verify(self.signature, self.body_hash)
95
- return True
96
- except Exception:
97
- return False
98
-
@@ -1,83 +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 __eq__(self, other: "Transaction") -> bool:
78
- if not isinstance(other, Transaction):
79
- return NotImplemented
80
- return self.tx_hash == other.tx_hash
81
-
82
- def __hash__(self) -> int:
83
- return int.from_bytes(self.tx_hash, 'big')
File without changes
File without changes
File without changes
File without changes
File without changes