blockchainkit 0.2.0__py3-none-any.whl

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.
Files changed (101) hide show
  1. blockchainkit/__init__.py +12 -0
  2. blockchainkit/_validation.py +44 -0
  3. blockchainkit/consensus/__init__.py +66 -0
  4. blockchainkit/consensus/core/__init__.py +25 -0
  5. blockchainkit/consensus/core/base.py +97 -0
  6. blockchainkit/consensus/systems/__init__.py +46 -0
  7. blockchainkit/consensus/systems/byzantine.py +91 -0
  8. blockchainkit/consensus/systems/catch_up.py +60 -0
  9. blockchainkit/consensus/systems/difficulty.py +74 -0
  10. blockchainkit/consensus/systems/finality.py +92 -0
  11. blockchainkit/consensus/systems/fork_choice.py +44 -0
  12. blockchainkit/consensus/systems/pbft.py +78 -0
  13. blockchainkit/consensus/systems/pos.py +53 -0
  14. blockchainkit/consensus/systems/pow.py +58 -0
  15. blockchainkit/consensus/systems/pricing.py +50 -0
  16. blockchainkit/consensus/systems/randomized.py +114 -0
  17. blockchainkit/consensus/systems/selfish.py +81 -0
  18. blockchainkit/consensus/systems/sortition.py +48 -0
  19. blockchainkit/consensus/systems/stake_games.py +44 -0
  20. blockchainkit/consensus/systems/synchrony.py +49 -0
  21. blockchainkit/consensus/visualizers/__init__.py +9 -0
  22. blockchainkit/consensus/visualizers/plots.py +80 -0
  23. blockchainkit/constants.py +66 -0
  24. blockchainkit/crypto/__init__.py +157 -0
  25. blockchainkit/crypto/core/__init__.py +21 -0
  26. blockchainkit/crypto/core/base.py +109 -0
  27. blockchainkit/crypto/systems/__init__.py +137 -0
  28. blockchainkit/crypto/systems/asymmetric.py +136 -0
  29. blockchainkit/crypto/systems/commitments.py +93 -0
  30. blockchainkit/crypto/systems/curves.py +205 -0
  31. blockchainkit/crypto/systems/discrete_log.py +156 -0
  32. blockchainkit/crypto/systems/hashing.py +95 -0
  33. blockchainkit/crypto/systems/lamport.py +65 -0
  34. blockchainkit/crypto/systems/mac.py +34 -0
  35. blockchainkit/crypto/systems/merkle_damgard.py +139 -0
  36. blockchainkit/crypto/systems/multisig.py +98 -0
  37. blockchainkit/crypto/systems/one_time_pad.py +42 -0
  38. blockchainkit/crypto/systems/puzzles.py +96 -0
  39. blockchainkit/crypto/systems/sharing.py +159 -0
  40. blockchainkit/crypto/systems/signatures.py +232 -0
  41. blockchainkit/crypto/utils/__init__.py +5 -0
  42. blockchainkit/crypto/utils/primes.py +39 -0
  43. blockchainkit/crypto/visualizers/__init__.py +9 -0
  44. blockchainkit/crypto/visualizers/plots.py +88 -0
  45. blockchainkit/network/__init__.py +70 -0
  46. blockchainkit/network/core/__init__.py +21 -0
  47. blockchainkit/network/core/base.py +149 -0
  48. blockchainkit/network/systems/__init__.py +54 -0
  49. blockchainkit/network/systems/addresses.py +126 -0
  50. blockchainkit/network/systems/broadcast.py +124 -0
  51. blockchainkit/network/systems/clocks.py +149 -0
  52. blockchainkit/network/systems/epidemics.py +111 -0
  53. blockchainkit/network/systems/gossip.py +201 -0
  54. blockchainkit/network/systems/kademlia.py +147 -0
  55. blockchainkit/network/systems/privacy.py +98 -0
  56. blockchainkit/network/systems/propagation.py +60 -0
  57. blockchainkit/network/systems/relay.py +123 -0
  58. blockchainkit/network/systems/replication.py +122 -0
  59. blockchainkit/network/systems/topology.py +238 -0
  60. blockchainkit/network/visualizers/__init__.py +14 -0
  61. blockchainkit/network/visualizers/plots.py +177 -0
  62. blockchainkit/py.typed +0 -0
  63. blockchainkit/structures/__init__.py +61 -0
  64. blockchainkit/structures/core/__init__.py +21 -0
  65. blockchainkit/structures/core/base.py +100 -0
  66. blockchainkit/structures/systems/__init__.py +45 -0
  67. blockchainkit/structures/systems/block.py +144 -0
  68. blockchainkit/structures/systems/bloom.py +81 -0
  69. blockchainkit/structures/systems/chain.py +131 -0
  70. blockchainkit/structures/systems/hash_chain.py +35 -0
  71. blockchainkit/structures/systems/headers.py +54 -0
  72. blockchainkit/structures/systems/ledger.py +87 -0
  73. blockchainkit/structures/systems/merkle.py +273 -0
  74. blockchainkit/structures/systems/mmr.py +112 -0
  75. blockchainkit/structures/systems/sparse_merkle.py +139 -0
  76. blockchainkit/structures/systems/transaction.py +116 -0
  77. blockchainkit/structures/systems/utxo.py +156 -0
  78. blockchainkit/structures/utils/__init__.py +6 -0
  79. blockchainkit/structures/utils/accounts.py +13 -0
  80. blockchainkit/structures/utils/encoding.py +11 -0
  81. blockchainkit/structures/visualizers/__init__.py +13 -0
  82. blockchainkit/structures/visualizers/plots.py +154 -0
  83. blockchainkit/vm/__init__.py +60 -0
  84. blockchainkit/vm/core/__init__.py +25 -0
  85. blockchainkit/vm/core/base.py +171 -0
  86. blockchainkit/vm/systems/__init__.py +40 -0
  87. blockchainkit/vm/systems/assembler.py +80 -0
  88. blockchainkit/vm/systems/expressions.py +91 -0
  89. blockchainkit/vm/systems/programs.py +143 -0
  90. blockchainkit/vm/systems/reentrancy.py +70 -0
  91. blockchainkit/vm/systems/script.py +268 -0
  92. blockchainkit/vm/systems/stack_machine.py +239 -0
  93. blockchainkit/vm/systems/turing.py +111 -0
  94. blockchainkit/vm/systems/verifier.py +81 -0
  95. blockchainkit/vm/visualizers/__init__.py +9 -0
  96. blockchainkit/vm/visualizers/plots.py +97 -0
  97. blockchainkit-0.2.0.dist-info/METADATA +195 -0
  98. blockchainkit-0.2.0.dist-info/RECORD +101 -0
  99. blockchainkit-0.2.0.dist-info/WHEEL +5 -0
  100. blockchainkit-0.2.0.dist-info/licenses/LICENSE +21 -0
  101. blockchainkit-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,156 @@
1
+ """The unspent-transaction-output (UTXO) model of Bitcoin (2008).
2
+
3
+ There are no accounts. Value lives in *coins*, outputs of earlier
4
+ transactions, each owned by an address. A transaction consumes whole coins
5
+ as inputs and creates new ones as outputs; any value not assigned to an
6
+ output is the miner's fee. A coin can be spent once: double spending is
7
+ the attempt to use one input twice.
8
+ """
9
+
10
+ from collections.abc import Mapping, Sequence
11
+ from dataclasses import dataclass, field, replace
12
+
13
+ from blockchainkit._validation import integer
14
+ from blockchainkit.constants import UINT64_LIMIT, UTXO_DOMAIN
15
+ from blockchainkit.crypto.core.base import SchnorrSignature
16
+ from blockchainkit.crypto.systems.curves import public_key
17
+ from blockchainkit.crypto.systems.hashing import sha256
18
+ from blockchainkit.crypto.systems.signatures import deterministic_nonce, sign, verify
19
+ from blockchainkit.structures.core.base import Coin, OutPoint
20
+ from blockchainkit.structures.systems.transaction import address
21
+ from blockchainkit.structures.utils.accounts import is_account_id
22
+ from blockchainkit.structures.utils.encoding import canonical_json
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class UTXOTransaction:
27
+ """Spend whole coins and create new ones.
28
+
29
+ Parameters
30
+ ----------
31
+ inputs : tuple of OutPoint
32
+ Coins to consume, each at most once.
33
+ outputs : tuple
34
+ ``(owner_address, amount)`` pairs; amounts are positive integers.
35
+ witnesses : tuple
36
+ One ``(public_key, SchnorrSignature)`` per input, added by
37
+ :meth:`signed`.
38
+ """
39
+
40
+ inputs: tuple[OutPoint, ...]
41
+ outputs: tuple[tuple[str, int], ...]
42
+ witnesses: tuple[tuple[tuple[int, int], SchnorrSignature], ...] = field(default=())
43
+
44
+ def __post_init__(self) -> None:
45
+ if not self.inputs or not self.outputs:
46
+ raise ValueError("a transaction needs inputs and outputs")
47
+ if len(set(self.inputs)) != len(self.inputs):
48
+ raise ValueError("an input cannot be listed twice")
49
+ for owner, amount in self.outputs:
50
+ if not is_account_id(owner):
51
+ raise ValueError("output owners must be account identifiers")
52
+ integer(amount, "amount", 1)
53
+ if amount >= UINT64_LIMIT:
54
+ raise ValueError("amounts must fit unsigned 64-bit integers")
55
+
56
+ def payload(self) -> bytes:
57
+ """Return the bytes each input's owner signs."""
58
+ return canonical_json(
59
+ {
60
+ "domain": UTXO_DOMAIN,
61
+ "inputs": [[i.txid.hex(), i.index] for i in self.inputs],
62
+ "outputs": [list(o) for o in self.outputs],
63
+ }
64
+ )
65
+
66
+ @property
67
+ def txid(self) -> bytes:
68
+ """Return the hash of the unsigned payload; outputs are named by it."""
69
+ return sha256(self.payload())
70
+
71
+ def signed(self, privates: Sequence[int]) -> "UTXOTransaction":
72
+ """Return a copy with one signature per input, keys given in input order.
73
+
74
+ Each witness is the signer's public key and a Schnorr signature over
75
+ :meth:`payload` with an RFC 6979 nonce. Whether the key owns the coin
76
+ is checked by :meth:`UTXOSet.apply`.
77
+ """
78
+ if len(privates) != len(self.inputs):
79
+ raise ValueError("give exactly one private key per input")
80
+ payload = self.payload()
81
+ witnesses = tuple(
82
+ (public_key(x), sign(payload, x, nonce=deterministic_nonce(x, payload)))
83
+ for x in privates
84
+ )
85
+ return replace(self, witnesses=witnesses)
86
+
87
+ def fee(self, utxos: "UTXOSet") -> int:
88
+ """Return inputs minus outputs: what the miner may claim."""
89
+ return sum(utxos[i].amount for i in self.inputs) - sum(a for _, a in self.outputs)
90
+
91
+
92
+ class UTXOSet:
93
+ """An immutable set of unspent coins; applying a transaction returns a new set.
94
+
95
+ Examples
96
+ --------
97
+ >>> import blockchainkit as bk
98
+ >>> alice = bk.structures.address(bk.crypto.public_key(7))
99
+ >>> bk.structures.UTXOSet.genesis({alice: 50}).balance(alice)
100
+ 50
101
+ """
102
+
103
+ def __init__(self, coins: Mapping[OutPoint, Coin]) -> None:
104
+ self._coins = dict(coins)
105
+
106
+ @classmethod
107
+ def genesis(cls, allocations: Mapping[str, int]) -> "UTXOSet":
108
+ """Create one coin per address from an initial allocation."""
109
+ coins = {}
110
+ for index, (owner, amount) in enumerate(sorted(allocations.items())):
111
+ coins[OutPoint(sha256(b"genesis"), index)] = Coin(owner, amount)
112
+ return cls(coins)
113
+
114
+ def __getitem__(self, outpoint: OutPoint) -> Coin:
115
+ return self._coins[outpoint]
116
+
117
+ def __contains__(self, outpoint: object) -> bool:
118
+ return outpoint in self._coins
119
+
120
+ def __len__(self) -> int:
121
+ return len(self._coins)
122
+
123
+ def __repr__(self) -> str:
124
+ return f"UTXOSet(coins={len(self)}, total={sum(c.amount for c in self._coins.values())})"
125
+
126
+ def coins_of(self, owner: str) -> tuple[OutPoint, ...]:
127
+ """Return the outpoints of every coin an address owns, sorted."""
128
+ return tuple(sorted(o for o, coin in self._coins.items() if coin.owner == owner))
129
+
130
+ def balance(self, owner: str) -> int:
131
+ """Return the total value of an address's coins (a wallet's view)."""
132
+ return sum(self._coins[o].amount for o in self.coins_of(owner))
133
+
134
+ def apply(self, tx: UTXOTransaction) -> "UTXOSet":
135
+ """Validate a transaction and return the set after it.
136
+
137
+ Raises
138
+ ------
139
+ ValueError
140
+ An input is spent or unknown, a witness is missing or does not
141
+ match the coin's owner, or outputs exceed inputs.
142
+ """
143
+ if len(tx.witnesses) != len(tx.inputs):
144
+ raise ValueError("every input needs a signature")
145
+ for outpoint, (key, signature) in zip(tx.inputs, tx.witnesses, strict=True):
146
+ if outpoint not in self._coins:
147
+ raise ValueError(f"{outpoint} is not an unspent output")
148
+ owner = self._coins[outpoint].owner
149
+ if address(key) != owner or not verify(tx.payload(), signature, key):
150
+ raise ValueError(f"bad signature for {outpoint}")
151
+ if tx.fee(self) < 0:
152
+ raise ValueError("outputs exceed inputs")
153
+ coins = {o: c for o, c in self._coins.items() if o not in tx.inputs}
154
+ for index, (owner, amount) in enumerate(tx.outputs):
155
+ coins[OutPoint(tx.txid, index)] = Coin(owner, amount)
156
+ return UTXOSet(coins)
@@ -0,0 +1,6 @@
1
+ """Encoding and identifier helpers that support the authenticated data structures."""
2
+
3
+ from blockchainkit.structures.utils.accounts import is_account_id
4
+ from blockchainkit.structures.utils.encoding import canonical_json
5
+
6
+ __all__ = ["canonical_json", "is_account_id"]
@@ -0,0 +1,13 @@
1
+ """Account identifiers: 64 lowercase hex characters, the SHA-256 of a public key."""
2
+
3
+ _HEX = frozenset("0123456789abcdef")
4
+
5
+
6
+ def is_account_id(value: object) -> bool:
7
+ """Return whether ``value`` has the shape of an account identifier.
8
+
9
+ >>> from blockchainkit.structures.utils import is_account_id
10
+ >>> is_account_id("ab" * 32), is_account_id("AB" * 32)
11
+ (True, False)
12
+ """
13
+ return isinstance(value, str) and len(value) == 64 and _HEX.issuperset(value)
@@ -0,0 +1,11 @@
1
+ """Deterministic byte encodings for hashing and signing records."""
2
+
3
+ import json
4
+
5
+
6
+ def canonical_json(value: object) -> bytes:
7
+ """Encode internal integer/string records as sorted compact UTF-8 JSON.
8
+
9
+ This is a package-specific encoding, not a general canonical-JSON standard.
10
+ """
11
+ return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode()
@@ -0,0 +1,13 @@
1
+ """Plotting helpers for blockchainkit.structures.
2
+
3
+ Imports matplotlib, so ``import blockchainkit`` does not load this module;
4
+ import it explicitly: ``from blockchainkit.structures.visualizers import ...``.
5
+ """
6
+
7
+ from blockchainkit.structures.visualizers.plots import (
8
+ plot_block_tree,
9
+ plot_merkle_tree,
10
+ plot_proof_trace,
11
+ )
12
+
13
+ __all__ = ["plot_merkle_tree", "plot_proof_trace", "plot_block_tree"]
@@ -0,0 +1,154 @@
1
+ """Plotting helpers for blockchainkit.structures: Merkle trees, proof traces, block trees."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import matplotlib.pyplot as plt
6
+ from matplotlib.axes import Axes
7
+
8
+ from blockchainkit.structures.core.base import MerkleTrace
9
+ from blockchainkit.structures.systems.chain import Blockchain
10
+ from blockchainkit.structures.systems.merkle import MerkleTree
11
+
12
+ __all__ = ["plot_merkle_tree", "plot_proof_trace", "plot_block_tree"]
13
+
14
+ _PATH, _SIBLING, _OTHER = "#2563eb", "#ea580c", "#cbd5e1"
15
+
16
+
17
+ def plot_merkle_tree(
18
+ tree: MerkleTree, *, highlight: int | None = None, ax: Axes | None = None
19
+ ) -> Axes:
20
+ """Draw every level of a Merkle tree, optionally highlighting one proof.
21
+
22
+ Parameters
23
+ ----------
24
+ tree : MerkleTree
25
+ A tree with at least one leaf.
26
+ highlight : int, optional
27
+ Leaf index whose authentication path (blue) and proof siblings
28
+ (orange) are colored: the siblings are exactly what a proof sends.
29
+ ax : matplotlib.axes.Axes, optional
30
+ Axes to draw on; a new figure is created if omitted.
31
+
32
+ Returns
33
+ -------
34
+ matplotlib.axes.Axes
35
+ """
36
+ if tree.leaf_count == 0:
37
+ raise ValueError("cannot draw an empty tree")
38
+ if ax is None:
39
+ _, ax = plt.subplots(figsize=(max(6, 1.4 * tree.leaf_count), 1.3 * len(tree.levels) + 1))
40
+ width = len(tree.levels[0])
41
+ positions: list[list[float]] = [[i + 0.5 for i in range(width)]]
42
+ for level in tree.levels[1:]:
43
+ below = positions[-1]
44
+ positions.append(
45
+ [(below[2 * i] + below[min(2 * i + 1, len(below) - 1)]) / 2 for i in range(len(level))]
46
+ )
47
+ path = {}
48
+ if highlight is not None:
49
+ tree.proof(highlight) # Validates the index.
50
+ position = highlight
51
+ for depth in range(len(tree.levels)):
52
+ path[(depth, position)] = _PATH
53
+ if (position ^ 1) < len(tree.levels[depth]):
54
+ path[(depth, position ^ 1)] = _SIBLING
55
+ position //= 2
56
+ for depth, level in enumerate(tree.levels):
57
+ for i, digest in enumerate(level):
58
+ x, y = positions[depth][i], depth
59
+ if depth + 1 < len(tree.levels):
60
+ ax.plot([x, positions[depth + 1][i // 2]], [y, y + 1], color="#94a3b8", zorder=1)
61
+ color = path.get((depth, i), _OTHER)
62
+ ink = "black" if color == _OTHER else "white"
63
+ ax.scatter([x], [y], s=900, color=color, zorder=2)
64
+ ax.text(
65
+ x, y, digest.hex()[:6], ha="center", va="center", fontsize=7, color=ink, zorder=3
66
+ )
67
+ top_x = positions[-1][0]
68
+ note = f"root = H(0x02 || count={tree.leaf_count} || top digest)"
69
+ ax.text(top_x, len(tree.levels) - 0.55, note, ha="center", fontsize=9)
70
+ ax.set_ylim(-0.7, len(tree.levels) - 0.2)
71
+ ax.set_xlim(0, width)
72
+ ax.axis("off")
73
+ title = "Merkle tree (digest prefixes)"
74
+ if highlight is not None:
75
+ title += f": proof for leaf {highlight} sends the orange siblings"
76
+ ax.set_title(title)
77
+ return ax
78
+
79
+
80
+ def plot_proof_trace(trace: MerkleTrace, ax: Axes | None = None) -> Axes:
81
+ """Tabulate each step of reconstructing a root from a leaf and its proof.
82
+
83
+ Parameters
84
+ ----------
85
+ trace : MerkleTrace
86
+ From :func:`~blockchainkit.structures.systems.merkle.trace_proof`.
87
+ ax : matplotlib.axes.Axes, optional
88
+ Axes to draw on; a new figure is created if omitted.
89
+
90
+ Returns
91
+ -------
92
+ matplotlib.axes.Axes
93
+ """
94
+ if ax is None:
95
+ _, ax = plt.subplots(figsize=(9, 0.5 * len(trace.steps) + 1.6))
96
+ describe = {
97
+ "left": "hash(sibling || node)",
98
+ "right": "hash(node || sibling)",
99
+ "promoted": "no sibling: promote",
100
+ }
101
+ rows = [["hash(0x00 || leaf)", "", trace.leaf_digest.hex()[:16]]]
102
+ for step in trace.steps:
103
+ sibling = "" if step.sibling is None else step.sibling.hex()[:16]
104
+ rows.append([describe[step.side], sibling, step.digest.hex()[:16]])
105
+ verdict = "matches trusted root" if trace.valid else "does NOT match trusted root"
106
+ rows.append([f"bind leaf count; {verdict}", "", trace.root.hex()[:16]])
107
+ ax.axis("off")
108
+ table = ax.table(
109
+ cellText=rows, colLabels=["step", "sibling", "result"], cellLoc="center", loc="center"
110
+ )
111
+ table.auto_set_font_size(False)
112
+ table.set_fontsize(9)
113
+ table.scale(1, 1.6)
114
+ ax.set_title("A Merkle proof is a recipe for reconstructing the root")
115
+ return ax
116
+
117
+
118
+ def plot_block_tree(chain: Blockchain, ax: Axes | None = None) -> Axes:
119
+ """Draw every stored block by height, one lane per fork, canonical chain highlighted.
120
+
121
+ Parameters
122
+ ----------
123
+ chain : Blockchain
124
+ A chain, possibly holding side forks.
125
+ ax : matplotlib.axes.Axes, optional
126
+ Axes to draw on; a new figure is created if omitted.
127
+
128
+ Returns
129
+ -------
130
+ matplotlib.axes.Axes
131
+ """
132
+ if ax is None:
133
+ _, ax = plt.subplots(figsize=(8, 3))
134
+ canonical = {block.hash for block in chain.canonical_blocks()}
135
+ lane: dict[bytes, int] = {}
136
+ for index, tip in enumerate(chain.tips()):
137
+ block = tip
138
+ while block.hash not in lane:
139
+ lane[block.hash] = index
140
+ if block.height == 0:
141
+ break
142
+ block = chain.blocks[block.previous_hash]
143
+ for digest, block in chain.blocks.items():
144
+ x, y = block.height, -lane[digest]
145
+ if block.height:
146
+ parent = chain.blocks[block.previous_hash]
147
+ ax.plot([parent.height, x], [-lane[parent.hash], y], color="#94a3b8", zorder=1)
148
+ color = _PATH if digest in canonical else _OTHER
149
+ ax.scatter([x], [y], s=700, marker="s", color=color, zorder=2)
150
+ ax.text(x, y, digest.hex()[:4], ha="center", va="center", fontsize=7, zorder=3)
151
+ ax.set_xlabel("height")
152
+ ax.set_yticks([])
153
+ ax.set_title(f"Block tree: canonical chain in blue (work {chain.cumulative_work})")
154
+ return ax
@@ -0,0 +1,60 @@
1
+ """Execution: a deterministic stack machine, its languages, contracts, and attacks."""
2
+
3
+ from blockchainkit.vm.core.base import (
4
+ BusyBeaverResult,
5
+ ExecutionResult,
6
+ Instruction,
7
+ ReentrancyResult,
8
+ ScriptResult,
9
+ TraceStep,
10
+ TuringRun,
11
+ VerificationResult,
12
+ VMError,
13
+ )
14
+ from blockchainkit.vm.systems.assembler import assemble
15
+ from blockchainkit.vm.systems.expressions import compile_expression, to_rpn
16
+ from blockchainkit.vm.systems.programs import batch_transfer, vending_machine
17
+ from blockchainkit.vm.systems.reentrancy import drain_bank
18
+ from blockchainkit.vm.systems.script import (
19
+ encode_public_key,
20
+ encode_signature,
21
+ htlc_locking,
22
+ number,
23
+ p2pkh_locking,
24
+ p2pkh_unlocking,
25
+ verify_script,
26
+ )
27
+ from blockchainkit.vm.systems.stack_machine import execute, validate_program
28
+ from blockchainkit.vm.systems.turing import busy_beaver, enumerate_machines, run_turing_machine
29
+ from blockchainkit.vm.systems.verifier import verify_bytecode
30
+
31
+ __all__ = [
32
+ "BusyBeaverResult",
33
+ "ExecutionResult",
34
+ "Instruction",
35
+ "ReentrancyResult",
36
+ "ScriptResult",
37
+ "TraceStep",
38
+ "TuringRun",
39
+ "VerificationResult",
40
+ "VMError",
41
+ "assemble",
42
+ "compile_expression",
43
+ "to_rpn",
44
+ "batch_transfer",
45
+ "vending_machine",
46
+ "drain_bank",
47
+ "encode_public_key",
48
+ "encode_signature",
49
+ "htlc_locking",
50
+ "number",
51
+ "p2pkh_locking",
52
+ "p2pkh_unlocking",
53
+ "verify_script",
54
+ "execute",
55
+ "validate_program",
56
+ "busy_beaver",
57
+ "enumerate_machines",
58
+ "run_turing_machine",
59
+ "verify_bytecode",
60
+ ]
@@ -0,0 +1,25 @@
1
+ """Instruction type, errors, and result containers."""
2
+
3
+ from blockchainkit.vm.core.base import (
4
+ BusyBeaverResult,
5
+ ExecutionResult,
6
+ Instruction,
7
+ ReentrancyResult,
8
+ ScriptResult,
9
+ TraceStep,
10
+ TuringRun,
11
+ VerificationResult,
12
+ VMError,
13
+ )
14
+
15
+ __all__ = [
16
+ "BusyBeaverResult",
17
+ "ExecutionResult",
18
+ "Instruction",
19
+ "ReentrancyResult",
20
+ "ScriptResult",
21
+ "TraceStep",
22
+ "TuringRun",
23
+ "VerificationResult",
24
+ "VMError",
25
+ ]
@@ -0,0 +1,171 @@
1
+ """Instruction type, errors, and result containers for blockchainkit.vm."""
2
+
3
+ from collections.abc import Mapping
4
+ from dataclasses import dataclass
5
+
6
+ Instruction = tuple[str, int | None]
7
+ """An ``(opcode, operand)`` pair; the operand is ``None`` for simple opcodes."""
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class TraceStep:
12
+ """Machine state just after one executed instruction.
13
+
14
+ Attributes
15
+ ----------
16
+ pc : int
17
+ Index of the instruction that ran.
18
+ opcode : str
19
+ Its opcode.
20
+ operand : int or None
21
+ Its operand.
22
+ stack : tuple of int
23
+ Stack after the instruction, bottom first.
24
+ storage : Mapping
25
+ Working storage after the instruction (committed only on success).
26
+ gas_used : int
27
+ Cumulative gas, including this instruction.
28
+ """
29
+
30
+ pc: int
31
+ opcode: str
32
+ operand: int | None
33
+ stack: tuple[int, ...]
34
+ storage: Mapping[int, int]
35
+ gas_used: int
36
+
37
+
38
+ class VMError(ValueError):
39
+ """Invalid instruction, stack operation, arithmetic operation, or resource limit.
40
+
41
+ Attributes
42
+ ----------
43
+ trace : tuple of TraceStep
44
+ Steps executed before the failure, when ``execute(..., trace=True)``
45
+ was requested; otherwise empty.
46
+ """
47
+
48
+ trace: tuple[TraceStep, ...] = ()
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class ExecutionResult:
53
+ """Final stack, read-only storage, consumed gas, and optional step trace."""
54
+
55
+ stack: tuple[int, ...]
56
+ storage: Mapping[int, int]
57
+ gas_used: int
58
+ trace: tuple[TraceStep, ...] = ()
59
+
60
+
61
+ @dataclass(frozen=True)
62
+ class VerificationResult:
63
+ """Outcome of static bytecode verification.
64
+
65
+ Attributes
66
+ ----------
67
+ max_depth : int
68
+ The largest stack height any path can reach.
69
+ errors : tuple of str
70
+ Every problem found; empty if the program is safe.
71
+ """
72
+
73
+ max_depth: int
74
+ errors: tuple[str, ...]
75
+
76
+ @property
77
+ def ok(self) -> bool:
78
+ """True if no path can underflow and every join agrees on the stack height."""
79
+ return not self.errors
80
+
81
+
82
+ @dataclass(frozen=True)
83
+ class TuringRun:
84
+ """How a Turing machine run ended.
85
+
86
+ Attributes
87
+ ----------
88
+ outcome : str
89
+ ``"halted"``, ``"looping"`` (a configuration repeated, so it never
90
+ halts), or ``"unknown"`` (the step budget ran out first).
91
+ steps : int
92
+ Steps executed, counting the halting transition.
93
+ ones : int
94
+ Number of 1s on the tape at the end.
95
+ """
96
+
97
+ outcome: str
98
+ steps: int
99
+ ones: int
100
+
101
+
102
+ @dataclass(frozen=True)
103
+ class BusyBeaverResult:
104
+ """The longest-running halting machine found by an exhaustive search.
105
+
106
+ Attributes
107
+ ----------
108
+ steps : int
109
+ Its number of steps: the busy-beaver value S if no unknown machine halts later.
110
+ ones : int
111
+ The 1s it leaves on the tape.
112
+ machine : dict
113
+ Its rules, ``(state, symbol) -> (write, move, next_state)``.
114
+ counts : dict
115
+ Machines per outcome: ``"halted"``, ``"looping"``, ``"unknown"``.
116
+ """
117
+
118
+ steps: int
119
+ ones: int
120
+ machine: dict[tuple[str, int], tuple[int, int, str]]
121
+ counts: dict[str, int]
122
+
123
+
124
+ @dataclass(frozen=True)
125
+ class ScriptResult:
126
+ """Outcome of validating a Bitcoin-style script pair.
127
+
128
+ Attributes
129
+ ----------
130
+ valid : bool
131
+ Both scripts ran without error and left a true value on top.
132
+ stack : tuple of bytes
133
+ The final stack, bottom first.
134
+ error : str or None
135
+ Why validation failed, if it did.
136
+ operations : int
137
+ Opcodes and pushes executed. Scripts have no loops, so this never
138
+ exceeds the combined script length.
139
+ """
140
+
141
+ valid: bool
142
+ stack: tuple[bytes, ...]
143
+ error: str | None
144
+ operations: int
145
+
146
+
147
+ @dataclass(frozen=True)
148
+ class ReentrancyResult:
149
+ """What an attacker withdrew from a bank contract.
150
+
151
+ Attributes
152
+ ----------
153
+ withdrawn : int
154
+ Total paid out to the attacker.
155
+ deposited : int
156
+ What the attacker had deposited.
157
+ calls : int
158
+ Times ``withdraw`` was entered, including re-entries.
159
+ bank_balance : int
160
+ Funds left in the bank afterwards.
161
+ """
162
+
163
+ withdrawn: int
164
+ deposited: int
165
+ calls: int
166
+ bank_balance: int
167
+
168
+ @property
169
+ def stolen(self) -> int:
170
+ """Withdrawn beyond the attacker's own deposit."""
171
+ return self.withdrawn - self.deposited
@@ -0,0 +1,40 @@
1
+ """Execution engines and the programs, languages and models built on them."""
2
+
3
+ from blockchainkit.vm.systems.assembler import assemble
4
+ from blockchainkit.vm.systems.expressions import compile_expression, to_rpn
5
+ from blockchainkit.vm.systems.programs import batch_transfer, vending_machine
6
+ from blockchainkit.vm.systems.reentrancy import drain_bank
7
+ from blockchainkit.vm.systems.script import (
8
+ encode_public_key,
9
+ encode_signature,
10
+ htlc_locking,
11
+ number,
12
+ p2pkh_locking,
13
+ p2pkh_unlocking,
14
+ verify_script,
15
+ )
16
+ from blockchainkit.vm.systems.stack_machine import execute, validate_program
17
+ from blockchainkit.vm.systems.turing import busy_beaver, enumerate_machines, run_turing_machine
18
+ from blockchainkit.vm.systems.verifier import verify_bytecode
19
+
20
+ __all__ = [
21
+ "assemble",
22
+ "compile_expression",
23
+ "to_rpn",
24
+ "batch_transfer",
25
+ "vending_machine",
26
+ "drain_bank",
27
+ "encode_public_key",
28
+ "encode_signature",
29
+ "htlc_locking",
30
+ "number",
31
+ "p2pkh_locking",
32
+ "p2pkh_unlocking",
33
+ "verify_script",
34
+ "execute",
35
+ "validate_program",
36
+ "busy_beaver",
37
+ "enumerate_machines",
38
+ "run_turing_machine",
39
+ "verify_bytecode",
40
+ ]